shaogen1995 пре 1 месец
родитељ
комит
4c97f7f4c9

+ 25 - 1
README.md

@@ -1 +1,25 @@
-测试服务器后端代码存放位置:
+测试服务器后端代码存放位置:腾讯云-四维时代-项目测试服务器-111.230.233.212/data/data/node_porject/museum_anhui_hefei_guojia_tongbufusheshiyanshi
+测试服务器端口:192.168.10.10:8500
+测试服务器 pm2 启动名称:pm2 start app.js --anHui_guoJiaTongBuFuShe
+测服务器域名:https://sit-tongbulab.4dage.com
+
+1:常用管理命令
+查看所有运行中的应用状态:pm2 list
+
+查看实时日志:pm2 logs 名字
+
+重启应用(例如代码更新后):pm2 restart 名字
+
+停止应用:pm2 stop 名字
+
+从 PM2 列表中删除应用:pm2 delete 名字
+
+2.进阶设置:开机自启
+
+# 设置 PM2 自启动脚本
+
+pm2 startup
+
+# 保存当前应用列表
+
+pm2 save

+ 6 - 5
src/app.ts

@@ -18,6 +18,9 @@ app.use(express.urlencoded());
 // 跨域中间件
 app.use(cors());
 
+// 静态文件访问
+app.use('/uploads', express.static(path.join(process.cwd(), 'uploads')));
+
 // 所有接口都要做一次验证
 app.use(proofZhong);
 
@@ -34,8 +37,6 @@ else {
 
   app.use(morgan('combined', { stream: accessLogStream }));
 }
-// 静态文件访问
-app.use('/uploads', express.static(path.join(process.cwd(), 'uploads')));
 
 // 导入路由
 app.use('/api/v1', router);
@@ -43,7 +44,7 @@ app.use('/api/v1', router);
 // 最后,注册错误处理中间件
 app.use(errorHandler);
 
