# blog **Repository Path**: candy_7/blog ## Basic Information - **Project Name**: blog - **Description**: 基于 ThinkPHP 6.1(PHP) + Vue 3(Element Plus) 的个人技术博客系统。 支持 7 套可切换前台主题、会员系统、评论、友链、SEO(sitemap/rss/robots)、富文本 + Markdown 双编辑器、自动标签匹配、数据库在线运维等完整功能。 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-07-19 - **Last Updated**: 2026-07-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: vuepress, Vue ## README # Candy Blog > 一个基于 **ThinkPHP 6.1(PHP) + Vue 3(Element Plus)** 的个人 / 技术博客系统。 > 内置 **7 套可切换前台主题**、会员系统、评论(楼中楼)、友链、SEO(sitemap / rss / robots)、富文本 + Markdown 双编辑器、自动标签匹配、数据库在线运维,以及 **前后端接口 RSA + AES 混合加密** 与 **前端存储加密**。 --- ## 目录 - [一、特性总览](#一特性总览) - [二、技术栈](#二技术栈) - [三、系统架构](#三系统架构) - [四、接口安全(加密方案)](#四接口安全加密方案) - [五、项目结构](#五项目结构) - [六、数据模型(数据表)](#六数据模型数据表) - [七、API 接口参考](#七api-接口参考) - [八、本地开发](#八本地开发) - [九、生产部署](#九生产部署) - [十、环境变量](#十环境变量) - [十一、权限与鉴权(RBAC)](#十一权限与鉴权rbac) - [十二、主题开发指南](#十二主题开发指南) - [十三、常用命令](#十三常用命令) - [十四、许可证](#十四许可证) --- ## 一、特性总览 ### 后台管理(访问路径 `/admin`) | 模块 | 说明 | |---|---| | **仪表盘** | 文章数、评论数、会员数、访问趋势等概览统计 | | **文章管理** | 列表筛选 / 发布 / 编辑 / 草稿 / 下架 / 软删除 / 批量删除;HTML + Markdown 双编辑器;**摘要自动截取**(发布时若留空则从正文截取 120 字);**自动生成标签**(点按钮按标题+正文匹配标签库并勾选) | | **分类管理** | CRUD + 文章数统计 + 删除前校验(有文章不可直接删) | | **标签管理** | CRUD + 使用次数统计 + 批量删除 | | **评论管理** | 审核 / 回复 / 批量审核 / 软删除(含楼中楼) | | **会员管理** | 列表 / 编辑 / 禁用 / 重置密码 | | **友链管理** | 申请审核 / 上架下架 / 排序 | | **主题页配置** | 多主题切换 / 页面显隐 / 模块显隐与文案自定义 / 主导航排序与图标 / 一键同步 / 重置 | | **文件管理** | 分组 / 上传 / 替换 / 删除;图片 / 视频 / 音频 / 文件四类 | | **数据库维护** | 备份 / 导出 / 删除备份;登录日志、操作日志、全量日志清理;表优化 / 修复;缓存清理;孤儿文件扫描;环境体检;安全审计;维护模式;计划任务配置 | | **系统管理** | 部门 / 员工 / 角色 / 菜单 / 功能点管理;权限(功能点)绑定 | | **个人设置** | 后台主题色 / 字号 / 夜间模式 / 布局(侧边栏 / 顶部 / 混合) | ### 前台站点 - **7 套主题**:`candy`、`geek`、`inkwash`(水墨)、`matrix`、`ocean`、`segmentfault`(思否风格)、`terminal`(终端风格) - 页面:首页 / 分类 / 标签 / 归档 / 时间轴 / 关于 / 友链 / 搜索 / 文章详情 / 自定义单页 - 文章详情支持 **Markdown 渲染** 与 **HTML 直出** - 会员:注册 / 登录 / 资料编辑 / 修改密码 / 我的评论 / 我的收藏 - 互动:点赞 / 收藏(action)、评论(含楼中楼回复) - SEO:自动生成 `sitemap.xml`、`rss.xml`、`robots.txt` - 可选 Live2D 看板娘 - **接口加密**:所有业务请求 / 响应走 RSA + AES 混合加密,Token 在 localStorage 中 AES 加密存储 --- ## 二、技术栈 | 层 | 技术 | 版本要求 | |---|---|---| | 后端框架 | ThinkPHP | ^6.1.0 | | 运行环境 | PHP | >= 7.2.5(**推荐 7.4+**,项目本地基于 7.4 验证) | | ORM | ThinkORM | ^2.0 | | 数据库 | MySQL | 8.0+ | | 缓存 / 会话 | 文件或 Redis | — | | 构建工具 | Composer | 2.x | | 前端框架 | Vue 3 + Vite | Vue ^3.5 / Vite ^6.0 | | UI 组件库 | Element Plus | ^2.10 | | 富文本编辑器 | WangEditor | ^5.1 | | Markdown 编辑器 / 渲染 | MdEditor / marked | ^18.0 | | 状态管理 | Pinia | ^2.3 | | 路由 | Vue Router | ^4.5 | | **前端加密** | **crypto-js(AES)+ jsencrypt(RSA)** | **^4.2 / ^3.5** | | **内容净化** | **DOMPurify** | **^3.4** | | 对象存储 | 阿里云 OSS SDK | ^2.7(可选,默认走本地 storage) | --- ## 三、系统架构 ``` ┌──────────────────────────────────────────────────────────────┐ │ 浏览器 │ │ ┌─────────────────────┐ ┌──────────────────────────┐ │ │ │ 后台管理 (SPA) │ │ 前台站点 (多主题 SPA) │ │ │ │ web/src/views/** │ │ web/src/site/themes/** │ │ │ │ Element Plus 界面 │ │ candy / geek / ... │ │ │ └──────────┬──────────┘ └──────────────┬───────────┘ │ └──────────────┼───────────────────────────────────┼─────────────┘ │ 同源 /api(代理 / 生产同域) │ ▼ ▼ ┌──────────────────────────────────────────────────────────────┐ │ Nginx(生产) / Vite Proxy(开发) │ │ /api/* ──────────────► public/index.php (ThinkPHP) │ │ /storage/* ──────────────► public/storage (静态资源,禁执行) │ │ / ──────────────► web/dist/index.html (SPA 回退) │ └──────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────┐ │ 后端应用 (ThinkPHP) │ │ route/admin.php → app/controller/admin/** (需登录 + RBAC) │ │ route/site.php → app/controller/api/blog/** (前台公开 API) │ │ middleware/EncryptMiddleware.php → 请求解密 + 响应加密 │ │ service/ → 业务服务(文章标签匹配 / 摘要截取 / 加解密) │ │ model/ → ThinkORM 模型 │ └──────────────────────────────────────────────────────────────┘ │ ▼ MySQL 8.0+ (candy_blog) ``` **关键设计点** - **前后端分离但同源部署**:生产环境前端静态产物 `web/dist` 与后端 `public/index.php` 同域,API 走相对路径 `/api`,无跨域问题。 - **双路由命名空间**: - `app/controller/admin/**` —— 后台管理接口,统一继承 `BaseController`,强制登录 + RBAC 鉴权。 - `app/controller/api/blog/**` —— 前台公开接口,继承各自基类,内部自行校验(如会员 Token)。 - **响应格式统一**:所有接口返回 JSON,结构为 `{ status, msg, data }`(`status=1` 成功,`status=0` 失败)。封装函数 `json_success()` / `json_error()`。 - **配置即代码,主题即配置**:前台页面 / 导航 / 模块显隐与文案全部由后台「主题页配置」驱动,主题模板只负责渲染,不在模板里写死导航项(详见[第十二章](#十二主题开发指南))。 --- ## 四、接口安全(加密方案) > 说明:浏览器是开放环境,前端加密只能**提高逆向成本 + 防传输层泄露**,无法绝对防住 F12 调试 / Hook。真正的防护仍需后端 HTTPS + 短时效 Token + 风控兜底。 ### 4.1 传输层:RSA + AES 混合加密 | 层 | 是否加密 | 说明 | |---|---|---| | 请求 / 响应 body(网络包) | ✅ AES-256-CBC | 每次请求携带 RSA 加密的随机 AES 会话密钥,后端无状态解密 | | RSA 公钥 | 明文下发 | `config/rsa/public_key.pem`,公开无妨(仅用于加密) | | RSA 私钥 | ✅ 仅后端持有 | `config/rsa/private_key.pem`,位于 `config/` 目录(非 web 根),已进 `.gitignore` | | localStorage 中的 Token | ✅ AES-256 存储加密 | 见 4.2 | **密钥交换流程** ``` ① 前端首次请求向 /api/crypto/public-key 拉取 RSA 公钥 ② 前端运行时随机生成 AES 会话密钥 ③ 用公钥加密该 AES 密钥 → 发给后端 ④ 后端用私钥解出 AES 密钥 ⑤ 之后双方用该 AES 密钥加解密所有业务数据(每次请求 IV 随机) ``` **接入方式** - 后端:`app/middleware/EncryptMiddleware.php` 在请求进入控制器前解密、响应输出前加密,控制器无感;`app/service/CryptoService.php` 封装 RSA 私钥解密 + AES 加解密;`app/controller/CryptoController.php` 提供公钥下发接口 `POST /api/crypto/public-key`。 - 前端:`web/src/utils/crypto.js` 封装拉公钥 + 生成 AES 密钥 + 加解密;`web/src/utils/request.js` 与 `web/src/site/utils/request.js` 的请求 / 响应拦截器自动加解密。 - 向后兼容:中间件只处理带请求头 `X-Encrypt: 1` 的请求,旧请求直接放行,可灰度切换。 - 开关:前端环境变量 `VITE_API_ENCRYPT=false` 可关闭接口加密(默认开启)。 **安全边界** - 公钥明文返回是 RSA 设计本质,不是漏洞:拿到公钥只能加密、无法解密(需私钥),从公钥数学上无法反推私钥。 - 私钥只在后端读取、从不下发,HTTP 无法直接下载。 - 公钥下发与所有接口均应置于 **HTTPS** 之下,防止中间人替换公钥。 ### 4.2 存储层:前端 localStorage Token 加密 - 新增 `web/src/utils/storage.js`,对白名单 key(`token`、`blog_member_token`)做 AES-256-CBC 加密后落地,格式 `ivBase64:cipherBase64`,随机 IV 保证相同明文每次密文不同。 - 其余非敏感 key(站点标题、布局、字号等)透明透传,零副作用。 - 读取自动解密;对历史明文 Token 做平滑降级兼容(直接返回原值),不会逼用户清缓存。 - 前端存储密钥编译进包,仅提升「直接抄 Token」的门槛,无法防住 F12 调试 JS / Hook 解密函数。 --- ## 五、项目结构 ``` blog/ ├── app/ # 后端应用(ThinkPHP) │ ├── BaseController.php # 后台控制器基类:登录解析 / RBAC 鉴权 / 响应封装 / 操作日志 │ ├── common.php # 全局函数:json_success / json_error / 密码哈希 等 │ ├── controller/ │ │ ├── admin/ # 后台管理 API(需登录 + 权限) │ │ │ ├── IndexController.php # 登录 / 配置 / 切换账号 / 权限校验 / 样式偏好 │ │ │ ├── MenuController.php # 菜单 + 功能点 CRUD │ │ │ ├── RoleController.php # 角色 + 功能点分配 │ │ │ ├── DepartmentController.php # 部门树 │ │ │ ├── EmployeeController.php # 员工 / 用户管理 │ │ │ ├── LoginLogController.php # 登录日志 │ │ │ ├── OperationLogController.php # 操作日志 │ │ │ ├── ConfigController.php # 系统配置 / 邮件测试 │ │ │ ├── CommonController.php # 文件上传 │ │ │ ├── CaptchaController.php # 验证码 │ │ │ ├── FileController.php # 文件管理(分组/上传/替换/删除) │ │ │ ├── DatabaseController.php # 数据库备份/维护/日志清理/体检/审计 │ │ │ └── blog/ # 博客业务模块 │ │ │ ├── DashboardController.php # 仪表盘统计 │ │ │ ├── ArticleController.php # 文章 CRUD + 自动标签 + 摘要补全 │ │ │ ├── CategoryController.php # 分类 │ │ │ ├── TagController.php # 标签 │ │ │ ├── CommentController.php # 评论审核/回复 │ │ │ ├── MemberController.php # 会员 │ │ │ ├── ConfigController.php # 站点配置 │ │ │ ├── ThemePageController.php # 主题页/模块配置(含主导航) │ │ │ └── FriendLinkController.php # 友链 │ │ ├── api/blog/ # 前台公开 API(无需后台登录) │ │ │ ├── SiteController.php # 站点数据:config/home/article/archive/category/tag/comment/action/member/friend-link/captcha │ │ │ └── SeoController.php # sitemap.xml / rss.xml / robots.txt │ │ └── CryptoController.php # 公钥下发接口(/api/crypto/public-key) │ ├── model/ # 数据模型(前缀 ca_,见第六章) │ │ ├── blog/ Employee/Role/Department/Menu/FuncModel/File/FileGroup/Config/... │ │ └── EmployeeLoginLog.php / EmployeeOperationLog.php │ ├── service/ │ │ ├── CryptoService.php # RSA 私钥解密 + AES 加解密封装 │ │ ├── SiteCacheService.php # 前台接口缓存(含 home / config) │ │ ├── blog/ArticleService.php # 文章构建(摘要截取)/ 标签匹配(matchTags)/ 字数统计 │ │ ├── TokenService.php # Token ↔ 员工 ID 映射 │ │ └── RateLimit.php │ └── middleware/ │ ├── CorsMiddleware.php # 跨域(已放行 X-Encrypt 头) │ └── EncryptMiddleware.php # 请求解密 + 响应加密 │ ├── config/ # ThinkPHP 配置 │ ├── app.php / database.php / cache.php / cookie.php / cors.php ... │ ├── rsa/ # RSA 密钥对(私钥已 .gitignore) │ │ ├── private_key.pem # 私钥(仅后端) │ │ └── public_key.pem # 公钥(下发给前端) │ └── admin_tools.php # 运维工具路径(mysqldump / PHP CLI / 备份目录) │ ├── route/ │ ├── admin.php # 后台管理路由 │ └── site.php # 前台站点路由(含 SEO 静态文件) │ ├── public/ # Web 入口 & 上传目录(对外可访问) │ ├── index.php # ThinkPHP 入口 │ ├── .htaccess # Apache 重写 │ └── storage/ # 上传文件根目录(对外 /storage,禁止执行 PHP) │ ├── runtime/ # 运行时(缓存/日志/数据库备份),需可写 │ └── backup/ # 数据库备份默认输出目录 │ ├── web/ # 前端项目(Vue 3 + Vite) │ ├── src/ │ │ ├── views/ # 后台管理页面(blog/ sys/ content/ logs/) │ │ ├── site/ # 前台主题系统 │ │ │ ├── themes/ # 7 套主题(candy/geek/inkwash/matrix/ocean/segmentfault/terminal) │ │ │ ├── composables/ # useSiteStore / useMember / useSiteRouter / useModule / useThemePages │ │ │ └── utils/api.js # 前台 API 封装(对应 api/blog-site/*) │ │ ├── api/ # 后台 API 封装 │ │ ├── components/ # 共用组件(Layout / WangEditor / MdEditor / NavIcon / MobileNav ...) │ │ ├── stores/ # Pinia(user / site / member ...) │ │ ├── router/ # 后台 & 前台路由 │ │ └── utils/ # request(axios 封装)/ crypto(接口加密)/ storage(Token 存储加密)/ tools │ ├── .env.development # 开发环境变量 │ ├── .env.production # 生产环境变量 │ ├── vite.config.js # Vite 配置(代理 / 多页 / HMR) │ └── package.json │ ├── deploy/ │ ├── README.md # 部署说明(双域名零配置差异) │ ├── nginx/ # Nginx 虚拟主机配置(blog.com.conf / blog.candyblog.cn.conf) │ └── candy_blog_full.sql # 数据库建库 + 种子 SQL │ ├── composer.json ├── .env # 后端环境变量(数据库 / 缓存 / 跨域等)—— 不入库 ├── think # ThinkPHP CLI 入口 └── README.md ``` --- ## 六、数据模型(数据表) > 数据库名约定 `candy_blog`(可在 `.env` 的 `[DATABASE]` 段修改)。**全部表统一前缀 `ca_`**(博客模块为 `ca_blog_*`,系统模块为 `ca_*`)。 ### 博客模块(`app/model/blog/`) | 表名 | 模型 | 说明 | |---|---|---| | `ca_blog_article` | `Article` | 文章。关键字段:`id` `title` `slug` `category_id` `status`(0草稿/1发布/2下架) `summary` `content` `content_type`(html/markdown) `word_count` `reading_time` `author_id` `tags`(多对多) `is_top` `views` `create_time` `delete_time`(软删除) | | `ca_blog_category` | `Category` | 文章分类 | | `ca_blog_tag` | `Tag` | 标签(含 `use_count` 使用次数) | | `ca_blog_article_tag` | `ArticleTag` | 文章-标签关联(联合主键 `article_id` + `tag_id`) | | `ca_blog_comment` | `Comment` | 评论(含 `parent_id` 楼中楼、`guest_name`、`status` 审核状态) | | `ca_blog_member` | `Member` | 前台注册会员(邮箱/密码/昵称/状态) | | `ca_blog_friend_link` | `FriendLink` | 友链(名称/URL/logo/排序/状态/审核) | | `ca_blog_action` | `Action` | 用户行为记录(点赞 / 收藏,按 `type` 区分) | | `ca_blog_page` | `Page` | 自定义单页(关于 / 友链等可由数据库驱动) | | `ca_blog_config` | `Config` | 站点配置键值对 | | `ca_blog_theme_page` | `ThemePage` | 主题页配置(页面显隐 / 排序 / 自定义 URL / 文案) | | `ca_blog_theme_page_module` | `ThemePageModule` | 主题页模块配置(模块显隐 / 标题 / 文案 / 限制条数) | ### 系统模块(`app/model/`) | 表名 | 模型 | 说明 | |---|---|---| | `ca_employee` | `Employee` | 后台员工 / 管理员(`is_admin` 超管标识、`is_login`/`status` 状态) | | `ca_role` | `Role` | 角色(关联功能点 `func_id`) | | `ca_department` | `Department` | 部门(树形) | | `ca_menu` | `Menu` | 后台菜单(树形) | | `ca_func` | `FuncModel` | 功能点 / 权限点(含 `menu_url` 用于 URL 权限映射) | | `ca_config` | `Config` | 系统级配置 | | `ca_file` | `File` | 文件记录 | | `ca_file_group` | `FileGroup` | 文件分组 | | `ca_employee_login_log` | `EmployeeLoginLog` | 员工登录日志 | | `ca_employee_operation_log` | `EmployeeOperationLog` | 员工操作日志 | --- ## 七、API 接口参考 > 所有接口均为 `POST`(除 SEO 静态文件为 `GET`),请求体 `application/json` 或 `form-data`,统一返回 `{ status, msg, data }`(成功 `status=1`,失败 `status=0`)。 > 后台接口需在请求头携带 `X-Token`(或 `Authorization: Bearer `)。 ### 7.1 后台接口(`route/admin.php`) **鉴权 / 基础** | 接口 | 控制器@方法 | 说明 | |---|---|---| | `api/index/login` | `IndexController@login` | 员工登录(获取 Token) | | `api/index/send_login_email_code` | `IndexController@sendLoginEmailCode` | 发送登录验证码邮件 | | `api/index/config` | `IndexController@config` | 后台基础配置(菜单/权限/个人信息) | | `api/index/switch_user` | `IndexController@switchUser` | 切换登录账号(超管功能) | | `api/index/back_super_admin` | `IndexController@backSuperAdmin` | 退出「切换账号」回到超管 | | `api/index/check_permissions` | `IndexController@checkPermissions` | 当前账号权限校验 | | `api/index/edit_style` | `IndexController@editStyle` | 保存后台样式偏好(主题色/字号/布局) | | `api/common/upload` | `CommonController@upload` | 文件上传 | | `api/common/captcha` | `CaptchaController@index` | 验证码(GET/POST) | **系统管理** | 接口 | 控制器@方法 | |---|---| | `api/menu/index` `add` `edit` `delete` | `MenuController` | | `api/func/index` `add` `edit` `delete` | `MenuController`(功能点) | | `api/role/index` `add` `edit` `delete` `get_func` `set_func` | `RoleController` | | `api/department/index` `add` `edit` `delete` | `DepartmentController` | | `api/employee/index` `add` `edit` `delete` `edit_pass` `set_field` | `EmployeeController` | | `api/employee-login-log/index` | `LoginLogController` | | `api/employee-operation-log/index` | `OperationLogController` | | `api/config/index` `edit` `test_mail` | `ConfigController` | **文件管理** | 接口 | 控制器@方法 | |---|---| | `api/file/index` `delete` `edit` `save_upload` `replace` `group_index` `group_add` `group_edit` `group_delete` | `FileController` | **数据库维护** | 接口 | 控制器@方法 | |---|---| | `api/database/index` | 概览 | | `api/database/backup` `export` `delete_backup` | 备份 / 导出 / 删备份 | | `api/database/clear_login_log` `clear_operation_log` `clear_all_logs` `clear_logs_by_range` | 日志清理 | | `api/database/tables` `optimize_tables` `repair_tables` | 表信息 / 优化 / 修复 | | `api/database/cache_info` `clear_cache` | 缓存 | | `api/database/orphan_files` `delete_orphan_files` | 孤儿文件扫描 / 删除 | | `api/database/health_check` | 环境体检 | | `api/database/export_log_csv` | 日志导出 CSV | | `api/database/admin_security_check` `permission_check` `audit_log` | 安全审计 / 权限校验 / 审计日志 | | `api/database/offline_users` `set_maintenance_mode` | 用户离线 / 维护模式 | | `api/database/schedule_config` `save_schedule_config` | 计划任务配置 | **博客模块** | 接口 | 控制器@方法 | |---|---| | `api/blog/dashboard/index` | 仪表盘统计 | | `api/blog/article/index` `detail` `add` `edit` `delete` `batch_delete` `set_field` `auto_tags` `fill_summaries` | `ArticleController` | | `api/blog/category/index` `add` `edit` `delete` | `CategoryController` | | `api/blog/tag/index` `add` `edit` `delete` `batch_delete` | `TagController` | | `api/blog/comment/index` `audit` `batch_audit` `delete` `reply` | `CommentController` | | `api/blog/member/index` `edit` `disable` `reset_password` | `MemberController` | | `api/blog/config/get` `edit` | 站点配置 | | `api/blog/theme-page/index` `page-toggle` `page-edit` `module-toggle` `module-edit` `module-sync-all` `reset` `public` | `ThemePageController` | | `api/blog/friend-link/index` `detail` `add` `edit` `delete` `batch_delete` `audit` `batch_audit` | `FriendLinkController` | **接口加密(前端自动调用)** | 接口 | 控制器@方法 | 说明 | |---|---|---| | `api/crypto/public-key` | `CryptoController@publicKey` | 返回 RSA 公钥(明文,前端用它加密 AES 会话密钥) | ### 7.2 前台接口(`route/site.php`) | 接口 | 前端函数 | 说明 | |---|---|---| | `api/blog-site/config` | `siteConfig` | 站点配置(分类/标签/主题页/全局文案) | | `api/blog-site/home` | `siteHome` | 首页数据(最新/热门/置顶/最新评论 + 作者),各栏目条数写死于 `SiteController::home()` | | `api/blog-site/article/list` | `articleList` | 文章列表(分页/分类/标签/关键词) | | `api/blog-site/article/detail` | `articleDetail` | 文章详情(Markdown/HTML + 作者 + 标签 + 上下篇) | | `api/blog-site/archive` | `archiveList` | 归档(按年/月分组) | | `api/blog-site/category/tree` | `categoryList` | 分类树 | | `api/blog-site/tag/list` | `tagList` | 标签列表 | | `api/blog-site/comment/list` | `commentList` | 评论列表(含楼中楼) | | `api/blog-site/comment/add` | `commentAdd` | 发表评论(需登录 + 验证码) | | `api/blog-site/action/toggle` | `actionToggle` | 点赞 / 收藏切换 | | `api/blog-site/member/register` `login` `logout` `profile` `update` `password` `comments` `favorites` | 对应函数 | 会员相关 | | `api/blog-site/friend-link/list` `apply` | `friendLinks` `friendLinkApply` | 友链列表 / 申请 | | `api/blog-site/captcha` | — | 验证码(GET/POST) | | `sitemap.xml` `rss.xml` `robots.txt` | — | SEO 文件(GET) | ### 7.3 典型请求 / 响应 **请求(发布文章)** ```http POST /api/blog/article/add Headers: X-Token: Content-Type: application/json { "title": "PHP 入门教程", "category_id": 3, "content": "

