数据库存储版本范围:SemVer 比较与漏洞版本匹配方案

为什么不能用字符串比较版本号 SELECT * FROM assets WHERE version < '2.15.0'字符串比较结果: '2.10.0' < '2.9.0' -- 错误!字典序 '1' < '9'SemVer(语义化版本)中 2.10.0 > 2.9.0,必须按数字分段比较。 方案一:程序端比较(推荐) 数据库只存原始版本字符串,程序端做 SemVer 比较: Java(semver4j): <dependency> <groupId>org.semver4j</groupId> <artifactId>semver4j</artifactId> <version>5.3.0</version> </dependency>import org.semver4j.Semver;public class VersionMatcher { public static boolean isVulnerable(String version, String range) { try { Semver v = Semver.parse(version); return v.satisfies(range); } catch (Exception e) { return false; } } }// 使用 isVulnerable("2.14.1", ">=2.0.0 <2.15.0") // true isVulnerable("2.15.0", ">=2.0.0 <2.15.0") // falsePython(packaging 库): from packaging.version import Version from packaging.specifiers import SpecifierSetdef is_vulnerable(version: str, spec: str) -> bool: try: return Version(version) in SpecifierSet(spec) except Exception: return Falseis_vulnerable("2.14.1", ">=2.0.0,<2.15.0") # True方案二:拆成上下界存数据库 将范围条件拆为四个字段,程序比较时不需要解析字符串: CREATE TABLE vuln_rule ( id BIGINT PRIMARY KEY, component VARCHAR(100), min_version VARCHAR(50), max_version VARCHAR(50), min_include TINYINT DEFAULT 1, -- 1=包含 >=,0=不包含 > max_include TINYINT DEFAULT 0 -- 1=包含 <=,0=不包含 < );表示 >= 2.0.0 < 2.15.0:min_version max_version min_include max_include2.0.0 2.15.0 1 0程序判断: boolean matches(String version, VulnRule rule) { Semver v = Semver.parse(version); Semver min = Semver.parse(rule.getMinVersion()); Semver max = Semver.parse(rule.getMaxVersion()); boolean lowerOk = rule.isMinInclude() ? v.isGreaterThanOrEqualTo(min) : v.isGreaterThan(min); boolean upperOk = rule.isMaxInclude() ? v.isLowerThanOrEqualTo(max) : v.isLowerThan(max); return lowerOk && upperOk; }方案三:版本号数字化后数据库直接查 将 2.15.0 转换为定宽整数 002015000,再存数据库: public static long versionToLong(String version) { String[] parts = version.split("\\."); long major = parts.length > 0 ? Long.parseLong(parts[0]) : 0; long minor = parts.length > 1 ? Long.parseLong(parts[1]) : 0; long patch = parts.length > 2 ? Long.parseLong(parts[2]) : 0; return major * 1_000_000L + minor * 1_000L + patch; }// "2.15.0" -> 2015000 // "2.9.0" -> 2009000 // "2.10.0" -> 2010000 (正确:2010000 > 2009000)数据库存 version_num BIGINT,直接用 SQL 范围查询: SELECT * FROM vuln_rule WHERE version_num >= 2000000 AND version_num < 2015000;适合版本号分段均不超过 999 的情况。 方案对比方案 优点 缺点程序端比较 支持复杂范围(pre-release) 无法在 SQL 直接过滤上下界字段 数据结构清晰,易扩展 多字段 JOIN 稍复杂数字化版本 SQL 直接查询,性能最好 分段超过 999 会溢出推荐:中小规模用方案一(程序端比较),大规模漏洞库(百万级 asset)用方案三配合索引加速。 漏洞管理平台实践 批量扫描 asset 时,避免 N+1 查询: // 一次查出所有漏洞规则 List<VulnRule> rules = vulnRuleMapper.selectAll();// 按 component 分组 Map<String, List<VulnRule>> ruleMap = rules.stream() .collect(Collectors.groupingBy(VulnRule::getComponent));// 批量匹配 for (Asset asset : assets) { List<VulnRule> candidates = ruleMap.getOrDefault(asset.getComponent(), List.of()); for (VulnRule rule : candidates) { if (VersionMatcher.isVulnerable(asset.getVersion(), rule.getRange())) { record(asset, rule); } } }

版本号存数据库 + 范围匹配:SemVer 的正确姿势

