Explorar o código

add 用户工单功能

zoujiajian %!s(int64=3) %!d(string=hai) anos
pai
achega
8af7287cec
Modificáronse 19 ficheiros con 829 adicións e 64 borrados
  1. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/manage/sys/WorkOrderChatHistoryMapper.java
  2. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/manage/sys/WorkOrderMapper.java
  3. 41 0
      netflix-dao/src/main/java/com/cyksj/model/entity/WorkOrder.java
  4. 61 0
      netflix-dao/src/main/java/com/cyksj/model/entity/WorkOrderChatHistory.java
  5. 29 0
      netflix-dao/src/main/java/com/cyksj/model/request/WorkOrderReq.java
  6. 30 0
      netflix-dao/src/main/java/com/cyksj/model/views/GroupRelationDetailView.java
  7. 36 0
      netflix-dao/src/main/java/com/cyksj/model/views/UserTicketView.java
  8. 54 0
      netflix-dao/src/main/java/com/cyksj/model/views/WorkOrderChatHistoryView.java
  9. 75 0
      netflix-dao/src/main/java/com/cyksj/model/views/WorkOrderView.java
  10. 4 33
      netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java
  11. 7 0
      netflix-service/src/main/java/com/cyksj/service/user/UserService.java
  12. 16 0
      netflix-service/src/main/java/com/cyksj/service/user/WorkOrderFrontService.java
  13. 32 0
      netflix-service/src/main/java/com/cyksj/service/user/impl/UserServiceImpl.java
  14. 121 0
      netflix-service/src/main/java/com/cyksj/service/user/impl/WorkOrderFrontServiceImpl.java
  15. 28 0
      netflix-service/src/main/java/com/cyksj/task/Scheduler.java
  16. 19 2
      netflix-web/src/main/java/com/cyksj/web/controller/group/GroupRelationController.java
  17. 155 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/sys/WorkOrderController.java
  18. 7 25
      netflix-web/src/main/java/com/cyksj/web/controller/payment/OrderController.java
  19. 88 4
      netflix-web/src/main/java/com/cyksj/web/controller/user/UserController.java

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/manage/sys/WorkOrderChatHistoryMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.manage.sys;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.WorkOrderChatHistory;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrderChatHistoryMapper
+ *创建者: JavaZou
+ *创建时间:2023/4/19 15:48
+ */
+public interface WorkOrderChatHistoryMapper extends BaseMapper<WorkOrderChatHistory> {
+}

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/manage/sys/WorkOrderMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.manage.sys;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.WorkOrder;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrderMapper
+ *创建者: JavaZou
+ *创建时间:2023/4/19 15:44
+ */
+public interface WorkOrderMapper extends BaseMapper<WorkOrder> {
+}

+ 41 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/WorkOrder.java

@@ -0,0 +1,41 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrder
+ *创建者: JavaZou
+ *创建时间:2023/4/19 15:12
+ */
+@Getter
+@Setter
+public class WorkOrder extends BaseEntity{
+	private Long userId;
+
+	private Long replyCustomerServiceId;
+
+	private String topic;
+
+	private Long relationId;
+
+	private Long goodsId;
+
+	private Long skuId;
+
+	private Status status;
+
+	private Long closeCustomerServiceId;
+
+	@Getter
+	public enum Status {
+		waiting("待处理"),
+		close("已关闭");
+
+		String desc;
+
+		Status(String desc) {
+		}
+	}
+}

+ 61 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/WorkOrderChatHistory.java

@@ -0,0 +1,61 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrderChatHistory
+ *创建者: JavaZou
+ *创建时间:2023/4/19 15:15
+ */
+@Getter
+@Setter
+public class WorkOrderChatHistory extends BaseEntity {
+	@NotNull(message = "工单id为空")
+	private Long workOrderId;
+
+	/**
+	 * 消息发送方
+	 */
+	private Messenger fromUser;
+
+	/**
+	 * 消息接收方
+	 */
+	private Messenger toUser;
+
+	/**
+	 * 聊天的用户id
+	 */
+	private Long userId;
+
+	/**
+	 * 回复的客服id
+	 */
+	private Long replyCustomerServiceId;
+
+	/**
+	 * 发送的内容
+	 */
+	private String content;
+
+	/**
+	 * 图片
+	 */
+	private String imgs;
+
+	public enum Messenger {
+		root("客服"),
+		user("用户"),
+		;
+
+		String desc;
+
+		Messenger(String desc) {
+			this.desc = desc;
+		}
+	}
+}

+ 29 - 0
netflix-dao/src/main/java/com/cyksj/model/request/WorkOrderReq.java

@@ -0,0 +1,29 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrderReq
+ *创建者: JavaZou
+ *创建时间:2023/4/20 10:52
+ */
+@Getter
+@Setter
+public class WorkOrderReq {
+	private Long userId;
+
+	@NotEmpty(message = "请输入工单主题")
+	private String topic;
+
+	@NotNull(message = "请选择车票")
+	private Long relationId;
+
+	private String content;
+
+	private String imgs;
+}

+ 30 - 0
netflix-dao/src/main/java/com/cyksj/model/views/GroupRelationDetailView.java

