Quellcode durchsuchen

活动客服活码

zoujiajian vor 2 Jahren
Ursprung
Commit
ec10f4fa11
39 geänderte Dateien mit 1572 neuen und 68 gelöschten Zeilen
  1. 9 1
      netflix-common/src/main/java/com/cyksj/common/constant/Constant.java
  2. 4 0
      netflix-dao/src/main/java/com/cyksj/mapper/OrderDonMapper.java
  3. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/corp/CorpFollowActiveCodeClickRecordMapper.java
  4. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/corp/CorpFollowActiveCodeMapper.java
  5. 19 0
      netflix-dao/src/main/java/com/cyksj/mapper/corp/CorpFollowDutyRelateMapper.java
  6. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/corp/CorpFollowServiceRelateMapper.java
  7. 36 0
      netflix-dao/src/main/java/com/cyksj/model/dto/CorpFollowActiveCodeDto.java
  8. 40 0
      netflix-dao/src/main/java/com/cyksj/model/dto/YhServiceDto.java
  9. 2 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollow.java
  10. 78 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollowActiveCode.java
  11. 16 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollowActiveCodeClickRecord.java
  12. 42 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollowDutyRelate.java
  13. 27 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollowServiceRelate.java
  14. 5 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CorpUser.java
  15. 1 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CouponUser.java
  16. 2 1
      netflix-dao/src/main/java/com/cyksj/model/manage/views/OrderDonBackView.java
  17. 5 0
      netflix-dao/src/main/java/com/cyksj/model/request/corp/CorpFollowContact.java
  18. 59 0
      netflix-dao/src/main/java/com/cyksj/model/views/CorpActiveCodeGoodsOrderView.java
  19. 44 0
      netflix-dao/src/main/java/com/cyksj/model/views/CorpActiveCodeOrderView.java
  20. 110 0
      netflix-dao/src/main/java/com/cyksj/model/views/CorpFollowActiveCodeView.java
  21. 21 0
      netflix-dao/src/main/resources/mapper/CorpFollowDutyRelateMapper.xml
  22. 23 0
      netflix-dao/src/main/resources/mapper/OrderDonMapper.xml
  23. 3 1
      netflix-service/src/main/java/com/cyksj/redis/RedisService.java
  24. 11 0
      netflix-service/src/main/java/com/cyksj/service/corp/CorpFollowActiveCodeFrontService.java
  25. 6 0
      netflix-service/src/main/java/com/cyksj/service/corp/CorpFollowDynamicConfigService.java
  26. 19 0
      netflix-service/src/main/java/com/cyksj/service/corp/CorpFollowService.java
  27. 7 3
      netflix-service/src/main/java/com/cyksj/service/corp/WxCorpOps.java
  28. 150 24
      netflix-service/src/main/java/com/cyksj/service/corp/impl/CorpEventActionServiceImpl.java
  29. 185 0
      netflix-service/src/main/java/com/cyksj/service/corp/impl/CorpFollowActiveCodeFrontServiceImpl.java
  30. 83 0
      netflix-service/src/main/java/com/cyksj/service/corp/impl/CorpFollowDynamicConfigServiceImpl.java
  31. 215 0
      netflix-service/src/main/java/com/cyksj/service/corp/impl/CorpFollowServiceImpl.java
  32. 19 2
      netflix-service/src/main/java/com/cyksj/service/corp/impl/WxCorpOpsImpl.java
  33. 28 1
      netflix-service/src/main/java/com/cyksj/service/user/impl/UserServiceImpl.java
  34. 32 0
      netflix-web/src/main/java/com/cyksj/web/controller/landing/CorpFollowActiveCodeController.java
  35. 8 1
      netflix-web/src/main/java/com/cyksj/web/controller/manage/CmsOrderController.java
  36. 213 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CmsCorpFollowActiveCodeController.java
  37. 0 34
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpFollowConfigController.java
  38. 11 0
      netflix-web/src/main/java/com/cyksj/web/controller/sys/SysConfigController.java
  39. 0 0
      netflix-web/src/main/resources/application-dev.yml

+ 9 - 1
netflix-common/src/main/java/com/cyksj/common/constant/Constant.java

