news 2026/9/16 16:04:29

Vue3+Node.js+MySQL校园资产管理系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue3+Node.js+MySQL校园资产管理系统实战

简介:这是一套基于Vue+Node.js+MySQL技术栈开发的校园资产管理系统全栈源码,面向Web全栈初学者与高校课程设计者,解决校园场景下资产登记、借用归还、分类统计等核心管理需求。资源共61个文件,含25个JavaScript后端逻辑与API封装文件、15个Vue组件实现前端页面与交互、1个SQL脚本用于初始化MySQL数据库表结构(含资产、借用记录等关键表),以及配置文件、样式文件和静态资源,整体压缩包仅532KB,轻量易部署。已有499人学习下载,适合通过真实项目掌握前后端协同开发流程。读者可直接运行并深入理解Vue组件化架构、Express/Koa RESTful接口设计、MySQL关系建模与CRUD实践,同时参考清晰的目录划分——前端src下含views路由视图与api请求封装,后端server中包含routes、models、controllers分层逻辑,database目录提供开箱即用的建库脚本,助力快速上手与二次开发。

1. 这不是又一个“学生练手项目”:Vue+Node.js+MySQL 校园资产管理系统,解决的是真实场景下的台账混乱、责任不清、盘点低效三大痛点

高校实验室设备、办公家具、多媒体教具、IT 资产等分散在各院系、中心、办公室,常出现“谁领的记不清”“坏了找不着维修记录”“年底盘点靠Excel人工对表三天”“报废流程走完系统里还是‘在用’状态”。这个基于 Vue + Node.js + MySQL 的校园资产管理系统源码,不是教学 Demo,而是能直接部署进二级学院信息科、国资办或后勤处的轻量级生产级方案。它用 Vue 3 Composition API 构建响应式前端,Node.js(Express)提供 RESTful 接口与业务逻辑层,MySQL 8.0 设计符合资产管理规范的关系模型(含资产分类、使用部门、责任人、状态流转、维保周期、出入库日志)。源码结构清晰,包含完整数据库初始化脚本、前后端分离部署说明、基础权限控制(管理员/资产员/普通用户三级),且所有接口均通过 Postman 验证。适合 IT 部门快速落地、外包团队二次开发,或计算机专业学生完成课程设计、毕设时直接复用核心模块——关键在于,它把“资产全生命周期管理”这个抽象概念,拆解成了可配置、可审计、可追溯的具体字段和操作流。

2. 搭建环境:从零开始配置 Vue 3 前端 + Express 后端 + MySQL 8.0 数据库三件套

2.1 前端环境:Vue 3 + Vite + Element Plus 快速初始化

校园资产管理系统前端需兼顾数据表格密集展示与表单录入效率,Vue 3 的响应式性能和组合式 API 更利于管理复杂状态(如资产状态切换、多级分类联动)。Vite 作为现代构建工具,热更新速度远超传统 Webpack,尤其适合频繁修改资产字段的开发阶段。Element Plus 提供开箱即用的 Table、Form、Dialog 组件,避免重复造轮子。

# 在项目根目录下创建前端子目录 mkdir campus-asset-fe && cd campus-asset-fe # 使用 Vite 创建 Vue 3 项目(选择 npm + TypeScript + Vue) npm create vite@latest . -- --template vue-ts # 安装依赖(含 Element Plus 和 axios) npm install element-plus @element-plus/icons-vue axios pinia # 启动开发服务器 npm run dev

提示npm create vite@latest是 Vue 官方推荐的初始化方式,比vue create更轻量。@element-plus/icons-vue提供 SVG 图标,避免字体图标加载延迟;pinia替代 Vuex,是 Vue 3 官方状态管理库,API 更简洁。

安装完成后,需在src/main.ts中全局注册 Element Plus:

// src/main.ts import { createApp } from 'vue' import { createPinia } from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' const app = createApp(App) app.use(createPinia()) app.use(ElementPlus) // 全局注册 app.mount('#app')

