diff --git a/dev-protocol-springcloud/NetflixHystrix.md b/dev-protocol-springcloud/NetflixHystrix.md
new file mode 100644
index 0000000..1609f5e
--- /dev/null
+++ b/dev-protocol-springcloud/NetflixHystrix.md
@@ -0,0 +1,29 @@
+## SpringCloud Netflix Hystrix
+
+
+### SpringCloud Netflix Hystrix 概览
+
+
+### 使用注解方式实现服务的容错、降级
+
+
+### 使用编程方式实现服务的容错、降级
+
+
+### 编程方式开启 Hystrix 请求缓存
+
+
+### 注解方式开启 Hystrix 请求缓存
+
+
+### 编程方式应用 Hystrix 请求合并
+
+
+### 注解方式应用 Hystrix 请求合并
+
+### OpenFeign 集成 Hystrix 开启后备模式
+
+
+### 使用 Hystrix 监控面板监测客户端容错
+
+### SpringCloud Netflix Hystrix 容错组件总结
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-common/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/pom.xml
index a3c6db4..feb826b 100644
--- a/dev-protocol-springcloud/dev-protocol-springcloud-project-common/pom.xml
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-common/pom.xml
@@ -44,12 +44,18 @@
hutool-all
5.7.22
+
+ org.apache.commons
+ commons-collections4
+ 4.4
+
com.alibaba.fastjson2
fastjson2
2.0.18
+
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/pom.xml
new file mode 100644
index 0000000..8b5cd7c
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/pom.xml
@@ -0,0 +1,99 @@
+
+
+ 4.0.0
+
+ org.example
+ dev-protocol
+ 1.0-SNAPSHOT
+ ../../pom.xml
+
+
+ dev-protocol-springcloud-project-goods-service
+ 1.0-SNAPSHOT
+ jar
+
+
+ dev-protocol-springcloud-project-goods-service
+ 商品服务
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-zipkin
+
+
+ org.springframework.kafka
+ spring-kafka
+ 2.5.0.RELEASE
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.12
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+ org.example
+ dev-protocol-springcloud-project-service-config
+ 1.0-SNAPSHOT
+
+
+ org.example
+ dev-protocol-springcloud-project-service-sdk
+ 1.0-SNAPSHOT
+
+
+
+
+
+ ${artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/GoodsApplication.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/GoodsApplication.java
new file mode 100644
index 0000000..9d32063
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/GoodsApplication.java
@@ -0,0 +1,22 @@
+package org.example;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+/**
+ *
商品微服务启动入口
+ * 启动依赖组件/中间件: Redis + MySQL + Nacos + Kafka + Zipkin
+ * http://127.0.0.1:8001/dev-protocol-springcloud-project-goods-service/doc.html
+ * */
+@EnableJpaAuditing
+@EnableDiscoveryClient
+@SpringBootApplication
+public class GoodsApplication {
+
+ public static void main(String[] args) {
+
+ SpringApplication.run(GoodsApplication.class, args);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/config/AsyncPoolConfig.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/config/AsyncPoolConfig.java
new file mode 100644
index 0000000..442ab70
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/config/AsyncPoolConfig.java
@@ -0,0 +1,78 @@
+package org.example.config;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 自定义异步任务线程池, 异步任务异常捕获处理器
+ * */
+@Slf4j
+@EnableAsync // 开启 Spring 异步任务支持
+@Configuration
+public class AsyncPoolConfig implements AsyncConfigurer {
+
+ /**
+ * 将自定义的线程池注入到 Spring 容器中
+ * */
+ @Bean
+ @Override
+ public Executor getAsyncExecutor() {
+
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+
+ executor.setCorePoolSize(10);
+ executor.setMaxPoolSize(20);
+ executor.setQueueCapacity(20);
+ executor.setKeepAliveSeconds(60);
+ executor.setThreadNamePrefix("Q-Async-"); // 这个非常重要
+
+ // 等待所有任务结果候再关闭线程池
+ executor.setWaitForTasksToCompleteOnShutdown(true);
+ executor.setAwaitTerminationSeconds(60);
+ // 定义拒绝策略
+ executor.setRejectedExecutionHandler(
+ new ThreadPoolExecutor.CallerRunsPolicy()
+ );
+ // 初始化线程池, 初始化 core 线程
+ executor.initialize();
+
+ return executor;
+ }
+
+ /**
+ * 指定系统中的异步任务在出现异常时使用到的处理器
+ * */
+ @Override
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+ return new AsyncExceptionHandler();
+ }
+
+ /**
+ * 异步任务异常捕获处理器
+ * */
+ @SuppressWarnings("all")
+ class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
+
+ @Override
+ public void handleUncaughtException(Throwable throwable, Method method,
+ Object... objects) {
+
+ throwable.printStackTrace();
+ log.error("Async Error: [{}], Method: [{}], Param: [{}]",
+ throwable.getMessage(), method.getName(),
+ JSON.toJSONString(objects));
+
+ // TODO 发送邮件或者是短信, 做进一步的报警处理
+ }
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/AsyncTaskStatusEnum.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/AsyncTaskStatusEnum.java
new file mode 100644
index 0000000..1598bd1
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/AsyncTaskStatusEnum.java
@@ -0,0 +1,24 @@
+package org.example.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 异步任务状态枚举
+ * */
+@Getter
+@AllArgsConstructor
+public enum AsyncTaskStatusEnum {
+
+ STARTED(0, "已经启动"),
+ RUNNING(1, "正在运行"),
+ SUCCESS(2, "执行成功"),
+ FAILED(3, "执行失败"),
+ ;
+
+ /** 执行状态编码 */
+ private final int state;
+
+ /** 执行状态描述 */
+ private final String stateInfo;
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/BrandCategory.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/BrandCategory.java
new file mode 100644
index 0000000..3966072
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/BrandCategory.java
@@ -0,0 +1,43 @@
+package org.example.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * 品牌分类
+ * */
+@Getter
+@AllArgsConstructor
+public enum BrandCategory {
+
+ BRAND_A("20001", "品牌A"),
+ BRAND_B("20002", "品牌B"),
+ BRAND_C("20003", "品牌C"),
+ BRAND_D("20004", "品牌D"),
+ BRAND_E("20005", "品牌E"),
+ ;
+
+ /** 品牌分类编码 */
+ private final String code;
+
+ /** 品牌分类描述信息 */
+ private final String description;
+
+ /**
+ * 根据 code 获取到 BrandCategory
+ * */
+ public static BrandCategory of(String code) {
+
+ Objects.requireNonNull(code);
+
+ return Stream.of(values())
+ .filter(bean -> bean.code.equals(code))
+ .findAny()
+ .orElseThrow(
+ () -> new IllegalArgumentException(code + " not exists")
+ );
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/GoodsCategory.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/GoodsCategory.java
new file mode 100644
index 0000000..8474484
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/GoodsCategory.java
@@ -0,0 +1,45 @@
+package org.example.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * 商品类别
+ * 电器 -> 手机、电脑
+ * */
+@Getter
+@AllArgsConstructor
+public enum GoodsCategory {
+
+ DIAN_QI("10001", "电器"),
+ JIA_JU("10002", "家具"),
+ FU_SHI("10003", "服饰"),
+ MY_YIN("10004", "母婴"),
+ SHI_PIN("10005", "食品"),
+ TU_SHU("10006", "图书"),
+ ;
+
+ /** 商品类别编码 */
+ private final String code;
+
+ /** 商品类别描述信息 */
+ private final String description;
+
+ /**
+ * 根据 code 获取到 GoodsCategory
+ * */
+ public static GoodsCategory of(String code) {
+
+ Objects.requireNonNull(code);
+
+ return Stream.of(values())
+ .filter(bean -> bean.code.equals(code))
+ .findAny()
+ .orElseThrow(
+ () -> new IllegalArgumentException(code + " not exists")
+ );
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/GoodsConstant.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/GoodsConstant.java
new file mode 100644
index 0000000..0d4b9b1
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/GoodsConstant.java
@@ -0,0 +1,11 @@
+package org.example.constant;
+
+/**
+ * 商品常量信息
+ * */
+public class GoodsConstant {
+
+ /** redis key */
+ public static final String ECOMMERCE_GOODS_DICT_KEY =
+ "ecommerce:goods:dict:20210101";
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/GoodsStatus.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/GoodsStatus.java
new file mode 100644
index 0000000..52f5ae5
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/constant/GoodsStatus.java
@@ -0,0 +1,41 @@
+package org.example.constant;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * 商品状态枚举类
+ * */
+@Getter
+@AllArgsConstructor
+public enum GoodsStatus {
+
+ ONLINE(101, "上线"),
+ OFFLINE(102, "下线"),
+ STOCK_OUT(103, "缺货"),
+ ;
+
+ /** 状态码 */
+ private final Integer status;
+
+ /** 状态描述 */
+ private final String description;
+
+ /**
+ * 根据 code 获取到 GoodsStatus
+ * */
+ public static GoodsStatus of(Integer status) {
+
+ Objects.requireNonNull(status);
+
+ return Stream.of(values())
+ .filter(bean -> bean.status.equals(status))
+ .findAny()
+ .orElseThrow(
+ () -> new IllegalArgumentException(status + " not exists")
+ );
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/controller/AsyncGoodsController.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/controller/AsyncGoodsController.java
new file mode 100644
index 0000000..9baaa4f
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/controller/AsyncGoodsController.java
@@ -0,0 +1,44 @@
+package org.example.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.example.goods.GoodsInfo;
+import org.example.service.async.AsyncTaskManager;
+import org.example.vo.AsyncTaskInfo;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 异步任务服务对外提供的 API
+ * */
+@Api(tags = "商品异步入库服务")
+@Slf4j
+@RestController
+@RequestMapping("/async-goods")
+public class AsyncGoodsController {
+
+ private final AsyncTaskManager asyncTaskManager;
+
+ public AsyncGoodsController(AsyncTaskManager asyncTaskManager) {
+ this.asyncTaskManager = asyncTaskManager;
+ }
+
+ @ApiOperation(value = "导入商品", notes = "导入商品进入到商品表", httpMethod = "POST")
+ @PostMapping("/import-goods")
+ public AsyncTaskInfo importGoods(@RequestBody List goodsInfos) {
+ return asyncTaskManager.submit(goodsInfos);
+ }
+
+ @ApiOperation(value = "查询状态", notes = "查询异步任务的执行状态", httpMethod = "GET")
+ @GetMapping("/task-info")
+ public AsyncTaskInfo getTaskInfo(@RequestParam String taskId) {
+ return asyncTaskManager.getTaskInfo(taskId);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/controller/GoodsController.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/controller/GoodsController.java
new file mode 100644
index 0000000..df2965d
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/controller/GoodsController.java
@@ -0,0 +1,64 @@
+package org.example.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.example.common.TableId;
+import org.example.goods.DeductGoodsInventory;
+import org.example.goods.GoodsInfo;
+import org.example.goods.SimpleGoodsInfo;
+import org.example.service.IGoodsService;
+import org.example.vo.PageSimpleGoodsInfo;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 商品微服务对外暴露的功能服务 API 接口
+ * */
+@Api(tags = "商品微服务功能接口")
+@Slf4j
+@RestController
+@RequestMapping("/goods")
+public class GoodsController {
+
+ private final IGoodsService goodsService;
+
+ public GoodsController(IGoodsService goodsService) {
+ this.goodsService = goodsService;
+ }
+
+ @ApiOperation(value = "详细商品信息", notes = "根据 TableId 查询详细商品信息",
+ httpMethod = "POST")
+ @PostMapping("/goods-info")
+ public List getGoodsInfoByTableId(@RequestBody TableId tableId) {
+ return goodsService.getGoodsInfoByTableId(tableId);
+ }
+
+ @ApiOperation(value = "简单商品信息", notes = "获取分页的简单商品信息", httpMethod = "GET")
+ @GetMapping("/page-simple-goods-info")
+ public PageSimpleGoodsInfo getSimpleGoodsInfoByPage(
+ @RequestParam(required = false, defaultValue = "1") int page) {
+ return goodsService.getSimpleGoodsInfoByPage(page);
+ }
+
+ @ApiOperation(value = "简单商品信息", notes = "根据 TableId 查询简单商品信息",
+ httpMethod = "POST")
+ @PostMapping("/simple-goods-info")
+ public List getSimpleGoodsInfoByTableId(@RequestBody TableId tableId) {
+ return goodsService.getSimpleGoodsInfoByTableId(tableId);
+ }
+
+ @ApiOperation(value = "扣减商品库存", notes = "扣减商品库存", httpMethod = "PUT")
+ @PutMapping("/deduct-goods-inventory")
+ public Boolean deductGoodsInventory(
+ @RequestBody List deductGoodsInventories) {
+ return goodsService.deductGoodsInventory(deductGoodsInventories);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/converter/BrandCategoryConverter.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/converter/BrandCategoryConverter.java
new file mode 100644
index 0000000..bb42d24
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/converter/BrandCategoryConverter.java
@@ -0,0 +1,22 @@
+package org.example.converter;
+
+
+import org.example.constant.BrandCategory;
+
+import javax.persistence.AttributeConverter;
+
+/**
+ * 品牌分类枚举属性转换器
+ * */
+public class BrandCategoryConverter implements AttributeConverter {
+
+ @Override
+ public String convertToDatabaseColumn(BrandCategory brandCategory) {
+ return brandCategory.getCode();
+ }
+
+ @Override
+ public BrandCategory convertToEntityAttribute(String code) {
+ return BrandCategory.of(code);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/converter/GoodsCategoryConverter.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/converter/GoodsCategoryConverter.java
new file mode 100644
index 0000000..edd3538
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/converter/GoodsCategoryConverter.java
@@ -0,0 +1,22 @@
+package org.example.converter;
+
+
+import org.example.constant.GoodsCategory;
+
+import javax.persistence.AttributeConverter;
+
+/**
+ * 商品类别枚举属性转换器
+ * */
+public class GoodsCategoryConverter implements AttributeConverter {
+
+ @Override
+ public String convertToDatabaseColumn(GoodsCategory goodsCategory) {
+ return goodsCategory.getCode();
+ }
+
+ @Override
+ public GoodsCategory convertToEntityAttribute(String code) {
+ return GoodsCategory.of(code);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/converter/GoodsStatusConverter.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/converter/GoodsStatusConverter.java
new file mode 100644
index 0000000..6997296
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/converter/GoodsStatusConverter.java
@@ -0,0 +1,28 @@
+package org.example.converter;
+
+
+import org.example.constant.GoodsStatus;
+
+import javax.persistence.AttributeConverter;
+
+/**
+ * 商品状态枚举属性转换器
+ * */
+public class GoodsStatusConverter implements AttributeConverter {
+
+ /**
+ * 转换成可以存入数据表的基本类型
+ * */
+ @Override
+ public Integer convertToDatabaseColumn(GoodsStatus goodsStatus) {
+ return goodsStatus.getStatus();
+ }
+
+ /**
+ * 还原数据表中的字段值到 Java 数据类型
+ * */
+ @Override
+ public GoodsStatus convertToEntityAttribute(Integer status) {
+ return GoodsStatus.of(status);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/dao/EcommerceGoodsDao.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/dao/EcommerceGoodsDao.java
new file mode 100644
index 0000000..8ed1fa7
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/dao/EcommerceGoodsDao.java
@@ -0,0 +1,25 @@
+package org.example.dao;
+
+
+import org.example.constant.BrandCategory;
+import org.example.constant.GoodsCategory;
+import org.example.entity.EcommerceGoods;
+import org.springframework.data.repository.PagingAndSortingRepository;
+
+import java.util.Optional;
+
+/**
+ * EcommerceGoods Dao 接口定义
+ * */
+public interface EcommerceGoodsDao extends PagingAndSortingRepository {
+
+ /**
+ * 根据查询条件查询商品表, 并限制返回结果
+ * select * from t_dev_protocol_cloud_goods where goods_category = ? and brand_category = ?
+ * and goods_name = ? limit 1;
+ * */
+ Optional findFirst1ByGoodsCategoryAndBrandCategoryAndGoodsName(
+ GoodsCategory goodsCategory, BrandCategory brandCategory,
+ String goodsName
+ );
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/entity/EcommerceGoods.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/entity/EcommerceGoods.java
new file mode 100644
index 0000000..ec0871c
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/entity/EcommerceGoods.java
@@ -0,0 +1,162 @@
+package org.example.entity;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.example.constant.BrandCategory;
+import org.example.constant.GoodsCategory;
+import org.example.constant.GoodsStatus;
+import org.example.converter.BrandCategoryConverter;
+import org.example.converter.GoodsCategoryConverter;
+import org.example.converter.GoodsStatusConverter;
+import org.example.goods.GoodsInfo;
+import org.example.goods.SimpleGoodsInfo;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.Column;
+import javax.persistence.Convert;
+import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+
+/**
+ * 商品表实体类定义
+ * */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@EntityListeners(AuditingEntityListener.class)
+@Table(name = "t_dev_protocol_cloud_goods")
+public class EcommerceGoods {
+
+ /** 自增主键 */
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", nullable = false)
+ private Long id;
+
+ /** 商品类型 */
+ @Column(name = "goods_category", nullable = false)
+ @Convert(converter = GoodsCategoryConverter.class)
+ private GoodsCategory goodsCategory;
+
+ /** 品牌分类 */
+ @Column(name = "brand_category", nullable = false)
+ @Convert(converter = BrandCategoryConverter.class)
+ private BrandCategory brandCategory;
+
+ /** 商品名称 */
+ @Column(name = "goods_name", nullable = false)
+ private String goodsName;
+
+ /** 商品名称 */
+ @Column(name = "goods_pic", nullable = false)
+ private String goodsPic;
+
+ /** 商品描述信息 */
+ @Column(name = "goods_description", nullable = false)
+ private String goodsDescription;
+
+ /** 商品状态 */
+ @Column(name = "goods_status", nullable = false)
+ @Convert(converter = GoodsStatusConverter.class)
+ private GoodsStatus goodsStatus;
+
+ /** 商品价格: 单位: 分、厘 */
+ @Column(name = "price", nullable = false)
+ private Integer price;
+
+ /** 总供应量 */
+ @Column(name = "supply", nullable = false)
+ private Long supply;
+
+ /** 库存 */
+ @Column(name = "inventory", nullable = false)
+ private Long inventory;
+
+ /** 商品属性, json 字符串存储 */
+ @Column(name = "goods_property", nullable = false)
+ private String goodsProperty;
+
+ /** 创建时间 */
+ @CreatedDate
+ @Column(name = "create_time", nullable = false)
+ private Date createTime;
+
+ /** 更新时间 */
+ @LastModifiedDate
+ @Column(name = "update_time", nullable = false)
+ private Date updateTime;
+
+ /**
+ * 将 GoodsInfo 转成实体对象
+ * */
+ public static EcommerceGoods to(GoodsInfo goodsInfo) {
+
+ EcommerceGoods ecommerceGoods = new EcommerceGoods();
+
+ ecommerceGoods.setGoodsCategory(GoodsCategory.of(goodsInfo.getGoodsCategory()));
+ ecommerceGoods.setBrandCategory(BrandCategory.of(goodsInfo.getBrandCategory()));
+ ecommerceGoods.setGoodsName(goodsInfo.getGoodsName());
+ ecommerceGoods.setGoodsPic(goodsInfo.getGoodsPic());
+ ecommerceGoods.setGoodsDescription(goodsInfo.getGoodsDescription());
+ ecommerceGoods.setGoodsStatus(GoodsStatus.ONLINE); // 可以增加一个审核的过程
+ ecommerceGoods.setPrice(goodsInfo.getPrice());
+ ecommerceGoods.setSupply(goodsInfo.getSupply());
+ ecommerceGoods.setInventory(goodsInfo.getSupply());
+ ecommerceGoods.setGoodsProperty(
+ JSON.toJSONString(goodsInfo.getGoodsProperty())
+ );
+
+ return ecommerceGoods;
+ }
+
+ /**
+ * 将实体对象转成 GoodsInfo 对象
+ * */
+ public GoodsInfo toGoodsInfo() {
+
+ GoodsInfo goodsInfo = new GoodsInfo();
+
+ goodsInfo.setId(this.id);
+ goodsInfo.setGoodsCategory(this.goodsCategory.getCode());
+ goodsInfo.setBrandCategory(this.brandCategory.getCode());
+ goodsInfo.setGoodsName(this.goodsName);
+ goodsInfo.setGoodsPic(this.goodsPic);
+ goodsInfo.setGoodsDescription(this.goodsDescription);
+ goodsInfo.setGoodsStatus(this.goodsStatus.getStatus());
+ goodsInfo.setPrice(this.price);
+ goodsInfo.setGoodsProperty(
+ JSON.parseObject(this.goodsProperty, GoodsInfo.GoodsProperty.class)
+ );
+ goodsInfo.setSupply(this.supply);
+ goodsInfo.setInventory(this.inventory);
+ goodsInfo.setCreateTime(this.createTime);
+ goodsInfo.setUpdateTime(this.updateTime);
+
+ return goodsInfo;
+ }
+
+ /**
+ * 将实体对象转成 SimpleGoodsInfo 对象
+ * */
+ public SimpleGoodsInfo toSimple() {
+
+ SimpleGoodsInfo goodsInfo = new SimpleGoodsInfo();
+
+ goodsInfo.setId(this.id);
+ goodsInfo.setGoodsName(this.goodsName);
+ goodsInfo.setGoodsPic(this.goodsPic);
+ goodsInfo.setPrice(this.price);
+
+ return goodsInfo;
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/IGoodsService.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/IGoodsService.java
new file mode 100644
index 0000000..afb63b9
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/IGoodsService.java
@@ -0,0 +1,36 @@
+package org.example.service;
+
+
+import org.example.common.TableId;
+import org.example.goods.DeductGoodsInventory;
+import org.example.goods.GoodsInfo;
+import org.example.goods.SimpleGoodsInfo;
+import org.example.vo.PageSimpleGoodsInfo;
+
+import java.util.List;
+
+/**
+ * 商品微服务相关服务接口定义
+ * */
+public interface IGoodsService {
+
+ /**
+ * 根据 TableId 查询商品详细信息
+ * */
+ List getGoodsInfoByTableId(TableId tableId);
+
+ /**
+ * 获取分页的商品信息
+ * */
+ PageSimpleGoodsInfo getSimpleGoodsInfoByPage(int page);
+
+ /**
+ * 根据 TableId 查询简单商品信息
+ * */
+ List getSimpleGoodsInfoByTableId(TableId tableId);
+
+ /**
+ * 扣减商品库存
+ * */
+ Boolean deductGoodsInventory(List deductGoodsInventories);
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/AsyncServiceImpl.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/AsyncServiceImpl.java
new file mode 100644
index 0000000..2b5be27
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/AsyncServiceImpl.java
@@ -0,0 +1,155 @@
+package org.example.service.async;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.lang3.time.StopWatch;
+import org.example.constant.GoodsConstant;
+import org.example.dao.EcommerceGoodsDao;
+import org.example.entity.EcommerceGoods;
+import org.example.goods.GoodsInfo;
+import org.example.goods.SimpleGoodsInfo;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * 异步服务接口实现
+ * */
+@Slf4j
+@Service
+@Transactional
+public class AsyncServiceImpl implements IAsyncService {
+
+ private final EcommerceGoodsDao ecommerceGoodsDao;
+ private final StringRedisTemplate redisTemplate;
+
+ public AsyncServiceImpl(EcommerceGoodsDao ecommerceGoodsDao,
+ StringRedisTemplate redisTemplate) {
+ this.ecommerceGoodsDao = ecommerceGoodsDao;
+ this.redisTemplate = redisTemplate;
+ }
+
+ /**
+ * 异步任务需要加上注解, 并指定使用的线程池
+ * 异步任务处理两件事:
+ * 1. 将商品信息保存到数据表
+ * 2. 更新商品缓存
+ * */
+ @Async("getAsyncExecutor")
+ @Override
+ public void asyncImportGoods(List goodsInfos, String taskId) {
+
+ log.info("async task running taskId: [{}]", taskId);
+
+ StopWatch watch = StopWatch.createStarted();
+
+ // 1. 如果是 goodsInfo 中存在重复的商品, 不保存; 直接返回, 记录错误日志
+ // 请求数据是否合法的标记
+ boolean isIllegal = false;
+
+ // 将商品信息字段 joint 在一起, 用来判断是否存在重复
+ Set goodsJointInfos = new HashSet<>(goodsInfos.size());
+ // 过滤出来的, 可以入库的商品信息(规则按照自己的业务需求自定义即可)
+ List filteredGoodsInfo = new ArrayList<>(goodsInfos.size());
+
+ // 走一遍循环, 过滤非法参数与判定当前请求是否合法
+ for (GoodsInfo goods : goodsInfos) {
+
+ // 基本条件不满足的, 直接过滤器
+ if (goods.getPrice() <= 0 || goods.getSupply() <= 0) {
+ log.info("goods info is invalid: [{}]", JSON.toJSONString(goods));
+ continue;
+ }
+
+ // 组合商品信息 - 用于判断唯一性
+ String jointInfo = String.format(
+ "%s,%s,%s",
+ goods.getGoodsCategory(), goods.getBrandCategory(),
+ goods.getGoodsName()
+ );
+ if (goodsJointInfos.contains(jointInfo)) {
+ isIllegal = true;
+ }
+
+ // 加入到两个容器中
+ goodsJointInfos.add(jointInfo);
+ filteredGoodsInfo.add(goods);
+ }
+
+ // 如果存在重复商品或者是没有需要入库的商品, 直接打印日志返回
+ if (isIllegal || CollectionUtils.isEmpty(filteredGoodsInfo)) {
+ watch.stop();
+ log.warn("import nothing: [{}]", JSON.toJSONString(filteredGoodsInfo));
+ log.info("check and import goods done: [{}ms]", watch.getTime(TimeUnit.MILLISECONDS));
+ return;
+ }
+
+ List ecommerceGoods = filteredGoodsInfo.stream()
+ .map(EcommerceGoods::to)
+ .collect(Collectors.toList());
+ List targetGoods = new ArrayList<>(ecommerceGoods.size());
+
+ // 2. 保存 goodsInfo 之前先判断下是否存在重复商品
+ ecommerceGoods.forEach(g -> {
+
+ // limit 1
+ if (null != ecommerceGoodsDao
+ .findFirst1ByGoodsCategoryAndBrandCategoryAndGoodsName(
+ g.getGoodsCategory(), g.getBrandCategory(),
+ g.getGoodsName()
+ ).orElse(null)) {
+ return;
+ }
+
+ targetGoods.add(g);
+ });
+
+ // 商品信息入库
+ List savedGoods = IterableUtils.toList(
+ ecommerceGoodsDao.saveAll(targetGoods)
+ );
+ // 将入库商品信息同步到 Redis 中
+ saveNewGoodsInfoToRedis(savedGoods);
+
+ log.info("save goods info to db and redis: [{}]", savedGoods.size());
+
+ watch.stop();
+ log.info("check and import goods success: [{}ms]",
+ watch.getTime(TimeUnit.MILLISECONDS));
+ }
+
+ /**
+ * 将保存到数据表中的数据缓存到 Redis 中
+ * dict: key ->
+ * */
+ private void saveNewGoodsInfoToRedis(List savedGoods) {
+
+ // 由于 Redis 是内存存储, 只存储简单商品信息
+ List simpleGoodsInfos = savedGoods.stream()
+ .map(EcommerceGoods::toSimple)
+ .collect(Collectors.toList());
+
+ Map id2JsonObject = new HashMap<>(simpleGoodsInfos.size());
+ simpleGoodsInfos.forEach(
+ g -> id2JsonObject.put(g.getId().toString(), JSON.toJSONString(g))
+ );
+
+ // 保存到 Redis 中
+ redisTemplate.opsForHash().putAll(
+ GoodsConstant.ECOMMERCE_GOODS_DICT_KEY,
+ id2JsonObject
+ );
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/AsyncTaskManager.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/AsyncTaskManager.java
new file mode 100644
index 0000000..d51952e
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/AsyncTaskManager.java
@@ -0,0 +1,73 @@
+package org.example.service.async;
+
+import lombok.extern.slf4j.Slf4j;
+import org.example.constant.AsyncTaskStatusEnum;
+import org.example.goods.GoodsInfo;
+import org.example.vo.AsyncTaskInfo;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 异步任务执行管理器
+ * 对异步任务进行包装管理, 记录并塞入异步任务执行信息
+ * */
+@Slf4j
+@Component
+public class AsyncTaskManager {
+
+ /** 异步任务执行信息容器 */
+ private final Map taskContainer =
+ new HashMap<>(16);
+
+ private final IAsyncService asyncService;
+
+ public AsyncTaskManager(IAsyncService asyncService) {
+ this.asyncService = asyncService;
+ }
+
+ /**
+ * 初始化异步任务
+ * */
+ public AsyncTaskInfo initTask() {
+
+ AsyncTaskInfo taskInfo = new AsyncTaskInfo();
+ // 设置一个唯一的异步任务 id, 只要唯一即可
+ taskInfo.setTaskId(UUID.randomUUID().toString());
+ taskInfo.setStatus(AsyncTaskStatusEnum.STARTED);
+ taskInfo.setStartTime(new Date());
+
+ // 初始化的时候就要把异步任务执行信息放入到存储容器中
+ taskContainer.put(taskInfo.getTaskId(), taskInfo);
+ return taskInfo;
+ }
+
+ /**
+ * 提交异步任务
+ * */
+ public AsyncTaskInfo submit(List goodsInfos) {
+
+ // 初始化一个异步任务的监控信息
+ AsyncTaskInfo taskInfo = initTask();
+ asyncService.asyncImportGoods(goodsInfos, taskInfo.getTaskId());
+ return taskInfo;
+ }
+
+ /**
+ * 设置异步任务执行状态信息
+ * */
+ public void setTaskInfo(AsyncTaskInfo taskInfo) {
+ taskContainer.put(taskInfo.getTaskId(), taskInfo);
+ }
+
+ /**
+ * 获取异步任务执行状态信息
+ * */
+ public AsyncTaskInfo getTaskInfo(String taskId) {
+ return taskContainer.get(taskId);
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/AsyncTaskMonitor.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/AsyncTaskMonitor.java
new file mode 100644
index 0000000..f60c22a
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/AsyncTaskMonitor.java
@@ -0,0 +1,70 @@
+package org.example.service.async;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.example.constant.AsyncTaskStatusEnum;
+import org.example.vo.AsyncTaskInfo;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+/**
+ * 异步任务执行监控切面
+ * */
+@Slf4j
+@Aspect
+@Component
+public class AsyncTaskMonitor {
+
+ /** 注入异步任务管理器 */
+ private final AsyncTaskManager asyncTaskManager;
+
+ public AsyncTaskMonitor(AsyncTaskManager asyncTaskManager) {
+ this.asyncTaskManager = asyncTaskManager;
+ }
+
+ /**
+ * 异步任务执行的环绕切面
+ * 环绕切面让我们可以在方法执行之前和执行之后做一些 "额外" 的操作
+ * */
+ @Around("execution(* org.example.service.async.AsyncServiceImpl.*(..))")
+ public Object taskHandle(ProceedingJoinPoint proceedingJoinPoint) {
+
+ // 获取 taskId, 调用异步任务传入的第二个参数
+ String taskId = proceedingJoinPoint.getArgs()[1].toString();
+
+ // 获取任务信息, 在提交任务的时候就已经放入到容器中了
+ AsyncTaskInfo taskInfo = asyncTaskManager.getTaskInfo(taskId);
+ log.info("AsyncTaskMonitor is monitoring async task: [{}]", taskId);
+
+ taskInfo.setStatus(AsyncTaskStatusEnum.RUNNING);
+ asyncTaskManager.setTaskInfo(taskInfo); // 设置为运行状态, 并重新放入容器
+
+ AsyncTaskStatusEnum status;
+ Object result;
+
+ try {
+ // 执行异步任务
+ result = proceedingJoinPoint.proceed();
+ status = AsyncTaskStatusEnum.SUCCESS;
+ } catch (Throwable ex) {
+ // 异步任务出现了异常
+ result = null;
+ status = AsyncTaskStatusEnum.FAILED;
+ log.error("AsyncTaskMonitor: async task [{}] is failed, Error Info: [{}]",
+ taskId, ex.getMessage(), ex);
+ }
+
+ // 设置异步任务其他的信息, 再次重新放入到容器中
+ taskInfo.setEndTime(new Date());
+ taskInfo.setStatus(status);
+ taskInfo.setTotalTime(String.valueOf(
+ taskInfo.getEndTime().getTime() - taskInfo.getStartTime().getTime()
+ ));
+ asyncTaskManager.setTaskInfo(taskInfo);
+
+ return result;
+ }
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/IAsyncService.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/IAsyncService.java
new file mode 100644
index 0000000..b29cc53
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/async/IAsyncService.java
@@ -0,0 +1,17 @@
+package org.example.service.async;
+
+
+import org.example.goods.GoodsInfo;
+
+import java.util.List;
+
+/**
+ * 异步服务接口定义
+ * */
+public interface IAsyncService {
+
+ /**
+ * 异步将商品信息保存下来
+ * */
+ void asyncImportGoods(List goodsInfos, String taskId);
+}
diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/impl/GoodsServiceImpl.java b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/impl/GoodsServiceImpl.java
new file mode 100644
index 0000000..b08e606
--- /dev/null
+++ b/dev-protocol-springcloud/dev-protocol-springcloud-project-goods-service/src/main/java/org/example/service/impl/GoodsServiceImpl.java
@@ -0,0 +1,229 @@
+package org.example.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.IterableUtils;
+import org.example.common.TableId;
+import org.example.constant.GoodsConstant;
+import org.example.dao.EcommerceGoodsDao;
+import org.example.entity.EcommerceGoods;
+import org.example.goods.DeductGoodsInventory;
+import org.example.goods.GoodsInfo;
+import org.example.goods.SimpleGoodsInfo;
+import org.example.service.IGoodsService;
+import org.example.vo.PageSimpleGoodsInfo;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 商品微服务相关服务功能实现
+ * */
+@Slf4j
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class GoodsServiceImpl implements IGoodsService {
+
+ private final StringRedisTemplate redisTemplate;
+ private final EcommerceGoodsDao ecommerceGoodsDao;
+
+ public GoodsServiceImpl(StringRedisTemplate redisTemplate,
+ EcommerceGoodsDao ecommerceGoodsDao) {
+ this.redisTemplate = redisTemplate;
+ this.ecommerceGoodsDao = ecommerceGoodsDao;
+ }
+
+ @Override
+ public List getGoodsInfoByTableId(TableId tableId) {
+
+ // 详细的商品信息, 不能从 redis cache 中去拿
+ List ids = tableId.getIds().stream()
+ .map(TableId.Id::getId)
+ .collect(Collectors.toList());
+ log.info("get goods info by ids: [{}]", JSON.toJSONString(ids));
+
+ List ecommerceGoods = IterableUtils.toList(
+ ecommerceGoodsDao.findAllById(ids)
+ );
+
+ return ecommerceGoods.stream()
+ .map(EcommerceGoods::toGoodsInfo).collect(Collectors.toList());
+ }
+
+ @Override
+ public PageSimpleGoodsInfo getSimpleGoodsInfoByPage(int page) {
+
+ // 分页不能从 redis cache 中去拿
+ if (page <= 1) {
+ page = 1; // 默认是第一页
+ }
+
+ // 这里分页的规则(你可以自由修改): 1页10条数据, 按照 id 倒序排列
+ Pageable pageable = PageRequest.of(
+ page - 1, 10, Sort.by("id").descending()
+ );
+ Page orderPage = ecommerceGoodsDao.findAll(pageable);
+
+ // 是否还有更多页: 总页数是否大于当前给定的页
+ boolean hasMore = orderPage.getTotalPages() > page;
+
+ return new PageSimpleGoodsInfo(
+ orderPage.getContent().stream()
+ .map(EcommerceGoods::toSimple).collect(Collectors.toList()),
+ hasMore
+ );
+ }
+
+ @Override
+ public List getSimpleGoodsInfoByTableId(TableId tableId) {
+
+ // 获取商品的简单信息, 可以从 redis cache 中去拿, 拿不到需要从 DB 中获取并保存到 Redis 里面
+ // Redis 中的 KV 都是字符串类型
+ List