@@ -174,7 +174,10 @@ public interface Constant {
 	 */
 	String CN_CYS = "¥";
 
-	String DY_CS_CODE_KEY = "抖音免费设备";
+	/**
+	 * 活码临时使用
+	 */
+	String ACTIVE_CODE_TEMP_KEY = "active_code_temp_key";
 
 	List<String> DY_CUS = List.of("QiuDaoYuBuGuoQi.", "YinHeLuXiangJuSheBeiShouQian");
 
@@ -195,4 +198,9 @@ public interface Constant {
 	String USER_OPEN_SHOP_DS_ORDERS_LIMIT_KEY = "user_open_shop_ds_orders_limit_key";
 
 	String DOUBLE_VERIFY_CODE_TIMES_KEY = "double_verify_code_times_key";
+
+	/**
+	 * 活码标记key
+	 */
+	String AC_CODE_KEY = "ac_code_key:";
 }

+ 4 - 0
netflix-dao/src/main/java/com/cyksj/mapper/OrderDonMapper.java

@@ -54,4 +54,8 @@ public interface OrderDonMapper extends BaseMapper<OrderDon> {
 	BigDecimal statisticsUsedBalance(@Param("startDate") Date startDate, @Param("endDate") Date endDate, @Param("noOrderAllStatus") List<String> noOrderAllStatus);
 
 	Map<String, Object> selectYhShopDistributeOrderDetail(@Param("startDate") Date startDate, @Param("endDate") Date endDate);
+
+	Integer selectCountActiveCodeOrderByAcId(Long acId);
+
+	Map<String, Object> selectActiveCodeOrderDetail(@Param("acId") Long acId, @Param("startTime") String startTime, @Param("endTime") String endTime);
 }

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/corp/CorpFollowActiveCodeClickRecordMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.corp;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CorpFollowActiveCodeClickRecord;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowActiveCodeClickRecordMapper
+ * 创建者: JavaZou
+ * 创建时间:2023/10/9 16:28
+ */
+public interface CorpFollowActiveCodeClickRecordMapper extends BaseMapper<CorpFollowActiveCodeClickRecord> {
+}

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/corp/CorpFollowActiveCodeMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.corp;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CorpFollowActiveCode;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowActiveCodeMapper
+ * 创建者: JavaZou
+ * 创建时间:2023/10/9 16:27
+ */
+public interface CorpFollowActiveCodeMapper extends BaseMapper<CorpFollowActiveCode> {
+}

+ 19 - 0
netflix-dao/src/main/java/com/cyksj/mapper/corp/CorpFollowDutyRelateMapper.java

@@ -0,0 +1,19 @@
+package com.cyksj.mapper.corp;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CorpFollowDutyRelate;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: CorpFollowDutyRelateMapper
+ * 创建者: JavaZou
+ * 创建时间:2023/10/9 17:20
+ */
+public interface CorpFollowDutyRelateMapper extends BaseMapper<CorpFollowDutyRelate> {
+	List<CorpFollowDutyRelate> selectCorpFollowRelate(@Param("list") List<String> followIds, @Param("isWeekend") Boolean isWeekend);
+
+	List<CorpFollowDutyRelate> selectTodayDutyRelate(@Param("list") List<String> followIds, @Param("type") Integer type);
+}

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/corp/CorpFollowServiceRelateMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.corp;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CorpFollowServiceRelate;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowServiceRelateMapper
+ * 创建者: JavaZou
+ * 创建时间:2023/10/11 9:49
+ */
+public interface CorpFollowServiceRelateMapper extends BaseMapper<CorpFollowServiceRelate> {
+}

+ 36 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/CorpFollowActiveCodeDto.java

@@ -0,0 +1,36 @@
+package com.cyksj.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowActiveCodeDto
+ * 创建者: JavaZou
+ * 创建时间:2023/10/9 17:10
+ */
+
+@Getter
+@Setter
+public class CorpFollowActiveCodeDto {
+
+	/**
+	 * 企业微信的客服id 数组
+	 */
+	private String followIds;
+
+	/**
+	 * 对应系统配置的客服
+	 */
+	private String serviceNames;
+
+	/**
+	 * 配置id
+	 */
+	private String configId;
+
+	/**
+	 * 活码
+	 */
+	private String qrCode;
+}

+ 40 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/YhServiceDto.java

@@ -0,0 +1,40 @@
+package com.cyksj.model.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: YhServiceDto
+ * 创建者: JavaZou
+ * 创建时间:2023/10/10 16:12
+ */
+@Getter
+@Setter
+public class YhServiceDto {
+	private String startTime;
+
+	private String endTime;
+
+	@JsonProperty("indx")
+	private Integer index;
+
+	private List<CustomerServiceDto> select;
+
+
+	@Getter
+	@Setter
+	public static class CustomerServiceDto{
+		private String name;
+
+		private String img;
+
+		@JsonProperty("indx")
+		private Integer index;
+
+		private String url;
+	}
+}

+ 2 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollow.java

@@ -1,5 +1,6 @@
 package com.cyksj.model.entity;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -9,6 +10,7 @@ import java.io.Serializable;
  * @date 2022/3/14 下午7:23
  */
 @Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
 public class CorpFollow extends BaseEntity implements Serializable {
 
     private Long wxCorpId;

+ 78 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollowActiveCode.java

@@ -0,0 +1,78 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.cyksj.model.request.corp.Attachments;
+import com.ejlchina.searcher.bean.DbIgnore;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowActiveCode
+ * 创建者: JavaZou
+ * 创建时间:2023/10/9 16:23
+ */
+@Getter
+@Setter
+public class CorpFollowActiveCode extends BaseEntity{
+
+	@NotEmpty(message = "活码标题不为空")
+	private String name;
+
+	@TableField(exist = false)
+	@DbIgnore
+	@NotNull(message = "请选择对应客服")
+	private List<String> followIds;
+
+	/**
+	 * 客服
+	 */
+	private String followStr;
+
+	/**
+	 * 欢迎语内容 文本、附件
+	 */
+	@TableField(updateStrategy = FieldStrategy.IGNORED)
+	private String text;
+
+	@TableField(exist = false)
+	@DbIgnore
+	private List<Attachments> attachments;
+
+	/**
+	 * 附件字符串
+	 */
+	@TableField(updateStrategy = FieldStrategy.IGNORED)
+	private String attachmentsStr;
+
+	/**
+	 * 活码
+	 */
+	private String codeStr;
+
+
+	/**
+	 * 客户标签
+	 */
+	@NotEmpty(message = "标签不为空")
+	private String markTag;
+
+	/**
+	 * 是否值班
+	 */
+	private Boolean isDuty;
+
+	/**
+	 * 创建者
+	 */
+	private String operator;
+
+	private Long couponId;
+
+	private Boolean deleted;
+}

+ 16 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollowActiveCodeClickRecord.java

@@ -0,0 +1,16 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowActiveCodeClickRecord
+ * 创建者: JavaZou
+ * 创建时间:2023/10/9 16:28
+ */
+@Getter
+@Setter
+public class CorpFollowActiveCodeClickRecord extends BaseEntity{
+	private Long acId;
+}

+ 42 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollowDutyRelate.java

@@ -0,0 +1,42 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: CorpFollowDutyRelate
+ * 创建者: JavaZou
+ * 创建时间:2023/10/9 17:19
+ */
+@Getter
+@Setter
+public class CorpFollowDutyRelate extends BaseEntity{
+
+	/**
+	 * 企业微信客服id
+	 */
+	private String followId;
+
+
+	/**
+	 * 企业微信客服名称
+	 */
+	private String followName;
+
+	/**
+	 * 值班客服名称
+	 */
+	private String serviceName;
+
+	private Integer st;
+
+	private Integer et;
+
+	private Integer indexTag;
+
+	/**
+	 * 1工作日,2.周六,3周末
+	 */
+	private Integer type;
+}

+ 27 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CorpFollowServiceRelate.java

@@ -0,0 +1,27 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowServiceRelate
+ * 创建者: JavaZou
+ * 创建时间:2023/10/11 9:48
+ */
+@Getter
+@Setter
+public class CorpFollowServiceRelate extends BaseEntity{
+
+	/**
+	 * 企业微信客服id
+	 */
+	private String followId;
+
+	private String followName;
+
+	/**
+	 * 值班客服名称
+	 */
+	private String serviceName;
+}

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CorpUser.java

@@ -47,4 +47,9 @@ public class CorpUser extends BaseEntity implements Serializable {
 	 * 添加渠道来源
 	 */
 	private String state;
+
+	/**
+	 * 活动活码id
+	 */
+	private Long acId;
 }

+ 1 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CouponUser.java

@@ -70,6 +70,7 @@ public class CouponUser extends BaseEntity {
 		corp_bulk_task("corp_bulk_task", "企业微信群发任务"),
 		channel_popularize("channel_popularize", "渠道推广"),
 		new_welfare("new_welfare", "新人福利"),
+		corp_active_code("corp_active_code", "企业微信活码")
 		;
 		private String channel;
 		private String desc;

+ 2 - 1
netflix-dao/src/main/java/com/cyksj/model/manage/views/OrderDonBackView.java

@@ -25,7 +25,8 @@ import java.util.List;
 @Setter
 @SearchBean(tables = "order_don o left join goods_don g on g.id = o.goods_id " +
 		"left join user u on o.user_id = u.id " +
-		"left join order_don_transport odt on odt.order_id = o.id"
+		"left join order_don_transport odt on odt.order_id = o.id",
+		where = ":condition:"
 )
 public class OrderDonBackView {
 	@DbField("o.id")

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/request/corp/CorpFollowContact.java

@@ -12,6 +12,11 @@ import java.util.List;
 @Setter
 @JsonInclude(JsonInclude.Include.NON_NULL)
 public class CorpFollowContact {
+	/**
+	 * 配置id
+	 */
+	private String configId;
+
 	/**
 	 * 联系方式类型,1-单人, 2-多人
 	 */

+ 59 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CorpActiveCodeGoodsOrderView.java

@@ -0,0 +1,59 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpActiveCodeGoodsOrderView
+ * 创建者: JavaZou
+ * 创建时间:2023/10/10 13:45
+ */
+@Getter
+@Setter
+@SearchBean(tables = "corp_user cu inner join order_don o on cu.user_id = o.user_id and cu.user_id != 0 and o.status not in ('close','noPayment') :acId: :time:" +
+		" left join goods_don g on g.id = o.goods_id" +
+		" left join goods_don_sku sku on sku.id = o.sku_id" +
+		" left join user u on u.id = o.user_id",
+		groupBy = "g.id,g.title")
+public class CorpActiveCodeGoodsOrderView {
+	/**
+	 * 平台名称
+	 */
+	@DbField("g.title")
+	private String goodsName;
+
+	@DbField("g.id")
+	private Long goodsId;
+
+	@DbField("g.type")
+	private Integer goodsDonType;
+
+	/**
+	 * 订单数
+	 */
+	@DbField("count(o.id)")
+	private Integer orderCount;
+
+	/**
+	 * 订单金额
+	 */
+	@DbField("IFNULL(sum(o.money),0)")
+	private BigDecimal orderMoney;
+
+	/**
+	 * 退款数
+	 */
+	@DbField("count(if(o.status = 'refund',1,NULL))")
+	private Integer refundCount;
+
+	/**
+	 * 退款金额
+	 */
+	@DbField("IFNULL(sum(if(o.status = 'refund',o.refund_money,0)),0)")
+	private BigDecimal refundMoney;
+}

+ 44 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CorpActiveCodeOrderView.java

@@ -0,0 +1,44 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpActiveCodeOrderView
+ * 创建者: JavaZou
+ * 创建时间:2023/10/10 11:35
+ */
+@Getter
+@Setter
+@SearchBean(tables = "corp_user cu inner join order_don o on cu.user_id = o.user_id and cu.user_id != 0 and o.status not in ('close','noPayment','refund') :acId:" +
+		" left join goods_don g on g.id = o.goods_id" +
+		" left join goods_don_sku sku on sku.id = o.sku_id" +
+		" left join user u on u.id = o.user_id")
+public class CorpActiveCodeOrderView {
+	@DbField("o.id")
+	private Long orderId;
+
+	@DbField("o.user_id")
+	private Long userId;
+
+	@DbField("u.nickname")
+	private String nickname;
+
+	@DbField("g.title")
+	private String title;
+
+	@DbField("sku.spec_val")
+	private String specVal;
+
+	@DbField("o.money")
+	private BigDecimal money;
+
+	@DbField("o.created_time")
+	private Date createdTime;
+}

+ 110 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CorpFollowActiveCodeView.java

@@ -0,0 +1,110 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.request.corp.Attachments;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowActiveCodeView
+ * 创建者: JavaZou
+ * 创建时间:2023/10/10 10:22
+ */
+@Getter
+@Setter
+@SearchBean(tables = "corp_follow_active_code cfa",
+		where = "cfa.deleted is true")
+public class CorpFollowActiveCodeView {
+
+	@DbField("cfa.id")
+	private Long id;
+
+	@DbField("cfa.name")
+	private String name;
+
+	/**
+	 * 客服
+	 */
+	@DbField("cfa.follow_str")
+	private String followStr;
+
+	@DbIgnore
+	private List<String> followIds;
+
+	/**
+	 * 欢迎语内容 文本、附件
+	 */
+	@DbField("cfa.text")
+	private String text;
+
+	@DbIgnore
+	private List<Attachments> attachments;
+
+	/**
+	 * 附件字符串
+	 */
+	@DbField("cfa.attachments_str")
+	private String attachmentsStr;
+
+	/**
+	 * 活码
+	 */
+	@DbField("cfa.code_str")
+	private String codeStr;
+
+
+	/**
+	 * 客户标签
+	 */
+	@DbField("cfa.mark_tag")
+	private String markTag;
+
+	/**
+	 * 是否值班
+	 */
+	@DbField("cfa.is_duty")
+	private Boolean isDuty;
+
+	/**
+	 * 创建者
+	 */
+	@DbField("cfa.operator")
+	private String operator;
+
+	@DbField("cfa.coupon_id")
+	private Long couponId;
+
+	/**
+	 * 优惠券名
+	 */
+	@DbIgnore
+	private String couponName;
+
+	/**
+	 * 用户数
+	 */
+	@DbIgnore
+	private Integer numOfUser;
+
+	/**
+	 * 订单数
+	 */
+	@DbIgnore
+	private Integer orderNum;
+
+	@DbIgnore
+	private Integer clickNum;
+
+	@DbIgnore
+	private BigDecimal orderMoney = BigDecimal.ZERO;
+
+	@DbField("cfa.created_time")
+	private Date createdTime;
+}

+ 21 - 0
netflix-dao/src/main/resources/mapper/CorpFollowDutyRelateMapper.xml

@@ -0,0 +1,21 @@
+<?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.cyksj.mapper.corp.CorpFollowDutyRelateMapper">
+
+
+    <select id="selectCorpFollowRelate" resultType="com.cyksj.model.entity.CorpFollowDutyRelate">
+        select st,et,index from corp_follow_duty_relate where follow_id in
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item}
+        </foreach> and is_weekend is ${isWeekend} group by st,et,index
+    </select>
+
+    <select id="selectTodayDutyRelate" resultType="com.cyksj.model.entity.CorpFollowDutyRelate">
+        select st,et from corp_follow_duty_relate where follow_id in
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item}
+        </foreach>
+        and type = #{type} group by st,et order by st
+    </select>
+</mapper>

+ 23 - 0
netflix-dao/src/main/resources/mapper/OrderDonMapper.xml

@@ -232,4 +232,27 @@
             and created_time &lt; #{endDate}
         </if>
     </select>
+
+    <select id="selectCountActiveCodeOrderByAcId" resultType="java.lang.Integer">
+        select count(distinct o.id)
+        from (select user_id from corp_user where ac_id = #{acId} and user_id != 0) s
+                 left join order_don o
+                           on o.user_id = s.user_Id
+        where o.status not in ('close', 'noPayment', 'refund')
+    </select>
+
+    <select id="selectActiveCodeOrderDetail" resultType="java.util.Map">
+        select count(distinct if(o.status != 'refund', o.id, null)) as orderNum,
+               sum(money) - sum(ifnull(refund_money, 0))            as orderMoney
+        from (select user_id from corp_user where ac_id = #{acId} and user_id != 0) s
+                 left join order_don o
+                           on o.user_id = s.user_Id
+        where o.status not in ('close', 'noPayment')
+        <if test="startTime != null">
+            and o.created_time >= #{startTime}
+        </if>
+        <if test="endTime != null">
+            and o.created_time &lt; #{endTime}
+        </if>
+    </select>
 </mapper>

+ 3 - 1
netflix-service/src/main/java/com/cyksj/redis/RedisService.java

@@ -652,6 +652,8 @@ public class RedisService {
         PLUS_GOODS_ADDRESS_KEY("plus_goods_address_key:", "优惠加购实物地址key", 10 * 60l),
         GROUPS_TRIPS_OUTSIDE_RELATION("groups_trips_outside_relation:key", "额外车位", 30l),
         DISTRIBUTE_RULE_TIME_KEY("DISTRIBUTE_RULE_TIME_KEY", "渠道规格记录时间key", 5 * 60L),
+        CORP_FOLLOW_ACTIVE_CODE_KEY("corp_follow_active_code_key:%s:%s", "活动客服活码key", 60 * 61),
+        CORP_FOLLOW_ACTIVE_CODE_CLEAN_KEY("corp_follow_active_code_key:%s", "活动客服活码清除key", -1),
         ;
 
         private String name;
@@ -664,7 +666,7 @@ public class RedisService {
             return this.name + env;
         }
         public String getNameFormat(Object ... str) {
-            return String.format(this.getName(), str);
+            return String.format(this.getEnvName(), str);
         }
 
     }

+ 11 - 0
netflix-service/src/main/java/com/cyksj/service/corp/CorpFollowActiveCodeFrontService.java

@@ -0,0 +1,11 @@
+package com.cyksj.service.corp;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowActiveCodeFrontService
+ * 创建者: JavaZou
+ * 创建时间:2023/10/10 14:36
+ */
+public interface CorpFollowActiveCodeFrontService {
+	String getCorpActiveQrCode(Long acId) throws Exception;
+}

+ 6 - 0
netflix-service/src/main/java/com/cyksj/service/corp/CorpFollowDynamicConfigService.java

@@ -2,6 +2,8 @@ package com.cyksj.service.corp;
 
 import com.cyksj.model.request.corp.CorpFollowContact;
 
+import java.util.List;
+
 /*
  *项目名: netflix
  *文件名: CorpFollowDynamicConfigService
@@ -10,4 +12,8 @@ import com.cyksj.model.request.corp.CorpFollowContact;
  */
 public interface CorpFollowDynamicConfigService {
 	String corpFollowContactConfig(CorpFollowContact corpFollowContact) throws Exception;
+
+	String createCorpFollowActiveCode(List<String> followIds, String state, Boolean isDuty) throws Exception;
+
+	void updateCorpFollowActiveCode(String oldMarkTag, String markTag) throws Exception;
 }

+ 19 - 0
netflix-service/src/main/java/com/cyksj/service/corp/CorpFollowService.java

@@ -0,0 +1,19 @@
+package com.cyksj.service.corp;
+
+import com.cyksj.model.entity.CorpFollowActiveCode;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowService
+ * 创建者: JavaZou
+ * 创建时间:2023/10/9 16:27
+ */
+public interface CorpFollowService{
+	void postActiveCode(Long adminId, CorpFollowActiveCode activeCode) throws Exception;
+
+	void upActiveCode(CorpFollowActiveCode activeCode) throws Exception;
+
+	void deleteActiveCodeById(Long id) throws Exception;
+
+	void syncActiveCodeFollowRelate(String config) throws Exception;
+}

+ 7 - 3
netflix-service/src/main/java/com/cyksj/service/corp/WxCorpOps.java

@@ -2,11 +2,10 @@ package com.cyksj.service.corp;
 
 import cn.hutool.json.JSONObject;
 import com.cyksj.dto.CorpOauth2UserInfo;
+import com.cyksj.model.request.corp.CorpFollowContact;
 import com.cyksj.model.request.corp.CorpGroupConfig;
 import com.cyksj.model.request.corp.CorpMsgTemplate;
 import com.cyksj.model.response.corp.*;
-import com.cyksj.model.request.corp.CorpFollowContact;
-
 
 import java.util.List;
 
@@ -140,7 +139,12 @@ public interface WxCorpOps {
 	void updateGroupsConfig(CorpGroupConfig config) throws Exception;
 
 	/**
-	 * 联系客配置
+	 * 联系客配置
 	 */
 	String corpFollowContactConfig(CorpFollowContact corpFollowContact) throws Exception;
+
+	/**
+	 * 更新客服配置
+	 */
+	void updateCorpFollowContactConfig(CorpFollowContact corpFollowContact) throws Exception;
 }

+ 150 - 24
netflix-service/src/main/java/com/cyksj/service/corp/impl/CorpEventActionServiceImpl.java

@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.lang.Validator;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONObject;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -25,6 +26,7 @@ import com.cyksj.model.response.FollowedUser;
 import com.cyksj.model.response.corp.CorpExternalContactInfo;
 import com.cyksj.model.response.corp.CorpUserExternalTagGroupInfo;
 import com.cyksj.model.response.corp.CorpUserExternalTagGroupList;
+import com.cyksj.service.corp.CorpService;
 import com.cyksj.service.corp.WxCorpOps;
 import com.cyksj.service.corp.msg.CorpEventActionService;
 import com.cyksj.service.mange.coupon.CouponCommonService;
@@ -83,6 +85,10 @@ public class CorpEventActionServiceImpl implements CorpEventActionService {
 
 	private final CouponCommonService couponCommonService;
 
+	private final CorpFollowActiveCodeMapper corpFollowActiveCodeMapper;
+
+	private final CorpService corpService;
+
 	private static final String DEFAULT_MSG = "您好,欢迎加入银河录像局";
 
 	/**
@@ -105,12 +111,12 @@ public class CorpEventActionServiceImpl implements CorpEventActionService {
 		}
 
 
-		THREAD_POOL.execute(()->{
+		THREAD_POOL.execute(() -> {
 			//2.客户添加成员信息
 			List<FollowedUser> followedUsers = externalContact.getFollowedUsers();
 			//保存企微用户
 			CorpUser corpUser = saveOrUpdateCorpUser(message, corpUserInfo, followedUsers);
-			if (StrUtil.equals(message.getState(), Constant.DY_CS_CODE_KEY)) {
+			if (StrUtil.isNotBlank(state)) {
 				try {
 					followedUsers = wxCorpOps.getExternalContact(message.getToUserName(), message.getExternalUserId()).getFollowedUsers();
 				} catch (Exception e) {
@@ -313,7 +319,7 @@ public class CorpEventActionServiceImpl implements CorpEventActionService {
 				.ifPresent(corpUser -> {
 					if (corpUser.getUserId() == 0) {
 						Optional.ofNullable(userMapper.selectOne(Wrappers.lambdaQuery(User.class)
-								.eq(User::getUnionid, corpUser.getUnionid()).last("limit 1")))
+										.eq(User::getUnionid, corpUser.getUnionid()).last("limit 1")))
 								.ifPresent(user -> {
 									corpUser.setUserId(user.getId());
 									corpUserMapper.updateById(corpUser);
@@ -414,8 +420,8 @@ public class CorpEventActionServiceImpl implements CorpEventActionService {
 	public CorpUser saveOrUpdateCorpUser(CorpXmlMessage message, ExternalContact corpUserInfo, List<FollowedUser> followedUsers) {
 		//查询客户是否存在
 		CorpUser corpUser = Optional.ofNullable(
-				corpUserMapper.selectOne(Wrappers.lambdaQuery(CorpUser.class)
-						.eq(CorpUser::getUnionid, corpUserInfo.getUnionId())))
+						corpUserMapper.selectOne(Wrappers.lambdaQuery(CorpUser.class)
+								.eq(CorpUser::getUnionid, corpUserInfo.getUnionId())))
 				.orElseGet(() -> new CorpUser()
 						.setUnionid(corpUserInfo.getUnionId()));
 
@@ -423,30 +429,64 @@ public class CorpEventActionServiceImpl implements CorpEventActionService {
 		corpUser.setGender(corpUserInfo.getGender());
 		corpUser.setAvatar(corpUserInfo.getAvatar());
 		corpUser.setType(corpUserInfo.getType());
-		//扫码来源
-		corpUser.setState(message.getState());
-		//打标签
-		if (StrUtil.equals(message.getState(), Constant.DY_CS_CODE_KEY) && CollUtil.isNotEmpty(followedUsers)) {
-			//查询对应标签是否存在
-			CorpTag corpTag = corpTagMapper.selectOne(Wrappers.lambdaQuery(CorpTag.class)
-					.eq(CorpTag::getName, Constant.DY_CS_CODE_KEY).last("limit 1"));
-			if (corpTag == null) {
+		String state = message.getState();
+		CorpFollowActiveCode corpFollowActiveCode = null;
+		if (StrUtil.isNotBlank(state) && corpUser.getId() == null && state.contains(Constant.AC_CODE_KEY)) {
+			//查找对应的活动活码 记录
+			String acIdStr = StrUtil.subAfter(state, StrUtil.C_COLON, true);
+			if (Validator.isNumber(acIdStr)) {
+				Long acId = Long.parseLong(acIdStr);
+				corpFollowActiveCode = corpFollowActiveCodeMapper.selectById(acId);
+				if (corpFollowActiveCode != null) {
+					corpUser.setAcId(acId);
+					String welcomeContent = corpFollowActiveCode.getText();
+					String attachmentsStr = corpFollowActiveCode.getAttachmentsStr();
+					//发送对应欢迎语
+					if (StrUtil.isNotBlank(welcomeContent) || StrUtil.isNotBlank(attachmentsStr)) {
+						THREAD_POOL.execute(() -> {
+							sendCorpFollowActiveCode(message, welcomeContent, attachmentsStr);
+						});
+					}
+				}
 				try {
-					corpTag = addCorpTag(Constant.DY_CS_CODE_KEY, Constant.DY_CS_CODE_KEY, message.getToUserName());
+					List<String> followIds = Jsons.parseList(corpFollowActiveCode.getFollowStr(), String.class);
+					//对客户进行标记
+					markAddUserTag(corpFollowActiveCode.getMarkTag(), message.getToUserName(), corpUserInfo.getExternalUserId(), followIds, followedUsers);
 				} catch (Exception e) {
 				}
+
 			}
-			if (corpTag != null) {
-				List<String> dy_tag = List.of(corpTag.getTagId());
-				followedUsers.forEach(follow -> {
-					if (!Constant.DY_CUS.contains(follow.getUserId())) {
-						return;
-					}
-					try {
-						wxCorpOps.markTag(corpUserInfo.getExternalUserId(), follow.getUserId(), dy_tag.toArray(new String[dy_tag.size()]), null);
-					} catch (Exception e) {
+		}
+		//扫码来源
+		if (corpUser.getId() == null) {
+			corpUser.setState(state);
+		}
+
+		//打标签 抖音渠道活码 已有 兼容之前的临时
+		if (StrUtil.isNotBlank(message.getState())) {
+			//打标签
+			SysConfig one = sysConfigService.getOne(Wrappers.lambdaQuery(SysConfig.class)
+					.eq(SysConfig::getSysKey, Constant.ACTIVE_CODE_TEMP_KEY).last("limit 1"));
+			if (one != null && StrUtil.isNotBlank(one.getSysValue()) && CollUtil.isNotEmpty(followedUsers)) {
+				try {
+					List<String> activeCodeList = Jsons.parseList(one.getSysValue(), String.class);
+					if (activeCodeList.contains(message.getState())) {
+						//查询对应标签是否存在
+						CorpTag corpTag = corpTagMapper.selectOne(Wrappers.lambdaQuery(CorpTag.class)
+								.eq(CorpTag::getName, message.getState()).last("limit 1"));
+						if (corpTag == null) {
+							try {
+								corpTag = addCorpTag(message.getState(), message.getState(), message.getToUserName());
+							} catch (Exception e) {
+							}
+						}
+						if (corpTag != null) {
+							List<String> tagId = List.of(corpTag.getTagId());
+							wxCorpOps.markTag(corpUserInfo.getExternalUserId(), message.getUserId(), tagId.toArray(new String[tagId.size()]), null);
+						}
 					}
-				});
+				} catch (Exception e) {
+				}
 			}
 		}
 
@@ -465,6 +505,11 @@ public class CorpEventActionServiceImpl implements CorpEventActionService {
 				}
 				//添加固定客服发放优惠券
 				isJoinFixedCustomer(user.getId(), message.getUserId());
+
+				//活码 优惠券发送
+				if (corpFollowActiveCode != null && corpFollowActiveCode.getCouponId() > 0) {
+					couponCommonService.sendCouponToUser(user.getId(), corpFollowActiveCode.getCouponId(), 0l, 0l, 0l, CouponUser.Channel.corp_active_code);
+				}
 			}
 			corpUserMapper.insert(corpUser);
 		} else {
@@ -492,6 +537,11 @@ public class CorpEventActionServiceImpl implements CorpEventActionService {
 				corpTag.setGroupName(tagGroup.getGroupName());
 				corpTag.setType(1);
 
+				CorpTagGroup corpTagGroup = new CorpTagGroup();
+				corpTagGroup.setGroupId(tagGroup.getGroupId());
+				corpTagGroup.setGroupName(tagGroup.getGroupName());
+				corpTagGroupMapper.insert(corpTagGroup);
+
 				corpTag.setTagId(save_tag.getId());
 				corpTag.setName(save_tag.getName());
 				corpTagMapper.insert(corpTag);
@@ -500,4 +550,80 @@ public class CorpEventActionServiceImpl implements CorpEventActionService {
 		}
 		return null;
 	}
+
+	/**
+	 * 发送添加活动活码客服 发送欢迎语
+	 */
+	public void sendCorpFollowActiveCode(CorpXmlMessage message, String content, String attachmentsStr) {
+		try {
+			String welcomeCode = message.getWelcomeCode();
+			String followId = message.getUserId();
+			JSONObject params = new JSONObject();
+			//新用户欢迎语
+			if (StrUtil.isNotBlank(content)) {
+				JSONObject text = new JSONObject();
+				text.putOpt("content", content);
+				params.putOpt("text", text);
+			}
+			//附件
+			LinkedList<Attachments> attachments;
+			if (StrUtil.isNotBlank(attachmentsStr)) {
+				List<Attachments> dbAttachments = Jsons.parseList(attachmentsStr, Attachments.class);
+				attachments = new LinkedList<>();
+				for (Attachments dbAttachment : dbAttachments) {
+					attachments.add(corpService.updateMaterial(dbAttachment, message.getToUserName(), 1));
+				}
+				params.putOpt("attachments", attachments);
+			}
+
+			params.putOpt("welcome_code", welcomeCode);
+			HttpResponse<byte[]> res = null;
+			res = J11HttpC.custom()
+					.ofPost()
+					.url(String.format("https://qyapi.weixin.qq.com/cgi-bin/externalcontact/send_welcome_msg?access_token=%s", wxCorpOps.getAccessToken(message.getToUserName())))
+					.body(HttpRequest.BodyPublishers.ofString(params.toString(), IoKit.Charsets.UTF_8.getCharset()))
+					.headers(J11HttpC.ReqType.raw_json)
+					.send(HttpResponse.BodyHandlers.ofByteArray());
+			JSONObject jsonObject = Jsons.parseObject(res.body(), JSONObject.class);
+			if (jsonObject.getInt("errcode") == 0) {
+				log.info("{}成员,发送欢迎语至客户{}成功", followId, message.getExternalUserId());
+				return;
+			}
+			log.info("{}成员,发送欢迎语至客户{}失败,msg:{}", followId, message.getExternalUserId(), jsonObject.get("errmsg"));
+		} catch (Exception e) {
+			log.info("发送欢迎语过程异常,{}", e);
+		}
+	}
+
+	/**
+	 * 创建标签 并标记客户
+	 */
+	public void markAddUserTag(String tagName, String corpId, String externalUserId, List<String> relateFollowIds, List<FollowedUser> followedUsers) {
+		//对客户进行标记
+		//查询对应标签是否存在
+		CorpTag corpTag = corpTagMapper.selectOne(Wrappers.lambdaQuery(CorpTag.class)
+				.eq(CorpTag::getName, tagName).last("limit 1"));
+		if (corpTag == null) {
+			try {
+				corpTag = addCorpTag(tagName, tagName, corpId);
+			} catch (Exception e) {
+			}
+		}
+		if (corpTag != null) {
+			List<String> tag = List.of(corpTag.getTagId());
+			try {
+				followedUsers.forEach(follow -> {
+					if (!relateFollowIds.contains(follow.getUserId())) {
+						return;
+					}
+					try {
+						wxCorpOps.markTag(externalUserId, follow.getUserId(), tag.toArray(new String[tag.size()]), null);
+					} catch (Exception e) {
+					}
+				});
+			} catch (Exception e) {
+			}
+
+		}
+	}
 }

+ 185 - 0
netflix-service/src/main/java/com/cyksj/service/corp/impl/CorpFollowActiveCodeFrontServiceImpl.java

@@ -0,0 +1,185 @@
+package com.cyksj.service.corp.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.Week;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.mapper.corp.CorpFollowActiveCodeClickRecordMapper;
+import com.cyksj.mapper.corp.CorpFollowActiveCodeMapper;
+import com.cyksj.mapper.corp.CorpFollowDutyRelateMapper;
+import com.cyksj.model.dto.CorpFollowActiveCodeDto;
+import com.cyksj.model.entity.CorpFollowActiveCode;
+import com.cyksj.model.entity.CorpFollowActiveCodeClickRecord;
+import com.cyksj.model.entity.CorpFollowDutyRelate;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.corp.CorpFollowActiveCodeFrontService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowActiveCodeFrontServiceImpl
+ * 创建者: JavaZou
+ * 创建时间:2023/10/10 14:37
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class CorpFollowActiveCodeFrontServiceImpl implements CorpFollowActiveCodeFrontService {
+
+	private final CorpFollowActiveCodeMapper corpFollowActiveCodeMapper;
+
+	private final CorpFollowDutyRelateMapper corpFollowDutyRelateMapper;
+
+	private final CorpFollowActiveCodeClickRecordMapper corpFollowActiveCodeClickRecordMapper;
+
+	private final RedisService redisService;
+
+	@Override
+	public String getCorpActiveQrCode(Long acId) throws Exception {
+		CorpFollowActiveCode corpFollowActiveCode = corpFollowActiveCodeMapper.selectById(acId);
+		if (corpFollowActiveCode == null) {
+			return null;
+		}
+		CorpFollowActiveCodeClickRecord clickRecord = new CorpFollowActiveCodeClickRecord();
+		clickRecord.setAcId(acId);
+		corpFollowActiveCodeClickRecordMapper.insert(clickRecord);
+
+		int hour = DateUtil.thisHour(true);
+		String activeCodeKey = RedisService.key.CORP_FOLLOW_ACTIVE_CODE_KEY.getNameFormat(acId, hour);
+		String qrCode = redisService.getStr(activeCodeKey);
+		if (StrUtil.isNotBlank(qrCode)) {
+			return qrCode;
+		}
+		Week week = DateUtil.dayOfWeekEnum(DateTime.now());
+		Integer type = 1;
+		if (week == Week.SATURDAY) {
+			type = 2;
+		}
+		if (week == Week.SUNDAY) {
+			type = 3;
+		}
+		String followStr = corpFollowActiveCode.getFollowStr();
+		List<String> followIds = Jsons.parseList(followStr, String.class);
+		String codeStr = corpFollowActiveCode.getCodeStr();
+		List<CorpFollowActiveCodeDto> activeCodeDtos = Jsons.parseList(codeStr, CorpFollowActiveCodeDto.class);
+		if (CollUtil.isEmpty(activeCodeDtos)) {
+			return null;
+		}
+		if (!corpFollowActiveCode.getIsDuty()) {
+			qrCode = activeCodeDtos.get(0).getQrCode();
+			redisService.set(activeCodeKey, qrCode, RedisService.key.CORP_FOLLOW_ACTIVE_CODE_KEY.getTimeout());
+			return qrCode;
+		}
+		//星期五-->星期六 星期六--->星期天 星期天-->星期一
+		//值班
+		if (corpFollowActiveCode.getIsDuty()) {
+			if (activeCodeDtos.size() == 1) {
+				CorpFollowActiveCodeDto corpFollowActiveCodeDto = activeCodeDtos.get(0);
+				qrCode = corpFollowActiveCodeDto.getQrCode();
+				redisService.set(activeCodeKey, qrCode, RedisService.key.CORP_FOLLOW_ACTIVE_CODE_KEY.getTimeout());
+				return qrCode;
+			}
+			List<CorpFollowDutyRelate> corpFollowDutyRelates = corpFollowDutyRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowDutyRelate.class)
+					.le(CorpFollowDutyRelate::getSt, hour)
+					.ge(CorpFollowDutyRelate::getEt, hour)
+					.eq(CorpFollowDutyRelate::getType, type));
+			if (CollUtil.isNotEmpty(corpFollowDutyRelates)) {
+				qrCode = getActiveQrCode(corpFollowDutyRelates, activeCodeDtos);
+				redisService.set(activeCodeKey, qrCode, RedisService.key.CORP_FOLLOW_ACTIVE_CODE_KEY.getTimeout());
+				return qrCode;
+			}
+			Integer index;
+			//未在时间期间
+			//若在当天之前
+			//若在当天之后 寻找下日值班客服
+			List<CorpFollowDutyRelate> todayCorpFollowDuty = corpFollowDutyRelateMapper.selectTodayDutyRelate(followIds, type);
+			CorpFollowDutyRelate relate = todayCorpFollowDuty.get(0);
+			Integer st = relate.getSt();
+			Integer et = relate.getEt();
+			//当天
+			if (hour < st) {
+				qrCode = getActiveQrCode(corpFollowDutyRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowDutyRelate.class)
+						.eq(CorpFollowDutyRelate::getSt, st)
+						.eq(CorpFollowDutyRelate::getEt, et)
+						.eq(CorpFollowDutyRelate::getType, type)), activeCodeDtos);
+				redisService.set(activeCodeKey, qrCode, RedisService.key.CORP_FOLLOW_ACTIVE_CODE_KEY.getTimeout());
+				return qrCode;
+			}
+			//下一天
+			CorpFollowDutyRelate corpFollowDutyRelate = todayCorpFollowDuty.get(todayCorpFollowDuty.size() - 1);
+			Integer f_et = corpFollowDutyRelate.getEt();
+			//如果是周五-->周六 周六下午-->周日 周日下午-->周一
+			if (hour > f_et) {
+				if (week == Week.FRIDAY) {
+					index = 3;
+					corpFollowDutyRelates = corpFollowDutyRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowDutyRelate.class)
+							.eq(CorpFollowDutyRelate::getIndexTag, index)
+							.in(CorpFollowDutyRelate::getFollowId, followIds));
+					if (CollUtil.isEmpty(corpFollowDutyRelates)) {
+						index = 4;
+						corpFollowDutyRelates = corpFollowDutyRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowDutyRelate.class)
+								.eq(CorpFollowDutyRelate::getIndexTag, index)
+								.in(CorpFollowDutyRelate::getFollowId, followIds));
+					}
+					qrCode = getActiveQrCode(corpFollowDutyRelates, activeCodeDtos);
+				} else if (week == Week.SATURDAY) {
+					index = 5;
+					corpFollowDutyRelates = corpFollowDutyRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowDutyRelate.class)
+							.eq(CorpFollowDutyRelate::getIndexTag, index)
+							.in(CorpFollowDutyRelate::getFollowId, followIds));
+					if (CollUtil.isEmpty(corpFollowDutyRelates)) {
+						index = 6;
+						corpFollowDutyRelates = corpFollowDutyRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowDutyRelate.class)
+								.eq(CorpFollowDutyRelate::getIndexTag, index)
+								.in(CorpFollowDutyRelate::getFollowId, followIds));
+					}
+					qrCode = getActiveQrCode(corpFollowDutyRelates, activeCodeDtos);
+				}else {
+					index = 1;
+					corpFollowDutyRelates = corpFollowDutyRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowDutyRelate.class)
+							.eq(CorpFollowDutyRelate::getIndexTag, index)
+							.in(CorpFollowDutyRelate::getFollowId, followIds));
+					if (CollUtil.isEmpty(corpFollowDutyRelates)) {
+						index = 2;
+						corpFollowDutyRelates = corpFollowDutyRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowDutyRelate.class)
+								.eq(CorpFollowDutyRelate::getIndexTag, index)
+								.in(CorpFollowDutyRelate::getFollowId, followIds));
+					}
+					qrCode = getActiveQrCode(corpFollowDutyRelates, activeCodeDtos);
+				}
+				redisService.set(activeCodeKey, qrCode, RedisService.key.CORP_FOLLOW_ACTIVE_CODE_KEY.getTimeout());
+				return qrCode;
+			}
+			//兜底
+			log.info("当前时间:{}客户通过活动acId:{}未到获取客服活码,进行兜底", DateTime.now(), acId);
+			corpFollowDutyRelates = corpFollowDutyRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowDutyRelate.class)
+					.in(CorpFollowDutyRelate::getFollowId, followIds));
+			qrCode = getActiveQrCode(corpFollowDutyRelates, activeCodeDtos);
+			redisService.set(activeCodeKey, qrCode, RedisService.key.CORP_FOLLOW_ACTIVE_CODE_KEY.getTimeout());
+			return qrCode;
+		}
+		return null;
+	}
+
+
+	public String getActiveQrCode(List<CorpFollowDutyRelate> corpFollowDutyRelates, List<CorpFollowActiveCodeDto> activeCodeDtos) {
+		Integer randNum = 0;
+		if (corpFollowDutyRelates.size() > 1) {
+			randNum = RandomUtil.randomInt(corpFollowDutyRelates.size());
+		}
+		for (CorpFollowActiveCodeDto activeCodeDto : activeCodeDtos) {
+			if (activeCodeDto.getFollowIds().contains(corpFollowDutyRelates.get(randNum).getFollowId())) {
+				return activeCodeDto.getQrCode();
+			}
+		}
+		return null;
+	}
+}

