第19天-商品详情页,线程与异步编排,页面静态化改造
创始人
2025-05-28 05:20:46
0

1.商品详情

在这里插入图片描述



1.1.搭建域名环境

域名映射

192.168.139.10 item.gmall.com

网关配置

- id: gmall_host_routeuri: lb://gmall-productpredicates:- Host=gmall.com, item.gmall.com

Nginx静态资源

在这里插入图片描述

模板页面

  • 模板页面 item.html 放入到 gmall-product 商品服务的 templates 目录
  • 静态资源路径加前置 /static/item/


1.2.商品详情VO模型抽取

SkuItemVO

package com.atguigu.gmall.product.vo;import com.atguigu.gmall.product.entity.SkuImagesEntity;
import com.atguigu.gmall.product.entity.SkuInfoEntity;
import com.atguigu.gmall.product.entity.SpuInfoDescEntity;
import lombok.Data;import java.util.List;/*** sku详情页 {@link SkuItemVO}** @author zhangwen* @email: 1466787185@qq.com*/
@Data
public class SkuItemVO {/*** sku基本信息*/private SkuInfoEntity skuInfo;/*** 是否有货*/private Boolean hasStock;/*** sku图片信息*/private List images;/*** spu销售属性组合*/private List saleAttrs;/*** spu介绍*/private SpuInfoDescEntity spuDesc;/*** spu规格参数*/private List groupAttrs;
}
/*** 销售属性 {@link SkuSaleAttrVO}** @author zhangwen* @email: 1466787185@qq.com*/
@Data
public class SkuSaleAttrVO {private Long attrId;private String attrName;private List attrValues;
}
/*** 规格参数分组 {@link SpuAttrGroupVO}** @author zhangwen* @email: 1466787185@qq.com*/
@Data
public class SpuAttrGroupVO {private String groupName;private List attrs;
}
/*** sku属性组合 {@link AttrValueWithSkuIdVO}** @author zhangwen* @email: 1466787185@qq.com*/
@Data
public class AttrValueWithSkuIdVO {private String attrValue;private String skuIds;
}


1.3.后台接口实现

ItemController

package com.atguigu.gmall.product.web;import com.atguigu.gmall.product.service.SkuInfoService;
import com.atguigu.gmall.product.vo.SkuItemVO;
import com.yuandengta.gmall.product.service.SkuInfoService;
import com.yuandengta.gmall.product.vo.SkuItemVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;/*** 商品详情页 {@link ItemController}** @author zhangwen* @email: 1466787185@qq.com*/
@Controller
public class ItemController {@Autowiredprivate SkuInfoService skuInfoService;/*** sku详情页* @param skuId* @return*/@GetMapping("/{skuId}.html")public String item(@PathVariable("skuId") Long skuId, Model model) {SkuItemVO skuItemVO = skuInfoService.item(skuId);model.addAttribute("skuItemVO", skuItemVO);return "item";}
}

SkuInfoServiceImpl

 /*** sku详情页* @param skuId* @return*/@Overridepublic SkuItemVO item(Long skuId) {SkuItemVO skuItemVO = new SkuItemVO();//1.sku基本信息 pms_sku_infoSkuInfoEntity skuInfo = getById(skuId);skuItemVO.setSkuInfo(skuInfo);Long spuId = skuInfo.getSpuId();Long catalogId = skuInfo.getCatalogId();//TODO 查询库存skuItemVO.setHasStock(true);//2.sku图片信息 pms_sku_imagesList images = skuImagesService.getImagesBySkuId(skuId);skuItemVO.setImages(images);//3.spu销售属性组合List saleAttrs = skuSaleAttrValueService.getSaleAttrsBySpuId(spuId);skuItemVO.setSaleAttrs(saleAttrs);//4.spu商品介绍SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(spuId);skuItemVO.setSpuDesc(spuInfoDescEntity);//5.spu规格参数List groupAttrs = attrGroupService.getAttrGroupWithAttrsBySpuId(catalogId, spuId);skuItemVO.setGroupAttrs(groupAttrs);return skuItemVO;}


1.4.商品详情页面渲染

  • sku图片
  • sku标题
  • sku价格
  • sku销售属性组合
  • 商品介绍
  • 规格与包装


2.Thread 线程



2.1.初始化线程的4种方式

  • 继承 Thread
  • 实现 Runnable 接口
  • 实现 Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)
  • 线程池 ExecutorService

方式1和方式2:主进程无法获取线程的运算结果。

