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

鸿蒙开发-HMS Kit能力集(应用内支付、推送服务)

1 应用内支付

开发步骤

步骤一:判断当前登录的华为账号所在服务地是否支持应用内支付

在使用应用内支付之前,您的应用需要向IAP Kit发送queryEnvironmentStatus请求,以此判断用户当前登录的华为帐号所在的服务地是否在IAP Kit支持结算的国家/地区中。

// pages/Index.ets
/*** @description 应用内支付服务示例-消耗型商品* @author 白晓明* @organization 坚果派* @website: nutpi.com.cn* @date 2024-06-01*/
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';@Entry
@Component
struct Index {private context: common.UIAbilityContext = {} as common.UIAbilityContext;@State querying: boolean = true;@State queryingFailed: boolean = false;@State productInfoArray: iap.Product[] = [];@State queryFailedText: string = "查询失败!";showLoadingPage() {this.queryingFailed = false;this.querying = true;}showFailedPage(failedText?: string) {if (failedText) {this.queryFailedText = failedText;}this.queryingFailed = true;this.querying = false;}showNormalPage() {this.queryingFailed = false;this.querying = false;}aboutToAppear(): void {this.showLoadingPage();this.context = getContext(this) as common.UIAbilityContext;this.onCase();}async onCase() {this.showLoadingPage();const queryEnvCode = await this.queryEnv();if (queryEnvCode !== 0) {let queryEnvFailedText = "当前应用不支持IAP Kit服务!";if (queryEnvCode === iap.IAPErrorCode.ACCOUNT_NOT_LOGGED_IN) {queryEnvFailedText = "请通过桌面设置入口登录华为账号后再次尝试!";}this.showFailedPage(queryEnvFailedText);return;}}// 判断当前登录的华为账号所在服务地是否支持应用内支付。async queryEnv(): Promise<number> {try {console.log("IAPKitDemo queryEnvironmentStatus begin.");await iap.queryEnvironmentStatus(this.context);return 0;} catch (error) {promptAction.showToast({message: "IAPKitDemo queryEnvironmentStatus failed. Cause: " + JSON.stringify(error)})return error.code;}}build() {...}
}

步骤二:确保权益发放

用户购买商品后,开发者需要及时发放相关权益。但实际应用场景中,若出现异常(网络错误、进程被中止等)将导致应用无法知道用户实际是否支付成功,从而无法及时发放权益,即出现掉单情况。为了确保权益发放,您需要在以下场景检查用户是否存在已购未发货的商品:

  • 应用启动时。
  • 购买请求返回1001860001时。
  • 购买请求返回1001860051时。

如果存在已购未发货商品,则发放相关权益,然后向IAP Kit确认发货,完成购买。

// pages/Index.ets
/*** @description 应用内支付服务示例-消耗型商品* @author 白晓明* @organization 坚果派* @website: nutpi.com.cn* @date 2024-06-01*/
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';@Entry
@Component
struct Index {private context: common.UIAbilityContext = {} as common.UIAbilityContext;@State querying: boolean = true;@State queryingFailed: boolean = false;@State productInfoArray: iap.Product[] = [];@State queryFailedText: string = "查询失败!";showLoadingPage() {...}showFailedPage(failedText?: string) {...}showNormalPage() {...}aboutToAppear(): void {...}async onCase() {...await this.queryPurchase();}// 判断当前登录的华为账号所在服务地是否支持应用内支付。async queryEnv(): Promise<number> {...}async queryPurchase() {console.log("IAPKitDemo queryPurchase begin.");const queryPurchaseParam: iap.QueryPurchasesParameter = {productType: iap.ProductType.CONSUMABLE,queryType: iap.PurchaseQueryType.UNFINISHED};const result: iap.QueryPurchaseResult = await iap.queryPurchases(this.context, queryPurchaseParam);// 处理订单信息if (result) {const purchaseDataList: string[] = result.purchaseDataList;if (purchaseDataList === undefined || purchaseDataList.length <= 0) {console.log("IAPKitDemo queryPurchase, list empty.");return;}for (let i = 0; i < purchaseDataList.length; i++) {const purchaseData = purchaseDataList[i];const jwsPurchaseOrder = (JSON.parse(purchaseData) as PurchaseData).jwsPurchaseOrder;if (!jwsPurchaseOrder) {console.log("IAPKitDemo queryPurchase, jwsPurchaseOrder invalid.");continue;}const purchaseStr = JWTUtil.decodeJwtObj(jwsPurchaseOrder);const purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;}}}build() {...}
}

