一、服务端实现(Spring Boot)
服务端采用Spring Boot框架接收图片上传请求,核心实现如下:
接口设计
- 请求方式:POST
- 内容类型:multipart/form-data
- 接口路径:/updateAvatar
- 文件参数名:file(与客户端保持一致)
核心代码
kotlin
@PostMapping("updateAvatar")
fun updateAvatar(
@RequestParam("file") file: MultipartFile?,
): GeneralResult<LoginResultVO> {
return GeneralResult.success(userService.modifyUserAvatarUrl(file))
}
服务端通过@RequestParam("file")
注解接收客户端上传的图片文件,类型为MultipartFile
,然后调用业务层方法处理文件并返回结果。
二、鸿蒙客户端实现
鸿蒙客户端实现图片上传功能主要分为四个步骤:选择图片、读取文件、上传文件,全程无需申请文件访问权限,仅访问用户明确选择的文件。
1. 选择图片(PhotoViewPicker)
使用鸿蒙系统提供的photoAccessHelper.PhotoViewPicker
组件实现图片选择,该组件无需额外权限申请,仅能访问用户明确选择的图片。
实现步骤:
初始化选择器
typescript
// 初始化图片选择器
photoPicker = new photoAccessHelper.PhotoViewPicker();
// 配置选择参数
options: photoAccessHelper.PhotoSelectOptions = {
maxSelectNumber: 1, // 限制最大选择数量为1张
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE // 仅允许选择图片类型
};
调用选择器并处理结果
typescript
// 调用选择器
this.photoPicker.select(this.options)
.then((result) => {
let uriList = result.photoUris; // 获取选中图片的URI数组
// 判断是否选择了图片
if (ArrayUtil.isNotEmpty(uriList)) {
this.viewModel.state.imageUri = uriList[0]; // 保存选中图片的URI
this.viewModel.updateAvatar(); // 触发上传操作
}
})
.catch((err: BusinessError) => {
// 处理选择过程中的异常
console.error("图片选择失败:" + err.message);
});
2. 读取文件内容
选择图片后获得的是图片的URI,不能直接用于上传,需要通过文件IO操作读取文件内容到ArrayBuffer
中。
typescript
// 打开文件,以只读模式
let file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY);
// 获取文件信息(包括文件大小)
let fileStat = fileIo.statSync(file.fd);
// 初始化与文件大小匹配的缓冲区
let arrayBuffer = new ArrayBuffer(fileStat.size);
// 将文件内容读取到缓冲区
let readLen = fileIo.readSync(file.fd, arrayBuffer);
// 验证读取长度是否与文件大小一致
if (readLen !== fileStat.size) {
console.warn("文件读取不完整,实际读取:" + readLen + ",文件大小:" + fileStat.size);
}
// 关闭文件,释放资源
fileIo.closeSync(file);
// 从URI中提取文件名
const fileName = uri.substring(uri.lastIndexOf('/') + 1);
3. 上传文件(NetworkKit)
使用鸿蒙@kit.NetworkKit
提供的HTTP能力实现文件上传,核心是配置multipart/form-data
类型的请求。
封装上传方法
typescript
static postFile<T>(url: string, fileData: ArrayBuffer, fileName: string): Promise<Result<T>> {
// 创建HTTP请求对象
let httpRequest = http.createHttp();
return new Promise<Result<T>>((resolve) => {
// 发起POST请求
httpRequest.request(
HttpService.BASE_URL + url,
{
method: http.RequestMethod.POST,
header: {
// 必须指定为multipart/form-data类型
'Content-Type': 'multipart/form-data'
},
// 配置表单数据
multiFormDataList: [
{
name: 'file', // 与服务端定义的参数名保持一致
contentType: 'image/*', // 图片类型
data: fileData, // 文件内容(ArrayBuffer)
remoteFileName: fileName // 文件名
}
],
expectDataType: http.HttpDataType.STRING // 预期返回数据类型
},
(err: BusinessError | undefined, data: http.HttpResponse) => {
// 清理HTTP资源的函数
const cleanup = () => {
try {
httpRequest.destroy();
console.info('[HTTP] 请求资源已销毁');
} catch (destroyError) {
console.error('[HTTP] 销毁请求时出错:', destroyError);
}
};
// 处理响应结果(根据实际业务需求实现)
if (err) {
resolve({ success: false, error: err.message });
} else {
resolve({ success: true, data: JSON.parse(data.result as string) });
}
// 清理资源
cleanup();
}
);
});
}
调用上传方法
typescript
// 调用封装的上传接口
let result = await ApiService.updateAvatar(arrayBuffer, fileName);
// 处理上传结果
if (result.success) {
console.info("图片上传成功");
} else {
console.error("图片上传失败:" + result.error);
}
三、实现要点总结
-
权限处理 :使用
PhotoViewPicker
无需申请文件访问权限,仅访问用户明确选择的文件,更符合隐私保护要求。 -
数据流转:
- 客户端:URI → ArrayBuffer → 表单数据 → 服务端
- 服务端:MultipartFile → 业务处理
-
关键匹配项:
- 客户端
multiFormDataList
中的name
属性需与服务端@RequestParam
的参数名一致(均为"file") - 内容类型需统一为
multipart/form-data
- 客户端
-
资源管理:文件操作和网络请求完成后需及时释放资源,避免内存泄漏。
通过以上实现,鸿蒙5.1.1应用可以安全、高效地实现图片上传功能,兼顾用户体验与系统安全性。