React+eggjs+mysql实现多文件上传

news2024/12/26 23:40:37

文章目录

  • 一、开发环境
  • 二、数据库准备files表【视项目需求不同可略过,一般小项目可忽略】
    • 1.数据库准备files表的目的
    • 2.表结构
  • 三、后端接口准备
    • 1.配置config
    • 2.router.ts中配置路由
    • 3.创建controller
    • 4.创建service
      • ①为什么做这步
      • ②创建service/common文件
      • ③service/common.ts文件内容
  • 四、前端调接口上传图片
  • 五、前后端联调

一、开发环境

  1. 前端 React.js + Antd。你用其他的vue什么的或者其他的UI组件库都是一样的。
  2. 后端 Eggjs
  3. 数据库 MySQL

二、数据库准备files表【视项目需求不同可略过,一般小项目可忽略】

1.数据库准备files表的目的

有同学可能会有疑问,为啥会涉及到新建一个表?

因为我们需要把file的原始信息存储起来,比如file name、mime type、服务器上的url等。【可能后续产品会要求展示图片的原始信息,上传人等等】

2.表结构

在这里插入图片描述

PS:可根据自己项目需要自行拓展字段

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for files
-- ----------------------------
DROP TABLE IF EXISTS `files`;
CREATE TABLE `files` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `file_name` varchar(255) DEFAULT NULL COMMENT '文件名',
  `mime_type` varchar(20) DEFAULT NULL COMMENT '文件类型',
  `url` varchar(255) NOT NULL COMMENT '文件的服务器地址',
  `upload_user_id` bigint(20) DEFAULT NULL COMMENT '上传人id',
  `create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '上传时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

SET FOREIGN_KEY_CHECKS = 1;

三、后端接口准备

PS:依赖的安装包笔者这里没写,笔者是直接生成的项目,都是自带的,如果你报错缺哪个包就安哪个包即可。

1.配置config

找到 config/config.default.ts 文件追加如下配置:

// 上传文件相关
uploadImgDir: 'app/public/img',
multipart: {
  mode: 'file',
  fileSize: 1048576000,
  whitelist: ['.txt', '.png', '.jpg', '.jpeg', '.webp'], // 配置允许哪些文件上传
},
static: {
  prefix: '/app/public', //访问前缀
  dir: path.join(appInfo.baseDir, 'app/public'),
  dynamic: true,
  preload: false,
  maxAge: 31536000,
  buffer: true,
},
// END-上传文件相关

在这里插入图片描述

2.router.ts中配置路由

在这里插入图片描述

3.创建controller

PS: 注意这里controller文件的命名是和上面router.ts中配置的第二个参数是关联的

由于我们上面注册的是controller.common.upload
那么我们就在app/controller 目录下新建common.ts文件。里面追加一个upload接口。
在这里插入图片描述

common.ts全部代码:

import { Controller } from 'egg';
const fs = require('fs');
const moment = require('moment');
const mkdirp = require('mkdirp');
const path = require('path');

module.exports = app => {
  /**
   * @Controller 公共接口
   */
  return class CommonController extends Controller {
    /**
      * @summary 上传图片
      * @description 公共的上传图片的接口
      * @router post /api/common/upload
      * @request header string token
      * @request formData file *file
      * @response 200 uploadResponse 上传成功
      */
    public async upload(ctx) {
      // 需要前往 config/config.default.js 设置 config.multipart 的 mode 属性为 file
      const files = ctx.request.files;
      // 存放最终资源的路径集合
      const uploadDirs: string[] = [];
      // 存放最终的id集合,判断是否上传成功的依据
      const ids: number[] = [];
      // 将所有的文件都放到params里面,然后一起存数据库
      const params: any[] = [];

      // 1.获取当前日期-用于创建目录
      const day = moment(new Date()).format('YYYYMMDD');
      // 2.创建图片保存的路径
      const dir = path.join(this.config.uploadImgDir, day);
      await mkdirp(dir); // 不存在就创建目录
      // 从token中获取操作人的id - 你如果不需要存upload_user_id那么就忽略这步
      const userInfo = await app.jwt.verify(ctx?.headers?.token || '', app.config.jwt.secret);
      try {
        files.forEach(file => {
          const fileContent = fs.readFileSync(file.filepath);
          // 生成图片最终在服务器的名称以及返回在服务器上的地址
          const uploadDir = path.join(dir, Date.now() + path.extname(file.filename));
          uploadDirs.push(uploadDir);
          params.push({
            url: uploadDir,
            filename: file.filename,
            mimeType: file.mimeType,
            uploadUserId: userInfo.id,
          });
          // 写入文件夹
          fs.writeFileSync(uploadDir, fileContent);
        });
        // 存到数据库
        const res = await ctx.service.common.upload(params);
        ids.push(res?.insertId);
        if (ids.every(ite => !ite) || ids.length === 0) {
          ctx.failure('上传失败', { ids });
        } else {
          ctx.success('上传成功', { ids, uploadDirs });
        }
      } catch (e) {
        console.log('error', e);
        // 清除临时文件
        ctx.cleanupRequestFiles();
      }
    }
  };
};

4.创建service

①为什么做这步

因为我们在上一步的controller里面是把数据的操作放到了service里面进行处理的,所以我们在创建一下service文件然后写存数据库的sql。
在这里插入图片描述

②创建service/common文件

在这里插入图片描述

③service/common.ts文件内容

import { Service } from 'egg';

type UploadParams = {
  url: string; // 图片的服务器地址 url
  filename: string; // 图片的原始name file_name
  mimeType: string; // 图片的类型 mime_ype
  uploadUserId: number; // 上传人id upload_user_id
};
/**
 * Common Service
 */
export default class User extends Service {
  /**
   * 上传文件
   */
  public async upload(uploadParams: UploadParams[]) {
    try {
      let sqlValue = '';
      uploadParams.forEach((param, index) => {
        if (index !== 0) {
          sqlValue += ',';
        }
        sqlValue += `('${param.url}', '${param.filename}', '${param.mimeType}', ${param.uploadUserId})`;
      });
      const sql = `INSERT INTO files(url, file_name, mime_type, upload_user_id) VALUES${sqlValue}`;
      const insertFileres = await this.app.mysql.query(sql);
      return insertFileres;
    } catch (err) {
      console.log('存文件到数据库失败:', err);
    }
  }
}

至此服务端的任务都完成了,就差写前端组件调接口联调了。

四、前端调接口上传图片

PS: 前端笔者这里就把大部分代码都省略了,如果后续真的很多朋友需要的话笔者在补充上来。笔者只会在下面写调接口的关键代码。

笔者用的是antd的Upload组件,然后用提供的api:customRequest 调用接口写后续的逻辑。

antd-upload组件的API链接:https://ant.design/components/upload-cn#api

// 前端主要是把文件放到formData里面
// 然后options配置'Content-type': 'multipart/form-data'
// 这样传给后端就OK了
// 这个customRequest是放到Upload组件里面的一个props哈
// 如果你不是用的antd的Upload组件你只需要把后面的function留着用即可
customRequest: async ({ file }) => {
  let param = new FormData();
  param.append('files', file) // 通过append向form对象添加数据 

  const res = await upload(param, {
    'Content-type': 'multipart/form-data'
  });
  console.log('res: ', res);
}


/** 上传文件接口 POST /api/common/upload */
function upload(data, options) {
  return request(`${你自己项目后端的IP地址}/api/common/upload`, {
    method: 'POST',
    data,
    ...(options || {}),
  });
}

五、前后端联调

前端上传图片调接口,发现有成功的返回,并且我们后端接口抛回来的ids和uploadDirs都能收到就代表我们OK了,然后我们用返回回来的uploadDirs跟服务器的IP地址拼接一下能出来对应的图片,那就相当于完全OK了!!!


入参:
在这里插入图片描述


返回值:
在这里插入图片描述


然后我们根据返回回来的uploadDirs去尝试回显一下图片,在这个地址前面拼接上服务器的ip地址:
在这里插入图片描述
成功显示~


且返回回来的ids也是跟数据库对应上的,功能完成!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/187172.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IronWebScraper for .NET 2023.1 Crack

用于从 HTML Web 应用程序中提取干净的结构化数据的 C# 框架。 IronWebScraper for .NET 2023 :Adds support for Microsoft .NET 6 and .NET 7.January 27, 2023 - 17:25 New Version ::: Added support for Microsoft .NET 6 an…

【Java】SpringCloud使用

SpringCloud使用 发起远程调用 根据订单id查询订单的同时,把订单所属的用户信息一起返回,但订单信息和用户信息分属两个不同的模块。 本质上是订单模块向用户模块发起请求,在spring中使用resttemplate发起。 MapperScan("cn.itcast.o…

电脑配置怎么看?这3种方法就可以轻松查看

在我们的日常生活和工作中,我们越来越离不开计算机。对于一台好的电脑,首先,我们需要了解它的配置,以便更好地选择它。电脑配置怎么看?可能还有很多人不知道。别担心,本篇文章就是来教会你如何查看电脑的配…

Android studio集成flutter

1.获取Flutter SDK 视窗安装|扑动 (flutter.dev) 2.配置环境变量 3.Android Studio安装Flutter插件 4.打开管理窗口cmd,输入flutter doctor。 5.因为第二步是感叹号(如下图),需要run: flutter doctor --android-licenses&#xf…

基础课程7:多线程与Pad可获得性

目标 GStreamer自动处理多线程,但在某些情况下,您可能需要手动解耦线程。本教程展示了如何做到这一点,此外,还完成了关于Pad可用性的阐述。更准确地说,本文档解释了: 如何为管道的某些部分创建新的执行线程Pad的可用…

SpringCloud之消息总线

spring CloudBus 将分布式的节点和轻量的消息代理连接起来。这可以用于广播配置文件的更改或者其他的管理工作。一个关键的思想就是,消息总线可以为微服务做监控,也可以作为应用程序之间相互通讯。 一、准备工作 本文还是基于上一篇文章来实现。按照官…

MySQL运维(二)MySQL分库分表概念及实战、读取分离详解

MySQL运维(二)MySQL分库分表详解、读取分离详解 1、MySQL分库分表相关概念 1.1 分库分表概念 1.1.1 分库的原因 分库:就是一个数据库分成多个数据库,部署到不同机器。 如果业务量剧增,数据库可能会出现性能瓶颈,这时候我们就需…

盘点那些免费好用的高清录屏软件,7款宝藏软件(2023年新版)

有不少的小伙伴私信小编,希望小编能够分享一些好用的高清录屏软件。那么今天,小编就给大家盘点一下那些免费好用的高清录屏软件吧!这些都是小编亲自体验过的,有需要的小伙伴赶紧码住收藏,这些宝藏软件错过就难找了&…

即时通讯开发之详解TCP/IP中的ICMP 协议、ping 和 Traceroute

前面讲到了,IP 协议并不是一个可靠的协议,它不保证数据被送达,那么,自然的,保证数据送达的工作应该由其他的模块来完 成。其中一个重要的模块就是 ICMP(网络控制报文)协议。当传送 IP 数据包发生错误--比如主机不可达,路由不可达等等,ICMP 协议将会把错误信息封包,然后传送回给…

拉伯证券|兔年资本市场三大重点

近来,承受《证券日报》采访的多位专家展望兔年资本商场,称深化推动股票发行注册制变革、稳步扩展准则型敞开以及防备化解严重危险是三大重点工作。 申万宏源证券首席经济学家杨生长对记者表明,本年是我国经济康复到常态性增加的要害一年&…

硬件系列(1)-电阻、电容、电感三大件

下面是目录电阻电阻参数(1)**SIZE 尺寸**(2)**TOLERANCE 误差**(3)PACKAGING TYPE 外包装(4)TEMPERATURE COEFFICIENT OF RESISTANCE 温度(5)**TAPING REEL & POWER 功率**(6)**RESISTANCE VALUE 电阻值**三位的四位的查表的(7) DEFAULT CODE 缺省编码电阻的分类碳膜电阻金…

leetcode63 不同路径二

题目描述 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。 现在考虑网格中有障碍物。那么从左上…

Elasticsearch7.3.2通俗易懂

文章目录一、安装1.步骤2.报错(1) can not run elasticsearch as root(2) could not find java in JAVA_HOME or bundled at ...(3) Error: Could not find or load main class XXX.JavaVersionChecker(4)BindTransportException[Failed to bind to [9300-9400]](5)max virtual…

如何在高压系统中实现电源和信号线的电气隔离

介绍 在一系列高压应用中存在电源和信号线的情况下,需要为设备和用户提供强大的保护,包括工厂自动化和电机驱动器等工业 4.0 系统。这延伸到汽车和电动汽车 (EV)、医疗系统、测试和测量应用以及光伏系统和电网基础设施等绿色能源…

ChatGPT API调用python和脚本实现

Chat GPT 由于其独特、近乎准确且类似人类的响应,如今在互联网上引起了过多的讨论。本文讨论如何通过 Python 代码连接到 Chat GPT API。 如果需要用website访问chatGPT, 请参考保姆级教程 火爆全球的网红OpenAI ChatGPT注册教程 文章目录第 1 步&#x…

随笔记——线程池

文章目录1 概览2 核心点2.1 使用线程池的好处2.2 如何创建线程池2.3 线程池的参数2.4 如何处理任务流程?2.5 如何关闭线程池2.6 拒绝策略2.7 线程池满了,会怎样?1 概览 2 核心点 2.1 使用线程池的好处 降低资源消耗:通过重复利用…

Servlet程序创建步骤

1. 创建项目 使用 IDEA 创建一个 Maven 项目. 1) 菜单 -> 文件 -> 新建项目 -> Maven 2. 引入依赖 Maven 项目创建完毕后, 会自动生成一个 pom.xml 文件. 我们需要在 pom.xml 中引入 Servlet API 依赖的 jar 包. 1) 在 中央仓库 中搜索 "servlet", 一般…

android 系统安全内容总结

部分android系统安全内容网上已经存在,这里的android系统安全内容还是以经验总结为主,夹带不少引用,并形成个人的理解。 android安全内容学习需要一定基础,没接触安全的开发可以认识一下,接触过安全的可以对比安全上的理解。组建android系统安全讨论群进行维护更新android…

基于ssm高校科研成果管理系统 java ideamysql

(1)教师功能需求 教师进入系统可以查看个人中心、科研成果初审管理、科研成果终审管理、科研发布管理、留言板管理等操作。 (2)管理员功能需求 管理员登陆后,主要功能模块包括个人中心、教师管理、学院管理员管理、科…

linux Redis 搭建

命令下载:wget https://download.redis.io/releases/redis-6.2.6.tar.gz安装gcc:yum -y install gcc automake autoconf libtool make;进入src下make编译:make;make MALLOClibc(报错时执行)make install新建文件夹相关…