commit
Some checks failed
API接口参数变更检测 / api-param-check (push) Has been cancelled

This commit is contained in:
2026-06-05 16:18:40 +08:00
parent 1ca34c6bb2
commit 3cba3bb74e
4393 changed files with 450030 additions and 103 deletions

View File

@@ -0,0 +1,17 @@
package jnpf.franchisee.consumer;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
/**
* 加盟商消息通道定义
*/
public interface FranchiseeConsumerSource {
/**
* 消费通道
*/
String INPUT = "permission-franchisee-input";
@Input(INPUT)
SubscribableChannel input();
}

View File

@@ -0,0 +1,239 @@
package jnpf.franchisee.consumer;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jnpf.base.UserInfo;
import jnpf.config.ConfigValueUtil;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.exception.LoginException;
import jnpf.franchisee.mapper.FranchiseeMapper;
import jnpf.message.enums.permission.v2.OperationTypeMessageEnums;
import jnpf.message.enums.permission.v2.OrganizeCategoryMessageEnums;
import jnpf.message.model.permission.v2.OrganizeUpdateMessageDTO;
import jnpf.model.franchisee.po.FranchiseeEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* 加盟商门店数量写扩散消费者
*/
@Slf4j
@Component
@EnableBinding(FranchiseeConsumerSource.class)
public class FranchiseeStoreNumConsumer {
private static final String IDEMPOTENT_KEY_PREFIX = "franchisee:storeNum:consume";
private static final String IDEMPOTENT_VALUE = "1";
private static final long IDEMPOTENT_EXPIRE_DAYS = 1L;
@Resource
private FranchiseeMapper franchiseeMapper;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
@Lazy
private ConfigValueUtil configValueUtil;
/**
* 监听组织消息中的门店更新事件,执行加盟商门店数量写扩散。
* <p>
* 幂等与并发控制在上层消费入口处理:使用消息头 ROCKET_KEYS 作为 setnx 键,过期时间 1 天。
* 如果缓存过期后发生重复消费导致数量偏差,会在“查询加盟商门店数量接口”按门店表实时修正。
*
* @param message 消息体
*/
@StreamListener(target = FranchiseeConsumerSource.INPUT, condition = "headers['ROCKET_TAGS'] == 'TAG_ORGANIZE'")
public void receiveStoreFranchiseeChange(Message<String> message) {
String payload = message.getPayload();
if (StrUtil.isBlank(payload)) {
return;
}
MessageHeaders headers = message.getHeaders();
String rocketKeys = headers.get("ROCKET_KEYS", String.class);
if (StrUtil.isBlank(rocketKeys)) {
log.warn("加盟商门店数量写扩散消息缺少ROCKET_KEYS跳过处理。payload={}", payload);
return;
}
String idempotentKey = IDEMPOTENT_KEY_PREFIX + ":" + rocketKeys;
Boolean firstConsume = stringRedisTemplate.opsForValue()
.setIfAbsent(idempotentKey, IDEMPOTENT_VALUE, IDEMPOTENT_EXPIRE_DAYS, TimeUnit.DAYS);
if (!Boolean.TRUE.equals(firstConsume)) {
return;
}
List<OrganizeUpdateMessageDTO> messageList;
try {
messageList = JSONUtil.toList(JSONUtil.parseArray(payload), OrganizeUpdateMessageDTO.class);
} catch (Exception e) {
log.error("加盟商门店数量写扩散消息解析失败payload={}", payload, e);
return;
}
if (CollUtil.isEmpty(messageList)) {
return;
}
for (OrganizeUpdateMessageDTO item : messageList) {
try {
processStoreUpdate(item);
} catch (Exception e) {
log.error("处理加盟商门店数量写扩散消息失败item={}", JSONUtil.toJsonStr(item), e);
}
}
}
/**
* 处理单条门店更新消息
*
* @param item 消息项
*/
private void processStoreUpdate(OrganizeUpdateMessageDTO item) {
if (item == null) {
return;
}
if (!OrganizeCategoryMessageEnums.STORE.equals(item.getOrganizeCategoryEnum())) {
return;
}
log.error("处理加盟商门店数量写扩散消息item={}", JSONUtil.toJsonStr(item));
OperationTypeMessageEnums operationType = item.getOperationTypeEnum();
if (operationType == null) {
return;
}
String tenantId = StrUtil.trim(item.getTenantId());
if (StrUtil.isBlank(tenantId)) {
return;
}
String oldFranchiseeId = StrUtil.trim(item.getOldFranchiseeId());
JSONObject jsonObject = parseJsonEntity(item.getJsonEntity());
String newFranchiseeId = jsonObject.getStr("franchiseeId");
Date lastModifyTime = jsonObject.getDate("lastModifyTime");//使用门店表修改时间,作为事件水位线。
if(Objects.isNull(lastModifyTime)){
log.error("processStoreUpdate lastModifyTime is null.item:{}",JSONUtil.toJsonStr(item));
return;
}
long updateStoreNumTimestamp = lastModifyTime.getTime();
if (OperationTypeMessageEnums.ADD.equals(operationType)) {
if (StrUtil.isBlank(newFranchiseeId)) {
return;
}
switchTenant(tenantId);
updateStoreNum(newFranchiseeId, updateStoreNumTimestamp,1);
return;
}
if (OperationTypeMessageEnums.DELETE.equals(operationType)) {
String deleteFranchiseeId = StrUtil.isNotBlank(oldFranchiseeId) ? oldFranchiseeId : newFranchiseeId;
if (StrUtil.isBlank(deleteFranchiseeId)) {
return;
}
switchTenant(tenantId);
updateStoreNum(deleteFranchiseeId, updateStoreNumTimestamp,-1);
return;
}
if (OperationTypeMessageEnums.UPDATE.equals(operationType)) {
if (StrUtil.equals(oldFranchiseeId, newFranchiseeId)) {
return;
}
switchTenant(tenantId);
if (StrUtil.isNotBlank(newFranchiseeId)) {
updateStoreNum(newFranchiseeId, updateStoreNumTimestamp,1);
}
if (StrUtil.isNotBlank(oldFranchiseeId)) {
updateStoreNum(oldFranchiseeId, updateStoreNumTimestamp,-1);
}
}
}
/**
* 解析消息中的新加盟商ID
*
* @param jsonEntity 门店JSON实体
* @return 新加盟商ID
*/
private String parseNewFranchiseeId(String jsonEntity) {
if (StrUtil.isBlank(jsonEntity) || !JSONUtil.isTypeJSONObject(jsonEntity)) {
return null;
}
try {
JSONObject jsonObject = JSONUtil.parseObj(jsonEntity);
return StrUtil.trim(jsonObject.getStr("franchiseeId"));
} catch (Exception e) {
return null;
}
}
private JSONObject parseJsonEntity(String jsonEntity) {
if (StrUtil.isBlank(jsonEntity) || !JSONUtil.isTypeJSONObject(jsonEntity)) {
return new JSONObject();
}
try {
return JSONUtil.parseObj(jsonEntity);
} catch (Exception e) {
return new JSONObject();
}
}
/**
* 写扩散更新加盟商门店数量
*
* @param franchiseeId 加盟商ID
* @param updateStoreNumTimestamp 更新水位
* @param delta 变更值(+1 或 -1
*/
private void updateStoreNum(String franchiseeId,long updateStoreNumTimestamp, int delta) {
if (StrUtil.isBlank(franchiseeId) || delta == 0) {
return;
}
LambdaUpdateWrapper<FranchiseeEntity> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(FranchiseeEntity::getId, franchiseeId);
updateWrapper.eq(FranchiseeEntity::getEnabledMark, 0);
updateWrapper.lt(FranchiseeEntity::getUpdateStoreNumTimestamp, updateStoreNumTimestamp);//作为事件水位线。
if (delta > 0) {
updateWrapper.setSql("F_StoreNum = IFNULL(F_StoreNum, 0) + " + delta);
} else {
int abs = Math.abs(delta);
updateWrapper.setSql("F_StoreNum = CASE WHEN IFNULL(F_StoreNum, 0) >= "
+ abs + " THEN IFNULL(F_StoreNum, 0) - " + abs + " ELSE 0 END");
}
franchiseeMapper.update(null, updateWrapper);
}
/**
* 切换租户数据源
*
* @param tenantId 租户ID
*/
private void switchTenant(String tenantId) {
// 判断是否为多租户
if (!configValueUtil.isMultiTenancy()) {
log.info("加盟商门店数量写扩散消息,配置为非多租户跳过处理。tenantId={}", tenantId);
return;
}
try {
TenantDataSourceUtil.switchTenant(tenantId);
} catch (Exception e) {
throw new RuntimeException("切换租户失败", e);
}
}
}

