diff --git a/CustomKeyboard/Network/KBNetworkManager.m b/CustomKeyboard/Network/KBNetworkManager.m index 5e1385a..ae71f5e 100644 --- a/CustomKeyboard/Network/KBNetworkManager.m +++ b/CustomKeyboard/Network/KBNetworkManager.m @@ -41,38 +41,10 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; } - (void)getSignWithParare:(NSDictionary *)bodyParams{ - - NSString *appId = @"loveKeyboard"; - NSString *secret = @"kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H"; // 和服务端保持一致 - NSString *timestamp = [KBSignUtils currentTimestamp]; - NSString *nonce = [KBSignUtils generateNonceWithLength:16]; - // 1. 组装参与签名的所有参数 - NSMutableDictionary *signParams = [NSMutableDictionary dictionary]; - signParams[@"appId"] = appId; - signParams[@"timestamp"] = timestamp; - signParams[@"nonce"] = nonce; - // 把 body 里的字段也加入签名参数 - [bodyParams enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - if ([obj isKindOfClass:[NSString class]]) { - signParams[key] = obj; - } else { - signParams[key] = [obj description]; - } - }]; - NSString *sign = [KBSignUtils signWithParams:signParams secret:secret]; - - // 将签名相关字段合并进默认请求头 + NSDictionary *signHeaders = [KBSignUtils signHeadersWithBodyParams:bodyParams]; NSMutableDictionary *headers = [self.defaultHeaders mutableCopy] ?: [NSMutableDictionary dictionary]; - - if (sign.length > 0) { - headers[@"X-Sign"] = sign; - } - headers[@"X-App-Id"] = appId; - headers[@"X-Timestamp"] = timestamp; - headers[@"X-Nonce"] = nonce; - - // 触发 copy 语义,确保对外仍是不可变字典 + [headers addEntriesFromDictionary:signHeaders ?: @{}]; self.defaultHeaders = headers; } diff --git a/CustomKeyboard/View/KBFunctionView.m b/CustomKeyboard/View/KBFunctionView.m index 4f1b340..c883ad9 100644 --- a/CustomKeyboard/View/KBFunctionView.m +++ b/CustomKeyboard/View/KBFunctionView.m @@ -19,6 +19,7 @@ #import "KBHostAppLauncher.h" #import "KBInputBufferManager.h" #import "KBResponderUtils.h" // 统一查找 UIInputViewController 的工具 +#import "KBSignUtils.h" #import "KBSkinManager.h" #import "KBStreamOverlayView.h" // 带关闭按钮的流式层 #import "KBStreamTextView.h" // 流式文本视图 @@ -435,11 +436,32 @@ cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60]; request.HTTPMethod = @"POST"; + [request setValue:@"text/event-stream" forHTTPHeaderField:@"Accept"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + // 签名头(后端要求,否则会 401:Missing sign headers) + NSDictionary *signHeaders = + [KBSignUtils signHeadersWithBodyParams:payload]; + [signHeaders enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, + BOOL *stop) { + if (key.length == 0 || obj.length == 0) { + return; + } + [request setValue:obj forHTTPHeaderField:key]; + }]; NSString *token = KBAuthManager.shared.current.accessToken ?: @""; if (token.length > 0) { [request setValue:token forHTTPHeaderField:@"auth-token"]; } + // 与主 App 的请求头保持一致(Bearer) + NSDictionary *authHeader = + [[KBAuthManager shared] authorizationHeader]; + [authHeader enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, + BOOL *stop) { + if (key.length == 0 || obj.length == 0) { + return; + } + [request setValue:obj forHTTPHeaderField:key]; + }]; request.HTTPBody = bodyData; self.streamHasOutput = NO; @@ -463,7 +485,7 @@ __strong typeof(weakSelf) self = weakSelf; if (!self) return; - [self kb_handleEventSourceError:event.error]; +// [self kb_handleEventSourceError:event.error]; } forEvent:WJXEventNameError queue:NSOperationQueue.mainQueue]; diff --git a/Shared/KBMaiPointReporter.m b/Shared/KBMaiPointReporter.m index 2f29c62..366ab24 100644 --- a/Shared/KBMaiPointReporter.m +++ b/Shared/KBMaiPointReporter.m @@ -95,7 +95,7 @@ static void KBMaiPoint_DebugLogError(NSURLResponse *response, NSError *error) { @"eventId": trimmedName, @"value": val.copy }; - [self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion]; +// [self postPath:KB_MAI_POINT_PATH_GENERIC_DATA parameters:params completion:completion]; } - (void)reportPageExposureWithEventName:(NSString *)eventName diff --git a/Shared/KBSignUtils.h b/Shared/KBSignUtils.h index 605bad8..b3910f2 100644 --- a/Shared/KBSignUtils.h +++ b/Shared/KBSignUtils.h @@ -30,6 +30,10 @@ NS_ASSUME_NONNULL_BEGIN /// 简单 nonce 生成(默认 16 位) + (NSString *)generateNonceWithLength:(NSUInteger)length; +/// 生成本项目后端约定的签名请求头(X-Sign/X-App-Id/X-Timestamp/X-Nonce)。 +/// bodyParams:参与签名的业务参数(如 JSON body 字段)。内部会做类型容错与空值过滤。 ++ (NSDictionary *)signHeadersWithBodyParams:(nullable NSDictionary *)bodyParams; + @end NS_ASSUME_NONNULL_END diff --git a/Shared/KBSignUtils.m b/Shared/KBSignUtils.m index c6efe2a..e6c58e2 100644 --- a/Shared/KBSignUtils.m +++ b/Shared/KBSignUtils.m @@ -10,6 +10,32 @@ @implementation KBSignUtils +static NSString *const KBSignAppId = @"loveKeyboard"; +static NSString *const KBSignSecret = @"kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H"; + +static NSString *KBSignStringFromObject(id obj) { + if (!obj || obj == (id)kCFNull) { + return nil; + } + if ([obj isKindOfClass:[NSString class]]) { + return (NSString *)obj; + } + if ([obj isKindOfClass:[NSNumber class]]) { + return [(NSNumber *)obj stringValue]; + } + if ([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) { + NSJSONWritingOptions options = 0; + if (@available(iOS 11.0, *)) { + options = NSJSONWritingSortedKeys; + } + NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:options error:nil]; + if (data.length > 0) { + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + } + } + return [obj description]; +} + + (NSString *)urlEncode:(NSString *)value { if (!value) return @""; // 按 application/x-www-form-urlencoded 规则编码(更贴近后端常见实现) @@ -96,4 +122,50 @@ return [uuid substringToIndex:length]; } ++ (NSDictionary *)signHeadersWithBodyParams:(NSDictionary *)bodyParams { + NSString *timestamp = [self currentTimestamp]; + NSString *nonce = [self generateNonceWithLength:16]; + + NSMutableDictionary *signParams = [NSMutableDictionary dictionary]; + if (KBSignAppId.length > 0) { + signParams[@"appId"] = KBSignAppId; + } + if (timestamp.length > 0) { + signParams[@"timestamp"] = timestamp; + } + if (nonce.length > 0) { + signParams[@"nonce"] = nonce; + } + + if ([bodyParams isKindOfClass:[NSDictionary class]] && bodyParams.count > 0) { + [bodyParams enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + if (![key isKindOfClass:[NSString class]]) { + return; + } + NSString *value = KBSignStringFromObject(obj); + if (value.length == 0) { + return; + } + signParams[(NSString *)key] = value; + }]; + } + + NSString *sign = [self signWithParams:signParams secret:KBSignSecret ?: @""]; + + NSMutableDictionary *headers = [NSMutableDictionary dictionary]; + if (sign.length > 0) { + headers[@"X-Sign"] = sign; + } + if (KBSignAppId.length > 0) { + headers[@"X-App-Id"] = KBSignAppId; + } + if (timestamp.length > 0) { + headers[@"X-Timestamp"] = timestamp; + } + if (nonce.length > 0) { + headers[@"X-Nonce"] = nonce; + } + return [headers copy]; +} + @end diff --git a/keyBoard/Class/Network/KBNetworkManager.m b/keyBoard/Class/Network/KBNetworkManager.m index 5c927dd..aef2a07 100644 --- a/keyBoard/Class/Network/KBNetworkManager.m +++ b/keyBoard/Class/Network/KBNetworkManager.m @@ -33,29 +33,6 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; @implementation KBNetworkManager -static NSString *KBSignStringFromObject(id obj) { - if (!obj || obj == (id)kCFNull) { - return nil; - } - if ([obj isKindOfClass:[NSString class]]) { - return (NSString *)obj; - } - if ([obj isKindOfClass:[NSNumber class]]) { - return [(NSNumber *)obj stringValue]; - } - if ([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) { - NSJSONWritingOptions options = 0; - if (@available(iOS 11.0, *)) { - options = NSJSONWritingSortedKeys; - } - NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:options error:nil]; - if (data.length > 0) { - return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - } - } - return [obj description]; -} - + (instancetype)shared { static KBNetworkManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ m = [KBNetworkManager new]; }); return m; @@ -84,48 +61,10 @@ static NSString *KBSignStringFromObject(id obj) { } - (void)getSignWithParare:(NSDictionary *)bodyParams{ - - NSString *appId = @"loveKeyboard"; - NSString *secret = @"kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H"; // 和服务端保持一致 - NSString *timestamp = [KBSignUtils currentTimestamp]; - NSString *nonce = [KBSignUtils generateNonceWithLength:16]; - // 1. 组装参与签名的所有参数 - NSMutableDictionary *signParams = [NSMutableDictionary dictionary]; - signParams[@"appId"] = appId; - signParams[@"timestamp"] = timestamp; - signParams[@"nonce"] = nonce; - // 把 body 里的字段也加入签名参数 - [bodyParams enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - NSString *value = KBSignStringFromObject(obj); - if (value.length == 0) { - return; - } - signParams[key] = value; - }]; - NSString *signSource = [KBSignUtils signSourceStringWithParams:signParams secret:secret]; - NSString *sign = [KBSignUtils signWithParams:signParams secret:secret]; - -#if DEBUG - if (signSource.length > 0) { - NSString *secretPart = [NSString stringWithFormat:@"secret=%@", [KBSignUtils urlEncode:secret ?: @""]]; - NSString *masked = [signSource stringByReplacingOccurrencesOfString:secretPart withString:@"secret=***"]; - KBLOG(@"[KBNetwork] sign source: %@", masked); - KBLOG(@"[KBNetwork] sign value: %@", sign ?: @""); - } -#endif - - // 将签名相关字段合并进默认请求头 + NSDictionary *signHeaders = [KBSignUtils signHeadersWithBodyParams:bodyParams]; NSMutableDictionary *headers = [self.defaultHeaders mutableCopy] ?: [NSMutableDictionary dictionary]; - - if (sign.length > 0) { - headers[@"X-Sign"] = sign; - } - headers[@"X-App-Id"] = appId; - headers[@"X-Timestamp"] = timestamp; - headers[@"X-Nonce"] = nonce; - - // 触发 copy 语义,确保对外仍是不可变字典 + [headers addEntriesFromDictionary:signHeaders ?: @{}]; self.defaultHeaders = headers; }