refactor: 重构文档结构与环境配置,统一研发工单命名
1. 更新 .env 配置文件,替换原有云同步变量为 API 目标配置 2. 调整 publish-linux.ps1 中的文档路径,使用研发工单目录 3. 重构项目文档目录:将原 v1.2.0-tasks 迁移为研发工单-v1.2.0 目录,统一术语为"研发工单"替代"任务" 4. 更新 README.md 与各 PRD 文档的术语对照表,明确业务实体与研发工作项的区分 5. 新增多个研发工单文档,覆盖搜索、云同步、Linux 打包等模块 6. 删除旧的任务拆分文档,统一使用新的研发工单体系
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
## 云同步设置 — 服务端化改造方案
|
||||
|
||||
### 1. 当前架构概述
|
||||
|
||||
#### 1.1 项目层次关系
|
||||
|
||||
```
|
||||
src/
|
||||
├── Hua.Todo.Application/ ← 共享层(服务端 & 客户端公用)
|
||||
│ ├── CloudSync/ ← 云同步业务逻辑
|
||||
│ │ ├── Auth/ ← 认证/授权
|
||||
│ │ ├── Models/ ← DTO(纯数据合约,两端可用)
|
||||
│ │ └── Services/ ← 业务服务
|
||||
│ ├── Data/ ← EF Core DbContext
|
||||
│ └── DynamicApi/ ← 动态 API 中间件
|
||||
│
|
||||
├── Hua.Todo.Host/ ← ASP.NET 服务端(云同步接口的提供方)
|
||||
│ └── Program.cs
|
||||
│ ├── AddApplicationServices() ← Todo CRUD 服务
|
||||
│ └── AddCloudSyncServer() ← 云同步认证/授权/端点 **仅服务端**
|
||||
│
|
||||
└── Hua.Todo.Maui/ ← 桌面/移动客户端(嵌入 WebView)
|
||||
└── MauiProgram.cs
|
||||
└── AddApplicationServices() ← **仅** Todo CRUD 服务
|
||||
(不注册 AddCloudSyncServer,不暴露云同步端点)
|
||||
```
|
||||
|
||||
#### 1.2 两种部署模式
|
||||
|
||||
**模式 A:Host 独立服务端(开发/部署)**
|
||||
|
||||
```
|
||||
┌───────────────────────────────┐
|
||||
│ Vue 前端 (Vite :5174) │
|
||||
│ cloudClient │
|
||||
│ baseURL = serverUrl(外部) │ 直连外部
|
||||
│ → /auth/login │ ────────→ 外部云同步服务
|
||||
│ probeReachability() │ 直连外部
|
||||
│ → fetch(serverUrl) │ ────────→
|
||||
├───────────────────────────────┤
|
||||
│ proxy /api │
|
||||
├───────────────────────────────┤
|
||||
│ ASP.NET Host (:5173) │
|
||||
│ AddApplicationServices() │ Todo CRUD
|
||||
│ AddCloudSyncServer() │ 云同步端点 ← **服务端**
|
||||
│ ├─ /api/* (本地 Todo) │
|
||||
│ └─ /auth/* (云同步) │
|
||||
│ /tasks/* │
|
||||
│ /cloud-sync/probe (新增) │
|
||||
└───────────────────────────────┘
|
||||
```
|
||||
|
||||
**模式 B:MAUI 嵌入式 WebView(桌面端/移动端)**
|
||||
|
||||
```
|
||||
┌───────────────────────────────┐
|
||||
│ MAUI WebView │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ Vue 前端 (静态托管) │ │
|
||||
│ │ cloudClient │ │
|
||||
│ │ baseURL = 同源 │ │ 同源请求
|
||||
│ │ → /api/* (本地 Todo) │──┼───────→ Embedded WebServer
|
||||
│ │ → /auth/* (❌ 不可用) │ │ (不暴露云同步端点)
|
||||
│ └─────────────────────────┘ │
|
||||
├───────────────────────────────┤
|
||||
│ Embedded WebServer (:5057) │
|
||||
│ AddApplicationServices() │ 仅 Todo CRUD
|
||||
│ AddCloudSyncServer() │ ❌ 未注册
|
||||
│ UseDynamicApi() │ 仅本地 Todo API
|
||||
│ MapCloudSyncEndpoints() │ ❌ 未映射
|
||||
└───────────────────────────────┘
|
||||
```
|
||||
|
||||
> **关键区别**:`Hua.Todo.Application` 被两方引用,但云同步能力**仅在 Host 端通过 `AddCloudSyncServer()` 激活**。MAUI 端只使用 `AddApplicationServices()` 获取 Todo CRUD 能力,**不暴露也不应暴露云同步端点**。
|
||||
|
||||
#### 1.3 核心问题
|
||||
|
||||
1. 前端的"保存并探测"使用**浏览器端直接 `fetch()`** 探测外部 URL,受 CORS、证书、网络隔离限制。
|
||||
2. 前端的"登录"通过 `cloudClient` 直连外部 `serverUrl`,session token 存在前端 `sessionStorage`,与 Host 脱节。
|
||||
3. 在 Host 模式下,Host 本身就是云同步服务,但前端并不知道,额外配置了外部 `serverUrl`。
|
||||
|
||||
### 2. 改造目标
|
||||
|
||||
| 操作 | 当前 | 目标 |
|
||||
|---|---|---|
|
||||
| **保存并探测** | 浏览器 `fetch()` 直连 | 前端 → Host 接口 → Application CloudProbeService → 返回结果 |
|
||||
| **登录** | 前端直连 `serverUrl` | 前端 → Host `/auth/login` |
|
||||
| **后续云同步请求** | `cloudClient` 直连 `serverUrl` | 统一走 Host(同源请求) |
|
||||
|
||||
### 3. 变更详情
|
||||
|
||||
#### 3.1 新增:服务端探测接口
|
||||
|
||||
> **注意**:`CloudProbeService` 和服务端点注册均在 `AddCloudSyncServer()` 路径下,MAUI 端不受影响。
|
||||
|
||||
**Application 层新增 `CloudProbeService`**
|
||||
|
||||
位置:`src/Hua.Todo.Application/CloudSync/Services/CloudProbeService.cs`
|
||||
|
||||
职责:接收目标 URL,从服务端发起 HTTP 探测,返回探测结果。
|
||||
|
||||
**新增 DTO**(位置:`src/Hua.Todo.Application/CloudSync/Models/AdminDtos.cs`,或新建 `CloudSync/Models/ProbeDtos.cs`)
|
||||
|
||||
DTO 是纯数据合约,放在 Application 共享层无副作用,MAUI 端即使不调用也不会产生依赖。
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 服务端探测请求。
|
||||
/// </summary>
|
||||
public class ProbeRequest
|
||||
{
|
||||
public string TargetUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务端探测响应。
|
||||
/// </summary>
|
||||
public class ProbeResponse
|
||||
{
|
||||
public bool IsReachable { get; set; }
|
||||
public int? HttpStatus { get; set; }
|
||||
public bool IsHttps { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// success | warn | error
|
||||
/// </summary>
|
||||
public string Type { get; set; } = "error";
|
||||
}
|
||||
```
|
||||
|
||||
**DI 注册(`CloudSyncServiceCollectionExtensions.AddCloudSyncServer()`)**
|
||||
|
||||
在 `AddCloudSyncServer()` 方法末尾追加,确保 `CloudProbeService` 仅在服务端 DI 容器中可用:
|
||||
|
||||
```csharp
|
||||
// CloudSyncServiceCollectionExtensions.AddCloudSyncServer() 末尾新增:
|
||||
services.AddHttpClient("ProbeClient", client =>
|
||||
{
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("HuaTodo-Probe/1.0");
|
||||
});
|
||||
services.AddScoped<CloudProbeService>();
|
||||
```
|
||||
|
||||
> **为什么放在 `AddCloudSyncServer()` 而非 `AddApplicationServices()`**:
|
||||
> - `AddApplicationServices()` 被 MAUI 端调用,MAUI 不需要也不会启动云同步探测
|
||||
> - `AddCloudSyncServer()` 仅在 Host 的 `Program.cs` 中被调用,确保服务仅注册在服务端
|
||||
|
||||
**端点注册(`CloudSyncEndpointExtensions.MapCloudSyncEndpoints()`)**
|
||||
|
||||
在 `MapCloudSyncEndpoints()` 方法内部新增端点组:
|
||||
|
||||
```csharp
|
||||
var probe = app.MapGroup("/cloud-sync").WithTags("CloudSync - Setup");
|
||||
probe.MapPost("/probe", ProbeAsync).AllowAnonymous();
|
||||
```
|
||||
|
||||
端点实现:
|
||||
|
||||
```csharp
|
||||
private static async Task<IResult> ProbeAsync(
|
||||
ProbeRequest request,
|
||||
CloudProbeService probeService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (request == null || string.IsNullOrWhiteSpace(request.TargetUrl))
|
||||
{
|
||||
return CloudApiErrors.BadRequest("TargetUrl is required.");
|
||||
}
|
||||
var result = await probeService.ProbeAsync(request.TargetUrl, cancellationToken);
|
||||
return Results.Json(result);
|
||||
}
|
||||
```
|
||||
|
||||
**前端修改** `src/Hua.Todo.Web/src/components/CloudSyncSettingsDialog.vue`
|
||||
|
||||
删除 `probeReachability()` 函数(原 L235-L280),`saveServerUrl()` 中改为:
|
||||
|
||||
```typescript
|
||||
// 旧:前端直接 fetch
|
||||
// probeResult.value = await probeReachability(serverUrlSaved.value);
|
||||
|
||||
// 新:调用 Host 的探测接口
|
||||
const response = await cloudClient.post<ProbeResponse>('/cloud-sync/probe', {
|
||||
targetUrl: serverUrlSaved.value,
|
||||
});
|
||||
probeResult.value = {
|
||||
type: response.data.type as ProbeType,
|
||||
title: response.data.title,
|
||||
desc: response.data.description,
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.2 修改:登录统一走 Host
|
||||
|
||||
**核心思路**:Host 自身就是云同步服务方,`/auth/login` 已实现完整认证。前端只需改为走 Host 同源请求。
|
||||
|
||||
**前端 `cloudClient.ts` 改造**(`src/Hua.Todo.Web/src/api/cloudClient.ts`)
|
||||
|
||||
移除对外部 `serverUrl` 的依赖:
|
||||
|
||||
```typescript
|
||||
// 改造前(L36-L39):
|
||||
// const { serverUrl } = CloudSyncStorage.loadSettings();
|
||||
// if (serverUrl) { config.baseURL = serverUrl; }
|
||||
|
||||
// 改造后:不设 baseURL,axios 使用浏览器默认同源
|
||||
// cloudClient 请求直接打到当前 Host:/auth/* , /tasks/* , /cloud-sync/*
|
||||
```
|
||||
|
||||
> **同源适配说明**:
|
||||
> - **Host 模式**(Vite dev):Vue 运行在 :5174,`/api` 走 proxy 到 Host(:5173),云同步端点 `/auth/*` 需配 Vite proxy 或直接用 Host 地址
|
||||
> - **MAUI 模式**(WebView):Vue 部署在 MAUI 嵌入服务器同源,不需要额外代理
|
||||
> - 具体方案:在 Vite dev 时增加 proxy 规则将 `/auth`、`/tasks`、`/cloud-sync` 也 proxy 到 Host;或在 cloudClient 中按环境设置 baseURL
|
||||
|
||||
**前端 `CloudSyncSettingsDialog.vue` 的 `login()` 无需修改**:
|
||||
`cloudSyncApi.login()` 调用 `cloudClient.post('/auth/login', ...)`,自动打到 Host。
|
||||
|
||||
**Host 端 `CloudAuthService.LoginAsync` 已有完整实现**:
|
||||
- 创建 `UserSessionEntity` 写入 DB(session 管理在 Host)
|
||||
- 返回 `LoginResponse`(`AccessToken` = DB SessionId)
|
||||
- Session 验证由 `SessionAuthenticationHandler` 查询 DB 完成
|
||||
|
||||
#### 3.3 Vite 代理配置补充
|
||||
|
||||
Vite dev 模式下,除了已有的 `/api` 代理,需**补充云同步端点的代理规则**(`vite.config.ts`):
|
||||
|
||||
```typescript
|
||||
server: {
|
||||
port: 5174,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
// 新增:云同步端点代理到 Host
|
||||
'/auth': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/tasks': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/sync': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/security': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/cloud-sync': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
> **MAUI 模式无需此配置**:MAUI 使用 `vite build --mode maui`,静态部署到嵌入服务器同源。
|
||||
|
||||
#### 3.4 `serverUrl` 字段的语义变化
|
||||
|
||||
| 维度 | 改造前 | 改造后 |
|
||||
|---|---|---|
|
||||
| **用途** | 云同步 API 的 base URL(前端直连) | 仅用于"服务端探测"的目标地址 |
|
||||
| **存储位置** | `localStorage` | 不变(探测时使用) |
|
||||
| **影响登录/Todo 拉取** | 是(前端用它做 baseURL) | 否(走 Host 自身端点) |
|
||||
|
||||
### 4. 需要变更的文件清单
|
||||
|
||||
| 层 | 文件 | 变更类型 | 说明 |
|
||||
|---|---|---|---|
|
||||
| Application | `Services/CloudProbeService.cs` | **新增** | 服务端探测逻辑,注册于 `AddCloudSyncServer()` |
|
||||
| Application | `Models/AdminDtos.cs`(或新 `Models/ProbeDtos.cs`) | **新增 DTO** | `ProbeRequest` / `ProbeResponse`(纯数据合约) |
|
||||
| Application | `CloudSyncEndpointExtensions.cs` | **修改** | 注册 `POST /cloud-sync/probe` |
|
||||
| Application | `CloudSyncServiceCollectionExtensions.cs` | **修改** | `AddCloudSyncServer()` 内注册 `CloudProbeService` + `HttpClient` |
|
||||
| Host | `Program.cs` | **无需修改** | `MapCloudSyncEndpoints()` 自动包含新端点 |
|
||||
| Web(Vue) | `vite.config.ts` | **修改** | 补充云同步端点的 dev proxy |
|
||||
| Web(Vue) | `api/cloudClient.ts` | **修改** | 移除外部 `serverUrl` baseURL |
|
||||
| Web(Vue) | `api/cloudSync.ts` | **修改** | 新增 `probeServerUrl()` 方法 |
|
||||
| Web(Vue) | `components/CloudSyncSettingsDialog.vue` | **修改** | 删除 `probeReachability()`,改用 API 调用 |
|
||||
|
||||
| 层 | 文件 | 变更 | 说明 |
|
||||
|---|---|---|---|
|
||||
| MAUI | `MauiProgram.cs` | **无需修改** | 不注册 `AddCloudSyncServer()` |
|
||||
| MAUI | `EmbeddedWebServerService.cs` | **无需修改** | 不映射 `MapCloudSyncEndpoints()` |
|
||||
|
||||
### 5. 端点路由变更汇总
|
||||
|
||||
| 路由 | 方法 | 变更 | 可用环境 |
|
||||
|---|---|---|---|
|
||||
| `/cloud-sync/probe` | POST | **新增** | Host(Vite dev proxy)/ MAUI(同源请求到 Host) |
|
||||
| `/auth/login` | POST | 无变更 | 同上 |
|
||||
| `/auth/step-up` | POST | 无变更 | 同上 |
|
||||
| `/tasks/` | GET | 无变更 | 同上 |
|
||||
| `/security/policy` | GET | 无变更 | 同上 |
|
||||
|
||||
### 6. 服务端/客户端职责边界总结
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Hua.Todo.Application (共享层) │
|
||||
│ │
|
||||
│ AddApplicationServices() AddCloudSyncServer() │
|
||||
│ ├─ TodoDbContext ├─ CloudAuthService │
|
||||
│ ├─ TaskRepository ├─ CloudAdminService │
|
||||
│ ├─ TaskService ├─ CloudTaskSyncService │
|
||||
│ ├─ DynamicApi ├─ SecurityPolicyService │
|
||||
│ └─ DTOs (Models/*) ├─ CloudProbeService (新) │
|
||||
│ ↑ 纯数据合约,两端安全 ├─ Authentication/Policy │
|
||||
│ └─ MapCloudSyncEndpoints │
|
||||
│ │
|
||||
│ Host 注册: AddApplicationServices + AddCloudSyncServer │
|
||||
│ MAUI 注册: AddApplicationServices only │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 7. 安全考量
|
||||
|
||||
| 风险点 | 缓解措施 |
|
||||
|---|---|
|
||||
| SSRF(探测端点攻击内网) | `CloudProbeService` 限制目标 URL 必须是 HTTP/HTTPS 公网地址,禁止探测 localhost/内网 IP |
|
||||
| 探测请求被滥用 | 加频率限制(如每分钟 3 次),仅允许同源请求 |
|
||||
| MAUI 端不暴露云同步端点 | `AddCloudSyncServer()` 仅在 Host 调用,MAUI 无法访问云同步端点 |
|
||||
Reference in New Issue
Block a user