Compare commits

...

2 Commits
main ... yjc

Author SHA1 Message Date
hd 903575472e 修改配置文件 2 weeks ago
hd 63c2e35108 初次提交 2 weeks ago

38
.gitignore vendored

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
/.logs/
/.idea/
.logs
.idea

@ -0,0 +1,124 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>registration</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>registration</name>
<description>培训报名系统</description>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<knife4j.version>4.3.0</knife4j.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- Mybatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Knife4j (Swagger UI) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Apache Commons Pool2 (Redis连接池需要) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 阿里云短信服务SDK -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>3.0.0</version>
</dependency>
<!-- 阿里云SDK依赖 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-openapi</artifactId>
<version>0.3.9</version>
</dependency>
<!-- fastjson (阿里云SDK依赖) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.43</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,116 @@
/*
Navicat Premium Dump SQL
Source Server : JavaProjects
Source Server Type : MySQL
Source Server Version : 80040 (8.0.40)
Source Host : localhost:3306
Source Schema : registration
Target Server Type : MySQL
Target Server Version : 80040 (8.0.40)
File Encoding : 65001
Date: 15/05/2026 10:33:13
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for training_registration
-- ----------------------------
DROP TABLE IF EXISTS `training_registration`;
CREATE TABLE `training_registration` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键ID',
`bm_id` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '报名编号格式BMYYYYMMxxxx',
`random_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '随机唯一编号(隐藏)',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名(真实姓名)',
`company` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '所在单位(学校/企业全称)',
`department` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '所在部门(院系/部门名称)',
`referrer` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '推荐人',
`referrer_company` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '推荐人单位',
`title` enum('教授','副教授','讲师','工程师','其他') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '职务/职称',
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号码',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '电子邮箱',
`id_card` varchar(18) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '身份证号',
`period` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '报名期次',
`fee` decimal(10, 2) NOT NULL DEFAULT 2500.00 COMMENT '培训费用(元)',
`remark` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '备注说明(饮食禁忌、特殊需求等)',
`sms_code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '短信验证码',
`is_verified` tinyint(1) NULL DEFAULT 0 COMMENT '短信是否已验证0-未验证1-已验证',
`verification_time` datetime NULL DEFAULT NULL COMMENT '验证时间',
`status` enum('draft','submitted','reviewing','approved','rejected','paid','completed','cancelled') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'draft' COMMENT '状态:草稿/已提交/审核中/已通过/已拒绝/已支付/已完成/已取消',
`submit_time` datetime NULL DEFAULT NULL COMMENT '提交时间',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建IP',
`create_user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建用户代理',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `bm_id`(`bm_id` ASC) USING BTREE,
UNIQUE INDEX `random_code`(`random_code` ASC) USING BTREE,
INDEX `idx_bm_id`(`bm_id` ASC) USING BTREE,
INDEX `idx_phone`(`phone` ASC) USING BTREE,
INDEX `idx_email`(`email` ASC) USING BTREE,
INDEX `idx_id_card`(`id_card` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE,
INDEX `idx_create_time`(`create_time` ASC) USING BTREE,
INDEX `idx_period`(`period` ASC) USING BTREE,
INDEX `idx_company`(`company`(20) ASC) USING BTREE,
INDEX `idx_random_code`(`random_code` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '培训报名表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of training_registration
-- ----------------------------
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名(唯一)',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱(唯一)',
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号(唯一,可选)',
`password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码哈希值',
`password_salt` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码盐值',
`real_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '真实姓名',
`nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '昵称',
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像URL',
`gender` enum('male','female','unknown') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'unknown' COMMENT '性别:男/女/未知',
`birthday` date NULL DEFAULT NULL COMMENT '出生日期',
`status` enum('active','inactive','locked','deleted') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'active' COMMENT '账号状态:激活/未激活/锁定/已删除',
`email_verified` tinyint(1) NULL DEFAULT 0 COMMENT '邮箱是否已验证0-未验证1-已验证',
`phone_verified` tinyint(1) NULL DEFAULT 0 COMMENT '手机号是否已验证0-未验证1-已验证',
`last_login_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '最后登录IP',
`last_login_time` datetime NULL DEFAULT NULL COMMENT '最后登录时间',
`login_count` int NULL DEFAULT 0 COMMENT '登录次数',
`failed_login_attempts` int NULL DEFAULT 0 COMMENT '连续登录失败次数',
`account_locked_until` datetime NULL DEFAULT NULL COMMENT '账号锁定至',
`password_changed_at` datetime NULL DEFAULT NULL COMMENT '密码最后修改时间',
`password_expires_at` datetime NULL DEFAULT NULL COMMENT '密码过期时间',
`role` enum('user','admin','super_admin') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'user' COMMENT '用户角色',
`created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted_at` datetime NULL DEFAULT NULL COMMENT '删除时间(软删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username` ASC) USING BTREE,
UNIQUE INDEX `email`(`email` ASC) USING BTREE,
UNIQUE INDEX `phone`(`phone` ASC) USING BTREE,
INDEX `idx_username`(`username` ASC) USING BTREE,
INDEX `idx_email`(`email` ASC) USING BTREE,
INDEX `idx_phone`(`phone` ASC) USING BTREE,
INDEX `idx_status`(`status` ASC) USING BTREE,
INDEX `idx_role`(`role` ASC) USING BTREE,
INDEX `idx_created`(`created_at` ASC) USING BTREE,
INDEX `idx_login_time`(`last_login_time` ASC) USING BTREE,
INDEX `idx_status_created`(`status` ASC, `created_at` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'admin', '1', '1', 'a3c2b756aae43be93802ed8dda4f9c06', '1234', NULL, NULL, NULL, 'unknown', NULL, 'active', 0, 0, NULL, '2026-05-15 10:03:43', 7, 0, NULL, NULL, NULL, 'super_admin', '2026-04-30 15:08:28', '2026-04-30 15:26:32', NULL);
SET FOREIGN_KEY_CHECKS = 1;

@ -0,0 +1,14 @@
package com.example.registration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.example.registration.mapper")
public class RegistrationApplication {
public static void main(String[] args) {
SpringApplication.run(RegistrationApplication.class, args);
}
}

@ -0,0 +1,36 @@
package com.example.registration.config;
import com.example.registration.util.Result;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BindException.class)
public Result<Void> handleBindException(BindException e) {
String message = e.getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(message);
}
@ExceptionHandler(ConstraintViolationException.class)
public Result<Void> handleConstraintViolationException(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
return Result.error(message);
}
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
return Result.error(e.getMessage());
}
}

@ -0,0 +1,39 @@
package com.example.registration.config;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
/**
* http://10.23.22.43:8099/doc.html
*/
@Configuration
@EnableSwagger2WebMvc
@EnableKnife4j
public class Knife4jConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.registration.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("培训报名系统 API")
.description("培训报名系统接口文档")
.version("1.0.0")
.build();
}
}

