HarmonyOS 鸿蒙Next ArkTS 前端和SpringBoot后端文件上传示例(Request.upload)

HarmonyOS 鸿蒙Next ArkTS 前端和SpringBoot后端文件上传示例(Request.upload) 前言

此帖主要讲解通过开发文档示例代码写一个完整Demo,方便初学者理解开发文档内容,大家都知道3.0使用的是FA模式、3.1使用的是Stage模式,所以同样是文件上传,代码写法上有些不一样,开发文档也不一样,比如在3.1下,可以在HarmonyOS Developer > 文档 > 指南 > 开发下找到文件上传下载示例代码,而在3.0下,就找不到相应指南开发了,只能在HarmonyOS Developer > 文档 > API参考 > ArkTS API参考找到@ohos.request (上传下载)文档,为了实现一个完整文件上传Demo,后端是少不了的,这里我使用了我平常工作中用到的SpringBoot开发后端,为了验证文件上传接口是否正常,使用Thymeleaf写一个简单的前端页面来测试接口,先保证后端文件上传接口是正常的,这样其它前端调用就可以排除后端文件上传接口问题,专心调试前端代码,希望小伙伴通过此贴学习到文件上传同时,参考此思路也可以自己完成其它示例代码完成Demo。

知识点

  • ArkTS(3.0)文件管理(前端)
  • ArkTS(3.1)文件管理(前端)
  • SpringBoot(后端)

