提交 cd4f14e6 编写于 作者: H haoxr

refactor: 包结构优化,websocket 重构

上级 28646fdf
......@@ -45,17 +45,22 @@ youlai-boot
├── mysql5 # MySQL5 脚本
├── mysql8 # MySQL8 脚本
├── src # 源码目录
├── base # 核心模块
├── mybatisplus # Mybatis-Plus 配置和插件
├── security # Spring Security 安全配置和扩展
├── common # 公共模块
├── config # 自动装配配置
├── CorsConfig # 跨域共享配置
├── RedisConfig # Redis 配置
├── MybatisConfig # Mybatis 自动装配配置
├── RedisCacheConfig # Redis 缓存自动装配配置
├── RedisConfig # Redis 自动装配配置
├── SecurityConfig # Spring Security 自动装配配置
├── SwaggerConfig # API 接口文档配置
├── WebMvcConfig # WebMvc 配置
├── WebSocketConfig # WebSocket 自动装配配置
├── XxlJobConfig # XXL-JOB 自动装配配置
├── controller # 控制层
├── converter # MapStruct转换器
├── core # 核心模块
├── security # Spring Security 安全配置和扩展
├── mybatis # Mybatis-Plus 配置和插件
├── filter # 过滤器
├── RequestLogFilter # 请求日志过滤器
├── VerifyCodeFilter # 验证码过滤器
......@@ -68,6 +73,7 @@ youlai-boot
├── vo # 视图对象
├── mapper # 数据库访问层
├── plugin # 插件(可选)
├── captcha # 验证码插件,用于生成验证码
├── dupsubmit # 防重提交插件,用于防止表单重复提交
├── easyexcel # EasyExcel 插件,Excel 文件的读写
├── rabbitmq # RabbitMQ 插件,消息队列交互
......
......@@ -6,7 +6,7 @@
<groupId>com.youlai</groupId>
<artifactId>youlai-boot</artifactId>
<version>2.5.0</version>
<version>2.5.1</version>
<description>基于 Java 17 + SpringBoot 3 构建的权限管理系统。</description>
<parent>
......
......@@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.constant.SystemConstants;
import com.youlai.system.base.security.model.SysUserDetails;
import com.youlai.system.core.security.model.SysUserDetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
......
package com.youlai.system.base.mybatisplus.config;
package com.youlai.system.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.youlai.system.base.mybatisplus.handler.MyDataPermissionHandler;
import com.youlai.system.base.mybatisplus.handler.MyMetaObjectHandler;
import com.youlai.system.core.mybatis.handler.MyDataPermissionHandler;
import com.youlai.system.core.mybatis.handler.MyMetaObjectHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
......@@ -19,7 +19,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
*/
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {
public class MybatisConfig {
/**
* 分页插件和数据权限插件
......
package com.youlai.system.base.security.config;
package com.youlai.system.config;
import com.youlai.system.common.constant.SecurityConstants;
import com.youlai.system.base.security.exception.MyAccessDeniedHandler;
import com.youlai.system.base.security.exception.MyAuthenticationEntryPoint;
import com.youlai.system.base.security.jwt.JwtTokenFilter;
import com.youlai.system.core.security.exception.MyAccessDeniedHandler;
import com.youlai.system.core.security.exception.MyAuthenticationEntryPoint;
import com.youlai.system.core.security.jwt.JwtTokenFilter;
import com.youlai.system.filter.VerifyCodeFilter;
import com.youlai.system.base.security.jwt.JwtTokenProvider;
import com.youlai.system.core.security.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......
package com.youlai.system.plugin.websocket;
package com.youlai.system.config;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.core.security.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
......@@ -18,9 +27,10 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo
@Configuration
@EnableWebSocketMessageBroker // 启用WebSocket消息代理功能和配置STOMP协议,实现实时双向通信和消息传递
@RequiredArgsConstructor
@Slf4j
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final WebsocketChannelInterceptor websocketChannelInterceptor;
private final JwtTokenProvider jwtTokenProvider;
/**
* 注册一个端点,客户端通过这个端点进行连接
......@@ -58,6 +68,44 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(websocketChannelInterceptor);
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
// 如果 StompHeaderAccessor 为 null,说明不是 STOMP 消息,直接放行
if (accessor == null) {
return ChannelInterceptor.super.preSend(message, channel);
}
// 如果是连接请求(CONNECT 命令),从请求头中取出 token 并设置到认证信息中
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// 从连接头中提取授权令牌
String bearerToken = accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION);
// 验证令牌格式并提取用户信息
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
try {
// 移除 "Bearer " 前缀
String tokenWithoutPrefix = bearerToken.substring(7);
String username = jwtTokenProvider.getUsername(tokenWithoutPrefix);
// 如果用户名有效,设置用户到访问器中
if (StrUtil.isNotBlank(username)) {
accessor.setUser(() -> username);
return message;
}
} catch (Exception e) {
// 异常处理,可能是解析令牌失败
log.error("Failed to process authentication token.", e);
}
}
}
// 如果不是连接命令或授权失败,继续执行默认逻辑
return ChannelInterceptor.super.preSend(message, channel);
}
});
}
}
package com.youlai.system.plugin.xxljob;
package com.youlai.system.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
......
package com.youlai.system.controller;
import com.youlai.system.model.dto.SocketMessage;
import com.youlai.system.model.dto.ChatMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.DestinationVariable;
......@@ -48,11 +48,14 @@ public class WebsocketController {
* @param message 消息内容
*/
@MessageMapping("/sendToUser/{username}")
//@SendToUser(value = "/queue/greeting")
public void sendToUser(Principal principal, @DestinationVariable String username, String message) {
log.info("sender:{};receiver:{}", principal.getName(), username);
messagingTemplate.convertAndSendToUser(username, "/queue/greeting", new SocketMessage(principal.getName(), message));
/// return "Hello, " + message;
String sender = principal.getName(); // 发送人
String receiver = username; // 接收人
log.info("发送人:{}; 接收人:{}", sender, receiver);
// 发送消息给指定用户 /user/{username}/queue/greeting
messagingTemplate.convertAndSendToUser(receiver, "/queue/greeting", new ChatMessage(sender, message));
}
}
package com.youlai.system.base.mybatisplus.annotation;
package com.youlai.system.core.mybatis.annotation;
import java.lang.annotation.*;
......
package com.youlai.system.base.mybatisplus.handler;
package com.youlai.system.core.mybatis.handler;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
import com.youlai.system.base.mybatisplus.annotation.DataPermission;
import com.youlai.system.core.mybatis.annotation.DataPermission;
import com.youlai.system.common.base.IBaseEnum;
import com.youlai.system.common.enums.DataScopeEnum;
import com.youlai.system.common.util.SecurityUtils;
......
package com.youlai.system.base.mybatisplus.handler;
package com.youlai.system.core.mybatis.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
......
package com.youlai.system.base.security.exception;
package com.youlai.system.core.security.exception;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.util.ResponseUtils;
......
package com.youlai.system.base.security.exception;
package com.youlai.system.core.security.exception;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.util.ResponseUtils;
......
package com.youlai.system.base.security.jwt;
package com.youlai.system.core.security.jwt;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.util.ResponseUtils;
......
package com.youlai.system.base.security.jwt;
package com.youlai.system.core.security.jwt;
import cn.hutool.core.convert.Convert;
import com.youlai.system.common.constant.JwtClaimConstants;
import com.youlai.system.base.security.model.SysUserDetails;
import com.youlai.system.core.security.model.SysUserDetails;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
......
package com.youlai.system.base.security.model;
package com.youlai.system.core.security.model;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
......
package com.youlai.system.base.security.service;
package com.youlai.system.core.security.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
......
package com.youlai.system.base.security.service;
package com.youlai.system.core.security.service;
import com.youlai.system.base.security.model.SysUserDetails;
import com.youlai.system.core.security.model.SysUserDetails;
import com.youlai.system.model.dto.UserAuthInfo;
import com.youlai.system.service.SysUserService;
import lombok.RequiredArgsConstructor;
......
......@@ -3,7 +3,7 @@ package com.youlai.system.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.youlai.system.base.mybatisplus.annotation.DataPermission;
import com.youlai.system.core.mybatis.annotation.DataPermission;
import com.youlai.system.model.entity.SysDept;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
......
......@@ -2,7 +2,7 @@ package com.youlai.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.system.base.mybatisplus.annotation.DataPermission;
import com.youlai.system.core.mybatis.annotation.DataPermission;
import com.youlai.system.model.bo.UserBO;
import com.youlai.system.model.entity.SysUser;
import com.youlai.system.model.dto.UserAuthInfo;
......
......@@ -10,7 +10,7 @@ import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SocketMessage {
public class ChatMessage {
/**
* 发送者
......
......@@ -4,7 +4,7 @@ import cn.hutool.core.util.StrUtil;
import com.youlai.system.plugin.dupsubmit.annotation.PreventDuplicateSubmit;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.exception.BusinessException;
import com.youlai.system.base.security.jwt.JwtTokenProvider;
import com.youlai.system.core.security.jwt.JwtTokenProvider;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......
package com.youlai.system.plugin.websocket;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.base.security.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;
import java.security.Principal;
/**
* Websocket 连接认证拦截器
*
* @author haoxr
* @since 2.4.0
*/
@Component
@RequiredArgsConstructor
public class WebsocketChannelInterceptor implements ChannelInterceptor {
private final JwtTokenProvider jwtTokenProvider;
/**
* 连接前监听
*
* @param message 消息
* @param channel 通道
* @return
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
assert accessor != null;
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String bearerToken = accessor.getFirstNativeHeader("Authorization");
if (StrUtil.isNotBlank(bearerToken)) {
bearerToken = bearerToken.substring(7); // remove "Bearer "
String username = jwtTokenProvider.getUsername(bearerToken);
if (StrUtil.isNotBlank(username)) {
Principal principal = () -> username;
accessor.setUser(principal);
return message;
}
}
}
return ChannelInterceptor.super.preSend(message, channel);
}
}
package com.youlai.system.service.impl;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.constant.CacheConstants;
import com.youlai.system.base.security.jwt.JwtTokenProvider;
import com.youlai.system.core.security.jwt.JwtTokenProvider;
import com.youlai.system.model.dto.CaptchaResult;
import com.youlai.system.model.dto.LoginResult;
import com.youlai.system.plugin.captcha.CaptchaGenerator;
......
......@@ -10,7 +10,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.system.common.constant.SystemConstants;
import com.youlai.system.common.model.Option;
import com.youlai.system.converter.RoleConverter;
import com.youlai.system.base.security.service.PermissionService;
import com.youlai.system.core.security.service.PermissionService;
import com.youlai.system.mapper.SysRoleMapper;
import com.youlai.system.model.entity.SysRole;
import com.youlai.system.model.entity.SysRoleMenu;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册