pom.xml
@@ -294,7 +294,11 @@ <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.16</version> </dependency> </dependencies> <profiles> <profile> src/main/java/org/springblade/common/desensitization/Desensitization.java
New file @@ -0,0 +1,28 @@ package org.springblade.common.desensitization; import cn.hutool.core.util.DesensitizedUtil; import java.lang.annotation.*; import static cn.hutool.core.util.DesensitizedUtil.DesensitizedType.FIXED_PHONE; /** * 脱敏 * @author zhongrj * @date 2023-09-11 */ public @interface Desensitization { /** * json path 的标识 * @return */ String jsonPath(); /** * 脱敏的字段的数据分类, 默认是座机号码类型脱敏 * @return */ DesensitizedUtil.DesensitizedType desensitizedType() default FIXED_PHONE; } src/main/java/org/springblade/common/desensitization/DesensitizationAspect.java
New file @@ -0,0 +1,143 @@ package org.springblade.common.desensitization; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.DesensitizedUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONPath; import liquibase.util.StringUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springblade.core.secure.utils.AuthUtil; import org.springframework.stereotype.Component; import static cn.hutool.core.util.DesensitizedUtil.DesensitizedType.FIXED_PHONE; /** * 数据脱敏切面 * @author zhongrj * @date 2023-09-11 */ @Slf4j @Aspect @Component public class DesensitizationAspect { /** * 脱敏环绕通知 * @param pjp * @param desensitizationWord * @return * @throws Throwable */ @Around("@annotation(desensitizationWord)") public Object around(ProceedingJoinPoint pjp, DesensitizationWord desensitizationWord) throws Throwable{ Object object = pjp.proceed(); // 判断是否需要脱敏处理 if (isDesensitization()) { try { Class<?> aClass = object.getClass(); // 改变返回值 Desensitization[] value = desensitizationWord.value(); JSONObject ob = JSON.parseObject(JSON.toJSONString(object)); // 循环处理每一个 json 路径下的值 for (Desensitization s : value) { replace(ob, s); } object = JSON.parseObject(JSON.toJSONString(ob), aClass); } catch (Exception e) { log.info("数据脱敏失败:" + e.getMessage()); } } return object; } /** * 判断是否需要脱敏 * @return */ private boolean isDesensitization() { // 获取用户角色,根据角色判断是否进行脱敏处理 String userRole = AuthUtil.getUserRole(); // 如果用户角色为null,则需要脱敏 if (null!=userRole && !userRole.equals("")){ // 判断角色是否为民警角色,如果是则不需要脱敏,否则需要脱敏处理 if (userRole.equals("民警")){ return false; } } return true; } /** * 脱敏 */ private Object sensitiveByString(Object value) { if (StringUtils.isNotEmpty(value.toString())) { String st = Convert.toStr(value); st = st.substring(0, st.length() - 3 > 0 ? 3 : st.length()) + "****" + st.substring(Math.max(st.length() - 4, 0)); return st; } return value; } /** * 敏感数据替换 * * @param jsonObject * @param s */ private void replace(JSONObject jsonObject, Desensitization s) { // 只有传入的 JSON 路径在 这个 JSONObject 中才会进行脱敏处理 if (JSONPath.contains(jsonObject, s.jsonPath())) { // 查询是否有数组 列表 类型的数据需要脱敏 int index = s.jsonPath().lastIndexOf("[*]"); if (index > -1) { String prefix = StrUtil.subPre(s.jsonPath(), index); String suffix = StrUtil.subSuf(s.jsonPath(), index + 3); // 提取json 路径下的 数组\链表 元素 Object eval = JSONPath.eval(jsonObject, prefix); // 将数组\链表 元素 转为 JSONArray 方便做 统一格式处理 JSONArray jsonArray = (JSONArray) eval; int size = jsonArray.size(); for (int i = 0; i < size; i++) { // 由于脱敏数组内部的参数传入格式为 :jsonPath = "$.data.records[*].username" // 所以需要重新组装 jsonPath 将 * 号 替换成具体的值 String indexJsonPath = StrUtil.strBuilder().append(prefix).append("[").append(i).append("]").append(suffix).toString(); // 使用 cn.hutool.core.convert Convert.toStr 转换为字符串 如果给定的值为null,或者转换失败,返回默认值null,这样可以减少报错,避免程序异常 String desensitized = Convert.toStr(JSONPath.eval(jsonObject, indexJsonPath)); if (StrUtil.isBlank(desensitized)) { continue; } // 如果是默认指定 则使用默认方式脱敏 if (s.desensitizedType() == FIXED_PHONE) { desensitized = sensitiveByString(desensitized).toString(); }else { // 否则使用 cn.hutool.core.util 进行数据脱敏 desensitized = DesensitizedUtil.desensitized(desensitized, s.desensitizedType()); } // 使用JSON 路径操作,将已经脱敏的新数据,放入之前未脱敏的数据地址处,替换未脱敏数据 JSONPath.set(jsonObject, indexJsonPath, desensitized); } } else { // 使用 cn.hutool.core.convert Convert.toStr 转换为字符串 如果给定的值为null,或者转换失败,返回默认值null,这样可以减少报错,避免程序异常 Object eval = JSONPath.eval(jsonObject, Convert.toStr(s.jsonPath())); String desensitized = ""; if (s.desensitizedType() == FIXED_PHONE) { desensitized = sensitiveByString(Convert.toStr(eval)).toString(); }else { desensitized = DesensitizedUtil.desensitized(Convert.toStr(eval), s.desensitizedType()); } JSONPath.set(jsonObject, s.jsonPath(), desensitized); } } } } src/main/java/org/springblade/common/desensitization/DesensitizationWord.java
New file @@ -0,0 +1,16 @@ package org.springblade.common.desensitization; import java.lang.annotation.*; /** * 脱敏 * @author zhongrj * @date 2023-09-11 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DesensitizationWord { Desensitization [] value(); } src/main/java/org/springblade/modules/auth/endpoint/BladeTokenEndPoint.java
@@ -158,13 +158,17 @@ } } } }else { System.out.println("grantType = " + grantType); //刷新 token 不新增登录记录 // if (!grantType.equals("refresh_token")){ // //新增登录记录 // this.saveLoginRecord(userInfo); // } if (!grantType.equals("refresh_token")){ //新增登录记录 this.saveLoginRecord(userInfo); } }else { //刷新 token 不新增登录记录 if (!grantType.equals("refresh_token")){ //新增登录记录 this.saveLoginRecord(userInfo); } } return TokenUtil.createAuthInfo(userInfo); } src/main/java/org/springblade/modules/quartz/task/Task.java
@@ -1,5 +1,6 @@ package org.springblade.modules.quartz.task; import org.springblade.modules.system.service.IUserService; import org.springblade.modules.training.service.TrainingRegistrationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; @@ -20,6 +21,9 @@ @Autowired private TrainingRegistrationService trainingRegistrationService; @Autowired private IUserService userService; public void testTask(){ System.out.println("测试定时任务执行-----------------"); } @@ -38,11 +42,26 @@ /** * 定时任务,处理考试中的人员 */ // @Scheduled(cron = "0 0 22 * * ?") public void examLoading(){ System.out.println("定时任务2:执行时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); System.out.println("定时任务,处理考试中的人员:执行时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); //自动处理之前报了名,考试忘记提交或者中断考试的 trainingRegistrationService.examLoading(); } /** * 定时任务,6个月未登录人员,进行冻结 */ public void sixMonthNotLoginHandle(){ System.out.println("定时任务,6个月未登录人员,进行冻结:执行时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); userService.sixMonthNotLoginHandle(); } /** * 定时任务,12个月未登录人员,进行注销 */ public void oneYearNotLoginHandle(){ System.out.println("定时任务,12个月未登录人员,进行注销:执行时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); userService.oneYearNotLoginHandle(); } } src/main/java/org/springblade/modules/system/mapper/UserMapper.java
@@ -279,4 +279,24 @@ * @return */ User getUserByPhone(@Param("tenantId")String tenantId,@Param("phone") String phone); /** * 按天查询 day 天未登录的人员ids集合信息 * @param day * @return */ List<String> getMoreDayNotLoginUser(@Param("day")int day); /** * 用户冻结操作 * @param userIds */ void updateFreeze(@Param("list") List<String> userIds); /** * 用户注销操作 * @param userIds */ void updateLogout(@Param("list") List<String> userIds); } src/main/java/org/springblade/modules/system/mapper/UserMapper.xml
@@ -1144,5 +1144,48 @@ and tenant_id = #{tenantId} and phone = #{phone} </select> <!--按天查询 day 天未登录的人员ids集合信息--> <select id="getMoreDayNotLoginUser" resultType="java.lang.String"> select bu.id from blade_user bu left join ( select user_id,max(create_time) as create_time from sys_login_record GROUP BY user_id )slr on bu.id = slr.user_id where bu.status = 1 and bu.is_deleted = 0 and bu.role_id = 1412226235153731586 and DATE_SUB(CURDATE(), INTERVAL #{day} DAY) > date(bu.create_time) and DATE_SUB(CURDATE(), INTERVAL #{day} DAY) > date(slr.create_time) </select> <!--用户冻结操作--> <update id="updateFreeze"> update blade_user set is_deleted = 1 <choose> <when test="list!=null and list.size()>0"> where id in <foreach collection="list" item="id" open="(" close=")" separator=","> #{id} </foreach> </when> <otherwise> where id in ('') </otherwise> </choose> </update> <!--用户注销操作--> <update id="updateLogout"> update blade_user set is_deleted = 1 <choose> <when test="list!=null and list.size()>0"> where id in <foreach collection="list" item="id" open="(" close=")" separator=","> #{id} </foreach> </when> <otherwise> where id in ('') </otherwise> </choose> </update> </mapper> src/main/java/org/springblade/modules/system/service/IUserService.java
@@ -394,4 +394,14 @@ * @return */ UserInfo userInfoByWx(String tenantId, String phone, UserEnum web); /** * 6个月未登录人员,进行冻结 */ void sixMonthNotLoginHandle(); /** * 12个月未登录人员,进行注销 */ void oneYearNotLoginHandle(); } src/main/java/org/springblade/modules/system/service/impl/UserServiceImpl.java
@@ -1991,4 +1991,26 @@ public User getUserById(String id) { return baseMapper.getUserById(Long.parseLong(id)); } /** * 6个月未登录人员,进行冻结 */ @Override public void sixMonthNotLoginHandle() { // 查询6个月未登录人员 List<String> userIds = baseMapper.getMoreDayNotLoginUser(183); // 统一冻结操作 baseMapper.updateFreeze(userIds); } /** * 12个月未登录人员,进行注销 */ @Override public void oneYearNotLoginHandle() { // 查询12个月未登录人员 List<String> userIds = baseMapper.getMoreDayNotLoginUser(366); // 统一注销操作 baseMapper.updateLogout(userIds); } } src/main/java/org/springblade/modules/test/TestController.java
New file @@ -0,0 +1,75 @@ package org.springblade.modules.test; import com.yunpian.sdk.model.Result; import lombok.AllArgsConstructor; import org.springblade.common.desensitization.Desensitization; import org.springblade.common.desensitization.DesensitizationWord; import org.springblade.core.tool.api.R; import org.springblade.modules.system.entity.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import static cn.hutool.core.util.DesensitizedUtil.DesensitizedType.*; @AllArgsConstructor @RestController @RequestMapping("/test") public class TestController { @GetMapping("/getUserInfo") @DesensitizationWord({ @Desensitization(jsonPath = "$.data.name",desensitizedType = CHINESE_NAME), @Desensitization(jsonPath = "$.data.phone",desensitizedType = MOBILE_PHONE), @Desensitization(jsonPath = "$.data.bankCard",desensitizedType = BANK_CARD), }) public R getUserInfo() { Map<String, String> map = new HashMap<>(); map.put("name","张三"); map.put("phone","18111111111"); map.put("bankCard","6227112222211111211"); return R.data(map); } /** * 对象脱敏 * @return */ @GetMapping("getInfo2") @DesensitizationWord({ @Desensitization(jsonPath = "$.data.phone",desensitizedType = MOBILE_PHONE), @Desensitization(jsonPath = "$.data.realName",desensitizedType = CHINESE_NAME), }) public R getCaseInfo2() { User user = new User(); user.setPhone("18111111111"); user.setRealName("cs1"); return R.data(user); } /** * 集合脱敏 * @return */ @GetMapping("getInfo3") @DesensitizationWord(@Desensitization(jsonPath = "$.data[*].phone")) public R getCaseInfo(){ List<User> users = new ArrayList<>(); User user = new User(); user.setPhone("18111111111"); User user1 = new User(); user1.setPhone("18222222222"); users.add(user); users.add(user1); return R.data(users); } } src/main/resources/application-test.yml
@@ -15,7 +15,7 @@ # MySql url: jdbc:mysql://127.0.0.1:3308/zhbaw_test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true username: root password: 123456 password: root # rabbitmq 设置 # rabbitmq: # host: 192.168.0.191 @@ -79,9 +79,9 @@ enable: stop type: sql driver: com.mysql.cj.jdbc.Driver url: jdbc:mysql://106.225.193.35:3306/zhbaw-test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true url: jdbc:mysql://127.0.0.1:3308/zhbaw_test?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true username: root password: HCyj@2022 password: root #第三方登陆 social: src/main/resources/application.yml
@@ -195,6 +195,7 @@ secure: #接口放行 skip-url: - /test/** - /blade-test/** - /blade-system/dept/selectInfo - /blade-user/zc