stomp + websocket 业务代码实现

This commit is contained in:
yyb 2025-04-15 14:39:18 +08:00
parent 4c19ee2627
commit c265d3a19a
59 changed files with 1729 additions and 951 deletions

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# 忽略 IDE 和编译生成的文件
.idea/
target/
*.iml
*.class
# 忽略日志文件
logs/
*.log
# 忽略配置文件
application-local.yml
bootstrap-local.yml
# 忽略临时文件
*.tmp
*.bak
*.swp
# 忽略依赖缓存
.m2/
node_modules/
# 忽略敏感信息
.env
credentials.json
# 忽略系统文件
.DS_Store
Thumbs.db

View File

@ -0,0 +1,64 @@
#Tomcat
server:
port: 9200
# Spring
spring:
#邮箱基本配置
mail:
# 配置在limit_time内用户可以发送limit次验证码
limit: 2 这个是我额外的配置,结合邮箱服务用的
limitTime: 10 这个是我额外的配置
#配置smtp服务主机地址
# sina smtp.sina.cn
# aliyun smtp.aliyun.com
# 163 smtp.163.com 端口号465或994
host: mail.privateemail.com
#发送者邮箱
username: do.not.reply@m2pool.com
#配置密码,注意不是真正的密码,而是刚刚申请到的授权码
# password:
# password: M2pool2024@!
# password: axvm-zfgx-cgcg-qhhu
password: M2202401!
#端口号
port: 587
#默认的邮件编码为UTF-8
default-encoding: UTF-8
#其他参数
properties:
mail:
#配置SSL 加密工厂
smtp:
ssl:
#本地测试先放开ssl
enable: false
required: false
#开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
debug: false
socketFactory:
class: javax.net.ssl.SSLSocketFactory
application:
# 应用名称
name: m2pool-auth
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8808
namespace: m2_prod
group: m2_prod_group
config:
# 配置中心地址
server-addr: 127.0.0.1:8808
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_prod
group: m2_prod_group

View File

@ -0,0 +1,89 @@
server:
port: 9500
# Spring
spring:
#邮箱基本配置
mail:
# 配置在limit_time内用户可以发送limit次验证码
limit: 2 这个是我额外的配置,结合邮箱服务用的
limitTime: 10 这个是我额外的配置
#配置smtp服务主机地址
# sina smtp.sina.cn
# aliyun smtp.aliyun.com
# 163 smtp.163.com 端口号465或994
host: mail.privateemail.com
#发送者邮箱
username: do.not.reply@m2pool.com
#配置密码,注意不是真正的密码,而是刚刚申请到的授权码
# password:
# password: M2202401!
# password: axvm-zfgx-cgcg-qhhu
password: M2202401!
#端口号
port: 587
# port: 465
#默认的邮件编码为UTF-8
default-encoding: UTF-8
#其他参数
properties:
mail:
#配置SSL 加密工厂
smtp:
ssl:
#本地测试先放开ssl
enable: false
required: false
#开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
debug: false
socketFactory:
class: javax.net.ssl.SSLSocketFactory
#
# host: smtp.qq.com
# #发送者邮箱
# username: 1328642438@qq.com
# #配置密码,注意不是真正的密码,而是刚刚申请到的授权码
# # password:
# # password: M2pool2024@!
# # password: axvm-zfgx-cgcg-qhhu
# password: eqrzqxeqzlshhedh
#
# #端口号
# port: 465
# #默认的邮件编码为UTF-8
# default-encoding: UTF-8
# #其他参数
# properties:
# mail:
# #配置SSL 加密工厂
# smtp:
# ssl:
# #本地测试先放开ssl
# enable: true
# required: true
# #开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
# debug: false
# socketFactory:
# class: javax.net.ssl.SSLSocketFactory
application:
# 应用名称
name: m2pool-auth
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8848
namespace: m2_test
group: m2_test_group
config:
# 配置中心地址
server-addr: 127.0.0.1:8848
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_test
group: m2_test_group

View File

@ -1,159 +1,3 @@
#Tomcat
server:
port: 9200
# Spring
spring:
#邮箱基本配置
mail:
# 配置在limit_time内用户可以发送limit次验证码
limit: 2 这个是我额外的配置,结合邮箱服务用的
limitTime: 10 这个是我额外的配置
#配置smtp服务主机地址
# sina smtp.sina.cn
# aliyun smtp.aliyun.com
# 163 smtp.163.com 端口号465或994
host: mail.privateemail.com
#发送者邮箱
username: do.not.reply@m2pool.com
#配置密码,注意不是真正的密码,而是刚刚申请到的授权码
# password:
# password: M2pool2024@!
# password: axvm-zfgx-cgcg-qhhu
password: M2202401!
#端口号
port: 587
#默认的邮件编码为UTF-8
default-encoding: UTF-8
#其他参数
properties:
mail:
#配置SSL 加密工厂
smtp:
ssl:
#本地测试先放开ssl
enable: false
required: false
#开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
debug: false
socketFactory:
class: javax.net.ssl.SSLSocketFactory
application:
# 应用名称
name: m2pool-auth
profiles:
# 环境配置
active: prod
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8808
namespace: m2_prod
group: m2_prod_group
config:
# 配置中心地址
server-addr: 127.0.0.1:8808
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_prod
group: m2_prod_group
#server:
# port: 9500
#
## Spring
#spring:
# #邮箱基本配置
# mail:
# # 配置在limit_time内用户可以发送limit次验证码
# limit: 2 这个是我额外的配置,结合邮箱服务用的
# limitTime: 10 这个是我额外的配置
# #配置smtp服务主机地址
# # sina smtp.sina.cn
# # aliyun smtp.aliyun.com
# # 163 smtp.163.com 端口号465或994
# host: mail.privateemail.com
# #发送者邮箱
# username: do.not.reply@m2pool.com
# #配置密码,注意不是真正的密码,而是刚刚申请到的授权码
# # password:
# # password: M2202401!
# # password: axvm-zfgx-cgcg-qhhu
# password: M2202401!
#
# #端口号
# port: 587
## port: 465
# #默认的邮件编码为UTF-8
# default-encoding: UTF-8
# #其他参数
# properties:
# mail:
# #配置SSL 加密工厂
# smtp:
# ssl:
# #本地测试先放开ssl
# enable: false
# required: false
# #开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
# debug: false
# socketFactory:
# class: javax.net.ssl.SSLSocketFactory
##
## host: smtp.qq.com
## #发送者邮箱
## username: 1328642438@qq.com
## #配置密码,注意不是真正的密码,而是刚刚申请到的授权码
## # password:
## # password: M2pool2024@!
## # password: axvm-zfgx-cgcg-qhhu
## password: eqrzqxeqzlshhedh
##
## #端口号
## port: 465
## #默认的邮件编码为UTF-8
## default-encoding: UTF-8
## #其他参数
## properties:
## mail:
## #配置SSL 加密工厂
## smtp:
## ssl:
## #本地测试先放开ssl
## enable: true
## required: true
## #开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
## debug: false
## socketFactory:
## class: javax.net.ssl.SSLSocketFactory
#
# application:
# # 应用名称
# name: m2pool-auth
# profiles:
# # 环境配置
# active: test
# cloud:
# nacos:
# discovery:
# # 服务注册地址
# server-addr: 127.0.0.1:8808
# namespace: m2_test
# group: m2_test_group
# config:
# # 配置中心地址
# server-addr: 127.0.0.1:8808
# # 配置文件格式
# file-extension: yml
# # 共享配置
# shared-configs:
# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
# namespace: m2_test
# group: m2_test_group
active: prod

View File

@ -1,5 +1,6 @@
package com.m2pool.gateway;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

View File

