feat: 完善后端架构和service层实现
- 创建完整的entity实体类体系,包括所有业务实体 - 实现BaseEntity基类,统一管理公共字段 - 创建雪花算法ID生成器和自动填充处理器 - 简化所有mapper接口,只继承BaseMapper - 重构service层,使用LambdaQueryWrapper进行数据库操作 - 创建BasePageRequest分页查询基类 - 完善用户上下文管理和JWT认证 - 新增WebSocket聊天功能和相关控制器 - 更新前端配置和组件,完善用户认证流程 - 同步数据库建表脚本
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
package com.emotion.interceptor;
|
||||
|
||||
import com.emotion.util.JwtUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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;
|
||||
|
||||
/**
|
||||
* JWT认证拦截器
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Component
|
||||
public class JwtAuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(JwtAuthInterceptor.class);
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
String requestURI = request.getRequestURI();
|
||||
String method = request.getMethod();
|
||||
|
||||
log.debug("JWT拦截器处理请求: {} {}", method, requestURI);
|
||||
|
||||
// 跨域预检请求直接放行
|
||||
if ("OPTIONS".equals(method)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 不需要认证的接口
|
||||
if (isPublicEndpoint(requestURI)) {
|
||||
log.debug("公开接口,无需认证: {}", requestURI);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取Authorization头
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
log.warn("请求缺少Authorization头或格式错误: {}", requestURI);
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":401,\"message\":\"未登录或登录已过期\",\"data\":null}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 提取token
|
||||
String token = authHeader.substring(7);
|
||||
|
||||
// 验证token
|
||||
if (!jwtUtil.validateToken(token)) {
|
||||
log.warn("Token验证失败: {}", requestURI);
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":401,\"message\":\"Token无效或已过期\",\"data\":null}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从token中获取用户信息并设置到请求属性中
|
||||
String userId = jwtUtil.getUserIdFromToken(token);
|
||||
String username = jwtUtil.getUsernameFromToken(token);
|
||||
|
||||
request.setAttribute("userId", userId);
|
||||
request.setAttribute("username", username);
|
||||
request.setAttribute("token", token);
|
||||
|
||||
log.debug("Token验证成功,用户: {} ({})", username, userId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为公开接口(不需要认证)
|
||||
*/
|
||||
private boolean isPublicEndpoint(String requestURI) {
|
||||
// 公开接口列表
|
||||
String[] publicEndpoints = {
|
||||
"/api/auth/login",
|
||||
"/api/auth/register",
|
||||
"/api/auth/captcha",
|
||||
"/api/auth/refresh-token",
|
||||
"/api/health",
|
||||
"/api/ws/chat",
|
||||
"/swagger-ui",
|
||||
"/v3/api-docs",
|
||||
"/actuator"
|
||||
};
|
||||
|
||||
for (String endpoint : publicEndpoints) {
|
||||
if (requestURI.startsWith(endpoint)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.emotion.interceptor;
|
||||
|
||||
import com.emotion.util.UserContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 用户上下文拦截器
|
||||
* 用于在请求处理前设置用户上下文信息,请求处理后清理上下文
|
||||
*
|
||||
* @author emotion-museum
|
||||
* @date 2025-07-23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UserContextInterceptor implements HandlerInterceptor {
|
||||
|
||||
/**
|
||||
* 请求处理前
|
||||
*/
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
try {
|
||||
// 生成请求ID
|
||||
String requestId = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
// 获取用户信息
|
||||
String userId = getUserIdFromRequest(request);
|
||||
String username = getUsernameFromRequest(request);
|
||||
String userType = getUserTypeFromRequest(request);
|
||||
String clientIp = getClientIpFromRequest(request);
|
||||
|
||||
// 设置用户上下文
|
||||
UserContextHolder.setUserContext(userId, username, userType, clientIp, requestId);
|
||||
|
||||
// 设置响应头中的请求ID,便于追踪
|
||||
response.setHeader("X-Request-Id", requestId);
|
||||
|
||||
log.debug("设置用户上下文: {}", UserContextHolder.getContextSummary());
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("设置用户上下文失败: {}", e.getMessage());
|
||||
// 即使设置失败也不影响请求处理
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求处理后
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception ex) {
|
||||
try {
|
||||
log.debug("清理用户上下文: {}", UserContextHolder.getContextSummary());
|
||||
// 清理用户上下文
|
||||
UserContextHolder.clear();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("清理用户上下文失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中获取用户ID
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 用户ID
|
||||
*/
|
||||
private String getUserIdFromRequest(HttpServletRequest request) {
|
||||
// 1. 从请求头获取
|
||||
String userId = request.getHeader("X-User-Id");
|
||||
if (StringUtils.hasText(userId)) {
|
||||
return userId;
|
||||
}
|
||||
|
||||
// 2. 从请求参数获取
|
||||
userId = request.getParameter("userId");
|
||||
if (StringUtils.hasText(userId)) {
|
||||
return userId;
|
||||
}
|
||||
|
||||
// 3. 从Session获取
|
||||
Object sessionUserId = request.getSession().getAttribute("userId");
|
||||
if (sessionUserId != null) {
|
||||
return sessionUserId.toString();
|
||||
}
|
||||
|
||||
// 4. 生成访客ID
|
||||
return "guest_" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中获取用户名
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 用户名
|
||||
*/
|
||||
private String getUsernameFromRequest(HttpServletRequest request) {
|
||||
// 1. 从请求头获取
|
||||
String username = request.getHeader("X-Username");
|
||||
if (StringUtils.hasText(username)) {
|
||||
return username;
|
||||
}
|
||||
|
||||
// 2. 从请求参数获取
|
||||
username = request.getParameter("username");
|
||||
if (StringUtils.hasText(username)) {
|
||||
return username;
|
||||
}
|
||||
|
||||
// 3. 从Session获取
|
||||
Object sessionUsername = request.getSession().getAttribute("username");
|
||||
if (sessionUsername != null) {
|
||||
return sessionUsername.toString();
|
||||
}
|
||||
|
||||
return "guest";
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中获取用户类型
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 用户类型
|
||||
*/
|
||||
private String getUserTypeFromRequest(HttpServletRequest request) {
|
||||
// 1. 从请求头获取
|
||||
String userType = request.getHeader("X-User-Type");
|
||||
if (StringUtils.hasText(userType)) {
|
||||
return userType;
|
||||
}
|
||||
|
||||
// 2. 从请求参数获取
|
||||
userType = request.getParameter("userType");
|
||||
if (StringUtils.hasText(userType)) {
|
||||
return userType;
|
||||
}
|
||||
|
||||
// 3. 从Session获取
|
||||
Object sessionUserType = request.getSession().getAttribute("userType");
|
||||
if (sessionUserType != null) {
|
||||
return sessionUserType.toString();
|
||||
}
|
||||
|
||||
return "GUEST";
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中获取客户端IP
|
||||
*
|
||||
* @param request HTTP请求
|
||||
* @return 客户端IP
|
||||
*/
|
||||
private String getClientIpFromRequest(HttpServletRequest request) {
|
||||
String ip = null;
|
||||
|
||||
// 1. 从X-Forwarded-For获取(经过代理的情况)
|
||||
ip = request.getHeader("X-Forwarded-For");
|
||||
if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
// 多个IP的情况,取第一个
|
||||
int index = ip.indexOf(',');
|
||||
if (index != -1) {
|
||||
ip = ip.substring(0, index);
|
||||
}
|
||||
return ip.trim();
|
||||
}
|
||||
|
||||
// 2. 从X-Real-IP获取
|
||||
ip = request.getHeader("X-Real-IP");
|
||||
if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip.trim();
|
||||
}
|
||||
|
||||
// 3. 从Proxy-Client-IP获取
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip.trim();
|
||||
}
|
||||
|
||||
// 4. 从WL-Proxy-Client-IP获取
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip.trim();
|
||||
}
|
||||
|
||||
// 5. 从HTTP_CLIENT_IP获取
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip.trim();
|
||||
}
|
||||
|
||||
// 6. 从HTTP_X_FORWARDED_FOR获取
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
|
||||
return ip.trim();
|
||||
}
|
||||
|
||||
// 7. 最后从getRemoteAddr获取
|
||||
ip = request.getRemoteAddr();
|
||||
return StringUtils.hasText(ip) ? ip.trim() : "unknown";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user