25 KiB
25 KiB
Admin Service 架构文档
目录
1. 服务概述
1.1 服务职责
Admin Service 是 RWA Durian 项目的应用版本管理服务,负责:
- 📱 版本发布管理: 管理 Android/iOS 应用版本的创建、更新、启用/禁用
- 🔄 版本检查: 为移动端提供版本检查 API,支持强制更新和普通更新
- 📊 版本查询: 支持按平台、版本号、启用状态等条件查询版本信息
- 🔐 SHA256 校验: 确保 APK/IPA 文件完整性和安全性
1.2 核心功能
| 功能 | 说明 | API 端点 |
|---|---|---|
| 创建版本 | 发布新版本(Android/iOS) | POST /api/v1/version |
| 检查更新 | 移动端检查是否有新版本 | GET /api/v1/version/check |
| 查询版本 | 查询所有版本或特定版本 | GET /api/v1/version |
| 启用/禁用版本 | 控制版本可用性 | PATCH /api/v1/version/:id/enable PATCH /api/v1/version/:id/disable |
2. 架构设计
2.1 架构模式
Admin Service 采用 DDD (领域驱动设计) + Hexagonal Architecture (六边形架构) 的混合架构模式。
┌─────────────────────────────────────────────────────────┐
│ API Layer (NestJS) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Controllers │ │ DTOs │ │ Guards │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Application Layer (Handlers) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Commands │ │ Queries │ │ Events │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Domain Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Entities │ │ Value Objects│ │ Services │ │
│ │ │ │ │ │ │ │
│ │ AppVersion │ │VersionCode │ │VersionCheck │ │
│ │ │ │VersionName │ │ Service │ │
│ │ │ │ FileSize │ │ │ │
│ │ │ │ FileSha256 │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Repository Interfaces (Port) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Infrastructure Layer (Adapters) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Repositories │ │ Mappers │ │ Prisma │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌──────────┐
│PostgreSQL│
└──────────┘
2.2 分层职责
API Layer (接口层)
- Controllers: 处理 HTTP 请求,路由分发
- DTOs: 定义请求/响应数据传输对象
- Guards: 身份验证、权限控制 (暂未实现)
- 依赖方向: → Application Layer
Application Layer (应用层)
- Command Handlers: 处理写操作命令 (Create, Update, Delete)
- Query Handlers: 处理读操作查询 (Get, List, Find)
- Event Handlers: 处理领域事件 (暂未实现)
- 依赖方向: → Domain Layer
Domain Layer (领域层)
- Entities: 聚合根,包含业务逻辑 (
AppVersion) - Value Objects: 不可变值对象 (
VersionCode,VersionName,FileSize,FileSha256) - Domain Services: 跨实体的业务逻辑 (
VersionCheckService) - Repository Interfaces: 持久化端口定义
- 依赖方向: 无外部依赖 (核心层)
Infrastructure Layer (基础设施层)
- Repositories: Repository 接口的 Prisma 实现
- Mappers: 领域对象 ↔ 持久化对象转换
- Prisma Client: 数据库 ORM
- 依赖方向: → Domain Layer (依赖倒置)
3. 领域设计
3.1 领域模型
聚合根: AppVersion
class AppVersion {
// 标识
private readonly _id: string;
private readonly _platform: Platform;
// 版本信息
private readonly _versionCode: VersionCode;
private readonly _versionName: VersionName;
private readonly _buildNumber: string;
// 文件信息
private _downloadUrl: string;
private readonly _fileSize: FileSize;
private readonly _fileSha256: FileSha256;
// 更新信息
private _changelog: string;
private _isEnabled: boolean;
private _isForceUpdate: boolean;
// 审计信息
private readonly _createdBy: string;
private readonly _createdAt: Date;
private _updatedBy: string;
private _updatedAt: Date;
}
业务不变式:
versionCode必须是正整数versionName必须符合语义化版本格式 (x.y.z)fileSize必须大于 0fileSha256必须是有效的 64 位十六进制字符串- 同一平台同一版本号的版本只能有一个启用
值对象
VersionCode (版本号)
class VersionCode {
constructor(private readonly value: number) {
if (!Number.isInteger(value) || value < 1) {
throw new DomainException('Version code must be a positive integer');
}
}
isGreaterThan(other: VersionCode): boolean
isLessThan(other: VersionCode): boolean
equals(other: VersionCode): boolean
}
VersionName (版本名称)
class VersionName {
private readonly SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
constructor(private readonly value: string) {
if (!this.SEMVER_REGEX.test(value)) {
throw new DomainException('Invalid semantic version format');
}
}
get major(): number
get minor(): number
get patch(): number
}
FileSize (文件大小)
class FileSize {
constructor(private readonly bytes: bigint) {
if (bytes < 0n) {
throw new DomainException('File size cannot be negative');
}
}
toHumanReadable(): string // "1.50 MB"
toMegabytes(): string // "1.50"
}
FileSha256 (SHA256 哈希)
class FileSha256 {
private readonly SHA256_REGEX = /^[a-f0-9]{64}$/;
constructor(private readonly hash: string) {
if (!this.SHA256_REGEX.test(hash.toLowerCase())) {
throw new DomainException('Invalid SHA256 hash format');
}
}
}
3.2 领域服务
VersionCheckService
class VersionCheckService {
async checkForUpdate(
platform: Platform,
currentVersionCode: number,
): Promise<VersionCheckResult> {
// 1. 查找最新启用的版本
const latestVersion = await this.repository.findLatestEnabledVersion(platform);
// 2. 比较版本号
if (!latestVersion || latestVersion.versionCode.value <= currentVersionCode) {
return VersionCheckResult.noUpdate();
}
// 3. 返回更新信息
return VersionCheckResult.hasUpdate({
latestVersion: latestVersion.versionName.value,
downloadUrl: latestVersion.downloadUrl,
isForceUpdate: latestVersion.isForceUpdate,
changelog: latestVersion.changelog,
});
}
}
3.3 业务规则
| 规则 | 实现位置 | 验证时机 |
|---|---|---|
| 版本号必须唯一 | AppVersionRepository |
创建版本时 |
| 禁用版本不能强制更新 | AppVersion.disable() |
禁用操作时 |
| 文件大小必须 > 0 | FileSize VO |
值对象创建时 |
| SHA256 必须 64 位十六进制 | FileSha256 VO |
值对象创建时 |
| 版本名称必须符合 semver | VersionName VO |
值对象创建时 |
4. 技术栈
4.1 核心框架
| 技术 | 版本 | 用途 |
|---|---|---|
| NestJS | 10.0.0 | Web 框架 |
| TypeScript | 5.1.3 | 编程语言 |
| Node.js | 20.x | 运行时 |
| Prisma | 5.7.0 | ORM |
| PostgreSQL | 16 | 数据库 |
4.2 开发工具
| 工具 | 版本 | 用途 |
|---|---|---|
| Jest | 29.5.0 | 测试框架 |
| ts-jest | 29.1.0 | TypeScript + Jest |
| Supertest | 6.3.3 | HTTP 测试 |
| ESLint | 8.42.0 | 代码检查 |
| Prettier | 3.0.0 | 代码格式化 |
4.3 部署工具
| 工具 | 用途 |
|---|---|
| Docker | 容器化 |
| Docker Compose | 多容器编排 |
| Makefile | 自动化脚本 |
5. 目录结构
admin-service/
├── src/
│ ├── api/ # API 层
│ │ ├── controllers/ # 控制器
│ │ │ └── version.controller.ts
│ │ └── dtos/ # 数据传输对象
│ │ ├── create-version.dto.ts
│ │ ├── update-version.dto.ts
│ │ ├── check-version.dto.ts
│ │ └── version-response.dto.ts
│ │
│ ├── application/ # 应用层
│ │ ├── commands/ # 命令
│ │ │ ├── create-version.command.ts
│ │ │ ├── enable-version.command.ts
│ │ │ └── disable-version.command.ts
│ │ ├── handlers/ # 处理器
│ │ │ ├── create-version.handler.ts
│ │ │ ├── enable-version.handler.ts
│ │ │ └── disable-version.handler.ts
│ │ └── queries/ # 查询
│ │ ├── find-version-by-id.query.ts
│ │ └── find-all-versions.query.ts
│ │
│ ├── domain/ # 领域层
│ │ ├── entities/ # 实体
│ │ │ └── app-version.entity.ts
│ │ ├── value-objects/ # 值对象
│ │ │ ├── version-code.vo.ts
│ │ │ ├── version-name.vo.ts
│ │ │ ├── file-size.vo.ts
│ │ │ └── file-sha256.vo.ts
│ │ ├── repositories/ # 仓储接口
│ │ │ └── app-version.repository.ts
│ │ ├── services/ # 领域服务
│ │ │ └── version-check.service.ts
│ │ └── enums/ # 枚举
│ │ └── platform.enum.ts
│ │
│ ├── infrastructure/ # 基础设施层
│ │ ├── persistence/ # 持久化
│ │ │ ├── repositories/ # 仓储实现
│ │ │ │ └── app-version.repository.impl.ts
│ │ │ └── mappers/ # 映射器
│ │ │ └── app-version.mapper.ts
│ │ └── prisma/ # Prisma
│ │ └── prisma.service.ts
│ │
│ ├── shared/ # 共享模块
│ │ ├── exceptions/ # 异常
│ │ │ ├── domain.exception.ts
│ │ │ └── application.exception.ts
│ │ └── utils/ # 工具
│ │
│ ├── app.module.ts # 根模块
│ └── main.ts # 入口文件
│
├── prisma/
│ ├── schema.prisma # Prisma Schema
│ └── migrations/ # 数据库迁移
│
├── test/ # 测试
│ ├── unit/ # 单元测试
│ ├── integration/ # 集成测试
│ └── e2e/ # E2E 测试
│
├── database/ # 数据库初始化
│ ├── init.sql # 初始化脚本
│ └── README.md
│
├── docs/ # 文档
│ ├── ARCHITECTURE.md # 本文档
│ ├── API.md # API 文档
│ ├── DEVELOPMENT.md # 开发指南
│ ├── TESTING.md # 测试文档
│ └── DEPLOYMENT.md # 部署文档
│
├── scripts/ # 脚本
│ ├── test-in-wsl.sh
│ ├── run-wsl-tests.ps1
│ └── test-with-docker-db.sh
│
├── docker-compose.yml # Docker Compose
├── Dockerfile # Dockerfile
├── Makefile # Make 命令
├── package.json # NPM 配置
├── tsconfig.json # TypeScript 配置
└── README.md # 项目说明
6. 数据流
6.1 创建版本流程
Client Request (POST /api/v1/version)
│
▼
┌─────────────────────────────────────┐
│ VersionController.createVersion() │ ← API Layer
└─────────────────────────────────────┘
│ CreateVersionDto
▼
┌─────────────────────────────────────┐
│ CreateVersionHandler.execute() │ ← Application Layer
│ 1. Create Command │
│ 2. Validate Business Rules │
│ 3. Call Repository │
└─────────────────────────────────────┘
│ CreateVersionCommand
▼
┌─────────────────────────────────────┐
│ AppVersion.create() │ ← Domain Layer
│ 1. Create Value Objects │
│ - VersionCode │
│ - VersionName │
│ - FileSize │
│ - FileSha256 │
│ 2. Create Entity │
│ 3. Apply Business Rules │
└─────────────────────────────────────┘
│ AppVersion Entity
▼
┌─────────────────────────────────────┐
│ AppVersionRepositoryImpl.save() │ ← Infrastructure Layer
│ 1. Map Entity → Prisma Model │
│ 2. Save to Database │
│ 3. Return Persisted Entity │
└─────────────────────────────────────┘
│
▼
PostgreSQL
6.2 检查更新流程
Mobile Client (GET /api/v1/version/check?platform=android&versionCode=100)
│
▼
┌─────────────────────────────────────┐
│ VersionController.checkForUpdate() │ ← API Layer
└─────────────────────────────────────┘
│ CheckVersionDto
▼
┌─────────────────────────────────────┐
│ VersionCheckService.checkForUpdate()│ ← Domain Service
│ 1. Query Latest Enabled Version │
│ 2. Compare Version Codes │
│ 3. Build Update Result │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ AppVersionRepository │ ← Repository
│ .findLatestEnabledVersion() │
└─────────────────────────────────────┘
│
▼
PostgreSQL
│
▼
┌─────────────────────────────────────┐
│ VersionCheckResult │ ← Response
│ - hasUpdate: boolean │
│ - latestVersion: string │
│ - downloadUrl: string │
│ - isForceUpdate: boolean │
│ - changelog: string │
└─────────────────────────────────────┘
6.3 依赖方向
API Layer
↓ depends on
Application Layer
↓ depends on
Domain Layer (Core)
↑ implemented by
Infrastructure Layer
核心原则:
- Domain Layer 不依赖任何外部层
- Infrastructure Layer 通过接口依赖 Domain Layer (依赖倒置原则)
- Application Layer 协调 Domain 和 Infrastructure
- API Layer 仅依赖 Application Layer
7. 设计原则
7.1 SOLID 原则应用
| 原则 | 应用实例 |
|---|---|
| S (单一职责) | 每个值对象只负责一个验证逻辑 每个 Handler 只处理一个命令/查询 |
| O (开闭原则) | 新增平台类型无需修改现有代码 通过 enum 扩展实现 |
| L (里氏替换) | Repository 接口可被不同实现替换 (Prisma, TypeORM, InMemory) |
| I (接口隔离) | Repository 接口仅定义必要方法 不强制实现不需要的功能 |
| D (依赖倒置) | Domain Layer 定义 Repository 接口 Infrastructure Layer 实现接口 |
7.2 DDD 战术模式
| 模式 | 应用 |
|---|---|
| Entity | AppVersion 聚合根 |
| Value Object | VersionCode, VersionName, FileSize, FileSha256 |
| Aggregate | AppVersion 作为聚合边界 |
| Repository | AppVersionRepository 接口及实现 |
| Domain Service | VersionCheckService 处理跨实体逻辑 |
| Factory Method | AppVersion.create() 静态工厂方法 |
8. 扩展性设计
8.1 新增平台支持
当需要支持新平台(如 HarmonyOS)时:
- 枚举扩展 (
domain/enums/platform.enum.ts):
export enum Platform {
ANDROID = 'android',
IOS = 'ios',
HARMONYOS = 'harmonyos', // 新增
}
- 无需修改:
- Entity 逻辑
- Repository 实现
- Controller/Handler
8.2 新增版本检查策略
当需要支持灰度发布、A/B 测试时:
- 新增领域服务:
class GrayReleaseService {
async checkEligibility(userId: string, version: AppVersion): Promise<boolean>
}
- 修改 VersionCheckService:
async checkForUpdate(
platform: Platform,
currentVersionCode: number,
userId?: string, // 新增参数
): Promise<VersionCheckResult>
9. 性能考量
9.1 数据库索引
-- 平台 + 版本号唯一索引
CREATE UNIQUE INDEX idx_platform_versioncode
ON "AppVersion" (platform, "versionCode");
-- 启用状态 + 平台 + 版本号索引(查询最新版本)
CREATE INDEX idx_enabled_platform_versioncode
ON "AppVersion" ("isEnabled", platform, "versionCode" DESC);
9.2 缓存策略
建议实现 (当前未实现):
@Injectable()
export class CachedVersionCheckService {
constructor(
private readonly versionCheckService: VersionCheckService,
private readonly cacheManager: Cache,
) {}
async checkForUpdate(platform: Platform, versionCode: number) {
const cacheKey = `version:${platform}:${versionCode}`;
const cached = await this.cacheManager.get(cacheKey);
if (cached) return cached;
const result = await this.versionCheckService.checkForUpdate(platform, versionCode);
await this.cacheManager.set(cacheKey, result, { ttl: 300 }); // 5分钟
return result;
}
}
10. 安全性
10.1 文件校验
- SHA256 验证: 确保下载文件未被篡改
- 下载 URL: 建议使用 HTTPS + CDN
- 文件大小: 防止异常大文件攻击
10.2 API 安全 (待实现)
@Controller('api/v1/version')
@UseGuards(JwtAuthGuard) // 管理端需要认证
export class VersionController {
@Post()
@UseGuards(RolesGuard)
@Roles('admin', 'developer') // 仅管理员和开发者可创建版本
async createVersion(@Body() dto: CreateVersionDto) {}
@Get('check')
// 公开端点,无需认证
async checkForUpdate(@Query() dto: CheckVersionDto) {}
}
11. 监控和日志
11.1 关键指标
| 指标 | 说明 | 监控方式 |
|---|---|---|
| 版本检查 QPS | 每秒查询次数 | Prometheus + Grafana |
| 创建版本成功率 | 创建操作成功/失败比例 | Application Logs |
| 数据库查询延迟 | 查询耗时 | Prisma Metrics |
| 强制更新触发率 | 强制更新用户占比 | Business Metrics |
11.2 日志记录
@Injectable()
export class CreateVersionHandler {
private readonly logger = new Logger(CreateVersionHandler.name);
async execute(command: CreateVersionCommand): Promise<AppVersion> {
this.logger.log(`Creating version: ${command.platform} v${command.versionName}`);
try {
const version = await this.repository.save(appVersion);
this.logger.log(`Version created successfully: ${version.id}`);
return version;
} catch (error) {
this.logger.error(`Failed to create version: ${error.message}`, error.stack);
throw error;
}
}
}
12. 未来改进
12.1 短期 (1-3 个月)
- 实现 JWT 认证和 RBAC 权限控制
- 添加版本删除功能(软删除)
- 实现分页查询
- 添加 Redis 缓存层
12.2 中期 (3-6 个月)
- 实现灰度发布功能
- 添加版本回滚机制
- 实现版本发布审批流程
- 集成 CDN 文件上传
12.3 长期 (6-12 个月)
- 实现多渠道版本管理(Google Play, App Store, 自建服务器)
- 添加 A/B 测试支持
- 实现版本使用统计和分析
- 集成 Sentry 错误监控
最后更新: 2025-12-03 版本: 1.0.0 维护者: RWA Durian Team