@@ -0,0 +1,30 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: GroupRelationDetailView
+ *创建者: JavaZou
+ *创建时间:2023/4/20 11:20
+ */
+@Getter
+@Setter
+@SearchBean(tables = "groups_relation gr left join groups_trips gt on gt.id = gr.groups_id" +
+		" left join goods_don_sku gdk on gdk.id = gt.sku_id")
+public class GroupRelationDetailView {
+	@DbField("gr.id")
+	private Long relationId;
+
+	@DbField("gr.user_id")
+	private Long userId;
+
+	@DbField("gt.sku_id")
+	private Long skuId;
+
+	@DbField("gdk.goods_id")
+	private Long goodsId;
+}

+ 36 - 0
netflix-dao/src/main/java/com/cyksj/model/views/UserTicketView.java

@@ -0,0 +1,36 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: UserTicketView
+ *创建者: JavaZou
+ *创建时间:2023/4/21 14:52
+ */
+@Getter
+@Setter
+@SearchBean(tables = "groups_relation gr left join groups_trips gt on gt.id = gr.groups_id" +
+		" left join goods_don_sku gdk on gdk.id = gt.sku_id left join goods_don g on g.id = gdk.goods_id")
+public class UserTicketView {
+	@DbField("gr.id")
+	private Long relationId;
+
+	@DbField("gr.user_id")
+	private Long userId;
+
+	@DbField("g.id")
+	private Long goodsId;
+
+	@DbField("gt.sku_id")
+	private Long skuId;
+
+	@DbField("g.title")
+	private String title;
+
+	@DbField("gdk.spec_val")
+	private String specVal;
+}

+ 54 - 0
netflix-dao/src/main/java/com/cyksj/model/views/WorkOrderChatHistoryView.java

@@ -0,0 +1,54 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.WorkOrderChatHistory;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrderChatHistoryView
+ *创建者: JavaZou
+ *创建时间:2023/4/19 15:55
+ */
+@Getter
+@Setter
+@SearchBean(tables = "work_order_chat_history ws left join cms_user cu on ws.reply_customer_service_id = cu.id")
+public class WorkOrderChatHistoryView {
+	@DbField("ws.id")
+	private Long id;
+
+	@DbField("ws.work_order_id")
+	private Long workOrderId;
+
+	@DbField("ws.from_user")
+	private WorkOrderChatHistory.Messenger fromUser;
+
+	@DbField("ws.to_user")
+	private WorkOrderChatHistory.Messenger toUser;
+
+	@DbField("ws.user_id")
+	private Long userId;
+
+	@DbIgnore
+	private String nickname;
+
+	@DbField("ws.content")
+	private String content;
+
+	@DbField("ws.imgs")
+	private String imgs;
+
+	@DbField("ws.reply_customer_service_id")
+	private Long replyCustomerServiceId;
+
+	@DbField("cu.nickname")
+	private String customer;
+
+	@DbField("ws.created_time")
+	private Date createdTime;
+}

+ 75 - 0
netflix-dao/src/main/java/com/cyksj/model/views/WorkOrderView.java

@@ -0,0 +1,75 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.WorkOrder;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrderView
+ *创建者: JavaZou
+ *创建时间:2023/4/19 15:26
+ */
+@Getter
+@Setter
+@SearchBean(tables = "work_order wo left join user u on u.id = wo.user_id")
+public class WorkOrderView {
+	@DbField("wo.id")
+	private Long id;
+
+	@DbField("wo.user_id")
+	private Long userId;
+
+	@DbField("wo.reply_customer_service_id")
+	private Long replyCustomerServiceId;
+
+	/**
+	 * 回复客服
+	 */
+	@DbField("select nickname from cms_user cu where cu.id = wo.reply_customer_service_id")
+	private String replyCustomer;
+
+	@DbField("wo.close_customer_service_id")
+	private Long closeCustomerServiceId;
+
+	/**
+	 * 关闭工单客服
+	 */
+	@DbField("select nickname from cms_user cu where cu.id = wo.close_customer_service_id")
+	private String closCustomer;
+
+	@DbField("u.nickname")
+	private String nickname;
+
+	@DbField("wo.relation_id")
+	private Long relationId;
+
+	@DbField("wo.goods_id")
+	private Long goodsId;
+
+	@DbIgnore
+	private String title;
+
+	@DbField("wo.sku_id")
+	private Long skuId;
+
+	@DbIgnore
+	private String specVal;
+
+	@DbField("wo.topic")
+	private String topic;
+
+	@DbField("wo.status")
+	private WorkOrder.Status status;
+
+	@DbField("wo.created_time")
+	private Date createdTime;
+
+	@DbIgnore
+	private Date finalReplyTime;
+}

+ 4 - 33
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java

@@ -3,19 +3,18 @@ package com.cyksj.service.relation.impl;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.StrUtil;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.cyksj.common.exception.BusinessRuntimeException;
 import com.cyksj.enums.GatewayApiCode;
 import com.cyksj.mapper.GroupsRelationMapper;
 import com.cyksj.mapper.UserMapper;
 import com.cyksj.mapper.corp.CorpUserMapper;
