opsx 7dfbc0b680 feat(redis): 大 key 分页 + 流式导出(含票据鉴权)+ 查询行数上限默认 5000
=== 一、问题:大 key 会把内存打爆,且查询结果没有导出 ===
RedisGetKeyDetail 原来对每种类型都是全量读取:
  string → GET        list → LRANGE 0 -1     set → SMEMBERS
  zset   → ZRANGE 0 -1                        hash → HGETALL
然后整包序列化返回。像 PERP:market:* 这类盘口/kline key,
一个 key 几十万条就会:Go 侧整值入内存 → JSON 序列化翻倍 → 浏览器渲染卡死。

=== 二、后端:键详情分页(统一令牌,兼容四种类型)===
GET /redis/:id/keys/:key?db=&count=&token=
  count  默认 200,硬上限 2000
  token  语义按类型区分,前端无感知:
           list / zset  → 十进制 offset(LRANGE / ZRANGE 区间)
           hash / set   → Redis SCAN 游标(HSCAN / SSCAN)
           string       → 忽略
  返回   total / count / nextToken / truncated / previewCap

string 特殊处理:不能分页,改为预览上限 256KB
  - 用 STRLEN 拿真实大小(O(1))
  - 超过上限时用 GETRANGE 只取前 256KB,truncated=true
  - 完整内容只能靠导出

新增 wrapper 方法:HScan / ZScan / HLen / ZCard / StrLen / GetRange
(HGETALL、SMEMBERS、ZRANGE 0 -1 一律不再用于详情读取)

=== 三、后端:流式导出(两段式票据,避免令牌进 URL 日志)===
① POST /redis/:id/keys/:key/export-ticket   (走 Bearer 鉴权)
     → HMAC-SHA256(JWTSecret, instance|db|key|exp) 生成票据,2 分钟过期,
       绑定具体 instance/db/key,不能跨 key 复用
② GET  /redis/:id/keys/:key/export?db=&format=csv|jsonl&ticket=
     → 注册在鉴权组【之外】,靠票据校验
     为什么这么做:浏览器原生下载(<a download>/window.open)带不了
     Authorization 头;若前端用 axios 取 blob,整份导出会进浏览器内存,
     与「大 key 不爆内存」的目标相悖。

导出实现全程流式(服务端内存 = 单批 + 64KB 缓冲):
  string → GETRANGE 256KB 分块
  list   → LRANGE 每批 1000
  zset   → ZRANGE WITHSCORES 每批 1000
  hash   → HSCAN 游标每批 1000
  set    → SSCAN 游标每批 1000
单次上限 200 万行;CSV 带 UTF-8 BOM(Excel 直接打开不乱码);
列顺:hash(field,value) / list(index,value) / set(member) / zset(rank,member,score)

=== 四、前端:详情面板加分页条 + 4 个导出按钮 ===
RedisKeyDetailPanel:
  - 「共 N 条 · 第 X 页 · 每页 [100/200/500/1000/2000]」+ 上一页/下一页
  - SCAN 游标不可逆,上一页用令牌栈回放
  - string 超预览上限时显示黄色提示「仅预览前 256KB,共 N 字节」
  - 导出四档:当前页 CSV / 当前页 JSON(纯前端,瞬时)
             导出全部 CSV / 导出全部 JSONL(服务端流式,边读边写)
  顺带修掉头注释里「已知限制:大值不分页」的过时说明。

=== 五、查询行数上限:默认 1000 → 5000,并修掉硬编码 ===
用户要求:「默认设置里面有查询最大上限吧 比如 mysql pg 查询最大Limit限制 5000」

发现两个问题:
1. seed 默认值只有 1000(main.go query_limit_max)
2. ExecuteQuery 里还有一段硬编码:
     if input.Limit <= 0 { input.Limit = maxLimit; if input.Limit > 1000 { input.Limit = 1000 } }
   → 管理员把设置改成 5000 也白搭,前端不传 limit 时永远被压回 1000

修复:
  - seed 默认值 1000 → 5000(Value + DefaultValue)
  - getSettingInt("query_limit_max", 1000) → 5000
  - 删掉硬编码 1000 封顶,唯一口径就是设置值
  - 线上已有记录的 value 已通过 PUT /settings 改成 5000(seed 只在缺失时插入,不会自动更新旧值)

对 MySQL / PostgreSQL / Redis / MongoDB 统一生效(ExecuteQuery 按 db_type 分发,
limit 在入口处统一 clamp)。

