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:
5
.cursor/rules/zh.mdc
Normal file
5
.cursor/rules/zh.mdc
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
description:
|
||||
globs: 中文回答我
|
||||
alwaysApply: true
|
||||
---
|
||||
262
docs/game.sql
262
docs/game.sql
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
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.
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.
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.
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user