commit be437a360d6fa67f9c189d5e1b502448003fdb8a Author: zyh Date: Sun Aug 24 15:33:03 2025 +0800 first commit 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 0000000..91b7441 Binary files /dev/null and b/target/classes/com/gameplatform/server/GamePlatformServerApplication.class differ diff --git a/target/classes/com/gameplatform/server/controller/UserController.class b/target/classes/com/gameplatform/server/controller/UserController.class new file mode 100644 index 0000000..1b5da30 Binary files /dev/null and b/target/classes/com/gameplatform/server/controller/UserController.class differ diff --git a/target/classes/com/gameplatform/server/controller/auth/AuthController$1.class b/target/classes/com/gameplatform/server/controller/auth/AuthController$1.class new file mode 100644 index 0000000..b7a7381 Binary files /dev/null and b/target/classes/com/gameplatform/server/controller/auth/AuthController$1.class differ 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 0000000..4bba42f Binary files /dev/null and b/target/classes/com/gameplatform/server/controller/auth/AuthController.class differ diff --git a/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$1.class b/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$1.class new file mode 100644 index 0000000..813e541 Binary files /dev/null and b/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler$1.class differ 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 0000000..8e7751d Binary files /dev/null and b/target/classes/com/gameplatform/server/exception/GlobalExceptionHandler.class differ diff --git a/target/classes/com/gameplatform/server/mapper/UserMapper.class b/target/classes/com/gameplatform/server/mapper/UserMapper.class new file mode 100644 index 0000000..e344c5c Binary files /dev/null and b/target/classes/com/gameplatform/server/mapper/UserMapper.class differ diff --git a/target/classes/com/gameplatform/server/mapper/admin/AdminUserMapper.class b/target/classes/com/gameplatform/server/mapper/admin/AdminUserMapper.class new file mode 100644 index 0000000..4b5753f Binary files /dev/null and b/target/classes/com/gameplatform/server/mapper/admin/AdminUserMapper.class differ diff --git a/target/classes/com/gameplatform/server/mapper/agent/AgentMapper.class b/target/classes/com/gameplatform/server/mapper/agent/AgentMapper.class new file mode 100644 index 0000000..b7dfaa1 Binary files /dev/null and b/target/classes/com/gameplatform/server/mapper/agent/AgentMapper.class differ diff --git a/target/classes/com/gameplatform/server/model/User.class b/target/classes/com/gameplatform/server/model/User.class new file mode 100644 index 0000000..9b4483a Binary files /dev/null and b/target/classes/com/gameplatform/server/model/User.class differ 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 0000000..7d38703 Binary files /dev/null and b/target/classes/com/gameplatform/server/model/dto/auth/LoginRequest.class differ 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 0000000..2557f1e Binary files /dev/null and b/target/classes/com/gameplatform/server/model/dto/auth/LoginResponse.class differ 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 0000000..3ad556f Binary files /dev/null and b/target/classes/com/gameplatform/server/model/entity/admin/AdminUser.class differ 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 0000000..35a8571 Binary files /dev/null and b/target/classes/com/gameplatform/server/model/entity/agent/Agent.class differ 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 0000000..8c612a2 Binary files /dev/null and b/target/classes/com/gameplatform/server/security/JwtService.class differ 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 0000000..3b8504e Binary files /dev/null and b/target/classes/com/gameplatform/server/security/SecurityConfig.class differ 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 0000000..8706941 Binary files /dev/null and b/target/classes/com/gameplatform/server/service/UserService.class differ diff --git a/target/classes/com/gameplatform/server/service/auth/AuthService.class b/target/classes/com/gameplatform/server/service/auth/AuthService.class new file mode 100644 index 0000000..cbca48f Binary files /dev/null and b/target/classes/com/gameplatform/server/service/auth/AuthService.class differ diff --git a/target/classes/mapper/UserMapper.xml b/target/classes/mapper/UserMapper.xml new file mode 100644 index 0000000..9a7f4a0 --- /dev/null +++ b/target/classes/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/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 +); +