This commit is contained in:
zxr
2026-06-21 18:27:35 +08:00
parent 17063cbad0
commit 2fc6cd3a2d
33 changed files with 19 additions and 2367 deletions

View File

@@ -11,10 +11,7 @@ OPS 是面向医院信息化场景的开发运维一体化平台,首期交付
| `docs/integrated-ops-platform-blueprint-design.md` | 平台蓝图、分期路线和审查结论。 | | `docs/integrated-ops-platform-blueprint-design.md` | 平台蓝图、分期路线和审查结论。 |
| `docs/首期验收矩阵.md` | OPS 需求到演示路径、数据准备、通过标准和证据的映射。 | | `docs/首期验收矩阵.md` | OPS 需求到演示路径、数据准备、通过标准和证据的映射。 |
| `docs/首期数据模型与状态机.md` | 第 1 阶段核心模型、状态机和存储分工。 | | `docs/首期数据模型与状态机.md` | 第 1 阶段核心模型、状态机和存储分工。 |
| `docs/首期项目架构数据模型与接口设计.md` | 第 1 阶段可编码的项目架构、数据模型和 REST API 设计。 |
| `docs/首期UI状态覆盖.md` | 第 1 阶段页面状态覆盖要求。 | | `docs/首期UI状态覆盖.md` | 第 1 阶段页面状态覆盖要求。 |
| `docs/P1故障救援策略.md` | 采集、解析、通知、派单失败的救援策略。 |
| `docs/P1测试计划.md` | 第 1 阶段后端、前端和端到端测试计划。 |
| `docs/本地开发与验收部署说明.md` | 本地开发、联调验收和 Linux/麒麟部署边界。 | | `docs/本地开发与验收部署说明.md` | 本地开发、联调验收和 Linux/麒麟部署边界。 |
| `docs/H3C华三首批接入调研.md` | H3C/华三首批接入基线和现场确认表。 | | `docs/H3C华三首批接入调研.md` | H3C/华三首批接入基线和现场确认表。 |
| `docs/国产时序数据库选型验证.md` | TDengine、Apache IoTDB、openGemini 选型验证。 | | `docs/国产时序数据库选型验证.md` | TDengine、Apache IoTDB、openGemini 选型验证。 |

View File

@@ -30,33 +30,30 @@
## P1 ## P1
- [x] 定义采集失败、Trap/Syslog 解析失败、通知失败、派单失败的错误与救援策略。 - [] 定义采集失败、Trap/Syslog 解析失败、通知失败、派单失败的错误与救援策略。
- 原因:一体化运维平台自身故障不能静默,否则验收时无法证明平台可靠性。 - 原因:一体化运维平台自身故障不能静默,否则验收时无法证明平台可靠性。
- 验收:每类失败都有重试、降级、用户提示、日志、审计和测试要求。 - 验收:每类失败都有重试、降级、用户提示、日志、审计和测试要求。
- 产物:`docs/P1故障救援策略.md`
- [x] 为第 1 阶段建立后端与前端测试计划。 - [] 为第 1 阶段建立后端与前端测试计划。
- 原因:当前仓库尚无实际 `server/``web/` 工程,测试策略需要先指导实现。 - 原因:当前仓库尚无实际 `server/``web/` 工程,测试策略需要先指导实现。
- 验收后端包含单元、接口、SQLite 内存库测试;前端包含类型检查、状态渲染和核心 E2E。 - 验收后端包含单元、接口、SQLite 内存库测试;前端包含类型检查、状态渲染和核心 E2E。
- 产物:`docs/P1测试计划.md`
- [x] 使用 gstack 工程评审口径梳理首期架构、数据模型和接口设计。 - [] 使用 gstack 工程评审口径梳理首期架构、数据模型和接口设计。
- 原因P0/P1 文档已形成验收、状态机、UI 状态、救援和测试计划,但后续初始化 `server/``web/` 前还需要一份可编码的模块边界和 REST API 规格。 - 原因P0/P1 文档已形成验收、状态机、UI 状态、救援和测试计划,但后续初始化 `server/``web/` 前还需要一份可编码的模块边界和 REST API 规格。
- 验收:文档包含后端/前端模块划分、核心数据流、数据模型、统一响应、错误码、API 路由、前端状态映射和实施顺序。 - 验收:文档包含后端/前端模块划分、核心数据流、数据模型、统一响应、错误码、API 路由、前端状态映射和实施顺序。
- 产物:`docs/首期项目架构数据模型与接口设计.md`
- [x] 区分本地开发命令和验收部署说明。 - [] 区分本地开发命令和验收部署说明。
- 原因:本地开发和调试使用 Windows PowerShell会议已确认验收部署需要面向 Linux 与麒麟系统,模板 README 不能直接成为交付文档。 - 原因:本地开发和调试使用 Windows PowerShell会议已确认验收部署需要面向 Linux 与麒麟系统,模板 README 不能直接成为交付文档。
- 验收:实际 `server/``web/` 文档提供 Windows PowerShell 本地开发路径;`deploy/` 提供 Linux/麒麟部署、迁移、回滚和烟测说明,且不包含真实凭据。 - 验收:实际 `server/``web/` 文档提供 Windows PowerShell 本地开发路径;`deploy/` 提供 Linux/麒麟部署、迁移、回滚和烟测说明,且不包含真实凭据。
- 产物:`docs/本地开发与验收部署说明.md``deploy/README.md` - 产物:`docs/本地开发与验收部署说明.md``deploy/README.md`
- [x] 完成 H3C/华三设备首批接入调研。 - [] 完成 H3C/华三设备首批接入调研。
- 原因:会议确认现场设备以 H3C/华三为主,第 1 阶段应优先适配该品牌常见指标、接口状态和 Trap/Syslog 样例。 - 原因:会议确认现场设备以 H3C/华三为主,第 1 阶段应优先适配该品牌常见指标、接口状态和 Trap/Syslog 样例。
- 验收明确首批设备型号、SNMP 版本、OID 清单、Trap 字典、Syslog 样例、账号权限和网络连通性。 - 验收明确首批设备型号、SNMP 版本、OID 清单、Trap 字典、Syslog 样例、账号权限和网络连通性。
- 产物:`docs/H3C华三首批接入调研.md` - 产物:`docs/H3C华三首批接入调研.md`
- 风险仓库现有资料未提供现场具体型号、SNMP 版本、真实 Trap/Syslog 样例和账号权限;文档已提供首批接入基线和现场确认表,正式验收前必须由院方或现场工程师补齐。 - 风险仓库现有资料未提供现场具体型号、SNMP 版本、真实 Trap/Syslog 样例和账号权限;文档已提供首批接入基线和现场确认表,正式验收前必须由院方或现场工程师补齐。
- [x] 完成国产/国内生态时序数据库选型验证。 - [] 完成国产/国内生态时序数据库选型验证。
- 原因:高频指标和采集样本不应全部压入 PostgreSQL需要选型 TDengine、Apache IoTDB、openGemini 或其他合适产品。 - 原因:高频指标和采集样本不应全部压入 PostgreSQL需要选型 TDengine、Apache IoTDB、openGemini 或其他合适产品。
- 验收:形成选型记录,覆盖 Linux/麒麟部署、Go 连接方式、批量写入、范围查询、聚合、降采样、保留策略、备份恢复和授权风险。 - 验收:形成选型记录,覆盖 Linux/麒麟部署、Go 连接方式、批量写入、范围查询、聚合、降采样、保留策略、备份恢复和授权风险。
- 产物:`docs/国产时序数据库选型验证.md` - 产物:`docs/国产时序数据库选型验证.md`

