Showing
18 changed files
with
445 additions
and
26 deletions
| ... | @@ -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 | +} |
| 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 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 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.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); | ... | ... |
-
Please register or login to post a comment