View File

@@ -0,0 +1,86 @@
package jnpf.franchisee.controller;
import jnpf.franchisee.FranchiseeApi;
import jnpf.franchisee.service.FranchiseeService;
import jnpf.model.franchisee.vo.FranchiseeIdName;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
import java.util.List;
@Slf4j
@Validated
@RestController
@RequestMapping("/web/franchise-api")
public class FranchiseeApiController implements FranchiseeApi {
@Autowired
private FranchiseeService franchiseeService;
/**
* 获取所有加盟商信息
* @return
*/
@Override
@GetMapping("/getFranchiseeIdNameList")
public List<FranchiseeIdName> getFranchiseeIdNameList() {
return franchiseeService.queryIdListByPrefix(null);
}
/**
* 根据ID列表获取加盟商信息
* @param ids
* @return
*/
@Override
public List<FranchiseeIdName> getFranchiseeIdNameListByIds(Collection<String> ids) {
return franchiseeService.getFranchiseeIdNameListByIds(ids);
}
/**
* 根据ID获取加盟商信息
* @param id
* @return
*/
@Override
public FranchiseeIdName getFranchiseeIdNameListById(String id) {
return franchiseeService.getFranchiseeIdNameListById(id);
}
/**
* 根据名称获取加盟商信息
* @param name
* @return
*/
@Override
public List<FranchiseeIdName> getFranchiseeIdNameListByName(String name) {
return franchiseeService.getFranchiseeIdNameListByName(name);
}
/**
* 根据名称列表获取加盟商信息
* @param names
* @return
*/
@Override
public List<FranchiseeIdName> getFranchiseeIdNameListByNames(Collection<String> names) {
return franchiseeService.getFranchiseeIdNameListByNames(names);
}
@Override
public FranchiseeIdName getFranchiseeIdNameListByCode(String code) {
return franchiseeService.getFranchiseeIdNameListByCode(code);
}
@Override
public List<FranchiseeIdName> getFranchiseeIdNameListByCodes(Collection<String> codes) {
return franchiseeService.getFranchiseeIdNameListByCodes(codes);
}
}

View File

