接入提供方的AI

查看文档

具体文档不能放上来,但是总之涉及到的东西就是

  1. 基本的请求头:
    1. App-Key
    2. Request-Timestamp:13位毫秒级
    3. Signature-Headers:参与签名的header列表
    4. Request-Uuid:随机一个id
    5. Reqeust-Signature:计算出的签名
      Pasted image 20250815140548.pngPasted image 20250815140652.png
      Pasted image 20250815140709.pngPasted image 20250815140734.pngPasted image 20250815140752.png
      有点像java还是javascript,不认识。总归能看懂点意思。
      反正一个函数一个函数实现。

开始实现签名-签名的底层运算

  1. 第一个合并签名太容易弄了。
//.h
static FString GetStringToSign(const FString& RequestMethod, const FString& RequestBodyMD5, const FString& RequestHeadersToSign, const FString& RequestUrlToSign);
//.cpp
FString UAPI::GetStringToSign(const FString& RequestMethod, const FString& RequestBodyMD5, const FString& RequestHeadersToSign, const FString& RequestUrlToSign)
{
    return RequestMethod + TEXT("\n") + RequestBodyMD5 + TEXT("\n") + RequestHeadersToSign + RequestUrlToSign;
}
  1. 不好,是加密
    因为CPP的基本操作是造轮子,所以没有给md5加密库,base64编码库,以及hash256加密库;为什么我会这么觉得,因为我要吃玉米卷了。【精神恍惚,精神崩溃,呆滞ing】。
    1. 找网上的第三方cpp头文件,某些网安领域的专业人士开源的源码。能满足需求这里一口气把所有的源码配置都弄上来。Pasted image 20250815145905.png这这里的加密库Hashlib是我自己到处找到的一批能满足需求的源码,有的从github下下来,有的从csdn上拷过来,反正我是直接看他们的引导文档,和引导用的main函试着跑的,跑下来对的上提供的签名样例,我就把.h和.cpp文件分拆到这个Hashlib里了。【我是小老虎收集的Hashilib压缩包,如果有人能认出来代码的位置请踢我,我需要去引用它们,事实上我没引用仅仅是因为我找不到链接了。Pasted image 20250815150643.png
    2. 然后修改build.cs:注意hashilib里的build.cs没有用,删掉也无大碍,这个Hashlib.Build.cs的作用就是能让Pasted image 20250815150953.pngPasted image 20250815151118.png,额,让我们在VS创建UE的C++类的时候,多一个模块。做更高程度的封装肯定要这个,但是我tm都不用get方法了,也没打算现在接语音,所以就不封装成插件之类的东西了,应该是叫插件或者模块啥的吧?算不重要。
    3. 编写项目对应的build.cs文件,项目也是一个模块,不然刚才的模块列表不会有。什么?你不懂C#?我也不动,反正我也不用写c#,这个build.cs文件里我只用会4个函数就够用了。
      1. 第一个Console.WriteLine($"-------------------【HashilibPath:{HashilibPath}】----------------------");这个函数拿来print,这样Pasted image 20250815152244.png就能知道什么路径是被order了的了。
      2. 第二个string HashilibPath = Path.Combine(ModuleDirectory,"Hashlib");类似于python的os库path操作,反正我python看得懂。
      3. 第三个PublicIncludePaths.AddRange(new string[]{Path.Combine(HashilibPath,"include/")//Path.Combine(ThirdPartyPath, "Hashlib", "include")});看不懂,总之就是用指定路径让ue能定位到这个里面的.h头文件的。
public class SeaAPI : ModuleRules
{
	public SeaAPI(ReadOnlyTargetRules       Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore","HeadMountedDisplay","HTTP","Json", "JsonUtilities" });

		PrivateDependencyModuleNames.AddRange(new string[] {  });


		//添加第三方库,ModuleDirectory就是Game/ProjectName/Source/ProjectName
		string HashilibPath = Path.Combine(ModuleDirectory,"Hashlib");
		Console.WriteLine($"-------------------【HashilibPath:{HashilibPath}】----------------------");
		PublicIncludePaths.AddRange(new string[]
		{
			Path.Combine(HashilibPath,"include/")
			//Path.Combine(ThirdPartyPath, "Hashlib", "include")
		});

		string SeaActorPath = Path.Combine(ModuleDirectory, "SeaActor");
		Console.WriteLine($"-------------------【SeaActorPath:{SeaActorPath}】----------------------");
		PublicIncludePaths.AddRange(new string[]
		{
			Path.Combine(SeaActorPath)
		   //Path.Combine(ThirdPartyPath, "Hashlib", "include")
		});

		string SharedPath = Path.Combine(ModuleDirectory, "Shareds");
		Console.WriteLine($"------------------【SharedPath:】{SharedPath}--------------------------");
		PublicIncludePaths.Add(SeaActorPath);



		// Uncomment if you are using Slate UI
		// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

		// Uncomment if you are using online features
		// PrivateDependencyModuleNames.Add("OnlineSubsystem");

		// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
	}
}
5. 第四个,将路径添加到ue的cpp类中。就是直接把源文件引入这里是md5.h和hmac_sha256.h这两个文件。至于Base64是我后面发现能用现成的库的时候了,当时我天都塌了,那我弄这么久算什么,算我能吃苦吗?不过用都用了,就不改了,能跑就行,而且总会有需求ue没提供cpp吧?比如opencv,ffmpeg之类的。到时侯来这里看笔记就行了。这个导入主要还是导入到.cpp中,因为我们给蓝图节点预留的参数肯定是UE能识别的数据。
	#include "Misc/Base64.h"
	#include "md5.h"
	#include "Algo/StableSort.h"
	#include "HttpModule.h"
	#include "Interfaces/IHttpResponse.h"
	extern "C" {
		#include "hmac_sha256.h"
	}
  1. md5+base64:当时我弄的时候真希望友人能整整这个cpp的包管理。
//.h
static FString getRequestBodyMD5(const FString& BodyString);

//.cpp
FString UAPI::getRequestBodyMD5(const FString& BodyString)
{
    UE_LOG(LogTemp, Warning, TEXT("BodyString:::%s"),*BodyString);
    std::string StdInput = TCHAR_TO_UTF8(*BodyString);
    if (StdInput.empty()) {
        return "";
    }

    MD5 md5Encoder(StdInput);
    std::string OnceEncode = md5Encoder.md5();
    
    TArray<uint8> MD5Bytes;
    MD5Bytes.Append((const uint8*)OnceEncode.c_str(), OnceEncode.length());
    FString TwiceEncode = FBase64::Encode(MD5Bytes);

    return TwiceEncode;
}
  1. HeadertoSign:我不知道为啥我的排序和提供方的排序不一样。按照ascil排序O应该在g后面,但是提供方的O在g前面。可能就是这个原因最终没能成功签名吧。主要提供方的后端也换人了,他也不知道为啥O的排序在g前面。
FString UAPI::getRequestHeadersToSign(const TMap<FString, FString>& Headers, const FString& SignatureHeadersRaw)
{
    TArray<FString> Keys;
    SignatureHeadersRaw.ParseIntoArray(Keys, TEXT(","), true);
    Keys.Sort([](const FString& A, const FString& B) {
        return A < B;
        });

    FString Result;
    for (const FString& Key : Keys)
    {
        const FString* Value = Headers.Find(Key);  
        FString Line = Key + TEXT(":");            
        if (Value)
        {
            Line += *Value;                        
        }
        Result += Line + TEXT("\n");               
    }
    return Result;
}
  1. urlToSign:这个后面又有调整,因为GET方法才有后面的?和参数,POST没有,所以得针对有无?进行判断,没有的话URI[0]作为返回值就行
FString UAPI::getRequestUrlToSign(const FString& URL)
{
    TArray<FString> URI;
    URL.ParseIntoArray(URI, TEXT("?"), true);
    if (URI.Num() < 2 || URI[1].IsEmpty())
    {
        return URL;
    }
    TArray<FString> KVPare;
    URI[1].ParseIntoArray(KVPare, TEXT("&"), true);
    
    TArray<TPair<FString, FString>> Params;
    for (const FString& KV : KVPare)
    {
        FString Key, Value;

        if (KV.Split(TEXT("="), &Key, &Value))
        {
            Params.Emplace(Key, Value);
        }
    }
    if (Params.Num() == 0)
    {
        return URI[0];
    }
    Algo::StableSortBy(Params, &TPair<FString, FString>::Key,[](const FString& A, const FString& B) { return A < B; });

    FString NewKVPairStr;
    for (const auto& Param:Params) 
    {
        if (!NewKVPairStr.IsEmpty())NewKVPairStr += TEXT("&");
        NewKVPairStr += Param.Key + TEXT("=") + Param.Value;
    }
    
    //GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, RetStr);
    return URI[0] + TEXT("?") + NewKVPairStr;
}
  1. 签名,hash256
FString UAPI::GenerateSignature(const FString& StringToSign, const FString& SecretKey)
{
    // 1. 把 FString → UTF-8 字节
    FTCHARToUTF8 SignBytes(StringToSign);
    FTCHARToUTF8 KeyBytes(SecretKey);

    // 2. 计算 HMAC-SHA256
    uint8 Digest[32];
    hmac_sha256(
        (const uint8*)KeyBytes.Get(), KeyBytes.Length(),
        (const uint8*)SignBytes.Get(), SignBytes.Length(),
        Digest, sizeof(Digest));

    // 3. Base64 编码并返回
    FString RetStr = FBase64::Encode(Digest, sizeof(Digest));
    //GEngine.AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, RetStr);
    return RetStr;
}

暴露签名用的方法

下面的这些方法基本就是想要的效果。其中有几个特别需要注意的点。

  1. UFUNCTION(BlueprintCallable,Category = "SeaAPI",DisplayName = "F002_BuildBodyString")UE用于暴露cpp函数给蓝图的宏,DisplayName是用标签替换函数名,Category是蓝图中的分类检索。
  2. 蓝图中的节点有两种,一种是static打头的,一种没有static(由实例化一个对象来调用);我这里是一个蓝图库类,本意就是只存放一批函数。一点都不想调用一个方法还得进行如下操作(拖actor到关卡蓝图,选中actor,进入关卡蓝图,获取索引),而,UPARAM(ref) FAPIData& APIDAta是一个结构体存放请求涉及的数据。
UFUNCTION(BlueprintCallable, Category = "SeaAPI", DisplayName="F000_DataSaver")
static FAPIData CreateDataSaver();


UFUNCTION(BlueprintCallable,Category = "SeaAPI",DisplayName = "F002_BuildBodyString")
static FString BuildBodyString(
	UPARAM(ref) FAPIData& APIData,
	const FString& Query,
	const TMap<FString, FString>& Inputs,
	const FString& ResponseMode=TEXT("blocking"),
	const FString& User = TEXT(""),
	const FString& ConversationId = TEXT(""));

UFUNCTION(BlueprintCallable, Category = "SeaAPI",DisplayName = "F001_SetParameter")
static void SetParameters(
	UPARAM(ref) FAPIData& APIData,
	const FString& InRequestUuid,
	const FString& InTenantId,
	const FString& InUserName,
	const FString& InRequestMethod=TEXT("POST"),
	const FString& InURL = TEXT("https://mr-stage.sensetime.com/api/dify/v1/parameters"),
	const FString& InAppKey = TEXT("3n8qrFG2ZprS5EzW"),
	const FString& InAppSecret = TEXT("U64GJatMDafySqRVTV"),
	const FString InSignatureHeader = TEXT("TenantId,UserName")
	);


//借助外界的AppKey,AppSecret,
UFUNCTION(BlueprintCallable, Category = "SeaAPI", DisplayName = "F004_BuildSignature")
static FString BuildSignature(UPARAM(ref) FAPIData& APIData);
//static FString BuildSignature(FAPIData& APIData, const FString& RequestMethod, const FString& BodyString, const TMap<FString, FString>& Headers, const FString& SignatureHeadersRaw, const FString& URL);


UFUNCTION(BlueprintCallable,Category = "SeaAPI",DisplayName = "F003_GetTimestamp")
static FString BuildTimestamp(UPARAM(ref) FAPIData& APIData);

Http异步请求

这块儿,我连看都看不懂,完全理解不了,现在的我也还理解不了。直接炒的答案。我看的后半段教程
点我查看视频,我照着实现了后面的例子,因为前面的不支持异步调用。然后对方的后端老哥人很好。还帮我停掉了签名让我来测请求能不能过关。请求很容易就发出去了,然后因为这一段完全不会,所以解析请求里的内容并转码成utf16也是交给claude。等于说接下里把签名和请求连个类衔接起来就可以跑了。

这就是之前存储数据到结构体里的好处,直接拿出来构造请求就可以,而且还可以用数组存放多个用户的请求。让我们的客户端在访问的时候自己指定一些id之类的。让关卡中的多个玩家分别和ai对话。这里主要靠客户端大哥帮我擦屁股吧。因为我还不够强。

posted @ 2025-08-15 16:53  抓泥鳅的小老虎  阅读(7)  评论(0)    收藏  举报