HarmonyOS APP开发:华为云AI服务对接

举报
Jack20 发表于 2026/06/21 14:40:17 2026/06/21
【摘要】 HarmonyOS APP开发:华为云AI服务对接核心要点:华为云AI服务(ModelArts、HiLens、OCR服务、语音服务、NLP服务等)为HarmonyOS应用提供了强大的云端AI推理能力。本文将深入讲解HarmonyOS应用如何对接华为云AI服务,涵盖认证鉴权、API调用、结果处理、错误重试等全链路实战。 一、背景与动机先说个真实场景:我有个朋友做了一款HarmonyOS美食A...

HarmonyOS APP开发:华为云AI服务对接

核心要点:华为云AI服务(ModelArts、HiLens、OCR服务、语音服务、NLP服务等)为HarmonyOS应用提供了强大的云端AI推理能力。本文将深入讲解HarmonyOS应用如何对接华为云AI服务,涵盖认证鉴权、API调用、结果处理、错误重试等全链路实战。


一、背景与动机

先说个真实场景:我有个朋友做了一款HarmonyOS美食APP,想加个"拍照识别菜品"的功能。他一开始用端侧模型做,准确率只有60%——毕竟端侧模型受限于体积,能力有限。后来他接了华为云的图像识别API,准确率直接飙到92%。

这就是云端AI的价值——算力不受限、模型不受限、精度不受限

但对接云端AI服务也不是没有挑战。你至少要面对这些问题:

  1. 认证鉴权怎么搞? 华为云的IAM、AK/SK、Token三种认证方式各有适用场景
  2. 网络请求怎么封装? HTTPS请求、签名计算、超时处理
  3. 大文件怎么上传? 图片、音频这些二进制数据不能直接JSON传
  4. 并发和限流怎么处理? 云端API有QPS限制,不加控制会被限流
  5. 端云怎么协同? 什么时候走端侧、什么时候走云侧?

这篇文章就是来解决这些问题的。


二、核心原理

2.1 华为云AI服务架构

华为云AI服务从底层到上层分为四层:

graph TB
    subgraph CLIENT["HarmonyOS应用端"]
        A1[ArkTS业务逻辑]
        A2[AI服务SDK封装层]
        A3[网络请求引擎]
    end

    subgraph GATEWAY["华为云网关层"]
        B1[API Gateway]
        B2[IAM认证鉴权]
        B3[流量控制与限流]
    end

    subgraph SERVICE["AI服务层"]
        C1[OCR文字识别]
        C2[图像识别]
        C3[语音服务]
        C4[NLP自然语言]
        C5[ModelArts自定义模型]
    end

    subgraph COMPUTE["算力层"]
        D1[GPU推理集群]
        D2[NPU推理集群]
        D3[模型仓库]
    end

    A1 --> A2 --> A3
    A3 --> B1
    B1 --> B2 & B3
    B1 --> C1 & C2 & C3 & C4 & C5
    C1 & C2 & C3 & C4 & C5 --> D1 & D2 & D3

    classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff
    classDef warning fill:#F5A623,stroke:#C77D05,color:#fff
    classDef error fill:#D0021B,stroke:#8B0000,color:#fff
    classDef info fill:#7B68EE,stroke:#5B48C2,color:#fff
    classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff

    class A1,A2,A3 primary
    class B1,B2,B3 warning
    class C1,C2,C3,C4,C5 info
    class D1,D2,D3 purple

2.2 认证鉴权机制

华为云AI服务支持三种认证方式:

认证方式 原理 适用场景 安全等级
AK/SK签名 请求中携带签名,服务端验签 服务端调用 ⭐⭐⭐⭐⭐
Token认证 先获取Token,请求中携带Token 临时调用 ⭐⭐⭐⭐
IAM代理 通过IAM代理授权 企业多账号 ⭐⭐⭐⭐⭐

在HarmonyOS应用中,推荐使用Token认证。原因很简单:AK/SK不能硬编码在客户端(会被反编译),而Token有过期时间,即使泄露影响也有限。

2.3 端云协同决策流程

