tio-boot 案例 - 文件上传和下载

添加依赖

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <java.version>1.8</java.version>
  <maven.compiler.source>${java.version}</maven.compiler.source>
  <maven.compiler.target>${java.version}</maven.compiler.target>
  <tio-boot.version>1.5.2</tio-boot.version>
  <lombok-version>1.18.30</lombok-version>
  <main.class>com.litongjava.tio.boot.sqllite.App</main.class>
</properties>
<dependencies>

  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>

  <!--https://central.sonatype.com/artifact/com.litongjava/ApiTable-->
  <dependency>
    <groupId>com.litongjava</groupId>
    <artifactId>ApiTable</artifactId>
    <version>1.2.7</version>
  </dependency>
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.23</version>
  </dependency>

  <!-- sqlite-jdbc -->
  <dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>3.7.2</version>
  </dependency>

  <dependency>
    <groupId>com.litongjava</groupId>
    <artifactId>tio-boot</artifactId>
    <version>${tio-boot.version}</version>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok-version}</version>
    <optional>true</optional>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>com.litongjava</groupId>
    <artifactId>hotswap-classloader</artifactId>
    <version>1.2.2</version>
  </dependency>
</dependencies>
<profiles>
  <!-- 开发环境 -->
  <profile>
    <id>development</id>
    <activation>
      <activeByDefault>true</activeByDefault>
    </activation>
  </profile>

  <!-- 生产环境 -->
  <profile>
    <id>production</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>2.7.4</version>
          <configuration>
            <mainClass>${main.class}</mainClass>
            <excludeGroupIds>org.projectlombok</excludeGroupIds>
          </configuration>
          <!-- 设置执行目标 -->
          <executions>
            <execution>
              <goals>
                <goal>repackage</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

数据表

创建一张数据表,保持上传的数据

CREATE TABLE "sys_upload_file" (
  "id" text NOT NULL,
  "saveFolder" TEXT NOT NULL,
  "suffixName" TEXT NOT NULL,
  "filename" TEXT NOT NULL,
  PRIMARY KEY ("id")
);

配置文件

app.properties

#jdbc-sqlliste
jdbc.driverClass=org.sqlite.JDBC
jdbc.url=jdbc:sqlite:D:/sqllite/student.db
jdbc.user=
jdbc.pswd=
jdbc.showSql=true
jdbc.validationQuery=select 1

启动类

package com.litongjava.tio.boo.demo.file;

import com.litongjava.hotswap.wrapper.tio.boot.TioApplicationWrapper;
import com.litongjava.jfinal.aop.annotation.AComponentScan;

@AComponentScan
public class TioBootFileApp {
  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    TioApplicationWrapper.run(TioBootFileApp.class, args);
    long end = System.currentTimeMillis();
    System.out.println((end - start) + "ms");
  }
}

配置类

package com.litongjava.tio.boo.demo.file.config;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSource;
import com.jfinal.template.Engine;
import com.jfinal.template.source.ClassPathSourceFactory;
import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.litongjava.jfinal.plugin.activerecord.OrderedFieldContainerFactory;
import com.litongjava.jfinal.plugin.activerecord.dialect.Sqlite3Dialect;
import com.litongjava.jfinal.plugin.hikaricp.DsContainer;
import com.litongjava.tio.boot.constatns.ConfigKeys;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.utils.environment.EnvUtils;

@AConfiguration
public class DbConfig {

  /**
   * create datasource
   * @return
   */
  @AInitialization(priority = 10)
  public DataSource dataSource() {
    // get parameter from config file
    String jdbcUrl = EnvUtils.get("jdbc.url");
    String jdbcUser = EnvUtils.get("jdbc.user");
    String jdbcPswd = EnvUtils.get("jdbc.pswd");
    String jdbcValidationQuery = EnvUtils.get("jdbc.validationQuery");

    // create datasource
    DruidDataSource druidDataSource = new DruidDataSource();

    // set basic parameter
    druidDataSource.setUrl(jdbcUrl);
    druidDataSource.setUsername(jdbcUser);
    druidDataSource.setPassword(jdbcPswd);
    druidDataSource.setValidationQuery(jdbcValidationQuery);
    // save datasource
    DsContainer.setDataSource(druidDataSource);
    // close datasource while server close
    TioBootServer.me().addDestroyMethod(druidDataSource::close);
    return druidDataSource;
  }

