接入文档:https://inspire.sg.larksuite.com/docx/RPgndd77VofvNBxPcQ8lBMJngeb
渠道号:
![[Pasted image 20250814091956.png]]
hj和ds的生产环境配置:
测试环境:http://192.168.1.33:8080/platform_new/
正式环境:https://developer.ilnc.inspiregames.cn:8888/platform/
hj
123456
ds
123456
正式环境 用户信息
X项目:
junbao@inspiregames.cn
244466666
元素:
aide@inspiregames.cn
Yuan1234
蜂鸟:
laojiang@inspiregames.cn
sujiang91
测试环境 上报地址:
http://192.168.1.33/
正式环境 上报地址:
元素:ilnc.icongamesg.com
X项目:ilnc.doomsurvivor.com
三消项目:ilnc.hummingbirdgamesltd.com
参考接入逻辑:
using Firebase.Extensions;
using Ideatech;
using LuaInterface;
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Networking;
/// <summary>
/// FCM推送相关
/// </summary>
public class FirebaseMessageUtil
{
private static FirebaseMessageUtil _instance;
public bool isSuccess = false;//初始化是否成功的标志,在FirebaseUtils脚本中同步该标志位
private string fcmAppid_PT; //Application ID of fcm for PTServer
private string fcmAppid_MX; //Application ID of fcm for MXServer
private string fcmAppid_Test; //Application ID of fcm for TestServer
public string fcmToken; //token of fcm
private FirebaseMessageUtil() { }
public static FirebaseMessageUtil Instance
{
get
{
if (_instance == null)
{
_instance = new FirebaseMessageUtil();
}
return _instance;
}
}
[NoToLua]
public void InitializeFirebase()
{
Firebase.Messaging.FirebaseMessaging.MessageReceived += OnMessageReceived;
Firebase.Messaging.FirebaseMessaging.TokenReceived += OnTokenReceived;
if (!ThirdPartyConst.TryGetValue("fcmAppidpt", out fcmAppid_PT))
{
Debug.LogWarning("Failed to obtain the FCM application ID: fcmAppidpt");
}
if (!ThirdPartyConst.TryGetValue("fcmAppidmx", out fcmAppid_MX))
{
Debug.LogWarning("Failed to obtain the FCM application ID: fcmAppidmx");
}
if (!ThirdPartyConst.TryGetValue("fcmAppidtest", out fcmAppid_Test))
{
Debug.LogWarning("Failed to obtain the FCM application ID: fcmAppidtest");
}
}
[NoToLua]
public void RemoveFirebase()
{
Firebase.Messaging.FirebaseMessaging.MessageReceived -= OnMessageReceived;
Firebase.Messaging.FirebaseMessaging.TokenReceived -= OnTokenReceived;
}
private void OnMessageReceived(object sender, Firebase.Messaging.MessageReceivedEventArgs e)
{
Dictionary<string, object> args = new Dictionary<string, object>();
args.Add(NativeEventConst.EVENT_ID_KEY, NativeEventConst.FIREBASE_MESSAGE_RECEIVED);
var notification = e.Message.Notification;
if (notification != null)
{
args.Add("title", notification.Title);
args.Add("body", notification.Body);
var android = notification.Android;
if (android != null)
{
args.Add("android channel_id", android.ChannelId);
}
}
if (e.Message.From.Length > 0)
{
args.Add("from", e.Message.From);
}
if (e.Message.Link != null)
{
args.Add("link", e.Message.Link.ToString());
}
if (e.Message.Data.Count > 0)
{
foreach (KeyValuePair<string, string> item in e.Message.Data)
{
args.Add(item.Key, item.Value);
}
}
//NativeUtils.CallBackToLua(args);
}
private void OnTokenReceived(object sender, Firebase.Messaging.TokenReceivedEventArgs token)
{
fcmToken = token.Token;
Debug.Log("Received FCM Registration Token: " + token.Token);
}
/// <summary>
/// 检测json合法性
/// </summary>
/// <param name="jsonStr"></param>
/// <returns></returns>
private (bool isValid, string error) CheckJsonDataValid<TKey, TValue>(Dictionary<TKey, TValue> jsonMap)
{
if (jsonMap == null)
return (false, "The dictionary object is null");
var emptyItems = jsonMap
.Where(kv => IsInvalidValue(kv.Value))
.Select(kv => kv.Key.ToString())
.ToList();
return emptyItems.Count == 0
? (true, null)
: (false, $"The following key values are empty: {string.Join(", ", emptyItems)}");
}
private bool IsInvalidValue<T>(T value)
{
if (value == null) return true;
if (value is string str) return string.IsNullOrEmpty(str);
return false;
}
/// <summary>
/// 供Lua层调用,设备上报
/// </summary>
public void EquipmentPost(string url, string jsonData)
{
if (isSuccess)
{
//检测json键值合法性
Dictionary<string, string> paramsMap = JsonConvert.DeserializeObject<Dictionary<string, string>>(jsonData);
var (isValid, error) = CheckJsonDataValid(paramsMap);
if (!isValid)
{
Debug.LogWarning($"Equipment reporting failed: {error}");
return;
}
CoroutineManager.AddCoroutine(PostRequest(url, jsonData));
}
else
{
Debug.LogWarning("The device failed to report and the SDK was not successfully initialized");
}
}
[System.Serializable]
private class PostResponse
{
public int code;
public string message;
public string data;
}
private IEnumerator PostRequest(string url, string jsonData)
{
using (UnityWebRequest request = UnityWebRequest.Post(url, "POST"))
{
byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(jsonData);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogWarning("Equipment reporting failed." + request.error);
yield break;
}
var response = JsonUtility.FromJson<PostResponse>(request.downloadHandler.text);
if (response.code != 0)
{
Debug.LogWarning("The device failed to report the error code" + response.code);
}
};
}
/// <summary>
/// 供Lua调用,订阅消息主题,订阅成功后可以通过主题推送
/// </summary>
/// <param name="topic"></param>
public void SubscribeTopic(string topicJsonStr)
{
if (!isSuccess)
{
Debug.LogWarning("Subscription to the topic failed! The SDK was not initialized successfully");
return;
}
//检测json键值合法性
Dictionary<string, string> paramsMap = JsonConvert.DeserializeObject<Dictionary<string, string>>(topicJsonStr);
var (isValid, error) = CheckJsonDataValid(paramsMap);
if (!isValid)
{
Debug.LogWarning($"Failed to subscribe to the topic: {error}");
return;
}
foreach (string key in paramsMap.Keys)
{
Firebase.Messaging.FirebaseMessaging.SubscribeAsync(paramsMap[key]).ContinueWithOnMainThread(task =>
{
if (task.IsFaulted)
Debug.LogWarning($"Failed to subscribe to the topic: {task.Exception}");
});
}
}
/// <summary>
/// 供Lua调用,根据服务器类型,获取应用ID
/// </summary>
/// <param name="serverCode"></param>
/// <returns></returns>
public string GetAppIdByServer(string serverCode)
{
if (string.IsNullOrEmpty(serverCode))
{
return fcmAppid_Test;
}
else
{
string lowerServerCode = serverCode.ToLower();
if (lowerServerCode == "pt")
{
return fcmAppid_PT;
}
else if (lowerServerCode == "es")
{
return fcmAppid_MX;
}
else
{
return "";
}
}
}
}
参考链接: https://firebase.google.com/docs/cloud-messaging/ios/send-image?hl=zh-cn#node.js
https://blog.csdn.net/qq_38718912/article/details/126975533
FCM带图片,发送给客户端回包,数据结构:
FCM: userInfo: {
aps = {
alert = {
body = 2;
title = 1;
};
"mutable-content" = 1;
};
"fcm_options" = {
image = "https://octodex.github.com/images/codercat.jpg";
};
"gcm.message_id" = 1755861458675849;
"google.c.a.e" = 1;
"google.c.fid" = cRZAyUCe5Uxcihd2iGk7Yf;
"google.c.sender.id" = 967184518610;
traceId = "D0bWCJoRYhib3+FPKvN8ueLz6EX+ITcDBifBlie+Ki6iMKwlC4h4dVYQoWGC845E";
}.
FCM: userInfo: {
aps = {
alert = {
body = 2;
title = 1;
};
"mutable-content" = 1;
};
"fcm_options" = {
image = "https://octodex.github.com/images/codercat.jpg";
};
"gcm.message_id" = 1755861458675849;
"google.c.a.e" = 1;
"google.c.fid" = cRZAyUCe5Uxcihd2iGk7Yf;
"google.c.sender.id" = 967184518610;
traceId = "D0bWCJoRYhib3+FPKvN8ueLz6EX+ITcDBifBlie+Ki6iMKwlC4h4dVYQoWGC845E";
}.
如有需要,还要在podFile里面,添加fcm相关的插件,到消息通知扩展插件
target 'NotificationService' do
pod 'Firebase/Messaging', '12.0.0'
end
编辑Editor脚本:
#if UNITY_IOS
using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using UnityEditor.iOS.Xcode.Extensions;
using UnityEngine;
using UnityEditor.iOS.Xcode.PBX;
public static class PureNotificationExtensionCreator
{
private const string ExtensionName = "NotificationService";
[PostProcessBuild(200)]
public static void OnPostProcessBuild(BuildTarget target, string buildPath)
{
if (target != BuildTarget.iOS) return;
Debug.Log("🛠️ 开始创建纯净版 Notification Service Extension...");
string projPath = PBXProject.GetPBXProjectPath(buildPath);
PBXProject proj = new PBXProject();
proj.ReadFromFile(projPath);
#if UNITY_2019_3_OR_NEWER
string mainTargetGuid = proj.GetUnityMainTargetGuid();
#else
string mainTargetGuid = proj.TargetGuidByName(PBXProject.GetUnityTargetName());
#endif
// 1. 创建 Extension Target(这会自动创建基础文件)
string extGuid = proj.AddAppExtension(mainTargetGuid, ExtensionName, ExtensionName,
$"{ExtensionName}/Info.plist");
Debug.Log($"✅ 创建 Extension Target: {extGuid}");
// 2. 配置基本设置
proj.SetBuildProperty(extGuid, "PRODUCT_BUNDLE_IDENTIFIER",
PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.iOS) + "." + ExtensionName);
proj.SetBuildProperty(extGuid, "ENABLE_BITCODE", "NO");
proj.SetBuildProperty(extGuid, "IPHONEOS_DEPLOYMENT_TARGET", "15.0");
// 3. 配置签名
ConfigureCodeSigning(proj, extGuid);
// 4. 移动预制的代码文件到扩展目录
MovePrebuiltSourceFiles(buildPath);
// 5. 添加系统框架
proj.AddFrameworkToProject(extGuid, "UserNotifications.framework", false);
// 6. 配置 Push Notifications 能力
ConfigurePushNotificationsCapability(proj, extGuid, buildPath);
// 7. 关键修复:确保文件被添加到 Xcode 项目结构和编译阶段
AddFilesToProjectStructure(proj, extGuid, buildPath);
// 8. 保存工程
proj.WriteToFile(projPath);
AssetDatabase.Refresh();
Debug.Log("🎉 纯净版 Notification Service Extension 创建完成!");
}
// 关键方法:确保文件被添加到 Xcode 项目结构
private static void AddFilesToProjectStructure(PBXProject proj, string extGuid, string buildPath)
{
Debug.Log("🔧 开始添加文件到 Xcode 项目结构...");
// 1. 获取或创建 Sources Build Phase
var buildPhaseID = proj.AddSourcesBuildPhase(extGuid);
Debug.Log($"✅ Sources Build Phase ID: {buildPhaseID}");
// 2. 添加 .h 文件到项目结构
string headerPath = Path.Combine(ExtensionName, "NotificationService.h");
string headerGuid = proj.AddFile(headerPath, headerPath, PBXSourceTree.Source);
Debug.Log($"✅ Header 文件添加到项目: {headerGuid}");
// 3. 添加 .m 文件到项目结构
string sourcePath = Path.Combine(ExtensionName, "NotificationService.m");
string sourceGuid = proj.AddFile(sourcePath, sourcePath, PBXSourceTree.Source);
Debug.Log($"✅ Source 文件添加到项目: {sourceGuid}");
// 4. 将文件添加到编译阶段(确保 Target Membership 正确)
proj.AddFileToBuildSection(extGuid, buildPhaseID, headerGuid);
proj.AddFileToBuildSection(extGuid, buildPhaseID, sourceGuid);
Debug.Log($"✅ 文件已添加到编译阶段");
// 5. 确保 Info.plist 也被添加到项目
string plistPath = Path.Combine(ExtensionName, "Info.plist");
string plistGuid = proj.AddFile(plistPath, plistPath, PBXSourceTree.Source);
Debug.Log($"✅ Info.plist 添加到项目: {plistGuid}");
Debug.Log("🎯 所有文件已成功添加到 Xcode 项目结构!");
}
private static void ConfigureCodeSigning(PBXProject proj, string extGuid)
{
string teamId = PlayerSettings.iOS.appleDeveloperTeamID;
if (!string.IsNullOrEmpty(teamId))
{
proj.SetBuildProperty(extGuid, "DEVELOPMENT_TEAM", teamId);
proj.SetBuildProperty(extGuid, "CODE_SIGN_STYLE", "Automatic");
}
}
private static void MovePrebuiltSourceFiles(string buildPath)
{
string extDir = Path.Combine(buildPath, ExtensionName);
// 确保目录存在
if (!Directory.Exists(extDir))
Directory.CreateDirectory(extDir);
// 源文件路径(Plugins/iOS 目录)
string sourceDir = Path.Combine(Application.dataPath, "Plugins", "iOS");
// 移动 .h 文件
string headerSource = Path.Combine(sourceDir, "NotificationService.h");
string headerDest = Path.Combine(extDir, "NotificationService.h");
if (File.Exists(headerSource))
{
// 如果目标文件已存在,先删除
if (File.Exists(headerDest))
{
File.Delete(headerDest);
Debug.Log("🗑️ 删除已存在的目标文件: " + headerDest);
}
File.Move(headerSource, headerDest);
Debug.Log("✅ 移动 NotificationService.h 文件");
}
else
{
Debug.LogError("❌ 找不到源文件: " + headerSource);
}
// 移动 .m 文件
string sourceSource = Path.Combine(sourceDir, "NotificationService.m");
string sourceDest = Path.Combine(extDir, "NotificationService.m");
if (File.Exists(sourceSource))
{
// 如果目标文件已存在,先删除
if (File.Exists(sourceDest))
{
File.Delete(sourceDest);
Debug.Log("🗑️ 删除已存在的目标文件: " + sourceDest);
}
File.Move(sourceSource, sourceDest);
Debug.Log("✅ 移动 NotificationService.m 文件");
}
else
{
Debug.LogError("❌ 找不到源文件: " + sourceSource);
}
// 创建 Info.plist(如果需要)
string plistPath = Path.Combine(extDir, "Info.plist");
if (!File.Exists(plistPath))
{
File.WriteAllText(plistPath, PureInfoPlistContent);
Debug.Log("✅ 创建 Info.plist 文件");
}
}
private static void ConfigurePushNotificationsCapability(PBXProject proj, string extGuid, string buildPath)
{
Debug.Log("🔧 开始配置 Push Notifications 能力...");
// 1. 添加 Push Notifications 能力
proj.AddCapability(extGuid, PBXCapabilityType.PushNotifications);
Debug.Log("✅ 添加 Push Notifications 能力");
// 2. 确保签名配置正确
string teamId = PlayerSettings.iOS.appleDeveloperTeamID;
if (!string.IsNullOrEmpty(teamId))
{
proj.SetBuildProperty(extGuid, "DEVELOPMENT_TEAM", teamId);
proj.SetBuildProperty(extGuid, "CODE_SIGN_STYLE", "Automatic");
Debug.Log($"✅ 配置开发团队: {teamId}");
}
Debug.Log("✅ Push Notifications 能力配置完成");
}
// 纯净的 Info.plist 内容
private static readonly string PureInfoPlistContent =
@"<?xml version=""1.0"" encoding=""UTF-8""?>
<!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd"">
<plist version=""1.0"">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>NotificationService</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>NotificationService</string>
</dict>
</dict>
</plist>";
}
#endif
//
// NotificationService.h
// NotificationService
//
// Created by unity on 2025/8/23.
//
#import <UserNotifications/UserNotifications.h>
@interface NotificationService : UNNotificationServiceExtension
@end
//
// NotificationService.m
// NotificationService
//
// Created by unity on 2025/8/23.
//
#import "NotificationService.h"
#import <UIKit/UIKit.h>
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
NSLog(@"✅ Extension被调用!");
NSLog(@"📦 原始通知内容: %@", request.content.userInfo);
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
// 1. 从userInfo中获取图片URL
NSDictionary *userInfo = request.content.userInfo;
NSString *imageUrlString = nil;
// 检查FCM格式的图片URL (fcm_options -> image)
if (userInfo[@"fcm_options"] && userInfo[@"fcm_options"][@"image"]) {
imageUrlString = userInfo[@"fcm_options"][@"image"];
NSLog(@"🖼️ 找到FCM图片URL: %@", imageUrlString);
}
// 也可以检查其他自定义字段
else if (userInfo[@"image_url"]) {
imageUrlString = userInfo[@"image_url"];
NSLog(@"🖼️ 找到自定义图片URL: %@", imageUrlString);
}
// 2. 如果没有图片URL,直接返回原始内容
if (!imageUrlString) {
NSLog(@"⚠️ 未找到图片URL,使用原始内容");
self.contentHandler(self.bestAttemptContent);
return;
}
// 3. 下载图片并添加到通知
[self loadAttachmentForUrlString:imageUrlString completionHandler:^(UNNotificationAttachment *attachment) {
if (attachment) {
NSLog(@"✅ 图片下载成功,添加到通知");
self.bestAttemptContent.attachments = @[attachment];
} else {
NSLog(@"❌ 图片下载失败,使用原始内容");
}
// 4. 最终返回通知内容
self.contentHandler(self.bestAttemptContent);
}];
}
- (void)loadAttachmentForUrlString:(NSString *)urlString completionHandler:(void (^)(UNNotificationAttachment *))completionHandler {
__block UNNotificationAttachment *attachment = nil;
NSURL *attachmentURL = [NSURL URLWithString:urlString];
if (!attachmentURL) {
completionHandler(nil);
return;
}
NSLog(@"🌐 开始下载图片: %@", urlString);
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:attachmentURL completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
// 检查下载错误
if (error) {
NSLog(@"❌ 图片下载错误: %@", error);
completionHandler(nil);
return;
}
// 检查文件是否存在
if (!temporaryFileLocation) {
NSLog(@"❌ 临时文件位置为空");
completionHandler(nil);
return;
}
// 获取文件扩展名
NSString *fileExtension = [self getFileExtensionForResponse:response] ?: @"jpg";
NSLog(@"📄 文件扩展名: %@", fileExtension);
// 创建唯一文件名
NSString *uniqueFileName = [NSString stringWithFormat:@"%@.%@", [[NSUUID UUID] UUIDString], fileExtension];
NSString *tempFile = [NSTemporaryDirectory() stringByAppendingPathComponent:uniqueFileName];
// 移动文件到临时目录
NSError *moveError = nil;
[[NSFileManager defaultManager] moveItemAtURL:temporaryFileLocation toURL:[NSURL fileURLWithPath:tempFile] error:&moveError];
if (moveError) {
NSLog(@"❌ 移动文件错误: %@", moveError);
completionHandler(nil);
return;
}
// 创建通知附件
attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image"
URL:[NSURL fileURLWithPath:tempFile]
options:nil
error:&moveError];
if (moveError) {
NSLog(@"❌ 创建附件错误: %@", moveError);
completionHandler(nil);
return;
}
completionHandler(attachment);
}];
[task resume];
}
- (NSString *)getFileExtensionForResponse:(NSURLResponse *)response {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSString *contentType = httpResponse.allHeaderFields[@"Content-Type"];
if ([contentType isEqualToString:@"image/jpeg"]) return @"jpg";
if ([contentType isEqualToString:@"image/jpg"]) return @"jpg";
if ([contentType isEqualToString:@"image/png"]) return @"png";
if ([contentType isEqualToString:@"image/gif"]) return @"gif";
if ([contentType isEqualToString:@"image/webp"]) return @"webp";
}
// 从URL路径推断扩展名
NSString *pathExtension = response.URL.pathExtension;
if (pathExtension.length > 0) {
return pathExtension;
}
return @"jpg"; // 默认使用jpg
}
- (void)serviceExtensionTimeWillExpire {
NSLog(@"⏰ Extension处理超时,使用原始内容");
self.contentHandler(self.bestAttemptContent);
}
@end