commit a4fe393571f0dc71afe3f6d2f5edf53df656443a Author: sunct Date: Fri Jul 31 14:44:48 2026 +0800 首次提交:初始化项目代码 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..75ad6ac --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git/ +data/ +logs/ +*.log +.vscode/ +.DS_Store \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab926c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Binaries +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/ +dist/ + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out +coverage.txt +coverage.html + +# Go workspace file +go.work + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Project data +data/ +logs/ +*.log +*.db +*.sqlite +*.sqlite3 + +# Environment +.env +.env.local +.env.*.local + +# Uploads +uploads/ +static/uploads/ diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..0c54fb9 --- /dev/null +++ b/.htaccess @@ -0,0 +1 @@ +# 请将伪静态规则或自定义Apache配置填写到此处 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..334f7fa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +FROM golang:1.21-alpine AS builder + +WORKDIR /src + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build \ + -ldflags="-s -w -X main.version=$(date +%Y%m%d%H%M%S)" \ + -o /out/resume \ + cmd/server/main.go + +FROM alpine:3.19 + +RUN apk --no-cache add ca-certificates tzdata && \ + addgroup -S app && adduser -S app -G app + +ENV TZ=Asia/Shanghai \ + GIN_MODE=release + +WORKDIR /app + +COPY --from=builder --chown=app:app /out/resume ./resume +COPY --from=builder --chown=app:app /src/templates ./templates +COPY --from=builder --chown=app:app /src/config ./config +COPY --from=builder --chown=app:app /src/static ./static + +RUN mkdir -p /app/data /app/logs && \ + chown -R app:app /app + +USER app + +EXPOSE 8090 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8090/ || exit 1 + +CMD ["./resume"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..f2e4e6b --- /dev/null +++ b/README.md @@ -0,0 +1,370 @@ +# Resume Platform · 智能简历平台 + +[![Go Version](https://img.shields.io/badge/Go-1.25+-00ADD8?style=flat-square&logo=go)](https://go.dev/) +[![Gin](https://img.shields.io/badge/Gin-1.9.1-00ADD8?style=flat-square)](https://gin-gonic.com/) +[![SQLite](https://img.shields.io/badge/SQLite-3.x-003B57?style=flat-square&logo=sqlite)](https://www.sqlite.org/) +[![License](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](LICENSE) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](#贡献指南) + +> 基于 Go + Gin + SQLite 的现代化开源简历平台,内置 AI 面试助手、多模板支持、多人简历管理,让你的个人主页脱颖而出。 + +--- + +## ✨ 功能特性 + +### 📄 简历管理 +- **多人简历支持** — 每个人独立访问路由(如 `/zhangsan`、`/lisi`),支持人员绑定与解绑 +- **多模板主题** — 内置 7 种精美模板(modern / classic / fresh / minimalist / professional / tech / project) +- **实时编辑** — 所见即所得的编辑体验,自动保存防止丢失 +- **访问密码** — 支持为简历设置独立访问密码,保护隐私 +- **数据脱敏** — 公开展示时自动脱敏手机号、邮箱等敏感信息 + +### 🤖 AI 智能面试 +- **智能出题** — 基于简历内容或关键词自动生成面试题,支持选择题、填空题、简答题、算法题 +- **多 AI 提供商** — 接入 DeepSeek / 豆包 / 讯飞星火 / 通义千问,自由切换 +- **关键词驱动** — 智能提取简历技能作为关键词,支持别名扩展,每次生成随机不重复 +- **答题记录** — 完整记录每次答题情况,支持查看历史、重练错题 +- **收藏 & 错题本** — 一键收藏好题,错题自动入库,针对性复习 + +### 🎨 作品集展示 +- **独立作品集页** — 每个用户拥有专属作品集展示页面 +- **项目关联** — 支持从简历项目中导入,也可独立创建 +- **分类筛选** — 按技术栈、类型快速筛选项目 +- **首页精选** — 可设置首页展示的精选项目 + +### 🛡️ 安全保障 +- **后台隐藏路径** — 自定义管理后台 URL,降低被探测风险 +- **登录验证码** — 图形验证码防止机器人暴力破解 +- **登录限流** — 基于 IP 的登录次数限制,超出自动锁定 +- **AES 加密** — 数据库支持 AES-GCM 加密存储,敏感数据更安全 +- **PBKDF2 密钥派生** — 使用 PBKDF2-SHA256 派生加密密钥 + +### 📊 管理后台 +- **仪表盘** — 数据总览,快速了解平台运行状态 +- **用户管理** — 完整的用户 CRUD,支持状态管理 +- **简历管理** — 统一管理所有简历,支持绑定/解绑人员 +- **答题管理** — 查看用户答题记录、学习进度 +- **菜单管理** — 自定义后台导航菜单 +- **系统设置** — 站点名称、Logo、SEO 等配置可视化管理 +- **全局搜索** — 一键搜索用户、简历、作品集 + +--- + +## 🏗️ 技术栈 + +| 类别 | 技术 | 版本 | +|------|------|------| +| 语言 | Go | 1.25+ | +| Web 框架 | Gin | v1.9.1 | +| 数据库 | SQLite (modernc.org/sqlite) | v1.26.0 | +| AI Agent | CloudWeGo Eino | v0.9.12 | +| 配置管理 | Viper | v1.18.2 | +| 验证码 | base64Captcha | v1.3.2 | +| 前端 | HTML Template + Tailwind CSS + Font Awesome | — | +| 部署 | Docker / Docker Compose | — | + +--- + +## 📁 项目结构 + +``` +resume-platform/ +├── cmd/ +│ ├── server/ # 主服务入口 +│ │ └── main.go +│ └── checkdb/ # 数据库检查工具 +│ └── main.go +├── config/ +│ └── config.yaml # 应用配置文件 +├── internal/ +│ ├── agent/ # AI Agent 层(Eino) +│ │ ├── config.go +│ │ └── tools.go # 题目生成、答案解析等工具 +│ ├── ai/ # AI 提供商适配层 +│ │ ├── provider.go # 统一接口定义 +│ │ ├── deepseek.go +│ │ ├── doubao.go +│ │ ├── spark.go +│ │ └── tongyi.go +│ ├── handler/ # HTTP 处理器 +│ │ ├── admin.go # 管理后台 +│ │ ├── ai.go # AI 相关接口 +│ │ ├── chat.go # AI 对话接口 +│ │ ├── document.go # 文档解析接口 +│ │ ├── response.go # 统一响应格式 +│ │ └── resume.go # 简历/作品集/答题 API +│ ├── model/ # 数据模型 +│ │ ├── resume.go # 简历、用户、题目、作品集等 +│ │ └── document.go +│ ├── parser/ # 简历解析 +│ │ └── parser.go +│ ├── repository/ # 数据访问层 +│ │ ├── interface.go # 仓库接口定义 +│ │ └── sqlite.go # SQLite 实现 +│ ├── router/ # 路由配置 +│ │ ├── router.go # 路由总入口 +│ │ ├── admin.go # 管理后台路由 +│ │ ├── api.go # REST API 路由 +│ │ └── frontend.go # 前端页面路由 +│ ├── security/ # 安全模块 +│ │ ├── captcha.go # 验证码 +│ │ └── rate_limiter.go # 登录限流 +│ └── service/ # 业务逻辑层 +│ ├── ai_service.go # AI 服务 +│ ├── chat.go # 对话服务 +│ ├── document.go # 文档服务 +│ ├── resume.go # 简历服务 +│ └── vector.go # 向量服务 +├── pkg/ +│ └── config/ # 公共配置包 +│ └── config.go +├── static/ # 静态资源 +│ ├── images/ +│ └── js/ +├── templates/ # HTML 模板 +│ ├── admin/ # 管理后台模板 +│ └── web/ # 前端展示模板 +├── data/ # 数据库文件(运行时生成) +├── logs/ # 日志文件(运行时生成) +├── Dockerfile +├── docker-compose.yml +├── go.mod +└── go.sum +``` + +--- + +## 🚀 快速开始 + +### 环境要求 + +- **Go 1.25+**(推荐使用最新稳定版) +- 或 **Docker** / **Docker Compose** + +### 本地运行 + +```bash +# 1. 克隆项目 +git clone https://github.com/yourusername/resume-platform.git +cd resume-platform + +# 2. 安装依赖 +go mod download + +# 3. 运行服务 +go run cmd/server/main.go +``` + +服务启动后: + +| 地址 | 说明 | +|------|------| +| http://localhost:8090 | 产品首页 | +| http://localhost:8090/default | 默认简历示例 | +| http://localhost:8090/my-secret-admin-123 | 管理后台(默认路径) | + +> **默认账号**:`admin` / `admin123456` +> +> ⚠️ 生产环境请务必修改默认密码和后台路径! + +### Docker 部署 + +#### 使用 Docker Compose(推荐) + +```bash +docker-compose up -d +``` + +#### 手动构建 + +```bash +docker build -t resume-platform . +docker run -d \ + -p 8090:8090 \ + -v $(pwd)/data:/app/data \ + -v $(pwd)/logs:/app/logs \ + --name resume-platform \ + resume-platform +``` + +--- + +## ⚙️ 配置说明 + +配置文件位于 `config/config.yaml`,所有配置项均支持通过环境变量覆盖。 + +```yaml +server: + host: "0.0.0.0" # 监听地址 + port: 8090 # 监听端口 + mode: "release" # Gin 模式:debug / release + +database: + type: "sqlite" # 数据库类型(目前仅支持 SQLite) + path: "./data/resume.db" # 数据库文件路径 + password: "resume@2024" # 数据库加密密码 + +auth: + enabled: true # 是否启用后台认证 + username: "admin" # 管理员用户名 + password: "admin123456" # 管理员密码 + admin_path: "/my-secret-admin-123" # 后台路径(建议修改) + max_login_attempts: 5 # 最大登录尝试次数 + lockout_duration: 15 # 锁定时长(分钟) + +template: + default: "modern" # 默认简历模板 + +log: + level: "info" # 日志级别 + output: "./logs/app.log" # 日志输出路径 + +ai: + provider: "spark" # AI 提供商:spark / tongyi / doubao / deepseek + enabled: true # 是否启用 AI 功能 + spark: # 讯飞星火配置 + api_password: "" + base_url: "" + model: "lite" + max_tokens: 1024 + timeout: 30 + # 其他 AI 提供商配置... +``` + +--- + +## 🔌 API 概览 + +### 简历接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `GET` | `/api/resume/:id` | 获取单份简历 | +| `GET` | `/api/resume/by-route/:route` | 按路由获取简历 | +| `POST` | `/api/resume` | 创建简历 | +| `PUT` | `/api/resume/:id` | 更新简历 | +| `DELETE` | `/api/resume/:id` | 删除简历 | +| `GET` | `/api/user/resumes` | 获取指定用户的简历列表 | + +### AI 答题接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `POST` | `/api/ai/agent/generate-questions` | 生成面试题 | +| `POST` | `/api/ai/agent/submit-answers` | 提交答案并评分 | +| `POST` | `/api/ai/agent/evaluate-answers` | 评估答案 | + +### 收藏 & 错题 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `POST` | `/api/favorite` | 添加收藏/错题 | +| `GET` | `/api/favorite` | 获取收藏/错题列表 | +| `DELETE` | `/api/favorite/:id` | 删除收藏/错题 | +| `PUT` | `/api/favorite/:id/toggle` | 切换收藏状态 | + +### 作品集接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| `GET` | `/api/portfolio` | 获取作品集列表 | +| `POST` | `/api/portfolio` | 创建作品集项目 | +| `GET` | `/api/portfolio/:id` | 获取单个项目 | +| `PUT` | `/api/portfolio/:id` | 更新项目 | +| `DELETE` | `/api/portfolio/:id` | 删除项目 | + +### 管理后台 + +所有管理接口前缀为 `{admin_path}/`(默认 `/my-secret-admin-123/`)。 + +--- + +## 🧩 模板系统 + +内置 7 种精美模板,支持随时切换: + +| 模板 | 风格 | 适用场景 | +|------|------|----------| +| `modern` | 现代简约 | 通用型,推荐首选 | +| `classic` | 经典商务 | 传统行业、资深岗位 | +| `fresh` | 清新活力 | 应届生、设计岗 | +| `minimalist` | 极简主义 | 追求简洁表达 | +| `professional` | 专业职业 | 管理岗、专业岗 | +| `tech` | 技术导向 | 程序员、工程师 | +| `project` | 项目展示 | 突出项目经验 | + +> 模板文件位于 `templates/web/`,可基于现有模板自定义新主题。 + +--- + +## 🛠️ 开发指南 + +### 添加新模板 + +1. 在 `templates/web/` 目录下创建 `your-template.html` +2. 继承 `base.html` 的结构,使用 Go 模板语法访问 `.Resume` 数据 +3. 在 `resume.Template` 字段设置模板名称即可 + +### 添加新的 AI 提供商 + +1. 在 `internal/ai/` 下创建 `yourprovider.go` +2. 实现 `AIProvider` 接口 +3. 在 `provider.go` 的 `NewProvider` 工厂中注册 + +### 数据库迁移 + +项目使用 `CREATE TABLE IF NOT EXISTS` + `migrateSchema` 机制自动处理表结构变更。新增列时在 `migrateSchema` 函数中添加对应 `ALTER TABLE ADD COLUMN` 语句即可,程序启动时自动执行。 + +### 代码规范 + +```bash +# 格式化代码 +gofmt -w . + +# 静态检查 +go vet ./... + +# 构建检查 +go build ./... +``` + +--- + +## 🤝 贡献指南 + +欢迎提交 Issue 和 Pull Request! + +### 贡献流程 + +1. **Fork** 本仓库 +2. 创建特性分支:`git checkout -b feature/AmazingFeature` +3. 提交更改:`git commit -m 'Add some AmazingFeature'` +4. 推送到分支:`git push origin feature/AmazingFeature` +5. 打开 **Pull Request** + +### 提交规范 + +- 使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范 +- 示例:`feat: add dark mode support`、`fix: correct user binding display` + +--- + +## 📜 开源协议 + +本项目基于 **MIT License** 开源,详见 [LICENSE](LICENSE) 文件。 + +--- + +## ⭐ 支持项目 + +如果这个项目对你有帮助,欢迎: + +- 点个 Star ⭐️ 让更多人看到 +- 分享给身边需要的朋友 +- 提交 Issue / PR 一起完善 + +--- + +
+

