跳至内容
WebViewGold 从 Vibe Coding 到 App Store
中文
获取 WebViewGold

Claude Code + 隐私

用 Claude Code 实现账户删除和数据导出:通过 Apple 5.1.1(v) 和 Google Play 审核

如果用户可以在您的应用中创建账户,Apple 和 Google 都要求用户能在应用内删除账户,Google 还要求提供一个接收删除请求的网页。Claude Code 一个下午就能构建好这项功能,但前提是它清楚每一份用户数据存放在哪里。本指南从这份数据清单开始,然后构建删除功能、“下载我的数据”导出,以及 WebViewGold 应用内的清理。

每个平台一次性 $109 起,或由我们的团队全程代劳,$698 起。

  • 审核通过保证 – 否则 14 天内退款
  • 无需编程
  • 一次性付费,免费更新
  • 7×24 小时人工支持
  • 2014 年起德国出品 · ISO 27001

商店实际的要求

Apple,准则 5.1.1(v):如果您的应用支持创建账户,用户必须能够在应用内发起账户删除。Apple 的指引要求删除账户记录及相关的个人数据,而不是提供一个停用开关;依法必须保留的数据可以保留,并且您应该告诉用户删除需要多长时间。如果您提供 Sign in with Apple,请在账户删除时通过 Apple 的 REST API 撤销该用户的令牌。删除账户并不会取消通过 Apple 购买的订阅,所以请告诉用户如何取消。

Google Play:允许用户创建账户的应用,必须在应用内提供删除功能,并提供一个网页链接,让用户无需重新安装应用即可申请删除。您需要在数据安全表单中填写这个链接,删除范围必须涵盖与账户关联的数据,您声明保留的数据除外。Google Play 账户删除指南介绍了控制台那一侧的操作。

数据导出:两个商店都没有要求,但 GDPR 等隐私法赋予用户访问权和可携带权,一个“下载我的数据”按钮无需客服工单就能响应这类请求。它也是对数据清单的一次很好的检验:如果代理导不出某项数据,它多半也删不掉。

Claude Code 做什么,您做什么

领域Claude Code您
数据清单在计划模式下搜索代码、迁移、SDK 导入和 webhook,起草 DATA_MAP.md补充代码中看不到的内容(电子表格、客服邮箱、手动导出),并确认保留期限
代码设置界面、导出和删除路由、清理辅助函数、迁移、测试、/delete-account 页面审阅,在托管平台上设置环境变量,部署
商店控制台起草隐私政策的修改以及数据安全表单的答案在 Play Console 中填写删除 URL,在 App Store Connect 中更新 App Privacy
WebViewGold无需任何改动;如果项目在仓库中,可以设置下载器选项如果修改了下载器设置,重新构建一次

提示词 1:在计划模式下制作数据清单

在计划模式下,Claude Code 可以读取和搜索仓库,但不会编辑任何内容。把结果保存为 DATA_MAP.md,并在继续之前修正它;正是这个文件让删除变得完整。

在计划模式下粘贴到 Claude Code(Shift+Tab)
只使用计划模式,不要修改任何文件。我们需要应用内账户删除(Apple 准则 5.1.1(v) 和 Google Play 账户删除政策)以及“下载我的数据”导出。先建立一份完整的数据清单。

搜索代码库、迁移、环境变量名称、SDK 导入、定时任务和 webhook 处理程序,列出所有存放用户相关数据的地方:身份验证记录、每一张包含 user_id、owner_id 或 email 列的表及其引用用户的方式、存储桶和文件路径、Stripe 客户和订阅、RevenueCat 客户、OneSignal 订阅 ID、Sign in with Apple 令牌、分析和错误跟踪、邮件和新闻通讯工具、日志和备份。

对每一项给出:存放位置、将其与用户关联的标识符、如何删除(外键级联、API 调用,或附带理由的保留),以及它是否应包含在导出中。标出所有不确定的内容。以 markdown 表格的形式呈现结果,方便我保存为 DATA_MAP.md。

我很高兴购买了这个应用!非常简单,文档也很棒。联系客服后,他们回复很快且非常友好,一天之内就解决了我的问题。10/10,值得推荐。

Nelis Geiger · CodeCanyon

Supabase 应用的典型数据清单

数据如何删除是否包含在导出中?
身份验证用户auth.admin.deleteUser,最后一步ID、邮箱、创建日期
用户资料和内容表指向该用户的外键,设置 ON DELETE CASCADE是
上传的文件Storage API,按用户文件夹删除(列出操作不会递归)链接或文件本身
Stripe 客户和订阅取消订阅;按法律要求保留发票,并在 /privacy 中说明方案和发票列表
App Store / Google Play 订阅您无法替用户取消;在应用内显示 cancelinapppurchase://方案名称
RevenueCat 客户通过 RevenueCat REST API 删除客户否
OneSignal 订阅 ID删除您存储的记录;也可以通过其 API 删除 OneSignal 用户否
Sign in with Apple在 Apple 的撤销端点撤销令牌否
分析、错误跟踪、邮件工具服务商的删除 API 或已声明的保留期限通常否