+ 83 - 0
netflix-service/src/main/java/com/cyksj/service/corp/impl/CorpFollowDynamicConfigServiceImpl.java

@@ -1,9 +1,18 @@
 package com.cyksj.service.corp.impl;
 
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
 import com.cyksj.common.util.Jsons;
 import com.cyksj.mapper.corp.CorpFollowConfigMapper;
+import com.cyksj.mapper.corp.CorpFollowDutyRelateMapper;
+import com.cyksj.mapper.corp.CorpFollowServiceRelateMapper;
+import com.cyksj.mapper.corp.CorpTagMapper;
+import com.cyksj.model.dto.CorpFollowActiveCodeDto;
 import com.cyksj.model.entity.CorpFollowConfig;
+import com.cyksj.model.entity.CorpFollowServiceRelate;
+import com.cyksj.model.entity.CorpTag;
 import com.cyksj.model.request.corp.CorpFollowContact;
 import com.cyksj.service.corp.CorpFollowDynamicConfigService;
 import com.cyksj.service.corp.WxCorpOps;
@@ -11,7 +20,10 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /*
  *项目名: netflix
@@ -27,6 +39,12 @@ public class CorpFollowDynamicConfigServiceImpl implements CorpFollowDynamicConf
 
 	private final CorpFollowConfigMapper corpFollowConfigMapper;
 
+	private final CorpFollowDutyRelateMapper corpFollowDutyRelateMapper;
+
+	private final CorpFollowServiceRelateMapper serviceRelateMapper;
+
+	private final CorpTagMapper corpTagMapper;
+
 	@Override
 	public String corpFollowContactConfig(CorpFollowContact corpFollowContact) throws Exception {
 		String body = wxCorpOps.corpFollowContactConfig(corpFollowContact);
@@ -43,4 +61,69 @@ public class CorpFollowDynamicConfigServiceImpl implements CorpFollowDynamicConf
 		});
 		return body;
 	}
+
+	@Override
+	public String createCorpFollowActiveCode(List<String> followIds, String state, Boolean isDuty) throws Exception {
+		List<CorpFollowActiveCodeDto> codeList = new ArrayList<>();
+		Integer type = 1;
+		if (!isDuty) {
+			//不是值班 多人生成一个二维码
+			//联系方式类型 2-多人
+			if (followIds.size() > 1) {
+				type = 2;
+			}
+			String serviceNames = serviceRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowServiceRelate.class)
+					.in(CorpFollowServiceRelate::getFollowId, followIds)).stream().map(CorpFollowServiceRelate::getServiceName).collect(Collectors.joining(","));
+			if (StrUtil.isEmpty(serviceNames)) {
+				throw BusinessRuntimeException.getInstance("存在被删除的客服,请刷新页面重试");
+			}
+			CorpFollowActiveCodeDto corpFollowActiveCodeDto = createCorpFollowActiveCode(followIds, state, type, serviceNames);
+			codeList.add(corpFollowActiveCodeDto);
+		} else {
+			Map<String, List<CorpFollowServiceRelate>> collect = serviceRelateMapper.selectList(Wrappers.lambdaQuery(CorpFollowServiceRelate.class)
+					.in(CorpFollowServiceRelate::getFollowId, followIds)).stream().collect(Collectors.groupingBy(CorpFollowServiceRelate::getFollowId));
+			if (collect.keySet().size() != followIds.size()) {
+				throw BusinessRuntimeException.getInstance("存在被删除的客服,请刷新页面重试");
+			}
+			for (String followId : followIds) {
+				CorpFollowActiveCodeDto corpFollowActiveCodeDto = createCorpFollowActiveCode(List.of(followId), state, type, collect.get(followId).get(0).getServiceName());
+				codeList.add(corpFollowActiveCodeDto);
+			}
+		}
+		return Jsons.toJson(codeList);
+	}
+
+	@Override
+	public void updateCorpFollowActiveCode(String oldMarkTag, String markTag) throws Exception {
+		CorpTag corpTag = corpTagMapper.selectOne(Wrappers.lambdaQuery(CorpTag.class)
+				.eq(CorpTag::getName, oldMarkTag).last("limit 1"));
+		if (corpTag != null) {
+			//更新该标签名
+			JSONObject params = new JSONObject();
+			params.putOpt("id", corpTag.getTagId());
+			params.putOpt("name", markTag);
+			wxCorpOps.editCorpTag(params);
+			corpTag.setName(markTag);
+			corpTagMapper.updateById(corpTag);
+		}
+	}
+
+	public CorpFollowActiveCodeDto createCorpFollowActiveCode(List<String> followIds, String state, Integer type, String serviceName) throws Exception {
+		CorpFollowContact c = new CorpFollowContact();
+		c.setType(type);
+		c.setScene(2);
+		c.setUser(followIds);
+		c.setState(state);
+		c.setRemark("客服活动活码");
+		String body = wxCorpOps.corpFollowContactConfig(c);
+		JSONObject jsonObject = Jsons.parseObject(body, JSONObject.class);
+		String qrCode = jsonObject.getStr("qr_code");
+		String configId = jsonObject.getStr("config_id");
+		CorpFollowActiveCodeDto corpFollowActiveCodeDto = new CorpFollowActiveCodeDto();
+		corpFollowActiveCodeDto.setFollowIds(followIds.toString());
+		corpFollowActiveCodeDto.setServiceNames(serviceName);
+		corpFollowActiveCodeDto.setQrCode(qrCode);
+		corpFollowActiveCodeDto.setConfigId(configId);
+		return corpFollowActiveCodeDto;
+	}
 }

+ 215 - 0
netflix-service/src/main/java/com/cyksj/service/corp/impl/CorpFollowServiceImpl.java

@@ -0,0 +1,215 @@
+package com.cyksj.service.corp.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.constant.Constant;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.mapper.corp.CorpFollowActiveCodeMapper;
+import com.cyksj.mapper.corp.CorpFollowDutyRelateMapper;
+import com.cyksj.mapper.corp.CorpFollowServiceRelateMapper;
+import com.cyksj.mapper.corp.CorpTagMapper;
+import com.cyksj.model.dto.YhServiceDto;
+import com.cyksj.model.entity.*;
+import com.cyksj.model.request.corp.Attachments;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.corp.CorpFollowDynamicConfigService;
+import com.cyksj.service.corp.CorpFollowService;
+import com.cyksj.service.corp.WxCorpOps;
+import com.cyksj.service.mange.cms.CmsUserManager;
+import lombok.RequiredArgsConstructor;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowServiceImpl
+ * 创建者: JavaZou
+ * 创建时间:2023/10/9 16:27
+ */
+@Service
+@RequiredArgsConstructor
+public class CorpFollowServiceImpl implements CorpFollowService {
+	private final CorpFollowActiveCodeMapper corpFollowActiveCodeMapper;
+
+	private final CmsUserManager cmsUserManager;
+
+	private final CorpFollowDynamicConfigService corpFollowDynamicConfigService;
+
+	private final CorpFollowDutyRelateMapper corpFollowDutyRelateMapper;
+
+	private final CorpFollowServiceRelateMapper corpFollowServiceRelateMapper;
+
+	private final WxCorpOps wxCorpOps;
+
+	private final CorpTagMapper corpTagMapper;
+
+	private final RedisService redisService;
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	public void postActiveCode(Long adminId, CorpFollowActiveCode activeCode) throws Exception {
+		CmsUser cmsUser = cmsUserManager.getUser(adminId);
+		if (cmsUser != null) {
+			activeCode.setOperator(cmsUser.getNickname());
+		}
+		Boolean isDuty = Optional.ofNullable(activeCode.getIsDuty()).orElse(false);
+		List<String> followIds = activeCode.getFollowIds();
+		if (CollUtil.isEmpty(followIds)) {
+			throw BusinessRuntimeException.getInstance("请选择对应客服");
+		}
+		activeCode.setFollowStr(Jsons.toJson(followIds));
+		activeCode.setMarkTag(activeCode.getMarkTag().trim());
+		String text = activeCode.getText();
+		if (StrUtil.isNotBlank(text) && text.length() > 4000) {
+			throw BusinessRuntimeException.getInstance("欢迎语内容过长");
+		}
+		//附件
+		List<Attachments> attachments = activeCode.getAttachments();
+		if (CollUtil.isNotEmpty(attachments)) {
+			activeCode.setAttachmentsStr(Jsons.toJson(attachments));
+		}
+		//是否值班
+		String codeStr;
+		try {
+			corpFollowActiveCodeMapper.insert(activeCode);
+		} catch (DuplicateKeyException e) {
+			throw BusinessRuntimeException.getInstance("存在相同的标记标签");
+		}
+		if (!isDuty) {
+			//生成一个活码 一个客服或多个客服
+			codeStr = corpFollowDynamicConfigService.createCorpFollowActiveCode(followIds, Constant.AC_CODE_KEY + activeCode.getId(), isDuty);
+		} else {
+			codeStr = corpFollowDynamicConfigService.createCorpFollowActiveCode(followIds, Constant.AC_CODE_KEY + activeCode.getId(), isDuty);
+		}
+		activeCode.setCodeStr(codeStr);
+		corpFollowActiveCodeMapper.updateById(activeCode);
+	}
+
+	@Override
+	public void upActiveCode(CorpFollowActiveCode activeCode) throws Exception {
+		Long activeCodeId = activeCode.getId();
+		if (activeCodeId == null) {
+			throw BusinessRuntimeException.getInstance("请编辑对应的数据");
+		}
+		CorpFollowActiveCode dbActiveCode = corpFollowActiveCodeMapper.selectById(activeCodeId);
+		if (dbActiveCode == null || !dbActiveCode.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("对应的数据已不存在");
+		}
+		String db_followStr = dbActiveCode.getFollowStr();
+		List<String> up_corpFollows = activeCode.getFollowIds();
+		if (CollUtil.isEmpty(up_corpFollows)) {
+			throw BusinessRuntimeException.getInstance("请选择对应客服");
+		}
+		activeCode.setFollowStr(Jsons.toJson(up_corpFollows));
+		if (StrUtil.isEmpty(activeCode.getMarkTag())) {
+			throw BusinessRuntimeException.getInstance("请输入标签");
+		}
+		List<String> db_followIds = Jsons.parseList(db_followStr, String.class);
+		Boolean isReCreateAcCode = false;
+		Boolean isUpdateAcCode = false;
+		if (db_followIds.size() != up_corpFollows.size()) {
+			isReCreateAcCode = true;
+		}
+		if (!isReCreateAcCode) {
+			for (String upFollowId : up_corpFollows) {
+				if (!db_followIds.contains(upFollowId)) {
+					isReCreateAcCode = true;
+					break;
+				}
+			}
+		}
+		Boolean db_isDuty = dbActiveCode.getIsDuty();
+		Boolean up_isDuty = activeCode.getIsDuty();
+		if (db_isDuty != up_isDuty) {
+			isReCreateAcCode = true;
+		}
+		activeCode.setMarkTag(activeCode.getMarkTag().trim());
+		if (!dbActiveCode.getMarkTag().equals(activeCode.getMarkTag())) {
+			isUpdateAcCode = true;
+		}
+		List<Attachments> attachments = activeCode.getAttachments();
+		if (CollUtil.isEmpty(attachments)) {
+			activeCode.setAttachmentsStr(null);
+		} else {
+			String attachmentsStr = Jsons.toJson(attachments);
+			activeCode.setAttachmentsStr(attachmentsStr);
+		}
+
+		//重新构建活码配置
+		if (isReCreateAcCode) {
+			String code_str = corpFollowDynamicConfigService.createCorpFollowActiveCode(up_corpFollows, Constant.AC_CODE_KEY + activeCode.getId(), up_isDuty);
+			activeCode.setCodeStr(code_str);
+			Set<String> keys = redisService.keys(RedisService.key.CORP_FOLLOW_ACTIVE_CODE_KEY.getNameFormat(activeCode.getId(), "*"));
+			redisService.del(keys.toArray(String[]::new));
+		} else if (isUpdateAcCode) {
+			corpFollowDynamicConfigService.updateCorpFollowActiveCode(dbActiveCode.getMarkTag(), activeCode.getMarkTag());
+		}
+		if (activeCode.getCouponId() == null) {
+			activeCode.setCouponId(0l);
+		}
+		corpFollowActiveCodeMapper.updateById(activeCode);
+	}
+
+	@Override
+	public void deleteActiveCodeById(Long id) throws Exception {
+		CorpFollowActiveCode corpFollowActiveCode = corpFollowActiveCodeMapper.selectById(id);
+		if (corpFollowActiveCode == null) {
+			return;
+		}
+		corpFollowActiveCode.setDeleted(false);
+		corpFollowActiveCodeMapper.updateById(corpFollowActiveCode);
+
+		CorpTag corpTag = corpTagMapper.selectOne(Wrappers.lambdaQuery(CorpTag.class)
+				.eq(CorpTag::getName, corpFollowActiveCode.getMarkTag()).last("limit 1"));
+		if (corpTag != null) {
+			wxCorpOps.delCorpTag(new String[]{corpTag.getTagId()}, null);
+		}
+	}
+
+	@Override
+	public void syncActiveCodeFollowRelate(String config) throws Exception {
+		if (StrUtil.isEmpty(config)) {
+			return;
+		}
+		corpFollowDutyRelateMapper.delete(null);
+		List<YhServiceDto> yhServiceDtos = Jsons.parseList(config, YhServiceDto.class);
+		yhServiceDtos.forEach(data -> {
+			DateTime stTime = DateUtil.parse(data.getStartTime(), "HH:mm");
+			int st = stTime.hour(true);
+			DateTime etTime = DateUtil.parse(data.getEndTime(), "HH:mm");
+			int et = etTime.hour(true);
+			List<YhServiceDto.CustomerServiceDto> select = data.getSelect();
+			select.forEach(cs -> {
+				String name = cs.getName();
+				CorpFollowDutyRelate relate = new CorpFollowDutyRelate();
+				CorpFollowServiceRelate corpFollowServiceRelate = corpFollowServiceRelateMapper.selectOne(Wrappers.lambdaQuery(CorpFollowServiceRelate.class)
+						.eq(CorpFollowServiceRelate::getServiceName, name).last("limit 1"));
+				if (corpFollowServiceRelate != null) {
+					relate.setFollowId(corpFollowServiceRelate.getFollowId());
+					relate.setFollowName(corpFollowServiceRelate.getFollowName());
+					relate.setServiceName(name);
+					relate.setSt(st);
+					relate.setEt(et);
+					Integer index = data.getIndex();
+					relate.setIndexTag(index);
+					//1工作日,2.周六,3周末
+					Integer type = index <= 2 ? 1 : index <= 4 ? 2 : 3;
+					relate.setType(type);
+					corpFollowDutyRelateMapper.insert(relate);
+				}
+			});
+		});
+		//清除客服活码缓存
+		Set<String> keys = redisService.keys(RedisService.key.CORP_FOLLOW_ACTIVE_CODE_CLEAN_KEY.getNameFormat("*"));
+		redisService.del(keys.toArray(String[]::new));
+	}
+}