方式3:主进程可以获取线程的运算结果,但是不利于控制服务器中的线程资源。可能导致服务器资源耗尽。



2.2.创建线程池


2.2.1.两种方式初始化线程池

Executors 工具类

Executors.newFiexedThreadPool(8);

ThreadPoolExecutor

/**
* corePoolSize 核心线程数
* maximumPoolSize 最大线程数,控制资源
* keepAliveTime 存活时间,当前线程数大于核心线程数,释放空闲线程
* TimeUnit 存活时间单位
* workQueue 阻塞队列(BlockingQueue),存放任务队列,只要有空闲线程就去队列取出继续执行
* threadFactory 线程创建工厂
* handler 如果队列满了,按照指定的拒绝策略拒绝执行任务
*/
new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler);new ThreadPoolExecutor(8,100,10,TimeUnit.SECONDS,new LinkedBlockingDeque<>(10000),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()
)

通过线程池性能稳定,也可以获取执行结果,并捕获异常。但是,在业务复杂情况下,一个异步调用可能会依赖于另一个异步调用的执行结果

使用 CompletableFuture 异步编排来解决异步调用依赖问题


2.2.2.线程池运行流程

  1. 线程池创建好,准备好core数量的核心线程,准备接受任务
  2. 新的任务进来,用core准备好的空闲线程执行
    1. core满了,就将再进来的任务放入阻塞队列中,空闲的core就会自己去阻塞队列获取任务执行
    2. 阻塞队列满了,就直接开新线程执行,最大只能开到max指定的数量
    3. max满了,max-core数量空闲的线程会在KeepAliveTime指定的时间后自动销毁,最终保持到core大小
    4. 如果线程数开到了max的数量,还有新任务进来,就会使用reject指定的拒绝策略进行处理
  3. 所有的线程创建都是由指定的factory 创建的

面试:一个线程池 core:7,max:20,queue:50,100个并发进来怎么分配?

先有7个能直接得到执行,接下来50个进入队列排队,再多开13个线程继续执行,现在就有70个被分配上了,剩下的30个默认拒绝策略。

阿里面试:如果给你8G内存,500G固态硬盘,双CPU四核的配置,现在有100个用户访问你的系 统,请你设计一下你刚刚说的那些线程池参数。

为了说明合理设置的条件,我们首先确定有以下几个相关参数:
1.tasks,程序每秒需要处理的最大任务数量(假设系统每秒任务数为100~1000)
2.tasktime,单线程处理一个任务所需要的时间(每个任务耗时0.1秒)
3.responsetime,系统允许任务最大的响应时间(每个任务的响应时间不得超过2秒)

corePoolSize:核心线程数 每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即taskstasktime个线程数。假设系统每秒任务数为100~1000,每个任务
耗时0.1秒,则需要1000.1至10000.1,即10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下任务数小于200,最多时为1000,则corePoolSize可设置为20

queueCapacity:任务队列的长度:任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为 (corePoolSize/tasktime)responsetime: (20/0.1)2=400,即队列长度可设置为400。
如果队列长度设置过大,会导致任务响应时间过长,如以下写法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也
不会增加,当任务数量陡增时,任务响应时间也将随之陡增。

maxPoolSize:最大线程数
当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒
200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)X(20/200),即60个线程,可将maxPoolSize设置为60。

keepAliveTime:线程存活时间
线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到
keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。
keepAliveTiime设定值可根据任务峰值持续时间来设定。

以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50
时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒
1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)。

应用是IO密集型和CPU密集型
CPU繁忙时间占比越高,设置线程数越少,CPU繁忙时间占比越低,设置线程数越多(当然也有限 度)。


2.2.3.常见的4种线程池

  • newCachedThreadPool

    创建一个可缓存线程池,若线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

  • newFixedThreadPool

    创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

  • newScheduledThreadPool

    创建一个定长线程池,支持定时及周期性任务执行

  • newSingleThreadExecutor

    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务


2.2.4.线程池好处

  • 降低资源的消耗

    通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗

  • 提高响应速度

    因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行

  • 提高线程的可管理性

    线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配

PS:在使用多线程之前要确认一点,是否需要使用多线程,因为多线程会带来切换的开销,如果不断切换上下文的开销损耗和程序新增的复杂度大于并行带来的受益,那么是不建议使用多线程的。



3.CompletableFuture 异步编排



3.1.业务场景

