🚀 添加websocket和task,aop切面示例

This commit is contained in:
bunny 2024-05-06 09:11:03 +08:00
parent 7762d8f9fa
commit d1e9baa15a
16 changed files with 276 additions and 71 deletions

View File

@ -0,0 +1,11 @@
package cn.bunny.common.constant;
/**
* 数据库中自动填充字段
*/
public class SQLAutoFillConstant {
public static final String SET_CREATE_TIME = "setCreateTime";
public static final String SET_UPDATE_TIME = "setUpdateTime";
public static final String SET_CREATE_USER = "setCreateUser";
public static final String SET_UPDATE_USER = "setUpdateUser";
}

View File

@ -9,8 +9,6 @@ import java.util.List;
@Component
public class SnowflakeIdGenerator {
// 数据中心id
private final long datacenterId;
// 数据中心id位数
@ -47,8 +45,6 @@ public class SnowflakeIdGenerator {
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(SnowflakeProperties properties) {
// 数据中心id
this.datacenterId = properties.getDatacenterId();
// 数据中心id位数
@ -76,56 +72,37 @@ public class SnowflakeIdGenerator {
// 单次批量生成id的最大数量
this.maxBatchCount = properties.getMaxBatchCount();
// 校验datacenterId和workerId是否超出最大值
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("数据中心Id不能大于%d或小于0", maxDatacenterId));
}
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("机器Id不能大于%d或小于0", maxWorkerId));
}
}
/**
* id生成方法(单个)
*
* @return
*/
public synchronized long nextId() {
// 获取当前时间的毫秒数
long timestamp = currentTime();
// 判断时钟是否回拨
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("时钟回拨,回拨毫秒数:%d", lastTimestamp - timestamp));
}
// 设置序列号
if (lastTimestamp == timestamp) {
// 设置序列号递增如果当前毫秒内序列号已经达到最大值则直到下一毫秒在重新从0开始计算序列号
sequence = (sequence + 1) & maxSequence;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// 计算id
@ -138,22 +115,13 @@ public class SnowflakeIdGenerator {
/**
* id生成方法(批量)
*
* @return
*/
public synchronized List<Long> nextIds(int count) {
if (count > maxBatchCount || count < 0) {
throw new IllegalArgumentException(String.format("批量生成id的数量不能大于%d或小于0", maxBatchCount));
}
List<Long> ids = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ids.add(nextId());
}
return ids;
@ -164,13 +132,9 @@ public class SnowflakeIdGenerator {
* 确保生成的时间戳总是向前移动的即使在相同的毫秒内请求多个ID时也能保持唯一性
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = currentTime();
// 循环等待直至获取到新的毫秒时间戳
while (timestamp <= lastTimestamp) {
timestamp = currentTime();
}
return timestamp;
@ -180,9 +144,6 @@ public class SnowflakeIdGenerator {
* 获取当前时间的毫秒数
*/
private long currentTime() {
return System.currentTimeMillis();
}
}

View File

@ -2,16 +2,20 @@ package cn.bunny.security.config;
import cn.bunny.security.custom.CustomPasswordEncoder;
import cn.bunny.security.handelr.*;
import cn.bunny.security.service.UserDetailsService;
import cn.bunny.security.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.SecurityFilterChain;
@ -25,7 +29,7 @@ public class WebSecurityConfig {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserDetailsService userDetailsService;
private MyUserDetailsService myUserDetailsService;
@Autowired
private CustomPasswordEncoder customPasswordEncoder;
@Autowired
@ -53,7 +57,7 @@ public class WebSecurityConfig {
// 后登录的账号会使先登录的账号失效
.sessionManagement(session -> {
// 禁用session
// session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 最大登录数为1
session.maximumSessions(1)
// 可以获取到所有登录的用户以及登录状态设置session状态
@ -67,8 +71,6 @@ public class WebSecurityConfig {
httpSecurity.csrf(AbstractHttpConfigurer::disable);
// 跨域访问权限
httpSecurity.cors(withDefaults());
// 自定义用户认证和密码
httpSecurity.userDetailsService(userDetailsService).passwordManagement(customPasswordEncoder);
// 记住我
httpSecurity.rememberMe(e -> e.rememberMeParameter("rememberBunny").rememberMeCookieName("rememberBunny").key("BunnyKey"));
// 自定义过滤器
@ -79,6 +81,15 @@ public class WebSecurityConfig {
return httpSecurity.build();
}
// 自定义用户认证和密码
@Bean
public AuthenticationManager authenticationManager() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(customPasswordEncoder);
provider.setUserDetailsService(myUserDetailsService);
return new ProviderManager(provider);
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();

View File

@ -0,0 +1,19 @@
package cn.bunny.security.custom;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;
import java.util.function.Supplier;
@Component
@Slf4j
public class CustomAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
return null;
}
}

View File

@ -12,7 +12,6 @@ import org.springframework.util.DigestUtils;
*/
@Configuration
public class CustomPasswordEncoder implements PasswordEncoder, Customizer<PasswordManagementConfigurer<HttpSecurity>> {
@Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());

View File

@ -2,11 +2,8 @@ package cn.bunny.security.service;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Component
public interface UserDetailsService extends org.springframework.security.core.userdetails.UserDetailsService {
public interface MyUserDetailsService extends org.springframework.security.core.userdetails.UserDetailsService {
/**
* 根据用户名获取用户对象获取不到直接抛异常
*/

View File

@ -0,0 +1,8 @@
package cn.bunny.enums;
/**
* 数据库操作类型
*/
public enum OperationType {
UPDATE, INSERT
}

13
pom.xml
View File

@ -34,6 +34,7 @@
<jwt.version>0.9.1</jwt.version>
<easyexcel.version>3.3.3</easyexcel.version>
<jodatime.version>2.10.1</jodatime.version>
<aspectj>1.9.21</aspectj>
</properties>
<dependencyManagement>
<dependencies>
@ -91,6 +92,18 @@
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<!-- aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj}</version>
</dependency>
<!-- aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>

View File

@ -42,6 +42,20 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- asp 切面 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,15 +1,22 @@
package cn.bunny.service;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@ComponentScan(basePackages = {"cn.bunny"})
@MapperScan("cn.bunny.service.mapper")
@EnableScheduling// 定时任务
@EnableCaching// 开启缓存注解
@SpringBootApplication
@Slf4j
public class ServiceApplication {
public static void main(String[] args) {
log.info("ServiceApplication启动...");
SpringApplication.run(ServiceApplication.class, args);
}
}

View File

@ -0,0 +1,15 @@
package cn.bunny.service.annotation;
import cn.bunny.enums.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
// 数据库操作类型
OperationType value();
}

View File

@ -0,0 +1,74 @@
package cn.bunny.service.aspect;
import cn.bunny.common.constant.SQLAutoFillConstant;
import cn.bunny.common.service.context.BaseContext;
import cn.bunny.enums.OperationType;
import cn.bunny.service.annotation.AutoFill;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
@Pointcut("execution(* cn.bunny.service.*.*(..))")
public void autoFillPointcut() {
}
/**
* 之前操作
*
* @param joinPoint 参数
*/
@Before("autoFillPointcut()")
public void autoFill(JoinPoint joinPoint) {
log.info("开始进行自动填充");
// 获取当前被拦截数据库操作
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
OperationType operationType = autoFill.value();
// 获取实体对象
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
Object entity = args[0];
// 准备赋值数据
LocalDateTime localDateTime = LocalDateTime.now();
Long id = BaseContext.getUserId();
// 根据当前不同的操作类型为对应属性来反射赋值
if (operationType == OperationType.INSERT) {
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(SQLAutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(SQLAutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getMethod(SQLAutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getMethod(SQLAutoFillConstant.SET_UPDATE_USER, Long.class);
setCreateTime.invoke(entity, localDateTime);
setCreateUser.invoke(entity, id);
setUpdateTime.invoke(entity, localDateTime);
setUpdateUser.invoke(entity, id);
} catch (Exception e) {
e.printStackTrace();
}
} else if (operationType == OperationType.UPDATE) {
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(SQLAutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(SQLAutoFillConstant.SET_UPDATE_USER, Long.class);
setUpdateTime.invoke(entity, localDateTime);
setUpdateUser.invoke(entity, id);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

View File

@ -5,9 +5,9 @@ import cn.bunny.common.service.exception.BunnyException;
import cn.bunny.entity.system.SysRole;
import cn.bunny.entity.system.SysUser;
import cn.bunny.security.custom.CustomUser;
import cn.bunny.security.service.UserDetailsService;
import cn.bunny.service.mapper.SysUserMapper;
import cn.bunny.service.service.SysRoleService;
import cn.bunny.service.service.SysUserService;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.authority.AuthorityUtils;
@ -17,15 +17,15 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.List;
@Configuration
public class MyUserDetailsService implements UserDetailsService {
public class MyUserDetailsService implements cn.bunny.security.service.MyUserDetailsService {
@Autowired
private SysUserService sysUserService;
private SysUserMapper sysUserMapper;
@Autowired
private SysRoleService sysRoleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = sysUserService.getByUsername(username);
SysUser sysUser = sysUserMapper.selectOne(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getUsername, username));
if (sysUser == null) {
throw new UsernameNotFoundException(MessageConstant.USER_DOES_NOT_EXIST);
}
@ -35,7 +35,7 @@ public class MyUserDetailsService implements UserDetailsService {
}
List<SysRole> sysRoleList = sysRoleService.list();
List<String> roleAuthoritieList = sysRoleList.stream().map(SysRole::getRoleName).toList();
List<String> roleAuthoritieList = sysRoleList.stream().map(SysRole::getRoleCode).toList();
return new CustomUser(sysUser, AuthorityUtils.createAuthorityList(roleAuthoritieList));
}
}

View File

@ -4,11 +4,9 @@ import cn.bunny.common.constant.MessageConstant;
import cn.bunny.common.service.exception.BunnyException;
import cn.bunny.common.utils.SnowflakeIdGenerator;
import cn.bunny.entity.system.Login;
import cn.bunny.entity.system.SysRole;
import cn.bunny.entity.system.SysUser;
import cn.bunny.entity.system.SysUserinfo;
import cn.bunny.service.mapper.SysUserMapper;
import cn.bunny.service.service.SysRoleService;
import cn.bunny.service.service.SysUserService;
import cn.bunny.vo.system.LoginVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -17,13 +15,12 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
* 用户表 服务实现类
@ -34,12 +31,12 @@ import java.util.stream.Collectors;
*/
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
@Autowired
private SysRoleService sysRoleService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private SnowflakeIdGenerator snowflakeIdGenerator;
@Autowired
private AuthenticationManager authenticationManager;
/**
* 登录
@ -73,13 +70,9 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
throw new BunnyException(MessageConstant.PASSWORD_ERROR);
}
List<String> roleList = sysRoleService.list().stream().map(SysRole::getRoleCode).collect(Collectors.toList());
HashMap<String, Object> map = new HashMap<>();
map.put("sysUser", sysUser);
map.put("roleList", roleList);
redisTemplate.opsForValue().set(String.valueOf(snowId), map);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(vo.getUsername(), vo.getPassword());
Authentication authenticate = authenticationManager.authenticate(authentication);
redisTemplate.opsForValue().set(String.valueOf(snowId), authenticate);
// 添加token
return Login.builder().token(String.valueOf(snowId)).build();
}

View File

@ -0,0 +1,12 @@
package cn.bunny.service.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
@Slf4j
public class TemplateTask {
@Scheduled(cron = "0/1 * * * * ?")
public void templateTask() {
log.warn("TemplateTask...");
}
}

View File

@ -0,0 +1,71 @@
package cn.bunny.service.websocket;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* WebSocket服务
*/
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {
// 存放会话对象
private static final Map<String, Session> sessionMap = new HashMap();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
System.out.println("客户端:" + sid + "建立连接");
sessionMap.put(sid, session);
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, @PathParam("sid") String sid) {
System.out.println("收到来自客户端:" + sid + "的信息:" + message);
}
/**
* 连接关闭调用的方法
*
* @param sid 请求id
*/
@OnClose
public void onClose(@PathParam("sid") String sid) {
System.out.println("连接断开:" + sid);
sessionMap.remove(sid);
}
/**
* 群发
*
* @param message 消息
*/
public void sendToAllClient(String message) {
Collection<Session> sessions = sessionMap.values();
for (Session session : sessions) {
try {
// 服务器向客户端发送消息
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}