Initial commit

This commit is contained in:
linmeng
2026-06-16 09:31:43 +08:00
commit a47d43de5e
135 changed files with 9240 additions and 0 deletions
+777
View File
@@ -0,0 +1,777 @@
# Gitea 工单管理系统架构设计
## 1. 架构摘要
本项目采用 Flutter 单客户端架构,客户端直连 Gitea REST API,不新增自研后端服务。业务代码按 Clean Architecture / Hexagonal Architecture 的思想分层:UI 依赖应用用例,应用用例依赖领域模型和仓储接口,数据层负责 Gitea API、认证头、分页、缓存和错误转换。
目标平台:
- Android:主验证平台。
- iOS:主验证平台,依赖 macOS、Xcode、证书和签名配置。
- 鸿蒙 / OpenHarmony:高风险适配平台,通过 OpenHarmony-SIG Flutter 兼容扩展做 Spike 和后续适配。
关键结论:
- 不做微服务,不做移动 BFF,MVP 直连 Gitea。
- 不在本地保存完整业务数据库,仅做 token、用户偏好、最近仓库、轻量响应缓存。
- 用户使用账号密码登录;客户端优先换取 Personal Access Token,无法换取时退回 Basic Auth 会话。
- 当前 Gitea 服务是 HTTP,生产环境必须切换 HTTPS;内测阶段需要明确移动端明文访问配置。
- 所有第三方 Flutter 插件必须记录 Android、iOS、鸿蒙兼容结论。
## 2. 设计目标
功能目标:
- 支持登录、仓库选择、工单列表、工单详情、创建工单、评论、关闭和重开。
- 支持标签、里程碑、状态、关键词等筛选。
- 支持后续通知、最近访问、收藏仓库等增强功能。
质量目标:
- UI 与业务逻辑解耦,便于测试。
- Gitea API 与领域模型解耦,便于处理 API 版本差异。
- 网络错误、权限错误、token 失效有统一处理。
- 尽量使用纯 Dart 依赖,降低鸿蒙适配成本。
- 关键业务逻辑可单测,核心用户路径可集成测试。
非目标:
- 不替代完整 Gitea Web。
- 不实现服务端推送、Webhook 中间服务或移动推送平台。
- 不在 MVP 中做复杂离线编辑和冲突合并。
- 不为 Gitea 建立独立同步数据库。
## 3. 系统上下文
```mermaid
flowchart LR
User["移动端用户"]
App["Flutter Gitea 工单 App"]
SecureStore["本地安全存储"]
LocalCache["本地轻量缓存"]
Gitea["Gitea 1.26.2 REST API\nhttp://111.228.39.103:3000/api/v1"]
User --> App
App --> SecureStore
App --> LocalCache
App -- "HTTPS/HTTP REST\nAuthorization: token <token>" --> Gitea
```
说明:
- 客户端是唯一新增系统。
- Gitea 是既有外部系统,作为认证、权限、仓库、Issue、评论、标签、里程碑和通知的数据源。
- 本地存储只服务于会话、偏好和体验优化,不作为事实数据源。
## 4. 架构模式选择
推荐模式:Flutter 客户端内的 Clean Architecture + Ports and Adapters。
选择理由:
- 移动端业务长期会演进,Issue、Repository、Notification 等领域需要清晰边界。
- Gitea API 是外部系统,未来可能出现版本差异、鉴权变化或代理层,适合用 Repository 接口隔离。
- Flutter UI 变化快,业务用例需要保持可测试。
- 项目团队规模预期较小,不需要服务端微服务复杂度。
不选择微服务 / BFF 的理由:
- 当前核心能力都由 Gitea API 提供。
- 引入后端会增加部署、安全、日志、运维和权限转发成本。
- MVP 没有跨系统聚合、服务端推送或复杂数据加工需求。
未来可引入 BFF 的触发条件:
- 需要移动推送,且必须通过 Gitea Webhook 汇聚事件。
- 需要统一企业登录、Token 换发、审计或风控。
- 需要跨多个 Gitea 实例聚合搜索。
- Gitea HTTP 无法短期改 HTTPS,但又需要安全外网访问。
- 需要服务端缓存或报表计算,移动端直连无法满足性能目标。
## 5. 客户端分层
```mermaid
flowchart TB
Presentation["Presentation\nPages / Widgets / ViewModels"]
Application["Application\nUse Cases / State Notifiers"]
Domain["Domain\nEntities / Value Objects / Repository Ports"]
Data["Data\nRepository Impl / DTO Mapper / API Client"]
Platform["Platform\nSecure Storage / Local Cache / Device Config"]
External["External\nGitea REST API"]
Presentation --> Application
Application --> Domain
Application --> Data
Data --> Domain
Data --> Platform
Data --> External
```
依赖规则:
- UI 不直接调用 HTTP Client。
- Application 不直接解析 Gitea JSON。
- Domain 不依赖 Flutter、HTTP、存储插件。
- Data 可以依赖外部 SDK、HTTP、平台存储,并负责转换成领域模型。
- Platform 能力通过接口包装,方便替换和 mock。
## 6. 模块划分
建议按 Feature-first + Shared Core 组织:
```text
lib/
main.dart
app/
app.dart
router.dart
theme/
localization/
core/
config/
app_config.dart
gitea_endpoints.dart
error/
app_exception.dart
failure.dart
error_mapper.dart
network/
api_client.dart
auth_interceptor.dart
pagination.dart
storage/
secure_token_store.dart
preferences_store.dart
cache_store.dart
platform/
platform_capabilities.dart
widgets/
features/
auth/
domain/
application/
data/
presentation/
repositories/
domain/
application/
data/
presentation/
issues/
domain/
application/
data/
presentation/
comments/
domain/
application/
data/
presentation/
metadata/
domain/
application/
data/
presentation/
notifications/
domain/
application/
data/
presentation/
```
模块职责:
| 模块 | 职责 |
| --- | --- |
| `auth` | Gitea 地址、token 登录、会话恢复、退出登录 |
| `repositories` | 仓库列表、搜索、最近访问、收藏 |
| `issues` | 工单列表、筛选、详情、创建、编辑、关闭、重开 |
| `comments` | 评论列表、发表评论 |
| `metadata` | 标签、里程碑、指派人候选 |
| `notifications` | Gitea 通知列表、标记已读、跳转到工单 |
| `core/network` | HTTP 请求、鉴权头、超时、重试、分页 |
| `core/storage` | token、偏好、轻缓存 |
| `core/error` | 统一异常和用户可见错误 |
边界规则:
- Feature 之间不能直接访问对方的 `data` 层。
- 跨 Feature 共享的领域对象放在 `core` 或明确的 shared model 中,避免重复 JSON DTO。
- 对 Gitea API 的所有调用必须经过 `core/network`
## 7. 领域模型
核心领域对象:
```mermaid
classDiagram
class GiteaAccount {
+String username
+String displayName
+String avatarUrl
+bool isAdmin
}
class Repository {
+int id
+String owner
+String name
+String fullName
+bool private
+bool archived
+RepositoryPermissions permissions
}
class Issue {
+int id
+int number
+String title
+String body
+IssueState state
+User author
+List~User~ assignees
+List~IssueLabel~ labels
+Milestone? milestone
+DateTime createdAt
+DateTime updatedAt
+DateTime? closedAt
}
class IssueComment {
+int id
+String body
+User author
+DateTime createdAt
+DateTime updatedAt
}
class IssueFilter {
+IssueState state
+String? keyword
+List~String~ labels
+String? milestone
+String? assignee
+SortOrder sort
}
Repository "1" --> "*" Issue
Issue "1" --> "*" IssueComment
Issue "*" --> "*" IssueLabel
Issue "*" --> "0..1" Milestone
```
模型设计原则:
- Domain Model 使用客户端稳定命名,如 `number` 表示 Gitea issue `index`
- DTO 保留 Gitea 原始字段,如 `index``html_url``pull_request` 等。
- Mapper 负责 DTO 到 Domain 的转换,避免 Gitea API 细节污染 UI。
- Issue 与 Pull Request 需要区分。Gitea API 的 issue 搜索可能返回 PR,应在列表中默认过滤或明确标识。
## 8. Repository 接口
Domain 层定义端口:
```dart
abstract interface class AuthRepository {
Future<GiteaAccount> validateToken(GiteaServer server, String token);
Future<Session?> restoreSession();
Future<void> saveSession(Session session);
Future<void> clearSession();
}
abstract interface class GiteaRepositoryRepository {
Future<PagedResult<Repository>> listMyRepositories(PageRequest page);
Future<PagedResult<Repository>> searchRepositories(String keyword, PageRequest page);
Future<void> rememberRepository(Repository repository);
Future<List<Repository>> listRecentRepositories();
}
abstract interface class IssueRepository {
Future<PagedResult<Issue>> listIssues(RepositoryRef repo, IssueFilter filter, PageRequest page);
Future<PagedResult<Issue>> searchIssues(IssueSearchQuery query, PageRequest page);
Future<IssueDetail> getIssue(RepositoryRef repo, int number);
Future<Issue> createIssue(RepositoryRef repo, CreateIssueInput input);
Future<Issue> updateIssue(RepositoryRef repo, int number, UpdateIssueInput input);
Future<Issue> changeIssueState(RepositoryRef repo, int number, IssueState state);
}
abstract interface class CommentRepository {
Future<List<IssueComment>> listComments(RepositoryRef repo, int issueNumber);
Future<IssueComment> addComment(RepositoryRef repo, int issueNumber, String body);
}
```
实现位于 Data 层:
- `GiteaAuthRepository`
- `GiteaRepositoryRepository`
- `GiteaIssueRepository`
- `GiteaCommentRepository`
- `GiteaNotificationRepository`
## 9. API 集成设计
Base URL
- 默认:`http://111.228.39.103:3000/api/v1`
- 配置:支持在登录页或构建参数中覆盖。
认证:
- 登录入口:Gitea 地址、账号、密码。
- 首次校验:使用 Basic Auth 调用 `GET /user`
- Token 换取:登录成功后调用 `POST /users/{username}/tokens`,成功则后续请求使用 `Authorization: token <token>`
- 退回策略:如果 token 创建被拒绝但 Basic Auth 校验成功,后续请求使用 `Authorization: Basic <base64(username:password)>`
- 认证信息本地安全存储。
- 401:清理会话并引导重新登录。
- 403:显示无权限,不自动退出。
分页:
- 使用 Gitea 的 `page``limit` 查询参数。
- 默认 `limit = 20`,列表页可调到 `30`
- `PagedResult` 包含 `items``page``limit``hasMore`
- 若响应头提供总数或 Link,优先使用;否则用返回数量是否等于 limit 推断。
错误映射:
| HTTP 状态 | 客户端错误 |
| --- | --- |
| 400 | 请求参数错误 |
| 401 | 登录已失效 |
| 403 | 没有操作权限 |
| 404 | 资源不存在或无访问权限 |
| 409 | 状态冲突 |
| 422 | 表单校验失败 |
| 429 | 请求过于频繁 |
| 5xx | Gitea 服务异常 |
| timeout | 网络超时 |
| socket | 网络不可达 |
重试策略:
- GET 请求可在网络抖动或 5xx 下重试 1 次。
- POST/PATCH 写操作默认不自动重试,避免重复创建或重复评论。
- 用户手动重试时保留表单内容。
## 10. 关键用户流程
### 登录和会话恢复
```mermaid
sequenceDiagram
participant U as User
participant UI as LoginPage
participant Auth as AuthUseCase
participant API as GiteaApiClient
participant Store as SecureTokenStore
participant Gitea as Gitea API
U->>UI: 输入 Gitea 地址、账号和密码
UI->>Auth: login(server, username, password)
Auth->>API: GET /user
API->>Gitea: Authorization: Basic <base64>
Gitea-->>API: 200 User
API-->>Auth: GiteaAccount
Auth->>API: POST /users/{username}/tokens
API->>Gitea: Authorization: Basic <base64>
Gitea-->>API: AccessToken 或权限错误
Auth->>Store: 优先保存 token,否则保存 Basic Auth 凭据
Auth-->>UI: 登录成功
UI->>UI: 跳转主页
```
### 工单列表
```mermaid
sequenceDiagram
participant UI as IssueListPage
participant VM as IssueListController
participant Repo as IssueRepository
participant API as GiteaApiClient
participant Gitea as Gitea API
UI->>VM: 打开仓库工单页
VM->>Repo: listIssues(repo, filter, page)
Repo->>API: GET /repos/{owner}/{repo}/issues
API->>Gitea: page, limit, state, labels, milestone
Gitea-->>API: Issue DTO[]
API-->>Repo: DTO[]
Repo-->>VM: PagedResult<Issue>
VM-->>UI: 渲染列表
```
### 创建工单
```mermaid
sequenceDiagram
participant UI as CreateIssuePage
participant UseCase as CreateIssueUseCase
participant Repo as IssueRepository
participant API as GiteaApiClient
participant Gitea as Gitea API
UI->>UseCase: submit(input)
UseCase->>UseCase: 校验标题和正文
UseCase->>Repo: createIssue(repo, input)
Repo->>API: POST /repos/{owner}/{repo}/issues
API->>Gitea: title, body, labels, milestone, assignees
Gitea-->>API: 201 Issue DTO
API-->>Repo: Issue DTO
Repo-->>UseCase: Issue
UseCase-->>UI: 跳转详情
```
## 11. 状态管理
推荐 Riverpod。
理由:
- 对 Flutter 项目足够轻量。
- Provider 可组合,适合按 Feature 组织。
- 易于注入 mock repository 做单元测试。
- 不强依赖 BuildContext,业务状态更清晰。
状态边界:
| 状态 | 生命周期 | 建议实现 |
| --- | --- | --- |
| 会话状态 | App 全局 | `SessionController` |
| 当前仓库 | 页面 / App 局部 | `SelectedRepositoryController` |
| 工单列表 | 页面级,可缓存 | `IssueListController` |
| 工单详情 | 页面级 | `IssueDetailController` |
| 创建 / 评论表单 | 页面级短生命周期 | `StateNotifier``AsyncNotifier` |
| 主题和偏好 | App 全局 | `PreferencesController` |
状态模型:
- 使用 `AsyncValue` 表示 loading/data/error。
- 列表页保留上一次成功数据,错误以 banner/snackbar 呈现。
- 写操作单独维护 submitting 状态,避免影响详情主数据。
## 12. 本地存储策略
| 数据 | 存储位置 | 说明 |
| --- | --- | --- |
| Gitea token 或 Basic Auth 凭据 | 安全存储 | Android Keystore / iOS Keychain / 鸿蒙待验证 |
| Gitea server URL | 安全存储或偏好存储 | 与 token 配套保存 |
| 最近访问仓库 | 偏好存储 | 非敏感,可清理 |
| 收藏仓库 | 偏好存储 | 非敏感 |
| 主题、语言 | 偏好存储 | 非敏感 |
| 工单列表缓存 | 轻量缓存 | 可选,仅用于弱网体验 |
| 工单表单草稿 | 本地偏好或轻量缓存 | 不含 token,可按仓库和 issue key 存储 |
安全要求:
- Token、密码和 Basic Auth 凭据不进入日志、URL、崩溃报告、错误弹窗。
- 退出登录必须清除认证信息。
- 切换 Gitea 地址时必须重新校验认证信息。
- 当前 HTTP 环境只适合内测,生产必须使用 HTTPS。
鸿蒙风险:
- `flutter_secure_storage` 等插件在 OpenHarmony 适配链上可能不可用。
- 需要抽象 `TokenStore` 接口,为鸿蒙提供独立实现或降级策略。
- 不允许为了兼容鸿蒙而把 token 或密码明文落盘作为生产方案。
## 13. 跨平台架构
```mermaid
flowchart TB
Shared["lib/ 共享 Dart 业务代码\nUI / Domain / Application / Data"]
Android["android/\nManifest / Network Security Config"]
IOS["ios/\nInfo.plist / ATS / Signing"]
OHOS["ohos/\nOpenHarmony Flutter SDK / DevEco 配置"]
Plugins["平台插件适配层\nSecure Storage / Preferences / URL Launcher"]
Shared --> Android
Shared --> IOS
Shared --> OHOS
Shared --> Plugins
Plugins --> Android
Plugins --> IOS
Plugins --> OHOS
```
平台策略:
- 业务代码只写一次,尽量不使用 `Platform.isXxx` 分支散落在 Feature 中。
- 平台差异集中到 `core/platform``core/storage`
- Android/iOS 使用官方 Flutter stable 构建链。
- 鸿蒙使用 OpenHarmony-SIG Flutter SDK,优先在 Sprint 0 验证最小可运行路径。
HTTP 明文访问:
- Android:需要 Network Security Config 放行目标 host。
- iOS:需要 ATS 例外配置;生产环境应移除例外。
- 鸿蒙:需要在 OpenHarmony 工程中验证网络权限和明文策略。
## 14. 依赖选择
建议依赖:
| 能力 | 首选 | 备选 | 备注 |
| --- | --- | --- | --- |
| 状态管理 | `flutter_riverpod` | `bloc` | 以测试和注入便利为优先 |
| HTTP | `dio` | `http` | `dio` 拦截器和错误处理更成熟;鸿蒙需验证 |
| JSON | `json_serializable` | 手写 mapper | 类型安全和可维护性优先 |
| 路由 | `go_router` | Flutter Navigator | 深链和页面守卫更清晰 |
| 安全存储 | `flutter_secure_storage` | 平台 TokenStore 实现 | 鸿蒙必须验证 |
| 偏好存储 | `shared_preferences` | 本地文件封装 | 鸿蒙必须验证 |
| Markdown | `flutter_markdown` | 自定义简化渲染 | 鸿蒙必须验证 |
| 日志 | `logging` | 自定义 logger | 必须脱敏 |
依赖准入规则:
- 引入前检查 Android、iOS、鸿蒙兼容性。
- 若插件含原生代码,必须记录替代方案。
- 业务核心不能直接依赖插件 API,应通过接口封装。
## 15. 网络与安全架构
请求链路:
```mermaid
flowchart LR
UseCase["Use Case"] --> Repo["Repository Impl"]
Repo --> Client["ApiClient"]
Client --> Auth["AuthInterceptor"]
Auth --> Error["ErrorMapper"]
Error --> Gitea["Gitea API"]
```
安全控制:
- `AuthInterceptor``CredentialStore` 获取 token 或 Basic Auth 凭据并注入请求头。
- `LogInterceptor` 必须脱敏 `Authorization`、token、密码、cookie。
- 所有请求设置超时。
- 写操作使用明确的提交状态,防止重复点击。
- 统一处理 401 并触发会话失效事件。
生产安全门槛:
- Gitea 必须启用 HTTPS。
- 如果 token 创建成功,应设置最小权限,只授予仓库和工单所需范围。
- 应提供 token / 凭据清理和退出登录路径。
- 构建包中不硬编码真实 token。
## 16. 缓存与一致性
缓存策略:
- Gitea API 是事实数据源。
- 列表页可以缓存最近一次成功响应,缓存时间建议 1-5 分钟。
- 详情页进入时优先显示列表传入的摘要,再拉取详情和评论。
- 写操作成功后主动失效对应列表和详情缓存。
一致性规则:
- 创建工单成功后跳转详情,并刷新对应仓库的 open 列表。
- 评论成功后追加本地评论,再用后台刷新校准。
- 关闭 / 重开成功后更新详情状态,并让列表下一次出现时刷新。
- 写操作失败不得修改本地事实状态。
## 17. 可观测性
客户端日志:
- 开发环境记录请求方法、路径、状态码、耗时。
- 生产环境默认不记录请求体和敏感头。
- 记录错误分类:network、auth、permission、validation、server。
建议指标:
- 登录成功率。
- 工单列表加载耗时 p50/p95。
- 工单创建成功率。
- 评论发送成功率。
- 401/403/5xx 出现次数。
- Android/iOS/鸿蒙构建结果。
说明:
- MVP 可先使用本地日志和调试面板。
- 后续如果接入崩溃采集或埋点,需要先确认隐私和内网合规要求。
## 18. 测试架构
测试金字塔:
```mermaid
flowchart TB
E2E["少量端到端测试\n登录、列表、创建、评论、关闭"]
Widget["Widget 测试\n页面状态、表单校验、错误展示"]
Unit["大量单元测试\nUse Case、Mapper、Filter、ErrorMapper"]
E2E --> Widget
Widget --> Unit
```
测试重点:
- DTO 到 Domain Mapper。
- API 错误到用户错误映射。
- Issue 筛选参数构造。
- 登录 token 校验和会话恢复。
- 创建、评论、关闭、重开用例。
- 写操作失败后表单内容保留。
Mock 策略:
- Domain/Application 测试 mock Repository。
- Data 层测试 mock HTTP Client。
- Widget 测试注入 fake Controller 或 fake Repository。
- 集成测试使用测试 Gitea 实例或可控 mock server。
## 19. 构建与交付
建议环境矩阵:
| 阶段 | Android | iOS | 鸿蒙 |
| --- | --- | --- | --- |
| Sprint 0 | Debug 可运行 | 工程可生成 | 最小 demo Spike |
| Sprint 1 | Debug 主路径 | Simulator/真机准备 | 插件兼容清单 |
| Sprint 2 | APK 内测包 | iOS Debug 包 | 共享代码编译验证 |
| Sprint 3 | Release 包 | 可签名包 | 输出可运行包或阻塞报告 |
构建配置:
- 使用 `--dart-define=GITEA_BASE_URL=...` 支持环境切换。
- Debug 可默认指向 `http://111.228.39.103:3000`
- Release 不应强依赖 HTTP 默认值,需确认 HTTPS 地址。
- Android/iOS/HarmonyOS 平台配置文件分开维护。
## 20. 风险与缓解
| 风险 | 等级 | 架构缓解 |
| --- | --- | --- |
| HTTP 明文传输密码或 token | Critical | 生产强制 HTTPS;内测配置清晰标记;日志脱敏 |
| 鸿蒙 Flutter 生态不确定 | High | 平台能力抽象;减少原生插件;Sprint 0 Spike |
| Gitea API 字段和权限差异 | Medium | DTO/Domain 分离;统一错误映射;写操作处理 401/403/422 |
| 插件不兼容鸿蒙 | Medium | 依赖准入清单;对安全存储和偏好存储做接口封装 |
| 工单列表数据量大 | Medium | 分页、筛选、轻缓存、骨架屏 |
| 重复提交创建或评论 | Medium | 写操作禁用重复点击;POST 不自动重试 |
| PR 与 Issue 混合返回 | Medium | Mapper 标记 pull request;列表默认过滤或明确展示 |
## 21. ADR
### ADR-001:采用客户端直连 Gitea API
状态:Accepted
背景:
- MVP 的核心数据和操作都由 Gitea REST API 提供。
- 当前没有跨系统聚合、推送、审计中台或复杂数据加工要求。
决策:
- Flutter 客户端直接调用 Gitea `/api/v1`
- 不新增后端服务。
后果:
- 交付更快,部署更少。
- 移动端必须直接处理 token 安全、网络错误和权限错误。
- 如果未来需要推送、企业统一认证或多实例聚合,可以新增 BFF。
### ADR-002:采用 Clean Architecture / Ports and Adapters 分层
状态:Accepted
背景:
- Gitea API 是外部系统,字段、错误、权限都不应泄漏到 UI。
- 移动端页面和状态管理会持续变化。
决策:
- 按 Presentation、Application、Domain、Data、Platform 分层。
- Domain 定义 Repository 接口,Data 层实现 Gitea API 适配。
后果:
- 初始文件数量比简单 CRUD 多。
- 测试和后续平台适配更稳。
### ADR-003:账号密码登录,优先换取 Personal Access Token
状态:Accepted
背景:
- Gitea API 支持 `Authorization: token <token>`
- 用户明确要求使用账号密码登录。
- Gitea API 同时支持 Basic Auth 和 `POST /users/{username}/tokens` 创建访问令牌。
决策:
- MVP 使用账号密码作为登录入口。
- 登录时先用 Basic Auth 调用 `GET /user` 校验身份。
- 校验成功后尝试创建访问令牌;成功则保存 token,失败但 Basic Auth 可用则保存 Basic Auth 凭据。
- 认证信息安全存储,本地会话恢复。
后果:
- 用户无需提前生成 token。
- 当前 HTTP 环境下密码和 token 都可能明文传输,生产必须启用 HTTPS。
- 如果 Gitea 开启 2FA 或禁用 token API,可能需要补充 OTP 输入或仅使用 Basic Auth。
### ADR-004:本地只做轻缓存,不建立同步数据库
状态:Accepted
背景:
- MVP 不要求离线编辑。
- Gitea 是事实数据源。
决策:
- 本地保存 token、偏好、最近仓库、可过期列表缓存和草稿。
- 不保存完整 Issue 数据库。
后果:
- 实现简单,数据一致性风险低。
- 弱网体验有限,后续如需离线能力需引入同步机制。
### ADR-005:鸿蒙作为独立平台 Spike 管理
状态:Accepted
背景:
- Flutter 官方稳定平台不包含 HarmonyOS/OpenHarmony。
- 鸿蒙依赖 OpenHarmony-SIG Flutter 兼容扩展和 DevEco Studio。
决策:
- Sprint 0 做最小 demo 和插件兼容性验证。
- 平台相关能力统一接口封装,不散落到业务代码。
后果:
- Android/iOS 主路径不被鸿蒙不确定性拖住。
- 鸿蒙交付需要单独记录环境、插件和构建阻塞。
## 22. 推荐落地顺序
1. 初始化 Flutter 工程,建立 `core``features` 目录。
2. 实现 `ApiClient``CredentialStore``ErrorMapper`
3. 实现 `AuthRepository` 和登录页。
4. 实现仓库列表和最近仓库。
5. 实现工单列表、筛选和分页。
6. 实现工单详情、评论列表和 Markdown 展示。
7. 实现创建、评论、关闭、重开。
8. 补充通知和体验增强。
9. 完成 Android/iOS 构建配置。
10. 执行鸿蒙 OpenHarmony Spike 并输出兼容性报告。
## 23. 参考
- 产品需求:[docs/product-requirements.md](product-requirements.md)
- Flutter 官方支持平台:https://docs.flutter.dev/reference/supported-platforms
- OpenHarmony-SIG Flutter 兼容扩展:https://gitee.com/openharmony-sig/flutter_flutter/blob/master/README.en.md
- Gitea API Swagger`http://111.228.39.103:3000/swagger.v1.json`
+90
View File
@@ -0,0 +1,90 @@
# iOS 打包说明
## 当前限制
iOS / IPA 打包必须在 macOS 上完成,并且需要:
- Xcode
- CocoaPods
- Flutter macOS 环境
- Apple Developer 账号和证书,用于真机安装、TestFlight 或 App Store
当前电脑是 Windows,无法直接生成可安装的 iOS 包。
## 项目配置
当前 iOS 工程已经生成:
- Xcode workspace`ios/Runner.xcworkspace`
- Bundle ID`com.example.gitea.giteaIssueManager`
- 显示名:`Gitea Issue Manager`
- 当前 HTTP 内测地址 `111.228.39.103` 已加入 ATS 例外。
正式发布前建议:
- 将 Gitea 切换为 HTTPS。
- 移除 `ios/Runner/Info.plist` 中的 HTTP ATS 例外。
- 将 Bundle ID 改成你的公司域名,例如 `com.yourcompany.giteaIssues`
## 在 Mac 上生成未签名 IPA
未签名 IPA 不能直接安装到普通 iPhone,只适合交给后续签名流程。
```bash
chmod +x scripts/build_ios_package.sh
./scripts/build_ios_package.sh
```
输出:
```text
build/ios/ipa/Runner-unsigned.ipa
```
## 在 Mac 上生成可签名 IPA
先在 Xcode 中打开:
```bash
open ios/Runner.xcworkspace
```
然后配置:
- Signing & Capabilities
- Team
- Bundle Identifier
- Provisioning Profile
也可以通过环境变量执行脚本:
```bash
export APPLE_TEAM_ID=你的TeamID
export EXPORT_METHOD=development
export GITEA_BASE_URL=https://你的gitea域名
./scripts/build_ios_package.sh
```
常见 `EXPORT_METHOD`
- `development`:开发真机测试
- `ad-hoc`:指定设备分发
- `app-store`TestFlight / App Store
签名 IPA 默认输出到:
```text
build/ios/ipa/
```
## 常见问题
如果 CocoaPods 依赖未安装:
```bash
cd ios
pod install
cd ..
```
如果真机无法访问当前 HTTP Gitea 地址,请确认 `Info.plist` 中仍包含 `111.228.39.103` 的 ATS 例外。正式环境请改用 HTTPS。
+513
View File
@@ -0,0 +1,513 @@
# Gitea 工单管理系统需求分析
## 1. 背景与目标
本项目计划使用 Flutter 开发一个面向移动端的 Gitea 工单管理系统,默认连接到:
- Gitea 地址:`http://111.228.39.103:3000/`
- API Base URL`http://111.228.39.103:3000/api/v1`
- 已验证 Gitea 版本:`1.26.2`
产品目标是让研发、测试、项目负责人能够在 Android、iOS 和鸿蒙设备上快速查看、创建、跟进和关闭 Gitea Issue,减少移动场景下只能打开网页处理工单的不便。
## 2. 产品定位
这是一个“移动优先的 Gitea Issue 工作台”,不是完整的 Gitea 客户端。
核心价值:
- 快速进入与自己相关的仓库和工单。
- 支持常见工单动作:查看、筛选、创建、评论、关闭、重新打开。
- 在手机上保持清晰、低干扰、高效率的处理体验。
- 业务代码尽量保持纯 Flutter/Dart,降低 Android、iOS、鸿蒙多端适配成本。
## 3. 目标用户
| 用户角色 | 主要诉求 |
| --- | --- |
| 研发成员 | 查看分配给自己的问题、评论进展、关闭已完成工单 |
| 测试人员 | 创建缺陷、补充复现信息、跟踪状态变化 |
| 项目负责人 | 按仓库、标签、里程碑查看工作进度 |
| 新成员 | 快速找到参与项目的仓库和待处理事项 |
| 仓库管理员 | 需要在移动端处理标签、里程碑、权限相关问题,但这部分不是 MVP 核心 |
## 4. 已确认接口能力
从目标 Gitea 实例的 Swagger/OpenAPI 中确认,当前版本提供以下工单相关接口:
| 能力 | Gitea API |
| --- | --- |
| 版本探测 | `GET /version` |
| 仓库搜索 | `GET /repos/search` |
| 当前用户仓库 | `GET /user/repos` |
| 跨仓库工单搜索 | `GET /repos/issues/search` |
| 仓库工单列表 | `GET /repos/{owner}/{repo}/issues` |
| 创建工单 | `POST /repos/{owner}/{repo}/issues` |
| 工单详情 | `GET /repos/{owner}/{repo}/issues/{index}` |
| 编辑工单 | `PATCH /repos/{owner}/{repo}/issues/{index}` |
| 工单评论 | `GET/POST /repos/{owner}/{repo}/issues/{index}/comments` |
| 标签 | `GET /repos/{owner}/{repo}/labels` |
| 里程碑 | `GET /repos/{owner}/{repo}/milestones` |
| 通知 | `GET/PUT /notifications` |
认证建议:
- 用户入口使用 Gitea 账号密码登录。
- 客户端登录时先用 Basic Auth 校验账号密码,再尝试调用 `POST /users/{username}/tokens` 创建访问令牌。
- 如果 Gitea 禁止 API 创建 token 或受 2FA 策略限制,客户端可退回 Basic Auth 会话。
- API 请求优先使用 `Authorization: token <token>` 请求头;退回 Basic Auth 时使用 `Authorization: Basic <base64(username:password)>`
- 不建议把 token 或密码放到 URL 查询参数中。
- 当前服务是 HTTP 地址,正式使用前强烈建议启用 HTTPS,否则 token 会明文传输。
## 5. 平台范围
| 平台 | MVP 支持策略 |
| --- | --- |
| Android | Flutter 官方稳定支持,MVP 必须构建并验证 |
| iOS | Flutter 官方稳定支持,MVP 必须构建配置;真机签名与上架证书需单独准备 |
| 鸿蒙 / OpenHarmony | 通过 OpenHarmony-SIG Flutter 适配 SDK 验证,作为高风险适配项提前做 Spike |
重要约束:
- Flutter 官方支持的平台列表包含 Android、iOS、Web、Windows、macOS、Linux 等,未把 HarmonyOS/OpenHarmony 作为官方稳定目标平台。
- 鸿蒙支持依赖 OpenHarmony-SIG 的 Flutter 兼容扩展及 DevEco Studio 环境,插件兼容性需要逐项验证。
- 为降低鸿蒙适配风险,MVP 阶段应尽量减少原生插件依赖,优先使用纯 Dart 能力。
## 6. MVP 范围
MVP 必做:
1. 登录与会话
- 输入或预置 Gitea 地址。
- 输入 Gitea 账号和密码。
- 校验账号密码是否可用。
- 优先换取并安全保存访问令牌;无法换取时安全保存 Basic Auth 凭据。
- 支持退出登录。
2. 仓库选择
- 显示当前用户可访问仓库。
- 支持搜索仓库。
- 支持最近访问或收藏仓库。
3. 工单列表
- 支持按仓库查看工单。
- 支持 open / closed 状态筛选。
- 支持关键词搜索。
- 支持标签、里程碑、指派人基础筛选。
- 支持下拉刷新和分页加载。
4. 工单详情
- 显示标题、编号、状态、作者、指派人、标签、里程碑、创建时间、更新时间。
- 显示正文 Markdown。
- 显示评论列表。
- 支持刷新详情。
5. 工单操作
- 创建工单。
- 创建工单时选择已有标签。
- 在工单详情中添加或移除已有标签。
- 编辑标题和正文。
- 添加评论。
- 关闭工单。
- 重新打开工单。
6. 基础质量
- 统一错误提示。
- 网络超时和重试。
- Token 失效时引导重新登录。
- Android 和 iOS 可构建。
- 鸿蒙完成环境 Spike 和兼容性报告。
MVP 暂不做:
- Pull Request 审查。
- 代码浏览、提交记录、分支管理。
- Release、Wiki、Packages。
- 管理员后台能力。
- Webhook 服务端推送。
- 离线编辑和冲突合并。
- 附件上传、图片预览、语音、扫码等依赖较多原生插件的能力。
- 完整项目看板或甘特图。
## 7. Epic 拆分
| Epic | 名称 | 目标 | 估算 |
| --- | --- | --- | --- |
| E1 | 认证与安全会话 | 用户能安全连接 Gitea 并保持登录状态 | 13 pts |
| E2 | 仓库上下文 | 用户能找到并进入可访问仓库 | 8 pts |
| E3 | 工单发现 | 用户能按条件找到要处理的工单 | 13 pts |
| E4 | 工单详情与协作 | 用户能查看完整上下文并参与讨论 | 13 pts |
| E5 | 工单变更 | 用户能创建、编辑、关闭和重开工单 | 13 pts |
| E6 | 通知与个人工作流 | 用户能关注未读事项和最近工作 | 8 pts |
| E7 | 跨端交付 | Android、iOS、鸿蒙构建路径清晰可验证 | 13 pts |
| E8 | 体验打磨 | 提升性能、错误恢复、可访问性和稳定性 | 8 pts |
## 8. 用户故事与验收标准
### US-001 登录 Gitea
优先级:High
估算:5 pts
作为研发成员,我想用 Gitea 账号密码登录移动端客户端,以便安全访问我有权限的仓库和工单。
验收标准:
- Given 用户输入默认 Gitea 地址和有效账号密码,When 点击登录,Then 客户端调用用户校验接口并进入主页。
- Given Gitea 支持创建访问令牌,When 登录成功,Then 客户端创建并保存 API token 用于后续请求。
- Given Gitea 不允许创建访问令牌但 Basic Auth 可用,When 登录成功,Then 客户端保存 Basic Auth 凭据并进入主页。
- Given 账号或密码无效,When 用户点击登录,Then 显示明确错误,并停留在登录页。
- Given 网络不可达,When 用户点击登录,Then 显示网络失败提示和重试入口。
- Given 登录成功,When 用户重新打开应用,Then 应保持登录状态。
- Given 用户选择退出登录,When 操作确认,Then 本地认证信息被清除并返回登录页。
INVEST:独立、可协商、有用户价值、可估算、小于 8 点、可测试。
### US-002 查看可访问仓库
优先级:High
估算:3 pts
作为研发成员,我想查看自己可访问的仓库列表,以便选择要处理工单的项目。
验收标准:
- Given 用户已登录,When 进入主页,Then 显示当前用户可访问仓库。
- Given 仓库较多,When 用户滚动到底部,Then 支持分页加载更多。
- Given 用户输入关键词,When 搜索仓库,Then 只显示匹配仓库。
- Given API 返回空列表,When 页面加载完成,Then 显示空状态而不是错误页。
INVEST:通过。
### US-003 按仓库查看工单列表
优先级:High
估算:5 pts
作为测试人员,我想查看某个仓库的工单列表,以便快速找到需要跟进的问题。
验收标准:
- Given 用户选择仓库,When 进入工单页,Then 默认显示 open 工单。
- Given 用户切换 open / closedWhen 状态变化,Then 列表按所选状态刷新。
- Given 工单较多,When 用户滚动到底部,Then 分页加载下一页。
- Given 用户下拉刷新,When API 请求成功,Then 列表显示最新数据。
- Given API 请求失败,When 加载失败,Then 保留可用旧数据并显示错误提示。
INVEST:通过。
### US-004 搜索和筛选工单
优先级:High
估算:5 pts
作为项目负责人,我想按关键词、标签、里程碑、指派人筛选工单,以便快速定位当前关注范围。
验收标准:
- Given 用户输入关键词,When 提交搜索,Then 工单列表按关键词刷新。
- Given 用户选择标签,When 应用筛选,Then 只显示包含该标签的工单。
- Given 用户选择里程碑,When 应用筛选,Then 只显示该里程碑下的工单。
- Given 用户清空筛选,When 返回列表,Then 恢复默认 open 工单列表。
- Given 筛选组合无结果,When 请求完成,Then 显示清晰空状态。
INVEST:通过。
### US-005 查看工单详情
优先级:High
估算:5 pts
作为研发成员,我想查看工单正文、状态和评论,以便理解问题背景并决定下一步动作。
验收标准:
- Given 用户点击工单,When 详情接口返回成功,Then 显示工单标题、编号、状态、标签、作者、指派人、里程碑和时间信息。
- Given 工单正文包含 MarkdownWhen 页面渲染,Then 应以可读格式展示。
- Given 工单有评论,When 页面加载,Then 按时间顺序显示评论。
- Given 用户下拉刷新,When 请求成功,Then 详情和评论更新。
- Given 用户无权限或工单不存在,When 打开详情,Then 显示无权限或不存在提示。
INVEST:通过。
### US-006 创建工单
优先级:High
估算:5 pts
作为测试人员,我想在移动端创建 Gitea 工单,以便现场记录缺陷或任务。
验收标准:
- Given 用户在仓库中点击创建,When 输入标题和正文并提交,Then 创建成功并进入新工单详情。
- Given 标题为空,When 用户提交,Then 阻止提交并提示标题必填。
- Given 用户选择标签或里程碑,When 创建成功,Then 新工单包含对应元数据。
- Given API 返回权限错误,When 提交失败,Then 提示用户无创建权限。
- Given 网络失败,When 提交失败,Then 保留表单内容以便重试。
INVEST:通过。
### US-007 评论工单
优先级:High
估算:3 pts
作为研发成员,我想给工单添加评论,以便同步处理进展和沟通问题。
验收标准:
- Given 用户在详情页输入评论,When 点击发送,Then 评论成功后出现在评论列表底部。
- Given 评论为空,When 用户点击发送,Then 阻止提交并提示内容必填。
- Given 评论发送失败,When API 返回错误,Then 保留输入内容并显示失败原因。
- Given 发送中,When 请求未完成,Then 发送按钮进入不可重复点击状态。
INVEST:通过。
### US-007A 编辑工单标签
优先级:High
估算:3 pts
作为研发成员,我想在工单详情里添加或移除已有标签,以便准确标记问题类型和处理阶段。
验收标准:
- Given 仓库存在标签,When 用户打开工单详情并点击编辑标签,Then 显示当前仓库可用标签列表。
- Given 用户勾选或取消标签,When 点击保存,Then 工单标签被更新。
- Given 用户无权限修改标签,When 保存失败,Then 显示权限不足提示。
- Given 标签更新成功,When 返回工单列表,Then 工单标签状态保持一致。
INVEST:通过。
### US-008 关闭和重新打开工单
优先级:High
估算:3 pts
作为研发成员,我想关闭已解决工单或重新打开未解决工单,以便维护准确的工作状态。
验收标准:
- Given 工单为 openWhen 用户点击关闭并确认,Then 工单状态变为 closed。
- Given 工单为 closedWhen 用户点击重新打开并确认,Then 工单状态变为 open。
- Given 用户无权限修改状态,When 操作失败,Then 显示权限不足提示。
- Given 状态修改成功,When 返回列表,Then 列表状态与详情一致。
INVEST:通过。
### US-009 查看通知
优先级:Medium
估算:3 pts
作为项目成员,我想查看 Gitea 未读通知,以便及时处理与我相关的工单变化。
验收标准:
- Given 用户已登录,When 进入通知页,Then 显示通知线程列表。
- Given 用户点击通知,When 目标是工单,Then 跳转到对应工单详情。
- Given 用户点击标记已读,When 请求成功,Then 通知从未读列表中移除或状态更新。
- Given 通知接口不可用,When 加载失败,Then 显示错误提示而不影响工单列表使用。
INVEST:通过。
### US-010 跨平台构建验证
优先级:High
估算:8 pts
作为项目负责人,我想确认 Android、iOS、鸿蒙三端都有明确构建路径,以便项目不会在交付阶段才暴露平台阻塞。
验收标准:
- Given Flutter 项目代码完成,When 执行 Android 构建,Then 能生成 APK 或 AAB。
- Given iOS 构建环境和证书具备,When 执行 iOS 构建,Then 能生成可签名包。
- Given OpenHarmony Flutter SDK 和 DevEco Studio 环境具备,When 执行鸿蒙构建 Spike,Then 输出可运行结果或明确阻塞清单。
- Given 引入第三方插件,When 做平台兼容检查,Then 每个插件都有 Android、iOS、鸿蒙兼容结论。
- Given 当前 Gitea 是 HTTP 地址,When 移动端请求 APIThen Android/iOS/鸿蒙网络安全配置风险被记录并处理。
INVEST:通过,但鸿蒙部分风险最高,应尽早执行。
## 9. Backlog 优先级
| 优先级 | 条目 | 原因 |
| --- | --- | --- |
| Critical | HTTPS 或 HTTP 明文访问策略 | 直接影响密码、token 安全和移动端网络配置 |
| High | 登录、仓库、工单列表、详情、创建、评论、状态变更 | 构成最小可用闭环 |
| High | Android/iOS 构建与鸿蒙 Spike | 用户明确要求三端支持 |
| Medium | 通知、最近访问、收藏仓库、筛选增强 | 提升日常效率 |
| Medium | 编辑标签、里程碑、指派人 | 常用但可在基本闭环后补齐 |
| Low | 附件上传、图片预览、扫码登录、推送通知 | 依赖更多原生插件和服务端能力 |
| Low | 看板、报表、时间追踪、依赖关系 | 更偏高级项目管理 |
## 10. 建议迭代计划
以下计划假设 2 周一个 Sprint,移动端 1-2 名开发,初始速度按 20-24 点估算。真实承诺应在确认团队人数、可用时间和设备环境后调整。
### Sprint 0:项目启动与风险 Spike
目标:建立工程基础,打通 Gitea API,尽早验证三端风险。
建议范围:
- Flutter 工程初始化。
- API 客户端基础封装。
- Gitea 账号密码校验与 token 换取 Spike。
- HTTP 明文请求在 Android/iOS 上的配置验证。
- 鸿蒙 OpenHarmony Flutter SDK 环境调研和最小 demo Spike。
- 插件候选清单:HTTP、状态管理、安全存储、Markdown 渲染。
完成标准:
- 有可运行 demo 能访问 `GET /version``GET /user`
- 明确账号密码、token 换取和本地认证信息存储方案。
- 明确鸿蒙构建环境和阻塞项。
### Sprint 1MVP 纵向闭环
目标:用户能登录、选择仓库、查看工单列表和详情。
建议范围:
- US-001 登录 Gitea。
- US-002 查看可访问仓库。
- US-003 按仓库查看工单列表。
- US-005 查看工单详情。
完成标准:
- Android 本地可运行。
- iOS 工程配置可生成。
- 工单详情能显示正文和评论。
### Sprint 2:工单处理能力
目标:用户能完成移动端核心工单操作。
建议范围:
- US-004 搜索和筛选工单。
- US-006 创建工单。
- US-007 评论工单。
- US-008 关闭和重新打开工单。
完成标准:
- 创建、评论、关闭、重开形成完整闭环。
- 失败场景保留用户输入。
- 核心接口有单元测试或集成测试覆盖。
### Sprint 3:通知、跨端与稳定性
目标:提升可用性并进入多端验证。
建议范围:
- US-009 查看通知。
- US-010 跨平台构建验证。
- 最近访问仓库。
- 错误提示统一。
- 页面性能和加载状态优化。
- Android/iOS 打包脚本。
- 鸿蒙 Spike 结论转为构建说明或适配任务。
完成标准:
- Android 可打包。
- iOS 构建路径清晰。
- 鸿蒙有可执行构建说明或明确技术阻塞。
## 11. Definition of Ready
故事进入 Sprint 前应满足:
- 用户故事按 “作为...我想...以便...” 描述。
- 至少 3 条可测试验收标准。
- 接口、权限和异常路径已确认。
- 估算不超过 8 点;超过则拆分。
- 依赖项已标注,尤其是鸿蒙环境、证书、HTTPS、Gitea 权限。
## 12. Definition of Done
故事完成需满足:
- 功能代码完成并通过 Review。
- 验收标准逐条验证。
- 关键逻辑有测试覆盖。
- Android 至少完成本地运行验证。
- 涉及平台能力时记录 Android、iOS、鸿蒙兼容性。
- 不在日志、URL、错误信息中泄露 token。
- 用户可见错误有中文提示。
- 文档或 README 更新。
- 无 Critical 或 High 级未处理缺陷。
## 13. 非功能需求
安全:
- 认证信息必须安全存储。
- 禁止把 token 或密码放进 URL。
- 默认不打印请求头和敏感响应。
- 当前 HTTP 服务用于内测可以临时放行;生产必须切换 HTTPS。
性能:
- 首屏已有会话时 1 秒内进入主页骨架屏。
- 常规网络下,50 条工单列表加载 p95 小于 2 秒。
- 列表滚动保持流畅,分页加载不阻塞当前内容浏览。
可靠性:
- 网络失败时保留当前页面数据。
- 表单提交失败时保留用户输入。
- Token 或 Basic Auth 凭据失效时引导重新登录。
可用性:
- 中文界面。
- 支持浅色和深色模式。
- 工单状态、标签、里程碑要能快速识别。
- 手机单手操作优先,常用动作放在详情页底部或明确菜单中。
可测试性:
- API 客户端可注入 mock。
- 工单列表筛选逻辑可单测。
- 登录、创建工单、评论、关闭工单作为集成测试主路径。
## 14. 主要风险与应对
| 风险 | 等级 | 影响 | 应对 |
| --- | --- | --- | --- |
| Gitea 当前使用 HTTP | Critical | 密码和 token 明文传输,iOS/Android 也需额外放行明文请求 | 内测可临时配置,正式环境必须上 HTTPS |
| 鸿蒙不是 Flutter 官方稳定平台 | High | 构建链、插件、渲染和调试存在不确定性 | Sprint 0 做 Spike,减少插件依赖,保留独立适配任务 |
| Gitea 权限差异 | Medium | 不同用户创建、编辑、关闭权限不同 | 所有写操作处理 401/403/404/422 |
| Token 获取流程 | Medium | 用户不知道如何生成 token | 登录页提供简短指引,后续可补网页登录跳转 |
| 附件和推送需求扩张 | Medium | 引入原生插件和服务端组件,影响鸿蒙适配 | 放到 MVP 后,逐项评估 |
| 私有仓库与组织仓库较多 | Medium | 列表和搜索性能下降 | 分页、缓存、最近访问 |
## 15. 开放问题
1. 是否可以为 Gitea 配置 HTTPS 域名或反向代理?
2. Gitea 是否允许通过 API 创建访问令牌?如果不允许,是否接受客户端保存 Basic Auth 凭据?
3. 是否需要管理员能力,例如创建标签、创建里程碑、修改指派人?
4. 是否要求工单附件上传和图片预览?
5. 鸿蒙目标是 HarmonyOS NEXT / OpenHarmony 哪个版本?是否已有 DevEco Studio、签名证书和测试机?
6. 是否需要推送通知?如果需要,是否有可部署的中间服务用于对接 Gitea Webhook 和移动推送通道?
7. 是否需要多 Gitea 实例切换,还是只固定当前地址?
## 16. 推荐技术取向
- 架构:Flutter 分层架构,UI、状态、Repository、API Client 分离。
- 状态管理:优先 Riverpod 或 Bloc,选择团队更熟悉的一种。
- 网络:统一 API Client,集中处理 token、错误、分页和超时。
- 存储:token 使用安全存储;非敏感缓存可用本地轻量存储。
- Markdown:选择 Android、iOS、鸿蒙兼容性更好的 Flutter/Dart 方案。
- 适配策略:先完成 Android/iOS 主路径,再用同一业务代码执行鸿蒙构建 Spike。
- 插件策略:每引入一个插件,都在 backlog 标注三端兼容结论。
## 17. 参考资料
- Flutter 官方支持平台文档:https://docs.flutter.dev/reference/supported-platforms
- OpenHarmony-SIG Flutter 兼容扩展:https://gitee.com/openharmony-sig/flutter_flutter/blob/master/README.en.md
- Gitea API Swagger`http://111.228.39.103:3000/swagger.v1.json`