john hace 8 meses
padre
commit
77b1d34af2

+ 5 - 0
epub_node/.gitignore

@@ -0,0 +1,5 @@
+node_modules
+base_files/*
+*.tar
+pm2Log/*
+public/*

+ 1 - 0
epub_node/.npmrc

@@ -0,0 +1 @@
+registry=https://registry.npmmirror.com

+ 1 - 0
epub_node/.yarnrc

@@ -0,0 +1 @@
+registry "https://registry.npmmirror.com"

+ 23 - 0
epub_node/.zed/settings.json

@@ -0,0 +1,23 @@
+// Folder-specific settings
+//
+// For a full list of overridable settings, and general information on folder-specific settings,
+// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
+{
+  //
+  // 1. "VSCode"
+  // 2. "Atom"
+  // 3. "JetBrains"
+  // 4. "None"
+  // 5. "SublimeText"
+  // 6. "TextMate"
+  "base_keymap": "JetBrains",
+  // Keep in mind, if the autosave with delay is enabled, format_on_save will be ignored
+  "format_on_save": "off",
+  "ui_font_size": 17,
+  "buffer_font_size": 17,
+  "theme": {
+    "mode": "system",
+    "light": "Andromeda",
+    "dark": "One Dark"
+  }
+}

+ 8 - 0
epub_node/Dockerfile

@@ -0,0 +1,8 @@
+FROM node:18.20.4
+WORKDIR /app
+COPY package*.json ./
+RUN npm install
+COPY . .
+EXPOSE 3000
+VOLUME /app/base_files
+CMD ["node", "app.js"]

+ 79 - 0
epub_node/app.js

@@ -0,0 +1,79 @@
+import express from "express";
+import fileUpload from "express-fileupload";
+import bodyParser from "body-parser";
+import cors from "cors";
+
+import authors from "./router/authors/index.js";
+import authorsLogin from "./router/authors/login.js";
+import books from "./router/books/index.js";
+import files from "./router/files/index.js";
+import record from "./router/record/index.js";
+import moreRecord from "./router/record/more.js";
+import types from "./router/types/index.js";
+import epub from "./router/epub/index.js";
+
+import { generateToken, verifyToken } from "#utils";
+
+const port = 3000;
+const app = express();
+const json = express.json({ type: "*/json" });
+
+// 全局启用 CORS
+const corsOptions = {
+  origin: "http://localhost:3032", // 仅允许这个来源
+  methods: ["GET", "POST", "PUT", "DELETE"], // 允许的 HTTP 方法
+  allowedHeaders: ["Content-Type", "Authorization"], // 允许的头部
+};
+app.use(cors(corsOptions));
+app.use(fileUpload());
+app.use(json);
+app.use(bodyParser.urlencoded({ extended: false }));
+
+// Helper function to validate referer
+function isValidReferer(referer) {
+  return (
+    referer.indexOf("zs_interval") > -1 && referer.indexOf("/api/v1/files") > -1 || referer.indexOf('api_files_') > -1
+  );
+}
+
+// Helper function to extract `file_id` from referer
+function extractFileId(referer) {
+  const match = referer.match(/api_files_([^/]+)/); // 正则匹配 `file_id`
+  return match ? match[1] : null;
+}
+
+// middleware that is specific to this router
+app.use(async function timeLog(req, res, next) {
+  if (isValidReferer(req.url)) {
+    const fileId = extractFileId(req.url);
+    if (fileId) {
+      return res.redirect(`/api/v1/files/${fileId}`);
+    } else {
+      console.error("File ID not found in referer");
+    }
+  }
+  next();
+});
+
+// 静态文件目录,添加前缀路径 /static
+app.use("/static", express.static("public"));
+
+app.get("/", (req, res) => {
+  res.send("Hello World! " + req.url);
+});
+
+app.use("/api/v1/login", authors);
+app.use("/api/v1/files", files);
+app.use("/api/v1/epub", epub);
+
+app.use("/api/v1/*", verifyToken); // 注册token验证中间件
+
+app.use("/api/v1/auth", authorsLogin);
+app.use("/api/v1/books", books);
+app.use("/api/v1/record", record);
+app.use("/api/v1/more_record", moreRecord);
+app.use("/api/v1/types", types);
+
+app.listen(port, () => {
+  console.log(`Example app listening on port ${port}`);
+});

+ 114 - 0
epub_node/db/DB.sql