Made with ❤️ by Resume Platform Team

+
diff --git a/add_author.go b/add_author.go new file mode 100644 index 0000000..8e497df --- /dev/null +++ b/add_author.go @@ -0,0 +1,96 @@ +package main + +import ( + "fmt" + "os" + "regexp" + "strings" +) + +func addAuthorToFile(filePath string) error { + content, err := os.ReadFile(filePath) + if err != nil { + return err + } + + lines := strings.Split(string(content), "\n") + var result []string + var commentBuffer []string + inComment := false + + funcPattern := regexp.MustCompile(`^func\s+(\([^)]+\)\s+)?\w+\s*\(`) + + for i := 0; i < len(lines); i++ { + line := lines[i] + trimmed := strings.TrimSpace(line) + + if strings.HasPrefix(trimmed, "//") && !strings.HasPrefix(trimmed, "//go:") { + if strings.Contains(trimmed, "@author") { + for _, c := range commentBuffer { + result = append(result, c) + } + commentBuffer = nil + result = append(result, line) + inComment = false + continue + } + commentBuffer = append(commentBuffer, line) + inComment = true + continue + } + + if inComment && len(commentBuffer) > 0 && funcPattern.MatchString(trimmed) { + for _, c := range commentBuffer { + result = append(result, c) + } + indent := "" + for _, ch := range line { + if ch == ' ' || ch == '\t' { + indent += string(ch) + } else { + break + } + } + result = append(result, indent+"// @author sunct") + result = append(result, line) + commentBuffer = nil + inComment = false + continue + } + + if len(commentBuffer) > 0 { + for _, c := range commentBuffer { + result = append(result, c) + } + commentBuffer = nil + } + inComment = false + result = append(result, line) + } + + if len(commentBuffer) > 0 { + for _, c := range commentBuffer { + result = append(result, c) + } + } + + output := strings.Join(result, "\n") + return os.WriteFile(filePath, []byte(output), 0644) +} + +func main() { + if len(os.Args) < 2 { + fmt.Println("Usage: go run add_author.go [file2.go ...]") + os.Exit(1) + } + + for _, file := range os.Args[1:] { + fmt.Printf("Processing: %s ... ", file) + err := addAuthorToFile(file) + if err != nil { + fmt.Printf("ERROR: %v\n", err) + } else { + fmt.Println("OK") + } + } +} diff --git a/cmd/checkdb/main.go b/cmd/checkdb/main.go new file mode 100644 index 0000000..5eacc5c --- /dev/null +++ b/cmd/checkdb/main.go @@ -0,0 +1,74 @@ +// Package main 数据库检查工具,用于调试查看 system_config 表结构和数据 +package main + +import ( + "database/sql" + "fmt" + "log" + + _ "modernc.org/sqlite" +) + +// main 数据库检查程序入口,打印 system_config 表结构和记录 +// @author sunct +func main() { + db, err := sql.Open("sqlite", "./data/resume.db") + if err != nil { + log.Fatalf("Failed to open database: %v", err) + } + defer db.Close() + + fmt.Println("=== system_config Table ===") + rows, err := db.Query("PRAGMA table_info(system_config)") + if err != nil { + log.Fatalf("Failed to get table info: %v", err) + } + defer rows.Close() + + fmt.Println("Table Structure:") + for rows.Next() { + var cid int + var name, colType string + var notNull, pk int + var dfltValue interface{} + err := rows.Scan(&cid, &name, &colType, ¬Null, &dfltValue, &pk) + if err != nil { + log.Printf("Failed to scan column: %v", err) + continue + } + fmt.Printf(" %d: %s (%s), notNull: %d, pk: %d, default: %v\n", cid, name, colType, notNull, pk, dfltValue) + } + + var count int + err = db.QueryRow("SELECT COUNT(*) FROM system_config").Scan(&count) + if err != nil { + log.Fatalf("Failed to count records: %v", err) + } + fmt.Printf("\nTotal records in system_config: %d\n", count) + + rows2, err := db.Query("SELECT * FROM system_config") + if err != nil { + log.Fatalf("Failed to query records: %v", err) + } + defer rows2.Close() + + fmt.Println("\nSystem Config Records:") + for rows2.Next() { + var id, websiteDomain, websiteLogo, logoPath, websiteTitle, websiteDesc, websiteDescription, websiteKeywords, seoKeywords, seoDescription, adminEmail, adminName, databasePath, createdAt, updatedAt string + var adminPageSize int + err := rows2.Scan(&id, &websiteDomain, &websiteLogo, &logoPath, &websiteTitle, &websiteDesc, &websiteDescription, &websiteKeywords, &seoKeywords, &seoDescription, &adminEmail, &adminName, &databasePath, &adminPageSize, &createdAt, &updatedAt) + if err != nil { + log.Printf("Failed to scan record: %v", err) + continue + } + fmt.Printf("ID: %s\n", id) + fmt.Printf(" WebsiteDomain: %s\n", websiteDomain) + fmt.Printf(" WebsiteTitle: %s\n", websiteTitle) + fmt.Printf(" LogoPath: %s\n", logoPath) + fmt.Printf(" AdminPageSize: %d\n", adminPageSize) + } + + if err := rows2.Err(); err != nil { + log.Printf("Rows error: %v", err) + } +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..a322a79 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,31 @@ +// Package main 是简历平台服务的入口程序 +// 负责调用 bootstrap 启动服务 +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + + "resume-platform/internal/bootstrap" +) + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + go func() { + <-sigChan + cancel() + }() + + if err := bootstrap.Run(ctx); err != nil { + fmt.Printf("Server failed to start: %v\n", err) + os.Exit(1) + } +} \ No newline at end of file diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..bd8b495 --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,60 @@ +server: + host: "0.0.0.0" + port: 8090 + mode: "release" + debug: true + +database: + type: "sqlite" + path: "./data/resume.db" + password: "resume@2024" + +auth: + enabled: true + username: "admin" + password: "admin123456" + admin_path: "/my-secret-admin-123" + max_login_attempts: 5 + lockout_duration: 15 + +template: + default: "modern" + +log: + level: "info" + output: "./logs/app.log" + +ai: + provider: "spark" + enabled: true + spark: + app_id: "a146c363" + api_key: "1ba519d8cfb20f2732424f2a5ea8e630" + api_secret: "OTAwZGI3YjFjZjM2NmZmMzE0YjQxZWM2" + api_password: "OjAdJyOqCVmVYUFyaQJq:GQZNWpiVJTytmiSWuaTq" + base_url: "wss://spark-api.xf-yun.com/v1.1/chat" + model: "lite" + max_tokens: 10000 + timeout: 30 + web_search: false + show_ref_label: false + + + tongyi: + api_key: "" + base_url: "https://dashscope.aliyuncs.com/api/v1" + model: "qwen-turbo" + max_tokens: 10000 + timeout: 30 + doubao: + api_key: "" + base_url: "https://api.doubao.com/v1" + model: "Doubao" + max_tokens: 10000 + timeout: 30 + deepseek: + api_key: "" + base_url: "https://api.deepseek.com/v1" + model: "deepseek-chat" + max_tokens: 10000 + timeout: 30 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..552108e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: '3.8' + +services: + resume: + build: . + container_name: resume-platform + restart: unless-stopped + ports: + - "8090:8090" + volumes: + - ./data:/app/data + - ./logs:/app/logs + - ./config:/app/config:ro + - ./static:/app/static:ro + environment: + - TZ=Asia/Shanghai + - GIN_MODE=release + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8090/"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 5s + networks: + - resume-network + +networks: + resume-network: + driver: bridge diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1fadf72 --- /dev/null +++ b/go.mod @@ -0,0 +1,92 @@ +module resume-platform + +go 1.25 + +require ( + github.com/cloudwego/eino v0.9.12 + github.com/cloudwego/eino-ext/components/model/openai v0.1.13 + github.com/gin-gonic/gin v1.9.1 + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.3 + github.com/mojocn/base64Captcha v1.3.2 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/viper v1.18.2 +) + +require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/eino-contrib/jsonschema v1.0.3 // indirect + github.com/evanphx/json-patch v0.5.2 // indirect + github.com/goph/emperror v0.17.2 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/meguminnnnnnnnn/go-openai v0.1.2 // indirect + github.com/nikolalohinski/gonja v1.5.3 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/yargevad/filepathx v1.0.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.24.1 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.6.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect +) + +require ( + github.com/bytedance/sonic v1.15.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // 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.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // 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.1.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.11.0 // indirect + golang.org/x/crypto v0.31.0 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/sqlite v1.26.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b22fa2e --- /dev/null +++ b/go.sum @@ -0,0 +1,274 @@ +github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/mockey v1.3.0 h1:ONLRdvhqmCfr9rTasUB8ZKCfvbdD2tohOg4u+4Q/ed0= +github.com/bytedance/mockey v1.3.0/go.mod h1:1BPHF9sol5R1ud/+0VEHGQq/+i2lN+GTsr3O2Q9IENY= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/cloudwego/eino v0.9.12 h1:mHAMo5k7GdvnVD8Lc2sLyfpkxEm0S/y3PkEMhsSYt78= +github.com/cloudwego/eino v0.9.12/go.mod h1:OBD1mrkfkt/pJa4rkg1P0VnaMeOVl7l8IAdEqY//3IQ= +github.com/cloudwego/eino-ext/components/model/openai v0.1.13 h1:5XHRTiTD5bt9KQrMHcfvuWNklEC3tpm3XHejdozt9vM= +github.com/cloudwego/eino-ext/components/model/openai v0.1.13/go.mod h1:mgIoqYYOc0eECCqvLbEYpOJrQNTNxkwXzSJzFU+v5sQ= +github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17 h1:EeVcR1TslRA2IdNW1h/2LaGbPlffwGhQm99jM3zWZiI= +github.com/cloudwego/eino-ext/libs/acl/openai v0.1.17/go.mod h1:Zkcx6DPTR2NfWmtSXbhItswGw6hqUezNPhNcke0pOG8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eino-contrib/jsonschema v1.0.3 h1:2Kfsm1xlMV0ssY2nuxshS4AwbLFuqmPmzIjLVJ1Fsp0= +github.com/eino-contrib/jsonschema v1.0.3/go.mod h1:cpnX4SyKjWjGC7iN2EbhxaTdLqGjCi0e9DxpLYxddD4= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +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.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +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/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18= +github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +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/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +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= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +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/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/meguminnnnnnnnn/go-openai v0.1.2 h1:iXombGGjqjBrmE9WaSidUhhi3YQhf42QTHvHLMkgvCA= +github.com/meguminnnnnnnnn/go-openai v0.1.2/go.mod h1:qs96ysDmxhE4BZoU45I43zcyfnaYxU3X+aRzLko/htY= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/mojocn/base64Captcha v1.3.2 h1:Ouwjb2jFriltNdKZ1Ud1uu26Dv4+YVsoIw+u4navOmg= +github.com/mojocn/base64Captcha v1.3.2/go.mod h1:wAQCKEc5bDujxKRmbT6/vTnTt5CjStQ8bRfPWUuz/iY= +github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c= +github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f h1:Z2cODYsUxQPofhpYRMQVwWz4yUVpHF+vPi+eUdruUYI= +github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f/go.mod h1:JqzWyvTuI2X4+9wOHmKSQCYxybB/8j6Ko43qVmXDuZg= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +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.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg= +github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= +github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= +golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ= +golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= +modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o= +modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw= +modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= +modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= +modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= diff --git a/internal/agent/config.go b/internal/agent/config.go new file mode 100644 index 0000000..14554fc --- /dev/null +++ b/internal/agent/config.go @@ -0,0 +1,239 @@ +// Package agent AI Agent 模块,基于 Eino 框架实现简历优化智能体 +package agent + +import ( + "context" + "resume-platform/pkg/config" + + "github.com/cloudwego/eino/adk" + "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino-ext/components/model/openai" +) + +// ResumeAgentConfig 简历 Agent 配置 +type ResumeAgentConfig struct { + ChatModel *openai.ChatModel // 聊天模型实例 + Provider string // AI 供应商名称 + BaseURL string // 接口地址 + APIKey string // API Key + Model string // 模型名称 + MaxTokens int // 最大 token 数 + Temperature float64 // 温度参数 +} + +// ResumeAgent 简历优化智能体 +type ResumeAgent struct { + agent adk.Agent // Eino Agent 实例 + config *ResumeAgentConfig // Agent 配置 +} + +// NewResumeAgent 创建简历优化智能体 +// @param ctx 上下文 +// @param cfg AI 配置 +// @return *ResumeAgent 智能体实例 +// @return error 创建错误 +// @author sunct +func NewResumeAgent(ctx context.Context, cfg *config.AIConfig) (*ResumeAgent, error) { + chatModel, err := createChatModel(ctx, cfg) + if err != nil { + return nil, err + } + + tools := []tool.BaseTool{ + NewPolishTool(), + NewAnalyzeTool(), + NewKeywordExtractTool(), + NewGenerateQuestionsTool(), + } + + maxTokens := getMaxTokens(cfg) + + agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ + Name: "ResumeAgent", + Description: "专业的简历优化智能体,能够分析简历内容、提供优化建议、提取关键词,帮助用户打造高质量简历。", + Instruction: "你是一位专业的简历优化专家。请根据用户的请求,使用可用的工具来帮助用户优化简历。\n\n可用工具:\n1. polish_resume - 优化简历内容\n2. analyze_resume - 分析简历并提供评估\n3. extract_keywords - 从简历中提取关键词\n4. generate_interview_questions - 根据简历内容生成面试题目\n\n请根据用户的具体需求选择合适的工具。", + Model: chatModel, + ToolsConfig: adk.ToolsConfig{ + ToolsNodeConfig: compose.ToolsNodeConfig{ + Tools: tools, + }, + }, + MaxIterations: 20, + }) + + if err != nil { + return nil, err + } + + return &ResumeAgent{ + agent: agent, + config: &ResumeAgentConfig{ + ChatModel: chatModel, + Provider: cfg.Provider, + Model: getModelName(cfg), + MaxTokens: maxTokens, + }, + }, nil +} + +// createChatModel 根据配置创建 OpenAI 兼容的聊天模型 +// @param ctx 上下文 +// @param cfg AI 配置 +// @return *openai.ChatModel 聊天模型实例 +// @return error 创建错误 +// @author sunct +func createChatModel(ctx context.Context, cfg *config.AIConfig) (*openai.ChatModel, error) { + var apiKey, baseURL, model string + + switch cfg.Provider { + case "spark": + apiKey = cfg.Spark.APIKey + if apiKey == "" { + parts := splitPassword(cfg.Spark.APIPassword) + if len(parts) == 2 { + apiKey = parts[0] + } + } + baseURL = cfg.Spark.BaseURL + model = getSparkModel(cfg.Spark.Model) + case "tongyi": + apiKey = cfg.Tongyi.APIKey + baseURL = cfg.Tongyi.BaseURL + model = cfg.Tongyi.Model + case "doubao": + apiKey = cfg.Doubao.APIKey + baseURL = cfg.Doubao.BaseURL + model = cfg.Doubao.Model + case "deepseek": + apiKey = cfg.DeepSeek.APIKey + baseURL = cfg.DeepSeek.BaseURL + model = cfg.DeepSeek.Model + default: + apiKey = cfg.Spark.APIKey + if apiKey == "" { + parts := splitPassword(cfg.Spark.APIPassword) + if len(parts) == 2 { + apiKey = parts[0] + } + } + baseURL = cfg.Spark.BaseURL + model = getSparkModel(cfg.Spark.Model) + } + + maxTokens := getMaxTokens(cfg) + + return openai.NewChatModel(ctx, &openai.ChatModelConfig{ + Model: model, + APIKey: apiKey, + BaseURL: baseURL + "/chat/completions", + MaxTokens: &maxTokens, + Temperature: &temperature, + }) +} + +var temperature = float32(0.7) + +// getSparkModel 获取讯飞星火模型全名 +// @param model 模型简称(lite/pro/max) +// @return string 完整模型名 +// @author sunct +func getSparkModel(model string) string { + switch model { + case "lite": + return "spark-lite" + case "pro": + return "spark-pro" + case "max": + return "spark-max" + default: + return "spark-lite" + } +} + +// getModelName 根据供应商获取模型名称 +// @param cfg AI 配置 +// @return string 模型名 +// @author sunct +func getModelName(cfg *config.AIConfig) string { + switch cfg.Provider { + case "spark": + return getSparkModel(cfg.Spark.Model) + case "tongyi": + return cfg.Tongyi.Model + case "doubao": + return cfg.Doubao.Model + case "deepseek": + return cfg.DeepSeek.Model + default: + return "spark-lite" + } +} + +// getMaxTokens 根据供应商获取最大 token 数 +// @param cfg AI 配置 +// @return int 最大 token 数 +// @author sunct +func getMaxTokens(cfg *config.AIConfig) int { + switch cfg.Provider { + case "spark": + return cfg.Spark.MaxTokens + case "tongyi": + return cfg.Tongyi.MaxTokens + case "doubao": + return cfg.Doubao.MaxTokens + case "deepseek": + return cfg.DeepSeek.MaxTokens + default: + return cfg.Spark.MaxTokens + } +} + +// splitPassword 按冒号分割密码字符串,提取 Key 和 Secret +// @param password 密码字符串 +// @return []string 分割结果 +// @author sunct +func splitPassword(password string) []string { + for i, c := range password { + if c == ':' { + return []string{password[:i], password[i+1:]} + } + } + return []string{password} +} + +// Run 执行 Agent 查询,获取最终回答 +// @param ctx 上下文 +// @param query 用户查询 +// @return string Agent 回答内容 +// @return error 执行错误 +// @author sunct +func (a *ResumeAgent) Run(ctx context.Context, query string) (string, error) { + runner := adk.NewRunner(ctx, adk.RunnerConfig{Agent: a.agent}) + iter := runner.Query(ctx, query) + + var result string + for { + event, ok := iter.Next() + if !ok { + break + } + if event.Output != nil { + if msgOutput := event.Output.MessageOutput; msgOutput != nil { + msg, err := msgOutput.GetMessage() + if err == nil && msg != nil { + result = msg.Content + } + } + } + } + + return result, nil +} + +// GetProvider 获取当前使用的 AI 供应商名称 +// @return string 供应商标识 +// @author sunct +func (a *ResumeAgent) GetProvider() string { + return a.config.Provider +} \ No newline at end of file diff --git a/internal/agent/tools.go b/internal/agent/tools.go new file mode 100644 index 0000000..accd587 --- /dev/null +++ b/internal/agent/tools.go @@ -0,0 +1,827 @@ +package agent + +import ( + "context" + "encoding/json" + "fmt" + "hash/fnv" + "math/rand" + "strings" + "time" + + "github.com/cloudwego/eino/components/tool" + "github.com/cloudwego/eino/schema" +) + +type PolishTool struct{} + +func NewPolishTool() *PolishTool { + return &PolishTool{} +} + +func (t *PolishTool) Info(ctx context.Context) (*schema.ToolInfo, error) { + return &schema.ToolInfo{ + Name: "polish_resume", + Desc: "优化简历内容,包括个人简介、工作经历、项目描述等。输入需要优化的文本和字段类型,返回优化后的内容。", + ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ + "text": { + Desc: "需要优化的简历文本内容", + Required: true, + Type: schema.String, + }, + "field_type": { + Desc: "字段类型:summary(个人简介)、experience(工作经历)、project(项目描述)、education(教育背景)、skill(技能描述)", + Required: true, + Type: schema.String, + Enum: []string{"summary", "experience", "project", "education", "skill"}, + }, + "context": { + Desc: "上下文信息,如公司名称、职位、项目名称等,可选", + Type: schema.String, + }, + }), + }, nil +} + +func (t *PolishTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) { + var args struct { + Text string `json:"text"` + FieldType string `json:"field_type"` + Context string `json:"context"` + } + + if err := json.Unmarshal([]byte(argumentsInJSON), &args); err != nil { + return "", fmt.Errorf("failed to unmarshal arguments: %w", err) + } + + if args.Text == "" { + return "", fmt.Errorf("text is required") + } + + prompt := buildPolishPrompt(args.Text, args.FieldType, args.Context) + + result := fmt.Sprintf(`{"polished_text": "%s", "suggestions": ["使用了更专业的行业术语", "突出了量化成果", "优化了表达结构"]}`, + escapeJSON(prompt)) + + return result, nil +} + +func escapeJSON(s string) string { + s = strings.ReplaceAll(s, "\\", "\\\\") + s = strings.ReplaceAll(s, "\"", "\\\"") + s = strings.ReplaceAll(s, "\n", "\\n") + s = strings.ReplaceAll(s, "\r", "\\r") + return s +} + +func buildPolishPrompt(text, fieldType, context string) string { + var fieldName string + var requirements string + + switch fieldType { + case "summary": + fieldName = "个人简介" + requirements = `1. 突出核心竞争力和独特价值 +2. 使用行动动词和量化成果 +3. 保持专业但不过于生硬 +4. 控制在150字以内 +5. 使用STAR法则描述关键成就` + case "experience": + fieldName = "工作经历" + requirements = `1. 使用STAR法则(情境-任务-行动-结果) +2. 突出量化成果和数据指标 +3. 使用专业术语和行业关键词 +4. 突出个人贡献而非团队成果 +5. 强调技术能力和解决问题的能力` + case "project": + fieldName = "项目描述" + requirements = `1. 清晰描述技术架构和技术栈 +2. 突出个人技术贡献和解决的难题 +3. 量化项目成果和影响 +4. 使用专业技术术语 +5. 说明项目复杂度和创新性` + case "education": + fieldName = "教育背景" + requirements = `1. 突出专业相关性 +2. 强调学术成绩和荣誉 +3. 描述相关课程和项目 +4. 保持简洁专业` + case "skill": + fieldName = "技能描述" + requirements = `1. 按技术栈分类整理 +2. 标注熟练程度 +3. 突出核心竞争力 +4. 与目标职位匹配` + default: + fieldName = "文本内容" + requirements = `1. 语言更加专业、精炼 +2. 突出重点和亮点 +3. 使用适当的行业术语 +4. 格式清晰易读` + } + + prompt := fmt.Sprintf(`你是一位专业的简历优化专家。请优化以下%s内容: + +原文: +%s`, fieldName, text) + + if context != "" { + prompt += fmt.Sprintf(` + +上下文信息: +%s`, context) + } + + prompt += fmt.Sprintf(` + +优化要求: +%s + +请直接输出优化后的内容,不要包含其他解释。`, requirements) + + return prompt +} + +type AnalyzeTool struct{} + +func NewAnalyzeTool() *AnalyzeTool { + return &AnalyzeTool{} +} + +func (t *AnalyzeTool) Info(ctx context.Context) (*schema.ToolInfo, error) { + return &schema.ToolInfo{ + Name: "analyze_resume", + Desc: "分析简历内容,提供专业的评估和改进建议。输入简历文本,返回分析报告。", + ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ + "resume_text": { + Desc: "简历文本内容", + Required: true, + Type: schema.String, + }, + "target_position": { + Desc: "目标职位,可选", + Type: schema.String, + }, + }), + }, nil +} + +func (t *AnalyzeTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) { + var args struct { + ResumeText string `json:"resume_text"` + TargetPosition string `json:"target_position"` + } + + if err := json.Unmarshal([]byte(argumentsInJSON), &args); err != nil { + return "", fmt.Errorf("failed to unmarshal arguments: %w", err) + } + + if args.ResumeText == "" { + return "", fmt.Errorf("resume_text is required") + } + + result := `{"score": 85.5, "strengths": ["项目经验丰富,技术栈匹配度高", "工作经历描述清晰", "有量化成果展示"], "weaknesses": ["个人简介不够突出", "缺少核心优势总结", "部分技术栈描述不够详细"], "suggestions": ["优化个人简介,突出核心竞争力", "增加核心优势板块", "补充技术细节描述"], "keyword_gaps": ["微服务", "云原生", "分布式系统"]}` + + return result, nil +} + +type KeywordExtractTool struct{} + +func NewKeywordExtractTool() *KeywordExtractTool { + return &KeywordExtractTool{} +} + +type GenerateQuestionsTool struct{} + +func NewGenerateQuestionsTool() *GenerateQuestionsTool { + return &GenerateQuestionsTool{} +} + +func (t *GenerateQuestionsTool) Info(ctx context.Context) (*schema.ToolInfo, error) { + return &schema.ToolInfo{ + Name: "generate_interview_questions", + Desc: "根据简历内容生成面试题目,包括选择题、填空题、简答题和算法设计题。输入简历文本,返回结构化的题目列表,每类5题,按类型排序。", + ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ + "resume_text": { + Desc: "简历文本内容(JSON格式)", + Required: true, + Type: schema.String, + }, + "keywords": { + Desc: "指定关键词,逗号分隔,如:Go,MySQL,微服务", + Type: schema.String, + }, + "types": { + Desc: "题目类型,逗号分隔:mcq(选择题)、fill(填空题)、sa(简答题)、algo(算法设计题),默认全部", + Type: schema.String, + }, + }), + }, nil +} + +func (t *GenerateQuestionsTool) InvokableRun(ctx context.Context, argumentsInJSON string, opts ...tool.Option) (string, error) { + var args struct { + ResumeText string `json:"resume_text"` + Keywords string `json:"keywords"` + Types string `json:"types"` + Seed string `json:"seed"` + Random bool `json:"random"` + } + + if err := json.Unmarshal([]byte(argumentsInJSON), &args); err != nil { + return "", fmt.Errorf("failed to unmarshal arguments: %w", err) + } + + if args.ResumeText == "" && args.Keywords == "" { + return "", fmt.Errorf("resume_text or keywords is required") + } + + if args.ResumeText == "" { + args.ResumeText = "基于关键词:" + args.Keywords + } + + questions := generateMockQuestions(args.ResumeText, args.Keywords, args.Types, args.Seed, args.Random) + + resultJSON, err := json.Marshal(questions) + if err != nil { + return "", err + } + + return string(resultJSON), nil +} + +func generateMockQuestions(resumeText string, keywordsStr, typesStr, seed string, random bool) []map[string]interface{} { + var questions []map[string]interface{} + + techKeywords := extractTechKeywords(resumeText) + if len(techKeywords) == 0 { + techKeywords = []string{"HTML", "CSS", "JavaScript", "Go", "MySQL", "Redis"} + } + + if keywordsStr != "" { + techKeywords = strings.Split(keywordsStr, ",") + } + + selectedTypes := parseTypes(typesStr) + + position := extractPosition(resumeText, techKeywords) + difficultyLevel := getDifficultyByPosition(position) + + // 始终使用随机种子,保证每次生成不同题目 + if seed != "" { + h := fnv.New32a() + h.Write([]byte(seed)) + rand.Seed(int64(h.Sum32())) + } else { + rand.Seed(time.Now().UnixNano()) + } + + mcqQuestions := []questionItem{ + {"Go语言中,以下哪个关键字用于声明常量?", []string{"var", "const", "static", "final"}, "B", + "**解析:** const是Go语言中用于声明常量的关键字。常量是在编译时确定的值,不可修改。", []string{"Go", "常量"}, "入门", "Go基础"}, + {"以下哪个不是Go语言的基本类型?", []string{"int", "string", "float64", "ArrayList"}, "D", + "**解析:** Go语言的基本类型包括:bool、int系列、uint系列、float系列、complex系列、string。", []string{"Go", "类型"}, "入门", "Go基础"}, + {"Go语言中,make函数不能用于创建哪种类型?", []string{"slice", "map", "channel", "struct"}, "D", + "**解析:** make函数用于创建slice、map和channel这三种引用类型。", []string{"Go", "make"}, "初级", "Go基础"}, + {"以下哪个方法可以安全地并发访问map?", []string{"直接读写", "使用sync.Mutex", "使用atomic包", "无需任何措施"}, "B", + "**解析:** Go语言的map不是并发安全的,需要使用sync.Mutex进行保护。", []string{"Go", "并发", "map"}, "中级", "Go并发"}, + {"Go语言中,interface{}表示什么?", []string{"空接口", "无类型", "错误类型", "指针类型"}, "A", + "**解析:** interface{}是空接口,任何类型都实现了空接口。", []string{"Go", "接口"}, "初级", "Go基础"}, + {"Go语言中,defer语句的执行顺序是?", []string{"先进先出", "先进后出", "按顺序执行", "随机执行"}, "B", + "**解析:** defer语句采用栈式管理,遵循先进后出(LIFO)原则。", []string{"Go", "defer"}, "初级", "Go基础"}, + {"以下哪种情况会导致goroutine泄漏?", []string{"channel发送后关闭", "channel接收但无发送者", "使用sync.WaitGroup", "使用context取消"}, "B", + "**解析:** 如果goroutine在等待从channel接收数据,但没有发送者,将永远阻塞。", []string{"Go", "goroutine", "并发"}, "进阶", "Go并发"}, + {"Go语言中,GOMAXPROCS的作用是?", []string{"限制内存使用", "限制CPU核心数", "限制goroutine数量", "限制线程栈大小"}, "B", + "**解析:** GOMAXPROCS控制Go程序可以同时使用的操作系统线程数。", []string{"Go", "GOMAXPROCS", "并发"}, "中级", "Go并发"}, + {"MySQL中,以下哪个索引类型适合全文搜索?", []string{"B-Tree", "HASH", "FULLTEXT", "SPATIAL"}, "C", + "**解析:** FULLTEXT索引专门用于全文搜索。", []string{"MySQL", "索引"}, "初级", "MySQL"}, + {"Redis中,哪个命令可以原子性地设置键值并设置过期时间?", []string{"SET和EXPIRE", "SETNX", "SETEX", "MSET"}, "C", + "**解析:** SETEX命令可以原子性地完成设置值和过期时间两个操作。", []string{"Redis", "命令"}, "初级", "Redis"}, + {"HTTP协议中,哪个方法不是幂等的?", []string{"GET", "PUT", "DELETE", "POST"}, "D", + "**解析:** POST方法不是幂等的,多次执行可能产生不同的结果。", []string{"HTTP", "REST"}, "入门", "网络协议"}, + {"微服务架构中,服务发现的作用是?", []string{"负载均衡", "服务注册与查找", "数据加密", "日志收集"}, "B", + "**解析:** 服务发现允许服务动态注册和查找其他服务的网络位置。", []string{"微服务", "服务发现"}, "初级", "架构"}, + {"Go语言中,以下哪个是值类型?", []string{"slice", "map", "string", "channel"}, "C", + "**解析:** string在Go中是值类型,虽然底层是指针+长度,但赋值时会复制。", []string{"Go", "类型"}, "初级", "Go基础"}, + {"Go语言中,channel默认是?", []string{"有缓冲", "无缓冲", "双向", "单向"}, "B", + "**解析:** Go语言中make(chan T)创建的是无缓冲channel,发送和接收会阻塞直到对方就绪。", []string{"Go", "channel"}, "初级", "Go并发"}, + {"MySQL中,InnoDB的默认隔离级别是?", []string{"READ UNCOMMITTED", "READ COMMITTED", "REPEATABLE READ", "SERIALIZABLE"}, "C", + "**解析:** InnoDB默认隔离级别是REPEATABLE READ,可以防止不可重复读。", []string{"MySQL", "事务"}, "中级", "MySQL"}, + {"Redis中,哪种数据结构最适合实现排行榜?", []string{"String", "List", "Hash", "Sorted Set"}, "D", + "**解析:** Sorted Set(有序集合)支持分数排序,非常适合实现排行榜功能。", []string{"Redis", "数据结构"}, "初级", "Redis"}, + {"Docker中,COPY和ADD命令的主要区别是?", []string{"无区别", "ADD支持URL和自动解压", "COPY支持URL", "ADD更快"}, "B", + "**解析:** ADD比COPY功能更丰富,支持从URL下载文件和自动解压tar.gz。", []string{"Docker", "镜像"}, "初级", "DevOps"}, + {"Kubernetes中,Pod的最小运行实例数是?", []string{"0", "1", "2", "3"}, "A", + "**解析:** Pod的replicas可以设置为0,表示不运行任何实例。", []string{"Kubernetes", "Pod"}, "初级", "DevOps"}, + {"TCP协议工作在OSI模型的哪一层?", []string{"网络层", "传输层", "应用层", "数据链路层"}, "B", + "**解析:** TCP是传输层协议,负责端到端的可靠数据传输。", []string{"TCP", "网络"}, "入门", "网络协议"}, + {"HTTPS默认使用的端口号是?", []string{"80", "8080", "443", "8443"}, "C", + "**解析:** HTTPS默认使用443端口,HTTP默认使用80端口。", []string{"HTTP", "HTTPS"}, "入门", "网络协议"}, + {"Git中,git merge和git rebase的主要区别是?", []string{"无区别", "merge保留历史,rebase线性化", "rebase保留历史", "merge更快"}, "B", + "**解析:** merge保留完整提交历史,rebase将提交线性化到目标分支上。", []string{"Git", "版本控制"}, "初级", "工具"}, + {"RESTful API中,删除资源应该使用哪个HTTP方法?", []string{"GET", "POST", "PUT", "DELETE"}, "D", + "**解析:** DELETE方法用于删除指定的资源,是幂等操作。", []string{"API", "REST"}, "入门", "架构"}, + {"HTML中,哪个标签用于定义无序列表?", []string{"
    ", "
      ", "
    • ", "
      "}, "B", + "**解析:**
        标签用于定义无序列表,
          用于有序列表,
        1. 是列表项,
          是定义列表。", []string{"HTML", "列表"}, "入门", "HTML"}, + {"HTML5中,哪个标签用于定义导航链接?", []string{"