
1. 为什么我会做一套新闻宣传审核考评系统先说个真实场景。我接触过不少单位宣传口的工作量其实很大内部通讯员投稿、部门稿件报送、新媒体转载登记、月底按稿量计分排名。可大部分单位用的还是微信群传文档、Excel表记分数、月底人力对着聊天记录翻聊天记录一套流程跑下来审核人不清楚稿子卡在哪个环节投稿人催稿靠打听管理员算分靠加班最后统计结果还经常被质疑是不是漏记了。这活儿干过的人都知道纯靠手工维持一个月两次考核就够呛。所以我花了一段时间用PHP把这块流程整个捋了一遍做了一套新闻宣传审核考评系统带完整源码。它解决的问题很明确把投稿→审核→统计→考评这条链路从微信和Excel里搬到Web平台上稿件走到哪一步状态可见稿件的采用情况自动关联积分月底报表一键生成不用再对着表格手工加总。这个系统适合谁一是做内部信息化、OA办公系统的开发人员可以直接拿来改改当一个完整模块用二是单位宣传部门或办公室管考核的同事他们可以拿这套系统理解线上审核考评应该长什么样再拿着原型去和技术沟通需求三是想练手PHP实践的初学者这套源码覆盖了登录鉴权、权限分级、数据表设计、状态流转、统计报表、文件上传这些最常见的Web后端知识点代码量适中看不懂的位置我可以慢慢拆给你听。我在整理这套系统的过程中实际上是在回答几个核心问题多人协作的审核流程在数据库里怎么表示稿件的计分规则怎么和审核操作挂钩统计页面怎么做到看一眼就知道本月数据?这些问题的答案都落在了具体的表和函数里下面从头讲。因为标题里带了源码所以这篇文章不会只讲概念我会把项目结构、数据表字段、关键函数逻辑、部署运行要求全部摊开来说。无论你是拿到源码准备改还是想参考这套设计做自己的版本都能沿着这条线走通。2. 需求拆解审核、考评、统计这三件事到底指什么动手写代码之前我心里先画了三张流程图分别对应系统的三大核心业务。你拿到项目源码后会看到整个目录结构其实就是围绕这三张图展开的。2.1 投稿人视角从登录到看到积分变化系统的使用者分三类角色普通用户通讯员投稿人、审核员科室负责人或编辑、系统管理员统筹考核。第一张流程图的起点是投稿人。投稿人登录后应该能做的事情包括新建稿件填写标题、选择栏目、上传附件查看自己历史稿件列表每一条都带当前状态未提交、待审核、已通过、已驳回被驳回的稿件能查看审核意见修改后重新提交首页能看到自己的当月积分和排名明白这个月表现如何。你可以发现这里有一个很容易被忽略的设计点稿件有未提交和待审核两个状态。为什么分开因为很多写稿人习惯先起草、后上报不写完不点提交。如果一开始就强制所有稿件直接进审核队列那审核员看到的半成品稿件会变多审核效率反而下降。所以我在表单里加了保存草稿和正式提交两种提交方式状态字段一开始是未提交点提交审核才会走到待审核。2.2 审核员视角快速处理留有依据审核员的角色更像阀門。列表页按时间倒序展示所有待审核稿件审核员点进去能看到稿件正文和附件然后做两个操作通过或驳回。如果驳回必须填写驳回原因这个原因会立刻反馈给投稿人。为什么强调必须填写原因考评场景里最容易产生矛盾的就是一句冷冰冰的未通过投稿人完全不知道稿子哪里不合适下次还会犯同样的错。加了必填原因之后审核员每一次驳回都有据可查投稿人也能针对性修改系统运转起来摩擦小很多。另外我还给审核员加了一个小功能稿件标记为已采用时可以选择它发表在哪一级媒体内部平台、市级、省级、国家级。这一步非常重要它会直接影响后面的考评计分——不同层级的采用分值是不同的。这个字段默认不填等于只审核不参与加权给了系统很强的灵活性。2.3 管理员视角规则配置和统计数据一把抓管理员在这个系统里做的事情最多包括用户管理开通账号、重置密码、停用离职人员账号栏目管理配置稿件分类如工作动态经验交流文艺副刊并给每个栏目设置考评权值权值管理这个在原文里就叫栏目系数比如工作动态1.2、文艺副刊0.8用于调节不同栏目稿件的计分权重数据统计按月、按人、按部门查看积分排名支持导出表格系统参数设置网站的公告、基础分、奖励分规则等。你发现没有管理员这一层做的是规则的设定而不是规则的执行。执行规则的是系统代码——稿件通过就用权值乘基础分驳回就不计分一级媒体采用再加奖励分。把规则显性做成数据库表而不是写死在代码里是我在这套系统里比较满意的一个决策因为考核制度常年微调改界面不如改数据。3. 项目结构速览拿到源码后第一眼看哪里标题既然是带完整源码那源码怎么组织就值得花一节单独说。这套系统我用的是经典的PHP项目分层方式不依赖重型框架主要方便二次开发和快速部署。PHP的生态里Laravel、ThinkPHP这类框架功能全面但对应的部署环境要求也高这個系统设计时考虑了放到一台普通服务器或虚拟主机就能跑的需求所以我走的是原生PHP 轻量封装的路子依赖非常少。3.1 源码目录结构news_review_system/ ├── admin/ # 管理员后端 │ ├── login.php │ ├── index.php # 控制台 │ ├── user_manage.php # 用户管理 │ ├── category_manage.php# 栏目管理 │ ├── article_manage.php # 稿件管理 │ └── statistic.php # 考评统计 ├── api/ # 前后端交互JSON接口 │ ├── login.php │ ├── logout.php │ ├── article.php │ └── statistic.php ├── public/ # 对外静态资源目录 │ ├── css/ │ ├── js/ │ └── uploads/ # 稿件附件存储目录 ├── includes/ │ ├── config.php # 数据库配置 │ ├── db.php # PDO封装 │ ├── auth.php # 登录状态和权限校验 │ ├── functions.php # 公共函数时间、分页、评分计算等 │ └── constants.php # 状态常量表 ├── index.php # 前台入口投稿人首页 ├── submit_article.php # 投稿页面 ├── my_articles.php # 我的稿件列表 └── install.sql # 数据库初始导入脚本我建议拿到源码的人先看includes/constants.php里面定义了所有状态常量STATUS_DRAFT、STATUS_PENDING、STATUS_APPROVED、STATUS_REJECTED。理解了这些常量再去看api/article.php里的状态流转代码整个系统的骨架就浮出来了。3.2 为什么我坚持用PDO而不是mysqli数据库连接这块我特意用了PHP的PDO扩展而不是大家可能更熟悉的mysqli。原因是PDO的预处理机制写起来更别扭但更安全它强制你走参数绑定能在很大程度上避免SQL拼接注入风险。考核系统属于内部办公系统数据里虽然没有支付信息那么敏感但用户的账号密码、稿件的审核意见如果被注入拖库传出去影响很糟。而且PDO的定位是面向数据库的数据访问抽象层以后想从MySQL迁到PostgreSQL或者SQLite改一行数据库驱动配置就行。代码里典型的PDO用法是这样$stmt $pdo-prepare(SELECT * FROM article_info WHERE author_id :uid AND status :status ORDER BY create_time DESC); $stmt-execute([:uid $userId, :status STATUS_APPROVED]); $articles $stmt-fetchAll(PDO::FETCH_ASSOC);3.3 密码与防安全坑的处理经验用户表的密码字段我使用的是password_hash()函数存储不是网上很多老代码里直接拿MD5拼一段就入库。PHP从5.5开始就自带了password_hash目前推荐PASSWORD_DEFAULT算法也就是bcrypt。登录校验时对应使用password_verify()函数比对。这两个函数搭配就算数据库泄露了攻击者也很难从哈希串逆推出明文密码。有人会觉得考核系统又不是商城搞这么复杂干什么我可以负责任的讲很多单位的内网系统被攻破恰恰是因为内部系统安全意识薄弱账号弱口令、明文存储随处可见。既然源码准备拿出来分享安全习惯就得从一开始就摆正。4. 数据库设计稿件状态和考评计分的数据根基几乎所有业务系统的核心生命力都藏在表结构里前端做得再花哨表设计不合理后期维护就是无底洞。这套系统的数据表我反复调整过三轮下面把最终的结构和调整原因一起讲清楚。4.1 主要数据表一览表名用途关键字段sys_user用户表user_id, username, password, real_name, dept_id, role_id, statussys_dept部门表dept_id, dept_name, parent_idarticle_cat栏目表cat_id, cat_name, weight, sort_orderarticle_info稿件主表article_id, title, author_id, cat_id, content, attach_file, status, reject_reason, adopt_level, create_time, submit_time, audit_timearticle_audit_log审核日志表log_id, article_id, auditor_id, action, reason, audit_timereward_info积分记录表reward_id, user_id, article_id, score, reason, create_time我挑两个关键点重点说。4.2 稿件主表与审核日志表为什么要分开最初设计时我把当前状态和审核意见都直接放在稿件表里一条记录一个字段存状态驳回原因也跟着盖掉。后来在实际试用中发现一个问题一篇稿件可能会经历被驳回→修改→重新提交→通过的过程如果只保留最终状态上级考核时问这篇稿子审了两次还是三次中间谁驳回的完全没有记录可查。于是在第二版我拆出了article_audit_log表专门记录每次审核操作的完整细节。article_info表只保留当前状态的快照字段方便列表页快速筛选和展示article_audit_log表则像黑匣子一样把每一次状态变更的来龙去脉都留下来。这样做还有一个实际好处以后想加审核通过率或平均审核时长之类的统计指标直接对日志表做聚合查询就行数据都是现成的。4.3 考评计分字段是怎么设计的稿件的计分由几个字段共同决定article_info.cat_id栏目、article_cat.weight权值、article_info.adopt_level采用级别、基础分、奖励分和处罚分。基础分是系统参数表里的一个固定值比如100分通过即得采用级别是枚举值内网1、市级2、省级3、国家级4不同级别的加分不同奖励分和处罚分存放在积分记录表reward_info里由管理员手动添加说明。这个设计的关键在于评分始终跟着稿件走而不是跟着人走。文章通过审核后系统自动根据栏目权值、采用级别计算总得分并把分值写入该稿件作者的积分记录。后续如果稿件被举报抄袭管理员可以直接在reward_info里给该作者追加一条负分记录同时不影响历史积分明细的完整性。这种计分留痕的方式比在用户表里维护一个总分字段干净得多。数据库的初始导入可以用项目根目录的install.sql里面包含了建表语句和一些默认数据。如果是第一次跑我建议你先把article_cat表里的栏目权值改成符合自己单位实际情况的数值再开始测试。5. 审核流程的核心逻辑状态机驱动稿件流转稿件从投稿到见报本质就是一个状态机在运转。用户点击提交、审核员点击通过或驳回都是在驱动状态发生迁移。这一节我把状态迁移的细节、前端怎么展示、以及我踩过的一个坑都讲透。5.1 状态迁移路径系统定义了一组清晰的状态路径未提交(draft) ↓ 投稿人点击提交审核 待审核(pending) ↓ 审核员点击通过 ↓ 点击驳回并填写原因 已通过(approved) 已驳回(rejected) ↑ ↓ └──────驳回稿件修改后重新提交──────────┘实现这套迁移逻辑时我没有用复杂的规则引擎就是一组简单的switch分支判断。状态流转的入口统一收敛到ArticleStatus::transition()方法里任何入口调用它都会先校验当前状态和目标状态是否匹配不匹配直接抛出异常。这样写的好处是状态流转规则是单点维护的后面想加已发布已撤稿之类的状态只需要在这一处加分支不用担心其他地方状态不一致。核心代码大概是public static function transition($currentStatus, $action) { switch ($currentStatus) { case STATUS_DRAFT: return $action submit ? STATUS_PENDING : null; case STATUS_PENDING: if ($action approve) return STATUS_APPROVED; if ($action reject) return STATUS_REJECTED; return null; case STATUS_REJECTED: return $action submit ? STATUS_PENDING : null; default: return null; } }5.2 为什么把待审核稿件设为审核员的默认工作台稿件列表里我没有把全部稿件一股脑展示给审核员而是默认筛选status STATUS_PENDING也就是只显示还没处理的那部分。审核员的日常是处理存量不是翻历史记录默认只给待办能最大程度降低上手成本。已通过的稿件在另一个Tab里展示按时间倒序方便审核员回头查证。这是我从收件箱模式借鉴来的交互设计审核员不用在一个混乱的长列表里大海捞针。5.3 踩过的坑状态流转时未加事务处理第一版代码里状态迁移和积分计算是分开的两个数据库操作。测试的时候发现一个偶发问题审核员点击通过后页面提示成功但积分记录没写进去。查了半天原因是第一个UPDATE执行后系统的数据库连接因为异常中断了第二个INSERT根本没机会执行。后来我把这两个操作包进了一个数据库事务里先更新稿件状态再写积分记录最后提交事务。任何一个步骤失败整个操作回滚页面也只会看到一个统一的报错而不是造成状态变了分没加这种恼人的脏数据。这其实是后端开发里很基础的一个实践但很多半路出家的PHP开发教程根本不会强调它。我建议所有涉及业务状态改变资产变动的操作都要考虑加事务。5.4 审核意见的完整展示审核驳回时填写的reject_reason字段在投稿人端看得很清楚。我在稿件详情页做了一个审核意见时间线把审核日志表的记录按时间倒序渲染出来每一条显示审核人姓名、操作类型通过/驳回、意见内容和时间戳。这样做的好处是投稿人打开页面就能看到整个稿件的审核履历不用跑到微信上问审核员我稿子现在什么情况。时间线组件其实就是一个简单的数组循环加CSS样式没有引入任何前端框架依赖但效果非常直观。6. 考评计分细节从通过到排名公示中间发生了什么内容审核只是系统的一半另一半是考评。很多系统重审核轻考评做出来的Excel导出比线上系统还常用那这套系统的意义就少了一半。我把计分规则和报表逻辑单独拆开讲这部分是这套源码里比较有参考价值的设计。6.1 一次完整的计分过程我模拟一下实际的计分路径。假设某位通讯员提交了一篇工作动态栏目稿件栏目权值1.2审核员通过采用级别选内部平台记1分系统计算基础分基础分100 × 栏目权值1.2 120分追加采用级别加分内部平台加30分合计150分写入reward_info表原因记稿件审核通过内部平台采用。如果这篇稿子被上级媒体转载并在系统中登记了省级媒体系统会根据省级加分规则再追加一笔积分。所有积分变化都有记录统计页面按月聚合就是该通讯员本月的考评得分。6.2 统计报表怎么实现统计页面的核心SQL并不复杂关键在聚合SELECT u.real_name, d.dept_name, SUM(r.score) AS total_score, COUNT(DISTINCT r.article_id) AS article_count FROM reward_info r LEFT JOIN sys_user u ON r.user_id u.user_id LEFT JOIN sys_dept d ON u.dept_id d.dept_id WHERE r.create_time BETWEEN :start AND :end GROUP BY u.user_id ORDER BY total_score DESC这里有个设计细节聚合的时候用DISTINCT r.article_id统计稿件数而不是简单COUNT(*)。因为同一篇稿件可能因为采用级别不同被记录多条积分直接COUNT会把一篇稿子算成多篇排名自然失真。这个坑我在测试时踩得很深一开始统计榜单的数据涨得离谱后来排查才发现问题出在这里。报表页我同时给管理员提供了按部门筛选、按时间段筛选两个维度并且用了一个简单的柱状图对比各部门的总分。柱状图没有用图表库而是用CSS宽度百分比模拟出来的毕竟后端管理系统对图表的交互要求不高页面轻一点加载也快。6.3 积分总表和明细表给考核争议留后路我始终认为考评系统最重要的不是排行榜而是可追溯。所以统计页面除了有总分排名每个排名后面的数字都能点进去看到这个人的所有积分明细哪篇稿子加了多少分、加分的理由是什么、加分时间是什么。一旦员工对考评结果有异议管理员可以直接把积分明细导出发给当事人用数据说话。这一点很多外包开发的系统都不一定会做得这么细致。7. 部署实践从本地环境到内网服务器的完整步骤源码拿到手里能不能跑起来才是关键。我把这套系统在三种环境下测试过Windows本地的小皮面板phpStudy、Linux服务器上的宝塔面板、还有一台纯净的CentOS Nginx环境。下面按最常用的路径给你梳理部署步骤。7.1 环境要求先列一个最低要求清单防止小白摸着石头过河直接卡住PHP 7.4或以上版本推荐8.0/8.1我源码里没有用PHP8的语法特性但8.x跑起来更快MySQL 5.7或以上MariaDB 10.3也可以Web服务器Apache或Nginx均可扩展要求pdo_mysql、mysqli、fileinfo、gd如果是图片上传可能会用到缩放操作系统Windows、Linux都可以。项目没有依赖 composer没有 node_modules不需要额外构建步骤。这在PHP项目里算是零基础友好的配置了PHP风味的解压即用。7.2 部署五步走第一步把源码上传到Web根目录比如htdocs/news_review或/www/wwwroot/news_review。第二步创建数据库。可以用phpMyAdmin或者命令行执行install.sql脚本脚本会自动建库、建表、插入默认管理员账号。默认数据库名建议改为news_review配置在includes/config.php里define(DB_HOST, 127.0.0.1); define(DB_NAME, news_review); define(DB_USER, root); define(DB_PASS, ); define(DB_CHARSET, utf8mb4);第三步修改config.php里的数据库账号密码重点确认时区设置date_default_timezone_set(Asia/Shanghai)。这段已经在functions.php里写好了如果是本地测试可以不改。第四步确保public/uploads/目录可写。uploads用于存放稿件附件PHP进程需要有写入权限。Linux下执行chmod -R 755 public/uploads如果用了Nginx还要注意运行用户一般www或nginx是否拥有目录写权限我见过太多系统跑不起来就是栽在了uploads权限上。第五步访问前台首页用默认管理员账号登录install.sql里有初始账号默认密码是admin123登录后第一时间去用户管理里改掉。登录后先进入栏目管理把栏目和权值配好再去参数设置里调整基础分和各级采用加分最后创建普通用户整个系统就可以开始跑业务了。7.3 常见部署问题的排查方向我整理了三个出现频率最高的问题附带排查思路现象可能原因排查方向页面白屏PHP语法错误或扩展缺失打开PHP错误显示在php.ini设置display_errors On查看具体报错缺少pdo_mysql则安装扩展登录后报数据库错误数据库账号密码不对或编码不匹配检查config.php确认数据库是否存在尝试用命令行客户端重连上传附件失败目录不可写或超限检查uploads权限检查php.ini里的upload_max_filesize和post_max_size7.4 PHP版本的选择建议这套系统设计时就刻意保持了对PHP 7.x和8.x的兼容。如果问我推荐在条件允许的情况下直接用PHP 8.1或8.2性能比7.4有明显提升而且内部集成了更多现代语法。但如果你所在的单位是已有内网环境装的是老版本PHP 5.6或者7.0也不是不能用源码里没有用到太新的函数。唯一要注意的是PHP 8.0开始字符串和数字比较的语义变了如果改代码时涉及到比较建议统一改成避免潜在的类型强制转换问题。8. 源码的二次开发空间与延伸玩法系统交付出去了但真正考验开发者的是后续的定制需求。这一节我聊聊几个常见的二次开发方向活跃一下思路。8.1 对接单位统一的用户体系很多单位的OA系统已经有统一身份认证二次开发时可以把登录逻辑替换为对接接口用户输入工号密码后端POST到统一认证平台返回成功再拉取用户信息建本地会话。这套系统的登录模块集中在api/login.php和includes/auth.php替换起来比较清晰不需要动业务表结构。8.2 扩展积分规则加微信传播力维度现在的计分只考虑了栏目权值和采用级别如果单位考核里还包含稿件在内部公众号的阅读量点赞量可以再加一张metric_info表记录每篇稿件的传播数据然后在统计SQL里做二次聚合。这类扩展不需要改动审核流程只需要在统计模块增加读取逻辑对主流程完全没有侵入。8.3 定时任务月度考评自动汇总我目前的统计页面需要管理员手动选择月份来查看如果你想让系统每月1号自动生成上个月考评报表并推送给相关人员可以用系统计划任务cron或Windows计划任务每天凌晨调用一下api/statistic.php里的一个汇总接口把结果写入一张monthly_report表。这样结合消息推送工具整个考核流程就能做到全自动管理员只需要月底打开系统看一眼结果。8.4 数据显示层换成ECharts如果领导对图表有更高的视觉要求统计页的CSS模拟柱状图可以替换成开源的ECharts库。后端统计接口不变前端只需要把聚合好的JSON数据传入ECharts的series配置就行。这一步改造量不大但视觉效果提升非常直接。9. 写在最后的个人心得这套新闻宣传审核考评系统之所以选择PHP来做说到底是因为PHP在快速开发内部系统这件事上仍然是最省事的选项之一。它部署简单资料多改起来也直接配合PDO和一两个安全习惯足够撑起一个中等规模单位的日常业务。我不喜欢过度吹捧PHP也不觉得它是万能的但在这类内网办公工具的赛道上性价比确实很高。如果你准备在自己的环境里跑这套源码我建议你前三天不要急着改业务逻辑先把默认数据过一轮完整的投稿→审核→计分→统计流程。哪怕只有你自己一个账号也要走完这个闭环把数据库里article_info、article_audit_log、reward_info三张表的数据对应关系摸清楚。这样再去接真实需求心里就有底了后面怎么改都不会带偏。一个小技巧是上线第一周让部门内一两个同事先试用你坐在旁边看他们操作。真实用户永远会给你带来意想不到的操作习惯——有人会反复点击提交按钮有人会直接浏览器后退再前进造成页面状态过期还有人会把附件名字写成一串乱码。对于这些情况代码里的防重复提交、Session校验、文件名转义就是在这时候发挥作用。不要觉得这些是边缘case在内部系统里边缘case往往才是用户抱怨的起点。我自己在整理这套源码的过程中最大的收获其实不是某个具体函数怎么写而是想明白了审核和考评两个动作解耦的重要性。稿件审核是流程积分考评是规则两者交替运转但不能混在一个页面一个接口里做完。把这个边界划清楚后面加需求、改逻辑、查数据都会轻松很多。如果你从这套系统里也只能取走一个设计思路我希望是这个。