Add personal info page, account switch, real-name auth, withdrawal settings, session cache extensions, AlibabaCloudOSS SPM, and unit tests. Co-authored-by: Cursor <cursoragent@cursor.com>
140 lines
4.7 KiB
Swift
140 lines
4.7 KiB
Swift
//
|
||
// UploadImageProcessors.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import Foundation
|
||
import UIKit
|
||
import UniformTypeIdentifiers
|
||
|
||
/// 头像图片处理器,负责压缩和规范化用户选择的头像图片。
|
||
enum AvatarImageProcessor {
|
||
static let maxPixelLength: CGFloat = 1024
|
||
static let jpegCompressionQuality: CGFloat = 0.82
|
||
|
||
/// 处理后的上传图片实体,包含数据、文件名和内容类型。
|
||
struct ProcessedImage: Equatable {
|
||
let data: Data
|
||
let fileName: String
|
||
let contentType: UTType
|
||
}
|
||
|
||
/// 将原始图片数据转换为适合头像上传的 JPEG 数据。
|
||
static func process(data: Data, timestamp: TimeInterval = Date().timeIntervalSince1970) throws -> ProcessedImage {
|
||
try UploadImageRenderer.process(
|
||
data: data,
|
||
fileNamePrefix: "avatar",
|
||
maxPixelLength: maxPixelLength,
|
||
jpegCompressionQuality: jpegCompressionQuality,
|
||
timestamp: timestamp
|
||
)
|
||
}
|
||
}
|
||
|
||
/// 实名证件图片处理器,负责压缩和规范化身份证正反面图片。
|
||
enum RealNameImageProcessor {
|
||
static let maxPixelLength: CGFloat = 1800
|
||
static let jpegCompressionQuality: CGFloat = 0.88
|
||
|
||
/// 将原始图片数据转换为适合实名认证上传的 JPEG 数据。
|
||
static func process(data: Data, side: RealNameImageSide, timestamp: TimeInterval = Date().timeIntervalSince1970) throws -> AvatarImageProcessor.ProcessedImage {
|
||
try UploadImageRenderer.process(
|
||
data: data,
|
||
fileNamePrefix: "real_name_\(side.rawValue)",
|
||
maxPixelLength: maxPixelLength,
|
||
jpegCompressionQuality: jpegCompressionQuality,
|
||
timestamp: timestamp
|
||
)
|
||
}
|
||
}
|
||
|
||
/// 银行卡图片处理器,负责压缩银行卡正反面图片。
|
||
enum BankCardImageProcessor {
|
||
static let maxPixelLength: CGFloat = 1800
|
||
static let jpegCompressionQuality: CGFloat = 0.88
|
||
|
||
static func process(data: Data, side: BankCardImageSide, timestamp: TimeInterval = Date().timeIntervalSince1970) throws -> AvatarImageProcessor.ProcessedImage {
|
||
try UploadImageRenderer.process(
|
||
data: data,
|
||
fileNamePrefix: "bank_card_\(side.rawValue)",
|
||
maxPixelLength: maxPixelLength,
|
||
jpegCompressionQuality: jpegCompressionQuality,
|
||
timestamp: timestamp
|
||
)
|
||
}
|
||
}
|
||
|
||
/// 实名认证证件图片面向实体,区分人像面和国徽面。
|
||
enum RealNameImageSide: String {
|
||
case front
|
||
case back
|
||
}
|
||
|
||
/// 银行卡图片面向实体。
|
||
enum BankCardImageSide: String {
|
||
case front
|
||
case back
|
||
}
|
||
|
||
/// 上传图片处理错误实体,表示图片无效或编码失败。
|
||
enum UploadImageProcessingError: LocalizedError, Equatable {
|
||
case invalidImage
|
||
case encodingFailed
|
||
|
||
var errorDescription: String? {
|
||
switch self {
|
||
case .invalidImage:
|
||
"请选择有效的图片"
|
||
case .encodingFailed:
|
||
"图片处理失败,请重新选择"
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 上传图片渲染器,提供通用图片缩放、铺底和 JPEG 编码能力。
|
||
private enum UploadImageRenderer {
|
||
static func process(
|
||
data: Data,
|
||
fileNamePrefix: String,
|
||
maxPixelLength: CGFloat,
|
||
jpegCompressionQuality: CGFloat,
|
||
timestamp: TimeInterval
|
||
) throws -> AvatarImageProcessor.ProcessedImage {
|
||
guard let image = UIImage(data: data), image.size.width > 0, image.size.height > 0 else {
|
||
throw UploadImageProcessingError.invalidImage
|
||
}
|
||
|
||
let normalized = image.normalizedForUpload(maxPixelLength: maxPixelLength)
|
||
guard let jpegData = normalized.jpegData(compressionQuality: jpegCompressionQuality), !jpegData.isEmpty else {
|
||
throw UploadImageProcessingError.encodingFailed
|
||
}
|
||
|
||
return AvatarImageProcessor.ProcessedImage(
|
||
data: jpegData,
|
||
fileName: "\(fileNamePrefix)_\(Int(timestamp)).jpg",
|
||
contentType: .jpeg
|
||
)
|
||
}
|
||
}
|
||
|
||
private extension UIImage {
|
||
func normalizedForUpload(maxPixelLength: CGFloat) -> UIImage {
|
||
let longestSide = max(size.width, size.height)
|
||
let scale = min(1, maxPixelLength / longestSide)
|
||
let targetSize = CGSize(
|
||
width: max(1, (size.width * scale).rounded()),
|
||
height: max(1, (size.height * scale).rounded())
|
||
)
|
||
|
||
let format = UIGraphicsImageRendererFormat.default()
|
||
format.scale = 1
|
||
format.opaque = true
|
||
let renderer = UIGraphicsImageRenderer(size: targetSize, format: format)
|
||
return renderer.image { context in
|
||
UIColor.white.setFill()
|
||
context.fill(CGRect(origin: .zero, size: targetSize))
|
||
draw(in: CGRect(origin: .zero, size: targetSize))
|
||
}
|
||
}
|
||
}
|