2.2 后端环境:Express + Sequelize ORM + MySQL 8.0 连接池配置

后端采用 Express 框架,因其中间件生态成熟,便于集成 JWT 鉴权、日志、错误处理等通用能力。Sequelize 是 Node.js 生态最稳定的 ORM,支持 MySQL 8.0 的 JSON 字段、窗口函数等特性,且能自动生成迁移脚本,保障数据库结构演进可控。

# 在项目根目录下创建后端子目录 cd .. && mkdir campus-asset-be && cd campus-asset-be # 初始化 npm 包 npm init -y # 安装核心依赖 npm install express sequelize mysql2 bcryptjs jsonwebtoken dotenv cors morgan # 安装开发依赖(用于 TypeScript 编译) npm install -D typescript ts-node @types/node @types/express @types/cors @types/morgan

创建tsconfig.json并配置:

{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020", "DOM"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "moduleResolution": "node" }, "include": ["src/**/*"], "exclude": ["node_modules"] }

2.3 数据库:MySQL 8.0 安装与校园资产核心表结构设计

MySQL 8.0 是当前主流稳定版本,其utf8mb4_0900_as_cs排序规则支持完整 Unicode(含 emoji),避免资产名称含特殊符号时报错。校园资产管理系统数据库需满足以下核心约束:

  • 资产主表(assets)必须有唯一资产编码(asset_code),不可为空;
  • 状态字段(status)采用枚举值('in_stock', 'in_use', 'under_repair', 'scraped'),禁止自由文本;
  • 所有时间戳使用DATETIME类型,配合DEFAULT CURRENT_TIMESTAMP自动填充创建时间。

执行以下 SQL 创建数据库及初始表(保存为init_db.sql):

-- 创建数据库(字符集强制指定) CREATE DATABASE IF NOT EXISTS campus_asset_db CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_as_cs; USE campus_asset_db; -- 资产分类表(一级分类:仪器设备、家具、IT设备;二级分类:示波器、办公桌、笔记本电脑) CREATE TABLE asset_categories ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT '分类名称', parent_id INT DEFAULT NULL COMMENT '父分类ID,NULL为一级分类', level TINYINT NOT NULL DEFAULT 1 COMMENT '层级:1=一级,2=二级', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_parent (parent_id) ); -- 资产主表 CREATE TABLE assets ( id INT PRIMARY KEY AUTO_INCREMENT, asset_code VARCHAR(32) NOT NULL UNIQUE COMMENT '资产唯一编码,如:LAB-2024-0001', name VARCHAR(100) NOT NULL COMMENT '资产名称', category_id INT NOT NULL COMMENT '所属分类ID', department VARCHAR(50) NOT NULL COMMENT '使用部门', responsible_person VARCHAR(20) NOT NULL COMMENT '责任人姓名', status ENUM('in_stock', 'in_use', 'under_repair', 'scraped') NOT NULL DEFAULT 'in_stock', purchase_date DATE COMMENT '采购日期', warranty_months INT DEFAULT 12 COMMENT '保修月数', description TEXT COMMENT '备注说明', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (category_id) REFERENCES asset_categories(id) ON DELETE RESTRICT ); -- 资产操作日志表(记录每一次状态变更、责任人调整、维修登记) CREATE TABLE asset_logs ( id BIGINT PRIMARY KEY AUTO_INCREMENT, asset_id INT NOT NULL COMMENT '关联资产ID', operator VARCHAR(20) NOT NULL COMMENT '操作人', action_type ENUM('create', 'update_status', 'assign_person', 'repair_log', 'scrap') NOT NULL, before_data JSON COMMENT '变更前快照(JSON格式)', after_data JSON COMMENT '变更后快照', remark TEXT COMMENT '操作说明', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_asset (asset_id), INDEX idx_time (created_at) );

