Files
ShaoHua d81aa06681 refactor: 重构文档结构与环境配置,统一研发工单命名
1. 更新 .env 配置文件,替换原有云同步变量为 API 目标配置
2. 调整 publish-linux.ps1 中的文档路径,使用研发工单目录
3. 重构项目文档目录:将原 v1.2.0-tasks 迁移为研发工单-v1.2.0 目录,统一术语为"研发工单"替代"任务"
4. 更新 README.md 与各 PRD 文档的术语对照表,明确业务实体与研发工作项的区分
5. 新增多个研发工单文档,覆盖搜索、云同步、Linux 打包等模块
6. 删除旧的任务拆分文档,统一使用新的研发工单体系
2026-06-14 00:58:26 +08:00

14 KiB
Raw Permalink Blame History

云同步设置 — 服务端化改造方案

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  (新增)  │
└───────────────────────────────┘

模式 BMAUI 嵌入式 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 直连外部 serverUrlsession 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 端即使不调用也不会产生依赖。

/// <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 容器中可用:

// 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() 方法内部新增端点组:

var probe = app.MapGroup("/cloud-sync").WithTags("CloudSync - Setup");
probe.MapPost("/probe", ProbeAsync).AllowAnonymous();

端点实现:

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() 中改为:

// 旧:前端直接 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 的依赖:

// 改造前(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.vuelogin() 无需修改 cloudSyncApi.login() 调用 cloudClient.post('/auth/login', ...),自动打到 Host。

Host 端 CloudAuthService.LoginAsync 已有完整实现

  • 创建 UserSessionEntity 写入 DBsession 管理在 Host
  • 返回 LoginResponseAccessToken = DB SessionId
  • Session 验证由 SessionAuthenticationHandler 查询 DB 完成

3.3 Vite 代理配置补充

Vite dev 模式下,除了已有的 /api 代理,需补充云同步端点的代理规则vite.config.ts):

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 新增 HostVite 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 无法访问云同步端点