View File

@@ -80,7 +80,7 @@
### 5.1 处理流程 ### 5.1 处理流程
```text ```text
-----------+ +-------------+ +----------------+ +-----------+ +-------------+ +----------------+
| 接收原文 | ---> | 写 raw_events | ---> | 尝试字典/规则解析 | | 接收原文 | ---> | 写 raw_events | ---> | 尝试字典/规则解析 |
+-----------+ +-------------+ +----------------+ +-----------+ +-------------+ +----------------+
| |
@@ -170,4 +170,3 @@
| 页面截图 | 资源详情、未解析事件池、通知记录、派单失败提示。 | | 页面截图 | 资源详情、未解析事件池、通知记录、派单失败提示。 |
| 日志 | 能按 `traceId` 查询采集、解析、通知、派单链路。 | | 日志 | 能按 `traceId` 查询采集、解析、通知、派单链路。 |
| 数据库记录 | `collector_runs``raw_events``notification_records``ticket_transitions` 有对应记录。 | | 数据库记录 | `collector_runs``raw_events``notification_records``ticket_transitions` 有对应记录。 |

View File

@@ -2,7 +2,7 @@
## 1. 文档目标 ## 1. 文档目标
本文定义 OPS 第 1 阶段后端、前端和端到端测试范围。当前仓库尚未初始化 `server/``web/`本文作为编码前测试规格,后续实现必须按本文补齐测试。 本文定义 OPS 第 1 阶段后端、前端和端到端测试范围。当前仓库尚未保留实际 `server/``web/` 工程;本文作为编码和联调前测试规格,后续实现必须按本文补齐测试。
## 2. 测试分层 ## 2. 测试分层
@@ -73,7 +73,7 @@
## 6. 本地验证命令 ## 6. 本地验证命令
后端初始化后至少运行: 后端至少运行:
```powershell ```powershell
Set-Location .\server Set-Location .\server
@@ -111,4 +111,3 @@ pnpm build
- 前端核心页面必须覆盖 `docs/首期UI状态覆盖.md` 中列出的状态。 - 前端核心页面必须覆盖 `docs/首期UI状态覆盖.md` 中列出的状态。
- 端到端测试必须能证明主闭环,不得只校验静态页面存在。 - 端到端测试必须能证明主闭环,不得只校验静态页面存在。
- 测试不能使用 mock 数据库替代真实数据库行为。 - 测试不能使用 mock 数据库替代真实数据库行为。

View File

@@ -1,4 +1,6 @@
<!-- /autoplan restore point: C:\Users\27105\.gstack\projects\ops\main-autoplan-restore-20260621-151741.md --> <!-- /autoplan restore point: C:\Users\27105\.gstack\projects\ops\main-autoplan-restore-20260621-181451.md -->
<!-- /autoplan restore point: C:\Users\27105\.gstack\projects\ops\main-autoplan-restore-20260621-180424.md -->
<!-- /autoplan restore point: C:\Users\27105\.gstack\projects\ops\main-autoplan-restore-20260621-151741.md -->
# 一体化运维平台完整蓝图与分期落地方案 # 一体化运维平台完整蓝图与分期落地方案
生成来源:`/office-hours` 生成来源:`/office-hours`
@@ -838,4 +840,3 @@
**审批结论:** **审批结论:**
- 用户已选择 A按当前方案批准蓝图方向。后续进入 P1 待办处理与第 1 阶段实施准备Git 远程凭据风险已按用户要求忽略,不作为当前交付阻塞项。 - 用户已选择 A按当前方案批准蓝图方向。后续进入 P1 待办处理与第 1 阶段实施准备Git 远程凭据风险已按用户要求忽略,不作为当前交付阻塞项。

