zhuyifan

feat(server): 实现 WebSocket 服务并添加消息推送功能

...@@ -43,6 +43,7 @@ dependencies { ...@@ -43,6 +43,7 @@ dependencies {
43 implementation "org.apache.poi:poi-ooxml:4.1.2" //poi 43 implementation "org.apache.poi:poi-ooxml:4.1.2" //poi
44 implementation 'cn.hutool:hutool-all:5.8.27' //huTool-util 44 implementation 'cn.hutool:hutool-all:5.8.27' //huTool-util
45 implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.11.2' // Jackson XML 模块 45 implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.11.2' // Jackson XML 模块
46 + implementation 'org.springframework.boot:spring-boot-starter-websocket'// websocket
46 47
47 implementation 'com.aliyun:aliyun-java-sdk-core:4.6.4' //阿里云SDK核心库 48 implementation 'com.aliyun:aliyun-java-sdk-core:4.6.4' //阿里云SDK核心库
48 implementation 'com.aliyun:aliyun-java-sdk-dysmsapi:2.2.1'//阿里云短信服务SDK 49 implementation 'com.aliyun:aliyun-java-sdk-dysmsapi:2.2.1'//阿里云短信服务SDK
......
...@@ -4,7 +4,7 @@ import org.springframework.boot.SpringApplication; ...@@ -4,7 +4,7 @@ import org.springframework.boot.SpringApplication;
4 import org.springframework.boot.autoconfigure.SpringBootApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
5 import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration; 5 import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
6 6
7 -@SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class) 7 +@SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class, scanBasePackages = {"com.infoloop.tianting"})
8 public class App { 8 public class App {
9 9
10 public static void main(String[] args) { 10 public static void main(String[] args) {
......
1 package com.infoloop.tianting.config; 1 package com.infoloop.tianting.config;
2 2
3 -import static springfox.documentation.builders.RequestHandlerSelectors.basePackage;
4 -
5 import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; 3 import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
6 -import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
7 import com.infoloop.tianting.constant.CommonConstants; 4 import com.infoloop.tianting.constant.CommonConstants;
8 import io.swagger.models.auth.In; 5 import io.swagger.models.auth.In;
9 -import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Bean;
11 import org.springframework.context.annotation.Configuration; 7 import org.springframework.context.annotation.Configuration;
12 import springfox.documentation.builders.ApiInfoBuilder; 8 import springfox.documentation.builders.ApiInfoBuilder;
...@@ -43,7 +39,7 @@ public class SwaggerConfig { ...@@ -43,7 +39,7 @@ public class SwaggerConfig {
43 "/v2/api-docs", 39 "/v2/api-docs",
44 "/v3/api-docs", 40 "/v3/api-docs",
45 "/v3/api-docs/**", 41 "/v3/api-docs/**",
46 - "/doc.html", 42 + "/doc.html"
47 }; 43 };
48 44
49 /*引入Knife4j提供的扩展类*/ 45 /*引入Knife4j提供的扩展类*/
......
1 +package com.infoloop.tianting.config;
2 +
3 +import com.infoloop.tianting.intercepter.WebSocketHandshakeInterceptor;
4 +import com.infoloop.tianting.server.WebSocketServer;
5 +import lombok.extern.slf4j.Slf4j;
6 +import org.springframework.context.annotation.Bean;
7 +import org.springframework.context.annotation.Configuration;
8 +import org.springframework.web.socket.config.annotation.EnableWebSocket;
9 +import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
10 +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
11 +import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
12 +
13 +@Slf4j
14 +@Configuration
15 +@EnableWebSocket
16 +public class WebSocketConfig implements WebSocketConfigurer {
17 +
18 + @Override
19 + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
20 + registry.addHandler(webSocketServer(), "/ws/message")
21 + .setAllowedOrigins("*")
22 + .addInterceptors(new WebSocketHandshakeInterceptor())
23 + ;
24 + }
25 + @Bean
26 + public ServletServerContainerFactoryBean createWebSocketContainer() {
27 + ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
28 + container.setMaxTextMessageBufferSize(8192);
29 + container.setMaxBinaryMessageBufferSize(8192);
30 + return container;
31 + }
32 +
33 + @Bean
34 + public WebSocketServer webSocketServer() {
35 + return new WebSocketServer();
36 + }
37 +}
...@@ -58,4 +58,8 @@ public interface CommonConstants { ...@@ -58,4 +58,8 @@ public interface CommonConstants {
58 58
59 String OPEN_ID = "openId"; 59 String OPEN_ID = "openId";
60 60
61 + String USER_TYPE = "userType";
62 +
63 + String USER_ID = "userId";
64 +
61 } 65 }
......
1 package com.infoloop.tianting.enums; 1 package com.infoloop.tianting.enums;
2 2
3 +import com.infoloop.tianting.server.session.UserTypeEnum;
3 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
4 import lombok.Getter; 5 import lombok.Getter;
5 6
6 @Getter 7 @Getter
7 @AllArgsConstructor 8 @AllArgsConstructor
8 public enum LoginSourceEnum{ 9 public enum LoginSourceEnum{
9 - CUSTOMER(1, "住院客户"), 10 + CUSTOMER(1, UserTypeEnum.MICRO),
10 - MINI_PROGRAM(2, "阳光厅客户"), 11 + MINI_PROGRAM(2, UserTypeEnum.MICRO),
11 - OPERATOR(3, "院区端"), 12 + OPERATOR(3, UserTypeEnum.CLIENT),
12 - KDS(4, "KDS"), 13 + KDS(4, UserTypeEnum.KDS),
13 ; 14 ;
14 15
15 /** 16 /**
...@@ -19,5 +20,14 @@ public enum LoginSourceEnum{ ...@@ -19,5 +20,14 @@ public enum LoginSourceEnum{
19 /** 20 /**
20 * 订单类型的名字 21 * 订单类型的名字
21 */ 22 */
22 - private final String name; 23 + private final UserTypeEnum userType;
24 +
25 + public static LoginSourceEnum getByValue(Integer value) {
26 + for (LoginSourceEnum orderTypeEnum : LoginSourceEnum.values()) {
27 + if (orderTypeEnum.getValue().equals(value)) {
28 + return orderTypeEnum;
29 + }
30 + }
31 + return null;
32 + }
23 } 33 }
......
1 +package com.infoloop.tianting.intercepter;
2 +
3 +import cn.dev33.satoken.stp.StpUtil;
4 +import cn.hutool.core.convert.Convert;
5 +import com.infoloop.tianting.constant.CommonConstants;
6 +import com.infoloop.tianting.enums.LoginSourceEnum;
7 +import org.springframework.http.server.ServerHttpRequest;
8 +import org.springframework.http.server.ServerHttpResponse;
9 +import org.springframework.util.StringUtils;
10 +import org.springframework.web.socket.WebSocketHandler;
11 +import org.springframework.web.socket.server.HandshakeInterceptor;
12 +
13 +import java.util.Map;
14 +
15 +@SuppressWarnings("all")
16 +public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
17 +
18 + @Override
19 + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
20 + final var authorization = request.getHeaders().getFirst(CommonConstants.AUTHORIZATION);
21 + if (StringUtils.isEmpty(authorization)) {
22 + return false;
23 + }
24 + final var loginSource = Convert.toInt(StpUtil.getExtra(CommonConstants.LOGIN_SOURCE));
25 + final var loginSourceEnum = LoginSourceEnum.getByValue(loginSource);
26 + attributes.put(CommonConstants.USER_TYPE, loginSourceEnum.getUserType().name());
27 + attributes.put(CommonConstants.USER_ID, StpUtil.getLoginIdAsString());
28 + return true;
29 + }
30 +
31 + @Override
32 + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
33 + }
34 +}
1 +package com.infoloop.tianting.server;
2 +
3 +
4 +import com.infoloop.tianting.constant.CommonConstants;
5 +import com.infoloop.tianting.server.message.SocketMessage;
6 +import com.infoloop.tianting.server.session.UserSessionData;
7 +import com.infoloop.tianting.server.session.UserSessionKey;
8 +import com.infoloop.tianting.server.session.UserTypeEnum;
9 +import lombok.extern.slf4j.Slf4j;
10 +import org.springframework.web.socket.CloseStatus;
11 +import org.springframework.web.socket.TextMessage;
12 +import org.springframework.web.socket.WebSocketSession;
13 +import org.springframework.web.socket.handler.TextWebSocketHandler;
14 +
15 +import java.io.IOException;
16 +import java.util.List;
17 +import java.util.concurrent.ConcurrentHashMap;
18 +import java.util.concurrent.Executors;
19 +import java.util.concurrent.ScheduledExecutorService;
20 +import java.util.concurrent.TimeUnit;
21 +import java.util.stream.Collectors;
22 +
23 +@Slf4j
24 +public class WebSocketServer extends TextWebSocketHandler {
25 +
26 + private static final ConcurrentHashMap<UserSessionKey, UserSessionData> userSessions = new ConcurrentHashMap<>();
27 + private static final long INACTIVITY_TIMEOUT = 2 * 60 * 60 * 1000;
28 + private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
29 +
30 + static {
31 + scheduler.scheduleAtFixedRate(WebSocketServer::cleanInactiveSessions, 1, 1, TimeUnit.HOURS);
32 + }
33 +
34 + private static void cleanInactiveSessions() {
35 + final var currentTime = System.currentTimeMillis();
36 + final var iterator = userSessions.entrySet().iterator();
37 + while (iterator.hasNext()) {
38 + final var entry = iterator.next();
39 + final var key = entry.getKey();
40 + final var userData = entry.getValue();
41 + final var lastActiveTime = userData.getLastActiveTime();
42 + if (currentTime - lastActiveTime > INACTIVITY_TIMEOUT) {
43 + try {
44 + userData.closeSession();
45 + log.info("closeSession,userId : {}, userType : {}", key.getUserId(), key.getUserType());
46 + } catch (IOException e) {
47 + log.error("closeSession error,userId : {}, userType : {}", key.getUserId(), key.getUserType(), e);
48 + }
49 + iterator.remove();
50 + }
51 + }
52 + }
53 +
54 + public static <T> void sendMessageToUser(UserSessionKey key, SocketMessage<T> socketMessage) throws IOException {
55 + sendMessageToUsers(List.of(key), socketMessage);
56 + }
57 +
58 + public static <T> void sendMessageToUsers(List<UserSessionKey> keys, SocketMessage<T> socketMessage) throws IOException {
59 + for (final var key : keys) {
60 + final var userData = userSessions.get(key);
61 + if (userData != null && userData.getSession().isOpen()) {
62 + userData.getSession().sendMessage(new TextMessage(socketMessage.toJsonString()));
63 + userData.updateLastActiveTime();
64 + log.info("send message to userId : {}, userType : {}", key.getUserId(), key.getUserType());
65 + } else {
66 + log.info("userId : {}, userType : {} WebSocket connect closed; ", key.getUserId(), key.getUserType());
67 + }
68 + }
69 + }
70 +
71 + public static <T> void sendMessageByUserType(UserTypeEnum userType, SocketMessage<T> socketMessage) throws IOException {
72 + sendMessageByUserTypes(List.of(userType), socketMessage);
73 + }
74 +
75 + public static <T> void sendMessageByUserTypes(List<UserTypeEnum> userTypes, SocketMessage<T> socketMessage) throws IOException {
76 + final var keys = userSessions.keySet().stream()
77 + .filter(userSessionData -> userTypes.contains(userSessionData.getUserType()))
78 + .collect(Collectors.toList());
79 + sendMessageToUsers(keys, socketMessage);
80 + }
81 +
82 + public static void closeConnection(UserSessionKey key) {
83 + final var userData = userSessions.get(key);
84 + if (userData != null) {
85 + try {
86 + userData.closeSession();
87 + userSessions.remove(key);
88 + log.info("WebSocket close; userId = {}, userType = {}", key.getUserId(), key.getUserType());
89 + } catch (IOException e) {
90 + log.error("WebSocket close failed: userId = {}, userType = {}", key.getUserId(), key.getUserType(), e);
91 + }
92 + } else {
93 + log.warn("Not Found userId : {}, userType : {} WebSocket Connect ", key.getUserId(), key.getUserType());
94 + }
95 + }
96 +
97 + private UserSessionKey getUserSessionKey(WebSocketSession session) {
98 + final var userId = (String) session.getAttributes().get(CommonConstants.USER_ID);
99 + final var userTypeStr = (String) session.getAttributes().get(CommonConstants.USER_TYPE);
100 + final var userType = UserTypeEnum.fromString(userTypeStr);
101 + if (userId != null && userType != null) {
102 + return UserSessionKey.builder().userId(userId).userType(userType).build();
103 + }
104 + return null;
105 + }
106 +
107 + @Override
108 + public void afterConnectionEstablished(WebSocketSession session) {
109 + final var key = getUserSessionKey(session);
110 + if (key != null) {
111 + userSessions.put(key, new UserSessionData(session));
112 + log.info("WebSocket connect success: userId : {}, userType : {}", key.getUserId(), key.getUserType());
113 + } else {
114 + log.warn("WebSocket connect failed,unable to get valid user information");
115 + }
116 + }
117 +
118 + @Override
119 + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
120 + final var key = getUserSessionKey(session);
121 + if (key != null) {
122 + userSessions.remove(key);
123 + log.info("WebSocket closed: userId : {}, userType : {}", key.getUserId(), key.getUserType());
124 + } else {
125 + log.warn("WebSocket closed failed,unable to get valid user information");
126 + }
127 + }
128 +}
...\ No newline at end of file ...\ No newline at end of file
1 +package com.infoloop.tianting.server.message;
2 +
3 +import lombok.Getter;
4 +
5 +@Getter
6 +public enum MessageTypeEnum {
7 +
8 + ORDER("订单类型消息");
9 +
10 + private final String description;
11 +
12 + MessageTypeEnum(String description) {
13 + this.description = description;
14 + }
15 +}
1 +package com.infoloop.tianting.server.message;
2 +
3 +import lombok.Getter;
4 +
5 +@Getter
6 +public enum NoticeTypeEnum {
7 + ORDER_CREATED("下单成功", MessageTypeEnum.ORDER),
8 + ORDER_PAYMENT_SUCCESS("支付成功", MessageTypeEnum.ORDER);
9 +
10 + private final String description;
11 +
12 + private final MessageTypeEnum messageType;
13 +
14 + NoticeTypeEnum(String description, MessageTypeEnum messageType) {
15 + this.description = description;
16 + this.messageType = messageType;
17 + }
18 +}
1 +package com.infoloop.tianting.server.message;
2 +
3 +import com.infoloop.tianting.utils.JsonUtil;
4 +import lombok.AllArgsConstructor;
5 +import lombok.Data;
6 +import lombok.NoArgsConstructor;
7 +import lombok.experimental.SuperBuilder;
8 +
9 +import java.io.Serializable;
10 +
11 +@Data
12 +@SuperBuilder
13 +@NoArgsConstructor
14 +@AllArgsConstructor
15 +public final class SocketMessage<T> implements Serializable {
16 +
17 + private static final long serialVersionUID = 1L;
18 +
19 + private MessageTypeEnum type;
20 + private NoticeTypeEnum noticeType;
21 + private String subject;
22 + private String message;
23 + private T data;
24 +
25 + public String toJsonString() {
26 + return JsonUtil.writeAsJson(this);
27 + }
28 +}
1 +package com.infoloop.tianting.server.message.data;
2 +
3 +import lombok.Builder;
4 +import lombok.Data;
5 +
6 +@Data
7 +@Builder
8 +public class OrderCreateSuccessData {
9 +
10 + private int orderId;
11 +
12 +}
1 +package com.infoloop.tianting.server.message.data;
2 +
3 +import lombok.Builder;
4 +import lombok.Data;
5 +
6 +@Data
7 +@Builder
8 +public class OrderPaymentSuccessData {
9 +
10 + private int orderId;
11 +
12 + private String orderCode;
13 +
14 + private String createTime;
15 +
16 + private String payTime;
17 +}
1 +package com.infoloop.tianting.server.session;
2 +
3 +import lombok.Getter;
4 +import org.springframework.web.socket.CloseStatus;
5 +import org.springframework.web.socket.WebSocketSession;
6 +
7 +import java.io.IOException;
8 +import java.util.concurrent.atomic.AtomicLong;
9 +
10 +@Getter
11 +public class UserSessionData {
12 + private final WebSocketSession session;
13 + private final AtomicLong lastActiveTime;
14 +
15 + public UserSessionData(WebSocketSession session) {
16 + this.session = session;
17 + this.lastActiveTime = new AtomicLong(System.currentTimeMillis());
18 + }
19 +
20 + public long getLastActiveTime() {
21 + return lastActiveTime.get();
22 + }
23 +
24 + public void updateLastActiveTime() {
25 + lastActiveTime.set(System.currentTimeMillis());
26 + }
27 +
28 + public void closeSession() throws IOException {
29 + if (session != null && session.isOpen()) {
30 + session.close(CloseStatus.GOING_AWAY);
31 + }
32 + }
33 +}
1 +package com.infoloop.tianting.server.session;
2 +
3 +import lombok.Builder;
4 +import lombok.Data;
5 +
6 +@Data
7 +@Builder
8 +public class UserSessionKey {
9 + private final String userId;
10 + private final UserTypeEnum userType;
11 +}
1 +package com.infoloop.tianting.server.session;
2 +
3 +import lombok.Getter;
4 +
5 +@Getter
6 +public enum UserTypeEnum {
7 + ENTERPRISE,
8 + CLIENT,
9 + MICRO,
10 + KDS;
11 +
12 + public static UserTypeEnum fromString(String value) {
13 + if (value == null || value.isEmpty()) {
14 + return null;
15 + }
16 + try {
17 + return UserTypeEnum.valueOf(value.toUpperCase());
18 + } catch (IllegalArgumentException e) {
19 + return null;
20 + }
21 + }
22 +}
...@@ -42,7 +42,6 @@ import com.infoloop.tianting.clientcustomerorderservice.SkuSellQuantityModificat ...@@ -42,7 +42,6 @@ import com.infoloop.tianting.clientcustomerorderservice.SkuSellQuantityModificat
42 import com.infoloop.tianting.clientcustomerorderservice.UpdateClientCustomerOrderRpcRequest; 42 import com.infoloop.tianting.clientcustomerorderservice.UpdateClientCustomerOrderRpcRequest;
43 import com.infoloop.tianting.clientcustomerorderservice.UpdateClientCustomerOrderRpcResponse; 43 import com.infoloop.tianting.clientcustomerorderservice.UpdateClientCustomerOrderRpcResponse;
44 import com.infoloop.tianting.context.LoginContextHolder; 44 import com.infoloop.tianting.context.LoginContextHolder;
45 -import com.infoloop.tianting.enums.OrderTypeEnum;
46 import com.infoloop.tianting.exception.ClientEndExceptions; 45 import com.infoloop.tianting.exception.ClientEndExceptions;
47 import com.infoloop.tianting.exception.ErrorCodeEnum; 46 import com.infoloop.tianting.exception.ErrorCodeEnum;
48 import com.infoloop.tianting.logic.delay.DelayedQueue; 47 import com.infoloop.tianting.logic.delay.DelayedQueue;
...@@ -64,17 +63,26 @@ import com.infoloop.tianting.model.dto.OrderDbDTO.OrderDetailOperationDto; ...@@ -64,17 +63,26 @@ import com.infoloop.tianting.model.dto.OrderDbDTO.OrderDetailOperationDto;
64 import com.infoloop.tianting.model.dto.OrderDbDTO.QueryOrderByConditionDto; 63 import com.infoloop.tianting.model.dto.OrderDbDTO.QueryOrderByConditionDto;
65 import com.infoloop.tianting.model.dto.OrderDbDTO.QueryOrderByPaginationDto; 64 import com.infoloop.tianting.model.dto.OrderDbDTO.QueryOrderByPaginationDto;
66 import com.infoloop.tianting.model.dto.OrderDbDTO.UpdateOrderDto; 65 import com.infoloop.tianting.model.dto.OrderDbDTO.UpdateOrderDto;
66 +import com.infoloop.tianting.server.WebSocketServer;
67 +import com.infoloop.tianting.server.message.MessageTypeEnum;
68 +import com.infoloop.tianting.server.message.NoticeTypeEnum;
69 +import com.infoloop.tianting.server.message.SocketMessage;
70 +import com.infoloop.tianting.server.message.data.OrderCreateSuccessData;
71 +import com.infoloop.tianting.server.session.UserTypeEnum;
67 import com.infoloop.tianting.utils.DateUtil; 72 import com.infoloop.tianting.utils.DateUtil;
68 import com.infoloop.tianting.utils.JsonUtil; 73 import com.infoloop.tianting.utils.JsonUtil;
69 import lombok.RequiredArgsConstructor; 74 import lombok.RequiredArgsConstructor;
70 import lombok.extern.slf4j.Slf4j; 75 import lombok.extern.slf4j.Slf4j;
71 -import org.redisson.api.RAtomicLong; 76 +import org.apache.commons.lang3.StringUtils;
72 import org.redisson.api.RedissonClient; 77 import org.redisson.api.RedissonClient;
73 import org.springframework.beans.factory.annotation.Autowired; 78 import org.springframework.beans.factory.annotation.Autowired;
74 import org.springframework.stereotype.Service; 79 import org.springframework.stereotype.Service;
75 80
76 import javax.annotation.Nullable; 81 import javax.annotation.Nullable;
82 +import java.io.IOException;
83 +import java.time.Duration;
77 import java.time.LocalDate; 84 import java.time.LocalDate;
85 +import java.time.LocalDateTime;
78 import java.time.ZoneOffset; 86 import java.time.ZoneOffset;
79 import java.util.ArrayList; 87 import java.util.ArrayList;
80 import java.util.HashMap; 88 import java.util.HashMap;
...@@ -476,19 +484,22 @@ public class OrderServiceRpcClient { ...@@ -476,19 +484,22 @@ public class OrderServiceRpcClient {
476 } 484 }
477 485
478 public String generatePickupCode() { 486 public String generatePickupCode() {
479 - LocalDate today = LocalDate.now(); 487 + final var today = LocalDate.now();
480 - String currentDate = DateUtil.formatDate(today, DateUtil.YYYY_MM_DD); 488 + final var currentDate = DateUtil.formatDate(today, DateUtil.YYYY_MM_DD);
481 - String redisKey = REDIS_KEY_PREFIX + currentDate; 489 + final var redisKey = REDIS_KEY_PREFIX + currentDate;
482 - // 获取分布式原子Long对象,用于原子自增操作 490 + final var atomicLong = redissonClient.getAtomicLong(redisKey);
483 - RAtomicLong atomicLong = redissonClient.getAtomicLong(redisKey); 491 + if (atomicLong.get() == 0) {
484 - // 原子自增并获取当前值 492 + LocalDateTime endOfDay = today.atTime(23, 59, 59, 999999999);
485 - long counter = atomicLong.incrementAndGet(); 493 + long millisUntilEndOfDay = Duration.between(LocalDateTime.now(), endOfDay).toMillis();
494 + atomicLong.expire(Duration.ofMillis(millisUntilEndOfDay));
495 + }
496 + final var counter = atomicLong.incrementAndGet();
486 return String.format("%04d", (int) counter); 497 return String.format("%04d", (int) counter);
487 } 498 }
488 499
489 public CreateOrderResponse createOrder(CreateOrderDto createOrderDto) { 500 public CreateOrderResponse createOrder(CreateOrderDto createOrderDto) {
490 final var orderDetails = new ArrayList<ClientCustomerOrderDetailCreation>(); 501 final var orderDetails = new ArrayList<ClientCustomerOrderDetailCreation>();
491 - for (CreateOrderDetailDto orderDetailDto : createOrderDto.getOrderDetailDtos()) { 502 + for (final var orderDetailDto : createOrderDto.getOrderDetailDtos()) {
492 final var mealDetail = MealDetail.newBuilder() 503 final var mealDetail = MealDetail.newBuilder()
493 .setBasicMaterials(orderDetailDto.getMealDetailJson().getBasicMaterials()) 504 .setBasicMaterials(orderDetailDto.getMealDetailJson().getBasicMaterials())
494 .addAllTastes(orderDetailDto.getMealDetailJson().getTastes()) 505 .addAllTastes(orderDetailDto.getMealDetailJson().getTastes())
...@@ -544,8 +555,7 @@ public class OrderServiceRpcClient { ...@@ -544,8 +555,7 @@ public class OrderServiceRpcClient {
544 .setOpenId(LoginContextHolder.getOpenId()) 555 .setOpenId(LoginContextHolder.getOpenId())
545 .setShouldCreateRoomNo(!createOrderDto.getRoomNo().isEmpty()) 556 .setShouldCreateRoomNo(!createOrderDto.getRoomNo().isEmpty())
546 .setRoomNo(createOrderDto.getRoomNo()) 557 .setRoomNo(createOrderDto.getRoomNo())
547 - .setShouldCreateTableCode(createOrderDto.getOrderType().equals(OrderTypeEnum.DINE_IN.getValue()) || !createOrderDto.getTableCode().isEmpty()) 558 + .setTableCode(createOrderDto.getTableCode() == null ? "" : createOrderDto.getTableCode())
548 - .setTableCode(createOrderDto.getTableCode())
549 .setShouldCreateOnlinePay(true) 559 .setShouldCreateOnlinePay(true)
550 .setOnlinePay(createOrderDto.getOnlinePay()) 560 .setOnlinePay(createOrderDto.getOnlinePay())
551 .setShouldCreatePayStatus(createOrderDto.getOnlinePay()) 561 .setShouldCreatePayStatus(createOrderDto.getOnlinePay())
...@@ -554,7 +564,7 @@ public class OrderServiceRpcClient { ...@@ -554,7 +564,7 @@ public class OrderServiceRpcClient {
554 .setTotalNum(createOrderDto.getTotalNum()) 564 .setTotalNum(createOrderDto.getTotalNum())
555 .setPayPrice(createOrderDto.getPayPrice()) 565 .setPayPrice(createOrderDto.getPayPrice())
556 .setMealTime(createOrderDto.getMealTime().toLocalDate().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()) 566 .setMealTime(createOrderDto.getMealTime().toLocalDate().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli())
557 - .setShouldCreateTableCode(createOrderDto.getCloseTimeType().equals(CloseTimeType.IMMEDIATELY)) 567 + .setShouldCreateTableCode(createOrderDto.getCloseTimeType().equals(CloseTimeType.IMMEDIATELY) || StringUtils.isNotEmpty(createOrderDto.getTableCode()))
558 .setShouldCreatePickupCode(createOrderDto.getCloseTimeType().equals(CloseTimeType.IMMEDIATELY)) 568 .setShouldCreatePickupCode(createOrderDto.getCloseTimeType().equals(CloseTimeType.IMMEDIATELY))
559 .setPickupCode(generatePickupCode()) 569 .setPickupCode(generatePickupCode())
560 .setShouldCreateRemark(true) 570 .setShouldCreateRemark(true)
...@@ -593,14 +603,31 @@ public class OrderServiceRpcClient { ...@@ -593,14 +603,31 @@ public class OrderServiceRpcClient {
593 throw ClientEndExceptions.BusinessException.build(ErrorCodeEnum.SKUS_STOCK_NO_ENOUGH_TODAY, noEnoughSkus); 603 throw ClientEndExceptions.BusinessException.build(ErrorCodeEnum.SKUS_STOCK_NO_ENOUGH_TODAY, noEnoughSkus);
594 } 604 }
595 } 605 }
596 - final var orderResponse = orderServiceRpcBlockingStub 606 + final var orderResponse = orderServiceRpcBlockingStub.createClientCustomerOrder(CreateClientCustomerOrderRpcRequest.newBuilder()
597 - .createClientCustomerOrder(CreateClientCustomerOrderRpcRequest.newBuilder()
598 .setCreation(creation) 607 .setCreation(creation)
599 .setCreationSource(OrderSourceEnum.forNumber(createOrderDto.getCreationSource())) 608 .setCreationSource(OrderSourceEnum.forNumber(createOrderDto.getCreationSource()))
600 .setEnterpriseId(createOrderDto.getEnterpriseId()) 609 .setEnterpriseId(createOrderDto.getEnterpriseId())
601 .setCreatedBy(createOrderDto.getCreatedBy()) 610 .setCreatedBy(createOrderDto.getCreatedBy())
602 .build()); 611 .build());
603 if (orderResponse.getIsCreated()) { 612 if (orderResponse.getIsCreated()) {
613 + // 用餐时间为当天,并且非在线支付餐单,发送消息给客户端
614 + if (createOrderDto.getMealTime().toLocalDate().equals(LocalDate.now()) && menuById.getResponse().getOrderRuleJson().getModeOfPayment() == ModeOfPaymentEnum.ONLINE) {
615 + final var orderCreateSuccessData = OrderCreateSuccessData.builder()
616 + .orderId(orderResponse.getId())
617 + .build();
618 + final var data = SocketMessage.<OrderCreateSuccessData>builder()
619 + .type(MessageTypeEnum.ORDER)
620 + .noticeType(NoticeTypeEnum.ORDER_CREATED)
621 + .subject(NoticeTypeEnum.ORDER_CREATED.getDescription())
622 + .message("您下单成功啦,快去查看吧~")
623 + .data(orderCreateSuccessData)
624 + .build();
625 + try {
626 + WebSocketServer.sendMessageByUserTypes(List.of(UserTypeEnum.CLIENT, UserTypeEnum.KDS), data);
627 + } catch (IOException e) {
628 + log.error("Failed to send message to user", e);
629 + }
630 + }
604 //立即点餐需要扣除库存 631 //立即点餐需要扣除库存
605 if (menuById.getResponse().getOrderRuleJson().getCloseTimeType() == CloseTimeTypeEnum.IMMEDIATELY) { 632 if (menuById.getResponse().getOrderRuleJson().getCloseTimeType() == CloseTimeTypeEnum.IMMEDIATELY) {
606 final var isSuccess = this.addSkusSellQuantities(createOrderDto.getEnterpriseId(), createOrderDto.getStallId(), 633 final var isSuccess = this.addSkusSellQuantities(createOrderDto.getEnterpriseId(), createOrderDto.getStallId(),
......
...@@ -19,6 +19,13 @@ import com.infoloop.tianting.model.dto.PayDTO.PayResponseDto; ...@@ -19,6 +19,13 @@ import com.infoloop.tianting.model.dto.PayDTO.PayResponseDto;
19 import com.infoloop.tianting.model.dto.PayDTO.ThirdPartyPayInformRequestDto; 19 import com.infoloop.tianting.model.dto.PayDTO.ThirdPartyPayInformRequestDto;
20 import com.infoloop.tianting.model.dto.PaymentCallbackDTO; 20 import com.infoloop.tianting.model.dto.PaymentCallbackDTO;
21 import com.infoloop.tianting.model.dto.PaymentCallbackResult; 21 import com.infoloop.tianting.model.dto.PaymentCallbackResult;
22 +import com.infoloop.tianting.server.WebSocketServer;
23 +import com.infoloop.tianting.server.message.MessageTypeEnum;
24 +import com.infoloop.tianting.server.message.NoticeTypeEnum;
25 +import com.infoloop.tianting.server.message.SocketMessage;
26 +import com.infoloop.tianting.server.message.data.OrderPaymentSuccessData;
27 +import com.infoloop.tianting.server.session.UserSessionKey;
28 +import com.infoloop.tianting.server.session.UserTypeEnum;
22 import com.infoloop.tianting.utils.DateUtil; 29 import com.infoloop.tianting.utils.DateUtil;
23 import com.infoloop.tianting.utils.EncryptionUtil; 30 import com.infoloop.tianting.utils.EncryptionUtil;
24 import lombok.RequiredArgsConstructor; 31 import lombok.RequiredArgsConstructor;
...@@ -154,6 +161,25 @@ public class PayServiceClient { ...@@ -154,6 +161,25 @@ public class PayServiceClient {
154 .build(); 161 .build();
155 final var updateResponse = orderServiceRpcClient.updateClientCustomerOrderPaymentResult(orderById, build); 162 final var updateResponse = orderServiceRpcClient.updateClientCustomerOrderPaymentResult(orderById, build);
156 log.info("callback updateClientCustomerOrder result: {}", updateResponse.getIsUpdated()); 163 log.info("callback updateClientCustomerOrder result: {}", updateResponse.getIsUpdated());
164 + if (updateResponse.getIsUpdated()) {
165 + final var orderPaymentSuccessData = OrderPaymentSuccessData.builder()
166 + .orderId(orderById.getId())
167 + .orderCode(orderById.getOrderCode())
168 + .createTime(DateUtil.formatDate(orderById.getCreatedAt(), DateUtil.Y_M_D_H_M_S))
169 + .payTime(DateUtil.formatDate(orderById.getPayTime(), DateUtil.Y_M_D_H_M_S))
170 + .build();
171 + final var data = SocketMessage.<OrderPaymentSuccessData>builder()
172 + .type(MessageTypeEnum.ORDER)
173 + .noticeType(NoticeTypeEnum.ORDER_PAYMENT_SUCCESS)
174 + .subject(NoticeTypeEnum.ORDER_PAYMENT_SUCCESS.getDescription())
175 + .message("您的订单:" + orderById.getOrderCode() + " 支付成功啦,快去查看吧~")
176 + .data(orderPaymentSuccessData)
177 + .build();
178 + WebSocketServer.sendMessageToUser(UserSessionKey.builder()
179 + .userId(orderById.getOpenId())
180 + .userType(UserTypeEnum.MICRO)
181 + .build(), data);
182 + }
157 return updateResponse.getIsUpdated(); 183 return updateResponse.getIsUpdated();
158 } catch (JsonProcessingException e) { 184 } catch (JsonProcessingException e) {
159 log.error("Failed to parse XML response, raw XML: {}", paymentCallbackDTO.getPayResult(), e); 185 log.error("Failed to parse XML response, raw XML: {}", paymentCallbackDTO.getPayResult(), e);
......