Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat merge 0128 #200

Merged
merged 6 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ image:https://github.com/techa03/goodsKill/actions/workflows/codeql.yml/badge.sv

本项目为模拟秒杀项目,提供统一秒杀模拟请求接口,方便自行测试各个秒杀方案的执行速度。 技术上整体采用SpringMVC + Mybatis持久层框架,采用Dubbo3.x{empty}footnote:[由于SpringCloudAlibaba官方暂未支持Dubbo 3.x,本项目采用dubbo-spring-boot-starter集成],服务注册发现以及配置中心使用Nacos,支持数据库分库分表、分布式事务,使用状态机完成数据状态间的转换(基于Spring Statemachine实现)。

== 💎分支介绍
== 💎 分支介绍

``master``分支基于最新Spring Cloud 2022.x+ Spring Boot 3.x + JDK17体系构建,目前仅保留核心的模拟秒杀API接口,如需使用Spring Boot 2.7.x + JDK11版本可以切换到tag https://github.com/techa03/goodsKill/tree/v2.7.4[v2.7.4](支持登录注册以及简单的后台管理功能)。master分支目前升级改造中,功能不够稳定,如遇代码报错建议使用老版本。

Expand All @@ -21,7 +21,7 @@ ____

____

== ✨技术选型
== ✨ 技术选型

|===
|使用的工具或框架 |名称 |官网
Expand All @@ -47,7 +47,7 @@ ____
|Spring Statemachine |Spring 状态机 |https://spring.io/projects/spring-statemachine
|===

== 📝项目模块介绍
== 📝 项目模块介绍

----
goodsKill
Expand All @@ -69,7 +69,7 @@ goodsKill
|--oauth2-resource-server ||oauth2.0资源服务端,自定义的登录授权服务
----

== 🔥🔥秒杀方案
== 🔥🔥 秒杀方案

目前实现了几种秒杀方案,通过``SeckillMockController``提供测试接口

Expand Down Expand Up @@ -114,7 +114,7 @@ ns % Task name
----
====

== 🧰开发环境版本说明
== 🧰 开发环境版本说明

* JDK: OpenJDK17
* Sharding-JDBC: 5.3.2
Expand All @@ -140,7 +140,7 @@ ns % Task name
|RabbitMQ |latest |5672 15672 |无
|===

== 🎯快速开始
== 🎯 快速开始

* 项目根目录``goodsKill``中执行
+
Expand Down Expand Up @@ -227,7 +227,7 @@ curl --location --request POST 'http://www.goodskill.com:8080/limit' \
请求默认异步执行,可在控制台查看执行日志,如果最终成功交易笔数等于商品数量10则表示没有出现超卖或者少卖问题


== 🕹️️启动完整项目步骤
== 🕹️️ 启动完整项目步骤
在快速开始部分基础上增加以下步骤即可启动一个完整项目

* 进入``goodskill-gateway``模块,通过``GatewayApplication``类main方法启动服务网关
Expand All @@ -244,9 +244,6 @@ ____

* main方法运行``GoodskillAdminApplication``类(微服务健康状态指标监控)

== ✔️项目已知问题
* oauth2模块升级后兼容性尚未测试
* web服务移除shiro后的鉴权问题

== ❓常见问题
[qanda]
Expand Down Expand Up @@ -290,6 +287,15 @@ hub.docker被墙,国内可使用阿里云镜像加速器,具体操作见 htt
--add-opens java.base/sun.reflect.generics.reflectiveObjects=ALL-UNNAMED
----

== 🖲️ 状态机说明
目前秒杀活动状态的控制基于Spring Statemachine状态机实现,使用状态机的优点:

* 统一控制活动状态,便于状态的集中维护;
* 防止业务状态被随意更改,保证状态的可控更新;
+
image:./doc/shortcut/状态机.png[image]


== 📚分库分表情况说明