漏洞库 / 依赖资产表里经常要存"受影响版本范围",然后拿具体版本号去匹配——比如 Log4j >= 2.0-beta9 < 2.15.0、Spring 4Shell >= 5.3.0 < 5.3.18 || >= 5.2.0 < 5.2.20。直接 SQL 字符串比较必翻车: SELECT * WHERE version >= '2.9.0' AND version < '2.15.0'; -- 会把 2.10.0 判成 < 2.9.0(字符串按字典序)字典序里 '2.10' < '2.9'(因为 '1' < '9')。要按语义化版本比,得单独设计。 方案 1:存字符串,程序判断(灵活但慢) 数据库里就存原始范围: CREATE TABLE vuln_rule ( id BIGINT PRIMARY KEY, product VARCHAR(64), affected_range VARCHAR(255), -- '>= 2.0 < 2.15.0' fixed_version VARCHAR(50) );匹配时程序里跑 SemVer 库: Java: import com.vdurmont.semver4j.Requirement; import com.vdurmont.semver4j.Semver;Requirement req = Requirement.buildNPM(">=2.0 <2.15.0"); Semver ver = new Semver("2.14.1", Semver.SemverType.NPM); boolean vulnerable = req.isSatisfiedBy(ver);Node.js: import semver from "semver"; semver.satisfies("2.14.1", ">=2.0 <2.15.0"); // truePython: from packaging.specifiers import SpecifierSet from packaging.version import VersionVersion("2.14.1") in SpecifierSet(">=2.0,<2.15.0") # True优点:范围表达最灵活,各种 >=、<、!=、~、^ 都支持。 缺点:数据库查不了范围——每条规则拿出来在程序里跑一遍,几万条规则会慢。 方案 2:拆上下界(企业最常用) 把 >=2.0 <2.15.0 拆成四列: CREATE TABLE vuln_rule ( id BIGINT PRIMARY KEY, product VARCHAR(64), min_version VARCHAR(50), max_version VARCHAR(50), min_include TINYINT DEFAULT 1, -- 1: >= 0: > max_include TINYINT DEFAULT 0, -- 1: <= 0: < INDEX idx_product (product) );数据:product min_version max_version min_include max_includelog4j 2.0 2.15.0 1 0spring 5.3.0 5.3.18 1 0spring 5.2.0 5.2.20 1 0多个范围(OR 关系)拆成多行。 匹配:数据库先粗筛(按 product),程序做精确 SemVer 比: List<VulnRule> rules = mapper.findByProduct("spring"); for (VulnRule r : rules) { if (semver.gteq(target, r.minVersion) && semver.lt (target, r.maxVersion)) { // 命中 } }优点:结构清晰、粗筛快、精确匹配用程序保证正确。大多数漏洞平台走这条路。 方案 3:版本数字化后 SQL 直接查 把版本号编码成一个数字,SQL 就能直接范围查: 2.15.0 → 002 015 000 → 2015000 2.14.10 → 002 014 010 → 2014010 2.10.0 → 002 010 000 → 2010000每段用固定位数(3 位)保留: long encode(String v) { String[] p = v.split("\\."); long n = 0; for (int i = 0; i < 3; i++) { int part = i < p.length ? Integer.parseInt(p[i].split("[^0-9]")[0]) : 0; n = n * 1000 + part; } return n; }数据库: CREATE TABLE vuln_rule ( id BIGINT PRIMARY KEY, product VARCHAR(64), min_version_int BIGINT, max_version_int BIGINT, min_include TINYINT, max_include TINYINT, INDEX idx_product_range (product, min_version_int, max_version_int) );查询: SELECT * FROM vuln_rule WHERE product = 'log4j' AND 2014001 >= min_version_int -- target 版本 encode 后 AND 2014001 < max_version_int;优点:纯 SQL 就能查,走索引,几百万数据也很快。 缺点:每段有位数上限(3 位 = 999,遇到 10.1000.0 就崩) 预发布版本、build metadata(2.0-beta9、1.0+20130313144700)编码复杂 需要维护 encode / decode 函数大厂漏洞平台一般是"方案 3 粗筛 + 方案 1 精确"组合——SQL 层快速缩小范围,程序层用 SemVer 库保证语义正确。 处理预发布版本 2.0-beta9、2.0-rc1 这种版本比较:SemVer 规定 1.0.0-alpha < 1.0.0-beta < 1.0.0-rc < 1.0.0 字符串比又反过来:'1.0.0' < '1.0.0-alpha'(长度)别自己写比较,用 SemVer 库:Java:semver4j Node:semver Python:packaging 或 python-semver Go:semver三个方案对比方案 存储 查询速度 语义正确性 复杂度 推荐场景存字符串 一列 慢 完美 低 规则量少(< 1 万条)上下界拆列 4 列 中 靠程序 中 推荐,通用最优版本数字化 长整型 极快 简单版本 OK 高 亿级数据 + 版本规范一句话总结 版本号别当字符串比——SQL 存上下界列 + 程序里跑 SemVer 库判定是最稳的组合。追求极致查询速度就把版本 encode 成整数走索引,代价是预发布版本处理复杂。