@ -0,0 +1,18 @@
package com.example.registration.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

@ -0,0 +1,23 @@
package com.example.registration.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}

@ -0,0 +1,49 @@
package com.example.registration.config;
import com.example.registration.util.RedisTokenUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private RedisTokenUtil redisTokenUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行 OPTIONS 预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
String token = request.getHeader("token");
if (token == null || token.isEmpty()) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
Map<String, Object> result = new HashMap<>();
result.put("code", 401);
result.put("message", "token不能为空");
response.getWriter().write(new ObjectMapper().writeValueAsString(result));
return false;
}
if (!redisTokenUtil.validateToken(token)) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
Map<String, Object> result = new HashMap<>();
result.put("code", 401);
result.put("message", "token已过期或无效");
response.getWriter().write(new ObjectMapper().writeValueAsString(result));
return false;
}
// 刷新token过期时间
redisTokenUtil.refreshToken(token);
return true;
}
}

@ -0,0 +1,36 @@
package com.example.registration.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/user/login",
"/api/registration/submit",
"/api/sms/send",
"/api/sms/verify"
);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}

@ -0,0 +1,84 @@
package com.example.registration.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.registration.dto.RegistrationRequest;
import com.example.registration.dto.RegistrationUpdateRequest;
import com.example.registration.dto.StatisticsResponse;
import com.example.registration.entity.TrainingRegistration;
import com.example.registration.service.RegistrationService;
import com.example.registration.service.SmsService;
import com.example.registration.util.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/registration")
@Api(tags = "报名管理")
public class RegistrationController {
@Autowired
private RegistrationService registrationService;
@Autowired
private SmsService smsService;
@PostMapping("/submit")
@ApiOperation("提交报名表单")
public Result<TrainingRegistration> submit(@RequestBody @Validated RegistrationRequest request) {
// 验证短信验证码
if (!smsService.verifyCode(request.getPhone(), request.getSmsCode())) {
return Result.error("短信验证码错误或已过期");
}
TrainingRegistration registration = new TrainingRegistration();
BeanUtils.copyProperties(request, registration);
TrainingRegistration result = registrationService.submit(registration);
return Result.success(result);
}
@GetMapping("/list")
@ApiOperation("报名列表")
public Result<IPage<TrainingRegistration>> list(
@RequestParam(defaultValue = "1") @ApiParam("页码") Integer pageNum,
@RequestParam(defaultValue = "10") @ApiParam("每页条数") Integer pageSize,
@RequestParam(required = false) @ApiParam("关键词") String keyword,
@RequestParam(required = false) @ApiParam("状态") String status,
@RequestParam(required = false) @ApiParam("期次") String period) {
Page<TrainingRegistration> page = new Page<>(pageNum, pageSize);
return Result.success(registrationService.listPage(page, keyword, status, period));
}
@PutMapping("/update/{randomCode}")
@ApiOperation("修改报名信息")
public Result<TrainingRegistration> update(
@PathVariable @ApiParam("随机唯一编号") String randomCode,
@RequestBody RegistrationUpdateRequest request) {
TrainingRegistration registration = new TrainingRegistration();
BeanUtils.copyProperties(request, registration);
TrainingRegistration result = registrationService.updateByRandomCode(randomCode, registration);
if (result == null) {
return Result.error("报名信息不存在");
}
return Result.success(result);
}
@GetMapping("/statistics")
@ApiOperation("统计报名信息")
public Result<StatisticsResponse> statistics() {
Map<String, Object> map = registrationService.statistics();
StatisticsResponse response = new StatisticsResponse();
response.setTotalRegistrations((Long) map.get("totalRegistrations"));
response.setPaidRegistrations((Long) map.get("paidRegistrations"));
response.setPendingPaymentRegistrations((Long) map.get("pendingPaymentRegistrations"));
response.setPaidAmount((java.math.BigDecimal) map.get("paidAmount"));
return Result.success(response);
}
}

