14 KiB
云同步设置 — 服务端化改造方案
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 (新增) │
└───────────────────────────────┘
模式 B:MAUI 嵌入式 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 核心问题
- 前端的"保存并探测"使用浏览器端直接
fetch()探测外部 URL,受 CORS、证书、网络隔离限制。 - 前端的"登录"通过
cloudClient直连外部serverUrl,session token 存在前端sessionStorage,与 Host 脱节。 - 在 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.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):
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 | 新增 | 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 无法访问云同步端点 |