# cms **Repository Path**: symbol365/cms ## Basic Information - **Project Name**: cms - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-18 - **Last Updated**: 2026-10-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # CMS 管理后台 Laravel 13 + Vue 3 前后端分离的批次 / 商品编码管理系统。前端基于 `go-vue-admin` 模版二次开发,后端提供 Sanctum 鉴权、批次管理、雪花算法编码生成、导出与扫码计数。 ## 功能 - 管理员登录、退出、Token 失效自动回登录页。 - 批次列表:新建批次、按批次号搜索、分页、下载整批编码。 - 生成编码:弹窗只填生产数量(1–10,000),雪花算法生成全局唯一编码,输出前做密钥混淆,编码非连续、不可枚举。 - 生成完成弹窗内直接下载本次编码(txt,一行一个)。 - 商品编码列表:按批次进入,展示商品编码、扫码次数、批次、启用状态。 - 编码搜索、启用状态筛选、分页、复制编码、启用/停用。 - 批次内继续追加生成编码。 - 批次编码导出:默认 txt,接口支持 `?format=csv`(带 BOM,Excel 可直接打开)。 - 扫码接口:`POST /api/public/codes/{code}/scan`,启用状态下扫码次数 +1。 - 个人中心:查看账号信息、修改密码(修改后其他设备自动退出)。 - 登录日志:记录成功/失败、用户名、IP、浏览器,超级管理员可见。 - 操作日志:记录新建批次、追加编码、导出、启停、改密码等操作,含请求参数、状态码与耗时。 界面截图: ![登录](docs/screenshots/login.png) ![批次列表](docs/screenshots/batch-list.png) ![生成成功](docs/screenshots/batch-success.png) ![商品列表](docs/screenshots/product-list.png) ![个人中心](docs/screenshots/profile.png) ![登录日志](docs/screenshots/login-logs.png) ![操作日志](docs/screenshots/operation-logs.png) ## 技术栈 | 层 | 技术 | | --- | --- | | 后端 | PHP 8.4 / Laravel 13.37 / Laravel Sanctum 4 / MySQL 8.4(本地 3307)/ MySQL 8(生产) | | 前端 | Vue 3.5 / Vite 7 / TypeScript 5.9 / Element Plus 2.13 / Pinia 3 / Axios | | 部署 | Nginx + PHP-FPM,前后端同源 | ## 目录结构 ```text D:\project\cms\ ├─ backend\ # Laravel API │ ├─ app\Http\Controllers\Api\Admin\ │ │ ├─ AuthController.php │ │ ├─ BatchController.php │ │ ├─ LoginLogController.php │ │ ├─ OperationLogController.php │ │ └─ ProductController.php │ ├─ app\Http\Controllers\Api\PublicCodeController.php │ ├─ app\Http\Middleware\RecordOperationLog.php │ ├─ app\Models\{User,Batch,Product,LoginLog,OperationLog}.php │ ├─ app\Services\{BatchService,ProductCodeGenerator,ProductExportService,LoginLogService,OperationLogService}.php │ ├─ app\Support\{Snowflake,SnowflakeCodec,ApiResponse}.php │ ├─ database\migrations\ │ ├─ database\seeders\AdminUserSeeder.php │ ├─ routes\api.php │ └─ tests\Feature\... ├─ frontend\ # Vue 管理后台 │ └─ src\ │ ├─ api\modules\{auth,batch,product}.ts │ ├─ modules\product\ │ │ ├─ components\GenerateDialog.vue │ │ └─ views\{batch,product}\index.vue │ ├─ modules\system\ │ │ └─ views\{profile,login-log,operation-log}\index.vue │ ├─ stores\auth.ts │ ├─ utils\{request,download,menu,auth}.ts │ └─ layout\... ├─ deploy\nginx.conf ├─ docs\ │ ├─ 01-总体方案.md │ ├─ 安全自查报告.md │ ├─ 宝塔Nginx部署文档.md │ └─ screenshots\ ├─ scripts\ # dev / MySQL 8 启停 / 数据迁移 ├─ tools\ # 本地便携 PHP / MySQL 8 / Composer / E2E(不提交 git) └─ README.md ``` ## 快速开始 ### 环境要求 - PHP 8.3+(本项目本地使用 `tools/php` 下的 PHP 8.4.26 便携版) - Composer 2 - Node.js `^20.19.0 || >=22.12.0`(本机 v22.22.1) - 数据库:MySQL 8.4(本地便携版,端口 **3307**);phpStudy 自带的 MySQL 5.7 保留在 3306,互不影响 > `tools/` 已加入 `.gitignore`。重新拉取代码时请按常规方式安装 PHP / MySQL / Composer,或重新准备便携环境。 ### 1. 启动数据库与后端 ```powershell # 启动本地 MySQL 8.4(127.0.0.1:3307) powershell -ExecutionPolicy Bypass -File D:\project\cms\scripts\mysql8-start.ps1 cd D:\project\cms\backend # 首次安装依赖(使用本地便携 PHP + Composer) D:\project\cms\tools\php\php.exe D:\project\cms\tools\composer.phar install # 首次初始化数据库;已有数据时不要执行 fresh D:\project\cms\tools\php\php.exe artisan migrate --seed # 启动 API D:\project\cms\tools\php\php.exe artisan serve --host=127.0.0.1 --port=8000 ``` 若 PHP / Composer 已在 PATH 中,直接使用 `php artisan ...`、`composer install` 即可。 ### 2. 启动前端 ```powershell cd D:\project\cms\frontend npm install npm run dev ``` 访问 ,Vite 会把 `/api`、`/storage` 代理到 `127.0.0.1:8000`。 ### 3. 默认账号 ```text 用户名:admin 密码:Admin@123456 ``` 首次部署后请立即修改密码(可改 `AdminUserSeeder` 后重新 seed,或直接更新 users 表)。 ## 数据库 本地 `.env` 使用 MySQL 8.4,端口 **3307**(避开 phpStudy 5.7 的 3306): ```dotenv DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3307 DB_DATABASE=cms DB_USERNAME=cms DB_PASSWORD="Cms@123456" ``` - MySQL 8.4 便携版位于 `tools/mysql8`,数据目录 `tools/mysql8/data`。 - 启动:`scripts/mysql8-start.ps1`;停止:`scripts/mysql8-stop.ps1`。 - 本地 root 无密码(仅监听 127.0.0.1),应用使用 `cms` 用户。 - 便携版不注册 Windows 服务,重启电脑后需要再运行启动脚本。 - 已从 SQLite 迁移:**2 个批次 / 200 条编码 / 1 个管理员**。 - 切换前备份:`backend/.env.sqlite.bak`、`backend/database/database.sqlite.bak`。 - 浏览器如果提示登录失效,重新登录一次即可(Token 表已迁移,但重新登录会吊销旧 Token)。 - 原有 SQLite 数据保留在 `backend/database/database.sqlite`,如需回退: ```dotenv DB_CONNECTION=sqlite ``` 生产 MySQL 示例: ```dotenv APP_ENV=production APP_DEBUG=false APP_URL=https://cms.example.com APP_TIMEZONE=Asia/Shanghai DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=cms DB_USERNAME=cms DB_PASSWORD=******** SANCTUM_EXPIRATION=1440 SNOWFLAKE_DATACENTER_ID=1 SNOWFLAKE_WORKER_ID=1 SNOWFLAKE_CODE_SECRET=请替换为至少32位随机字符串 MAX_GENERATE_PER_REQUEST=10000 # 生产安全项 CORS_ALLOWED_ORIGINS= SESSION_SECURE_COOKIE=true LOG_LEVEL=warning ``` 表结构: - `users`:管理员账号、角色、状态、最近登录。 - `batches`:批次号、编码总数、创建人、时间。 - `products`:商品编码(雪花 ID 经密钥混淆后的字符串)、批次、扫码次数、启用状态、最近扫码时间。 - `login_logs`:登录尝试的用户名、IP、UA、成功/失败、说明。 - `operation_logs`:操作人、动作、方法、路径、请求参数(已脱敏)、状态码、耗时、IP、UA。 ## 接口 统一前缀 `/api`,响应包络 `{ code, message, data }`。除登录和扫码外均需 `Authorization: Bearer `。 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/api/admin/auth/login` | 登录(5 次/分钟限流) | | GET | `/api/admin/auth/me` | 当前用户与权限 | | POST | `/api/admin/auth/logout` | 退出并吊销 Token | | PUT | `/api/admin/auth/password` | 修改当前用户密码 | | GET | `/api/admin/login-logs` | 登录日志(超级管理员) | | GET | `/api/admin/operation-logs` | 操作日志(超级管理员) | | GET | `/api/admin/batches` | 批次分页列表 | | POST | `/api/admin/batches` | 新建批次并生成编码 | | GET | `/api/admin/batches/{id}` | 批次详情/汇总 | | GET | `/api/admin/batches/{id}/products` | 批次内编码分页 | | POST | `/api/admin/batches/{id}/products` | 批次内追加生成 | | GET | `/api/admin/batches/{id}/export?format=txt\|csv` | 导出批次编码 | | PATCH | `/api/admin/products/{id}/status` | 单条启用/停用 | | POST | `/api/admin/products/batch-status` | 批量启用/停用 | | POST | `/api/public/codes/{code}/scan` | 扫码计数(公开) | 编码字段全链路使用字符串,避免 18–19 位编码在 JavaScript 中丢失精度。对外编码不是裸雪花 ID:生成后会经过带密钥的 6 轮 Feistel 置换与 cycle-walking,输出非连续、不可按时间/序号枚举。 ## 雪花算法与编码混淆 - 内部雪花:41 位毫秒时间戳 + 5 位数据中心 + 5 位 worker + 12 位序列。 - epoch:`2024-01-01 00:00:00 UTC`。 - **对外编码**:雪花 ID 先经过 6 轮 Feistel(HMAC-SHA256 轮函数)+ cycle-walking,映射为 18–19 位十进制字符串。相邻雪花 ID 的输出差异巨大,同一毫秒内也不再连续。 - **密钥**:`SNOWFLAKE_CODE_SECRET`(至少 32 位,生产必须修改)。它只影响编码混淆/还原,不影响唯一性。 - **可逆**:`SnowflakeCodec::decode()` 可用同一密钥还原原始雪花 ID,便于排查;业务上不需要还原。 - **多实例部署**必须保证 `SNOWFLAKE_DATACENTER_ID + SNOWFLAKE_WORKER_ID` 唯一,否则可能生成重复编码。 - 时钟回拨会抛异常并记录日志,不静默生成。 配置在 `backend/config/snowflake.php` 与 `.env`。 > 单纯使用雪花 ID 作为对外编码时,同一毫秒内必然连续、可按时间推算;本项目的混淆层就是为解决这个安全问题。 ## 测试与构建 ```powershell # 后端:43 个测试 cd D:\project\cms\backend D:\project\cms\tools\php\php.exe artisan test # 前端:类型检查 + 生产构建 cd D:\project\cms\frontend npm run type-check npm run build ``` 浏览器 E2E(本地已装 `playwright-core`,使用系统 Edge): ```powershell node D:\project\cms\tools\e2e\smoke.mjs node D:\project\cms\tools\e2e\system-pages.mjs ``` `smoke.mjs` 覆盖登录 → 新建批次 → 生成 5 条 → 下载 → 商品列表 → 停用 → 扫码计数;`system-pages.mjs` 覆盖个人中心 → 登录日志 → 操作日志/详情。截图输出到 `docs/screenshots/`。 ## 生产部署 ```powershell cd backend composer install --no-dev --optimize-autoloader php artisan migrate --force php artisan config:cache php artisan route:cache cd ../frontend npm ci npm run build ``` Nginx 同源配置见 [deploy/nginx.conf](deploy/nginx.conf):`/` 指向 `frontend/dist`,`/api` 转 PHP-FPM,`/storage` 预留。 宝塔面板(Nginx + PHP 8.4 + MySQL 8)的完整部署步骤见 [宝塔Nginx部署文档](docs/宝塔Nginx部署文档.md),包含站点根目录、PHP-FPM socket、open_basedir、SSL、伪静态和常见故障排查。 ## 安全 已完成: - CORS 白名单(不再返回 `*`)。 - 后端权限中间件(批次/编码/导出,不只靠前端隐藏)。 - 修改密码限流、旧密码校验、其他设备 Token 吊销。 - 登录失败按「IP + 用户名」5 次锁定 15 分钟;每 IP 总限流 30 次/分钟。 - Token 有效期缩短为 1 天,现有 Token 已同步调整。 - Nginx CSP、X-Frame-Options、nosniff、Referrer-Policy 等响应头。 - 操作日志密码字段脱敏。 - Composer / npm 依赖审计:当前无已知漏洞。 上线前必须: - 修改 `admin` 默认密码和 MySQL 默认密码。 - 配置 HTTPS 并开启 HSTS。 - `APP_ENV=production`、`APP_DEBUG=false`、`LOG_LEVEL=warning`。 - 设置 `SESSION_SECURE_COOKIE=true`,同源部署时 `CORS_ALLOWED_ORIGINS` 留空。 - 收紧 `.env`、数据库文件和 `tools/mysql8/data` 的权限。 完整结论与剩余风险见 [安全自查报告](docs/安全自查报告.md)。 ## 暂未实现 - 批次删除、编码删除(避免误删生产数据,使用停用代替)。 - 批次备注、批次状态、创建人筛选。 - 角色/权限管理界面(当前为固定角色映射)。 - 超过 10,000 条/次的异步生成与进度。 - 导出字段自定义(当前仅导出编码,一行一个)。 - 日志清理/归档、按时间段导出。