-import com.cyksj.mapper.market.task.UserBindDetailMapper;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.views.GoodsDonViews;
 import com.cyksj.model.views.RenewalView;
 import com.cyksj.service.mange.coupon.CouponCommonService;
 import com.cyksj.service.relation.GroupRelationFrontService;
+import com.cyksj.service.user.UserService;
 import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.param.Operator;
 import com.ejlchina.searcher.util.MapUtils;
@@ -37,7 +36,7 @@ import java.util.stream.Collectors;
 @RequiredArgsConstructor
 @Slf4j
 public class GroupRelationFrontServiceImpl implements GroupRelationFrontService {
-	private final UserBindDetailMapper UserBindDetailMapper;
+	private final UserService userService;
 
 	private final UserMapper userMapper;
 
@@ -53,41 +52,13 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 	public List<RenewalView> getMyTicket(long userId, Boolean isLoginPopularize) {
 		User u = userMapper.selectById(userId);
 		if (u == null) throw BusinessRuntimeException.getGatewayApiCode(GatewayApiCode.CMS_TOKEN_VALID);
-		Boolean isPcUser = false;
-		if (StrUtil.isNotBlank(u.getLoginPhone())) {
-			isPcUser = true;
-		}
 		if (u.getIsBlack()) {
 			return new ArrayList<>();
 		}
-		//是否绑定过手机号
-		LambdaQueryWrapper<UserBindDetail> wrapper = Wrappers.lambdaQuery(UserBindDetail.class).last("limit 1");
-		if (isPcUser) {
-			wrapper.eq(UserBindDetail::getPhone, u.getLoginPhone());
-		} else {
-			wrapper.eq(UserBindDetail::getUserId, userId);
-		}
-		UserBindDetail userBindDetail = UserBindDetailMapper.selectOne(wrapper);
-		Long otherUserId = 0l;
-		if (userBindDetail != null) {
-			if (isPcUser) {
-				otherUserId = userBindDetail.getUserId();
-			} else {
-				if (StrUtil.isNotBlank(userBindDetail.getPhone())) {
-					User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
-							.eq(User::getLoginPhone, userBindDetail.getPhone()).last("limit 1"));
-					if (user != null) {
-						if (user.getIsBlack()) {
-							return new ArrayList<>();
-						}
-						otherUserId = user.getId();
-					}
-				}
-			}
-		}
+		List<Long> userIds = userService.getRelationUserIdList(userId, u);
 		List<RenewalView> list = beanSearcher.searchAll(RenewalView.class, MapUtils.builder()
 				.field(RenewalView::getUserId, 0).op(Operator.GreaterThan)
-				.field(RenewalView::getUserId, userId, otherUserId).op(Operator.InList)
+				.field(RenewalView::getUserId, userIds).op(Operator.InList)
 				.field(RenewalView::getStatus, List.of("validity", "overdue")).op(Operator.InList)
 				.orderBy(RenewalView::getExpiryTime).asc()
 				.build());

+ 7 - 0
netflix-service/src/main/java/com/cyksj/service/user/UserService.java

@@ -6,6 +6,8 @@ import com.cyksj.model.dto.UserWhoami;
 import com.cyksj.model.entity.User;
 import com.cyksj.model.entity.WxSyncLog;
 
+import java.util.List;
+
 /**
  * @author chan
  * @date 2021/10/10 下午3:27
@@ -22,4 +24,9 @@ public interface UserService extends IService<User> {
      * 发放添加固定客服渠道优惠券
      */
     void sendFixedCustomerCoupon(Long popularizeId, Long userId, String unionId);
+
+    /**
+     * 获取关联用户id集合
+     */
+    List<Long> getRelationUserIdList(Long userId, User selectUser);
 }

+ 16 - 0
netflix-service/src/main/java/com/cyksj/service/user/WorkOrderFrontService.java

@@ -0,0 +1,16 @@
+package com.cyksj.service.user;
+
+import com.cyksj.model.entity.WorkOrderChatHistory;
+import com.cyksj.model.request.WorkOrderReq;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrderFrontService
+ *创建者: JavaZou
+ *创建时间:2023/4/20 13:43
+ */
+public interface WorkOrderFrontService {
+	void submitWorkOrder(WorkOrderReq workOrderReq);
+
+	void replyCustomer(WorkOrderChatHistory history);
+}

+ 32 - 0
netflix-service/src/main/java/com/cyksj/service/user/impl/UserServiceImpl.java

@@ -14,6 +14,7 @@ import com.cyksj.mapper.UserMapper;
 import com.cyksj.mapper.UserTransferLogMapper;
 import com.cyksj.mapper.WxSyncLogMapper;
 import com.cyksj.mapper.channel.ChannelPopularizeMapper;
+import com.cyksj.mapper.market.task.UserBindDetailMapper;
 import com.cyksj.model.dto.GzhOAuth2UserInfo;
 import com.cyksj.model.dto.UserWhoami;
 import com.cyksj.model.entity.*;
@@ -29,6 +30,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.util.Assert;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
@@ -60,6 +62,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 
 	private final SysConfigService sysConfigService;
 