flowchart TD
    A[AI请求发起] --> B{判断任务类型}
    B -->|简单任务| C{端侧模型可用?}
    B -->|复杂任务| D{网络可用?}
    
    C -->|可用| E[端侧推理]
    C -->|不可用| D
    
    D -->|可用| F[云侧推理]
    D -->|不可用| G{端侧有降级模型?}
    
    G -->|| H[端侧降级推理]
    G -->|| I[返回错误提示]
    
    E --> J[结果后处理]
    F --> J
    H --> J
    J --> K[UI展示]

    classDef primary fill:#4A90D9,stroke:#2C5F8A,color:#fff
    classDef warning fill:#F5A623,stroke:#C77D05,color:#fff
    classDef error fill:#D0021B,stroke:#8B0000,color:#fff
    classDef info fill:#7B68EE,stroke:#5B48C2,color:#fff
    classDef purple fill:#9B59B6,stroke:#6C3483,color:#fff

    class A,B primary
    class C,D,G warning
    class E,F,H info
    class I error
    class J,K purple

三、代码实战

3.1 示例一:华为云AI服务认证与请求封装

这是最基础也最重要的部分——一个健壮的云端AI请求框架。

// 华为云AI服务 - 认证与请求封装
import { http } from '@kit.NetworkKit';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';

// 华为云AI服务配置
interface HuaweiCloudAIConfig {
  region: string;           // 区域,如 cn-north-4
  projectId: string;        // 项目ID
  domainName: string;       // 账号域名
  username: string;         // IAM用户名
  password: string;         // 密码(实际应从安全存储获取)
}

// Token信息
interface TokenInfo {
  token: string;            // 认证Token
  expiresAt: number;        // 过期时间戳(ms)
  userId: string;           // 用户ID
}

// API响应通用结构
interface AIResponse<T> {
  code: number;             // 状态码
  message: string;          // 消息
  data: T;                  // 业务数据
  requestId: string;        // 请求ID(用于排查问题)
}

// 华为云AI服务客户端
class HuaweiCloudAIClient {
  private config: HuaweiCloudAIConfig;
  private tokenInfo: TokenInfo | null = null;
  private requestQueue: Map<string, boolean> = new Map(); // 请求去重

  constructor(config: HuaweiCloudAIConfig) {
    this.config = config;
  }

  // 获取Token(带缓存和自动续期)
  async getToken(): Promise<string> {
    // Token未过期则直接使用
    if (this.tokenInfo && Date.now() < this.tokenInfo.expiresAt - 60000) {
      return this.tokenInfo.token;
    }

    // 请求新Token
    try {
      const url = `https://iam.${this.config.region}.myhuaweicloud.com/v3/auth/tokens`;
      const body = {
        auth: {
          identity: {
            methods: ['password'],
            password: {
              user: {
                name: this.config.username,
                password: this.config.password,
                domain: { name: this.config.domainName },
              },
            },
          },
          scope: {
            project: { name: this.config.region },
          },
        },
      };

      const response = await http.request(url, {
        method: http.RequestMethod.POST,
        header: { 'Content-Type': 'application/json' },
        extraData: JSON.stringify(body),
        connectTimeout: 10000,
        readTimeout: 10000,
      });

      if (response.responseCode === 201) {
        // 从响应头获取Token
        const token = response.header['X-Subject-Token'] as string;
        const responseBody = JSON.parse(response.result as string);
        const expiresAt = new Date(responseBody.token.expires_at).getTime();

        this.tokenInfo = {
          token: token,
          expiresAt: expiresAt,
          userId: responseBody.token.user.id,
        };

        console.info('[CloudAI] Token获取成功,有效期至: ' + new Date(expiresAt).toLocaleString());
        return token;
      } else {
        throw new Error(`Token请求失败: ${response.responseCode}`);
      }
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[CloudAI] Token获取异常: ${err.message}`);
      throw err;
    }
  }

  // 通用API调用方法(带重试和限流)
  async callAPI<T>(
    service: string,       // 服务名,如 ocr/v1
    action: string,        // 接口名,如/ocr/general
    body: object,          // 请求体
    maxRetries: number = 3 // 最大重试次数
  ): Promise<AIResponse<T>> {
    // 请求去重:相同请求5秒内不重复发送
    const requestKey = `${service}:${action}:${JSON.stringify(body)}`;
    if (this.requestQueue.get(requestKey)) {
      throw new Error('重复请求,请稍后再试');
    }
    this.requestQueue.set(requestKey, true);

    let lastError: Error | null = null;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const token = await this.getToken();
        const url = `https://${service}.${this.config.region}.myhuaweicloud.com${action}`;

        const response = await http.request(url, {
          method: http.RequestMethod.POST,
          header: {
            'Content-Type': 'application/json',
            'X-Auth-Token': token,
            'X-Project-Id': this.config.projectId,
          },
          extraData: JSON.stringify(body),
          connectTimeout: 15000,
          readTimeout: 30000,
        });

        // 释放请求锁
        this.requestQueue.delete(requestKey);

        if (response.responseCode === 200) {
          const result = JSON.parse(response.result as string) as AIResponse<T>;
          return result;
        } else if (response.responseCode === 429) {
          // 被限流,等待后重试
          const waitTime = Math.min(1000 * Math.pow(2, attempt), 10000);
          console.warn(`[CloudAI] 被限流,${waitTime}ms后重试(${attempt}/${maxRetries})`);
          await new Promise(resolve => setTimeout(resolve, waitTime));
          continue;
        } else if (response.responseCode === 401) {
          // Token过期,清除缓存后重试
          this.tokenInfo = null;
          console.warn('[CloudAI] Token过期,重新获取');
          continue;
        } else {
          throw new Error(`API调用失败: ${response.responseCode} - ${response.result}`);
        }
      } catch (error) {
        lastError = error as Error;
        console.error(`[CloudAI] 第${attempt}次请求失败: ${lastError.message}`);

        if (attempt < maxRetries) {
          const waitTime = 1000 * attempt; // 指数退避
          await new Promise(resolve => setTimeout(resolve, waitTime));
        }
      }
    }

