当前位置: 首页 > news >正文

UE4 C++联网RPC教程笔记(一)(第1~4集)

UE4 C++联网RPC教程笔记(一)(第1~4集)

  • 前言
  • 1. 教程介绍与资源
  • 2. 自定义 Debug 功能
  • 3. Actor 的复制
  • 4. 联网状态判断

前言

本系列笔记将会对梁迪老师的《UE4C++联网RPC框架开发吃鸡》教程进行个人的知识点梳理与总结,此课程也像全反射零耦合框架的课程那样,已经超过报名截止时间了,无法通过正常方法观看。

笔者依旧是采取神奇的方法,通过手机浏览器(不同浏览器的效果有差别,有的会直接要求你登录,遇到这样的就换一个;还有可能点开网页会发现没有播放按钮,遇到这样的就换一个网页)搜索该课程后可以在课程预览界面观看,也可以在目录进行跳转,不过没有字幕。建议是在 PC 端的手机模拟器观看。

本课程集数不多,可以通过目录跳转看完,就不需要复制一串数字到 URL 来切换集数了。

笔者用的引擎版本是 4.26.2,老师推荐的引擎版本是 4.20,不同的版本可能在代码上有所区别,笔者会通过注释标明。

本系列文章不允许转载。

本系列笔记可供读者学习后用于复习回顾或参考代码来解决一些敲错了代码导致的 Bug。并且笔者只会贴出对应集数修改的代码内容,已经有了的部分代码基本都不会贴出来,以免笔记篇幅过长。

1. 教程介绍与资源

此处列出本课程需要翻阅的网址:虚幻文档关于 RPC 的讲解 >>【】

RPC 的全称是 Remote Procedure Calls 远程过程调用。

本课程篇幅较短,分两步走:1. RPC 基础 2. 分别用蓝图和 C++ 实现监听服务器。

2. 自定义 Debug 功能

打开 UE4,创建一个新的 C++ 第三人称游戏项目,需带有初学者内容包,命名为 RPCCourse

如果学过梁迪老师另一个课程《UE4全反射零耦合框架开发坦克游戏》的读者可能会有印象,因为这个自定义 Debug 功能也在那个课程里面实现了,学过的读者可自行决定是否再看一遍。

创建以下 C++ 类:

创建一个 Object,命名为 RPCHelper,路径为默认。

要实现自定义 Debug 功能,我们需要用到单例模式

RPCHelper.h

#include "CoreMinimal.h"
// 引入头文件
#include "Engine/GameEngine.h"class RPCCOURSE_API DDRecord
{
private:// 自身单例static TSharedPtr<DDRecord> RecordInst;// 最终输出的字符串FString RecordInfo;// 显示时长float ShowTime;// 显示的颜色FColor ShowColor;public:// 构造和析构函数不写内容,并且由于可能会被大量调用所以写成内联函数inline DDRecord() {}~DDRecord() {}static TSharedPtr<DDRecord> Get();// 初始化显示时长和颜色inline void InitParam(float InTime, FColor InColor){ShowTime = InTime;ShowColor = InColor;}// 实际依赖引擎自带输出逻辑inline void Output(){if (GEngine)GEngine->AddOnScreenDebugMessage(-1, ShowTime, ShowColor, RecordInfo);// 清空最终输出字符串RecordInfo.Empty();}// 移位操作符重写,将传入的各种类型数据都转换成 FString 然后加入最终输出字符串inline DDRecord &operator<<(FString Info) { RecordInfo.Append(Info); return *this; }inline DDRecord &operator<<(FName Info) { RecordInfo.Append(Info.ToString()); return *this; }inline DDRecord &operator<<(FText Info) { RecordInfo.Append(Info.ToString()); return *this; }inline DDRecord &operator<<(const char* Info) { RecordInfo += Info; return *this; }inline DDRecord &operator<<(const char Info) { RecordInfo.AppendChar(Info); return *this; }inline DDRecord &operator<<(int32 Info) { RecordInfo.Append(FString::FromInt(Info)); return *this; }inline DDRecord &operator<<(float Info) { RecordInfo.Append(FString::SanitizeFloat(Info)); return *this; }inline DDRecord &operator<<(double Info) { RecordInfo.Append(FString::SanitizeFloat(Info)); return *this; }inline DDRecord &operator<<(bool Info) { RecordInfo.Append(Info ? FString("true") : FString("false")); return *this; }inline DDRecord &operator<<(FVector2D Info) { RecordInfo.Append(Info.ToString()); return *this; }inline DDRecord &operator<<(FVector Info) { RecordInfo.Append(Info.ToString()); return *this; }inline DDRecord &operator<<(FRotator Info) { RecordInfo.Append(Info.ToString()); return *this; }inline DDRecord &operator<<(FQuat Info) { RecordInfo.Append(Info.ToString()); return *this; }inline DDRecord &operator<<(FTransform Info) { RecordInfo.Append(Info.ToString()); return *this; }inline DDRecord &operator<<(FMatrix Info) { RecordInfo.Append(Info.ToString()); return *this; }inline DDRecord &operator<<(FColor Info) { RecordInfo.Append(Info.ToString()); return *this; }inline DDRecord &operator<<(FLinearColor Info) { RecordInfo.Append(Info.ToString()); return *this; }// 在遇到 DDRecord 对象时(即下文的 Endl)输出,即调用 Output()inline void operator<<(DDRecord& Record) { Record.Output(); }
};namespace DDH
{FORCEINLINE DDRecord& Debug(float InTime = 3000.f, FColor InColor = FColor::Yellow){DDRecord::Get()->InitParam(InTime, InColor);return *DDRecord::Get();}FORCEINLINE DDRecord& Endl(){return *DDRecord::Get();}
}

