docs(deployment): 更新部署文档并添加自动化部署脚本
- 更新了 DEPLOYMENT.md 文档,增加了更多部署细节和说明 - 添加了 Linux 和 Windows 平台的自动化部署脚本 - 更新了 README.md,增加了部署相关说明 - 调整了 .env 文件配置,以适应新的部署流程 - 移除了部分不必要的代码和配置
This commit is contained in:
96
backend-java/auth-service/pom.xml
Normal file
96
backend-java/auth-service/pom.xml
Normal file
@@ -0,0 +1,96 @@
|
||||
<?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>auth-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>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- BCrypt -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</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>
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.jiebanke.auth;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients
|
||||
@MapperScan("com.jiebanke.auth.mapper")
|
||||
@ComponentScan(basePackages = "com.jiebanke")
|
||||
public class AuthApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AuthApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.jiebanke.auth.controller;
|
||||
|
||||
import com.jiebanke.auth.service.AuthRedisService;
|
||||
import com.jiebanke.common.vo.ApiResponse;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String jwtSecret;
|
||||
|
||||
@Autowired
|
||||
private AuthRedisService authRedisService;
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public ApiResponse<AuthResult> register(@RequestBody RegisterRequest request) {
|
||||
User user = new User();
|
||||
user.setUsername(request.getUsername());
|
||||
user.setEmail(request.getEmail());
|
||||
user.setPhone(request.getPhone());
|
||||
user.setRealName(request.getNickname());
|
||||
|
||||
AuthResult result = authService.register(user, request.getPassword());
|
||||
return ApiResponse.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public ApiResponse<AuthResult> login(@RequestBody LoginRequest request) {
|
||||
AuthResult result = authService.login(request.getUsername(), request.getPassword());
|
||||
return ApiResponse.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
@GetMapping("/me")
|
||||
public ApiResponse<User> getCurrentUser(@RequestHeader("userId") Long userId) {
|
||||
User user = authService.getCurrentUser(userId);
|
||||
return ApiResponse.success(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户个人信息
|
||||
*/
|
||||
@PutMapping("/profile")
|
||||
public ApiResponse<User> updateProfile(
|
||||
@RequestHeader("userId") Long userId,
|
||||
@RequestBody User user) {
|
||||
User updatedUser = authService.updateProfile(userId, user);
|
||||
return ApiResponse.success(updatedUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
@PutMapping("/password")
|
||||
public ApiResponse<Map<String, String>> changePassword(
|
||||
@RequestHeader("userId") Long userId,
|
||||
@RequestBody ChangePasswordRequest request) {
|
||||
boolean success = authService.changePassword(userId, request.getCurrentPassword(), request.getNewPassword());
|
||||
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("message", success ? "密码修改成功" : "密码修改失败");
|
||||
return ApiResponse.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信登录/注册
|
||||
*/
|
||||
@PostMapping("/wechat")
|
||||
public ApiResponse<AuthResult> wechatLogin(@RequestBody WechatLoginRequest request) {
|
||||
AuthResult result = authService.wechatLogin(request.getCode(), request.getUserInfo());
|
||||
return ApiResponse.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
*/
|
||||
@PostMapping("/admin/login")
|
||||
public ApiResponse<AuthResult> adminLogin(@RequestBody LoginRequest request) {
|
||||
AuthResult result = authService.adminLogin(request.getUsername(), request.getPassword());
|
||||
return ApiResponse.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/validate")
|
||||
public ApiResponse<Boolean> validateToken(@RequestHeader("Authorization") String token) {
|
||||
try {
|
||||
// 移除 "Bearer " 前缀
|
||||
if (token.startsWith("Bearer ")) {
|
||||
token = token.substring(7);
|
||||
}
|
||||
|
||||
// 解析JWT令牌
|
||||
Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(jwtSecret.getBytes())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
// 从Redis中获取用户登录状态
|
||||
Long userId = authRedisService.getUserLoginStatus(token);
|
||||
if (userId != null && userId.equals(claims.get("userId", Long.class))) {
|
||||
return ApiResponse.success(true);
|
||||
}
|
||||
|
||||
return ApiResponse.success(false);
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.success(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 请求体类
|
||||
static class RegisterRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
private String nickname;
|
||||
private String email;
|
||||
private String phone;
|
||||
|
||||
// Getters and setters
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
|
||||
public String getNickname() { return nickname; }
|
||||
public void setNickname(String nickname) { this.nickname = nickname; }
|
||||
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
}
|
||||
|
||||
static class LoginRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
// Getters and setters
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
}
|
||||
|
||||
static class ChangePasswordRequest {
|
||||
private String currentPassword;
|
||||
private String newPassword;
|
||||
|
||||
// Getters and setters
|
||||
public String getCurrentPassword() { return currentPassword; }
|
||||
public void setCurrentPassword(String currentPassword) { this.currentPassword = currentPassword; }
|
||||
|
||||
public String getNewPassword() { return newPassword; }
|
||||
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
|
||||
}
|
||||
|
||||
static class WechatLoginRequest {
|
||||
private String code;
|
||||
private Object userInfo;
|
||||
|
||||
// Getters and setters
|
||||
public String getCode() { return code; }
|
||||
public void setCode(String code) { this.code = code; }
|
||||
|
||||
public Object getUserInfo() { return userInfo; }
|
||||
public void setUserInfo(Object userInfo) { this.userInfo = userInfo; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.jiebanke.auth.entity;
|
||||
|
||||
import com.jiebanke.common.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class User extends BaseEntity {
|
||||
private String username;
|
||||
private String password;
|
||||
private String email;
|
||||
private String phone;
|
||||
private String realName;
|
||||
private String idCard;
|
||||
private String status;
|
||||
private BigDecimal balance;
|
||||
private Integer creditScore;
|
||||
private LocalDateTime lastLogin;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.jiebanke.auth.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.jiebanke.auth.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
|
||||
/**
|
||||
* 根据用户名查找用户
|
||||
* @param username 用户名
|
||||
* @return 用户
|
||||
*/
|
||||
@Select("SELECT * FROM users WHERE username = #{username}")
|
||||
User selectByUsername(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* 根据邮箱查找用户
|
||||
* @param email 邮箱
|
||||
* @return 用户
|
||||
*/
|
||||
@Select("SELECT * FROM users WHERE email = #{email}")
|
||||
User selectByEmail(@Param("email") String email);
|
||||
|
||||
/**
|
||||
* 根据手机号查找用户
|
||||
* @param phone 手机号
|
||||
* @return 用户
|
||||
*/
|
||||
@Select("SELECT * FROM users WHERE phone = #{phone}")
|
||||
User selectByPhone(@Param("phone") String phone);
|
||||
|
||||
/**
|
||||
* 检查用户名是否存在
|
||||
* @param username 用户名
|
||||
* @return 是否存在
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM users WHERE username = #{username}")
|
||||
int existsByUsername(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* 检查邮箱是否存在
|
||||
* @param email 邮箱
|
||||
* @return 是否存在
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM users WHERE email = #{email}")
|
||||
int existsByEmail(@Param("email") String email);
|
||||
|
||||
/**
|
||||
* 检查手机号是否存在
|
||||
* @param phone 手机号
|
||||
* @return 是否存在
|
||||
*/
|
||||
@Select("SELECT COUNT(*) FROM users WHERE phone = #{phone}")
|
||||
int existsByPhone(@Param("phone") String phone);
|
||||
|
||||
/**
|
||||
* 更新最后登录时间
|
||||
* @param id 用户ID
|
||||
* @return 更新记录数
|
||||
*/
|
||||
@Update("UPDATE users SET last_login = NOW(), updated_at = NOW() WHERE id = #{id}")
|
||||
int updateLastLogin(@Param("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.jiebanke.auth.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 AuthRabbitMQService {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
// 发送登录成功消息
|
||||
public void sendLoginSuccessMessage(Long userId, String username, String ip) {
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("userId", userId);
|
||||
message.put("username", username);
|
||||
message.put("ip", ip);
|
||||
message.put("eventType", "USER_LOGIN_SUCCESS");
|
||||
|
||||
rabbitTemplate.convertAndSend(
|
||||
RabbitMQConfig.EXCHANGE_NAME,
|
||||
RabbitMQConfig.USER_ROUTING_KEY,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
// 发送登录失败消息
|
||||
public void sendLoginFailureMessage(String username, String ip, String reason) {
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("username", username);
|
||||
message.put("ip", ip);
|
||||
message.put("reason", reason);
|
||||
message.put("eventType", "USER_LOGIN_FAILURE");
|
||||
|
||||
rabbitTemplate.convertAndSend(
|
||||
RabbitMQConfig.EXCHANGE_NAME,
|
||||
RabbitMQConfig.USER_ROUTING_KEY,
|
||||
message
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.jiebanke.auth.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 AuthRedisService {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
// 缓存验证码
|
||||
public void cacheVerificationCode(String phone, String code) {
|
||||
redisTemplate.opsForValue().set("verification:code:" + phone, code, 5, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
// 获取缓存的验证码
|
||||
public String getCachedVerificationCode(String phone) {
|
||||
return (String) redisTemplate.opsForValue().get("verification:code:" + phone);
|
||||
}
|
||||
|
||||
// 删除缓存的验证码
|
||||
public void removeCachedVerificationCode(String phone) {
|
||||
redisTemplate.delete("verification:code:" + phone);
|
||||
}
|
||||
|
||||
// 缓存登录失败次数
|
||||
public void cacheLoginFailures(String identifier, Integer failures) {
|
||||
redisTemplate.opsForValue().set("login:failures:" + identifier, failures, 1, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
// 获取登录失败次数
|
||||
public Integer getLoginFailures(String identifier) {
|
||||
return (Integer) redisTemplate.opsForValue().get("login:failures:" + identifier);
|
||||
}
|
||||
|
||||
// 增加登录失败次数
|
||||
public void incrementLoginFailures(String identifier) {
|
||||
Integer failures = getLoginFailures(identifier);
|
||||
if (failures == null) {
|
||||
failures = 0;
|
||||
}
|
||||
redisTemplate.opsForValue().set("login:failures:" + identifier, failures + 1, 1, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
// 清除登录失败次数
|
||||
public void clearLoginFailures(String identifier) {
|
||||
redisTemplate.delete("login:failures:" + identifier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.jiebanke.auth.service;
|
||||
|
||||
import com.jiebanke.auth.entity.User;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AuthResult {
|
||||
private User user;
|
||||
private String token;
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.jiebanke.auth.service;
|
||||
|
||||
import com.jiebanke.auth.entity.User;
|
||||
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
* @param user 用户信息
|
||||
* @return 注册后的用户信息和Token
|
||||
*/
|
||||
AuthResult register(User user, String password);
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param username 用户名/邮箱/手机号
|
||||
* @param password 密码
|
||||
* @return 登录后的用户信息和Token
|
||||
*/
|
||||
AuthResult login(String username, String password);
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
* @param userId 用户ID
|
||||
* @return 用户信息
|
||||
*/
|
||||
User getCurrentUser(Long userId);
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
* @param userId 用户ID
|
||||
* @param user 更新的用户信息
|
||||
* @return 更新后的用户信息
|
||||
*/
|
||||
User updateProfile(Long userId, User user);
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param userId 用户ID
|
||||
* @param currentPassword 当前密码
|
||||
* @param newPassword 新密码
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
boolean changePassword(Long userId, String currentPassword, String newPassword);
|
||||
|
||||
/**
|
||||
* 微信登录/注册
|
||||
* @param code 微信授权码
|
||||
* @param userInfo 微信用户信息
|
||||
* @return 登录后的用户信息和Token
|
||||
*/
|
||||
AuthResult wechatLogin(String code, Object userInfo);
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
* @param username 用户名/邮箱/手机号
|
||||
* @param password 密码
|
||||
* @return 登录后的管理员信息和Token
|
||||
*/
|
||||
AuthResult adminLogin(String username, String password);
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package com.jiebanke.auth.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.jiebanke.auth.entity.User;
|
||||
import com.jiebanke.auth.mapper.UserMapper;
|
||||
import com.jiebanke.auth.service.AuthResult;
|
||||
import com.jiebanke.auth.service.AuthService;
|
||||
import com.jiebanke.auth.util.JwtUtil;
|
||||
import com.jiebanke.common.exception.BusinessException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AuthServiceImpl extends ServiceImpl<UserMapper, User> implements AuthService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@Autowired
|
||||
private BCryptPasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public AuthResult register(User user, String password) {
|
||||
// 检查用户名是否已存在
|
||||
if (userMapper.existsByUsername(user.getUsername()) > 0) {
|
||||
throw new BusinessException("用户名已存在");
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if (user.getEmail() != null && userMapper.existsByEmail(user.getEmail()) > 0) {
|
||||
throw new BusinessException("邮箱已存在");
|
||||
}
|
||||
|
||||
// 检查手机号是否已存在
|
||||
if (user.getPhone() != null && userMapper.existsByPhone(user.getPhone()) > 0) {
|
||||
throw new BusinessException("手机号已存在");
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
String hashedPassword = passwordEncoder.encode(password);
|
||||
user.setPassword(hashedPassword);
|
||||
|
||||
// 设置默认值
|
||||
if (user.getStatus() == null) {
|
||||
user.setStatus("active");
|
||||
}
|
||||
|
||||
// 创建新用户
|
||||
this.save(user);
|
||||
|
||||
// 生成Token
|
||||
String token = jwtUtil.generateToken(user.getId());
|
||||
|
||||
// 更新最后登录时间
|
||||
userMapper.updateLastLogin(user.getId());
|
||||
|
||||
// 创建返回结果
|
||||
AuthResult result = new AuthResult();
|
||||
result.setUser(user);
|
||||
result.setToken(token);
|
||||
result.setMessage("注册成功");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthResult login(String username, String password) {
|
||||
if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
|
||||
throw new BusinessException("用户名和密码不能为空");
|
||||
}
|
||||
|
||||
// 查找用户(支持用户名、邮箱、手机号登录)
|
||||
User user = userMapper.selectByUsername(username);
|
||||
if (user == null) {
|
||||
user = userMapper.selectByEmail(username);
|
||||
}
|
||||
if (user == null) {
|
||||
user = userMapper.selectByPhone(username);
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (!"active".equals(user.getStatus())) {
|
||||
throw new BusinessException("账户已被禁用");
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if (!passwordEncoder.matches(password, user.getPassword())) {
|
||||
throw new BusinessException("密码错误");
|
||||
}
|
||||
|
||||
// 生成Token
|
||||
String token = jwtUtil.generateToken(user.getId());
|
||||
|
||||
// 更新最后登录时间
|
||||
userMapper.updateLastLogin(user.getId());
|
||||
|
||||
// 创建返回结果
|
||||
AuthResult result = new AuthResult();
|
||||
result.setUser(user);
|
||||
result.setToken(token);
|
||||
result.setMessage("登录成功");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getCurrentUser(Long userId) {
|
||||
User user = this.getById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User updateProfile(Long userId, User user) {
|
||||
User existingUser = this.getById(userId);
|
||||
if (existingUser == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if (user.getRealName() != null) {
|
||||
existingUser.setRealName(user.getRealName());
|
||||
}
|
||||
if (user.getEmail() != null) {
|
||||
existingUser.setEmail(user.getEmail());
|
||||
}
|
||||
if (user.getPhone() != null) {
|
||||
existingUser.setPhone(user.getPhone());
|
||||
}
|
||||
|
||||
this.updateById(existingUser);
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changePassword(Long userId, String currentPassword, String newPassword) {
|
||||
if (currentPassword == null || currentPassword.isEmpty() || newPassword == null || newPassword.isEmpty()) {
|
||||
throw new BusinessException("当前密码和新密码不能为空");
|
||||
}
|
||||
|
||||
if (newPassword.length() < 6) {
|
||||
throw new BusinessException("新密码长度不能少于6位");
|
||||
}
|
||||
|
||||
User user = this.getById(userId);
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
// 验证当前密码
|
||||
if (!passwordEncoder.matches(currentPassword, user.getPassword())) {
|
||||
throw new BusinessException("当前密码错误");
|
||||
}
|
||||
|
||||
// 加密新密码
|
||||
String hashedPassword = passwordEncoder.encode(newPassword);
|
||||
user.setPassword(hashedPassword);
|
||||
|
||||
// 更新密码
|
||||
return this.updateById(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthResult wechatLogin(String code, Object userInfo) {
|
||||
if (code == null || code.isEmpty()) {
|
||||
throw new BusinessException("微信授权码不能为空");
|
||||
}
|
||||
|
||||
// 这里应该调用微信API获取openid和unionid
|
||||
// 模拟获取微信用户信息
|
||||
String openid = "mock_openid_" + System.currentTimeMillis();
|
||||
|
||||
// 查找是否已存在微信用户
|
||||
// 注意:在实际实现中,应该有一个专门的字段来存储微信openid
|
||||
User user = null;
|
||||
|
||||
if (user == null) {
|
||||
// 创建新用户(微信注册)
|
||||
user = new User();
|
||||
user.setUsername("wx_" + openid.substring(Math.max(0, openid.length() - 8)));
|
||||
String randomPassword = String.valueOf(System.currentTimeMillis()).substring(0, 8);
|
||||
String hashedPassword = passwordEncoder.encode(randomPassword);
|
||||
user.setPassword(hashedPassword);
|
||||
user.setRealName("微信用户");
|
||||
user.setStatus("active");
|
||||
|
||||
this.save(user);
|
||||
}
|
||||
|
||||
// 生成Token
|
||||
String token = jwtUtil.generateToken(user.getId());
|
||||
|
||||
// 创建返回结果
|
||||
AuthResult result = new AuthResult();
|
||||
result.setUser(user);
|
||||
result.setToken(token);
|
||||
result.setMessage("微信登录成功");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthResult adminLogin(String username, String password) {
|
||||
if (username == null || username.isEmpty() || password == null || password.isEmpty()) {
|
||||
throw new BusinessException("用户名和密码不能为空");
|
||||
}
|
||||
|
||||
// 查找用户(支持用户名、邮箱、手机号登录)
|
||||
User user = userMapper.selectByUsername(username);
|
||||
if (user == null) {
|
||||
user = userMapper.selectByEmail(username);
|
||||
}
|
||||
if (user == null) {
|
||||
user = userMapper.selectByPhone(username);
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
throw new BusinessException("用户不存在");
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (!"active".equals(user.getStatus())) {
|
||||
throw new BusinessException("账户已被禁用");
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if (!passwordEncoder.matches(password, user.getPassword())) {
|
||||
throw new BusinessException("密码错误");
|
||||
}
|
||||
|
||||
// 生成Token
|
||||
String token = jwtUtil.generateToken(user.getId());
|
||||
|
||||
// 更新最后登录时间
|
||||
userMapper.updateLastLogin(user.getId());
|
||||
|
||||
// 创建返回结果
|
||||
AuthResult result = new AuthResult();
|
||||
result.setUser(user);
|
||||
result.setToken(token);
|
||||
result.setMessage("管理员登录成功");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.jiebanke.auth.util;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Value("${jwt.secret:mySecretKey}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration:604800}")
|
||||
private Long expiration;
|
||||
|
||||
/**
|
||||
* 生成JWT Token
|
||||
* @param userId 用户ID
|
||||
* @return JWT Token
|
||||
*/
|
||||
public String generateToken(Long userId) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
return createToken(claims, userId.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT Token中提取用户ID
|
||||
* @param token JWT Token
|
||||
* @return 用户ID
|
||||
*/
|
||||
public Long extractUserId(String token) {
|
||||
return Long.valueOf(extractClaim(token, Claims::getSubject));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JWT Token是否有效
|
||||
* @param token JWT Token
|
||||
* @param userId 用户ID
|
||||
* @return 是否有效
|
||||
*/
|
||||
public Boolean validateToken(String token, Long userId) {
|
||||
final Long extractedUserId = extractUserId(token);
|
||||
return (extractedUserId.equals(userId) && !isTokenExpired(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT Token中提取声明
|
||||
* @param token JWT Token
|
||||
* @param claimsResolver 声明解析器
|
||||
* @param <T> 声明类型
|
||||
* @return 声明值
|
||||
*/
|
||||
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = extractAllClaims(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT Token中提取所有声明
|
||||
* @param token JWT Token
|
||||
* @return 所有声明
|
||||
*/
|
||||
private Claims extractAllClaims(String token) {
|
||||
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查JWT Token是否过期
|
||||
* @param token JWT Token
|
||||
* @return 是否过期
|
||||
*/
|
||||
private Boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT Token中提取过期时间
|
||||
* @param token JWT Token
|
||||
* @return 过期时间
|
||||
*/
|
||||
public Date extractExpiration(String token) {
|
||||
return extractClaim(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建JWT Token
|
||||
* @param claims 声明
|
||||
* @param subject 主题
|
||||
* @return JWT Token
|
||||
*/
|
||||
private String createToken(Map<String, Object> claims, String subject) {
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(new Date(System.currentTimeMillis()))
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
|
||||
.signWith(SignatureAlgorithm.HS512, secret)
|
||||
.compact();
|
||||
}
|
||||
}
|
||||
36
backend-java/auth-service/src/main/resources/application.yml
Normal file
36
backend-java/auth-service/src/main/resources/application.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: auth-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/
|
||||
|
||||
jwt:
|
||||
secret: mySecretKey
|
||||
expiration: 604800
|
||||
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: auto
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.jiebanke.auth.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 AuthRabbitMQServiceTest {
|
||||
|
||||
@Mock
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@InjectMocks
|
||||
private AuthRabbitMQService authRabbitMQService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendLoginSuccessMessage() {
|
||||
Long userId = 1L;
|
||||
String username = "testUser";
|
||||
String ip = "127.0.0.1";
|
||||
|
||||
authRabbitMQService.sendLoginSuccessMessage(userId, username, ip);
|
||||
|
||||
verify(rabbitTemplate).convertAndSend(
|
||||
RabbitMQConfig.EXCHANGE_NAME,
|
||||
RabbitMQConfig.USER_ROUTING_KEY,
|
||||
anyMap()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendLoginFailureMessage() {
|
||||
String username = "testUser";
|
||||
String ip = "127.0.0.1";
|
||||
String reason = "Invalid credentials";
|
||||
|
||||
authRabbitMQService.sendLoginFailureMessage(username, ip, reason);
|
||||
|
||||
verify(rabbitTemplate).convertAndSend(
|
||||
RabbitMQConfig.EXCHANGE_NAME,
|
||||
RabbitMQConfig.USER_ROUTING_KEY,
|
||||
anyMap()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.jiebanke.auth.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 AuthRedisServiceTest {
|
||||
|
||||
@Mock
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Mock
|
||||
private ValueOperations<String, Object> valueOperations;
|
||||
|
||||
@InjectMocks
|
||||
private AuthRedisService authRedisService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCacheVerificationCode() {
|
||||
String phone = "13800138000";
|
||||
String code = "123456";
|
||||
|
||||
authRedisService.cacheVerificationCode(phone, code);
|
||||
|
||||
verify(valueOperations).set(eq("verification:code:" + phone), eq(code), anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetCachedVerificationCode() {
|
||||
String phone = "13800138000";
|
||||
String code = "123456";
|
||||
when(valueOperations.get("verification:code:" + phone)).thenReturn(code);
|
||||
|
||||
String result = authRedisService.getCachedVerificationCode(phone);
|
||||
|
||||
assertEquals(code, result);
|
||||
verify(valueOperations).get("verification:code:" + phone);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveCachedVerificationCode() {
|
||||
String phone = "13800138000";
|
||||
|
||||
authRedisService.removeCachedVerificationCode(phone);
|
||||
|
||||
verify(redisTemplate).delete("verification:code:" + phone);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCacheLoginFailures() {
|
||||
String identifier = "testUser";
|
||||
Integer failures = 3;
|
||||
|
||||
authRedisService.cacheLoginFailures(identifier, failures);
|
||||
|
||||
verify(valueOperations).set(eq("login:failures:" + identifier), eq(failures), anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetLoginFailures() {
|
||||
String identifier = "testUser";
|
||||
Integer failures = 3;
|
||||
when(valueOperations.get("login:failures:" + identifier)).thenReturn(failures);
|
||||
|
||||
Integer result = authRedisService.getLoginFailures(identifier);
|
||||
|
||||
assertEquals(failures, result);
|
||||
verify(valueOperations).get("login:failures:" + identifier);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIncrementLoginFailures() {
|
||||
String identifier = "testUser";
|
||||
when(valueOperations.get("login:failures:" + identifier)).thenReturn(null);
|
||||
|
||||
authRedisService.incrementLoginFailures(identifier);
|
||||
|
||||
verify(valueOperations).set(eq("login:failures:" + identifier), eq(1), anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIncrementLoginFailuresWithExistingValue() {
|
||||
String identifier = "testUser";
|
||||
when(valueOperations.get("login:failures:" + identifier)).thenReturn(2);
|
||||
|
||||
authRedisService.incrementLoginFailures(identifier);
|
||||
|
||||
verify(valueOperations).set(eq("login:failures:" + identifier), eq(3), anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClearLoginFailures() {
|
||||
String identifier = "testUser";
|
||||
|
||||
authRedisService.clearLoginFailures(identifier);
|
||||
|
||||
verify(redisTemplate).delete("login:failures:" + identifier);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user