三天时间,把定制侠 iOS 从「TestFlight 能装上」推到「提审材料基本齐活」。本文按时间线记录四件事:后端原生苹果登录落地、一个 Hibernate 7 的部署阻塞、一次隐私协议「打假」,以及不靠 iPad 截图过审的 iPhone-only 方案。
v1.0.0 上传 TestFlight 后实测,苹果账号登录直接报「苹果登录失败,请重试」。前端先做了错误分三类(后端不可用带 HTTP 码 / 业务拒绝透传 / 网络异常),但根因在后端:POST /api/auth/apple/{appId}/native 这个端点根本不存在,请求命中 Spring 默认 404(无 message 字段),客户端兜底文案就成了用户看到的「失败请重试」。
在 zhangtian 后端新增 AppleNativeLoginRequestDTO 与 AppleAuthController 的 POST /{appId}/native,组装 AppleLoginRequestDTO 后复用既有 appleUserService.loginByRequest——JWKS 验签、aud 同时放行 ServicesID 与 BundleID、nonce 可空,这些既有能力直接继承,没有重复造轮子。
一个细节:苹果授权页的 fullName 是按「末空格」拆 Given/Family 的,需要转成苹果 user JSON 结构再交给既有服务。返回扁平 JSON(token/userId/expireTime/isNewUser/nickname),对齐前端 saveSession 的字段约定。
验证方式值得一提:本地起不来完整环境(无 H2、不能连生产库),改用字节码级验证——javap 反编译确认 @PostMapping("/{appId}/native") 注解真实存在于 jar 中的类文件。
端点编进 jar 后卡在启动:application.yml 里配了 database-platform: org.hibernate.dialect.MySQL8Dialect,而这个类在 Hibernate 7 已被移除,启动即 ClassNotFound。
解法干净利落:删掉该行。Hibernate 7 会依据 JDBC 连接(MySQL Connector/J 8.0.33)自动识别方言,MySQL8Dialect 本来就是多余配置。遗留的 hibernate.dialect.storage_engine: innodb 属性不受影响。改完重新打包,解压 jar 校验 BOOT-INF/classes/application.yml 确认无残留,BUILD SUCCESS。
这次排障还撞上一个环境级变化:Bash 和 PowerShell 都禁止调 cmd.exe 了,原来的 build.bat(JDK25 + IntelliJ 内置 maven3)直接报废。替代方案是绕过 shell,用 Python subprocess 直起 java 进程,通过 classworlds 启动 maven3:
java -Dmaven.multiModuleProjectDirectory=<项目根> \
-Dmaven.home=<maven3目录> \
-Dclassworlds.conf=<maven3>/bin/m2.conf \
-classpath <maven3>/boot/plexus-classworlds-2.7.0.jar \
org.codehaus.plexus.classworlds.launcher.Launcher -o -f pom.xml package
两个坑:① maven.multiModuleProjectDirectory 必须放在 java -D 上,放 maven 参数位置无效;② PowerShell 给原生命令传带空格路径的参数会被拆散(-Dmaven.home="$var" 断裂),必须走 Python 列表传参。脚本化后 9.4 秒 BUILD SUCCESS,比原来的 bat 还快。
这是三天里最危险的发现。准备 ASC 提审材料时打开隐私政策 URL https://www.dingzhixia.com/privacy.html,发现页面套的是微信小程序/直播商城模板,声明收集一堆 App 根本没有的东西:
为什么危险:苹果审核员一定会点开隐私政策 URL 对照 App 实际行为。政策说收身份证、App 却不收——这不只是「不实陈述」,还等于主动把审核员视线引到最敏感的字段上。
重写前先按代码核实真实行为,而不是凭印象:
| 核实项 | 方法 | 结论 |
|---|---|---|
| 定位 | AndroidManifest 权限清单 + iOS Info.plist 用途描述 | 无定位权限、无 NSLocation 声明 → 不收集 |
| 照片 | image_picker + 相机/相册用途描述 | 仅用户主动上传定制图时收集 |
| 收货信息 | order.dart 字段(receiverName/receiverPhone/省市区/addressDetail) | 收集,用于配送 |
| 诊断日志 | cloud_log_service 上传内容(错误堆栈+操作记录+userId) | 收集,用于排障 |
| 剪贴板 | 全库 grep Clipboard | 只有 setData 写入(复制邀请码/订单号),从不读取 |
然后三处同步重写:线上 privacy.html、App 内 privacy_policy_screen.dart、agreement_page.dart 的 privacy 分支。新版结构:真实收集清单(手机号/收货信息/照片/第三方登录昵称头像/用户 ID+设备标识/诊断日志/订单备注)+「我们不收集的信息」章节(位置、身份证、通讯录、麦克风、不读剪贴板)+ 真实 SDK 清单(微信登录支付、支付宝、QQ、B站、华为、Apple)+ App 内注销路径(符合 5.1.1(v))。
ASC 的 App 隐私问卷默认列了 11 个数据类型,逐个按代码事实判定:
| 数据类型 | 收集? | 用途 | 关联身份 | 追踪 |
|---|---|---|---|---|
| 姓名 | 是 | App 功能 | 是 | 否 |
| 电话号码 | 是 | App 功能 | 是 | 否 |
| 实际地址 | 是 | App 功能 | 是 | 否 |
| 照片或视频 | 是 | App 功能 | 是 | 否 |
| 用户 ID | 是 | App 功能 | 是 | 否 |
| 设备 ID | 是 | App 功能 | 否 | 否 |
| 崩溃数据 | 是 | 分析 | 否 | 否 |
| 其他诊断数据 | 是 | 分析 | 否 | 否 |
| 精确位置 | 删 | — | — | — |
| 粗略位置 | 删 | — | — | — |
| 性能数据 | 删 | — | — | — |
删三项的依据:无定位权限、无性能埋点。声明了却没收集一样会被打回——隐私标签的核心原则是「与事实一致」,多填和少填都是错。
ASC 要求「13 英寸 iPad 截屏」的前提是构建声明支持 iPad。我一开始以为后台有「支持设备」开关,找了一圈发现根本没有——设备支持由上传的构建二进制里的 UIDeviceFamily 决定(源自 Xcode 的 TARGETED_DEVICE_FAMILY),Flutter 模板默认 "1,2"(iPhone + iPad)。
正解:ios/Runner.xcodeproj/project.pbxproj 三处配置(Debug/Release/Profile)"1,2" → "1",重新打包。两个事实支撑这个选择:
TARGETED_DEVICE_FAMILY = 1 的 App 在 iPad 上仍以兼容模式运行,审核员照样能测,不算缺陷;另外确认了 CI 转换脚本只注入 DEVELOPMENT_TEAM、不碰设备族,改动能安全带进 GitHub Actions 构建。
文案三件套(都按字符上限校验过):
合规项:主要类别「购物」+ 次类别「生活」;定价选免费(实体商品走支付宝/微信,不需要 IAP,免费 App 也无需税务协议);内容版权填「2026 北京醉么信息技术有限公司」(不带 © 符号、不能填网址或 N/A);内容版权声明选「否」(用户上传的定制照片属 UGC,不构成 App 包含第三方内容);DSA 交易商声明选「是」——公司主体开发的商业 App 按欧盟标准就是 trader,且即使不在欧盟分发,提审新 App 时也会被要求披露。
cf4cb5c 推送 main → v1.0.1 tag 触发两条工作流,双双全绿:
本版内容:iPhone-only 设备族 + App 内隐私协议修正(真图标 v1.0.0 已带)。
三天下来最大的感触:提审材料的敌人不是功能缺失,而是「文档与事实不一致」。一份套错模板的隐私政策页、一组凭感觉勾选的隐私标签,比任何 bug 都更容易让审核进行不下去。所有合规项都按代码事实逐个核实过——这才是这次冲刺真正做完的事。
还没有评论,登录后来说两句。