@ -0,0 +1,48 @@
package com.example.registration.controller;
import com.example.registration.dto.SmsRequest;
import com.example.registration.service.SmsService;
import com.example.registration.util.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/sms")
@Api(tags = "短信服务")
public class SmsController {
@Autowired
private SmsService smsService;
@PostMapping("/send")
@ApiOperation("发送短信验证码")
public Result<Map<String, Object>> sendCode(@RequestBody @Validated SmsRequest request) {
boolean success = smsService.sendCode(request.getPhone());
if (success) {
Map<String, Object> data = new HashMap<>();
data.put("message", "验证码已发送请在5分钟内完成验证");
return Result.success(data);
}
return Result.error("验证码发送失败,请稍后重试");
}
@PostMapping("/verify")
@ApiOperation("验证短信验证码")
public Result<Map<String, Object>> verifyCode(@RequestBody @Validated SmsRequest request,
@RequestParam String code) {
boolean success = smsService.verifyCode(request.getPhone(), code);
Map<String, Object> data = new HashMap<>();
if (success) {
data.put("valid", true);
return Result.success(data);
}
data.put("valid", false);
return Result.error("验证码验证失败");
}
}

@ -0,0 +1,53 @@
package com.example.registration.controller;
import com.example.registration.dto.LoginRequest;
import com.example.registration.dto.LoginResponse;
import com.example.registration.entity.User;
import com.example.registration.service.UserService;
import com.example.registration.util.RedisTokenUtil;
import com.example.registration.util.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
@RestController
@RequestMapping("/api/user")
@Api(tags = "用户管理")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private RedisTokenUtil redisTokenUtil;
@PostMapping("/login")
@ApiOperation("用户登录")
public Result<LoginResponse> login(@RequestBody @Validated LoginRequest request) {
User user = userService.login(request.getUsername(), request.getPassword());
if (user == null) {
return Result.error("用户名或密码错误");
}
String token = UUID.randomUUID().toString().replace("-", "");
redisTokenUtil.storeToken(token, String.valueOf(user.getId()));
LoginResponse response = new LoginResponse();
response.setUserId(user.getId());
response.setUsername(user.getUsername());
response.setRealName(user.getRealName());
response.setRole(user.getRole());
response.setToken(token);
return Result.success(response);
}
@PostMapping("/logout")
@ApiOperation("退出登录")
public Result<Void> logout(@RequestHeader("token") String token) {
redisTokenUtil.deleteToken(token);
return Result.success();
}
}

