feat: 完善后端架构和service层实现

- 创建完整的entity实体类体系,包括所有业务实体
- 实现BaseEntity基类,统一管理公共字段
- 创建雪花算法ID生成器和自动填充处理器
- 简化所有mapper接口,只继承BaseMapper
- 重构service层,使用LambdaQueryWrapper进行数据库操作
- 创建BasePageRequest分页查询基类
- 完善用户上下文管理和JWT认证
- 新增WebSocket聊天功能和相关控制器
- 更新前端配置和组件,完善用户认证流程
- 同步数据库建表脚本
This commit is contained in:
2025-07-24 00:37:23 +08:00
parent 645036fcd2
commit 880e0e3c88
87 changed files with 8114 additions and 1106 deletions
@@ -0,0 +1,203 @@
package com.emotion.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
/**
* JWT工具类
*
* @author emotion-museum
* @date 2025-07-23
*/
@Component
public class JwtUtil {
private static final Logger log = LoggerFactory.getLogger(JwtUtil.class);
/**
* JWT密钥
*/
@Value("${emotion.jwt.secret:emotion-museum-secret-key-2025}")
private String secret;
/**
* JWT过期时间(毫秒)
*/
@Value("${emotion.jwt.expiration:86400000}")
private Long expiration;
/**
* 刷新Token过期时间(毫秒)
*/
@Value("${emotion.jwt.refresh-expiration:604800000}")
private Long refreshExpiration;
/**
* 获取密钥
*/
private SecretKey getSecretKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
/**
* 生成Token
*
* @param userId 用户ID
* @param username 用户名
* @return Token
*/
public String generateToken(String userId, String username) {
return generateToken(userId, username, expiration);
}
/**
* 生成刷新Token
*
* @param userId 用户ID
* @param username 用户名
* @return 刷新Token
*/
public String generateRefreshToken(String userId, String username) {
return generateToken(userId, username, refreshExpiration);
}
/**
* 生成Token
*
* @param userId 用户ID
* @param username 用户名
* @param expiration 过期时间(毫秒)
* @return Token
*/
private String generateToken(String userId, String username, Long expiration) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(userId)
.claim("username", username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(getSecretKey(), SignatureAlgorithm.HS512)
.compact();
}
/**
* 从Token中获取用户ID
*
* @param token Token
* @return 用户ID
*/
public String getUserIdFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.getSubject() : null;
}
/**
* 从Token中获取用户名
*
* @param token Token
* @return 用户名
*/
public String getUsernameFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.get("username", String.class) : null;
}
/**
* 从Token中获取过期时间
*
* @param token Token
* @return 过期时间
*/
public Date getExpirationDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.getExpiration() : null;
}
/**
* 从Token中获取Claims
*
* @param token Token
* @return Claims
*/
private Claims getClaimsFromToken(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(getSecretKey())
.build()
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
log.warn("解析Token失败: {}", e.getMessage());
return null;
}
}
/**
* 验证Token是否有效
*
* @param token Token
* @return 是否有效
*/
public boolean validateToken(String token) {
if (token == null || token.trim().isEmpty()) {
return false;
}
try {
Claims claims = getClaimsFromToken(token);
if (claims == null) {
return false;
}
// 检查是否过期
Date expiration = claims.getExpiration();
return expiration != null && expiration.after(new Date());
} catch (Exception e) {
log.warn("Token验证失败: {}", e.getMessage());
return false;
}
}
/**
* 检查Token是否过期
*
* @param token Token
* @return 是否过期
*/
public boolean isTokenExpired(String token) {
Date expiration = getExpirationDateFromToken(token);
return expiration != null && expiration.before(new Date());
}
/**
* 刷新Token
*
* @param token 原Token
* @return 新Token
*/
public String refreshToken(String token) {
try {
Claims claims = getClaimsFromToken(token);
if (claims == null) {
return null;
}
String userId = claims.getSubject();
String username = claims.get("username", String.class);
return generateToken(userId, username);
} catch (Exception e) {
log.warn("刷新Token失败: {}", e.getMessage());
return null;
}
}
}
@@ -0,0 +1,232 @@
package com.emotion.util;
import lombok.extern.slf4j.Slf4j;
/**
* 雪花算法ID生成器
* 生成64位长整型ID,转换为字符串避免前端精度丢失问题
*
* 雪花算法结构:
* 1位符号位(固定为0) + 41位时间戳 + 10位机器ID + 12位序列号
*
* @author emotion-museum
* @since 2025-07-23
*/
@Slf4j
public class SnowflakeIdGenerator {
/**
* 起始时间戳 (2024-01-01 00:00:00)
*/
private static final long START_TIMESTAMP = 1704067200000L;
/**
* 机器ID位数
*/
private static final long MACHINE_ID_BITS = 10L;
/**
* 序列号位数
*/
private static final long SEQUENCE_BITS = 12L;
/**
* 机器ID最大值
*/
private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS);
/**
* 序列号最大值
*/
private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
/**
* 机器ID左移位数
*/
private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
/**
* 时间戳左移位数
*/
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
/**
* 机器ID
*/
private final long machineId;
/**
* 序列号
*/
private long sequence = 0L;
/**
* 上次生成ID的时间戳
*/
private long lastTimestamp = -1L;
/**
* 构造函数
*
* @param machineId 机器ID (0-1023)
*/
public SnowflakeIdGenerator(long machineId) {
if (machineId > MAX_MACHINE_ID || machineId < 0) {
throw new IllegalArgumentException(
String.format("机器ID必须在0到%d之间", MAX_MACHINE_ID));
}
this.machineId = machineId;
log.info("雪花算法ID生成器初始化完成,机器ID: {}", machineId);
}
/**
* 默认构造函数,使用默认机器ID
*/
public SnowflakeIdGenerator() {
// 使用当前时间戳的后10位作为默认机器ID
this(System.currentTimeMillis() % (MAX_MACHINE_ID + 1));
}
/**
* 生成下一个ID
*
* @return 生成的ID
*/
public synchronized long nextId() {
long timestamp = getCurrentTimestamp();
// 如果当前时间小于上次ID生成的时间戳,说明系统时钟回退过,抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("系统时钟回退,拒绝生成ID。当前时间戳: %d, 上次时间戳: %d",
timestamp, lastTimestamp));
}
// 如果是同一时间戳,则在序列号上自增
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
// 如果序列号溢出,则等待下一个毫秒
if (sequence == 0) {
timestamp = getNextTimestamp(lastTimestamp);
}
} else {
// 如果是新的时间戳,则序列号重置为0
sequence = 0L;
}
// 更新上次生成ID的时间戳
lastTimestamp = timestamp;
// 移位并通过或运算拼到一起组成64位的ID
return ((timestamp - START_TIMESTAMP) << TIMESTAMP_SHIFT)
| (machineId << MACHINE_ID_SHIFT)
| sequence;
}
/**
* 生成字符串格式的ID
*
* @return 字符串格式的ID
*/
public String nextIdAsString() {
return String.valueOf(nextId());
}
/**
* 获取当前时间戳
*
* @return 当前时间戳
*/
private long getCurrentTimestamp() {
return System.currentTimeMillis();
}
/**
* 获取下一个时间戳
*
* @param lastTimestamp 上次时间戳
* @return 下一个时间戳
*/
private long getNextTimestamp(long lastTimestamp) {
long timestamp = getCurrentTimestamp();
while (timestamp <= lastTimestamp) {
timestamp = getCurrentTimestamp();
}
return timestamp;
}
/**
* 解析ID获取时间戳
*
* @param id 雪花算法生成的ID
* @return 时间戳
*/
public long parseTimestamp(long id) {
return (id >> TIMESTAMP_SHIFT) + START_TIMESTAMP;
}
/**
* 解析ID获取机器ID
*
* @param id 雪花算法生成的ID
* @return 机器ID
*/
public long parseMachineId(long id) {
return (id >> MACHINE_ID_SHIFT) & MAX_MACHINE_ID;
}
/**
* 解析ID获取序列号
*
* @param id 雪花算法生成的ID
* @return 序列号
*/
public long parseSequence(long id) {
return id & MAX_SEQUENCE;
}
/**
* 获取机器ID
*
* @return 机器ID
*/
public long getMachineId() {
return machineId;
}
/**
* 批量生成ID
*
* @param count 生成数量
* @return ID数组
*/
public long[] nextIds(int count) {
if (count <= 0) {
throw new IllegalArgumentException("生成数量必须大于0");
}
long[] ids = new long[count];
for (int i = 0; i < count; i++) {
ids[i] = nextId();
}
return ids;
}
/**
* 批量生成字符串格式的ID
*
* @param count 生成数量
* @return 字符串ID数组
*/
public String[] nextIdsAsString(int count) {
if (count <= 0) {
throw new IllegalArgumentException("生成数量必须大于0");
}
String[] ids = new String[count];
for (int i = 0; i < count; i++) {
ids[i] = nextIdAsString();
}
return ids;
}
}
@@ -0,0 +1,222 @@
package com.emotion.util;
import lombok.extern.slf4j.Slf4j;
/**
* 用户上下文持有者
* 用于在当前线程中存储用户信息
*
* @author emotion-museum
* @date 2025-07-23
*/
@Slf4j
public class UserContextHolder {
/**
* 用户ID线程本地变量
*/
private static final ThreadLocal<String> USER_ID_HOLDER = new ThreadLocal<>();
/**
* 用户名线程本地变量
*/
private static final ThreadLocal<String> USERNAME_HOLDER = new ThreadLocal<>();
/**
* 用户类型线程本地变量
*/
private static final ThreadLocal<String> USER_TYPE_HOLDER = new ThreadLocal<>();
/**
* 客户端IP线程本地变量
*/
private static final ThreadLocal<String> CLIENT_IP_HOLDER = new ThreadLocal<>();
/**
* 请求ID线程本地变量
*/
private static final ThreadLocal<String> REQUEST_ID_HOLDER = new ThreadLocal<>();
/**
* 设置当前用户ID
*
* @param userId 用户ID
*/
public static void setCurrentUserId(String userId) {
USER_ID_HOLDER.set(userId);
log.debug("设置当前用户ID: {}", userId);
}
/**
* 获取当前用户ID
*
* @return 用户ID
*/
public static String getCurrentUserId() {
return USER_ID_HOLDER.get();
}
/**
* 设置当前用户名
*
* @param username 用户名
*/
public static void setCurrentUsername(String username) {
USERNAME_HOLDER.set(username);
log.debug("设置当前用户名: {}", username);
}
/**
* 获取当前用户名
*
* @return 用户名
*/
public static String getCurrentUsername() {
return USERNAME_HOLDER.get();
}
/**
* 设置当前用户类型
*
* @param userType 用户类型
*/
public static void setCurrentUserType(String userType) {
USER_TYPE_HOLDER.set(userType);
log.debug("设置当前用户类型: {}", userType);
}
/**
* 获取当前用户类型
*
* @return 用户类型
*/
public static String getCurrentUserType() {
return USER_TYPE_HOLDER.get();
}
/**
* 设置客户端IP
*
* @param clientIp 客户端IP
*/
public static void setClientIp(String clientIp) {
CLIENT_IP_HOLDER.set(clientIp);
log.debug("设置客户端IP: {}", clientIp);
}
/**
* 获取客户端IP
*
* @return 客户端IP
*/
public static String getClientIp() {
return CLIENT_IP_HOLDER.get();
}
/**
* 设置请求ID
*
* @param requestId 请求ID
*/
public static void setRequestId(String requestId) {
REQUEST_ID_HOLDER.set(requestId);
log.debug("设置请求ID: {}", requestId);
}
/**
* 获取请求ID
*
* @return 请求ID
*/
public static String getRequestId() {
return REQUEST_ID_HOLDER.get();
}
/**
* 设置用户上下文信息
*
* @param userId 用户ID
* @param username 用户名
* @param userType 用户类型
* @param clientIp 客户端IP
* @param requestId 请求ID
*/
public static void setUserContext(String userId, String username, String userType,
String clientIp, String requestId) {
setCurrentUserId(userId);
setCurrentUsername(username);
setCurrentUserType(userType);
setClientIp(clientIp);
setRequestId(requestId);
log.debug("设置用户上下文: userId={}, username={}, userType={}, clientIp={}, requestId={}",
userId, username, userType, clientIp, requestId);
}
/**
* 清除当前用户ID
*/
public static void clearUserId() {
USER_ID_HOLDER.remove();
}
/**
* 清除当前用户名
*/
public static void clearUsername() {
USERNAME_HOLDER.remove();
}
/**
* 清除当前用户类型
*/
public static void clearUserType() {
USER_TYPE_HOLDER.remove();
}
/**
* 清除客户端IP
*/
public static void clearClientIp() {
CLIENT_IP_HOLDER.remove();
}
/**
* 清除请求ID
*/
public static void clearRequestId() {
REQUEST_ID_HOLDER.remove();
}
/**
* 清除所有用户上下文信息
*/
public static void clear() {
clearUserId();
clearUsername();
clearUserType();
clearClientIp();
clearRequestId();
log.debug("清除所有用户上下文信息");
}
/**
* 获取当前用户上下文摘要信息
*
* @return 用户上下文摘要
*/
public static String getContextSummary() {
return String.format("UserContext[userId=%s, username=%s, userType=%s, clientIp=%s, requestId=%s]",
getCurrentUserId(), getCurrentUsername(), getCurrentUserType(),
getClientIp(), getRequestId());
}
/**
* 检查是否有用户上下文
*
* @return 是否有用户上下文
*/
public static boolean hasUserContext() {
return getCurrentUserId() != null || getCurrentUsername() != null;
}
}
@@ -0,0 +1,214 @@
package com.emotion.util;
import lombok.extern.slf4j.Slf4j;
/**
* 用户上下文工具类
* 提供便捷的用户上下文操作方法
*
* @author emotion-museum
* @date 2025-07-23
*/
@Slf4j
public class UserContextUtils {
/**
* 获取当前用户ID,如果为空则返回默认值
*
* @param defaultValue 默认值
* @return 用户ID
*/
public static String getCurrentUserIdOrDefault(String defaultValue) {
String userId = UserContextHolder.getCurrentUserId();
return userId != null ? userId : defaultValue;
}
/**
* 获取当前用户ID,如果为空则返回"system"
*
* @return 用户ID
*/
public static String getCurrentUserIdOrSystem() {
return getCurrentUserIdOrDefault("system");
}
/**
* 获取当前用户名,如果为空则返回默认值
*
* @param defaultValue 默认值
* @return 用户名
*/
public static String getCurrentUsernameOrDefault(String defaultValue) {
String username = UserContextHolder.getCurrentUsername();
return username != null ? username : defaultValue;
}
/**
* 获取当前用户名,如果为空则返回"guest"
*
* @return 用户名
*/
public static String getCurrentUsernameOrGuest() {
return getCurrentUsernameOrDefault("guest");
}
/**
* 获取当前用户类型,如果为空则返回默认值
*
* @param defaultValue 默认值
* @return 用户类型
*/
public static String getCurrentUserTypeOrDefault(String defaultValue) {
String userType = UserContextHolder.getCurrentUserType();
return userType != null ? userType : defaultValue;
}
/**
* 获取当前用户类型,如果为空则返回"GUEST"
*
* @return 用户类型
*/
public static String getCurrentUserTypeOrGuest() {
return getCurrentUserTypeOrDefault("GUEST");
}
/**
* 获取客户端IP,如果为空则返回默认值
*
* @param defaultValue 默认值
* @return 客户端IP
*/
public static String getClientIpOrDefault(String defaultValue) {
String clientIp = UserContextHolder.getClientIp();
return clientIp != null ? clientIp : defaultValue;
}
/**
* 获取客户端IP,如果为空则返回"unknown"
*
* @return 客户端IP
*/
public static String getClientIpOrUnknown() {
return getClientIpOrDefault("unknown");
}
/**
* 获取请求ID,如果为空则返回默认值
*
* @param defaultValue 默认值
* @return 请求ID
*/
public static String getRequestIdOrDefault(String defaultValue) {
String requestId = UserContextHolder.getRequestId();
return requestId != null ? requestId : defaultValue;
}
/**
* 获取请求ID,如果为空则返回"unknown"
*
* @return 请求ID
*/
public static String getRequestIdOrUnknown() {
return getRequestIdOrDefault("unknown");
}
/**
* 检查当前用户是否为访客
*
* @return 是否为访客
*/
public static boolean isGuest() {
String userId = UserContextHolder.getCurrentUserId();
String userType = UserContextHolder.getCurrentUserType();
return userId == null ||
userId.startsWith("guest_") ||
"GUEST".equalsIgnoreCase(userType);
}
/**
* 检查当前用户是否为系统用户
*
* @return 是否为系统用户
*/
public static boolean isSystem() {
String userId = UserContextHolder.getCurrentUserId();
String userType = UserContextHolder.getCurrentUserType();
return "system".equals(userId) ||
"SYSTEM".equalsIgnoreCase(userType);
}
/**
* 检查当前用户是否为注册用户
*
* @return 是否为注册用户
*/
public static boolean isRegisteredUser() {
return !isGuest() && !isSystem();
}
/**
* 安全地执行需要用户上下文的操作
* 如果没有用户上下文,会设置默认的系统上下文
*
* @param operation 操作
*/
public static void executeWithContext(Runnable operation) {
boolean hasContext = UserContextHolder.hasUserContext();
try {
// 如果没有用户上下文,设置默认的系统上下文
if (!hasContext) {
UserContextHolder.setUserContext("system", "system", "SYSTEM", "127.0.0.1", "system");
log.debug("设置默认系统用户上下文");
}
// 执行操作
operation.run();
} finally {
// 如果是我们设置的默认上下文,执行后清理
if (!hasContext) {
UserContextHolder.clear();
log.debug("清理默认系统用户上下文");
}
}
}
/**
* 临时设置用户上下文执行操作
*
* @param userId 用户ID
* @param username 用户名
* @param userType 用户类型
* @param operation 操作
*/
public static void executeWithTempContext(String userId, String username, String userType, Runnable operation) {
// 保存当前上下文
String originalUserId = UserContextHolder.getCurrentUserId();
String originalUsername = UserContextHolder.getCurrentUsername();
String originalUserType = UserContextHolder.getCurrentUserType();
String originalClientIp = UserContextHolder.getClientIp();
String originalRequestId = UserContextHolder.getRequestId();
try {
// 设置临时上下文
UserContextHolder.setUserContext(userId, username, userType,
originalClientIp != null ? originalClientIp : "127.0.0.1",
originalRequestId != null ? originalRequestId : "temp");
// 执行操作
operation.run();
} finally {
// 恢复原始上下文
if (originalUserId != null || originalUsername != null) {
UserContextHolder.setUserContext(originalUserId, originalUsername, originalUserType,
originalClientIp, originalRequestId);
} else {
UserContextHolder.clear();
}
}
}
}