OpenHarmony外设驱动使用 (五),Fingerprint_auth

OpenHarmony外设驱动使用 (五)


Fingerprint_auth

概述

功能简介

指纹认证是端侧设备不可或缺的功能,为设备提供用户认证能力,可应用于设备解锁、支付、应用登录等身份认证场景。用户注册指纹后,指纹认证模块就可为设备提供指纹认证的功能。指纹认证功能整体框架如图1。

基于HDF(Hardware Driver Foundation)驱动框架开发的Fingerprint_auth驱动,能够屏蔽硬件器件差异,为上层用户认证框架和Fingerprint_auth服务提供稳定的指纹认证基础能力接口,包括指纹认证执行器列表查询、执行器信息查询、指定指纹模板ID查询模板信息、用户认证框架和执行器间的指纹模板信息对账、指纹的录入、删除、认证和识别等。

图1 指纹认证功能整体框架

image

基本概念

用户认证框架与各基础认证服务组成的身份认证系统支持用户认证凭据设置、删除、认证等基础功能。系统支持用户身份认证,需要提供数据采集、处理、存储及比对能力。

  • 执行器

    执行器是能够提供以上能力的处理模块,各基础认证服务提供执行器能力,被身份认证框架调度完成各项基础能力。

  • 执行器安全等级

    执行器提供能力时所在运行环境达到的安全级别。

  • 执行器角色

    • 全功能执行器:执行器可独立处理一次凭据注册和身份认证请求,即可提供用户认证数据采集、处理、储存及比对能力。

    • 采集器:执行器提供用户认证时的数据采集能力,需要和认证器配合完成用户认证。

    • 认证器:认证器提供用户认证时数据处理能力,读取存储的凭据模板与当前认证信息完成比对。

  • 执行器类型

    同一种身份认证类型的不同认证方式会产生认证算法差异,设备器件差异也会导致算法差异,执行器根据支持的算法类型差异或对接的器件差异,会定义不同的执行器类型。

  • 用户认证框架公钥 & 执行器公钥

    用户身份认证处理需要保证用户数据安全以及认证结果的准确性,用户认证框架与基础认证服务间的关键交互信息需要做数据完整性保护,各基础认证服务将提供的执行器能力对接到用户认证框架时,需要交换各自的公钥,其中:

    1)执行器通过用户认证框架公钥校验调度指令的准确性。

    2)执行器公钥可被用户认证框架用于校验认证结果的准确性,同时用于执行器交互认证时的校验交互信息的完整性。

  • 认证凭据

    在用户设置认证凭据时认证服务会产生并存储认证凭据,每个模板有一个ID,用于索引模板信息文件,在认证时读取模板信息并用于与当次认证过程中产生的认证数据做对比,完成身份认证。

  • 执行器对账

    用户认证框架统一管理用户身份和凭据ID的映射关系,执行器对接到用户认证框架时,会读取用户身份认证框架内保存的该执行器的模板ID列表,执行器需要与自己维护的模板ID列表进行比对,并删除冗余信息。

  • HAPs

    HAPs(Harmony Ability Packages),广义上指可以安装在OpenHarmony上的应用包,本章节中仅代表Fingerprint_auth驱动的上层应用。

  • IDL接口

    接口定义语言(Interface Definition Language)通过IDL编译器编译后,能够生成与编程语言相关的文件:客户端桩文件,服务器框架文件。本文主要是通过IDL接口生成的客户端和服务端来实现Fingerprint_auth服务和驱动的通信,详细使用方法可参考IDL简介。

  • IPC通信

    IPC(Inter Process Communication),进程间通信是指两个进程的数据之间产生交互,详细原理可参考IPC通信简介。

  • HDI

    HDI(Hardware Device Interface),硬件设备接口,位于基础系统服务层和设备驱动层之间,是提供给硬件系统服务开发者使用的、统一的硬件设备功能抽象接口,其目的是为系统服务屏蔽底层硬件设备差异,具体可参考HDI规范。

运作机制

Fingerprint_auth驱动的主要工作是为上层用户认证框架和Fingerprint_auth服务提供稳定的指纹认证基础能力,保证设备上指纹认证功能可以正常运行。如图2,Fingerprint_auth服务通过执行器列表接口获取执行器信息后,向用户认证框架注册执行器。Fingerprint_auth服务通过执行器功能接口实现和Fingerprint_auth驱动的认证、识别、查询等功能的信息交互。 开发者可基于HDF框架对不同芯片进行各自驱动的开发及HDI层接口的调用。

