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);
}
}

View File

@@ -37,4 +37,3 @@
</delete>
</mapper>

View File

@@ -1,20 +1,24 @@
<?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.gameplatform.server.mapper.admin.AdminUserMapper">
<resultMap id="AdminUserMap" type="com.gameplatform.server.model.entity.admin.AdminUser">
<mapper namespace="com.gameplatform.server.mapper.account.UserAccountMapper">
<resultMap id="UserAccountMap" type="com.gameplatform.server.model.entity.account.UserAccount">
<id property="id" column="id" />
<result property="userType" column="user_type" />
<result property="username" column="username" />
<result property="displayName" column="display_name" />
<result property="passwordHash" column="password_hash" />
<result property="role" column="role" />
<result property="status" column="status" />
<result property="pointsBalance" column="points_balance" />
<result property="createdAt" column="created_at" />
<result property="updatedAt" column="updated_at" />
</resultMap>
<select id="findByUsername" parameterType="string" resultMap="AdminUserMap">
SELECT id, username, password_hash, role, status, created_at, updated_at
FROM admin_user
<select id="findByUsernameAndType" resultMap="UserAccountMap">
SELECT id, user_type, username, display_name, password_hash, role, status, points_balance, created_at, updated_at
FROM user_account
WHERE username = #{username}
AND user_type = #{userType}
LIMIT 1
</select>
</mapper>

View File

@@ -1,22 +0,0 @@
<?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.gameplatform.server.mapper.agent.AgentMapper">
<resultMap id="AgentMap" type="com.gameplatform.server.model.entity.agent.Agent">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="loginAccount" column="login_account" />
<result property="passwordHash" column="password_hash" />
<result property="status" column="status" />
<result property="pointsBalance" column="points_balance" />
<result property="createdAt" column="created_at" />
<result property="updatedAt" column="updated_at" />
</resultMap>
<select id="findByLoginAccount" parameterType="string" resultMap="AgentMap">
SELECT id, name, login_account, password_hash, status, points_balance, created_at, updated_at
FROM agent
WHERE login_account = #{loginAccount}
LIMIT 1
</select>
</mapper>