Bladeren bron

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin 2 dagen geleden
bovenliggende
commit
6819c2cf2c
19 gewijzigde bestanden met toevoegingen van 359 en 57 verwijderingen
  1. 13 0
      fs-company/src/main/java/com/fs/company/controller/wx/controller/WxSopController.java
  2. 2 0
      fs-service/src/main/java/com/fs/ipad/vo/WxBaseVo.java
  3. 1 3
      fs-service/src/main/java/com/fs/wx/sop/domain/WxSopLogs.java
  4. 8 0
      fs-service/src/main/java/com/fs/wx/sop/mapper/WxSopLogsMapper.java
  5. 7 1
      fs-service/src/main/java/com/fs/wx/sop/mapper/WxSopUserInfoMapper.java
  6. 1 0
      fs-service/src/main/java/com/fs/wx/sop/mapper/WxSopUserMapper.java
  7. 13 0
      fs-service/src/main/java/com/fs/wx/sop/params/SendWxSopMsgParam.java
  8. 14 0
      fs-service/src/main/java/com/fs/wx/sop/service/IWxSopLogsService.java
  9. 0 3
      fs-service/src/main/java/com/fs/wx/sop/service/impl/WxSopExecuteServiceImpl.java
  10. 202 12
      fs-service/src/main/java/com/fs/wx/sop/service/impl/WxSopLogsServiceImpl.java
  11. 20 4
      fs-service/src/main/java/com/fs/wx/sop/service/impl/WxSopUserServiceImpl.java
  12. 8 0
      fs-service/src/main/java/com/fs/wx/sop/vo/WxSopLogsListVO.java
  13. 11 0
      fs-service/src/main/java/com/fs/wx/sop/vo/WxSopMsgVo.java
  14. 7 1
      fs-service/src/main/java/com/fs/wxwork/service/WxIpadService.java
  15. 20 6
      fs-service/src/main/resources/mapper/wx/WxSopLogsMapper.xml
  16. 4 4
      fs-service/src/main/resources/mapper/wx/WxSopUserInfoMapper.xml
  17. 1 3
      fs-service/src/main/resources/mapper/wx/WxSopUserMapper.xml
  18. 10 4
      fs-wx-ipad-task/src/main/java/com/fs/app/service/WxIpadSendServer.java
  19. 17 16
      fs-wx-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

+ 13 - 0
fs-company/src/main/java/com/fs/company/controller/wx/controller/WxSopController.java

@@ -14,11 +14,14 @@ 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.core.domain.R;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import com.fs.wx.sop.domain.WxSop;
+import com.fs.wx.sop.params.SendWxSopMsgParam;
+import com.fs.wx.sop.service.IWxSopLogsService;
 import com.fs.wx.sop.service.IWxSopService;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.common.core.page.TableDataInfo;
@@ -37,6 +40,8 @@ public class WxSopController extends BaseController
     private IWxSopService wxSopService;
     @Autowired
     private TokenService tokenService;
+    @Autowired
+    private IWxSopLogsService wxSopLogsService;
 
     /**
      * 查询个微SOP列表
@@ -107,4 +112,12 @@ public class WxSopController extends BaseController
     {
         return toAjax(wxSopService.deleteWxSopByIds(ids));
     }
+
+    /**
+     * 个微SOP一键群发
+     */
+    @PostMapping("/sendMsg")
+    public R sendWxSopMsg(@RequestBody SendWxSopMsgParam param) {
+        return wxSopLogsService.sendWxSopMsg(param);
+    }
 }

+ 2 - 0
fs-service/src/main/java/com/fs/ipad/vo/WxBaseVo.java

@@ -9,6 +9,7 @@ public class WxBaseVo {
 
     private Long id;
     private Long serverId;
+    private String userRemark;
     private String remark;
 
 
@@ -16,5 +17,6 @@ public class WxBaseVo {
         this.id = vo.getId();
         this.serverId = vo.getServerId();
         this.remark = vo.getRemark();
+        this.userRemark = vo.getUserRemark();
     }
 }

+ 1 - 3
fs-service/src/main/java/com/fs/wx/sop/domain/WxSopLogs.java

@@ -38,12 +38,10 @@ public class WxSopLogs extends BaseEntityTow {
     @Excel(name = "发送类型", readConverterExp = "字=典-wx_send_type")
     private Integer sendType;
 
-    private String sendTime;
+    private LocalDateTime sendTime;
 
     private String contentJson;
 
-    private Integer receivingStatus;
-
     /** 生成类型(0自动1手动) */
     @Excel(name = "生成类型(0自动1手动)")
     private Integer generateType;

+ 8 - 0
fs-service/src/main/java/com/fs/wx/sop/mapper/WxSopLogsMapper.java

@@ -23,6 +23,7 @@ public interface WxSopLogsMapper extends BaseMapper<WxSopLogs>{
      * @param id 个微发送记录主键
      * @return 个微发送记录
      */
+    @DataSource(DataSourceType.SOP)
     WxSopLogs selectWxSopLogsById(Long id);
 
     /**
@@ -31,6 +32,7 @@ public interface WxSopLogsMapper extends BaseMapper<WxSopLogs>{
      * @param wxSopLogs 个微发送记录
      * @return 个微发送记录集合
      */
+    @DataSource(DataSourceType.SOP)
     List<WxSopLogs> selectWxSopLogsList(WxSopLogs wxSopLogs);
 
     /**
@@ -39,6 +41,7 @@ public interface WxSopLogsMapper extends BaseMapper<WxSopLogs>{
      * @param param 查询参数
      * @return 执行记录集合
      */
+    @DataSource(DataSourceType.SOP)
     List<WxSopLogsListVO> selectWxSopLogsListBySopId(WxSopLogsParam param);
 
     /**
@@ -47,6 +50,7 @@ public interface WxSopLogsMapper extends BaseMapper<WxSopLogs>{
      * @param wxSopLogs 个微发送记录
      * @return 结果
      */
+    @DataSource(DataSourceType.SOP)
     int insertWxSopLogs(WxSopLogs wxSopLogs);
 
     /**
@@ -55,6 +59,7 @@ public interface WxSopLogsMapper extends BaseMapper<WxSopLogs>{
      * @param wxSopLogs 个微发送记录
      * @return 结果
      */
+    @DataSource(DataSourceType.SOP)
     int updateWxSopLogs(WxSopLogs wxSopLogs);
 
     /**
@@ -63,6 +68,7 @@ public interface WxSopLogsMapper extends BaseMapper<WxSopLogs>{
      * @param id 个微发送记录主键
      * @return 结果
      */
+    @DataSource(DataSourceType.SOP)
     int deleteWxSopLogsById(Long id);
 
     /**
@@ -71,10 +77,12 @@ public interface WxSopLogsMapper extends BaseMapper<WxSopLogs>{
      * @param ids 需要删除的数据主键集合
      * @return 结果
      */
+    @DataSource(DataSourceType.SOP)
     int deleteWxSopLogsByIds(Long[] ids);
 
     @DataSource(DataSourceType.SOP)
     void batchInsertWxSopLogs(List<WxSopLogs> logsToInsert);
 
+    @DataSource(DataSourceType.SOP)
     List<WxSopLogs> selectByWxId(@Param("id") Long id);
 }

+ 7 - 1
fs-service/src/main/java/com/fs/wx/sop/mapper/WxSopUserInfoMapper.java

@@ -12,7 +12,6 @@ import com.fs.wx.sop.domain.WxSopUserInfo;
  * @author 吴树波
  * @date 2026-02-24
  */
-@DataSource(DataSourceType.SOP)
 public interface WxSopUserInfoMapper extends BaseMapper<WxSopUserInfo>{
     /**
      * 查询个微营期详情
@@ -20,6 +19,7 @@ public interface WxSopUserInfoMapper extends BaseMapper<WxSopUserInfo>{
      * @param id 个微营期详情主键
      * @return 个微营期详情
      */
+    @DataSource(DataSourceType.SOP)
     WxSopUserInfo selectWxSopUserInfoById(Long id);
 
     /**
@@ -28,6 +28,7 @@ public interface WxSopUserInfoMapper extends BaseMapper<WxSopUserInfo>{
      * @param wxSopUserInfo 个微营期详情
      * @return 个微营期详情集合
      */
+    @DataSource(DataSourceType.SOP)
     List<WxSopUserInfo> selectWxSopUserInfoList(WxSopUserInfo wxSopUserInfo);
 
     /**
@@ -36,6 +37,7 @@ public interface WxSopUserInfoMapper extends BaseMapper<WxSopUserInfo>{
      * @param wxSopUserInfo 个微营期详情
      * @return 结果
      */
+    @DataSource(DataSourceType.SOP)
     int insertWxSopUserInfo(WxSopUserInfo wxSopUserInfo);
 
     /**
@@ -44,6 +46,7 @@ public interface WxSopUserInfoMapper extends BaseMapper<WxSopUserInfo>{
      * @param wxSopUserInfo 个微营期详情
      * @return 结果
      */
+    @DataSource(DataSourceType.SOP)
     int updateWxSopUserInfo(WxSopUserInfo wxSopUserInfo);
 
     /**
@@ -52,6 +55,7 @@ public interface WxSopUserInfoMapper extends BaseMapper<WxSopUserInfo>{
      * @param id 个微营期详情主键
      * @return 结果
      */
+    @DataSource(DataSourceType.SOP)
     int deleteWxSopUserInfoById(Long id);
 
     /**
@@ -60,6 +64,7 @@ public interface WxSopUserInfoMapper extends BaseMapper<WxSopUserInfo>{
      * @param ids 需要删除的数据主键集合
      * @return 结果
      */
+    @DataSource(DataSourceType.SOP)
     int deleteWxSopUserInfoByIds(Long[] ids);
 
     /**
@@ -68,5 +73,6 @@ public interface WxSopUserInfoMapper extends BaseMapper<WxSopUserInfo>{
      * @param wxSopUserInfo 个微营期详情
      * @return 个微营期详情
      */
+    @DataSource(DataSourceType.SOP)
     WxSopUserInfo selectWxSopUserInfoByCondition(WxSopUserInfo wxSopUserInfo);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/wx/sop/mapper/WxSopUserMapper.java

@@ -28,6 +28,7 @@ public interface WxSopUserMapper extends BaseMapper<WxSopUser>{
      * @param wxSopUser 个微营期
      * @return 个微营期集合
      */
+    @DataSource(DataSourceType.SOP)
     List<WxSopUser> selectWxSopUserList(WxSopUser wxSopUser);
 
     /**

+ 13 - 0
fs-service/src/main/java/com/fs/wx/sop/params/SendWxSopMsgParam.java

@@ -0,0 +1,13 @@
+package com.fs.wx.sop.params;
+
+import lombok.Data;
+
+@Data
+public class SendWxSopMsgParam {
+    /** 选中的SOP ID数组 */
+    private Long[] sopIds;
+    /** 消息内容JSON,格式: [{"contentType":"1","value":"文本内容"}] */
+    private String setting;
+    /** 发送时间 HH:mm 格式,不填默认立即发送 */
+    private String sendTime;
+}

+ 14 - 0
fs-service/src/main/java/com/fs/wx/sop/service/IWxSopLogsService.java

@@ -2,8 +2,12 @@ package com.fs.wx.sop.service;
 
 import java.util.List;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.wx.sop.domain.WxSopLogs;
+import com.fs.common.core.domain.R;
+import com.fs.wx.sop.params.SendWxSopMsgParam;
 import com.fs.wx.sop.params.WxSopLogsParam;
 import com.fs.wx.sop.vo.WxSopLogsListVO;
 
@@ -71,4 +75,14 @@ public interface IWxSopLogsService extends IService<WxSopLogs>{
     int deleteWxSopLogsById(Long id);
 
     void batchInsertQwSopLogs(List<WxSopLogs> logsToInsert);
+
+    /**
+     * 个微SOP一键群发
+     *
+     * @param param 群发参数
+     * @return 结果
+     */
+    R sendWxSopMsg(SendWxSopMsgParam param);
+
+    boolean updateMapper(WxSopLogs updateQwSop);
 }

+ 0 - 3
fs-service/src/main/java/com/fs/wx/sop/service/impl/WxSopExecuteServiceImpl.java

@@ -333,8 +333,6 @@ public class WxSopExecuteServiceImpl implements IWxSopExecuteService {
      * @param customerId 客户ID
      */
     @Override
-    @Transactional(rollbackFor = Exception.class)
-    @DataSource(DataSourceType.SOP)
     public void processCustomerTagsChange(Long customerId) {
         try {
             log.info("====== 开始处理客户标签变更,客户ID: {} ======", customerId);
@@ -418,7 +416,6 @@ public class WxSopExecuteServiceImpl implements IWxSopExecuteService {
      * @param customerId 客户ID
      * @return 营期成员记录列表
      */
-    @DataSource(DataSourceType.SOP)
     public List<WxSopUserInfo> querySopUserInfosByCustomer(Long sopId, Long customerId) {
         WxSopUserInfo queryParam = new WxSopUserInfo();
         queryParam.setSopId(sopId);

+ 202 - 12
fs-service/src/main/java/com/fs/wx/sop/service/impl/WxSopLogsServiceImpl.java

@@ -1,20 +1,34 @@
 package com.fs.wx.sop.service.impl;
 
-import java.util.List;
-
-import com.fs.common.annotation.DataScope;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.common.annotation.DataSource;
+import com.fs.common.core.domain.R;
 import com.fs.common.enums.DataSourceType;
 import com.fs.common.utils.DateUtils;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.fs.sop.domain.QwSopLogs;
+import com.fs.common.utils.PubFun;
+import com.fs.common.utils.date.DateUtil;
+import com.fs.company.domain.CompanyWxAccount;
+import com.fs.company.mapper.CompanyWxAccountMapper;
+import com.fs.crm.domain.CrmCustomer;
+import com.fs.crm.mapper.CrmCustomerMapper;
+import com.fs.wx.sop.domain.WxSopLogs;
+import com.fs.wx.sop.domain.WxSopUser;
+import com.fs.wx.sop.domain.WxSopUserInfo;
+import com.fs.wx.sop.mapper.WxSopLogsMapper;
+import com.fs.wx.sop.mapper.WxSopUserInfoMapper;
+import com.fs.wx.sop.mapper.WxSopUserMapper;
+import com.fs.wx.sop.params.SendWxSopMsgParam;
 import com.fs.wx.sop.params.WxSopLogsParam;
+import com.fs.wx.sop.service.IWxSopLogsService;
 import com.fs.wx.sop.vo.WxSopLogsListVO;
+import com.fs.wxcid.domain.WxContact;
+import com.fs.wxcid.mapper.WxContactMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.fs.wx.sop.mapper.WxSopLogsMapper;
-import com.fs.wx.sop.domain.WxSopLogs;
-import com.fs.wx.sop.service.IWxSopLogsService;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 个微发送记录Service业务层处理
@@ -27,6 +41,18 @@ public class WxSopLogsServiceImpl extends ServiceImpl<WxSopLogsMapper, WxSopLogs
     @Autowired
     private WxSopLogsMapper wxSopLogsMapper;
 
+    @Autowired
+    private WxSopUserMapper wxSopUserMapper;
+
+    @Autowired
+    private WxSopUserInfoMapper wxSopUserInfoMapper;
+    @Autowired
+    private CompanyWxAccountMapper companyWxAccountMapper;
+    @Autowired
+    private WxContactMapper wxContactMapper;
+    @Autowired
+    private CrmCustomerMapper crmCustomerMapper;
+
     /**
      * 查询个微发送记录
      *
@@ -114,14 +140,178 @@ public class WxSopLogsServiceImpl extends ServiceImpl<WxSopLogsMapper, WxSopLogs
      * @return 执行记录集合
      */
     @Override
-    @DataSource(DataSourceType.SOP)
-    public List<WxSopLogsListVO> selectWxSopLogsListBySopId(WxSopLogsParam param)
-    {
-        return baseMapper.selectWxSopLogsListBySopId(param);
+    public List<WxSopLogsListVO> selectWxSopLogsListBySopId(WxSopLogsParam param){
+        List<WxSopLogsListVO> list = baseMapper.selectWxSopLogsListBySopId(param);
+        List<Long> longs = PubFun.listToNewList(list, WxSopLogsListVO::getAccountId);
+        List<CompanyWxAccount> companyWxAccounts = companyWxAccountMapper.selectBatchIds(longs);
+        Map<Long, CompanyWxAccount> accountMap = PubFun.listToMapByGroupObject(companyWxAccounts, CompanyWxAccount::getId);
+        list.parallelStream().filter(e -> accountMap.containsKey(e.getAccountId())).forEach(e -> {
+           e.setAccountName(accountMap.get(e.getAccountId()).getWxNickName());
+        });
+        return list;
     }
 
+    @DataSource(DataSourceType.SOP)
     public void batchInsertQwSopLogs(List<WxSopLogs> logsToInsert) {
         if(logsToInsert == null || logsToInsert.isEmpty()) return;
         wxSopLogsMapper.batchInsertWxSopLogs(logsToInsert);
     }
+
+    /**
+     * 个微SOP一键群发
+     * 注意:不加 @DataSource(DataSourceType.SOP),因为需要跨库查询
+     * SOP相关Mapper方法自带 @DataSource(DataSourceType.SOP) 注解
+     * wx_contact、crm_customer 在主库,使用默认数据源
+     */
+    @Override
+    public R sendWxSopMsg(SendWxSopMsgParam param) {
+        if (param.getSopIds() == null || param.getSopIds().length == 0) {
+            return R.error("请选择要群发的SOP");
+        }
+
+        // 1. 解析发送时间:前端选了就用前端的,没选就用当前时间
+        String sendTimeStr;
+        if (param.getSendTime() != null && !param.getSendTime().isEmpty()) {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            sendTimeStr = sdf.format(new Date()) + " " + param.getSendTime() + ":00";
+        } else {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            sendTimeStr = sdf.format(new Date());
+        }
+
+        List<WxSopLogs> logsToInsert = new ArrayList<>();
+        // 收集所有联系人ID和客户ID,用于批量查询
+        List<Long> allContactIds = new ArrayList<>();
+        // 临时存储:sopUser + userInfos 的关系
+        List<Object[]> sopUserAndInfos = new ArrayList<>();
+
+        // 2. 遍历每个SOP,从SOP数据库查询营期和成员
+        for (Long sopId : param.getSopIds()) {
+            WxSopUser query = new WxSopUser();
+            query.setSopId(sopId);
+            // wxSopUserMapper.selectWxSopUserList 自带 @DataSource(DataSourceType.SOP)
+            List<WxSopUser> sopUsers = wxSopUserMapper.selectWxSopUserList(query);
+
+            if (sopUsers == null || sopUsers.isEmpty()) {
+                continue;
+            }
+
+            for (WxSopUser sopUser : sopUsers) {
+                WxSopUserInfo userInfoQuery = new WxSopUserInfo();
+                userInfoQuery.setSopUserId(sopUser.getId());
+                // wxSopUserInfoMapper.selectWxSopUserInfoList 自带 @DataSource(DataSourceType.SOP)
+                List<WxSopUserInfo> userInfos = wxSopUserInfoMapper.selectWxSopUserInfoList(userInfoQuery);
+
+                if (userInfos == null || userInfos.isEmpty()) {
+                    continue;
+                }
+
+                sopUserAndInfos.add(new Object[]{sopId, sopUser, userInfos});
+
+                for (WxSopUserInfo info : userInfos) {
+                    if (info.getWxContactId() != null) {
+                        allContactIds.add(info.getWxContactId());
+                    }
+                }
+            }
+        }
+
+        if (sopUserAndInfos.isEmpty()) {
+            return R.error("未找到可群发的营期成员");
+        }
+
+        // 3. 从主库批量查询 wx_contact 获取联系人昵称
+        Map<Long, WxContact> contactMap = new HashMap<>();
+        Map<Long, CrmCustomer> customerMap = new HashMap<>();
+        if (!allContactIds.isEmpty()) {
+            List<Long> uniqueContactIds = allContactIds.stream().distinct().collect(Collectors.toList());
+            List<WxContact> contacts = wxContactMapper.selectBatchIds(uniqueContactIds);
+            if (contacts != null) {
+                for (WxContact c : contacts) {
+                    contactMap.put(c.getId(), c);
+                }
+                // 4. 收集customerIds,从主库查询 crm_customer 获取客户标签
+                List<Long> customerIds = contacts.stream()
+                        .filter(c -> c.getCustomerId() != null)
+                        .map(WxContact::getCustomerId)
+                        .distinct()
+                        .collect(Collectors.toList());
+                if (!customerIds.isEmpty()) {
+                    List<CrmCustomer> customers = crmCustomerMapper.selectBatchIds(customerIds);
+                    if (customers != null) {
+                        for (CrmCustomer cust : customers) {
+                            customerMap.put(cust.getCustomerId(), cust);
+                        }
+                    }
+                }
+            }
+        }
+
+        // 5. 遍历构建发送记录,设置联系人昵称
+        // 同时收集需要更新标签的 userInfo
+        List<WxSopUserInfo> userInfosToUpdateTag = new ArrayList<>();
+        for (Object[] arr : sopUserAndInfos) {
+            Long sopId = (Long) arr[0];
+            WxSopUser sopUser = (WxSopUser) arr[1];
+            @SuppressWarnings("unchecked")
+            List<WxSopUserInfo> userInfos = (List<WxSopUserInfo>) arr[2];
+
+            for (WxSopUserInfo userInfo : userInfos) {
+                WxSopLogs log = new WxSopLogs();
+                log.setType(0);
+                log.setSopId(sopId);
+                log.setSopUserId(sopUser.getId());
+                log.setGenerateType(1);
+                log.setAccountId(sopUser.getAccountId());
+                log.setWxContactId(userInfo.getWxContactId());
+                log.setFsUserId(userInfo.getFsUserId());
+                log.setSendStatus(0);
+                log.setSendSort(30000000);
+                log.setContentJson(param.getSetting());
+                log.setSendTime(DateUtil.stringToLocalDateTime(sendTimeStr));
+                log.setCreateTime(new Date());
+
+                // 设置联系人昵称
+                WxContact contact = contactMap.get(userInfo.getWxContactId());
+                if (contact != null) {
+                    log.setWxContactName(contact.getNickName());
+
+                    // 更新 wx_sop_user_info 的标签(如果为空)
+                    if ((userInfo.getTagNames() == null || userInfo.getTagNames().isEmpty())
+                            && contact.getCustomerId() != null) {
+                        CrmCustomer customer = customerMap.get(contact.getCustomerId());
+                        if (customer != null && customer.getTags() != null && !customer.getTags().isEmpty()) {
+                            userInfo.setTagNames(customer.getTags());
+                            userInfosToUpdateTag.add(userInfo);
+                        }
+                    }
+                }
+
+                logsToInsert.add(log);
+            }
+        }
+
+        if (logsToInsert.isEmpty()) {
+            return R.error("未找到可群发的营期成员");
+        }
+
+        // 6. 更新 wx_sop_user_info 的标签信息(SOP数据库)
+        for (WxSopUserInfo info : userInfosToUpdateTag) {
+            WxSopUserInfo updateInfo = new WxSopUserInfo();
+            updateInfo.setId(info.getId());
+            updateInfo.setTagNames(info.getTagNames());
+            wxSopUserInfoMapper.updateWxSopUserInfo(updateInfo);
+        }
+
+        // 7. 批量插入发送记录到SOP数据库
+        batchInsertQwSopLogs(logsToInsert);
+
+        return R.ok("一键群发成功,共发送 " + logsToInsert.size() + " 条消息");
+    }
+
+    @Override
+    @DataSource(DataSourceType.SOP)
+    public boolean updateMapper(WxSopLogs updateQwSop) {
+        return updateById(updateQwSop);
+    }
 }

+ 20 - 4
fs-service/src/main/java/com/fs/wx/sop/service/impl/WxSopUserServiceImpl.java

@@ -1,11 +1,20 @@
 package com.fs.wx.sop.service.impl;
 
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.annotation.DataSource;
+import com.fs.common.core.domain.BaseEntityTow;
 import com.fs.common.enums.DataSourceType;
 import com.fs.common.utils.DateUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.PubFun;
+import com.fs.company.domain.CompanyWxAccount;
+import com.fs.company.mapper.CompanyWxAccountMapper;
+import com.fs.company.service.ICompanyWxAccountService;
+import lombok.AllArgsConstructor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.wx.sop.mapper.WxSopUserMapper;
@@ -19,8 +28,10 @@ import com.fs.wx.sop.service.IWxSopUserService;
  * @date 2026-02-24
  */
 @Service
+@AllArgsConstructor
 public class WxSopUserServiceImpl extends ServiceImpl<WxSopUserMapper, WxSopUser> implements IWxSopUserService {
 
+    private final CompanyWxAccountMapper companyWxAccountMapper;
     /**
      * 查询个微营期
      * 
@@ -41,10 +52,15 @@ public class WxSopUserServiceImpl extends ServiceImpl<WxSopUserMapper, WxSopUser
      * @return 个微营期
      */
     @Override
-    @DataSource(DataSourceType.SOP)
-    public List<WxSopUser> selectWxSopUserList(WxSopUser wxSopUser)
-    {
-        return baseMapper.selectWxSopUserList(wxSopUser);
+    public List<WxSopUser> selectWxSopUserList(WxSopUser wxSopUser){
+        List<WxSopUser> wxSopUsers = baseMapper.selectWxSopUserList(wxSopUser);
+        List<CompanyWxAccount> companyWxAccounts = companyWxAccountMapper.selectList(new QueryWrapper<CompanyWxAccount>().in("id", PubFun.listToNewList(wxSopUsers, WxSopUser::getAccountId)));
+        Map<Long, CompanyWxAccount> accountMap = PubFun.listToMapByGroupObject(companyWxAccounts, CompanyWxAccount::getId);
+        wxSopUsers.parallelStream().filter(e -> accountMap.containsKey(e.getAccountId())).forEach(e -> {
+            CompanyWxAccount companyWxAccount = accountMap.get(e.getAccountId());
+            e.setAccountName(companyWxAccount.getWxNickName());
+        });
+        return wxSopUsers;
     }
 
     /**

+ 8 - 0
fs-service/src/main/java/com/fs/wx/sop/vo/WxSopLogsListVO.java

@@ -5,6 +5,7 @@ import com.fs.common.annotation.Excel;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.time.LocalDateTime;
 import java.util.Date;
 
 /**
@@ -99,4 +100,11 @@ public class WxSopLogsListVO implements Serializable {
 
     /** 公司ID */
     private Long companyId;
+
+    /** 预计发送时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime sendTime;
+
+    /** 消息内容JSON */
+    private String contentJson;
 }

+ 11 - 0
fs-service/src/main/java/com/fs/wx/sop/vo/WxSopMsgVo.java

@@ -0,0 +1,11 @@
+package com.fs.wx.sop.vo;
+
+import lombok.Data;
+
+@Data
+public class WxSopMsgVo {
+    private Integer contentType;
+    private String value;
+    private Integer sendStatus;
+    private String sendRemarks;
+}

+ 7 - 1
fs-service/src/main/java/com/fs/wxwork/service/WxIpadService.java

@@ -8,6 +8,7 @@ import com.fs.company.param.AddWxActionParam;
 import com.fs.ipad.vo.WxTxtVo;
 import com.fs.wxcid.domain.CidIpadServer;
 import com.fs.wxcid.service.ICidIpadServerService;
+import com.fs.wxcid.vo.wxvo.WxSendMsgVo;
 import com.fs.wxwork.utils.WxHttpUtil;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -41,8 +42,13 @@ public class WxIpadService {
 
     public void sendTxt(WxTxtVo vo){
         String url = getUrl(vo.getServerId());
-        ResponseResult<Void> result = WxHttpUtil.postWithType(url+"/app/common/sendMsg", vo, new TypeReference<ResponseResult<Void>>() {
+        WxSendMsgVo msgVO = new WxSendMsgVo();
+        msgVO.setWxId(vo.getRemark());
+        msgVO.setRemark(vo.getUserRemark());
+        msgVO.setTxt(vo.getContent());
+        ResponseResult<Void> result = WxHttpUtil.postWithType(url+"/app/common/sendMsg", msgVO, new TypeReference<ResponseResult<Void>>() {
         }, vo.getServerId());
+        log.info("发送结果:{}", result);
     }
     public void addWx(AddWxActionParam vo){
         String url = getUrl(vo.getServerId());

+ 20 - 6
fs-service/src/main/resources/mapper/wx/WxSopLogsMapper.xml

@@ -128,7 +128,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         update_time,
         update_by,
         remark,
-        content_json
+        content_json,
+        send_time
         )
         VALUES
         <foreach collection="list" item="log" separator=",">
@@ -153,7 +154,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{log.updateTime},
             #{log.updateBy},
             #{log.remark},
-            #{log.contentJson}
+            #{log.contentJson},
+            #{log.sendTime}
             )
         </foreach>
     </insert>
@@ -220,6 +222,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="remark"    column="remark"    />
         <result property="fsUserId"    column="fs_user_id"    />
         <result property="companyId"    column="company_id"    />
+        <result property="sendTime"    column="send_time"    />
+        <result property="contentJson"    column="content_json"    />
     </resultMap>
 
     <select id="selectWxSopLogsListBySopId" parameterType="com.fs.wx.sop.params.WxSopLogsParam" resultMap="WxSopLogsListVOResult">
@@ -228,7 +232,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             wsl.sop_id,
             wsl.sop_user_id,
             wsl.account_id,
-            cwa.wx_nick_name as account_name,
             wsl.wx_contact_id,
             wsl.wx_contact_name,
             wsui.tag_names,
@@ -245,10 +248,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             wsl.update_time as real_send_time,
             wsl.remark,
             wsl.fs_user_id,
-            cwa.company_id
+            wsl.send_time,
+            wsl.content_json
         FROM wx_sop_logs wsl
         LEFT JOIN wx_sop_user_info wsui ON wsl.sop_id = wsui.sop_id AND wsl.wx_contact_id = wsui.wx_contact_id
-        LEFT JOIN company_wx_account cwa ON wsl.account_id = cwa.id
         <where>
             <if test="sopId != null">AND wsl.sop_id = #{sopId}</if>
             <if test="sopUserId != null">AND wsl.sop_user_id = #{sopUserId}</if>
@@ -270,6 +273,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ORDER BY wsl.create_time DESC
     </select>
 
-    <select id="selectByWxId" resultType="com.fs.sop.domain.QwSopLogs">
+    <select id="selectByWxId" resultType="com.fs.wx.sop.domain.WxSopLogs">
+        select ql.*,
+               qs.name,
+               qs.expiry_time as expiryTime
+        from wx_sop_logs ql
+                 left join wx_sop qs on qs.id = ql.sop_id
+        where ql.account_id = #{id}
+          and ql.send_status = 0
+        <![CDATA[
+          and ql.send_time <= now()
+        ]]>
+        order by ql.send_time limit 30
     </select>
 </mapper>

+ 4 - 4
fs-service/src/main/resources/mapper/wx/WxSopUserInfoMapper.xml

@@ -28,12 +28,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <sql id="selectWxSopUserInfoVo">
         select wsui.id, wsui.sop_id, wsui.sop_user_id, wsui.wx_contact_id, wsui.customer_id, wsui.fs_user_id, wsui.is_days_not_study,
                wsui.finish_cout, wsui.finish_time, wsui.finish_course_days, wsui.grade, wsui.status,
-               wsui.create_time, wsui.create_by, wsui.update_time, wsui.update_by, wsui.remark,
-               COALESCE(wsui.tag_names, cc.tags) as tag_names
+               wsui.create_time, wsui.create_by, wsui.update_time, wsui.update_by, wsui.remark
         from wx_sop_user_info wsui
-        left join wx_contact wc on wsui.wx_contact_id = wc.id
-        left join crm_customer cc on COALESCE(wc.customer_id, wsui.customer_id) = cc.customer_id
+
     </sql>
+<!--    left join wx_contact wc on wsui.wx_contact_id = wc.id-->
+<!--    left join crm_customer cc on COALESCE(wc.customer_id, wsui.customer_id) = cc.customer_id-->
 
     <select id="selectWxSopUserInfoList" parameterType="WxSopUserInfo" resultMap="WxSopUserInfoResult">
         <include refid="selectWxSopUserInfoVo"/>

+ 1 - 3
fs-service/src/main/resources/mapper/wx/WxSopUserMapper.xml

@@ -26,10 +26,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectWxSopUserList" parameterType="WxSopUser" resultMap="WxSopUserResult">
         SELECT
         w.id,w.type, w.sop_id, w.account_id, w.start_time, w.chat_id, w.status,
-        w.create_time, w.create_by, w.update_time, w.update_by, w.remark,
-        c.wx_nick_name AS account_name
+        w.create_time, w.create_by, w.update_time, w.update_by, w.remark
         FROM wx_sop_user w
-        LEFT JOIN company_wx_account c ON w.account_id = c.id
         <where>
             <if test="type != null "> and type = #{type}</if>
             <if test="sopId != null "> and sop_id = #{sopId}</if>

+ 10 - 4
fs-wx-ipad-task/src/main/java/com/fs/app/service/WxIpadSendServer.java

@@ -7,6 +7,9 @@ import com.fs.ipad.WxIpadSendUtils;
 import com.fs.ipad.vo.*;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.wx.sop.domain.WxSopLogs;
+import com.fs.wx.sop.vo.WxSopMsgVo;
+import com.fs.wxcid.domain.WxContact;
+import com.fs.wxcid.mapper.WxContactMapper;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -20,17 +23,20 @@ public class WxIpadSendServer {
 
     private final CompanyWxAccountMapper companyWxAccountMapper;
     private final WxIpadSendUtils wxIpadSendUtils;
+    private final WxContactMapper wxContactMapper;
 
 
-    public void send(QwSopCourseFinishTempSetting.Setting content, CompanyWxAccount account, WxSopLogs logs) {
+    public void send(WxSopMsgVo content, CompanyWxAccount account, WxSopLogs logs) {
         WxBaseVo vo = new WxBaseVo();
         vo.setId(logs.getId());
         vo.setServerId(account.getServerId());
-        vo.setRemark(logs.getWxRemark());
+        vo.setRemark(account.getWxNo());
+        WxContact wxContact = wxContactMapper.selectById(logs.getWxContactId());
+        vo.setUserRemark(wxContact.getRemark());
         try {
             content.setSendStatus(1);
             switch (content.getContentType()) {
-                case "1":
+                case 1:
                     // 文本
                     sendTxt(vo, content);
                     break;
@@ -46,7 +52,7 @@ public class WxIpadSendServer {
         }
     }
 
-    public void sendTxt(WxBaseVo vo, QwSopCourseFinishTempSetting.Setting content){
+    public void sendTxt(WxBaseVo vo, WxSopMsgVo content){
         wxIpadSendUtils.sendTxt(vo, content.getValue());
     }
 

+ 17 - 16
fs-wx-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -12,6 +12,8 @@ import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.wx.sop.domain.WxSopLogs;
 import com.fs.wx.sop.mapper.WxSopLogsMapper;
 import com.fs.wx.sop.mapper.WxSopMapper;
+import com.fs.wx.sop.service.IWxSopLogsService;
+import com.fs.wx.sop.vo.WxSopMsgVo;
 import com.fs.wxcid.domain.CidIpadServer;
 import com.fs.wxcid.mapper.CidIpadServerMapper;
 import lombok.extern.slf4j.Slf4j;
@@ -39,6 +41,7 @@ public class SendMsg {
     private final CompanyWxAccountMapper companyWxAccountMapper;
     private final CidIpadServerMapper ipadServerMapper;
     private final WxSopLogsMapper wxSopLogsMapper;
+    private final IWxSopLogsService wxSopLogsService;
     private final WxSopMapper wxSopMapper;
     private final WxIpadSendServer sendServer;
     private final RedisCacheT<Long> redisCache;
@@ -50,10 +53,11 @@ public class SendMsg {
     @Autowired
     private Executor sopChatTaskExecutor;
 
-    public SendMsg(CompanyWxAccountMapper companyWxAccountMapper, CidIpadServerMapper ipadServerMapper, WxSopLogsMapper wxSopLogsMapper, WxSopMapper wxSopMapper, WxIpadSendServer sendServer, RedisCacheT<Long> redisCache) {
+    public SendMsg(CompanyWxAccountMapper companyWxAccountMapper, CidIpadServerMapper ipadServerMapper, WxSopLogsMapper wxSopLogsMapper, IWxSopLogsService wxSopLogsService, WxSopMapper wxSopMapper, WxIpadSendServer sendServer, RedisCacheT<Long> redisCache) {
         this.companyWxAccountMapper = companyWxAccountMapper;
         this.ipadServerMapper = ipadServerMapper;
         this.wxSopLogsMapper = wxSopLogsMapper;
+        this.wxSopLogsService = wxSopLogsService;
         this.wxSopMapper = wxSopMapper;
         this.sendServer = sendServer;
         this.redisCache = redisCache;
@@ -93,7 +97,7 @@ public class SendMsg {
                 return new ArrayList<>();
             }
             List<Long> serverIds = PubFun.listToNewList(serverList, CidIpadServer::getId);
-            List<CompanyWxAccount> qwUsers = companyWxAccountMapper.selectList(new QueryWrapper<CompanyWxAccount>().eq("send_msg_type", 1).eq("server_status", 1).eq("ipad_status", 1).in("server_id", serverIds));
+            List<CompanyWxAccount> qwUsers = companyWxAccountMapper.selectList(new QueryWrapper<CompanyWxAccount>().eq("server_status", 1).eq("login_status", 1).in("server_id", serverIds));
             qwUserList.addAll(qwUsers);
         }
         log.info("getQwUserList {}", JSON.toJSONString(qwUserList));
@@ -191,18 +195,18 @@ public class SendMsg {
                 return;
             }
             long start2 = System.currentTimeMillis();
-            QwSopCourseFinishTempSetting setting = JSON.parseObject(qwSopLogs.getContentJson(), QwSopCourseFinishTempSetting.class);
             log.info("进入发送消息状态:{}", qwSopLogs.getId());
             String key = "qw:logs:pad:send:id:" + qwSopLogs.getId();
             Long time = redisCache.getCacheObject(key);
             // 判断这个消息有没有进入过发送,如果进了就不要再发了,防止重复发送,,,,, TODO 千万不能动!!!!!
-            if (redisCache.getCacheObject(key) != null) {
-                log.error("{}已有发送:{}, :{}", account.getWxNickName(), qwSopLogs.getId(), time);
-                continue;
-            }
+//            if (redisCache.getCacheObject(key) != null) {
+//                log.error("{}已有发送:{}, :{}", account.getWxNickName(), qwSopLogs.getId(), time);
+//                continue;
+//            }
             redisCache.setCacheObject(key, System.currentTimeMillis(), 24, TimeUnit.HOURS);
+            List<WxSopMsgVo> setting = JSON.parseArray(qwSopLogs.getContentJson(), WxSopMsgVo.class);
             // 循环发送消息里面的每一条消息
-            for (QwSopCourseFinishTempSetting.Setting content : setting.getSetting()) {
+            for (WxSopMsgVo content : setting) {
                 // 检查是否被取消
                 if (ctx.isCancelled()) {
                     log.info("任务被取消,中断发送:{}", account.getWxNickName());
@@ -231,24 +235,21 @@ public class SendMsg {
             qwSopLogs.setSend(true);
             WxSopLogs updateQwSop = new WxSopLogs();
             updateQwSop.setId(qwSopLogs.getId());
+            updateQwSop.setSendTime(LocalDateTime.now());
             // 是否全部发送失败
-            if (setting.getSetting().stream().allMatch(e -> e.getSendStatus() == 2)) {
-                updateQwSop.setReceivingStatus(0);
-                updateQwSop.setSendStatus(0);
+            if (setting.stream().allMatch(e -> e.getSendStatus() == 2)) {
+                updateQwSop.setSendStatus(2);
                 updateQwSop.setRemark("全部发送失败");
-            } else if (setting.getSetting().stream().anyMatch(e -> e.getSendStatus() == 2)) {
-                updateQwSop.setReceivingStatus(1);
+            } else if (setting.stream().anyMatch(e -> e.getSendStatus() == 2)) {
                 updateQwSop.setSendStatus(1);
                 updateQwSop.setRemark("部分发送失败");
             } else {
-                updateQwSop.setReceivingStatus(1);
                 updateQwSop.setSendStatus(1);
                 updateQwSop.setRemark("全部发送成功");
             }
             updateQwSop.setContentJson(JSON.toJSONString(setting));
-
             long end2 = System.currentTimeMillis();
-            int i = wxSopLogsMapper.updateById(updateQwSop);
+            boolean i = wxSopLogsService.updateMapper(updateQwSop);
             log.info("销售:{}, 修改条数{}, 发送方消息完成:{}, 耗时: {}", account.getWxNickName(), i, qwSopLogs.getId(), end2 - start2);
             try {
                 if (ctx.isCancelled()) {