新增后台管理系统(答辩用

This commit is contained in:
2026-06-24 05:07:32 +08:00
parent 2251f31524
commit 1f209e6761
98 changed files with 25099 additions and 0 deletions
@@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
+114
View File
@@ -0,0 +1,114 @@
<?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>
<parent>
<groupId>cn.novalon.gym.manage</groupId>
<artifactId>gym-manage-api</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>cn.novalon.gym.manage</groupId>
<artifactId>gym-manage-cuit</artifactId>
<version>1.0.0</version>
<name>gym-manage-cuit</name>
<description>Auth Management Module</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>cn.novalon.gym.manage</groupId>
<artifactId>manage-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.novalon.gym.manage</groupId>
<artifactId>manage-sys</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.novalon.gym.manage</groupId>
<artifactId>manage-db</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
<version>2.2.43</version>
<scope>compile</scope>
</dependency>
<!-- Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,63 @@
package cn.novalon.gym.manage.auth.converter;
import cn.novalon.gym.manage.auth.domain.User;
import cn.novalon.gym.manage.auth.dto.UserInfo;
import cn.novalon.gym.manage.auth.entity.UserEntity;
public class UserConverter {
public static User toDomain(UserEntity entity) {
if (entity == null) {
return null;
}
User user = new User();
user.setId(entity.getId());
user.setUsername(entity.getUsername());
user.setEmail(entity.getEmail());
user.setPhone(entity.getPhone());
user.setNickname(entity.getNickname());
user.setStatus(entity.getStatus());
user.setRoleId(entity.getRoleId());
user.setCreateBy(entity.getCreateBy());
user.setUpdateBy(entity.getUpdateBy());
user.setCreatedAt(entity.getCreatedAt());
user.setUpdatedAt(entity.getUpdatedAt());
user.setDeletedAt(entity.getDeletedAt());
return user;
}
public static UserEntity toEntity(User domain) {
if (domain == null) {
return null;
}
UserEntity entity = new UserEntity();
entity.setId(domain.getId());
entity.setUsername(domain.getUsername());
entity.setEmail(domain.getEmail());
entity.setPhone(domain.getPhone());
entity.setNickname(domain.getNickname());
entity.setStatus(domain.getStatus());
entity.setRoleId(domain.getRoleId());
entity.setCreateBy(domain.getCreateBy());
entity.setUpdateBy(domain.getUpdateBy());
entity.setCreatedAt(domain.getCreatedAt());
entity.setUpdatedAt(domain.getUpdatedAt());
entity.setDeletedAt(domain.getDeletedAt());
return entity;
}
public static UserInfo toUserInfo(User user) {
if (user == null) {
return null;
}
UserInfo userInfo = new UserInfo();
userInfo.setId(user.getId());
userInfo.setUsername(user.getUsername());
userInfo.setEmail(user.getEmail());
userInfo.setPhone(user.getPhone());
userInfo.setNickname(user.getNickname());
userInfo.setStatus(user.getStatus());
userInfo.setRoleId(user.getRoleId());
return userInfo;
}
}
@@ -0,0 +1,17 @@
package cn.novalon.gym.manage.auth.dao;
import cn.novalon.gym.manage.auth.entity.UserEntity;
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.data.r2dbc.repository.R2dbcRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;
@Repository
public interface UserDao extends R2dbcRepository<UserEntity, Long> {
@Query("SELECT * FROM sys_user WHERE username = :username AND deleted_at IS NULL")
Mono<UserEntity> findByUsername(String username);
@Query("SELECT * FROM sys_user WHERE id = :id AND deleted_at IS NULL")
Mono<UserEntity> findByIdAndDeletedAtIsNull(Long id);
}
@@ -0,0 +1,73 @@
package cn.novalon.gym.manage.auth.domain;
import cn.novalon.gym.manage.sys.core.domain.BaseDomain;
import io.swagger.v3.oas.annotations.media.Schema;
public class User extends BaseDomain {
@Schema(description = "用户名", example = "admin")
private String username;
@Schema(description = "邮箱", example = "admin@example.com")
private String email;
@Schema(description = "手机号", example = "13800138000")
private String phone;
@Schema(description = "昵称", example = "超级管理员")
private String nickname;
@Schema(description = "状态:0-禁用,1-正常", example = "1")
private Integer status;
@Schema(description = "角色ID", example = "1")
private Long roleId;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
}
@@ -0,0 +1,32 @@
package cn.novalon.gym.manage.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
@Schema(description = "用户登录请求")
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
@Schema(description = "用户名", example = "admin", required = true)
private String username;
@NotBlank(message = "密码不能为空")
@Schema(description = "密码", example = "Test@123", required = true)
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
@@ -0,0 +1,62 @@
package cn.novalon.gym.manage.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "用户登录响应")
public class LoginResponse {
@Schema(description = "访问令牌")
private String accessToken;
@Schema(description = "刷新令牌")
private String refreshToken;
@Schema(description = "令牌类型", example = "Bearer")
private String tokenType;
@Schema(description = "过期时间(秒)", example = "7200")
private Long expiresIn;
@Schema(description = "用户信息")
private UserInfo userInfo;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public Long getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(Long expiresIn) {
this.expiresIn = expiresIn;
}
public UserInfo getUserInfo() {
return userInfo;
}
public void setUserInfo(UserInfo userInfo) {
this.userInfo = userInfo;
}
}
@@ -0,0 +1,84 @@
package cn.novalon.gym.manage.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "用户信息")
public class UserInfo {
@Schema(description = "用户ID", example = "1")
private Long id;
@Schema(description = "用户名", example = "admin")
private String username;
@Schema(description = "邮箱", example = "admin@example.com")
private String email;
@Schema(description = "手机号", example = "13800138000")
private String phone;
@Schema(description = "昵称", example = "超级管理员")
private String nickname;
@Schema(description = "状态:0-禁用,1-正常", example = "1")
private Integer status;
@Schema(description = "角色ID", example = "1")
private Long roleId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
}
@@ -0,0 +1,86 @@
package cn.novalon.gym.manage.auth.entity;
import cn.novalon.gym.manage.db.entity.BaseEntity;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
@Table("sys_user")
public class UserEntity extends BaseEntity {
@Column("username")
private String username;
@Column("password")
private String password;
@Column("email")
private String email;
@Column("phone")
private String phone;
@Column("nickname")
private String nickname;
@Column("status")
private Integer status;
@Column("role_id")
private Long roleId;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
}
@@ -0,0 +1,83 @@
package cn.novalon.gym.manage.auth.handler;
import cn.novalon.gym.manage.auth.dto.LoginRequest;
import cn.novalon.gym.manage.auth.dto.LoginResponse;
import cn.novalon.gym.manage.auth.dto.UserInfo;
import cn.novalon.gym.manage.auth.service.IAuthService;
import cn.novalon.gym.manage.auth.util.ResponseCode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Validator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
@Component
@Tag(name = "认证管理", description = "用户认证相关操作")
public class AuthHandler {
private final IAuthService authService;
private final Validator validator;
public AuthHandler(IAuthService authService, Validator validator) {
this.authService = authService;
this.validator = validator;
}
@Operation(summary = "用户名+密码登录", description = "使用用户名和密码进行登录认证")
public Mono<ServerResponse> login(ServerRequest request) {
return request.bodyToMono(LoginRequest.class)
.flatMap(loginRequest -> {
return authService.login(loginRequest)
.flatMap(response -> {
Map<String, Object> result = new HashMap<>();
result.put("code", ResponseCode.SUCCESS);
result.put("message", "登录成功");
result.put("data", response);
return ServerResponse.ok().bodyValue(result);
})
.onErrorResume(error -> {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("code", ResponseCode.BAD_REQUEST);
errorResponse.put("message", error.getMessage());
return ServerResponse.badRequest().bodyValue(errorResponse);
});
})
.onErrorResume(error -> {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("code", ResponseCode.BAD_REQUEST);
errorResponse.put("message", "请求参数错误: " + error.getMessage());
return ServerResponse.badRequest().bodyValue(errorResponse);
});
}
@Operation(summary = "获取用户信息", description = "根据用户ID获取用户详细信息")
public Mono<ServerResponse> getUserInfo(ServerRequest request) {
try {
Long userId = Long.valueOf(request.pathVariable("id"));
return authService.getUserInfo(userId)
.flatMap(userInfo -> {
Map<String, Object> result = new HashMap<>();
result.put("code", ResponseCode.SUCCESS);
result.put("message", "获取用户信息成功");
result.put("data", userInfo);
return ServerResponse.ok().bodyValue(result);
})
.onErrorResume(error -> {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("code", ResponseCode.BAD_REQUEST);
errorResponse.put("message", error.getMessage());
return ServerResponse.badRequest().bodyValue(errorResponse);
});
} catch (NumberFormatException e) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("code", ResponseCode.BAD_REQUEST);
errorResponse.put("message", "无效的用户ID");
return ServerResponse.badRequest().bodyValue(errorResponse);
}
}
}
@@ -0,0 +1,14 @@
package cn.novalon.gym.manage.auth.repository;
import cn.novalon.gym.manage.auth.domain.User;
import cn.novalon.gym.manage.auth.dto.LoginRequest;
import reactor.core.publisher.Mono;
public interface AuthRepository {
Mono<User> findByUsername(String username);
Mono<User> findById(Long id);
Mono<User> validateLogin(LoginRequest request);
}
@@ -0,0 +1,50 @@
package cn.novalon.gym.manage.auth.repository.impl;
import cn.novalon.gym.manage.auth.converter.UserConverter;
import cn.novalon.gym.manage.auth.dao.UserDao;
import cn.novalon.gym.manage.auth.domain.User;
import cn.novalon.gym.manage.auth.dto.LoginRequest;
import cn.novalon.gym.manage.auth.repository.AuthRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class AuthRepositoryImpl implements AuthRepository {
private final UserDao userDao;
private final PasswordEncoder passwordEncoder;
public AuthRepositoryImpl(UserDao userDao, PasswordEncoder passwordEncoder) {
this.userDao = userDao;
this.passwordEncoder = passwordEncoder;
}
@Override
public Mono<User> findByUsername(String username) {
return userDao.findByUsername(username)
.map(UserConverter::toDomain);
}
@Override
public Mono<User> findById(Long id) {
return userDao.findByIdAndDeletedAtIsNull(id)
.map(UserConverter::toDomain);
}
@Override
public Mono<User> validateLogin(LoginRequest request) {
return userDao.findByUsername(request.getUsername())
.flatMap(userEntity -> {
if (userEntity.getStatus() == 0) {
return Mono.error(new RuntimeException("用户已被禁用"));
}
if (passwordEncoder.matches(request.getPassword(), userEntity.getPassword())) {
return Mono.just(UserConverter.toDomain(userEntity));
} else {
return Mono.error(new RuntimeException("用户名或密码错误"));
}
})
.switchIfEmpty(Mono.error(new RuntimeException("用户名或密码错误")));
}
}
@@ -0,0 +1,13 @@
package cn.novalon.gym.manage.auth.service;
import cn.novalon.gym.manage.auth.dto.LoginRequest;
import cn.novalon.gym.manage.auth.dto.LoginResponse;
import cn.novalon.gym.manage.auth.dto.UserInfo;
import reactor.core.publisher.Mono;
public interface IAuthService {
Mono<LoginResponse> login(LoginRequest request);
Mono<UserInfo> getUserInfo(Long userId);
}
@@ -0,0 +1,48 @@
package cn.novalon.gym.manage.auth.service.impl;
import cn.novalon.gym.manage.auth.converter.UserConverter;
import cn.novalon.gym.manage.auth.dto.LoginRequest;
import cn.novalon.gym.manage.auth.dto.LoginResponse;
import cn.novalon.gym.manage.auth.dto.UserInfo;
import cn.novalon.gym.manage.auth.repository.AuthRepository;
import cn.novalon.gym.manage.auth.service.IAuthService;
import cn.novalon.gym.manage.auth.util.JwtUtil;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class AuthServiceImpl implements IAuthService {
private final AuthRepository authRepository;
private final JwtUtil jwtUtil;
public AuthServiceImpl(AuthRepository authRepository, JwtUtil jwtUtil) {
this.authRepository = authRepository;
this.jwtUtil = jwtUtil;
}
@Override
public Mono<LoginResponse> login(LoginRequest request) {
return authRepository.validateLogin(request)
.flatMap(user -> {
String accessToken = jwtUtil.generateToken(user.getId(), user.getUsername());
String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername());
LoginResponse response = new LoginResponse();
response.setAccessToken(accessToken);
response.setRefreshToken(refreshToken);
response.setTokenType("Bearer");
response.setExpiresIn(7200L);
response.setUserInfo(UserConverter.toUserInfo(user));
return Mono.just(response);
});
}
@Override
public Mono<UserInfo> getUserInfo(Long userId) {
return authRepository.findById(userId)
.map(UserConverter::toUserInfo)
.switchIfEmpty(Mono.error(new RuntimeException("用户不存在")));
}
}
@@ -0,0 +1,80 @@
package cn.novalon.gym.manage.auth.util;
import cn.novalon.gym.manage.common.config.JwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JwtUtil {
private final JwtProperties jwtProperties;
public JwtUtil(JwtProperties jwtProperties) {
this.jwtProperties = jwtProperties;
}
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8));
}
public String generateToken(Long userId, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration()))
.signWith(getSigningKey())
.compact();
}
public String generateRefreshToken(Long userId, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
return Jwts.builder()
.setClaims(claims)
.setSubject(username + "_refresh")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration() * 7))
.signWith(getSigningKey())
.compact();
}
public Claims parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public Long getUserIdFromToken(String token) {
return parseToken(token).get("userId", Long.class);
}
public String getUsernameFromToken(String token) {
return parseToken(token).getSubject();
}
public boolean validateToken(String token) {
try {
parseToken(token);
return true;
} catch (Exception e) {
return false;
}
}
}
@@ -0,0 +1,34 @@
package cn.novalon.gym.manage.auth.util;
/**
* HTTP 响应状态码常量类
*
* @author 张翔
* @date 2026-06-16
*/
public class ResponseCode {
private ResponseCode() {
}
/** 成功 */
public static final int SUCCESS = 200;
/** 客户端错误:请求参数有误 */
public static final int BAD_REQUEST = 400;
/** 客户端错误:未授权 */
public static final int UNAUTHORIZED = 401;
/** 客户端错误:禁止访问 */
public static final int FORBIDDEN = 403;
/** 客户端错误:资源未找到 */
public static final int NOT_FOUND = 404;
/** 客户端错误:冲突(如重复数据) */
public static final int CONFLICT = 409;
/** 服务器错误:内部服务器错误 */
public static final int INTERNAL_SERVER_ERROR = 500;
}
@@ -0,0 +1,16 @@
# JWT Configuration
jwt:
secret: mySecretKeyForJwtTokenGenerationThatIsLongEnoughForHS256Algorithm
expiration: 7200 # 2 hours in seconds
refresh-expiration: 604800 # 7 days in seconds
# Server Configuration
server:
port: 8080
# Spring Configuration
spring:
application:
name: gym-manage-cuit
profiles:
active: dev
@@ -0,0 +1,15 @@
package cn.novalon.gym.manage.cuit.gymmanagecuit;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Disabled("模块无独立SpringBootApplication,需通过manage-app运行集成测试")
class GymManageCuitApplicationTests {
@Test
void contextLoads() {
}
}