图2 Fingerprint_auth服务和Fingerprint_auth驱动交互

image

约束与限制

要求设备上具有可信执行环境TEE(Trusted Execution Environment),指纹特征信息高强度加密保存在可信执行环境中。

开发指导

场景介绍

Fingerprint_auth驱动的主要工作是为上层用户认证框架和Fingerprint_auth服务提供稳定的指纹认证基础能力,保证设备上指纹认证功能可以正常运行。为实现上述场景的功能,开发者首先需要基于HDF驱动框架,完成Fingerprint_auth驱动开发,其次实现获取执行器列表接口和认证、识别查询等功能接口。

接口说明

注:以下接口列举的为IDL接口描述生成的对应C++语言函数接口,接口声明见idl文件(/drivers/interface/fingerprint_auth)。 在本文中,指纹凭据的录入、认证、识别和删除相关的HDI接口如表1所示,表2中的回调函数分别用于指纹执行器返回操作结果给框架和返回操作过程中的提示信息给上层应用。

表1 接口功能介绍

接口名称功能介绍
GetExecutorList(std::vector<sptr<IAllInOneExecutor>>& allInOneExecutors)获取V2_0版本执行器列表。
GetExecutorInfo(ExecutorInfo &executorInfo)获取执行器信息,包括执行器类型、执行器角色、认证类型、安全等级、执行器公钥等信息,用于向用户认证框架注册执行器。
OnRegisterFinish(const std::vector<uint64_t>& templateIdList,
const std::vector<uint8_t>& frameworkPublicKey, const std::vector<uint8_t>& extraInfo)
执行器注册成功后,获取用户认证框架的公钥信息;获取用户认证框架的模板列表用于对账。
Enroll(uint64_t scheduleId, const std::vector<uint8_t>& extraInfo,
const sptr<IExecutorCallback>& callbackObj)
录入指纹模板。
Authenticate(uint64_t scheduleId, const std::vector<uint64_t>& templateIdList, bool endAfterFirstFail,
const std::vector<uint8_t>& extraInfo, const sptr<IExecutorCallback>& callbackObj)
认证指纹模板(V2_0版本)。
Identify(uint64_t scheduleId, const std::vector<uint8_t>& extraInfo,
const sptr<IExecutorCallback>& callbackObj)
识别指纹模板。
Delete(const std::vector<uint64_t>& templateIdList)删除指纹模板。
Cancel(uint64_t scheduleId)通过scheduleId取消指定录入、认证、识别操作。
SendCommand(int32_t commandId, const std::vector<uint8_t>& extraInfo,
const sptr<IExecutorCallback>& callbackObj)
指纹认证服务向Fingerprint_auth驱动传递参数的通用接口。
GetProperty(const std::vector<uint64_t>& templateIdList,
const std::vector<int32_t>& propertyTypes, Property& property)
获取执行器属性信息。
SetCachedTemplates(const std::vector<uint64_t> &templateIdList)设置需缓存模板列表。
RegisterSaCommandCallback(const sptr<ISaCommandCallback> &callbackObj)注册SA命令回调。

表2 回调函数介绍

接口名称功能介绍
IExecutorCallback::OnResult(int32_t result, const std::vector<uint8_t>& extraInfo)返回操作的最终结果。
IExecutorCallback::OnTip(int32_t tip, const std::vector<uint8_t>& extraInfo)返回操作的过程交互信息。
ISaCommandCallback::OnSaCommands(const std::vector<SaCommand>& commands)发送命令列表。

开发步骤

以Hi3516DV300平台为例,我们提供了Fingerprint_auth驱动DEMO实例,以下是目录结构及各部分功能简介。

// drivers/peripheral/fingerprint_auth
├── BUILD.gn     # 编译脚本
├── bundle.json  # 组件描述文件
└── hdi_service  # Fingerprint_auth驱动实现├── BUILD.gn    # 编译脚本├── include     # 头文件└── src         # 源文件├── executor_impl.cpp                       # 认证、录入等功能接口实现├── fingerprint_auth_interface_driver.cpp   # Fingerprint_auth驱动入口└── fingerprint_auth_interface_service.cpp  # 获取执行器列表接口实现