@ -0,0 +1,20 @@
package com.example.registration.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
@ApiModel("登录请求")
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
@ApiModelProperty("用户名")
private String username;
@NotBlank(message = "密码不能为空")
@ApiModelProperty("密码")
private String password;
}

@ -0,0 +1,25 @@
package com.example.registration.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("登录响应")
public class LoginResponse {
@ApiModelProperty("用户ID")
private Long userId;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("真实姓名")
private String realName;
@ApiModelProperty("角色")
private String role;
@ApiModelProperty("Token")
private String token;
}

@ -0,0 +1,62 @@
package com.example.registration.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Data
@ApiModel("提交报名表单请求")
public class RegistrationRequest {
@NotBlank(message = "姓名不能为空")
@ApiModelProperty("姓名(真实姓名)")
private String name;
@NotBlank(message = "所在单位不能为空")
@ApiModelProperty("所在单位(学校/企业全称)")
private String company;
@ApiModelProperty("所在部门(院系/部门名称)")
private String department;
@NotBlank(message = "职务/职称不能为空")
@ApiModelProperty("职务/职称")
private String title;
@NotBlank(message = "手机号码不能为空")
@ApiModelProperty("手机号码")
private String phone;
@NotBlank(message = "短信验证码不能为空")
@ApiModelProperty("短信验证码")
private String smsCode;
@NotBlank(message = "电子邮箱不能为空")
@ApiModelProperty("电子邮箱")
private String email;
@NotBlank(message = "身份证号不能为空")
@ApiModelProperty("身份证号")
private String idCard;
@NotBlank(message = "报名期次不能为空")
@ApiModelProperty("报名期次")
private String period;
@NotNull(message = "培训费用不能为空")
@ApiModelProperty("培训费用(元)")
private BigDecimal fee;
@ApiModelProperty("备注说明(饮食禁忌、特殊需求等)")
private String remark;
@ApiModelProperty("推荐人")
private String referrer;
@ApiModelProperty("推荐人单位")
private String referrerCompany;
}

@ -0,0 +1,45 @@
package com.example.registration.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
@ApiModel("修改报名信息请求")
public class RegistrationUpdateRequest {
@ApiModelProperty("姓名(真实姓名)")
private String name;
@ApiModelProperty("所在单位(学校/企业全称)")
private String company;
@ApiModelProperty("所在部门(院系/部门名称)")
private String department;
@ApiModelProperty("职务/职称")
private String title;
@ApiModelProperty("手机号码")
private String phone;
@ApiModelProperty("电子邮箱")
private String email;
@ApiModelProperty("身份证号")
private String idCard;
@ApiModelProperty("报名期次")
private String period;
@ApiModelProperty("培训费用(元)")
private BigDecimal fee;
@ApiModelProperty("备注说明(饮食禁忌、特殊需求等)")
private String remark;
@ApiModelProperty("状态")
private String status;
}

