feat(credit): 添加被执行人企业关联匹配功能
- 在 CreditJudgmentDebtorController 中新增 refreshCompanyIdByCompanyName 接口 - 实现根据企业名称自动匹配并更新 companyId 的批量处理逻辑 - 支持按租户维度进行企业名称匹配,避免跨租户误匹配 - 添加企业名称标准化处理和模糊匹配机制 - 实现批量更新和事务管理,提升处理效率 - 优化关键词搜索条件,精确匹配企业名称而非模糊匹配 - 添加公司名称规范化方法 normalizeCompanyName - 修复竞品表字段别名从 mainCompanyName 改为 companyName feat(shop): 优化分销商推荐关系绑定机制 - 修改 ShopDealerRefereeController 的 save 方法为幂等绑定 - 新增 bindFirstLevel 方法实现一级推荐关系的幂等绑定 - 添加用户身份验证和安全校验机制 - 增加 source 和 scene 字段支持来源追踪 - 实现重复绑定防护和业务异常处理 - 添加经销商有效性校验机制
This commit is contained in:
@@ -6,9 +6,11 @@ import com.gxwebsoft.common.core.web.BaseController;
|
|||||||
import com.gxwebsoft.common.core.web.BatchParam;
|
import com.gxwebsoft.common.core.web.BatchParam;
|
||||||
import com.gxwebsoft.common.core.web.PageResult;
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
import com.gxwebsoft.common.system.entity.User;
|
import com.gxwebsoft.common.system.entity.User;
|
||||||
|
import com.gxwebsoft.credit.entity.CreditCompany;
|
||||||
import com.gxwebsoft.credit.entity.CreditJudgmentDebtor;
|
import com.gxwebsoft.credit.entity.CreditJudgmentDebtor;
|
||||||
import com.gxwebsoft.credit.param.CreditJudgmentDebtorImportParam;
|
import com.gxwebsoft.credit.param.CreditJudgmentDebtorImportParam;
|
||||||
import com.gxwebsoft.credit.param.CreditJudgmentDebtorParam;
|
import com.gxwebsoft.credit.param.CreditJudgmentDebtorParam;
|
||||||
|
import com.gxwebsoft.credit.service.CreditCompanyService;
|
||||||
import com.gxwebsoft.credit.service.CreditJudgmentDebtorService;
|
import com.gxwebsoft.credit.service.CreditJudgmentDebtorService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -51,6 +53,9 @@ public class CreditJudgmentDebtorController extends BaseController {
|
|||||||
@Resource
|
@Resource
|
||||||
private BatchImportSupport batchImportSupport;
|
private BatchImportSupport batchImportSupport;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CreditCompanyService creditCompanyService;
|
||||||
|
|
||||||
@Operation(summary = "分页查询被执行人")
|
@Operation(summary = "分页查询被执行人")
|
||||||
@GetMapping("/page")
|
@GetMapping("/page")
|
||||||
public ApiResult<PageResult<CreditJudgmentDebtor>> page(CreditJudgmentDebtorParam param) {
|
public ApiResult<PageResult<CreditJudgmentDebtor>> page(CreditJudgmentDebtorParam param) {
|
||||||
@@ -143,6 +148,171 @@ public class CreditJudgmentDebtorController extends BaseController {
|
|||||||
return fail("删除失败");
|
return fail("删除失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据企业名称匹配企业并更新 companyId(匹配 CreditCompany.name / CreditCompany.matchName)
|
||||||
|
*
|
||||||
|
* <p>默认仅更新 companyId 为空的记录;如需覆盖更新,传 onlyNull=false。</p>
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasAuthority('credit:creditJudgmentDebtor:update')")
|
||||||
|
@OperationLog
|
||||||
|
@Operation(summary = "根据企业名称匹配并更新companyId")
|
||||||
|
@PostMapping("/company-id/refresh")
|
||||||
|
public ApiResult<Map<String, Object>> refreshCompanyIdByCompanyName(
|
||||||
|
@RequestParam(value = "onlyNull", required = false, defaultValue = "true") Boolean onlyNull,
|
||||||
|
@RequestParam(value = "limit", required = false) Integer limit
|
||||||
|
) {
|
||||||
|
User loginUser = getLoginUser();
|
||||||
|
Integer currentTenantId = loginUser != null ? loginUser.getTenantId() : null;
|
||||||
|
|
||||||
|
// 1) 读取待处理数据(仅取必要字段,避免一次性拉全表字段)
|
||||||
|
var debtorQuery = creditJudgmentDebtorService.lambdaQuery()
|
||||||
|
.select(CreditJudgmentDebtor::getId, CreditJudgmentDebtor::getName, CreditJudgmentDebtor::getCompanyId, CreditJudgmentDebtor::getTenantId)
|
||||||
|
.eq(currentTenantId != null, CreditJudgmentDebtor::getTenantId, currentTenantId)
|
||||||
|
.isNotNull(CreditJudgmentDebtor::getName);
|
||||||
|
if (Boolean.TRUE.equals(onlyNull)) {
|
||||||
|
debtorQuery.isNull(CreditJudgmentDebtor::getCompanyId);
|
||||||
|
}
|
||||||
|
if (limit != null && limit > 0) {
|
||||||
|
debtorQuery.last("limit " + Math.min(limit, 200000));
|
||||||
|
}
|
||||||
|
List<CreditJudgmentDebtor> debtors = debtorQuery.list();
|
||||||
|
|
||||||
|
if (CollectionUtils.isEmpty(debtors)) {
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("updated", 0);
|
||||||
|
result.put("matched", 0);
|
||||||
|
result.put("notFound", 0);
|
||||||
|
result.put("ambiguous", 0);
|
||||||
|
return success("无可更新数据", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 按租户维度匹配(避免管理员/跨租户场景误匹配)
|
||||||
|
Map<Integer, List<CreditJudgmentDebtor>> debtorsByTenant = new LinkedHashMap<>();
|
||||||
|
for (CreditJudgmentDebtor d : debtors) {
|
||||||
|
if (d == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Integer tenantId = currentTenantId != null ? currentTenantId : d.getTenantId();
|
||||||
|
if (tenantId == null) {
|
||||||
|
// 未知租户下不做跨租户匹配,避免误更新
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
debtorsByTenant.computeIfAbsent(tenantId, k -> new ArrayList<>()).add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 批量更新 companyId
|
||||||
|
int updated = 0;
|
||||||
|
int matched = 0;
|
||||||
|
int notFound = 0;
|
||||||
|
int ambiguous = 0;
|
||||||
|
final int batchSize = 500;
|
||||||
|
List<CreditJudgmentDebtor> updates = new ArrayList<>(batchSize);
|
||||||
|
|
||||||
|
final int inChunkSize = 900;
|
||||||
|
for (Map.Entry<Integer, List<CreditJudgmentDebtor>> entry : debtorsByTenant.entrySet()) {
|
||||||
|
Integer tenantId = entry.getKey();
|
||||||
|
List<CreditJudgmentDebtor> tenantDebtors = entry.getValue();
|
||||||
|
if (tenantId == null || CollectionUtils.isEmpty(tenantDebtors)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.1) 查询当前租户下的 companyId 映射
|
||||||
|
LinkedHashMap<String, Integer> companyIdByName = new LinkedHashMap<>();
|
||||||
|
LinkedHashMap<String, Integer> ambiguousByName = new LinkedHashMap<>();
|
||||||
|
java.util.LinkedHashSet<String> nameSet = new java.util.LinkedHashSet<>();
|
||||||
|
for (CreditJudgmentDebtor d : tenantDebtors) {
|
||||||
|
String name = normalizeCompanyName(d != null ? d.getName() : null);
|
||||||
|
if (name != null) {
|
||||||
|
nameSet.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> allNames = new ArrayList<>(nameSet);
|
||||||
|
for (int i = 0; i < allNames.size(); i += inChunkSize) {
|
||||||
|
List<String> chunk = allNames.subList(i, Math.min(allNames.size(), i + inChunkSize));
|
||||||
|
if (CollectionUtils.isEmpty(chunk)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
List<CreditCompany> companies = creditCompanyService.lambdaQuery()
|
||||||
|
.select(CreditCompany::getId, CreditCompany::getName, CreditCompany::getMatchName, CreditCompany::getTenantId)
|
||||||
|
.eq(CreditCompany::getTenantId, tenantId)
|
||||||
|
.and(w -> w.in(CreditCompany::getName, chunk).or().in(CreditCompany::getMatchName, chunk))
|
||||||
|
.list();
|
||||||
|
|
||||||
|
for (CreditCompany c : companies) {
|
||||||
|
if (c == null || c.getId() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addCompanyNameMapping(companyIdByName, ambiguousByName, normalizeCompanyName(c.getName()), c.getId());
|
||||||
|
addCompanyNameMapping(companyIdByName, ambiguousByName, normalizeCompanyName(c.getMatchName()), c.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.2) 更新当前租户下的被执行人 companyId
|
||||||
|
for (CreditJudgmentDebtor d : tenantDebtors) {
|
||||||
|
String key = normalizeCompanyName(d != null ? d.getName() : null);
|
||||||
|
if (key == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer amb = ambiguousByName.get(key);
|
||||||
|
if (amb != null && amb > 0) {
|
||||||
|
ambiguous++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer companyId = companyIdByName.get(key);
|
||||||
|
if (companyId == null) {
|
||||||
|
notFound++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
matched++;
|
||||||
|
|
||||||
|
boolean needUpdate = d.getCompanyId() == null || !companyId.equals(d.getCompanyId());
|
||||||
|
if (Boolean.TRUE.equals(onlyNull)) {
|
||||||
|
needUpdate = d.getCompanyId() == null;
|
||||||
|
}
|
||||||
|
if (!needUpdate) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreditJudgmentDebtor patch = new CreditJudgmentDebtor();
|
||||||
|
patch.setId(d.getId());
|
||||||
|
patch.setCompanyId(companyId);
|
||||||
|
updates.add(patch);
|
||||||
|
if (updates.size() >= batchSize) {
|
||||||
|
updated += batchImportSupport.runInNewTx(() -> {
|
||||||
|
boolean ok = creditJudgmentDebtorService.updateBatchById(updates, batchSize);
|
||||||
|
return ok ? updates.size() : 0;
|
||||||
|
});
|
||||||
|
updates.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// currentTenantId 为空时,租户缺失的数据不做匹配更新,避免误更新
|
||||||
|
if (currentTenantId == null) {
|
||||||
|
for (CreditJudgmentDebtor d : debtors) {
|
||||||
|
if (d != null && d.getTenantId() == null) {
|
||||||
|
notFound++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updates.isEmpty()) {
|
||||||
|
updated += batchImportSupport.runInNewTx(() -> {
|
||||||
|
boolean ok = creditJudgmentDebtorService.updateBatchById(updates, batchSize);
|
||||||
|
return ok ? updates.size() : 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("updated", updated);
|
||||||
|
result.put("matched", matched);
|
||||||
|
result.put("notFound", notFound);
|
||||||
|
result.put("ambiguous", ambiguous);
|
||||||
|
return success("更新完成,更新" + updated + "条", result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量导入被执行人
|
* 批量导入被执行人
|
||||||
*/
|
*/
|
||||||
@@ -244,10 +414,32 @@ public class CreditJudgmentDebtorController extends BaseController {
|
|||||||
if (isImportHeaderRow(param)) {
|
if (isImportHeaderRow(param)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return ImportHelper.isBlank(param.getCaseNumber())
|
return ImportHelper.isBlank(param.getCaseNumber());
|
||||||
&& ImportHelper.isBlank(param.getName())
|
}
|
||||||
&& ImportHelper.isBlank(param.getName1())
|
|
||||||
&& ImportHelper.isBlank(param.getCode());
|
private static String normalizeCompanyName(String name) {
|
||||||
|
if (name == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String v = name.replace(' ', ' ').trim();
|
||||||
|
return v.isEmpty() ? null : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addCompanyNameMapping(Map<String, Integer> idByName,
|
||||||
|
Map<String, Integer> ambiguousByName,
|
||||||
|
String key,
|
||||||
|
Integer companyId) {
|
||||||
|
if (key == null || companyId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Integer existing = idByName.get(key);
|
||||||
|
if (existing == null) {
|
||||||
|
idByName.put(key, companyId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!existing.equals(companyId)) {
|
||||||
|
ambiguousByName.put(key, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isImportHeaderRow(CreditJudgmentDebtorImportParam param) {
|
private boolean isImportHeaderRow(CreditJudgmentDebtorImportParam param) {
|
||||||
|
|||||||
@@ -146,8 +146,10 @@ public class CreditJudicialDocumentController extends BaseController {
|
|||||||
int successCount = 0;
|
int successCount = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 支持按选项卡名称导入:默认读取“裁判文书”sheet(不存在则回退到第 0 个sheet)
|
||||||
|
int sheetIndex = ExcelImportSupport.findSheetIndex(file, "裁判文书", 0);
|
||||||
ExcelImportSupport.ImportResult<CreditJudicialDocumentImportParam> importResult = ExcelImportSupport.read(
|
ExcelImportSupport.ImportResult<CreditJudicialDocumentImportParam> importResult = ExcelImportSupport.read(
|
||||||
file, CreditJudicialDocumentImportParam.class, this::isEmptyImportRow);
|
file, CreditJudicialDocumentImportParam.class, this::isEmptyImportRow, sheetIndex);
|
||||||
List<CreditJudicialDocumentImportParam> list = importResult.getData();
|
List<CreditJudicialDocumentImportParam> list = importResult.getData();
|
||||||
int usedTitleRows = importResult.getTitleRows();
|
int usedTitleRows = importResult.getTitleRows();
|
||||||
int usedHeadRows = importResult.getHeadRows();
|
int usedHeadRows = importResult.getHeadRows();
|
||||||
@@ -544,8 +546,7 @@ public class CreditJudicialDocumentController extends BaseController {
|
|||||||
if (param == null) {
|
if (param == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return ImportHelper.isBlank(param.getCaseNumber())
|
return ImportHelper.isBlank(param.getCaseNumber());
|
||||||
&& ImportHelper.isBlank(param.getCauseOfAction());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CreditJudicialDocument convertImportParamToEntity(CreditJudicialDocumentImportParam param) {
|
private CreditJudicialDocument convertImportParamToEntity(CreditJudicialDocumentImportParam param) {
|
||||||
|
|||||||
@@ -228,10 +228,10 @@ public class CreditJudiciaryController extends BaseController {
|
|||||||
}
|
}
|
||||||
int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows;
|
int excelRowNumber = i + 1 + usedTitleRows + usedHeadRows;
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
if (item.getName() == null || item.getName().trim().isEmpty()) {
|
// if (item.getName() == null || item.getName().trim().isEmpty()) {
|
||||||
errorMessages.add("第" + excelRowNumber + "行:项目名称不能为空");
|
// errorMessages.add("第" + excelRowNumber + "行:项目名称不能为空");
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
if (item.getCode() == null || item.getCode().trim().isEmpty()) {
|
if (item.getCode() == null || item.getCode().trim().isEmpty()) {
|
||||||
errorMessages.add("第" + excelRowNumber + "行:唯一标识不能为空");
|
errorMessages.add("第" + excelRowNumber + "行:唯一标识不能为空");
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<!-- 关联查询sql -->
|
<!-- 关联查询sql -->
|
||||||
<sql id="selectSql">
|
<sql id="selectSql">
|
||||||
SELECT a.*, b.name AS mainCompanyName, u.real_name AS realName
|
SELECT a.*, b.name AS companyName, u.real_name AS realName
|
||||||
FROM credit_competitor a
|
FROM credit_competitor a
|
||||||
LEFT JOIN credit_company b ON a.company_id = b.id
|
LEFT JOIN credit_company b ON a.company_id = b.id
|
||||||
LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id
|
LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<if test="param.keywords != null">
|
<if test="param.keywords != null">
|
||||||
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
|
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
OR a.case_number = #{param.keywords}
|
OR a.case_number = #{param.keywords}
|
||||||
OR b.name LIKE CONCAT('%', #{param.keywords}, '%')
|
OR b.name = #{param.keywords}
|
||||||
)
|
)
|
||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
<if test="param.keywords != null">
|
<if test="param.keywords != null">
|
||||||
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
|
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
|
||||||
OR b.name = #{param.keywords}
|
OR b.name = #{param.keywords}
|
||||||
|
OR a.main_bodyName = #{param.keywords}
|
||||||
)
|
)
|
||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package com.gxwebsoft.shop.controller;
|
package com.gxwebsoft.shop.controller;
|
||||||
|
|
||||||
import com.gxwebsoft.common.core.web.BaseController;
|
import com.gxwebsoft.common.core.web.BaseController;
|
||||||
import com.gxwebsoft.shop.entity.ShopUserReferee;
|
import com.gxwebsoft.common.core.Constants;
|
||||||
|
import com.gxwebsoft.common.core.exception.BusinessException;
|
||||||
import com.gxwebsoft.shop.service.ShopDealerRefereeService;
|
import com.gxwebsoft.shop.service.ShopDealerRefereeService;
|
||||||
import com.gxwebsoft.shop.entity.ShopDealerReferee;
|
import com.gxwebsoft.shop.entity.ShopDealerReferee;
|
||||||
import com.gxwebsoft.shop.param.ShopDealerRefereeParam;
|
import com.gxwebsoft.shop.param.ShopDealerRefereeParam;
|
||||||
@@ -63,20 +64,33 @@ public class ShopDealerRefereeController extends BaseController {
|
|||||||
return success(shopDealerRefereeService.getByUserIdRel(userId));
|
return success(shopDealerRefereeService.getByUserIdRel(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasAuthority('shop:shopDealerReferee:save')")
|
|
||||||
@OperationLog
|
@OperationLog
|
||||||
@Operation(summary = "添加分销商推荐关系表")
|
@Operation(summary = "绑定分销商推荐关系(一级,幂等)")
|
||||||
@PostMapping()
|
@PostMapping()
|
||||||
public ApiResult<?> save(@RequestBody ShopDealerReferee shopDealerReferee) {
|
public ApiResult<?> save(@RequestBody ShopDealerReferee shopDealerReferee) {
|
||||||
// 记录当前登录用户id
|
|
||||||
User loginUser = getLoginUser();
|
User loginUser = getLoginUser();
|
||||||
if (loginUser != null) {
|
if (loginUser == null) {
|
||||||
shopDealerReferee.setUserId(loginUser.getUserId());
|
throw new BusinessException(Constants.UNAUTHENTICATED_CODE, Constants.UNAUTHENTICATED_MSG);
|
||||||
}
|
}
|
||||||
if (shopDealerRefereeService.save(shopDealerReferee)) {
|
|
||||||
return success("添加成功");
|
// 安全:仅信任token中的userId;若body.userId存在且不一致则拒绝
|
||||||
|
if (shopDealerReferee.getUserId() != null && !shopDealerReferee.getUserId().equals(loginUser.getUserId())) {
|
||||||
|
throw new BusinessException("非法userId参数");
|
||||||
}
|
}
|
||||||
return fail("添加失败");
|
|
||||||
|
Integer tenantId = getTenantId();
|
||||||
|
if (tenantId == null) {
|
||||||
|
throw new BusinessException("TenantId不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean created = shopDealerRefereeService.bindFirstLevel(
|
||||||
|
shopDealerReferee.getDealerId(),
|
||||||
|
loginUser.getUserId(),
|
||||||
|
tenantId,
|
||||||
|
shopDealerReferee.getSource(),
|
||||||
|
shopDealerReferee.getScene()
|
||||||
|
);
|
||||||
|
return success(created ? "绑定成功" : "已绑定");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasAuthority('shop:shopDealerReferee:update')")
|
@PreAuthorize("hasAuthority('shop:shopDealerReferee:update')")
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ public class ShopDealerReferee implements Serializable {
|
|||||||
@Schema(description = "推荐关系层级(1,2,3)")
|
@Schema(description = "推荐关系层级(1,2,3)")
|
||||||
private Integer level;
|
private Integer level;
|
||||||
|
|
||||||
|
@Schema(description = "来源(如 goods_share)")
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
@Schema(description = "场景参数(用于溯源统计,如 inviter=<inviter>&source=goods_share&t=<t>)")
|
||||||
|
private String scene;
|
||||||
|
|
||||||
@Schema(description = "商城ID")
|
@Schema(description = "商城ID")
|
||||||
private Integer tenantId;
|
private Integer tenantId;
|
||||||
|
|
||||||
|
|||||||
@@ -40,4 +40,17 @@ public interface ShopDealerRefereeService extends IService<ShopDealerReferee> {
|
|||||||
ShopDealerReferee getByIdRel(Integer id);
|
ShopDealerReferee getByIdRel(Integer id);
|
||||||
|
|
||||||
ShopDealerReferee getByUserIdRel(Integer userId);
|
ShopDealerReferee getByUserIdRel(Integer userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定分销商推荐关系(一级)。
|
||||||
|
* <p>
|
||||||
|
* 规则:
|
||||||
|
* <ul>
|
||||||
|
* <li>仅首次绑定生效:已存在(同一tenant、同一user、level=1)则不改绑</li>
|
||||||
|
* <li>建议配合唯一索引保证并发幂等:UNIQUE(tenant_id, user_id, level)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return true=本次新绑定写入成功;false=已绑定(幂等)
|
||||||
|
*/
|
||||||
|
boolean bindFirstLevel(Integer dealerId, Integer userId, Integer tenantId, String source, String scene);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
package com.gxwebsoft.shop.service.impl;
|
package com.gxwebsoft.shop.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.gxwebsoft.common.core.exception.BusinessException;
|
||||||
import com.gxwebsoft.shop.mapper.ShopDealerRefereeMapper;
|
import com.gxwebsoft.shop.mapper.ShopDealerRefereeMapper;
|
||||||
|
import com.gxwebsoft.shop.entity.ShopDealerUser;
|
||||||
|
import com.gxwebsoft.shop.service.ShopDealerUserService;
|
||||||
import com.gxwebsoft.shop.service.ShopDealerRefereeService;
|
import com.gxwebsoft.shop.service.ShopDealerRefereeService;
|
||||||
import com.gxwebsoft.shop.entity.ShopDealerReferee;
|
import com.gxwebsoft.shop.entity.ShopDealerReferee;
|
||||||
import com.gxwebsoft.shop.param.ShopDealerRefereeParam;
|
import com.gxwebsoft.shop.param.ShopDealerRefereeParam;
|
||||||
import com.gxwebsoft.common.core.web.PageParam;
|
import com.gxwebsoft.common.core.web.PageParam;
|
||||||
import com.gxwebsoft.common.core.web.PageResult;
|
import com.gxwebsoft.common.core.web.PageResult;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +29,9 @@ import java.util.List;
|
|||||||
@Service
|
@Service
|
||||||
public class ShopDealerRefereeServiceImpl extends ServiceImpl<ShopDealerRefereeMapper, ShopDealerReferee> implements ShopDealerRefereeService {
|
public class ShopDealerRefereeServiceImpl extends ServiceImpl<ShopDealerRefereeMapper, ShopDealerReferee> implements ShopDealerRefereeService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ShopDealerUserService shopDealerUserService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResult<ShopDealerReferee> pageRel(ShopDealerRefereeParam param) {
|
public PageResult<ShopDealerReferee> pageRel(ShopDealerRefereeParam param) {
|
||||||
PageParam<ShopDealerReferee, ShopDealerRefereeParam> page = new PageParam<>(param);
|
PageParam<ShopDealerReferee, ShopDealerRefereeParam> page = new PageParam<>(param);
|
||||||
@@ -52,4 +64,61 @@ public class ShopDealerRefereeServiceImpl extends ServiceImpl<ShopDealerRefereeM
|
|||||||
return param.getOne(baseMapper.selectListRel(param));
|
return param.getOne(baseMapper.selectListRel(param));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean bindFirstLevel(Integer dealerId, Integer userId, Integer tenantId, String source, String scene) {
|
||||||
|
if (dealerId == null || dealerId <= 0) {
|
||||||
|
throw new BusinessException("dealerId不能为空");
|
||||||
|
}
|
||||||
|
if (userId == null || userId <= 0) {
|
||||||
|
throw new BusinessException("userId不能为空");
|
||||||
|
}
|
||||||
|
if (tenantId == null || tenantId <= 0) {
|
||||||
|
throw new BusinessException("TenantId不能为空");
|
||||||
|
}
|
||||||
|
if (dealerId.equals(userId)) {
|
||||||
|
throw new BusinessException("禁止绑定自己");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已绑定则直接返回(幂等,不改绑)
|
||||||
|
ShopDealerReferee existed = getOne(new LambdaQueryWrapper<ShopDealerReferee>()
|
||||||
|
.eq(ShopDealerReferee::getTenantId, tenantId)
|
||||||
|
.eq(ShopDealerReferee::getUserId, userId)
|
||||||
|
.eq(ShopDealerReferee::getLevel, 1)
|
||||||
|
.last("limit 1"));
|
||||||
|
if (existed != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验邀请人(分销商)存在且有效
|
||||||
|
ShopDealerUser dealerUser = shopDealerUserService.getOne(new LambdaQueryWrapper<ShopDealerUser>()
|
||||||
|
.eq(ShopDealerUser::getTenantId, tenantId)
|
||||||
|
.eq(ShopDealerUser::getUserId, dealerId)
|
||||||
|
.eq(ShopDealerUser::getIsDelete, 0)
|
||||||
|
.last("limit 1"));
|
||||||
|
if (dealerUser == null) {
|
||||||
|
throw new BusinessException("邀请人不存在或已失效");
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
ShopDealerReferee entity = new ShopDealerReferee();
|
||||||
|
entity.setDealerId(dealerId);
|
||||||
|
entity.setUserId(userId);
|
||||||
|
entity.setLevel(1);
|
||||||
|
entity.setTenantId(tenantId);
|
||||||
|
entity.setSource(source);
|
||||||
|
entity.setScene(scene);
|
||||||
|
entity.setCreateTime(now);
|
||||||
|
entity.setUpdateTime(now);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 并发下依赖唯一索引保证幂等;重复写入视为已绑定
|
||||||
|
return save(entity);
|
||||||
|
} catch (DuplicateKeyException e) {
|
||||||
|
return false;
|
||||||
|
} catch (DataIntegrityViolationException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user