service.xpcool.com/internal/service/recruitment/source_config.go
夏犀麟 ad4567fc01 feat: 招聘爬虫健壮性 + 通知记录增强(归档工作区留存改动)
这两批改动此前一直压在工作区未提交,本次一并归档。go build ./... 通过。

一、招聘爬虫健壮性
- 新增 internal/service/recruitment/source_config.go:
  crawl_source.config(VARCHAR(1000) JSON)的源级抓取参数解析 —— 增量窗口天数、
  单次详情页上限、最大翻页数、详情间隔限速、自定义标准词/排除词。
  全部字段可选,JSON 解析失败即回退默认值,保证历史数据零迁移可用,
  且一个源的脏配置不会拖垮整个调度。
- 新增 internal/service/recruitment/keywords.go(含 keywords_test.go 与 testdata/):
  链接筛选由「URL 形态命中 AND 文本语义命中」改为评分制 —— URL 形态加分、
  标准词加分、排除词大幅减分,达阈值即入选。原与逻辑会系统性漏抓
  「补充工作人员的通知」「公开选调公务员简章」等标题变体,且静默无报错。
- 新增 manifest/sql/recruitment/004_crawl_source_status.sql:
  crawl_source 增加「最近一次运行状态」冗余列,抓取结束时写入,
  使 Sources() 查询零 JOIN 零扫描 —— 规避原实现全表扫描只增不减的 crawl_log、
  随运行时间线性劣化的问题。幂等,可重复执行。
- internal/service/recruitment/crawler.go:在既有「静态两级 / SPA / curl 回退」
  流程上做锚点抽取与过滤、编码回退的健壮性增强。
- internal/service/recruitment/bark.go、recruitment.go:配合上述调整。

二、通知记录增强
- api/notice、internal/controller/notice、internal/service/notice、
  internal/model/{dto,entity,do}/notice.go:通知日志查询与操作扩展 ——
  批次号、状态、重试次数、来源、耗时、详情、删除、清空。
- 新增 internal/model/dto/notice_meta.go(字典选项元数据,供前端筛选器取选项)。
- 新增 manifest/sql/017_notice_log_enhance.sql:notice_log 扩展列 +
  删除/清空/详情/字典选项接口权限,幂等(ALTER 走 information_schema 判断,
  菜单 ON DUPLICATE KEY UPDATE,角色绑定 INSERT IGNORE)。

三、仓库卫生
- .gitignore 补 rc_*.log 与 rc_verify*,挡掉本地离线验证产物
  (含 30MB 的 rc_verify.exe),避免误入库。

设计依据见 docs/recruitment-crawler-design.md。

注:017 与 recruitment/004 两个 SQL 为增量迁移,DB 结构变更不自动 DDL,
生产库需手动导入。
2026-09-14 01:22:39 +08:00

119 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package recruitment 的抓取适配配置。
//
// 本文件实现 crawl_source.config 字段VARCHAR(1000) JSON的解析与默认值兜底
// 让「抓取行为」可按数据源定制,无需改代码。设计目标:
// - 所有字段可选,未配置则用默认值,保证历史数据零迁移即可继续工作;
// - JSON 解析失败不报错,回退全默认值,避免一个源的脏配置拖垮整个调度。
package recruitment
import (
"encoding/json"
"github.com/gogf/gf/v2/frame/g"
)
// SourceConfig 是单个数据源的抓取适配配置(对应 crawl_source.config
// 所有字段均可省略,零值即「用默认」。
type SourceConfig struct {
// IncrDays 增量窗口天数:仅抓取该天数内发布的公告。默认 30。
// force=true 时忽略此项。
IncrDays int `json:"incrDays"`
// MaxItems 单次抓取详情页上限,默认 60force 模式下放宽到 300。
MaxItems int `json:"maxItems"`
// MaxPages 列表分页最大翻页数,默认 1不翻页。预留字段。
MaxPages int `json:"maxPages"`
// DelayMs 详情页之间的抓取间隔(毫秒),默认 0不限速
// 政府站对高频访问敏感,建议按源配置 300~1000。
DelayMs int `json:"delayMs"`
// IncludeWords 自定义标准词(命中则加分)。为空则用内置默认词表。
IncludeWords []string `json:"includeWords"`
// ExcludeWords 自定义排除词(命中则大幅减分)。为空则用内置默认词表。
ExcludeWords []string `json:"excludeWords"`
// MinScore 链接入选阈值,默认 2。调高更严格调低更宽松。
MinScore int `json:"minScore"`
// DatePatterns 发布日期自定义正则(需含一个捕获组或在匹配结果内含日期)。
// 为空则走内置的分段抽取策略。
DatePatterns []string `json:"datePatterns"`
}
// 默认值常量(未配置或配置缺失时使用)。
const (
defaultIncrDays = 30 // 增量窗口默认 30 天
defaultMaxItems = 60 // 常规抓取详情上限
forceMaxItems = 300 // force 全量回溯上限
defaultMinScore = 2 // 链接入选评分阈值
)
// parseSourceConfig 解析数据源的 config 字段。
// 任何解析错误都静默回退为默认值,保证抓取流程不因配置问题中断。
func parseSourceConfig(raw string) SourceConfig {
var cfg SourceConfig
raw = trimSpace(raw)
if raw == "" || raw == "{}" {
return cfg
}
if err := json.Unmarshal([]byte(raw), &cfg); err != nil {
// 配置格式错误不应阻断抓取,记一条日志后按默认行为继续。
g.Log().Warningf(nil, "解析数据源 config 失败,按默认配置抓取: %v", err)
return SourceConfig{}
}
return cfg
}
// incrDays 返回增量窗口天数(含默认值兜底)。
func (c SourceConfig) incrDays() int {
if c.IncrDays > 0 {
return c.IncrDays
}
return defaultIncrDays
}
// maxItems 返回单次抓取详情页上限。
func (c SourceConfig) maxItems(force bool) int {
if force {
return forceMaxItems
}
if c.MaxItems > 0 {
return c.MaxItems
}
return defaultMaxItems
}
// minScore 返回链接入选阈值(含默认值兜底)。
func (c SourceConfig) minScore() int {
if c.MinScore > 0 {
return c.MinScore
}
return defaultMinScore
}
// includeWordsOrDefault 返回标准词表:配置优先,否则用内置默认。
func (c SourceConfig) includeWordsOrDefault() []string {
if len(c.IncludeWords) > 0 {
return c.IncludeWords
}
return defaultIncludeWords
}
// excludeWordsOrDefault 返回排除词表:配置优先,否则用内置默认。
func (c SourceConfig) excludeWordsOrDefault() []string {
if len(c.ExcludeWords) > 0 {
return c.ExcludeWords
}
return defaultExcludeWords
}
// trimSpace 轻量去空白(避免为此引入 strings 依赖以外的耦合)。
func trimSpace(s string) string {
start, end := 0, len(s)
for start < end && (s[start] == ' ' || s[start] == '\t' || s[start] == '\n' || s[start] == '\r') {
start++
}
for end > start && (s[end-1] == ' ' || s[end-1] == '\t' || s[end-1] == '\n' || s[end-1] == '\r') {
end--
}
return s[start:end]
}