spring cloud alibaba开发笔记八(商品微服务,异步及管理)
时间:2022-10-25 11:00:01
微服务的初始化
在service在服务下,创建商品微服务goods-service
启动类
/** * 启动商品微服务入口
* 启动依赖组件/中间件: Redis MySQL Nacos Kafka Zipkin * http://127.0.0.1:8001/ecommerce-goods-service/doc.html */ @EnableJpaAuditing @EnableDiscoveryClient @SpringCloudApplication public class GoodsApplication { public static void main(String[] args) { SpringApplication.run(GoodsApplication.class, args); } }
pom
e-commerce-service com.taluohui.ecommerce 1.0-SNAPSHOT 4.0.0 e-commerce-goods-service 1.0-SNAPSHOT jar e-commerce-goods-service 商品服务 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 com.taluohui.ecommerce e-commerce-service-config 1.0-SNAPSHOT com.taluohui.ecommerce e-commerce-service-sdk 1.0-SNAPSHOT ${artifactId} org.springframework.boot spring-boot-maven-plugin repackage
配置项
server: port: 8001 servlet: context-path: /ecommerce-goods-service spring: application: name: e-commerce-goods-service # 也构成了应用名称 Nacos 配置管理 dataId 部分字段 (当 config.prefix 为空时) cloud: nacos: # 发现服务注册 discovery: enabled: rue # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
server-addr: 127.0.0.1:8848
# server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
namespace: 22d40198-8462-499d-a7fe-dbb2da958648
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
kafka:
bootstrap-servers: 1.15.247.9:9092
producer:
retries: 3
consumer:
auto-offset-reset: latest
sleuth:
sampler:
# ProbabilityBasedSampler 抽样策略
probability: 1.0 # 采样比例, 1.0 表示 100%, 默认是 0.1
# RateLimitingSampler 抽样策略, 设置了限速采集, spring.sleuth.sampler.probability 属性值无效
rate: 100 # 每秒间隔接受的 trace 量
zipkin:
sender:
type: kafka # 默认是 web
base-url: http://127.0.0.1:9411/
jpa:
show-sql: true
hibernate:
ddl-auto: none
properties:
hibernate.show_sql: true
hibernate.format_sql: true
open-in-view: false
datasource:
# 数据源
url: jdbc:mysql://127.0.0.1:3306/ecommerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: Cjw970404
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 连接池
hikari:
maximum-pool-size: 8
minimum-idle: 4
idle-timeout: 30000
connection-timeout: 30000
max-lifetime: 45000
auto-commit: true
pool-name: ImoocEcommerceHikariCP
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
枚举类和转换方法
枚举类示例,在文件夹constant下
/**
* 品牌分类
* */
@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")
);
}
}
转换方法,在文件夹converter下
/**
* 品牌分类枚举属性转换器
* */
public class BrandCategoryConverter implements AttributeConverter {
@Override
public String convertToDatabaseColumn(BrandCategory brandCategory) {
return brandCategory.getCode();
}
@Override
public BrandCategory convertToEntityAttribute(String code) {
return BrandCategory.of(code);
}
}
再用以下方法创建两个枚举类和他们的转换方法
/**
* 商品类别
* 电器 -> 手机、电脑
* */
public enum GoodsCategory {
DIAN_QI("10001", "电器"),
JIA_JU("10002", "家具"),
FU_SHI("10003", "服饰"),
MY_YIN("10004", "母婴"),
SHI_PIN("10005", "食品"),
TU_SHU("10006", "图书"),
;
·······································
}
/**
*商品状态枚举类
*/
@Getter
@AllArgsConstructor
public enum GoodsStatus {
ONLINE(101, "上线"),
OFFLINE(102, "下线"),
STOCK_OUT(103, "缺货"),
;
·····································
}
在mysql中创建商品表
-- 创建 t_ecommerce_goods 数据表
CREATE TABLE IF NOT EXISTS `ecommerce`.`t_ecommerce_goods` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`goods_category` varchar(64) NOT NULL DEFAULT '' COMMENT '商品类别',
`brand_category` varchar(64) NOT NULL DEFAULT '' COMMENT '品牌分类',
`goods_name` varchar(64) NOT NULL DEFAULT '' COMMENT '商品名称',
`goods_pic` varchar(256) NOT NULL DEFAULT '' COMMENT '商品图片',
`goods_description` varchar(512) NOT NULL DEFAULT '' COMMENT '商品描述信息',
`goods_status` int(11) NOT NULL DEFAULT 0 COMMENT '商品状态',
`price` int(11) NOT NULL DEFAULT 0 COMMENT '商品价格',
`supply` bigint(20) NOT NULL DEFAULT 0 COMMENT '总供应量',
`inventory` bigint(20) NOT NULL DEFAULT 0 COMMENT '库存',
`goods_property` varchar(1024) NOT NULL DEFAULT '' COMMENT '商品属性',
`create_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT '0000-01-01 00:00:00' COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `goods_category_brand_name` (`goods_category`, `brand_category`, `goods_name`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='商品表';
在entity文件下,创建对应的类以及对应的转换方法,在vo文件夹下创建对应的展示类。在sdk通用模块下创建对应的通用类。
Dao
public interface EcommerceGoodsDao extends PagingAndSortingRepository {
/**
* 根据查询条件查询商品表, 并限制返回结果
* select * from t_ecommerce_goods where goods_category = ? and brand_category = ?
* and goods_name = ? limit 1;
* */
Optional findFirst1ByGoodsCategoryAndBrandCategoryAndGoodsName(
GoodsCategory goodsCategory, BrandCategory brandCategory,
String goodsName
);
}
Service接口
/**
* 商品微服务相关服务接口定义
* */
public interface IGoodsService {
/**
* 根据 TableId 查询商品详细信息
* */
List getGoodsInfoByTableId(TableId tableId);
/**
* 获取分页的商品信息
* */
PageSimpleGoodsInfo getSimpleGoodsInfoByPage(int page);
/**
* 根据 TableId 查询简单商品信息
* */
List getSimpleGoodsInfoByTableId(TableId tableId);
/**
* 扣减商品库存
* */
Boolean deductGoodsInventory(List deductGoodsInventories);
}
使用异步的方式进行入库操作
异步类,在service文件夹下再创建async文件夹
/**
* 异步服务接口定义
*/
public interface IAsyncService {
/**
* 异步将商品信息保存下来
* */
void asyncImportGoods(List goodsInfos, String taskId);
}
在vo文件夹下,创建用于异步管理的类
/**
* 异步任务执行信息
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AsyncTaskInfo {
/** 异步任务 id */
private String taskId;
/** 异步任务开始时间 */
private Date startTime;
/** 异步任务结束时间 */
private Date endTime;
/** 异步任务总耗时 */
private String totalTime;
}
在商品微服务下创建config文件夹,自定义异步任务线程池,异步任务异常捕获处理器
import org.apache.commons.lang3.time.StopWatch;
/**
* 自定义异步任务线程池,异步任务异常捕获处理器
*/
@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("Qinyi-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 发送邮件或者是短信, 做进一步的报警处理
}
}
}
异步任务的实现,商品信息会同步到redis中
/**
* 异步服务接口实现
* */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
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
);
}
}
redis的Key值,定义到常量中
/**
* 商品常量信息
* */
public class GoodsConstant {
/** redis key */
public static final String ECOMMERCE_GOODS_DICT_KEY =
"ecommerce:goods:dict:20220308";
}
异步任务的管理
定义枚举类
/**
* 异步任务状态枚举
* */
@Getter
@AllArgsConstructor
public enum AsyncTaskStatusEnum {
STARTED(0, "已经启动"),
RUNNING(1, "正在运行"),
SUCCESS(2, "执行成功"),
FAILED(3, "执行失败"),
;
/** 执行状态编码 */
private final int state;
/** 执行状态描述 */
private final String stateInfo;
}
异步任务执行管理器
我们并不直接调用service中的方法,通过多方法的包装,我们可以实现对任务的监控。
/**
* 异步任务执行管理器
* 对异步任务进行包装管理, 记录并塞入异步任务执行信息
* */
@Slf4j
@Component
public class AsyncTaskManager {
/** 异步任务执行信息容器,这里作为演示,使用的map,可以使用mysql之类的做持久化 */
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);
}
}
使用AOP实现对异步任务的监控,以及状态的修改
/**
* 异步任务执行监控切面
* */
@Slf4j
@Aspect
@Component
public class AsyncTaskMonitor {
/** 注入异步任务管理器 */
private final AsyncTaskManager asyncTaskManager;
public AsyncTaskMonitor(AsyncTaskManager asyncTaskManager) {
this.asyncTaskManager = asyncTaskManager;
}
/**
* 异步任务执行的环绕切面
* 环绕切面让我们可以在方法执行之前和执行之后做一些 "额外" 的操作
* */
@Around("execution(* com.taluohui.ecommerce.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;
}
}
异步服务对外接口接口
/**
* 异步任务服务对外提供的 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);
}
}
创建Http文件,验证接口的可用性
###导入商品
POST http://localhost:9001/shuai/ecommerce-goods-service/async-goods/import-goods
Content-Type: application/json
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcInNodWFpemhhbGVcIn0iLCJqdGkiOiIzNjcxM2Y5NC03YjE2LTRkOTUtOTlkOS1lMzU0NWNhNGIwMGQiLCJleHAiOjE2NDc1MzI4MDB9.HuNiwCyukpsfRoWnvQMeYmOsBqOuCFZOFhd6p8M3dx-mc12uWT5Jv6LnyMN0hpewqHre5uST4gBumlNMmN53z0jARcSJPZvYYxnrL5XnIS5iTzAat_ZFwkc0T_t7aBjMBmjcAjbjBT-xaSB7jBKSKZIfCwBE5ZL4UYqsXF39BE6SwEgLgSpakCVPG_AFj8sCW2jIVjuM4uVOqa0LwOJtTIaOnsNu2VQjYdw3Lp08Dkg8O-DQ91h5IIg1M-OL8u2mxGcxam9zAKLTZH-m4_e9nRBHKIyM3GUiQTWMAnSa93q6201lvAxd1ItWGDDjHaP6L5AuwDVHDuQXOI_ArDq5KQ
[
{
"goodsCategory": "10001",
"brandCategory": "20001",
"goodsName": "iphone 11",
"goodsPic": "",
"goodsDescription": "苹果手机",
"price": 10000,
"supply": 200000,
"goodsProperty": {
"size": "12cm * 6.5cm",
"color": "绿色",
"material": "金属机身",
"pattern": "纯色"
}
}
]
### 查询导入商品状态
GET http://127.0.0.1:9001/shuai/ecommerce-goods-service/async-goods/task-info?taskId=f5c1c6ff-4efb-45e5-a8c9-9f3d4515228a
Accept: application/json
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjAyYTRiZDcxLWUyMTgtNGZmYS1hYzQ3LWE5MGQxNWIzYmEwYSIsImV4cCI6MTYyNDgwOTYwMH0.UWbvqkIq5b5bb-WomLziZyCmjqCqsdeU1EZ0TfWrloRoY7WwqmYGDsf2GnE7JBgVLM0DibhSkkrkXu-wdjzWnqtxLkQ5UgON9BdPm1ZYLvllLcbAMv8KAdbXiC1_FiZ9q1tM6vGXlKU4-G1t88cUUP1_xXOGY9PvC5yGr31lQXCc0Nni4Ds4WwDPHvOq9YBVILdaWYeFsxIWi0pTGwcAxaCkp3BdsvPkJ3uXmrmzuLgkorkfITsmJqdaBuiSCD74LK0F-CvvCv09qizij627O3RuTrpbBfdFjDXT5xyRcKXxAR-n6oFGZdG-JUqh3iXWv_JdsyW-d8wPk3-DZ5zufA
商品普通接口的编写
/**
* 商品微服务相关服务功能实现
* */
@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
/**
* 商品微服务对外暴露的功能服务 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);
}
}
### 根据 TableId 查询详细商品信息
POST http://127.0.0.1:9001/shuai/ecommerce-goods-service/goods/goods-info
Content-Type: application/json
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjI3NGUzYzQ3LTRmNTQtNDdlYy05MGNhLTcxNzYyMjcyN2EzYyIsImV4cCI6MTYyNDk4MjQwMH0.TUy1C-9FkpyGkTxjyAKP9tX4mFzdZ22RWYvtKOOUUwjFefHSESamFWTJ2l0PcJJp07EIpzKgk9sNnVRZ5NmW6_Beo2AQgPOMWbYHiJg7eiR0bVC2CK6Tw8rUwgpkoWSXePgUM_3kntvXc19mgzO1NLVPNw5gahkBigzDffrXVUuXyc6kAf6L-y37hCytqfUwpgwQYm4Z2G7tUmF0_BsnQR4qHuWHrEdHm3_8Y8V38Ph_1VAlcJGvNXZS3bqtBxWHa2Wf7WksVA-H3dO_7xO7AlGJvUNOyiMGOjvMiwXc5mbqqqe6KXnvr9W1CvAPFmR-nlmc81wiCqW5Yfwo2Rh_5A
{
"ids": [
{
"id": 1
},
{
"id": 2
}
]
}
### 根据分页查询简单商品信息
GET http://127.0.0.1:9001/shuai/ecommerce-goods-service/goods/page-simple-goods-info?page=2
Accept: application/json
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjI3NGUzYzQ3LTRmNTQtNDdlYy05MGNhLTcxNzYyMjcyN2EzYyIsImV4cCI6MTYyNDk4MjQwMH0.TUy1C-9FkpyGkTxjyAKP9tX4mFzdZ22RWYvtKOOUUwjFefHSESamFWTJ2l0PcJJp07EIpzKgk9sNnVRZ5NmW6_Beo2AQgPOMWbYHiJg7eiR0bVC2CK6Tw8rUwgpkoWSXePgUM_3kntvXc19mgzO1NLVPNw5gahkBigzDffrXVUuXyc6kAf6L-y37hCytqfUwpgwQYm4Z2G7tUmF0_BsnQR4qHuWHrEdHm3_8Y8V38Ph_1VAlcJGvNXZS3bqtBxWHa2Wf7WksVA-H3dO_7xO7AlGJvUNOyiMGOjvMiwXc5mbqqqe6KXnvr9W1CvAPFmR-nlmc81wiCqW5Yfwo2Rh_5A
### 根据 TableId 查询简单商品信息: 完整的 goods cache
### 第二步验证, 删掉 cache
### 第三步验证, 删除 cache 中其中一个商品
POST http://127.0.0.1:9001/shuai/ecommerce-goods-service/goods/simple-goods-info
Content-Type: application/json
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjI3NGUzYzQ3LTRmNTQtNDdlYy05MGNhLTcxNzYyMjcyN2EzYyIsImV4cCI6MTYyNDk4MjQwMH0.TUy1C-9FkpyGkTxjyAKP9tX4mFzdZ22RWYvtKOOUUwjFefHSESamFWTJ2l0PcJJp07EIpzKgk9sNnVRZ5NmW6_Beo2AQgPOMWbYHiJg7eiR0bVC2CK6Tw8rUwgpkoWSXePgUM_3kntvXc19mgzO1NLVPNw5gahkBigzDffrXVUuXyc6kAf6L-y37hCytqfUwpgwQYm4Z2G7tUmF0_BsnQR4qHuWHrEdHm3_8Y8V38Ph_1VAlcJGvNXZS3bqtBxWHa2Wf7WksVA-H3dO_7xO7AlGJvUNOyiMGOjvMiwXc5mbqqqe6KXnvr9W1CvAPFmR-nlmc81wiCqW5Yfwo2Rh_5A
{
"ids": [
{
"id": 1
},
{
"id": 2
}
]
}
### 扣减商品库存
PUT http://127.0.0.1:9001/shuai/ecommerce-goods-service/goods/deduct-goods-inventory
Content-Type: application/json
e-commerce-user: eyJhbGciOiJSUzI1NiJ9.eyJlLWNvbW1lcmNlLXVzZXIiOiJ7XCJpZFwiOjEwLFwidXNlcm5hbWVcIjpcIlFpbnlpQGltb29jLmNvbVwifSIsImp0aSI6IjI3NGUzYzQ3LTRmNTQtNDdlYy05MGNhLTcxNzYyMjcyN2EzYyIsImV4cCI6MTYyNDk4MjQwMH0.TUy1C-9FkpyGkTxjyAKP9tX4mFzdZ22RWYvtKOOUUwjFefHSESamFWTJ2l0PcJJp07EIpzKgk9sNnVRZ5NmW6_Beo2AQgPOMWbYHiJg7eiR0bVC2CK6Tw8rUwgpkoWSXePgUM_3kntvXc19mgzO1NLVPNw5gahkBigzDffrXVUuXyc6kAf6L-y37hCytqfUwpgwQYm4Z2G7tUmF0_BsnQR4qHuWHrEdHm3_8Y8V38Ph_1VAlcJGvNXZS3bqtBxWHa2Wf7WksVA-H3dO_7xO7AlGJvUNOyiMGOjvMiwXc5mbqqqe6KXnvr9W1CvAPFmR-nlmc81wiCqW5Yfwo2Rh_5A
[
{
"goodsId": 1,
"count": 100
},
{
"goodsId": 2,
"count": 34
}
]