View File

@@ -4,7 +4,7 @@
本文区分本地开发、联调验收和 Linux/麒麟部署三类场景,避免把模板 README 直接作为交付部署手册。 本文区分本地开发、联调验收和 Linux/麒麟部署三类场景,避免把模板 README 直接作为交付部署手册。
当前仓库尚未初始化 `server/``web/`。后续实际工程创建后,应在对应目录补充更细的运行说明。 当前仓库尚未保留实际 `server/``web/` 工程。后续实际工程创建后,应在对应目录补充更细的运行说明。
## 2. 场景边界 ## 2. 场景边界
@@ -18,16 +18,16 @@
| 目录 | 职责 | 状态 | | 目录 | 职责 | 状态 |
| --- | --- | --- | | --- | --- | --- |
| `server/` | 实际后端工程,基于 `templates/server_sample/` 初始化 | 尚未创建 | | `server/` | 实际后端工程,基于 `templates/server_sample/` 初始化后继续改造 | 尚未创建 |
| `web/` | 实际前端工程,基于 `templates/front_sample/standard` 初始化 | 尚未创建 | | `web/` | 实际前端工程,基于 `templates/front_sample/standard` 初始化 | 尚未创建 |
| `deploy/` | 部署、迁移、回滚、烟测说明和配置示例 | 已规划 | | `deploy/` | 部署、迁移、回滚、烟测说明和配置示例 | 已创建 |
| `docs/` | 需求、架构、验收和测试文档 | 已存在 | | `docs/` | 需求、架构、验收和测试文档 | 已存在 |
模板目录默认只读,不作为最终交付目录。 模板目录默认只读,不作为最终交付目录。
## 4. 初始化实际工程 ## 4. 初始化实际工程
后端初始化: 后端初始化。若在干净仓库中重新初始化,可参考
```powershell ```powershell
Copy-Item -LiteralPath .\templates\server_sample -Destination .\server -Recurse Copy-Item -LiteralPath .\templates\server_sample -Destination .\server -Recurse
@@ -43,7 +43,7 @@ Copy-Item -LiteralPath .\templates\front_sample\standard -Destination .\web -Rec
| 检查项 | 要求 | | 检查项 | 要求 |
| --- | --- | | --- | --- |
| 配置示例 | 只提交 `etc/*.example.yaml``.env.example`,不提交真实密码。 | | 配置示例 | 只提交 `etc/*.example.yaml``*.example.yaml``.env.example`,不提交真实密码。 |
| 目录结构 | 后端延续 `cmd/``internal/config/``internal/logic/``internal/models/``internal/routers/`。 | | 目录结构 | 后端延续 `cmd/``internal/config/``internal/logic/``internal/models/``internal/routers/`。 |
| 前端结构 | API 放入 `src/api/`,页面放入 `src/views/`,状态放入 Pinia。 | | 前端结构 | API 放入 `src/api/`,页面放入 `src/views/`,状态放入 Pinia。 |
| 模板引用 | 业务代码不直接写回 `templates/`。 | | 模板引用 | 业务代码不直接写回 `templates/`。 |
@@ -68,6 +68,8 @@ Set-Location .\server
go run .\cmd\cli\main.go migrate go run .\cmd\cli\main.go migrate
``` ```
后端重新初始化后,应只提交 `server/etc/*.example.yaml` 或等价 `.example` 配置文件。本地真实配置应复制为 `server/etc/ops_dev.yaml` 等文件后再填写,并在 `server/.gitignore` 中排除。
后端本地配置要求: 后端本地配置要求:
| 配置 | 要求 | | 配置 | 要求 |

View File