停车场场景图像标注 JSON 格式解析与训练模型选择

标注 JSON 结构 自动泊车停车场场景的图像标注通常以折线形式描述结构要素,典型 JSON 结构如下: { "code": 0, "data": { "markData": { "width": 1920, "height": 1536, "marks": [ { "type": "line", "pselect": "墙", "class": { "pname": "wall" }, "point": [ {"x": 117.9, "y": 890.8}, {"x": 164.4, "y": 824.3}, {"x": 210.9, "y": 777.4} ], "attrs": {}, "markId": "y4u-C3GB3x" }, { "type": "line", "pselect": "立柱", "class": { "pname": "pillar" }, "attrs": { "P": ["square"] }, "point": [ {"x": 546.7, "y": 626.9}, {"x": 590.8, "y": 621.1} ], "markId": "y4u-CBB2i3" } ], "totalNums": { "line": 10, "polygon": 0, "rect": 0, "point": 0 } } } }关键字段说明:字段 说明type: "line" 折线标注(polyline)pselect 中文类别名(墙、路沿、立柱、其他类)class.pname 英文类别名(wall / kerb / pillar / other)point 折线坐标数组,(x,y) 为像素坐标,原点左上角attrs.P 立柱形状(square=方形柱)objectId 实例 ID,0 表示无实例关联常见标注类别类别 说明 用途wall(墙) 停车场侧墙和隔断 边界检测kerb(路沿) 车位和道路边缘 导航限制pillar(立柱) 承重柱,通常为方形 障碍物避让other(其他类) 无法分类的边缘或遮挡 训练时通常忽略训练模型选择 这类折线标注本质是 Polyline Detection / Vector Map Learning,不是目标检测。 数据量 < 1 万张:SegFormer(推荐) 把折线标注膨胀成 Mask(每类一个像素值),用 SegFormer 做语义分割: JSON 折线标注 ↓ 生成 Mask(墙=1, 路沿=2, 立柱=3, 背景=0) ↓ SegFormer 训练 ↓ Wall/Pillar/Kerb 分割图SegFormer 训练门槛低、开源实现成熟,几天内可出可用结果。 从 Mask 提取折线坐标(后处理): import cv2# 骨架提取 skeleton = cv2.ximgproc.thinning(mask)# 轮廓提取 contours, _ = cv2.findContours(skeleton, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 折线拟合(减少点数) for cnt in contours: poly = cv2.approxPolyDP(cnt, epsilon=3, closed=False) points = [(int(p[0][0]), int(p[0][1])) for p in poly]数据量 > 10 万张:MapTR(生产级) MapTR 直接学习向量化地图,输出的就是折线坐标序列,不需要 Mask 中间步骤: 输入图像 ↓ MapTR {"wall": [[x1,y1],[x2,y2],...], "pillar": [...]}代价是训练难度高、数据格式转换复杂、对数据量要求更高。 方案对比方案 输出坐标 难度 适合数据量SegFormer + 后处理 间接 低 < 1 万LaneATT / LSTR 直接 中 数万MapTR 直接 高 > 10 万OpenCV 半自动辅助标注 标注工具里用 OpenCV 实现"沿已标线方向自动延伸",可减少 50%~80% 的点击次数: import cv2 import numpy as npdef predict_next_point(image, points): if len(points) < 2: return None last = np.array(points[-1]) prev = np.array(points[-2]) direction = last - prev direction = direction / (np.linalg.norm(direction) + 1e-6) roi_size = 80 cx, cy = int(last[0]), int(last[1]) roi = image[max(0,cy-roi_size):cy+roi_size, max(0,cx-roi_size):cx+roi_size] edges = cv2.Canny(roi, 50, 150) lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=15, minLineLength=10, maxLineGap=5) if lines is None: candidate = last + direction * 30 return tuple(candidate.astype(int)) best_line = None best_score = -1 for line in lines: x1, y1, x2, y2 = line[0] lv = np.array([x2-x1, y2-y1], dtype=float) lv = lv / (np.linalg.norm(lv) + 1e-6) score = abs(np.dot(direction, lv)) if score > best_score: best_score = score best_line = line[0] if best_line is not None and best_score > 0.7: x1, y1, x2, y2 = best_line mid = np.array([cx - roi_size + (x1+x2)//2, cy - roi_size + (y1+y2)//2]) return tuple(mid.astype(int)) return tuple((last + direction * 30).astype(int))用户标完前两个点后,鼠标移动时实时预显示下一个预测点,按 Enter 确认。