+	private final UserBindDetailMapper userBindDetailMapper;
+
 	private static final GlobalThreadPoolTaskExecutor TASK_POOL = GlobalThreadPoolTaskExecutor.getInstance();
 
 	@Override
@@ -214,4 +218,32 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
 			}
 		}
 	}
+
+
+	@Override
+	public List<Long> getRelationUserIdList(Long userId, User selectUser) {
+		List<Long> userIds = new ArrayList<>();
+		userIds.add(userId);
+		UserBindDetail userBindDetail = userBindDetailMapper.selectOne(Wrappers.lambdaQuery(UserBindDetail.class)
+				.eq(UserBindDetail::getUserId, userId).last("limit 1"));
+		if (userBindDetail == null) {
+			User user = Optional.ofNullable(selectUser).orElse(this.getById(userId));
+			if (StrUtil.isEmpty(user.getOpenId()) && StrUtil.isNotEmpty(user.getLoginPhone())) {
+				userBindDetail = userBindDetailMapper.selectOne(Wrappers.lambdaQuery(UserBindDetail.class)
+						.eq(UserBindDetail::getPhone, user.getLoginPhone()).last("limit 1"));
+				if (userBindDetail != null) {
+					userIds.add(userBindDetail.getUserId());
+				}
+			}
+		} else {
+			if (StrUtil.isNotBlank(userBindDetail.getPhone())) {
+				User otherUser = this.getOne(Wrappers.lambdaQuery(User.class)
+						.eq(User::getLoginPhone, userBindDetail.getPhone()).last("limit 1"));
+				if (otherUser != null) {
+					userIds.add(otherUser.getId());
+				}
+			}
+		}
+		return userIds;
+	}
 }

+ 121 - 0
netflix-service/src/main/java/com/cyksj/service/user/impl/WorkOrderFrontServiceImpl.java

@@ -0,0 +1,121 @@
+package com.cyksj.service.user.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.StrUtil;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.enums.GatewayApiCode;
+import com.cyksj.mapper.manage.sys.WorkOrderChatHistoryMapper;
+import com.cyksj.mapper.manage.sys.WorkOrderMapper;
+import com.cyksj.model.entity.User;
+import com.cyksj.model.entity.WorkOrder;
+import com.cyksj.model.entity.WorkOrderChatHistory;
+import com.cyksj.model.request.WorkOrderReq;
+import com.cyksj.model.views.GroupRelationDetailView;
+import com.cyksj.service.user.UserService;
+import com.cyksj.service.user.WorkOrderFrontService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrderFrontServiceImpl
+ *创建者: JavaZou
+ *创建时间:2023/4/20 13:43
+ */
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class WorkOrderFrontServiceImpl implements WorkOrderFrontService {
+	private final WorkOrderMapper workOrderMapper;
+
+	private final WorkOrderChatHistoryMapper historyMapper;
+
+	private final BeanSearcher beanSearcher;
+
+	private final UserService userService;
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	public void submitWorkOrder(WorkOrderReq workOrderReq) {
+		Long userId = workOrderReq.getUserId();
+		User user = userService.getById(userId);
+		if (user == null) throw BusinessRuntimeException.getGatewayApiCode(GatewayApiCode.CMS_TOKEN_VALID);
+		String content = workOrderReq.getContent();
+		String imgs = workOrderReq.getImgs();
+		if (StrUtil.isEmpty(content) && StrUtil.isEmpty(imgs)) {
+			throw BusinessRuntimeException.getInstance("请输入相关内容");
+		}
+		String topic = workOrderReq.getTopic();
+		if (topic.length() > 32) {
+			throw BusinessRuntimeException.getInstance("标题最多输入30字数以内");
+		}
+		if (StrUtil.isNotBlank(content) && content.length() > 200) {
+			throw BusinessRuntimeException.getInstance("问题描述最多输入200字数以内");
+		}
+		List<Long> userIds = userService.getRelationUserIdList(userId, user);
+		Long relationId = workOrderReq.getRelationId();
+		GroupRelationDetailView detailView = beanSearcher.searchFirst(GroupRelationDetailView.class, MapUtils.builder()
+				.field(GroupRelationDetailView::getUserId, userIds).op(Operator.InList)
+				.field(GroupRelationDetailView::getRelationId, relationId)
+				.build());
+		if (detailView == null) throw BusinessRuntimeException.getInstance("车票不存在");
+
+		WorkOrder workOrder = new WorkOrder();
+		BeanUtil.copyProperties(workOrderReq, workOrder);
+		workOrder.setGoodsId(detailView.getGoodsId());
+		workOrder.setSkuId(detailView.getSkuId());
+		workOrderMapper.insert(workOrder);
+
+		WorkOrderChatHistory workOrderChatHistory = new WorkOrderChatHistory();
+		workOrderChatHistory.setFromUser(WorkOrderChatHistory.Messenger.user);
+		workOrderChatHistory.setToUser(WorkOrderChatHistory.Messenger.root);
+		workOrderChatHistory.setUserId(userId);
+		workOrderChatHistory.setWorkOrderId(workOrder.getId());
+		workOrderChatHistory.setContent(content);
+		if (StrUtil.isNotBlank(imgs)) {
+			if (!imgs.contains("[") && !imgs.contains("]")) {
+				imgs = String.format("[\"%s\"]", imgs);
+			}
+			if (imgs.length() > 2000) throw BusinessRuntimeException.getInstance("选择图片过多");
+			workOrderChatHistory.setImgs(imgs);
+		}
+		historyMapper.insert(workOrderChatHistory);
+	}
+
+	@Override
+	public void replyCustomer(WorkOrderChatHistory history) {
+		Long userId = history.getUserId();
+		User user = userService.getById(userId);
+		if (user == null) throw BusinessRuntimeException.getGatewayApiCode(GatewayApiCode.CMS_TOKEN_VALID);
+		Long workOrderId = history.getWorkOrderId();
+		WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
+		if (workOrder == null) throw BusinessRuntimeException.getInstance("该工单不存在");
+		if (workOrder.getStatus() == WorkOrder.Status.close) throw BusinessRuntimeException.getInstance("本次对话已结束,如有问题请提交新的工单");
+		List<Long> userIds = userService.getRelationUserIdList(userId, user);
+		if (!userIds.contains(workOrder.getUserId())) throw BusinessRuntimeException.getInstance("网络错误");
+		String content = history.getContent();
+		String imgs = history.getImgs();
+		if (StrUtil.isEmpty(content) && StrUtil.isEmpty(imgs)) {
+			throw BusinessRuntimeException.getInstance("请回复相关内容");
+		}
+		if (StrUtil.isNotBlank(content) && content.length() > 200)
+			throw BusinessRuntimeException.getInstance("回复内容最多200字数以内");
+
+		if (StrUtil.isNotBlank(imgs)) {
+			if (!imgs.contains("[") && !imgs.contains("]")) {
+				imgs = String.format("[\"%s\"]", imgs);
+			}
+			if (imgs.length() > 2000) throw BusinessRuntimeException.getInstance("选择图片过多");
+		}
+		history.setFromUser(WorkOrderChatHistory.Messenger.user);
+		history.setToUser(WorkOrderChatHistory.Messenger.root);
+		historyMapper.insert(history);
+	}
+}

