Enhance authentication logging and update MyBatis configuration
This commit is contained in:
@@ -41,7 +41,7 @@
|
|||||||
- 建议:后续改为 Flyway 迁移(`V001__init.sql` 起步),避免手工执行 SQL
|
- 建议:后续改为 Flyway 迁移(`V001__init.sql` 起步),避免手工执行 SQL
|
||||||
|
|
||||||
## 安全与认证
|
## 安全与认证
|
||||||
- 登录:`POST /api/auth/login`(`userType=admin|agent` + `username` + `password`)
|
- 登录:`POST /api/auth/login`(`username` + `password`,角色自动识别)
|
||||||
- 自我信息:`GET /api/auth/me`(Authorization: Bearer <JWT>)
|
- 自我信息:`GET /api/auth/me`(Authorization: Bearer <JWT>)
|
||||||
- JWT:HS256;配置在 `security.jwt.*`(`application.yml`)
|
- JWT:HS256;配置在 `security.jwt.*`(`application.yml`)
|
||||||
- 密码:默认 `BCrypt`;兼容 `PLAIN:` 前缀以便初始化迁移
|
- 密码:默认 `BCrypt`;兼容 `PLAIN:` 前缀以便初始化迁移
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
- [ ] 乐观锁/并发扣点优化、观测/告警
|
- [ ] 乐观锁/并发扣点优化、观测/告警
|
||||||
|
|
||||||
## 接口清单(节选)
|
## 接口清单(节选)
|
||||||
- `POST /api/auth/login`:登录获取 JWT
|
- `POST /api/auth/login`:登录获取 JWT(仅需 `username`、`password`)
|
||||||
- `GET /api/auth/me`:当前用户信息
|
- `GET /api/auth/me`:当前用户信息
|
||||||
- `GET /api/link/{codeNo}`:查询链接元数据(待实现)
|
- `GET /api/link/{codeNo}`:查询链接元数据(待实现)
|
||||||
- `POST /api/link/{codeNo}/select-region`:选择区服(待实现)
|
- `POST /api/link/{codeNo}/select-region`:选择区服(待实现)
|
||||||
@@ -119,4 +119,3 @@
|
|||||||
- 默认管理员以 `PLAIN:` 存储密码,仅用于初始化。上线前必须改为 BCrypt:
|
- 默认管理员以 `PLAIN:` 存储密码,仅用于初始化。上线前必须改为 BCrypt:
|
||||||
- 方案:使用项目内 `BCryptPasswordEncoder` 生成哈希,更新 `user_account.password_hash`
|
- 方案:使用项目内 `BCryptPasswordEncoder` 生成哈希,更新 `user_account.password_hash`
|
||||||
- 统一返回体与 RBAC 规则将在后续迭代落地
|
- 统一返回体与 RBAC 规则将在后续迭代落地
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import com.gameplatform.server.model.dto.auth.LoginRequest;
|
|||||||
import com.gameplatform.server.model.dto.auth.LoginResponse;
|
import com.gameplatform.server.model.dto.auth.LoginResponse;
|
||||||
import com.gameplatform.server.security.JwtService;
|
import com.gameplatform.server.security.JwtService;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -13,6 +15,7 @@ import reactor.core.publisher.Mono;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/auth")
|
@RequestMapping("/api/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AuthController.class);
|
||||||
|
|
||||||
private final com.gameplatform.server.service.auth.AuthService authService;
|
private final com.gameplatform.server.service.auth.AuthService authService;
|
||||||
private final JwtService jwtService;
|
private final JwtService jwtService;
|
||||||
@@ -25,6 +28,7 @@ public class AuthController {
|
|||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
@ResponseStatus(HttpStatus.OK)
|
@ResponseStatus(HttpStatus.OK)
|
||||||
public Mono<LoginResponse> login(@Valid @RequestBody LoginRequest req) {
|
public Mono<LoginResponse> login(@Valid @RequestBody LoginRequest req) {
|
||||||
|
log.info("/api/auth/login called username={}", req.getUsername());
|
||||||
return authService.login(req);
|
return authService.login(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +36,7 @@ public class AuthController {
|
|||||||
public Mono<Object> me(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
|
public Mono<Object> me(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
|
||||||
String token = authorization != null && authorization.startsWith("Bearer ") ? authorization.substring(7) : authorization;
|
String token = authorization != null && authorization.startsWith("Bearer ") ? authorization.substring(7) : authorization;
|
||||||
return Mono.fromCallable(() -> jwtService.parse(token))
|
return Mono.fromCallable(() -> jwtService.parse(token))
|
||||||
|
.doOnError(e -> log.warn("/api/auth/me parse token error: {}", e.toString()))
|
||||||
.map(this::claimsToMe);
|
.map(this::claimsToMe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
package com.gameplatform.server.exception;
|
package com.gameplatform.server.exception;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
import org.springframework.web.server.ServerWebInputException;
|
||||||
|
import jakarta.validation.ConstraintViolation;
|
||||||
|
import jakarta.validation.ConstraintViolationException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
@@ -10,22 +17,61 @@ import java.util.LinkedHashMap;
|
|||||||
|
|
||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
public class GlobalExceptionHandler {
|
public class GlobalExceptionHandler {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||||
|
|
||||||
@ExceptionHandler(IllegalArgumentException.class)
|
@ExceptionHandler(IllegalArgumentException.class)
|
||||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
public Object handleBadRequest(IllegalArgumentException e) {
|
public Object handleBadRequest(IllegalArgumentException e) {
|
||||||
|
log.info("400 BadRequest: {}", e.getMessage());
|
||||||
return body(HttpStatus.BAD_REQUEST.value(), e.getMessage());
|
return body(HttpStatus.BAD_REQUEST.value(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(IllegalStateException.class)
|
@ExceptionHandler(IllegalStateException.class)
|
||||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||||
public Object handleForbidden(IllegalStateException e) {
|
public Object handleForbidden(IllegalStateException e) {
|
||||||
|
log.info("403 Forbidden: {}", e.getMessage());
|
||||||
return body(HttpStatus.FORBIDDEN.value(), e.getMessage());
|
return body(HttpStatus.FORBIDDEN.value(), e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(WebExchangeBindException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public Object handleBindException(WebExchangeBindException e) {
|
||||||
|
log.info("400 ValidationError: {}", e.getMessage());
|
||||||
|
var details = new java.util.LinkedHashMap<String, Object>();
|
||||||
|
e.getFieldErrors().forEach(fe -> details.put(fe.getField(), fe.getDefaultMessage()));
|
||||||
|
e.getGlobalErrors().forEach(ge -> details.put(ge.getObjectName(), ge.getDefaultMessage()));
|
||||||
|
return body(HttpStatus.BAD_REQUEST.value(), "参数校验失败", details);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ConstraintViolationException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public Object handleConstraintViolation(ConstraintViolationException e) {
|
||||||
|
log.info("400 ConstraintViolation: {}", e.getMessage());
|
||||||
|
var details = new java.util.LinkedHashMap<String, Object>();
|
||||||
|
for (ConstraintViolation<?> v : e.getConstraintViolations()) {
|
||||||
|
details.put(String.valueOf(v.getPropertyPath()), v.getMessage());
|
||||||
|
}
|
||||||
|
return body(HttpStatus.BAD_REQUEST.value(), "参数校验失败", details);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ServerWebInputException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public Object handleInput(ServerWebInputException e) {
|
||||||
|
log.info("400 InputError: {}", e.getMessage());
|
||||||
|
return body(HttpStatus.BAD_REQUEST.value(), "请求解析失败: " + e.getReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ResponseStatusException.class)
|
||||||
|
public Object handleRse(ResponseStatusException e) {
|
||||||
|
var status = e.getStatusCode();
|
||||||
|
log.info("{} ResponseStatusException: {}", status, e.getReason());
|
||||||
|
return body(status.value(), e.getReason());
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
public Object handleOther(Exception e) {
|
public Object handleOther(Exception e) {
|
||||||
|
log.error("500 InternalServerError", e);
|
||||||
return body(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器内部错误");
|
return body(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器内部错误");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,5 +82,13 @@ public class GlobalExceptionHandler {
|
|||||||
put("timestamp", Instant.now().toString());
|
put("timestamp", Instant.now().toString());
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
private Object body(int code, String message, Object details) {
|
||||||
|
return new LinkedHashMap<>() {{
|
||||||
|
put("code", code);
|
||||||
|
put("message", message);
|
||||||
|
put("details", details);
|
||||||
|
put("timestamp", Instant.now().toString());
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ import org.apache.ibatis.annotations.Param;
|
|||||||
public interface UserAccountMapper {
|
public interface UserAccountMapper {
|
||||||
UserAccount findByUsernameAndType(@Param("username") String username,
|
UserAccount findByUsernameAndType(@Param("username") String username,
|
||||||
@Param("userType") String userType);
|
@Param("userType") String userType);
|
||||||
|
UserAccount findByUsername(@Param("username") String username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,13 @@ package com.gameplatform.server.model.dto.auth;
|
|||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
public class LoginRequest {
|
public class LoginRequest {
|
||||||
// userType: admin | agent
|
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String userType;
|
private String username; // 统一登录名(admin/agent 共用)
|
||||||
@NotBlank
|
|
||||||
private String username; // admin: username, agent: loginAccount
|
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
public String getUserType() { return userType; }
|
|
||||||
public void setUserType(String userType) { this.userType = userType; }
|
|
||||||
public String getUsername() { return username; }
|
public String getUsername() { return username; }
|
||||||
public void setUsername(String username) { this.username = username; }
|
public void setUsername(String username) { this.username = username; }
|
||||||
public String getPassword() { return password; }
|
public String getPassword() { return password; }
|
||||||
public void setPassword(String password) { this.password = password; }
|
public void setPassword(String password) { this.password = password; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import io.jsonwebtoken.Jwts;
|
|||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.io.Decoders;
|
import io.jsonwebtoken.io.Decoders;
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class JwtService {
|
public class JwtService {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(JwtService.class);
|
||||||
private final SecretKey key;
|
private final SecretKey key;
|
||||||
private final long accessTokenMinutes;
|
private final long accessTokenMinutes;
|
||||||
|
|
||||||
@@ -39,11 +41,14 @@ public class JwtService {
|
|||||||
if (extra != null) {
|
if (extra != null) {
|
||||||
extra.forEach(builder::claim);
|
extra.forEach(builder::claim);
|
||||||
}
|
}
|
||||||
return builder.signWith(key, SignatureAlgorithm.HS256).compact();
|
String token = builder.signWith(key, SignatureAlgorithm.HS256).compact();
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("JWT generated subject={}, userType={}, userId={}, username={} expInMin={}", subject, userType, userId, username, accessTokenMinutes);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public io.jsonwebtoken.Claims parse(String token) {
|
public io.jsonwebtoken.Claims parse(String token) {
|
||||||
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
|
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.gameplatform.server.model.dto.auth.LoginRequest;
|
|||||||
import com.gameplatform.server.model.dto.auth.LoginResponse;
|
import com.gameplatform.server.model.dto.auth.LoginResponse;
|
||||||
import com.gameplatform.server.model.entity.account.UserAccount;
|
import com.gameplatform.server.model.entity.account.UserAccount;
|
||||||
import com.gameplatform.server.security.JwtService;
|
import com.gameplatform.server.security.JwtService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
@@ -14,6 +16,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class AuthService {
|
public class AuthService {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AuthService.class);
|
||||||
private final UserAccountMapper userAccountMapper;
|
private final UserAccountMapper userAccountMapper;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final JwtService jwtService;
|
private final JwtService jwtService;
|
||||||
@@ -27,22 +30,25 @@ public class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Mono<LoginResponse> login(LoginRequest req) {
|
public Mono<LoginResponse> login(LoginRequest req) {
|
||||||
String userType = normalizeType(req.getUserType());
|
log.info("login attempt username={}", req.getUsername());
|
||||||
return Mono.fromCallable(() -> userAccountMapper.findByUsernameAndType(req.getUsername(), userType))
|
long start = System.currentTimeMillis();
|
||||||
|
return Mono.fromCallable(() -> userAccountMapper.findByUsername(req.getUsername()))
|
||||||
.subscribeOn(Schedulers.boundedElastic())
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
.flatMap(acc -> validatePasswordAndBuild(acc, userType, req.getPassword()));
|
.doOnNext(acc -> {
|
||||||
|
if (acc == null) {
|
||||||
|
log.warn("login account not found username={}", req.getUsername());
|
||||||
|
} else {
|
||||||
|
log.debug("login account loaded id={}, status={}, role={} userType={}", acc.getId(), acc.getStatus(), acc.getRole(), acc.getUserType());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatMap(acc -> validatePasswordAndBuild(acc, req.getPassword()))
|
||||||
|
.doOnSuccess(r -> log.info("login success username={}, tookMs={}", req.getUsername(), (System.currentTimeMillis()-start)))
|
||||||
|
.doOnError(e -> log.warn("login failed username={}, err={}, tookMs={}", req.getUsername(), e.toString(), (System.currentTimeMillis()-start)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizeType(String t) {
|
private Mono<LoginResponse> validatePasswordAndBuild(UserAccount acc, String rawPwd) {
|
||||||
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) {
|
if (acc == null || acc.getPasswordHash() == null) {
|
||||||
|
log.debug("validatePasswordAndBuild: account missing or no password hash");
|
||||||
return Mono.error(new IllegalArgumentException("用户名或密码错误"));
|
return Mono.error(new IllegalArgumentException("用户名或密码错误"));
|
||||||
}
|
}
|
||||||
boolean ok;
|
boolean ok;
|
||||||
@@ -54,19 +60,24 @@ public class AuthService {
|
|||||||
} else {
|
} else {
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
if (!ok) return Mono.error(new IllegalArgumentException("用户名或密码错误"));
|
if (!ok) {
|
||||||
|
log.debug("validatePasswordAndBuild: password not match for user id={}", acc.getId());
|
||||||
|
return Mono.error(new IllegalArgumentException("用户名或密码错误"));
|
||||||
|
}
|
||||||
if (!"ENABLED".equalsIgnoreCase(acc.getStatus())) {
|
if (!"ENABLED".equalsIgnoreCase(acc.getStatus())) {
|
||||||
|
log.debug("validatePasswordAndBuild: account disabled id={}", acc.getId());
|
||||||
return Mono.error(new IllegalStateException("账户已禁用"));
|
return Mono.error(new IllegalStateException("账户已禁用"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String userType = acc.getUserType() == null ? "agent" : acc.getUserType().toLowerCase();
|
||||||
String token = jwtService.generateToken(
|
String token = jwtService.generateToken(
|
||||||
userType.toLowerCase() + ":" + acc.getId(),
|
userType + ":" + acc.getId(),
|
||||||
userType.toLowerCase(), acc.getId(), acc.getUsername(),
|
userType, acc.getId(), acc.getUsername(),
|
||||||
userType.equals("ADMIN") ? Map.of("role", acc.getRole()) : Map.of("displayName", acc.getDisplayName())
|
"admin".equals(userType) ? Map.of("role", acc.getRole()) : Map.of("displayName", acc.getDisplayName())
|
||||||
);
|
);
|
||||||
LoginResponse resp = new LoginResponse();
|
LoginResponse resp = new LoginResponse();
|
||||||
resp.setAccessToken(token);
|
resp.setAccessToken(token);
|
||||||
resp.setUserType(userType.toLowerCase());
|
resp.setUserType(userType);
|
||||||
resp.setUserId(acc.getId());
|
resp.setUserId(acc.getId());
|
||||||
resp.setUsername(acc.getUsername());
|
resp.setUsername(acc.getUsername());
|
||||||
resp.setExpiresIn(60L * 30);
|
resp.setExpiresIn(60L * 30);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ spring:
|
|||||||
connection-timeout: 30000
|
connection-timeout: 30000
|
||||||
|
|
||||||
mybatis:
|
mybatis:
|
||||||
mapper-locations: classpath:mapper/*.xml
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
type-aliases-package: com.gameplatform.server.model
|
type-aliases-package: com.gameplatform.server.model
|
||||||
configuration:
|
configuration:
|
||||||
map-underscore-to-camel-case: true
|
map-underscore-to-camel-case: true
|
||||||
@@ -31,6 +31,9 @@ logging:
|
|||||||
level:
|
level:
|
||||||
root: info
|
root: info
|
||||||
com.gameplatform.server: debug
|
com.gameplatform.server: debug
|
||||||
|
org.mybatis: debug
|
||||||
|
org.apache.ibatis: debug
|
||||||
|
com.zaxxer.hikari: info
|
||||||
|
|
||||||
security:
|
security:
|
||||||
jwt:
|
jwt:
|
||||||
|
|||||||
@@ -21,5 +21,11 @@
|
|||||||
AND user_type = #{userType}
|
AND user_type = #{userType}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
</mapper>
|
|
||||||
|
|
||||||
|
<select id="findByUsername" 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}
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ spring:
|
|||||||
connection-timeout: 30000
|
connection-timeout: 30000
|
||||||
|
|
||||||
mybatis:
|
mybatis:
|
||||||
mapper-locations: classpath:mapper/*.xml
|
mapper-locations: classpath:mapper/**/*.xml
|
||||||
type-aliases-package: com.gameplatform.server.model
|
type-aliases-package: com.gameplatform.server.model
|
||||||
configuration:
|
configuration:
|
||||||
map-underscore-to-camel-case: true
|
map-underscore-to-camel-case: true
|
||||||
@@ -31,6 +31,9 @@ logging:
|
|||||||
level:
|
level:
|
||||||
root: info
|
root: info
|
||||||
com.gameplatform.server: debug
|
com.gameplatform.server: debug
|
||||||
|
org.mybatis: debug
|
||||||
|
org.apache.ibatis: debug
|
||||||
|
com.zaxxer.hikari: info
|
||||||
|
|
||||||
security:
|
security:
|
||||||
jwt:
|
jwt:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -21,5 +21,11 @@
|
|||||||
AND user_type = #{userType}
|
AND user_type = #{userType}
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
</select>
|
</select>
|
||||||
</mapper>
|
|
||||||
|
|
||||||
|
<select id="findByUsername" 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}
|
||||||
|
LIMIT 1
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
|
|||||||
Reference in New Issue
Block a user