@ -0,0 +1,18 @@
package com.example.registration.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
@Data
@ApiModel("发送短信请求")
public class SmsRequest {
@NotBlank(message = "手机号不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
@ApiModelProperty("手机号码")
private String phone;
}

@ -0,0 +1,24 @@
package com.example.registration.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
@ApiModel("统计信息响应")
public class StatisticsResponse {
@ApiModelProperty("报名人数")
private Long totalRegistrations;
@ApiModelProperty("已支付人数")
private Long paidRegistrations;
@ApiModelProperty("待支付人数")
private Long pendingPaymentRegistrations;
@ApiModelProperty("已支付金额")
private BigDecimal paidAmount;
}

@ -0,0 +1,90 @@
package com.example.registration.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("training_registration")
@ApiModel("培训报名表")
public class TrainingRegistration {
@TableId(type = IdType.AUTO)
@ApiModelProperty("自增主键ID")
private Long id;
@ApiModelProperty("报名编号")
private String bmId;
@ApiModelProperty("随机唯一编号")
private String randomCode;
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("所在单位")
private String company;
@ApiModelProperty("所在部门")
private String department;
@ApiModelProperty("职务/职称")
private String title;
@ApiModelProperty("手机号码")
private String phone;
@ApiModelProperty("电子邮箱")
private String email;
@ApiModelProperty("身份证号")
private String idCard;
@ApiModelProperty("报名期次")
private String period;
@ApiModelProperty("培训费用")
private BigDecimal fee;
@ApiModelProperty("备注说明")
private String remark;
@ApiModelProperty("短信验证码")
private String smsCode;
@ApiModelProperty("短信是否已验证")
private Boolean isVerified;
@ApiModelProperty("验证时间")
private LocalDateTime verificationTime;
@ApiModelProperty("状态")
private String status;
@ApiModelProperty("提交时间")
private LocalDateTime submitTime;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("创建IP")
private String createIp;
@ApiModelProperty("创建用户代理")
private String createUserAgent;
@ApiModelProperty("推荐人")
private String referrer;
@ApiModelProperty("推荐人单位")
private String referrerCompany;
}

@ -0,0 +1,95 @@
package com.example.registration.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.TableLogic;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("user")
@ApiModel("用户")
public class User {
@TableId(type = IdType.AUTO)
@ApiModelProperty("用户ID")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("手机号")
private String phone;
@ApiModelProperty("密码哈希值")
private String passwordHash;
@ApiModelProperty("密码盐值")
private String passwordSalt;
@ApiModelProperty("真实姓名")
private String realName;
@ApiModelProperty("昵称")
private String nickname;
@ApiModelProperty("头像URL")
private String avatar;
@ApiModelProperty("性别")
private String gender;
@ApiModelProperty("出生日期")
private LocalDate birthday;
@ApiModelProperty("账号状态")
private String status;
@ApiModelProperty("邮箱是否已验证")
private Boolean emailVerified;
@ApiModelProperty("手机号是否已验证")
private Boolean phoneVerified;
@ApiModelProperty("最后登录IP")
private String lastLoginIp;
@ApiModelProperty("最后登录时间")
private LocalDateTime lastLoginTime;
@ApiModelProperty("登录次数")
private Integer loginCount;
@ApiModelProperty("连续登录失败次数")
private Integer failedLoginAttempts;
@ApiModelProperty("账号锁定至")
private LocalDateTime accountLockedUntil;
@ApiModelProperty("密码最后修改时间")
private LocalDateTime passwordChangedAt;
@ApiModelProperty("密码过期时间")
private LocalDateTime passwordExpiresAt;
@ApiModelProperty("用户角色")
private String role;
@ApiModelProperty("创建时间")
private LocalDateTime createdAt;
@ApiModelProperty("更新时间")
private LocalDateTime updatedAt;
@ApiModelProperty("删除时间")
@TableLogic(value = "null", delval = "now()")
private LocalDateTime deletedAt;
}

@ -0,0 +1,28 @@
package com.example.registration.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.registration.entity.TrainingRegistration;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.math.BigDecimal;
@Mapper
public interface TrainingRegistrationMapper extends BaseMapper<TrainingRegistration> {
@Select("SELECT COUNT(*) FROM training_registration WHERE status != 'cancelled' AND status != 'draft'")
Long countTotalRegistrations();
@Select("SELECT COUNT(*) FROM training_registration WHERE status = 'paid'")
Long countPaidRegistrations();
@Select("SELECT COUNT(*) FROM training_registration WHERE status = 'submitted'")
Long countPendingPaymentRegistrations();
@Select("SELECT COALESCE(SUM(fee), 0) FROM training_registration WHERE status = 'paid'")
BigDecimal sumPaidAmount();
@Select("SELECT * FROM training_registration WHERE random_code = #{randomCode} LIMIT 1")
TrainingRegistration selectByRandomCode(@Param("randomCode") String randomCode);
}

@ -0,0 +1,14 @@
package com.example.registration.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.registration.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM user WHERE username = #{username} LIMIT 1")
User selectByUsername(@Param("username") String username);
}

