1
This commit is contained in:
62
Shared/KBBizCode.h
Normal file
62
Shared/KBBizCode.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// KBBizCode.h
|
||||||
|
// 服务端业务状态码与通用解析工具
|
||||||
|
//
|
||||||
|
// 说明:
|
||||||
|
// - 将所有常用的业务 code 集中到这里,避免在项目里到处写裸数字;
|
||||||
|
// - 实际数值请按照你们后端的约定修改;
|
||||||
|
// - 若后端新增/调整 code,只需要维护这一处即可。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef KBBizCode_h
|
||||||
|
#define KBBizCode_h
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "KBAPI.h" // 引入 SUCCESS_CODE/ERROR_CODE 等基础定义
|
||||||
|
|
||||||
|
/// 服务端业务状态码(按项目实际调整)
|
||||||
|
typedef NS_ENUM(NSInteger, KBBizCode) {
|
||||||
|
/// 通用成功(通常为 200,对应 SUCCESS_CODE)
|
||||||
|
KBBizCodeSuccess = SUCCESS_CODE,
|
||||||
|
|
||||||
|
/// token 失效/未登录(示例值;请按后端实际 code 修改)
|
||||||
|
KBBizCodeTokenInvalid = 40101,
|
||||||
|
|
||||||
|
/// token 过期(可选;如无区分可与 KBBizCodeTokenInvalid 复用)
|
||||||
|
KBBizCodeTokenExpired = 40102,
|
||||||
|
|
||||||
|
/// 账号在其他设备登录,被服务端强制下线
|
||||||
|
KBBizCodeAccountKicked = 40103,
|
||||||
|
};
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
/// 从 JSON 响应中提取业务 code。
|
||||||
|
/// 支持常见字段:code / status / retcode(数字或字符串)。
|
||||||
|
/// 若未找到返回 NSNotFound。
|
||||||
|
static inline NSInteger KBBizCodeFromJSONObject(id obj) {
|
||||||
|
if (![obj isKindOfClass:NSDictionary.class]) return NSNotFound;
|
||||||
|
NSDictionary *d = (NSDictionary *)obj;
|
||||||
|
id code = d[@"code"] ?: d[@"status"] ?: d[@"retcode"];
|
||||||
|
if ([code isKindOfClass:NSNumber.class]) {
|
||||||
|
return ((NSNumber *)code).integerValue;
|
||||||
|
}
|
||||||
|
if ([code isKindOfClass:NSString.class]) {
|
||||||
|
return ((NSString *)code).integerValue;
|
||||||
|
}
|
||||||
|
return NSNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从 JSON 响应中提取错误文案,支持 message/msg/error。
|
||||||
|
static inline NSString *KBBizMessageFromJSONObject(id obj) {
|
||||||
|
if (![obj isKindOfClass:NSDictionary.class]) return nil;
|
||||||
|
NSDictionary *d = (NSDictionary *)obj;
|
||||||
|
NSString *msg = d[@"message"] ?: d[@"msg"] ?: d[@"error"];
|
||||||
|
if (![msg isKindOfClass:NSString.class]) return nil;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
#endif /* KBBizCode_h */
|
||||||
|
|
||||||
@@ -363,6 +363,7 @@
|
|||||||
04890A032EC0BBBB00FABA60 /* KBCategoryTitleImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBCategoryTitleImageView.m; sourceTree = "<group>"; };
|
04890A032EC0BBBB00FABA60 /* KBCategoryTitleImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBCategoryTitleImageView.m; sourceTree = "<group>"; };
|
||||||
04890B102EC2F00000FABA60 /* KBMyHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyHeaderView.h; sourceTree = "<group>"; };
|
04890B102EC2F00000FABA60 /* KBMyHeaderView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyHeaderView.h; sourceTree = "<group>"; };
|
||||||
04890B112EC2F00000FABA60 /* KBMyHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyHeaderView.m; sourceTree = "<group>"; };
|
04890B112EC2F00000FABA60 /* KBMyHeaderView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyHeaderView.m; sourceTree = "<group>"; };
|
||||||
|
0498BD5E2EDF2157006CC1D5 /* KBBizCode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBizCode.h; sourceTree = "<group>"; };
|
||||||
049FB2092EC1C13800FAB05D /* KBSkinBottomActionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinBottomActionView.h; sourceTree = "<group>"; };
|
049FB2092EC1C13800FAB05D /* KBSkinBottomActionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinBottomActionView.h; sourceTree = "<group>"; };
|
||||||
049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinBottomActionView.m; sourceTree = "<group>"; };
|
049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinBottomActionView.m; sourceTree = "<group>"; };
|
||||||
049FB20C2EC1CD2800FAB05D /* KBAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAlert.h; sourceTree = "<group>"; };
|
049FB20C2EC1CD2800FAB05D /* KBAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAlert.h; sourceTree = "<group>"; };
|
||||||
@@ -1356,6 +1357,7 @@
|
|||||||
04D1F6B12EDFF10A00B12345 /* KBSkinInstallBridge.m */,
|
04D1F6B12EDFF10A00B12345 /* KBSkinInstallBridge.m */,
|
||||||
04791F8C2ED469C0004E8522 /* KBHostAppLauncher.h */,
|
04791F8C2ED469C0004E8522 /* KBHostAppLauncher.h */,
|
||||||
04791F8D2ED469C0004E8522 /* KBHostAppLauncher.m */,
|
04791F8D2ED469C0004E8522 /* KBHostAppLauncher.m */,
|
||||||
|
0498BD5E2EDF2157006CC1D5 /* KBBizCode.h */,
|
||||||
);
|
);
|
||||||
path = Shared;
|
path = Shared;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ typedef NS_ERROR_ENUM(KBNetworkErrorDomain, KBNetworkError) {
|
|||||||
KBNetworkErrorInvalidURL = 2,
|
KBNetworkErrorInvalidURL = 2,
|
||||||
KBNetworkErrorInvalidResponse = 3,
|
KBNetworkErrorInvalidResponse = 3,
|
||||||
KBNetworkErrorDecodeFailed = 4,
|
KBNetworkErrorDecodeFailed = 4,
|
||||||
|
KBNetworkErrorBusiness = 5, // 服务端返回非成功业务 code
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 简单的 JSON 回调:json 为 NSDictionary/NSArray 或者在非 JSON 情况下返回 NSData
|
/// 简单的 JSON 回调:json 为 NSDictionary/NSArray 或者在非 JSON 情况下返回 NSData
|
||||||
|
|||||||
@@ -4,8 +4,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "KBNetworkManager.h"
|
#import "KBNetworkManager.h"
|
||||||
|
#import <TargetConditionals.h>
|
||||||
#import "AFNetworking.h"
|
#import "AFNetworking.h"
|
||||||
#import "KBAuthManager.h"
|
#import "KBAuthManager.h"
|
||||||
|
#import "KBBizCode.h"
|
||||||
|
// 仅在主 App 内需要的会话管理与 HUD 组件
|
||||||
|
#import "KBUserSessionManager.h"
|
||||||
|
#import "KBHUD.h"
|
||||||
|
|
||||||
NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
||||||
|
|
||||||
@@ -209,19 +214,37 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
|||||||
NSString *pretty = [self kb_prettyJSONStringFromObject:json] ?: @"(null)";
|
NSString *pretty = [self kb_prettyJSONStringFromObject:json] ?: @"(null)";
|
||||||
pretty = [self kb_trimmedString:pretty maxLength:4096];
|
pretty = [self kb_trimmedString:pretty maxLength:4096];
|
||||||
NSInteger status = [response isKindOfClass:NSHTTPURLResponse.class] ? ((NSHTTPURLResponse *)response).statusCode : 0;
|
NSInteger status = [response isKindOfClass:NSHTTPURLResponse.class] ? ((NSHTTPURLResponse *)response).statusCode : 0;
|
||||||
KBLOG(@"响应成功(JSON)\nURL: %@\n状态: %ld\nContent-Type: %@\n数据: %@",
|
KBLOG(@"\n[KBNetwork] 响应成功(JSON)\nURL: %@\n状态: %ld\nContent-Type: %@\n数据: %@\n",
|
||||||
req.URL.absoluteString,
|
req.URL.absoluteString,
|
||||||
(long)status,
|
(long)status,
|
||||||
ct ?: @"",
|
ct ?: @"",
|
||||||
pretty);
|
pretty);
|
||||||
#endif
|
#endif
|
||||||
|
// 统一解析业务 code:约定后端顶层包含 { code, message, data }
|
||||||
|
if ([json isKindOfClass:NSDictionary.class]) {
|
||||||
|
NSInteger bizCode = KBBizCodeFromJSONObject(json);
|
||||||
|
if (bizCode != NSNotFound && bizCode != KBBizCodeSuccess) {
|
||||||
|
// 非成功业务 code:执行通用处理(如 token 失效)并通过 error 方式回调
|
||||||
|
[self kb_handleBizCode:bizCode json:json response:response];
|
||||||
|
NSString *msg = KBBizMessageFromJSONObject(json) ?: KBLocalized(@"Server error");
|
||||||
|
NSError *bizErr = [NSError errorWithDomain:KBNetworkErrorDomain
|
||||||
|
code:KBNetworkErrorBusiness
|
||||||
|
userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey : msg,
|
||||||
|
@"code" : @(bizCode)
|
||||||
|
}];
|
||||||
|
if (completion) completion(json, response, bizErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// code 缺失或为成功,按正常成功回调
|
||||||
if (completion) completion(json, response, nil);
|
if (completion) completion(json, response, nil);
|
||||||
} else {
|
} else {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
NSString *text = [self kb_textFromData:data];
|
NSString *text = [self kb_textFromData:data];
|
||||||
text = [self kb_trimmedString:text maxLength:4096];
|
text = [self kb_trimmedString:text maxLength:4096];
|
||||||
NSInteger status = [response isKindOfClass:NSHTTPURLResponse.class] ? ((NSHTTPURLResponse *)response).statusCode : 0;
|
NSInteger status = [response isKindOfClass:NSHTTPURLResponse.class] ? ((NSHTTPURLResponse *)response).statusCode : 0;
|
||||||
KBLOG(@"响应成功(Data)\nURL: %@\n状态: %ld\nContent-Type: %@\n数据: %@",
|
KBLOG(@"\n[KBNetwork] 响应成功(Data)\nURL: %@\n状态: %ld\nContent-Type: %@\n数据: %@\n",
|
||||||
req.URL.absoluteString,
|
req.URL.absoluteString,
|
||||||
(long)status,
|
(long)status,
|
||||||
ct ?: @"",
|
ct ?: @"",
|
||||||
@@ -251,6 +274,43 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
|||||||
|
|
||||||
#pragma mark - Private helpers
|
#pragma mark - Private helpers
|
||||||
|
|
||||||
|
/// 处理通用业务 code(token 失效、被踢下线等)
|
||||||
|
- (void)kb_handleBizCode:(NSInteger)bizCode
|
||||||
|
json:(id)json
|
||||||
|
response:(NSURLResponse *)response {
|
||||||
|
// 键盘扩展内暂不做复杂会话管理,交由宿主 App 处理。
|
||||||
|
// 如需支持,可通过 Darwin 通知或 App Group 自行扩展。
|
||||||
|
(void)bizCode;
|
||||||
|
(void)json;
|
||||||
|
(void)response;
|
||||||
|
return;
|
||||||
|
switch (bizCode) {
|
||||||
|
case KBBizCodeTokenInvalid:
|
||||||
|
case KBBizCodeTokenExpired:
|
||||||
|
case KBBizCodeAccountKicked: {
|
||||||
|
// 登录态异常:统一清理本地会话并提示用户重新登录
|
||||||
|
NSString *msg = KBBizMessageFromJSONObject(json);
|
||||||
|
if (msg.length == 0) {
|
||||||
|
msg = KBLocalized(@"Your session has expired. Please sign in again.");
|
||||||
|
}
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
// 清理本地用户会话(Keychain + 用户缓存)
|
||||||
|
[[KBUserSessionManager shared] logout];
|
||||||
|
// 友好提示
|
||||||
|
[KBHUD showInfo:msg];
|
||||||
|
// 广播通知,便于 UI 侧跳转登录页等
|
||||||
|
NSDictionary *info = @{ @"code": @(bizCode),
|
||||||
|
@"message": msg ?: @"" };
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:@"KBUserSessionInvalidNotification"
|
||||||
|
object:nil
|
||||||
|
userInfo:info];
|
||||||
|
});
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion {
|
- (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion {
|
||||||
NSString *msg = KBLocalized(@"Network error");
|
NSString *msg = KBLocalized(@"Network error");
|
||||||
switch (code) {
|
switch (code) {
|
||||||
@@ -258,6 +318,7 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network";
|
|||||||
case KBNetworkErrorInvalidURL: msg = KBLocalized(@"Invalid URL"); break;
|
case KBNetworkErrorInvalidURL: msg = KBLocalized(@"Invalid URL"); break;
|
||||||
case KBNetworkErrorInvalidResponse: msg = KBLocalized(@"Invalid response"); break;
|
case KBNetworkErrorInvalidResponse: msg = KBLocalized(@"Invalid response"); break;
|
||||||
case KBNetworkErrorDecodeFailed: msg = KBLocalized(@"Parse failed"); break;
|
case KBNetworkErrorDecodeFailed: msg = KBLocalized(@"Parse failed"); break;
|
||||||
|
case KBNetworkErrorBusiness: msg = KBLocalized(@"Server error"); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
|
NSError *e = [NSError errorWithDomain:KBNetworkErrorDomain
|
||||||
|
|||||||
Reference in New Issue
Block a user