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

news2024/12/27 2:26:39

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 Token
      const 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 Token
  authorization: string = "Bearer ****";

  async aboutToAppear(): Promise<void> {
    try {
      // 获取Push Token
      const 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;
}

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

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

相关文章

VBA代码解决方案第二十讲:EXCEL工作表的添加与删除

《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程&#xff0c;目前已经是第三版修订了。这套教程定位于入门后的提高&#xff0c;在学习这套教程过程中&#xff0c;侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…

LeetCode-315. Count of Smaller Numbers After Self

目录 题目描述 解题思路 【C】 【Java】 复杂度分析 LeetCode-315. Count of Smaller Numbers After Selfhttps://leetcode.com/problems/count-of-smaller-numbers-after-self/description/ 题目描述 Given an integer array nums, return an integer array counts whe…

用c语言完成俄罗斯方块小游戏

用c语言完成俄罗斯方块小游戏 这估计是你在编程学习过程中的第一个小游戏开发&#xff0c;怎么说呢&#xff0c;在这里只针对刚学程序设计的学生&#xff0c;就是说刚接触C语言没多久&#xff0c;有一点功底的学生看看&#xff0c;简陋的代码&#xff0c;简陋的实现&#xff0…

23种设计模式-原型(Prototype)设计模式

文章目录 一.什么是原型设计模式&#xff1f;二.原型模式的特点三.原型模式的结构四.原型模式的优缺点五.原型模式的 C 实现六.原型模式的 Java 实现七. 代码解析八.总结 类图&#xff1a; 原型设计模式类图 一.什么是原型设计模式&#xff1f; 原型模式&#xff08;Prototype…

基于大数据python 房屋价格数据分析预测可视化系统(源码+LW+部署讲解+数据库+ppt)

&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 很对人不知道选题怎么选 不清楚自己适合做哪块内容 都可以免费来问我 避免后期給自己答辩找麻烦 增加难度&#xff08;部分学校只有一次答辩机会 没弄好就延迟…

使用Grafana K6来测测你的系统负载能力

背景 近期我们有个号称会有很高很高并发的系统要上线&#xff0c;为了测试一下自己开发的系统的负载能力&#xff0c;准备了点海克斯科技&#xff0c;来看看抗不抗的住。 之前笔者写过用Apache JMeter进行压力测试的文章&#xff08;传送门&#x1f449;&#xff1a;https://…

Java 上机实践10(常用实用类)

&#xff08;大家好&#xff0c;今天分享的是Java的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 Plug&#xff1a;程序实现方法一&#xff08;记事本&#xff09; 方法二&#xff08;IDEA&#xff09; 实验一&…

28.UE5实现对话系统

目录 1.对话结构的设计&#xff08;重点&#xff09; 2.NPC对话接口的实现 2.1创建类型为pawn的蓝图 2.2创建对话接口 3.对话组件的创建 4.对话的UI设计 4.1UI_对话内容 4.2UI_对话选项 4.3UI_对话选项框 5.对话组件的逻辑实现 通过组件蓝图&#xff0c;也就是下图中的…

混沌工程/混沌测试/云原生测试/云平台测试

背景 私有云/公有云/混合云等具有复杂&#xff0c;分布式&#xff0c;环境多样性等特点&#xff0c;许多特殊场景引发的线上问题很难被有效发现。所以需要引入混沌工程&#xff0c;建立对系统抵御生产环境中失控条件的能力以及信心&#xff0c;提高系统面对未知风险得能力。 …

OpenMP出现Stack Overflow及其疑问

今天对着《OpenMP核心技术指南》练习OpenMP&#xff0c;其中一个案例: #include <stdio.h> #include <math.h> #include <omp.h>#define ITER 100000000void main() {int i;double A[ITER];for (i 0; i < ITER; i)A[i] 2.0 * i;#pragma omp parallel{/…

小F的矩阵值调整

问题描述 小F得到了一个矩阵。如果矩阵中某一个格子的值是偶数&#xff0c;则该值变为它的三倍&#xff1b;如果是奇数&#xff0c;则保持不变。小F想知道调整后的矩阵是什么样子的。 测试样例 样例1&#xff1a; 输入&#xff1a;a [[1, 2, 3], [4, 5, 6]] 输出&#xff1a…

【Python网络爬虫笔记】5-(Request 带参数的get请求) 爬取豆瓣电影排行信息

目录 1.抓包工具查看网站信息2.代码实现3.运行结果 1.抓包工具查看网站信息 请求路径 url:https://movie.douban.com/typerank请求参数 页面往下拉&#xff0c;出现新的请求结果&#xff0c;参数start更新&#xff0c;每次刷新出20条新的电影数据 2.代码实现 # 使用网络爬…

JiaJia-CP-1,2,3的WP(2)

一.JiaJia-CP-2 一看题目&#xff0c;聊天软件&#xff0c;用的什么聊天软件直接userassist看运行过什么程序 vol -f JiaJia_Co.raw --profileWin7SP1x64 userassist 发现Telegram.exe(小飞机) 可能性很大啊(真是个摸鱼大神) 除此之外&#xff0c;filescan也能看到&#xff0…

群控系统服务端开发模式-应用开发-前端邮箱短信通道开发

一、添加视图 在根目录下src文件夹下views文件夹下param文件夹下emailsms文件夹下&#xff0c;新建index.vue&#xff0c;代码如下 <template><el-tabs type"border-card"><el-tab-pane v-if"$store.getters.butts.includes(ParamEmailsmsIndex…

C/C++ 数据结构与算法【线性表】 顺序表+链表详细解析【日常学习,考研必备】带图+详细代码

1&#xff09;线性表的定义 线性表&#xff08;List&#xff09;&#xff1a;零个或多个数据元素的有限序列。 线性表的数据集合为{a1,a2,…,an}&#xff0c;假设每个元素的类型均为DataType。其中&#xff0c;除第一个元素a1外&#xff0c;每一个元素有且只有一个直接前驱元素…

高效特征选择:优化机器学习的嵌入式方法指南

高效特征选择&#xff1a;优化机器学习的嵌入式方法指南 文章目录 一、说明二、特征选择方法三、嵌入式方法四、Lasso正则化五、Python 中的 Lasso 实现六、决策树的特征重要性七、Python 实现八、嵌入式方法和递归特征消除九、结论 一、说明 假设您正在处理一个大型数据集&am…

系统架构:MVVM

引言 MVVM 全称 Model-View-ViewModel&#xff0c;是在 MVP&#xff08;Model-View-Presenter&#xff09;架构模式基础上的进一步演进与优化。MVVM 与 MVP 的基本架构相似&#xff0c;但 MVVM 独特地引入了数据双向绑定机制。这一创新机制有效解决了 MVP 模式中 Model 与 Vie…

家校通小程序实战教程04教师管理

目录 1 创建数据源2 搭建管理后台3 搭建查询条件4 功能测试总结 我们上一篇介绍了如何将学生加入班级&#xff0c;学生加入之后就需要教师加入了。教师分为任课老师和班主任&#xff0c;班主任相当于一个班级的管理员&#xff0c;日常可以发布各种任务&#xff0c;发布接龙&…

cesium 3Dtiles变量

原本有一个变亮的属性luminanceAtZenith&#xff0c;但是新版本的cesium没有这个属性了。于是 let lightColor 3.0result._customShader new this.ffCesium.Cesium.CustomShader({fragmentShaderText:void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial mate…

SpringBoot小知识(3):热部署知识

一、热部署 热部署是一个非常消耗内存的机制&#xff0c;在实际大型项目开发中几乎用不到&#xff0c;只有小型项目或者分模块或者不停机更新的时候才会用到&#xff0c;仁者见仁智者见智。 1.1 什么是热部署&#xff1f; 热部署是指在不停止应用程序或服务器的情况下&#xf…