(MK)Spring3仿12306
简介
主要功能
模块划分
gateway 网关模块:路由转发、登录校验
member 会员模块:会员、乘客、已购买的车票
business 业务模块:所有的车次数据、余票信息
batch 跑批模块:所有的定时任务,可通过界面启停
web 模块:会员相关界面
admin 模块:管理员相关界面
Springcloud Alibaba nacos配置注册中心
MySQL
Redis,reddison
rocketMQ
sential限流熔断
seata分布式事务
quartz定时任务
父模块
主要用来管理多模块项目和公共依赖
pom
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<module>member</module>
<module>common</module>
<module>gateway</module>
<module>generator</module>
<module>business</module>
<module>batch</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jiawa</groupId>
<artifactId>train</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>train</name>
<description>train</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
<dependency>
<groupId>com.jiawa</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 集成mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- 集成mysql连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
<!-- spring cloud alibaba https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
https://github.com/alibaba/spring-cloud-alibaba/blob/2022.x/README-zh.md
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2022.0.0.0-RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--至少3.18.0版本,才支持spring boot 3-->
<!--升级到3.20.0,否则打包生产会报错:Could not initialize class org.redisson.spring.data.connection.RedissonConnection-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.21.0</version>
</dependency>
<!-- 图形验证码 升级到JDK17后,排除掉javax.servlet-api包 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
common公共模块
放一些公共资源
pom
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>train</artifactId>
<groupId>com.jiawa</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 集成mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- 集成mysql连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<!--SpringBoot 2.4版本之后 SpringCloud 2020,需要引入该依赖,才能读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--spring内置缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--<dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-alibaba-seata</artifactId>-->
<!--</dependency>-->
</dependencies>
</project>
日志切面
用来打印整个流程的日志
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
@Aspect
@Component
public class LogAspect {
public LogAspect() {
System.out.println("Common LogAspect");
}
private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);
/**
* 定义一个切点
*/
@Pointcut("execution(public * com.jiawa..*Controller.*(..))")
public void controllerPointcut() {
}
@Before("controllerPointcut()")
public void doBefore(JoinPoint joinPoint) {
//添加日志跟踪ID
//MDC.put("LOG_ID",System.currentTimeMillis()+RandomUtil.randomString(3));
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Signature signature = joinPoint.getSignature();
String name = signature.getName();
// 打印请求信息
LOG.info("------------- 开始 -------------");
LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);
LOG.info("远程地址: {}", request.getRemoteAddr());
// 打印请求参数
Object[] args = joinPoint.getArgs();
// LOG.info("请求参数: {}", JSONObject.toJSONString(args));
// 排除特殊类型的参数,如文件类型
Object[] arguments = new Object[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof ServletRequest
|| args[i] instanceof ServletResponse
|| args[i] instanceof MultipartFile) {
continue;
}
arguments[i] = args[i];
}
// 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等
String[] excludeProperties = {};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
LOG.info("请求参数: {}", JSONObject.toJSONString(arguments, excludefilter));
}
@Around("controllerPointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
// 排除字段,敏感字段或太长的字段不显示:身份证、手机号、邮箱、密码等
String[] excludeProperties = {};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
LOG.info("返回结果: {}", JSONObject.toJSONString(result, excludefilter));
LOG.info("------------- 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
return result;
}
}
====
json-config
处理json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// package com.jiawa.train.common.config;
//
// import com.fasterxml.jackson.databind.ObjectMapper;
// import com.fasterxml.jackson.databind.module.SimpleModule;
// import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
// import org.springframework.context.annotation.Bean;
// import org.springframework.context.annotation.Configuration;
// import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
//
// /**
// * 统一注解,解决前后端交互Long类型精度丢失的问题
// */
// @Configuration
// public class JacksonConfig {
// @Bean
// public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
// ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// SimpleModule simpleModule = new SimpleModule();
// simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
// objectMapper.registerModule(simpleModule);
// return objectMapper;
// }
// }
===
登录会员上下文
存储登录的用户
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
package com.jiawa.train.common.context;
import com.jiawa.train.common.resp.MemberLoginResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoginMemberContext {
private static final Logger LOG = LoggerFactory.getLogger(LoginMemberContext.class);
private static ThreadLocal<MemberLoginResp> member = new ThreadLocal<>();
public static MemberLoginResp getMember() {
return member.get();
}
public static void setMember(MemberLoginResp member) {
LoginMemberContext.member.set(member);
}
public static Long getId() {
try {
return member.get().getId();
} catch (Exception e) {
LOG.error("获取登录会员信息异常", e);
throw e;
}
}
}
===
统一异常处理
使用Spring的ControllerAdvice
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.jiawa.train.common.controller;
import com.jiawa.train.common.exception.BusinessException;
import com.jiawa.train.common.resp.CommonResp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 统一异常处理、数据预处理等
*/
@ControllerAdvice
public class ControllerExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class);
/**
* 所有异常统一处理
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public CommonResp exceptionHandler(Exception e) throws Exception {
// LOG.info("seata全局事务ID: {}", RootContext.getXID());
// // 如果是在一次全局事务里出异常了,就不要包装返回值,将异常抛给调用方,让调用方回滚事务
// if (StrUtil.isNotBlank(RootContext.getXID())) {
// throw e;
// }
CommonResp commonResp = new CommonResp();
LOG.error("系统异常:", e);
commonResp.setSuccess(false);
commonResp.setMessage("系统出现异常,请联系管理员");
return commonResp;
}
/**
* 业务异常统一处理
* @param e
* @return
*/
@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public CommonResp exceptionHandler(BusinessException e) {
CommonResp commonResp = new CommonResp();
LOG.error("业务异常:{}", e.getE().getDesc());
commonResp.setSuccess(false);
commonResp.setMessage(e.getE().getDesc());
return commonResp;
}
/**
* 校验异常统一处理
* @param e
* @return
*/
@ExceptionHandler(value = BindException.class)
@ResponseBody
public CommonResp exceptionHandler(BindException e) {
CommonResp commonResp = new CommonResp();
LOG.error("校验异常:{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
commonResp.setSuccess(false);
commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return commonResp;
}
/**
* 校验异常统一处理
* @param e
* @return
*/
@ExceptionHandler(value = RuntimeException.class)
@ResponseBody
public CommonResp exceptionHandler(RuntimeException e) {
throw e;
}
}
===
自定义异常
业务异常
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
package com.jiawa.train.common.exception;
public class BusinessException extends RuntimeException {
private BusinessExceptionEnum e;
public BusinessException(BusinessExceptionEnum e) {
this.e = e;
}
public BusinessExceptionEnum getE() {
return e;
}
public void setE(BusinessExceptionEnum e) {
this.e = e;
}
/**
* 不写入堆栈信息,提高性能
*/
@Override
public Throwable fillInStackTrace() {
return this;
}
}
异常类型枚举
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
package com.jiawa.train.common.exception;
public enum BusinessExceptionEnum {
MEMBER_MOBILE_EXIST("手机号已注册"),
MEMBER_MOBILE_NOT_EXIST("请先获取短信验证码"),
MEMBER_MOBILE_CODE_ERROR("短信验证码错误"),
BUSINESS_STATION_NAME_UNIQUE_ERROR("车站已存在"),
BUSINESS_TRAIN_CODE_UNIQUE_ERROR("车次编号已存在"),
BUSINESS_TRAIN_STATION_INDEX_UNIQUE_ERROR("同车次站序已存在"),
BUSINESS_TRAIN_STATION_NAME_UNIQUE_ERROR("同车次站名已存在"),
BUSINESS_TRAIN_CARRIAGE_INDEX_UNIQUE_ERROR("同车次厢号已存在"),
CONFIRM_ORDER_TICKET_COUNT_ERROR("余票不足"),
CONFIRM_ORDER_EXCEPTION("服务器忙,请稍候重试"),
CONFIRM_ORDER_LOCK_FAIL("当前抢票人数过多,请稍候重试"),
CONFIRM_ORDER_FLOW_EXCEPTION("当前抢票人数太多了,请稍候重试"),
CONFIRM_ORDER_SK_TOKEN_FAIL("当前抢票人数过多,请5秒后重试"),
;
private String desc;
BusinessExceptionEnum(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "BusinessExceptionEnum{" +
"desc='" + desc + '\'' +
"} " + super.toString();
}
}
===
日志拦截器
日志拦截
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.jiawa.train.common.interceptor;
import cn.hutool.core.util.RandomUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 日志拦截器
*/
@Component
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 增加日志流水号
MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3));
return true;
}
}
===
会员登录拦截器
拦截登录
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
package com.jiawa.train.common.interceptor;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.jiawa.train.common.util.JwtUtil;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.MemberLoginResp;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印
*/
@Component
public class MemberInterceptor implements HandlerInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(MemberInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LOG.info("MemberInterceptor开始");
//获取header的token参数
String token = request.getHeader("token");
if (StrUtil.isNotBlank(token)) {
LOG.info("获取会员登录token:{}", token);
JSONObject loginMember = JwtUtil.getJSONObject(token);
LOG.info("当前登录会员:{}", loginMember);
MemberLoginResp member = JSONUtil.toBean(loginMember, MemberLoginResp.class);
LoginMemberContext.setMember(member);
}
LOG.info("MemberInterceptor结束");
return true;
}
}
===
req
分页page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.jiawa.train.common.req;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotNull;
@Data
public class PageReq {
@NotNull(message = "【页码】不能为空")
private Integer page;
@NotNull(message = "【每页条数】不能为空")
@Max(value = 100, message = "【每页条数】不能超过100")
private Integer size;
}
会员车票
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.jiawa.train.common.req;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.Date;
@Data
public class MemberTicketReq {
/**
* 乘客id
*/
@NotNull(message = "【会员id】不能为空")
private Long memberId;
/**
* 乘客id
*/
@NotNull(message = "【乘客id】不能为空")
private Long passengerId;
/**
* 乘客id
*/
@NotNull(message = "【乘客名字】不能为空")
private String passengerName;
/**
* 日期
*/
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
@NotNull(message = "【日期】不能为空")
private Date trainDate;
/**
* 车次编号
*/
@NotBlank(message = "【车次编号】不能为空")
private String trainCode;
/**
* 箱序
*/
@NotNull(message = "【箱序】不能为空")
private Integer carriageIndex;
/**
* 排号|01, 02
*/
@NotBlank(message = "【排号】不能为空")
private String seatRow;
/**
* 列号|枚举[SeatColumnEnum]
*/
@NotBlank(message = "【列号】不能为空")
private String seatCol;
/**
* 出发站
*/
@NotBlank(message = "【出发站】不能为空")
private String startStation;
/**
* 出发时间
*/
@JsonFormat(pattern = "HH:mm:ss",timezone = "GMT+8")
@NotNull(message = "【出发时间】不能为空")
private Date startTime;
/**
* 到达站
*/
@NotBlank(message = "【到达站】不能为空")
private String endStation;
/**
* 到站时间
*/
@JsonFormat(pattern = "HH:mm:ss",timezone = "GMT+8")
@NotNull(message = "【到站时间】不能为空")
private Date endTime;
/**
* 座位类型|枚举[SeatTypeEnum]
*/
@NotBlank(message = "【座位类型】不能为空")
private String seatType;
}
===
resp
公共返回
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
package com.jiawa.train.common.resp;
public class CommonResp<T> {
/**
* 业务上的成功或失败
*/
private boolean success = true;
/**
* 返回信息
*/
private String message;
/**
* 返回泛型数据,自定义类型
*/
private T content;
public CommonResp() {
}
public CommonResp(boolean success, String message, T content) {
this.success = success;
this.message = message;
this.content = content;
}
public CommonResp(T content) {
this.content = content;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("CommonResp{");
sb.append("success=").append(success);
sb.append(", message='").append(message).append('\'');
sb.append(", content=").append(content);
sb.append('}');
return sb.toString();
}
}
分页返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.jiawa.train.common.resp;
import java.io.Serializable;
import java.util.List;
public class PageResp<T> implements Serializable {
/**
* 总条数
*/
private Long total;
/**
* 当前页的列表
*/
private List<T> list;
}
会员登录
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.jiawa.train.common.resp;
@Data
public class MemberLoginResp {
private Long id;
private String mobile;
private String token;
}
===
utils
雪花算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.jiawa.train.common.util;
import cn.hutool.core.util.IdUtil;
/**
* 封装hutool雪花算法
*/
public class SnowUtil {
private static long dataCenterId = 1; //数据中心
private static long workerId = 1; //机器标识
public static long getSnowflakeNextId() {
return IdUtil.getSnowflake(workerId, dataCenterId).nextId();
}
public static String getSnowflakeNextIdStr() {
return IdUtil.getSnowflake(workerId, dataCenterId).nextIdStr();
}
}
JWT令牌
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
63
64
65
66
67
68
69
70
71
72
73
74
package com.jiawa.train.common.util;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.crypto.GlobalBouncyCastleProvider;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
private static final Logger LOG = LoggerFactory.getLogger(JwtUtil.class);
/**
* 盐值很重要,不能泄漏,且每个项目都应该不一样,可以放到配置文件中
*/
private static final String key = "Jiawa12306";
public static String createToken(Long id, String mobile) {
LOG.info("开始生成JWT token,id:{},mobile:{}", id, mobile);
GlobalBouncyCastleProvider.setUseBouncyCastle(false);
DateTime now = DateTime.now();
DateTime expTime = now.offsetNew(DateField.HOUR, 24);
Map<String, Object> payload = new HashMap<>();
// 签发时间
payload.put(JWTPayload.ISSUED_AT, now);
// 过期时间
payload.put(JWTPayload.EXPIRES_AT, expTime);
// 生效时间
payload.put(JWTPayload.NOT_BEFORE, now);
// 内容
payload.put("id", id);
payload.put("mobile", mobile);
String token = JWTUtil.createToken(payload, key.getBytes());
LOG.info("生成JWT token:{}", token);
return token;
}
public static boolean validate(String token) {
LOG.info("开始JWT token校验,token:{}", token);
GlobalBouncyCastleProvider.setUseBouncyCastle(false);
JWT jwt = JWTUtil.parseToken(token).setKey(key.getBytes());
// validate包含了verify
boolean validate = jwt.validate(0);
LOG.info("JWT token校验结果:{}", validate);
return validate;
}
public static JSONObject getJSONObject(String token) {
GlobalBouncyCastleProvider.setUseBouncyCastle(false);
JWT jwt = JWTUtil.parseToken(token).setKey(key.getBytes());
JSONObject payloads = jwt.getPayloads();
payloads.remove(JWTPayload.ISSUED_AT);
payloads.remove(JWTPayload.EXPIRES_AT);
payloads.remove(JWTPayload.NOT_BEFORE);
LOG.info("根据token获取原始内容:{}", payloads);
return payloads;
}
public static void main(String[] args) {
createToken(1L, "123");
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYmYiOjE2NzY4OTk4MjcsIm1vYmlsZSI6IjEyMyIsImlkIjoxLCJleHAiOjE2NzY4OTk4MzcsImlhdCI6MTY3Njg5OTgyN30.JbFfdeNHhxKhAeag63kifw9pgYhnNXISJM5bL6hM8eU";
validate(token);
getJSONObject(token);
}
}
generator代码生成器模块
pom
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>train</artifactId>
<groupId>com.jiawa</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>generator</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- 模板引擎freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!-- 读xml -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jaxen/jaxen -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 集成mysql连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.jiawa</groupId>
<artifactId>member</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.jiawa</groupId>
<artifactId>business</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- mybatis generator 自动生成代码插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.0</version>
<configuration>
<!--<configurationFile>src/main/resources/generator-config-member.xml</configurationFile>-->
<configurationFile>src/main/resources/generator-config-business.xml</configurationFile>
<!--<configurationFile>src/main/resources/generator-config-batch.xml</configurationFile>-->
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
resource
生成配置generator-config-member.xml
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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat">
<!-- 自动检查关键字,为关键字增加反引号 -->
<property name="autoDelimitKeywords" value="true"/>
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!--覆盖生成XML文件-->
<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin" />
<!-- 生成的实体类添加toString()方法 -->
<plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
<!-- 不生成注释 -->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- 配置数据源,需要根据自己的项目修改 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://rm-uf62v90x7t5hukv5dro.mysql.rds.aliyuncs.com/train_member?serverTimezone=Asia/Shanghai"
userId="train_member"
password="Member123">
</jdbcConnection>
<!-- domain类的位置 targetProject是相对pom.xml的路径-->
<javaModelGenerator targetProject="../member/src/main/java"
targetPackage="com.jiawa.train.member.domain"/>
<!-- mapper xml的位置 targetProject是相对pom.xml的路径 -->
<sqlMapGenerator targetProject="../member/src/main/resources"
targetPackage="mapper"/>
<!-- mapper类的位置 targetProject是相对pom.xml的路径 -->
<javaClientGenerator targetProject="../member/src/main/java"
targetPackage="com.jiawa.train.member.mapper"
type="XMLMAPPER"/>
<!--<table tableName="member" domainObjectName="Member"/>-->
<!--<table tableName="passenger" domainObjectName="Passenger"/>-->
<table tableName="ticket" domainObjectName="Ticket"/>
</context>
</generatorConfiguration>
模板
文件queryReq.ftl
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.jiawa.train.${module}.req;
import com.jiawa.train.common.req.PageReq;
public class ${Domain}QueryReq extends PageReq {
@Override
public String toString() {
return "${Domain}QueryReq{" +
"} " + super.toString();
}
}
文件queryResp.ftl
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
package com.jiawa.train.${module}.resp;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
<#list typeSet as type>
<#if type=='Date'>
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
</#if>
<#if type=='BigDecimal'>
import java.math.BigDecimal;
</#if>
</#list>
public class ${Domain}QueryResp {
<#list fieldList as field>
/**
* ${field.comment}
*/
<#if field.javaType=='Date'>
<#if field.type=='time'>
@JsonFormat(pattern = "HH:mm:ss",timezone = "GMT+8")
<#elseif field.type=='date'>
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
<#else>
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
</#if>
</#if>
<#if field.name=='id' || field.name?ends_with('_id')>
@JsonSerialize(using= ToStringSerializer.class)
</#if>
private ${field.javaType} ${field.nameHump};
</#list>
<#list fieldList as field>
public ${field.javaType} get${field.nameBigHump}() {
return ${field.nameHump};
}
public void set${field.nameBigHump}(${field.javaType} ${field.nameHump}) {
this.${field.nameHump} = ${field.nameHump};
}
</#list>
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
<#list fieldList as field>
sb.append(", ${field.nameHump}=").append(${field.nameHump});
</#list>
sb.append("]");
return sb.toString();
}
}
文件saveReq.ftl
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
63
64
65
66
package com.jiawa.train.${module}.req;
<#list typeSet as type>
<#if type=='Date'>
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
</#if>
<#if type=='BigDecimal'>
import java.math.BigDecimal;
</#if>
</#list>
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public class ${Domain}SaveReq {
<#list fieldList as field>
/**
* ${field.comment}
*/
<#if field.javaType=='Date'>
<#if field.type=='time'>
@JsonFormat(pattern = "HH:mm:ss",timezone = "GMT+8")
<#elseif field.type=='date'>
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
<#else>
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
</#if>
</#if>
<#if field.name!="id" && field.nameHump!="createdAt" && field.nameHump!="updatedAt">
<#if !field.nullAble>
<#if field.javaType=='String'>
@NotBlank(message = "【${field.nameCn}】不能为空")
<#else>
@NotNull(message = "【${field.nameCn}】不能为空")
</#if>
</#if>
</#if>
private ${field.javaType} ${field.nameHump};
</#list>
<#list fieldList as field>
public ${field.javaType} get${field.nameBigHump}() {
return ${field.nameHump};
}
public void set${field.nameBigHump}(${field.javaType} ${field.nameHump}) {
this.${field.nameHump} = ${field.nameHump};
}
</#list>
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
<#list fieldList as field>
sb.append(", ${field.nameHump}=").append(${field.nameHump});
</#list>
sb.append("]");
return sb.toString();
}
}
文件service.ftl
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
63
64
65
66
67
68
69
70
71
package com.jiawa.train.${module}.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import com.jiawa.train.${module}.domain.${Domain};
import com.jiawa.train.${module}.domain.${Domain}Example;
import com.jiawa.train.${module}.mapper.${Domain}Mapper;
import com.jiawa.train.${module}.req.${Domain}QueryReq;
import com.jiawa.train.${module}.req.${Domain}SaveReq;
import com.jiawa.train.${module}.resp.${Domain}QueryResp;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ${Domain}Service {
private static final Logger LOG = LoggerFactory.getLogger(${Domain}Service.class);
@Resource
private ${Domain}Mapper ${domain}Mapper;
public void save(${Domain}SaveReq req) {
DateTime now = DateTime.now();
${Domain} ${domain} = BeanUtil.copyProperties(req, ${Domain}.class);
if (ObjectUtil.isNull(${domain}.getId())) {
${domain}.setId(SnowUtil.getSnowflakeNextId());
${domain}.setCreateTime(now);
${domain}.setUpdateTime(now);
${domain}Mapper.insert(${domain});
} else {
${domain}.setUpdateTime(now);
${domain}Mapper.updateByPrimaryKey(${domain});
}
}
public PageResp<${Domain}QueryResp> queryList(${Domain}QueryReq req) {
${Domain}Example ${domain}Example = new ${Domain}Example();
${domain}Example.setOrderByClause("id desc");
${Domain}Example.Criteria criteria = ${domain}Example.createCriteria();
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<${Domain}> ${domain}List = ${domain}Mapper.selectByExample(${domain}Example);
PageInfo<${Domain}> pageInfo = new PageInfo<>(${domain}List);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<${Domain}QueryResp> list = BeanUtil.copyToList(${domain}List, ${Domain}QueryResp.class);
PageResp<${Domain}QueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
${domain}Mapper.deleteByPrimaryKey(id);
}
}
文件adminController.ftl
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
package com.jiawa.train.${module}.controller.admin;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.${module}.req.${Domain}QueryReq;
import com.jiawa.train.${module}.req.${Domain}SaveReq;
import com.jiawa.train.${module}.resp.${Domain}QueryResp;
import com.jiawa.train.${module}.service.${Domain}Service;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/${do_main}")
public class ${Domain}AdminController {
@Resource
private ${Domain}Service ${domain}Service;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody ${Domain}SaveReq req) {
${domain}Service.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<${Domain}QueryResp>> queryList(@Valid ${Domain}QueryReq req) {
PageResp<${Domain}QueryResp> list = ${domain}Service.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
${domain}Service.delete(id);
return new CommonResp<>();
}
}
生成器
EnumGenerator
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package com.jiawa.train.generator.gen;
import cn.hutool.core.util.StrUtil;
import com.jiawa.train.business.enums.ConfirmOrderStatusEnum;
import com.jiawa.train.business.enums.SeatColEnum;
import com.jiawa.train.business.enums.SeatTypeEnum;
import com.jiawa.train.business.enums.TrainTypeEnum;
import com.jiawa.train.member.enums.PassengerTypeEnum;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class EnumGenerator {
// static String path = "web/src/assets/js/enums.js";
static String path = "admin/src/assets/js/enums.js";
public static void main(String[] args) {
StringBuffer bufferObject = new StringBuffer();
StringBuffer bufferArray = new StringBuffer();
long begin = System.currentTimeMillis();
try {
toJson(PassengerTypeEnum.class, bufferObject, bufferArray);
toJson(TrainTypeEnum.class, bufferObject, bufferArray);
toJson(SeatTypeEnum.class, bufferObject, bufferArray);
toJson(SeatColEnum.class, bufferObject, bufferArray);
toJson(ConfirmOrderStatusEnum.class, bufferObject, bufferArray);
StringBuffer buffer = bufferObject.append("\r\n").append(bufferArray);
writeJs(buffer);
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("执行耗时:" + (end - begin) + " 毫秒");
}
private static void toJson(Class clazz, StringBuffer bufferObject, StringBuffer bufferArray) throws Exception {
// enumConst:将YesNoEnum变成YES_NO
String enumConst = StrUtil.toUnderlineCase(clazz.getSimpleName())
.toUpperCase().replace("_ENUM", "");
Object[] objects = clazz.getEnumConstants();
Method name = clazz.getMethod("name");
// 排除枚举属性和$VALUES,只获取code desc等
List<Field> targetFields = new ArrayList<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!Modifier.isPrivate(field.getModifiers()) || "$VALUES".equals(field.getName())) {
continue;
}
targetFields.add(field);
}
// 生成对象
bufferObject.append(enumConst).append("={");
for (int i = 0; i < objects.length; i++) {
Object obj = objects[i];
bufferObject.append(name.invoke(obj)).append(":");
// 将一个枚举值转成JSON对象字符串
formatJsonObj(bufferObject, targetFields, clazz, obj);
if (i < objects.length - 1) {
bufferObject.append(",");
}
}
bufferObject.append("};\r\n");
// 生成数组
bufferArray.append(enumConst).append("_ARRAY=[");
for (int i = 0; i < objects.length; i++) {
Object obj = objects[i];
// 将一个枚举值转成JSON对象字符串
formatJsonObj(bufferArray, targetFields, clazz, obj);
if (i < objects.length - 1) {
bufferArray.append(",");
}
}
bufferArray.append("];\r\n");
}
/**
* 将一个枚举值转成JSON对象字符串
* 比如:SeatColEnum.YDZ_A("A", "A", "1")
* 转成:{code:"A",desc:"A",type:"1"}
*/
private static void formatJsonObj(StringBuffer bufferObject, List<Field> targetFields, Class clazz, Object obj) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
bufferObject.append("{");
for (int j = 0; j < targetFields.size(); j++) {
Field field = targetFields.get(j);
String fieldName = field.getName();
String getMethod = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
bufferObject.append(fieldName).append(":\"").append(clazz.getMethod(getMethod).invoke(obj)).append("\"");
if (j < targetFields.size() - 1) {
bufferObject.append(",");
}
}
bufferObject.append("}");
}
/**
* 写文件
* @param stringBuffer
*/
public static void writeJs(StringBuffer stringBuffer) {
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
System.out.println(path);
osw.write(stringBuffer.toString());
osw.close();
} catch (Exception e) {
e.printStackTrace();
}
finally {
try {
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
ServerGenerator
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package com.jiawa.train.generator.gen;
import com.jiawa.train.generator.util.DbUtil;
import com.jiawa.train.generator.util.Field;
import com.jiawa.train.generator.util.FreemarkerUtil;
import freemarker.template.TemplateException;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class ServerGenerator {
static boolean readOnly = false;
static String vuePath = "admin/src/views/main/";
static String serverPath = "[module]/src/main/java/com/jiawa/train/[module]/";
static String pomPath = "generator/pom.xml";
static String module = "";
// static {
// new File(serverPath).mkdirs();
// }
public static void main(String[] args) throws Exception {
// 获取mybatis-generator
String generatorPath = getGeneratorPath();
// 比如generator-config-member.xml,得到module = member
module = generatorPath.replace("src/main/resources/generator-config-", "").replace(".xml", "");
System.out.println("module: " + module);
serverPath = serverPath.replace("[module]", module);
new File(serverPath).mkdirs();
System.out.println("servicePath: " + serverPath);
// 读取table节点
Document document = new SAXReader().read("generator/" + generatorPath);
Node table = document.selectSingleNode("//table");
System.out.println(table);
Node tableName = table.selectSingleNode("@tableName");
Node domainObjectName = table.selectSingleNode("@domainObjectName");
System.out.println(tableName.getText() + "/" + domainObjectName.getText());
// 为DbUtil设置数据源
Node connectionURL = document.selectSingleNode("//@connectionURL");
Node userId = document.selectSingleNode("//@userId");
Node password = document.selectSingleNode("//@password");
System.out.println("url: " + connectionURL.getText());
System.out.println("user: " + userId.getText());
System.out.println("password: " + password.getText());
DbUtil.url = connectionURL.getText();
DbUtil.user = userId.getText();
DbUtil.password = password.getText();
// 示例:表名 jiawa_test
// Domain = JiawaTest
String Domain = domainObjectName.getText();
// domain = jiawaTest
String domain = Domain.substring(0, 1).toLowerCase() + Domain.substring(1);
// do_main = jiawa-test
String do_main = tableName.getText().replaceAll("_", "-");
// 表中文名
String tableNameCn = DbUtil.getTableComment(tableName.getText());
List<Field> fieldList = DbUtil.getColumnByTableName(tableName.getText());
Set<String> typeSet = getJavaTypes(fieldList);
// 组装参数
Map<String, Object> param = new HashMap<>();
param.put("module", module);
param.put("Domain", Domain);
param.put("domain", domain);
param.put("do_main", do_main);
param.put("tableNameCn", tableNameCn);
param.put("fieldList", fieldList);
param.put("typeSet", typeSet);
param.put("readOnly", readOnly);
System.out.println("组装参数:" + param);
gen(Domain, param, "service", "service");
gen(Domain, param, "controller/admin", "adminController");
gen(Domain, param, "req", "saveReq");
gen(Domain, param, "req", "queryReq");
gen(Domain, param, "resp", "queryResp");
genVue(do_main, param);
}
private static void gen(String Domain, Map<String, Object> param, String packageName, String target) throws IOException, TemplateException {
FreemarkerUtil.initConfig(target + ".ftl");
String toPath = serverPath + packageName + "/";
new File(toPath).mkdirs();
String Target = target.substring(0, 1).toUpperCase() + target.substring(1);
String fileName = toPath + Domain + Target + ".java";
System.out.println("开始生成:" + fileName);
FreemarkerUtil.generator(fileName, param);
}
private static void genVue(String do_main, Map<String, Object> param) throws IOException, TemplateException {
FreemarkerUtil.initConfig("vue.ftl");
new File(vuePath + module).mkdirs();
String fileName = vuePath + module + "/" + do_main + ".vue";
System.out.println("开始生成:" + fileName);
FreemarkerUtil.generator(fileName, param);
}
private static String getGeneratorPath() throws DocumentException {
SAXReader saxReader = new SAXReader();
Map<String, String> map = new HashMap<String, String>();
map.put("pom", "http://maven.apache.org/POM/4.0.0");
saxReader.getDocumentFactory().setXPathNamespaceURIs(map);
Document document = saxReader.read(pomPath);
Node node = document.selectSingleNode("//pom:configurationFile");
System.out.println(node.getText());
return node.getText();
}
/**
* 获取所有的Java类型,使用Set去重
*/
private static Set<String> getJavaTypes(List<Field> fieldList) {
Set<String> set = new HashSet<>();
for (int i = 0; i < fieldList.size(); i++) {
Field field = fieldList.get(i);
set.add(field.getJavaType());
}
return set;
}
}
工具类
DbUtil
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package com.jiawa.train.generator.util;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DbUtil {
public static String url = "";
public static String user = "";
public static String password = "";
public static Connection getConnection() {
Connection conn = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = DbUtil.url;
String user = DbUtil.user;
String password = DbUtil.password;
conn = DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 获得表注释
* @param tableName
* @return
* @throws Exception
*/
public static String getTableComment(String tableName) throws Exception {
Connection conn = getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select table_comment from information_schema.tables Where table_name = '" + tableName + "'");
String tableNameCH = "";
if (rs != null) {
while(rs.next()) {
tableNameCH = rs.getString("table_comment");
break;
}
}
rs.close();
stmt.close();
conn.close();
System.out.println("表名:" + tableNameCH);
return tableNameCH;
}
/**
* 获得所有列信息
* @param tableName
* @return
* @throws Exception
*/
public static List<Field> getColumnByTableName(String tableName) throws Exception {
List<Field> fieldList = new ArrayList<>();
Connection conn = getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("show full columns from `" + tableName + "`");
if (rs != null) {
while(rs.next()) {
String columnName = rs.getString("Field");
String type = rs.getString("Type");
String comment = rs.getString("Comment");
String nullAble = rs.getString("Null"); //YES NO
Field field = new Field();
field.setName(columnName);
field.setNameHump(lineToHump(columnName));
field.setNameBigHump(lineToBigHump(columnName));
field.setType(type);
field.setJavaType(DbUtil.sqlTypeToJavaType(rs.getString("Type")));
field.setComment(comment);
if (comment.contains("|")) {
field.setNameCn(comment.substring(0, comment.indexOf("|")));
} else {
field.setNameCn(comment);
}
field.setNullAble("YES".equals(nullAble));
if (type.toUpperCase().contains("varchar".toUpperCase())) {
String lengthStr = type.substring(type.indexOf("(") + 1, type.length() - 1);
field.setLength(Integer.valueOf(lengthStr));
} else {
field.setLength(0);
}
if (comment.contains("枚举")) {
field.setEnums(true);
// 以课程等级为例:从注释中的“枚举[CourseLevelEnum]”,得到enumsConst = COURSE_LEVEL
int start = comment.indexOf("[");
int end = comment.indexOf("]");
String enumsName = comment.substring(start + 1, end); // CourseLevelEnum
String enumsConst = StrUtil.toUnderlineCase(enumsName)
.toUpperCase().replace("_ENUM", "");
field.setEnumsConst(enumsConst);
} else {
field.setEnums(false);
}
fieldList.add(field);
}
}
rs.close();
stmt.close();
conn.close();
System.out.println("列信息:" + JSONUtil.toJsonPrettyStr(fieldList));
return fieldList;
}
/**
* 下划线转小驼峰:member_id 转成 memberId
*/
public static String lineToHump(String str){
Pattern linePattern = Pattern.compile("_(\\w)");
str = str.toLowerCase();
Matcher matcher = linePattern.matcher(str);
StringBuffer sb = new StringBuffer();
while(matcher.find()){
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* 下划线转大驼峰:member_id 转成 MemberId
*/
public static String lineToBigHump(String str){
String s = lineToHump(str);
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
/**
* 数据库类型转为Java类型
*/
public static String sqlTypeToJavaType(String sqlType) {
if (sqlType.toUpperCase().contains("varchar".toUpperCase())
|| sqlType.toUpperCase().contains("char".toUpperCase())
|| sqlType.toUpperCase().contains("text".toUpperCase())) {
return "String";
} else if (sqlType.toUpperCase().contains("datetime".toUpperCase())) {
return "Date";
} else if (sqlType.toUpperCase().contains("time".toUpperCase())) {
return "Date";
} else if (sqlType.toUpperCase().contains("date".toUpperCase())) {
return "Date";
} else if (sqlType.toUpperCase().contains("bigint".toUpperCase())) {
return "Long";
} else if (sqlType.toUpperCase().contains("int".toUpperCase())) {
return "Integer";
} else if (sqlType.toUpperCase().contains("long".toUpperCase())) {
return "Long";
} else if (sqlType.toUpperCase().contains("decimal".toUpperCase())) {
return "BigDecimal";
} else if (sqlType.toUpperCase().contains("boolean".toUpperCase())) {
return "Boolean";
} else {
return "String";
}
}
}
Field
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
package com.jiawa.train.generator.util;
@Data
public class Field {
private String name; // 字段名:course_id
private String nameHump; // 字段名小驼峰:courseId
private String nameBigHump; // 字段名大驼峰:CourseId
private String nameCn; // 中文名:课程
private String type; // 字段类型:char(8)
private String javaType; // java类型:String
private String comment; // 注释:课程|ID
private Boolean nullAble; // 是否可为空
private Integer length; // 字符串长度
private Boolean enums; // 是否是枚举
private String enumsConst; // 枚举常量 COURSE_LEVEL
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Field{");
sb.append("name='").append(name).append('\'');
sb.append(", nameHump='").append(nameHump).append('\'');
sb.append(", nameBigHump='").append(nameBigHump).append('\'');
sb.append(", nameCn='").append(nameCn).append('\'');
sb.append(", type='").append(type).append('\'');
sb.append(", javaType='").append(javaType).append('\'');
sb.append(", comment='").append(comment).append('\'');
sb.append(", nullAble=").append(nullAble);
sb.append(", length=").append(length);
sb.append(", enums=").append(enums);
sb.append(", enumsConst='").append(enumsConst).append('\'');
sb.append('}');
return sb.toString();
}
}
FreemarkerUtil
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
package com.jiawa.train.generator.util;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
public class FreemarkerUtil {
static String ftlPath = "generator/src/main/java/com/jiawa/train/generator/ftl/";
static Template temp;
/**
* 读模板
*/
public static void initConfig(String ftlName) throws IOException {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
cfg.setDirectoryForTemplateLoading(new File(ftlPath));
cfg.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_31));
temp = cfg.getTemplate(ftlName);
}
/**
* 根据模板,生成文件
*/
public static void generator(String fileName, Map<String, Object> map) throws IOException, TemplateException {
FileWriter fw = new FileWriter(fileName);
BufferedWriter bw = new BufferedWriter(fw);
temp.process(map, bw);
bw.flush();
fw.close();
}
}
gateway网关模块
pom
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>train</artifactId>
<groupId>com.jiawa</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringBoot 2.4版本之后 SpringCloud 2020,需要引入该依赖,才能读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--客户端负载均衡loadbalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>/dist/${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 配置当前项目的jdk版本信息 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
resource
application-prod
1
server.port=80
application
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
server.port=8000
# 路由转发,将/member/...的请求转发了member模块
spring.cloud.gateway.routes[0].id=member
#spring.cloud.gateway.routes[0].uri=http://127.0.0.1:8001
spring.cloud.gateway.routes[0].uri=lb://member
spring.cloud.gateway.routes[0].predicates[0]=Path=/member/**
# 路由转发,将/business/...的请求转发了business模块
spring.cloud.gateway.routes[1].id=business
#spring.cloud.gateway.routes[1].uri=http://127.0.0.1:8002
spring.cloud.gateway.routes[1].uri=lb://business
spring.cloud.gateway.routes[1].predicates[0]=Path=/business/**
# 路由转发,将/batch/...的请求转发了batch模块
spring.cloud.gateway.routes[2].id=batch
#spring.cloud.gateway.routes[2].uri=http://127.0.0.1:8003
spring.cloud.gateway.routes[2].uri=lb://batch
spring.cloud.gateway.routes[2].predicates[0]=Path=/batch/**
# 允许请求来源(老版本叫allowedOrigin)
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedOriginPatterns=*
# 允许携带的头信息
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedHeaders=*
# 允许的请求方式
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedMethods=*
# 是否允许携带cookie
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowCredentials=true
# 跨域检测的有效期,会发起一个OPTION请求
spring.cloud.gateway.globalcors.cors-configurations.[/**].maxAge=3600
bootstrap-prod
1
2
3
4
## nacos server注册中心地址,使用ECS内网IP
spring.cloud.nacos.discovery.server-addr=172.17.208.8:8848
spring.cloud.nacos.discovery.namespace=train
bootstrap
1
2
3
4
5
6
7
# 注册中心的名字
spring.application.name=gateway
## nacos server注册中心地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.namespace=train
logback-spring
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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 修改一下路径-->
<property name="PATH" value="./log/gateway"></property>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>-->
<Pattern>%d{mm:ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>
</encoder>
</appender>
<appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/trace.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
</layout>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
</layout>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="ERROR">
<appender-ref ref="ERROR_FILE" />
</root>
<root level="TRACE">
<appender-ref ref="TRACE_FILE" />
</root>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
启动类
GatewayApplication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.jiawa.train.gateway.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
@SpringBootApplication
@ComponentScan("com.jiawa")
public class GatewayApplication {
private static final Logger LOG = LoggerFactory.getLogger(GatewayApplication.class);
public static void main(String[] args) {
SpringApplication app = new SpringApplication(GatewayApplication.class);
Environment env = app.run(args).getEnvironment();
LOG.info("启动成功!!");
LOG.info("网关地址: \thttp://127.0.0.1:{}", env.getProperty("server.port"));
}
}
过滤器
LoginMemberFilter
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
63
64
65
66
67
package com.jiawa.train.gateway.config;
import com.jiawa.train.gateway.util.JwtUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class LoginMemberFilter implements Ordered, GlobalFilter {
private static final Logger LOG = LoggerFactory.getLogger(LoginMemberFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
// 排除不需要拦截的请求
if (path.contains("/admin")
|| path.contains("/redis")
|| path.contains("/hello")
|| path.contains("/member/member/login")
|| path.contains("/member/member/send-code")
|| path.contains("/business/kaptcha")) {
LOG.info("不需要登录验证:{}", path);
return chain.filter(exchange);
} else {
LOG.info("需要登录验证:{}", path);
}
// 获取header的token参数
String token = exchange.getRequest().getHeaders().getFirst("token");
LOG.info("会员登录验证开始,token:{}", token);
if (token == null || token.isEmpty()) {
LOG.info( "token为空,请求被拦截" );
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 校验token是否有效,包括token是否被改过,是否过期
boolean validate = JwtUtil.validate(token);
if (validate) {
LOG.info("token有效,放行该请求");
return chain.filter(exchange);
} else {
LOG.warn( "token无效,请求被拦截" );
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
/**
* 优先级设置 值越小 优先级越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
batch定时任务模块
使用quartz实现定时任务,使用open-feign实现服务调用
sql
sql
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#
# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
#
# PLEASE consider using mysql with innodb tables to avoid locking issues
#
# In your Quartz properties file, you'll need to set
# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
CREATE TABLE QRTZ_JOB_DETAILS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
JOB_NAME VARCHAR(200) NOT NULL comment 'job名称',
JOB_GROUP VARCHAR(200) NOT NULL comment 'job组',
DESCRIPTION VARCHAR(250) NULL comment '描述',
JOB_CLASS_NAME VARCHAR(250) NOT NULL comment 'job类名',
IS_DURABLE VARCHAR(1) NOT NULL comment '是否持久化',
IS_NONCONCURRENT VARCHAR(1) NOT NULL comment '是否非同步',
IS_UPDATE_DATA VARCHAR(1) NOT NULL comment '是否更新数据',
REQUESTS_RECOVERY VARCHAR(1) NOT NULL comment '请求是否覆盖',
JOB_DATA BLOB NULL comment 'job数据',
PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称',
TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组',
JOB_NAME VARCHAR(200) NOT NULL comment 'job名称',
JOB_GROUP VARCHAR(200) NOT NULL comment 'job组',
DESCRIPTION VARCHAR(250) NULL comment '描述',
NEXT_FIRE_TIME BIGINT(13) NULL comment '下一次触发时间',
PREV_FIRE_TIME BIGINT(13) NULL comment '前一次触发时间',
PRIORITY INTEGER NULL comment '等级',
TRIGGER_STATE VARCHAR(16) NOT NULL comment '触发状态',
TRIGGER_TYPE VARCHAR(8) NOT NULL comment '触发类型',
START_TIME BIGINT(13) NOT NULL comment '开始时间',
END_TIME BIGINT(13) NULL comment '结束时间',
CALENDAR_NAME VARCHAR(200) NULL comment '日程名称',
MISFIRE_INSTR SMALLINT(2) NULL comment '未触发实例',
JOB_DATA BLOB NULL comment 'job数据',
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称',
TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组',
REPEAT_COUNT BIGINT(7) NOT NULL comment '重复执行次数',
REPEAT_INTERVAL BIGINT(12) NOT NULL comment '重复执行间隔',
TIMES_TRIGGERED BIGINT(10) NOT NULL comment '已经触发次数',
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称',
TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组',
CRON_EXPRESSION VARCHAR(200) NOT NULL comment 'cron表达式',
TIME_ZONE_ID VARCHAR(80) comment '时区',
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称',
TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组',
STR_PROP_1 VARCHAR(512) NULL comment '开始配置1',
STR_PROP_2 VARCHAR(512) NULL comment '开始配置2',
STR_PROP_3 VARCHAR(512) NULL comment '开始配置3',
INT_PROP_1 INT NULL comment 'int配置1',
INT_PROP_2 INT NULL comment 'int配置2',
LONG_PROP_1 BIGINT NULL comment 'long配置1',
LONG_PROP_2 BIGINT NULL comment 'long配置2',
DEC_PROP_1 NUMERIC(13,4) NULL comment '配置描述1',
DEC_PROP_2 NUMERIC(13,4) NULL comment '配置描述2',
BOOL_PROP_1 VARCHAR(1) NULL comment 'bool配置1',
BOOL_PROP_2 VARCHAR(1) NULL comment 'bool配置2',
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_BLOB_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称',
TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组',
BLOB_DATA BLOB NULL comment '数据',
PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CALENDARS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
CALENDAR_NAME VARCHAR(200) NOT NULL comment '日程名称',
CALENDAR BLOB NOT NULL comment '日程数据',
PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组',
PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
ENTRY_ID VARCHAR(95) NOT NULL comment 'entryId',
TRIGGER_NAME VARCHAR(200) NOT NULL comment '触发器名称',
TRIGGER_GROUP VARCHAR(200) NOT NULL comment '触发器组',
INSTANCE_NAME VARCHAR(200) NOT NULL comment '实例名称',
FIRED_TIME BIGINT(13) NOT NULL comment '执行时间',
SCHED_TIME BIGINT(13) NOT NULL comment '定时任务时间',
PRIORITY INTEGER NOT NULL comment '等级',
STATE VARCHAR(16) NOT NULL comment '状态',
JOB_NAME VARCHAR(200) NULL comment 'job名称',
JOB_GROUP VARCHAR(200) NULL comment 'job组',
IS_NONCONCURRENT VARCHAR(1) NULL comment '是否异步',
REQUESTS_RECOVERY VARCHAR(1) NULL comment '是否请求覆盖',
PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
INSTANCE_NAME VARCHAR(200) NOT NULL comment '实例名称',
LAST_CHECKIN_TIME BIGINT(13) NOT NULL comment '最近检入时间',
CHECKIN_INTERVAL BIGINT(13) NOT NULL comment '检入间隔',
PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS
(
SCHED_NAME VARCHAR(120) NOT NULL comment '定时任务名称',
LOCK_NAME VARCHAR(40) NOT NULL comment 'lock名称',
PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
===
pom
pom
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>train</artifactId>
<groupId>com.jiawa</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>batch</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.jiawa</groupId>
<artifactId>common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!--远程调用openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--openfeign默认使用的是loadBalance的负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<!-- 限流熔断 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- sentinel + nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>/dist/${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 配置当前项目的jdk版本信息 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
===
resource
application
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server.port=8003
server.servlet.context-path=/batch
spring.application.name=batch
# 数据库连接
spring.datasource.url=jdbc:mysql://rm-uf62v90x7t5hukv5dro.mysql.rds.aliyuncs.com/train_batch?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=train_batch
spring.datasource.password=Batch123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mybatis xml路径
mybatis.mapper-locations=classpath:/mapper/**/*.xml
logging.level.com.jiawa.train.batch.mapper=trace
bootstrap-prod
1
2
3
4
5
6
## nacos server注册中心地址,使用ECS内网IP
spring.cloud.nacos.discovery.server-addr=172.17.208.8:8848
spring.cloud.nacos.discovery.namespace=train
## 关于生成的数据库、RDS,可以通过生产nacos来配置
bootstrap
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
# 注册中心的名字
spring.application.name=batch
## 启动环境,nacos会根据环境读不同的配置dataId:batch-dev.properties
spring.profiles.active=dev
## nacos server地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
## 配置中心文件后缀,默认properties
spring.cloud.nacos.config.file-extension=properties
## nacos命名空间
spring.cloud.nacos.config.namespace=train
## nacos server注册中心地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.namespace=train
# sentinel控台:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.dashboard=localhost:18080
# sentinel + nacos
spring.cloud.sentinel.datasource.degrade.nacos.serverAddr=127.0.0.1:8848
spring.cloud.sentinel.datasource.degrade.nacos.namespace=train
spring.cloud.sentinel.datasource.degrade.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.degrade.nacos.dataId=sentinel-batch-degrade
spring.cloud.sentinel.datasource.degrade.nacos.ruleType=degrade
# sentinel默认不监控feign,需改成true,在簇点链路界面会显示资源:GET:http://business/business/hello
feign.sentinel.enabled=true
# 上面改成true后,启动会报注入失败,需改成懒加载
spring.cloud.openfeign.lazy-attributes-resolution=true
logback-spring
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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 修改一下路径-->
<property name="PATH" value="./log/batch"></property>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>-->
<Pattern>%d{mm:ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>
</encoder>
</appender>
<appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/trace.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
</layout>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
</layout>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="ERROR">
<appender-ref ref="ERROR_FILE" />
</root>
<root level="TRACE">
<appender-ref ref="TRACE_FILE" />
</root>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
===
启动
BatchApplication
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
package com.jiawa.train.batch.config;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
@SpringBootApplication
@ComponentScan("com.jiawa")
@MapperScan("com.jiawa.train.*.mapper")
@EnableFeignClients("com.jiawa.train.batch.feign")
public class BatchApplication {
private static final Logger LOG = LoggerFactory.getLogger(BatchApplication.class);
public static void main(String[] args) {
SpringApplication app = new SpringApplication(BatchApplication.class);
Environment env = app.run(args).getEnvironment();
LOG.info("启动成功!!");
LOG.info("测试地址: \thttp://127.0.0.1:{}{}/hello", env.getProperty("server.port"), env.getProperty("server.servlet.context-path"));
}
}
===
enums
ConfirmOrderStatusEnum
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
package com.jiawa.train.business.enums;
public enum ConfirmOrderStatusEnum {
INIT("I", "初始"),
PENDING("P", "处理中"),
SUCCESS("S", "成功"),
FAILURE("F", "失败"),
EMPTY("E", "无票"),
CANCEL("C", "取消");
private String code;
private String desc;
ConfirmOrderStatusEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
@Override public String toString() {
return "ConfirmOrderStatusEnum{" +
"code='" + code + '\'' +
", desc='" + desc + '\'' +
"} " + super.toString();
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
RedisKeyPreEnum
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
package com.jiawa.train.business.enums;
public enum RedisKeyPreEnum {
CONFIRM_ORDER("LOCK_CONFIRM_ORDER", "购票锁"),
SK_TOKEN("LOCK_SK_TOKEN", "令牌锁"),
SK_TOKEN_COUNT("SK_TOKEN_COUNT", "令牌数");
private final String code;
private final String desc;
RedisKeyPreEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
RocketMQTopicEnum
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
package com.jiawa.train.business.enums;
public enum RocketMQTopicEnum {
CONFIRM_ORDER("CONFIRM_ORDER", "确认订单排队");
private String code;
private String desc;
RocketMQTopicEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
@Override
public String toString() {
return "RocketMQTopicEnum{" +
"code='" + code + '\'' +
", desc='" + desc + '\'' +
"} " + super.toString();
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
}
SeatColEnum
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
63
64
65
66
67
68
69
70
71
72
73
package com.jiawa.train.business.enums;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
public enum SeatColEnum {
YDZ_A("A", "A", "1"),
YDZ_C("C", "C", "1"),
YDZ_D("D", "D", "1"),
YDZ_F("F", "F", "1"),
EDZ_A("A", "A", "2"),
EDZ_B("B", "B", "2"),
EDZ_C("C", "C", "2"),
EDZ_D("D", "D", "2"),
EDZ_F("F", "F", "2");
private String code;
private String desc;
/**
* 对应SeatTypeEnum.code
*/
private String type;
SeatColEnum(String code, String desc, String type) {
this.code = code;
this.desc = desc;
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
/**
* 根据车箱的座位类型,筛选出所有的列,比如车箱类型是一等座,则筛选出columnList={ACDF}
*/
public static List<SeatColEnum> getColsByType(String seatType) {
List<SeatColEnum> colList = new ArrayList<>();
EnumSet<SeatColEnum> seatColEnums = EnumSet.allOf(SeatColEnum.class);
for (SeatColEnum anEnum : seatColEnums) {
if (seatType.equals(anEnum.getType())) {
colList.add(anEnum);
}
}
return colList;
}
}
SeatTypeEnum
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
63
64
65
66
67
package com.jiawa.train.business.enums;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
public enum SeatTypeEnum {
YDZ("1", "一等座", new BigDecimal("0.4")),
EDZ("2", "二等座", new BigDecimal("0.3")),
RW("3", "软卧", new BigDecimal("0.6")),
YW("4", "硬卧", new BigDecimal("0.5"));
private String code;
private String desc;
/**
* 基础票价 N元/公里,0.4即为0.4元/公里
*/
private BigDecimal price;
SeatTypeEnum(String code, String desc, BigDecimal price) {
this.code = code;
this.desc = desc;
this.price = price;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public static List<HashMap<String,String>> getEnumList() {
List<HashMap<String, String>> list = new ArrayList<>();
for (SeatTypeEnum anEnum : EnumSet.allOf(SeatTypeEnum.class)) {
HashMap<String, String> map = new HashMap<>();
map.put("code",anEnum.code);
map.put("desc",anEnum.desc);
list.add(map);
}
return list;
}
public static SeatTypeEnum getEnumByCode(String code) {
for (SeatTypeEnum enums : SeatTypeEnum.values()) {
if (enums.getCode().equalsIgnoreCase(code)) {
return enums;
}
}
return null;
}
}
TrainTypeEnum
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
63
64
65
66
67
68
69
70
71
72
73
74
package com.jiawa.train.business.enums;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
public enum TrainTypeEnum {
G("G", "高铁", new BigDecimal("1.2")),
D("D", "动车", new BigDecimal("1")),
K("K", "快速", new BigDecimal("0.8"));
private String code;
private String desc;
/**
* 票价比例,例:1.1,则票价 = 1.1 * 每公里单价(SeatTypeEnum.price) * 公里(station.km)
*/
private BigDecimal priceRate;
TrainTypeEnum(String code, String desc, BigDecimal priceRate) {
this.code = code;
this.desc = desc;
this.priceRate = priceRate;
}
@Override
public String toString() {
return "TrainTypeEnum{" +
"code='" + code + '\'' +
", desc='" + desc + '\'' +
", priceRate=" + priceRate +
"} " + super.toString();
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public BigDecimal getPriceRate() {
return priceRate;
}
public void setPriceRate(BigDecimal priceRate) {
this.priceRate = priceRate;
}
public static List<HashMap<String,String>> getEnumList() {
List<HashMap<String, String>> list = new ArrayList<>();
for (TrainTypeEnum anEnum : EnumSet.allOf(TrainTypeEnum.class)) {
HashMap<String, String> map = new HashMap<>();
map.put("code",anEnum.code);
map.put("desc",anEnum.desc);
list.add(map);
}
return list;
}
}
===
feign
MemberFeign
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.jiawa.train.business.feign;
import com.jiawa.train.common.req.MemberTicketReq;
import com.jiawa.train.common.resp.CommonResp;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
// @FeignClient("member")
@FeignClient(name = "member", url = "http://127.0.0.1:8001")
public interface MemberFeign {
@GetMapping("/member/feign/ticket/save")
CommonResp<Object> save(@RequestBody MemberTicketReq req);
}
===
mq
ConfirmOrderConsumer
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
// package com.jiawa.train.business.mq;
//
// import com.alibaba.fastjson.JSON;
// import com.jiawa.train.business.dto.ConfirmOrderMQDto;
// import com.jiawa.train.business.req.ConfirmOrderDoReq;
// import com.jiawa.train.business.service.ConfirmOrderService;
// import jakarta.annotation.Resource;
// import org.apache.rocketmq.common.message.MessageExt;
// import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
// import org.apache.rocketmq.spring.core.RocketMQListener;
// import org.slf4j.Logger;
// import org.slf4j.LoggerFactory;
// import org.slf4j.MDC;
// import org.springframework.stereotype.Service;
//
// @Service
// @RocketMQMessageListener(consumerGroup = "default", topic = "CONFIRM_ORDER")
// public class ConfirmOrderConsumer implements RocketMQListener<MessageExt> {
//
// private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderConsumer.class);
//
// @Resource
// private ConfirmOrderService confirmOrderService;
//
// @Override
// public void onMessage(MessageExt messageExt) {
// byte[] body = messageExt.getBody();
// ConfirmOrderMQDto dto = JSON.parseObject(new String(body), ConfirmOrderMQDto.class);
// MDC.put("LOG_ID", dto.getLogId());
// LOG.info("ROCKETMQ收到消息:{}", new String(body));
// confirmOrderService.doConfirm(dto);
// }
// }
config
MyJobFactory
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
package com.jiawa.train.batch.config;
import jakarta.annotation.Resource;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
import org.springframework.stereotype.Component;
@Component
public class MyJobFactory extends SpringBeanJobFactory {
@Resource
private AutowireCapableBeanFactory beanFactory;
/**
* 这里覆盖了super的createJobInstance方法,对其创建出来的类再进行autowire。
*/
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
beanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
QuartzConfig
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
// package com.jiawa.train.batch.config;
//
// import com.jiawa.train.batch.job.TestJob;
// import org.quartz.*;
// import org.springframework.context.annotation.Bean;
// import org.springframework.context.annotation.Configuration;
//
// @Configuration
// public class QuartzConfig {
//
// /**
// * 声明一个任务
// * @return
// */
// @Bean
// public JobDetail jobDetail() {
// return JobBuilder.newJob(TestJob.class)
// .withIdentity("TestJob", "test")
// .storeDurably()
// .build();
// }
//
// /**
// * 声明一个触发器,什么时候触发这个任务
// * @return
// */
// @Bean
// public Trigger trigger() {
// return TriggerBuilder.newTrigger()
// .forJob(jobDetail())
// .withIdentity("trigger", "trigger")
// .startNow()
// .withSchedule(CronScheduleBuilder.cronSchedule("*/2 * * * * ?"))
// .build();
// }
// }
SchedulerConfig
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
package com.jiawa.train.batch.config;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.sql.DataSource;
import java.io.IOException;
@Configuration
public class SchedulerConfig {
@Resource
private MyJobFactory myJobFactory;
@Bean
public SchedulerFactoryBean schedulerFactoryBean(@Qualifier("dataSource") DataSource dataSource) throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
factory.setJobFactory(myJobFactory);
factory.setStartupDelay(2);
return factory;
}
}
SpringMvcConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.jiawa.train.batch.config;
import com.jiawa.train.common.interceptor.LogInterceptor;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
@Resource
LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor)
.addPathPatterns("/**");
}
}
===
controller
JobController
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package com.jiawa.train.batch.controller;
import com.jiawa.train.batch.req.CronJobReq;
import com.jiawa.train.batch.resp.CronJobResp;
import com.jiawa.train.common.resp.CommonResp;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping(value = "/admin/job")
public class JobController {
private static Logger LOG = LoggerFactory.getLogger(JobController.class);
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;
@RequestMapping(value = "/run")
public CommonResp<Object> run(@RequestBody CronJobReq cronJobReq) throws SchedulerException {
String jobClassName = cronJobReq.getName();
String jobGroupName = cronJobReq.getGroup();
LOG.info("手动执行任务开始:{}, {}", jobClassName, jobGroupName);
schedulerFactoryBean.getScheduler().triggerJob(JobKey.jobKey(jobClassName, jobGroupName));
return new CommonResp<>();
}
@RequestMapping(value = "/add")
public CommonResp add(@RequestBody CronJobReq cronJobReq) {
String jobClassName = cronJobReq.getName();
String jobGroupName = cronJobReq.getGroup();
String cronExpression = cronJobReq.getCronExpression();
String description = cronJobReq.getDescription();
LOG.info("创建定时任务开始:{},{},{},{}", jobClassName, jobGroupName, cronExpression, description);
CommonResp commonResp = new CommonResp();
try {
// 通过SchedulerFactory获取一个调度器实例
Scheduler sched = schedulerFactoryBean.getScheduler();
// 启动调度器
sched.start();
//构建job信息
JobDetail jobDetail = JobBuilder.newJob((Class<? extends Job>) Class.forName(jobClassName)).withIdentity(jobClassName, jobGroupName).build();
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName).withDescription(description).withSchedule(scheduleBuilder).build();
sched.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
LOG.error("创建定时任务失败:" + e);
commonResp.setSuccess(false);
commonResp.setMessage("创建定时任务失败:调度异常");
} catch (ClassNotFoundException e) {
LOG.error("创建定时任务失败:" + e);
commonResp.setSuccess(false);
commonResp.setMessage("创建定时任务失败:任务类不存在");
}
LOG.info("创建定时任务结束:{}", commonResp);
return commonResp;
}
@RequestMapping(value = "/pause")
public CommonResp pause(@RequestBody CronJobReq cronJobReq) {
String jobClassName = cronJobReq.getName();
String jobGroupName = cronJobReq.getGroup();
LOG.info("暂停定时任务开始:{},{}", jobClassName, jobGroupName);
CommonResp commonResp = new CommonResp();
try {
Scheduler sched = schedulerFactoryBean.getScheduler();
sched.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
} catch (SchedulerException e) {
LOG.error("暂停定时任务失败:" + e);
commonResp.setSuccess(false);
commonResp.setMessage("暂停定时任务失败:调度异常");
}
LOG.info("暂停定时任务结束:{}", commonResp);
return commonResp;
}
@RequestMapping(value = "/resume")
public CommonResp resume(@RequestBody CronJobReq cronJobReq) {
String jobClassName = cronJobReq.getName();
String jobGroupName = cronJobReq.getGroup();
LOG.info("重启定时任务开始:{},{}", jobClassName, jobGroupName);
CommonResp commonResp = new CommonResp();
try {
Scheduler sched = schedulerFactoryBean.getScheduler();
sched.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
} catch (SchedulerException e) {
LOG.error("重启定时任务失败:" + e);
commonResp.setSuccess(false);
commonResp.setMessage("重启定时任务失败:调度异常");
}
LOG.info("重启定时任务结束:{}", commonResp);
return commonResp;
}
@RequestMapping(value = "/reschedule")
public CommonResp reschedule(@RequestBody CronJobReq cronJobReq) {
String jobClassName = cronJobReq.getName();
String jobGroupName = cronJobReq.getGroup();
String cronExpression = cronJobReq.getCronExpression();
String description = cronJobReq.getDescription();
LOG.info("更新定时任务开始:{},{},{},{}", jobClassName, jobGroupName, cronExpression, description);
CommonResp commonResp = new CommonResp();
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
CronTriggerImpl trigger1 = (CronTriggerImpl) scheduler.getTrigger(triggerKey);
trigger1.setStartTime(new Date()); // 重新设置开始时间
CronTrigger trigger = trigger1;
// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withDescription(description).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (Exception e) {
LOG.error("更新定时任务失败:" + e);
commonResp.setSuccess(false);
commonResp.setMessage("更新定时任务失败:调度异常");
}
LOG.info("更新定时任务结束:{}", commonResp);
return commonResp;
}
@RequestMapping(value = "/delete")
public CommonResp delete(@RequestBody CronJobReq cronJobReq) {
String jobClassName = cronJobReq.getName();
String jobGroupName = cronJobReq.getGroup();
LOG.info("删除定时任务开始:{},{}", jobClassName, jobGroupName);
CommonResp commonResp = new CommonResp();
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
} catch (SchedulerException e) {
LOG.error("删除定时任务失败:" + e);
commonResp.setSuccess(false);
commonResp.setMessage("删除定时任务失败:调度异常");
}
LOG.info("删除定时任务结束:{}", commonResp);
return commonResp;
}
@RequestMapping(value="/query")
public CommonResp query() {
LOG.info("查看所有定时任务开始");
CommonResp commonResp = new CommonResp();
List<CronJobResp> cronJobDtoList = new ArrayList();
try {
Scheduler scheduler = schedulerFactoryBean.getScheduler();
for (String groupName : scheduler.getJobGroupNames()) {
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
CronJobResp cronJobResp = new CronJobResp();
cronJobResp.setName(jobKey.getName());
cronJobResp.setGroup(jobKey.getGroup());
//get job's trigger
List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jobKey);
CronTrigger cronTrigger = (CronTrigger) triggers.get(0);
cronJobResp.setNextFireTime(cronTrigger.getNextFireTime());
cronJobResp.setPreFireTime(cronTrigger.getPreviousFireTime());
cronJobResp.setCronExpression(cronTrigger.getCronExpression());
cronJobResp.setDescription(cronTrigger.getDescription());
Trigger.TriggerState triggerState = scheduler.getTriggerState(cronTrigger.getKey());
cronJobResp.setState(triggerState.name());
cronJobDtoList.add(cronJobResp);
}
}
} catch (SchedulerException e) {
LOG.error("查看定时任务失败:" + e);
commonResp.setSuccess(false);
commonResp.setMessage("查看定时任务失败:调度异常");
}
commonResp.setContent(cronJobDtoList);
LOG.info("查看定时任务结束:{}", commonResp);
return commonResp;
}
}
===
feign
BusinessFeign
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.jiawa.train.batch.feign;
import com.jiawa.train.common.resp.CommonResp;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.Date;
@FeignClient(value = "business", fallback = BusinessFeignFallback.class)
// @FeignClient(name = "business", url = "http://127.0.0.1:8002/business")
public interface BusinessFeign {
@GetMapping("/business/hello")
String hello();
@GetMapping("/business/admin/daily-train/gen-daily/{date}")
CommonResp<Object> genDaily(@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") Date date);
}
BusinessFeignFallback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.jiawa.train.batch.feign;
import com.jiawa.train.common.resp.CommonResp;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class BusinessFeignFallback implements BusinessFeign {
@Override
public String hello() {
return "Fallback";
}
@Override
public CommonResp<Object> genDaily(Date date) {
return null;
}
}
MemberFeign
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.jiawa.train.batch.feign;
import com.jiawa.train.common.resp.CommonResp;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "member")
public interface MemberFeign {
@GetMapping("/member/passenger/init")
CommonResp init();
}
===
job
DailyTrainJob
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
package com.jiawa.train.batch.job;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import com.jiawa.train.batch.feign.BusinessFeign;
import com.jiawa.train.common.resp.CommonResp;
import jakarta.annotation.Resource;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.Date;
@DisallowConcurrentExecution
public class DailyTrainJob implements Job {
private static final Logger LOG = LoggerFactory.getLogger(DailyTrainJob.class);
@Resource
BusinessFeign businessFeign;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 增加日志流水号
MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3));
LOG.info("生成15天后的车次数据开始");
Date date = new Date();
DateTime dateTime = DateUtil.offsetDay(date, 15);
Date offsetDate = dateTime.toJdkDate();
CommonResp<Object> commonResp = businessFeign.genDaily(offsetDate);
LOG.info("生成15天后的车次数据结束,结果:{}", commonResp);
}
}
PassengerJob
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
package com.jiawa.train.batch.job;
import cn.hutool.core.util.RandomUtil;
import com.jiawa.train.batch.feign.MemberFeign;
import jakarta.annotation.Resource;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
@DisallowConcurrentExecution
public class PassengerJob implements Job {
private static final Logger LOG = LoggerFactory.getLogger(PassengerJob.class);
@Resource
MemberFeign memberFeign;
/**
* 初始化乘客,如果没有张三,就增加乘客张三,李四、王五同理,防止线上体验时乘客被删光
* @param jobExecutionContext
* @throws JobExecutionException
*/
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// 增加日志流水号
MDC.put("LOG_ID", System.currentTimeMillis() + RandomUtil.randomString(3));
LOG.info("初始化乘客数据开始");
memberFeign.init();
LOG.info("初始化乘客数据结束");
}
}
===
req-resp
CronJobReq
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.jiawa.train.batch.req;
public class CronJobReq {
private String group;
private String name;
private String description;
private String cronExpression;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("CronJobDto{");
sb.append("cronExpression='").append(cronExpression).append('\'');
sb.append(", group='").append(group).append('\'');
sb.append(", name='").append(name).append('\'');
sb.append(", description='").append(description).append('\'');
sb.append('}');
return sb.toString();
}
}
CronJobResp
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
package com.jiawa.train.batch.resp;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Date;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class CronJobResp {
private String group;
private String name;
private String description;
private String state;
private String cronExpression;
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date nextFireTime;
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date preFireTime;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("CronJobDto{");
sb.append("cronExpression='").append(cronExpression).append('\'');
sb.append(", group='").append(group).append('\'');
sb.append(", name='").append(name).append('\'');
sb.append(", description='").append(description).append('\'');
sb.append(", state='").append(state).append('\'');
sb.append(", nextFireTime=").append(nextFireTime);
sb.append(", preFireTime=").append(preFireTime);
sb.append('}');
return sb.toString();
}
}
member会员模块
也就是用户模块
pom
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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>train</artifactId>
<groupId>com.jiawa</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>member</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.jiawa</groupId>
<artifactId>common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>/dist/${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 配置当前项目的jdk版本信息 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
======
sql
sql
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
drop table if exists `member`;
create table `member` (
`id` bigint not null comment 'id',
`mobile` varchar(11) comment '手机号',
primary key (`id`),
unique key `mobile_unique` (`mobile`)
) engine=innodb default charset=utf8mb4 comment='会员';
drop table if exists `passenger`;
create table `passenger` (
`id` bigint not null comment 'id',
`member_id` bigint not null comment '会员id',
`name` varchar(20) not null comment '姓名',
`id_card` varchar(18) not null comment '身份证',
`type` char(1) not null comment '旅客类型|枚举[PassengerTypeEnum]',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
index `member_id_index` (`member_id`)
) engine=innodb default charset=utf8mb4 comment='乘车人';
drop table if exists `ticket`;
create table `ticket` (
`id` bigint not null comment 'id',
`member_id` bigint not null comment '会员id',
`passenger_id` bigint not null comment '乘客id',
`passenger_name` varchar(20) comment '乘客姓名',
`train_date` date not null comment '日期',
`train_code` varchar(20) not null comment '车次编号',
`carriage_index` int not null comment '箱序',
`seat_row` char(2) not null comment '排号|01, 02',
`seat_col` char(1) not null comment '列号|枚举[SeatColEnum]',
`start_station` varchar(20) not null comment '出发站',
`start_time` time not null comment '出发时间',
`end_station` varchar(20) not null comment '到达站',
`end_time` time not null comment '到站时间',
`seat_type` char(1) not null comment '座位类型|枚举[SeatTypeEnum]',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
index `member_id_index` (`member_id`)
) engine=innodb default charset=utf8mb4 comment='车票';
=====
resource
配置application
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server.port=8001
#项目前缀
server.servlet.context-path=/member
# 数据库连接
spring.datasource.url=jdbc:mysql://rm-uf62v90x7t5hukv5dro.mysql.rds.aliyuncs.com/train_member?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=train_member
spring.datasource.password=Member123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mybatis xml路径
mybatis.mapper-locations=classpath:/mapper/**/*.xml
logging.level.com.jiawa.train.member.mapper=trace
test.nacos=Member
nacos配置bootstrap-prod.properties
1
2
3
4
5
## nacos server注册中心地址,使用ECS内网IP
spring.cloud.nacos.discovery.server-addr=172.17.208.8:8848
spring.cloud.nacos.discovery.namespace=train
## 关于生成的数据库、RDS,可以通过生产nacos来配置
nacos主要配置bootstrap.properties
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
# 注册中心的名字
spring.application.name=member
## 启动环境,nacos会根据环境读不同的配置dataId:member-dev.properties
spring.profiles.active=dev
## nacos server地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
## 配置中心文件后缀,默认properties
spring.cloud.nacos.config.file-extension=properties
## nacos命名空间
spring.cloud.nacos.config.namespace=train
## nacos server注册中心地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.namespace=train
# seata注册中心,要和seata server的application.yml配置保持一致
seata.registry.type=nacos
seata.registry.nacos.application=seata-server
seata.registry.nacos.server-addr=127.0.0.1:8848
seata.registry.nacos.group=SEATA_GROUP
seata.registry.nacos.namespace=train
seata.registry.nacos.username=nacos
seata.registry.nacos.password=nacos
# seata配置中心,要和seata server的application.yml配置保持一致
seata.config.type=nacos
seata.config.nacos.server-addr=127.0.0.1:8848
seata.config.nacos.group=SEATA_GROUP
seata.config.nacos.namespace=train
seata.config.nacos.dataId=seataServer.properties
seata.config.nacos.username=nacos
seata.config.nacos.password=nacos
# 事务组名称,必须在nacos中有配置过:service.vgroupMapping.train-group=default
#seata.tx-service-group=train-group
# 事务组和seata集群做关联
#seata.service.vgroup-mapping.test-group=default
# seata集群对应的机器
#seata.service.grouplist.default=127.0.0.1:8091
################################################
# 以下是nacos中的seataServer.properties的相关配置
################################################
# # 和微服务模块的seata.tx-service-group保持一致
# service.vgroupMapping.train-group=default
# service.default.grouplist=127.0.0.1:8091
#
# # 和微服务模块的seata.tx-service-group保持一致
# service.vgroupMapping.test-group=default1
# service.default1.grouplist=127.0.0.1:18091
################################################
日志相关配置logback-spring.xml
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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志文件,修改一下路径-->
<property name="PATH" value="./log/member"></property>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>-->
<Pattern>%d{mm:ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>
</encoder>
</appender>
<appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/trace.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
</layout>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
</layout>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="ERROR">
<appender-ref ref="ERROR_FILE" />
</root>
<root level="TRACE">
<appender-ref ref="TRACE_FILE" />
</root>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
启动类
主启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
@ComponentScan("com.jiawa")
@MapperScan("com.jiawa.train.*.mapper")
public class MemberApplication {
private static final Logger LOG = LoggerFactory.getLogger(MemberApplication.class);
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MemberApplication.class);
Environment env = app.run(args).getEnvironment();
LOG.info("启动成功!!");
LOG.info("测试地址: \thttp://127.0.0.1:{}{}/hello", env.getProperty("server.port"), env.getProperty("server.servlet.context-path"));
}
}
HTTPClient测试
可以新建test.http文件来写测试接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET http://localhost:8001/member/hello
Accept: application/json
###
GET http://localhost:8000/member/hello
Accept: application/json
###
GET http://47.101.44.52/member/hello
Accept: application/json
###
======
req
登录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
@Data
public class MemberLoginReq {
@NotBlank(message = "【手机号】不能为空")
@Pattern(regexp = "^1\\d{10}$", message = "手机号码格式错误")
private String mobile;
@NotBlank(message = "【短信验证码】不能为空")
private String code;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("MemberLoginReq{");
sb.append("mobile='").append(mobile).append('\'');
sb.append(", code='").append(code).append('\'');
sb.append('}');
return sb.toString();
}
}
注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import jakarta.validation.constraints.NotBlank;
@Data
public class MemberRegisterReq {
@NotBlank(message = "【手机号】不能为空")
private String mobile;
@Override
public String toString() {
return "MemberRegisterReq{" +
"mobile='" + mobile + '\'' +
'}';
}
}
发送验证码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
@Data
public class MemberSendCodeReq {
@NotBlank(message = "【手机号】不能为空")
@Pattern(regexp = "^1\\d{10}$", message = "手机号码格式错误")
private String mobile;
@Override
public String toString() {
return "MemberSendCodeReq{" +
"mobile='" + mobile + '\'' +
'}';
}
}
查乘车人
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.jiawa.train.common.req.PageReq;
@Data
public class PassengerQueryReq extends PageReq {
private Long memberId;
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("PassengerQueryReq{");
sb.append("memberId=").append(memberId);
sb.append('}');
return sb.toString();
}
}
乘车人保存
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
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotBlank;
import java.util.Date;
@Data
public class PassengerSaveReq {
/**
* id
*/
private Long id;
/**
* 会员id
*/
private Long memberId;
/**
* 姓名
*/
@NotBlank(message = "【姓名】不能为空")
private String name;
/**
* 身份证
*/
@NotBlank(message = "【身份证】不能为空")
private String idCard;
/**
* 旅客类型|枚举[PassengerTypeEnum]
*/
@NotBlank(message = "【旅客类型】不能为空")
private String type;
/**
* 新增时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date createTime;
/**
* 修改时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date updateTime;
}
查票
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.jiawa.train.member.req;
import com.jiawa.train.common.req.PageReq;
@Data
public class TicketQueryReq extends PageReq {
private Long memberId;
@Override
public String toString() {
return "TicketQueryReq{" +
"memberId=" + memberId +
"} " + super.toString();
}
}
保存票
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.jiawa.train.member.req;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
@Data
public class TicketSaveReq {
/**
* id
*/
private Long id;
/**
* 会员id
*/
@NotNull(message = "【会员id】不能为空")
private Long memberId;
/**
* 乘客id
*/
@NotNull(message = "【乘客id】不能为空")
private Long passengerId;
/**
* 乘客姓名
*/
private String passengerName;
/**
* 日期
*/
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
@NotNull(message = "【日期】不能为空")
private Date trainDate;
/**
* 车次编号
*/
@NotBlank(message = "【车次编号】不能为空")
private String trainCode;
/**
* 箱序
*/
@NotNull(message = "【箱序】不能为空")
private Integer carriageIndex;
/**
* 排号|01, 02
*/
@NotBlank(message = "【排号】不能为空")
private String seatRow;
/**
* 列号|枚举[SeatColEnum]
*/
@NotBlank(message = "【列号】不能为空")
private String seatCol;
/**
* 出发站
*/
@NotBlank(message = "【出发站】不能为空")
private String startStation;
/**
* 出发时间
*/
@JsonFormat(pattern = "HH:mm:ss",timezone = "GMT+8")
@NotNull(message = "【出发时间】不能为空")
private Date startTime;
/**
* 到达站
*/
@NotBlank(message = "【到达站】不能为空")
private String endStation;
/**
* 到站时间
*/
@JsonFormat(pattern = "HH:mm:ss",timezone = "GMT+8")
@NotNull(message = "【到站时间】不能为空")
private Date endTime;
/**
* 座位类型|枚举[SeatTypeEnum]
*/
@NotBlank(message = "【座位类型】不能为空")
private String seatType;
/**
* 新增时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date createTime;
/**
* 修改时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date updateTime;
}
=====
resp
登录
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.jiawa.train.member.resp;
@Data
public class MemberLoginResp {
private Long id;
private String mobile;
private String token;
}
乘车人查询
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
package com.jiawa.train.member.resp;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.util.Date;
@Data
public class PassengerQueryResp {
/**
* id
*/
@JsonSerialize(using= ToStringSerializer.class)
private Long id;
/**
* 会员id
*/
@JsonSerialize(using= ToStringSerializer.class)
private Long memberId;
/**
* 姓名
*/
private String name;
/**
* 身份证
*/
private String idCard;
/**
* 旅客类型|枚举[PassengerTypeEnum]
*/
private String type;
/**
* 新增时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date createTime;
/**
* 修改时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date updateTime;
}
车票查询
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.jiawa.train.member.resp;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
@Data
public class TicketQueryResp {
/**
* id
*/
@JsonSerialize(using= ToStringSerializer.class)
private Long id;
/**
* 会员id
*/
@JsonSerialize(using= ToStringSerializer.class)
private Long memberId;
/**
* 乘客id
*/
@JsonSerialize(using= ToStringSerializer.class)
private Long passengerId;
/**
* 乘客姓名
*/
private String passengerName;
/**
* 日期
*/
@JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
private Date trainDate;
/**
* 车次编号
*/
private String trainCode;
/**
* 箱序
*/
private Integer carriageIndex;
/**
* 排号|01, 02
*/
private String seatRow;
/**
* 列号|枚举[SeatColEnum]
*/
private String seatCol;
/**
* 出发站
*/
private String startStation;
/**
* 出发时间
*/
@JsonFormat(pattern = "HH:mm:ss",timezone = "GMT+8")
private Date startTime;
/**
* 到达站
*/
private String endStation;
/**
* 到站时间
*/
@JsonFormat(pattern = "HH:mm:ss",timezone = "GMT+8")
private Date endTime;
/**
* 座位类型|枚举[SeatTypeEnum]
*/
private String seatType;
/**
* 新增时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date createTime;
/**
* 修改时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date updateTime;
}
====
enum
乘车人类型
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
package com.jiawa.train.member.enums;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
public enum PassengerTypeEnum {
ADULT("1", "成人"),
CHILD("2", "儿童"),
STUDENT("3", "学生");
private String code;
private String desc;
PassengerTypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public static List<HashMap<String,String>> getEnumList() {
List<HashMap<String, String>> list = new ArrayList<>();
for (PassengerTypeEnum anEnum : EnumSet.allOf(PassengerTypeEnum.class)) {
HashMap<String, String> map = new HashMap<>();
map.put("code",anEnum.code);
map.put("desc",anEnum.desc);
list.add(map);
}
return list;
}
}
config
springmvc配置
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
import com.jiawa.train.common.interceptor.LogInterceptor;
import com.jiawa.train.common.interceptor.MemberInterceptor;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
@Resource
LogInterceptor logInterceptor;
@Resource
MemberInterceptor memberInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor);
// 路径不要包含context-path
registry.addInterceptor(memberInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/hello",
"/member/send-code",
"/member/login"
);
}
}
====
service
会员member
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.jiawa.train.common.exception.BusinessException;
import com.jiawa.train.common.exception.BusinessExceptionEnum;
import com.jiawa.train.common.util.JwtUtil;
import com.jiawa.train.common.util.SnowUtil;
import com.jiawa.train.member.domain.Member;
import com.jiawa.train.member.domain.MemberExample;
import com.jiawa.train.member.mapper.MemberMapper;
import com.jiawa.train.member.req.MemberLoginReq;
import com.jiawa.train.member.req.MemberRegisterReq;
import com.jiawa.train.member.req.MemberSendCodeReq;
import com.jiawa.train.member.resp.MemberLoginResp;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MemberService {
private static final Logger LOG = LoggerFactory.getLogger(MemberService.class);
@Resource
private MemberMapper memberMapper;
public int count() {
return Math.toIntExact(memberMapper.countByExample(null));
}
//注册
public long register(MemberRegisterReq req) {
String mobile = req.getMobile();
Member memberDB = selectByMobile(mobile);
if (ObjectUtil.isNull(memberDB)) {
// return list.get(0).getId();
throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_EXIST);
}
Member member = new Member();
member.setId(SnowUtil.getSnowflakeNextId());
member.setMobile(mobile);
memberMapper.insert(member);
return member.getId();
}
//发送验证码
public void sendCode(MemberSendCodeReq req) {
String mobile = req.getMobile();
Member memberDB = selectByMobile(mobile);
// 如果手机号不存在,则插入一条记录
if (ObjectUtil.isNull(memberDB)) {
LOG.info("手机号不存在,插入一条记录");
Member member = new Member();
member.setId(SnowUtil.getSnowflakeNextId());
member.setMobile(mobile);
memberMapper.insert(member);
} else {
LOG.info("手机号存在,不插入记录");
}
// 生成验证码
// String code = RandomUtil.randomString(4);
String code = "8888";
LOG.info("生成短信验证码:{}", code);
// 保存短信记录表:手机号,短信验证码,有效期,是否已使用,业务类型,发送时间,使用时间
LOG.info("保存短信记录表");
// 对接短信通道,发送短信
LOG.info("对接短信通道");
}
//登录
public MemberLoginResp login(MemberLoginReq req) {
String mobile = req.getMobile();
String code = req.getCode();
Member memberDB = selectByMobile(mobile);
// 如果手机号不存在,则插入一条记录
if (ObjectUtil.isNull(memberDB)) {
throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_NOT_EXIST);
}
// 校验短信验证码
if (!"8888".equals(code)) {
throw new BusinessException(BusinessExceptionEnum.MEMBER_MOBILE_CODE_ERROR);
}
MemberLoginResp memberLoginResp = BeanUtil.copyProperties(memberDB, MemberLoginResp.class);
String token = JwtUtil.createToken(memberLoginResp.getId(), memberLoginResp.getMobile());
memberLoginResp.setToken(token);
return memberLoginResp;
}
private Member selectByMobile(String mobile) {
MemberExample memberExample = new MemberExample();
memberExample.createCriteria().andMobileEqualTo(mobile);
List<Member> list = memberMapper.selectByExample(memberExample);
if (CollUtil.isEmpty(list)) {
return null;
} else {
return list.get(0);
}
}
}
乘客
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.jiawa.train.member.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import com.jiawa.train.member.domain.Member;
import com.jiawa.train.member.domain.MemberExample;
import com.jiawa.train.member.domain.Passenger;
import com.jiawa.train.member.domain.PassengerExample;
import com.jiawa.train.member.enums.PassengerTypeEnum;
import com.jiawa.train.member.mapper.MemberMapper;
import com.jiawa.train.member.mapper.PassengerMapper;
import com.jiawa.train.member.req.PassengerQueryReq;
import com.jiawa.train.member.req.PassengerSaveReq;
import com.jiawa.train.member.resp.PassengerQueryResp;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
public class PassengerService {
private static final Logger LOG = LoggerFactory.getLogger(PassengerService.class);
@Resource
private PassengerMapper passengerMapper;
@Resource
private MemberMapper memberMapper;
//保存乘车人
public void save(PassengerSaveReq req) {
DateTime now = DateTime.now();
Passenger passenger = BeanUtil.copyProperties(req, Passenger.class);
if (ObjectUtil.isNull(passenger.getId())) {
passenger.setMemberId(LoginMemberContext.getId());
passenger.setId(SnowUtil.getSnowflakeNextId());
passenger.setCreateTime(now);
passenger.setUpdateTime(now);
passengerMapper.insert(passenger);
} else {
passenger.setUpdateTime(now);
passengerMapper.updateByPrimaryKey(passenger);
}
}
//查询乘车人
public PageResp<PassengerQueryResp> queryList(PassengerQueryReq req) {
PassengerExample passengerExample = new PassengerExample();
passengerExample.setOrderByClause("id desc");
PassengerExample.Criteria criteria = passengerExample.createCriteria();
if (ObjectUtil.isNotNull(req.getMemberId())) {
criteria.andMemberIdEqualTo(req.getMemberId());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<Passenger> passengerList = passengerMapper.selectByExample(passengerExample);
PageInfo<Passenger> pageInfo = new PageInfo<>(passengerList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<PassengerQueryResp> list = BeanUtil.copyToList(passengerList, PassengerQueryResp.class);
PageResp<PassengerQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
//删除
public void delete(Long id) {
passengerMapper.deleteByPrimaryKey(id);
}
/**
* 查询我的所有乘客
*/
public List<PassengerQueryResp> queryMine() {
PassengerExample passengerExample = new PassengerExample();
passengerExample.setOrderByClause("name asc");
PassengerExample.Criteria criteria = passengerExample.createCriteria();
criteria.andMemberIdEqualTo(LoginMemberContext.getId());
List<Passenger> list = passengerMapper.selectByExample(passengerExample);
return BeanUtil.copyToList(list, PassengerQueryResp.class);
}
/**
* 初始化乘客,如果没有张三,就增加乘客张三,李四、王五同理,防止线上体验时乘客被删光
*/
public void init() {
DateTime now = DateTime.now();
MemberExample memberExample = new MemberExample();
memberExample.createCriteria().andMobileEqualTo("13000000000");
List<Member> memberList = memberMapper.selectByExample(memberExample);
Member member = memberList.get(0);
List<Passenger> passengerList = new ArrayList<>();
List<String> nameList = Arrays.asList("张三", "李四", "王五");
for (String s : nameList) {
Passenger passenger = new Passenger();
passenger.setId(SnowUtil.getSnowflakeNextId());
passenger.setMemberId(member.getId());
passenger.setName(s);
passenger.setIdCard("123456789123456789");
passenger.setType(PassengerTypeEnum.ADULT.getCode());
passenger.setCreateTime(now);
passenger.setUpdateTime(now);
passengerList.add(passenger);
}
for (Passenger passenger : passengerList) {
PassengerExample passengerExample = new PassengerExample();
passengerExample.createCriteria().andNameEqualTo(passenger.getName());
long l = passengerMapper.countByExample(passengerExample);
if (l == 0) {
passengerMapper.insert(passenger);
}
}
}
}
车票
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.jiawa.train.member.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.common.req.MemberTicketReq;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import com.jiawa.train.member.domain.Ticket;
import com.jiawa.train.member.domain.TicketExample;
import com.jiawa.train.member.mapper.TicketMapper;
import com.jiawa.train.member.req.TicketQueryReq;
import com.jiawa.train.member.resp.TicketQueryResp;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TicketService {
private static final Logger LOG = LoggerFactory.getLogger(TicketService.class);
@Resource
private TicketMapper ticketMapper;
/**
* 会员购买车票后新增保存
*
* @param req
*/
public void save(MemberTicketReq req) throws Exception {
// LOG.info("seata全局事务ID save: {}", RootContext.getXID());
DateTime now = DateTime.now();
Ticket ticket = BeanUtil.copyProperties(req, Ticket.class);
ticket.setId(SnowUtil.getSnowflakeNextId());
ticket.setCreateTime(now);
ticket.setUpdateTime(now);
ticketMapper.insert(ticket);
// 模拟被调用方出现异常
// if (1 == 1) {
// throw new Exception("测试异常11");
// }
}
public PageResp<TicketQueryResp> queryList(TicketQueryReq req) {
TicketExample ticketExample = new TicketExample();
ticketExample.setOrderByClause("id desc");
TicketExample.Criteria criteria = ticketExample.createCriteria();
if (ObjUtil.isNotNull(req.getMemberId())) {
criteria.andMemberIdEqualTo(req.getMemberId());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<Ticket> ticketList = ticketMapper.selectByExample(ticketExample);
PageInfo<Ticket> pageInfo = new PageInfo<>(ticketList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<TicketQueryResp> list = BeanUtil.copyToList(ticketList, TicketQueryResp.class);
PageResp<TicketQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
ticketMapper.deleteByPrimaryKey(id);
}
}
=====
controller
member会员
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
package com.jiawa.train.member.controller;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.member.req.MemberLoginReq;
import com.jiawa.train.member.req.MemberRegisterReq;
import com.jiawa.train.member.req.MemberSendCodeReq;
import com.jiawa.train.member.resp.MemberLoginResp;
import com.jiawa.train.member.service.MemberService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/member")
public class MemberController {
@Resource
private MemberService memberService;
@GetMapping("/count")
public CommonResp<Integer> count() {
int count = memberService.count();
CommonResp<Integer> commonResp = new CommonResp<>();
commonResp.setContent(count);
return commonResp;
}
@PostMapping("/register")
public CommonResp<Long> register(@Valid MemberRegisterReq req) {
long register = memberService.register(req);
// CommonResp<Long> commonResp = new CommonResp<>();
// commonResp.setContent(register);
// return commonResp;
return new CommonResp<>(register);
}
@PostMapping("/send-code")
public CommonResp<Long> sendCode(@Valid @RequestBody MemberSendCodeReq req) {
memberService.sendCode(req);
return new CommonResp<>();
}
@PostMapping("/login")
public CommonResp<MemberLoginResp> login(@Valid @RequestBody MemberLoginReq req) {
MemberLoginResp resp = memberService.login(req);
return new CommonResp<>(resp);
}
}
buisness业务模块
主要的抢票业务流程
pom
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>train</artifactId>
<groupId>com.jiawa</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>business</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.jiawa</groupId>
<artifactId>common</artifactId>
</dependency>
<!--远程调用openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--openfeign默认使用的是loadBalance的负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--暂不使用redisson,演示机是macbook m2,经常会启动失败,报错:连接不到redis或获取不到连接-->
<!--<dependency>-->
<!-- <groupId>org.redisson</groupId>-->
<!-- <artifactId>redisson-spring-boot-starter</artifactId>-->
<!--</dependency>-->
<!-- 限流熔断 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- sentinel + nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- 图形验证码 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
</dependency>
<!--<!–RocketMQ–>-->
<!--<dependency>-->
<!-- <groupId>org.apache.rocketmq</groupId>-->
<!-- <artifactId>rocketmq-spring-boot-starter</artifactId>-->
<!--</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>/dist/${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 配置当前项目的jdk版本信息 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
===
sql
sql
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
drop table if exists `station`;
create table `station` (
`id` bigint not null comment 'id',
`name` varchar(20) not null comment '站名',
`name_pinyin` varchar(50) not null comment '站名拼音',
`name_py` varchar(50) not null comment '站名拼音首字母',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
unique key `name_unique` (`name`)
) engine=innodb default charset=utf8mb4 comment='车站';
drop table if exists `train`;
create table `train` (
`id` bigint not null comment 'id',
`code` varchar(20) not null comment '车次编号',
`type` char(1) not null comment '车次类型|枚举[TrainTypeEnum]',
`start` varchar(20) not null comment '始发站',
`start_pinyin` varchar(50) not null comment '始发站拼音',
`start_time` time not null comment '出发时间',
`end` varchar(20) not null comment '终点站',
`end_pinyin` varchar(50) not null comment '终点站拼音',
`end_time` time not null comment '到站时间',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
unique key `code_unique` (`code`)
) engine=innodb default charset=utf8mb4 comment='车次';
drop table if exists `train_station`;
create table `train_station` (
`id` bigint not null comment 'id',
`train_code` varchar(20) not null comment '车次编号',
`index` int not null comment '站序',
`name` varchar(20) not null comment '站名',
`name_pinyin` varchar(50) not null comment '站名拼音',
`in_time` time comment '进站时间',
`out_time` time comment '出站时间',
`stop_time` time comment '停站时长',
`km` decimal(8, 2) not null comment '里程(公里)|从上一站到本站的距离',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
unique key `train_code_index_unique` (`train_code`, `index`),
unique key `train_code_name_unique` (`train_code`, `name`)
) engine=innodb default charset=utf8mb4 comment='火车车站';
drop table if exists `train_carriage`;
create table `train_carriage` (
`id` bigint not null comment 'id',
`train_code` varchar(20) not null comment '车次编号',
`index` int not null comment '厢号',
`seat_type` char(1) not null comment '座位类型|枚举[SeatTypeEnum]',
`seat_count` int not null comment '座位数',
`row_count` int not null comment '排数',
`col_count` int not null comment '列数',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
unique key `train_code_index_unique` (`train_code`, `index`),
primary key (`id`)
) engine=innodb default charset=utf8mb4 comment='火车车厢';
drop table if exists `train_seat`;
create table `train_seat` (
`id` bigint not null comment 'id',
`train_code` varchar(20) not null comment '车次编号',
`carriage_index` int not null comment '厢序',
`row` char(2) not null comment '排号|01, 02',
`col` char(1) not null comment '列号|枚举[SeatColEnum]',
`seat_type` char(1) not null comment '座位类型|枚举[SeatTypeEnum]',
`carriage_seat_index` int not null comment '同车厢座序',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`)
) engine=innodb default charset=utf8mb4 comment='座位';
drop table if exists `daily_train`;
create table `daily_train` (
`id` bigint not null comment 'id',
`date` date not null comment '日期',
`code` varchar(20) not null comment '车次编号',
`type` char(1) not null comment '车次类型|枚举[TrainTypeEnum]',
`start` varchar(20) not null comment '始发站',
`start_pinyin` varchar(50) not null comment '始发站拼音',
`start_time` time not null comment '出发时间',
`end` varchar(20) not null comment '终点站',
`end_pinyin` varchar(50) not null comment '终点站拼音',
`end_time` time not null comment '到站时间',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
unique key `date_code_unique` (`date`, `code`)
) engine=innodb default charset=utf8mb4 comment='每日车次';
drop table if exists `daily_train_station`;
create table `daily_train_station` (
`id` bigint not null comment 'id',
`date` date not null comment '日期',
`train_code` varchar(20) not null comment '车次编号',
`index` int not null comment '站序|第一站是0',
`name` varchar(20) not null comment '站名',
`name_pinyin` varchar(50) not null comment '站名拼音',
`in_time` time comment '进站时间',
`out_time` time comment '出站时间',
`stop_time` time comment '停站时长',
`km` decimal(8, 2) not null comment '里程(公里)|从上一站到本站的距离',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
unique key `date_train_code_index_unique` (`date`, `train_code`, `index`),
unique key `date_train_code_name_unique` (`date`, `train_code`, `name`)
) engine=innodb default charset=utf8mb4 comment='每日车站';
drop table if exists `daily_train_carriage`;
create table `daily_train_carriage` (
`id` bigint not null comment 'id',
`date` date not null comment '日期',
`train_code` varchar(20) not null comment '车次编号',
`index` int not null comment '箱序',
`seat_type` char(1) not null comment '座位类型|枚举[SeatTypeEnum]',
`seat_count` int not null comment '座位数',
`row_count` int not null comment '排数',
`col_count` int not null comment '列数',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
unique key `date_train_code_index_unique` (`date`, `train_code`, `index`)
) engine=innodb default charset=utf8mb4 comment='每日车厢';
drop table if exists `daily_train_seat`;
create table `daily_train_seat` (
`id` bigint not null comment 'id',
`date` date not null comment '日期',
`train_code` varchar(20) not null comment '车次编号',
`carriage_index` int not null comment '箱序',
`row` char(2) not null comment '排号|01, 02',
`col` char(1) not null comment '列号|枚举[SeatColEnum]',
`seat_type` char(1) not null comment '座位类型|枚举[SeatTypeEnum]',
`carriage_seat_index` int not null comment '同车箱座序',
`sell` varchar(50) not null comment '售卖情况|将经过的车站用01拼接,0表示可卖,1表示已卖',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`)
) engine=innodb default charset=utf8mb4 comment='每日座位';
drop table if exists `daily_train_ticket`;
create table `daily_train_ticket` (
`id` bigint not null comment 'id',
`date` date not null comment '日期',
`train_code` varchar(20) not null comment '车次编号',
`start` varchar(20) not null comment '出发站',
`start_pinyin` varchar(50) not null comment '出发站拼音',
`start_time` time not null comment '出发时间',
`start_index` int not null comment '出发站序|本站是整个车次的第几站',
`end` varchar(20) not null comment '到达站',
`end_pinyin` varchar(50) not null comment '到达站拼音',
`end_time` time not null comment '到站时间',
`end_index` int not null comment '到站站序|本站是整个车次的第几站',
`ydz` int not null comment '一等座余票',
`ydz_price` decimal(8, 2) not null comment '一等座票价',
`edz` int not null comment '二等座余票',
`edz_price` decimal(8, 2) not null comment '二等座票价',
`rw` int not null comment '软卧余票',
`rw_price` decimal(8, 2) not null comment '软卧票价',
`yw` int not null comment '硬卧余票',
`yw_price` decimal(8, 2) not null comment '硬卧票价',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
unique key `date_train_code_start_end_unique` (`date`, `train_code`, `start`, `end`)
) engine=innodb default charset=utf8mb4 comment='余票信息';
drop table if exists `confirm_order`;
create table `confirm_order` (
`id` bigint not null comment 'id',
`member_id` bigint not null comment '会员id',
`date` date not null comment '日期',
`train_code` varchar(20) not null comment '车次编号',
`start` varchar(20) not null comment '出发站',
`end` varchar(20) not null comment '到达站',
`daily_train_ticket_id` bigint not null comment '余票ID',
`tickets` json not null comment '车票',
`status` char(1) not null comment '订单状态|枚举[ConfirmOrderStatusEnum]',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
index `date_train_code_index` (`date`, `train_code`)
) engine=innodb default charset=utf8mb4 comment='确认订单';
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
drop table if exists `sk_token`;
create table `sk_token` (
`id` bigint not null comment 'id',
`date` date not null comment '日期',
`train_code` varchar(20) not null comment '车次编号',
`count` int not null comment '令牌余量',
`create_time` datetime(3) comment '新增时间',
`update_time` datetime(3) comment '修改时间',
primary key (`id`),
unique key `date_train_code_unique` (`date`, `train_code`)
) engine=innodb default charset=utf8mb4 comment='秒杀令牌';
===
resource
application
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
server.port=8002
server.servlet.context-path=/business
spring.application.name=business
# 数据库连接
spring.datasource.url=jdbc:mysql://rm-uf62v90x7t5hukv5dro.mysql.rds.aliyuncs.com/train_business?characterEncoding=UTF8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=train_business
spring.datasource.password=Business123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mybatis xml路径
mybatis.mapper-locations=classpath:/mapper/**/*.xml
logging.level.com.jiawa.train.business.mapper=trace
# 配置为statement,即关闭一级缓存
mybatis.configuration.local-cache-scope=statement
spring.data.redis.host=r-uf6ljbcdaxobsifyctpd.redis.rds.aliyuncs.com
spring.data.redis.port=6379
spring.data.redis.password=Redis000
spring.cache.type=redis
spring.cache.redis.use-key-prefix=true
spring.cache.redis.key-prefix=train_cache_
spring.cache.redis.cache-null-values=true
spring.cache.redis.time-to-live=60s
# rocketmq
#rocketmq.name-server=http://localhost:9876
#rocketmq.producer.group=default
bootstrap-prod
1
2
3
4
5
6
## nacos server注册中心地址,使用ECS内网IP
spring.cloud.nacos.discovery.server-addr=172.17.208.8:8848
spring.cloud.nacos.discovery.namespace=train
## 关于生成的数据库、RDS,可以通过生产nacos来配置
bootstrap
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
63
64
65
66
67
# 注册中心的名字
spring.application.name=business
## 启动环境,nacos会根据环境读不同的配置dataId:business-dev.properties
spring.profiles.active=dev
## nacos server地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
## 配置中心文件后缀,默认properties
spring.cloud.nacos.config.file-extension=properties
## nacos命名空间
spring.cloud.nacos.config.namespace=train
## nacos server注册中心地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.namespace=train
# seata注册中心
seata.registry.type=nacos
seata.registry.nacos.application=seata-server
seata.registry.nacos.server-addr=127.0.0.1:8848
seata.registry.nacos.group=SEATA_GROUP
seata.registry.nacos.namespace=train
seata.registry.nacos.username=nacos
seata.registry.nacos.password=nacos
# seata配置中心
seata.config.type=nacos
seata.config.nacos.server-addr=127.0.0.1:8848
seata.config.nacos.group=SEATA_GROUP
seata.config.nacos.namespace=train
seata.config.nacos.dataId=seataServer.properties
seata.config.nacos.username=nacos
seata.config.nacos.password=nacos
# 事务组名称,必须在nacos中有配置过:service.vgroupMapping.train-group=default
#seata.tx-service-group=train-group
# 事务组和seata集群做关联
#seata.service.vgroup-mapping.test-group=default
# seata集群对应的机器
#seata.service.grouplist.default=127.0.0.1:8091
################################################
# 以下是nacos中的seataServer.properties的相关配置
################################################
# # 和微服务模块的seata.tx-service-group保持一致
# service.vgroupMapping.train-group=default
# service.default.grouplist=127.0.0.1:8091
#
# # 和微服务模块的seata.tx-service-group保持一致
# service.vgroupMapping.test-group=default1
# service.default1.grouplist=127.0.0.1:18091
################################################
# sentinel控台:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
spring.cloud.sentinel.transport.port=8719
spring.cloud.sentinel.transport.dashboard=localhost:18080
# 流控模式是链路时,必须关闭这个配置,默认是true,为true时可以在控台-簇点链路界面看到所有请求都在一个链路下面
spring.cloud.sentinel.web-context-unify=false
# sentinel + nacos
spring.cloud.sentinel.datasource.flow.nacos.serverAddr=127.0.0.1:8848
spring.cloud.sentinel.datasource.flow.nacos.namespace=train
spring.cloud.sentinel.datasource.flow.nacos.groupId=DEFAULT_GROUP
spring.cloud.sentinel.datasource.flow.nacos.dataId=sentinel-business-flow
spring.cloud.sentinel.datasource.flow.nacos.ruleType=flow
logback-spring
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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 修改一下路径-->
<property name="PATH" value="./log/business"></property>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %blue(%-50logger{50}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>-->
<Pattern>%d{mm:ss.SSS} %highlight(%-5level) %blue(%-30logger{30}:%-4line) %thread %green(%-18X{LOG_ID}) %msg%n</Pattern>
</encoder>
</appender>
<appender name="TRACE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/trace.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/trace.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
</layout>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PATH}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${PATH}/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<layout>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-50logger{50}:%-4line %green(%-18X{LOG_ID}) %msg%n</pattern>
</layout>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="ERROR">
<appender-ref ref="ERROR_FILE" />
</root>
<root level="TRACE">
<appender-ref ref="TRACE_FILE" />
</root>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
===
启动类
BusinessApplication
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
package com.jiawa.train.business.config;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.ArrayList;
import java.util.List;
@SpringBootApplication
@ComponentScan("com.jiawa")
@MapperScan("com.jiawa.train.*.mapper")
@EnableFeignClients("com.jiawa.train.business.feign")
@EnableCaching
@EnableAsync
public class BusinessApplication {
private static final Logger LOG = LoggerFactory.getLogger(BusinessApplication.class);
public static void main(String[] args) {
SpringApplication app = new SpringApplication(BusinessApplication.class);
Environment env = app.run(args).getEnvironment();
LOG.info("启动成功!!");
LOG.info("测试地址: \thttp://127.0.0.1:{}{}/hello", env.getProperty("server.port"), env.getProperty("server.servlet.context-path"));
// // 限流规则
// initFlowRules();
// LOG.info("已定义限流规则");
}
private static void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("doConfirm");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// Set limit QPS to 20.
rule.setCount(1);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
===
config
KaptchaConfig
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
package com.jiawa.train.business.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha getDefaultKaptcha() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "no");
// properties.setProperty("kaptcha.border.color", "105,179,90");
properties.setProperty("kaptcha.textproducer.font.color", "blue");
properties.setProperty("kaptcha.image.width", "90");
properties.setProperty("kaptcha.image.height", "28");
properties.setProperty("kaptcha.textproducer.font.size", "20");
properties.setProperty("kaptcha.session.key", "code");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.font.names", "Arial");
properties.setProperty("kaptcha.noise.color", "255,96,0");
properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
// properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
properties.setProperty("kaptcha.obscurificator.impl", KaptchaWaterRipple.class.getName());
properties.setProperty("kaptcha.background.impl", KaptchaNoBackhround.class.getName());
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
@Bean
public DefaultKaptcha getWebKaptcha() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "no");
// properties.setProperty("kaptcha.border.color", "105,179,90");
properties.setProperty("kaptcha.textproducer.font.color", "blue");
properties.setProperty("kaptcha.image.width", "90");
properties.setProperty("kaptcha.image.height", "45");
properties.setProperty("kaptcha.textproducer.font.size", "30");
properties.setProperty("kaptcha.session.key", "code");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.font.names", "Arial");
properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
properties.setProperty("kaptcha.obscurificator.impl", KaptchaWaterRipple.class.getName());
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
KaptchaNoBackhround
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
package com.jiawa.train.business.config;
import com.google.code.kaptcha.BackgroundProducer;
import com.google.code.kaptcha.util.Configurable;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
public class KaptchaNoBackhround extends Configurable implements BackgroundProducer {
public KaptchaNoBackhround(){
}
@Override
public BufferedImage addBackground(BufferedImage baseImage) {
int width = baseImage.getWidth();
int height = baseImage.getHeight();
BufferedImage imageWithBackground = new BufferedImage(width, height, 1);
Graphics2D graph = (Graphics2D)imageWithBackground.getGraphics();
graph.fill(new Rectangle2D.Double(0.0D, 0.0D, (double)width, (double)height));
graph.drawImage(baseImage, 0, 0, null);
return imageWithBackground;
}
}
KaptchaWaterRipple
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
package com.jiawa.train.business.config;
import com.google.code.kaptcha.GimpyEngine;
import com.google.code.kaptcha.NoiseProducer;
import com.google.code.kaptcha.util.Configurable;
import com.jhlabs.image.RippleFilter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.util.Random;
public class KaptchaWaterRipple extends Configurable implements GimpyEngine {
public KaptchaWaterRipple(){}
@Override
public BufferedImage getDistortedImage(BufferedImage baseImage) {
NoiseProducer noiseProducer = this.getConfig().getNoiseImpl();
BufferedImage distortedImage = new BufferedImage(baseImage.getWidth(), baseImage.getHeight(), 2);
Graphics2D graph = (Graphics2D)distortedImage.getGraphics();
Random rand = new Random();
RippleFilter rippleFilter = new RippleFilter();
rippleFilter.setXAmplitude(7.6F);
rippleFilter.setYAmplitude(rand.nextFloat() + 1.0F);
rippleFilter.setEdgeAction(1);
BufferedImage effectImage = rippleFilter.filter(baseImage, (BufferedImage)null);
graph.drawImage(effectImage, 0, 0, (Color)null, (ImageObserver)null);
graph.dispose();
noiseProducer.makeNoise(distortedImage, 0.1F, 0.1F, 0.25F, 0.25F);
noiseProducer.makeNoise(distortedImage, 0.1F, 0.25F, 0.5F, 0.9F);
return distortedImage;
}
}
RocketMQConfig
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
// package com.jiawa.train.business.config;
//
// import org.apache.rocketmq.client.producer.DefaultMQProducer;
// import org.apache.rocketmq.spring.core.RocketMQTemplate;
// import org.springframework.context.annotation.Bean;
// import org.springframework.stereotype.Component;
//
// @Component
// public class RocketMQConfig {
//
// /**
// * 新版本需要声明RocketMQTemplate,否则会注入失败
// * @return
// */
// @Bean
// public RocketMQTemplate rocketMQTemplate() {
// RocketMQTemplate rocketMQTemplate = new RocketMQTemplate();
// DefaultMQProducer producer = new DefaultMQProducer();
// producer.setProducerGroup("default");
// producer.setNamesrvAddr("http://localhost:9876");
// producer.setSendMsgTimeout(3000);
// rocketMQTemplate.setProducer(producer);
// return rocketMQTemplate;
// }
// }
SpringMvcConfig
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
package com.jiawa.train.business.config;
import com.jiawa.train.common.interceptor.LogInterceptor;
import com.jiawa.train.common.interceptor.MemberInterceptor;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
@Resource
LogInterceptor logInterceptor;
@Resource
MemberInterceptor memberInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor)
.addPathPatterns("/**");
registry.addInterceptor(memberInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(
"/hello"
);
}
}
===
controller
后台:
ConfirmOrderController
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.jiawa.train.business.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.jiawa.train.business.req.ConfirmOrderDoReq;
import com.jiawa.train.business.service.BeforeConfirmOrderService;
import com.jiawa.train.business.service.ConfirmOrderService;
import com.jiawa.train.common.exception.BusinessExceptionEnum;
import com.jiawa.train.common.resp.CommonResp;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/confirm-order")
public class ConfirmOrderController {
private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderController.class);
@Resource
private BeforeConfirmOrderService beforeConfirmOrderService;
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${spring.profiles.active}")
private String env;
@Resource
private ConfirmOrderService confirmOrderService;
// 接口的资源名称不要和接口路径一致,会导致限流后走不到降级方法中
@SentinelResource(value = "confirmOrderDo", blockHandler = "doConfirmBlock")
@PostMapping("/do")
public CommonResp<Object> doConfirm(@Valid @RequestBody ConfirmOrderDoReq req) {
if (!env.equals("dev")) {
// 图形验证码校验
String imageCodeToken = req.getImageCodeToken();
String imageCode = req.getImageCode();
String imageCodeRedis = redisTemplate.opsForValue().get(imageCodeToken);
LOG.info("从redis中获取到的验证码:{}", imageCodeRedis);
if (ObjectUtils.isEmpty(imageCodeRedis)) {
return new CommonResp<>(false, "验证码已过期", null);
}
// 验证码校验,大小写忽略,提升体验,比如Oo Vv Ww容易混
if (!imageCodeRedis.equalsIgnoreCase(imageCode)) {
return new CommonResp<>(false, "验证码不正确", null);
} else {
// 验证通过后,移除验证码
redisTemplate.delete(imageCodeToken);
}
}
Long id = beforeConfirmOrderService.beforeDoConfirm(req);
return new CommonResp<>(String.valueOf(id));
}
@GetMapping("/query-line-count/{id}")
public CommonResp<Integer> queryLineCount(@PathVariable Long id) {
Integer count = confirmOrderService.queryLineCount(id);
return new CommonResp<>(count);
}
@GetMapping("/cancel/{id}")
public CommonResp<Integer> cancel(@PathVariable Long id) {
Integer count = confirmOrderService.cancel(id);
return new CommonResp<>(count);
}
/** 降级方法,需包含限流方法的所有参数和BlockException参数,且返回值要保持一致
* @param req
* @param e
*/
public CommonResp<Object> doConfirmBlock(ConfirmOrderDoReq req, BlockException e) {
LOG.info("ConfirmOrderController购票请求被限流:{}", req);
// throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
CommonResp<Object> commonResp = new CommonResp<>();
commonResp.setSuccess(false);
commonResp.setMessage(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION.getDesc());
return commonResp;
}
}
DailyTrainStationController
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
package com.jiawa.train.business.controller;
import com.jiawa.train.business.req.DailyTrainStationQueryAllReq;
import com.jiawa.train.business.resp.DailyTrainStationQueryResp;
import com.jiawa.train.business.service.DailyTrainStationService;
import com.jiawa.train.common.resp.CommonResp;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/daily-train-station")
public class DailyTrainStationController {
@Autowired
private DailyTrainStationService dailyTrainStationService;
@GetMapping("/query-by-train-code")
public CommonResp<List<DailyTrainStationQueryResp>> queryByTrain(@Valid DailyTrainStationQueryAllReq req) {
List<DailyTrainStationQueryResp> list = dailyTrainStationService.queryByTrain(req.getDate(), req.getTrainCode());
return new CommonResp<>(list);
}
}
DailyTrainTicketController
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
package com.jiawa.train.business.controller;
import com.jiawa.train.business.req.DailyTrainTicketQueryReq;
import com.jiawa.train.business.resp.DailyTrainTicketQueryResp;
import com.jiawa.train.business.service.DailyTrainTicketService;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/daily-train-ticket")
public class DailyTrainTicketController {
@Resource
private DailyTrainTicketService dailyTrainTicketService;
@GetMapping("/query-list")
public CommonResp<PageResp<DailyTrainTicketQueryResp>> queryList(@Valid DailyTrainTicketQueryReq req) {
PageResp<DailyTrainTicketQueryResp> list = dailyTrainTicketService.queryList(req);
return new CommonResp<>(list);
}
}
KaptchaController
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
package com.jiawa.train.business.controller;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/kaptcha")
public class KaptchaController {
@Qualifier("getDefaultKaptcha")
@Autowired
DefaultKaptcha defaultKaptcha;
@Resource
public StringRedisTemplate stringRedisTemplate;
@GetMapping("/image-code/{imageCodeToken}")
public void imageCode(@PathVariable(value = "imageCodeToken") String imageCodeToken, HttpServletResponse httpServletResponse) throws Exception{
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
try {
// 生成验证码字符串
String createText = defaultKaptcha.createText();
// 将生成的验证码放入redis缓存中,后续验证的时候用到
stringRedisTemplate.opsForValue().set(imageCodeToken, createText, 300, TimeUnit.SECONDS);
// 使用验证码字符串生成验证码图片
BufferedImage challenge = defaultKaptcha.createImage(createText);
ImageIO.write(challenge, "jpg", jpegOutputStream);
} catch (IllegalArgumentException e) {
httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
byte[] captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
httpServletResponse.setHeader("Cache-Control", "no-store");
httpServletResponse.setHeader("Pragma", "no-cache");
httpServletResponse.setDateHeader("Expires", 0);
httpServletResponse.setContentType("image/jpeg");
ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
responseOutputStream.write(captchaChallengeAsJpeg);
responseOutputStream.flush();
responseOutputStream.close();
}
}
RedisController
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
package com.jiawa.train.business.controller;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class RedisController {
private static final Logger LOG = LoggerFactory.getLogger(RedisController.class);
@Resource
private RedisTemplate redisTemplate;
@RequestMapping("/redis/set/{key}/{value}")
public String set(@PathVariable String key, @PathVariable String value) {
redisTemplate.opsForValue().set(key, value, 3600, TimeUnit.SECONDS);
LOG.info("key: {}, value: {}", key, value);
return "success";
}
@RequestMapping("/redis/get/{key}")
public Object get(@PathVariable String key) {
Object object = redisTemplate.opsForValue().get(key);
LOG.info("key: {}, value: {}", key, object);
return object;
}
}
SeatSellController
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
package com.jiawa.train.business.controller;
import com.jiawa.train.business.req.SeatSellReq;
import com.jiawa.train.business.resp.SeatSellResp;
import com.jiawa.train.business.service.DailyTrainSeatService;
import com.jiawa.train.common.resp.CommonResp;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
;
@RestController
@RequestMapping("/seat-sell")
public class SeatSellController {
@Autowired
private DailyTrainSeatService dailyTrainSeatService;
@GetMapping("/query")
public CommonResp<List<SeatSellResp>> query(@Valid SeatSellReq req) {
List<SeatSellResp> seatList = dailyTrainSeatService.querySeatSell(req);
return new CommonResp<>(seatList);
}
}
StationController
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
package com.jiawa.train.business.controller;
import com.jiawa.train.business.resp.StationQueryResp;
import com.jiawa.train.business.service.StationService;
import com.jiawa.train.common.resp.CommonResp;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/station")
public class StationController {
@Resource
private StationService stationService;
@GetMapping("/query-all")
public CommonResp<List<StationQueryResp>> queryList() {
List<StationQueryResp> list = stationService.queryAll();
return new CommonResp<>(list);
}
}
TrainController
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
package com.jiawa.train.business.controller;
import com.jiawa.train.business.resp.TrainQueryResp;
import com.jiawa.train.business.service.TrainService;
import com.jiawa.train.common.resp.CommonResp;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/train")
public class TrainController {
@Resource
private TrainService trainService;
@GetMapping("/query-all")
public CommonResp<List<TrainQueryResp>> queryList() {
List<TrainQueryResp> list = trainService.queryAll();
return new CommonResp<>(list);
}
}
admin
ConfirmOrderAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.business.req.ConfirmOrderQueryReq;
import com.jiawa.train.business.req.ConfirmOrderDoReq;
import com.jiawa.train.business.resp.ConfirmOrderQueryResp;
import com.jiawa.train.business.service.ConfirmOrderService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/confirm-order")
public class ConfirmOrderAdminController {
@Resource
private ConfirmOrderService confirmOrderService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody ConfirmOrderDoReq req) {
confirmOrderService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<ConfirmOrderQueryResp>> queryList(@Valid ConfirmOrderQueryReq req) {
PageResp<ConfirmOrderQueryResp> list = confirmOrderService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
confirmOrderService.delete(id);
return new CommonResp<>();
}
}
DailyTrainAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.business.req.DailyTrainQueryReq;
import com.jiawa.train.business.req.DailyTrainSaveReq;
import com.jiawa.train.business.resp.DailyTrainQueryResp;
import com.jiawa.train.business.service.DailyTrainService;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
@RestController
@RequestMapping("/admin/daily-train")
public class DailyTrainAdminController {
@Resource
private DailyTrainService dailyTrainService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody DailyTrainSaveReq req) {
dailyTrainService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<DailyTrainQueryResp>> queryList(@Valid DailyTrainQueryReq req) {
PageResp<DailyTrainQueryResp> list = dailyTrainService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
dailyTrainService.delete(id);
return new CommonResp<>();
}
@GetMapping("/gen-daily/{date}")
public CommonResp<Object> genDaily(@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
dailyTrainService.genDaily(date);
return new CommonResp<>();
}
}
DailyTrainCarriageAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.business.req.DailyTrainCarriageQueryReq;
import com.jiawa.train.business.req.DailyTrainCarriageSaveReq;
import com.jiawa.train.business.resp.DailyTrainCarriageQueryResp;
import com.jiawa.train.business.service.DailyTrainCarriageService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/daily-train-carriage")
public class DailyTrainCarriageAdminController {
@Resource
private DailyTrainCarriageService dailyTrainCarriageService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody DailyTrainCarriageSaveReq req) {
dailyTrainCarriageService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<DailyTrainCarriageQueryResp>> queryList(@Valid DailyTrainCarriageQueryReq req) {
PageResp<DailyTrainCarriageQueryResp> list = dailyTrainCarriageService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
dailyTrainCarriageService.delete(id);
return new CommonResp<>();
}
}
DailyTrainSeatAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.business.req.DailyTrainSeatQueryReq;
import com.jiawa.train.business.req.DailyTrainSeatSaveReq;
import com.jiawa.train.business.resp.DailyTrainSeatQueryResp;
import com.jiawa.train.business.service.DailyTrainSeatService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/daily-train-seat")
public class DailyTrainSeatAdminController {
@Resource
private DailyTrainSeatService dailyTrainSeatService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody DailyTrainSeatSaveReq req) {
dailyTrainSeatService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<DailyTrainSeatQueryResp>> queryList(@Valid DailyTrainSeatQueryReq req) {
PageResp<DailyTrainSeatQueryResp> list = dailyTrainSeatService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
dailyTrainSeatService.delete(id);
return new CommonResp<>();
}
}
DailyTrainStationAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.business.req.DailyTrainStationQueryReq;
import com.jiawa.train.business.req.DailyTrainStationSaveReq;
import com.jiawa.train.business.resp.DailyTrainStationQueryResp;
import com.jiawa.train.business.service.DailyTrainStationService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/daily-train-station")
public class DailyTrainStationAdminController {
@Resource
private DailyTrainStationService dailyTrainStationService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody DailyTrainStationSaveReq req) {
dailyTrainStationService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<DailyTrainStationQueryResp>> queryList(@Valid DailyTrainStationQueryReq req) {
PageResp<DailyTrainStationQueryResp> list = dailyTrainStationService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
dailyTrainStationService.delete(id);
return new CommonResp<>();
}
}
DailyTrainTicketAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.business.req.DailyTrainTicketQueryReq;
import com.jiawa.train.business.req.DailyTrainTicketSaveReq;
import com.jiawa.train.business.resp.DailyTrainTicketQueryResp;
import com.jiawa.train.business.service.DailyTrainTicketService;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/daily-train-ticket")
public class DailyTrainTicketAdminController {
@Resource
private DailyTrainTicketService dailyTrainTicketService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody DailyTrainTicketSaveReq req) {
dailyTrainTicketService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<DailyTrainTicketQueryResp>> queryList(@Valid DailyTrainTicketQueryReq req) {
PageResp<DailyTrainTicketQueryResp> list = dailyTrainTicketService.queryList(req);
return new CommonResp<>(list);
}
@GetMapping("/query-list2")
public CommonResp<PageResp<DailyTrainTicketQueryResp>> queryList2(@Valid DailyTrainTicketQueryReq req) {
PageResp<DailyTrainTicketQueryResp> list = dailyTrainTicketService.queryList2(req);
return new CommonResp<>(list);
}
@GetMapping("/query-list3")
public CommonResp<PageResp<DailyTrainTicketQueryResp>> queryList3(@Valid DailyTrainTicketQueryReq req) {
PageResp<DailyTrainTicketQueryResp> list = dailyTrainTicketService.queryList3(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
dailyTrainTicketService.delete(id);
return new CommonResp<>();
}
}
SkTokenAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.business.req.SkTokenQueryReq;
import com.jiawa.train.business.req.SkTokenSaveReq;
import com.jiawa.train.business.resp.SkTokenQueryResp;
import com.jiawa.train.business.service.SkTokenService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/sk-token")
public class SkTokenAdminController {
@Resource
private SkTokenService skTokenService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody SkTokenSaveReq req) {
skTokenService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<SkTokenQueryResp>> queryList(@Valid SkTokenQueryReq req) {
PageResp<SkTokenQueryResp> list = skTokenService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
skTokenService.delete(id);
return new CommonResp<>();
}
}
StationAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.business.req.StationQueryReq;
import com.jiawa.train.business.req.StationSaveReq;
import com.jiawa.train.business.resp.StationQueryResp;
import com.jiawa.train.business.service.StationService;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/admin/station")
public class StationAdminController {
@Resource
private StationService stationService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody StationSaveReq req) {
stationService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<StationQueryResp>> queryList(@Valid StationQueryReq req) {
PageResp<StationQueryResp> list = stationService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
stationService.delete(id);
return new CommonResp<>();
}
@GetMapping("/query-all")
public CommonResp<List<StationQueryResp>> queryList() {
List<StationQueryResp> list = stationService.queryAll();
return new CommonResp<>(list);
}
}
TrainAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.business.req.TrainQueryReq;
import com.jiawa.train.business.req.TrainSaveReq;
import com.jiawa.train.business.resp.TrainQueryResp;
import com.jiawa.train.business.service.TrainSeatService;
import com.jiawa.train.business.service.TrainService;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/admin/train")
public class TrainAdminController {
@Resource
private TrainService trainService;
@Resource
private TrainSeatService trainSeatService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody TrainSaveReq req) {
trainService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<TrainQueryResp>> queryList(@Valid TrainQueryReq req) {
PageResp<TrainQueryResp> list = trainService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
trainService.delete(id);
return new CommonResp<>();
}
@GetMapping("/query-all")
public CommonResp<List<TrainQueryResp>> queryList() {
List<TrainQueryResp> list = trainService.queryAll();
return new CommonResp<>(list);
}
@GetMapping("/gen-seat/{trainCode}")
public CommonResp<Object> genSeat(@PathVariable String trainCode) {
trainSeatService.genTrainSeat(trainCode);
return new CommonResp<>();
}
}
TrainCarriageAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.business.req.TrainCarriageQueryReq;
import com.jiawa.train.business.req.TrainCarriageSaveReq;
import com.jiawa.train.business.resp.TrainCarriageQueryResp;
import com.jiawa.train.business.service.TrainCarriageService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/train-carriage")
public class TrainCarriageAdminController {
@Resource
private TrainCarriageService trainCarriageService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody TrainCarriageSaveReq req) {
trainCarriageService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<TrainCarriageQueryResp>> queryList(@Valid TrainCarriageQueryReq req) {
PageResp<TrainCarriageQueryResp> list = trainCarriageService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
trainCarriageService.delete(id);
return new CommonResp<>();
}
}
TrainCarriageAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.business.req.TrainCarriageQueryReq;
import com.jiawa.train.business.req.TrainCarriageSaveReq;
import com.jiawa.train.business.resp.TrainCarriageQueryResp;
import com.jiawa.train.business.service.TrainCarriageService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/train-carriage")
public class TrainCarriageAdminController {
@Resource
private TrainCarriageService trainCarriageService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody TrainCarriageSaveReq req) {
trainCarriageService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<TrainCarriageQueryResp>> queryList(@Valid TrainCarriageQueryReq req) {
PageResp<TrainCarriageQueryResp> list = trainCarriageService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
trainCarriageService.delete(id);
return new CommonResp<>();
}
}
TrainSeatAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.business.req.TrainSeatQueryReq;
import com.jiawa.train.business.req.TrainSeatSaveReq;
import com.jiawa.train.business.resp.TrainSeatQueryResp;
import com.jiawa.train.business.service.TrainSeatService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/train-seat")
public class TrainSeatAdminController {
@Resource
private TrainSeatService trainSeatService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody TrainSeatSaveReq req) {
trainSeatService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<TrainSeatQueryResp>> queryList(@Valid TrainSeatQueryReq req) {
PageResp<TrainSeatQueryResp> list = trainSeatService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
trainSeatService.delete(id);
return new CommonResp<>();
}
}
TrainStationAdminController
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
package com.jiawa.train.business.controller.admin;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.resp.CommonResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.business.req.TrainStationQueryReq;
import com.jiawa.train.business.req.TrainStationSaveReq;
import com.jiawa.train.business.resp.TrainStationQueryResp;
import com.jiawa.train.business.service.TrainStationService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/train-station")
public class TrainStationAdminController {
@Resource
private TrainStationService trainStationService;
@PostMapping("/save")
public CommonResp<Object> save(@Valid @RequestBody TrainStationSaveReq req) {
trainStationService.save(req);
return new CommonResp<>();
}
@GetMapping("/query-list")
public CommonResp<PageResp<TrainStationQueryResp>> queryList(@Valid TrainStationQueryReq req) {
PageResp<TrainStationQueryResp> list = trainStationService.queryList(req);
return new CommonResp<>(list);
}
@DeleteMapping("/delete/{id}")
public CommonResp<Object> delete(@PathVariable Long id) {
trainStationService.delete(id);
return new CommonResp<>();
}
}
====
service
AfterConfirmOrderService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package com.jiawa.train.business.service;
import com.jiawa.train.business.domain.ConfirmOrder;
import com.jiawa.train.business.domain.DailyTrainSeat;
import com.jiawa.train.business.domain.DailyTrainTicket;
import com.jiawa.train.business.enums.ConfirmOrderStatusEnum;
import com.jiawa.train.business.feign.MemberFeign;
import com.jiawa.train.business.mapper.ConfirmOrderMapper;
import com.jiawa.train.business.mapper.DailyTrainSeatMapper;
import com.jiawa.train.business.mapper.cust.DailyTrainTicketMapperCust;
import com.jiawa.train.business.req.ConfirmOrderTicketReq;
import com.jiawa.train.common.req.MemberTicketReq;
import com.jiawa.train.common.resp.CommonResp;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Service
public class AfterConfirmOrderService {
private static final Logger LOG = LoggerFactory.getLogger(AfterConfirmOrderService.class);
@Resource
private DailyTrainSeatMapper dailyTrainSeatMapper;
@Resource
private DailyTrainTicketMapperCust dailyTrainTicketMapperCust;
@Resource
private MemberFeign memberFeign;
@Resource
private ConfirmOrderMapper confirmOrderMapper;
/**
* 选中座位后事务处理:
* 座位表修改售卖情况sell;
* 余票详情表修改余票;
* 为会员增加购票记录
* 更新确认订单为成功
*/
// @Transactional
// @GlobalTransactional
public void afterDoConfirm(DailyTrainTicket dailyTrainTicket, List<DailyTrainSeat> finalSeatList, List<ConfirmOrderTicketReq> tickets, ConfirmOrder confirmOrder) throws Exception {
// LOG.info("seata全局事务ID: {}", RootContext.getXID());
for (int j = 0; j < finalSeatList.size(); j++) {
DailyTrainSeat dailyTrainSeat = finalSeatList.get(j);
DailyTrainSeat seatForUpdate = new DailyTrainSeat();
seatForUpdate.setId(dailyTrainSeat.getId());
seatForUpdate.setSell(dailyTrainSeat.getSell());
seatForUpdate.setUpdateTime(new Date());
dailyTrainSeatMapper.updateByPrimaryKeySelective(seatForUpdate);
// 计算这个站卖出去后,影响了哪些站的余票库存
// 参照2-3节 如何保证不超卖、不少卖,还要能承受极高的并发 10:30左右
// 影响的库存:本次选座之前没卖过票的,和本次购买的区间有交集的区间
// 假设10个站,本次买4~7站
// 原售:001000001
// 购买:000011100
// 新售:001011101
// 影响:XXX11111X
// Integer startIndex = 4;
// Integer endIndex = 7;
// Integer minStartIndex = startIndex - 往前碰到的最后一个0;
// Integer maxStartIndex = endIndex - 1;
// Integer minEndIndex = startIndex + 1;
// Integer maxEndIndex = endIndex + 往后碰到的最后一个0;
Integer startIndex = dailyTrainTicket.getStartIndex();
Integer endIndex = dailyTrainTicket.getEndIndex();
char[] chars = seatForUpdate.getSell().toCharArray();
Integer maxStartIndex = endIndex - 1;
Integer minEndIndex = startIndex + 1;
Integer minStartIndex = 0;
for (int i = startIndex - 1; i >= 0; i--) {
char aChar = chars[i];
if (aChar == '1') {
minStartIndex = i + 1;
break;
}
}
LOG.info("影响出发站区间:" + minStartIndex + "-" + maxStartIndex);
Integer maxEndIndex = seatForUpdate.getSell().length();
for (int i = endIndex; i < seatForUpdate.getSell().length(); i++) {
char aChar = chars[i];
if (aChar == '1') {
maxEndIndex = i;
break;
}
}
LOG.info("影响到达站区间:" + minEndIndex + "-" + maxEndIndex);
dailyTrainTicketMapperCust.updateCountBySell(
dailyTrainSeat.getDate(),
dailyTrainSeat.getTrainCode(),
dailyTrainSeat.getSeatType(),
minStartIndex,
maxStartIndex,
minEndIndex,
maxEndIndex);
// 调用会员服务接口,为会员增加一张车票
MemberTicketReq memberTicketReq = new MemberTicketReq();
memberTicketReq.setMemberId(confirmOrder.getMemberId());
memberTicketReq.setPassengerId(tickets.get(j).getPassengerId());
memberTicketReq.setPassengerName(tickets.get(j).getPassengerName());
memberTicketReq.setTrainDate(dailyTrainTicket.getDate());
memberTicketReq.setTrainCode(dailyTrainTicket.getTrainCode());
memberTicketReq.setCarriageIndex(dailyTrainSeat.getCarriageIndex());
memberTicketReq.setSeatRow(dailyTrainSeat.getRow());
memberTicketReq.setSeatCol(dailyTrainSeat.getCol());
memberTicketReq.setStartStation(dailyTrainTicket.getStart());
memberTicketReq.setStartTime(dailyTrainTicket.getStartTime());
memberTicketReq.setEndStation(dailyTrainTicket.getEnd());
memberTicketReq.setEndTime(dailyTrainTicket.getEndTime());
memberTicketReq.setSeatType(dailyTrainSeat.getSeatType());
CommonResp<Object> commonResp = memberFeign.save(memberTicketReq);
LOG.info("调用member接口,返回:{}", commonResp);
// 更新订单状态为成功
ConfirmOrder confirmOrderForUpdate = new ConfirmOrder();
confirmOrderForUpdate.setId(confirmOrder.getId());
confirmOrderForUpdate.setUpdateTime(new Date());
confirmOrderForUpdate.setStatus(ConfirmOrderStatusEnum.SUCCESS.getCode());
confirmOrderMapper.updateByPrimaryKeySelective(confirmOrderForUpdate);
// 模拟调用方出现异常
// Thread.sleep(10000);
// if (1 == 1) {
// throw new Exception("测试异常");
// }
}
}
}
BeforeConfirmOrderService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.jiawa.train.business.service;
import cn.hutool.core.date.DateTime;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.jiawa.train.business.domain.ConfirmOrder;
import com.jiawa.train.business.dto.ConfirmOrderMQDto;
import com.jiawa.train.business.enums.ConfirmOrderStatusEnum;
import com.jiawa.train.business.mapper.ConfirmOrderMapper;
import com.jiawa.train.business.req.ConfirmOrderDoReq;
import com.jiawa.train.business.req.ConfirmOrderTicketReq;
import com.jiawa.train.common.context.LoginMemberContext;
import com.jiawa.train.common.exception.BusinessException;
import com.jiawa.train.common.exception.BusinessExceptionEnum;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
@Service
public class BeforeConfirmOrderService {
private static final Logger LOG = LoggerFactory.getLogger(BeforeConfirmOrderService.class);
@Resource
private ConfirmOrderMapper confirmOrderMapper;
@Autowired
private SkTokenService skTokenService;
// @Resource
// public RocketMQTemplate rocket
// public RocketMQTemplate rocketMQTemplate;
@Resource
private ConfirmOrderService confirmOrderService;
@SentinelResource(value = "beforeDoConfirm", blockHandler = "beforeDoConfirmBlock")
public Long beforeDoConfirm(ConfirmOrderDoReq req) {
Long id = null;
// 根据前端传值,加入排队人数
for (int i = 0; i < req.getLineNumber() + 1; i++) {
req.setMemberId(LoginMemberContext.getId());
// 校验令牌余量
boolean validSkToken = skTokenService.validSkToken(req.getDate(), req.getTrainCode(), LoginMemberContext.getId());
if (validSkToken) {
LOG.info("令牌校验通过");
} else {
LOG.info("令牌校验不通过");
throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_SK_TOKEN_FAIL);
}
Date date = req.getDate();
String trainCode = req.getTrainCode();
String start = req.getStart();
String end = req.getEnd();
List<ConfirmOrderTicketReq> tickets = req.getTickets();
// 保存确认订单表,状态初始
DateTime now = DateTime.now();
ConfirmOrder confirmOrder = new ConfirmOrder();
confirmOrder.setId(SnowUtil.getSnowflakeNextId());
confirmOrder.setCreateTime(now);
confirmOrder.setUpdateTime(now);
confirmOrder.setMemberId(req.getMemberId());
confirmOrder.setDate(date);
confirmOrder.setTrainCode(trainCode);
confirmOrder.setStart(start);
confirmOrder.setEnd(end);
confirmOrder.setDailyTrainTicketId(req.getDailyTrainTicketId());
confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode());
confirmOrder.setTickets(JSON.toJSONString(tickets));
confirmOrderMapper.insert(confirmOrder);
// 发送MQ排队购票
ConfirmOrderMQDto confirmOrderMQDto = new ConfirmOrderMQDto();
confirmOrderMQDto.setDate(req.getDate());
confirmOrderMQDto.setTrainCode(req.getTrainCode());
confirmOrderMQDto.setLogId(MDC.get("LOG_ID"));
String reqJson = JSON.toJSONString(confirmOrderMQDto);
// LOG.info("排队购票,发送mq开始,消息:{}", reqJson);
// rocketMQTemplate.convertAndSend(RocketMQTopicEnum.CONFIRM_ORDER.getCode(), reqJson);
// LOG.info("排队购票,发送mq结束");
confirmOrderService.doConfirm(confirmOrderMQDto);
id = confirmOrder.getId();
}
return id;
}
/**
* 降级方法,需包含限流方法的所有参数和BlockException参数
* @param req
* @param e
*/
public void beforeDoConfirmBlock(ConfirmOrderDoReq req, BlockException e) {
LOG.info("购票请求被限流:{}", req);
throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
}
}
ConfirmOrderService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.*;
import com.jiawa.train.business.dto.ConfirmOrderMQDto;
import com.jiawa.train.business.enums.ConfirmOrderStatusEnum;
import com.jiawa.train.business.enums.RedisKeyPreEnum;
import com.jiawa.train.business.enums.SeatColEnum;
import com.jiawa.train.business.enums.SeatTypeEnum;
import com.jiawa.train.business.mapper.ConfirmOrderMapper;
import com.jiawa.train.business.req.ConfirmOrderDoReq;
import com.jiawa.train.business.req.ConfirmOrderQueryReq;
import com.jiawa.train.business.req.ConfirmOrderTicketReq;
import com.jiawa.train.business.resp.ConfirmOrderQueryResp;
import com.jiawa.train.common.exception.BusinessException;
import com.jiawa.train.common.exception.BusinessExceptionEnum;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Service
public class ConfirmOrderService {
private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);
@Resource
private ConfirmOrderMapper confirmOrderMapper;
@Resource
private DailyTrainTicketService dailyTrainTicketService;
@Resource
private DailyTrainCarriageService dailyTrainCarriageService;
@Resource
private DailyTrainSeatService dailyTrainSeatService;
@Resource
private AfterConfirmOrderService afterConfirmOrderService;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private SkTokenService skTokenService;
// @Autowired
// private RedissonClient redissonClient;
public void save(ConfirmOrderDoReq req) {
DateTime now = DateTime.now();
ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
if (ObjectUtil.isNull(confirmOrder.getId())) {
confirmOrder.setId(SnowUtil.getSnowflakeNextId());
confirmOrder.setCreateTime(now);
confirmOrder.setUpdateTime(now);
confirmOrderMapper.insert(confirmOrder);
} else {
confirmOrder.setUpdateTime(now);
confirmOrderMapper.updateByPrimaryKey(confirmOrder);
}
}
public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
confirmOrderExample.setOrderByClause("id desc");
ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);
PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);
PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
confirmOrderMapper.deleteByPrimaryKey(id);
}
@Async
@SentinelResource(value = "doConfirm", blockHandler = "doConfirmBlock")
public void doConfirm(ConfirmOrderMQDto dto) {
MDC.put("LOG_ID", dto.getLogId());
LOG.info("异步出票开始:{}", dto);
// // 校验令牌余量
// boolean validSkToken = skTokenService.validSkToken(dto.getDate(), dto.getTrainCode(), LoginMemberContext.getId());
// if (validSkToken) {
// LOG.info("令牌校验通过");
// } else {
// LOG.info("令牌校验不通过");
// throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_SK_TOKEN_FAIL);
// }
//
// 获取分布式锁
String lockKey = RedisKeyPreEnum.CONFIRM_ORDER + "-" + DateUtil.formatDate(dto.getDate()) + "-" + dto.getTrainCode();
// setIfAbsent就是对应redis的setnx
Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(setIfAbsent)) {
LOG.info("恭喜,抢到锁了!lockKey:{}", lockKey);
} else {
// 只是没抢到锁,并不知道票抢完了没,所以提示稍候再试
// LOG.info("很遗憾,没抢到锁!lockKey:{}", lockKey);
// throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);
LOG.info("没抢到锁,有其它消费线程正在出票,不做任何处理");
return;
}
// RLock lock = null;
/*
关于红锁,看16.7节:
A B C D E
1: A B C D E
2: C D E
3: C
*/
try {
// // 使用redisson,自带看门狗
// lock = redissonClient.getLock(lockKey);
//
// // 红锁的写法
// // RedissonRedLock redissonRedLock = new RedissonRedLock(lock, lock, lock);
// // boolean tryLock1 = redissonRedLock.tryLock(0, TimeUnit.SECONDS);
//
// /**
// waitTime – the maximum time to acquire the lock 等待获取锁时间(最大尝试获得锁的时间),超时返回false
// leaseTime – lease time 锁时长,即n秒后自动释放锁
// time unit – time unit 时间单位
// */
// // boolean tryLock = lock.tryLock(30, 10, TimeUnit.SECONDS); // 不带看门狗
// boolean tryLock = lock.tryLock(0, TimeUnit.SECONDS); // 带看门狗
// if (tryLock) {
// LOG.info("恭喜,抢到锁了!");
// // 可以把下面这段放开,只用一个线程来测试,看看redisson的看门狗效果
// // for (int i = 0; i < 30; i++) {
// // Long expire = redisTemplate.opsForValue().getOperations().getExpire(lockKey);
// // LOG.info("锁过期时间还有:{}", expire);
// // Thread.sleep(1000);
// // }
// } else {
// // 只是没抢到锁,并不知道票抢完了没,所以提示稍候再试
// LOG.info("很遗憾,没抢到锁");
// throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);
// }
while (true) {
// 取确认订单表的记录,同日期车次,状态是I,分页处理,每次取N条
ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
confirmOrderExample.setOrderByClause("id asc");
ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
criteria.andDateEqualTo(dto.getDate())
.andTrainCodeEqualTo(dto.getTrainCode())
.andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
PageHelper.startPage(1, 5);
List<ConfirmOrder> list = confirmOrderMapper.selectByExampleWithBLOBs(confirmOrderExample);
if (CollUtil.isEmpty(list)) {
LOG.info("没有需要处理的订单,结束循环");
break;
} else {
LOG.info("本次处理{}条订单", list.size());
}
// 一条一条的卖
list.forEach(confirmOrder -> {
try {
sell(confirmOrder);
} catch (BusinessException e) {
if (e.getE().equals(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR)) {
LOG.info("本订单余票不足,继续售卖下一个订单");
confirmOrder.setStatus(ConfirmOrderStatusEnum.EMPTY.getCode());
updateStatus(confirmOrder);
} else {
throw e;
}
}
});
}
// LOG.info("购票流程结束,释放锁!lockKey:{}", lockKey);
// redisTemplate.delete(lockKey);
// } catch (InterruptedException e) {
// LOG.error("购票异常", e);
} finally {
// try finally不能包含加锁的那段代码,否则加锁失败会走到finally里,从而释放别的线程的锁
LOG.info("购票流程结束,释放锁!lockKey:{}", lockKey);
redisTemplate.delete(lockKey);
// LOG.info("购票流程结束,释放锁!");
// if (null != lock && lock.isHeldByCurrentThread()) {
// lock.unlock();
// }
}
}
/**
* 更新状态
* @param confirmOrder
*/
public void updateStatus(ConfirmOrder confirmOrder) {
ConfirmOrder confirmOrderForUpdate = new ConfirmOrder();
confirmOrderForUpdate.setId(confirmOrder.getId());
confirmOrderForUpdate.setUpdateTime(new Date());
confirmOrderForUpdate.setStatus(confirmOrder.getStatus());
confirmOrderMapper.updateByPrimaryKeySelective(confirmOrderForUpdate);
}
/**
* 售票
* @param confirmOrder
*/
private void sell(ConfirmOrder confirmOrder) {
// 为了演示排队效果,每次出票增加200毫秒延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 构造ConfirmOrderDoReq
ConfirmOrderDoReq req = new ConfirmOrderDoReq();
req.setMemberId(confirmOrder.getMemberId());
req.setDate(confirmOrder.getDate());
req.setTrainCode(confirmOrder.getTrainCode());
req.setStart(confirmOrder.getStart());
req.setEnd(confirmOrder.getEnd());
req.setDailyTrainTicketId(confirmOrder.getDailyTrainTicketId());
req.setTickets(JSON.parseArray(confirmOrder.getTickets(), ConfirmOrderTicketReq.class));
req.setImageCode("");
req.setImageCodeToken("");
req.setLogId("");
// 省略业务数据校验,如:车次是否存在,余票是否存在,车次是否在有效期内,tickets条数>0,同乘客同车次是否已买过
// 将订单设置成处理中,避免重复处理
LOG.info("将确认订单更新成处理中,避免重复处理,confirm_order.id: {}", confirmOrder.getId());
confirmOrder.setStatus(ConfirmOrderStatusEnum.PENDING.getCode());
updateStatus(confirmOrder);
Date date = req.getDate();
String trainCode = req.getTrainCode();
String start = req.getStart();
String end = req.getEnd();
List<ConfirmOrderTicketReq> tickets = req.getTickets();
//
// // 保存确认订单表,状态初始
// DateTime now = DateTime.now();
// ConfirmOrder confirmOrder = new ConfirmOrder();
// confirmOrder.setId(SnowUtil.getSnowflakeNextId());
// confirmOrder.setCreateTime(now);
// confirmOrder.setUpdateTime(now);
// confirmOrder.setMemberId(req.getMemberId());
// confirmOrder.setDate(date);
// confirmOrder.setTrainCode(trainCode);
// confirmOrder.setStart(start);
// confirmOrder.setEnd(end);
// confirmOrder.setDailyTrainTicketId(req.getDailyTrainTicketId());
// confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode());
// confirmOrder.setTickets(JSON.toJSONString(tickets));
// confirmOrderMapper.insert(confirmOrder);
// // 从数据库里查出订单
// ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
// confirmOrderExample.setOrderByClause("id asc");
// ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
// criteria.andDateEqualTo(req.getDate())
// .andTrainCodeEqualTo(req.getTrainCode())
// .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
// List<ConfirmOrder> list = confirmOrderMapper.selectByExampleWithBLOBs(confirmOrderExample);
// ConfirmOrder confirmOrder;
// if (CollUtil.isEmpty(list)) {
// LOG.info("找不到原始订单,结束");
// return;
// } else {
// LOG.info("本次处理{}条确认订单", list.size());
// confirmOrder = list.get(0);
// }
// 查出余票记录,需要得到真实的库存
DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);
LOG.info("查出余票记录:{}", dailyTrainTicket);
// 预扣减余票数量,并判断余票是否足够
reduceTickets(req, dailyTrainTicket);
// 最终的选座结果
List<DailyTrainSeat> finalSeatList = new ArrayList<>();
// 计算相对第一个座位的偏移值
// 比如选择的是C1,D2,则偏移值是:[0,5]
// 比如选择的是A1,B1,C1,则偏移值是:[0,1,2]
ConfirmOrderTicketReq ticketReq0 = tickets.get(0);
if (StrUtil.isNotBlank(ticketReq0.getSeat())) {
LOG.info("本次购票有选座");
// 查出本次选座的座位类型都有哪些列,用于计算所选座位与第一个座位的偏离值
List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(ticketReq0.getSeatTypeCode());
LOG.info("本次选座的座位类型包含的列:{}", colEnumList);
// 组成和前端两排选座一样的列表,用于作参照的座位列表,例:referSeatList = {A1, C1, D1, F1, A2, C2, D2, F2}
List<String> referSeatList = new ArrayList<>();
for (int i = 1; i <= 2; i++) {
for (SeatColEnum seatColEnum : colEnumList) {
referSeatList.add(seatColEnum.getCode() + i);
}
}
LOG.info("用于作参照的两排座位:{}", referSeatList);
List<Integer> offsetList = new ArrayList<>();
// 绝对偏移值,即:在参照座位列表中的位置
List<Integer> aboluteOffsetList = new ArrayList<>();
for (ConfirmOrderTicketReq ticketReq : tickets) {
int index = referSeatList.indexOf(ticketReq.getSeat());
aboluteOffsetList.add(index);
}
LOG.info("计算得到所有座位的绝对偏移值:{}", aboluteOffsetList);
for (Integer index : aboluteOffsetList) {
int offset = index - aboluteOffsetList.get(0);
offsetList.add(offset);
}
LOG.info("计算得到所有座位的相对第一个座位的偏移值:{}", offsetList);
getSeat(finalSeatList,
date,
trainCode,
ticketReq0.getSeatTypeCode(),
ticketReq0.getSeat().split("")[0], // 从A1得到A
offsetList,
dailyTrainTicket.getStartIndex(),
dailyTrainTicket.getEndIndex()
);
} else {
LOG.info("本次购票没有选座");
for (ConfirmOrderTicketReq ticketReq : tickets) {
getSeat(finalSeatList,
date,
trainCode,
ticketReq.getSeatTypeCode(),
null,
null,
dailyTrainTicket.getStartIndex(),
dailyTrainTicket.getEndIndex()
);
}
}
LOG.info("最终选座:{}", finalSeatList);
// 选中座位后事务处理:
// 座位表修改售卖情况sell;
// 余票详情表修改余票;
// 为会员增加购票记录
// 更新确认订单为成功
try {
afterConfirmOrderService.afterDoConfirm(dailyTrainTicket, finalSeatList, tickets, confirmOrder);
} catch (Exception e) {
LOG.error("保存购票信息失败", e);
throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_EXCEPTION);
}
}
/**
* 挑座位,如果有选座,则一次性挑完,如果无选座,则一个一个挑
* @param date
* @param trainCode
* @param seatType
* @param column
* @param offsetList
*/
private void getSeat(List<DailyTrainSeat> finalSeatList, Date date, String trainCode, String seatType, String column, List<Integer> offsetList, Integer startIndex, Integer endIndex) {
List<DailyTrainSeat> getSeatList = new ArrayList<>();
List<DailyTrainCarriage> carriageList = dailyTrainCarriageService.selectBySeatType(date, trainCode, seatType);
LOG.info("共查出{}个符合条件的车厢", carriageList.size());
// 一个车箱一个车箱的获取座位数据
for (DailyTrainCarriage dailyTrainCarriage : carriageList) {
LOG.info("开始从车厢{}选座", dailyTrainCarriage.getIndex());
getSeatList = new ArrayList<>();
List<DailyTrainSeat> seatList = dailyTrainSeatService.selectByCarriage(date, trainCode, dailyTrainCarriage.getIndex());
LOG.info("车厢{}的座位数:{}", dailyTrainCarriage.getIndex(), seatList.size());
for (int i = 0; i < seatList.size(); i++) {
DailyTrainSeat dailyTrainSeat = seatList.get(i);
Integer seatIndex = dailyTrainSeat.getCarriageSeatIndex();
String col = dailyTrainSeat.getCol();
// 判断当前座位不能被选中过
boolean alreadyChooseFlag = false;
for (DailyTrainSeat finalSeat : finalSeatList){
if (finalSeat.getId().equals(dailyTrainSeat.getId())) {
alreadyChooseFlag = true;
break;
}
}
if (alreadyChooseFlag) {
LOG.info("座位{}被选中过,不能重复选中,继续判断下一个座位", seatIndex);
continue;
}
// 判断column,有值的话要比对列号
if (StrUtil.isBlank(column)) {
LOG.info("无选座");
} else {
if (!column.equals(col)) {
LOG.info("座位{}列值不对,继续判断下一个座位,当前列值:{},目标列值:{}", seatIndex, col, column);
continue;
}
}
boolean isChoose = calSell(dailyTrainSeat, startIndex, endIndex);
if (isChoose) {
LOG.info("选中座位");
getSeatList.add(dailyTrainSeat);
} else {
continue;
}
// 根据offset选剩下的座位
boolean isGetAllOffsetSeat = true;
if (CollUtil.isNotEmpty(offsetList)) {
LOG.info("有偏移值:{},校验偏移的座位是否可选", offsetList);
// 从索引1开始,索引0就是当前已选中的票
for (int j = 1; j < offsetList.size(); j++) {
Integer offset = offsetList.get(j);
// 座位在库的索引是从1开始
// int nextIndex = seatIndex + offset - 1;
int nextIndex = i + offset;
// 有选座时,一定是在同一个车箱
if (nextIndex >= seatList.size()) {
LOG.info("座位{}不可选,偏移后的索引超出了这个车箱的座位数", nextIndex);
isGetAllOffsetSeat = false;
break;
}
DailyTrainSeat nextDailyTrainSeat = seatList.get(nextIndex);
boolean isChooseNext = calSell(nextDailyTrainSeat, startIndex, endIndex);
if (isChooseNext) {
LOG.info("座位{}被选中", nextDailyTrainSeat.getCarriageSeatIndex());
getSeatList.add(nextDailyTrainSeat);
} else {
LOG.info("座位{}不可选", nextDailyTrainSeat.getCarriageSeatIndex());
isGetAllOffsetSeat = false;
break;
}
}
}
if (!isGetAllOffsetSeat) {
getSeatList = new ArrayList<>();
continue;
}
// 保存选好的座位
finalSeatList.addAll(getSeatList);
return;
}
}
}
/**
* 计算某座位在区间内是否可卖
* 例:sell=10001,本次购买区间站1~4,则区间已售000
* 全部是0,表示这个区间可买;只要有1,就表示区间内已售过票
*
* 选中后,要计算购票后的sell,比如原来是10001,本次购买区间站1~4
* 方案:构造本次购票造成的售卖信息01110,和原sell 10001按位与,最终得到11111
*/
private boolean calSell(DailyTrainSeat dailyTrainSeat, Integer startIndex, Integer endIndex) {
// 00001, 00000
String sell = dailyTrainSeat.getSell();
// 000, 000
String sellPart = sell.substring(startIndex, endIndex);
if (Integer.parseInt(sellPart) > 0) {
LOG.info("座位{}在本次车站区间{}~{}已售过票,不可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
return false;
} else {
LOG.info("座位{}在本次车站区间{}~{}未售过票,可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
// 111, 111
String curSell = sellPart.replace('0', '1');
// 0111, 0111
curSell = StrUtil.fillBefore(curSell, '0', endIndex);
// 01110, 01110
curSell = StrUtil.fillAfter(curSell, '0', sell.length());
// 当前区间售票信息curSell 01110与库里的已售信息sell 00001按位与,即可得到该座位卖出此票后的售票详情
// 15(01111), 14(01110 = 01110|00000)
int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
// 1111, 1110
String newSell = NumberUtil.getBinaryStr(newSellInt);
// 01111, 01110
newSell = StrUtil.fillBefore(newSell, '0', sell.length());
LOG.info("座位{}被选中,原售票信息:{},车站区间:{}~{},即:{},最终售票信息:{}"
, dailyTrainSeat.getCarriageSeatIndex(), sell, startIndex, endIndex, curSell, newSell);
dailyTrainSeat.setSell(newSell);
return true;
}
}
private static void reduceTickets(ConfirmOrderDoReq req, DailyTrainTicket dailyTrainTicket) {
for (ConfirmOrderTicketReq ticketReq : req.getTickets()) {
String seatTypeCode = ticketReq.getSeatTypeCode();
SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
switch (seatTypeEnum) {
case YDZ -> {
int countLeft = dailyTrainTicket.getYdz() - 1;
if (countLeft < 0) {
throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
}
dailyTrainTicket.setYdz(countLeft);
}
case EDZ -> {
int countLeft = dailyTrainTicket.getEdz() - 1;
if (countLeft < 0) {
throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
}
dailyTrainTicket.setEdz(countLeft);
}
case RW -> {
int countLeft = dailyTrainTicket.getRw() - 1;
if (countLeft < 0) {
throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
}
dailyTrainTicket.setRw(countLeft);
}
case YW -> {
int countLeft = dailyTrainTicket.getYw() - 1;
if (countLeft < 0) {
throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
}
dailyTrainTicket.setYw(countLeft);
}
}
}
}
/**
* 降级方法,需包含限流方法的所有参数和BlockException参数
* @param req
* @param e
*/
public void doConfirmBlock(ConfirmOrderDoReq req, BlockException e) {
LOG.info("购票请求被限流:{}", req);
throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
}
/**
* 查询前面有几个人在排队
* @param id
*/
public Integer queryLineCount(Long id) {
ConfirmOrder confirmOrder = confirmOrderMapper.selectByPrimaryKey(id);
ConfirmOrderStatusEnum statusEnum = EnumUtil.getBy(ConfirmOrderStatusEnum::getCode, confirmOrder.getStatus());
int result = switch (statusEnum) {
case PENDING -> 0; // 排队0
case SUCCESS -> -1; // 成功
case FAILURE -> -2; // 失败
case EMPTY -> -3; // 无票
case CANCEL -> -4; // 取消
case INIT -> 999; // 需要查表得到实际排队数量
};
if (result == 999) {
// 排在第几位,下面的写法:where a=1 and (b=1 or c=1) 等价于 where (a=1 and b=1) or (a=1 and c=1)
ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
confirmOrderExample.or().andDateEqualTo(confirmOrder.getDate())
.andTrainCodeEqualTo(confirmOrder.getTrainCode())
.andCreateTimeLessThan(confirmOrder.getCreateTime())
.andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
confirmOrderExample.or().andDateEqualTo(confirmOrder.getDate())
.andTrainCodeEqualTo(confirmOrder.getTrainCode())
.andCreateTimeLessThan(confirmOrder.getCreateTime())
.andStatusEqualTo(ConfirmOrderStatusEnum.PENDING.getCode());
return Math.toIntExact(confirmOrderMapper.countByExample(confirmOrderExample));
} else {
return result;
}
}
/**
* 取消排队,只有I状态才能取消排队,所以按状态更新
* @param id
*/
public Integer cancel(Long id) {
ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
criteria.andIdEqualTo(id).andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
ConfirmOrder confirmOrder = new ConfirmOrder();
confirmOrder.setStatus(ConfirmOrderStatusEnum.CANCEL.getCode());
return confirmOrderMapper.updateByExampleSelective(confirmOrder, confirmOrderExample);
}
}
DailyTrainCarriageService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.*;
import com.jiawa.train.business.enums.SeatColEnum;
import com.jiawa.train.business.mapper.DailyTrainCarriageMapper;
import com.jiawa.train.business.req.DailyTrainCarriageQueryReq;
import com.jiawa.train.business.req.DailyTrainCarriageSaveReq;
import com.jiawa.train.business.resp.DailyTrainCarriageQueryResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
public class DailyTrainCarriageService {
private static final Logger LOG = LoggerFactory.getLogger(DailyTrainCarriageService.class);
@Resource
private DailyTrainCarriageMapper dailyTrainCarriageMapper;
@Resource
private TrainCarriageService trainCarriageService;
public void save(DailyTrainCarriageSaveReq req) {
DateTime now = DateTime.now();
// 自动计算出列数和总座位数
List<SeatColEnum> seatColEnums = SeatColEnum.getColsByType(req.getSeatType());
req.setColCount(seatColEnums.size());
req.setSeatCount(req.getColCount() * req.getRowCount());
DailyTrainCarriage dailyTrainCarriage = BeanUtil.copyProperties(req, DailyTrainCarriage.class);
if (ObjectUtil.isNull(dailyTrainCarriage.getId())) {
dailyTrainCarriage.setId(SnowUtil.getSnowflakeNextId());
dailyTrainCarriage.setCreateTime(now);
dailyTrainCarriage.setUpdateTime(now);
dailyTrainCarriageMapper.insert(dailyTrainCarriage);
} else {
dailyTrainCarriage.setUpdateTime(now);
dailyTrainCarriageMapper.updateByPrimaryKey(dailyTrainCarriage);
}
}
public PageResp<DailyTrainCarriageQueryResp> queryList(DailyTrainCarriageQueryReq req) {
DailyTrainCarriageExample dailyTrainCarriageExample = new DailyTrainCarriageExample();
dailyTrainCarriageExample.setOrderByClause("date desc, train_code asc, `index` asc");
DailyTrainCarriageExample.Criteria criteria = dailyTrainCarriageExample.createCriteria();
if (ObjUtil.isNotNull(req.getDate())) {
criteria.andDateEqualTo(req.getDate());
}
if (ObjUtil.isNotEmpty(req.getTrainCode())) {
criteria.andTrainCodeEqualTo(req.getTrainCode());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<DailyTrainCarriage> dailyTrainCarriageList = dailyTrainCarriageMapper.selectByExample(dailyTrainCarriageExample);
PageInfo<DailyTrainCarriage> pageInfo = new PageInfo<>(dailyTrainCarriageList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<DailyTrainCarriageQueryResp> list = BeanUtil.copyToList(dailyTrainCarriageList, DailyTrainCarriageQueryResp.class);
PageResp<DailyTrainCarriageQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
dailyTrainCarriageMapper.deleteByPrimaryKey(id);
}
@Transactional
public void genDaily(Date date, String trainCode) {
LOG.info("生成日期【{}】车次【{}】的车厢信息开始", DateUtil.formatDate(date), trainCode);
// 删除某日某车次的车厢信息
DailyTrainCarriageExample dailyTrainCarriageExample = new DailyTrainCarriageExample();
dailyTrainCarriageExample.createCriteria()
.andDateEqualTo(date)
.andTrainCodeEqualTo(trainCode);
dailyTrainCarriageMapper.deleteByExample(dailyTrainCarriageExample);
// 查出某车次的所有的车厢信息
List<TrainCarriage> carriageList = trainCarriageService.selectByTrainCode(trainCode);
if (CollUtil.isEmpty(carriageList)) {
LOG.info("该车次没有车厢基础数据,生成该车次的车厢信息结束");
return;
}
for (TrainCarriage trainCarriage : carriageList) {
DateTime now = DateTime.now();
DailyTrainCarriage dailyTrainCarriage = BeanUtil.copyProperties(trainCarriage, DailyTrainCarriage.class);
dailyTrainCarriage.setId(SnowUtil.getSnowflakeNextId());
dailyTrainCarriage.setCreateTime(now);
dailyTrainCarriage.setUpdateTime(now);
dailyTrainCarriage.setDate(date);
dailyTrainCarriageMapper.insert(dailyTrainCarriage);
}
LOG.info("生成日期【{}】车次【{}】的车厢信息结束", DateUtil.formatDate(date), trainCode);
}
public List<DailyTrainCarriage> selectBySeatType (Date date, String trainCode, String seatType) {
DailyTrainCarriageExample example = new DailyTrainCarriageExample();
example.createCriteria()
.andDateEqualTo(date)
.andTrainCodeEqualTo(trainCode)
.andSeatTypeEqualTo(seatType);
return dailyTrainCarriageMapper.selectByExample(example);
}
}
DailyTrainSeatService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.*;
import com.jiawa.train.business.req.SeatSellReq;
import com.jiawa.train.business.resp.SeatSellResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import com.jiawa.train.business.mapper.DailyTrainSeatMapper;
import com.jiawa.train.business.req.DailyTrainSeatQueryReq;
import com.jiawa.train.business.req.DailyTrainSeatSaveReq;
import com.jiawa.train.business.resp.DailyTrainSeatQueryResp;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
public class DailyTrainSeatService {
private static final Logger LOG = LoggerFactory.getLogger(DailyTrainSeatService.class);
@Resource
private DailyTrainSeatMapper dailyTrainSeatMapper;
@Resource
private TrainSeatService trainSeatService;
@Resource
private TrainStationService trainStationService;
public void save(DailyTrainSeatSaveReq req) {
DateTime now = DateTime.now();
DailyTrainSeat dailyTrainSeat = BeanUtil.copyProperties(req, DailyTrainSeat.class);
if (ObjectUtil.isNull(dailyTrainSeat.getId())) {
dailyTrainSeat.setId(SnowUtil.getSnowflakeNextId());
dailyTrainSeat.setCreateTime(now);
dailyTrainSeat.setUpdateTime(now);
dailyTrainSeatMapper.insert(dailyTrainSeat);
} else {
dailyTrainSeat.setUpdateTime(now);
dailyTrainSeatMapper.updateByPrimaryKey(dailyTrainSeat);
}
}
public PageResp<DailyTrainSeatQueryResp> queryList(DailyTrainSeatQueryReq req) {
DailyTrainSeatExample dailyTrainSeatExample = new DailyTrainSeatExample();
dailyTrainSeatExample.setOrderByClause("date desc, train_code asc, carriage_index asc, carriage_seat_index asc");
DailyTrainSeatExample.Criteria criteria = dailyTrainSeatExample.createCriteria();
if (ObjectUtil.isNotEmpty(req.getTrainCode())) {
criteria.andTrainCodeEqualTo(req.getTrainCode());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<DailyTrainSeat> dailyTrainSeatList = dailyTrainSeatMapper.selectByExample(dailyTrainSeatExample);
PageInfo<DailyTrainSeat> pageInfo = new PageInfo<>(dailyTrainSeatList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<DailyTrainSeatQueryResp> list = BeanUtil.copyToList(dailyTrainSeatList, DailyTrainSeatQueryResp.class);
PageResp<DailyTrainSeatQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
dailyTrainSeatMapper.deleteByPrimaryKey(id);
}
@Transactional
public void genDaily(Date date, String trainCode) {
LOG.info("生成日期【{}】车次【{}】的座位信息开始", DateUtil.formatDate(date), trainCode);
// 删除某日某车次的座位信息
DailyTrainSeatExample dailyTrainSeatExample = new DailyTrainSeatExample();
dailyTrainSeatExample.createCriteria()
.andDateEqualTo(date)
.andTrainCodeEqualTo(trainCode);
dailyTrainSeatMapper.deleteByExample(dailyTrainSeatExample);
List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode);
String sell = StrUtil.fillBefore("", '0', stationList.size() - 1);
// 查出某车次的所有的座位信息
List<TrainSeat> seatList = trainSeatService.selectByTrainCode(trainCode);
if (CollUtil.isEmpty(seatList)) {
LOG.info("该车次没有座位基础数据,生成该车次的座位信息结束");
return;
}
for (TrainSeat trainSeat : seatList) {
DateTime now = DateTime.now();
DailyTrainSeat dailyTrainSeat = BeanUtil.copyProperties(trainSeat, DailyTrainSeat.class);
dailyTrainSeat.setId(SnowUtil.getSnowflakeNextId());
dailyTrainSeat.setCreateTime(now);
dailyTrainSeat.setUpdateTime(now);
dailyTrainSeat.setDate(date);
dailyTrainSeat.setSell(sell);
dailyTrainSeatMapper.insert(dailyTrainSeat);
}
LOG.info("生成日期【{}】车次【{}】的座位信息结束", DateUtil.formatDate(date), trainCode);
}
public int countSeat(Date date, String trainCode) {
return countSeat(date, trainCode, null);
}
public int countSeat(Date date, String trainCode, String seatType) {
DailyTrainSeatExample example = new DailyTrainSeatExample();
DailyTrainSeatExample.Criteria criteria = example.createCriteria();
criteria.andDateEqualTo(date)
.andTrainCodeEqualTo(trainCode);
if (StrUtil.isNotBlank(seatType)) {
criteria.andSeatTypeEqualTo(seatType);
}
long l = dailyTrainSeatMapper.countByExample(example);
if (l == 0L) {
return -1;
}
return (int) l;
}
public List<DailyTrainSeat> selectByCarriage(Date date, String trainCode, Integer carriageIndex) {
DailyTrainSeatExample example = new DailyTrainSeatExample();
example.setOrderByClause("carriage_seat_index asc");
example.createCriteria()
.andDateEqualTo(date)
.andTrainCodeEqualTo(trainCode)
.andCarriageIndexEqualTo(carriageIndex);
return dailyTrainSeatMapper.selectByExample(example);
}
/**
* 查询某日某车次的所有座位
*/
public List<SeatSellResp> querySeatSell(SeatSellReq req) {
Date date = req.getDate();
String trainCode = req.getTrainCode();
LOG.info("查询日期【{}】车次【{}】的座位销售信息", DateUtil.formatDate(date), trainCode);
DailyTrainSeatExample dailyTrainSeatExample = new DailyTrainSeatExample();
dailyTrainSeatExample.setOrderByClause("`carriage_index` asc, carriage_seat_index asc");
dailyTrainSeatExample.createCriteria()
.andDateEqualTo(date)
.andTrainCodeEqualTo(trainCode);
return BeanUtil.copyToList(dailyTrainSeatMapper.selectByExample(dailyTrainSeatExample), SeatSellResp.class);
}
}
DailyTrainService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.DailyTrain;
import com.jiawa.train.business.domain.DailyTrainExample;
import com.jiawa.train.business.domain.Train;
import com.jiawa.train.business.mapper.DailyTrainMapper;
import com.jiawa.train.business.req.DailyTrainQueryReq;
import com.jiawa.train.business.req.DailyTrainSaveReq;
import com.jiawa.train.business.resp.DailyTrainQueryResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
public class DailyTrainService {
private static final Logger LOG = LoggerFactory.getLogger(DailyTrainService.class);
@Resource
private DailyTrainMapper dailyTrainMapper;
@Resource
private TrainService trainService;
@Resource
private DailyTrainStationService dailyTrainStationService;
@Resource
private DailyTrainCarriageService dailyTrainCarriageService;
@Resource
private DailyTrainSeatService dailyTrainSeatService;
@Resource
private DailyTrainTicketService dailyTrainTicketService;
@Resource
private SkTokenService skTokenService;
public void save(DailyTrainSaveReq req) {
DateTime now = DateTime.now();
DailyTrain dailyTrain = BeanUtil.copyProperties(req, DailyTrain.class);
if (ObjectUtil.isNull(dailyTrain.getId())) {
dailyTrain.setId(SnowUtil.getSnowflakeNextId());
dailyTrain.setCreateTime(now);
dailyTrain.setUpdateTime(now);
dailyTrainMapper.insert(dailyTrain);
} else {
dailyTrain.setUpdateTime(now);
dailyTrainMapper.updateByPrimaryKey(dailyTrain);
}
}
public PageResp<DailyTrainQueryResp> queryList(DailyTrainQueryReq req) {
DailyTrainExample dailyTrainExample = new DailyTrainExample();
dailyTrainExample.setOrderByClause("date desc, code asc");
DailyTrainExample.Criteria criteria = dailyTrainExample.createCriteria();
if (ObjectUtil.isNotNull(req.getDate())) {
criteria.andDateEqualTo(req.getDate());
}
if (ObjectUtil.isNotEmpty(req.getCode())) {
criteria.andCodeEqualTo(req.getCode());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<DailyTrain> dailyTrainList = dailyTrainMapper.selectByExample(dailyTrainExample);
PageInfo<DailyTrain> pageInfo = new PageInfo<>(dailyTrainList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<DailyTrainQueryResp> list = BeanUtil.copyToList(dailyTrainList, DailyTrainQueryResp.class);
PageResp<DailyTrainQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
dailyTrainMapper.deleteByPrimaryKey(id);
}
/**
* 生成某日所有车次信息,包括车次、车站、车厢、座位
* @param date
*/
public void genDaily(Date date) {
List<Train> trainList = trainService.selectAll();
if (CollUtil.isEmpty(trainList)) {
LOG.info("没有车次基础数据,任务结束");
return;
}
for (Train train : trainList) {
genDailyTrain(date, train);
}
}
@Transactional
public void genDailyTrain(Date date, Train train) {
LOG.info("生成日期【{}】车次【{}】的信息开始", DateUtil.formatDate(date), train.getCode());
// 删除该车次已有的数据
DailyTrainExample dailyTrainExample = new DailyTrainExample();
dailyTrainExample.createCriteria()
.andDateEqualTo(date)
.andCodeEqualTo(train.getCode());
dailyTrainMapper.deleteByExample(dailyTrainExample);
// 生成该车次的数据
DateTime now = DateTime.now();
DailyTrain dailyTrain = BeanUtil.copyProperties(train, DailyTrain.class);
dailyTrain.setId(SnowUtil.getSnowflakeNextId());
dailyTrain.setCreateTime(now);
dailyTrain.setUpdateTime(now);
dailyTrain.setDate(date);
dailyTrainMapper.insert(dailyTrain);
// 生成该车次的车站数据
dailyTrainStationService.genDaily(date, train.getCode());
// 生成该车次的车厢数据
dailyTrainCarriageService.genDaily(date, train.getCode());
// 生成该车次的座位数据
dailyTrainSeatService.genDaily(date, train.getCode());
// 生成该车次的余票数据
dailyTrainTicketService.genDaily(dailyTrain, date, train.getCode());
// 生成令牌余量数据
skTokenService.genDaily(date, train.getCode());
LOG.info("生成日期【{}】车次【{}】的信息结束", DateUtil.formatDate(date), train.getCode());
}
}
DailyTrainStationService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.DailyTrainStation;
import com.jiawa.train.business.domain.DailyTrainStationExample;
import com.jiawa.train.business.domain.TrainStation;
import com.jiawa.train.business.mapper.DailyTrainStationMapper;
import com.jiawa.train.business.req.DailyTrainStationQueryReq;
import com.jiawa.train.business.req.DailyTrainStationSaveReq;
import com.jiawa.train.business.resp.DailyTrainStationQueryResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
public class DailyTrainStationService {
private static final Logger LOG = LoggerFactory.getLogger(DailyTrainStationService.class);
@Resource
private DailyTrainStationMapper dailyTrainStationMapper;
@Resource
private TrainStationService trainStationService;
public void save(DailyTrainStationSaveReq req) {
DateTime now = DateTime.now();
DailyTrainStation dailyTrainStation = BeanUtil.copyProperties(req, DailyTrainStation.class);
if (ObjectUtil.isNull(dailyTrainStation.getId())) {
dailyTrainStation.setId(SnowUtil.getSnowflakeNextId());
dailyTrainStation.setCreateTime(now);
dailyTrainStation.setUpdateTime(now);
dailyTrainStationMapper.insert(dailyTrainStation);
} else {
dailyTrainStation.setUpdateTime(now);
dailyTrainStationMapper.updateByPrimaryKey(dailyTrainStation);
}
}
public PageResp<DailyTrainStationQueryResp> queryList(DailyTrainStationQueryReq req) {
DailyTrainStationExample dailyTrainStationExample = new DailyTrainStationExample();
dailyTrainStationExample.setOrderByClause("date desc, train_code asc, `index` asc");
DailyTrainStationExample.Criteria criteria = dailyTrainStationExample.createCriteria();
if (ObjUtil.isNotNull(req.getDate())) {
criteria.andDateEqualTo(req.getDate());
}
if (ObjUtil.isNotEmpty(req.getTrainCode())) {
criteria.andTrainCodeEqualTo(req.getTrainCode());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<DailyTrainStation> dailyTrainStationList = dailyTrainStationMapper.selectByExample(dailyTrainStationExample);
PageInfo<DailyTrainStation> pageInfo = new PageInfo<>(dailyTrainStationList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<DailyTrainStationQueryResp> list = BeanUtil.copyToList(dailyTrainStationList, DailyTrainStationQueryResp.class);
PageResp<DailyTrainStationQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
dailyTrainStationMapper.deleteByPrimaryKey(id);
}
@Transactional
public void genDaily(Date date, String trainCode) {
LOG.info("生成日期【{}】车次【{}】的车站信息开始", DateUtil.formatDate(date), trainCode);
// 删除某日某车次的车站信息
DailyTrainStationExample dailyTrainStationExample = new DailyTrainStationExample();
dailyTrainStationExample.createCriteria()
.andDateEqualTo(date)
.andTrainCodeEqualTo(trainCode);
dailyTrainStationMapper.deleteByExample(dailyTrainStationExample);
// 查出某车次的所有的车站信息
List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode);
if (CollUtil.isEmpty(stationList)) {
LOG.info("该车次没有车站基础数据,生成该车次的车站信息结束");
return;
}
for (TrainStation trainStation : stationList) {
DateTime now = DateTime.now();
DailyTrainStation dailyTrainStation = BeanUtil.copyProperties(trainStation, DailyTrainStation.class);
dailyTrainStation.setId(SnowUtil.getSnowflakeNextId());
dailyTrainStation.setCreateTime(now);
dailyTrainStation.setUpdateTime(now);
dailyTrainStation.setDate(date);
dailyTrainStationMapper.insert(dailyTrainStation);
}
LOG.info("生成日期【{}】车次【{}】的车站信息结束", DateUtil.formatDate(date), trainCode);
}
/**
* 按车次查询全部车站
*/
public long countByTrainCode(Date date, String trainCode) {
DailyTrainStationExample example = new DailyTrainStationExample();
example.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
long stationCount = dailyTrainStationMapper.countByExample(example);
return stationCount;
}
/**
* 按车次日期查询车站列表,用于界面显示一列车经过的车站
*/
public List<DailyTrainStationQueryResp> queryByTrain(Date date, String trainCode) {
DailyTrainStationExample dailyTrainStationExample = new DailyTrainStationExample();
dailyTrainStationExample.setOrderByClause("`index` asc");
dailyTrainStationExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
List<DailyTrainStation> list = dailyTrainStationMapper.selectByExample(dailyTrainStationExample);
return BeanUtil.copyToList(list, DailyTrainStationQueryResp.class);
}
}
DailyTrainTicketService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.DailyTrain;
import com.jiawa.train.business.domain.DailyTrainTicket;
import com.jiawa.train.business.domain.DailyTrainTicketExample;
import com.jiawa.train.business.domain.TrainStation;
import com.jiawa.train.business.enums.SeatTypeEnum;
import com.jiawa.train.business.enums.TrainTypeEnum;
import com.jiawa.train.business.mapper.DailyTrainTicketMapper;
import com.jiawa.train.business.req.DailyTrainTicketQueryReq;
import com.jiawa.train.business.req.DailyTrainTicketSaveReq;
import com.jiawa.train.business.resp.DailyTrainTicketQueryResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
@Service
public class DailyTrainTicketService {
private static final Logger LOG = LoggerFactory.getLogger(DailyTrainTicketService.class);
@Resource
private DailyTrainTicketMapper dailyTrainTicketMapper;
@Resource
private TrainStationService trainStationService;
@Resource
private DailyTrainSeatService dailyTrainSeatService;
public void save(DailyTrainTicketSaveReq req) {
DateTime now = DateTime.now();
DailyTrainTicket dailyTrainTicket = BeanUtil.copyProperties(req, DailyTrainTicket.class);
if (ObjectUtil.isNull(dailyTrainTicket.getId())) {
dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
dailyTrainTicket.setCreateTime(now);
dailyTrainTicket.setUpdateTime(now);
dailyTrainTicketMapper.insert(dailyTrainTicket);
} else {
dailyTrainTicket.setUpdateTime(now);
dailyTrainTicketMapper.updateByPrimaryKey(dailyTrainTicket);
}
}
@Cacheable(value = "DailyTrainTicketService.queryList3")
public PageResp<DailyTrainTicketQueryResp> queryList3(DailyTrainTicketQueryReq req) {
LOG.info("测试缓存击穿");
return null;
}
@CachePut(value = "DailyTrainTicketService.queryList")
public PageResp<DailyTrainTicketQueryResp> queryList2(DailyTrainTicketQueryReq req) {
return queryList(req);
}
// @Cacheable(value = "DailyTrainTicketService.queryList")
public PageResp<DailyTrainTicketQueryResp> queryList(DailyTrainTicketQueryReq req) {
// 常见的缓存过期策略
// TTL 超时时间
// LRU 最近最少使用
// LFU 最近最不经常使用
// FIFO 先进先出
// Random 随机淘汰策略
// 去缓存里取数据,因数据库本身就没数据而造成缓存穿透
// if (有数据) { null []
// return
// } else {
// 去数据库取数据
// }
DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
dailyTrainTicketExample.setOrderByClause("`date` desc, start_time asc, train_code asc, `start_index` asc, `end_index` asc");
DailyTrainTicketExample.Criteria criteria = dailyTrainTicketExample.createCriteria();
if (ObjUtil.isNotNull(req.getDate())) {
criteria.andDateEqualTo(req.getDate());
}
if (ObjUtil.isNotEmpty(req.getTrainCode())) {
criteria.andTrainCodeEqualTo(req.getTrainCode());
}
if (ObjUtil.isNotEmpty(req.getStart())) {
criteria.andStartEqualTo(req.getStart());
}
if (ObjUtil.isNotEmpty(req.getEnd())) {
criteria.andEndEqualTo(req.getEnd());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<DailyTrainTicket> dailyTrainTicketList = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample);
PageInfo<DailyTrainTicket> pageInfo = new PageInfo<>(dailyTrainTicketList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<DailyTrainTicketQueryResp> list = BeanUtil.copyToList(dailyTrainTicketList, DailyTrainTicketQueryResp.class);
PageResp<DailyTrainTicketQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
dailyTrainTicketMapper.deleteByPrimaryKey(id);
}
@Transactional
public void genDaily(DailyTrain dailyTrain, Date date, String trainCode) {
LOG.info("生成日期【{}】车次【{}】的余票信息开始", DateUtil.formatDate(date), trainCode);
// 删除某日某车次的余票信息
DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
dailyTrainTicketExample.createCriteria()
.andDateEqualTo(date)
.andTrainCodeEqualTo(trainCode);
dailyTrainTicketMapper.deleteByExample(dailyTrainTicketExample);
// 查出某车次的所有的车站信息
List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode);
if (CollUtil.isEmpty(stationList)) {
LOG.info("该车次没有车站基础数据,生成该车次的余票信息结束");
return;
}
DateTime now = DateTime.now();
for (int i = 0; i < stationList.size(); i++) {
// 得到出发站
TrainStation trainStationStart = stationList.get(i);
BigDecimal sumKM = BigDecimal.ZERO;
for (int j = (i + 1); j < stationList.size(); j++) {
TrainStation trainStationEnd = stationList.get(j);
sumKM = sumKM.add(trainStationEnd.getKm());
DailyTrainTicket dailyTrainTicket = new DailyTrainTicket();
dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
dailyTrainTicket.setDate(date);
dailyTrainTicket.setTrainCode(trainCode);
dailyTrainTicket.setStart(trainStationStart.getName());
dailyTrainTicket.setStartPinyin(trainStationStart.getNamePinyin());
dailyTrainTicket.setStartTime(trainStationStart.getOutTime());
dailyTrainTicket.setStartIndex(trainStationStart.getIndex());
dailyTrainTicket.setEnd(trainStationEnd.getName());
dailyTrainTicket.setEndPinyin(trainStationEnd.getNamePinyin());
dailyTrainTicket.setEndTime(trainStationEnd.getInTime());
dailyTrainTicket.setEndIndex(trainStationEnd.getIndex());
int ydz = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.YDZ.getCode());
int edz = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.EDZ.getCode());
int rw = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.RW.getCode());
int yw = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.YW.getCode());
// 票价 = 里程之和 * 座位单价 * 车次类型系数
String trainType = dailyTrain.getType();
// 计算票价系数:TrainTypeEnum.priceRate
BigDecimal priceRate = EnumUtil.getFieldBy(TrainTypeEnum::getPriceRate, TrainTypeEnum::getCode, trainType);
BigDecimal ydzPrice = sumKM.multiply(SeatTypeEnum.YDZ.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
BigDecimal edzPrice = sumKM.multiply(SeatTypeEnum.EDZ.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
BigDecimal rwPrice = sumKM.multiply(SeatTypeEnum.RW.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
BigDecimal ywPrice = sumKM.multiply(SeatTypeEnum.YW.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
dailyTrainTicket.setYdz(ydz);
dailyTrainTicket.setYdzPrice(ydzPrice);
dailyTrainTicket.setEdz(edz);
dailyTrainTicket.setEdzPrice(edzPrice);
dailyTrainTicket.setRw(rw);
dailyTrainTicket.setRwPrice(rwPrice);
dailyTrainTicket.setYw(yw);
dailyTrainTicket.setYwPrice(ywPrice);
dailyTrainTicket.setCreateTime(now);
dailyTrainTicket.setUpdateTime(now);
dailyTrainTicketMapper.insert(dailyTrainTicket);
}
}
LOG.info("生成日期【{}】车次【{}】的余票信息结束", DateUtil.formatDate(date), trainCode);
}
public DailyTrainTicket selectByUnique(Date date, String trainCode, String start, String end) {
DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
dailyTrainTicketExample.createCriteria()
.andDateEqualTo(date)
.andTrainCodeEqualTo(trainCode)
.andStartEqualTo(start)
.andEndEqualTo(end);
List<DailyTrainTicket> list = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample);
if (CollUtil.isNotEmpty(list)) {
return list.get(0);
} else {
return null;
}
}
}
SkTokenService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.SkToken;
import com.jiawa.train.business.domain.SkTokenExample;
import com.jiawa.train.business.enums.RedisKeyPreEnum;
import com.jiawa.train.business.mapper.SkTokenMapper;
import com.jiawa.train.business.mapper.cust.SkTokenMapperCust;
import com.jiawa.train.business.req.SkTokenQueryReq;
import com.jiawa.train.business.req.SkTokenSaveReq;
import com.jiawa.train.business.resp.SkTokenQueryResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Service
public class SkTokenService {
private static final Logger LOG = LoggerFactory.getLogger(SkTokenService.class);
@Resource
private SkTokenMapper skTokenMapper;
@Resource
private DailyTrainSeatService dailyTrainSeatService;
@Resource
private DailyTrainStationService dailyTrainStationService;
@Resource
private SkTokenMapperCust skTokenMapperCust;
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${spring.profiles.active}")
private String env;
/**
* 初始化
*/
public void genDaily(Date date, String trainCode) {
LOG.info("删除日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);
SkTokenExample skTokenExample = new SkTokenExample();
skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
skTokenMapper.deleteByExample(skTokenExample);
DateTime now = DateTime.now();
SkToken skToken = new SkToken();
skToken.setDate(date);
skToken.setTrainCode(trainCode);
skToken.setId(SnowUtil.getSnowflakeNextId());
skToken.setCreateTime(now);
skToken.setUpdateTime(now);
int seatCount = dailyTrainSeatService.countSeat(date, trainCode);
LOG.info("车次【{}】座位数:{}", trainCode, seatCount);
long stationCount = dailyTrainStationService.countByTrainCode(date, trainCode);
LOG.info("车次【{}】到站数:{}", trainCode, stationCount);
// 3/4需要根据实际卖票比例来定,一趟火车最多可以卖(seatCount * stationCount)张火车票
int count = (int) (seatCount * stationCount); // * 3/4);
LOG.info("车次【{}】初始生成令牌数:{}", trainCode, count);
skToken.setCount(count);
skTokenMapper.insert(skToken);
}
public void save(SkTokenSaveReq req) {
DateTime now = DateTime.now();
SkToken skToken = BeanUtil.copyProperties(req, SkToken.class);
if (ObjectUtil.isNull(skToken.getId())) {
skToken.setId(SnowUtil.getSnowflakeNextId());
skToken.setCreateTime(now);
skToken.setUpdateTime(now);
skTokenMapper.insert(skToken);
} else {
skToken.setUpdateTime(now);
skTokenMapper.updateByPrimaryKey(skToken);
}
}
public PageResp<SkTokenQueryResp> queryList(SkTokenQueryReq req) {
SkTokenExample skTokenExample = new SkTokenExample();
skTokenExample.setOrderByClause("id desc");
SkTokenExample.Criteria criteria = skTokenExample.createCriteria();
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<SkToken> skTokenList = skTokenMapper.selectByExample(skTokenExample);
PageInfo<SkToken> pageInfo = new PageInfo<>(skTokenList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<SkTokenQueryResp> list = BeanUtil.copyToList(skTokenList, SkTokenQueryResp.class);
PageResp<SkTokenQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
skTokenMapper.deleteByPrimaryKey(id);
}
/**
* 校验令牌
*/
public boolean validSkToken(Date date, String trainCode, Long memberId) {
LOG.info("会员【{}】获取日期【{}】车次【{}】的令牌开始", memberId, DateUtil.formatDate(date), trainCode);
// 需要去掉这段,否则发布生产后,体验多人排队功能时,会因拿不到锁而返回:等待5秒,加入20人时,只有第1次循环能拿到锁
// if (!env.equals("dev")) {
// // 先获取令牌锁,再校验令牌余量,防止机器人抢票,lockKey就是令牌,用来表示【谁能做什么】的一个凭证
// String lockKey = RedisKeyPreEnum.SK_TOKEN + "-" + DateUtil.formatDate(date) + "-" + trainCode + "-" + memberId;
// Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, 5, TimeUnit.SECONDS);
// if (Boolean.TRUE.equals(setIfAbsent)) {
// LOG.info("恭喜,抢到令牌锁了!lockKey:{}", lockKey);
// } else {
// LOG.info("很遗憾,没抢到令牌锁!lockKey:{}", lockKey);
// return false;
// }
// }
String skTokenCountKey = RedisKeyPreEnum.SK_TOKEN_COUNT + "-" + DateUtil.formatDate(date) + "-" + trainCode;
Object skTokenCount = redisTemplate.opsForValue().get(skTokenCountKey);
if (skTokenCount != null) {
LOG.info("缓存中有该车次令牌大闸的key:{}", skTokenCountKey);
Long count = redisTemplate.opsForValue().decrement(skTokenCountKey, 1);
if (count < 0L) {
LOG.error("获取令牌失败:{}", skTokenCountKey);
return false;
} else {
LOG.info("获取令牌后,令牌余数:{}", count);
redisTemplate.expire(skTokenCountKey, 60, TimeUnit.SECONDS);
// 每获取5个令牌更新一次数据库
if (count % 5 == 0) {
skTokenMapperCust.decrease(date, trainCode, 5);
}
return true;
}
} else {
LOG.info("缓存中没有该车次令牌大闸的key:{}", skTokenCountKey);
// 检查是否还有令牌
SkTokenExample skTokenExample = new SkTokenExample();
skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
List<SkToken> tokenCountList = skTokenMapper.selectByExample(skTokenExample);
if (CollUtil.isEmpty(tokenCountList)) {
LOG.info("找不到日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);
return false;
}
SkToken skToken = tokenCountList.get(0);
if (skToken.getCount() <= 0) {
LOG.info("日期【{}】车次【{}】的令牌余量为0", DateUtil.formatDate(date), trainCode);
return false;
}
// 令牌还有余量
// 令牌余数-1
Integer count = skToken.getCount() - 1;
skToken.setCount(count);
LOG.info("将该车次令牌大闸放入缓存中,key: {}, count: {}", skTokenCountKey, count);
// 不需要更新数据库,只要放缓存即可
redisTemplate.opsForValue().set(skTokenCountKey, String.valueOf(count), 60, TimeUnit.SECONDS);
// skTokenMapper.updateByPrimaryKey(skToken);
return true;
}
// 令牌约等于库存,令牌没有了,就不再卖票,不需要再进入购票主流程去判断库存,判断令牌肯定比判断库存效率高
// int updateCount = skTokenMapperCust.decrease(date, trainCode, 1);
// if (updateCount > 0) {
// return true;
// } else {
// return false;
// }
}
}
StationService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.common.exception.BusinessException;
import com.jiawa.train.common.exception.BusinessExceptionEnum;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import com.jiawa.train.business.domain.Station;
import com.jiawa.train.business.domain.StationExample;
import com.jiawa.train.business.mapper.StationMapper;
import com.jiawa.train.business.req.StationQueryReq;
import com.jiawa.train.business.req.StationSaveReq;
import com.jiawa.train.business.resp.StationQueryResp;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StationService {
private static final Logger LOG = LoggerFactory.getLogger(StationService.class);
@Resource
private StationMapper stationMapper;
public void save(StationSaveReq req) {
DateTime now = DateTime.now();
Station station = BeanUtil.copyProperties(req, Station.class);
if (ObjectUtil.isNull(station.getId())) {
// 保存之前,先校验唯一键是否存在
Station stationDB = selectByUnique(req.getName());
if (ObjectUtil.isNotEmpty(stationDB)) {
throw new BusinessException(BusinessExceptionEnum.BUSINESS_STATION_NAME_UNIQUE_ERROR);
}
station.setId(SnowUtil.getSnowflakeNextId());
station.setCreateTime(now);
station.setUpdateTime(now);
stationMapper.insert(station);
} else {
station.setUpdateTime(now);
stationMapper.updateByPrimaryKey(station);
}
}
private Station selectByUnique(String name) {
StationExample stationExample = new StationExample();
stationExample.createCriteria().andNameEqualTo(name);
List<Station> list = stationMapper.selectByExample(stationExample);
if (CollUtil.isNotEmpty(list)) {
return list.get(0);
} else {
return null;
}
}
public PageResp<StationQueryResp> queryList(StationQueryReq req) {
StationExample stationExample = new StationExample();
stationExample.setOrderByClause("id desc");
StationExample.Criteria criteria = stationExample.createCriteria();
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<Station> stationList = stationMapper.selectByExample(stationExample);
PageInfo<Station> pageInfo = new PageInfo<>(stationList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<StationQueryResp> list = BeanUtil.copyToList(stationList, StationQueryResp.class);
PageResp<StationQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
stationMapper.deleteByPrimaryKey(id);
}
public List<StationQueryResp> queryAll() {
StationExample stationExample = new StationExample();
stationExample.setOrderByClause("name_pinyin asc");
List<Station> stationList = stationMapper.selectByExample(stationExample);
return BeanUtil.copyToList(stationList, StationQueryResp.class);
}
}
TrainCarriageService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.TrainCarriage;
import com.jiawa.train.business.domain.TrainCarriageExample;
import com.jiawa.train.business.enums.SeatColEnum;
import com.jiawa.train.business.mapper.TrainCarriageMapper;
import com.jiawa.train.business.req.TrainCarriageQueryReq;
import com.jiawa.train.business.req.TrainCarriageSaveReq;
import com.jiawa.train.business.resp.TrainCarriageQueryResp;
import com.jiawa.train.common.exception.BusinessException;
import com.jiawa.train.common.exception.BusinessExceptionEnum;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TrainCarriageService {
private static final Logger LOG = LoggerFactory.getLogger(TrainCarriageService.class);
@Resource
private TrainCarriageMapper trainCarriageMapper;
public void save(TrainCarriageSaveReq req) {
DateTime now = DateTime.now();
// 自动计算出列数和总座位数
List<SeatColEnum> seatColEnums = SeatColEnum.getColsByType(req.getSeatType());
req.setColCount(seatColEnums.size());
req.setSeatCount(req.getColCount() * req.getRowCount());
TrainCarriage trainCarriage = BeanUtil.copyProperties(req, TrainCarriage.class);
if (ObjectUtil.isNull(trainCarriage.getId())) {
// 保存之前,先校验唯一键是否存在
TrainCarriage trainCarriageDB = selectByUnique(req.getTrainCode(), req.getIndex());
if (ObjectUtil.isNotEmpty(trainCarriageDB)) {
throw new BusinessException(BusinessExceptionEnum.BUSINESS_TRAIN_CARRIAGE_INDEX_UNIQUE_ERROR);
}
trainCarriage.setId(SnowUtil.getSnowflakeNextId());
trainCarriage.setCreateTime(now);
trainCarriage.setUpdateTime(now);
trainCarriageMapper.insert(trainCarriage);
} else {
trainCarriage.setUpdateTime(now);
trainCarriageMapper.updateByPrimaryKey(trainCarriage);
}
}
private TrainCarriage selectByUnique(String trainCode, Integer index) {
TrainCarriageExample trainCarriageExample = new TrainCarriageExample();
trainCarriageExample.createCriteria()
.andTrainCodeEqualTo(trainCode)
.andIndexEqualTo(index);
List<TrainCarriage> list = trainCarriageMapper.selectByExample(trainCarriageExample);
if (CollUtil.isNotEmpty(list)) {
return list.get(0);
} else {
return null;
}
}
public PageResp<TrainCarriageQueryResp> queryList(TrainCarriageQueryReq req) {
TrainCarriageExample trainCarriageExample = new TrainCarriageExample();
trainCarriageExample.setOrderByClause("train_code asc, `index` asc");
TrainCarriageExample.Criteria criteria = trainCarriageExample.createCriteria();
if (ObjectUtil.isNotEmpty(req.getTrainCode())) {
criteria.andTrainCodeEqualTo(req.getTrainCode());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<TrainCarriage> trainCarriageList = trainCarriageMapper.selectByExample(trainCarriageExample);
PageInfo<TrainCarriage> pageInfo = new PageInfo<>(trainCarriageList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<TrainCarriageQueryResp> list = BeanUtil.copyToList(trainCarriageList, TrainCarriageQueryResp.class);
PageResp<TrainCarriageQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
trainCarriageMapper.deleteByPrimaryKey(id);
}
public List<TrainCarriage> selectByTrainCode(String trainCode) {
TrainCarriageExample trainCarriageExample = new TrainCarriageExample();
trainCarriageExample.setOrderByClause("`index` asc");
TrainCarriageExample.Criteria criteria = trainCarriageExample.createCriteria();
criteria.andTrainCodeEqualTo(trainCode);
return trainCarriageMapper.selectByExample(trainCarriageExample);
}
}
TrainSeatService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.TrainCarriage;
import com.jiawa.train.business.domain.TrainSeat;
import com.jiawa.train.business.domain.TrainSeatExample;
import com.jiawa.train.business.enums.SeatColEnum;
import com.jiawa.train.business.mapper.TrainSeatMapper;
import com.jiawa.train.business.req.TrainSeatQueryReq;
import com.jiawa.train.business.req.TrainSeatSaveReq;
import com.jiawa.train.business.resp.TrainSeatQueryResp;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class TrainSeatService {
private static final Logger LOG = LoggerFactory.getLogger(TrainSeatService.class);
@Resource
private TrainSeatMapper trainSeatMapper;
@Resource
private TrainCarriageService trainCarriageService;
public void save(TrainSeatSaveReq req) {
DateTime now = DateTime.now();
TrainSeat trainSeat = BeanUtil.copyProperties(req, TrainSeat.class);
if (ObjectUtil.isNull(trainSeat.getId())) {
trainSeat.setId(SnowUtil.getSnowflakeNextId());
trainSeat.setCreateTime(now);
trainSeat.setUpdateTime(now);
trainSeatMapper.insert(trainSeat);
} else {
trainSeat.setUpdateTime(now);
trainSeatMapper.updateByPrimaryKey(trainSeat);
}
}
public PageResp<TrainSeatQueryResp> queryList(TrainSeatQueryReq req) {
TrainSeatExample trainSeatExample = new TrainSeatExample();
trainSeatExample.setOrderByClause("train_code asc, carriage_index asc, carriage_seat_index asc");
TrainSeatExample.Criteria criteria = trainSeatExample.createCriteria();
if (ObjectUtil.isNotEmpty(req.getTrainCode())) {
criteria.andTrainCodeEqualTo(req.getTrainCode());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<TrainSeat> trainSeatList = trainSeatMapper.selectByExample(trainSeatExample);
PageInfo<TrainSeat> pageInfo = new PageInfo<>(trainSeatList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<TrainSeatQueryResp> list = BeanUtil.copyToList(trainSeatList, TrainSeatQueryResp.class);
PageResp<TrainSeatQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
trainSeatMapper.deleteByPrimaryKey(id);
}
@Transactional
public void genTrainSeat(String trainCode) {
DateTime now = DateTime.now();
// 清空当前车次下的所有的座位记录
TrainSeatExample trainSeatExample = new TrainSeatExample();
TrainSeatExample.Criteria criteria = trainSeatExample.createCriteria();
criteria.andTrainCodeEqualTo(trainCode);
trainSeatMapper.deleteByExample(trainSeatExample);
// 查找当前车次下的所有的车厢
List<TrainCarriage> carriageList = trainCarriageService.selectByTrainCode(trainCode);
LOG.info("当前车次下的车厢数:{}", carriageList.size());
// 循环生成每个车厢的座位
for (TrainCarriage trainCarriage : carriageList) {
// 拿到车厢数据:行数、座位类型(得到列数)
Integer rowCount = trainCarriage.getRowCount();
String seatType = trainCarriage.getSeatType();
int seatIndex = 1;
// 根据车厢的座位类型,筛选出所有的列,比如车箱类型是一等座,则筛选出columnList={ACDF}
List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(seatType);
LOG.info("根据车厢的座位类型,筛选出所有的列:{}", colEnumList);
// 循环行数
for (int row = 1; row <= rowCount; row++) {
// 循环列数
for (SeatColEnum seatColEnum : colEnumList) {
// 构造座位数据并保存数据库
TrainSeat trainSeat = new TrainSeat();
trainSeat.setId(SnowUtil.getSnowflakeNextId());
trainSeat.setTrainCode(trainCode);
trainSeat.setCarriageIndex(trainCarriage.getIndex());
trainSeat.setRow(StrUtil.fillBefore(String.valueOf(row), '0', 2));
trainSeat.setCol(seatColEnum.getCode());
trainSeat.setSeatType(seatType);
trainSeat.setCarriageSeatIndex(seatIndex++);
trainSeat.setCreateTime(now);
trainSeat.setUpdateTime(now);
trainSeatMapper.insert(trainSeat);
}
}
}
}
public List<TrainSeat> selectByTrainCode(String trainCode) {
TrainSeatExample trainSeatExample = new TrainSeatExample();
trainSeatExample.setOrderByClause("`id` asc");
TrainSeatExample.Criteria criteria = trainSeatExample.createCriteria();
criteria.andTrainCodeEqualTo(trainCode);
return trainSeatMapper.selectByExample(trainSeatExample);
}
}
TrainService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.Train;
import com.jiawa.train.business.domain.TrainExample;
import com.jiawa.train.business.mapper.TrainMapper;
import com.jiawa.train.business.req.TrainQueryReq;
import com.jiawa.train.business.req.TrainSaveReq;
import com.jiawa.train.business.resp.TrainQueryResp;
import com.jiawa.train.common.exception.BusinessException;
import com.jiawa.train.common.exception.BusinessExceptionEnum;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class TrainService {
private static final Logger LOG = LoggerFactory.getLogger(TrainService.class);
@Resource
private TrainMapper trainMapper;
public void save(TrainSaveReq req) {
DateTime now = DateTime.now();
Train train = BeanUtil.copyProperties(req, Train.class);
if (ObjectUtil.isNull(train.getId())) {
// 保存之前,先校验唯一键是否存在
Train trainDB = selectByUnique(req.getCode());
if (ObjectUtil.isNotEmpty(trainDB)) {
throw new BusinessException(BusinessExceptionEnum.BUSINESS_TRAIN_CODE_UNIQUE_ERROR);
}
train.setId(SnowUtil.getSnowflakeNextId());
train.setCreateTime(now);
train.setUpdateTime(now);
trainMapper.insert(train);
} else {
train.setUpdateTime(now);
trainMapper.updateByPrimaryKey(train);
}
}
private Train selectByUnique(String code) {
TrainExample trainExample = new TrainExample();
trainExample.createCriteria()
.andCodeEqualTo(code);
List<Train> list = trainMapper.selectByExample(trainExample);
if (CollUtil.isNotEmpty(list)) {
return list.get(0);
} else {
return null;
}
}
public PageResp<TrainQueryResp> queryList(TrainQueryReq req) {
TrainExample trainExample = new TrainExample();
trainExample.setOrderByClause("code asc");
TrainExample.Criteria criteria = trainExample.createCriteria();
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<Train> trainList = trainMapper.selectByExample(trainExample);
PageInfo<Train> pageInfo = new PageInfo<>(trainList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<TrainQueryResp> list = BeanUtil.copyToList(trainList, TrainQueryResp.class);
PageResp<TrainQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
trainMapper.deleteByPrimaryKey(id);
}
@Transactional
public List<TrainQueryResp> queryAll() {
List<Train> trainList = selectAll();
// LOG.info("再查一次");
// trainList = selectAll();
return BeanUtil.copyToList(trainList, TrainQueryResp.class);
}
public List<Train> selectAll() {
TrainExample trainExample = new TrainExample();
trainExample.setOrderByClause("code asc");
return trainMapper.selectByExample(trainExample);
}
}
TrainStationService
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.jiawa.train.business.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jiawa.train.business.domain.TrainStation;
import com.jiawa.train.business.domain.TrainStationExample;
import com.jiawa.train.business.mapper.TrainStationMapper;
import com.jiawa.train.business.req.TrainStationQueryReq;
import com.jiawa.train.business.req.TrainStationSaveReq;
import com.jiawa.train.business.resp.TrainStationQueryResp;
import com.jiawa.train.common.exception.BusinessException;
import com.jiawa.train.common.exception.BusinessExceptionEnum;
import com.jiawa.train.common.resp.PageResp;
import com.jiawa.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TrainStationService {
private static final Logger LOG = LoggerFactory.getLogger(TrainStationService.class);
@Resource
private TrainStationMapper trainStationMapper;
public void save(TrainStationSaveReq req) {
DateTime now = DateTime.now();
TrainStation trainStation = BeanUtil.copyProperties(req, TrainStation.class);
if (ObjectUtil.isNull(trainStation.getId())) {
// 保存之前,先校验唯一键是否存在
TrainStation trainStationDB = selectByUnique(req.getTrainCode(), req.getIndex());
if (ObjectUtil.isNotEmpty(trainStationDB)) {
throw new BusinessException(BusinessExceptionEnum.BUSINESS_TRAIN_STATION_INDEX_UNIQUE_ERROR);
}
// 保存之前,先校验唯一键是否存在
trainStationDB = selectByUnique(req.getTrainCode(), req.getName());
if (ObjectUtil.isNotEmpty(trainStationDB)) {
throw new BusinessException(BusinessExceptionEnum.BUSINESS_TRAIN_STATION_NAME_UNIQUE_ERROR);
}
trainStation.setId(SnowUtil.getSnowflakeNextId());
trainStation.setCreateTime(now);
trainStation.setUpdateTime(now);
trainStationMapper.insert(trainStation);
} else {
trainStation.setUpdateTime(now);
trainStationMapper.updateByPrimaryKey(trainStation);
}
}
private TrainStation selectByUnique(String trainCode, Integer index) {
TrainStationExample trainStationExample = new TrainStationExample();
trainStationExample.createCriteria()
.andTrainCodeEqualTo(trainCode)
.andIndexEqualTo(index);
List<TrainStation> list = trainStationMapper.selectByExample(trainStationExample);
if (CollUtil.isNotEmpty(list)) {
return list.get(0);
} else {
return null;
}
}
private TrainStation selectByUnique(String trainCode, String name) {
TrainStationExample trainStationExample = new TrainStationExample();
trainStationExample.createCriteria()
.andTrainCodeEqualTo(trainCode)
.andNameEqualTo(name);
List<TrainStation> list = trainStationMapper.selectByExample(trainStationExample);
if (CollUtil.isNotEmpty(list)) {
return list.get(0);
} else {
return null;
}
}
public PageResp<TrainStationQueryResp> queryList(TrainStationQueryReq req) {
TrainStationExample trainStationExample = new TrainStationExample();
trainStationExample.setOrderByClause("train_code asc, `index` asc");
TrainStationExample.Criteria criteria = trainStationExample.createCriteria();
if (ObjectUtil.isNotEmpty(req.getTrainCode())) {
criteria.andTrainCodeEqualTo(req.getTrainCode());
}
LOG.info("查询页码:{}", req.getPage());
LOG.info("每页条数:{}", req.getSize());
PageHelper.startPage(req.getPage(), req.getSize());
List<TrainStation> trainStationList = trainStationMapper.selectByExample(trainStationExample);
PageInfo<TrainStation> pageInfo = new PageInfo<>(trainStationList);
LOG.info("总行数:{}", pageInfo.getTotal());
LOG.info("总页数:{}", pageInfo.getPages());
List<TrainStationQueryResp> list = BeanUtil.copyToList(trainStationList, TrainStationQueryResp.class);
PageResp<TrainStationQueryResp> pageResp = new PageResp<>();
pageResp.setTotal(pageInfo.getTotal());
pageResp.setList(list);
return pageResp;
}
public void delete(Long id) {
trainStationMapper.deleteByPrimaryKey(id);
}
public List<TrainStation> selectByTrainCode(String trainCode) {
TrainStationExample trainStationExample = new TrainStationExample();
trainStationExample.setOrderByClause("`index` asc");
trainStationExample.createCriteria().andTrainCodeEqualTo(trainCode);
return trainStationMapper.selectByExample(trainStationExample);
}
}
