ソースを参照

企业微信接口

zoujiajian 1 年間 前
コミット
4e0f143bb3
16 ファイル変更2451 行追加0 行削除
  1. 22 0
      netflix-dao/src/main/java/com/cyksj/model/request/CorpKfUserMarkReq.java
  2. 251 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CmsCorpFollowActiveCodeController.java
  3. 65 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpChatController.java
  4. 196 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpCustomerAcquistionLinkController.java
  5. 93 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpCustomerAcquistionLinkPopularizeController.java
  6. 131 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpInteractiveRadarController.java
  7. 130 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpMsgAuditController.java
  8. 311 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpMsgBulkTaskController.java
  9. 99 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpMsgFollowController.java
  10. 225 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpTagController.java
  11. 41 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpUserController.java
  12. 138 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpWelcomeTemplateController.java
  13. 242 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/relation/CorpRelationController.java
  14. 337 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/wxkf/CorpKfController.java
  15. 86 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/wxkf/CorpKfMsgController.java
  16. 84 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/wxkf/CorpKfUserController.java

+ 22 - 0
netflix-dao/src/main/java/com/cyksj/model/request/CorpKfUserMarkReq.java

@@ -0,0 +1,22 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: CorpKfUserMarkReq
+ * 创建者: JavaZou
+ * 创建时间:2025/1/17 11:08
+ */
+@Getter
+@Setter
+public class CorpKfUserMarkReq {
+
+	private String externalUserid;
+
+	/**
+	 * 客服id
+	 */
+	private String followId;
+}

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