+ 19 - 2
netflix-service/src/main/java/com/cyksj/service/corp/impl/WxCorpOpsImpl.java

@@ -15,6 +15,7 @@ import com.cyksj.config.corp.YHLXWxCorpConfig;
 import com.cyksj.dto.CorpOauth2UserInfo;
 import com.cyksj.dto.RedisKey;
 import com.cyksj.model.entity.WxCorpApp;
+import com.cyksj.model.request.corp.CorpFollowContact;
 import com.cyksj.model.request.corp.CorpGroupConfig;
 import com.cyksj.model.request.corp.CorpMsgTemplate;
 import com.cyksj.model.response.OffiaccountJsTicket;
@@ -22,8 +23,6 @@ import com.cyksj.model.response.UserExternalContactList;
 import com.cyksj.model.response.UserWithExternalPermission;
 import com.cyksj.model.response.WechatAccessToken;
 import com.cyksj.model.response.corp.*;
-import com.cyksj.model.request.corp.CorpFollowContact;
-import com.cyksj.model.response.*;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.corp.WxCorpOps;
 import lombok.extern.slf4j.Slf4j;
@@ -315,6 +314,24 @@ public class WxCorpOpsImpl implements WxCorpOps {
 		return body;
 	}
 
+	@Override
+	public void updateCorpFollowContactConfig(CorpFollowContact corpFollowContact) throws Exception {
+		//配置
+		String url = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/update_contact_way?access_token=" + getAccessToken(null);
+		HttpResponse<byte[]> res =
+				J11HttpC.custom()
+						.ofPost()
+						.url(url)
+						.cacheURI()
+						.body(HttpRequest.BodyPublishers.ofString(Jsons.toJson(corpFollowContact), IoKit.Charsets.UTF_8.getCharset()))
+						.send(HttpResponse.BodyHandlers.ofByteArray());
+		String body = IoKit.toString(res.body());
+		JSONObject jsonObject = Jsons.parseObject(body, JSONObject.class);
+		if (jsonObject.getInt("errcode") != 0) {
+			throw BusinessRuntimeException.getInstance("更新客服活码失败");
+		}
+	}
+
 	@Override
 	public CorpUserExternalTagGroupInfo addCorpTag(CorpUserExternalTagGroupInfo tagGroup, String corpId) throws Exception {
 		corpId = Optional.ofNullable(corpId).orElse(yhlXWxCorpConfig.getCorpId());

+ 28 - 1
netflix-service/src/main/java/com/cyksj/service/user/impl/UserServiceImpl.java

@@ -18,6 +18,8 @@ import com.cyksj.common.util.StringUtil;
 import com.cyksj.config.wxlogin.WxOpenPlatYHLXLoginConfig;
 import com.cyksj.mapper.*;
 import com.cyksj.mapper.channel.ChannelPopularizeMapper;
+import com.cyksj.mapper.corp.CorpFollowActiveCodeMapper;
+import com.cyksj.mapper.corp.CorpUserMapper;
 import com.cyksj.mapper.manage.cps.UserDistributePopularizeMapper;
 import com.cyksj.mapper.market.task.UserBindDetailMapper;
 import com.cyksj.model.dto.*;
@@ -99,6 +101,10 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 
 	private final YhShopDistributeService yhShopDistributeService;
 
+	private final CorpUserMapper corpUserMapper;
+
+	private final CorpFollowActiveCodeMapper corpFollowActiveCodeMapper;
+
 	private static final GlobalThreadPoolTaskExecutor TASK_POOL = GlobalThreadPoolTaskExecutor.getInstance();
 
 	@Override
@@ -149,6 +155,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 					sendNewUserWelfare(fUid);
 				} catch (Exception e) {
 				}