    this.requestQueue.delete(requestKey);
    throw lastError || new Error('API调用失败');
  }

  // 释放资源
  release(): void {
    this.tokenInfo = null;
    this.requestQueue.clear();
  }
}

3.2 示例二:华为云OCR服务对接

基于上面的客户端封装,实现一个完整的云端OCR识别功能。

// 华为云OCR服务对接 - 完整实现
import { image } from '@kit.ImageKit';
import { buffer } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';

// OCR识别结果
interface CloudOCRResult {
  wordsBlockList: OCRWordBlock[];
  direction: number;        // 图片方向
  totalCount: number;       // 识别文字总数
}

interface OCRWordBlock {
  words: string;            // 识别文字
  confidence: number;       // 置信度
  location: OCRLocation;    // 文字位置
}

interface OCRLocation {
  topLeft: Point;
  topRight: Point;
  bottomLeft: Point;
  bottomRight: Point;
}

interface Point {
  x: number;
  y: number;
}

// OCR服务封装
class CloudOCRService {
  private client: HuaweiCloudAIClient;

  constructor(client: HuaweiCloudAIClient) {
    this.client = client;
  }

  // 通用文字识别
  async recognizeGeneral(pixelMap: image.PixelMap): Promise<CloudOCRResult> {
    // 将PixelMap转为Base64
    const base64Image = await this.pixelMapToBase64(pixelMap);

    // 调用云端OCR API
    const response = await this.client.callAPI<CloudOCRResult>(
      'ocr',
      '/v2/' + this.client.getProjectId() + '/ocr/general-text',
      {
        image: base64Image,     // Base64编码的图片
        detect_direction: true, // 检测图片方向
        detect_language: true,  // 检测语言
      }
    );

    return response.data;
  }

  // 身份证识别(专用接口,精度更高)
  async recognizeIDCard(
    pixelMap: image.PixelMap,
    side: 'front' | 'back'
  ): Promise<object> {
    const base64Image = await this.pixelMapToBase64(pixelMap);

    const response = await this.client.callAPI<object>(
      'ocr',
      '/v2/' + this.client.getProjectId() + '/ocr/id-card',
      {
        image: base64Image,
        side: side,             // 正面或反面
        return_text_location: true,
      }
    );

    return response.data;
  }

  // 发票识别
  async recognizeInvoice(pixelMap: image.PixelMap): Promise<object> {
    const base64Image = await this.pixelMapToBase64(pixelMap);

    const response = await this.client.callAPI<object>(
      'ocr',
      '/v2/' + this.client.getProjectId() + '/ocr/vat-invoice',
      {
        image: base64Image,
        return_text_location: true,
      }
    );

    return response.data;
  }

  // PixelMap转Base64辅助方法
  private async pixelMapToBase64(pixelMap: image.PixelMap): Promise<string> {
    const packingApi = image.createImagePacker();
    const packOpts: image.PackingOption = {
      format: 'image/jpeg',
      quality: 90,  // 压缩质量,平衡精度和传输大小
    };

    const data: buffer.Buffer = await packingApi.pack(pixelMap, packOpts);
    const base64Helper = new util.Base64Helper();
    const base64Str = base64Helper.encodeToStringSync(data.buffer);

    packingApi.release();
    return base64Str;
  }
}

