本文说明从Web 配置录像计划到Cron 服务按周历触发、调用流媒体开始/停止录制的完整链路便于联调与排障。参考代码 点击直达一、功能概述录像计划一条配置包含名称、启用状态、关联通道列表、周历格子计划7×24 小时每小时是否录像。生效方式Cron 服务core/app/sev/cron按秒轮询任务表对启用且当前时刻落在计划时段内的通道向流媒体MS下发开始录像对上一轮在录、本轮已不在计划内的通道下发停止录像。与「设备录像 / 平台录像查询」的区别计划录制是主动推流 MS 落盘设备侧 GB28181 RecordInfo 查询是读设备索引见docs/vss/gbs/设备录像.md、docs/vss/1.1平台与设备录像.md。二、数据模型项目说明表名sk-video-projects模型core/repositories/models/video-projects/model.go→VideoProjects主要字段name、state0 停用 / 1 启用、channelUniqueIdsJSON 数组字符串、plans周历位图字符串plans格式与前端一致固定长度168个字符**0/1**表示。下标按周分段第week天1周一 … 7周日对应plans[(week-1)*24 : week*24]共 24 位表示当天 0–23 点1表示该小时需要录像。后端周序号与前端一致TimestampWeekDay将时间戳映射为1–7周一–周六及 7 表示周日。三、前端配置3.1 代码路径本机示例路径skeyevss_backend_showcase/src/pages/configs/video-projects文件作用api.ts对接 Backend HTTPindex.tsx表格 表单 CRUDmodel.tsx列定义、表单字段含通道弹窗setChannelcomponents.tsxCPlain7×24 格子编辑plans3.2 HTTP 接口方法路径说明POST/video-project创建PUT/video-project更新DELETE/video-project删除GET/video-project/:id单行POST/video-project/list列表权限index.tsxP_1_4_4、P_0_2_4。3.3 计划编辑CPlain内部用长度168的数组0/1表示格子状态拖拽与点击批量修改。提交前通过props.setFieldValue(plans, data.join())写入连续字符串与 DBplans一致。3.4 关联通道表格列「关联通道」打开弹窗勾选通道存库为 JSON 数组字符串。四、后端配置 APIBackend → DB项目路径路由注册core/app/sev/backend/internal/handler/routes.go/video-project系列Handlercore/app/sev/backend/internal/handler/config/vp/Create / Update / Delete / Row / ListLogiccore/app/sev/backend/internal/logic/config/vp/持久化gRPC → ConfigService.VideoProjectCreate配置写入sk-video-projects后Cron 不读 HTTP仅通过Config RPC 拉列表。五、Cron 调度与任务注册5.1 进程入口core/app/sev/cron/main.go注册crontab.VideoProjectLogic{StartRecordingIds:make(chanmap[uint64]*cTypes.ChannelMSRelItem,10),StopRecordingIds:make(chanmap[uint64]*cTypes.ChannelMSRelItem,10),},5.2 调度周期core/app/sev/cron/internal/handler/crontab.go全局time.Ticker(1 * time.Second)。对每个任务记录若status!0且interval!0且now % interval 0则触发一次DO。初始化数据见core/repositories/models/crontab/variables.gouniqueId video-projectinterval 1→每秒执行一次逻辑若任务启用。timeout单次DO外层context超时默认 10 秒。blockStatus 1在VideoProjectLogic.DO内同步执行do 0则go do当前初始化为1同步。5.3 防并发VideoProjectLogic.Executing()为 true 时本轮跳过避免上一轮未结束又进。Redis 锁video-project-record-cron-lock保证多实例下同一时刻只有一个节点跑「计划计算 下发通道集合」主逻辑。六、VideoProjectLogic执行流程实现文件core/app/sev/cron/internal/logic/crontab/video-project.go。6.1DO入口makeRecord.Do仅启动一次 goroutinemakeRecord死循环从StartRecordingIds/StopRecordingIds两个 channel 收包分别调用startRecording/stopRecording与主循环解耦避免阻塞调度。根据BlockStatus同步或异步调用do。6.2do算「当前应录」的通道集抢 Redis 锁失败写CrontabRecord.Logs并返回。RPCVideoProjectList条件state 1且channelUniqueIds ! []启用且绑定了通道。对每条计划plans为空或len(plains) ! 168则跳过数据错乱。按天切成weekMaps[1..7]每天 24 个字符。取weekNum TimestampWeekDay(params.Now)当天的 24 位调用stream.NewVideoPlain().GetTimeRanges(hourState, params.Now)得到当日若干Unix 区间[start,end]左闭右开语义由GetTimeRanges实现见core/common/stream/plain.go。若params.Now落在任一区间内则将该计划下所有channelUniqueIds并入本轮「应录集合」。去重channelIds。停录集合上一轮保存在内存的execChannelUniqueIds中不在本轮channelIds里的→stopChannelIds。更新内存l.execChannelUniqueIds channelIds。RPCMediaServersWithChannelIds一次性查询「应开录 应停录」通道的设备/通道/MS 绑定ChannelMSRelItem。若start非空StartRecordingIds - start若stop非空StopRecordingIds - stop。6.3 时间片算法与前端展示对齐GetTimeRanges以params.Now所在自然日 0 点为基准把 24 个1连续段转为 Unix 时间区间跨 23→24 点用当日 24:00。七、实际开始 / 停止录制core/common/videoProject/recording.goCron 中startRecording/stopRecording调用videoProject.NewRecoding().StartRecording|StopRecording注意包内命名为NewRecoding。对map 中每个通道启独立 goroutine并发下发。7.1StartRecording计划录像POST http://{VssHttpTarget}/api/video/streamBodydeviceUniqueId、channelUniqueId—— 在 VSS 侧创建/拉起播放group与实时预览同源链路。POST http://{MS}/api/record/startstream_namestream.New().Produce(device, channel, PlayTypePlay)Cron 传入PlayTypePlay。record_interval60秒分片record_type0计划录像record_format0MP4。非下载模式RPCChannelUpdate将对应通道recordingState 1。7.2StopRecordingPOST http://{MS}/api/record/stopstream_name同上record_type: 0。非下载模式recordingState 0。八、端到端时序图流媒体 MSVSS HTTPCron VideoProjectLogicConfig/DBBackend前端 video-projects流媒体 MSVSS HTTPCron VideoProjectLogicConfig/DBBackend前端 video-projectsloop[每秒interval1且任务启用]par[每个通道 goroutine]par[停录通道]POST/PUT /video-projectplans channelUniqueIds stateVideoProjectCreate/UpdateVideoProjectList(state1)解析 plans 当前时刻是否在时段内MediaServersWithChannelIdsStartRecordingIds / StopRecordingIdsPOST /api/video/streamPOST /api/record/startChannelUpdate recordingState1POST /api/record/stopChannelUpdate recordingState0九、故障排除问题排查方向配置了计划从不录state是否为 1channelUniqueIds是否非空plans长度是否为 168当前星期与小时格子是否为1偶发不执行Cron 任务表status/intervalExecuting是否一直为 true上一轮卡住Redis 锁是否被占用开始录失败VSSVssHttpTarget是否可达/api/video/stream是否成功MS 无文件MSrecord/start是否成功stream_name与推流是否一致MS 磁盘与 record 配置停不掉StopRecording是否被调用通道是否从「应录集合」中移除MSrecord/stop日志十、相关代码索引模块路径Cron 录像计划逻辑core/app/sev/cron/internal/logic/crontab/video-project.goCron 调度器core/app/sev/cron/internal/handler/crontab.go开始/停止录制实现core/common/videoProject/recording.go周历时间区间计算core/common/stream/plain.goVideoPlain.GetTimeRanges任务默认配置core/repositories/models/crontab/variables.goUniqueIdVideoProject数据模型core/repositories/models/video-projects/
SkeyeVSS中录像计划源码实现:前端配置到定时录制执行
本文说明从Web 配置录像计划到Cron 服务按周历触发、调用流媒体开始/停止录制的完整链路便于联调与排障。参考代码 点击直达一、功能概述录像计划一条配置包含名称、启用状态、关联通道列表、周历格子计划7×24 小时每小时是否录像。生效方式Cron 服务core/app/sev/cron按秒轮询任务表对启用且当前时刻落在计划时段内的通道向流媒体MS下发开始录像对上一轮在录、本轮已不在计划内的通道下发停止录像。与「设备录像 / 平台录像查询」的区别计划录制是主动推流 MS 落盘设备侧 GB28181 RecordInfo 查询是读设备索引见docs/vss/gbs/设备录像.md、docs/vss/1.1平台与设备录像.md。二、数据模型项目说明表名sk-video-projects模型core/repositories/models/video-projects/model.go→VideoProjects主要字段name、state0 停用 / 1 启用、channelUniqueIdsJSON 数组字符串、plans周历位图字符串plans格式与前端一致固定长度168个字符**0/1**表示。下标按周分段第week天1周一 … 7周日对应plans[(week-1)*24 : week*24]共 24 位表示当天 0–23 点1表示该小时需要录像。后端周序号与前端一致TimestampWeekDay将时间戳映射为1–7周一–周六及 7 表示周日。三、前端配置3.1 代码路径本机示例路径skeyevss_backend_showcase/src/pages/configs/video-projects文件作用api.ts对接 Backend HTTPindex.tsx表格 表单 CRUDmodel.tsx列定义、表单字段含通道弹窗setChannelcomponents.tsxCPlain7×24 格子编辑plans3.2 HTTP 接口方法路径说明POST/video-project创建PUT/video-project更新DELETE/video-project删除GET/video-project/:id单行POST/video-project/list列表权限index.tsxP_1_4_4、P_0_2_4。3.3 计划编辑CPlain内部用长度168的数组0/1表示格子状态拖拽与点击批量修改。提交前通过props.setFieldValue(plans, data.join())写入连续字符串与 DBplans一致。3.4 关联通道表格列「关联通道」打开弹窗勾选通道存库为 JSON 数组字符串。四、后端配置 APIBackend → DB项目路径路由注册core/app/sev/backend/internal/handler/routes.go/video-project系列Handlercore/app/sev/backend/internal/handler/config/vp/Create / Update / Delete / Row / ListLogiccore/app/sev/backend/internal/logic/config/vp/持久化gRPC → ConfigService.VideoProjectCreate配置写入sk-video-projects后Cron 不读 HTTP仅通过Config RPC 拉列表。五、Cron 调度与任务注册5.1 进程入口core/app/sev/cron/main.go注册crontab.VideoProjectLogic{StartRecordingIds:make(chanmap[uint64]*cTypes.ChannelMSRelItem,10),StopRecordingIds:make(chanmap[uint64]*cTypes.ChannelMSRelItem,10),},5.2 调度周期core/app/sev/cron/internal/handler/crontab.go全局time.Ticker(1 * time.Second)。对每个任务记录若status!0且interval!0且now % interval 0则触发一次DO。初始化数据见core/repositories/models/crontab/variables.gouniqueId video-projectinterval 1→每秒执行一次逻辑若任务启用。timeout单次DO外层context超时默认 10 秒。blockStatus 1在VideoProjectLogic.DO内同步执行do 0则go do当前初始化为1同步。5.3 防并发VideoProjectLogic.Executing()为 true 时本轮跳过避免上一轮未结束又进。Redis 锁video-project-record-cron-lock保证多实例下同一时刻只有一个节点跑「计划计算 下发通道集合」主逻辑。六、VideoProjectLogic执行流程实现文件core/app/sev/cron/internal/logic/crontab/video-project.go。6.1DO入口makeRecord.Do仅启动一次 goroutinemakeRecord死循环从StartRecordingIds/StopRecordingIds两个 channel 收包分别调用startRecording/stopRecording与主循环解耦避免阻塞调度。根据BlockStatus同步或异步调用do。6.2do算「当前应录」的通道集抢 Redis 锁失败写CrontabRecord.Logs并返回。RPCVideoProjectList条件state 1且channelUniqueIds ! []启用且绑定了通道。对每条计划plans为空或len(plains) ! 168则跳过数据错乱。按天切成weekMaps[1..7]每天 24 个字符。取weekNum TimestampWeekDay(params.Now)当天的 24 位调用stream.NewVideoPlain().GetTimeRanges(hourState, params.Now)得到当日若干Unix 区间[start,end]左闭右开语义由GetTimeRanges实现见core/common/stream/plain.go。若params.Now落在任一区间内则将该计划下所有channelUniqueIds并入本轮「应录集合」。去重channelIds。停录集合上一轮保存在内存的execChannelUniqueIds中不在本轮channelIds里的→stopChannelIds。更新内存l.execChannelUniqueIds channelIds。RPCMediaServersWithChannelIds一次性查询「应开录 应停录」通道的设备/通道/MS 绑定ChannelMSRelItem。若start非空StartRecordingIds - start若stop非空StopRecordingIds - stop。6.3 时间片算法与前端展示对齐GetTimeRanges以params.Now所在自然日 0 点为基准把 24 个1连续段转为 Unix 时间区间跨 23→24 点用当日 24:00。七、实际开始 / 停止录制core/common/videoProject/recording.goCron 中startRecording/stopRecording调用videoProject.NewRecoding().StartRecording|StopRecording注意包内命名为NewRecoding。对map 中每个通道启独立 goroutine并发下发。7.1StartRecording计划录像POST http://{VssHttpTarget}/api/video/streamBodydeviceUniqueId、channelUniqueId—— 在 VSS 侧创建/拉起播放group与实时预览同源链路。POST http://{MS}/api/record/startstream_namestream.New().Produce(device, channel, PlayTypePlay)Cron 传入PlayTypePlay。record_interval60秒分片record_type0计划录像record_format0MP4。非下载模式RPCChannelUpdate将对应通道recordingState 1。7.2StopRecordingPOST http://{MS}/api/record/stopstream_name同上record_type: 0。非下载模式recordingState 0。八、端到端时序图流媒体 MSVSS HTTPCron VideoProjectLogicConfig/DBBackend前端 video-projects流媒体 MSVSS HTTPCron VideoProjectLogicConfig/DBBackend前端 video-projectsloop[每秒interval1且任务启用]par[每个通道 goroutine]par[停录通道]POST/PUT /video-projectplans channelUniqueIds stateVideoProjectCreate/UpdateVideoProjectList(state1)解析 plans 当前时刻是否在时段内MediaServersWithChannelIdsStartRecordingIds / StopRecordingIdsPOST /api/video/streamPOST /api/record/startChannelUpdate recordingState1POST /api/record/stopChannelUpdate recordingState0九、故障排除问题排查方向配置了计划从不录state是否为 1channelUniqueIds是否非空plans长度是否为 168当前星期与小时格子是否为1偶发不执行Cron 任务表status/intervalExecuting是否一直为 true上一轮卡住Redis 锁是否被占用开始录失败VSSVssHttpTarget是否可达/api/video/stream是否成功MS 无文件MSrecord/start是否成功stream_name与推流是否一致MS 磁盘与 record 配置停不掉StopRecording是否被调用通道是否从「应录集合」中移除MSrecord/stop日志十、相关代码索引模块路径Cron 录像计划逻辑core/app/sev/cron/internal/logic/crontab/video-project.goCron 调度器core/app/sev/cron/internal/handler/crontab.go开始/停止录制实现core/common/videoProject/recording.go周历时间区间计算core/common/stream/plain.goVideoPlain.GetTimeRanges任务默认配置core/repositories/models/crontab/variables.goUniqueIdVideoProject数据模型core/repositories/models/video-projects/