下面结合DEMO实例介绍驱动开发的具体步骤。

  1. Fingerprint_auth驱动是基于HDF驱动框架设计,所以开发者需要按照驱动Driver Entry程序,完成Fingerprint_auth驱动框架开发,主要由Bind、Init、Release、Dispatch函数接口实现。关键代码如下,详细代码请参见fingerprint_auth_interface_driver.cpp文件。

    // 通过自定义的HdfFingerprintAuthInterfaceHost对象包含ioService对象和真正的HDI Service实现IRemoteObject对象
    struct HdfFingerprintAuthInterfaceHost {struct IDeviceIoService ioService;OHOS::sptr<OHOS::IRemoteObject> stub;
    };// 服务接口调用响应接口
    static int32_t FingerprintAuthInterfaceDriverDispatch(struct HdfDeviceIoClient *client, int cmdId, struct HdfSBuf *data,struct HdfSBuf *reply)
    {auto *hdfFingerprintAuthInterfaceHost = CONTAINER_OF(client->device->service, struct HdfFingerprintAuthInterfaceHost, ioService);OHOS::MessageParcel *dataParcel = nullptr;OHOS::MessageParcel *replyParcel = nullptr;OHOS::MessageOption option;if (SbufToParcel(data, &dataParcel) != HDF_SUCCESS) {HDF_LOGE("%{public}s: invalid data sbuf object to dispatch", __func__);return HDF_ERR_INVALID_PARAM;}if (SbufToParcel(reply, &replyParcel) != HDF_SUCCESS) {HDF_LOGE("%{public}s: invalid reply sbuf object to dispatch", __func__);return HDF_ERR_INVALID_PARAM;}return hdfFingerprintAuthInterfaceHost->stub->SendRequest(cmdId, *dataParcel, *replyParcel, option);
    }// 初始化接口
    static int HdfFingerprintAuthInterfaceDriverInit(struct HdfDeviceObject *deviceObject)
    {HDF_LOGI("%{public}s: driver init start", __func__);return HDF_SUCCESS;
    }// Fingerprint_auth驱动对外提供的服务绑定到HDF框架
    static int HdfFingerprintAuthInterfaceDriverBind(struct HdfDeviceObject *deviceObject)
    {HDF_LOGI("%{public}s: driver bind start", __func__);auto *hdfFingerprintAuthInterfaceHost = new (std::nothrow) HdfFingerprintAuthInterfaceHost;if (hdfFingerprintAuthInterfaceHost == nullptr) {HDF_LOGE("%{public}s: failed to create create HdfFingerprintAuthInterfaceHost object", __func__);return HDF_FAILURE;}hdfFingerprintAuthInterfaceHost->ioService.Dispatch = FingerprintAuthInterfaceDriverDispatch;hdfFingerprintAuthInterfaceHost->ioService.Open = NULL;hdfFingerprintAuthInterfaceHost->ioService.Release = NULL;auto serviceImpl = OHOS::HDI::FingerprintAuth::V2_0::IFingerprintAuthInterface::Get(true);if (serviceImpl == nullptr) {HDF_LOGE("%{public}s: failed to get of implement service", __func__);delete hdfFingerprintAuthInterfaceHost;return HDF_FAILURE;}hdfFingerprintAuthInterfaceHost->stub = OHOS::HDI::ObjectCollector::GetInstance().GetOrNewObject(serviceImpl,OHOS::HDI::FingerprintAuth::V2_0::IFingerprintAuthInterface::GetDescriptor());if (hdfFingerprintAuthInterfaceHost->stub == nullptr) {HDF_LOGE("%{public}s: failed to get stub object", __func__);delete hdfFingerprintAuthInterfaceHost;return HDF_FAILURE;}deviceObject->service = &hdfFingerprintAuthInterfaceHost->ioService;return HDF_SUCCESS;
    }// 释放Fingerprint_auth驱动中的资源
    static void HdfFingerprintAuthInterfaceDriverRelease(struct HdfDeviceObject *deviceObject)
    {HDF_LOGI("%{public}s: driver release start", __func__);if (deviceObject->service == nullptr) {return;}auto *hdfFingerprintAuthInterfaceHost = CONTAINER_OF(deviceObject->service, struct HdfFingerprintAuthInterfaceHost, ioService);if (hdfFingerprintAuthInterfaceHost != nullptr) {delete hdfFingerprintAuthInterfaceHost;}
    }// 注册Fingerprint_auth驱动入口数据结构体对象
    struct HdfDriverEntry g_fingerprintAuthInterfaceDriverEntry = {.moduleVersion = 1,.moduleName = "drivers_peripheral_fingerprint_auth",.Bind = HdfFingerprintAuthInterfaceDriverBind,.Init = HdfFingerprintAuthInterfaceDriverInit,.Release = HdfFingerprintAuthInterfaceDriverRelease,
    };// 调用HDF_INIT将驱动入口注册到HDF框架中。在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动。当Init调用异常时,HDF框架会调用Release释放驱动资源并退出
    HDF_INIT(g_fingerprintAuthInterfaceDriverEntry);
    
  2. Fingerprint_auth驱动框架开发完成后,Fingerprint_auth驱动需要向Fingerprint_auth服务和统一身份认证注册执行器,所以需要实现获取执行器列表接口。关键代码如下,详细代码请参见fingerprint_auth_interface_service.cpp文件。

    // 执行器实现类
    class AllInOneExecutorImpl : public IAllInOneExecutor {
    public:AllInOneExecutorImpl(struct ExecutorInfo executorInfo);virtual ~AllInOneExecutorImpl() {}private:struct ExecutorInfo executorInfo_; // 执行器信息
    };static constexpr uint16_t SENSOR_ID = 123; // 执行器sensorID
    static constexpr uint32_t EXECUTOR_TYPE = 123; // 执行器类型
    static constexpr size_t PUBLIC_KEY_LEN = 32; // 执行器32字节公钥// 创建HDI服务对象
    extern "C" IFingerprintAuthInterface *FingerprintAuthInterfaceImplGetInstance(void)
    {auto fingerprintAuthInterfaceService = new (std::nothrow) FingerprintAuthInterfaceService();if (fingerprintAuthInterfaceService == nullptr) {IAM_LOGE("fingerprintAuthInterfaceService is nullptr");return nullptr;}return fingerprintAuthInterfaceService;
    }// 获取V2_0执行器列表实现
    int32_t FingerprintAuthInterfaceService::GetExecutorList(std::vector<sptr<IAllInOneExecutor>> &executorList)
    {IAM_LOGI("interface mock start");for (auto executor : executorList_) {executorList.push_back(executor);}IAM_LOGI("interface mock success");return HDF_SUCCESS;
    }
    
  3. 步骤1、2完成后基本实现了Fingerprint_auth驱动和Fingerprint_auth服务对接。接下来需实现执行器每个功能接口,来完成指纹认证基础能力。关键代码如下,详细代码请参见all_in_one_executor_impl.cpp文件。

    // 实现获取执行器信息接口
    int32_t AllInOneExecutorImpl::GetExecutorInfo(ExecutorInfo &executorInfo)
    {IAM_LOGI("interface mock start");executorInfo = executorInfo_;IAM_LOGI("get executor information success");return HDF_SUCCESS;
    }// 实现执行器注册成功后,获取用户认证框架的公钥信息、获取用户认证框架的模板列表接口。将公钥信息保持,模板列表用于和本地的模板做对账
    int32_t AllInOneExecutorImpl::OnRegisterFinish(const std::vector<uint64_t> &templateIdList,const std::vector<uint8_t> &frameworkPublicKey, const std::vector<uint8_t> &extraInfo)
    {IAM_LOGI("interface mock start");static_cast<void>(templateIdList);static_cast<void>(extraInfo);static_cast<void>(frameworkPublicKey);IAM_LOGI("register finish");return HDF_SUCCESS;
    }// 实现指纹录入接口
    int32_t AllInOneExecutorImpl::Enroll(uint64_t scheduleId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
    {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}IAM_LOGI("enroll, result is %{public}d", ResultCode::OPERATION_NOT_SUPPORT);int32_t ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}return HDF_SUCCESS;
    }// 实现指纹认证接口
    int32_t AllInOneExecutorService::Authenticate(uint64_t scheduleId, const std::vector<uint64_t> &templateIdList,const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
    {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);static_cast<void>(templateIdList);static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}IAM_LOGI("authenticate, result is %{public}d", ResultCode::NOT_ENROLLED);int32_t ret = callbackObj->OnResult(ResultCode::NOT_ENROLLED, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}return HDF_SUCCESS;
    }// 实现指纹识别接口
    int32_t AllInOneExecutorService::Identify(uint64_t scheduleId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
    {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}IAM_LOGI("identify, result is %{public}d", ResultCode::OPERATION_NOT_SUPPORT);int32_t ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}return HDF_SUCCESS;
    }// 实现删除指纹模板接口
    int32_t AllInOneExecutorService::Delete(const std::vector<uint64_t> &templateIdList)
    {IAM_LOGI("interface mock start");static_cast<void>(templateIdList);IAM_LOGI("delete success");return HDF_SUCCESS;
    }// 实现通过scheduleId取消指定操作接口
    int32_t AllInOneExecutorService::Cancel(uint64_t scheduleId)
    {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);IAM_LOGI("cancel success");return HDF_SUCCESS;
    }// 实现指纹认证服务向Fingerprint_auth驱动传递参数的通用接口,当前需要实现冻结与解锁模板命令
    int32_t AllInOneExecutorService::SendCommand(int32_t commandId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj)
    {IAM_LOGI("interface mock start");static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}int32_t ret;switch (commandId) {case DriverCommandId::LOCK_TEMPLATE:IAM_LOGI("lock template, result is %{public}d", ResultCode::SUCCESS);ret = callbackObj->OnResult(ResultCode::SUCCESS, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}break;case DriverCommandId::UNLOCK_TEMPLATE:IAM_LOGI("unlock template, result is %{public}d", ResultCode::SUCCESS);ret = callbackObj->OnResult(ResultCode::SUCCESS, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}break;case DriverCommandId::INIT_ALGORITHM:IAM_LOGI("init algorithm, result is %{public}d", ResultCode::SUCCESS);ret = callbackObj->OnResult(ResultCode::SUCCESS, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}break;default:IAM_LOGD("not support DriverCommandId : %{public}d", commandId);ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}}return HDF_SUCCESS;
    }// 实现获取执行器属性接口
    int32_t AllInOneExecutorService::GetProperty(const std::vector<uint64_t> &templateIdList, const std::vector<int32_t> &propertyTypes, Property &property)
    {IAM_LOGI("interface mock start");property = {};IAM_LOGI("get property success");return HDF_SUCCESS;
    }// 实现设置需缓存模板列表接口
    int32_t AllInOneExecutorService::SetCachedTemplates(const std::vector<uint64_t> &templateIdList)
    {IAM_LOGI("interface mock start");IAM_LOGI("set cached templates success");return HDF_SUCCESS;
    }// 实现注册SA命令回调接口
    int32_t AllInOneExecutorService::RegisterSaCommandCallback(const sptr<ISaCommandCallback> &callbackObj)
    {IAM_LOGI("interface mock start");IAM_LOGI("register sa command callback success");return HDF_SUCCESS;
    }
    
  4. 用户身份认证框架支持多driver,当增加driver或者修改driver信息,需要修改如下文件中serviceName2Config。

    // base/user_iam/fingerprint_auth/services/src/fingerprint_auth_service.cpp
    void FingerprintAuthService::StartDriverManager()
    {IAM_LOGI("start");int32_t ret = UserAuth::IDriverManager::Start(HDI_NAME_2_CONFIG);if (ret != UserAuth::ResultCode::SUCCESS) {IAM_LOGE("start driver manager failed");}
    }
    

