抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Hello world!

Typora

typora11.16有毒,两天没了三次内容,已经写到了前端,不过前端也不想听了,反正cv就完事了

检索服务-页面渲染

吐了,CV

这一块真的是败笔

面包屑导航

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 6 面包屑导航信息
if (param.getAttrs() != null && param.getAttrs().size() > 0) {
List<SearchResultVo.NavVo> navVoList = param.getAttrs().stream().map(attr -> {
// 分析每一个attr
SearchResultVo.NavVo navVo = new SearchResultVo.NavVo();
String[] s = attr.split("_");
// 远程调用product里的方法获得attrName(duck不必)
AttrResponseTo attrResponseTo = productFeignService.attrResponseInfoFeign(Long.valueOf(s[0]));
if (attrResponseTo != null) {
navVo.setNavName(attrResponseTo.getAttrName());
} else {
navVo.setNavName(s[0]);
}
navVo.setNavValue(s[1]);
// 处理连接,如果是当前的查询条件,则去掉
String encode = null;
try {
// 属性内容带空格的会被编码成+号,需要换回
encode = URLEncoder.encode(attr, "UTF-8").replace("+", "%20");
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
String replace = param.get_queryString().replace("&attrs=" + encode, "");
navVo.setLink("http://search.gulimall.com/list.html?" + replace);
return navVo;
}).collect(Collectors.toList());
resultVo.setNavs(navVoList);
}
// 6 品牌面包屑导航信息
if (param.getBrandId() != null && param.getBrandId().size() > 0) {
List<SearchResultVo.NavVo> navs = resultVo.getNavs();
SearchResultVo.NavVo navVo = new SearchResultVo.NavVo();
// 这里老师用远程调用,duck不必,result里都封装好了
navVo.setNavName("品牌");
StringBuilder builder = new StringBuilder();
for (SearchResultVo.BrandVo brand : resultVo.getBrands()) {
builder.append(brand.getBrandName()).append(";");
}
navVo.setNavValue(String.valueOf(builder));
}

异步

初始化线程的 4 种方式

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

方式 1 和方式 2: 主进程无法获取线程的运算结果。 不适合当前场景。方式 3: 主进程可以获取线程的运算结果, 但是不利于控制服务器中的线程资源。 可以导致服务器资源耗尽。方式 4: 通过如下两种方式初始化线程池Executors.newFiexedThreadPool(3); 或者new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit unit, workQueue, threadFactory, handler);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class ThreadTest {

// 单例模式,线程池只有一个
public static ExecutorService service = Executors.newFixedThreadPool(10);

public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("---------- main方法start ----------");
/*
* 1. 继承 Thread
* 2. 实现 Runnable 接口
* 3. 实现 Callable 接口 + FutureTask (可以拿到返回结果, 可以处理异常)
* 4. 线程池
*/

// Thread1 thread1 = new Thread1();
// thread1.start(); // 启动线程

// Runnable1 runnable1 = new Runnable1();
// new Thread(runnable1).start();

// Callable1 callable1 = new Callable1();
// FutureTask<Integer> integerFutureTask = new FutureTask<>(callable1);
// new Thread(integerFutureTask).start();
// // 阻塞等待,只有task执行完之后才能执行下面的语句
// System.out.println(integerFutureTask.get());

// 将所有的多线程异步任务都交给线程池执行,既能控制资源,也稳定
service.submit(new Runnable1());

System.out.println("---------- main方法end ----------");
}

