标签 HarmonyOS SDK 下的文章

当应用平台组织诸如秒杀、抽奖等营销活动时,经常会遭遇"薅羊毛"行为,给业务方带来不小的经费损失。比如通过虚假手机号进行批量注册,多次参加活动;又比如,当应用商户进行红包补贴、优惠券发放等营销活动时,使用脚本或模拟器"薅羊毛"。

为避免该问题,HarmonyOS SDK华为账号服务(Account Kit)提供了获取用户风险等级的能力,能够有效识别恶意场景,提前防范业务风险。

应用场景

一、应用登录风控场景:

当用户使用华为账号关联登录应用时,开发者可通过华为账号获取用户风险等级的能力获取用户账号的风险等级,对高风险等级账号进行风控,提升应用的安全等级。

二、营销活动反作弊场景:

在应用进行营销活动期间,如进行商户补贴、优惠券发放等商业营销活动时获取华为账号风险等级,协助开发者有效识别"薅羊毛"风险;保护营销资源合理使用,降低业务安全问题给营销方带来的损失,为相关活动保驾护航。

风险等级

获取用户风险等级方式

一、 通过华为账号一键登录获取用户风险等级。

在应用登录风控场景中,开发者可以通过华为账号一键登录获取用户风险等级,对恶意账号进行风控,提升应用的安全等级。
大致业务流程如下:

通过华为账号一键登录获取用户风险等级的开发,需要建立在一键登录的开发基础上。在进行代码开发前,请确认已经完成一键登录的开发准备工作,然后申请对应的scope权限,接着就可以进行客户端部分的开发。

在客户端开发部分,需要参考一键登录开发流程步骤1及步骤2,确保系统账号已登录,匿名手机号获取成功,且用户首次通过华为账号登录该应用。接着再参考步骤3的示例代码,在LoginWithHuaweiIDButton组件参数params中设置riskLevel标识为true,其余示例代码保持不变,拉起应用登录页。

LoginWithHuaweiIDButton({
  params: {
    // LoginWithHuaweiIDButton支持的样式
    style: loginComponentManager.Style.BUTTON_RED,
    // 账号登录按钮在登录过程中展示加载态
    extraStyle: {
      buttonStyle: new loginComponentManager.ButtonStyle().loadingStyle({
        show: true
      })
    },
    // LoginWithHuaweiIDButton的边框圆角半径
    borderRadius: 24,
    // LoginWithHuaweiIDButton支持的登录类型
    loginType: loginComponentManager.LoginType.QUICK_LOGIN,
    // LoginWithHuaweiIDButton支持按钮的样式跟随系统深浅色模式切换
    supportDarkMode: true,
    // verifyPhoneNumber:如果华为账号用户在过去90天内未进行短信验证,是否拉起Account Kit提供的短信验证码页面
    verifyPhoneNumber: true,
    // riskLevel:标识应用期望在登录后获取华为账号的风险等级
    riskLevel: true,
  },
  controller: this.controller
})

用户同意协议并点击一键登录按钮后,可获取到Authorization Code,并在服务端使用Client ID、Client Secret、Authorization Code调用获取用户级凭证接口向华为账号服务器请求获取Access Token,最后使用Access Token调用获取用户风险等级接口获取用户的风险等级。

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 获取用户风险等级
 */
@Slf4j
public class GetUserRiskLevelDemo {
    public static void main(String[] args) throws IOException {
        // 获取用户风险等级的接口URL
        String url = "https://account.cloud.huawei.com/user/getuserrisklevel";
        // 替换为您实际的Client ID
        String clientID = "<Client ID>";
        // 替换为您实际的transactionID
        String transactionID = "<transactionID>";
        // 替换为您实际的获取到的用户级凭证Access Token
        String accessToken = "<Access Token>";
        // 替换为您实际的scene
        String scene = "<scene>";
        JSONObject result = getUserRiskLevel(url, clientID, transactionID, accessToken, scene);
        // 解析获取errCode
        Integer errCode = result.getInteger("errCode");
        // 解析获取errMsg
        String errMsg = result.getString("errMsg");
        // 解析获取riskLevel
        Integer riskLevel = result.getInteger("riskLevel");
        // 解析获取riskTag
        JSONArray riskTag = result.getJSONArray("riskTag");
    }

