2 次代码提交 8b6a83e645 ... 5a82947007

作者 SHA1 备注 提交日期
  caoliqin 5a82947007 feat:公司码相关功能 2 周之前
  caoliqin 07f44589de feat:客服管理、sop发课逻辑 2 周之前
共有 22 个文件被更改,包括 300 次插入94 次删除
  1. 18 5
      fs-admin/src/main/java/com/fs/app/controller/AppCustomerRoleMemberController.java
  2. 0 28
      fs-app-task/src/main/resources/application.yml
  3. 3 29
      fs-service/src/main/java/com/fs/app/cusrole/domain/AppCustomerRoleMember.java
  4. 19 0
      fs-service/src/main/java/com/fs/app/cusrole/dto/AppCustomerRoleMemberDTO.java
  5. 6 4
      fs-service/src/main/java/com/fs/app/cusrole/mapper/AppCustomerRoleMemberMapper.java
  6. 3 3
      fs-service/src/main/java/com/fs/app/cusrole/service/IAppCustomerRoleMemberService.java
  7. 29 6
      fs-service/src/main/java/com/fs/app/cusrole/service/impl/AppCustomerRoleMemberServiceImpl.java
  8. 5 5
      fs-service/src/main/java/com/fs/app/cusrole/vo/AppCustomerRoleMemberVO.java
  9. 5 0
      fs-service/src/main/java/com/fs/app/invitation/domain/AppCompanyInvitationCode.java
  10. 6 0
      fs-service/src/main/java/com/fs/app/invitation/mapper/AppCompanyInvitationCodeMapper.java
  11. 5 0
      fs-service/src/main/java/com/fs/app/invitation/service/IAppCompanyInvitationCodeService.java
  12. 20 0
      fs-service/src/main/java/com/fs/app/invitation/service/impl/AppCompanyInvitationCodeServiceImpl.java
  13. 44 0
      fs-service/src/main/java/com/fs/app/invitation/service/impl/AppInvitationCodeServiceImpl.java
  14. 3 0
      fs-service/src/main/java/com/fs/app/invitation/vo/AppInvitationCodeVO.java
  15. 13 0
      fs-service/src/main/java/com/fs/app/invitation/vo/AppRandomInvitationCodeVO.java
  16. 3 1
      fs-service/src/main/java/com/fs/app/sop/service/impl/AppSopUserLogServiceImpl.java
  17. 28 2
      fs-service/src/main/resources/application-config-druid-hst.yml
  18. 18 1
      fs-service/src/main/resources/mapper/app/AppCompanyInvitationCodeMapper.xml
  19. 19 9
      fs-service/src/main/resources/mapper/app/AppCustomerRoleMemberMapper.xml
  20. 4 1
      fs-service/src/main/resources/mapper/app/AppInvitationCodeMapper.xml
  21. 29 0
      fs-user-app/src/main/java/com/fs/app/controller/UserBindInvitationCodeController.java
  22. 20 0
      fs-user-app/src/main/java/com/fs/app/controller/UserInvitedController.java

+ 18 - 5
fs-admin/src/main/java/com/fs/app/controller/AppCustomerRoleMemberController.java

@@ -1,6 +1,7 @@
 package com.fs.app.controller;
 
 import com.fs.app.cusrole.domain.AppCustomerRoleMember;
+import com.fs.app.cusrole.dto.AppCustomerRoleMemberDTO;
 import com.fs.app.cusrole.service.IAppCustomerRoleMemberService;
 import com.fs.app.cusrole.vo.AppCustomerRoleMemberVO;
 import com.fs.common.annotation.Log;