步骤三:查询商品信息

通过queryProducts来获取在AppGallery Connect上配置的商品信息。发起请求时,开发者需在请求参数QueryProductsParameter中携带相关的商品ID,并根据实际配置指定其productType。

当接口请求成功时,IAP Kit将返回商品信息Product的列表。 您可以使用Product包含的商品价格、名称和描述等信息,向用户展示可供购买的商品列表。

// pages/Index.ets
/*** @description 应用内支付服务示例-消耗型商品* @author 白晓明* @organization 坚果派* @website: nutpi.com.cn* @date 2024-06-01*/
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';@Entry
@Component
struct Index {private context: common.UIAbilityContext = {} as common.UIAbilityContext;@State querying: boolean = true;@State queryingFailed: boolean = false;@State productInfoArray: iap.Product[] = [];@State queryFailedText: string = "查询失败!";showLoadingPage() {...}showFailedPage(failedText?: string) {...}showNormalPage() {...}aboutToAppear(): void {...}async onCase() {...await this.queryProducts();}// 判断当前登录的华为账号所在服务地是否支持应用内支付。async queryEnv(): Promise<number> {...}// 查询商品信息async queryProducts() {try {console.log("IAPKitDemo queryProducts begin.");const queryProductParam: iap.QueryProductsParameter = {productType: iap.ProductType.CONSUMABLE,productIds: ['nutpi_course_1']};const result: iap.Product[] = await iap.queryProducts(this.context, queryProductParam);this.productInfoArray = result;this.showNormalPage();} catch (error) {this.showFailedPage();}}async queryPurchase() {...}build() {...}
}

步骤四:构建商品列表UI

// pages/Index.ets
/*** @description 应用内支付服务示例-消耗型商品* @author 白晓明* @organization 坚果派* @website: nutpi.com.cn* @date 2024-06-01*/
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';@Entry
@Component
struct Index {private context: common.UIAbilityContext = {} as common.UIAbilityContext;@State querying: boolean = true;@State queryingFailed: boolean = false;@State productInfoArray: iap.Product[] = [];@State queryFailedText: string = "查询失败!";showLoadingPage() {...}showFailedPage(failedText?: string) {...}showNormalPage() {...}aboutToAppear(): void {...}async onCase() {...}// 判断当前登录的华为账号所在服务地是否支持应用内支付。async queryEnv(): Promise<number> {...}// 查询商品信息async queryProducts() {...}async queryPurchase() {...}build() {Column() {Column() {Text('应用内支付服务示例-消耗型').fontSize(18).fontWeight(FontWeight.Bolder)}.width('100%').height(54).justifyContent(FlexAlign.Center).backgroundColor(Color.White)Column() {Column() {Row() {Text('Consumables').fontSize(28).fontWeight(FontWeight.Bold).margin({ left: 24, right: 24 })}.margin({ top: 16, bottom: 12 }).height(48).justifyContent(FlexAlign.Start).width('100%')// 商品列表信息List({ space: 0, initialIndex: 0 }) {ForEach(this.productInfoArray, (item: iap.Product) => {ListItem() {Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {Image($r('app.media.app_icon')).height(48).width(48).objectFit(ImageFit.Contain)Text(item.name).width('100%').height(48).fontSize(16).textAlign(TextAlign.Start).padding({ left: 12, right: 12 })Button(item.localPrice).width(200).fontSize(16).height(30).onClick(() => {this.createPurchase(item.id, item.type)}).stateEffect(true)}.borderRadius(16).backgroundColor('#FFFFFF').alignSelf(ItemAlign.Auto)}})}.divider({ strokeWidth: 1, startMargin: 2, endMargin: 2 }).padding({ left: 12, right: 12 }).margin({ left: 12, right: 12 }).borderRadius(16).backgroundColor('#FFFFFF').alignSelf(ItemAlign.Auto)}.backgroundColor('#F1F3F5').width('100%').height('100%').visibility(this.querying || this.queryingFailed ? Visibility.None : Visibility.Visible)// 加载进度组件Stack() {LoadingProgress().width(96).height(96)}.backgroundColor('#F1F3F5').width('100%').height('100%').visibility(this.querying ? Visibility.Visible : Visibility.None)// 异常文本提示Stack({ alignContent: Alignment.Center }) {Text(this.queryFailedText).fontSize(28).fontWeight(FontWeight.Bold).margin({ left: 24, right: 24 })}.backgroundColor('#F1F3F5').width('100%').height('100%').visibility(this.queryingFailed ? Visibility.Visible : Visibility.None).onClick(() => {this.onCase();})}.width('100%').layoutWeight(1)}.width('100%').height('100%').backgroundColor(0xF1F3F5)}
}

