# zer0q-framework **Repository Path**: T-T33code/zer0q-framework ## Basic Information - **Project Name**: zer0q-framework - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-06-24 - **Last Updated**: 2024-07-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 初始化 ### 1. 创建基本的项目结构 ![image-20240622090140068](/Users/freeloop/Library/Application Support/typora-user-images/image-20240622090140068.png) ### 2. 完成各层次的依赖关系 父POM: ```xml 3.8.7 UTF-8 UTF-8 1.8 3.1.1 5.3.33 1.2.20 1.21 3.0.0 2.3.3 1.4.7 2.0.43 6.5.0 2.13.0 4.1.2 2.3 0.9.1 ``` ```xml org.springframework spring-framework-bom ${spring-framework.version} pom import org.springframework.boot spring-boot-dependencies 2.5.15 pom import com.alibaba druid-spring-boot-starter ${druid.version} eu.bitwalker UserAgentUtils ${bitwalker.version} com.github.pagehelper pagehelper-spring-boot-starter ${pagehelper.boot.version} com.github.oshi oshi-core ${oshi.version} io.springfox springfox-boot-starter ${swagger.version} io.swagger swagger-models commons-io commons-io ${commons.io.version} org.apache.poi poi-ooxml ${poi.version} org.apache.velocity velocity-engine-core ${velocity.version} com.alibaba.fastjson2 fastjson2 ${fastjson.version} io.jsonwebtoken jjwt ${jwt.version} pro.fessional kaptcha ${kaptcha.version} com.weyoung zeroq-application 1.0-SNAPSHOT com.weyoung zeroq-interface 1.0-SNAPSHOT com.weyoung zeroq-domain 1.0-SNAPSHOT com.weyoung zeroq-infrastructure 1.0-SNAPSHOT ``` 其他; Interface的pom.xml 引入 application ```xml com.weyoung zeroq-application ``` application的pom.xml引入infrastructure ```xml com.weyoung zeroq-infrastructure ``` infrastructure的pom.xml引入domain ```xml com.weyoung zeroq-domain ``` ### 3. 引入springboot 的项目依赖(domain) ```xml org.springframework.boot spring-boot-starter-web ``` ### 4. 书写启动类 ```java /** * @author 33 meiko_ooo@163.com * @description 启动类 * @create 2024/6/22 09:23 */ @SpringBootApplication public class ApplicationService { public static void main(String[] args) { SpringApplication.run(ApplicationService.class, args); } } ``` 注意:启动类包的位置(SpringBoot的知识) 访问: ![image-20240622093230303](/Users/freeloop/Library/Application Support/typora-user-images/image-20240622093230303.png) 正常哦!!! ## Redis配置 ### 1. 先构建仓储层的一个基本的结构 ![image-20240622091544466](/Users/freeloop/Library/Application Support/typora-user-images/image-20240622091544466.png) 分别为:缓存,对象转换器,仓鼠实现,工具包 ### 2. 引入Redis的依赖配置(在infrastructure) ```xml org.springframework.boot spring-boot-starter-data-redis ``` ### 3. 在cache中新增一个类RedisCache ```java package com.weyoung.infrastructure.cache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; /** * @author 33 meiko_ooo@163.com * @description Redis 常用工具类 * @create 2024/6/22 09:20 */ @SuppressWarnings(value = { "unchecked", "rawtypes" }) @Component public class RedisCache { @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 */ public void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @param unit 时间单位 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 获取有效时间 * * @param key Redis键 * @return 有效时间 */ public long getExpire(final String key) { return redisTemplate.getExpire(key); } /** * 判断 key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public Boolean hasKey(String key) { return redisTemplate.hasKey(key); } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public T getCacheObject(final String key) { ValueOperations operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection 多个对象 * @return */ public boolean deleteObject(final Collection collection) { return redisTemplate.delete(collection) > 0; } /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public long setCacheList(final String key, final List dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public List getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public BoundSetOperations setCacheSet(final String key, final Set dataSet) { BoundSetOperations setOperation = redisTemplate.boundSetOps(key); Iterator it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public Set getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 缓存Map * * @param key * @param dataMap */ public void setCacheMap(final String key, final Map dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 获得缓存的Map * * @param key * @return */ public Map getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入数据 * * @param key Redis键 * @param hKey Hash键 * @param value 值 */ public void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 获取Hash中的数据 * * @param key Redis键 * @param hKey Hash键 * @return Hash中的对象 */ public T getCacheMapValue(final String key, final String hKey) { HashOperations opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 获取多个Hash中的数据 * * @param key Redis键 * @param hKeys Hash键集合 * @return Hash对象集合 */ public List getMultiCacheMapValue(final String key, final Collection hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 删除Hash中的某条数据 * * @param key Redis键 * @param hKey Hash键 * @return 是否成功 */ public boolean deleteCacheMapValue(final String key, final String hKey) { return redisTemplate.opsForHash().delete(key, hKey) > 0; } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection keys(final String pattern) { return redisTemplate.keys(pattern); } } ``` ### 4. 测试 ```xml org.springframework.boot spring-boot-starter-test test junit junit 4.13.2 test ``` 书写配置类: ```java package com.weyoung.test.redis; import com.weyoung.infrastructure.cache.RedisCache; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; /** * @author 33 meiko_ooo@163.com * @description Redis配置 * @create 2024/6/22 09:39 */ @RunWith(SpringRunner.class) @SpringBootTest public class RedisCacheTest { @Resource private RedisCache redisCache; @Test public void setValue(){ redisCache.setCacheObject("cache_value","33coo"); } } ``` 执行查看结果: 解决办法: 配置类1: ```java package com.weyoung.infrastructure.config; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.filter.Filter; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * Redis使用FastJson序列化 * * @author 33coo */ public class FastJson2JsonRedisSerializer implements RedisSerializer { public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; /** * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) */ static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter("org.springframework", "com.weyoung"); private final Class clazz; public FastJson2JsonRedisSerializer(Class clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); } } ``` 配置类2: ```java package com.weyoung.infrastructure.config; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * redis配置 * * @author 33coo */ @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean @SuppressWarnings(value = { "unchecked", "rawtypes" }) public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } @Bean public DefaultRedisScript limitScript() { DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(limitScriptText()); redisScript.setResultType(Long.class); return redisScript; } /** * 限流脚本 */ private String limitScriptText() { return "local key = KEYS[1]\n" + "local count = tonumber(ARGV[1])\n" + "local time = tonumber(ARGV[2])\n" + "local current = redis.call('get', key);\n" + "if current and tonumber(current) > count then\n" + " return tonumber(current);\n" + "end\n" + "current = redis.call('incr', key)\n" + "if tonumber(current) == 1 then\n" + " redis.call('expire', key, time)\n" + "end\n" + "return tonumber(current);"; } } ``` 重新测试: 正常 ## ORM框架 ### 1. mybaits-plus整合 ```xml com.baomidou mybatis-plus-boot-starter 3.5.7 com.mysql mysql-connector-j 8.0.33 org.projectlombok lombok 1.18.32 ``` ### 2. 配置数据链接 ```yaml server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/demo-test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true username: root password: xxxxxxxx type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver ``` ### 3. 创建一个数据库表,以及结构 ```sql create table demo_test ( id bigint auto_increment comment '序号' primary key, username varchar(50) null comment '用户名', age int null comment '年龄' ) comment '测试表'; ``` ### 4. 创建持久化对象 ```java package com.weyoung.infrastructure.po; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author 33 meiko_ooo@163.com * @description 测试持久化对象 * @create 2024/6/24 18:59 */ @Data @AllArgsConstructor @NoArgsConstructor @Builder @TableName(value = "demo_test") public class DemoTestPO implements Serializable { @TableId(value = "id", type = IdType.AUTO) private Long id; private String username; private Integer age; } ``` ### 5. 创建baseMapper ```java package com.weyoung.infrastructure.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.weyoung.infrastructure.po.DemoTestPO; /** * @author 33 meiko_ooo@163.com * @description * @create 2024/6/24 19:03 */ public interface DemoTestDaoI extends BaseMapper { } ``` ### 6. 构建测试类 ```java package com.weyoung.test.demotest; import com.alibaba.fastjson2.JSONObject; import com.weyoung.infrastructure.mapper.DemoTestDaoI; import com.weyoung.infrastructure.po.DemoTestPO; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource; /** * @author 33 meiko_ooo@163.com * @description * @create 2024/6/24 19:04 */ @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class DemoTest { @Resource private DemoTestDaoI demoTestDao; @Test public void testDemo(){ DemoTestPO demo = demoTestDao.selectById(1); log.info(" [ demo ] :{}", JSONObject.toJSONString(demo)); } } ``` ### 7. note备注信息(在RUOYI中替换mbp) ```te 如果你需要在ruoyi中去替换成mybatis-plus 请查看: https://doc.ruoyi.vip/ruoyi-vue/document/cjjc.html#%E9%9B%86%E6%88%90mybatisplus%E5%AE%9E%E7%8E%B0mybatis%E5%A2%9E%E5%BC%BA ``` ## 编码前的准备工作 ### 1. 导入SQL文件 ```tex https://gitee.com/y_project/RuoYi-Vue/blob/master/sql/ry_20240529.sql ``` ### 2. 更改sys_user下的phonenumber为phone_number ### 3. 将项包结构,创建完毕 ![image-20240624195235755](/Users/freeloop/Library/Application Support/typora-user-images/image-20240624195235755.png) ![image-20240624201307040](/Users/freeloop/Library/Application Support/typora-user-images/image-20240624201307040.png) ### 4. 前后端通用返回对象 ```java package com.weyoung.application.dto; import java.util.HashMap; import java.util.Objects; /** * 操作消息提醒 * * @author ruoyi */ public class AjaxResult extends HashMap { private static final long serialVersionUID = 1L; /** * 状态码 */ public static final String CODE_TAG = "code"; /** * 返回内容 */ public static final String MSG_TAG = "msg"; /** * 数据对象 */ public static final String DATA_TAG = "data"; /** * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 */ public AjaxResult() { } /** * 初始化一个新创建的 AjaxResult 对象 * * @param code 状态码 * @param msg 返回内容 */ public AjaxResult(int code, String msg) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); } /** * 初始化一个新创建的 AjaxResult 对象 * * @param code 状态码 * @param msg 返回内容 * @param data 数据对象 */ public AjaxResult(int code, String msg, Object data) { super.put(CODE_TAG, code); super.put(MSG_TAG, msg); if (null != data) { super.put(DATA_TAG, data); } } /** * 返回成功消息 * * @return 成功消息 */ public static AjaxResult success() { return AjaxResult.success("操作成功"); } /** * 返回成功数据 * * @return 成功消息 */ public static AjaxResult success(Object data) { return AjaxResult.success("操作成功", data); } /** * 返回成功消息 * * @param msg 返回内容 * @return 成功消息 */ public static AjaxResult success(String msg) { return AjaxResult.success(msg, null); } /** * 返回成功消息 * * @param msg 返回内容 * @param data 数据对象 * @return 成功消息 */ public static AjaxResult success(String msg, Object data) { return new AjaxResult(200, msg, data); } /** * 返回警告消息 * * @param msg 返回内容 * @return 警告消息 */ public static AjaxResult warn(String msg) { return AjaxResult.warn(msg, null); } /** * 返回警告消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static AjaxResult warn(String msg, Object data) { return new AjaxResult(601, msg, data); } /** * 返回错误消息 * * @return 错误消息 */ public static AjaxResult error() { return AjaxResult.error("操作失败"); } /** * 返回错误消息 * * @param msg 返回内容 * @return 错误消息 */ public static AjaxResult error(String msg) { return AjaxResult.error(msg, null); } /** * 返回错误消息 * * @param msg 返回内容 * @param data 数据对象 * @return 错误消息 */ public static AjaxResult error(String msg, Object data) { return new AjaxResult(500, msg, data); } /** * 返回错误消息 * * @param code 状态码 * @param msg 返回内容 * @return 错误消息 */ public static AjaxResult error(int code, String msg) { return new AjaxResult(code, msg, null); } /** * 是否为成功消息 * * @return 结果 */ public boolean isSuccess() { return Objects.equals(200, this.get(CODE_TAG)); } /** * 是否为警告消息 * * @return 结果 */ public boolean isWarn() { return Objects.equals(601, this.get(CODE_TAG)); } /** * 是否为错误消息 * * @return 结果 */ public boolean isError() { return Objects.equals(500, this.get(CODE_TAG)); } /** * 方便链式调用 * * @param key 键 * @param value 值 * @return 数据对象 */ @Override public AjaxResult put(String key, Object value) { super.put(key, value); return this; } } ``` ## 登陆权限相关 ### 1. 拷贝持久化对象 ```java package com.weyoung.infrastructure.po; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; /** * 用户对象 sys_user * * @author 33 */ @Data @AllArgsConstructor @NoArgsConstructor @Builder @TableName(value = "sys_user") public class SysUserPO implements Serializable { private static final long serialVersionUID = 1L; /** * 用户ID */ @TableId(value = "user_id", type = IdType.AUTO) private Long userId; /** * 部门ID */ private Long deptId; /** * 用户账号 */ private String userName; /** * 用户昵称 */ private String nickName; /** * 用户邮箱 */ private String email; /** * 手机号码 */ private String phoneNumber; /** * 用户性别 */ private String sex; /** * 用户头像 */ private String avatar; /** * 密码 */ private String password; /** * 帐号状态(0正常 1停用) */ private String status; /** * 删除标志(0代表存在 2代表删除) */ private String delFlag; /** * 最后登录IP */ private String loginIp; /** * 最后登录时间 */ private Date loginDate; /** * 创建时间 */ private Date createTime; /** * 创建人 */ private String createBy; /** * 更新时间 */ private Date updateTime; /** * 更新人 */ private String updateBy; /** * 备注信息 */ private String remark; } ``` ### 2. 创建登陆命令对象 ```java package com.weyoung.application.dto.command; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author 33 meiko_ooo@163.com * @description 登陆命令对象 * @create 2024/6/24 19:56 */ @Data @AllArgsConstructor @NoArgsConstructor @Builder public class LoginBodyCommand implements Serializable { /** * 用户名 */ private String username; /** * 用户密码 */ private String password; /** * 验证码 */ private String code; /** * 唯一标识 */ private String uuid; } ``` ### 3.书写业务逻辑 **控制器** ```java package com.weyoung.interfaces.controller; import com.weyoung.basic.model.AjaxResult; import com.weyoung.application.dto.command.LoginBodyCommand; import com.weyoung.application.service.LoginServiceI; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author 33 meiko_ooo@163.com * @description 登陆授权控制器 * @create 2024/6/24 19:41 */ @RestController public class AuthController { @Resource private LoginServiceI userService; @PostMapping(value = "/login") public AjaxResult login(@RequestBody LoginBodyCommand reqCommand) { String token = userService.login(reqCommand); return AjaxResult.success().put("token", token); } } ``` **** **应用层** ```java package com.weyoung.application.service.impl; import cn.hutool.core.map.MapUtil; import com.weyoung.application.dto.command.LoginBodyCommand; import com.weyoung.application.service.LoginServiceI; import com.weyoung.domain.user.aggressive.LoginUserAggressive; import com.weyoung.domain.user.service.SecurityComponent; import com.weyoung.domain.user.service.TokenService; import com.weyoung.domain.user.service.channel.CaptchaInitConfig; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.HashMap; import java.util.Map; /** * @author 33 meiko_ooo@163.com * @description 应用编排层实现 * @create 2024-06-24 22:31 */ @Service public class LoginServiceImpl extends CaptchaInitConfig implements LoginServiceI { @Resource private SecurityComponent securityComponent; @Resource private TokenService tokenService; @Override public String login(LoginBodyCommand reqCommand) { // step1. 封装对象校验需要的对象 Map param = MapUtil.builder(new HashMap()) .put("code", reqCommand.getCode()) .put("uuid", reqCommand.getUuid()).build(); channel.get("qr-code").validateCaptcha(reqCommand.getUsername(), param); // step 2.登录前置校验(账号密码校验) LoginUserAggressive loginUser = securityComponent.usernamePasswordAuthenticationToken(reqCommand.getUsername(), reqCommand.getPassword()); return tokenService.createToken(loginUser); } } ``` **** **各个领域层代码** ```java package com.weyoung.domain.user.service; import com.alibaba.fastjson2.JSONObject; import com.weyoung.domain.user.aggressive.LoginUserAggressive; import com.weyoung.domain.user.holder.AuthenticationContextHolder; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * @author 33 meiko_ooo@163.com * @description 认证组件 * @create 2024-06-24 22:57 */ @Component public class SecurityComponent { @Resource private AuthenticationManager authenticationManager; public LoginUserAggressive usernamePasswordAuthenticationToken(String username, String password) { // 用户验证 Authentication authentication = null; try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); AuthenticationContextHolder.setContext(authenticationToken); // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { if (e instanceof BadCredentialsException) { throw new RuntimeException("账号密码错误"); } else { throw new RuntimeException("系统异常"); } } finally { AuthenticationContextHolder.clearContext(); } /* LoginUser loginUser = (LoginUser) authentication.getPrincipal(); recordLoginInfo(loginUser.getUserId());*/ // 生成token return new LoginUserAggressive(); } } ``` ```java package com.weyoung.domain.user.service; import com.weyoung.domain.user.aggressive.LoginUserAggressive; import org.springframework.stereotype.Service; /** * @author 33 meiko_ooo@163.com * @description Token创建 * @create 2024-06-24 23:01 */ @Service public class TokenService { //TODO 临时写法 public String createToken(LoginUserAggressive loginUser) { return "token:123456"; } } ``` ```java package com.weyoung.domain.user.service; import com.alibaba.fastjson2.JSONObject; import com.weyoung.domain.user.aggressive.LoginUserAggressive; import com.weyoung.domain.user.entity.UserEntity; import com.weyoung.domain.user.repository.SysUserRepositoryI; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.HashSet; import java.util.Set; /** * @author 33 meiko_ooo@163.com * @description spring security 实现 * @create 2024-06-24 23:13 */ @Component @Slf4j public class UserDetailsServiceImpl implements UserDetailsService { @Resource private SysUserRepositoryI userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserEntity userEntity = userRepository.loadUserByUserName(username); log.info(" [ 用户实体信息 ] - {} ", JSONObject.toJSONString(userEntity)); return createLoginUser(userEntity); } public UserDetails createLoginUser(UserEntity user) { Set perms = new HashSet<>(); //TODO 先给管理员权限 perms.add("*:*:*"); LoginUserAggressive loginUserAggressive = new LoginUserAggressive(user.getUserId(), user.getDeptId(), user, perms); loginUserAggressive.checkUserStatus(); return loginUserAggressive; } } ``` ### 4. JWT完成整合 引入JWT依赖 ```xml io.jsonwebtoken jjwt ``` 常量类 ```java package com.weyoung.basic.constants; /** * 缓存的key 常量 * * @author ruoyi */ public class CacheConstants { /** * 登录用户 redis key */ public static final String LOGIN_TOKEN_KEY = "login_tokens:"; /** * 验证码 redis key */ public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; /** * 参数管理 cache key */ public static final String SYS_CONFIG_KEY = "sys_config:"; /** * 字典管理 cache key */ public static final String SYS_DICT_KEY = "sys_dict:"; /** * 防重提交 redis key */ public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; /** * 限流 redis key */ public static final String RATE_LIMIT_KEY = "rate_limit:"; /** * 登录账户密码错误次数 redis key */ public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; } ``` ```java package com.weyoung.basic.constants; import io.jsonwebtoken.Claims; import java.util.Locale; /** * 通用常量信息 * * @author ruoyi */ public class Constants { /** * UTF-8 字符集 */ public static final String UTF8 = "UTF-8"; /** * GBK 字符集 */ public static final String GBK = "GBK"; /** * 系统语言 */ public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; /** * www主域 */ public static final String WWW = "www."; /** * http请求 */ public static final String HTTP = "http://"; /** * https请求 */ public static final String HTTPS = "https://"; /** * 通用成功标识 */ public static final String SUCCESS = "0"; /** * 通用失败标识 */ public static final String FAIL = "1"; /** * 登录成功 */ public static final String LOGIN_SUCCESS = "Success"; /** * 注销 */ public static final String LOGOUT = "Logout"; /** * 注册 */ public static final String REGISTER = "Register"; /** * 登录失败 */ public static final String LOGIN_FAIL = "Error"; /** * 所有权限标识 */ public static final String ALL_PERMISSION = "*:*:*"; /** * 管理员角色权限标识 */ public static final String SUPER_ADMIN = "admin"; /** * 角色权限分隔符 */ public static final String ROLE_DELIMETER = ","; /** * 权限标识分隔符 */ public static final String PERMISSION_DELIMETER = ","; /** * 验证码有效期(分钟) */ public static final Integer CAPTCHA_EXPIRATION = 2; /** * 令牌 */ public static final String TOKEN = "token"; /** * 令牌前缀 */ public static final String TOKEN_PREFIX = "Bearer "; /** * 令牌前缀 */ public static final String LOGIN_USER_KEY = "login_user_key"; /** * 用户ID */ public static final String JWT_USERID = "userid"; /** * 用户名称 */ public static final String JWT_USERNAME = Claims.SUBJECT; /** * 用户头像 */ public static final String JWT_AVATAR = "avatar"; /** * 创建时间 */ public static final String JWT_CREATED = "created"; /** * 用户权限 */ public static final String JWT_AUTHORITIES = "authorities"; /** * 资源映射路径 前缀 */ public static final String RESOURCE_PREFIX = "/profile"; /** * RMI 远程方法调用 */ public static final String LOOKUP_RMI = "rmi:"; /** * LDAP 远程方法调用 */ public static final String LOOKUP_LDAP = "ldap:"; /** * LDAPS 远程方法调用 */ public static final String LOOKUP_LDAPS = "ldaps:"; /** * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) */ public static final String[] JSON_WHITELIST_STR = {"org.springframework", "com.weyoung"}; /** * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) */ public static final String[] JOB_WHITELIST_STR = {"com.ruoyi.quartz.task"}; /** * TODO 后续更改 定时任务违规的字符 */ public static final String[] JOB_ERROR_STR = {"java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator"}; } ``` ```java package com.weyoung.domain.user.service; import cn.hutool.core.lang.UUID; import com.weyoung.basic.constants.CacheConstants; import com.weyoung.basic.constants.Constants; import com.weyoung.domain.user.aggressive.LoginUserAggressive; import com.weyoung.domain.user.repository.RedisAuthRepositoryI; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @author 33 meiko_ooo@163.com * @description Token创建 * @create 2024-06-24 23:01 */ @Service public class TokenService { @Resource private RedisAuthRepositoryI redisAuthRepository; public String createToken(LoginUserAggressive loginUser) { String token = UUID.fastUUID().toString(); loginUser.setToken(token); setUserAgent(loginUser); refreshToken(loginUser); Map claims = new HashMap<>(); claims.put(Constants.LOGIN_USER_KEY, token); return createToken(claims); } private String createToken(Map claims) { return Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, "abcdefghjdasdasdasd").compact(); } /** * 设置用户代理信息 * * @param loginUser 登录信息 */ public void setUserAgent(LoginUserAggressive loginUser) { loginUser.setIpaddr("127.0.0.1"); loginUser.setLoginLocation("浙江杭州"); loginUser.setBrowser("Google"); loginUser.setOs("windows"); } public void refreshToken(LoginUserAggressive loginUser) { loginUser.setLoginTime(System.currentTimeMillis()); // TODO 设置默认30秒时间 loginUser.setExpireTime(loginUser.getLoginTime() + 30 * 1000); // 根据uuid将loginUser缓存 String userKey = getTokenKey(loginUser.getToken()); redisAuthRepository.setCacheObj(userKey, loginUser, 30, TimeUnit.MINUTES); } private String getTokenKey(String uuid) { return CacheConstants.LOGIN_TOKEN_KEY + uuid; } } ``` ## 全局异常处理 ### 1. 书写一个配置类 ```java package com.weyoung.basic.exception; import com.weyoung.basic.model.AjaxResult; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; /** * @author 33 meiko_ooo@163.com * @description 全局异常处理 * @create 2024-07-02 23:58 */ @RestControllerAdvice @Slf4j public class WebException { @ExceptionHandler(value = RuntimeException.class) public AjaxResult handlerRuntimeException(RuntimeException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); return AjaxResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); } } ``` ## 流媒体服务器搭建(推流,拉流) ``` 安装GIT sudo yum install git git --version 步骤1 sudo yum install openssl-devel sudo yum install SDL-devel sudo yum install ffmpeg-devel 失败执行: sudo yum install https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm sudo yum install ffmpeg-devel 步骤2 cd ZLMediaKit (重试 需增加 rm -rf build 删除build) mkdir build cd build cmake .. make -j4 cmake ..失败,提示版本低,按照下面的步骤操作。 安装高版本的cmake sudo yum remove cmake cd /tmp wget https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0.tar.gz tar -zxvf cmake-3.20.0.tar.gz cd cmake-3.20.0 ./bootstrap make sudo make install cmake --version whereis cmake vim ~/.bash_profile export PATH="具体的路径:$PATH" 生效 source ~/.bash_profile 重试步骤2,记得先删除build下的缓存文件 ``` ```tex 2024-07-04 22:55:14.880 I [MediaServer] [25091-MediaServer] Factory.cpp:41 registerPlugin | Load codec: L16 -h --help 无参 默认:null 选填 打印此信息 -d --daemon 无参 默认:null 选填 是否以Daemon方式启动 -l --level 有参 默认:1 选填 日志等级,LTrace~LErr or(0~4) -m --max_day 有参 默认:7 选填 日志最多保存天数 -c --config 有参 默认:/home/media/ZLMediaKit/release/linux/Debug/config.ini 选填 配置文件路径 -s --ssl 有参 默认:/home/media/ZLMediaKit/release/linux/Debug/default.pem 选填 ssl证书文件或文件夹, 支持p12/pem类型 -t --threads 有参 默认:4 选填 启动事件触发线程数 --affinity 有参 默认:1 选填 是否启动cpu亲和性设 置 -v --version 无参 默认:null 选填 显示版本号 --log-slice 有参 默认:100 选填 最大保存日志切片个数 --log-size 有参 默认:256 选填 单个日志切片最大容量 ,单位MB --log-dir 有参 默认:/home/media/ZLMediaKit/release/linux/Debug/log/ 选填 日志保存文件夹路径 ``` ``` 推送一个视频 ffmpeg -re -i "C:\Users\Administrator\Downloads\ffmpeg-release-essentials\ffmpeg-7.0.1-essentials_build\bin\自动切换_bilibili.mp4" -vcodec h264 -acodec aac -f flv rtmp://114.67.240.83/live/test http://114.67.240.83/live/test.live.flv ``` 查看本地电脑的音频设备 ``` ffmpeg -list_devices true -f dshow -i dummy [dshow @ 000001a963bf2580] "OBS Virtual Camera" (video) [dshow @ 000001a963bf2580] Alternative name "@device_sw_{860BB310-5D01-11D0-BD3B-00A0C911CE86}\{A3FCE0F5-3493-419F-958A-ABA1250EC20B}" [dshow @ 000001a963bf2580] "Mix 01 (ASIOVADPRO Driver)" (audio) [dshow @ 000001a963bf2580] Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{B3DF5A7D-7423-42B5-AB76-A36458F6F9B7}" ``` Mix 01 (ASIOVADPRO Driver) 是我自己电脑的音频设备 ``` ffmpeg -f gdigrab -framerate 30 -i desktop -f dshow -i audio="" -vcodec libx264 -pix_fmt yuv420p -preset ultrafast -acodec aac -strict -2 -f flv rtmp://114.67.240.83/live/test ffmpeg -f gdigrab -framerate 30 -i desktop -f dshow -i audio="Mix 01 (ASIOVADPRO Driver)" -vcodec libx264 -pix_fmt yuv420p -preset ultrafast -acodec aac -strict -2 -f flv rtmp://114.67.240.83/live/test http://114.67.240.83/live/test.live.flv ``` 视频流,鉴权推送,以及鉴权播放