@ -0,0 +1,247 @@
package com.m2pool.gateway.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.socket.CloseStatus;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import org.springframework.web.reactive.socket.client.WebSocketClient;
import org.springframework.web.reactive.socket.server.WebSocketService;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.*;
/**
* 解决gateway网关 websocket关闭异常 问题
* @author yyb
* @Desc websocket客户端主动断开连接,
* @date 2025/4/14 11:21
*/
@Component
public class CustomWebsocketRoutingFilter implements GlobalFilter, Ordered {
public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
private static final Log log = LogFactory.getLog(CustomWebsocketRoutingFilter.class);
private final WebSocketClient webSocketClient;
private final WebSocketService webSocketService;
private final ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider;
private volatile List<HttpHeadersFilter> headersFilters;
public CustomWebsocketRoutingFilter(WebSocketClient webSocketClient, WebSocketService webSocketService, ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider) {
this.webSocketClient = webSocketClient;
this.webSocketService = webSocketService;
this.headersFiltersProvider = headersFiltersProvider;
}
static String convertHttpToWs(String scheme) {
scheme = scheme.toLowerCase();
return "http".equals(scheme) ? "ws" : ("https".equals(scheme) ? "wss" : scheme);
}
@Override
public int getOrder() {
return 2147483645;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
changeSchemeIfIsWebSocketUpgrade(exchange);
URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (!ServerWebExchangeUtils.isAlreadyRouted(exchange) && ("ws".equals(scheme) || "wss".equals(scheme))) {
ServerWebExchangeUtils.setAlreadyRouted(exchange);
HttpHeaders headers = exchange.getRequest().getHeaders();
HttpHeaders filtered = HttpHeadersFilter.filterRequest(this.getHeadersFilters(), exchange);
List<String> protocols = this.getProtocols(headers);
return this.webSocketService.handleRequest(exchange, new CustomWebsocketRoutingFilter.ProxyWebSocketHandler(requestUrl, this.webSocketClient, filtered, protocols));
} else {
return chain.filter(exchange);
}
}
List<String> getProtocols(HttpHeaders headers) {
List<String> protocols = headers.get("Sec-WebSocket-Protocol");
if (protocols != null) {
ArrayList<String> updatedProtocols = new ArrayList();
for(int i = 0; i < ((List)protocols).size(); ++i) {
String protocol = (String)((List)protocols).get(i);
updatedProtocols.addAll(Arrays.asList(StringUtils.tokenizeToStringArray(protocol, ",")));
}
protocols = updatedProtocols;
}
return (List)protocols;
}
List<HttpHeadersFilter> getHeadersFilters() {
if (this.headersFilters == null) {
this.headersFilters = (List)this.headersFiltersProvider.getIfAvailable(ArrayList::new);
this.headersFilters.add((headers, exchange) -> {
HttpHeaders filtered = new HttpHeaders();
filtered.addAll(headers);
filtered.remove("Host");
boolean preserveHost = (Boolean)exchange.getAttributeOrDefault(ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, false);
if (preserveHost) {
String host = exchange.getRequest().getHeaders().getFirst("Host");
filtered.add("Host", host);
}
return filtered;
});
this.headersFilters.add((headers, exchange) -> {
HttpHeaders filtered = new HttpHeaders();
Iterator var3 = headers.entrySet().iterator();
while(var3.hasNext()) {
Map.Entry<String, List<String>> entry = (Map.Entry)var3.next();
if (!((String)entry.getKey()).toLowerCase().startsWith("sec-websocket")) {
filtered.addAll((String)entry.getKey(), (List)entry.getValue());
}
}
return filtered;
});
}
return this.headersFilters;
}
static void changeSchemeIfIsWebSocketUpgrade(ServerWebExchange exchange) {
URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme().toLowerCase();
String upgrade = exchange.getRequest().getHeaders().getUpgrade();
if ("WebSocket".equalsIgnoreCase(upgrade) && ("http".equals(scheme) || "https".equals(scheme))) {
String wsScheme = convertHttpToWs(scheme);
boolean encoded = ServerWebExchangeUtils.containsEncodedParts(requestUrl);
URI wsRequestUrl = UriComponentsBuilder.fromUri(requestUrl).scheme(wsScheme).build(encoded).toUri();
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, wsRequestUrl);
if (log.isTraceEnabled()) {
log.trace("changeSchemeTo:[" + wsRequestUrl + "]");
}
}
}
private static class ProxyWebSocketHandler implements WebSocketHandler {
private final WebSocketClient client;
private final URI url;
private final HttpHeaders headers;
private final List<String> subProtocols;
ProxyWebSocketHandler(URI url, WebSocketClient client, HttpHeaders headers, List<String> protocols) {
this.client = client;
this.url = url;
this.headers = headers;
if (protocols != null) {
this.subProtocols = protocols;
} else {
this.subProtocols = Collections.emptyList();
}
}
@Override
public List<String> getSubProtocols() {
return this.subProtocols;
}
@Override
public Mono<Void> handle(WebSocketSession session) {
return this.client.execute(this.url, this.headers, new WebSocketHandler() {
private CloseStatus adaptCloseStatus(CloseStatus closeStatus) {
int code = closeStatus.getCode();
if (code > 2999 && code < 5000) {
return closeStatus;
}
switch (code) {
case 1000:
return closeStatus;
case 1001:
return closeStatus;
case 1002:
return closeStatus;
case 1003:
return closeStatus;
case 1004:
// Should not be used in a close frame
// RESERVED;
return CloseStatus.PROTOCOL_ERROR;
case 1005:
// Should not be used in a close frame
// return CloseStatus.NO_STATUS_CODE;
return CloseStatus.PROTOCOL_ERROR;
case 1006:
// Should not be used in a close frame
// return CloseStatus.NO_CLOSE_FRAME;
return CloseStatus.PROTOCOL_ERROR;
case 1007:
return closeStatus;
case 1008:
return closeStatus;
case 1009:
return closeStatus;
case 1010:
return closeStatus;
case 1011:
return closeStatus;
case 1012:
// Not in RFC6455
// return CloseStatus.SERVICE_RESTARTED;
return CloseStatus.PROTOCOL_ERROR;
case 1013:
// Not in RFC6455
// return CloseStatus.SERVICE_OVERLOAD;
return CloseStatus.PROTOCOL_ERROR;
case 1015:
// Should not be used in a close frame
// return CloseStatus.TLS_HANDSHAKE_FAILURE;
return CloseStatus.PROTOCOL_ERROR;
default:
return CloseStatus.PROTOCOL_ERROR;
}
}
@Override
public Mono<Void> handle(WebSocketSession proxySession) {
Mono<Void> serverClose = proxySession.closeStatus().filter(__ -> session.isOpen())
.map(this::adaptCloseStatus)
.flatMap(session::close);
Mono<Void> proxyClose = session.closeStatus().filter(__ -> proxySession.isOpen())
.map(this::adaptCloseStatus)
.flatMap(proxySession::close);
// Use retain() for Reactor Netty
Mono<Void> proxySessionSend = proxySession
.send(session.receive().doOnNext(WebSocketMessage::retain));
Mono<Void> serverSessionSend = session
.send(proxySession.receive().doOnNext(WebSocketMessage::retain));
// Ensure closeStatus from one propagates to the other
Mono.when(serverClose, proxyClose).subscribe();
// Complete when both sessions are done
return Mono.zip(proxySessionSend, serverSessionSend).then();
}
@Override
public List<String> getSubProtocols() {
return CustomWebsocketRoutingFilter.ProxyWebSocketHandler.this.subProtocols;
}
});
}
}
}

View File

@ -0,0 +1,48 @@
# Tomcat
server:
port: 8201
# Spring
spring:
application:
# 应用名称
name: m2pool-gateway
main:
allow-circular-references: true
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8808
namespace: m2_prod
group: m2_prod_group
config:
# 配置中心地址
server-addr: 127.0.0.1:8808
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_prod
group: m2_prod_group
sentinel:
# 取消控制台懒加载
eager: true
transport:
# 控制台地址
dashboard: 127.0.0.1:8718
# nacos配置持久化
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8808
dataId: sentinel-m2pool-gateway
groupId: m2_prod_group
data-type: json
rule-type: flow
servlet:
multipart:
max-file-size: 2MB
max-request-size: 8MB

View File

@ -0,0 +1,46 @@
server:
port: 8101
# Spring
spring:
application:
# 应用名称
name: m2pool-gateway
main:
allow-circular-references: true
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: m2_test
group: m2_test_group
config:
# 配置中心地址
server-addr: 127.0.0.1:8848
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_test
group: m2_test_group
sentinel:
# 取消控制台懒加载
eager: true
transport:
# 控制台地址
dashboard: 127.0.0.1:8718
# nacos配置持久化
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8848
dataId: sentinel-m2pool-gateway
groupId: m2_test_group
data-type: json
rule-type: flow
servlet:
multipart:
max-file-size: 2MB
max-request-size: 8MB