// OCR页面组件
@Entry
@Component
struct CloudOCRPage {
  @State ocrResult: CloudOCRResult | null = null;
  @State isLoading: boolean = false;
  @State fullText: string = '';
  @State errorMessage: string = '';

  private ocrService: CloudOCRService | null = null;

  aboutToAppear(): void {
    // 初始化云端AI客户端
    const client = new HuaweiCloudAIClient({
      region: 'cn-north-4',
      projectId: 'your-project-id',
      domainName: 'your-domain',
      username: 'your-username',
      password: 'your-password', // 实际应从安全存储获取
    });

    this.ocrService = new CloudOCRService(client);
  }

  // 执行云端OCR识别
  private async doRecognize(pixelMap: image.PixelMap): Promise<void> {
    if (!this.ocrService) {
      this.errorMessage = 'OCR服务未初始化';
      return;
    }

    this.isLoading = true;
    this.errorMessage = '';
    this.ocrResult = null;
    this.fullText = '';

    try {
      const result = await this.ocrService.recognizeGeneral(pixelMap);
      this.ocrResult = result;

      // 拼接完整文本
      this.fullText = result.wordsBlockList
        .map(block => block.words)
        .join('\n');

      console.info(`[CloudOCR] 识别完成,共${result.totalCount}个文字块`);
    } catch (error) {
      const err = error as BusinessError;
      this.errorMessage = `识别失败: ${err.message}`;
      console.error(`[CloudOCR] 错误: ${err.code} - ${err.message}`);
    } finally {
      this.isLoading = false;
    }
  }

  aboutToDisappear(): void {
    this.ocrService = null;
  }