@ -0,0 +1,20 @@
package com.example.registration.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.registration.entity.TrainingRegistration;
import java.math.BigDecimal;
import java.util.Map;
public interface RegistrationService extends IService<TrainingRegistration> {
TrainingRegistration submit(TrainingRegistration registration);
IPage<TrainingRegistration> listPage(Page<TrainingRegistration> page, String keyword, String status, String period);
TrainingRegistration updateByRandomCode(String randomCode, TrainingRegistration registration);
Map<String, Object> statistics();
}

@ -0,0 +1,19 @@
package com.example.registration.service;
public interface SmsService {
/**
*
* @param phone
* @return
*/
boolean sendCode(String phone);
/**
*
* @param phone
* @param code
* @return
*/
boolean verifyCode(String phone, String code);
}

@ -0,0 +1,9 @@
package com.example.registration.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.registration.entity.User;
public interface UserService extends IService<User> {
User login(String username, String password);
}

@ -0,0 +1,105 @@
package com.example.registration.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.registration.entity.TrainingRegistration;
import com.example.registration.mapper.TrainingRegistrationMapper;
import com.example.registration.service.RegistrationService;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service
public class RegistrationServiceImpl extends ServiceImpl<TrainingRegistrationMapper, TrainingRegistration> implements RegistrationService {
@Override
public TrainingRegistration submit(TrainingRegistration registration) {
// 生成报名编号 BMYYYYMMxxxx
String bmId = generateBmId();
registration.setBmId(bmId);
// 生成随机唯一编号
registration.setRandomCode(UUID.randomUUID().toString().replace("-", ""));
// 默认状态为已提交
if (StringUtils.isBlank(registration.getStatus())) {
registration.setStatus("submitted");
}
registration.setSubmitTime(LocalDateTime.now());
baseMapper.insert(registration);
return registration;
}
@Override
public IPage<TrainingRegistration> listPage(Page<TrainingRegistration> page, String keyword, String status, String period) {
LambdaQueryWrapper<TrainingRegistration> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(keyword)) {
wrapper.and(w -> w.like(TrainingRegistration::getName, keyword)
.or().like(TrainingRegistration::getPhone, keyword)
.or().like(TrainingRegistration::getCompany, keyword)
.or().like(TrainingRegistration::getBmId, keyword));
}
if (StringUtils.isNotBlank(status)) {
wrapper.eq(TrainingRegistration::getStatus, status);
}
if (StringUtils.isNotBlank(period)) {
wrapper.eq(TrainingRegistration::getPeriod, period);
}
wrapper.orderByDesc(TrainingRegistration::getCreateTime);
return baseMapper.selectPage(page, wrapper);
}
@Override
public TrainingRegistration updateByRandomCode(String randomCode, TrainingRegistration registration) {
TrainingRegistration existing = baseMapper.selectByRandomCode(randomCode);
if (existing == null) {
return null;
}
registration.setId(existing.getId());
registration.setBmId(null);
registration.setRandomCode(null);
registration.setCreateTime(null);
baseMapper.updateById(registration);
return baseMapper.selectById(existing.getId());
}
@Override
public Map<String, Object> statistics() {
Map<String, Object> result = new HashMap<>();
Long total = baseMapper.countTotalRegistrations();
Long paid = baseMapper.countPaidRegistrations();
Long pending = baseMapper.countPendingPaymentRegistrations();
BigDecimal paidAmount = baseMapper.sumPaidAmount();
result.put("totalRegistrations", total);
result.put("paidRegistrations", paid);
result.put("pendingPaymentRegistrations", pending);
result.put("paidAmount", paidAmount);
return result;
}
private String generateBmId() {
String prefix = "BM" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
// 查询当月最大编号
LambdaQueryWrapper<TrainingRegistration> wrapper = new LambdaQueryWrapper<>();
wrapper.likeRight(TrainingRegistration::getBmId, prefix);
wrapper.orderByDesc(TrainingRegistration::getBmId);
wrapper.last("LIMIT 1");
TrainingRegistration last = baseMapper.selectOne(wrapper);
int seq = 1;
if (last != null && last.getBmId() != null) {
try {
String seqStr = last.getBmId().substring(prefix.length());
seq = Integer.parseInt(seqStr) + 1;
} catch (NumberFormatException e) {
seq = 1;
}
}
return prefix + String.format("%04d", seq);
}
}

