这两批改动此前一直压在工作区未提交,本次一并归档。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,
生产库需手动导入。
119 lines
4.0 KiB
Go
119 lines
4.0 KiB
Go
// 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 单次抓取详情页上限,默认 60;force 模式下放宽到 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]
|
||
}
|