=== 六、实测结果(线上)===
分页:
  zset PERP:market:kline:symbol_kline_12h_AAVEUSDT_PERP_MARK
        total=83,count=5 → 第1页 5 条 nextToken=5 → 第2页 5 条 nextToken=10,数据不同 ✅
  hash ST:market:ticker:symbol_ticker
        total=27,count=3 → 第1页 3 字段 nextToken=24 → 第2页 3 字段(完全不同)✅
  string PERP:market:depth:symbol_depth_BTCUSDT_PERP
        STRLEN=14192 → 导出 14192 字节,完全一致 ✅(实时 key 两次采样有差异属正常)
导出:
  zset CSV  200 / 30970 字节 / 84 行(header+83)✅
  zset JSONL 200 / 84 行 ✅
  string CSV 200 / 分块 GETRANGE 拼接完整 ✅
  无票据 → 403 ✅   伪造票据 → 403 ✅
查询上限:
  limit=99999 → 5000 行 ✅    limit=0(默认)→ 5000 行 ✅
SQL 导出 CSV(原已存在 ExportQueryCSV,支持 mysql/pg/redis/mongo)200 / 101 行 ✅

镜像:lghost/opsxsql:latest + v0.1 = sha256:68fc996681c6
2026-10-27 09:14:02 +00:00

OpsxSQL — SQL 审核与数据变更审批平台

安全、高效的企业级数据库自助查询 + 数据变更审批平台


项目简介

OpsxSQL 是一个面向 DBA 和开发人员的数据库管理平台,核心定位:

  • 自助查询 — 支持 MySQL / PostgreSQL / Redis / MongoDB 多类型数据库的在线查询
  • 数据变更审批流 — SQL 变更工单提交 → 审核 → 执行的完整审批流程
  • SQL 审核 — 内置 26+ 条审核规则,覆盖语法、性能、安全、命名规范等维度
  • 实例管理 — 统一管理多种数据库实例,支持分组和标签

技术栈

层级 技术
后端 Go 1.26 + Gin + GORM + SQLite
前端 React 19 + TypeScript + Vite + Tailwind CSS + shadcn/ui
认证 JWT + LDAP 集成
数据库 SQLite(零依赖,开箱即用)

功能模块

🗄️ 实例管理

  • MySQL / PostgreSQL / Redis / MongoDB 实例注册与连接测试
  • 实例分组管理
  • 库表结构浏览(字段、索引详情)

📝 自助查询

  • 在线 SQL 编辑器(CodeMirror)
  • 语法高亮 + 自动补全
  • 查询历史记录(上限 500 条/用户)
  • 慢查询日志分析

✅ 工单审批

  • SQL 变更工单创建(支持 DDL / DML)
  • 多级审批流程(developer → dba → admin)
  • 工单状态追踪与通知
  • 执行结果确认

🔍 SQL 审核

  • 26 条内置审核规则(MySQL 11 / PostgreSQL 7 / Redis 4 / MongoDB 4)
  • 规则类型:syntax / performance / safety / security / naming / DDL / DML / DQL / DCL
  • 规则启用/禁用 + 自定义配置

👥 用户与权限

  • 四级角色:admin / dba / developer / auditor
  • LDAP 集成登录
  • 基于角色的数据隔离

🔔 通知渠道

  • 飞书 Webhook 通知
  • 钉钉 Webhook 通知
  • 工单状态变更自动推送

📊 其他

  • Dashboard 运营数据总览
  • 审计日志
  • 公告管理
  • 系统设置

项目结构

sql-platform/
├── backend/                  # Go 后端
│   ├── cmd/server/           # 入口 + seed
│   │   ├── main.go           # 启动入口
│   │   └── seed.go           # 默认数据初始化
│   ├── internal/
│   │   ├── handler/          # 请求处理器
│   │   ├── middleware/        # 中间件(认证/权限/日志)
│   │   ├── model/            # 数据模型
│   │   └── service/          # 业务逻辑
│   ├── config/               # 配置管理
│   └── router/               # 路由注册
├── frontend/                 # React 前端
│   └── src/
│       ├── api/              # API 请求封装
│       ├── components/       # 通用组件
│       │   └── ui/           # shadcn/ui 组件
│       ├── pages/            # 页面组件(27 个)
│       └── lib/              # 工具函数
├── tests/                    # 集成测试
├── run.sh                    # 启动/停止脚本
├── docker-compose.yml        # Docker Compose 部署(单镜像)
├── Dockerfile                # 单镜像多阶段构建(前端+后端+gh-ost)
└── kubernetes/               # K8s 部署(install.sh 一键 + 清单)

快速开始

前置要求

  • Go 1.26+
  • Node.js 18+
  • npm 或 yarn