-app.listen(6789, () => {
-  console.log('Run http://localhost:6789');
-  console.log(`Run http://${getLocalIP()}:6789`);
+app.listen(8500, () => {
+  console.log('Run http://localhost:8500');
+  console.log(`Run http://${getLocalIP()}:8500`);
 });

+ 4 - 1
src/config/config.default.ts

@@ -19,7 +19,10 @@ const envUrl =
 
 // 生产环境地址
 const buildUrl =
-  'mongodb://root:4Dkk2021testproject%25@111.230.233.212:27017/anHui_guoJiaTongBuFuShe?authSource=admin';
+  'mongodb://root:4Dkk2021testproject%25@127.0.0.1:27017/anHui_guoJiaTongBuFuShe?authSource=admin';
+
+// 上传静态文件的目录
+export const upStaticFileUrl = '/data/data/museum_anhui_hefei_guojia_tongbufusheshiyanshi_data';
 
 // 服务器链接地址
 export const mongodbUrl = isEnv ? envUrl : buildUrl;

+ 7 - 12
src/controller/fileController.ts

@@ -9,7 +9,7 @@ const file = {
   upload: async (req: any, res: any) => {
     if (!req.file) return resSend(res, 404, '请选择要上传的文件');
 
-    const { isDb, type = 'other' } = req.body;
+    const { isDb, myType } = req.body;
 
     const upPath = req.query.upPath || 'default';
 
@@ -20,8 +20,10 @@ const file = {
     let message = '文件上传成功';
 
     let result: any = {
+      originalName: file.originalname,
       originalUrl: `/uploads/${upPath}/${path.basename(file.path)}`,
-      compressedUrl: null,
+      size: file.size,
+      compressedUrl: '',
     };
 
     // 如果是图片且大于500KB,进行压缩
@@ -45,23 +47,16 @@ const file = {
     // 如果需要存储到数据库
     if (isDb === 'true') {
       const fileRecord = new Files({
+        ...result,
         filename: path.basename(file.filename),
-        originalName: file.originalname,
         mimetype: file.mimetype,
-        type,
-        size: file.size,
-        compressedSize: result.compressedSize || null,
-        path: file.path,
-        compressedPath: result.compressedUrl
-          ? path.join(path.dirname(file.path), path.basename(result.compressedUrl))
-          : null,
+        type: myType || 'other',
         updateTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
       });
-
       await fileRecord.save();
       result._id = fileRecord._id;
       result.type = fileRecord.type;
-      result.originalName = fileRecord.originalName;
+      result.updateTime = fileRecord.updateTime;
     }
 
     // 记录API描述(根据您之前的代码风格)

+ 95 - 1
src/controller/issueController.ts

@@ -1,5 +1,5 @@
 import dayjs from 'dayjs';
-import { Dict } from '../model/index.js';
+import { Dict, Video } from '../model/index.js';
 import resSend from '../util/resSend.js';
 import { passWordJia } from '../util/pass.js';
 
@@ -29,6 +29,100 @@ const issue = {
     await introObj.save();
     return resSend(res, 0, '编辑项目简介成功');
   },
+  getVideoList: async (req: any, res: any) => {
+    req.apiDescription = '内容发布-获取视频展示列表';
+    const { pageNum = 1, pageSize = 10, searchKey = '' } = req.body;
+
+    // 构建查询条件
+    const query: any = {};
+
+    if (searchKey) {
+      // 使用正则表达式实现模糊查询,'i'表示不区分大小写
+      query.name = { $regex: searchKey, $options: 'i' };
+    }
+
+    // 计算跳过的文档数量
+    const skip = (pageNum - 1) * pageSize;
+
+    // 修改排序逻辑:先按sort字段降序,再按updateTime字段降序
+    const sortCondition: any = { sort: -1, updateTime: -1 };
+
+    // 并行执行:获取总条数和查询当前页数据
+    const [total, data] = await Promise.all([
+      // 获取满足条件的总记录数
+      Video.countDocuments(query),
+      // 查询当前页数据
+      Video.find(query).skip(skip).limit(parseInt(pageSize)).sort(sortCondition), // 按sort字段降序,数字越大越靠前;相同则按updateTime降序
+    ]);
+
+    // 计算总页数
+    const totalPages = Math.ceil(total / pageSize);
+    return resSend(res, 0, '获取视频展示列表成功', {
+      list: data,
+      pageNum: parseInt(pageNum),
+      pageSize: parseInt(pageSize),
+      total,
+      totalPages,
+    });
+  },
+  saveVideo: async (req: any, res: any) => {
+    if (req.body._id) {
+      // 编辑视频展示
+      // 检查数据是否存在
+      const existingVideo: any = await Video.findById(req.body._id);
+      if (!existingVideo) return resSend(res, 404, '数据不存在');
+      // 更新字段
+
+      // 过滤一些字段
+      const filetStr: string[] = [];
+
+      Object.keys(req.body).forEach((key) => {
+        if (key !== '_id' && req.body[key] !== undefined) {
+          if (!filetStr.includes(key)) {
+            existingVideo[key] = req.body[key];
+          }
+        }
+      });
+
+      existingVideo.updateTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
+      const updatedUser = await existingVideo.save();
+
+      const videoObj = updatedUser.toObject();
+      req.apiDescription = `内容发布-编辑视频展示-${videoObj.name}`;
+      return resSend(res, 0, '编辑视频展示成功', videoObj);
+    } else {
+      const userModel = new Video(req.body);
+      // 新增视频展示
+      // 保存数据到数据库
+      const dbBack = await userModel.save();
+      // 将文档转换为普通对象
+      const videoObj = dbBack.toObject();
+      req.apiDescription = `内容发布-新增视频展示-${videoObj.name}`;
+      return resSend(res, 0, '新增视频展示成功', videoObj);
+    }
+  },
+  delVideo: async (req: any, res: any) => {
+    const { _id } = req.params; // 从URL参数中获取ID
+    // 1. 根据ID查找数据
+    const info = await Video.findById(_id);
+    if (!info) return resSend(res, 404, '_id错误或数据已被删除');
+
+    const deletedInfo: any = await Video.findByIdAndDelete(_id);
+    req.apiDescription = `内容发布-删除视频展示数据-${deletedInfo.name}`;
+    return resSend(res, 0, '删除视频展示数据成功');
+  },
+  getVideoInfo: async (req: any, res: any) => {
+    const { _id } = req.params;
+
+    if (!_id) return resSend(res, 400, '_id不能为空');
+
+    // 根据ID查询信息
+    const info = await Video.findById(_id);
+
+    if (!info) return resSend(res, 404, '_id错误或数据已被删除');
+    req.apiDescription = `内容发布-获取视频展示详情-${info.name}`;
+    return resSend(res, 0, '获取视频展示详情成功', info);
+  },
 };
 
 export default issue;

+ 11 - 2
src/middleware/fileUpload.ts

@@ -1,15 +1,24 @@
 import multer from 'multer';
 import path from 'path';
 import fs from 'fs';
+import { isEnv, upStaticFileUrl } from '../config/config.default.js';
 
 // 配置 Multer 存储策略
 const storage = multer.diskStorage({
   destination: (req: any, file, cb) => {
+    // 在文件过滤阶段就修复编码问题
+    file.originalname = Buffer.from(file.originalname, 'latin1').toString('utf8');
+
     const upPath = req.query.upPath || 'default';
 
     // 设置默认路径,如果 upPath 不存在
-    const baseDir = 'uploads';
-    const targetDir = upPath ? path.join(baseDir, upPath) : path.join(baseDir, 'default');
+    let baseDir = '';
+
+    // 针对 本地和 生产 生成不同路径的静态文件
+    if (isEnv) baseDir = 'uploads';
+    else baseDir = upStaticFileUrl + '/uploads';
+
+    const targetDir = path.join(baseDir, upPath || 'default');
 
     // 确保目录存在
     if (!fs.existsSync(targetDir)) {

+ 17 - 0
src/middleware/validator/issueValidator.ts

@@ -0,0 +1,17 @@
+import { body } from 'express-validator';
+import errorBack from './errorBack.js';
+
+// 新增/编辑视频展示
+export const addVideoVali = errorBack([
+  body('name')
+    .notEmpty()
+    .withMessage('标题不能为空')
+    .bail()
+    .isLength({ max: 30 })
+    .withMessage('长度最大为30个字符'),
+  body('releaseDate').notEmpty().withMessage('发布日期不能为空'),
+  body('type').notEmpty().withMessage('类别不能为空'),
+  body('cover').notEmpty().withMessage('封面地址不能为空'),
+  body('videoUrl').notEmpty().withMessage('视频地址不能为空'),
+  body('videoName').notEmpty().withMessage('视频名称不能为空'),
+]);

+ 4 - 3
src/model/fileModel.ts

@@ -22,16 +22,17 @@ const fileModel = new mongoose.Schema({
   compressedSize: {
     type: Number,
   },
-  path: {
+  originalUrl: {
     type: String,
     required: true,
   },
-  compressedPath: {
+  compressedUrl: {
     type: String,
+    default: '',
   },
 
   updateTime: {
-    type: Date,
+    type: String,
     default: dayjs().format('YYYY-MM-DD HH:mm:ss'),
   },
 });

+ 2 - 0
src/model/index.ts

@@ -4,6 +4,7 @@ import logSchema from './logModel.js';
 import { mongodbUrl } from '../config/config.default.js';
 import dictModel from './dictModel.js';
 import fileModel from './fileModel.js';
+import VideoSchema from './videlModel.js';
 
 const main = async () => {
   await mongoose.connect(mongodbUrl);
@@ -21,3 +22,4 @@ export const User = mongoose.model('User', userSchema);
 export const Log = mongoose.model('Log', logSchema);
 export const Dict = mongoose.model('Dict', dictModel);
 export const Files = mongoose.model('File', fileModel);
+export const Video = mongoose.model('video', VideoSchema);

+ 54 - 0
src/model/videlModel.ts

@@ -0,0 +1,54 @@
+import dayjs from 'dayjs';
+import mongoose from 'mongoose';
+
+const VideoSchema = new mongoose.Schema({
+  // 标题
+  name: {
+    type: String,
+    require: true,
+  },
+  // 发布日期
+  releaseDate: {
+    type: String,
+    require: true,
+  },
+  // 类别
+  type: {
+    type: String,
+    require: true,
+  },
+  // 封面图地址(原图)
+  cover: {
+    type: String,
+    require: true,
+  },
+  // 封面图地址(压缩)
+  coverSmall: {
+    type: String,
+    default: '',
+  },
+  // 视频地址
+  videoUrl: {
+    type: String,
+    require: true,
+  },
+  videoName: {
+    type: String,
+    require: true,
+  },
+  // 排序值
+  sort: {
+    type: Number,
+    default: 999,
+  },
+  createTime: {
+    type: String,
+    default: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  },
+  updateTime: {
+    type: String,
+    default: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  },
+});
+
+export default VideoSchema;

+ 12 - 25
src/router/issue.ts

@@ -1,9 +1,11 @@
 import express from 'express';
+import { issue } from '../controller/inedx.js';
 // 检测token的中间件
 import { verifyToken } from '../middleware/jwt.js';
 // 记录日志的中间件
 import requestLogger from '../middleware/requestLogger.js';
-import { issue } from '../controller/inedx.js';
+//  校验字段的中间件
+import { addVideoVali } from '../middleware/validator/issueValidator.js';
 
 const issueRouter = express.Router();
 
@@ -11,29 +13,14 @@ issueRouter
   // 获取项目简介
   .get('/getIntro', verifyToken, issue.getIntro)
   // 编辑项目简介
-  .post('/setIntro', verifyToken, requestLogger, issue.setIntro);
-
-// //获取验证码
-// .get('/getCode', user.getCode)
-// //用户登录
-// .post('/login', loginVali, requestLogger, user.login)
-// //登录- 调试获取token专用
-// .post('/loginText', user.loginText)
-// // 新增/编辑用户
-// .post('/addOrEdit', addUserVali, verifyToken, isAdminZhong, requestLogger, user.addOrEdit)
-// // 获取用户列表
-// .post('/list', verifyToken, isAdminZhong, user.list)
-// // 获取日志列表
-// .post('/log', verifyToken, isAdminZhong, user.log)
-// // 通过id重置用户密码
-// .get('/resetPassWord/:_id', verifyToken, isAdminZhong, requestLogger, user.resetPassWord)
-// // 修改自己的密码
-// .post('/editPassWord', editPassWord, verifyToken, requestLogger, user.editPassWord)
-// // 删除除了isAdmin为1的用户
-// .get('/del/:_id', verifyToken, isAdminZhong, requestLogger, user.del)
-// // 设置用户页面权限
-// .post('/setAuthority', verifyToken, isAdminZhong, requestLogger, user.setAuthority)
-// // 获取用户详情
-// .get('/getInfo/:_id', verifyToken, user.getInfo);
+  .post('/setIntro', verifyToken, requestLogger, issue.setIntro)
+  // 获取视频展示列表
+  .post('/getVideoList', verifyToken, issue.getVideoList)
+  // 新增/编辑视频展示列表
+  .post('/saveVideo', verifyToken, addVideoVali, requestLogger, issue.saveVideo)
+  // 删除视频展示
+  .get('/delVideo/:_id', verifyToken, requestLogger, issue.delVideo)
+  // 通过id获取视频展示详情
+  .get('/getVideoInfo/:_id', verifyToken, issue.getVideoInfo);
 
 export default issueRouter;

+ 5 - 1
src/router/show.ts

@@ -7,6 +7,10 @@ showRouter
   // 获取所有接口的凭证
   .get('/getProof', issue.getProof)
   // 获取项目简介
-  .get('/getIntro', issue.getIntro);
+  .get('/getIntro', issue.getIntro)
+  // 获取视频展示列表
+  .post('/getVideoList', issue.getVideoList)
+  // 通过id获取视频展示详情
+  .get('/getVideoInfo/:_id', issue.getVideoInfo);
 
 export default showRouter;

+ 2 - 2
src/router/user.ts

@@ -19,7 +19,7 @@ userRouter
   //登录- 调试获取token专用
   .post('/loginText', user.loginText)
   // 新增/编辑用户
-  .post('/addOrEdit', addUserVali, verifyToken, isAdminZhong, requestLogger, user.addOrEdit)
+  .post('/addOrEdit', verifyToken, addUserVali, isAdminZhong, requestLogger, user.addOrEdit)
   // 获取用户列表
   .post('/list', verifyToken, isAdminZhong, user.list)
   // 获取日志列表
@@ -27,7 +27,7 @@ userRouter
   // 通过id重置用户密码
   .get('/resetPassWord/:_id', verifyToken, isAdminZhong, requestLogger, user.resetPassWord)
   // 修改自己的密码
-  .post('/editPassWord', editPassWord, verifyToken, requestLogger, user.editPassWord)
+  .post('/editPassWord', verifyToken, editPassWord, requestLogger, user.editPassWord)
   // 删除除了isAdmin为1的用户
   .get('/del/:_id', verifyToken, isAdminZhong, requestLogger, user.del)
   // 设置用户页面权限