注意ON DELETE RESTRICT确保删除分类前必须先清空该分类下所有资产,防止数据孤儿化;JSON类型字段用于灵活存储快照,避免为每种操作建单独字段。

3. 核心功能实现:资产增删改查、状态流转、出入库日志的完整代码链路

3.1 后端:Express 路由与 Sequelize 模型定义

首先定义Asset模型(src/models/asset.model.ts):

// src/models/asset.model.ts import { DataTypes, Model, Optional } from 'sequelize'; import { sequelize } from '../config/db.config'; interface AssetAttributes { id: number; asset_code: string; name: string; category_id: number; department: string; responsible_person: string; status: 'in_stock' | 'in_use' | 'under_repair' | 'scraped'; purchase_date?: Date; warranty_months: number; description?: string; } export interface AssetCreationAttributes extends Optional<AssetAttributes, 'id'> {} export class Asset extends Model<AssetAttributes, AssetCreationAttributes> implements AssetAttributes { public id!: number; public asset_code!: string; public name!: string; public category_id!: number; public department!: string; public responsible_person!: string; public status!: 'in_stock' | 'in_use' | 'under_repair' | 'scraped'; public purchase_date?: Date; public warranty_months!: number; public description?: string; public readonly created_at!: Date; public readonly updated_at!: Date; } Asset.init( { id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true, }, asset_code: { type: DataTypes.STRING(32), allowNull: false, unique: true, validate: { notEmpty: true }, }, name: { type: DataTypes.STRING(100), allowNull: false, validate: { notEmpty: true }, }, category_id: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false, references: { model: 'asset_categories', key: 'id', }, }, department: { type: DataTypes.STRING(50), allowNull: false, validate: { notEmpty: true }, }, responsible_person: { type: DataTypes.STRING(20), allowNull: false, validate: { notEmpty: true }, }, status: { type: DataTypes.ENUM('in_stock', 'in_use', 'under_repair', 'scraped'), defaultValue: 'in_stock', allowNull: false, }, purchase_date: { type: DataTypes.DATE, }, warranty_months: { type: DataTypes.SMALLINT.UNSIGNED, defaultValue: 12, validate: { min: 1 }, }, description: { type: DataTypes.TEXT, }, }, { sequelize, tableName: 'assets', timestamps: true, createdAt: 'created_at', updatedAt: 'updated_at', } );

定义路由控制器(src/controllers/asset.controller.ts):

// src/controllers/asset.controller.ts import { Request, Response } from 'express'; import { Asset } from '../models/asset.model'; export const getAllAssets = async (req: Request, res: Response) => { try { const { page = 1, limit = 10, status, department } = req.query; const offset = (+page - 1) * +limit; const whereClause: any = {}; if (status) whereClause.status = status; if (department) whereClause.department = department; const { count, rows } = await Asset.findAndCountAll({ where: whereClause, limit: +limit, offset, order: [['created_at', 'DESC']], attributes: { exclude: ['created_at', 'updated_at'], // 前端不需要时间戳字段 }, }); res.json({ success: true, data: rows, pagination: { total: count, page: +page, pages: Math.ceil(count / +limit), limit: +limit, }, }); } catch (error) { console.error('获取资产列表失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }; // 更新资产状态(核心业务逻辑:状态只能按预设路径流转) export const updateAssetStatus = async (req: Request, res: Response) => { const { id } = req.params; const { status, remark = '' } = req.body; // 定义合法状态流转图(防止非法跳转,如直接 from 'scraped' to 'in_use') const validTransitions: Record<string, string[]> = { in_stock: ['in_use', 'scraped'], in_use: ['under_repair', 'scraped'], under_repair: ['in_use', 'scraped'], scraped: [], // 已报废不可逆 }; try { const asset = await Asset.findByPk(id); if (!asset) { return res.status(404).json({ success: false, message: '资产不存在' }); } const currentStatus = asset.status; if (!validTransitions[currentStatus]?.includes(status)) { return res.status(400).json({ success: false, message: `状态不可从 '${currentStatus}' 变更为 '${status}'`, }); } // 记录日志(使用事务保证原子性) await sequelize.transaction(async (t) => { await asset.update({ status }, { transaction: t }); // 插入日志(before_data 为旧状态快照) await sequelize.query( `INSERT INTO asset_logs (asset_id, operator, action_type, before_data, after_data, remark, created_at) VALUES (?, ?, 'update_status', ?, ?, ?, NOW())`, { replacements: [ id, req.user?.username || 'system', JSON.stringify({ status: currentStatus }), JSON.stringify({ status }), remark, ], transaction: t, } ); }); res.json({ success: true, message: '状态更新成功' }); } catch (error) { console.error('更新资产状态失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } };

