docs: Update documentation to reflect scoped RBAC and remove org system

- Update CLAUDE.md to describe scoped RBAC instead of multi-tenant org
    - Simplify README.md removing org-related features
    - Update auth.md with comprehensive scoped permission documentation
    - Remove configuration.md (merged into other docs)
    - Update design.md with new architecture decisions
    - Update integration.md with scoped auth examples
    - Update UI documentation removing org references
    - Update test README removing org test references
master
veypi 3 weeks ago
parent a7ffd15601
commit 438a84d9fc

@ -4,12 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
VBase is a Go-based identity authentication and permission management framework built on the [Vigo](https://github.com/veypi/vigo) web framework (onion-model middleware architecture). It provides user management, organization (multi-tenant), RBAC permissions, and OAuth2 authentication services.
VBase is a Go-based identity authentication and permission management framework built on the [Vigo](https://github.com/veypi/vigo) web framework (onion-model middleware architecture). It provides user management, scoped RBAC permissions, and OAuth2 authentication services.
- **Language**: Go 1.24+
- **Framework**: Vigo (onion-model middleware)
- **ORM**: GORM (MySQL, PostgreSQL, SQLite supported)
- **Authentication**: JWT + OAuth2
- **Permissions**: Scoped RBAC (Role-Based Access Control)
- **Frontend**: vhtml (embedded HTML-based UI at `/vb/`)
## Common Commands
@ -26,8 +27,6 @@ go run ./cli/main.go db drop
go test -v ./tests/...
```
Tests in `tests/` use an in-memory SQLite database with mock Redis. The `ensureUsers(t)` helper sets up test users (admin, user1, user2) with tokens stored in globals (`AdminToken`, `User1Token`, etc.).
## Architecture
### Onion Model Request Flow
@ -38,30 +37,22 @@ Request -> [Global Before Middlewares] -> [Router] -> [Handler] -> [Service] ->
Response <- [Global After Middleware] <--------+
```
Key middleware stages:
- **Before**: Authentication, preprocessing, context injection (parent hooks run before child hooks)
- **Handler**: Business logic, returns data (does not write response directly)
- **After**: Response formatting (parent hooks run after child hooks)
### Directory Structure
```
├── api/ # REST API handlers and routing
│ ├── auth/ # Login, register, refresh token, SMS
│ ├── auth/ # Login, register, refresh token
│ ├── oauth/ # OAuth2 provider endpoints
│ ├── org/ # Organization/tenant management
│ ├── role/ # Role management
│ ├── user/ # User management
│ └── init.go # Router aggregation
├── auth/ # Core RBAC permission system
├── auth/ # Core Scoped RBAC permission system
│ ├── auth.go # Permission checking implementation
│ └── middleware.go # Auth middleware
│ └── design.md # Permission system design doc
├── cfg/ # Configuration (DB, Redis, JWT settings)
├── models/ # GORM data models
│ ├── auth.go # Role, Policy, UserRole models
│ ├── auth.go # Role, Permission, UserRole models
│ ├── user.go # User, Identity, Session models
│ ├── org.go # Org, OrgMember models
│ └── init.go # Model registration and migrations
├── libs/ # Utilities (cache, crypto, jwt, sms, email)
├── ui/ # Frontend admin interface (vhtml framework)
@ -69,96 +60,39 @@ Key middleware stages:
└── tests/ # Go integration tests
```
### Permission System (RBAC)
Permission format: `resource:action` or `app:resource:action`
### Permission System (Scoped RBAC)
Examples: `user:read`, `org:create`, `oauth-client:delete`
The system uses a scoped permission model where permissions are isolated by `Scope` (e.g., "vb" for VBase, "app1" for external apps).
**Important**: Resource identifiers must NOT contain colons (use kebab-case or snake_case).
**Permission Format**: `resource:instance:sub-resource:sub-instance` (Tree structure)
Permission middleware usage:
```go
// Basic permission
Router.Post("/articles", AppAuth.Perm("article:create"), createArticle)
// Owner check - allows if user is resource owner
Router.Patch("/articles/{id}", AppAuth.PermWithOwner("article:update", "id"), updateArticle)
// Wildcard support
AppAuth.AddRole("admin", "Admin", "*:*") // All permissions
```
**Levels**:
- Level 1: Create (Odd layers)
- Level 2: Read (Even layers)
- Level 4: Write (Even layers)
- Level 7: Admin (Even layers, inherited downwards)
### Multi-Tenancy (Org)
Organizations provide data isolation. Three usage patterns:
1. **B2C (Single Tenant)**: Ignore Org, bind roles to global (`org_id=""`)
2. **B2B SaaS (Multi-Tenant)**: Require `X-Org-ID` header on all business APIs
3. **Platform (Hybrid)**: Merchant side uses Org, consumer side is global
The `AuthMiddleware` extracts `X-Org-ID` header and loads user's roles for that org context.
**Key Interfaces**:
- `auth.Factory.New(scope)`: Get scoped auth instance.
- `auth.PermRead(code)`, `auth.PermWrite(code)`: Middleware checks.
- `auth.Grant(ctx, userID, permID, level)`: Grant permission.
### API Response Format
All responses are JSON formatted by `common.JsonResponse` middleware:
**Success:**
```json
{"code": 200, "data": { ... }}
```
**Error:**
```json
{ "code": 40001, "message": "Error description" }
```
Error codes combine HTTP status (3 digits) + scenario (2 digits), e.g., `40001` = Bad Request + invalid argument.
### Configuration
Configuration is loaded from:
1. Config file (`-f ./cfg/dev.yml`)
2. Environment variables (uppercase with underscores, e.g., `DB_DSN`, `JWT_SECRET`)
Key config in `cfg/cfg.go`:
- `DB`: Database connection (type: mysql/postgres/sqlite)
- `Redis`: Use `memory` for development
- `Key`: System encryption key (32+ chars recommended)
- `JWT`: Token expiry settings
- `InitAdmin`: Auto-create admin on first run (empty password = first registered user becomes admin)
### Handler Pattern (Vigo Framework)
Handlers use generics with automatic parameter binding:
```go
type CreateReq struct {
Name string `json:"name" src:"json"` // Required
Email *string `json:"email" src:"json"` // Optional (pointer)
Page int `src:"query" default:"1"` // Query param with default
}
func CreateUser(x *vigo.X, req *CreateReq) (*User, error) {
// Business logic
return user, nil // Framework handles JSON response
}
```
Parameter sources: `path`, `query`, `header`, `json`, `form`. Non-pointer fields are required by default.
### Frontend (vhtml)
The admin UI is in `ui/` using the vhtml framework:
- Pages in `ui/page/` (e.g., `user_list.html` → route `/user_list`)
- Components as HTML files, referenced with hyphens (e.g., `<form-user_create>` for `/ui/form/user_create.html`)
- Routes defined in `ui/routes.js`
- Access UI at `/vb/` path when server is running
Full API documentation is available at `/_api.json` when the server is running.
- Routes defined in `ui/routes.js`

@ -22,13 +22,13 @@
## 简介
**VBase** 是一个基于 [Vigo](https://github.com/veypi/vigo) 框架开发的高性能后端基础框架,提供一套标准化的用户管理、组织架构(多租户)、权限控制(RBAC和 OAuth2 认证服务。可作为独立服务部署,也可作为基础库集成到你的 Go 项目中。
**VBase** 是一个基于 [Vigo](https://github.com/veypi/vigo) 框架开发的高性能后端基础框架,提供一套标准化的用户管理、作用域权限控制Scoped RBAC和 OAuth2 认证服务。可作为独立服务部署,也可作为基础库集成到你的 Go 项目中。
## 核心特性
- 🔐 **完整认证体系** - JWT Token 认证Access + Refresh、多因素认证短信验证码
- 🛡️ **RBAC 权限控制** - 基于角色访问控制,支持资源级权限和通配符权限
- 🏢 **多租户架构** - 组织级别的数据隔离,灵活适配 B2C/B2B/平台型业务
- 🛡️ **Scoped RBAC 权限控制** - 基于作用域Scope角色访问控制,支持资源级权限和通配符权限
- <EFBFBD> **层级权限管理** - 支持资源类型(奇数层)和实例(偶数层)的精细化权限控制
- 🔑 **OAuth2 Provider** - 完整的 OAuth2 服务端实现,支持第三方应用接入
- 🗄️ **多数据库支持** - MySQL、PostgreSQL、SQLite 自动适配
- 📱 **内置管理界面** - 基于 vhtml 的嵌入式管理后台
@ -90,191 +90,12 @@ vbase/
├── api/ # REST API 接口层
│ ├── auth/ # 认证接口登录、注册、Token 刷新)
│ ├── oauth/ # OAuth2 Provider 接口
│ ├── org/ # 组织/租户管理
│ ├── role/ # 角色管理
│ └── user/ # 用户管理
├── auth/ # 核心权限控制模块RBAC 实现)
├── auth/ # 核心权限控制模块(Scoped RBAC 实现)
├── cfg/ # 配置管理
├── cli/ # 命令行入口
├── models/ # 数据模型GORM
├── ui/ # 前端管理界面vhtml
└── docs/ # 文档
```
## 配置说明
VBase 支持通过配置文件或环境变量进行配置:
```yaml
# 数据库配置
dsn: "root:password@tcp(127.0.0.1:3306)/vbase?charset=utf8&parseTime=True"
db: "mysql" # 可选: mysql, postgres, sqlite
# JWT 配置
jwt:
secret: "your-secret-key-min-32-characters"
access_expiry: "1h"
refresh_expiry: "720h"
# 应用配置
app:
id: "myapp"
name: "My Application"
init_admin:
username: "admin"
password: "admin123"
email: "admin@example.com"
# OAuth2 提供商配置
providers:
github:
enabled: true
client_id: "your-github-client-id"
client_secret: "your-github-client-secret"
```
环境变量命名规则:直接使用配置项的大写下划线形式,例如 `JWT_SECRET`、`PROVIDERS_GITHUB_CLIENT_ID`。
## 权限系统
### 基础用法
```go
// 创建应用专属的权限实例
var AppAuth = vbase.Auth.New("myapp")
func init() {
// 定义角色
AppAuth.AddRole("admin", "管理员", "*:*")
AppAuth.AddRole("editor", "编辑", "article:create", "article:update")
}
// 使用权限中间件保护路由
Router.Post("/articles", "创建文章", AppAuth.Perm("article:create"), createArticle)
Router.Patch("/articles/{id}", "更新文章", AppAuth.PermWithOwner("article:update", "id"), updateArticle)
```
### 权限格式
- `resource:action` - 基础格式,如 `user:read`, `org:create`
- `app:resource:action` - 带应用前缀,如 `myapp:article:delete`
- `resource:*` - 通配符权限,匹配资源的所有操作
更多权限用法请参考 [集成文档](./docs/integration.md)。
## API 概览
### 认证接口
| 方法 | 路径 | 描述 |
|------|------|------|
| POST | `/vb/api/auth/login` | 用户登录 |
| POST | `/vb/api/auth/register` | 用户注册 |
| POST | `/vb/api/auth/refresh` | 刷新 Token |
| POST | `/vb/api/auth/logout` | 退出登录 |
| POST | `/vb/api/auth/sms/send` | 发送短信验证码 |
| POST | `/vb/api/auth/sms/login` | 短信验证码登录 |
### 用户管理
| 方法 | 路径 | 描述 |
|------|------|------|
| GET | `/vb/api/user/info` | 获取当前用户信息 |
| PATCH | `/vb/api/user/info` | 更新用户信息 |
| GET | `/vb/api/users` | 获取用户列表(管理员) |
### 组织管理
| 方法 | 路径 | 描述 |
|------|------|------|
| POST | `/vb/api/orgs` | 创建组织 |
| GET | `/vb/api/orgs` | 获取组织列表 |
| GET | `/vb/api/orgs/{id}` | 获取组织详情 |
| POST | `/vb/api/orgs/{id}/members` | 添加组织成员 |
| GET | `/vb/api/orgs/{id}/members` | 获取组织成员列表 |
### OAuth2 接口
| 方法 | 路径 | 描述 |
|------|------|------|
| GET | `/vb/api/oauth/authorize` | 授权端点 |
| POST | `/vb/api/oauth/token` | Token 端点 |
| GET | `/vb/api/userinfo` | 用户信息端点 |
完整 API 文档请参考 [API 文档](./docs/api.md)。
## 多租户场景支持
VBase 的组织Org设计灵活支持多种业务模式
| 场景 | 特点 | Org 使用方式 |
|------|------|--------------|
| **B2C 应用** | 单租户,所有用户直接面对平台 | 可选,权限绑定到全局 |
| **B2B SaaS** | 多租户,数据严格隔离 | 强制使用,请求需携带 `X-Org-ID` |
| **平台型** | 平台 + 商家 + 消费者 | 商家端使用 Org消费者端可选 |
## 架构设计
VBase 采用洋葱模型和分层架构:
```
Request -> [Global Middlewares] -> [Router] -> [Group Middlewares]
-> [Handler]
-> [Service/Logic]
-> [Model/DAO] -> Database
|
Response <- [Global After Middleware] <-------------+
```
详细架构说明请参考 [架构设计文档](./docs/design.md)。
## 开发
### 运行测试
```bash
# 运行集成测试
./scripts/tests/run_all.sh
# 或单独运行测试
./scripts/tests/01_basic_auth.sh
```
### 构建
```bash
go build -o vbase cli/main.go
```
### 命令行子命令
```bash
# 生成配置文件
go run ./cli/main.go gen_cfg
# 数据库操作
go run ./cli/main.go db [migrate|seed|...]
```
## 技术栈
- **语言**: Go 1.24+
- **Web 框架**: [Vigo](https://github.com/veypi/vigo) (洋葱模型)
- **ORM**: GORM
- **认证**: JWT + OAuth2
- **前端**: vhtml/vhtml-ui
- **数据库**: MySQL / PostgreSQL / SQLite
## 贡献
欢迎提交 Issue 和 Pull Request
## 许可证
[Apache License 2.0](./LICENSE)
---
<p align="center">
Made with ❤️ by <a href="https://github.com/veypi">veypi</a>
</p>

@ -1,156 +1,341 @@
# 鉴权与授权模块设计 (Authentication & Authorization)
# Auth 权限系统设计
`auth` 模块为 VBase 应用提供了灵活且强大的权限控制系统。它摒弃了隐式的“所有权猜测”,转而采用**显式授权**ACL和**手动鉴权**相结合的策略,以确保系统的安全性和可维护性。
---
## 1. 核心鉴权机制概览
## 一、权限码层级
我们提供三种层级的鉴权方式,分别适用于不同的业务场景:
### 1.1 层级定义
| 方式 | 描述 | 适用场景 | 示例 |
| :--- | :--- | :--- | :--- |
| **全局中间件** (`Perm`) | 检查用户是否拥有某种**全局能力**(基于角色)。 | 管理后台、列表查询、无需特定资源ID的操作。 | `user:read` (查看所有用户) |
| **简单手动鉴权** (Manual) | 在业务代码中直接检查资源字段(如 `OwnerID`)。 | **高频/私有资源**(如订单、日志),逻辑简单且追求高性能。 | 修改自己的订单 |
| **复杂资源鉴权** (`PermOnResource`) | 基于数据库 ACL 表检查用户对**特定实例**的权限。 | **高价值/共享资源**(如项目、组织),需要精细控制和跨用户授权。 | 修改项目成员 |
```
层级从 1 开始计数:
- 奇数层第1、3、5层资源类型
- 偶数层第2、4、6层实例
```
---
### 1.2 示例
## 2. 基础:上下文与中间件
```
app → 第1层 (奇数) - 资源类型
app:vbase → 第2层 (偶数) - 实例
app:vbase:role → 第3层 (奇数) - 资源类型
app:vbase:role:admin → 第4层 (偶数) - 实例
```
### 2.1 认证中间件 (AuthMiddleware)
---
认证中间件仅负责验证 JWT Token 的有效性,并将核心的用户 ID 注入到请求上下文中。为了保持高性能,它**不会**预加载用户详情、角色或组织信息。
## 二、权限等级
### 2.2 上下文访问
### 2.1 奇数层(资源类型)
请使用 `auth` 包提供的辅助函数来访问上下文中的认证信息:
| level | 二进制 | 含义 |
| ----- | ------ | -------------------- |
| 0 | 000 | 无权限 |
| 1 | 001 | 可创建该类型的子资源 |
```go
func MyHandler(x *vigo.X) {
// 1. 获取当前用户 ID (核心,所有认证请求都可用)
userID := auth.GetUserID(x)
### 2.2 偶数层(实例)
if userID == "" {
// 未登录
return vigo.ErrUnauthorized
}
| level | 二进制 | 含义 |
| ----- | ------ | ---------------------- |
| 0 | 000 | 无权限 |
| 2 | 010 | 读取 |
| 4 | 100 | 写入(修改,不能删除) |
| 6 | 110 | 读写(读取+修改) |
| 7 | 111 | 管理员(完全控制:读写+删除+授权) |
// 2. 获取当前组织 ID
// 注意:仅当路由使用了 LoadOrg 中间件,或手动调用了 LoadOrg 后才可用
orgID := auth.GetOrgID(x)
---
// 3. 获取在当前组织中的角色列表
// 注意:仅当路由使用了 LoadOrg 中间件后可用
roles := auth.GetOrgRoles(x)
}
```
## 三、检查规则
### 2.3 组织上下文加载 (LoadOrg)
### 3.1 层级与权限对应
对于需要组织上下文的接口(如 `/api/orgs/{org_id}/...`),使用 `LoadOrg` 中间件按需加载组织信息。
| 权限 | level | 检查层级 | 说明 |
|------|-------|----------|------|
| 创建 | 1 | 奇数层 | 检查资源类型层 |
| 读取 | 2 | 偶数层 | 检查实例层 |
| 写入 | 4 | 偶数层 | 检查实例层 |
| 读写 | 6 | 偶数层 | 检查实例层 |
| 管理 | 7 | 偶数层 | 检查实例层 |
- **行为**: 自动从 Path (`{org_id}`), Query (`?org_id=`), Header (`X-Org-ID`) 中解析组织 ID。
- **验证**: 检查当前用户是否为该组织的有效成员。
- **注入**: 将 OrgID 和用户在该组织的角色注入上下文。
### 3.2 具体规则
```go
// 路由注册示例
// 对于需要组织上下文的接口,显式添加 LoadOrg 中间件
Router.Get("/{org_id}", "获取组织详情", auth.VBaseAuth.LoadOrg, auth.VBaseAuth.Perm("org:read"), get)
```
创建资源 (level 1)
→ 检查当前 permissionID 对应的奇数层
→ 例: "app:{appID}:role" 检查 "app:{appID}:role" 层
读取/更新/删除资源 (level 2,4,6,7)
→ 检查当前 permissionID 对应的偶数层
→ 如无权限,递归向上检查父实例层
→ 注意:只有 Level 7 (管理员) 权限才会向下继承Level 2,4,6 不会继承
→ 例: "app:{appID}:role:{roleID}" 先检查实例层,再检查 "app:{appID}"
```
---
## 3. 场景一:全局/类别权限 (Perm 中间件)
## 四、权限流程示例
用于控制“谁能进入这个门”。不关心具体操作哪个数据,只关心你有没有这个资格。
### 场景一:用户 A 创建应用
- **原理**: 检查用户的角色Role是否包含指定权限。
- **配置**: 通常在路由定义时使用。
```
1. 用户A创建应用 "VBase"
2. 自动创建权限:
- PermissionID: "app:vbase"
- Level: 7 (创建者完全控制)
```
```go
// 只有管理员 (拥有 user:delete 权限) 可以删除用户
Router.Delete("/{user_id}", auth.VBaseAuth.Perm("user:delete"), DeleteUser)
### 场景二:用户 A 邀请用户 B 加入应用
// 只有拥有 user:read 权限的人可以查看列表
Router.Get("/", auth.VBaseAuth.Perm("user:read"), ListUsers)
```
1. 用户A授予用户B: app:vbase level 2 (读)
2. 用户B权限表:
- app:vbase level 2
3. 用户B可执行:
- ✓ 读取 vbase 信息
- ✗ 修改/删除
```
---
### 场景三:用户 B 创建角色
## 4. 场景二:简单手动鉴权 (Manual Check)
```
前置: 用户B有 app:vbase level 2 (读),需要额外授权
对于大多数业务场景(如电商订单、个人博客文章),资源的所有权非常明确且单一。此时,直接在业务代码中判断 `UserID` 字段是最简单、最高效的方式。
1. 用户A授予用户B: app:vbase:role level 1 (创建角色)
2. 用户B创建角色 "Editor"
3. 自动创建权限:
- PermissionID: "app:vbase:role:editor"
- Level: 7
```
**推荐范式:**
---
## 五、Auth 接口设计
```go
// 更新文章 (PATCH /articles/{id})
func UpdateArticle(x *vigo.X) {
// 1. 获取 ID
articleID := x.PathParams.Get("id")
currentUserID := auth.GetUserID(x)
// 2. 查库获取资源
article := db.GetArticle(articleID)
// 3. 【核心鉴权逻辑】
// 允许条件:(我是作者) OR (我是管理员/有特权)
if article.OwnerID != currentUserID {
// 如果不是作者,再检查是否有全局权限进行兜底
if !auth.VBaseAuth.CheckPerm(x.Context(), currentUserID, "", "article:update", "") {
return vigo.ErrForbidden
}
package auth
import (
"context"
"github.com/veypi/vigo"
)
// ========== 权限等级 ==========
const (
LevelNone = 0
LevelCreate = 1 // 001 创建 (检查奇数层)
LevelRead = 2 // 010 读取 (检查偶数层)
LevelWrite = 4 // 100 写入 (检查偶数层)
LevelReadWrite = 6 // 110 读写 (检查偶数层)
LevelAdmin = 7 // 111 管理员 (完全控制)
)
// PermFunc 权限检查函数类型
type PermFunc func(x *vigo.X) error
// Auth 权限管理接口
type Auth interface {
// ========== 上下文 ==========
// UserID 获取当前用户ID
UserID(x *vigo.X) string
// ========== 登录检查 ==========
// Login 检查用户是否登录
Login() PermFunc
// ========== 权限检查 ==========
// Perm 检查权限
// code: 权限码,支持动态解析
// - 固定写法: "app:vbase"
// - 动态解析: "app:{appID}" 从 path 获取
// "app:{appID@query}" 从 query 获取
// "app:{appID@header}" 从 header 获取
// "app:{appID@ctx}" 从 ctx 获取
// level: 需要的权限等级
Perm(code string, level int) PermFunc
// ========== 快捷方法 ==========
// PermCreate 检查创建权限 (level 1检查奇数层)
PermCreate(code string) PermFunc
// PermRead 检查读取权限 (level 2检查偶数层)
PermRead(code string) PermFunc
// PermWrite 检查更新权限 (level 4检查偶数层)
PermWrite(code string) PermFunc
// PermAdmin 检查管理员权限 (level 7检查偶数层)
PermAdmin(code string) PermFunc
// ========== 权限授予(业务调用) ==========
// Grant 授予权限
// 在创建资源、被授权等业务逻辑中调用
// permissionID: 权限码,如 "app:vbase"
// level: 权限等级
Grant(ctx context.Context, userID, permissionID string, level int) error
// Revoke 撤销权限
Revoke(ctx context.Context, userID, permissionID string) error
// ========== 权限查询 ==========
// Check 检查权限 不支持动态解析
// permissionID: 完整的权限码,如 "app:vbase"
Check(ctx context.Context, userID, permissionID string, level int) bool
// ========== 资源列表查询 ==========
// ListResources 查询用户在特定资源类型下的详细权限信息
// 用于解决 "查询我有权限的 app 列表" 等场景
// userID: 用户ID
// resourceType: 资源类型 (奇数层),如 "app" 或 "app:{appID}:role"
// 返回: map[实例ID]权限等级 (如 {"vbase": 2, "other": 7})
ListResources(ctx context.Context, userID, resourceType string) (map[string]int, error)
// ListUsers 查询特定资源的所有协作者及其权限
// 用于解决 "查看这个应用有哪些成员" 等场景
// permissionID: 资源实例权限码,如 "app:vbase"
// 返回: map[用户ID]权限等级 (如 {"user1": 2, "user2": 7})
ListUsers(ctx context.Context, permissionID string) (map[string]int, error)
}
// 4. 执行更新
db.Save(article)
// ========== 数据结构 ==========
// Permission 用户权限
type Permission struct {
ID string `json:"id"`
UserID string `json:"user_id"`
RoleID string `json:"role_id"`
PermissionID string `json:"permission_id"`
Level int `json:"level"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
```
**优点**:
- **零开销**: 不需要额外的 ACL 表查询。
- **逻辑清晰**: 鉴权逻辑与业务逻辑紧密结合,不易出错。
---
## 5. 场景三:复杂资源鉴权 (PermOnResource 中间件)
## 六、使用示例
对于像“项目协作”、“共享文档”这样需要多人协作、权限动态分配的资源,简单的字段判断就不够用了。这时我们需要引入 ACL (Access Control List)。
### 6.1 固定写法
### 5.1 使用方法
```go
var Router = vigo.NewRouter()
**路由配置**:
告诉中间件去检查 `user_permissions` 表,看当前用户对这个 `project_id` 是否有 `project:update` 权限。
func init() {
// 创建应用 - 需要系统级 app 权限
Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp)
```go
// 这里的 "project_id" 是 URL 路径参数的 key
Router.Patch("/{project_id}", auth.VBaseAuth.PermOnResource("project:update", "project_id"), UpdateProject)
// 超级管理员接口
Router.Get("/admin/users", cfg.Auth.PermAdmin("*"), AdminListUsers)
}
```
### 5.2 授权 (Grant)
### 6.2 动态解析
在创建资源时,必须**显式**授予创建者权限。
```go
func init() {
// 从路径参数获取 appID (默认)
// GET /apps/{appID}
Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp)
// 从 query 参数获取
// GET /apps?appID=xxx
Router.Get("/apps", cfg.Auth.PermRead("app:{appID@query}"), GetApp)
// 多层嵌套
// GET /apps/{appID}/roles/{roleID}
Router.Get("/apps/{appID}/roles/{roleID}",
cfg.Auth.PermRead("app:{appID}:role:{roleID}"),
GetRole,
)
}
```
### 6.3 完整示例
```go
func CreateProject(x *vigo.X) {
// ... 创建项目逻辑 ...
project := db.CreateProject(...)
var Router = vigo.NewRouter().Use(cfg.Auth.Login())
func init() {
// 创建应用 - 系统级权限
Router.Post("/apps", cfg.Auth.PermCreate("app"), CreateApp)
// 【关键】赋予创建者对该资源的全部权限
// 权限ID "project:*" 表示对该项目的所有操作权限
auth.VBaseAuth.GrantResourcePerm(x.Context(), currentUserID, orgID, "project:*", project.ID)
// 列出我的应用 - 只需登录
Router.Get("/apps", ListMyApps)
// 应用操作 - 从路径获取
Router.Get("/apps/{appID}", cfg.Auth.PermRead("app:{appID}"), GetApp)
Router.Put("/apps/{appID}", cfg.Auth.PermWrite("app:{appID}"), UpdateApp)
Router.Delete("/apps/{appID}", cfg.Auth.PermAdmin("app:{appID}"), DeleteApp)
// 角色操作 - 嵌套资源
Router.Post("/apps/{appID}/roles", cfg.Auth.PermCreate("app:{appID}:role"), CreateRole)
Router.Get("/apps/{appID}/roles/{roleID}", cfg.Auth.PermRead("app:{appID}:role:{roleID}"), GetRole)
Router.Put("/apps/{appID}/roles/{roleID}", cfg.Auth.PermWrite("app:{appID}:role:{roleID}"), UpdateRole)
Router.Delete("/apps/{appID}/roles/{roleID}", cfg.Auth.PermAdmin("app:{appID}:role:{roleID}"), DeleteRole)
}
```
### 6.4 动态解析规则
| 语法 | 来源 | 示例 |
|------|------|------|
| `{key}` | path 参数 | `{appID}` |
| `{key@query}` | query 参数 | `{appID@query}` |
| `{key@header}` | header | `{appID@header}` |
| `{key@ctx}` | context | `{appID@ctx}` |
---
## 6. 设计决策记录 (ADR)
## 七、接口说明
### 6.1 废弃隐式所有权推断 (PermWithOwner)
我们移除了曾尝试自动推断资源所有权的 `PermWithOwner` 中间件。因为在中间件层无法准确知道资源的 `OwnerID` 字段名,也无法处理复杂的“多所有者”场景,这导致了安全漏洞和难以调试的错误。现在我们推荐使用 **Manual Check** 模式。
### 7.1 业务调用 vs 管理端
### 6.2 上下文精简
为了减少内存占用和数据库压力Request Context (`vigo.X`) 中现在**仅**包含最核心的 `UserID`。其他的非核心信息(如组织信息、详细的用户资料)都改为**按需加载**On-Demand或通过特定中间件`LoadOrg`)加载。
此接口是**业务层**调用,用于:
- 创建资源时自动授予权限
- 业务逻辑中检查权限
**管理端**(如权限管理后台)可以通过直接操作数据库实现批量管理。
### 7.2 Grant 调用时机
```go
// 创建应用时
func CreateApp(x *vigo.X, req *CreateAppReq) (*AppResp, error) {
app := models.App{Name: req.Name}
db.Create(&app)
// 授予创建者完全控制权限
cfg.Auth.Grant(x.Context(), userID, "app:"+app.ID, auth.LevelAdmin)
return &AppResp{App: app}, nil
}
```
### 6.3 组织信息解耦
组织信息的获取逻辑已从核心 `AuthMiddleware` 中剥离,移至 `LoadOrg` 中间件。这意味着不涉及组织业务的接口(如个人资料、系统设置)不再承担加载组织信息的额外开销。
### 7.3 列表与搜索接口设计
对于资源列表List或搜索接口推荐以下设计模式
1. **全量管理接口**(如后台管理系统):
- 使用 `PermAdmin("*")``PermAdmin("app:*")`
- 这类接口返回所有数据,必须严格控制权限。
2. **用户侧列表/搜索**(如“我的应用”):
- **方式一(仅所有者)**
- 使用 `Login()` 确保登录。
- 业务层:`db.Where("owner_id = ?", userID).Find(&apps)`
- **方式二(协作模式 - 使用 ListResources**
- 调用 Auth 接口获取有权限的 ID 列表。
- `perms, _ := auth.ListResources(ctx, userID, "app")`
- `ids := keys(perms)`
- 业务层:`db.Where("id IN ?", ids).Find(&apps)`
- **方式三(混合模式)**
- 同时查询 owner_id 和 授权列表。
- `perms, _ := auth.ListResources(...)`
- `ids := keys(perms)`
- `db.Where("owner_id = ? OR id IN ?", userID, ids).Find(&apps)`

@ -98,300 +98,3 @@ init_admin:
**其他注意事项:**
- 仅在没有用户时生效,已有用户则跳过
- 密码会被 bcrypt 加密存储
- 自动授予 `admin` 角色(系统级权限)
- 启动成功后会打印日志:`[vbase] Initial admin user 'xxx' created successfully`
### 1.3 使用场景
- 多环境部署(开发/测试/生产使用不同的数据库)
- 容器化部署(通过环境变量注入)
- 密钥管理(密钥文件不进入版本控制)
### 1.4 注意事项
- 修改后需要**重启服务**才能生效
- 敏感信息(密码、密钥)建议通过环境变量注入
- 生产环境务必修改默认密钥
---
## 二、线上配置Runtime Config
存储在数据库 `settings` 表,支持运行时动态修改,无需重启服务。
### 2.1 配置分类
| 分类 | 前缀 | 说明 |
|------|------|------|
| `auth` | `auth.*` | 登录注册策略 |
| `email` | `email.*` | 邮件服务配置 |
| `sms` | `sms.*` | 短信服务配置 |
| `security` | `security.*` | 安全策略 |
| `oauth` | `oauth.*` | 第三方登录配置 |
### 2.2 认证配置auth
| 配置键 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `auth.reg.require_email` | bool | `false` | 注册是否要求填写邮箱 |
| `auth.reg.require_phone` | bool | `false` | 注册是否要求填写手机号 |
| `auth.login.methods` | json | `["password"]` | 启用的登录方式:`password`/`email_code`/`phone_code` |
| `auth.login.password_fields` | json | `["username"]` | 密码登录支持的身份标识 |
**配置组合示例:**
```json
// 仅用户名登录(最小化配置)
{
"auth.reg.require_email": "false",
"auth.reg.require_phone": "false",
"auth.login.methods": "[\"password\"]",
"auth.login.password_fields": "[\"username\"]"
}
// 用户名+邮箱,支持邮箱验证码登录
{
"auth.reg.require_email": "true",
"auth.reg.require_phone": "false",
"auth.login.methods": "[\"password\", \"email_code\"]",
"auth.login.password_fields": "[\"username\", \"email\"]"
}
// 用户名+手机,支持手机验证码登录
{
"auth.reg.require_email": "false",
"auth.reg.require_phone": "true",
"auth.login.methods": "[\"password\", \"phone_code\"]",
"auth.login.password_fields": "[\"username\", \"phone\"]"
}
```
### 2.3 验证码配置security
| 配置键 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `code.expiry` | int | `5` | 验证码有效期(分钟) |
| `code.length` | int | `6` | 验证码长度 |
| `code.max_attempt` | int | `3` | 最大验证尝试次数 |
| `code.send_interval` | int | `60` | 发送间隔(秒) |
| `code.max_daily_count` | int | `100` | 单日最大发送次数0=禁用,-1=不限制) |
### 2.4 邮件配置email
| 配置键 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `email.enabled` | bool | `false` | 是否启用邮件服务 |
| `email.provider` | string | `"smtp"` | 邮件服务商smtp/sendgrid |
| `email.smtp.host` | string | `""` | SMTP 服务器地址 |
| `email.smtp.port` | int | `587` | SMTP 端口 |
| `email.smtp.user` | string | `""` | SMTP 用户名 |
| `email.smtp.pass` | string | `""` | SMTP 密码(加密存储) |
| `email.from` | string | `""` | 发件人地址 |
| `email.from_name` | string | `"VBase"` | 发件人显示名称 |
**SMTP 配置示例:**
```json
{
"email.enabled": "true",
"email.provider": "smtp",
"email.smtp.host": "smtp.gmail.com",
"email.smtp.port": "587",
"email.smtp.user": "noreply@example.com",
"email.smtp.pass": "encrypted_password",
"email.from": "noreply@example.com",
"email.from_name": "MyApp"
}
```
### 2.5 短信配置sms
| 配置键 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `sms.enabled` | bool | `false` | 是否启用短信服务 |
| `sms.provider` | string | `"aliyun"` | 短信服务商aliyun/tencent |
| `sms.access_key` | string | `""` | 阿里云 AccessKey ID |
| `sms.access_secret` | string | `""` | 阿里云 AccessKey Secret加密存储 |
| `sms.sign_name` | string | `""` | 短信签名 |
| `sms.template_code` | string | `""` | 验证码模板 CODE |
| `sms.endpoint` | string | `""` | 自定义接入点(可选) |
### 2.6 第三方登录OAuth Providers
OAuth Provider 配置存储在数据库中,支持动态添加、修改、启用/禁用。系统内置常用 Provider 模板,也可自定义接入任意支持 OAuth 2.0 的平台。
**内置模板:**
| 模板名称 | 说明 |
|----------|------|
| `google` | Google 账号登录 |
| `github` | GitHub 账号登录 |
| `wechat` | 微信登录 |
| `feishu` | 飞书登录 |
| `dingtalk` | 钉钉登录 |
**Provider 数据结构:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `code` | string | 唯一标识,如 `google`、`github` |
| `name` | string | 显示名称 |
| `enabled` | bool | 是否启用 |
| `client_id` | string | OAuth Client ID |
| `client_secret` | string | OAuth Client Secret |
| `auth_url` | string | 授权端点 URL |
| `token_url` | string | Token 交换端点 URL |
| `user_info_url` | string | 用户信息端点 URL |
| `scopes` | []string | 请求的权限范围 |
| `user_id_path` | string | 从用户信息中提取 ID 的字段路径 |
| `user_name_path` | string | 从用户信息中提取用户名的字段路径 |
| `user_email_path` | string | 从用户信息中提取邮箱的字段路径 |
| `user_avatar_path` | string | 从用户信息中提取头像的字段路径 |
| `extra_config` | json | 额外配置参数 |
**Field Path 说明:**
用于从 OAuth 服务商返回的用户信息 JSON 中提取字段,支持点号路径:
- `id` - 直接取根级 id 字段
- `data.employee_id` - 取 data 对象下的 employee_id
- `sub` - OpenID Connect 标准字段
**使用内置模板创建 Provider**
```http
POST /api/v1/oauth/providers
{
"template": "google",
"name": "Google",
"client_id": "your-client-id.apps.googleusercontent.com",
"client_secret": "your-client-secret",
"enabled": true
}
```
**自定义 Provider 示例(企业微信):**
```http
POST /api/v1/oauth/providers
{
"code": "wecom",
"name": "企业微信",
"client_id": "wwxxxxxxxxxxxxxxxx",
"client_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"auth_url": "https://open.weixin.qq.com/connect/oauth2/authorize",
"token_url": "https://qyapi.weixin.qq.com/cgi-bin/gettoken",
"user_info_url": "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo",
"scopes": ["snsapi_base"],
"user_id_path": "UserId",
"user_name_path": "name",
"extra_config": {
"agent_id": "1000002"
}
}
```
---
## 三、配置管理 API
### 3.1 获取配置
```http
# 获取所有配置
GET /api/v1/settings
# 获取指定分类
GET /api/v1/settings?category=auth
# 响应示例
{
"data": [
{
"key": "auth.reg.require_email",
"value": "false",
"type": "bool",
"category": "auth",
"desc": "注册是否要求邮箱"
}
]
}
```
### 3.2 更新配置
```http
# 批量更新(管理员权限)
PATCH /api/v1/settings
{
"settings": [
{"key": "auth.reg.require_email", "value": "true"},
{"key": "auth.login.methods", "value": "[\"password\", \"email_code\"]"}
]
}
# 响应
{
"message": "配置已更新",
"updated": 2
}
```
---
## 四、配置优先级
当配置存在冲突时,优先级从高到低:
1. **环境变量** - 如 `VBASE_DSN`、`VBASE_PORT`
2. **配置文件** - `config.yaml`
3. **代码默认值** - `cfg/cfg.go` 中的默认值
4. **运行时配置** - 数据库 `settings` 表(仅适用于线上配置项)
---
## 五、部署建议
### 5.1 开发环境
```yaml
# config.yaml
db: "sqlite"
dsn: "file:./dev.db?cache=shared"
redis:
addr: "memory"
```
### 5.2 生产环境
```yaml
# config.yaml最小化
dsn: ${DB_DSN}
redis:
addr: ${REDIS_ADDR}
password: ${REDIS_PASSWORD}
key: ${SECRET_KEY} # 32位以上随机字符串
storage_path: /data/storage
```
线上配置通过管理后台设置:
1. 首次启动后访问 `http://host:port/admin`
2. 使用初始管理员账号登录
3. 进入「系统设置」配置邮件/短信/登录策略
---
## 六、变更日志
| 版本 | 变更内容 |
|------|---------|
| v1.0 | 分离本地配置和线上配置 |
| v1.0 | 统一验证码模块(支持邮箱+短信) |
| v1.0 | 支持动态修改登录注册策略 |
| v1.0 | 支持动态 OAuth Provider内置模板+自定义接入) |
| v1.0 | 统一登录方式配置(密码/邮箱验证码/手机验证码/OAuth |

@ -2,7 +2,7 @@
## 1. 概览
VBase 是一个基于 Golang 的高性能后端基础框架,旨在提供一套标准化的用户管理、组织架构(多租户)、权限控制(RBAC和 OAuth2 认证服务。项目采用分层架构设计,基于 `vigo` 框架构建,强调代码的可维护性、扩展性和安全性。
VBase 是一个基于 Golang 的高性能后端基础框架,旨在提供一套标准化的用户管理、作用域权限控制Scoped RBAC和 OAuth2 认证服务。项目采用分层架构设计,基于 `vigo` 框架构建,强调代码的可维护性、扩展性和安全性。
## 2. 技术栈
@ -30,46 +30,49 @@ Response <- [Global After Middleware] <------------------------------------+
├── api/ # API 接口层 (路由定义、请求处理)
│ ├── auth/ # 认证相关接口 (登录、注册、刷新Token)
│ ├── oauth/ # OAuth2 Provider 接口
│ ├── org/ # 组织/租户管理接口
│ ├── role/ # 角色管理接口
│ ├── user/ # 用户管理接口
│ └── init.go # 路由聚合与全局中间件配置
├── auth/ # 核心权限控制模块 (RBAC 实现)
├── auth/ # 核心权限控制模块 (Scoped RBAC 实现)
├── cfg/ # 配置与基础设施 (DB, Redis, Log)
├── models/ # 数据模型定义 (GORM 结构体)
├── libs/ # 通用工具库
├── cli/ # 命令行入口
└── doc/ # 文档
└── docs/ # 文档
```
## 4. 核心模块设计
### 4.1 认证与授权 (Auth Module)
Auth 模块是系统的核心安全组件,实现了基于角色访问控制 (RBAC) 和资源级权限管理。
Auth 模块是系统的核心安全组件,实现了基于作用域Scope角色访问控制 (RBAC) 和资源级权限管理。
#### 4.1.1 核心概念
- **Permission (权限)**: 最小粒度的操作许可,格式严格遵循 `resource:action``app:resource:action`
- 示例: `user:read`, `org:create`, `oauth-client:delete`
- **约束**: 资源标识符 (`resource`) 只能包含字母、数字、下划线和连字符,**严禁包含冒号**,以避免解析歧义。
- **Role (角色)**: 权限的集合。系统预设角色包括 `admin` (管理员) 和 `user` (普通用户)。
- **UserRole (用户-角色关联)**: 用户在特定组织 (`OrgID`) 下拥有的角色。
- **Policy (策略)**: 定义角色拥有的权限列表。
- **Scope (作用域)**: 权限隔离的边界,不同应用或模块可以拥有独立的权限体系(如 "vb", "app1")。
- **Permission (权限)**: 最小粒度的操作许可,格式为树状结构字符串。
- **层级定义**:
- **奇数层**: 资源类型 (Level 1: Create)
- **偶数层**: 资源实例 (Level 2: Read, Level 4: Write, Level 7: Admin)
- 示例: `org:orgA:project:projB`
- **Role (角色)**: 权限的集合。角色也属于特定 Scope。
- **UserRole (用户-角色关联)**: 用户在特定 Scope 下拥有的角色。
#### 4.1.2 权限验证机制
系统提供 `auth.Auth` 接口和 `VBaseAuth` 实例,支持多种验证方式
系统提供 `auth.Auth` 接口和 `auth.Factory` 工厂,支持多应用集成
1. **基础权限**: `Perm("resource:action")` - 检查用户是否拥有指定权限。
2. **资源所有者**: `PermWithOwner("resource:action", "owner_id_key")` - 如果用户是资源所有者则放行,否则检查权限。
3. **特定资源**: `PermOnResource("resource:action", "resource_id_key")` - 检查用户对特定资源实例的权限。
4. **组合权限**: `PermAny`, `PermAll` - 支持“任一”或“所有”权限的逻辑组合。
5. **通配符支持**: 支持 `*` 通配符,如 `app:resource:*` 匹配该资源下的所有操作。
1. **获取实例**: `myAuth := auth.Factory.New("my_scope")`
2. **基础权限**: `myAuth.PermRead("resource:id")` - 检查读取权限。
3. **层级检查**:
- **Admin (Level 7)**: 拥有 `*` 或前缀匹配权限(向下继承)。
- **Standard**: 精确匹配且满足 Level 要求。
4. **动态解析**: 支持从 URL、Query、Header 解析 ID`org:{orgID}`
#### 4.1.3 中间件流程
1. **AuthMiddleware**: 解析 JWT Token提取 UserID 和 OrgID注入到请求上下文 (`vigo.X`)
2. **PermMiddleware**: 在业务 Handler 执行前拦截请求,调用 `CheckPermission` 进行鉴权。
1. **LoginMiddleware**: 解析 JWT Token提取 UserID。
2. **PermMiddleware**: 在业务 Handler 执行前拦截请求,调用 `Check` 进行鉴权。
### 4.2 用户体系 (User Module)
@ -77,15 +80,7 @@ Auth 模块是系统的核心安全组件,实现了基于角色的访问控制
- **Identity**: 认证信息表,支持多种登录方式 (密码、OAuth、验证码等) 关联到同一用户。
- **Session**: 用户会话管理,用于记录登录状态和刷新 Token。
### 4.3 组织架构 (Org Module)
支持多租户模型的组织管理:
- **Org**: 组织/团队实体。
- **OrgMember**: 组织成员关系表,记录用户在组织内的角色 (`RoleIDs`)。
- **Context 隔离**: 所有数据操作通过 `OrgID` 进行逻辑隔离 (在 `auth.AuthMiddleware` 中解析并注入)。
### 4.4 OAuth2 服务 (OAuth Module)
### 4.3 OAuth2 服务 (OAuth Module)
实现了完整的 OAuth2 Provider 功能,支持第三方应用接入:
@ -116,7 +111,7 @@ Auth 模块是系统的核心安全组件,实现了基于角色的访问控制
**错误响应**:
```json
{
"code": 400, // 或 401, 403, 500 等
"code": 40001, // 400 + 01 (Bad Request + Invalid Argument)
"message": "错误描述信息"
}
```
@ -139,66 +134,6 @@ Auth 模块是系统的核心安全组件,实现了基于角色的访问控制
## 8. 开发规范
1. **资源命名**: 权限资源标识符必须使用 `kebab-case` (如 `oauth-client`) 或 `snake_case`**禁止使用冒号**
1. **资源命名**: 权限资源标识符必须使用 `kebab-case` `snake_case`,使用冒号 `:` 分隔层级
2. **错误处理**: 优先使用 `vigo.NewError` 或预定义错误 (如 `vigo.ErrNotFound`),以便统一处理 HTTP 状态码。
3. **测试**: 编写集成测试脚本 (`test.sh`, `full_test.sh`) 覆盖核心业务流程。
## 9. 实战场景指南 (Use Cases)
VBase 的 `Org` (组织) 设计非常灵活,既支持单租户应用,也支持复杂的多租户 B2B 系统。以下是几种典型场景的最佳实践。
### 场景一B2C 电商平台 / 简单应用 (Single Tenant)
**示例**: 网上书店、个人博客、C端工具应用。
**特点**: 所有用户直接面对平台,不存在“团队”或“公司”的概念。
- **Org 使用策略**:
- **忽略 Org**: 绝大多数 API 请求不需要携带 `X-Org-ID`
- **权限管理**: 用户角色(如 `admin`, `user`)直接绑定到 Global 域 (`org_id=""`)。
- **数据隔离**: 数据通常是全局可见(如商品列表)或基于 `UserID` 隔离(如用户订单)。
- **权限模型**:
- **管理员**: 拥有全局 `admin` 角色,管理所有书籍 (`book:create`, `book:delete`)。
- **普通用户**: 拥有全局 `user` 角色,可以浏览和购买 (`book:read`, `order:create`)。
- **接口调用**: 客户端无需处理 `OrgID`,直接使用 Token 访问。
### 场景二B2B SaaS 系统 (Multi-Tenant)
**示例**: 项目管理工具 (Jira)、企业协作平台 (Slack)、HR 系统。
**特点**: 客户以“公司”或“团队”为单位,数据严格隔离。
- **Org 使用策略**:
- **强制 Org**: 几乎所有业务 API (如创建任务、查看员工) 都必须在 Header 中携带 `X-Org-ID`
- **数据隔离**: 数据库查询必须带上 `Where("org_id = ?", ctxOrgID)`
- **角色复用**: 同一个用户 (User) 可以加入多个组织 (Org),在不同组织中拥有不同角色 (在 A 公司是管理员,在 B 公司是普通成员)。
- **权限模型**:
- **用户张三**:
- 在 **字节跳动 (OrgA)**: 角色 `admin` -> 权限 `project:create`, `member:add`
- 在 **个人工作室 (OrgB)**: 角色 `viewer` -> 权限 `project:read`
- **鉴权流程**: `AuthMiddleware` 会自动校验张三是否是目标 Org 的成员,并将对应的角色权限加载到上下文中。
### 场景三:双边市场 / 平台型 SaaS (Hybrid)
**示例**: 淘宝/亚马逊 (平台 + 商家)、外卖平台。
**特点**: 既有平台运营方,又有独立商家 (Org),还有 C 端消费者。
- **Org 使用策略**:
- **商家端 (Seller)**: 类似 B2B 模式。商家登录后,操作店铺数据需携带 `X-Org-ID` (店铺ID)。
- **消费者端 (Buyer)**: 类似 B2C 模式。消费者浏览商品不需要 Org 上下文,或者处于一个特殊的“公域”上下文中。
- **平台管理 (Platform Admin)**: 拥有全局超级权限,可以管理所有 Org。
- **权限模型**:
- **平台管理员**: 全局 `super_admin`,可封禁任何商家 (`org:delete`)。
- **商家**: 在自己的 Org 内拥有 `shop_admin`,管理自家商品 (`product:create`)。
- **消费者**: 全局 `buyer`,可跨 Org 下单 (`order:create`)。
### 总结
| 场景 | Org 角色 | 权限绑定 | 请求 Header | 数据隔离方式 |
| :--- | :--- | :--- | :--- | :--- |
| **B2C (书店)** | 不使用 / 仅后台用 | 全局绑定 (`org_id=""`) | 无需 `X-Org-ID` | 基于 `UserID` 或公开 |
| **B2B (Jira)** | 核心隔离单元 | 绑定到特定 Org | **必须** `X-Org-ID` | 严格基于 `OrgID` |
| **平台 (淘宝)** | 商家店铺 | 商家绑定 Org / 买家全局 | 商家端需要 / 买家端不需要 | 商家数据基于 `OrgID` |
在 VBase 中,通过 `CheckPermission` 接口的逻辑(优先检查指定 Org 的角色,若无则检查全局角色),可以完美兼容上述所有模式。
3. **测试**: 编写集成测试脚本覆盖核心业务流程。

@ -1,250 +1,130 @@
# VBase 集成文档
# VBase 集成指南
本文档介绍如何将 VBase 集成到您的 Golang (Vigo) 项目中,快速实现用户管理、权限控制和组织架构功能
本文档介绍如何将 VBase 作为基础库集成到现有的 Golang 项目中,并使用其权限管理系统
## 1. 快速集成 (Quick Start)
## 1. 安装
集成 VBase 仅需简单几步:
### 1.1 引入依赖
在您的项目中引入 VBase
```go
import "github.com/veypi/vbase"
```
### 1.2 挂载路由
在您的项目根路由初始化处,挂载 VBase 路由:
```go
// 您的项目初始化代码
func init() {
// 挂载 vbase 路由到 /vbase 路径下
// 这样 vbase 的 API 将可以通过 /vb/api/auth/login 等访问
MyRouter.Extend("/vb", vbase.Router)
}
```
### 1.3 配置数据库
确保您的项目已经配置了 GORM 数据库连接VBase 将复用该连接。
```go
// VBase 会自动读取您的配置,前提是您使用了兼容的配置结构或手动设置
vbase.Config.SetDB(yourDB)
// 或者直接集成到您的项目配置中由vigo.Flags 自动加载
type AppConfig struct {
Vbase any
}
var Config = *AppConfig{
Vbase: vbase.Config,
}
```bash
go get github.com/veypi/vbase
```
---
## 2. 详细集成方案
### 2.1 路由集成
## 2. 初始化
VBase 提供了一个标准的 `vigo.Router` 实例,包含所有用户、组织、权限相关的 API
在你的应用入口文件(如 `main.go``init.go`)中进行初始化。
```go
package main
import (
"github.com/veypi/vbase"
"github.com/veypi/vbase/cfg"
"github.com/veypi/vbase/auth"
"github.com/veypi/vigo"
)
// 1. 创建你的应用 Router
var Router = vigo.NewRouter()
// 2. 创建你的应用 Auth 实例 (指定 Scope)
var AppAuth = auth.Factory.New("my_app")
func init() {
// 1. 挂载 VBase 路由
// 建议挂载在独立的前缀下,避免路由冲突
// 3. 挂载 VBase 路由 (提供登录、用户管理等基础接口)
Router.Extend("/vb", vbase.Router)
}
```
### 2.2 权限系统集成
VBase 提供了强大的 RBAC (基于角色的访问控制) 权限系统。
#### 2.2.1 初始化应用权限
在您的应用中,使用 `vbase.Auth.New` 创建应用专属的权限实例。
```go
import "github.com/veypi/vbase"
// 定义您的应用权限实例
var AppAuth = vbase.Auth.New("my_app")
func init() {
// 定义应用的默认角色
AppAuth.AddRole("admin", "管理员", "*:*") // 拥有所有权限
AppAuth.AddRole("editor", "编辑",
"article:create",
"article:update",
)
// 4. 配置数据库连接 (如果尚未配置)
// export DSN="root:password@tcp(127.0.0.1:3306)/myapp?charset=utf8&parseTime=True"
}
```
#### 2.2.2 使用权限中间件
使用 `vbase.AuthMiddleware` 进行认证,并结合 `AppAuth.Perm` 进行权限检查。
```go
func init() {
// 1. 全局认证中间件 (解析 Token, 注入 user_id, org_id)
Router.Use(vbase.AuthMiddleware())
Router.Get("/info", "获取app信息", vigo.SkipBefore, getInfo)
// 2. 路由权限控制
// 检查 "article:create" 权限
Router.Post("/articles", "发布文章", AppAuth.Perm("article:create"), createArticle)
// 检查资源所有权 (确保用户只能修改自己的文章)
// 检查 "article:update" 权限,且文章的 owner_id 必须等于当前用户 ID
Router.Patch("/articles/{id}", "更新文章", AppAuth.PermWithOwner("article:update", "id"), updateArticle)
}
```
### 2.3 配置管理
VBase 允许自定义部分配置。
```go
import "github.com/veypi/vbase"
func main() {
// 获取配置单例
cfg := vbase.Config
// 启用调试模式
cfg.Debug = true
// 设置 JWT 密钥 (建议从环境变量读取)
cfg.JwtSecret = "your-secret-key"
// 自定义短信服务提供商 (可选)
// vbase.Config.SetSmsProvider(...)
// 5. 初始化数据库迁移
if err := vbase.Init(); err != nil {
panic(err)
}
```
### 2.4 上下文使用
通过 `vbase.AuthMiddleware` 认证后,您可以在 Handler 中通过 `*vigo.X` 获取用户信息。
```go
func MyHandler(x *vigo.X) error {
// 获取当前用户 ID
userID := x.Get("user_id").(string)
// 获取当前组织 ID (如果有)
orgID := x.Get("org_id").(string)
// ... 业务逻辑
// 6. 启动服务
app := vigo.New("myapp", Router, cfg.Global, nil)
panic(app.Run())
}
```
### 2.5 权限管理高级用法
`AppAuth` 实例提供了丰富的接口来管理和检查权限。
## 3. 使用权限系统
#### 2.5.1 复杂权限检查中间件
### 3.1 定义路由权限
除了基础的 `Perm``PermWithOwner`,还支持复合权限检查:
使用 `AppAuth` 实例的中间件方法来保护你的路由。
```go
// 要求同时拥有 user:read 和 order:read 权限
Router.Get("/stats", "统计数据", AppAuth.PermAll([]string{"user:read", "order:read"}), getStats)
// 只要拥有 user:read 或 user:admin 其中之一即可
Router.Get("/users", "用户列表", AppAuth.PermAny([]string{"user:read", "user:admin"}), listUsers)
```
#### 2.5.2 代码中动态检查权限
func init() {
// 需要登录
Router.Use(AppAuth.Login())
有时需要在 Handler 内部根据业务逻辑进行动态鉴权:
// 资源操作示例
// 假设你的资源是 "article"
```go
func someHandler(x *vigo.X) error {
userID := x.Get("user_id").(string)
orgID := x.Get("org_id").(string)
// 创建文章 (需要 "article" 的创建权限 Level 1)
Router.Post("/articles", AppAuth.PermCreate("article"), CreateArticle)
// 检查是否有 "report:export" 权限
// 注意resourceID 为空时检查通用权限,指定 resourceID 时检查特定资源权限
allowed, err := AppAuth.CheckPermission(x.Context(), userID, orgID, "report:export", "")
// 读取文章 (需要 "article:{id}" 的读取权限 Level 2)
Router.Get("/articles/{id}", AppAuth.PermRead("article:{id}"), GetArticle)
if err != nil || !allowed {
return vigo.ErrForbidden
}
// 更新文章 (需要 "article:{id}" 的写入权限 Level 4)
Router.Put("/articles/{id}", AppAuth.PermWrite("article:{id}"), UpdateArticle)
// ...
// 删除文章 (需要 "article:{id}" 的管理员权限 Level 7)
Router.Delete("/articles/{id}", AppAuth.PermAdmin("article:{id}"), DeleteArticle)
}
```
#### 2.5.3 授予和撤销角色
### 3.2 业务逻辑授权
您可以在业务逻辑中动态授予或撤销用户角色。
**注意**`orgID` 可以为空字符串,表示授予系统级(全局)角色。
在创建资源时,通常需要授予创建者管理权限。
```go
// 授予用户 "editor" 角色 (在指定组织下)
err := AppAuth.GrantRole(ctx, targetUserID, orgID, "editor")
func CreateArticle(x *vigo.X, req *ArticleReq) (*Article, error) {
// 1. 获取当前用户
userID := AppAuth.UserID(x)
// 2. 创建资源逻辑...
article := &Article{Title: req.Title, AuthorID: userID}
db.Create(article)
// 3. 授予权限
// 授予用户对该文章的管理员权限 (Level 7)
// 这样用户后续可以读取、修改、删除该文章
permissionID := fmt.Sprintf("article:%s", article.ID)
if err := AppAuth.Grant(x.Context(), userID, permissionID, auth.LevelAdmin); err != nil {
// 处理错误 (通常记录日志)
x.Log.Error("Failed to grant permission", "err", err)
}
// 撤销角色
err := AppAuth.RevokeRole(ctx, targetUserID, orgID, "editor")
return article, nil
}
```
#### 2.5.4 细粒度资源授权
### 3.3 复杂场景:项目成员管理
如果角色机制不够灵活,可以直接授予用户对特定资源的权限:
假设你有一个项目管理系统,用户可以被邀请加入项目。
```go
// 授予用户对 ID 为 "123" 的文章的 "read" 权限
err := AppAuth.GrantResourcePerm(ctx, targetUserID, orgID, "article:read", "123")
// 撤销资源权限
err := AppAuth.RevokeResourcePerm(ctx, targetUserID, orgID, "article:read", "123")
```
#### 2.5.5 检查资源权限
授予了细粒度资源权限后,可以通过以下两种方式进行检查:
1. **使用中间件 (推荐)**
`PermOnResource` 中间件会自动从路径参数或查询参数中获取资源ID并检查用户是否有权访问该特定资源。
```go
// 自动从路径参数 "id" 获取资源ID (如 /articles/123)
// 如果用户拥有 "article:read" 角色权限,或者被单独授予了对 "123" 的 "article:read" 权限,均可通过检查
Router.Get("/articles/{id}", "获取文章", AppAuth.PermOnResource("article:read", "id"), getArticle)
// 邀请成员接口
func InviteMember(x *vigo.X, req *InviteReq) error {
// 检查当前用户是否有权限邀请 (通常需要管理员权限)
// 这一步由路由中间件 PermAdmin("project:{id}") 保证
// 授予被邀请人读取权限 (Level 2)
permID := fmt.Sprintf("project:%s", req.ProjectID)
return AppAuth.Grant(x.Context(), req.TargetUserID, permID, auth.LevelRead)
}
```
2. **手动检查**
## 4. 配置说明
在 Handler 中手动调用 `CheckPermission`
VBase 使用 `cfg` 包管理配置,支持环境变量。
```go
func getArticle(x *vigo.X) error {
articleID := x.PathParams.Get("id")
userID := x.Get("user_id").(string)
orgID := x.Get("org_id").(string)
// 检查权限
allowed, err := AppAuth.CheckPermission(x.Context(), userID, orgID, "article:read", articleID)
if err != nil {
return err
}
if !allowed {
return vigo.ErrForbidden
}
// ...
}
```
- `DB_DSN`: 数据库连接字符串
- `DB_TYPE`: 数据库类型 (mysql, postgres, sqlite)
- `JWT_SECRET`: JWT 签名密钥 (生产环境务必修改)
- `JWT_ISSUER`: Token 签发者
更多配置详情请参考 [配置文档](./configuration.md)。

@ -4,21 +4,19 @@
### Dashboard Statistics
- **Endpoint**: `GET /api/stats/dashboard`
- **Description**: Returns summary statistics for the dashboard (e.g., total users, active orgs, api calls, revenue).
- **Description**: Returns summary statistics for the dashboard (e.g., total users, api calls).
- **Reason**: The dashboard needs high-level metrics to display to the user immediately upon login. Currently using mock data in `ui/page/dashboard/index.html`.
## Future Improvements
### UI
- Add proper form validation for all inputs.
- Implement pagination for lists (Users, Orgs, OAuth Clients).
- Implement pagination for lists (Users, OAuth Clients).
- Add search and filter functionality for lists.
- Improve error handling and user feedback messages.
- Add loading states for all async operations.
### Features
- Implement Organization editing (currently placeholder).
- Implement Organization member management (add/remove members).
- Implement User creation modal (currently placeholder).
- Implement User editing (currently placeholder).
- Implement detailed OAuth client management (scopes, secrets).

@ -23,14 +23,11 @@
│ │ └── register.html
│ ├── dashboard/ # 仪表盘
│ │ └── index.html
│ ├── sys/ # 系统管理 (User, Org, OAuth)
│ ├── sys/ # 系统管理 (User, OAuth)
│ │ ├── user/
│ │ │ ├── index.html # 用户列表
│ │ │ └── form.html # 用户创建/编辑
│ │ ├── org/
│ │ │ ├── index.html # 组织列表
│ │ │ └── detail.html # 组织详情/设置
│ │ └── oauth/
│ │ ├── oauth/
│ │ ├── index.html # 应用列表
│ │ └── form.html # 应用注册
│ ├── user/ # 个人中心
@ -51,10 +48,9 @@
### 2.2 Default Layout (`layout/default.html`)
- **用途**: 系统核心业务页面。
- **结构**:
- **Sidebar (Left)**: 导航菜单 (Dashboard, User, Org, OAuth, Settings)。支持折叠。
- **Header (Top)**: 面包屑导航, 组织切换器 (Org Switcher), 用户头像/下拉菜单 (Profile, Logout)。
- **Sidebar (Left)**: 导航菜单 (Dashboard, User, OAuth, Settings)。支持折叠。
- **Header (Top)**: 面包屑导航, 用户头像/下拉菜单 (Profile, Logout)。
- **Main Content**: 路由视图插槽 (`<router-view>`)。
- **特性**: 需集成 `OrgID` 切换逻辑,切换组织时更新全局状态并刷新数据。
## 3. 核心模块界面规划与权限
@ -66,21 +62,10 @@
### 3.2 仪表盘 (Dashboard)
- **权限**: 登录用户 (Authenticated)
- **概览页 (`/`)**: 展示核心指标。
- **普通用户**: 仅可见个人或所属项目的数据概览。
- **管理员**: 可见整个组织或系统的宏观数据。
- **普通用户**: 可见个人相关数据概览。
- **管理员**: 可见整个系统的宏观数据。
### 3.3 组织管理 (Org Module)
- **组织列表 (`/org`)**:
- **权限**: 登录用户 (Authenticated)
- 展示用户加入的所有组织。
- “创建组织”入口:视系统配置开放给所有用户或仅限特定用户。
- **组织详情/设置 (`/org/:id`)**:
- **权限**: 组织成员 (Member of Org)
- **基本信息**: 仅 **Org Admin** 可修改名称、Logo。
- **成员管理**: 仅 **Org Admin** 可邀请、移除成员或修改角色。
- **只读视图**: 普通成员仅可查看组织信息和成员列表。
### 3.4 用户管理 (User Module)
### 3.3 用户管理 (User Module)
- **用户列表 (`/sys/user`)**:
- **权限**: 系统管理员 (System Admin Only)
- 全局用户管理表格,包含搜索、分页、禁用/启用用户功能。
@ -88,7 +73,7 @@
- **权限**: 登录用户 (Authenticated)
- 基本信息修改 (头像, 昵称)、账号安全 (修改密码, 绑定第三方账号)。
### 3.5 OAuth2 Provider (OAuth Module)
### 3.4 OAuth2 Provider (OAuth Module)
- **应用管理 (`/sys/oauth`)**:
- **权限**: 登录用户 (Authenticated)
- 开发者注册的 OAuth 应用列表。
@ -98,69 +83,12 @@
## 4. 组件策略
### 4.1 基础组件库
本项目直接使用 **vhtml-ui** 组件库,无需重复封装基础组件。
- 按钮、输入框、卡片、模态框、表格等均直接使用库组件。
- 组件文档:`http://localhost:4000/v/README.md`
### 4.2 业务组件
- 仅当组件包含特定业务逻辑且多处复用时,封装为自定义组件。
- 命名规范:使用短前缀(如 `c-` 或直接目录名)。
- 存放位置:
- 全局复用:`/ui/c/`
- 局部复用:直接存放在调用方同级目录或 `_components` 子目录中。
## 5. 路由规划 (`routes.js`)
```javascript
const routes = [
// Public
{ path: '/login', component: '/page/auth/login.html', layout: 'public', meta: { guest: true } },
{ path: '/register', component: '/page/auth/register.html', layout: 'public', meta: { guest: true } },
// Dashboard (Default Layout)
{
path: '/',
layout: 'default',
meta: { auth: true }, // 登录用户均可访问
component: '/page/dashboard/index.html'
},
// Org Management
{ path: '/org', component: '/page/sys/org/index.html', layout: 'default', meta: { auth: true } },
{
path: '/org/:id',
component: '/page/sys/org/detail.html',
layout: 'default',
meta: { auth: true } // 页面内部需校验是否为成员
},
// User System
{ path: '/profile', component: '/page/user/profile.html', layout: 'default', meta: { auth: true } },
{
path: '/users',
component: '/page/sys/user/index.html',
layout: 'default',
meta: { auth: true, roles: ['admin'] } // 仅系统管理员
},
// OAuth Management
{ path: '/oauth/apps', component: '/page/sys/oauth/index.html', layout: 'default', meta: { auth: true } },
// Errors
{ path: '/403', component: '/page/403.html', layout: 'public' },
{ path: '*', component: '/page/404.html', layout: 'public' }
]
```
## 6. 交互与状态管理
- **全局状态 (`$env.$G`)**:
- `user`: 当前登录用户信息。
- `currentOrg`: 当前选中的组织上下文 (影响 Header 显示和 API 请求 Header)。
- `token`: JWT Token 管理。
- **API 请求**:
- 使用 `$axios` 拦截器,自动注入 `Authorization: Bearer ...`
- 自动注入 `X-Org-ID` (当 `currentOrg` 存在时)。
- **权限控制**:
- 路由守卫 (`beforeEnter`) 检查登录状态和角色。
- 页面内使用 `v-if` 或指令控制按钮级别的权限显示。
使用 `vhtml-ui` 提供的基础组件:
- Button, Input, Select, Checkbox
- Table, Pagination
- Modal, Toast, Confirm
- Card, Badge, Tag
### 4.2 业务组件 (`ui/c/`)
- `<c-user-select>`: 用户选择器 (支持搜索)
- `<c-perm-tree>`: 权限树展示与编辑

@ -25,9 +25,7 @@ scripts/tests/
├── run_all.sh # 运行所有测试脚本 (不负责清理环境)
├── 00_none_auth.sh # 1. 未登录访问测试
├── 01_setup_users.sh # 2. 用户初始化与基础认证
├── 02_resource_perm.sh # 3. 资源权限交叉测试
├── 03_org_permission.sh # 4. 组织权限测试
└── 04_org_load_middleware.sh # 5. LoadOrg 中间件测试
└── 02_resource_perm.sh # 3. 资源权限交叉测试
```
## 推荐运行方式
@ -63,7 +61,7 @@ bash scripts/tests/02_resource_perm.sh
**测试内容**:未登录状态下访问受保护接口
- 验证 API 是否正确拦截未携带 Token 的请求
- 覆盖 Users, Orgs, Roles, Settings 等核心模块
- 覆盖 Users, Roles, Settings 等核心模块
### 01_setup_users.sh
@ -81,23 +79,6 @@ bash scripts/tests/02_resource_perm.sh
- 场景 4: User1 修改 Admin 信息 (禁止 403)
- 场景 5: User2 修改 User1 信息 (禁止 403)
### 03_org_permission.sh
**测试内容**:组织权限控制
- admin 创建组织
- user 不能修改他人创建的组织
- admin 邀请 user 加入组织
- 普通成员不能修改组织信息
- 只有 admin/owner 可以修改组织
### 04_org_load_middleware.sh
**测试内容**LoadOrg 中间件验证
- 验证 `X-Org-ID` 头部的处理
- 验证用户是否为组织成员的检查逻辑
- 验证非成员访问组织资源被拒绝 (403)
- 验证成员访问组织资源被允许 (200)
## 测试环境变量参考
| 变量 | 默认值 | 说明 |

@ -41,8 +41,6 @@ go test -v ./tests/auth_test.go
- **`none_auth_test.go`**: 检查公开端点,并验证受保护端点拒绝未认证请求。
- **`auth_test.go`**: 测试用户生命周期事件(注册 -> 登录 -> 获取/更新个人资料 -> 登出)。
- **`resource_perm_test.go`**: 验证用户除非获得授权(例如管理员),否则无法修改其他用户的数据。
- **`org_permission_test.go`**: 测试组织基于角色的访问控制 (RBAC)(创建组织 -> 添加成员 -> 验证角色)。
- **`org_load_middleware_test.go`**: 专门测试 `LoadOrg` 中间件,该中间件强制执行组织特定端点的成员资格检查。
## 开发注意事项

Loading…
Cancel
Save