diff --git a/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/WebSocketController.java b/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/WebSocketController.java new file mode 100644 index 000000000..4ae0335ec --- /dev/null +++ b/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/WebSocketController.java @@ -0,0 +1,53 @@ +package com.stdiet.web.controller.custom; + +import com.alibaba.fastjson.JSONObject; +import com.stdiet.common.core.controller.BaseController; +import com.stdiet.custom.utils.WsUtils; +import com.stdiet.custom.server.WebSocketServer; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@Controller +@EnableScheduling +@RequestMapping("/ws/api") +public class WebSocketController extends BaseController { + + @ResponseBody + @RequestMapping("/push/{cid}") + public Map pushToWeb(@PathVariable String cid, String message) { + if (message == null) { + message = "我是消息44"; + } + Map result = new HashMap(); + try { + WebSocketServer.sendInfo(message, cid); + result.put("code", 200); + result.put("msg", "success"); + } catch (IOException e) { + e.printStackTrace(); + } + return result; + + } + +// @Scheduled(fixedRate = 30000) +// public void boardCast() { +// try { +// JSONObject heartBeat = new JSONObject(); +// heartBeat.put("type", WsUtils.WS_TYPE_HEART_BEAT); +// heartBeat.put("msg", "ping"); +// +// WebSocketServer.sendInfo(heartBeat.toJSONString(), null); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +} diff --git a/stdiet-custom/pom.xml b/stdiet-custom/pom.xml index 5db28a2cc..a7e81b668 100644 --- a/stdiet-custom/pom.xml +++ b/stdiet-custom/pom.xml @@ -99,6 +99,11 @@ ikanalyzer 2012_u6 + + org.apache.tomcat.embed + tomcat-embed-websocket + + \ No newline at end of file diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/domain/SysServicesTopic.java b/stdiet-custom/src/main/java/com/stdiet/custom/domain/SysServicesTopic.java index f465d7522..ca31d6095 100644 --- a/stdiet-custom/src/main/java/com/stdiet/custom/domain/SysServicesTopic.java +++ b/stdiet-custom/src/main/java/com/stdiet/custom/domain/SysServicesTopic.java @@ -85,8 +85,15 @@ public class SysServicesTopic { String toName; String name; + String avatar; + String fromAvatar; + String toAvatar; + + List comments; List replys; + Integer count; + } diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/mapper/SysServicesTopicMapper.java b/stdiet-custom/src/main/java/com/stdiet/custom/mapper/SysServicesTopicMapper.java index 333417f81..0217a94e8 100644 --- a/stdiet-custom/src/main/java/com/stdiet/custom/mapper/SysServicesTopicMapper.java +++ b/stdiet-custom/src/main/java/com/stdiet/custom/mapper/SysServicesTopicMapper.java @@ -18,4 +18,6 @@ public interface SysServicesTopicMapper { int inserSysServicesTopicReply(SysServicesTopic topic); List selectSysServicesTopicSessionByTopicId(String topicId); + + List selectUnreadTopicCount(List topics); } diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/server/WebSocketServer.java b/stdiet-custom/src/main/java/com/stdiet/custom/server/WebSocketServer.java new file mode 100644 index 000000000..7bc506c04 --- /dev/null +++ b/stdiet-custom/src/main/java/com/stdiet/custom/server/WebSocketServer.java @@ -0,0 +1,143 @@ +package com.stdiet.custom.server; + +import com.alibaba.fastjson.JSONObject; +import com.stdiet.common.core.domain.model.LoginUser; +import com.stdiet.common.utils.spring.SpringUtils; +import com.stdiet.custom.domain.SysServicesTopic; +import com.stdiet.custom.service.ISysServicesTopicService; +import com.stdiet.custom.utils.WsUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArraySet; + +@ServerEndpoint(value = "/ws") +@Component +@Slf4j +public class WebSocketServer { + // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。 + private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet(); + + //private static ConcurrentHashMap websocketList = new ConcurrentHashMap<>(); + // 与某个客户端的连接会话,需要通过它来给客户端发送数据 + private Session session; + // 接收sid + private String sid = ""; + + public static CopyOnWriteArraySet getWebSocketSet() { + return webSocketSet; + } + + /** + * 群发自定义消息 + */ + public static void sendInfo(String message, String sid) throws IOException { + log.info("推送消息到窗口" + sid + ",推送内容:" + message + "目标:" + webSocketSet.size()); + for (WebSocketServer item : webSocketSet) { + try { + // 这里可以设定只推送给这个sid的,为null则全部推送 + if (sid == null) { + item.sendMessage(message); + } else if (item.sid.equals(sid)) { + item.sendMessage(message); + } + } catch (IOException e) { + // 清理断开的连接 + webSocketSet.remove(item); + continue; + } + } + } + + // * 连接建立成功调用的方法* + @OnOpen + public void onOpen(Session session) { + this.session = session; + this.sid = String.valueOf(getUserId(session)); + + webSocketSet.add(this); // 加入set中 + + try { + JSONObject object = new JSONObject(); + object.put("type", WsUtils.WS_TYPE_SYSTEM_MESSAGE); + object.put("msg", "连接成功"); + sendMessage(object.toJSONString()); + } catch (IOException e) { + log.error("websocket IO异常"); + } + } + + @OnClose + public void onClose() { + webSocketSet.remove(this); // 从set中删除 + log.info("有一连接关闭!"); + } + + + /** + * @param session + * @param error + */ + @OnError + public void onError(Session session, Throwable error) { + log.error("发生错误"); + error.printStackTrace(); + } + + /** + * 实现服务器主动推送 + */ + public void sendMessage(String message) throws IOException { + log.info("服务器消息推送:" + message); + this.session.getBasicRemote().sendText(message); + } + + public Long getUserId(Session session) { + try { + return ((LoginUser) ((UsernamePasswordAuthenticationToken) session.getUserPrincipal()).getPrincipal()).getUser().getUserId(); + } catch (Exception e) { + return 0L; + } + } + + @OnMessage + public void onMessage(String message, Session session) { + log.info("收到来自窗口" + sid + "的信息:" + message); + try { + String sid = String.valueOf(getUserId(session)); + if (sid.equals("0")) { + return; + } + JSONObject resultObj = new JSONObject(); + if (message.equals(WsUtils.WS_GET_UNREAD_COUNT)) { + SysServicesTopic topic = new SysServicesTopic(); + topic.setUid(sid); + List statusList = new ArrayList<>(); + statusList.add(topic); + ISysServicesTopicService servicesTopicService = SpringUtils.getBean(ISysServicesTopicService.class); + List result = servicesTopicService.selectUnreadTopicCount(statusList); + + JSONObject dataObj = new JSONObject(); + dataObj.put("count", result.get(0).getCount()); + + resultObj.put("type", WsUtils.WS_TYPE_MESSAGE_COUNT); + resultObj.put("msg", "未读消息数"); + resultObj.put("data", dataObj); + + } else if (message.equals(WsUtils.WS_PING)) { + + } + WebSocketServer.sendInfo(resultObj.toJSONString(), sid); + } catch (IOException e) { + e.printStackTrace(); + } + } + + +} \ No newline at end of file diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/service/ISysServicesTopicService.java b/stdiet-custom/src/main/java/com/stdiet/custom/service/ISysServicesTopicService.java index 1b9b14291..054424584 100644 --- a/stdiet-custom/src/main/java/com/stdiet/custom/service/ISysServicesTopicService.java +++ b/stdiet-custom/src/main/java/com/stdiet/custom/service/ISysServicesTopicService.java @@ -17,4 +17,6 @@ public interface ISysServicesTopicService { SysServicesTopic inserSysServicesTopicComment(SysServicesTopic topic); List selectSysServicesTopicSessionByTopicId(String topicId); + + List selectUnreadTopicCount(List topic); } diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysServicesTopicServiceImp.java b/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysServicesTopicServiceImp.java index dcecfdd49..1e57ffabd 100644 --- a/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysServicesTopicServiceImp.java +++ b/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysServicesTopicServiceImp.java @@ -1,15 +1,19 @@ package com.stdiet.custom.service.impl; +import com.alibaba.fastjson.JSONObject; import com.stdiet.common.utils.DateUtils; import com.stdiet.common.utils.uuid.UUID; import com.stdiet.custom.domain.SysCustomer; import com.stdiet.custom.domain.SysServicesTopic; import com.stdiet.custom.mapper.SysCustomerMapper; import com.stdiet.custom.mapper.SysServicesTopicMapper; +import com.stdiet.custom.server.WebSocketServer; import com.stdiet.custom.service.ISysServicesTopicService; +import com.stdiet.custom.utils.WsUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -40,35 +44,58 @@ public class SysServicesTopicServiceImp implements ISysServicesTopicService { List statusList = new ArrayList<>(); + String customerId = String.valueOf(customer.getId()); SysServicesTopic customerStatus = new SysServicesTopic(); - customerStatus.setUid(String.valueOf(customer.getId())); + customerStatus.setUid(customerId); customerStatus.setRole("customer"); customerStatus.setRead(1); customerStatus.setTopicId(topic.getTopicId()); statusList.add(customerStatus); + String dieticianId = String.valueOf(customer.getMainDietitian()); SysServicesTopic dieticianStatus = new SysServicesTopic(); - dieticianStatus.setUid(String.valueOf(customer.getMainDietitian())); + dieticianStatus.setUid(dieticianId); dieticianStatus.setRole("dietician"); dieticianStatus.setRead(0); dieticianStatus.setTopicId(topic.getTopicId()); statusList.add(dieticianStatus); + String afterSaleId = String.valueOf(customer.getAfterDietitian()); SysServicesTopic afterSaleStatus = new SysServicesTopic(); - afterSaleStatus.setUid(String.valueOf(customer.getAfterDietitian())); + afterSaleStatus.setUid(afterSaleId); afterSaleStatus.setRole("after_sale"); afterSaleStatus.setRead(0); afterSaleStatus.setTopicId(topic.getTopicId()); statusList.add(afterSaleStatus); + String dieticianAssistantId = String.valueOf(customer.getAssistantDietitian()); SysServicesTopic dieticianAssistantStatus = new SysServicesTopic(); - dieticianAssistantStatus.setUid(String.valueOf(customer.getAssistantDietitian())); + dieticianAssistantStatus.setUid(dieticianAssistantId); dieticianAssistantStatus.setRole("dietician_assistant"); dieticianAssistantStatus.setRead(0); dieticianAssistantStatus.setTopicId(topic.getTopicId()); statusList.add(dieticianAssistantStatus); - servicesTopicMapper.insertSysServicesTopicStatus(statusList); + int rows = servicesTopicMapper.insertSysServicesTopicStatus(statusList); + if (rows > 0) { + try { + List counts = servicesTopicMapper.selectUnreadTopicCount(statusList); + for (int i = 0; i < counts.size(); i++) { + topic.setId(statusList.get(i).getId()); + JSONObject dataObj = new JSONObject(); + dataObj.put("count", counts.get(i).getCount()); + dataObj.put("data", topic); + + JSONObject msgObj = new JSONObject(); + msgObj.put("type", WsUtils.WS_TYPE_MESSAGE_COUNT); + msgObj.put("msg", "未读消息数"); + msgObj.put("data", dataObj); + WebSocketServer.sendInfo(msgObj.toJSONString(), counts.get(i).getUid()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } topic.setId(customerStatus.getId()); topic.setUid(null); @@ -94,10 +121,13 @@ public class SysServicesTopicServiceImp implements ISysServicesTopicService { status.setTopicId(topic.getTopicId()); status.setRole(topic.getRole()); servicesTopicMapper.updateSysServicesTopicStatus(status); + + afterReply(topic); } return topic; } + @Override public SysServicesTopic inserSysServicesTopicComment(SysServicesTopic topic) { String uuid = java.util.UUID.randomUUID().toString().replace("-", ""); @@ -110,12 +140,51 @@ public class SysServicesTopicServiceImp implements ISysServicesTopicService { status.setTopicId(topic.getTopicId()); status.setRole(topic.getRole()); servicesTopicMapper.updateSysServicesTopicStatus(status); + + afterReply(topic); } return topic; } + public void afterReply(SysServicesTopic topic) { + SysCustomer customer = sysCustomerMapper.selectSysCustomerById(Long.parseLong(topic.getFromUid())); + + List statusList = new ArrayList<>(); + SysServicesTopic dieticianStatus = new SysServicesTopic(); + dieticianStatus.setUid(String.valueOf(customer.getMainDietitian())); + statusList.add(dieticianStatus); + SysServicesTopic afterSaleStatus = new SysServicesTopic(); + afterSaleStatus.setUid(String.valueOf(customer.getAfterDietitian())); + statusList.add(afterSaleStatus); + SysServicesTopic dieticianAssistantStatus = new SysServicesTopic(); + dieticianAssistantStatus.setUid(String.valueOf(customer.getAssistantDietitian())); + statusList.add(dieticianAssistantStatus); + + try { + List counts = servicesTopicMapper.selectUnreadTopicCount(statusList); + for (int i = 0; i < counts.size(); i++) { + JSONObject dataObj = new JSONObject(); + dataObj.put("count", counts.get(i).getCount()); + dataObj.put("topicId", topic.getTopicId()); + + JSONObject msgObj = new JSONObject(); + msgObj.put("type", WsUtils.WS_TYPE_NEW_CUSTOMER_REPLY); + msgObj.put("msg", "新客户回复"); + msgObj.put("data", dataObj); + WebSocketServer.sendInfo(msgObj.toJSONString(), statusList.get(i).getUid()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + @Override public List selectSysServicesTopicSessionByTopicId(String topicId) { return servicesTopicMapper.selectSysServicesTopicSessionByTopicId(topicId); } + + @Override + public List selectUnreadTopicCount(List statusList) { + return servicesTopicMapper.selectUnreadTopicCount(statusList); + } } diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/utils/WsUtils.java b/stdiet-custom/src/main/java/com/stdiet/custom/utils/WsUtils.java new file mode 100644 index 000000000..a1b2286bf --- /dev/null +++ b/stdiet-custom/src/main/java/com/stdiet/custom/utils/WsUtils.java @@ -0,0 +1,16 @@ +package com.stdiet.custom.utils; + +public class WsUtils { + + public static final String WS_TYPE_HEART_BEAT = "WS_TYPE_HEART_BEAT"; + + public static final String WS_TYPE_SYSTEM_MESSAGE = "WS_TYPE_SYSTEM_MESSAGE"; + + public static final String WS_TYPE_MESSAGE_COUNT = "WS_TYPE_MESSAGE_COUNT"; + + public static final String WS_TYPE_NEW_CUSTOMER_REPLY = "WS_TYPE_NEW_CUSTOMER_REPLY"; + + public static final String WS_GET_UNREAD_COUNT = "GET_UNREAD_COUNT"; + + public static final String WS_PING = "ping"; +} diff --git a/stdiet-custom/src/main/resources/mapper/custom/SysServicesTopicMapper.xml b/stdiet-custom/src/main/resources/mapper/custom/SysServicesTopicMapper.xml index 688494714..4afeb6edb 100644 --- a/stdiet-custom/src/main/resources/mapper/custom/SysServicesTopicMapper.xml +++ b/stdiet-custom/src/main/resources/mapper/custom/SysServicesTopicMapper.xml @@ -22,11 +22,22 @@ @@ -39,6 +50,7 @@ + @@ -53,7 +65,9 @@ + + @@ -71,7 +85,9 @@ + + @@ -103,6 +119,19 @@ + + + @@ -225,5 +254,23 @@ + + + + + \ No newline at end of file diff --git a/stdiet-custom/src/main/resources/mapper/custom/SysWxFanStatisticsMapper.xml b/stdiet-custom/src/main/resources/mapper/custom/SysWxFanStatisticsMapper.xml index 0e8f7664e..ce3a124cf 100644 --- a/stdiet-custom/src/main/resources/mapper/custom/SysWxFanStatisticsMapper.xml +++ b/stdiet-custom/src/main/resources/mapper/custom/SysWxFanStatisticsMapper.xml @@ -67,7 +67,7 @@ ) AS wfs ON wd.wechat_account = wfs.wx_id LEFT JOIN (SELECT id, wx_account, wx_phone FROM sys_wx_sale_account) AS wsa ON wd.wechat_account = wsa.id - LEFT JOIN (SELECT dict_label, dict_value FROM sys_dict_data WHERE dict_type = 'fan_channel') AS cn ON cn.dict_value = wd.account_id + LEFT JOIN (SELECT dict_label, dict_value FROM sys_dict_data WHERE dict_type = 'cus_account') AS cn ON cn.dict_value = wd.account_id LEFT JOIN sys_user AS su ON su.user_id = wd.user_id WHERE wd.del_flag = 0 ORDER BY wd.sale_group_id, wd.user_id, wd.wechat_account ASC diff --git a/stdiet-framework/pom.xml b/stdiet-framework/pom.xml index 6db2e7872..c94845c5d 100644 --- a/stdiet-framework/pom.xml +++ b/stdiet-framework/pom.xml @@ -59,6 +59,23 @@ stdiet-system + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + \ No newline at end of file diff --git a/stdiet-framework/src/main/java/com/stdiet/framework/config/SecurityConfig.java b/stdiet-framework/src/main/java/com/stdiet/framework/config/SecurityConfig.java index af34f5aa1..07f5dc900 100644 --- a/stdiet-framework/src/main/java/com/stdiet/framework/config/SecurityConfig.java +++ b/stdiet-framework/src/main/java/com/stdiet/framework/config/SecurityConfig.java @@ -102,6 +102,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { "/custom/wxUserInfo/wx/**", "/custom/wxUserLog/wx/**", "/wx/**", +// "/ws/**", "/wap/**", "/investigate/**", "/common/customerUploadFile", diff --git a/stdiet-framework/src/main/java/com/stdiet/framework/config/WebsocketConfig.java b/stdiet-framework/src/main/java/com/stdiet/framework/config/WebsocketConfig.java new file mode 100644 index 000000000..795c5a971 --- /dev/null +++ b/stdiet-framework/src/main/java/com/stdiet/framework/config/WebsocketConfig.java @@ -0,0 +1,18 @@ +package com.stdiet.framework.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +@Configuration +@ConditionalOnWebApplication +public class WebsocketConfig { + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + + +} diff --git a/stdiet-ui/src/components/RecipesPlanDrawer/index.vue b/stdiet-ui/src/components/RecipesPlanDrawer/index.vue index 66535901b..60e7b60b1 100644 --- a/stdiet-ui/src/components/RecipesPlanDrawer/index.vue +++ b/stdiet-ui/src/components/RecipesPlanDrawer/index.vue @@ -75,14 +75,14 @@ {{ `${scope.row.startDate} 至 ${scope.row.endDate}` }} - + - + - +