3.2 前端:Vue 3 Composition API 实现资产列表与状态切换组件

src/views/AssetList.vue中,使用onMounted获取数据,ref管理分页状态,defineAsyncComponent懒加载大表格组件:

<!-- src/views/AssetList.vue --> <template> <div class="asset-list"> <el-page-header @back="goBack" content="资产总览"></el-page-header> <!-- 筛选栏 --> <el-form :model="filterForm" inline class="filter-form"> <el-form-item label="部门"> <el-select v-model="filterForm.department" placeholder="全部部门" clearable> <el-option label="计算机学院" value="计算机学院" /> <el-option label="物理实验中心" value="物理实验中心" /> <el-option label="后勤处" value="后勤处" /> </el-select> </el-form-item> <el-form-item label="状态"> <el-select v-model="filterForm.status" placeholder="全部状态" clearable> <el-option label="在库" value="in_stock" /> <el-option label="在用" value="in_use" /> <el-option label="维修中" value="under_repair" /> <el-option label="已报废" value="scraped" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="fetchAssets">查询</el-button> <el-button @click="resetFilter">重置</el-button> </el-form-item> </el-form> <!-- 资产表格 --> <el-table :data="assets" style="width: 100%" stripe> <el-table-column prop="asset_code" label="资产编码" width="150" /> <el-table-column prop="name" label="资产名称" /> <el-table-column prop="department" label="使用部门" width="120" /> <el-table-column prop="responsible_person" label="责任人" width="100" /> <el-table-column label="状态" width="100"> <template #default="{ row }"> <el-tag :type="getStatusTagType(row.status)"> {{ getStatusText(row.status) }} </el-tag> </template> </el-table-column> <el-table-column label="操作" width="180" fixed="right"> <template #default="{ row }"> <el-button size="small" @click="handleEdit(row)">编辑</el-button> <el-dropdown trigger="click"> <el-button size="small">状态</el-button> <template #dropdown> <el-dropdown-menu> <el-dropdown-item v-for="opt in getValidStatusOptions(row.status)" :key="opt.value" @click="changeStatus(row.id, opt.value)" > {{ opt.label }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </template> </el-table-column> </el-table> <!-- 分页 --> <el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.limit" :total="pagination.total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" class="mt-4" /> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue'; import { ElMessage } from 'element-plus'; import { fetchAssetsList, updateAssetStatus } from '@/api/asset.api'; // 表单筛选 const filterForm = ref({ department: '', status: '', }); // 表格数据 const assets = ref<any[]>([]); const pagination = ref({ total: 0, page: 1, limit: 10, pages: 1, }); // 状态映射 const getStatusText = (status: string) => { const map: Record<string, string> = { in_stock: '在库', in_use: '在用', under_repair: '维修中', scraped: '已报废', }; return map[status] || status; }; const getStatusTagType = (status: string) => { const map: Record<string, string> = { in_stock: 'info', in_use: 'success', under_repair: 'warning', scraped: 'danger', }; return map[status] || 'info'; }; // 获取合法状态选项(根据当前状态动态生成下拉) const getValidStatusOptions = (currentStatus: string) => { const options: { value: string; label: string }[] = []; const validMap: Record<string, string[]> = { in_stock: ['in_use', 'scraped'], in_use: ['under_repair', 'scraped'], under_repair: ['in_use', 'scraped'], scraped: [], }; const labels: Record<string, string> = { in_use: '启用', under_repair: '报修', scraped: '报废', }; validMap[currentStatus]?.forEach((s) => { options.push({ value: s, label: labels[s] || s }); }); return options; }; // 获取资产列表 const fetchAssets = async () => { try { const res = await fetchAssetsList({ page: pagination.value.page, limit: pagination.value.limit, ...filterForm.value, }); assets.value = res.data; Object.assign(pagination.value, res.pagination); } catch (error) { ElMessage.error('获取资产列表失败'); } }; // 分页事件 const handleSizeChange = (val: number) => { pagination.value.limit = val; fetchAssets(); }; const handleCurrentChange = (val: number) => { pagination.value.page = val; fetchAssets(); }; // 重置筛选 const resetFilter = () => { filterForm.value = { department: '', status: '' }; fetchAssets(); }; // 状态切换 const changeStatus = async (id: number, newStatus: string) => { try { await updateAssetStatus(id, { status: newStatus }); ElMessage.success('状态更新成功'); fetchAssets(); // 刷新列表 } catch (error) { ElMessage.error('状态更新失败'); } }; // 页面加载 onMounted(() => { fetchAssets(); }); </script>