RPCHelper.cpp

TSharedPtr<DDRecord> DDRecord::RecordInst = NULL;TSharedPtr<DDRecord> DDRecord::Get()
{if (!RecordInst.IsValid())RecordInst = MakeShareable(new DDRecord());return RecordInst;
}

接下来到第三人称项目自带的这个 RPCCourseCharacter,重写它的 BeginPlay() 方法来测试一下我们的自定义 Debug 功能。

RPCCourseCharacter.h

protected:void BeginPlay() override;

RPCCourseCharacter.cpp

// 引入头文件
#include "RPCHelper.h"void ARPCCourseCharacter::BeginPlay()
{Super::BeginPlay();// 输出 DebugDDH::Debug(20.f, FColor::Red) << "Hello UE4 " << 123 << 0.888 << FVector(30, 40, 50) << FColor::Red << DDH::Endl();
}

编译后运行游戏,左上角输出红色的 Debug 语句,20 秒后消失。

在这里插入图片描述
继续改进下,让 Debug 支持更多输出方式,比如输出到 Output Log 控制台里,并且有日志记录、警告、报错模式。

RPCHelper.h

class RPCCOURSE_API DDRecord
{
public:// 状态模式,0:Debug,1:Log,2:Warning,3:Erroruint8 PatternID;public:inline void Output(){switch (PatternID) {case 0:{if (GEngine)GEngine->AddOnScreenDebugMessage(-1, ShowTime, ShowColor, RecordInfo);}break;case 1:{UE_LOG(LogTemp, Log, TEXT("%s"), *RecordInfo);}break;case 2:{UE_LOG(LogTemp, Warning, TEXT("%s"), *RecordInfo);}break;case 3:{UE_LOG(LogTemp, Error, TEXT("%s"), *RecordInfo);}break;}RecordInfo.Empty();}
};namespace DDH
{FORCEINLINE DDRecord& Debug(float InTime = 3000.f, FColor InColor = FColor::Yellow){DDRecord::Get()->PatternID = 0;		// 初始化DDRecord::Get()->InitParam(InTime, InColor);return *DDRecord::Get();}// 只改变输出颜色,不管显示时间FORCEINLINE DDRecord& Debug(FColor InColor){return Debug(3000.f, InColor);}FORCEINLINE DDRecord& Log(){DDRecord::Get()->PatternID = 1;return *DDRecord::Get();}FORCEINLINE DDRecord& Warning(){DDRecord::Get()->PatternID = 2;return *DDRecord::Get();}FORCEINLINE DDRecord& Error(){DDRecord::Get()->PatternID = 3;return *DDRecord::Get();}
}

最后测试一下日志记录模式。

RPCCourseCharacter.cpp

void ARPCCourseCharacter::BeginPlay()
{Super::BeginPlay();DDH::Log() << "Hello UE4 " << 123 << 0.888 << FVector(30, 40, 50) << FColor::Red << DDH::Endl();
}

编译后,打开 Window -> Develop Tools -> Output Log,运行游戏,可以看到日志输出了 Debug 语句。

在这里插入图片描述

最后将 BeginPlay() 里的 Debug 语句删除掉。

3. Actor 的复制

以下知识点内容截取自梁迪老师准备的 RPC 联网文档:

(1)bool 变量 bNetLoadOnClient
这个变量是给一开始就放置在场景中的对象使用的。
如果bNetLoadOnClient 设置为 true,当客户端连接上服务端时,客户端也会存在这个对象。
如果 bNetLoadOnClient 设置为 false,当客户端连接上服务端时,客户端不会存在这个对象。
SetReplicates 无论是否为 true 都不会影响这个变量的作用。(关于 SetReplicates 下面会讲解)

在默认路径下新建 3 个 C++ 的 Actor 类,分别命名为 RPCActorCubeReplicateCubeNoReplicate

我们先用 RPCActor 来测试 bNetLoadOnClient

RPCActor.cpp

ARPCActor::ARPCActor()
{bNetLoadOnClient = false;	// 设置为 不网络同步到客户端
}

来到角色类,在 BeginPlay() 里输出场上 RPCActor 实例的数量。

RPCCourseCharacter.cpp

// 引入头文件
#include "Kismet/GameplayStatics.h"
#include "RPCActor.h"void ARPCCourseCharacter::BeginPlay()
{Super::BeginPlay();// 寻找场景中的 RPCActorTArray<AActor*> ActArray;UGameplayStatics::GetAllActorsOfClass(GetWorld(), ARPCActor::StaticClass(), ActArray);DDH::Debug() << "RPCActor Num --> " << ActArray.Num() << DDH::Endl();
}

编译后,将 C++ 类的 RPCActor 拖进场景,随后作以下设置(对于 4.26 版本会多出第 2 步)。运行后结果如图所示。

在这里插入图片描述
输出场景内 RPCActor 的数量为 1 的语句是服务端发出来的,输出数量为 0 的语句则是客户端发出来的。至于为何会分别输出了两遍,是因为在编辑器中,调用引擎自带的屏幕输出方法,会使语句在每个端都输出一次。

同时因为 RPCActor 设置了 bNetLoadOnClientfalse,所以 RPCActor 只存在于服务端。

RPCActor.cpp

ARPCActor::ARPCActor()
{// 重新设置为 网络同步到客户端bNetLoadOnClient = true;
}

编译后,将原本场景里的 RPCActor 删除,重新放置一个 RPCActor。运行游戏,结果如图所示:

在这里插入图片描述
这时服务端和客户端的场景里都存在这个 RPCActor 的实例。

(2)SetReplicates(bool)
调用 SetReplicates(true) 设置 Actor 可以复制。
调用 SetReplicates(false) 设置 Actor 不可以复制。

当在服务端 Spawn 可复制的 Actor 时,客户端会生成。
当在客户端 Spawn 可复制的 Actor 时,其他端不会生成。

测试下 SetReplicates(bool)bNetLoadOnClient 共同作用是什么样的效果。

RPCActor.cpp

ARPCActor::ARPCActor()
{// bNetLoadOnClient 设置为 true 时,如果该对象是一开始就在场景中的对象,// 客户端连接到服务端时该对象也会存在,与 SetReplicates 是否为 true 没有关系SetReplicates(true);bNetLoadOnClient = false;
}

编译后,将原本场景里的 RPCActor 删除,重新放置一个 RPCActor。运行游戏,结果如图所示:

在这里插入图片描述
又变成了客户端没有 RPCActor,服务端有。说明确实 bNetLoadOnClient 的优先级比 SetReplicates(bool) 更高。

接下来单独测试一下 SetReplicates(bool)。我们给 CubeReplicate 和 CubeNoReplicate 添加一些组件方便观察,前者设置可复制,后者设置不可复制。

CubeReplicate.h

protected:UStaticMeshComponent* CubeMesh;

CubeReplicate.cpp

 // 引入头文件
#include "Components/StaticMeshComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "RPCHelper.h"ACubeReplicate::ACubeReplicate()
{PrimaryActorTick.bCanEverTick = true;// 设置复制SetReplicates(true);RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));CubeMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CubeMesh"));CubeMesh->SetupAttachment(RootComponent);// 附加模型ConstructorHelpers::FObjectFinder<UStaticMesh> StaticCubeMesh(TEXT("StaticMesh'/Game/StarterContent/Shapes/Shape_Cylinder.Shape_Cylinder'"));CubeMesh->SetStaticMesh(StaticCubeMesh.Object);
}

CubeNoReplicate.h

protected:UStaticMeshComponent* CubeMesh;

CubeNoReplicate.cpp

 // 引入头文件
#include "Components/StaticMeshComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "RPCHelper.h"ACubeNoReplicate::ACubeNoReplicate()
{PrimaryActorTick.bCanEverTick = true;// 设置不复制SetReplicates(false);RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootScene"));CubeMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("CubeMesh"));CubeMesh->SetupAttachment(RootComponent);// 附加模型ConstructorHelpers::FObjectFinder<UStaticMesh> StaticCubeMesh(TEXT("StaticMesh'/Game/StarterContent/Shapes/Shape_WideCapsule.Shape_WideCapsule'"));CubeMesh->SetStaticMesh(StaticCubeMesh.Object);
}

来到 RPCActor,让前面的两个 Cube 只在服务端生成。

RPCActor.cpp

// 引入头文件
#include "CubeReplicate.h"
#include "CubeNoReplicate.h"
#include "RPCHelper.h"ARPCActor::ARPCActor()
{//SetReplicates(true);bNetLoadOnClient = true;	// 设置为 网络同步到客户端
}void ARPCActor::BeginPlay()
{Super::BeginPlay();// 判断是不是服务端if (GetWorld()->IsServer()) {GetWorld()->SpawnActor<ACubeReplicate>(ACubeReplicate::StaticClass(), GetActorLocation() + FVector::RightVector * 300.f, FQuat::Identity.Rotator());GetWorld()->SpawnActor<ACubeNoReplicate>(ACubeNoReplicate::StaticClass(), GetActorLocation() - FVector::RightVector * 300.f, FQuat::Identity.Rotator());}
}

编译后,将场景内的 RPCActor 删除。

新建一个 Blueprint 文件夹,在里面创建一个基于 RPCActor 的蓝图,命名为 RPCActor_BP,然后将其拖进场景内。

运行游戏,得到效果如图。可以看到服务端出现了 CubeReplicate 和 CubeNoReplicate 的实例,但是客户端没有 CubeNoReplicate 的实例。说明 SetReplicates(bool) 生效了。

在这里插入图片描述
再试一下只在客户端生成,只需要在判断条件前面加个 ! 取反就可以了。

RPCActor.cpp

void ARPCActor::BeginPlay()
{Super::BeginPlay();// 判断是不是客户端if (!GetWorld()->IsServer()) {GetWorld()->SpawnActor<ACubeReplicate>(ACubeReplicate::StaticClass(), GetActorLocation() + FVector::RightVector * 300.f, FQuat::Identity.Rotator());GetWorld()->SpawnActor<ACubeNoReplicate>(ACubeNoReplicate::StaticClass(), GetActorLocation() - FVector::RightVector * 300.f, FQuat::Identity.Rotator());}
}

编译后再次运行,可以看到这回服务端场景内没有生成两个 Actor 的实例,而客户端出现了 CubeReplicate 和 CubeNoReplicate 的实例。说明客户端内生成对象时,即便这个对象是可复制的,它也不会生成到服务端。

在这里插入图片描述

最后将 RPCCourseCharacter.cpp 的 BeginPlay() 方法里的 Debug 语句注释掉。

4. 联网状态判断

本节课需要看的官方参考文档:网络概述 >>【】

以下知识点内容截取自梁迪老师准备的 RPC 联网文档:

(1)AActor 的 HasAuthority(),返回 true 是说明 Actor 是该端创建的角色。

在关卡蓝图或者是 GameMode 以及默认放在场景中的 Actor 等对象使用这个函数可以用来判断是否是服务端,因为关卡蓝图和 GameMode 与默认放在场景中的对象可以看做是由服务端生成的。

不推荐用 HasAuthority() 来判断当前端是不是服务端。梁迪老师推荐的用法是用来它来做下面这个 Actor 的角色判断。

(2)Actor 的角色判断
AActor 里的 ENetRole Role 枚举是用来识别角色的 Actor 的身份的。ENetRole 的几个值:

ROLE_None:该 Actor 在网络游戏中无角色,不会复制。
ROLE_SimulatedProxy:这个 Actor 是其他客户端在本机客户端的一个模拟代理
ROLE_AutonomousProxy:这个 Actor 是本机客户端的自己控制的角色
ROLE_Authority:这个 Actor 是服务器上的 Actor
ROLE_MAX:官方没有解释,笔者个人猜测应该是代表该枚举的最大枚举值。

(3)是否是服务端判断,不推荐使用 HasAuthority() 来判断
推荐使用 GetWorld()->IsServer() 或者 GetNetMode() 判断

(4)端的判断
使用 GetNetMode() 函数可以获取端的属性 ENetMode,分类如下:

NM_Standalone:单独端,单机游戏
NM_DedicatedServer:专用服务器
NM_ListenServer:监听服务器
NM_Client:客户端
NM_MAX:官方没有解释,笔者个人猜测应该是代表该枚举的最大枚举值。

接下来我们打算测试一下 IsServer()HasAuthority() 在 “判断当前端是不是服务端” 的需求上表现如何。

在复制 Cube 和不复制 Cube 的 BeginPlay() 函数输出一下调用上面两个方法后返回的结果。

CubeReplicate.cpp

void ACubeReplicate::BeginPlay()
{Super::BeginPlay();DDH::Debug() << "IsServer --> " << GetWorld()->IsServer() << " ; HasAuthority() --> " << HasAuthority() << "  ACubeReplicate BeginPlay" << DDH::Endl();
}

CubeNoReplicate.cpp

void ACubeNoReplicate::BeginPlay()
{Super::BeginPlay();DDH::Debug() << "IsServer --> " << GetWorld()->IsServer() << " ; HasAuthority() --> " << HasAuthority() << "  ACubeNoReplicate BeginPlay" << DDH::Endl();
}

接上一节课结尾的代码,此时两个 Cube 的生成逻辑是在客户端上运行的,所以只有在客户端才会生成这两个 Cube,服务端不会生成。

编译后运行,可以看到左上角客户端输出的两句 Debug 信息,IsServer() 返回的结果是 false,符合预期;而 HasAuthority() 返回的结果是 true,说明这个方法不一定能判断当前端是服务端还是客户端。

在这里插入图片描述
重新调整下,让两个 Cube 在服务端生成。

RPCActor.cpp

void ARPCActor::BeginPlay()
{Super::BeginPlay();// 将判断表达式的 ! 去掉if (GetWorld()->IsServer()) {GetWorld()->SpawnActor<ACubeReplicate>(ACubeReplicate::StaticClass(), GetActorLocation() + FVector::RightVector * 300.f, FQuat::Identity.Rotator());GetWorld()->SpawnActor<ACubeNoReplicate>(ACubeNoReplicate::StaticClass(), GetActorLocation() - FVector::RightVector * 300.f, FQuat::Identity.Rotator());}
}

编译后运行,下面和中间的 Debug 语句是服务端打印的,上面的 Debug 语句是客户端打印的。此时 HasAuthority() 确实在服务端则输出了 true,在客户端输出了 false。两次测试结果相比之下还是 IsServer() 更适合用于判断当前端的性质。

在这里插入图片描述
随后将 CubeReplicate 和 CubeNoReplicate 的 BeginPlay() 内的 Debug 语句注释掉。

接下来我们测试一下 AActor 的 GetNetMode() 方法,用于获取当前端类型 ENetMode 的值。

RPCActor.h

protected:// 获取端类型的枚举后以文本形式输出void EchoNetMode();

RPCActor.cpp

void ARPCActor::BeginPlay()
{// 测试完毕后记得注释掉EchoNetMode();
}void ARPCActor::EchoNetMode()
{ENetMode NetMode = GetNetMode();switch (NetMode){case NM_Standalone:DDH::Debug() << "NM_Standalone" << DDH::Endl();break;case NM_DedicatedServer:DDH::Debug() << "NM_DedicatedServer" << DDH::Endl();break;case NM_ListenServer:DDH::Debug() << "NM_ListenServer" << DDH::Endl();break;case NM_Client:DDH::Debug() << "NM_Client" << DDH::Endl();break;case NM_MAX:DDH::Debug() << "NM_MAX" << DDH::Endl();break;}
}

编译后运行游戏,很明显 Client 是客户端输出的,ListenServer 是服务端输出的。

在这里插入图片描述
如果将运行模式调整如下后运行游戏,则会显示 NM_Standalone。

在这里插入图片描述

相关文章:

UE4 C++联网RPC教程笔记(一)(第1~4集)

UE4 C联网RPC教程笔记&#xff08;一&#xff09;&#xff08;第1~4集&#xff09; 前言1. 教程介绍与资源2. 自定义 Debug 功能3. Actor 的复制4. 联网状态判断 前言 本系列笔记将会对梁迪老师的《UE4C联网RPC框架开发吃鸡》教程进行个人的知识点梳理与总结&#xff0c;此课程…...

备战蓝桥杯 Day11(滚动数组优化+完全背包)

01背包的滚动数组优化 【题目描述】 经典0—1背包问题,有n个物品&#xff0c;编号为i的物品的重量为w[i]&#xff0c;价值为c[i]&#xff0c;现在要从这些物品中选一些物品装到一个容量为m的背包中&#xff0c;使得背包内物体在总重量不超过m的前提下价值尽量大。 #include&…...

Java SE 入门到精通—4.抽象类与接口【Java】

抽象类 同接口一样&#xff0c;用来约束子类&#xff0c;限制子类必须拥有某些方法&#xff0c;比普通类多了个抽象方法&#xff0c;用抽象方法该类必为抽象类 概念 没有具体的对象&#xff0c;具体的方法的一个类 abstract关键字声明为抽象类/方法 一个类中有抽象方法则该…...

Python 开发转 Java 简易路线 - 更新中

有了 Python 开发基础&#xff0c;Java 的内容都可以快速过一遍&#xff0c;复杂地方跟着写一遍。 一、基础 1、Java 基础&#xff1a;尚硅谷 - Java基础 全部快速过一遍&#xff0c; 2、数据库&#xff1a;略。 着重 mysql 高级部分&#xff08;针对面试&#xff09;&…...

Python编程语言学习

1.Python 特点 Python是一种简单、易读、易学和高效的编程语言&#xff0c;具有以下特点&#xff1a; 简单易学&#xff1a;Python采用清晰简洁的语法&#xff0c;注重代码的可读性和可维护性&#xff0c;使得初学者能够快速上手并编写出清晰的代码。 面向对象&#xff1a;Py…...

Cartographer框架简述

catographer框架分为前端和后端 前端包括雷达数据处理&#xff1b;位姿预测&#xff1b;扫描匹配和栅格地图更新。 后端包括后端&#xff1a;线程池任务与调度&#xff1b;向位姿图添加节点&#xff0c;计算节点的子图内约束和子图间约束&#xff08;回环检测&#xff09;&…...

适用于 Linux、Windows 和 macOS 的免费 ONLYOFFICE 桌面应用程序

前言&#xff1a; 最近也是发现了一款特别好用的免费ONLYOFFICE 桌面应用程序忍不住分享给大家&#xff0c;这款编辑器能够打开、阅读和编辑多种文件类型&#xff0c;包括.docx文档、.pptx幻灯片和.xlsx表格等开放XML格式的Office文档。此外&#xff0c;ONLYOFFICE桌面编辑器还…...

C++面向对象程序设计-北京大学-郭炜【课程笔记(四)】

C面向对象程序设计-北京大学-郭炜【课程笔记&#xff08;四&#xff09;】 1、this指针1.1、this指针的作用1.2、this指针和静态成员函数 2、静态成员变量和静态成员函数2.1、基本概念2.2、基本概念总结2.3、如何访问静态成员2.4、静态成员变量的使用场景&#xff08;重要&…...

前端构建效率优化之路

项目背景 我们的系统&#xff08;一个 ToB 的 Web 单页应用&#xff09;前端单页应用经过多年的迭代&#xff0c;目前已经累积有大几十万行的业务代码&#xff0c;30 路由模块&#xff0c;整体的代码量和复杂度还是比较高的。 项目整体是基于 Vue TypeScirpt&#xff0c;而构…...

react实现拖拽的插件

插件一&#xff1a;dnd-kit 插件官网链接https://docs.dndkit.com/introduction/installation 插件二&#xff1a;react-beautiful-dnd https://github.com/atlassian/react-beautiful-dnd/tree/master 两个插件的区别&#xff1a; 插件一可以做到从区域A拖住到区域B 插件二…...

解决Uncaught SyntaxError: Cannot use import statement outside a module(at XXX)报错

报错原因&#xff1a;这个错误通常是因为你正在尝试在一个不支持 ES6 模块语法的环境中使用 import 语句。这可能是因为你的代码是在一个只支持 CommonJS 或 AMD 模块系统的环境中运行的&#xff0c;或者你的代码运行的环境没有正确配置以支持 ES6 模块。如果是在浏览器环境&am…...

PHP如何利用post与get方式传值接收数据

目录 一、POST传值1. 使用curl库发送 POST 请求&#xff1a;2. 使用file_get_contents()函数发送 POST 请求&#xff1a;3. 使用stream_socket_client()函数发送 POST 请求&#xff1a;4. 利用from表单提交数据&#xff1a; 二、GET传值1. 使用http_build_query()函数构建 URL …...

在Mac上搭建MongoDB环境

最近工作中需要装MongoDB环境&#xff0c;搭建过程中遇到了一些问题&#xff0c;在这里记录一下安装MongoDB环境的方法以及问题的解决方法。有两种安装MongoDB的方法&#xff1a;brew安装和手动安装。 目录 使用Homebrew安装MongoDB 手动安装MongoDB&#xff08;不使用Homebr…...

第三十九天| 62.不同路径、63. 不同路径 II

Leetcode 62.不同路径 题目链接&#xff1a;62 不同路径 题干&#xff1a;一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “…...

提高代码质量的 10 条编码原则

提高代码质量的 10 条编码原则 本文转自 公众号 ByteByteGo&#xff0c;如有侵权&#xff0c;请联系&#xff0c;立即删除 今天来聊聊提高代码质量的 10 条编码原则。 软件开发需要良好的系统设计和编码标准。我们在下图中列出了 10 条良好的编码原则。 01 遵循代码规范 我们…...

SHERlocked93 的 2017 年终总结

回家的路上有点无聊&#xff0c;简短回顾一下2017年的得失收获 开始两个月3月到5月用C#完结了一个烂尾的wpf小项目&#xff0c;对自己前半年的.net生涯也算是一个句号&#xff08;虽然不知道最后有没有采用&#xff09;&#xff0c;后面由于项目组转变技术栈&#xff0c;选择了…...

【FreeRTOS基础入门】任务通知

文章目录 前言一、任务通知介绍1.1 任务通知怎么通信1.2 任务通知与其他通信方式的区别1.3 优势及限制任务通知的优势任务通知的限制 1.4 内部原理 二、任务通知的使用2.1 发出与接收通知简化版2.1 发出与接收通知专业版 总结 前言 FreeRTOS 提供了丰富而灵活的任务通知机制&a…...

python opencv比较图片相似度

目录 一:均值哈希算法 二:三直方图算法 三:单通道直方图 一:均值哈希算法 均值哈希算法是一种快速比较图像相似度的方法。它首先将图像转化为灰度图像,然后计算图像的均值,接着将每个像素的...

校园兼职|大学生校园兼职小程序|基于微信小程序的大学生校园兼职系统设计与实现(源码+数据库+文档)

大学生校园兼职小程序目录 目录 基于微信小程序的大学生校园兼职系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户​微信端功能模块​ 2、管理员服务端功能模块 &#xff08;1&#xff09; 兼职管理 &#xff08;2&#xff09;论坛管理 &#xff08;3&…...

linux系统离线安装docker服务教程

1、下载、上传docker-20.10.0.tgz压缩包至服务器&#xff0c;其中&#xff0c;docker下载地址https://download.docker.com/linux/static/stable/x86_64/ 2、新建安装docker脚本docker-install.sh #!/usr/bin/env bash tar -xvf docker-20.10.0.tgzcp docker/* /usr/bin/cat …...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

Ubuntu Cursor升级成v1.0

0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开&#xff0c;快捷键也不好用&#xff0c;当看到 Cursor 升级后&#xff0c;还是蛮高兴的 1. 下载 Cursor 下载地址&#xff1a;https://www.cursor.com/cn/downloads 点击下载 Linux (x64) &#xff0c;…...

安卓基础(Java 和 Gradle 版本)

1. 设置项目的 JDK 版本 方法1&#xff1a;通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分&#xff0c;设置 Gradle JDK 方法2&#xff1a;通过 Settings File → Settings... (或 CtrlAltS)…...

node.js的初步学习

那什么是node.js呢&#xff1f; 和JavaScript又是什么关系呢&#xff1f; node.js 提供了 JavaScript的运行环境。当JavaScript作为后端开发语言来说&#xff0c; 需要在node.js的环境上进行当JavaScript作为前端开发语言来说&#xff0c;需要在浏览器的环境上进行 Node.js 可…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物&#xff0c;因为每个访问一个线程局部变量的线程&#xff08;通过其 get 或 set 方法&#xff09;都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段&#xff0c;这些类希望将…...