Files
Hua.Todo/docs/project/v1.2.0-tasks/08-cloud_sync_refactor_plan.md
T
2026-06-12 00:49:03 +08:00

14 KiB
Raw 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()  ← 任务 CRUD 服务
│       └── AddCloudSyncServer()      ← 云同步认证/授权/端点  **仅服务端**
│
└── Hua.Todo.Maui/               ← 桌面/移动客户端(嵌入 WebView
    └── MauiProgram.cs
        └── AddApplicationServices()  ← **仅** 任务 CRUD 服务
            (不注册 AddCloudSyncServer,不暴露云同步端点)

1.2 两种部署模式

模式 A:Host 独立服务端(开发/部署)

┌───────────────────────────────┐
│  Vue 前端 (Vite :5174)         │
│  cloudClient                  │
│    baseURL = serverUrl(外部)    │  直连外部
│    → /auth/login              │ ────────→ 外部云同步服务
│    probeReachability()         │  直连外部
│    → fetch(serverUrl)          │ ────────→
├───────────────────────────────┤
│  proxy /api                    │
├───────────────────────────────┤
│  ASP.NET Host (:5173)          │
│  AddApplicationServices()      │  任务 CRUD
│  AddCloudSyncServer()          │  云同步端点  ← **服务端**
│  ├─ /api/*     (本地任务)       │
│  └─ /auth/*    (云同步)         │
│     /tasks/*                   │
│     /cloud-sync/probe  (新增)  │
└───────────────────────────────┘

模式 BMAUI 嵌入式 WebView(桌面端/移动端)

┌───────────────────────────────┐
│  MAUI WebView                 │
│  ┌─────────────────────────┐  │
│  │  Vue 前端 (静态托管)      │  │
│  │  cloudClient             │  │
│  │    baseURL = 同源        │  │  同源请求
│  │    → /api/*   (本地任务)  │──┼───────→ Embedded WebServer
│  │    → /auth/*  (❌ 不可用) │  │        (不暴露云同步端点)
│  └─────────────────────────┘  │
├───────────────────────────────┤
│  Embedded WebServer (:5057)   │
│  AddApplicationServices()     │  仅任务 CRUD
│  AddCloudSyncServer()         │  ❌ 未注册
│  UseDynamicApi()              │  仅本地任务 API
│  MapCloudSyncEndpoints()      │  ❌ 未映射
└───────────────────────────────┘

关键区别Hua.Todo.Application 被两方引用,但云同步能力仅在 Host 端通过 AddCloudSyncServer() 激活。MAUI 端只使用 AddApplicationServices() 获取任务 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 不变(探测时使用)
影响登录/任务拉取 是(前端用它做 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 无法访问云同步端点