@@ -0,0 +1,251 @@
+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.CorpFollowMapper;
+import com.cyksj.mapper.corp.CorpUserMapper;
+import com.cyksj.mapper.corp.CorpWelcomeTemplateMapper;
+import com.cyksj.mapper.manage.coupon.CouponMapper;
+import com.cyksj.model.entity.*;
+import com.cyksj.model.request.corp.Attachments;
+import com.cyksj.model.request.corp.CorpFollowContact;
+import com.cyksj.model.views.*;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.corp.CorpFollowDynamicConfigService;
+import com.cyksj.service.corp.CorpFollowService;
+import com.cyksj.service.sys.SysConfigService;
+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.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 CorpWelcomeTemplateMapper corpWelcomeTemplateMapper;
+
+	private final SysConfigService sysConfigService;
+
+	private final HttpServletRequest request;
+
+	private final CorpFollowMapper corpFollowMapper;
+
+	/**
+	 * 获取企业成员列表
+	 */
+	@GetMapping("/get")
+	public Result<SearchResult<CorpFollow>> getCorpFollow() {
+		SearchResult<CorpFollow> search = beanSearcher.search(CorpFollow.class, MapUtils.flatBuilder(request.getParameterMap())
+				.onlySelect(CorpFollow::getId, CorpFollow::getUserid, CorpFollow::getName)
+				.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 编辑企业成员
+	 */
+	@PutMapping("/update/follow")
+	public Result<String> updateFollow(@RequestBody CorpFollow corpFollow) {
+		corpFollowMapper.updateById(corpFollow);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+
+	/**
+	 * 生成动态配置
+	 */
+	@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<SysCorpFollowView>> getAvailableFollow() {
+		List<SysCorpFollowView> corpFollowServiceDtos = beanSearcher.searchAll(SysCorpFollowView.class, MapUtils.builder().build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpFollowServiceDtos);
+	}
+
+	/**
+	 * 创建活动活码
+	 */
+	@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 -> {
+			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.setUserIndxList(Jsons.parseList(data.getFollowStr(), Integer.class));
+				if (StrUtil.isNotBlank(data.getAttachmentsStr())) {
+					data.setAttachments(Jsons.parseList(data.getAttachmentsStr(), Attachments.class));
+				}
+			} catch (Exception e) {
+			}
+			CorpWelcomeTemplate corpWelcomeTemplate = corpWelcomeTemplateMapper.selectById(data.getMsgId());
+			if (corpWelcomeTemplate != null) {
+				data.setMsgName(corpWelcomeTemplate.getName());
+			}
+		});
+		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);
+	}
+
+	/**
+	 * 活码人数每日添加详情
+	 * @param acId 活码记录id
+	 * @return
+	 */
+	@GetMapping("/get/statistics/user/data/{acId}")
+	public Result<SearchResult<CorpActiveCodeUserDayDataView>> getAcUserDayData(@PathVariable Long acId) {
+		String condition = String.format("ac_id = %s", acId);
+		SearchResult<CorpActiveCodeUserDayDataView> search = beanSearcher.search(CorpActiveCodeUserDayDataView.class, MapUtils.flatBuilder(request.getParameterMap()).put("condition", condition)
+				.orderBy(CorpActiveCodeUserDayDataView::getDate).desc()
+				.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+}

+ 65 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpChatController.java

@@ -0,0 +1,65 @@
+package com.cyksj.web.controller.manage.corp;
+
+import cn.hutool.core.util.StrUtil;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.views.CorpGroupChatDetailView;
+import com.cyksj.model.views.CorpGroupChatView;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapBuilder;
+import com.ejlchina.searcher.util.MapUtils;
+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;
+
+import javax.servlet.http.HttpServletRequest;
+
+/*
+ *项目名: netflix
+ *文件名: CorpChatController
+ *创建者: JavaZou
+ *创建时间:2023/6/26 16:24
+ */
+@RestController
+@RequestMapping("/manage/corp/group/chat")
+@RequiredArgsConstructor
+public class CorpChatController {
+	private final BeanSearcher beanSearcher;
+
+	private final HttpServletRequest request;
+
+	/**
+	 * 客户群列表
+	 */
+	@GetMapping("/get")
+	public Result<SearchResult<CorpGroupChatView>> getGroupsChatList() {
+		SearchResult<CorpGroupChatView> search = beanSearcher.search(CorpGroupChatView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 客户群详情
+	 */
+	@GetMapping("/get/detail/{id}")
+	public Result<SearchResult<CorpGroupChatDetailView>> getGroupsDetail(@PathVariable Long id, Long goodsId, Boolean exists) {
+		String condition = StrUtil.EMPTY;
+		if (goodsId != null) {
+			String connect = (exists == null || exists) ? "in" : "not in";
+			condition += String.format("user_id %s (select gr.user_id from (select gt.id from groups_trips gt inner join goods_don_sku sku on sku.id = gt.sku_id and goods_id = %s) s inner join groups_relation gr on gr.groups_id = s.id)", connect, goodsId);
+		}
+		MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap())
+				.field(CorpGroupChatDetailView::getGroupChatId, id);
+		if (StrUtil.isNotBlank(condition)) {
+			builder.put("condition", condition);
+		}
+		SearchResult<CorpGroupChatDetailView> search = beanSearcher.search(CorpGroupChatDetailView.class, builder.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+}
+
+
+
+

+ 196 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpCustomerAcquistionLinkController.java

@@ -0,0 +1,196 @@
+package com.cyksj.web.controller.manage.corp;
+
+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.SysCorpFollowRelateMapper;
+import com.cyksj.mapper.corp.CorpCustomerAcquisitionLinkExtraCustomerMapper;
+import com.cyksj.mapper.corp.CorpFollowMapper;
+import com.cyksj.mapper.corp.CorpTagMapper;
+import com.cyksj.mapper.corp.CorpWelcomeTemplateMapper;
+import com.cyksj.model.entity.*;
+import com.cyksj.model.views.CorpCustomerAcquisitionLinkExtraCustomerView;
+import com.cyksj.model.views.SysCorpFollowView;
+import com.cyksj.service.corp.CorpCustomerAcquisitionLinkService;
+import com.cyksj.service.sys.SysConfigService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ *  获客助手
+ * @author chan
+ * @date 2023/11/10 09:53
+ */
+@RestController
+@RequestMapping("/manage/corp/customer/acuistion/link")
+@RequiredArgsConstructor
+public class CorpCustomerAcquistionLinkController {
+
+    private final CorpCustomerAcquisitionLinkService corpCustomerAcquisitionLinkService;
+
+    private final CorpFollowMapper corpFollowMapper;
+
+    private final CorpTagMapper corpTagMapper;
+
+    private final CorpWelcomeTemplateMapper corpWelcomeTemplateMapper;
+
+    private final BeanSearcher beanSearcher;
+
+    private final HttpServletRequest request;
+
+    private final SysConfigService sysConfigService;
+
+    private final CorpCustomerAcquisitionLinkExtraCustomerMapper corpCustomerAcquisitionLinkExtraCustomerMapper;
+
+    private final SysCorpFollowRelateMapper sysCorpFollowRelateMapper;
+
+    /**
+     * 新增获客助手链接
+     */
+    @PostMapping
+    public Result<String> add(@RequestBody CorpCustomerAcquisitionLink corpCustomerAcquisitionLink) throws Exception {
+        corpCustomerAcquisitionLinkService.add(corpCustomerAcquisitionLink);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 修改获客助手链接
+     */
+    @PutMapping
+    public Result<String> up(@RequestBody CorpCustomerAcquisitionLink corpCustomerAcquisitionLink) throws Exception {
+        corpCustomerAcquisitionLinkService.up(corpCustomerAcquisitionLink);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+
+    /**
+     * 删除获客助手链接
+     */
+    @DeleteMapping("/{id}")
+    public Result<String> up(@PathVariable Long id) throws Exception {
+        corpCustomerAcquisitionLinkService.del(id);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 获取获客助手链接
+     */
+    @GetMapping("/get")
+    public Result<SearchResult<CorpCustomerAcquisitionLink>> get() throws Exception {
+        SearchResult<CorpCustomerAcquisitionLink> search = beanSearcher.search(CorpCustomerAcquisitionLink.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+
+        List<SysCorpFollowView> corpFollowServiceDtos = beanSearcher.searchAll(SysCorpFollowView.class, MapUtils.builder().build());
+
+        search.getDataList().forEach(e -> {
+            try {
+                List<String> userStrList = corpFollowServiceDtos.stream().filter(cf -> {
+                    try {
+                        return Jsons.parseList(e.getUserList(), Long.class).contains(cf.getSid());
+                    } catch (Exception ex) {
+                    }
+                    return false;
+                }).map(SysCorpFollowView::getShowName).collect(Collectors.toList());
+                e.setUserStrList(userStrList);
+
+                if (StrUtil.isNotEmpty(e.getChooseDutyUsers())) {
+                    List<String> dutyUserList = corpFollowServiceDtos.stream().filter(cf -> {
+                        try {
+                            return Jsons.parseList(e.getChooseDutyUsers(), Long.class).contains(cf.getSid());
+                        } catch (Exception ex) {
+                        }
+                        return false;
+                    }).map(SysCorpFollowView::getShowName).collect(Collectors.toList());
+
+                    e.setChooseDutyUserList(dutyUserList);
+                }
+
+                List<String> tagStrList = corpTagMapper.selectList(Wrappers.lambdaQuery(CorpTag.class)
+                        .in(CorpTag::getId, Jsons.parseList(e.getTags(), Long.class))).stream().map(CorpTag::getName).collect(Collectors.toList());
+                e.setTagStrList(tagStrList);
+            } catch (Exception ex) {
+            }
+
+            CorpWelcomeTemplate corpWelcomeTemplate = corpWelcomeTemplateMapper.selectById(e.getMsgId());
+            if (corpWelcomeTemplate != null) {
+                e.setMsgName(corpWelcomeTemplate.getName());
+            }
+        });
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+    /**
+     * 针对获客链接添加额外客服
+     */
+    @PostMapping("/add/extra/service")
+    public Result<String> addExtraService(@RequestBody @Validated CorpCustomerAcquisitionLinkExtraCustomer extraCustomer) throws Exception {
+        Integer serviceId = extraCustomer.getServiceId();
+        SysCorpFollowRelate sysCorpFollowRelate = sysCorpFollowRelateMapper.selectById(serviceId);
+        extraCustomer.setFollowId(sysCorpFollowRelate.getFollowId());
+        corpCustomerAcquisitionLinkExtraCustomerMapper.insert(extraCustomer);
+        corpCustomerAcquisitionLinkService.handleCorpLinkSchedulerJob();
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 修改获客链接添加额外客服
+     */
+    @PutMapping("/update/extra/service")
+    public Result<String> updateExtraService(@RequestBody @Validated CorpCustomerAcquisitionLinkExtraCustomer extraCustomer) throws Exception {
+        Integer serviceId = extraCustomer.getServiceId();
+        SysCorpFollowRelate sysCorpFollowRelate = sysCorpFollowRelateMapper.selectById(serviceId);
+        extraCustomer.setFollowId(sysCorpFollowRelate.getFollowId());
+	    corpCustomerAcquisitionLinkExtraCustomerMapper.updateById(extraCustomer);
+        corpCustomerAcquisitionLinkService.handleCorpLinkSchedulerJob();
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 删除获客链接添加额外客服
+     */
+    @DeleteMapping("/del/extra/service/{id}")
+    public Result<String> delExtraService(@PathVariable Long id) {
+	    Optional.ofNullable(corpCustomerAcquisitionLinkExtraCustomerMapper.selectById(id))
+			    .ifPresent(e -> {
+				    if (e.getDeleted()) {
+                        try {
+                            e.setDeleted(false);
+                            corpCustomerAcquisitionLinkExtraCustomerMapper.updateById(e);
+                            corpCustomerAcquisitionLinkService.handleCorpLinkSchedulerJob();
+                        } catch (Exception ex) {
+                        }
+                    }
+			    });
+	    return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 获取获客链接添加额外客服
+     */
+    @GetMapping("/get/extra/service")
+    public Result<SearchResult<CorpCustomerAcquisitionLinkExtraCustomerView>> getExtraService() throws Exception {
+        SearchResult<CorpCustomerAcquisitionLinkExtraCustomerView> search = beanSearcher.search(CorpCustomerAcquisitionLinkExtraCustomerView.class, MapUtils.flatBuilder(request.getParameterMap())
+                .orderBy(CorpCustomerAcquisitionLinkExtraCustomerView::getAcId).asc()
+                .orderBy(CorpCustomerAcquisitionLinkExtraCustomerView::getCreatedTime).desc()
+                .build());
+        List<SysCorpFollowView> corpFollowServiceDtos = beanSearcher.searchAll(SysCorpFollowView.class, MapUtils.builder().build());
+        Map<Long, List<SysCorpFollowView>> serviceMap = corpFollowServiceDtos.stream().collect(Collectors.groupingBy(SysCorpFollowView::getSid));
+        search.getDataList().forEach(e->{
+            Optional.ofNullable(serviceMap.get(e.getServiceId()))
+                    .ifPresent(service -> e.setServiceName(service.get(0).getShowName()));
+        });
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+}

+ 93 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpCustomerAcquistionLinkPopularizeController.java

@@ -0,0 +1,93 @@
+package com.cyksj.web.controller.manage.corp;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.EnvCommonService;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.entity.CorpCustomerAcquisitionLinkPopularize;
+import com.cyksj.model.views.CorpCustomerAcquisitionLinkPopularizeView;
+import com.cyksj.service.corp.CorpCustomerAcquisitionLinkPopularizeService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ *  获客助手
+ * @author chan
+ * @date 2023/11/10 09:53
+ */
+@RestController
+@RequestMapping("/v2/corp/customer/acuistion/popularize")
+@RequiredArgsConstructor
+public class CorpCustomerAcquistionLinkPopularizeController {
+    private final CorpCustomerAcquisitionLinkPopularizeService corpCustomerAcquisitionLinkPopularizeService;
+
+    private final BeanSearcher beanSearcher;
+
+    private final HttpServletRequest request;
+
+    private final EnvCommonService envCommonService;
+
+    /**
+     * 创建加粉链接
+     */
+    @PostMapping
+    public Result<String> add(@RequestBody CorpCustomerAcquisitionLinkPopularize corpCustomerAcquisitionLinkPopularize){
+        CorpCustomerAcquisitionLinkPopularize linkPopularize = corpCustomerAcquisitionLinkPopularizeService.add(corpCustomerAcquisitionLinkPopularize);
+        StringBuilder url = new StringBuilder();
+        url.append(envCommonService.getNotifyHost()).append("applets/channel/customerAcquistion?projectid=__PROJECT_ID__&clickid=__CLICKID__&linkId=").append(linkPopularize.getId());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(url.toString());
+    }
+
+    /**
+     * 获取加粉链接
+     * @param id 推广链接id
+     * @return 加粉链接
+     */
+    @GetMapping("/{id}")
+    public Result<CorpCustomerAcquisitionLinkPopularize> get(@PathVariable Long id){
+        CorpCustomerAcquisitionLinkPopularize linkPopularize = corpCustomerAcquisitionLinkPopularizeService.
+                getOne(Wrappers.lambdaQuery(CorpCustomerAcquisitionLinkPopularize.class)
+                        .eq(CorpCustomerAcquisitionLinkPopularize::getPopularizeId, id)
+                        .last("limit 1"));
+        if (linkPopularize == null) {
+            throw BusinessRuntimeException.getInstance("请先创建加粉链接。");
+        }
+
+        StringBuilder url = new StringBuilder();
+        url.append(envCommonService.getNotifyHost()).append("applets/channel/customerAcquistion?projectid=__PROJECT_ID__&clickid=__CLICKID__&linkId=").append(linkPopularize.getId());
+
+        linkPopularize.setUrl(url.toString());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(linkPopularize);
+    }
+
+
+    /**
+     * 编辑加粉链接
+     */
+    @PutMapping
+    public Result<String> up(@RequestBody CorpCustomerAcquisitionLinkPopularize corpCustomerAcquisitionLinkPopularize){
+        CorpCustomerAcquisitionLinkPopularize popularize = corpCustomerAcquisitionLinkPopularizeService.getById(corpCustomerAcquisitionLinkPopularize.getId());
+        if (popularize  == null) {
+            throw BusinessRuntimeException.getInstance("该加粉链接不存在.");
+        }
+        corpCustomerAcquisitionLinkPopularizeService.updateById(corpCustomerAcquisitionLinkPopularize);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 加粉链接列表
+     */
+    @GetMapping("/get")
+    public Result<SearchResult<CorpCustomerAcquisitionLinkPopularizeView>> get(){
+        SearchResult<CorpCustomerAcquisitionLinkPopularizeView> search = beanSearcher.search(CorpCustomerAcquisitionLinkPopularizeView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+
+}

+ 131 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpInteractiveRadarController.java

@@ -0,0 +1,131 @@
+package com.cyksj.web.controller.manage.corp;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.corp.CorpInteractiveRadarUserRecordMapper;
+import com.cyksj.model.entity.CorpInteractiveRadar;
+import com.cyksj.model.entity.CorpInteractiveReaderLink;
+import com.cyksj.model.views.CorpInteractiveRadarOrderDataView;
+import com.cyksj.model.views.CorpInteractiveReaderOrderDetailView;
+import com.cyksj.model.views.CorpInteractiveReaderView;
+import com.cyksj.service.corp.CorpInteractiveRadarService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Map;
+
+/*
+ *项目名: netflix
+ *文件名: CorpInteractiveRadarController
+ *创建者: JavaZou
+ *创建时间:2023/8/4 16:27
+ */
+@RestController
+@RequestMapping("/manage/corp/interactive/reader")
+@RequiredArgsConstructor
+public class CorpInteractiveRadarController {
+	private final CorpInteractiveRadarService corpInteractiveRadarService;
+
+	private final BeanSearcher beanSearcher;
+
+	private final HttpServletRequest request;
+
+	private final CorpInteractiveRadarUserRecordMapper corpInteractiveRadarUserRecordMapper;
+
+	/**
+	 * 新增互动雷达
+	 */
+	@PostMapping
+	public Result<String> saveInteractiveReader(@RequestBody @Validated CorpInteractiveRadar radar) {
+		corpInteractiveRadarService.saveOrUpdateRadar(radar);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 编辑互动雷达
+	 */
+	@PutMapping
+	public Result<String> updateInteractiveReader(@RequestBody @Validated CorpInteractiveRadar radar) {
+		if (radar.getId() == null || corpInteractiveRadarService.getById(radar.getId()) == null) {
+			throw BusinessRuntimeException.getInstance("雷达链接不存在");
+		}
+		corpInteractiveRadarService.saveOrUpdateRadar(radar);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 删除互动雷达
+	 */
+	@DeleteMapping("/{id}")
+	public Result<String> deleteById(@PathVariable Long id) {
+		corpInteractiveRadarService.removeById(id);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 互动雷达列表
+	 */
+	@GetMapping("/get")
+	public Result<SearchResult<CorpInteractiveReaderView>> get() {
+		SearchResult<CorpInteractiveReaderView> search = beanSearcher.search(CorpInteractiveReaderView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		search.getDataList().forEach(data -> {
+			if (data.getType() == 1) {
+				String linkInfoStr = data.getLinkInfoStr();
+				try {
+					CorpInteractiveReaderLink linkInfo = Jsons.parseObject(linkInfoStr, CorpInteractiveReaderLink.class);
+					data.setLinkInfo(linkInfo);
+				} catch (Exception e) {
+				}
+			}
+
+			Map<String, Long> map = corpInteractiveRadarUserRecordMapper.selectClickRecord(data.getId());
+			Integer clickNum = map.get("clickNum").intValue();
+			data.setClickNum(clickNum);
+			Integer clickUserNum = map.get("clickUserNum").intValue();
+			data.setClickUserNum(clickUserNum);
+
+			CorpInteractiveReaderOrderDetailView detailView = beanSearcher.searchFirst(CorpInteractiveReaderOrderDetailView.class, MapUtils.builder().put("id", String.format("id = %s", data.getId())).build());
+			data.setPayNum(detailView.getTotal());
+			data.setPayNewUserNum(detailView.getNumOfNewUser());
+			data.setPayOldUserNum(detailView.getNumOfOldUser());
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 购买人数详情
+	 */
+	@GetMapping("/get/payNum/detail/{id}")
+	public Result<CorpInteractiveReaderOrderDetailView> getPayNumDetailById(@PathVariable Long id) {
+		CorpInteractiveReaderOrderDetailView detailView = beanSearcher.searchFirst(CorpInteractiveReaderOrderDetailView.class, MapUtils.builder().put("id", String.format("id = %s", id)).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(detailView);
+	}
+
+
+	/**
+	 * 雷达数据
+	 */
+	@GetMapping("/get/data/{id}")
+	public Result<CorpInteractiveRadarOrderDataView> corpInteractiveRadarOrderDataViewResult(@PathVariable Long id) {
+		CorpInteractiveRadarOrderDataView view = new CorpInteractiveRadarOrderDataView();
+		DateTime beginOfDay = DateUtil.beginOfDay(DateTime.now());
+		String idEq = String.format(" cr.radar_id = %s", id);
+		String conditionSql = String.format(" %s and cr.created_time >= '%s'", idEq, beginOfDay.toString());
+		CorpInteractiveRadarOrderDataView.CorpInteractiveRadarOrderData today = beanSearcher.searchFirst(CorpInteractiveRadarOrderDataView.CorpInteractiveRadarOrderData.class, MapUtils.builder().put("condition", conditionSql).build());
+
+		CorpInteractiveRadarOrderDataView.CorpInteractiveRadarOrderData history = beanSearcher.searchFirst(CorpInteractiveRadarOrderDataView.CorpInteractiveRadarOrderData.class, MapUtils.builder().put("condition", idEq).build());
+
+		view.setToday(today);
+		view.setHistory(history);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(view);
+	}
+}

+ 130 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpMsgAuditController.java

@@ -0,0 +1,130 @@
+package com.cyksj.web.controller.manage.corp;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.corp.CorpUserPayIntentionMapper;
+import com.cyksj.model.entity.CorpUserPayIntention;
+import com.cyksj.model.entity.QyMsgContent;
+import com.cyksj.model.manage.views.CorpFollowMsgView;
+import com.cyksj.model.manage.views.CorpMsgAuditUserView;
+import com.cyksj.service.corp.CorpQyMsgContentService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.operator.InList;
+import com.ejlchina.searcher.util.MapBuilder;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author chan
+ * @date 2024/1/22 14:25
+ */
+@RequiredArgsConstructor
+@Slf4j
+@RestController
+@RequestMapping("/manage/corp/msg/audit")
+public class CorpMsgAuditController {
+
+    private final BeanSearcher beanSearcher;
+
+    private final HttpServletRequest request;
+
+    private final CorpQyMsgContentService corpQyMsgContentService;
+
+    private final CorpUserPayIntentionMapper corpUserPayIntentionMapper;
+
+
+
+    /**
+     * 会话存档用户列表
+     * @param textKey 关键词搜索
+     * @param tagIds 标签筛选
+     * @return
+     */
+    @GetMapping
+    public Result<SearchResult<CorpMsgAuditUserView>> get(String textKey, @RequestParam(value = "tagIds", required = false) List<String> tagIds, Integer intention, Long talkStartTime, Long talkEndTime) throws Exception {
+        long adminId = StpUtil.getLoginIdAsLong();
+        Set<String> userIds = new HashSet<>();
+        if(StringUtils.isNotBlank(textKey)){
+            List<QyMsgContent> text = corpQyMsgContentService.list(Wrappers.lambdaQuery(QyMsgContent.class).select(QyMsgContent::getFrom, QyMsgContent::getExternalUserid,QyMsgContent::getTolist).like(QyMsgContent::getText, textKey).eq(QyMsgContent::getRoomid,"").eq(QyMsgContent::getMsgtype, "text"));
+            userIds = text.stream().map(QyMsgContent::getExternalUserid).collect(Collectors.toSet());
+        }
+        MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+        if (userIds.size() > 0) {
+            builder.field(CorpMsgAuditUserView::getExternalUserid,userIds).op(InList.class);
+        }
+        if(CollUtil.isNotEmpty(tagIds)){
+            StringBuilder tagStr = new StringBuilder();
+            if (tagIds.size() == 1) {
+                tagStr.append("=").append("'").append(tagIds.get(0)).append("'");
+            } else {
+                tagStr.append("in").append(Jsons.toJson(tagIds).replaceAll("\\[", "(").replaceAll("]", ")"));
+            }
+            builder.put("tag", String.format( "and cr.id = t.relation_id and t.tag_id %s", tagStr));
+            builder.put("tagTable", ",corp_user_tag t");
+        }
+        //管理员
+        if (adminId == 75l) {
+            builder.put("tagTable", ",corp_user_tag t");
+            builder.put("tag", String.format("and cr.id = t.relation_id and t.tag_id =  'etvj2DPQAAxqh9QlGJG4N1EbdQBNYGAA'"));
+        }
+        if (intention != null) {
+            builder.put("intention", String.format("and exists (select 1 from corp_user_pay_intention cup where cup.union_id = c.unionid and intention = %s limit 1)", intention));
+        }
+        if (talkEndTime != null && talkStartTime != null) {
+            builder.put("talkTime", String.format(" and exists (select 1 from qy_msg_content qmc where qmc.external_userid = cu.external_userid and qmc.msgtime between %s and %s)", talkStartTime, talkEndTime));
+        }
+        SearchResult<CorpMsgAuditUserView> search = beanSearcher.search(CorpMsgAuditUserView.class, builder.build());
+        search.getDataList().forEach(e -> {
+            Optional.ofNullable(corpUserPayIntentionMapper.selectOne(Wrappers.lambdaQuery(CorpUserPayIntention.class)
+                    .eq(CorpUserPayIntention::getUnionId, e.getUnionId())
+                    .last("limit 1"))).ifPresent(cu -> e.setIntention(cu.getIntention()));
+        });
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+
+    }
+
+    /**
+     * 获取会话列表
+     */
+    @GetMapping("/msgList")
+    public Result<SearchResult<CorpFollowMsgView>> msgList(String keyword){
+        MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+        if(StringUtils.isNotBlank(keyword)){
+            builder.put("keyword", String.format("where q.text like '%s%%')",keyword));
+        }
+        SearchResult<CorpFollowMsgView> search = beanSearcher.search(CorpFollowMsgView.class, builder.build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+
+    /**
+     * 获取聊天记录
+     */
+    @GetMapping("/getMsgByFollowIdAndUserId")
+    public Result<SearchResult<QyMsgContent>> getMsgByFollowIdAndUserId(){
+        Map<String, Object> build = MapUtils.flatBuilder(request.getParameterMap()).build();
+        log.info("获取聊天记录 param:{}", JSONUtil.toJsonStr(build));
+        if (!build.containsKey("followId") || !build.containsKey("externalUserid")){
+            throw BusinessRuntimeException.getInstance("缺少必填参数.");
+        }
+        SearchResult<QyMsgContent> search = beanSearcher.search(QyMsgContent.class, build);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+}

+ 311 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpMsgBulkTaskController.java

@@ -0,0 +1,311 @@
+package com.cyksj.web.controller.manage.corp;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.excel.support.ExcelTypeEnum;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.corp.CorpMsgBulkTaskCategoryMapper;
+import com.cyksj.mapper.corp.CorpTagMapper;
+import com.cyksj.mapper.manage.coupon.CouponMapper;
+import com.cyksj.model.dto.CornMetaDto;
+import com.cyksj.model.entity.CorpMsgBulkTask;
+import com.cyksj.model.entity.CorpMsgBulkTaskCategory;
+import com.cyksj.model.entity.CorpMsgBulkTaskRecord;
+import com.cyksj.model.entity.CorpTag;
+import com.cyksj.model.excel.ExcelCorpMsgBulkTaskSendRecordData;
+import com.cyksj.model.request.CorpActiveBulkMsg;
+import com.cyksj.model.request.corp.Attachments;
+import com.cyksj.model.views.CorpMsgBulkTaskCorpFollowSendRecordView;
+import com.cyksj.model.views.CorpMsgBulkTaskSendRecordView;
+import com.cyksj.service.corp.CorpMsgBulkTaskService;
+import com.cyksj.web.util.EasyExcelUtils;
+import com.cyksj.xxljob.XxlJobUtil;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapBuilder;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/*
+ *项目名: netflix
+ *文件名: CorpMsgBulkTaskController
+ *创建者: JavaZou
+ *创建时间:2023/6/21 9:31
+ */
+@RestController
+@RequestMapping("/manage/corpMsgBulkTask")
+@RequiredArgsConstructor
+public class CorpMsgBulkTaskController {
+
+	private final CorpMsgBulkTaskService corpMsgBulkTaskService;
+
+	private final BeanSearcher beanSearcher;
+
+	private final CouponMapper couponMapper;
+
+	private final HttpServletRequest request;
+
+	private final CorpTagMapper corpTagMapper;
+
+	private final CorpMsgBulkTaskCategoryMapper corpMsgBulkTaskCategoryMapper;
+
+
+	/**
+	 * 新建群发
+	 */
+	@PostMapping("/post")
+	public Result<String> saveCorpMsgBulkTask(@RequestBody @Validated CorpMsgBulkTask corpMsgBulkTask) throws Exception {
+		corpMsgBulkTaskService.saveOrUpdateMsgBulkTask(corpMsgBulkTask);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("新建群发成功");
+	}
+
+
+	/**
+	 * 编辑群发
+	 */
+	@PutMapping("/put")
+	public Result<String> updateCorpMsgBulkTask(@RequestBody @Validated CorpMsgBulkTask corpMsgBulkTask) throws Exception {
+		corpMsgBulkTaskService.saveOrUpdateMsgBulkTask(corpMsgBulkTask);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("修改群发成功");
+	}
+
+	/**
+	 * 群发列表
+	 */
+	@GetMapping("/get")
+	public Result<SearchResult<CorpMsgBulkTask>> getCorpMsgBulkTaskList(String[] tagIds) {
+		MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+		if (tagIds != null && tagIds.length > 0) {
+			List<String> collect = corpTagMapper.selectList(Wrappers.lambdaQuery(CorpTag.class)
+					.in(CorpTag::getId, tagIds)).stream().map(CorpTag::getTagId).collect(Collectors.toList());
+			if (CollUtil.isNotEmpty(collect)) {
+				StringBuilder sb = new StringBuilder();
+				for (String tagId : collect) {
+					sb.append("\"" + tagId + "\"");
+					sb.append(",");
+					String substring = sb.substring(0, sb.lastIndexOf(","));
+					builder.put("tag_id", String.format("id in (select distinct task_id from corp_msg_bulk_task_tag_relation where tag_id in(%s))", substring));
+				}
+			}
+		}
+		SearchResult<CorpMsgBulkTask> search = beanSearcher.search(CorpMsgBulkTask.class, builder
+				.field(CorpMsgBulkTask::getDeleted, true)
+				.orderBy(CorpMsgBulkTask::getId).desc()
+				.build());
+		search.getDataList().forEach(data -> {
+			try {
+				List<String> tagList = Jsons.parseList(data.getChooseTagStr(), String.class);
+				data.setTagSize(tagList.size());
+
+				if (StringUtils.isNotBlank(data.getCorn())) {
+					String[] s = data.getCorn().split(" ");
+					CornMetaDto cornMetaDto = new CornMetaDto();
+					cornMetaDto.setHour(s[2]);
+					cornMetaDto.setDay(StringPool.ASTERISK.equals(s[3]) ? "" : s[3]);
+					cornMetaDto.setMonth(StringPool.ASTERISK.equals(s[4]) ? "" : s[4]);
+					cornMetaDto.setWeek(StringPool.QUESTION_MARK.equals(s[5]) ? "" : s[5]);
+					data.setCornDto(cornMetaDto);
+				}
+				if (StrUtil.isNotBlank(data.getAttachmentsStr())) {
+					data.setAttachments(Jsons.parseList(data.getAttachmentsStr(), Attachments.class));
+				}
+				if (StrUtil.isNotEmpty(data.getCouponIds())) {
+					List<Long> couponIds = Jsons.parseList(data.getCouponIds(), Long.class);
+					data.setCouponName(couponMapper.getCouponNameByIds(couponIds));
+				}
+				if (data.getCategory() != null && data.getCategory() > 0) {
+					Optional.ofNullable(corpMsgBulkTaskCategoryMapper.selectById(data.getCategory())).ifPresent(e -> data.setCategoryName(e.getName()));
+				}
+			} catch (Exception e) {
+
+			}
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 标签详情
+	 */
+	@GetMapping("/get/tag/detail/{id}")
+	public Result<Map<String, List<CorpTag>>> getTagDetail(@PathVariable Long id) throws Exception {
+		Map<String, List<CorpTag>> collect = corpMsgBulkTaskService.getTagDetail(id);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(collect);
+	}
+
+	/**
+	 * 删除群发
+	 */
+	@DeleteMapping("/delete/{id}")
+	public Result<String> deleteBulkTaskList(@PathVariable Long id) throws Exception {
+		CorpMsgBulkTask task = corpMsgBulkTaskService.getById(id);
+		if (task == null || !task.getDeleted()) {
+			throw BusinessRuntimeException.getInstance("群发任务不存在");
+		}
+		task.setDeleted(!task.getDeleted());
+		corpMsgBulkTaskService.updateById(task);
+
+		if (task.getJobId() != null) {
+			XxlJobUtil.deleteJob(task.getJobId());
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 群发记录
+	 */
+	@GetMapping("/get/sendRecord/{id}")
+	public Result<SearchResult<CorpMsgBulkTaskSendRecordView>> getSendRecord(@PathVariable Long id, Long startTime, Long endTime) {
+		MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+		builder.put("task_id", String.format("task_id = %s", id));
+		String time = StrUtil.EMPTY;
+		if (startTime != null) {
+			time += String.format(" and created_time >= '%s'", DateTime.of(startTime));
+		}
+		if (endTime != null) {
+			time += String.format(" and created_time < '%s'", DateTime.of(endTime));
+		}
+		if (StrUtil.isNotEmpty(time)) {
+			builder.put("time", time);
+		}
+		SearchResult<CorpMsgBulkTaskSendRecordView> search = beanSearcher.search(CorpMsgBulkTaskSendRecordView.class, builder
+				.orderBy(CorpMsgBulkTaskSendRecordView::getCreateTime).desc()
+				.build());
+		search.getDataList().forEach(e -> {
+			e.setTotal(beanSearcher.searchSum(CorpMsgBulkTaskRecord.class, MapUtils.builder().field(CorpMsgBulkTaskRecord::getSendId, e.getSenId()).build(), "total").intValue());
+			e.setNotSend(e.getTotal() - e.getSend());
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 导出某任务群发记录
+	 */
+	@GetMapping("/export/sendRecord/{id}")
+	public void exportSendRecord(@PathVariable Long id, Long startTime, Long endTime, HttpServletResponse response) {
+		CorpMsgBulkTask byId = corpMsgBulkTaskService.getById(id);
+		if (byId == null) {
+			throw BusinessRuntimeException.getInstance("群发任务不存在");
+		}
+		MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+		builder.put("task_id", String.format("task_id = %s", id));
+		String time = StrUtil.EMPTY;
+		if (startTime != null) {
+			time += String.format(" and created_time >= '%s'", DateTime.of(startTime));
+		}
+		if (endTime != null) {
+			time += String.format(" and created_time < '%s'", DateTime.of(endTime));
+		}
+		if (StrUtil.isNotEmpty(time)) {
+			builder.put("time", time);
+		}
+		List<ExcelCorpMsgBulkTaskSendRecordData> list = beanSearcher.searchAll(ExcelCorpMsgBulkTaskSendRecordData.class, builder
+				.orderBy(ExcelCorpMsgBulkTaskSendRecordData::getCreateTime).desc()
+				.build());
+		String fileName = String.format("%s群发任务发送记录", byId.getName());
+		EasyExcelUtils.createExcelStreamMutilByEasyExcel(response, ExcelCorpMsgBulkTaskSendRecordData.class, list, fileName, fileName, ExcelTypeEnum.XLSX, null);
+	}
+
+	/**
+	 * 群发记录 客服发送详情
+	 */
+	@GetMapping("/get/corpFollow/sendRecord/{id}")
+	public Result<SearchResult<CorpMsgBulkTaskCorpFollowSendRecordView>> getCorpFollowSendRecord(@PathVariable Long id) {
+		MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+		builder.put("send_id", String.format("send_id = %s", id));
+		SearchResult<CorpMsgBulkTaskCorpFollowSendRecordView> search = beanSearcher.search(CorpMsgBulkTaskCorpFollowSendRecordView.class, builder
+				.orderBy(CorpMsgBulkTaskCorpFollowSendRecordView::getCreateTime).desc()
+				.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 提醒群发
+	 */
+	@PutMapping("/remind/{msgid}")
+	public Result<String> remindBulkTask(@PathVariable String msgid) {
+		corpMsgBulkTaskService.remindBulkTask(msgid);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("提醒群发成功");
+	}
+
+	/**
+	 * 立即执行 打动态标签
+	 */
+	@PostMapping("/execute/dynamicRuleTag/{tagId}")
+	public Result<String> executeDynamicRuleTagTask(@PathVariable Long tagId) throws Exception {
+		corpMsgBulkTaskService.executeDynamicRuleTagTask(tagId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 群发任务通知
+	 */
+	@PostMapping("/send/active/corpBulkMsg")
+	public Result<String> sendActiveCorpBulkMsg(@RequestBody CorpActiveBulkMsg msg) {
+		corpMsgBulkTaskService.sendActiveCorpBulkMsg(msg);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+
+	/**
+	 * 新增分类
+	 */
+	@PostMapping("/category/post")
+	public Result<String> postCategory(@RequestBody @Validated CorpMsgBulkTaskCategory corpMsgBulkTaskCategory) {
+		try {
+			corpMsgBulkTaskCategoryMapper.insert(corpMsgBulkTaskCategory);
+		} catch (DuplicateKeyException e) {
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 编辑分类
+	 */
+	@PutMapping("/category/update")
+	public Result<String> upCategory(@RequestBody @Validated CorpMsgBulkTaskCategory corpMsgBulkTaskCategory) {
+		try {
+			corpMsgBulkTaskCategoryMapper.updateById(corpMsgBulkTaskCategory);
+		} catch (DuplicateKeyException e) {
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 删除分类
+	 */
+	@DeleteMapping("/category/delete/{id}")
+	public Result<String> delCategory(@PathVariable Long id) {
+		int count = corpMsgBulkTaskService.count(Wrappers.lambdaQuery(CorpMsgBulkTask.class).eq(CorpMsgBulkTask::getCategory, id).eq(CorpMsgBulkTask::getDeleted, 1));
+		if (count > 0) {
+			throw BusinessRuntimeException.getInstance("存在该分类下的群发任务");
+		}
+		corpMsgBulkTaskCategoryMapper.deleteById(id);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 分类列表
+	 */
+	@GetMapping("/category/get")
+	public Result<SearchResult<CorpMsgBulkTaskCategory>> getCategory() {
+		SearchResult<CorpMsgBulkTaskCategory> search = beanSearcher.search(CorpMsgBulkTaskCategory.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+}

+ 99 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpMsgFollowController.java

@@ -0,0 +1,99 @@
+package com.cyksj.web.controller.manage.corp;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.config.corp.YHLXWxCorpConfig;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.entity.CorpMsgFollowAssociation;
+import com.cyksj.model.manage.views.CorpMsgFollowAssociationView;
+import com.cyksj.service.corp.CorpMsgFollowAssociationService;
+import com.cyksj.service.corp.WxCorpOps;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author chan
+ * @date 2024/1/18 12:14
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/manage/msg/follow")
+public class CorpMsgFollowController {
+
+    private final CorpMsgFollowAssociationService corpMsgFollowAssociationService;
+
+    private final BeanSearcher beanSearcher;
+
+    private final HttpServletRequest request;
+
+    private final WxCorpOps wxCorpOps;
+
+    private final YHLXWxCorpConfig corpConfig;
+
+    /**
+     *  会话存档开启成员列表
+     * @author chan
+     * @date 2024-01-18 17:27
+     */
+    @GetMapping
+    public Result<SearchResult<CorpMsgFollowAssociationView>> getPermitUserList() throws Exception {
+        List<String> res = wxCorpOps.getPermitUserList(corpConfig.getCorpId(), null);
+        SearchResult<CorpMsgFollowAssociationView> search = beanSearcher.search(CorpMsgFollowAssociationView.class, MapUtils.flatBuilder(request.getParameterMap()).field(CorpMsgFollowAssociationView::getUserid, res).op(Operator.InList).build());
+        search.getDataList().forEach((data)->{
+            List<CorpMsgFollowAssociation> corpMsgFollowAssociations = beanSearcher.searchAll(CorpMsgFollowAssociation.class, MapUtils.builder().field(CorpMsgFollowAssociation::getFollowId, data.getUserid()).build());
+            List<String> followIds = corpMsgFollowAssociations.stream().map(CorpMsgFollowAssociation::getAssociationFollowId).collect(Collectors.toList());
+            if(followIds.size() > 0 ){
+                List<CorpMsgFollowAssociationView> corpMsgFollowAssociationViews = beanSearcher.searchAll(CorpMsgFollowAssociationView.class, MapUtils.builder().field(CorpMsgFollowAssociationView::getUserid, followIds).op(Operator.InList).build());
+                data.setAssociationList(corpMsgFollowAssociationViews);
+            }else {
+                data.setAssociationList(new ArrayList<>());
+            }
+        });
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+    /**
+     *
+     * @return
+     */
+    @GetMapping("/all")
+    public Result<List<CorpMsgFollowAssociationView>> getAll() throws Exception {
+        List<String> res = wxCorpOps.getPermitUserList(corpConfig.getCorpId(), null);
+        List<CorpMsgFollowAssociationView> search = beanSearcher.searchAll(CorpMsgFollowAssociationView.class, MapUtils.flatBuilder(request.getParameterMap()).field(CorpMsgFollowAssociationView::getUserid, res).op(Operator.InList).build());
+
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+    /**
+     * 新增会话存档关系
+     * @author chan
+     * @date 2024-01-18 17:26
+     */
+    @PostMapping
+    public Result<String> addAssociation(@RequestBody CorpMsgFollowAssociation corpMsgFollowAssociation){
+        corpMsgFollowAssociationService.save(corpMsgFollowAssociation);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 删除会话存档关系
+     * @author chan
+     * @date 2024-01-18 17:26
+     */
+    @DeleteMapping
+    public Result<String> delAssociation(@RequestBody CorpMsgFollowAssociation corpMsgFollowAssociation){
+        corpMsgFollowAssociationService.remove(Wrappers.lambdaQuery(CorpMsgFollowAssociation.class).eq(CorpMsgFollowAssociation::getAssociationFollowId,corpMsgFollowAssociation.getAssociationFollowId()).eq(CorpMsgFollowAssociation::getFollowId,corpMsgFollowAssociation.getFollowId()));
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+}

+ 225 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpTagController.java

@@ -0,0 +1,225 @@
+package com.cyksj.web.controller.manage.corp;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.lang.Assert;
+import com.alibaba.excel.support.ExcelTypeEnum;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.entity.CorpTag;
+import com.cyksj.model.entity.CorpTagRule;
+import com.cyksj.model.entity.CorpTagRuleRelation;
+import com.cyksj.model.excel.CorpUserTagExcelData;
+import com.cyksj.model.request.CorpDelTagReq;
+import com.cyksj.model.views.*;
+import com.cyksj.service.corp.CorpTagService;
+import com.cyksj.web.util.EasyExcelUtils;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: CorpTagController
+ *创建者: JavaZou
+ *创建时间:2023/6/20 16:01
+ */
+@RestController
+@RequestMapping("/manage/corp")
+@RequiredArgsConstructor
+public class CorpTagController {
+
+	private final CorpTagService corpTagService;
+
+	private final BeanSearcher beanSearcher;
+
+	private final HttpServletRequest request;
+
+
+	/**
+	 * 添加企业标签组
+	 */
+	@PostMapping("/addGroup")
+	public Result<CorpTagGroupView> addGroup(@RequestBody CorpTagGroupView tagGroup) throws Exception {
+		CorpTagGroupView corpTagGroupView = corpTagService.addGroup(tagGroup);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpTagGroupView);
+	}
+
+	/**
+	 * 添加企业标签组及标签
+	 */
+	@PostMapping("/addCorpTagGroup")
+	public Result<CorpTagGroupView> addCorpTagGroup(@RequestBody CorpTagGroupView tagGroup) throws Exception {
+		CorpTagGroupView corpTagGroupView = corpTagService.addCorpTagGroup(tagGroup);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpTagGroupView);
+	}
+
+	/**
+	 * 编辑企业标签
+	 */
+	@PutMapping("/editCorpTag")
+	public Result<String> editCorpTag(@RequestBody CorpTagGroupView tagGroup) throws Exception {
+		corpTagService.editCorpTag(tagGroup);
+
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 删除企业标签组、标签
+	 */
+	@PutMapping("/del")
+	public Result<String> delTags(@RequestBody CorpDelTagReq req) throws Exception {
+		corpTagService.delTags(req);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 获取企业微信标签列表
+	 *
+	 * @author chan
+	 * @date 2022-03-24 下午1:58
+	 */
+	@RequestMapping("/get/corpTagList")
+	public Result<Page<CorpTagGroupView>> getCorpTagList(String groupName, @RequestParam(defaultValue = "0") Integer start, @RequestParam(defaultValue = "10") Integer limit) {
+		Page<CorpTagGroupView> tagGroupList = corpTagService.getCorpTagList(groupName, start, limit);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(tagGroupList);
+	}
+
+	/**
+	 * 规则列表展示
+	 */
+	@GetMapping("/get/ruleList")
+	public Result<List<CorpTagRule>> rules() {
+		List<CorpTagRule> corpTagRules = beanSearcher.searchAll(CorpTagRule.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpTagRules);
+	}
+
+	/**
+	 * 新增规则
+	 */
+	@PostMapping("/post/rule")
+	public Result<String> postRule(CorpTagRule rule) {
+		corpTagService.saveOrUpdateRule(rule);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 编辑规则
+	 */
+	@PutMapping("/put/rule")
+	public Result<String> updateRule(CorpTagRule rule) {
+		corpTagService.saveOrUpdateRule(rule);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 标签管理详情
+	 */
+	@GetMapping("/get/corpTag")
+	public Result<Page<CorpTagBackView>> getCorpTagBackView(@RequestParam(defaultValue = "0") Integer start, @RequestParam(defaultValue = "10") Integer limit, @RequestParam(required = false, defaultValue = "1") Integer type, String name, String[] groupIds, String sort, String order, String tagIdStr) throws Exception {
+		Long adminId = StpUtil.getLoginIdAsLong();
+		//指定管理员可查看标签
+		//马慧文
+		if (adminId == 75l) {
+			tagIdStr = "etvj2DPQAAxqh9QlGJG4N1EbdQBNYGAA";
+		}
+		Page<CorpTagBackView> corpTagBackViews = corpTagService.getCorpTagBackView(start, limit, type, name, groupIds, sort, order, tagIdStr);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpTagBackViews);
+	}
+
+	/**
+	 * 标签对应规则详情
+	 */
+	@GetMapping("/get/corpTag/rule/detail/{id}")
+	public Result<List<CorpTagRuleRelation>> getCorpTagRuleDetail(@PathVariable Long id) {
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpTagService.getCorpTagRuleDetail(id));
+	}
+
+	/**
+	 * 客户详情
+	 */
+	@GetMapping("/get/corpUser/detail")
+	public Result<Page<CorpUserView>> getCorpUserDetail(String tagId, String groupId, @RequestParam(defaultValue = "1") Integer start, @RequestParam(defaultValue = "10") Integer limit) {
+		Page<CorpUserView> result = corpTagService.getCorpUserDetail(tagId, groupId, start, limit);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(result);
+	}
+
+	/**
+	 * 标签组内标签详情
+	 */
+	@GetMapping("/get/groupTag/detail/{groupId}")
+	public Result<List<CorpTag>> getGroupTagDetail(@PathVariable String groupId) {
+		List<CorpTag> corpTags = corpTagService.getGroupTagDetail(groupId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpTags);
+	}
+
+	/**
+	 * 导出标签下所有客户
+	 */
+	@GetMapping("/export")
+	public void export(String[] tagId, String[] groupId, HttpServletResponse response) {
+		List<CorpUserTagExcelData> list = corpTagService.exportUserTagDetail(tagId, groupId);
+		EasyExcelUtils.createExcelStreamMutilByEasyExcel(response, CorpUserTagExcelData.class, list, "标签下客户详情", "标签下客户详情", ExcelTypeEnum.XLSX, null);
+	}
+
+	/**
+	 * 企业微信标签 头部数据
+	 */
+	@GetMapping("/get/headData")
+	public Result<CorpTagHeadDataView> getCorpTagHeadDataView() {
+		CorpTagHeadDataView corpTagHeadDataView = corpTagService.getCorpTagHeadDataView();
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpTagHeadDataView);
+	}
+
+	/**
+	 * 按照标签时间,消费时间 查看用户总览
+	 */
+	@GetMapping("/get/tagUser/headData")
+	public Result<UserOrderDetailView> tagUserHeadData(@RequestParam(required = true) String tagIdStr, String startTime, String endTime, String consumeStTime, String consumeEtTime) {
+		Assert.notEmpty(tagIdStr, "请输入对应标签");
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpTagService.tagUserHeadData(tagIdStr,startTime,endTime,consumeStTime,consumeEtTime));
+	}
+
+	/**
+	 * 通过标签 查找进粉订单记录
+	 */
+	@GetMapping("/get/tagUser/fansData")
+	public Result<Page<UserOrderDetailView> > getTagUserData(@RequestParam(defaultValue = "1") Integer start, @RequestParam(defaultValue = "10") Integer limit, @RequestParam(required = true) String tagIdStr, String startTime, String endTime, String consumeStTime, String consumeEtTime) {
+		Assert.notEmpty(tagIdStr, "请输入对应标签");
+		Page<UserOrderDetailView>  page = corpTagService.getTagUserData(start, limit, tagIdStr, startTime, endTime, consumeStTime, consumeEtTime);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(page);
+	}
+
+	/**
+	 * 通过标签 查找标签用户历史订单记录
+	 */
+	@GetMapping("/get/tagUser/pastData")
+	public Result<Page<UserOrderDetailView>> getTagUserPastRecord(@RequestParam(defaultValue = "1") Integer start, @RequestParam(defaultValue = "10") Integer limit, @RequestParam(required = true) String tagIdStr, String startTime, String endTime, String consumeStTime, String consumeEtTime) {
+		Assert.notEmpty(tagIdStr, "请输入对应标签");
+		Page<UserOrderDetailView> page = corpTagService.getTagUserPastRecord(start, limit, tagIdStr, startTime, endTime, consumeStTime, consumeEtTime);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(page);
+	}
+
+	/**
+	 * 当日进粉户订单详情
+	 */
+	@GetMapping("/get/tagUser/time/orderData")
+	public Result<Page<CorpTagUserOrderDataView>> getTagUserTimeOrderData(@RequestParam(defaultValue = "1") Integer start, @RequestParam(defaultValue = "10") Integer limit, @RequestParam(required = true) String tagIdStr, String toDate, String startTime, String endTime) {
+		Page<CorpTagUserOrderDataView> page = corpTagService.getTagUserTimeOrderData(start, limit, tagIdStr, toDate, startTime, endTime);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(page);
+	}
+
+	/**
+	 * 当日标签用户 本月下单情况
+	 */
+	@GetMapping("/get/fans/toMonth/orders")
+	public Result<UserOrderDetailView> getFansUserToMonthOrdersDetail(String tagId, String date) {
+		UserOrderDetailView userOrderDetailView = corpTagService.getFansUserToMonthOrdersDetail(tagId, date);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(userOrderDetailView);
+	}
+}

+ 41 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpUserController.java

@@ -0,0 +1,41 @@
+package com.cyksj.web.controller.manage.corp;
+
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.manage.views.CorpUserView;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 企业微信/用户列表
+ * @author chan
+ * @date 2024/1/19 10:48
+ */
+@Slf4j
+@RestController
+@RequestMapping("/manage/corp/user")
+@RequiredArgsConstructor
+public class CorpUserController {
+
+    private final BeanSearcher beanSearcher;
+
+    private final HttpServletRequest request;
+
+    /**
+     * 企业微信用户列表
+     */
+    @GetMapping
+    public Result<SearchResult<CorpUserView>> get(){
+        SearchResult<CorpUserView> search = beanSearcher.search(CorpUserView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+}

+ 138 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/CorpWelcomeTemplateController.java

@@ -0,0 +1,138 @@
+package com.cyksj.web.controller.manage.corp;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.corp.CorpCustomerAcquisitionLinkMapper;
+import com.cyksj.mapper.corp.CorpFollowActiveCodeMapper;
+import com.cyksj.mapper.manage.coupon.CouponMapper;
+import com.cyksj.model.entity.CorpCustomerAcquisitionLink;
+import com.cyksj.model.entity.CorpFollowActiveCode;
+import com.cyksj.model.entity.CorpWelcomeTemplate;
+import com.cyksj.model.request.corp.Attachments;
+import com.cyksj.service.corp.CorpWelcomeTemplateService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+/**
+ * admin/企业微信/欢迎语模板
+ * 项目名: yhlxj
+ * 文件名: CorpWelcomeTemplateController
+ * 创建者: JavaZou
+ * 创建时间:2023/12/5 18:23
+ */
+@RestController
+@RequestMapping("/manage/corp/welcome")
+@RequiredArgsConstructor
+public class CorpWelcomeTemplateController {
+
+	private final CorpWelcomeTemplateService corpWelcomeTemplateService;
+
+	private final CouponMapper couponMapper;
+
+	private final CorpFollowActiveCodeMapper corpFollowActiveCodeMapper;
+
+	private final CorpCustomerAcquisitionLinkMapper acquisitionLinkMapper;
+
+	private final BeanSearcher beanSearcher;
+
+	private final HttpServletRequest request;
+
+	/**
+	 * 新增欢迎语 模板
+	 */
+	@PostMapping("/post")
+	public Result<String> post(@RequestBody @Validated CorpWelcomeTemplate template) throws Exception {
+		corpWelcomeTemplateService.saveOrUpdateTemplate(template);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 删除欢迎语 模板
+	 */
+	@DeleteMapping("/delete/{id}")
+	public Result<String> deleteById(@PathVariable Long id) {
+		CorpWelcomeTemplate byId = corpWelcomeTemplateService.getById(id);
+		Integer count = null;
+		if (byId.getType() == 0) {
+			int count1 = corpWelcomeTemplateService.count(Wrappers.lambdaQuery(CorpWelcomeTemplate.class)
+					.eq(CorpWelcomeTemplate::getType, 0).eq(CorpWelcomeTemplate::getDeleted, 1));
+			if (count1 == 0) {
+				throw BusinessRuntimeException.getInstance("自然渠道欢迎语默认存在一条");
+			}
+		} else if (byId.getType() == 1) {
+			count = corpFollowActiveCodeMapper.selectCount(Wrappers.lambdaQuery(CorpFollowActiveCode.class)
+					.eq(CorpFollowActiveCode::getDeleted, 1)
+					.eq(CorpFollowActiveCode::getMsgId, byId.getId()));
+		} else if (byId.getType() == 2) {
+			count = acquisitionLinkMapper.selectCount(Wrappers.lambdaQuery(CorpCustomerAcquisitionLink.class)
+					.eq(CorpCustomerAcquisitionLink::getMsgId, byId.getId()));
+		}
+		if (count != null && count > 0) {
+			throw BusinessRuntimeException.getInstance("该欢迎语已使用");
+		}
+		if (byId != null) {
+			byId.setDeleted(false);
+			corpWelcomeTemplateService.updateById(byId);
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 修改欢迎语 模板
+	 */
+	@PutMapping("/update")
+	public Result<String> update(@RequestBody CorpWelcomeTemplate template) throws Exception {
+		corpWelcomeTemplateService.saveOrUpdateTemplate(template);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 欢迎语模板列表
+	 */
+	@GetMapping("/get")
+	public Result<SearchResult<CorpWelcomeTemplate>> get() {
+		SearchResult<CorpWelcomeTemplate> search = beanSearcher.search(CorpWelcomeTemplate.class, MapUtils.flatBuilder(request.getParameterMap())
+				.field(CorpWelcomeTemplate::getDeleted, 1)
+				.build());
+		search.getDataList().forEach(e -> {
+			String attachmentsStr = e.getAttachmentsStr();
+			if (StrUtil.isNotEmpty(attachmentsStr)) {
+				try {
+					e.setAttachments(Jsons.parseList(attachmentsStr, Attachments.class));
+				} catch (Exception ex) {
+				}
+			}
+			if (StrUtil.isNotBlank(e.getCouponIds())) {
+				try {
+					List<Long> couponIds = Jsons.parseList(e.getCouponIds(), Long.class);
+					e.setCouponName(couponMapper.getCouponNameByIds(couponIds));
+				} catch (Exception ex) {
+				}
+			}
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 不分页 欢迎语模板
+	 */
+	@GetMapping("/get/all")
+	public Result<List<CorpWelcomeTemplate>> getAll() {
+		List<CorpWelcomeTemplate> list = beanSearcher.searchAll(CorpWelcomeTemplate.class, MapUtils.flatBuilder(request.getParameterMap())
+				.field(CorpWelcomeTemplate::getDeleted, 1)
+				.onlySelect(CorpWelcomeTemplate::getId, CorpWelcomeTemplate::getType, CorpWelcomeTemplate::getName).build());
+
+		return GatewayResponse.SUCCESS.newBuilder().toResult(list);
+	}
+}

+ 242 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/relation/CorpRelationController.java

@@ -0,0 +1,242 @@
+package com.cyksj.web.controller.manage.corp.relation;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.GoogleGenerator;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.DoubleVerifyClickRecordMapper;
+import com.cyksj.mapper.DoubleVerifyClickRefreshRecordMapper;
+import com.cyksj.model.dto.DoubleVerifyDto;
+import com.cyksj.model.entity.DoubleVerifyClickRecord;
+import com.cyksj.model.entity.DoubleVerifyClickRefreshRecord;
+import com.cyksj.model.manage.views.GroupsRelationView;
+import com.cyksj.model.request.NfDeviceReq;
+import com.cyksj.model.request.TicketLoginReq;
+import com.cyksj.model.views.DoubleVerifyCodeView;
+import com.cyksj.model.views.RenewalView;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.relation.GroupRelationFrontService;
+import com.cyksj.service.relation.GroupsRelationNetflixService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * admin/企业微信/车票功能
+ * 项目名: yhlxj2
+ * 文件名: CorpRelationController
+ * 创建者: JavaZou
+ * 创建时间:2025/1/16 18:13
+ */
+@RestController
+@RequestMapping("/manage/corp/relation")
+@RequiredArgsConstructor
+public class CorpRelationController {
+	private final GroupRelationFrontService groupRelationFrontService;
+
+	private final GroupsRelationNetflixService groupsRelationNetflixService;
+
+	private final DoubleVerifyClickRecordMapper doubleVerifyRecordMapper;
+
+	private final DoubleVerifyClickRefreshRecordMapper refreshRecordMapper;
+
+	private final BeanSearcher beanSearcher;
+
+	private final RedisService redisService;
+
+
+	/**
+	 * 车票用户所有购买平台
+	 */
+	@GetMapping("/get/pay/goods")
+	public Result<List<RenewalView>> getHasPayGoods(Long userId) {
+		List<RenewalView> hasPayGoods = groupRelationFrontService.getHasPayGoods(userId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(hasPayGoods);
+	}
+
+	/**
+	 * 奈飞同户装置链接
+	 */
+	@GetMapping("/get/nf/household/device")
+	public Result<String> getNfHouseholdDevice(@NotNull Long relationId, Long userId) throws Exception {
+		String url = groupRelationFrontService.getNfHouseholdDevice(userId, relationId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(url);
+	}
+
+	/**
+	 * 读取车票账号登录验证码
+	 */
+	@PostMapping("/get/verifyCode")
+	public Result<String> getAiVerifyCode(@RequestBody TicketLoginReq loginReq) throws Exception {
+		return GatewayResponse.SUCCESS.newBuilder().toResult(groupRelationFrontService.getAiVerifyCode(loginReq));
+	}
+
+	/**
+	 * 设置pin码
+	 */
+	@PutMapping("/set/pin/{relationId}")
+	public Result<String> setPin(@PathVariable Long relationId, String code, Long userId) {
+		JSONObject data = groupsRelationNetflixService.setNetflixPin(relationId, userId, code);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(data.toString());
+	}
+
+	/**
+	 * 查询是否设置成功
+	 */
+	@GetMapping("/get/pin/status/{taskId}")
+	public Result<Integer> getPinStatus(@PathVariable String taskId) throws Exception {
+		return GatewayResponse.SUCCESS.newBuilder().toResult(groupsRelationNetflixService.getPinStatus(taskId));
+	}
+
+	/**
+	 * 踢出奈飞设备
+	 */
+	@PostMapping("/kickOut/nf/{relationId}")
+	public Result<String> nfKickOut(@PathVariable Long relationId, Long userId) {
+		JSONObject data = groupsRelationNetflixService.nfKickOut(userId, relationId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(data.toString());
+	}
+
+	/**
+	 * 代点奈飞同户装置
+	 */
+	@PostMapping("/click/nf/household/device")
+	public void clickNfHouseholdDevice(@RequestBody NfDeviceReq nfDeviceReq) {
+		groupsRelationNetflixService.clickNfHouseholdDevice(nfDeviceReq);
+	}
+
+	/**
+	 * 获取双重验证码记录
+	 */
+	@GetMapping("/get/doubleVerify/codeList")
+	public Result<List<DoubleVerifyCodeView>> getDoubleVerifyCodeList(Long relationId, Long userId) {
+		List<DoubleVerifyCodeView> views = doubleVerifyRecordMapper.getDoubleVerifyCodeList(userId, relationId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(views);
+	}
+
+	/**
+	 * 获取AI类 ChatGPT产品谷歌验证码
+	 */
+	@GetMapping("/get/authCode")
+	public Result<String> getAuthCode(Long relationId, Long userId) {
+		if (relationId == null) throw BusinessRuntimeException.getInstance("车票不存在");
+		GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+				.field(GroupsRelationView::getId, relationId)
+				.field(GroupsRelationView::getUserId, userId).build());
+		if (groupsRelationView == null) throw BusinessRuntimeException.getInstance("车票不存在");
+		if (StrUtil.isEmpty(groupsRelationView.getApiSecret()))
+			throw BusinessRuntimeException.getInstance("未开启验证");
+		DateTime now = DateTime.now();
+		//获取双重验证码
+		Boolean isHasVerifyCode = StrUtil.isNotBlank(groupsRelationView.getVerifyCode()) ? true : false;
+		//一分钟内 只能一人获取
+		boolean b = redisService.setNx(RedisService.key.DOUBLE_VERIFY_CODE_VIEW_MINUTES_KEY.getName() + groupsRelationView.getTripsAccount(), relationId, RedisService.key.DOUBLE_VERIFY_CODE_VIEW_MINUTES_KEY.getTimeout());
+		if (!b) {
+			throw BusinessRuntimeException.getInstance("一分钟内已有人获取该账号验证码,请稍后获取..");
+		}
+		int second = now.second();
+		if (second < 30 && 30 - second < 3) {
+			ThreadUtil.sleep(30 - second + 1, TimeUnit.SECONDS);
+		} else if (second > 30 && 60 - second < 3) {
+			ThreadUtil.sleep(60 - second + 1, TimeUnit.SECONDS);
+		}
+		StringBuilder sb = new StringBuilder();
+		JSONObject re = new JSONObject();
+		try {
+			long code = GoogleGenerator.getCode(groupsRelationView.getApiSecret(), now.getTime());
+			sb.append(code);
+			while (sb.length() < 6) {
+				sb.insert(0, 0);
+			}
+			re.putOpt("code", sb.toString());
+			int nowSecond = DateTime.now().second();
+			int time = 0;
+			if (nowSecond < 30) {
+				time = 30 - nowSecond;
+			} else {
+				time = 60 - nowSecond;
+			}
+			re.putOpt("time", time);
+
+			DoubleVerifyClickRecord doubleVerifyClickRecord = new DoubleVerifyClickRecord();
+			doubleVerifyClickRecord.setRelationId(relationId);
+			//设置为0
+			doubleVerifyClickRecord.setUserId(0l);
+
+			doubleVerifyClickRecord.setAccountId(groupsRelationView.getAccountId());
+			doubleVerifyClickRecord.setAccount(groupsRelationView.getTripsAccount());
+			doubleVerifyClickRecord.setSkuId(groupsRelationView.getSkuId());
+			doubleVerifyClickRecord.setCode(sb.toString());
+			doubleVerifyClickRecord.setRemainTime(time);
+			doubleVerifyRecordMapper.insert(doubleVerifyClickRecord);
+
+			DoubleVerifyDto doubleVerifyDto = new DoubleVerifyDto();
+			doubleVerifyDto.setClickId(doubleVerifyClickRecord.getId());
+			doubleVerifyDto.setApiSecret(groupsRelationView.getApiSecret());
+			redisService.set(String.format("%s-%s", userId, sb.toString()), 0, 60 * 10l);
+			redisService.set(String.format("%s/%s", userId, sb.toString()), Jsons.toJson(doubleVerifyDto), 60 * 10l);
+		} catch (Exception e) {
+			throw BusinessRuntimeException.getInstance("获取双重验证码错误,请联系开发");
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult(re.toString());
+	}
+
+	/**
+	 * 刷新验证码
+	 */
+	@GetMapping("/refresh/code")
+	public Result<String> refreshCode(String code, Long userId) {
+		String objKey = String.format("%s/%s", userId, code);
+		Object objValue =  redisService.get(objKey);
+		String apiSecret = null;
+		Long clickId = 0l;
+		try {
+			DoubleVerifyDto dto = Jsons.parseObject(objValue.toString(), DoubleVerifyDto.class);
+			apiSecret = dto.getApiSecret();
+			clickId = dto.getClickId();
+		} catch (Exception e) {
+			apiSecret = objValue.toString();
+		}
+		if (apiSecret == null) {
+			throw BusinessRuntimeException.getInstance("密钥不存在");
+		}
+		StringBuilder sb = new StringBuilder();
+		try {
+			long c = GoogleGenerator.getCode(apiSecret, DateTime.now().getTime());
+			sb.append(c);
+			while (sb.length() < 6) {
+				sb.insert(0, 0);
+			}
+
+		} catch (Exception e) {
+			throw BusinessRuntimeException.getInstance("刷新双重验证码错误,请联系开发");
+		}
+		if (clickId != 0) {
+			int nowSecond = DateTime.now().second();
+			int time = 0;
+			if (nowSecond < 30) {
+				time = 30 - nowSecond;
+			} else {
+				time = 60 - nowSecond;
+			}
+			//刷新双重验证码记录
+			DoubleVerifyClickRefreshRecord refreshRecord = new DoubleVerifyClickRefreshRecord();
+			refreshRecord.setClickId(clickId);
+			refreshRecord.setUserId(0l);
+			refreshRecord.setCode(sb.toString());
+			refreshRecord.setRemainTime(time);
+			refreshRecordMapper.insert(refreshRecord);
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult(sb.toString());
+	}
+}

+ 337 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/wxkf/CorpKfController.java

@@ -0,0 +1,337 @@
+package com.cyksj.web.controller.manage.corp.wxkf;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.annotation.Log;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.BusinessType;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.UserMapper;
+import com.cyksj.mapper.corp.CorpFollowMapper;
+import com.cyksj.mapper.corp.kf.CorpKfAccountUserChangeRecordMapper;
+import com.cyksj.model.entity.*;
+import com.cyksj.model.request.CorpKfDutySetReq;
+import com.cyksj.model.request.corp.kf.CorpKfDutyReq;
+import com.cyksj.model.views.CorpKfDutyServicersStatusView;
+import com.cyksj.model.views.CorpKfServicerStatisticView;
+import com.cyksj.model.views.CorpKfUserChatRecordView;
+import com.cyksj.model.views.SysCorpFollowView;
+import com.cyksj.service.corp.kf.CorpKfService;
+import com.cyksj.service.mange.cms.service.CmsUserService;
+import com.cyksj.service.user.UserBindRelationService;
+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.dao.DuplicateKeyException;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * admin/微信客服
+ * 项目名: yhlxj2
+ * 文件名: CorpWxKfController
+ * 创建者: JavaZou
+ * 创建时间:2024/12/30 18:02
+ */
+@RestController
+@RequestMapping("/manage/corp/kf")
+@RequiredArgsConstructor
+public class CorpKfController {
+	private final CorpKfService corpKfService;
+
+	private final BeanSearcher beanSearcher;
+
+	private final HttpServletRequest request;
+
+	private final CmsUserService cmsUserService;
+
+	private final CorpFollowMapper corpFollowMapper;
+
+	private final CorpKfAccountUserChangeRecordMapper corpKfAccountUserChangeRecordMapper;
+
+	private final UserBindRelationService userBindRelationService;
+
+	private final UserMapper userMapper;
+
+
+	/**
+	 * 添加客服账号
+	 */
+	@PostMapping("/account/add")
+	public Result<String> addKfAccount(@RequestBody CorpKfAccount corpKfAccount) throws Exception {
+		corpKfService.addKfAccount(corpKfAccount);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 删除客服账号
+	 */
+	@DeleteMapping("/account/del/{id}")
+	public Result<String> delKfAccount(@PathVariable Long id) throws Exception {
+		corpKfService.delKfAccount(id);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 修改客服账号
+	 */
+	@PutMapping("/account/update")
+	public Result<String> updateKfAccount(@RequestBody CorpKfAccount corpKfAccount) throws Exception {
+		corpKfService.updateKfAccount(corpKfAccount);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 客服账号列表
+	 */
+	@GetMapping("/account/get")
+	public Result<SearchResult<CorpKfAccount>> getKfAccount() {
+		SearchResult<CorpKfAccount> search = beanSearcher.search(CorpKfAccount.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 获取客服账号链接
+	 */
+	@GetMapping("/get/url")
+	public Result<String> getUrl(String openKfId, String scene) throws Exception {
+		String url = corpKfService.getUrl(openKfId, scene);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(url);
+	}
+
+	//===============================================账号会话设置========================================================================
+
+	/**
+	 * 设置规则
+	 */
+	@PostMapping("/set/rule")
+	public Result<String> setKfAccountRule(@RequestBody CorpKfAccountSessionConfig CorpKfAccountSessionConfig) {
+		corpKfService.setKfAccountRuleOrAutoReply(CorpKfAccountSessionConfig);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 获取规则设置
+	 */
+	@GetMapping("/get/rule")
+	public Result<CorpKfAccountSessionConfig> getRule(String openKfId) {
+		CorpKfAccountSessionConfig corpKfAccountSessionConfig = beanSearcher.searchFirst(CorpKfAccountSessionConfig.class, MapUtils.flatBuilder(request.getParameterMap()).field(CorpKfAccountSessionConfig::getOpenKfId, openKfId).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpKfAccountSessionConfig);
+	}
+
+	/**
+	 * 设置自动回复
+	 */
+	@PostMapping("/set/auto/reply")
+	public Result<String> setKfAccountAutoReply(@RequestBody CorpKfAccountSessionConfig CorpKfAccountSessionConfig) {
+		corpKfService.setKfAccountRuleOrAutoReply(CorpKfAccountSessionConfig);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 获取自动回复设置
+	 */
+	@GetMapping("/get/auto/reply")
+	public Result<CorpKfAccountSessionConfig> getAutoReply(String openKfId) {
+		CorpKfAccountSessionConfig corpKfAccountSessionConfig = beanSearcher.searchFirst(CorpKfAccountSessionConfig.class, MapUtils.flatBuilder(request.getParameterMap()).field(CorpKfAccountSessionConfig::getOpenKfId, openKfId).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpKfAccountSessionConfig);
+	}
+
+
+
+
+	//===============================================账号值班设置========================================================================
+	/**
+	 * 设置值班时间
+	 */
+	@PostMapping("/set/duty")
+	public Result<String> setKfDuty(@RequestBody CorpKfDutyReq dutyReq) throws Exception {
+		long adminId = StpUtil.getLoginIdAsLong();
+		dutyReq.setAdminId(adminId);
+		corpKfService.setKfDuty(dutyReq);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 客服变更记录
+	 */
+	@GetMapping("/get/follow/change/logs")
+	public Result<SearchResult<CorpKfFollowChangeRecord>> getChangeLogs() {
+		SearchResult<CorpKfFollowChangeRecord> search = beanSearcher.search(CorpKfFollowChangeRecord.class, MapUtils.flatBuilder(request.getParameterMap()).orderBy(CorpKfFollowChangeRecord::getId).desc().build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 获取账号值班设置
+	 */
+	@GetMapping("/get/duty")
+	public Result<List<CorpKfAccountFollow>> getKfDuty(String openKfId) {
+		List<CorpKfAccountFollow> corpKfAccountFollows = beanSearcher.searchAll(CorpKfAccountFollow.class, MapUtils.flatBuilder(request.getParameterMap()).field(CorpKfAccountFollow::getOpenKfId, openKfId).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpKfAccountFollows);
+	}
+
+	//===============================================数据统计========================================================================
+
+	/**
+	 * 数据统计
+	 */
+	@GetMapping("/get/statistics")
+	public Result<SearchResult<CorpKfServicerStatisticView>> getStatisticsData(String startTime, String endTime) {
+		long adminId = StpUtil.getLoginIdAsLong();
+		Boolean rootRole = cmsUserService.isRootRole(adminId);
+		MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+		if (!rootRole) {
+			List<String> userids = corpFollowMapper.selectList(Wrappers.lambdaQuery(CorpFollow.class)
+					.eq(CorpFollow::getCmsUserId, adminId)).stream().map(CorpFollow::getUserid).collect(Collectors.toList());
+			builder.field(CorpKfServicerStatistic::getServicerUserid, userids).op(Operator.InList);
+		}
+		String timeSql = StrUtil.EMPTY;
+		if (startTime != null) {
+			timeSql += String.format(" statistics_date>= '%s'", startTime);
+		}
+		if (endTime != null) {
+			timeSql += String.format(" and statistics_date <= '%s'", endTime);
+		}
+		if (StrUtil.isNotEmpty(timeSql)) {
+			builder.put("time", timeSql);
+		}
+		SearchResult<CorpKfServicerStatisticView> search = beanSearcher.search(CorpKfServicerStatisticView.class, builder.build());
+		search.getDataList().forEach(data -> {
+			SysCorpFollowView sysCorpFollowView = beanSearcher.searchFirst(SysCorpFollowView.class, MapUtils.builder().field(SysCorpFollowView::getFollowId, data.getServicerUserid()).build());
+			if (sysCorpFollowView != null) {
+				data.setName(sysCorpFollowView.getName());
+				data.setFollowName(sysCorpFollowView.getFollowName());
+			}
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	//===============================================聊天记录========================================================================
+
+	/**
+	 * 会话记录
+	 */
+	@GetMapping("/get/session")
+	public Result<SearchResult<CorpKfUserChatRecordView>> getSession(Long userId, String keyword) {
+		String unionId = getUnionid(userId);
+		MapBuilder builder = MapUtils.flatBuilder(request.getParameterMap());
+		if (unionId != null) {
+			builder.field(CorpKfUserChatRecordView::getUnionid, unionId);
+		}
+		if (StringUtils.isNotBlank(keyword)) {
+			builder.put("keyword", String.format(" and exist (select 1 from corp_kf_account_session_chat ct where ct.session_id = cr.id and ct.msg_content like '%s%%')", keyword));
+		}
+		SearchResult<CorpKfUserChatRecordView> search = beanSearcher.search(CorpKfUserChatRecordView.class, builder.build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+
+	/**
+	 * 获取聊天记录
+	 */
+	@GetMapping("/get/session/chat")
+	public Result<SearchResult<CorpKfAccountSessionChat>> getSessionChat(){
+		Map<String, Object> build = MapUtils.flatBuilder(request.getParameterMap()).build();
+		SearchResult<CorpKfAccountSessionChat> search = beanSearcher.search(CorpKfAccountSessionChat.class, build);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 客服上下班
+	 */
+	@PutMapping("/servicer/commute")
+	@Log(module = "微信客服/客服上下班", businessType = BusinessType.PUT, isSaveRequestData = true)
+	public Result<String> servicerCommute(@RequestBody CorpKfDutySetReq corpKfDutySetReq) throws Exception {
+		long cmsUserId = StpUtil.getLoginIdAsLong();
+		corpKfService.servicerCommute(cmsUserId, corpKfDutySetReq.getOpenKfId(), corpKfDutySetReq.getStatus());
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 目前值班人员变化
+	 */
+	@GetMapping("/get/duty/{openKfId}")
+	public Result<List<CorpKfDutyServicersStatusView>> getDutyFollowByOpenKfId(@PathVariable String openKfId) throws Exception {
+		long cmsUserId = StpUtil.getLoginIdAsLong();
+		List<CorpKfDutyServicersStatusView> dutyStatuses = corpKfService.getDutyFollowByOpenKfId(cmsUserId, openKfId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(dutyStatuses);
+	}
+
+	/**
+	 * 目前用户值班微信客服
+	 */
+	@GetMapping("/get/duty/accounts")
+	public Result<List<CorpKfAccount>> getCorpKfAccountByUId() {
+		long cmsUserId = StpUtil.getLoginIdAsLong();
+		List<CorpKfAccount> corpKfAccounts = corpKfService.getCorpKfAccountByUId(cmsUserId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpKfAccounts);
+	}
+
+	/**
+	 * 记录用户切换客服记录
+	 */
+	@PostMapping("/change/account/record/{openKfId}")
+	public Result<String> recordUserAccountRecord(@PathVariable String openKfId) {
+		long cmsUserId = StpUtil.getLoginIdAsLong();
+		CorpKfAccountUserChangeRecord corpKfAccountUserChangeRecord = Optional.ofNullable(corpKfAccountUserChangeRecordMapper.selectOne(Wrappers.lambdaQuery(CorpKfAccountUserChangeRecord.class)
+				.eq(CorpKfAccountUserChangeRecord::getCmsUserId, cmsUserId)
+				.last("limit 1"))).orElse(new CorpKfAccountUserChangeRecord());
+		corpKfAccountUserChangeRecord.setCmsUserId(cmsUserId);
+		corpKfAccountUserChangeRecord.setOpenKfId(openKfId);
+		if (corpKfAccountUserChangeRecord.getId() == null) {
+			try {
+				corpKfAccountUserChangeRecordMapper.insert(corpKfAccountUserChangeRecord);
+			} catch (DuplicateKeyException e) {
+			}
+		} else {
+			corpKfAccountUserChangeRecordMapper.updateById(corpKfAccountUserChangeRecord);
+		}
+
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 用户切换德微信账号
+	 */
+	@GetMapping("/get/change/account")
+	public Result<CorpKfAccountUserChangeRecord> getChangeAccount() {
+		long cmsUserId = StpUtil.getLoginIdAsLong();
+		CorpKfAccountUserChangeRecord corpKfAccountUserChangeRecord = Optional.ofNullable(corpKfAccountUserChangeRecordMapper.selectOne(Wrappers.lambdaQuery(CorpKfAccountUserChangeRecord.class)
+				.eq(CorpKfAccountUserChangeRecord::getCmsUserId, cmsUserId)
+				.last("limit 1"))).orElse(new CorpKfAccountUserChangeRecord());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpKfAccountUserChangeRecord);
+	}
+
+	public String getUnionid(Long userId) {
+		String unionId = null;
+		if (userId != null) {
+			try {
+				unionId = StrUtil.COLON;
+				List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+				if (CollUtil.isNotEmpty(userIdList)) {
+					User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
+							.in(User::getId, userIdList)
+							.ne(User::getUnionid, StrUtil.EMPTY)
+							.last("limit 1"));
+					if (user != null) {
+						unionId = user.getUnionid();
+					}
+				}
+			} catch (Exception e) {
+			}
+		}
+		return unionId;
+	}
+
+}

+ 86 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/wxkf/CorpKfMsgController.java

@@ -0,0 +1,86 @@
+package com.cyksj.web.controller.manage.corp.wxkf;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.corp.kf.CorpKfServicerSessionRecordMapper;
+import com.cyksj.mapper.corp.kf.CorpKfUserMapper;
+import com.cyksj.model.entity.CorpKfServicerSessionRecord;
+import com.cyksj.model.entity.CorpKfUser;
+import com.cyksj.model.kf.CorpKfUserInfo;
+import com.cyksj.service.corp.WxCorpOps;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 项目名: yhlxj2
+ * 文件名: CorpKfMsgController
+ * 创建者: JavaZou
+ * 创建时间:2025/1/2 13:59
+ */
+@RestController
+@RequestMapping("/manage/corp/kf")
+@RequiredArgsConstructor
+public class CorpKfMsgController {
+	private final WxCorpOps wxCorpOps;
+
+	private final CorpKfServicerSessionRecordMapper corpKfServicerSessionRecordMapper;
+
+	private final CorpKfUserMapper corpKfUserMapper;
+
+	/**
+	 * 发送满意度调查消息
+	 */
+	@PostMapping("/send/satisfaction/msg")
+	public void sendSatisfactionMsg(Long id) throws Exception {
+		CorpKfServicerSessionRecord corpKfServicerSessionRecord = corpKfServicerSessionRecordMapper.selectById(id);
+		//更新发送满意度字段
+		if (corpKfServicerSessionRecord != null) {
+			corpKfServicerSessionRecord.setSendSatisfaction(true);
+			corpKfServicerSessionRecordMapper.updateById(corpKfServicerSessionRecord);
+			wxCorpOps.sendSatisfactionMsg(corpKfServicerSessionRecord.getMsgid(), corpKfServicerSessionRecord.getOpenKfId(), corpKfServicerSessionRecord.getExternalUserid(), null);
+		}
+	}
+
+	/**
+	 * 获取客户用户信息
+	 */
+	@GetMapping("/get/user/info")
+	public Result<CorpKfUserInfo> getUserInfo(String externalUserid) throws Exception {
+		CorpKfUserInfo corpKfUserInfo = wxCorpOps.getCorpKfUserInfo(externalUserid);
+		CorpKfUser corpKfUser = corpKfUserMapper.selectOne(Wrappers.lambdaQuery(CorpKfUser.class)
+				.eq(CorpKfUser::getExternalUserid, externalUserid)
+				.last("limit 1"));
+		if (corpKfUser == null) {
+			try {
+				corpKfUser = new CorpKfUser();
+				BeanUtil.copyProperties(corpKfUserInfo, corpKfUser);
+				corpKfUserMapper.insert(corpKfUser);
+			} catch (Exception e) {
+			}
+		} else {
+			corpKfUser.setAvatar(corpKfUserInfo.getAvatar());
+			corpKfUser.setNickname(corpKfUserInfo.getNickname());
+			corpKfUser.setGender(corpKfUserInfo.getGender());
+			corpKfUserMapper.updateById(corpKfUser);
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpKfUserInfo);
+	}
+
+	/**
+	 * 查询用户会话
+	 */
+	@GetMapping("/get/user/session")
+	public Result<CorpKfServicerSessionRecord> getUserSession(String userid, String externalUserid) {
+		CorpKfServicerSessionRecord corpKfServicerSessionRecord = corpKfServicerSessionRecordMapper.selectOne(Wrappers.lambdaQuery(CorpKfServicerSessionRecord.class)
+				.eq(CorpKfServicerSessionRecord::getServicerUserid, userid)
+				.eq(CorpKfServicerSessionRecord::getExternalUserid, externalUserid)
+				.eq(CorpKfServicerSessionRecord::getServiceState, 0)
+				.last("limit 1"));
+		return GatewayResponse.SUCCESS.newBuilder().toResult(corpKfServicerSessionRecord);
+	}
+}

+ 84 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/corp/wxkf/CorpKfUserController.java

@@ -0,0 +1,84 @@
+package com.cyksj.web.controller.manage.corp.wxkf;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.corp.kf.CorpKfUserMapper;
+import com.cyksj.model.entity.CorpKfUser;
+import com.cyksj.model.kf.CorpKfUserInfo;
+import com.cyksj.model.request.CorpKfUserMarkReq;
+import com.cyksj.service.corp.WxCorpOps;
+import lombok.RequiredArgsConstructor;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * admin/企业微信/微信客服
+ * 项目名: yhlxj2
+ * 文件名: CorpKfUserController
+ * 创建者: JavaZou
+ * 创建时间:2025/1/15 18:27
+ */
+@RestController
+@RequestMapping("/manage/corp/kf/user")
+@RequiredArgsConstructor
+public class CorpKfUserController {
+	private final CorpKfUserMapper corpKfUserMapper;
+
+	private final WxCorpOps wxCorpOps;
+
+	/**
+	 * 微信客户详情
+	 */
+	@GetMapping("/get/detail")
+	public Result<CorpKfUser> getCorpKfUserInfo(String externalUserid) throws Exception {
+		CorpKfUser user = getCorpUser(externalUserid);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(user);
+	}
+
+	/**
+	 * 设置备注
+	 */
+	@PostMapping("/set/remark")
+	public Result<String> setUserRemark(@RequestBody CorpKfUser corpKfUser) throws Exception {
+		CorpKfUser user = getCorpUser(corpKfUser.getExternalUserid());
+		user.setRemark(corpKfUser.getRemark());
+		corpKfUserMapper.updateById(user);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	/**
+	 * 标记为设备用户
+	 */
+	@PostMapping("/mark/device")
+	public Result<String> markUserDevice(@RequestBody CorpKfUserMarkReq corpKfUserMarkReq) throws Exception {
+		String externalUserid = corpKfUserMarkReq.getExternalUserid();
+		String followId = corpKfUserMarkReq.getFollowId();
+		CorpKfUser user = getCorpUser(externalUserid);
+		if (user.getIsDevice()) {
+			user.setFollowId(null);
+		} else {
+			user.setFollowId(followId);
+		}
+		user.setIsDevice(!user.getIsDevice());
+		corpKfUserMapper.updateById(user);
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+
+	public CorpKfUser getCorpUser(String externalUserid) throws Exception {
+		CorpKfUser corpKfUser = corpKfUserMapper.selectOne(Wrappers.lambdaQuery(CorpKfUser.class)
+				.eq(CorpKfUser::getExternalUserid, externalUserid)
+				.last("limit 1"));
+		if (corpKfUser == null) {
+			CorpKfUserInfo corpKfUserInfo = wxCorpOps.getCorpKfUserInfo(externalUserid);
+			try {
+				corpKfUser = new CorpKfUser();
+				BeanUtil.copyProperties(corpKfUserInfo, corpKfUser);
+				corpKfUserMapper.insert(corpKfUser);
+			} catch (DuplicateKeyException e) {
+			}
+		}
+		return corpKfUser;
+	}
+}