View File

@ -1,100 +1,3 @@
# Tomcat
server:
port: 8201
# Spring
spring:
application:
# 应用名称
name: m2pool-gateway
profiles:
# 环境配置
active: prod
main:
allow-circular-references: true
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8808
namespace: m2_prod
group: m2_prod_group
config:
# 配置中心地址
server-addr: 127.0.0.1:8808
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_prod
group: m2_prod_group
sentinel:
# 取消控制台懒加载
eager: true
transport:
# 控制台地址
dashboard: 127.0.0.1:8718
# nacos配置持久化
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8808
dataId: sentinel-m2pool-gateway
groupId: m2_prod_group
data-type: json
rule-type: flow
servlet:
multipart:
max-file-size: 2MB
max-request-size: 8MB
#server:
# port: 8101
## Spring
#spring:
# application:
# # 应用名称
# name: m2pool-gateway
# profiles:
# # 环境配置
# active: test
# main:
# allow-circular-references: true
# allow-bean-definition-overriding: true
#
# cloud:
# nacos:
# discovery:
# server-addr: 127.0.0.1:8808
# namespace: m2_test
# group: m2_test_group
# config:
# # 配置中心地址
# server-addr: 127.0.0.1:8808
# # 配置文件格式
# file-extension: yml
# # 共享配置
# shared-configs:
# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
# namespace: m2_test
# group: m2_test_group
# sentinel:
# # 取消控制台懒加载
# eager: true
# transport:
# # 控制台地址
# dashboard: 127.0.0.1:8718
# # nacos配置持久化
# datasource:
# ds1:
# nacos:
# server-addr: 127.0.0.1:8808
# dataId: sentinel-m2pool-gateway
# groupId: m2_test_group
# data-type: json
# rule-type: flow
# servlet:
# multipart:
# max-file-size: 2MB
# max-request-size: 8MB
active: prod

View File

@ -135,6 +135,12 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!--google两步认证相关-->
<dependency>
<groupId>de.taimos</groupId>

View File

@ -28,7 +28,7 @@ public class M2ChatApplication {
public static void main(String[] args) {
SpringApplication.run(M2ChatApplication.class,args);
System.out.println("m2pool矿池 开放api微服务启动成功");
System.out.println("m2pool矿池 聊天室微服务启动成功");
}
}

View File

@ -1,5 +1,6 @@
package com.m2pool.chat.config;
import com.m2pool.chat.constant.Destination;
import com.m2pool.chat.coverter.CommonMessageConvert;
import com.m2pool.chat.interceptor.WebsocketChannelInterceptor;
import com.m2pool.chat.interceptor.WebsocketHandshakeInterceptor;
@ -30,12 +31,17 @@ public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat")
//sockjs 形式端点
registry.addEndpoint("/sockjs")
.addInterceptors(new WebsocketHandshakeInterceptor())
//.setHandshakeHandler(webSocketHandshakeHandler)
//允许跨域访问
.setAllowedOrigins("*")
.withSockJS();
// websocket 形式端点
registry.addEndpoint("/ws")
.addInterceptors(new WebsocketHandshakeInterceptor())
.setAllowedOrigins("*");
}
/**
@ -46,13 +52,13 @@ public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue")
config.enableSimpleBroker(Destination.TOPIC, Destination.QUEUE)
.setHeartbeatValue(new long[] {10000, 10000})
.setTaskScheduler(new DefaultManagedTaskScheduler());
config.setApplicationDestinationPrefixes("/message");
config.setApplicationDestinationPrefixes(Destination.SEND_PREFIX);
//服务端通知客户端的前缀可以不设置默认为user
config.setUserDestinationPrefix("/user");
config.setUserDestinationPrefix(Destination.USER_PREFIX);
}

View File

@ -1,21 +0,0 @@
package com.m2pool.chat.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.websocket.server.ServerEndpointConfig;
/**
* @Description TODO
* @Date 2025/2/25 14:43
* @Author 杜懿
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}

View File

@ -0,0 +1,34 @@
package com.m2pool.chat.constant;
/**
* @ClassName Destination
* @Description 消息发送目的地
* @Author yyb
* @Date 2025/4/14 14:54
*/
public class Destination {
/**
* stomp 默认单对单 发送目的地
*/
public static final String QUEUE = "/queue";
/**
* stomp 默认群发 目的地
*/
public static final String TOPIC = "/topic";
/**
* 前端订阅消息所需前缀 stomp 默认user前缀
*/
public static final String USER_PREFIX = "/user";
/**
* stomp 默认发送消息前缀
*/
public static final String SEND_PREFIX = "/message";
/**
* 游客标识
*/
public static final String VISITOR_PREFIX = "visitor";
}

View File

@ -0,0 +1,19 @@
package com.m2pool.chat.constant;
/**
* @ClassName messageType
* @Description 消息类型常量类
* @Author yyb
* @Date 2025/4/14 14:51
*/
public class MessageType {
/**
* 文本消息
*/
public static final Integer TYPE_TEXT = 0;
/**
* 图片信息
*/
public static final Integer TYPE_IMAGE = 1;
}

View File

@ -0,0 +1,26 @@
package com.m2pool.chat.constant;
/**
* @ClassName UserType
* @Description TODO
* @Author yyb
* @Date 2025/4/15 11:25
*/
public class UserType {
/**
* 游客
*/
public static final Integer TOURIST = 0;
/**
* 登录用户
*/
public static final Integer LOGIN_USER = 1;
/**
* 客服
*/
public static final Integer CUSTOMER = 2;
}

View File

@ -1,10 +0,0 @@
package com.m2pool.chat.constant;
/**
* @ClassName ExceptionCode
* @Description websocket 异常码
* @Author yyb
* @Date 2025/4/10 16:37
*/
public class WebsocketExceptionCode {
}

View File

@ -1,82 +0,0 @@
package com.m2pool.chat.controller;
import com.m2pool.chat.dto.WebsocketMessageDto;
import com.m2pool.chat.service.ChatService;
import com.m2pool.chat.vo.ChatHistoryVo;
import com.m2pool.chat.vo.UserMessageVo;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.core.web.controller.BaseController;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
/**
* @Description TODO
* @Date 2025/2/24 15:05
* @Author 杜懿
*/
@Controller
public class ChatController extends BaseController {
@Resource
private ChatService chatService;
//@Resource
// //private WebSocketServer webSocketServer;
@GetMapping("/history")
@ResponseBody
public AjaxResult getChatHistory(@RequestBody ChatHistoryVo vo) {
if(StringUtils.isNull(vo)){
return AjaxResult.error("请求参数为空");
}
String identifier = StringUtils.isNotBlank(vo.getEmail()) ? vo.getEmail() : vo.getBrowserId();
return chatService.getHistory(identifier);
}
//spring提供的推送方式
@Resource
private SimpMessagingTemplate messagingTemplate;
/**
* 发送消息到对应的用户
* @param userId 用户id消息接受者
* @param userMessageVo 消息体
* @return 返回值通过CommonMessageConvert消息转换器转换
*/
@MessageMapping("/message/{userId}")
@SendToUser("/queue/{userId}")
public WebsocketMessageDto sendMessageToUser(@DestinationVariable String userId, UserMessageVo userMessageVo) {
WebsocketMessageDto websocketMessageDto = new WebsocketMessageDto();
websocketMessageDto.setType("message");
websocketMessageDto.setMessage(userMessageVo.getMessage());
return websocketMessageDto;
}
//TODO 前端打开聊天框获取用户信息建立一对一链接
//TODO 用户登录后保存历史信息到数据库表,分表存储(7天)
//TODO 用户注销需删除历史信息
//
}

View File