调测验证

驱动开发完成后,开发者可以通过用户认证API接口开发HAP应用,基于RK3568平台验证。认证和取消功能验证的测试代码如下:

1.发起认证并获取认证结果的测试代码如下:

  // API version 10import type {BusinessError} from '@ohos.base';import userIAM_userAuth from '@ohos.userIAM.userAuth';// 设置认证参数const authParam: userIAM_userAuth.AuthParam = {challenge: new Uint8Array([49, 49, 49, 49, 49, 49]),authType: [userIAM_userAuth.UserAuthType.PIN, userIAM_userAuth.UserAuthType.FINGERPRINT],authTrustLevel: userIAM_userAuth.AuthTrustLevel.ATL3,};// 配置认证界面const widgetParam: userIAM_userAuth.WidgetParam = {title: '请进行身份认证',};try {// 获取认证对象let userAuthInstance = userIAM_userAuth.getUserAuthInstance(authParam, widgetParam);console.info('get userAuth instance success');// 订阅认证结果userAuthInstance.on('result', {onResult(result) {console.info(`userAuthInstance callback result: ${JSON.stringify(result)}`);// 可在认证结束或其他业务需要场景,取消订阅认证结果userAuthInstance.off('result');}});console.info('auth on success');userAuthInstance.start();console.info('auth start success');} catch (error) {const err: BusinessError = error as BusinessError;console.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`);}

