fix: 安全审查 P0-P2 问题修复(26项)

P0 关键修复:
- 移除 exit(0) 强制退出,改为应用语言设置后下次启动生效
- 修复 LivePhotoValidator hasResumed data race,引入线程安全 ResumeOnce
- 修复 addAssetID(toVideo:) continuation 泄漏,添加 writer/reader 启动状态检查
- 修复 OnboardingView "跳过" 按钮未国际化
- 修复 LanguageManager "跟随系统" 硬编码中文
- .gitignore 补全 AI 工具目录

P1 架构与 UI 修复:
- 修复 RealESRGANProcessor actor 隔离违规
- 修复 ODRManager continuation 生命周期保护
- TiledImageProcessor 改为流式拼接,降低内存峰值
- EditorView 硬编码颜色统一为设计系统
- ProcessingView 取消导航竞态修复
- 反馈诊断包添加知情同意提示

P2 代码质量与合规:
- EditorView/WallpaperGuideView 硬编码间距圆角统一为设计令牌
- PrivacyPolicyView 设计系统颜色统一
- HomeView 重复 onChange 合并
- PHAuthorizationStatus 改为英文技术术语
- Analytics 日志 assetId 脱敏
- 隐私政策补充 localIdentifier 存储说明
- 清理孤立的 subscription 翻译 key
- 脚本硬编码绝对路径改为相对路径
- DesignSystem SoftSlider 类型不匹配编译错误修复

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
empty
2026-02-07 20:04:41 +08:00
parent e08cfc981e
commit 4bcad4d4b8
19 changed files with 640 additions and 1396 deletions

View File

@@ -347,6 +347,21 @@ public actor AlbumWriter {
}
}
/// 线 continuation resume
private final class ResumeOnce: @unchecked Sendable {
private var _consumed = false
private let lock = NSLock()
/// true false
func tryConsume() -> Bool {
lock.lock()
defer { lock.unlock() }
if _consumed { return false }
_consumed = true
return true
}
}
public actor LivePhotoValidator {
public init() {}
@@ -378,16 +393,13 @@ public actor LivePhotoValidator {
public func requestLivePhoto(photoURL: URL, pairedVideoURL: URL) async -> PHLivePhoto? {
await withCheckedContinuation { continuation in
var hasResumed = false
let resumeOnce = ResumeOnce()
let requestID = PHLivePhoto.request(
withResourceFileURLs: [pairedVideoURL, photoURL],
placeholderImage: nil,
targetSize: .zero,
contentMode: .aspectFit
) { livePhoto, info in
// resume
guard !hasResumed else { return }
//
if let isDegraded = info[PHLivePhotoInfoIsDegradedKey] as? Bool, isDegraded {
return
@@ -398,8 +410,9 @@ public actor LivePhotoValidator {
#if DEBUG
print("[LivePhotoValidator] requestLivePhoto error: \(error.localizedDescription)")
#endif
hasResumed = true
continuation.resume(returning: nil)
if resumeOnce.tryConsume() {
continuation.resume(returning: nil)
}
return
}
@@ -407,23 +420,24 @@ public actor LivePhotoValidator {
#if DEBUG
print("[LivePhotoValidator] requestLivePhoto cancelled")
#endif
hasResumed = true
continuation.resume(returning: nil)
if resumeOnce.tryConsume() {
continuation.resume(returning: nil)
}
return
}
hasResumed = true
continuation.resume(returning: livePhoto)
if resumeOnce.tryConsume() {
continuation.resume(returning: livePhoto)
}
}
//
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
guard !hasResumed else { return }
guard resumeOnce.tryConsume() else { return }
#if DEBUG
print("[LivePhotoValidator] requestLivePhoto timeout, requestID: \(requestID)")
#endif
PHLivePhoto.cancelRequest(withRequestID: requestID)
hasResumed = true
continuation.resume(returning: nil)
}
}
@@ -966,6 +980,23 @@ public actor LivePhotoBuilder {
assetWriter.startWriting()
videoReader.startReading()
metadataReader.startReading()
// writer/reader continuation resume
guard assetWriter.status == .writing else {
continuation.resume(throwing: AppError(code: "LPB-301", stage: .writeVideoMetadata, message: "视频处理失败", underlyingErrorDescription: assetWriter.error?.localizedDescription ?? "Writer 启动失败", suggestedActions: ["重试"]))
return
}
guard videoReader.status == .reading else {
assetWriter.cancelWriting()
continuation.resume(throwing: AppError(code: "LPB-301", stage: .writeVideoMetadata, message: "视频处理失败", underlyingErrorDescription: videoReader.error?.localizedDescription ?? "VideoReader 启动失败", suggestedActions: ["重试"]))
return
}
guard metadataReader.status == .reading else {
assetWriter.cancelWriting()
continuation.resume(throwing: AppError(code: "LPB-301", stage: .writeVideoMetadata, message: "视频处理失败", underlyingErrorDescription: metadataReader.error?.localizedDescription ?? "MetadataReader 启动失败", suggestedActions: ["重试"]))
return
}
assetWriter.startSession(atSourceTime: .zero)
var currentFrameCount = 0