From 09e2373965f8058f6ab495d6f29e30b916943576 Mon Sep 17 00:00:00 2001 From: laoyang Date: Tue, 3 Nov 2026 09:55:06 +0800 Subject: [PATCH] =?UTF-8?q?docs(README):=20=E6=9B=B4=E6=96=B0=E5=88=B0=200?= =?UTF-8?q?.2.0=20=E7=8E=B0=E7=8A=B6=EF=BC=8C=E8=A1=A5=E3=80=8C=E8=87=AA?= =?UTF-8?q?=E8=A1=8C=E7=BC=96=E8=AF=91=E4=B8=8A=E6=9E=B6=E9=9C=80=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E5=93=AA=E4=BA=9B=E6=9D=83=E9=99=90=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README 停留在 0.1.0 且有多处与代码不符,本次逐项核对后更新。 所有结论均以 module.json5 / AppScope / SettingsPage.ets / release profile 实测为准。 ## 修正的过时内容 - 版本号 0.1.0 (10000) → **0.2.0 (10200)** - 待办(VTODO)描述错误:「只读展示,不参与推送」 → 实际 0.2.0 已由 `TodoService` 实现新建/编辑/打勾/删除全量双向推送, 且写回时先 GET 原 .ics 原地改写,保留服务端未知属性 - 设置项一览与界面对不上 → 按 SettingsPage.ets 实际分组重写为「系统日历 / 通用 / 数据 / 关于」四组, 补上此前漏掉的「系统日历中的显示名称」「清除系统日历数据」「日历本只读·静音」 ## 新增 ### 📝 最近更新 补 v0.2.0 与 v0.1.0 两个版本条目,让版本变化可追溯。v0.2.0 记录: 待办双向同步、3 项同步核心修复(删除 404 / 防复活 / 404 物理清库)、反馈渠道迁移。 ### 🔐 权限清单与用途(独立小节) 用表格列清 5 个权限的类型、用途、申请时机。之前只在技术要点里一行带过, 读者无法判断哪些要额外动作。 ### 🔑 自行编译并上架:权限要怎么处理 这是本次重点。回答「fork 后自己上架需要申请哪些权限」: 1. **必须自己重走的**:READ_WHOLE_CALENDAR(ACL)、PUBLISH_AGENT_REMINDER。 并明确一个大坑:**ACL 绑定 bundleName**,本项目已获批的 ACL 只对 `synccalendar.yangyq.net` 有效;fork 后改了包名(几乎必然要改)就立即失效, 必须用新包名重新申请。很多人会误以为代码里声明了就自动继承。 2. **ACL 怎么验证落地**:给出 openssl 解 p7b + 读 allowed-acls 的命令。 ACL 不在 module.json5 里,而在签名 Profile 里——这正是我自己这次判断失误的地方, 写进文档避免后来者重蹈。 3. **不想申请 ACL 的替代方案**:删掉权限,日历经 READ_CALENDAR 仍可读本应用写入的日历, 只是看不到其他来源。社区实践过的合规绕行。 4. **其他审核注意点**:图标 alpha 通道要求、审核员无 CalDAV 账号需 Demo 模式、 隐私政策与 module.json5 必须一致。 ## 未改动 - 构建/签名章节(与当前 build-profile.json5 一致) - 隐私、功能特性正文、相关链接(链接已是新的 Feedlite 地址) README 353 → 479 行。 --- README.md | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cea73ef..7df20ca 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,41 @@ 一款以**同步为核心**的鸿蒙原生日历应用,基于 HarmonyOS NEXT(ArkTS / Stage 模型)开发。对接标准 CalDAV 服务器(群晖、Nextcloud、Radicale、Baikal 等),让你的日程真正掌握在自己手里——不经过第三方云服务,数据始终同步在你自己的 NAS 上。 -- 应用市场送审版本:**0.1.0** | 仓库当前:**0.1.0**(versionCode 10000) +- 应用市场送审版本:**0.2.0** | 仓库当前:**0.2.0**(versionCode 10200) - 官网: | 问题反馈: - 开源协议:Apache License 2.0 --- +## 📝 最近更新 + +### v0.2.0(2026-11) + +**待办(VTODO)从只读升级为完整双向同步** +- 新增 `TodoService`:新建 / 编辑 / 打勾完成 / 删除待办,全部推送回服务器 +- 打过勾会改写 `STATUS:COMPLETED` + `PERCENT-COMPLETE:100` 并 PUT +- 修改前先 GET 原始 `.ics`,原地改写而不是整份重建 —— 保留服务器上本应用不认识的属性 +- 日历本按类型区分「日历」与「待办」,待办在专属 Tab 中管理 + +**同步核心修复(3 项高危)** +- **删除 404**:`remotePath` 改为保存服务器真实资源文件名。此前一直用 + `encodeURIComponent(uid) + '.ics'` 去猜,遇到「文件名 ≠ UID」的服务器 + (如用 `preg_replace` 改名的家书)会 DELETE 到不存在的路径 → 404 → 服务器上删不掉。 + 修复覆盖三条取值链路:REPORT 补拉、etag 未变的复用路径、以及 unchanged 分支的存量回填。 +- **防复活**:`applyRemote` 增加 `dirtyKeyed` / `dirtyDeletedUids` 白名单。 + 此前只把 dirty 行排除出索引,远端数据查不到就走"新增"分支又插一行, + 导致同一事件出现两条、或"删了又回来"。 +- **404/410 物理清库**:删除请求返回 404/410 时视为服务器上已不存在, + 继续清理本地行而不是直接 return —— 否则用户会看到"怎么删都删不掉"的日程。 + +**反馈渠道迁移** +- 从自建 PHP 论坛迁至多软件共用的 **FeedLite**,按软件分栏管理 + +### v0.1.0(首个送审版本) +- CalDAV 全链路同步、月/周/列表视图、重复日程展开、系统日历镜像、桌面卡片、后台上层同步 + +--- + ## ✨ 功能特性 ### CalDAV 同步 @@ -15,7 +44,10 @@ - 完整协议链路:**PROPFIND** 发现日历本 → **REPORT** 全量拉取 → **ETag** 增量比对 → **PUT / DELETE** 双向推送 - 一个账号可挂载多个日历本,按需勾选同步范围;支持在线编辑账号、重选日历本、删除账号 - 解析 **VEVENT / VTODO / VALARM / RRULE / EXDATE / RECURRENCE-ID**,重复日程的**单次覆盖实例**也能正确还原 -- 支持 VTODO 待办同步(只读展示,不参与推送) +- 完整 **VTODO 待办**支持:新建 / 编辑 / 打勾完成 / 删除,均可双向推送回服务器 + - 待办与日程分列:日历本按类型区分「日历」与「待办」,待办在专属 Tab 中管理 + - 打勾完成会改写 `STATUS:COMPLETED` + `PERCENT-COMPLETE:100` 并 PUT 回服务器 + - 修改时先 GET 原始 `.ics` 再原地改写,保留服务器上本应用不认识的属性 - 「重建日程数据」可强制整表重拉,用于修复提醒丢失等本地数据问题 ### 日历视图与交互 @@ -82,7 +114,27 @@ ### 设置项一览 -系统日历处理方式(仅显示 / 备份到 CalDAV)· 镜像到系统日历 · 选择要镜像的日历本 · 回写开关 · 默认视图 · 日程显示方式 · 自动同步间隔 · 后台自动同步 · 通知权限 · 混合显示系统日历 · 日历本只读 / 静音 · 重建日程数据 · 重置账号库 · 软件特性 · 使用帮助 · 隐私政策与用户协议 +**系统日历** +- 镜像到系统日历(含「选择要镜像的日历本」) +- 把系统日历的改动回写 +- 系统日历中的显示名称(三种命名格式可选) +- 混合显示系统日历(关闭后只显示 DAV 账号的日程) +- 清除系统日历数据(卸载前清理镜像数据,防止残留孤儿账户) + +**通用** +- 默认视图(月 / 周 / 列表) +- 日程显示方式(竖向时间轴 / 列表) +- 自动同步间隔(1 / 5 / 15 / 30 分钟) +- 后台自动同步 +- 通知权限 + +**数据** +- 重建日程数据(强制整表重拉) +- 重置账号库 +- 日历本只读 / 静音(逐本设置) + +**关于** +- 软件特性 · 使用帮助 · 问题反馈 · 隐私政策与用户协议 --- @@ -111,7 +163,81 @@ - **权限**:INTERNET / READ_CALENDAR / READ_WHOLE_CALENDAR / WRITE_CALENDAR(仅镜像)/ PUBLISH_AGENT_REMINDER - ⚠️ **LOCATION 已于 0.1.0 移除**:`geoLocationManager.getAddressesFromLocationName`(地址→坐标)无 @permission 标注,地理编码不需要定位权限;应用也从不调用 `getCurrentLocation`。 隐私政策/隐私标签里**不得**再声称读取位置。 - - ⚠️ **READ_WHOLE_CALENDAR 是受限权限**:用于读取系统日历中非本应用创建的日程以实现混合展示。上架时需在 AGC 单独申请 ACL 并说明用途,否则审核不通过。 + +## 🔐 权限清单与用途 + +| 权限 | 类型 | 用途 | 何时申请 | +|---|---|---|---| +| `ohos.permission.INTERNET` | 系统授权 | 访问你填写的 CalDAV 服务器 | 安装即授予 | +| `ohos.permission.READ_CALENDAR` | user_grant | 读取本应用写入系统日历的日程 | 打开「混合显示」时 | +| `ohos.permission.READ_WHOLE_CALENDAR` | **受限(需 ACL)** | 读取系统日历中**非本应用创建**的日程,实现混合展示 | 同上 | +| `ohos.permission.WRITE_CALENDAR` | user_grant | 把 CalDAV 日历本镜像到系统日历 | 开启「镜像到系统日历」时 | +| `ohos.permission.PUBLISH_AGENT_REMINDER` | 系统授权 | 发送日程提醒通知 | 保存带提醒的日程时 | + +> 所有 user_grant 权限都**只在用户主动操作对应功能时**才弹窗,启动 / 首启 / 同意隐私政策时一律不申请。 +> 拒绝后功能默认关闭,可随时在系统设置或本应用设置页重新开启。 + +--- + +## 🔑 自行编译并上架:权限要怎么处理 + +> 如果你 fork 本项目、修改后想上架华为应用市场,**仅修改 `module.json5` 是不够的**。 +> 下面这些权限需要额外动作,否则审核会被驳回、或安装后权限静默失效。 + +### 一、必须自己去申请的(本项目无法代劳) + +以下权限绑定在**华为开发者账号**上,与本仓库代码无关。你必须用自己的账号重新走一遍: + +| 权限 | 需要做什么 | 审核周期 | 被拒风险 | +|---|---|---|---| +| `READ_WHOLE_CALENDAR` | 在 **AGC → 我的应用 → 权限管理** 提交 **ACL 申请**,说明为什么必须读取非本应用创建的日程 | 数个工作日 ~ 2 周 | 中。用途说明不充分会被驳回 | +| `PUBLISH_AGENT_REMINDER` | 属受限开放权限,需在 AGC 申请 | 数个工作日 | 低 | + +**本项目已获批的 ACL 只对 `bundleName = synccalendar.yangyq.net` 生效。** +一旦你改了 `bundleName`(几乎必然要改,否则会与本项目冲突),ACL 立即失效,必须用新包名重新申请。 + +### 二、ACL 是怎么落地的 + +ACL **不写在 `module.json5` 里**,而是由 AGC 在审核通过后签发进 **签名 Profile(`.p7b`)**。 + +想确认手上这份 Profile 有没有带 ACL,可以这样查: + +```bash +# 解出 profile 明文(p7b 本质是 PKCS#7 包裹的 JSON) +openssl smime -verify -inform DER -in your.release.p7b -noverify -out profile.json + +# 查看 allowed-acls +python3 -c "import json;d=json.load(open('profile.json'));print(d.get('acls'))" +``` + +正常应输出类似: + +```json +{"allowed-acls": ["ohos.permission.READ_WHOLE_CALENDAR"]} +``` + +**若 `acls` 为空或缺失,说明该 Profile 未获批 ACL** —— 此时即使 `module.json5` 声明了权限, +装到普通用户手机上也会被系统拒绝,功能静默失效。 + +### 三、不想申请 ACL 怎么办 + +如果你不想走 ACL 流程(例如只是自用、或嫌审核慢),可以把 `READ_WHOLE_CALENDAR` 从 +`module.json5` 中删掉,代价是: + +- 「混合显示系统日历」只能看到**本应用自己写入**的日历(`READ_CALENDAR` 足够) +- 看不到华为账号自带日历、其他 App 写入的日历 + +功能仍然可用,只是视图里少了其他来源的日程。这是一种被社区实践过的合规绕行方案。 + +### 四、其他与审核相关的注意点 + +- **应用图标**:AGC 要求分层图标的后景图(`background.png`)为 **100% 不透明、无圆角**(规范 7.1.1.4.3)。 + 本项目 `AppScope/resources/base/media/background.png` 已满足;若你要换图,务必重新校验 alpha 通道。 + 另外 `app_icon.png` 目前含透明像素,某些场景(如应用内展示)不受影响,但如要提交商店素材建议先转为不透明。 +- **Demo / 免账号体验**:审核员通常没有你的 CalDAV 账号,无法测试核心功能,这是常见的驳回原因。 + 建议提供一个「一键载入演示数据」入口,或在上架备注中提供测试账号。 +- **隐私政策一致性**:隐私政策里声明的权限、数据流向必须与 `module.json5` 完全一致。 + 本项目已移除 LOCATION,隐私政策中**不得**再出现"读取位置"之类表述。 ### 目录结构