public static class Thread1 extends Thread {
@Override
public void run() {
System.out.println("当前线程ID:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}
}

public static class Runnable1 implements Runnable {

@Override
public void run() {
System.out.println("当前线程ID:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}
}

public static class Callable1 implements Callable<Integer> {
@Override
public Integer call() throws Exception {

System.out.println("当前线程ID:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}
}
}

线程池

线程池种类

  1. newCachedThreadPool
    创建一个可缓存线程池, 如果线程池长度超过处理需要, 可灵活回收空闲线程, 若无可回收, 则新建线程。
    core为0,所有线程可回收
  2. newFixedThreadPool
    创建一个定长线程池, 可控制线程最大并发数, 超出的线程会在队列中等
    core = max,max-core线程可回收
  3. newScheduledThreadPool
    创建一个定长线程池, 支持定时及周期性任务执行。
  4. newSingleThreadExecutor
    创建一个单线程化的线程池, 它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ThreadTest {

// 单例模式,线程池只有一个
public static ExecutorService service = Executors.newFixedThreadPool(10);

public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 七大参数:
* corePoolSize 核心线程数:线程池创建好以后就准备就绪的线程数,一直存在除非allowCoreThreadTimeOut
* maximumPoolSize 最大线程数:控制资源
* keepAliveTime 存活时间:线程空闲时间大于指定时间,释放空闲线程(释放的线程是大于核心线程数的线程)
* unit 时间单位
* BlockingQueue<Runnable> 阻塞队列:如果任务很多,多余任务将放在队列当中,只要有空闲线程,就会从队列中取出任务执行
* threadFactory 线程工厂
* RejectedExecutionHandler 拒绝策略:处理队列满了之后的操作
*
* 工作顺序:
* 1、 线程池创建, 准备好 core 数量的核心线程, 准备接受任务
* 2、 新的任务进来, 用 core 准备好的空闲线程执行。
* (1) 、 core 满了, 就将再进来的任务放入阻塞队列中。 空闲的 core 就会自己去阻塞队列获取任务执行
* (2) 、 阻塞队列满了, 就直接开新线程执行, 最大只能开到 max 指定的数量
* (3) 、 max 都执行好了。 Max-core 数量空闲的线程会在 keepAliveTime 指定的时间后自动销毁。 最终保持到 core 大小
* (4) 、 如果线程数开到了 max 的数量, 还有新任务进来, 就会使用 reject 指定的拒绝策略进行处理
* 3、 所有的线程创建都是由指定的 factory 创建的。
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 200, 10, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

System.out.println("---------- main方法end ----------");
}
}

为什么使用线程池

image-20211114130824911

CompletableFuture 异步编排

image-20211114131606023

创建异步对象

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

1、 runXxxx 都是没有返回结果的, supplyXxx 都是可以获取返回结果的
2、 可以传入自定义的线程池, 否则就用默认的线程池;

image-20211114132155487

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CompletableFutureTest {

// 单例模式,线程池只有一个
public static ExecutorService executor = Executors.newFixedThreadPool(10);

public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("---------- main方法start ----------");

// CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// System.out.println("当前线程ID:" + Thread.currentThread().getId());
// int i = 10 / 2;
// System.out.println("运行结果:" + i);
// }, executor);

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程ID:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}, executor);
System.out.println("future结果:" + future.get());

System.out.println("---------- main方法end ----------");
}

}

计算完成时回调方法、异常处理

whenComplete 可以处理正常和异常的计算结果, exceptionally 处理异常情况。

whenComplete 和 whenCompleteAsync 的区别

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

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

image-20211114132319399

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CompletableFutureTest {

public static ExecutorService executor = Executors.newFixedThreadPool(10);

public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("---------- main方法start ----------");

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程ID:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}, executor).whenComplete((result, exception) -> {
System.out.println("异步任务成功完成了...结果是:" + result + ";异常是:" + exception);
});
System.out.println("future结果:" + future.get());


System.out.println("---------- main方法end ----------");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class CompletableFutureTest {

public static ExecutorService executor = Executors.newFixedThreadPool(10);

public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("---------- main方法start ----------");

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程ID:" + Thread.currentThread().getId());
int i = 10 / 0;
System.out.println("运行结果:" + i);
return i;
}, executor).whenComplete((result, exception) -> {
// 当future执行完之后执行回调函数,可以获得结果与异常
System.out.println("异步任务成功完成了...结果是:" + result + ";异常是:" + exception);
}).exceptionally((exception) -> {
// 用于处理异常
return 10; // 出现异常后,返回默认值
});
System.out.println("future结果:" + future.get());