@@ -0,0 +1,151 @@
package jnpf.franchisee.controller.app;
import com.github.pagehelper.PageInfo;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.franchisee.service.FranchiseeService;
import jnpf.model.franchisee.req.FranchiseeAddReq;
import jnpf.model.franchisee.req.FranchiseeQueryReq;
import jnpf.model.franchisee.req.FranchiseeSimpleAddReq;
import jnpf.model.franchisee.req.FranchiseeUpdateReq;
import jnpf.model.franchisee.vo.FranchiseeIdName;
import jnpf.model.franchisee.vo.FranchiseePageVO;
import jnpf.model.franchisee.vo.FranchiseeStoreVO;
import jnpf.model.franchisee.vo.FranchiseeVO;
import jnpf.parameter.service.ParamService;
import jnpf.util.FtbUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* APP加盟商控制器
*/
@RestController
@Validated
@RequestMapping("/app/franchisee")
public class FranchiseeAppController {
private static final String FRANCHISEE_CODE_PREFIX = "JSM";
private static final String FRANCHISEE_CODE_SEQ_KEY = "franchiseeCodeSeq";
private static final DateTimeFormatter FRANCHISEE_CODE_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
@Autowired
private FranchiseeService franchiseeService;
@Autowired
private ParamService paramService;
/**
* 简单新增加盟商
*
* @param req 简单新增请求参数
* @return 操作结果
*/
@PostMapping("/simple-add")
public ActionResult<Void> simpleAdd(@Validated @RequestBody FranchiseeSimpleAddReq req) {
franchiseeService.add(req.convert());
return ActionResult.success();
}
/**
* 自动生成加盟商编码
*
* 规则JSM + yyyyMMdd + 递增序号。
* 递增序号通过固定key在数据库中维护不按天重置。
*
* @return 加盟商编码
*/
@GetMapping("/generate-code")
public ActionResult<String> generateCode() {
return ActionResult.success("success",franchiseeService.generateCode());
}
/**
* 新增加盟商
*
* @param req 新增请求参数
* @return 操作结果
*/
@PostMapping("/add")
public ActionResult<Void> add(@Validated @RequestBody FranchiseeAddReq req) {
franchiseeService.add(req);
return ActionResult.success();
}
/**
* 编辑加盟商
*
* @param req 编辑请求参数
* @return 操作结果
*/
@PutMapping("/update")
public ActionResult<Void> update(@Validated @RequestBody FranchiseeUpdateReq req) {
franchiseeService.update(req);
return ActionResult.success();
}
/**
* 删除加盟商
*
* @param id 主键ID
* @return 操作结果
*/
@DeleteMapping("/delete/{id}")
public ActionResult<Void> delete(@PathVariable("id") @NotBlank(message = "主键ID不能为空") String id) {
franchiseeService.delete(id);
return ActionResult.success();
}
/**
* 查询加盟商详情
*
* @param id 主键ID
* @return 加盟商详情
*/
@GetMapping("/query-info/{id}")
public ActionResult<FranchiseeVO> queryInfo(@PathVariable("id") @NotBlank(message = "主键ID不能为空") String id) {
return ActionResult.success(franchiseeService.queryInfo(id));
}
/**
* 分页查询加盟商列表
*
* @param req 查询参数
* @return 分页结果
*/
@GetMapping("/query-page")
public ActionResult<PageListVO<FranchiseePageVO>> queryPage(@Valid FranchiseeQueryReq req) {
PageInfo<FranchiseePageVO> pageInfo = franchiseeService.queryPage(req,true);
return ActionResult.page(pageInfo.getList(), FtbUtil.getPagination(pageInfo));
}
/**
* 根据关键字查询加盟商ID列表模糊匹配加盟商名称
*
* @param keyword 关键字(加盟商名称)
* @return 加盟商ID列表
*/
@GetMapping("/query-id-list")
public ActionResult<List<FranchiseeIdName>> queryIdListByPrefix(@RequestParam(value = "keyword", required = false) String keyword) {
return ActionResult.success(franchiseeService.queryIdListByPrefix(keyword));
}
/**
* 根据加盟商ID查询其下门店列表
*
* @param franchiseeId 加盟商ID
* @return 门店列表
*/
@GetMapping("/query-store-list/{franchiseeId}")
public ActionResult<List<FranchiseeStoreVO>> queryStoreList(@PathVariable("franchiseeId") @NotBlank(message = "加盟商ID不能为空") String franchiseeId) {
return ActionResult.success(franchiseeService.queryStoreList(franchiseeId));
}
}

View File

