import http from '@ohos.net.http';
import { BusinessError } from '@kit.BasicServicesKit';
import { Config, logger, preferencesUtil, ToastUtil } from '@ecell/common';
import { BaseErr } from '../base/BaseErr';
import { router } from '@kit.ArkUI';
import { BaseBody } from '../base/BaseBody';

const TAG = "HttpCore"

// 1、创建RequestOption.ets 配置类
export interface RequestOptions {
  url: string; //url,请求地址
  HEADER?: Record<string, Object>,
  method?: RequestMethod; //  请求类型 default is GET
  queryParams ?: Record<string, string | number | boolean>; //请求参数 示例 「{"a":"b"}」
  extraData?: string | BaseBody | ArrayBuffer; //请求体
  contentType?: ContentType; //请求类型-json或者form表单
  multiFormDataArray?: Array<MultiFormData>; //当为form表单填,也可不填当然不填入参为空没有意义
  /* 示例:
   {
          name: "openid",
          contentType: MediaType.TEXT,
          data: openid
   }
     */
}

//请求类型枚举,参考http.RequestMethod.POST
export enum RequestMethod {
  OPTIONS = "OPTIONS",
  GET = "GET",
  HEAD = "HEAD",
  POST = "POST",
  PUT = "PUT",
  DELETE = "DELETE",
  TRACE = "TRACE",
  CONNECT = "CONNECT"
}

export enum ContentType {
  JSON = "application/json", //json
  FORM = "multipart/form-data" //表单
}

/**
 * 表单媒体数据类型
 */
export enum MediaType {
  TEXT = "text/plain",
  IMAGE = "image/jpeg",
  AMR = "audio/amr"
}

/**
 * Represents the properties of a form object.
 * @interface MultiFormData
 * @syscap SystemCapability.Communication.NetStack
 * @since 11
 */
export interface MultiFormData {
  /**
   * MIME name for the data field.
   * @type {string}
   * @syscap SystemCapability.Communication.NetStack
   * @since 11
   */
  name: string;

  /**
   * Content type of the data field.
   * @type {string}
   * @syscap SystemCapability.Communication.NetStack
   * @since 11
   */
  contentType: MediaType;

  /**
   * Remote file name for the data field.
   * @type {?string}
   * @syscap SystemCapability.Communication.NetStack
   * @since 11
   */
  remoteFileName?: string;

  /**
   * This parameter sets a mime part's body content from memory data.
   * @type {?(string | Object | ArrayBuffer)}
   * @syscap SystemCapability.Communication.NetStack
   * @since 11
   */
  data?: string | Object | ArrayBuffer;

  /**
   * This parameter sets a mime part's body content from the file's contents.
   * This is an alternative to curl_mime_data for setting data to a mime part.
   * If data is empty, filePath must be set.
   * If data has a value, filePath does not take effect.
   * @type {?string}
   * @syscap SystemCapability.Communication.NetStack
   * @since 11
   */
  filePath?: string;
}

async function getToken(): Promise<string> {
  return new Promise<string>(async (resolve, reject) => {
    try {
      const data = await preferencesUtil.getPreferenceValue("ecell_preferences", "token", "null")
      if (typeof data === 'string') {
        resolve(data);
      } else {
        reject(new Error('Invalid token'));
      }
    } catch (err) {
      reject(err);
    }
  });

}

/**
 * Http请求器
 */
export class HttpCore {
  //基础URL
  private baseURL: string = Config.BASE_URL

  /**
   * 发送请求
   * @param requestOption
   * @returns Promise
   */
  request<T extends BaseErr>(requestOption: RequestOptions): Promise<T> {
    return new Promise<T>((resolve: Function, reject: Function) => {
      this.sendRequest(requestOption)
        .then((response: http.HttpResponse) => {
          if (typeof response.result !== 'string') {
            reject(new Error('无效的数据类型'));
          } else {
            let bean: T = JSON.parse(response.result);
            if (bean) {
              if (bean.code === 402) {
                logger.error(TAG, bean.msg)
                ToastUtil.showToast(bean.msg)
                router.clear() //清空页面栈中的所有历史页面,仅保留当前页面作为栈顶页面。
                router.replaceUrl({
                  url: "pages/Login"
                })
              }
              resolve(bean);
            } else {
              reject(new Error('数据类型无效,JSON转T失败'));
            }
          }
        })
        .catch((error: BusinessError) => {
          reject(error);
        });
    });
  }

