refactor: 项目目录结构优化

This commit is contained in:
haoxr
2023-11-07 23:34:22 +08:00
parent 791dc49a52
commit f93190e81d
27 changed files with 68 additions and 64 deletions

View File

@@ -0,0 +1,15 @@
package com.youlai.system.plugin.easyexcel;
import com.alibaba.excel.event.AnalysisEventListener;
/**
* 自定义解析结果监听器
*
* @author haoxr
* @since 2023/03/01
*/
public abstract class MyAnalysisEventListener<T> extends AnalysisEventListener<T> {
private String msg;
public abstract String getMsg();
}

View File

@@ -0,0 +1,175 @@
package com.youlai.system.plugin.easyexcel;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.youlai.system.common.base.IBaseEnum;
import com.youlai.system.common.constant.SystemConstants;
import com.youlai.system.common.enums.GenderEnum;
import com.youlai.system.common.enums.StatusEnum;
import com.youlai.system.converter.UserConverter;
import com.youlai.system.model.entity.SysRole;
import com.youlai.system.model.entity.SysUser;
import com.youlai.system.model.entity.SysUserRole;
import com.youlai.system.model.vo.UserImportVO;
import com.youlai.system.service.SysRoleService;
import com.youlai.system.service.SysUserRoleService;
import com.youlai.system.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户导入监听器
* <p>
* <a href="https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read#%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84%E8%AF%BB%E7%9A%84%E7%9B%91%E5%90%AC%E5%99%A8">最简单的读的监听器</a>
*
* @author haoxr
* @since 2022/4/10 20:49
*/
@Slf4j
public class UserImportListener extends MyAnalysisEventListener<UserImportVO> {
// 有效条数
private int validCount;
// 无效条数
private int invalidCount;
// 导入返回信息
StringBuilder msg = new StringBuilder();
// 部门ID
private final Long deptId;
private final SysUserService userService;
private final PasswordEncoder passwordEncoder;
private final UserConverter userConverter;
private final SysRoleService roleService;
private final SysUserRoleService userRoleService;
public UserImportListener(Long deptId) {
this.deptId = deptId;
this.userService = SpringUtil.getBean(SysUserService.class);
this.passwordEncoder = SpringUtil.getBean(PasswordEncoder.class);
this.roleService = SpringUtil.getBean(SysRoleService.class);
this.userRoleService = SpringUtil.getBean(SysUserRoleService.class);
this.userConverter = SpringUtil.getBean(UserConverter.class);
}
/**
* 每一条数据解析都会来调用
* <p>
* 1. 数据校验;全字段校验
* 2. 数据持久化;
*
* @param userImportVO 一行数据,类似于 {@link AnalysisContext#readRowHolder()}
* @param analysisContext
*/
@Override
public void invoke(UserImportVO userImportVO, AnalysisContext analysisContext) {
log.info("解析到一条用户数据:{}", JSONUtil.toJsonStr(userImportVO));
// 校验数据
StringBuilder validationMsg = new StringBuilder();
String username = userImportVO.getUsername();
if (StrUtil.isBlank(username)) {
validationMsg.append("用户名为空;");
} else {
long count = userService.count(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
if (count > 0) {
validationMsg.append("用户名已存在;");
}
}
String nickname = userImportVO.getNickname();
if (StrUtil.isBlank(nickname)) {
validationMsg.append("用户昵称为空;");
}
String mobile = userImportVO.getMobile();
if (StrUtil.isBlank(mobile)) {
validationMsg.append("手机号码为空;");
} else {
if (!Validator.isMobile(mobile)) {
validationMsg.append("手机号码不正确;");
}
}
if (validationMsg.length() == 0) {
// 校验通过,持久化至数据库
SysUser entity = userConverter.importVo2Entity(userImportVO);
entity.setDeptId(deptId); // 部门
entity.setPassword(passwordEncoder.encode(SystemConstants.DEFAULT_PASSWORD)); // 默认密码
// 性别翻译
String genderLabel = userImportVO.getGender();
if (StrUtil.isNotBlank(genderLabel)) {
Integer genderValue = (Integer) IBaseEnum.getValueByLabel(genderLabel, GenderEnum.class);
entity.setGender(genderValue);
}
// 角色解析
String roleCodes = userImportVO.getRoleCodes();
List<Long> roleIds = null;
if (StrUtil.isNotBlank(roleCodes)) {
roleIds = roleService.list(
new LambdaQueryWrapper<SysRole>()
.in(SysRole::getCode, roleCodes.split(","))
.eq(SysRole::getStatus, StatusEnum.ENABLE.getValue())
.select(SysRole::getId)
).stream()
.map(role -> role.getId())
.collect(Collectors.toList());
}
boolean saveResult = userService.save(entity);
if (saveResult) {
validCount++;
// 保存用户角色关联
if (CollectionUtil.isNotEmpty(roleIds)) {
List<SysUserRole> userRoles = roleIds.stream()
.map(roleId -> new SysUserRole(entity.getId(), roleId))
.collect(Collectors.toList());
userRoleService.saveBatch(userRoles);
}
} else {
invalidCount++;
msg.append("" + (validCount + invalidCount) + "行数据保存失败;<br/>");
}
} else {
invalidCount++;
msg.append("" + (validCount + invalidCount) + "行数据校验失败:").append(validationMsg + "<br/>");
}
}
/**
* 所有数据解析完成会来调用
*
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
@Override
public String getMsg() {
// 总结信息
String summaryMsg = StrUtil.format("导入用户结束:成功{}条,失败{}条;<br/>{}", validCount, invalidCount, msg);
return summaryMsg;
}
}

View File

@@ -0,0 +1,8 @@
package com.youlai.system.plugin.rabbitmq;
/**
* @author haoxr
* @since 0.0.1
*/
public class TestListener {
}

View File

@@ -0,0 +1,9 @@
package com.youlai.system.plugin.websocket;
/**
* @author haoxr
* @since 2023/11/7
*/
public class StompPrincipal {
}

View File

@@ -0,0 +1,64 @@
package com.youlai.system.plugin.websocket;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
/**
* WebSocket 配置
*
* @author haoxr
* @since 2.4.0
*/
@Configuration
@ConditionalOnProperty(name = "system.config.websocket-enabled")// system.config.websocket-enabled = true 才会自动装配
@EnableWebSocketMessageBroker // 启用WebSocket消息代理功能和配置STOMP协议实现实时双向通信和消息传递
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final WebsocketChannelInterceptor websocketChannelInterceptor;
/**
* 注册一个端点,客户端通过这个端点进行连接
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws") // 注册了一个 /ws 的端点
.setAllowedOriginPatterns("*") // 允许跨域的 WebSocket 连接
.withSockJS(); // 启用 SockJS (浏览器不支持WebSocketSockJS 将会提供兼容性支持)
registry.addEndpoint("/ws-app").setAllowedOriginPatterns("*"); // 注册了一个 /ws-app 的端点,支持 uni-app 的 ws 连接协议
}
/**
* 配置消息代理
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// 客户端发送消息的请求前缀
registry.setApplicationDestinationPrefixes("/app");
// 客户端订阅消息的请求前缀topic一般用于广播推送queue用于点对点推送
registry.enableSimpleBroker("/topic", "/queue");
// 服务端通知客户端的前缀可以不设置默认为user
registry.setUserDestinationPrefix("/user");
}
/**
* 配置客户端入站通道拦截器
*
* @param registration 通道注册器
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(websocketChannelInterceptor);
}
}

View File

@@ -0,0 +1,68 @@
package com.youlai.system.plugin.websocket;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;
import org.springframework.web.socket.messaging.SessionUnsubscribeEvent;
import java.security.Principal;
/**
* Websocket 客户端事件监听器
*
* @author haoxr
* @since 2023/10/10
*/
@Component
@Slf4j
public class WebSocketEventListener {
/**
* 监听客户端连接事件
*
* @param event 连接事件对象
*/
@EventListener
public void handleWebSocketConnectListener(SessionConnectedEvent event) {
Principal user = event.getUser();
log.info("客户端连接成功");
}
/**
* 监听客户端断开连接事件
*
* @param event 断开连接事件对象
*/
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
log.info("客户端断开连接");
}
/**
* 监听客户端订阅事件
*
* @param event 订阅事件对象
*/
@EventListener
public void handleSubscription(SessionSubscribeEvent event) {
log.info("客户端订阅:{}", JSONUtil.toJsonStr(event.getMessage()));
}
/**
* 监听客户端取消订阅事件
*
* @param event 取消订阅事件对象
*/
@EventListener
public void handleUnSubscription(SessionUnsubscribeEvent event) {
log.info("客户端取消订阅:{}", JSONUtil.toJsonStr(event.getMessage()));
}
}

View File

@@ -0,0 +1,55 @@
package com.youlai.system.plugin.websocket;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.core.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);
}
}

View File

@@ -0,0 +1,62 @@
package com.youlai.system.plugin.xxljob;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* xxl-job config
*
* @author xuxueli 2017-04-28
*/
@Configuration
// system.config.xxl-job-enabled = true 才会自动装配
@ConditionalOnProperty(name = "system.config.xxl-job-enabled")
@Slf4j
public class XxlJobConfig {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.appname}")
private String appname;
@Value("${xxl.job.executor.address}")
private String address;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
log.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setAddress(address);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
}

View File

@@ -0,0 +1,19 @@
package com.youlai.system.plugin.xxljob;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* xxl-job 测试示例Bean模式
*/
@Component
@Slf4j
public class XxlJobSampleHandler {
@XxlJob("demoJobHandler")
public void demoJobHandler() {
log.info("XXL-JOB, Hello World.");
}
}