diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1423ed5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,37 @@ +# Repository Guidelines + +## 项目结构 +仓库分为 `frontend/` 和 `backend/` 两部分。前端是 Vue 3 + Vite,主要代码在 `frontend/src/`:页面在 `src/views/`,通用组件在 `src/components/`,接口封装在 `src/api/`,状态管理在 `src/stores/`,路由在 `src/router/`。后端是 Express + TypeScript,代码在 `backend/src/`,按 `routes/`、`controllers/`、`middleware/`、`utils/` 分层。数据库模型统一在 `backend/prisma/schema.prisma`。部署相关文件在仓库根目录:`docker-compose.yml`、`Dockerfile`、`deploy.sh`。 + +## 构建、运行与开发命令 +首次进入前后端目录分别执行 `npm install`。 + +- `cd frontend && npm run dev`:启动前端开发环境。 +- `cd frontend && npm run build`:执行类型检查并打包前端。 +- `cd backend && npm run dev`:用 `nodemon + ts-node` 启动后端。 +- `cd backend && npm run build`:编译后端到 `dist/`。 +- `cd backend && npm run prisma:generate`:Prisma 模型变更后重建客户端。 +- `cd backend && npm run prisma:migrate`:生成并应用本地数据库迁移。 +- `docker compose up --build`:同时启动 MySQL、后端、前端。 + +## 代码风格与命名 +项目以 TypeScript 为主,现有代码统一使用 2 空格缩进。Vue 组件文件使用 PascalCase,如 `EquipmentForm.vue`;接口、store、工具模块使用语义化名称,如 `equipment.ts`、`storageLocation.ts`。新增功能时,尽量保持前端 API 名称、后端路由、控制器按同一业务域对应,方便排查问题。 + +## 测试与验证 +当前仓库没有现成自动化测试脚本,因此每次改动后至少执行 `frontend` 和 `backend` 的 `npm run build`。涉及接口改动时,还应手动验证对应页面流程,并检查 `GET /api/health`。涉及 Prisma 或数据库结构调整时,额外执行 `npm run prisma:generate`,确认相关增删改查正常。若后续补测试,建议使用 `*.spec.ts` 命名。 + +## 提交与合并请求 +当前 Git 历史很少,只有初始提交,没有强约束格式。建议提交信息使用简短中文,写清“改了什么”,例如:`后端:修复借用记录筛选条件`。Pull Request 需要说明变更范围、验证方式、是否影响数据库或部署;前端页面改动建议附截图,接口或表结构改动要明确标注兼容性影响。 + +## 配置与安全 +不要提交真实密码、数据库连接串或生产环境地址。`DATABASE_URL`、会话密钥、认证模式等应通过环境变量管理。修改 `docker-compose.yml`、认证配置或对外端口时,先确认是否会影响本地联调和线上部署。 + +## Multi-Agent 团队分工 +推荐使用 4 个角色,保持团队小而清晰。 + +- 主控 / 集成代理:负责拆任务、安排子代理、收敛方案、做最终验收;默认可读全仓库,但不要直接承担大批量业务改动。 +- 前端业务代理:只负责 `frontend/src/views/`、`frontend/src/components/`、`frontend/src/api/`、`frontend/src/stores/`、`frontend/src/router/`,处理页面、交互、表单、状态和前端接口联调。 +- 后端业务代理:只负责 `backend/src/routes/`、`backend/src/controllers/`,处理资产、借还、搜索、统计、人员等业务接口。 +- 数据与系统代理:只负责 `backend/prisma/`、`backend/src/middleware/`、`backend/src/utils/`、认证配置、`docker-compose.yml`、`Dockerfile`、`deploy.sh`,处理数据库结构、权限、认证、部署和环境问题。 + +协作时尽量避免跨边界改动:前端代理不直接改 Prisma,后端业务代理不直接动 Docker,数据与系统代理不直接改页面。 diff --git a/BACKUP.md b/BACKUP.md new file mode 100644 index 0000000..2ff969c --- /dev/null +++ b/BACKUP.md @@ -0,0 +1,42 @@ +# 固定资产系统备份说明 + +本仓库只备份固定资产系统,不处理摄影设备借还系统。对应服务器资源如下: + +- 项目目录:`/opt/equipment-management` +- 数据库容器:`equipment-mysql` +- 数据库名:`equipment_management` +- 后端容器:`equipment-backend` +- 上传目录:`/app/uploads` +- 备份目录:`/opt/backups/equipment-management` +- 默认保留:最近 `3` 份备份 + +## 执行方式 + +先把脚本上传到服务器,例如放到 `/opt/equipment-management/scripts/backup-equipment.sh`,然后执行: + +```bash +chmod +x /opt/equipment-management/scripts/backup-equipment.sh +/opt/equipment-management/scripts/backup-equipment.sh +``` + +如果以后要临时调整保留数量,可以这样执行: + +```bash +KEEP_BACKUPS=5 /opt/equipment-management/scripts/backup-equipment.sh +``` + +## 备份内容 + +每次执行会生成一个时间戳目录,包含: + +- `db/equipment_management.sql.gz`:数据库导出 +- `uploads/uploads.tar.gz`:上传文件归档 +- `config/`:当前部署配置副本 +- `backup-info.txt`:备份元信息 +- `SHA256SUMS`:校验文件 + +脚本完成后会自动清理更早的备份目录,只保留最近 `KEEP_BACKUPS` 份,避免占满服务器磁盘。 + +## 回滚思路 + +发生部署异常时,先恢复代码和部署配置,再恢复数据库和上传目录。数据库恢复建议在停掉固定资产系统容器后执行,避免写入冲突。摄影设备借还系统使用独立容器、端口、网络和数据卷,不应纳入这套备份或回滚流程。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..f8f504d --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# 固定资产管理系统 + +这是一个前后端分离的固定资产管理系统,覆盖资产台账、借用归还、扫码出入库、挂载关系、维修、报废、统计分析和系统设置等功能。 + +## 技术栈 + +- 前端:Vue 3、Vite、TypeScript、Pinia、Vue Router、Tailwind CSS +- 后端:Express、TypeScript、Prisma、MySQL +- 部署:Docker Compose + +## 目录结构 + +- `frontend/`:前端页面、组件、状态管理、接口封装 +- `backend/`:后端路由、控制器、中间件、工具函数 +- `backend/prisma/schema.prisma`:数据库模型 +- `deploy.sh`:固定资产系统安全部署脚本 +- `scripts/backup-equipment.sh`:固定资产系统备份脚本 +- `AGENTS.md`:仓库协作与多代理分工说明 +- `BACKUP.md`:备份与回滚说明 + +## 本地开发 + +分别安装依赖: + +```bash +cd frontend && npm install +cd backend && npm install +``` + +常用命令: + +```bash +cd frontend && npm run dev +cd frontend && npm run build +cd backend && npm run dev +cd backend && npm run build +cd backend && npm run prisma:generate +cd backend && npm run prisma:migrate +``` + +## 服务器部署 + +生产服务器上同时运行两套同类系统: + +- 固定资产系统:`/opt/equipment-management` +- 摄影设备借还系统:`/opt/shebeiguanli` + +两套系统使用不同的容器、端口、网络和数据卷。部署本项目时,只能操作固定资产系统这一套资源,不能影响 `shebeiguanli-*` 容器。 + +执行部署: + +```bash +cd /opt/equipment-management +./deploy.sh +``` + +部署脚本默认会先备份,再重建固定资产系统容器,最后检查健康状态和另一套系统状态。 + +## 备份与回滚 + +执行备份: + +```bash +cd /opt/equipment-management +./scripts/backup-equipment.sh +``` + +默认只保留最近 `3` 份备份,备份目录在 `/opt/backups/equipment-management`。详细说明见 `BACKUP.md`。 + +## 当前线上资源 + +- 前端:`http://1.13.22.133` +- 后端:`http://1.13.22.133:3000/api` +- 健康检查:`http://1.13.22.133:3000/api/health` + +## 注意事项 + +- 不要把真实密码、数据库连接串或生产环境地址提交到仓库。 +- 涉及 Prisma 模型调整时,先生成客户端,再验证相关接口。 +- 发布前默认执行备份,不建议跳过。 diff --git a/deploy.sh b/deploy.sh index f54eb1e..aa2fb0b 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,41 +1,87 @@ #!/bin/bash -# 摄影设备借还系统部署脚本 -set -e +# 固定资产管理系统部署脚本 +set -euo pipefail + +PROJECT_DIR="/opt/equipment-management" +COMPOSE_FILE="$PROJECT_DIR/docker-compose.yml" +PROJECT_NAME="equipment-management" +BACKUP_SCRIPT="$PROJECT_DIR/scripts/backup-equipment.sh" +SKIP_BACKUP="${SKIP_BACKUP:-0}" +EQUIPMENT_CONTAINERS=("equipment-frontend" "equipment-backend" "equipment-mysql") +LEGACY_CONTAINERS=("shebeiguanli-frontend" "shebeiguanli-backend" "shebeiguanli-mysql") + +if docker compose version >/dev/null 2>&1; then + COMPOSE_CMD=(docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE") +elif command -v docker-compose >/dev/null 2>&1; then + COMPOSE_CMD=(docker-compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE") +else + echo "未找到 docker compose / docker-compose,无法部署。" + exit 1 +fi echo "==========================================" -echo " 摄影设备借还系统 - 部署脚本" +echo " 固定资产管理系统 - 安全部署脚本" echo "==========================================" +echo "项目目录: $PROJECT_DIR" +echo "Compose 文件: $COMPOSE_FILE" +echo "Compose 项目名: $PROJECT_NAME" +echo "备份脚本: $BACKUP_SCRIPT" +echo "" -# 进入项目目录 -cd /opt/equipment-management +if [ ! -f "$COMPOSE_FILE" ]; then + echo "未找到 $COMPOSE_FILE" + exit 1 +fi -# 停止旧容器 -echo ">>> 停止旧容器..." -sudo docker-compose down 2>/dev/null || true +cd "$PROJECT_DIR" -# 构建并启动容器 -echo ">>> 构建Docker镜像..." -sudo docker-compose build --no-cache +if [ "$SKIP_BACKUP" = "1" ]; then + echo ">>> 已设置 SKIP_BACKUP=1,跳过备份" +else + if [ ! -x "$BACKUP_SCRIPT" ]; then + echo "备份脚本不存在或不可执行: $BACKUP_SCRIPT" + exit 1 + fi -echo ">>> 启动服务..." -sudo docker-compose up -d + echo ">>> 先执行固定资产系统备份..." + "$BACKUP_SCRIPT" +fi + +echo ">>> 部署前检查旧系统容器状态..." +for container in "${LEGACY_CONTAINERS[@]}"; do + status=$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null || echo "missing") + echo " $container => $status" +done + +echo ">>> 仅更新固定资产系统容器..." +"${COMPOSE_CMD[@]}" down +"${COMPOSE_CMD[@]}" build +"${COMPOSE_CMD[@]}" up -d -# 等待服务启动 echo ">>> 等待服务启动..." sleep 10 -# 检查服务状态 -echo ">>> 检查服务状态..." -sudo docker-compose ps +echo ">>> 固定资产系统容器状态..." +for container in "${EQUIPMENT_CONTAINERS[@]}"; do + status=$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null || echo "missing") + echo " $container => $status" +done -# 检查后端健康状态 -echo ">>> 检查后端API..." -curl -s http://localhost:3000/api/health || echo "后端服务启动中..." +echo ">>> 再次确认摄影设备借还系统未受影响..." +for container in "${LEGACY_CONTAINERS[@]}"; do + status=$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null || echo "missing") + echo " $container => $status" +done + +echo ">>> 检查固定资产系统后端健康状态..." +curl -fsS http://localhost:3000/api/health || echo "后端服务启动中..." echo "" echo "==========================================" -echo " 部署完成!" -echo " 前端访问: http://1.13.22.133" -echo " 后端API: http://1.13.22.133:3000/api" +echo " 部署完成" +echo " 固定资产前端: http://1.13.22.133" +echo " 固定资产后端: http://1.13.22.133:3000/api" +echo " 摄影设备前端: http://1.13.22.133:8081" +echo " 摄影设备后端: http://1.13.22.133:3001" echo "==========================================" diff --git a/scripts/backup-equipment.sh b/scripts/backup-equipment.sh new file mode 100644 index 0000000..a59e53a --- /dev/null +++ b/scripts/backup-equipment.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +# 固定资产系统备份脚本 +set -euo pipefail + +PROJECT_DIR="${PROJECT_DIR:-/opt/equipment-management}" +PROJECT_NAME="${PROJECT_NAME:-equipment-management}" +BACKUP_ROOT="${BACKUP_ROOT:-/opt/backups/equipment-management}" +KEEP_BACKUPS="${KEEP_BACKUPS:-3}" +DB_CONTAINER="${DB_CONTAINER:-equipment-mysql}" +BACKEND_CONTAINER="${BACKEND_CONTAINER:-equipment-backend}" +DB_NAME="${DB_NAME:-equipment_management}" +DB_USER="${DB_USER:-root}" +DB_PASSWORD="${DB_PASSWORD:-jsjj8888}" +UPLOADS_PATH="${UPLOADS_PATH:-/app/uploads}" +TIMESTAMP="$(date +%Y%m%d_%H%M%S)" +BACKUP_DIR="${BACKUP_ROOT}/${TIMESTAMP}" + +mkdir -p "${BACKUP_DIR}" +mkdir -p "${BACKUP_DIR}/db" "${BACKUP_DIR}/uploads" "${BACKUP_DIR}/config" + +cleanup_on_error() { + echo "备份失败,清理不完整目录: ${BACKUP_DIR}" + rm -rf "${BACKUP_DIR}" +} + +prune_old_backups() { + if ! [[ "${KEEP_BACKUPS}" =~ ^[0-9]+$ ]]; then + echo "KEEP_BACKUPS 不是有效数字: ${KEEP_BACKUPS}" + exit 1 + fi + + mapfile -t existing_backups < <(find "${BACKUP_ROOT}" -mindepth 1 -maxdepth 1 -type d | sort) + local total="${#existing_backups[@]}" + + if [ "${total}" -le "${KEEP_BACKUPS}" ]; then + echo ">>> 当前备份数量 ${total},无需清理" + return + fi + + local delete_count=$((total - KEEP_BACKUPS)) + echo ">>> 仅保留最近 ${KEEP_BACKUPS} 份备份,清理 ${delete_count} 份旧备份" + for backup_dir in "${existing_backups[@]:0:${delete_count}}"; do + echo " 删除: ${backup_dir}" + rm -rf "${backup_dir}" + done +} + +trap cleanup_on_error ERR + +echo "备份目录: ${BACKUP_DIR}" +echo "保留策略: 最近 ${KEEP_BACKUPS} 份" + +if ! docker inspect "${DB_CONTAINER}" >/dev/null 2>&1; then + echo "数据库容器不存在: ${DB_CONTAINER}" + exit 1 +fi + +if ! docker inspect "${BACKEND_CONTAINER}" >/dev/null 2>&1; then + echo "后端容器不存在: ${BACKEND_CONTAINER}" + exit 1 +fi + +echo ">>> 备份数据库 ${DB_NAME}" +docker exec "${DB_CONTAINER}" \ + sh -lc "exec mysqldump -u${DB_USER} -p'${DB_PASSWORD}' --single-transaction --routines --triggers ${DB_NAME}" \ + | gzip > "${BACKUP_DIR}/db/${DB_NAME}.sql.gz" + +echo ">>> 备份上传目录 ${UPLOADS_PATH}" +docker exec "${BACKEND_CONTAINER}" \ + sh -lc "if [ -d '${UPLOADS_PATH}' ]; then tar -C '${UPLOADS_PATH}' -czf - .; else printf '' | gzip; fi" \ + > "${BACKUP_DIR}/uploads/uploads.tar.gz" + +echo ">>> 备份部署配置" +cp "${PROJECT_DIR}/docker-compose.yml" "${BACKUP_DIR}/config/docker-compose.yml" +cp "${PROJECT_DIR}/deploy.sh" "${BACKUP_DIR}/config/deploy.sh" +cp "${PROJECT_DIR}/Dockerfile" "${BACKUP_DIR}/config/Dockerfile" + +cat > "${BACKUP_DIR}/backup-info.txt" <>> 生成校验信息" +( + cd "${BACKUP_DIR}" + sha256sum db/${DB_NAME}.sql.gz uploads/uploads.tar.gz config/docker-compose.yml config/deploy.sh config/Dockerfile > SHA256SUMS +) + +prune_old_backups +trap - ERR + +echo "备份完成: ${BACKUP_DIR}"