Files
youle_app_ios/msext/Class/Utils/QiniuManager.m

259 lines
10 KiB
Objective-C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// QiniuManager.m
// msext
//
// Created on June 15, 2025.
//
#import "QiniuManager.h"
#import <QiniuSDK.h>
#import "QiniuConfig.h"
#import <objc/runtime.h>
#import <CommonCrypto/CommonHMAC.h>
#import <CommonCrypto/CommonDigest.h>
#import "FuncPublic.h"
@implementation QiniuManager
+ (instancetype)sharedManager {
static QiniuManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
[instance setupQiniuSDK];
});
return instance;
}
- (void)setupQiniuSDK {
// Qiniu SDK doesn't require explicit initialization
// The configuration will be applied when creating the QNUploadManager instance
}
- (void)uploadAudioFile:(NSString *)filePath
fileName:(NSString *)fileName
progressHandler:(QiniuProgressHandler)progressHandler
completionHandler:(QiniuUploadCompletionHandler)completionHandler {
// 检查文件是否存在
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSError *error = [NSError errorWithDomain:@"QiniuManager" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"上传文件不存在"}];
if (completionHandler) {
completionHandler(nil, error);
}
return;
}
// 创建上传配置
QNConfiguration *config = [QNConfiguration build:^(QNConfigurationBuilder *builder) {
builder.zone = [QNFixedZone zone2]; // 使用华东区域
builder.timeoutInterval = 60; // 超时设置,单位为秒
}];
// 生成上传策略
QNUploadManager *uploadManager = [[QNUploadManager alloc] initWithConfiguration:config];
NSString *token = [self generateUploadToken];
// 文件在七牛云存储中的完整路径
NSString *key = [NSString stringWithFormat:@"%@%@", kQiniuRecordingDirectory, fileName];
// 配置上传选项
QNUploadOption *option = [[QNUploadOption alloc] initWithMime:@"audio/amr"
progressHandler:^(NSString *key, float percent) {
if (progressHandler) {
progressHandler(percent);
}
} params:nil checkCrc:NO cancellationSignal:nil];
// 执行上传
[uploadManager putFile:filePath
key:key
token:token
complete:^(QNResponseInfo *info, NSString *key, NSDictionary *resp) {
if (info.statusCode == 200) {
if (completionHandler) {
completionHandler(key, nil);
}
} else {
NSError *error = [NSError errorWithDomain:@"QiniuManager"
code:info.statusCode
userInfo:@{NSLocalizedDescriptionKey: info.error.localizedDescription ?: @"上传失败"}];
if (completionHandler) {
completionHandler(nil, error);
}
}
}
option:option];
}
- (void)downloadAudioFile:(NSString *)key
progressHandler:(QiniuProgressHandler)progressHandler
completionHandler:(QiniuDownloadCompletionHandler)completionHandler {
// 获取完整的下载URL
NSString *urlString = [self getFileUrlWithKey:key];
NSURL *url = [NSURL URLWithString:urlString];
// 创建下载任务
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
if (completionHandler) {
completionHandler(nil, error);
}
return;
}
// 获取临时文件URL
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode != 200) {
NSError *downloadError = [NSError errorWithDomain:@"QiniuManager"
code:httpResponse.statusCode
userInfo:@{NSLocalizedDescriptionKey: @"下载失败"}];
if (completionHandler) {
completionHandler(nil, downloadError);
}
return;
}
// 从URL中获取文件名
NSString *fileName = [key lastPathComponent];
if ([fileName length] == 0) {
fileName = [NSString stringWithFormat:@"qiniu_download_%@", [[NSUUID UUID] UUIDString]];
}
// 创建目标路径
NSString *cachesDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *downloadDirectory = [cachesDirectory stringByAppendingPathComponent:@"QiniuDownloads"];
// 确保目录存在
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:downloadDirectory]) {
[fileManager createDirectoryAtPath:downloadDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
// 构建最终路径
NSString *destinationPath = [downloadDirectory stringByAppendingPathComponent:fileName];
// 移动文件到目标路径
if ([fileManager fileExistsAtPath:destinationPath]) {
[fileManager removeItemAtPath:destinationPath error:nil];
}
NSError *moveError = nil;
[fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:destinationPath] error:&moveError];
if (moveError) {
if (completionHandler) {
completionHandler(nil, moveError);
}
} else {
if (completionHandler) {
completionHandler(destinationPath, nil);
}
}
}];
// 添加进度追踪
if (progressHandler) {
[downloadTask addObserver:self forKeyPath:@"countOfBytesReceived" options:NSKeyValueObservingOptionNew context:NULL];
objc_setAssociatedObject(downloadTask, "progressHandler", [progressHandler copy], OBJC_ASSOCIATION_COPY);
objc_setAssociatedObject(downloadTask, "totalBytes", @(0), OBJC_ASSOCIATION_RETAIN);
}
// 启动下载任务
[downloadTask resume];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionDownloadTask class]]) {
NSURLSessionDownloadTask *task = (NSURLSessionDownloadTask *)object;
if ([keyPath isEqualToString:@"countOfBytesReceived"]) {
NSNumber *totalBytes = objc_getAssociatedObject(task, "totalBytes");
if ([totalBytes longLongValue] == 0 && task.countOfBytesExpectedToReceive > 0) {
objc_setAssociatedObject(task, "totalBytes", @(task.countOfBytesExpectedToReceive), OBJC_ASSOCIATION_RETAIN);
}
float progress = 0;
if (task.countOfBytesExpectedToReceive > 0) {
progress = (float)task.countOfBytesReceived / (float)task.countOfBytesExpectedToReceive;
}
QiniuProgressHandler progressHandler = objc_getAssociatedObject(task, "progressHandler");
if (progressHandler) {
dispatch_async(dispatch_get_main_queue(), ^{
progressHandler(progress);
});
}
}
}
}
- (NSString *)getFileUrlWithKey:(NSString *)key {
// 域名已在AppDelegate启动时从文件夹读取并赋值给kQiniuDomain
return [NSString stringWithFormat:@"http://%@/%@", kQiniuDomain, key];
}
#pragma mark - Private Methods
- (NSString *)generateUploadToken {
// 构建上传策略(putPolicy)
NSMutableDictionary *policy = [NSMutableDictionary dictionary];
// 指定上传的目标资源空间确保完整格式为bucketName或bucketName:keyPrefix
NSString *scope = [NSString stringWithFormat:@"%@", kQiniuBucketName];
[policy setObject:scope forKey:@"scope"];
// 上传策略的过期时间1小时
NSInteger deadline = (NSInteger)[[NSDate dateWithTimeIntervalSinceNow:3600] timeIntervalSince1970];
[policy setObject:@(deadline) forKey:@"deadline"];
// 将上传策略转换为JSON
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:policy options:0 error:&error];
if (error) {
NSLog(@"生成JSON数据失败: %@", error);
return nil;
}
// Base64编码JSON数据
NSString *encodedPolicy = [self urlsafeBase64EncodeData:jsonData];
// 使用HMAC-SHA1算法进行签名注意签名的内容只是encodedPolicy
NSData *signData = [encodedPolicy dataUsingEncoding:NSUTF8StringEncoding];
NSString *encodedSign = [self hmacSha1:kQiniuSecretKey data:signData];
// 构造上传凭证 - 格式为: accessKey:encodedSign:encodedPolicy
NSString *uploadToken = [NSString stringWithFormat:@"%@:%@:%@", kQiniuAccessKey, encodedSign, encodedPolicy];
// NSLog(@"七牛云配置信息 - AccessKey: %@, BucketName: %@, Domain: %@",
// kQiniuAccessKey, kQiniuBucketName, kQiniuDomain);
// NSLog(@"生成的上传凭证: %@", uploadToken);
return uploadToken;
}
#pragma mark - Utility Methods
- (NSString *)urlsafeBase64EncodeData:(NSData *)data {
NSString *base64 = [data base64EncodedStringWithOptions:0];
base64 = [base64 stringByReplacingOccurrencesOfString:@"+" withString:@"-"];
base64 = [base64 stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
return base64;
}
- (NSString *)hmacSha1:(NSString *)key data:(NSData *)data {
const char *cKey = [key cStringUsingEncoding:NSUTF8StringEncoding];
const char *cData = [data bytes];
unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, [data length], cHMAC);
NSData *hmacData = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
return [self urlsafeBase64EncodeData:hmacData];
}
@end