    private static JSONObject getUserRiskLevel(String url, String clientID, String transactionID,
        String accessToken, String scene) throws IOException {
        HttpPost httpPost = new HttpPost(url + "?" + "clientID=" + clientID + "&transactionID=" + transactionID);
        Map<String, String> reqBody = new HashMap<>();
        reqBody.put("accessToken", accessToken);
        reqBody.put("scene", scene);
        httpPost.setHeader("Content-Type", "application/json;charset=utf-8");
        httpPost.setEntity(CallUtils.wrapJsonEntity(reqBody));
        return CallUtils.toJsonObject(CallUtils.remoteCall(httpPost, (CloseableHttpResponse response, String rawBody) -> {
            int statusCode = response.getStatusLine().getStatusCode();
            // http状态码不是200,请求失败
            if (statusCode != 200) {
                return new IOException("call failed! http status code: " + statusCode + ", response data: " + rawBody);
            }
            // http状态码为200,解析响应的body,判断业务错误码
            JSONObject errorResponseBody = CallUtils.toJsonObject(rawBody);
            // 错误码
            Integer errCode = errorResponseBody.getInteger("errCode");
            // errCode为0表示成功,非0表示失败
            if (Objects.nonNull(errCode) && errCode != 0) {
                return new IOException("call failed! http status code: " + statusCode + ", response data: " + rawBody);
            }
            return null;
        }));
    }
}

二、 通过华为账号其他方式登录获取用户风险等级。

在应用已使用华为账号关联登录的场景中,开展商户补贴、优惠券发放等商业营销活动时,开发者可通过华为账号其他方式登录获取华为账号风险等级,有效识别"薅羊毛"风险,保护营销资源合理使用。

大致业务流程如下:

通过华为账号其他方式登录获取用户风险等级的开发步骤同样分为客户端开发和服务端开发。客户端开发步骤如下:

  1. 首先导入authentication模块及相关公共模块。

    import { authentication } from '@kit.AccountKit';
    import { hilog } from '@kit.PerformanceAnalysisKit';
    import { util } from '@kit.ArkTS';
    import { BusinessError } from '@kit.BasicServicesKit';

  2. 然后创建授权请求并设置参数。

    // 创建授权请求,并设置参数
    const authRequest = new authentication.HuaweiIDProvider().createAuthorizationWithHuaweiIDRequest();
    // 获取风险等级需要传如下scope
    authRequest.scopes = ['riskLevel'];
    // 获取authorizationCode需传如下permission
    authRequest.permissions = ['serviceauthcode'];
    // 用户是否需要登录授权,该值为true且用户未登录或未授权时,会拉起用户登录或授权页面
    authRequest.forceAuthorization = true;
    // 用于防跨站点请求伪造
    authRequest.state = util.generateRandomUUID();

  3. 调用AuthenticationController对象的executeRequest方法执行授权请求,并处理授权结果,从授权结果中解析出authorizedScopes和Authorization Code。

    // 执行授权请求
    try {
    // 此示例为代码片段,实际需在自定义组件实例中使用,以获取UIContext对象作为函数入参
    const controller = new authentication.AuthenticationController(this.getUIContext().getHostContext());
    controller.executeRequest(authRequest).then((data) => {

     const authorizationWithHuaweiIDResponse = data as authentication.AuthorizationWithHuaweiIDResponse;
     const state = authorizationWithHuaweiIDResponse.state;
     if (state && authRequest.state !== state) {
       hilog.error(0x0000, 'testTag', `Failed to authorize. The state is different, response state: ${state}`);
       return;
     }
     hilog.info(0x0000, 'testTag', 'Succeeded in authentication.');
     let riskLevelAuthorized: boolean = false;
     const authorizationWithHuaweiIDCredential = authorizationWithHuaweiIDResponse?.data;
     const authorizedScopes = authorizationWithHuaweiIDCredential?.authorizedScopes;
     // 判断授权成功scopes中是否包含riskLevel
     if (authorizedScopes?.includes("riskLevel")) {
         riskLevelAuthorized = true;
     }
     const authorizationCode = authorizationWithHuaweiIDCredential?.authorizationCode;
     // 开发者处理riskLevelAuthorized, authorizationCode

    }).catch((err: BusinessError) => {

     dealAllError(err);

    });
    } catch (error) {
    dealAllError(error);
    }
    // 错误处理
    function dealAllError(error: BusinessError): void {
    hilog.error(0x0000, 'testTag', Failed to obtain userInfo. Code: ${error.code}, message: ${error.message});
    // 在应用获取用户风险等级场景下,涉及UI交互时,建议按照如下错误码指导提示用户
    if (error.code === ErrorCode.ERROR_CODE_LOGIN_OUT) {

     // 用户未登录华为账号,请登录华为账号并重试

    } else if (error.code === ErrorCode.ERROR_CODE_NETWORK_ERROR) {

     // 网络异常,请检查当前网络状态并重试

    } else if (error.code === ErrorCode.ERROR_CODE_USER_CANCEL) {

     // 用户取消授权

    } else if (error.code === ErrorCode.ERROR_CODE_SYSTEM_SERVICE) {

     // 系统服务异常,请稍后重试

    } else if (error.code === ErrorCode.ERROR_CODE_REQUEST_REFUSE) {

     // 重复请求,应用无需处理

    } else {

     // 获取用户信息失败,请稍后重试

    }
    }

    export enum ErrorCode {
    // 账号未登录
    ERROR_CODE_LOGIN_OUT = 1001502001,
    // 网络错误
    ERROR_CODE_NETWORK_ERROR = 1001502005,
    // 用户取消授权
    ERROR_CODE_USER_CANCEL = 1001502012,
    // 系统服务异常
    ERROR_CODE_SYSTEM_SERVICE = 12300001,
    // 重复请求
    ERROR_CODE_REQUEST_REFUSE = 1001500002
    }

  4. 在客户端开发完成后,同样需要调用获取用户级凭证接口向华为账号服务器请求获取Access Token,并使用Access Token调用获取用户风险等级接口获取用户的风险等级。

