Refactor user account management by replacing User entity with UserAccount, updating UserController and UserService for CRUD operations, and modifying MyBatis mappers accordingly.

This commit is contained in:
zyh
2025-08-24 17:42:47 +08:00
parent 4cfd19195f
commit f37159e1fc
36 changed files with 327 additions and 330 deletions

5
.cursor/rules/zh.mdc Normal file
View File

@@ -0,0 +1,5 @@
---
description:
globs: 中文回答我
alwaysApply: true
---

View File

@@ -1,133 +1,149 @@
-- =============================================================================
-- 上号系统 - 数据库结构 (MySQL 8+)
-- =============================================================================
/*
Navicat Premium Dump SQL
-- 可选:创建并使用独立库
CREATE DATABASE IF NOT EXISTS login_task_db
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_0900_ai_ci;
USE login_task_db;
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80043 (8.0.43)
Source Host : localhost:3306
Source Schema : login_task_db
Target Server Type : MySQL
Target Server Version : 80043 (8.0.43)
File Encoding : 65001
Date: 24/08/2025 17:24:44
*/
SET NAMES utf8mb4;
SET sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO';
SET FOREIGN_KEY_CHECKS = 0;
-- -----------------------------------------------------------------------------
-- 1) 统一账户表(管理员/代理商共用)
-- 用 user_type 区分ADMIN | AGENT
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS user_account (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_type ENUM('ADMIN','AGENT') NOT NULL,
username VARCHAR(64) NOT NULL UNIQUE, -- 登录名(两类共用)
display_name VARCHAR(100) NULL, -- 展示名AGENT 可用)
password_hash VARCHAR(120) NOT NULL, -- 建议存储 BCrypt或临时 PLAIN:<pwd> 便于初始化)
role ENUM('SUPER','ADMIN') NULL, -- 仅 ADMIN 使用
status ENUM('ENABLED','DISABLED') NOT NULL DEFAULT 'ENABLED',
points_balance BIGINT UNSIGNED NOT NULL DEFAULT 0, -- 仅 AGENT 使用
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
CONSTRAINT chk_points_nonneg CHECK (points_balance >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for agent_points_tx
-- ----------------------------
DROP TABLE IF EXISTS `agent_points_tx`;
CREATE TABLE `agent_points_tx` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
`account_id` bigint UNSIGNED NOT NULL,
`type` enum('ADD','DEDUCT') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`before_points` bigint UNSIGNED NOT NULL,
`delta_points` bigint NOT NULL,
`after_points` bigint UNSIGNED NOT NULL,
`reason` enum('create_links','manual','refund_no_rollback','other') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'other',
`ref_id` bigint UNSIGNED NULL DEFAULT NULL,
`operator_id` bigint UNSIGNED NULL DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_apx_account_time`(`account_id` ASC, `created_at` ASC) USING BTREE,
INDEX `fk_apx_operator`(`operator_id` ASC) USING BTREE,
CONSTRAINT `fk_apx_account` FOREIGN KEY (`account_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `fk_apx_operator` FOREIGN KEY (`operator_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- 默认管理员账号密码admin7uqweh12
-- 生产环境请尽快替换为 BCrypt 哈希;此处为 PLAIN 方便首次初始化。
INSERT INTO user_account(user_type, username, display_name, password_hash, role, status, points_balance)
VALUES ('ADMIN', 'admin', 'Super Admin', 'PLAIN:admin7uqweh12', 'SUPER', 'ENABLED', 0)
ON DUPLICATE KEY UPDATE username = username;
-- ----------------------------
-- Table structure for announcement
-- ----------------------------
DROP TABLE IF EXISTS `announcement`;
CREATE TABLE `announcement` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT 1,
`jump_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- -----------------------------------------------------------------------------
-- 2) 代理商点数流水
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS agent_points_tx (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
account_id BIGINT UNSIGNED NOT NULL, -- 指向 user_account(AGENT)
type ENUM('ADD','DEDUCT') NOT NULL,
before_points BIGINT UNSIGNED NOT NULL,
delta_points BIGINT SIGNED NOT NULL, -- 可为正/负;与 type 对应
after_points BIGINT UNSIGNED NOT NULL,
reason ENUM('create_links','manual','refund_no_rollback','other') NOT NULL DEFAULT 'other',
ref_id BIGINT UNSIGNED NULL, -- 可关联到 link_batch.id
operator_id BIGINT UNSIGNED NULL, -- 操作者(管理员,指向 user_account
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX idx_apx_account_time (account_id, created_at),
CONSTRAINT fk_apx_account FOREIGN KEY (account_id) REFERENCES user_account(id),
CONSTRAINT fk_apx_operator FOREIGN KEY (operator_id) REFERENCES user_account(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for link_batch
-- ----------------------------
DROP TABLE IF EXISTS `link_batch`;
CREATE TABLE `link_batch` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
`agent_id` bigint UNSIGNED NOT NULL,
`quantity` int UNSIGNED NOT NULL,
`times` int UNSIGNED NOT NULL,
`batch_size` int UNSIGNED NOT NULL,
`deduct_points` bigint UNSIGNED NOT NULL,
`operator_id` bigint UNSIGNED NULL DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_lb_agent_time`(`agent_id` ASC, `created_at` ASC) USING BTREE,
INDEX `fk_lb_operator`(`operator_id` ASC) USING BTREE,
CONSTRAINT `fk_lb_agent` FOREIGN KEY (`agent_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `fk_lb_operator` FOREIGN KEY (`operator_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `chk_lb_batch_pos` CHECK (`batch_size` > 0),
CONSTRAINT `chk_lb_quantity_pos` CHECK (`quantity` > 0),
CONSTRAINT `chk_lb_times_pos` CHECK (`times` > 0)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- -----------------------------------------------------------------------------
-- 3) 链接批次(一次生成 N 个链接,按统一设置扣费)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS link_batch (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
agent_id BIGINT UNSIGNED NOT NULL, -- 指向 user_account(AGENT)
quantity INT UNSIGNED NOT NULL, -- 每次奖励数量(如 50
times INT UNSIGNED NOT NULL, -- 重复执行次数(如 20
batch_size INT UNSIGNED NOT NULL, -- 本批生成链接数量(如 10
deduct_points BIGINT UNSIGNED NOT NULL, -- 扣点=quantity*times*batch_size
operator_id BIGINT UNSIGNED NULL, -- 操作者(管理员)
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX idx_lb_agent_time (agent_id, created_at),
CONSTRAINT chk_lb_quantity_pos CHECK (quantity > 0),
CONSTRAINT chk_lb_times_pos CHECK (times > 0),
CONSTRAINT chk_lb_batch_pos CHECK (batch_size > 0),
CONSTRAINT fk_lb_agent FOREIGN KEY (agent_id) REFERENCES user_account(id),
CONSTRAINT fk_lb_operator FOREIGN KEY (operator_id) REFERENCES user_account(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for link_task
-- ----------------------------
DROP TABLE IF EXISTS `link_task`;
CREATE TABLE `link_task` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
`batch_id` bigint UNSIGNED NOT NULL,
`agent_id` bigint UNSIGNED NOT NULL,
`code_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`token_hash` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`expire_at` datetime(3) NOT NULL,
`status` enum('NEW','USING','LOGGED_IN','REFUNDED','EXPIRED') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NEW',
`region` enum('Q','V') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`machine_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`login_at` datetime(3) NULL DEFAULT NULL,
`refund_at` datetime(3) NULL DEFAULT NULL,
`revoked_at` datetime(3) NULL DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_code_no`(`code_no` ASC) USING BTREE,
UNIQUE INDEX `uk_token_hash`(`token_hash` ASC) USING BTREE,
INDEX `idx_agent_status`(`agent_id` ASC, `status` ASC) USING BTREE,
INDEX `idx_expire_at`(`expire_at` ASC) USING BTREE,
INDEX `idx_created_at`(`created_at` ASC) USING BTREE,
INDEX `fk_lt_batch`(`batch_id` ASC) USING BTREE,
CONSTRAINT `fk_lt_agent` FOREIGN KEY (`agent_id`) REFERENCES `user_account` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `fk_lt_batch` FOREIGN KEY (`batch_id`) REFERENCES `link_batch` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- -----------------------------------------------------------------------------
-- 4) 单链接任务(用户访问的“加密链接”对应的实体)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS link_task (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
batch_id BIGINT UNSIGNED NOT NULL,
agent_id BIGINT UNSIGNED NOT NULL, -- 指向 user_account(AGENT)
code_no VARCHAR(32) NOT NULL, -- 后端生成的全局唯一编号
token_hash CHAR(64) NOT NULL, -- 加密token的SHA-256用于失效/撤销)
expire_at DATETIME(3) NOT NULL, -- 链接有效期(默认 24h
status ENUM('NEW','USING','LOGGED_IN','REFUNDED','EXPIRED') NOT NULL DEFAULT 'NEW',
region ENUM('Q','V') NULL, -- 选区;未选择前为 NULL
machine_id VARCHAR(64) NULL, -- 绑定的脚本端机器编号
login_at DATETIME(3) NULL,
refund_at DATETIME(3) NULL,
revoked_at DATETIME(3) NULL, -- 主动撤销(如需要立即失效)
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
UNIQUE KEY uk_code_no (code_no),
UNIQUE KEY uk_token_hash (token_hash),
INDEX idx_agent_status (agent_id, status),
INDEX idx_expire_at (expire_at),
INDEX idx_created_at (created_at),
CONSTRAINT fk_lt_batch FOREIGN KEY (batch_id) REFERENCES link_batch(id),
CONSTRAINT fk_lt_agent FOREIGN KEY (agent_id) REFERENCES user_account(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for operation_log
-- ----------------------------
DROP TABLE IF EXISTS `operation_log`;
CREATE TABLE `operation_log` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
`actor_type` enum('admin','agent','system','user') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`actor_id` bigint UNSIGNED NULL DEFAULT NULL,
`code_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`op` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`detail` json NULL,
`client_ip` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_log_code_time`(`code_no` ASC, `created_at` ASC) USING BTREE,
INDEX `idx_log_time`(`created_at` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- -----------------------------------------------------------------------------
-- 5) 操作日志(审计/可观测性)
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS operation_log (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
actor_type ENUM('admin','agent','system','user') NOT NULL,
actor_id BIGINT UNSIGNED NULL, -- 不强制外键,避免多态复杂度
code_no VARCHAR(32) NULL,
op VARCHAR(64) NOT NULL, -- 如create_links / refund / select_region / create_qr / poll_login / release_machine
detail JSON NULL, -- 具体参数/返回(注意脱敏)
client_ip VARCHAR(45) NULL,
user_agent VARCHAR(255) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX idx_log_code_time (code_no, created_at),
INDEX idx_log_time (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- -----------------------------------------------------------------------------
-- 6) 公告
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS announcement (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100) NOT NULL,
content TEXT NOT NULL, -- 富文本
enabled TINYINT(1) NOT NULL DEFAULT 1,
jump_url VARCHAR(255) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for user_account
-- ----------------------------
DROP TABLE IF EXISTS `user_account`;
CREATE TABLE `user_account` (
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
`user_type` enum('ADMIN','AGENT') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`display_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`password_hash` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`role` enum('SUPER','ADMIN') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`status` enum('ENABLED','DISABLED') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'ENABLED',
`points_balance` bigint UNSIGNED NOT NULL DEFAULT 0,
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username` ASC) USING BTREE,
CONSTRAINT `chk_points_nonneg` CHECK (`points_balance` >= 0)
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -1,48 +1,83 @@
package com.gameplatform.server.controller;
import com.gameplatform.server.model.User;
import com.gameplatform.server.service.UserService;
import com.gameplatform.server.model.dto.account.AccountCreateRequest;
import com.gameplatform.server.model.dto.account.AccountResponse;
import com.gameplatform.server.model.dto.account.AccountUpdateRequest;
import com.gameplatform.server.model.dto.common.PageResult;
import com.gameplatform.server.service.account.AccountService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 用户接口控制器 - 基于UserAccount实体
* 提供用户账户的基本CRUD操作
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
private final AccountService accountService;
public UserController(UserService userService) {
this.userService = userService;
public UserController(AccountService accountService) {
this.accountService = accountService;
}
/**
* 根据ID获取用户账户信息
*/
@GetMapping("/{id}")
public Mono<User> getById(@PathVariable Long id) {
return userService.getById(id);
public Mono<AccountResponse> getById(@PathVariable Long id) {
return accountService.get(id);
}
/**
* 分页查询用户列表
*/
@GetMapping
public Flux<User> listAll() {
return userService.listAll();
public Mono<PageResult<AccountResponse>> list(
@RequestParam(value = "userType", required = false) String userType,
@RequestParam(value = "status", required = false) String status,
@RequestParam(value = "role", required = false) String role,
@RequestParam(value = "keyword", required = false) String keyword,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "20") Integer size
) {
return accountService.list(userType, status, role, keyword, page, size);
}
/**
* 创建新用户账户
*/
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<User> create(@Valid @RequestBody User user) {
return userService.create(user);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> delete(@PathVariable Long id) {
return userService.deleteById(id)
.filter(Boolean::booleanValue)
.then();
public Mono<AccountResponse> create(@Valid @RequestBody AccountCreateRequest request) {
return accountService.create(request);
}
/**
* 更新用户账户信息
*/
@PutMapping("/{id}")
public Mono<User> update(@PathVariable Long id, @Valid @RequestBody User user) {
return userService.update(id, user);
public Mono<AccountResponse> update(@PathVariable Long id, @Valid @RequestBody AccountUpdateRequest request) {
return accountService.update(id, request);
}
/**
* 启用用户账户
*/
@PostMapping("/{id}/enable")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> enable(@PathVariable Long id) {
return accountService.setStatus(id, "ENABLED").then();
}
/**
* 禁用用户账户
*/
@PostMapping("/{id}/disable")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> disable(@PathVariable Long id) {
return accountService.setStatus(id, "DISABLED").then();
}
}

View File

@@ -1,18 +0,0 @@
package com.gameplatform.server.mapper;
import com.gameplatform.server.model.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
User findById(@Param("id") Long id);
List<User> findAll();
int insert(User user);
int deleteById(@Param("id") Long id);
int update(User user);
}

View File

@@ -1,51 +0,0 @@
package com.gameplatform.server.model;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDateTime;
public class User {
private Long id;
@NotBlank
private String username;
@Email
private String email;
private LocalDateTime createdAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -2,7 +2,7 @@ package com.gameplatform.server.security;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -3,7 +3,7 @@ package com.gameplatform.server.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

View File

@@ -1,54 +1,92 @@
package com.gameplatform.server.service;
import com.gameplatform.server.mapper.UserMapper;
import com.gameplatform.server.model.User;
import com.gameplatform.server.mapper.account.UserAccountMapper;
import com.gameplatform.server.model.dto.account.AccountResponse;
import com.gameplatform.server.model.dto.common.PageResult;
import com.gameplatform.server.model.entity.account.UserAccount;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.util.Objects;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户服务类 - 基于UserAccount实体
* 提供用户账户相关的业务逻辑
*/
@Service
public class UserService {
private final UserMapper userMapper;
private final UserAccountMapper userAccountMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
public UserService(UserAccountMapper userAccountMapper) {
this.userAccountMapper = userAccountMapper;
}
public Mono<User> getById(Long id) {
return Mono.fromCallable(() -> userMapper.findById(id))
/**
* 根据ID获取用户账户
*/
public Mono<AccountResponse> getById(Long id) {
return Mono.fromCallable(() -> userAccountMapper.findById(id))
.subscribeOn(Schedulers.boundedElastic())
.filter(Objects::nonNull)
.map(this::toAccountResponse);
}
/**
* 根据用户名获取用户账户
*/
public Mono<UserAccount> getByUsername(String username) {
return Mono.fromCallable(() -> userAccountMapper.findByUsername(username))
.subscribeOn(Schedulers.boundedElastic())
.filter(Objects::nonNull);
}
public Flux<User> listAll() {
return Mono.fromCallable(userMapper::findAll)
.subscribeOn(Schedulers.boundedElastic())
.flatMapMany(Flux::fromIterable);
}
/**
* 分页查询用户列表
*/
public Mono<PageResult<AccountResponse>> list(String userType, String status, String role, String keyword,
Integer page, Integer size) {
int p = (page == null || page < 1) ? 1 : page;
int s = (size == null || size < 1 || size > 200) ? 20 : size;
int offset = (p - 1) * s;
public Mono<User> create(User user) {
return Mono.fromCallable(() -> {
userMapper.insert(user);
return user;
long total = userAccountMapper.countByFilter(userType, status, role, keyword);
List<UserAccount> list = userAccountMapper.listByFilter(userType, status, role, keyword, s, offset);
List<AccountResponse> items = list.stream()
.map(this::toAccountResponse)
.collect(Collectors.toList());
return new PageResult<>(items, total, p, s);
})
.subscribeOn(Schedulers.boundedElastic());
}
public Mono<Boolean> deleteById(Long id) {
return Mono.fromCallable(() -> userMapper.deleteById(id) > 0)
/**
* 检查用户名是否存在
*/
public Mono<Boolean> existsByUsername(String username) {
return Mono.fromCallable(() -> userAccountMapper.findByUsername(username) != null)
.subscribeOn(Schedulers.boundedElastic());
}
public Mono<User> update(Long id, User user) {
return Mono.fromCallable(() -> {
user.setId(id);
int n = userMapper.update(user);
return n;
})
.subscribeOn(Schedulers.boundedElastic())
.flatMap(n -> n > 0 ? getById(id) : Mono.empty());
/**
* 将UserAccount实体转换为AccountResponse DTO
*/
private AccountResponse toAccountResponse(UserAccount account) {
if (account == null) return null;
AccountResponse response = new AccountResponse();
response.setId(account.getId());
response.setUserType(account.getUserType());
response.setUsername(account.getUsername());
response.setDisplayName(account.getDisplayName());
response.setRole(account.getRole());
response.setStatus(account.getStatus());
response.setPointsBalance(account.getPointsBalance());
response.setCreatedAt(account.getCreatedAt());
response.setUpdatedAt(account.getUpdatedAt());
return response;
}
}

View File

@@ -1,46 +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.UserMapper">
<resultMap id="UserResultMap" type="com.gameplatform.server.model.User">
<id property="id" column="id" />
<result property="username" column="username" />
<result property="email" column="email" />
<result property="createdAt" column="created_at" />
</resultMap>
<sql id="Base_Column_List">
id, username, email, created_at
</sql>
<select id="findById" parameterType="long" resultMap="UserResultMap">
SELECT <include refid="Base_Column_List"/>
FROM users
WHERE id = #{id}
</select>
<select id="findAll" resultMap="UserResultMap">
SELECT <include refid="Base_Column_List"/>
FROM users
ORDER BY id DESC
</select>
<insert id="insert" parameterType="com.gameplatform.server.model.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, email, created_at)
VALUES (#{username}, #{email}, NOW())
</insert>
<delete id="deleteById" parameterType="long">
DELETE FROM users WHERE id = #{id}
</delete>
<update id="update" parameterType="com.gameplatform.server.model.User">
UPDATE users
SET username = #{username},
email = #{email}
WHERE id = #{id}
</update>
</mapper>

View File

@@ -1,39 +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.UserMapper">
<resultMap id="UserResultMap" type="com.gameplatform.server.model.User">
<id property="id" column="id" />
<result property="username" column="username" />
<result property="email" column="email" />
<result property="createdAt" column="created_at" />
</resultMap>
<sql id="Base_Column_List">
id, username, email, created_at
</sql>
<select id="findById" parameterType="long" resultMap="UserResultMap">
SELECT <include refid="Base_Column_List"/>
FROM users
WHERE id = #{id}
</select>
<select id="findAll" resultMap="UserResultMap">
SELECT <include refid="Base_Column_List"/>
FROM users
ORDER BY id DESC
</select>
<insert id="insert" parameterType="com.gameplatform.server.model.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, email, created_at)
VALUES (#{username}, #{email}, NOW())
</insert>
<delete id="deleteById" parameterType="long">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>

View File

@@ -28,4 +28,61 @@
WHERE username = #{username}
LIMIT 1
</select>
<select id="findById" parameterType="long" resultMap="UserAccountMap">
SELECT id, user_type, username, display_name, password_hash, role, status, points_balance, created_at, updated_at
FROM user_account
WHERE id = #{id}
LIMIT 1
</select>
<insert id="insert" parameterType="com.gameplatform.server.model.entity.account.UserAccount" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user_account (user_type, username, display_name, password_hash, role, status, points_balance)
VALUES (#{userType}, #{username}, #{displayName}, #{passwordHash}, #{role}, #{status}, #{pointsBalance})
</insert>
<update id="update" parameterType="com.gameplatform.server.model.entity.account.UserAccount">
UPDATE user_account
<set>
<if test="displayName != null">display_name = #{displayName},</if>
<if test="role != null">role = #{role},</if>
<if test="status != null">status = #{status},</if>
</set>
WHERE id = #{id}
</update>
<update id="setStatus">
UPDATE user_account SET status = #{status} WHERE id = #{id}
</update>
<update id="updatePassword">
UPDATE user_account SET password_hash = #{passwordHash} WHERE id = #{id}
</update>
<select id="countByFilter" resultType="long">
SELECT COUNT(1) FROM user_account
<where>
<if test="userType != null and userType != ''">AND user_type = #{userType}</if>
<if test="status != null and status != ''">AND status = #{status}</if>
<if test="role != null and role != ''">AND role = #{role}</if>
<if test="keyword != null and keyword != ''">
AND (username LIKE CONCAT('%', #{keyword}, '%') OR display_name LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
</select>
<select id="listByFilter" resultMap="UserAccountMap">
SELECT id, user_type, username, display_name, password_hash, role, status, points_balance, created_at, updated_at
FROM user_account
<where>
<if test="userType != null and userType != ''">AND user_type = #{userType}</if>
<if test="status != null and status != ''">AND status = #{status}</if>
<if test="role != null and role != ''">AND role = #{role}</if>
<if test="keyword != null and keyword != ''">
AND (username LIKE CONCAT('%', #{keyword}, '%') OR display_name LIKE CONCAT('%', #{keyword}, '%'))
</if>
</where>
ORDER BY id DESC
LIMIT #{size} OFFSET #{offset}
</select>
</mapper>