service.xpcool.com/api/notice/notice.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

211 lines
8.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 notice_v1 通知模块接口契约(统一推送管理:渠道/规则/日志/测试)。
// 规范2026-08-27全部 POSTURL 不含参数;入参一律 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"`
}