System.out.println("---------- main方法end ----------");
}

}

handle方法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class CompletableFutureTest {

public static ExecutorService executor = Executors.newFixedThreadPool(10);

public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("---------- main方法start ----------");

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程ID:" + Thread.currentThread().getId());
int i = 10 / 0;
System.out.println("运行结果:" + i);
return i;
}, executor).handle((result, exception) -> {
// 既可以获得也可以处理
System.out.println("异步任务成功完成了...结果是:" + result + ";异常是:" + exception);
if (result != null) {
return result;
}
if (exception != null) {
return 10;
}
return 0;
});
System.out.println("future结果:" + future.get());

System.out.println("---------- main方法end ----------");
}

}

线程串行化方法

image-20211114133526668

thenApply 方法: 当一个线程依赖另一个线程时, 获取上一个任务返回的结果, 并返回当前
任务的返回值。

thenAccept 方法: 消费处理结果。 接收任务的处理结果, 并消费处理, 无返回结果。

thenRun 方法: 只要上面的任务执行完成, 就开始执行 thenRun, 只是处理完任务后, 执行thenRun 的后续操作带有 Async 默认是异步执行的。 同之前。

以上都要前置任务成功完成。

Function<? super T,? extends U>
T: 上一个任务返回结果的类型
U: 当前任务的返回值类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CompletableFutureTest {

public static ExecutorService executor = Executors.newFixedThreadPool(10);

public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("---------- main方法start ----------");

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程ID:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}, executor).thenApplyAsync(result -> {
System.out.println("任务二启动,上一次结果+" + result);
return result + 1;
}, executor);
System.out.println("future结果:" + future.get());

System.out.println("---------- main方法end ----------");
}

}

两任务组合 - 都要完成

image-20211114134407616

image-20211114134411872

两个任务必须都完成, 触发该任务。

thenCombine: 组合两个 future, 获取两个 future 的返回结果, 并返回当前任务的返回值
thenAcceptBoth: 组合两个 future, 获取两个 future 任务的返回结果, 然后处理任务, 没有
返回值。
runAfterBoth: 组合两个 future, 不需要获取 future 的结果, 只需两个 future 处理完任务后,
处理该任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  CompletableFuture<Object> future01 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务1线程:" + Thread.currentThread().getId());
int i = 10 / 4;
System.out.println("任务1结束:" );
return i;
}, executor);

CompletableFuture<Object> future02 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务2线程:" + Thread.currentThread().getId());

try {
Thread.sleep(3000);
System.out.println("任务2结束:" );
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello";
}, executor);

future01.runAfterBothAsync(future02,()->{
System.out.println("任务3开始...");
},executor);
// void accept(T t, U u);
future01.thenAcceptBothAsync(future02,(f1,f2)->{
System.out.println("任务3开始...之前的结果:"+f1+"--》"+f2);
},executor);
//R apply(T t, U u);
CompletableFuture<String> future = future01.thenCombineAsync(future02, (f1, f2) -> {
return f1 + ":" + f2 + " -> Haha";
}, executor);

两任务组合 - 一个完成

image-20211114134431864

image-20211114134436669

当两个任务中, 任意一个 future 任务完成的时候, 执行任务。

