PHP新闻宣传审核考评系统:审核流、计分与权限实战

📅 发布时间:2026/10/3 4:09:54
PHP新闻宣传审核考评系统:审核流、计分与权限实战
这套系统的价值不只是“能跑”我更看重的是它把审核流、分数量化、权限管控三件事组合进了一个轻量级的 PHP 项目里。对很多机关单位的宣传科、企业品牌部、新闻中心来说日常最头疼的其实就是稿件的流转状态不清楚、考核打分凭感觉、月底统计靠人工。有了这套系统这些都能在线上解决而且部署成本极低。后面我会从需求拆解、数据表设计、核心代码实现、实测部署、踩坑记录一条线讲完想直接拿去改造成自己业务场景的朋友可以直接跳到自己关心的章节。1. 项目整体设计与需求拆解1.1 新闻宣传审核考评到底要解决什么问题先说清楚业务背景。宣传部门的工作流程一般是这样的记者或通讯员写完稿件提交给科室负责人初审初审过了送到分管领导终审终审通过后再安排发布到微信公众号、网站、报纸等平台。月底或者季度末上级要对各单位、各科室、甚至每个通讯员的投稿量和采用量进行排名打分作为绩效依据。这个流程看着不复杂但实际运行起来问题不少。比如纸质稿单靠微信群传来传去稿件到底在谁手里等着审、审到哪一步了全靠人工问月底统计采用篇数时得人工翻聊天记录、翻网站后台漏记错记是常事打分标准又不统一同样是市级媒体采用有的人算 5 分有的人算 3 分月底核对时常常扯皮。所以这类系统的核心需求可以拆成三块内容管理稿件的提交、编辑、附件上传、版本留痕。审核流控制自定义审核层级每一步都有明确的负责人和操作记录。量化考评根据媒体级别、稿件质量、传播数据等维度自动计算得分并能按部门、按人、按时间段汇总排名。1.2 功能模块边界怎么划把这套源码拆开看模块划分是比较清晰的适合做二次开发的人直接参考模块负责内容关键页面用户与权限登录、角色管理、部门管理login.php、user_list.php稿件管理投稿、编辑、列表、详情article_add.php、article_list.php审核中心待审列表、通过/退回处理review_list.php、review_detail.php考评计算计分规则配置、得分明细score_list.php、score_rank.php统计报表按周期、部门、人员筛选导出report.php、export.php这样的边界划分有它的道理。稿件管理和审核中心分开是因为两种角色的操作习惯完全不同——普通投稿人只需要看到“我的稿件”和状态审核人员则需要一个集中的“待办箱”。考评计算独立成模块是为了让计分规则可以动态调整不用每次改需求都动代码。我见过不少半成品系统把审核和计分写死在一起想调分数权重就得改 SQL这在后期维护时相当被动。1.3 角色权限的设计取舍系统里我设置了四种角色管理员系统配置人员和最终仲裁、审核员对应科室负责人和分管领导、投稿人记者、通讯员、访客只读账号给上级查阅用。这里有一个很关键的设计细节——审核员不是单一角色而是通过 level 字段区分审核层级比如 level1 是初审level2 是终审。这样做的原因是实际业务里不同科室的审核层级数量不一样有的科室只需要一级审核有的需要三级。用 level 字段动态控制就不用每个层级建一套角色表。权限的粒度上也没有做得很细只控制了“菜单可见性数据范围”。数据范围是另一个容易踩坑的地方——部门负责人应该只能看到本部门的稿件分管领导能看到全单位稿件。这个逻辑在查询列表的 SQL 里根据 session 中的 role_type 动态拼接 WHERE 条件来实现。非要做成按钮级的权限也行但对这种内部系统来说那是过度设计徒增维护成本。2. 数据表设计与核心技术点2.1 数据表结构讲解整套系统的表不算多核心是这六张users用户、departments部门、articles新闻稿件、attachments附件、review_logs审核记录、score_rules计分规则。users 表里需要特别注意的字段是dept_id和role_type前者把用户归属到部门后者控制权限范围。articles 表是核心下面看实际建表语句的思路CREATE TABLE articles ( id int(11) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 稿件标题, content text COMMENT 正文内容, author_id int(11) NOT NULL COMMENT 投稿人ID, dept_id int(11) NOT NULL COMMENT 所属部门ID, media_type tinyint(1) DEFAULT 1 COMMENT 发布媒体类型:1内部平台,2市级媒体,3省级媒体,4国家级媒体, status tinyint(1) DEFAULT 1 COMMENT 状态:1待初审,2初审通过,3初审退回,4待终审,5已发布,6终审退回, publish_url varchar(500) DEFAULT NULL COMMENT 发布链接, publish_time datetime DEFAULT NULL COMMENT 发布时间, reading_count int(11) DEFAULT 0 COMMENT 阅读量, created_at datetime DEFAULT NULL, updated_at datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_status (status), KEY idx_dept (dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT新闻稿件表;这里有两个设计细节值得说说。第一为什么 status 不用字符串而用 tinyint因为后端代码只要定义一组常量就能管理所有状态配合 PHP 里的 match 表达式可以很干净地映射状态中文名而且 MySQL 对整数索引的效率更高排序和筛选都更快。第二为什么把 media_type 和 reading_count 直接冗余在 articles 表里因为考评计分时需要频繁读取这两个字段如果单独建表关联每次算分都要 JOIN统计数据量大时会明显变慢。媒体类型固定几个枚举值就行阅读量虽然实时性不强但每天同步一次足够。review_logs 表的重点在于每条审核记录都是追加写不改不删。这样设计的好处是能完整回溯一个稿件的审核历史——谁在什么时间初审的、写了什么意见、终审为什么退回清清楚楚。出了问题找责任人的时候一张表就能查明白不用翻聊天记录。2.2 审核状态机的流转逻辑审核流程是整个系统最核心的部分本质是一个有限状态机。我画不出流程图但状态流转用文字说也一目了然投稿人 submit 后status 1待初审。初审人 pass 后如果系统配置了终审环节status 变为 4待终审没有终审环节则直接变为 5已发布。初审人 reject 后status 3初审退回稿件退回到投稿人修改后可重新提交。终审人 pass 后status 5已发布此时要求补充发布链接和发布时间。终审人 reject 后status 6终审退回。这个状态机的完整实现我稍后在第五部分给具体代码。这里只强调一个容易犯错的地方退回之后重新提交的状态变化。我见过有人把退回重提设计成新增一条稿件记录导致同一个稿件的版本历史被拆成两条统计时出现重复计数。这套系统的做法是在 re_submit 操作里直接把原记录 status 改回 1保留原有 id这样一条稿件从头到尾只有一条记录统计口径绝对不会错。2.3 计分规则的弹性配置考评计分不能写死在代码里这是这类系统的铁律。因为领导的考核风向经常变这季度看重省级以上媒体采用量下季度可能又看重公众号阅读量。如果规则写死每变一次需求就要改一次代码上线一次折腾死人。所以 score_rules 表的设计是这样的CREATE TABLE score_rules ( id int(11) NOT NULL AUTO_INCREMENT, rule_name varchar(100) NOT NULL, media_type tinyint(1) DEFAULT 0 COMMENT 适用媒体类型0为全部, quality_level tinyint(1) DEFAULT 0 COMMENT 质量等级0为全部, base_score decimal(5,2) NOT NULL DEFAULT 0, extra_score decimal(5,2) NOT NULL DEFAULT 0 COMMENT 额外加分, is_active tinyint(1) DEFAULT 1, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;我来解释一下规则怎么匹配。系统里每篇稿件发布后会有一个质量等级字段由终审人评定再加上它本身的 media_type。算分的时候程序遍历所有 is_active1 的规则只要规则的 media_type 或 quality_level 与稿件匹配就把 base_score 加上如果稿件满足特定条件再叠加 extra_score。举个例子默认规则是市级媒体采用 10 分省级媒体 20 分国家级 40 分如果终审认定稿件质量等级为“优秀”再加 5 分。月底汇总时所有分数自然累积按部门求和、按人求和排名就出来了。我把这个逻辑封装成一个独立的ScoreCalculator类输入稿件 ID输出明细数组。后续要调权重只需在后台改规则表代码一行业不用动。这就是“配置优于编码”的典型应用。3. PHP 技术栈选型分析与环境部署3.1 为什么这套系统选择 PHP 而不是 Java 或 Python可能有人会问现在 Python 和后端框架那么多为什么还要用 PHP说实话对这类新闻宣传内部管理系统PHP 的优势是实打实的部署门槛极低只要服务器上有 PHP 运行环境配一个 Nginx 或 Apache外加 MySQL代码一扔就能跑。不像 Java 那套要装 Tomcat、配环境变量也不像 Python 要管理虚拟环境和各种依赖包。运行成本低这类系统并发量不高一个单位可能就几十到几百个用户PHP-FPM 的进程模型完全够用服务器配置要求很低1核2G 的小机器跑得稳稳的。开发迭代快PHP 是解释型语言改完代码直接生效不用编译重启。对于业务规则经常调整的内部系统来说这个优势太香了。生态丰富上传图片、导出 Excel、生成验证码这些周边功能都有成熟的开源类库不用从零手写。当然PHP 也常被诟病安全性问题但这主要是历史版本和开发者不规范造成的。用 PHP 7.4 以上版本配合 PDO 预处理和密码哈希函数安全性和现代后端语言没有本质差距。3.2 环境要求与快速部署步骤这套源码我在本地实测用的环境是PHP 7.4 MySQL 5.7 Apache 2.4放在 Nginx PHP-FPM 上也完全兼容。下面把部署步骤走一遍照着做基本不会出问题。第一步目录复制。把源码压缩包解压到 Web 根目录比如 Apache 的htdocs或 Nginx 的html目录确认目录结构长这样/ ├── admin/ # 后端管理及审核功能 │ ├── article_list.php │ ├── review_list.php │ ├── score_rank.php │ ├── rule_config.php │ └── ... ├── api/ # 前后端交互接口 │ ├── login.php │ ├── article_submit.php │ ├── review_action.php │ └── ... ├── assets/ # 静态资源(CSS/JS/图片) ├── config/ │ └── config.php # 系统配置文件 ├── includes/ │ ├── db.php # 数据库连接 │ ├── auth.php # 登录验证与权限 │ └── ScoreCalculator.php ├── uploads/ # 附件上传目录(需要写权限) └── install.sql # 数据库初始化脚本第二步导入数据库。用 phpMyAdmin 或命令行执行install.sql它会自动建库建表并写入默认管理员账号和初始计分规则。命令行导入是这么写的mysql -u root -p install.sql第三步改配置文件。打开config/config.php把数据库连接信息改成你自己的define(DB_HOST, localhost); define(DB_NAME, news_review_system); define(DB_USER, root); define(DB_PASS, your_password); define(BASE_URL, http://localhost/news_review); // 按实际路径修改第四步设置 uploads 目录写权限。这一步最容易被忽略。在 Linux 服务器上执行chmod -R 755 uploads/ chown -R www-data:www-data uploads/第五步打开浏览器访问后台地址http://localhost/news_review/admin/用默认账号 admin / admin123 登录进去后第一件事就是到“系统设置”里改掉密码。提示PHP 7.4 以下环境跑这套代码会报语法错误项目里多处用了箭头函数和 null 合并运算符这些语法是 7.0 之后才支持的。建议直接用 PHP 7.4别在旧版本上浪费时间。3.3 伪静态与安全配置建议如果要把系统部署到公网服务器还有几个配置要处理。Apache 下面的伪静态配置主要是为了隐藏入口文件对内部系统来说其实非必需但为了 URL 好看可以在.htaccess里加上简单的重写规则。更重要的是 Nginx 的配置里有几个需要留意的点location / { index index.php; # 防止直接访问 .sql 和 .log 文件 location ~* \.(sql|log)$ { deny all; } # PHP 文件交给 fpm 处理 location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; } }上传目录 uploads 建议禁止执行 PHP 脚本不然攻击者传一个图片马上去访问就能 getshell。Nginx 配置里加一行location ~* /uploads/.*\.php$ { deny all; }这是很多 PHP 项目被拿下的高发原因非技术背景的站长尤其容易漏掉。系统本身代码再安全这一步漏了也白搭。4. 核心功能实现从登录鉴权到审核打分4.1 登录与 Session 权限控制实现系统基础是登录鉴权实现上用了 PHP 的 Session 和内置密码哈希函数。登录接口的核心逻辑不长但每行都很关键// api/login.php session_start(); $username trim($_POST[username] ?? ); $password $_POST[password] ?? ; $stmt $pdo-prepare(SELECT * FROM users WHERE username :username LIMIT 1); $stmt-execute([:username $username]); $user $stmt-fetch(); if ($user password_verify($password, $user[password_hash])) { $_SESSION[uid] $user[id]; $_SESSION[username] $user[username]; $_SESSION[role_type] $user[role_type]; $_SESSION[dept_id] $user[dept_id]; echo json_encode([code 0, msg 登录成功]); } else { echo json_encode([code 1, msg 用户名或密码错误]); }密码字段这里用password_hash()生成的哈希后台设密码也走同一函数。用 PHP 官方推荐的加密方式比网上那些 md5 加盐的老代码不知道安全多少倍。登录之后的每个页面前端都先引入includes/auth.phpsession_start(); if (!isset($_SESSION[uid])) { header(Location: /login.php); exit; } // 可选权限分级校验 function require_role($minLevel) { if (($_SESSION[role_type] ?? 0) $minLevel) { die(权限不足无法访问该页面); } }4.2 稿件提交流程的实现细节投稿页面包含标题、正文、媒体类型、附件上传。附件上传用的是标准的上传处理逻辑但有几个细节我特意处理过// api/article_submit.php $uploadDir __DIR__ . /../uploads/; // 1. 校验文件大小和类型 $allowedExt [jpg, png, gif, pdf, doc, docx]; $ext strtolower(pathinfo($_FILES[attachment][name], PATHINFO_EXTENSION)); if (!in_array($ext, $allowedExt)) { die(json_encode([code 1, msg 不允许的文件类型])); } // 2. 重命名文件名防止路径穿越和重复 $newName date(YmdHis) . _ . mt_rand(1000, 9999) . . . $ext; $targetPath $uploadDir . $newName; // 3. 移动上传文件 if (!move_uploaded_file($_FILES[attachment][tmp_name], $targetPath)) { die(json_encode([code 1, msg 上传失败请检查目录权限])); }文件名改成时间戳加随机数这一点很关键用原始文件名直接把图片传上去的话一是容易重名覆盖二是如果文件名里带了特殊字符还可能引发安全问题。存到数据库的附件记录里还同步存了原始文件名方便下载时还原。4.3 审核动作与状态流转代码审核动作是这套系统的核心操作我封装成了一个独立函数随时可以调用// includes/review_action.php function reviewAction(PDO $pdo, int $articleId, int $reviewerId, string $action, string $comment ) { $pdo-beginTransaction(); try { // 加锁读取当前稿件状态防止并发重复审核 $stmt $pdo-prepare(SELECT * FROM articles WHERE id :id FOR UPDATE); $stmt-execute([:id $articleId]); $article $stmt-fetch(); $newStatus 0; if ($action pass) { if ($article[status] 1) { // 初审通过看是否需要终审 $newStatus needsFinalReview($article) ? 4 : 5; } elseif ($article[status] 4) { // 终审通过 $newStatus 5; } } elseif ($action reject) { if ($article[status] 1) { $newStatus 3; // 初审退回 } elseif ($article[status] 4) { $newStatus 6; // 终审退回 } } // 更新稿件状态 if ($newStatus 5) { $stmt $pdo-prepare(UPDATE articles SET status :status, publish_url :url, publish_time NOW(), updated_at NOW() WHERE id :id); $stmt-execute([ :status $newStatus, :url $_POST[publish_url] ?? , :id $articleId ]); } else { $stmt $pdo-prepare(UPDATE articles SET status :status, updated_at NOW() WHERE id :id); $stmt-execute([:status $newStatus, :id $articleId]); } // 写入审核日志 $stmt $pdo-prepare(INSERT INTO review_logs (article_id, reviewer_id, action, comment, created_at) VALUES (:aid, :rid, :action, :comment, NOW())); $stmt-execute([ :aid $articleId, :rid $reviewerId, :action $action, :comment $comment ]); $pdo-commit(); return [code 0, msg 操作成功, new_status $newStatus]; } catch (Exception $e) { $pdo-rollBack(); return [code 1, msg 操作失败: . $e-getMessage()]; } }这段代码里有三个细节我特别说明一下。第一FOR UPDATE锁行——我实测时发现两个审核员同时打开同一个稿件、同时点了通过如果没有行锁状态会被后面的覆盖造成“一人审核通过另一人的操作无记录”这种脏数据。加锁之后第二个提交的操作会等待第一个完成然后再读取最新状态这就不会冲突了。第二审核日志和状态更新放在同一个事务里要么都成功要么都回滚。我只想保持数据一致性不想让日志和状态出现错位。第三是退回后状态管理退回不是改一个标记位而是保留完整的状态值这样稿件列表里可以同时区分“退回待修改”和“从未提交”以免投稿人搞不清楚自己的稿子为什么在待办箱里不见了。4.4 考评计分与统计报表代码计分逻辑上我前面提到用 ScoreCalculator 类来统一处理。它的核心方法如下// includes/ScoreCalculator.php class ScoreCalculator { private PDO $pdo; public function __construct(PDO $pdo) { $this-pdo $pdo; } public function calcArticleScore(int $articleId): array { // 获取稿件信息 $stmt $this-pdo-prepare(SELECT * FROM articles WHERE id :id); $stmt-execute([:id $articleId]); $article $stmt-fetch(); // 只给已发布的稿件计分 if (!$article || $article[status] ! 5) { return [total 0, detail 未发布不计分]; } // 读取所有激活的计分规则 $rules $this-pdo-query(SELECT * FROM score_rules WHERE is_active 1)-fetchAll(); $total 0; $detail []; foreach ($rules as $rule) { // 媒体类型匹配规则要求的类型为0或与稿件类型一致 if ($rule[media_type] ! 0 $rule[media_type] ! $article[media_type]) { continue; } // 质量等级匹配 if ($rule[quality_level] ! 0 $rule[quality_level] ! $article[quality_level]) { continue; } $score $rule[base_score]; // 阅读量达标额外加分比如阅读量超过5000加5分 if ($rule[extra_score] 0 $article[reading_count] 5000) { $score $rule[extra_score]; } $total $score; $detail[] 命中规则[{$rule[rule_name]}]得分{$score}; } return [total $total, detail $detail]; } }这里的思路是让规则完全配置化。每月的考评排名实际上就是对时间段内的稿件逐个调用calcArticleScore然后按 dept_id 或 author_id 分组求和。一条稿件一条稿件的算分数看着笨但对一个县区级单位一个月几十条稿件的量来说绰绰有余如果一年几千条稿件也可以用一条汇总 SQL 把规则匹配条件改成 CASE WHEN 条件表达式直接数据库里算速度更快。我在系统的score_rank.php里用的就是 SQL 聚合方案用 PHP 逐条计算只是为了演示逻辑更清晰。4.5 验证码与安全性补充热搜词里看到了“php ocr识别验证码”这类词这里正好说一下。这套登录也接了一个简单的验证码组件——用 PHP 的 GD 库生成图片把验证码字符串存在 Session 里提交时比对。没上复杂识别难度的歪扭字体和干扰线因为内部系统真的没必要把用户体验搞得太差。但如果部署到公网建议至少升级成交互式滑块验证码原因很简单——普通的图片验证码在现成工具面前基本等于没有OCR 识别四位纯数字的准确率已经非常高了防不住脚本批量尝试。登录接口本身还做了两个可选的安全开关一是连续输错 5 次密码锁定账号 10 分钟在 users 表里加 login_attempts 和 locked_until 字段就能实现。二是登录日志表记录了每次登录的 IP、时间和结果方便排查异常。这些功能虽然不是刚需但代码里已经预留了位置二次开发时按需补齐就行。5. 实测部署记录与二次开发指导5.1 我在本地跑通全流程的完整记录我用虚拟机部署了一套 Ubuntu Server 20.04 PHP 7.4 MySQL 5.7 Nginx 1.18 的环境把源码放到了/var/www/html/news_review。从头到尾把全流程跑了一遍把几个观察到的现象记录在这。先说 PHP 版本兼容性。源码确实是用 PHP 7.4 语法写的我在备用环境里切到 PHP 7.0 测试直接报了一堆语法错误主要是箭头函数和:返回值声明这类 7.0 不支持的特性。官方文档写的 7.4 是正确的配置环境的时候不要用老版本。然后是数据库字符集问题。我导入 install.sql 之后检查了库的默认字符集确认是 utf8mb4这么做是必要的。如果库是 utf8老项目迁移常见存入的表情符号会变成乱码而且中文排序、全文检索等业务逻辑也可能有小问题。另外注意连接数据库时 PDO 的 DSN 里必须显式指定字符集$dsn mysql:host . DB_HOST . ;dbname . DB_NAME . ;charsetutf8mb4;漏掉charsetutf8mb4的话即使表和库都是 utf8mb4PDO 连接也可能因为默认字符集覆盖而乱码。这个坑我踩过印象很深。接着是登录和权限验证。默认账号 admin/admin123 登录没问题密码用的password_hash格式登录接口能正常校验。不过用默认密码上线的系统等于没锁门所以安装步骤里我都会强调登录后立刻改密码。我实测的时候还顺手把 admin 账号的密码强度调高要求至少 10 位且包含特殊字符这个规则是写在后端校验里的不是前端做个样子。然后重点测了审核全流程。用投稿人账号提交一篇测试稿状态变成了 1待初审用初审账号登录审核中心能看到这篇稿子点通过后因为系统开启了终审环节状态变为 4待终审终审账号通过时填了发布链接和发布时间状态最终变为 5已发布。这整个过程在 review_logs 里留下了三条记录提交、初审通过、终审通过时间线和操作人都能对上。退回流程也测了初审退回后投稿人修改重提原稿件 id 不变状态重新变成 1不会出现两条重复稿件。这个设计验证下来确实能规避统计重复的问题。计分和报表模块我分别测了两种场景。场景一是市级媒体 普通质量得分严格执行了规则表里的基础分。场景二是省级媒体 优秀质量 阅读量超过 5000三个条件叠加总分正确。排名页面按部门汇总时部门之间的联系人数和稿件数都统计无误。Excel 导出功能依赖 PHPExcel 类库导出中文文件名时需要注意 URL 编码否则浏览器下载时会出现随机文件名。更省事的做法是输出Content-Disposition头时带 filename 参数用 RFC 5987 编码格式写filename*UTF-8这样中文文件名在大多数浏览器里都能正常解析。5.2 新增一个审核层级的操作步骤很多单位实际使用时会发现需要三级审核初审、复审、终审。这套系统的原始设计是 level 字段控制在两个层级那要加一层怎么办不需要大改按下面三步操作即可。第一步articles 表增加一个字段。如果不想动表结构也可以直接复用审查记录类型的逻辑但我的建议是明确加一个字段来标记当前审核层级。我在稿件表加一个current_level字段默认 1。第二步调整review_action.php里的状态流转逻辑。把“初审通过后是否直接终审”的判断改成先判断是否还有更高层级有则进入下一级没有才进入已发布状态。这套逻辑本身就是用 level 数值来比较的不需要硬编码。第三步在审核中心列表页的查询条件上加一个当前审核员对应的 level 过滤。每个审核员在 users 表里有max_level字段表示他能审到的最高层级。这样对应层级的审核员只会看到该层级的稿件。全流程走下来一个三级审核就接好了改动集中在 config 和 review 逻辑不会牵连其他功能。这其实就是分层设计的价值——只要状态机流转做得清晰、状态值能表达“当前在哪一级”新增审核层级就没有想象中那么伤筋动骨。如果一开始就把审核逻辑写死在 if...else 里想扩展就比较麻烦了。5.3 二次开发思路把系统改造得更贴合自己的业务拿这套源码做底子后续有几个高性价比的改造方向。方向一接入微信公众号的稿件数据。现在很多单位的宣传阵地是公众号发稿记录和阅读量都在公众号后台。可以在文章发布后手动填链接和阅读量也可以做一个定时脚本调用公众号后台的接口把数据自动回填到 reading_count 字段。前者零成本后者省人力看单位的技术条件选。方向二把计分规则做到后台可视化配置。源码里 rule_config 页面已经有了但只是简单的列表编辑。如果业务上想支持“按部门单独规则”或“按月份额外加分”需要新增规则适用范围的字段把规则匹配由全局匹配改成部门匹配。这个方向改动不大收益却很直接——以后领导说“这个月重点考核省级媒体”后台加一条规则就能落实不用再求开发改代码。方向三增加消息通知。审核通过或退回时给投稿人发短信或站内信。源码目前的处理方式是投稿人刷新列表才能看到状态变化体验一般。加消息通知的关键点在于把通知发送挂在review_action.php的事务里审核操作成功的同时写入 notification 表由前端轮询或 WebSocket 推送。这样投稿人第一时间就能收到结果不用反复刷新页面。方向四数据导出和分析增强。现在的报表只是按部门和人员汇总分数如果想让领导看得更直观可以增加趋势图和占比图。源码的统计页面已经预留了 JSON 数据接口前端接 Chart.js 或 ECharts 就能渲染图表后端只需要把按月的统计数组返回给前端就行。这个功能看着花哨实际代码量不大。6. 常见问题与排查技巧实录6.1 问题速查表我在部署和使用过程中遇到了一些典型问题整理成了一张速查表。问题现象可能的根因解决办法登录后页面跳转回登录页Session 保存路径不可写检查session_save_path目录权限改为可写目录上传图片提示 500uploads 目录没有写权限chmod -R 755 uploads/Nginx/Apache 用户需有权限文件上传成功但显示乱码文件名含中文且未转码上传时强制用时间戳随机数重命名不要用原始中文名审核时报“操作失败: Deadlock found”并发审核导致死锁检查事务里是否忘记提交适当增加innodb_lock_wait_timeout参数值统计报表人数对不上部门统计时未按 dept_id 分组检查 SQL 的 GROUP BY 条件按 dept_id 分组后再 JOIN 用户表稿件列表分页数据重复排序字段没有唯一键ORDER BY 里加上 id 作为第二排序条件加索引优化首页数据加载慢稿件表无索引给 status、dept_id、created_at 加联合索引中文内容写入数据库变成问号数据库或表字符集不是 utf8mb4改库表字符集并确保 PDO DSN 指定charsetutf8mb4上表里每一行都来自实际部署过程中的真实记录不是凭空猜的。特别是死锁问题我专门在测试环境里开了两个浏览器并发点审核复现过一次最终靠行锁和事务顺序化搞定说明开发环境测并发很值钱。6.2 排查思路实录分享一个实际排查案例。当时部署在客户服务器上收集到的情况是“审核员 A 点通过之后页面提示成功但列表里稿件状态还是待初审”。我第一反应是状态更新没生效先看 review_logs发现审核记录确实没有写入。再查代码发现review_action.php里的事务提交前多了一个 exit 中断是上一轮临时调试时忘记删掉的代码。这个问题的坑在于业务代码里用了exit直接把事务中断了理论上应该抛异常而不是静默退出。从那次之后我养成了一个习惯业务逻辑里尽量不用exit和die统一用抛异常加 return 的方式处理错误这样事务能正常回滚不会留下脏数据。另一个让我印象比较深的案例是导出的 Excel 打不开。排查下来是 PHPExcel 生成的临时文件权限问题临时目录没有写权限。报错信息被前端吞掉了页面提示“导出成功”实际文件却是损坏的。这类问题最坑人——它不报错而是给你一个打不开的文件。后来我在导出接口里加了文件完整性校验生成文件后读文件头字节判断是否真的是 xls/xlsx 格式不对就直接返回错误提示。6.3 系统上线前必须做的几件事根据我给多个单位做类似系统的经验有五个上线检查项是一定要过的第一修改所有默认密码。包括管理员、测试账号、数据库连接账号。默认密码说白了是系统最明显的安全隐患。第二备份数据库并在另一台机器上做恢复演练。不要等到数据真丢了才想起来备份。备份策略可以简单每天凌晨用 mysqldump 全量备份保留最近 7 天。第三确认上传目录不可执行 PHP。前面 Nginx 配置里说过了这步遗漏的话大半个系统相当于裸奔。第四检查官网部署环境时确认 PHP 关闭了错误显示、开启了错误日志。生产环境开 display_errors 等于把服务器目录结构和 SQL 语句全部交给攻击者这个开关必须关掉。第五在真实业务数据上线前先用三五个人的账号跑一遍完整审核流程确认退回、改稿、再提交的路径顺畅。这个“预演”很重要可以避免正式启用后业务人员被流程卡住又不知道怎么反馈。7. 个人经验与扩展可能性先聊几句个人体会。在做这类管理系统时我最大的感受是技术本身从来不是最难的难的是把业务规则理解透并把流程固化到代码里。审核级数的弹性、退回重提时的状态处理、考评规则的动态配置这三件事看着不起眼却是整个系统能不能长期用下去的胜负手。很多同类项目死在后期改需求原因就是早期设计时没把变数考虑进去。再说一个实际操作里的小技巧这类新闻考评系统的用户年纪跨度比较大有的审核员对系统的操作习惯可能停留在“用 Excel 看表格”的水平。所以我在设计页面时特意做了一些减法——待办列表默认只显示当前审核人自己的稿件导航栏只放该角色常用的功能而不是把所有菜单都堆出来。上线后推广阻力小很多用户反馈也普遍比较正面。经验就是越简单的界面在内部系统里往往越受欢迎花哨的交互反而制造门槛。这套代码后续还有不少演进空间。目前它是一套标准的 LAMP/LEMP 应用可以平滑升级到 PHP 8 并引入 Laravel 或 ThinkPHP 做更规范的分层也可以把前端拆成 Vue 单页应用、后端改用 API 模式但这要看团队实际的技术储备没必要为了时髦而重写。核心业务逻辑和数据结构已经在这篇文章里讲透了真到了要重构那天照着这套流程和数据模型迁移也不会太痛苦。最后分享一个我在交付项目时反复用到的理念交付源码只是交付的开始真正的交付是对方能独立使用、独立调整规则、独立排查问题。所以这篇文章里那些踩坑记录和排查手法比代码本身更有价值。搞懂你的数据怎么流转、状态怎么变化、规则怎么匹配比多写几个页面更能体现一个开发者的功力。希望这套系统的设计思路能帮你在自己的业务场景里少走一些弯路。