2.取消认证的测试代码如下:

  // API version 10import type {BusinessError} from '@ohos.base';import userIAM_userAuth from '@ohos.userIAM.userAuth';const authParam: userIAM_userAuth.AuthParam = {challenge: new Uint8Array([49, 49, 49, 49, 49, 49]),authType: [userIAM_userAuth.UserAuthType.PIN, userIAM_userAuth.UserAuthType.FINGERPRINT],authTrustLevel: userIAM_userAuth.AuthTrustLevel.ATL3,};const widgetParam: userIAM_userAuth.WidgetParam = {title: '请进行身份认证',};try {// 获取认证对象let userAuthInstance = userIAM_userAuth.getUserAuthInstance(authParam, widgetParam);console.info('get userAuth instance success');// 开始认证userAuthInstance.start();console.info('auth start success');// 取消认证userAuthInstance.cancel();console.info('auth cancel success');} catch (error) {const err: BusinessError = error as BusinessError;console.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/81499.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端(vue)学习笔记(CLASS 6):路由进阶

1、路由的封装抽离 将之前写在main.js文件中的路由配置与规则抽离出来&#xff0c;放置在router/index.js文件中&#xff0c;再将其导入回main.js文件中&#xff0c;即可实现路由的封装抽离 例如 //index.js import { createMemoryHistory, createRouter } from vue-routerim…