方式一:Docker Compose(推荐,单镜像前后端同源)

git clone https://gitee.com/opsx-tools/opsxsql.git
cd opsxsql

# 生成密钥(必填;SECRET_KEY 部署后不可更换,否则已加密的实例/LDAP 密码无法解密)
export SECRET_KEY=$(openssl rand -hex 32)
# 可选;不设则生产模式自动生成随机 JWT 密钥并持久化到数据卷
export JWT_SECRET=$(openssl rand -hex 32)

docker compose up -d --build

访问 http://localhost:9000(单镜像,Gin 同时托管前端静态资源与 API)。 自定义端口:OPSXSQL_PORT=18000 docker compose up -d。 安全基线:非 root 运行、no-new-privileges、cap_drop ALL、/health 健康检查、资源限额、日志轮转。

方式二:Kubernetes 一键部署

# 默认部署(镜像 opsxsql:latest)
bash kubernetes/install.sh

# 指定镜像 + 入口
IMAGE=registry.example.com/opsxsql:v1.0.0 bash kubernetes/install.sh --nodeport
IMAGE=registry.example.com/opsxsql:v1.1.0 bash kubernetes/install.sh --ingress opsxsql.example.com

install.sh 幂等:自动建命名空间/生成并复用密钥/应用 PVC+Deployment+Service,等待就绪后打印访问方式。 注意:SQLite 单写者,Deployment 固定 单副本 + Recreate(脚本已内置);详见 kubernetes/README.md。

方式三:本地开发

./run.sh start    # 后端 9000 + 前端 5173

后端 http://localhost:9000,前端 dev server http://localhost:5173(vite 代理 /api)。

默认账号

首次启动自动初始化以下账号(密码均为 admin123):

用户名 角色 说明
admin admin 超级管理员
dba dba 数据库管理员
auditor auditor 审计员(只读)
developer developer 开发者

测试

# 后端集成测试
cd tests
pip install pytest pytest-asyncio httpx
pytest -v

# 前端类型检查
cd frontend
npx tsc --noEmit

首次部署获取默认账号密码

首次启动会生成 16 位随机密码(不再固定 admin123),以下默认账号共用该密码:

账号 角色 说明
admin 超级管理员 全权限
dba DBA管理员 数据库实例与变更
auditor 审计员 审核工单
developer 开发者 提交 SQL 工单

密码同时:

  • 打印到启动日志(仅首次,日志轮转后就没了)
  • 写入数据目录 initial-admin-password.txt(权限 0600)

获取方式(推荐第 1 种)

# Docker Compose
docker compose exec <服务名> cat /app/data/initial-admin-password.txt
docker compose logs <服务名> | grep -A8 "已创建默认账号"
docker cp <容器名>:/app/data/initial-admin-password.txt ./

# Kubernetes
kubectl -n sql exec deploy/opsxsql -- cat /app/data/initial-admin-password.txt
kubectl -n sql logs deploy/opsxsql | grep -A8 "已创建默认账号"
kubectl -n sql cp <pod>:/app/data/initial-admin-password.txt ./

自定义密码(跳过随机生成)

# Docker Compose:部署前设置
export ADMIN_INIT_PASSWORD='your-strong-password'
docker compose up -d

# Kubernetes:在 deployment env 中加 ADMIN_INIT_PASSWORD(或用 Secret 注入)

说明

项 说明
幂等 已存在同名账号时不重建、不改密码;只有缺失的账号才创建
生成失败 随机密码生成失败时直接退出,不降级到公开默认口令
轮转 本平台暂无自助改密接口;改密请用管理员 POST /api/v1/users/:id/reset-password
清理 确认所有账号改密后可删除 initial-admin-password.txt

版本历史

版本 说明
v0.4.0 弹框架构重构 + 审核规则 API + LDAP 配置 + 全量 seed
v0.3.2 通知渠道对接 + TicketCreatePage 重写 + 默认用户初始化
v0.3.1 Go 后端迁移 + 工单审批流程 + 实例管理
v0.2.0 前端 UI 重构 + shadcn/ui 组件化
v0.1.0 项目初始化 + 基础 CRUD

代码统计

  • 后端 Go:29 文件,8,000+ 行
  • 前端 TypeScript:131 文件,27,000+ 行
  • 集成测试:21 文件,1,100+ 行
  • 总计 36,000+ 行

许可证

MIT License


Gitee 仓库

S
Description
No description provided
Readme
3.2 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 72.5%
Go 23%
Shell 2.1%
Python 1.3%
CSS 0.5%
Other 0.6%