☰
新闻宣传审核考评系统PHP源码:状态机驱动审核与自动计分实现解析
2026/10/8 2:44:36 网站建设 项目流程

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, status
sys_dept部门表dept_id, dept_name, parent_id
article_cat栏目表cat_id, cat_name, weight, sort_order
article_info稿件主表article_id, title, author_id, cat_id, content, attach_file, status, reject_reason, adopt_level, create_time, submit_time, audit_time
article_audit_log审核日志表log_id, article_id, auditor_id, action, reason, audit_time
reward_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_size

7.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往往才是用户抱怨的起点。

我自己在整理这套源码的过程中最大的收获,其实不是某个具体函数怎么写,而是想明白了"审核"和"考评"两个动作解耦的重要性。稿件审核是流程,积分考评是规则,两者交替运转,但不能混在一个页面一个接口里做完。把这个边界划清楚,后面加需求、改逻辑、查数据,都会轻松很多。如果你从这套系统里也只能取走一个设计思路,我希望是这个。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询