✨ 应收账单接入角色数据权限并同步在线缓存

This commit is contained in:
caojia
2026-10-27 11:29:24 +08:00
parent f3fa3e1396
commit 4659c63ef9
13 changed files with 649 additions and 24 deletions
@@ -2,6 +2,7 @@ package com.bms.ar.mapper;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.bms.ar.domain.ArBillGenerateTask;
@@ -14,7 +15,8 @@ public interface ArBillGenerateTaskMapper {
ArBillGenerateTask selectByRequestToken(String requestToken);
List<ArBillGenerateTask> selectActiveByCreateBy(@Param("createBy") String createBy);
List<ArBillGenerateTask> selectActiveByCreateBy(@Param("createBy") String createBy,
@Param("params") Map<String, Object> params);
List<ArBillGenerateTask> selectWaiting(@Param("limit") int limit);
@@ -1,12 +1,13 @@
package com.bms.ar.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Param;
import com.bms.ar.domain.ArBillLog;
public interface ArBillLogMapper
{
List<ArBillLog> selectList(ArBillLog query);
List<ArBillLog> selectList(@Param("query") ArBillLog query, @Param("params") Map<String, Object> params);
ArBillLog selectSuccessByRequestId(@Param("requestId") String requestId,
@Param("objectType") String objectType,
@@ -11,6 +11,10 @@ import com.bms.ar.domain.vo.ArBillQuery;
public interface ArBillMapper {
List<ArBill> selectArBillList(ArBillQuery query);
ArBill selectArBillByDataScope(ArBillQuery query);
Long selectCustomerIdByDataScope(ArBillQuery query);
ArBill selectArBillById(Long arBillId);
ArBill selectArBillByBillNo(String billNo);
@@ -0,0 +1,82 @@
package com.bms.ar.service;
import java.util.List;
import java.util.function.Supplier;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bms.ar.domain.ArBill;
import com.bms.ar.domain.ArBillGenerateTask;
import com.bms.ar.domain.ArBillLog;
import com.bms.ar.domain.vo.ArBillQuery;
import com.bms.ar.mapper.ArBillGenerateTaskMapper;
import com.bms.ar.mapper.ArBillLogMapper;
import com.bms.ar.mapper.ArBillMapper;
import com.bms.common.annotation.DataScope;
import com.bms.common.constant.HttpStatus;
import com.bms.common.exception.ServiceException;
/**
* 按客户归属业务员和其部门校验账单可见范围。
* 独立 Bean 保证内部业务调用也经过 DataScope 切面;范围始终取具有账单列表权限的角色。
*/
@Service
public class ArBillDataScopeService {
@Autowired
private ArBillMapper billMapper;
@Autowired
private ArBillLogMapper logMapper;
@Autowired
private ArBillGenerateTaskMapper generateTaskMapper;
@DataScope(deptAlias = "ar_scope_user", userAlias = "ar_scope_user", permission = "ar:bill:list")
public ArBill requireBill(ArBillQuery query) {
return withoutPagination(() -> {
ArBill bill = query.getArBillId() == null ? null : billMapper.selectArBillByDataScope(query);
if (bill == null) {
throw new ServiceException("应收账单不存在或无权访问", HttpStatus.FORBIDDEN);
}
return bill;
});
}
@DataScope(deptAlias = "ar_scope_user", userAlias = "ar_scope_user", permission = "ar:bill:list")
public void requireCustomer(ArBillQuery query) {
withoutPagination(() -> {
Long customerId = query.getCustomerId() == null || query.getCustomerId() <= 0
? null : billMapper.selectCustomerIdByDataScope(query);
if (customerId == null || customerId <= 0) {
throw new ServiceException("客户不存在或无权访问该客户的应收账单", HttpStatus.FORBIDDEN);
}
return customerId;
});
}
@DataScope(deptAlias = "ar_scope_user", userAlias = "ar_scope_user", permission = "ar:bill:list")
public List<ArBillLog> selectLogs(ArBillQuery scope, ArBillLog query) {
return logMapper.selectList(query, scope.getParams());
}
@DataScope(deptAlias = "ar_scope_user", userAlias = "ar_scope_user", permission = "ar:bill:list")
public List<ArBillGenerateTask> selectActiveGenerateTasks(ArBillQuery scope, String operator) {
return generateTaskMapper.selectActiveByCreateBy(operator, scope.getParams());
}
/** 权限校验 SQL 不消耗调用方为明细/订单预览设置的分页,拒绝访问时清理分页。 */
private <T> T withoutPagination(Supplier<T> lookup) {
Page<?> pendingPage = PageHelper.getLocalPage();
PageHelper.clearPage();
try {
T result = lookup.get();
if (pendingPage != null) {
PageHelper.setLocalPage(pendingPage);
}
return result;
} catch (RuntimeException | Error e) {
PageHelper.clearPage();
throw e;
}
}
}
@@ -13,16 +13,20 @@ import com.bms.ar.enums.ArLogOperationType;
import com.bms.ar.enums.ArLogResult;
import com.bms.ar.mapper.ArBillLogMapper;
import com.bms.ar.service.IArBillLogService;
import com.bms.ar.service.ArBillDataScopeService;
import com.bms.ar.domain.vo.ArBillQuery;
import com.bms.common.utils.uuid.IdUtils;
@Service
public class ArBillLogServiceImpl implements IArBillLogService {
@Autowired
private ArBillLogMapper logMapper;
@Autowired
private ArBillDataScopeService dataScopeService;
@Override
public List<ArBillLog> selectLogList(ArBillLog query) {
return logMapper.selectList(query);
return dataScopeService.selectLogs(new ArBillQuery(), query);
}
@Override
@@ -75,11 +75,13 @@ import com.bms.ar.mapper.ArBankAccountMapper;
import com.bms.ar.mapper.ArExportTemplateMapper;
import com.bms.ar.service.IArBillLogService;
import com.bms.ar.service.IArBillService;
import com.bms.ar.service.ArBillDataScopeService;
import com.bms.ar.domain.vo.ArBillActionRequest;
import com.bms.ar.domain.vo.ArBillGenerateQuery;
import com.bms.ar.domain.vo.ArBillGenerateRequest;
import com.bms.ar.domain.vo.ArBillQuery;
import com.bms.common.config.BmsConfig;
import com.bms.common.annotation.DataScope;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.DateUtils;
import com.bms.common.utils.SecurityUtils;
@@ -197,6 +199,8 @@ public class ArBillServiceImpl implements IArBillService {
@Autowired
private IArBillLogService logService;
@Autowired
private ArBillDataScopeService dataScopeService;
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private PrcOrderBillMapper sourceBillMapper;
@@ -211,6 +215,7 @@ public class ArBillServiceImpl implements IArBillService {
* 查询账单列表,并对返回给前端的收款账号做脱敏。
*/
@Override
@DataScope(deptAlias = "ar_scope_user", userAlias = "ar_scope_user", permission = "ar:bill:list")
public List<ArBill> selectArBillList(ArBillQuery query) {
long startedAt = System.nanoTime();
List<ArBill> bills = billMapper.selectArBillList(query);
@@ -229,10 +234,7 @@ public class ArBillServiceImpl implements IArBillService {
@Override
public ArBill selectArBillById(Long arBillId) {
long startedAt = System.nanoTime();
ArBill bill = billMapper.selectArBillById(arBillId);
if (bill == null) {
throw new ServiceException("应收账单不存在");
}
ArBill bill = requireBillDataScope(arBillId);
bill.setBankAccountNo(maskAccount(bill.getBankAccountNo()));
bill.setExports(exportMapper.selectByBillId(arBillId));
log.debug("应收账单详情查询完成,arBillId={}, status={}, exportCount={}, costMs={}",
@@ -247,6 +249,7 @@ public class ArBillServiceImpl implements IArBillService {
@Override
public List<ArBillDetail> selectArBillDetails(Long arBillId) {
long startedAt = System.nanoTime();
requireBillDataScope(arBillId);
List<ArBillDetail> details = detailMapper.selectByBillId(arBillId);
log.debug("应收账单明细查询完成,arBillId={}, resultCount={}, costMs={}",
arBillId, details.size(), elapsedMillis(startedAt));
@@ -259,6 +262,7 @@ public class ArBillServiceImpl implements IArBillService {
@Override
public List<ArBillDetailFee> selectArBillDetailFees(Long arBillId, Long detailId) {
long startedAt = System.nanoTime();
requireBillDataScope(arBillId);
List<ArBillDetailFee> fees = detailFeeMapper.selectByBillIdAndDetailId(arBillId, detailId);
log.debug("应收账单费用明细查询完成,arBillId={}, detailId={}, resultCount={}, costMs={}",
arBillId, detailId, fees.size(), elapsedMillis(startedAt));
@@ -299,6 +303,7 @@ public class ArBillServiceImpl implements IArBillService {
public List<PrcOrderBill> previewBill(ArBillGenerateQuery query) {
long startedAt = System.nanoTime();
validatePreviewQuery(query);
requireCustomerDataScope(query.getCustomerId());
List<String> orderIdentifiers = normalizeOrderIdentifiers(query.getOrderOrTrackingNo());
List<String> excludeOrderIdentifiers = normalizeOrderIdentifiers(query.getExcludeOrderOrTrackingNo());
List<PrcOrderBill> result = sourceBillMapper.selectPrcOrderBillByCustomerPeriod(query.getCustomerId(),
@@ -327,6 +332,7 @@ public class ArBillServiceImpl implements IArBillService {
public BigDecimal sumPreviewBill(ArBillGenerateQuery query) {
long startedAt = System.nanoTime();
validatePreviewQuery(query);
requireCustomerDataScope(query.getCustomerId());
List<String> orderIdentifiers = normalizeOrderIdentifiers(query.getOrderOrTrackingNo());
List<String> excludeOrderIdentifiers = normalizeOrderIdentifiers(query.getExcludeOrderOrTrackingNo());
BigDecimal total = nvl(sourceBillMapper.sumPrcOrderBillIncomeByCustomerPeriod(query.getCustomerId(),
@@ -350,8 +356,13 @@ public class ArBillServiceImpl implements IArBillService {
long startedAt = System.nanoTime();
Long createUserId = SecurityUtils.getUserId();
validatePeriod(request);
requireCustomerDataScope(request.getCustomerId());
ArBillGenerateTask existed = generateTaskMapper.selectByRequestToken(requestId);
if (existed != null) {
ArBill existedBill = requireBillDataScope(existed.getArBillId());
if (!request.getCustomerId().equals(existedBill.getCustomerId())) {
throw new ServiceException("请求标识已用于其他客户,请重新发起");
}
log.info("应收账单生成请求命中幂等记录,operator={}, requestId={}, taskId={}, status={}, costMs={}",
safeLogValue(operator), safeLogValue(requestId), existed.getTaskId(), existed.getStatus(),
elapsedMillis(startedAt));
@@ -471,6 +482,7 @@ public class ArBillServiceImpl implements IArBillService {
if (task == null) {
throw new ServiceException("账单生成任务不存在");
}
requireBillDataScope(task.getArBillId());
log.debug("应收账单生成任务状态查询,taskId={}, status={}, processedCount={}, totalCount={}, costMs={}",
taskId, task.getStatus(), task.getProcessedCount(), task.getTotalCount(), elapsedMillis(startedAt));
return task;
@@ -482,7 +494,7 @@ public class ArBillServiceImpl implements IArBillService {
@Override
public List<ArBillGenerateTask> selectActiveGenerateTasks(String operator) {
long startedAt = System.nanoTime();
List<ArBillGenerateTask> tasks = generateTaskMapper.selectActiveByCreateBy(operator);
List<ArBillGenerateTask> tasks = dataScopeService.selectActiveGenerateTasks(new ArBillQuery(), operator);
log.debug("当前用户应收账单生成任务查询,operator={}, taskCount={}, costMs={}",
safeLogValue(operator), tasks.size(), elapsedMillis(startedAt));
return tasks;
@@ -913,6 +925,7 @@ public class ArBillServiceImpl implements IArBillService {
@Transactional(rollbackFor = Exception.class)
public ArBill confirmBill(Long arBillId, ArBillActionRequest request, String operator, String requestId) {
long startedAt = System.nanoTime();
requireBillDataScope(arBillId);
ArBill bill = billMapper.selectArBillByIdForUpdate(arBillId);
if (bill == null) {
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.CONFIRM,
@@ -957,6 +970,7 @@ public class ArBillServiceImpl implements IArBillService {
@Transactional(rollbackFor = Exception.class)
public ArBill voidBill(Long arBillId, ArBillActionRequest request, String operator, String requestId) {
long startedAt = System.nanoTime();
requireBillDataScope(arBillId);
String remark = request == null ? null : request.getRemark();
if (StringUtils.isEmpty(remark)) {
throw failure(arBillId, ArLogObjectType.BILL, String.valueOf(arBillId), ArLogOperationType.VOID,
@@ -1001,23 +1015,22 @@ public class ArBillServiceImpl implements IArBillService {
@Transactional(rollbackFor = Exception.class)
public ArBillExport exportBill(Long arBillId, String operator, String requestId) {
long startedAt = System.nanoTime();
ArBill bill = requireBillDataScope(arBillId);
Long exportUserId = SecurityUtils.getUserId();
ArBillLog previous = logMapper.selectSuccessByRequestId(requestId, ArLogObjectType.EXPORT.getCode(),
ArLogResult.SUCCESS.getCode());
if (previous != null && isNumber(previous.getObjectId())) {
ArBillExport previousExport = exportMapper.selectById(Long.valueOf(previous.getObjectId()));
if (previousExport != null) {
if (!arBillId.equals(previousExport.getArBillId())) {
throw new ServiceException("请求标识已用于其他账单,请重新发起");
}
log.info("应收账单导出请求命中幂等记录,operator={}, requestId={}, arBillId={}, exportId={}, status={}, costMs={}",
safeLogValue(operator), safeLogValue(requestId), arBillId, previousExport.getExportId(),
previousExport.getExportStatus(), elapsedMillis(startedAt));
return previousExport;
}
}
ArBill bill = billMapper.selectArBillById(arBillId);
if (bill == null) {
throw failure(arBillId, ArLogObjectType.EXPORT, String.valueOf(arBillId), ArLogOperationType.EXPORT,
null, "应收账单不存在", operator, requestId);
}
if (!ArBillGenerationStatus.READY.equals(bill.getGenerationStatus())) {
throw failure(arBillId, ArLogObjectType.EXPORT, String.valueOf(arBillId), ArLogOperationType.EXPORT,
bill.getStatus(), "账单尚未生成完成,暂不能导出", operator, requestId);
@@ -1078,6 +1091,7 @@ public class ArBillServiceImpl implements IArBillService {
if (export == null) {
throw new ServiceException("导出任务不存在");
}
requireBillDataScope(export.getArBillId());
log.debug("应收账单导出任务状态查询,exportId={}, status={}, costMs={}",
exportId, export.getExportStatus(), elapsedMillis(startedAt));
return export;
@@ -1158,6 +1172,7 @@ public class ArBillServiceImpl implements IArBillService {
@Override
public List<ArBillExport> selectExports(Long arBillId) {
long startedAt = System.nanoTime();
requireBillDataScope(arBillId);
List<ArBillExport> exports = exportMapper.selectByBillId(arBillId);
log.debug("应收账单导出记录查询,arBillId={}, exportCount={}, costMs={}",
arBillId, exports.size(), elapsedMillis(startedAt));
@@ -1167,8 +1182,8 @@ public class ArBillServiceImpl implements IArBillService {
@Override
public void downloadExport(Long exportId, HttpServletResponse response) throws Exception {
long startedAt = System.nanoTime();
ArBillExport export = exportMapper.selectById(exportId);
if (export == null || !ArExportStatus.SUCCESS.getCode().equals(export.getExportStatus()) || StringUtils.isEmpty(export.getStorageKey())) {
ArBillExport export = selectExport(exportId);
if (!ArExportStatus.SUCCESS.getCode().equals(export.getExportStatus()) || StringUtils.isEmpty(export.getStorageKey())) {
throw new ServiceException("导出文件不存在或尚未生成成功");
}
String resource = export.getStorageKey();
@@ -1197,6 +1212,18 @@ public class ArBillServiceImpl implements IArBillService {
return logs;
}
private ArBill requireBillDataScope(Long arBillId) {
ArBillQuery query = new ArBillQuery();
query.setArBillId(arBillId);
return dataScopeService.requireBill(query);
}
private void requireCustomerDataScope(Long customerId) {
ArBillQuery query = new ArBillQuery();
query.setCustomerId(customerId);
dataScopeService.requireCustomer(query);
}
private String exportFailureMessage(Exception e) {
String message = StringUtils.isEmpty(e.getMessage()) ? "账单导出失败" : e.getMessage();
return message.length() > 500 ? message.substring(0, 500) : message;
@@ -66,6 +66,12 @@
<include refid="columns"/>
where create_by = #{createBy}
and status in ('WAITING', 'RUNNING', 'CANCEL_REQUESTED')
<if test="params.dataScope != null and params.dataScope != ''">
and exists (
select 1 from ar_bill b where b.ar_bill_id = ar_bill_generate_task.ar_bill_id
<include refid="com.bms.ar.mapper.ArBillMapper.customerDataScope"><property name="customerIdColumn" value="b.customer_id"/></include>
)
</if>
order by create_time desc, task_id desc
</select>
@@ -26,7 +26,7 @@
<result property="createTime" column="create_time"/>
</resultMap>
<select id="selectList" parameterType="ArBillLog" resultMap="ArBillLogResult">
<select id="selectList" resultMap="ArBillLogResult">
select l.log_id, l.ar_bill_id, l.object_type, l.object_id, l.operation_type, l.operation_desc,
case l.object_type
when 'BILL' then b.bill_no
@@ -43,14 +43,15 @@
left join ar_balance_writeoff bw on bw.writeoff_id = case when l.object_type = 'BALANCE_WRITEOFF' then
cast(l.object_id as unsigned) end
<where>
<if test="arBillId != null">and l.ar_bill_id = #{arBillId}</if>
<if test="objectType != null and objectType != ''">and l.object_type = #{objectType}</if>
<if test="objectId != null and objectId != ''">and l.object_id = #{objectId}</if>
<if test="operationType != null and operationType != ''">and l.operation_type = #{operationType}</if>
<if test="operationSource != null and operationSource != ''">and l.operation_source = #{operationSource}
<if test="query.arBillId != null">and l.ar_bill_id = #{query.arBillId}</if>
<if test="query.objectType != null and query.objectType != ''">and l.object_type = #{query.objectType}</if>
<if test="query.objectId != null and query.objectId != ''">and l.object_id = #{query.objectId}</if>
<if test="query.operationType != null and query.operationType != ''">and l.operation_type = #{query.operationType}</if>
<if test="query.operationSource != null and query.operationSource != ''">and l.operation_source = #{query.operationSource}
</if>
<if test="requestId != null and requestId != ''">and l.request_id = #{requestId}</if>
<if test="result != null and result != ''">and l.result = #{result}</if>
<if test="query.requestId != null and query.requestId != ''">and l.request_id = #{query.requestId}</if>
<if test="query.result != null and query.result != ''">and l.result = #{query.result}</if>
<include refid="com.bms.ar.mapper.ArBillMapper.customerDataScope"><property name="customerIdColumn" value="b.customer_id"/></include>
</where>
order by l.create_time desc, l.log_id desc
</select>
@@ -67,6 +67,27 @@
from ar_bill
</sql>
<!-- 仅受限角色增加 EXISTS,避免业务员历史关联产生重复账单和错误分页。
sales_user_id 优先按用户ID解析;找不到ID时兼容历史工号,防止数字工号与用户ID同时匹配两个人。
customerIdColumn 仅由下面各个 include 的固定属性传入。 -->
<sql id="customerDataScope">
<if test="params.dataScope != null and params.dataScope != ''">
and exists (
select 1 from ctr_customer ar_scope_customer
left join sys_user ar_scope_id_user
on ar_scope_customer.sales_user_id regexp '^[0-9]+$'
and ar_scope_id_user.user_id = ar_scope_customer.sales_user_id + 0
left join sys_user ar_scope_user
on ar_scope_user.del_flag = '0'
and (ar_scope_user.user_id = ar_scope_id_user.user_id
or (ar_scope_id_user.user_id is null
and ar_scope_user.user_name = ar_scope_customer.sales_user_id))
where ar_scope_customer.customer_id = ${customerIdColumn}
${params.dataScope}
)
</if>
</sql>
<select id="selectArBillList" parameterType="com.bms.ar.domain.vo.ArBillQuery" resultMap="ArBillResult">
<include refid="selectArBillColumns"/>
<where>
@@ -85,10 +106,23 @@
<if test="billDateStart != null">and bill_date &gt;= #{billDateStart}</if>
<if test="billDateEnd != null">and bill_date &lt;= #{billDateEnd}</if>
<if test="billPeriodEnd != null">and bill_period_end = #{billPeriodEnd}</if>
<include refid="customerDataScope"><property name="customerIdColumn" value="ar_bill.customer_id"/></include>
</where>
order by create_time desc, ar_bill_id desc
</select>
<select id="selectArBillByDataScope" parameterType="com.bms.ar.domain.vo.ArBillQuery" resultMap="ArBillResult">
<include refid="selectArBillColumns"/>
where ar_bill_id = #{arBillId}
<include refid="customerDataScope"><property name="customerIdColumn" value="ar_bill.customer_id"/></include>
</select>
<select id="selectCustomerIdByDataScope" parameterType="com.bms.ar.domain.vo.ArBillQuery" resultType="long">
select c.customer_id from ctr_customer c where c.customer_id = #{customerId}
<include refid="customerDataScope"><property name="customerIdColumn" value="c.customer_id"/></include>
</select>
<!-- 后台生成/导出任务使用原始查询,前台入口通过 selectArBillByDataScope 校验。 -->
<select id="selectArBillById" parameterType="long" resultMap="ArBillResult">
<include refid="selectArBillColumns"/>
where ar_bill_id = #{arBillId}