@ -0,0 +1,114 @@
package com.example.registration.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.teaopenapi.models.Config;
import com.example.registration.service.SmsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class SmsServiceImpl implements SmsService {
private static final String SMS_CODE_PREFIX = "sms:code:";
private static final int CODE_EXPIRE_MINUTES = 5;
private static final int CODE_LENGTH = 6;
private final StringRedisTemplate redisTemplate;
private Client client;
private final String signName;
private final String templateCode;
public SmsServiceImpl(StringRedisTemplate redisTemplate,
@Value("${aliyun.sms.access-key-id}") String accessKeyId,
@Value("${aliyun.sms.access-key-secret}") String accessKeySecret,
@Value("${aliyun.sms.sign-name}") String signName,
@Value("${aliyun.sms.template-code}") String templateCode) {
this.redisTemplate = redisTemplate;
this.signName = signName;
this.templateCode = templateCode;
try {
Config config = new Config()
.setAccessKeyId(accessKeyId)
.setAccessKeySecret(accessKeySecret)
.setEndpoint("dysmsapi.aliyuncs.com");
this.client = new Client(config);
} catch (Exception e) {
log.error("初始化阿里云短信客户端失败", e);
}
}
@Override
public boolean sendCode(String phone) {
String code = generateCode();
String redisKey = SMS_CODE_PREFIX + phone;
try {
// 发送短信
SendSmsRequest request = new SendSmsRequest()
.setPhoneNumbers(phone)
.setSignName(signName)
.setTemplateCode(templateCode)
.setTemplateParam(JSONObject.toJSONString(java.util.Collections.singletonMap("code", code)));
if (client != null) {
SendSmsResponse response = client.sendSms(request);
log.info("短信发送响应: code={}, message={}", response.getBody().getCode(), response.getBody().getMessage());
if ("OK".equals(response.getBody().getCode())) {
// 将验证码存入Redis
redisTemplate.opsForValue().set(redisKey, code, CODE_EXPIRE_MINUTES, TimeUnit.MINUTES);
return true;
}
} else {
// 短信客户端未初始化时,使用模拟模式(仅用于开发测试)
log.warn("短信客户端未初始化,使用模拟验证码: {}", code);
redisTemplate.opsForValue().set(redisKey, code, CODE_EXPIRE_MINUTES, TimeUnit.MINUTES);
return true;
}
} catch (Exception e) {
log.error("发送短信验证码失败phone={}", phone, e);
// 开发环境下模拟成功
redisTemplate.opsForValue().set(redisKey, code, CODE_EXPIRE_MINUTES, TimeUnit.MINUTES);
log.info("开发环境模拟短信验证码: phone={}, code={}", phone, code);
return true;
}
return false;
}
@Override
public boolean verifyCode(String phone, String code) {
String redisKey = SMS_CODE_PREFIX + phone;
String cachedCode = redisTemplate.opsForValue().get(redisKey);
if (cachedCode == null) {
log.warn("验证码已过期或不存在phone={}", phone);
return false;
}
if (cachedCode.equals(code)) {
// 验证成功后删除验证码
redisTemplate.delete(redisKey);
return true;
}
log.warn("验证码不匹配phone={}, input={}, cached={}", phone, code, cachedCode);
return false;
}
private String generateCode() {
Random random = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < CODE_LENGTH; i++) {
code.append(random.nextInt(10));
}
return code.toString();
}
}

@ -0,0 +1,38 @@
package com.example.registration.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.registration.entity.User;
import com.example.registration.mapper.UserMapper;
import com.example.registration.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public User login(String username, String password) {
User user = baseMapper.selectByUsername(username);
if (user == null) {
return null;
}
if (!"active".equals(user.getStatus())) {
return null;
}
String hashedPassword = DigestUtils.md5DigestAsHex(
(password + user.getPasswordSalt()).getBytes(StandardCharsets.UTF_8)
);
if (!hashedPassword.equals(user.getPasswordHash())) {
return null;
}
// 更新登录信息
user.setLastLoginTime(LocalDateTime.now());
user.setLoginCount(user.getLoginCount() == null ? 1 : user.getLoginCount() + 1);
user.setFailedLoginAttempts(0);
baseMapper.updateById(user);
return user;
}
}