提示词 2:构建删除和导出功能

切换回默认权限模式,以便逐一审阅每处编辑;如果您更愿意在最后审阅完整的 diff,也可以切换到自动接受编辑模式(accept-edits)。

DATA_MAP.md 确认无误后粘贴到 Claude Code
根据 DATA_MAP.md 实现账户删除和数据导出。我们的应用同时运行在用 WebViewGold 构建的原生 iOS 和 Android 应用中(User-Agent 中包含 WebViewGoldApp)。

1. 设置 > 账户:一个“下载我的数据”按钮和一个“删除账户”按钮。
2. 导出:GET /api/account/export 为当前登录用户返回一个 JSON 文件,包含清单中所有可导出的条目,响应头为 Content-Type application/json 和 Content-Disposition attachment; filename=my-data.json。用一个指向该 URL 的普通链接触发下载,不要用 Blob 或 createObjectURL:在应用内,WebViewGold 下载器是根据文件扩展名和 Content-Disposition 响应头来工作的。如果导出内容可能很大,就在后台任务中生成,并通过邮件发送一个 24 小时后失效的签名链接。
3. 删除:一个确认页面,说明会删除什么、出于法律原因会保留什么(发票),以及 App Store 或 Google Play 订阅不会被自动取消。在应用内,如果用户有商店订阅,就显示一个导航到 cancelinapppurchase:// 的“管理订阅”链接。要求用户输入 DELETE。
4. POST /api/account/delete:先验证身份,然后按以下顺序执行:取消 Stripe 订阅,删除 RevenueCat 客户,如果有 Sign in with Apple 令牌就撤销它们(Apple REST API,/auth/revoke),删除存储中的文件,删除数据行(优先使用 ON DELETE CASCADE;缺失的地方添加迁移),最后用 admin 客户端删除身份验证用户。只在服务器端执行;service-role 密钥绝不能进入客户端打包文件。让该路由具备幂等性,这样在部分失败后重试也能完成任务。
5. 成功后:让用户退出登录并显示 /account-deleted。在应用内,该页面的“完成”按钮导航到 reset://,以清除封装层保存的 Cookie 和缓存。
6. 一个公开的 /delete-account 页面,面向已经没有安装应用的人:说明应用内的删除路径,并提供申请表单或邮箱地址,无需登录即可使用,在 320 px 宽的屏幕上也能正常显示。
7. 在 /privacy 中更新保留期限和 /delete-account 的 URL。
8. 为删除路由编写测试:一个在每张表中都有数据的用户,删除后行数为零;第二次调用该路由仍然成功。

报告修改过的文件、迁移,以及我需要手动完成的每一个步骤。

删除路由,按不会丢失 ID 的顺序执行

先处理第三方,趁您还知道客户 ID;身份验证用户放在最后,因为大多数外键都挂在它上面。account-cleanup 中的辅助函数封装了数据清单中的 Stripe、RevenueCat 和 Apple 调用。

typescript
// app/api/account/delete/route.ts  (仅限服务器端)
import { NextResponse } from "next/server";
import { createClient as createAdmin } from "@supabase/supabase-js";
import { createClient } from "@/lib/supabase/server";
import { cancelStripeSubscriptions, deleteRevenueCatCustomer, revokeAppleTokens } from "@/lib/account-cleanup";

export async function POST(req: Request) {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) return NextResponse.json({ error: "unauthorized" }, { status: 401 });

  const { confirm } = await req.json();
  if (confirm !== "DELETE") return NextResponse.json({ error: "confirmation missing" }, { status: 400 });

  const admin = createAdmin(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);

  // 1. 第三方,趁 ID 还在 (没有需要处理的内容时,每个辅助函数什么也不做)
  await cancelStripeSubscriptions(user.id);
  await deleteRevenueCatCustomer(user.id);
  await revokeAppleTokens(user.id);

  // 2. 文件:list() 不会递归,嵌套文件夹需要单独处理
  const { data: files } = await admin.storage.from("uploads").list(user.id);
  if (files && files.length > 0) {
    await admin.storage.from("uploads").remove(files.map((f) => user.id + "/" + f.name));
  }

  // 3. 数据行通过 ON DELETE CASCADE 随用户一起删除 (参见 DATA_MAP.md)
  // 4. 身份验证用户,放在最后
  const { error } = await admin.auth.admin.deleteUser(user.id);
  if (error) return NextResponse.json({ error: "delete failed, safe to retry" }, { status: 500 });

  return NextResponse.json({ ok: true });
}

让导出文件能在应用内下载