+				//去关联对应企业微信用户
+				relateCorpUserAndSengWelfare(fUid, unionid);
 			});
 		} else {
 			if (StrUtil.isEmpty(user.getRegisterEnv())) {
@@ -196,7 +204,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 		return user;
 	}
 
-    @Override
+	@Override
     public UserWhoami whoami(long userId) {
 	    User user = this.getById(userId);
 	    Assert.notNull(user, "业务处理失败,请联系客服");
@@ -659,4 +667,23 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 			couponIds.forEach(couponId -> couponCommonService.sendCouponToUser(userId, couponId, null, null, null, CouponUser.Channel.new_welfare));
 		}
 	}
+
+	/**
+	 * 关联企业微信用户 并发放福利
+	 */
+	private void relateCorpUserAndSengWelfare(Long fUid, String unionid) {
+		CorpUser corpUser = corpUserMapper.selectOne(Wrappers.lambdaQuery(CorpUser.class)
+				.eq(CorpUser::getUnionid, unionid).last("limit 1"));
+		if (corpUser != null) {
+			corpUser.setUserId(fUid);
+			corpUserMapper.updateById(corpUser);
+			//活码福利
+			if (corpUser.getAcId() != null && corpUser.getAcId() > 0) {
+				CorpFollowActiveCode corpFollowActiveCode = corpFollowActiveCodeMapper.selectById(corpUser.getAcId());
+				if (corpFollowActiveCode != null && corpFollowActiveCode.getCouponId() > 0) {
+					couponCommonService.sendCouponToUser(fUid, corpFollowActiveCode.getCouponId(), 0l, 0l, 0l, CouponUser.Channel.corp_active_code);
+				}
+			}
+		}
+	}
 }