  build() {
    Column() {
      // 标题
      Text('云端OCR识别')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#e0e0e0')
        .margin({ bottom: 20 })

      // 错误信息
      if (this.errorMessage) {
        Text(this.errorMessage)
          .fontSize(14)
          .fontColor('#D0021B')
          .padding(12)
          .backgroundColor('#3d1111')
          .borderRadius(8)
          .width('100%')
          .margin({ bottom: 12 })
      }

      // 识别结果
      if (this.ocrResult) {
        // 统计信息
        Row() {
          Text(`识别到 ${this.ocrResult.totalCount} 个文字块`)
            .fontSize(14)
            .fontColor('#7B68EE')
          Text(`方向: ${this.ocrResult.direction}°`)
            .fontSize(14)
            .fontColor('#4A90D9')
            .margin({ left: 16 })
        }
        .width('100%')
        .margin({ bottom: 12 })

        // 文字块列表
        List() {
          ForEach(this.ocrResult.wordsBlockList, (block: OCRWordBlock, index: number) => {
            ListItem() {
              Row() {
                Text(block.words)
                  .fontSize(16)
                  .fontColor('#e0e0e0')
                  .layoutWeight(1)

                Text(`${(block.confidence * 100).toFixed(1)}%`)
                  .fontSize(12)
                  .fontColor(block.confidence > 0.9 ? '#4A90D9' : '#F5A623')
              }
              .width('100%')
              .padding(12)
              .backgroundColor('#1a1a2e')
              .borderRadius(8)
              .margin({ bottom: 4 })
            }
          }, (block: OCRWordBlock, index: number) => `${index}`)
        }
        .layoutWeight(1)
        .width('100%')
      } else {
        // 空状态
        Column() {
          Text('📷')
            .fontSize(48)
            .margin({ bottom: 12 })
          Text('选择图片开始识别')
            .fontSize(16)
            .fontColor('#666')
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center)
      }

      // 操作按钮
      Button(this.isLoading ? '识别中...' : '选择图片识别')
        .width('80%')
        .height(50)
        .fontSize(18)
        .backgroundColor(this.isLoading ? '#666' : '#4A90D9')
        .borderRadius(25)
        .enabled(!this.isLoading)
        .margin({ top: 16, bottom: 32 })
        .onClick(() => {
          // 实际项目中调起相册选择图片
          console.info('[CloudOCR] 选择图片');
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor('#0d0d1a')
  }
}

3.3 示例三:ModelArts自定义模型部署与调用

当华为云预置的AI服务不满足需求时,可以使用ModelArts训练自定义模型并部署为API。

// ModelArts自定义模型调用服务
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 自定义模型推理请求
interface ModelInferenceRequest {
  data: object;             // 推理输入数据
  parameters?: object;      // 推理参数
}

// 自定义模型推理响应
interface ModelInferenceResponse {
  predicted_label: string;  // 预测标签
  probabilities: Record<string, number>; // 各标签概率
  meta: {
    model_id: string;
    model_version: string;
    inference_time: number;  // 推理耗时(ms)
  };
}

// ModelArts服务封装
class ModelArtsService {
  private client: HuaweiCloudAIClient;
  private serviceId: string;  // 部署的服务ID

  constructor(client: HuaweiCloudAIClient, serviceId: string) {
    this.client = client;
    this.serviceId = serviceId;
  }

  // 调用自定义模型推理
  async infer(inputData: object, parameters?: object): Promise<ModelInferenceResponse> {
    const response = await this.client.callAPI<ModelInferenceResponse>(
      'modelarts',
      `/v1/${this.client.getProjectId()}/services/${this.serviceId}/infer`,
      {
        data: inputData,
        parameters: parameters || {},
      }
    );

    return response.data;
  }

  // 批量推理
  async batchInfer(
    inputList: object[],
    batchSize: number = 5
  ): Promise<ModelInferenceResponse[]> {
    const results: ModelInferenceResponse[] = [];

    // 分批处理,避免超时
    for (let i = 0; i < inputList.length; i += batchSize) {
      const batch = inputList.slice(i, i + batchSize);
      const batchPromises = batch.map(input => this.infer(input));

      try {
        const batchResults = await Promise.allSettled(batchPromises);

        for (const result of batchResults) {
          if (result.status === 'fulfilled') {
            results.push(result.value);
          } else {
            console.error(`[ModelArts] 批量推理某项失败: ${result.reason}`);
          }
        }
      } catch (error) {
        console.error(`[ModelArts] 批量推理异常: ${(error as Error).message}`);
      }

      // 批次间短暂延迟,避免限流
      if (i + batchSize < inputList.length) {
        await new Promise(resolve => setTimeout(resolve, 200));
      }
    }

    return results;
  }

  // 查询服务状态
  async getServiceStatus(): Promise<{
    status: string;
    inferType: string;
    startTime: string;
  }> {
    const token = await this.client.getToken();
    const url = `https://modelarts.${this.client.getRegion()}.myhuaweicloud.com/v1/${this.client.getProjectId()}/services/${this.serviceId}`;

    const response = await http.request(url, {
      method: http.RequestMethod.GET,
      header: { 'X-Auth-Token': token },
      connectTimeout: 10000,
      readTimeout: 10000,
    });

    if (response.responseCode === 200) {
      const result = JSON.parse(response.result as string);
      return {
        status: result.status,
        inferType: result.infer_type,
        startTime: result.start_time,
      };
    }

    throw new Error(`服务状态查询失败: ${response.responseCode}`);
  }
}

// 自定义模型推理页面
@Entry
@Component
struct CustomModelPage {
  @State inferenceResult: ModelInferenceResponse | null = null;
  @State isInferring: boolean = false;
  @State serviceStatus: string = 'unknown';
  @State inferenceHistory: ModelInferenceResponse[] = [];

  private modelArtsService: ModelArtsService | null = null;

  aboutToAppear(): void {
    const client = new HuaweiCloudAIClient({
      region: 'cn-north-4',
      projectId: 'your-project-id',
      domainName: 'your-domain',
      username: 'your-username',
      password: 'your-password',
    });

    this.modelArtsService = new ModelArtsService(client, 'your-service-id');
    this.checkServiceStatus();
  }

  // 检查服务状态
  private async checkServiceStatus(): Promise<void> {
    if (!this.modelArtsService) return;

    try {
      const status = await this.modelArtsService.getServiceStatus();
      this.serviceStatus = status.status;
      console.info(`[ModelArts] 服务状态: ${status.status}`);
    } catch (error) {
      this.serviceStatus = 'error';
      console.error('[ModelArts] 状态检查失败');
    }
  }

  // 执行推理
  private async doInference(): Promise<void> {
    if (!this.modelArtsService) return;

    this.isInferring = true;
    try {
      // 示例:发送图片特征数据
      const result = await this.modelArtsService.infer(
        { image_base64: 'your-base64-image-data' },
        { top_k: 5 }
      );

      this.inferenceResult = result;
      this.inferenceHistory.push(result);
      console.info(`[ModelArts] 推理完成: ${result.predicted_label}`);
    } catch (error) {
      console.error(`[ModelArts] 推理失败: ${(error as Error).message}`);
    } finally {
      this.isInferring = false;
    }
  }

  build() {
    Scroll() {
      Column() {
        // 服务状态指示器
        Row() {
          Circle({ width: 10, height: 10 })
            .fill(this.serviceStatus === 'running' ? '#4A90D9' : '#D0021B')
          Text(`服务状态: ${this.serviceStatus}`)
            .fontSize(14)
            .fontColor('#999')
            .margin({ left: 8 })
        }
        .margin({ bottom: 20 })

        // 推理结果
        if (this.inferenceResult) {
          Text('推理结果')
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
            .fontColor('#e0e0e0')
            .margin({ bottom: 12 })

          // 预测标签
          Row() {
            Text('预测标签:')
              .fontSize(16)
              .fontColor('#999')
            Text(this.inferenceResult.predicted_label)
              .fontSize(20)
              .fontWeight(FontWeight.Bold)
              .fontColor('#4A90D9')
              .margin({ left: 8 })
          }
          .width('100%')
          .margin({ bottom: 12 })

          // 概率分布
          Text('概率分布:')
            .fontSize(14)
            .fontColor('#7B68EE')
            .margin({ bottom: 8 })

          ForEach(
            Object.entries(this.inferenceResult.probabilities),
            ([label, prob]: [string, number]) => {
              Row() {
                Text(label)
                  .fontSize(14)
                  .fontColor('#e0e0e0')
                  .width(80)
                Progress({ value: prob * 100, total: 100, type: ProgressType.Linear })
                  .layoutWeight(1)
                  .color('#7B68EE')
                Text(`${(prob * 100).toFixed(2)}%`)
                  .fontSize(12)
                  .fontColor('#F5A623')
                  .width(70)
                  .textAlign(TextAlign.End)
              }
              .width('100%')
              .padding(8)
              .backgroundColor('#1a1a2e')
              .borderRadius(6)
              .margin({ bottom: 4 })
            }
          )

          // 推理元信息
          Text(`模型: ${this.inferenceResult.meta.model_id} | 耗时: ${this.inferenceResult.meta.inference_time}ms`)
            .fontSize(12)
            .fontColor('#666')
            .margin({ top: 12 })
        }

        // 操作按钮
        Button(this.isInferring ? '推理中...' : '执行推理')
          .width('80%')
          .height(50)
          .fontSize(18)
          .backgroundColor(this.isInferring ? '#666' : '#7B68EE')
          .borderRadius(25)
          .enabled(!this.isInferring && this.serviceStatus === 'running')
          .margin({ top: 24, bottom: 32 })
          .onClick(() => this.doInference())
      }
      .width('100%')
      .padding(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0d0d1a')
  }
}

四、踩坑与注意事项

4.1 AK/SK千万不要硬编码

这是最常见的安全问题。很多开发者图方便,直接把AK/SK写在代码里:

// ❌ 绝对不要这样做!
const AK = 'WRDXMSZJU...';
const SK = 'v3F7qN9pL...';

正确做法

  1. 使用HarmonyOS的安全存储(@ohos.security.huks)加密保存密钥
  2. 通过自有后端服务器代理API调用,客户端不直接接触AK/SK
  3. 使用Token认证,Token有有效期,泄露风险可控

4.2 图片Base64编码的坑

华为云OCR等视觉服务要求图片以Base64编码传入,但有几个细节容易踩坑:

  • 编码后大小限制:Base64编码会使数据膨胀约33%,原图超过10MB可能超限
  • 格式要求:支持JPEG/PNG/BMP/TIFF,不支持WebP
  • EXIF方向:部分手机拍的照片有EXIF旋转标记,需要先校正方向再编码

4.3 网络超时与弱网处理

云端AI服务的响应时间通常在1-5秒,但在弱网环境下可能更久。必须设置合理的超时时间

// 推荐的超时配置
const HTTP_CONFIG = {
  connectTimeout: 10000,  // 连接超时10秒
  readTimeout: 60000,     // 读取超时60秒(大模型推理可能较慢)
  writeTimeout: 30000,    // 写入超时30秒(上传大图片时)
};

同时,建议实现离线降级策略:网络不可用时自动切换到端侧模型。

4.4 并发请求的坑

华为云AI服务有QPS限制(不同服务不同,通常5-20 QPS),批量推理时务必控制并发

// 并发控制器
class ConcurrencyController {
  private running: number = 0;
  private maxConcurrency: number;

  constructor(maxConcurrency: number = 5) {
    this.maxConcurrency = maxConcurrency;
  }

  async run<T>(task: () => Promise<T>): Promise<T> {
    // 等待空闲槽位
    while (this.running >= this.maxConcurrency) {
      await new Promise(resolve => setTimeout(resolve, 100));
    }

    this.running++;
    try {
      return await task();
    } finally {
      this.running--;
    }
  }
}

4.5 费用监控

云端AI服务按调用次数计费,务必在代码中加入费用监控,避免因为bug导致大量无效调用:

// 简单的费用追踪器
class CostTracker {
  private callCount: number = 0;
  private dailyLimit: number;

  constructor(dailyLimit: number = 10000) {
    this.dailyLimit = dailyLimit;
  }

  // 每次调用前检查
  canCall(): boolean {
    if (this.callCount >= this.dailyLimit) {
      console.warn(`[CostTracker] 已达每日调用上限(${this.dailyLimit})`);
      return false;
    }
    return true;
  }

  // 记录一次调用
  record(): void {
    this.callCount++;
    if (this.callCount % 100 === 0) {
      console.info(`[CostTracker] 今日已调用${this.callCount}`);
    }
  }

  getUsage(): number {
    return this.callCount;
  }
}

五、HarmonyOS 6适配

5.1 网络安全增强

HarmonyOS 6引入了更严格的网络安全策略:

变更项 HarmonyOS 5 HarmonyOS 6
证书校验 系统级校验 新增应用级证书锁定(Certificate Pinning)
DNS解析 系统DNS 新增DoH(DNS over HTTPS)支持
请求签名 手动实现 新增@ohos.net.signature模块

5.2 迁移指南

// HarmonyOS 6 证书锁定配置
import { http } from '@kit.NetworkKit';

const httpClient = http.createHttpClient({
  // 证书锁定:只信任特定证书
  certificatePinning: {
    pins: [
      {
        hostname: '*.myhuaweicloud.com',
        publicKeyHash: 'sha256/your-pin-hash-here',
      },
    ],
  },
  // DoH配置
  dnsOverHttps: {
    url: 'https://dns.huaweicloud.com/dns-query',
  },
});

5.3 端云无缝切换

HarmonyOS 6新增了AIServiceRouter,可以自动在端侧和云侧之间切换:

// HarmonyOS 6 端云自动路由
import { aiServiceRouter } from '@hms.core.ml-kit';

const router = aiServiceRouter.create({
  strategy: aiServiceRouter.Strategy.AUTO, // 自动选择最优路径
  fallback: aiServiceRouter.Fallback.ON_DEVICE, // 云端不可用时降级到端侧
  latencyThreshold: 2000, // 延迟阈值,超过2秒考虑切换
});

// 使用路由器调用AI能力
const result = await router.recognizeText(imageData);
// result.source 会告诉你实际使用了端侧还是云侧

六、总结

本文深入讲解了HarmonyOS应用对接华为云AI服务的全链路实现,核心知识点如下:

华为云AI服务对接知识图谱
├── 认证鉴权
│   ├── AK/SK签名(服务端用)
│   ├── Token认证(客户端推荐)
│   ├── IAM代理(企业场景)
│   └── 安全存储密钥(禁止硬编码)
├── 请求封装
│   ├── Token缓存与自动续期
│   ├── 请求去重
│   ├── 指数退避重试
│   ├── 429限流处理
│   └── 401自动重新认证
├── 服务对接
│   ├── OCR文字识别
│   ├── 图像识别
│   ├── 语音服务
│   ├── NLP服务
│   └── ModelArts自定义模型
├── 踩坑要点
│   ├── AK/SK安全
│   ├── Base64编码细节
│   ├── 超时与弱网
│   ├── 并发控制
│   └── 费用监控
└── HarmonyOS 6适配
    ├── 证书锁定
    ├── DoH支持
    ├── 请求签名模块
    └── AIServiceRouter端云自动路由

一句话总结:华为云AI服务让HarmonyOS应用获得了远超端侧算力的AI能力,但对接过程中必须重视认证安全、请求健壮性、并发控制和费用管理,HarmonyOS 6的端云自动路由让这一切变得更简单。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。