ArkTS(3.0)

  1. 此版本使用的是FA模式、配置文件名是config.json 由于文件上传需要网络,需要添加权限:ohos.permission.INTERNET,默认支持https,如果要支持http,需要在config.json里增加network标签,属性标识 “cleartextTraffic”: true。所以config.json要添加的内容以下:
{
  "app": {...},
  "deviceConfig": {
    "default": {
      "network": {
        "cleartextTraffic": true
      }
    }
  },
  "module": {
    "reqPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}
  1. 文件上传页面就一个index.ets文件,里面包含UI和调用后端接口,代码如下:
import request from '@ohos.request';
import featureAbility from '@ohos.ability.featureAbility';
import fileio from '@ohos.fileio';

@Entry
@Component
struct Index {
  @State btnLabel: string = '提交文件'
  private uploadTask: request.UploadTask
  aboutToAppear() {
    var context = featureAbility.getContext();
    context.getCacheDir().then((data) => {
      console.info("xx ======================>getCacheDirPromsie====================>");
      console.info("xx ====>data====>" + JSON.stringify(data));
      let fd = fileio.openSync(data + '/test.txt', 0o102, 0o666);
      fileio.writeSync(fd, 'upload file test by army');
      fileio.closeSync(fd);
    });
  }
  aboutToDisappear() {
    this.uploadTask.off("progress")
  }
  uploadFile() {
    let uploadConfig = {
      url: 'http://111.114.238.134:8740/file/upload',
      header: { key1: 'Content-Type', key2: 'multipart/form-data' },
      method: 'POST',
      files: [
        { filename: 'test.txt', name: 'test', uri: 'internal://cache/test.txt', type: 'txt' }
      ],
      data: [
        { name: 'fileId', value: 'FP000008' }
      ]
    }
    try {
      this.btnLabel = '文件上传中...'
      request.upload(uploadConfig)
        .then((data) => {
          this.btnLabel = '文件上传成功'
          this.uploadTask = data
          this.uploadTask.on("progress", (uploadedSize, totalSize) => {
            console.info('xx 上传进度值是:' + uploadedSize + ', 总大小:' + totalSize)
          })
        }).catch((err) => {
          this.btnLabel = '文件上传失败'
          console.error('xx Failed to request the upload. Cause: ' + JSON.stringify(err));
        })
    } catch (err) {
      this.btnLabel = '文件上传失败'
      console.error(`xx Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
    }
  }
  build() {
    Column({space: 30}) {
      Text('上传文件实例:')
        .width('100%')
        .height(50)
        .fontSize(24)
        .textAlign(TextAlign.Center)
      Button('提交文件')
        .onClick(() => {
          this.uploadFile()
        })
        .width('80%')
        .height(50)
        .fontSize(24)
    }.width('100%').height('100%')
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
  }
}

ArkTS(3.1)

  1. 此版本使用的是Stage模式、配置文件名是module.json5 由于文件上传需要网络,需要添加权限:ohos.permission.INTERNET,在3.1不用配置,就支持http和https,当前上传应用文件功能,仅支持上传应用缓存文件路径(cacheDir)下的文件。所以module.json5要添加的内容以下:
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}
  1. 文件上传页面就一个index.ets文件,里面包含UI和调用后端接口,代码如下:
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import request from '@ohos.request';
import hash from '@ohos.file.hash';

let context = getContext(this) as common.UIAbilityContext;

@Entry
@Component
struct Index {
  @State btnLabel: string = '提交文件'
  private uploadTask: request.UploadTask
  aboutToAppear() {
    let cacheDir = context.cacheDir;
    let file = fs.openSync(cacheDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    fs.writeSync(file.fd, 'upload file test by API9');
    fs.closeSync(file);
  }
  aboutToDisappear() {
    this.uploadTask.off("complete")
  }
  uploadFile() {
    let uploadConfig = {
      url: 'http://111.114.238.134:8740/file/upload',
      header: { key1: 'Content-Type', key2: 'multipart/form-data' },
      method: 'POST',
      files: [
        { filename: 'test.txt', name: 'test', uri: 'internal://cache/test.txt', type: 'txt' }
      ],
      data: [
        { name: 'fileId', value: 'FP000008' }
      ]
    }
    try {
      this.btnLabel = '文件上传中...'
      request.uploadFile(context, uploadConfig)
        .then((data) => {
          this.btnLabel = '文件上传成功'
          this.uploadTask = data
          this.uploadTask.on('complete', (taskStates) => {
            for (let i = 0; i < taskStates.length; i++) {
              console.info(`xx upload complete taskState: ${JSON.stringify(taskStates[i])}`);
            }
          });
        })
        .catch((err) => {
          this.btnLabel = '文件上传失败'
          console.error(`xx Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
        })
    } catch (err) {
      this.btnLabel = '文件上传失败'
      console.error(`xx Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
    }
  }
  build() {
    Column({space:30}) {
      Text('上传文件实例:')
        .width('100%')
        .height(50)
        .fontSize(24)
        .textAlign(TextAlign.Center)
      Button(this.btnLabel)
        .onClick(() => {
          this.uploadFile()
        })
        .width('80%')
        .height(50)
        .fontSize(24)
    }
    .width('100%').height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

SpringBoot和Thymeleaf

  1. 后端首先列出pom.xml文件,里面包含项目依赖jar配置,比如web、thymeleaf依赖,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.army</groupId>
    <artifactId>file-manage</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>file-manage</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  1. 前端调用接口文件Controller代码如下:
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {
    @Autowired
    FileService fileService;

    @PostMapping("/upload")
    public StandardResponse upload(String fileId, MultipartHttpServletRequest multiPartRequest) {
        log.info("**Upload File Controller!");
        FileCriteria criteria = new FileCriteria();
        criteria.setFileId(fileId);
        try {
            Iterator<String> itr = multiPartRequest.getFileNames();
            MultipartFile mpf = null;
            while(itr.hasNext()){
                mpf = multiPartRequest.getFile(itr.next());
                break;
            }
            byte[] fileByteArr = null;
            if (null != mpf && !mpf.isEmpty()) {
                String originalFileName = mpf.getOriginalFilename();
                log.info(originalFileName);
                criteria.setFileName("");
                String fileExtension = FilenameUtils.getExtension(originalFileName);
                criteria.setFileExtension(fileExtension);
                fileByteArr = mpf.getBytes();
                criteria.setFileByteArray(fileByteArr);
                criteria.setFileName(originalFileName);
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
        return fileService.uploadFile(criteria);
    }
}
  1. 后端业务逻辑代码,也就是文件上传处理逻辑Service代码如下:

3.1 业务接口:

public interface FileService {
    StandardResponse uploadFile(FileCriteria criteria);
    String saveFile(FileCriteria criteria);
}

3.2 业务实现类:

@Service
@Slf4j
public class FileServiceImpl implements FileService {
    @Value("${project.root.path}")
    private String rootPath = "rootPath";
    @Value("${project.baseUrl}")
    private String baseUrl;

    @Override
    public StandardResponse uploadFile(FileCriteria criteria) {
        String filePath = this.saveFile(criteria);
        String imgPath = baseUrl + "filePath/" + filePath;
        StandardResponse standardResponse = new StandardResponse();
        standardResponse.setSuccess(true);
        standardResponse.setStatusCode("100");
        standardResponse.setStatusDesc("上传成功");
        standardResponse.setData(imgPath);
        return standardResponse;
    }

    @Override
    public String saveFile(FileCriteria criteria) {
        log.info("上传文件开始!");
        String pictureId = IdUtils.getId("FP");
        String fileName = pictureId + "." + criteria.getFileExtension();
        criteria.setFileName(fileName);
        String filePath = sourceFile(criteria);
        log.info("File Path: " + filePath);
        log.info("上传文件结束!");
        return filePath;
    }

    private String sourceFile(FileCriteria criteria) {
        byte[] attachmentFileByteArray = criteria.getFileByteArray();
        if (null != attachmentFileByteArray) {
            log.info("1.1.创建根目录.");
            String basePath = rootPath + this.genDatePath();
            File basePathFolder = new File(basePath);
            if (!basePathFolder.exists()) basePathFolder.mkdirs();
            log.info("根目录: " + basePath);
            File file = new File(basePath + File.separator + criteria.getFileName());
            log.info("1.2.保存源文件 - 绝对路径: " + file.getAbsolutePath());
            try {
                FileCopyUtils.copy(attachmentFileByteArray, file);
                log.info("1.3.1.保存源文件 - 保存成功 !!!");
                String relativePath = this.genDatePath() + File.separator + criteria.getFileName();
                return relativePath;
            } catch (IOException e) {
                log.info("1.3.2.保存源文件 - 保存失败 !!!");
                file.deleteOnExit();
                return "";
            }
        }
        return "";
    }

    private String genDatePath(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String yyyyMMdd = sdf.format(new Date());
        return yyyyMMdd;
    }
}
  1. 配置文件
server:
  port: 8740
project:
  root:
    path: /var/tomcat/file-manage/filePath/
  baseUrl: http://111.114.238.134:8740/
  1. 访问域名或IP加端口访问到Thymeleaf页面,要添加一个Controller跳转
@Controller
public class IndexController {
    @GetMapping("/")
    public String Index() {
        return "index";
    }
}
  1. 在templates目录下创建index.htm页面文件,这里的index名要和上面Controller返回“index”名一致,才能跳转过去,index.html代码如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
<p>单文件上传</p>
<form method="post" action="/file/upload" enctype="multipart/form-data">
    <input type="text" name="fileId" value="FP00001">
    <p><input type="file" name="file00"></p>
    <input type="submit" value="提交">
</form>
</body>
</html>

总结

通过此贴学习到文件上传3.0与3.1的不同处,同时也学习到了后端开发流程,其实写这个贴子之前,是一个小伙伴问到我关于文件上传问题,由于之前我写的实例里,也没有用到文件上传功能,于是我就用最新API9也就是Stage模式写了一个Demo给他参考,然后他通过参考我的Demo,学会了,可惜他现在开发的项目是用API8的,由于开发模式不一样,他遇到了问题,于是我在用API8写了一个Demo给他参考,最后他的项目也实现了文件上传,我想想还是把这个文件上传Demo分享出来,让以后遇到问题的小伙伴也可以参考一下,最后我会把源码分享出来,有兴趣的小伙伴,也可以同步下来学习。

源码


更多关于HarmonyOS 鸿蒙Next ArkTS 前端和SpringBoot后端文件上传示例(Request.upload)的实战教程也可以访问 https://www.itying.com/category-93-b0.html

18 回复
楼主你好,我在arkts上传文件给后端,但是后端没有收到这个post请求,请问这是什么原因啊?

这是我的上传文件的代码

这是我的后端调用的controller接口

更多关于HarmonyOS 鸿蒙Next ArkTS 前端和SpringBoot后端文件上传示例(Request.upload)的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


你修改为后端IP地址,你写localhost不行的,

我在本地跑了后端代码也不可以吗,

我在postman测试是可以上传成功的,

楼主你好,请问我使用api9,选择批量文件后,复制到沙箱时,如何获取源文件姓名与类型?

如果直接定义名称不会出现文件写入失败吗?

比如我可能选择的是mp4格式文件,结果复制时写入jpg?

let PhotoSelectOptions = new picker.PhotoSelectOptions(); PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; PhotoSelectOptions.maxSelectNumber = 5; let photoPicker = new picker.PhotoViewPicker(); photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult) => { this.img = PhotoSelectResult.photoUris[0]; //datashare:///media/image/98 格式 … 而request.uploadFile 需要 dataability格式


我该如何将datashare 变成图片上传所需要的dataability文件格式?或者我该如何从相册选取后直接返回dataability存储位置照片

选择图库返回的uri,你可以用fs打开文件,然后复制到缓存目录就可以了,

我如何将选取的datashare文件 经怎样的转换才能变成dataability 并作为参数传递给request.uploadFile方法

uri: ‘internal://cache/test.txt’


let PhotoSelectOptions = new picker.PhotoSelectOptions();
PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
PhotoSelectOptions.maxSelectNumber = 5;
let photoPicker = new picker.PhotoViewPicker();
photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult) => {
  this.img = PhotoSelectResult.photoUris[0]; //datashare:///media/image/98  格式..................而request.uploadFile 需要 dataability格式

我该如何将datashare 变成图片上传所需要的dataability文件格式?或者我该如何从相册选取后直接返回dataability存储位置照片

你好,请问上传成功后,想获取服务器的返回值怎么获取?

从目前API来看,返回信息没能包含服务器的,所以在提交成功后,要显示出来,就从缓存获取,下次打开时,再到服务器获取。

学习了

楼主试过上传相册里面的图片吗?

还没有,你有这个业务需求?

我昨晚写了一篇上传相册图片到服务器的帖子,你可以去看看。

在HarmonyOS鸿蒙系统中,使用ArkTS进行前端开发并与SpringBoot后端进行文件上传,可以通过以下方式实现。这里我们重点讨论ArkTS前端部分以及SpringBoot后端处理文件上传的接口。

ArkTS前端部分:

  1. 创建一个上传按钮或表单,用于选择并上传文件。
  2. 使用ArkTS的网络请求能力,构建一个POST请求,将文件数据发送到SpringBoot后端。
  3. 在请求中,使用multipart/form-data编码类型,确保文件能够正确传输。

示例代码(伪代码,具体实现需根据ArkTS API调整):

// 假设有一个文件选择器
let fileInput = document.getElementById('fileInput');

// 监听文件选择事件
fileInput.addEventListener('change', async (event) => {
    let file = event.target.files[0];
    let formData = new FormData();
    formData.append('file', file);

    // 发送POST请求到SpringBoot后端
    let response = await fetch('http://your-springboot-backend/upload', {
        method: 'POST',
        body: formData,
        headers: {
            'Content-Type': 'multipart/form-data'
        }
    });

    // 处理响应
    console.log(await response.json());
});

SpringBoot后端部分:

  1. 创建一个控制器,定义文件上传的接口。
  2. 使用@RequestParam("file") MultipartFile file接收上传的文件。
  3. 处理文件保存逻辑。

如果问题依旧没法解决请联系官网客服,官网地址是 https://www.itying.com/category-93-b0.html

回到顶部