Commit 4bf8eaf1 by jianan

操作日志1

parent dd86ab31
package com.yd.csf.api.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yd.common.result.Result;
import com.yd.csf.api.dto.AuditLogQueryRequest;
import com.yd.csf.service.model.SysOperDataChange;
import com.yd.csf.service.model.SysOperDataSnapshot;
import com.yd.csf.service.model.SysOperLog;
import com.yd.csf.service.service.AuditLogDataChangeService;
import com.yd.csf.service.service.AuditLogDataSnapshotService;
import com.yd.csf.service.service.AuditLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/auditlog")
@Tag(name = "审计日志")
@RequiredArgsConstructor
public class AuditLogController {
@Resource
private AuditLogService operLogService;
@Resource
private AuditLogDataSnapshotService snapshotService;
@Resource
private AuditLogDataChangeService changeService;
@PostMapping("/log/page")
@Operation(summary = "操作日志分页查询")
public Result<Page<SysOperLog>> page(@RequestBody AuditLogQueryRequest request) {
LambdaQueryWrapper<SysOperLog> wrapper = new LambdaQueryWrapper<SysOperLog>()
.eq(StringUtils.isNotBlank(request.getModule()), SysOperLog::getModule, request.getModule())
.eq(StringUtils.isNotBlank(request.getOperName()), SysOperLog::getOperName, request.getOperName())
.eq(request.getStatus() != null, SysOperLog::getStatus, request.getStatus())
.ge(StringUtils.isNotBlank(request.getStartTime()), SysOperLog::getOperTime, request.getStartTime())
.le(StringUtils.isNotBlank(request.getEndTime()), SysOperLog::getOperTime, request.getEndTime())
.orderByDesc(SysOperLog::getOperTime);
Page<SysOperLog> page = operLogService.page(
new Page<>(request.getPageNo(), request.getPageSize()), wrapper);
return Result.success(page);
}
@GetMapping("/log/detail")
@Operation(summary = "审计详情查询")
public Result<Map<String, Object>> detail(@RequestParam String requestId) {
if (StringUtils.isBlank(requestId)) {
return Result.success(null);
}
SysOperLog operLog = operLogService.getOne(
new LambdaQueryWrapper<SysOperLog>().eq(SysOperLog::getRequestId, requestId));
List<SysOperDataSnapshot> snapshots = snapshotService.list(
new LambdaQueryWrapper<SysOperDataSnapshot>().eq(SysOperDataSnapshot::getRequestId, requestId));
List<SysOperDataChange> changes = changeService.list(
new LambdaQueryWrapper<SysOperDataChange>().eq(SysOperDataChange::getRequestId, requestId));
Map<String, Object> result = new HashMap<>();
result.put("operLog", operLog);
result.put("snapshots", snapshots);
result.put("changes", changes);
return Result.success(result);
}
}
package com.yd.csf.service.audit;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Date;
@Data
@Accessors(chain = true)
public class AuditContext {
private String requestId;
private String module;
private Integer businessType;
private String method;
private String content;
private Long businessId;
private Integer operationType;
private Long userId;
private String operName;
private String deptName;
private String operUrl;
private String operIp;
private String operLocation;
private String operParam;
private String jsonResult;
private Integer status;
private String errorMsg;
private Date operTime;
}
package com.yd.csf.service.audit;
public class AuditContextHolder {
private static final ThreadLocal<AuditContext> CONTEXT = new ThreadLocal<>();
public static void set(AuditContext context) {
CONTEXT.set(context);
}
public static AuditContext get() {
return CONTEXT.get();
}
public static String getRequestId() {
AuditContext ctx = CONTEXT.get();
return ctx != null ? ctx.getRequestId() : null;
}
public static void clear() {
CONTEXT.remove();
}
}
package com.yd.csf.service.audit;
import cn.hutool.core.collection.CollUtil;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.yd.csf.service.annotation.Auditable;
import com.yd.csf.service.model.SysOperDataChange;
import com.yd.csf.service.model.SysOperDataSnapshot;
import com.yd.csf.service.service.AuditLogDataChangeService;
import com.yd.csf.service.service.AuditLogDataSnapshotService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.*;
@Slf4j
@Component
@RequiredArgsConstructor
public class AuditHelper {
private final AuditLogDataSnapshotService snapshotService;
private final AuditLogDataChangeService changeService;
private final FieldLabelResolver fieldLabelResolver;
private static final Set<String> EXCLUDED_FIELDS = new HashSet<>(Arrays.asList(
"id", "createTime", "updateTime", "creatorId", "creatorName",
"updaterId", "updaterName", "isDeleted", "serialVersionUID"
));
private static final Gson GSON = new GsonBuilder()
.addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return EXCLUDED_FIELDS.contains(f.getName());
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
/**
* 记录数据变更。
*
* @param tableName 表名
* @param beforeMap 变更前的数据,key 为主键值,value 为实体对象(INSERT 时为 null)
* @param afterMap 变更后的数据,key 为主键值,value 为实体对象(DELETE 时为 null)
* @param ctx 审计上下文
*/
public void record(String tableName, Map<String, ?> beforeMap, Map<String, ?> afterMap, AuditContext ctx) {
if (beforeMap == null && afterMap == null) {
return;
}
Class<?> entityClass = getEntityClass(beforeMap, afterMap);
if (entityClass == null) {
log.warn("无法确定实体类型,跳过审计记录: table={}", tableName);
return;
}
Auditable auditable = entityClass.getAnnotation(Auditable.class);
if (auditable == null) {
throw new IllegalArgumentException("实体类 " + entityClass.getName() + " 缺少 @Auditable 注解");
}
List<Field> diffFields = getDiffFields(entityClass);
Set<String> allIds = new LinkedHashSet<>();
if (beforeMap != null) allIds.addAll(beforeMap.keySet());
if (afterMap != null) allIds.addAll(afterMap.keySet());
List<SysOperDataSnapshot> snapshots = new ArrayList<>();
List<SysOperDataChange> changes = new ArrayList<>();
Date now = new Date();
for (String recordId : allIds) {
Object before = beforeMap != null ? beforeMap.get(recordId) : null;
Object after = afterMap != null ? afterMap.get(recordId) : null;
String beforeJson = before != null ? GSON.toJson(before) : null;
String afterJson = after != null ? GSON.toJson(after) : null;
boolean isInsert = before == null && after != null;
boolean isDelete = before != null && after == null;
String changeType = isInsert ? "ADD" : (isDelete ? "DELETE" : "MODIFY");
SysOperDataSnapshot snapshot = new SysOperDataSnapshot();
snapshot.setRequestId(ctx.getRequestId());
snapshot.setTableName(tableName);
snapshot.setRecordId(recordId);
snapshot.setBeforeJson(beforeJson);
snapshot.setAfterJson(afterJson);
snapshot.setCreateTime(now);
snapshots.add(snapshot);
if (!isInsert && !isDelete && before != null && after != null) {
for (Field field : diffFields) {
try {
field.setAccessible(true);
Object oldVal = field.get(before);
Object newVal = field.get(after);
if (!Objects.equals(oldVal, newVal)) {
SysOperDataChange change = new SysOperDataChange();
change.setRequestId(ctx.getRequestId());
change.setTableName(tableName);
change.setRecordId(recordId);
change.setFieldName(field.getName());
change.setFieldComment(fieldLabelResolver.resolve(field, tableName));
change.setOldValue(oldVal != null ? oldVal.toString() : null);
change.setNewValue(newVal != null ? newVal.toString() : null);
change.setChangeType("MODIFY");
change.setCreateTime(now);
changes.add(change);
}
} catch (IllegalAccessException e) {
log.debug("跳过无法访问的字段: {}", field.getName());
}
}
}
}
if (CollUtil.isNotEmpty(snapshots)) {
snapshotService.saveBatch(snapshots);
}
if (CollUtil.isNotEmpty(changes)) {
changeService.saveBatch(changes);
}
}
private Class<?> getEntityClass(Map<String, ?> beforeMap, Map<String, ?> afterMap) {
if (beforeMap != null) {
for (Object v : beforeMap.values()) {
if (v != null) return v.getClass();
}
}
if (afterMap != null) {
for (Object v : afterMap.values()) {
if (v != null) return v.getClass();
}
}
return null;
}
private List<Field> getDiffFields(Class<?> entityClass) {
List<Field> fields = new ArrayList<>();
Class<?> current = entityClass;
while (current != null && current != Object.class) {
for (Field field : current.getDeclaredFields()) {
if (!EXCLUDED_FIELDS.contains(field.getName())
&& !java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
fields.add(field);
}
}
current = current.getSuperclass();
}
return fields;
}
/**
* 对实体列表做深拷贝快照。
* 使用 Gson 序列化→反序列化做深拷贝,自动从 @Auditable.idField 读取 Map key。
*
* @param entities 实体列表,null 或空列表返回空 Map
* @param <T> 实体类型
* @return Map&lt;业务ID, 深拷贝后的实体&gt;
* @throws IllegalArgumentException 如果实体类缺少 @Auditable 注解
*/
@SuppressWarnings("unchecked")
public <T> Map<String, T> snapshot(List<T> entities) {
if (CollUtil.isEmpty(entities)) {
return Collections.emptyMap();
}
T first = entities.get(0);
if (first == null) {
return Collections.emptyMap();
}
Class<?> entityClass = first.getClass();
Auditable auditable = entityClass.getAnnotation(Auditable.class);
if (auditable == null) {
throw new IllegalArgumentException("实体类 " + entityClass.getName() + " 缺少 @Auditable 注解");
}
String idFieldName = auditable.idField();
Map<String, T> result = new LinkedHashMap<>();
for (T entity : entities) {
try {
Field idField = findField(entityClass, idFieldName);
if (idField == null) {
throw new RuntimeException("实体类 " + entityClass.getName() + " 中没有字段: " + idFieldName);
}
idField.setAccessible(true);
Object idValue = idField.get(entity);
if (idValue == null) {
log.warn("实体 idField={} 值为 null,跳过: {}", idFieldName, entity);
continue;
}
T copy = (T) GSON.fromJson(GSON.toJson(entity), entityClass);
result.put(idValue.toString(), copy);
} catch (IllegalAccessException e) {
throw new RuntimeException("无法读取字段 " + idFieldName, e);
}
}
return result;
}
private Field findField(Class<?> clazz, String fieldName) {
Class<?> current = clazz;
while (current != null && current != Object.class) {
try {
return current.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
current = current.getSuperclass();
}
}
return null;
}
}
package com.yd.csf.service.audit;
import com.yd.auth.core.dto.AuthUserDto;
import com.yd.auth.core.utils.SecurityUtil;
import com.yd.csf.service.model.SysOperLog;
import com.yd.csf.service.service.AuditLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.UUID;
@Slf4j
@Component
@RequiredArgsConstructor
public class AuditService {
private final AuditLogService operLogService;
/**
* 开始审计,创建新的审计上下文。
*
* @param module 业务模块
* @param businessType 业务类型
* @param content 操作描述
* @return requestId
*/
public String begin(String module, Integer businessType, String content) {
String requestId = UUID.randomUUID().toString().replace("-", "");
AuditContext ctx = new AuditContext();
ctx.setRequestId(requestId);
ctx.setModule(module);
ctx.setBusinessType(businessType);
ctx.setContent(content);
ctx.setOperTime(new Date());
fillRequestInfo(ctx);
fillOperatorInfo(ctx);
AuditContextHolder.set(ctx);
return requestId;
}
/**
* 使用已有 requestId 恢复审计上下文(用于异步回调)。
*/
public void begin(String requestId) {
AuditContext ctx = new AuditContext();
ctx.setRequestId(requestId);
ctx.setOperTime(new Date());
AuditContextHolder.set(ctx);
}
/**
* 标记操作成功,写入 sys_oper_log。
*/
public void success(String requestId) {
AuditContext ctx = AuditContextHolder.get();
if (ctx == null) {
return;
}
SysOperLog log = buildOperLog(ctx);
log.setStatus(0);
operLogService.save(log);
}
/**
* 标记操作失败,写入 sys_oper_log。
*/
public void fail(String requestId, String errorMsg) {
AuditContext ctx = AuditContextHolder.get();
if (ctx == null) {
return;
}
SysOperLog log = buildOperLog(ctx);
log.setStatus(1);
log.setErrorMsg(errorMsg);
operLogService.save(log);
}
/**
* 关闭审计,清理 ThreadLocal。
*/
public void close(String requestId) {
AuditContextHolder.clear();
}
private void fillRequestInfo(AuditContext ctx) {
try {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attrs != null) {
HttpServletRequest request = attrs.getRequest();
ctx.setOperUrl(request.getRequestURI());
ctx.setOperIp(request.getRemoteAddr());
}
} catch (Exception e) {
log.debug("获取请求信息失败: {}", e.getMessage());
}
}
private void fillOperatorInfo(AuditContext ctx) {
try {
AuthUserDto user = SecurityUtil.getCurrentLoginUser();
ctx.setUserId(user.getId());
ctx.setOperName(user.getUsername());
} catch (Exception e) {
log.debug("获取操作人信息失败: {}", e.getMessage());
}
}
private SysOperLog buildOperLog(AuditContext ctx) {
SysOperLog log = new SysOperLog();
log.setRequestId(ctx.getRequestId());
log.setModule(ctx.getModule());
log.setBusinessType(ctx.getBusinessType());
log.setMethod(ctx.getMethod());
log.setContent(ctx.getContent());
log.setBusinessId(ctx.getBusinessId());
log.setOperationType(ctx.getOperationType());
log.setUserId(ctx.getUserId());
log.setOperName(ctx.getOperName());
log.setDeptName(ctx.getDeptName());
log.setOperUrl(ctx.getOperUrl());
log.setOperIp(ctx.getOperIp());
log.setOperLocation(ctx.getOperLocation());
log.setOperParam(ctx.getOperParam());
log.setJsonResult(ctx.getJsonResult());
log.setOperTime(ctx.getOperTime());
return log;
}
}
package com.yd.csf.service.audit;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 审计生命周期编排器。
* 封装 begin → action → success/fail → close 的固定模板,调用方只需编写业务逻辑。
*
* <pre>{@code
* // INSERT 场景
* return auditTemplate.execute("来佣管理", 1, "导入入账记录", ctx -> {
* saveBatch(list);
* auditHelper.record("commission", null, toMap(list), ctx);
* return true;
* });
*
* // UPDATE 场景
* return auditTemplate.execute("应付款管理", 2, "生成可出账", ctx -> {
* Map<String, ExpectedFortune> before = auditHelper.snapshot(list);
* // ... 业务变更 ...
* Map<String, ExpectedFortune> after = auditHelper.snapshot(list);
* auditHelper.record("expected_fortune", before, after, ctx);
* return true;
* });
* }</pre>
*
* @author Zhang Jianan
*/
@Component
@RequiredArgsConstructor
public class AuditTemplate {
private final AuditService auditService;
/**
* 执行带审计的操作。
* 自动处理 begin → success → close(正常路径)或 begin → fail → close(异常路径)。
* 异常会原样重新抛出,不改变异常传播链。
*
* @param module 业务模块名称
* @param businessType 业务类型
* @param content 操作描述
* @param action 业务逻辑,接收 AuditContext,返回结果
* @param <T> 返回值类型
* @return action 的返回值
*/
public <T> T execute(String module, Integer businessType,
String content, AuditAction<T> action) {
String requestId = auditService.begin(module, businessType, content);
try {
T result = action.apply(AuditContextHolder.get());
auditService.success(requestId);
return result;
} catch (Exception e) {
auditService.fail(requestId, e.getMessage());
throw (RuntimeException) e;
} finally {
auditService.close(requestId);
}
}
/**
* 审计操作函数式接口。
*
* @param <T> 返回值类型
*/
@FunctionalInterface
public interface AuditAction<T> {
T apply(AuditContext ctx);
}
}
package com.yd.csf.service.audit;
import com.yd.csf.service.annotation.AuditField;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
@RequiredArgsConstructor
public class FieldLabelResolver {
private final JdbcTemplate jdbcTemplate;
private final Map<String, String> cache = new ConcurrentHashMap<>();
private static final String SQL = "SELECT COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?";
/**
* 获取字段中文名,优先级:
* 1. @AuditField.label
* 2. information_schema.COLUMNS.COLUMN_COMMENT
* 3. field 名本身
*/
public String resolve(Field field, String tableName) {
AuditField auditField = field.getAnnotation(AuditField.class);
if (auditField != null && !auditField.label().isEmpty()) {
return auditField.label();
}
String columnName = camelToUnderscore(field.getName());
String cacheKey = tableName + "." + columnName;
String comment = cache.computeIfAbsent(cacheKey, k -> {
try {
return jdbcTemplate.queryForObject(SQL, String.class, tableName, columnName);
} catch (Exception e) {
return null;
}
});
if (comment != null && !comment.isEmpty()) {
return comment;
}
return field.getName();
}
private String camelToUnderscore(String camel) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < camel.length(); i++) {
char c = camel.charAt(i);
if (Character.isUpperCase(c)) {
sb.append('_').append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
}
package com.yd.csf.service.component; package com.yd.csf.service.component;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yd.auth.core.dto.AuthUserDto; import com.yd.auth.core.dto.AuthUserDto;
import com.yd.auth.core.utils.SecurityUtil; import com.yd.auth.core.utils.SecurityUtil;
import com.yd.common.enums.ResultCode; import com.yd.common.enums.ResultCode;
import com.yd.common.exception.BusinessException; import com.yd.common.exception.BusinessException;
import com.yd.csf.service.audit.AuditContextHolder;
import com.yd.csf.service.audit.AuditHelper;
import com.yd.csf.service.audit.AuditService;
import com.yd.csf.service.enums.CommissionExpectedStatusEnum; import com.yd.csf.service.enums.CommissionExpectedStatusEnum;
import com.yd.csf.service.enums.CommissionStatusEnum; import com.yd.csf.service.enums.CommissionStatusEnum;
import com.yd.csf.service.model.Commission; import com.yd.csf.service.model.Commission;
...@@ -24,9 +28,7 @@ import org.springframework.transaction.annotation.Transactional; ...@@ -24,9 +28,7 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -39,28 +41,148 @@ public class CommissionAsyncService { ...@@ -39,28 +41,148 @@ public class CommissionAsyncService {
private CommissionService commissionService; private CommissionService commissionService;
@Resource @Resource
private CommissionCompareRecordService commissionCompareRecordService; private CommissionCompareRecordService commissionCompareRecordService;
@Resource
private AuditService auditService;
@Resource
private AuditHelper auditHelper;
// @Async("commonAsyncExecutor") // @Async("commonAsyncExecutor")
@Transactional(propagation = Propagation.REQUIRES_NEW) @Transactional(propagation = Propagation.REQUIRES_NEW)
public void commissionCompareBatch(List<Commission> entities) { public void commissionCompareBatch(List<Commission> entities) {
// 根据是否关联保单号,来区分比对方式 // 保留旧签名兼容(无审计)
List<Commission> hasPolicyNoList = new ArrayList<>(); commissionCompareBatch(entities, null);
List<Commission> noPolicyNoList = new ArrayList<>(); }
for (Commission entity : entities) {
if ("U".equals(entity.getCommissionBizType())) { @Transactional(propagation = Propagation.REQUIRES_NEW)
noPolicyNoList.add(entity); public void commissionCompareBatch(List<Commission> entities, String requestId) {
} else { if (requestId != null) {
hasPolicyNoList.add(entity); auditService.begin(requestId);
}
try {
// 根据是否关联保单号,来区分比对方式
List<Commission> hasPolicyNoList = new ArrayList<>();
List<Commission> noPolicyNoList = new ArrayList<>();
for (Commission entity : entities) {
if ("U".equals(entity.getCommissionBizType())) {
noPolicyNoList.add(entity);
} else {
hasPolicyNoList.add(entity);
}
}
// 审计:收集将被修改的记录ID,拍快照
Set<String> commissionBizIds = new HashSet<>();
Set<String> expectedBizIds = new HashSet<>();
collectAffectedIds(hasPolicyNoList, noPolicyNoList, commissionBizIds, expectedBizIds);
Map<String, Commission> commissionBefore = queryCommissionsByBizIds(commissionBizIds);
Map<String, CommissionExpected> expectedBefore = queryExpectedByBizIds(expectedBizIds);
// 执行比对
if (CollectionUtils.isNotEmpty(hasPolicyNoList)) {
normalBatch(hasPolicyNoList);
}
if (CollectionUtils.isNotEmpty(noPolicyNoList)) {
noPolicyNoBatch(noPolicyNoList);
}
// 审计:记录变更(不写 oper_log,主事务已写)
if (requestId != null) {
Map<String, Commission> commissionAfter = queryCommissionsByBizIds(commissionBizIds);
Map<String, CommissionExpected> expectedAfter = queryExpectedByBizIds(expectedBizIds);
if (CollUtil.isNotEmpty(commissionBefore) && CollUtil.isNotEmpty(commissionAfter)) {
auditHelper.record("commission", commissionBefore, commissionAfter, AuditContextHolder.get());
}
if (CollUtil.isNotEmpty(expectedBefore) && CollUtil.isNotEmpty(expectedAfter)) {
auditHelper.record("commission_expected", expectedBefore, expectedAfter, AuditContextHolder.get());
}
}
} catch (Exception e) {
throw e;
} finally {
if (requestId != null) {
auditService.close(requestId);
} }
} }
}
/**
* 收集比对过程中会被修改的 commission 和 commission_expected 记录ID。
*/
private void collectAffectedIds(List<Commission> hasPolicyNoList, List<Commission> noPolicyNoList,
Set<String> commissionBizIds, Set<String> expectedBizIds) {
// 新插入的 commission
for (Commission entity : hasPolicyNoList) {
if (entity.getCommissionBizId() != null) {
commissionBizIds.add(entity.getCommissionBizId());
}
}
for (Commission entity : noPolicyNoList) {
if (entity.getCommissionBizId() != null) {
commissionBizIds.add(entity.getCommissionBizId());
}
}
// normalBatch: 查询同一 expectedBizId 下的所有已存在 commission
if (CollectionUtils.isNotEmpty(hasPolicyNoList)) { if (CollectionUtils.isNotEmpty(hasPolicyNoList)) {
normalBatch(hasPolicyNoList); List<String> commissionExpectedBizIdList = hasPolicyNoList.stream()
.map(Commission::getCommissionExpectedBizId)
.filter(StringUtils::isNotBlank)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(commissionExpectedBizIdList)) {
List<Commission> existing = commissionService.lambdaQuery()
.in(Commission::getCommissionExpectedBizId, commissionExpectedBizIdList)
.list();
existing.forEach(c -> {
if (c.getCommissionBizId() != null) {
commissionBizIds.add(c.getCommissionBizId());
}
});
expectedBizIds.addAll(commissionExpectedBizIdList);
}
} }
// noPolicyNoBatch: 查询同一 receivableNo 的 commissionExpected
if (CollectionUtils.isNotEmpty(noPolicyNoList)) { if (CollectionUtils.isNotEmpty(noPolicyNoList)) {
noPolicyNoBatch(noPolicyNoList); List<String> receivableNoList = noPolicyNoList.stream()
.map(Commission::getReceivableNo)
.filter(StringUtils::isNotBlank)
.distinct()
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(receivableNoList)) {
List<CommissionExpected> expectedList = commissionExpectedService.lambdaQuery()
.in(CommissionExpected::getReceivableNo, receivableNoList)
.list();
expectedList.forEach(e -> {
if (e.getCommissionExpectedBizId() != null) {
expectedBizIds.add(e.getCommissionExpectedBizId());
}
});
}
} }
} }
private Map<String, Commission> queryCommissionsByBizIds(Set<String> bizIds) {
if (CollectionUtils.isEmpty(bizIds)) {
return Collections.emptyMap();
}
List<Commission> list = commissionService.lambdaQuery()
.in(Commission::getCommissionBizId, new ArrayList<>(bizIds))
.list();
return list.stream().collect(Collectors.toMap(Commission::getCommissionBizId, Function.identity()));
}
private Map<String, CommissionExpected> queryExpectedByBizIds(Set<String> bizIds) {
if (CollectionUtils.isEmpty(bizIds)) {
return Collections.emptyMap();
}
List<CommissionExpected> list = commissionExpectedService.lambdaQuery()
.in(CommissionExpected::getCommissionExpectedBizId, new ArrayList<>(bizIds))
.list();
return list.stream().collect(Collectors.toMap(CommissionExpected::getCommissionExpectedBizId, Function.identity()));
}
/** /**
* 无保单号的来佣比对 * 无保单号的来佣比对
* *
......
package com.yd.csf.service.dao;
import com.yd.csf.service.model.SysOperDataChange;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Zhang Jianan
* @description 针对表【sys_oper_data_change】的数据库操作Mapper
* @createDate 2026-07-30 14:46:28
* @Entity generator.domain.SysOperDataChange
*/
public interface AuditLogDataChangeMapper extends BaseMapper<SysOperDataChange> {
}
\ No newline at end of file
package com.yd.csf.service.dao;
import com.yd.csf.service.model.SysOperDataSnapshot;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Zhang Jianan
* @description 针对表【sys_oper_data_snapshot】的数据库操作Mapper
* @createDate 2026-07-30 14:46:24
* @Entity generator.domain.SysOperDataSnapshot
*/
public interface AuditLogDataSnapshotMapper extends BaseMapper<SysOperDataSnapshot> {
}
\ No newline at end of file
package com.yd.csf.service.dao;
import com.yd.csf.service.model.SysOperLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Zhang Jianan
* @description 针对表【sys_oper_log】的数据库操作Mapper
* @createDate 2026-07-30 14:45:38
* @Entity generator.domain.SysOperLog
*/
public interface AuditLogMapper extends BaseMapper<SysOperLog> {
}
\ No newline at end of file
package com.yd.csf.service.model; package com.yd.csf.service.model;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import com.yd.csf.service.annotation.Auditable;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
...@@ -13,6 +14,7 @@ import java.util.Date; ...@@ -13,6 +14,7 @@ import java.util.Date;
* @TableName commission * @TableName commission
*/ */
@TableName(value = "commission") @TableName(value = "commission")
@Auditable(table = "commission", idField = "commissionBizId")
@Data @Data
public class Commission implements Serializable { public class Commission implements Serializable {
/** /**
......
package com.yd.csf.service.model; package com.yd.csf.service.model;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import com.yd.csf.service.annotation.Auditable;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
...@@ -14,6 +15,7 @@ import java.util.Date; ...@@ -14,6 +15,7 @@ import java.util.Date;
* @TableName commission_expected * @TableName commission_expected
*/ */
@TableName(value = "commission_expected") @TableName(value = "commission_expected")
@Auditable(table = "commission_expected", idField = "commissionExpectedBizId")
@Data @Data
public class CommissionExpected implements Serializable { public class CommissionExpected implements Serializable {
/** /**
......
package com.yd.csf.service.model; package com.yd.csf.service.model;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import com.yd.csf.service.annotation.Auditable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
...@@ -20,6 +21,7 @@ import java.time.LocalDateTime; ...@@ -20,6 +21,7 @@ import java.time.LocalDateTime;
@Getter @Getter
@Setter @Setter
@TableName("expected_fortune") @TableName("expected_fortune")
@Auditable(table = "expected_fortune", idField = "expectedFortuneBizId")
public class ExpectedFortune implements Serializable { public class ExpectedFortune implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
......
package com.yd.csf.service.model; package com.yd.csf.service.model;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import com.yd.csf.service.annotation.Auditable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
...@@ -14,6 +15,7 @@ import lombok.Data; ...@@ -14,6 +15,7 @@ import lombok.Data;
* @TableName fortune * @TableName fortune
*/ */
@TableName(value = "fortune") @TableName(value = "fortune")
@Auditable(table = "fortune", idField = "fortuneBizId")
@Data @Data
public class Fortune { public class Fortune {
/** /**
......
package com.yd.csf.service.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
*
* @TableName sys_oper_data_change
*/
@TableName(value ="sys_oper_data_change")
@Data
public class SysOperDataChange {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
*
*/
private String requestId;
/**
* 关联 sys_oper_log.id(MP拦截器路径不设置,由request_id关联)
*/
private Long operLogId;
/**
* 表名
*/
private String tableName;
/**
* 记录主键
*/
private String recordId;
/**
* 字段名
*/
private String fieldName;
/**
* 字段中文名
*/
private String fieldComment;
/**
* 变更前值
*/
private String oldValue;
/**
* 变更后值
*/
private String newValue;
/**
* ADD/MODIFY/DELETE
*/
private String changeType;
/**
*
*/
private Date createTime;
}
\ No newline at end of file
package com.yd.csf.service.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
*
* @TableName sys_oper_data_snapshot
*/
@TableName(value ="sys_oper_data_snapshot")
@Data
public class SysOperDataSnapshot {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
*
*/
private String requestId;
/**
* 表名
*/
private String tableName;
/**
* 记录主键
*/
private String recordId;
/**
* 变更前完整快照
*/
private String beforeJson;
/**
* 变更后完整快照
*/
private String afterJson;
/**
*
*/
private Date createTime;
}
\ No newline at end of file
package com.yd.csf.service.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.Data;
/**
*
* @TableName sys_oper_log
*/
@TableName(value ="sys_oper_log")
@Data
public class SysOperLog {
/**
* 日志主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
*
*/
private String requestId;
/**
* 业务模块
*/
private String module;
/**
* 业务类型 0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据
*/
private Integer businessType;
/**
* 方法名称
*/
private String method;
/**
* 日志内容
*/
private String content;
/**
* 业务数据ID
*/
private Long businessId;
/**
* 操作类型(0其它 1新增 2修改 3删除)
*/
private Integer operationType;
/**
* 操作人ID
*/
private Long userId;
/**
* 操作人员
*/
private String operName;
/**
* 部门名称
*/
private String deptName;
/**
* 请求URL
*/
private String operUrl;
/**
* 主机地址
*/
private String operIp;
/**
* 操作地点
*/
private String operLocation;
/**
* 请求参数
*/
private String operParam;
/**
* 返回参数
*/
private String jsonResult;
/**
* 0成功 1失败
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
private Date operTime;
}
\ No newline at end of file
package com.yd.csf.service.service;
import com.yd.csf.service.model.SysOperDataChange;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Zhang Jianan
* @description 针对表【sys_oper_data_change】的数据库操作Service
* @createDate 2026-07-30 14:46:28
*/
public interface AuditLogDataChangeService extends IService<SysOperDataChange> {
}
package com.yd.csf.service.service;
import com.yd.csf.service.model.SysOperDataSnapshot;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Zhang Jianan
* @description 针对表【sys_oper_data_snapshot】的数据库操作Service
* @createDate 2026-07-30 14:46:24
*/
public interface AuditLogDataSnapshotService extends IService<SysOperDataSnapshot> {
}
package com.yd.csf.service.service;
import com.yd.csf.service.model.SysOperLog;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Zhang Jianan
* @description 针对表【sys_oper_log】的数据库操作Service
* @createDate 2026-07-30 14:45:38
*/
public interface AuditLogService extends IService<SysOperLog> {
}
package com.yd.csf.service.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yd.csf.service.model.SysOperDataChange;
import com.yd.csf.service.service.AuditLogDataChangeService;
import com.yd.csf.service.dao.AuditLogDataChangeMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Zhang Jianan
* @description 针对表【sys_oper_data_change】的数据库操作Service实现
* @createDate 2026-07-30 14:46:28
*/
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class AuditLogDataChangeServiceImpl extends ServiceImpl<AuditLogDataChangeMapper, SysOperDataChange>
implements AuditLogDataChangeService {
}
package com.yd.csf.service.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yd.csf.service.model.SysOperDataSnapshot;
import com.yd.csf.service.service.AuditLogDataSnapshotService;
import com.yd.csf.service.dao.AuditLogDataSnapshotMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Zhang Jianan
* @description 针对表【sys_oper_data_snapshot】的数据库操作Service实现
* @createDate 2026-07-30 14:46:24
*/
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class AuditLogDataSnapshotServiceImpl extends ServiceImpl<AuditLogDataSnapshotMapper, SysOperDataSnapshot>
implements AuditLogDataSnapshotService {
}
package com.yd.csf.service.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yd.csf.service.model.SysOperLog;
import com.yd.csf.service.service.AuditLogService;
import com.yd.csf.service.dao.AuditLogMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Zhang Jianan
* @description 针对表【sys_oper_log】的数据库操作Service实现
* @createDate 2026-07-30 14:45:38
*/
@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class AuditLogServiceImpl extends ServiceImpl<AuditLogMapper, SysOperLog>
implements AuditLogService {
}
...@@ -24,6 +24,10 @@ import com.yd.csf.service.enums.CommissionStatusEnum; ...@@ -24,6 +24,10 @@ import com.yd.csf.service.enums.CommissionStatusEnum;
import com.yd.csf.service.enums.FortuneStatusEnum; import com.yd.csf.service.enums.FortuneStatusEnum;
import com.yd.csf.service.model.*; import com.yd.csf.service.model.*;
import com.yd.csf.service.service.*; import com.yd.csf.service.service.*;
import com.yd.csf.service.audit.AuditContextHolder;
import com.yd.csf.service.audit.AuditHelper;
import com.yd.csf.service.audit.AuditService;
import com.yd.csf.service.audit.AuditTemplate;
import com.yd.csf.service.dao.CommissionMapper; import com.yd.csf.service.dao.CommissionMapper;
import com.yd.csf.service.vo.CommissionStatisticsVO; import com.yd.csf.service.vo.CommissionStatisticsVO;
import com.yd.csf.service.vo.CommissionVO; import com.yd.csf.service.vo.CommissionVO;
...@@ -85,6 +89,12 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss ...@@ -85,6 +89,12 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss
private ApiInsuranceReconciliationCompanyFeignClient apiInsuranceReconciliationCompanyFeignClient; private ApiInsuranceReconciliationCompanyFeignClient apiInsuranceReconciliationCompanyFeignClient;
@Resource @Resource
private PolicyPolicyholderService policyPolicyholderService; private PolicyPolicyholderService policyPolicyholderService;
@Resource
private AuditService auditService;
@Resource
private AuditHelper auditHelper;
@Resource
private AuditTemplate auditTemplate;
@Override @Override
...@@ -151,7 +161,7 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss ...@@ -151,7 +161,7 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss
CommissionExpected::getStatus, CommissionExpected::getStatus,
CommissionExpected::getReceivableNo, CommissionExpected::getReceivableNo,
CommissionExpected::getStatusDesc CommissionExpected::getStatusDesc
) )
.in(CommissionExpected::getCommissionExpectedBizId, commissionExpectedBizIdSet) .in(CommissionExpected::getCommissionExpectedBizId, commissionExpectedBizIdSet)
.list(); .list();
Map<String, CommissionExpected> commissionExpectedMap = commissionExpectedList.stream().collect(Collectors.toMap(CommissionExpected::getCommissionExpectedBizId, a -> a, (oldValue, newValue) -> newValue)); Map<String, CommissionExpected> commissionExpectedMap = commissionExpectedList.stream().collect(Collectors.toMap(CommissionExpected::getCommissionExpectedBizId, a -> a, (oldValue, newValue) -> newValue));
...@@ -608,113 +618,141 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss ...@@ -608,113 +618,141 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Boolean generateFortune(GenerateFortuneRequest generateFortuneRequest) { public Boolean generateFortune(GenerateFortuneRequest generateFortuneRequest) {
List<String> commissionBizIdList = generateFortuneRequest.getCommissionBizIdList(); return auditTemplate.execute("应付款管理", 2, "生成可出账记录", ctx -> {
List<Commission> commissions = this.lambdaQuery().in(Commission::getCommissionBizId, commissionBizIdList).list(); List<String> commissionBizIdList = generateFortuneRequest.getCommissionBizIdList();
List<Commission> commissions = this.lambdaQuery().in(Commission::getCommissionBizId, commissionBizIdList).list();
// 校验来佣记录是否存在 // 校验来佣记录是否存在
if (CollectionUtils.isEmpty(commissions)) { if (CollectionUtils.isEmpty(commissions)) {
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "未找到对应的来佣记录,请先创建来佣记录"); throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "未找到对应的来佣记录,请先创建来佣记录");
} }
// 获取所有检核年月
Set<String> reconciliationYearMonthSet = commissions.stream()
.map(Commission::getReconciliationYearMonth)
.collect(Collectors.toSet());
// 校验检核年月是否一致 // 获取所有检核年月
if (reconciliationYearMonthSet.size() != 1) { Set<String> reconciliationYearMonthSet = commissions.stream()
throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "请选择相同的检核年月"); .map(Commission::getReconciliationYearMonth)
} .collect(Collectors.toSet());
// 本次检核年月
String reconciliationYearMonth = reconciliationYearMonthSet.iterator().next();
for (Commission commission : commissions) { // 校验检核年月是否一致
if ("U".equals(commission.getCommissionBizType())) { if (reconciliationYearMonthSet.size() != 1) {
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "非关联保单应收单,不能点击生成可出账记录"); throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "请选择相同的检核年月");
} }
if (StringUtils.isBlank(commission.getPolicyNo())) { // 本次检核年月
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "保单号不能为空"); String reconciliationYearMonth = reconciliationYearMonthSet.iterator().next();
for (Commission commission : commissions) {
if ("U".equals(commission.getCommissionBizType())) {
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "非关联保单应收单,不能点击生成可出账记录");
}
if (StringUtils.isBlank(commission.getPolicyNo())) {
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "保单号不能为空");
}
} }
}
// 获取所有保单号 // 获取所有保单号
Set<String> policyNoSet = commissions.stream() Set<String> policyNoSet = commissions.stream()
.map(Commission::getPolicyNo) .map(Commission::getPolicyNo)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 1.1 根据保单号查询的预计发佣记录 // 1.1 根据保单号查询的预计发佣记录
List<ExpectedFortune> expectedFortuneList = iExpectedFortuneService.lambdaQuery() List<ExpectedFortune> expectedFortuneList = iExpectedFortuneService.lambdaQuery()
.in(ExpectedFortune::getPolicyNo, policyNoSet) .in(ExpectedFortune::getPolicyNo, policyNoSet)
.list(); .list();
// 1.2 构建保单号+佣金期数的快速查找集合 // 1.2 构建保单号+佣金期数的快速查找集合
Set<String> commissionKeySet = commissions.stream() Set<String> commissionKeySet = commissions.stream()
.map(c -> c.getPolicyNo() + "_" + c.getCommissionPeriod()) .map(c -> c.getPolicyNo() + "_" + c.getCommissionPeriod())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 1.3 根据保单号+佣金期数筛选符合的预计发佣记录 // 1.3 根据保单号+佣金期数筛选符合的预计发佣记录
List<ExpectedFortune> filteredExpectedFortuneList1 = new ArrayList<>(); List<ExpectedFortune> filteredExpectedFortuneList1 = new ArrayList<>();
for (ExpectedFortune expectedFortune : expectedFortuneList) { for (ExpectedFortune expectedFortune : expectedFortuneList) {
if (commissionKeySet.contains(expectedFortune.getPolicyNo() + "_" + expectedFortune.getFortunePeriod())) { if (commissionKeySet.contains(expectedFortune.getPolicyNo() + "_" + expectedFortune.getFortunePeriod())) {
filteredExpectedFortuneList1.add(expectedFortune); filteredExpectedFortuneList1.add(expectedFortune);
}
}
if (CollectionUtils.isEmpty(filteredExpectedFortuneList1)) {
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "未找到保单对应的预计发佣记录,请先创建预计发佣记录");
} }
}
if (CollectionUtils.isEmpty(filteredExpectedFortuneList1)) {
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "未找到保单对应的预计发佣记录,请先创建预计发佣记录");
}
// 2. 根据本次涉及的预计发佣记录,查询相关的 fortune 记录 // 2. 根据本次涉及的预计发佣记录,查询相关的 fortune 记录
List<Fortune> fortuneList = fortuneService.lambdaQuery() List<Fortune> fortuneList = fortuneService.lambdaQuery()
.in(Fortune::getExpectedFortuneBizId, filteredExpectedFortuneList1.stream().map(ExpectedFortune::getExpectedFortuneBizId).collect(Collectors.toList())) .in(Fortune::getExpectedFortuneBizId, filteredExpectedFortuneList1.stream().map(ExpectedFortune::getExpectedFortuneBizId).collect(Collectors.toList()))
.list(); .list();
// 2.1 对 fortune 记录进行分类,已出账、已分期、手动加的记录不能删除,其他记录删除后根据预计发佣记录重新生成 // 2.1 对 fortune 记录进行分类,已出账、已分期、手动加的记录不能删除,其他记录删除后根据预计发佣记录重新生成
List<Fortune> deleteList = new ArrayList<>(); List<Fortune> deleteList = new ArrayList<>();
List<Fortune> keepList = new ArrayList<>(); List<Fortune> keepList = new ArrayList<>();
for (Fortune fortune : fortuneList) { for (Fortune fortune : fortuneList) {
if (FortuneStatusEnum.SENT.getItemValue().equals(fortune.getStatus()) if (FortuneStatusEnum.SENT.getItemValue().equals(fortune.getStatus())
|| !"系统生成".equals(fortune.getReconciliationOperator()) || !"系统生成".equals(fortune.getReconciliationOperator())
|| 1 == fortune.getIsPart()) { || 1 == fortune.getIsPart()) {
keepList.add(fortune); keepList.add(fortune);
} else { } else {
deleteList.add(fortune); deleteList.add(fortune);
}
} }
}
// 2.1 物理删除 // 2.1 物理删除
if (CollectionUtils.isNotEmpty(deleteList)) { if (CollectionUtils.isNotEmpty(deleteList)) {
fortuneService.removeByIdsPhysical(deleteList.stream().map(Fortune::getId).collect(Collectors.toList())); fortuneService.removeByIdsPhysical(deleteList.stream().map(Fortune::getId).collect(Collectors.toList()));
} }
// 2.2 keepList 的记录,不重新生成 // 2.2 keepList 的记录,不重新生成
Set<String> keepExpectedFortuneBizIdSet = keepList.stream().map(Fortune::getExpectedFortuneBizId).collect(Collectors.toSet()); Set<String> keepExpectedFortuneBizIdSet = keepList.stream().map(Fortune::getExpectedFortuneBizId).collect(Collectors.toSet());
// 2.3 筛选出需要重新生成的记录 // 2.3 筛选出需要重新生成的记录
List<ExpectedFortune> filteredExpectedFortuneList2 = new ArrayList<>(); List<ExpectedFortune> filteredExpectedFortuneList2 = new ArrayList<>();
for (ExpectedFortune expectedFortune : filteredExpectedFortuneList1) { for (ExpectedFortune expectedFortune : filteredExpectedFortuneList1) {
// 如果是 keep 记录,跳过 // 如果是 keep 记录,跳过
if (keepExpectedFortuneBizIdSet.contains(expectedFortune.getExpectedFortuneBizId())) { if (keepExpectedFortuneBizIdSet.contains(expectedFortune.getExpectedFortuneBizId())) {
continue; continue;
}
filteredExpectedFortuneList2.add(expectedFortune);
} }
filteredExpectedFortuneList2.add(expectedFortune); // 2.4 校验预计发佣记录是否有出账币种、默认结算汇率
} for (ExpectedFortune expectedFortune : filteredExpectedFortuneList2) {
// 2.4 校验预计发佣记录是否有出账币种、默认结算汇率 if (StringUtils.isBlank(expectedFortune.getRuleCurrency())) {
for (ExpectedFortune expectedFortune : filteredExpectedFortuneList2) { throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "预计发佣记录" + expectedFortune.getExpectedFortuneBizId() + "未配置保单币种");
if (StringUtils.isBlank(expectedFortune.getRuleCurrency())) { }
throw new BusinessException(ResultCode.NULL_ERROR.getCode(), "预计发佣记录" + expectedFortune.getExpectedFortuneBizId() + "未配置保单币种");
} }
}
// 2.5 根据保单号、期数查询入账检核汇率 // 2.5 根据保单号、期数查询入账检核汇率
Map<String, BigDecimal> exchangeRateMap = this.queryCommissionExchangeRateMap(commissions); Map<String, BigDecimal> exchangeRateMap = this.queryCommissionExchangeRateMap(commissions);
// 3. 构建实际的初始发佣记录(使用入账检核汇率) // 审计:深拷贝预计发佣记录变更前快照
List<Fortune> newFortuneList = buildNewFortunes(filteredExpectedFortuneList2, commissions, exchangeRateMap, reconciliationYearMonth); Map<String, ExpectedFortune> beforeExpectedFortuneMap =
auditHelper.snapshot(filteredExpectedFortuneList2);
// 4. 保存发佣记录 // 3. 构建实际的初始发佣记录(使用入账检核汇率)
saveNewFortunes(newFortuneList); List<Fortune> newFortuneList = buildNewFortunes(filteredExpectedFortuneList2, commissions, exchangeRateMap, reconciliationYearMonth);
return true; // 4. 保存发佣记录
saveNewFortunes(newFortuneList);
// 审计:fortune DELETE
if (CollUtil.isNotEmpty(deleteList)) {
Map<String, Fortune> deleteMap = deleteList.stream()
.collect(Collectors.toMap(Fortune::getFortuneBizId, Function.identity()));
auditHelper.record("fortune", deleteMap, null, ctx);
}
// 审计:fortune INSERT
Map<String, Fortune> insertMap = newFortuneList.stream()
.collect(Collectors.toMap(Fortune::getFortuneBizId, Function.identity()));
auditHelper.record("fortune", null, insertMap, ctx);
// 审计:expected_fortune UPDATE(重新查询 after 状态后 diff)
List<String> efBizIds = new ArrayList<>(beforeExpectedFortuneMap.keySet());
if (CollUtil.isNotEmpty(efBizIds)) {
Map<String, ExpectedFortune> afterExpectedFortuneMap = iExpectedFortuneService.lambdaQuery()
.in(ExpectedFortune::getExpectedFortuneBizId, efBizIds)
.list().stream()
.collect(Collectors.toMap(ExpectedFortune::getExpectedFortuneBizId, Function.identity()));
auditHelper.record("expected_fortune", beforeExpectedFortuneMap, afterExpectedFortuneMap, ctx);
}
return true;
});
} }
private Map<String, BigDecimal> queryCommissionExchangeRateMap(List<Commission> commissions) { private Map<String, BigDecimal> queryCommissionExchangeRateMap(List<Commission> commissions) {
...@@ -991,89 +1029,97 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss ...@@ -991,89 +1029,97 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss
if (CollectionUtils.isEmpty(customerAddRequestList)) { if (CollectionUtils.isEmpty(customerAddRequestList)) {
throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "入账数据不能为空"); throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "入账数据不能为空");
} }
validateAddCommissionBatch(customerAddRequestList);
Set<String> policyNoSet = customerAddRequestList.stream() return auditTemplate.execute("来佣管理", 1, "导入入账记录", ctx -> {
.map(CommissionAddRequest::getPolicyNo) validateAddCommissionBatch(customerAddRequestList);
.filter(StringUtils::isNotBlank).collect(Collectors.toSet());
// 关联查询保单信息、预计入账信息 Set<String> policyNoSet = customerAddRequestList.stream()
Map<String, Policy> policyMap = new HashMap<>(); .map(CommissionAddRequest::getPolicyNo)
List<CommissionExpected> commissionExpectedList = new ArrayList<>(); .filter(StringUtils::isNotBlank).collect(Collectors.toSet());
if (CollUtil.isNotEmpty(policyNoSet)) {
// 查询保单信息
List<Policy> policyList = policyService.lambdaQuery().in(Policy::getPolicyNo, policyNoSet).list();
// 保单映射
policyMap = policyList.stream().collect(Collectors.toMap(Policy::getPolicyNo, Function.identity()));
}
List<Commission> commissionList = new ArrayList<>(); // 关联查询保单信息、预计入账信息
for (CommissionAddRequest request : customerAddRequestList) { Map<String, Policy> policyMap = new HashMap<>();
Commission commission = new Commission(); List<CommissionExpected> commissionExpectedList = new ArrayList<>();
BeanUtils.copyProperties(request, commission); if (CollUtil.isNotEmpty(policyNoSet)) {
commission.setManualRemark(request.getManualRemark()); // 查询保单信息
List<Policy> policyList = policyService.lambdaQuery().in(Policy::getPolicyNo, policyNoSet).list();
// 保单映射
policyMap = policyList.stream().collect(Collectors.toMap(Policy::getPolicyNo, Function.identity()));
}
// 入账年月 List<Commission> commissionList = new ArrayList<>();
commission.setCommissionDate(DateUtil.parse(request.getCommissionDate() + "-01")); for (CommissionAddRequest request : customerAddRequestList) {
// 计算来佣金额(港币) Commission commission = new Commission();
commission.setHkdAmount(calculateHKDAmount(request.getAmount(), request.getCurrency())); BeanUtils.copyProperties(request, commission);
// 入账业务id commission.setManualRemark(request.getManualRemark());
commission.setCommissionBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_COMMISSION.getCode()));
// 关联业务id // 入账年月
if ("R".equals(request.getCommissionBizType())) { commission.setCommissionDate(DateUtil.parse(request.getCommissionDate() + "-01"));
Policy policy = policyMap.get(request.getPolicyNo()); // 计算来佣金额(港币)
if (policy != null) { commission.setHkdAmount(calculateHKDAmount(request.getAmount(), request.getCurrency()));
commission.setReconciliationCompany(policy.getReconciliationCompany()); // 入账业务id
commission.setReconciliationCompanyBizId(policy.getReconciliationCompanyBizId()); commission.setCommissionBizId(RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_COMMISSION.getCode()));
commission.setProductLaunchBizId(policy.getProductLaunchBizId()); // 关联业务id
commission.setInsuranceCompanyBizId(policy.getInsuranceCompanyBizId()); if ("R".equals(request.getCommissionBizType())) {
commission.setPremium(Convert.toStr(policy.getPaymentPremium())); Policy policy = policyMap.get(request.getPolicyNo());
commission.setPolicyCurrency(policy.getCurrency()); if (policy != null) {
// 本次入账比例 commission.setReconciliationCompany(policy.getReconciliationCompany());
commission.setCurrentCommissionRatio(calculateCurrentCommissionRatio( commission.setReconciliationCompanyBizId(policy.getReconciliationCompanyBizId());
commission.getPremium(), commission.setProductLaunchBizId(policy.getProductLaunchBizId());
commission.getAmount(), commission.setInsuranceCompanyBizId(policy.getInsuranceCompanyBizId());
commission.getCurrency(), commission.setPremium(Convert.toStr(policy.getPaymentPremium()));
commission.getExchangeRate(), commission.setPolicyCurrency(policy.getCurrency());
commission.getPolicyCurrency()) // 本次入账比例
); commission.setCurrentCommissionRatio(calculateCurrentCommissionRatio(
} else { commission.getPremium(),
commission.getAmount(),
commission.getCurrency(),
commission.getExchangeRate(),
commission.getPolicyCurrency())
);
} else {
// throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "保单不存在, policyNo: " + request.getPolicyNo()); // throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "保单不存在, policyNo: " + request.getPolicyNo());
throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "保单号不存在"); throw new BusinessException(ResultCode.PARAMS_ERROR.getCode(), "保单号不存在");
}
} else {
commission.setPolicyNo(null);
} }
} else { // 关联预计来佣信息
commission.setPolicyNo(null); CommissionExpected expected = getCommissionExpectedBizIdBatch(request, policyNoSet, commission);
if (expected != null) {
commission.setCommissionExpectedBizId(expected.getCommissionExpectedBizId());
commission.setReceivableNo(expected.getReceivableNo());
commission.setTotalPeriod(expected.getTotalPeriod());
}
commissionList.add(commission);
} }
// 关联预计来佣信息 // 1. 先保存主数据
CommissionExpected expected = getCommissionExpectedBizIdBatch(request, policyNoSet, commission); saveOrUpdateBatch(commissionList);
if (expected != null) {
commission.setCommissionExpectedBizId(expected.getCommissionExpectedBizId()); // 审计:commission INSERT
commission.setReceivableNo(expected.getReceivableNo()); if (CollUtil.isNotEmpty(commissionList)) {
commission.setTotalPeriod(expected.getTotalPeriod()); Map<String, Commission> insertMap = commissionList.stream()
.collect(Collectors.toMap(Commission::getCommissionBizId, Function.identity()));
auditHelper.record("commission", null, insertMap, ctx);
} }
commissionList.add(commission); // 2. 事务提交后执行比对
} TransactionSynchronizationManager.registerSynchronization(
// 1. 先保存主数据 new TransactionSynchronization() {
saveOrUpdateBatch(commissionList); @Override
public void afterCommit() {
// 2. 事务提交后执行比对 try {
TransactionSynchronizationManager.registerSynchronization( commissionAsyncService.commissionCompareBatch(commissionList, ctx.getRequestId());
new TransactionSynchronization() { } catch (Exception e) {
@Override log.error("比对更新失败", e);
public void afterCommit() { }
try {
// 这里会开启新事务执行更新
commissionAsyncService.commissionCompareBatch(commissionList);
} catch (Exception e) {
log.error("比对更新失败", e);
// 可以发送告警或记录失败任务
} }
} }
} );
);
return commissionList; return commissionList;
});
} }
private CommissionExpected getCommissionExpectedBizIdBatch(CommissionAddRequest request, Set<String> policyNoSet, Commission commission) { private CommissionExpected getCommissionExpectedBizIdBatch(CommissionAddRequest request, Set<String> policyNoSet, Commission commission) {
...@@ -1311,8 +1357,8 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss ...@@ -1311,8 +1357,8 @@ public class CommissionServiceImpl extends ServiceImpl<CommissionMapper, Commiss
@Override @Override
public Long queryCount(String commissionExpectedBizId) { public Long queryCount(String commissionExpectedBizId) {
return this.baseMapper.selectCount(new LambdaQueryWrapper<Commission>() return this.baseMapper.selectCount(new LambdaQueryWrapper<Commission>()
.eq(Commission::getCommissionExpectedBizId,commissionExpectedBizId) .eq(Commission::getCommissionExpectedBizId, commissionExpectedBizId)
.eq(Commission::getIsDeleted,0) .eq(Commission::getIsDeleted, 0)
); );
} }
......
...@@ -18,6 +18,11 @@ import com.yd.common.exception.BusinessException; ...@@ -18,6 +18,11 @@ import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result; import com.yd.common.result.Result;
import com.yd.common.utils.RandomStringGenerator; import com.yd.common.utils.RandomStringGenerator;
import com.yd.common.utils.RedisUtil; import com.yd.common.utils.RedisUtil;
import com.yd.csf.service.audit.AuditContextHolder;
import com.yd.csf.service.audit.AuditHelper;
import com.yd.csf.service.audit.AuditService;
import com.yd.csf.service.audit.AuditTemplate;
import com.yd.csf.service.audit.AuditContext;
import com.yd.csf.service.dto.*; import com.yd.csf.service.dto.*;
import com.yd.csf.service.enums.FortuneStatusEnum; import com.yd.csf.service.enums.FortuneStatusEnum;
import com.yd.csf.service.model.*; import com.yd.csf.service.model.*;
...@@ -75,6 +80,12 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune> ...@@ -75,6 +80,12 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune>
private ApiExchangeRateFeignClient apiExchangeRateFeignClient; private ApiExchangeRateFeignClient apiExchangeRateFeignClient;
@Resource @Resource
private IAgentAccumulatedFycService iAgentAccumulatedFycService; private IAgentAccumulatedFycService iAgentAccumulatedFycService;
@Resource
private AuditService auditService;
@Resource
private AuditHelper auditHelper;
@Resource
private AuditTemplate auditTemplate;
@Override @Override
public Wrapper<Fortune> getQueryWrapper(FortuneQueryRequest fortuneQueryRequest) { public Wrapper<Fortune> getQueryWrapper(FortuneQueryRequest fortuneQueryRequest) {
...@@ -767,6 +778,11 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune> ...@@ -767,6 +778,11 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune>
return false; return false;
} }
return auditTemplate.execute("出账管理", 1, "批量新增出账记录", ctx ->
doAddFortuneBatch(fortuneAddRequestList, ctx));
}
private Boolean doAddFortuneBatch(List<FortuneAddRequest> fortuneAddRequestList, AuditContext ctx) {
Set<String> policyNoSet = fortuneAddRequestList.stream() Set<String> policyNoSet = fortuneAddRequestList.stream()
.map(FortuneAddRequest::getPolicyNo) .map(FortuneAddRequest::getPolicyNo)
.filter(StringUtils::isNotBlank).collect(Collectors.toSet()); .filter(StringUtils::isNotBlank).collect(Collectors.toSet());
...@@ -810,6 +826,8 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune> ...@@ -810,6 +826,8 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune>
String realName = currentLoginUser.getRealName(); String realName = currentLoginUser.getRealName();
List<Fortune> fortuneList = new ArrayList<>(); List<Fortune> fortuneList = new ArrayList<>();
// 收集自动创建的 ExpectedFortune bizId
List<String> createdExpectedBizIds = new ArrayList<>();
for (FortuneAddRequest fortuneAddRequest : fortuneAddRequestList) { for (FortuneAddRequest fortuneAddRequest : fortuneAddRequestList) {
// 校验参数 // 校验参数
...@@ -845,6 +863,8 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune> ...@@ -845,6 +863,8 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune>
.update(); .update();
// 关联应付款编号 // 关联应付款编号
fortune.setPayableNo(payableNo); fortune.setPayableNo(payableNo);
// 记录自动创建的 expectedFortune
createdExpectedBizIds.add(expectedFortune.getExpectedFortuneBizId());
} else { } else {
// 如果有预计出账记录,提示去应付款管理页面修改金额,而不是在出账页面新增 // 如果有预计出账记录,提示去应付款管理页面修改金额,而不是在出账页面新增
throw new BusinessException(ResultCode.FAIL.getCode(), fortuneAddRequest.getFortuneName() + " 已存在应付记录,请在应付款管理页面修改金额"); throw new BusinessException(ResultCode.FAIL.getCode(), fortuneAddRequest.getFortuneName() + " 已存在应付记录,请在应付款管理页面修改金额");
...@@ -888,6 +908,25 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune> ...@@ -888,6 +908,25 @@ public class FortuneServiceImpl extends ServiceImpl<FortuneMapper, Fortune>
} }
this.saveBatch(fortuneList); this.saveBatch(fortuneList);
// 审计:fortune INSERT
if (CollUtil.isNotEmpty(fortuneList)) {
Map<String, Fortune> insertMap = fortuneList.stream()
.collect(Collectors.toMap(Fortune::getFortuneBizId, Function.identity()));
auditHelper.record("fortune", null, insertMap, ctx);
}
// 审计:expected_fortune INSERT(自动创建时)
if (CollUtil.isNotEmpty(createdExpectedBizIds)) {
List<ExpectedFortune> createdList = expectedFortuneService.lambdaQuery()
.in(ExpectedFortune::getExpectedFortuneBizId, createdExpectedBizIds)
.list();
if (CollUtil.isNotEmpty(createdList)) {
Map<String, ExpectedFortune> efInsertMap = createdList.stream()
.collect(Collectors.toMap(ExpectedFortune::getExpectedFortuneBizId, Function.identity()));
auditHelper.record("expected_fortune", null, efInsertMap, ctx);
}
}
return true; return true;
} }
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yd.csf.service.dao.AuditLogDataChangeMapper">
<resultMap id="BaseResultMap" type="com.yd.csf.service.model.SysOperDataChange">
<id property="id" column="id"/>
<result property="requestId" column="request_id"/>
<result property="operLogId" column="oper_log_id"/>
<result property="tableName" column="table_name"/>
<result property="recordId" column="record_id"/>
<result property="fieldName" column="field_name"/>
<result property="fieldComment" column="field_comment"/>
<result property="oldValue" column="old_value"/>
<result property="newValue" column="new_value"/>
<result property="changeType" column="change_type"/>
<result property="createTime" column="create_time"/>
</resultMap>
<sql id="Base_Column_List">
id
,request_id,oper_log_id,table_name,record_id,field_name,
field_comment,old_value,new_value,change_type,create_time
</sql>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yd.csf.service.dao.AuditLogDataSnapshotMapper">
<resultMap id="BaseResultMap" type="com.yd.csf.service.model.SysOperDataSnapshot">
<id property="id" column="id"/>
<result property="requestId" column="request_id"/>
<result property="tableName" column="table_name"/>
<result property="recordId" column="record_id"/>
<result property="beforeJson" column="before_json"/>
<result property="afterJson" column="after_json"/>
<result property="createTime" column="create_time"/>
</resultMap>
<sql id="Base_Column_List">
id
,request_id,table_name,record_id,before_json,after_json,
create_time
</sql>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yd.csf.service.dao.AuditLogMapper">
<resultMap id="BaseResultMap" type="com.yd.csf.service.model.SysOperLog">
<id property="id" column="id"/>
<result property="requestId" column="request_id"/>
<result property="module" column="module"/>
<result property="businessType" column="business_type"/>
<result property="method" column="method"/>
<result property="content" column="content"/>
<result property="businessId" column="business_id"/>
<result property="operationType" column="operation_type"/>
<result property="userId" column="user_id"/>
<result property="operName" column="oper_name"/>
<result property="deptName" column="dept_name"/>
<result property="operUrl" column="oper_url"/>
<result property="operIp" column="oper_ip"/>
<result property="operLocation" column="oper_location"/>
<result property="operParam" column="oper_param"/>
<result property="jsonResult" column="json_result"/>
<result property="status" column="status"/>
<result property="errorMsg" column="error_msg"/>
<result property="operTime" column="oper_time"/>
</resultMap>
<sql id="Base_Column_List">
id
,request_id,module,business_type,method,content,
business_id,operation_type,user_id,oper_name,dept_name,
oper_url,oper_ip,oper_location,oper_param,json_result,
status,error_msg,oper_time
</sql>
</mapper>
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