Compare commits

...

27 Commits

Author SHA1 Message Date
shanshanzhong147 e22a34ef3e build: 优化 Dockerfile 构建缓存并移除 API 解密中间件调试日志
Build docker and publish / prepare (20.15.1) (push) Successful in 12s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Successful in 1m30s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.rpc-core image_name:ppanel-rpc-core name:rpc-core]) (push) Successful in 1m55s
Build docker and publish / notify (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Successful in 4m13s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Successful in 5m30s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Successful in 1m36s
Build docker and publish / deploy (push) Successful in 44s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Successful in 1m32s
2026-03-01 20:30:28 -08:00
shanshanzhong147 b8dab70de5 feat: 新增设备登录开关配置,优化 CI Go 模块缓存机制,并添加解密中间件调试日志。
Build docker and publish / prepare (20.15.1) (push) Successful in 10s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Successful in 1m16s
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.rpc-core image_name:ppanel-rpc-core name:rpc-core]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Has been cancelled
2026-03-01 20:21:02 -08:00
shanshanzhong147 d3da56891b chore: 更新 api-dev.yaml 以硬编码应用密钥,并优化部署工作流,增加 Go 模块缓存并启用 Docker 构建缓存。
Build docker and publish / prepare (20.15.1) (push) Successful in 10s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Successful in 8m45s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Successful in 10m59s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.rpc-core image_name:ppanel-rpc-core name:rpc-core]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Has been cancelled
2026-03-01 19:44:36 -08:00
shanshanzhong147 3b4429bdd9 feat: 新增设备登录功能,包括API接口、RPC服务、逻辑处理和相关数据类型及错误码。
Build docker and publish / prepare (20.15.1) (push) Successful in 11s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Successful in 4m32s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Successful in 8m6s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Successful in 4m26s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.rpc-core image_name:ppanel-rpc-core name:rpc-core]) (push) Successful in 8m23s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Successful in 4m1s
Build docker and publish / deploy (push) Successful in 45s
Build docker and publish / notify (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Successful in 3m55s
2026-03-01 18:51:12 -08:00
shanshanzhong147 be4cc669d2 env 更新
Build docker and publish / prepare (20.15.1) (push) Successful in 10s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Successful in 4m16s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Successful in 7m46s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Successful in 4m24s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Successful in 4m9s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Successful in 3m52s
Build docker and publish / deploy (push) Successful in 37s
Build docker and publish / notify (push) Successful in 4s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.rpc-core image_name:ppanel-rpc-core name:rpc-core]) (push) Successful in 8m14s
2026-02-28 05:32:49 -08:00
shanshanzhong147 54d4ebd54c 配置信息
Build docker and publish / prepare (20.15.1) (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Successful in 8m6s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Successful in 4m7s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Successful in 4m11s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Successful in 4m11s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.rpc-core image_name:ppanel-rpc-core name:rpc-core]) (push) Successful in 8m12s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Successful in 4m9s
Build docker and publish / deploy (push) Successful in 41s
Build docker and publish / notify (push) Successful in 2s
2026-02-28 04:31:20 -08:00
shanshanzhong147 20c87d79ea core rpc
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.rpc-core image_name:ppanel-rpc-core name:rpc-core]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / prepare (20.15.1) (push) Successful in 7s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Has been cancelled
2026-02-28 04:23:03 -08:00
shanshanzhong147 b42c90188a go mod tidy
Build docker and publish / prepare (20.15.1) (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Successful in 4m10s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Has been cancelled
2026-02-28 04:15:05 -08:00
shanshanzhong147 9bdc593db4 环境变量
Build docker and publish / prepare (20.15.1) (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Failing after 1m37s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Failing after 1m42s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Failing after 1m42s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Failing after 1m34s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Failing after 1m39s
Build docker and publish / deploy (push) Has been skipped
Build docker and publish / notify (push) Successful in 2s
2026-02-28 04:02:14 -08:00
shanshanzhong147 baeb875471 dockerfile
Build docker and publish / prepare (20.15.1) (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Successful in 7m56s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Successful in 4m12s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Successful in 3m47s
Build docker and publish / deploy (push) Successful in 36s
Build docker and publish / notify (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Successful in 4m5s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Successful in 3m49s
2026-02-28 02:35:11 -08:00
shanshanzhong147 f6047c6476 dockerfile
Build docker and publish / prepare (20.15.1) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:ppanel-admin name:admin]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:ppanel-api name:api]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:ppanel-queue name:queue]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:ppanel-scheduler name:scheduler]) (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:ppanel-node name:node]) (push) Has been cancelled
2026-02-28 02:26:18 -08:00
shanshanzhong147 a67a889293 dockerfile
Build docker and publish / prepare (20.15.1) (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Successful in 8m0s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Successful in 4m4s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Successful in 3m51s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Successful in 3m53s
Build docker and publish / deploy (push) Failing after 17s
Build docker and publish / notify (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Successful in 4m23s
2026-02-28 01:54:14 -08:00
shanshanzhong147 1cd83a3099 dockerfile
Build docker and publish / prepare (20.15.1) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Has been cancelled
2026-02-28 01:44:32 -08:00
shanshanzhong147 e8c651cc6b dockerfile
Build docker and publish / prepare (20.15.1) (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Failing after 1m40s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Successful in 4m11s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Successful in 4m6s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Successful in 3m53s
Build docker and publish / notify (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Successful in 4m12s
Build docker and publish / deploy (push) Has been skipped
2026-02-27 23:48:18 -08:00
shanshanzhong147 24c2cbd0e0 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Successful in 3m58s
Build docker and publish / deploy (push) Has been skipped
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Failing after 1m41s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Successful in 4m7s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Successful in 3m35s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Successful in 3m45s
Build docker and publish / notify (push) Successful in 2s
2026-02-27 23:30:41 -08:00
shanshanzhong147 5af84c5628 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Has been cancelled
2026-02-27 23:24:38 -08:00
shanshanzhong147 7750daed54 修复gitea文件
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Has been cancelled
Build docker and publish / prepare (20.15.1) (push) Has been cancelled
2026-02-27 23:20:54 -08:00
shanshanzhong147 6363a85505 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Successful in 5s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Successful in 1m12s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Successful in 1m7s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Successful in 1m10s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Successful in 1m7s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Successful in 1m10s
Build docker and publish / deploy (push) Failing after 19s
Build docker and publish / notify (push) Failing after 3s
2026-02-27 22:02:15 -08:00
shanshanzhong147 552bfb0915 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Successful in 1m8s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Successful in 1m10s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Successful in 1m10s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Successful in 1m7s
Build docker and publish / deploy (push) Failing after 1s
Build docker and publish / notify (push) Failing after 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Successful in 1m12s
2026-02-27 21:53:37 -08:00
shanshanzhong147 18ee00a23f 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Successful in 1m8s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Successful in 1m6s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Successful in 1m10s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Successful in 1m7s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Successful in 1m7s
Build docker and publish / deploy (push) Failing after 3s
Build docker and publish / notify (push) Failing after 2s
2026-02-27 21:46:34 -08:00
shanshanzhong147 72c11fc762 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
2026-02-27 21:17:45 -08:00
shanshanzhong147 329a7815b8 修复gitea文件
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Failing after 1m18s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Failing after 1m15s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Failing after 1m16s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Failing after 1m20s
Build docker and publish / prepare (20.15.1) (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Failing after 1m18s
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
2026-02-27 21:14:31 -08:00
shanshanzhong147 2161df8cc7 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Failing after 1s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Failing after 1s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Failing after 1s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Failing after 1s
Build docker and publish / deploy (push) Has been cancelled
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Failing after 1s
2026-02-27 21:13:19 -08:00
shanshanzhong147 8d1c16ba04 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Failing after 1m39s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Failing after 35s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Failing after 35s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Failing after 35s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Failing after 35s
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
2026-02-27 21:07:31 -08:00
shanshanzhong147 dae845fe74 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Successful in 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Failing after 1m4s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Failing after 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Failing after 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Failing after 4s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Failing after 3s
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
2026-02-27 18:49:09 -08:00
shanshanzhong147 77ab4e4785 修复gitea文件
Build docker and publish / prepare (20.15.1) (push) Successful in 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Failing after 3s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Failing after 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Failing after 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Failing after 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Failing after 2s
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
2026-02-27 18:43:45 -08:00
shanshanzhong147 fe914e781e 修复gitea文件
Build docker and publish / prepare (push) Successful in 0s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.admin image_name:zero-ppanel-admin name:admin]) (push) Failing after 15s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.api image_name:zero-ppanel-api name:api]) (push) Failing after 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.node image_name:zero-ppanel-node name:node]) (push) Failing after 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.queue image_name:zero-ppanel-queue name:queue]) (push) Failing after 2s
Build docker and publish / build (map[dockerfile:deploy/Dockerfile.scheduler image_name:zero-ppanel-scheduler name:scheduler]) (push) Failing after 2s
Build docker and publish / notify (push) Has been cancelled
Build docker and publish / deploy (push) Has been cancelled
2026-02-27 17:34:26 -08:00
60 changed files with 2596 additions and 225 deletions
+12
View File
@@ -0,0 +1,12 @@
# 数据库连接字符串
# 请根据您的实际环境修改此处的数据库用户名、密码、地址、端口和数据库名
DATABASE_DSN="mysql://root:password@tcp(127.0.0.1:3306)/ppanel?charset=utf8mb4&parseTime=true&multiStatements=true"
# 应用签名密钥 (App Signature Secrets)
# 在 Go-Zero 配置 (e.g., apps/api/etc/api-dev.yaml) 中,
# AppSecrets 下的键名 (例如 "android-client", "web-client") 即为 APP_ID。
# 对应的环境变量值 (APP_SECRET_ANDROID_CLIENT, APP_SECRET_WEB_CLIENT) 为其 SECRET_KEY。
APP_SECRET_ANDROID_CLIENT="uB4G,XxL2{7b" # 对应 APP_ID "android-client"
APP_SECRET_WEB_CLIENT="uB4G,XxL2{7b" # 对应 APP_ID "web-client"
APP_SECRET_IOS_CLIENT="uB4G,XxL2{7b" # 对应 APP_ID "ios-client"
APP_SECRET_MAC_CLIENT="uB4G,XxL2{7b" # 对应 APP_ID "mac-client"
+176 -52
View File
@@ -17,8 +17,8 @@ env:
SSH_PORT: ${{ vars.SSH_PORT }}
SSH_USER: ${{ vars.SSH_USER }}
SSH_PASSWORD: ${{ github.ref_name == 'main' && vars.SSH_PASSWORD || vars.DEV_SSH_PASSWORD }}
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
TG_BOT_TOKEN: 8114337882:AAHkEx03HSu7RxN4IHBJJEnsK9aPPzNLIk0
TG_CHAT_ID: "-4940243803"
VERSION: ${{ github.sha }}
BUILDTIME: ${{ github.event.head_commit.timestamp }}
@@ -27,11 +27,20 @@ jobs:
# Job 1: 设置环境变量,供后续 jobs 共享
# ============================================================
prepare:
runs-on: ario-server
runs-on: zero-ppanel-server
container:
image: node:20
strategy:
matrix:
# 只有node支持版本号别名
node: ['20.15.1']
outputs:
docker_tag: ${{ steps.vars.outputs.docker_tag }}
container_suffix: ${{ steps.vars.outputs.container_suffix }}
deploy_path: ${{ steps.vars.outputs.deploy_path }}
has_changes: ${{ steps.changes.outputs.has_changes }}
changed_services: ${{ steps.changes.outputs.changed_services }}
deploy_services: ${{ steps.changes.outputs.deploy_services }}
steps:
- name: ⚙️ 计算部署变量
id: vars
@@ -54,16 +63,127 @@ jobs:
;;
esac
- name: 📥 下载代码
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: 🔍 检测变更服务
id: changes
shell: bash
run: |
set -euo pipefail
BEFORE="${{ github.event.before }}"
SHA="${{ github.sha }}"
RANGE=""
if [ -n "${BEFORE}" ] && [ "${BEFORE}" != "0000000000000000000000000000000000000000" ]; then
RANGE="${BEFORE}..${SHA}"
elif git rev-parse HEAD~1 >/dev/null 2>&1; then
RANGE="HEAD~1..HEAD"
else
RANGE="HEAD"
fi
if [ "${RANGE}" = "HEAD" ]; then
git show --pretty=format: --name-only HEAD | sed '/^$/d' > changed_files.txt
else
git diff --name-only "${RANGE}" > changed_files.txt
fi
echo "Changed files:"
cat changed_files.txt || true
has_file() {
grep -Eq "$1" changed_files.txt
}
add_service() {
local service="$1"
if [[ " ${services[*]} " != *" ${service} "* ]]; then
services+=("${service}")
fi
}
all=0
if has_file '^(go\.mod|go\.sum|pkg/|sql/|deploy/docker-compose\.cloud\.yml|deploy/docker-compose-env\.yml|deploy/\.env\.example|\.gitea/workflows/deploy\.yml)'; then
all=1
fi
services=()
if [ "${all}" -eq 1 ]; then
services=(rpc-core api admin node queue scheduler)
else
if has_file '^apps/rpc/' || has_file '^deploy/Dockerfile.rpc-core$' || has_file '^deploy/etc/core/'; then
add_service "rpc-core"
fi
if has_file '^apps/api/' || has_file '^deploy/Dockerfile.api$' || has_file '^deploy/etc/api/'; then
add_service "api"
fi
if has_file '^apps/admin/' || has_file '^deploy/Dockerfile.admin$' || has_file '^deploy/etc/admin/'; then
add_service "admin"
fi
if has_file '^apps/node/' || has_file '^deploy/Dockerfile.node$' || has_file '^deploy/etc/node/'; then
add_service "node"
fi
if has_file '^apps/queue/' || has_file '^deploy/Dockerfile.queue$' || has_file '^deploy/etc/queue/'; then
add_service "queue"
fi
if has_file '^apps/scheduler/' || has_file '^deploy/Dockerfile.scheduler$' || has_file '^deploy/etc/scheduler/'; then
add_service "scheduler"
fi
fi
if [ "${#services[@]}" -eq 0 ]; then
echo "No service changes detected, skip build/deploy."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "changed_services=" >> "$GITHUB_OUTPUT"
echo "deploy_services=" >> "$GITHUB_OUTPUT"
exit 0
fi
deploy_services=()
for service in "${services[@]}"; do
case "${service}" in
rpc-core) deploy_services+=("ppanel-rpc-core") ;;
api) deploy_services+=("ppanel-api") ;;
admin) deploy_services+=("ppanel-admin") ;;
node) deploy_services+=("ppanel-node") ;;
queue) deploy_services+=("ppanel-queue") ;;
scheduler) deploy_services+=("ppanel-scheduler") ;;
esac
done
changed_services="$(IFS=,; echo "${services[*]}")"
deploy_services_str="${deploy_services[*]}"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "changed_services=${changed_services}" >> "$GITHUB_OUTPUT"
echo "deploy_services=${deploy_services_str}" >> "$GITHUB_OUTPUT"
echo "Will build services: ${changed_services}"
echo "Will deploy services: ${deploy_services_str}"
# ============================================================
# Job 2: 并行矩阵构建 5 个服务镜像
# Job 2: 并行矩阵构建 6 个服务镜像 - 💥 重点修改这里 💥
# ============================================================
build:
runs-on: ario-server
runs-on: zero-ppanel-server
container: # <-- 整个 build job 在 Node.js 容器中运行
image: node:20.15.1
volumes:
- /root/go/pkg/mod:/root/go/pkg/mod
- /root/.cache/go-build:/root/.cache/go-build
needs: prepare
if: needs.prepare.outputs.has_changes == 'true' && contains(needs.prepare.outputs.changed_services, matrix.service.name)
strategy:
fail-fast: false
matrix:
service:
- name: rpc-core
dockerfile: deploy/Dockerfile.rpc-core
image_name: ppanel-rpc-core
- name: api
dockerfile: deploy/Dockerfile.api
image_name: ppanel-api
@@ -84,65 +204,61 @@ jobs:
- name: 📥 下载代码
uses: actions/checkout@v4
- name: 🔧 安装 Docker CLI
- name: Set up Go environment # 在 build job 中也设置 Go 环境
uses: actions/setup-go@v2
with:
go-version: '1.24.0' # 确保使用 go.mod 中指定的精确版本
- name: 🔧 确保 Docker CLI 可用并初始化 Go Modules
run: |
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y ca-certificates curl gnupg
# 等待 apt 锁释放
for i in $(seq 1 60); do
if ! fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; then break; fi
echo "等待 apt 锁... ($i/60)"; sleep 5
done
apt-get update -y -o Dpkg::Lock::Timeout=300
apt-get install -y -o Dpkg::Lock::Timeout=300 ca-certificates curl gnupg
# 安装 Docker 官方 CLI (API >= 1.44)
install -m 0755 -d /etc/apt/keyrings
# !!! 在 node 容器中安装 docker-ce-cli !!!
curl -fsSL https://download.docker.com/linux/debian/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update -y -o Dpkg::Lock::Timeout=300
apt-get install -y -o Dpkg::Lock::Timeout=300 docker-ce-cli docker-buildx-plugin
apt-get update -y
apt-get install -y docker-ce-cli # <<-- 确保 docker CLI 被安装
echo "Docker CLI 版本: $(docker --version)"
echo "API 版本: $(docker version --format '{{.Client.APIVersion}}')"
echo "Test Docker connectivity: $(docker info --format '{{.ServerVersion}}') (Server)"
- name: 📤 构建并推送 ${{ matrix.service.name }}
go env -w GOPROXY=https://goproxy.cn,direct # 设置 Go Proxy
go mod download # 确保所有模块已下载(缓存命中时自动跳过)
- name: 📦 构建并推送 Docker 镜像
run: |
DOCKER_TAG="${{ needs.prepare.outputs.docker_tag }}"
IMAGE_BASE="${{ env.REPO }}/${{ matrix.service.image_name }}"
set -e
IMAGE_NAME="${{ matrix.service.image_name }}"
DOCKERFILE="${{ matrix.service.dockerfile }}"
DEPLOY_TAG="${{ needs.prepare.outputs.docker_tag }}"
REPO="${{ env.REPO }}"
echo "构建服务: ${{ matrix.service.name }}"
echo "镜像: ${IMAGE_BASE}"
echo "标签: ${DOCKER_TAG} | ${{ env.VERSION }}"
FULL_IMAGE_NAME="${REPO}/${IMAGE_NAME}:${DEPLOY_TAG}"
docker build \
-f ${{ matrix.service.dockerfile }} \
--platform linux/amd64 \
--build-arg VERSION=${{ env.VERSION }} \
--build-arg BUILDTIME=${{ env.BUILDTIME }} \
-t ${IMAGE_BASE}:${{ env.VERSION }} \
-t ${IMAGE_BASE}:${DOCKER_TAG} \
.
echo "🚀 开始构建镜像: ${FULL_IMAGE_NAME} 从 ${DOCKERFILE}"
docker build -t "${FULL_IMAGE_NAME}" -f "${DOCKERFILE}" .
docker push ${IMAGE_BASE}:${{ env.VERSION }}
docker push ${IMAGE_BASE}:${DOCKER_TAG}
echo "⬆️ 推送镜像: ${FULL_IMAGE_NAME}"
docker push "${FULL_IMAGE_NAME}"
echo "✅ ${{ matrix.service.name }} 镜像推送完成"
# ============================================================
# Job 3: 部署到服务器
# ============================================================
deploy:
runs-on: ario-server
runs-on: zero-ppanel-server
container: # <-- 新增:deploy job 也在 Node.js 容器中运行
image: node:20.15.1
needs: [prepare, build]
# PR 不触发部署,只有直接推送才部署
if: github.event_name == 'push'
if: github.event_name == 'push' && needs.prepare.outputs.has_changes == 'true'
steps:
- name: 📥 下载代码 (获取 docker-compose.cloud.yml)
@@ -155,7 +271,7 @@ jobs:
username: ${{ env.SSH_USER }}
password: ${{ env.SSH_PASSWORD }}
port: ${{ env.SSH_PORT }}
source: "deploy/docker-compose.cloud.yml"
source: "deploy/docker-compose.cloud.yml,deploy/etc,deploy/.env.example"
target: "${{ needs.prepare.outputs.deploy_path }}/"
strip_components: 1
@@ -173,24 +289,32 @@ jobs:
DEPLOY_PATH="${{ needs.prepare.outputs.deploy_path }}"
DOCKER_TAG="${{ needs.prepare.outputs.docker_tag }}"
REPO="${{ env.REPO }}"
DEPLOY_SERVICES="${{ needs.prepare.outputs.deploy_services }}"
echo "部署目录: ${DEPLOY_PATH}"
echo "镜像标签: ${DOCKER_TAG}"
cd ${DEPLOY_PATH}
# 写入环境变量供 docker-compose 使用
cat > .env <<EOF
PPANEL_TAG=${DOCKER_TAG}
PPANEL_REPO=${REPO}
EOF
# 保留已有业务配置,仅更新镜像仓库和标签
touch .env
grep -q '^PPANEL_TAG=' .env \
&& sed -i "s|^PPANEL_TAG=.*|PPANEL_TAG=${DOCKER_TAG}|" .env \
|| echo "PPANEL_TAG=${DOCKER_TAG}" >> .env
grep -q '^PPANEL_REPO=' .env \
&& sed -i "s|^PPANEL_REPO=.*|PPANEL_REPO=${REPO}|" .env \
|| echo "PPANEL_REPO=${REPO}" >> .env
if [ -z "${DEPLOY_SERVICES}" ]; then
echo "没有服务变更,跳过部署。"
exit 0
fi
# 拉取所有服务的最新镜像
docker-compose -f docker-compose.cloud.yml pull
docker-compose -f docker-compose.cloud.yml pull ${DEPLOY_SERVICES}
# 滚动更新所有 ppanel 服务
docker-compose -f docker-compose.cloud.yml up -d \
ppanel-api ppanel-admin ppanel-node ppanel-queue ppanel-scheduler
docker-compose -f docker-compose.cloud.yml up -d ${DEPLOY_SERVICES}
# 清理旧镜像
docker image prune -f || true
@@ -202,7 +326,7 @@ jobs:
# Job 4: 通知
# ============================================================
notify:
runs-on: ario-server
runs-on: zero-ppanel-server
needs: [prepare, build, deploy]
if: always() && github.event_name == 'push'
steps:
@@ -212,7 +336,7 @@ jobs:
token: ${{ env.TG_BOT_TOKEN }}
to: ${{ env.TG_CHAT_ID }}
message: |
${{ (needs.build.result == 'success' && needs.deploy.result == 'success') && '✅ 部署成功!' || '❌ 部署失败!' }}
${{ needs.prepare.outputs.has_changes != 'true' && '⏭️ 无服务变更,已跳过构建与部署。' || ((needs.build.result == 'success' && needs.deploy.result == 'success') && '✅ 部署成功!' || '❌ 部署失败!') }}
📦 项目: zero-ppanel
🌿 分支: ${{ github.ref_name }}
@@ -221,6 +345,6 @@ jobs:
👤 提交者: ${{ github.actor }}
🕐 时间: ${{ github.event.head_commit.timestamp }}
构建: ${{ needs.build.result }} | 部署: ${{ needs.deploy.result }}
${{ (needs.build.result != 'success' || needs.deploy.result != 'success') && '⚠️ 请检查 Actions 日志获取详细信息' || '' }}
构建: ${{ needs.prepare.outputs.has_changes != 'true' && 'skipped(no changes)' || needs.build.result }} | 部署: ${{ needs.prepare.outputs.has_changes != 'true' && 'skipped(no changes)' || needs.deploy.result }}
${{ (needs.prepare.outputs.has_changes == 'true' && (needs.build.result != 'success' || needs.deploy.result != 'success')) && '⚠️ 请检查 Actions 日志获取详细信息' || '' }}
parse_mode: Markdown
+2
View File
@@ -14,6 +14,8 @@ logs/
# Local config overrides
etc/*.local.yaml
apps/*/etc/*.local.yaml
deploy/.env
goctl_tpl/
# IDE
.idea/
+1
View File
@@ -0,0 +1 @@
/cache
+126
View File
@@ -0,0 +1,126 @@
# the name by which the project can be referenced within Serena
project_name: "zero-ppanel"
# list of languages for which language servers are started; choose from:
# al bash clojure cpp csharp
# csharp_omnisharp dart elixir elm erlang
# fortran fsharp go groovy haskell
# java julia kotlin lua markdown
# matlab nix pascal perl php
# php_phpactor powershell python python_jedi r
# rego ruby ruby_solargraph rust scala
# swift terraform toml typescript typescript_vts
# vue yaml zig
# (This list may be outdated. For the current list, see values of Language enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- go
# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"
# The language backend to use for this project.
# If not set, the global setting from serena_config.yml is used.
# Valid values: LSP, JetBrains
# Note: the backend is fixed at startup. If a project with a different backend
# is activated post-init, an error will be returned.
language_backend:
# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true
# list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **.
# Note: global ignored_paths from serena_config.yml are also applied additively.
ignored_paths: []
# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
# * `activate_project`: Activates a project by name.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory.
# * `delete_lines`: Deletes a range of lines within a file.
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
# * `execute_shell_command`: Executes a shell command.
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
# * `initial_instructions`: Gets the initial instructions for the current project.
# Should only be used in settings where the system prompt cannot be set,
# e.g. in clients you have no control over, like Claude Desktop.
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
# * `insert_at_line`: Inserts content at a given line in a file.
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
# * `list_memories`: Lists memories in Serena's project-specific memory store.
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
# * `read_file`: Reads a file within the project directory.
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
# * `remove_project`: Removes a project from the Serena configuration.
# * `replace_lines`: Replaces a range of lines within a file with new content.
# * `replace_symbol_body`: Replaces the full definition of a symbol.
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
# * `search_for_pattern`: Performs a search for a pattern in the project.
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
# * `switch_modes`: Activates modes by providing a list of their names
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
excluded_tools: []
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
fixed_tools: []
# list of mode names to that are always to be included in the set of active modes
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this setting overrides the global configuration.
# Set this to [] to disable base modes for this project.
# Set this to a list of mode names to always include the respective modes for this project.
base_modes:
# list of mode names that are to be activated by default.
# The full set of modes to be activated is base_modes + default_modes.
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# This setting can, in turn, be overridden by CLI parameters (--mode).
default_modes:
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
# time budget (seconds) per tool call for the retrieval of additional symbol information
# such as docstrings or parameter information.
# This overrides the corresponding setting in the global configuration; see the documentation there.
# If null or missing, use the setting from the global configuration.
symbol_info_budget:
+34 -1
View File
@@ -1,6 +1,6 @@
.PHONY: build-all build-api build-admin build-node build-queue build-scheduler build-rpc-core \
gen-api gen-model docker-up docker-down docker-env-up docker-env-down lint clean \
run-api run-admin run-node run-queue run-scheduler run-rpc-core
run-api run-admin run-node run-queue run-scheduler run-rpc-core migrate migrate-up migrate-down migrate-create migrate-version migrate-force migrate-goto
# Environment: dev | test | prod (default: dev)
ENV ?= dev
@@ -81,3 +81,36 @@ lint:
clean:
rm -rf bin/
# Database migrations (using go-migrate)
migrate:
@echo "Usage: make migrate <command> [args...]"
@echo "Commands: up, down [version], create <name>, version, force <version>, goto <version>"
migrate-up:
@echo "Running database migration UP..."
go run pkg/migrate/main.go up
migrate-down:
@echo "Running database migration DOWN..."
ifeq ($(ARGS),)
go run pkg/migrate/main.go down
else
go run pkg/migrate/main.go down $(ARGS)
endif
migrate-create:
@echo "Creating new migration files..."
go run pkg/migrate/main.go create $(NAME)
migrate-version:
@echo "Getting current database migration version..."
go run pkg/migrate/main.go version
migrate-force:
@echo "Forcing database migration version..."
go run pkg/migrate/main.go force $(VERSION)
migrate-goto:
@echo "Going to specific database migration version..."
go run pkg/migrate/main.go goto $(VERSION)
+1 -1
View File
@@ -10,7 +10,7 @@ import (
common "github.com/zero-ppanel/zero-ppanel/apps/admin/internal/handler/common"
console "github.com/zero-ppanel/zero-ppanel/apps/admin/internal/handler/console"
order "github.com/zero-ppanel/zero-ppanel/apps/admin/internal/handler/order"
server serverhandler "github.com/zero-ppanel/zero-ppanel/apps/admin/internal/handler/server"
serverhandler "github.com/zero-ppanel/zero-ppanel/apps/admin/internal/handler/server"
subscribe "github.com/zero-ppanel/zero-ppanel/apps/admin/internal/handler/subscribe"
system "github.com/zero-ppanel/zero-ppanel/apps/admin/internal/handler/system"
ticket "github.com/zero-ppanel/zero-ppanel/apps/admin/internal/handler/ticket"
+1 -1
View File
@@ -21,7 +21,7 @@ func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
conf.MustLoad(*configFile, &c, conf.UseEnv())
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
+19
View File
@@ -19,6 +19,14 @@ type (
Token string `json:"token"`
}
DeviceLoginReq {
Identifier string `json:"identifier" validate:"required"`
UserAgent string `json:"user_agent" validate:"required"`
ShortCode string `json:"short_code,optional"`
CfToken string `json:"cf_token,optional"`
IP string `header:"X-Original-Forwarded-For,optional"`
}
UserRegisterReq {
Identifier string `json:"identifier"`
Email string `json:"email" validate:"required"`
@@ -58,3 +66,14 @@ service ppanel-api {
post /reset (ResetPasswordReq) returns (LoginResp)
}
@server (
prefix: /api/v1/auth
group: auth
middleware: DecryptMiddleware
)
service ppanel-api {
@doc "设备登录"
@handler DeviceLoginHandler
post /login/device (DeviceLoginReq) returns (LoginResp)
}
+7 -4
View File
@@ -37,10 +37,10 @@ CacheRedis:
AppSignature:
AppSecrets:
android-client: "uB4G,XxL2{7b"
web-client: "uB4G,XxL2{7b"
ios-client: "uB4G,XxL2{7b"
mac-client: "uB4G,XxL2{7b"
android-client: "uB4G,XxL2{7b}"
web-client: "uB4G,XxL2{7b}"
ios-client: "uB4G,XxL2{7b}"
mac-client: "uB4G,XxL2{7b}"
ValidWindowSeconds: 300
SkipPrefixes:
- /api/v1/health
@@ -49,6 +49,9 @@ Security:
Enable: true
SecuritySecret: "uB4G,XxL2{7b"
Device:
Enable: true
Asynq:
Addr: 127.0.0.1:6379
+3
View File
@@ -57,3 +57,6 @@ Security:
Asynq:
Addr: "${REDIS_HOST}"
Pass: "${REDIS_PASS}"
CoreRpc:
Target: "${CORE_RPC_TARGET}"
+3
View File
@@ -23,4 +23,7 @@ type Config struct {
Enable bool
SecuritySecret string
}
Device struct {
Enable bool
}
}
@@ -0,0 +1,28 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.2
package auth
import (
"net/http"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/logic/auth"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/svc"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/types"
"github.com/zero-ppanel/zero-ppanel/pkg/result"
)
// 设备登录
func DeviceLoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DeviceLoginReq
if err := result.Parse(r, &req); err != nil {
result.HttpResult(r, w, nil, err)
return
}
l := auth.NewDeviceLoginLogic(r.Context(), svcCtx)
resp, err := l.DeviceLogin(&req)
result.HttpResult(r, w, resp, err)
}
}
+15
View File
@@ -38,6 +38,21 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1/auth"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.DecryptMiddleware},
[]rest.Route{
{
// 设备登录
Method: http.MethodPost,
Path: "/login/device",
Handler: auth.DeviceLoginHandler(serverCtx),
},
}...,
),
rest.WithPrefix("/api/v1/auth"),
)
server.AddRoutes(
[]rest.Route{
{
@@ -0,0 +1,92 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.2
package auth
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/svc"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/types"
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/core"
"github.com/zero-ppanel/zero-ppanel/pkg/jwtx"
"github.com/zero-ppanel/zero-ppanel/pkg/xerr"
"github.com/zeromicro/go-zero/core/logx"
)
type DeviceLoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// 设备登录
func NewDeviceLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeviceLoginLogic {
return &DeviceLoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginReq) (resp *types.LoginResp, err error) {
// todo: 新建设备登录是否禁用逻辑
if !l.svcCtx.Config.Device.Enable {
return nil, xerr.NewErrCode(xerr.DeviceLoginDisabled)
}
// 1. 调用 Core RPC 设备登录
rpcResp, err := l.svcCtx.CoreRpc.DeviceLogin(l.ctx, &core.DeviceLoginReq{
Identifier: req.Identifier,
UserAgent: req.UserAgent,
Ip: req.IP,
ShortCode: req.ShortCode,
})
if err != nil {
return nil, err
}
// 2. 检查用户是否禁用
if rpcResp.IsDisabled {
return nil, xerr.NewErrCode(xerr.UserDisabled)
}
// 3. 生成 session ID
sessionId := uuid.New().String()
// 4. 生成 JWT Token
token, err := jwtx.GenerateToken(
l.svcCtx.Config.JwtAuth.AccessSecret,
rpcResp.UserId,
"device",
rpcResp.IsAdmin,
l.svcCtx.Config.JwtAuth.AccessExpire,
)
if err != nil {
l.Errorf("GenerateToken error: %v", err)
return nil, xerr.NewErrCode(xerr.ServerError)
}
// 5. 写入 Redis session
expireDuration := time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire) * time.Second
// session:{sessionId} → userId
sessionKey := fmt.Sprintf("session:%s", sessionId)
if err := l.svcCtx.Redis.SetexCtx(l.ctx, sessionKey, fmt.Sprintf("%d", rpcResp.UserId), int(expireDuration.Seconds())); err != nil {
l.Errorf("Redis set session error: %v", err)
}
// device:{identifier} → sessionId
deviceKey := fmt.Sprintf("device:%s", req.Identifier)
if err := l.svcCtx.Redis.SetexCtx(l.ctx, deviceKey, sessionId, int(expireDuration.Seconds())); err != nil {
l.Errorf("Redis set device session error: %v", err)
}
return &types.LoginResp{
Token: token,
}, nil
}
@@ -8,6 +8,7 @@ import (
"io"
"net"
"net/http"
"net/url"
"strings"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/config"
@@ -87,6 +88,10 @@ func (m *DecryptMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return
}
r.Body = io.NopCloser(bytes.NewBuffer(plain))
// 防止 httpx.Parse 内部 r.ParseForm() 消费已替换的 body。
// Go 标准库 ParseForm 首行检查 r.PostForm==nil 才读 body,提前置空即可跳过。
r.PostForm = url.Values{}
r.ContentLength = int64(len(plain))
}
next(rw, r)
+6 -3
View File
@@ -6,17 +6,19 @@ package svc
import (
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/config"
"github.com/zero-ppanel/zero-ppanel/apps/api/internal/middleware"
coreClient "github.com/zero-ppanel/zero-ppanel/apps/rpc/core/coreclient"
coreClient "github.com/zero-ppanel/zero-ppanel/apps/rpc/core/coreClient"
"github.com/zero-ppanel/zero-ppanel/pkg/signature"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/zrpc"
)
type ServiceContext struct {
Config config.Config
CoreRpc coreClient.Core
Redis *redis.Redis
SignatureMiddleware *middleware.SignatureMiddleware
DecryptMiddleware *middleware.DecryptMiddleware
DecryptMiddleware rest.Middleware
}
func NewServiceContext(c config.Config) *ServiceContext {
@@ -26,7 +28,8 @@ func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
CoreRpc: coreClient.NewCore(zrpc.MustNewClient(c.CoreRpc)),
Redis: rds,
SignatureMiddleware: middleware.NewSignatureMiddleware(c, nonceStore),
DecryptMiddleware: middleware.NewDecryptMiddleware(c),
DecryptMiddleware: middleware.NewDecryptMiddleware(c).Handle,
}
}
+8
View File
@@ -31,6 +31,14 @@ type CreateTicketReq struct {
Content string `json:"content"`
}
type DeviceLoginReq struct {
Identifier string `json:"identifier" validate:"required"`
UserAgent string `json:"user_agent" validate:"required"`
ShortCode string `json:"short_code,optional"`
CfToken string `json:"cf_token,optional"`
IP string `header:"X-Original-Forwarded-For,optional"`
}
type DocumentDetailReq struct {
Id int64 `path:"id"`
}
+1 -2
View File
@@ -23,14 +23,13 @@ func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
conf.MustLoad(*configFile, &c, conf.UseEnv())
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
ctx := svc.NewServiceContext(c)
server.Use(ctx.SignatureMiddleware.Handle)
server.Use(ctx.DecryptMiddleware.Handle)
handler.RegisterHandlers(server, ctx)
// Registe global http error handler
+1 -1
View File
@@ -21,7 +21,7 @@ func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
conf.MustLoad(*configFile, &c, conf.UseEnv())
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
+1 -1
View File
@@ -19,7 +19,7 @@ func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
conf.MustLoad(*configFile, &c, conf.UseEnv())
logx.MustSetup(c.Log)
if c.Telemetry.Name != "" {
+1 -1
View File
@@ -22,7 +22,7 @@ func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
conf.MustLoad(*configFile, &c, conf.UseEnv())
ctx := svc.NewServiceContext(c)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
+20
View File
@@ -45,6 +45,23 @@ message GetNodeInfoResp {
string status = 4;
}
// ----------------------------------------------------------------------------
// Device 服务定义
// ----------------------------------------------------------------------------
message DeviceLoginReq {
string identifier = 1;
string user_agent = 2;
string ip = 3;
string short_code = 4;
}
message DeviceLoginResp {
int64 user_id = 1;
bool is_admin = 2;
bool is_disabled = 3;
bool is_new_user = 4;
}
// ----------------------------------------------------------------------------
// 核心 RPC 服务接口
// ----------------------------------------------------------------------------
@@ -57,4 +74,7 @@ service Core {
// 节点相关
rpc GetNodeInfo(GetNodeInfoReq) returns(GetNodeInfoResp);
// 设备登录
rpc DeviceLogin(DeviceLoginReq) returns(DeviceLoginResp);
}
+168 -9
View File
@@ -374,6 +374,145 @@ func (x *GetNodeInfoResp) GetStatus() string {
return ""
}
// ----------------------------------------------------------------------------
// Device 服务定义
// ----------------------------------------------------------------------------
type DeviceLoginReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"`
UserAgent string `protobuf:"bytes,2,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"`
ShortCode string `protobuf:"bytes,4,opt,name=short_code,json=shortCode,proto3" json:"short_code,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceLoginReq) Reset() {
*x = DeviceLoginReq{}
mi := &file_core_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeviceLoginReq) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeviceLoginReq) ProtoMessage() {}
func (x *DeviceLoginReq) ProtoReflect() protoreflect.Message {
mi := &file_core_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeviceLoginReq.ProtoReflect.Descriptor instead.
func (*DeviceLoginReq) Descriptor() ([]byte, []int) {
return file_core_proto_rawDescGZIP(), []int{6}
}
func (x *DeviceLoginReq) GetIdentifier() string {
if x != nil {
return x.Identifier
}
return ""
}
func (x *DeviceLoginReq) GetUserAgent() string {
if x != nil {
return x.UserAgent
}
return ""
}
func (x *DeviceLoginReq) GetIp() string {
if x != nil {
return x.Ip
}
return ""
}
func (x *DeviceLoginReq) GetShortCode() string {
if x != nil {
return x.ShortCode
}
return ""
}
type DeviceLoginResp struct {
state protoimpl.MessageState `protogen:"open.v1"`
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
IsAdmin bool `protobuf:"varint,2,opt,name=is_admin,json=isAdmin,proto3" json:"is_admin,omitempty"`
IsDisabled bool `protobuf:"varint,3,opt,name=is_disabled,json=isDisabled,proto3" json:"is_disabled,omitempty"`
IsNewUser bool `protobuf:"varint,4,opt,name=is_new_user,json=isNewUser,proto3" json:"is_new_user,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceLoginResp) Reset() {
*x = DeviceLoginResp{}
mi := &file_core_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeviceLoginResp) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeviceLoginResp) ProtoMessage() {}
func (x *DeviceLoginResp) ProtoReflect() protoreflect.Message {
mi := &file_core_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeviceLoginResp.ProtoReflect.Descriptor instead.
func (*DeviceLoginResp) Descriptor() ([]byte, []int) {
return file_core_proto_rawDescGZIP(), []int{7}
}
func (x *DeviceLoginResp) GetUserId() int64 {
if x != nil {
return x.UserId
}
return 0
}
func (x *DeviceLoginResp) GetIsAdmin() bool {
if x != nil {
return x.IsAdmin
}
return false
}
func (x *DeviceLoginResp) GetIsDisabled() bool {
if x != nil {
return x.IsDisabled
}
return false
}
func (x *DeviceLoginResp) GetIsNewUser() bool {
if x != nil {
return x.IsNewUser
}
return false
}
var File_core_proto protoreflect.FileDescriptor
const file_core_proto_rawDesc = "" +
@@ -403,11 +542,27 @@ const file_core_proto_rawDesc = "" +
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x16\n" +
"\x06server\x18\x03 \x01(\tR\x06server\x12\x16\n" +
"\x06status\x18\x04 \x01(\tR\x06status2\xa8\x01\n" +
"\x06status\x18\x04 \x01(\tR\x06status\"~\n" +
"\x0eDeviceLoginReq\x12\x1e\n" +
"\n" +
"identifier\x18\x01 \x01(\tR\n" +
"identifier\x12\x1d\n" +
"\n" +
"user_agent\x18\x02 \x01(\tR\tuserAgent\x12\x0e\n" +
"\x02ip\x18\x03 \x01(\tR\x02ip\x12\x1d\n" +
"\n" +
"short_code\x18\x04 \x01(\tR\tshortCode\"\x86\x01\n" +
"\x0fDeviceLoginResp\x12\x17\n" +
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x19\n" +
"\bis_admin\x18\x02 \x01(\bR\aisAdmin\x12\x1f\n" +
"\vis_disabled\x18\x03 \x01(\bR\n" +
"isDisabled\x12\x1e\n" +
"\vis_new_user\x18\x04 \x01(\bR\tisNewUser2\xe4\x01\n" +
"\x04Core\x12(\n" +
"\x04Ping\x12\v.core.Empty\x1a\x13.core.BasicResponse\x12:\n" +
"\vGetUserInfo\x12\x14.core.GetUserInfoReq\x1a\x15.core.GetUserInfoResp\x12:\n" +
"\vGetNodeInfo\x12\x14.core.GetNodeInfoReq\x1a\x15.core.GetNodeInfoRespB\bZ\x06./coreb\x06proto3"
"\vGetNodeInfo\x12\x14.core.GetNodeInfoReq\x1a\x15.core.GetNodeInfoResp\x12:\n" +
"\vDeviceLogin\x12\x14.core.DeviceLoginReq\x1a\x15.core.DeviceLoginRespB\bZ\x06./coreb\x06proto3"
var (
file_core_proto_rawDescOnce sync.Once
@@ -421,7 +576,7 @@ func file_core_proto_rawDescGZIP() []byte {
return file_core_proto_rawDescData
}
var file_core_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_core_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_core_proto_goTypes = []any{
(*Empty)(nil), // 0: core.Empty
(*BasicResponse)(nil), // 1: core.BasicResponse
@@ -429,16 +584,20 @@ var file_core_proto_goTypes = []any{
(*GetUserInfoResp)(nil), // 3: core.GetUserInfoResp
(*GetNodeInfoReq)(nil), // 4: core.GetNodeInfoReq
(*GetNodeInfoResp)(nil), // 5: core.GetNodeInfoResp
(*DeviceLoginReq)(nil), // 6: core.DeviceLoginReq
(*DeviceLoginResp)(nil), // 7: core.DeviceLoginResp
}
var file_core_proto_depIdxs = []int32{
0, // 0: core.Core.Ping:input_type -> core.Empty
2, // 1: core.Core.GetUserInfo:input_type -> core.GetUserInfoReq
4, // 2: core.Core.GetNodeInfo:input_type -> core.GetNodeInfoReq
1, // 3: core.Core.Ping:output_type -> core.BasicResponse
3, // 4: core.Core.GetUserInfo:output_type -> core.GetUserInfoResp
5, // 5: core.Core.GetNodeInfo:output_type -> core.GetNodeInfoResp
3, // [3:6] is the sub-list for method output_type
0, // [0:3] is the sub-list for method input_type
6, // 3: core.Core.DeviceLogin:input_type -> core.DeviceLoginReq
1, // 4: core.Core.Ping:output_type -> core.BasicResponse
3, // 5: core.Core.GetUserInfo:output_type -> core.GetUserInfoResp
5, // 6: core.Core.GetNodeInfo:output_type -> core.GetNodeInfoResp
7, // 7: core.Core.DeviceLogin:output_type -> core.DeviceLoginResp
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
@@ -455,7 +614,7 @@ func file_core_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_core_proto_rawDesc), len(file_core_proto_rawDesc)),
NumEnums: 0,
NumMessages: 6,
NumMessages: 8,
NumExtensions: 0,
NumServices: 1,
},
+40
View File
@@ -22,6 +22,7 @@ const (
Core_Ping_FullMethodName = "/core.Core/Ping"
Core_GetUserInfo_FullMethodName = "/core.Core/GetUserInfo"
Core_GetNodeInfo_FullMethodName = "/core.Core/GetNodeInfo"
Core_DeviceLogin_FullMethodName = "/core.Core/DeviceLogin"
)
// CoreClient is the client API for Core service.
@@ -38,6 +39,8 @@ type CoreClient interface {
GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error)
// 节点相关
GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts ...grpc.CallOption) (*GetNodeInfoResp, error)
// 设备登录
DeviceLogin(ctx context.Context, in *DeviceLoginReq, opts ...grpc.CallOption) (*DeviceLoginResp, error)
}
type coreClient struct {
@@ -78,6 +81,16 @@ func (c *coreClient) GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts .
return out, nil
}
func (c *coreClient) DeviceLogin(ctx context.Context, in *DeviceLoginReq, opts ...grpc.CallOption) (*DeviceLoginResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DeviceLoginResp)
err := c.cc.Invoke(ctx, Core_DeviceLogin_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// CoreServer is the server API for Core service.
// All implementations must embed UnimplementedCoreServer
// for forward compatibility.
@@ -92,6 +105,8 @@ type CoreServer interface {
GetUserInfo(context.Context, *GetUserInfoReq) (*GetUserInfoResp, error)
// 节点相关
GetNodeInfo(context.Context, *GetNodeInfoReq) (*GetNodeInfoResp, error)
// 设备登录
DeviceLogin(context.Context, *DeviceLoginReq) (*DeviceLoginResp, error)
mustEmbedUnimplementedCoreServer()
}
@@ -111,6 +126,9 @@ func (UnimplementedCoreServer) GetUserInfo(context.Context, *GetUserInfoReq) (*G
func (UnimplementedCoreServer) GetNodeInfo(context.Context, *GetNodeInfoReq) (*GetNodeInfoResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetNodeInfo not implemented")
}
func (UnimplementedCoreServer) DeviceLogin(context.Context, *DeviceLoginReq) (*DeviceLoginResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeviceLogin not implemented")
}
func (UnimplementedCoreServer) mustEmbedUnimplementedCoreServer() {}
func (UnimplementedCoreServer) testEmbeddedByValue() {}
@@ -186,6 +204,24 @@ func _Core_GetNodeInfo_Handler(srv interface{}, ctx context.Context, dec func(in
return interceptor(ctx, in, info, handler)
}
func _Core_DeviceLogin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeviceLoginReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CoreServer).DeviceLogin(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Core_DeviceLogin_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CoreServer).DeviceLogin(ctx, req.(*DeviceLoginReq))
}
return interceptor(ctx, in, info, handler)
}
// Core_ServiceDesc is the grpc.ServiceDesc for Core service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -205,6 +241,10 @@ var Core_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetNodeInfo",
Handler: _Core_GetNodeInfo_Handler,
},
{
MethodName: "DeviceLogin",
Handler: _Core_DeviceLogin_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "core.proto",
+17 -7
View File
@@ -2,7 +2,7 @@
// goctl 1.9.2
// Source: core.proto
package coreclient
package coreClient
import (
"context"
@@ -14,12 +14,14 @@ import (
)
type (
BasicResponse = core.BasicResponse
Empty = core.Empty
GetNodeInfoReq = core.GetNodeInfoReq
GetNodeInfoResp = core.GetNodeInfoResp
GetUserInfoReq = core.GetUserInfoReq
GetUserInfoResp = core.GetUserInfoResp
BasicResponse = core.BasicResponse
DeviceLoginReq = core.DeviceLoginReq
DeviceLoginResp = core.DeviceLoginResp
Empty = core.Empty
GetNodeInfoReq = core.GetNodeInfoReq
GetNodeInfoResp = core.GetNodeInfoResp
GetUserInfoReq = core.GetUserInfoReq
GetUserInfoResp = core.GetUserInfoResp
Core interface {
// Ping 检查服务健康状态
@@ -28,6 +30,8 @@ type (
GetUserInfo(ctx context.Context, in *GetUserInfoReq, opts ...grpc.CallOption) (*GetUserInfoResp, error)
// 节点相关
GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts ...grpc.CallOption) (*GetNodeInfoResp, error)
// 设备登录
DeviceLogin(ctx context.Context, in *DeviceLoginReq, opts ...grpc.CallOption) (*DeviceLoginResp, error)
}
defaultCore struct {
@@ -58,3 +62,9 @@ func (m *defaultCore) GetNodeInfo(ctx context.Context, in *GetNodeInfoReq, opts
client := core.NewCoreClient(m.cli.Conn())
return client.GetNodeInfo(ctx, in, opts...)
}
// 设备登录
func (m *defaultCore) DeviceLogin(ctx context.Context, in *DeviceLoginReq, opts ...grpc.CallOption) (*DeviceLoginResp, error) {
client := core.NewCoreClient(m.cli.Conn())
return client.DeviceLogin(ctx, in, opts...)
}
@@ -0,0 +1,104 @@
package logic
import (
"context"
"database/sql"
"errors"
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/core"
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/repo"
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/svc"
"github.com/zero-ppanel/zero-ppanel/pkg/xerr"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type DeviceLoginLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDeviceLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeviceLoginLogic {
return &DeviceLoginLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// DeviceLogin 设备登录:查设备 → 不存在则注册 → 返回用户信息
func (l *DeviceLoginLogic) DeviceLogin(in *core.DeviceLoginReq) (*core.DeviceLoginResp, error) {
// 1. 查询设备
device, err := l.svcCtx.DeviceRepo.FindDeviceByIdentifier(l.ctx, in.Identifier)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
l.Errorf("FindDeviceByIdentifier error: %v", err)
return nil, status.Error(codes.Code(xerr.DatabaseQueryError), "查询设备失败")
}
var userID int64
var isNewUser bool
var isAdmin bool
if errors.Is(err, sql.ErrNoRows) || device == nil {
// 2. 设备不存在,创建用户+设备
userID, err = l.svcCtx.DeviceRepo.CreateUserWithDevice(l.ctx, repo.CreateUserWithDeviceParams{
Identifier: in.Identifier,
UserAgent: in.UserAgent,
IP: in.Ip,
ShortCode: in.ShortCode,
})
if err != nil {
l.Errorf("CreateUserWithDevice error: %v", err)
return nil, status.Error(codes.Code(xerr.DatabaseInsertError), "创建用户设备失败")
}
isNewUser = true
// 记录注册日志
_ = l.svcCtx.DeviceRepo.InsertLoginLog(l.ctx, repo.LoginLogParams{
UserID: userID,
Method: "device",
IP: in.Ip,
UserAgent: in.UserAgent,
Success: true,
})
} else {
// 3. 设备存在,检查是否启用
if !device.Enabled {
return nil, status.Error(codes.Code(xerr.DeviceNotEnabled), "设备已禁用")
}
userID = device.UserID
// 查用户信息
user, err := l.svcCtx.DeviceRepo.FindUserByID(l.ctx, userID)
if err != nil {
l.Errorf("FindUserByID error: %v", err)
return nil, status.Error(codes.Code(xerr.DatabaseQueryError), "查询用户失败")
}
if !user.Enable {
return nil, status.Error(codes.Code(xerr.UserDisabled), "用户已禁用")
}
isAdmin = user.IsAdmin
}
// 4. 记录登录日志
_ = l.svcCtx.DeviceRepo.InsertLoginLog(l.ctx, repo.LoginLogParams{
UserID: userID,
Method: "device",
IP: in.Ip,
UserAgent: in.UserAgent,
Success: true,
})
return &core.DeviceLoginResp{
UserId: userID,
IsAdmin: isAdmin,
IsDisabled: false,
IsNewUser: isNewUser,
}, nil
}
+155
View File
@@ -0,0 +1,155 @@
package repo
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"time"
"github.com/zero-ppanel/zero-ppanel/pkg/cryptox"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
// DeviceInfo represents a row from user_device table.
type DeviceInfo struct {
ID int64 `db:"id"`
UserID int64 `db:"user_id"`
Identifier string `db:"Identifier"`
Enabled bool `db:"enabled"`
}
// UserInfo represents minimal user fields needed for login.
type UserInfo struct {
ID int64 `db:"id"`
Enable bool `db:"enable"`
IsAdmin bool `db:"is_admin"`
IsDeleted bool `db:"is_del"`
}
// CreateUserWithDeviceParams holds parameters for creating a user and device in one transaction.
type CreateUserWithDeviceParams struct {
Identifier string
UserAgent string
IP string
ShortCode string
}
// LoginLogParams holds parameters for inserting a login log.
type LoginLogParams struct {
UserID int64
Method string
IP string
UserAgent string
Success bool
}
// DeviceRepo defines device-related data access methods.
type DeviceRepo interface {
FindDeviceByIdentifier(ctx context.Context, identifier string) (*DeviceInfo, error)
FindUserByID(ctx context.Context, userID int64) (*UserInfo, error)
CreateUserWithDevice(ctx context.Context, params CreateUserWithDeviceParams) (int64, error)
InsertLoginLog(ctx context.Context, params LoginLogParams) error
}
type deviceRepo struct {
conn sqlx.SqlConn
}
func NewDeviceRepo(conn sqlx.SqlConn) DeviceRepo {
return &deviceRepo{conn: conn}
}
func (r *deviceRepo) FindDeviceByIdentifier(ctx context.Context, identifier string) (*DeviceInfo, error) {
var d DeviceInfo
err := r.conn.QueryRowCtx(ctx, &d,
"SELECT `id`, `user_id`, `Identifier`, `enabled` FROM `user_device` WHERE `Identifier` = ? LIMIT 1",
identifier,
)
if err != nil {
return nil, err
}
return &d, nil
}
func (r *deviceRepo) FindUserByID(ctx context.Context, userID int64) (*UserInfo, error) {
var u UserInfo
err := r.conn.QueryRowCtx(ctx, &u,
"SELECT `id`, `enable`, `is_admin`, COALESCE(`is_del`, 0) AS `is_del` FROM `user` WHERE `id` = ? LIMIT 1",
userID,
)
if err != nil {
return nil, err
}
return &u, nil
}
func (r *deviceRepo) CreateUserWithDevice(ctx context.Context, params CreateUserWithDeviceParams) (int64, error) {
// Generate a random password hash for device-only users
randomPwd, err := cryptox.GeneratePasswordHash("device-placeholder-" + params.Identifier)
if err != nil {
return 0, fmt.Errorf("hash password: %w", err)
}
var userID int64
err = r.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
// 1. Create user
result, err := session.ExecCtx(ctx,
"INSERT INTO `user` (`password`, `algo`, `salt`, `enable`, `is_admin`, `created_at`, `updated_at`) VALUES (?, 'bcrypt', 'default', 1, 0, NOW(3), NOW(3))",
randomPwd,
)
if err != nil {
return fmt.Errorf("insert user: %w", err)
}
userID, err = result.LastInsertId()
if err != nil {
return fmt.Errorf("last insert id: %w", err)
}
// 2. Create auth method
_, err = session.ExecCtx(ctx,
"INSERT INTO `user_auth_methods` (`user_id`, `auth_type`, `auth_identifier`, `verified`, `created_at`, `updated_at`) VALUES (?, 'device', ?, 1, NOW(3), NOW(3))",
userID, params.Identifier,
)
if err != nil {
return fmt.Errorf("insert auth method: %w", err)
}
// 3. Create device
_, err = session.ExecCtx(ctx,
"INSERT INTO `user_device` (`user_id`, `ip`, `Identifier`, `short_code`, `user_agent`, `online`, `enabled`, `created_at`, `updated_at`) VALUES (?, ?, ?, ?, ?, 0, 1, NOW(3), NOW(3))",
userID, params.IP, params.Identifier, params.ShortCode, params.UserAgent,
)
if err != nil {
return fmt.Errorf("insert device: %w", err)
}
return nil
})
return userID, err
}
func (r *deviceRepo) InsertLoginLog(ctx context.Context, params LoginLogParams) error {
content, _ := json.Marshal(map[string]interface{}{
"method": params.Method,
"login_ip": params.IP,
"user_agent": params.UserAgent,
"success": params.Success,
"timestamp": time.Now().UnixMilli(),
})
_, err := r.conn.ExecCtx(ctx,
"INSERT INTO `system_logs` (`type`, `date`, `object_id`, `content`, `created_at`) VALUES (?, ?, ?, ?, NOW(3))",
6, // type=6 is Login log
time.Now().Format("2006-01-02"),
params.UserID,
string(content),
)
if err != nil {
return fmt.Errorf("insert login log: %w", err)
}
return nil
}
// ErrNotFound is re-exported for convenience.
var ErrNotFound = sql.ErrNoRows
@@ -40,3 +40,9 @@ func (s *CoreServer) GetNodeInfo(ctx context.Context, in *core.GetNodeInfoReq) (
l := logic.NewGetNodeInfoLogic(ctx, s.svcCtx)
return l.GetNodeInfo(in)
}
// 设备登录
func (s *CoreServer) DeviceLogin(ctx context.Context, in *core.DeviceLoginReq) (*core.DeviceLoginResp, error) {
l := logic.NewDeviceLoginLogic(ctx, s.svcCtx)
return l.DeviceLogin(in)
}
+17 -3
View File
@@ -1,13 +1,27 @@
package svc
import "github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/config"
import (
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/config"
"github.com/zero-ppanel/zero-ppanel/apps/rpc/core/internal/repo"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type ServiceContext struct {
Config config.Config
Config config.Config
DB sqlx.SqlConn
Redis *redis.Redis
DeviceRepo repo.DeviceRepo
}
func NewServiceContext(c config.Config) *ServiceContext {
db := sqlx.NewMysql(c.MySQL.DataSource)
rds := redis.MustNewRedis(c.CacheRedis)
return &ServiceContext{
Config: c,
Config: c,
DB: db,
Redis: rds,
DeviceRepo: repo.NewDeviceRepo(db),
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
conf.MustLoad(*configFile, &c, conf.UseEnv())
logx.MustSetup(c.Log)
if c.Telemetry.Name != "" {
+15
View File
@@ -0,0 +1,15 @@
PPANEL_REPO=registry.kxsw.us/vpn-server
PPANEL_TAG=latest
# 如果密码包含 $,请写成 $$,避免被 docker compose 当成变量展开
MYSQL_DSN=root:replace-with-db-password@tcp(mysql:3306)/ppanel?charset=utf8mb4&parseTime=true
REDIS_HOST=redis:6379
REDIS_PASS= kP9#vR2$mX7
JWT_SECRET=replace-with-strong-secret
JWT_ADMIN_SECRET=replace-with-strong-secret
APP_SECRET=replace-with-strong-secret
SECURITY_SECRET=replace-with-strong-secret
NODE_SECRET=replace-with-strong-secret
CORE_RPC_TARGET=ppanel-rpc-core:8083
+10 -4
View File
@@ -1,11 +1,17 @@
FROM golang:1.23-alpine AS builder
# syntax=docker/dockerfile:1
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /ppanel-admin ./apps/admin/admin.go
COPY apps/admin ./apps/admin
COPY pkg ./pkg
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
CGO_ENABLED=0 go build -trimpath -o /ppanel-admin ./apps/admin/ppaneladmin.go
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
+11 -4
View File
@@ -1,11 +1,18 @@
FROM golang:1.23-alpine AS builder
# syntax=docker/dockerfile:1
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /ppanel-api ./apps/api/api.go
COPY apps/api ./apps/api
COPY apps/rpc ./apps/rpc
COPY pkg ./pkg
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
CGO_ENABLED=0 go build -trimpath -o /ppanel-api ./apps/api/ppanel.go
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
+10 -4
View File
@@ -1,11 +1,17 @@
FROM golang:1.23-alpine AS builder
# syntax=docker/dockerfile:1
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /ppanel-node ./apps/node/node.go
COPY apps/node ./apps/node
COPY pkg ./pkg
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
CGO_ENABLED=0 go build -trimpath -o /ppanel-node ./apps/node/ppanelnode.go
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
+10 -4
View File
@@ -1,11 +1,17 @@
FROM golang:1.23-alpine AS builder
# syntax=docker/dockerfile:1
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /ppanel-queue ./apps/queue/queue.go
COPY apps/queue ./apps/queue
COPY pkg ./pkg
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
CGO_ENABLED=0 go build -trimpath -o /ppanel-queue ./apps/queue/queue.go
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
+25
View File
@@ -0,0 +1,25 @@
# syntax=docker/dockerfile:1
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
go mod download
COPY apps/rpc ./apps/rpc
COPY pkg ./pkg
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
CGO_ENABLED=0 go build -trimpath -o /ppanel-rpc-core ./apps/rpc/core/core.go
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
ENV TZ=Asia/Shanghai
WORKDIR /app
COPY --from=builder /ppanel-rpc-core .
COPY apps/rpc/core/etc /app/etc
EXPOSE 8083
CMD ["./ppanel-rpc-core", "-f", "etc/core.yaml"]
+10 -4
View File
@@ -1,11 +1,17 @@
FROM golang:1.23-alpine AS builder
# syntax=docker/dockerfile:1
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /ppanel-scheduler ./apps/scheduler/scheduler.go
COPY apps/scheduler ./apps/scheduler
COPY pkg ./pkg
RUN --mount=type=cache,target=/root/go/pkg/mod,id=gomodcache \
--mount=type=cache,target=/root/.cache/go-build,id=gobuildcache \
CGO_ENABLED=0 go build -trimpath -o /ppanel-scheduler ./apps/scheduler/scheduler.go
FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
+4 -1
View File
@@ -11,10 +11,13 @@ services:
MYSQL_DATABASE: ppanel
volumes:
- mysql_data:/var/lib/mysql
- ../sql:/docker-entrypoint-initdb.d
# - ../sql:/docker-entrypoint-initdb.d
redis:
image: redis:7-alpine
env_file:
- ./.env
command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASS:?REDIS_PASS is required}"]
ports:
- "6379:6379"
volumes:
+27 -2
View File
@@ -2,12 +2,29 @@
# 镜像由 CI 预先构建并推送到镜像仓库
# 本地基础设施 (mysql/redis) 需提前独立部署
version: '3.8'
services:
ppanel-rpc-core:
image: ${PPANEL_REPO}/ppanel-rpc-core:${PPANEL_TAG:-latest}
restart: unless-stopped
env_file:
- ./.env
ports:
- "8083:8083"
volumes:
- ./etc/core:/app/etc
logging:
driver: json-file
options:
max-size: "50m"
max-file: "3"
ppanel-api:
image: ${PPANEL_REPO}/ppanel-api:${PPANEL_TAG:-latest}
restart: unless-stopped
depends_on:
- ppanel-rpc-core
env_file:
- ./.env
ports:
- "8080:8080"
volumes:
@@ -21,6 +38,8 @@ services:
ppanel-admin:
image: ${PPANEL_REPO}/ppanel-admin:${PPANEL_TAG:-latest}
restart: unless-stopped
env_file:
- ./.env
ports:
- "8081:8081"
volumes:
@@ -34,6 +53,8 @@ services:
ppanel-node:
image: ${PPANEL_REPO}/ppanel-node:${PPANEL_TAG:-latest}
restart: unless-stopped
env_file:
- ./.env
ports:
- "8082:8082"
volumes:
@@ -47,6 +68,8 @@ services:
ppanel-queue:
image: ${PPANEL_REPO}/ppanel-queue:${PPANEL_TAG:-latest}
restart: unless-stopped
env_file:
- ./.env
volumes:
- ./etc/queue:/app/etc
logging:
@@ -58,6 +81,8 @@ services:
ppanel-scheduler:
image: ${PPANEL_REPO}/ppanel-scheduler:${PPANEL_TAG:-latest}
restart: unless-stopped
env_file:
- ./.env
volumes:
- ./etc/scheduler:/app/etc
logging:
+6 -16
View File
@@ -2,9 +2,7 @@ version: '3.8'
services:
ppanel-api:
build:
context: ..
dockerfile: deploy/Dockerfile.api
image: "${REPO}/api:latest"
ports:
- "8080:8080"
- "6160:6160"
@@ -16,9 +14,7 @@ services:
- ../apps/api/etc:/app/etc
ppanel-admin:
build:
context: ..
dockerfile: deploy/Dockerfile.admin
image: "${REPO}/admin:latest"
ports:
- "8081:8081"
- "6161:6161"
@@ -30,9 +26,7 @@ services:
- ../apps/admin/etc:/app/etc
ppanel-node:
build:
context: ..
dockerfile: deploy/Dockerfile.node
image: "${REPO}/node:latest"
ports:
- "8082:8082"
- "6162:6162"
@@ -46,9 +40,7 @@ services:
replicas: 2
ppanel-queue:
build:
context: ..
dockerfile: deploy/Dockerfile.queue
image: "${REPO}/queue:latest"
depends_on:
- mysql
- redis
@@ -57,9 +49,7 @@ services:
- ../apps/queue/etc:/app/etc
ppanel-scheduler:
build:
context: ..
dockerfile: deploy/Dockerfile.scheduler
image: "${REPO}/scheduler:latest"
depends_on:
- mysql
- redis
@@ -73,7 +63,7 @@ services:
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_ROOT_PASSWORD: pavR2$mX7*tQ
MYSQL_DATABASE: ppanel
volumes:
- mysql_data:/var/lib/mysql
+51
View File
@@ -0,0 +1,51 @@
Name: zero-ppanel-admin
Host: 0.0.0.0
Port: 8081
Mode: pro
Log:
Mode: file
Encoding: json
Level: info
Path: /var/log/zero-ppanel/admin
KeepDays: 15
Rotation: daily
Telemetry:
Name: zero-ppanel-admin
Endpoint: http://jaeger:4318/v1/traces
Sampler: 0.1
Batcher: otlphttp
DevServer:
Enabled: true
Port: 6161
EnableMetrics: true
EnablePprof: false
JwtAuth:
AccessSecret: "${JWT_ADMIN_SECRET}"
AccessExpire: 28800
MySQL:
DataSource: "${MYSQL_DSN}"
Redis:
Host: "${REDIS_HOST}"
Type: node
Pass: "${REDIS_PASS}"
CacheRedis:
Host: "${REDIS_HOST}"
Type: node
Pass: "${REDIS_PASS}"
AppSignature:
AppSecrets:
android-client: "${APP_SECRET}"
web-client: "${APP_SECRET}"
ios-client: "${APP_SECRET}"
mac-client: "${APP_SECRET}"
ValidWindowSeconds: 300
SkipPrefixes:
- /api/v1/admin/health
+62
View File
@@ -0,0 +1,62 @@
Name: zero-ppanel-api
Host: 0.0.0.0
Port: 8080
Mode: pro
Log:
Mode: file
Encoding: json
Level: info
Path: /var/log/zero-ppanel/api
KeepDays: 15
Rotation: daily
Telemetry:
Name: zero-ppanel-api
Endpoint: http://jaeger:4318/v1/traces
Sampler: 0.1
Batcher: otlphttp
DevServer:
Enabled: true
Port: 6160
EnableMetrics: true
EnablePprof: false
JwtAuth:
AccessSecret: "${JWT_SECRET}"
AccessExpire: 86400
MySQL:
DataSource: "${MYSQL_DSN}"
Redis:
Host: "${REDIS_HOST}"
Type: node
Pass: "${REDIS_PASS}"
CacheRedis:
Host: "${REDIS_HOST}"
Type: node
Pass: "${REDIS_PASS}"
AppSignature:
AppSecrets:
android-client: "${APP_SECRET}"
web-client: "${APP_SECRET}"
ios-client: "${APP_SECRET}"
mac-client: "${APP_SECRET}"
ValidWindowSeconds: 300
SkipPrefixes:
- /api/v1/health
Security:
Enable: true
SecuritySecret: "${SECURITY_SECRET}"
Asynq:
Addr: "${REDIS_HOST}"
Pass: "${REDIS_PASS}"
CoreRpc:
Target: "${CORE_RPC_TARGET}"
+31
View File
@@ -0,0 +1,31 @@
Name: core.rpc
ListenOn: 0.0.0.0:8083
Mode: pro
Log:
Mode: file
Encoding: json
Level: info
Path: /var/log/zero-ppanel/rpc-core
KeepDays: 15
Rotation: daily
Telemetry:
Name: core.rpc
Endpoint: http://jaeger:4318/v1/traces
Sampler: 0.1
Batcher: otlphttp
DevServer:
Enabled: true
Port: 6163
EnableMetrics: true
EnablePprof: false
MySQL:
DataSource: "${MYSQL_DSN}"
CacheRedis:
Host: "${REDIS_HOST}"
Type: node
Pass: "${REDIS_PASS}"
+49
View File
@@ -0,0 +1,49 @@
Name: zero-ppanel-node
Host: 0.0.0.0
Port: 8082
Mode: pro
Log:
Mode: file
Encoding: json
Level: info
Path: /var/log/zero-ppanel/node
KeepDays: 15
Rotation: daily
Telemetry:
Name: zero-ppanel-node
Endpoint: http://jaeger:4318/v1/traces
Sampler: 0.1
Batcher: otlphttp
DevServer:
Enabled: true
Port: 6162
EnableMetrics: true
EnablePprof: false
NodeSecret: "${NODE_SECRET}"
MySQL:
DataSource: "${MYSQL_DSN}"
Redis:
Host: "${REDIS_HOST}"
Type: node
Pass: "${REDIS_PASS}"
CacheRedis:
Host: "${REDIS_HOST}"
Type: node
Pass: "${REDIS_PASS}"
AppSignature:
AppSecrets:
android-client: "${APP_SECRET}"
web-client: "${APP_SECRET}"
ios-client: "${APP_SECRET}"
mac-client: "${APP_SECRET}"
ValidWindowSeconds: 300
SkipPrefixes:
- /api/v1/node/health
+28
View File
@@ -0,0 +1,28 @@
Name: zero-ppanel-queue
Mode: pro
Log:
Mode: file
Encoding: json
Level: info
Path: /var/log/zero-ppanel/queue
KeepDays: 15
Rotation: daily
Telemetry:
Name: zero-ppanel-queue
Endpoint: http://jaeger:4318/v1/traces
Sampler: 0.1
Batcher: otlphttp
MySQL:
DataSource: "${MYSQL_DSN}"
Redis:
Host: "${REDIS_HOST}"
Type: node
Pass: "${REDIS_PASS}"
Asynq:
Addr: "${REDIS_HOST}"
Pass: "${REDIS_PASS}"
+28
View File
@@ -0,0 +1,28 @@
Name: zero-ppanel-scheduler
Mode: pro
Log:
Mode: file
Encoding: json
Level: info
Path: /var/log/zero-ppanel/scheduler
KeepDays: 15
Rotation: daily
Telemetry:
Name: zero-ppanel-scheduler
Endpoint: http://jaeger:4318/v1/traces
Sampler: 0.1
Batcher: otlphttp
MySQL:
DataSource: "${MYSQL_DSN}"
Redis:
Host: "${REDIS_HOST}"
Type: node
Pass: "${REDIS_PASS}"
Asynq:
Addr: "${REDIS_HOST}"
Pass: "${REDIS_PASS}"
+20 -16
View File
@@ -3,36 +3,39 @@ module github.com/zero-ppanel/zero-ppanel
go 1.24.0
require (
github.com/go-sql-driver/mysql v1.9.3
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/golang-migrate/migrate/v4 v4.19.1
github.com/hibiken/asynq v0.25.1
github.com/pkg/errors v0.9.1
github.com/joho/godotenv v1.5.1
github.com/zeromicro/go-zero v1.7.6
golang.org/x/crypto v0.48.0
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.36.1
google.golang.org/grpc v1.74.2
google.golang.org/protobuf v1.36.7
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
@@ -58,29 +61,30 @@ require (
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.8.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+81 -32
View File
@@ -1,3 +1,9 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0=
@@ -14,26 +20,43 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
@@ -43,15 +66,19 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -59,8 +86,8 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -74,6 +101,8 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslC
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hibiken/asynq v0.25.1 h1:phj028N0nm15n8O2ims+IvJ2gz4k2auvermngh9JhTw=
github.com/hibiken/asynq v0.25.1/go.mod h1:pazWNOLBu0FEynQRBvHA26qdIKRSmfdIfUm4HdsLmXg=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -91,6 +120,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -98,25 +129,37 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
@@ -131,8 +174,8 @@ github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
@@ -165,12 +208,16 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
@@ -179,12 +226,14 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDO
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
@@ -212,8 +261,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -235,8 +284,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -248,14 +297,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+4 -3
View File
@@ -13,10 +13,11 @@ type Claims struct {
jwt.RegisteredClaims
}
func GenerateToken(secret string, userId int64, isAdmin bool, expire int64) (string, error) {
func GenerateToken(secret string, userId int64, loginType string, isAdmin bool, expire int64) (string, error) {
claims := Claims{
UserId: userId,
IsAdmin: isAdmin,
UserId: userId,
LoginType: loginType,
IsAdmin: isAdmin,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)),
IssuedAt: jwt.NewNumericDate(time.Now()),
+156
View File
@@ -0,0 +1,156 @@
package main
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"strconv"
"time"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/mysql"
_ "github.com/golang-migrate/migrate/v4/source/file"
"github.com/joho/godotenv"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 加载 .env 文件
err := godotenv.Load(".env")
if err != nil {
log.Println("未能加载 .env 文件,可能使用环境变量。")
}
if len(os.Args) < 2 {
log.Fatalf("使用方法: go run main.go <command> [args...]")
}
command := os.Args[1]
if command == "create" {
if len(os.Args) < 3 {
log.Fatalf("使用方法: go run main.go create <migration_name>")
}
createMigration(os.Args[2])
return
}
databaseUrl := os.Getenv("DATABASE_DSN")
if databaseUrl == "" {
log.Fatalf("DATABASE_DSN 环境变量未设置")
}
m, err := migrate.New(
"file://sql", // 迁移文件路径
databaseUrl,
)
if err != nil {
log.Fatalf("初始化迁移工具失败: %v", err)
}
defer func() {
if sourceErr, databaseErr := m.Close(); sourceErr != nil || databaseErr != nil {
log.Printf("关闭迁移工具时出错: sourceErr=%v, databaseErr=%v", sourceErr, databaseErr)
}
}()
switch command {
case "up":
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
log.Fatalf("执行迁移 Up 失败: %v", err)
}
log.Println("迁移 Up 成功")
case "down":
if len(os.Args) < 3 { // 不带版本号时回滚最新一个版本
if err := m.Down(); err != nil && err != migrate.ErrNoChange {
log.Fatalf("执行迁移 Down 失败: %v", err)
}
log.Println("迁移 Down 成功 (回滚一个版本)")
} else { // 带版本号时回滚到指定版本
version, err := strconv.Atoi(os.Args[2])
if err != nil {
log.Fatalf("版本号错误: %v", err)
}
if err := m.Migrate(uint(version)); err != nil && err != migrate.ErrNoChange {
log.Fatalf("执行迁移 Migrate 到版本 %d 失败: %v", version, err)
}
log.Printf("迁移 Down 成功 (回滚到版本 %d)", version)
}
case "goto":
if len(os.Args) < 3 {
log.Fatalf("使用方法: go run main.go goto <version>")
}
version, err := strconv.Atoi(os.Args[2])
if err != nil {
log.Fatalf("版本号错误: %v", err)
}
if err := m.Migrate(uint(version)); err != nil && err != migrate.ErrNoChange {
log.Fatalf("执行迁移 Goto 版本 %d 失败: %v", version, err)
}
log.Printf("迁移 Goto 成功 (到版本 %d)", version)
case "force":
if len(os.Args) < 3 {
log.Fatalf("使用方法: go run main.go force <version>")
}
version, err := strconv.Atoi(os.Args[2])
if err != nil {
log.Fatalf("版本号错误: %v", err)
}
if err := m.Force(int(version)); err != nil {
log.Fatalf("强制设置版本 %d 失败: %v", version, err)
}
log.Printf("强制设置版本 %d 成功", version)
case "version":
version, dirty, err := m.Version()
if err != nil && err != migrate.ErrNoChange {
log.Fatalf("获取版本失败: %v", err)
}
if dirty {
log.Printf("当前数据库版本: %d (dirty)", version)
} else {
log.Printf("当前数据库版本: %d", version)
}
default:
log.Fatalf("未知命令: %s. 支持的命令: up, down, goto, force, version, create", command)
}
}
func createMigration(name string) {
timestamp := time.Now().Unix()
upFilename := fmt.Sprintf("sql/%d_%s.up.sql", timestamp, name)
downFilename := fmt.Sprintf("sql/%d_%s.down.sql", timestamp, name)
if err := os.WriteFile(upFilename, []byte(""), 0644); err != nil {
log.Fatalf("创建 %s 失败: %v", upFilename, err)
}
if err := os.WriteFile(downFilename, []byte(""), 0644); err != nil {
log.Fatalf("创建 %s 失败: %v", downFilename, err)
}
log.Printf("成功创建迁移文件: %s 和 %s", upFilename, downFilename)
}
// 辅助函数,用于检查数据库连接是否正常
func checkDBConnection(dsn string) error {
db, err := sql.Open("mysql", dsn)
if err != nil {
return fmt.Errorf("无法连接数据库: %v", err)
}
defer db.Close()
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
db.SetConnMaxLifetime(5 * time.Second)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err = db.PingContext(ctx)
if err != nil {
return fmt.Errorf("无法 Ping 数据库: %v", err)
}
return nil
}
+2
View File
@@ -24,4 +24,6 @@ const (
SignatureInvalid = 7003
SignatureReplay = 7004
DecryptFailed = 7005
DeviceLoginDisabled = 8001
DeviceNotEnabled = 8002
)
+2
View File
@@ -24,6 +24,8 @@ var codeText = map[int]string{
SignatureInvalid: "签名错误",
SignatureReplay: "重放攻击",
DecryptFailed: "解密失败",
DeviceLoginDisabled: "设备登录未启用",
DeviceNotEnabled: "设备已禁用",
}
func IsCodeErr(errcode uint32) bool {
+30
View File
@@ -0,0 +1,30 @@
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `ads`;
DROP TABLE IF EXISTS `announcement`;
DROP TABLE IF EXISTS `auth_method`;
DROP TABLE IF EXISTS `coupon`;
DROP TABLE IF EXISTS `document`;
DROP TABLE IF EXISTS `nodes`;
DROP TABLE IF EXISTS `order`;
DROP TABLE IF EXISTS `payment`;
DROP TABLE IF EXISTS `redemption_code`;
DROP TABLE IF EXISTS `redemption_record`;
DROP TABLE IF EXISTS `server`;
DROP TABLE IF EXISTS `servers`;
DROP TABLE IF EXISTS `subscribe`;
DROP TABLE IF EXISTS `subscribe_application`;
DROP TABLE IF EXISTS `system`;
DROP TABLE IF EXISTS `system_logs`;
DROP TABLE IF EXISTS `task`;
DROP TABLE IF EXISTS `ticket`;
DROP TABLE IF EXISTS `ticket_follow`;
DROP TABLE IF EXISTS `traffic_log`;
DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `user_auth_methods`;
DROP TABLE IF EXISTS `user_device`;
DROP TABLE IF EXISTS `user_device_online_record`;
DROP TABLE IF EXISTS `user_subscribe`;
DROP TABLE IF EXISTS `withdrawals`;
SET FOREIGN_KEY_CHECKS = 1;
+519
View File
@@ -0,0 +1,519 @@
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `ads` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Ads title',
`type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Ads type',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Ads content',
`target_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Ads target url',
`start_time` datetime DEFAULT NULL COMMENT 'Ads start time',
`end_time` datetime DEFAULT NULL COMMENT 'Ads end time',
`status` tinyint(1) DEFAULT '0' COMMENT 'Ads status,0 disable,1 enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
`description` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Description',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `announcement` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Content',
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show',
`pinned` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Pinned',
`popup` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Popup',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `auth_method` (
`id` bigint NOT NULL AUTO_INCREMENT,
`method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'method',
`config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'OAuth Configuration',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_auth_method` (`method`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `coupon` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Name',
`code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Coupon Code',
`count` bigint NOT NULL DEFAULT '0' COMMENT 'Count Limit',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Coupon Type: 1: Percentage 2: Fixed Amount',
`discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount',
`start_time` bigint NOT NULL DEFAULT '0' COMMENT 'Start Time',
`expire_time` bigint NOT NULL DEFAULT '0' COMMENT 'Expire Time',
`user_limit` bigint NOT NULL DEFAULT '0' COMMENT 'User Limit',
`subscribe` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Limit',
`used_count` bigint NOT NULL DEFAULT '0' COMMENT 'Used Count',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enable',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_coupon_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `document` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Title',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Document Content',
`tags` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Document Tags',
`show` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Show',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `nodes` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name',
`tags` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags',
`port` smallint unsigned NOT NULL DEFAULT '0' COMMENT 'Connect Port',
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Connect Address',
`server_id` bigint NOT NULL DEFAULT '0' COMMENT 'Server ID',
`protocol` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled',
`sort` int unsigned NOT NULL DEFAULT '0' COMMENT 'Sort',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`parent_id` bigint DEFAULT NULL COMMENT 'Parent Order Id',
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'User Id',
`order_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Order No',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Type: 1: Subscribe, 2: Renewal, 3: ResetTraffic, 4: Recharge',
`quantity` bigint NOT NULL DEFAULT '1' COMMENT 'Quantity',
`price` bigint NOT NULL DEFAULT '0' COMMENT 'Original price',
`amount` bigint NOT NULL DEFAULT '0' COMMENT 'Order Amount',
`gift_amount` bigint NOT NULL DEFAULT '0' COMMENT 'User Gift Amount',
`discount` bigint NOT NULL DEFAULT '0' COMMENT 'Discount Amount',
`coupon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Coupon',
`coupon_discount` bigint NOT NULL DEFAULT '0' COMMENT 'Coupon Discount Amount',
`commission` bigint NOT NULL DEFAULT '0' COMMENT 'Order Commission',
`payment_id` bigint NOT NULL DEFAULT '-1' COMMENT 'Payment Id',
`method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Method',
`fee_amount` bigint NOT NULL DEFAULT '0' COMMENT 'Fee Amount',
`trade_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Trade No',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Order Status: 1: Pending, 2: Paid, 3:Close, 4: Failed, 5:Finished',
`subscribe_id` bigint NOT NULL DEFAULT '0' COMMENT 'Subscribe Id',
`subscribe_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Renewal Subscribe Token',
`is_new` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is New Order',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_order_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `payment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Payment Name',
`platform` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Platform',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Payment Description',
`token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Payment Token',
`icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Payment Icon',
`domain` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Notification Domain',
`config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Payment Configuration',
`fee_mode` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Fee Mode: 0: No Fee 1: Percentage 2: Fixed Amount 3: Percentage + Fixed Amount',
`fee_percent` bigint DEFAULT '0' COMMENT 'Fee Percentage',
`fee_amount` bigint DEFAULT '0' COMMENT 'Fixed Fee Amount',
`enable` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Enabled',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `redemption_code` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
`code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Redemption Code',
`total_count` bigint NOT NULL DEFAULT '0' COMMENT 'Total Redemption Count',
`used_count` bigint NOT NULL DEFAULT '0' COMMENT 'Used Redemption Count',
`subscribe_plan` bigint NOT NULL DEFAULT '0' COMMENT 'Subscribe Plan',
`unit_time` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'month' COMMENT 'Unit Time: day, month, quarter, half_year, year',
`quantity` bigint NOT NULL DEFAULT '1' COMMENT 'Quantity',
`status` tinyint NOT NULL DEFAULT '1' COMMENT 'Status: 1=enabled, 0=disabled',
`created_at` datetime NOT NULL COMMENT 'Creation Time',
`updated_at` datetime NOT NULL COMMENT 'Update Time',
`deleted_at` datetime DEFAULT NULL COMMENT 'Deletion Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`),
KEY `idx_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Redemption Code Table';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `redemption_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
`redemption_code_id` bigint NOT NULL DEFAULT '0' COMMENT 'Redemption Code Id',
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'User Id',
`subscribe_id` bigint NOT NULL DEFAULT '0' COMMENT 'Subscribe Id',
`unit_time` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'month' COMMENT 'Unit Time',
`quantity` bigint NOT NULL DEFAULT '1' COMMENT 'Quantity',
`redeemed_at` datetime NOT NULL COMMENT 'Redeemed Time',
`created_at` datetime NOT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`),
KEY `idx_redemption_code_id` (`redemption_code_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Redemption Record Table';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `schema_migrations` (
`version` bigint NOT NULL,
`dirty` tinyint(1) NOT NULL,
PRIMARY KEY (`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `server` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Name',
`tags` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Tags',
`country` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Country',
`city` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'City',
`latitude` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'latitude',
`longitude` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'longitude',
`server_addr` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Address',
`relay_mode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'none' COMMENT 'Relay Mode',
`relay_node` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Relay Node',
`speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit',
`traffic_ratio` decimal(4,2) NOT NULL DEFAULT '0.00' COMMENT 'Traffic Ratio',
`group_id` bigint DEFAULT NULL COMMENT 'Group ID',
`protocol` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Protocol',
`config` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Config',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Enabled',
`sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort',
`last_reported_at` datetime(3) DEFAULT NULL COMMENT 'Last Reported Time',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `servers` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Name',
`country` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Country',
`city` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'City',
`ratio` decimal(4,2) NOT NULL DEFAULT '0.00' COMMENT 'Traffic Ratio',
`address` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Server Address',
`sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort',
`protocols` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Protocol',
`last_reported_at` datetime(3) DEFAULT NULL COMMENT 'Last Reported Time',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
`longitude` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'longitude',
`latitude` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'latitude',
`longitude_center` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'longitude center',
`latitude_center` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'latitude center',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `subscribe` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Subscribe Name',
`language` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Language',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Subscribe Description',
`unit_price` bigint NOT NULL DEFAULT '0' COMMENT 'Unit Price',
`unit_time` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Unit Time',
`discount` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Discount',
`replacement` bigint NOT NULL DEFAULT '0' COMMENT 'Replacement',
`inventory` bigint NOT NULL DEFAULT '0' COMMENT 'Inventory',
`traffic` bigint NOT NULL DEFAULT '0' COMMENT 'Traffic',
`speed_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Speed Limit',
`device_limit` bigint NOT NULL DEFAULT '0' COMMENT 'Device Limit',
`quota` bigint NOT NULL DEFAULT '0' COMMENT 'Quota',
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Show portal page',
`sell` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Sell',
`sort` bigint NOT NULL DEFAULT '0' COMMENT 'Sort',
`deduction_ratio` bigint DEFAULT '0' COMMENT 'Deduction Ratio',
`allow_deduction` tinyint(1) DEFAULT '1' COMMENT 'Allow deduction',
`reset_cycle` bigint DEFAULT '0' COMMENT 'Reset Cycle: 0: No Reset, 1: 1st, 2: Monthly, 3: Yearly',
`renewal_reset` tinyint(1) DEFAULT '0' COMMENT 'Renew Reset',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`show_original_price` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'display the original price: 0 not display, 1 display',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
`nodes` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node IDs',
`node_tags` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Node Tags',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `subscribe_application` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Application Name',
`icon` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Application Icon',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Application Description',
`scheme` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Application Scheme',
`user_agent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'User Agent',
`is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Default Application',
`subscribe_template` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Subscribe Template',
`output_format` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'yaml' COMMENT 'Output Format',
`download_link` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Download Link',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `system` (
`id` bigint NOT NULL AUTO_INCREMENT,
`category` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Category',
`key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Key Name',
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Key Value',
`type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Type',
`desc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Description',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_system_key` (`key`),
KEY `index_key` (`key`)
) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `system_logs` (
`id` bigint NOT NULL AUTO_INCREMENT,
`type` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Log Type: 1: Email Message 2: Mobile Message 3: Subscribe 4: Subscribe Traffic 5: Server Traffic 6: Login 7: Register 8: Balance 9: Commission 10: Reset Subscribe 11: Gift',
`date` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Log Date',
`object_id` bigint NOT NULL DEFAULT '0' COMMENT 'Object ID',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Log Content',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
PRIMARY KEY (`id`),
KEY `idx_type` (`type`),
KEY `idx_object_id` (`object_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `task` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`type` tinyint NOT NULL COMMENT 'Task Type',
`scope` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Task Scope',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Task Content',
`status` tinyint NOT NULL DEFAULT '0' COMMENT 'Task Status: 0: Pending, 1: In Progress, 2: Completed, 3: Failed',
`errors` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Task Errors',
`total` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Total Number',
`current` bigint unsigned NOT NULL DEFAULT '0' COMMENT 'Current Number',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `ticket` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'Title',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Description',
`user_id` bigint NOT NULL DEFAULT '0' COMMENT 'UserId',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Status',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `ticket_follow` (
`id` bigint NOT NULL AUTO_INCREMENT,
`ticket_id` bigint NOT NULL DEFAULT '0' COMMENT 'TicketId',
`from` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'From',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Type: 1 text, 2 image',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'Content',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Create Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `traffic_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`server_id` bigint NOT NULL COMMENT 'Server ID',
`user_id` bigint NOT NULL COMMENT 'User ID',
`subscribe_id` bigint NOT NULL COMMENT 'Subscription ID',
`download` bigint DEFAULT '0' COMMENT 'Download Traffic',
`upload` bigint DEFAULT '0' COMMENT 'Upload Traffic',
`timestamp` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Traffic Log Time',
PRIMARY KEY (`id`),
KEY `idx_subscribe_id` (`subscribe_id`),
KEY `idx_server_id` (`server_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_traffic_log_time_user_sub` (`timestamp`,`user_id`,`subscribe_id`),
KEY `idx_timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'User Password',
`algo` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'default' COMMENT 'Encryption Algorithm',
`salt` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'default' COMMENT 'Password Salt',
`avatar` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'User Avatar',
`balance` bigint DEFAULT '0' COMMENT 'User Balance',
`telegram` bigint DEFAULT NULL COMMENT 'Telegram Account',
`refer_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Referral Code',
`referer_id` bigint DEFAULT NULL COMMENT 'Referrer ID',
`commission` bigint DEFAULT '0' COMMENT 'Commission',
`referral_percentage` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'Referral Percentage',
`only_first_purchase` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Only First Purchase',
`gift_amount` bigint DEFAULT '0' COMMENT 'User Gift Amount',
`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Is Account Enabled',
`is_admin` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Admin',
`valid_email` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Email Verified',
`enable_email_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Email Notifications',
`enable_telegram_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Telegram Notifications',
`enable_balance_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Balance Change Notifications',
`enable_login_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Login Notifications',
`enable_subscribe_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Subscription Notifications',
`enable_trade_notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Enable Trade Notifications',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`rules` text COLLATE utf8mb4_general_ci COMMENT 'User rules for subscription',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
`deleted_at` datetime(3) DEFAULT NULL COMMENT 'Deletion Time',
`is_del` bigint unsigned DEFAULT NULL COMMENT '1: Normal 0: Deleted',
PRIMARY KEY (`id`),
KEY `idx_referer` (`referer_id`),
KEY `idx_refer_code` (`refer_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `user_auth_methods` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`auth_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Type 1: apple 2: google 3: github 4: facebook 5: telegram 6: email 7: phone',
`auth_identifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Auth Identifier',
`verified` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Is Verified',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_auth_identifier` (`auth_identifier`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `user_device` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`subscribe_id` bigint DEFAULT NULL COMMENT 'Subscribe ID',
`ip` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Ip.',
`Identifier` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device Identifier.',
`short_code` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Short Code',
`user_agent` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'Device User Agent.',
`online` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Online',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'EnableDeviceNumber',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `user_device_online_record` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`identifier` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT 'Device Identifier',
`online_time` datetime DEFAULT NULL COMMENT 'Online Time',
`offline_time` datetime DEFAULT NULL COMMENT 'Offline Time',
`online_seconds` bigint DEFAULT NULL COMMENT 'Offline Seconds',
`duration_days` bigint DEFAULT NULL COMMENT 'Duration Days',
`created_at` datetime DEFAULT NULL COMMENT 'Creation Time',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `user_subscribe` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT 'User ID',
`order_id` bigint NOT NULL COMMENT 'Order ID',
`subscribe_id` bigint NOT NULL COMMENT 'Subscription ID',
`start_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'Subscription Start Time',
`expire_time` datetime(3) DEFAULT NULL COMMENT 'Subscription Expire Time',
`finished_at` datetime DEFAULT NULL COMMENT 'Subscribe Finished Time',
`traffic` bigint DEFAULT '0' COMMENT 'Traffic',
`download` bigint DEFAULT '0' COMMENT 'Download Traffic',
`upload` bigint DEFAULT '0' COMMENT 'Upload Traffic',
`token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'Token',
`uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'UUID',
`status` tinyint(1) DEFAULT '0' COMMENT 'Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired 4: Deducted',
`note` varchar(500) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'User note for subscription',
`created_at` datetime(3) DEFAULT NULL COMMENT 'Creation Time',
`updated_at` datetime(3) DEFAULT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_user_subscribe_token` (`token`),
UNIQUE KEY `uni_user_subscribe_uuid` (`uuid`),
KEY `idx_user_id` (`user_id`),
KEY `idx_order_id` (`order_id`),
KEY `idx_subscribe_id` (`subscribe_id`),
KEY `idx_token` (`token`),
KEY `idx_uuid` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `withdrawals` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
`user_id` bigint NOT NULL COMMENT 'User ID',
`amount` bigint NOT NULL COMMENT 'Withdrawal Amount',
`content` text COMMENT 'Withdrawal Content',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Withdrawal Status',
`reason` varchar(500) NOT NULL DEFAULT '' COMMENT 'Rejection Reason',
`created_at` datetime NOT NULL COMMENT 'Creation Time',
`updated_at` datetime NOT NULL COMMENT 'Update Time',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+30
View File
@@ -0,0 +1,30 @@
SET FOREIGN_KEY_CHECKS = 0;
TRUNCATE TABLE `ads`;
TRUNCATE TABLE `announcement`;
TRUNCATE TABLE `auth_method`;
TRUNCATE TABLE `coupon`;
TRUNCATE TABLE `document`;
TRUNCATE TABLE `nodes`;
TRUNCATE TABLE `order`;
TRUNCATE TABLE `payment`;
TRUNCATE TABLE `redemption_code`;
TRUNCATE TABLE `redemption_record`;
TRUNCATE TABLE `server`;
TRUNCATE TABLE `servers`;
TRUNCATE TABLE `subscribe`;
TRUNCATE TABLE `subscribe_application`;
TRUNCATE TABLE `system`;
TRUNCATE TABLE `system_logs`;
TRUNCATE TABLE `task`;
TRUNCATE TABLE `ticket`;
TRUNCATE TABLE `ticket_follow`;
TRUNCATE TABLE `traffic_log`;
TRUNCATE TABLE `user`;
TRUNCATE TABLE `user_auth_methods`;
TRUNCATE TABLE `user_device`;
TRUNCATE TABLE `user_device_online_record`;
TRUNCATE TABLE `user_subscribe`;
TRUNCATE TABLE `withdrawals`;
SET FOREIGN_KEY_CHECKS = 1;
File diff suppressed because one or more lines are too long
-7
View File
@@ -1,7 +0,0 @@
-- PPanel Database Schema Rollback
-- Migration: 001_init
-- Description: Drop all tables from initial schema
DROP TABLE IF EXISTS `user`;
-- TODO: Add remaining DROP TABLE statements (in reverse order of creation)
-40
View File
@@ -1,40 +0,0 @@
-- PPanel Database Schema
-- Migration: 001_init
-- Description: Initial database schema
-- ============================================================
-- User Domain
-- ============================================================
CREATE TABLE IF NOT EXISTS `user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
`telephone` varchar(32) DEFAULT NULL,
`password` varchar(255) NOT NULL DEFAULT '',
`refer_code` varchar(32) DEFAULT NULL,
`refer_id` bigint unsigned DEFAULT 0,
`avatar` varchar(512) DEFAULT '',
`balance` bigint DEFAULT 0,
`commission` bigint DEFAULT 0,
`gift_amount` bigint DEFAULT 0,
`is_admin` tinyint(1) NOT NULL DEFAULT 0,
`is_staff` tinyint(1) NOT NULL DEFAULT 0,
`enable` tinyint(1) NOT NULL DEFAULT 1,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_email` (`email`),
UNIQUE KEY `idx_telephone` (`telephone`),
UNIQUE KEY `idx_refer_code` (`refer_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- TODO: Add remaining tables
-- user_auth_methods, user_device, user_subscribe, user_balance_log,
-- user_commission_log, user_subscribe_log, user_login_log,
-- user_device_online_record, user_gift_amount_log, user_reset_subscribe_log
-- order, coupon, ticket, ticket_follow
-- payment, announcement, document
-- system, server, server_group, server_rule_group
-- subscribe, subscribe_group, subscribe_type, subscribe_application
-- ads, application, application_config, application_version
-- traffic_log, system_logs
+148
View File
@@ -0,0 +1,148 @@
/**
* 设备登录接口测试脚本
*
* 复现服务端的加密与签名逻辑:
* - AES-CBC 加密: key=SHA256(secret)[:32], iv=SHA256(md5hex(nonce)+secret)[:16]
* - 签名: HMAC-SHA256(appSecret, METHOD\nPATH\nQUERY\nBODY_SHA256\nAppId\nTimestamp\nNonce)
*/
import crypto from 'node:crypto';
import https from 'node:https';
import http from 'node:http';
// ─── 配置(对应 api-dev.yaml)─────────────────────────────────────────────────
const CONFIG = {
baseUrl: 'http://127.0.0.1:8080', // 本地测试改这里;生产改为 https://tapi.hifast.biz
appId: 'android-client',
appSecret: 'uB4G,XxL2{7b}', // AppSignature.AppSecrets[appId]
securitySecret: 'uB4G,XxL2{7b', // Security.SecuritySecret(注意少一个 }
};
// ─── AES-CBC 加密工具(与 pkg/cryptox/aes.go 对齐)─────────────────────────────
function generateKey(secret) {
return crypto.createHash('sha256').update(secret).digest().slice(0, 32);
}
function generateIv(nonce, secret) {
const md5hex = crypto.createHash('md5').update(nonce).digest('hex');
return crypto.createHash('sha256').update(md5hex + secret).digest().slice(0, 16);
}
function aesEncrypt(plaintext, secret) {
const nonce = crypto.randomBytes(8).toString('hex'); // 16位 hex,对应 Go 的 fmt.Sprintf("%x", UnixNano)
const key = generateKey(secret);
const iv = generateIv(nonce, secret);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
return {
data: encrypted.toString('base64'),
time: nonce,
};
}
// ─── 签名工具(与 pkg/signature/canonical.go 对齐)────────────────────────────
function sha256Hex(data) {
return crypto.createHash('sha256').update(data).digest('hex');
}
function buildStringToSign(method, path, rawQuery, bodyBytes, appId, timestamp, nonce) {
// 规范化 query(按 key 字母排序)
let canonical = '';
if (rawQuery) {
const params = new URLSearchParams(rawQuery);
const sorted = [...params.entries()].sort((a, b) => a[0].localeCompare(b[0]));
canonical = sorted.map(([k, v]) => `${k}=${v}`).join('&');
}
const bodyHash = sha256Hex(bodyBytes);
return [method.toUpperCase(), path, canonical, bodyHash, appId, timestamp, nonce].join('\n');
}
function hmacSha256(secret, data) {
return crypto.createHmac('sha256', secret).update(data).digest('hex');
}
// ─── 发送请求 ─────────────────────────────────────────────────────────────────
async function request(method, path, payload) {
const url = new URL(CONFIG.baseUrl + path);
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString('hex');
// 1. AES 加密 body
const { data: encData, time: encTime } = aesEncrypt(JSON.stringify(payload), CONFIG.securitySecret);
const bodyObj = { data: encData, time: encTime };
const bodyStr = JSON.stringify(bodyObj);
const bodyBytes = Buffer.from(bodyStr, 'utf8');
// 2. 构造签名
const sts = buildStringToSign(method, url.pathname, url.search.slice(1), bodyBytes, CONFIG.appId, timestamp, nonce);
const sig = hmacSha256(CONFIG.appSecret, sts);
const headers = {
'Content-Type': 'application/json',
'X-App-Id': CONFIG.appId,
'X-Timestamp': timestamp,
'X-Nonce': nonce,
'X-Signature': sig,
'Login-Type': 'device',
};
console.log('\n📤 请求信息');
console.log(' URL:', url.toString());
console.log(' timestamp:', timestamp);
console.log(' nonce:', nonce);
console.log(' signature:', sig);
console.log(' 加密 nonce(time):', encTime);
console.log(' 签名原文:\n' + sts.split('\n').map(l => ' ' + l).join('\n'));
console.log(' body:', bodyStr);
return new Promise((resolve, reject) => {
const lib = url.protocol === 'https:' ? https : http;
const req = lib.request(url, { method, headers }, (res) => {
let raw = '';
res.on('data', chunk => raw += chunk);
res.on('end', () => {
console.log('\n📥 响应');
console.log(' status:', res.statusCode);
try {
console.log(' body:', JSON.stringify(JSON.parse(raw), null, 2));
} catch {
console.log(' body:', raw);
}
resolve({ status: res.statusCode, body: raw });
});
});
req.on('error', reject);
req.write(bodyStr);
req.end();
});
}
// ─── 测试用例 ──────────────────────────────────────────────────────────────────
async function main() {
console.log('='.repeat(60));
console.log('🔐 设备登录接口测试');
console.log('='.repeat(60));
// 测试 1:正常设备登录
console.log('\n【TEST 1】正常设备登录');
await request('POST', '/api/v1/auth/login/device', {
identifier: 'test-device-001',
user_agent: 'TestClient/1.0 (Node.js test)',
short_code: '',
cf_token: '',
});
// 测试 2:不同 identifier(新设备,应该自动注册)
console.log('\n【TEST 2】新设备首次登录');
await request('POST', '/api/v1/auth/login/device', {
identifier: `device-${Date.now()}`,
user_agent: 'TestClient/1.0 (Node.js test)',
});
}
main().catch(console.error);