docs(deployment): 更新部署文档并添加自动化部署脚本

- 更新了 DEPLOYMENT.md 文档,增加了更多部署细节和说明
- 添加了 Linux 和 Windows 平台的自动化部署脚本
- 更新了 README.md,增加了部署相关说明
- 调整了 .env 文件配置,以适应新的部署流程
- 移除了部分不必要的代码和配置
This commit is contained in:
2025-09-10 14:16:27 +08:00
parent 18fe719f94
commit b2d940e014
114 changed files with 6990 additions and 247 deletions

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.jiebanke</groupId>
<artifactId>backend-java</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>user-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- 结伴客公共模块 -->
<dependency>
<groupId>com.jiebanke</groupId>
<artifactId>common</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,15 @@
package com.jiebanke.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.jiebanke.user.client")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}

View File

@@ -0,0 +1,14 @@
package com.jiebanke.user.client;
import com.jiebanke.common.vo.ApiResponse;
import com.jiebanke.user.config.FeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
@FeignClient(name = "auth-service", configuration = FeignConfig.class)
public interface AuthServiceClient {
@GetMapping("/api/auth/validate")
ApiResponse<Boolean> validateToken(@RequestHeader("Authorization") String token);
}

View File

@@ -0,0 +1,31 @@
package com.jiebanke.user.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
String authorization = request.getHeader("Authorization");
if (authorization != null) {
template.header("Authorization", authorization);
}
}
}
};
}
}

View File

@@ -0,0 +1,44 @@
package com.jiebanke.user.service;
import com.jiebanke.common.config.RabbitMQConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserRabbitMQService {
@Autowired
private RabbitTemplate rabbitTemplate;
// 发送用户注册消息
public void sendUserRegistrationMessage(Long userId, String username) {
Map<String, Object> message = new HashMap<>();
message.put("userId", userId);
message.put("username", username);
message.put("eventType", "USER_REGISTERED");
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.USER_ROUTING_KEY,
message
);
}
// 发送用户更新消息
public void sendUserUpdateMessage(Long userId, String username) {
Map<String, Object> message = new HashMap<>();
message.put("userId", userId);
message.put("username", username);
message.put("eventType", "USER_UPDATED");
rabbitTemplate.convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.USER_ROUTING_KEY,
message
);
}
}

View File

@@ -0,0 +1,44 @@
package com.jiebanke.user.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserRedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 缓存用户信息
public void cacheUserInfo(Long userId, Object userInfo) {
redisTemplate.opsForValue().set("user:" + userId, userInfo, 30, TimeUnit.MINUTES);
}
// 获取缓存的用户信息
public Object getCachedUserInfo(Long userId) {
return redisTemplate.opsForValue().get("user:" + userId);
}
// 删除缓存的用户信息
public void removeCachedUserInfo(Long userId) {
redisTemplate.delete("user:" + userId);
}
// 缓存用户登录状态
public void cacheUserLoginStatus(String token, Long userId) {
redisTemplate.opsForValue().set("login:token:" + token, userId, 7, TimeUnit.DAYS);
}
// 获取用户登录状态
public Long getUserLoginStatus(String token) {
return (Long) redisTemplate.opsForValue().get("login:token:" + token);
}
// 删除用户登录状态
public void removeUserLoginStatus(String token) {
redisTemplate.delete("login:token:" + token);
}
}

View File

@@ -0,0 +1,25 @@
server:
port: 8082
spring:
application:
name: user-service
datasource:
url: jdbc:mysql://localhost:3306/jiebanke?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: localhost
port: 6379
database: 0
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/

View File

@@ -0,0 +1,53 @@
package com.jiebanke.user.service;
import com.jiebanke.common.config.RabbitMQConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import static org.mockito.Mockito.*;
class UserRabbitMQServiceTest {
@Mock
private RabbitTemplate rabbitTemplate;
@InjectMocks
private UserRabbitMQService userRabbitMQService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void testSendUserRegistrationMessage() {
Long userId = 1L;
String username = "testUser";
userRabbitMQService.sendUserRegistrationMessage(userId, username);
verify(rabbitTemplate).convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.USER_ROUTING_KEY,
anyMap()
);
}
@Test
void testSendUserUpdateMessage() {
Long userId = 1L;
String username = "testUser";
userRabbitMQService.sendUserUpdateMessage(userId, username);
verify(rabbitTemplate).convertAndSend(
RabbitMQConfig.EXCHANGE_NAME,
RabbitMQConfig.USER_ROUTING_KEY,
anyMap()
);
}
}

View File

@@ -0,0 +1,92 @@
package com.jiebanke.user.service;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class UserRedisServiceTest {
@Mock
private RedisTemplate<String, Object> redisTemplate;
@Mock
private ValueOperations<String, Object> valueOperations;
@InjectMocks
private UserRedisService userRedisService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
}
@Test
void testCacheUserInfo() {
Long userId = 1L;
String userInfo = "testUser";
userRedisService.cacheUserInfo(userId, userInfo);
verify(valueOperations).set(eq("user:" + userId), eq(userInfo), anyLong(), any());
}
@Test
void testGetCachedUserInfo() {
Long userId = 1L;
String userInfo = "testUser";
when(valueOperations.get("user:" + userId)).thenReturn(userInfo);
Object result = userRedisService.getCachedUserInfo(userId);
assertEquals(userInfo, result);
verify(valueOperations).get("user:" + userId);
}
@Test
void testRemoveCachedUserInfo() {
Long userId = 1L;
userRedisService.removeCachedUserInfo(userId);
verify(redisTemplate).delete("user:" + userId);
}
@Test
void testCacheUserLoginStatus() {
String token = "testToken";
Long userId = 1L;
userRedisService.cacheUserLoginStatus(token, userId);
verify(valueOperations).set(eq("login:token:" + token), eq(userId), anyLong(), any());
}
@Test
void testGetUserLoginStatus() {
String token = "testToken";
Long userId = 1L;
when(valueOperations.get("login:token:" + token)).thenReturn(userId);
Long result = userRedisService.getUserLoginStatus(token);
assertEquals(userId, result);
verify(valueOperations).get("login:token:" + token);
}
@Test
void testRemoveUserLoginStatus() {
String token = "testToken";
userRedisService.removeUserLoginStatus(token);
verify(redisTemplate).delete("login:token:" + token);
}
}