Refactor authentication logic to unify user account handling and update database schema for user accounts

This commit is contained in:
zyh
2025-08-24 15:54:21 +08:00
parent be437a360d
commit c65c03b933
22 changed files with 256 additions and 288 deletions

View File

@@ -10,7 +10,7 @@ import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/users")
public class UserController {
public class UserController {
private final UserService userService;
public UserController(UserService userService) {

View File

@@ -40,9 +40,8 @@ public class AuthController {
put("userType", c.get("userType"));
put("userId", c.get("userId"));
put("username", c.get("username"));
put("role", c.get("role"));
if ("admin".equals(c.get("userType"))) put("role", c.get("role"));
put("exp", c.getExpiration());
}};
}
}

View File

@@ -0,0 +1,10 @@
package com.gameplatform.server.mapper.account;
import com.gameplatform.server.model.entity.account.UserAccount;
import org.apache.ibatis.annotations.Param;
public interface UserAccountMapper {
UserAccount findByUsernameAndType(@Param("username") String username,
@Param("userType") String userType);
}

View File

@@ -1,9 +0,0 @@
package com.gameplatform.server.mapper.admin;
import com.gameplatform.server.model.entity.admin.AdminUser;
import org.apache.ibatis.annotations.Param;
public interface AdminUserMapper {
AdminUser findByUsername(@Param("username") String username);
}

View File

@@ -1,9 +0,0 @@
package com.gameplatform.server.mapper.agent;
import com.gameplatform.server.model.entity.agent.Agent;
import org.apache.ibatis.annotations.Param;
public interface AgentMapper {
Agent findByLoginAccount(@Param("loginAccount") String loginAccount);
}

View File