+ 28 - 0
netflix-service/src/main/java/com/cyksj/task/Scheduler.java

@@ -1,5 +1,6 @@
 package com.cyksj.task;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateField;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
@@ -9,6 +10,7 @@ import com.cyksj.common.util.Jsons;
 import com.cyksj.dto.RedisKey;
 import com.cyksj.mapper.*;
 import com.cyksj.mapper.manage.distribute.DistributeWaitingSendPointsMapper;
+import com.cyksj.mapper.manage.sys.WorkOrderMapper;
 import com.cyksj.model.dto.WxMpTemplateData;
 import com.cyksj.model.dto.WxMpTemplateMessage;
 import com.cyksj.model.entity.*;
@@ -26,6 +28,7 @@ import com.cyksj.service.wechat.WeChatService;
 import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.param.Operator;
 import com.ejlchina.searcher.util.MapUtils;
+import com.google.common.collect.Lists;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.dao.DuplicateKeyException;
@@ -81,6 +84,8 @@ public class Scheduler {
 
         private final CmsAccountService cmsAccountService;
 
+        private final WorkOrderMapper workOrderMapper;
+
 
     /**
      * 每5分钟清理过期账号
@@ -315,4 +320,27 @@ public class Scheduler {
         }
 
     }
+
+    /**
+     * 2天前的工单未关闭 直接关闭
+     */
+    @Scheduled(cron = "0 10 0 * * ?")
+    public void closeWorkOrderBeforeTowDays() {
+        DateTime twoBefore = DateUtil.offsetDay(DateTime.now(), -2);
+        List<WorkOrder> workOrders = beanSearcher.searchAll(WorkOrder.class, MapUtils.builder()
+                .field(WorkOrder::getStatus, WorkOrder.Status.waiting.name())
+                .field(WorkOrder::getCreatedTime, twoBefore).op(Operator.LessEqual)
+                .onlySelect(WorkOrder::getId)
+                .build());
+        if (CollUtil.isNotEmpty(workOrders)) {
+            List<Long> collect = workOrders.stream().map(WorkOrder::getId).collect(Collectors.toList());
+            List<List<Long>> lists = Lists.partition(collect, 1000);
+            log.info("关闭2天前未关闭的工单");
+            lists.forEach(ids -> {
+                workOrderMapper.update(null, Wrappers.lambdaUpdate(WorkOrder.class)
+                        .set(WorkOrder::getStatus, WorkOrder.Status.close)
+                        .in(WorkOrder::getId, ids));
+            });
+        }
+    }
 }

+ 19 - 2
netflix-web/src/main/java/com/cyksj/web/controller/group/GroupRelationController.java

@@ -9,11 +9,14 @@ import com.cyksj.model.entity.GroupsRelation;
 import com.cyksj.model.entity.OrderDon;
 import com.cyksj.model.request.OrderPayRequest;
 import com.cyksj.model.views.RenewalView;
