HarmonyOS鸿蒙Next应用中的网络请求有什么特殊处理?

HarmonyOS鸿蒙Next应用中的网络请求有什么特殊处理? 鸿蒙应用的网络请求在超时设置、错误处理、证书验证等方面需要注意,本文介绍网络请求的最佳实践和常见问题解决。

4 回复

一、基础网络请求配置

1. 创建网络请求工具(utils/request.js)

/**
 * 网络请求工具
 * 统一处理鸿蒙应用的网络请求
 */

// 请求配置
const REQUEST_CONFIG = {
    // 基础 URL
    baseURL: 'https://your-api.com/api',
    // 超时时间(毫秒)
    timeout: 10000,
    // 请求头
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    },
    // 是否显示加载提示
    showLoading: true,
    // 是否显示错误提示
    showError: true,
    // 重试次数
    retryCount: 2,
    // 重试延迟(毫秒)
    retryDelay: 1000
}

// 请求队列(用于取消请求)
const requestQueue = new Map()

/**
 * 统一请求方法
 * @param {Object} options 请求配置
 * @returns {Promise} 请求结果
 */
export function request(options = {}) {
    const {
        url = '',
        method = 'GET',
        data = {},
        header = {},
        timeout = REQUEST_CONFIG.timeout,
        showLoading = REQUEST_CONFIG.showLoading,
        showError = REQUEST_CONFIG.showError,
        retry = REQUEST_CONFIG.retryCount,
        cancelToken = null  // 取消令牌
    } = options
    
    // 生成请求 ID
    const requestId = `${Date.now()}_${Math.random()}`
    
    // 构建完整 URL
    const fullUrl = url.startsWith('http') ? url : `${REQUEST_CONFIG.baseURL}${url}`
    
    // 合并请求头
    const headers = {
        ...REQUEST_CONFIG.headers,
        ...header
    }
    
    // 显示加载提示
    if (showLoading) {
        uni.showLoading({
            title: '加载中...',
            mask: true
        })
    }
    
    // 创建请求 Promise
    return new Promise((resolve, reject) => {
        // 检查网络状态
        checkNetworkStatus().then(isConnected => {
            if (!isConnected) {
                if (showLoading) uni.hideLoading()
                if (showError) {
                    uni.showToast({
                        title: '网络连接失败',
                        icon: 'none'
                    })
                }
                reject(new Error('网络连接失败'))
                return
            }
            
            // 发起请求
            const requestTask = uni.request({
                url: fullUrl,
                method: method,
                data: data,
                header: headers,
                timeout: timeout,
                success: (response) => {
                    // 隐藏加载提示
                    if (showLoading) uni.hideLoading()
                    
                    // 从队列中移除
                    requestQueue.delete(requestId)
                    
                    // 处理响应
                    handleResponse(response, showError)
                        .then(resolve)
                        .catch(reject)
                },
                fail: (error) => {
                    // 隐藏加载提示
                    if (showLoading) uni.hideLoading()
                    
                    // 从队列中移除
                    requestQueue.delete(requestId)
                    
                    // 处理错误
                    handleError(error, {
                        url: fullUrl,
                        method: method,
                        retry: retry,
                        showError: showError
                    })
                        .then(resolve)
                        .catch(reject)
                }
            })
            
            // 保存到队列
            requestQueue.set(requestId, requestTask)
            
            // 设置取消令牌
            if (cancelToken) {
                cancelToken.promise.then(() => {
                    requestTask.abort()
                    requestQueue.delete(requestId)
                    reject(new Error('请求已取消'))
                })
            }
        }).catch(error => {
            if (showLoading) uni.hideLoading()
            reject(error)
        })
    })
}

/**
 * 检查网络状态
 * @returns {Promise<Boolean>} 是否连接
 */
function checkNetworkStatus() {
    return new Promise((resolve) => {
        // #ifdef APP-HARMONY
        try {
            // 使用 plus API 检查网络
            plus.networkinfo.getCurrentType((type) => {
                // type: 0-无网络, 1-WiFi, 2-2G, 3-3G, 4-4G, 5-5G
                resolve(type !== 0)
            }, (error) => {
                console.error('获取网络状态失败:', error)
                resolve(true)  // 默认认为有网络
            })
        } catch (error) {
            console.error('检查网络状态失败:', error)
            resolve(true)
        }
        // #endif
        
        // #ifndef APP-HARMONY
        // H5 或其他平台
        uni.getNetworkType({
            success: (res) => {
                resolve(res.networkType !== 'none')
            },
            fail: () => {
                resolve(true)  // 默认认为有网络
            }
        })
        // #endif
    })
}

