鸿蒙Next API 12开发,使用@ohos/axios进行HTTP请求
创建了一个名为 HttpRequest
的类,它封装了 axios
的功能,并添加了请求和响应拦截器以处理一些通用的请求和响应逻辑。这个类提供了多种 HTTP 方法(GET、POST、PUT、DELETE)以及用于发送请求并处理响应数据的方法。以下是对您的代码的一些观察和潜在改进建议:
-
导入模块:
- 您从
@ohos/axios
导入了axios
。请注意,通常axios
是从axios
包本身导入的,而不是从@ohos/axios
。除非您正在使用特定于 OHOS(OpenHarmony)的axios
版本,否则您可能需要检查这一点。
- 您从
-
请求拦截器:
- 您在请求拦截器中添加了多个自定义头,并生成了
requestId
和签名。这是一个很好的做法,可以增强请求的安全性。 - 您设置了
validateStatus
函数来允许所有状态码小于 500 的请求通过。这意味着即使服务器返回 4xx 错误,它们也会被视为成功的响应,除非您在响应拦截器中进一步处理它们。
- 您在请求拦截器中添加了多个自定义头,并生成了
-
响应拦截器:
- 您在响应拦截器中记录了响应和错误。这是一个很好的调试工具。
- 对于错误处理,您检查了
error.response
是否存在,并据此决定如何构建要拒绝的BusinessError
对象。这是一个合理的做法。
-
方法实现:
- 您为不同的 HTTP 方法(GET、POST、PUT、DELETE)提供了单独的包装器方法,这些方法设置了
config.method
并调用了request
或requestForData
。这是一个很好的封装,使得使用这些方法时不需要每次都指定方法类型。 postData
方法似乎是多余的,因为它只是调用了post
方法并解析了.data
属性。如果post
方法已经返回了.data
(通过requestForData
),则postData
可以被移除或重构为直接调用requestForData
。
- 您为不同的 HTTP 方法(GET、POST、PUT、DELETE)提供了单独的包装器方法,这些方法设置了
-
错误处理:
- 您在
catch
块中处理了错误,并根据错误类型构建了不同的BusinessError
对象。这是一个很好的做法,因为它允许调用者根据错误类型采取不同的行动。 - 请注意,如果服务器返回的错误不包含
detailMessage
或message
字段,则您的错误对象可能会包含undefined
值。您可能需要添加一些默认值或额外的检查来处理这种情况。
- 您在
-
代码重构:
- 考虑将
request
和requestForData
方法中的重复逻辑(如错误处理)提取到单独的私有方法中,以减少代码冗余并提高可维护性。
- 考虑将
-
类型安全:
- 您已经使用了 TypeScript,这是一个很好的选择,因为它提供了类型安全。确保所有类型定义都是准确的,并且与您的业务逻辑保持一致。
-
测试:
- 考虑为您的
HttpRequest
类编写单元测试,以确保其行为符合预期。这可以帮助您在更改代码时捕获潜在的问题。
- 考虑为您的
总的来说,您的代码结构清晰,逻辑合理。通过一些小的改进和重构,您可以进一步提高其健壮性和可维护性。
这段代码定义了几个TypeScript接口,用于描述在Web开发或API交互中可能使用的数据结构。下面是对这些接口的解释:
-
BaseResponse 接口:
code
: 一个数字,通常用于表示操作的状态码,例如200表示成功,4xx表示客户端错误,5xx表示服务器错误。detailMessage
: 一个字符串,提供关于响应的详细消息或错误描述。message
: 一个字符串,提供响应的简短描述或错误消息。status
: 一个数字,通常与code
相似,用于表示HTTP状态码。success
: 一个布尔值,表示操作是否成功。timestamp
: 一个字符串,表示响应生成的时间戳。
-
BaseResponseModel 接口:
- 这个接口继承自
BaseResponse
接口,并添加了一个可选的data
属性。 data?
: 一个泛型T
,表示响应中携带的数据。由于它是可选的,所以即使没有数据,这个接口也可以被使用。- 泛型
T
允许BaseResponseModel
接口用于不同类型的响应数据,提高了接口的灵活性和复用性。
- 这个接口继承自
-
RejectError 接口:
- 这个接口继承自
BaseResponse
接口,并添加了三个额外的属性,用于描述一个拒绝或错误的详细情况。 path
: 一个字符串,表示出错时请求的路径。error
: 一个字符串,提供错误的详细描述。requestId
: 一个字符串,表示请求的唯一标识符,可以用于日志追踪或调试。
- 这个接口继承自
这些接口的设计体现了面向接口编程的思想,使得代码更加模块化、易于维护和扩展。通过定义明确的接口,可以确保不同部分之间的数据交互符合预期,减少了错误和调试的难度。此外,使用泛型T
提高了代码的复用性和灵活性,使得BaseResponseModel
接口可以适应多种不同类型的数据响应。
/**
* @Title HttpRequest
* @Description
* @Author
* @create
*/
import axios,
{ AxiosInstance,
InternalAxiosRequestConfig,
AxiosRequestConfig,
AxiosError,
AxiosResponse
} from '@ohos/axios';
import { Logger } from 'commlib'
import { BaseResponseModel, RejectError } from './Types'
import { NetworkUtil } from './util/NetworkUtil';
import { NetworkConstants } from './constants/NetworkConstants';
import { BusinessError } from '@kit.BasicServicesKit';
const MAX_RETRIES = 3;
const TAG = '[HttpRequest]';
class HttpRequest {
service: AxiosInstance
constructor() {
this.service = axios.create({
baseURL: 'https://www.baidu.com',
timeout: 10 * 1000
});
// 请求拦截
this.service.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
Logger.debug('[Request]', `url=${config.baseURL || ''}${config.url || ''}, params=${JSON.stringify(config.data)}`)
const requestId = NetworkUtil.makeUUID()
const timestamp = Date.parse(new Date().toString())
config.headers['Content-Type'] = 'application/json'
config.headers['timestamp'] = timestamp
config.headers['appKey'] = NetworkUtil.getAppKey()
config.headers['requestId'] = requestId
config.headers['sign'] = await NetworkUtil.signText(NetworkConstants.API_SECRET + timestamp + requestId)
config.headers['App-Version'] = NetworkConstants.APP_VERSION
config.headers['App-Source'] = NetworkConstants.APP_SOURCE
config.validateStatus = (status) => status < 500
return config
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
// 响应拦截
this.service.interceptors.response.use(
(response: AxiosResponse) => {
Logger.debug('[Response]', JSON.stringify(response))
return response
}, (error: AxiosError) => {
Logger.debug('[Error]', JSON.stringify(error))
return Promise.reject(error);
}
);
}
request<T>(config: AxiosRequestConfig): Promise<BaseResponseModel<T>> {
return new Promise((resolve: (value: BaseResponseModel<T>) => void, reject: (reason?: BusinessError<RejectError>) => void) => {
this.service.request<BaseResponseModel<T>>(config)
.then((response: AxiosResponse<BaseResponseModel<T>>) => {
resolve(response.data as BaseResponseModel<T>);
})
.catch((error: AxiosError) => {
if (error.response?.data != undefined) {
// reject(error.response?.data as RejectError)
let result = error.response?.data as RejectError
reject({
code: -1,
message: result.detailMessage,
name: result.message,
data: result,
});
} else {
reject({
code: -1,
message: 'AxiosError',
name: '网络异常',
})
}
})
});
}
requestForData<T>(config: AxiosRequestConfig): Promise<T> {
return new Promise((resolve: (value: T) => void, reject: (reason?: RejectError) => void) => {
this.service.request<BaseResponseModel<T>>(config)
.then((response: AxiosResponse<BaseResponseModel<T>>) => {
resolve(response.data.data!);
})
.catch((error: AxiosError) => {
reject(error.response?.data as RejectError)
})
});
}
get<T>(config: AxiosRequestConfig): Promise<BaseResponseModel<T>> {
config.method = 'GET'
return this.request<T>(config)
}
post<T>(config: AxiosRequestConfig): Promise<BaseResponseModel<T>> {
config.method = 'POST'
return this.request<T>(config)
}
postForData<T>(config: AxiosRequestConfig): Promise<T> {
config.method = 'POST'
return this.requestForData<T>(config)
}
put<T>(config: AxiosRequestConfig): Promise<BaseResponseModel<T>> {
config.method = 'PUT'
return this.request<T>(config)
}
delete<T>(config: AxiosRequestConfig): Promise<BaseResponseModel<T>> {
config.method = 'DELETE'
return this.request<T>(config)
}
postData<T>(config: AxiosRequestConfig): Promise<T> {
return new Promise((resolve: (result: T) => void, reject: (error: RejectError) => void) => {
this.post<T>(config).then((result: BaseResponseModel<T>) => {
resolve(result.data!)
}).catch((error: RejectError) => {
reject(error);
})
})
}
}
const httpRequest = new HttpRequest()
export default httpRequest;
/**
* @Title Types
* @Description
* @Author
* @create
*/
export interface RejectError extends BaseResponse {
path: string
error: string
requestId: string
}
export interface BaseResponseModel<T> extends BaseResponse {
data?: T
}
interface BaseResponse {
code: number
detailMessage: string
message: string
status: number
success: boolean
timestamp: string
}
/**
* @Title NetworkUtil
* @Description
* @Author
* @create
*/
import { util } from '@kit.ArkTS'
import { cryptoFramework } from '@kit.CryptoArchitectureKit'
import { NetworkConstants } from '../constants/NetworkConstants'
export class NetworkUtil {
public static async signText(rawStr: string): Promise<string> {
let data = new util.TextEncoder().encodeInto(rawStr)
let sha1 = cryptoFramework.createMd('SHA1')
await sha1.update({ data: data })
const dataBlob = await sha1.digest()
const result = NetworkUtil.uint8ArrayToHexStr(dataBlob.data)
return result
}
private static uint8ArrayToHexStr(data: Uint8Array): string {
let hexString = "";
let i: number;
for (i = 0; i < data.length; i++) {
let char = ('00' + data[i].toString(16)).slice(-2);
hexString += char;
}
return hexString;
}
public static makeUUID(): string {
const result: string = util.generateRandomUUID(false).replace(/-/g, '')
return result
}
public static getAppKey(): string {
return NetworkConstants.API_KEY
}
public static getAppSecret(): string {
return NetworkConstants.API_SECRET
}
}
/**
* @Title NetworkConstants
* @Description
* @Author 贾少英
* @create 2024/3/27
*/
export class NetworkConstants {
static readonly API_KEY: string = ''
static readonly API_SECRET: string = ''
static readonly APP_SOURCE: string = 'HarmonyOS'
static readonly APP_VERSION: string = '1.0.1'
}