关键点说明getValidStatusOptions动态生成下拉项,确保前端 UI 层面就阻断非法状态跳转;changeStatus调用 API 后立即刷新列表,保证视图与数据强一致;ElTablestripe属性提升长表格可读性,符合资产管理场景需求。

4. 数据库优化与安全加固:索引策略、SQL 注入防护、敏感字段加密

4.1 MySQL 索引优化:针对高频查询字段建立复合索引

校园资产系统中,assets表的查询模式高度集中于“部门+状态”组合筛选(如“计算机学院在用资产”)、“资产编码模糊搜索”、“按采购日期范围统计”。若仅依赖主键索引,百万级数据下查询将严重超时。

执行以下 SQL 添加关键索引:

-- 为部门+状态组合查询建立联合索引(覆盖查询,避免回表) CREATE INDEX idx_dept_status ON assets(department, status); -- 为资产编码添加前缀索引(VARCHAR(32)过长,取前16位足够区分) CREATE INDEX idx_asset_code_prefix ON assets(asset_code(16)); -- 为采购日期建立索引,支撑年度统计报表 CREATE INDEX idx_purchase_date ON assets(purchase_date); -- 为日志表的资产ID和时间建立联合索引(查询某资产所有操作历史) CREATE INDEX idx_log_asset_time ON asset_logs(asset_id, created_at);

验证效果:使用EXPLAIN SELECT * FROM assets WHERE department='计算机学院' AND status='in_use';查看执行计划,key字段应显示idx_dept_statusrows应显著低于全表扫描行数。

4.2 后端安全:Sequelize 参数化查询与密码字段加密

Node.js 后端必须杜绝字符串拼接 SQL,否则asset_code输入' OR 1=1 --将导致全表泄露。Sequelize 默认使用参数化查询,但开发者仍可能误用原始查询。

错误写法(绝对禁止)

// ❌ 危险!直接拼接用户输入 const sql = `SELECT * FROM assets WHERE asset_code = '${req.query.code}'`; await sequelize.query(sql); // SQL注入高危

正确写法(Sequelize 推荐)

// ✅ 安全:使用占位符 const [results] = await sequelize.query( 'SELECT * FROM assets WHERE asset_code = ?', { replacements: [req.query.code], type: QueryTypes.SELECT } );

对于responsible_person等非密码字段,虽无需加密,但需防范 XSS。在asset.model.ts中添加校验:

// src/models/asset.model.ts(续) responsible_person: { type: DataTypes.STRING(20), allowNull: false, validate: { notEmpty: true, // 防XSS:拒绝HTML标签 is: /^[^\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f<>]*$/i, }, },

4.3 敏感操作审计:所有资产状态变更必须记录操作人与IP

资产报废、责任人变更属于高风险操作,必须留痕。在 Express 中间件层统一注入操作人信息:

// src/middleware/auth.middleware.ts import { Request, Response, NextFunction } from 'express'; export const injectOperator = (req: Request, res: Response, next: NextFunction) => { // 从JWT解析出用户名(实际项目中应从token解析) const username = req.headers.authorization?.split(' ')[1] ? 'admin_user' // 演示用,生产环境替换为真实解析逻辑 : 'anonymous'; // 获取客户端真实IP(处理Nginx反向代理场景) const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress; (req as any).operator = { username, ip }; next(); };

updateAssetStatus控制器中调用:

// src/controllers/asset.controller.ts(修改部分) export const updateAssetStatus = async (req: Request, res: Response) => { const { id } = req.params; const { status, remark = '' } = req.body; const { username, ip } = (req as any).operator; // 注入的操作人信息 // ... 状态校验逻辑 ... await sequelize.transaction(async (t) => { await asset.update({ status }, { transaction: t }); await sequelize.query( `INSERT INTO asset_logs (asset_id, operator, action_type, before_data, after_data, remark, created_at) VALUES (?, ?, 'update_status', ?, ?, ?, NOW())`, { replacements: [ id, username, // 使用中间件注入的用户名 JSON.stringify({ status: currentStatus }), JSON.stringify({ status }), remark, ], transaction: t, } ); }); // 记录到独立审计日志文件(生产环境建议接入ELK) console.log(`[AUDIT] ${username}(${ip}) updated asset ${id} status to ${status}`); };

5. 部署与上线:Nginx 反向代理 + PM2 进程守护 + MySQL 主从读写分离预备

5.1 前端静态资源部署:Nginx 配置 gzip 与缓存策略

Vue 项目构建后生成dist目录,需通过 Nginx 提供静态服务。关键配置包括:启用 gzip 压缩(减小 JS/CSS 体积)、设置强缓存(immutable防止资源更新后用户仍加载旧版)、配置 history 模式 fallback。

# /etc/nginx/conf.d/campus-asset.conf server { listen 80; server_name asset.your-university.edu.cn; root /var/www/campus-asset-fe/dist; index index.html; # 启用gzip压缩 gzip on; gzip_types application/javascript text/css text/plain; gzip_min_length 1000; # 静态资源强缓存(JS/CSS带hash,可永久缓存) location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; add_header Vary Accept-Encoding; } # history模式fallback:所有非静态资源请求都返回index.html location / { try_files $uri $uri/ /index.html; } # API代理到后端(开发环境用vite proxy,生产环境用Nginx代理) location /api/ { proxy_pass http://127.0.0.1:3000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

注意add_header Cache-Control "public, immutable"是关键,Vue CLI 构建的文件名含 contenthash(如index.abc123.js),内容变更则文件名变,因此可放心设置一年缓存。

5.2 后端进程管理:PM2 启动 Express 服务并监控内存

Node.js 进程需常驻运行,PM2 是最成熟的进程管理器,支持负载均衡、自动重启、内存监控。

# 全局安装PM2 npm install pm2 -g # 进入后端目录,启动服务(--name 指定进程名便于管理) cd /path/to/campus-asset-be pm2 start dist/server.js --name "campus-asset-be" --watch # 设置开机自启(Ubuntu/Debian) pm2 startup systemd pm2 save # 查看进程状态与内存占用 pm2 show campus-asset-be pm2 monit # 实时监控

配置ecosystem.config.js实现优雅重启:

// ecosystem.config.js module.exports = { apps: [{ name: 'campus-asset-be', script: './dist/server.js', instances: 1, autorestart: true, watch: false, // 由PM2监控dist目录,源码变更不触发重启 max_memory_restart: '512M', // 内存超512MB自动重启 env: { NODE_ENV: 'production', PORT: 3000, DB_HOST: 'localhost', DB_NAME: 'campus_asset_db', DB_USER: 'asset_app', DB_PASS: 'your_secure_password', } }] };

5.3 数据库高可用预备:MySQL 主从架构搭建要点

当资产数据量突破 100 万条或并发查询激增时,单机 MySQL 成为瓶颈。主从分离是第一步:主库(Master)负责写入与事务,从库(Slave)承担报表查询、前端列表读取。

主库配置(my.cnf)

[mysqld] server-id = 1 log-bin = mysql-bin binlog-format = ROW expire_logs_days = 7 max_binlog_size = 100M

从库配置(my.cnf)

[mysqld] server-id = 2 relay-log = mysql-relay-bin read_only = ON

主从同步命令(在从库执行)

-- 停止从库复制 STOP SLAVE; -- 配置主库连接信息(需提前在主库创建复制用户) CHANGE MASTER TO MASTER_HOST='master_ip', MASTER_USER='repl_user', MASTER_PASSWORD='repl_pass', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=154; -- 启动复制 START SLAVE; -- 查看状态(Seconds_Behind_Master 应为 0) SHOW SLAVE STATUS\G

关键提示:应用层需改造数据库连接——所有INSERT/UPDATE/DELETE发往主库,SELECT查询发往从库。可使用 Sequelize 的readReplication配置,或在业务代码中显式指定replication: { read: [...] }。此步骤虽不在初始部署必需,但源码中已预留db.config.ts的多实例配置接口,为后续扩展留出空间。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 16:03:54

NSGA-II与NSGA-III算法解析:Python实现多目标优化与选择指南

简介&#xff1a;一套面向多目标优化学习与研究者的NSGA3与NSGA-II算法Python/Matlab实现代码包。针对工程设计、调度、投资组合等具有相互冲突目标的优化问题&#xff0c;提供从帕累托前沿构建、快速非支配排序到拥挤距离与分层选择的完整实现框架。压缩包共10个文件&#xff…

作者头像 李华
网站建设 2026/9/16 16:03:16

三菱PLC在药片装瓶机控制系统中的应用与优化

1. 自动药片装瓶机控制系统架构解析在制药行业的自动化生产线上&#xff0c;药片装瓶机堪称"精密舞者"&#xff0c;其控制系统需要协调机械臂、传送带、计数机构等十余个执行单元。这套由三菱FX3U PLC作为主控的系统&#xff0c;通过组态王上位机实现人机交互&#x…

作者头像 李华
网站建设 2026/9/16 15:59:23

docker-minecraft-server 环境变量改配置:4 个部署场景与避坑指南

docker-minecraft-server 环境变量改配置&#xff1a;4 个部署场景与避坑指南 【免费下载链接】docker-minecraft-server Docker image that provides a Minecraft Server for Java Edition that automatically installs/upgrades versions, modloaders, modpacks and more at …

作者头像 李华
网站建设 2026/9/16 15:59:21

改对 3 处配置,让 ESP-IDF USB Host 驱动一次枚举成功

改对 3 处配置&#xff0c;让 ESP-IDF USB Host 驱动一次枚举成功 【免费下载链接】esp-idf Espressif IoT Development Framework. Official development framework for Espressif SoCs. 项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf 你正在用 ESP-IDF 的…

作者头像 李华
网站建设 2026/9/16 15:56:54

OpenAI Agents SDK集成CubeSandbox实战:为Agent装上安全代码执行器

OpenAI Agents SDK集成CubeSandbox实战&#xff1a;为Agent装上安全代码执行器 【免费下载链接】CubeSandbox Instant, Concurrent, Secure & Lightweight Sandbox for AI Agents. 项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox 想让 AI Agent 真正…

作者头像 李华