前后端交互中的绝对路径和相对路径

前端 <form action"hello" method"post"> 1. 不加斜杠 &#xff08;相对路径&#xff0c;如 action"hello"&#xff09; 解析规则&#xff1a;基于当前页面的 URL 路径部分 进行拼接。 假设当前页面 URL 是 http://域名:端口/应用上下文…

在Odoo 18中创建进度条指南

在Odoo 18中创建进度条指南 一、创建进度条模板 首先在名为 progress_bar_widget.xml 的文件中定义一个名为 ProgressBarWidget 的新模板。该模板使用两个CSS类&#xff1a;progress-bar-inner 用于样式化进度条&#xff0c;progress_number 用于显示进度百分比。您可以根据需…

Linux grep 命令详解:常用选项、参数及实战场景

一、grep 命令简介 grep&#xff08;Global Regular Expression Print&#xff09;是 Linux 中用于文本搜索的核心工具&#xff0c;支持正则表达式&#xff0c;能快速定位文件中的目标内容。 二、常用选项&#xff08;Options&#xff09;及英文对照 | 选项 | 英文全称 | 作用 …

【Java-EE进阶】SpringBoot针对某个IP限流问题

目录 简介 1. 使用Guava的RateLimiter实现限流 添加Guava依赖 实现RateLimiter限流逻辑 限流管理类 控制器中应用限流逻辑 2. 使用计数器实现限流 限流管理类 控制器中应用限流逻辑 简介 针对某个IP进行限流以防止恶意点击是一种常见的反爬虫和防止DoS的措施。限流策…

Linux问题排查-找到偷偷写文件的进程

在 Linux 系统中&#xff0c;若要通过已修改的文件找到修改该文件的进程 PID&#xff0c;可以结合以下方法分析&#xff0c;具体取决于文件是否仍被进程打开或已被删除但句柄仍存在&#xff1a; 一、文件仍被进程打开&#xff08;未删除&#xff09; 如果文件当前正在被某个进…

More Effective C++:改善编程与设计(下)

目录 条款19:了解临时对象的来源 条款20:协助完成“返回值优化” 条款21:利用重载技术避免隐式类型转换 条款22:考虑以操作符复合形式&#xff08;op&#xff09;取代其独身形式&#xff08;op&#xff09; 条款23:考虑使用其他程序库 条款24:了解virtual functions、mul…

VTK|类似CloudCompare的比例尺实现2-vtk实现

文章目录 实现类头文件实现类源文件调用逻辑关键问题缩放限制问题投影模式项目git链接实现类头文件 以下是对你提供的 ScaleBarController.h 头文件添加详细注释后的版本,帮助你更清晰地理解每个成员和方法的用途,尤其是在 VTK 中的作用: #ifndef SCALEBARCONTROLLER_H #de…

PostgreSQL 联合索引生效条件

最近面试的时候&#xff0c;总会遇到一个问题 在 PostgreSQL 中&#xff0c;联合索引在什么条件下会生效&#xff1f; 特此记录~ 前置信息 数据库版本 PostgreSQL 14.13, compiled by Visual C build 1941, 64-bit 建表语句 CREATE TABLE people (id SERIAL PRIMARY KEY,c…