PHP 是一门...

", "content_type": "html", "tag_ids": [1, 5, 8], "status": 1 } ``` **响应(统一结构 `{ status, msg, data }`)** ```json { "status": 1, "msg": "操作成功", "data": { "id": 102, "author": { "id": 1, "name": "admin", "nickname": "admin" } } } ``` > 详情接口(如 `articleDetail`)返回的 `author` 字段结构:`{ id, name, nickname }`,前端统一使用 `author?.nickname` 显示作者名。 --- ## 八、本地开发 ### 环境要求 - PHP >= 7.2.5(推荐 7.4+),扩展:`pdo_mysql`、`mbstring`、`fileinfo`、`openssl`、`json`、`curl` - MySQL 8.0+ - Node.js >= 18 - Composer 2.x ### 步骤 1:后端 ```bash # 1. 安装 PHP 依赖 composer install # 2. 准备 .env(项目根目录已含 .env;按下方「环境变量」小节确认数据库连接) # [DATABASE] 段:HOSTNAME / DATABASE / USERNAME / PASSWORD / HOSTPORT # 3. 导入数据库(首次) mysql -uroot -p candy_blog < deploy/candy_blog_full.sql ``` ### 步骤 2:前端 ```bash cd web npm install # 启动开发服务器(默认 http://localhost:5174) npm run dev ``` 开发模式下 `vite.config.js` 已配置代理:将 `/api` 与 `/storage` 转发到后端(默认 `http://blog.com`),前端与后端同源访问,无跨域问题。 ### 步骤 3(推荐):用域名访问 在 `hosts` 中加入: ``` 127.0.0.1 blog.com ``` 用 phpstudy / 本地 nginx 新建站点 `blog.com`,**根目录指向本项目的 `public/`**。然后浏览器打开 **`http://blog.com:5174/admin`** 即可开发(文件预览/上传走当前域名,零跨域)。 > 详细本地开发双方案(含纯 80 端口反代)见 `deploy/README.md`。 --- ## 九、生产部署 > 设计目标:**一次构建、打包上传,本地与生产零配置差异**(数据库账号/密码/库名一致,`.env` 通用)。详见 `deploy/README.md`。 ### 1. 构建前端 ```bash cd web npm install # 首次或依赖变化时 npm run build # 产物输出到 web/dist ``` ### 2. 上传清单(保持目录结构) ``` web/dist/ → 服务器 /var/www/candyblog/web/dist app/ → /var/www/candyblog/app config/ → /var/www/candyblog/config route/ → /var/www/candyblog/route public/ → /var/www/candyblog/public (含 index.php / storage) vendor/ → /var/www/candyblog/vendor .env → /var/www/candyblog/.env runtime/ → /var/www/candyblog/runtime (需可写) ``` > 注意:`config/rsa/private_key.pem`(RSA 私钥)**务必随项目一起上传**到服务器,且确保 Web 不可直接访问该目录(它位于 `config/` 而非 `public/`);切勿将私钥提交到公开仓库。 ### 3. 应用 Nginx 配置 将 `deploy/nginx/blog.candyblog.cn.conf` 放到服务器 nginx 的 `sites-enabled/`(或 `vhosts/`),修改其中的路径占位(`/var/www/candyblog`、`/etc/ssl/candyblog.cn`)与 `fastcgi_pass` 端口,然后: ```bash nginx -t && nginx -s reload ``` **Nginx 配置要点** - 站点根目录 = `web/dist`(SPA) - `/api/*` → ThinkPHP(`public/index.php`) - `/storage/*` → 静态托管后端上传目录,并 **禁止执行 PHP**(防上传马) - 其余路径 `try_files ... /index.html`(SPA 路由回退) - 80 → 443 强制 HTTPS(加密方案的安全前提) ### 4. 权限 ```bash chmod -R 755 runtime/ chmod -R 755 public/storage/ # 确保 runtime/ 与 public/storage/ 对 PHP 进程(www-data / nginx)可写 ``` ### 5. 注意 - 上传目录 `public/storage` 已配置禁止执行 PHP,**切勿在该目录放开脚本执行权限**。 - 如需本地调试看错误,仅在本地临时改 `APP_DEBUG=true`,上传前还原为 `false`。 --- ## 十、环境变量 ### 后端 `.env`(不入库,需自行创建 / 已随仓库提供) 采用 ThinkPHP 段落式格式: ```ini APP_DEBUG = false [APP] DEFAULT_TIMEZONE = Asia/Shanghai CORS_ALLOWED_ORIGINS = http://blog.com,https://blog.com,https://candyblog.cn,https://www.candyblog.cn [DATABASE] TYPE = mysql HOSTNAME = 127.0.0.1 DATABASE = candy_blog USERNAME = candy_blog PASSWORD = your_password HOSTPORT = 3306 CHARSET = utf8mb4 PREFIX = ca_ DEBUG = false [LANG] default_lang = zh-cn ``` | 变量 | 段 | 说明 | 默认值 | |---|---|---|---| | `APP_DEBUG` | 全局 | 调试模式(生产务必 false) | `false` | | `CORS_ALLOWED_ORIGINS` | `[APP]` | 跨域白名单(逗号分隔,独立部署 API 时填前端域名) | 空=同源 | | `HOSTNAME` | `[DATABASE]` | 数据库主机 | `127.0.0.1` | | `DATABASE` | `[DATABASE]` | 数据库名 | `candy_blog` | | `USERNAME` / `PASSWORD` | `[DATABASE]` | 数据库账号 / 密码 | — | | `HOSTPORT` | `[DATABASE]` | 数据库端口 | `3306` | | `CHARSET` | `[DATABASE]` | 字符集 | `utf8mb4` | | `PREFIX` | `[DATABASE]` | 表前缀 | `ca_` | | `admin_tools.mysql_bin` | — | `mysqldump` 所在 bin 目录(留空自动探测) | 自动探测 | | `admin_tools.php_bin` | — | PHP CLI 可执行目录(留空自动探测) | 自动探测 | | `admin_tools.database_backup_dir` | — | 数据库备份输出目录(留空用 `runtime/backup`) | `runtime/backup` | > 线上 Linux 若 `mysqldump` 不在 PATH,可在 `.env` 设置 `admin_tools.mysql_bin=/usr/local/mysql`(代码会探测 `{dir}/bin/mysqldump` 与 `{dir}/mysqldump`)。 ### 前端 `.env.production` | 变量 | 说明 | 默认值 | |---|---|---| | `VITE_API_BASE` | API 基地址。双域名同源部署**留空**(走相对 `/api`);API 独立部署时填完整域名 | 空 | ### 前端 `.env.development` | 变量 | 说明 | 默认值 | |---|---|---| | `VITE_USE_MOCK` | 是否走 Mock 数据(`false`=真实后端) | `false` | | `VITE_API_ENCRYPT` | 是否启用接口 RSA+AES 加密(`false`=关闭,便于调试) | `true` | --- ## 十一、权限与鉴权(RBAC) 后台接口统一继承 `app/BaseController`,鉴权流程: 1. **Token 解析**(优先级):`X-Token` 头 → `Authorization: Bearer ` → `POST token` → 兜底 Session `employee_id`。 2. **登录态校验**:`employee.status=1` 且 `is_login=1` 才放行。 3. **RBAC 校验**: - 角色 `is_admin=1` → 超管,跳过检查; - 否则将当前请求 URL 转成候选权限键(如 `/blog/article/add`),与角色已分配功能点 `func.menu_url` 求交集; - 无法匹配到任何权限键时:读接口(`index`/`detail`/`tree` 等)默认放行,写接口默认拒绝。 4. **白名单**:子类可声明 `$noLoginAction`(免登录)与 `$noAuthAction`(免权限校验),前台 API 命名空间自动免登录。 > 老 URL 会自动映射为新权限键(如 `employee/list` → `sys/user/list`),见 `BaseController::permissionAlias()`。 --- ## 十二、主题开发指南 > ⚠️ **重要约定**:前台主题的 **Layout.vue 只允许通过 `useModule().items('nav')` 读取后台「主导航」配置来渲染导航**,严禁在模板里写死分类 / 标签 / 任意导航项,也严禁在 JS 里 `continue` 掉 `category` / `tag` 项。否则后台对导航的**排序、图标、显隐**修改将不生效(这是项目踩过的坑)。所有 7 套主题目前均已统一为「配置驱动」。 前台主题位于 `web/src/site/themes//`,每套主题包含: ``` themes// ├── pages/ # 页面组件(Home / Article / Archive / About / Category / Tag / Search ...) ├── Layout.vue # 布局组件(页头/侧栏/页脚)—— 导航必须读配置 └── styles.css # 主题样式 ``` **共用能力(通过 composables 注入,无需自行请求)** ```js import { useSiteStore } from '@/site/composables/useSiteStore' // 站点全局数据(分类/标签/配置) import { useMember } from '@/site/composables/useMember' // 会员登录态 import { useSiteRouter } from '@/site/composables/useSiteRouter' // 路由跳转(slug→路由映射) import { useModule } from '@/site/composables/useModule' // 模块配置(可见性/文案/限制条数) import { useThemePages } from '@/site/composables/useThemePages' // 主题页面开关 ``` **新增一套主题的步骤** 1. 复制 `themes/candy` 为 `themes/`; 2. 修改 `styles.css` 与 `pages/*`、替换 Logo / 配色; 3. 在 `Layout.vue` 中通过 `useModule().items('nav')` 渲染导航,并使用 `` 渲染后台配置的图标; 4. 在后台 **主题页配置** 中登记该主题,并设置页面/模块显隐与文案。 **主题页模块配置** - 后台「主题页配置」可控制:哪些页面显示(首页/关于/友链/归档/时间轴)、各页面模块显隐、自定义标题与文案、主导航的排序与图标、模块数据条数限制。 - 主题内通过 `useModule().visible('module_key')` 与 `useModule().text('module_key', 'title', '默认值')` 读取配置,配置缺失时优雅降级(显示默认文案)。 - 首页热门 / 置顶 / 最新评论等栏目条数当前**写死在 `SiteController::home()` 的 `limit()` 中**(后台仅能控制「是否显示」),如需后台可调,需在 `blog_theme_schema` 增加对应字段并接回 `$cfg`。 --- ## 十三、常用命令 ```bash # ===== 后端 ===== composer install # 安装 PHP 依赖 php think optimize:route # 生成路由缓存(生产推荐) php think clear # 清空运行时缓存 php think run Schedule # 若启用计划任务 # ===== 前端 ===== cd web npm install # 安装前端依赖 npm run dev # 启动开发服务器(默认 :5174) npm run build # 生产构建(输出 web/dist) npm run preview # 预览生产构建 npm run test # 运行单元测试(Vitest) # ===== 数据库备份 ===== mysqldump -uroot -p candy_blog > backup_$(date +%Y%m%d).sql # 或在后台「数据库维护 → 备份」一键操作(自动探测 mysqldump 路径) ``` --- ## 十四、许可证 MIT License。详见 `LICENSE`。