refactor: 项目目录结构优化
This commit is contained in:
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.youlai.system.plugin.rabbitmq;
|
||||
|
||||
/**
|
||||
* @author haoxr
|
||||
* @since 0.0.1
|
||||
*/
|
||||
public class TestListener {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.youlai.system.plugin.websocket;
|
||||
|
||||
/**
|
||||
* @author haoxr
|
||||
* @since 2023/11/7
|
||||
*/
|
||||
public class StompPrincipal {
|
||||
|
||||
}
|
||||
@@ -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 (浏览器不支持WebSocket,SockJS 将会提供兼容性支持)
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user