applyToEither: 两个任务有一个执行完成, 获取它的返回值, 处理任务并有新的返回值。
acceptEither: 两个任务有一个执行完成, 获取它的返回值, 处理任务, 没有新的返回值。
runAfterEither: 两个任务有一个执行完成, 不需要获取 future 的结果, 处理任务, 也没有返
回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
     /**
* 两个任务,只要有一个完成,我们就执行任务3
* runAfterEitherAsync:不感知结果,自己没有返回值
* acceptEitherAsync:感知结果,自己没有返回值
* applyToEitherAsync:感知结果,自己有返回值
*/
// future01.runAfterEitherAsync(future02,()->{
// System.out.println("任务3开始...之前的结果:");
// },executor);
//void accept(T t);
// future01.acceptEitherAsync(future02,(res)->{
// System.out.println("任务3开始...之前的结果:"+res);
// },executor);
// CompletableFuture<String> future = future01.applyToEitherAsync(future02, res -> {
// System.out.println("任务3开始...之前的结果:" + res);
// return res.toString() + "->哈哈";
// }, executor);

多任务组合

image-20211114134505324

allOf: 等待所有任务完成
anyOf: 只要有一个任务完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {
System.out.println("查询商品的图片信息");
return "hello.jpg";
},executor);

CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {
System.out.println("查询商品的属性");
return "黑色+256G";
},executor);

CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
System.out.println("查询商品介绍");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "华为";
},executor);

// CompletableFuture<Void> allOf = CompletableFuture.allOf(futureImg, futureAttr, futureDesc);
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(futureImg, futureAttr, futureDesc);
anyOf.get();//等待所有结果完成

// System.out.println("main....end...."+futureImg.get()+"=>"+futureAttr.get()+"=>"+futureDesc.get());
System.out.println("main....end...."+anyOf.get());

}

前台 - 商品详情

环境搭建

静态资源

存放到nginx -> html -> static 下对应文件夹

html放到对应模块resources -> templates下

host

192.168.128.129 item.gulimall.com

nacos

之前配置的*.gulimall.com即可

gateway

1
2
3
4
5
6
7
8
9
# -----------------------nginx,一定要放在最后,要不然/api/下的配置会被覆盖-----------------------
- id: gulimall_host_route
uri: lb://gulimall-product
predicates:
- Host=gulimall.com,item.gulimall.com
- id: gulimall_search_route
uri: lb://gulimall-search
predicates:
- Host=search.gulimall.com

页面基本跳转

1
2
3
4
<!-- |内可直接放字符串| -->
<a th:href="|http://item.gulimall.com/${product.skuId}.html|">
<img th:src="${product.skuImg}" class="dim">
</a>

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
public class ItemController {

@Autowired
SkuInfoService skuInfoService;

/*
* 展示当前对应skuId的信息
*/
@GetMapping("/{skuId}.html")
public String skuItem(@PathVariable("skuId") Long skuId, Model model) {
SkuItemVo skuItemVo = skuInfoService.getItemInfo(skuId);
model.addAttribute("item", skuItemVo);
return "item";
}
}

SkuItemVo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Data
public class SkuItemVo {
// 1 获取sku基本信息,pms_sku_info
SkuInfoEntity info;
boolean hasStock = true;
// 2 sku图片 pms_sku_images
List<SkuImagesEntity> images;
// 3 获得spu销售属性组合
List<SkuItemSaleAttrVo> saleAttr;
// 4 spu销售属性介绍
SpuInfoDescEntity desp;
// 5 spu规格参数
List<SpuItemAttrGroupVo> groupAttrs;

@Data
public static class SkuItemSaleAttrVo {
// 属性id
private Long attrId;
// 属性名
private String attrName;
// 属性值
private List<AttrValueWithSkuIdVo> attrValues;
}

@Data
public static class SpuItemAttrGroupVo {
private String groupName;
private List<SpuItemBaseAttrVo> attrs;
}

@Data
public static class SpuItemBaseAttrVo {
private String attrName;
private String attrValue;
}

@Data
public static class AttrValueWithSkuIdVo {
private String attrValue;
private String skuIds;
}

}