  /**
   * create ActiveRecordPlugin
   * @return
   * @throws Exception
   */
  @AInitialization
  public ActiveRecordPlugin activeRecordPlugin() throws Exception {
    // get datasource from DsContainer
    DataSource dataSource = DsContainer.ds;

    // get parameter from config file
    Boolean tioDevMode = EnvUtils.getBoolean(ConfigKeys.TIO_DEV_MODE, false);
    boolean jdbcShowSql = EnvUtils.getBoolean("jdbc.showSql", false);
    // cretae plugin
    ActiveRecordPlugin arp = new ActiveRecordPlugin(dataSource);
    // set parameter
    arp.setDialect(new Sqlite3Dialect());
    arp.setContainerFactory(new OrderedFieldContainerFactory());
    arp.setShowSql(jdbcShowSql);

    if (tioDevMode) {
      arp.setDevMode(true);
    }

    // config engine
    Engine engine = arp.getEngine();
    engine.setSourceFactory(new ClassPathSourceFactory());
    engine.setCompressorOn(' ');
    engine.setCompressorOn('\n');
    // arp.addSqlTemplate("/sql/all_sqls.sql");
    // start plugin
    arp.start();
    // close plugin while server close
    TioBootServer.me().addDestroyMethod(arp::stop);
    return arp;
  }
}

实体类

package com.litongjava.tio.boo.demo.file.model;

import java.io.File;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class FileSaveResult {
  private String id;
  private String saveFolder;
  private String suffixName;
  private String filename;
  private File file;
}

常量类

  • UPlOAD_FOLDER_NAME 保持文件上传路径
package com.litongjava.tio.boo.demo.file.constants;

public interface AppConstants {
  String UPlOAD_FOLDER_NAME = "upload";
}

service

package com.litongjava.tio.boo.demo.file.services;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