@ -0,0 +1,74 @@
package com.m2pool.chat.controller;
import com.m2pool.chat.service.ChatMessageService;
import com.m2pool.common.core.web.Result.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/chat/message")
@Api(tags = "聊天消息相关接口")
public class ChatMessageController {
@Autowired
private ChatMessageService chatMessageService;
/**
* 查询7天前的历史记录
* @return
*/
@GetMapping("/find/history/message")
@ApiOperation(value = "查询7天前的历史记录", notes = "根据ID查询7天前的历史记录")
public AjaxResult findHistoryMessage(
@ApiParam(value = "游标最后一条消息id", required = true, example = "1")
@RequestParam(defaultValue = "1") Long id,
@ApiParam(value = "分页页码默认为10条/页", example = "10")
@RequestParam(required = false,defaultValue = "10")Integer pageNum,
@ApiParam(value = "聊天室id", required = true, example = "1")
@RequestParam Long roomId
) {
return chatMessageService.findHistoryMessage(id,pageNum,roomId);
}
@GetMapping("/find/recently/message")
@ApiOperation(value = "查询七天内的记录", notes = "根据ID查询七天内的记录")
public AjaxResult findRecentlyMessage(
@ApiParam(value = "游标最后一条消息id", required = true, example = "1")
@RequestParam(defaultValue = "1") Long id,
@ApiParam(value = "分页页码默认为10条/页", example = "10")
@RequestParam(required = false,defaultValue = "10")Integer pageNum,
@ApiParam(value = "聊天室id", required = true, example = "1")
@RequestParam Long roomId
) {
return chatMessageService.findRecentlyMessage(id,pageNum,roomId);
}
@GetMapping("/delete/message")
@ApiOperation(value = "消息撤回或删除")
public AjaxResult deleteMessage(
@ApiParam(value = "消息id", required = true, example = "1")
@RequestParam(defaultValue = "1") Long id,
@ApiParam(value = "消息类型 0 文本 1 图片", example = "0")
@RequestParam(required = false,defaultValue = "0")Integer type
) {
return chatMessageService.deleteMessage(id,type);
}
@GetMapping("/read/message")
@ApiOperation(value = "聊天室消息改已读")
public AjaxResult readMessage(
@ApiParam(value = "聊天室id", required = true, example = "1")
@RequestParam Long roomId) {
return null;
}
}

View File

@ -0,0 +1,41 @@
package com.m2pool.chat.controller;
import com.m2pool.chat.service.ChatRoomService;
import com.m2pool.chat.vo.CharRoomVo;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.core.web.page.TableDataInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Api(tags = "聊天室相关接口")
@RestController
@RequestMapping("/chat/rooms")
public class ChatRoomController {
@Autowired
private ChatRoomService chatRoomService;
@GetMapping("/find/room/list")
@ApiOperation(value = "查询客服人员的聊天室列表")
public TableDataInfo findRoomList() {
return chatRoomService.findRoomList();
}
@GetMapping("/find/room/by/userid")
@ApiOperation(value = "根据当前用户邮箱查询聊天室")
public AjaxResult findRoomByUserid(@ApiParam(value = "当前用户聊天对象的userId", example = "1")
@RequestParam(required = false) String email) {
return chatRoomService.findRoomByUserid(email);
}
@GetMapping("/update/room")
@ApiOperation(value = "修改room相关信息")
public AjaxResult updateRoom(@RequestBody CharRoomVo charRoomVo) {
return chatRoomService.updateRoom(charRoomVo);
}
}

View File

@ -0,0 +1,38 @@
package com.m2pool.chat.controller;
import com.m2pool.chat.service.StompService;
import com.m2pool.chat.vo.UserMessageVo;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.core.web.controller.BaseController;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
/**
* @Description TODO
* @Date 2025/2/24 15:05
* @Author 杜懿
*/
@Controller
public class StompController extends BaseController {
@Resource
private StompService stompService;
/**
* 发送消息到对应的用户
* @param userMessageVo 消息体
* @return 返回值通过CommonMessageConvert消息转换器转换
*/
@MessageMapping("/send/message")
@SendToUser("/queue")
public AjaxResult sendMessageToUser(@Payload UserMessageVo userMessageVo) {
return stompService.sendMessageToUser(userMessageVo);
}
}

View File

@ -0,0 +1,33 @@
package com.m2pool.chat.dto;
import cn.hutool.core.date.DateTime;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @ClassName ChatMessageDto
* @Description 聊天消息返回对象
* @Author yyb
* @Date 2025/4/14 14:28
*/
@ApiModel(description = "聊天消息返回对象")
@Data
public class ChatMessageDto {
@ApiModelProperty(value = "消息ID", example = "1")
private Long id;
@ApiModelProperty(value = "消息类型", example = "0 文本 1 图片")
private Integer type;
@ApiModelProperty(value = "发送者邮箱", example = "54544@qq.com")
private String sendEmail;
@ApiModelProperty(value = "消息内容", example = "消息内容")
private String content;
@ApiModelProperty(value = "发送时间", example = "")
private DateTime createTime;
}

View File

@ -0,0 +1,43 @@
package com.m2pool.chat.dto;
import cn.hutool.core.date.DateTime;
import lombok.Data;
/**
* @ClassName ChatRoomDto
* @Description 客服 聊天室列表返回对象
* @Author yyb
* @Date 2025/4/14 14:28
*/
@Data
public class ChatRoomDto {
/**
* 聊天室id
*/
private Long id;
/**
* 聊天对象id
*/
private String userEmail;
/**
* 聊天室重要程度
*/
private Integer flag;
/**
* 未读消息条数
*/
private Integer unReadNumber;
/**
* 用户回复时间
*/
private DateTime lastUserSendTime;
/**
* 客服回复时间
*/
private DateTime lastCustomerSendTime;
}

View File

@ -10,6 +10,23 @@ import lombok.Data;
*/
@Data
public class WebsocketMessageDto {
private String type;
private String message;
/**
* 消息id
*/
private Long id;
/**
* 消息类型
*/
private Integer type;
/**
* 消息内容
*/
private String content;
/**
* 是否是登录用户
*/
private boolean isLoginUser;
}

View File

@ -1,22 +1,18 @@
package com.m2pool.chat.entity;
import cn.hutool.core.date.DateTime;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
/**
* @Description TODO
* @Date 2025/3/7 15:53
* @Author 杜懿
*/
@Builder
@Data
public class ChatMessage {
private Long id;
private String senderType; // USER 0/CUSTOMER_SERVICE 1
private String senderId; // 邮箱或客服ID
private String receiverId;
private Integer type;
private Integer read;
private String sendEmail;
private String content;
private String userIdentifier; // 邮箱或浏览器指纹
private String browserFingerprint;
private Date createdTime;
private Long roomId;
private DateTime createTime;
private DateTime updateTime;
}

View File

@ -0,0 +1,15 @@
package com.m2pool.chat.entity;
import cn.hutool.core.date.DateTime;
import lombok.Data;
@Data
public class ChatMessageHistory {
private Long id;
private Integer type;
private String sendEmail;
private String content;
private Long roomId;
private DateTime createTime;
private DateTime updateTime;
}

View File

@ -1,60 +0,0 @@
package com.m2pool.chat.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import java.util.Date;
/**
* @Description TODO
* @Date 2025/2/28 18:11
* @Author 杜懿
*/
@Data
public class ChatMsg {
@Id
private String id;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy_MM_dd_HH_mm_ss")
private Date createTime=new Date();
/**
* 通信keysendId_receiveId
*/
private String msgKey;
/**
* 通信消息
*/
private String chatMsg;
/**
* 发送id
*/
private String sendId;
/**
* 接收id
*/
private String receiveId;
/**
* 查看状态 0未看 1已看
*/
private String readState;
/**
*1为我 0 为他
*/
@Transient
private Integer flag;
@Transient
private String name;
}

View File

@ -0,0 +1,19 @@
package com.m2pool.chat.entity;
import cn.hutool.core.date.DateTime;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class ChatRoom {
private Long id;
private String userOneEmail;
private String userTwoEmail;
private DateTime lastUserSendTime;
private DateTime lastCustomerSendTime;
private Boolean flag;
private DateTime createTime;
private DateTime updateTime;
private Boolean del;
}

View File