@@ -0,0 +1,149 @@
package jnpf.franchisee.controller.web;
import com.github.pagehelper.PageInfo;
import jnpf.base.ActionResult;
import jnpf.base.vo.PageListVO;
import jnpf.franchisee.service.FranchiseeService;
import jnpf.model.franchisee.req.FranchiseeAddReq;
import jnpf.model.franchisee.req.FranchiseeQueryReq;
import jnpf.model.franchisee.req.FranchiseeSimpleAddReq;
import jnpf.model.franchisee.req.FranchiseeUpdateReq;
import jnpf.model.franchisee.vo.FranchiseeIdName;
import jnpf.model.franchisee.vo.FranchiseePageVO;
import jnpf.model.franchisee.vo.FranchiseeStoreVO;
import jnpf.model.franchisee.vo.FranchiseeVO;
import jnpf.util.FtbUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.List;
/**
* Web加盟商控制器
*/
@RestController
@Validated
@RequestMapping("/web/franchisee")
public class FranchiseeController {
@Autowired
private FranchiseeService franchiseeService;
/**
* 简单新增加盟商
*
* @param req 简单新增请求参数
* @return 操作结果
*/
@PostMapping("/simple-add")
public ActionResult<Void> simpleAdd(@Validated @RequestBody FranchiseeSimpleAddReq req) {
franchiseeService.add(req.convert());
return ActionResult.success();
}
/**
* 自动生成加盟商编码
*
* 规则JMS + yyyyMMdd + 递增序号。
* 递增序号通过固定key在数据库中维护不按天重置。
*
* @return 加盟商编码
*/
@GetMapping("/generate-code")
public ActionResult<String> generateCode() {
return ActionResult.success("succeed",franchiseeService.generateCode());
}
/**
* 新增加盟商
*
* @param req 新增请求参数
* @return 操作结果
*/
@PostMapping("/add")
public ActionResult<Void> add(@Validated @RequestBody FranchiseeAddReq req) {
franchiseeService.add(req);
return ActionResult.success();
}
/**
* 编辑加盟商
*
* @param req 编辑请求参数
* @return 操作结果
*/
@PutMapping("/update")
public ActionResult<Void> update(@Validated @RequestBody FranchiseeUpdateReq req) {
franchiseeService.update(req);
return ActionResult.success();
}
/**
* 删除加盟商
*
* @param id 主键ID
* @return 操作结果
*/
@DeleteMapping("/delete/{id}")
public ActionResult<Void> delete(@PathVariable("id") @NotBlank(message = "主键ID不能为空") String id) {
franchiseeService.delete(id);
return ActionResult.success();
}
/**
* 查询加盟商详情
*
* @param id 主键ID
* @return 加盟商详情
*/
@GetMapping("/query-info/{id}")
public ActionResult<FranchiseeVO> queryInfo(@PathVariable("id") @NotBlank(message = "主键ID不能为空") String id) {
return ActionResult.success(franchiseeService.queryInfo(id));
}
/**
* 分页查询加盟商列表
*
* @param req 查询参数
* @return 分页结果
*/
@GetMapping("/query-page")
public ActionResult<PageListVO<FranchiseePageVO>> queryPage(@Valid FranchiseeQueryReq req) {
PageInfo<FranchiseePageVO> pageInfo = franchiseeService.queryPage(req,false);
return ActionResult.page(pageInfo.getList(), FtbUtil.getPagination(pageInfo));
}
/**
* 根据关键字查询加盟商ID列表模糊匹配加盟商名称
*
* @param keyword 关键字(加盟商名称)
* @return 加盟商ID列表
*/
@GetMapping("/query-id-list")
public ActionResult<List<FranchiseeIdName>> queryIdListByPrefix(@RequestParam(value = "keyword", required = false) String keyword) {
return ActionResult.success(franchiseeService.queryIdListByPrefix(keyword));
}
/**
* 根据加盟商ID查询其下门店列表
*
* @param franchiseeId 加盟商ID
* @return 门店列表
*/
@GetMapping("/query-store-list/{franchiseeId}")
public ActionResult<List<FranchiseeStoreVO>> queryStoreList(@PathVariable("franchiseeId") @NotBlank(message = "加盟商ID不能为空") String franchiseeId) {
return ActionResult.success(franchiseeService.queryStoreList(franchiseeId));
}
}

View File

@@ -0,0 +1,114 @@
package jnpf.franchisee.controller.web;
import jnpf.base.ActionResult;
import jnpf.franchisee.service.FranchiseeCustomFieldConfigService;
import jnpf.model.franchisee.req.FranchiseeCustomFieldConfigReq;
import jnpf.model.franchisee.req.FranchiseeCustomNameUpdateReq;
import jnpf.model.franchisee.vo.FranchiseeCustomFieldConfigVO;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import java.util.List;
/**
* web加盟商自定义字段配置控制器
*/
@RestController
@Validated
@RequestMapping("/web/franchisee/custom-field-config")
public class FranchiseeCustomFieldConfigController {
@Autowired
private FranchiseeCustomFieldConfigService franchiseeCustomFieldConfigService;
/**
* 新增自定义字段配置
*
* @param req 请求参数
* @return 配置key
*/
@PostMapping("/add")
public ActionResult<String> add(@Validated @RequestBody FranchiseeCustomFieldConfigReq req) {
return ActionResult.success("succeed",franchiseeCustomFieldConfigService.add(req));
}
/**
* 更新自定义字段配置
*
* @param req 请求参数
* @return 配置key
*/
@PutMapping("/update")
public ActionResult<String> update(@Validated @RequestBody FranchiseeCustomFieldConfigReq req) {
return ActionResult.success("succeed",franchiseeCustomFieldConfigService.update(req));
}
/**
* 查询所有自定义字段配置
*
* @return 配置列表
*/
@GetMapping("/query-list")
public ActionResult<List<FranchiseeCustomFieldConfigVO>> queryList() {
return ActionResult.success(franchiseeCustomFieldConfigService.queryList());
}
/**
* 查询自定义字段配置详情
*
* @return 配置列表
*/
@GetMapping("/query")
public ActionResult<FranchiseeCustomFieldConfigVO> query(@RequestParam("key") String key) {
return ActionResult.success(franchiseeCustomFieldConfigService.query(key));
}
/**
* 查询自定义名称
*
* @return 自定义名称
*/
@GetMapping("/query-custom-name")
public ActionResult<String> queryCustomName() {
return ActionResult.success("succeed",franchiseeCustomFieldConfigService.queryCustomName());
}
/**
* 更新自定义名称
*
* @param req 更新请求参数
* @return 更新后的自定义名称
*/
@PutMapping("/update-custom-name")
public ActionResult<String> updateCustomName(@Validated @RequestBody FranchiseeCustomNameUpdateReq req) {
return ActionResult.success("succeed",franchiseeCustomFieldConfigService.updateCustomName(req.getCustomName()));
}
/**
* 删除自定义字段配置
*
* @param key 配置key
* @return 操作结果
*/
@DeleteMapping("/delete/{key}")
public ActionResult<Void> delete(@PathVariable("key") @NotBlank(message = "key不能为空") String key) {
franchiseeCustomFieldConfigService.delete(key);
return ActionResult.success();
}
/**
* 调整自定义字段排序
*
* @param key 按目标顺序传入的字段key列表
* @return 操作结果
*/
@PutMapping("/adjust-sort")
public ActionResult<Void> adjustSort(@RequestBody List<String> key) {
franchiseeCustomFieldConfigService.adjustSort(key);
return ActionResult.success();
}
}

