Przeglądaj źródła

ai销冠系统工作流生成
标签绑定
企微龙虾标签绑定
工作流绘制

lk 3 dni temu
rodzic
commit
47ffdeab26
28 zmienionych plików z 1070 dodań i 68 usunięć
  1. 23 1
      fs-company/src/main/java/com/fs/company/controller/companyWorkflow/CompanyTagTemplateBindingController.java
  2. 18 0
      fs-company/src/main/java/com/fs/company/controller/companyWorkflow/CompanyWorkflowLobsterController.java
  3. 77 1
      fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java
  4. 45 0
      fs-service/src/main/java/com/fs/company/domain/CompanyLobsterTagUserRel.java
  5. 6 0
      fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowLobsterNode.java
  6. 89 0
      fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowLobsterTask.java
  7. 15 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyLobsterTagUserRelMapper.java
  8. 9 7
      fs-service/src/main/java/com/fs/company/mapper/CompanyTagTemplateBindingMapper.java
  9. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterEdgeMapper.java
  10. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterMapper.java
  11. 5 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterNodeMapper.java
  12. 38 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterTaskMapper.java
  13. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterVariableMapper.java
  14. 26 0
      fs-service/src/main/java/com/fs/company/param/BatchBindLobsterTagParam.java
  15. 2 0
      fs-service/src/main/java/com/fs/company/param/CompanyWorkflowLobsterEdgeParam.java
  16. 5 0
      fs-service/src/main/java/com/fs/company/param/CompanyWorkflowLobsterNodeParam.java
  17. 1 0
      fs-service/src/main/java/com/fs/company/param/CompanyWorkflowLobsterVariableParam.java
  18. 15 8
      fs-service/src/main/java/com/fs/company/service/ICompanyTagTemplateBindingService.java
  19. 4 0
      fs-service/src/main/java/com/fs/company/service/ICompanyWorkflowLobsterService.java
  20. 274 5
      fs-service/src/main/java/com/fs/company/service/impl/CompanyTagTemplateBindingServiceImpl.java
  21. 136 34
      fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowLobsterServiceImpl.java
  22. 29 0
      fs-service/src/main/resources/mapper/company/CompanyLobsterTagUserRelMapper.xml
  23. 12 3
      fs-service/src/main/resources/mapper/company/CompanyTagTemplateBindingMapper.xml
  24. 28 5
      fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterEdgeMapper.xml
  25. 9 0
      fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterMapper.xml
  26. 23 4
      fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterNodeMapper.xml
  27. 159 0
      fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterTaskMapper.xml
  28. 16 0
      fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterVariableMapper.xml

+ 23 - 1
fs-company/src/main/java/com/fs/company/controller/tag/CompanyTagTemplateBindingController.java → fs-company/src/main/java/com/fs/company/controller/companyWorkflow/CompanyTagTemplateBindingController.java

@@ -1,9 +1,10 @@
-package com.fs.company.controller.tag;
+package com.fs.company.controller.companyWorkflow;
 
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.utils.ServletUtils;
 import com.fs.company.domain.CompanyTagTemplateBinding;
+import com.fs.company.param.BatchBindLobsterTagParam;
 import com.fs.company.service.ICompanyTagTemplateBindingService;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
@@ -37,6 +38,16 @@ public class CompanyTagTemplateBindingController extends BaseController {
             loginUser.getCompany().getCompanyId(), tagCode, templateId);
         return AjaxResult.success(list);
     }
+    /**
+     * 查询标签绑定列表
+     */
+    @GetMapping("/tag-binding/listByStatus")
+    public AjaxResult listByStatus(@RequestParam(required = false) Integer status){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        List<CompanyTagTemplateBinding> list = tagTemplateBindingService.listByStatus(
+                loginUser.getCompany().getCompanyId(), status);
+        return AjaxResult.success(list);
+    }
 
     /**
      * 获取绑定详情
@@ -103,4 +114,15 @@ public class CompanyTagTemplateBindingController extends BaseController {
         Map<String, Object> result = tagTemplateBindingService.testMatch(loginUser.getCompany().getCompanyId(), id, testTags);
         return AjaxResult.success(result);
     }
+
+    /**
+     * 批量添加龙虾标签给企微客户
+     */
+    @PostMapping("/tag-binding/batch-bind-lobster-tag")
+    public AjaxResult batchBindLobsterTag(@RequestBody BatchBindLobsterTagParam param) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        return tagTemplateBindingService.batchBindLobsterTag(
+                loginUser.getCompany().getCompanyId(), loginUser.getUsername(),
+                param.getQwCorpId(), param.getUserIds(), param.getTagCodes(),loginUser.getUser().getUserId());
+    }
 }

+ 18 - 0
fs-company/src/main/java/com/fs/company/controller/companyWorkflow/CompanyWorkflowLobsterController.java

@@ -40,6 +40,15 @@ public class CompanyWorkflowLobsterController extends BaseController {
         return AjaxResult.success(lobsterService.listTemplate(loginUser.getCompany().getCompanyId(), page, size));
     }
 
+    /**
+     * 分页查询模板列表
+     */
+    @GetMapping("/template/listTemplate")
+    public AjaxResult listTemplate(@RequestParam Integer status) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        return AjaxResult.success(lobsterService.listTemplateByStatus(loginUser.getCompany().getCompanyId(), status));
+    }
+
     /**
      * 获取模板详情(用于画布编辑)
      */
@@ -67,6 +76,15 @@ public class CompanyWorkflowLobsterController extends BaseController {
         return lobsterService.updateTemplate(loginUser.getCompany().getCompanyId(), loginUser.getUsername(), templateId, param);
     }
 
+    /**
+     * 更新模板状态
+     */
+    @PutMapping("/template/{templateId}/{status}")
+    public AjaxResult updateTemplateStatus(@PathVariable Long templateId, @PathVariable Integer status) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        return lobsterService.updateTemplateStatus(loginUser.getCompany().getCompanyId(), loginUser.getUsername(), templateId, status);
+    }
+
     /**
      * 删除模板
      */

+ 77 - 1
fs-ipad-task/src/main/java/com/fs/app/task/SendMsg.java

@@ -3,12 +3,16 @@ package com.fs.app.task;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.app.service.IpadSendServer;
 import com.fs.common.annotation.TenantDataScope;
 import com.fs.common.core.redis.RedisCacheTenant;
+import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.PubFun;
+import com.fs.company.domain.CompanyWorkflowLobsterTask;
+import com.fs.company.mapper.CompanyWorkflowLobsterTaskMapper;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.service.IFsCoursePlaySourceConfigService;
@@ -30,6 +34,8 @@ import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.service.IQwSopLogsService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
+import com.fs.wxwork.dto.*;
+import com.fs.wxwork.service.WxWorkServiceNew;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -48,6 +54,7 @@ import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
+import com.fs.wxwork.service.WxWorkService;
 
 @Component
 @Slf4j