Linux 上 cron 定时任务不跑?先查 cron 服务本身

写好一条 crontab,等到点没触发,先怀疑不是 cron 表达式的问题,是 cron 服务本身。Debian 系和 RHEL 系的服务名和日志路径都不一样,一步步查。 第一步:确认服务在跑 Debian / Ubuntu — 服务名叫 cron: systemctl status cron sudo systemctl start cron # 没起就启动 sudo systemctl enable cron # 开机自启CentOS / Rocky / AlmaLinux — 服务名叫 crond: systemctl status crond sudo systemctl start crond sudo systemctl enable crond粗暴验证: ps -ef | grep -E 'cron|crond'看到常驻进程就没死。 第二步:确认任务被 cron 读到了 用户任务: crontab -l # 查看 crontab -e # 编辑系统级任务分散在这几处: cat /etc/crontab ls /etc/cron.d/ ls /etc/cron.{hourly,daily,weekly,monthly}/cron 表达式必须以换行结尾——最后一条任务后没换行,很多 cron 实现会直接忽略这一条。 第三步:看日志 Ubuntu / Debian: grep CRON /var/log/syslog或走 journalctl: journalctl -u cron -fCentOS / Rocky: grep CRON /var/log/cron tail -f /var/log/cron journalctl -u crond -f日志里应该能看到每次触发的 (user) CMD (...)。看不到说明 cron 根本没执行这条:检查表达式、用户、文件位置。 第四步:cron 环境是"最小 shell" crontab 里的命令继承的 PATH 极简,通常只有 /usr/bin:/bin。写 python 会找不到,得写绝对路径: * * * * * /usr/bin/python3 /home/user/task.py或者在 crontab 里显式设 PATH: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin * * * * * python3 /home/user/task.py第五步:脚本挂了但看不到报错 cron 默认把 stdout/stderr 发邮件,服务器上一般没配。把输出重定向到日志: * * * * * /path/to/task.sh >> /var/log/task.log 2>&1>> /var/log/task.log 追加、2>&1 把 stderr 合流。看日志就能知道到底哪一步失败。 常见坑速查现象 原因完全没触发 cron 服务没起 / 表达式写错手动跑脚本 OK,cron 跑挂 PATH 缺、用户身份不同、cwd 不同报"command not found" 命令写了相对路径,改绝对路径任务跑了但看不到结果 没重定向输出,看邮件或加 >> 日志最后一条任务没跑 crontab 文件末尾没换行系统 crontab 没生效 系统级 /etc/crontab 需要用户字段系统 crontab 和用户 crontab 的区别 /etc/crontab 每行必须写明执行用户: 0 3 * * * root /root/backup.shcrontab -e 编辑的用户 crontab 不需要,因为它已经是"这个用户"的。搞混了就没反应。 一句话总结 cron 任务不跑,按 服务状态 → 任务在不在 → 日志 → PATH → 输出重定向 五步走完。90% 的问题在 PATH 缺失或输出被吞。

抖音弹幕互动游戏怎么做:架构 + 弹幕获取方案