/**
 * 处理响应
 * @param {Object} response 响应对象
 * @param {Boolean} showError 是否显示错误
 * @returns {Promise} 处理结果
 */
function handleResponse(response, showError) {
    return new Promise((resolve, reject) => {
        const { statusCode, data } = response
        
        // HTTP 状态码检查
        if (statusCode >= 200 && statusCode < 300) {
            // 业务状态码检查
            if (data && typeof data === 'object') {
                if (data.code === 200 || data.success) {
                    resolve(data.data || data)
                } else {
                    // 业务错误
                    const error = new Error(data.message || data.msg || '请求失败')
                    error.code = data.code
                    error.data = data
                    
                    if (showError) {
                        uni.showToast({
                            title: error.message,
                            icon: 'none'
                        })
                    }
                    
                    reject(error)
                }
            } else {
                resolve(data)
            }
        } else {
            // HTTP 错误
            const error = new Error(`请求失败: ${statusCode}`)
            error.statusCode = statusCode
            error.data = data
            
            if (showError) {
                let errorMsg = '请求失败'
                if (statusCode === 404) {
                    errorMsg = '请求的资源不存在'
                } else if (statusCode === 500) {
                    errorMsg = '服务器错误'
                } else if (statusCode === 403) {
                    errorMsg = '没有权限'
                } else if (statusCode === 401) {
                    errorMsg = '未授权,请重新登录'
                    // 可以触发重新登录
                    // handleUnauthorized()
                }
                
                uni.showToast({
                    title: errorMsg,
                    icon: 'none'
                })
            }
            
            reject(error)
        }
    })
}

/**
 * 处理错误
 * @param {Object} error 错误对象
 * @param {Object} options 配置选项
 * @returns {Promise} 处理结果
 */
function handleError(error, options) {
    return new Promise((resolve, reject) => {
        const { url, method, retry, showError } = options
        
        // 错误类型判断
        let errorMessage = '网络请求失败'
        
        if (error.errMsg) {
            if (error.errMsg.includes('timeout')) {
                errorMessage = '请求超时,请检查网络'
            } else if (error.errMsg.includes('fail')) {
                errorMessage = '网络连接失败'
            } else if (error.errMsg.includes('abort')) {
                errorMessage = '请求已取消'
                reject(new Error(errorMessage))
                return
            }
        }
        
        // 重试机制
        if (retry > 0) {
            console.log(`请求失败,${REQUEST_CONFIG.retryDelay}ms 后重试,剩余 ${retry} 次`)
            
            setTimeout(() => {
                // 递归重试
                request({
                    url: url,
                    method: method,
                    retry: retry - 1,
                    showLoading: false,  // 重试时不显示加载
                    showError: false    // 重试时不显示错误
                })
                    .then(resolve)
                    .catch((retryError) => {
                        // 重试也失败
                        if (showError) {
                            uni.showToast({
                                title: errorMessage,
                                icon: 'none'
                            })
                        }
                        reject(retryError)
                    })
            }, REQUEST_CONFIG.retryDelay)
        } else {
            // 不再重试
            if (showError) {
                uni.showToast({
                    title: errorMessage,
                    icon: 'none'
                })
            }
            reject(new Error(errorMessage))
        }
    })
}

二、请求拦截和响应拦截

2. 拦截器实现

// utils/request.js 中添加拦截器

// 请求拦截器队列
const requestInterceptors = []

// 响应拦截器队列
const responseInterceptors = []

/**
 * 添加请求拦截器
 * @param {Function} interceptor 拦截器函数
 */
export function addRequestInterceptor(interceptor) {
    requestInterceptors.push(interceptor)
}

/**
 * 添加响应拦截器
 * @param {Function} interceptor 拦截器函数
 */
export function addResponseInterceptor(interceptor) {
    responseInterceptors.push(interceptor)
}

/**
 * 执行请求拦截器
 * @param {Object} config 请求配置
 * @returns {Promise<Object>} 处理后的配置
 */
async function executeRequestInterceptors(config) {
    let processedConfig = { ...config }
    
    for (const interceptor of requestInterceptors) {
        processedConfig = await interceptor(processedConfig) || processedConfig
    }
    
    return processedConfig
}

/**
 * 执行响应拦截器
 * @param {Object} response 响应对象
 * @returns {Promise<Object>} 处理后的响应
 */
async function executeResponseInterceptors(response) {
    let processedResponse = response
    
    for (const interceptor of responseInterceptors) {
        processedResponse = await interceptor(processedResponse) || processedResponse
    }
    
    return processedResponse
}

