Sfoglia il codice sorgente

附件上传功能

john 8 mesi fa
parent
commit
e29722b1a3

+ 10 - 6
DB/cashbook.sql

@@ -4,9 +4,9 @@ CREATE DATABASE `cashbook` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb
 CREATE TABLE `book` (
   `id` int NOT NULL AUTO_INCREMENT,
   `book_name` varchar(200) NOT NULL,
-  `create_timr` timestamp NOT NULL,
+  `create_time` timestamp NOT NULL,
   `update_time` timestamp NOT NULL,
-  `auther` varchar(100) DEFAULT NULL COMMENT '账本创建者',
+  `author` varchar(100) DEFAULT NULL COMMENT '账本创建者',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='账本信息';
 
@@ -51,8 +51,12 @@ CREATE TABLE `authors` (
 -- cashbook.files definition
 CREATE TABLE `files` (
   `id` int NOT NULL AUTO_INCREMENT,
-  `url` varchar(300) NOT NULL,
-  `record_id` int NOT NULL,
-  `author_id` int NOT NULL,
+  `md5` varchar(300) NOT NULL,
+  `mimetype` varchar(300) NOT NULL,
+  `size` int NOT NULL,
+  `name` varchar(300) DEFAULT NULL,
+  `create_time` timestamp NOT NULL,
+  `update_time` timestamp NOT NULL,
+  `author` varchar(300) DEFAULT NULL COMMENT '账本创建者',
   PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

+ 1 - 0
node_expores/.gitignore

@@ -1 +1,2 @@
 node_modules
+base_files/*

+ 26 - 0
node_expores/Dockerfile

@@ -0,0 +1,26 @@
+# 使用 node 18 作为基础镜像
+FROM node:18
+
+# 设置工作目录
+WORKDIR /app
+
+# 安装 yarn@1
+RUN npm install -g yarn@1
+
+# 将 package.json 和 yarn.lock 拷贝到容器中
+COPY package.json yarn.lock ./
+
+# 安装依赖,并设置国内镜像
+RUN npm install --registry=https://registry.npmmirror.com
+
+# 拷贝项目的所有文件到容器中
+COPY . .
+
+# 对外暴露 8080 端口
+EXPOSE 3000
+
+# 设置 files 目录为一个卷,以便于数据持久化或文件共享
+VOLUME /app/base_files
+
+# 启动应用(根据您项目的启动命令修改)
+CMD ["yarn", "start"]

+ 4 - 0
node_expores/app.js

@@ -4,7 +4,11 @@ import books from "./router/books/index.js";
 import files from "./router/files/index.js";
 import record from "./router/record/index.js";
 import types from "./router/types/index.js";
+import fileUpload from 'express-fileupload'
 const app = express();
+// default options
+app.use(fileUpload());
+
 const port = 3000;
 
 app.get("/", (req, res) => {

+ 16 - 0
node_expores/db/base.js

@@ -0,0 +1,16 @@
+import mysql from "mysql2";
+import environment from "../environment/index.js";
+const connection = mysql.createConnection({
+  ...environment.dbInfo(),
+});
+connection.connect();
+// connection.query("SELECT * from book", (err, rows, fields) => {
+//   console.log(13, err);
+//   if (err) throw err;
+
+//   console.log("The solution is: ", rows);
+// });
+
+// connection.end();
+
+export default connection;

+ 54 - 0
node_expores/db/files.js

@@ -0,0 +1,54 @@
+import connection from './base.js';
+export async function files_insert({
+  name = '',
+  mimetype = '',
+  size = '',
+  md5 = '',
+  create_time = '',
+  update_time = '',
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO files (name, mimetype, size, md5, create_time, update_time)
+        VALUES (?, ?, ?, ?, ?, ?)
+      `;
+      const values = [name, mimetype, size, md5, create_time, update_time];
+      // 直接接收 execute 返回的内容
+      const result = await connection.execute(sql, values);
+      // console.log("Record inserted successfully:", result);
+      return resolve(result);
+    } catch (err) {
+      // console.error("Error inserting data:", err);
+      // throw err;
+      return resolve(false);
+    }
+  })
+}
+
+// 是否存在重复的数据
+export function ishaveFileBymd5(md5Str) {
+  return new Promise((resolve, reject) => {
+    connection.query(`SELECT * FROM files WHERE md5 = ?`, [md5Str], (err, rows) => {
+      if (err) {
+        // reject(err);
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(rows.length > 0); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}
+
+// 查询图片信息
+export function getFileBymd5(md5Str) {
+  return new Promise((resolve, reject) => {
+    connection.query(`SELECT * FROM files WHERE md5 = ?`, [md5Str], (err, rows) => {
+      if (err) {
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(rows[0]); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}

+ 1 - 16
node_expores/db/index.js

@@ -1,16 +1 @@
-import mysql from "mysql2";
-import environment from "../environment/index.js";
-const connection = mysql.createConnection({
-  ...environment.dbInfo(),
-});
-
-connection.connect();
-
-connection.query("SELECT * from book", (err, rows, fields) => {
-  console.log(13, err);
-  if (err) throw err;
-
-  console.log("The solution is: ", rows);
-});
-
-connection.end();
+export * from './files.js';

+ 9 - 0
node_expores/ecosystem.config.cjs

@@ -0,0 +1,9 @@
+module.exports = {
+  apps : [{
+    name   : "cashBook",
+    script: './app.js',
+    watch: true,
+    max_restarts: 20,
+    ignore_watch: [ "node_modules"]
+  }]
+};

+ 12 - 1
node_expores/package.json

@@ -5,6 +5,8 @@
   "type": "module",
   "main": "index.js",
   "scripts": {
+    "dev": "pm2 start ecosystem.config.cjs",
+    "start": "node app.js",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "repository": {
@@ -19,16 +21,25 @@
   },
   "homepage": "https://github.com/Johnhong9527/newCashBook#readme",
   "dependencies": {
+    "crypto": "^1.0.1",
+    "dayjs": "^1.11.13",
     "express": "^4.21.1",
+    "express-fileupload": "^1.5.1",
     "mysql": "^2.18.1",
-    "mysql2": "^3.11.4"
+    "mysql2": "^3.11.4",
+    "spark-md5": "^3.0.2"
   },
   "volta": {
     "node": "18.20.4"
   },
   "devDependencies": {
     "@types/express": "^5.0.0",
+    "@types/express-fileupload": "^1.5.1",
     "@types/mysql": "^2.15.26",
     "@types/node": "^22.9.0"
+  },
+  "imports": {
+    "#db": "./db/index.js",
+    "#utils": "./utils/index.js"
   }
 }

+ 0 - 0
node_expores/pm2.config.js


+ 76 - 5
node_expores/router/files/index.js

@@ -1,16 +1,87 @@
-// 添加账本
+// 附件
 import express from "express";
+import path from "path";
+import fs from "node:fs";
+import {files_insert, ishaveFileBymd5, getFileBymd5} from '#db';
+import { computeFileMD5 } from '#utils';
 
 const router = express.Router();
 
-// middleware that is specific to this router
+import dayjs from 'dayjs'
+
 router.use(function timeLog(req, res, next) {
   console.log("Time: ", Date.now());
   next();
 });
-// define the home page route
-router.get("/", function (req, res) {
-  res.send("Files home page");
+
+router.get("/:fileId", async function (req, res) {
+  const fileId = req.params.fileId; // 获取 fileId 参数
+  const fileRow = await getFileBymd5(fileId)
+
+  if(!fileRow) {
+     return res.status(404).send("文件查询失败");
+  }
+
+  const uploadPath = './base_files/' + fileId;
+  const filePath = path.resolve(uploadPath);
+
+  // 检查文件是否存在
+  if (!fs.existsSync(filePath)) {
+    return res.status(404).send("服务器中不存在该文件");
+  }
+
+  // 返回文件
+  res.setHeader("Content-Type", fileRow.mimetype);
+  res.sendFile(filePath);
+});
+
+// 上传附件
+router.put("/", async function (req, res) {
+  let sampleFile;
+  let uploadPath;
+
+  if (!req.files || Object.keys(req.files).length === 0) {
+    return res.status(400).send('No files were uploaded.');
+  }
+
+  sampleFile = req.files.sampleFile;
+
+  // const fileMd5 = await computeFileMD5(sampleFile);
+  const fileMd5 = sampleFile.md5;
+  // 如果存在重复的数据, 终止后续的操作
+  if(await ishaveFileBymd5(fileMd5)) {
+    res.send({
+      file_id: fileMd5
+    });
+    return
+  }
+
+  const fileInfo = {
+    name: sampleFile.name,
+    mimetype: sampleFile.mimetype,
+    size: sampleFile.size,
+    md5: sampleFile.md5,
+    create_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+    update_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  }
+  uploadPath = './base_files/' + sampleFile.md5;
+
+  // 、移动上传文件至指定目录
+  sampleFile.mv(uploadPath, async function(err) {
+    if (err) {
+      return res.status(500).send(err);
+    }
+    const isAdd = await files_insert(fileInfo)
+    if(!isAdd) {
+      res.status(500).send({
+        msg: '添加失败'
+      });
+      return
+    }
+    res.send({
+      file_id: fileMd5
+    });
+  });
 });
 // define the about route
 router.get("/about", function (req, res) {

+ 82 - 0
node_expores/utils/createMD5.js

@@ -0,0 +1,82 @@
+import crypto from 'crypto';
+import fs from 'node:fs';
+import SparkMD5 from 'spark-md5';
+
+export function createMD5(filePath) {
+  return new Promise((res, rej) => {
+    const hash = crypto.createHash('md5');
+    const rStream = fs.createReadStream(filePath);
+    rStream.on('data', (data) => {
+      hash.update(data);
+    });
+    rStream.on('end', () => {
+      res(hash.digest('hex'));
+    });
+  })
+}
+
+
+
+
+/**
+ * 作者:起一个可以中奖的名字
+ * 链接:https://juejin.cn/post/7142831050052665381
+ * 来源:稀土掘金
+ * 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
+ * 计算文件Md5
+ * 将文件分片逐步计算最终合并得出整个文件md5, 提升计算速度
+ * @param {*} file
+ */
+
+// export function computeFileMD5(file) {
+//   return new Promise((resolve, reject) => {
+//     let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
+//     let chunkSize = 2097152;  // 按照一片 2MB 分片
+//     let chunks = Math.ceil(file.size / chunkSize); // 片数
+//     let currentChunk = 0;
+//     let spark = new SparkMD5.ArrayBuffer();
+//     let fileReader = new FileReader();
+
+//     fileReader.onload = function (e) {
+//       console.log('read chunk nr', currentChunk + 1, 'of', chunks);
+//       spark.append(e.target.result);
+//       currentChunk++;
+
+//       if (currentChunk < chunks) {
+//         loadNext();
+//       } else {
+//         console.log('finished loading');
+//         let md5 = spark.end(); //最终md5值
+//         spark.destroy(); //释放缓存
+//         resolve(md5);
+//       }
+//     };
+
+//     fileReader.onerror = function (e) {
+//       console.warn('oops, something went wrong.');
+//       reject(e);
+//     };
+
+//     function loadNext() {
+//       let start = currentChunk * chunkSize;
+//       const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
+//       fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
+//     }
+
+//     loadNext();
+//   })
+// }
+//
+
+export function computeFileMD5(file) {
+  return new Promise((resolve, reject) => {
+    const hash = crypto.createHash('md5');
+
+    // 直接将整个 Buffer 更新到 hash
+    hash.update(file.data);
+
+    // 计算哈希值
+    const md5 = hash.digest('hex');
+    resolve(md5);
+  });
+}

+ 0 - 0
node_expores/utils/files.js


+ 2 - 0
node_expores/utils/index.js

@@ -0,0 +1,2 @@
+export * from './createMD5.js'
+export * from './files.js'

+ 50 - 1
node_expores/yarn.lock

@@ -10,6 +10,13 @@
     "@types/connect" "*"
     "@types/node" "*"
 
+"@types/busboy@*":
+  version "1.5.4"
+  resolved "https://registry.npmmirror.com/@types/busboy/-/busboy-1.5.4.tgz#0038c31102ca90f2a7f0d8bc27ee5ebf1088e230"
+  integrity sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==
+  dependencies:
+    "@types/node" "*"
+
 "@types/connect@*":
   version "3.4.38"
   resolved "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858"
@@ -17,6 +24,14 @@
   dependencies:
     "@types/node" "*"
 
+"@types/express-fileupload@^1.5.1":
+  version "1.5.1"
+  resolved "https://registry.npmmirror.com/@types/express-fileupload/-/express-fileupload-1.5.1.tgz#3c00e22f5515d4179a4b11b1b340d78197959819"
+  integrity sha512-DllImBVI1lCyjl2klky/TEwk60mbNebgXv1669h66g9TfptWSrEFq5a/raHSutaFzjSm1tmn9ypdNfu4jPSixQ==
+  dependencies:
+    "@types/busboy" "*"
+    "@types/express" "*"
+
 "@types/express-serve-static-core@^5.0.0":
   version "5.0.1"
   resolved "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz#3c9997ae9d00bc236e45c6374e84f2596458d9db"
@@ -27,7 +42,7 @@
     "@types/range-parser" "*"
     "@types/send" "*"
 
-"@types/express@^5.0.0":
+"@types/express@*", "@types/express@^5.0.0":
   version "5.0.0"
   resolved "https://registry.npmmirror.com/@types/express/-/express-5.0.0.tgz#13a7d1f75295e90d19ed6e74cab3678488eaa96c"
   integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==
@@ -129,6 +144,13 @@ body-parser@1.20.3:
     type-is "~1.6.18"
     unpipe "1.0.0"
 
+busboy@^1.6.0:
+  version "1.6.0"
+  resolved "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
+  integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
+  dependencies:
+    streamsearch "^1.1.0"
+
 bytes@3.1.2:
   version "3.1.2"
   resolved "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
@@ -172,6 +194,16 @@ core-util-is@~1.0.0:
   resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
   integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
 
+crypto@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
+  integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
+
+dayjs@^1.11.13:
+  version "1.11.13"
+  resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
+  integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
+
 debug@2.6.9:
   version "2.6.9"
   resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -240,6 +272,13 @@ etag@~1.8.1:
   resolved "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
   integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
 
+express-fileupload@^1.5.1:
+  version "1.5.1"
+  resolved "https://registry.npmmirror.com/express-fileupload/-/express-fileupload-1.5.1.tgz#a8be859e9d0ffb4497634b025ecb6d17796c370e"
+  integrity sha512-LsYG1ALXEB7vlmjuSw8ABeOctMp8a31aUC5ZF55zuz7O2jLFnmJYrCv10py357ky48aEoBQ/9bVXgFynjvaPmA==
+  dependencies:
+    busboy "^1.6.0"
+
 express@^4.21.1:
   version "4.21.1"
   resolved "https://registry.npmmirror.com/express/-/express-4.21.1.tgz#9dae5dda832f16b4eec941a4e44aa89ec481b281"
@@ -639,6 +678,11 @@ side-channel@^1.0.6:
     get-intrinsic "^1.2.4"
     object-inspect "^1.13.1"
 
+spark-md5@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc"
+  integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==
+
 sqlstring@2.3.1:
   version "2.3.1"
   resolved "https://registry.npmmirror.com/sqlstring/-/sqlstring-2.3.1.tgz#475393ff9e91479aea62dcaf0ca3d14983a7fb40"
@@ -654,6 +698,11 @@ statuses@2.0.1:
   resolved "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
   integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
 
+streamsearch@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
+  integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+
 string_decoder@~1.1.1:
   version "1.1.1"
   resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"