@ -0,0 +1,45 @@
package com.example.registration.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisTokenUtil {
private static final String TOKEN_PREFIX = "token:";
private static final long TOKEN_EXPIRE_MINUTES = 30;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void storeToken(String token, String userId) {
String key = TOKEN_PREFIX + token;
redisTemplate.opsForValue().set(key, userId, TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES);
}
public boolean validateToken(String token) {
String key = TOKEN_PREFIX + token;
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
public String getUserId(String token) {
String key = TOKEN_PREFIX + token;
Object value = redisTemplate.opsForValue().get(key);
return value != null ? value.toString() : null;
}
public void refreshToken(String token) {
String key = TOKEN_PREFIX + token;
if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
redisTemplate.expire(key, TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES);
}
}
public void deleteToken(String token) {
String key = TOKEN_PREFIX + token;
redisTemplate.delete(key);
}
}

@ -0,0 +1,45 @@
package com.example.registration.util;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("通用响应")
public class Result<T> {
@ApiModelProperty("状态码 200成功")
private Integer code;
@ApiModelProperty("提示信息")
private String message;
@ApiModelProperty("响应数据")
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
public static <T> Result<T> error(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
}

@ -0,0 +1,54 @@
server:
port: 8099
servlet:
context-path: /
spring:
application:
name: registration
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ngsk.tech:3307/registration?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: ngsk0809
schema: classpath:data.sql
initialization-mode: always
redis:
host: ngsk.tech
port: 6389
password: ngsk0809
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
type-aliases-package: com.example.registration.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deletedAt
logic-delete-value: "NOW()"
logic-not-delete-value: "NULL"
knife4j:
enable: true
setting:
language: zh_cn
# 阿里云短信配置
aliyun:
sms:
access-key-id: LTAI5t7peh76HpuVGhpXabPb
access-key-secret: Y119TqVZeaU7LcYRgbIHLpmscteQnw
sign-name: 培训报名成功模板
template-code: SMS_506365455

@ -0,0 +1,54 @@
server:
port: 8099
servlet:
context-path: /
spring:
application:
name: registration
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ngsk.tech:3307/registration?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: ngsk0809
schema: classpath:data.sql
initialization-mode: always
redis:
host: ngsk.tech
port: 6389
password: ngsk0809
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
type-aliases-package: com.example.registration.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deletedAt
logic-delete-value: "NOW()"
logic-not-delete-value: "NULL"
knife4j:
enable: true
setting:
language: zh_cn
# 阿里云短信配置
aliyun:
sms:
access-key-id: LTAI5t7peh76HpuVGhpXabPb
access-key-secret: Y119TqVZeaU7LcYRgbIHLpmscteQnw
sign-name: 培训报名成功模板
template-code: SMS_506365455

@ -0,0 +1,54 @@
server:
port: 8099
servlet:
context-path: /
spring:
application:
name: registration
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ngsk.tech:3307/registration?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: ngsk0809
schema: classpath:data.sql
initialization-mode: always
redis:
host: ngsk.tech
port: 6389
password: ngsk0809
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
type-aliases-package: com.example.registration.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deletedAt
logic-delete-value: "NOW()"
logic-not-delete-value: "NULL"
knife4j:
enable: true
setting:
language: zh_cn
# 阿里云短信配置
aliyun:
sms:
access-key-id: LTAI5t7peh76HpuVGhpXabPb
access-key-secret: Y119TqVZeaU7LcYRgbIHLpmscteQnw
sign-name: 培训报名成功模板
template-code: SMS_506365455

@ -0,0 +1,3 @@
spring:
profiles:
active: pro

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATH" value="logs"/>
<property name="LOG_FILE" value="registration"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- 错误日志文件 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE}-error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>60</maxHistory>
</rollingPolicy>
</appender>
<!-- 开发环境配置 -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.example.registration" level="DEBUG"/>
</springProfile>
<!-- 测试环境配置 -->
<springProfile name="test">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.example.registration" level="INFO"/>
</springProfile>
<!-- 生产环境配置 -->
<springProfile name="prod">
<root level="WARN">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<logger name="com.example.registration" level="INFO"/>
</springProfile>
</configuration>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.registration.mapper.TrainingRegistrationMapper">
</mapper>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.registration.mapper.UserMapper">
</mapper>
Loading…
Cancel
Save