两个小设置决定了文件能否到达手机。

  1. 提供真实的文件 URL

    上面的路由返回带 Content-Disposition attachment 响应头的 JSON。用一个普通的锚点链接指向它;WebViewGold 下载器文档并未涵盖由 JavaScript 生成的 Blob 下载。

  2. iOS

    要么在 Config.swift 中启用 autodownloader(根据 Content-Disposition 响应头下载),要么让文件的 URL 以 .json 结尾,并把“.json”加入 extentionARY。参见iOS 文件下载器文档。

  3. Android

    在 Config.java 中启用 AUTO_DOWNLOAD_FILES,或者把扩展名加入 downloadableExtension;如果使用下载功能,requireStorage 必须保持为 true。参见Android 文件下载器文档。

  4. 重新构建一次

    这些是原生设置,所以需要一次新的构建。本指南中的其他所有内容都会随您下一次 Web 部署上线。Cloud Builder 无需在本地安装 Xcode 或 Android Studio 就能生成这个构建版本。

验证清单

  • 在 TestFlight 和内部测试版本中,创建一个全新账户,添加内容,上传一个文件;如果您销售订阅,就在沙盒中买一个。
  • 点“下载我的数据”:文件到达手机,其中包含您的内容,而不是一个空对象。
  • 点“删除账户”:确认页面说明了后果和订阅提示,“管理订阅”链接会打开商店页面,输入 DELETE 后流程完成,应用显示确认信息。
  • 在确认页面上点“完成”:reset:// 清除封装层的 Cookie 和缓存。记下您的构建版本随后显示什么,确保那是一次干净的重新开始,而不是错误。
  • 尝试再次登录:该账户已不存在。按用户 ID 查询,DATA_MAP.md 中的每张表都返回零行,存储文件夹也是空的。
  • 对于一个 Sign in with Apple 测试用户,删除后,该应用会从那个 Apple 账户设置中的“通过 Apple 登录”列表里消失,这表明令牌已被撤销。
  • 在手机浏览器的无痕窗口中打开 /delete-account:未登录也能使用,并且同一个 URL 已填写在 Play Console 的数据安全表单中。

常见陷阱

  • 只做软删除。一个 deleted_at 标记只是停用。审核人员和 Google 期望数据真正消失,已声明保留的数据除外。
  • 先删除身份验证用户。没有级联时,数据行会残留;有级联时,您会丢失处理 Stripe 和 RevenueCat 所需的 ID。先处理第三方,最后再删身份验证用户。
  • 忘记存储中的文件。头像和上传文件在数据行删除后依然存在。数据清单正是为了发现这类问题。
  • 删掉了审核人员的演示账号。审核人员可能会在您提供的账号上测试删除功能。在审核备注中说明他们可以重新注册一个账号,并在每次审核后重新创建演示账号。
  • 藏在客服后面。把“发邮件给我们以删除账户”作为唯一途径,对大多数应用来说都无法满足 5.1.1(v)。必须由应用内的按钮发起删除。

常见问题

停用账户就够了吗?

不够。Apple 要求删除账户及其相关的个人数据,Google Play 也要求数据被删除,而不只是隐藏。停用可以作为额外的选项,但不能是唯一的选项。

删除后可以保留部分数据吗?

可以,前提是您有法律义务或正当理由,例如出于税法要求保留发票。请在隐私政策和数据安全表单中声明。

删除可以花几天时间吗?

设置一个短暂的宽限期很常见。在应用内发起删除,明确告诉用户数据何时会被清除,并查阅 Apple 最新的账户删除指引,了解审核人员期望的措辞。

在应用内购买的订阅会怎样?

您无法代用户取消 App Store 或 Google Play 订阅。显示一个“管理订阅”链接(在应用内使用 cancelinapppurchase://),并清楚地说明在用户取消之前会继续扣费。

导出应该用什么格式?

一种结构化、机器可读的格式,例如 JSON 或 CSV。每个账户一个 JSON 文件,最容易生成、测试和解释。

让您的应用上架商店的三种方式

结果相同,只是您需要投入的工作量不同。三种方式都附带审核通过保证和 7×24 小时人工支持。

自己动手

$109 每个平台,一次性付费

WebViewGold 许可证:一个现成的 Xcode 或 Android Studio 项目。粘贴您的 URL,设置名称和图标,开启所需功能,构建并提交。永久免费更新。

在主页查看方案

Cloud Builder

$349 每个许可证,一次性附加购买

无需 Mac、Xcode 或 Android Studio:在浏览器中完成配置,我们在云端构建两个应用,并将其交付到 App Store Connect 和 Play Console。

Cloud Builder 如何运作 试用免费演示

Full Service

$698 每个平台 $798

把您的 URL 和图标发给我们。我们的团队在 72 小时内完成配置、构建并交付您的应用,白标,附源码项目。两个平台共 $1,158。

Full Service 详情 服务提供方: WeSetupYourWebViewApp

冬季特惠价格有效期至 2026年12月31日。

准备好发布您的应用了吗?

App Store & Google Play 审核通过保证 – 否则 14 天内退款

想直接交给我们?Full Service:我们在 72 小时内完成配置、构建并交付,每个平台 $698 起。有疑问?通过 7×24 小时在线聊天联系我们。

退款依据我们 FAQ 中列出的条件:应用因非内容原因被 Apple 和/或 Google 拒审,并在购买后 14 天内提出申请。