+ 32 - 0
netflix-web/src/main/java/com/cyksj/web/controller/landing/CorpFollowActiveCodeController.java

@@ -0,0 +1,32 @@
+package com.cyksj.web.controller.landing;
+
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.service.corp.CorpFollowActiveCodeFrontService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 项目名: yhlxj
+ * 文件名: CorpFollowActiveCodeController
+ * 创建者: JavaZou
+ * 创建时间:2023/10/10 14:31
+ */
+@RestController
+@RequestMapping("/applets/landing")
+@RequiredArgsConstructor
+public class CorpFollowActiveCodeController {
+	private final CorpFollowActiveCodeFrontService corpFollowActiveCodeFrontService;
+
+	/**
+	 * 活动活码落地页 获取对应客服二维码
+	 */
+	@GetMapping("/get/corp/active/qrCode/{id}")
+	public Result<String> getCorpActiveQrCode(@PathVariable Long id) throws Exception {
+		String qrCode = corpFollowActiveCodeFrontService.getCorpActiveQrCode(id);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(qrCode);
+	}
+}

+ 8 - 1
netflix-web/src/main/java/com/cyksj/web/controller/manage/CmsOrderController.java

@@ -89,7 +89,7 @@ public class CmsOrderController {
 
 	@GetMapping("/get")
 	@SaCheckPermission("order:view")
-	public Result<SearchResult<OrderDonBackView>> get(Long channelId, String account, String showId) {
+	public Result<SearchResult<OrderDonBackView>> get(Long channelId, String account, String showId, Long acId) {
 		MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
 		if (channelId != null) {
 			List<Long> popularizeIds = channelPopularizeMapper.getPopularizeIdByChannelId(channelId);
@@ -107,6 +107,13 @@ public class CmsOrderController {
 		if (StrUtil.isNotBlank(showId)) {
 			builder.field(OrderDonBackView::getUserId, userService.getUserIdByShowId(showId));
 		}
+		String conditionSql = StrUtil.EMPTY;
+		if (acId != null) {
+			conditionSql += String.format(" o.user_id in (select user_id from corp_user where ac_id = %s)", acId);
+		}
+		if (StrUtil.isNotBlank(conditionSql)) {
+			builder.put("condition", conditionSql);
+		}
 		SearchResult<OrderDonBackView> search = beanSearcher.search(OrderDonBackView.class, builder.build());
 		search.getDataList().forEach(data -> {
 			if (data.getPopularizeId() != null) {

+ 213 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CmsCorpFollowActiveCodeController.java

@@ -0,0 +1,213 @@
+package com.cyksj.web.controller.manage.corp;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.OrderDonMapper;
+import com.cyksj.mapper.corp.CorpUserMapper;
+import com.cyksj.mapper.manage.coupon.CouponMapper;
+import com.cyksj.model.entity.CorpFollowActiveCode;
+import com.cyksj.model.entity.CorpFollowActiveCodeClickRecord;
+import com.cyksj.model.entity.CorpFollowServiceRelate;
+import com.cyksj.model.entity.CorpUser;
+import com.cyksj.model.request.corp.Attachments;
+import com.cyksj.model.request.corp.CorpFollowContact;
+import com.cyksj.model.views.CorpActiveCodeGoodsOrderView;
+import com.cyksj.model.views.CorpActiveCodeOrderView;
+import com.cyksj.model.views.CorpFollowActiveCodeView;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.corp.CorpFollowDynamicConfigService;
+import com.cyksj.service.corp.CorpFollowService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapBuilder;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/** admin/企业微信/客服活码
+ *项目名: netflix
+ *文件名: CmsCorpFollowActiveCodeController
+ *创建者: JavaZou
+ *创建时间:2023/7/6 18:36
+ */
+@RestController
+@RequestMapping("/manage/corp")
+@RequiredArgsConstructor
+public class CmsCorpFollowActiveCodeController {
+
+	private final CorpFollowDynamicConfigService corpFollowDynamicConfigService;
+
+	private final CorpFollowService corpFollowService;
+
+	private final BeanSearcher beanSearcher;
+
+	private final CouponMapper couponMapper;
+
+	private final OrderDonMapper orderDonMapper;
+
+	private final CorpUserMapper corpUserMapper;
+
+	private final RedisService redisService;
+
+	private final HttpServletRequest request;
+
+
+	/**
+	 * 生成动态配置
+	 */
+	@PostMapping("/post/config")
+	public Result<String> getCorpFollowConcat(@RequestBody CorpFollowContact corpFollowContact) throws Exception {
+		String body = corpFollowDynamicConfigService.corpFollowContactConfig(corpFollowContact);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(body);
+	}
+
+	/**
+	 * 可选客服配置
+	 */
+	@GetMapping("/get/available/follow")
+	public Result<List<CorpFollowServiceRelate>> getAvailableFollow() {
+		List<CorpFollowServiceRelate> corpFollowServiceRelates = beanSearcher.searchAll(CorpFollowServiceRelate.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpFollowServiceRelates);
+	}
+
+	/**
+	 * 创建活动活码
+	 */
+	@PostMapping("/post/active/code")
+	public Result<String> postActiveCode(@RequestBody @Validated CorpFollowActiveCode activeCode) throws Exception {
+		long adminId = StpUtil.getLoginIdAsLong();
+		corpFollowService.postActiveCode(adminId, activeCode);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 编辑活动活码
+	 */
+	@PutMapping("/update/active/code")
+	public Result<String> upActiveCode(@RequestBody @Validated CorpFollowActiveCode activeCode) throws Exception {
+		corpFollowService.upActiveCode(activeCode);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 删除活码
+	 */
+	@DeleteMapping("/del/active/code/{id}")
+	public Result<String> deleteByAcCodeById(@PathVariable Long id) throws Exception {
+		corpFollowService.deleteActiveCodeById(id);
+		Set<String> keys = redisService.keys(RedisService.key.CORP_FOLLOW_ACTIVE_CODE_KEY.getNameFormat(id, "*"));
+		redisService.del(keys.toArray(String[]::new));
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 活动活码列表
+	 */
+	@GetMapping("/get/active/code")
+	public Result<SearchResult<CorpFollowActiveCodeView>> getActiveCodeList() {
+		SearchResult<CorpFollowActiveCodeView> search = beanSearcher.search(CorpFollowActiveCodeView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		search.getDataList().forEach(data -> {
+			if (data.getCouponId() > 0) {
+				Optional.ofNullable(couponMapper.selectById(data.getCouponId())).ifPresent(c -> data.setCouponName(c.getName()));
+			}
+			Integer numOfUser = corpUserMapper.selectCount(Wrappers.lambdaQuery(CorpUser.class)
+					.eq(CorpUser::getAcId, data.getId()));
+			data.setNumOfUser(numOfUser);
+			Integer orderNum = orderDonMapper.selectCountActiveCodeOrderByAcId(data.getId());
+			data.setOrderNum(orderNum);
+			try {
+				data.setFollowIds(Jsons.parseList(data.getFollowStr(), String.class));
+				if (StrUtil.isNotBlank(data.getAttachmentsStr())) {
+					data.setAttachments(Jsons.parseList(data.getAttachmentsStr(), Attachments.class));
+				}
+			} catch (Exception e) {
+			}
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 查看活码人数列表
+	 */
+	@GetMapping("/get/active/code/userList")
+	public Result<SearchResult<CorpUser>> getCorpActiveCodeUser() {
+		MapBuilder mapBuilder = MapUtils.flatBuilder(request.getParameterMap());
+		SearchResult<CorpUser> search = beanSearcher.search(CorpUser.class, mapBuilder.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 查看活码订单 详情
+	 */
+	@GetMapping("/get/active/code/orderDetail")
+	public Result<SearchResult<CorpActiveCodeOrderView>> getCorpActiveCodeOrderView(Long id) {
+		MapBuilder mapBuilder = MapUtils.flatBuilder(request.getParameterMap());
+		if (id != null) {
+			mapBuilder.put("acId", String.format(" and cu.ac_id = %s", id));
+		}
+		SearchResult<CorpActiveCodeOrderView> search = beanSearcher.search(CorpActiveCodeOrderView.class, mapBuilder.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 活码 平台订单数据
+	 */
+	@GetMapping("/get/active/code/goods/order")
+	public Result<SearchResult<CorpActiveCodeGoodsOrderView>> getCorpActiveCodeGoodsOrder(Long id, String startTime, String endTime) {
+		String timeSql = StringUtils.EMPTY;
+		if (StringUtils.isNotBlank(startTime)) {
+			timeSql += " and o.created_time >= '" + startTime + "'";
+		}
+		if (StringUtils.isNotBlank(endTime)) {
+			timeSql += " and o.created_time <= '" + endTime + "'";
+		}
+		MapBuilder mapBuilder = MapUtils.flatBuilder(request.getParameterMap());
+		if (id != null) {
+			mapBuilder.put("acId", String.format(" and cu.ac_id = %s", id));
+		}
+		if (StrUtil.isNotBlank(timeSql)) {
+			mapBuilder.put("time", timeSql);
+		}
+		SearchResult<CorpActiveCodeGoodsOrderView> search = beanSearcher.search(CorpActiveCodeGoodsOrderView.class, mapBuilder.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 活码订单详情头部信息
+	 */
+	@GetMapping("/get/active/code/order/headData")
+	public Result<CorpFollowActiveCodeView> getActiveCodeOrderHeadDataView(Long id, String startTime, String endTime) {
+		CorpFollowActiveCodeView view = new CorpFollowActiveCodeView();
+		view.setId(id);
+		MapBuilder builder = MapUtils.builder();
+		if (startTime != null && endTime != null) {
+			builder.field(CorpFollowActiveCodeClickRecord::getCreatedTime, startTime, endTime).op(Operator.Between);
+		}
+		Number clickNumber = beanSearcher.searchCount(CorpFollowActiveCodeClickRecord.class, builder.build());
+		Map<String, Object> map = orderDonMapper.selectActiveCodeOrderDetail(view.getId(), startTime, endTime);
+		Object orderNum = map.get("orderNum");
+		Object orderMoney = map.get("orderMoney");
+		view.setClickNum(clickNumber.intValue());
+		if (orderNum != null) {
+			view.setOrderNum(Integer.parseInt(orderNum.toString()));
+		}
+		if (orderMoney != null) {
+			view.setOrderMoney(BigDecimal.valueOf(Long.valueOf(orderMoney.toString())));
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult(view);
+	}
+}

+ 0 - 34
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpFollowConfigController.java

@@ -1,34 +0,0 @@
-package com.cyksj.web.controller.manage.corp;
-
-import com.cyksj.dto.Result;
-import com.cyksj.enums.GatewayResponse;
-import com.cyksj.model.request.corp.CorpFollowContact;
-import com.cyksj.service.corp.CorpFollowDynamicConfigService;
-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;
-
-/*
- *项目名: netflix
- *文件名: CorpFollowConfigController
- *创建者: JavaZou
- *创建时间:2023/7/6 18:36
- */
-@RestController
-@RequestMapping("/manage/corp")
-@RequiredArgsConstructor
-public class CorpFollowConfigController {
-
-	private final CorpFollowDynamicConfigService corpFollowDynamicConfigService;
-
-	/**
-	 * 生成动态配置
-	 */
-	@PostMapping("/post/config")
-	public Result<String> getCorpFollowConcat(@RequestBody CorpFollowContact corpFollowContact) throws Exception {
-		String body = corpFollowDynamicConfigService.corpFollowContactConfig(corpFollowContact);
-		return GatewayResponse.SUCCESS.newBuilder().toResult(body);
-	}
-}

+ 11 - 0
netflix-web/src/main/java/com/cyksj/web/controller/sys/SysConfigController.java

@@ -18,6 +18,7 @@ import com.cyksj.model.entity.HelpQuestion;
 import com.cyksj.model.entity.HelpQuestionClassification;
 import com.cyksj.model.entity.SysConfig;
 import com.cyksj.model.views.HelpQuestionClassificationView;
+import com.cyksj.service.corp.CorpFollowService;
 import com.cyksj.service.sys.SysConfigService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -42,6 +43,8 @@ public class SysConfigController {
 
 	private final HelpQuestionClassificationMapper helpQuestionClassificationMapper;
 
+	private final CorpFollowService corpFollowService;
+
 	@GetMapping("/get")
 	public Result<SysConfig> get(String key) {
 		SysConfig sysConfig = sysConfigService.getOne(Wrappers.lambdaQuery(SysConfig.class).eq(SysConfig::getSysKey, key).last(" limit 1"));
@@ -64,6 +67,9 @@ public class SysConfigController {
 	@PutMapping("/update")
 	@Log(module = "系统设置/编辑配置", businessType = BusinessType.PUT, isSaveRequestData = true)
 	public Result<SysConfig> update(@RequestBody SysConfig sysConfig) throws Exception {
+		Long id = sysConfig.getId();
+		SysConfig byId = sysConfigService.getById(id);
+		sysConfig.setSysKey(byId.getSysKey());
 		if (Constant.EQUIPMENT_DISTRIBUTE_KEY.equals(sysConfig.getSysKey())) {
 			String sysValue = sysConfig.getSysValue();
 			if (StrUtil.isNotBlank(sysValue)) {
@@ -78,6 +84,11 @@ public class SysConfigController {
 			}
 		}
 
+		if (Constant.serviceKeys.get(0).equals(sysConfig.getSysKey())) {
+			//同步活码客服关联配置
+			corpFollowService.syncActiveCodeFollowRelate(sysConfig.getSysValue());
+		}
+
 		sysConfigService.updateById(sysConfig);
 
 		return GatewayResponse.SUCCESS.newBuilder().toResult(sysConfig);

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
netflix-web/src/main/resources/application-dev.yml


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.