Commit 655980ca by zhangxingmin

push

parent 95df52a3
package com.yd.communication.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class AsyncConfig {
@Bean(name = "communicationExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(10); // 最大线程数
executor.setQueueCapacity(100); // 队列容量
executor.setThreadNamePrefix("communication-"); // 线程名前缀
executor.initialize();
return executor;
}
}
\ No newline at end of file
package com.yd.communication.api.controller;
import com.yd.common.result.Result;
import com.yd.communication.api.service.ApiCoDesensitizationRuleService;
import com.yd.communication.feign.client.ApiCoDesensitizationRuleFeignClient;
import com.yd.communication.feign.response.desensitization.ApiCoDesensitizationRuleResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
/**
* 脱敏信息
*
* @author zxm
* @since 2026-07-28
*/
@Slf4j
@RestController
@RequestMapping("/desensitization")
@Validated
public class ApiCoDesensitizationRuleController implements ApiCoDesensitizationRuleFeignClient {
@Resource
private ApiCoDesensitizationRuleService apiCoDesensitizationRuleService;
/**
* 根据 ID 查询单条规则
* @param bizId
* @return
*/
@Override
public Result<ApiCoDesensitizationRuleResponse> getByBizId(String bizId) {
return apiCoDesensitizationRuleService.getByBizId(bizId);
}
/**
* 根据资源类型和资源ID获取生效的脱敏规则列表(供脱敏引擎调用)
* @param resourceType
* @param resourceId
* @return
*/
@Override
public Result<List<ApiCoDesensitizationRuleResponse>> getEnabledRules(String resourceType, String resourceId) {
return apiCoDesensitizationRuleService.getEnabledRules(resourceType,resourceType);
}
}
\ No newline at end of file
package com.yd.communication.api.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 协同-脱敏设置表(通用) 前端控制器
* </p>
*
* @author zxm
* @since 2026-07-28
*/
@RestController
@RequestMapping("/coDesensitizationRule")
public class CoDesensitizationRuleController {
}
package com.yd.communication.api.service;
import com.yd.common.result.Result;
import com.yd.communication.feign.response.desensitization.ApiCoDesensitizationRuleResponse;
import java.util.List;
public interface ApiCoDesensitizationRuleService {
Result<ApiCoDesensitizationRuleResponse> getByBizId(String bizId);
Result<List<ApiCoDesensitizationRuleResponse>> getEnabledRules(String resourceType, String resourceId);
}
package com.yd.communication.api.service.impl;
import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result;
import com.yd.communication.api.service.ApiCoDesensitizationRuleService;
import com.yd.communication.feign.response.desensitization.ApiCoDesensitizationRuleResponse;
import com.yd.communication.service.model.CoDesensitizationRule;
import com.yd.communication.service.service.ICoDesensitizationRuleService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class ApiCoDesensitizationRuleServiceImpl implements ApiCoDesensitizationRuleService {
@Resource
private ICoDesensitizationRuleService ruleService;
/**
* 根据 ID 查询单条规则
* @param bizId
* @return
*/
@Override
public Result<ApiCoDesensitizationRuleResponse> getByBizId(String bizId) {
CoDesensitizationRule rule = ruleService.lambdaQuery()
.eq(CoDesensitizationRule::getRuleBizId,bizId)
.last(" limit 1 ")
.one();
if (rule == null) {
throw new BusinessException("脱敏信息不存在");
}
ApiCoDesensitizationRuleResponse response = new ApiCoDesensitizationRuleResponse();
BeanUtils.copyProperties(rule,response);
return Result.success(response);
}
/**
* 根据资源类型和资源ID获取生效的脱敏规则列表(供脱敏引擎调用)
* @param resourceType
* @param resourceId
* @return
*/
@Override
public Result<List<ApiCoDesensitizationRuleResponse>> getEnabledRules(String resourceType, String resourceId) {
List<CoDesensitizationRule> rules = ruleService.getEnabledRulesByResource(resourceType, resourceId);
if (CollectionUtils.isEmpty(rules)) {
return Result.success();
}
List<ApiCoDesensitizationRuleResponse> responses = rules.stream().map(dto -> {
ApiCoDesensitizationRuleResponse response = new ApiCoDesensitizationRuleResponse();
BeanUtils.copyProperties(dto,response);
return response;
}).collect(Collectors.toList());
return Result.success(responses);
}
}
......@@ -7,6 +7,7 @@ import com.yd.common.result.Result;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.common.utils.RedisUtil;
import com.yd.communication.api.service.ApiCoSessionService;
import com.yd.communication.api.utils.ClientInfoUtils;
import com.yd.communication.feign.dto.RoomRedisInfoDTO;
import com.yd.communication.feign.enums.CoSessionStatusEnum;
import com.yd.communication.feign.enums.ControlHolderTypeEnum;
......@@ -20,6 +21,7 @@ import com.yd.communication.feign.response.CreateResponse;
import com.yd.communication.feign.response.JoinResponse;
import com.yd.communication.feign.response.SessionDetailResponse;
import com.yd.communication.service.model.CoSession;
import com.yd.communication.service.service.ICoOperationLogService;
import com.yd.communication.service.service.ICoSessionService;
import com.yd.communication.service.service.IRecordingTaskService;
import com.yd.communication.service.utils.RandomUtil;
......@@ -28,7 +30,6 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.UUID;
......@@ -44,6 +45,9 @@ public class ApiCoSessionServiceImpl implements ApiCoSessionService {
private IRecordingTaskService recordingService;
@Resource
private ICoOperationLogService operationLogService;
@Resource
private RedisUtil redisUtil;
/**
......@@ -138,8 +142,19 @@ public class ApiCoSessionServiceImpl implements ApiCoSessionService {
// recordingService.startRecording(session.getCoSessionBizId(), roomId);
log.info("【创建会话-内部】自动录制已触发(未实际启动)");
}
log.info("【创建会话-内部】创建成功, roomId={}, roomPwd={}", roomId, roomPwd);
//生成协同操作日志
operationLogService.log(
session.getCoSessionBizId(),
ownerId,
ownerType,
ownerId,
"create_session",
String.format("创建会话 roomId=%s, roomPwd=%s", session.getRoomId(), session.getRoomPwd()),
ClientInfoUtils.getDevice(),
ClientInfoUtils.getClientIp()
);
return session;
} catch (Exception e) {
log.error("【创建会话-内部】异常", e);
......@@ -257,6 +272,17 @@ public class ApiCoSessionServiceImpl implements ApiCoSessionService {
log.warn("【加入会话-内部】Redis缓存不存在,可能已过期,roomId={}", session.getRoomId());
}
//生成协同操作日志
operationLogService.log(
session.getCoSessionBizId(),
participantId,
participantType,
participantId,
"join_session",
String.format("加入会话 roomId=%s, 控制权转移至%s", session.getRoomId(), session.getControlHolderType()),
ClientInfoUtils.getDevice(),
ClientInfoUtils.getClientIp()
);
return session;
} catch (Exception e) {
log.error("【加入会话-内部】异常", e);
......@@ -314,9 +340,17 @@ public class ApiCoSessionServiceImpl implements ApiCoSessionService {
redisUtil.deleteObject(RedisEnum.ROOM.getPrefix() + coSession.getRoomId());
log.info("【结束会话】Redis缓存已删除");
// 结束录制视频,并且更新录制任务表信息存档 TODO
// 添加操作日志,协同-操作日志表 TODO
// 添加操作日志,协同-操作日志表
operationLogService.log(
coSession.getCoSessionBizId(),
null,
null,
null,
"end_session",
String.format("结束会话 roomId=%s", coSession.getRoomId()),
ClientInfoUtils.getDevice(),
ClientInfoUtils.getClientIp()
);
CommonResponse response = new CommonResponse();
response.setMessage("会话已结束");
......@@ -395,7 +429,17 @@ public class ApiCoSessionServiceImpl implements ApiCoSessionService {
} else {
log.warn("【切换控制权-内部】Redis缓存不存在,可能已过期");
}
// 添加操作日志,协同-操作日志表 TODO
// 添加操作日志,协同-操作日志表
operationLogService.log(
session.getCoSessionBizId(),
session.getParticipantId(),
ControlHolderTypeEnum.PARTICIPANT.getItemValue(),
session.getParticipantId(),
"transfer_control",
String.format("切换控制权至 %s:%s", session.getControlHolderType(), session.getControlHolderId()),
ClientInfoUtils.getDevice(),
ClientInfoUtils.getClientIp()
);
} catch (Exception e) {
log.error("【切换控制权-内部】异常", e);
throw e;
......
......@@ -3,15 +3,15 @@ package com.yd.communication.api.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yd.common.enums.CommonEnum;
import com.yd.common.enums.ProjectEnum;
import com.yd.common.enums.TenantEnum;
import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.communication.api.service.ApiRecordingTaskService;
import com.yd.communication.api.utils.ClientInfoUtils;
import com.yd.communication.feign.request.recording.ApiStartRecordingRequest;
import com.yd.communication.feign.response.recording.ApiQueryRecordingResponse;
import com.yd.communication.service.model.RecordingTask;
import com.yd.communication.service.service.ICoOperationLogService;
import com.yd.communication.service.service.IRecordingTaskService;
import com.yd.oss.feign.client.ApiOssFeignClient;
import com.yd.oss.feign.request.ApiUploadFileRequest;
......@@ -39,6 +39,9 @@ public class ApiRecordingTaskServiceImpl implements ApiRecordingTaskService {
private ApiOssFeignClient apiOssFeignClient;
@Resource
private ICoOperationLogService operationLogService;
@Resource
private ObjectMapper objectMapper; // 注入 Jackson ObjectMapper
/**
......@@ -74,6 +77,18 @@ public class ApiRecordingTaskServiceImpl implements ApiRecordingTaskService {
iRecordingTaskService.save(task);
log.info("录制任务创建成功,taskId={}", task.getTaskId());
//生成协同操作日志
operationLogService.log(
request.getBizId(),
"system",
"system",
"system",
"start_recording",
String.format("开始录制 taskId=%s, bizType=%s", task.getTaskId(), request.getBizType()),
ClientInfoUtils.getDevice(),
ClientInfoUtils.getClientIp()
);
return Result.success(task.getTaskId());
}
......@@ -142,6 +157,18 @@ public class ApiRecordingTaskServiceImpl implements ApiRecordingTaskService {
result.put("fileKey", uploadResponse.getFileKey());
log.info("录制文件上传成功,fileUrl={}", task.getFileUrl());
//生成协同操作日志
operationLogService.log(
task.getBizId(),
"system",
"system",
"system",
"stop_recording",
String.format("停止录制 taskId=%s, fileUrl=%s", task.getTaskId(), task.getFileUrl()),
ClientInfoUtils.getDevice(),
ClientInfoUtils.getClientIp()
);
return Result.success(result);
}
......
package com.yd.communication.api.utils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* 客户端信息工具类(IP、设备号等)
* 优先从 HTTP 请求获取,若不存在则从 IpContextHolder 获取(WebSocket 场景)
*
* @author zxm
* @date 2026-08-03
*/
public class ClientInfoUtils {
/**
* 获取客户端真实 IP
* 优先级:X-Forwarded-For > X-Real-IP > RemoteAddr > IpContextHolder
*
* @return 客户端 IP
*/
public static String getClientIp() {
// 1. 从 HTTP 请求获取
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
if (request != null) {
// 获取 X-Forwarded-For(经过代理时)
String ip = request.getHeader("X-Forwarded-For");
if (StringUtils.isBlank(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (StringUtils.isBlank(ip)) {
ip = request.getRemoteAddr();
}
// X-Forwarded-For 可能包含多个 IP,取第一个
if (StringUtils.isNotBlank(ip) && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
if (StringUtils.isNotBlank(ip)) {
return ip;
}
}
}
} catch (Exception e) {
// 忽略异常,继续回退
}
// 2. 回退到 IpContextHolder(WebSocket 场景由 CoWebSocketServer 设置)
return IpContextHolder.getIp();
}
/**
* 获取设备号(User-Agent)
* 优先级:HTTP 请求头 > IpContextHolder
*
* @return 设备号(User-Agent)
*/
public static String getDevice() {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
if (request != null) {
return request.getHeader("User-Agent");
}
}
} catch (Exception e) {
// 忽略异常
}
return IpContextHolder.getDevice();
}
/**
* 获取完整客户端信息(IP + 设备)
* @return 格式:IP | User-Agent
*/
public static String getClientInfo() {
return getClientIp() + " | " + getDevice();
}
}
\ No newline at end of file
package com.yd.communication.api.utils;
/**
* 用于在 WebSocket 等非 HTTP 环境下传递客户端 IP 和设备信息
* <p>
* 使用 ThreadLocal 存储,避免参数透传,确保线程安全。
* 通常在 WebSocket 的 @OnMessage 方法中设置,在业务 Service 中获取,
* 并在 finally 块中清除,防止内存泄漏。
* </p>
*
* @author zxm
* @date 2026-08-03
*/
public class IpContextHolder {
private static final ThreadLocal<String> IP_HOLDER = new ThreadLocal<>();
private static final ThreadLocal<String> DEVICE_HOLDER = new ThreadLocal<>();
/**
* 设置当前线程的客户端 IP
*/
public static void setIp(String ip) {
IP_HOLDER.set(ip);
}
/**
* 获取当前线程的客户端 IP
*/
public static String getIp() {
return IP_HOLDER.get();
}
/**
* 设置当前线程的设备号(User-Agent 等)
*/
public static void setDevice(String device) {
DEVICE_HOLDER.set(device);
}
/**
* 获取当前线程的设备号
*/
public static String getDevice() {
return DEVICE_HOLDER.get();
}
/**
* 清除当前线程的上下文(防止内存泄漏)
*/
public static void clear() {
IP_HOLDER.remove();
DEVICE_HOLDER.remove();
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yd.communication.api.service.ApiCoSessionService;
import com.yd.communication.api.utils.IpContextHolder;
import com.yd.communication.feign.dto.RoomRedisInfoDTO;
import com.yd.communication.feign.enums.ControlHolderTypeEnum;
import com.yd.communication.feign.request.EndSessionRequest;
......@@ -23,13 +24,14 @@ import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.connection.RedisConnection;
/**
* 协同 WebSocket 服务端(支持多节点部署)
......@@ -513,6 +515,11 @@ public class CoWebSocketServer {
*/
@OnMessage
public void onMessage(Session session, String message) {
// 获取 IP
String remoteIp = getClientIp(session);
IpContextHolder.setIp(remoteIp);
IpContextHolder.setDevice(null);
// 根据会话查询对应的房间号
String roomId = SESSION_ROOM.get(session);
if (roomId == null) {
......@@ -592,7 +599,7 @@ public class CoWebSocketServer {
// 记录操作日志(用于合规审计)
if (bizId != null) {
operationLogService.log(bizId, userId, userType, userId, action, correctMsg, null, null);
operationLogService.log(bizId, userId, userType, userId, action, correctMsg, IpContextHolder.getDevice(), IpContextHolder.getIp());
}
log.info("控制权切换成功: roomId={}, holder={}:{}", roomId, correctHolderType, correctHolderId);
......@@ -627,6 +634,19 @@ public class CoWebSocketServer {
// 关闭房间所有连接并清理资源
closeRoom(roomId);
log.info("共享已结束: roomId={}", roomId);
if (bizId != null) {
operationLogService.log(
bizId,
userId,
userType,
userId,
"end_sharing",
"结束共享",
IpContextHolder.getDevice(),
IpContextHolder.getIp()
);
}
return;
}
......@@ -639,7 +659,7 @@ public class CoWebSocketServer {
// 记录操作日志
if (bizId != null) {
operationLogService.log(bizId, userId, userType, userId, action, message, null, null);
operationLogService.log(bizId, userId, userType, userId, action, message, IpContextHolder.getDevice(), IpContextHolder.getIp());
}
return;
}
......@@ -686,7 +706,7 @@ public class CoWebSocketServer {
// 4.5 记录操作日志(合规审计)
if (bizId != null) {
operationLogService.log(bizId, userId, userType, userId, action, message, null, null);
operationLogService.log(bizId, userId, userType, userId, action, message, IpContextHolder.getDevice(), IpContextHolder.getIp());
}
return;
}
......@@ -698,6 +718,9 @@ public class CoWebSocketServer {
// 处理消息异常,向客户端返回错误信息
log.error("处理WebSocket消息异常,消息内容: {}", message, e);
sendError(session, "服务器处理异常");
} finally {
//清除上下文,防止内存泄漏
IpContextHolder.clear();
}
}
......@@ -877,4 +900,40 @@ public class CoWebSocketServer {
return value;
}
}
// ==================== 获取客户端 IP 的工具方法 ====================
/**
* 获取客户端 IP(由于 javax.websocket.Session 接口没有 getRemoteAddress,通过反射调用)
*/
private String getClientIp(Session session) {
try {
// 尝试直接通过反射调用 getRemoteAddress(Tomcat 等实现类有该方法)
Method method = session.getClass().getMethod("getRemoteAddress");
Object result = method.invoke(session);
if (result instanceof InetSocketAddress) {
InetSocketAddress address = (InetSocketAddress) result;
return address.getAddress().getHostAddress();
}
} catch (Exception e) {
log.debug("通过反射获取 getRemoteAddress 失败,尝试其他方式");
}
// 备选:从请求参数中获取(前端可传 ?ip=xxx)
Map<String, List<String>> params = session.getRequestParameterMap();
if (params != null && params.containsKey("ip")) {
List<String> ips = params.get("ip");
if (ips != null && !ips.isEmpty()) {
return ips.get(0);
}
}
// 再备选:从 getUserProperties 中获取(如由 configurator 存储)
Object storedIp = session.getUserProperties().get("remoteIp");
if (storedIp != null) {
return storedIp.toString();
}
return "unknown";
}
}
\ No newline at end of file
package com.yd.communication.feign.client;
import com.yd.common.result.Result;
import com.yd.communication.feign.fallback.ApiCoDesensitizationRuleFeignFallbackFactory;
import com.yd.communication.feign.response.desensitization.ApiCoDesensitizationRuleResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotBlank;
import java.util.List;
/**
* 通信服务-脱敏信息Feign 客户端
*
* @author zxm
* @date 2026-07-28
*/
@FeignClient(name = "yd-communication-api", path = "/communication/api/desensitization", fallbackFactory = ApiCoDesensitizationRuleFeignFallbackFactory.class)
public interface ApiCoDesensitizationRuleFeignClient {
/**
* 根据业务ID查询单条规则
*/
@GetMapping("/{bizId}")
Result<ApiCoDesensitizationRuleResponse> getByBizId(@PathVariable @NotBlank(message = "业务ID不能为空") String bizId);
/**
* 根据资源类型和资源ID获取生效的脱敏规则列表(供脱敏引擎调用)
*/
@GetMapping("/enabled")
Result<List<ApiCoDesensitizationRuleResponse>> getEnabledRules(
@RequestParam(value = "resourceType",required = false) String resourceType,
@RequestParam(value = "resourceId",required = false) String resourceId);
}
package com.yd.communication.feign.fallback;
import com.yd.common.result.Result;
import com.yd.communication.feign.client.ApiCoDesensitizationRuleFeignClient;
import com.yd.communication.feign.response.desensitization.ApiCoDesensitizationRuleResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 通信服务-脱敏信息Feign降级处理
*/
@Slf4j
@Component
public class ApiCoDesensitizationRuleFeignFallbackFactory implements FallbackFactory<ApiCoDesensitizationRuleFeignClient> {
@Override
public ApiCoDesensitizationRuleFeignClient create(Throwable cause) {
return new ApiCoDesensitizationRuleFeignClient() {
@Override
public Result<ApiCoDesensitizationRuleResponse> getByBizId(String bizId) {
return null;
}
@Override
public Result<List<ApiCoDesensitizationRuleResponse>> getEnabledRules(String resourceType, String resourceId) {
return null;
}
};
}
}
package com.yd.communication.feign.response.desensitization;
import lombok.Data;
@Data
public class ApiCoDesensitizationRuleResponse {
/**
* 主键
*/
private Long id;
/**
* 脱敏规则唯一业务ID
*/
private String ruleBizId;
/**
* 规则名称(如:报告-客户姓名脱敏)
*/
private String ruleName;
/**
* 资源类型:report/news/mini_program
*/
private String resourceType;
/**
* 资源业务ID(NULL表示全局规则,适用于该类型下所有资源)
*/
private String resourceId;
/**
* 脱敏字段路径(JSON Path,如:$.customer.name)
*/
private String fieldPath;
/**
* 字段显示名称(冗余,便于运营配置)
*/
private String fieldName;
/**
* 脱敏方式:mask-掩码(如:张**)、hide-完全隐藏(****)、replace-替换(***)、partial-部分显示(如:138****8000)
*/
private String maskType;
/**
* 脱敏配置JSON(如:{"prefix":1,"suffix":1,"replace_char":"*"})
*/
private String maskConfig;
/**
* 规则是否启用:0-停用,1-启用
*/
private Integer enabled;
/**
* 是否为默认规则:0-否,1-是(资源未配置时使用默认)
*/
private Integer isDefault;
/**
* 排序顺序
*/
private Integer sortOrder;
}
package com.yd.communication.service.service;
import com.yd.communication.service.model.CoDesensitizationRule;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yd.communication.service.model.CoDesensitizationRule;
import java.util.List;
/**
* <p>
* 协同-脱敏设置表(通用) 服务类
* </p>
*
* @author zxm
* @since 2026-07-28
* 协同-脱敏设置表 服务类
*/
public interface ICoDesensitizationRuleService extends IService<CoDesensitizationRule> {
/**
* 分页查询脱敏规则列表
*/
IPage<CoDesensitizationRule> pageList(Page<CoDesensitizationRule> page, String resourceType, String resourceId, Integer enabled);
/**
* 根据资源类型和资源ID获取生效的脱敏规则列表
*/
List<CoDesensitizationRule> getEnabledRulesByResource(String resourceType, String resourceId);
/**
* 启用/停用规则
*/
void toggleEnabled(Long id, Integer enabled);
/**
* 逻辑删除规则
*/
void deleteById(Long id);
}
\ No newline at end of file
package com.yd.communication.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yd.communication.service.model.CoDesensitizationRule;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yd.common.enums.CommonEnum;
import com.yd.common.exception.BusinessException;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.communication.service.dao.CoDesensitizationRuleMapper;
import com.yd.communication.service.model.CoDesensitizationRule;
import com.yd.communication.service.service.ICoDesensitizationRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* <p>
* 协同-脱敏设置表(通用) 服务实现类
* </p>
*
* @author zxm
* @since 2026-07-28
*/
@Slf4j
@Service
public class CoDesensitizationRuleServiceImpl extends ServiceImpl<CoDesensitizationRuleMapper, CoDesensitizationRule> implements ICoDesensitizationRuleService {
@Resource
private CoDesensitizationRuleMapper ruleMapper;
public class CoDesensitizationRuleServiceImpl
extends ServiceImpl<CoDesensitizationRuleMapper, CoDesensitizationRule>
implements ICoDesensitizationRuleService {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public IPage<CoDesensitizationRule> pageList(Page<CoDesensitizationRule> page,
String resourceType,
String resourceId,
Integer enabled) {
LambdaQueryWrapper<CoDesensitizationRule> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CoDesensitizationRule::getIsDeleted, 0);
if (StringUtils.isNotBlank(resourceType)) {
wrapper.eq(CoDesensitizationRule::getResourceType, resourceType);
}
if (StringUtils.isNotBlank(resourceId)) {
wrapper.eq(CoDesensitizationRule::getResourceId, resourceId);
}
if (enabled != null) {
wrapper.eq(CoDesensitizationRule::getEnabled, enabled);
}
wrapper.orderByAsc(CoDesensitizationRule::getSortOrder)
.orderByDesc(CoDesensitizationRule::getCreateTime);
return this.page(page, wrapper);
}
public String applyDesensitization(String rawData, String resourceType, String resourceId) {
// 获取规则(先特定资源,再默认)
@Override
public List<CoDesensitizationRule> getEnabledRulesByResource(String resourceType, String resourceId) {
LambdaQueryWrapper<CoDesensitizationRule> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CoDesensitizationRule::getResourceType, resourceType)
wrapper.eq(CoDesensitizationRule::getIsDeleted, 0)
.eq(CoDesensitizationRule::getEnabled, 1)
.eq(StringUtils.isNotBlank(resourceType),CoDesensitizationRule::getResourceType, resourceType)
.eq(StringUtils.isNotBlank(resourceId),CoDesensitizationRule::getResourceId,resourceId)
.orderByAsc(CoDesensitizationRule::getSortOrder);
return this.list(wrapper);
}
List<CoDesensitizationRule> rules;
// 先查特定资源
wrapper.eq(CoDesensitizationRule::getResourceId, resourceId);
rules = ruleMapper.selectList(wrapper);
if (rules.isEmpty()) {
// 查默认规则
wrapper.clear();
wrapper.eq(CoDesensitizationRule::getResourceType, resourceType)
.eq(CoDesensitizationRule::getIsDefault, 1)
.eq(CoDesensitizationRule::getEnabled, 1)
.orderByAsc(CoDesensitizationRule::getSortOrder);
rules = ruleMapper.selectList(wrapper);
@Override
@Transactional(rollbackFor = Exception.class)
public void toggleEnabled(Long id, Integer enabled) {
CoDesensitizationRule rule = this.getById(id);
if (rule == null || rule.getIsDeleted() == 1) {
throw new BusinessException("规则不存在或已删除");
}
rule.setEnabled(enabled);
rule.setUpdaterId("system");
rule.setUpdateTime(LocalDateTime.now());
this.updateById(rule);
log.info("脱敏规则 {} 状态已切换为: {}", id, enabled == 1 ? "启用" : "停用");
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteById(Long id) {
CoDesensitizationRule rule = this.getById(id);
if (rule == null || rule.getIsDeleted() == 1) {
throw new BusinessException("规则不存在或已删除");
}
if (rules.isEmpty()) {
return rawData;
rule.setIsDeleted(1);
rule.setUpdaterId("system");
rule.setUpdateTime(LocalDateTime.now());
this.updateById(rule);
log.info("脱敏规则 {} 已逻辑删除", id);
}
try {
JsonNode root = objectMapper.readTree(rawData);
for (CoDesensitizationRule rule : rules) {
String fieldPath = rule.getFieldPath();
// 简单处理:假设路径是$.xxx,直接取字段
String fieldName = fieldPath.replace("$.", "");
if (root.has(fieldName)) {
String original = root.get(fieldName).asText();
String masked = maskValue(original, rule.getMaskType(), rule.getMaskConfig());
// 由于JsonNode不可变,简单返回,生产需构建新节点
/**
* 保存或更新前的公共逻辑
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveOrUpdate(CoDesensitizationRule entity) {
if (entity == null) {
return false;
}
if (entity.getId() == null) {
// 新增
entity.setRuleBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_DESENSITIZATION_RULE.getCode()));
entity.setIsDeleted(0);
entity.setCreateTime(LocalDateTime.now());
entity.setUpdateTime(LocalDateTime.now());
if (StringUtils.isBlank(entity.getCreatorId())) {
entity.setCreatorId("system");
}
return objectMapper.writeValueAsString(root);
} catch (Exception e) {
log.error("脱敏失败", e);
return rawData;
if (StringUtils.isBlank(entity.getUpdaterId())) {
entity.setUpdaterId("system");
}
// 默认启用
if (entity.getEnabled() == null) {
entity.setEnabled(1);
}
private String maskValue(String original, String maskType, String maskConfig) {
try {
JsonNode config = objectMapper.readTree(maskConfig != null ? maskConfig : "{}");
int prefix = config.has("prefix") ? config.get("prefix").asInt() : 0;
int suffix = config.has("suffix") ? config.get("suffix").asInt() : 0;
String replaceChar = config.has("replace_char") ? config.get("replace_char").asText() : "*";
switch (maskType) {
case "mask":
case "partial":
if (original.length() <= prefix + suffix) return original;
int middle = original.length() - prefix - suffix;
return original.substring(0, prefix) +
// replaceChar.repeat(middle) +
original.substring(original.length() - suffix);
case "hide":
return "****";
case "replace":
return config.has("replace_char") ? config.get("replace_char").asText() : "***";
default:
return original;
}
} catch (Exception e) {
return original;
} else {
// 更新
CoDesensitizationRule exist = this.getById(entity.getId());
if (exist == null || exist.getIsDeleted() == 1) {
throw new BusinessException("规则不存在或已删除");
}
entity.setUpdateTime(LocalDateTime.now());
if (StringUtils.isBlank(entity.getUpdaterId())) {
entity.setUpdaterId("system");
}
// 不允许修改 ruleBizId 和 isDeleted
entity.setRuleBizId(null);
entity.setIsDeleted(null);
entity.setCreateTime(null);
}
return super.saveOrUpdate(entity);
}
}
\ No newline at end of file
......@@ -5,6 +5,7 @@ import com.yd.communication.service.dao.CoOperationLogMapper;
import com.yd.communication.service.service.ICoOperationLogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
......@@ -21,6 +22,7 @@ import org.springframework.stereotype.Service;
public class CoOperationLogServiceImpl extends ServiceImpl<CoOperationLogMapper, CoOperationLog> implements ICoOperationLogService {
@Override
@Async("communicationExecutor")
public void log(String bizId, String operatorId, String operatorType,
String operatorName, String action, String content,
String deviceNumber, String ip) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment