这两批改动此前一直压在工作区未提交,本次一并归档。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,
生产库需手动导入。
211 lines
8.0 KiB
Go
211 lines
8.0 KiB
Go
// Package notice_v1 通知模块接口契约(统一推送管理:渠道/规则/日志/测试)。
|
||
// 规范(2026-08-27):全部 POST;URL 不含参数;入参一律 body。
|
||
package notice
|
||
|
||
import "github.com/gogf/gf/v2/frame/g"
|
||
|
||
// ---------- 通知渠道 ----------
|
||
type NoticeChannelItem struct {
|
||
Id uint64 `json:"id"`
|
||
Code string `json:"code"`
|
||
Name string `json:"name"`
|
||
Enabled int `json:"enabled"`
|
||
Config string `json:"config"` // JSON 字符串
|
||
Remark string `json:"remark"`
|
||
}
|
||
|
||
type NoticeChannelListReq struct {
|
||
g.Meta `path:"/notice/channel/list" method:"post" tags:"Admin/Notice" summary:"通知渠道列表"`
|
||
}
|
||
|
||
type NoticeChannelListRes struct {
|
||
List []*NoticeChannelItem `json:"list"`
|
||
}
|
||
|
||
type NoticeChannelSaveReq struct {
|
||
g.Meta `path:"/notice/channel/save" method:"post" tags:"Admin/Notice" summary:"保存通知渠道(新增/更新)"`
|
||
Id uint64 `json:"id"` // 0=新增
|
||
Code string `json:"code" v:"required"`
|
||
Name string `json:"name" v:"required"`
|
||
Enabled int `json:"enabled" d:"1"`
|
||
Config string `json:"config"`
|
||
Remark string `json:"remark"`
|
||
}
|
||
|
||
type NoticeChannelSaveRes struct {
|
||
Id uint64 `json:"id"`
|
||
}
|
||
|
||
// ---------- 通知规则 ----------
|
||
type NoticeRuleItem struct {
|
||
Id uint64 `json:"id"`
|
||
Name string `json:"name"`
|
||
EventType string `json:"eventType"`
|
||
ChannelCodes []string `json:"channelCodes"`
|
||
UserIds []uint64 `json:"userIds"`
|
||
TitleTemplate string `json:"titleTemplate"`
|
||
BodyTemplate string `json:"bodyTemplate"`
|
||
Enabled int `json:"enabled"`
|
||
}
|
||
|
||
type NoticeRuleListReq struct {
|
||
g.Meta `path:"/notice/rule/list" method:"post" tags:"Admin/Notice" summary:"通知规则列表"`
|
||
EventType string `json:"eventType"` // 可选过滤
|
||
}
|
||
|
||
type NoticeRuleListRes struct {
|
||
List []*NoticeRuleItem `json:"list"`
|
||
}
|
||
|
||
type NoticeRuleSaveReq struct {
|
||
g.Meta `path:"/notice/rule/save" method:"post" tags:"Admin/Notice" summary:"保存通知规则(新增/更新)"`
|
||
Id uint64 `json:"id"` // 0=新增
|
||
Name string `json:"name" v:"required"`
|
||
EventType string `json:"eventType" v:"required"`
|
||
ChannelCodes []string `json:"channelCodes" v:"required|min-length:1"`
|
||
UserIds []uint64 `json:"userIds" v:"required|min-length:1"`
|
||
TitleTemplate string `json:"titleTemplate"`
|
||
BodyTemplate string `json:"bodyTemplate"`
|
||
Enabled int `json:"enabled" d:"1"`
|
||
}
|
||
|
||
type NoticeRuleSaveRes struct {
|
||
Id uint64 `json:"id"`
|
||
}
|
||
|
||
type NoticeRuleDeleteReq struct {
|
||
g.Meta `path:"/notice/rule/delete" method:"post" tags:"Admin/Notice" summary:"删除通知规则"`
|
||
Id uint64 `json:"id" v:"required"`
|
||
}
|
||
|
||
type NoticeRuleDeleteRes struct{}
|
||
|
||
// ---------- 通知历史记录 ----------
|
||
// NoticeLogItem 通知历史记录行:落库字段 + 渠道/事件/类型/分组/接收人等派生展示字段。
|
||
type NoticeLogItem struct {
|
||
Id uint64 `json:"id"`
|
||
BatchId string `json:"batchId"` // 批次号(同一次业务触发)
|
||
RuleId uint64 `json:"ruleId"` // 规则ID
|
||
RuleName string `json:"ruleName"` // 规则名称
|
||
EventType string `json:"eventType"` // 事件编码
|
||
EventName string `json:"eventName"` // 事件名称
|
||
NoticeType string `json:"noticeType"` // 类型编码
|
||
TypeName string `json:"typeName"` // 类型名称
|
||
Group string `json:"group"` // 分组编码
|
||
GroupName string `json:"groupName"` // 分组名称
|
||
ChannelCode string `json:"channelCode"` // 渠道编码
|
||
ChannelName string `json:"channelName"` // 渠道名称
|
||
UserId uint64 `json:"userId"` // 接收用户ID
|
||
UserName string `json:"userName"` // 接收人
|
||
Target string `json:"target"` // 发送目标
|
||
Title string `json:"title"` // 标题
|
||
Body string `json:"body"` // 内容
|
||
Status int `json:"status"` // 0待发送 1成功 2失败
|
||
StatusName string `json:"statusName"` // 状态名称
|
||
Result int `json:"result"` // 兼容旧字段 1成功 0失败
|
||
Error string `json:"error"` // 错误信息
|
||
RetryCount int `json:"retryCount"` // 已重试次数
|
||
DurationMs int `json:"durationMs"` // 耗时(毫秒)
|
||
Source string `json:"source"` // 触发来源 auto/manual/test
|
||
Remark string `json:"remark"` // 备注
|
||
CreatedAt string `json:"createdAt"` // 通知时间
|
||
}
|
||
|
||
type NoticeLogListReq struct {
|
||
g.Meta `path:"/notice/log/list" method:"post" tags:"Admin/Notice" summary:"通知历史记录分页"`
|
||
Page int `json:"page" d:"1" v:"min:1"`
|
||
Size int `json:"size" d:"10" v:"min:1|max:100"`
|
||
Keyword string `json:"keyword"` // 标题/内容/目标/错误 模糊
|
||
NoticeType string `json:"noticeType"` // 类型编码
|
||
Group string `json:"group"` // 分组编码
|
||
EventType string `json:"eventType"` // 事件编码
|
||
ChannelCode string `json:"channelCode"` // 渠道编码
|
||
UserId uint64 `json:"userId"` // 接收用户ID
|
||
BatchId string `json:"batchId"` // 批次号
|
||
Status int `json:"status"` // 0全部 1成功 2失败
|
||
Result int `json:"result"` // 兼容旧参数:1成功 2失败
|
||
DateFrom string `json:"dateFrom"` // 起始时间
|
||
DateTo string `json:"dateTo"` // 结束时间
|
||
OrderBy string `json:"orderBy"` // createdAt(默认) | durationMs
|
||
OrderDir string `json:"orderDir"` // desc(默认) | asc
|
||
}
|
||
|
||
type NoticeLogListRes struct {
|
||
List []*NoticeLogItem `json:"list"`
|
||
Total int `json:"total"`
|
||
Stats *NoticeLogStatsItem `json:"stats"` // 当前筛选条件下的统计概览
|
||
}
|
||
|
||
// NoticeLogStatsItem 统计概览。
|
||
type NoticeLogStatsItem struct {
|
||
Total int `json:"total"`
|
||
Success int `json:"success"`
|
||
Failed int `json:"failed"`
|
||
SuccessRate int `json:"successRate"`
|
||
}
|
||
|
||
// NoticeLogDetailReq 通知历史记录详情。
|
||
type NoticeLogDetailReq struct {
|
||
g.Meta `path:"/notice/log/detail" method:"post" tags:"Admin/Notice" summary:"通知历史记录详情"`
|
||
Id uint64 `json:"id" v:"required"`
|
||
}
|
||
|
||
type NoticeLogDetailRes struct {
|
||
Item *NoticeLogItem `json:"item"`
|
||
}
|
||
|
||
// NoticeLogDeleteReq 删除通知历史记录(支持批量)。
|
||
type NoticeLogDeleteReq struct {
|
||
g.Meta `path:"/notice/log/delete" method:"post" tags:"Admin/Notice" summary:"删除通知历史记录"`
|
||
Ids []uint64 `json:"ids" v:"required|min-length:1"`
|
||
}
|
||
|
||
type NoticeLogDeleteRes struct {
|
||
Deleted int `json:"deleted"`
|
||
}
|
||
|
||
// NoticeLogClearReq 清空通知历史记录(按时间范围,可选保留最近 N 天)。
|
||
type NoticeLogClearReq struct {
|
||
g.Meta `path:"/notice/log/clear" method:"post" tags:"Admin/Notice" summary:"清空通知历史记录"`
|
||
KeepDays int `json:"keepDays"` // 保留最近 N 天(>0 时忽略 DateFrom/DateTo)
|
||
DateFrom string `json:"dateFrom"` // 清空起始时间
|
||
DateTo string `json:"dateTo"` // 清空结束时间
|
||
}
|
||
|
||
type NoticeLogClearRes struct {
|
||
Deleted int `json:"deleted"`
|
||
}
|
||
|
||
// ---------- 字典选项 ----------
|
||
type NoticeMetaItem struct {
|
||
Code string `json:"code"`
|
||
Name string `json:"name"`
|
||
}
|
||
|
||
type NoticeMetaOptionsReq struct {
|
||
g.Meta `path:"/notice/log/options" method:"post" tags:"Admin/Notice" summary:"通知字典选项(渠道/事件/类型/分组)"`
|
||
}
|
||
|
||
type NoticeMetaOptionsRes struct {
|
||
Channels []*NoticeMetaItem `json:"channels"`
|
||
Events []*NoticeMetaItem `json:"events"`
|
||
Types []*NoticeMetaItem `json:"types"`
|
||
Groups []*NoticeMetaItem `json:"groups"`
|
||
}
|
||
|
||
// ---------- 测试发送 ----------
|
||
type NoticeTestReq struct {
|
||
g.Meta `path:"/notice/test" method:"post" tags:"Admin/Notice" summary:"测试通知(按规则或直接指定)"`
|
||
RuleId uint64 `json:"ruleId"` // 按规则测试(0=手动指定)
|
||
ChannelCode string `json:"channelCode"` // 手动:渠道编码
|
||
Target string `json:"target"` // 手动:设备key/token
|
||
UserIds []uint64 `json:"userIds"` // 手动:或按用户
|
||
Title string `json:"title"`
|
||
Body string `json:"body"`
|
||
}
|
||
|
||
type NoticeTestRes struct {
|
||
Result bool `json:"result"`
|
||
Message string `json:"message"`
|
||
}
|