@@ -0,0 +1,114 @@
+-- epub_manage.book definition
+CREATE TABLE `book` (
+  `id` INT NOT NULL AUTO_INCREMENT,                          -- 主键使用自动递增的整数类型
+  `book_name` VARCHAR(255) NOT NULL,                          -- 合理缩短长度
+  `book_id` VARCHAR(100) NOT NULL,                            -- 修改长度为 100
+  `book_md5` VARCHAR(32) NOT NULL,                            -- MD5 长度是固定的 32 个字符
+  `language` VARCHAR(50) DEFAULT NULL COMMENT '语言',         -- 合理缩短字段长度
+  `date` DATE DEFAULT NULL COMMENT '创建时间',                -- 使用 DATE 类型来存储日期
+  `creatorFileAs` VARCHAR(255) DEFAULT NULL COMMENT '电子书创建人',
+  `UUID` VARCHAR(36) DEFAULT NULL COMMENT '电子书唯一编号',   -- UUID 长度设置为 36
+  `ISBN` VARCHAR(20) DEFAULT NULL COMMENT '电子书出版编号',   -- ISBN 长度设置为 20
+  `author_id` VARCHAR(100) NOT NULL COMMENT '作者id',         -- 合理缩短字段长度
+  `category_id` VARCHAR(255) DEFAULT NULL COMMENT '书籍类别编号',
+  `Introduction` TEXT DEFAULT NULL COMMENT '简介',            -- TEXT 类型更合适
+  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,         -- 默认当前时间戳
+  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  -- 自动更新时间
+  PRIMARY KEY (`id`),                                        -- 使用 `id` 作为主键
+  UNIQUE KEY `book_id_unique` (`book_id`)                     -- 为 `book_id` 设置唯一约束
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='书籍信息';
+
+-- epub_manage.author definition
+CREATE TABLE `author` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `name` VARCHAR(255) NOT NULL, -- 将 name 长度调整为 255
+  `author_id` VARCHAR(100) NOT NULL,  -- 调整为更合理的长度
+  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `author_id_unique` (`author_id`)  -- 添加唯一约束,确保 author_id 唯一
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+
+-- 书籍的类别 category 一本书有且只有一个类型,但是类别可以有多本书
+-- epub_manage.category definition
+CREATE TABLE `category` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `name` VARCHAR(255) NOT NULL, -- 将 name 长度调整为 255
+  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+
+-- 书籍的风格 style 一本书可以有多种风格, 同时 风格 也可以对应多种书籍
+-- epub_manage.types definition
+CREATE TABLE `style` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `name` VARCHAR(255) NOT NULL,  -- 将 name 长度调整为 255
+  `style_id` VARCHAR(100) NOT NULL, -- 将 style_id 长度调整为 100
+  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `style_id_unique` (`style_id`) -- 为 style_id 添加唯一约束
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+
+CREATE TABLE `style_link_book` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `style_id` VARCHAR(100) NOT NULL,  -- style_id 长度为 100
+  `book_id` VARCHAR(100) NOT NULL,  -- book_id 长度为 100
+  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `style_book_unique` (`style_id`, `book_id`),  -- 添加组合唯一索引
+  CONSTRAINT `fk_style` FOREIGN KEY (`style_id`) REFERENCES `style`(`style_id`) ON DELETE CASCADE,  -- 添加外键约束
+  CONSTRAINT `fk_book` FOREIGN KEY (`book_id`) REFERENCES `book`(`book_id`) ON DELETE CASCADE -- 添加外键约束
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+-- epub_manage.chapter definition
+CREATE TABLE `chapter` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `name` VARCHAR(255) NOT NULL,  -- 将 name 长度调整为 255
+  `book_id` VARCHAR(100) NOT NULL,  -- book_id 长度为 100
+  `author_id` VARCHAR(100) NOT NULL,  -- author_id 长度为 100
+  `content` LONGTEXT DEFAULT NULL,
+  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_chapter_book` FOREIGN KEY (`book_id`) REFERENCES `book`(`book_id`) ON DELETE CASCADE,  -- 外键约束
+  CONSTRAINT `fk_chapter_author` FOREIGN KEY (`author_id`) REFERENCES `author`(`author_id`) ON DELETE CASCADE -- 外键约束
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+
+-- epub_manage.files definition
+-- epub_manage.files table definition
+CREATE TABLE `files` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `file_id` VARCHAR(100) NOT NULL,  -- file_id 长度为 100
+  `md5` VARCHAR(32) NOT NULL,  -- MD5 长度为 32
+  `mimetype` VARCHAR(255) NOT NULL,  -- mimetype 长度为 255
+  `size` INT NOT NULL,
+  `name` VARCHAR(255) DEFAULT NULL,
+  `path` VARCHAR(255) DEFAULT NULL,
+  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `file_id_unique` (`file_id`)  -- 为 file_id 添加唯一约束
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+
+-- epub_manage.record_files definition
+-- epub_manage.book_link_file table definition
+CREATE TABLE `book_link_file` (
+  `id` INT NOT NULL AUTO_INCREMENT,
+  `file_id` VARCHAR(100) NOT NULL,  -- file_id 长度为 100
+  `book_id` VARCHAR(100) NOT NULL,  -- book_id 长度为 100
+  `author_id` VARCHAR(100) NOT NULL,  -- author_id 长度为 100
+  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  CONSTRAINT `fk_book_file` FOREIGN KEY (`file_id`) REFERENCES `files`(`file_id`) ON DELETE CASCADE,  -- 外键约束
+  CONSTRAINT `fk_link_book` FOREIGN KEY (`book_id`) REFERENCES `book`(`book_id`) ON DELETE CASCADE,  -- 外键约束
+  CONSTRAINT `fk_link_author` FOREIGN KEY (`author_id`) REFERENCES `author`(`author_id`) ON DELETE CASCADE -- 外键约束
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

+ 91 - 0
epub_node/db/auth.js

@@ -0,0 +1,91 @@
+import connection from "./base.js";
+
+export async function auth_insert({
+  name = "",
+  user_id = "",
+  account = "",
+  password = "",
+  create_time = "",
+  update_time = "",
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO authors (name, user_id, account, password, create_time, update_time)
+        VALUES (?, ?, ?, ?, ?, ?)
+      `;
+      const values = [
+        name,
+        user_id,
+        account,
+        password,
+        create_time,
+        update_time,
+      ];
+      // 直接接收 execute 返回的内容
+      connection.execute(sql, values, (result, fields) => {
+        return resolve(fields);
+      });
+    } catch (err) {
+      console.error("Error inserting data:", err);
+      return resolve(false);
+    }
+  });
+}
+
+// 是否存在重复的数据
+export function isHaveUserByUserId({ user_id = "", account = ""}) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM authors WHERE user_id = ? OR account = ?`,
+      [user_id, account],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows.length > 0); // 如果存在记录,则返回 true,否则返回 false
+        }
+      },
+    );
+  });
+}
+
+// 获取用户详情
+export function getUserInfoByuserId(user_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM authors WHERE user_id = ?`,
+      [user_id],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows.length > 0 ? rows[0] : false); // 如果存在记录,则返回 true,否则返回 false
+        }
+      },
+    );
+  });
+}
+
+// 用户是否已经注册
+export function isLoginUserByUserId({
+  account = "",
+  password = "",
+}) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM authors WHERE password = ? AND account = ?`,
+      [password, account],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows.length > 0 ? rows[0] : false); // 如果存在记录,则返回 true,否则返回 false
+        }
+      },
+    );
+  });
+}

+ 7 - 0
epub_node/db/base.js

@@ -0,0 +1,7 @@
+import mysql from "mysql2";
+import environment from "#environment";
+const connection = mysql.createConnection({
+  ...environment.dbInfo(),
+});
+connection.connect();
+export default connection;

+ 100 - 0
epub_node/db/book.js

@@ -0,0 +1,100 @@
+import connection from "./base.js";
+
+export async function books_insert({
+  book_name = '',
+  author_id = '',
+  create_time = '',
+  update_time = '',
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO book (book_name, author_id, create_time, update_time)
+        VALUES (?, ?, ?, ?)
+      `;
+      const values = [book_name, author_id, create_time, update_time];
+      // 直接接收 execute 返回的内容
+      connection.execute(sql, values, (result, fields) => {
+        return resolve(fields.insertId);
+      });
+    } catch (err) {
+      return resolve(false);
+    }
+  })
+}
+
+// 获取所有账本
+export function getAllBook(author_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(`SELECT * FROM book WHERE author_id = ? AND is_del=0`, [author_id], (err, rows) => {
+      if (err) {
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(rows); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}
+
+// 查询账本信息
+export function getBookById(book_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(`SELECT * FROM book WHERE id = ? AND is_del=0`, [book_id], (err, rows) => {
+      if (err) {
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(rows.length > 0 ?  rows[0]: false); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}
+
+// 删除账本
+export function delBookById(book_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(`update book SET is_del = 1 WHERE id = ?`, [book_id], (err, rows) => {
+      if (err) {
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(true); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}
+
+
+
+// 是否存在重复的账本数据
+export function ishaveBookById({
+  book_name = '',
+  author_id = '',
+}) {
+  return new Promise((resolve, reject) => {
+    connection.query(`SELECT * FROM book WHERE book_name = ? AND author_id = ? AND is_del=0`, [book_name, author_id], (err, rows) => {
+      if (err) {
+        // reject(err);
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(rows.length > 0); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}
+
+// 更新账本名称
+export function updateBookName({
+  book_name = '',
+  book_id = '',
+  update_time = '',
+}) {
+  return new Promise((resolve, reject) => {
+    connection.query(`UPDATE book SET book_name = ? , update_time = ? WHERE id = ?`, [book_name, update_time, book_id], (err, rows) => {
+      if (err) {
+        // reject(err);
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(rows.length > 0); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}

+ 122 - 0
epub_node/db/files.js

@@ -0,0 +1,122 @@
+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);
+      return resolve(result);
+    } catch (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
+      }
+    });
+  });
+}
+
+// 删除图片
+export function delFileBymd5(md5Str) {
+  return new Promise((resolve, reject) => {
+    connection.query(`update files SET is_del = 1 WHERE md5=?`, [md5Str], (err, rows) => {
+      if (err) {
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(true); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}
+
+// 映射数据与文件数据
+export function addFileByRecordId({
+  file_id = '',
+  record_id = '',
+  book_id = '',
+  author_id = '',
+  create_time = '',
+  update_time = '',
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO record_files (file_id, record_id, book_id, author_id, create_time, update_time)
+        VALUES (?, ?, ?, ?, ?, ?)
+      `;
+      const values = [file_id, record_id, book_id, author_id, create_time, update_time];
+      // 直接接收 execute 返回的内容
+      const result = await connection.execute(sql, values);
+      return resolve(result);
+    } catch (err) {
+      return resolve(false);
+    }
+  })
+}
+
+// 根据id获取files
+export function getFileByRecordId(record_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(`SELECT * FROM record_files WHERE record_id = ?`, [record_id], (err, rows) => {
+      if (err) {
+        resolve(false); // 如果存在记录,则返回 true,否则返回 false
+      } else {
+        resolve(rows); // 如果存在记录,则返回 true,否则返回 false
+      }
+    });
+  });
+}
+
+// 根据id删除映射数据
+export function delFileByRecordId(record_id, file_id) {
+  return new Promise((resolve, reject) => {
+    try {
+      const sql = `DELETE FROM record_files WHERE record_id = ? AND file_id = ?`;
+      connection.query(sql, [record_id, file_id], (err, result) => {
+        if (err) {
+          // 如果执行过程中出现错误,返回 false
+          return resolve(false);
+        }
+        // 删除成功后返回结果
+        return resolve(result);
+      });
+    } catch (err) {
+      // 捕获异常并返回 false
+      return resolve(false);
+    }
+  });
+}

+ 6 - 0
epub_node/db/index.js

@@ -0,0 +1,6 @@
+export * from './files.js';
+export * from './auth.js';
+export * from './record.js';
+export * from './more_record.js';
+export * from './type.js';
+export * from './book.js';

+ 206 - 0
epub_node/db/more_record.js

@@ -0,0 +1,206 @@
+import connection from "./base.js";
+import dayjs from "dayjs";
+
+export async function addMoreRecord({
+  more_id = "",
+  name = "",
+  remark = "",
+  total_fee = "",
+  book_id = "",
+  author_id = "",
+  create_time = "",
+  update_time = "",
+  start_time = "",
+  end_time = "",
+  type_id = "",
+  log_day = "",
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO more_record (more_id, name, remark, total_fee, book_id, author_id, create_time, update_time, start_time, end_time, type_id, log_day)
+        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+      `;
+      const values = [
+        more_id,
+        name,
+        remark,
+        total_fee,
+        book_id,
+        author_id,
+        create_time,
+        update_time,
+        start_time,
+        end_time,
+        type_id,
+        log_day,
+      ];
+      // 直接接收 execute 返回的内容
+      connection.execute(sql, values, (result, fields) => {
+        return resolve(fields.insertId);
+      });
+    } catch (err) {
+      return resolve(false);
+    }
+  });
+}
+
+export async function updataMoreRecord(params) {
+  const {
+    more_id = "",
+    type_id = "",
+    name = "",
+    remark = "",
+    total_fee = "",
+    update_time = "",
+    start_time = "",
+    end_time = "",
+    log_day = "",
+  } = params;
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        UPDATE more_record SET type_id = ?, name = ?, total_fee = ?, remark = ?, update_time = ?, start_time= ?, end_time = ? , log_day = ? WHERE more_id = ?;
+      `;
+      const values = [
+        type_id,
+        name,
+        total_fee,
+        remark,
+        update_time,
+        start_time,
+        end_time,
+        log_day,
+        more_id,
+      ];
+      // 执行更新语句
+      connection.execute(sql, values, (err, result) => {
+        if (err) {
+          // 错误处理
+          return resolve(false);
+        }
+        return resolve(result.changedRows > 0);
+      });
+    } catch (err) {
+      return resolve(false);
+    }
+  });
+}
+
+export function delMoreRecord(more_id, author_id) {
+  return new Promise((resolve, reject) => {
+    try {
+      const sql = `DELETE FROM more_record WHERE more_id = ? AND author_id = ?`;
+      connection.query(sql, [more_id, author_id], (err, result) => {
+        if (err) {
+          // 如果执行过程中出现错误,返回 false
+          return resolve(false);
+        }
+        // 删除成功后返回结果
+        return resolve(result);
+      });
+    } catch (err) {
+      // 捕获异常并返回 false
+      return resolve(false);
+    }
+  });
+}
+
+// 根据日期查询记录
+export function getMoreRecordsInfoByTime(time, day, book_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT more_record.*, types.name AS type
+        FROM
+            more_record
+        JOIN
+            types
+        ON
+            more_record.type_id = types.id
+        WHERE
+            more_record.start_time <= ? AND
+            more_record.end_time >= ? AND
+            more_record.log_day = ? AND
+            more_record.book_id = ?;`,
+      [
+        `${dayjs(time).endOf("month").format("YYYY-MM-DD")}`,
+        `${dayjs(time).startOf("month").format("YYYY-MM-DD")}`,
+        day,
+        book_id,
+      ],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows); // 如果存在记录,则返回 true,否则返回 false
+        }
+      }
+    );
+  });
+}
+
+// 根据月份查询记录
+export function getMoreRecordsInfoByMonth(time, book_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT more_record.*, types.name AS type
+        FROM
+            more_record
+        JOIN
+            types
+        ON
+            more_record.type_id = types.id
+        WHERE
+            more_record.start_time <= ? AND
+            more_record.end_time >= ? AND
+            more_record.book_id = ?;`,
+      [
+        `${dayjs(time).endOf("month").format("YYYY-MM-DD")}`,
+        `${time}-01`,
+        book_id,
+      ],
+      (err, rows) => {
+        if (err) {
+          resolve(false);
+        } else {
+          resolve(rows);
+        }
+      }
+    );
+  });
+}
+
+// 查询所有账本信息
+export function getMoreRecordList(book_id, author_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM more_record WHERE book_id = ? AND author_id = ?`,
+      [book_id, author_id],
+      (err, rows) => {
+        if (err) {
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows); // 如果存在记录,则返回 true,否则返回 false
+        }
+      }
+    );
+  });
+}
+
+// 查询账本信息
+export function getMoreRecordByMoreId(more_id, author_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM more_record WHERE more_id = ? AND author_id = ?`,
+      [more_id, author_id],
+      (err, rows) => {
+        if (err) {
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows.length > 0 ? rows[0] : false); // 如果存在记录,则返回 true,否则返回 false
+        }
+      }
+    );
+  });
+}

+ 230 - 0
epub_node/db/record.js

@@ -0,0 +1,230 @@
+import dayjs from "dayjs";
+import connection from "./base.js";
+
+export async function record_insert({
+  book_id = "",
+  type_id = "",
+  author_id = "",
+  total_fee = "",
+  remark = "",
+  time = "",
+  create_time = "",
+  update_time = "",
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO record (book_id, type_id, author_id, total_fee, remark, create_time, update_time, time)
+        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+      `;
+      const values = [
+        book_id,
+        type_id,
+        author_id,
+        total_fee,
+        remark,
+        create_time,
+        update_time,
+        time,
+      ];
+      // 直接接收 execute 返回的内容
+      connection.execute(sql, values, (result, fields) => {
+        return resolve(fields.insertId);
+      });
+    } catch (err) {
+      return resolve(false);
+    }
+  });
+}
+
+export async function record_update( params ) {
+  const {
+    id = "", // 要更新的记录的唯一标识符
+    type_id = "",
+    author_id = "",
+    total_fee = "",
+    remark = "",
+    time = "",
+    update_time = "",
+  } = params
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        UPDATE record SET type_id = ?, author_id = ?, total_fee = ?, remark = ?, time = ?, update_time = ? WHERE id = ?
+      `;
+      const values = [ type_id, author_id, total_fee, remark, time, update_time, id, ];
+      // 执行更新语句
+      connection.execute(sql, values, (err, result) => {
+        if (err) {
+          // 错误处理
+          return resolve(false);
+        }
+        return resolve(result.changedRows > 0);
+      });
+    } catch (err) {
+      return resolve(false);
+    }
+  });
+}
+
+// 关联 账单 跟 附件的数据
+/*
+`file_id` varchar(300) NOT NULL,
+`record_id` varchar(300) NOT NULL,
+`book_id` varchar(300) NOT NULL,
+`user_id` varchar(300) NOT NULL,
+*/
+export function record_and_files({
+  file_id = "",
+  record_id = "",
+  book_id = "",
+  user_id = "",
+  create_time = "",
+  update_time = "",
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO record_files (file_id, record_id, book_id, user_id, create_time, update_time)
+        VALUES (?, ?, ?, ?, ?, ?)
+      `;
+      const values = [
+        file_id,
+        record_id,
+        book_id,
+        user_id,
+        create_time,
+        update_time,
+      ];
+      // 直接接收 execute 返回的内容
+      const result = await connection.execute(sql, values);
+      return resolve(result);
+    } catch (err) {
+      return resolve(false);
+    }
+  });
+}
+
+export function recordRelation({
+  name = "",
+  record_id = "",
+  book_id = "",
+  user_id = "",
+  create_time = "",
+  update_time = "",
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO record_files (file_id, record_id, book_id, user_id, create_time, update_time)
+        VALUES (?, ?, ?, ?, ?, ?)
+      `;
+      const values = [
+        file_id,
+        record_id,
+        book_id,
+        user_id,
+        create_time,
+        update_time,
+      ];
+      // 直接接收 execute 返回的内容
+      const result = await connection.execute(sql, values);
+      return resolve(result);
+    } catch (err) {
+      return resolve(false);
+    }
+  });
+}
+
+// 获取用户详情
+export function getRecordInfoById(record_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM record WHERE id = ?`,
+      [record_id],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows.length > 0 ? rows[0] : false); // 如果存在记录,则返回 true,否则返回 false
+        }
+      }
+    );
+  });
+}
+
+// 根据日期查询记录
+export function getRecordsInfoByTime(time, book_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT record.*, types.name AS type
+        FROM
+            record
+        JOIN
+            types
+        ON
+            record.type_id = types.id
+        WHERE
+        record.time = ?
+        AND record.book_id = ?;`,
+      [`${dayjs(time).format('YYYY-MM-DD')} 00:00:00`, book_id],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows); // 如果存在记录,则返回 true,否则返回 false
+        }
+      }
+    );
+  });
+}
+// 根据月份查询记录
+export function getRecordsInfoByMonth(time, book_id) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      // `SELECT * FROM record WHERE DATE_FORMAT(time, '%Y-%m') = ? AND book_id = ?`,
+      `SELECT record.*, types.name AS type
+        FROM
+            record
+        JOIN
+            types
+        ON
+            record.type_id = types.id
+        WHERE
+        DATE_FORMAT(record.time, '%Y-%m') = ?
+        AND record.book_id = ?;`,
+      [time, book_id],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows); // 如果存在记录,则返回 true,否则返回 false
+        }
+      }
+    );
+  });
+}
+
+
+// 根据id删除数据
+export function delByRecordId(record_id, author_id) {
+  return new Promise((resolve, reject) => {
+    try {
+      const sql = `DELETE FROM record WHERE id = ? AND author_id = ?`;
+      connection.query(sql, [record_id, author_id], (err, result) => {
+        if (err) {
+          // 如果执行过程中出现错误,返回 false
+          return resolve(false);
+        }
+        // 删除成功后返回结果
+        return resolve(result);
+      });
+    } catch (err) {
+      // 捕获异常并返回 false
+      return resolve(false);
+    }
+  });
+}

+ 77 - 0
epub_node/db/type.js

@@ -0,0 +1,77 @@
+import connection from "./base.js";
+
+// 获取类型详情
+export function getTypesById({ typeId = "", book_id = "", author_id = "" }) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM types WHERE id = ? AND author_id = ? AND book_id = ?`,
+      [typeId, author_id, book_id],
+      (err, rows) => {
+        if (err) {
+          resolve(false);
+        } else {
+          resolve(rows.length > 0 ? rows[0] : false);
+        }
+      }
+    );
+  });
+}
+
+// 获取用户所有的类型
+export function getTypesByUserId({ book_id = "", author_id = "" }) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM types WHERE book_id = ? AND author_id = ?`,
+      [book_id, author_id],
+      (err, rows) => {
+        if (err) {
+          resolve(false);
+        } else {
+          resolve(rows);
+        }
+      }
+    );
+  });
+}
+// 用户是否已经注册
+export function isType({ book_id = "", author_id = "", type = "" }) {
+  return new Promise((resolve, reject) => {
+    connection.query(
+      `SELECT * FROM types WHERE book_id = ? AND author_id = ? AND name = ?`,
+      [book_id, author_id, type],
+      (err, rows) => {
+        if (err) {
+          // reject(err);
+          resolve(false); // 如果存在记录,则返回 true,否则返回 false
+        } else {
+          resolve(rows.length > 0 ? rows[0] : false); // 如果存在记录,则返回 true,否则返回 false
+        }
+      }
+    );
+  });
+}
+
+export async function type_insert({
+  book_id = "",
+  author_id = "",
+  type = "",
+  create_time = "",
+  update_time = "",
+}) {
+  return new Promise(async (resolve, reject) => {
+    try {
+      const sql = `
+        INSERT INTO types (name, book_id, author_id, create_time, update_time)
+        VALUES (?, ?, ?, ?, ?)
+      `;
+      const values = [type, book_id, author_id, create_time, update_time];
+      // 直接接收 execute 返回的内容
+      const result = await connection.execute(sql, values);
+      return resolve(result);
+    } catch (err) {
+      console.error("Error inserting data:", err);
+      // throw err;
+      return resolve(false);
+    }
+  });
+}

+ 33 - 0
epub_node/db/update.sql

@@ -0,0 +1,33 @@
+ALTER TABLE cashbook.authors MODIFY COLUMN id int NOT NULL AUTO_INCREMENT;
+
+ALTER TABLE cashbook.authors DROP KEY authors_unique_1;
+ALTER TABLE cashbook.authors DROP COLUMN mobile;
+ALTER TABLE cashbook.authors CHANGE email account varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL;
+ALTER TABLE cashbook.authors DROP COLUMN login_type;
+
+
+ALTER TABLE cashbook.record ADD source_id varchar(100) NULL;
+
+
+ALTER TABLE cashbook.record MODIFY COLUMN remark LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL;
+
+ALTER TABLE cashbook.more_record ADD more_id varchar(300) NOT NULL;
+ALTER TABLE cashbook.more_record ADD CONSTRAINT more_record_unique UNIQUE KEY (more_id);
+ALTER TABLE cashbook.more_record ADD remark LONGTEXT NULL;
+
+ALTER TABLE cashbook.more_record ADD total_fee decimal(15,2) NOT NULL;
+
+ALTER TABLE cashbook.more_record MODIFY COLUMN name varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL;
+
+
+ALTER TABLE cashbook.more_record ADD start_time TIMESTAMP NOT NULL;
+
+ALTER TABLE cashbook.more_record ADD start_time TIMESTAMP NOT NULL;
+ALTER TABLE cashbook.more_record ADD end_time TIMESTAMP NOT NULL;
+
+ALTER TABLE cashbook.more_record ADD type_id INT NULL;
+
+
+ALTER TABLE cashbook.more_record ADD log_day INT NOT NULL;
+
+

+ 16 - 0
epub_node/ecosystem.config.cjs

@@ -0,0 +1,16 @@
+module.exports = {
+  apps: [
+    {
+      name: "cashBook",
+      script: "./app.js",
+      error_file: "./pm2Log/err.log",
+      out_file: "./pm2Log/out.log",
+      watch: true,
+      max_restarts: 20,
+      ignore_watch: ["node_modules"],
+      interpreter: "/Users/honghaitao/.volta/tools/image/node/22.2.0/bin/node", // 指定 Node.js 版本路径
+    },
+  ],
+};
+
+

+ 32 - 0
epub_node/environment/index.js

@@ -0,0 +1,32 @@
+function dbInfo() {
+  // 根据需要更新db的数据配置
+  return {
+    host: "localhost",
+    port: 3306,
+    user: "root",
+    password: "12345678",
+    database: "epub_manage",
+  };
+  return {
+    host: "192.168.2.101",
+    port: 6806,
+    user: "root",
+    password: "admin",
+    database: "epub_manage",
+  };
+}
+
+
+
+export default {
+  dbInfo,
+  aes_info: () => {
+    return {
+    // key的长度必须为32bytes:
+    key: 'Passw0rdPassw0rdPassw0rdPassw0rd',
+    // iv的长度必须为16bytes:
+    iv: 'a1b2c3d4e5f6g7h8'
+    }
+  },
+  privateKey: 'cashbook1994'
+};

+ 56 - 0
epub_node/package.json

@@ -0,0 +1,56 @@
+{
+  "name": "new_cash_book_node",
+  "version": "1.0.0",
+  "description": "",
+  "type": "module",
+  "main": "index.js",
+  "scripts": {
+    "dev": "pm2 start ecosystem.config.cjs",
+    "dev:stop": "pm2 stop cashBook",
+    "dev:del": "pm2 delete cashBook",
+    "start": "node app.js",
+    "serve": "nodemon app.js",
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/Johnhong9527/newCashBook.git"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "bugs": {
+    "url": "https://github.com/Johnhong9527/newCashBook/issues"
+  },
+  "homepage": "https://github.com/Johnhong9527/newCashBook#readme",
+  "dependencies": {
+    "body-parser": "^1.20.3",
+    "cors": "^2.8.5",
+    "crypto": "^1.0.1",
+    "dayjs": "^1.11.13",
+    "epub2": "^3.0.2",
+    "express": "^4.21.1",
+    "express-fileupload": "^1.5.1",
+    "jsonwebtoken": "^9.0.2",
+    "mysql": "^2.18.1",
+    "mysql2": "^3.11.4",
+    "nodemon": "^3.1.7",
+    "spark-md5": "^3.0.2",
+    "uuid": "^11.0.2"
+  },
+  "volta": {
+    "node": "22.12.0"
+  },
+  "devDependencies": {
+    "@types/express": "^5.0.0",
+    "@types/express-fileupload": "^1.5.1",
+    "@types/jsonwebtoken": "^9.0.7",
+    "@types/mysql": "^2.15.26",
+    "@types/node": "^22.9.0"
+  },
+  "imports": {
+    "#db": "./db/index.js",
+    "#utils": "./utils/index.js",
+    "#environment": "./environment/index.js"
+  }
+}

+ 0 - 0
epub_node/pm2.config.js


+ 78 - 0
epub_node/router/authors/index.js

@@ -0,0 +1,78 @@
+// 添加账本
+import express from "express";
+import { generateToken, aes_encrypt, shanghaiTime } from "#utils";
+import { isHaveUserByUserId, auth_insert, isLoginUserByUserId } from "#db";
+const router = express.Router();
+import { v4 as uuidv4 } from "uuid";
+
+// middleware that is specific to this router
+router.use(function timeLog(req, res, next) {
+  console.log("Time: ", Date.now());
+  next();
+});
+
+// 注册
+router.post("/register", async function (req, res) {
+  const { account, name = "", password = "" } = req.body;
+  const user_id = uuidv4();
+  if (await isHaveUserByUserId({ user_id, account,})) {
+    res.send({
+      code: 500,
+      msg: "当前注册信息有重复,请检查之后重新提交!",
+    });
+    return;
+  }
+  // 写入数据
+  await auth_insert({
+    name,
+    account,
+    user_id,
+    password: aes_encrypt(password),
+    create_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+    update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+  });
+
+  const token = await getToken(account, password)
+  res.json({
+    code: 200,
+    data: {
+      token
+    }
+  });
+});
+
+async function getToken(account, password) {
+  const islogin = await isLoginUserByUserId({
+    password: aes_encrypt(password),
+    account,
+  });
+  if(!islogin) return false
+  delete islogin.password;
+  delete islogin.login_type;
+  delete islogin.id;
+  return generateToken(islogin);
+}
+
+// 登陆
+router.post("/", async function (req, res) {
+  const { account, password } = req.body;
+
+  const token = await getToken(account, password);
+
+  if (!token) {
+    res.json({
+      code: 404,
+      msg: "登录失败,当前用户不存在",
+      data: {},
+    });
+    return;
+  }
+  res.json({
+    code: 200,
+    msg: "登录成功",
+    data: { token },
+  });
+  // res.send("authors home page");
+});
+
+export default router;

+ 33 - 0
epub_node/router/authors/login.js

@@ -0,0 +1,33 @@
+// 添加账本
+import express from "express";
+
+const router = express.Router();
+
+// 更新密码
+router.put("/", function (req, res) {
+  res.send("authors home page");
+});
+
+// 注销
+router.delete("/", function (req, res) {
+  res.send("authors home page");
+});
+
+// 获取用户详情
+router.post("/user_info", function (req, res) {
+  let userInfo = {
+    name: req.body.userInfo.name,
+    user_id: req.body.userInfo.user_id,
+    create_time: req.body.userInfo.create_time,
+    update_time: req.body.userInfo.update_time,
+    mobile: req.body.userInfo.mobile,
+    email: req.body.userInfo.email
+  }
+  res.json({
+    code: 200,
+    data: userInfo
+  })
+});
+
+
+export default router;

+ 113 - 0
epub_node/router/books/index.js

@@ -0,0 +1,113 @@
+// 添加账本
+import express from "express";
+
+const router = express.Router();
+
+import {
+  getBookById,
+  books_insert,
+  delBookById,
+  ishaveBookById,
+  updateBookName,
+  getAllBook
+} from "#db";
+import {
+  shanghaiTime,
+  shanghaiTimeFormat
+} from '#utils'
+
+// middleware that is specific to this router
+router.use(function timeLog(req, res, next) {
+  console.log("Time: ", Date.now());
+  next();
+});
+
+// 获取所有账本
+router.get("/", async function (req, res) {
+  const { userInfo = {} } = req.body;
+  const bookInfo = await getAllBook(userInfo.user_id);
+  
+  res.json({
+    code: 200,
+    data: bookInfo.map(elm => {
+      return {
+        book_name: elm.book_name,
+        create_time: shanghaiTimeFormat(elm.create_time),
+        id: elm.id,
+        update_time: shanghaiTimeFormat(elm.update_time),
+      }
+    }),
+  });
+});
+
+// 获取账本详情
+router.get("/:book_id", async function (req, res) {
+  const book_id = req.params.book_id; // 获取 fileId 参数
+  const bookInfo = await getBookById(book_id);
+  bookInfo.create_time = shanghaiTimeFormat(bookInfo.create_time)
+  bookInfo.update_time = shanghaiTimeFormat(bookInfo.update_time)
+  delete bookInfo.is_del
+  res.json({
+    code: 200,
+    data: bookInfo,
+  });
+});
+
+// 添加账本数据
+router.post("/", async function (req, res) {
+  const { book_name = "", userInfo = {} } = req.body;
+  // type 是否存在重复项,有就返回id,没有就新增 再返回id
+  const isAddType = await ishaveBookById({
+    book_name,
+    author_id: userInfo.user_id,
+  });
+  if (isAddType) {
+    res.json({
+      code: 500,
+      msg: "已存在重复数据",
+    });
+    return;
+  }
+  const insertId = await books_insert({
+    book_name,
+    author_id: userInfo.user_id,
+    create_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+    update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+  });
+
+  res.json({
+    code: 200,
+    data: {
+      book_id: insertId,
+    },
+  });
+});
+
+// 更新bookName
+router.put("/:book_id", async function (req, res) {
+  const book_id = req.params.book_id; // 获取 fileId 参数
+  const { book_name = ""} = req.body;
+  await updateBookName({
+    book_name,
+    book_id,
+    update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+  })
+  res.json({
+    code: 200,
+    msg: "更新成功!",
+  });
+});
+
+// 删除数据
+router.delete("/:book_id", async function (req, res) {
+  delBookById;
+  // getBookById
+  const book_id = req.params.book_id; // 获取 fileId 参数
+  await delBookById(book_id);
+  res.json({
+    code: 200,
+    msg: "删除成功!",
+  });
+});
+
+export default router;

+ 73 - 0
epub_node/router/epub/index.js

@@ -0,0 +1,73 @@
+// 添加账本
+import express from "express";
+import { EPub } from "epub2";
+
+const router = express.Router();
+import { getTypesByUserId } from "#db";
+
+// middleware that is specific to this router
+router.use(function timeLog(req, res, next) {
+  next();
+});
+// define the home page route
+router.get("/", async function (req, res) {
+  const { userInfo = {} } = req.body;
+  // getTypesByUserId
+  const { book_id = "" } = req.query;
+
+  const typesRes = await getTypesByUserId({
+    book_id,
+    author_id: userInfo.user_id,
+  });
+
+  res.json({
+    code: 200,
+    data: typesRes.map((elm) => ({
+      name: elm.name,
+      id: elm.id,
+    })),
+  });
+});
+// define the about route
+router.get("/about", function (req, res) {
+  res.send("About types");
+});
+// define the about route
+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.file;
+  console.log(4545, sampleFile);
+
+  const file_md5 = sampleFile.md5
+
+  var epub = await EPub.createAsync(sampleFile.data, null, "");
+  console.log(4545, epub.metadata);
+  // console.log('manifest__', epub.manifest);
+  
+  // Object.keys(epub.metadata).forEach((objKey) => {
+  //   console.log(464646, objKey, epub.metadata[objKey]);
+  // });
+  // let imgs = epub.listImage();
+  // await epub.getImageAsync(imgs[0].id).then( function([data, mimeType]){
+
+  //   console.log(`\ngetImage: cover\n`);
+
+  //   console.log(data);
+  //   console.log(mimeType)
+  // });
+  /* 
+    1、读取图片信息
+    2、替换文件中的图片内容
+    3、存储html数据
+   */
+
+  res.send("About types");
+});
+
+export default router;

+ 90 - 0
epub_node/router/files/index.js

@@ -0,0 +1,90 @@
+// 附件
+import express from "express";
+import path from "path";
+import fs from "node:fs";
+import { files_insert, ishaveFileBymd5, getFileBymd5 } from "#db";
+import { shanghaiTime, dirExists } from "#utils";
+const router = express.Router();
+
+router.use(function timeLog(req, res, next) {
+  console.log("Time: ", Date.now());
+  next();
+});
+
+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: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+    update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+  };
+  uploadPath = "./base_files/" + sampleFile.md5;
+  dirExists("./base_files/");
+
+  // 、移动上传文件至指定目录
+  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) {
+  res.send("About files");
+});
+
+export default router;

+ 238 - 0
epub_node/router/record/index.js

@@ -0,0 +1,238 @@
+// 添加账本
+import express from "express";
+import dayjs from "dayjs";
+const router = express.Router();
+import {
+  record_insert,
+  record_update,
+  isType,
+  type_insert,
+  getRecordInfoById,
+  addFileByRecordId,
+  getFileByRecordId,
+  getRecordsInfoByTime,
+  getRecordsInfoByMonth,
+  getTypesById,
+  delFileByRecordId,
+  delByRecordId,
+  getMoreRecordsInfoByMonth,
+  getMoreRecordsInfoByTime,
+} from "#db";
+import { shanghaiTime, shanghaiTimeFormat } from "#utils";
+import {
+  getTypeInfoFn,
+  setFilesById,
+  setFilesByRecord,
+  getFileUrl,
+} from "./utils.js";
+
+// middleware that is specific to this router
+router.use(function timeLog(req, res, next) {
+  console.log("Time: ", Date.now());
+  next();
+});
+
+// 添加单个账单记录
+router.post("/", async function (req, res) {
+  const {
+    book_id = "",
+    total_fee = 0,
+    type = "",
+    time = "",
+    remark = "",
+    files = [],
+    userInfo = {},
+  } = req.body;
+  const type_id = await getTypeInfoFn({ userInfo, book_id, type });
+  const insertId = await record_insert({
+    book_id,
+    type_id: type_id,
+    author_id: userInfo.user_id,
+    total_fee,
+    remark,
+    time: shanghaiTimeFormat(time),
+    create_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+    update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+  });
+
+  await setFilesByRecord({
+    files,
+    insertId,
+    book_id,
+    userInfo,
+  });
+
+  res.json({
+    code: 200,
+    data: {
+      record_id: insertId,
+    },
+  });
+});
+
+// define the home page route
+router.get("/:record_id", async function (req, res) {
+  const record_id = req.params.record_id; // 获取 fileId 参数
+  const recordInfo = await getRecordInfoById(record_id);
+  const files = await getFileByRecordId(record_id);
+
+  const typesRes = await getTypesById({
+    typeId: recordInfo.type_id,
+    book_id: recordInfo.book_id,
+    author_id: recordInfo.author_id,
+  });
+  if (typesRes) {
+    recordInfo.type = typesRes.name;
+  } else {
+    recordInfo.type = "";
+  }
+
+  res.json({
+    code: 200,
+    data: {
+      headers: req.headers,
+      ...recordInfo,
+      time: shanghaiTimeFormat(recordInfo.time, "YYYY-MM-DD"),
+      create_time: shanghaiTimeFormat(recordInfo.create_time),
+      update_time: shanghaiTimeFormat(recordInfo.update_time),
+      files: files.map((elm) => getFileUrl(req, elm)),
+    },
+  });
+});
+
+// 更新账本数据
+router.put("/:record_id", async function (req, res) {
+  const record_id = req.params.record_id; // 获取 fileId 参数
+  const {
+    book_id = "",
+    total_fee = 0,
+    type = "",
+    time = "",
+    remark = "",
+    files = [],
+    userInfo = {},
+  } = req.body;
+
+  // 更新附件信息
+  await setFilesById({
+    record_id,
+    userInfo,
+    book_id,
+    files,
+  });
+
+  // 更新类型
+  const typeId = await getTypeInfoFn({
+    userInfo,
+    book_id,
+    type,
+  });
+
+  const recordInfo = await record_update({
+    id: record_id, // 要更新的记录的唯一标识符
+    type_id: typeId,
+    author_id: userInfo.user_id,
+    total_fee: total_fee,
+    remark: remark,
+    time: time,
+    update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+  });
+  res.json({
+    code: 200,
+    data: recordInfo ? "" : "更新失败",
+  });
+});
+
+// define the about route
+router.get("/about", function (req, res) {
+  res.send("About record");
+});
+
+// 根据日期获取数据
+router.get("/:book_id/:time", async function (req, res) {
+  const time = req.params.time; // 获取 fileId 参数
+  const book_id = req.params.book_id; // 获取 fileId 参数
+  const recordsInfoRes = await getRecordsInfoByTime(time, book_id);
+  const moreRecordsInfoRes = await getMoreRecordsInfoByTime(
+    time,
+    dayjs(time).date(),
+    book_id
+  );
+  res.json({
+    code: 200,
+    data: recordsInfoRes
+      .map((elm) => ({
+        ...elm,
+        time: shanghaiTimeFormat(elm.time, "YYYY-MM-DD"),
+        create_time: shanghaiTimeFormat(elm.create_time),
+        update_time: shanghaiTimeFormat(elm.update_time),
+      }))
+      .concat(
+        moreRecordsInfoRes.map((elm) => {
+          return {
+            ...elm,
+            time: shanghaiTimeFormat(`${time}-${elm.log_day}`, "YYYY-MM-DD"),
+            create_time: shanghaiTimeFormat(elm.create_time),
+            update_time: shanghaiTimeFormat(elm.update_time),
+          };
+        })
+      )
+      .sort(
+        (a, b) => dayjs(b.update_time).unix() - dayjs(a.update_time).unix()
+      ),
+  });
+});
+
+// 根据日期获取数据
+router.get("/:book_id/m/:time", async function (req, res) {
+  const time = req.params.time; // 获取 fileId 参数
+  const book_id = req.params.book_id; // 获取 fileId 参数
+  const recordsInfoRes = await getRecordsInfoByMonth(time, book_id);
+  const moreRecordsInfoRes = await getMoreRecordsInfoByMonth(time, book_id);
+
+  res.json({
+    code: 200,
+    data: recordsInfoRes
+      .map((elm) => ({
+        ...elm,
+        time: shanghaiTimeFormat(elm.time, "YYYY-MM-DD"),
+        create_time: shanghaiTimeFormat(elm.create_time),
+        update_time: shanghaiTimeFormat(elm.update_time),
+      }))
+      .concat(
+        moreRecordsInfoRes.map((elm) => {
+          return {
+            ...elm,
+            time: shanghaiTimeFormat(`${time}-${elm.log_day}`, "YYYY-MM-DD"),
+            create_time: shanghaiTimeFormat(elm.create_time),
+            update_time: shanghaiTimeFormat(elm.update_time),
+          };
+        })
+      )
+      .sort((a, b) => dayjs(a.time).unix() - dayjs(b.time).unix()),
+  });
+});
+
+// 删除指定账单
+router.delete("/:record_id", async function (req, res) {
+  const record_id = req.params.record_id; // 获取 fileId 参数
+  const { userInfo = {} } = req.body;
+  const recordInfo = await getRecordInfoById(record_id);
+
+  // 删除附件映射关系
+  const getAllfiles = await getFileByRecordId(record_id);
+  if (getAllfiles.length) {
+    await Promise.all(
+      getAllfiles.map((elm) => delFileByRecordId(record_id, elm.file_id))
+    );
+  }
+
+  // 删除record数据
+  await delByRecordId(record_id, userInfo.user_id);
+
+  res.json({
+    code: 200,
+    data: "",
+  });
+});
+export default router;

+ 200 - 0
epub_node/router/record/more.js

@@ -0,0 +1,200 @@
+import express from "express";
+
+const router = express.Router();
+import { v4 as uuidv4 } from "uuid";
+import { shanghaiTime, shanghaiTimeFormat } from "#utils";
+import {
+  addMoreRecord,
+  updataMoreRecord,
+  delMoreRecord,
+  record_update,
+  getRecordInfoById,
+  getFileByRecordId,
+  delFileByRecordId,
+  delByRecordId,
+  getMoreRecordList,
+  getMoreRecordByMoreId,
+  getTypesById,
+} from "#db";
+import { getTypeInfoFn, setFilesById, setFilesByRecord, getFileUrl } from "./utils.js";
+
+// 添加多个账单记录
+router.post("/", async function (req, res) {
+  const {
+    name = "",
+    remark = "",
+    book_id = "",
+    start_time = "",
+    end_time = "",
+    total_fee = 0,
+    log_day = '',
+    type = "",
+    files = [],
+    userInfo = {},
+  } = req.body;
+
+  const more_id = `M_${uuidv4()}`;
+
+  const type_id = await getTypeInfoFn({ userInfo, book_id, type });
+
+  await addMoreRecord({
+    more_id,
+    book_id,
+    type_id: type_id,
+    author_id: userInfo.user_id,
+    total_fee,
+    name,
+    remark,
+    log_day,
+    start_time: shanghaiTimeFormat(start_time, "YYYY-MM-DD") + " 00:00:00",
+    end_time: shanghaiTimeFormat(end_time, "YYYY-MM-DD") + " 23:59:59",
+
+    create_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+    update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+  });
+
+  await setFilesByRecord({
+    files,
+    book_id,
+    more_id,
+    userInfo,
+  });
+
+  res.json({
+    code: 200,
+    data: {
+      record_id: more_id,
+    },
+  });
+});
+
+// 编辑多个账单记录
+router.put("/:more_id", async function (req, res) {
+  const more_id = req.params.more_id; // 获取 fileId 参数
+  const {
+    name = "",
+    remark = "",
+    book_id = "",
+    start_time = "",
+    end_time = "",
+    total_fee = 0,
+    type = "",
+    log_day = "",
+    files = [],
+    userInfo = {},
+  } = req.body;
+
+  // 更新附件信息
+  await setFilesById({
+    record_id: more_id,
+    userInfo,
+    book_id,
+    files,
+  });
+
+  // 更新类型
+  const typeId = await getTypeInfoFn({
+    userInfo,
+    book_id,
+    type,
+  });
+
+  // 更新数据内容
+  const recordInfo = await updataMoreRecord({
+    name,
+    more_id,
+    type_id: typeId,
+    author_id: userInfo.user_id,
+    total_fee: total_fee,
+    remark: remark,
+    log_day,
+    start_time: shanghaiTimeFormat(start_time, "YYYY-MM-DD") + " 00:00:00",
+    end_time: shanghaiTimeFormat(end_time, "YYYY-MM-DD") + " 23:59:59",
+    update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+  });
+
+  res.json({
+    code: 200,
+    data: recordInfo ? "" : "更新失败",
+  });
+});
+
+// 删除单个账单记录
+router.delete("/:more_id", async function (req, res) {
+  const more_id = req.params.more_id; // 获取 fileId 参数
+  const { userInfo = {} } = req.body;
+  // const recordInfo = await getRecordInfoById(more_id);
+
+  // 删除附件映射关系
+  const getAllFiles = await getFileByRecordId(more_id);
+  if (getAllFiles.length) {
+    await Promise.all(
+      getAllFiles.map((elm) => delFileByRecordId(more_id, elm.file_id))
+    );
+  }
+  // 删除record数据
+  await delMoreRecord(more_id, userInfo.user_id);
+
+  res.json({
+    code: 200,
+    data: "",
+  });
+});
+
+// 获取所有批量账单记录
+router.post("/list/:book_id", async function (req, res) {
+  const book_id = req.params.book_id; // 获取 fileId 参数
+  const { userInfo = {} } = req.body;
+  const moreRecordList = await getMoreRecordList(book_id, userInfo.user_id);
+
+  res.json({
+    code: 200,
+    data: moreRecordList.map((elm) => ({
+      name: elm.name,
+      more_id: elm.more_id,
+      remark: elm.remark,
+      total_fee: elm.total_fee,
+      create_time: shanghaiTimeFormat(elm.create_time),
+      start_time: shanghaiTimeFormat(elm.start_time),
+      end_time: shanghaiTimeFormat(elm.end_time),
+    })),
+  });
+});
+
+// 获取所有批量账单记录
+router.get("/:more_id", async function (req, res) {
+  const more_id = req.params.more_id; // 获取 fileId 参数
+  const { userInfo = {} } = req.body;
+  const files = await getFileByRecordId(more_id);
+  const moreRecordInfo = await getMoreRecordByMoreId(more_id, userInfo.user_id);
+
+  const typesRes = await getTypesById({
+    typeId: moreRecordInfo.type_id,
+    book_id: moreRecordInfo.book_id,
+    author_id: moreRecordInfo.author_id,
+  });
+  if (typesRes) {
+    moreRecordInfo.type = typesRes.name;
+  } else {
+    moreRecordInfo.type = "";
+  }
+
+
+  res.json({
+    code: 200,
+    data: {
+      type: moreRecordInfo.type,
+      name: moreRecordInfo.name,
+      more_id: moreRecordInfo.more_id,
+      remark: moreRecordInfo.remark,
+      total_fee: moreRecordInfo.total_fee,
+      log_day: moreRecordInfo.log_day,
+      create_time: shanghaiTimeFormat(moreRecordInfo.create_time),
+      start_time: shanghaiTimeFormat(moreRecordInfo.start_time),
+      end_time: shanghaiTimeFormat(moreRecordInfo.end_time),
+      files: files.map((elm) => getFileUrl(req, elm)),
+    },
+  });
+});
+
+export default router;

+ 118 - 0
epub_node/router/record/utils.js

@@ -0,0 +1,118 @@
+import {
+  record_insert,
+  record_update,
+  isType,
+  type_insert,
+  getRecordInfoById,
+  addFileByRecordId,
+  getFileByRecordId,
+  getRecordsInfoByTime,
+  getRecordsInfoByMonth,
+  getTypesById,
+  delFileByRecordId,
+  delByRecordId,
+} from "#db";
+
+import { shanghaiTime, shanghaiTimeFormat } from "#utils";
+
+export async function getTypeInfoFn({ userInfo, book_id, type }) {
+  console.log(1919191919, userInfo, book_id, type )
+  if (!type) {
+    return "";
+  }
+  // type 是否存在重复项,有就返回id,没有就新增 再返回id
+  const isAddType = await isType({
+    book_id,
+    author_id: userInfo.user_id,
+    type,
+  });
+
+  let typeInfo = {};
+  if (!isAddType) {
+    await type_insert({
+      book_id,
+      author_id: userInfo.user_id,
+      type,
+      create_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+      update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+    });
+    typeInfo = await isType({
+      book_id,
+      author_id: userInfo.user_id,
+      type,
+    });
+  } else {
+    typeInfo = { ...isAddType };
+  }
+  return Promise.resolve(typeInfo.id);
+}
+
+
+
+export async function setFilesByRecord(params) {
+  const {files = [], more_id, userInfo, book_id} = params
+  if(!files.length) return
+  await Promise.all(
+    files.map((file_id) =>
+      addFileByRecordId({
+        file_id,
+        record_id: more_id,
+        book_id,
+        author_id: userInfo.user_id,
+        create_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+        update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+      })
+    )
+  );
+}
+
+
+export async function setFilesById(params) {
+  const {record_id, userInfo, book_id, files = []} = params
+  const getAllFiles = await getFileByRecordId(record_id);
+  const getAllFilesIds = getAllFiles.map((elm) => elm.file_id);
+
+  const dellFilesInRecordFiles = getAllFiles.filter(
+    (elm) => files.indexOf(elm.file_id) < 0
+  );
+  const needAddFiles = files.filter(
+    (file_id) => getAllFilesIds.indexOf(file_id) < 0
+  );
+
+  if (dellFilesInRecordFiles.length) {
+    await Promise.all(
+      dellFilesInRecordFiles.map((elm) =>
+        delFileByRecordId(record_id, elm.file_id)
+      )
+    );
+  }
+  if (needAddFiles.length) {
+    // 更新附件信息
+    await Promise.all(
+      needAddFiles.map((file_id) =>
+        addFileByRecordId({
+          file_id,
+          record_id,
+          book_id,
+          author_id: userInfo.user_id,
+          create_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+          update_time: shanghaiTime().format("YYYY-MM-DD HH:mm:ss"),
+        })
+      )
+    );
+  }
+}
+
+
+export function getFileUrl(req,elm) {
+  // 获取请求的来源域名
+  const host = req.headers["host"]; // 主机名 + 端口
+  const origin = req.headers["referer"]; // 请求的来源域(适用于跨域)
+  if (`${origin}`.indexOf("3032") > -1 || `${host}`.indexOf("3000") > -1) {
+    return "http://localhost:3000" + `/api_files_${elm.file_id}`;
+  }
+  if (`${origin}`.indexOf("zs_interval") > -1) {
+    return `api_files_${elm.file_id}`;
+  }
+  return `${origin}api_files_${elm.file_id}`;
+}

+ 35 - 0
epub_node/router/types/index.js

@@ -0,0 +1,35 @@
+// 添加账本
+import express from "express";
+
+const router = express.Router();
+import { getTypesByUserId } from "#db";
+
+// middleware that is specific to this router
+router.use(function timeLog(req, res, next) {
+  next();
+});
+// define the home page route
+router.get("/", async function (req, res) {
+  const { userInfo = {} } = req.body;
+  // getTypesByUserId
+  const { book_id = "" } = req.query;
+
+  const typesRes = await getTypesByUserId({
+    book_id,
+    author_id: userInfo.user_id,
+  });
+
+  res.json({
+    code: 200,
+    data: typesRes.map((elm) => ({
+      name: elm.name,
+      id: elm.id,
+    })),
+  });
+});
+// define the about route
+router.get("/about", function (req, res) {
+  res.send("About types");
+});
+
+export default router;

+ 17 - 0
epub_node/types/db.d.ts

@@ -0,0 +1,17 @@
+declare module '#db' {
+  // 在这里定义模块的类型
+  // 例如,如果模块导出一个连接数据库的函数,可以这样定义:
+  export function connect(connectionString: string): Promise<void>;
+
+  // 如果模块导出一个数据库配置对象
+  export interface DatabaseConfig {
+    host: string;
+    port: number;
+    username: string;
+    password: string;
+  }
+
+  // 如果模块有默认导出
+  const dbConfig: DatabaseConfig;
+  export default dbConfig;
+}

+ 22 - 0
epub_node/types/environment.d.ts

@@ -0,0 +1,22 @@
+declare module '#environment' {
+  // 在这里定义模块的类型,比如:
+  export interface aesInfoConfig {
+    key: string;
+    iv: string;
+  }
+
+  export interface DbInfoConfig {
+    host: string,
+    port: number,
+    user: string,
+    password: string,
+    database: string,
+  }
+
+  // const config: Config;
+  export default  {
+    dbInfo: () => DbInfoConfig,
+    aes_info: () => aesInfoConfig,
+    privateKey: string
+  };
+}

+ 15 - 0
epub_node/types/utils.d.ts

@@ -0,0 +1,15 @@
+declare module '#utils' {
+  // 在这里定义模块的类型
+  // 例如,如果模块导出一个函数,可以这样定义:
+  export function someUtilityFunction(arg: string): number;
+
+  // 如果模块导出多个内容,可以使用命名导出
+  export interface UtilityOptions {
+    optionA: string;
+    optionB: number;
+  }
+
+  // 如果模块有默认导出
+  const defaultExport: UtilityOptions;
+  export default defaultExport;
+}

+ 33 - 0
epub_node/utils/authorization.js

@@ -0,0 +1,33 @@
+import jwt from "jsonwebtoken";
+import environment from "#environment";
+const secretKey = "secretKey";
+
+// 生成token
+export function generateToken(payload) {
+  const token =
+    "Bearer " +
+    jwt.sign(payload, secretKey, {
+      // expiresIn: 60 * 60 * 24 * 7 * 4 * 12, // 一年过期
+      // expiresIn: 60 * 60 * 24 * 7 * 4, // 一个月
+      expiresIn: 60 * 60 * 24 * 7, // 一周
+      // expiresIn: 60 * 60 * 24, // 一天
+      // expiresIn: 60 * 60, // 一个小时
+    }, environment.privateKey, { algorithm: 'RS256' });
+
+  return token;
+}
+
+// 验证token
+export function verifyToken(req, res, next) {
+  if(!req.headers.authorization) {
+    return res.status(401).json({ code: "401", msg: "token无效" });
+  }
+  const token = req.headers.authorization.split(" ")[1];
+  jwt.verify(token, secretKey, function (err, decoded) {
+    if (err) {
+      return res.status(401).json({ code: "401", msg: "token无效" });
+    }
+    req.body.userInfo = decoded
+    next();
+  });
+}

+ 105 - 0
epub_node/utils/createMD5.js

@@ -0,0 +1,105 @@
+import crypto from "crypto";
+import fs from "node:fs";
+import SparkMD5 from "spark-md5";
+import environment from "#environment";
+
+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);
+  });
+}
+
+
+// 加密
+export function aes_encrypt(msg) {
+  const aesInfo = environment.aes_info();
+  const cipher = crypto.createCipheriv("aes-256-cbc", aesInfo.key, aesInfo.iv);
+  // input encoding: utf8
+  // output encoding: hex
+  let encrypted = cipher.update(msg, "utf8", "hex");
+  encrypted += cipher.final("hex");
+  return encrypted;
+}
+
+// 解谜
+export function aes_decrypt(encrypted) {
+  const aesInfo = environment.aes_info();
+  const decipher = crypto.createDecipheriv(
+    "aes-256-cbc",
+    aesInfo.key,
+    aesInfo.iv,
+  );
+  let decrypted = decipher.update(encrypted, "hex", "utf8");
+  decrypted += decipher.final("utf8");
+  return decrypted;
+}

+ 22 - 0
epub_node/utils/dayTime.js

@@ -0,0 +1,22 @@
+import dayjs from "dayjs";
+import utc from 'dayjs/plugin/utc.js'; // 导入 UTC 插件
+import timezone from 'dayjs/plugin/timezone.js'; // 导入时区插件
+dayjs.extend(utc); // 加载 UTC 插件
+dayjs.extend(timezone); // 加载时区插件
+
+export function shanghaiTime () {
+  // 获取当前时间
+  const now = dayjs();
+
+  // 设置为上海时区
+  const shanghaiTime = now.tz('Asia/Shanghai');
+  return shanghaiTime
+}
+
+export function shanghaiTimeFormat (time, formatStr = 'YYYY-MM-DD HH:mm:ss') {
+  // 获取当前时间
+  const now = dayjs(time);
+  // 设置为上海时区
+  const shanghaiTime = now.tz('Asia/Shanghai');
+  return shanghaiTime.format(formatStr)
+}

+ 57 - 0
epub_node/utils/files.js

@@ -0,0 +1,57 @@
+import fs from "node:fs";
+import path from "path";
+/**
+ * 读取路径信息
+ * @param {string} path 路径
+ */
+export function getStat(path) {
+  return new Promise((resolve, reject) => {
+    fs.stat(path, (err, stats) => {
+      if (err) {
+        resolve(false);
+      } else {
+        resolve(stats);
+      }
+    })
+  })
+}
+
+/**
+  * 创建路径
+  * @param {string} dir 路径
+  */
+export function mkdir(dir) {
+  return new Promise((resolve, reject) => {
+    fs.mkdir(dir, err => {
+      if (err) {
+        resolve(false);
+      } else {
+        resolve(true);
+      }
+    })
+  })
+}
+
+/**
+  * 路径是否存在,不存在则创建
+  * @param {string} dir 路径
+  */
+export async function dirExists(dir) {
+  let isExists = await getStat(dir);
+  //如果该路径且不是文件,返回true
+  if (isExists && isExists.isDirectory()) {
+    return true;
+  } else if (isExists) {
+    //如果该路径存在但是文件,返回false
+    return false;
+  }
+  //如果该路径不存在,拿到上级路径
+  let tempDir = path.parse(dir).dir;
+  //递归判断,如果上级目录也不存在,则会代码会在此处继续循环执行,直到目录存在
+  let status = await dirExists(tempDir);
+  let mkdirStatus;
+  if (status) {
+    mkdirStatus = await mkdir(dir);
+  }
+  return mkdirStatus;
+}

+ 4 - 0
epub_node/utils/index.js

@@ -0,0 +1,4 @@
+export * from './createMD5.js'
+export * from './files.js'
+export * from './authorization.js'
+export * from './dayTime.js'

+ 1139 - 0
epub_node/yarn.lock

@@ -0,0 +1,1139 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@types/body-parser@*":
+  version "1.19.5"
+  resolved "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4"
+  integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==
+  dependencies:
+    "@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"
+  integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==
+  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"
+  integrity sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==
+  dependencies:
+    "@types/node" "*"
+    "@types/qs" "*"
+    "@types/range-parser" "*"
+    "@types/send" "*"
+
+"@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==
+  dependencies:
+    "@types/body-parser" "*"
+    "@types/express-serve-static-core" "^5.0.0"
+    "@types/qs" "*"
+    "@types/serve-static" "*"
+
+"@types/http-errors@*":
+  version "2.0.4"
+  resolved "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f"
+  integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==
+
+"@types/jsonwebtoken@^9.0.7":
+  version "9.0.7"
+  resolved "https://registry.npmmirror.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2"
+  integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==
+  dependencies:
+    "@types/node" "*"
+
+"@types/mime@^1":
+  version "1.3.5"
+  resolved "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
+  integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
+
+"@types/mysql@^2.15.26":
+  version "2.15.26"
+  resolved "https://registry.npmmirror.com/@types/mysql/-/mysql-2.15.26.tgz#f0de1484b9e2354d587e7d2bd17a873cc8300836"
+  integrity sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==
+  dependencies:
+    "@types/node" "*"
+
+"@types/node@*", "@types/node@^22.9.0":
+  version "22.9.0"
+  resolved "https://registry.npmmirror.com/@types/node/-/node-22.9.0.tgz#b7f16e5c3384788542c72dc3d561a7ceae2c0365"
+  integrity sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==
+  dependencies:
+    undici-types "~6.19.8"
+
+"@types/qs@*":
+  version "6.9.17"
+  resolved "https://registry.npmmirror.com/@types/qs/-/qs-6.9.17.tgz#fc560f60946d0aeff2f914eb41679659d3310e1a"
+  integrity sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==
+
+"@types/range-parser@*":
+  version "1.2.7"
+  resolved "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
+  integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
+
+"@types/send@*":
+  version "0.17.4"
+  resolved "https://registry.npmmirror.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a"
+  integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==
+  dependencies:
+    "@types/mime" "^1"
+    "@types/node" "*"
+
+"@types/serve-static@*":
+  version "1.15.7"
+  resolved "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714"
+  integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==
+  dependencies:
+    "@types/http-errors" "*"
+    "@types/node" "*"
+    "@types/send" "*"
+
+accepts@~1.3.8:
+  version "1.3.8"
+  resolved "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+  integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
+  dependencies:
+    mime-types "~2.1.34"
+    negotiator "0.6.3"
+
+adm-zip@^0.5.10:
+  version "0.5.16"
+  resolved "https://registry.npmmirror.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909"
+  integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==
+
+anymatch@~3.1.2:
+  version "3.1.3"
+  resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
+  integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+  dependencies:
+    normalize-path "^3.0.0"
+    picomatch "^2.0.4"
+
+array-flatten@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+  integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
+
+array-hyper-unique@^2.1.4:
+  version "2.1.6"
+  resolved "https://registry.npmmirror.com/array-hyper-unique/-/array-hyper-unique-2.1.6.tgz#429412fd63b7bd7c920f6cdbf60d1dd292855b2e"
+  integrity sha512-BdlHRqjKSYs88WFaVNVEc6Kv8ln/FdzCKPbcDPuWs4/EXkQFhnjc8TyR7hnPxRjcjo5LKOhUMGUWpAqRgeJvpA==
+  dependencies:
+    deep-eql "= 4.0.0"
+    lodash "^4.17.21"
+
+aws-ssl-profiles@^1.1.1:
+  version "1.1.2"
+  resolved "https://registry.npmmirror.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641"
+  integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==
+
+balanced-match@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+bignumber.js@9.0.0:
+  version "9.0.0"
+  resolved "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075"
+  integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==
+
+binary-extensions@^2.0.0:
+  version "2.3.0"
+  resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
+  integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
+
+bluebird@^3.7.2:
+  version "3.7.2"
+  resolved "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
+  integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
+
+body-parser@1.20.3, body-parser@^1.20.3:
+  version "1.20.3"
+  resolved "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
+  integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
+  dependencies:
+    bytes "3.1.2"
+    content-type "~1.0.5"
+    debug "2.6.9"
+    depd "2.0.0"
+    destroy "1.2.0"
+    http-errors "2.0.0"
+    iconv-lite "0.4.24"
+    on-finished "2.4.1"
+    qs "6.13.0"
+    raw-body "2.5.2"
+    type-is "~1.6.18"
+    unpipe "1.0.0"
+
+brace-expansion@^1.1.7:
+  version "1.1.11"
+  resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+  dependencies:
+    balanced-match "^1.0.0"
+    concat-map "0.0.1"
+
+braces@~3.0.2:
+  version "3.0.3"
+  resolved "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
+  integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+  dependencies:
+    fill-range "^7.1.1"
+
+buffer-equal-constant-time@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+  integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
+
+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"
+  integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
+call-bind@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
+  integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
+  dependencies:
+    es-define-property "^1.0.0"
+    es-errors "^1.3.0"
+    function-bind "^1.1.2"
+    get-intrinsic "^1.2.4"
+    set-function-length "^1.2.1"
+
+chokidar@^3.5.2:
+  version "3.6.0"
+  resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
+  integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
+  dependencies:
+    anymatch "~3.1.2"
+    braces "~3.0.2"
+    glob-parent "~5.1.2"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.6.0"
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+concat-map@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+  integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+content-disposition@0.5.4:
+  version "0.5.4"
+  resolved "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+  integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
+  dependencies:
+    safe-buffer "5.2.1"
+
+content-type@~1.0.4, content-type@~1.0.5:
+  version "1.0.5"
+  resolved "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
+  integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
+cookie-signature@1.0.6:
+  version "1.0.6"
+  resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+  integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
+
+cookie@0.7.1:
+  version "0.7.1"
+  resolved "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9"
+  integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
+
+core-util-is@~1.0.0:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+  integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
+cors@^2.8.5:
+  version "2.8.5"
+  resolved "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
+  integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
+  dependencies:
+    object-assign "^4"
+    vary "^1"
+
+crlf-normalize@^1.0.19:
+  version "1.0.20"
+  resolved "https://registry.npmmirror.com/crlf-normalize/-/crlf-normalize-1.0.20.tgz#0b3105d3de807bce8a7599113235d725fe9361a8"
+  integrity sha512-h/rBerTd3YHQGfv7tNT25mfhWvRq2BBLCZZ80GFarFxf6HQGbpW6iqDL3N+HBLpjLfAdcBXfWAzVlLfHkRUQBQ==
+  dependencies:
+    ts-type ">=2"
+
+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"
+  integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+  dependencies:
+    ms "2.0.0"
+
+debug@^4:
+  version "4.3.7"
+  resolved "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
+  integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+  dependencies:
+    ms "^2.1.3"
+
+"deep-eql@= 4.0.0":
+  version "4.0.0"
+  resolved "https://registry.npmmirror.com/deep-eql/-/deep-eql-4.0.0.tgz#c70af2713a4e18d9c2c1203ff9d11abbd51c8fbd"
+  integrity sha512-GxJC5MOg2KyQlv6WiUF/VAnMj4MWnYiXo4oLgeptOELVoknyErb4Z8+5F/IM/K4g9/80YzzatxmWcyRwUseH0A==
+  dependencies:
+    type-detect "^4.0.0"
+
+define-data-property@^1.1.4:
+  version "1.1.4"
+  resolved "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
+  integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+  dependencies:
+    es-define-property "^1.0.0"
+    es-errors "^1.3.0"
+    gopd "^1.0.1"
+
+denque@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
+  integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
+
+depd@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+  integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+destroy@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+  integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
+
+ecdsa-sig-formatter@1.0.11:
+  version "1.0.11"
+  resolved "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+  integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
+  dependencies:
+    safe-buffer "^5.0.1"
+
+ee-first@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+  integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+
+encodeurl@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+  integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
+
+encodeurl@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
+  integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+
+epub2@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.npmmirror.com/epub2/-/epub2-3.0.2.tgz#eee764e2b6b965d36c7713736bd49f6941d8c9c4"
+  integrity sha512-rhvpt27CV5MZfRetfNtdNwi3XcNg1Am0TwfveJkK8YWeHItHepQ8Js9J06v8XRIjuTrCW/NSGYMTy55Of7BfNQ==
+  dependencies:
+    adm-zip "^0.5.10"
+    array-hyper-unique "^2.1.4"
+    bluebird "^3.7.2"
+    crlf-normalize "^1.0.19"
+    tslib "^2.6.2"
+    xml2js "^0.6.2"
+
+es-define-property@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
+  integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
+  dependencies:
+    get-intrinsic "^1.2.4"
+
+es-errors@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+  integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
+escape-html@~1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+  integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+etag@~1.8.1:
+  version "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"
+  integrity sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==
+  dependencies:
+    accepts "~1.3.8"
+    array-flatten "1.1.1"
+    body-parser "1.20.3"
+    content-disposition "0.5.4"
+    content-type "~1.0.4"
+    cookie "0.7.1"
+    cookie-signature "1.0.6"
+    debug "2.6.9"
+    depd "2.0.0"
+    encodeurl "~2.0.0"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    finalhandler "1.3.1"
+    fresh "0.5.2"
+    http-errors "2.0.0"
+    merge-descriptors "1.0.3"
+    methods "~1.1.2"
+    on-finished "2.4.1"
+    parseurl "~1.3.3"
+    path-to-regexp "0.1.10"
+    proxy-addr "~2.0.7"
+    qs "6.13.0"
+    range-parser "~1.2.1"
+    safe-buffer "5.2.1"
+    send "0.19.0"
+    serve-static "1.16.2"
+    setprototypeof "1.2.0"
+    statuses "2.0.1"
+    type-is "~1.6.18"
+    utils-merge "1.0.1"
+    vary "~1.1.2"
+
+fill-range@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
+  integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+  dependencies:
+    to-regex-range "^5.0.1"
+
+finalhandler@1.3.1:
+  version "1.3.1"
+  resolved "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019"
+  integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
+  dependencies:
+    debug "2.6.9"
+    encodeurl "~2.0.0"
+    escape-html "~1.0.3"
+    on-finished "2.4.1"
+    parseurl "~1.3.3"
+    statuses "2.0.1"
+    unpipe "~1.0.0"
+
+forwarded@0.2.0:
+  version "0.2.0"
+  resolved "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
+  integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
+
+fresh@0.5.2:
+  version "0.5.2"
+  resolved "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+  integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
+
+fsevents@~2.3.2:
+  version "2.3.3"
+  resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+  integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+function-bind@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+  integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+generate-function@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.npmmirror.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f"
+  integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==
+  dependencies:
+    is-property "^1.0.2"
+
+get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
+  version "1.2.4"
+  resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
+  integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
+  dependencies:
+    es-errors "^1.3.0"
+    function-bind "^1.1.2"
+    has-proto "^1.0.1"
+    has-symbols "^1.0.3"
+    hasown "^2.0.0"
+
+glob-parent@~5.1.2:
+  version "5.1.2"
+  resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+  dependencies:
+    is-glob "^4.0.1"
+
+gopd@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+  integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+  dependencies:
+    get-intrinsic "^1.1.3"
+
+has-flag@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+  integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
+
+has-property-descriptors@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
+  integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
+  dependencies:
+    es-define-property "^1.0.0"
+
+has-proto@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
+  integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
+
+has-symbols@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+  integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+hasown@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+  integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+  dependencies:
+    function-bind "^1.1.2"
+
+http-errors@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+  integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+  dependencies:
+    depd "2.0.0"
+    inherits "2.0.4"
+    setprototypeof "1.2.0"
+    statuses "2.0.1"
+    toidentifier "1.0.1"
+
+iconv-lite@0.4.24:
+  version "0.4.24"
+  resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+  integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3"
+
+iconv-lite@^0.6.3:
+  version "0.6.3"
+  resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+  integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3.0.0"
+
+ignore-by-default@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
+  integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
+
+inherits@2.0.4, inherits@~2.0.3:
+  version "2.0.4"
+  resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ipaddr.js@1.9.1:
+  version "1.9.1"
+  resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+  integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+is-binary-path@~2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+  integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+  dependencies:
+    binary-extensions "^2.0.0"
+
+is-extglob@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+  integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-glob@^4.0.1, is-glob@~4.0.1:
+  version "4.0.3"
+  resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+  dependencies:
+    is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-property@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
+  integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==
+
+isarray@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+  integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+
+jsonwebtoken@^9.0.2:
+  version "9.0.2"
+  resolved "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
+  integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
+  dependencies:
+    jws "^3.2.2"
+    lodash.includes "^4.3.0"
+    lodash.isboolean "^3.0.3"
+    lodash.isinteger "^4.0.4"
+    lodash.isnumber "^3.0.3"
+    lodash.isplainobject "^4.0.6"
+    lodash.isstring "^4.0.1"
+    lodash.once "^4.0.0"
+    ms "^2.1.1"
+    semver "^7.5.4"
+
+jwa@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.npmmirror.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
+  integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
+  dependencies:
+    buffer-equal-constant-time "1.0.1"
+    ecdsa-sig-formatter "1.0.11"
+    safe-buffer "^5.0.1"
+
+jws@^3.2.2:
+  version "3.2.2"
+  resolved "https://registry.npmmirror.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
+  integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
+  dependencies:
+    jwa "^1.4.1"
+    safe-buffer "^5.0.1"
+
+lodash.includes@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
+  integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
+
+lodash.isboolean@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+  integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
+
+lodash.isinteger@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
+  integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
+
+lodash.isnumber@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
+  integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
+
+lodash.isplainobject@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+  integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
+
+lodash.isstring@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
+  integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
+
+lodash.once@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+  integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
+
+lodash@^4.17.21:
+  version "4.17.21"
+  resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+long@^5.2.1:
+  version "5.2.3"
+  resolved "https://registry.npmmirror.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
+  integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
+
+lru-cache@^7.14.1:
+  version "7.18.3"
+  resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89"
+  integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==
+
+lru.min@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.npmmirror.com/lru.min/-/lru.min-1.1.1.tgz#146e01e3a183fa7ba51049175de04667d5701f0e"
+  integrity sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==
+
+media-typer@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+  integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
+
+merge-descriptors@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
+  integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
+
+methods@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+  integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
+
+mime-db@1.52.0:
+  version "1.52.0"
+  resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+  integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@~2.1.24, mime-types@~2.1.34:
+  version "2.1.35"
+  resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+  integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+  dependencies:
+    mime-db "1.52.0"
+
+mime@1.6.0:
+  version "1.6.0"
+  resolved "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+  integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+minimatch@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+  dependencies:
+    brace-expansion "^1.1.7"
+
+ms@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+  integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
+ms@2.1.3, ms@^2.1.1, ms@^2.1.3:
+  version "2.1.3"
+  resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+mysql2@^3.11.4:
+  version "3.11.4"
+  resolved "https://registry.npmmirror.com/mysql2/-/mysql2-3.11.4.tgz#08658b6285adbace7d43b2eaa18efddb85f99501"
+  integrity sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==
+  dependencies:
+    aws-ssl-profiles "^1.1.1"
+    denque "^2.1.0"
+    generate-function "^2.3.1"
+    iconv-lite "^0.6.3"
+    long "^5.2.1"
+    lru.min "^1.0.0"
+    named-placeholders "^1.1.3"
+    seq-queue "^0.0.5"
+    sqlstring "^2.3.2"
+
+mysql@^2.18.1:
+  version "2.18.1"
+  resolved "https://registry.npmmirror.com/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717"
+  integrity sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==
+  dependencies:
+    bignumber.js "9.0.0"
+    readable-stream "2.3.7"
+    safe-buffer "5.1.2"
+    sqlstring "2.3.1"
+
+named-placeholders@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.npmmirror.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351"
+  integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==
+  dependencies:
+    lru-cache "^7.14.1"
+
+negotiator@0.6.3:
+  version "0.6.3"
+  resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+  integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
+nodemon@^3.1.7:
+  version "3.1.7"
+  resolved "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.7.tgz#07cb1f455f8bece6a499e0d72b5e029485521a54"
+  integrity sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==
+  dependencies:
+    chokidar "^3.5.2"
+    debug "^4"
+    ignore-by-default "^1.0.1"
+    minimatch "^3.1.2"
+    pstree.remy "^1.1.8"
+    semver "^7.5.3"
+    simple-update-notifier "^2.0.0"
+    supports-color "^5.5.0"
+    touch "^3.1.0"
+    undefsafe "^2.0.5"
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+object-assign@^4:
+  version "4.1.1"
+  resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+  integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+object-inspect@^1.13.1:
+  version "1.13.3"
+  resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.3.tgz#f14c183de51130243d6d18ae149375ff50ea488a"
+  integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==
+
+on-finished@2.4.1:
+  version "2.4.1"
+  resolved "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+  integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+  dependencies:
+    ee-first "1.1.1"
+
+parseurl@~1.3.3:
+  version "1.3.3"
+  resolved "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+  integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-to-regexp@0.1.10:
+  version "0.1.10"
+  resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
+  integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
+
+picomatch@^2.0.4, picomatch@^2.2.1:
+  version "2.3.1"
+  resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+  integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+process-nextick-args@~2.0.0:
+  version "2.0.1"
+  resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+proxy-addr@~2.0.7:
+  version "2.0.7"
+  resolved "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
+  integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
+  dependencies:
+    forwarded "0.2.0"
+    ipaddr.js "1.9.1"
+
+pstree.remy@^1.1.8:
+  version "1.1.8"
+  resolved "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
+  integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
+
+qs@6.13.0:
+  version "6.13.0"
+  resolved "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
+  integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
+  dependencies:
+    side-channel "^1.0.6"
+
+range-parser@~1.2.1:
+  version "1.2.1"
+  resolved "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+  integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+raw-body@2.5.2:
+  version "2.5.2"
+  resolved "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
+  integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
+  dependencies:
+    bytes "3.1.2"
+    http-errors "2.0.0"
+    iconv-lite "0.4.24"
+    unpipe "1.0.0"
+
+readable-stream@2.3.7:
+  version "2.3.7"
+  resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+  integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~2.0.0"
+    safe-buffer "~5.1.1"
+    string_decoder "~1.1.1"
+    util-deprecate "~1.0.1"
+
+readdirp@~3.6.0:
+  version "3.6.0"
+  resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+  integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+  dependencies:
+    picomatch "^2.2.1"
+
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+  version "5.1.2"
+  resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+safe-buffer@5.2.1, safe-buffer@^5.0.1:
+  version "5.2.1"
+  resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+  integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
+  version "2.1.2"
+  resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+  integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+sax@>=0.6.0:
+  version "1.4.1"
+  resolved "https://registry.npmmirror.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
+  integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
+
+semver@^7.5.3, semver@^7.5.4:
+  version "7.6.3"
+  resolved "https://registry.npmmirror.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+  integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
+send@0.19.0:
+  version "0.19.0"
+  resolved "https://registry.npmmirror.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
+  integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
+  dependencies:
+    debug "2.6.9"
+    depd "2.0.0"
+    destroy "1.2.0"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    fresh "0.5.2"
+    http-errors "2.0.0"
+    mime "1.6.0"
+    ms "2.1.3"
+    on-finished "2.4.1"
+    range-parser "~1.2.1"
+    statuses "2.0.1"
+
+seq-queue@^0.0.5:
+  version "0.0.5"
+  resolved "https://registry.npmmirror.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e"
+  integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==
+
+serve-static@1.16.2:
+  version "1.16.2"
+  resolved "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296"
+  integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
+  dependencies:
+    encodeurl "~2.0.0"
+    escape-html "~1.0.3"
+    parseurl "~1.3.3"
+    send "0.19.0"
+
+set-function-length@^1.2.1:
+  version "1.2.2"
+  resolved "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
+  integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
+  dependencies:
+    define-data-property "^1.1.4"
+    es-errors "^1.3.0"
+    function-bind "^1.1.2"
+    get-intrinsic "^1.2.4"
+    gopd "^1.0.1"
+    has-property-descriptors "^1.0.2"
+
+setprototypeof@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+  integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+side-channel@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
+  integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
+  dependencies:
+    call-bind "^1.0.7"
+    es-errors "^1.3.0"
+    get-intrinsic "^1.2.4"
+    object-inspect "^1.13.1"
+
+simple-update-notifier@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb"
+  integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==
+  dependencies:
+    semver "^7.5.3"
+
+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"
+  integrity sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==
+
+sqlstring@^2.3.2:
+  version "2.3.3"
+  resolved "https://registry.npmmirror.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c"
+  integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==
+
+statuses@2.0.1:
+  version "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"
+  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+  dependencies:
+    safe-buffer "~5.1.0"
+
+supports-color@^5.5.0:
+  version "5.5.0"
+  resolved "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+  integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+  dependencies:
+    has-flag "^3.0.0"
+
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+  dependencies:
+    is-number "^7.0.0"
+
+toidentifier@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+  integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
+touch@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.npmmirror.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694"
+  integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==
+
+ts-type@>=2:
+  version "3.0.1"
+  resolved "https://registry.npmmirror.com/ts-type/-/ts-type-3.0.1.tgz#b52e7623065e0beb43c77c426347d85cf81dff84"
+  integrity sha512-cleRydCkBGBFQ4KAvLH0ARIkciduS745prkGVVxPGvcRGhMMoSJUB7gNR1ByKhFTEYrYRg2CsMRGYnqp+6op+g==
+  dependencies:
+    "@types/node" "*"
+    tslib ">=2"
+    typedarray-dts "^1.0.0"
+
+tslib@>=2, tslib@^2.6.2:
+  version "2.8.1"
+  resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
+  integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
+type-detect@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.npmmirror.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c"
+  integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==
+
+type-is@~1.6.18:
+  version "1.6.18"
+  resolved "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+  integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+  dependencies:
+    media-typer "0.3.0"
+    mime-types "~2.1.24"
+
+typedarray-dts@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/typedarray-dts/-/typedarray-dts-1.0.0.tgz#9dec9811386dbfba964c295c2606cf9a6b982d06"
+  integrity sha512-Ka0DBegjuV9IPYFT1h0Qqk5U4pccebNIJCGl8C5uU7xtOs+jpJvKGAY4fHGK25hTmXZOEUl9Cnsg5cS6K/b5DA==
+
+undefsafe@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
+  integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
+
+undici-types@~6.19.8:
+  version "6.19.8"
+  resolved "https://registry.npmmirror.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
+  integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
+
+unpipe@1.0.0, unpipe@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+  integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+
+util-deprecate@~1.0.1:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+  integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+utils-merge@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+  integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
+
+uuid@^11.0.2:
+  version "11.0.3"
+  resolved "https://registry.npmmirror.com/uuid/-/uuid-11.0.3.tgz#248451cac9d1a4a4128033e765d137e2b2c49a3d"
+  integrity sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==
+
+vary@^1, vary@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+  integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+
+xml2js@^0.6.2:
+  version "0.6.2"
+  resolved "https://registry.npmmirror.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499"
+  integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==
+  dependencies:
+    sax ">=0.6.0"
+    xmlbuilder "~11.0.0"
+
+xmlbuilder@~11.0.0:
+  version "11.0.1"
+  resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
+  integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==