diff --git a/README.md b/README.md
index 979f6c9b14a7a5c3c8549950089e53365f4030c9..461c7ccc1a3ff630d3cd0ff4d899c290eb6ad520 100644
--- a/README.md
+++ b/README.md
@@ -66,72 +66,55 @@ territory-paint-game/
│ └── vite.config.js
├── backend/ # .NET 8后端项目 (DDD架构)
│ ├── src/
-│ │ ├── TerritoryGame.API/ # 接口层
-│ │ │ ├── Controllers/ # RESTful API控制器
-│ │ │ │ ├── CommandController.cs # 命令处理 (CQRS)
-│ │ │ │ └── QueryController.cs # 查询处理 (CQRS)
-│ │ │ ├── Hubs/ # SignalR实时通信
+│ │ ├── TerritoryGame.API/ # 接口层 (ASP.NET Core Web API)
+│ │ │ ├── Controllers/ # API控制器
+│ │ │ │ ├── CommandController.cs
+│ │ │ │ └── QueryController.cs
+│ │ │ ├── Hubs/ # SignalR Hubs
│ │ │ │ └── GameHub.cs
-│ │ │ ├── Program.cs # 启动配置
+│ │ │ ├── Program.cs
│ │ │ └── appsettings.json
-│ │ ├── TerritoryGame.Domain/ # 领域层 (核心业务逻辑)
-│ │ │ ├── Entities/ # 实体
-│ │ │ │ ├── Game.cs # 游戏聚合根
-│ │ │ │ ├── Player.cs # 玩家实体
-│ │ │ │ └── GameRoom.cs # 房间实体
-│ │ │ ├── ValueObjects/ # 值对象
-│ │ │ │ ├── Position.cs # 位置坐标
-│ │ │ │ ├── Color.cs # 玩家颜色
-│ │ │ │ └── GameScore.cs # 游戏分数
-│ │ │ ├── Events/ # 领域事件
-│ │ │ │ ├── GameStartedEvent.cs
-│ │ │ │ ├── AreaCapturedEvent.cs
-│ │ │ │ └── GameEndedEvent.cs
-│ │ │ ├── Services/ # 领域服务
-│ │ │ │ ├── IGameLogicService.cs
-│ │ │ │ └── GameLogicService.cs # 游戏规则引擎
-│ │ │ └── Repositories/ # 仓储接口
-│ │ │ ├── IGameRepository.cs
-│ │ │ └── IPlayerRepository.cs
-│ │ ├── TerritoryGame.Application/ # 应用层 (用例编排)
-│ │ │ ├── Commands/ # 命令模式
+│ │ ├── TerritoryGame.Application/ # 应用层
+│ │ │ ├── Commands/ # 命令定义
│ │ │ │ ├── CreateRoomCommand.cs
│ │ │ │ ├── JoinRoomCommand.cs
│ │ │ │ └── PaintActionCommand.cs
-│ │ │ ├── Queries/ # 查询模式
+│ │ │ ├── Queries/ # 查询定义
│ │ │ │ ├── GetRoomQuery.cs
│ │ │ │ └── GetLeaderboardQuery.cs
-│ │ │ ├── Handlers/ # CQRS处理器
+│ │ │ ├── Handlers/ # 命令和查询处理器
│ │ │ │ ├── CommandHandlers/
│ │ │ │ └── QueryHandlers/
│ │ │ └── Services/ # 应用服务
-│ │ │ └── GameOrchestrator.cs # 游戏编排服务
+│ │ │ └── GameOrchestrator.cs
+│ │ ├── TerritoryGame.Domain/ # 领域层
+│ │ │ ├── Entities/ # 聚合根和实体
+│ │ │ │ ├── Game.cs
+│ │ │ │ ├── Player.cs
+│ │ │ │ └── GameRoom.cs
+│ │ │ ├── Events/ # 领域事件
+│ │ │ │ ├── GameStartedEvent.cs
+│ │ │ │ ├── AreaCapturedEvent.cs
+│ │ │ │ └── GameEndedEvent.cs
+│ │ │ ├── Repositories/ # 仓储接口
+│ │ │ │ ├── IGameRepository.cs
+│ │ │ │ └── IPlayerRepository.cs
+│ │ │ ├── Services/ # 领域服务
+│ │ │ │ └── IGameLogicService.cs
+│ │ │ └── ValueObjects/ # 值对象
+│ │ │ ├── Position.cs
+│ │ │ ├── Color.cs
+│ │ │ └── GameScore.cs
│ │ ├── TerritoryGame.Infrastructure/ # 基础设施层
-│ │ │ ├── Persistence/ # 数据持久化
+│ │ │ ├── Persistence/ # 数据持久化 (EF Core)
│ │ │ │ ├── GameContext.cs # EF Core上下文
│ │ │ │ └── Repositories/ # 仓储实现
│ │ │ │ ├── GameRepository.cs
│ │ │ │ └── PlayerRepository.cs
-│ │ │ ├── Redis/ # 缓存服务
+│ │ │ ├── Caching/ # 缓存 (Redis)
│ │ │ │ └── GameCacheService.cs
-│ │ │ └── Messaging/ # 事件总线
+│ │ │ └── Messaging/ # 消息/事件总线
│ │ │ └── EventBus.cs
-│ │ │ ├── Entities/ # 实体类
-│ │ │ │ ├── GameRoom.cs
-│ │ │ │ ├── Player.cs
-│ │ │ │ ├── GameSession.cs
-│ │ │ │ └── PaintAction.cs
-│ │ │ └── Interfaces/
-│ │ ├── TerritoryGame.Infrastructure/
-│ │ │ ├── Data/
-│ │ │ │ └── GameDbContext.cs
-│ │ │ └── Repositories/
-│ │ └── TerritoryGame.Application/
-│ │ ├── DTOs/
-│ │ ├── Services/
-│ │ │ ├── GameService.cs
-│ │ │ └── AreaCalculationService.cs
-│ │ └── Hubs/
│ └── TerritoryGame.sln
├── paint-demo.html # 游戏演示页面
├── docker-compose.yml # Docker编排文件
diff --git a/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs b/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e6d1801927764267a1fe00cc8d7a7f8dd8a09826
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Commands/CreatePlayerCommand.cs
@@ -0,0 +1,8 @@
+namespace TerritoryGame.Application.Commands;
+
+///
+/// 创建新玩家的命令
+///
+/// 玩家昵称
+/// 玩家头像的URL(可选)
+public record CreatePlayerCommand(string NickName, string? Avatar) : IRequest;
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs b/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs
new file mode 100644
index 0000000000000000000000000000000000000000..34d22aaf2bb26cd48302ae5bb90312079d7061bf
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Commands/CreateRoomCommand.cs
@@ -0,0 +1,10 @@
+using MediatR;
+using TerritoryGame.Application.DTOs;
+
+namespace TerritoryGame.Application.Commands;
+
+///
+/// 创建新游戏房间的命令
+///
+/// 房间名称
+public record CreateRoomCommand(string Name) : IRequest;
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs b/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a46235245040b19f78767cd48be3f8effcb0401f
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Commands/JoinRoomCommand.cs
@@ -0,0 +1,11 @@
+using MediatR;
+using TerritoryGame.Application.DTOs;
+
+namespace TerritoryGame.Application.Commands;
+
+///
+/// 玩家加入游戏房间的命令
+///
+/// 房间ID
+/// 玩家ID
+public record JoinRoomCommand(Guid RoomId, Guid PlayerId) : IRequest;
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs b/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9991424ac22049f6d2319bd14829c1ff0c1fcaaf
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Commands/PaintActionCommand.cs
@@ -0,0 +1,13 @@
+using MediatR;
+
+namespace TerritoryGame.Application.Commands;
+
+///
+/// 玩家执行涂色动作的命令
+///
+/// 房间ID
+/// 玩家ID
+/// X坐标
+/// Y坐标
+/// 笔刷大小
+public record PaintActionCommand(Guid RoomId, Guid PlayerId, int X, int Y, int BrushSize) : IRequest;
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/DTOs/GameRoomDto.cs b/backend/src/TerritoryGame.Application/DTOs/GameRoomDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..631029cc700ead17c8a68a0476159484ebea9535
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/DTOs/GameRoomDto.cs
@@ -0,0 +1,27 @@
+namespace TerritoryGame.Application.DTOs;
+
+///
+/// 游戏房间信息的数据传输对象
+///
+public class GameRoomDto
+{
+ ///
+ /// 房间ID
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// 房间名称
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 房间内的玩家列表
+ ///
+ public List Players { get; set; } = new();
+
+ ///
+ /// 游戏状态
+ ///
+ public string Status { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/DTOs/LeaderboardDto.cs b/backend/src/TerritoryGame.Application/DTOs/LeaderboardDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..86df4808eef35b18248ab8ba427a4b04acccd688
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/DTOs/LeaderboardDto.cs
@@ -0,0 +1,12 @@
+namespace TerritoryGame.Application.DTOs;
+
+///
+/// 排行榜信息的数据传输对象
+///
+public class LeaderboardDto
+{
+ ///
+ /// 排行榜条目列表
+ ///
+ public List Items { get; set; } = new();
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs b/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e3b98ff2eba1e964c5f8c9d3231e178f53f8cf99
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/DTOs/LeaderboardItemDto.cs
@@ -0,0 +1,22 @@
+namespace TerritoryGame.Application.DTOs;
+
+///
+/// 排行榜条目的数据传输对象
+///
+public class LeaderboardItemDto
+{
+ ///
+ /// 玩家ID
+ ///
+ public Guid PlayerId { get; set; }
+
+ ///
+ /// 玩家昵称
+ ///
+ public string PlayerName { get; set; } = string.Empty;
+
+ ///
+ /// 分数
+ ///
+ public int Score { get; set; }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs b/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs
new file mode 100644
index 0000000000000000000000000000000000000000..59a0114c48d020d64c3004ac37fd5308173a7326
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/DTOs/PlayerDto.cs
@@ -0,0 +1,27 @@
+namespace TerritoryGame.Application.DTOs;
+
+///
+/// 玩家信息的数据传输对象
+///
+public class PlayerDto
+{
+ ///
+ /// 玩家ID
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// 玩家昵称
+ ///
+ public string NickName { get; set; } = string.Empty;
+
+ ///
+ /// 玩家颜色
+ ///
+ public string Color { get; set; } = string.Empty;
+
+ ///
+ /// 玩家头像
+ ///
+ public string Avatar { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ed02b99139b5e67c3711cec6511cc2b3093c37ae
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreatePlayerCommandHandler.cs
@@ -0,0 +1,35 @@
+using MediatR;
+using TerritoryGame.Application.Commands;
+using TerritoryGame.Application.DTOs;
+using TerritoryGame.Domain.Entities;
+using TerritoryGame.Domain.Repositories;
+
+namespace TerritoryGame.Application.Handlers.CommandHandlers;
+
+public class CreatePlayerCommandHandler : IRequestHandler
+{
+ private readonly IPlayerRepository _playerRepository;
+
+ public CreatePlayerCommandHandler(IPlayerRepository playerRepository)
+ {
+ _playerRepository = playerRepository;
+ }
+
+ public async Task Handle(CreatePlayerCommand request, CancellationToken cancellationToken)
+ {
+ // 使用充血模型的构造函数创建实体
+ var player = new Player(request.NickName, request.Avatar);
+
+ await _playerRepository.AddAsync(player, cancellationToken);
+
+ // 返回一个DTO,而不是领域实体
+ return new PlayerDto
+ {
+ Id = player.Id,
+ NickName = player.NickName,
+ Avatar = player.Avatar,
+ TotalGames = player.TotalGames,
+ WinCount = player.WinCount
+ };
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreateRoomCommandHandler.cs b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreateRoomCommandHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ce9bab19145fee9b83eeab24a93a077dd70e2bb0
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/CreateRoomCommandHandler.cs
@@ -0,0 +1,32 @@
+using MediatR;
+using TerritoryGame.Application.Commands;
+using TerritoryGame.Application.DTOs;
+using TerritoryGame.Domain.Entities;
+using TerritoryGame.Domain.Repositories;
+
+namespace TerritoryGame.Application.Handlers.CommandHandlers;
+
+public class CreateRoomCommandHandler : IRequestHandler
+{
+ private readonly IGameRoomRepository _gameRoomRepository;
+
+ public CreateRoomCommandHandler(IGameRoomRepository gameRoomRepository)
+ {
+ _gameRoomRepository = gameRoomRepository;
+ }
+
+ public async Task Handle(CreateRoomCommand request, CancellationToken cancellationToken)
+ {
+ var gameRoom = new GameRoom(request.Name);
+
+ await _gameRoomRepository.AddAsync(gameRoom, cancellationToken);
+
+ return new GameRoomDto
+ {
+ Id = gameRoom.Id,
+ Name = gameRoom.Name,
+ Status = gameRoom.Status.ToString(),
+ Players = new List() // Initially empty
+ };
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/JoinRoomCommandHandler.cs b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/JoinRoomCommandHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a3ee0aead7751811361df91ed763a51c318aa664
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/JoinRoomCommandHandler.cs
@@ -0,0 +1,47 @@
+using MediatR;
+using TerritoryGame.Application.Commands;
+using TerritoryGame.Application.DTOs;
+using TerritoryGame.Domain.Repositories;
+
+namespace TerritoryGame.Application.Handlers.CommandHandlers;
+
+public class JoinRoomCommandHandler : IRequestHandler
+{
+ private readonly IGameRoomRepository _gameRoomRepository;
+ private readonly IPlayerRepository _playerRepository;
+
+ public JoinRoomCommandHandler(IGameRoomRepository gameRoomRepository, IPlayerRepository playerRepository)
+ {
+ _gameRoomRepository = gameRoomRepository;
+ _playerRepository = playerRepository;
+ }
+
+ public async Task Handle(JoinRoomCommand request, CancellationToken cancellationToken)
+ {
+ var gameRoom = await _gameRoomRepository.GetByIdAsync(request.RoomId, cancellationToken);
+ if (gameRoom == null)
+ {
+ // Or throw a custom exception
+ throw new Exception("Game room not found.");
+ }
+
+ var player = await _playerRepository.GetByIdAsync(request.PlayerId, cancellationToken);
+ if (player == null)
+ {
+ // Or throw a custom exception
+ throw new Exception("Player not found.");
+ }
+
+ gameRoom.AddPlayer(player);
+
+ await _gameRoomRepository.UpdateAsync(gameRoom, cancellationToken);
+
+ return new GameRoomDto
+ {
+ Id = gameRoom.Id,
+ Name = gameRoom.Name,
+ Status = gameRoom.Status.ToString(),
+ Players = gameRoom.Players.Select(p => new PlayerDto { Id = p.Id, NickName = p.NickName, Avatar = p.Avatar }).ToList()
+ };
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/PaintActionCommandHandler.cs b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/PaintActionCommandHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1d2da52412a0e89faff84cbc2e0eeec8273071f9
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Handlers/CommandHandlers/PaintActionCommandHandler.cs
@@ -0,0 +1,44 @@
+using MediatR;
+using TerritoryGame.Application.Commands;
+using TerritoryGame.Domain.Repositories;
+using TerritoryGame.Domain.Services;
+
+namespace TerritoryGame.Application.Handlers.CommandHandlers;
+
+public class PaintActionCommandHandler : IRequestHandler
+{
+ private readonly IGameRoomRepository _gameRoomRepository;
+ private readonly IPlayerRepository _playerRepository;
+ private readonly IGameLogicService _gameLogicService;
+
+ public PaintActionCommandHandler(
+ IGameRoomRepository gameRoomRepository,
+ IPlayerRepository playerRepository,
+ IGameLogicService gameLogicService)
+ {
+ _gameRoomRepository = gameRoomRepository;
+ _playerRepository = playerRepository;
+ _gameLogicService = gameLogicService;
+ }
+
+ public async Task Handle(PaintActionCommand request, CancellationToken cancellationToken)
+ {
+ var gameRoom = await _gameRoomRepository.GetByIdAsync(request.RoomId, cancellationToken);
+ if (gameRoom == null)
+ {
+ throw new Exception("Game room not found.");
+ }
+
+ var player = await _playerRepository.GetByIdAsync(request.PlayerId, cancellationToken);
+ if (player == null)
+ {
+ throw new Exception("Player not found.");
+ }
+
+ _gameLogicService.HandlePaintAction(gameRoom, player, request.X, request.Y, request.BrushSize);
+
+ await _gameRoomRepository.UpdateAsync(gameRoom, cancellationToken);
+
+ return Unit.Value;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetLeaderboardQueryHandler.cs b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetLeaderboardQueryHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7aac7c77a7210f035bd66bfabb65262e82d486b4
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetLeaderboardQueryHandler.cs
@@ -0,0 +1,38 @@
+using MediatR;
+using TerritoryGame.Application.DTOs;
+using TerritoryGame.Application.Queries;
+using TerritoryGame.Domain.Repositories;
+
+namespace TerritoryGame.Application.Handlers.QueryHandlers;
+
+public class GetLeaderboardQueryHandler : IRequestHandler
+{
+ private readonly IPlayerRepository _playerRepository;
+
+ public GetLeaderboardQueryHandler(IPlayerRepository playerRepository)
+ {
+ _playerRepository = playerRepository;
+ }
+
+ public async Task Handle(GetLeaderboardQuery request, CancellationToken cancellationToken)
+ {
+ // This is a simplified implementation. A real-world leaderboard might involve more complex logic
+ // and potentially a separate, optimized data store for leaderboard data.
+ var players = await _playerRepository.GetAllAsync(cancellationToken);
+
+ var leaderboardItems = players
+ .OrderByDescending(p => p.WinCount) // Or any other metric
+ .Select(p => new LeaderboardItemDto
+ {
+ PlayerId = p.Id,
+ PlayerName = p.NickName,
+ Score = p.WinCount // Or a calculated score
+ })
+ .ToList();
+
+ return new LeaderboardDto
+ {
+ Items = leaderboardItems
+ };
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..611b68dc0b4f77ffc8d2ad5609bb7a4870007783
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetPlayerQueryHandler.cs
@@ -0,0 +1,35 @@
+using MediatR;
+using TerritoryGame.Application.DTOs;
+using TerritoryGame.Application.Queries;
+using TerritoryGame.Domain.Repositories;
+
+namespace TerritoryGame.Application.Handlers.QueryHandlers;
+
+public class GetPlayerQueryHandler : IRequestHandler
+{
+ private readonly IPlayerRepository _playerRepository;
+
+ public GetPlayerQueryHandler(IPlayerRepository playerRepository)
+ {
+ _playerRepository = playerRepository;
+ }
+
+ public async Task Handle(GetPlayerQuery request, CancellationToken cancellationToken)
+ {
+ var player = await _playerRepository.GetByIdAsync(request.Id, cancellationToken);
+
+ if (player == null)
+ {
+ return null;
+ }
+
+ return new PlayerDto
+ {
+ Id = player.Id,
+ NickName = player.NickName,
+ Avatar = player.Avatar,
+ TotalGames = player.TotalGames,
+ WinCount = player.WinCount
+ };
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetRoomQueryHandler.cs b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetRoomQueryHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8d33a4238b789381a9132109f084f0bb79546ced
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Handlers/QueryHandlers/GetRoomQueryHandler.cs
@@ -0,0 +1,34 @@
+using MediatR;
+using TerritoryGame.Application.DTOs;
+using TerritoryGame.Application.Queries;
+using TerritoryGame.Domain.Repositories;
+
+namespace TerritoryGame.Application.Handlers.QueryHandlers;
+
+public class GetRoomQueryHandler : IRequestHandler
+{
+ private readonly IGameRoomRepository _gameRoomRepository;
+
+ public GetRoomQueryHandler(IGameRoomRepository gameRoomRepository)
+ {
+ _gameRoomRepository = gameRoomRepository;
+ }
+
+ public async Task Handle(GetRoomQuery request, CancellationToken cancellationToken)
+ {
+ var gameRoom = await _gameRoomRepository.GetByIdAsync(request.RoomId, cancellationToken);
+
+ if (gameRoom == null)
+ {
+ return null;
+ }
+
+ return new GameRoomDto
+ {
+ Id = gameRoom.Id,
+ Name = gameRoom.Name,
+ Status = gameRoom.Status.ToString(),
+ Players = gameRoom.Players.Select(p => new PlayerDto { Id = p.Id, NickName = p.NickName, Avatar = p.Avatar }).ToList()
+ };
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs b/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d13edc8598c9fa9e3fd333f3f3cb6d6963630ced
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Queries/GetLeaderboardQuery.cs
@@ -0,0 +1,9 @@
+using MediatR;
+using TerritoryGame.Application.DTOs;
+
+namespace TerritoryGame.Application.Queries;
+
+///
+/// 获取排行榜信息的查询
+///
+public record GetLeaderboardQuery : IRequest;
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs b/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e9b36097fbdfa2e3b0f55cb5e4c520e4aa629fbe
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Queries/GetPlayerQuery.cs
@@ -0,0 +1,10 @@
+using MediatR;
+using TerritoryGame.Application.DTOs;
+
+namespace TerritoryGame.Application.Queries;
+
+///
+/// 获取单个玩家信息的查询
+///
+/// 玩家ID
+public record GetPlayerQuery(Guid Id) : IRequest;
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs b/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3dd0e424302589afd788a4073c8ffc21a42dc0dc
--- /dev/null
+++ b/backend/src/TerritoryGame.Application/Queries/GetRoomQuery.cs
@@ -0,0 +1,10 @@
+using MediatR;
+using TerritoryGame.Application.DTOs;
+
+namespace TerritoryGame.Application.Queries;
+
+///
+/// 获取单个游戏房间信息的查询
+///
+/// 房间ID
+public record GetRoomQuery(Guid RoomId) : IRequest;
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs b/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9cb2c3811f8b3e3495156815761ab0d1420790ac
--- /dev/null
+++ b/backend/src/TerritoryGame.Domain/Entities/GameRoom.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using TerritoryGame.Domain.Enums;
+
+namespace TerritoryGame.Domain.Entities;
+
+///
+/// 游戏房间
+///
+public class GameRoom
+{
+ ///
+ /// 房间ID
+ ///
+ public Guid Id { get; private set; }
+
+ ///
+ /// 房间名称
+ ///
+ public string Name { get; private set; }
+
+ ///
+ /// 游戏状态
+ ///
+ public GameStatus Status { get; private set; }
+
+ ///
+ /// 房间内的玩家列表
+ ///
+ private readonly List _players = new();
+ public IReadOnlyCollection Players => _players.AsReadOnly();
+
+ ///
+ /// 游戏会话列表
+ ///
+ public ICollection GameSessions { get; private set; } = new List();
+
+ ///
+ /// 涂色动作列表
+ ///
+ public ICollection PaintActions { get; private set; } = new List();
+
+ // EF Core 构造函数
+ private GameRoom() { }
+
+ public GameRoom(string name)
+ {
+ Id = Guid.NewGuid();
+ Name = name;
+ Status = GameStatus.Waiting;
+ }
+
+ ///
+ /// 添加玩家到房间
+ ///
+ /// 要添加的玩家
+ public void AddPlayer(Player player)
+ {
+ if (Status != GameStatus.Waiting)
+ throw new InvalidOperationException("游戏已经开始或结束,不能加入玩家。");
+
+ if (_players.Count >= 6) // 假设最大玩家数为6
+ throw new InvalidOperationException("房间已满。");
+
+ if (_players.Any(p => p.Id == player.Id))
+ throw new InvalidOperationException("玩家已在房间内。");
+
+ _players.Add(player);
+ }
+
+ ///
+ /// 开始游戏
+ ///
+ public void StartGame()
+ {
+ if (Status != GameStatus.Waiting)
+ throw new InvalidOperationException("游戏已经开始或结束。");
+
+ if (_players.Count < 2) // 假设最少需要2个玩家才能开始
+ throw new InvalidOperationException("玩家人数不足,无法开始游戏。");
+
+ Status = GameStatus.InProgress;
+ // 在这里可以发布一个领域事件,例如 GameStartedEvent
+ }
+
+ ///
+ /// 结束游戏
+ ///
+ public void EndGame()
+ {
+ if (Status != GameStatus.InProgress)
+ throw new InvalidOperationException("游戏尚未开始,不能结束。");
+
+ Status = GameStatus.Finished;
+ // 在这里可以发布一个领域事件,例如 GameEndedEvent
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Domain/Entities/GameSession.cs b/backend/src/TerritoryGame.Domain/Entities/GameSession.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e751a680274c7bc2ff27d439238bb3be64f9a67b
--- /dev/null
+++ b/backend/src/TerritoryGame.Domain/Entities/GameSession.cs
@@ -0,0 +1,54 @@
+using System;
+
+namespace TerritoryGame.Domain.Entities;
+
+///
+/// 表示玩家的游戏会话。
+///
+public class GameSession
+{
+ ///
+ /// 游戏会话的唯一标识符。
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// 游戏房间的ID。
+ ///
+ public Guid RoomId { get; set; }
+
+ ///
+ /// 玩家的ID。
+ ///
+ public Guid PlayerId { get; set; }
+
+ ///
+ /// 玩家在游戏中的颜色。
+ ///
+ public string PlayerColor { get; set; } = string.Empty;
+
+ ///
+ /// 玩家最终占领的区域面积。
+ ///
+ public int FinalArea { get; set; }
+
+ ///
+ /// 玩家的排名。
+ ///
+ public int? Rank { get; set; }
+
+ ///
+ /// 玩家加入游戏的时间戳。
+ ///
+ public DateTime JoinedAt { get; set; }
+
+ ///
+ /// 导航属性,指向游戏房间。
+ ///
+ public GameRoom? GameRoom { get; set; }
+
+ ///
+ /// 导航属性,指向玩家。
+ ///
+ public Player? Player { get; set; }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Domain/Entities/PaintAction.cs b/backend/src/TerritoryGame.Domain/Entities/PaintAction.cs
new file mode 100644
index 0000000000000000000000000000000000000000..31f484152b4626d92c40f31d89648f16ef83c5ec
--- /dev/null
+++ b/backend/src/TerritoryGame.Domain/Entities/PaintAction.cs
@@ -0,0 +1,54 @@
+using System;
+
+namespace TerritoryGame.Domain.Entities;
+
+///
+/// 表示玩家的涂色动作。
+///
+public class PaintAction
+{
+ ///
+ /// 涂色动作的唯一标识符。
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ /// 游戏房间的ID。
+ ///
+ public Guid RoomId { get; set; }
+
+ ///
+ /// 玩家的ID。
+ ///
+ public Guid PlayerId { get; set; }
+
+ ///
+ /// 涂色点的X坐标。
+ ///
+ public int X { get; set; }
+
+ ///
+ /// 涂色点的Y坐标。
+ ///
+ public int Y { get; set; }
+
+ ///
+ /// 笔刷的大小。
+ ///
+ public int BrushSize { get; set; }
+
+ ///
+ /// 动作发生时的时间戳。
+ ///
+ public DateTime Timestamp { get; set; }
+
+ ///
+ /// 导航属性,指向游戏房间。
+ ///
+ public GameRoom? GameRoom { get; set; }
+
+ ///
+ /// 导航属性,指向玩家。
+ ///
+ public Player? Player { get; set; }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Domain/Entities/Player.cs b/backend/src/TerritoryGame.Domain/Entities/Player.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1e12d2fd48a63099a24c28aba4fe2e059ca097f0
--- /dev/null
+++ b/backend/src/TerritoryGame.Domain/Entities/Player.cs
@@ -0,0 +1,87 @@
+namespace TerritoryGame.Domain.Entities;
+
+///
+/// 玩家实体
+///
+public class Player
+{
+ ///
+ /// 玩家ID
+ ///
+ public Guid Id { get; private set; }
+
+ ///
+ /// 玩家昵称
+ ///
+ public string NickName { get; private set; }
+
+ ///
+ /// 玩家头像的URL
+ ///
+ public string? Avatar { get; private set; }
+
+ ///
+ /// 玩家进行的游戏总数
+ ///
+ public int TotalGames { get; private set; }
+
+ ///
+ /// 玩家获胜的游戏数
+ ///
+ public int WinCount { get; private set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; private set; }
+
+ ///
+ /// 游戏会话列表
+ ///
+ public ICollection GameSessions { get; private set; } = new List();
+
+ ///
+ /// 涂色动作列表
+ ///
+ public ICollection PaintActions { get; private set; } = new List();
+
+ // EF Core 构造函数
+ private Player() { }
+
+ public Player(string nickName, string? avatar = null)
+ {
+ Id = Guid.NewGuid();
+ NickName = nickName;
+ Avatar = avatar;
+ TotalGames = 0;
+ WinCount = 0;
+ CreatedAt = DateTime.UtcNow;
+ }
+
+ ///
+ /// 更新玩家资料
+ ///
+ /// 新昵称
+ /// 新头像
+ public void UpdateProfile(string newNickName, string? newAvatar)
+ {
+ if (string.IsNullOrWhiteSpace(newNickName))
+ throw new ArgumentException("昵称不能为空。", nameof(newNickName));
+
+ NickName = newNickName;
+ Avatar = newAvatar;
+ }
+
+ ///
+ /// 记录一次游戏结果
+ ///
+ /// 是否获胜
+ public void RecordGamePlayed(bool hasWon)
+ {
+ TotalGames++;
+ if (hasWon)
+ {
+ WinCount++;
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Domain/Enums/GameStatus.cs b/backend/src/TerritoryGame.Domain/Enums/GameStatus.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9d759f3f05050fe47258ffde19ca5907dc7570cf
--- /dev/null
+++ b/backend/src/TerritoryGame.Domain/Enums/GameStatus.cs
@@ -0,0 +1,22 @@
+namespace TerritoryGame.Domain.Enums;
+
+///
+/// 游戏状态
+///
+public enum GameStatus
+{
+ ///
+ /// 等待中
+ ///
+ Waiting,
+
+ ///
+ /// 进行中
+ ///
+ InProgress,
+
+ ///
+ /// 已结束
+ ///
+ Finished
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Domain/Repositories/IPlayerRepository.cs b/backend/src/TerritoryGame.Domain/Repositories/IPlayerRepository.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d6e47a5f2762685d9abf5b43a19d3d6c54908980
--- /dev/null
+++ b/backend/src/TerritoryGame.Domain/Repositories/IPlayerRepository.cs
@@ -0,0 +1,11 @@
+using TerritoryGame.Domain.Entities;
+
+namespace TerritoryGame.Domain.Repositories;
+
+public interface IPlayerRepository
+{
+ Task GetByIdAsync(Guid id);
+ Task AddAsync(Player player);
+ Task UpdateAsync(Player player);
+ Task DeleteAsync(Player player);
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Domain/Services/GameLogicService.cs b/backend/src/TerritoryGame.Domain/Services/GameLogicService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..71c305a93fb0480b4e0b56be7633fd257212eb65
--- /dev/null
+++ b/backend/src/TerritoryGame.Domain/Services/GameLogicService.cs
@@ -0,0 +1,46 @@
+using TerritoryGame.Domain.Entities;
+
+namespace TerritoryGame.Domain.Services;
+
+///
+/// 游戏核心逻辑的服务
+///
+public class GameLogicService : IGameLogicService
+{
+ ///
+ /// 处理玩家的涂色动作
+ ///
+ /// 当前游戏房间
+ /// 执行动作的玩家
+ /// X坐标
+ /// Y坐标
+ /// 笔刷大小
+ public void HandlePaintAction(GameRoom gameRoom, Player player, int x, int y, int brushSize)
+ {
+ // 1. 验证游戏是否正在进行中
+ if (gameRoom.Status != Enums.GameStatus.InProgress)
+ {
+ // 可以抛出异常或直接返回
+ return;
+ }
+
+ // 2. 验证玩家是否在房间内
+ if (!gameRoom.Players.Any(p => p.Id == player.Id))
+ {
+ // 玩家不在房间内,非法操作
+ return;
+ }
+
+ // 3. 创建并记录涂色动作(此处只是示例,具体实现可能需要更复杂的逻辑)
+ var paintAction = new PaintAction(player.Id, gameRoom.Id, x, y, brushSize, DateTime.UtcNow);
+ gameRoom.PaintActions.Add(paintAction);
+
+ // 4. TODO: 在此实现更复杂的逻辑,例如:
+ // - 检查涂色区域是否合法(例如,是否在画布边界内)
+ // - 判断是否覆盖了其他玩家的颜色
+ // - 计算新占领的面积
+ // - 发布领域事件(如 AreaPaintedEvent),通知其他部分(如应用层)更新状态或通知客户端
+
+ Console.WriteLine($"Player {player.NickName} painted at ({x}, {y}) in room {gameRoom.Name}");
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Domain/Services/IGameLogicService.cs b/backend/src/TerritoryGame.Domain/Services/IGameLogicService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7cac3ad83aecbaebb9c3fc01fad5c475c1feb3e3
--- /dev/null
+++ b/backend/src/TerritoryGame.Domain/Services/IGameLogicService.cs
@@ -0,0 +1,19 @@
+using TerritoryGame.Domain.Entities;
+
+namespace TerritoryGame.Domain.Services;
+
+///
+/// 定义游戏核心逻辑的服务接口
+///
+public interface IGameLogicService
+{
+ ///
+ /// 处理玩家的涂色动作
+ ///
+ /// 当前游戏房间
+ /// 执行动作的玩家
+ /// X坐标
+ /// Y坐标
+ /// 笔刷大小
+ void HandlePaintAction(GameRoom gameRoom, Player player, int x, int y, int brushSize);
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/PlayerRepository.cs b/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/PlayerRepository.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d61bbd19c89c7ae6bf58fc0285a64d3690e2dccd
--- /dev/null
+++ b/backend/src/TerritoryGame.Infrastructure/Persistence/Repositories/PlayerRepository.cs
@@ -0,0 +1,39 @@
+using Microsoft.EntityFrameworkCore;
+using TerritoryGame.Domain.Entities;
+using TerritoryGame.Domain.Repositories;
+using TerritoryGame.Infrastructure.Persistence;
+
+namespace TerritoryGame.Infrastructure.Persistence.Repositories;
+
+public class PlayerRepository : IPlayerRepository
+{
+ private readonly TerritoryGameDbContext _context;
+
+ public PlayerRepository(TerritoryGameDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task GetByIdAsync(Guid id)
+ {
+ return await _context.Players.FindAsync(id);
+ }
+
+ public async Task AddAsync(Player player)
+ {
+ await _context.Players.AddAsync(player);
+ await _context.SaveChangesAsync();
+ }
+
+ public async Task UpdateAsync(Player player)
+ {
+ _context.Players.Update(player);
+ await _context.SaveChangesAsync();
+ }
+
+ public async Task DeleteAsync(Player player)
+ {
+ _context.Players.Remove(player);
+ await _context.SaveChangesAsync();
+ }
+}
\ No newline at end of file
diff --git a/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs b/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bf3981effa96e2b91c42bb26b7077fc5d5d49f98
--- /dev/null
+++ b/backend/src/TerritoryGame.Infrastructure/Persistence/TerritoryGameDbContext.cs
@@ -0,0 +1,41 @@
+using Microsoft.EntityFrameworkCore;
+using TerritoryGame.Domain.Entities;
+
+namespace TerritoryGame.Infrastructure.Persistence;
+
+public class TerritoryGameDbContext : DbContext
+{
+ public TerritoryGameDbContext(DbContextOptions options) : base(options)
+ {
+ }
+
+ public DbSet Players { get; set; }
+ public DbSet GameRooms { get; set; }
+ public DbSet GameSessions { get; set; }
+ public DbSet PaintActions { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity()
+ .HasOne(gs => gs.GameRoom)
+ .WithMany(gr => gr.GameSessions)
+ .HasForeignKey(gs => gs.RoomId);
+
+ modelBuilder.Entity()
+ .HasOne(gs => gs.Player)
+ .WithMany(p => p.GameSessions)
+ .HasForeignKey(gs => gs.PlayerId);
+
+ modelBuilder.Entity()
+ .HasOne(pa => pa.GameRoom)
+ .WithMany(gr => gr.PaintActions)
+ .HasForeignKey(pa => pa.RoomId);
+
+ modelBuilder.Entity()
+ .HasOne(pa => pa.Player)
+ .WithMany(p => p.PaintActions)
+ .HasForeignKey(pa => pa.PlayerId);
+ }
+}
\ No newline at end of file
diff --git a/docs/BACKEND_DEVELOPMENT_PROCESS.md b/docs/BACKEND_DEVELOPMENT_PROCESS.md
new file mode 100644
index 0000000000000000000000000000000000000000..8ccbe7e78cbd9f81b791db0603e626204717965a
--- /dev/null
+++ b/docs/BACKEND_DEVELOPMENT_PROCESS.md
@@ -0,0 +1,207 @@
+# 后端开发全流程指南
+
+本文档旨在为后端开发团队提供一个标准化的、端到端的开发流程,涵盖从需求分析到最终部署和长期维护的每一个环节。遵循此流程有助于提高开发效率、保证代码质量、降低沟通成本,并确保项目成功交付。
+
+## 目录
+1. [阶段一:需求分析 (Requirements Analysis)](#阶段一需求分析-requirements-analysis)
+2. [阶段二:系统设计 (System Design)](#阶段二系统设计-system-design)
+3. [阶段三:开发实现 (Development)](#阶段三开发实现-development)
+4. [阶段四:测试验证 (Testing)](#阶段四测试验证-testing)
+5. [阶段五:部署发布 (Deployment)](#阶段五部署发布-deployment)
+6. [阶段六:运维与监控 (Operations & Monitoring)](#阶段六运维与监控-operations--monitoring)
+7. [阶段七:维护与迭代 (Maintenance & Iteration)](#阶段七维护与迭代-maintenance--iteration)
+
+---
+
+## 阶段一:需求分析 (Requirements Analysis)
+
+此阶段的目标是清晰、完整地理解要做什么,并确保所有项目相关方对需求有统一的认识。
+
+- **关键任务**:
+ - 与产品经理(PM)、项目经理和客户进行深入沟通,理解业务目标和用户场景。
+ - 明确功能性需求(如:用户注册、商品下单、数据查询等)。
+ - 明确非功能性需求(如:性能指标、安全标准、可扩展性、数据一致性要求等)。
+ - 参与需求评审会议,从技术角度评估需求的可行性、复杂度和潜在风险。
+
+- **参与人员**:
+ - `主要`:后端开发负责人、产品经理
+ - `次要`:架构师、测试工程师、项目经理
+
+- **交付物**:
+ - **需求规格说明书 (SRS)**:由产品经理主导,技术团队评审确认。
+ - **用户故事 (User Stories)** 和 **用例 (Use Cases)**:清晰描述每个功能点。
+ - **技术可行性分析报告**:针对复杂或不明确的需求。
+
+- **最佳实践**:
+ - **刨根问底**:不要只满足于表面需求,要理解其背后的商业逻辑。
+ - **量化指标**:尽可能量化非功能性需求,例如“接口响应时间应在200ms以内”、“系统需支持1000 QPS的并发量”。
+ - **早期介入**:开发人员应尽早参与需求讨论,以便提前识别技术挑战。
+ - **文档化**:所有需求和变更都应记录在案,避免口头约定。
+
+---
+
+## 阶段二:系统设计 (System Design)
+
+此阶段将需求转化为详细的技术方案和蓝图,是后续开发工作的指导方针。
+
+- **关键任务**:
+ - **架构设计**:根据需求选择合适的架构模式(如:微服务、单体、Serverless),划分服务模块。
+ - **技术选型**:选择合适的编程语言、框架、中间件(如:数据库、缓存、消息队列)。
+ - **数据库设计**:设计数据库表结构(ER图)、索引和数据字典。
+ - **API 设计**:设计清晰、一致的 API 接口(如:RESTful、GraphQL),并撰写接口文档。
+ - **安全设计**:规划认证授权方案(如:OAuth2, JWT)、数据加密、防范常见攻击(如:SQL注入, XSS)。
+
+- **参与人员**:
+ - `主要`:架构师、高级后端工程师
+ - `次要`:后端开发团队、DBA、安全专家
+
+- **交付物**:
+ - **架构设计文档**:包含系统架构图、模块划分、核心流程图。
+ - **数据库设计文档**:包含 ER 图、表结构详情、数据字典。
+ - **API 接口文档**:使用 OpenAPI/Swagger 等工具生成,包含详细的请求/响应格式、参数说明和错误码。
+ - **技术选型报告**:说明选型原因和优缺点对比。
+
+- **最佳实践**:
+ - **KISS, DRY, SOLID**:遵循高内聚、低耦合等基本设计原则。
+ - **面向未来设计**:充分考虑系统的可扩展性、可维护性和弹性。
+ - **评审机制**:组织设计评审会议,让团队成员充分讨论,凝聚共识。
+ - **文档先行**:在编码前完成关键设计文档的编写。
+
+---
+
+## 阶段三:开发实现 (Development)
+
+此阶段是根据设计文档将技术方案转化为实际可运行的代码。
+
+- **关键任务**:
+ - 搭建和配置本地开发环境。
+ - 根据任务分配,实现业务逻辑、数据访问层和 API 接口。
+ - 编写单元测试,确保代码模块的正确性。
+ - 代码审查(Code Review),保证代码质量和风格统一。
+ - 与前端、测试等团队保持密切沟通,及时解决联调和集成问题。
+ - 配置并实践持续集成(CI),自动化构建和测试流程。
+
+- **参与人员**:
+ - `主要`:后端开发工程师
+
+- **交付物**:
+ - **源代码**:托管于版本控制系统(如 Git)。
+ - **单元测试用例**及测试报告。
+ - **Code Review 记录**。
+ - **构建脚本**(如:Dockerfile, Maven/Gradle 配置)。
+
+- **最佳实践**:
+ - **编码规范**:遵循团队统一的编码规范和代码风格。
+ - **版本控制**:使用 Git Flow 或类似的分支管理策略,保持提交信息清晰明了。
+ - **TDD/BDD**:鼓励测试驱动开发或行为驱动开发。
+ - **小步快跑**:频繁提交、小批量集成,尽早发现和解决冲突。
+ - **注释与文档**:对复杂逻辑或公共模块编写必要的注释和开发者文档。
+
+---
+
+## 阶段四:测试验证 (Testing)
+
+此阶段通过系统化的测试来保证软件质量,确保其符合需求并能稳定运行。
+
+- **关键任务**:
+ - **集成测试**:测试不同模块或服务协同工作的正确性。
+ - **API 功能测试**:使用 Postman、Insomnia 等工具对所有 API 接口进行黑盒测试。
+ - **性能测试**:对核心接口进行压力测试和负载测试,评估系统性能瓶颈。
+ - **安全测试**:进行漏洞扫描和渗透测试。
+ - **回归测试**:在新版本发布前,确保原有功能未受影响。
+ - 缺陷管理:记录、跟踪和验证 Bug 的修复。
+
+- **参与人员**:
+ - `主要`:测试工程师(QA)
+ - `次要`:后端开发工程师、产品经理
+
+- **交付物**:
+ - **测试计划**和**测试用例**。
+ - **测试报告**:包含功能、性能、安全等维度的测试结果。
+ - **缺陷报告列表**(Bug List)。
+
+- **最佳实践**:
+ - **测试自动化**:将回归测试和核心功能的 API 测试自动化,提高效率。
+ - **分层测试**:建立单元测试、集成测试、端到端测试的金字塔模型。
+ - **隔离的测试环境**:搭建与生产环境尽可能一致的独立测试环境。
+ - **有效的缺陷管理**:使用 Jira 等工具进行系统化的缺陷跟踪。
+
+---
+
+## 阶段五:部署发布 (Deployment)
+
+此阶段将经过测试验证的应用程序安全、平稳地发布到生产环境。
+
+- **关键任务**:
+ - 准备生产环境,包括服务器、网络、数据库等基础设施。
+ - 编写和测试部署脚本(如:Ansible, Terraform)。
+ - 构建最终的部署产物(如:Docker 镜像)。
+ - 执行部署流程,并进行部署后验证(Smoke Test)。
+ - 制定回滚计划,以应对部署失败的情况。
+
+- **参与人员**:
+ - `主要`:运维工程师(DevOps/Ops)
+ - `次要`:后端开发工程师
+
+- **交付物**:
+ - **持续部署(CD)流水线**。
+ - **部署操作手册**和**回滚方案**。
+ - **版本发布说明(Release Notes)**。
+
+- **最佳实践**:
+ - **IaC(基础设施即代码)**:使用代码来管理和配置基础设施,实现自动化和可重复性。
+ - **蓝绿部署/金丝雀发布**:采用先进的发布策略,实现零停机或灰度发布,降低发布风险。
+ - **自动化**:尽可能自动化整个部署流程,减少人为错误。
+ - **配置中心**:将应用配置与代码分离,实现动态配置管理。
+
+---
+
+## 阶段六:运维与监控 (Operations & Monitoring)
+
+应用上线后,需要持续监控其运行状态,保障服务的稳定性和可用性。
+
+- **关键任务**:
+ - **日志管理**:集中收集、存储和分析应用日志(ELK/EFK Stack)。
+ - **性能监控**:监控系统关键指标(CPU、内存、磁盘、网络、JVM/CLR 等)。
+ - **业务监控**:监控核心业务指标(如:订单量、用户注册数)。
+ - **告警系统**:设置合理的告警阈值,在问题发生时通过短信、电话、邮件等方式及时通知相关人员。
+ - **备份与恢复**:制定并定期演练数据备份和灾难恢复计划。
+
+- **参与人员**:
+ - `主要`:运维工程师(DevOps/SRE)
+ - `次要`:后端开发工程师
+
+- **交付物**:
+ - **监控仪表盘(Dashboard)**:如 Grafana, Prometheus。
+ - **告警规则和通知策略**。
+ - **应急预案(On-Call Playbook)**和**灾备演练报告**。
+
+- **最佳实践**:
+ - **可观测性(Observability)**:建立涵盖日志(Logging)、指标(Metrics)和追踪(Tracing)的全面监控体系。
+ - **定义SLO/SLA**:明确服务水平目标和服务水平协议,作为衡量服务质量的标准。
+ - **自动化运维**:利用脚本和工具实现自动化故障发现和自愈。
+ - **定期演练**:定期进行故障注入和灾备演练,确保预案的有效性。
+
+---
+
+## 阶段七:维护与迭代 (Maintenance & Iteration)
+
+此阶段是应用的长期生命周期管理,包括修复问题、优化系统和响应新的业务需求。
+
+- **关键任务**:
+ - **问题修复**:快速响应和修复线上发现的 Bug。
+ - **技术优化**:进行代码重构、性能优化、升级老旧依赖,偿还技术债务。
+ - **需求迭代**:根据用户反馈和新的业务规划,进入新的开发循环(重复阶段一至六)。
+ - **文档更新**:同步更新架构、设计、API 等文档,确保其与代码一致。
+
+- **参与人员**:
+ - 所有项目相关方
+
+- **交付物**:
+ - **软件补丁**和**新版本**。
+ - **更新后的各类文档**。
+
+- **最佳实践**:
+ - **敏捷迭代**:采用 Scrum 或 Kanban 等敏捷方法,快速响应变化。
+ - **建立知识库**:沉淀问题解决方案和最佳实践,供团队共享。
+ - **定期复盘**:定期召开项目复盘会议,总结经验教训,持续改进流程。
\ No newline at end of file