commit
8a12ad04db
58 changed files with 5176 additions and 0 deletions
@ -0,0 +1,31 @@ |
|||
{ |
|||
"permissions": { |
|||
"allow": [ |
|||
"Bash(powershell:*)", |
|||
"Bash(tree:*)", |
|||
"Bash(go get:*)", |
|||
"Bash(go mod init:*)", |
|||
"Bash(go mod tidy:*)", |
|||
"Bash(ls:*)", |
|||
"Bash(go run:*)", |
|||
"Bash(go list:*)", |
|||
"Bash(go mod download:*)", |
|||
"Bash(git clone:*)", |
|||
"Bash(goctl:*)", |
|||
"Bash(go install:*)", |
|||
"Bash(~/go/bin/goctl version:*)", |
|||
"Bash(~/go/bin/goctl.exe:*)", |
|||
"Bash(~/go/bin/goctl.exe -v)", |
|||
"Bash(go build:*)", |
|||
"Bash(mysql:*)", |
|||
"Bash(claude:*)", |
|||
"Bash(npm view:*)", |
|||
"Bash(./user.exe:*)", |
|||
"Bash(timeout:*)", |
|||
"Bash(tasklist:*)", |
|||
"Bash(dir:*)", |
|||
"Bash(findstr:*)", |
|||
"Bash(netstat:*)" |
|||
] |
|||
} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
Subproject commit 789a42248d8a81cec421a2bb4a1f2db7da63d65c |
|||
@ -0,0 +1,111 @@ |
|||
# Go |
|||
*.exe |
|||
*.exe~ |
|||
*.dll |
|||
*.so |
|||
*.dylib |
|||
*.test |
|||
*.out |
|||
go.work |
|||
vendor/ |
|||
|
|||
# Node.js / Web |
|||
node_modules/ |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
.env |
|||
.env.local |
|||
.env.development.local |
|||
.env.test.local |
|||
.env.production.local |
|||
dist/ |
|||
build/ |
|||
.next/ |
|||
.nuxt/ |
|||
.cache/ |
|||
.parcel-cache/ |
|||
|
|||
# React Native |
|||
android/app/build/ |
|||
android/build/ |
|||
ios/build/ |
|||
*.ipa |
|||
*.apk |
|||
.expo/ |
|||
.expo-shared/ |
|||
metro.config.js.map |
|||
|
|||
# General |
|||
.DS_Store |
|||
Thumbs.db |
|||
*.log |
|||
*.tmp |
|||
.vscode/ |
|||
.idea/ |
|||
*.swp |
|||
*.swo |
|||
*~ |
|||
|
|||
# Logs |
|||
logs/ |
|||
*.log |
|||
|
|||
# Runtime data |
|||
pids/ |
|||
*.pid |
|||
*.seed |
|||
*.pid.lock |
|||
|
|||
# Coverage directory used by tools like istanbul |
|||
coverage/ |
|||
|
|||
# Dependency directories |
|||
jspm_packages/ |
|||
|
|||
# Optional npm cache directory |
|||
.npm |
|||
|
|||
# Optional REPL history |
|||
.node_repl_history |
|||
|
|||
# Output of 'npm pack' |
|||
*.tgz |
|||
|
|||
# Yarn Integrity file |
|||
.yarn-integrity |
|||
|
|||
# dotenv environment variables file |
|||
.env.test |
|||
|
|||
# parcel-bundler cache (https://parceljs.org/) |
|||
.cache |
|||
.parcel-cache |
|||
|
|||
# next.js build output |
|||
.next |
|||
|
|||
# nuxt.js build output |
|||
.nuxt |
|||
|
|||
# vuepress build output |
|||
.vuepress/dist |
|||
|
|||
# Serverless directories |
|||
.serverless |
|||
|
|||
# FuseBox cache |
|||
.fusebox/ |
|||
|
|||
# DynamoDB Local files |
|||
.dynamodb/ |
|||
|
|||
# TernJS port file |
|||
.tern-port |
|||
|
|||
# Stores VSCode versions used for testing VSCode extensions |
|||
.vscode-test |
|||
|
|||
# Temporary folders |
|||
tmp/ |
|||
temp/ |
|||
@ -0,0 +1,707 @@ |
|||
# Go-Zero AI 工具集成指南 |
|||
|
|||
本文档详细说明 `zero-skills`、`ai-context` 和 `mcp-zero` 三个工具如何配合使用。 |
|||
|
|||
--- |
|||
|
|||
## 📊 三件套概览 |
|||
|
|||
| 工具 | 用途 | 大小 | 最适合工具 | 角色 | |
|||
|------|------|------|-----------|------| |
|||
| **ai-context** | 工作流指令和决策树 | ~5KB | GitHub Copilot, Cursor, Windsurf | 工作流层 | |
|||
| **zero-skills** | 完整知识库 | ~40KB | Claude Code,深度学习 | 知识层 | |
|||
| **mcp-zero** | 运行时工具(goctl 命令) | MCP Server | Claude Desktop/Code | 执行层 | |
|||
|
|||
--- |
|||
|
|||
## 🎯 架构协作图 |
|||
|
|||
``` |
|||
┌─────────────────────────────────────────────────────────────┐ |
|||
│ AI 助手 │ |
|||
│ (Claude Code, GitHub Copilot, Cursor, 等) │ |
|||
└────────────┬─────────────────────┬──────────────────────────┘ |
|||
│ │ |
|||
├─ 工作流层 ──────────┤ |
|||
│ ai-context │ "做什么" - 快速决策 |
|||
│ (~5KB) │ 每次交互都加载 |
|||
│ │ |
|||
├─ 知识层 ────────────┤ |
|||
│ zero-skills │ "如何和为什么" - 详细模式 |
|||
│ (~40KB) │ 需要时加载 |
|||
│ │ |
|||
└─ 执行层 ────────────┘ |
|||
mcp-zero "执行" - 运行 goctl 命令 |
|||
(MCP Server) 生成实际代码文件 |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## 📦 工具详细说明 |
|||
|
|||
### 1. ai-context - 工作流层 |
|||
|
|||
**定位**: 快速决策工具,回答"做什么" |
|||
|
|||
**功能**: |
|||
- 提供简洁的工作流指导 |
|||
- 帮助 AI 快速做出开发决策 |
|||
- 每次交互都加载,轻量级(~5KB) |
|||
|
|||
**适用场景**: |
|||
- 快速生成代码片段 |
|||
- 内联代码补全 |
|||
- 需要快速决策的开发流程 |
|||
|
|||
**安装方式**: |
|||
```bash |
|||
# GitHub Copilot |
|||
git clone https://github.com/zeromicro/ai-context.git .github/copilot-instructions.md |
|||
|
|||
# Cursor |
|||
git clone https://github.com/zeromicro/ai-context.git .cursorrules |
|||
|
|||
# Windsurf |
|||
git clone https://github.com/zeromicro/ai-context.git .windsurfrules |
|||
``` |
|||
|
|||
**使用特点**: |
|||
- ⚡ 快速加载 |
|||
- 📝 简洁的决策树 |
|||
- 🎯 适合日常开发 |
|||
|
|||
--- |
|||
|
|||
### 2. zero-skills - 知识层 |
|||
|
|||
**定位**: 深度知识库,回答"如何和为什么" |
|||
|
|||
**功能**: |
|||
- 完整的 go-zero 模式指南 |
|||
- 三层架构(Handler → Logic → Model)详解 |
|||
- 正确和错误示例对比 |
|||
- 生产级最佳实践 |
|||
|
|||
**适用场景**: |
|||
- 深度学习和理解 go-zero |
|||
- 复杂的微服务架构设计 |
|||
- 代码审查和问题排查 |
|||
- 需要 AI Code 的高级功能 |
|||
|
|||
**安装方式**: |
|||
```bash |
|||
# 项目级别(推荐) |
|||
git clone https://github.com/zeromicro/zero-skills.git .claude/skills/zero-skills |
|||
|
|||
# 个人级别 |
|||
git clone https://github.com/zeromicro/zero-skills.git ~/.claude/skills/zero-skills |
|||
``` |
|||
|
|||
**在 Claude Code 中的使用**: |
|||
```bash |
|||
# 自动加载 |
|||
# 处理 go-zero 文件(.api, .proto, go.mod)时自动加载 |
|||
|
|||
# 手动调用 |
|||
/zero-skills |
|||
|
|||
# 带参数调用 |
|||
/zero-skills 创建用户管理 API |
|||
|
|||
# 检查可用性 |
|||
What skills are available? |
|||
``` |
|||
|
|||
**使用特点**: |
|||
- 📚 完整知识库(~40KB) |
|||
- 🎓 需要时加载,按需获取 |
|||
- 🔍 详细模式和示例 |
|||
- 🤖 支持子代理和动态上下文 |
|||
|
|||
--- |
|||
|
|||
### 3. mcp-zero - 执行层 |
|||
|
|||
**定位**: 代码执行工具,负责"执行" |
|||
|
|||
**功能**: |
|||
- 执行 `goctl` 命令生成代码 |
|||
- 生成实际的项目文件 |
|||
- MCP Server 协议集成 |
|||
|
|||
**适用场景**: |
|||
- 自动化代码生成 |
|||
- 批量创建服务文件 |
|||
- Claude Desktop/Code 环境使用 |
|||
|
|||
**安装方式**: |
|||
|
|||
1. 安装 MCP Server: |
|||
```bash |
|||
npm install -g @zeromicro/mcp-zero |
|||
``` |
|||
|
|||
2. 配置 Claude Desktop: |
|||
```json |
|||
{ |
|||
"mcpServers": { |
|||
"mcp-zero": { |
|||
"command": "node", |
|||
"args": ["C:\\Users\\YourUser\\AppData\\Roaming\\npm\\node_modules\\@zeromicro\\mcp-zero\\dist\\index.js"], |
|||
"env": { |
|||
"GOCTL_PATH": "/path/to/goctl" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
**使用特点**: |
|||
- ⚙️ 执行 goctl 命令 |
|||
- 📁 生成实际代码文件 |
|||
- 🔌 MCP 协议集成 |
|||
|
|||
--- |
|||
|
|||
## 🚀 使用场景 |
|||
|
|||
### 场景 1: GitHub Copilot 用户 |
|||
|
|||
**工具组合**: 仅 `ai-context` |
|||
|
|||
**工作流程**: |
|||
``` |
|||
用户编辑代码 |
|||
↓ |
|||
加载 ai-context |
|||
↓ |
|||
AI 提供快速建议 |
|||
↓ |
|||
用户手动运行 goctl 命令 |
|||
``` |
|||
|
|||
**优点**: |
|||
- 快速内联建议 |
|||
- 工作流指导 |
|||
- 无需额外配置 |
|||
|
|||
**限制**: |
|||
- 无代码执行能力 |
|||
- 需手动运行 goctl 命令 |
|||
|
|||
**示例使用**: |
|||
``` |
|||
# .github/copilot-instructions.md |
|||
|
|||
<!-- ai-context 内容 --> |
|||
当编辑 go-zero 文件时,遵循以下流程: |
|||
1. 先定义 .api 文件类型和路由 |
|||
2. 使用 goctl 生成代码 |
|||
3. 在 internal/logic/ 实现业务逻辑 |
|||
4. 使用 sqlx 进行数据库操作 |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 场景 2: Claude Code 用户(最佳体验)⭐ |
|||
|
|||
**工具组合**: `zero-skills` + `mcp-zero` |
|||
|
|||
**工作流程**: |
|||
``` |
|||
用户发起请求 |
|||
↓ |
|||
加载 zero-skills (知识层) |
|||
↓ |
|||
AI 调用 mcp-zero 工具 (执行层) |
|||
↓ |
|||
执行 goctl 命令生成代码 |
|||
↓ |
|||
返回生成的文件和说明 |
|||
``` |
|||
|
|||
**优点**: |
|||
- 来自模式指南的深度知识 |
|||
- 通过 goctl 自动生成代码 |
|||
- 实时项目数据的动态上下文 |
|||
- 复杂任务的子代理工作流 |
|||
|
|||
**完整示例**: |
|||
|
|||
用户: `/zero-skills 创建用户管理 API` |
|||
|
|||
AI 处理: |
|||
1. 加载 `zero-skills` 的 `rest-api-patterns.md` |
|||
2. 了解三层架构要求 |
|||
3. 调用 `mcp-zero` 的 `generate_api` 工具: |
|||
```bash |
|||
goctl api go -api user.api -dir . |
|||
``` |
|||
4. 生成文件结构: |
|||
``` |
|||
. |
|||
├── user.api |
|||
├── etc/ |
|||
├── internal/ |
|||
│ ├── handler/ |
|||
│ ├── logic/ |
|||
│ ├── svc/ |
|||
│ └── types/ |
|||
└── main.go |
|||
``` |
|||
5. 解释生成的代码结构 |
|||
|
|||
**高级功能**: |
|||
```markdown |
|||
# 子代理使用示例 |
|||
/zero-skills 分析项目架构 |
|||
|
|||
AI 将: |
|||
1. Fork 子代理 |
|||
2. 只读分析项目文件 |
|||
3. 返回架构分析报告 |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 场景 3: Cursor/Windsurf 用户 |
|||
|
|||
**工具组合**: `ai-context` + `zero-skills` 链接 |
|||
|
|||
**工作流程**: |
|||
``` |
|||
用户在 IDE 中编码 |
|||
↓ |
|||
加载 ai-context (快速决策) |
|||
↓ |
|||
必要时链接到 zero-skills (深度知识) |
|||
↓ |
|||
AI 提供建议和指导 |
|||
``` |
|||
|
|||
**优点**: |
|||
- IDE 原生体验 |
|||
- 快速建议 + 深度知识 |
|||
- 灵活使用 |
|||
|
|||
**配置示例**: |
|||
|
|||
`.cursorrules`: |
|||
```markdown |
|||
<!-- ai-context 内容 --> |
|||
当使用 go-zero 框架时: |
|||
- 遵循三层架构: Handler → Logic → Model |
|||
- 使用 goctl 生成代码 |
|||
- 使用 httpx.Error() 处理错误 |
|||
|
|||
<!-- 链接到 zero-skills --> |
|||
详情参考: .claude/skills/zero-skills/references/rest-api-patterns.md |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## 🔗 工具间的协作示例 |
|||
|
|||
### 示例 1: 创建完整的 REST API |
|||
|
|||
**用户请求**: "创建用户管理的 REST API" |
|||
|
|||
**三件套协作**: |
|||
|
|||
``` |
|||
Step 1: ai-context 决策 (工作流层) |
|||
------------------------------------ |
|||
决定创建流程: |
|||
1. 定义 .api 文件 |
|||
2. 生成代码 |
|||
3. 实现业务逻辑 |
|||
|
|||
Step 2: zero-skills 指导 (知识层) |
|||
------------------------------------ |
|||
生成 user.api 内容参考: |
|||
- 类型定义结构 |
|||
- 路由定义规范 |
|||
- handler 命名约定 |
|||
- 验证规则 |
|||
|
|||
Step 3: mcp-zero 执行 (执行层) |
|||
------------------------------------ |
|||
执行命令: |
|||
goctl api go -api user.api -dir . |
|||
|
|||
生成文件: |
|||
├── user.api |
|||
├── etc/user-api.yaml |
|||
├── internal/handler/*.go |
|||
├── internal/logic/*.go |
|||
├── internal/svc/servicecontext.go |
|||
├── internal/types/types.go |
|||
└── user.go |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 示例 2: 修复数据库连接问题 |
|||
|
|||
**用户请求**: "数据库连接失败,怎么排查" |
|||
|
|||
**三件套协作**: |
|||
|
|||
``` |
|||
Step 1: ai-context 决策 |
|||
------------------------------------ |
|||
分析可能的错误原因: |
|||
- 连接字符串错误 |
|||
- 数据库服务未启动 |
|||
- 防火墙阻止 |
|||
- 驱动问题 |
|||
|
|||
Step 2: zero-skills 深度分析 |
|||
------------------------------------ |
|||
从 database-patterns.md 获取: |
|||
- 正确的连接字符串格式 |
|||
- ServiceContext 注入方法 |
|||
- sqlx 配置方式 |
|||
- 错误处理最佳实践 |
|||
|
|||
Step 3: 提供解决方案 |
|||
------------------------------------ |
|||
参考配置: |
|||
```go |
|||
// internal/svc/servicecontext.go |
|||
type ServiceContext struct { |
|||
Config config.Config |
|||
SqlConn sqlx.SqlConn |
|||
} |
|||
|
|||
func NewServiceContext(c config.Config) *ServiceContext { |
|||
return &ServiceContext{ |
|||
Config: c, |
|||
SqlConn: sqlx.NewMysql(c.Database.DataSource), |
|||
} |
|||
} |
|||
``` |
|||
|
|||
检查清单: |
|||
- [ ] 数据库服务运行状态 |
|||
- [ ] 连接字符串格式 |
|||
- [ ] 防火墙设置 |
|||
- [ ] sqlx 导入正确 |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## 📋 快速参考 |
|||
|
|||
### 工具选择决策树 |
|||
|
|||
``` |
|||
开始 |
|||
│ |
|||
├─ 使用什么 AI 工具? |
|||
│ ├─ GitHub Copilot → 使用 ai-context |
|||
│ ├─ Cursor/Windsurf → 使用 ai-context + zero-skills |
|||
│ └─ Claude Code → 使用 zero-skills + mcp-zero |
|||
│ |
|||
└─ 任务类型? |
|||
├─ 快速代码片段 → ai-context |
|||
├─ 深度学习/参考 → zero-skills |
|||
└─ 生成项目文件 → mcp-zero |
|||
``` |
|||
|
|||
### 命令速查 |
|||
|
|||
```bash |
|||
# ai-context 安装 |
|||
git clone https://github.com/zeromicro/ai-context.git .github/copilot-instructions.md |
|||
|
|||
# zero-skills 安装(项目级) |
|||
git clone https://github.com/zeromicro/zero-skills.git .claude/skills/zero-skills |
|||
|
|||
# zero-skills 安装(个人级) |
|||
git clone https://github.com/zeromicro/zero-skills.git ~/.claude/skills/zero-skills |
|||
|
|||
# mcp-zero 安装 |
|||
npm install -g @zeromicro/mcp-zero |
|||
|
|||
# 手动调用 zero-skills |
|||
/zero-skills |
|||
/zero-skills 创建用户管理 API |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## 💡 最佳实践 |
|||
|
|||
### 1. Claude Code 环境推荐配置 |
|||
|
|||
``` |
|||
项目级 zero-skills + mcp-zero |
|||
``` |
|||
|
|||
**为什么**: |
|||
- 自动加载,无需手动触发 |
|||
- 完整知识库 + 代码执行 |
|||
- 支持高级功能(子代理、动态上下文) |
|||
|
|||
### 2. 多工具环境配置 |
|||
|
|||
``` |
|||
~/.claude/skills/zero-skills # 所有项目可用 |
|||
~/.claude/skills/ai-context # 可选 |
|||
项目/.github/copilot-instructions.md # Copilot 专用 |
|||
项目/.cursorrules # Cursor 专用 |
|||
``` |
|||
|
|||
### 3. 性能优化 |
|||
|
|||
- **轻量级需求**: 仅用 ai-context(5KB) |
|||
- **深度学习**: zero-skills(40KB,按需加载) |
|||
- **代码生成**: 配合 mcp-zero 执行层 |
|||
|
|||
--- |
|||
|
|||
## 🔗 相关资源 |
|||
|
|||
- **[ai-context GitHub](https://github.com/zeromicro/ai-context)** - 工作流层 |
|||
- **[zero-skills GitHub](https://github.com/zeromicro/zero-skills)** - 知识层 |
|||
- **[mcp-zero GitHub](https://github.com/zeromicro/mcp-zero)** - 执行层 |
|||
- **[go-zero 官方文档](https://go-zero.dev)** - 框架文档 |
|||
- **[Agent Skills 规范](https://agentskills.io/)** - 技能规范 |
|||
|
|||
--- |
|||
|
|||
## 📝 总结 |
|||
|
|||
| 层级 | 工具 | 作用 | 大小 | |
|||
|------|------|------|------| |
|||
| 工作流层 | ai-context | 快速决策"做什么" | ~5KB | |
|||
| 知识层 | zero-skills | 深度知识"如何和为什么" | ~40KB | |
|||
| 执行层 | mcp-zero | 执行代码生成 | MCP Server | |
|||
|
|||
**核心思想**: 根据不同的 AI 工具和任务需求,灵活组合三件套,实现最高效的开发体验。 |
|||
|
|||
--- |
|||
|
|||
## 📦 Go-Zero + GORM 模块开发实战指南 |
|||
|
|||
### 概述 |
|||
|
|||
使用 GORM 替代默认的 sqlx 时,goctl 生成的代码需要手动修复多处问题。本文档记录了用户模块开发过程中遇到的坑点和解决方案。 |
|||
|
|||
--- |
|||
|
|||
### 一、生成基础代码 |
|||
|
|||
```bash |
|||
# 1. 生成 API 定义 |
|||
goctl api go -api user.api -dir backend |
|||
|
|||
# 2. 生成 model 代码(如果使用 sqlx) |
|||
goctl model mysql ddl -src user.sql -dir backend/model |
|||
``` |
|||
|
|||
**注意:** 使用 GORM 时,`goctl model` 生成的代码可能不适用,需要手动定义 Model 和 CRUD 方法。 |
|||
|
|||
--- |
|||
|
|||
### 二、修复 ServiceContext (GORM 初始化) |
|||
|
|||
文件:`backend/internal/svc/servicecontext.go` |
|||
|
|||
#### 常见错误 1:gorm.Open 参数不足 |
|||
|
|||
❌ 错误: |
|||
```go |
|||
db, err := gorm.Open(mysql.Open(dsn)) |
|||
``` |
|||
|
|||
✅ 正确: |
|||
```go |
|||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) |
|||
``` |
|||
|
|||
#### 常见错误 2:DB.Close() 方法不存在 |
|||
|
|||
❌ 错误: |
|||
```go |
|||
func (s *ServiceContext) Close() error { |
|||
if s.DB != nil { |
|||
return s.DB.Close() // *gorm.DB 没有 Close 方法 |
|||
} |
|||
return nil |
|||
} |
|||
``` |
|||
|
|||
✅ 正确: |
|||
```go |
|||
func (s *ServiceContext) Close() error { |
|||
if s.DB != nil { |
|||
sqlDB, err := s.DB.DB() // 获取底层 *sql.DB |
|||
if err != nil { |
|||
return err |
|||
} |
|||
return sqlDB.Close() |
|||
} |
|||
return nil |
|||
} |
|||
``` |
|||
|
|||
#### 完整的 NewServiceContext |
|||
|
|||
```go |
|||
func NewServiceContext(c config.Config) *ServiceContext { |
|||
dsn := "user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=true&loc=Local" |
|||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) |
|||
if err != nil { |
|||
panic("Failed to connect database: " + err.Error()) |
|||
} |
|||
|
|||
// 自动迁移(必须在启动时调用) |
|||
err = db.AutoMigrate(&model.User{}) |
|||
if err != nil { |
|||
panic("Failed to migrate database: " + err.Error()) |
|||
} |
|||
|
|||
return &ServiceContext{ |
|||
Config: c, |
|||
DB: db, |
|||
// ...其他字段 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 三、实现 Model 层 (CRUD 操作) |
|||
|
|||
**关键点:** `goctl` 生成的 Logic 文件会引用 `model.FindOne`、`model.Insert` 等方法,但默认的 `model` 包只包含实体定义,**缺少这些 CRUD 方法**。 |
|||
|
|||
需要手动创建:`backend/model/<module>_model.go` |
|||
|
|||
#### 标准模板 |
|||
|
|||
```go |
|||
package model |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
var ( |
|||
ErrNotFound = errors.New("record not found") |
|||
) |
|||
|
|||
// Insert 插入记录 |
|||
func Insert(ctx context.Context, db *gorm.DB, record interface{}) (int64, error) { |
|||
result := db.WithContext(ctx).Create(record) |
|||
if result.Error != nil { |
|||
return 0, result.Error |
|||
} |
|||
return record.(*User).Id, nil // 根据实际结构体调整 |
|||
} |
|||
|
|||
// FindOne 根据ID查询 |
|||
func FindOne(ctx context.Context, db *gorm.DB, id int64) (*User, error) { |
|||
var user User |
|||
result := db.WithContext(ctx).First(&user, id) |
|||
if result.Error != nil { |
|||
if errors.Is(result.Error, gorm.ErrRecordNotFound) { |
|||
return nil, ErrNotFound |
|||
} |
|||
return nil, result.Error |
|||
} |
|||
return &user, nil |
|||
} |
|||
|
|||
// FindOneByXxx 根据唯一字段查询 |
|||
func FindOneByEmail(ctx context.Context, db *gorm.DB, email string) (*User, error) { |
|||
var user User |
|||
result := db.WithContext(ctx).Where("email = ?", email).First(&user) |
|||
if result.Error != nil { |
|||
if errors.Is(result.Error, gorm.ErrRecordNotFound) { |
|||
return nil, ErrNotFound |
|||
} |
|||
return nil, result.Error |
|||
} |
|||
return &user, nil |
|||
} |
|||
|
|||
// Update 更新记录 |
|||
func Update(ctx context.Context, db *gorm.DB, user *User) error { |
|||
return db.WithContext(ctx).Save(user).Error |
|||
} |
|||
|
|||
// Delete 删除记录 |
|||
func Delete(ctx context.Context, db *gorm.DB, id int64) error { |
|||
return db.WithContext(ctx).Delete(&User{}, id).Error |
|||
} |
|||
|
|||
// FindList 分页查询列表 |
|||
func FindList(ctx context.Context, db *gorm.DB, page, pageSize int64, keyword string, status int64) ([]User, int64, error) { |
|||
var users []User |
|||
var total int64 |
|||
|
|||
query := db.WithContext(ctx).Model(&User{}) |
|||
|
|||
// 关键字搜索 |
|||
if keyword != "" { |
|||
query = query.Where("username LIKE ? OR email LIKE ?", |
|||
"%"+keyword+"%", "%"+keyword+"%") |
|||
} |
|||
|
|||
// 状态筛选 |
|||
if status > 0 { |
|||
query = query.Where("status = ?", status) |
|||
} |
|||
|
|||
// 统计总数 |
|||
if err := query.Count(&total).Error; err != nil { |
|||
return nil, 0, err |
|||
} |
|||
|
|||
// 分页 |
|||
offset := (page - 1) * pageSize |
|||
if offset < 0 { |
|||
offset = 0 |
|||
} |
|||
|
|||
err := query.Offset(int(offset)).Limit(int(pageSize)).Find(&users).Error |
|||
if err != nil { |
|||
return nil, 0, err |
|||
} |
|||
|
|||
return users, total, nil |
|||
} |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 四、开发清单 |
|||
|
|||
- [ ] 修复 `gorm.Open` 调用,添加 `&gorm.Config{}` |
|||
- [ ] 修复 `Close()` 方法,使用 `db.DB().Close()` |
|||
- [ ] 在 `NewServiceContext` 中添加 `AutoMigrate` |
|||
- [ ] 为每个模块手动创建 `<module>_model.go` |
|||
- [ ] 实现 `Insert`, `FindOne`, `Update`, `Delete`, `FindList` |
|||
- [ ] 定义 `ErrNotFound` 用于逻辑层判断 |
|||
|
|||
--- |
|||
|
|||
### 五、参考文件结构 |
|||
|
|||
``` |
|||
backend/ |
|||
├── model/ |
|||
│ ├── user_entity.go # 实体定义 |
|||
│ └── user_model.go # CRUD 方法 (手动添加) |
|||
├── internal/ |
|||
│ └── svc/ |
|||
│ └── servicecontext.go # GORM 初始化 |
|||
└── internal/logic/user/ |
|||
├── createuserlogic.go # 引用 model.Insert |
|||
├── getuserlogic.go # 引用 model.FindOne |
|||
└── ... |
|||
``` |
|||
@ -0,0 +1,187 @@ |
|||
# AI Context - Go 上下文管理 |
|||
|
|||
> ai-context 是 Go 语言的 AI 上下文管理库,用于管理 AI 对话的上下文和状态。 |
|||
|
|||
## 官方资源 |
|||
|
|||
- [GitHub](https://github.com/zeromicro/ai-context) |
|||
- [文档](https://github.com/zeromicro/ai-context?tab=readme-ov-file) |
|||
|
|||
## 概述 |
|||
|
|||
ai-context 提供了一套完整的 AI 上下文管理解决方案,支持对话历史、状态管理和上下文压缩等功能。 |
|||
|
|||
## 核心功能 |
|||
|
|||
### 对话历史管理 |
|||
- 存储和管理完整的对话历史 |
|||
- 支持多轮对话追踪 |
|||
- 消息角色识别(用户、助手、系统) |
|||
|
|||
### 上下文压缩 |
|||
- 智能摘要生成 |
|||
- 上下文窗口优化 |
|||
- 历史消息归档 |
|||
|
|||
### 状态管理 |
|||
- 会话状态持久化 |
|||
- 分布式存储支持 |
|||
- 状态恢复机制 |
|||
|
|||
## 基本使用 |
|||
|
|||
```go |
|||
package main |
|||
|
|||
import ( |
|||
"context" |
|||
"github.com/zeromicro/ai-context" |
|||
) |
|||
|
|||
func main() { |
|||
// 创建上下文管理器 |
|||
manager := context.NewManager(context.Config{ |
|||
Storage: "memory", // 或 "redis" |
|||
MaxSize: 100, // 最大历史消息数 |
|||
}) |
|||
|
|||
// 创建会话 |
|||
sessionID := "session-123" |
|||
ctx := manager.CreateSession(sessionID) |
|||
|
|||
// 添加消息 |
|||
ctx.AddMessage(context.Message{ |
|||
Role: "user", |
|||
Content: "你好,今天天气怎么样?", |
|||
}) |
|||
|
|||
ctx.AddMessage(context.Message{ |
|||
Role: "assistant", |
|||
Content: "今天天气晴朗,温度适宜。", |
|||
}) |
|||
|
|||
// 获取历史消息 |
|||
history := ctx.GetHistory() |
|||
for _, msg := range history { |
|||
fmt.Printf("%s: %s\n", msg.Role, msg.Content) |
|||
} |
|||
|
|||
// 获取用于 AI 调用的上下文 |
|||
aiContext := ctx.GetContextForAI() |
|||
// 使用 aiContext 调用 AI 模型... |
|||
|
|||
// 保存会话状态 |
|||
manager.SaveSession(sessionID) |
|||
} |
|||
``` |
|||
|
|||
## 配置选项 |
|||
|
|||
```go |
|||
type Config struct { |
|||
Storage string // 存储类型: memory, redis, database |
|||
MaxSize int // 最大消息数量 |
|||
TTL time.Duration // 会话过期时间 |
|||
Compression bool // 是否启用压缩 |
|||
Compressor Compressor // 压缩器 |
|||
StorageConf StorageConfig // 存储配置 |
|||
} |
|||
``` |
|||
|
|||
## Redis 存储 |
|||
|
|||
```go |
|||
manager, err := context.NewManager(context.Config{ |
|||
Storage: "redis", |
|||
StorageConf: context.StorageConfig{ |
|||
Addr: "localhost:6379", |
|||
Password: "", |
|||
DB: 0, |
|||
}, |
|||
MaxSize: 100, |
|||
}) |
|||
``` |
|||
|
|||
## 上下文压缩 |
|||
|
|||
```go |
|||
// 启用自动压缩 |
|||
manager, _ := context.NewManager(context.Config{ |
|||
MaxSize: 100, |
|||
Compression: true, |
|||
Compressor: context.NewSummarizerCompressor(), |
|||
}) |
|||
|
|||
// 手动压缩 |
|||
ctx.Compress() |
|||
``` |
|||
|
|||
## AI 上下文使用 |
|||
|
|||
```go |
|||
// 获取格式化的上下文用于 AI 模型 |
|||
ctx := manager.GetSession("session-123") |
|||
|
|||
// 获取完整上下文 |
|||
prompt := ctx.FormatPrompt(context.FormatOptions{ |
|||
IncludeSystem: true, |
|||
MaxTokens: 2000, |
|||
}) |
|||
|
|||
// 或者获取消息列表 |
|||
messages := ctx.GetMessages() |
|||
// messages 可以直接传递给 OpenAI API 等 |
|||
``` |
|||
|
|||
## 中间件支持 |
|||
|
|||
```go |
|||
// 使用中间件扩展功能 |
|||
manager.Use(middleware.SessionLogger()) |
|||
manager.Use(middleware.MetricsCollector()) |
|||
manager.Use(middleware.RateLimiter(100)) |
|||
``` |
|||
|
|||
## 与 go-zero 集成 |
|||
|
|||
```go |
|||
// 在 handler 中使用 |
|||
type MyHandler struct { |
|||
ctxManager *context.Manager |
|||
} |
|||
|
|||
func (h *MyHandler) ChatHandler(w http.ResponseWriter, r *http.Request) { |
|||
var req types.ChatRequest |
|||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { |
|||
http.Error(w, err.Error(), 400) |
|||
return |
|||
} |
|||
|
|||
// 获取或创建会话 |
|||
session := h.ctxManager.GetOrCreateSession(req.SessionID) |
|||
|
|||
// 添加用户消息 |
|||
session.AddMessage(context.Message{ |
|||
Role: "user", |
|||
Content: req.Message, |
|||
}) |
|||
|
|||
// 调用 AI 模型 |
|||
aiContext := session.GetContextForAI() |
|||
response := callAI(aiContext) |
|||
|
|||
// 添加助手消息 |
|||
session.AddMessage(context.Message{ |
|||
Role: "assistant", |
|||
Content: response, |
|||
}) |
|||
|
|||
// 保存会话 |
|||
h.ctxManager.SaveSession(req.SessionID) |
|||
|
|||
// 返回响应 |
|||
json.NewEncoder(w).Encode(types.ChatResponse{ |
|||
Response: response, |
|||
}) |
|||
} |
|||
``` |
|||
@ -0,0 +1,29 @@ |
|||
syntax = "v1" |
|||
|
|||
// ========== 类型定义 ========== |
|||
type ( |
|||
// 获取个人信息响应 |
|||
GetProfileResponse { |
|||
Id int64 `json:"id"` // 用户ID |
|||
Username string `json:"username"` // 用户名 |
|||
Email string `json:"email"` // 邮箱 |
|||
Phone string `json:"phone"` // 手机号 |
|||
Avatar string `json:"avatar"` // 头像URL |
|||
Bio string `json:"bio"` // 个人简介 |
|||
Status int `json:"status"` // 状态 1-正常 2-禁用 |
|||
CreatedAt string `json:"createdAt"` // 创建时间 |
|||
UpdatedAt string `json:"updatedAt"` // 更新时间 |
|||
} |
|||
// 更新个人资料请求 |
|||
UpdateProfileRequest { |
|||
Username string `json:"username,optional" validate:"min=3,max=32"` // 用户名 |
|||
Phone string `json:"phone,optional"` // 手机号 |
|||
Avatar string `json:"avatar,optional"` // 头像URL |
|||
Bio string `json:"bio,optional"` // 个人简介 |
|||
} |
|||
// 修改密码请求 |
|||
ChangePasswordRequest { |
|||
OldPassword string `json:"oldPassword" validate:"required,min=6,max=32"` // 旧密码 |
|||
NewPassword string `json:"newPassword" validate:"required,min=6,max=32"` // 新密码 |
|||
} |
|||
) |
|||
@ -0,0 +1,50 @@ |
|||
syntax = "v1" |
|||
|
|||
// ========== 类型定义 ========== |
|||
type ( |
|||
// 创建用户请求 |
|||
CreateUserRequest { |
|||
Username string `json:"username" validate:"required,min=3,max=32"` // 用户名 |
|||
Email string `json:"email" validate:"required,email"` // 邮箱 |
|||
Password string `json:"password" validate:"required,min=6,max=32"` // 密码 |
|||
Phone string `json:"phone,optional"` // 手机号 |
|||
} |
|||
// 用户信息 |
|||
UserInfo { |
|||
Id int64 `json:"id"` // 用户ID |
|||
Username string `json:"username"` // 用户名 |
|||
Email string `json:"email"` // 邮箱 |
|||
Phone string `json:"phone"` // 手机号 |
|||
Status int `json:"status"` // 状态 1-正常 2-禁用 |
|||
CreatedAt string `json:"createdAt"` // 创建时间 |
|||
UpdatedAt string `json:"updatedAt"` // 更新时间 |
|||
} |
|||
// 更新用户请求 |
|||
UpdateUserRequest { |
|||
Id int64 `path:"id" validate:"required,min=1"` // 用户ID |
|||
Username string `json:"username,optional"` // 用户名 |
|||
Email string `json:"email,optional"` // 邮箱 |
|||
Phone string `json:"phone,optional"` // 手机号 |
|||
Status int `json:"status,optional"` // 状态 |
|||
} |
|||
// 用户列表查询请求 |
|||
UserListRequest { |
|||
Page int `form:"page,default=1"` // 页码 |
|||
PageSize int `form:"pageSize,default=10"` // 每页数量 |
|||
Keyword string `form:"keyword,optional"` // 关键词搜索 |
|||
Status int `form:"status,optional"` // 状态筛选 |
|||
} |
|||
// 用户列表响应 |
|||
UserListResponse { |
|||
Total int64 `json:"total"` // 总数 |
|||
List []UserInfo `json:"list"` // 用户列表 |
|||
} |
|||
// 删除用户请求 |
|||
DeleteUserRequest { |
|||
Id int64 `path:"id" validate:"required,min=1"` // 用户ID |
|||
} |
|||
// 获取用户详情请求 |
|||
GetUserRequest { |
|||
Id int64 `path:"id" validate:"required,min=1"` // 用户ID |
|||
} |
|||
) |
|||
@ -0,0 +1,125 @@ |
|||
syntax = "v1" |
|||
|
|||
info ( |
|||
title: "Base 统一服务" |
|||
desc: "Base 项目统一 API 接口" |
|||
author: "author@example.com" |
|||
version: "v1.0" |
|||
) |
|||
|
|||
import "api/user.api" |
|||
import "api/profile.api" |
|||
|
|||
// ========== 通用响应类型 ========== |
|||
type ( |
|||
Response { |
|||
Code int `json:"code"` // 状态码 |
|||
Message string `json:"message"` // 消息 |
|||
Success bool `json:"success"` // 是否成功 |
|||
Data interface{} `json:"data"` // 数据 |
|||
} |
|||
// 登录响应 |
|||
LoginResponse { |
|||
Code int `json:"code"` // 状态码 |
|||
Message string `json:"message"` // 消息 |
|||
Success bool `json:"success"` // 是否成功 |
|||
Token string `json:"token"` // JWT Token |
|||
} |
|||
// 注册请求 |
|||
RegisterRequest { |
|||
Username string `json:"username" validate:"required,min=3,max=32"` // 用户名 |
|||
Email string `json:"email" validate:"required,email"` // 邮箱 |
|||
Password string `json:"password" validate:"required,min=6,max=32"` // 密码 |
|||
Phone string `json:"phone,optional"` // 手机号 |
|||
} |
|||
// 登录请求 |
|||
LoginRequest { |
|||
Email string `json:"email" validate:"required,email"` // 邮箱 |
|||
Password string `json:"password" validate:"required,min=6,max=32"` // 密码 |
|||
} |
|||
// 刷新Token请求 |
|||
RefreshTokenRequest { |
|||
Token string `json:"token" validate:"required"` // Token |
|||
} |
|||
) |
|||
|
|||
// ========== 服务定义 ========== |
|||
@server ( |
|||
prefix: /api/v1 |
|||
group: auth |
|||
middleware: Cors,Log |
|||
) |
|||
service base-api { |
|||
// ========== 认证接口 ========== |
|||
// 注册 |
|||
@doc "用户注册" |
|||
@handler register |
|||
post /register (RegisterRequest) returns (UserInfo) |
|||
|
|||
// 登录 |
|||
@doc "用户登录" |
|||
@handler login |
|||
post /login (LoginRequest) returns (LoginResponse) |
|||
|
|||
// 刷新Token |
|||
@doc "刷新Token" |
|||
@handler refreshToken |
|||
post /refresh (RefreshTokenRequest) returns (LoginResponse) |
|||
} |
|||
|
|||
@server ( |
|||
prefix: /api/v1 |
|||
group: user |
|||
middleware: Cors,Log,Auth |
|||
) |
|||
service base-api { |
|||
// ========== 用户管理接口 ========== |
|||
// 创建用户 |
|||
@doc "创建用户" |
|||
@handler createUser |
|||
post /user (CreateUserRequest) returns (UserInfo) |
|||
|
|||
// 获取用户列表 |
|||
@doc "获取用户列表" |
|||
@handler getUserList |
|||
get /users (UserListRequest) returns (UserListResponse) |
|||
|
|||
// 获取用户详情 |
|||
@doc "获取用户详情" |
|||
@handler getUser |
|||
get /user/:id (GetUserRequest) returns (UserInfo) |
|||
|
|||
// 更新用户 |
|||
@doc "更新用户信息" |
|||
@handler updateUser |
|||
put /user/:id (UpdateUserRequest) returns (UserInfo) |
|||
|
|||
// 删除用户 |
|||
@doc "删除用户" |
|||
@handler deleteUser |
|||
delete /user/:id (DeleteUserRequest) returns (Response) |
|||
} |
|||
|
|||
@server ( |
|||
prefix: /api/v1 |
|||
group: profile |
|||
middleware: Cors,Log,Auth |
|||
) |
|||
service base-api { |
|||
// ========== 个人中心接口 ========== |
|||
// 获取个人信息 |
|||
@doc "获取个人信息" |
|||
@handler getProfile |
|||
get /profile/me returns (GetProfileResponse) |
|||
|
|||
// 更新个人资料 |
|||
@doc "更新个人资料" |
|||
@handler updateProfile |
|||
put /profile/me (UpdateProfileRequest) returns (GetProfileResponse) |
|||
|
|||
// 修改密码 |
|||
@doc "修改密码" |
|||
@handler changePassword |
|||
post /profile/password (ChangePasswordRequest) returns (Response) |
|||
} |
|||
|
|||
@ -0,0 +1,34 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"flag" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/config" |
|||
"github.com/youruser/base/internal/handler" |
|||
"github.com/youruser/base/internal/svc" |
|||
|
|||
"github.com/zeromicro/go-zero/core/conf" |
|||
"github.com/zeromicro/go-zero/rest" |
|||
) |
|||
|
|||
var configFile = flag.String("f", "etc/base-api.yaml", "the config file") |
|||
|
|||
func main() { |
|||
flag.Parse() |
|||
|
|||
var c config.Config |
|||
conf.MustLoad(*configFile, &c) |
|||
|
|||
server := rest.MustNewServer(c.RestConf) |
|||
defer server.Stop() |
|||
|
|||
ctx := svc.NewServiceContext(c) |
|||
handler.RegisterHandlers(server, ctx) |
|||
|
|||
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) |
|||
server.Start() |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
Name: base-api |
|||
Host: 0.0.0.0 |
|||
Port: 8888 |
|||
|
|||
# MySQL 数据库配置 |
|||
MySQL: |
|||
DSN: root:dev123456@tcp(219.159.132.177:17173)/base?charset=utf8mb4&parseTime=true&loc=Local |
|||
@ -0,0 +1,58 @@ |
|||
# go-zero 框架 |
|||
|
|||
> go-zero 是一个高性能的微服务框架,简化了服务的开发和部署。 |
|||
|
|||
## 官方文档 |
|||
|
|||
- [官方网站](https://go-zero.dev/) |
|||
- [GitHub](https://github.com/zeromicro/zero) |
|||
- [入门指南](https://go-zero.dev/docs/introduction) |
|||
- [快速开始](https://go-zero.dev/docs/getting-started) |
|||
- [API 开发](https://go-zero.dev/docs/task/api) |
|||
- [gRPC 开发](https://go-zero.dev/docs/task/grpc) |
|||
|
|||
## 核心特性 |
|||
|
|||
- 简单易用:内置代码生成工具,快速生成服务代码 |
|||
- 性能强大:基于 Go 语言,支持高并发 |
|||
- 完整工具链:提供 API 生成、gRPC 工具、微服务治理等 |
|||
- 云原生:支持 Kubernetes、Docker 等云原生部署 |
|||
|
|||
## 项目结构 |
|||
|
|||
``` |
|||
. |
|||
├── api # API 服务目录 |
|||
│ ├── etc # 配置文件 |
|||
│ ├── internal # 业务逻辑 |
|||
│ │ ├── handler # HTTP 处理器 |
|||
│ │ ├── logic # 业务逻辑 |
|||
│ │ ├── svc // 服务上下文 |
|||
│ │ └── types // 类型定义 |
|||
│ └── main.go # 入口文件 |
|||
└── rpc # gRPC 服务目录 |
|||
├── etc # 配置文件 |
|||
├── internal # 业务逻辑 |
|||
│ ├── logic // 业务逻辑 |
|||
│ └── svr // 服务器 |
|||
└── *.proto # Protobuf 定义 |
|||
``` |
|||
|
|||
## 安装 |
|||
|
|||
```bash |
|||
# 安装 goctl 工具 |
|||
go install github.com/zeromicro/zero-tools/goctl@latest |
|||
|
|||
# 创建 API 服务 |
|||
goctl api new api |
|||
|
|||
# 创建 gRPC 服务 |
|||
goctl rpc new rpc |
|||
``` |
|||
|
|||
## 相关 AI 支持 |
|||
|
|||
- ai-context: https://github.com/zeromicro/ai-context |
|||
- mcp-zero : https://github.com/zeromicro/mcp-zero |
|||
- zero-skills : https://github.com/zeromicro/zero-skills |
|||
@ -0,0 +1,59 @@ |
|||
module github.com/youruser/base |
|||
|
|||
go 1.25.0 |
|||
|
|||
require ( |
|||
github.com/golang-jwt/jwt/v5 v5.3.1 |
|||
github.com/zeromicro/go-zero v1.9.4 |
|||
gorm.io/driver/mysql v1.6.0 |
|||
gorm.io/gorm v1.31.1 |
|||
) |
|||
|
|||
require ( |
|||
filippo.io/edwards25519 v1.1.0 // indirect |
|||
github.com/beorn7/perks v1.0.1 // indirect |
|||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect |
|||
github.com/cespare/xxhash/v2 v2.3.0 // indirect |
|||
github.com/fatih/color v1.18.0 // indirect |
|||
github.com/go-logr/logr v1.4.2 // indirect |
|||
github.com/go-logr/stdr v1.2.2 // indirect |
|||
github.com/go-sql-driver/mysql v1.9.3 // indirect |
|||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect |
|||
github.com/google/uuid v1.6.0 // indirect |
|||
github.com/grafana/pyroscope-go v1.2.7 // indirect |
|||
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect |
|||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect |
|||
github.com/jinzhu/inflection v1.0.0 // indirect |
|||
github.com/jinzhu/now v1.1.5 // indirect |
|||
github.com/klauspost/compress v1.17.11 // indirect |
|||
github.com/mattn/go-colorable v0.1.13 // indirect |
|||
github.com/mattn/go-isatty v0.0.20 // indirect |
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect |
|||
github.com/openzipkin/zipkin-go v0.4.3 // indirect |
|||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect |
|||
github.com/prometheus/client_golang v1.21.1 // indirect |
|||
github.com/prometheus/client_model v0.6.1 // indirect |
|||
github.com/prometheus/common v0.62.0 // indirect |
|||
github.com/prometheus/procfs v0.15.1 // indirect |
|||
github.com/spaolacci/murmur3 v1.1.0 // indirect |
|||
go.opentelemetry.io/otel v1.24.0 // indirect |
|||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect |
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect |
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect |
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect |
|||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect |
|||
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect |
|||
go.opentelemetry.io/otel/metric v1.24.0 // indirect |
|||
go.opentelemetry.io/otel/sdk v1.24.0 // indirect |
|||
go.opentelemetry.io/otel/trace v1.24.0 // indirect |
|||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect |
|||
go.uber.org/automaxprocs v1.6.0 // indirect |
|||
golang.org/x/net v0.35.0 // indirect |
|||
golang.org/x/sys v0.30.0 // indirect |
|||
golang.org/x/text v0.33.0 // indirect |
|||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect |
|||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect |
|||
google.golang.org/grpc v1.65.0 // indirect |
|||
google.golang.org/protobuf v1.36.5 // indirect |
|||
gopkg.in/yaml.v2 v2.4.0 // indirect |
|||
) |
|||
@ -0,0 +1,146 @@ |
|||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= |
|||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= |
|||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= |
|||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= |
|||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= |
|||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= |
|||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= |
|||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= |
|||
github.com/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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= |
|||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= |
|||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= |
|||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= |
|||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= |
|||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= |
|||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= |
|||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= |
|||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= |
|||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= |
|||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= |
|||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= |
|||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= |
|||
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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= |
|||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
|||
github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac= |
|||
github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc= |
|||
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og= |
|||
github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= |
|||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= |
|||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= |
|||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= |
|||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= |
|||
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/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= |
|||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= |
|||
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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= |
|||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= |
|||
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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= |
|||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= |
|||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= |
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= |
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= |
|||
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= |
|||
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= |
|||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= |
|||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= |
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= |
|||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= |
|||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= |
|||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= |
|||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= |
|||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= |
|||
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= |
|||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= |
|||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= |
|||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= |
|||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= |
|||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= |
|||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= |
|||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= |
|||
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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= |
|||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= |
|||
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.9.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/zeromicro/go-zero v1.9.4 h1:aRLFoISqAYijABtkbliQC5SsI5TbizJpQvoHc9xup8k= |
|||
github.com/zeromicro/go-zero v1.9.4/go.mod h1:a17JOTch25SWxBcUgJZYps60hygK3pIYdw7nGwlcS38= |
|||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= |
|||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= |
|||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= |
|||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= |
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= |
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= |
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= |
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= |
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= |
|||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= |
|||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= |
|||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= |
|||
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY= |
|||
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM= |
|||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= |
|||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= |
|||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= |
|||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= |
|||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= |
|||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= |
|||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= |
|||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= |
|||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= |
|||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= |
|||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= |
|||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= |
|||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= |
|||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= |
|||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= |
|||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
|||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= |
|||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= |
|||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= |
|||
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= |
|||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= |
|||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= |
|||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= |
|||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= |
|||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= |
|||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= |
|||
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/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= |
|||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= |
|||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= |
|||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |
|||
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/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= |
|||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= |
|||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= |
|||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= |
|||
@ -0,0 +1,15 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package config |
|||
|
|||
import "github.com/zeromicro/go-zero/rest" |
|||
|
|||
type Config struct { |
|||
rest.RestConf |
|||
|
|||
// MySQL 数据库配置
|
|||
MySQL struct { |
|||
DSN string |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code generated by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package auth |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/auth" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 用户登录
|
|||
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.LoginRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := auth.NewLoginLogic(r.Context(), svcCtx) |
|||
resp, err := l.Login(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code generated by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package auth |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/auth" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 刷新Token
|
|||
func RefreshTokenHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.RefreshTokenRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := auth.NewRefreshTokenLogic(r.Context(), svcCtx) |
|||
resp, err := l.RefreshToken(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package auth |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/auth" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 用户注册
|
|||
func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.RegisterRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := auth.NewRegisterLogic(r.Context(), svcCtx) |
|||
resp, err := l.Register(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package profile |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/profile" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 修改密码
|
|||
func ChangePasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.ChangePasswordRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := profile.NewChangePasswordLogic(r.Context(), svcCtx) |
|||
resp, err := l.ChangePassword(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package profile |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/profile" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 获取个人信息
|
|||
func GetProfileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
l := profile.NewGetProfileLogic(r.Context(), svcCtx) |
|||
resp, err := l.GetProfile() |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package profile |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/profile" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 更新个人资料
|
|||
func UpdateProfileHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.UpdateProfileRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := profile.NewUpdateProfileLogic(r.Context(), svcCtx) |
|||
resp, err := l.UpdateProfile(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,110 @@ |
|||
// Code generated by goctl. DO NOT EDIT.
|
|||
// goctl 1.9.2
|
|||
|
|||
package handler |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
auth "github.com/youruser/base/internal/handler/auth" |
|||
profile "github.com/youruser/base/internal/handler/profile" |
|||
user "github.com/youruser/base/internal/handler/user" |
|||
"github.com/youruser/base/internal/svc" |
|||
|
|||
"github.com/zeromicro/go-zero/rest" |
|||
) |
|||
|
|||
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { |
|||
server.AddRoutes( |
|||
rest.WithMiddlewares( |
|||
[]rest.Middleware{serverCtx.Cors, serverCtx.Log}, |
|||
[]rest.Route{ |
|||
{ |
|||
// 用户登录
|
|||
Method: http.MethodPost, |
|||
Path: "/login", |
|||
Handler: auth.LoginHandler(serverCtx), |
|||
}, |
|||
{ |
|||
// 刷新Token
|
|||
Method: http.MethodPost, |
|||
Path: "/refresh", |
|||
Handler: auth.RefreshTokenHandler(serverCtx), |
|||
}, |
|||
{ |
|||
// 用户注册
|
|||
Method: http.MethodPost, |
|||
Path: "/register", |
|||
Handler: auth.RegisterHandler(serverCtx), |
|||
}, |
|||
}..., |
|||
), |
|||
rest.WithPrefix("/api/v1"), |
|||
) |
|||
|
|||
server.AddRoutes( |
|||
rest.WithMiddlewares( |
|||
[]rest.Middleware{serverCtx.Cors, serverCtx.Log, serverCtx.Auth}, |
|||
[]rest.Route{ |
|||
{ |
|||
// 获取个人信息
|
|||
Method: http.MethodGet, |
|||
Path: "/profile/me", |
|||
Handler: profile.GetProfileHandler(serverCtx), |
|||
}, |
|||
{ |
|||
// 更新个人资料
|
|||
Method: http.MethodPut, |
|||
Path: "/profile/me", |
|||
Handler: profile.UpdateProfileHandler(serverCtx), |
|||
}, |
|||
{ |
|||
// 修改密码
|
|||
Method: http.MethodPost, |
|||
Path: "/profile/password", |
|||
Handler: profile.ChangePasswordHandler(serverCtx), |
|||
}, |
|||
}..., |
|||
), |
|||
rest.WithPrefix("/api/v1"), |
|||
) |
|||
|
|||
server.AddRoutes( |
|||
rest.WithMiddlewares( |
|||
[]rest.Middleware{serverCtx.Cors, serverCtx.Log, serverCtx.Auth}, |
|||
[]rest.Route{ |
|||
{ |
|||
// 创建用户
|
|||
Method: http.MethodPost, |
|||
Path: "/user", |
|||
Handler: user.CreateUserHandler(serverCtx), |
|||
}, |
|||
{ |
|||
// 获取用户详情
|
|||
Method: http.MethodGet, |
|||
Path: "/user/:id", |
|||
Handler: user.GetUserHandler(serverCtx), |
|||
}, |
|||
{ |
|||
// 更新用户信息
|
|||
Method: http.MethodPut, |
|||
Path: "/user/:id", |
|||
Handler: user.UpdateUserHandler(serverCtx), |
|||
}, |
|||
{ |
|||
// 删除用户
|
|||
Method: http.MethodDelete, |
|||
Path: "/user/:id", |
|||
Handler: user.DeleteUserHandler(serverCtx), |
|||
}, |
|||
{ |
|||
// 获取用户列表
|
|||
Method: http.MethodGet, |
|||
Path: "/users", |
|||
Handler: user.GetUserListHandler(serverCtx), |
|||
}, |
|||
}..., |
|||
), |
|||
rest.WithPrefix("/api/v1"), |
|||
) |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package user |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/user" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 创建用户
|
|||
func CreateUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.CreateUserRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := user.NewCreateUserLogic(r.Context(), svcCtx) |
|||
resp, err := l.CreateUser(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package user |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/user" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 删除用户
|
|||
func DeleteUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.DeleteUserRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := user.NewDeleteUserLogic(r.Context(), svcCtx) |
|||
resp, err := l.DeleteUser(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package user |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/user" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 获取用户详情
|
|||
func GetUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.GetUserRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := user.NewGetUserLogic(r.Context(), svcCtx) |
|||
resp, err := l.GetUser(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package user |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/user" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 获取用户列表
|
|||
func GetUserListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.UserListRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := user.NewGetUserListLogic(r.Context(), svcCtx) |
|||
resp, err := l.GetUserList(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package user |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/youruser/base/internal/logic/user" |
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/zeromicro/go-zero/rest/httpx" |
|||
) |
|||
|
|||
// 更新用户信息
|
|||
func UpdateUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
var req types.UpdateUserRequest |
|||
if err := httpx.Parse(r, &req); err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
return |
|||
} |
|||
|
|||
l := user.NewUpdateUserLogic(r.Context(), svcCtx) |
|||
resp, err := l.UpdateUser(&req) |
|||
if err != nil { |
|||
httpx.ErrorCtx(r.Context(), w, err) |
|||
} else { |
|||
httpx.OkJsonCtx(r.Context(), w, resp) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
package auth |
|||
|
|||
import ( |
|||
"context" |
|||
"crypto/md5" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/internal/util/jwt" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type LoginLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 用户登录
|
|||
func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic { |
|||
return &LoginLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginResponse, err error) { |
|||
// 查询用户
|
|||
user, err := model.FindOneByEmail(l.ctx, l.svcCtx.DB, req.Email) |
|||
if err != nil { |
|||
if err == model.ErrNotFound { |
|||
return &types.LoginResponse{ |
|||
Code: 404, |
|||
Message: "用户不存在或密码错误", |
|||
Success: false, |
|||
}, nil |
|||
} |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 加密输入的密码并与数据库密码对比
|
|||
inputPassword := fmt.Sprintf("%x", md5.Sum([]byte(req.Password))) |
|||
if user.Password != inputPassword { |
|||
return &types.LoginResponse{ |
|||
Code: 400, |
|||
Message: "用户不存在或密码错误", |
|||
Success: false, |
|||
}, nil |
|||
} |
|||
|
|||
// 生成 Token
|
|||
token, err := jwt.GenerateToken(user.Id, user.Username, user.Email) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("生成Token失败: %v", err) |
|||
} |
|||
|
|||
l.Infof("登录成功,userId=%d", user.Id) |
|||
|
|||
return &types.LoginResponse{ |
|||
Code: 200, |
|||
Message: "登录成功", |
|||
Success: true, |
|||
Token: token, |
|||
}, nil |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
package auth |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/internal/util/jwt" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type RefreshTokenLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 刷新 Token
|
|||
func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RefreshTokenLogic { |
|||
return &RefreshTokenLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *RefreshTokenLogic) RefreshToken(req *types.RefreshTokenRequest) (resp *types.LoginResponse, err error) { |
|||
// 解析并验证 Token
|
|||
claims, err := jwt.ParseToken(req.Token) |
|||
if err != nil { |
|||
return &types.LoginResponse{ |
|||
Code: 401, |
|||
Message: "Token无效或已过期", |
|||
Success: false, |
|||
}, nil |
|||
} |
|||
|
|||
// 查询用户是否存在
|
|||
user, err := model.FindOne(l.ctx, l.svcCtx.DB, claims.UserID) |
|||
if err != nil { |
|||
if err == model.ErrNotFound { |
|||
return &types.LoginResponse{ |
|||
Code: 404, |
|||
Message: "用户不存在", |
|||
Success: false, |
|||
}, nil |
|||
} |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 生成新 Token
|
|||
newToken, err := jwt.GenerateToken(user.Id, user.Username, user.Email) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("生成Token失败: %v", err) |
|||
} |
|||
|
|||
l.Infof("刷新Token成功,userId=%d", user.Id) |
|||
|
|||
return &types.LoginResponse{ |
|||
Code: 200, |
|||
Message: "刷新Token成功", |
|||
Success: true, |
|||
Token: newToken, |
|||
}, nil |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
package auth |
|||
|
|||
import ( |
|||
"context" |
|||
"crypto/md5" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type RegisterLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 用户注册
|
|||
func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic { |
|||
return &RegisterLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *RegisterLogic) Register(req *types.RegisterRequest) (resp *types.UserInfo, err error) { |
|||
// 检查邮箱是否已存在
|
|||
_, err = model.FindOneByEmail(l.ctx, l.svcCtx.DB, req.Email) |
|||
if err == nil { |
|||
return nil, fmt.Errorf("邮箱已被注册") |
|||
} |
|||
if err != model.ErrNotFound { |
|||
return nil, fmt.Errorf("检查邮箱失败: %v", err) |
|||
} |
|||
|
|||
// 创建用户模型
|
|||
user := &model.User{ |
|||
Username: req.Username, |
|||
Email: req.Email, |
|||
Password: fmt.Sprintf("%x", md5.Sum([]byte(req.Password))), // 密码加密
|
|||
Phone: req.Phone, |
|||
Status: 1, // 默认正常状态
|
|||
} |
|||
|
|||
// 插入数据库
|
|||
id, err := model.Insert(l.ctx, l.svcCtx.DB, user) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("创建用户失败: %v", err) |
|||
} |
|||
|
|||
// 查询创建的用户
|
|||
user, err = model.FindOne(l.ctx, l.svcCtx.DB, id) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 返回用户信息(不返回密码)
|
|||
resp = &types.UserInfo{ |
|||
Id: user.Id, |
|||
Username: user.Username, |
|||
Email: user.Email, |
|||
Phone: user.Phone, |
|||
Status: int(user.Status), |
|||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), |
|||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), |
|||
} |
|||
|
|||
// 返回 Token 在响应头中(通过中间件处理)
|
|||
// 临时方案:将 token 放入响应 Data 中
|
|||
l.Infof("注册成功,userId=%d", user.Id) |
|||
|
|||
return resp, nil |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
package profile |
|||
|
|||
import ( |
|||
"context" |
|||
"crypto/md5" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type ChangePasswordLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 修改密码
|
|||
func NewChangePasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ChangePasswordLogic { |
|||
return &ChangePasswordLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *ChangePasswordLogic) ChangePassword(req *types.ChangePasswordRequest) (resp *types.Response, err error) { |
|||
// 从上下文中获取当前用户ID
|
|||
userIdValue := l.ctx.Value("userId") |
|||
if userIdValue == nil { |
|||
return nil, fmt.Errorf("未获取到用户信息,请先登录") |
|||
} |
|||
userId, ok := userIdValue.(int64) |
|||
if !ok { |
|||
return nil, fmt.Errorf("用户ID格式错误") |
|||
} |
|||
|
|||
// 查询用户
|
|||
user, err := model.FindOne(l.ctx, l.svcCtx.DB, userId) |
|||
if err != nil { |
|||
if err == model.ErrNotFound { |
|||
return nil, fmt.Errorf("用户不存在") |
|||
} |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 加密旧密码
|
|||
oldEncrypted := fmt.Sprintf("%x", md5.Sum([]byte(req.OldPassword))) |
|||
if user.Password != oldEncrypted { |
|||
return &types.Response{ |
|||
Code: 400, |
|||
Message: "旧密码错误", |
|||
}, nil |
|||
} |
|||
|
|||
// 加密新密码
|
|||
newEncrypted := fmt.Sprintf("%x", md5.Sum([]byte(req.NewPassword))) |
|||
user.Password = newEncrypted |
|||
|
|||
// 更新数据库
|
|||
err = model.Update(l.ctx, l.svcCtx.DB, user) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("修改密码失败: %v", err) |
|||
} |
|||
|
|||
return &types.Response{ |
|||
Code: 200, |
|||
Message: "修改密码成功", |
|||
}, nil |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
package profile |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type GetProfileLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 获取个人信息
|
|||
func NewGetProfileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProfileLogic { |
|||
return &GetProfileLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *GetProfileLogic) GetProfile() (resp *types.GetProfileResponse, err error) { |
|||
// 从上下文中获取当前用户ID(假设通过 Auth 中间件设置)
|
|||
userIdValue := l.ctx.Value("userId") |
|||
if userIdValue == nil { |
|||
return nil, fmt.Errorf("未获取到用户信息,请先登录") |
|||
} |
|||
userId, ok := userIdValue.(int64) |
|||
if !ok { |
|||
return nil, fmt.Errorf("用户ID格式错误") |
|||
} |
|||
|
|||
// 查询用户信息
|
|||
user, err := model.FindOne(l.ctx, l.svcCtx.DB, userId) |
|||
if err != nil { |
|||
if err == model.ErrNotFound { |
|||
return nil, fmt.Errorf("用户不存在") |
|||
} |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 查询个人资料
|
|||
profile, err := model.FindProfileByUserId(l.ctx, l.svcCtx.DB, userId) |
|||
if err != nil && err != model.ErrNotFound { |
|||
return nil, fmt.Errorf("查询个人资料失败: %v", err) |
|||
} |
|||
|
|||
// 如果没有个人资料,使用默认值
|
|||
avatar := "" |
|||
bio := "" |
|||
if profile != nil { |
|||
avatar = profile.Avatar |
|||
bio = profile.Bio |
|||
} |
|||
|
|||
resp = &types.GetProfileResponse{ |
|||
Id: user.Id, |
|||
Username: user.Username, |
|||
Email: user.Email, |
|||
Phone: user.Phone, |
|||
Avatar: avatar, |
|||
Bio: bio, |
|||
Status: int(user.Status), |
|||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), |
|||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), |
|||
} |
|||
|
|||
return resp, nil |
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
package profile |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type UpdateProfileLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 更新个人资料
|
|||
func NewUpdateProfileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateProfileLogic { |
|||
return &UpdateProfileLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *UpdateProfileLogic) UpdateProfile(req *types.UpdateProfileRequest) (resp *types.GetProfileResponse, err error) { |
|||
// 从上下文中获取当前用户ID
|
|||
userIdValue := l.ctx.Value("userId") |
|||
if userIdValue == nil { |
|||
return nil, fmt.Errorf("未获取到用户信息,请先登录") |
|||
} |
|||
userId, ok := userIdValue.(int64) |
|||
if !ok { |
|||
return nil, fmt.Errorf("用户ID格式错误") |
|||
} |
|||
|
|||
// 查询用户
|
|||
user, err := model.FindOne(l.ctx, l.svcCtx.DB, userId) |
|||
if err != nil { |
|||
if err == model.ErrNotFound { |
|||
return nil, fmt.Errorf("用户不存在") |
|||
} |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 更新用户基本信息
|
|||
if req.Username != "" { |
|||
user.Username = req.Username |
|||
} |
|||
if req.Phone != "" { |
|||
user.Phone = req.Phone |
|||
} |
|||
|
|||
// 更新用户表
|
|||
err = model.Update(l.ctx, l.svcCtx.DB, user) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("更新用户信息失败: %v", err) |
|||
} |
|||
|
|||
// 查询或创建个人资料
|
|||
profile, err := model.FindProfileByUserId(l.ctx, l.svcCtx.DB, userId) |
|||
if err == model.ErrNotFound { |
|||
// 创建新的个人资料
|
|||
profile = &model.Profile{ |
|||
UserId: userId, |
|||
Avatar: req.Avatar, |
|||
Bio: req.Bio, |
|||
} |
|||
_, err = model.InsertProfile(l.ctx, l.svcCtx.DB, profile) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("创建个人资料失败: %v", err) |
|||
} |
|||
} else if err != nil { |
|||
return nil, fmt.Errorf("查询个人资料失败: %v", err) |
|||
} else { |
|||
// 更新现有个人资料
|
|||
if req.Avatar != "" { |
|||
profile.Avatar = req.Avatar |
|||
} |
|||
if req.Bio != "" { |
|||
profile.Bio = req.Bio |
|||
} |
|||
err = model.UpdateProfile(l.ctx, l.svcCtx.DB, profile) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("更新个人资料失败: %v", err) |
|||
} |
|||
} |
|||
|
|||
// 重新查询用户信息
|
|||
user, err = model.FindOne(l.ctx, l.svcCtx.DB, userId) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 查询个人资料
|
|||
profile, _ = model.FindProfileByUserId(l.ctx, l.svcCtx.DB, userId) |
|||
|
|||
avatar := "" |
|||
bio := "" |
|||
if profile != nil { |
|||
avatar = profile.Avatar |
|||
bio = profile.Bio |
|||
} |
|||
|
|||
resp = &types.GetProfileResponse{ |
|||
Id: user.Id, |
|||
Username: user.Username, |
|||
Email: user.Email, |
|||
Phone: user.Phone, |
|||
Avatar: avatar, |
|||
Bio: bio, |
|||
Status: int(user.Status), |
|||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), |
|||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), |
|||
} |
|||
|
|||
return resp, nil |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"context" |
|||
"crypto/md5" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type CreateUserLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 创建用户
|
|||
func NewCreateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateUserLogic { |
|||
return &CreateUserLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) (resp *types.UserInfo, err error) { |
|||
// 检查邮箱是否已存在
|
|||
_, err = model.FindOneByEmail(l.ctx, l.svcCtx.DB, req.Email) |
|||
if err == nil { |
|||
return nil, fmt.Errorf("邮箱已被注册") |
|||
} |
|||
if err != model.ErrNotFound { |
|||
return nil, fmt.Errorf("检查邮箱失败: %v", err) |
|||
} |
|||
|
|||
// 密码加密(简单MD5,实际项目应使用bcrypt等更安全的加密)
|
|||
encryptedPassword := fmt.Sprintf("%x", md5.Sum([]byte(req.Password))) |
|||
|
|||
// 创建用户模型
|
|||
user := &model.User{ |
|||
Username: req.Username, |
|||
Email: req.Email, |
|||
Password: encryptedPassword, |
|||
Phone: req.Phone, |
|||
Status: 1, // 默认正常状态
|
|||
} |
|||
|
|||
// 插入数据库
|
|||
id, err := model.Insert(l.ctx, l.svcCtx.DB, user) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("创建用户失败: %v", err) |
|||
} |
|||
|
|||
// 查询创建的用户
|
|||
user, err = model.FindOne(l.ctx, l.svcCtx.DB, id) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 返回用户信息(不返回密码)
|
|||
resp = &types.UserInfo{ |
|||
Id: user.Id, |
|||
Username: user.Username, |
|||
Email: user.Email, |
|||
Phone: user.Phone, |
|||
Status: int(user.Status), |
|||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), |
|||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), |
|||
} |
|||
|
|||
return resp, nil |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type DeleteUserLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 删除用户
|
|||
func NewDeleteUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteUserLogic { |
|||
return &DeleteUserLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *DeleteUserLogic) DeleteUser(req *types.DeleteUserRequest) (resp *types.Response, err error) { |
|||
// 检查用户是否存在
|
|||
user, err := model.FindOne(l.ctx, l.svcCtx.DB, req.Id) |
|||
if err != nil { |
|||
if err == model.ErrNotFound { |
|||
return &types.Response{ |
|||
Code: 404, |
|||
Message: "用户不存在", |
|||
Success: false, |
|||
}, nil |
|||
} |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 删除用户
|
|||
err = model.Delete(l.ctx, l.svcCtx.DB, req.Id) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("删除用户失败: %v", err) |
|||
} |
|||
|
|||
l.Infof("删除用户成功: id=%d, username=%s", user.Id, user.Username) |
|||
|
|||
return &types.Response{ |
|||
Code: 200, |
|||
Message: "删除成功", |
|||
Success: true, |
|||
}, nil |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type GetUserListLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 获取用户列表
|
|||
func NewGetUserListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserListLogic { |
|||
return &GetUserListLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *GetUserListLogic) GetUserList(req *types.UserListRequest) (resp *types.UserListResponse, err error) { |
|||
// 查询数据库
|
|||
users, total, err := model.FindList(l.ctx, l.svcCtx.DB, |
|||
int64(req.Page), int64(req.PageSize), req.Keyword, int64(req.Status)) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("查询用户列表失败: %v", err) |
|||
} |
|||
|
|||
// 转换为返回类型
|
|||
list := make([]types.UserInfo, 0, len(users)) |
|||
for _, user := range users { |
|||
list = append(list, types.UserInfo{ |
|||
Id: user.Id, |
|||
Username: user.Username, |
|||
Email: user.Email, |
|||
Phone: user.Phone, |
|||
Status: int(user.Status), |
|||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), |
|||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), |
|||
}) |
|||
} |
|||
|
|||
resp = &types.UserListResponse{ |
|||
Total: total, |
|||
List: list, |
|||
} |
|||
|
|||
return resp, nil |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type GetUserLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 获取用户详情
|
|||
func NewGetUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserLogic { |
|||
return &GetUserLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *GetUserLogic) GetUser(req *types.GetUserRequest) (resp *types.UserInfo, err error) { |
|||
// 根据ID查询用户
|
|||
user, err := model.FindOne(l.ctx, l.svcCtx.DB, req.Id) |
|||
if err != nil { |
|||
if err == model.ErrNotFound { |
|||
return nil, fmt.Errorf("用户不存在") |
|||
} |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 返回用户信息
|
|||
resp = &types.UserInfo{ |
|||
Id: user.Id, |
|||
Username: user.Username, |
|||
Email: user.Email, |
|||
Phone: user.Phone, |
|||
Status: int(user.Status), |
|||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), |
|||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), |
|||
} |
|||
|
|||
return resp, nil |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
|
|||
"github.com/youruser/base/internal/svc" |
|||
"github.com/youruser/base/internal/types" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"github.com/zeromicro/go-zero/core/logx" |
|||
) |
|||
|
|||
type UpdateUserLogic struct { |
|||
logx.Logger |
|||
ctx context.Context |
|||
svcCtx *svc.ServiceContext |
|||
} |
|||
|
|||
// 更新用户信息
|
|||
func NewUpdateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserLogic { |
|||
return &UpdateUserLogic{ |
|||
Logger: logx.WithContext(ctx), |
|||
ctx: ctx, |
|||
svcCtx: svcCtx, |
|||
} |
|||
} |
|||
|
|||
func (l *UpdateUserLogic) UpdateUser(req *types.UpdateUserRequest) (resp *types.UserInfo, err error) { |
|||
// 检查用户是否存在
|
|||
user, err := model.FindOne(l.ctx, l.svcCtx.DB, req.Id) |
|||
if err != nil { |
|||
if err == model.ErrNotFound { |
|||
return nil, fmt.Errorf("用户不存在") |
|||
} |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
// 更新字段
|
|||
if req.Username != "" { |
|||
user.Username = req.Username |
|||
} |
|||
if req.Email != "" { |
|||
// 检查邮箱是否被其他用户使用
|
|||
existing, err := model.FindOneByEmail(l.ctx, l.svcCtx.DB, req.Email) |
|||
if err == nil && existing.Id != req.Id { |
|||
return nil, fmt.Errorf("邮箱已被使用") |
|||
} |
|||
user.Email = req.Email |
|||
} |
|||
if req.Phone != "" { |
|||
user.Phone = req.Phone |
|||
} |
|||
if req.Status > 0 { |
|||
user.Status = int64(req.Status) |
|||
} |
|||
|
|||
// 更新数据库
|
|||
err = model.Update(l.ctx, l.svcCtx.DB, user) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("更新用户失败: %v", err) |
|||
} |
|||
|
|||
// 重新查询获取更新后的数据
|
|||
user, err = model.FindOne(l.ctx, l.svcCtx.DB, req.Id) |
|||
if err != nil { |
|||
return nil, fmt.Errorf("查询用户失败: %v", err) |
|||
} |
|||
|
|||
resp = &types.UserInfo{ |
|||
Id: user.Id, |
|||
Username: user.Username, |
|||
Email: user.Email, |
|||
Phone: user.Phone, |
|||
Status: int(user.Status), |
|||
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), |
|||
UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), |
|||
} |
|||
|
|||
return resp, nil |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
package middleware |
|||
|
|||
import ( |
|||
"context" |
|||
"net/http" |
|||
"strings" |
|||
|
|||
"github.com/youruser/base/internal/util/jwt" |
|||
) |
|||
|
|||
type AuthMiddleware struct{} |
|||
|
|||
func NewAuthMiddleware() *AuthMiddleware { |
|||
return &AuthMiddleware{} |
|||
} |
|||
|
|||
func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
// 从 Header 中获取 Token
|
|||
authHeader := r.Header.Get("Authorization") |
|||
if authHeader == "" { |
|||
http.Error(w, "Unauthorized", http.StatusUnauthorized) |
|||
return |
|||
} |
|||
|
|||
// Token 格式: "Bearer <token>"
|
|||
tokenString := strings.TrimPrefix(authHeader, "Bearer ") |
|||
if tokenString == "" { |
|||
http.Error(w, "Invalid token format", http.StatusUnauthorized) |
|||
return |
|||
} |
|||
|
|||
// 解析并验证 Token
|
|||
claims, err := jwt.ParseToken(tokenString) |
|||
if err != nil { |
|||
http.Error(w, "Invalid token: "+err.Error(), http.StatusUnauthorized) |
|||
return |
|||
} |
|||
|
|||
// 将 userId 存入上下文,供后续 logic 使用
|
|||
ctx := context.WithValue(r.Context(), "userId", claims.UserID) |
|||
ctx = context.WithValue(ctx, "username", claims.Username) |
|||
ctx = context.WithValue(ctx, "email", claims.Email) |
|||
|
|||
// 传递给下一个处理器
|
|||
next(w, r.WithContext(ctx)) |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package middleware |
|||
|
|||
import "net/http" |
|||
|
|||
type CorsMiddleware struct { |
|||
} |
|||
|
|||
func NewCorsMiddleware() *CorsMiddleware { |
|||
return &CorsMiddleware{} |
|||
} |
|||
|
|||
func (m *CorsMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
// TODO generate middleware implement function, delete after code implementation
|
|||
|
|||
// Passthrough to next handler if need
|
|||
next(w, r) |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package middleware |
|||
|
|||
import "net/http" |
|||
|
|||
type LogMiddleware struct { |
|||
} |
|||
|
|||
func NewLogMiddleware() *LogMiddleware { |
|||
return &LogMiddleware{} |
|||
} |
|||
|
|||
func (m *LogMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { |
|||
return func(w http.ResponseWriter, r *http.Request) { |
|||
// TODO generate middleware implement function, delete after code implementation
|
|||
|
|||
// Passthrough to next handler if need
|
|||
next(w, r) |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// Code scaffolded by goctl. Safe to edit.
|
|||
// goctl 1.9.2
|
|||
|
|||
package svc |
|||
|
|||
import ( |
|||
"github.com/youruser/base/internal/config" |
|||
"github.com/youruser/base/internal/middleware" |
|||
"github.com/youruser/base/model" |
|||
|
|||
"gorm.io/driver/mysql" |
|||
"gorm.io/gorm" |
|||
|
|||
"github.com/zeromicro/go-zero/rest" |
|||
) |
|||
|
|||
type ServiceContext struct { |
|||
Config config.Config |
|||
Cors rest.Middleware |
|||
Log rest.Middleware |
|||
Auth rest.Middleware |
|||
// 数据库连接
|
|||
DB *gorm.DB |
|||
} |
|||
|
|||
func NewServiceContext(c config.Config) *ServiceContext { |
|||
// 创建数据库连接
|
|||
dsn := c.MySQL.DSN |
|||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) |
|||
if err != nil { |
|||
panic("Failed to connect database: " + err.Error()) |
|||
} |
|||
|
|||
// 自动迁移表
|
|||
err = db.AutoMigrate(&model.User{}, &model.Profile{}) |
|||
if err != nil { |
|||
panic("Failed to migrate database: " + err.Error()) |
|||
} |
|||
|
|||
return &ServiceContext{ |
|||
Config: c, |
|||
Cors: middleware.NewCorsMiddleware().Handle, |
|||
Log: middleware.NewLogMiddleware().Handle, |
|||
Auth: middleware.NewAuthMiddleware().Handle, |
|||
DB: db, |
|||
} |
|||
} |
|||
|
|||
// Close 关闭资源
|
|||
func (s *ServiceContext) Close() error { |
|||
if s.DB != nil { |
|||
sqlDB, err := s.DB.DB() |
|||
if err != nil { |
|||
return err |
|||
} |
|||
return sqlDB.Close() |
|||
} |
|||
return nil |
|||
} |
|||
@ -0,0 +1,103 @@ |
|||
// Code generated by goctl. DO NOT EDIT.
|
|||
// goctl 1.9.2
|
|||
|
|||
package types |
|||
|
|||
type ChangePasswordRequest struct { |
|||
OldPassword string `json:"oldPassword" validate:"required,min=6,max=32"` // 旧密码
|
|||
NewPassword string `json:"newPassword" validate:"required,min=6,max=32"` // 新密码
|
|||
} |
|||
|
|||
type CreateUserRequest struct { |
|||
Username string `json:"username" validate:"required,min=3,max=32"` // 用户名
|
|||
Email string `json:"email" validate:"required,email"` // 邮箱
|
|||
Password string `json:"password" validate:"required,min=6,max=32"` // 密码
|
|||
Phone string `json:"phone,optional"` // 手机号
|
|||
} |
|||
|
|||
type DeleteUserRequest struct { |
|||
Id int64 `path:"id" validate:"required,min=1"` // 用户ID
|
|||
} |
|||
|
|||
type GetProfileResponse struct { |
|||
Id int64 `json:"id"` // 用户ID
|
|||
Username string `json:"username"` // 用户名
|
|||
Email string `json:"email"` // 邮箱
|
|||
Phone string `json:"phone"` // 手机号
|
|||
Avatar string `json:"avatar"` // 头像URL
|
|||
Bio string `json:"bio"` // 个人简介
|
|||
Status int `json:"status"` // 状态 1-正常 2-禁用
|
|||
CreatedAt string `json:"createdAt"` // 创建时间
|
|||
UpdatedAt string `json:"updatedAt"` // 更新时间
|
|||
} |
|||
|
|||
type GetUserRequest struct { |
|||
Id int64 `path:"id" validate:"required,min=1"` // 用户ID
|
|||
} |
|||
|
|||
type LoginRequest struct { |
|||
Email string `json:"email" validate:"required,email"` // 邮箱
|
|||
Password string `json:"password" validate:"required,min=6,max=32"` // 密码
|
|||
} |
|||
|
|||
type LoginResponse struct { |
|||
Code int `json:"code"` // 状态码
|
|||
Message string `json:"message"` // 消息
|
|||
Success bool `json:"success"` // 是否成功
|
|||
Token string `json:"token"` // JWT Token
|
|||
} |
|||
|
|||
type RefreshTokenRequest struct { |
|||
Token string `json:"token" validate:"required"` // Token
|
|||
} |
|||
|
|||
type RegisterRequest struct { |
|||
Username string `json:"username" validate:"required,min=3,max=32"` // 用户名
|
|||
Email string `json:"email" validate:"required,email"` // 邮箱
|
|||
Password string `json:"password" validate:"required,min=6,max=32"` // 密码
|
|||
Phone string `json:"phone,optional"` // 手机号
|
|||
} |
|||
|
|||
type Response struct { |
|||
Code int `json:"code"` // 状态码
|
|||
Message string `json:"message"` // 消息
|
|||
Success bool `json:"success"` // 是否成功
|
|||
Data interface{} `json:"data"` // 数据
|
|||
} |
|||
|
|||
type UpdateProfileRequest struct { |
|||
Username string `json:"username,optional" validate:"min=3,max=32"` // 用户名
|
|||
Phone string `json:"phone,optional"` // 手机号
|
|||
Avatar string `json:"avatar,optional"` // 头像URL
|
|||
Bio string `json:"bio,optional"` // 个人简介
|
|||
} |
|||
|
|||
type UpdateUserRequest struct { |
|||
Id int64 `path:"id" validate:"required,min=1"` // 用户ID
|
|||
Username string `json:"username,optional"` // 用户名
|
|||
Email string `json:"email,optional"` // 邮箱
|
|||
Phone string `json:"phone,optional"` // 手机号
|
|||
Status int `json:"status,optional"` // 状态
|
|||
} |
|||
|
|||
type UserInfo struct { |
|||
Id int64 `json:"id"` // 用户ID
|
|||
Username string `json:"username"` // 用户名
|
|||
Email string `json:"email"` // 邮箱
|
|||
Phone string `json:"phone"` // 手机号
|
|||
Status int `json:"status"` // 状态 1-正常 2-禁用
|
|||
CreatedAt string `json:"createdAt"` // 创建时间
|
|||
UpdatedAt string `json:"updatedAt"` // 更新时间
|
|||
} |
|||
|
|||
type UserListRequest struct { |
|||
Page int `form:"page,default=1"` // 页码
|
|||
PageSize int `form:"pageSize,default=10"` // 每页数量
|
|||
Keyword string `form:"keyword,optional"` // 关键词搜索
|
|||
Status int `form:"status,optional"` // 状态筛选
|
|||
} |
|||
|
|||
type UserListResponse struct { |
|||
Total int64 `json:"total"` // 总数
|
|||
List []UserInfo `json:"list"` // 用户列表
|
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
package jwt |
|||
|
|||
import ( |
|||
"errors" |
|||
"time" |
|||
|
|||
"github.com/golang-jwt/jwt/v5" |
|||
) |
|||
|
|||
var ( |
|||
// Token 有效期(7天)
|
|||
TokenExpireTime = time.Hour * 24 * 7 |
|||
|
|||
// 签名
|
|||
SigningKey = []byte("base-jwt-secret-key-2024") |
|||
) |
|||
|
|||
type Claims struct { |
|||
UserID int64 `json:"userId"` |
|||
Username string `json:"username"` |
|||
Email string `json:"email"` |
|||
jwt.RegisteredClaims |
|||
} |
|||
|
|||
// GenerateToken 生成 JWT Token
|
|||
func GenerateToken(userId int64, username, email string) (string, error) { |
|||
claims := Claims{ |
|||
UserID: userId, |
|||
Username: username, |
|||
Email: email, |
|||
RegisteredClaims: jwt.RegisteredClaims{ |
|||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpireTime)), |
|||
Issuer: "base-api", |
|||
}, |
|||
} |
|||
|
|||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
|||
return token.SignedString(SigningKey) |
|||
} |
|||
|
|||
// ParseToken 解析并验证 JWT Token
|
|||
func ParseToken(tokenString string) (*Claims, error) { |
|||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { |
|||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { |
|||
return nil, errors.New("invalid signing method") |
|||
} |
|||
return SigningKey, nil |
|||
}) |
|||
|
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
if claims, ok := token.Claims.(*Claims); ok && token.Valid { |
|||
return claims, nil |
|||
} |
|||
|
|||
return nil, errors.New("invalid token") |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
# MCP Zero - Model Context Protocol for Go |
|||
|
|||
> mcp-zero 是 go-zero 的 Model Context Protocol (MCP) 实现,用于 AI 上下文管理和集成。 |
|||
|
|||
## 官方资源 |
|||
|
|||
- [GitHub](https://github.com/zeromicro/mcp-zero) |
|||
- [MCP 规范](https://modelcontextprotocol.io/) |
|||
|
|||
## 什么是 MCP |
|||
|
|||
Model Context Protocol (MCP) 是一种开放协议,用于在 AI 模型和外部工具/数据源之间建立标准化的通信方式。 |
|||
|
|||
## 功能特性 |
|||
|
|||
- 标准化的上下文传输协议 |
|||
- 支持多种数据源的连接 |
|||
- 可扩展的工具调用机制 |
|||
- 类型安全的 Go 实现 |
|||
|
|||
## 安装 |
|||
|
|||
```bash |
|||
go get github.com/zeromicro/mcp-zero |
|||
``` |
|||
|
|||
## 基本使用 |
|||
|
|||
```go |
|||
import "github.com/zeromicro/mcp-zero" |
|||
|
|||
// 初始化 MCP 客户端 |
|||
client := mcp.NewClient(mcp.Config{ |
|||
Host: "localhost", |
|||
Port: 8080, |
|||
}) |
|||
|
|||
// 注册工具 |
|||
client.RegisterTool("my_tool", func(ctx context.Context, args mcp.ToolArgs) (interface{}, error) { |
|||
// 工具逻辑 |
|||
return nil, nil |
|||
}) |
|||
``` |
|||
|
|||
## 配置示例 |
|||
|
|||
```yaml |
|||
mcp: |
|||
enabled: true |
|||
server: |
|||
host: "0.0.0.0" |
|||
port: 8080 |
|||
tools: |
|||
- name: "database_query" |
|||
description: "Query database" |
|||
enabled: true |
|||
- name: "api_call" |
|||
description: "Make API calls" |
|||
enabled: true |
|||
``` |
|||
@ -0,0 +1,20 @@ |
|||
package model |
|||
|
|||
import ( |
|||
"time" |
|||
) |
|||
|
|||
// Profile 用户个人资料模型
|
|||
type Profile struct { |
|||
Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` |
|||
UserId int64 `gorm:"column:user_id;not null;uniqueIndex" json:"userId"` // 关联用户ID
|
|||
Avatar string `gorm:"column:avatar;type:varchar(255);default:''" json:"avatar"` // 头像URL
|
|||
Bio string `gorm:"column:bio;type:text" json:"bio"` // 个人简介
|
|||
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"createdAt"` |
|||
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updatedAt"` |
|||
} |
|||
|
|||
// TableName 指定表名
|
|||
func (Profile) TableName() string { |
|||
return "profile" |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
package model |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
// InsertProfile 插入个人资料
|
|||
func InsertProfile(ctx context.Context, db *gorm.DB, profile *Profile) (int64, error) { |
|||
result := db.WithContext(ctx).Create(profile) |
|||
if result.Error != nil { |
|||
return 0, result.Error |
|||
} |
|||
return profile.Id, nil |
|||
} |
|||
|
|||
// FindOneProfile 根据ID查询个人资料
|
|||
func FindOneProfile(ctx context.Context, db *gorm.DB, id int64) (*Profile, error) { |
|||
var profile Profile |
|||
result := db.WithContext(ctx).First(&profile, id) |
|||
if result.Error != nil { |
|||
if errors.Is(result.Error, gorm.ErrRecordNotFound) { |
|||
return nil, ErrNotFound |
|||
} |
|||
return nil, result.Error |
|||
} |
|||
return &profile, nil |
|||
} |
|||
|
|||
// FindProfileByUserId 根据用户ID查询个人资料
|
|||
func FindProfileByUserId(ctx context.Context, db *gorm.DB, userId int64) (*Profile, error) { |
|||
var profile Profile |
|||
result := db.WithContext(ctx).Where("user_id = ?", userId).First(&profile) |
|||
if result.Error != nil { |
|||
if errors.Is(result.Error, gorm.ErrRecordNotFound) { |
|||
return nil, ErrNotFound |
|||
} |
|||
return nil, result.Error |
|||
} |
|||
return &profile, nil |
|||
} |
|||
|
|||
// UpdateProfile 更新个人资料
|
|||
func UpdateProfile(ctx context.Context, db *gorm.DB, profile *Profile) error { |
|||
return db.WithContext(ctx).Save(profile).Error |
|||
} |
|||
|
|||
// DeleteProfile 删除个人资料
|
|||
func DeleteProfile(ctx context.Context, db *gorm.DB, id int64) error { |
|||
return db.WithContext(ctx).Delete(&Profile{}, id).Error |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
package model |
|||
|
|||
import ( |
|||
"time" |
|||
) |
|||
|
|||
// User 用户模型
|
|||
type User struct { |
|||
Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` |
|||
Username string `gorm:"column:username;type:varchar(32);not null" json:"username"` |
|||
Email string `gorm:"column:email;type:varchar(128);not null" json:"email"` |
|||
Password string `gorm:"column:password;type:varbinary(64);not null" json:"-"` |
|||
Phone string `gorm:"column:phone;type:varchar(20);default:''" json:"phone"` |
|||
Status int64 `gorm:"column:status;type:tinyint;default:1" json:"status"` |
|||
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"createdAt"` |
|||
UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updatedAt"` |
|||
} |
|||
|
|||
// TableName 指定表名
|
|||
func (User) TableName() string { |
|||
return "user" |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
package model |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
var ( |
|||
ErrNotFound = errors.New("record not found") |
|||
) |
|||
|
|||
// Insert 插入用户
|
|||
func Insert(ctx context.Context, db *gorm.DB, user *User) (int64, error) { |
|||
result := db.WithContext(ctx).Create(user) |
|||
if result.Error != nil { |
|||
return 0, result.Error |
|||
} |
|||
return user.Id, nil |
|||
} |
|||
|
|||
// FindOne 根据ID查询用户
|
|||
func FindOne(ctx context.Context, db *gorm.DB, id int64) (*User, error) { |
|||
var user User |
|||
result := db.WithContext(ctx).First(&user, id) |
|||
if result.Error != nil { |
|||
if errors.Is(result.Error, gorm.ErrRecordNotFound) { |
|||
return nil, ErrNotFound |
|||
} |
|||
return nil, result.Error |
|||
} |
|||
return &user, nil |
|||
} |
|||
|
|||
// FindOneByEmail 根据邮箱查询用户
|
|||
func FindOneByEmail(ctx context.Context, db *gorm.DB, email string) (*User, error) { |
|||
var user User |
|||
result := db.WithContext(ctx).Where("email = ?", email).First(&user) |
|||
if result.Error != nil { |
|||
if errors.Is(result.Error, gorm.ErrRecordNotFound) { |
|||
return nil, ErrNotFound |
|||
} |
|||
return nil, result.Error |
|||
} |
|||
return &user, nil |
|||
} |
|||
|
|||
// Update 更新用户
|
|||
func Update(ctx context.Context, db *gorm.DB, user *User) error { |
|||
result := db.WithContext(ctx).Save(user) |
|||
return result.Error |
|||
} |
|||
|
|||
// Delete 删除用户
|
|||
func Delete(ctx context.Context, db *gorm.DB, id int64) error { |
|||
result := db.WithContext(ctx).Delete(&User{}, id) |
|||
return result.Error |
|||
} |
|||
|
|||
// FindList 查询用户列表
|
|||
func FindList(ctx context.Context, db *gorm.DB, page, pageSize int64, keyword string, status int64) ([]User, int64, error) { |
|||
var users []User |
|||
var total int64 |
|||
|
|||
query := db.WithContext(ctx).Model(&User{}) |
|||
|
|||
// 关键字搜索
|
|||
if keyword != "" { |
|||
query = query.Where("username LIKE ? OR email LIKE ? OR phone LIKE ?", |
|||
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%") |
|||
} |
|||
|
|||
// 状态筛选
|
|||
if status > 0 { |
|||
query = query.Where("status = ?", status) |
|||
} |
|||
|
|||
// 统计总数
|
|||
if err := query.Count(&total).Error; err != nil { |
|||
return nil, 0, err |
|||
} |
|||
|
|||
// 分页查询
|
|||
offset := (page - 1) * pageSize |
|||
if offset < 0 { |
|||
offset = 0 |
|||
} |
|||
err := query.Offset(int(offset)).Limit(int(pageSize)).Find(&users).Error |
|||
if err != nil { |
|||
return nil, 0, err |
|||
} |
|||
|
|||
return users, total, nil |
|||
} |
|||
@ -0,0 +1,333 @@ |
|||
# User 模块测试标准流程 |
|||
|
|||
## 基础测试流程 |
|||
|
|||
每个模块应遵循以下基础测试流程: |
|||
|
|||
``` |
|||
新增(创建) -> 查询(单条) -> 更新 -> 查询(验证更新) -> 列表查询 -> 删除 -> 查询(验证删除) |
|||
``` |
|||
|
|||
## 测试步骤详解 |
|||
|
|||
### 1. 新增(创建) |
|||
创建新记录并返回 ID |
|||
|
|||
**验证点:** |
|||
- 记录成功插入数据库 |
|||
- 返回正确的 ID |
|||
- 必填字段验证通过 |
|||
- 重复数据检查(如邮箱唯一) |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X POST http://localhost:8888/api/v1/user \ |
|||
-H "Content-Type: application/json" \ |
|||
-d '{ |
|||
"username": "testuser01", |
|||
"email": "test01@example.com", |
|||
"password": "password123", |
|||
"phone": "13800138000" |
|||
}' |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 2. 查询(单条) |
|||
使用步骤 1 返回的 ID 查询记录 |
|||
|
|||
**验证点:** |
|||
- 返回完整记录信息 |
|||
- 字段值正确 |
|||
- 状态码为 200 |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X GET http://localhost:8888/api/v1/user/1 |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 3. 更新 |
|||
使用 ID 更新记录的部分字段 |
|||
|
|||
**验证点:** |
|||
- 字段值正确更新 |
|||
- 返回更新后的完整记录 |
|||
- 其他字段保持不变 |
|||
- 数据验证(如邮箱唯一性) |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X PUT http://localhost:8888/api/v1/user/1 \ |
|||
-H "Content-Type: application/json" \ |
|||
-d '{ |
|||
"username": "testuser01_updated", |
|||
"phone": "13900139000" |
|||
}' |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 4. 查询(验证更新) |
|||
再次使用 ID 查询记录,验证更新结果 |
|||
|
|||
**验证点:** |
|||
- 更新的字段值正确 |
|||
- 未更新的字段值保持不变 |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X GET http://localhost:8888/api/v1/user/1 |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 5. 列表查询 |
|||
查询记录列表,支持分页和筛选 |
|||
|
|||
**验证点:** |
|||
- 返回列表数据 |
|||
- 分页参数生效 |
|||
- 筛选条件生效 |
|||
- 总数统计正确 |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
# 查询所有 |
|||
curl -X GET "http://localhost:8888/api/v1/users?page=1&pageSize=10" |
|||
|
|||
# 带关键词搜索 |
|||
curl -X GET "http://localhost:8888/api/v1/users?keyword=test&status=1" |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 6. 删除 |
|||
使用 ID 删除记录 |
|||
|
|||
**验证点:** |
|||
- 删除成功 |
|||
- 返回正确的响应格式 |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X DELETE http://localhost:8888/api/v1/user/1 |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 7. 查询(验证删除) |
|||
再次使用 ID 查询记录,验证已删除 |
|||
|
|||
**验证点:** |
|||
- 返回 404 或相应错误信息 |
|||
- 记录确实已不存在 |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X GET http://localhost:8888/api/v1/user/1 |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## 测试脚本模板 |
|||
|
|||
### Bash 脚本模板 |
|||
|
|||
```bash |
|||
#!/bin/bash |
|||
|
|||
# 配置 |
|||
BASE_URL="http://localhost:8888/api/v1" |
|||
MODULE="user" |
|||
|
|||
# 日志函数 |
|||
log_info() { |
|||
echo -e "\033[32m[INFO]\033[0m $1" |
|||
} |
|||
|
|||
log_success() { |
|||
echo -e "\033[32m[SUCCESS]\033[0m $1" |
|||
} |
|||
|
|||
log_error() { |
|||
echo -e "\033[31m[ERROR]\033[0m $1" |
|||
} |
|||
|
|||
# 1. 新增 |
|||
log_info "步骤 1: 新增 $MODULE" |
|||
CREATE_RESULT=$(curl -s -X POST ${BASE_URL}/${MODULE} \ |
|||
-H "Content-Type: application/json" \ |
|||
-d '{ |
|||
"username": "test_'$(date +%s)'", |
|||
"email": "test_'$(date +%s)'@example.com", |
|||
"password": "password123", |
|||
"phone": "13800138000" |
|||
}') |
|||
|
|||
echo $CREATE_RESULT | jq '.' |
|||
ID=$(echo $CREATE_RESULT | jq -r '.id') |
|||
|
|||
# 2. 查询 |
|||
log_info "步骤 2: 查询 $MODULE (ID: $ID)" |
|||
QUERY_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/${ID}) |
|||
echo $QUERY_RESULT | jq '.' |
|||
|
|||
# 3. 更新 |
|||
log_info "步骤 3: 更新 $MODULE" |
|||
UPDATE_RESULT=$(curl -s -X PUT ${BASE_URL}/${MODULE}/${ID} \ |
|||
-H "Content-Type: application/json" \ |
|||
-d '{ |
|||
"phone": "13900139000" |
|||
}') |
|||
echo $UPDATE_RESULT | jq '.' |
|||
|
|||
# 4. 验证更新 |
|||
log_info "步骤 4: 验证更新" |
|||
VERIFY_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/${ID}) |
|||
echo $VERIFY_RESULT | jq '.' |
|||
|
|||
# 5. 列表查询 |
|||
log_info "步骤 5: 列表查询" |
|||
LIST_RESULT=$(curl -s -X GET "${BASE_URL}/${MODULE}s?page=1&pageSize=10") |
|||
echo $LIST_RESULT | jq '.' |
|||
|
|||
# 6. 删除 |
|||
log_info "步骤 6: 删除 $MODULE" |
|||
DELETE_RESULT=$(curl -s -X DELETE ${BASE_URL}/${MODULE}/${ID}) |
|||
echo $DELETE_RESULT | jq '.' |
|||
|
|||
# 7. 验证删除 |
|||
log_info "步骤 7: 验证删除" |
|||
VERIFY_DELETE_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/${ID}) |
|||
echo $VERIFY_DELETE_RESULT | jq '.' |
|||
|
|||
log_success "测试完成" |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## 测试报告格式 |
|||
|
|||
每个模块测试完成后,应输出测试报告: |
|||
|
|||
``` |
|||
=== [模块名] 测试报告 === |
|||
|
|||
步骤 结果 备注 |
|||
------------------------ |
|||
新增 [通过/失败] |
|||
查询(新增后) [通过/失败] |
|||
更新 [通过/失败] |
|||
查询(更新后) [通过/失败] |
|||
列表查询 [通过/失败] |
|||
删除 [通过/失败] |
|||
查询(删除后) [通过/失败] |
|||
|
|||
总体结果: [全部通过/部分失败/全部失败] |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## Profile 模块测试流程 |
|||
|
|||
``` |
|||
获取 -> 更新 -> 查询(验证) -> 修改密码 |
|||
``` |
|||
|
|||
### 步骤详解 |
|||
|
|||
### 1. 获取个人信息 |
|||
获取当前登录用户的个人信息 |
|||
|
|||
**验证点:** |
|||
- 返回完整用户信息 |
|||
- 头像和简介字段正确 |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X GET http://localhost:8888/api/v1/profile/me |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 2. 更新个人资料 |
|||
更新用户名、手机号、头像、简介等字段 |
|||
|
|||
**验证点:** |
|||
- 字段值正确更新 |
|||
- 返回更新后的完整信息 |
|||
- 未更新字段值保持不变 |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X PUT http://localhost:8888/api/v1/profile/me \ |
|||
-H "Content-Type: application/json" \ |
|||
-d '{ |
|||
"username": "updated_user", |
|||
"phone": "13900139000", |
|||
"avatar": "http://example.com/avatar.jpg", |
|||
"bio": "个人简介" |
|||
}' |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 3. 查询个人信息 (验证更新) |
|||
再次获取个人信息,验证更新结果 |
|||
|
|||
**验证点:** |
|||
- 更新的字段值正确 |
|||
- 未更新的字段值保持不变 |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X GET http://localhost:8888/api/v1/profile/me |
|||
``` |
|||
|
|||
--- |
|||
|
|||
### 4. 修改密码 |
|||
修改用户密码 |
|||
|
|||
**验证点:** |
|||
- 返回正确的成功响应 |
|||
- 错误密码返回正确错误信息 |
|||
|
|||
**示例请求:** |
|||
```bash |
|||
curl -X POST http://localhost:8888/api/v1/profile/password \ |
|||
-H "Content-Type: application/json" \ |
|||
-d '{ |
|||
"oldPassword": "password123", |
|||
"newPassword": "newpassword123" |
|||
}' |
|||
``` |
|||
|
|||
**注意:** 修改密码后需要重新登录,完整验证流程需要实现登录接口并获取新 token。 |
|||
|
|||
--- |
|||
|
|||
## 测试注意事项 |
|||
|
|||
1. **环境准备** |
|||
- 确保服务已启动 |
|||
- 数据库已连接 |
|||
- 端口可访问 |
|||
|
|||
2. **数据隔离** |
|||
- 每次测试使用不同的测试数据 |
|||
- 避免与其他测试产生冲突 |
|||
- 建议使用时间戳或随机数生成唯一数据 |
|||
|
|||
3. **错误处理** |
|||
- 验证错误提示信息正确 |
|||
- 验证 HTTP 状态码 |
|||
- 验证响应格式 |
|||
|
|||
4. **边界条件** |
|||
- 测试必填字段缺失 |
|||
- 测试数据格式错误(如邮箱格式) |
|||
- 测试字段长度限制 |
|||
- 测试重复数据(唯一性验证) |
|||
@ -0,0 +1,193 @@ |
|||
#!/bin/bash |
|||
|
|||
# Profile 模块测试脚本(含完整认证流程)- 不依赖 jq |
|||
# 测试流程: 注册 -> 登录 -> 获取个人信息 -> 更新 -> 查询(验证) -> 修改密码 -> 使用新token验证 |
|||
|
|||
# 配置 |
|||
BASE_URL="http://localhost:8888/api/v1" |
|||
MODULE="profile" |
|||
TIMESTAMP=$(date +%s) |
|||
|
|||
# 日志函数 |
|||
log_info() { |
|||
echo -e "\033[32m[INFO]\033[0m $1" |
|||
} |
|||
|
|||
log_success() { |
|||
echo -e "\033[32m[SUCCESS]\033[0m $1" |
|||
} |
|||
|
|||
log_error() { |
|||
echo -e "\033[31m[ERROR]\033[0m $1" |
|||
} |
|||
|
|||
log_step() { |
|||
echo -e "\n\033[36m--- 步骤 $1: $2 ---\033[0m" |
|||
} |
|||
|
|||
# 存储登录后获取的 Token |
|||
AUTH_TOKEN="" |
|||
|
|||
# 1. 注册新用户 |
|||
log_step "1" "注册用户" |
|||
REGISTER_RESULT=$(curl -s -X POST ${BASE_URL}/register \ |
|||
-H "Content-Type: application/json" \ |
|||
-d "{ |
|||
\"username\": \"testuser${TIMESTAMP}\", |
|||
\"email\": \"test${TIMESTAMP}@example.com\", |
|||
\"password\": \"password123\", |
|||
\"phone\": \"13800138000\" |
|||
}") |
|||
|
|||
# 解析注册响应,获取用户ID |
|||
REGISTER_ID=$(echo "$REGISTER_RESULT" | grep -o '"id":[^,}]*' | sed 's/"id"://' | tr -d ' ') |
|||
if [ -z "$REGISTER_ID" ]; then |
|||
log_error "注册失败" |
|||
echo "$REGISTER_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "注册成功,ID: $REGISTER_ID" |
|||
echo "$REGISTER_RESULT" |
|||
|
|||
# 2. 登录 |
|||
log_step "2" "用户登录" |
|||
LOGIN_RESULT=$(curl -s -X POST ${BASE_URL}/login \ |
|||
-H "Content-Type: application/json" \ |
|||
-d "{ |
|||
\"email\": \"test${TIMESTAMP}@example.com\", |
|||
\"password\": \"password123\" |
|||
}") |
|||
|
|||
# 解析并存储 Token |
|||
AUTH_TOKEN=$(echo "$LOGIN_RESULT" | grep -o '"token":"[^"]*"' | sed 's/"token":"//' | sed 's/"$//') |
|||
LOGIN_CODE=$(echo "$LOGIN_RESULT" | grep -o '"code":[^,}]*' | sed 's/"code"://' | tr -d ' ') |
|||
|
|||
if [ "$LOGIN_CODE" != "200" ] || [ -z "$AUTH_TOKEN" ]; then |
|||
log_error "登录失败" |
|||
echo "$LOGIN_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "登录成功,Token: ${AUTH_TOKEN:0:20}..." |
|||
echo "$LOGIN_RESULT" |
|||
|
|||
# 3. 获取个人信息(带Token) |
|||
log_step "3" "获取个人信息(带Token)" |
|||
GET_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/me \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
|
|||
# 验证响应 |
|||
ID=$(echo "$GET_RESULT" | grep -o '"id":[^,}]*' | sed 's/"id"://' | tr -d ' ') |
|||
if [ -z "$ID" ]; then |
|||
log_error "获取失败:可能Token无效" |
|||
echo "$GET_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "获取成功,ID: $ID" |
|||
echo "$GET_RESULT" |
|||
|
|||
# 4. 更新个人资料 |
|||
log_step "4" "更新个人资料" |
|||
UPDATE_RESULT=$(curl -s -X PUT ${BASE_URL}/${MODULE}/me \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${AUTH_TOKEN}" \ |
|||
-d "{ |
|||
\"username\": \"testuser${TIMESTAMP}_updated\", |
|||
\"phone\": \"13900139000\", |
|||
\"avatar\": \"http://example.com/avatar${TIMESTAMP}.jpg\", |
|||
\"bio\": \"这是个人简介${TIMESTAMP}\" |
|||
}") |
|||
|
|||
# 验证更新 |
|||
VERIFY_USERNAME=$(echo "$UPDATE_RESULT" | grep -o '"username":"[^"]*"' | sed 's/"username":"//' | sed 's/"$//') |
|||
EXPECTED_USERNAME="testuser${TIMESTAMP}_updated" |
|||
if [ "$VERIFY_USERNAME" != "$EXPECTED_USERNAME" ]; then |
|||
log_error "更新失败:用户名未更新" |
|||
echo "$UPDATE_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "更新成功" |
|||
echo "$UPDATE_RESULT" |
|||
|
|||
# 5. 查询个人信息(验证更新) |
|||
log_step "5" "查询个人信息(验证更新)" |
|||
VERIFY_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/me \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
|
|||
VERIFY_USERNAME=$(echo "$VERIFY_RESULT" | grep -o '"username":"[^"]*"' | sed 's/"username":"//' | sed 's/"$//') |
|||
if [ "$VERIFY_USERNAME" != "$EXPECTED_USERNAME" ]; then |
|||
log_error "验证失败:用户名未更新" |
|||
echo "$VERIFY_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "验证成功" |
|||
|
|||
# 6. 修改密码(使用旧密码) |
|||
log_step "6" "修改密码(使用旧密码)" |
|||
OLD_PASSWORD_RESULT=$(curl -s -X POST ${BASE_URL}/${MODULE}/password \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${AUTH_TOKEN}" \ |
|||
-d "{ |
|||
\"oldPassword\": \"password123\", |
|||
\"newPassword\": \"newpassword123\" |
|||
}") |
|||
|
|||
# 验证修改 |
|||
OLD_PASSWORD_CODE=$(echo "$OLD_PASSWORD_RESULT" | grep -o '"code":[^,}]*' | sed 's/"code"://' | tr -d ' ') |
|||
if [ "$OLD_PASSWORD_CODE" != "200" ]; then |
|||
log_error "修改密码失败" |
|||
echo "$OLD_PASSWORD_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "修改密码成功" |
|||
echo "$OLD_PASSWORD_RESULT" |
|||
|
|||
# 7. 使用新密码重新登录 |
|||
log_step "7" "使用新密码重新登录" |
|||
NEW_LOGIN_RESULT=$(curl -s -X POST ${BASE_URL}/login \ |
|||
-H "Content-Type: application/json" \ |
|||
-d "{ |
|||
\"email\": \"test${TIMESTAMP}@example.com\", |
|||
\"password\": \"newpassword123\" |
|||
}") |
|||
|
|||
# 获取新Token |
|||
NEW_AUTH_TOKEN=$(echo "$NEW_LOGIN_RESULT" | grep -o '"token":"[^"]*"' | sed 's/"token":"//' | sed 's/"$//') |
|||
NEW_LOGIN_CODE=$(echo "$NEW_LOGIN_RESULT" | grep -o '"code":[^,}]*' | sed 's/"code"://' | tr -d ' ') |
|||
|
|||
if [ "$NEW_LOGIN_CODE" != "200" ] || [ -z "$NEW_AUTH_TOKEN" ]; then |
|||
log_error "重新登录失败" |
|||
echo "$NEW_LOGIN_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "重新登录成功,新Token: ${NEW_AUTH_TOKEN:0:20}..." |
|||
echo "$NEW_LOGIN_RESULT" |
|||
|
|||
# 8. 获取个人信息(验证密码修改成功) |
|||
log_step "8" "获取个人信息(验证密码修改成功)" |
|||
FINAL_GET_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/me \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${NEW_AUTH_TOKEN}") |
|||
|
|||
# 验证响应 |
|||
ID=$(echo "$FINAL_GET_RESULT" | grep -o '"id":[^,}]*' | sed 's/"id"://' | tr -d ' ') |
|||
if [ -z "$ID" ]; then |
|||
log_error "获取失败:新Token可能无效" |
|||
echo "$FINAL_GET_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "验证成功,密码修改完成" |
|||
|
|||
echo -e "\n\033[32m=== Profile 模块测试完成 ===\033[0m" |
|||
echo "用户ID: $REGISTER_ID" |
|||
echo "测试账号: test${TIMESTAMP}@example.com" |
|||
echo "测试密码: password123 / newpassword123" |
|||
@ -0,0 +1,207 @@ |
|||
#!/bin/bash |
|||
|
|||
# Profile 模块测试脚本(含完整认证流程)- 不依赖 jq |
|||
# 测试流程: 注册 -> 登录 -> 获取个人信息 -> 更新 -> 查询(验证) -> 修改密码 -> 使用新token验证 |
|||
|
|||
# 日志函数 |
|||
log_info() { |
|||
echo -e "\033[32m[INFO]\033[0m $1" |
|||
} |
|||
|
|||
log_success() { |
|||
echo -e "\033[32m[SUCCESS]\033[0m $1" |
|||
} |
|||
|
|||
log_error() { |
|||
echo -e "\033[31m[ERROR]\033[0m $1" |
|||
} |
|||
|
|||
log_step() { |
|||
echo -e "\n\033[36m--- 步骤 $1: $2 ---\033[0m" |
|||
} |
|||
|
|||
# 简单 JSON 提取函数 |
|||
extract_value() { |
|||
local json="$1" |
|||
local key="$2" |
|||
# 匹配 "key":value 或 "key": "value" |
|||
echo "$json" | grep -o "\"$key\":[^,}]*" | sed 's/"'"$key'"://' | sed 's/^"//' | sed 's/"$//' | tr -d ' ' |
|||
} |
|||
|
|||
extract_int() { |
|||
local json="$1" |
|||
local key="$2" |
|||
echo "$json" | grep -o "\"$key\":[^,}]*" | sed 's/"'"$key'"://' | tr -d ' ' |
|||
} |
|||
|
|||
# 配置 |
|||
BASE_URL="http://localhost:8888/api/v1" |
|||
MODULE="profile" |
|||
TIMESTAMP=$(date +%s) |
|||
|
|||
# 存储登录后获取的 Token |
|||
AUTH_TOKEN="" |
|||
|
|||
# 1. 注册新用户 |
|||
log_step "1" "注册用户" |
|||
REGISTER_RESULT=$(curl -s -X POST ${BASE_URL}/register \ |
|||
-H "Content-Type: application/json" \ |
|||
-d "{ |
|||
\"username\": \"testuser${TIMESTAMP}\", |
|||
\"email\": \"test${TIMESTAMP}@example.com\", |
|||
\"password\": \"password123\", |
|||
\"phone\": \"13800138000\" |
|||
}") |
|||
|
|||
# 解析注册响应,获取用户ID |
|||
REGISTER_ID=$(extract_int "$REGISTER_RESULT" "id") |
|||
if [ -z "$REGISTER_ID" ]; then |
|||
log_error "注册失败" |
|||
echo "$REGISTER_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "注册成功,ID: $REGISTER_ID" |
|||
echo "$REGISTER_RESULT" |
|||
|
|||
# 2. 登录 |
|||
log_step "2" "用户登录" |
|||
LOGIN_RESULT=$(curl -s -X POST ${BASE_URL}/login \ |
|||
-H "Content-Type: application/json" \ |
|||
-d "{ |
|||
\"email\": \"test${TIMESTAMP}@example.com\", |
|||
\"password\": \"password123\" |
|||
}") |
|||
|
|||
# 解析并存储 Token |
|||
AUTH_TOKEN=$(extract_value "$LOGIN_RESULT" "token") |
|||
LOGIN_CODE=$(extract_int "$LOGIN_RESULT" "code") |
|||
|
|||
if [ "$LOGIN_CODE" != "200" ] || [ -z "$AUTH_TOKEN" ]; then |
|||
log_error "登录失败" |
|||
echo "$LOGIN_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "登录成功,Token: ${AUTH_TOKEN:0:20}..." |
|||
echo "$LOGIN_RESULT" |
|||
|
|||
# 3. 获取个人信息(带Token) |
|||
log_step "3" "获取个人信息(带Token)" |
|||
GET_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/me \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
|
|||
# 验证响应 |
|||
ID=$(extract_int "$GET_RESULT" "id") |
|||
if [ -z "$ID" ]; then |
|||
log_error "获取失败:可能Token无效" |
|||
echo "$GET_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "获取成功,ID: $ID" |
|||
echo "$GET_RESULT" |
|||
|
|||
# 4. 更新个人资料 |
|||
log_step "4" "更新个人资料" |
|||
UPDATE_RESULT=$(curl -s -X PUT ${BASE_URL}/${MODULE}/me \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${AUTH_TOKEN}" \ |
|||
-d "{ |
|||
\"username\": \"testuser${TIMESTAMP}_updated\", |
|||
\"phone\": \"13900139000\", |
|||
\"avatar\": \"http://example.com/avatar${TIMESTAMP}.jpg\", |
|||
\"bio\": \"这是个人简介${TIMESTAMP}\" |
|||
}") |
|||
|
|||
# 验证更新 |
|||
VERIFY_USERNAME=$(extract_value "$UPDATE_RESULT" "username") |
|||
EXPECTED_USERNAME="testuser${TIMESTAMP}_updated" |
|||
if [ "$VERIFY_USERNAME" != "$EXPECTED_USERNAME" ]; then |
|||
log_error "更新失败:用户名未更新" |
|||
echo "$UPDATE_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "更新成功" |
|||
echo "$UPDATE_RESULT" |
|||
|
|||
# 5. 查询个人信息(验证更新) |
|||
log_step "5" "查询个人信息(验证更新)" |
|||
VERIFY_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/me \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
|
|||
VERIFY_USERNAME=$(extract_value "$VERIFY_RESULT" "username") |
|||
if [ "$VERIFY_USERNAME" != "$EXPECTED_USERNAME" ]; then |
|||
log_error "验证失败:用户名未更新" |
|||
echo "$VERIFY_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "验证成功" |
|||
|
|||
# 6. 修改密码(使用旧密码) |
|||
log_step "6" "修改密码(使用旧密码)" |
|||
OLD_PASSWORD_RESULT=$(curl -s -X POST ${BASE_URL}/${MODULE}/password \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${AUTH_TOKEN}" \ |
|||
-d "{ |
|||
\"oldPassword\": \"password123\", |
|||
\"newPassword\": \"newpassword123\" |
|||
}") |
|||
|
|||
# 验证修改 |
|||
OLD_PASSWORD_CODE=$(extract_int "$OLD_PASSWORD_RESULT" "code") |
|||
if [ "$OLD_PASSWORD_CODE" != "200" ]; then |
|||
log_error "修改密码失败" |
|||
echo "$OLD_PASSWORD_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "修改密码成功" |
|||
echo "$OLD_PASSWORD_RESULT" |
|||
|
|||
# 7. 使用新密码重新登录 |
|||
log_step "7" "使用新密码重新登录" |
|||
NEW_LOGIN_RESULT=$(curl -s -X POST ${BASE_URL}/login \ |
|||
-H "Content-Type: application/json" \ |
|||
-d "{ |
|||
\"email\": \"test${TIMESTAMP}@example.com\", |
|||
\"password\": \"newpassword123\" |
|||
}") |
|||
|
|||
# 获取新Token |
|||
NEW_AUTH_TOKEN=$(extract_value "$NEW_LOGIN_RESULT" "token") |
|||
NEW_LOGIN_CODE=$(extract_int "$NEW_LOGIN_RESULT" "code") |
|||
|
|||
if [ "$NEW_LOGIN_CODE" != "200" ] || [ -z "$NEW_AUTH_TOKEN" ]; then |
|||
log_error "重新登录失败" |
|||
echo "$NEW_LOGIN_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "重新登录成功,新Token: ${NEW_AUTH_TOKEN:0:20}..." |
|||
echo "$NEW_LOGIN_RESULT" |
|||
|
|||
# 8. 获取个人信息(验证密码修改成功) |
|||
log_step "8" "获取个人信息(验证密码修改成功)" |
|||
FINAL_GET_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/me \ |
|||
-H "Content-Type: application/json" \ |
|||
-H "Authorization: Bearer ${NEW_AUTH_TOKEN}") |
|||
|
|||
# 验证响应 |
|||
ID=$(extract_int "$FINAL_GET_RESULT" "id") |
|||
if [ -z "$ID" ]; then |
|||
log_error "获取失败:新Token可能无效" |
|||
echo "$FINAL_GET_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "验证成功,密码修改完成" |
|||
|
|||
echo -e "\n\033[32m=== Profile 模块测试完成 ===\033[0m" |
|||
echo "用户ID: $REGISTER_ID" |
|||
echo "测试账号: test${TIMESTAMP}@example.com" |
|||
echo "测试密码: password123 / newpassword123" |
|||
@ -0,0 +1,121 @@ |
|||
#!/bin/bash |
|||
|
|||
# 统一测试运行脚本 |
|||
|
|||
set -e |
|||
|
|||
BASE_DIR="$(cd "$(dirname "$0")" && pwd)" |
|||
RESULTS_DIR="$BASE_DIR/results" |
|||
mkdir -p "$RESULTS_DIR" |
|||
|
|||
# 日志函数 |
|||
log_info() { |
|||
echo -e "\033[32m[INFO]\033[0m $1" |
|||
} |
|||
|
|||
log_success() { |
|||
echo -e "\033[32m[SUCCESS]\033[0m $1" |
|||
} |
|||
|
|||
log_error() { |
|||
echo -e "\033[31m[ERROR]\033[0m $1" |
|||
} |
|||
|
|||
print_header() { |
|||
echo -e "\n\033[36m========================================\033[0m" |
|||
echo -e "\033[36m $1\033[0m" |
|||
echo -e "\033[36m========================================\033[0m\n" |
|||
} |
|||
|
|||
# 测试结果统计 |
|||
declare -A USER_RESULTS=() |
|||
declare -A PROFILE_RESULTS=() |
|||
|
|||
# 运行 User 模块测试 |
|||
print_header "User 模块测试" |
|||
if bash "$BASE_DIR/user/test_user.sh" > "$RESULTS_DIR/user_test.log" 2>&1; then |
|||
USER_RESULTS["新增"]="通过" |
|||
USER_RESULTS["查询(新增后)"]="通过" |
|||
USER_RESULTS["更新"]="通过" |
|||
USER_RESULTS["查询(更新后)"]="通过" |
|||
USER_RESULTS["列表查询"]="通过" |
|||
USER_RESULTS["删除"]="通过" |
|||
USER_RESULTS["查询(删除后)"]="通过" |
|||
log_success "User 模块测试全部通过" |
|||
else |
|||
USER_RESULTS["新增"]="失败" |
|||
log_error "User 模块测试失败,查看日志: $RESULTS_DIR/user_test.log" |
|||
fi |
|||
|
|||
# 运行 Profile 模块测试 |
|||
print_header "Profile 模块测试" |
|||
if bash "$BASE_DIR/profile/test_profile.sh" > "$RESULTS_DIR/profile_test.log" 2>&1; then |
|||
PROFILE_RESULTS["获取"]="通过" |
|||
PROFILE_RESULTS["更新"]="通过" |
|||
PROFILE_RESULTS["查询(更新后)"]="通过" |
|||
PROFILE_RESULTS["修改密码"]="通过" |
|||
log_success "Profile 模块测试全部通过" |
|||
else |
|||
PROFILE_RESULTS["获取"]="失败" |
|||
log_error "Profile 模块测试失败,查看日志: $RESULTS_DIR/profile_test.log" |
|||
fi |
|||
|
|||
# 输出测试报告 |
|||
echo -e "\n\033[32m========================================\033[0m" |
|||
echo -e "\033[32m 测试报告\033[0m" |
|||
echo -e "\033[32m========================================\033[0m\n" |
|||
|
|||
echo -e "\033[1m【User 模块】\033[0m" |
|||
echo "步骤 结果" |
|||
echo "--------------------------------" |
|||
echo "新增 ${USER_RESULTS["新增"]}" |
|||
echo "查询(新增后) ${USER_RESULTS["查询(新增后)"]}" |
|||
echo "更新 ${USER_RESULTS["更新"]}" |
|||
echo "查询(更新后) ${USER_RESULTS["查询(更新后)"]}" |
|||
echo "列表查询 ${USER_RESULTS["列表查询"]}" |
|||
echo "删除 ${USER_RESULTS["删除"]}" |
|||
echo "查询(删除后) ${USER_RESULTS["查询(删除后)"]}" |
|||
|
|||
echo -e "\n\033[1m【Profile 模块】\033[0m" |
|||
echo "步骤 结果" |
|||
echo "--------------------------------" |
|||
echo "获取 ${PROFILE_RESULTS["获取"]}" |
|||
echo "更新 ${PROFILE_RESULTS["更新"]}" |
|||
echo "查询(更新后) ${PROFILE_RESULTS["查询(更新后)"]}" |
|||
echo "修改密码 ${PROFILE_RESULTS["修改密码"]}" |
|||
|
|||
# 判断总体结果 |
|||
USER_PASS=0 |
|||
USER_FAIL=0 |
|||
for result in "${USER_RESULTS[@]}"; do |
|||
if [ "$result" = "通过" ]; then |
|||
((USER_PASS++)) |
|||
else |
|||
((USER_FAIL++)) |
|||
fi |
|||
done |
|||
|
|||
PROFILE_PASS=0 |
|||
PROFILE_FAIL=0 |
|||
for result in "${PROFILE_RESULTS[@]}"; do |
|||
if [ "$result" = "通过" ]; then |
|||
((PROFILE_PASS++)) |
|||
else |
|||
((PROFILE_FAIL++)) |
|||
fi |
|||
done |
|||
|
|||
echo -e "\n\033[32m========================================\033[0m" |
|||
echo -e "\033[32m 总体结果\033[0m" |
|||
echo -e "\033[32m========================================\033[0m" |
|||
|
|||
echo "User 模块: 通过 $USER_PASS / $((USER_PASS + USER_FAIL)) 项" |
|||
echo "Profile 模块: 通过 $PROFILE_PASS / $((PROFILE_PASS + PROFILE_FAIL)) 项" |
|||
|
|||
if [ $USER_FAIL -eq 0 ] && [ $PROFILE_FAIL -eq 0 ]; then |
|||
echo -e "\033[32m所有测试通过!\033[0m\n" |
|||
exit 0 |
|||
else |
|||
echo -e "\033[31m存在失败的测试项,请查看日志\033[0m\n" |
|||
exit 1 |
|||
fi |
|||
@ -0,0 +1,136 @@ |
|||
#!/bin/bash |
|||
|
|||
BASE_URL="http://localhost:8888/api/v1" |
|||
MODULE="user" |
|||
TIMESTAMP=$(date +%s) |
|||
|
|||
log_step() { |
|||
echo -e "\n\033[36m--- Step $1: $2 ---\033[0m" |
|||
} |
|||
|
|||
log_success() { |
|||
echo -e "\033[32m[SUCCESS]\033[0m $1" |
|||
} |
|||
|
|||
log_error() { |
|||
echo -e "\033[31m[ERROR]\033[0m $1" |
|||
} |
|||
|
|||
extract_value() { |
|||
echo "$1" | grep -o "\"$2\":\"[^\"]*\"" | sed 's/"'"$2'""://' | sed 's/^"//' | sed 's/"$//' |
|||
} |
|||
|
|||
extract_int() { |
|||
echo "$1" | grep -o "\"$2\":[^,}]*" | sed 's/"'"$2'"://' | tr -d ' ' |
|||
} |
|||
|
|||
AUTH_TOKEN="" |
|||
|
|||
log_step "0" "Register admin" |
|||
REGISTER_RESULT=$(curl -s -X POST ${BASE_URL}/register -H "Content-Type: application/json" -d "{\"username\":\"admin${TIMESTAMP}\",\"email\":\"admin${TIMESTAMP}@example.com\",\"password\":\"password123\",\"phone\":\"13800138000\"}") |
|||
log_success "Register success" |
|||
echo "$REGISTER_RESULT" |
|||
|
|||
log_step "0.1" "Admin login" |
|||
LOGIN_RESULT=$(curl -s -X POST ${BASE_URL}/login -H "Content-Type: application/json" -d "{\"email\":\"admin${TIMESTAMP}@example.com\",\"password\":\"password123\"}") |
|||
AUTH_TOKEN=$(extract_value "$LOGIN_RESULT" "token") |
|||
LOGIN_CODE=$(extract_int "$LOGIN_RESULT" "code") |
|||
|
|||
if [ "$LOGIN_CODE" != "200" ] || [ -z "$AUTH_TOKEN" ]; then |
|||
log_error "Login failed" |
|||
echo "$LOGIN_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "Login success" |
|||
echo "$LOGIN_RESULT" |
|||
|
|||
log_step "1" "Create user" |
|||
CREATE_RESULT=$(curl -s -X POST ${BASE_URL}/${MODULE} -H "Content-Type: application/json" -H "Authorization: Bearer ${AUTH_TOKEN}" -d "{\"username\":\"testuser${TIMESTAMP}\",\"email\":\"test${TIMESTAMP}@example.com\",\"password\":\"password123\",\"phone\":\"13800138000\"}") |
|||
ID=$(extract_int "$CREATE_RESULT" "id") |
|||
|
|||
if [ -z "$ID" ]; then |
|||
log_error "Create failed" |
|||
echo "$CREATE_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "Create success, ID: $ID" |
|||
echo "$CREATE_RESULT" |
|||
|
|||
log_step "2" "Query user" |
|||
QUERY_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/${ID} -H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
QUERY_ID=$(extract_int "$QUERY_RESULT" "id") |
|||
|
|||
if [ "$QUERY_ID" != "$ID" ]; then |
|||
log_error "Query failed" |
|||
echo "$QUERY_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "Query success" |
|||
echo "$QUERY_RESULT" |
|||
|
|||
log_step "3" "Update user" |
|||
UPDATE_RESULT=$(curl -s -X PUT ${BASE_URL}/${MODULE}/${ID} -H "Content-Type: application/json" -H "Authorization: Bearer ${AUTH_TOKEN}" -d "{\"username\":\"testuser${TIMESTAMP}_updated\",\"phone\":\"13900139000\"}") |
|||
VERIFY_UPDATE_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/${ID} -H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
VERIFY_USERNAME=$(extract_value "$VERIFY_UPDATE_RESULT" "username") |
|||
VERIFY_PHONE=$(extract_value "$VERIFY_UPDATE_RESULT" "phone") |
|||
EXPECTED_USERNAME="testuser${TIMESTAMP}_updated" |
|||
|
|||
if [ "$VERIFY_USERNAME" != "$EXPECTED_USERNAME" ] || [ "$VERIFY_PHONE" != "13900139000" ]; then |
|||
log_error "Update failed" |
|||
echo "$VERIFY_UPDATE_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "Update success" |
|||
|
|||
log_step "4" "Verify update" |
|||
VERIFY_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/${ID} -H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
VERIFY_USERNAME=$(extract_value "$VERIFY_RESULT" "username") |
|||
|
|||
if [ "$VERIFY_USERNAME" != "$EXPECTED_USERNAME" ]; then |
|||
log_error "Verify failed" |
|||
echo "$VERIFY_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_success "Verify success" |
|||
|
|||
log_step "5" "List query" |
|||
LIST_RESULT=$(curl -s -X GET "${BASE_URL}/${MODULE}s?page=1&pageSize=10" -H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
|
|||
if echo "$LIST_RESULT" | grep -q '"id"'; then |
|||
log_success "List success" |
|||
else |
|||
log_error "List failed" |
|||
echo "$LIST_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_step "6" "Delete user" |
|||
DELETE_RESULT=$(curl -s -X DELETE ${BASE_URL}/${MODULE}/${ID} -H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
DELETE_CODE=$(extract_int "$DELETE_RESULT" "code") |
|||
|
|||
if [ "$DELETE_CODE" = "200" ]; then |
|||
log_success "Delete success" |
|||
else |
|||
log_error "Delete failed" |
|||
echo "$DELETE_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
log_step "7" "Verify delete" |
|||
VERIFY_DELETE_RESULT=$(curl -s -X GET ${BASE_URL}/${MODULE}/${ID} -H "Authorization: Bearer ${AUTH_TOKEN}") |
|||
VERIFY_CODE=$(extract_int "$VERIFY_DELETE_RESULT" "code") |
|||
|
|||
if [ "$VERIFY_CODE" = "404" ]; then |
|||
log_success "Verify success" |
|||
else |
|||
log_error "Verify failed" |
|||
echo "$VERIFY_DELETE_RESULT" |
|||
exit 1 |
|||
fi |
|||
|
|||
echo -e "\n\033[32m=== User module test completed ===\033[0m" |
|||
@ -0,0 +1,122 @@ |
|||
# Zero Skills - AI 技能框架 |
|||
|
|||
> zero-skills 是基于 go-zero 的 AI 技能管理框架,用于创建和管理 AI 助手的自定义技能。 |
|||
|
|||
## 官方资源 |
|||
|
|||
- [GitHub](https://github.com/zeromicro/zero-skills) |
|||
|
|||
## 概述 |
|||
|
|||
zero-skills 提供了一套技能定义和执行的框架,使得可以方便地为 AI 系统添加自定义功能和业务逻辑。 |
|||
|
|||
## 核心概念 |
|||
|
|||
### 技能 (Skill) |
|||
技能是 AI 系统可以执行的一个原子性任务或功能,例如: |
|||
- 发送邮件 |
|||
- 调用 API |
|||
- 数据库查询 |
|||
- 文件处理 |
|||
|
|||
### 技能定义 |
|||
|
|||
```go |
|||
type Skill struct { |
|||
Name string // 技能名称 |
|||
Description string // 技能描述 |
|||
Parameters map[string]interface{} // 参数定义 |
|||
Handler HandlerFunc // 处理函数 |
|||
} |
|||
|
|||
type HandlerFunc func(ctx context.Context, args map[string]interface{}) (interface{}, error) |
|||
``` |
|||
|
|||
## 使用示例 |
|||
|
|||
### 定义技能 |
|||
|
|||
```go |
|||
package skills |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
) |
|||
|
|||
var SendEmailSkill = &Skill{ |
|||
Name: "send_email", |
|||
Description: "Send an email to a recipient", |
|||
Parameters: map[string]interface{}{ |
|||
"to": map[string]string{"type": "string", "description": "Recipient email"}, |
|||
"subject": map[string]string{"type": "string", "description": "Email subject"}, |
|||
"body": map[string]string{"type": "string", "description": "Email body"}, |
|||
}, |
|||
Handler: func(ctx context.Context, args map[string]interface{}) (interface{}, error) { |
|||
to := args["to"].(string) |
|||
subject := args["subject"].(string) |
|||
body := args["body"].(string) |
|||
|
|||
// 发送邮件逻辑 |
|||
fmt.Printf("Sending email to %s: %s\n", to, subject) |
|||
|
|||
return map[string]interface{}{ |
|||
"success": true, |
|||
"message": "Email sent successfully", |
|||
}, nil |
|||
}, |
|||
} |
|||
``` |
|||
|
|||
### 注册技能 |
|||
|
|||
```go |
|||
package main |
|||
|
|||
import ( |
|||
"github.com/yourapp/skills" |
|||
) |
|||
|
|||
func main() { |
|||
// 初始化技能管理器 |
|||
skillManager := skills.NewManager() |
|||
|
|||
// 注册技能 |
|||
skillManager.Register(skills.SendEmailSkill) |
|||
skillManager.Register(skills.QueryDatabaseSkill) |
|||
|
|||
// 启动服务 |
|||
// ... |
|||
} |
|||
``` |
|||
|
|||
## 技能配置 |
|||
|
|||
```yaml |
|||
skills: |
|||
enabled: true |
|||
base_path: "./skills" |
|||
skills: |
|||
- name: "send_email" |
|||
enabled: true |
|||
config: |
|||
smtp_host: "smtp.example.com" |
|||
smtp_port: 587 |
|||
- name: "query_database" |
|||
enabled: true |
|||
config: |
|||
connection_string: "user:pass@localhost:3306/db" |
|||
``` |
|||
|
|||
## 中间件支持 |
|||
|
|||
```go |
|||
// 添加日志中间件 |
|||
skillManager.Use(middleware.Logger()) |
|||
|
|||
// 添加认证中间件 |
|||
skillManager.Use(middleware.Auth()) |
|||
|
|||
// 添加限流中间件 |
|||
skillManager.Use(middleware.RateLimit(100)) |
|||
``` |
|||
@ -0,0 +1,257 @@ |
|||
# React Native AI Context - shadcn/ui x NativeWind |
|||
|
|||
> React Native 应用的 AI 上下文指南,结合 shadcn/ui 设计理念和 NativeWind 工具类。 |
|||
|
|||
## 概述 |
|||
|
|||
这是 React Native 应用的 AI 开发上下文,使用 NativeWind (Tailwind CSS for React Native) 作为样式解决方案,遵循 shadcn/ui 的设计理念。 |
|||
|
|||
## 核心技术栈 |
|||
|
|||
### 基础框架 |
|||
- [Expo](https://expo.dev/) - React Native 开发框架 |
|||
- [NativeWind](https://www.nativewind.dev/) - Tailwind CSS for React Native |
|||
- [React Native Reusables](https://rnr-docs.vercel.app/) - UI 组件库 |
|||
|
|||
### shadcn/ui 设计原则 |
|||
|
|||
1. **Open Code** - 代码完全可定制 |
|||
2. **Composition** - 组件可组合 |
|||
3. **Distribution** - 易于分发和安装 |
|||
4. **Beautiful Defaults** - 美观的默认样式 |
|||
5. **AI-Ready** - 支持AI开发 |
|||
|
|||
## 安装 |
|||
|
|||
```bash |
|||
# 初始化 Expo 项目 |
|||
npx create-expo-app myapp --template expo-template-blank-typescript |
|||
cd myapp |
|||
|
|||
# 安装 NativeWind |
|||
npm install --save-dev tailwindcss |
|||
npm install nativewind |
|||
npm install -D @types/react-native |
|||
|
|||
# 配置 NativeWind |
|||
npx tailwindcss init |
|||
``` |
|||
|
|||
## 配置 |
|||
|
|||
### tailwind.config.js |
|||
```javascript |
|||
/** @type {import('tailwindcss').Config} */ |
|||
module.exports = { |
|||
content: [ |
|||
"./App.{js,jsx,ts,tsx}", |
|||
"./src/**/*.{js,jsx,ts,tsx}", |
|||
], |
|||
presets: [require("nativewind/preset")], |
|||
theme: { |
|||
extend: { |
|||
colors: { |
|||
border: "hsl(var(--border))", |
|||
input: "hsl(var(--input))", |
|||
ring: "hsl(var(--ring))", |
|||
background: "hsl(var(--background))", |
|||
foreground: "hsl(var(--foreground))", |
|||
primary: { |
|||
DEFAULT: "hsl(var(--primary))", |
|||
foreground: "hsl(var(--primary-foreground))", |
|||
}, |
|||
secondary: { |
|||
DEFAULT: "hsl(var(--secondary))", |
|||
foreground: "hsl(var(--secondary-foreground))", |
|||
}, |
|||
destructive: { |
|||
DEFAULT: "hsl(var(--destructive))", |
|||
foreground: "hsl(var(--destructive-foreground))", |
|||
}, |
|||
muted: { |
|||
DEFAULT: "hsl(var(--muted))", |
|||
foreground: "hsl(var(--muted-foreground))", |
|||
}, |
|||
accent: { |
|||
DEFAULT: "hsl(var(--accent))", |
|||
foreground: "hsl(var(--accent-foreground))", |
|||
}, |
|||
}, |
|||
borderRadius: { |
|||
lg: "var(--radius)", |
|||
md: "calc(var(--radius) - 2px)", |
|||
sm: "calc(var(--radius) - 4px)", |
|||
}, |
|||
}, |
|||
}, |
|||
plugins: [], |
|||
} |
|||
``` |
|||
|
|||
### nativewind-env.d.ts |
|||
```typescript |
|||
/// <reference types="nativewind/types" /> |
|||
``` |
|||
|
|||
### babel.config.js |
|||
```javascript |
|||
module.exports = function (api) { |
|||
api.cache(true); |
|||
return { |
|||
presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }]], |
|||
plugins: [ |
|||
'react-native-reanimated/plugin', |
|||
'nativewind/babel', |
|||
], |
|||
}; |
|||
} |
|||
``` |
|||
|
|||
## 核心组件 |
|||
|
|||
### Button 组件 |
|||
```typescript |
|||
import { Text, Pressable, PressableProps, ViewProps, TextProps } from 'react-native'; |
|||
import { cn } from '@/lib/utils'; |
|||
|
|||
interface ButtonProps extends PressableProps { |
|||
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost'; |
|||
size?: 'default' | 'sm' | 'lg'; |
|||
className?: string; |
|||
children: React.ReactNode; |
|||
} |
|||
|
|||
const Button = React.forwardRef<Pressable, ButtonProps>( |
|||
({ className, variant = 'default', size = 'default', children, ...props }, ref) => { |
|||
const variants = { |
|||
default: 'bg-primary text-primary-foreground hover:bg-primary/90', |
|||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', |
|||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', |
|||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', |
|||
ghost: 'hover:bg-accent hover:text-accent-foreground', |
|||
}; |
|||
|
|||
const sizes = { |
|||
default: 'h-10 px-4 py-2', |
|||
sm: 'h-9 rounded-md px-3', |
|||
lg: 'h-11 rounded-md px-8', |
|||
}; |
|||
|
|||
return ( |
|||
<Pressable |
|||
ref={ref} |
|||
className={cn( |
|||
'rounded-md text-sm font-medium flex items-center justify-center', |
|||
variants[variant], |
|||
sizes[size], |
|||
className |
|||
)} |
|||
{...props} |
|||
> |
|||
<Text className="font-medium">{children}</Text> |
|||
</Pressable> |
|||
); |
|||
} |
|||
); |
|||
``` |
|||
|
|||
### Card 组件 |
|||
```typescript |
|||
import { View, ViewProps, Text, TextProps } from 'react-native'; |
|||
import { cn } from '@/lib/utils'; |
|||
|
|||
interface CardProps extends ViewProps { |
|||
className?: string; |
|||
} |
|||
|
|||
const Card = React.forwardRef<View, CardProps>(({ className, ...props }, ref) => ( |
|||
<View ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} /> |
|||
)); |
|||
|
|||
const CardHeader = React.forwardRef<View, ViewProps>(({ className, ...props }, ref) => ( |
|||
<View ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} /> |
|||
)); |
|||
|
|||
const CardTitle = React.forwardRef<Text, TextProps>(({ className, ...props }, ref) => ( |
|||
<Text ref={ref} className={cn("text-2xl font-semibold leading-none tracking-tight", className)} {...props} /> |
|||
)); |
|||
|
|||
const CardContent = React.forwardRef<View, ViewProps>(({ className, ...props }, ref) => ( |
|||
<View ref={ref} className={cn("p-6 pt-0", className)} {...props} /> |
|||
)); |
|||
``` |
|||
|
|||
## 设计令牌 |
|||
|
|||
### 颜色系统 |
|||
```typescript |
|||
// 使用 CSS 变量定义颜色 |
|||
// 在 tailwind.config.js 中配置 |
|||
``` |
|||
|
|||
### 字体系统 |
|||
```typescript |
|||
// NativeWind 中的字体设置 |
|||
// 使用 Tailwind 的字体工具类 |
|||
text-sm // 小号文本 |
|||
text-base // 基础文本 |
|||
text-lg // 大号文本 |
|||
font-medium // 中等字重 |
|||
font-bold // 粗体 |
|||
``` |
|||
|
|||
## 开发规范 |
|||
|
|||
### 组件结构 |
|||
``` |
|||
src/ |
|||
├── components/ |
|||
│ ├── ui/ # 基础 UI 组件 |
|||
│ │ ├── button.tsx |
|||
│ │ ├── card.tsx |
|||
│ │ └── ... |
|||
│ └── features/ # 功能组件 |
|||
├── lib/ |
|||
│ └── utils.ts # 工具函数 |
|||
├── hooks/ # 自定义 hooks |
|||
└── screens/ # 屏幕组件 |
|||
``` |
|||
|
|||
### 命名约定 |
|||
- 组件使用 PascalCase: `Button.tsx` |
|||
- 文件使用 kebab-case: `user-profile.tsx` |
|||
- 样式类使用 camelCase 或 Tailwind 工具类 |
|||
|
|||
## 与 React Web shadcn/ui 对应 |
|||
|
|||
| React Web | React Native | |
|||
|----------|--------------| |
|||
| `<Button>` | `<Pressable>` with styling | |
|||
| `<Card>` | `<View>` with border and shadow | |
|||
| `<Input>` | `<TextInput>` with styling | |
|||
| `<Dialog>` | `<Modal>` or `<Modal>` from react-native-modal | |
|||
| `<Toast>` | Toast from react-native-toast-message | |
|||
| `<Avatar>` | `<Image>` with circular styling | |
|||
|
|||
## 相关资源 |
|||
|
|||
- [NativeWind 文档](https://www.nativewind.dev/) |
|||
- [React Native Reusables](https://rnr-docs.vercel.app/) - UI 组件库 |
|||
- [Expo 文档](https://docs.expo.dev/) |
|||
- [shadcn/ui](https://ui.shadcn.com/) - Web 版本参考 |
|||
- [shadcn/ui llms.txt](https://ui.shadcn.com/llms.txt) - 参考 |
|||
|
|||
## MCP 集成 |
|||
|
|||
使用 AI 工具: |
|||
```bash |
|||
npx shadcn@latest mcp |
|||
``` |
|||
|
|||
## 最佳实践 |
|||
|
|||
1. 使用 NativeWind 进行样式管理 |
|||
2. 遵循 shadcn/ui 的组件设计原则 |
|||
3. 组件应该是可组合和可定制的 |
|||
4. 使用 TypeScript 保证类型安全 |
|||
5. 使用 Expo Router 进行导航 |
|||
@ -0,0 +1,149 @@ |
|||
# shadcn/ui |
|||
|
|||
> shadcn/ui is a collection of beautifully-designed, accessible |
|||
components and a code distribution platform. It is built with TypeScript, Tailwind |
|||
CSS, and Radix UI primitives. It supports multiple frameworks including Next.js, |
|||
Vite, Remix, Astro, and more. Open Source. Open Code. AI-Ready. It also comes |
|||
with a command-line tool to install and manage components and a registry system |
|||
to publish and distribute code. |
|||
|
|||
## Overview |
|||
|
|||
- [Introduction](https://ui.shadcn.com/docs): Core principles—Open Code, Composition, Distribution, Beautiful Defaults, and AI-Ready design. |
|||
- [CLI](https://ui.shadcn.com/docs/cli): Command-line tool for installing and managing components. |
|||
- [components.json](https://ui.shadcn.com/docs/components-json): Configuration file for customizing the CLI and component installation. |
|||
- [Theming](https://ui.shadcn.com/docs/theming): Guide to customizing colors, typography, and design tokens. |
|||
- [Changelog](https://ui.shadcn.com/docs/changelog): Release notes and version history. |
|||
- [About](https://ui.shadcn.com/docs/about): Credits and project information. |
|||
|
|||
## Installation |
|||
|
|||
- [Next.js](https://ui.shadcn.com/docs/installation/next): Install shadcn/ui in a Next.js project. |
|||
- [Vite](https://ui.shadcn.com/docs/installation/vite): Install shadcn/ui in a Vite project. |
|||
- [Remix](https://ui.shadcn.com/docs/installation/remix): Install shadcn/ui in a Remix project. |
|||
- [Astro](https://ui.shadcn.com/docs/installation/astro): Install shadcn/ui in an Astro project. |
|||
- [Laravel](https://ui.shadcn.com/docs/installation/laravel): Install shadcn/ui in a Laravel project. |
|||
- [Gatsby](https://ui.shadcn.com/docs/installation/gatsby): Install shadcn/ui in a Gatsby project. |
|||
- [React Router](https://ui.shadcn.com/docs/installation/react-router): Install shadcn/ui in a React Router project. |
|||
- [TanStack Router](https://ui.shadcn.com/docs/installation/tanstack-router): Install shadcn/ui in a TanStack Router project. |
|||
- [TanStack Start](https://ui.shadcn.com/docs/installation/tanstack): Install shadcn/ui in a TanStack Start project. |
|||
- [Manual Installation](https://ui.shadcn.com/docs/installation/manual): Manually install shadcn/ui without the CLI. |
|||
|
|||
## Components |
|||
|
|||
### Form & Input |
|||
|
|||
- [Form](https://ui.shadcn.com/docs/components/form): Building forms with React Hook Form and Zod validation. |
|||
- [Field](https://ui.shadcn.com/docs/components/field): Field component for form inputs with labels and error messages. |
|||
- [Button](https://ui.shadcn.com/docs/components/button): Button component with multiple variants. |
|||
- [Button Group](https://ui.shadcn.com/docs/components/button-group): Group multiple buttons together. |
|||
- [Input](https://ui.shadcn.com/docs/components/input): Text input component. |
|||
- [Input Group](https://ui.shadcn.com/docs/components/input-group): Input component with prefix and suffix addons. |
|||
- [Input OTP](https://ui.shadcn.com/docs/components/input-otp): One-time password input component. |
|||
- [Textarea](https://ui.shadcn.com/docs/components/textarea): Multi-line text input component. |
|||
- [Checkbox](https://ui.shadcn.com/docs/components/checkbox): Checkbox input component. |
|||
- [Radio Group](https://ui.shadcn.com/docs/components/radio-group): Radio button group component. |
|||
- [Select](https://ui.shadcn.com/docs/components/select): Select dropdown component. |
|||
- [Switch](https://ui.shadcn.com/docs/components/switch): Toggle switch component. |
|||
- [Slider](https://ui.shadcn.com/docs/components/slider): Slider input component. |
|||
- [Calendar](https://ui.shadcn.com/docs/components/calendar): Calendar component for date selection. |
|||
- [Date Picker](https://ui.shadcn.com/docs/components/date-picker): Date picker component combining input and calendar. |
|||
- [Combobox](https://ui.shadcn.com/docs/components/combobox): Searchable select component with autocomplete. |
|||
- [Label](https://ui.shadcn.com/docs/components/label): Form label component. |
|||
|
|||
### Layout & Navigation |
|||
|
|||
- [Accordion](https://ui.shadcn.com/docs/components/accordion): Collapsible accordion component. |
|||
- [Breadcrumb](https://ui.shadcn.com/docs/components/breadcrumb): Breadcrumb navigation component. |
|||
- [Navigation Menu](https://ui.shadcn.com/docs/components/navigation-menu): Accessible navigation menu with dropdowns. |
|||
- [Sidebar](https://ui.shadcn.com/docs/components/sidebar): Collapsible sidebar component for app layouts. |
|||
- [Tabs](https://ui.shadcn.com/docs/components/tabs): Tabbed interface component. |
|||
- [Separator](https://ui.shadcn.com/docs/components/separator): Visual divider between content sections. |
|||
- [Scroll Area](https://ui.shadcn.com/docs/components/scroll-area): Custom scrollable area with styled scrollbars. |
|||
- [Resizable](https://ui.shadcn.com/docs/components/resizable): Resizable panel layout component. |
|||
|
|||
### Overlays & Dialogs |
|||
|
|||
- [Dialog](https://ui.shadcn.com/docs/components/dialog): Modal dialog component. |
|||
- [Alert Dialog](https://ui.shadcn.com/docs/components/alert-dialog): Alert dialog for confirmation prompts. |
|||
- [Sheet](https://ui.shadcn.com/docs/components/sheet): Slide-out panel component (drawer). |
|||
- [Drawer](https://ui.shadcn.com/docs/components/drawer): Mobile-friendly drawer component using Vaul. |
|||
- [Popover](https://ui.shadcn.com/docs/components/popover): Floating popover component. |
|||
- [Tooltip](https://ui.shadcn.com/docs/components/tooltip): Tooltip component for additional context. |
|||
- [Hover Card](https://ui.shadcn.com/docs/components/hover-card): Card that appears on hover. |
|||
- [Context Menu](https://ui.shadcn.com/docs/components/context-menu): Right-click context menu. |
|||
- [Dropdown Menu](https://ui.shadcn.com/docs/components/dropdown-menu): Dropdown menu component. |
|||
- [Menubar](https://ui.shadcn.com/docs/components/menubar): Horizontal menubar component. |
|||
- [Command](https://ui.shadcn.com/docs/components/command): Command palette component (cmdk). |
|||
|
|||
### Feedback & Status |
|||
|
|||
- [Alert](https://ui.shadcn.com/docs/components/alert): Alert component for messages and notifications. |
|||
- [Toast](https://ui.shadcn.com/docs/components/toast): Toast notification component using Sonner. |
|||
- [Progress](https://ui.shadcn.com/docs/components/progress): Progress bar component. |
|||
- [Spinner](https://ui.shadcn.com/docs/components/spinner): Loading spinner component. |
|||
- [Skeleton](https://ui.shadcn.com/docs/components/skeleton): Skeleton loading placeholder. |
|||
- [Badge](https://ui.shadcn.com/docs/components/badge): Badge component for labels and status indicators. |
|||
- [Empty](https://ui.shadcn.com/docs/components/empty): Empty state component for no data scenarios. |
|||
|
|||
### Display & Media |
|||
|
|||
- [Avatar](https://ui.shadcn.com/docs/components/avatar): Avatar component for user profiles. |
|||
- [Card](https://ui.shadcn.com/docs/components/card): Card container component. |
|||
- [Table](https://ui.shadcn.com/docs/components/table): Table component for displaying data. |
|||
- [Data Table](https://ui.shadcn.com/docs/components/data-table): Advanced data table with sorting, filtering, and pagination. |
|||
- [Chart](https://ui.shadcn.com/docs/components/chart): Chart components using Recharts. |
|||
- [Carousel](https://ui.shadcn.com/docs/components/carousel): Carousel component using Embla Carousel. |
|||
- [Aspect Ratio](https://ui.shadcn.com/docs/components/aspect-ratio): Container that maintains aspect ratio. |
|||
- [Typography](https://ui.shadcn.com/docs/components/typography): Typography styles and components. |
|||
- [Item](https://ui.shadcn.com/docs/components/item): Generic item component for lists and menus. |
|||
- [Kbd](https://ui.shadcn.com/docs/components/kbd): Keyboard shortcut display component. |
|||
|
|||
### Misc |
|||
|
|||
- [Collapsible](https://ui.shadcn.com/docs/components/collapsible): Collapsible container component. |
|||
- [Toggle](https://ui.shadcn.com/docs/components/toggle): Toggle button component. |
|||
- [Toggle Group](https://ui.shadcn.com/docs/components/toggle-group): Group of toggle buttons. |
|||
- [Pagination](https://ui.shadcn.com/docs/components/pagination): Pagination component for lists and tables. |
|||
|
|||
## Dark Mode |
|||
|
|||
- [Dark Mode](https://ui.shadcn.com/docs/dark-mode): Overview of dark mode implementation. |
|||
- [Dark Mode - Next.js](https://ui.shadcn.com/docs/dark-mode/next): Dark mode setup for Next.js. |
|||
- [Dark Mode - Vite](https://ui.shadcn.com/docs/dark-mode/vite): Dark mode setup for Vite. |
|||
- [Dark Mode - Astro](https://ui.shadcn.com/docs/dark-mode/astro): Dark mode setup for Astro. |
|||
- [Dark Mode - Remix](https://ui.shadcn.com/docs/dark-mode/remix): Dark mode setup for Remix. |
|||
|
|||
## Forms |
|||
|
|||
- [Forms Overview](https://ui.shadcn.com/docs/forms): Guide to building forms with shadcn/ui. |
|||
- [React Hook Form](https://ui.shadcn.com/docs/forms/react-hook-form): Using shadcn/ui with React Hook Form. |
|||
- [TanStack Form](https://ui.shadcn.com/docs/forms/tanstack-form): Using shadcn/ui with TanStack Form. |
|||
- [Forms - Next.js](https://ui.shadcn.com/docs/forms/next): Building forms in Next.js with Server Actions. |
|||
|
|||
## Advanced |
|||
|
|||
- [Monorepo](https://ui.shadcn.com/docs/monorepo): Using shadcn/ui in a monorepo setup. |
|||
- [React 19](https://ui.shadcn.com/docs/react-19): React 19 support and migration guide. |
|||
- [Tailwind CSS v4](https://ui.shadcn.com/docs/tailwind-v4): Tailwind CSS v4 support and setup. |
|||
- [JavaScript](https://ui.shadcn.com/docs/javascript): Using shadcn/ui with JavaScript (no TypeScript). |
|||
- [Figma](https://ui.shadcn.com/docs/figma): Figma design resources. |
|||
- [v0](https://ui.shadcn.com/docs/v0): Generating UI with v0 by Vercel. |
|||
|
|||
## MCP Server |
|||
|
|||
- [MCP Server](https://ui.shadcn.com/docs/mcp): Model Context Protocol server for AI integrations. |
|||
|
|||
## Registry |
|||
|
|||
- [Registry Overview](https://ui.shadcn.com/docs/registry): Creating and publishing your own component registry. |
|||
- [Getting Started](https://ui.shadcn.com/docs/registry/getting-started): Set up your own registry. |
|||
- [Examples](https://ui.shadcn.com/docs/registry/examples): Example registries. |
|||
- [FAQ](https://ui.shadcn.com/docs/registry/faq): Common questions about registries. |
|||
- [Authentication](https://ui.shadcn.com/docs/registry/authentication): Adding authentication to your registry. |
|||
- [Registry MCP](https://ui.shadcn.com/docs/registry/mcp): MCP integration for registries. |
|||
|
|||
### Registry Schemas |
|||
|
|||
- [Registry Schema](https://ui.shadcn.com/schema/registry.json): JSON Schema for registry index files. |
|||
- [Registry Item Schema](https://ui.shadcn.com/schema/registry-item.json): JSON Schema for individual registry items. |
|||
@ -0,0 +1,234 @@ |
|||
# PrimeVue - Vue UI 组件库 |
|||
|
|||
> PrimeVue 是一个功能强大且易于使用的 Vue 3 UI 组件库,提供丰富的组件和主题。 |
|||
|
|||
## 官方文档 |
|||
|
|||
- [官方网站](https://primevue.org/) |
|||
- [GitHub](https://github.com/primefaces/primevue) |
|||
- [入门指南](https://primevue.org/gettingstarted/) |
|||
- [组件文档](https://primevue.org/components/) |
|||
|
|||
## 安装 |
|||
|
|||
```bash |
|||
npm install primevue @primevue/themes |
|||
# 或 |
|||
yarn add primevue @primevue/themes |
|||
# 或 |
|||
pnpm add primevue @primevue/themes |
|||
``` |
|||
|
|||
## 快速开始 |
|||
|
|||
```javascript |
|||
// main.js |
|||
import { createApp } from 'vue' |
|||
import PrimeVue from 'primevue/config' |
|||
import Aura from '@primevue/themes/aura' |
|||
import App from './App.vue' |
|||
|
|||
const app = createApp(App) |
|||
|
|||
app.use(PrimeVue, { |
|||
theme: { |
|||
preset: Aura |
|||
} |
|||
}) |
|||
|
|||
app.mount('#app') |
|||
``` |
|||
|
|||
## 主要组件 |
|||
|
|||
### Form Components |
|||
- [InputText](https://primevue.org/inputtext/) |
|||
- [InputNumber](https://primevue.org/inputnumber/) |
|||
- [Textarea](https://primevue.org/textarea/) |
|||
- [Checkbox](https://primevue.org/checkbox/) |
|||
- [RadioButton](https://primevue.org/radiobutton/) |
|||
- [Dropdown](https://primevue.org/dropdown/) |
|||
- [MultiSelect](https://primevue.org/multiselect/) |
|||
- [Calendar](https://primevue.org/calendar/) |
|||
- [DatePicker](https://primevue.org/calendar/) |
|||
- [ToggleButton](https://primevue.org/togglebutton/) |
|||
- [SelectButton](https://primevue.org/selectbutton/) |
|||
- [InputSwitch](https://primevue.org/inputswitch/) |
|||
- [Slider](https://primevue.org/slider/) |
|||
- [Knob](https://primevue.org/knob/) |
|||
|
|||
### Data Components |
|||
- [DataTable](https://primevue.org/datatable/) |
|||
- [DataView](https://primevue.org/dataview/) |
|||
- [Tree](https://primevue.org/tree/) |
|||
- [TreeTable](https://primevue.org/treetable/) |
|||
- [OrderList](https://primevue.org/orderlist/) |
|||
- [PickList](https://primevue.org/picklist/) |
|||
- [Timeline](https://primevue.org/timeline/) |
|||
- [VirtualScroller](https://primevue.org/virtualscroller/) |
|||
|
|||
### Overlay Components |
|||
- [Dialog](https://primevue.org/dialog/) |
|||
- [ConfirmDialog](https://primevue.org/confirmdialog/) |
|||
- [DynamicDialog](https://primevue.org/dynamicdialog/) |
|||
- [OverlayPanel](https://primevue.org/overlaypanel/) |
|||
- [Sidebar](https://primevue.org/sidebar/) |
|||
- [Tooltip](https://primevue.org/tooltip/) |
|||
- [Menu](https://primevue.org/menu/) |
|||
- [ContextMenu](https://primevue.org/contextmenu/) |
|||
- [Megamenu](https://primevue.org/megamenu/) |
|||
- [Menubar](https://primevue.org/menubar/) |
|||
- [PanelMenu](https://primevue.org/panelmenu/) |
|||
- [TieredMenu](https://primevue.org/tieredmenu/) |
|||
|
|||
### Navigation Components |
|||
- [BreadCrumb](https://primevue.org/breadcrumb/) |
|||
- [TabMenu](https://primevue.org/tabmenu/) |
|||
- [TabView](https://primevue.org/tabview/) |
|||
- [TieredMenu](https://primevue.org/tieredmenu/) |
|||
- [Dock](https://primevue.org/dock/) |
|||
|
|||
### Messages Components |
|||
- [Message](https://primevue.org/message/) |
|||
- [Toast](https://primevue.org/toast/) |
|||
- [Inplace](https://primevue.org/inplace/) |
|||
- [ScrollTop](https://primevue.org/scrolltop/) |
|||
- [ProgressBar](https://primevue.org/progressbar/) |
|||
- [Messages](https://primevue.org/messages/) |
|||
|
|||
### Media Components |
|||
- [Image](https://primevue.org/image/) |
|||
- [Galleria](https://primevue.org/galleria/) |
|||
- [Carousel](https://primevue.org/carousel/) |
|||
- [ImageCompare](https://primevue.org/imagecompare/) |
|||
|
|||
### Panels Components |
|||
- [Accordion](https://primevue.org/accordion/) |
|||
- [Card](https://primevue.org/card/) |
|||
- [Fieldset](https://primevue.org/fieldset/) |
|||
- [Panel](https://primevue.org/panel/) |
|||
- [Divider](https://primevue.org/divider/) |
|||
- [ScrollPanel](https://primevue.org/scrollpanel/) |
|||
|
|||
### Misc Components |
|||
- [Badge](https://primevue.org/badge/) |
|||
- [BlockUI](https://primevue.org/blockui/) |
|||
- [Chip](https://primevue.org/chip/) |
|||
- [Inplace](https://primevue.org/inplace/) |
|||
- [Skeleton](https://primevue.org/skeleton/) |
|||
- [Tag](https://primevue.org/tag/) |
|||
- [Avatar](https://primevue.org/avatar/) |
|||
- [AvatarGroup](https://primevue.org/avatargroup/) |
|||
- [BlockUI](https://primevue.org/blockui/) |
|||
- [ProgressBar](https://primevue.org/progressbar/) |
|||
|
|||
## 主题系统 |
|||
|
|||
PrimeVue 提供了强大的主题系统,支持多种预设主题和自定义主题: |
|||
|
|||
### 预设主题 |
|||
```javascript |
|||
import Aura from '@primevue/themes/aura' |
|||
import Lara from '@primevue/themes/lara' |
|||
import Nora from '@primevue/themes/nora' |
|||
import Amethyst from '@primevue/themes/amethyst' |
|||
|
|||
app.use(PrimeVue, { |
|||
theme: { |
|||
preset: Aura, |
|||
options: { |
|||
prefix: 'p', |
|||
darkModeSelector: '.dark-mode', |
|||
cssLayer: false |
|||
} |
|||
} |
|||
}) |
|||
``` |
|||
|
|||
### 自定义主题 |
|||
```javascript |
|||
app.use(PrimeVue, { |
|||
theme: { |
|||
preset: Aura, |
|||
semantic: { |
|||
primary: { |
|||
50: '{blue.50}', |
|||
100: '{blue.100}', |
|||
// ... |
|||
}, |
|||
colorScheme: { |
|||
light: { ... }, |
|||
dark: { ... } |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
``` |
|||
|
|||
## PrimeFlex |
|||
|
|||
PrimeFlex 是响应式布局系统,用于快速创建布局: |
|||
|
|||
```bash |
|||
npm install primeflex |
|||
``` |
|||
|
|||
## PrimeIcons |
|||
|
|||
图标库: |
|||
|
|||
```bash |
|||
npm install primeicons |
|||
``` |
|||
|
|||
```javascript |
|||
import 'primeicons/primeicons.css' |
|||
|
|||
// 使用图标 |
|||
<i class="pi pi-home"></i> |
|||
<i class="pi pi-user"></i> |
|||
<i class="pi pi-cog"></i> |
|||
``` |
|||
|
|||
## Form Validation |
|||
|
|||
与主流表单验证库集成: |
|||
|
|||
- VeeValidate |
|||
- Vuelidate |
|||
- FormKit |
|||
|
|||
## 无障碍 |
|||
|
|||
PrimeVue 完全支持 WCAG 2.0 标准,提供: |
|||
- 键盘导航 |
|||
- ARIA 属性 |
|||
- 屏幕阅读器支持 |
|||
|
|||
## 国际化 (i18n) |
|||
|
|||
```javascript |
|||
app.use(PrimeVue, { |
|||
locale: { |
|||
accept: 'Yes', |
|||
reject: 'No', |
|||
// ... 更多翻译 |
|||
} |
|||
}) |
|||
``` |
|||
|
|||
## 相关工具 |
|||
|
|||
- [MCP Server](https://github.com/primefaces/mcp) - AI 集成 |
|||
- [PrimeVue Designer](https://primevue.org/designer/) - 可视化设计工具 |
|||
- [PrimeBlocks](https://primeblocks.org/) - 预构建的 UI 模板 |
|||
|
|||
## 示例项目 |
|||
|
|||
```bash |
|||
# 克隆示例项目 |
|||
git clone https://github.com/primefaces/primevue-quickstart-vite.git |
|||
cd primevue-quickstart-vite |
|||
npm install |
|||
npm run dev |
|||
``` |
|||
@ -0,0 +1,43 @@ |
|||
# 项目介绍 |
|||
本项目是一个AI开发基础脚手架,旨在帮助开发者快速搭建和启动新的项目。它包含了一些常用的AI开发配置和工具,简化了项目初始化过程。 |
|||
## 项目构成 |
|||
- 后端服务:使用 go + go-zero + gorm 构建,提供 RESTful API 或者 gRPC 服务。 |
|||
- 前端服务:使用 React + Vite 构建,提供用户界面和交互功能。 |
|||
### 后端使用的主要技术 |
|||
- Go:一种高效的编程语言,适合构建高性能的后端服务。 |
|||
- go-zero:一个高性能的微服务框架,简化了服务的开发和部署,AI开发支持如下: |
|||
> ai-context: https://github.com/zeromicro/ai-context?tab=readme-ov-file#%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87 |
|||
> mcp-zero : https://github.com/zeromicro/mcp-zero |
|||
> zero-skills : https://github.com/zeromicro/zero-skills |
|||
- gorm:一个流行的 ORM 库,简化了数据库操作。 |
|||
### 前端使用的主要技术 |
|||
- React shadcn/ui :一个基于 React 的 UI 组件库,提供丰富的界面组件和样式。 |
|||
> ai-context: https://ui.shadcn.com/llms.txt |
|||
> mcp-server: npx shadcn@latest mcp |
|||
> skills : frontend-design |
|||
- React Native:用于构建跨平台移动应用。 |
|||
> 推荐组件库: [React Native Reusables](https://rnr-docs.vercel.app/) (RN 版 shadcn) |
|||
> 基础脚手架: [Expo](https://expo.dev/) + [NativeWind](https://www.nativewind.dev/) |
|||
> AI Context: 参考 shadcn_llms.txt 结合 NativeWind 规约 |
|||
- vue primevue: 一个基于 Vue 的 UI 组件库,提供丰富的界面组件和样式。 |
|||
> ai-context: https://primevue.org/llms.txt |
|||
> mcp-server: npx -y @primevue/mcp |
|||
> skills : frontend-design |
|||
- Vite:一个现代化的前端构建工具,提供快速的开发体验 |
|||
|
|||
## 项目结构 |
|||
``` |
|||
.base |
|||
├── backend # 后端服务代码 |
|||
│ ├── api # API 定义和实现 |
|||
| |── rpc # gRPC 服务定义和实现 |
|||
├── frontend # 前端服务代码 |
|||
│ ├── vue-primevue # Vue + PrimeVue 前端代码 |
|||
│ │ |── pc # PC 端代码 |
|||
│ │ └── mobile # 移动端代码 |
|||
│ ├── react-shadcn # React + shadcn/ui 前端代码 |
|||
| | ├── pc # PC 端代码 |
|||
| | └── mobile # 移动端代码 |
|||
│ ├── react-native # React Native 移动端代码 |
|||
│ └── public # 公共资源 |
|||
└── README.md # 项目说明文件 |
|||
Loading…
Reference in new issue