封装基本信息

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Service("skuInfoService")
public class SkuInfoServiceImpl extends ServiceImpl<SkuInfoDao, SkuInfoEntity> implements SkuInfoService {
/*
* 获取skuInfo,用于详情页展示
*/
@Override
public SkuItemVo getItemInfo(Long skuId) {
SkuItemVo skuItemVo = new SkuItemVo();

// 1 获取sku基本信息,pms_sku_info
SkuInfoEntity skuInfoEntity = getById(skuId);
skuItemVo.setInfo(skuInfoEntity);
Long spuId = skuInfoEntity.getSpuId();
Long catalogId = skuInfoEntity.getCatalogId();

// 2 sku图片 pms_sku_images
List<SkuImagesEntity> skuImagesEntityList = skuImagesService.getBySkuId(skuId);
skuItemVo.setImages(skuImagesEntityList);

// 3 获得spu销售属性组合

// 4 spu销售属性介绍 spu_info_desc
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(spuId);
skuItemVo.setDesp(spuInfoDescEntity);

// 5 spu规格参数
return skuItemVo;
}
}

封装spu规格参数

service

1
2
3
// 5 spu规格参数
List<SkuItemVo.SpuItemAttrGroupVo> spuItemAttrGroupVoList = attrGroupService.getGroupWithAttrForSkuItem(spuId, catalogId);
skuItemVo.setGroupAttrs(spuItemAttrGroupVoList);
1
2
3
4
5
6
7
8
@Service("attrGroupService")
public class AttrGroupServiceImpl extends ServiceImpl<AttrGroupDao, AttrGroupEntity> implements AttrGroupService {
@Override
public List<SkuItemVo.SpuItemAttrGroupVo> getWithAttrForSkuItem(Long spuId, Long catalogId) {
return baseMapper.getWithAttrForSkuItem(spuId, catalogId);
}

}

mapper

联表查询,自定义结果集,内部类使用$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.gulimall.product.dao.AttrGroupDao">
<!-- resultMap 指定返回的结果集及其中元素 -->
<resultMap id="spuItemAttrGroupVo" type="com.atguigu.gulimall.product.vo.front.SkuItemVo$SpuItemAttrGroupVo">
<result property="groupName" column="attr_group_name" />
<collection property="attrs" ofType="com.atguigu.gulimall.product.vo.front.SkuItemVo$SpuItemBaseAttrVo">
<result property="attrName" column="attr_name" />
<result property="attrValue" column="attr_value" />
</collection>
</resultMap>
<select id="getWithAttrForSkuItem" resultMap="spuItemAttrGroupVo">
select ag.attr_group_id,
ag.attr_group_name,
aar.attr_id,
pav.attr_name,
pav.attr_value
from gulimall_pms.pms_attr_group ag
left join gulimall_pms.pms_attr_attrgroup_relation aar on aar.attr_group_id = ag.attr_group_id
left join gulimall_pms.pms_product_attr_value pav on pav.attr_id = aar.attr_id
where ag.catelog_id = #{catalogId}
and pav.spu_id = #{spuId};
</select>
</mapper>

封装spu销售属性

service

1
2
3
// 3 获得spu销售属性组合
List<SkuItemVo.SkuItemSaleAttrVo> skuItemSaleAttrVoList = skuSaleAttrValueService.getAttrBySpuId(skuId);
skuItemVo.setSaleAttr(skuItemSaleAttrVoList);
1
2
3
4
5
6
7
8
@Service("skuSaleAttrValueService")
public class SkuSaleAttrValueServiceImpl extends ServiceImpl<SkuSaleAttrValueDao, SkuSaleAttrValueEntity> implements SkuSaleAttrValueService {
@Override
public List<SkuItemVo.SkuItemSaleAttrVo> getAttrBySpuId(Long spuId) {
return baseMapper.getAttrBySpuId(spuId);
}

}

mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.gulimall.product.dao.SkuSaleAttrValueDao">
<resultMap id="skuItemSaleAttrVo" type="com.atguigu.gulimall.product.vo.front.SkuItemVo$SkuItemSaleAttrVo">
<result property="attrId" column="attr_id" />
<result property="attrName" column="attr_name" />
<collection property="attrValues" ofType="com.atguigu.gulimall.product.vo.front.SkuItemVo$AttrValueWithSkuIdVo">
<result property="skuIds" column="sku_ids" />
<result property="attrValue" column="attr_value" />
</collection>
</resultMap>
<select id="getAttrBySpuId" resultMap="skuItemSaleAttrVo">
select sav.attr_id,
sav.attr_name,
sav.attr_value,
group_concat(distinct si.sku_id) sku_ids
from gulimall_pms.pms_sku_info si
left join gulimall_pms.pms_sku_sale_attr_value sav
on sav.sku_id = si.sku_id
where spu_id = #{spuId}
group by sav.attr_id,
sav.attr_name,
sav.attr_value
</select>
</mapper>

这里最好起别名,要不然可能会封装不到

前端

自己看

异步编排优化

外部配置

就是可以在配置文件中配置如下的属性

1
2
3
4
5
6
7
8
9
// 需要引入依赖spring-boot-configuration-processor
@ConfigurationProperties(prefix = "gulimall.thread") // 外部化配置
@Component
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}

线程池配置

1
2
3
4
5
6
7
8
9
10
11
@Configuration
// @EnableConfigurationProperties(ThreadPoolConfigProperties.class) //开启属性配置,这个注解和配置类的@Component注解二选一
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
return new ThreadPoolExecutor(
pool.getCoreSize(), pool.getMaxSize(), pool.getKeepAliveTime(), TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100000),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
}
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Override
public SkuItemVo getItemInfo(Long skuId) throws ExecutionException, InterruptedException {
SkuItemVo skuItemVo = new SkuItemVo();

// 任务1,supplyAsync有返回值
CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
// 1 获取sku基本信息,pms_sku_info
SkuInfoEntity skuInfoEntity = getById(skuId);
skuItemVo.setInfo(skuInfoEntity);
return skuInfoEntity;
}, executor);

// 一下三个任务等任务1完成后才可执行,而且是并行的
CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((skuInfoEntity) -> {
// 3 获得spu销售属性组合
List<SkuItemVo.SkuItemSaleAttrVo> skuItemSaleAttrVoList = skuSaleAttrValueService.getAttrBySpuId(skuInfoEntity.getSpuId());
System.out.println(JSON.toJSONString(skuItemSaleAttrVoList));
skuItemVo.setSaleAttr(skuItemSaleAttrVoList);
}, executor);

CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync((skuInfoEntity) -> {
// 4 spu销售属性介绍 spu_info_desc
SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(skuInfoEntity.getSpuId());
skuItemVo.setDesp(spuInfoDescEntity);
}, executor);

CompletableFuture<Void> groupAttrFuture = infoFuture.thenAcceptAsync((skuInfoEntity) -> {
// 5 spu规格参数
List<SkuItemVo.SpuItemAttrGroupVo> spuItemAttrGroupVoList = attrGroupService.getGroupWithAttrForSkuItem(skuInfoEntity.getSpuId(), skuInfoEntity.getCatalogId());
skuItemVo.setGroupAttrs(spuItemAttrGroupVoList);
}, executor);

// 任务2,该任务和任务1无关,所以和任务1并行即可,runAsync无返回值
CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
// 2 sku图片 pms_sku_images
List<SkuImagesEntity> skuImagesEntityList = skuImagesService.getBySkuId(skuId);
skuItemVo.setImages(skuImagesEntityList);
}, executor);

// 所有任务都完成后才返回
CompletableFuture.allOf(saleAttrFuture, descFuture, groupAttrFuture, imageFuture).get();
System.out.println("商品详情结果:" + JSON.toJSONString(skuItemVo));
return skuItemVo;
}

评论




🧡💛💚💙💜🖤🤍