View File

@@ -0,0 +1,21 @@
package jnpf.franchisee.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.franchisee.po.FranchiseeCustomFieldEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 加盟商自定义字段值Mapper
*/
public interface FranchiseeCustomFieldMapper extends SuperMapper<FranchiseeCustomFieldEntity> {
/**
* 批量插入
*
* @param entities 实体列表
* @return 插入的数量
*/
int batchInsert(@Param("entities") List<FranchiseeCustomFieldEntity> entities);
}

View File

@@ -0,0 +1,21 @@
package jnpf.franchisee.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.franchisee.po.FranchiseeExperienceEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 加盟商从业经历Mapper
*/
public interface FranchiseeExperienceMapper extends SuperMapper<FranchiseeExperienceEntity> {
/**
* 批量插入
*
* @param entities
* @return 插入的数量
*/
int batchInsert(@Param("entities") List<FranchiseeExperienceEntity> entities);
}

View File

@@ -0,0 +1,21 @@
package jnpf.franchisee.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.franchisee.po.FranchiseeJoinRegionEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 加盟商加盟地区冗余表Mapper
*/
public interface FranchiseeJoinRegionMapper extends SuperMapper<FranchiseeJoinRegionEntity> {
/**
* 批量插入
*
* @param entities 实体列表
* @return 影响行数
*/
int batchInsert(@Param("entities") List<FranchiseeJoinRegionEntity> entities);
}

View File

@@ -0,0 +1,29 @@
package jnpf.franchisee.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.model.franchisee.po.FranchiseeEntity;
import jnpf.model.franchisee.req.FranchiseeJoinRegionReq;
import jnpf.model.franchisee.req.FranchiseeQueryReq;
import jnpf.model.franchisee.vo.FranchiseePageVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 加盟商主表 Mapper
*/
public interface FranchiseeMapper extends SuperMapper<FranchiseeEntity> {
/**
* 分页查询加盟商列表
*
* @param req 查询参数
* @param provinceCityJoinRegion 区为空时的省市集合
* @param provinceList 市为空时的省集合
* @return 列表数据
*/
List<FranchiseePageVO> queryPageList(@Param("req") FranchiseeQueryReq req,
@Param("provinceCityJoinRegion") List<FranchiseeJoinRegionReq> provinceCityJoinRegion,
@Param("provinceList") List<String> provinceList);
}

View File

@@ -0,0 +1,11 @@
package jnpf.franchisee.mapper;
import jnpf.base.mapper.SuperMapper;
import jnpf.permission.entity.StoreEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* 加盟商门店Mapper。
*/
public interface FranchiseeStoreMapper extends SuperMapper<StoreEntity> {
}

View File

@@ -0,0 +1,71 @@
package jnpf.franchisee.service;
import jnpf.model.franchisee.req.FranchiseeCustomFieldConfigReq;
import jnpf.model.franchisee.vo.FranchiseeCustomFieldConfigVO;
import java.util.List;
/**
* 加盟商自定义字段配置服务
*/
public interface FranchiseeCustomFieldConfigService {
/**
* 新增自定义字段配置
*
* @param req 请求参数
* @return 配置key
*/
String add(FranchiseeCustomFieldConfigReq req);
/**
* 更新自定义字段配置
*
* @param req 请求参数
* @return 配置key
*/
String update(FranchiseeCustomFieldConfigReq req);
/**
* 查询所有自定义字段配置
*
* @return 配置列表
*/
List<FranchiseeCustomFieldConfigVO> queryList();
/**
* 查询自定义字段配置详情
*
* @return 配置
*/
FranchiseeCustomFieldConfigVO query(String key);
/**
* 删除自定义字段配置
*
* @param key 配置key
*/
void delete(String key);
/**
* 调整自定义字段排序
*
* @param key 按目标顺序传入的字段key列表
*/
void adjustSort(List<String> key);
/**
* 查询自定义名称。
*
* @return 自定义名称
*/
String queryCustomName();
/**
* 更新自定义名称。
*
* @param customName 自定义名称
* @return 更新后的自定义名称
*/
String updateCustomName(String customName);
}

View File