@@ -1,113 +0,0 @@
name: Actions
run-name: ${{ gitea.actor }} 🚀
on:
push:
tags:
- 'v*'
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- name: Clean temp directory
run: rm -rf *
# 设置 Go 环境
- name: 设置 Go
uses: https://git.apinb.com/github/setup-go@v5
with:
go-version: '1.24.6'
tag_name: ${{ gitea.ref }}
release_name: ${{ gitea.ref }}
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository_owner }}."
- name: Extract repository name
id: extract_repo
run: |
repo_name=$(echo ${{ gitea.repository }} | cut -d'/' -f2)
echo "Repository name: $repo_name"
echo "::set-output name=repo_name::$repo_name"
# 提取tag标签名称
- name: Extract tag name
id: extract_tag
run: echo "::set-output name=tag_name::$(echo ${{ gitea.ref }} | sed 's/^refs\/tags\///')"
- run: echo "🏷 The tag name is ${{ steps.extract_tag.outputs.tag_name }}."
# 检查出代码
- name: Check out repository code
uses: https://git.apinb.com/github/checkout@v4
# 缓存 Go 依赖项
- name: Cache Go modules
uses: https://git.apinb.com/github/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: set last version
run: |
echo "export srv_version='${{ steps.extract_tag.outputs.tag_name }}';export srv_name='${{ steps.extract_repo.outputs.repo_name }}'" > ${{ gitea.repository_owner }}-${{ steps.extract_repo.outputs.repo_name }}_last_version.sh
- run: go env -w GOPROXY=https://goproxy.cn
- run: go env -w GOPRIVATE=git.apinb.com/*
- run: go env -w GONOPROXY=git.apinb.com/*
- run: go env -w GOINSECURE=git.apinb.com/*
- run: go env -w GONOSUMDB=git.apinb.com/*
- run: go env -w GOEXPERIMENT=jsonv2
- run: go build -o ${{ gitea.repository_owner }}-${{ steps.extract_repo.outputs.repo_name }} ./cmd/main/main.go
- run: echo "🍏 This job's status is ${{ job.status }}."
- name: upload version.sh to minio oss - 1
uses: https://git.apinb.com/github/minio-upload@main
with:
endpoint: http://172.24.0.20:9000
access-key-id: 9jtPPB7wwJpe5R2164bS
access-key-secret: ho9LYavUIGdkf0b50aaOduIA4zdx8FQFWpUYx30p
bucket: reles
source: ./${{ gitea.repository_owner }}-${{ steps.extract_repo.outputs.repo_name }}_last_version.sh
insecure: true
recursive: true
- name: upload bin to minio oss - 2
uses: https://git.apinb.com/github/minio-upload@main
with:
endpoint: http://172.24.0.20:9000
access-key-id: 9jtPPB7wwJpe5R2164bS
access-key-secret: ho9LYavUIGdkf0b50aaOduIA4zdx8FQFWpUYx30p
bucket: reles
source: ./${{ gitea.repository_owner }}-${{ steps.extract_repo.outputs.repo_name }}
target: /${{ gitea.repository_owner }}-${{ steps.extract_repo.outputs.repo_name }}@${{ steps.extract_tag.outputs.tag_name }}/
insecure: true
recursive: false
- name: upload etc/ to minio oss - 3
uses: https://git.apinb.com/github/minio-upload@main
with:
endpoint: http://172.24.0.20:9000
access-key-id: 9jtPPB7wwJpe5R2164bS
access-key-secret: ho9LYavUIGdkf0b50aaOduIA4zdx8FQFWpUYx30p
bucket: reles
source: ./etc
target: /${{ gitea.repository_owner }}-${{ steps.extract_repo.outputs.repo_name }}@${{ steps.extract_tag.outputs.tag_name }}/
insecure: true
recursive: true
- name: upload swagger to minio oss - 4
uses: https://git.apinb.com/github/minio-upload@main
with:
endpoint: http://172.24.0.20:9000
access-key-id: 9jtPPB7wwJpe5R2164bS
access-key-secret: ho9LYavUIGdkf0b50aaOduIA4zdx8FQFWpUYx30p
bucket: reles
source: ./swagger
target: /${{ gitea.repository_owner }}-${{ steps.extract_repo.outputs.repo_name }}@${{ steps.extract_tag.outputs.tag_name }}/
insecure: true
recursive: true
- name: upload success
run: echo "build&upload success."

35
server/.gitignore vendored
View File

@@ -1,35 +0,0 @@
# ---> Go
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
builds/
build/
logs/
pkg/
cache/
tmp/
.builds/
.build/
.logs/
.pkg/
.cache/
.tmp/
# Go workspace file
go.work

View File

@@ -1,27 +0,0 @@
.PHONY: build run cli migrate lint tidy clean
BINARY_NAME=sample
CLI_NAME=sample-cli
build:
go build -o build/$(BINARY_NAME) ./cmd/main/main.go
go build -o build/$(CLI_NAME) ./cmd/cli/main.go
run:
go run ./cmd/main/main.go
cli:
go run ./cmd/cli/main.go $(ARGS)
migrate:
go run ./cmd/cli/main.go migrate
lint:
go vet ./...
go fmt ./...
tidy:
go mod tidy
clean:
rm -rf build/

View File

@@ -1,183 +0,0 @@
# Sample
基于 [BSM-SDK Core](https://git.apinb.com/bsm-sdk/core) 的 OPS 微服务样板项目,演示 Gin HTTP 服务、配置管理、数据库GORM、Redis 缓存、JWT 鉴权与标准分层架构,可作为新服务的起始模板。
## 功能概览
| 模块 | 说明 |
|------|------|
| 健康检查 | `HEAD /``GET /Sample/v1/ping/hello` |
| 示例 CRUD | `Sample/v1/sample/demo` 完整增删改查JWT 鉴权) |
| 数据模型 | `sample_demo` 表,启动时自动迁移 |
| CLI | `migrate` 数据库迁移、`version` 版本查看 |
## 项目结构
```
sample/
├── cmd/
│ ├── main/ # HTTP 服务入口
│ └── cli/ # 命令行工具
├── internal/
│ ├── config/ # 配置加载与校验
│ ├── impl/ # 基础设施实例DB、Redis、缓存、日志
│ ├── logic/ # 业务逻辑层
│ │ ├── ping/ # 健康检查
│ │ └── demo/ # 示例 CRUD
│ ├── models/ # 数据模型与数据库操作
│ └── routers/ # 路由注册
├── etc/ # 配置文件(按环境区分)
├── scripts/ # 构建与工具脚本
├── test/ # HTTP 接口测试文件
└── Makefile
```
## 环境要求
- Go 1.26+
- PostgreSQL或修改配置使用 MySQL
- Redis
- 可访问 `git.apinb.com` 私有仓库
## 快速开始
### 1. 配置 Go 私有仓库
```bash
go env -w GOPRIVATE=git.apinb.com/*
go env -w GONOPROXY=git.apinb.com/*
go env -w GOINSECURE=git.apinb.com/*
go env -w GONOSUMDB=git.apinb.com/*
```
### 2. 准备配置文件
配置文件命名规则:`{服务键}_{运行模式}.yaml`,本项目服务键为 `Sample`,开发模式对应 `etc/sample_dev.yaml`
```bash
cp etc/sample_dev.yaml.example etc/sample_dev.yaml
# 编辑 etc/sample_dev.yaml填入数据库与 Redis 连接信息
```
### 3. 设置环境变量
```bash
# Linux / macOS
export BSM_RuntimeMode=dev
export BSM_Prefix=$(pwd)/etc
export BSM_JwtSecretKey=your-secret-key
# Windows PowerShell
$env:BSM_RuntimeMode="dev"
$env:BSM_Prefix="$(pwd)/etc"
$env:BSM_JwtSecretKey="your-secret-key"
```
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `BSM_RuntimeMode` | 运行模式:`dev` / `test` / `prod` | `dev` |
| `BSM_Prefix` | 配置文件目录 | 开发模式为当前路径 |
| `BSM_JwtSecretKey` | JWT 签名密钥 | SDK 内置默认值 |
| `BSM_Workspace` | 工作空间标识 | `default` |
### 4. 安装依赖并启动
```bash
go mod tidy
make build # 或 go run ./cmd/main/main.go
make run
```
服务默认监听 `12426` 端口,启动成功后访问:
```bash
curl http://127.0.0.1:12426/Sample/v1/ping/hello
```
## API 接口
### 统一响应格式
```json
{
"code": 0,
"message": "",
"details": {},
"timeseq": 1710000000000
}
```
### 匿名接口
| 方法 | 路径 | 说明 |
|------|------|------|
| `HEAD` | `/` | 健康检查 |
| `GET` | `/Sample/v1/ping/hello` | Ping 测试 |
### 鉴权接口JWT
路由组:`/Sample/v1/sample`,请求头需携带 `Authorization: Bearer <token>`
| 方法 | 路径 | 说明 |
|------|------|------|
| `POST` | `/Sample/v1/demo` | 创建示例数据 |
| `GET` | `/Sample/v1/demo` | 分页列表(`page``size` |
| `GET` | `/Sample/v1/demo/:identity` | 按 identity 查询 |
| `PUT` | `/Sample/v1/demo/:identity` | 更新 |
| `DELETE` | `/Sample/v1/demo/:identity` | 删除 |
**创建请求示例:**
```json
{
"name": "Alice",
"age": 25
}
```
## CLI 工具
```bash
# 查看版本
go run ./cmd/cli/main.go version
# 执行数据库自动迁移(模型定义见 internal/models
go run ./cmd/cli/main.go migrate
```
也可使用 Makefile
```bash
make migrate
make cli ARGS="version"
```
## 开发与测试
```bash
make lint # go vet + go fmt
make tidy # go mod tidy
```
接口测试文件位于 `test/` 目录,可使用 VS Code REST Client 或 IntelliJ HTTP Client 直接运行。
## 生产部署
### 构建
```bash
./scripts/build.sh
# 产物build/sample、build/sample-cli
```
### systemd
```bash
cp etc/sample.service /etc/systemd/system/
cp etc/sample.env.example /data/app/etc/sample.env
# 编辑环境变量后
systemctl enable sample
systemctl start sample
```

View File

@@ -1,42 +0,0 @@
package main
import (
"fmt"
"os"
"git.apinb.com/ops/sample/internal/config"
"git.apinb.com/ops/sample/internal/impl"
_ "git.apinb.com/ops/sample/internal/models"
)
const (
serviceKey = "Sample"
version = "1.0.0"
)
func main() {
if len(os.Args) < 2 {
printUsage()
return
}
switch os.Args[1] {
case "version":
fmt.Println("sample-cli", version)
case "migrate":
config.New(serviceKey)
impl.NewImpl()
fmt.Println("database migrate completed")
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\n", os.Args[1])
printUsage()
os.Exit(1)
}
}
func printUsage() {
fmt.Println("usage: sample-cli <command>")
fmt.Println("commands:")
fmt.Println(" version show cli version")
fmt.Println(" migrate run database auto migrate")
}

View File

@@ -1,74 +0,0 @@
/**
* @Author: david.yan(david.yan@qq.com)
* @Date: 2021-11-26 15:25:03
*/
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/bsm-sdk/core/middleware"
"git.apinb.com/bsm-sdk/core/printer"
"git.apinb.com/ops/sample/internal/config"
"git.apinb.com/ops/sample/internal/impl"
"git.apinb.com/ops/sample/internal/routers"
"github.com/gin-gonic/gin"
)
var (
ServiceKey = "Sample"
)
func main() {
// 配置初始化
config.New(ServiceKey)
// 创建实现层
impl.NewImpl()
// 初始化Gin引擎
app := gin.Default()
// 使用中间件
middleware.Mode(app)
app.Use(middleware.Cors())
app.Use(gin.Recovery())
// 注册健康检查路由
app.HEAD("/", infra.Health)
// 注册应用路由
routers.Register(ServiceKey, app)
addr := fmt.Sprintf(":%s", config.Spec.Port)
srv := &http.Server{Addr: addr, Handler: app}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
panic(err)
}
}()
printer.Success("[BSM - %s] Service Started At: %s", ServiceKey, addr)
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
printer.Info("[BSM - %s] Shutting down...", ServiceKey)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
panic(err)
}
printer.Success("[BSM - %s] Service stopped", ServiceKey)
}

