feat: 游戏房间
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
package com.webgame.webgamebackend.common.constants;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户相关常量
|
||||||
|
*/
|
||||||
|
public final class UserConstants {
|
||||||
|
|
||||||
|
private UserConstants() {
|
||||||
|
// 工具类禁止实例化
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像背景色板(14 种 Material Design 颜色)
|
||||||
|
*/
|
||||||
|
private static final String[] AVATAR_COLORS = {
|
||||||
|
"#E53935", "#D81B60", "#8E24AA", "#5E35B1",
|
||||||
|
"#3949AB", "#1E88E5", "#039BE5", "#00ACC1",
|
||||||
|
"#00897B", "#43A047", "#7CB342", "#C0CA33",
|
||||||
|
"#FB8C00", "#F4511E"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认昵称(当用户昵称为空时使用)
|
||||||
|
*/
|
||||||
|
private static final String DEFAULT_NICKNAME = "玩家";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据昵称生成默认头像 data URI(本地 SVG,无需外部 API)
|
||||||
|
*
|
||||||
|
* @param nickName 用户昵称
|
||||||
|
* @return data:image/svg+xml;base64,... 格式的头像
|
||||||
|
*/
|
||||||
|
public static String generateDefaultAvatar(String nickName) {
|
||||||
|
String name = (nickName == null || nickName.isBlank()) ? DEFAULT_NICKNAME : nickName.trim();
|
||||||
|
// 取首字符作为头像文字
|
||||||
|
String initial = name.substring(0, 1);
|
||||||
|
// 根据昵称 hash 选颜色,同一昵称颜色不变
|
||||||
|
int colorIndex = Math.abs(name.hashCode()) % AVATAR_COLORS.length;
|
||||||
|
String bgColor = AVATAR_COLORS[colorIndex];
|
||||||
|
|
||||||
|
String svg = """
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<circle cx="50" cy="50" r="50" fill="%s"/>
|
||||||
|
<text x="50" y="62" text-anchor="middle" font-size="40"
|
||||||
|
fill="#FFFFFF" font-family="Arial, sans-serif">%s</text>
|
||||||
|
</svg>
|
||||||
|
""".formatted(bgColor, escapeXml(initial));
|
||||||
|
|
||||||
|
String base64 = Base64.getEncoder().encodeToString(svg.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return "data:image/svg+xml;base64," + base64;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XML 特殊字符转义(防止文字中特殊字符破坏 SVG 结构)
|
||||||
|
*/
|
||||||
|
private static String escapeXml(String text) {
|
||||||
|
return text
|
||||||
|
.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace("\"", """)
|
||||||
|
.replace("'", "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Positive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建房间请求,对应前端 CreateRoomRequest
|
||||||
|
*/
|
||||||
|
public record CreateRoomRequest(
|
||||||
|
/** 游戏 ID */
|
||||||
|
@NotBlank(message = "游戏ID不能为空")
|
||||||
|
String gameId,
|
||||||
|
|
||||||
|
/** 房间名称(可选,留空由后端自动生成) */
|
||||||
|
String name,
|
||||||
|
|
||||||
|
/** 房间密码(可选,留空为公开房间) */
|
||||||
|
String password,
|
||||||
|
|
||||||
|
/** 玩法模式 */
|
||||||
|
@NotBlank(message = "玩法模式不能为空")
|
||||||
|
String mode,
|
||||||
|
|
||||||
|
/** 底注金额 */
|
||||||
|
@NotNull(message = "底注金额不能为空")
|
||||||
|
@Positive(message = "底注金额必须为正数")
|
||||||
|
Integer stakes,
|
||||||
|
|
||||||
|
/** 最大玩家数 */
|
||||||
|
@NotNull(message = "最大玩家数不能为空")
|
||||||
|
@Positive(message = "最大玩家数必须为正数")
|
||||||
|
Integer maxPlayers
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建房间响应
|
||||||
|
*/
|
||||||
|
public record CreateRoomResponse(RoomItemResponse room) {}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏配置 DTO,对应前端 GameConfig
|
||||||
|
*/
|
||||||
|
public record GameConfigDto(
|
||||||
|
int maxPlayers,
|
||||||
|
List<String> modes,
|
||||||
|
List<Integer> stakesOptions
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.webgame.webgamebackend.entities.GameConfigEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏列表项响应,对应前端 GameItem
|
||||||
|
*/
|
||||||
|
public record GameItemResponse(
|
||||||
|
String id,
|
||||||
|
String icon,
|
||||||
|
String name,
|
||||||
|
String description,
|
||||||
|
String gameplay,
|
||||||
|
GameConfigDto config
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 从实体转换
|
||||||
|
*
|
||||||
|
* @param entity 游戏配置实体
|
||||||
|
* @return 响应 DTO
|
||||||
|
*/
|
||||||
|
public static GameItemResponse from(GameConfigEntity entity) {
|
||||||
|
List<String> modes = JSON.parseArray(entity.getModes(), String.class);
|
||||||
|
List<Integer> stakesOptions = JSON.parseArray(entity.getStakesOptions(), Integer.class);
|
||||||
|
return new GameItemResponse(
|
||||||
|
entity.getGameId(),
|
||||||
|
entity.getIcon(),
|
||||||
|
entity.getName(),
|
||||||
|
entity.getDescription(),
|
||||||
|
entity.getGameplay(),
|
||||||
|
new GameConfigDto(entity.getMaxPlayers(), modes, stakesOptions)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏列表响应
|
||||||
|
*/
|
||||||
|
public record GameListResponse(List<GameItemResponse> list) {}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加入房间请求
|
||||||
|
*/
|
||||||
|
public record JoinRoomRequest(
|
||||||
|
/** 房间 ID */
|
||||||
|
@NotBlank(message = "房间ID不能为空")
|
||||||
|
String roomId,
|
||||||
|
|
||||||
|
/** 房间密码(公开房间无需传) */
|
||||||
|
String password
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 踢出玩家请求
|
||||||
|
*/
|
||||||
|
public record KickPlayerRequest(
|
||||||
|
@NotBlank(message = "房间ID不能为空")
|
||||||
|
String roomId,
|
||||||
|
|
||||||
|
@NotBlank(message = "目标用户ID不能为空")
|
||||||
|
String userId
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 离开房间请求
|
||||||
|
*/
|
||||||
|
public record LeaveRoomRequest(
|
||||||
|
@NotBlank(message = "房间ID不能为空")
|
||||||
|
String roomId
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 准备/取消准备请求
|
||||||
|
*/
|
||||||
|
public record ReadyRequest(
|
||||||
|
@NotBlank(message = "房间ID不能为空")
|
||||||
|
String roomId
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.GameRoomEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房间信息响应,对应前端 RoomItem
|
||||||
|
*/
|
||||||
|
public record RoomItemResponse(
|
||||||
|
String id,
|
||||||
|
String name,
|
||||||
|
String gameIcon,
|
||||||
|
String gameId,
|
||||||
|
int maxPlayers,
|
||||||
|
List<RoomPlayerResponse> players,
|
||||||
|
String status,
|
||||||
|
String statusText,
|
||||||
|
int stakes,
|
||||||
|
String mode,
|
||||||
|
String creatorName
|
||||||
|
) {
|
||||||
|
/** 状态 → 展示文本映射 */
|
||||||
|
private static final Map<String, String> STATUS_TEXT_MAP = Map.of(
|
||||||
|
"waiting", "等待中",
|
||||||
|
"playing", "进行中",
|
||||||
|
"finished", "已结束"
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从实体和玩家列表组装响应
|
||||||
|
*
|
||||||
|
* @param room 房间实体
|
||||||
|
* @param players 房间内玩家列表
|
||||||
|
* @param gameIcon 游戏图标
|
||||||
|
* @param creatorName 房主昵称
|
||||||
|
* @return 响应 DTO
|
||||||
|
*/
|
||||||
|
public static RoomItemResponse from(
|
||||||
|
GameRoomEntity room,
|
||||||
|
List<RoomPlayerResponse> players,
|
||||||
|
String gameIcon,
|
||||||
|
String creatorName) {
|
||||||
|
return new RoomItemResponse(
|
||||||
|
room.getId(),
|
||||||
|
room.getName(),
|
||||||
|
gameIcon,
|
||||||
|
room.getGameId(),
|
||||||
|
room.getMaxPlayers(),
|
||||||
|
players,
|
||||||
|
room.getStatus(),
|
||||||
|
STATUS_TEXT_MAP.getOrDefault(room.getStatus(), "未知"),
|
||||||
|
room.getStakes(),
|
||||||
|
room.getMode(),
|
||||||
|
creatorName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房间列表响应
|
||||||
|
*/
|
||||||
|
public record RoomListResponse(List<RoomItemResponse> list) {}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房间内玩家信息,对应前端 RoomPlayer
|
||||||
|
*/
|
||||||
|
public record RoomPlayerResponse(
|
||||||
|
String avatar,
|
||||||
|
String nickname
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.game;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始游戏请求
|
||||||
|
*/
|
||||||
|
public record StartGameRequest(
|
||||||
|
@NotBlank(message = "房间ID不能为空")
|
||||||
|
String roomId
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.webgame.webgamebackend.common.dto.user;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户信息请求
|
||||||
|
*
|
||||||
|
* 所有字段可选,传什么更新什么。
|
||||||
|
*/
|
||||||
|
public record UpdateUserInfoRequest(
|
||||||
|
String nickName,
|
||||||
|
String avatar,
|
||||||
|
String signature,
|
||||||
|
Integer age,
|
||||||
|
Integer sex,
|
||||||
|
LocalDate birthday
|
||||||
|
) {}
|
||||||
@@ -21,6 +21,19 @@ public enum ErrorCode {
|
|||||||
|
|
||||||
// ========== 用户模块 ==========
|
// ========== 用户模块 ==========
|
||||||
USER_NOT_FOUND(2001, "用户不存在"),
|
USER_NOT_FOUND(2001, "用户不存在"),
|
||||||
|
|
||||||
|
// ========== 游戏模块 ==========
|
||||||
|
GAME_NOT_FOUND(3001, "游戏不存在或已下架"),
|
||||||
|
ROOM_NOT_FOUND(3002, "房间不存在"),
|
||||||
|
ROOM_FULL(3003, "房间已满"),
|
||||||
|
ROOM_PASSWORD_ERROR(3004, "房间密码错误"),
|
||||||
|
ROOM_ALREADY_STARTED(3005, "游戏已开始,无法加入"),
|
||||||
|
NOT_ROOM_OWNER(3006, "非房主无权操作"),
|
||||||
|
PLAYER_NOT_IN_ROOM(3007, "玩家不在该房间"),
|
||||||
|
NOT_ALL_READY(3008, "有玩家尚未准备"),
|
||||||
|
PLAYER_COUNT_INSUFFICIENT(3009, "人数不足,无法开始"),
|
||||||
|
BALANCE_INSUFFICIENT(3010, "余额不足"),
|
||||||
|
ALREADY_IN_ROOM(3011, "已在房间中"),
|
||||||
;
|
;
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.webgame.webgamebackend.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.annotation.SaIgnore;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import com.webgame.webgamebackend.common.dto.ApiResponse;
|
||||||
|
import com.webgame.webgamebackend.common.dto.game.*;
|
||||||
|
import com.webgame.webgamebackend.service.GameService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏控制器
|
||||||
|
*
|
||||||
|
* 管理游戏列表查询、房间的创建/加入/离开/踢人/准备/开始等操作。
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/game")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GameController {
|
||||||
|
|
||||||
|
private final GameService gameService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取游戏列表
|
||||||
|
*
|
||||||
|
* 无需登录即可调用。
|
||||||
|
*/
|
||||||
|
@SaIgnore
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ApiResponse<GameListResponse> list() {
|
||||||
|
GameListResponse result = gameService.getGameList();
|
||||||
|
return ApiResponse.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取房间列表
|
||||||
|
*
|
||||||
|
* 支持按状态筛选。无需登录即可调用。
|
||||||
|
*
|
||||||
|
* @param gameId 游戏 ID(必填)
|
||||||
|
* @param status 状态筛选(可选)
|
||||||
|
*/
|
||||||
|
@SaIgnore
|
||||||
|
@GetMapping("/room/list")
|
||||||
|
public ApiResponse<RoomListResponse> roomList(
|
||||||
|
@RequestParam String gameId,
|
||||||
|
@RequestParam(required = false) String status) {
|
||||||
|
RoomListResponse result = gameService.getRoomList(gameId, status);
|
||||||
|
return ApiResponse.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建房间
|
||||||
|
*
|
||||||
|
* 需登录。房主自动加入房间并设为已准备。
|
||||||
|
*/
|
||||||
|
@PostMapping("/room/create")
|
||||||
|
public ApiResponse<CreateRoomResponse> createRoom(@Valid @RequestBody CreateRoomRequest request) {
|
||||||
|
String userId = StpUtil.getLoginIdAsString();
|
||||||
|
CreateRoomResponse result = gameService.createRoom(request, userId);
|
||||||
|
return ApiResponse.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加入房间
|
||||||
|
*
|
||||||
|
* 需登录。公开房间无需传密码。
|
||||||
|
*/
|
||||||
|
@PostMapping("/room/join")
|
||||||
|
public ApiResponse<RoomItemResponse> joinRoom(@Valid @RequestBody JoinRoomRequest request) {
|
||||||
|
String userId = StpUtil.getLoginIdAsString();
|
||||||
|
RoomItemResponse result = gameService.joinRoom(request, userId);
|
||||||
|
return ApiResponse.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 离开房间
|
||||||
|
*
|
||||||
|
* 需登录。房主离开时顺位转让。无人时房间自动结束。
|
||||||
|
*/
|
||||||
|
@PostMapping("/room/leave")
|
||||||
|
public ApiResponse<Void> leaveRoom(@Valid @RequestBody LeaveRoomRequest request) {
|
||||||
|
String userId = StpUtil.getLoginIdAsString();
|
||||||
|
gameService.leaveRoom(request.roomId(), userId);
|
||||||
|
return ApiResponse.ok(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 踢出玩家
|
||||||
|
*
|
||||||
|
* 需登录。仅房主可操作。
|
||||||
|
*/
|
||||||
|
@PostMapping("/room/kick")
|
||||||
|
public ApiResponse<Void> kickPlayer(@Valid @RequestBody KickPlayerRequest request) {
|
||||||
|
String ownerId = StpUtil.getLoginIdAsString();
|
||||||
|
gameService.kickPlayer(request.roomId(), ownerId, request.userId());
|
||||||
|
return ApiResponse.ok(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换准备状态
|
||||||
|
*
|
||||||
|
* 需登录。在等待中的房间里切换准备/取消准备。
|
||||||
|
*/
|
||||||
|
@PostMapping("/room/ready")
|
||||||
|
public ApiResponse<Boolean> toggleReady(@Valid @RequestBody ReadyRequest request) {
|
||||||
|
String userId = StpUtil.getLoginIdAsString();
|
||||||
|
boolean ready = gameService.toggleReady(request.roomId(), userId);
|
||||||
|
return ApiResponse.ok(ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始游戏
|
||||||
|
*
|
||||||
|
* 需登录。仅房主可操作。需全部玩家准备且人数 >= 2。
|
||||||
|
* 开始后扣除所有玩家的底注。
|
||||||
|
*/
|
||||||
|
@PostMapping("/room/start")
|
||||||
|
public ApiResponse<Void> startGame(@Valid @RequestBody StartGameRequest request) {
|
||||||
|
String ownerId = StpUtil.getLoginIdAsString();
|
||||||
|
gameService.startGame(request.roomId(), ownerId);
|
||||||
|
return ApiResponse.ok(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
package com.webgame.webgamebackend.controller;
|
package com.webgame.webgamebackend.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import com.webgame.webgamebackend.common.dto.ApiResponse;
|
import com.webgame.webgamebackend.common.dto.ApiResponse;
|
||||||
|
import com.webgame.webgamebackend.common.dto.user.UpdateUserInfoRequest;
|
||||||
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
||||||
import com.webgame.webgamebackend.service.UserService;
|
import com.webgame.webgamebackend.service.UserService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户控制器
|
* 用户控制器
|
||||||
@@ -28,4 +29,16 @@ public class UserController {
|
|||||||
UserInfoResponse result = userService.getCurrentUserInfo();
|
UserInfoResponse result = userService.getCurrentUserInfo();
|
||||||
return ApiResponse.ok(result);
|
return ApiResponse.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前登录用户信息
|
||||||
|
*
|
||||||
|
* 所有字段可选,传什么更新什么。
|
||||||
|
*/
|
||||||
|
@PutMapping("/info")
|
||||||
|
public ApiResponse<UserInfoResponse> updateInfo(@Valid @RequestBody UpdateUserInfoRequest request) {
|
||||||
|
String accountId = StpUtil.getLoginIdAsString();
|
||||||
|
UserInfoResponse result = userService.updateUserInfo(accountId, request);
|
||||||
|
return ApiResponse.ok(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.webgame.webgamebackend.entities;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.base.UUIDBaseEntity;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏配置实体
|
||||||
|
*
|
||||||
|
* 存储每款游戏的元信息(图标、名称、玩法介绍)和可选配置(模式、底注档位)。
|
||||||
|
* 采用独立表而非硬编码,支持运营后台动态上下架游戏。
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@Table(name = "game_config")
|
||||||
|
public class GameConfigEntity extends UUIDBaseEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏唯一标识,如 snake、tetris
|
||||||
|
*/
|
||||||
|
@Column(name = "game_id", unique = true, nullable = false, length = 32)
|
||||||
|
private String gameId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏图标 emoji
|
||||||
|
*/
|
||||||
|
@Column(name = "icon", nullable = false, length = 10)
|
||||||
|
private String icon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏名称
|
||||||
|
*/
|
||||||
|
@Column(name = "name", nullable = false, length = 32)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简短描述
|
||||||
|
*/
|
||||||
|
@Column(name = "description", nullable = false, length = 256)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 玩法介绍(长文本)
|
||||||
|
*/
|
||||||
|
@Column(name = "gameplay", nullable = false, columnDefinition = "TEXT")
|
||||||
|
private String gameplay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认最大玩家数
|
||||||
|
*/
|
||||||
|
@Column(name = "max_players", nullable = false)
|
||||||
|
private Integer maxPlayers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可选玩法模式列表(JSON 数组字符串)
|
||||||
|
*/
|
||||||
|
@Column(name = "modes", nullable = false, length = 512)
|
||||||
|
private String modes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可选底注档位列表(JSON 数组字符串)
|
||||||
|
*/
|
||||||
|
@Column(name = "stakes_options", nullable = false, length = 256)
|
||||||
|
private String stakesOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序权重,越大越靠前
|
||||||
|
*/
|
||||||
|
@Column(name = "sort_order", nullable = false)
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用
|
||||||
|
*/
|
||||||
|
@Column(name = "enabled", nullable = false)
|
||||||
|
private Boolean enabled;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.webgame.webgamebackend.entities;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.base.UUIDBaseEntity;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏房间(桌台)实体
|
||||||
|
*
|
||||||
|
* 每个房间属于某款游戏,包含桌号、人数上限、玩法模式、底注等信息。
|
||||||
|
* 房间有三种状态:waiting(等待中)、playing(进行中)、finished(已结束)。
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@Table(name = "game_room")
|
||||||
|
public class GameRoomEntity extends UUIDBaseEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的游戏标识
|
||||||
|
*/
|
||||||
|
@Column(name = "game_id", nullable = false, length = 32)
|
||||||
|
private String gameId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该游戏下的桌号序号(自增),与 gameId 联合唯一
|
||||||
|
*/
|
||||||
|
@Column(name = "room_number", nullable = false)
|
||||||
|
private Integer roomNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 桌号显示名,如 "桌 01"
|
||||||
|
*/
|
||||||
|
@Column(name = "name", nullable = false, length = 32)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本房间最大玩家数
|
||||||
|
*/
|
||||||
|
@Column(name = "max_players", nullable = false)
|
||||||
|
private Integer maxPlayers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选定的玩法模式
|
||||||
|
*/
|
||||||
|
@Column(name = "mode", nullable = false, length = 32)
|
||||||
|
private String mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 底注金额
|
||||||
|
*/
|
||||||
|
@Column(name = "stakes", nullable = false)
|
||||||
|
private Integer stakes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房间密码(bcrypt 哈希),null 表示公开房间
|
||||||
|
*/
|
||||||
|
@Column(name = "password", length = 64)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房间状态:waiting / playing / finished
|
||||||
|
*/
|
||||||
|
@Column(name = "status", nullable = false, length = 16)
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房主 account_id
|
||||||
|
*/
|
||||||
|
@Column(name = "creator_id", nullable = false, length = 64)
|
||||||
|
private String creatorId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.webgame.webgamebackend.entities;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.base.UUIDBaseEntity;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房间玩家关联实体
|
||||||
|
*
|
||||||
|
* 记录玩家与房间的关联关系、座位号和准备状态。
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Accessors(chain = true)
|
||||||
|
@Table(name = "room_player")
|
||||||
|
public class RoomPlayerEntity extends UUIDBaseEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的房间 ID
|
||||||
|
*/
|
||||||
|
@Column(name = "room_id", nullable = false, length = 64)
|
||||||
|
private String roomId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的账号 ID
|
||||||
|
*/
|
||||||
|
@Column(name = "user_id", nullable = false, length = 64)
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 座位号(1 ~ maxPlayers)
|
||||||
|
*/
|
||||||
|
@Column(name = "seat_number", nullable = false)
|
||||||
|
private Integer seatNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已准备
|
||||||
|
*/
|
||||||
|
@Column(name = "ready_status", nullable = false)
|
||||||
|
private Boolean readyStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加入时间
|
||||||
|
*/
|
||||||
|
@Column(name = "join_time", nullable = false)
|
||||||
|
private LocalDateTime joinTime;
|
||||||
|
}
|
||||||
@@ -68,4 +68,10 @@ public class UserEntity extends UUIDBaseEntity {
|
|||||||
*/
|
*/
|
||||||
@Column(name = "birthday")
|
@Column(name = "birthday")
|
||||||
private LocalDate birthday;
|
private LocalDate birthday;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 金币余额,默认 1000
|
||||||
|
*/
|
||||||
|
@Column(name = "balance", nullable = false)
|
||||||
|
private Long balance = 1000L;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.webgame.webgamebackend.repository;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.GameConfigEntity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏配置 Repository
|
||||||
|
*/
|
||||||
|
public interface GameConfigRepository extends JpaRepository<GameConfigEntity, String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据游戏标识查询
|
||||||
|
*
|
||||||
|
* @param gameId 游戏标识
|
||||||
|
* @return 游戏配置实体
|
||||||
|
*/
|
||||||
|
Optional<GameConfigEntity> findByGameId(String gameId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有启用的游戏,按排序权重升序
|
||||||
|
*
|
||||||
|
* @return 启用的游戏列表
|
||||||
|
*/
|
||||||
|
List<GameConfigEntity> findByEnabledTrueOrderBySortOrderAsc();
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.webgame.webgamebackend.repository;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.GameRoomEntity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏房间 Repository
|
||||||
|
*/
|
||||||
|
public interface GameRoomRepository extends JpaRepository<GameRoomEntity, String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据游戏 ID 和状态查询房间列表
|
||||||
|
*
|
||||||
|
* @param gameId 游戏标识
|
||||||
|
* @param status 房间状态
|
||||||
|
* @return 房间列表
|
||||||
|
*/
|
||||||
|
List<GameRoomEntity> findByGameIdAndStatus(String gameId, String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据游戏 ID 查询所有房间(不限状态)
|
||||||
|
*
|
||||||
|
* @param gameId 游戏标识
|
||||||
|
* @return 房间列表
|
||||||
|
*/
|
||||||
|
List<GameRoomEntity> findByGameId(String gameId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定游戏下最大的桌号
|
||||||
|
*
|
||||||
|
* @param gameId 游戏标识
|
||||||
|
* @return 最大桌号,可能为 null
|
||||||
|
*/
|
||||||
|
@Query("SELECT MAX(r.roomNumber) FROM GameRoomEntity r WHERE r.gameId = :gameId")
|
||||||
|
Integer findMaxRoomNumberByGameId(@Param("gameId") String gameId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 ID 查询房间(带悲观写锁,防止并发问题)
|
||||||
|
*
|
||||||
|
* @param id 房间 ID
|
||||||
|
* @return 房间实体
|
||||||
|
*/
|
||||||
|
@Query("SELECT r FROM GameRoomEntity r WHERE r.id = :id")
|
||||||
|
Optional<GameRoomEntity> findByIdForUpdate(@Param("id") String id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.webgame.webgamebackend.repository;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.entities.RoomPlayerEntity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房间玩家关联 Repository
|
||||||
|
*/
|
||||||
|
public interface RoomPlayerRepository extends JpaRepository<RoomPlayerEntity, String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询房间内所有玩家
|
||||||
|
*
|
||||||
|
* @param roomId 房间 ID
|
||||||
|
* @return 玩家列表
|
||||||
|
*/
|
||||||
|
List<RoomPlayerEntity> findByRoomId(String roomId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定房间和用户的关联记录
|
||||||
|
*
|
||||||
|
* @param roomId 房间 ID
|
||||||
|
* @param userId 用户 ID
|
||||||
|
* @return 关联记录
|
||||||
|
*/
|
||||||
|
Optional<RoomPlayerEntity> findByRoomIdAndUserId(String roomId, String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计房间当前玩家人数
|
||||||
|
*
|
||||||
|
* @param roomId 房间 ID
|
||||||
|
* @return 玩家人数
|
||||||
|
*/
|
||||||
|
int countByRoomId(String roomId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定房间和用户的关联
|
||||||
|
*
|
||||||
|
* @param roomId 房间 ID
|
||||||
|
* @param userId 用户 ID
|
||||||
|
*/
|
||||||
|
void deleteByRoomIdAndUserId(String roomId, String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断用户是否已在某房间中
|
||||||
|
*
|
||||||
|
* @param userId 用户 ID
|
||||||
|
* @return 是否存在关联
|
||||||
|
*/
|
||||||
|
boolean existsByUserId(String userId);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.webgame.webgamebackend.repository;
|
|||||||
import com.webgame.webgamebackend.entities.UserEntity;
|
import com.webgame.webgamebackend.entities.UserEntity;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,4 +15,9 @@ public interface UserRepository extends JpaRepository<UserEntity, String> {
|
|||||||
* 根据账号 ID 查询用户
|
* 根据账号 ID 查询用户
|
||||||
*/
|
*/
|
||||||
Optional<UserEntity> findByAccountId(String accountId);
|
Optional<UserEntity> findByAccountId(String accountId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据账号 ID 列表批量查询用户
|
||||||
|
*/
|
||||||
|
List<UserEntity> findByAccountIdIn(List<String> accountIds);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.webgame.webgamebackend.service;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.common.dto.game.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏服务接口
|
||||||
|
*/
|
||||||
|
public interface GameService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取游戏列表
|
||||||
|
*
|
||||||
|
* @return 所有启用的游戏信息
|
||||||
|
*/
|
||||||
|
GameListResponse getGameList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定游戏的房间列表
|
||||||
|
*
|
||||||
|
* @param gameId 游戏标识
|
||||||
|
* @param status 状态筛选(可选),不传返回所有未结束的房间
|
||||||
|
* @return 房间列表
|
||||||
|
*/
|
||||||
|
RoomListResponse getRoomList(String gameId, String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建房间
|
||||||
|
*
|
||||||
|
* @param request 创建房间请求
|
||||||
|
* @param creatorId 房主 account_id
|
||||||
|
* @return 新建的房间信息
|
||||||
|
*/
|
||||||
|
CreateRoomResponse createRoom(CreateRoomRequest request, String creatorId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加入房间
|
||||||
|
*
|
||||||
|
* @param request 加入房间请求
|
||||||
|
* @param userId 用户 account_id
|
||||||
|
* @return 房间信息
|
||||||
|
*/
|
||||||
|
RoomItemResponse joinRoom(JoinRoomRequest request, String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 离开房间
|
||||||
|
*
|
||||||
|
* @param roomId 房间 ID
|
||||||
|
* @param userId 用户 account_id
|
||||||
|
*/
|
||||||
|
void leaveRoom(String roomId, String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 房主踢出玩家
|
||||||
|
*
|
||||||
|
* @param roomId 房间 ID
|
||||||
|
* @param ownerId 房主 account_id
|
||||||
|
* @param targetUserId 目标用户 account_id
|
||||||
|
*/
|
||||||
|
void kickPlayer(String roomId, String ownerId, String targetUserId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换准备状态
|
||||||
|
*
|
||||||
|
* @param roomId 房间 ID
|
||||||
|
* @param userId 用户 account_id
|
||||||
|
* @return 切换后的准备状态
|
||||||
|
*/
|
||||||
|
boolean toggleReady(String roomId, String userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始游戏
|
||||||
|
*
|
||||||
|
* @param roomId 房间 ID
|
||||||
|
* @param ownerId 房主 account_id
|
||||||
|
*/
|
||||||
|
void startGame(String roomId, String ownerId);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.webgame.webgamebackend.service;
|
package com.webgame.webgamebackend.service;
|
||||||
|
|
||||||
|
import com.webgame.webgamebackend.common.dto.user.UpdateUserInfoRequest;
|
||||||
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,4 +14,13 @@ public interface UserService {
|
|||||||
* @return 用户信息
|
* @return 用户信息
|
||||||
*/
|
*/
|
||||||
UserInfoResponse getCurrentUserInfo();
|
UserInfoResponse getCurrentUserInfo();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前登录用户的资料
|
||||||
|
*
|
||||||
|
* @param accountId 账号 ID
|
||||||
|
* @param request 更新请求(所有字段可选)
|
||||||
|
* @return 更新后的用户信息
|
||||||
|
*/
|
||||||
|
UserInfoResponse updateUserInfo(String accountId, UpdateUserInfoRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.webgame.webgamebackend.service.impl;
|
package com.webgame.webgamebackend.service.impl;
|
||||||
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import com.webgame.webgamebackend.common.constants.UserConstants;
|
||||||
import com.webgame.webgamebackend.common.dto.account.LoginRequest;
|
import com.webgame.webgamebackend.common.dto.account.LoginRequest;
|
||||||
import com.webgame.webgamebackend.common.dto.account.LoginResponse;
|
import com.webgame.webgamebackend.common.dto.account.LoginResponse;
|
||||||
import com.webgame.webgamebackend.common.dto.account.RegisterRequest;
|
import com.webgame.webgamebackend.common.dto.account.RegisterRequest;
|
||||||
@@ -64,10 +65,12 @@ public class AccountServiceImpl implements AccountService {
|
|||||||
// 创建用户资料(默认值)
|
// 创建用户资料(默认值)
|
||||||
UserEntity user = new UserEntity();
|
UserEntity user = new UserEntity();
|
||||||
user.setAccount(account);
|
user.setAccount(account);
|
||||||
user.setNickName("玩家" + generateRandomCode());
|
String nickName = "玩家" + generateRandomCode();
|
||||||
|
user.setNickName(nickName);
|
||||||
user.setSex(0);
|
user.setSex(0);
|
||||||
user.setSignature("");
|
user.setSignature("");
|
||||||
user.setAvatar("");
|
// 根据昵称生成默认 DiceBear 头像
|
||||||
|
user.setAvatar(UserConstants.generateDefaultAvatar(nickName));
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
log.info("用户注册成功: username={}", request.username());
|
log.info("用户注册成功: username={}", request.username());
|
||||||
|
|||||||
@@ -0,0 +1,392 @@
|
|||||||
|
package com.webgame.webgamebackend.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.webgame.webgamebackend.common.dto.game.*;
|
||||||
|
import com.webgame.webgamebackend.common.exception.BusinessException;
|
||||||
|
import com.webgame.webgamebackend.common.exception.ErrorCode;
|
||||||
|
import com.webgame.webgamebackend.entities.*;
|
||||||
|
import com.webgame.webgamebackend.repository.*;
|
||||||
|
import com.webgame.webgamebackend.service.GameService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GameServiceImpl implements GameService {
|
||||||
|
|
||||||
|
private final GameConfigRepository gameConfigRepository;
|
||||||
|
private final GameRoomRepository gameRoomRepository;
|
||||||
|
private final RoomPlayerRepository roomPlayerRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final BCryptPasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GameListResponse getGameList() {
|
||||||
|
List<GameConfigEntity> configs = gameConfigRepository.findByEnabledTrueOrderBySortOrderAsc();
|
||||||
|
List<GameItemResponse> list = configs.stream()
|
||||||
|
.map(GameItemResponse::from)
|
||||||
|
.toList();
|
||||||
|
log.info("[游戏服务] 获取游戏列表, 共 {} 款", list.size());
|
||||||
|
return new GameListResponse(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RoomListResponse getRoomList(String gameId, String status) {
|
||||||
|
// 校验游戏存在且启用
|
||||||
|
GameConfigEntity gameConfig = gameConfigRepository.findByGameId(gameId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.GAME_NOT_FOUND));
|
||||||
|
if (!gameConfig.getEnabled()) {
|
||||||
|
throw new BusinessException(ErrorCode.GAME_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询房间:有状态筛选则按状态查,否则查所有
|
||||||
|
List<GameRoomEntity> rooms;
|
||||||
|
if (status != null && !status.isBlank()) {
|
||||||
|
rooms = gameRoomRepository.findByGameIdAndStatus(gameId, status);
|
||||||
|
} else {
|
||||||
|
// 默认不返回已结束的房间
|
||||||
|
rooms = gameRoomRepository.findByGameId(gameId).stream()
|
||||||
|
.filter(r -> !"finished".equals(r.getStatus()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组装每个房间的玩家信息和房主昵称
|
||||||
|
List<RoomItemResponse> list = new ArrayList<>();
|
||||||
|
for (GameRoomEntity room : rooms) {
|
||||||
|
List<RoomPlayerResponse> players = buildRoomPlayers(room.getId());
|
||||||
|
String creatorName = getNickname(room.getCreatorId());
|
||||||
|
list.add(RoomItemResponse.from(room, players, gameConfig.getIcon(), creatorName));
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("[游戏服务] 获取房间列表, gameId={}, status={}, 共 {} 个", gameId, status, list.size());
|
||||||
|
return new RoomListResponse(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public CreateRoomResponse createRoom(CreateRoomRequest request, String creatorId) {
|
||||||
|
// 校验游戏存在且启用
|
||||||
|
GameConfigEntity gameConfig = gameConfigRepository.findByGameId(request.gameId())
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.GAME_NOT_FOUND));
|
||||||
|
if (!gameConfig.getEnabled()) {
|
||||||
|
throw new BusinessException(ErrorCode.GAME_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验 mode 在可选列表中
|
||||||
|
List<String> modes = JSON.parseArray(gameConfig.getModes(), String.class);
|
||||||
|
if (!modes.contains(request.mode())) {
|
||||||
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的模式: " + request.mode());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验 stakes 在可选列表中
|
||||||
|
List<Integer> stakesOptions = JSON.parseArray(gameConfig.getStakesOptions(), Integer.class);
|
||||||
|
if (!stakesOptions.contains(request.stakes())) {
|
||||||
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的底注: " + request.stakes());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户是否已在其他房间中
|
||||||
|
if (roomPlayerRepository.existsByUserId(creatorId)) {
|
||||||
|
throw new BusinessException(ErrorCode.ALREADY_IN_ROOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成桌号
|
||||||
|
Integer maxRoomNumber = gameRoomRepository.findMaxRoomNumberByGameId(request.gameId());
|
||||||
|
int nextRoomNumber = (maxRoomNumber == null) ? 1 : maxRoomNumber + 1;
|
||||||
|
|
||||||
|
// 生成桌名
|
||||||
|
String roomName = (request.name() != null && !request.name().isBlank())
|
||||||
|
? request.name()
|
||||||
|
: "桌 " + String.format("%02d", nextRoomNumber);
|
||||||
|
|
||||||
|
// 创建房间
|
||||||
|
GameRoomEntity room = new GameRoomEntity()
|
||||||
|
.setGameId(request.gameId())
|
||||||
|
.setRoomNumber(nextRoomNumber)
|
||||||
|
.setName(roomName)
|
||||||
|
.setMaxPlayers(request.maxPlayers())
|
||||||
|
.setMode(request.mode())
|
||||||
|
.setStakes(request.stakes())
|
||||||
|
.setStatus("waiting")
|
||||||
|
.setCreatorId(creatorId);
|
||||||
|
|
||||||
|
// 密码加密(如果提供了密码)
|
||||||
|
if (request.password() != null && !request.password().isBlank()) {
|
||||||
|
room.setPassword(passwordEncoder.encode(request.password()));
|
||||||
|
}
|
||||||
|
|
||||||
|
gameRoomRepository.save(room);
|
||||||
|
|
||||||
|
// 房主自动加入房间(座位 1,已准备)
|
||||||
|
RoomPlayerEntity creatorPlayer = new RoomPlayerEntity()
|
||||||
|
.setRoomId(room.getId())
|
||||||
|
.setUserId(creatorId)
|
||||||
|
.setSeatNumber(1)
|
||||||
|
.setReadyStatus(true)
|
||||||
|
.setJoinTime(LocalDateTime.now());
|
||||||
|
roomPlayerRepository.save(creatorPlayer);
|
||||||
|
|
||||||
|
// 组装响应
|
||||||
|
List<RoomPlayerResponse> players = buildRoomPlayers(room.getId());
|
||||||
|
String creatorName = getNickname(creatorId);
|
||||||
|
RoomItemResponse roomResponse = RoomItemResponse.from(room, players, gameConfig.getIcon(), creatorName);
|
||||||
|
|
||||||
|
log.info("[游戏服务] 创建房间成功, roomId={}, gameId={}, creatorId={}", room.getId(), request.gameId(), creatorId);
|
||||||
|
return new CreateRoomResponse(roomResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public RoomItemResponse joinRoom(JoinRoomRequest request, String userId) {
|
||||||
|
GameRoomEntity room = gameRoomRepository.findById(request.roomId())
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.ROOM_NOT_FOUND));
|
||||||
|
|
||||||
|
// 检查房间状态
|
||||||
|
if (!"waiting".equals(room.getStatus())) {
|
||||||
|
throw new BusinessException(ErrorCode.ROOM_ALREADY_STARTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查密码
|
||||||
|
if (room.getPassword() != null && !room.getPassword().isBlank()) {
|
||||||
|
if (request.password() == null || request.password().isBlank()) {
|
||||||
|
throw new BusinessException(ErrorCode.ROOM_PASSWORD_ERROR, "请输入房间密码");
|
||||||
|
}
|
||||||
|
if (!passwordEncoder.matches(request.password(), room.getPassword())) {
|
||||||
|
throw new BusinessException(ErrorCode.ROOM_PASSWORD_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查房间是否已满
|
||||||
|
int currentCount = roomPlayerRepository.countByRoomId(room.getId());
|
||||||
|
if (currentCount >= room.getMaxPlayers()) {
|
||||||
|
throw new BusinessException(ErrorCode.ROOM_FULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户是否已在其他房间
|
||||||
|
if (roomPlayerRepository.existsByUserId(userId)) {
|
||||||
|
throw new BusinessException(ErrorCode.ALREADY_IN_ROOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分配座位号
|
||||||
|
List<RoomPlayerEntity> existingPlayers = roomPlayerRepository.findByRoomId(room.getId());
|
||||||
|
int nextSeat = 1;
|
||||||
|
if (!existingPlayers.isEmpty()) {
|
||||||
|
int maxSeat = existingPlayers.stream()
|
||||||
|
.mapToInt(RoomPlayerEntity::getSeatNumber)
|
||||||
|
.max()
|
||||||
|
.orElse(0);
|
||||||
|
nextSeat = maxSeat + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入房间
|
||||||
|
RoomPlayerEntity player = new RoomPlayerEntity()
|
||||||
|
.setRoomId(room.getId())
|
||||||
|
.setUserId(userId)
|
||||||
|
.setSeatNumber(nextSeat)
|
||||||
|
.setReadyStatus(false)
|
||||||
|
.setJoinTime(LocalDateTime.now());
|
||||||
|
roomPlayerRepository.save(player);
|
||||||
|
|
||||||
|
// 组装响应
|
||||||
|
GameConfigEntity gameConfig = gameConfigRepository.findByGameId(room.getGameId())
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.GAME_NOT_FOUND));
|
||||||
|
List<RoomPlayerResponse> players = buildRoomPlayers(room.getId());
|
||||||
|
String creatorName = getNickname(room.getCreatorId());
|
||||||
|
|
||||||
|
log.info("[游戏服务] 玩家加入房间, roomId={}, userId={}", room.getId(), userId);
|
||||||
|
return RoomItemResponse.from(room, players, gameConfig.getIcon(), creatorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void leaveRoom(String roomId, String userId) {
|
||||||
|
GameRoomEntity room = gameRoomRepository.findById(roomId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.ROOM_NOT_FOUND));
|
||||||
|
|
||||||
|
// 检查玩家是否在房间中
|
||||||
|
RoomPlayerEntity player = roomPlayerRepository.findByRoomIdAndUserId(roomId, userId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.PLAYER_NOT_IN_ROOM));
|
||||||
|
|
||||||
|
roomPlayerRepository.delete(player);
|
||||||
|
|
||||||
|
List<RoomPlayerEntity> remaining = roomPlayerRepository.findByRoomId(roomId);
|
||||||
|
|
||||||
|
if (remaining.isEmpty()) {
|
||||||
|
// 无人了,房间标记为结束
|
||||||
|
room.setStatus("finished");
|
||||||
|
gameRoomRepository.save(room);
|
||||||
|
log.info("[游戏服务] 房间无人,标记为结束, roomId={}", roomId);
|
||||||
|
} else if (userId.equals(room.getCreatorId())) {
|
||||||
|
// 房主离开,顺位转让给下一个玩家
|
||||||
|
RoomPlayerEntity nextOwner = remaining.get(0);
|
||||||
|
room.setCreatorId(nextOwner.getUserId());
|
||||||
|
// 新房主自动设为已准备
|
||||||
|
nextOwner.setReadyStatus(true);
|
||||||
|
roomPlayerRepository.save(nextOwner);
|
||||||
|
gameRoomRepository.save(room);
|
||||||
|
log.info("[游戏服务] 房主离开,转让给 userId={}, roomId={}", nextOwner.getUserId(), roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("[游戏服务] 玩家离开房间, roomId={}, userId={}", roomId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void kickPlayer(String roomId, String ownerId, String targetUserId) {
|
||||||
|
GameRoomEntity room = gameRoomRepository.findById(roomId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.ROOM_NOT_FOUND));
|
||||||
|
|
||||||
|
// 校验操作者是房主
|
||||||
|
if (!ownerId.equals(room.getCreatorId())) {
|
||||||
|
throw new BusinessException(ErrorCode.NOT_ROOM_OWNER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不能踢自己
|
||||||
|
if (ownerId.equals(targetUserId)) {
|
||||||
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "不能踢出自己,请使用离开房间");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查目标玩家是否在房间中
|
||||||
|
RoomPlayerEntity target = roomPlayerRepository.findByRoomIdAndUserId(roomId, targetUserId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.PLAYER_NOT_IN_ROOM));
|
||||||
|
|
||||||
|
roomPlayerRepository.delete(target);
|
||||||
|
log.info("[游戏服务] 房主踢出玩家, roomId={}, targetUserId={}", roomId, targetUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean toggleReady(String roomId, String userId) {
|
||||||
|
GameRoomEntity room = gameRoomRepository.findById(roomId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.ROOM_NOT_FOUND));
|
||||||
|
|
||||||
|
if (!"waiting".equals(room.getStatus())) {
|
||||||
|
throw new BusinessException(ErrorCode.ROOM_ALREADY_STARTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomPlayerEntity player = roomPlayerRepository.findByRoomIdAndUserId(roomId, userId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.PLAYER_NOT_IN_ROOM));
|
||||||
|
|
||||||
|
boolean newStatus = !player.getReadyStatus();
|
||||||
|
player.setReadyStatus(newStatus);
|
||||||
|
roomPlayerRepository.save(player);
|
||||||
|
|
||||||
|
log.info("[游戏服务] 玩家{}准备, roomId={}, userId={}", newStatus ? "" : "取消", roomId, userId);
|
||||||
|
return newStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void startGame(String roomId, String ownerId) {
|
||||||
|
GameRoomEntity room = gameRoomRepository.findById(roomId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.ROOM_NOT_FOUND));
|
||||||
|
|
||||||
|
// 校验操作者是房主
|
||||||
|
if (!ownerId.equals(room.getCreatorId())) {
|
||||||
|
throw new BusinessException(ErrorCode.NOT_ROOM_OWNER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验房间状态
|
||||||
|
if (!"waiting".equals(room.getStatus())) {
|
||||||
|
throw new BusinessException(ErrorCode.ROOM_ALREADY_STARTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验玩家人数 >= 2
|
||||||
|
List<RoomPlayerEntity> players = roomPlayerRepository.findByRoomId(roomId);
|
||||||
|
if (players.size() < 2) {
|
||||||
|
throw new BusinessException(ErrorCode.PLAYER_COUNT_INSUFFICIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验所有玩家已准备(房主除外,房主默认准备)
|
||||||
|
for (RoomPlayerEntity player : players) {
|
||||||
|
if (!player.getReadyStatus() && !player.getUserId().equals(room.getCreatorId())) {
|
||||||
|
throw new BusinessException(ErrorCode.NOT_ALL_READY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扣除房主底注
|
||||||
|
UserEntity ownerUser = userRepository.findByAccountId(ownerId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||||||
|
if (ownerUser.getBalance() < room.getStakes()) {
|
||||||
|
throw new BusinessException(ErrorCode.BALANCE_INSUFFICIENT);
|
||||||
|
}
|
||||||
|
ownerUser.setBalance(ownerUser.getBalance() - room.getStakes());
|
||||||
|
userRepository.save(ownerUser);
|
||||||
|
|
||||||
|
// 扣除其他玩家底注
|
||||||
|
for (RoomPlayerEntity player : players) {
|
||||||
|
if (!player.getUserId().equals(ownerId)) {
|
||||||
|
UserEntity user = userRepository.findByAccountId(player.getUserId())
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||||||
|
if (user.getBalance() < room.getStakes()) {
|
||||||
|
throw new BusinessException(ErrorCode.BALANCE_INSUFFICIENT,
|
||||||
|
"玩家 " + user.getNickName() + " 余额不足");
|
||||||
|
}
|
||||||
|
user.setBalance(user.getBalance() - room.getStakes());
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新房间状态为进行中
|
||||||
|
room.setStatus("playing");
|
||||||
|
gameRoomRepository.save(room);
|
||||||
|
|
||||||
|
log.info("[游戏服务] 游戏开始, roomId={}, 参与人数={}, 底注={}", roomId, players.size(), room.getStakes());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 私有辅助方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组装房间内的玩家信息列表
|
||||||
|
*
|
||||||
|
* @param roomId 房间 ID
|
||||||
|
* @return 玩家响应列表(含头像和昵称)
|
||||||
|
*/
|
||||||
|
private List<RoomPlayerResponse> buildRoomPlayers(String roomId) {
|
||||||
|
List<RoomPlayerEntity> roomPlayers = roomPlayerRepository.findByRoomId(roomId);
|
||||||
|
if (roomPlayers.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量查询玩家用户信息
|
||||||
|
List<String> userIds = roomPlayers.stream()
|
||||||
|
.map(RoomPlayerEntity::getUserId)
|
||||||
|
.toList();
|
||||||
|
List<UserEntity> users = userRepository.findByAccountIdIn(userIds);
|
||||||
|
Map<String, UserEntity> userMap = users.stream()
|
||||||
|
.collect(Collectors.toMap(u -> u.getAccount().getId(), u -> u));
|
||||||
|
|
||||||
|
return roomPlayers.stream()
|
||||||
|
.map(rp -> {
|
||||||
|
UserEntity user = userMap.get(rp.getUserId());
|
||||||
|
String avatar = (user != null) ? user.getAvatar() : "";
|
||||||
|
String nickname = (user != null) ? user.getNickName() : "未知玩家";
|
||||||
|
return new RoomPlayerResponse(avatar, nickname);
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户昵称
|
||||||
|
*
|
||||||
|
* @param accountId 账号 ID
|
||||||
|
* @return 昵称,查不到则返回 "未知玩家"
|
||||||
|
*/
|
||||||
|
private String getNickname(String accountId) {
|
||||||
|
return userRepository.findByAccountId(accountId)
|
||||||
|
.map(UserEntity::getNickName)
|
||||||
|
.orElse("未知玩家");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.webgame.webgamebackend.service.impl;
|
package com.webgame.webgamebackend.service.impl;
|
||||||
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import com.webgame.webgamebackend.common.dto.user.UpdateUserInfoRequest;
|
||||||
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
import com.webgame.webgamebackend.common.dto.user.UserInfoResponse;
|
||||||
import com.webgame.webgamebackend.common.exception.BusinessException;
|
import com.webgame.webgamebackend.common.exception.BusinessException;
|
||||||
import com.webgame.webgamebackend.common.exception.ErrorCode;
|
import com.webgame.webgamebackend.common.exception.ErrorCode;
|
||||||
@@ -10,6 +11,7 @@ import com.webgame.webgamebackend.service.UserService;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户服务实现
|
* 用户服务实现
|
||||||
@@ -29,4 +31,35 @@ public class UserServiceImpl implements UserService {
|
|||||||
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||||||
return UserInfoResponse.from(user);
|
return UserInfoResponse.from(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public UserInfoResponse updateUserInfo(String accountId, UpdateUserInfoRequest request) {
|
||||||
|
UserEntity user = userRepository.findByAccountId(accountId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
|
||||||
|
|
||||||
|
// 仅更新非 null 字段
|
||||||
|
if (request.nickName() != null) {
|
||||||
|
user.setNickName(request.nickName());
|
||||||
|
}
|
||||||
|
if (request.avatar() != null) {
|
||||||
|
user.setAvatar(request.avatar());
|
||||||
|
}
|
||||||
|
if (request.signature() != null) {
|
||||||
|
user.setSignature(request.signature());
|
||||||
|
}
|
||||||
|
if (request.age() != null) {
|
||||||
|
user.setAge(request.age());
|
||||||
|
}
|
||||||
|
if (request.sex() != null) {
|
||||||
|
user.setSex(request.sex());
|
||||||
|
}
|
||||||
|
if (request.birthday() != null) {
|
||||||
|
user.setBirthday(request.birthday());
|
||||||
|
}
|
||||||
|
|
||||||
|
userRepository.save(user);
|
||||||
|
log.info("[用户服务] 更新用户信息, accountId={}", accountId);
|
||||||
|
return UserInfoResponse.from(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,19 @@ spring:
|
|||||||
# 连接池中的最小空闲连接
|
# 连接池中的最小空闲连接
|
||||||
min-idle: 0
|
min-idle: 0
|
||||||
|
|
||||||
|
# SQL 初始化(data.sql)
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: always
|
||||||
|
|
||||||
# JPA相关配置
|
# JPA相关配置
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: update
|
ddl-auto: update
|
||||||
show-sql: false # 是否输出sql
|
show-sql: false # 是否输出sql
|
||||||
open-in-view: false
|
open-in-view: false
|
||||||
|
# JPA 初始化延迟到 data.sql 执行之后,避免表未创建
|
||||||
|
defer-datasource-initialization: true
|
||||||
|
|
||||||
# Servlet相关配置
|
# Servlet相关配置
|
||||||
servlet:
|
servlet:
|
||||||
|
|||||||
53
src/main/resources/data.sql
Normal file
53
src/main/resources/data.sql
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
-- 游戏配置初始化数据
|
||||||
|
-- 使用 INSERT IGNORE 保证幂等,重复执行不会报错
|
||||||
|
|
||||||
|
INSERT IGNORE INTO game_config (id, game_id, icon, name, description, gameplay, max_players, modes, stakes_options, sort_order, enabled, create_time, update_time)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
REPLACE(UUID(), '-', ''),
|
||||||
|
'snake',
|
||||||
|
'🐍',
|
||||||
|
'贪吃蛇',
|
||||||
|
'经典贪吃蛇竞技,在狭小空间内争夺生存权',
|
||||||
|
'方向键控制蛇的移动,吃到食物变长。撞墙或其他蛇的身体即死亡,最后存活的玩家获胜。',
|
||||||
|
4,
|
||||||
|
'["经典模式","道具模式","加速模式"]',
|
||||||
|
'[100,200,300,400,500]',
|
||||||
|
0, 1, NOW(), NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
REPLACE(UUID(), '-', ''),
|
||||||
|
'tetris',
|
||||||
|
'🧊',
|
||||||
|
'俄罗斯方块',
|
||||||
|
'经典下落消除,与对手一较高下',
|
||||||
|
'方向键移动和旋转方块,填满横排即可消除。同时向对手发送干扰行,坚持到最后的玩家获胜。',
|
||||||
|
2,
|
||||||
|
'["1v1对战","竞速模式"]',
|
||||||
|
'[150,200,300,400,500]',
|
||||||
|
1, 1, NOW(), NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
REPLACE(UUID(), '-', ''),
|
||||||
|
'gomoku',
|
||||||
|
'⚫',
|
||||||
|
'五子棋',
|
||||||
|
'黑白对弈,先连成五子者胜',
|
||||||
|
'鼠标点击落子,黑白双方轮流在棋盘上放置棋子。先在横、竖、斜任意方向连成五子的一方获胜。',
|
||||||
|
2,
|
||||||
|
'["无禁手","有禁手"]',
|
||||||
|
'[100,200,300,400,500]',
|
||||||
|
2, 1, NOW(), NOW()
|
||||||
|
),
|
||||||
|
(
|
||||||
|
REPLACE(UUID(), '-', ''),
|
||||||
|
'guess-number',
|
||||||
|
'🔢',
|
||||||
|
'猜数字',
|
||||||
|
'逻辑推理对战,猜中对方的秘密数字',
|
||||||
|
'每轮输入猜测的数字组合,系统会提示位置和数字正确的数量。率先猜中对手数字组合的玩家获胜。',
|
||||||
|
4,
|
||||||
|
'["经典模式","限时模式"]',
|
||||||
|
'[100,150,200,300,500]',
|
||||||
|
3, 1, NOW(), NOW()
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user