SpringBoot项目里面发起http请求的几种方法

在Spring Boot项目中发起HTTP请求的方法 在Spring Boot项目中&#xff0c;有几种常用的方式可以发起HTTP请求&#xff0c;以下是主要的几种方法&#xff1a; 1. 使用RestTemplate (Spring 5之前的主流方式) // 需要先注入RestTemplate Autowired private RestTemplate restT…

《Python星球日记》 第90天:微调的概念以及如何微调大模型?

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、微调原理1. 什么是大模型微调?2. 为什么需要微调?3. 微调的基本流程4. 微调策略分类二、LoRA(Low-Rank Adaptation)技术详解1. LoRA的核…

机器学习-人与机器生数据的区分模型测试 - 模型融合与检验

模型融合 # 先用普通Pipeline训练 from sklearn.pipeline import Pipeline#from sklearn2pmml.pipeline import PMMLPipeline train_pipe Pipeline([(scaler, StandardScaler()),(ensemble, VotingClassifier(estimators[(rf, RandomForestClassifier(n_estimators200, max_de…

怎样免费开发部署自己的网站?

要免费开发自己的网站&#xff0c;您可以根据自己的技术水平和需求选择以下两种主要方式&#xff1a; 零基础用户&#xff1a;建议使用如WordPress.com、Weebly、Strikingly等平台&#xff0c;快速搭建网站。 有一定技术基础的用户&#xff1a;可选择自行开发网站&#xff0c;…

调用百度云API机器翻译

新建Python文件&#xff0c;叫 text_translator.py 输入 import requests import jsonAPI_KEY "glYiYVF2dSc7EQ8n78VDRCpa" # 替换为自己的API Key SECRET_KEY "kUlhze8OQZ7xbVRp" # 替换为自己的Secret Keydef main():# 选择翻译方向while True:di…

OpenAI与微软洽谈新融资及IPO,Instagram因TikTok流失四成用户

OpenAI与微软洽谈新融资及IPO 据悉&#xff0c;OpenAI 正与微软洽谈新融资及筹备 IPO&#xff0c;关键问题是微软在 OpenAI 重组后的股权比例。微软已投资超 130 亿美元&#xff0c;双方修订 2019 年合同&#xff0c;微软拟弃部分股权换新技术访问权。OpenAI 上周放弃了有争议转…

git工具使用详细教程-------命令行和TortoiseGit图形化

下载 git下载地址&#xff1a;https://git-scm.com/downloads TortoiseGit&#xff08;图形化工具&#xff09;下载地址&#xff1a;https://tortoisegit.org/download/ 认识git结构 工作区&#xff1a;存放代码的地方 暂存区&#xff1a;临时存储&#xff0c;将工作区的代码…

构建RAG混合开发---PythonAI+JavaEE+Vue.js前端的实践

7GB显存如何部署bf16精度的DeepSeek-R1 70B大模型&#xff1f;-CSDN博客 服务容错治理框架resilience4j&sentinel基础应用---微服务的限流/熔断/降级解决方案-CSDN博客 conda管理python环境-CSDN博客 快速搭建对象存储服务 - Minio&#xff0c;并解决临时地址暴露ip、短…

【Java ee初阶】jvm(3)

一、双亲委派机制&#xff08;类加载机制中&#xff0c;最经常考到的问题&#xff09; 类加载的第一个环节中&#xff0c;根据类的全限定类名&#xff08;包名类名&#xff09;找到对应的.class文件的过程。 JVM中进行类加载的操作&#xff0c;需要以来内部的模块“类加载器”…

wps excel将表格输出pdf时所有列在一张纸上

记录&#xff1a;wps excel将表格输出pdf时所有列在一张纸上 1&#xff0c;调整缩放比例&#xff0c;或选择将所有列打印在一页 2&#xff0c;将表格的所有铺满到这套虚线

分布式微服务系统架构第134集:笔记1运维服务器经验,高并发,大数据量系统

加群联系作者vx&#xff1a;xiaoda0423 仓库地址&#xff1a;https://webvueblog.github.io/JavaPlusDoc/ https://1024bat.cn/ https://github.com/webVueBlog/fastapi_plus https://webvueblog.github.io/JavaPlusDoc/ ✅ 一、查看端口是否被占用的常用命令 1️⃣ lsof 命令&…