修复素材上传 OSS 路径并完善调试与表单交互。

对齐 Android material/{scenicId}/0/ 目录结构以修复视频元数据写入失败,补充 API 请求体调试日志、素材拍摄麦克风权限与上传控件点击穿透修复。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 11:19:58 +08:00
parent 005349f8e6
commit 7deeedec68
5 changed files with 72 additions and 3 deletions

View File

@ -414,6 +414,7 @@
INFOPLIST_KEY_CFBundleDisplayName = "随心瞰商家版"; INFOPLIST_KEY_CFBundleDisplayName = "随心瞰商家版";
INFOPLIST_KEY_NSCameraUsageDescription = "需要访问相机以拍摄任务素材、扫描订单核销二维码,并连接有线相机进行旅拍相册传输"; INFOPLIST_KEY_NSCameraUsageDescription = "需要访问相机以拍摄任务素材、扫描订单核销二维码,并连接有线相机进行旅拍相册传输";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "需要获取您的位置信息,用于位置上报、景区距离排序及多点位核销打卡"; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "需要获取您的位置信息,用于位置上报、景区距离排序及多点位核销打卡";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "需要访问麦克风以在拍摄素材视频时录制声音";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "需要保存收款二维码、排队打卡点小程序码和云盘图片视频到相册"; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "需要保存收款二维码、排队打卡点小程序码和云盘图片视频到相册";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "需要访问相册以选择头像、身份证、银行卡照片和云盘上传素材"; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "需要访问相册以选择头像、身份证、银行卡照片和云盘上传素材";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@ -478,6 +479,7 @@
INFOPLIST_KEY_CFBundleDisplayName = "随心瞰商家版"; INFOPLIST_KEY_CFBundleDisplayName = "随心瞰商家版";
INFOPLIST_KEY_NSCameraUsageDescription = "需要访问相机以拍摄任务素材、扫描订单核销二维码,并连接有线相机进行旅拍相册传输"; INFOPLIST_KEY_NSCameraUsageDescription = "需要访问相机以拍摄任务素材、扫描订单核销二维码,并连接有线相机进行旅拍相册传输";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "需要获取您的位置信息,用于位置上报、景区距离排序及多点位核销打卡"; INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "需要获取您的位置信息,用于位置上报、景区距离排序及多点位核销打卡";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "需要访问麦克风以在拍摄素材视频时录制声音";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "需要保存收款二维码、排队打卡点小程序码和云盘图片视频到相册"; INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "需要保存收款二维码、排队打卡点小程序码和云盘图片视频到相册";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "需要访问相册以选择头像、身份证、银行卡照片和云盘上传素材"; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "需要访问相册以选择头像、身份证、银行卡照片和云盘上传素材";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;

View File