// 修改 request 函数,添加拦截器调用
export function request(options = {}) {
    // ... 前面的代码
    
    return new Promise(async (resolve, reject) => {
        try {
            // 执行请求拦截器
            const processedOptions = await executeRequestInterceptors(options)
            
            // 使用处理后的配置发起请求
            const requestTask = uni.request({
                // ... 使用 processedOptions
                success: async (response) => {
                    // 执行响应拦截器
                    const processedResponse = await executeResponseInterceptors(response)
                    // 处理响应
                    handleResponse(processedResponse, showError)
                        .then(resolve)
                        .catch(reject)
                },
                // ...
            })
        } catch (error) {
            reject(error)
        }
    })
}

3. 常用拦截器示例

// utils/interceptors.js

import { addRequestInterceptor, addResponseInterceptor } from '@/utils/request.js'
import { getToken, removeToken } from '@/utils/auth.js'

/**
 * 添加 Token 到请求头
 */
addRequestInterceptor((config) => {
    const token = getToken()
    if (token) {
        config.header = config.header || {}
        config.header['Authorization'] = `Bearer ${token}`
    }
    return config
})

/**
 * 处理未授权错误
 */
addResponseInterceptor((response) => {
    if (response.statusCode === 401) {
        // 清除 token
        removeToken()
        // 跳转到登录页
        uni.reLaunch({
            url: '/pages/login/index'
        })
    }
    return response
})

/**
 * 添加请求时间戳(防止缓存)
 */
addRequestInterceptor((config) => {
    if (config.method === 'GET') {
        const separator = config.url.includes('?') ? '&' : '?'
        config.url = `${config.url}${separator}_t=${Date.now()}`
    }
    return config
})

三、请求缓存策略

4. 缓存实现

// utils/request-cache.js

// 缓存存储
const cacheStore = new Map()
// 缓存配置
const CACHE_CONFIG = {
    // 默认缓存时间(毫秒)
    defaultTTL: 5 * 60 * 1000,  // 5分钟
    // 最大缓存数量
    maxSize: 100
}

/**
 * 生成缓存 key
 */
function generateCacheKey(url, data) {
    const dataStr = JSON.stringify(data || {})
    return `${url}_${dataStr}`
}

/**
 * 获取缓存
 */
export function getCache(url, data) {
    const key = generateCacheKey(url, data)
    const cached = cacheStore.get(key)
    
    if (cached) {
        // 检查是否过期
        if (Date.now() - cached.timestamp < cached.ttl) {
            return cached.data
        } else {
            // 过期,删除
            cacheStore.delete(key)
        }
    }
    
    return null
}

/**
 * 设置缓存
 */
export function setCache(url, data, response, ttl = CACHE_CONFIG.defaultTTL) {
    // 检查缓存数量
    if (cacheStore.size >= CACHE_CONFIG.maxSize) {
        // 删除最旧的缓存
        const firstKey = cacheStore.keys().next().value
        cacheStore.delete(firstKey)
    }
    
    const key = generateCacheKey(url, data)
    cacheStore.set(key, {
        data: response,
        timestamp: Date.now(),
        ttl: ttl
    })
}

/**
 * 清除缓存
 */
export function clearCache(url = null) {
    if (url) {
        // 清除指定 URL 的缓存
        const keys = Array.from(cacheStore.keys())
        keys.forEach(key => {
            if (key.startsWith(url)) {
                cacheStore.delete(key)
            }
        })
    } else {
        // 清除所有缓存
        cacheStore.clear()
    }
}

/**
 * 带缓存的请求
 */
export async function cachedRequest(options = {}) {
    const {
        url,
        data,
        cache = false,  // 是否使用缓存
        cacheTTL = CACHE_CONFIG.defaultTTL
    } = options
    
    // 检查缓存
    if (cache) {
        const cachedData = getCache(url, data)
        if (cachedData) {
            return Promise.resolve(cachedData)
        }
    }
    
    // 发起请求
    const response = await request({
        ...options,
        cache: false  // 请求时不使用缓存
    })
    
    // 保存到缓存
    if (cache) {
        setCache(url, data, response, cacheTTL)
    }
    
    return response
}

四、网络状态监听

5. 网络状态管理

// utils/network-monitor.js

/**
 * 网络状态监听
 */
class NetworkMonitor {
    constructor() {
        this.isConnected = true
        this.networkType = 'unknown'
        this.listeners = []
    }
    
    /**
     * 初始化监听
     */
    init() {
        // #ifdef APP-HARMONY
        // 监听网络状态变化
        plus.networkinfo.addEventListener('change', (event) => {
            const type = event.type
            this.networkType = this.getNetworkTypeName(type)
            this.isConnected = type !== 0
            
            // 通知监听器
            this.notifyListeners({
                isConnected: this.isConnected,
                networkType: this.networkType
            })
        })
        // #endif
        
        // #ifndef APP-HARMONY
        // H5 或其他平台
        uni.onNetworkStatusChange((res) => {
            this.isConnected = res.isConnected
            this.networkType = res.networkType
            
            this.notifyListeners({
                isConnected: this.isConnected,
                networkType: this.networkType
            })
        })
        // #endif
        
        // 初始检查
        this.checkNetworkStatus()
    }
    
