实现我的 Tab、对齐 Android 流程并接入 OSS 上传

This commit is contained in:
2026-07-06 16:01:05 +08:00
parent caf737b09b
commit b7bc62e94b
36 changed files with 4077 additions and 57 deletions
@@ -0,0 +1,139 @@
//
// 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))
}
}
}