@@ -0,0 +1,149 @@
package jnpf.franchisee.service;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.extension.service.IService;
import com.github.pagehelper.PageInfo;
import jnpf.model.franchisee.po.FranchiseeEntity;
import jnpf.model.franchisee.req.FranchiseeAddReq;
import jnpf.model.franchisee.req.FranchiseeQueryReq;
import jnpf.model.franchisee.req.FranchiseeUpdateReq;
import jnpf.model.franchisee.vo.FranchiseeIdName;
import jnpf.model.franchisee.vo.FranchiseePageVO;
import jnpf.model.franchisee.vo.FranchiseeVO;
import jnpf.model.franchisee.vo.FranchiseeStoreVO;
import jnpf.permission.vo.store.StoreInfoDetailVO;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Collection;
import java.util.List;
/**
* 加盟商服务
*/
public interface FranchiseeService extends IService<FranchiseeEntity> {
/**
* 新增加盟商
*
* @param req 新增请求参数
*/
void add(FranchiseeAddReq req);
/**
* 编辑加盟商
*
* @param req 编辑请求参数
*/
void update(FranchiseeUpdateReq req);
/**
* 删除加盟商
*
* @param id 主键ID
*/
void delete(String id);
/**
* 查询加盟商详情
*
* @param id 主键ID
* @return 加盟商详情
*/
FranchiseeVO queryInfo(String id);
/**
* 分页查询加盟商列表
*
* @param req 查询参数
* @return 分页结果
*/
PageInfo<FranchiseePageVO> queryPage(FranchiseeQueryReq req,boolean includeJoinRegion);
/**
* 根据关键字查询加盟商ID列表模糊匹配加盟商编码/名称/手机号)
*
* @param keyword 关键字(加盟商编码/加盟商名称/手机号)
* @return 加盟商ID,Name列表
*/
List<FranchiseeIdName> queryIdListByPrefix(String keyword);
/**
* 根据加盟商ID查询其下门店列表。
*
* @param franchiseeId 加盟商ID
* @return 门店列表
*/
List<FranchiseeStoreVO> queryStoreList(String franchiseeId);
/**
* 查询加盟商门店数量,并修正加盟商门店数量。返回查询到的门店信息
* @param franchiseeId
* @return
*/
List<StoreInfoDetailVO> queryFranchiseeStoreNumAndUpdate(String franchiseeId);
/**
* 生成加盟商编码
*
* @return 加盟商编码
*/
String generateCode();
/**
* 根据ID列表获取加盟商信息
* @param ids
* @return
*/
List<FranchiseeIdName> getFranchiseeIdNameListByIds(Collection<String> ids);
/**
* 根据ID获取加盟商信息
* @param id
* @return
*/
default FranchiseeIdName getFranchiseeIdNameListById(String id){
List<FranchiseeIdName> franchiseeIdNames = getFranchiseeIdNameListByIds(List.of(id));
if(CollectionUtil.isEmpty(franchiseeIdNames)){
return null;
}
return franchiseeIdNames.get(0);
}
/**
* 根据名称获取加盟商信息
* @param name
* @return
*/
default List<FranchiseeIdName> getFranchiseeIdNameListByName(String name){
return getFranchiseeIdNameListByNames(List.of(name));
}
/**
* 根据名称列表获取加盟商信息
* @param names
* @return
*/
List<FranchiseeIdName> getFranchiseeIdNameListByNames(Collection<String> names);
/**
* 根据编码获取加盟商信息
* @param code
* @return
*/
default FranchiseeIdName getFranchiseeIdNameListByCode(String code){
List<FranchiseeIdName> franchiseeIdNames = getFranchiseeIdNameListByCodes(List.of(code));
if(CollectionUtil.isEmpty(franchiseeIdNames)){
return null;
}
return franchiseeIdNames.get(0);
}
/**
* 根据编码列表获取加盟商信息
* @param codes
* @return
*/
List<FranchiseeIdName> getFranchiseeIdNameListByCodes(Collection<String> codes);
}

View File

