实习内容:
1.定时任务与数据补全:基于 XXL-JOB 实现分布式定时任务调度,补全近半年历史操作日志数据,有效解决因网络异常导致的数据缺失问题。
业务场景;集团的4a日志半年内没有同步,这边需要把日志数据同步到集团上
首先先评估每天的数提量,因为我配照的是开发环境,所有的据量不全,让运维的同事宣询一下每天的数据量是多少?差不多是3.6w日志,那么据总量软是180*3.6=650w号数据知道数据的大概范围认后效开始确认调用天数,循环执行、最终根据实际情况,每15天循环一次,然后添加xx1-job注解。从job平台项用,补全数据朴全过程中断怎么办,可以通过Redis记录处理日期,重启后跳过,同时处理期间增加道暂间隔,减少数据库压大
合井数据库文件
java
//将操作日志数据同步到4A平台上,根据选取的日期,往前推15天,
@XxlJob("syncLog4AJobNew")
public void JobHandlerNew() throws IOException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date curTime = null;
Date endTime = null;
try {
curTime = sdf.parse("2024-12-10 00:00:00");
endTime = sdf.parse("2025-05-08 22:00:00");
} catch (ParseException e) {
throw new RuntimeException(e);
}
// 每次间隔的时间,单位ms
long interval = 24 * 60 * 60 * 1000;
int num = 1;
while (curTime.before(endTime)) {
Date nextTime = new Date(curTime.getTime() + interval);
log.info("syncLog4AJobNew-4A日志同步任务:"+nextTime);
String curFileName = null;
BufferedWriter bufferedWriter = null;
try {
// 读取需要发的日志数据
//List<OperateLogDto> logs = opLogService.queryOperateLog4ANew();
List<OperateLogDto> logs = opLogService.queryOperateLog4ANew(nextTime);//填加时间范围
log.info("syncLog4AJobNew-4A待发送日志查询成功,获取日志信息数为:" + logs.size());
if (CollectionUtils.isEmpty(logs)) {
XxlJobHelper.log("无需要同步数据.");
curTime = nextTime;//修改
continue;
}
// 写入文件流
int length = 0;
int startId = 0, endId = 0;
for (OperateLogDto logDto : logs) {
if (StringUtils.isBlank(logDto.getUserCode()))
continue;
if (curFileName == null)
curFileName = logConfig.getResourceCode() + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xml";
if (bufferedWriter == null) {
bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(ftpConfig.getLocalPath() + curFileName), "UTF-8"));
bufferedWriter.write("<?xml version='1.0' encoding='UTF-8' ?><ROOT>");
}
if (startId == 0)
startId = logDto.getOperateLogId();
String generateLog = log4AUtil.generateLog(logDto.getToken(), logDto.getOperateLogId().toString(), logDto.getDoneDate(),
logDto.getUserCode(), logDto.getOperateTypeName(), logDto.getOperateTypeId(),
logDto.getOperateResult(), logDto.getModuleId(), logDto.getModuleName(),
logDto.getIp(), logDto.getOperateName());
bufferedWriter.write(new String(generateLog.getBytes("UTF-8"), "UTF-8"));
endId = logDto.getOperateLogId();
// 每个文件控制在5M到10M之间
length += generateLog.getBytes(StandardCharsets.UTF_8).length;
//当文件长度大于5则传送数据
if (length > fileLength) {
bufferedWriter.write("</ROOT>");
bufferedWriter.flush();
// 发送本地文件到4A
boolean ss = this.sendSftp(curFileName);
if (ss) {
opLogService.updateLog4A(startId, endId);
log.info("syncLog4AJobNew-4A日志同步任务成功,上传SFTP成功,传输数据范围为:" + startId + "-" + endId);
} else {
XxlJobHelper.log("4A日志同步任务失败。上传SFTP失败。");
XxlJobHelper.handleFail();
return;
}
// 初始化写入
curFileName = null;
bufferedWriter = null;
length = 0;
startId = 0;
}
}
//最后收尾,存在数据没有发送
if (bufferedWriter != null) {
bufferedWriter.write("</ROOT>");
bufferedWriter.flush();
// 发送本地文件到4A
boolean ss = this.sendSftp(curFileName);
if (ss) {
opLogService.updateLog4A(startId, endId);
log.info("syncLog4AJobNew-4A日志同步任务成功,上传SFTP成功,传输数据范围为:" + startId + "-" + endId);
} else {
XxlJobHelper.log("4A日志同步任务失败。上传SFTP失败。");
XxlJobHelper.handleFail();
}
}
XxlJobHelper.handleSuccess("4A日志同步任务成功");
} catch (Exception e) {
log.error("4A日志同步任务失败。" + e);
XxlJobHelper.log("4A日志同步任务失败。" + e.getMessage());
XxlJobHelper.handleFail();
} finally {
if (bufferedWriter != null)
bufferedWriter.close();
}
curTime = nextTime;
log.info("======SyncLog4AJob.JobHandlerNew()," + num + "," + sdf.format(curTime));
num++;
}
}
2.数据安全与合规控制:通过 SpringAOP 自动脱敏敏感字段,结合 SpringSecurily+ 数据权限注解 实现细粒度访问控制,确保数据隔离与合规性。
aop切面
java
package com.asiainfo.osm.aop;
import com.asiainfo.osm.common.result.ResultObject;
import com.asiainfo.osm.common.result.ResultPage;
import com.asiainfo.osm.common.user.dto.UserDTO;
import com.asiainfo.osm.system.dto.UserExtDTO;
import com.asiainfo.osm.system.dto.UserInfoDTO;
import com.asiainfo.osm.utils.DesensitizationUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
/**
* 脱敏切面
*
* @author renhx
* @createDate 2025/5/9 19:29
**/
@Aspect
@Component
public class SensitiveDataAspect {
@Around("execution(* com.asiainfo.osm.system.web.UserController.getUsers(..)) || " +
"execution(* com.asiainfo.osm.system.web.UserController.getUserInfo(Integer)) ||" +
"execution(* com.asiainfo.osm.system.web.UserController.queryUserInfo(Integer))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
return processSensitiveData(result);
}
private Object processSensitiveData(Object obj) {
if (obj == null) {
return null;
}
// 人员信息下拉列表出参
if (obj instanceof UserInfoDTO) {
UserInfoDTO user = (UserInfoDTO) obj;
user.setUserName(DesensitizationUtils.chineseName(user.getUserName()));
user.setEmail(DesensitizationUtils.email(user.getEmail()));
return user;
}
// 用户详情出参
if (obj instanceof UserDTO) {
UserDTO user = (UserDTO) obj;
user.setUserName(DesensitizationUtils.chineseName(user.getUserName()));
user.setPhone(DesensitizationUtils.mobilePhone(user.getPhone()));
user.setEmail(DesensitizationUtils.email(user.getEmail()));
return user;
}
// 用户列表出参
if (obj instanceof UserExtDTO) {
UserExtDTO user = (UserExtDTO) obj;
user.setUserName(DesensitizationUtils.chineseName(user.getUserName()));
user.setPhone(DesensitizationUtils.mobilePhone(user.getPhone()));
user.setEmail(DesensitizationUtils.email(user.getEmail()));
return user;
}
if (obj instanceof List) {
List<?> list = (List<?>) obj;
return list.stream()
.map(this::processSensitiveData)
.collect(Collectors.toList());
}
if (obj instanceof ResultPage) {
ResultPage<?> resultPage = (ResultPage<?>) obj;
List<?> pageData = resultPage.getPageData().stream()
.map(this::processSensitiveData)
.collect(Collectors.toList());
return ResultPage.instance(resultPage.getTotal(),pageData);
}
if (obj instanceof ResultObject) {
ResultObject<?> resultObject = (ResultObject<?>) obj;
Object data = processSensitiveData(resultObject.getData());
ResultObject<Object> resultObjectNew = new ResultObject<>();
resultObjectNew.setCode(resultObject.getCode());
resultObjectNew.setName(resultObject.getName());
resultObjectNew.setMessage(resultObject.getMessage());
resultObjectNew.setData(data);
return resultObjectNew;
}
return obj;
}
}
脱敏规则
java
package com.asiainfo.osm.utils;
import org.apache.commons.lang.StringUtils;
/**
* 脱敏工具
*
* @author renhx
* @createDate 2025/5/10 8:19
**/
public class DesensitizationUtils {
// 姓名脱敏 (两字: 张* | 三字: 张*三 | 多字: 张**...三)
public static String chineseName(String fullName) {
if (StringUtils.isBlank(fullName)) {
return "";
}
int length = fullName.length();
if (length == 1) {
return fullName; // 单字名不做处理
}
if (length == 2) {
// 两字名: 张*
return fullName.charAt(0) + "*";
}
if (length == 3) {
// 三字名: 张*三
return fullName.charAt(0) + "*" + fullName.charAt(2);
}
// 多字名: 张**...三
return fullName.charAt(0) +
StringUtils.repeat("*", length - 2) +
fullName.charAt(length - 1);
}
// 手机号脱敏 (保留前3后4)
public static String mobilePhone(String phone) {
if (StringUtils.isBlank(phone)) {
return "";
}
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
// 邮箱脱敏 (保留@前3位和域名)
public static String email(String email) {
if (StringUtils.isBlank(email)) {
return "";
}
int index = email.indexOf("@");
if (index <= 1) {
return email;
}
String head = "";
if (index <= 3) {
head = email.substring(0,index);
} else {
head = email.substring(0,3);
}
return head + "*********" + email.substring(index);
}
}
越权规则