@@ -64,6 +71,10 @@ public class SendMsg {
     private final IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
     private final QwPushCountMapper qwPushCountMapper;
     private final QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper;
+    private final CompanyWorkflowLobsterTaskMapper companyWorkflowLobsterTaskMapper;
+    private final WxWorkService wxWorkService;
+    private final WxWorkServiceNew wxWorkServiceNew;
+
     private final SopTenantDataSourceAspect sopTenantDataSourceAspect;
 
     @Value("${group-no}")
@@ -100,7 +111,7 @@ public class SendMsg {
     @Qualifier("customThreadPool")
     private ThreadPoolTaskExecutor customThreadPool;
 
-    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheTenant<Long> redisCache, AsyncSopTestService asyncSopTestService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService, QwPushCountMapper qwPushCountMapper, QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper, SopTenantDataSourceAspect sopTenantDataSourceAspect) {
+    public SendMsg(QwUserMapper qwUserMapper, QwSopLogsMapper qwSopLogsMapper, IpadSendServer sendServer, SysConfigMapper sysConfigMapper, IQwSopLogsService qwSopLogsService, QwIpadServerMapper qwIpadServerMapper, RedisCacheTenant<Long> redisCache, AsyncSopTestService asyncSopTestService, IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService, QwPushCountMapper qwPushCountMapper, QwRestrictionPushRecordMapper qwRestrictionPushRecordMapper, SopTenantDataSourceAspect sopTenantDataSourceAspect, CompanyWorkflowLobsterTaskMapper companyWorkflowLobsterTaskMapper, WxWorkService wxWorkService, WxWorkServiceNew wxWorkServiceNew) {
         this.qwUserMapper = qwUserMapper;
         this.qwSopLogsMapper = qwSopLogsMapper;
         this.sendServer = sendServer;
@@ -112,6 +123,9 @@ public class SendMsg {
         this.fsCoursePlaySourceConfigService = fsCoursePlaySourceConfigService;
         this.qwPushCountMapper = qwPushCountMapper;
         this.qwRestrictionPushRecordMapper = qwRestrictionPushRecordMapper;
+        this.companyWorkflowLobsterTaskMapper = companyWorkflowLobsterTaskMapper;
+        this.wxWorkService = wxWorkService;
+        this.wxWorkServiceNew = wxWorkServiceNew;
         this.sopTenantDataSourceAspect = sopTenantDataSourceAspect;
     }
     private List<QwUser> getQwUserList() {
@@ -410,4 +424,66 @@ public class SendMsg {
         long end3 = System.currentTimeMillis();
         log.info("销售执行完成:{}, 耗时:{}", user.getQwUserName(), end3 - start3);
     }
+
+    @Scheduled(fixedDelay = 10000*60*30) // 每30min执行一次
+    public void sendLobsterQwMsg(){
+        log.info("开始龙虾发送企微消息");
+        List<CompanyWorkflowLobsterTask> companyWorkflowLobsterTasks = companyWorkflowLobsterTaskMapper.selectList(new LambdaQueryWrapper<CompanyWorkflowLobsterTask>().eq(CompanyWorkflowLobsterTask::getDelFlag, 0)
+                .between(CompanyWorkflowLobsterTask::getSendTime, LocalDateTime.now().minusMinutes(30), LocalDateTime.now())
+        );
+        for (CompanyWorkflowLobsterTask task : companyWorkflowLobsterTasks){
+            QwUser qwUser = qwUserMapper.selectQwUserById(task.getQwUserId());
+            WxLoginResp login = isLogin(qwUser.getUid(), qwUser.getServerId());
+            WxWorkSendTextMsgDTO dto = new WxWorkSendTextMsgDTO();
+            BaseVo vo = new BaseVo();
+            vo.setUuid(qwUser.getUid());
+            vo.setCorpId(qwUser.getCorpId());
+            vo.setCorpCode(login.getUser_info().getObject().getScorp_id());
+            vo.setServerId(qwUser.getServerId());
+            dto.setUuid(qwUser.getUid());
+            dto.setSend_userid(userIds(vo));//目前没接群聊
+            dto.setContent(task.getTaskContent());
+            dto.setIsRoom(false);
+            WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> wxWorkSendTextMsgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(dto, qwUser.getServerId());
+
+            log.info("开始发送企微消息,内容:{}", wxWorkSendTextMsgRespDTOWxWorkResponseDTO);
+            //todo 发企微,
+            if (wxWorkSendTextMsgRespDTOWxWorkResponseDTO.getErrcode()==0){
+                task.setExecuteStatus(2);
+                log.info("企微发送成功:{}", task.getId());
+            }else {
+                task.setExecuteStatus(3);
+                log.info("企微发送失败:{}", task.getId());
+            }
+        }
+        companyWorkflowLobsterTaskMapper.updateTaskListExecuteStatus(companyWorkflowLobsterTasks);
+
+    }
+    /**
+     * 获取发送对象的userid
+     * @param vo 调用接口参数
+     * @return 返回的userid
+     */
+    public Long userIds(BaseVo vo){
+        WxWorkUserId2VidDTO wxWorkUserId2VidDTO = new WxWorkUserId2VidDTO();
+        wxWorkUserId2VidDTO.setOpenid(Collections.singletonList(vo.getExId()));
+        wxWorkUserId2VidDTO.setCorpid(vo.getCorpId());
+        wxWorkUserId2VidDTO.setScorpid(vo.getCorpCode());
+        wxWorkUserId2VidDTO.setUuid(vo.getUuid());
+        WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> WxWorkVid2UserIdRespDTO = wxWorkService.UserId2Vid(wxWorkUserId2VidDTO, vo.getServerId());
+        List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
+        if(data.isEmpty()) {
+            log.error("未找到用户数据,基础数据:{},请求数据:{},返回数据:{}", vo, JSON.toJSONString(wxWorkUserId2VidDTO), JSON.toJSONString(WxWorkVid2UserIdRespDTO));
+            throw new BaseException("未找到用户:" + vo.getId());
+        }
+        return data.get(0).getUser_id();
+    }
+
+    public WxLoginResp isLogin(String uuid, Long serverId){
+        WxLoginDTO dto = new WxLoginDTO();
+        dto.setUuid(uuid);
+        WxWorkResponseDTO<WxLoginResp> result = wxWorkServiceNew.isLogin(dto, serverId);
+        if(result.getErrcode() != 0) throw new BaseException("验证登录失败:" + result.getErrmsg());
+        return result.getData();
+    }
 }

+ 45 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyLobsterTagUserRel.java

@@ -0,0 +1,45 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 龙虾标签-企微客户关联表
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CompanyLobsterTagUserRel extends BaseEntity {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 公司ID(租户隔离) */
+    private Long companyId;
+
+    /** 标签模板绑定ID */
+    private Long bindingId;
+
+    /** 标签编码(冗余) */
+    private String tagCode;
+
+    /** 工作流模板ID(冗余) */
+    private Long templateId;
+
+    /** 企微外部联系人ID */
+    private Long externalContactId;
+
+    /** 企微主体id */
+    private String corpId;
+
+    /** 销售id */
+    private Long companyUserId;
+
+    /** 企微用户id */
+    private Long qwUserId;
+
+    /** 删除标志 */
+    private Integer delFlag;
+}

+ 6 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowLobsterNode.java

@@ -6,6 +6,10 @@ import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
+import java.sql.Time;
+import java.time.LocalTime;
+import java.util.Date;
+
 /**
  * 工作流龙虾节点
  */
@@ -36,5 +40,7 @@ public class CompanyWorkflowLobsterNode extends BaseEntity {
 
     private String greetingConfig;
 
+    private LocalTime sendTime;
+
     private Integer delFlag;
 }

+ 89 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowLobsterTask.java

@@ -0,0 +1,89 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * 龙虾工作流模板自动任务表
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class CompanyWorkflowLobsterTask extends BaseEntity {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 公司ID(租户隔离) */
+    private Long companyId;
+
+    /** 工作流模板ID */
+    private Long templateId;
+
+    /** 任务名称 */
+    private String taskName;
+
+    /** 任务类型: '1开始 2消息 3判断 4等待 5结束 6API调用' */
+    private Integer taskType;
+
+    /** 任务内容(JSON格式,存储节点配置参数) */
+    private String taskContent;
+
+    /** Cron表达式(定时触发用) */
+    private String cronExpression;
+
+    /** 执行状态:0-待执行, 1-执行中, 2-执行成功, 3-执行失败, 4-已取消, 5-已过期 */
+    private Integer executeStatus;
+
+    /** 已执行次数 */
+    private Integer executeCount;
+
+    /** 最大重试次数(0表示不重试) */
+    private Integer maxRetry;
+
+    /** 已重试次数 */
+    private Integer retryCount;
+
+    /** 上次执行时间 */
+    private Date lastExecuteTime;
+
+    /** 下次执行时间 */
+    private Date nextExecuteTime;
+
+    /** 失败原因 */
+    private String failReason;
+
+    /** 排序号 */
+    private Integer sortOrder;
+
+    /** 删除标志:0-正常, 1-已删除 */
+    private Integer delFlag;
+
+    /**
+     * 企微主体
+     */
+    private String corpId;
+
+    /**
+     * 销售id
+     */
+    private Long companyUserId;
+
+    /**
+     * 节点id
+     */
+    private Long lobsterNodeId;
+
+    private LocalDateTime sendTime; // 发送时间
+
+    private Long qwUserId; // 企微用户id
+
+    private Long bindingId;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyLobsterTagUserRelMapper.java

@@ -0,0 +1,15 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyLobsterTagUserRel;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface CompanyLobsterTagUserRelMapper extends BaseMapper<CompanyLobsterTagUserRel> {
+
+    int batchInsert(@Param("list") List<CompanyLobsterTagUserRel> list);
+
+    void updateBatchRelBybinding(@Param("id") Long id,@Param("flag") Integer unDelFlag);
+
+}

+ 9 - 7
fs-service/src/main/java/com/fs/company/mapper/CompanyTagTemplateBindingMapper.java

@@ -7,23 +7,25 @@ import org.apache.ibatis.annotations.Param;
 import java.util.List;
 
 public interface CompanyTagTemplateBindingMapper extends BaseMapper<CompanyTagTemplateBinding> {
-    
+
     List<CompanyTagTemplateBinding> selectBindingList(@Param("companyId") Long companyId,
                                                        @Param("tagCode") String tagCode,
                                                        @Param("templateId") Long templateId);
-    
+
     int insertBinding(CompanyTagTemplateBinding entity);
-    
+
     CompanyTagTemplateBinding selectBindingByIdAndCompanyId(@Param("id") Long id, @Param("companyId") Long companyId);
-    
+
     int updateBindingById(CompanyTagTemplateBinding entity);
-    
+
     int logicalDeleteById(@Param("id") Long id, @Param("companyId") Long companyId);
-    
+
     List<CompanyTagTemplateBinding> selectMatchedTemplates(@Param("companyId") Long companyId,
                                                             @Param("tagCodes") List<String> tagCodes);
-    
+
     int batchBind(@Param("companyId") Long companyId,
                   @Param("templateId") Long templateId,
                   @Param("tagCodes") List<String> tagCodes);
+
+    List<CompanyTagTemplateBinding> listByStatus(@Param("companyId") Long companyId,@Param("status") Integer status);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterEdgeMapper.java

@@ -12,4 +12,6 @@ public interface CompanyWorkflowLobsterEdgeMapper extends BaseMapper<CompanyWork
     int deleteByWorkflowId(@Param("workflowId") Long workflowId);
 
     List<CompanyWorkflowLobsterEdge> selectByWorkflowId(@Param("workflowId") Long workflowId);
+
+    int updateById(@Param("entity") CompanyWorkflowLobsterEdge entity);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterMapper.java

@@ -16,4 +16,6 @@ public interface CompanyWorkflowLobsterMapper extends BaseMapper<CompanyWorkflow
     int updateTemplateById(CompanyWorkflowLobster entity);
 
     int logicalDeleteById(@Param("id") Long id, @Param("companyId") Long companyId);
+
+    List<CompanyWorkflowLobster> selectTemplateListByStatus(@Param("companyId") Long companyId,@Param("status") Integer status);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterNodeMapper.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.company.domain.CompanyWorkflowLobsterNode;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.ArrayList;
 import java.util.List;
 
 public interface CompanyWorkflowLobsterNodeMapper extends BaseMapper<CompanyWorkflowLobsterNode> {
@@ -12,4 +13,8 @@ public interface CompanyWorkflowLobsterNodeMapper extends BaseMapper<CompanyWork
     int deleteByWorkflowId(@Param("workflowId") Long workflowId);
 
     List<CompanyWorkflowLobsterNode> selectByWorkflowId(@Param("workflowId") Long workflowId);
+
+    void updateBatch(@Param("list") ArrayList<CompanyWorkflowLobsterNode> nodesNew);
+
+    int checkNodeGreeting(@Param("templateId") Long templateId);
 }

+ 38 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterTaskMapper.java

@@ -0,0 +1,38 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyWorkflowLobsterTask;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+public interface CompanyWorkflowLobsterTaskMapper extends BaseMapper<CompanyWorkflowLobsterTask> {
+
+    CompanyWorkflowLobsterTask selectByIdAndCompanyId(@Param("id") Long id, @Param("companyId") Long companyId);
+
+    List<CompanyWorkflowLobsterTask> selectByTemplateId(@Param("templateId") Long templateId,
+                                                         @Param("companyId") Long companyId);
+
+    List<CompanyWorkflowLobsterTask> selectPendingTasks(@Param("companyId") Long companyId,
+                                                         @Param("beforeTime") Date beforeTime,
+                                                         @Param("limit") Integer limit);
+
+    int insertTask(CompanyWorkflowLobsterTask entity);
+
+    int updateTaskById(CompanyWorkflowLobsterTask entity);
+
+    int updateExecuteStatus(@Param("id") Long id,
+                            @Param("executeStatus") Integer executeStatus,
+                            @Param("failReason") String failReason,
+                            @Param("lastExecuteTime") Date lastExecuteTime,
+                            @Param("nextExecuteTime") Date nextExecuteTime);
+
+    int logicalDeleteById(@Param("id") Long id, @Param("companyId") Long companyId);
+
+    int batchInsert(@Param("list") List<CompanyWorkflowLobsterTask> list);
+
+    void updateDelFlagByBinding(@Param("id") Long id, @Param("flag") Integer unDelFlag);
+
+    void updateTaskListExecuteStatus(List<CompanyWorkflowLobsterTask> companyWorkflowLobsterTasks);
+}

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowLobsterVariableMapper.java

@@ -12,4 +12,6 @@ public interface CompanyWorkflowLobsterVariableMapper extends BaseMapper<Company
     int deleteByWorkflowId(@Param("workflowId") Long workflowId);
 
     List<CompanyWorkflowLobsterVariable> selectByWorkflowId(@Param("workflowId") Long workflowId);
+
+    int updateById(@Param("entity") CompanyWorkflowLobsterVariable entity);
 }

+ 26 - 0
fs-service/src/main/java/com/fs/company/param/BatchBindLobsterTagParam.java

@@ -0,0 +1,26 @@
+package com.fs.company.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 批量添加龙虾标签给企微客户参数
+ */
+@Data
+public class BatchBindLobsterTagParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 企微外部联系人ID列表 */
+    private List<Long> userIds;
+
+    /** 标签编码列表 */
+    private List<String> tagCodes;
+
+    /** 企微公司ID */
+    private String qwCorpId;
+
+
+}

+ 2 - 0
fs-service/src/main/java/com/fs/company/param/CompanyWorkflowLobsterEdgeParam.java

@@ -11,6 +11,8 @@ import java.io.Serializable;
 public class CompanyWorkflowLobsterEdgeParam implements Serializable {
     private static final long serialVersionUID = 1L;
 
+    /** 主键ID */
+    private Long id;
     /** 连线唯一标识 */
     private String edgeKey;
     

+ 5 - 0
fs-service/src/main/java/com/fs/company/param/CompanyWorkflowLobsterNodeParam.java

@@ -1,13 +1,17 @@
 package com.fs.company.param;
 
+import cn.hutool.core.date.DateTime;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.time.LocalTime;
+import java.util.Date;
 
 @Data
 public class CompanyWorkflowLobsterNodeParam implements Serializable {
     private static final long serialVersionUID = 1L;
 
+    private Long id;
     private String nodeCode;
     private String nodeName;
     private Integer nodeType;
@@ -17,4 +21,5 @@ public class CompanyWorkflowLobsterNodeParam implements Serializable {
     private String conditionExpr;
     private String nodeConfig;
     private String greetingConfig;
+    private LocalTime sendTime;
 }

+ 1 - 0
fs-service/src/main/java/com/fs/company/param/CompanyWorkflowLobsterVariableParam.java

@@ -8,6 +8,7 @@ import java.io.Serializable;
 public class CompanyWorkflowLobsterVariableParam implements Serializable {
     private static final long serialVersionUID = 1L;
 
+    private Long id;
     private String varCode;
     private String varName;
     private String varType;

+ 15 - 8
fs-service/src/main/java/com/fs/company/service/ICompanyTagTemplateBindingService.java

@@ -7,44 +7,51 @@ import java.util.List;
 import java.util.Map;
 
 public interface ICompanyTagTemplateBindingService {
-    
+
     /**
      * 查询标签绑定列表
      */
     List<CompanyTagTemplateBinding> listBinding(Long companyId, String tagCode, Long templateId);
-    
+
     /**
      * 根据ID查询绑定
      */
     CompanyTagTemplateBinding getBindingById(Long companyId, Long id);
-    
+
     /**
      * 新增绑定
      */
     AjaxResult addBinding(Long companyId, String userName, CompanyTagTemplateBinding binding);
-    
+
     /**
      * 修改绑定
      */
     AjaxResult updateBinding(Long companyId, String userName, CompanyTagTemplateBinding binding);
-    
+
     /**
      * 删除绑定
      */
     AjaxResult deleteBinding(Long companyId, String userName, Long id);
-    
+
     /**
      * 批量绑定
      */
     AjaxResult batchBind(Long companyId, String userName, Long templateId, List<String> tagCodes);
-    
+
     /**
      * 根据用户标签匹配模板
      */
     Map<String, Object> matchTemplate(Long companyId, List<String> userTags);
-    
+
     /**
      * 测试标签匹配
      */
     Map<String, Object> testMatch(Long companyId, Long id, Map<String, Object> testTags);
+
+    List<CompanyTagTemplateBinding> listByStatus(Long companyId, Integer status);
+
+    /**
+     * 批量添加龙虾标签给企微客户
+     */
+    AjaxResult batchBindLobsterTag(Long companyId, String userName, String qwCorpId, List<Long> externalContactIds, List<String> tagCodes,Long companyUserId);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyWorkflowLobsterService.java

@@ -59,4 +59,8 @@ public interface ICompanyWorkflowLobsterService {
      * 保存画布数据(包含节点位置、连线等可视化信息)
      */
     AjaxResult saveCanvas(Long companyId, String userName, Long templateId, CompanyWorkflowLobsterCanvasParam param);
+
+    AjaxResult updateTemplateStatus(Long companyId, String username, Long templateId, Integer status);
+
+    List<CompanyWorkflowLobster> listTemplateByStatus(Long companyId, Integer status);
 }

+ 274 - 5
fs-service/src/main/java/com/fs/company/service/impl/CompanyTagTemplateBindingServiceImpl.java

@@ -1,15 +1,37 @@
 package com.fs.company.service.impl;
 
+import cn.hutool.core.map.MapUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
-import com.fs.company.domain.CompanyTagTemplateBinding;
-import com.fs.company.mapper.CompanyTagTemplateBindingMapper;
+import com.fs.company.domain.*;
+import com.fs.company.mapper.*;
 import com.fs.company.service.ICompanyTagTemplateBindingService;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * 标签模板绑定Service实现
@@ -17,8 +39,23 @@ import java.util.*;
 @Service
 public class CompanyTagTemplateBindingServiceImpl implements ICompanyTagTemplateBindingService {
 
+    private static final Logger log = LoggerFactory.getLogger(CompanyTagTemplateBindingServiceImpl.class);
     @Autowired
     private CompanyTagTemplateBindingMapper tagTemplateBindingMapper;
+    @Autowired
+    private CompanyWorkflowLobsterNodeMapper companyWorkflowLobsterNodeMapper;
+    @Autowired
+    private CompanyWorkflowLobsterMapper companyWorkflowLobsterMapper;
+    @Autowired
+    private CompanyWorkflowLobsterServiceImpl workflowService;
+    @Autowired
+    private RedissonClient redissonClient;
+    @Autowired
+    private CompanyLobsterTagUserRelMapper companyLobsterTagUserRelMapper;
+    @Autowired
+    private CompanyWorkflowLobsterTaskMapper companyWorkflowLobsterTaskMapper;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
 
     @Override
     public List<CompanyTagTemplateBinding> listBinding(Long companyId, String tagCode, Long templateId) {
@@ -34,7 +71,7 @@ public class CompanyTagTemplateBindingServiceImpl implements ICompanyTagTemplate
     @Transactional(rollbackFor = Exception.class)
     public AjaxResult addBinding(Long companyId, String userName, CompanyTagTemplateBinding binding) {
         binding.setCompanyId(companyId);
-        binding.setStatus(1);
+        binding.setStatus(0);
         binding.setDelFlag(0);
         binding.setCreateBy(userName);
         binding.setCreateTime(DateUtils.getNowDate());
@@ -45,6 +82,11 @@ public class CompanyTagTemplateBindingServiceImpl implements ICompanyTagTemplate
         return result > 0 ? AjaxResult.success("新增成功") : AjaxResult.error("新增失败");
     }
 
+    @Value("${lobster.init.node.key:fastgpt-zTwq4M6jZ6R3fZsnMPUOVRm0Mo0o0I5BqoaHA8QHYoCUT6OD6uXuimhJHBH6q2l0B}")
+    private String appKey;
+    private static final Integer UN_DEL_FLAG = 0;
+    private static final Integer DO_DEL_FLAG = 1;
+    private static final String INIT_NODE_KEY = "LOBSTER:INIT:NODE:TEMPLATE:ID:";
     @Override
     @Transactional(rollbackFor = Exception.class)
     public AjaxResult updateBinding(Long companyId, String userName, CompanyTagTemplateBinding binding) {
@@ -55,11 +97,122 @@ public class CompanyTagTemplateBindingServiceImpl implements ICompanyTagTemplate
 
         binding.setUpdateBy(userName);
         binding.setUpdateTime(DateUtils.getNowDate());
-
+        binding.setCompanyId(companyId);
         int result = tagTemplateBindingMapper.updateBindingById(binding);
+        // 如果是状态启用,生成工作流消息节点的具体内容
+        if (exist.getStatus() == 0 && binding.getStatus() == 1) {
+            List<CompanyLobsterTagUserRel> rels = companyLobsterTagUserRelMapper.selectList(new LambdaQueryWrapper<CompanyLobsterTagUserRel>().eq(CompanyLobsterTagUserRel::getBindingId, binding.getId())
+                    .eq(CompanyLobsterTagUserRel::getDelFlag, DO_DEL_FLAG));
+            if (rels != null && !rels.isEmpty()){
+                companyLobsterTagUserRelMapper.updateBatchRelBybinding(binding.getId(), UN_DEL_FLAG);
+                companyWorkflowLobsterTaskMapper.updateDelFlagByBinding(binding.getId(), UN_DEL_FLAG);
+            }
+            String lockKey = INIT_NODE_KEY + exist.getTemplateId();
+            asyncInitMsgNodeWithLock(companyId, exist.getTemplateId(), lockKey);
+        }else if (exist.getStatus() == 1 && binding.getStatus() == 0) {
+            List<CompanyLobsterTagUserRel> rels = companyLobsterTagUserRelMapper.selectList(new LambdaQueryWrapper<CompanyLobsterTagUserRel>().eq(CompanyLobsterTagUserRel::getBindingId, binding.getId())
+                    .eq(CompanyLobsterTagUserRel::getDelFlag, UN_DEL_FLAG));
+            if (rels != null && !rels.isEmpty()){
+                companyLobsterTagUserRelMapper.updateBatchRelBybinding(binding.getId(), DO_DEL_FLAG);
+                companyWorkflowLobsterTaskMapper.updateDelFlagByBinding(binding.getId(), DO_DEL_FLAG);
+            }
+        }
         return result > 0 ? AjaxResult.success("修改成功") : AjaxResult.error("修改失败");
     }
 
+    @Async
+    public void lobsterMsgNodeInit(Long companyId, Long templateId) {
+        List<CompanyWorkflowLobsterNode> nodes = companyWorkflowLobsterNodeMapper.selectList(new LambdaQueryWrapper<CompanyWorkflowLobsterNode>()
+                .eq(CompanyWorkflowLobsterNode::getWorkflowId, templateId).eq(CompanyWorkflowLobsterNode::getDelFlag, UN_DEL_FLAG)
+                .isNull(CompanyWorkflowLobsterNode::getGreetingConfig));
+        if (nodes == null || nodes.isEmpty()) {
+            log.info("没有需要生成的节点");
+            return;
+        }
+
+        List<CompanyWorkflowLobsterNode> msgNodes = nodes.stream()
+                .filter(node -> node.getNodeType().equals(2))
+                .collect(Collectors.toList());
+
+        if (msgNodes.isEmpty()) {
+            log.info("没有消息节点需要生成");
+            return;
+        }
+
+        int threadCount = Math.min(msgNodes.size(), 8);
+        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+
+        try {
+            ArrayList<CompanyWorkflowLobsterNode> nodesNew =(new ArrayList<>());
+
+            List<CompletableFuture<Void>> futures = msgNodes.stream()
+                    .map(node -> CompletableFuture.runAsync(() -> {
+                        try {
+                            String requestStr = "{ \"userContent\": \"" + node.getMessageTemplate()
+                                    + "\", \"aiContent\": null,\"userInfo\" : null ,\"aiInfo\" : null,\"history\" : null}";
+                            R r = workflowService.callAiService(requestStr, node.getId(), appKey);
+
+                            CompanyWorkflowLobsterNode nodeNew = new CompanyWorkflowLobsterNode();
+                            nodeNew.setId(node.getId());
+                            try {
+                                ObjectMapper mapper = new ObjectMapper();
+                                JsonNode root = mapper.readTree(mapper.writeValueAsString(r));
+                                JsonNode aiContent = root.path("data")
+                                        .path("choices")
+                                        .path(0)
+                                        .path("message")
+                                        .path("content");
+                                if (aiContent.isTextual()) {
+                                    aiContent = mapper.readTree(aiContent.asText()).path("aiContent");
+                                }
+                                log.info("{}节点生成消息成功:{}", node.getId(), aiContent.asText());
+                                if (!aiContent.isNull()) {
+                                    nodeNew.setGreetingConfig(aiContent.asText());
+                                }
+                            } catch (Exception e) {
+                                log.error("提取AI返回aiContent失败, nodeId={}", node.getId(), e);
+                            }
+                            nodesNew.add(nodeNew);
+                        } catch (Exception e) {
+                            log.error("调用AI服务失败, nodeId={}", node.getId(), e);
+                        }
+                    }, executor))
+                    .collect(Collectors.toList());
+
+            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+
+            if (!nodesNew.isEmpty()) {
+                companyWorkflowLobsterNodeMapper.updateBatch(nodesNew);
+            }
+        } finally {
+            executor.shutdown();
+        }
+    }
+
+    /**
+     * 异步初始化消息节点(带分布式锁,防重复)
+     */
+    @Async
+    public void asyncInitMsgNodeWithLock(Long companyId, Long templateId, String lockKey) {
+        RLock lock = redissonClient.getLock(lockKey);
+        boolean locked = false;
+        try {
+            locked = lock.tryLock(3, TimeUnit.SECONDS);
+            if (locked) {
+                lobsterMsgNodeInit(companyId, templateId);
+            } else {
+                log.warn("获取锁失败,跳过节点初始化, lockKey={}", lockKey);
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("获取锁被中断, lockKey={}", lockKey, e);
+        } finally {
+            if (locked && lock.isHeldByCurrentThread()) {
+                lock.unlock();
+            }
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public AjaxResult deleteBinding(Long companyId, String userName, Long id) {
@@ -67,7 +220,14 @@ public class CompanyTagTemplateBindingServiceImpl implements ICompanyTagTemplate
         if (exist == null) {
             return AjaxResult.error("绑定关系不存在");
         }
-
+        if (exist.getStatus() == 1) {
+            List<CompanyLobsterTagUserRel> rels = companyLobsterTagUserRelMapper.selectList(new LambdaQueryWrapper<CompanyLobsterTagUserRel>().eq(CompanyLobsterTagUserRel::getBindingId, exist.getId())
+                    .eq(CompanyLobsterTagUserRel::getDelFlag, UN_DEL_FLAG));
+            if (rels != null && !rels.isEmpty()){
+                companyLobsterTagUserRelMapper.updateBatchRelBybinding(exist.getId(), DO_DEL_FLAG);
+                companyWorkflowLobsterTaskMapper.updateDelFlagByBinding(exist.getId(), DO_DEL_FLAG);
+            }
+        }
         int result = tagTemplateBindingMapper.logicalDeleteById(id, companyId);
         return result > 0 ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
     }
@@ -145,4 +305,113 @@ public class CompanyTagTemplateBindingServiceImpl implements ICompanyTagTemplate
 
         return result;
     }
+
+    @Override
+    public List<CompanyTagTemplateBinding> listByStatus(Long companyId, Integer status) {
+        List<CompanyTagTemplateBinding> bindings = tagTemplateBindingMapper.listByStatus(companyId, status);
+        for (CompanyTagTemplateBinding binding : bindings){
+            int i = companyWorkflowLobsterNodeMapper.checkNodeGreeting(binding.getTemplateId());
+            if (i>0){
+                bindings.remove( binding);
+            }
+        }
+        return bindings;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AjaxResult batchBindLobsterTag(Long companyId, String userName, String qwCorpId, List<Long> externalContactIds,
+                                           List<String> tagCodes,Long companyUserId) {
+        if (externalContactIds == null || externalContactIds.isEmpty()) {
+            return AjaxResult.error("请勾选需要添加龙虾标签的客户");
+        }
+        if (tagCodes == null || tagCodes.isEmpty()) {
+            return AjaxResult.error("请选择龙虾标签");
+        }
+
+        // 根据 tagCode 查询已启用的绑定关系
+        List<CompanyTagTemplateBinding> bindings = tagTemplateBindingMapper.listByStatus(companyId, 1);
+        if (bindings == null || bindings.isEmpty()) {
+            return AjaxResult.error("没有已启用的标签绑定关系");
+        }
+
+        // 筛选传入的 tagCodes 对应的绑定
+        List<CompanyTagTemplateBinding> targetBindings = bindings.stream()
+                .filter(b -> tagCodes.contains(b.getTagCode()))
+                .collect(Collectors.toList());
+        if (targetBindings.isEmpty()) {
+            return AjaxResult.error("所选标签没有已启用的绑定关系");
+        }
+
+        // 验证每个绑定的模板状态为启用(status=1)
+        for (CompanyTagTemplateBinding binding : targetBindings) {
+            CompanyWorkflowLobster template = companyWorkflowLobsterMapper
+                    .selectTemplateByIdAndCompanyId(binding.getTemplateId(), companyId);
+            if (template == null || template.getStatus() == null || template.getStatus() != 1) {
+                return AjaxResult.error("模板【" + binding.getTemplateName() + "】未启用,无法添加标签【" + binding.getTagCode() + "】");
+            }
+        }
+
+        List<Long> bindingIds = targetBindings.stream().map(CompanyTagTemplateBinding::getId).collect(Collectors.toList());
+        int count =  companyLobsterTagUserRelMapper.selectCount(new LambdaQueryWrapper<CompanyLobsterTagUserRel>().in(CompanyLobsterTagUserRel::getBindingId, bindingIds)
+                .in(CompanyLobsterTagUserRel::getExternalContactId, externalContactIds).eq(CompanyLobsterTagUserRel::getDelFlag, 0));
+        if (count>0){
+            return AjaxResult.error("无法重复绑定龙虾标签");
+        }
+
+        // 构建中间表数据
+        Date now = new Date();
+        List<CompanyLobsterTagUserRel> relList = new ArrayList<>();
+        ArrayList<CompanyWorkflowLobsterTask> tasks = new ArrayList<>();
+        Map<Long, Long> collect = qwExternalContactMapper.selectQwExternalContactByIds(externalContactIds)
+                .stream()
+                .collect(Collectors.toMap(
+                        QwExternalContact::getId,
+                        QwExternalContact::getQwUserId
+                ));
+        for (CompanyTagTemplateBinding binding : targetBindings) {
+            List<CompanyWorkflowLobsterNode> nodes = companyWorkflowLobsterNodeMapper.selectList(new LambdaQueryWrapper<CompanyWorkflowLobsterNode>()
+                    .eq(CompanyWorkflowLobsterNode::getWorkflowId, binding.getTemplateId()).eq(CompanyWorkflowLobsterNode::getNodeType, 2)
+                    .eq(CompanyWorkflowLobsterNode::getDelFlag, 0));
+            externalContactIds.forEach(externalContactId ->{
+                Long qwUserId = collect.get(externalContactId);
+                CompanyLobsterTagUserRel rel = new CompanyLobsterTagUserRel();
+                rel.setCompanyId(companyId);
+                rel.setBindingId(binding.getId());
+                rel.setTagCode(binding.getTagCode());
+                rel.setTemplateId(binding.getTemplateId());
+                rel.setExternalContactId(externalContactId);
+                rel.setCreateBy(userName);
+                rel.setCreateTime(now);
+                rel.setCorpId(qwCorpId);
+                rel.setCompanyUserId(companyUserId);
+                rel.setQwUserId(qwUserId);
+                relList.add(rel);
+                if (!nodes.isEmpty()){
+
+                    nodes.forEach(node ->{
+                        CompanyWorkflowLobsterTask task = new CompanyWorkflowLobsterTask();
+                        task.setCompanyId(companyId).setCompanyUserId(companyUserId).setCorpId(qwCorpId)
+                                .setTemplateId(binding.getTemplateId()).setTaskName(binding.getTemplateName())
+                                .setTaskType(2).setTaskContent(node.getGreetingConfig()).setTaskName("龙虾企微发消息")
+                                .setQwUserId(qwUserId).setBindingId(binding.getId()).setLobsterNodeId(node.getId());
+                        task.setSendTime(getSendTimeNode(node));
+                        tasks.add(task);
+                    });
+                }
+
+            });
+        }
+        companyLobsterTagUserRelMapper.batchInsert(relList);
+        companyWorkflowLobsterTaskMapper.batchInsert(tasks);
+        log.info("批量添加龙虾标签成功, companyId={}, externalContactCount={}, tagCount={}, relCount={}",
+                companyId, externalContactIds.size(), targetBindings.size(), relList.size());
+        return AjaxResult.success("已为 " + externalContactIds.size() + " 个客户添加 " + targetBindings.size() + " 个龙虾标签");
+    }
+
+    private LocalDateTime getSendTimeNode(CompanyWorkflowLobsterNode node) {
+        Integer days = Integer.valueOf(node.getNodeCode().substring(4));
+        LocalDate date = LocalDate.now().plusDays(days);
+        return LocalDateTime.of(date, node.getSendTime());
+    }
 }

+ 136 - 34
fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowLobsterServiceImpl.java

@@ -8,6 +8,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.spring.SpringUtils;
 import com.fs.company.domain.CompanyWorkflowLobster;
+import com.fs.company.domain.CompanyTagTemplateBinding;
 import com.fs.company.domain.CompanyWorkflowLobsterEdge;
 import com.fs.company.domain.CompanyWorkflowLobsterNode;
 import com.fs.company.domain.CompanyWorkflowLobsterRecord;
@@ -17,6 +18,7 @@ import com.fs.company.mapper.CompanyWorkflowLobsterMapper;
 import com.fs.company.mapper.CompanyWorkflowLobsterNodeMapper;
 import com.fs.company.mapper.CompanyWorkflowLobsterRecordMapper;
 import com.fs.company.mapper.CompanyWorkflowLobsterVariableMapper;
+import com.fs.company.mapper.CompanyTagTemplateBindingMapper;
 import com.fs.company.param.CompanyWorkflowLobsterCanvasParam;
 import com.fs.company.param.CompanyWorkflowLobsterConfirmParam;
 import com.fs.company.param.CompanyWorkflowLobsterEdgeParam;
@@ -26,18 +28,24 @@ import com.fs.company.param.CompanyWorkflowLobsterVariableParam;
 import com.fs.company.service.ICompanyWorkflowLobsterService;
 import com.fs.fastgptApi.param.ChatParam;
 import com.fs.fastgptApi.service.ChatService;
+import com.fs.wxwork.dto.WxWorkSendTextMsgDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 
+import java.sql.Time;
 import java.util.*;
 import java.util.stream.Collectors;
 
+
 @Service
 public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobsterService {
 
+    private static final Logger log = LoggerFactory.getLogger(CompanyWorkflowLobsterServiceImpl.class);
     @Autowired
     private CompanyWorkflowLobsterMapper lobsterMapper;
     @Autowired
@@ -48,6 +56,8 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
     private CompanyWorkflowLobsterRecordMapper recordMapper;
     @Autowired
     private CompanyWorkflowLobsterEdgeMapper edgeMapper;
+    @Autowired
+    private CompanyTagTemplateBindingMapper tagBindingMapper;
 
     @Override
     public List<CompanyWorkflowLobster> listTemplate(Long companyId, Integer page, Integer size) {
@@ -81,12 +91,13 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
         record.setDelFlag(0);
         String requestStr = "{ \"userContent\": \""+param.getRequirement()+"\", \"aiContent\": null }";
         R r = callAiService(requestStr, l, LOBSTER_KEY);
+        log.info("流程图生成成功: {}", param.getRequirement());
 //        System.out.println(r);
         record.setResultJson(buildResultJsonFromAi(r, param.getRequirement()));
         recordMapper.insertRecord(record);
         return recordNo;
     }
-    private static R callAiService(String requestParam, Long logId, String appKey) {
+    public static R callAiService(String requestParam, Long logId, String appKey) {
         try {
             ChatParam param = new ChatParam();
             param.setChatId(logId.toString());
@@ -257,7 +268,7 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
         );
 
         Map<String, Object> map = new HashMap<>();
-        map.put("status", 3);
+        map.put("status", template.getStatus());
         map.put("errorMsg", null);
         map.put("templateId", template.getId());
         map.put("templateCode", template.getTemplateCode());
@@ -281,9 +292,9 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
                 variableMap("follow_date", "跟进日期", "date", "manual", 0, "计划跟进时间")
         ));
         result.put("nodes", Arrays.asList(
-                nodeMap("START", "开始节点", 1, 1, "MSG_1", "", "", "{}"),
-                nodeMap("MSG_1", "消息节点", 2, 2, "END", "生成失败,请重新编写提示词", "", "{}"),
-                nodeMap("END", "结束节点", 5, 3, "", "", "", "{}")
+                nodeMap("START", "开始节点", 1, 1, "MSG_1", "", "", "{}", null),
+                nodeMap("MSG_1", "消息节点", 2, 2, "END", "生成失败,请重新编写提示词", "", "{}", null),
+                nodeMap("END", "结束节点", 5, 3, "", "", "", "{}", null)
         ));
         // 默认连线:START -> MSG_1 -> END
         result.put("edges", Arrays.asList(
@@ -325,7 +336,7 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
             }
 
             List<Map<String, Object>> nodes = new ArrayList<>();
-            nodes.add(nodeMap("START", "开始节点", 1, 1, "", "", "", "{}"));
+            nodes.add(nodeMap("START", "开始节点", 1, 1, "", "", "", "{}", null));
 
             int sortNo = 2;
             List<String> dayCodes = new ArrayList<>();
@@ -343,7 +354,8 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
                 String nodeCode = "DAY_" + day;
                 dayCodes.add(nodeCode);
                 String template = itemMap.get("content") == null ? "" : String.valueOf(itemMap.get("content"));
-                nodes.add(nodeMap(nodeCode, "第" + day + "天", 2, sortNo++, "", template, "", "{}"));
+                Object sendTime = itemMap.get("sendTime");
+                nodes.add(nodeMap(nodeCode, "第" + day + "天", 2, sortNo++, "", template, "", "{}", sendTime));
             }
 
             if (dayCodes.isEmpty()) {
@@ -371,7 +383,7 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
                 edges.add(edgeMap(edgeKey, currentCode, nextCode, "right", "left", "", "#999", ""));
             }
 
-            nodes.add(nodeMap("END", "结束节点", 5, sortNo, "", "", "", "{}"));
+            nodes.add(nodeMap("END", "结束节点", 5, sortNo, "", "", "", "{}", null));
 
             Map<String, Object> result = new HashMap<>();
             result.put("templateName", "AI生成工作流方案");
@@ -400,7 +412,7 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
         return m;
     }
 
-    private Map<String, Object> nodeMap(String nodeCode, String nodeName, Integer nodeType, Integer sortNo, String nextNodeCode, String messageTemplate, String conditionExpr, String nodeConfig) {
+    private Map<String, Object> nodeMap(String nodeCode, String nodeName, Integer nodeType, Integer sortNo, String nextNodeCode, String messageTemplate, String conditionExpr, String nodeConfig, Object sendTime) {
         Map<String, Object> m = new HashMap<>();
         m.put("nodeCode", nodeCode);
         m.put("nodeName", nodeName);
@@ -410,6 +422,7 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
         m.put("messageTemplate", messageTemplate);
         m.put("conditionExpr", conditionExpr);
         m.put("nodeConfig", nodeConfig);
+        m.put("sendTime", sendTime);
         return m;
     }
 
@@ -527,6 +540,12 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
             return AjaxResult.error("模板不存在");
         }
 
+        // 检查是否存在绑定的标签,有则提示先删除绑定
+        List<CompanyTagTemplateBinding> bindings = tagBindingMapper.selectBindingList(companyId, null, templateId);
+        if (bindings != null && !bindings.isEmpty()) {
+            return AjaxResult.error("已存在绑定标签,请先删除绑定关系");
+        }
+
         // 更新模板基本信息和画布数据
         template.setTemplateName(param.getTemplateName());
         template.setIndustryType(param.getIndustryType());
@@ -537,29 +556,51 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
         template.setUpdateTime(DateUtils.getNowDate());
         lobsterMapper.updateById(template);
 
-        // 先逻辑删除旧数据
-        variableMapper.deleteByWorkflowId(templateId);
-        nodeMapper.deleteByWorkflowId(templateId);
-        edgeMapper.deleteByWorkflowId(templateId);
+        // 查询旧数据
+        List<CompanyWorkflowLobsterVariable> oldVariables = variableMapper.selectByWorkflowId(templateId);
+        List<CompanyWorkflowLobsterNode> oldNodes = nodeMapper.selectByWorkflowId(templateId);
+        List<CompanyWorkflowLobsterEdge> oldEdges = edgeMapper.selectByWorkflowId(templateId);
 
-        // 保存变量
+        Date now = DateUtils.getNowDate();
+
+        // ==================== 变量:有id更新 / 无id新增 / 旧的且不在新参数中的逻辑删除 ====================
+        Set<Long> newVarIds = new HashSet<>();
         if (param.getVariables() != null && !param.getVariables().isEmpty()) {
-            variableMapper.batchInsert(param.getVariables().stream().map(v -> {
+            List<CompanyWorkflowLobsterVariable> insertVars = new ArrayList<>();
+            for (CompanyWorkflowLobsterVariable v : param.getVariables()) {
                 CompanyWorkflowLobsterVariable entity = new CompanyWorkflowLobsterVariable();
                 BeanUtils.copyProperties(v, entity);
                 entity.setWorkflowId(templateId);
                 entity.setDelFlag(0);
-                entity.setCreateBy(userName);
-                entity.setCreateTime(DateUtils.getNowDate());
                 entity.setUpdateBy(userName);
-                entity.setUpdateTime(DateUtils.getNowDate());
-                return entity;
-            }).collect(Collectors.toList()));
+                entity.setUpdateTime(now);
+                if (v.getId() != null) {
+                    entity.setId(v.getId());
+                    variableMapper.updateById(entity);
+                    newVarIds.add(v.getId());
+                } else {
+                    entity.setCreateBy(userName);
+                    entity.setCreateTime(now);
+                    insertVars.add(entity);
+                }
+            }
+            if (!insertVars.isEmpty()) {
+                variableMapper.batchInsert(insertVars);
+                insertVars.forEach(v -> newVarIds.add(v.getId()));
+            }
+        }
+        // 旧的且不在新参数中的变量 → 逻辑删除
+        for (CompanyWorkflowLobsterVariable old : oldVariables) {
+            if (!newVarIds.contains(old.getId())) {
+                variableMapper.deleteById(old.getId());
+            }
         }
 
-        // 保存节点(包含位置信息)
+        // ==================== 节点:有id更新 / 无id新增 / 旧的且不在新参数中的逻辑删除 ====================
+        Set<Long> newNodeIds = new HashSet<>();
         if (param.getNodes() != null && !param.getNodes().isEmpty()) {
-            nodeMapper.batchInsert(param.getNodes().stream().map(n -> {
+            List<CompanyWorkflowLobsterNode> insertNodes = new ArrayList<>();
+            for (CompanyWorkflowLobsterNode n : param.getNodes()) {
                 CompanyWorkflowLobsterNode entity = new CompanyWorkflowLobsterNode();
                 BeanUtils.copyProperties(n, entity);
                 entity.setWorkflowId(templateId);
@@ -567,17 +608,35 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
                     entity.setSortNo(0);
                 }
                 entity.setDelFlag(0);
-                entity.setCreateBy(userName);
-                entity.setCreateTime(DateUtils.getNowDate());
                 entity.setUpdateBy(userName);
-                entity.setUpdateTime(DateUtils.getNowDate());
-                return entity;
-            }).collect(Collectors.toList()));
+                entity.setUpdateTime(now);
+                if (n.getId() != null) {
+                    entity.setId(n.getId());
+                    nodeMapper.updateById(entity);
+                    newNodeIds.add(n.getId());
+                } else {
+                    entity.setCreateBy(userName);
+                    entity.setCreateTime(now);
+                    insertNodes.add(entity);
+                }
+            }
+            if (!insertNodes.isEmpty()) {
+                nodeMapper.batchInsert(insertNodes);
+                insertNodes.forEach(n -> newNodeIds.add(n.getId()));
+            }
+        }
+        // 旧的且不在新参数中的节点 → 逻辑删除
+        for (CompanyWorkflowLobsterNode old : oldNodes) {
+            if (!newNodeIds.contains(old.getId())) {
+                nodeMapper.deleteById(old.getId());
+            }
         }
 
-        // 保存连线
+        // ==================== 连线:有id更新 / 无id新增 / 旧的且不在新参数中的物理删除 ====================
+        Set<Long> newEdgeIds = new HashSet<>();
         if (param.getEdges() != null && !param.getEdges().isEmpty()) {
-            edgeMapper.batchInsert(param.getEdges().stream().map(e -> {
+            List<CompanyWorkflowLobsterEdge> insertEdges = new ArrayList<>();
+            for (CompanyWorkflowLobsterEdge e : param.getEdges()) {
                 CompanyWorkflowLobsterEdge entity = new CompanyWorkflowLobsterEdge();
                 BeanUtils.copyProperties(e, entity);
                 entity.setWorkflowId(templateId);
@@ -585,14 +644,57 @@ public class CompanyWorkflowLobsterServiceImpl implements ICompanyWorkflowLobste
                     entity.setSortNo(0);
                 }
                 entity.setDelFlag(0);
-                entity.setCreateBy(userName);
-                entity.setCreateTime(DateUtils.getNowDate());
                 entity.setUpdateBy(userName);
-                entity.setUpdateTime(DateUtils.getNowDate());
-                return entity;
-            }).collect(Collectors.toList()));
+                entity.setUpdateTime(now);
+                if (e.getId() != null) {
+                    entity.setId(e.getId());
+                    edgeMapper.updateById(entity);
+                    newEdgeIds.add(e.getId());
+                } else {
+                    entity.setCreateBy(userName);
+                    entity.setCreateTime(now);
+                    insertEdges.add(entity);
+                }
+            }
+            if (!insertEdges.isEmpty()) {
+                edgeMapper.batchInsert(insertEdges);
+                insertEdges.forEach(e -> newEdgeIds.add(e.getId()));
+            }
+        }
+        // 旧的且不在新参数中的连线 → 物理删除(避免唯一键冲突)
+        for (CompanyWorkflowLobsterEdge old : oldEdges) {
+            if (!newEdgeIds.contains(old.getId())) {
+                edgeMapper.deleteById(old.getId());
+            }
         }
 
         return AjaxResult.success("画布保存成功");
     }
+
+    @Override
+    public AjaxResult updateTemplateStatus(Long companyId, String username, Long templateId, Integer status) {
+        CompanyWorkflowLobster template = lobsterMapper.selectById(templateId);
+        if (template == null || !Objects.equals(template.getCompanyId(), companyId) || Objects.equals(template.getDelFlag(), 1)) {
+            return AjaxResult.error("模板不存在");
+        }
+        // 检查是否存在绑定的标签,有则提示先删除绑定
+        List<CompanyTagTemplateBinding> bindings = tagBindingMapper.selectBindingList(companyId, null, templateId);
+        if (bindings != null && !bindings.isEmpty()) {
+            return AjaxResult.error("已存在绑定标签,请先删除绑定关系");
+        }
+        template.setVersion(template.getVersion() == null ? 1 : template.getVersion() + 1);
+        template.setStatus(status);
+        template.setUpdateBy(username);
+        template.setUpdateTime(DateUtils.getNowDate());
+        lobsterMapper.updateById(template);
+
+        return AjaxResult.success("修改成功");
+    }
+
+    @Override
+    public List<CompanyWorkflowLobster> listTemplateByStatus(Long companyId, Integer status) {
+        List<CompanyWorkflowLobster> all = lobsterMapper.selectTemplateListByStatus(companyId, status);
+        return all;
+    }
+
 }

+ 29 - 0
fs-service/src/main/resources/mapper/company/CompanyLobsterTagUserRelMapper.xml

@@ -0,0 +1,29 @@
+<?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.company.mapper.CompanyLobsterTagUserRelMapper">
+
+    <resultMap id="CompanyLobsterTagUserRelResult" type="CompanyLobsterTagUserRel">
+        <result property="id"                column="id"/>
+        <result property="companyId"         column="company_id"/>
+        <result property="bindingId"         column="binding_id"/>
+        <result property="tagCode"           column="tag_code"/>
+        <result property="templateId"        column="template_id"/>
+        <result property="externalContactId" column="external_contact_id"/>
+        <result property="createBy"          column="create_by"/>
+        <result property="createTime"        column="create_time"/>
+    </resultMap>
+
+    <insert id="batchInsert">
+        insert into company_lobster_tag_user_rel
+        (company_id, binding_id, tag_code, template_id, external_contact_id, create_by, create_time,corp_id,company_user_id,qw_user_id)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.companyId}, #{item.bindingId}, #{item.tagCode}, #{item.templateId},
+             #{item.externalContactId}, #{item.createBy}, #{item.createTime},#{item.corpId},#{item.companyUserId},#{item.qwUserId})
+        </foreach>
+    </insert>
+    <update id="updateBatchRelBybinding">
+        update company_lobster_tag_user_rel set del_flag = #{flag} where binding_id = #{id}
+    </update>
+
+</mapper>

+ 12 - 3
fs-service/src/main/resources/mapper/company/CompanyTagTemplateBindingMapper.xml

@@ -36,7 +36,7 @@
 
     <insert id="insertBinding" parameterType="CompanyTagTemplateBinding" useGeneratedKeys="true" keyProperty="id">
         insert into company_tag_template_binding
-        (company_id, tag_code, tag_name, template_id, template_name, priority, match_condition, 
+        (company_id, tag_code, tag_name, template_id, template_name, priority, match_condition,
          status, del_flag, create_by, create_time, update_by, update_time)
         values
         (#{companyId}, #{tagCode}, #{tagName}, #{templateId}, #{templateName}, #{priority}, #{matchCondition},
@@ -92,14 +92,23 @@
         </foreach>
         order by priority desc
     </select>
+    <select id="listByStatus" resultType="com.fs.company.domain.CompanyTagTemplateBinding">
+        select id, company_id, tag_code, tag_name, template_id, template_name, priority,
+        match_condition, status, del_flag, create_by, create_time, update_by, update_time
+        from company_tag_template_binding
+        where del_flag = 0
+        and company_id = #{companyId}
+        and status = #{status}
+        order by priority desc, create_time desc
+    </select>
 
     <insert id="batchBind">
         insert into company_tag_template_binding
-        (company_id, tag_code, tag_name, template_id, template_name, priority, status, 
+        (company_id, tag_code, tag_name, template_id, template_name, priority, status,
          del_flag, create_by, create_time, update_by, update_time)
         values
         <foreach collection="tagCodes" item="tagCode" separator=",">
-            (#{companyId}, #{tagCode}, #{tagCode}, #{templateId}, '', 0, 1, 0, 
+            (#{companyId}, #{tagCode}, #{tagCode}, #{templateId}, '', 0, 1, 0,
              #{createBy}, #{createTime}, #{updateBy}, #{updateTime})
         </foreach>
     </insert>

+ 28 - 5
fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterEdgeMapper.xml

@@ -33,11 +33,16 @@
         </foreach>
     </insert>
 
-    <update id="deleteByWorkflowId">
-        update company_workflow_lobster_edge
-        set del_flag = 1
-        where workflow_id = #{workflowId} and del_flag = 0
-    </update>
+    <!--
+      这里使用物理删除而不是逻辑删除:
+      因为存在唯一索引 (workflow_id, edge_key, del_flag),当历史上已存在 del_flag=1 的同 edge_key 记录时,
+      再将 del_flag=0 更新为 1 会触发唯一键冲突(Duplicate entry)。
+      画布保存场景下连线是可重建数据,直接按 workflow_id 清理后重插最稳妥。
+    -->
+    <delete id="deleteByWorkflowId">
+        delete from company_workflow_lobster_edge
+        where workflow_id = #{workflowId}
+    </delete>
 
     <select id="selectByWorkflowId" resultMap="CompanyWorkflowLobsterEdgeResult">
         select id, workflow_id, edge_key, source_node_code, target_node_code, source_port, target_port,
@@ -47,4 +52,22 @@
         order by sort_no asc
     </select>
 
+    <update id="updateById">
+        update company_workflow_lobster_edge
+        <trim prefix="set" suffixOverrides=",">
+            <if test="entity.edgeKey != null">edge_key = #{entity.edgeKey},</if>
+            <if test="entity.sourceNodeCode != null">source_node_code = #{entity.sourceNodeCode},</if>
+            <if test="entity.targetNodeCode != null">target_node_code = #{entity.targetNodeCode},</if>
+            <if test="entity.sourcePort != null">source_port = #{entity.sourcePort},</if>
+            <if test="entity.targetPort != null">target_port = #{entity.targetPort},</if>
+            <if test="entity.edgeLabel != null">edge_label = #{entity.edgeLabel},</if>
+            <if test="entity.edgeColor != null">edge_color = #{entity.edgeColor},</if>
+            <if test="entity.conditionExpr != null">condition_expr = #{entity.conditionExpr},</if>
+            <if test="entity.sortNo != null">sort_no = #{entity.sortNo},</if>
+            <if test="entity.updateBy != null">update_by = #{entity.updateBy},</if>
+            <if test="entity.updateTime != null">update_time = #{entity.updateTime},</if>
+        </trim>
+        where id = #{entity.id}
+    </update>
+
 </mapper>

+ 9 - 0
fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterMapper.xml

@@ -43,6 +43,15 @@
           and del_flag = 0
         limit 1
     </select>
+    <select id="selectTemplateListByStatus" resultType="com.fs.company.domain.CompanyWorkflowLobster">
+        select id, company_id, template_code, template_name, industry_type, description, status, version,
+               create_by, create_time, update_by, update_time, del_flag
+        from company_workflow_lobster
+        where del_flag = 0
+          and company_id = #{companyId}
+          and status = #{status}
+        order by create_time desc
+    </select>
 
     <update id="updateTemplateById" parameterType="CompanyWorkflowLobster">
         update company_workflow_lobster

+ 23 - 4
fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterNodeMapper.xml

@@ -4,27 +4,46 @@
 
     <insert id="batchInsert">
         insert into company_workflow_lobster_node
-        (workflow_id, node_code, node_name, node_type, sort_no, next_node_code, message_template, condition_expr, node_config, greeting_config, create_by, create_time, update_by, update_time, del_flag)
+        (workflow_id, node_code, node_name, node_type, sort_no, next_node_code, message_template, condition_expr, node_config, greeting_config, send_time, create_by, create_time, update_by, update_time, del_flag)
         values
         <foreach collection="list" item="item" separator=",">
             (#{item.workflowId}, #{item.nodeCode}, #{item.nodeName}, #{item.nodeType}, #{item.sortNo}, #{item.nextNodeCode},
-            #{item.messageTemplate}, #{item.conditionExpr}, #{item.nodeConfig}, #{item.greetingConfig}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.delFlag})
+            #{item.messageTemplate}, #{item.conditionExpr}, #{item.nodeConfig}, #{item.greetingConfig}, #{item.sendTime}, #{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.delFlag})
         </foreach>
     </insert>
 
     <update id="deleteByWorkflowId">
         update company_workflow_lobster_node
         set del_flag = 1
-        where workflow_id = #{workflowId}
+        where workflow_id = #{workflowId} and del_flag = 0
+    </update>
+    <update id="updateBatch">
+        update company_workflow_lobster_node
+        set greeting_config = case id
+        <foreach collection="list" item="item">
+            when #{item.id} then #{item.greetingConfig}
+        </foreach>
+        end
+        where id in
+        <foreach collection="list" item="item" separator="," open="(" close=")">
+            #{item.id}
+        </foreach>
     </update>
 
     <select id="selectByWorkflowId" resultType="CompanyWorkflowLobsterNode">
         select id, workflow_id, node_code, node_name, node_type, sort_no, next_node_code, message_template,
-               condition_expr, node_config, greeting_config, create_by, create_time, update_by, update_time, del_flag
+               condition_expr, node_config, greeting_config, send_time, create_by, create_time, update_by, update_time, del_flag
         from company_workflow_lobster_node
         where workflow_id = #{workflowId}
           and del_flag = 0
         order by sort_no asc, id asc
     </select>
+    <select id="checkNodeGreeting" resultType="java.lang.Integer">
+        select count(1) from company_workflow_lobster_node
+        where workflow_id = #{templateId}
+          and greeting_config is null
+        and del_flag = 0
+        and node_type = 2
+    </select>
 
 </mapper>

+ 159 - 0
fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterTaskMapper.xml

@@ -0,0 +1,159 @@
+<?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.company.mapper.CompanyWorkflowLobsterTaskMapper">
+
+    <resultMap id="CompanyWorkflowLobsterTaskResult" type="CompanyWorkflowLobsterTask">
+        <result property="id"              column="id"/>
+        <result property="companyId"       column="company_id"/>
+        <result property="templateId"      column="template_id"/>
+        <result property="taskName"        column="task_name"/>
+        <result property="taskType"        column="task_type"/>
+        <result property="taskContent"     column="task_content"/>
+        <result property="cronExpression"  column="cron_expression"/>
+        <result property="executeStatus"   column="execute_status"/>
+        <result property="executeCount"    column="execute_count"/>
+        <result property="maxRetry"        column="max_retry"/>
+        <result property="retryCount"      column="retry_count"/>
+        <result property="lastExecuteTime" column="last_execute_time"/>
+        <result property="nextExecuteTime" column="next_execute_time"/>
+        <result property="failReason"      column="fail_reason"/>
+        <result property="sortOrder"       column="sort_order"/>
+        <result property="delFlag"         column="del_flag"/>
+        <result property="corpId"          column="corp_id"/>
+        <result property="companyUserId"   column="company_user_id"/>
+        <result property="lobsterNodeId"   column="lobster_node_id"/>
+        <result property="remark"          column="remark"/>
+        <result property="createBy"        column="create_by"/>
+        <result property="createTime"      column="create_time"/>
+        <result property="updateBy"        column="update_by"/>
+        <result property="updateTime"      column="update_time"/>
+    </resultMap>
+
+    <select id="selectByIdAndCompanyId" resultMap="CompanyWorkflowLobsterTaskResult">
+        select id, company_id, template_id, task_name, task_type, task_content,
+               cron_expression, execute_status, execute_count, max_retry, retry_count,
+               last_execute_time, next_execute_time, fail_reason, sort_order, del_flag,
+               corp_id, company_user_id, lobster_node_id, remark,
+               create_by, create_time, update_by, update_time
+        from company_workflow_lobster_task
+        where id = #{id}
+          and company_id = #{companyId}
+          and del_flag = 0
+        limit 1
+    </select>
+
+    <select id="selectByTemplateId" resultMap="CompanyWorkflowLobsterTaskResult">
+        select id, company_id, template_id, task_name, task_type, task_content,
+               cron_expression, execute_status, execute_count, max_retry, retry_count,
+               last_execute_time, next_execute_time, fail_reason, sort_order, del_flag,
+               corp_id, company_user_id, lobster_node_id, remark,
+               create_by, create_time, update_by, update_time
+        from company_workflow_lobster_task
+        where template_id = #{templateId}
+          and company_id = #{companyId}
+          and del_flag = 0
+        order by sort_order asc, id asc
+    </select>
+
+    <select id="selectPendingTasks" resultMap="CompanyWorkflowLobsterTaskResult">
+        select id, company_id, template_id, task_name, task_type, task_content,
+               cron_expression, execute_status, execute_count, max_retry, retry_count,
+               last_execute_time, next_execute_time, fail_reason, sort_order, del_flag,
+               corp_id, company_user_id, lobster_node_id, remark,
+               create_by, create_time, update_by, update_time
+        from company_workflow_lobster_task
+        where company_id = #{companyId}
+          and del_flag = 0
+          and execute_status = 0
+          and next_execute_time &lt;= #{beforeTime}
+        order by next_execute_time asc, sort_order asc
+        limit #{limit}
+    </select>
+
+    <insert id="insertTask" parameterType="CompanyWorkflowLobsterTask" useGeneratedKeys="true" keyProperty="id">
+        insert into company_workflow_lobster_task
+        (company_id, template_id, task_name, task_type, task_content, cron_expression,
+         execute_status, execute_count, max_retry, retry_count,
+         last_execute_time, next_execute_time, fail_reason, sort_order, del_flag,
+         corp_id, company_user_id, lobster_node_id, remark,
+         create_by, create_time, update_by, update_time)
+        values
+        (#{companyId}, #{templateId}, #{taskName}, #{taskType}, #{taskContent}, #{cronExpression},
+         #{executeStatus}, #{executeCount}, #{maxRetry}, #{retryCount},
+         #{lastExecuteTime}, #{nextExecuteTime}, #{failReason}, #{sortOrder}, #{delFlag},
+         #{corpId}, #{companyUserId}, #{lobsterNodeId}, #{remark},
+         #{createBy}, #{createTime}, #{updateBy}, #{updateTime})
+    </insert>
+
+    <update id="updateTaskById" parameterType="CompanyWorkflowLobsterTask">
+        update company_workflow_lobster_task
+        <trim prefix="set" suffixOverrides=",">
+            <if test="taskName != null">task_name = #{taskName},</if>
+            <if test="taskType != null">task_type = #{taskType},</if>
+            <if test="taskContent != null">task_content = #{taskContent},</if>
+            <if test="cronExpression != null">cron_expression = #{cronExpression},</if>
+            <if test="executeStatus != null">execute_status = #{executeStatus},</if>
+            <if test="executeCount != null">execute_count = #{executeCount},</if>
+            <if test="maxRetry != null">max_retry = #{maxRetry},</if>
+            <if test="retryCount != null">retry_count = #{retryCount},</if>
+            <if test="lastExecuteTime != null">last_execute_time = #{lastExecuteTime},</if>
+            <if test="nextExecuteTime != null">next_execute_time = #{nextExecuteTime},</if>
+            <if test="failReason != null">fail_reason = #{failReason},</if>
+            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+          and del_flag = 0
+    </update>
+
+    <update id="updateExecuteStatus">
+        update company_workflow_lobster_task
+        set execute_status = #{executeStatus},
+            execute_count = execute_count + 1,
+            last_execute_time = #{lastExecuteTime},
+            next_execute_time = #{nextExecuteTime}
+        <if test="failReason != null">
+            , fail_reason = #{failReason}
+        </if>
+        where id = #{id}
+          and del_flag = 0
+    </update>
+
+    <update id="logicalDeleteById">
+        update company_workflow_lobster_task
+        set del_flag = 1
+        where id = #{id}
+          and company_id = #{companyId}
+          and del_flag = 0
+    </update>
+    <update id="updateDelFlagByBinding">
+        update company_workflow_lobster_task
+        set del_flag = #{flag}
+        where binding_id = #{id}
+    </update>
+    <update id="updateTaskListExecuteStatus">
+        update company_workflow_lobster_task
+        <foreach item="item" collection="list" separator="," open="(" close=")" index="index">
+            set execute_status = #{item.executeStatus}
+            where
+                id =
+            #{item.id}
+        </foreach>
+    </update>
+
+    <insert id="batchInsert">
+        insert into company_workflow_lobster_task
+        (company_id, template_id, task_name, task_type, task_content,
+         corp_id,create_by, create_time, company_user_id, lobster_node_id, send_time,qw_user_id,binding_id)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.companyId}, #{item.templateId}, #{item.taskName}, #{item.taskType}, #{item.taskContent},
+             #{item.corpId},#{item.createBy}, #{item.createTime},
+            #{item.companyUserId}, #{item.lobsterNodeId},#{item.sendTime},#{item.qwUserId},#{item.bindingId}
+             )
+        </foreach>
+    </insert>
+
+</mapper>

+ 16 - 0
fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterVariableMapper.xml

@@ -27,4 +27,20 @@
         order by id asc
     </select>
 
+    <update id="updateById">
+        update company_workflow_lobster_variable
+        <trim prefix="set" suffixOverrides=",">
+            <if test="entity.varCode != null">var_code = #{entity.varCode},</if>
+            <if test="entity.varName != null">var_name = #{entity.varName},</if>
+            <if test="entity.varType != null">var_type = #{entity.varType},</if>
+            <if test="entity.sourceType != null">source_type = #{entity.sourceType},</if>
+            <if test="entity.required != null">required = #{entity.required},</if>
+            <if test="entity.defaultValue != null">default_value = #{entity.defaultValue},</if>
+            <if test="entity.description != null">description = #{entity.description},</if>
+            <if test="entity.updateBy != null">update_by = #{entity.updateBy},</if>
+            <if test="entity.updateTime != null">update_time = #{entity.updateTime},</if>
+        </trim>
+        where id = #{entity.id}
+    </update>
+
 </mapper>