From be437a360d6fa67f9c189d5e1b502448003fdb8a Mon Sep 17 00:00:00 2001 From: zyh Date: Sun, 24 Aug 2025 15:33:03 +0800 Subject: [PATCH] first commit --- .idea/.gitignore | 8 + .idea/compiler.xml | 18 ++ .idea/encodings.xml | 6 + .idea/jarRepositories.xml | 20 ++ .idea/misc.xml | 14 ++ .idea/vcs.xml | 6 + README.md | 62 +++++++ docs/game.sql | 174 ++++++++++++++++++ docs/需求文档.md | 21 +++ pom.xml | 122 ++++++++++++ .../server/GamePlatformServerApplication.java | 14 ++ .../server/controller/UserController.java | 44 +++++ .../controller/auth/AuthController.java | 48 +++++ .../exception/GlobalExceptionHandler.java | 40 ++++ .../server/mapper/UserMapper.java | 17 ++ .../server/mapper/admin/AdminUserMapper.java | 9 + .../server/mapper/agent/AgentMapper.java | 9 + .../com/gameplatform/server/model/User.java | 51 +++++ .../server/model/dto/auth/LoginRequest.java | 21 +++ .../server/model/dto/auth/LoginResponse.java | 24 +++ .../server/model/entity/admin/AdminUser.java | 29 +++ .../server/model/entity/agent/Agent.java | 32 ++++ .../server/security/JwtService.java | 49 +++++ .../server/security/SecurityConfig.java | 37 ++++ .../server/service/UserService.java | 45 +++++ .../server/service/auth/AuthService.java | 93 ++++++++++ src/main/resources/application.yml | 39 ++++ src/main/resources/mapper/UserMapper.xml | 40 ++++ .../mapper/admin/AdminUserMapper.xml | 21 +++ .../resources/mapper/agent/AgentMapper.xml | 22 +++ src/main/resources/schema.sql | 8 + target/classes/application.yml | 39 ++++ .../GamePlatformServerApplication.class | Bin 0 -> 887 bytes .../server/controller/UserController.class | Bin 0 -> 3218 bytes .../controller/auth/AuthController$1.class | Bin 0 -> 1304 bytes .../controller/auth/AuthController.class | Bin 0 -> 4019 bytes .../exception/GlobalExceptionHandler$1.class | Bin 0 -> 1312 bytes .../exception/GlobalExceptionHandler.class | Bin 0 -> 2038 bytes .../server/mapper/UserMapper.class | Bin 0 -> 613 bytes .../server/mapper/admin/AdminUserMapper.class | Bin 0 -> 394 bytes .../server/mapper/agent/AgentMapper.class | Bin 0 -> 390 bytes .../com/gameplatform/server/model/User.class | Bin 0 -> 1528 bytes .../server/model/dto/auth/LoginRequest.class | Bin 0 -> 1211 bytes .../server/model/dto/auth/LoginResponse.class | Bin 0 -> 1745 bytes .../server/model/entity/admin/AdminUser.class | Bin 0 -> 1965 bytes .../server/model/entity/agent/Agent.class | Bin 0 -> 2181 bytes .../server/security/JwtService.class | Bin 0 -> 4599 bytes .../server/security/SecurityConfig.class | Bin 0 -> 4317 bytes .../server/service/UserService.class | Bin 0 -> 4414 bytes .../server/service/auth/AuthService.class | Bin 0 -> 8164 bytes target/classes/mapper/UserMapper.xml | 40 ++++ .../classes/mapper/admin/AdminUserMapper.xml | 21 +++ target/classes/mapper/agent/AgentMapper.xml | 22 +++ target/classes/schema.sql | 8 + 54 files changed, 1273 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 docs/game.sql create mode 100644 docs/需求文档.md create mode 100644 pom.xml create mode 100644 src/main/java/com/gameplatform/server/GamePlatformServerApplication.java create mode 100644 src/main/java/com/gameplatform/server/controller/UserController.java create mode 100644 src/main/java/com/gameplatform/server/controller/auth/AuthController.java create mode 100644 src/main/java/com/gameplatform/server/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/gameplatform/server/mapper/UserMapper.java create mode 100644 src/main/java/com/gameplatform/server/mapper/admin/AdminUserMapper.java create mode 100644 src/main/java/com/gameplatform/server/mapper/agent/AgentMapper.java create mode 100644 src/main/java/com/gameplatform/server/model/User.java create mode 100644 src/main/java/com/gameplatform/server/model/dto/auth/LoginRequest.java create mode 100644 src/main/java/com/gameplatform/server/model/dto/auth/LoginResponse.java create mode 100644 src/main/java/com/gameplatform/server/model/entity/admin/AdminUser.java create mode 100644 src/main/java/com/gameplatform/server/model/entity/agent/Agent.java create mode 100644 src/main/java/com/gameplatform/server/security/JwtService.java create mode 100644 src/main/java/com/gameplatform/server/security/SecurityConfig.java create mode 100644 src/main/java/com/gameplatform/server/service/UserService.java create mode 100644 src/main/java/com/gameplatform/server/service/auth/AuthService.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/mapper/UserMapper.xml create mode 100644 src/main/resources/mapper/admin/AdminUserMapper.xml create mode 100644 src/main/resources/mapper/agent/AgentMapper.xml create mode 100644 src/main/resources/schema.sql create mode 100644 target/classes/application.yml create mode 100644 target/classes/com/gameplatform/server/GamePlatformServerApplication.class create mode 100644 target/classes/com/gameplatform/server/controller/UserController.class create mode 100644 target/classes/com/gameplatform/server/controller/auth/AuthController$1.class create mode 100644 target/classes/com/gameplatform/server/controller/auth/AuthController.class create mode 100644 target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$1.class create mode 100644 target/classes/com/gameplatform/server/exception/GlobalExceptionHandler.class create mode 100644 target/classes/com/gameplatform/server/mapper/UserMapper.class create mode 100644 target/classes/com/gameplatform/server/mapper/admin/AdminUserMapper.class create mode 100644 target/classes/com/gameplatform/server/mapper/agent/AgentMapper.class create mode 100644 target/classes/com/gameplatform/server/model/User.class create mode 100644 target/classes/com/gameplatform/server/model/dto/auth/LoginRequest.class create mode 100644 target/classes/com/gameplatform/server/model/dto/auth/LoginResponse.class create mode 100644 target/classes/com/gameplatform/server/model/entity/admin/AdminUser.class create mode 100644 target/classes/com/gameplatform/server/model/entity/agent/Agent.class create mode 100644 target/classes/com/gameplatform/server/security/JwtService.class create mode 100644 target/classes/com/gameplatform/server/security/SecurityConfig.class create mode 100644 target/classes/com/gameplatform/server/service/UserService.class create mode 100644 target/classes/com/gameplatform/server/service/auth/AuthService.class create mode 100644 target/classes/mapper/UserMapper.xml create mode 100644 target/classes/mapper/admin/AdminUserMapper.xml create mode 100644 target/classes/mapper/agent/AgentMapper.xml create mode 100644 target/classes/schema.sql diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..21cc3ed --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..63e9001 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..baa5bf6 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae27d25 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Game Platform Server (Spring Boot WebFlux + MyBatis + MySQL) + +A minimal backend scaffold using Spring Boot 3, WebFlux, MyBatis, and MySQL. Includes a sample `User` CRUD implemented via MyBatis (blocking JDBC) safely wrapped in reactive APIs. + +## Tech Stack + +- Spring Boot 3 (WebFlux, Actuator) +- MyBatis Spring Boot Starter (JDBC) +- MySQL Connector/J +- Java 17 + +## Blocking JDBC with WebFlux + +MyBatis uses JDBC which is blocking. To keep WebFlux event loop non-blocking, all data access is offloaded to `Schedulers.boundedElastic()` (see `UserService`). This is a common and safe pattern when you need WebFlux endpoints but must use JDBC/MyBatis. + +## Project Layout + +- `pom.xml` – Maven build, dependencies +- `src/main/resources/application.yml` – datasource + mybatis config +- `src/main/resources/mapper/*.xml` – MyBatis mappers (XML) +- `src/main/resources/schema.sql` – initial table +- `src/main/java/com/gameplatform/server` – application code + +## Configure Database + +1. Create database: + ```sql + CREATE DATABASE game_platform CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + ``` +2. Update `spring.datasource.*` in `application.yml` with your MySQL credentials. +3. Run the SQL init: + ```sql + USE game_platform; + -- then execute contents of src/main/resources/schema.sql + ``` + +## Build & Run + +Requires JDK 17+ and Maven 3.9+. + +```bash +mvn spring-boot:run +``` + +The app starts on `http://localhost:8080`. + +## Endpoints + +- `GET /actuator/health` – health check +- `GET /api/users` – list users +- `GET /api/users/{id}` – get user by id +- `POST /api/users` – create user + - body: + ```json + {"username": "alice", "email": "alice@example.com"} + ``` +- `DELETE /api/users/{id}` – delete user + +## Notes + +- If you need true end-to-end reactive IO, consider R2DBC instead of JDBC/MyBatis. Here we keep MyBatis for mapping convenience and use bounded elastic threads to avoid blocking the event loop. + diff --git a/docs/game.sql b/docs/game.sql new file mode 100644 index 0000000..214df5f --- /dev/null +++ b/docs/game.sql @@ -0,0 +1,174 @@ +-- ============================================================================= +-- 上号系统 - 数据库结构 (MySQL 8+) +-- ============================================================================= + +-- 可选:创建并使用独立库 +CREATE DATABASE IF NOT EXISTS login_task_db + DEFAULT CHARACTER SET utf8mb4 + DEFAULT COLLATE utf8mb4_0900_ai_ci; +USE login_task_db; + +SET NAMES utf8mb4; +SET sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO'; + +-- ----------------------------------------------------------------------------- +-- 1) 管理员(平台侧) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS admin_user ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(64) NOT NULL UNIQUE, + password_hash VARBINARY(100) NOT NULL, -- 建议存储 bcrypt/argon2 等 + role ENUM('SUPER','ADMIN') NOT NULL DEFAULT 'ADMIN', + status ENUM('ENABLED','DISABLED') NOT NULL DEFAULT 'ENABLED', + 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; + +-- ----------------------------------------------------------------------------- +-- 2) 代理商(商家) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS agent ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(100) NOT NULL, + login_account VARCHAR(64) NOT NULL UNIQUE, + password_hash VARBINARY(100) NOT NULL, + status ENUM('ENABLED','DISABLED') 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), + CONSTRAINT chk_agent_points_nonneg CHECK (points_balance >= 0) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ----------------------------------------------------------------------------- +-- 3) 代理商点数流水 +-- 记录加点/扣点(生成链接时扣 Times×Quantity×BatchSize) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS agent_points_tx ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + agent_id BIGINT UNSIGNED NOT NULL, + 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, -- 操作者(管理员) + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + INDEX idx_agent_points_tx_agent_time (agent_id, created_at), + CONSTRAINT fk_apx_agent FOREIGN KEY (agent_id) REFERENCES agent(id), + CONSTRAINT fk_apx_operator FOREIGN KEY (operator_id) REFERENCES admin_user(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ----------------------------------------------------------------------------- +-- 4) 链接批次(一次生成 N 个链接,按统一设置扣费) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS link_batch ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + agent_id BIGINT UNSIGNED NOT NULL, + 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 agent(id), + CONSTRAINT fk_lb_operator FOREIGN KEY (operator_id) REFERENCES admin_user(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ----------------------------------------------------------------------------- +-- 5) 单链接任务(用户访问的“加密链接”对应的实体) +-- ----------------------------------------------------------------------------- +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, + 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 agent(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ----------------------------------------------------------------------------- +-- 6) 操作日志(审计/可观测性) +-- ----------------------------------------------------------------------------- +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; + +-- ----------------------------------------------------------------------------- +-- 7) 公告 +-- ----------------------------------------------------------------------------- +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), + INDEX idx_announce_enabled (enabled) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ----------------------------------------------------------------------------- +-- 8) 平台级配置(商家不可配) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS sys_config ( + cfg_key VARCHAR(64) PRIMARY KEY, + cfg_value TEXT NOT NULL, + description VARCHAR(255) NULL, + updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 预置关键配置(可按需改值) +INSERT INTO sys_config (cfg_key, cfg_value, description) VALUES + ('LINK_EXPIRE_HOURS', '24', '链接有效期(小时)'), + ('QR_EXPIRE_SECONDS', '60', '二维码过期秒数(脚本端未返回TTL时使用)'), + ('REFRESH_WAIT_SECONDS', '10', '刷新后强制等待秒数(商家不可配置)'), + ('MACHINE_COOLDOWN_MINUTES','10', '同一机器复用冷却时长(分钟)'), + ('DEFAULT_BATCH_SIZE', '10', '批量生成默认数量'), + ('SECOND_SCREEN_URL_TEMPLATE','https://你的域名/{codeNo}','二界面URL模板(包含编号占位)') + ON DUPLICATE KEY UPDATE + cfg_value = VALUES(cfg_value), + description = VALUES(description); + +-- ----------------------------------------------------------------------------- +-- 推荐的视图 / 物化统计可按需追加(此处略) +-- ----------------------------------------------------------------------------- + +-- ============================================================================= +-- 说明: +-- 1) 生成链接时,请在业务层完成: +-- - 计算扣点 = quantity * times * batch_size +-- - 扣减 agent.points_balance,并写入 agent_points_tx +-- - 写入 link_batch 及其下的若干 link_task(生成 code_no / token 及 token_hash、expire_at) +-- 2) 用户端所有页面均以“加密链接 token”为入口,通过 token_hash 可实现失效/撤销。 +-- 3) operation_log.detail 建议仅存必要字段并脱敏;日志保留 90 天可通过定时归档或分区处理。 +-- ============================================================================= diff --git a/docs/需求文档.md b/docs/需求文档.md new file mode 100644 index 0000000..378a934 --- /dev/null +++ b/docs/需求文档.md @@ -0,0 +1,21 @@ +明白✅ + +1. 二界面 URL 模板 + 我已把配置改成:**`域名/{codeNo}`**(文档已更新为 `https://你的域名/{codeNo}`,按运行环境替换域名即可)。 + +2. 关于 `http://36.138.184.60:12345/编号/二维码.png` + 这个是“**脚本端**”暴露出来的**静态图片**地址——**正常情况下会直接返回 PNG 图片**。如果你在页面里看不到图,常见原因有: + +* **混合内容被拦截**:你的前端如果是 **HTTPS**,而这张图是 **HTTP**,浏览器会拦截(安全策略)。 + 解决:给 `36.138.184.60:12345` 配 **HTTPS**,或让后端提供一个 **HTTPS 代理端点**(例如:`GET /api/link/{token}/qr.png`)去转发这张图片,前端只请求自己的 HTTPS 域名即可。(我已在文档里加了这条注意事项) +* **二维码尚未生成 / 已过期**:脚本端还没把 `二维码.png` 写到对应 `编号/` 目录,或已被轮转清理 → 会 404 或显示旧图。 + 解决:确保先触发“生成二维码”动作,并用返回的 `createdAt/expireAt` 做本地倒计时;必要时加 `?t=时间戳` 防缓存。 +* **编号不匹配**:前端用的 `编号` 与实际分配的机器编号不同。 + 解决:以后端返回的 `machineId/编号` 为准。 +* **端口/防火墙**:12345 端口未对外放通或临时不可达。 + 解决:主机/网关放行该端口并做健康检查。 +* **跨域非问题**:`` 加载图片不受 CORS 限制,但若被混合内容拦截,同样会显示不出。 + +如果你愿意最省心的做法:我们在后端新增一个**图片代理接口**(HTTPS),前端统一拿这个接口的 URL,当脚本端换 IP/端口或做 HTTPS,这边都不用改前端。 + +需要的话我可以顺手把这个代理端点的 Spring Boot 代码骨架也给你(带缓存/超时/错误降级)。 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6fb87e2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,122 @@ + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + + com.gameplatform + server + 0.0.1-SNAPSHOT + gameplatform-server + Spring Boot WebFlux + MyBatis + MySQL backend + + + 17 + 3.0.3 + + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis.spring.boot.version} + + + + + com.mysql + mysql-connector-j + runtime + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${java.version} + ${java.version} + UTF-8 + + -parameters + + + + + + diff --git a/src/main/java/com/gameplatform/server/GamePlatformServerApplication.java b/src/main/java/com/gameplatform/server/GamePlatformServerApplication.java new file mode 100644 index 0000000..3f35810 --- /dev/null +++ b/src/main/java/com/gameplatform/server/GamePlatformServerApplication.java @@ -0,0 +1,14 @@ +package com.gameplatform.server; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@MapperScan("com.gameplatform.server.mapper") +public class GamePlatformServerApplication { + public static void main(String[] args) { + SpringApplication.run(GamePlatformServerApplication.class, args); + } +} + diff --git a/src/main/java/com/gameplatform/server/controller/UserController.java b/src/main/java/com/gameplatform/server/controller/UserController.java new file mode 100644 index 0000000..741cbf0 --- /dev/null +++ b/src/main/java/com/gameplatform/server/controller/UserController.java @@ -0,0 +1,44 @@ +package com.gameplatform.server.controller; + +import com.gameplatform.server.model.User; +import com.gameplatform.server.service.UserService; +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; + +@RestController +@RequestMapping("/api/users") +public class UserController { + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/{id}") + public Mono getById(@PathVariable Long id) { + return userService.getById(id); + } + + @GetMapping + public Flux listAll() { + return userService.listAll(); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Mono create(@Valid @RequestBody User user) { + return userService.create(user); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public Mono delete(@PathVariable Long id) { + return userService.deleteById(id) + .filter(Boolean::booleanValue) + .then(); + } +} + diff --git a/src/main/java/com/gameplatform/server/controller/auth/AuthController.java b/src/main/java/com/gameplatform/server/controller/auth/AuthController.java new file mode 100644 index 0000000..77458f3 --- /dev/null +++ b/src/main/java/com/gameplatform/server/controller/auth/AuthController.java @@ -0,0 +1,48 @@ +package com.gameplatform.server.controller.auth; + +import com.gameplatform.server.model.dto.auth.LoginRequest; +import com.gameplatform.server.model.dto.auth.LoginResponse; +import com.gameplatform.server.security.JwtService; +import io.jsonwebtoken.Claims; +import jakarta.validation.Valid; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/api/auth") +public class AuthController { + + private final com.gameplatform.server.service.auth.AuthService authService; + private final JwtService jwtService; + + public AuthController(com.gameplatform.server.service.auth.AuthService authService, JwtService jwtService) { + this.authService = authService; + this.jwtService = jwtService; + } + + @PostMapping("/login") + @ResponseStatus(HttpStatus.OK) + public Mono login(@Valid @RequestBody LoginRequest req) { + return authService.login(req); + } + + @GetMapping("/me") + public Mono me(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) { + String token = authorization != null && authorization.startsWith("Bearer ") ? authorization.substring(7) : authorization; + return Mono.fromCallable(() -> jwtService.parse(token)) + .map(this::claimsToMe); + } + + private Object claimsToMe(Claims c) { + return new java.util.LinkedHashMap<>() {{ + put("userType", c.get("userType")); + put("userId", c.get("userId")); + put("username", c.get("username")); + put("role", c.get("role")); + put("exp", c.getExpiration()); + }}; + } +} + diff --git a/src/main/java/com/gameplatform/server/exception/GlobalExceptionHandler.java b/src/main/java/com/gameplatform/server/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..b74f1c1 --- /dev/null +++ b/src/main/java/com/gameplatform/server/exception/GlobalExceptionHandler.java @@ -0,0 +1,40 @@ +package com.gameplatform.server.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.time.Instant; +import java.util.LinkedHashMap; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public Object handleBadRequest(IllegalArgumentException e) { + return body(HttpStatus.BAD_REQUEST.value(), e.getMessage()); + } + + @ExceptionHandler(IllegalStateException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public Object handleForbidden(IllegalStateException e) { + return body(HttpStatus.FORBIDDEN.value(), e.getMessage()); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Object handleOther(Exception e) { + return body(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器内部错误"); + } + + private Object body(int code, String message) { + return new LinkedHashMap<>() {{ + put("code", code); + put("message", message); + put("timestamp", Instant.now().toString()); + }}; + } +} + diff --git a/src/main/java/com/gameplatform/server/mapper/UserMapper.java b/src/main/java/com/gameplatform/server/mapper/UserMapper.java new file mode 100644 index 0000000..ec052d5 --- /dev/null +++ b/src/main/java/com/gameplatform/server/mapper/UserMapper.java @@ -0,0 +1,17 @@ +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 findAll(); + + int insert(User user); + + int deleteById(@Param("id") Long id); +} + diff --git a/src/main/java/com/gameplatform/server/mapper/admin/AdminUserMapper.java b/src/main/java/com/gameplatform/server/mapper/admin/AdminUserMapper.java new file mode 100644 index 0000000..6010c49 --- /dev/null +++ b/src/main/java/com/gameplatform/server/mapper/admin/AdminUserMapper.java @@ -0,0 +1,9 @@ +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); +} + diff --git a/src/main/java/com/gameplatform/server/mapper/agent/AgentMapper.java b/src/main/java/com/gameplatform/server/mapper/agent/AgentMapper.java new file mode 100644 index 0000000..bb7ddb5 --- /dev/null +++ b/src/main/java/com/gameplatform/server/mapper/agent/AgentMapper.java @@ -0,0 +1,9 @@ +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); +} + diff --git a/src/main/java/com/gameplatform/server/model/User.java b/src/main/java/com/gameplatform/server/model/User.java new file mode 100644 index 0000000..fbe2c0a --- /dev/null +++ b/src/main/java/com/gameplatform/server/model/User.java @@ -0,0 +1,51 @@ +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; + } +} + diff --git a/src/main/java/com/gameplatform/server/model/dto/auth/LoginRequest.java b/src/main/java/com/gameplatform/server/model/dto/auth/LoginRequest.java new file mode 100644 index 0000000..9dc7997 --- /dev/null +++ b/src/main/java/com/gameplatform/server/model/dto/auth/LoginRequest.java @@ -0,0 +1,21 @@ +package com.gameplatform.server.model.dto.auth; + +import jakarta.validation.constraints.NotBlank; + +public class LoginRequest { + // userType: admin | agent + @NotBlank + private String userType; + @NotBlank + private String username; // admin: username, agent: loginAccount + @NotBlank + private String password; + + 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 getPassword() { return password; } + public void setPassword(String password) { this.password = password; } +} + diff --git a/src/main/java/com/gameplatform/server/model/dto/auth/LoginResponse.java b/src/main/java/com/gameplatform/server/model/dto/auth/LoginResponse.java new file mode 100644 index 0000000..0e5700e --- /dev/null +++ b/src/main/java/com/gameplatform/server/model/dto/auth/LoginResponse.java @@ -0,0 +1,24 @@ +package com.gameplatform.server.model.dto.auth; + +public class LoginResponse { + private String tokenType = "Bearer"; + private String accessToken; + private long expiresIn; // seconds + private String userType; // admin | agent + private Long userId; + private String username; + + public String getTokenType() { return tokenType; } + public void setTokenType(String tokenType) { this.tokenType = tokenType; } + public String getAccessToken() { return accessToken; } + public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + public long getExpiresIn() { return expiresIn; } + public void setExpiresIn(long expiresIn) { this.expiresIn = expiresIn; } + public String getUserType() { return userType; } + public void setUserType(String userType) { this.userType = userType; } + public Long getUserId() { return userId; } + public void setUserId(Long userId) { this.userId = userId; } + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } +} + diff --git a/src/main/java/com/gameplatform/server/model/entity/admin/AdminUser.java b/src/main/java/com/gameplatform/server/model/entity/admin/AdminUser.java new file mode 100644 index 0000000..011ad6d --- /dev/null +++ b/src/main/java/com/gameplatform/server/model/entity/admin/AdminUser.java @@ -0,0 +1,29 @@ +package com.gameplatform.server.model.entity.admin; + +import java.time.LocalDateTime; + +public class AdminUser { + private Long id; + private String username; + private String passwordHash; + private String role; // SUPER / ADMIN + private String status; // ENABLED / DISABLED + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + 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 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 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; } +} + diff --git a/src/main/java/com/gameplatform/server/model/entity/agent/Agent.java b/src/main/java/com/gameplatform/server/model/entity/agent/Agent.java new file mode 100644 index 0000000..72854cc --- /dev/null +++ b/src/main/java/com/gameplatform/server/model/entity/agent/Agent.java @@ -0,0 +1,32 @@ +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; } +} + diff --git a/src/main/java/com/gameplatform/server/security/JwtService.java b/src/main/java/com/gameplatform/server/security/JwtService.java new file mode 100644 index 0000000..221b0ec --- /dev/null +++ b/src/main/java/com/gameplatform/server/security/JwtService.java @@ -0,0 +1,49 @@ +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.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Map; + +@Component +public class JwtService { + + private final SecretKey key; + private final long accessTokenMinutes; + + public JwtService(@Value("${security.jwt.secret}") String secret, + @Value("${security.jwt.access-token-minutes:30}") long accessTokenMinutes) { + // accept raw text secret; if base64 provided, still works with Decoders + byte[] bytes = secret.length() < 32 ? (secret + "_pad_to_32_chars_secret_key_value").getBytes() : secret.getBytes(); + this.key = Keys.hmacShaKeyFor(bytes); + this.accessTokenMinutes = accessTokenMinutes; + } + + public String generateToken(String subject, String userType, Long userId, String username, Map extra) { + Instant now = Instant.now(); + var builder = Jwts.builder() + .setSubject(subject) + .setIssuedAt(Date.from(now)) + .setExpiration(Date.from(now.plus(accessTokenMinutes, ChronoUnit.MINUTES))) + .claim("userType", userType) + .claim("userId", userId) + .claim("username", username); + if (extra != null) { + extra.forEach(builder::claim); + } + return builder.signWith(key, SignatureAlgorithm.HS256).compact(); + } + + public io.jsonwebtoken.Claims parse(String token) { + return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); + } +} + diff --git a/src/main/java/com/gameplatform/server/security/SecurityConfig.java b/src/main/java/com/gameplatform/server/security/SecurityConfig.java new file mode 100644 index 0000000..8d77014 --- /dev/null +++ b/src/main/java/com/gameplatform/server/security/SecurityConfig.java @@ -0,0 +1,37 @@ +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; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.server.SecurityWebFilterChain; + +@Configuration +@EnableWebFluxSecurity +public class SecurityConfig { + + @Bean + public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + return http + .csrf(ServerHttpSecurity.CsrfSpec::disable) + .cors(ServerHttpSecurity.CorsSpec::disable) + .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) + .formLogin(ServerHttpSecurity.FormLoginSpec::disable) + .authorizeExchange(ex -> ex + .pathMatchers("/actuator/**").permitAll() + .pathMatchers(HttpMethod.POST, "/api/auth/login").permitAll() + .pathMatchers(HttpMethod.GET, "/api/auth/me").permitAll() + .anyExchange().permitAll() // 其他接口后续再收紧 + ) + .build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/gameplatform/server/service/UserService.java b/src/main/java/com/gameplatform/server/service/UserService.java new file mode 100644 index 0000000..ff67dcb --- /dev/null +++ b/src/main/java/com/gameplatform/server/service/UserService.java @@ -0,0 +1,45 @@ +package com.gameplatform.server.service; + +import com.gameplatform.server.mapper.UserMapper; +import com.gameplatform.server.model.User; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import java.util.Objects; + +@Service +public class UserService { + private final UserMapper userMapper; + + public UserService(UserMapper userMapper) { + this.userMapper = userMapper; + } + + public Mono getById(Long id) { + return Mono.fromCallable(() -> userMapper.findById(id)) + .subscribeOn(Schedulers.boundedElastic()) + .filter(Objects::nonNull); + } + + public Flux listAll() { + return Mono.fromCallable(userMapper::findAll) + .subscribeOn(Schedulers.boundedElastic()) + .flatMapMany(Flux::fromIterable); + } + + public Mono create(User user) { + return Mono.fromCallable(() -> { + userMapper.insert(user); + return user; + }) + .subscribeOn(Schedulers.boundedElastic()); + } + + public Mono deleteById(Long id) { + return Mono.fromCallable(() -> userMapper.deleteById(id) > 0) + .subscribeOn(Schedulers.boundedElastic()); + } +} + diff --git a/src/main/java/com/gameplatform/server/service/auth/AuthService.java b/src/main/java/com/gameplatform/server/service/auth/AuthService.java new file mode 100644 index 0000000..2a6ddc4 --- /dev/null +++ b/src/main/java/com/gameplatform/server/service/auth/AuthService.java @@ -0,0 +1,93 @@ +package com.gameplatform.server.service.auth; + +import com.gameplatform.server.mapper.admin.AdminUserMapper; +import com.gameplatform.server.mapper.agent.AgentMapper; +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.security.JwtService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import java.util.Map; + +@Service +public class AuthService { + private final AdminUserMapper adminUserMapper; + private final AgentMapper agentMapper; + private final PasswordEncoder passwordEncoder; + private final JwtService jwtService; + + public AuthService(AdminUserMapper adminUserMapper, + AgentMapper agentMapper, + PasswordEncoder passwordEncoder, + JwtService jwtService) { + this.adminUserMapper = adminUserMapper; + this.agentMapper = agentMapper; + this.passwordEncoder = passwordEncoder; + this.jwtService = jwtService; + } + + public Mono 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())); + } else { + return Mono.error(new IllegalArgumentException("unsupported userType: " + userType)); + } + } + + private Mono 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())) { + return Mono.error(new IllegalStateException("账户已禁用")); + } + String token = jwtService.generateToken( + "admin:" + admin.getId(), + "admin", admin.getId(), admin.getUsername(), Map.of("role", admin.getRole()) + ); + 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 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.setExpiresIn(60L * 30); + return Mono.just(resp); + } +} + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..128e22e --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,39 @@ +spring: + application: + name: gameplatform-server + + datasource: + url: jdbc:mysql://localhost:3306/login_task_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&allowPublicKeyRetrieval=true + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + minimum-idle: 2 + connection-timeout: 30000 + +mybatis: + mapper-locations: classpath:mapper/*.xml + type-aliases-package: com.gameplatform.server.model + configuration: + map-underscore-to-camel-case: true + +server: + port: 18080 + +management: + endpoints: + web: + exposure: + include: health,info + +logging: + level: + root: info + com.gameplatform.server: debug + +security: + jwt: + secret: "change-this-secret-to-a-long-random-string-please" + access-token-minutes: 30 + refresh-token-days: 7 diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..9a7f4a0 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + id, username, email, created_at + + + + + + + + INSERT INTO users (username, email, created_at) + VALUES (#{username}, #{email}, NOW()) + + + + DELETE FROM users WHERE id = #{id} + + + + diff --git a/src/main/resources/mapper/admin/AdminUserMapper.xml b/src/main/resources/mapper/admin/AdminUserMapper.xml new file mode 100644 index 0000000..98f50e5 --- /dev/null +++ b/src/main/resources/mapper/admin/AdminUserMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/agent/AgentMapper.xml b/src/main/resources/mapper/agent/AgentMapper.xml new file mode 100644 index 0000000..621835b --- /dev/null +++ b/src/main/resources/mapper/agent/AgentMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..aeae656 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,8 @@ +-- Initial database schema for game_platform +CREATE TABLE IF NOT EXISTS users ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50) NOT NULL, + email VARCHAR(120) NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + diff --git a/target/classes/application.yml b/target/classes/application.yml new file mode 100644 index 0000000..128e22e --- /dev/null +++ b/target/classes/application.yml @@ -0,0 +1,39 @@ +spring: + application: + name: gameplatform-server + + datasource: + url: jdbc:mysql://localhost:3306/login_task_db?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&allowPublicKeyRetrieval=true + username: root + password: root + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 10 + minimum-idle: 2 + connection-timeout: 30000 + +mybatis: + mapper-locations: classpath:mapper/*.xml + type-aliases-package: com.gameplatform.server.model + configuration: + map-underscore-to-camel-case: true + +server: + port: 18080 + +management: + endpoints: + web: + exposure: + include: health,info + +logging: + level: + root: info + com.gameplatform.server: debug + +security: + jwt: + secret: "change-this-secret-to-a-long-random-string-please" + access-token-minutes: 30 + refresh-token-days: 7 diff --git a/target/classes/com/gameplatform/server/GamePlatformServerApplication.class b/target/classes/com/gameplatform/server/GamePlatformServerApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..91b74416115ff01e428b17a264507c89e52a1c98 GIT binary patch literal 887 zcmbVL%We}f6g^J!Y8q&pLZLj$W0htRuYg6M5=4Sc3Kc+3vGrcvB`KMGMT5s&5g}?A?-q1{)VvQB{mJv%REn2ESxgBgAO)Y z=yq`p*BJ)JCcMb4(#hOX-zQ@)cw~&@(*RaFX|ydhL)4#K9dDe9q8Oc2kg4-zql3vR z=GbT_KRZ4)dajbviYS$p5d{16h@n0lQQ&ZYlzE=LokN;`N z(2L2i_rBClWpbtp`O$9#nNsr z{*K^BBG09r#zIqZV%Jz5Pf4?-bmRbisKlZ5ONbAB%AxdvB6gCBp@GHihu+NBWWyeyxJ9eIJhYO z6I6kz$_IV`#c!f`vs!cn%O&$c+UeQu*WIssdgsr--~0h!7OyfGz@UL46DbS}WM9c` zX*trZS*zt&suBteKeSytoD&$Fm@K7n2x$Wu6Nh06+^BeUt0wEJ;mB~q^Xpci{B7l1 z70(TQ&vCeX#;sgS6gV7lqd+ygq6DtydqHRJq*`b-8#am+y0#Bq!WOdr5jz^JKk zVdrU8z?#Un^UQm0ZDum>D_IFW9l5WpMpSm}V3W}=d#)F|9W`+hV?^PLaNcnQ&QA2S zT5_TvVykyejN=qZtPnqBzPX8>3D>=|>Ut5%|VS@}g-cDOT=_bSpU zN#E9TvdKv7K;Tx-^7L%!86KNu6>fUfHRO{YckyYw;=Skxc|}Wn}{Jj zYXmO!L!5x!yT>{R-nY5?j_gkV)ObNh9q#vSx2}Q)E3OJGeH4(?%X^w`hGD~c!oQZD zky~G!FD^b3IMZF^;!dNxmrk+0x1=9Ri``*Y;})|@dP#TvXSlAuk2tOtyy_0^hMN;j zVE&+|qdn709z)P4;ohD(@73*(;yK!G_{_@c%iQWpadD-{DO>O&zoM3GJw8r!f6=aK zw*m($OxXS)GRT9N#!PiYX~~9d=`QEd&MtUf7=*rTG{;epMM2V<|k# zpoA|Bd}-nXP}G`YZe-pmGg1K{UP-_5v6ah<}4+`UR^iBJ7LGxa;f)bwvKexWpgYy3ZoLH=hk z#A{WW_7&zg&OX&~CiJ&r{;azaL&($J0G4B`5w2D!ueL1L@Hu78 V{x_`GI*NFKDt9v6QK-Sj_&&xoFcyYOGeS) zgCF2W8P^QNU% z8)l&V6Xlzh=W5?`9ICRV56w3`*8gF-1%X@o&<=9*^il52Nh#!#G>}4Cp!-BRIZI%q zYGUE0E1!dXEaUA+;(A?BzRc)J;7pR7h&wt0{qgc0uj78dX+WRvjYQ zvS&%BB7Hmjz69gp&wSjJ06wW2}a_jVX{ zHh*Ky+c|Sic2%Iaynw@JY`d=Vximqb0!(l?4Q_rclL_|>pyc8c5q$em;C+$WSH#gp zSuf6&cK7Rqup<4X_ literal 0 HcmV?d00001 diff --git a/target/classes/com/gameplatform/server/controller/auth/AuthController.class b/target/classes/com/gameplatform/server/controller/auth/AuthController.class new file mode 100644 index 0000000000000000000000000000000000000000..4bba42f257ee6409b437a2d1a3a99bb41b6b9c8a GIT binary patch literal 4019 zcmbVPYf}?f7=BK;tO={6QSja>q5?rzt*C7f6{?~JwLtOK+a@`{!e%$_ZcyyK_CEas z{RREn&Q$E!nSShtPJdIU&)IB4kR*d4O0yv8w5{RKiL#vK9 zv8F zz{o0wNqN64{UFny^`w~(T;(!P8l`g1w)}azZNhcj%qHv*&_<={NpD{QyRcis9vyq} z>Y7kZ1)k;1(Gh-NdVzn-3g!j6`m(FRtWae7?~7*J z0e#kUi~q9V=7L$3uoTXyn+Q98z$ zZCXV?(?63y2E!W8>KMT}fpjA~eYq}M8HtkV`79WN_0D!9odUVWI2sn1hS^-lFv4Pf zj4{mxIM})%)5n~rpNjl zNRQH5Jb7PWsG-beg^1-~%9?Y`pzP7+9~*nDnUJOjbNB`A@=LDc%ghL|?wl+;fmM_< zmT%FA7aYe8OqE0gJ(P95Im0ih5;&`T@xb*K4IVrrXE_DKtkN1cT|by$E|ZWl+m=kb zOsMTfRKd?|SkR=b*fvEY%06Wstjjtd1SMmfk1Ad4#x;SxYr#w}m(~K>#X`BrI&K=Y z&?>O1^{hvsvFgaLyL#yG zuo94%?#M4P5K$^zd{KqNHPS0xN_a?4)(t|vgK$~aoP!oap+3D~=E!B^(kw_%VOPCP z4faGnEOpcF1jVvYHL8T`K?sPvz>Y>7?3!$|m@AmQMcK=1DRJ>(UY5cv(7+3S>#EPI zJEWR&%U)hyvQ#PBy0#BHsyI{;+Gqyx;0I&XHjdz5gf(n74+Rd@9=AwL zeaVlM1+6grEr~3>cP|SoxrQjNm-gJzF@byiJvLpKc&7qI7+F`pYJxdX>B;qoeP}Z zQ6DpWcMd#-7)U>XHZb@En}6j}45NIe(8}jFKD!V{H@09q#<;St!iKkSkuwSi)rfGU z6vc3f<9LkAY7^AT?*+7nTF-J@Sr;Gp4O)5yul&MEMPEA!fjyy)?g~*=M;whES2^p{A8s9rfp5^w$JqRDyaYKzIZsKiBayt&u&DRwF)BJa^c!!gBI1!k^ExJXy zO6}yr;%rgHV3=OgF^BO-mB8a!SR)P3#2yqKJ-cztbi0_9$ z_)#hFfm-zOA#G>w@u`rEFlx%pi9axOXCVC)r=R!p;Tk_Ds%y-SX59a!X`ow^K6(g(s;ag)~F* zoIAOSR}~Dbqy)N-Uff~mQG&V%0$w9ocK3{*bIajw%{r34?bZruWYDi6t78Cz3?rdJ zQ`(NTYr7vrb%zJ1d%QuAi?(aab%wZ+KTcwpA)y?maToVA+}AORF^2ws4oa>RHQ_U8 zgrO-8P8f!bQa%Eqtw9)Kc&OtM#>sl4DH&Fbh}uEp z+Qdp;Z=^AUSq+AcJj`pTN@|r{QgF#{-1R;(3>j_9Q6Zt6!Mu(IEKQ%UN%zaz&?Zbp>F@|EtFmlTU zhRmLjr(X4t`_$=D_+&A4WY=6Sn?8BoXd%7XFU3d*|E=FTIY=LQO}`@E*ea0G+b3pG zH5EhOwp($$fV#E?N<)!4rPKM*v5pN&YT2uPW|%c@OZv?qsPy~PO*>vd-J+LVSNK~F z4+0TjoVqwePenYFQ9}t3r5OwWDXCAbPI{atuYhcUdsN=R2p83eLG@84#4Qot*sX}YeA()SnF C#9KT7 literal 0 HcmV?d00001 diff --git a/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler.class b/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler.class new file mode 100644 index 0000000000000000000000000000000000000000..8e7751d4b9006d68389c93e3826728a3e1374f1c GIT binary patch literal 2038 zcmbVNOK%%h6#i~rZtMweL(?{dlBP|lla>wz3L#EY9LH%aIZhb|ONd1@wr}kz;~6tE zPGOB;fy9nYB{uB3E3il&VA3 zgy9%QG>l?Q;P@SL-_&i>sp~tHJF*%GjICOZ6|4yirxwfOIEL{U;u;dr1TMHemEy)X4f5VZ6yE~UT>+P*+yJ+rZAV1G$e6cV07QK z+cGqO_Dg%~$<$(@ zFH{PY?L<3+=@{P7FoQP*maA@4uhXBFZ3eepuc`ae+n1g$pDNXF+HS?Pdmvk;Q?sQf zFj8@A-w0ex<)23x1nxvPt>XEXhFKNN@y#7$J-@M$E5T;$U~KmU06!!L*T?jL^t-Q#cXKmO_7qX!RS zSQJ<}s;~1`<4EC>DxAv#w^D_pDoRI%s?1Pk-5L{?ELe^#wwo2{?V1&vl*xiyHSMzL zS?b;;jsy*hwYG9}Gg$qNFpcYG&5&QUrO)bLP7UPoMJ1Mw$gF^%sq?QQP2g=AG~C)P zQ#DZ_JvuUNw4K0e%ChBK^fKc(E;G-14xNl@p}Vr8S1hNdn>}8=S18>AP1B>u{)-Ye zq~9W*?DR23lb!f%x?aVq)g)W%T10sMTcOdhnR`Ai&xmnsbkKH!hH9+EL8P8r(b}g5 zwD(zAQPr@bL|!1)d^!*&2bjC#fFq;iw!Ny{v{b##L^t~t)tdrq|0kY7)^!5U<*4#9 zwSB898P!CQm9{Nafit&S-f^UtwN2lbKIgz04ruj_f`braT;JhM=Pnd==^?~#JPhFq zSM>~Nd{%47fOm0~H-Y!?K88c#EO(_ex%4|?2bj2YfD;Ee{VPd>YEFb|PIXwjYAA#c zaEM5p6jsnU=#QR8NyN&>&Q}_3i|jE$shq3!3NhMX@dGH7Lqm091 z#nHP!N`J~YNU;gmICp7}P7xJX1Wmt0M8opG@t@Ir-0Doder$;ewZ zJk**|r`KOMoI9ntuQCT(eWD^GT%Lk@wMDr8&xdYdQ&k4hA)oW!ee2YI0HL!@Jlk8Jr84 zZm>sb(P{ZsZb(G|w=3hS*KOgoak)~yb1cpuii2|*gK>KHSp19h6nkz^6tEt$>L=LEU{Y(bV*5T2os3bLP(Zh|ze*JNJbTT4IB0P12Msw-OHqx!+=3 z+N14Az_uwo0yfp1X~L`_Tz8o|C_+O;g)1G#QV{8pN1Onl)7 z@Ix8@b3JH#9^u99-rmgo=eIL+fBydZO++v0`8ZiLmZywOS;`5TI+K^uai!mJ-k+SQ zmJyVDp?z(31&!6}%_0@3n5S`@Cdd}F(h5AMBRzHDO7l4gJ*TU}OBFg^&{nSV2`538 zZbP^+P;LZ%XNQ&U*i@n^L4_V1eaHwZ5BVLLP{*ESn`YQEt30V)j-9n>j_wL7wn8P1 zYVQH{em~e~PdSaCCEeGs9OJYD+sB^IhNkZ76Ibo|eqf~0f!`HWN8Gs#jdU)htJ~3M zrv;MYRBC8n9rnBv6&_1~aS0f` zDMQWY_+!SLYLHeNw^tn6*^V;rQ1x<+uXfeZsO~U!`^K4C(pGPxQ3uML2JH_rL_drQ zk#quT+{%83DkGY4kKhE=fz#};cI?CI1xQ5&z$*gcR~ujV2W_0c%8=Sf8*xL<3vs3t zUrsoF6!gNDdaJpzMF!mDg3vNX$igp>!HQAFo5iz;^&Z}Hgn_~S08fF$3`Hl4XK=`4 ze#C=M7SAO1BI5BsVO_w5Xm zCX-AHsZ2GhBgcPClS!r}FfHG}#QnKuDn(3dsZ7&JrfMqFdOFi|lBou!`VCAx5!XzZ q6C5_^SxTKMNv4fdrcF#sN}Vc6rY$gS-@wE(c+E5yF>PVBX!{>f`v@oi literal 0 HcmV?d00001 diff --git a/target/classes/com/gameplatform/server/model/dto/auth/LoginRequest.class b/target/classes/com/gameplatform/server/model/dto/auth/LoginRequest.class new file mode 100644 index 0000000000000000000000000000000000000000..7d38703c7456abd3530b4c2280ce5eaeb30377f2 GIT binary patch literal 1211 zcmbV~OK%e~5XZ-PH{CXmKA=E>@={VHd_Y`iiv&oVLaEx4yOWs2?e02wy+M2{kU&Tr z_yBw;#NXRAX~GsH4m;i%&*L{U{`u?s4F0{JrN_peG&Hgo6d>s zT1JIk6)O9TQMS=+FHngVid6P!kvv9^yE@=~5y+u0>^p4&9!WEjh6lPQecrR0i`Wi$ zOZQcHDBs62LdH@Iuj7wH$*A6%7Cf>>h5a2@V%ejL%b$3L$nT6bkLrnWD5B_tHa$ig zhjD0CAloWZ9bY~VLv4jsI*b@Kk@{2^EBHwGs+X8~7gyE@721gJYx@G7p6)Q(oXz9d zW^(0Tq8~<;mI~#59CUy$P|xw}uJGH!D5sB&IXh4Qc+&ch=>TT2FYVjOgjO4_k@KaD zX9TC{qSIRAPoHKR>wGQkK=%%W!3?c5pgPa)raDbC)=>FiGF(?;s+Es)Y`XH5a&6Zx zF7%PZqb+PGgEy6h#M^*efmR?!t2o#2m59OQw2a&CKRmGNzj_pr8BZ2+9T_=JhBLqY zjo9a;#1%+)4I^EJ^v3@ibPXD#>vRKhHuwt_KjAV%)8OUQ;7z&-FBkk2r|Y}0{qQRl z=Sx?T==C!XIxFq4+=Lk2LR=n8z`^KtD$mk9Pc@ZiE1hR)o~L#RkK4zDr;_m8N#&`| d^VCy$?xyop=Xq8x;c?ra@YEBYdypBr{~Hkv&(Z(@ literal 0 HcmV?d00001 diff --git a/target/classes/com/gameplatform/server/model/dto/auth/LoginResponse.class b/target/classes/com/gameplatform/server/model/dto/auth/LoginResponse.class new file mode 100644 index 0000000000000000000000000000000000000000..2557f1e0e9d426e7d991d4afbb1a25da866a9e2c GIT binary patch literal 1745 zcmbW1ZBNrs6vxlq129$?j4?q(=JPg``hX9BnxN4HG9Mzy`?6g}ach^hBl@9CG?AG2 z!VlnwGXBqP>AKpzF-v;yJv|S<^S`J2{paT|B6>obla!+LIAt`-(wLx`W8=ioJBHQP z-y9yxrYC6Zv1ys!Q$gu!?O=i?V0k7DM>;v0r2IH(8cor(p!KHR)!Rl__Bw|5!FIa3 zE1eVR=v})dJ9^8rb))Yc=?%MWTJNOWvn^K&%6axjY3+aRVP9+v%I|rOX|=bw$c#p_ zTp@2Xo6>dnIe_hR8kJ}sDdeY~=}33S5+ru{%{h%0IAWp?2F}gK^BOJD1>m!Br-f!C zZOJnFgm^@Q$S}4t1f##=QmZ>@XK?K)sKq_wV5>y?>hA zv-?g{zBGfIh)mTQj}cwMou@G6V|Zdhu}kqBsTFghR!oUnF&Jva)nlC^!Jh-36`VE@ zC$Q%0KS_cdCR_hbxv$vx3AhS! zM}o6q@C~ZMtAh8NSXD3e)uHl!w5FoVzDE$veRJ1>YKYGY9GIt`$Ww^(OeONHCi4^q za`-&cBY0T-kY`r$+@xDDRj}8Af2L3I3k*?x3W+>xDu{zODUBa`uCJU@G0rnHf`_Ld zr;;N6i4vzG=@A2isvrfgQvSI~a`4dn wEXH|CBY60$2zi!}27hab`YgwJ<`a1yChM~t=P8fi;jc5~Sy4QX@J!LhU!?B>HUIzs literal 0 HcmV?d00001 diff --git a/target/classes/com/gameplatform/server/model/entity/admin/AdminUser.class b/target/classes/com/gameplatform/server/model/entity/admin/AdminUser.class new file mode 100644 index 0000000000000000000000000000000000000000..3ad556f581621a29f4dc1efeaab11a414d9f048f GIT binary patch literal 1965 zcmbW1TTc@~6vxk$wo755(3V006}+~ftQSP(VnQ%Rld426?@K$DCEYIB-D3P!nrKXX z@B{dvjQ=w&ZJA78e3>(IZu6US&hDSTzkUIdGSj@Bjw6*hDIr4(U?u+WD8oa1-{d8eRlpF9nh(oXInX@U+ zp7yyLMW2JP{>F_?ImL`kMK&bEpeb1~XH$t4=_q#NcEqX$n-=MspiC{4ZY=A&cxS0| zPwe^9sRlK-`3japoD|p%o0e2W`@DV`k=(;81!b#VOTKUWCo(*APnrsuiuj3XO2 zqf_I;m7Luw!wFgYPBVkKyFQ#fi&XmqO@Ib*S2Y%VKB6CjEZ91`40H#oC1?b+mwOxf zy?$R7F4+v7LHiLn;JnynH25+qr#|X8kb?(~V+TPytjX6Nb58c=b&cngmN7O7d@~kS z%szGr9#thVh)QDal*G6xi5XK8lLMJ0^oi0~mvM3+1!QXFCy8&$?Iu<>LrJ%=+THza zx`UITyL1oh)!^@BeT6YW{oqMsaG5Ib;^0k4eo1;|^#@t{)mar??08V;YR2L>3tB;3 z3WEV5XqE08cqVk7DI?DVS~KxX=seRyc)0dGo~+_=j6BmiPtM5m(9AQf^W=x{@C@{L z@`~q?k*A>Z6pTEN%{&F2XJ!Zw&ti|KsCb?jd1iHZknHYou@Q}hri7p&!XaaVdS~4^UNE0HqAWOb)JPGJp7O7@hmBx JEvyOJ{s+UWApHOU literal 0 HcmV?d00001 diff --git a/target/classes/com/gameplatform/server/model/entity/agent/Agent.class b/target/classes/com/gameplatform/server/model/entity/agent/Agent.class new file mode 100644 index 0000000000000000000000000000000000000000..35a8571ec88ca15e91d56f559184badec6e61fe2 GIT binary patch literal 2181 zcmb7_TTc@~6oAi^UTj_Ll|rG2h?f?WMZ5!|1!FX+NCNUcY{#;s-EDTK82^>$@IaOIWyopw5 z?|YKMrz|KdLyw2yXFq7Y<>3h#W-O?XAsI?8`yr|3ET|IyWY2d!8SZeb*b!v8ZovZF zV32kK!KG+55V@m;K+LAL37Vz1vl7QrDu z?x>&^eY%}oZ0wvg}eMhmri5Jw5)BhJ-fWd>g=kWzoVXf*{#j+q0$ zA2{NTOWn?>nO~(@heaH&7`{1L@$KL)M&qs|4z!Xuze?h`Dv2|yBu=D~IA9z~Iw_MYS`yk8ufx2jW>(Md&<}I#1rnvjz`LJd-+4VGIxT zV8oMEJnKfDyv|cJ@;o&2O^JTmf>be<_A&to%BN#`k#;h~uv@st(M z6C=;G&NFS~d1~gF)_G>e@X*^B@l+JgGvhqZ>O2)A&vWxU&+0t0V|eI|j(DnyX9G4- zrO)%c&NFA^*}{Ki!~2@od8%W0=(l3Tv!HmkjXaAw&%BZ6rI}|@=ea(Hhkmn0JWGn_ J6|OO8`~$=4JX`<( literal 0 HcmV?d00001 diff --git a/target/classes/com/gameplatform/server/security/JwtService.class b/target/classes/com/gameplatform/server/security/JwtService.class new file mode 100644 index 0000000000000000000000000000000000000000..8c612a29deded9401cdda34f9155b111674296d2 GIT binary patch literal 4599 zcmcIo`ClAo9e!qE8Q2Vg(1esEZ3ra^M_^*o7|kZJfhG-fAu$WYin6l_hyzSXnRUlaI7f$#xAbXjQPgV9bkQ+e#bWRnyCH=6Z(Zxe5;TB+6<7Iq}|& zOHy~6hTE`HL1R{ULrb0@cYROq^iT}#*sbDr4IS8{;11K)=Um%b6f>SZFD%^^>5^l5 zOZxp{i9F4@f{{+<41Qg(9R>S(riTKJ*YvcrRP<~;DbkMcNDyzrKHQ<=P7V9fsh~e? z7xb)A5XHRV&Du_Z!kq=-ly!_QdPy#sX+ad`#gc;CeQ!3A-KF6!bSr2x(rMwk6Vl)@ z(<=GiDWfqQ!eJFX8hX*EV22NtXBLD$5+Fz=mc6K8S5F992C%oSd;6sFP9nnB*2orb>& zZ&qxd1hX}*YJpJ{j6gb6dVlYRyiF4u}1BieOAL;aF2p*G;73lOCocQ z={*?omua{3zX?MaR&lR}^SHo7D@VfPTpBN(M?#e9iDIoUH>i)kWMBsHwoNtO39MhOjL(%EJ@d;VI{bPUh5twvm8 z?Ub0X6H7%Q>E5d03TR$5oi|LvIaDJ|6{Q$71!~(G_#iBl^k&{pS9VjawLjgOddPI8tdNR#71gA!EFsiJV?A$(WO6);$5ebo!$upJxmKZM;X4prJ znLJn85_a0iPZ^FWzr)3PFK5z}fyDoDV#Zm%3>1%EDYRVXVLHi31jl}4dWfM;dPaJl z<>+fv@g%P#W5UbX8HSP##}f_>=(!S+L@_St7N1VpJ9>)5Tpq zr&}uo_yXypxaK+Rt1as!G53O%s?&$JsSOQ=MY?){AXL@4!hC=2pCPcqxY8 z;I}G%Cm#~O=dn@l3Z}Kd+p-?;2XBiDL9HryqSB<0FmUanVP*2d?M~SCe5tr;#rR>Y zVw9Jr8syV8Jgry?MGPDHeA4tpoG!f_!yoY{6@S+73jRWvH@K8A3Nsml_8MXH;k}jp zto-%r>+jx{zE^Dqh*m;Vwe90pSsd#jQJ{@jY<|ps*A=dczwu(f0h3zMQ}GYJS+*wS zkrIR&2l$kW@J$*)n|#c1)+XOx9P4<0jN)?~KhLkHI7T@R_Pqe5|0dMd&Zp7Re+$jP zP3(9EDi?R0s_*NlU&gK**xT260|%b-<$QsE+kNicsONKZKlY#pdoe)WU*ujNS1B1^ z!k0P9*%SB*wab~bB|?rCd=*cU?rU7#iO7E`hL7}pDjLbl=f~Ia4em5W$OFMB>38tmz%yl#mNk;tUBXsQu@l#QAEgmdg&z{YpZl!O;|52`e3Rn~ g{I!w!*Zf|_9|*xM{1U&yt6Yz9?XUPd{)vwN0pAZnPXGV_ literal 0 HcmV?d00001 diff --git a/target/classes/com/gameplatform/server/security/SecurityConfig.class b/target/classes/com/gameplatform/server/security/SecurityConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..3b8504ed11f247a5db233bde888cfffa24f3d3cb GIT binary patch literal 4317 zcmcIo`F9gl6#ibDHfb1W2%7~3fdXxz3{*rVfV3fO2B@UARu-L1r_IpGOq@(gi-HT{ zzVH4Q>JR0ddi40S$G^$r_cD_)Mp2TADqfUd&!MbWfo5sA-$tae?Np?%_DOA`03N6=>5-MbF8bHw}TluI{ws zPN>C#YuXdzu9i1$IPRocH1v{ddNZo-*yHAenyj$6i6|7T4r7gicEkkw8)3g;WGT{} zGF&xNCTBdakO_1Mg!H02E>P-vSX&x1eo5_#I0PbcQ|lD0mpcmSj$4e=zvaaGD)#*^5HV6T9pYPwg_JjYdgdZOsX{xDPp2XIheWNGC-j%qd{ z@lXwf&i=*D$`lNpS__(&JFj_q&TxwYd%7;C7vSz&m*}n<@-u)gc}n2gBeJTqU)POd zF^WDst>7@8AwmjW3F%M!S}TttJs`Vam{wBb1%2_vE4D2@s2thYl6R#p1;yg~dC zVxhr{nIVB)bu(O=(33$&|R!Bi!FRZC)9hPz+JdO#puKT{XWX)I21KPMHWtJ4;V0A25nkUn}QqJZJ zWu=-W%Rr~YlT0XjF>Tt$g;G9ixI_EvX@ESekrH4{GwXVgaPzIxdxL z&&(UcX3?Zm`)%9tG|zNw&h^$+N?o2Y?Wx-QQgy)4Z2GgkP+JpOR+j~tUs)@R>Y}Pj zp*t)spB>XW9~i670|Hl8AX@cnXrg;qpvjmP7_IA#`Cv8@f(NJ&h-RFUs~bchVZN>c z$YePo_RESbaJa7eI>GR{N^UudbYP|~<6^t3qA6kN8a%GdDML-!GS^0UWLl-^ieZ!* z8*m)2=($=@CyO!g9Iyui676QdztCVBJmPr2}zS@bJJ zYFWHBaF`}ya0D){1f(i95=C^v>ruGU2~4e(*gx-Da{n=uR?6ByqHpbuD5j;gGnD@S zqa{z&@*U7O`3-@gm2hy4Sl{kjQQVexzqKNEpQ>C?eY@XTA-ikDO1oQeCyF%QQ*Z@W zW4OwJ52E-GABFL;f=}=%E8)V`&9tYSNkdJSOCyWAHqLZ)T!v~D3;ROv@9KOEnEW%~ zjAoBnMzJ&PIFqG9qAG2JYhl$$4dc*E!Khx|_@u9LY2NC~XdpSMSysmM3>F>btZnn! zZt<8gm_!>MaO-XLZA45-3mZ+i$gi9RABuzgD*ghzh)e9Tq+^ILDam|` ztEJH1y>kcyvsg8UwUWl?utCy|bLfzC^BkU#bn7g(|4as7$qq!=UPoo?v4#vA`L`9D zu$wQpZnpMuP8zV$R}vUNb$AKGG-QNxErK23W&Whiqoe|_;4)dHg?*%QCz0QCsa3!-$@Hf+WrwvX|FdXjX({V80-t6UetYy37rD{oo;HzCWu z&fge+hZx!S85{!>u!B|sUeKDt&7k!r-VR#t;x0ZT=Me_5m!bZgo9$!k3kK3;|4UMr W^efV7(yvKxlYT?`ExyN(==c}kbVHc{ literal 0 HcmV?d00001 diff --git a/target/classes/com/gameplatform/server/service/UserService.class b/target/classes/com/gameplatform/server/service/UserService.class new file mode 100644 index 0000000000000000000000000000000000000000..8706941703bcd54efcefdadf451eece3de7a27f8 GIT binary patch literal 4414 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 + + + + + + + + + + + + id, username, email, created_at + + + + + + + + INSERT INTO users (username, email, created_at) + VALUES (#{username}, #{email}, NOW()) + + + + DELETE FROM users WHERE id = #{id} + + + + diff --git a/target/classes/mapper/admin/AdminUserMapper.xml b/target/classes/mapper/admin/AdminUserMapper.xml new file mode 100644 index 0000000..98f50e5 --- /dev/null +++ b/target/classes/mapper/admin/AdminUserMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/target/classes/mapper/agent/AgentMapper.xml b/target/classes/mapper/agent/AgentMapper.xml new file mode 100644 index 0000000..621835b --- /dev/null +++ b/target/classes/mapper/agent/AgentMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/target/classes/schema.sql b/target/classes/schema.sql new file mode 100644 index 0000000..aeae656 --- /dev/null +++ b/target/classes/schema.sql @@ -0,0 +1,8 @@ +-- Initial database schema for game_platform +CREATE TABLE IF NOT EXISTS users ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(50) NOT NULL, + email VARCHAR(120) NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); +