فهرست منبع

Merge remote-tracking branch 'origin/saas-api' into saas-api

zyp 2 روز پیش
والد
کامیت
ef94c29f85

+ 259 - 0
fs-admin-saas/src/main/java/com/fs/admin/controller/company/SysRedpacketConfigMoreController.java

@@ -0,0 +1,259 @@
+package com.fs.admin.controller.company;
+
+
+import com.fs.common.BeanCopyUtils;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.framework.datasource.TenantDataSourceContextHelper;
+import com.fs.his.domain.SysRedpacketConfigMore;
+import com.fs.his.param.UpdateChangeMchIdParam;
+import com.fs.his.service.ISysRedpacketConfigMoreService;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.service.TenantInfoService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Slf4j
+
+/**
+ * 多商户配置Controller
+ *
+ * @author fs
+ * @date 2025-11-27
+ */
+@RestController
+@RequestMapping("/redPacket/more")
+public class SysRedpacketConfigMoreController extends BaseController
+{
+    @Autowired
+    private ISysRedpacketConfigMoreService sysRedpacketConfigMoreService;
+
+    @Autowired
+    private TenantInfoService tenantInfoService;
+
+    @Autowired
+    private TenantDataSourceContextHelper tenantContextHelper;
+
+    /**
+     * 查询多商户配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysRedpacketConfigMore sysRedpacketConfigMore)
+    {
+        startPage();
+        List<SysRedpacketConfigMore> list = sysRedpacketConfigMoreService.selectSysRedpacketConfigMoreList(sysRedpacketConfigMore);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 获取多商户配置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(sysRedpacketConfigMoreService.selectSysRedpacketConfigMoreById(id));
+    }
+
+    /**
+     * 获取当前发红包的商户号
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:editConfig')")
+    @GetMapping(value = "/getRedPacketConfig")
+    public AjaxResult getRedPacketConFig()
+    {
+        return AjaxResult.success(sysRedpacketConfigMoreService.getRedPacketConFig());
+    }
+
+
+
+
+    /**
+     * 新增多商户配置
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:add')")
+    @Log(title = "多商户配置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SysRedpacketConfigMore sysRedpacketConfigMore)
+    {
+        int result = sysRedpacketConfigMoreService.insertSysRedpacketConfigMore(sysRedpacketConfigMore);
+
+        // 如果指定了租户,同步到租户库
+        if (result > 0 && sysRedpacketConfigMore.getTenantId() != null) {
+            try {
+                syncToTenant(sysRedpacketConfigMore, sysRedpacketConfigMore.getTenantId());
+            } catch (Exception e) {
+                log.error("同步商户配置到租户库失败, tenantId={}, mchId={}",
+                    sysRedpacketConfigMore.getTenantId(), sysRedpacketConfigMore.getMchId(), e);
+                // 不影响主流程,记录日志即可
+            }
+        }
+
+        return toAjax(result);
+    }
+
+    /**
+     * 修改发商户号的商户
+     */
+    @Log(title = "修改发商户号的商户", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateChangeMchId")
+    public R updateChangeMchId(@RequestBody UpdateChangeMchIdParam param)
+    {
+        return sysRedpacketConfigMoreService.updateChangeMchId(param);
+    }
+
+    /**
+     * 清空当前红包商户号配置
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:editConfig')")
+    @Log(title = "清空红包商户号配置", businessType = BusinessType.UPDATE)
+    @PostMapping("/clearRedPacketMchId")
+    public R clearRedPacketMchId()
+    {
+        return sysRedpacketConfigMoreService.clearRedPacketMchId();
+    }
+
+    /**
+     * 修改多商户配置
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:edit')")
+    @Log(title = "多商户配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SysRedpacketConfigMore sysRedpacketConfigMore)
+    {
+        // 查询原数据,获取原租户ID
+        SysRedpacketConfigMore oldConfig = sysRedpacketConfigMoreService.selectSysRedpacketConfigMoreById(sysRedpacketConfigMore.getId());
+        Long oldTenantId = oldConfig != null ? oldConfig.getTenantId() : null;
+        Long newTenantId = sysRedpacketConfigMore.getTenantId();
+        String mchId = sysRedpacketConfigMore.getMchId();
+
+        int result = sysRedpacketConfigMoreService.updateSysRedpacketConfigMore(sysRedpacketConfigMore);
+
+        if (result > 0) {
+            try {
+                // 如果原租户存在且与新租户不同,从原租户库删除
+                if (oldTenantId != null && !oldTenantId.equals(newTenantId)) {
+                    deleteFromTenant(mchId, oldTenantId);
+                }
+                // 如果指定了新租户,同步到新租户库
+                if (newTenantId != null) {
+                    syncToTenant(sysRedpacketConfigMore, newTenantId);
+                }
+            } catch (Exception e) {
+                log.error("同步商户配置到租户库失败, mchId={}, oldTenantId={}, newTenantId={}",
+                    mchId, oldTenantId, newTenantId, e);
+            }
+        }
+
+        return toAjax(result);
+    }
+
+    /**
+     * 删除多商户配置
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:remove')")
+    @Log(title = "多商户配置", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        // 先查询要删除的数据,获取租户信息
+        for (Long id : ids) {
+            SysRedpacketConfigMore config = sysRedpacketConfigMoreService.selectSysRedpacketConfigMoreById(id);
+            if (config != null && config.getTenantId() != null) {
+                try {
+                    deleteFromTenant(config.getMchId(), config.getTenantId());
+                } catch (Exception e) {
+                    log.error("从租户库删除商户配置失败, tenantId={}, mchId={}",
+                        config.getTenantId(), config.getMchId(), e);
+                }
+            }
+        }
+
+        return toAjax(sysRedpacketConfigMoreService.deleteSysRedpacketConfigMoreByIds(ids));
+    }
+
+    /**
+     * 获取租户列表(用于下拉选择)
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:list')")
+    @GetMapping("/tenantList")
+    public AjaxResult tenantList()
+    {
+        TenantInfo query = new TenantInfo();
+        query.setStatus(1); // 只查询启用的租户
+        List<TenantInfo> list = tenantInfoService.selectTenantInfoList(query);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 同步商户配置到租户库
+     */
+    private void syncToTenant(SysRedpacketConfigMore config, Long tenantId) {
+        if (tenantId == null) {
+            return;
+        }
+        tenantContextHelper.runInTenant(tenantId, () -> {
+            try {
+                // 查询租户库是否已存在该商户号的配置
+                SysRedpacketConfigMore existConfig = sysRedpacketConfigMoreService.getOne(
+                    new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<SysRedpacketConfigMore>()
+                        .eq("mch_id", config.getMchId())
+                );
+
+                // 复制一份数据用于租户库
+                SysRedpacketConfigMore tenantConfig = new SysRedpacketConfigMore();
+                BeanCopyUtils.copy(config, tenantConfig);
+
+                if (existConfig != null) {
+                    // 更新现有记录
+                    tenantConfig.setId(existConfig.getId());
+                    sysRedpacketConfigMoreService.updateSysRedpacketConfigMore(tenantConfig);
+                    log.info("同步商户配置到租户库-更新, tenantId={}, mchId={}", tenantId, config.getMchId());
+                } else {
+                    // 插入新记录,清空ID让数据库自增
+                    tenantConfig.setId(null);
+                    sysRedpacketConfigMoreService.insertSysRedpacketConfigMore(tenantConfig);
+                    log.info("同步商户配置到租户库-新增, tenantId={}, mchId={}", tenantId, config.getMchId());
+                }
+            } catch (Exception e) {
+                log.error("同步商户配置到租户库异常, tenantId={}, mchId={}", tenantId, config.getMchId(), e);
+                throw new RuntimeException("同步到租户库失败: " + e.getMessage(), e);
+            }
+        });
+    }
+
+    /**
+     * 从租户库删除商户配置
+     */
+    private void deleteFromTenant(String mchId, Long tenantId) {
+        if (tenantId == null || mchId == null) {
+            return;
+        }
+        tenantContextHelper.runInTenant(tenantId, () -> {
+            try {
+                // 查询租户库中的记录
+                SysRedpacketConfigMore config = sysRedpacketConfigMoreService.getOne(
+                    new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<SysRedpacketConfigMore>()
+                        .eq("mch_id", mchId)
+                );
+                if (config != null) {
+                    sysRedpacketConfigMoreService.deleteSysRedpacketConfigMoreById(config.getId());
+                    log.info("从租户库删除商户配置, tenantId={}, mchId={}", tenantId, mchId);
+                }
+            } catch (Exception e) {
+                log.error("从租户库删除商户配置异常, tenantId={}, mchId={}", tenantId, mchId, e);
+                throw new RuntimeException("从租户库删除失败: " + e.getMessage(), e);
+            }
+        });
+    }
+}

+ 248 - 0
fs-admin/src/main/java/com/fs/admin/controller/company/controller/SysRedpacketConfigMoreController.java

@@ -0,0 +1,248 @@
+package com.fs.admin.controller.company.controller;
+
+
+import com.fs.common.BeanCopyUtils;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.framework.datasource.TenantDataSourceContextHelper;
+import com.fs.his.domain.SysRedpacketConfigMore;
+import com.fs.his.param.UpdateChangeMchIdParam;
+import com.fs.his.service.ISysRedpacketConfigMoreService;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.service.TenantInfoService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Slf4j
+
+/**
+ * 多商户配置Controller
+ *
+ * @author fs
+ * @date 2025-11-27
+ */
+@RestController
+@RequestMapping("/redPacket/more")
+public class SysRedpacketConfigMoreController extends BaseController
+{
+    @Autowired
+    private ISysRedpacketConfigMoreService sysRedpacketConfigMoreService;
+
+    @Autowired
+    private TenantInfoService tenantInfoService;
+
+    @Autowired
+    private TenantDataSourceContextHelper tenantContextHelper;
+
+    /**
+     * 查询多商户配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysRedpacketConfigMore sysRedpacketConfigMore)
+    {
+        startPage();
+        List<SysRedpacketConfigMore> list = sysRedpacketConfigMoreService.selectSysRedpacketConfigMoreList(sysRedpacketConfigMore);
+        return getDataTable(list);
+    }
+
+
+    /**
+     * 获取多商户配置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(sysRedpacketConfigMoreService.selectSysRedpacketConfigMoreById(id));
+    }
+
+    /**
+     * 获取当前发红包的商户号
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:editConfig')")
+    @GetMapping(value = "/getRedPacketConfig")
+    public AjaxResult getRedPacketConFig()
+    {
+        return AjaxResult.success(sysRedpacketConfigMoreService.getRedPacketConFig());
+    }
+
+
+
+
+    /**
+     * 新增多商户配置
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:add')")
+    @Log(title = "多商户配置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody SysRedpacketConfigMore sysRedpacketConfigMore)
+    {
+        int result = sysRedpacketConfigMoreService.insertSysRedpacketConfigMore(sysRedpacketConfigMore);
+        
+        // 如果指定了租户,同步到租户库
+        if (result > 0 && sysRedpacketConfigMore.getTenantId() != null) {
+            try {
+                syncToTenant(sysRedpacketConfigMore, sysRedpacketConfigMore.getTenantId());
+            } catch (Exception e) {
+                log.error("同步商户配置到租户库失败, tenantId={}, mchId={}", 
+                    sysRedpacketConfigMore.getTenantId(), sysRedpacketConfigMore.getMchId(), e);
+                // 不影响主流程,记录日志即可
+            }
+        }
+        
+        return toAjax(result);
+    }
+
+    /**
+     * 修改发商户号的商户
+     */
+    @Log(title = "修改发商户号的商户", businessType = BusinessType.UPDATE)
+    @PostMapping("/updateChangeMchId")
+    public R updateChangeMchId(@RequestBody UpdateChangeMchIdParam param)
+    {
+        return sysRedpacketConfigMoreService.updateChangeMchId(param);
+    }
+
+    /**
+     * 修改多商户配置
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:edit')")
+    @Log(title = "多商户配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody SysRedpacketConfigMore sysRedpacketConfigMore)
+    {
+        // 查询原数据,获取原租户ID
+        SysRedpacketConfigMore oldConfig = sysRedpacketConfigMoreService.selectSysRedpacketConfigMoreById(sysRedpacketConfigMore.getId());
+        Long oldTenantId = oldConfig != null ? oldConfig.getTenantId() : null;
+        Long newTenantId = sysRedpacketConfigMore.getTenantId();
+        String mchId = sysRedpacketConfigMore.getMchId();
+        
+        int result = sysRedpacketConfigMoreService.updateSysRedpacketConfigMore(sysRedpacketConfigMore);
+        
+        if (result > 0) {
+            try {
+                // 如果原租户存在且与新租户不同,从原租户库删除
+                if (oldTenantId != null && !oldTenantId.equals(newTenantId)) {
+                    deleteFromTenant(mchId, oldTenantId);
+                }
+                // 如果指定了新租户,同步到新租户库
+                if (newTenantId != null) {
+                    syncToTenant(sysRedpacketConfigMore, newTenantId);
+                }
+            } catch (Exception e) {
+                log.error("同步商户配置到租户库失败, mchId={}, oldTenantId={}, newTenantId={}", 
+                    mchId, oldTenantId, newTenantId, e);
+            }
+        }
+        
+        return toAjax(result);
+    }
+
+    /**
+     * 删除多商户配置
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:remove')")
+    @Log(title = "多商户配置", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        // 先查询要删除的数据,获取租户信息
+        for (Long id : ids) {
+            SysRedpacketConfigMore config = sysRedpacketConfigMoreService.selectSysRedpacketConfigMoreById(id);
+            if (config != null && config.getTenantId() != null) {
+                try {
+                    deleteFromTenant(config.getMchId(), config.getTenantId());
+                } catch (Exception e) {
+                    log.error("从租户库删除商户配置失败, tenantId={}, mchId={}", 
+                        config.getTenantId(), config.getMchId(), e);
+                }
+            }
+        }
+        
+        return toAjax(sysRedpacketConfigMoreService.deleteSysRedpacketConfigMoreByIds(ids));
+    }
+
+    /**
+     * 获取租户列表(用于下拉选择)
+     */
+    @PreAuthorize("@ss.hasPermi('redPacket:more:list')")
+    @GetMapping("/tenantList")
+    public AjaxResult tenantList()
+    {
+        TenantInfo query = new TenantInfo();
+        query.setStatus(1); // 只查询启用的租户
+        List<TenantInfo> list = tenantInfoService.selectTenantInfoList(query);
+        return AjaxResult.success(list);
+    }
+
+    /**
+     * 同步商户配置到租户库
+     */
+    private void syncToTenant(SysRedpacketConfigMore config, Long tenantId) {
+        if (tenantId == null) {
+            return;
+        }
+        tenantContextHelper.runInTenant(tenantId, () -> {
+            try {
+                // 查询租户库是否已存在该商户号的配置
+                SysRedpacketConfigMore existConfig = sysRedpacketConfigMoreService.getOne(
+                    new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<SysRedpacketConfigMore>()
+                        .eq("mch_id", config.getMchId())
+                );
+
+                // 复制一份数据用于租户库
+                SysRedpacketConfigMore tenantConfig = new SysRedpacketConfigMore();
+                BeanCopyUtils.copy(config, tenantConfig);
+
+                if (existConfig != null) {
+                    // 更新现有记录
+                    tenantConfig.setId(existConfig.getId());
+                    sysRedpacketConfigMoreService.updateSysRedpacketConfigMore(tenantConfig);
+                    log.info("同步商户配置到租户库-更新, tenantId={}, mchId={}", tenantId, config.getMchId());
+                } else {
+                    // 插入新记录,清空ID让数据库自增
+                    tenantConfig.setId(null);
+                    sysRedpacketConfigMoreService.insertSysRedpacketConfigMore(tenantConfig);
+                    log.info("同步商户配置到租户库-新增, tenantId={}, mchId={}", tenantId, config.getMchId());
+                }
+            } catch (Exception e) {
+                log.error("同步商户配置到租户库异常, tenantId={}, mchId={}", tenantId, config.getMchId(), e);
+                throw new RuntimeException("同步到租户库失败: " + e.getMessage(), e);
+            }
+        });
+    }
+
+    /**
+     * 从租户库删除商户配置
+     */
+    private void deleteFromTenant(String mchId, Long tenantId) {
+        if (tenantId == null || mchId == null) {
+            return;
+        }
+        tenantContextHelper.runInTenant(tenantId, () -> {
+            try {
+                // 查询租户库中的记录
+                SysRedpacketConfigMore config = sysRedpacketConfigMoreService.getOne(
+                    new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<SysRedpacketConfigMore>()
+                        .eq("mch_id", mchId)
+                );
+                if (config != null) {
+                    sysRedpacketConfigMoreService.deleteSysRedpacketConfigMoreById(config.getId());
+                    log.info("从租户库删除商户配置, tenantId={}, mchId={}", tenantId, mchId);
+                }
+            } catch (Exception e) {
+                log.error("从租户库删除商户配置异常, tenantId={}, mchId={}", tenantId, mchId, e);
+                throw new RuntimeException("从租户库删除失败: " + e.getMessage(), e);
+            }
+        });
+    }
+}

+ 6 - 6
fs-admin/src/main/java/com/fs/qw/controller/QwCompanyController.java

@@ -58,12 +58,12 @@ public class QwCompanyController extends BaseController
     {
 
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        String json = configService.selectConfigByKey("course.config");
-        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
-            qwCompany.setCreateDeptId(loginUser.getDeptId());
-            qwCompany.setCreateUserId(loginUser.getUserId());
-        }
+//        String json = configService.selectConfigByKey("course.config");
+//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+//        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
+//            qwCompany.setCreateDeptId(loginUser.getDeptId());
+//            qwCompany.setCreateUserId(loginUser.getUserId());
+//        }
         startPage();
         List<QwCompany> list = qwCompanyService.selectQwCompanyList(qwCompany);
         return getDataTable(list);

+ 25 - 8
fs-service/src/main/java/com/fs/comm/service/CommSmsSendService.java

@@ -110,11 +110,11 @@ public class CommSmsSendService {
             throw new ServiceException("客户不存在");
         }
         String targetPhone = StringUtils.isNotBlank(param.getPhone()) ? param.getPhone() : customer.getMobile();
-        checkSmsBlacklist(companyId, targetPhone);
+        checkCustomerSmsBlacklist(companyId, targetPhone);
         if (companyUserId != null) {
             CompanyUser companyUser = companyUserService.selectCompanyUserById(companyUserId);
             if (companyUser != null && StringUtils.isNotBlank(companyUser.getPhonenumber())) {
-                checkSmsBlacklist(companyId, companyUser.getPhonenumber());
+                checkSalesSmsBlacklist(companyId, companyUser.getPhonenumber());
             }
         }
 
@@ -298,14 +298,14 @@ public class CommSmsSendService {
             for (Long customerId : param.getCustomerIds()) {
                 CrmCustomer customer = crmCustomerService.selectCrmCustomerById(customerId);
                 if (customer != null && StringUtils.isNotBlank(customer.getMobile())) {
-                    checkSmsBlacklist(param.getCompanyId(), customer.getMobile());
+                    checkCustomerSmsBlacklist(param.getCompanyId(), customer.getMobile());
                 }
             }
         }
         if (param.getCompanyUserId() != null) {
             CompanyUser companyUser = companyUserService.selectCompanyUserById(param.getCompanyUserId());
             if (companyUser != null && StringUtils.isNotBlank(companyUser.getPhonenumber())) {
-                checkSmsBlacklist(param.getCompanyId(), companyUser.getPhonenumber());
+                checkSalesSmsBlacklist(param.getCompanyId(), companyUser.getPhonenumber());
             }
         }
         try {
@@ -359,17 +359,34 @@ public class CommSmsSendService {
         }
     }
 
-    private void checkSmsBlacklist(Long companyId, String phone) {
+    private void checkCustomerSmsBlacklist(Long companyId, String phone) {
+        checkSmsBlacklistInternal(companyId, phone, false);
+    }
+
+    private void checkSalesSmsBlacklist(Long companyId, String phone) {
+        checkSmsBlacklistInternal(companyId, phone, true);
+    }
+
+    private void checkSmsBlacklistInternal(Long companyId, String phone, boolean salesSide) {
         if (StringUtils.isBlank(phone)) {
             return;
         }
+        String targetValue = PhoneUtil.resolvePhoneForBlacklist(phone);
+        if (StringUtils.isBlank(targetValue)) {
+            throw new ServiceException("手机号无效或解密失败");
+        }
         CompanyVoiceRoboticCallBlacklistCheckParam checkParam = new CompanyVoiceRoboticCallBlacklistCheckParam();
         checkParam.setCompanyId(companyId);
         checkParam.setBusinessType(BusinessTypeEnum.SMS.getCode());
-        checkParam.setTargetValue(PhoneUtil.decryptPhone(phone));
+        checkParam.setTargetType(1);
+        checkParam.setTargetValue(targetValue);
         CompanyVoiceRoboticCallBlacklistCheckVO vo = companyVoiceRoboticCallBlacklistService.checkBlacklist(checkParam);
-        if (!vo.getPass()) {
-            throw new ServiceException("号码命中短信黑名单: " + phone);
+        if (Boolean.TRUE.equals(vo.getPass())) {
+            return;
+        }
+        if (Boolean.TRUE.equals(vo.getHitBlacklist())) {
+            throw new ServiceException(salesSide ? "销售号码黑名单校验未通过" : "客户号码黑名单校验未通过");
         }
+        throw new ServiceException(StringUtils.defaultIfBlank(vo.getReason(), "黑名单校验未通过"));
     }
 }

+ 11 - 2
fs-service/src/main/java/com/fs/common/service/impl/SmsServiceImpl.java

@@ -33,6 +33,7 @@ import com.fs.crm.param.SmsSendParam;
 import com.fs.crm.param.SmsSendUserParam;
 import com.fs.crm.service.ICrmCustomerService;
 import com.fs.his.config.FsSmsConfig;
+import com.fs.his.utils.PhoneUtil;
 import com.fs.his.domain.FsPayConfig;
 import com.fs.his.domain.FsStoreOrder;
 import com.fs.his.mapper.FsPackageOrderMapper;
@@ -781,7 +782,11 @@ public class SmsServiceImpl implements ISmsService
             CompanyVoiceRoboticCallBlacklistCheckParam salesBlacklistParam = new CompanyVoiceRoboticCallBlacklistCheckParam();
             salesBlacklistParam.setCompanyId(param.getCompanyId());
             salesBlacklistParam.setBusinessType(BusinessTypeEnum.SMS.getCode());
-            salesBlacklistParam.setTargetValue(companyUser.getPhonenumber());
+            salesBlacklistParam.setTargetType(1);
+            salesBlacklistParam.setTargetValue(PhoneUtil.resolvePhoneForBlacklist(companyUser.getPhonenumber()));
+            if (StringUtils.isEmpty(salesBlacklistParam.getTargetValue())) {
+                throw new RuntimeException("销售手机号无效或解密失败");
+            }
             CompanyVoiceRoboticCallBlacklistCheckVO salesBlacklistVo = companyVoiceRoboticCallBlacklistService.checkBlacklist(salesBlacklistParam);
             if (!salesBlacklistVo.getPass()){
                 throw new RuntimeException("销售号码黑名单校验未通过");
@@ -796,7 +801,11 @@ public class SmsServiceImpl implements ISmsService
                 CompanyVoiceRoboticCallBlacklistCheckParam customerBlacklistParam = new CompanyVoiceRoboticCallBlacklistCheckParam();
                 customerBlacklistParam.setCompanyId(param.getCompanyId());
                 customerBlacklistParam.setBusinessType(BusinessTypeEnum.SMS.getCode());
-                customerBlacklistParam.setTargetValue(crmCustomer.getMobile());
+                customerBlacklistParam.setTargetType(1);
+                customerBlacklistParam.setTargetValue(PhoneUtil.resolvePhoneForBlacklist(crmCustomer.getMobile()));
+                if (StringUtils.isEmpty(customerBlacklistParam.getTargetValue())) {
+                    throw new RuntimeException("客户手机号无效或解密失败");
+                }
                 CompanyVoiceRoboticCallBlacklistCheckVO customerBlacklistVo = companyVoiceRoboticCallBlacklistService.checkBlacklist(customerBlacklistParam);
                 if (!customerBlacklistVo.getPass()){
                     throw new RuntimeException("客户号码黑名单校验未通过");

+ 82 - 0
fs-service/src/main/java/com/fs/his/domain/SysRedpacketConfigMore.java

@@ -0,0 +1,82 @@
+package com.fs.his.domain;
+
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 多商户配置对象 sys_redpacket_config_more
+ *
+ * @author fs
+ * @date 2025-11-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class SysRedpacketConfigMore {
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 0:老商户 商家转账到零钱 1:新商户 商家转账 */
+    @Excel(name = "0:老商户 商家转账到零钱 1:新商户 商家转账")
+    private Integer isNew;
+
+    /** 公众号appId */
+    @Excel(name = "公众号appId")
+    private String appId;
+
+    /** 小程序appId */
+    @Excel(name = "小程序appId")
+    private String miniappId;
+
+    /** 商户号 */
+    @Excel(name = "商户号")
+    private String mchId;
+
+    /** 商户密钥 */
+    @Excel(name = "商户密钥")
+    private String mchKey;
+
+    /** p12证书文件的绝对路径或者以classpath:开头的类路径. */
+    @Excel(name = "p12证书文件的绝对路径或者以classpath:开头的类路径.")
+    private String keyPath;
+
+    /** apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径. */
+    @Excel(name = "apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.")
+    private String privateKeyPath;
+
+    /** apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径. */
+    @Excel(name = "apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.")
+    private String privateCertPath;
+
+    /** apiV3 秘钥值. */
+    @Excel(name = "apiV3 秘钥值.")
+    private String apiV3Key;
+
+    /** 公钥ID */
+    @Excel(name = "公钥ID")
+    private String publicKeyId;
+
+    /** pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径. */
+    @Excel(name = "pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.")
+    private String publicKeyPath;
+
+    /** 回调地址 */
+    @Excel(name = "回调地址")
+    private String notifyUrl;
+
+    /** $column.columnComment */
+    @Excel(name = "回调地址")
+    private String notifyUrlScrm;
+
+    /** 分配的租户ID */
+    @Excel(name = "分配的租户ID")
+    private Long tenantId;
+
+    /** 租户名称(非数据库字段) */
+    @TableField(exist = false)
+    private String tenantName;
+
+}

+ 63 - 0
fs-service/src/main/java/com/fs/his/mapper/SysRedpacketConfigMoreMapper.java

@@ -0,0 +1,63 @@
+package com.fs.his.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.SysRedpacketConfigMore;
+
+import java.util.List;
+
+/**
+ * 多商户配置Mapper接口
+ *
+ * @author fs
+ * @date 2025-11-27
+ */
+public interface SysRedpacketConfigMoreMapper extends BaseMapper<SysRedpacketConfigMore> {
+    /**
+     * 查询多商户配置
+     *
+     * @param id 多商户配置主键
+     * @return 多商户配置
+     */
+    SysRedpacketConfigMore selectSysRedpacketConfigMoreById(Long id);
+
+    /**
+     * 查询多商户配置列表
+     *
+     * @param sysRedpacketConfigMore 多商户配置
+     * @return 多商户配置集合
+     */
+    List<SysRedpacketConfigMore> selectSysRedpacketConfigMoreList(SysRedpacketConfigMore sysRedpacketConfigMore);
+
+    /**
+     * 新增多商户配置
+     *
+     * @param sysRedpacketConfigMore 多商户配置
+     * @return 结果
+     */
+    int insertSysRedpacketConfigMore(SysRedpacketConfigMore sysRedpacketConfigMore);
+
+    /**
+     * 修改多商户配置
+     *
+     * @param sysRedpacketConfigMore 多商户配置
+     * @return 结果
+     */
+    int updateSysRedpacketConfigMore(SysRedpacketConfigMore sysRedpacketConfigMore);
+
+    /**
+     * 删除多商户配置
+     *
+     * @param id 多商户配置主键
+     * @return 结果
+     */
+    int deleteSysRedpacketConfigMoreById(Long id);
+
+    /**
+     * 批量删除多商户配置
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteSysRedpacketConfigMoreByIds(Long[] ids);
+}

+ 9 - 0
fs-service/src/main/java/com/fs/his/param/UpdateChangeMchIdParam.java

@@ -0,0 +1,9 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+@Data
+public class UpdateChangeMchIdParam {
+    private Long oldChangeMchId;
+    private Long newChangeMchId;
+}

+ 70 - 0
fs-service/src/main/java/com/fs/his/service/ISysRedpacketConfigMoreService.java

@@ -0,0 +1,70 @@
+package com.fs.his.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.domain.R;
+import com.fs.his.domain.SysRedpacketConfigMore;
+import com.fs.his.param.UpdateChangeMchIdParam;
+
+import java.util.List;
+
+/**
+ * 多商户配置Service接口
+ *
+ * @author fs
+ * @date 2025-11-27
+ */
+public interface ISysRedpacketConfigMoreService extends IService<SysRedpacketConfigMore> {
+    /**
+     * 查询多商户配置
+     *
+     * @param id 多商户配置主键
+     * @return 多商户配置
+     */
+    SysRedpacketConfigMore selectSysRedpacketConfigMoreById(Long id);
+    Long getRedPacketConFig();
+    R updateChangeMchId(UpdateChangeMchIdParam param);
+    R clearRedPacketMchId();
+    void changeRedPacketConfig();
+
+
+    /**
+     * 查询多商户配置列表
+     *
+     * @param sysRedpacketConfigMore 多商户配置
+     * @return 多商户配置集合
+     */
+    List<SysRedpacketConfigMore> selectSysRedpacketConfigMoreList(SysRedpacketConfigMore sysRedpacketConfigMore);
+
+    /**
+     * 新增多商户配置
+     *
+     * @param sysRedpacketConfigMore 多商户配置
+     * @return 结果
+     */
+    int insertSysRedpacketConfigMore(SysRedpacketConfigMore sysRedpacketConfigMore);
+
+    /**
+     * 修改多商户配置
+     *
+     * @param sysRedpacketConfigMore 多商户配置
+     * @return 结果
+     */
+    int updateSysRedpacketConfigMore(SysRedpacketConfigMore sysRedpacketConfigMore);
+
+    /**
+     * 批量删除多商户配置
+     *
+     * @param ids 需要删除的多商户配置主键集合
+     * @return 结果
+     */
+    int deleteSysRedpacketConfigMoreByIds(Long[] ids);
+
+    /**
+     * 删除多商户配置信息
+     *
+     * @param id 多商户配置主键
+     * @return 结果
+     */
+    int deleteSysRedpacketConfigMoreById(Long id);
+}

+ 12 - 2
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -97,6 +97,7 @@ import com.github.binarywang.wxpay.service.TransferService;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 import com.hc.openapi.tool.fastjson.JSON;
+import io.netty.util.internal.StringUtil;
 import me.chanjar.weixin.common.error.WxErrorException;
 
 import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -631,7 +632,10 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             // 根据红包模式获取配置
             switch (param.getRedPacketMode()){
                 case 1:
-                    json = configService.selectConfigByKey("redPacket.config");
+                    json = redisCache.getCacheObject("sys_config:redPacket.config.new");
+                    if (StringUtil.isNullOrEmpty(json) || json.isEmpty()) {
+                        json = configService.selectConfigByKey("redPacket.config");
+                    }
                     config = JSONUtil.toBean(json, RedPacketConfig.class);
                     break;
                 case 2:
@@ -674,6 +678,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
                 WxPayException wxPayException = (WxPayException) e;
                 String customErrorMsg = wxPayException.getCustomErrorMsg();
                 if (null != customErrorMsg && customErrorMsg.startsWith("商户运营账户资金不足")) {
+                    redisCache.incr("sys_config:redPacket.config.newCount",1L);
                     return R.error("[红包领取] 账户余额不足,请联系管理员!");
                 }
             }
@@ -761,7 +766,10 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             // 根据红包模式获取配置
             switch (param.getRedPacketMode()){
                 case 1:
-                    json = configService.selectConfigByKey("redPacket.config");
+                    json = redisCache.getCacheObject("sys_config:redPacket.config.new");
+                    if (StringUtil.isNullOrEmpty(json) || json.isEmpty()) {
+                        json = configService.selectConfigByKey("redPacket.config");
+                    }
                     config = JSONUtil.toBean(json, RedPacketConfig.class);
                     break;
                 case 2:
@@ -794,6 +802,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
             result.put("isNew",config.getIsNew());
             logger.info("红包返回:{}",result);
 
+
             // 更新账户余额
             logger.info("[更新账户余额] 当前余额{} 更新后余额{}",companyMoney.toPlainString(),companyMoney.subtract(amount).toPlainString());
 
@@ -939,6 +948,7 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
                 WxPayException wxPayException = (WxPayException) e;
                 String customErrorMsg = wxPayException.getCustomErrorMsg();
                 if (null != customErrorMsg && customErrorMsg.startsWith("商户运营账户资金不足")) {
+                    redisCache.incr("sys_config:redPacket.config.newCount",1L);
                     return R.error("[红包领取] 账户余额不足,请联系管理员!");
                 }
             }

+ 220 - 0
fs-service/src/main/java/com/fs/his/service/impl/SysRedpacketConfigMoreServiceImpl.java

@@ -0,0 +1,220 @@
+package com.fs.his.service.impl;
+
+
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.BeanCopyUtils;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.course.config.RedPacketConfig;
+import com.fs.his.domain.SysRedpacketConfigMore;
+import com.fs.his.mapper.SysRedpacketConfigMoreMapper;
+import com.fs.his.param.UpdateChangeMchIdParam;
+import com.fs.his.service.ISysRedpacketConfigMoreService;
+import com.fs.system.service.ISysConfigService;
+import io.netty.util.internal.StringUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * 多商户配置Service业务层处理
+ *
+ * @author fs
+ * @date 2025-11-27
+ */
+@Slf4j
+@Service
+public class SysRedpacketConfigMoreServiceImpl extends ServiceImpl<SysRedpacketConfigMoreMapper, SysRedpacketConfigMore> implements ISysRedpacketConfigMoreService {
+
+
+    @Autowired
+    private ISysConfigService configService;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 查询多商户配置
+     *
+     * @param id 多商户配置主键
+     * @return 多商户配置
+     */
+    @Override
+    public SysRedpacketConfigMore selectSysRedpacketConfigMoreById(Long id)
+    {
+        return baseMapper.selectSysRedpacketConfigMoreById(id);
+    }
+
+    @Override
+    public Long getRedPacketConFig() {
+        String json = redisCache.getCacheObject("sys_config:redPacket.config.new");
+        if (StringUtil.isNullOrEmpty(json) || json.isEmpty()) {
+            json = configService.selectConfigByKey("redPacket.config");
+        }
+
+        // 如果没有配置,返回null,前端显示"未配置"
+        if (StringUtil.isNullOrEmpty(json) || json.isEmpty()) {
+            return null;
+        }
+
+        RedPacketConfig config = JSONUtil.toBean(json, RedPacketConfig.class);
+        if (config == null || config.getMchId() == null || config.getMchId().isEmpty()) {
+            return null;
+        }
+
+        return Long.valueOf(config.getMchId());
+    }
+
+    @Override
+    public R updateChangeMchId(UpdateChangeMchIdParam param) {
+
+        SysRedpacketConfigMore configMore = baseMapper.selectOne(new QueryWrapper<SysRedpacketConfigMore>().eq("mch_id", param.getNewChangeMchId()));
+
+        if (configMore == null || configMore.getMchId() == null) {
+            return R.error("商户号不存在");
+        }
+
+        RedPacketConfig config = new RedPacketConfig();
+        BeanCopyUtils.copy(configMore,config);
+
+        if (config.getMchId()!=null){
+            redisCache.setCacheObject("sys_config:redPacket.config.new", JSONUtil.toJsonStr(config));
+            redisCache.deleteObject("sys_config:redPacket.config.newCount");
+        }
+
+        return R.ok();
+    }
+
+    @Override
+    public R clearRedPacketMchId() {
+        // 删除 Redis 中的红包商户号配置
+        redisCache.deleteObject("sys_config:redPacket.config.new");
+        redisCache.deleteObject("sys_config:redPacket.config.newCount");
+        log.info("清空当前红包商户号配置");
+        return R.ok("已清空当前商户号配置");
+    }
+
+    @Override
+    public void changeRedPacketConfig() {
+        Integer count = redisCache.getCacheObject("sys_config:redPacket.config.newCount");
+        if (count >= 1000) {
+            String json = redisCache.getCacheObject("sys_config:redPacket.config.new");
+            if (StringUtil.isNullOrEmpty(json) || json.isEmpty()) {
+                json = configService.selectConfigByKey("redPacket.config");
+            }
+
+            RedPacketConfig config   = JSONUtil.toBean(json, RedPacketConfig.class);
+
+            SysRedpacketConfigMore configMore = baseMapper.selectOne(new QueryWrapper<SysRedpacketConfigMore>().eq("mch_id", config.getMchId()));
+
+            if (configMore == null) {
+                log.error("获取不到商户号");
+                return;
+            }
+
+            List<SysRedpacketConfigMore> configMoreList = baseMapper.selectList(
+                    new QueryWrapper<SysRedpacketConfigMore>().orderByAsc("id")
+            );
+
+            // 空列表处理
+            if (configMoreList.isEmpty()) {
+                log.error("商户号列表为空");
+            }
+
+
+            // 查找当前元素的索引
+            int currentIndex = -1;
+            Long currentId = configMore != null ? configMore.getId() : null;
+
+            for (int i = 0; i < configMoreList.size(); i++) {
+                if (configMoreList.get(i).getId().equals(currentId)) {
+                    currentIndex = i;
+                    break;
+                }
+            }
+
+            // 计算下一个索引(使用取模实现循环)
+            int nextIndex = (currentIndex + 1) % configMoreList.size();
+
+            SysRedpacketConfigMore configMoreNew = configMoreList.get(nextIndex);
+
+
+            RedPacketConfig configNew = new RedPacketConfig();
+            BeanCopyUtils.copy(configMoreNew,configNew);
+
+            if (configNew.getMchId()!=null){
+                log.info("自动切换商户号-原:"+configMore.getMchId()+"-新:"+configNew.getMchId());
+                redisCache.setCacheObject("sys_config:redPacket.config.new", JSONUtil.toJsonStr(configNew));
+                redisCache.deleteObject("sys_config:redPacket.config.newCount");
+            }
+        }
+    }
+
+
+
+
+    /**
+     * 查询多商户配置列表
+     *
+     * @param sysRedpacketConfigMore 多商户配置
+     * @return 多商户配置
+     */
+    @Override
+    public List<SysRedpacketConfigMore> selectSysRedpacketConfigMoreList(SysRedpacketConfigMore sysRedpacketConfigMore)
+    {
+        return baseMapper.selectSysRedpacketConfigMoreList(sysRedpacketConfigMore);
+    }
+
+    /**
+     * 新增多商户配置
+     *
+     * @param sysRedpacketConfigMore 多商户配置
+     * @return 结果
+     */
+    @Override
+    public int insertSysRedpacketConfigMore(SysRedpacketConfigMore sysRedpacketConfigMore)
+    {
+        return baseMapper.insertSysRedpacketConfigMore(sysRedpacketConfigMore);
+    }
+
+    /**
+     * 修改多商户配置
+     *
+     * @param sysRedpacketConfigMore 多商户配置
+     * @return 结果
+     */
+    @Override
+    public int updateSysRedpacketConfigMore(SysRedpacketConfigMore sysRedpacketConfigMore)
+    {
+        return baseMapper.updateSysRedpacketConfigMore(sysRedpacketConfigMore);
+    }
+
+    /**
+     * 批量删除多商户配置
+     *
+     * @param ids 需要删除的多商户配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteSysRedpacketConfigMoreByIds(Long[] ids)
+    {
+        return baseMapper.deleteSysRedpacketConfigMoreByIds(ids);
+    }
+
+    /**
+     * 删除多商户配置信息
+     *
+     * @param id 多商户配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteSysRedpacketConfigMoreById(Long id)
+    {
+        return baseMapper.deleteSysRedpacketConfigMoreById(id);
+    }
+}

+ 44 - 10
fs-service/src/main/java/com/fs/his/utils/PhoneUtil.java

@@ -26,30 +26,31 @@ public class PhoneUtil {
     * 解密
     */
     public static String decryptPhone(String encryptedText) {
-        String text=null;
+        String text = null;
         try {
             SecretKeySpec secretKey = new SecretKeySpec("AESAabCdeREssREA".getBytes(), "AES");
             Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
             cipher.init(Cipher.DECRYPT_MODE, secretKey);
             byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
             text = new String(decryptedBytes);
-        } catch (Exception e) {
-            e.printStackTrace();
+        } catch (Exception ignored) {
+            // 非 AES 密文时解密失败属正常,由 decryptAutoPhone 兜底
         }
         return text;
     }
+
     /**
-    * 解密加*
-    */
+     * 解密加*
+     */
     public static String decryptPhoneMk(String encryptedText) {
-        String text=null;
+        String text = null;
         try {
             SecretKeySpec secretKey = new SecretKeySpec("AESAabCdeREssREA".getBytes(), "AES");
             Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
             cipher.init(Cipher.DECRYPT_MODE, secretKey);
             byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
             text = new String(decryptedBytes);
-            text =text.replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2");
+            text = text.replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2");
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -64,10 +65,43 @@ public class PhoneUtil {
             return null;
         }
         String text = encryptedText.trim();
-        if (text.length() > 11) {
-            return decryptPhone(text);
+        if (text.length() <= 11 && text.matches("\\d+")) {
+            return text;
         }
-        return text;
+        if (looksLikePlainPhone(text)) {
+            return text;
+        }
+        String decrypted = decryptPhone(text);
+        if (decrypted != null && !decrypted.isEmpty()) {
+            return decrypted;
+        }
+        return looksLikePlainPhone(text) ? text : null;
+    }
+
+    /**
+     * 黑名单校验用手机号:自动解密并归一化(去 +86、空格等)
+     */
+    public static String resolvePhoneForBlacklist(String phone) {
+        String decrypted = decryptAutoPhone(phone);
+        if (decrypted == null || decrypted.isEmpty()) {
+            return null;
+        }
+        return decrypted.replace(" ", "")
+                .replace("-", "")
+                .replace("+86", "")
+                .replace("(", "")
+                .replace(")", "");
+    }
+
+    private static boolean looksLikePlainPhone(String text) {
+        if (text == null || text.isEmpty()) {
+            return false;
+        }
+        if (!text.matches("^[+\\d\\-\\s()]+$")) {
+            return false;
+        }
+        String digits = text.replaceAll("\\D", "");
+        return digits.length() >= 11 && digits.length() <= 15;
     }
 
     public static String decryptAutoPhoneMk(String encryptedText) {

+ 54 - 0
fs-service/src/main/resources/db/tenant-initTable.sql

@@ -18684,6 +18684,60 @@ CREATE TABLE `lobster_learning_corpus`
     KEY                 `idx_company_status` (`company_id`, `status`),
     KEY                 `idx_company_scenario` (`company_id`, `scenario`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售对话语料学习表';
+
+DROP TABLE IF EXISTS `tencent_word`;
+CREATE TABLE `tencent_word`
+(
+    `id`               bigint                                                        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `title`            varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '标题',
+    `type`             varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '类型',
+    `template_version` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci  DEFAULT NULL COMMENT '模板版本',
+    `url`              varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '文档URL',
+    `file_id`          varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件ID',
+    `create_by`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci  DEFAULT NULL COMMENT '创建者',
+    `create_time`      datetime                                                      DEFAULT NULL COMMENT '创建时间',
+    `update_by`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci  DEFAULT NULL COMMENT '更新者',
+    `update_time`      datetime                                                      DEFAULT NULL COMMENT '更新时间',
+    PRIMARY KEY (`id`) USING BTREE,
+    KEY                `idx_file_id` (`file_id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='腾讯文档表';
+
+DROP TABLE IF EXISTS `tencent_word_detail`;
+CREATE TABLE `tencent_word_detail`
+(
+    `id`          bigint                                                        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `file_id`     varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件ID',
+    `sheet_id`    varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci  NOT NULL COMMENT '工作表ID',
+    `q`           text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '问题',
+    `a`           text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '答案',
+    `create_by`   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建者',
+    `create_time` datetime                                                     DEFAULT NULL COMMENT '创建时间',
+    `update_by`   varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '更新者',
+    `update_time` datetime                                                     DEFAULT NULL COMMENT '更新时间',
+    PRIMARY KEY (`id`) USING BTREE,
+    KEY           `idx_file_id` (`file_id`) USING BTREE,
+    KEY           `idx_sheet_id` (`sheet_id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=175 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='腾讯文档详情表';
+
+DROP TABLE IF EXISTS `tencent_word_sheet`;
+CREATE TABLE `tencent_word_sheet`
+(
+    `id`           bigint                                                        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    `file_id`      varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '文件ID',
+    `sheet_id`     varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci  NOT NULL COMMENT '工作表ID',
+    `title`        varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '工作表标题',
+    `row_count`    int                                                           DEFAULT NULL COMMENT '行数',
+    `column_count` int                                                           DEFAULT NULL COMMENT '列数',
+    `row_total`    int                                                           DEFAULT NULL COMMENT '总行数',
+    `column_total` int                                                           DEFAULT NULL COMMENT '总列数',
+    `create_by`    varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci  DEFAULT NULL COMMENT '创建者',
+    `create_time`  datetime                                                      DEFAULT NULL COMMENT '创建时间',
+    `update_by`    varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci  DEFAULT NULL COMMENT '更新者',
+    `update_time`  datetime                                                      DEFAULT NULL COMMENT '更新时间',
+    PRIMARY KEY (`id`) USING BTREE,
+    KEY            `idx_file_id` (`file_id`) USING BTREE,
+    KEY            `idx_sheet_id` (`sheet_id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='腾讯文档工作表表';
 SET
 FOREIGN_KEY_CHECKS = 1;
 

+ 126 - 0
fs-service/src/main/resources/mapper/his/SysRedpacketConfigMoreMapper.xml

@@ -0,0 +1,126 @@
+<?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.fs.his.mapper.SysRedpacketConfigMoreMapper">
+
+    <resultMap type="SysRedpacketConfigMore" id="SysRedpacketConfigMoreResult">
+        <result property="id"    column="id"    />
+        <result property="isNew"    column="is_new"    />
+        <result property="appId"    column="app_id"    />
+        <result property="miniappId"    column="miniapp_id"    />
+        <result property="mchId"    column="mch_id"    />
+        <result property="mchKey"    column="mch_key"    />
+        <result property="keyPath"    column="key_path"    />
+        <result property="privateKeyPath"    column="private_key_path"    />
+        <result property="privateCertPath"    column="private_cert_path"    />
+        <result property="apiV3Key"    column="api_v3_key"    />
+        <result property="publicKeyId"    column="public_key_id"    />
+        <result property="publicKeyPath"    column="public_key_path"    />
+        <result property="notifyUrl"    column="notify_url"    />
+        <result property="notifyUrlScrm"    column="notify_url_scrm"    />
+        <result property="tenantId"    column="tenant_id"    />
+        <result property="tenantName"    column="tenant_name"    />
+    </resultMap>
+
+    <sql id="selectSysRedpacketConfigMoreVo">
+        select r.id, r.is_new, r.app_id, r.miniapp_id, r.mch_id, r.mch_key, r.key_path, r.private_key_path, r.private_cert_path, r.api_v3_key, r.public_key_id, r.public_key_path, r.notify_url, r.notify_url_scrm, r.tenant_id, t.tenant_name
+        from sys_redpacket_config_more r
+        left join tenant_info t on r.tenant_id = t.id
+    </sql>
+
+    <select id="selectSysRedpacketConfigMoreList" parameterType="SysRedpacketConfigMore" resultMap="SysRedpacketConfigMoreResult">
+        <include refid="selectSysRedpacketConfigMoreVo"/>
+        <where>
+            <if test="isNew != null "> and r.is_new = #{isNew}</if>
+            <if test="appId != null  and appId != ''"> and r.app_id = #{appId}</if>
+            <if test="miniappId != null  and miniappId != ''"> and r.miniapp_id = #{miniappId}</if>
+            <if test="mchId != null  and mchId != ''"> and r.mch_id = #{mchId}</if>
+            <if test="mchKey != null  and mchKey != ''"> and r.mch_key = #{mchKey}</if>
+            <if test="keyPath != null  and keyPath != ''"> and r.key_path = #{keyPath}</if>
+            <if test="privateKeyPath != null  and privateKeyPath != ''"> and r.private_key_path = #{privateKeyPath}</if>
+            <if test="privateCertPath != null  and privateCertPath != ''"> and r.private_cert_path = #{privateCertPath}</if>
+            <if test="apiV3Key != null  and apiV3Key != ''"> and r.api_v3_key = #{apiV3Key}</if>
+            <if test="publicKeyId != null  and publicKeyId != ''"> and r.public_key_id = #{publicKeyId}</if>
+            <if test="publicKeyPath != null  and publicKeyPath != ''"> and r.public_key_path = #{publicKeyPath}</if>
+            <if test="notifyUrl != null  and notifyUrl != ''"> and r.notify_url = #{notifyUrl}</if>
+            <if test="notifyUrlScrm != null  and notifyUrlScrm != ''"> and r.notify_url_scrm = #{notifyUrlScrm}</if>
+            <if test="tenantId != null"> and r.tenant_id = #{tenantId}</if>
+        </where>
+    </select>
+
+    <select id="selectSysRedpacketConfigMoreById" parameterType="Long" resultMap="SysRedpacketConfigMoreResult">
+        <include refid="selectSysRedpacketConfigMoreVo"/>
+        where r.id = #{id}
+    </select>
+
+    <insert id="insertSysRedpacketConfigMore" parameterType="SysRedpacketConfigMore">
+        insert into sys_redpacket_config_more
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="isNew != null">is_new,</if>
+            <if test="appId != null">app_id,</if>
+            <if test="miniappId != null">miniapp_id,</if>
+            <if test="mchId != null">mch_id,</if>
+            <if test="mchKey != null">mch_key,</if>
+            <if test="keyPath != null">key_path,</if>
+            <if test="privateKeyPath != null">private_key_path,</if>
+            <if test="privateCertPath != null">private_cert_path,</if>
+            <if test="apiV3Key != null">api_v3_key,</if>
+            <if test="publicKeyId != null">public_key_id,</if>
+            <if test="publicKeyPath != null">public_key_path,</if>
+            <if test="notifyUrl != null">notify_url,</if>
+            <if test="notifyUrlScrm != null">notify_url_scrm,</if>
+            <if test="tenantId != null">tenant_id,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="isNew != null">#{isNew},</if>
+            <if test="appId != null">#{appId},</if>
+            <if test="miniappId != null">#{miniappId},</if>
+            <if test="mchId != null">#{mchId},</if>
+            <if test="mchKey != null">#{mchKey},</if>
+            <if test="keyPath != null">#{keyPath},</if>
+            <if test="privateKeyPath != null">#{privateKeyPath},</if>
+            <if test="privateCertPath != null">#{privateCertPath},</if>
+            <if test="apiV3Key != null">#{apiV3Key},</if>
+            <if test="publicKeyId != null">#{publicKeyId},</if>
+            <if test="publicKeyPath != null">#{publicKeyPath},</if>
+            <if test="notifyUrl != null">#{notifyUrl},</if>
+            <if test="notifyUrlScrm != null">#{notifyUrlScrm},</if>
+            <if test="tenantId != null">#{tenantId},</if>
+         </trim>
+    </insert>
+
+    <update id="updateSysRedpacketConfigMore" parameterType="SysRedpacketConfigMore">
+        update sys_redpacket_config_more
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="isNew != null">is_new = #{isNew},</if>
+            <if test="appId != null">app_id = #{appId},</if>
+            <if test="miniappId != null">miniapp_id = #{miniappId},</if>
+            <if test="mchId != null">mch_id = #{mchId},</if>
+            <if test="mchKey != null">mch_key = #{mchKey},</if>
+            <if test="keyPath != null">key_path = #{keyPath},</if>
+            <if test="privateKeyPath != null">private_key_path = #{privateKeyPath},</if>
+            <if test="privateCertPath != null">private_cert_path = #{privateCertPath},</if>
+            <if test="apiV3Key != null">api_v3_key = #{apiV3Key},</if>
+            <if test="publicKeyId != null">public_key_id = #{publicKeyId},</if>
+            <if test="publicKeyPath != null">public_key_path = #{publicKeyPath},</if>
+            <if test="notifyUrl != null">notify_url = #{notifyUrl},</if>
+            <if test="notifyUrlScrm != null">notify_url_scrm = #{notifyUrlScrm},</if>
+            <if test="tenantId != null">tenant_id = #{tenantId},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteSysRedpacketConfigMoreById" parameterType="Long">
+        delete from sys_redpacket_config_more where id = #{id}
+    </delete>
+
+    <delete id="deleteSysRedpacketConfigMoreByIds" parameterType="String">
+        delete from sys_redpacket_config_more where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 18 - 0
fs-task/src/main/java/com/fs/his/task/companyTask.java

@@ -0,0 +1,18 @@
+package com.fs.his.task;
+
+import com.fs.his.service.ISysRedpacketConfigMoreService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component("companyTask")
+public class companyTask {
+
+    @Autowired
+    private ISysRedpacketConfigMoreService sysRedpacketConfigMoreService;
+
+    //切换商户号
+    public void changeRedPacketConfig()
+    {
+        sysRedpacketConfigMoreService.changeRedPacketConfig();
+    }
+}