步骤五:发起购买

用户发起购买时,开发者的应用可通过向IAP Kit发送createPurchase请求来拉起IAP Kit收银台。发起请求时,需在请求参数PurchaseParameter中携带开发者此前已在华为AppGallery Connect网站上配置并生效的商品ID,并根据实际配置指定其productType。

// pages/Index.ets
/*** @description 应用内支付服务示例-消耗型商品* @author 白晓明* @organization 坚果派* @website: nutpi.com.cn* @date 2024-06-01*/
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';@Entry
@Component
struct Index {private context: common.UIAbilityContext = {} as common.UIAbilityContext;@State querying: boolean = true;@State queryingFailed: boolean = false;@State productInfoArray: iap.Product[] = [];@State queryFailedText: string = "查询失败!";showLoadingPage() {...}showFailedPage(failedText?: string) {...}showNormalPage() {...}aboutToAppear(): void {...}async onCase() {...}// 判断当前登录的华为账号所在服务地是否支持应用内支付。async queryEnv(): Promise<number> {...}// 查询商品信息async queryProducts() {...}async queryPurchase() {...}/*** 发起购买* @param id AppGallery Connect控制台配置的商品ID* @param type 商品类型*/createPurchase(id: string, type: iap.ProductType) {console.log("IAPKitDemo createPurchase begin.");try {const createPurchaseParam: iap.PurchaseParameter = {productId: id,productType: type};iap.createPurchase(this.context, createPurchaseParam).then(async (result) => {console.log("IAPKitDemo createPurchase success. Data: " + JSON.stringify(result));// 获取PurchaseOrderPayload的JSON字符串const purchaseData: PurchaseData = JSON.parse(result.purchaseData) as PurchaseData;const jwsPurchaseOrder: string = purchaseData.jwsPurchaseOrder;// 解码 JWTUtil为自定义类,可参见Sample Code工程const purchaseStr = JWTUtil.decodeJwtObj(jwsPurchaseOrder);const purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;// 处理发货}).catch((error: BusinessError) => {promptAction.showToast({message: "IAPKitDemo createPurchase failed. Cause: " + JSON.stringify(error)})if (error.code === iap.IAPErrorCode.PRODUCT_OWNED || error.code === iap.IAPErrorCode.SYSTEM_ERROR) {// 参考权益发放检查是否需要补发货,确保权益发放this.queryPurchase();}})} catch (err) {promptAction.showToast({message: "IAPKitDemo createPurchase failed. Error: " + JSON.stringify(err)})}}build() {...}
}

步骤六:完成购买

对PurchaseData.jwsPurchaseOrder解码验签成功后,如果PurchaseOrderPayload.purchaseOrderRevocationReasonCode为空,则代表购买成功,即可发放相关权益。