View File

@@ -1,5 +0,0 @@
# systemd EnvironmentFile 示例,复制为 /data/app/etc/sample.env 后按需修改
BSM_Workspace=default
BSM_RuntimeMode=prod
BSM_Prefix=/data/app
BSM_JwtSecretKey=change-me-to-a-random-string

View File

@@ -1,16 +0,0 @@
[Unit]
Description=Sample Service
[Service]
Type=simple
Restart=always
RestartSec=5s
EnvironmentFile=/data/app/etc/sample.env
ExecStart=/data/app/sample
WorkingDirectory=/data/app/
StandardOutput=file:/data/app/logs/sample.log
StandardError=file:/data/app/logs/sample.error.log
[Install]
WantedBy=multi-user.target

View File

@@ -1,27 +0,0 @@
Service: sample
Port: 12426
Databases:
Driver: postgres
Source:
- host=8.137.107.29 user=postgres password=Weidong2023~! dbname=sample_dev port=19432 sslmode=disable TimeZone=Asia/Shanghai
# cache DB的选择请在后面直接带参数不带会自动HASH计算选择DB库。
Cache: redis://null:Weidong2023~!@8.137.107.29:19379/0
# 是否启用微服务模式
OnMicroService: false
# 微服务调用密钥
SecretKey: 6ad9529688041483f458f8de11ed16ff
# Rpc:
# fts:
# Endpoint: https://api-v2.traingo.cn/fts/v2
# SecretKey: 4ef05311358cd1c8f787281f08b38b1c
# 链路追踪,性能监控,日志收集
# APM:
# Platform: elasticAPM
# Endpoint: http://127.0.0.1:14268/api/traces