  private async sendRequest(requestOption: RequestOptions): Promise<http.HttpResponse> {
    // 每一个httpRequest对应一个HTTP请求任务,不可复用
    let httpRequest = http.createHttp(); //创建http请求
    // 用于订阅HTTP响应头,此接口会比request请求先返回。可以根据业务需要订阅此消息
    // 从API 8开始,使用on('headersReceive', Callback)替代on('headerReceive', AsyncCallback)。 8+
    httpRequest.on('headersReceive', (header) => {
      logger.info('header: ' + JSON.stringify(header));
    });

    let resolveFunction: Function, rejectFunction: Function;
    //这里参考arkTs异步编程哈
    //https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/async-concurrency-overview-0000001820879737
    //Promise是一种用于处理异步操作的对象,可以将异步操作转换为类似于同步操作的风格,以方便代码编写和维护。Promise提供了一个状态机制来管理异步操作的不同阶段,并提供了一些方法来注册回调函数以处理异步操作的成功或失败的结果。
    // Promise有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。Promise对象创建后处于pending状态,并在异步操作完成后转换为fulfilled或rejected状态。
    const resultPromise = new Promise<http.HttpResponse>((resolve: Function, reject: Function) => {
      resolveFunction = resolve;
      rejectFunction = reject;
    });
    if (!this.isValidUrl(requestOption.url)) {
      return Promise.reject(new Error('url格式不合法.'));
    }
    //获取token
    let token: string = "";
    await getToken().then((v: string) => {
      token = v;
    })
    //拼接URL
    let URL: string = '';
    if (requestOption.url.indexOf('http') > -1) {
      URL = requestOption.url
    } else {
      URL = this.appendQueryParams(`${this.baseURL}` + requestOption.url, token, requestOption.queryParams);
    }
    logger.info(TAG, URL);
    //拼接请求头
    let HEADER: Record<string, Object> = {
      'Content-Type': 'application/json',
      'token': token,
      'client_type': 'HarmonyOS'
    }
    if (requestOption.HEADER) {
      Object.keys(requestOption.HEADER).forEach((key) => {
        if (requestOption.HEADER)
          HEADER[key] = requestOption.HEADER[key]
      })
    }
    //拼接表单
    let form: Array<MultiFormData> = requestOption.multiFormDataArray ?? new Array<MultiFormData>();
    if (!requestOption.contentType) {
      requestOption.contentType = ContentType.JSON //默认JSON
    }
    //数据格式协议
    switch (requestOption.contentType) {
      case ContentType.FORM:
        HEADER['Content-Type'] = requestOption.contentType;
        form.push({
          name: "accesstoken", //表单自动拼接
          contentType: MediaType.TEXT,
          data: token //表单自动拼接token
        })
        break;
      case ContentType.JSON:
        if (requestOption.extraData) {
          (requestOption.extraData as BaseBody).accesstoken = token //json赋值token
        }
        break;
      default:
    }


    //调用
    let promise = httpRequest.request
    (
      // 填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在extraData中指定
      URL,
      {
        method: requestOption.method, // 可选,默认为http.RequestMethod.GET
        header: HEADER, // 开发者根据自身业务需要添加header字段
        extraData: requestOption.extraData, // 当使用POST请求时此字段用于传递内容
        expectDataType: http.HttpDataType.STRING, // 可选,指定返回数据的类型(字符)
        multiFormDataList: form// 可选,仅当Header中,'content-Type'为'multipart/form-data'时生效,自API 11开始支持该属性
      }
    );

    promise.then((response: http.HttpResponse) => {
      logger.info('Result:' + response.result); // data.result为HTTP响应内容,可根据业务需要进行解析
      logger.info('code:' + response.responseCode); // data.responseCode响应编码
      logger.info('header:' + JSON.stringify(response.header)); // data.header为HTTP响应头,可根据业务需要进行解析
      if (http.ResponseCode.OK !== response.responseCode) {
        rejectFunction(new Error('http responseCode !=200'))
      } else {
        //如果返回数据包含token信息 则刷新token
        /*let resultObj = JSON.parse(response.result);
        if (response.token != undefined)  {
          PreferenceUtil.putPreference('token',resultObj.token)
        }*/
        resolveFunction(response);
      }
    }).catch((err: BusinessError) => {
      logger.error("请求失败" + `${JSON.stringify(err)}`)
      // 取消订阅HTTP响应头事件
      httpRequest.off('headersReceive');
      rejectFunction(err);
    }).finally(() => {
      // 当该请求使用完毕时,调用destroy方法主动销毁。
      httpRequest.destroy();
    })
    return resultPromise;
  }


  private appendQueryParams(url: string, token: string, queryParams?: Record<string, string | number | boolean>): string {
    //  使用将参数拼接到url上
    let urlParams = ''
    if (queryParams) {
      urlParams = Object.keys(queryParams).map(key => `${key}=${queryParams[key]}`).join('&');
    }
    if (urlParams) {
      url = url + "?" + urlParams + "&accesstoken=" + token;
    } else {
      url = url + "?accesstoken=" + token;
    }
    return url;
  }

  private isValidUrl(url: string): boolean {
    // 实现URL格式判断
    return true;
  }
}

// 实例化请求器
const httpCore = new HttpCore();


export class HttpManager {
  private static mInstance: HttpManager;

  // 防止实例化
  private constructor() {
  }

  //单例模式
  static getInstance(): HttpManager {
    if (!HttpManager.mInstance) {
      HttpManager.mInstance = new HttpManager();
    }
    return HttpManager.mInstance;
  }

  //请求
  request<T extends BaseErr>(option: RequestOptions): Promise<T> {
    return httpCore.request(option);
  }
}

export default HttpManager;

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