了解更多详情\>\>

访问华为账号服务联盟官网

获取获取风险等级开发指导文档

1.问题描述:

断点太多是否会使DevEco Studio运行卡顿?如何处理?

解决方案:

断点太多会影响DevEco Studio运行,可以通过断点管理删除不必要的断点。

2.问题描述:

为什么图片使用imagePacker.packToFile压缩完之后,反而变大了?

解决方案:

可以参考图片压缩API的质量参数quality与图片原始大小、压缩后大小的关系,quality是图片质量参数,并非是指按百分比压缩。若压缩前图片质量比指定的压缩参数quality小的话,就可能会导致压缩后的图片文件比压缩前更大;若想实现压缩变小,可以降低quality值,或是压缩前使用。PixelMap.scale缩放图片后再进行压缩。

3.问题描述:

AVPlayer有两个播放源,清晰度不一样,希望切换播放源时尽量顺滑,让用户没有感知,有什么方案?

解决方案:

应用中通过层叠布局创建两个avPlayer播放器堆叠,用户只能看到最上层的播放器界面;点击播放时,两个清晰度不一样的视频同时在两个播放器上播放,点击切换时,设置对应清晰度视频所在的播放器的堆叠顺序为高优先级,则会展示该播放器界面在最上层,达到切换的目的。

用代码浇灌春天,最终必将见证万紫千红的生态盛景。

她说:“我愿在这群芳争艳的时代,绽放一抹‘吉祥’红!”

吉林银行作为吉林省经济发展的 “金融引擎”,在数字化转型浪潮中勇立潮头。其开发团队通过分布式架构重构、ArkUI-X 框架迁移及原子化服务开发等技术突破,历时 21 个自然日完成 HarmonyOS NEXT 核心功能版本适配。今天让我们采访一下吉林银行的鸿蒙开发者代表卢妍娆女士,一起听她讲讲应用适配 HarmonyOS NEXT 的故事。

