1
This commit is contained in:
@@ -18,11 +18,21 @@
|
|||||||
|
|
||||||
// 兼容旧命名(如有使用 API_APPLE_LOGIN 的位置,映射到统一命名)
|
// 兼容旧命名(如有使用 API_APPLE_LOGIN 的位置,映射到统一命名)
|
||||||
#define API_APPLE_LOGIN @"/user/appleLogin" // Apple 登录
|
#define API_APPLE_LOGIN @"/user/appleLogin" // Apple 登录
|
||||||
|
#define API_EMAIL_LOGIN @"/user/login" // email 登录
|
||||||
#define API_LOGOUT @"/user/logout" // 退出登录
|
#define API_LOGOUT @"/user/logout" // 退出登录
|
||||||
|
|
||||||
|
#define API_UPDATA_INFO @"/user/updateInfo" // 更新用户
|
||||||
|
|
||||||
#define KB_API_USER_DETAIL @"/user/detail" // 用户详情
|
#define KB_API_USER_DETAIL @"/user/detail" // 用户详情
|
||||||
#define KB_API_CHARACTER_LIST @"/character/list" // 排行榜角色列表(综合)
|
#define KB_API_CHARACTER_LIST @"/character/list" // 排行榜角色列表(综合)
|
||||||
#define KB_API_CHARACTER_LIST_BY_TAG @"/character/listByTag" // 根据 tagId 获取角色列表
|
#define KB_API_CHARACTER_LIST_BY_TAG @"/character/listByTag" // 根据 tagId 获取角色列表
|
||||||
#define KB_API_TAG_LIST @"/tag/list" // 排行榜标签列表
|
#define KB_API_TAG_LIST @"/tag/list" // 排行榜标签列表
|
||||||
|
#define KB_API_FILE_UPLOAD @"/file/upload" // 上传头像
|
||||||
|
#define KB_API_CHARACTER_DETAIL @"/character/detail" // 人设详情
|
||||||
|
#define KB_API_CHARACTER_LISTBYUSER @"/character/listByUser" // 人设详情
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 应用配置
|
// 应用配置
|
||||||
#ifndef KB_API_APP_CONFIG
|
#ifndef KB_API_APP_CONFIG
|
||||||
|
|||||||
@@ -6,7 +6,11 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
typedef NS_ENUM(NSInteger, UserSex) {
|
||||||
|
UserSexMan = 0, // 男
|
||||||
|
UserSexWeman = 1, // 女
|
||||||
|
UserSexTwoSex = 2, // 两性
|
||||||
|
};
|
||||||
@interface KBUser : NSObject
|
@interface KBUser : NSObject
|
||||||
|
|
||||||
// 标识
|
// 标识
|
||||||
@@ -14,12 +18,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
// 基本信息
|
// 基本信息
|
||||||
@property (nonatomic, copy, nullable) NSString *nickName;
|
@property (nonatomic, copy, nullable) NSString *nickName;
|
||||||
@property (nonatomic, copy, nullable) NSString *avatarUrl; // 头像 URL
|
@property (nonatomic, copy, nullable) NSString *avatarUrl; // 头像 URL
|
||||||
@property (nonatomic, copy, nullable) NSString *gender; // 性别(后端可能返回 string/int,统一转字符串存)
|
/// 0
|
||||||
|
@property (nonatomic, assign) UserSex gender; // 性别(后端可能返回 string/int,统一转字符串存)
|
||||||
@property (nonatomic, copy, nullable) NSString *email;
|
@property (nonatomic, copy, nullable) NSString *email;
|
||||||
/// 邮箱是否验证
|
/// 邮箱是否验证
|
||||||
@property (nonatomic, assign) BOOL emailVerified;
|
@property (nonatomic, assign) BOOL emailVerified;
|
||||||
|
|
||||||
// 会话信息
|
// token
|
||||||
@property (nonatomic, copy, nullable) NSString *token; // token/access_token/accessToken
|
@property (nonatomic, copy, nullable) NSString *token; // token/access_token/accessToken
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -10,9 +10,26 @@
|
|||||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||||
return @{
|
return @{
|
||||||
@"userId": @[@"uid"],
|
@"userId": @[@"uid"],
|
||||||
@"gender": @[@"gender", @"sex"],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
+ (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property {
|
||||||
|
if ([property.name isEqualToString:@"gender"]) {
|
||||||
|
if ([oldValue isKindOfClass:[NSNumber class]]) {
|
||||||
|
NSInteger intValue = [(NSNumber *)oldValue integerValue];
|
||||||
|
if (intValue >= UserSexTwoSex && intValue <= UserSexMan) {
|
||||||
|
return @(intValue);
|
||||||
|
} else {
|
||||||
|
// 如果收到非法值,可以返回默认值
|
||||||
|
KBLOG(@"⚠️ 收到非法的userStatus值: %ld", (long)intValue);
|
||||||
|
return @(UserSexMan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @(UserSexMan);
|
||||||
|
}
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ typedef void(^KBLoginCompletion)(BOOL success, NSError * _Nullable error);
|
|||||||
- (void)signInWithAppleFromViewController:(UIViewController *)presenter
|
- (void)signInWithAppleFromViewController:(UIViewController *)presenter
|
||||||
completion:(KBLoginCompletion)completion;
|
completion:(KBLoginCompletion)completion;
|
||||||
|
|
||||||
|
|
||||||
|
/// 邮箱登录
|
||||||
|
- (void)signInWithAppleFromViewController:(UIViewController *)presenter
|
||||||
|
completion:(KBLoginCompletion)completion;
|
||||||
|
|
||||||
|
/// 邮箱登录
|
||||||
|
- (void)emailLoginEmail:(NSString *)email password:(NSString *)password WithCompletion:(KBLoginCompletion)completion;
|
||||||
|
|
||||||
/// 是否已登录:由 KBAuthManager 判断(是否存在有效 token)
|
/// 是否已登录:由 KBAuthManager 判断(是否存在有效 token)
|
||||||
- (BOOL)isLoggedIn;
|
- (BOOL)isLoggedIn;
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,35 @@
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 邮箱登录
|
||||||
|
- (void)emailLoginEmail:(NSString *)email password:(NSString *)password WithCompletion:(KBLoginCompletion)completion;
|
||||||
|
{
|
||||||
|
[KBHUD show];
|
||||||
|
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
||||||
|
if (email.length) params[@"email"] = email;
|
||||||
|
if (password.length) params[@"password"] = password;
|
||||||
|
// 向服务端发起校验
|
||||||
|
[[KBNetworkManager shared] POST:API_EMAIL_LOGIN jsonBody:params headers:nil completion:^(NSDictionary * _Nullable jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||||
|
[KBHUD dismiss];
|
||||||
|
if (error) { if (completion) completion(NO, error); return; }
|
||||||
|
NSDictionary *dict = jsonOrData[@"data"];
|
||||||
|
KBUser *user = [KBUser mj_objectWithKeyValues:dict];
|
||||||
|
self.currentUser = user;
|
||||||
|
if (user.token.length == 0) {
|
||||||
|
if (completion) completion(NO, [NSError errorWithDomain:@"KBLogin" code:-2 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"No token returned")}]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[[KBUserSessionManager shared] handleLoginSuccessWithUser:user];
|
||||||
|
completion(true,nil);
|
||||||
|
// 保存登录态到共享钥匙串;供 App 与扩展共享
|
||||||
|
// BOOL ok = [[KBAuthManager shared] saveAccessToken:user.token
|
||||||
|
// refreshToken:nil
|
||||||
|
// expiryDate:nil
|
||||||
|
// userIdentifier:cred.user];
|
||||||
|
// if (completion) completion(ok, ok ? nil : [NSError errorWithDomain:@"KBLogin" code:-3 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Failed to save login state")}]);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Helpers
|
#pragma mark - Helpers
|
||||||
|
|
||||||
// 宽松解析:token / access_token / accessToken,支持顶层或 data/user 层
|
// 宽松解析:token / access_token / accessToken,支持顶层或 data/user 层
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#import "UICollectionViewLeftAlignedLayout.h"
|
#import "UICollectionViewLeftAlignedLayout.h"
|
||||||
#import "KBMyKeyboardCell.h"
|
#import "KBMyKeyboardCell.h"
|
||||||
#import "KBAlert.h"
|
#import "KBAlert.h"
|
||||||
|
#import "KBMyVM.h"
|
||||||
|
|
||||||
/// 复用标识
|
/// 复用标识
|
||||||
static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
|
static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
|
||||||
@@ -29,6 +30,7 @@ static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
|
|||||||
|
|
||||||
// 数据源(必须是二维数组,库内部会在拖动时直接调整顺序)
|
// 数据源(必须是二维数组,库内部会在拖动时直接调整顺序)
|
||||||
@property (nonatomic, strong) NSMutableArray<NSMutableArray<NSDictionary *> *> *dataSourceArray; // {emoji,title}
|
@property (nonatomic, strong) NSMutableArray<NSMutableArray<NSDictionary *> *> *dataSourceArray; // {emoji,title}
|
||||||
|
@property (nonatomic, strong) KBMyVM *viewModel; // 我的页面 VM
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
|
|||||||
|
|
||||||
- (void)viewDidLoad {
|
- (void)viewDidLoad {
|
||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
self.viewModel = [[KBMyVM alloc] init];
|
||||||
self.view.backgroundColor = [UIColor colorWithHex:0xF6F8F9];
|
self.view.backgroundColor = [UIColor colorWithHex:0xF6F8F9];
|
||||||
self.kb_navView.backgroundColor = [UIColor clearColor];
|
self.kb_navView.backgroundColor = [UIColor clearColor];
|
||||||
self.kb_titleLabel.text = @"My KeyBoard";
|
self.kb_titleLabel.text = @"My KeyBoard";
|
||||||
@@ -71,7 +74,11 @@ static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
// 初始数据
|
// 初始数据
|
||||||
[self buildDefaultData];
|
// [self buildDefaultData];
|
||||||
|
__weak typeof(self) weakSelf = self;
|
||||||
|
[self.viewModel fetchCharacterListByUserWithCompletion:^(NSArray<KBCharacter *> * _Nonnull characterArray, NSError * _Nullable error) {
|
||||||
|
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated {
|
- (void)viewWillAppear:(BOOL)animated {
|
||||||
|
|||||||
@@ -326,17 +326,26 @@
|
|||||||
|
|
||||||
- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results API_AVAILABLE(ios(14.0)) {
|
- (void)picker:(PHPickerViewController *)picker didFinishPicking:(NSArray<PHPickerResult *> *)results API_AVAILABLE(ios(14.0)) {
|
||||||
[picker dismissViewControllerAnimated:YES completion:nil];
|
[picker dismissViewControllerAnimated:YES completion:nil];
|
||||||
PHPickerResult *first = results.firstObject; if (!first) return;
|
|
||||||
|
PHPickerResult *first = results.firstObject;
|
||||||
|
if (!first) return;
|
||||||
|
|
||||||
NSItemProvider *p = first.itemProvider;
|
NSItemProvider *p = first.itemProvider;
|
||||||
if ([p canLoadObjectOfClass:UIImage.class]) {
|
if ([p canLoadObjectOfClass:UIImage.class]) {
|
||||||
__weak typeof(self) weakSelf = self;
|
__weak typeof(self) weakSelf = self;
|
||||||
[p loadObjectOfClass:UIImage.class completionHandler:^(__kindof id<NSItemProviderReading> _Nullable object, NSError * _Nullable error) {
|
[p loadObjectOfClass:UIImage.class completionHandler:^(__kindof id<NSItemProviderReading> _Nullable object, NSError * _Nullable error) {
|
||||||
UIImage *img = ([object isKindOfClass:UIImage.class] ? (UIImage *)object : nil);
|
UIImage *img = ([object isKindOfClass:UIImage.class] ? (UIImage *)object : nil);
|
||||||
if (!img) return;
|
if (!img) return;
|
||||||
|
|
||||||
|
// 这里就在子线程里压缩,避免卡 UI
|
||||||
|
// 比如目标 100KB,你自己改成想要的值
|
||||||
|
NSUInteger targetKB = 50;
|
||||||
|
NSData *compressedData = [weakSelf kb_compressImage:img targetKB:targetKB];
|
||||||
|
if (!compressedData) return;
|
||||||
|
UIImage *compressedImage = [UIImage imageWithData:compressedData];
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
UIImage *compressed = [weakSelf kb_compressImage:img maxPixel:512 quality:0.85];
|
|
||||||
weakSelf.avatarView.image = compressed;
|
|
||||||
weakSelf.avatarJPEGData = UIImageJPEGRepresentation(compressed, 0.85);
|
|
||||||
});
|
});
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -378,6 +387,95 @@
|
|||||||
UIImage *result = [UIImage imageWithData:jpeg] ?: scaled ?: image;
|
UIImage *result = [UIImage imageWithData:jpeg] ?: scaled ?: image;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
/// 按目标大小(KB)压缩图片:
|
||||||
|
/// 1. 先逐步降低 JPEG 质量;
|
||||||
|
/// 2. 如果质量已经降到下限仍然太大,再按比例缩小尺寸,并重复尝试。
|
||||||
|
- (nullable NSData *)kb_compressImage:(UIImage *)image targetKB:(NSUInteger)targetKB {
|
||||||
|
if (!image || targetKB == 0) { return nil; }
|
||||||
|
|
||||||
|
NSUInteger maxBytes = targetKB * 1024;
|
||||||
|
|
||||||
|
// 初始质量参数
|
||||||
|
CGFloat compression = 0.9f;
|
||||||
|
CGFloat minCompression = 0.1f; // 不建议再低了,太低会糊
|
||||||
|
NSData *data = UIImageJPEGRepresentation(image, compression);
|
||||||
|
if (!data) return nil;
|
||||||
|
|
||||||
|
// 如果一开始就小于目标大小,直接返回
|
||||||
|
if (data.length <= maxBytes) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) 先通过降低质量来压缩
|
||||||
|
while (data.length > maxBytes && compression > minCompression + 0.01f) {
|
||||||
|
compression -= 0.1f;
|
||||||
|
data = UIImageJPEGRepresentation(image, compression);
|
||||||
|
if (!data) return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length <= maxBytes) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 质量降到下限还是太大 -> 等比缩放尺寸
|
||||||
|
UIImage *currentImage = image;
|
||||||
|
|
||||||
|
// 防止死循环,限定最多缩放几次
|
||||||
|
NSInteger maxResizeCount = 6;
|
||||||
|
NSInteger resizeCount = 0;
|
||||||
|
|
||||||
|
while (data.length > maxBytes && resizeCount < maxResizeCount) {
|
||||||
|
resizeCount++;
|
||||||
|
|
||||||
|
// 按面积比例来算缩放因子:新的面积约等于 (maxBytes / 当前字节数) * 原面积
|
||||||
|
CGFloat ratio = (CGFloat)maxBytes / (CGFloat)data.length;
|
||||||
|
// 为了更激进一点,可以乘个经验系数,比如 0.8
|
||||||
|
ratio *= 0.8f;
|
||||||
|
if (ratio <= 0.0f) {
|
||||||
|
ratio = 0.5f; // 兜底,至少缩小一半
|
||||||
|
}
|
||||||
|
|
||||||
|
CGFloat scale = sqrt(ratio);
|
||||||
|
if (scale >= 1.0f) {
|
||||||
|
scale = 0.5f; // 理论上不会走到这里,兜底
|
||||||
|
}
|
||||||
|
|
||||||
|
CGSize newSize = CGSizeMake(currentImage.size.width * scale,
|
||||||
|
currentImage.size.height * scale);
|
||||||
|
if (newSize.width < 10 || newSize.height < 10) {
|
||||||
|
// 太小就没意义了,直接 break,返回目前能做到的最小值
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制缩小后的图片
|
||||||
|
UIGraphicsBeginImageContextWithOptions(newSize, NO, currentImage.scale);
|
||||||
|
[currentImage drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
|
||||||
|
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||||
|
UIGraphicsEndImageContext();
|
||||||
|
|
||||||
|
if (!resizedImage) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentImage = resizedImage;
|
||||||
|
|
||||||
|
// 每次尺寸缩小之后,再重新从稍高一点的质量开始压一轮
|
||||||
|
compression = 0.9f;
|
||||||
|
data = UIImageJPEGRepresentation(currentImage, compression);
|
||||||
|
if (!data) return nil;
|
||||||
|
|
||||||
|
// 再次在质量范围内往下压
|
||||||
|
while (data.length > maxBytes && compression > minCompression + 0.01f) {
|
||||||
|
compression -= 0.1f;
|
||||||
|
data = UIImageJPEGRepresentation(currentImage, compression);
|
||||||
|
if (!data) return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终返回当前能做到的最小数据(可能略大于目标,但已经尽力)
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
- (KBMyVM *)myVM{
|
- (KBMyVM *)myVM{
|
||||||
if (!_myVM) {
|
if (!_myVM) {
|
||||||
|
|||||||
@@ -6,18 +6,31 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "KBCharacter.h"
|
||||||
@class KBUser;
|
@class KBUser;
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
typedef void(^KBMyUserDetailCompletion)(KBUser *_Nullable user, NSError *_Nullable error);
|
typedef void(^KBMyUserDetailCompletion)(KBUser *_Nullable user, NSError *_Nullable error);
|
||||||
|
typedef void(^KBCharacterListCompletion)(NSArray<KBCharacter *> *characterArray, NSError *_Nullable error);
|
||||||
|
typedef void(^KBUpLoadAvatarCompletion)(BOOL success, NSError * _Nullable error);
|
||||||
|
typedef void(^KBUpdateUserInfoCompletion)(BOOL success, NSError * _Nullable error);
|
||||||
|
|
||||||
@interface KBMyVM : NSObject
|
@interface KBMyVM : NSObject
|
||||||
|
|
||||||
/// 获取当前用户详情(/user/detail)
|
/// 获取当前用户详情(/user/detail)
|
||||||
- (void)fetchUserDetailWithCompletion:(KBMyUserDetailCompletion)completion;
|
- (void)fetchUserDetailWithCompletion:(KBMyUserDetailCompletion)completion;
|
||||||
|
|
||||||
|
/// 用户人设列表(/character/listByUser)
|
||||||
|
- (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion;
|
||||||
|
|
||||||
|
/// 上传头像
|
||||||
|
- (void)upLoadAvatarWithData:(NSData *)avatarData completion:(KBUpLoadAvatarCompletion)completion;
|
||||||
|
|
||||||
|
/// 更新用户信息
|
||||||
|
- (void)updateUserInfo:(KBUser *)user completion:(KBUpdateUserInfoCompletion)completion;
|
||||||
|
|
||||||
|
|
||||||
/// 退出登录
|
/// 退出登录
|
||||||
- (void)logout;
|
- (void)logout;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -42,6 +42,72 @@
|
|||||||
if (completion) completion(user, nil);
|
if (completion) completion(user, nil);
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)fetchCharacterListByUserWithCompletion:(KBCharacterListCompletion)completion{
|
||||||
|
[[KBNetworkManager shared] GET:KB_API_CHARACTER_LISTBYUSER
|
||||||
|
parameters:nil
|
||||||
|
headers:nil
|
||||||
|
autoShowBusinessError:NO
|
||||||
|
completion:^(NSDictionary *jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||||
|
[KBHUD dismiss];
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
NSString *msg = KBBizMessageFromJSONObject(jsonOrData) ?: error.localizedDescription ?: KBLocalized(@"Network error");
|
||||||
|
[KBHUD showInfo:msg];
|
||||||
|
if (completion) completion([NSArray new], error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
id dataObj = jsonOrData[KBData] ?: jsonOrData[@"data"];
|
||||||
|
if (![dataObj isKindOfClass:[NSArray class]]) {
|
||||||
|
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
|
||||||
|
code:KBNetworkErrorInvalidResponse
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid response")}];
|
||||||
|
[KBHUD showInfo:e.localizedDescription];
|
||||||
|
if (completion) completion([NSArray new], e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSArray<KBCharacter *> *list = [KBCharacter mj_objectArrayWithKeyValuesArray:(NSArray *)dataObj];
|
||||||
|
if (completion) completion(list, nil);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 上传头像
|
||||||
|
- (void)upLoadAvatarWithData:(NSData *)avatarData completion:(KBUpLoadAvatarCompletion)completion{
|
||||||
|
[KBHUD show];
|
||||||
|
[[KBNetworkManager shared] uploadFile:KB_API_FILE_UPLOAD
|
||||||
|
fileData:avatarData
|
||||||
|
fileName:@"avatar.jpg"
|
||||||
|
mimeType:@"image/jpeg"
|
||||||
|
headers:nil
|
||||||
|
completion:^(NSDictionary * _Nullable json,
|
||||||
|
NSURLResponse * _Nullable response,
|
||||||
|
NSError * _Nullable error) {
|
||||||
|
[KBHUD dismiss];
|
||||||
|
if (error) {
|
||||||
|
NSLog(@"上传失败: %@", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSString *avImageString = json[@"data"];
|
||||||
|
// [weakSelf.avatarView kb_setImageURL:[NSURL URLWithString:avImageString] placeholder:KBPlaceholderImage];
|
||||||
|
|
||||||
|
NSLog(@"上传成功: %@", json);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// 更新用户信息
|
||||||
|
- (void)updateUserInfo:(KBUser *)user completion:(KBUpdateUserInfoCompletion)completion{
|
||||||
|
/// 获取用户信息
|
||||||
|
KBUser *localUser = [KBUserSessionManager shared].currentUser;
|
||||||
|
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
||||||
|
if (localUser.userId.length) params[@"uid"] = localUser.userId;
|
||||||
|
if (localUser.nickName.length) params[@"nickName"] = localUser.nickName;
|
||||||
|
params[@"gender"] = (NSInteger)localUser.gender;
|
||||||
|
if (localUser.avatarUrl.length) params[@"avatarUrl"] = localUser.avatarUrl;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
- (void)logout{
|
- (void)logout{
|
||||||
[KBHUD show];
|
[KBHUD show];
|
||||||
[[KBNetworkManager shared] GET:API_LOGOUT
|
[[KBNetworkManager shared] GET:API_LOGOUT
|
||||||
|
|||||||
@@ -79,6 +79,28 @@ typedef void(^KBNetworkDataCompletion)(NSData *_Nullable data,
|
|||||||
headers:(nullable NSDictionary<NSString *, NSString *> *)headers
|
headers:(nullable NSDictionary<NSString *, NSString *> *)headers
|
||||||
completion:(KBNetworkCompletion)completion;
|
completion:(KBNetworkCompletion)completion;
|
||||||
|
|
||||||
|
/// 上传文件(multipart/form-data,表单字段名固定为 "file")
|
||||||
|
/// path: 例如 @"file/upload" 或 @"/file/upload"
|
||||||
|
/// fileData: 文件二进制数据(比如 UIImageJPEGRepresentation)
|
||||||
|
/// fileName: 例如 @"avatar.jpg"
|
||||||
|
/// mimeType: 例如 @"image/jpeg"
|
||||||
|
- (nullable NSURLSessionDataTask *)uploadFile:(NSString *)path
|
||||||
|
fileData:(NSData *)fileData
|
||||||
|
fileName:(NSString *)fileName
|
||||||
|
mimeType:(NSString *)mimeType
|
||||||
|
headers:(nullable NSDictionary<NSString *, NSString *> *)headers
|
||||||
|
autoShowBusinessError:(BOOL)autoShowBusinessError
|
||||||
|
completion:(KBNetworkCompletion)completion;
|
||||||
|
|
||||||
|
/// 便捷版本:默认 autoShowBusinessError = YES
|
||||||
|
- (nullable NSURLSessionDataTask *)uploadFile:(NSString *)path
|
||||||
|
fileData:(NSData *)fileData
|
||||||
|
fileName:(NSString *)fileName
|
||||||
|
mimeType:(NSString *)mimeType
|
||||||
|
headers:(nullable NSDictionary<NSString *, NSString *> *)headers
|
||||||
|
completion:(KBNetworkCompletion)completion;
|
||||||
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@@ -186,6 +186,98 @@ autoShowBusinessError:YES
|
|||||||
return [self startDataTaskWithRequest:req completion:completion];
|
return [self startDataTaskWithRequest:req completion:completion];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Upload (multipart/form-data)
|
||||||
|
|
||||||
|
- (NSURLSessionDataTask *)uploadFile:(NSString *)path
|
||||||
|
fileData:(NSData *)fileData
|
||||||
|
fileName:(NSString *)fileName
|
||||||
|
mimeType:(NSString *)mimeType
|
||||||
|
headers:(NSDictionary<NSString *,NSString *> *)headers
|
||||||
|
autoShowBusinessError:(BOOL)autoShowBusinessError
|
||||||
|
completion:(KBNetworkCompletion)completion
|
||||||
|
{
|
||||||
|
NSLog(@"[KBNetworkManager] UPLOAD called, enabled=%d, path=%@", self.isEnabled, path);
|
||||||
|
if (![self ensureEnabled:completion]) return nil;
|
||||||
|
|
||||||
|
NSString *urlString = [self buildURLStringWithPath:path];
|
||||||
|
if (!urlString) {
|
||||||
|
[self fail:KBNetworkErrorInvalidURL completion:completion];
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileData || fileData.length == 0) {
|
||||||
|
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
|
||||||
|
code:KBNetworkErrorInvalidResponse
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Empty file data")}];
|
||||||
|
if (completion) completion(nil, nil, e);
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
|
||||||
|
serializer.timeoutInterval = self.timeout;
|
||||||
|
|
||||||
|
NSError *error = nil;
|
||||||
|
NSMutableURLRequest *req =
|
||||||
|
[serializer multipartFormRequestWithMethod:@"POST"
|
||||||
|
URLString:urlString
|
||||||
|
parameters:nil
|
||||||
|
constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
|
||||||
|
|
||||||
|
// Apifox 后端要求:表单字段名为 "file"
|
||||||
|
NSString *fieldName = @"file";
|
||||||
|
NSString *fname = fileName ?: @"file";
|
||||||
|
NSString *type = mimeType ?: @"application/octet-stream";
|
||||||
|
|
||||||
|
[formData appendPartWithFileData:fileData
|
||||||
|
name:fieldName
|
||||||
|
fileName:fname
|
||||||
|
mimeType:type];
|
||||||
|
} error:&error];
|
||||||
|
|
||||||
|
if (error || !req) {
|
||||||
|
if (completion) {
|
||||||
|
completion(nil, nil,
|
||||||
|
error ?: [NSError errorWithDomain:KBNetworkErrorDomain
|
||||||
|
code:KBNetworkErrorInvalidURL
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid upload request")}]);
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不强行指定 Content-Type,留给 AFN 里带 boundary 的那一套
|
||||||
|
[self applyHeaders:headers toMutableRequest:req contentType:nil];
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
KBLOG(@"HTTP UPLOAD (multipart)\nURL: %@\nHeaders: %@\nfileName: %@\nlength: %lu",
|
||||||
|
req.URL.absoluteString,
|
||||||
|
req.allHTTPHeaderFields ?: @{},
|
||||||
|
fileName,
|
||||||
|
(unsigned long)fileData.length);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 按 JSON 响应处理(和 POST 一样:解析 {code,message,data,...})
|
||||||
|
return [self startJSONTaskWithRequest:req
|
||||||
|
autoShowBusinessError:autoShowBusinessError
|
||||||
|
completion:completion];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSURLSessionDataTask *)uploadFile:(NSString *)path
|
||||||
|
fileData:(NSData *)fileData
|
||||||
|
fileName:(NSString *)fileName
|
||||||
|
mimeType:(NSString *)mimeType
|
||||||
|
headers:(NSDictionary<NSString *,NSString *> *)headers
|
||||||
|
completion:(KBNetworkCompletion)completion
|
||||||
|
{
|
||||||
|
return [self uploadFile:path
|
||||||
|
fileData:fileData
|
||||||
|
fileName:fileName
|
||||||
|
mimeType:mimeType
|
||||||
|
headers:headers
|
||||||
|
autoShowBusinessError:YES
|
||||||
|
completion:completion];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Core
|
#pragma mark - Core
|
||||||
|
|
||||||
- (BOOL)ensureEnabled:(KBNetworkCompletion)completion {
|
- (BOOL)ensureEnabled:(KBNetworkCompletion)completion {
|
||||||
|
|||||||
Reference in New Issue
Block a user