-import com.cyksj.service.corp.CorpService;
+import com.cyksj.model.views.UserTicketView;
 import com.cyksj.service.order.OrderDonService;
 import com.cyksj.service.relation.GroupRelationFrontService;
+import com.cyksj.service.user.UserService;
 import com.cyksj.web.util.StpUserUtil;
 import com.ejlchina.searcher.BeanSearcher;
+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.*;
@@ -38,7 +41,7 @@ public class GroupRelationController {
 
     private final GroupRelationFrontService groupRelationFrontService;
 
-    private final CorpService corpService;
+    private final UserService userService;
 
     @RequestMapping("/get/renewal")
     public Result<List<RenewalView>> get(Boolean isLoginPopularize) {
@@ -76,4 +79,18 @@ public class GroupRelationController {
         Boolean flag = groupRelationFrontService.isJoinCorpWx(userId, false, false);
         return GatewayResponse.SUCCESS.newBuilder().toResult(flag);
     }
+
+    /**
+     * 获取全部车票
+     */
+    @GetMapping("/get")
+    public Result<List<UserTicketView>> getUserTicketView() {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        List<Long> userIds = userService.getRelationUserIdList(userId, null);
+        List<UserTicketView> ticketViews = beanSearcher.searchAll(UserTicketView.class, MapUtils.builder()
+                .field(UserTicketView::getUserId, userIds).op(Operator.InList)
+                .orderBy(UserTicketView::getRelationId).asc()
+                .build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(ticketViews);
+    }
 }

+ 155 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/sys/WorkOrderController.java

@@ -0,0 +1,155 @@
+package com.cyksj.web.controller.manage.sys;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.GoodsDonMapper;
+import com.cyksj.mapper.GoodsDonSkuMapper;
+import com.cyksj.mapper.UserMapper;
+import com.cyksj.mapper.manage.sys.WorkOrderChatHistoryMapper;
+import com.cyksj.mapper.manage.sys.WorkOrderMapper;
+import com.cyksj.model.entity.WorkOrder;
+import com.cyksj.model.entity.WorkOrderChatHistory;
+import com.cyksj.model.views.WorkOrderChatHistoryView;
+import com.cyksj.model.views.WorkOrderView;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.Assert;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Optional;
+
+/*
+ *项目名: netflix
+ *文件名: WorkOrderController
+ *创建者: JavaZou
+ *创建时间:2023/4/19 15:08
+ */
+@RestController
+@RequestMapping("/manage/work/order")
+@RequiredArgsConstructor
+public class WorkOrderController {
+	private final BeanSearcher beanSearcher;
+
+	private final HttpServletRequest request;
+
+	private final GoodsDonMapper goodsDonMapper;
+
+	private final GoodsDonSkuMapper goodsDonSkuMapper;
+
+	private final WorkOrderMapper workOrderMapper;
+
+	private final WorkOrderChatHistoryMapper historyMapper;
+
+	private final UserMapper userMapper;
+
+	@GetMapping("/get")
+	@SaCheckPermission("workOrder:view")
+	public Result<SearchResult<WorkOrderView>> getWorkOrderList() {
+		SearchResult<WorkOrderView> search = beanSearcher.search(WorkOrderView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		search.getDataList().forEach(data -> {
+			Optional.ofNullable(goodsDonMapper.selectById(data.getGoodsId()))
+					.ifPresent(goodsDon -> data.setTitle(goodsDon.getTitle()));
+
+			Optional.ofNullable(goodsDonSkuMapper.selectById(data.getSkuId()))
+					.ifPresent(sku -> data.setSpecVal(sku.getSpecVal()));
+
+			Optional.ofNullable(historyMapper.selectOne(Wrappers.lambdaQuery(WorkOrderChatHistory.class)
+					.eq(WorkOrderChatHistory::getWorkOrderId, data.getId()).orderByDesc(WorkOrderChatHistory::getId)
+					.last("limit 1")
+					.select(WorkOrderChatHistory::getCreatedTime)))
+					.ifPresent(last -> data.setFinalReplyTime(last.getCreatedTime()));
+		});
+
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 修改工单状态
+	 */
+	@PutMapping("/put/{id}")
+	@SaCheckPermission("workOrder:updateStatus")
+	public Result<String> putWorkOrderStatus(@PathVariable Long id) {
+		long adminId = StpUtil.getLoginIdAsLong();
+		WorkOrder workOrder = workOrderMapper.selectById(id);
+
+		if (workOrder == null) {
+			throw BusinessRuntimeException.getInstance("该工单不存在");
+		}
+		if (workOrder.getStatus() == WorkOrder.Status.close) {
+			throw BusinessRuntimeException.getInstance("该工单已关闭");
+		}
+		if (workOrder.getCloseCustomerServiceId() == 0) {
+			workOrder.setCloseCustomerServiceId(adminId);
+		}
+		workOrder.setStatus(WorkOrder.Status.close);
+		workOrderMapper.updateById(workOrder);
+		return GatewayResponse.SUCCESS.newBuilder().toResult("关闭成功");
+	}
+
+	/**
+	 * 查看聊天
+	 */
+	@GetMapping("/get/chat/history")
+	@SaCheckPermission("workOrder:detail")
+	public Result<SearchResult<WorkOrderChatHistoryView>> getChatHistoryById() {
+		SearchResult<WorkOrderChatHistoryView> search = beanSearcher.search(WorkOrderChatHistoryView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		search.getDataList().forEach(data->{
+			Optional.ofNullable(userMapper.selectById(data.getUserId()))
+					.ifPresent(user -> data.setNickname(user.getNickname()));
+		});
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 客服回复用户工单
+	 */
+	@PostMapping("/customer/reply")
+	@SaCheckPermission("workOrder:reply")
+	@Transactional(rollbackFor = Throwable.class)
+	public Result<String> customerReplyWorkOrder(@RequestBody @Validated WorkOrderChatHistory history) {
+		long adminId = StpUtil.getLoginIdAsLong();
+		//回复内容字数
+		Long workOrderId = history.getWorkOrderId();
+		WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
+		Assert.notNull(workOrder, "该工单不存在");
+		if (workOrder.getStatus() == WorkOrder.Status.close) {
+			throw BusinessRuntimeException.getInstance("该工单已被关闭");
+		}
+		history.setFromUser(WorkOrderChatHistory.Messenger.root);
+		history.setToUser(WorkOrderChatHistory.Messenger.user);
+		history.setReplyCustomerServiceId(adminId);
+		history.setUserId(workOrder.getUserId());
+		String content = history.getContent();
+		String imgs = history.getImgs();
+		if (StrUtil.isEmpty(content) && StrUtil.isEmpty(imgs)) {
+			throw BusinessRuntimeException.getInstance("请回复相关内容");
+		}
+		if (StrUtil.isNotBlank(content) && content.length() > 200) {
+			throw BusinessRuntimeException.getInstance("回复内容最多200字数以内");
+		}
+		if (StrUtil.isNotBlank(imgs)) {
+			if (!imgs.contains("[") && !imgs.contains("]")) {
+				imgs = String.format("[\"%s\"]", imgs);
+			}
+			if (imgs.length() > 2000) throw BusinessRuntimeException.getInstance("选择图片过多");
+			history.setImgs(imgs);
+		}
+		historyMapper.insert(history);
+		if (workOrder.getCloseCustomerServiceId() == 0) {
+			//记录首次回复聊天的客服
+			workOrder.setReplyCustomerServiceId(adminId);
+			workOrderMapper.updateById(workOrder);
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
+}

+ 7 - 25
netflix-web/src/main/java/com/cyksj/web/controller/payment/OrderController.java

@@ -34,7 +34,7 @@ import com.cyksj.model.response.AliPayTradeStatus;
 import com.cyksj.model.views.GoodsDonSkuView;
 import com.cyksj.model.views.OrderCommentView;
 import com.cyksj.service.order.OrderDonService;
-import com.cyksj.service.sys.SysConfigService;
+import com.cyksj.service.user.UserService;
 import com.cyksj.web.util.StpUserUtil;
 import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.SearchResult;
@@ -50,7 +50,10 @@ import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.OutputStream;
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -87,7 +90,7 @@ public class OrderController {
     private OrderDonWaybillMapper waybillMapper;
 
     @Autowired
-    private SysConfigService sysConfigService;
+    private UserService userService;
 
     /**
      * 支付
@@ -351,28 +354,7 @@ public class OrderController {
     @GetMapping("/get")
     public Result<SearchResult<OrderDonView>> list(){
         long userId = StpUserUtil.getLoginIdAsLong();
-        List<Long> userIds = new ArrayList<>();
-        userIds.add(userId);
-        UserBindDetail userBindDetail = userBindDetailMapper.selectOne(Wrappers.lambdaQuery(UserBindDetail.class)
-                .eq(UserBindDetail::getUserId, userId).last("limit 1"));
-        if (userBindDetail == null) {
-            User user = userMapper.selectById(userId);
-            if (StrUtil.isEmpty(user.getOpenId()) && StrUtil.isNotEmpty(user.getLoginPhone())) {
-                userBindDetail = userBindDetailMapper.selectOne(Wrappers.lambdaQuery(UserBindDetail.class)
-                        .eq(UserBindDetail::getPhone, user.getLoginPhone()).last("limit 1"));
-                if (userBindDetail != null) {
-                    userIds.add(userBindDetail.getUserId());
-                }
-            }
-        }else {
-            if (StrUtil.isNotBlank(userBindDetail.getPhone())) {
-                User otherUser = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
-                        .eq(User::getLoginPhone, userBindDetail.getPhone()).last("limit 1"));
-                if (otherUser != null) {
-                    userIds.add(otherUser.getId());
-                }
-            }
-        }
+        List<Long> userIds = userService.getRelationUserIdList(userId, null);
         SearchResult<OrderDonView> search = beanSearcher.search(OrderDonView.class, MapUtils.flatBuilder(request.getParameterMap())
                 .field(OrderDonView::getUserId, userIds).op(Operator.InList)
                 .orderBy(OrderDonView::getId).desc()

+ 88 - 4
netflix-web/src/main/java/com/cyksj/web/controller/user/UserController.java

@@ -2,31 +2,38 @@ package com.cyksj.web.controller.user;
 
 import cn.hutool.core.date.DateTime;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.annotation.NoSubmit;
+import com.cyksj.common.exception.BusinessRuntimeException;
 import com.cyksj.dto.Result;
 import com.cyksj.enums.GatewayResponse;
 import com.cyksj.mapper.OrderDonMapper;
 import com.cyksj.mapper.manage.coupon.CouponUserMapper;
 import com.cyksj.mapper.market.task.UserBindDetailMapper;
 import com.cyksj.model.dto.UserWhoami;
-import com.cyksj.model.entity.CouponUser;
-import com.cyksj.model.entity.OrderDon;
-import com.cyksj.model.entity.User;
-import com.cyksj.model.entity.UserBindDetail;
+import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.GroupsRelationView;
 import com.cyksj.model.request.ExchangeCodeTicket;
 import com.cyksj.model.request.ReceiveBenefitsReq;
+import com.cyksj.model.request.WorkOrderReq;
 import com.cyksj.model.views.RenewalView;
 import com.cyksj.model.views.UserAvailableGoodsTicketView;
 import com.cyksj.model.views.UserPersonalView;
+import com.cyksj.model.views.WorkOrderChatHistoryView;
 import com.cyksj.service.exchange.ExchangeCodeService;
 import com.cyksj.service.order.UserRealOrderBenefitsService;
 import com.cyksj.service.relation.GroupRelationFrontService;
 import com.cyksj.service.user.UserService;
+import com.cyksj.service.user.WorkOrderFrontService;
 import com.cyksj.web.util.StpUserUtil;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
 import lombok.RequiredArgsConstructor;
 import org.springframework.util.Assert;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletRequest;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -53,6 +60,12 @@ public class UserController {
 
     private final UserRealOrderBenefitsService userRealOrderBenefitsService;
 
+    private final BeanSearcher beanSearcher;
+
+    private final WorkOrderFrontService workOrderFrontService;
+
+    private final HttpServletRequest request;
+
     @GetMapping(value = "/wx/whoami")
     public Result<UserWhoami> whoami() {
         long userId = StpUserUtil.getLoginIdAsLong();
@@ -135,4 +148,75 @@ public class UserController {
         });
         return GatewayResponse.SUCCESS.newBuilder().toResult(re);
     }
+
+    /**
+     * 用户提交工单
+     */
+    @PostMapping("/workOrder/submit")
+    @NoSubmit
+    public Result<String> submitWorkOrder(@RequestBody @Validated WorkOrderReq workOrderReq) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        workOrderReq.setUserId(userId);
+        workOrderFrontService.submitWorkOrder(workOrderReq);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 用户工单历史列表
+     */
+    @GetMapping("/workOrder/get")
+    public Result<SearchResult<WorkOrder>> getWorkOrder() {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        SearchResult<WorkOrder> search = beanSearcher.search(WorkOrder.class, MapUtils.flatBuilder(request.getParameterMap())
+                .field(WorkOrder::getUserId, userId)
+                .orderBy(WorkOrder::getId).desc()
+                .onlySelect(WorkOrder::getId, WorkOrder::getTopic, WorkOrder::getStatus, WorkOrder::getCreatedTime)
+                .build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+    /**
+     * 用户工单详情
+     */
+    @GetMapping("/workOrder/get/detail/{id}")
+    public Result<SearchResult<WorkOrderChatHistoryView>> getWorkOrderDetails(@PathVariable Long id) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        SearchResult<WorkOrderChatHistoryView> search = beanSearcher.search(WorkOrderChatHistoryView.class, MapUtils.builder()
+                .field(WorkOrderChatHistoryView::getUserId, userId)
+                .field(WorkOrderChatHistoryView::getWorkOrderId, id)
+                .orderBy(WorkOrderChatHistoryView::getId).desc()
+                .selectExclude(WorkOrderChatHistoryView::getReplyCustomerServiceId,
+                        WorkOrderChatHistoryView::getCustomer)
+                .build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+    /**
+     * 工单状态查询
+     */
+    @GetMapping("/workOrder/get/status/{id}")
+    public Result<WorkOrder> getWorkOrderStatus(@PathVariable Long id) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        WorkOrder workOrder = beanSearcher.searchFirst(WorkOrder.class, MapUtils.builder()
+                .field(WorkOrder::getUserId, userId)
+                .field(WorkOrder::getId, id)
+                .onlySelect(WorkOrder::getId, WorkOrder::getUserId, WorkOrder::getStatus)
+                .build());
+        if (workOrder == null) {
+            throw BusinessRuntimeException.getInstance("该工单不存在");
+        }
+        return GatewayResponse.SUCCESS.newBuilder().toResult(workOrder);
+    }
+
+
+    /**
+     * 用户回复客服
+     */
+    @PostMapping("/workOrder/reply")
+    public Result<String> userReplyWorkOrder(@RequestBody @Validated WorkOrderChatHistory history) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        history.setUserId(userId);
+        workOrderFrontService.replyCustomer(history);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
 }