View File

@@ -1,27 +0,0 @@
Service: sample
Port: 12426
Databases:
Driver: postgres
Source:
- host=8.137.107.29 user=postgres password=Weidong2023~! dbname=sample_prod port=19432 sslmode=disable TimeZone=Asia/Shanghai
# cache DB的选择请在后面直接带参数不带会自动HASH计算选择DB库。
Cache: redis://null:Weidong2023~!@8.137.107.29:19379/0
# 是否启用微服务模式
OnMicroService: false
# 微服务调用密钥
SecretKey: 6ad9529688041483f458f8de11ed16ff
# Rpc:
# fts:
# Endpoint: https://api-v2.traingo.cn/fts/v2
# SecretKey: 4ef05311358cd1c8f787281f08b38b1c
# 链路追踪,性能监控,日志收集
# APM:
# Platform: elasticAPM
# Endpoint: http://127.0.0.1:14268/api/traces

View File

@@ -1,27 +0,0 @@
Service: sample
Port: 12426
Databases:
Driver: postgres
Source:
- host=8.137.107.29 user=postgres password=Weidong2023~! dbname=sample_test port=19432 sslmode=disable TimeZone=Asia/Shanghai
# cache DB的选择请在后面直接带参数不带会自动HASH计算选择DB库。
Cache: redis://null:Weidong2023~!@8.137.107.29:19379/0
# 是否启用微服务模式
OnMicroService: false
# 微服务调用密钥
SecretKey: 6ad9529688041483f458f8de11ed16ff
# Rpc:
# fts:
# Endpoint: https://api-v2.traingo.cn/fts/v2
# SecretKey: 4ef05311358cd1c8f787281f08b38b1c
# 链路追踪,性能监控,日志收集
# APM:
# Platform: elasticAPM
# Endpoint: http://127.0.0.1:14268/api/traces

View File

@@ -1,80 +0,0 @@
module git.apinb.com/ops/sample
go 1.26.1
require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
gorm.io/driver/mysql v1.6.0 // indirect
)
require github.com/gin-gonic/gin v1.12.0
require github.com/patrickmn/go-cache v2.1.0+incompatible
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic v1.15.2 // indirect
github.com/bytedance/sonic/loader v0.5.1 // indirect
github.com/cloudwego/base64x v0.1.7 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/cors v1.7.7 // indirect
github.com/gin-contrib/sse v1.1.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.3 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.4.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.60.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.etcd.io/etcd/api/v3 v3.6.12 // indirect
go.mongodb.org/mongo-driver/v2 v2.7.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.28.0 // indirect
golang.org/x/arch v0.28.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260618152121-87f3d3e198d3 // indirect
)
require (
git.apinb.com/bsm-sdk/core v0.2.0
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/go-sql-driver/mysql v1.10.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.10.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/oklog/ulid/v2 v2.1.1 // indirect
github.com/redis/go-redis/v9 v9.20.1 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.12 // indirect
go.etcd.io/etcd/client/v3 v3.6.12 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/text v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260618152121-87f3d3e198d3 // indirect
google.golang.org/grpc v1.81.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/postgres v1.6.0 // indirect
gorm.io/gorm v1.31.1
)

View File