发货成功后,开发者需在应用中发送finishPurchase请求确认发货,以此通知IAP服务器更新商品的发货状态,完成购买流程。发送finishPurchase请求时,需在请求参数FinishPurchaseParameter中携带PurchaseOrderPayload中的productType、purchaseToken、purchaseOrderId。请求成功后,IAP服务器会将相应商品标记为已发货。

对于消耗型商品,应用成功执行finishPurchase之后,IAP服务器会将相应商品重新设置为可购买状态,用户即可再次购买该商品。

// pages/Index.ets
/*** @description 应用内支付服务示例-消耗型商品* @author 白晓明* @organization 坚果派* @website: nutpi.com.cn* @date 2024-06-01*/
import { common } from '@kit.AbilityKit'
import { iap } from '@kit.IAPKit';
import { JSON } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';
import { promptAction } from '@kit.ArkUI';@Entry
@Component
struct Index {private context: common.UIAbilityContext = {} as common.UIAbilityContext;@State querying: boolean = true;@State queryingFailed: boolean = false;@State productInfoArray: iap.Product[] = [];@State queryFailedText: string = "查询失败!";showLoadingPage() {...}showFailedPage(failedText?: string) {...}showNormalPage() {...}aboutToAppear(): void {...}async onCase() {...}// 判断当前登录的华为账号所在服务地是否支持应用内支付。async queryEnv(): Promise<number> {...}// 查询商品信息async queryProducts() {...}async queryPurchase() {...}/*** 发起购买* @param id AppGallery Connect控制台配置的商品ID* @param type 商品类型*/createPurchase(id: string, type: iap.ProductType) {console.log("IAPKitDemo createPurchase begin.");try {const createPurchaseParam: iap.PurchaseParameter = {productId: id,productType: type};iap.createPurchase(this.context, createPurchaseParam).then(async (result) => {console.log("IAPKitDemo createPurchase success. Data: " + JSON.stringify(result));// 获取PurchaseOrderPayload的JSON字符串const purchaseData: PurchaseData = JSON.parse(result.purchaseData) as PurchaseData;const jwsPurchaseOrder: string = purchaseData.jwsPurchaseOrder;// 解码 JWTUtil为自定义类,可参见Sample Code工程const purchaseStr = JWTUtil.decodeJwtObj(jwsPurchaseOrder);const purchaseOrderPayload = JSON.parse(purchaseStr) as PurchaseOrderPayload;// 处理发货this.finishPurchase(purchaseOrderPayload);}).catch((error: BusinessError) => {promptAction.showToast({message: "IAPKitDemo createPurchase failed. Cause: " + JSON.stringify(error)})if (error.code === iap.IAPErrorCode.PRODUCT_OWNED || error.code === iap.IAPErrorCode.SYSTEM_ERROR) {// 参考权益发放检查是否需要补发货,确保权益发放this.queryPurchase();}})} catch (err) {promptAction.showToast({message: "IAPKitDemo createPurchase failed. Error: " + JSON.stringify(err)})}}finishPurchase(purchaseOrder: PurchaseOrderPayload) {console.log("IAPKitDemo finishPurchase begin.");const finishPurchaseParam: iap.FinishPurchaseParameter = {productType: purchaseOrder.productType,purchaseToken: purchaseOrder.purchaseToken,purchaseOrderId: purchaseOrder.purchaseOrderId};iap.finishPurchase(this.context, finishPurchaseParam).then((result) => {console.log("IAPKitDemo finishPurchase success");}).catch((error: BusinessError) => {promptAction.showToast({message: "IAPKitDemo finishPurchase failed. Cause: " + JSON.stringify(error)})})}build() {...}
}

2 推送服务

开发步骤

步骤一:请求通知授权

为确保应用可正常收到消息,建议应用发送通知前调用requestEnableNotification()方法弹出提醒,告知用户需要允许接收通知消息。

