# 企业新闻舆情部署与维护 后台页面 `/companyNews/index` 支持新闻采集、公司和发布日期筛选、人工正文编辑、留存审核及批次记录。当前没有对外展示接口。所有新闻默认 `is_visible=false`,保存与审核也不会开启展示。 ## 业务规则 - 点击“获取更新”创建一个全量候选和已占额企业快照,不受新闻列表筛选和分页影响;仅 `company_qcc_accounts.status` 为 `selected` 或 `occupied` 的企业进入批次,按公司 ID 顺序执行。 - 执行前再次检查企业是否仍为候选或已占额;不符合的任务记录失败/跳过,不调用元禾。 - 单次请求使用元禾 `enterprise/packInfo`,连接超时 10 秒、总超时 60 秒;只有外层 `code=200` 且 `data.news` 是数组或 null 才进行处理。null/空数组记成功、0 新增;缺字段、非数组记失败。 - 本功能直接调用 Repository,不调用 QccCallService;成功、失败、超时均不更新企业户状态。原来的 `qcc:verify-pack-info` 诊断命令仍有状态流转,不能用它代替本功能。 - 按企业、数据提供方及去重键建立唯一约束。去重键按 NewsId、Id、原文 URL 的优先级生成 SHA-256;来源标识必须跨次稳定。同一新闻关联不同公司分别保存。 - 重复采集只更新来源字段、原始 JSON 与最近采集信息,不覆盖标题、人工正文、审核结论或展示状态。不留存是逻辑状态,不删除记录。 - 原文 HTML 使用现有 TinyMCE 组件编辑,并通过服务端 HTMLPurifier 清理。来源 Content 独立保存,接口正文为空时不会伪造原文。 - 原文抓取仅预留字段,本期不自动访问来源链接抓取正文。 - 新闻列表日期筛选依据原文 PublishTime,按应用时区保存与筛选,结束日期包含当天全部时间。 - 单条新闻字段错误会记录序号与错误,其余正常条目继续保存;该企业任务记失败,以便核查和重试。成功/失败按企业计数,新增加总实际插入行数。 ## 企业下拉查询 公司筛选仅返回企业户中 selected、occupied 的企业,不返回 unknown 或未纳入的企业。接口按企业名称、ID 排序,每页 30 家;前端滚动触底加载下一页,输入关键词时从第一页重新搜索。“获取更新”采集全部候选和已占额企业,不受当前公司筛选或下拉分页影响。 ## 元禾接口配置 后端 `.env` 必须设置 YUANHE_BASE_URL、YUANHE_CUSTOMERID、YUANHE_AUTHKEY,值使用当前授权的元禾配置。密钥不得提交到版本控制。项目通过 config/yuanhe.php 读取,因此使用配置缓存的环境更新后需重新生成配置缓存,并重启采集进程。 创建批次和启动消费者前会校验配置,缺少配置时直接说明缺少的键名,不会创建一批注定失败的任务;页面顶部也会显示配置错误。 ## 上线步骤 部署本次 PHP 源码后,在项目根目录执行这两条迁移(不包含工作区其他功能的迁移): ```bash php artisan migrate --path=database/migrations/2026_10_09_100000_create_company_news_tables.php --force php artisan migrate --path=database/migrations/2026_10_09_100001_add_company_news_menu.php --force ``` 新增五张表:company_news、company_news_logs、company_news_batches、company_news_tasks、company_news_control。 菜单迁移创建“企业新闻舆情”,页面地址 `/companyNews/index`,API 权限前缀 `api/admin/company-news/`。继承现有 `/qccEnterpriseAccount/index` 菜单的角色及直接授权;若旧菜单不存在或未给用户分配权限,请在角色管理中分配新菜单。新接口同时经过管理员登录与 RBAC 权限检查。重新登录加载新菜单。 在前端项目构建并将产物部署到站点 `public/admin`: ```bash npm run build:prod -- --dest dist ``` 后端要使用与现有项目一致的 PHP CLI 和 composer.lock 依赖(HTMLPurifier 已包含在当前锁定的生产依赖中)。更新了路由缓存的环境需重新生成或清理路由缓存。 ## 调度执行 本地使用 `php artisan serve` 启动 HTTP 服务并不会启动后台调度。`company-news/latest` 只读查询进度,轮询它不会执行企业请求。需在另一个终端保持以下专用消费者运行: ```bash php artisan qcc:collect-news --watch ``` 此模式每 5 秒检查是否有新建批次,只运行新闻采集,不触发其他短信、邮件等调度任务。重启电脑、停止终端或更改元禾配置后需重启该进程。生产环境可使用宝塔守护进程/Supervisor 管理该命令,或使用下面已有的 Laravel scheduler 方式。 复用服务器已有 Laravel scheduler,每分钟执行 `php artisan schedule:run`。无需新增 Redis 或修改全站 QUEUE_CONNECTION;任务和锁存储在数据库中。 本次 Kernel 增加 `qcc:collect-news`,每分钟在后台启动消费者。它仅处理管理员已经创建的批次,不会每分钟重新创建采集批次。页面点击后通常在下一分钟开始;关闭页面不影响执行。 如果服务器尚未设置 Laravel scheduler,可在宝塔计划任务配置每分钟执行,PHP 路径使用服务器实际安装的 CLI 路径: ```bash cd /www/wwwroot/wx.sstbc.com && php artisan schedule:run ``` 也可以手工消费已经创建的批次: ```bash php artisan qcc:collect-news ``` 限制本次消费者处理量: ```bash php artisan qcc:collect-news --limit=20 ``` 命令不创建批次、不改企业户状态;没有任务时立即退出。1000 家为顺序执行,实际耗时取决于元禾响应时间,页面持续展示进度,不承诺 10 分钟完成。 ## 重复执行和故障恢复 - 创建批次和领取任务都锁定 company_news_control 的唯一控制行。同一时间仅允许一个活动批次,一个企业请求在执行;多实例 scheduler 可重复启动消费者,但不会重复领取活跃任务。 - 领取时生成 worker_token 和 5 分钟租约。外部请求在数据库事务外执行,结果提交时核验 token;过期 worker 不允许写入后续 worker 的任务结果。 - 进程崩溃后,下次消费者在租约到期时把中断任务记为失败,继续后续企业,不会自动重复外部请求。 - 网络超时不能证明元禾未收到请求。对方没有提供幂等协议时,无法保证外部调用严格只发生一次;“仅重试失败企业”由管理员明确启动新批次,并重新检查候选或已占额状态。 - 某个任务保存失败,其事务回滚后独立记失败;数据库整体不可用时命令退出,调度恢复后按租约继续。 - 页面显示“等待后台调度”长时间无进展:检查宝塔 scheduler、PHP CLI 版本、任务日志和 active_batch_id。不要通过删除控制行或随意清锁恢复,这可能破坏防重入机制。 - 不留存记录必须保留,否则后续采集又会变成新的待审核记录。 ## 验证命令 ```bash php vendor/bin/phpunit tests/Feature/CompanyNewsWorkflowTest.php tests/Unit/Services/QccCallServiceTest.php ``` 前端项目: ```bash npx vue-cli-service test:unit tests/unit/components/CompanyNews.spec.js --runInBand ``` 后端测试使用 SQLite 内存数据库、模拟元禾响应,不访问业务数据库、不调用收费接口。上线人工验收:创建包含候选和已占额企业的小规模批次,查看进度、历史与失败明细;重复采集验证新闻不重复,编辑/留存后再采集验证人工数据保持。