|===
Expand Down
Binary file added doc/shortcut/状态机.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.goodskill.common.core.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
* 支付事件
*/
@Getter
@AllArgsConstructor
public enum Events {
// 活动创建
ACTIVITY_CREATE,
// 活动开始
ACTIVITY_START,
// 活动开始结算
ACTIVITY_CALCULATE,
// 活动结束
ACTIVITY_END,
// 中止活动
ACTIVITY_INTERRUPT,
// 重置
ACTIVITY_RESET,
;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.goodskill.common.core.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
* 秒杀活动状态枚举
* @author techa03
* @since 2024/1/20
*/
@AllArgsConstructor
@Getter
public enum States {
/**
* 活动已创建尚未开始
*/
INIT,
/**
* 活动进行中
*/
IN_PROGRESS,
/**
* 活动结束结算中(统计成功交易笔数),
*/
CALCULATING,
/**
* 活动已结束
*/
END,
/**
* 活动已中断
*/
INTERRUPTTED,
;

// // 支付状态机内容
// private static final StateMachine<SeckillActivityStatesEnum, ActivityEvent> STATE_MACHINE = new StateMachine<>();
// static {
// STATE_MACHINE.accept(null, ActivityEvent.ACTIVITY_CREATE, INIT);
// STATE_MACHINE.accept(INIT, ActivityEvent.ACTIVITY_START, IN_PROGRESS);
// STATE_MACHINE.accept(IN_PROGRESS, ActivityEvent.ACTIVITY_END, END);
// STATE_MACHINE.accept(IN_PROGRESS, ActivityEvent.ACTIVITY_INTERRUPT, INTERRUPTTED);
// }

}
10 changes: 10 additions & 0 deletions goodskill-seckill-provider/goodskill-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<artifactId>kryo</artifactId>
<groupId>com.esotericsoftware</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -193,6 +199,10 @@
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-data-redis</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package com.goodskill.service.config;

import com.goodskill.common.core.enums.ActivityEvent;
import com.goodskill.common.core.enums.SeckillActivityStates;
import com.goodskill.common.core.enums.Events;
import com.goodskill.common.core.enums.States;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.data.redis.RedisStateMachineContextRepository;
import org.springframework.statemachine.data.redis.RedisStateMachinePersister;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.persist.RepositoryStateMachinePersist;
import org.springframework.statemachine.state.State;

import java.util.EnumSet;

import static com.goodskill.common.core.enums.ActivityEvent.*;
import static com.goodskill.common.core.enums.SeckillActivityStates.*;
import static com.goodskill.common.core.enums.Events.*;
import static com.goodskill.common.core.enums.States.*;

/**
* 控制秒杀活动的状态机配置
Expand All @@ -29,10 +34,10 @@
@EnableStateMachine
@Slf4j
public class StateMachineConfig
extends EnumStateMachineConfigurerAdapter<SeckillActivityStates, ActivityEvent> {
extends EnumStateMachineConfigurerAdapter<States, Events> {

@Override
public void configure(StateMachineConfigurationConfigurer<SeckillActivityStates, ActivityEvent> config)
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
throws Exception {
config
.withConfiguration()
Expand All @@ -41,17 +46,17 @@ public void configure(StateMachineConfigurationConfigurer<SeckillActivityStates,
}

@Override
public void configure(StateMachineStateConfigurer<SeckillActivityStates, ActivityEvent> states)
public void configure(StateMachineStateConfigurer<States, Events> states)
throws Exception {
// 状态初始化
states
.withStates()
.initial(INIT)
.states(EnumSet.allOf(SeckillActivityStates.class));
.states(EnumSet.allOf(States.class));
}

@Override
public void configure(StateMachineTransitionConfigurer<SeckillActivityStates, ActivityEvent> transitions)
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
// 控制流程状态跳转
transitions
Expand Down Expand Up @@ -85,26 +90,32 @@ public void configure(StateMachineTransitionConfigurer<SeckillActivityStates, Ac
}

@Bean
public StateMachineListener<SeckillActivityStates, ActivityEvent> listener() {
public StateMachineListener<States, Events> listener() {
return new StateMachineListenerAdapter<>() {
@Override
public void stateChanged(State<SeckillActivityStates, ActivityEvent> from, State<SeckillActivityStates, ActivityEvent> to) {
public void stateChanged(State<States, Events> from, State<States, Events> to) {
log.info("State change to {}", to.getId());
}

@Override
public void eventNotAccepted(Message<ActivityEvent> event) {
public void eventNotAccepted(Message<Events> event) {
log.info("Event not accepted {}", event.getPayload());
super.eventNotAccepted(event);
}
};
}

// @Bean
// public Guard<SeckillActivityStates, ActivityEvent> guard() {
// return context -> {
// context.getEvent()
// };
// }
@Bean
public StateMachinePersist<States, Events, String> stateMachinePersist(RedisConnectionFactory connectionFactory) {
RedisStateMachineContextRepository<States, Events> repository =
new RedisStateMachineContextRepository<States, Events>(connectionFactory);
return new RepositoryStateMachinePersist<States, Events>(repository);
}

@Bean
public RedisStateMachinePersister<States, Events> redisStateMachinePersister(
StateMachinePersist<States, Events, String> stateMachinePersist) {
return new RedisStateMachinePersister<States, Events>(stateMachinePersist);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
import com.goodskill.api.vo.GoodsVO;
import com.goodskill.api.vo.SeckillVO;
import com.goodskill.common.core.constant.SeckillStatusConstant;
import com.goodskill.common.core.enums.ActivityEvent;
import com.goodskill.common.core.enums.SeckillActivityStates;
import com.goodskill.common.core.enums.Events;
import com.goodskill.common.core.exception.SeckillCloseException;
import com.goodskill.common.core.util.MD5Util;
import com.goodskill.order.api.SuccessKilledMongoService;
Expand All @@ -24,6 +23,7 @@
import com.goodskill.service.mapper.SuccessKilledMapper;
import com.goodskill.service.mock.strategy.GoodsKillStrategy;
import com.goodskill.service.mock.strategy.GoodsKillStrategyEnum;
import com.goodskill.service.util.StateMachineUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
Expand All @@ -34,7 +34,6 @@
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.statemachine.StateMachine;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -48,8 +47,6 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.goodskill.service.util.StateMachineUtil.feedMachine;

/**
* <p>
* 秒杀库存表 服务实现类
Expand Down Expand Up @@ -85,7 +82,7 @@ public class SeckillServiceImpl extends ServiceImpl<SeckillMapper, Seckill> impl
@Resource
private SeckillMapper baseMapper;
@Resource
private StateMachine<SeckillActivityStates, ActivityEvent> activityStateMachine;
private StateMachineUtil stateMachineUtil;

@Override
public PageDTO<SeckillVO> getSeckillList(int pageNum, int pageSize, String goodsName) {
Expand Down Expand Up @@ -176,15 +173,15 @@ public long getSuccessKillCount(Long seckillId) {
public void prepareSeckill(Long seckillId, int seckillCount, String taskId) {
// 初始化库存数量
// 使用状态机控制活动状态
if (!feedMachine(activityStateMachine, ActivityEvent.ACTIVITY_RESET)) {
throw new RuntimeException("当前状态不允许重置");
if (!stateMachineUtil.feedMachine(Events.ACTIVITY_RESET, seckillId)) {
throw new RuntimeException("活动尚未结束,请等待活动结束后再次操作");
}
Seckill entity = new Seckill();
entity.setSeckillId(seckillId);
entity.setNumber(seckillCount);
baseMapper.updateById(entity);

feedMachine(activityStateMachine, ActivityEvent.ACTIVITY_START);
stateMachineUtil.feedMachine(Events.ACTIVITY_START, seckillId);
// 清理已成功秒杀记录
this.deleteSuccessKillRecord(seckillId);

Expand Down Expand Up @@ -266,7 +263,7 @@ public SeckillInfoDTO getInfoById(Serializable seckillId) {

@Override
public boolean endSeckill(Long seckillId) {
return feedMachine(activityStateMachine, ActivityEvent.ACTIVITY_END);
return stateMachineUtil.feedMachine(Events.ACTIVITY_END, seckillId);
}

@Override
Expand Down
Loading
Loading