// entryability/EntryAbility.ets
/*** @description 应用入口* @author 白晓明* @organization 坚果派* @website: nutpi.com.cn* @date 2024-06-13*/
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON } from '@kit.ArkTS';
import { notificationManager } from '@kit.NotificationKit';export default class EntryAbility extends UIAbility {async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');// 请求通知授权await this.requestNotification();}async requestNotification() {try {console.info("requestNotification: 请求通知授权开始。");// 查询通知是否授权const notificationEnabled: boolean = await notificationManager.isNotificationEnabled();console.info("requestNotification: " + (notificationEnabled ? '已' : '未') + "授权");if (!notificationEnabled) {// 请求通知授权await notificationManager.requestEnableNotification();}} catch (error) {const e: BusinessError = error as BusinessError;console.error("requestNotification failed. Cause: " + JSON.stringify(e));}}
}

步骤二:获取Push Token

导入pushService模块。建议在应用的UIAbility(例如EntryAbility)的onCreate()方法中调用getToken()接口获取Push Token并上报到开发者的服务端,方便开发者的服务端向终端推送消息。本示例便于应用端测试发送通知消息请求,将Push Token获取放置在Index.ets页面。

// pages/Index.ets
import { pushService } from '@kit.PushKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON } from '@kit.ArkTS';
import { http } from '@kit.NetworkKit';
import { promptAction } from '@kit.ArkUI';
/*** @description 推送服务示例* @author 白晓明* @organization 坚果派* @website: nutpi.com.cn* @date 2024-06-13*/
@Entry
@Component
struct Index {@State pushToken: string = "";async aboutToAppear(): Promise<void> {try {// 获取Push Tokenconst pushToken: string = await pushService.getToken();console.log("getToken succeed. Token: " + pushToken);const now = new Date();const timestamp = now.getTime();console.log("getToken succeed. Time: " + Math.floor(timestamp / 1000));console.log("getToken succeed. Time: " + (Math.floor(timestamp / 1000) + 3600));this.pushToken = pushToken;// 此处需要上报Push Token到应用服务端} catch (error) {const e: BusinessError = error as BusinessError;console.error("getToken failed. Cause: " + JSON.stringify(e));}}build() {...}
}

步骤三:获取项目ID

登录AppGallery Connect控制台,选择“我的项目”,在项目列表中选择对应的项目,左侧导航栏选择“项目设置”,拷贝项目ID。

步骤四:创建服务账号密钥文件

  • 开发者需要在华为开发者联盟的API Console上创建并下载推送服务API的服务账号密钥文件。点击“管理中心 > API服务 > API库”,在API库页面选择“项目名称”,在展开的App Services列表中点击“推送服务”。

  • 点击推送服务页面中的“启用”,完成API添加。

  • 点击“管理中心 > API服务 > 凭证”,在凭证页面点击“服务账号密钥”卡片中的“创建凭证”按钮。

  • 在“创建服务账号密钥”页面输入信息并点击“生成公私钥”,点击“创建并下载JSON”,完成“服务账号密钥”凭证创建,需要开发者保存“支付公钥”,用于后期生成JWT鉴权令牌。

步骤五:生成JWT Token

开发者在正式开发前调试功能,可使用在线生成工具获取JWT Token,需要注意生成JWT Token时Algorithm请选择RS256或PS256。若用于正式环境,为了方便开发者生成服务账号鉴权令牌,华为提供了JWT开源组件,可根据开发者使用的开发语言选择进行开发。

  • HEADER中的kid指下载的服务账号密钥文件中key_id字段。
  • PAYLOAD数据中iss指下载的的服务账号密钥文件中sub_account字段。
  • VERIFY SIGNATURE中复制粘贴公钥和私钥。

步骤六:调用推送服务REST API

该模块需要开发者在应用服务端自行开发,需要结合用户信息留存设备Token,本课程中该功能位于应用端仅用于学习,不推荐该方法。应用服务端调用Push Kit服务端的REST API推送通知消息,需要传递的参数说明如下所示:

  • [projectId]:项目ID。
  • Authorization:JWT格式字符串,JWT Token前加“Bearer ”,需注意“Bearer”和JWT格式字符串中间的空格不能丢。
  • push-type:0表示Alert消息,此处为通知消息场景。
  • category:表示通知消息自分类的类别,MARKETING为资讯营销类消息。
  • actionType:0表示点击消息打开应用首页。
  • token:Push Token。
  • testMessage:测试消息标识,true标识测试消息。
  • notifyId:(选填)自定义消息标识字段,仅支持数字,范围[0, 2147483647],若要用于消息撤回则必填。

在应用端按钮组件Button的点击事件onClick中通过数据请求API实现发送通知消息。

// pages/Index.ets
import { pushService } from '@kit.PushKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { JSON } from '@kit.ArkTS';
import { http } from '@kit.NetworkKit';
import { promptAction } from '@kit.ArkUI';
/*** @description 推送服务示例* @author 白晓明* @organization 坚果派* @website: nutpi.com.cn* @date 2024-06-13*/
@Entry
@Component
struct Index {@State pushToken: string = "";@State isLoading: boolean = false;// 步骤五生成的JWT Tokenauthorization: string = "Bearer ****";async aboutToAppear(): Promise<void> {try {// 获取Push Tokenconst pushToken: string = await pushService.getToken();console.log("getToken succeed. Token: " + pushToken);const now = new Date();const timestamp = now.getTime();console.log("getToken succeed. Time: " + Math.floor(timestamp / 1000));console.log("getToken succeed. Time: " + (Math.floor(timestamp / 1000) + 3600));this.pushToken = pushToken;// 上报Push Token} catch (error) {const e: BusinessError = error as BusinessError;console.error("getToken failed. Cause: " + JSON.stringify(e));}}async deletePushTokenFunc() {try {await pushService.deleteToken();} catch (error) {const e: BusinessError = error as BusinessError;console.error("deleteToken failed. Cause: " + JSON.stringify(e));}}build() {Column() {Row() {Text('推送服务示例').fontSize(18).fontWeight(FontWeight.Bolder)}.width('100%').height(54).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)Column({ space: 16 }) {Row() {LoadingProgress()Text('等待通知发送完成').fontSize(16)}.width('100%').height(64).justifyContent(FlexAlign.Center).visibility(this.isLoading ? Visibility.Visible : Visibility.Hidden)Button('发送通知消息').type(ButtonType.Normal).borderRadius(8).enabled(!this.isLoading).onClick(async () => {try {this.isLoading = true;const url = "https://push-api.cloud.huawei.com/v3/388421841222199046/messages:send";const httpRequest = http.createHttp();const response: http.HttpResponse = await httpRequest.request(url, {header: {"Content-Type": "application/json","Authorization": this.authorization,"push-type": 0},method: http.RequestMethod.POST,extraData: {"payload": {"notification": {"category": "MARKETING","title": "普通通知标题","body": "普通通知内容","clickAction": {"actionType": 0},"notifyId": 12345}},"target": {"token": [this.pushToken]},"pushOptions": {"testMessage": true}}})if (response.responseCode === 200) {const result = response.result as string;const data = JSON.parse(result) as ResultData;promptAction.showToast({message: data.msg})}} catch (error) {const e: BusinessError = error as BusinessError;console.error("getToken failed. Cause: " + JSON.stringify(e));} finally {this.isLoading = false;}})}.width('100%').layoutWeight(1)}.height('100%').width('100%')}
}// 接口返回数据类
interface ResultData {code: string;msg: string;requestId: string;
}

相关文章:

鸿蒙开发-HMS Kit能力集(应用内支付、推送服务)

1 应用内支付 开发步骤 步骤一&#xff1a;判断当前登录的华为账号所在服务地是否支持应用内支付 在使用应用内支付之前&#xff0c;您的应用需要向IAP Kit发送queryEnvironmentStatus请求&#xff0c;以此判断用户当前登录的华为帐号所在的服务地是否在IAP Kit支持结算的国…...

TYUT设计模式大题

对比简单工厂&#xff0c;工厂方法&#xff0c;抽象工厂模式 比较安全组合模式和透明组合模式 安全组合模式容器节点有管理子部件的方法&#xff0c;而叶子节点没有&#xff0c;防止在用户在叶子节点上调用不适当的方法&#xff0c;保证了的安全性&#xff0c;防止叶子节点暴露…...

Webman中实现定时任务

文章目录 Webman中实现定时任务一、引言二、安装与配置1、安装Crontab组件2、创建进程文件3、配置进程文件随Webman启动4、重启Webman5、Cron表达式&#xff08;补充&#xff09;例子 三、使用示例四、总结 Webman中实现定时任务 一、引言 在现代的后端开发中&#xff0c;定时…...

《以 C++破局:人工智能系统可解释性的探索与实现》

在当今科技飞速发展的时代&#xff0c;人工智能已深度融入我们的生活&#xff0c;从医疗诊断到金融决策&#xff0c;从交通管控到司法审判&#xff0c;其影响力无处不在。然而&#xff0c;在这些涉及重大利益和社会影响的关键领域&#xff0c;人工智能系统却面临着严峻的信任危…...

C++:QTableWidget删除选中行(单行,多行即可)

转自博客&#xff1a; Qt C -在QTableWidget中删除行 - 腾讯云开发者社区 - 腾讯云 我的界面&#xff1a; 采集机器人位置和姿态信息并写入QTableWidget控件中 删除代码&#xff1a; 1.获取要删除行的索引 2.删除行 QList<QTableWidgetItem*> items ui->tableW…...

C++类中多线程的编码方式

问题 在C++代码中,一般的代码是需要封装在类里面,比如对象,方法等。否则就不能很好的利用C++面向对象的能力了。 但是这个方式在处理线程时会碰到一个问题。 考虑下面一个简单的场景: class demoC { public:std::thread t;int x;void threadFunc(){std::cout<<x&…...

数据湖的概念(包含数据中台、数据湖、数据仓库、数据集市的区别)--了解数据湖,这一篇就够了

文章目录 一、数据湖概念1、企业对数据的困扰2、什么是数据湖3、数据中台、数据湖、数据仓库、数据集市的区别 网上看了好多有关数据湖的帖子&#xff0c;还有数据中台、数据湖、数据仓库、数据集市的区别的帖子&#xff0c;发现帖子写的都很多&#xff0c;而且专业名词很多&am…...

EDKII之安全启动详细介绍

文章目录 安全启动简介安全启动流程介绍签名过程BIOS实现小结 安全启动简介 安全启动&#xff08;Secure Boot&#xff09;是一种计算机系统的安全功能&#xff0c;旨在确保系统启动过程中只能加载经过数字签名的受信任的操作系统和启动加载程序。通过使用安全启动&#xff0c…...

原生js上传图片

无样式上传图片 创建一个 FormData 对象&#xff1a;这个对象可以用于存储数据。 将文件添加到 FormData 对象&#xff1a;通过 append() 方法&#xff0c;将用户选择的文件添加到 formData 对象中。 使用 fetch 发送请求&#xff1a;使用 fetch API 或者其他方法将 FormDat…...

使用torch==2.5.1版本用的清华源遇到的坑

解决安装torch后,torch.cuda.is_available()结果为false的问题 清华源下载到的torch2.5.1版本的Lib\site-packages\torch\version.py 其中&#xff0c;清华源指的是&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorchhttps://mirrors.tuna.tsinghua.…...

泷羽Sec-星河飞雪-BurpSuite之解码、日志、对比模块基础使用

免责声明 学习视频来自 B 站up主泷羽sec&#xff0c;如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 泷羽sec官网&#xff1a;http…...

对拍详细使用方法

对拍的作用 对于我们在学校OJ&#xff0c;cf&#xff0c;牛客…各种只提供少量测试数据的题目&#xff0c;常常交上代码常常超时&#xff0c;能写出正确的暴力代码而题目要求的时间复杂度更低。然而这时你写出了能通过样例且时间复杂度更低的代码&#xff0c;但交上去就是错误…...

Python面向对象编程与模块化设计练习

需求&#xff1a; 编写一个BankAccount类&#xff0c;模拟银行账户功能&#xff1a; 属性&#xff1a;账户名、余额 方法&#xff1a;存款、取款、查询余额 使用模块将类和测试代码分离。 模块文件&#xff1a;bank_account.py 该模块包含 BankAccount 类。 class BankAccoun…...

Linux系统硬件老化测试脚本:自动化负载与监控

简介&#xff1a; 这篇文章介绍了一款用于Linux系统的自动化硬件老化测试脚本。该脚本能够通过对CPU、内存、硬盘和GPU进行高强度负载测试&#xff0c;持续运行设定的时长&#xff08;如1小时&#xff09;&#xff0c;以模拟长时间高负荷运行的环境&#xff0c;从而验证硬件的稳…...

搭建一个基于Web的文档管理系统,用于存储、共享和协作编辑文档

搭建一个基于Web的文档管理系统&#xff0c;用于存储、共享和协作编辑文档 本项目采用以下架构&#xff1a; NFS服务器: 负责存储文档资料。Web服务器: 负责提供文档访问和编辑功能。SELinux: 负责权限控制&#xff0c;确保文档安全。Git服务器: 负责存储文档版本历史&#x…...

排序学习整理(1)

1.排序的概念及运用 1.1概念 排序&#xff1a;所谓排序&#xff0c;就是使⼀串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作&#xff0c;以便更容易查找、组织或分析数据。 1.2运用 购物筛选排序 院校排名 1.3常见排序算法 2.实…...

《深入探究 Java 中的 boolean 类型》

在 Java 编程语言的世界里&#xff0c;boolean 类型虽然看似简单&#xff0c;却在程序的逻辑控制和决策中起着至关重要的作用。本文将带你深入了解 Java 中的 boolean 类型&#xff0c;从其基本概念、用法到实际应用场景&#xff0c;以及一些常见的注意事项。 一、boolean 类型…...

智享 AI 自动无人直播系统:打破地域与时间枷锁中小微企业的营销破局利器

中小微企业&#xff0c;在商业浪潮中恰似逐浪扁舟&#xff0c;常面临营销成本高、推广渠道窄、专业人才缺等 “暗礁”&#xff0c;而智享 AI 自动无人直播系统恰如精准导航的灯塔&#xff0c;助其破浪前行、突出重围。 成本维度&#xff0c;传统直播人力成本让中小微企业望而却…...

接口测试工具:reqable

背景 在众多接口测试工具中挑选出一个比较好用的接口测试工具。使用过很多工具&#xff0c;如Postman、Apifox、ApiPost等&#xff0c;基本上是同类产品&#xff0c;一般主要使用到的功能就是API接口和cURL&#xff0c;其他的功能目前还暂未使用到。 对比 性能方面&#xff…...

同时多平台git配置:GitHub和Gitee生成不同的SSH Key

文章目录 GitHub和Gitee生成不同的SSH Key步骤1&#xff1a;生成SSH Key步骤2&#xff1a;配置SSH配置文件步骤3&#xff1a;查看SSH公钥步骤4&#xff1a;将SSH公钥添加到GitHub和Gitee步骤5&#xff1a;测试SSH连接步骤6&#xff1a;添加remote远程库 GitHub和Gitee生成不同的…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

家政维修平台实战20:权限设计

目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系&#xff0c;主要是分成几个表&#xff0c;用户表我们是记录用户的基础信息&#xff0c;包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题&#xff0c;不同的角色&#xf…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

日常一水C

多态 言简意赅&#xff1a;就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过&#xff0c;当子类和父类的函数名相同时&#xff0c;会隐藏父类的同名函数转而调用子类的同名函数&#xff0c;如果要调用父类的同名函数&#xff0c;那么就需要对父类进行引用&#…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...