11 KiB
11 KiB
Mobile Upgrade Admin - 架构文档
概述
Mobile Upgrade Admin 是一个用于管理移动应用版本升级的 Web 管理后台。本项目采用 Clean Architecture(整洁架构) 设计模式,实现了关注点分离、可测试性和可维护性。
技术栈
| 分类 | 技术 | 版本 | 说明 |
|---|---|---|---|
| 框架 | Next.js | 14.x | React 全栈框架,App Router |
| 语言 | TypeScript | 5.x | 类型安全 |
| 状态管理 | Zustand | 4.x | 轻量级状态管理 |
| HTTP 客户端 | Axios | 1.x | API 请求 |
| 样式 | Tailwind CSS | 3.x | 原子化 CSS |
| 通知 | react-hot-toast | 2.x | Toast 通知 |
架构设计
Clean Architecture 分层
┌─────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ (UI Components, Pages) │
├─────────────────────────────────────────────────────────────┤
│ Application Layer │
│ (Stores, Hooks, Use Cases) │
├─────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ (Entities, Repository Interfaces) │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ (API Client, Repository Implementations) │
└─────────────────────────────────────────────────────────────┘
目录结构
src/
├── domain/ # 领域层 - 业务核心
│ ├── entities/
│ │ └── version.ts # 版本实体和类型定义
│ ├── repositories/
│ │ └── version-repository.ts # Repository 接口定义
│ └── index.ts # 统一导出
│
├── infrastructure/ # 基础设施层 - 外部依赖
│ ├── http/
│ │ └── api-client.ts # Axios 客户端配置
│ ├── repositories/
│ │ └── version-repository-impl.ts # Repository 实现
│ └── index.ts # 统一导出
│
├── application/ # 应用层 - 业务逻辑
│ ├── stores/
│ │ └── version-store.ts # Zustand 状态管理
│ ├── hooks/
│ │ └── use-versions.ts # React Hooks
│ └── index.ts # 统一导出
│
├── presentation/ # 表示层 - UI 组件
│ ├── components/
│ │ ├── version-card.tsx # 版本卡片组件
│ │ ├── upload-modal.tsx # 上传弹窗组件
│ │ └── edit-modal.tsx # 编辑弹窗组件
│ └── index.ts # 统一导出
│
└── app/ # Next.js App Router
├── layout.tsx # 根布局
├── page.tsx # 首页
└── globals.css # 全局样式
各层职责详解
1. Domain Layer(领域层)
职责:定义业务实体和接口,不依赖任何外部框架。
实体定义 (entities/version.ts)
// 平台类型
export type Platform = 'android' | 'ios'
// 应用版本实体
export interface AppVersion {
id: string
platform: Platform
versionCode: number
versionName: string
buildNumber: string
downloadUrl: string
fileSize: string
fileSha256: string
changelog: string
isForceUpdate: boolean
isEnabled: boolean
minOsVersion: string | null
releaseDate: string | null
createdAt: string
updatedAt: string
}
// 输入/输出 DTO
export interface CreateVersionInput { ... }
export interface UpdateVersionInput { ... }
export interface UploadVersionInput { ... }
export interface VersionListFilter { ... }
Repository 接口 (repositories/version-repository.ts)
export interface IVersionRepository {
list(filter?: VersionListFilter): Promise<AppVersion[]>
getById(id: string): Promise<AppVersion>
create(input: CreateVersionInput): Promise<AppVersion>
update(id: string, input: UpdateVersionInput): Promise<AppVersion>
delete(id: string): Promise<void>
toggle(id: string, isEnabled: boolean): Promise<void>
upload(input: UploadVersionInput): Promise<AppVersion>
}
2. Infrastructure Layer(基础设施层)
职责:实现与外部系统的交互(API、数据库等)。
API 客户端 (http/api-client.ts)
import axios from 'axios'
export const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
})
// 响应拦截器处理错误
apiClient.interceptors.response.use(
(response) => response,
(error) => {
const message = error.response?.data?.message || '请求失败'
return Promise.reject(new Error(message))
}
)
Repository 实现 (repositories/version-repository-impl.ts)
export class VersionRepositoryImpl implements IVersionRepository {
private client: AxiosInstance
constructor(client?: AxiosInstance) {
this.client = client || apiClient
}
async list(filter?: VersionListFilter): Promise<AppVersion[]> {
const response = await this.client.get<AppVersion[]>('/api/v1/versions', { params: filter })
return response.data
}
async upload(input: UploadVersionInput): Promise<AppVersion> {
const formData = new FormData()
formData.append('file', input.file)
// ... 其他字段
const response = await this.client.post<AppVersion>('/api/v1/versions/upload', formData)
return response.data
}
// ... 其他方法
}
3. Application Layer(应用层)
职责:协调业务逻辑,管理应用状态。
Zustand Store (stores/version-store.ts)
import { create } from 'zustand'
interface VersionState {
// 状态
versions: AppVersion[]
selectedVersion: AppVersion | null
isLoading: boolean
error: string | null
filter: { platform?: Platform; includeDisabled: boolean }
// Actions
fetchVersions: () => Promise<void>
createVersion: (input: CreateVersionInput) => Promise<AppVersion>
updateVersion: (id: string, input: UpdateVersionInput) => Promise<AppVersion>
deleteVersion: (id: string) => Promise<void>
toggleVersion: (id: string, isEnabled: boolean) => Promise<void>
uploadVersion: (input: UploadVersionInput) => Promise<AppVersion>
}
export const useVersionStore = create<VersionState>((set, get) => ({
// 实现...
}))
React Hooks (hooks/use-versions.ts)
// 获取版本列表
export function useVersions() {
const { versions, isLoading, error, fetchVersions, setFilter } = useVersionStore()
useEffect(() => {
fetchVersions()
}, [filter.platform])
return { versions, isLoading, error, refetch: fetchVersions, setFilter }
}
// 获取单个版本
export function useVersion(id: string | null) { ... }
// 版本操作
export function useVersionActions() { ... }
4. Presentation Layer(表示层)
职责:UI 渲染和用户交互。
组件结构
presentation/
├── components/
│ ├── version-card.tsx # 版本信息卡片
│ ├── upload-modal.tsx # 上传新版本弹窗
│ └── edit-modal.tsx # 编辑版本弹窗
数据流
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ User │───▶│ Presentation│───▶│ Application │───▶│Infrastructure│
│ Action │ │ Component │ │ Store │ │ Repository │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ ▼
│ │ ┌─────────────┐
│ │ │ Admin API │
│ │ └─────────────┘
│ │ │
│ ◀───────────────────┘
│ │
◀───────────────────┘
│
┌─────────────┐
│ UI Update │
└─────────────┘
依赖关系
Presentation ──▶ Application ──▶ Domain ◀── Infrastructure
│ │ │ │
│ │ │ │
▼ ▼ ▼ ▼
React Zustand 纯TS接口 Axios
Next.js Hooks API实现
关键原则:
- Domain 层不依赖任何外部层
- Infrastructure 依赖 Domain(实现接口)
- Application 依赖 Domain(使用接口)
- Presentation 依赖 Application(使用 Hooks/Store)
设计优势
- 可测试性:每层都可以独立测试,Infrastructure 可以被 Mock
- 可维护性:关注点分离,修改一层不影响其他层
- 可扩展性:新增功能只需在相应层添加代码
- 可替换性:可以轻松替换技术实现(如 Axios → Fetch)