@@ -1,227 +0,0 @@
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
git.apinb.com/bsm-sdk/core v0.2.0 h1:/e9yqpsbKBrRgMiGpS3KX2O4qDLxo5V5GpPSLNGxEKw=
git.apinb.com/bsm-sdk/core v0.2.0/go.mod h1:E9T6Eboo/0Zb36BjkKbIgvFzq4fQ2Q8P/7y5zmYTI6Y=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
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/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
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.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
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/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q=
github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE=
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
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/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
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/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pelletier/go-toml/v2 v2.4.0 h1:Mwu0mAkUKbittDs3/ADDWXqMmq3EOK2VHiuCkV00Row=
github.com/pelletier/go-toml/v2 v2.4.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
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/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0=
github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk=
github.com/redis/go-redis/v9 v9.20.1 h1:sfCU6A8P3dXbKyWes02uxA2baehGux9dZHfEKtsTB1w=
github.com/redis/go-redis/v9 v9.20.1/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.etcd.io/etcd/api/v3 v3.6.12 h1:OLOZUKEuAA36TR48F0cIaa8FdzrWygjyfrJxXg4iDgs=
go.etcd.io/etcd/api/v3 v3.6.12/go.mod h1:p14EIQXHbuOQbVvL/WEes5uqKnxP9AgKJgpjbMVvzvE=
go.etcd.io/etcd/client/pkg/v3 v3.6.12 h1:36zzB+pQOdHbhN+kH2iJz/K8bJn0ZLtLfPPO7jozTDo=
go.etcd.io/etcd/client/pkg/v3 v3.6.12/go.mod h1:hh2+ZXtfLzs3o6mn92ntgNPBrTJJOvXqICM5g3L3DMY=
go.etcd.io/etcd/client/v3 v3.6.12 h1:kMSP6JcPZMqSJiX+TXdUIBU/4eXEZWBAaui4VihMbIc=
go.etcd.io/etcd/client/v3 v3.6.12/go.mod h1:CMs6fJWYiZQk4ytFjd4lE1diOvvRMmtbbn/alZXd3dQ=
go.mongodb.org/mongo-driver/v2 v2.7.0 h1:RO+zqavD2/GCL3cxOMyZhx6R9Irzr8/6gsoqx5tcY/c=
go.mongodb.org/mongo-driver/v2 v2.7.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.28.0 h1:wVwVdqsTuUbJvhYVCspQYwZXHNYeLSoZnmHD+ggddpQ=
golang.org/x/arch v0.28.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
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=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
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=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260618152121-87f3d3e198d3 h1:ctPmKL12ZsoKAlmPUsoW70zEDiYF+/H6aLieXxgAU0k=
google.golang.org/genproto/googleapis/api v0.0.0-20260618152121-87f3d3e198d3/go.mod h1:Z4WJ5pJOYWFWcHEQUelD5QaZDknIQkpIL/+fyJOT9+A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260618152121-87f3d3e198d3 h1:phvBWCAQMGN1945mp5fjCXP6jEF0+a0+4TjokS4sxNY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260618152121-87f3d3e198d3/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

View File

@@ -1,33 +0,0 @@
package config
import (
"net"
"git.apinb.com/bsm-sdk/core/conf"
)
var (
Spec SrvConfig
)
type SrvConfig struct {
conf.Base `yaml:",inline"`
Databases *conf.DBConf `yaml:"Databases"`
Rpc map[string]conf.RpcConf `yaml:"Rpc"`
Apm *conf.ApmConf `yaml:"APM"`
}
func New(srvKey string) {
// 初始化配置 创建一个新的配置实例,用于服务配置
conf.New(srvKey, &Spec)
// 配置校验 服务IP,端口; 端口如果不合规,则随机分配端口
Spec.Port = conf.CheckPort(Spec.Port)
Spec.BindIP = conf.CheckIP(Spec.BindIP)
Spec.Addr = net.JoinHostPort(Spec.BindIP, Spec.Port)
// 配置校验 服务名称地址及监听地址不能为空
conf.NotNil(Spec.Service, Spec.Cache)
conf.PrintInfo(Spec.Addr)
}

View File

@@ -1,29 +0,0 @@
package impl
import (
"git.apinb.com/bsm-sdk/core/cache/redis"
"git.apinb.com/bsm-sdk/core/logger"
"git.apinb.com/bsm-sdk/core/with"
"git.apinb.com/ops/sample/internal/config"
"github.com/patrickmn/go-cache"
"gorm.io/gorm"
)
var (
RedisService *redis.RedisClient // Redis 客户端服务
DBService *gorm.DB // 数据库服务
MemoryService *cache.Cache // 内存缓存服务
Logger *logger.Logger
)
// NewImpl 初始化各类服务实例
func NewImpl() {
// 初始化内存缓存
MemoryService = with.Memory(nil)
// 初始化 Redis 缓存
RedisService = with.RedisCache(config.Spec.Cache)
// 初始化数据库服务
DBService = with.Databases(config.Spec.Databases, nil)
// 初始化 日志服务
logger.New(nil)
}

View File

@@ -1,125 +0,0 @@
package demo
import (
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/bsm-sdk/core/utils"
"git.apinb.com/ops/sample/internal/models"
"github.com/gin-gonic/gin"
)
type CreateRequest struct {
Name string `json:"name" binding:"required,max=255"`
Age int `json:"age"`
}
type UpdateRequest struct {
Name string `json:"name" binding:"required,max=255"`
Age int `json:"age"`
}
type ListReply struct {
Total int64 `json:"total"`
List []models.SampleDemo `json:"list"`
}
func Create(ctx *gin.Context) {
var req CreateRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
data := models.SampleDemo{
Name: req.Name,
Age: req.Age,
}
data.Identity = utils.UUID()
data.Status = 1
if err := models.CreateSampleDemo(&data); err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, data)
}
func List(ctx *gin.Context) {
page := utils.String2Int(ctx.DefaultQuery("page", "1"))
size := utils.String2Int(ctx.DefaultQuery("size", "20"))
if page < 1 {
page = 1
}
if size < 1 || size > 100 {
size = 20
}
list, total, err := models.ListSampleDemos(page, size)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, ListReply{Total: total, List: list})
}
func Get(ctx *gin.Context) {
identity := ctx.Param("identity")
if identity == "" {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
data, err := models.GetSampleDemoByField("identity", identity)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, data)
}
func Update(ctx *gin.Context) {
identity := ctx.Param("identity")
if identity == "" {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
var req UpdateRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
data, err := models.GetSampleDemoByField("identity", identity)
if err != nil {
infra.Response.Error(ctx, err)
return
}
data.Name = req.Name
data.Age = req.Age
if err := models.UpdateSampleDemo(data); err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, data)
}
func Delete(ctx *gin.Context) {
identity := ctx.Param("identity")
if identity == "" {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
data, err := models.GetSampleDemoByField("identity", identity)
if err != nil {
infra.Response.Error(ctx, err)
return
}
if err := models.DeleteSampleDemo(data); err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, identity)
}

View File