自 22 年加入吉林银行以来,卢妍娆便先后投入到了新一代核心系统建设以及吉林银行手机银行 6.0 迭代建设。23 年年末吉林银行对应用鸿蒙化表示明确认可,认为鸿蒙生态适配不仅仅是吉林银行构建数字金融护城河的战略突破口,更是实现技术自主可控的关键战役,如春潮涌动时抢占滩头的先锋。

“我们非常期待能在 HarmonyOS NEXT 这个种满花卉的生态里,迅速绽放并共同成长,掌握一定的话语权。”

在“打仗”之前,吉林银行研发团队完成了鸿蒙开发的学习,并于 2024 年 2 月与华为达成鸿蒙适配的合作意向。“华为为我们提供了技术上的答疑指导,帮助我们打通开发道路,让后面的开发更加便利。”万事俱备只欠东风,2024 年 5 月底立项申请通过,项目正式启动,基于手机银行 6.0 功能及性能提升后的框架,6 月 18 日正式上架核心交易功能版本。

卢妍娆在 HDD 活动照片

“HarmonyOS NEXT 跟安卓不一样,是个全新的系统,也是全新的体验”

卢妍娆最初担心,吉林银行 App 适配鸿蒙的时候会很困难,因为原有的代码架构需要大规模重构。在鸿蒙声明式开发里,UI 是通过声明式语法描述的,需要重新编写大量的 UI 代码。事实上,开发过程真的很艰难吗?

“遇到技术难题的时候,你可以直接提出问题,鸿蒙的官方技术人员会回复,甚至提供样例代码手把手帮你解决问题。例如,我们开发团队在遇到微信分享无法获取 uicontext,自定义弹窗无法展示的问题时,华为团队提供了示例代码解决问题;由于医保缴费框架存在中断逻辑,导致页面存在多次跳转,华为团队根据每次 ID 的不同,提供样例代码规避了消费者界面多次跳转的问题;开发语音识别功能的时候,我们团队没有足够的经验,华为技术人员提供了语音识别代码 Demo 以及 UI 代码,帮助我们快速实现语音识别功能。”卢妍娆回忆道。相比安卓开发中依赖第三方论坛的“投石问路”,鸿蒙的这种开发者帮扶模式更高效更贴心。

应用适配鸿蒙生态架构

HarmonyOS SDK 接入:纯净之境,开启开发新篇章

“我们的手机银行集成第三方 SDK 有 18 个,HarmonyOS SDK 替代了部分,不仅协同加速,提升了我们开发的效率,还为我们节省了大量成本。” 卢妍娆跟我介绍她们的应用。

传统 SDK 在架构设计上往往存在冗余和复杂的问题,在接入时会引入大量不必要的代码和依赖库。而 HarmonyOS SDK 采用的原子化服务架构,将功能拆解为最小可复用单元,使用起来就像搭建积木一样,我们可以根据需求灵活选择和组合这些原子化服务。这种模块化设计使得代码更加简洁、清晰,如同月光下的水晶棱镜,每一个模块都剔透纯净。以一个简单的天气卡片组件为例,在 HarmonyOS SDK 中,开发者可以通过简洁的代码实现其功能,非常高效简洁。

小组开会研讨方案

"HarmonyOS NEXT 不是简单的系统升级,而是给开发者重新定义了工具类应用的魔法棒。当设备间的界限消失,我们才能真正聚焦于用户需求本身。"对于吉林银行来说,鸿蒙生态带来的意义不仅仅优先他人一步,更重要的是带来了万物互联的时代。

夜幕降临,金融街的灯火次第亮起。在这场由鸿蒙系统掀起的数字化浪潮中,银行正从传统的 "金融服务提供者" 转变为 "智能生态构建者"。当吉林银行以金融级安全纽带编织起千万用户的数字生活场景,既筑牢数字经济时代的安全护城河,又为银行生态的生长埋下战略伏笔;当意图框架读懂用户每一个潜在需求,各个企业正在书写属于自己的全场景智慧篇章。而这,仅仅是鸿蒙星河下的序章。

了解鸿蒙开发认证详情,探索鸿蒙开发者联盟丰富资源,点击链接:​​鸿蒙开发者联盟​​。​这里有开发文档、论坛、工具等,快加入,开启鸿蒙开发之旅!