From 516a676e664267d9b58597e3ecd3ee4369f4b885 Mon Sep 17 00:00:00 2001
From: chenyuepan <3158614516@qq.com>
Date: Sun, 29 Jun 2025 11:04:57 +0800
Subject: [PATCH] =?UTF-8?q?Netty=20+=20RabbitMQ=20+=20MongoDB=20=E5=AE=9E?=
=?UTF-8?q?=E7=8E=B0=E5=9C=A8=E7=BA=BF=E8=81=8A=E5=A4=A9=E5=AE=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 20 ++--
.../config/RabbitMQConfig.java | 22 +++++
.../controller/UserController.java | 22 +----
.../dto/CheckDTO.java | 13 ---
.../entity/ChatMessage.java | 24 +++++
.../netty/ChatServerHandler.java | 47 +++++++++-
.../netty/NettyChatServer.java | 6 +-
.../repository/ChatMessageRepository.java | 11 +++
.../service/UserService.java | 2 -
.../service/impl/QuestionServiceImpl.java | 13 ++-
.../service/impl/UserServiceImpl.java | 93 +++++++++++++------
.../utils/PasswordGenerator.java | 40 ++++++++
.../venue_reservation_service/vo/LoginVo.java | 5 +-
src/main/resources/application.yml | 13 ++-
14 files changed, 247 insertions(+), 84 deletions(-)
delete mode 100644 src/main/java/com/example/venue_reservation_service/dto/CheckDTO.java
create mode 100644 src/main/java/com/example/venue_reservation_service/entity/ChatMessage.java
create mode 100644 src/main/java/com/example/venue_reservation_service/repository/ChatMessageRepository.java
create mode 100644 src/main/java/com/example/venue_reservation_service/utils/PasswordGenerator.java
diff --git a/pom.xml b/pom.xml
index 33481d8..ef36511 100644
--- a/pom.xml
+++ b/pom.xml
@@ -217,26 +217,18 @@
knife4j-openapi3-jakarta-spring-boot-starter
4.5.0
-
-
-
-
+
de.codecentric
spring-boot-admin-starter-client
3.0.2
-
-
-
-
-
-
-
-
-
-
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
diff --git a/src/main/java/com/example/venue_reservation_service/config/RabbitMQConfig.java b/src/main/java/com/example/venue_reservation_service/config/RabbitMQConfig.java
index 26866f4..8bd1ddc 100644
--- a/src/main/java/com/example/venue_reservation_service/config/RabbitMQConfig.java
+++ b/src/main/java/com/example/venue_reservation_service/config/RabbitMQConfig.java
@@ -1,6 +1,9 @@
package com.example.venue_reservation_service.config;
import org.springframework.amqp.core.*;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -32,4 +35,23 @@ public class RabbitMQConfig {
.with("delay.routing.key")
.noargs();
}
+
+ // 添加聊天消息队列
+ @Bean
+ public Queue chatMessageQueue() {
+ return new Queue("chat.message.queue");
+ }
+
+ // 配置RabbitTemplate用于JSON消息转换
+ @Bean
+ public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
+ final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
+ rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter());
+ return rabbitTemplate;
+ }
+
+ @Bean
+ public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
+ return new Jackson2JsonMessageConverter();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/example/venue_reservation_service/controller/UserController.java b/src/main/java/com/example/venue_reservation_service/controller/UserController.java
index f250f2e..0709696 100644
--- a/src/main/java/com/example/venue_reservation_service/controller/UserController.java
+++ b/src/main/java/com/example/venue_reservation_service/controller/UserController.java
@@ -64,12 +64,12 @@ public class UserController {
}
@Operation(
- summary = "管理员登录",
- description = "场馆管理员登录系统,验证账号密码",
+ summary = "账号密码登录",
+ description = "用户、管理员以及超级管理员登录系统,验证账号密码",
parameters = {
@Parameter(
name = "dto",
- description = "管理员登录DTO,包含账号和密码",
+ description = "登录DTO,包含账号和密码",
required = true,
schema = @Schema(implementation = AdminDTO.class)
)
@@ -114,22 +114,6 @@ public class UserController {
return userService.getUserList(dto);
}
- @Operation(
- summary = "校验用户身份",
- description = "校验用户提交的身份信息(如等)",
- parameters = {
- @Parameter(
- name = "dto",
- description = "身份校验DTO,包含用户身份信息",
- required = true,
- schema = @Schema(implementation = CheckDTO.class)
- )
- }
- )
- @PostMapping("/check")
- public Result check(@RequestBody CheckDTO dto) {
- return userService.userCheck(dto);
- }
@Operation(
summary = "获取用户认证信息",
diff --git a/src/main/java/com/example/venue_reservation_service/dto/CheckDTO.java b/src/main/java/com/example/venue_reservation_service/dto/CheckDTO.java
deleted file mode 100644
index 6523aa5..0000000
--- a/src/main/java/com/example/venue_reservation_service/dto/CheckDTO.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.example.venue_reservation_service.dto;
-
-import lombok.Data;
-
-@Data
-public class CheckDTO {
-
- private Integer userId;
-
- private String schoolId;
-
- private String passwd;
-}
diff --git a/src/main/java/com/example/venue_reservation_service/entity/ChatMessage.java b/src/main/java/com/example/venue_reservation_service/entity/ChatMessage.java
new file mode 100644
index 0000000..48a4328
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/entity/ChatMessage.java
@@ -0,0 +1,24 @@
+package com.example.venue_reservation_service.entity;
+
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import java.time.LocalDateTime;
+
+@Document(collection = "chat_messages")
+@Data
+public class ChatMessage {
+ @Id
+ private String id;
+ private String sender;
+ private Integer senderRole;
+ private String content;
+ private LocalDateTime timestamp;
+
+ public ChatMessage(String sender, Integer senderRole, String content) {
+ this.sender = sender;
+ this.senderRole = senderRole;
+ this.content = content;
+ this.timestamp = LocalDateTime.now();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/venue_reservation_service/netty/ChatServerHandler.java b/src/main/java/com/example/venue_reservation_service/netty/ChatServerHandler.java
index dbf2ac2..3913957 100644
--- a/src/main/java/com/example/venue_reservation_service/netty/ChatServerHandler.java
+++ b/src/main/java/com/example/venue_reservation_service/netty/ChatServerHandler.java
@@ -1,6 +1,9 @@
package com.example.venue_reservation_service.netty;
+import com.example.venue_reservation_service.entity.ChatMessage;
+import com.example.venue_reservation_service.repository.ChatMessageRepository;
import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
@@ -9,18 +12,28 @@ import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Scope;
+import org.springframework.data.domain.Page;
+import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
+@Component
+@Scope("prototype")
+@ChannelHandler.Sharable
public class ChatServerHandler extends SimpleChannelInboundHandler {
private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private static final Map userChannelMap = new HashMap<>();
private static final Map roleMap = new HashMap<>();
+ @Autowired
+ private ChatMessageRepository chatMessageRepository;
+
static {
roleMap.put(0, "普通用户");
roleMap.put(1, "普通用户");
@@ -41,7 +54,6 @@ public class ChatServerHandler extends SimpleChannelInboundHandler entry : userChannelMap.entrySet()) {
if (entry.getValue().equals(incoming)) {
username = entry.getKey();
@@ -71,6 +83,8 @@ public class ChatServerHandler extends SimpleChannelInboundHandler messages = chatMessageRepository.findByTimestampBeforeOrderByTimestampDesc(
+ beforeDate,
+ org.springframework.data.domain.PageRequest.of(0, pageSize)
+ );
+
+ JSONObject response = new JSONObject();
+ response.put("type", "history");
+ response.put("messages", messages.getContent());
+ response.put("hasMore", !messages.isLast());
+
+ incoming.writeAndFlush(new TextWebSocketFrame(response.toJSONString()));
+ }
+
private void broadcastMessage(String message) {
for (Channel channel : channels) {
channel.writeAndFlush(new TextWebSocketFrame(message));
@@ -158,7 +202,6 @@ public class ChatServerHandler extends SimpleChannelInboundHandler entry : userChannelMap.entrySet()) {
if (entry.getValue().equals(channel)) {
username = entry.getKey();
diff --git a/src/main/java/com/example/venue_reservation_service/netty/NettyChatServer.java b/src/main/java/com/example/venue_reservation_service/netty/NettyChatServer.java
index 9aa17af..e70cb68 100644
--- a/src/main/java/com/example/venue_reservation_service/netty/NettyChatServer.java
+++ b/src/main/java/com/example/venue_reservation_service/netty/NettyChatServer.java
@@ -15,6 +15,7 @@ import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketSe
import io.netty.handler.stream.ChunkedWriteHandler;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -24,6 +25,9 @@ public class NettyChatServer {
@Value("${server.netty.port}")
private int port;
+ @Autowired
+ private ChatServerHandler chatServerHandler; // 确保正确注入
+
private EventLoopGroup bossGroup;
private EventLoopGroup workerGroup;
@@ -46,7 +50,7 @@ public class NettyChatServer {
new ChunkedWriteHandler(),
new WebSocketServerCompressionHandler(),
new WebSocketServerProtocolHandler("/ws", null, true),
- new ChatServerHandler()
+ chatServerHandler // 使用注入的共享handler
);
}
});
diff --git a/src/main/java/com/example/venue_reservation_service/repository/ChatMessageRepository.java b/src/main/java/com/example/venue_reservation_service/repository/ChatMessageRepository.java
new file mode 100644
index 0000000..7709b72
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/repository/ChatMessageRepository.java
@@ -0,0 +1,11 @@
+package com.example.venue_reservation_service.repository;
+
+import com.example.venue_reservation_service.entity.ChatMessage;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import java.time.LocalDateTime;
+
+public interface ChatMessageRepository extends MongoRepository {
+ Page findByTimestampBeforeOrderByTimestampDesc(LocalDateTime before, Pageable pageable);
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/venue_reservation_service/service/UserService.java b/src/main/java/com/example/venue_reservation_service/service/UserService.java
index 2089358..8bf942a 100644
--- a/src/main/java/com/example/venue_reservation_service/service/UserService.java
+++ b/src/main/java/com/example/venue_reservation_service/service/UserService.java
@@ -21,8 +21,6 @@ public interface UserService extends IService {
Result getUserList(UserDTO dto);
- Result userCheck(CheckDTO dto);
-
Result getSchool(Integer userId);
Result userRecharge(Integer userId, Double amount);
diff --git a/src/main/java/com/example/venue_reservation_service/service/impl/QuestionServiceImpl.java b/src/main/java/com/example/venue_reservation_service/service/impl/QuestionServiceImpl.java
index 26d0ab7..b3bd672 100644
--- a/src/main/java/com/example/venue_reservation_service/service/impl/QuestionServiceImpl.java
+++ b/src/main/java/com/example/venue_reservation_service/service/impl/QuestionServiceImpl.java
@@ -66,6 +66,7 @@ public class QuestionServiceImpl extends ServiceImpl
question.setCreatedTime(LocalDateTime.now());
}
question.setToUser(1);
+ question.setToAdmin(1);
saveOrUpdate(question);
return Result.ok(question).message("保存问题成功");
}
@@ -108,10 +109,18 @@ public class QuestionServiceImpl extends ServiceImpl
if (!Objects.equals(question.getUserId(), userId)) {
return Result.fail().message("非本人提问,不可删除");
}
- if (question.getStatus() == 1) {
+ User user = userMapper.selectById(userId);
+ if(user.getType() == 3){
+ question.setToAdmin(0);
+ }else{
+ question.setToUser(0);
+ }
+ if (question.getToUser() == 0 && question.getToAdmin() == 0 && question.getStatus() == 1) {
replyMapper.delete(Wrappers.lambdaQuery().eq(Reply::getQuestionId, id));
+ removeById(id);
+ }else{
+ updateById(question);
}
- removeById(id);
return Result.ok(question).message("移除成功");
}
diff --git a/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java b/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java
index 4a0742f..e4fece6 100644
--- a/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java
+++ b/src/main/java/com/example/venue_reservation_service/service/impl/UserServiceImpl.java
@@ -85,20 +85,29 @@ public class UserServiceImpl extends ServiceImpl
return Result.fail().message("登录失败");
}
User user = getOne(Wrappers.lambdaQuery().eq(User::getOpenId, openid));
+ LoginVo loginVo = new LoginVo();
+ loginVo.setIsFirst(0);
if(Optional.ofNullable(user).isEmpty()){
// 如果当前用户信息不存在,进行注册
user = new User();
+ // 生成默认用户名
user.setUsername("用户" + System.currentTimeMillis());
+ // 设置OpenId,作唯一标识
user.setOpenId(openid);
- user.setType(0); // 表示普通用户
- user.setAccount(LocalDate.now().getYear() +"-"+ System.currentTimeMillis());
+ // 表示普通用户
+ user.setType(0);
+ // 设置用户账号(系统生成)
+ user.setAccount(PasswordGenerator.generateAccount());
+ // 设置用户初始密码
+ user.setAccessCode(PasswordGenerator.generatePassword());
user.setRegisterTime(LocalDateTime.now());
user.setIsUpload(0);
save(user);
-
// 添加新的账户余额信息
Balance balance = new Balance(0.0, user.getId());
balanceService.save(balance);
+ loginVo.setIsFirst(1);
+ loginVo.setAccessCode(user.getAccessCode());
}
// 获取用户账号余额数据
Double money = balanceService.getOne(Wrappers.lambdaQuery().eq(Balance::getUserId, user.getId()))
@@ -107,7 +116,8 @@ public class UserServiceImpl extends ServiceImpl
user.setAvatar(imgUrl + user.getAvatar());
}
user.setBalance(money);
- LoginVo loginVo = new LoginVo(user, JwtUtil.createToken(user.getId()));
+ loginVo.setUser(user);
+ loginVo.setToken(JwtUtil.createToken(user.getId()));
return Result.ok(loginVo).message("登录成功");
}
@@ -117,34 +127,66 @@ public class UserServiceImpl extends ServiceImpl
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getAccount, account);
User user = getOne(wrapper);
- if (getOne(wrapper) == null) {
- return Result.fail().message("管理员账号不存在");
+ if (user == null) { // 修正:避免重复查询
+ return Result.fail().message("用户账号不存在");
}
+
String password = dto.getPasswd();
- if(!user.getAccessCode().equals(password)){
- // 查询该账号在五分钟内的登录次数
- String key = name + dto.getAccount();
- if (Optional.ofNullable(redisUtil.get(key)).isEmpty()) {
- // 若该 key 值未出现过(或者过期了),则存入缓存,同时设置存活时间为5分钟
- redisUtil.set(key, 1+"-"+ System.currentTimeMillis(), 5 * 60);
- }else {
- String[] split = String.valueOf(redisUtil.get(key)).split("-");
- // 获取次数
- int i = Integer.parseInt(split[0]);
- if(i == 3){
- return Result.fail().message("该账号已被锁定,请在 "+ DateUtil.calc(split[1])+" 秒后,再进行登录");
- }else{
- i++;
- redisUtil.set(key, i+"-"+String.valueOf(System.currentTimeMillis()), 5 * 60 );
+ String redisKey = "login_lock:" + account; // 使用固定前缀
+
+ if (!user.getAccessCode().equals(password)) {
+ // 处理密码错误的情况
+ if (redisUtil.hasKey(redisKey)) {
+ String[] parts = String.valueOf(redisUtil.get(redisKey)).split("-");
+ int failCount = Integer.parseInt(parts[0]);
+ long firstFailTime = Long.parseLong(parts[1]);
+
+ // 错误次数已达上限
+ if (failCount >= 3) {
+ long lockTime = 5 * 60 * 1000; // 5分钟毫秒数
+ long remainMillis = lockTime - (System.currentTimeMillis() - firstFailTime);
+ if (remainMillis > 0) {
+ return Result.fail().message("该账号已被锁定,请在 " + (remainMillis / 1000) + " 秒后重新登录");
+ } else {
+ // 锁定已过期,重置计数器
+ redisUtil.remove(redisKey);
+ }
}
}
+
+ // 更新错误计数器(包含首次错误情况)
+ int newCount = 1;
+ long timestamp = System.currentTimeMillis();
+ if (redisUtil.hasKey(redisKey)) {
+ String[] parts = String.valueOf(redisUtil.get(redisKey)).split("-");
+ newCount = Integer.parseInt(parts[0]) + 1;
+ timestamp = Long.parseLong(parts[1]); // 保持第一次错误时间
+ }
+
+ // 更新Redis,仅首次设置过期时间
+ String value = newCount + "-" + timestamp;
+ if (newCount == 1) {
+ redisUtil.set(redisKey, value, 5 * 60);
+ } else {
+ redisUtil.set(redisKey, value); // 更新次数但保留原TTL
+ }
+
return Result.fail().message("账号密码不匹配");
}
+
+ // 密码正确,登录成功
+ // 清除登录错误计数
+ if (redisUtil.hasKey(redisKey)) {
+ redisUtil.remove(redisKey);
+ }
+
user.setAccessCode("");
- if(user.getIsUpload() == 1){
+ if (user.getIsUpload() == 1) {
user.setAvatar(imgUrl + user.getAvatar());
}
- LoginVo vo = new LoginVo(user, JwtUtil.createToken(user.getId()));
+ LoginVo vo = new LoginVo();
+ vo.setUser(user);
+ vo.setToken(JwtUtil.createToken(user.getId()));
return Result.ok(vo).message("登录成功");
}
@@ -204,11 +246,6 @@ public class UserServiceImpl extends ServiceImpl
return Result.ok(vo).message("ok");
}
- @Override
- public Result userCheck(CheckDTO dto) {
- return Result.ok( ).message("身份信息认证成功");
- }
-
@Override
public Result getSchool(Integer userId) {
School one = schoolService.getOne(Wrappers.lambdaQuery()
diff --git a/src/main/java/com/example/venue_reservation_service/utils/PasswordGenerator.java b/src/main/java/com/example/venue_reservation_service/utils/PasswordGenerator.java
new file mode 100644
index 0000000..50e55b8
--- /dev/null
+++ b/src/main/java/com/example/venue_reservation_service/utils/PasswordGenerator.java
@@ -0,0 +1,40 @@
+package com.example.venue_reservation_service.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+
+public class PasswordGenerator {
+
+ private static final char[] ALPHANUMERIC_CHARS =
+ "0123456789abcdefghijklmnopqrstuvwxyz".toCharArray();
+
+ public static String generatePassword() {
+ Random random = new Random();
+ char[] password = new char[6];
+
+ for (int i = 0; i < 6; i++) {
+ int randomIndex = random.nextInt(ALPHANUMERIC_CHARS.length);
+ password[i] = ALPHANUMERIC_CHARS[randomIndex];
+ }
+
+ return new String(password);
+ }
+
+ public static String generateAccount() {
+ // 1. 获取当前日期(yymmdd格式)
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMdd");
+ String datePart = dateFormat.format(new Date());
+
+ // 2. 获取时间戳毫秒值,并取最后6位
+ long timestamp = System.currentTimeMillis();
+ String timestampStr = String.valueOf(timestamp);
+
+ // 确保获取最后6位数字
+ String timePart = timestampStr.substring(
+ Math.max(0, timestampStr.length() - 6));
+
+ // 3. 组合成账号
+ return datePart + timePart;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/venue_reservation_service/vo/LoginVo.java b/src/main/java/com/example/venue_reservation_service/vo/LoginVo.java
index 43ee1ee..d366385 100644
--- a/src/main/java/com/example/venue_reservation_service/vo/LoginVo.java
+++ b/src/main/java/com/example/venue_reservation_service/vo/LoginVo.java
@@ -5,10 +5,13 @@ import lombok.AllArgsConstructor;
import lombok.Data;
@Data
-@AllArgsConstructor
public class LoginVo {
private User user;
private String token;
+
+ private Integer isFirst;
+
+ private String accessCode;
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index fe315b6..72ab5b6 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -35,6 +35,13 @@ spring:
max-wait: -1
max-idle: 5
min-idle: 0
+ mongodb:
+ host: ${localhosturl}
+ port: 27017
+ database: chat_db
+ username: mongo
+ password: mongo
+ authentication-database: admin
jackson:
date-format: yyyy-MM-dd HH:mm:ss
rabbitmq:
@@ -58,7 +65,7 @@ spring:
boot:
admin:
client:
- url: http://localhost:10020/ # 服务端地址
+ url: http://119.29.191.232:10020 # 服务端地址
username: admin # 服务端用户名
password: admin123! # 服务端密码
@@ -90,7 +97,9 @@ knife4j:
# username: admin
# password: 123456
-
+chat:
+ # MongoDB消息保留天数
+ message-retention-days: 30
weixin:
app_id: wxce0025f2c0b10a8e
secret: 686870f1b1a875369ee979e0648fa950