Просмотр исходного кода

益寿缘-销售端-优化获客链接组成部分

cgp 11 часов назад
Родитель
Сommit
44d5c706a7

+ 1 - 46
fs-company/src/main/java/com/fs/company/controller/qw/QwAcquisitionAssistantController.java

@@ -12,7 +12,6 @@ import com.fs.his.dto.SendResultDetailDTO;
 import com.fs.qw.bo.SendMsgLogBo;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.domain.QwUser;
-import com.fs.qw.dto.acquisition.AcquisitionListResponse;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.AcquisitionAssistantDetailVO;
@@ -70,7 +69,7 @@ public class QwAcquisitionAssistantController extends BaseController {
             SendMsgLogBo sendMsgLogBo=new SendMsgLogBo();
             sendMsgLogBo.setCompanyId(loginUser.getCompany().getCompanyId());
             sendMsgLogBo.setCompanyUserId(loginUser.getCompany().getUserId());
-            validatePhone( phone);
+            validatePhone(phone);
             SendResultDetailDTO sendResultDetailDTO = qwAcquisitionAssistantService.sendMessageAcquisition(phone, id,sendMsgLogBo);
             if (sendResultDetailDTO.isSuccess()){
                 return AjaxResult.success("发送成功");
@@ -82,50 +81,6 @@ public class QwAcquisitionAssistantController extends BaseController {
             return AjaxResult.error("网络异常,请稍后再试");
         }
     }
-
-    /**
-     * 从企微同步获客链接列表(全量)
-     * 手动点击同步按钮时调用
-     */
-    @PostMapping("/syncList")
-    public AjaxResult syncList(@RequestParam String corpId) {
-        try {
-
-            QwCompany qwCompany = getQwCompany(corpId);
-
-            // 调用同步服务
-            String result = qwAcquisitionAssistantService.syncListFromQw(qwCompany.getCorpId(), qwCompany.getOpenSecret());
-
-            return AjaxResult.success(result);
-        } catch (CustomException e) {
-            return AjaxResult.error(e.getMessage());
-        } catch (Exception e) {
-            return AjaxResult.error("系统异常:" + e.getMessage());
-        }
-    }
-
-    /**
-     * 分页获取企微列表(直接调用企微接口)
-     * 用于查看企微原始数据
-     */
-    @GetMapping("/qwList")
-    public AjaxResult getQwList(@RequestParam(required = false) Integer limit,
-                                @RequestParam(required = false) String cursor,
-                                @RequestParam String corpId) {
-        try {
-            QwCompany qwCompany = getQwCompany(corpId);
-            // 调用企微列表接口
-            AcquisitionListResponse response = qwAcquisitionAssistantService.getQwList(
-                    qwCompany.getCorpId(), qwCompany.getOpenSecret(), limit, cursor);
-
-            return AjaxResult.success(response);
-        } catch (CustomException e) {
-            return AjaxResult.error(e.getMessage());
-        } catch (Exception e) {
-            return AjaxResult.error("系统异常:" + e.getMessage());
-        }
-    }
-
     /**
      * 根据linkId直接获取详情
      *

+ 105 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwAcquisitionLinkInfoController.java

@@ -0,0 +1,105 @@
+package com.fs.company.controller.qw;
+
+import java.io.IOException;
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.qw.domain.QwAcquisitionLinkInfo;
+import com.fs.qw.service.IQwAcquisitionLinkInfoService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+
+/**
+ * 获客链接-号码链接生成记录Controller
+ *
+ * @author fs
+ * @date 2026-03-27
+ */
+@RestController
+@RequestMapping("/qw/linkInfo")
+public class QwAcquisitionLinkInfoController extends BaseController
+{
+    @Autowired
+    private IQwAcquisitionLinkInfoService qwAcquisitionLinkInfoService;
+
+    /**
+     * 查询获客链接-号码链接生成记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(QwAcquisitionLinkInfo qwAcquisitionLinkInfo)
+    {
+        startPage();
+        List<QwAcquisitionLinkInfo> list = qwAcquisitionLinkInfoService.selectQwAcquisitionLinkInfoList(qwAcquisitionLinkInfo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出获客链接-号码链接生成记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:export')")
+    @Log(title = "获客链接-号码链接生成记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, QwAcquisitionLinkInfo qwAcquisitionLinkInfo) throws IOException {
+        List<QwAcquisitionLinkInfo> list = qwAcquisitionLinkInfoService.selectQwAcquisitionLinkInfoList(qwAcquisitionLinkInfo);
+        ExcelUtil<QwAcquisitionLinkInfo> util = new ExcelUtil<QwAcquisitionLinkInfo>(QwAcquisitionLinkInfo.class);
+        util.exportExcel(response, list, "获客链接-号码链接生成记录数据");
+    }
+
+    /**
+     * 获取获客链接-号码链接生成记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(qwAcquisitionLinkInfoService.selectQwAcquisitionLinkInfoById(id));
+    }
+
+    /**
+     * 新增获客链接-号码链接生成记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:add')")
+    @Log(title = "获客链接-号码链接生成记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody QwAcquisitionLinkInfo qwAcquisitionLinkInfo)
+    {
+        return toAjax(qwAcquisitionLinkInfoService.insertQwAcquisitionLinkInfo(qwAcquisitionLinkInfo));
+    }
+
+    /**
+     * 修改获客链接-号码链接生成记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:edit')")
+    @Log(title = "获客链接-号码链接生成记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody QwAcquisitionLinkInfo qwAcquisitionLinkInfo)
+    {
+        return toAjax(qwAcquisitionLinkInfoService.updateQwAcquisitionLinkInfo(qwAcquisitionLinkInfo));
+    }
+
+    /**
+     * 删除获客链接-号码链接生成记录
+     */
+    @PreAuthorize("@ss.hasPermi('qw:linkInfo:remove')")
+    @Log(title = "获客链接-号码链接生成记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(qwAcquisitionLinkInfoService.deleteQwAcquisitionLinkInfoByIds(ids));
+    }
+}

+ 36 - 0
fs-service/src/main/java/com/fs/qw/domain/QwAcquisitionLinkInfo.java

@@ -0,0 +1,36 @@
+package com.fs.qw.domain;
+
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+
+/**
+ * 获客链接-号码链接生成记录对象 qw_acquisition_link_info
+ *
+ * @author fs
+ * @date 2026-03-27
+ */
+@Data
+public class QwAcquisitionLinkInfo extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 获客链接管理主键ID
+     */
+    private Long qwAcquisitionAssistantId;
+
+    /**
+     * 完整链接
+     */
+    private String link;
+
+    /**
+     * 客户电话
+     */
+    private String phone;
+
+}

+ 70 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwAcquisitionLinkInfoMapper.java

@@ -0,0 +1,70 @@
+package com.fs.qw.mapper;
+
+import java.util.List;
+import com.fs.qw.domain.QwAcquisitionLinkInfo;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 获客链接-号码链接生成记录Mapper接口
+ *
+ * @author fs
+ * @date 2026-03-27
+ */
+public interface QwAcquisitionLinkInfoMapper
+{
+    /**
+     * 查询获客链接-号码链接生成记录
+     *
+     * @param id 获客链接-号码链接生成记录主键
+     * @return 获客链接-号码链接生成记录
+     */
+    public QwAcquisitionLinkInfo selectQwAcquisitionLinkInfoById(Long id);
+
+    /**
+     * 查询获客链接-号码链接生成记录列表
+     *
+     * @param qwAcquisitionLinkInfo 获客链接-号码链接生成记录
+     * @return 获客链接-号码链接生成记录集合
+     */
+    public List<QwAcquisitionLinkInfo> selectQwAcquisitionLinkInfoList(QwAcquisitionLinkInfo qwAcquisitionLinkInfo);
+
+    /**
+     * 新增获客链接-号码链接生成记录
+     *
+     * @param qwAcquisitionLinkInfo 获客链接-号码链接生成记录
+     * @return 结果
+     */
+    public int insertQwAcquisitionLinkInfo(QwAcquisitionLinkInfo qwAcquisitionLinkInfo);
+
+    /**
+     * 修改获客链接-号码链接生成记录
+     *
+     * @param qwAcquisitionLinkInfo 获客链接-号码链接生成记录
+     * @return 结果
+     */
+    public int updateQwAcquisitionLinkInfo(QwAcquisitionLinkInfo qwAcquisitionLinkInfo);
+
+    /**
+     * 删除获客链接-号码链接生成记录
+     *
+     * @param id 获客链接-号码链接生成记录主键
+     * @return 结果
+     */
+    public int deleteQwAcquisitionLinkInfoById(Long id);
+
+    /**
+     * 批量删除获客链接-号码链接生成记录
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteQwAcquisitionLinkInfoByIds(Long[] ids);
+
+    /**
+     * 批量删除获客链接-号码链接生成记录
+     *
+     * @param qwAcquisitionAssistantIds 需要删除的获客链接管理主键ID集合
+     * @return 结果
+     */
+    public int deleteQwAcquisitionLinkInfoByQwAcquisitionAssistantIds(@Param("qwAcquisitionAssistantIds") Long[] qwAcquisitionAssistantIds);
+}

+ 0 - 27
fs-service/src/main/java/com/fs/qw/service/IQwAcquisitionAssistantService.java

@@ -4,7 +4,6 @@ import com.fs.common.exception.CustomException;
 import com.fs.his.dto.SendResultDetailDTO;
 import com.fs.qw.bo.SendMsgLogBo;
 import com.fs.qw.domain.QwAcquisitionAssistant;
-import com.fs.qw.dto.acquisition.AcquisitionListResponse;
 import com.fs.qw.vo.AcquisitionAssistantDetailVO;
 
 import java.util.List;
@@ -34,24 +33,6 @@ public interface IQwAcquisitionAssistantService
      */
     public SendResultDetailDTO sendMessageAcquisition(String phone, Long qwAcquisitionId, SendMsgLogBo sendMsgLogBo);
 
-    /**
-     * 从企微同步获客链接列表(全量拉取所有详情)
-     * @param corpid 企业ID
-     * @param corpsecret 应用密钥
-     * @return 同步结果统计
-     */
-    public String syncListFromQw(String corpid, String corpsecret);
-
-    /**
-     * 获取企微原始列表(分页)
-     * @param corpid 企业ID
-     * @param corpsecret 应用密钥
-     * @param limit 每页数量(最大100)
-     * @param cursor 分页游标
-     * @return 企微返回的列表数据
-     */
-    public AcquisitionListResponse getQwList(String corpid, String corpsecret, Integer limit, String cursor);
-
     /**
      * 获取获客链接详情(从企微实时查询)
      * @param linkId 获客链接ID
@@ -60,14 +41,6 @@ public interface IQwAcquisitionAssistantService
      */
     public AcquisitionAssistantDetailVO getDetailWithQw(String linkId);
 
-    /**
-     * 根据linkId同步单个获客链接详情到本地
-     * @param corpid 企业ID
-     * @param corpsecret 应用密钥
-     * @param linkId 获客链接ID
-     */
-    public void syncDetailToLocal(String corpid, String corpsecret, String linkId);
-
     /**
      * 创建获客链接
      *

+ 74 - 0
fs-service/src/main/java/com/fs/qw/service/IQwAcquisitionLinkInfoService.java

@@ -0,0 +1,74 @@
+package com.fs.qw.service;
+
+import java.util.List;
+import com.fs.qw.domain.QwAcquisitionLinkInfo;
+
+/**
+ * 获客链接-号码链接生成记录Service接口
+ *
+ * @author fs
+ * @date 2026-03-27
+ */
+public interface IQwAcquisitionLinkInfoService
+{
+    /**
+     * 查询获客链接-号码链接生成记录
+     *
+     * @param id 获客链接-号码链接生成记录主键
+     * @return 获客链接-号码链接生成记录
+     */
+    public QwAcquisitionLinkInfo selectQwAcquisitionLinkInfoById(Long id);
+
+    /**
+     * 查询获客链接-号码链接生成记录列表
+     *
+     * @param qwAcquisitionLinkInfo 获客链接-号码链接生成记录
+     * @return 获客链接-号码链接生成记录集合
+     */
+    public List<QwAcquisitionLinkInfo> selectQwAcquisitionLinkInfoList(QwAcquisitionLinkInfo qwAcquisitionLinkInfo);
+
+    /**
+     * 新增获客链接-号码链接生成记录
+     *
+     * @param qwAcquisitionLinkInfo 获客链接-号码链接生成记录
+     * @return 结果
+     */
+    public int insertQwAcquisitionLinkInfo(QwAcquisitionLinkInfo qwAcquisitionLinkInfo);
+
+    /**
+     * 修改获客链接-号码链接生成记录
+     *
+     * @param qwAcquisitionLinkInfo 获客链接-号码链接生成记录
+     * @return 结果
+     */
+    public int updateQwAcquisitionLinkInfo(QwAcquisitionLinkInfo qwAcquisitionLinkInfo);
+
+    /**
+     * 批量删除获客链接-号码链接生成记录
+     *
+     * @param ids 需要删除的获客链接-号码链接生成记录主键集合
+     * @return 结果
+     */
+    public int deleteQwAcquisitionLinkInfoByIds(Long[] ids);
+
+    /**
+     * 批量删除获客链接-号码链接生成记录
+     *
+     * @param qwAcquisitionAssistantIds 需要删除的获客链接管理主键ID集合
+     * @return 结果
+     */
+    public int deleteQwAcquisitionLinkInfoByQwAcquisitionAssistantIds(Long[] qwAcquisitionAssistantIds);
+
+    /**
+     * 删除获客链接-号码链接生成记录信息
+     *
+     * @param id 获客链接-号码链接生成记录主键
+     * @return 结果
+     */
+    public int deleteQwAcquisitionLinkInfoById(Long id);
+
+    /**
+     * 添加链接生成记录
+     * */
+    public int buildQwAcquisitionLinkInfoAdd(Long qwAcquisitionAssistantId,String originalPhone,String originalLink);
+}

+ 395 - 489
fs-service/src/main/java/com/fs/qw/service/impl/QwAcquisitionAssistantServiceImpl.java

@@ -19,6 +19,7 @@ import com.fs.qw.dto.acquisition.*;
 import com.fs.qw.enums.SmsLogType;
 import com.fs.qw.mapper.QwAcquisitionAssistantMapper;
 import com.fs.qw.service.IQwAcquisitionAssistantService;
+import com.fs.qw.service.IQwAcquisitionLinkInfoService;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.utils.UniqueStringUtil;
 import com.fs.qw.vo.AcquisitionAssistantDetailVO;