查询商品详情页的逻辑比较复杂,有些数据还需要远程调用,必然需要花费更多的时间。

  • 获取sku的基本信息:0.5s
  • 获取sku的图片信息:0.5s
  • 获取sku的促销信息:1s
  • 获取spu的所有销售属性:1s
  • 获取规格参数组及组下的规格参数:1.5s
  • spu详情:1s

假如商品详情页的每个查询,需要以上标注的时间才能完成,那么,用户需要5.5s后才能看到商品详情页的内容,很显然是不能接受的。如果有多个线程同时完成这6步操作,也许只需要1.5s即可完成响应。



3.2.CompletableFuture介绍

Future 是Java 5添加的类,用来描述一个异步计算的结果。你可以使用 isDone 方法检查计算是否完成,或者使用 get 阻塞住调用线程,直到计算完成返回结果,你也可以使用 cancel 方法停止任务的执行。

虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?

很多语言,比如Node.js,采用回调的方式实现异步编程。Java的一些框架,比如Netty,自己扩展了Java的 Future 接口,提供了 addListener 等多个扩展方法;Google guava也提供了通用的扩展Future;Scala也提供了简单易用且功能强大的Future/Promise异步编程模式。

作为正统的Java类库,是不是应该做点什么,加强一下自身库的功能呢?

在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。

CompletableFuture类实现了Future接口,所以你还是可以像以前一样通过 get 方法阻塞或者轮询的方式获得结果,但是这种方式不推荐使用。

CompletableFuture和FutureTask同属于Future接口的实现类,都可以获取线程的执行结果。

在这里插入图片描述



3.3.创建异步对象

CompletableFuture 提供了四个静态方法来创建一个异步操作。

static CompletableFuture runAsync(Runnable runnable)
public static CompletableFuture runAsync(Runnable runnable, Executor executor)
public static  CompletableFuture supplyAsync(Supplier supplier)
public static  CompletableFuture supplyAsync(Supplier supplier, Executor executor)

没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。

  • runAsync方法不支持返回值
  • supplyAsync可以支持返回值


3.4.计算完成时回调方法

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

public CompletableFuture whenComplete(BiConsumer action);
public CompletableFuture whenCompleteAsync(BiConsumer action);
public CompletableFuture whenCompleteAsync(BiConsumer action, Executor executor);
public CompletableFuture exceptionally(Function fn);
  • whenComplete可以处理正常和异常的计算结果
  • exceptionally处理异常情况
  • BiConsumer可以定义处理业务

whenComplete 和 whenCompleteAsync 的区别:

  • whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
  • whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。

方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)


代码示例:

public class CompletableFutureDemo {public static void main(String[] args) throws ExecutionException,InterruptedException {CompletableFuture future = CompletableFuture.supplyAsync(new Supplier() {@Overridepublic Object get() {System.out.println(Thread.currentThread().getName() + "\t completableFuture");int i = 10 / 0;return 1024;}}).whenComplete(new BiConsumer() {@Overridepublic void accept(Object o, Throwable throwable) {System.out.println("————————o=" + o.toString());System.out.println("————————throwable=" + throwable);}}).exceptionally(new Function() {@Overridepublic Object apply(Throwable throwable) {System.out.println("throwable=" + throwable);return 666;}});System.out.println(future.get()); //666}
}
 


3.5.handle方法

public  CompletableFuture handle(BiFunction fn);
public  CompletableFuture handleAsync(BiFunction fn);
public  CompletableFuture handleAsync(BiFunction fn, Executor executor);

和CompletableFuture一样,可对结果做最后处理(可处理异常),可改变返回值。



3.6.线程串行化方法

public  CompletableFuture thenApply(Function fn)
public  CompletableFuture thenApplyAsync(Function fn)
public  CompletableFuture thenApplyAsync(Function fn, Executor executor)
public CompletionStage thenAccept(Consumer action);
public CompletionStage thenAcceptAsync(Consumer action);
public CompletionStage thenAcceptAsync(Consumer action,Executor executor);
public CompletionStage thenRun(Runnable action);
public CompletionStage thenRunAsync(Runnable action);
public CompletionStage thenRunAsync(Runnable action,Executor executor);
  • thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值
  • thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果
  • thenRun方法:只要上面的任务执行完成,就开始执行thenRun,只是处理完任务,执行thenRun的后续操作
  • 带有Async默认是异步执行的,这里所谓的异步指的是不在当前线程内执行

Function
  • T:上一个任务返回结果的类型
  • U:当前任务的返回值类型

代码演示:

public static void main(String[] args) throws ExecutionException,InterruptedException {CompletableFuture future = CompletableFuture.supplyAsync(new Supplier() {@Overridepublic Integer get() {System.out.println(Thread.currentThread().getName() + "\t completableFuture");//int i = 10 / 0;return 1024;}}).thenApply(new Function() {@Overridepublic Integer apply(Integer o) {System.out.println("thenApply方法,上次返回结果:" + o);return o * 2;}}).whenComplete(new BiConsumer() {@Overridepublic void accept(Integer o, Throwable throwable) {System.out.println("————————o=" + o);System.out.println("————————throwable=" + throwable);}}).exceptionally(new Function() {@Overridepublic Integer apply(Throwable throwable) {System.out.println("throwable=" + throwable);return 666;}});System.out.println(future.get());
}


3.7.多任务组合

public static CompletableFuture allOf(CompletableFuture... cfs);
public static CompletableFuture anyOf(CompletableFuture... cfs);
 
  • allOf:等待所有任务完成
  • anyOf:只要有一个任务完成

代码示例:

public static void main(String[] args) {List futures = Arrays.asList(CompletableFuture.completedFuture("hello"),CompletableFuture.completedFuture(" world!"),CompletableFuture.completedFuture(" hello"),CompletableFuture.completedFuture("java!"));final CompletableFuture allCompleted = CompletableFuture.allOf(futures.toArray(new CompletableFuture[]{}));allCompleted.thenRun(() -> {futures.stream().forEach(future -> {try {System.out.println("get future at:"+System.currentTimeMillis()+", result:"+future.get());} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}});});
}

测试结果:

get future at:1568892339473, result:hello
get future at:1568892339473, result: world!
get future at:1568892339473, result: hello
get future at:1568892339473, result:java!

几乎同时完成任务!



4.商品详情优化



4.1.优化方式

  • 代码优化(多线程与异步编排)
  • 页面静态化


4.2.代码优化

使用多线程及以异步编排来优化商品详情接口实现逻辑

/*** 商品详情* @param skuId* @return*/
@Override
public SkuItemVO item(Long skuId) throws Exception {SkuItemVO skuItemVO = new SkuItemVO();// 异步编排CompletableFuture skuInfoFuture = CompletableFuture.supplyAsync(() -> {// 1.sku基本信息 pms_sku_infoSkuInfoEntity skuInfo = getById(skuId);skuItemVO.setSkuInfo(skuInfo);return skuInfo;}, executor);CompletableFuture saleAttrFuture = skuInfoFuture.thenAcceptAsync((res) -> {// 2.spu销售属性组合List saleAttrs = skuSaleAttrValueService.getSaleAttrsBySpuId(res.getSpuId());skuItemVO.setSaleAttrs(saleAttrs);}, executor);CompletableFuture descFuture = skuInfoFuture.thenAcceptAsync((res) -> {// 3.spu商品介绍SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());skuItemVO.setSpuDesc(spuInfoDescEntity);}, executor);CompletableFuture baseAttrFuture = skuInfoFuture.thenAcceptAsync((res) -> {// 4.spu规格参数List groupAttrs = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getCatalogId(), res.getSpuId());skuItemVO.setGroupAttrs(groupAttrs);}, executor);CompletableFuture imageFuture = CompletableFuture.runAsync(() -> {// 5.sku图片信息 pms_sku_imagesList images = skuImagesService.getImagesBySkuId(skuId);skuItemVO.setImages(images);}, executor);try {//等待所有任务执行完CompletableFuture.allOf(saleAttrFuture, descFuture, baseAttrFuture,imageFuture).get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}// TODO 查询库存skuItemVO.setHasStock(true);return skuItemVO;
}


5.页面静态化



5.1.简介


5.1.1.问题分析

现在,我们的页面是通过Thymeleaf模板引擎渲染后返回到客户端。在后台需要大量的数据查询,而后渲染得到HTML页面。会对数据库造成压力,并且请求的响应时间过长,并发能力不高。

大家能想到什么办法来解决这个问题?

首先我们能想到的就是缓存技术,比如之前学习过的Redis。不过Redis适合数据规模比较小的情况。假如数据量比较大,例如我们的商品详情页。每个页面如果10kb,100万商品,就是10GB空间,对内存占用比较大。此时就给缓存系统带来极大压力,如果缓存崩溃,接下来倒霉的就是数据库了。

所以缓存并不是万能的,某些场景需要其它技术来解决,比如静态化。

5.1.2.什么是静态化

静态化是指把动态生成的HTML页面变为静态内容保存,以后用户的请求到来,直接访问静态页面,不再经过服务的渲染。

而静态的HTML页面可以部署在nginx中,从而大大提高并发能力,减小tomcat压力。

5.1.3.如何实现静态化

目前,静态化页面都是通过模板引擎来生成,而后保存到nginx服务器来部署。常用的模板引擎比如:

  • Freemarker
  • Velocity
  • Thymeleaf

我们之前就使用的Thymeleaf,来渲染html返回给用户。Thymeleaf除了可以把渲染结果写入
Response,也可以写到本地文件,从而实现静态化。



5.2.Thymeleaf实现静态化


5.2.1.概念

先说下Thymeleaf中的几个概念:

  • Context:运行上下文
  • TemplateResolver:模板解析器
  • TemplateEngine:模板引擎

Context

上下文: 用来保存模型数据,当模板引擎渲染时,可以从Context上下文中获取数据用于渲染。

当与SpringBoot结合使用时,我们放入Model的数据就会被处理到Context,作为模板渲染的数据使用。

TemplateResolver

模板解析器:用来读取模板相关的配置,例如:模板存放的位置信息,模板文件名称,模板文件的类型等等。

当与SpringBoot结合时,TemplateResolver已经由其创建完成,并且各种配置也都有默认值,比如模板存放位置,其默认值就是:templates。比如模板文件类型,其默认值就是html。

TemplateEngine

模板引擎:用来解析模板的引擎,需要使用到上下文、模板解析器。分别从两者中获取模板中需要的数据,模板文件。然后利用内置的语法规则解析,从而输出解析后的文件。来看下模板引擎进行处理的函数:

templateEngine.process(“模板名”, context, writer);

三个参数:

  • 模板名称
  • 上下文:里面包含模型数据
  • writer:输出目的地的流

在输出时,我们可以指定输出的目的地,如果目的地是Response的流,那就是网络响应。如果目的地是本地文件,那就实现静态化了。

而在SpringBoot中已经自动配置了模板引擎,因此我们不需要关心这个。现在我们做静态化,就是把输出的目的地改成本地文件即可!

5.2.2.具体实现

/*** 创建静态页面* @param skuId*/
@Override
public void createHtml(Long skuId) {executor.submit(() -> {PrintWriter writer = null;try {SkuItemVO skuItemVO = item(skuId);Context context = new Context();context.setVariable("skuItemVO", skuItemVO);File file = new File("C:\\" + skuId + ".html");writer = new PrintWriter(file);templateEngine.process("item", context, writer);} catch (Exception e) {log.error("页面静态化出错:{}", e, skuId);} finally {if (writer != null) {writer.close();}}});
}

注意:生成html 的代码不能对用户请求产生影响,所以这里我们使用额外的线程进行异步创建。

5.2.3.什么时候创建静态文件

我们编写好了创建静态文件的service,那么问题来了:什么时候去调用它呢?

想想这样的场景:

假如大部分的商品都有了静态页面。那么用户的请求都会被nginx拦截下来,根本不会到达 gmall-product 服务。只有那些还没有页面的请求,才可能会到达这里。

因此,如果请求到达了这里,我们除了返回页面视图外,还应该创建一个静态页面,那么下次就不会再访问 gmall-product 服务的业务处理方法了。

所以,我们在 ItemController 中添加逻辑,去生成静态html文件:

/*** sku详情页* @param skuId* @return*/
@GetMapping("/{skuId}.html")
public String item(@PathVariable("skuId") Long skuId, Model model) {SkuItemVO skuItemVO = skuInfoService.item(skuId);model.addAttribute("skuItemVO", skuItemVO);//创建静态页面skuInfoService.createHtml(skuId);return "item";
}

5.3.Nginx代理静态页面

接下来,我们修改nginx,让它对商品请求进行监听,指向本地静态页面,如果本地没找到,才进行反向代理:

server {listen 80;server_name *.gmall.com gmall.com;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Server $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;location /static/ {root /usr/share/nginx/html;}location /item {# 先找本地root /usr/share/nginx/html;if (!-f $request_filename) { #请求的文件不存在,就反向代理proxy_pass http://gmall;break;}}location / {proxy_pass http://gmall;proxy_connect_timeout 600;proxy_read_timeout 600;}
}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...