"抖音弹幕互动游戏"——观众发弹幕 / 送礼物 / 点赞,主播直播间里显示对应的游戏事件。近两年很火:弹幕操控角色、礼物召唤怪物、弹幕押注、弹幕塔防……技术不难,玩法才是核心。 整体架构 三层: 抖音直播间 │ │ 观众发弹幕 / 礼物 / 点赞 │ ▼ 弹幕采集服务(Node.js / Go / Python) │ │ WebSocket 转发 │ ▼ 游戏服务端(Node.js / Go) │ │ WebSocket / socket.io │ ▼ 游戏前端(Phaser / PixiJS / Unity WebGL) │ │ OBS 捕获窗口 │ ▼ 主播画面 → 观众推荐技术栈:模块 技术弹幕采集 Python (TikTokLive) / Node.js服务端 Node.js (Express + socket.io)前端游戏 Phaser.js / PixiJS / Three.js后台管理 Next.js + Prisma数据库 SQLite / PostgreSQL部署 Docker + Nginx弹幕获取:三条路 1. 官方开放平台(合规首选) 抖音开放平台 提供直播 SDK 和事件订阅接口。 优点:稳、合规、企业支付通道可用。 缺点:直播弹幕权限很难申请——普通开发者基本拿不到,偏企业合作。 2. WebSocket 抓包(社区主流做法) 浏览器打开直播间时,抖音会建立 WebSocket 拉弹幕。开源项目复现了这个协议:DySpider — Node.js DouyinLiveRecorder — Python TikTokLive — Python(TikTok 版) Bilibili-Live-Client — B 站能拿到:弹幕文本 + 发送者昵称 礼物名称 + 数量 + 价值 进入直播间 / 关注 / 点赞 粉丝团勋章缺点:抖音会不定期改协议,得跟着更新。且属于灰色地带——大规模用可能被封号。 3. OBS 弹幕助手 / 直播工具 不写代码派:装个 弹弹play、Blivedm 之类的桌面工具,它自带 HTTP / WebSocket 接口把弹幕转发出来。 游戏引擎选型 2D 小游戏(推荐):Phaser.js — 老牌,社区大,示例多 PixiJS — 高性能 2D 渲染,做弹幕/粒子特别快3D 场景:Three.js — Web 3D 标配 Babylon.js — 更全能高质量游戏:Unity WebGL — 主播娱乐大项目常见 缺点:打包大、更新慢Web 优先,因为热更新方便、无需装客户端、主播直接 OBS 抓浏览器窗口就行。 最容易火的玩法 技术不是门槛,玩法才是。列几种验证过能火的: 1. 生存 / 塔防 弹幕内容映射操作: 弹幕 "1" → 角色向左移动 弹幕 "2" → 向右 弹幕 "3" → 攻击 "666" → 释放技能 礼物 → 召唤怪物 / 加血变种:弹幕修仙、弹幕养宠物、弹幕西天取经。 2. PK / 押注 分左右两队,弹幕选队: "红" → 加入红队 "蓝" → 加入蓝队 礼物 → 给自己队伍加战力按贡献值 PK,赢的一方观众瓜分虚拟奖励。 3. 抢答 / 竞猜 主播出题,弹幕抢答: "A" / "B" / "C" / "D" 第一个答对的观众上榜4. 弹幕控制角色 一款经典玩法—— Twitch Plays Pokémon 的抖音版: "上" → 角色向上一格 "下" → 向下 "跳" → 起跳所有观众同时操控一个角色,混乱又搞笑。 稳定性关键点 1. 消息高并发 热门直播间弹幕 100+ QPS 是常态,游戏引擎要批处理: // 别每条弹幕都触发一次渲染 const buffer = []; setInterval(() => { if (buffer.length) { processBatch(buffer.splice(0)); render(); } }, 100); // 每 100ms 一批2. 礼物防刷 有人一次刷 1000 个礼物,防止游戏被吃满:每用户单位时间限制事件数 高价值礼物才触发大动作,小礼物只加计数 敏感操作加 CD3. 弹幕采集断线重连 WebSocket 会断,需要指数退避重连: let retryDelay = 1000; function connect() { ws.on("close", () => { setTimeout(connect, retryDelay); retryDelay = Math.min(retryDelay * 2, 30000); }); ws.on("open", () => { retryDelay = 1000; }); }4. OBS 集成 前端做成透明背景(background: transparent),OBS 用"浏览器源"+"启用透明度"抓过去,可以叠在直播画面上。 合规提醒抖音对自动化操作直播间有敏感检测,账号可能被限流 大规模商业化必须走开放平台,别用抓包方案 礼物 / 打赏涉及支付分成,接开放平台走官方渠道 别做涉赌博性质(真金 PK)——严打一句话总结 弹幕采集 + WebSocket + 游戏前端三层架构,Phaser/PixiJS 做 2D 最快。抖音弹幕拿不到官方权限就抓 WebSocket,商业化必须走开放平台。玩法比技术更重要。