@@ -42,7 +43,7 @@ import java.util.concurrent.TimeUnit;
 public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistantService {
 
     @Autowired
-    private QwAcquisitionAssistantMapper qwAcquisitionAssistantMapper;
+    private QwAcquisitionAssistantMapper acquisitionAssistantMapper;
 
     @Autowired
     private IWeixinKfService weixinKfService;
@@ -58,11 +59,13 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
 
     @Autowired
     private ICompanySmsTempService smsTempService;
+    
+    @Autowired
+    private IQwAcquisitionLinkInfoService linkInfoService;
 
     // 获客链接管理-企微的ACCESS_TOKEN的key
     private static final String QW_ACQUISITION_KEY_PREFIX = "qw:acquisition:key:";
 
-
     // 获客链接-页面参数-url的key
     private static final String QW_ACQUISITION_URL_KEY_PREFIX = "qw:acquisition:url:key:";
 
@@ -72,174 +75,6 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
     //访问链接域名
     private static final String  LINK_DOMAIN = "https://c.ysyd.top/";
 
-    /**
-     * 获取access_token并返回完整URL
-     */
-    private String buildApiUrl(String corpid, String corpsecret, String apiPath) {
-        WeixinKfTokenVO token = getAccessToken(corpid, corpsecret);
-
-        if (token == null || StringUtils.isEmpty(token.getAccess_token())) {
-            log.error("获取access_token失败, corpid:{}", corpid);
-            throw new CustomException("获客链接管理-获取企业微信access_token失败");
-        }
-
-        return apiPath + "?access_token=" + token.getAccess_token();
-    }
-
-    /**
-     * 获取access_token(使用实际有效期)
-     */
-    private WeixinKfTokenVO getAccessToken(String corpid, String corpsecret) {
-        String key = QW_ACQUISITION_KEY_PREFIX + corpid;
-
-        // 1. 先从缓存获取
-        WeixinKfTokenVO token = null;
-        try {
-            Object cacheObj = redisCache.getCacheObject(key);
-            if (cacheObj instanceof WeixinKfTokenVO) {
-                token = (WeixinKfTokenVO) cacheObj;
-                log.debug("从缓存获取token成功, corpid:{}", corpid);
-            }
-        } catch (Exception e) {
-            log.warn("从缓存获取token异常, 将重新获取, corpid:{}", corpid);
-        }
-
-        // 2. 缓存中没有,则重新获取
-        if (token == null) {
-            log.info("缓存中没有token,重新获取, corpid:{}", corpid);
-            try {
-                // 获取新token
-                token = weixinKfService.getToken(corpid, corpsecret);
-
-                // 存入缓存,使用企业微信返回的实际有效期
-                if (token != null && StringUtils.isNotEmpty(token.getAccess_token())) {
-                    Integer expiresIn = token.getExpires_in();
-                    if (expiresIn == null) {
-                        // 如果返回中没有expires_in,默认2小时
-                        expiresIn = 7200;
-                        log.warn("token返回中没有expires_in字段,使用默认值7200秒");
-                    }
-
-                    // 实际缓存时间比有效期稍短(提前5分钟过期),避免边界问题
-                    Integer cacheExpire = expiresIn - 300; // 提前5分钟
-                    if (cacheExpire <= 0) {
-                        cacheExpire = expiresIn;
-                    }
-
-                    redisCache.setCacheObject(key, token, cacheExpire, TimeUnit.SECONDS);
-                    log.info("token缓存成功, corpid:{}, 有效期:{}秒, 缓存时间:{}秒",
-                            corpid, expiresIn, cacheExpire);
-                }
-            } catch (Exception e) {
-                log.error("获取token失败, corpid:{}, error:{}", corpid, e.getMessage());
-                return null;
-            }
-        }
-
-        return token;
-    }
-
-    /**
-     * 调用企微API并统一处理返回结果
-     */
-    private <T> T callQwApi(String qwApiUrl, Object request, Class<T> responseClass, String operationName) {
-        log.info("调用企微{},参数:{}", operationName, JSON.toJSONString(request));
-        String result = HttpUtil.sendAuthPost(request, qwApiUrl);
-        log.info("企微{}响应:{}", operationName, result);
-
-        if (StringUtils.isEmpty(result)) {
-            throw new CustomException("调用企微API失败,返回为空");
-        }
-
-        T response = JSON.parseObject(result, responseClass);
-
-        // 通过反射获取errcode(所有响应都有这个字段)
-        try {
-            java.lang.reflect.Field errcodeField = responseClass.getDeclaredField("errcode");
-            errcodeField.setAccessible(true);
-            Integer errcode = (Integer) errcodeField.get(response);
-
-            if (errcode != null && errcode != 0) {
-                java.lang.reflect.Field errmsgField = responseClass.getDeclaredField("errmsg");
-                errmsgField.setAccessible(true);
-                String errmsg = (String) errmsgField.get(response);
-                throw new CustomException("企微" + operationName + "失败:" + errmsg);
-            }
-        } catch (Exception e) {
-            if (e instanceof CustomException) {
-                throw (CustomException) e;
-            }
-            log.error("解析企微响应失败", e);
-        }
-
-        return response;
-    }
-
-    /**
-     * 生成范围描述
-     */
-    private String generateRangeDesc(QwAcquisitionAssistant assistant) {
-        StringBuilder desc = new StringBuilder();
-        if (assistant.getUserListParam() != null && !assistant.getUserListParam().isEmpty()) {
-            desc.append(assistant.getUserListParam().size()).append("名成员");
-        }
-        if (assistant.getDepartmentListParam() != null && !assistant.getDepartmentListParam().isEmpty()) {
-            if (desc.length() > 0) desc.append(" + ");
-            desc.append(assistant.getDepartmentListParam().size()).append("个部门");
-        }
-        return desc.length() > 0 ? desc.toString() : "未设置范围";
-    }
-
-    /**
-     * 生成scheme
-     */
-    private String generateScheme(String qwApiUrl) {
-        if (StringUtils.isNotEmpty(qwApiUrl)) {
-            try {
-                String encodedUrl = java.net.URLEncoder.encode(qwApiUrl, "UTF-8");
-                return "weixin://biz/ww/profile/" + encodedUrl;
-            } catch (Exception e) {
-                log.warn("生成scheme失败", e);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * 设置本地通用字段
-     */
-    private void setLocalFields(QwAcquisitionAssistant assistant, boolean isCreate) {
-        if (isCreate) {
-            assistant.setStatus(1);
-            assistant.setDelFlag("0");
-            assistant.setCreateTime(new Date());
-        } else {
-            assistant.setUpdateTime(new Date());
-        }
-        assistant.setSyncTime(DateUtils.getNowDate());
-        assistant.buildJsonFields();
-        assistant.setRangeDesc(generateRangeDesc(assistant));
-
-        if (StringUtils.isEmpty(assistant.getScheme())) {
-            assistant.setScheme(generateScheme(assistant.getUrl()));
-        }
-    }
-
-
-    // ==================== 列表相关方法 ====================
-
-    @Override
-    public AcquisitionListResponse getQwList(String corpid, String corpsecret, Integer limit, String cursor) {
-        AcquisitionListRequest request = new AcquisitionListRequest();
-        if (limit != null) {
-            request.setLimit(limit > 100 ? 100 : limit);
-        }
-        request.setCursor(cursor);
-
-        String qwApiUrl = buildApiUrl(corpid, corpsecret, QwApiConfig.listAcquisition);
-        return callQwApi(qwApiUrl, request, AcquisitionListResponse.class, "获取获客链接列表");
-    }
-
     /**
      * 查询企微-获客链接管理列表
      *
@@ -248,7 +83,7 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
      */
     @Override
     public List<QwAcquisitionAssistant> selectQwAcquisitionAssistantList(QwAcquisitionAssistant qwAcquisitionAssistant) {
-        List<QwAcquisitionAssistant> list = qwAcquisitionAssistantMapper.selectQwAcquisitionAssistantList(qwAcquisitionAssistant);
+        List<QwAcquisitionAssistant> list = acquisitionAssistantMapper.selectQwAcquisitionAssistantList(qwAcquisitionAssistant);
         if (CollectionUtils.isEmpty(list)) {
             return Collections.emptyList();
         }
@@ -260,6 +95,7 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public SendResultDetailDTO sendMessageAcquisition(String phone, Long qwAcquisitionId, SendMsgLogBo sendMsgLogBo) {
         log.info("发送获客链接短信,号码:{}", phone);
         CompanySmsTemp temp = smsTempService.selectCompanySmsTempByCode(SMS_LINK_TEMPLATE_CODE);
@@ -268,7 +104,7 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
             throw new CustomException("获客链接-未找到短信模板");
         }
         String originalContent = temp.getContent();
-        QwAcquisitionAssistant acquisitionAssistant = qwAcquisitionAssistantMapper.selectQwAcquisitionAssistantById(qwAcquisitionId);
+        QwAcquisitionAssistant acquisitionAssistant = acquisitionAssistantMapper.selectQwAcquisitionAssistantById(qwAcquisitionId);
         if (acquisitionAssistant == null){
             log.info("获客链接-未找到获客链接id:{}", qwAcquisitionId);
             throw new CustomException("获客链接-未找到获客链接信息");
@@ -281,6 +117,10 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
             R r = smsService.simpleSmsSend(phone, content, temp, SmsLogType.ACQUISITION_LINK, sendMsgLogBo);
 
             if (r != null && "200".equals(String.valueOf(r.get("code")))) {
+
+                //新增号码-链接生成记录
+                linkInfoService.buildQwAcquisitionLinkInfoAdd(acquisitionAssistant.getId(), phone, acquisitionAssistant.getUrl());
+
                 return new SendResultDetailDTO(true, null, null);
             } else {
                 String msg = r != null && r.get("msg") != null ? r.get("msg").toString() : "未知错误";
@@ -293,330 +133,94 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
         }
     }
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public String syncListFromQw(String corpid, String corpsecret) {
-        String cursor = null;
-        int totalCount = 0;
-        int successCount = 0;
-        int pageNum = 1;
+    // ==================== 获取详情方法 ====================
 
-        log.info("开始同步企微获客链接列表");
+    @Override
+    public AcquisitionAssistantDetailVO getDetailWithQw(String linkId) {
+        if (StringUtils.isEmpty(linkId)) {
+            throw new CustomException("链接ID不能为空");
+        }
+        // 1. 查询本地记录
+        QwAcquisitionAssistant query = new QwAcquisitionAssistant();
+        query.setLinkId(linkId);
+        List<QwAcquisitionAssistant> localRecords = acquisitionAssistantMapper.selectQwAcquisitionAssistantList(query);
+        QwAcquisitionAssistant localData = localRecords.isEmpty() ? null : localRecords.get(0);
 
-        do {
-            log.info("正在同步第{}页", pageNum);
-            AcquisitionListResponse listResponse = getQwList(corpid, corpsecret, 100, cursor);
+        // 2. 获取企业信息
+        String corpId = localData != null ? localData.getCorpId() : null;
+        QwCompany qwCompany = qwCompanyService.selectQwCompanyByCorpId(corpId);
+        if (qwCompany == null) {
+            throw new CustomException("未找到企业配置信息");
+        }
 
-            List<String> linkIdList = listResponse.getLinkIdList();
-            if (linkIdList == null || linkIdList.isEmpty()) {
-                break;
-            }
+        // 3. 调用企微API获取详情
+        AcquisitionGetResponse qwDetail = null;
+        try {
+            AcquisitionGetRequest request = new AcquisitionGetRequest();
+            request.setLinkId(linkId);
 
-            totalCount += linkIdList.size();
+            String qwApiUrl = buildApiUrl(qwCompany.getCorpId(), qwCompany.getOpenSecret(), QwApiConfig.getAcquisition);
+            qwDetail = callQwApi(qwApiUrl, request, AcquisitionGetResponse.class, "获取获客链接详情");
+        } catch (Exception e) {
+            log.error("调用企微API失败", e);
+            // 如果API调用失败,就返回本地数据
+        }
+        // 4. 构建VO对象
+        return buildDetailVO(localData, qwDetail, qwCompany, linkId);
+    }
 
-            for (String linkId : linkIdList) {
-                try {
-                    AcquisitionAssistantDetailVO detail = getDetailWithQw(linkId);
-                    QwAcquisitionAssistant assistant = convertToLocal(linkId, detail);
+    // ==================== 新增方法 ====================
 
-                    QwAcquisitionAssistant existData = qwAcquisitionAssistantMapper.selectQwAcquisitionAssistantByLinkId(linkId);
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public QwAcquisitionAssistant createWithQw(String corpid, String corpsecret, QwAcquisitionAssistant assistant) {
+        // 参数校验
+        if (StringUtils.isEmpty(assistant.getLinkName())) {
+            throw new CustomException("链接名称不能为空");
+        }
+        if (assistant.getLinkName().length() > 30) {
+            throw new CustomException("链接名称不能超过30个字符");
+        }
 
-                    if (existData != null) {
-                        assistant.setId(existData.getId());
-                        setLocalFields(assistant, false);
-                        qwAcquisitionAssistantMapper.updateQwAcquisitionAssistant(assistant);
-                    } else {
-                        setLocalFields(assistant, true);
-                        assistant.setCorpId(corpid);// 对于新增的获客链接默认corpId为当前传入的corpId
-                        qwAcquisitionAssistantMapper.insertQwAcquisitionAssistant(assistant);
-                    }
+        // 构建请求
+        JSONObject request = new JSONObject();  // 改用JSONObject以便灵活构建
+        request.put("link_name", assistant.getLinkName());
+        request.put("skip_verify", Boolean.parseBoolean(assistant.getSkipVerify()));
+        //request.put("mark_source", assistant.getMarkSource());
 
-                    successCount++;
-                } catch (Exception e) {
-                    log.error("同步链接详情失败,linkId:{}", linkId, e);
-                }
-            }
+        // 构建range对象 - 即使没有值也要传空对象
+        JSONObject range = new JSONObject();
 
-            cursor = listResponse.getNextCursor();
-            pageNum++;
+        // 如果有成员列表
+        if (assistant.getUserListParam() != null && !assistant.getUserListParam().isEmpty()) {
+            range.put("user_list", assistant.getUserListParam());
+        }
 
-        } while (StringUtils.isNotEmpty(cursor));
+        // 如果有部门列表
+        if (assistant.getDepartmentListParam() != null && !assistant.getDepartmentListParam().isEmpty()) {
+            range.put("department_list", assistant.getDepartmentListParam());
+        }
 
-        String resultMsg = String.format("同步完成,总链接数:%d,成功同步:%d", totalCount, successCount);
-        log.info(resultMsg);
-        return resultMsg;
-    }
-    /**
-     * 将企微详情转换为本地实体
-     */
-    private QwAcquisitionAssistant convertToLocal(String linkId, AcquisitionAssistantDetailVO detail) {
-        QwAcquisitionAssistant assistant = new QwAcquisitionAssistant();
-        assistant.setLinkId(linkId);
+        // 无论是否有值,都添加range字段
+        request.put("range", range);
 
-        assistant.setLinkName(detail.getLinkName());
-        assistant.setUrl(detail.getUrl());
-        assistant.setQwCreateTime(DateUtils.getNowDate());
-        assistant.setSkipVerify(detail.getSkipVerify());
-        //assistant.setMarkSource(detail.getLink().getMarkSource());
-        assistant.setUserList(JSON.toJSONString(detail.getUserList()));
-        assistant.setDepartmentList(JSON.toJSONString(detail.getDepartmentList()));
+        // 构建priority_option - 可选
+        if (assistant.getPriorityType() != null && assistant.getPriorityType() > 0) {
+            JSONObject priorityOption = new JSONObject();
+            priorityOption.put("priority_type", assistant.getPriorityType());
 
-        assistant.setPriorityType(detail.getPriorityType());
-        assistant.setPriorityUserList(JSON.toJSONString(detail.getPriorityUserList()));
+            // 如果priority_type为2,需要指定priority_userid_list
+            if (assistant.getPriorityType() == 2) {
+                if (assistant.getPriorityUserListParam() == null || assistant.getPriorityUserListParam().isEmpty()) {
+                    throw new CustomException("优先分配类型为指定范围内时,优先分配成员不能为空");
+                }
+                priorityOption.put("priority_userid_list", assistant.getPriorityUserListParam());
+            }
 
-        return assistant;
-    }
-
-    /**
-     * 构建统一的VO对象
-     */
-    private AcquisitionAssistantDetailVO buildDetailVO(QwAcquisitionAssistant localData,
-                                                       AcquisitionGetResponse qwDetail,
-                                                       QwCompany qwCompany,
-                                                       String linkId) {
-        AcquisitionAssistantDetailVO vo = new AcquisitionAssistantDetailVO();
-
-        // 处理企微API返回的数据
-        if (qwDetail != null) {
-            // 基础信息
-            if (qwDetail.getLink() != null) {
-                AcquisitionGetResponse.LinkDetail link = qwDetail.getLink();
-                vo.setLinkId(linkId);
-                vo.setLinkName(link.getLinkName());
-                vo.setUrl(link.getUrl());
-                vo.setSkipVerify(link.getSkipVerify());
-                vo.setQwCreateTime(link.getCreateTime());
-            }
-
-            // 范围信息处理
-            if (qwDetail.getRange() != null) {
-                AcquisitionGetResponse.RangeInfo range = qwDetail.getRange();
-
-                // 将userList和departmentList组合成JSON字符串
-                Map<String, Object> rangeUserListMap = new HashMap<>();
-                Map<String, Object> rangeDepartmentListMap = new HashMap<>();
-                rangeUserListMap.put("userList", range.getUserList() != null ? range.getUserList() : new ArrayList<>());
-                rangeDepartmentListMap.put("departmentList", range.getDepartmentList() != null ? range.getDepartmentList() : new ArrayList<>());
-                vo.setUserList(JSON.toJSONString(rangeUserListMap));
-                vo.setDepartmentList(JSON.toJSONString(rangeDepartmentListMap));
-
-                // 生成范围描述
-                vo.setRangeDesc(buildRangeDesc(range));
-            }
-
-            // 优先分配信息处理
-            if (qwDetail.getPriorityOption() != null) {
-                AcquisitionGetResponse.PriorityInfo priority = qwDetail.getPriorityOption();
-                vo.setPriorityType(priority.getPriorityType() != null ? priority.getPriorityType() : 0);
-
-                // 将优先分配成员列表转换为JSON字符串
-                if (priority.getPriorityUseridList() != null) {
-                    vo.setPriorityUserList(JSON.toJSONString(priority.getPriorityUseridList()));
-                } else {
-                    vo.setPriorityUserList("[]");
-                }
-            }
-        }
-
-        // 补充本地数据
-        if (localData != null) {
-            // 如果企微数据中没有的字段,使用本地数据
-            if (vo.getLinkId() == null) vo.setLinkId(localData.getLinkId());
-            if (vo.getLinkName() == null) vo.setLinkName(localData.getLinkName());
-            if (vo.getUrl() == null) vo.setUrl(localData.getUrl());
-            if (vo.getSkipVerify() == null) vo.setSkipVerify(localData.getSkipVerify());
-            if (vo.getUserList() == null) vo.setUserList(localData.getUserList());
-            if (vo.getPriorityType() == null) vo.setPriorityType(localData.getPriorityType());
-            if (vo.getPriorityUserList() == null) vo.setPriorityUserList(localData.getPriorityUserList());
-            if (vo.getQwUserTableIdList() == null) vo.setQwUserTableIdList(localData.getQwUserTableIdList());
-            if (vo.getRangeDesc() == null) vo.setRangeDesc(localData.getRangeDesc());
-
-            // 本地记录特有的字段
-            vo.setId(localData.getId());
-            vo.setStatus(localData.getStatus());
-            vo.setSyncTime(localData.getSyncTime());
-            vo.setRemark(localData.getRemark());
-            vo.setCorpId(localData.getCorpId());
-            vo.setScheme(localData.getScheme());
-        } else {
-            // 纯企微数据,设置默认值
-            vo.setStatus(1);
-            vo.setCorpId(qwCompany.getCorpId());
-            if (vo.getPriorityType() == null) vo.setPriorityType(0);
-            if (vo.getPriorityUserList() == null) vo.setPriorityUserList("[]");
-            if (vo.getUserList() == null) vo.setUserList("{\"userList\":[]}");
-            if (vo.getDepartmentList() == null) vo.setDepartmentList("{\"departmentList\":[]}");
-            if (vo.getRangeDesc() == null) vo.setRangeDesc("全企业");
-        }
-
-        return vo;
-    }
-
-    /**
-     * 生成范围描述 - 确保方法签名正确
-     */
-    private String buildRangeDesc(AcquisitionGetResponse.RangeInfo range) {
-        if (range == null) {
-            return "全企业";
-        }
-
-        List<String> userList = range.getUserList();
-        List<Integer> departmentList = range.getDepartmentList();
-
-        StringBuilder desc = new StringBuilder();
-
-        if (userList != null && !userList.isEmpty()) {
-            desc.append("成员(").append(userList.size()).append("人)");
-        }
-
-        if (departmentList != null && !departmentList.isEmpty()) {
-            if (desc.length() > 0) {
-                desc.append("、");
-            }
-            desc.append("部门(").append(departmentList.size()).append("个)");
-        }
-
-        return desc.length() > 0 ? desc.toString() : "全企业";
-    }
-
-    /**
-     * 生成唯一的页面参数(针对2000条数据的优化版本)
-     */
-    private String generateUniquePageParam() {
-        // 获取所有已存在的pageParam(只取需要的字段)
-        List<String> existingParams = qwAcquisitionAssistantMapper.selectAllPageParams();
-        //使用Set,提高查找效率 O(1)
-        Set<String> paramSet = new HashSet<>(existingParams);
-
-        int maxAttempts = 10; // 设置最大尝试次数
-        int attempt = 0;
-
-        while (attempt < maxAttempts) {
-            // 生成6位随机码
-            String candidate = UniqueStringUtil.generateTimeBasedUnique(6);
-
-            // 使用Set的contains方法,O(1)复杂度
-            if (!paramSet.contains(candidate)) {
-                log.debug("生成页面参数成功: {}, 尝试次数: {}", candidate, attempt + 1);
-                return candidate;
-            }
-
-            attempt++;
-            log.debug("页面参数 {} 已存在,重新生成,第{}次尝试", candidate, attempt);
-        }
-
-        // 如果多次尝试都失败,使用+1随机数方案
-        String finalParam = UniqueStringUtil.generateTimeBasedUnique(7);
-        log.warn("多次尝试后使用7位参数: {}", finalParam);
-        return finalParam;
-    }
-
-    // ==================== 获取详情方法 ====================
-
-    @Override
-    public AcquisitionAssistantDetailVO getDetailWithQw(String linkId) {
-        if (StringUtils.isEmpty(linkId)) {
-            throw new CustomException("链接ID不能为空");
-        }
-        // 1. 查询本地记录
-        QwAcquisitionAssistant query = new QwAcquisitionAssistant();
-        query.setLinkId(linkId);
-        List<QwAcquisitionAssistant> localRecords = qwAcquisitionAssistantMapper.selectQwAcquisitionAssistantList(query);
-        QwAcquisitionAssistant localData = localRecords.isEmpty() ? null : localRecords.get(0);
-
-        // 2. 获取企业信息
-        String corpId = localData != null ? localData.getCorpId() : null;
-        QwCompany qwCompany = qwCompanyService.selectQwCompanyByCorpId(corpId);
-        if (qwCompany == null) {
-            throw new CustomException("未找到企业配置信息");
-        }
-
-        // 3. 调用企微API获取详情
-        AcquisitionGetResponse qwDetail = null;
-        try {
-            AcquisitionGetRequest request = new AcquisitionGetRequest();
-            request.setLinkId(linkId);
-
-            String qwApiUrl = buildApiUrl(qwCompany.getCorpId(), qwCompany.getOpenSecret(), QwApiConfig.getAcquisition);
-            qwDetail = callQwApi(qwApiUrl, request, AcquisitionGetResponse.class, "获取获客链接详情");
-        } catch (Exception e) {
-            log.error("调用企微API失败", e);
-            // 如果API调用失败,就返回本地数据
-        }
-        // 4. 构建VO对象
-        return buildDetailVO(localData, qwDetail, qwCompany, linkId);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void syncDetailToLocal(String corpid, String corpsecret, String linkId) {
-        AcquisitionAssistantDetailVO detail = getDetailWithQw(linkId);
-        QwAcquisitionAssistant assistant = convertToLocal(linkId, detail);
-
-        QwAcquisitionAssistant existData = qwAcquisitionAssistantMapper.selectQwAcquisitionAssistantByLinkId(linkId);
-
-        if (existData != null) {
-            assistant.setId(existData.getId());
-            setLocalFields(assistant, false);
-            qwAcquisitionAssistantMapper.updateQwAcquisitionAssistant(assistant);
-        } else {
-            setLocalFields(assistant, true);
-            qwAcquisitionAssistantMapper.insertQwAcquisitionAssistant(assistant);
-        }
-    }
-
-    // ==================== 新增方法 ====================
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public QwAcquisitionAssistant createWithQw(String corpid, String corpsecret, QwAcquisitionAssistant assistant) {
-        // 参数校验
-        if (StringUtils.isEmpty(assistant.getLinkName())) {
-            throw new CustomException("链接名称不能为空");
-        }
-        if (assistant.getLinkName().length() > 30) {
-            throw new CustomException("链接名称不能超过30个字符");
-        }
-
-        // 构建请求
-        JSONObject request = new JSONObject();  // 改用JSONObject以便灵活构建
-        request.put("link_name", assistant.getLinkName());
-        request.put("skip_verify", Boolean.parseBoolean(assistant.getSkipVerify()));
-        //request.put("mark_source", assistant.getMarkSource());
-
-        // 构建range对象 - 即使没有值也要传空对象
-        JSONObject range = new JSONObject();
-
-        // 如果有成员列表
-        if (assistant.getUserListParam() != null && !assistant.getUserListParam().isEmpty()) {
-            range.put("user_list", assistant.getUserListParam());
-        }
-
-        // 如果有部门列表
-        if (assistant.getDepartmentListParam() != null && !assistant.getDepartmentListParam().isEmpty()) {
-            range.put("department_list", assistant.getDepartmentListParam());
-        }
-
-        // 无论是否有值,都添加range字段
-        request.put("range", range);
-
-        // 构建priority_option - 可选
-        if (assistant.getPriorityType() != null && assistant.getPriorityType() > 0) {
-            JSONObject priorityOption = new JSONObject();
-            priorityOption.put("priority_type", assistant.getPriorityType());
-
-            // 如果priority_type为2,需要指定priority_userid_list
-            if (assistant.getPriorityType() == 2) {
-                if (assistant.getPriorityUserListParam() == null || assistant.getPriorityUserListParam().isEmpty()) {
-                    throw new CustomException("优先分配类型为指定范围内时,优先分配成员不能为空");
-                }
-                priorityOption.put("priority_userid_list", assistant.getPriorityUserListParam());
-            }
-
-            request.put("priority_option", priorityOption);
-        }
-        // 调用企微API
-        String qwApiUrl = buildApiUrl(corpid, corpsecret, QwApiConfig.createAcquisition);
+            request.put("priority_option", priorityOption);
+        }
+        // 调用企微API
+        String qwApiUrl = buildApiUrl(corpid, corpsecret, QwApiConfig.createAcquisition);
 
         AcquisitionCreateResponse response = callQwApi(qwApiUrl, request, AcquisitionCreateResponse.class, "创建获客链接");
         if (response.getLink() == null || StringUtils.isEmpty(response.getLink().getUrl())) {
@@ -643,7 +247,7 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
 
         // 设置本地字段并保存
         setLocalFields(assistant, true);
-        qwAcquisitionAssistantMapper.insertQwAcquisitionAssistant(assistant);
+        acquisitionAssistantMapper.insertQwAcquisitionAssistant(assistant);
 
         // ========== 缓存URL,便于后续通过pageParam访问 ==========
         try {
@@ -672,7 +276,7 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
         }
 
         // 查询本地是否存在
-        QwAcquisitionAssistant existAssistant = qwAcquisitionAssistantMapper.selectQwAcquisitionAssistantById(assistant.getId());
+        QwAcquisitionAssistant existAssistant = acquisitionAssistantMapper.selectQwAcquisitionAssistantById(assistant.getId());
         if (existAssistant == null) {
             throw new CustomException("获客链接不存在");
         }
@@ -704,7 +308,7 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
 
         assistant.setPageParam(newPageParam);
 
-        int rows = qwAcquisitionAssistantMapper.updateQwAcquisitionAssistant(assistant);
+        int rows = acquisitionAssistantMapper.updateQwAcquisitionAssistant(assistant);
         if (rows <= 0) {
             throw new CustomException("本地数据更新失败");
         }
@@ -726,7 +330,7 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
         }
 
         // 查询本地是否存在(获取完整的记录,包括pageParam)
-        QwAcquisitionAssistant existAssistant = qwAcquisitionAssistantMapper.selectQwAcquisitionAssistantById(assistant.getId());
+        QwAcquisitionAssistant existAssistant = acquisitionAssistantMapper.selectQwAcquisitionAssistantById(assistant.getId());
         if (existAssistant == null) {
             throw new CustomException("获客链接不存在");
         }
@@ -756,7 +360,12 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
         }
 
         // 删除本地记录
-        int rows = qwAcquisitionAssistantMapper.deleteQwAcquisitionAssistantById(assistant.getId());
+        int rows = acquisitionAssistantMapper.deleteQwAcquisitionAssistantById(assistant.getId());
+
+        //删除新增链接记录
+        Long[] qwAcquisitionAssistantIds = {assistant.getId()};
+        linkInfoService.deleteQwAcquisitionLinkInfoByQwAcquisitionAssistantIds(qwAcquisitionAssistantIds);
+
         if (rows <= 0) {
             throw new CustomException("本地数据删除失败");
         }
@@ -770,7 +379,7 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
 
     @Override
     public QwAcquisitionAssistant selectQwAcquisitionAssistantById(Long id) {
-        QwAcquisitionAssistant assistant = qwAcquisitionAssistantMapper.selectQwAcquisitionAssistantById(id);
+        QwAcquisitionAssistant assistant = acquisitionAssistantMapper.selectQwAcquisitionAssistantById(id);
         if (assistant != null) {
             assistant.parseJsonFields();
         }
@@ -798,7 +407,7 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
         }
 
         // 缓存中没有,查询数据库
-        friendUrl = qwAcquisitionAssistantMapper.selectQwAcquisitionUrlByPageParam(pageParam);
+        friendUrl = acquisitionAssistantMapper.selectQwAcquisitionUrlByPageParam(pageParam);
 
         // 缓存处理(包括空值缓存)
         if (friendUrl == null) {
@@ -815,4 +424,301 @@ public class QwAcquisitionAssistantServiceImpl implements IQwAcquisitionAssistan
 
         return friendUrl;
     }
+
+    /**
+     * 获取access_token并返回完整URL
+     */
+    private String buildApiUrl(String corpid, String corpsecret, String apiPath) {
+        WeixinKfTokenVO token = getAccessToken(corpid, corpsecret);
+
+        if (token == null || StringUtils.isEmpty(token.getAccess_token())) {
+            log.error("获取access_token失败, corpid:{}", corpid);
+            throw new CustomException("获客链接管理-获取企业微信access_token失败");
+        }
+
+        return apiPath + "?access_token=" + token.getAccess_token();
+    }
+
+    /**
+     * 获取access_token(使用实际有效期)
+     */
+    private WeixinKfTokenVO getAccessToken(String corpid, String corpsecret) {
+        String key = QW_ACQUISITION_KEY_PREFIX + corpid;
+
+        // 1. 先从缓存获取
+        WeixinKfTokenVO token = null;
+        try {
+            Object cacheObj = redisCache.getCacheObject(key);
+            if (cacheObj instanceof WeixinKfTokenVO) {
+                token = (WeixinKfTokenVO) cacheObj;
+                log.debug("从缓存获取token成功, corpid:{}", corpid);
+            }
+        } catch (Exception e) {
+            log.warn("从缓存获取token异常, 将重新获取, corpid:{}", corpid);
+        }
+
+        // 2. 缓存中没有,则重新获取
+        if (token == null) {
+            log.info("缓存中没有token,重新获取, corpid:{}", corpid);
+            try {
+                // 获取新token
+                token = weixinKfService.getToken(corpid, corpsecret);
+
+                // 存入缓存,使用企业微信返回的实际有效期
+                if (token != null && StringUtils.isNotEmpty(token.getAccess_token())) {
+                    Integer expiresIn = token.getExpires_in();
+                    if (expiresIn == null) {
+                        // 如果返回中没有expires_in,默认2小时
+                        expiresIn = 7200;
+                        log.warn("token返回中没有expires_in字段,使用默认值7200秒");
+                    }
+
+                    // 实际缓存时间比有效期稍短(提前5分钟过期),避免边界问题
+                    Integer cacheExpire = expiresIn - 300; // 提前5分钟
+                    if (cacheExpire <= 0) {
+                        cacheExpire = expiresIn;
+                    }
+
+                    redisCache.setCacheObject(key, token, cacheExpire, TimeUnit.SECONDS);
+                    log.info("token缓存成功, corpid:{}, 有效期:{}秒, 缓存时间:{}秒",
+                            corpid, expiresIn, cacheExpire);
+                }
+            } catch (Exception e) {
+                log.error("获取token失败, corpid:{}, error:{}", corpid, e.getMessage());
+                return null;
+            }
+        }
+
+        return token;
+    }
+
+    /**
+     * 调用企微API并统一处理返回结果
+     */
+    private <T> T callQwApi(String qwApiUrl, Object request, Class<T> responseClass, String operationName) {
+        log.info("调用企微{},参数:{}", operationName, JSON.toJSONString(request));
+        String result = HttpUtil.sendAuthPost(request, qwApiUrl);
+        log.info("企微{}响应:{}", operationName, result);
+
+        if (StringUtils.isEmpty(result)) {
+            throw new CustomException("调用企微API失败,返回为空");
+        }
+
+        T response = JSON.parseObject(result, responseClass);
+
+        // 通过反射获取errcode(所有响应都有这个字段)
+        try {
+            java.lang.reflect.Field errcodeField = responseClass.getDeclaredField("errcode");
+            errcodeField.setAccessible(true);
+            Integer errcode = (Integer) errcodeField.get(response);
+
+            if (errcode != null && errcode != 0) {
+                java.lang.reflect.Field errmsgField = responseClass.getDeclaredField("errmsg");
+                errmsgField.setAccessible(true);
+                String errmsg = (String) errmsgField.get(response);
+                throw new CustomException("企微" + operationName + "失败:" + errmsg);
+            }
+        } catch (Exception e) {
+            if (e instanceof CustomException) {
+                throw (CustomException) e;
+            }
+            log.error("解析企微响应失败", e);
+        }
+
+        return response;
+    }
+
+    /**
+     * 生成范围描述
+     */
+    private String generateRangeDesc(QwAcquisitionAssistant assistant) {
+        StringBuilder desc = new StringBuilder();
+        if (assistant.getUserListParam() != null && !assistant.getUserListParam().isEmpty()) {
+            desc.append(assistant.getUserListParam().size()).append("名成员");
+        }
+        if (assistant.getDepartmentListParam() != null && !assistant.getDepartmentListParam().isEmpty()) {
+            if (desc.length() > 0) desc.append(" + ");
+            desc.append(assistant.getDepartmentListParam().size()).append("个部门");
+        }
+        return desc.length() > 0 ? desc.toString() : "未设置范围";
+    }
+
+    /**
+     * 生成scheme
+     */
+    private String generateScheme(String qwApiUrl) {
+        if (StringUtils.isNotEmpty(qwApiUrl)) {
+            try {
+                String encodedUrl = java.net.URLEncoder.encode(qwApiUrl, "UTF-8");
+                return "weixin://biz/ww/profile/" + encodedUrl;
+            } catch (Exception e) {
+                log.warn("生成scheme失败", e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 设置本地通用字段
+     */
+    private void setLocalFields(QwAcquisitionAssistant assistant, boolean isCreate) {
+        if (isCreate) {
+            assistant.setStatus(1);
+            assistant.setDelFlag("0");
+            assistant.setCreateTime(new Date());
+        } else {
+            assistant.setUpdateTime(new Date());
+        }
+        assistant.setSyncTime(DateUtils.getNowDate());
+        assistant.buildJsonFields();
+        assistant.setRangeDesc(generateRangeDesc(assistant));
+
+        if (StringUtils.isEmpty(assistant.getScheme())) {
+            assistant.setScheme(generateScheme(assistant.getUrl()));
+        }
+    }
+
+    /**
+     * 构建统一的VO对象
+     */
+    private AcquisitionAssistantDetailVO buildDetailVO(QwAcquisitionAssistant localData,
+                                                       AcquisitionGetResponse qwDetail,
+                                                       QwCompany qwCompany,
+                                                       String linkId) {
+        AcquisitionAssistantDetailVO vo = new AcquisitionAssistantDetailVO();
+
+        // 处理企微API返回的数据
+        if (qwDetail != null) {
+            // 基础信息
+            if (qwDetail.getLink() != null) {
+                AcquisitionGetResponse.LinkDetail link = qwDetail.getLink();
+                vo.setLinkId(linkId);
+                vo.setLinkName(link.getLinkName());
+                vo.setUrl(link.getUrl());
+                vo.setSkipVerify(link.getSkipVerify());
+                vo.setQwCreateTime(link.getCreateTime());
+            }
+
+            // 范围信息处理
+            if (qwDetail.getRange() != null) {
+                AcquisitionGetResponse.RangeInfo range = qwDetail.getRange();
+
+                // 将userList和departmentList组合成JSON字符串
+                Map<String, Object> rangeUserListMap = new HashMap<>();
+                Map<String, Object> rangeDepartmentListMap = new HashMap<>();
+                rangeUserListMap.put("userList", range.getUserList() != null ? range.getUserList() : new ArrayList<>());
+                rangeDepartmentListMap.put("departmentList", range.getDepartmentList() != null ? range.getDepartmentList() : new ArrayList<>());
+                vo.setUserList(JSON.toJSONString(rangeUserListMap));
+                vo.setDepartmentList(JSON.toJSONString(rangeDepartmentListMap));
+
+                // 生成范围描述
+                vo.setRangeDesc(buildRangeDesc(range));
+            }
+
+            // 优先分配信息处理
+            if (qwDetail.getPriorityOption() != null) {
+                AcquisitionGetResponse.PriorityInfo priority = qwDetail.getPriorityOption();
+                vo.setPriorityType(priority.getPriorityType() != null ? priority.getPriorityType() : 0);
+
+                // 将优先分配成员列表转换为JSON字符串
+                if (priority.getPriorityUseridList() != null) {
+                    vo.setPriorityUserList(JSON.toJSONString(priority.getPriorityUseridList()));
+                } else {
+                    vo.setPriorityUserList("[]");
+                }
+            }
+        }
+
+        // 补充本地数据
+        if (localData != null) {
+            // 如果企微数据中没有的字段,使用本地数据
+            if (vo.getLinkId() == null) vo.setLinkId(localData.getLinkId());
+            if (vo.getLinkName() == null) vo.setLinkName(localData.getLinkName());
+            if (vo.getUrl() == null) vo.setUrl(localData.getUrl());
+            if (vo.getSkipVerify() == null) vo.setSkipVerify(localData.getSkipVerify());
+            if (vo.getUserList() == null) vo.setUserList(localData.getUserList());
+            if (vo.getPriorityType() == null) vo.setPriorityType(localData.getPriorityType());
+            if (vo.getPriorityUserList() == null) vo.setPriorityUserList(localData.getPriorityUserList());
+            if (vo.getQwUserTableIdList() == null) vo.setQwUserTableIdList(localData.getQwUserTableIdList());
+            if (vo.getRangeDesc() == null) vo.setRangeDesc(localData.getRangeDesc());
+
+            // 本地记录特有的字段
+            vo.setId(localData.getId());
+            vo.setStatus(localData.getStatus());
+            vo.setSyncTime(localData.getSyncTime());
+            vo.setRemark(localData.getRemark());
+            vo.setCorpId(localData.getCorpId());
+            vo.setScheme(localData.getScheme());
+        } else {
+            // 纯企微数据,设置默认值
+            vo.setStatus(1);
+            vo.setCorpId(qwCompany.getCorpId());
+            if (vo.getPriorityType() == null) vo.setPriorityType(0);
+            if (vo.getPriorityUserList() == null) vo.setPriorityUserList("[]");
+            if (vo.getUserList() == null) vo.setUserList("{\"userList\":[]}");
+            if (vo.getDepartmentList() == null) vo.setDepartmentList("{\"departmentList\":[]}");
+            if (vo.getRangeDesc() == null) vo.setRangeDesc("全企业");
+        }
+
+        return vo;
+    }
+
+    /**
+     * 生成范围描述 - 确保方法签名正确
+     */
+    private String buildRangeDesc(AcquisitionGetResponse.RangeInfo range) {
+        if (range == null) {
+            return "全企业";
+        }
+
+        List<String> userList = range.getUserList();
+        List<Integer> departmentList = range.getDepartmentList();
+
+        StringBuilder desc = new StringBuilder();
+
+        if (userList != null && !userList.isEmpty()) {
+            desc.append("成员(").append(userList.size()).append("人)");
+        }
+
+        if (departmentList != null && !departmentList.isEmpty()) {
+            if (desc.length() > 0) {
+                desc.append("、");
+            }
+            desc.append("部门(").append(departmentList.size()).append("个)");
+        }
+
+        return desc.length() > 0 ? desc.toString() : "全企业";
+    }
+
+    /**
+     * 生成唯一的页面参数(针对2000条数据的优化版本)
+     */
+    private String generateUniquePageParam() {
+        // 获取所有已存在的pageParam(只取需要的字段)
+        List<String> existingParams = acquisitionAssistantMapper.selectAllPageParams();
+        //使用Set,提高查找效率 O(1)
+        Set<String> paramSet = new HashSet<>(existingParams);
+
+        int maxAttempts = 10; // 设置最大尝试次数
+        int attempt = 0;
+
+        while (attempt < maxAttempts) {
+            // 生成6位随机码
+            String candidate = UniqueStringUtil.generateTimeBasedUnique(6);
+
+            // 使用Set的contains方法,O(1)复杂度
+            if (!paramSet.contains(candidate)) {
+                log.debug("生成页面参数成功: {}, 尝试次数: {}", candidate, attempt + 1);
+                return candidate;
+            }
+
+            attempt++;
+            log.debug("页面参数 {} 已存在,重新生成,第{}次尝试", candidate, attempt);
+        }
+
+        // 如果多次尝试都失败,使用+1随机数方案
+        String finalParam = UniqueStringUtil.generateTimeBasedUnique(7);
+        log.warn("多次尝试后使用7位参数: {}", finalParam);
+        return finalParam;
+    }
 }

+ 123 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwAcquisitionLinkInfoServiceImpl.java

@@ -0,0 +1,123 @@
+package com.fs.qw.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.qw.mapper.QwAcquisitionLinkInfoMapper;
+import com.fs.qw.domain.QwAcquisitionLinkInfo;
+import com.fs.qw.service.IQwAcquisitionLinkInfoService;
+
+import static com.fs.his.utils.PhoneUtil.encryptPhone;
+
+/**
+ * 获客链接-号码链接生成记录Service业务层处理
+ *
+ * @author fs
+ * @date 2026-03-27
+ */
+@Service
+public class QwAcquisitionLinkInfoServiceImpl implements IQwAcquisitionLinkInfoService
+{
+    @Autowired
+    private QwAcquisitionLinkInfoMapper qwAcquisitionLinkInfoMapper;
+
+    //组装完整链接后缀(这个后面拼接加密后的手机字符串)
+    private static final String  LINK_SUFFIX = "?customer_channel=up:";
+
+    /**
+     * 查询获客链接-号码链接生成记录
+     *
+     * @param id 获客链接-号码链接生成记录主键
+     * @return 获客链接-号码链接生成记录
+     */
+    @Override
+    public QwAcquisitionLinkInfo selectQwAcquisitionLinkInfoById(Long id)
+    {
+        return qwAcquisitionLinkInfoMapper.selectQwAcquisitionLinkInfoById(id);
+    }
+
+    /**
+     * 查询获客链接-号码链接生成记录列表
+     *
+     * @param qwAcquisitionLinkInfo 获客链接-号码链接生成记录
+     * @return 获客链接-号码链接生成记录
+     */
+    @Override
+    public List<QwAcquisitionLinkInfo> selectQwAcquisitionLinkInfoList(QwAcquisitionLinkInfo qwAcquisitionLinkInfo)
+    {
+        return qwAcquisitionLinkInfoMapper.selectQwAcquisitionLinkInfoList(qwAcquisitionLinkInfo);
+    }
+
+    /**
+     * 新增获客链接-号码链接生成记录
+     *
+     * @param qwAcquisitionLinkInfo 获客链接-号码链接生成记录
+     * @return 结果
+     */
+    @Override
+    public int insertQwAcquisitionLinkInfo(QwAcquisitionLinkInfo qwAcquisitionLinkInfo)
+    {
+        return qwAcquisitionLinkInfoMapper.insertQwAcquisitionLinkInfo(qwAcquisitionLinkInfo);
+    }
+
+    /**
+     * 修改获客链接-号码链接生成记录
+     *
+     * @param qwAcquisitionLinkInfo 获客链接-号码链接生成记录
+     * @return 结果
+     */
+    @Override
+    public int updateQwAcquisitionLinkInfo(QwAcquisitionLinkInfo qwAcquisitionLinkInfo)
+    {
+        return qwAcquisitionLinkInfoMapper.updateQwAcquisitionLinkInfo(qwAcquisitionLinkInfo);
+    }
+
+    /**
+     * 批量删除获客链接-号码链接生成记录
+     *
+     * @param ids 需要删除的获客链接-号码链接生成记录主键集合
+     * @return 结果
+     */
+    @Override
+    public int deleteQwAcquisitionLinkInfoByIds(Long[] ids)
+    {
+        return qwAcquisitionLinkInfoMapper.deleteQwAcquisitionLinkInfoByIds(ids);
+    }
+
+    /**
+     * 删除获客链接-号码链接生成记录信息
+     *
+     * @param qwAcquisitionAssistantIds 需要删除的获客链接管理主键ID集合
+     * @return 结果
+     */
+    @Override
+    public int deleteQwAcquisitionLinkInfoByQwAcquisitionAssistantIds(Long[] qwAcquisitionAssistantIds){
+        return qwAcquisitionLinkInfoMapper.deleteQwAcquisitionLinkInfoByQwAcquisitionAssistantIds(qwAcquisitionAssistantIds);
+    }
+
+    /**
+     * 删除获客链接-号码链接生成记录信息
+     *
+     * @param id 获客链接-号码链接生成记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteQwAcquisitionLinkInfoById(Long id)
+    {
+        return qwAcquisitionLinkInfoMapper.deleteQwAcquisitionLinkInfoById(id);
+    }
+
+    /**
+     * 添加链接生成记录
+     * */
+    public int buildQwAcquisitionLinkInfoAdd(Long qwAcquisitionAssistantId,String originalPhone,String originalLink){
+        QwAcquisitionLinkInfo qwAcquisitionLinkInfo=new QwAcquisitionLinkInfo();
+        qwAcquisitionLinkInfo.setQwAcquisitionAssistantId(qwAcquisitionAssistantId);
+        qwAcquisitionLinkInfo.setPhone(originalPhone);//这里存储原始手机号
+        //加密手机号
+        String phonePlus = encryptPhone(originalPhone);
+        String linkPlus=originalLink+LINK_SUFFIX+ phonePlus;
+        qwAcquisitionLinkInfo.setLink(linkPlus);
+        return qwAcquisitionLinkInfoMapper.insertQwAcquisitionLinkInfo(qwAcquisitionLinkInfo);
+    }
+}

+ 144 - 0
fs-service/src/main/resources/mapper/qw/QwAcquisitionLinkInfoMapper.xml

@@ -0,0 +1,144 @@
+<?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.qw.mapper.QwAcquisitionLinkInfoMapper">
+
+    <resultMap type="com.fs.qw.domain.QwAcquisitionLinkInfo" id="QwAcquisitionLinkInfoResult">
+        <result property="id" column="id"/>
+        <result property="qwAcquisitionAssistantId" column="qw_acquisition_assistant_id"/>
+        <result property="link" column="link"/>
+        <result property="phone" column="phone"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="remark" column="remark"/>
+    </resultMap>
+
+    <sql id="selectQwAcquisitionLinkInfoVo">
+        select id, qw_acquisition_assistant_id, link, phone, create_by, create_time, update_by, update_time, remark from qw_acquisition_link_info
+    </sql>
+
+    <select id="selectQwAcquisitionLinkInfoList" parameterType="com.fs.qw.domain.QwAcquisitionLinkInfo" resultMap="QwAcquisitionLinkInfoResult">
+        <include refid="selectQwAcquisitionLinkInfoVo"/>
+        <where>
+            <if test="qwAcquisitionAssistantId != null and qwAcquisitionAssistantId != ''">
+                and qw_acquisition_assistant_id = #{qwAcquisitionAssistantId}
+            </if>
+            <if test="link != null and link != ''">
+                and link like concat('%', #{link}, '%')
+            </if>
+            <if test="phone != null and phone != ''">
+                and phone like concat('%', #{phone}, '%')
+            </if>
+            <if test="remark != null and remark != ''">
+                and remark like concat('%', #{remark}, '%')
+            </if>
+        </where>
+    </select>
+
+    <select id="selectQwAcquisitionLinkInfoById" parameterType="Long" resultMap="QwAcquisitionLinkInfoResult">
+        <include refid="selectQwAcquisitionLinkInfoVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertQwAcquisitionLinkInfo" parameterType="com.fs.qw.domain.QwAcquisitionLinkInfo" useGeneratedKeys="true" keyProperty="id">
+        insert into qw_acquisition_link_info
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="qwAcquisitionAssistantId != null">
+                qw_acquisition_assistant_id,
+            </if>
+            <if test="link != null and link != ''">
+                link,
+            </if>
+            <if test="phone != null and phone != ''">
+                phone,
+            </if>
+            <if test="createBy != null and createBy != ''">
+                create_by,
+            </if>
+            <if test="createTime != null">
+                create_time,
+            </if>
+            <if test="updateBy != null and updateBy != ''">
+                update_by,
+            </if>
+            <if test="updateTime != null">
+                update_time,
+            </if>
+            <if test="remark != null and remark != ''">
+                remark,
+            </if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="qwAcquisitionAssistantId != null">
+                #{qwAcquisitionAssistantId},
+            </if>
+            <if test="link != null and link != ''">
+                #{link},
+            </if>
+            <if test="phone != null and phone != ''">
+                #{phone},
+            </if>
+            <if test="createBy != null and createBy != ''">
+                #{createBy},
+            </if>
+            <if test="createTime != null">
+                #{createTime},
+            </if>
+            <if test="updateBy != null and updateBy != ''">
+                #{updateBy},
+            </if>
+            <if test="updateTime != null">
+                #{updateTime},
+            </if>
+            <if test="remark != null and remark != ''">
+                #{remark},
+            </if>
+        </trim>
+    </insert>
+
+    <update id="updateQwAcquisitionLinkInfo" parameterType="com.fs.qw.domain.QwAcquisitionLinkInfo">
+        update qw_acquisition_link_info
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="qwAcquisitionAssistantId != null">
+                qw_acquisition_assistant_id = #{qwAcquisitionAssistantId},
+            </if>
+            <if test="link != null and link != ''">
+                link = #{link},
+            </if>
+            <if test="phone != null and phone != ''">
+                phone = #{phone},
+            </if>
+            <if test="updateBy != null and updateBy != ''">
+                update_by = #{updateBy},
+            </if>
+            <if test="updateTime != null">
+                update_time = #{updateTime},
+            </if>
+            <if test="remark != null and remark != ''">
+                remark = #{remark},
+            </if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteQwAcquisitionLinkInfoById" parameterType="Long">
+        delete from qw_acquisition_link_info where id = #{id}
+    </delete>
+
+    <delete id="deleteQwAcquisitionLinkInfoByIds" parameterType="String">
+        delete from qw_acquisition_link_info where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <delete id="deleteQwAcquisitionLinkInfoByQwAcquisitionAssistantIds" parameterType="String">
+        delete from qw_acquisition_link_info where qw_acquisition_assistant_id in
+        <foreach item="id" collection="qwAcquisitionAssistantIds" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>