    /**
     * 检查网络状态
     */
    checkNetworkStatus() {
        // #ifdef APP-HARMONY
        plus.networkinfo.getCurrentType((type) => {
            this.networkType = this.getNetworkTypeName(type)
            this.isConnected = type !== 0
        })
        // #endif
        
        // #ifndef APP-HARMONY
        uni.getNetworkType({
            success: (res) => {
                this.isConnected = res.networkType !== 'none'
                this.networkType = res.networkType
            }
        })
        // #endif
    }
    
    /**
     * 获取网络类型名称
     */
    getNetworkTypeName(type) {
        const types = {
            0: 'none',
            1: 'wifi',
            2: '2g',
            3: '3g',
            4: '4g',
            5: '5g'
        }
        return types[type] || 'unknown'
    }
    
    /**
     * 添加监听器
     */
    addListener(listener) {
        this.listeners.push(listener)
    }
    
    /**
     * 移除监听器
     */
    removeListener(listener) {
        const index = this.listeners.indexOf(listener)
        if (index > -1) {
            this.listeners.splice(index, 1)
        }
    }
    
    /**
     * 通知监听器
     */
    notifyListeners(status) {
        this.listeners.forEach(listener => {
            try {
                listener(status)
            } catch (error) {
                console.error('网络状态监听器错误:', error)
            }
        })
    }
}

// 创建单例
const networkMonitor = new NetworkMonitor()
// 在 App.vue 中初始化
export function initNetworkMonitor() {
    networkMonitor.init()
}

export default networkMonitor

五、请求封装方法

6. 常用请求方法封装

// utils/request.js 中添加便捷方法

/**
 * GET 请求
 */
export function get(url, data = {}, options = {}) {
    return request({
        url,
        method: 'GET',
        data,
        ...options
    })
}

/**
 * POST 请求
 */
export function post(url, data = {}, options = {}) {
    return request({
        url,
        method: 'POST',
        data,
        ...options
    })
}

/**
 * PUT 请求
 */
export function put(url, data = {}, options = {}) {
    return request({
        url,
        method: 'PUT

更多关于HarmonyOS鸿蒙Next应用中的网络请求有什么特殊处理?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


HarmonyOS Next应用网络请求基于ArkTS开发,使用@ohos.net.http模块。支持HTTP/HTTPS协议,提供异步请求能力,可通过createHttp()创建实例并调用request()发起请求。系统自动管理网络权限声明,需在module.json5中配置ohos.permission.INTERNET权限。内置网络安全机制,默认启用证书校验,支持配置自定义CA证书。

在HarmonyOS Next中,网络请求的设计更强调安全、高效和声明式开发。其核心是使用ArkTS的异步并发能力和增强的安全机制。

主要特殊处理包括:

  1. 超时与重试:通过http模块的RequestOptions可灵活设置连接、读写超时。系统提供了更优雅的异步超时控制(如配合Promise.raceasync/await与定时器),并建议将重试逻辑与业务解耦,置于上层拦截器或统一网络层中实现。

  2. 安全通信(证书验证)

    • 默认强化:系统对证书验证要求更严格,开发版模拟器可能需手动处理自签名证书(如将证书放入rawfile目录并通过securityCert模块加载)。
    • SSL Pinning:支持证书锁定,可将特定证书或公钥哈希内置到应用中,防止中间人攻击。
    • 仅允许加密连接:明确要求使用HTTPS,HTTP请求在默认安全配置下会受到限制。
  3. 声明式与错误处理

    • 推荐使用@ohos.net.http模块,其错误码(如BUSINESS_ERRORHTTP_ERROR)定义清晰,应针对不同错误类型(网络异常、服务端错误、业务逻辑错误)进行分层捕获和处理。
    • 结合ArkUI的声明式特性,网络请求状态(加载中、成功、失败)可方便地驱动UI更新,通常与@State@Link等装饰器配合使用。
  4. 性能与规范

    • 鼓励使用连接复用(默认支持)以减少开销。
    • 在应用退到后台时,应及时取消未完成的网络请求(通过http.HttpRequestdestroy方法),以节省资源。
    • 网络权限需在module.json5中明确声明(ohos.permission.INTERNET)。

最佳实践是封装统一的网络请求库,集中处理通用配置、拦截器(日志、鉴权)、错误映射和状态管理,以提升应用的可维护性和一致性。

回到顶部