@ -340,12 +340,30 @@ final class APIClient {
} }
} }
/// Debug /// Debug GET POST Android Ktor `LogLevel.BODY`
private func logRequest(_ request: URLRequest) { private func logRequest(_ request: URLRequest) {
#if DEBUG #if DEBUG
let method = request.httpMethod ?? "REQUEST" let method = request.httpMethod ?? "REQUEST"
let url = request.url?.absoluteString ?? "<invalid url>" let url = request.url?.absoluteString ?? "<invalid url>"
print("[API][Request] \(method) \(url)") var lines = ["[API][Request] \(method) \(url)"]
if let queryItems = URLComponents(url: request.url ?? URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: false)?.queryItems,
!queryItems.isEmpty {
let params = queryItems
.map { item in
let value = item.value ?? ""
return "\(item.name)=\(value)"
}
.joined(separator: "&")
lines.append("params: \(params)")
}
let contentType = request.value(forHTTPHeaderField: "Content-Type")
if let body = Self.debugRequestBody(from: request.httpBody, contentType: contentType) {
lines.append("body:\n\(body)")
}
print(lines.joined(separator: "\n"))
#endif #endif
} }
@ -370,6 +388,28 @@ final class APIClient {
} }
#if DEBUG #if DEBUG
/// 便
private static func debugRequestBody(from data: Data?, contentType: String?) -> String? {
guard let data, !data.isEmpty else { return nil }
if let contentType, contentType.lowercased().contains("multipart/form-data") {
return "<multipart/form-data: \(data.count) bytes>"
}
if
let object = try? JSONSerialization.jsonObject(with: data),
let prettyData = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted, .sortedKeys]),
let prettyJSON = String(data: prettyData, encoding: .utf8) {
return prettyJSON
}
if let text = String(data: data, encoding: .utf8), !text.isEmpty {
return text.count > 2_000 ? String(text.prefix(2_000)) + "..." : text
}
return "<non-utf8 body: \(data.count) bytes>"
}
/// 便 /// 便
private static func debugResponseBody(from data: Data) -> String { private static func debugResponseBody(from data: Data) -> String {
guard !data.isEmpty else { return "<empty response>" } guard !data.isEmpty else { return "<empty response>" }

View File

@ -314,7 +314,8 @@ enum OSSUploadPolicy {
case "sample": case "sample":
return "sample/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)" return "sample/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)"
case "material": case "material":
return "material/\(currentDate)/\(scenicId)/\(uploadId)_\(safeName)" // Android folderId = 0
return "material/\(scenicId)/0/\(uploadId)_\(safeName)"
case "punch_point": case "punch_point":
let userId = AppStore.shared.session.userId.trimmingCharacters(in: .whitespacesAndNewlines) let userId = AppStore.shared.session.userId.trimmingCharacters(in: .whitespacesAndNewlines)
let ownerId = userId.isEmpty ? String(scenicId) : userId let ownerId = userId.isEmpty ? String(scenicId) : userId

View File

@ -685,9 +685,12 @@ private final class MaterialUploadThumbView: UIControl {
addCircle.backgroundColor = AppColor.primary addCircle.backgroundColor = AppColor.primary
addCircle.layer.cornerRadius = 14 addCircle.layer.cornerRadius = 14
addIcon.tintColor = .white addIcon.tintColor = .white
addCircle.isUserInteractionEnabled = false
addIcon.isUserInteractionEnabled = false
textLabel.font = .systemFont(ofSize: 12) textLabel.font = .systemFont(ofSize: 12)
textLabel.textColor = UIColor(hex: 0x4B5563) textLabel.textColor = UIColor(hex: 0x4B5563)
textLabel.textAlignment = .center textLabel.textAlignment = .center
textLabel.isUserInteractionEnabled = false
deleteButton.setImage(UIImage(named: "material_ic_clear"), for: .normal) deleteButton.setImage(UIImage(named: "material_ic_clear"), for: .normal)
deleteButton.tintColor = .white deleteButton.tintColor = .white
deleteButton.backgroundColor = UIColor.black.withAlphaComponent(0.6) deleteButton.backgroundColor = UIColor.black.withAlphaComponent(0.6)
@ -835,17 +838,22 @@ private final class MaterialCoverUploadView: UIControl {
clipsToBounds = true clipsToBounds = true
imageView.contentMode = .scaleAspectFill imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true imageView.clipsToBounds = true
imageView.isUserInteractionEnabled = false
addCircle.backgroundColor = AppColor.primary addCircle.backgroundColor = AppColor.primary
addCircle.layer.cornerRadius = 16 addCircle.layer.cornerRadius = 16
addIcon.tintColor = .white addIcon.tintColor = .white
addCircle.isUserInteractionEnabled = false
addIcon.isUserInteractionEnabled = false
titleLabel.text = "点击上传图片" titleLabel.text = "点击上传图片"
titleLabel.font = .systemFont(ofSize: 14) titleLabel.font = .systemFont(ofSize: 14)
titleLabel.textColor = UIColor(hex: 0x4B5563) titleLabel.textColor = UIColor(hex: 0x4B5563)
titleLabel.textAlignment = .center titleLabel.textAlignment = .center
titleLabel.isUserInteractionEnabled = false
hintLabel.text = "建议尺寸: 1920x1080px, 大小不超过5MB" hintLabel.text = "建议尺寸: 1920x1080px, 大小不超过5MB"
hintLabel.font = .systemFont(ofSize: 12) hintLabel.font = .systemFont(ofSize: 12)
hintLabel.textColor = UIColor(hex: 0xB6BECA) hintLabel.textColor = UIColor(hex: 0xB6BECA)
hintLabel.textAlignment = .center hintLabel.textAlignment = .center
hintLabel.isUserInteractionEnabled = false
deleteButton.setImage(UIImage(named: "material_ic_clear"), for: .normal) deleteButton.setImage(UIImage(named: "material_ic_clear"), for: .normal)
deleteButton.tintColor = .white deleteButton.tintColor = .white
deleteButton.backgroundColor = UIColor.black.withAlphaComponent(0.6) deleteButton.backgroundColor = UIColor.black.withAlphaComponent(0.6)

View File

@ -114,6 +114,24 @@ final class MaterialManagementAPITests: XCTestCase {
XCTAssertNil(editBody["project_id"]) XCTAssertNil(editBody["project_id"])
} }
func testMaterialOSSObjectKeyMatchesAndroidFolderLayout() {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
let date = calendar.date(from: DateComponents(year: 2026, month: 7, day: 13))!
let uuid = UUID(uuidString: "11111111-2222-3333-4444-555555555555")!
let key = OSSUploadPolicy.objectKey(
fileName: "video/demo.mp4",
scenicId: 9,
moduleType: "material",
date: date,
uuid: uuid,
timeZone: TimeZone(secondsFromGMT: 0)!
)
XCTAssertEqual(key, "material/9/0/11111111222233334444555555555555_video_demo.mp4")
}
private func envelopeJSON(_ dataJSON: String) -> Data { private func envelopeJSON(_ dataJSON: String) -> Data {
""" """
{"code":100000,"msg":"success","data":\(dataJSON)} {"code":100000,"msg":"success","data":\(dataJSON)}