鸿蒙开发-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 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDEA敲Web前端快捷键

1.html基础格式 英文符号TAB键 <!doctype html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport"content"widthdevice-width, user-scalableno, initial-scale1.0, maximum-scale1.0, mini…

字符串算法题

目录 题目一——14. 最长公共前缀 - 力扣&#xff08;LeetCode&#xff09; 1.1.两两比较 1.2.统一比较 题目二——5. 最长回文子串 - 力扣&#xff08;LeetCode&#xff09; 2.1.中心拓展算法 题目三——67. 二进制求和 - 力扣&#xff08;LeetCode&#xff09; 题目…

嵌入式Linux - UBoot学习篇

目录 使用tftp上传我们的zImage 在Ubuntu上安装TFTP 把我们的网线连接到Ubuntu上 mmc指令 基本命令 2. 重新扫描和分区管理 3. 硬件分区 4. 启动配置 5. 复位功能和 DSR 配置 关键警告与注意事项&#xff1a; 常见用途&#xff1a; mmc info mmc rescan mmc list …

Ubuntu 20.04 Server版连接Wifi

前言 有时候没有网线口插网线或者摆放电脑位置不够时&#xff0c;需要用Wifi联网。以下记录Wifi联网过程。 环境&#xff1a;Ubuntu 20.04 Server版&#xff0c;无UI界面 以下操作均为root用户&#xff0c;如果是普通用户&#xff0c;请切换到root用户&#xff0c;或者在需要权…

亚马逊自研大语言模型 Olympus 即将亮相,或将在 LLM 竞赛中掀起新波澜

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

采用片上光学相控阵的激光雷达

激光雷达基础知识 LIDAR 基于众所周知的 RADAR 原理雷达是20世纪初就存在的著名技术激光雷达使用光频率而不是无线电波 激光雷达和雷达 使用相控阵的激光雷达通过干涉来提高方向性 激光雷达的输出剖面是阵列因子和单天线远场的乘积。 N &#xff1a;天线数量 k &#xff1a;…

阿里云服务器(centos7.6)部署前后端分离项目(MAC环境)

Jdk17安装部署 下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/ 选择自己需要的jdk版本进行下载。 通过mac终端scp命令上传下载好的jdk17到服务器的/usr/local目录下 scp -r Downloads/jdk-17.0.13_linux-x64_bin.tar.gz 用户名服务器ip地址:/us…

SQL优化与性能——数据库设计优化

数据库设计优化是提高数据库性能、确保数据一致性和支持业务增长的关键环节。无论是大型企业应用还是小型项目&#xff0c;合理的数据库设计都能够显著提升系统性能、减少冗余数据、优化查询响应时间&#xff0c;并降低维护成本。本章将深入探讨数据库设计中的几个关键技术要点…

41 基于单片机的小车行走加温湿度检测系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采样DHT11温湿度传感器检测温湿度&#xff0c;滑动变阻器连接数码转换器模拟电量采集传感器&#xff0c; 电机采样L298N驱动&#xff0c;各项参数通过LCD1602显示&#x…

在VMware虚拟机上安装Kali Linux的详细教程(保姆级教程)

在VMware虚拟机上安装Kali Linux的详细教程 引言 Kali Linux是一个基于Debian的Linux发行版&#xff0c;专为渗透测试和安全审计而设计。它内置了数百种安全工具&#xff0c;广泛应用于网络安全领域。通过在VMware虚拟机上安装Kali Linux&#xff0c;您可以在不影响主操作系统…

30分钟学会正则表达式

正则表达式是对字符串操作的一种逻辑公式&#xff0c;就是用事先定义好的一些特定字符、及这些特定字符的组合&#xff0c;组成一个“规则字符串”&#xff0c;这个“规则字符串”用来表达对字符串的一种过滤逻辑。 作用 匹配 查看一个字符串是否符合正则表达式的语法 搜索 正…

spring-boot-maven-plugin 标红

情况&#xff1a;创建好 Spring Boot 项目后&#xff0c;pom.xml 文件中 spring-boot-maven-plugin 标红。 解决方案&#xff1a;加上 Spring Boot 的版本即可解决。

关于IDE的相关知识之三【插件安装、配置及推荐的意义】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于ide插件安装、配置及推荐意义的相关内容…

《通俗易懂 · JSqlParser 解析和构造SQL》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; 希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数&#xff0c;欢迎多多交流…

MySQL底层概述—7.优化原则及慢查询

大纲 1.Explain概述 2.Explain详解 3.索引优化数据准备 4.索引优化原则详解 5.慢查询设置与测试 6.慢查询SQL优化思路 1.Explain概述 使用Explain关键字可以模拟查询优化器来执行SQL查询语句&#xff0c;从而知道MySQL是如何处理SQL语句的&#xff0c;从而分析出查询语句…

从扩散模型开始的生成模型范式演变--SDE

SDE是在分数生成模型的基础上&#xff0c;将加噪过程扩展时连续、无限状态&#xff0c;使得扩散模型的正向、逆向过程通过SDE表示。在前文讲解DDPM后&#xff0c;本文主要讲解SDE扩散模型原理。本文内容主要来自B站Up主deep_thoughts分享视频Score Diffusion Model分数扩散模型…

NeuIPS 2024 | YOCO的高效解码器-解码器架构

该研究提出了一种新的大模型架构&#xff0c;名为YOCO&#xff08;You Only Cache Once&#xff09;&#xff0c;其目的是解决长序列语言模型推理中的内存瓶颈。YOCO通过解码器-解码器结构的创新设计&#xff0c;显著减少推理时的显存占用并提升了长序列的处理效率。 现有大模…

Android 设备使用 Wireshark 工具进行网络抓包

背景 电脑和手机连接同一网络&#xff0c;想使用wireshark抓包工具抓取Android手机网络日志&#xff0c;有以下两种连接方法&#xff1a; Wi-Fi 网络抓包。USB 网络共享抓包。需要USB 数据线将手机连接到电脑&#xff0c;并在开发者模式中启用 USB 网络共享。 查看设备连接信…

腾讯云 AI 代码助手:单元测试应用实践

引言 在软件开发这一充满创造性的领域中&#xff0c;开发人员不仅要构建功能强大的软件&#xff0c;还要确保这些软件的稳定性和可靠性。然而&#xff0c;开发过程中并非所有任务都能激发创造力&#xff0c;有些甚至是重复且乏味的。其中&#xff0c;编写单元测试无疑是最令人…

修改Docker 默认存储目录( Docker Root Dir: /var/lib/docker)

Docker 默认将所有的数据&#xff08;包括镜像、容器、卷等&#xff09;存储在 /var/lib/docker 目录下。这个目录默认被配置在系统的根分区或者较小的分区上。随着容器化应用的增加&#xff0c;或者 Docker 容器和镜像的数量增加&#xff0c;默认存储位置可能会迅速填满&#…