@@ -1,26 +1,35 @@
package com.gameplatform.server.model.entity.admin;
package com.gameplatform.server.model.entity.account;
import java.time.LocalDateTime;
public class AdminUser {
public class UserAccount {
private Long id;
private String username;
private String passwordHash;
private String role; // SUPER / ADMIN
private String userType; // ADMIN | AGENT
private String username; // 登录名admin/agent 共用
private String displayName; // 显示名称agent 可用
private String passwordHash; // BCrypt PLAIN:xxx初始化用
private String role; // ADMIN 使用SUPER / ADMIN
private String status; // ENABLED / DISABLED
private Long pointsBalance; // AGENT 使用
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUserType() { return userType; }
public void setUserType(String userType) { this.userType = userType; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public Long getPointsBalance() { return pointsBalance; }
public void setPointsBalance(Long pointsBalance) { this.pointsBalance = pointsBalance; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }

View File

@@ -1,32 +0,0 @@
package com.gameplatform.server.model.entity.agent;
import java.time.LocalDateTime;
public class Agent {
private Long id;
private String name;
private String loginAccount;
private String passwordHash;
private String status; // ENABLED / DISABLED
private Long pointsBalance;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getLoginAccount() { return loginAccount; }
public void setLoginAccount(String loginAccount) { this.loginAccount = loginAccount; }
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public Long getPointsBalance() { return pointsBalance; }
public void setPointsBalance(Long pointsBalance) { this.pointsBalance = pointsBalance; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}

View File

@@ -1,11 +1,9 @@
package com.gameplatform.server.service.auth;
import com.gameplatform.server.mapper.admin.AdminUserMapper;
import com.gameplatform.server.mapper.agent.AgentMapper;
import com.gameplatform.server.mapper.account.UserAccountMapper;
import com.gameplatform.server.model.dto.auth.LoginRequest;
import com.gameplatform.server.model.dto.auth.LoginResponse;
import com.gameplatform.server.model.entity.admin.AdminUser;
import com.gameplatform.server.model.entity.agent.Agent;
import com.gameplatform.server.model.entity.account.UserAccount;
import com.gameplatform.server.security.JwtService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@@ -16,78 +14,62 @@ import java.util.Map;
@Service
public class AuthService {
private final AdminUserMapper adminUserMapper;
private final AgentMapper agentMapper;
private final UserAccountMapper userAccountMapper;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
public AuthService(AdminUserMapper adminUserMapper,
AgentMapper agentMapper,
public AuthService(UserAccountMapper userAccountMapper,
PasswordEncoder passwordEncoder,
JwtService jwtService) {
this.adminUserMapper = adminUserMapper;
this.agentMapper = agentMapper;
this.userAccountMapper = userAccountMapper;
this.passwordEncoder = passwordEncoder;
this.jwtService = jwtService;
}
public Mono<LoginResponse> login(LoginRequest req) {
String userType = req.getUserType().toLowerCase();
if ("admin".equals(userType)) {
return Mono.fromCallable(() -> adminUserMapper.findByUsername(req.getUsername()))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(admin -> validateAdminPassword(admin, req.getPassword()));
} else if ("agent".equals(userType)) {
return Mono.fromCallable(() -> agentMapper.findByLoginAccount(req.getUsername()))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(agent -> validateAgentPassword(agent, req.getPassword()));
String userType = normalizeType(req.getUserType());
return Mono.fromCallable(() -> userAccountMapper.findByUsernameAndType(req.getUsername(), userType))
.subscribeOn(Schedulers.boundedElastic())
.flatMap(acc -> validatePasswordAndBuild(acc, userType, req.getPassword()));
}
private String normalizeType(String t) {
if (t == null) return "";
t = t.trim().toLowerCase();
if ("admin".equals(t)) return "ADMIN";
if ("agent".equals(t)) return "AGENT";
throw new IllegalArgumentException("unsupported userType: " + t);
}
private Mono<LoginResponse> validatePasswordAndBuild(UserAccount acc, String userType, String rawPwd) {
if (acc == null || acc.getPasswordHash() == null) {
return Mono.error(new IllegalArgumentException("用户名或密码错误"));
}
boolean ok;
String hash = acc.getPasswordHash();
if (hash.startsWith("$2a$") || hash.startsWith("$2b$") || hash.startsWith("$2y$")) {
ok = passwordEncoder.matches(rawPwd, hash);
} else if (hash.startsWith("PLAIN:")) {
ok = rawPwd.equals(hash.substring(6));
} else {
return Mono.error(new IllegalArgumentException("unsupported userType: " + userType));
ok = false;
}
}
private Mono<LoginResponse> validateAdminPassword(AdminUser admin, String rawPassword) {
if (admin == null || admin.getPasswordHash() == null) {
return Mono.error(new IllegalArgumentException("用户名或密码错误"));
}
boolean ok = passwordEncoder.matches(rawPassword, admin.getPasswordHash());
if (!ok) return Mono.error(new IllegalArgumentException("用户名或密码错误"));
if (!"ENABLED".equalsIgnoreCase(admin.getStatus())) {
if (!"ENABLED".equalsIgnoreCase(acc.getStatus())) {
return Mono.error(new IllegalStateException("账户已禁用"));
}
String token = jwtService.generateToken(
"admin:" + admin.getId(),
"admin", admin.getId(), admin.getUsername(), Map.of("role", admin.getRole())
userType.toLowerCase() + ":" + acc.getId(),
userType.toLowerCase(), acc.getId(), acc.getUsername(),
userType.equals("ADMIN") ? Map.of("role", acc.getRole()) : Map.of("displayName", acc.getDisplayName())
);
LoginResponse resp = new LoginResponse();
resp.setAccessToken(token);
resp.setUserType("admin");
resp.setUserId(admin.getId());
resp.setUsername(admin.getUsername());
resp.setExpiresIn(60L * 30); // align with default 30min
return Mono.just(resp);
}
private Mono<LoginResponse> validateAgentPassword(Agent agent, String rawPassword) {
if (agent == null || agent.getPasswordHash() == null) {
return Mono.error(new IllegalArgumentException("用户名或密码错误"));
}
boolean ok = passwordEncoder.matches(rawPassword, agent.getPasswordHash());
if (!ok) return Mono.error(new IllegalArgumentException("用户名或密码错误"));
if (!"ENABLED".equalsIgnoreCase(agent.getStatus())) {
return Mono.error(new IllegalStateException("账户已禁用"));
}
String token = jwtService.generateToken(
"agent:" + agent.getId(),
"agent", agent.getId(), agent.getLoginAccount(), Map.of("name", agent.getName())
);
LoginResponse resp = new LoginResponse();
resp.setAccessToken(token);
resp.setUserType("agent");
resp.setUserId(agent.getId());
resp.setUsername(agent.getLoginAccount());
resp.setUserType(userType.toLowerCase());
resp.setUserId(acc.getId());
resp.setUsername(acc.getUsername());
resp.setExpiresIn(60L * 30);
return Mono.just(resp);
}
}