@ -0,0 +1,19 @@
package com.m2pool.chat.entity;
import java.security.Principal;
/**
* stomp用户身份信息
*/
public class StompPrincipal implements Principal {
String name;
public StompPrincipal(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
}

View File

@ -4,23 +4,16 @@ import com.m2pool.common.core.exception.base.BaseException;
public class WebSocketException extends BaseException {
private static String websocket_module = "chat-websocket";
public WebSocketException(String module, String code, Object[] args, String defaultMessage) {
super(module, code, args, defaultMessage);
module = websocket_module;
}
public WebSocketException(String module, String code, Object[] args) {
super(module, code, args);
module = websocket_module;
}
public WebSocketException(String module, String defaultMessage) {
super(module, defaultMessage);
module = websocket_module;
}
public WebSocketException(String code, Object[] args) {

View File

@ -1,18 +1,26 @@
package com.m2pool.chat.interceptor;
import com.m2pool.chat.entity.StompPrincipal;
import org.springframework.lang.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import java.util.ArrayList;
import java.util.Map;
import static com.m2pool.chat.constant.Destination.VISITOR_PREFIX;
/**
* @ClassName WebsocketChannelInterceptor
* @Description websocket channel 通道拦截器
* @Description websocket channel 通道拦截器 适用于前端@stomp/stompjs 实现.
* @Author yyb
* @Date 2025/4/10 15:44
*/
@ -27,26 +35,41 @@ public class WebsocketChannelInterceptor implements ChannelInterceptor {
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
//获取链接建立时的请求头信息
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
//TODO 前端通过@stomp/stompjs基于stomp-client+webscoket等插件实现可以在该方法获取自定义请求头做一些校验
if (accessor.getCommand() == StompCommand.CONNECT) {
String visitor = (String) accessor.getSessionAttributes().get(VISITOR_PREFIX);
LOGGER.info("------------收到websocket的连接消息");
}
//获取channel 中的请求头信息
StompHeaderAccessor mha = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
System.out.println("raw:"+raw);
if (raw instanceof Map) {
Object userInfo = ((Map) raw).get("userId");
// 游客或登录用户
if (userInfo == null){
mha.setUser(new StompPrincipal(visitor));
}else if(userInfo instanceof ArrayList) {
mha.setUser(new StompPrincipal((String) ((ArrayList) userInfo).get(0)));
}
}
}
if (accessor.getCommand() == StompCommand.SEND) {
LOGGER.info("------------收到websocket的数据发送消息");
LOGGER.info("------------websocket send message");
}
if (accessor.getCommand() == StompCommand.SUBSCRIBE) {
LOGGER.info("------------收到websocket的订阅消息");
LOGGER.info("------------websocket subscribe message");
}
if (accessor.getCommand() == StompCommand.UNSUBSCRIBE) {
LOGGER.info("------------收到websocket的取消订阅消息");
LOGGER.info("------------websocket unsubscribe");
}
if (accessor.getCommand() == StompCommand.DISCONNECT){
LOGGER.info("------------websocket disconnect");
}
return message;
}

View File

@ -9,10 +9,12 @@ import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
import static com.m2pool.chat.constant.Destination.VISITOR_PREFIX;
/**
* @ClassName WebsocketHandshakeInterceptor
* @Description websocket 握手处理器类
* @Description websocket 握手处理器类 适用于前端原生websocket实现.建立websocket链接时调用
* @Author yyb
* @Date 2025/4/10 15:39
*/
@ -20,15 +22,32 @@ public class WebsocketHandshakeInterceptor implements HandshakeInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(WebsocketHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
LOGGER.info("------------------WebsocketHandshakeInterceptor:beforeHandshake");
//TODO 前端如果是webscoket原生实现 获取一些自定义请求头,做一些校验
// 为游客生成一个唯一标识
// String s = generateVisitorId(request);
// attributes.put(VISITOR_PREFIX, s);
// response.getHeaders().add(VISITOR_PREFIX, s);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
LOGGER.info("-----------------WebsocketHandshakeInterceptor:afterHandshake");
LOGGER.info("------------------WebsocketHandshakeInterceptor:afterHandshake");
}
/**
* 生成一个唯一标识
* @param request
* @return
*/
private String generateVisitorId(ServerHttpRequest request) {
String first = request.getHeaders().getFirst("sec-websocket-key");
String prefix = VISITOR_PREFIX;
return prefix + first;
}
}

View File

@ -1,20 +0,0 @@
package com.m2pool.chat.mapper;
import com.m2pool.chat.entity.ChatMessage;
import com.m2pool.common.datasource.annotation.Master;
import org.apache.ibatis.annotations.Param;
import javax.validation.constraints.Pattern;
import java.util.List;
/**
* @Description 角色数据层
* @Date 2024/6/12 17:37
* @Author dy
*/
@Master
public interface ChatMapper {
public boolean insert(@Param("vo") ChatMessage vo);
}

View File

@ -0,0 +1,22 @@
package com.m2pool.chat.mapper;
import com.m2pool.chat.dto.ChatMessageDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ChatMessageHistoryMapper {
/**
* 根据游标查询十条数据
* @param id
* @param pageNum
* @param roomId
* @return
*/
List<ChatMessageDto> findHistoryMessage(@Param("id") Long id,@Param("pageNum") Integer pageNum,@Param("roomId") Long roomId);
}

View File

@ -0,0 +1,34 @@
package com.m2pool.chat.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.m2pool.chat.dto.ChatMessageDto;
import com.m2pool.chat.entity.ChatMessage;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
@Mapper
public interface ChatMessageMapper extends BaseMapper<ChatMessage> {
/**
* 根据游标查询十条数据
* @param id
* @param pageNum
* @param roomId
* @return
*/
List<ChatMessageDto> findRecentlyMessage(@Param("id") Long id,@Param("pageNum") Integer pageNum,@Param("roomId") Long roomId);
/**
* 查询未读消息条数
* @param userEmails
* @return Map<String, Integer>键为用户邮箱值为未读消息条数
*/
@MapKey("userEmail")
Map<String, Integer> findUnReadNums(@Param("userEmails") List<String> userEmails);
}

View File

@ -0,0 +1,35 @@
package com.m2pool.chat.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.m2pool.chat.dto.ChatRoomDto;
import com.m2pool.chat.entity.ChatRoom;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ChatRoomMapper extends BaseMapper<ChatRoom> {
/**
* 查询客服的聊天室列表
* @param userEmail 客服邮箱
* @return
*/
List<ChatRoomDto> findRoomList(@Param("userEmail") String userEmail);
/**
* 查找当前用户与对应用户是否已存在创建的聊天室
* @param userEmail 用户邮箱
* @param email 客服邮箱
* @return
*/
String findRoomByUserEmail(@Param("userEmail") String userEmail,@Param("email") String email);
}

View File

@ -0,0 +1,42 @@
package com.m2pool.chat.service;
import com.m2pool.common.core.web.Result.AjaxResult;
public interface ChatMessageService{
/**
* 游标分页 查询7天前的历史记录
* @param id 游标
* @param pageNum 每页条数
* @param roomId 聊天室id
* @return
*/
AjaxResult findHistoryMessage(Long id, Integer pageNum,Long roomId);
/**
* 游标分页 查询7天内数据
* @param id 游标
* @param pageNum 每页条数
* @param roomId 聊天室id
* @return
*/
AjaxResult findRecentlyMessage(Long id,Integer pageNum,Long roomId);
/**
* 根据消息id 删除
* @param id 消息id
* @param type 消息类型 0 文本 1 图片
* @return
*/
AjaxResult deleteMessage(Long id,Integer type);
/**
* 根据聊天室修改用户未读信息
* @param roomId
* @return
*/
AjaxResult readMessage(Long roomId);
}

View File

@ -0,0 +1,30 @@
package com.m2pool.chat.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.m2pool.chat.entity.ChatRoom;
import com.m2pool.chat.vo.CharRoomVo;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.core.web.page.TableDataInfo;
public interface ChatRoomService extends IService<ChatRoom> {
/**
* 查询客服的聊天室列表
* @return
*/
TableDataInfo findRoomList();
/**
* 根据当前用户邮箱查询聊天室
* @return
*/
AjaxResult findRoomByUserid(String email);
/**
* 修改聊天室信息
* @param charRoomVo
* @return
*/
AjaxResult updateRoom( CharRoomVo charRoomVo);
}

View File

@ -1,30 +0,0 @@
package com.m2pool.chat.service;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.system.api.entity.SysUser;
import org.springframework.cache.CacheManager;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description TODO
* @Date 2025/2/28 18:06
* @Author 杜懿
*/
public interface ChatService {
//public AjaxResult userList(UserItem userItem, SysUser data);
public AjaxResult getHistory(String identifier);
}

View File

@ -0,0 +1,15 @@
package com.m2pool.chat.service;
import com.m2pool.chat.vo.UserMessageVo;
import com.m2pool.common.core.web.Result.AjaxResult;
public interface StompService {
/**
* 发送消息给用户
* @param userMessageVo
* @return
*/
AjaxResult sendMessageToUser(UserMessageVo userMessageVo);
}

View File

@ -1,169 +0,0 @@
package com.m2pool.chat.service;
import com.alibaba.fastjson.JSONObject;
import com.m2pool.chat.entity.ChatMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Description TODO
* @Date 2025/2/28 18:02
* @Author 杜懿
*/
@ServerEndpoint(value = "/ws/asset")
@Component
public class WebSocketServer {
private static ChatService chatService;
@Autowired
public void setChatService(ChatService chatService) {
WebSocketServer.chatService = chatService;
}
@PostConstruct
public void init() {
System.out.println("websocket 加载");
}
private static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
private static final AtomicInteger OnlineCount = new AtomicInteger(0);
// concurrent包的线程安全Set用来存放每个客户端对应的Session对象
private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
SessionSet.add(session);
int cnt = OnlineCount.incrementAndGet();
log.info("有连接加入,当前连接数为:{}", cnt);
SendMessage(session, "连接成功");
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
SessionSet.remove(session);
int cnt = OnlineCount.decrementAndGet();
log.info("有连接关闭,当前连接数为:{}", cnt);
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("来自客户端的消息:{}",message);
try{
JSONObject jsonObject = JSONObject.parseObject(message);
String linkType = jsonObject.getString("linkType");
String sendId = jsonObject.getString("sendId");
if (linkType.equals("bind")){
//CacheManager.set("bind_"+sendId,session);
SendMessage(session, "连接成功");
}else if (linkType.equals("msg")){
//聊天
ChatMsg msg = new ChatMsg();
//发消息
String chatMsg = jsonObject.getString("chatMsg");
String receiveId = jsonObject.getString("receiveId");
msg.setChatMsg(chatMsg);
msg.setSendId(sendId);
msg.setMsgKey(sendId+"_"+receiveId);
msg.setReceiveId(receiveId);
msg.setReadState("0");
//chatService.sendOne(msg);
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 出现错误
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误:{}Session ID {}",error.getMessage(),session.getId());
error.printStackTrace();
}
/**
* 发送消息实践表明每次浏览器刷新session会发生变化
* @param session
* @param message
*/
public static void SendMessage(Session session, String message) {
try {
//session.getBasicRemote().sendText(String.format("%s (From ServerSession ID=%s)",message,session.getId()));
log.info("sessionID="+session.getId());
session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("发送消息出错:{}", e.getMessage());
e.printStackTrace();
}
}
/**
* 指定Session发送消息
* @param sessionId
* @param message
* @throws IOException
*/
public static void SendMessageById(String message,String sessionId) throws IOException {
Session session = null;
for (Session s : SessionSet) {
if(s.getId().equals(sessionId)){
session = s;
break;
}
}
if(session!=null){
SendMessage(session, message);
}
else{
log.warn("没有找到你指定ID的会话{}",sessionId);
}
}
/**
* @description: 指定发送
* @author: lvyq
* @date: 2021/9/24 11:30
* @version 1.0
*/
public static void SendMessageByRecId(String message,String receiveId) throws IOException {
//Session session= CacheManager.get("bind_"+receiveId);
Session session= null;
String sessionId = "";
if (session!=null){
sessionId=session.getId();
}
for (Session s : SessionSet) {
if(s.getId().equals(sessionId)){
session = s;
break;
}
}
if(session!=null){
SendMessage(session, message);
}
else{
System.out.println("没有找到你指定ID的会话"+sessionId);
}
}
}

View File

@ -0,0 +1,69 @@
package com.m2pool.chat.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.m2pool.chat.dto.ChatMessageDto;
import com.m2pool.chat.entity.ChatMessage;
import com.m2pool.chat.entity.ChatRoom;
import com.m2pool.chat.mapper.ChatMessageHistoryMapper;
import com.m2pool.chat.mapper.ChatMessageMapper;
import com.m2pool.chat.mapper.ChatRoomMapper;
import com.m2pool.chat.service.ChatMessageService;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.security.utils.SecurityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service
public class ChatMessageServiceImpl implements ChatMessageService {
@Resource
private ChatMessageMapper chatMessageMapper;
@Resource
private ChatMessageHistoryMapper chatMessageHistoryMapper;
@Resource
private ChatRoomMapper chatRoomMapper;
@Override
public AjaxResult findHistoryMessage(Long id,Integer pageNum,Long roomId) {
List<ChatMessageDto> historyMessage = chatMessageHistoryMapper.findHistoryMessage(id, pageNum, roomId);
return AjaxResult.success(historyMessage);
}
@Override
public AjaxResult findRecentlyMessage(Long id,Integer pageNum,Long roomId) {
List<ChatMessageDto> recentlyMessage = chatMessageMapper.findRecentlyMessage(id, pageNum, roomId);
return AjaxResult.success(recentlyMessage);
}
@Override
public AjaxResult deleteMessage(Long id,Integer type) {
if(chatMessageMapper.deleteById(id) > 0){
//TODO 对象图片存储删除
return AjaxResult.success("删除成功");
}
return AjaxResult.error("删除失败");
}
@Override
@Transactional
public AjaxResult readMessage(Long roomId) {
ChatRoom chatRoom = chatRoomMapper.selectById(roomId);
String username = SecurityUtils.getUsername();
String updateUserEmail = "";
if(username.equals(chatRoom.getUserOneEmail())){
updateUserEmail = chatRoom.getUserTwoEmail();
}else if(username.equals(chatRoom.getUserTwoEmail())){
updateUserEmail = chatRoom.getUserOneEmail();
}
int update = chatMessageMapper.update(ChatMessage.builder().read(1).build(),
new LambdaUpdateWrapper<ChatMessage>().eq(ChatMessage::getSendEmail, updateUserEmail).eq(ChatMessage::getRoomId, roomId));
return update > 0 ? AjaxResult.success("已读") : AjaxResult.error("消息读取失败");
}
}

View File

@ -0,0 +1,89 @@
package com.m2pool.chat.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageInfo;
import com.m2pool.chat.dto.ChatRoomDto;
import com.m2pool.chat.entity.ChatRoom;
import com.m2pool.chat.mapper.ChatMessageMapper;
import com.m2pool.chat.mapper.ChatRoomMapper;
import com.m2pool.chat.service.ChatRoomService;
import com.m2pool.chat.vo.CharRoomVo;
import com.m2pool.common.core.constant.HttpStatus;
import com.m2pool.common.core.utils.PageUtils;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.core.web.page.TableDataInfo;
import com.m2pool.common.security.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class ChatRoomServiceImpl extends ServiceImpl<ChatRoomMapper, ChatRoom> implements ChatRoomService {
@Autowired
private ChatRoomMapper chatRoomMapper;
@Resource
private ChatMessageMapper chatMessageMapper;
@Override
public TableDataInfo findRoomList() {
PageUtils.startPage();
//1.查找当前客服对应的聊天室
List<ChatRoomDto> roomList = chatRoomMapper.findRoomList(SecurityUtils.getUsername());
List<String> userEmails = roomList.stream().map(ChatRoomDto::getUserEmail).collect(Collectors.toList());
//2.查询未读数量
Map<String, Integer> unReadNums = chatMessageMapper.findUnReadNums(userEmails);
for (ChatRoomDto room : roomList) {
Integer i = unReadNums.get(room.getUserEmail());
room.setUnReadNumber(i == null ? 0 : i);
}
return getDataTable(roomList);
}
private TableDataInfo getDataTable(List<?> list)
{
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.SUCCESS);
rspData.setRows(list);
rspData.setMsg("查询成功");
PageInfo pageInfo = new PageInfo(list);
rspData.setTotal(pageInfo.getTotal());
rspData.setTotalPage(pageInfo.getPages());
return rspData;
}
@Override
@Transactional
public AjaxResult findRoomByUserid(String email) {
//1.查询当前用户与对应用户是否已存在创建的聊天室
String userEmail = SecurityUtils.getUsername();
String roomByUserEmail = chatRoomMapper.findRoomByUserEmail(userEmail, email);
if(StringUtils.isNotEmpty(roomByUserEmail)){
return AjaxResult.success(roomByUserEmail);
}
//2.不存在创建一个聊天室
int insert = chatRoomMapper.insert(ChatRoom.builder().userOneEmail(userEmail).userTwoEmail(email).build());
if (insert > 0){
return AjaxResult.success(roomByUserEmail);
}
return AjaxResult.error("聊天室不存在,并且创建聊天室失败");
}
@Override
public AjaxResult updateRoom(CharRoomVo charRoomVo) {
int i = chatRoomMapper.updateById(ChatRoom.builder().id(charRoomVo.getId()).flag(charRoomVo.getFlag()).build());
if(i > 0){
return AjaxResult.success("修改成功");
}
return AjaxResult.error("修改失败");
}
}

View File

@ -1,37 +0,0 @@
package com.m2pool.chat.service.impl;
import com.m2pool.chat.entity.ChatMessage;
import com.m2pool.chat.mapper.ChatMapper;
import com.m2pool.chat.service.ChatService;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.security.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* @Description TODO
* @Date 2025/2/28 18:08
* @Author 杜懿
*/
@Service
public class ChatServiceImpl implements ChatService {
@Autowired
private ChatMapper chatMapper;
@Async("messageExecutor")
public void saveMessageAsync(ChatMessage message) {
// 批量插入优化
chatMapper.insert(message);
}
@Override
public AjaxResult getHistory(String identifier) {
return null;
}
}

View File

@ -0,0 +1,97 @@
package com.m2pool.chat.service.impl;
import cn.hutool.core.date.DateTime;
import com.m2pool.chat.constant.Destination;
import com.m2pool.chat.dto.WebsocketMessageDto;
import com.m2pool.chat.entity.ChatMessage;
import com.m2pool.chat.entity.ChatRoom;
import com.m2pool.chat.mapper.ChatMessageMapper;
import com.m2pool.chat.mapper.ChatRoomMapper;
import com.m2pool.chat.service.StompService;
import com.m2pool.chat.vo.UserMessageVo;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.security.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import static com.m2pool.chat.constant.UserType.CUSTOMER;
import static com.m2pool.chat.constant.UserType.TOURIST;
@Service
public class StompServiceImpl implements StompService {
@Autowired
private SimpUserRegistry userRegistry;
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Resource
private ChatMessageMapper chatMessageMapper;
@Resource
private ChatRoomMapper chatRoomMapper;
@Override
public AjaxResult sendMessageToUser(UserMessageVo userMessageVo) {
WebsocketMessageDto websocketMessageDto = new WebsocketMessageDto();
websocketMessageDto.setType(userMessageVo.getType());
websocketMessageDto.setContent(userMessageVo.getContent());
// 检查目标用户是否在线
if (!checkOnline(userMessageVo)) {
return AjaxResult.error("目标用户不在线");
}
//当前用户发送其他人, 发送给指定用户. 否则默认发送给当前发送者
messagingTemplate.convertAndSendToUser(userMessageVo.getEmail(), Destination.QUEUE + "/" + userMessageVo.getEmail(), userMessageVo.getContent());
// 接收者和发送者 都不是游客才能存储消息 和修改聊天室信息
if (!TOURIST.equals(userMessageVo.getReceiveUserType()) && TOURIST.equals(userMessageVo.getSendUserType())){
Long id = insertMessage(userMessageVo);
websocketMessageDto.setId(id);
updateRoom(userMessageVo);
}
return AjaxResult.success(websocketMessageDto);
}
/**
* 检查用户是否在线
* @return
*/
private boolean checkOnline(UserMessageVo userMessageVo){
return userRegistry.getUsers().stream()
.anyMatch(user -> user.getName().equals(userMessageVo.getEmail()));
}
/**
* 新增消息
* @param userMessageVo
* @return
*/
private Long insertMessage(UserMessageVo userMessageVo){
ChatMessage message = ChatMessage.builder()
.sendEmail(SecurityUtils.getUsername())
.content(userMessageVo.getContent())
.type(userMessageVo.getType())
.roomId(userMessageVo.getRoomId())
.build();
chatMessageMapper.insert(message);
return message.getId();
}
/**
* 修改聊天信息
* @param userMessageVo
*/
private void updateRoom(UserMessageVo userMessageVo){
ChatRoom room = ChatRoom.builder().id(userMessageVo.getRoomId()).build();
if (CUSTOMER.equals(userMessageVo.getSendUserType())) {
room.setLastCustomerSendTime(DateTime.now());
} else {
room.setLastUserSendTime(DateTime.now());
}
chatRoomMapper.insert(room);
}
}

View File

@ -0,0 +1,13 @@
package com.m2pool.chat.task;
/**
* @ClassName ChatTask
* @Description chat 聊天室和消息定时任务
* @Author yyb
* @Date 2025/4/15 11:51
*/
public class ChatTask {
}

View File

@ -0,0 +1,15 @@
package com.m2pool.chat.vo;
import lombok.Data;
/**
* @ClassName CharRoomVo
* @Description 聊天室请求对象
* @Author yyb
* @Date 2025/4/15 10:42
*/
@Data
public class CharRoomVo {
private Long id;
private Boolean flag;
}

View File

@ -1,14 +0,0 @@
package com.m2pool.chat.vo;
import lombok.Data;
/**
* @Description TODO
* @Date 2025/3/7 15:55
* @Author 杜懿
*/
@Data
public class ChatHistoryVo {
private String email;
private String browserId;
}

View File

@ -14,10 +14,32 @@ public class UserMessageVo {
/**
* 消息类型
*/
private String type;
private Integer type;
/**
* 消息
* 消息内容
*/
private String message;
private String content;
/**
* 接收者
*/
private String email;
/**
* 接受用户类型 0 游客 1 登录用户 2 客服人员
*/
private Integer receiveUserType;
/**
* 发送者类型 0 游客 1 登录用户 2 客服人员
*/
private Integer sendUserType;
/**
* 聊天室id
*/
private Long roomId;
}

View File

@ -0,0 +1,76 @@
server:
port: 9209
compression:
enabled: true
mime-types: application/json
spring:
#邮箱基本配置
mail:
# 配置在limit_time内用户可以发送limit次验证码
limit: 2 这个是我额外的配置,结合邮箱服务用的
limitTime: 10 这个是我额外的配置
#配置smtp服务主机地址
# sina smtp.sina.cn
# aliyun smtp.aliyun.com
# 163 smtp.163.com 端口号465或994
host: mail.privateemail.com
#发送者邮箱
username: support@m2pool.cc
#配置密码,注意不是真正的密码,而是刚刚申请到的授权码
# password:
# password: m2pool2024@!
# password: axvm-zfgx-cgcg-qhhu
password: m2pool2024@!
#端口号
port: 587
#默认的邮件编码为UTF-8
default-encoding: UTF-8
#其他参数
properties:
mail:
#配置SSL 加密工厂
smtp:
ssl:
#本地测试先放开ssl
enable: false
required: false
#开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
debug: false
socketFactory:
class: javax.net.ssl.SSLSocketFactory
application:
# 应用名称
name: m2pool-chat
# profiles:
# # 环境配置
# active: prod
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8808
namespace: m2_prod
group: m2_prod_group
# server-addr: 127.0.0.1:8808
config:
# 配置中心地址
server-addr: 127.0.0.1:8808
# server-addr: 127.0.0.1:8808
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_prod
group: m2_prod_group
servlet:
multipart:
max-file-size: 2MB
max-request-size: 8MB
myenv:
domain: https://www.m2pool.com
path: /var/www/html/web
img: /img

View File

@ -0,0 +1,43 @@
#测试环境
server:
port: 9509
compression:
enabled: true
mime-types: application/json
spring:
application:
# 应用名称
name: m2pool-chat
# profiles:
# # 环境配置
# active: test
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8848
namespace: m2_test
group: m2_test_group
# server-addr: 127.0.0.1:8808
config:
# 配置中心地址
server-addr: 127.0.0.1:8848
# server-addr: 127.0.0.1:8808
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_test
group: m2_test_group
servlet:
multipart:
max-file-size: 2MB
max-request-size: 8MB
myenv:
domain: https://test.m2pool.com
path: /var/www/html/web_test
img: /imgpp

View File

@ -1,156 +1,3 @@
server:
port: 9209
compression:
enabled: true
mime-types: application/json
spring:
#邮箱基本配置
mail:
# 配置在limit_time内用户可以发送limit次验证码
limit: 2 这个是我额外的配置,结合邮箱服务用的
limitTime: 10 这个是我额外的配置
#配置smtp服务主机地址
# sina smtp.sina.cn
# aliyun smtp.aliyun.com
# 163 smtp.163.com 端口号465或994
host: mail.privateemail.com
#发送者邮箱
username: support@m2pool.cc
#配置密码,注意不是真正的密码,而是刚刚申请到的授权码
# password:
# password: m2pool2024@!
# password: axvm-zfgx-cgcg-qhhu
password: m2pool2024@!
#端口号
port: 587
#默认的邮件编码为UTF-8
default-encoding: UTF-8
#其他参数
properties:
mail:
#配置SSL 加密工厂
smtp:
ssl:
#本地测试先放开ssl
enable: false
required: false
#开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
debug: false
socketFactory:
class: javax.net.ssl.SSLSocketFactory
application:
# 应用名称
name: m2pool-chat
profiles:
# 环境配置
active: prod
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8808
namespace: m2_prod
group: m2_prod_group
# server-addr: 127.0.0.1:8808
config:
# 配置中心地址
server-addr: 127.0.0.1:8808
# server-addr: 127.0.0.1:8808
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_prod
group: m2_prod_group
servlet:
multipart:
max-file-size: 2MB
max-request-size: 8MB
myenv:
domain: https://www.m2pool.com
path: /var/www/html/web
img: /img
#测试环境
#server:
# port: 9509
# compression:
# enabled: true
# mime-types: application/json
#
#spring:
# #邮箱基本配置
# mail:
# # 配置在limit_time内用户可以发送limit次验证码
# limit: 2 这个是我额外的配置,结合邮箱服务用的
# limitTime: 10 这个是我额外的配置
# #配置smtp服务主机地址
# # sina smtp.sina.cn
# # aliyun smtp.aliyun.com
# # 163 smtp.163.com 端口号465或994
# host: mail.privateemail.com
# #发送者邮箱
# username: support@m2pool.cc
# #配置密码,注意不是真正的密码,而是刚刚申请到的授权码
# # password:
# # password: m2pool2024@!
# # password: axvm-zfgx-cgcg-qhhu
# password: m2pool2024@!
# #端口号
# port: 587
# #默认的邮件编码为UTF-8
# default-encoding: UTF-8
# #其他参数
# properties:
# mail:
# #配置SSL 加密工厂
# smtp:
# ssl:
# #本地测试先放开ssl
# enable: false
# required: false
# #开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
# debug: false
# socketFactory:
# class: javax.net.ssl.SSLSocketFactory
#
# application:
# # 应用名称
# name: m2pool-chat
# profiles:
# # 环境配置
# active: test
# cloud:
# nacos:
# discovery:
# # 服务注册地址
# server-addr: 127.0.0.1:8808
# namespace: m2_test
# group: m2_test_group
# # server-addr: 127.0.0.1:8808
# config:
# # 配置中心地址
# server-addr: 127.0.0.1:8808
# # server-addr: 127.0.0.1:8808
# # 配置文件格式
# file-extension: yml
# # 共享配置
# shared-configs:
# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
# namespace: m2_test
# group: m2_test_group
# servlet:
# multipart:
# max-file-size: 2MB
# max-request-size: 8MB
#
#myenv:
# domain: https://test.m2pool.com
# path: /var/www/html/web_test
# img: /imgpp
#
active: prod

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.m2pool.chat.mapper.ChatMessageHistoryMapper">
<select id="findHistoryMessage" resultType="com.m2pool.chat.dto.ChatMessageDto">
SELECT id,
type,
send_email as sendEmail,
content,
create_time as createTime
FROM chat_message_history
WHERE id <![CDATA[ <= ]]> #{id} AND room_id = #{roomId}
ORDER BY create_time DESC
LIMIT #{pageNum}
</select>
</mapper>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.m2pool.chat.mapper.ChatMessageMapper">
<select id="findRecentlyMessage" resultType="com.m2pool.chat.dto.ChatMessageDto">
SELECT id,
type,
send_email as sendEmail,
content,
create_time as createTime
FROM chat_message
WHERE id <![CDATA[ <= ]]> #{id} AND room_id = #{roomId}
ORDER BY create_time DESC
LIMIT #{pageNum}
</select>
<select id="findUnReadNums" resultType="java.util.Map">
SELECT user_email as userEmail,
count(*) as unReadNums
FROM chat_message
WHERE send_email IN
<foreach collection="userEmails" item="userEmail" open="(" separator="," close=")">
#{userEmail}
</foreach>
AND read_flag = false
GROUP BY user_email
</select>
</mapper>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.m2pool.chat.mapper.ChatRoomMapper">
<select id="findRoomByUserEmail" resultType="java.lang.String">
SELECT
id
FROM
chat_room
WHERE
user_one_email = #{userEmail} AND user_two_email = #{email} OR
(user_one_email = #{email} AND user_two_email = #{userEmail})
</select>
<select id="findRoomList" resultType="com.m2pool.chat.dto.ChatRoomDto">
SELECT
id,
user_one_email AS userEmail,
flag,
last_user_send_time as lastUserSendTime,
last_customer_send_time as lastCustomerSendTime
FROM
chat_room
WHERE
user_two_email = #{userEmail}
AND del = FALSE
GROUP BY
flag DESC,
last_user_send_time DESC,
last_customer_send_time DESC
</select>
</mapper>

View File

@ -3,6 +3,7 @@ package com.m2pool.system;
import com.m2pool.common.security.annotation.EnableCustomConfig;
import com.m2pool.common.security.annotation.EnableM2PoolFeignClients;
import com.m2pool.common.swagger.annotation.EnableCustomSwagger2;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@ -16,6 +17,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableCustomSwagger2
@EnableM2PoolFeignClients
@SpringBootApplication
@MapperScan({"com.m2pool.system.mapper"})
public class M2PoolSystemApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,28 @@
# Tomcat
server:
port: 9201
# Spring
spring:
application:
# 应用名称
name: m2pool-system
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8808
namespace: m2_prod
group: m2_prod_group
config:
# 配置中心地址
server-addr: 127.0.0.1:8808
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_prod
group: m2_prod_group

View File

@ -0,0 +1,24 @@
server:
port: 9501
spring:
application:
# 应用名称
name: m2pool-system
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8848
namespace: m2_test
group: m2_test_group
config:
# 配置中心地址
server-addr: 127.0.0.1:8848
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_test
group: m2_test_group

View File

@ -1,58 +1,3 @@
## Tomcat
#server:
# port: 9201
#
## Spring
#spring:
# application:
# # 应用名称
# name: m2pool-system
# profiles:
# # 环境配置
# active: prod
# cloud:
# nacos:
# discovery:
# # 服务注册地址
# server-addr: 127.0.0.1:8808
# namespace: m2_prod
# group: m2_prod_group
# config:
# # 配置中心地址
# server-addr: 127.0.0.1:8808
# # 配置文件格式
# file-extension: yml
# # 共享配置
# shared-configs:
# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
# namespace: m2_prod
# group: m2_prod_group
server:
port: 9501
spring:
application:
# 应用名称
name: m2pool-system
profiles:
# 环境配置
active: test
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8808
namespace: m2_test
group: m2_test_group
config:
# 配置中心地址
server-addr: 127.0.0.1:8808
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
namespace: m2_test
group: m2_test_group
active: prod