@@ -50,8 +51,14 @@ public class AppCustomerRoleMemberController extends BaseController {
     @PreAuthorize("@ss.hasPermi('app:cusrolemember:add')")
     @Log(title = "app-客服", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody AppCustomerRoleMember appCustomerRoleMember) {
-        return toAjax(appCustomerRoleMemberService.insertAppCustomerRoleMember(appCustomerRoleMember));
+    public AjaxResult add(@RequestBody AppCustomerRoleMemberDTO dto) {
+        AppCustomerRoleMember member = new AppCustomerRoleMember();
+        member.setId(dto.getCompanyUserId());
+        member.setMemberName(dto.getCompanyUserName());
+        member.setAppCustomerId(dto.getAppCustomerId());
+        member.setAvatar(dto.getAvatar());
+        member.setRemark(dto.getRemark());
+        return toAjax(appCustomerRoleMemberService.insertAppCustomerRoleMember(member));
     }
 
     /**
@@ -60,8 +67,15 @@ public class AppCustomerRoleMemberController extends BaseController {
     @PreAuthorize("@ss.hasPermi('app:cusrolemember:edit')")
     @Log(title = "app-客服", businessType = BusinessType.UPDATE)
     @PutMapping
-    public AjaxResult edit(@RequestBody AppCustomerRoleMember appCustomerRoleMember) {
-        return toAjax(appCustomerRoleMemberService.updateAppCustomerRoleMember(appCustomerRoleMember));
+    public AjaxResult edit(@RequestBody AppCustomerRoleMemberDTO dto) {
+        AppCustomerRoleMember member = new AppCustomerRoleMember();
+        member.setRoleMemberId(dto.getRoleMemberId());
+        member.setId(dto.getCompanyUserId());
+        member.setMemberName(dto.getCompanyUserName());
+        member.setAppCustomerId(dto.getAppCustomerId());
+        member.setAvatar(dto.getAvatar());
+        member.setRemark(dto.getRemark());
+        return toAjax(appCustomerRoleMemberService.updateAppCustomerRoleMember(member));
     }
 
     /**
@@ -73,5 +87,4 @@ public class AppCustomerRoleMemberController extends BaseController {
     public AjaxResult remove(@PathVariable Long[] ids) {
         return toAjax(appCustomerRoleMemberService.deleteAppCustomerRoleMemberByIds(ids));
     }
-
 }

+ 0 - 28
fs-app-task/src/main/resources/application.yml

@@ -8,31 +8,3 @@ spring:
     #    active: druid-hdt
 #    active: dev
     active: druid-hst-test
-
-task:
-  enabled: true # 开启定时任务开关
-  mode: consumer # 区分是生产任务-producer,还是消费任务-consumer
-  producer:
-    gen-user-logs-delay: 60000 # 生成营期任务的间隔时间,单位毫秒
-    gen-user-logs-info-fragmentation-cron: 0 5 * * * * # 营期分片频率
-    gen-sop-logs-delay: 10000 #根据营期分片生成待执行记录的间隔时长
-    sync-watchlog-to-urgent-class-cron: 0 0 0/2 * * * # 同步看课记录到催课看板的频率
-    handle-repeat-data-delay: 1200000 # 处理待发记录重复数据的频次 20min = 20 * 60 * 1000
-    node: 0
-  consumer:
-    node: 0 # 消费节点编号,为消费者时必须设置且不可重复
-    future-timeout-seconds: 30 # future 等待时长
-    compensate-fixed-delay: 10000 # 消费补偿的间隔频率(10s)
-    consume-max-handle-minutes: 60 # 针对单条数据处理最大时长,超过则算作超时,单位分钟(60min)
-    consume-bind-tag-fixed-delay: 5000 # 消费绑定标签的间隔频率(5s)
-    consume-unbind-tag-fixed-delay: 5000 # 消费解绑标签的间隔频率(5s)
-    consume-bind-customer-fixed-delay: 5000 # 消费绑定客服的间隔频率(5s)
-    consume-unbind-customer-fixed-delay: 5000 # 消费解绑客服的间隔频率(5s)
-    consume-send-welcome-fixed-delay: 5000 # 消费发送欢迎语消息的间隔频率(5s)
-    consume-clear-app-sop-logs-cron: "0 0 1 * * *"
-    consume-clear-app-welcome-send-logs-cron: "0 5 1 * * *"
-    consume-auto-remind-ai-fixed-delay: 30000 # 自动 提醒/触达 ai的间隔频率(10s)
-    thread-pool:
-      core-pool-size: 30
-      max-pool-size: 50
-      thread-name-prefix: app-sop-send-thread-

+ 3 - 29
fs-service/src/main/java/com/fs/app/cusrole/domain/AppCustomerRoleMember.java

@@ -11,53 +11,28 @@ import java.util.Date;
 @Data
 public class AppCustomerRoleMember {
 
-    /**
-     * 物理主键:也是组内具体客服id,默认的一条等于客服组的id,根据客服组扩容策略,每次累加1
-     * 客服组扩容策略:由于一个客服只能添加5000(后续也可能调整)个用户,
-     * 但是要对于客户来说是无感的,所以添加客户是,除了id不一样,其余头像,名称等都一样,
-     * 都延用客服组的信息,这里为了方便,会把组的信息同步给每个组员,
-     * 也为了以后可能每个组员需要区别化配置,把名称,头像等信息字段都同步
-     */
     @TableId(type = IdType.AUTO)
+    private Long roleMemberId;
+
+    @Excel(name = "销售用户id")
     private Long id;
 
-    /**
-     * 客服组id
-     */
     @Excel(name = "客服组id")
     private Long appCustomerId;
 
-    /**
-     * 组成员名称
-     */
     @Excel(name = "组成员名称")
     private String memberName;
 
-    /**
-     * 组内成员编号,每个组都是从1开始递增的
-     */
     private Long memberNo;
 
-    /**
-     * 头像
-     */
     @Excel(name = "头像")
     private String avatar;
 
-    /**
-     * 备注
-     */
     private String remark;
 
-    /**
-     * 对应ai的配置信息主键
-     */
     @Excel(name = "对应ai的配置信息主键")
     private Long appFastgptRoleId;
 
-    /**
-     * 是否删除,0-未删除,1-已删除
-     */
     @Excel(name = "是否删除,0-未删除,1-已删除")
     private Integer isDelete;
 
@@ -68,5 +43,4 @@ public class AppCustomerRoleMember {
     //客服组内每个成员最大添加客户数
     @TableField(exist = false)
     private Integer memberMaxFriendCount;
-
 }

+ 19 - 0
fs-service/src/main/java/com/fs/app/cusrole/dto/AppCustomerRoleMemberDTO.java

@@ -0,0 +1,19 @@
+package com.fs.app.cusrole.dto;
+
+import lombok.Data;
+
+@Data
+public class AppCustomerRoleMemberDTO {
+
+    private Long roleMemberId;
+
+    private Long companyUserId;
+
+    private String companyUserName;
+
+    private Long appCustomerId;
+
+    private String avatar;
+
+    private String remark;
+}

+ 6 - 4
fs-service/src/main/java/com/fs/app/cusrole/mapper/AppCustomerRoleMemberMapper.java

@@ -17,10 +17,12 @@ import java.util.List;
 @Mapper
 public interface AppCustomerRoleMemberMapper extends BaseMapper<AppCustomerRoleMember> {
     /**
-     * 查询app-客服组-组员信息
-     *
-     * @param id app-客服组-组员信息主键
-     * @return app-客服组-组员信息
+     * 根据 role_member_id 查询
+     */
+    AppCustomerRoleMember selectAppCustomerRoleMemberByRoleMemberId(Long roleMemberId);
+
+    /**
+     * 根据 id 字段(销售用户id)查询
      */
     AppCustomerRoleMember selectAppCustomerRoleMemberById(Long id);
 

+ 3 - 3
fs-service/src/main/java/com/fs/app/cusrole/service/IAppCustomerRoleMemberService.java

@@ -16,7 +16,7 @@ public interface IAppCustomerRoleMemberService extends IService<AppCustomerRoleM
     /**
      * 查询app-客服组-组员信息
      *
-     * @param id app-客服组-组员信息主键
+     * @param id 销售用户id
      * @return app-客服组-组员信息
      */
     AppCustomerRoleMember selectAppCustomerRoleMemberById(Long id);
@@ -48,7 +48,7 @@ public interface IAppCustomerRoleMemberService extends IService<AppCustomerRoleM
     /**
      * 批量删除app-客服组-组员信息
      *
-     * @param ids 需要删除的app-客服组-组员信息主键集合
+     * @param ids 需要删除的 role_member_id 集合
      * @return 结果
      */
     int deleteAppCustomerRoleMemberByIds(Long[] ids);
@@ -56,7 +56,7 @@ public interface IAppCustomerRoleMemberService extends IService<AppCustomerRoleM
     /**
      * 删除app-客服组-组员信息信息
      *
-     * @param id app-客服组-组员信息主键
+     * @param id role_member_id
      * @return 结果
      */
     int deleteAppCustomerRoleMemberById(Long id);

+ 29 - 6
fs-service/src/main/java/com/fs/app/cusrole/service/impl/AppCustomerRoleMemberServiceImpl.java

@@ -6,6 +6,7 @@ import com.fs.app.cusrole.domain.AppCustomerRoleMember;
 import com.fs.app.cusrole.vo.AppCustomerRoleMemberVO;
 import com.fs.app.cusrole.mapper.AppCustomerRoleMemberMapper;
 import com.fs.app.cusrole.service.IAppCustomerRoleMemberService;
+import com.fs.common.exception.ServiceException;
 import org.springframework.stereotype.Service;
 
 import java.util.Date;
@@ -23,7 +24,7 @@ public class AppCustomerRoleMemberServiceImpl extends ServiceImpl<AppCustomerRol
     /**
      * 查询app-客服组-组员信息
      *
-     * @param id app-客服组-组员信息主键
+     * @param id 销售用户id
      * @return app-客服组-组员信息
      */
     @Override
@@ -50,6 +51,7 @@ public class AppCustomerRoleMemberServiceImpl extends ServiceImpl<AppCustomerRol
      */
     @Override
     public int insertAppCustomerRoleMember(AppCustomerRoleMember appCustomerRoleMember) {
+        validateCompanyUserNotBound(appCustomerRoleMember.getId(), null);
         if (appCustomerRoleMember.getMemberNo() == null && appCustomerRoleMember.getAppCustomerId() != null) {
             appCustomerRoleMember.setMemberNo(resolveNextMemberNo(appCustomerRoleMember.getAppCustomerId()));
         }
@@ -70,8 +72,9 @@ public class AppCustomerRoleMemberServiceImpl extends ServiceImpl<AppCustomerRol
      */
     @Override
     public int updateAppCustomerRoleMember(AppCustomerRoleMember appCustomerRoleMember) {
-        if (appCustomerRoleMember.getId() != null) {
-            AppCustomerRoleMember existing = baseMapper.selectAppCustomerRoleMemberById(appCustomerRoleMember.getId());
+        validateCompanyUserNotBound(appCustomerRoleMember.getId(), appCustomerRoleMember.getRoleMemberId());
+        if (appCustomerRoleMember.getRoleMemberId() != null) {
+            AppCustomerRoleMember existing = baseMapper.selectAppCustomerRoleMemberByRoleMemberId(appCustomerRoleMember.getRoleMemberId());
             if (existing != null && appCustomerRoleMember.getAppCustomerId() != null
                     && !appCustomerRoleMember.getAppCustomerId().equals(existing.getAppCustomerId())) {
                 appCustomerRoleMember.setMemberNo(resolveNextMemberNo(appCustomerRoleMember.getAppCustomerId()));
@@ -84,7 +87,7 @@ public class AppCustomerRoleMemberServiceImpl extends ServiceImpl<AppCustomerRol
     /**
      * 批量删除app-客服组-组员信息(逻辑删除)
      *
-     * @param ids 需要删除的app-客服组-组员信息主键
+     * @param ids 需要删除的 role_member_id
      * @return 结果
      */
     @Override
@@ -93,7 +96,7 @@ public class AppCustomerRoleMemberServiceImpl extends ServiceImpl<AppCustomerRol
         Date now = DateUtils.getNowDate();
         for (Long id : ids) {
             AppCustomerRoleMember member = new AppCustomerRoleMember();
-            member.setId(id);
+            member.setRoleMemberId(id);
             member.setIsDelete(1);
             member.setUpdateTime(now);
             count += baseMapper.updateAppCustomerRoleMember(member);
@@ -104,7 +107,7 @@ public class AppCustomerRoleMemberServiceImpl extends ServiceImpl<AppCustomerRol
     /**
      * 删除app-客服组-组员信息信息
      *
-     * @param id app-客服组-组员信息主键
+     * @param id role_member_id
      * @return 结果
      */
     @Override
@@ -117,6 +120,26 @@ public class AppCustomerRoleMemberServiceImpl extends ServiceImpl<AppCustomerRol
         return baseMapper.getMaxByCustomerGroupId(customerGroupId);
     }
 
+    /**
+     * 校验销售用户是否已被其他客服绑定
+     *
+     * @param companyUserId 销售用户id
+     * @param excludeRoleMemberId 修改时排除当前记录
+     */
+    private void validateCompanyUserNotBound(Long companyUserId, Long excludeRoleMemberId) {
+        if (companyUserId == null) {
+            return;
+        }
+        long boundCount = lambdaQuery()
+                .eq(AppCustomerRoleMember::getId, companyUserId)
+                .eq(AppCustomerRoleMember::getIsDelete, 0)
+                .ne(excludeRoleMemberId != null, AppCustomerRoleMember::getRoleMemberId, excludeRoleMemberId)
+                .count();
+        if (boundCount > 0) {
+            throw new ServiceException("该销售用户已被其他客服绑定");
+        }
+    }
+
     /**
      * 查询某客服组内最大的客服组内成员编号
      * @param appCustomerId 发送的客服组id

+ 5 - 5
fs-service/src/main/java/com/fs/app/cusrole/vo/AppCustomerRoleMemberVO.java

@@ -11,11 +11,13 @@ import java.util.Date;
 @Data
 public class AppCustomerRoleMemberVO {
 
-    private Long id;
+    private Long roleMemberId;
 
-    private Long appCustomerId;
+    private Long companyUserId;
+
+    private String companyUserName;
 
-    private String memberName;
+    private Long appCustomerId;
 
     private Long memberNo;
 
@@ -33,7 +35,5 @@ public class AppCustomerRoleMemberVO {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date updateTime;
 
-    /** 客服组名称 */
     private String roleName;
-
 }

+ 5 - 0
fs-service/src/main/java/com/fs/app/invitation/domain/AppCompanyInvitationCode.java

@@ -46,4 +46,9 @@ public class AppCompanyInvitationCode {
 
     private Date updateTime;
 
+    /**
+     * 公司二维码url
+     */
+    private String qrCode;
+
 }

+ 6 - 0
fs-service/src/main/java/com/fs/app/invitation/mapper/AppCompanyInvitationCodeMapper.java

@@ -2,6 +2,7 @@ package com.fs.app.invitation.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.app.invitation.domain.AppCompanyInvitationCode;
+import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
 
@@ -60,4 +61,9 @@ public interface AppCompanyInvitationCodeMapper extends BaseMapper<AppCompanyInv
      * @return 结果
      */
     int deleteAppCompanyInvitationCodeByIds(Long[] ids);
+
+    /**
+     * 根据公司id查询可用销售id列表
+     */
+    List<Long> selectInvitationCodeListByCompanyId(@Param("companyId") Long companyId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/app/invitation/service/IAppCompanyInvitationCodeService.java

@@ -59,4 +59,9 @@ public interface IAppCompanyInvitationCodeService extends IService<AppCompanyInv
      * @return 结果
      */
     int deleteAppCompanyInvitationCodeById(Long id);
+
+    /**
+     * 根据公司id轮询获取销售邀请码(销售id)
+     */
+    Long pollInvitationCodeByCompanyId(Long companyId);
 }

+ 20 - 0
fs-service/src/main/java/com/fs/app/invitation/service/impl/AppCompanyInvitationCodeServiceImpl.java

@@ -5,6 +5,8 @@ import com.fs.aiSipCall.utils.DateUtils;
 import com.fs.app.invitation.domain.AppCompanyInvitationCode;
 import com.fs.app.invitation.mapper.AppCompanyInvitationCodeMapper;
 import com.fs.app.invitation.service.IAppCompanyInvitationCodeService;
+import com.fs.common.core.redis.RedisCache;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
@@ -18,6 +20,11 @@ import java.util.List;
 @Service
 public class AppCompanyInvitationCodeServiceImpl extends ServiceImpl<AppCompanyInvitationCodeMapper, AppCompanyInvitationCode> implements IAppCompanyInvitationCodeService {
 
+    private static final String INVITATION_CODE_ROUND_ROBIN_KEY = "app:invitation_code:round_robin:";
+
+    @Autowired
+    private RedisCache redisCache;
+
     /**
      * 查询app-公司邀请码,公司扩展
      *
@@ -85,4 +92,17 @@ public class AppCompanyInvitationCodeServiceImpl extends ServiceImpl<AppCompanyI
     public int deleteAppCompanyInvitationCodeById(Long id) {
         return baseMapper.deleteAppCompanyInvitationCodeById(id);
     }
+
+    @Override
+    public Long pollInvitationCodeByCompanyId(Long companyId) {
+        List<Long> invitationCodeList = baseMapper.selectInvitationCodeListByCompanyId(companyId);
+        if (invitationCodeList == null || invitationCodeList.isEmpty()) {
+            return null;
+        }
+
+        // 轮询,按照公司id一次增加1,取余
+        Long seq = redisCache.incr(INVITATION_CODE_ROUND_ROBIN_KEY + companyId, 1L);
+        int index = Math.toIntExact(((seq - 1) % invitationCodeList.size()));
+        return invitationCodeList.get(index);
+    }
 }

+ 44 - 0
fs-service/src/main/java/com/fs/app/invitation/service/impl/AppInvitationCodeServiceImpl.java

@@ -16,8 +16,11 @@ import com.fs.app.invitation.service.IAppInvitationCodeTagService;
 import com.fs.app.invitation.vo.AppInvitationCodeVO;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.core.domain.R;
+import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.bean.BeanUtils;
+import com.fs.his.utils.qrcode.QRCodeUtils;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -25,6 +28,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class AppInvitationCodeServiceImpl extends ServiceImpl<AppInvitationCodeMapper, AppInvitationCode> implements IAppInvitationCodeService {
@@ -94,6 +98,9 @@ public class AppInvitationCodeServiceImpl extends ServiceImpl<AppInvitationCodeM
         }
         //公司邀请码
         else {
+            AppCompanyInvitationCode companyCode = this.appCompanyInvitationCodeService
+                    .selectAppCompanyInvitationCodeById(reqData.getId());
+
             this.appCompanyInvitationCodeService.lambdaUpdate()
                     .set(true, AppCompanyInvitationCode::getRemark, reqData.getRemark())
                     .set(true, AppCompanyInvitationCode::getTagIds,
@@ -102,6 +109,10 @@ public class AppInvitationCodeServiceImpl extends ServiceImpl<AppInvitationCodeM
                             ObjectUtil.isNotEmpty(reqData.getCustomerIds()) ? reqData.getCustomerIds().stream().map(String::valueOf).collect(Collectors.joining(",")) : null)
                     .eq(AppCompanyInvitationCode::getId, reqData.getId())
                     .update();
+            R qrResult = ensureCompanyInvitationQrCode(companyCode);
+            if (qrResult != null) {
+                return qrResult;
+            }
         }
         return R.ok();
     }
@@ -145,6 +156,7 @@ public class AppInvitationCodeServiceImpl extends ServiceImpl<AppInvitationCodeM
             AppInvitationCodeVO vo = new AppInvitationCodeVO();
             vo.setId(one.getId());
             vo.setInvitationCode(one.getCompanyId() + "");
+            vo.setQrCode(one.getQrCode());
             return vo;
         }
         AppInvitationCodeVO vo = new AppInvitationCodeVO();
@@ -152,4 +164,36 @@ public class AppInvitationCodeServiceImpl extends ServiceImpl<AppInvitationCodeM
         vo.setInvitationCode(one.getCompanyId() + "");
         return vo;
     }
+
+    /**
+     * 公司邀请码二维码:内容为公司id(邀请码)
+     *
+     * @return 成功返回 null
+     */
+    private R ensureCompanyInvitationQrCode(AppCompanyInvitationCode companyCode) {
+        if (companyCode == null || companyCode.getCompanyId() == null) {
+            return null;
+        }
+        if (StringUtils.isNotEmpty(companyCode.getQrCode())) {
+            return null;
+        }
+        try {
+            String invitationCode = String.valueOf(companyCode.getCompanyId());
+            R uploadResult = QRCodeUtils.createAndUpload(invitationCode);
+            Object url = uploadResult.get("url");
+            if (url == null || StringUtils.isEmpty(url.toString())) {
+                return R.error("二维码生成失败");
+            }
+            String qrCodeUrl = url.toString();
+            this.appCompanyInvitationCodeService.lambdaUpdate()
+                    .set(AppCompanyInvitationCode::getQrCode, qrCodeUrl)
+                    .eq(AppCompanyInvitationCode::getId, companyCode.getId())
+                    .update();
+            companyCode.setQrCode(qrCodeUrl);
+            return null;
+        } catch (Exception e) {
+            log.error("公司邀请码二维码生成失败, companyId={}", companyCode.getCompanyId(), e);
+            return R.error("二维码生成失败:" + e.getMessage());
+        }
+    }
 }

+ 3 - 0
fs-service/src/main/java/com/fs/app/invitation/vo/AppInvitationCodeVO.java

@@ -34,4 +34,7 @@ public class AppInvitationCodeVO {
 
     private String companyName;
 
+    /** 公司邀请码二维码地址 */
+    private String qrCode;
+
 }

+ 13 - 0
fs-service/src/main/java/com/fs/app/invitation/vo/AppRandomInvitationCodeVO.java

@@ -0,0 +1,13 @@
+package com.fs.app.invitation.vo;
+
+import lombok.Data;
+
+@Data
+public class AppRandomInvitationCodeVO {
+
+    /**
+     * 邀请码
+     */
+    private Long invitationCode;
+
+}

+ 3 - 1
fs-service/src/main/java/com/fs/app/sop/service/impl/AppSopUserLogServiceImpl.java

@@ -431,7 +431,9 @@ public class AppSopUserLogServiceImpl extends ServiceImpl<AppSopUserLogMapper, A
         sopLink.setQwUserId(userLogsInfo.getAppCustomerId());
         AppCustomerRoleMember roleMember = this.appCustomerRoleMemberService.lambdaQuery()
                 .eq(AppCustomerRoleMember::getIsDelete, 0)
-                .eq(AppCustomerRoleMember::getId, userLogsInfo.getAppCustomerId())
+                .eq(AppCustomerRoleMember::getAppCustomerId, userLogsInfo.getAppCustomerId())
+                .orderByDesc(AppCustomerRoleMember::getRoleMemberId)
+                .last("LIMIT 1")
                 .one();
         sopLink.setQwUserName(roleMember.getMemberName());
         sopLink.setVideoTitle(ObjectUtil.isNotEmpty(contentInfo.getLinkDescribe()) ? contentInfo.getLinkDescribe() : contentInfo.getLinkTitle());

+ 28 - 2
fs-service/src/main/resources/application-config-druid-hst.yml

@@ -111,8 +111,34 @@ config:
   receiver-prefix: U # IM发送消息,接收人前缀
   batch-size: 500 # 批量处理数据大小
   load: # 自定义条件加载配置
-    include: "sendSopLogs,generateSopLogs,consumer" # 包含需要加载的配置条件,*则是加载全部,如果需要指定加载组件,只需要和组件配置的名称一致即可,多个用英文逗号隔开,不需要用yml数组格式
+    include: "sendSopLogs,generateSopLogs,consumer,generateUrgentClass" # 包含需要加载的配置条件,*则是加载全部,如果需要指定加载组件,只需要和组件配置的名称一致即可,多个用英文逗号隔开,不需要用yml数组格式
 #    exclude: "*" # 排除需要加载的配置条件,*则是排除全部,如果需要指定排除组件,只需要和组件配置的名称一致即可,多个用英文逗号隔开,不需要用yml数组格式,优先级高于include
 
-
+task:
+  enabled: true # 开启定时任务开关
+  mode: consumer # 区分是生产任务-producer,还是消费任务-consumer
+  producer:
+    gen-user-logs-delay: 60000 # 生成营期任务的间隔时间,单位毫秒
+    gen-user-logs-info-fragmentation-cron: 0 5 * * * * # 营期分片频率
+    gen-sop-logs-delay: 10000 #根据营期分片生成待执行记录的间隔时长
+    sync-watchlog-to-urgent-class-cron: 0 0 0/2 * * * # 同步看课记录到催课看板的频率
+    handle-repeat-data-delay: 1200000 # 处理待发记录重复数据的频次 20min = 20 * 60 * 1000
+    node: 0
+  consumer:
+    node: 0 # 消费节点编号,为消费者时必须设置且不可重复
+    future-timeout-seconds: 30 # future 等待时长
+    compensate-fixed-delay: 10000 # 消费补偿的间隔频率(10s)
+    consume-max-handle-minutes: 60 # 针对单条数据处理最大时长,超过则算作超时,单位分钟(60min)
+    consume-bind-tag-fixed-delay: 5000 # 消费绑定标签的间隔频率(5s)
+    consume-unbind-tag-fixed-delay: 5000 # 消费解绑标签的间隔频率(5s)
+    consume-bind-customer-fixed-delay: 5000 # 消费绑定客服的间隔频率(5s)
+    consume-unbind-customer-fixed-delay: 5000 # 消费解绑客服的间隔频率(5s)
+    consume-send-welcome-fixed-delay: 5000 # 消费发送欢迎语消息的间隔频率(5s)
+    consume-clear-app-sop-logs-cron: "0 0 1 * * *"
+    consume-clear-app-welcome-send-logs-cron: "0 5 1 * * *"
+    consume-auto-remind-ai-fixed-delay: 30000 # 自动 提醒/触达 ai的间隔频率(10s)
+    thread-pool:
+      core-pool-size: 30
+      max-pool-size: 50
+      thread-name-prefix: app-sop-send-thread-
 

+ 18 - 1
fs-service/src/main/resources/mapper/app/AppCompanyInvitationCodeMapper.xml

@@ -13,10 +13,11 @@
         <result property="isDelete"    column="is_delete"    />
         <result property="createTime"    column="create_time"    />
         <result property="updateTime"    column="update_time"    />
+        <result property="qrCode"    column="qr_code"    />
     </resultMap>
 
     <sql id="selectAppCompanyInvitationCodeVo">
-        select id, company_id, remark, tag_ids, customer_ids, is_delete, create_time, update_time from app_company_invitation_code
+        select id, company_id, remark, tag_ids, customer_ids, is_delete, create_time, update_time, qr_code from app_company_invitation_code
     </sql>
 
     <select id="selectAppCompanyInvitationCodeList" parameterType="AppCompanyInvitationCode" resultMap="AppCompanyInvitationCodeResult">
@@ -44,6 +45,7 @@
             <if test="isDelete != null">is_delete,</if>
             <if test="createTime != null">create_time,</if>
             <if test="updateTime != null">update_time,</if>
+            <if test="qrCode != null">qr_code,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="companyId != null">#{companyId},</if>
@@ -53,6 +55,7 @@
             <if test="isDelete != null">#{isDelete},</if>
             <if test="createTime != null">#{createTime},</if>
             <if test="updateTime != null">#{updateTime},</if>
+            <if test="qrCode != null">#{qrCode},</if>
         </trim>
     </insert>
 
@@ -66,6 +69,7 @@
             <if test="isDelete != null">is_delete = #{isDelete},</if>
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="qrCode != null">qr_code = #{qrCode},</if>
         </trim>
         where id = #{id}
     </update>
@@ -80,4 +84,17 @@
             #{id}
         </foreach>
     </delete>
+
+    <select id="selectInvitationCodeListByCompanyId" resultType="java.lang.Long">
+        SELECT m.id
+        FROM app_company_invitation_code c
+        INNER JOIN app_customer_role_member m ON FIND_IN_SET(m.app_customer_id, c.customer_ids) > 0
+        WHERE c.company_id = #{companyId}
+          AND c.is_delete = 0
+          AND c.customer_ids IS NOT NULL
+          AND c.customer_ids != ''
+          AND m.is_delete = 0
+          AND m.id IS NOT NULL
+        ORDER BY m.role_member_id ASC
+    </select>
 </mapper>

+ 19 - 9
fs-service/src/main/resources/mapper/app/AppCustomerRoleMemberMapper.xml

@@ -5,6 +5,7 @@
 <mapper namespace="com.fs.app.cusrole.mapper.AppCustomerRoleMemberMapper">
 
     <resultMap type="AppCustomerRoleMember" id="AppCustomerRoleMemberResult">
+        <result property="roleMemberId"    column="role_member_id"    />
         <result property="id"    column="id"    />
         <result property="appCustomerId"    column="app_customer_id"    />
         <result property="memberName"    column="member_name"    />
@@ -18,14 +19,15 @@
     </resultMap>
 
     <sql id="selectAppCustomerRoleMemberVo">
-        select id, app_customer_id, member_name, member_no, avatar, remark, app_fastgpt_role_id, is_delete, create_time, update_time from app_customer_role_member
+        select role_member_id, id, app_customer_id, member_name, member_no, avatar, remark, app_fastgpt_role_id, is_delete, create_time, update_time from app_customer_role_member
     </sql>
 
     <select id="selectAppCustomerRoleMemberList" parameterType="AppCustomerRoleMember" resultType="com.fs.app.cusrole.vo.AppCustomerRoleMemberVO">
         select
-            m.id,
+            m.role_member_id as roleMemberId,
+            m.id as companyUserId,
+            m.member_name as companyUserName,
             m.app_customer_id as appCustomerId,
-            m.member_name as memberName,
             m.member_no as memberNo,
             m.avatar,
             m.remark,
@@ -46,14 +48,20 @@
         order by m.create_time desc
     </select>
 
+    <select id="selectAppCustomerRoleMemberByRoleMemberId" parameterType="Long" resultMap="AppCustomerRoleMemberResult">
+        <include refid="selectAppCustomerRoleMemberVo"/>
+        where role_member_id = #{roleMemberId}
+    </select>
+
     <select id="selectAppCustomerRoleMemberById" parameterType="Long" resultMap="AppCustomerRoleMemberResult">
         <include refid="selectAppCustomerRoleMemberVo"/>
         where id = #{id}
     </select>
 
-    <insert id="insertAppCustomerRoleMember" parameterType="AppCustomerRoleMember" useGeneratedKeys="true" keyProperty="id">
+    <insert id="insertAppCustomerRoleMember" parameterType="AppCustomerRoleMember" useGeneratedKeys="true" keyProperty="roleMemberId">
         insert into app_customer_role_member
         <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
             <if test="appCustomerId != null">app_customer_id,</if>
             <if test="memberName != null and memberName != ''">member_name,</if>
             <if test="memberNo != null">member_no,</if>
@@ -65,6 +73,7 @@
             <if test="updateTime != null">update_time,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
             <if test="appCustomerId != null">#{appCustomerId},</if>
             <if test="memberName != null and memberName != ''">#{memberName},</if>
             <if test="memberNo != null">#{memberNo},</if>
@@ -80,6 +89,7 @@
     <update id="updateAppCustomerRoleMember" parameterType="AppCustomerRoleMember">
         update app_customer_role_member
         <trim prefix="SET" suffixOverrides=",">
+            <if test="id != null">id = #{id},</if>
             <if test="appCustomerId != null">app_customer_id = #{appCustomerId},</if>
             <if test="memberName != null and memberName != ''">member_name = #{memberName},</if>
             <if test="memberNo != null">member_no = #{memberNo},</if>
@@ -90,15 +100,15 @@
             <if test="createTime != null">create_time = #{createTime},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
         </trim>
-        where id = #{id}
+        where role_member_id = #{roleMemberId}
     </update>
 
     <delete id="deleteAppCustomerRoleMemberById" parameterType="Long">
-        delete from app_customer_role_member where id = #{id}
+        delete from app_customer_role_member where role_member_id = #{id}
     </delete>
 
     <delete id="deleteAppCustomerRoleMemberByIds" parameterType="String">
-        delete from app_customer_role_member where id in
+        delete from app_customer_role_member where role_member_id in
         <foreach item="id" collection="array" open="(" separator="," close=")">
             #{id}
         </foreach>
@@ -112,8 +122,8 @@
             app_customer_role_member mem
         INNER JOIN app_customer_role acr ON acr.is_delete = 0 AND acr.id = mem.app_customer_id
         WHERE
-            mem.id = (
-                SELECT MAX(id) FROM app_customer_role_member WHERE app_customer_id = #{customerGroupId}
+            mem.role_member_id = (
+                SELECT MAX(role_member_id) FROM app_customer_role_member WHERE app_customer_id = #{customerGroupId}
             )
     </select>
 

+ 4 - 1
fs-service/src/main/resources/mapper/app/AppInvitationCodeMapper.xml

@@ -98,6 +98,7 @@
             c.company_id AS invitationCode,
             c.company_name AS companyName,
             acic.remark AS remark,
+            acic.qr_code AS qrCode,
             acic.tag_names AS tagNames,
             acic.customer_names AS customerNames,
             c.create_time AS createTime,
@@ -108,6 +109,7 @@
             SELECT
                 ic.company_id,
                 ic.remark,
+                ic.qr_code,
                 GROUP_CONCAT( DISTINCT ft.`name` ) AS tag_names,
                 GROUP_CONCAT( DISTINCT ft.`id` ) AS tag_ids,
                 GROUP_CONCAT( DISTINCT acr.role_name ) AS customer_names,
@@ -120,7 +122,8 @@
                 ic.is_delete = 0
             GROUP BY
                 ic.company_id,
-                ic.remark
+                ic.remark,
+                ic.qr_code
         ) acic ON acic.company_id = c.company_id
         WHERE
             c.is_del = 0

+ 29 - 0
fs-user-app/src/main/java/com/fs/app/controller/UserBindInvitationCodeController.java

@@ -0,0 +1,29 @@
+package com.fs.app.controller;
+
+import com.fs.app.invitation.dto.AppUserInvitationCodeDTO;
+import com.fs.app.invitation.service.IAppUserInvitationCodeService;
+import com.fs.common.core.domain.R;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * app-用户绑定邀请码
+ */
+@RestController
+@RequestMapping("/app/userInvited")
+@RequiredArgsConstructor
+public class UserBindInvitationCodeController {
+
+    private final IAppUserInvitationCodeService appUserInvitationCodeService;
+
+    @PostMapping("/bindInvForUser")
+    public R bindInvForUser(@RequestBody AppUserInvitationCodeDTO reqData) {
+        return appUserInvitationCodeService.bindInvForUser(reqData);
+    }
+
+
+
+}

+ 20 - 0
fs-user-app/src/main/java/com/fs/app/controller/UserInvitedController.java

@@ -3,6 +3,8 @@ package com.fs.app.controller;
 import java.util.List;
 
 import com.fs.app.annotation.Login;
+import com.fs.app.invitation.service.IAppCompanyInvitationCodeService;
+import com.fs.app.invitation.vo.AppRandomInvitationCodeVO;
 import com.fs.common.utils.StringUtils;
 import com.fs.his.domain.FsUserInvited;
 import com.fs.his.service.IFsUserInvitedService;
@@ -26,6 +28,9 @@ public class UserInvitedController extends AppBaseController {
     @Autowired
     private IFsUserInvitedService fsUserInvitedService;
 
+    @Autowired
+    private IAppCompanyInvitationCodeService appCompanyInvitationCodeService;
+
     /**
      * 查询用户填写邀请码记录列表
      */
@@ -86,4 +91,19 @@ public class UserInvitedController extends AppBaseController {
     {
         return AjaxResult.success(fsUserInvitedService.getDownloadPoster());
     }
+
+    /**
+     * 根据公司id轮询获取销售邀请码(销售id)
+     */
+    @GetMapping("/getInvitationCode")
+    public AjaxResult getInvitationCode(@RequestParam Long companyId)
+    {
+        Long invitationCode = appCompanyInvitationCodeService.pollInvitationCodeByCompanyId(companyId);
+        if (invitationCode == null) {
+            return AjaxResult.error("暂无可用的销售");
+        }
+        AppRandomInvitationCodeVO vo = new AppRandomInvitationCodeVO();
+        vo.setInvitationCode(invitationCode);
+        return AjaxResult.success(vo);
+    }
 }