@@ -0,0 +1,409 @@
package jnpf.franchisee.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jnpf.franchisee.service.FranchiseeCustomFieldConfigService;
import jnpf.model.franchisee.enums.FranchiseeCustomFieldTypeEnum;
import jnpf.model.franchisee.req.FranchiseeCustomFieldConfigOptionReq;
import jnpf.model.franchisee.req.FranchiseeCustomFieldConfigReq;
import jnpf.model.franchisee.vo.FranchiseeCustomFieldConfigOptionVO;
import jnpf.model.franchisee.vo.FranchiseeCustomFieldConfigVO;
import jnpf.model.warningnotice.po.FtbParamEntity;
import jnpf.storecertificatephoto.mapper.BaseParamMapper;
import jnpf.util.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 加盟商自定义字段配置服务实现
*/
@Service
public class FranchiseeCustomFieldConfigServiceImpl implements FranchiseeCustomFieldConfigService {
/**
* 自定义字段配置类型
*/
private static final String customerFieldType = "customerFieldType";
/**
* 自定义名称配置固定key同时type也固定为该值。
*/
private static final String customerNameType = "franchiseeCustomerNameType";
private static final String lockPrefix = "franchisee:customer:field:";
@Autowired
private BaseParamMapper baseParamMapper;
@Autowired
private RedissonClient redissonClient;
/**
* 新增自定义字段配置
*
* @param req 请求参数
* @return 配置key
*/
@Override
@Transactional(rollbackFor = Exception.class)
public String add(FranchiseeCustomFieldConfigReq req) {
return saveOrUpdate(req);
}
/**
* 更新自定义字段配置
*
* @param req 请求参数
* @return 配置key
*/
@Override
@Transactional(rollbackFor = Exception.class)
public String update(FranchiseeCustomFieldConfigReq req) {
String key = req.getKey();
if(StringUtil.isBlank(key)){
throw new ServiceException("key不能为空");
}
return saveOrUpdate(req);
}
/**
* 查询所有自定义字段配置
*
* @return 配置列表
*/
@Override
public List<FranchiseeCustomFieldConfigVO> queryList() {
LambdaQueryWrapper<FtbParamEntity> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(FtbParamEntity::getType, customerFieldType);
queryWrapper.orderByAsc(FtbParamEntity::getSort);
List<FtbParamEntity> entityList = baseParamMapper.selectList(queryWrapper);
if (CollUtil.isEmpty(entityList)) {
return new ArrayList<>();
}
List<FranchiseeCustomFieldConfigVO> result = new ArrayList<>();
for (FtbParamEntity entity : entityList) {
FranchiseeCustomFieldConfigVO vo = buildFranchiseeCustomerFieldVo(entity);
if (vo == null) {
continue;
}
result.add(vo);
}
return result;
}
@Override
public FranchiseeCustomFieldConfigVO query(String key) {
FtbParamEntity ftbParamEntity = baseParamMapper.selectByKey(key);
if(Objects.isNull(ftbParamEntity)){
throw new ServiceException("找不到该配置!",404);
}
FranchiseeCustomFieldConfigVO franchiseeCustomFieldConfigVO = buildFranchiseeCustomerFieldVo(ftbParamEntity);
if(Objects.isNull(franchiseeCustomFieldConfigVO)){
throw new ServiceException("该配置不正确!",404);
}
return franchiseeCustomFieldConfigVO;
}
private FranchiseeCustomFieldConfigVO buildFranchiseeCustomerFieldVo(FtbParamEntity entity) {
FranchiseeCustomFieldConfigVO vo = JsonUtil.getJsonToBean(entity.getValue(), FranchiseeCustomFieldConfigVO.class);
if (vo == null) {
return null;
}
vo.setKey(entity.getKey());
vo.setFieldCode(entity.getKey());
vo.setSort(entity.getSort());
vo.setFieldName(StrUtil.trim(vo.getFieldName()));
if (vo.getOptionList() == null) {
vo.setOptionList(new ArrayList<>());
}
if(vo.getImageList() == null){
vo.setImageList(new ArrayList<>());
}
return vo;
}
/**
* 删除自定义字段配置
*
* @param key 配置key
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(String key) {
String trimKey = StrUtil.trim(key);
ServiceException.isTrue(StrUtil.isNotBlank(trimKey), "key不能为空");
LambdaQueryWrapper<FtbParamEntity> deleteWrapper = Wrappers.lambdaQuery();
deleteWrapper.eq(FtbParamEntity::getType, customerFieldType);
deleteWrapper.eq(FtbParamEntity::getKey, trimKey);
int deleteCount = baseParamMapper.delete(deleteWrapper);
ServiceException.isTrue(deleteCount > 0, "自定义字段配置不存在");
}
@Override
@Transactional(rollbackFor = Exception.class)
public void adjustSort(List<String> key) {
ServiceException.isTrue(CollUtil.isNotEmpty(key), "key list cannot be empty");
List<String> sortedKeyList = new ArrayList<>();
Set<String> keySet = new HashSet<>();
for (String item : key) {
String trimKey = StrUtil.trim(item);
ServiceException.isTrue(StrUtil.isNotBlank(trimKey), "key cannot be blank");
ServiceException.isTrue(keySet.add(trimKey), "key list contains duplicate key");
sortedKeyList.add(trimKey);
}
LambdaQueryWrapper<FtbParamEntity> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(FtbParamEntity::getType, customerFieldType);
List<FtbParamEntity> entityList = baseParamMapper.selectList(queryWrapper);
if (CollUtil.isEmpty(entityList)) {
return;
}
Map<String, FtbParamEntity> entityMap = entityList.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(item -> StrUtil.trim(item.getKey()), item -> item, (a, b) -> a));
for (String sortKey : sortedKeyList) {
ServiceException.isTrue(entityMap.containsKey(sortKey), "自定义字段配置不存在");
}
List<FtbParamEntity> remainingEntityList = entityList.stream()
.filter(item -> !keySet.contains(StrUtil.trim(item.getKey())))
.sorted((a, b) -> {
Long sortA = a.getSort() == null ? Long.MAX_VALUE : a.getSort();
Long sortB = b.getSort() == null ? Long.MAX_VALUE : b.getSort();
int sortCompare = sortA.compareTo(sortB);
if (sortCompare != 0) {
return sortCompare;
}
return StrUtil.nullToDefault(a.getKey(), "").compareTo(StrUtil.nullToDefault(b.getKey(), ""));
})
.collect(Collectors.toList());
for (FtbParamEntity entity : remainingEntityList) {
sortedKeyList.add(StrUtil.trim(entity.getKey()));
}
long targetSort = 1L;
for (String sortKey : sortedKeyList) {
FtbParamEntity entity = entityMap.get(sortKey);
if (entity == null) {
continue;
}
if (!Objects.equals(entity.getSort(), targetSort)) {
entity.setSort(targetSort);
baseParamMapper.updateByKey(sortKey, entity);
}
targetSort++;
}
}
/**
* 按key新增或更新
*
* 规则:
* 1. key为空新增
* 2. key不为空按key更新。
*
* @param req 请求参数
* @return 配置key
*/
/**
* 查询自定义名称。
*
* 读取 base_param 表中 key=customerNameType 且 type=customerNameType 的记录。
*
* @return 自定义名称;未配置时返回空字符串
*/
@Override
public String queryCustomName() {
FtbParamEntity entity = baseParamMapper.selectByKey(customerNameType);
if (entity == null || !StrUtil.equals(customerNameType, StrUtil.trim(entity.getType()))) {
return "";
}
return StrUtil.blankToDefault(StrUtil.trim(entity.getValue()), "");
}
/**
* 更新自定义名称。
*
* 保存逻辑先按固定key更新若不存在则插入type 固定为 customerNameType。
*
* @param customName 自定义名称
* @return 更新后的自定义名称
*/
@Override
@Transactional(rollbackFor = Exception.class)
public String updateCustomName(String customName) {
String name = StrUtil.trim(customName);
ServiceException.isTrue(StrUtil.isNotBlank(name), "customName cannot be blank");
FtbParamEntity entity = new FtbParamEntity();
entity.setId(FtbUtil.getId());
entity.setKey(customerNameType);
entity.setType(customerNameType);
entity.setValue(name);
entity.setSort(0L);
int updateCount = baseParamMapper.updateByKey(customerNameType, entity);
if (updateCount > 0) {
return name;
}
try {
baseParamMapper.insertParam(entity);
} catch (DuplicateKeyException e) {
int retryCount = baseParamMapper.updateByKey(customerNameType, entity);
ServiceException.isTrue(retryCount > 0, "update customName failed");
}
return name;
}
private String saveOrUpdate(FranchiseeCustomFieldConfigReq req) {
validateReq(req);
String key = StrUtil.trim(req.getKey());
if (StrUtil.isBlank(key)) {
key = FtbUtil.getId();
FtbParamEntity entity = buildEntity(key, req);
entity.setId(FtbUtil.getId());
insertCustomerField(entity);
return key;
}
FtbParamEntity source = baseParamMapper.selectByKey(key);
ServiceException.notNull(source, "自定义字段配置不存在");
ServiceException.isTrue(customerFieldType.equals(source.getType()), "自定义字段配置不存在");
int updateCount = baseParamMapper.updateByKey(key, buildEntity(key, req));
ServiceException.isTrue(updateCount > 0, "更新失败");
return key;
}
private void insertCustomerField(FtbParamEntity entity) {
String lockKey = buildFranchiseeCustomerFieldAddLock();
RLock rLock = redissonClient.getLock(lockKey);
try {
rLock.lock(10, TimeUnit.SECONDS);
Long count = baseParamMapper.selectCount(new LambdaQueryWrapper<FtbParamEntity>()
.eq(FtbParamEntity::getType, customerFieldType));
if(count >= 100){
throw new ServiceException("自定义字段配置已满100");
}
baseParamMapper.insertParam(entity);
}finally {
rLock.unlock();
}
}
/**
* 构建并序列化配置实体
*
* @param key 配置key
* @param req 请求参数
* @return BaseParam实体
*/
private FtbParamEntity buildEntity(String key, FranchiseeCustomFieldConfigReq req) {
FranchiseeCustomFieldConfigValueModel saveData = buildSaveData(key, req);
FtbParamEntity entity = new FtbParamEntity();
entity.setKey(key);
entity.setType(customerFieldType);
entity.setSort(req.getSort() == null ? 0L : req.getSort());
entity.setValue(JsonUtil.getObjectToString(saveData));
return entity;
}
/**
* 构建保存数据对象
*
* @param key 配置key
* @param req 请求参数
* @return 保存对象
*/
private FranchiseeCustomFieldConfigValueModel buildSaveData(String key, FranchiseeCustomFieldConfigReq req) {
FranchiseeCustomFieldConfigValueModel vo = new FranchiseeCustomFieldConfigValueModel();
vo.setFieldCode(key);
vo.setFieldName(StrUtil.trim(req.getFieldName()));
vo.setFieldType(req.getFieldType());
vo.setRequiredMark(req.getRequiredMark() != null && req.getRequiredMark());
vo.setSort(req.getSort() == null ? 0L : req.getSort());
List<FranchiseeCustomFieldConfigOptionVO> optionList = req.getOptionList().stream()
.filter(Objects::nonNull)
.map(item -> {
FranchiseeCustomFieldConfigOptionVO option = new FranchiseeCustomFieldConfigOptionVO();
option.setOptionName(StrUtil.trim(item.getOptionName()));
return option;
})
.filter(item -> StrUtil.isNotBlank(item.getOptionName()))
.collect(Collectors.toList());
vo.setOptionList(optionList);
List<FranchiseeCustomFieldConfigOptionVO> imageList = req.getImageList().stream()
.filter(Objects::nonNull)
.map(item -> {
FranchiseeCustomFieldConfigOptionVO option = new FranchiseeCustomFieldConfigOptionVO();
option.setOptionName(StrUtil.trim(item.getOptionName()));
return option;
})
.filter(item -> StrUtil.isNotBlank(item.getOptionName()))
.collect(Collectors.toList());
vo.setImageList(imageList);
vo.setTextTips(req.getTextTips());
return vo;
}
/**
* 校验请求参数
*
* @param req 请求参数
*/
private void validateReq(FranchiseeCustomFieldConfigReq req) {
ServiceException.notNull(req, "请求参数不能为空");
ServiceException.isTrue(StrUtil.isNotBlank(req.getFieldName()), "字段名称不能为空");
ServiceException.notNull(req.getFieldType(), "字段类型不能为空");
if (req.getOptionList() == null) {
req.setOptionList(new ArrayList<>());
}
if (FranchiseeCustomFieldTypeEnum.MULTI_OPTION.equals(req.getFieldType())) {
ServiceException.isTrue(CollUtil.isNotEmpty(req.getOptionList()), "选项不能为空");
}
for (FranchiseeCustomFieldConfigOptionReq option : req.getOptionList()) {
ServiceException.notNull(option, "选项不能为空");
ServiceException.isTrue(StrUtil.isNotBlank(option.getOptionName()), "选项名称不能为空");
}
}
private String buildFranchiseeCustomerFieldAddLock() {
return lockPrefix+ UserProvider.getUser().getTenantId();
}
/**
* 保存到BaseParamEntity.value的模型不包含key
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
private static class FranchiseeCustomFieldConfigValueModel {
private String fieldCode;
private String fieldName;
private FranchiseeCustomFieldTypeEnum fieldType;
private List<FranchiseeCustomFieldConfigOptionVO> optionList = new ArrayList<>();
private List<FranchiseeCustomFieldConfigOptionVO> imageList = new ArrayList<>();
private Boolean requiredMark;
private Long sort;
private String textTips;
}
}