import com.litongjava.data.services.DbJsonService;
import com.litongjava.jfinal.aop.annotation.AAutowired;
import com.litongjava.jfinal.plugin.activerecord.Db;
import com.litongjava.jfinal.plugin.activerecord.Record;
import com.litongjava.tio.boo.demo.file.constants.AppConstants;
import com.litongjava.tio.boo.demo.file.model.FileSaveResult;
import com.litongjava.tio.utils.hutool.FileUtil;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FileService {
  @AAutowired
  private DbJsonService dbJsonService;

  public FileSaveResult save(String filename, byte[] fileData) {
    String newFilename = UUID.randomUUID().toString();

    FileSaveResult fileSaveResult = new FileSaveResult();
    fileSaveResult.setId(newFilename);
    fileSaveResult.setFilename(filename);

    int lastIndexOf = filename.lastIndexOf(".");
    if (lastIndexOf > 0) {
      String substring = filename.substring(lastIndexOf);
      fileSaveResult.setSuffixName(substring);
      newFilename += substring;
    }
    // create dirs
    File uploadFolderFile = new File(AppConstants.UPlOAD_FOLDER_NAME);
    if (!uploadFolderFile.exists()) {
      uploadFolderFile.mkdirs();
      log.info("crate upload dir:{}", uploadFolderFile.getAbsolutePath());
    }

    newFilename = AppConstants.UPlOAD_FOLDER_NAME + "/" + newFilename;
    fileSaveResult.setSaveFolder(AppConstants.UPlOAD_FOLDER_NAME);
    log.info("newFilename:{}", newFilename);

    // save to folder
    File file = new File(newFilename);
    try {
      FileUtil.writeBytes(fileData, file);
      fileSaveResult.setFile(file);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    // save to db
    // vresion 1
    Record record = new Record();
    record.set("id", fileSaveResult.getId());
    record.set("saveFolder", fileSaveResult.getSaveFolder());
    record.set("suffixName", fileSaveResult.getSuffixName());
    record.set("filename", filename);
    // version 2 test fail
    // Record record = Record.fromBean(fileSaveResult);
    boolean save = Db.save("sys_upload_file", record);
    if (save) {
      return fileSaveResult;
    } else {
      log.error("save file error:{}", filename);
    }
    return fileSaveResult;
  }

  public File getUploadFile(FileSaveResult result) {
    String filename = result.getSaveFolder() + "/" + result.getId() + result.getSuffixName();
    return new File(filename);
  }

  public File getUploadFile(String id) {
    Record record = Db.findById("sys_upload_file", id);
    FileSaveResult fileSaveResult = record.toBean(FileSaveResult.class);
    log.info("select from db:{}",fileSaveResult);
    return getUploadFile(fileSaveResult);
  }
}

controller

package com.litongjava.tio.boo.demo.file.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import com.litongjava.jfinal.aop.annotation.AAutowired;
import com.litongjava.tio.boo.demo.file.model.FileSaveResult;
import com.litongjava.tio.boo.demo.file.services.FileService;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.common.UploadFile;
import com.litongjava.tio.http.server.annotation.RequestPath;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.resp.RespVo;

import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;

@RequestPath("/file")
@Slf4j
public class FileController {

  @AAutowired
  private FileService fileService;

  public String index() {
    return "index";
  }

  /**
   * upload file
   * @param file
   * @return
   */
  public RespVo upload(UploadFile file) {
    if (file != null) {
      String name = file.getName();
      byte[] fileData = file.getData();
      // save file
      FileSaveResult fileSaveResult = fileService.save(name, fileData);
      fileSaveResult.setFile(null);

      log.info("save file finished");
      return RespVo.ok(fileSaveResult);

    }
    return RespVo.fail("fail").code(-1);
  }

  @RequestPath("/download/{id}")
  public HttpResponse download(HttpRequest request, String id) {
    log.info("id:{}", id);
    File file = fileService.getUploadFile(id);
    int available;
    try {
      @Cleanup
      InputStream inputStream = new FileInputStream(file);
      available = inputStream.available();
      byte[] fileBytes = new byte[available];
      inputStream.read(fileBytes, 0, available);
      String contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=utf-8";
      HttpResponse response = Resps.bytesWithContentType(request, fileBytes, contentType);
      return response;
    } catch (IOException e) {
      e.printStackTrace();
      return Resps.json(request, RespVo.fail("Error generating captcha"));
    }
  }
}

上传文件 请求示例

curl --location --request POST 'http://localhost/file/upload' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--form 'file=@"test-chinese.docx"'

响应示例

{
  "data": {
    "file": null,
    "docId": "6632a4ba-ae03-4f05-adce-de6acdd5c560",
    "saveFolder": "upload",
    "suffixName": ".docx",
    "filename": "test-chinese.docx"
  },
  "ok": true,
  "code": null,
  "msg": null
}
  • 下载文件 http://localhost/file/download/6632a4ba-ae03-4f05-adce-de6acdd5c560

RequestHanlder 版本

tio-boot 的 controller 因为使用了反射,性能会低一些.如果想要提高性能,推荐使用 tio-boot request handler,使用方式如下

添加 Handler

package com.litongjava.file.server.requesthandler;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import com.litongjava.file.server.model.FileSaveResult;
import com.litongjava.file.server.services.FileService;
import com.litongjava.jfinal.aop.annotation.AAutowired;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.common.UploadFile;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.resp.RespVo;

import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FileRequestHanlder {
  @AAutowired
  private FileService fileService;

  public HttpResponse upload(HttpRequest request) {
    UploadFile file = request.getUploadFile("file");
    RespVo respVo;
    if (file != null) {
      String name = file.getName();
      byte[] fileData = file.getData();
      log.info("upload file size:{}", fileData.length);
      // save file
      FileSaveResult fileSaveResult = fileService.save(name, fileData);
      fileSaveResult.setFile(null);

      log.info("save file finished");
      respVo = RespVo.ok(fileSaveResult);

    } else {
      respVo = RespVo.fail("fail").code(-1);
    }

    return Resps.json(request, respVo);
  }

  public HttpResponse download(HttpRequest request) {
    String id = request.getParam("id");
    log.info("id:{}", id);
    File file = fileService.getUploadFile(id);
    int available;
    try {
      @Cleanup
      InputStream inputStream = new FileInputStream(file);
      available = inputStream.available();
      byte[] fileBytes = new byte[available];
      inputStream.read(fileBytes, 0, available);
      // String contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=utf-8";
      String contentType = "";
      HttpResponse response = Resps.bytesWithContentType(request, fileBytes, contentType);
      return response;
    } catch (IOException e) {
      e.printStackTrace();
      return Resps.json(request, RespVo.fail("Error generating captcha"));
    }
  }
}

使用配置类配置 RequestHanlder

package com.litongjava.file.server.config;

import com.litongjava.file.server.requesthandler.FileRequestHanlder;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.jfinal.aop.annotation.BeforeStartConfiguration;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.http.server.handler.SimpleHttpRoutes;

@BeforeStartConfiguration
public class HttpServerRequestHanlderConfig {

  @AInitialization
  public void httpRoutes() {

    // 创建simpleHttpRoutes
    SimpleHttpRoutes simpleHttpRoutes = new SimpleHttpRoutes();
    // 创建controller
    FileRequestHanlder asrSubmitRequestHanlder = Aop.get(FileRequestHanlder.class);

    // 添加action
    simpleHttpRoutes.add("/upload", asrSubmitRequestHanlder::upload);
    simpleHttpRoutes.add("/download", asrSubmitRequestHanlder::download);

    // 将simpleHttpRoutes添加到TioBootServer
    TioBootServer.me().setHttpRoutes(simpleHttpRoutes);
  }

}
  • 开源地址 https://github.com/litongjava/tio-boot-file-server