简介
主要功能
模块划分
gateway 网关模块:路由转发、登录校验
member 会员模块:会员、乘客、已购买的车票
business 业务模块:所有的车次数据、余票信息
batch 跑批模块:所有的定时任务,可通过界面启停
web 模块:会员相关界面
admin 模块:管理员相关界面
Springcloud Alibaba nacos配置注册中心
MySQL
Redis,reddison
rocketMQ
sential限流熔断
seata分布式事务
quartz定时任务
父模块
主要用来管理多模块项目和公共依赖
pom
<?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
<?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>
日志切面
用来打印整个流程的日志
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
// 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;
// }
// }
===
登录会员上下文
存储登录的用户
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
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;
}
}
===
自定义异常
业务异常
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;
}
}
异常类型枚举
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();
}
}
===
日志拦截器
日志拦截
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;
}
}
===
会员登录拦截器
拦截登录
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
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;
}
会员车票
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
公共返回
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();
}
}
分页返回
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;
}
会员登录
package com.jiawa.train.common.resp;
@Data
public class MemberLoginResp {
private Long id;
private String mobile;
private String token;
}
===
utils
雪花算法
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令牌
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
<?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
<?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
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
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
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
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
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
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
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
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
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
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
<?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
server.port=80
application
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
## nacos server注册中心地址,使用ECS内网IP
spring.cloud.nacos.discovery.server-addr=172.17.208.8:8848
spring.cloud.nacos.discovery.namespace=train
bootstrap
# 注册中心的名字
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
<?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
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
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
#
# 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
<?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
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
## nacos server注册中心地址,使用ECS内网IP
spring.cloud.nacos.discovery.server-addr=172.17.208.8:8848
spring.cloud.nacos.discovery.namespace=train
## 关于生成的数据库、RDS,可以通过生产nacos来配置
bootstrap
# 注册中心的名字
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
<?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
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
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
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
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
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
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
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
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
// 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
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
// 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
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
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
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
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
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
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
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
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
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
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
<?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
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
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
## 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
# 注册中心的名字
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
<?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>
启动类
主启动类
@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
文件来写测试接口
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
登录
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();
}
}
注册
import jakarta.validation.constraints.NotBlank;
@Data
public class MemberRegisterReq {
@NotBlank(message = "【手机号】不能为空")
private String mobile;
@Override
public String toString() {
return "MemberRegisterReq{" +
"mobile='" + mobile + '\'' +
'}';
}
}
发送验证码
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 + '\'' +
'}';
}
}
查乘车人
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();
}
}
乘车人保存
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;
}
查票
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();
}
}
保存票
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
登录
package com.jiawa.train.member.resp;
@Data
public class MemberLoginResp {
private Long id;
private String mobile;
private String token;
}
乘车人查询
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;
}
车票查询
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
乘车人类型
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配置
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
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);
}
}
}
乘客
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);
}
}
}
}
车票
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会员
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
<?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
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
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
## nacos server注册中心地址,使用ECS内网IP
spring.cloud.nacos.discovery.server-addr=172.17.208.8:8848
spring.cloud.nacos.discovery.namespace=train
## 关于生成的数据库、RDS,可以通过生产nacos来配置
bootstrap
# 注册中心的名字
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
<?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
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
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
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
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
// 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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);
}
}