@@ -1,10 +0,0 @@
package ping
import (
"git.apinb.com/bsm-sdk/core/infra"
"github.com/gin-gonic/gin"
)
func Hello(ctx *gin.Context) {
infra.Response.Success(ctx, "OK")
}

View File

@@ -1,6 +0,0 @@
/**
* @Author: David Yan(david.yan@qq.com)
* @Description: 自定义查询方法
*/
package models

View File

@@ -1,86 +0,0 @@
package models
import (
"errors"
"git.apinb.com/bsm-sdk/core/database"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/types"
"git.apinb.com/ops/sample/internal/impl"
"gorm.io/gorm"
)
/*
* SampleDemo
* Comment: 示例数据表
* Version: 10
* Created: 2022-04-12 10:44:51 , Updated:0001-01-01 00:00:00
*/
type SampleDemo struct {
types.Std_IICUDS
Name string `gorm:"column:name;type:varchar(255);default:'';" json:"name"` // 名称
Age int `gorm:"column:age;type:int;default:0;" json:"age"` // 年龄
}
func init() {
database.AppendMigrate(&SampleDemo{})
}
// TableName .
func (table *SampleDemo) TableName() string {
return "sample_demo" //对应数据库表名
}
// GetSampleDemoByField 根据特定字段值获取 SampleDemo 对象
func GetSampleDemoByField(field string, value any) (*SampleDemo, error) {
var (
data SampleDemo
condition = map[string]any{field: value}
)
err := impl.DBService.Where(condition).First(&data).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errcode.ErrRecordNotFound
}
return nil, errcode.ErrDB
}
return &data, nil
}
func CreateSampleDemo(data *SampleDemo) error {
if err := impl.DBService.Create(data).Error; err != nil {
return errcode.ErrDB
}
return nil
}
func ListSampleDemos(page, size int) ([]SampleDemo, int64, error) {
var (
list []SampleDemo
total int64
)
db := impl.DBService.Model(&SampleDemo{})
if err := db.Count(&total).Error; err != nil {
return nil, 0, errcode.ErrDB
}
offset := (page - 1) * size
if err := db.Order("id desc").Offset(offset).Limit(size).Find(&list).Error; err != nil {
return nil, 0, errcode.ErrDB
}
return list, total, nil
}
func UpdateSampleDemo(data *SampleDemo) error {
if err := impl.DBService.Save(data).Error; err != nil {
return errcode.ErrDB
}
return nil
}
func DeleteSampleDemo(data *SampleDemo) error {
if err := impl.DBService.Delete(data).Error; err != nil {
return errcode.ErrDB
}
return nil
}

View File

@@ -1,36 +0,0 @@
package routers
import (
"fmt"
"git.apinb.com/bsm-sdk/core/middleware"
"git.apinb.com/ops/sample/internal/logic/demo"
"git.apinb.com/ops/sample/internal/logic/ping"
"github.com/gin-gonic/gin"
)
// Register 注册路由,请求地址格式: ip:port/{srvKey}/v1/{group}/{action}
func Register(srvKey string, engine *gin.Engine) {
v1Key := fmt.Sprintf("/%s/%s", srvKey, "v1")
registerAnonymous(v1Key, engine)
registerRouters(v1Key, engine)
}
func registerAnonymous(v1Key string, engine *gin.Engine) {
anonymous := engine.Group(v1Key)
{
anonymous.GET("/ping/hello", ping.Hello)
}
}
func registerRouters(v1Key string, engine *gin.Engine) {
demoGroup := engine.Group(fmt.Sprintf("/%s/%s", v1Key, "demo"))
demoGroup.Use(middleware.JwtAuth(true))
{
demoGroup.POST("", demo.Create)
demoGroup.GET("", demo.List)
demoGroup.GET("/:identity", demo.Get)
demoGroup.PUT("/:identity", demo.Update)
demoGroup.DELETE("/:identity", demo.Delete)
}
}

View File

@@ -1,9 +0,0 @@
#!/bin/bash
set -euo pipefail
mkdir -p build
GOARCH=amd64 GOOS=linux go build -o ./build/sample ./cmd/main/main.go
GOARCH=amd64 GOOS=linux go build -o ./build/sample-cli ./cmd/cli/main.go
echo "build completed: ./build/sample, ./build/sample-cli"

View File

@@ -1,5 +0,0 @@
#!/bin/bash
set -euo pipefail
go vet ./...
go fmt ./...

View File

@@ -1,7 +0,0 @@
git pull
go get all
go get -u ./...
go mod tidy
git add .
git commit -m 'run ./scripts/update.sh'
git push

View File

@@ -1,31 +0,0 @@
### 创建示例数据(需要 JWT
POST http://127.0.0.1:12426/Sample/v1/sample/demo
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Alice",
"age": 25
}
### 列表查询
GET http://127.0.0.1:12426/Sample/v1/sample/demo?page=1&size=20
Authorization: Bearer {{token}}
### 详情查询
GET http://127.0.0.1:12426/Sample/v1/sample/demo/{{identity}}
Authorization: Bearer {{token}}
### 更新
PUT http://127.0.0.1:12426/Sample/v1/sample/demo/{{identity}}
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Alice Updated",
"age": 26
}
### 删除
DELETE http://127.0.0.1:12426/Sample/v1/sample/demo/{{identity}}
Authorization: Bearer {{token}}

View File

@@ -1,2 +0,0 @@
GET http://127.0.0.1:12426/Sample/v1/ping/hello
Content-Type: application/json

View File

@@ -1,2 +0,0 @@
# HTTP 接口测试文件,使用 VS Code REST Client 或 IntelliJ HTTP Client 运行。
# 将 {{token}}、{{identity}} 替换为实际值。