feat: add user role menu management api
This commit is contained in:
@@ -0,0 +1,361 @@
|
||||
# 用户角色菜单管理系统
|
||||
|
||||
基于 v-hono 框架的用户认证和权限管理系统,提供完整的用户角色管理和动态菜单功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 🔐 用户认证
|
||||
- 用户注册和登录
|
||||
- JWT 令牌认证
|
||||
- 密码加密存储
|
||||
- 会话管理
|
||||
|
||||
### 👥 角色管理
|
||||
- 多级角色系统(admin、manager、user、guest)
|
||||
- 基于角色的权限控制
|
||||
- 灵活的权限分配
|
||||
|
||||
### 📋 菜单管理
|
||||
- 动态菜单系统
|
||||
- 树形菜单结构
|
||||
- 基于权限的菜单显示
|
||||
- 菜单排序和图标支持
|
||||
|
||||
### 🛡️ 安全特性
|
||||
- 密码 SHA256 加密
|
||||
- 令牌过期机制
|
||||
- 中间件权限验证
|
||||
- 防止路径遍历攻击
|
||||
|
||||
## 系统架构
|
||||
|
||||
```
|
||||
v-hono/
|
||||
├── hono/
|
||||
│ ├── auth.v # 认证核心模块
|
||||
│ ├── auth_routes.v # 认证路由
|
||||
│ ├── database.v # 数据库管理
|
||||
│ ├── request.v # 请求处理
|
||||
│ └── app.v # 应用框架
|
||||
├── auth_example.v # 完整示例
|
||||
└── README_AUTH_SYSTEM.md # 本文档
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 编译和运行
|
||||
|
||||
```bash
|
||||
# 编译示例应用
|
||||
v auth_example.v
|
||||
|
||||
# 运行应用
|
||||
./auth_example
|
||||
```
|
||||
|
||||
### 2. 访问系统
|
||||
|
||||
打开浏览器访问:`http://localhost:3000`
|
||||
|
||||
### 3. 默认用户
|
||||
|
||||
系统预创建了以下示例用户:
|
||||
|
||||
| 用户名 | 密码 | 角色 | 权限 |
|
||||
|--------|------|------|------|
|
||||
| admin | admin123 | admin | 所有权限 |
|
||||
| manager | manager123 | manager | 读写管理权限 |
|
||||
| user | user123 | user | 读写权限 |
|
||||
| guest | guest123 | guest | 只读权限 |
|
||||
|
||||
## API 接口
|
||||
|
||||
### 认证接口
|
||||
|
||||
#### 用户注册
|
||||
```http
|
||||
POST /api/auth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "newuser",
|
||||
"email": "user@example.com",
|
||||
"password": "password123",
|
||||
"role": "user"
|
||||
}
|
||||
```
|
||||
|
||||
#### 用户登录
|
||||
```http
|
||||
POST /api/auth/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"expires_at": 1640995200
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取用户信息
|
||||
```http
|
||||
GET /api/auth/profile
|
||||
Authorization: <token>
|
||||
```
|
||||
|
||||
#### 用户注销
|
||||
```http
|
||||
POST /api/auth/logout
|
||||
Authorization: <token>
|
||||
```
|
||||
|
||||
### 菜单接口
|
||||
|
||||
#### 获取用户菜单
|
||||
```http
|
||||
GET /api/auth/menus
|
||||
Authorization: <token>
|
||||
```
|
||||
|
||||
#### 获取所有菜单(管理员)
|
||||
```http
|
||||
GET /api/auth/menus/all
|
||||
Authorization: <token>
|
||||
```
|
||||
|
||||
#### 创建菜单项(管理员)
|
||||
```http
|
||||
POST /api/auth/menus
|
||||
Authorization: <token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "新菜单",
|
||||
"path": "/new-menu",
|
||||
"icon": "📄",
|
||||
"parent_id": 0,
|
||||
"sort_order": 1,
|
||||
"permissions": ["read", "write"]
|
||||
}
|
||||
```
|
||||
|
||||
### 受保护的路由
|
||||
|
||||
#### 用户路由
|
||||
```http
|
||||
GET /api/protected/user
|
||||
Authorization: <token>
|
||||
```
|
||||
|
||||
#### 管理员路由
|
||||
```http
|
||||
GET /api/protected/admin
|
||||
Authorization: <token>
|
||||
```
|
||||
|
||||
## 角色权限说明
|
||||
|
||||
### Admin(超级管理员)
|
||||
- 拥有所有权限
|
||||
- 可以管理所有用户和菜单
|
||||
- 可以访问所有系统功能
|
||||
|
||||
### Manager(管理员)
|
||||
- 拥有读写和管理权限
|
||||
- 可以管理用户和菜单
|
||||
- 可以访问大部分系统功能
|
||||
|
||||
### User(普通用户)
|
||||
- 拥有读写权限
|
||||
- 可以查看和编辑内容
|
||||
- 无法管理系统设置
|
||||
|
||||
### Guest(访客)
|
||||
- 只有只读权限
|
||||
- 只能查看公开内容
|
||||
- 无法进行任何修改操作
|
||||
|
||||
## 菜单权限配置
|
||||
|
||||
菜单项通过 `permissions` 字段控制访问权限:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "用户管理",
|
||||
"path": "/users",
|
||||
"icon": "👥",
|
||||
"permissions": ["read", "write", "manage"]
|
||||
}
|
||||
```
|
||||
|
||||
权限说明:
|
||||
- `read`: 只读权限
|
||||
- `write`: 读写权限
|
||||
- `manage`: 管理权限
|
||||
- `*`: 所有权限(仅管理员)
|
||||
|
||||
## 中间件使用
|
||||
|
||||
### 认证中间件
|
||||
```v
|
||||
// 添加认证中间件
|
||||
app.use(hono.auth_middleware(auth_manager))
|
||||
```
|
||||
|
||||
### 权限中间件
|
||||
```v
|
||||
// 添加权限检查中间件
|
||||
app.use(hono.permission_middleware('manage'))
|
||||
```
|
||||
|
||||
## 数据库结构
|
||||
|
||||
### users 表
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
status BOOLEAN DEFAULT 1,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
### menu_items 表
|
||||
```sql
|
||||
CREATE TABLE menu_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
icon TEXT,
|
||||
parent_id INTEGER DEFAULT 0,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
permissions TEXT,
|
||||
status BOOLEAN DEFAULT 1,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
### user_sessions 表
|
||||
```sql
|
||||
CREATE TABLE user_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
token TEXT UNIQUE NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
);
|
||||
```
|
||||
|
||||
## 开发指南
|
||||
|
||||
### 添加新的认证功能
|
||||
|
||||
1. 在 `auth.v` 中添加新的方法
|
||||
2. 在 `auth_routes.v` 中添加对应的路由
|
||||
3. 更新前端界面
|
||||
|
||||
### 自定义权限
|
||||
|
||||
1. 修改 `UserRole` 枚举
|
||||
2. 更新 `check_permission` 方法
|
||||
3. 调整菜单权限配置
|
||||
|
||||
### 扩展菜单功能
|
||||
|
||||
1. 添加菜单编辑和删除接口
|
||||
2. 实现菜单拖拽排序
|
||||
3. 添加菜单缓存机制
|
||||
|
||||
## 安全建议
|
||||
|
||||
1. **密码安全**
|
||||
- 使用强密码策略
|
||||
- 定期更换密码
|
||||
- 启用密码复杂度检查
|
||||
|
||||
2. **令牌安全**
|
||||
- 设置合理的过期时间
|
||||
- 实现令牌刷新机制
|
||||
- 支持令牌撤销
|
||||
|
||||
3. **权限控制**
|
||||
- 最小权限原则
|
||||
- 定期审查权限
|
||||
- 记录权限变更日志
|
||||
|
||||
4. **数据安全**
|
||||
- 数据库加密
|
||||
- 定期备份
|
||||
- 访问日志记录
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **数据库连接失败**
|
||||
- 检查数据库文件权限
|
||||
- 确保 SQLite 支持
|
||||
- 验证数据库路径
|
||||
|
||||
2. **认证失败**
|
||||
- 检查用户名密码
|
||||
- 验证令牌格式
|
||||
- 确认令牌未过期
|
||||
|
||||
3. **权限不足**
|
||||
- 检查用户角色
|
||||
- 验证菜单权限配置
|
||||
- 确认中间件配置
|
||||
|
||||
### 调试模式
|
||||
|
||||
启用调试输出:
|
||||
```v
|
||||
// 在代码中添加调试信息
|
||||
println('Debug: $debug_info')
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **数据库优化**
|
||||
- 添加适当的索引
|
||||
- 使用连接池
|
||||
- 优化查询语句
|
||||
|
||||
2. **缓存策略**
|
||||
- 菜单缓存
|
||||
- 用户信息缓存
|
||||
- 权限缓存
|
||||
|
||||
3. **并发处理**
|
||||
- 使用连接池
|
||||
- 实现请求队列
|
||||
- 优化锁机制
|
||||
|
||||
## 贡献指南
|
||||
|
||||
1. Fork 项目
|
||||
2. 创建功能分支
|
||||
3. 提交更改
|
||||
4. 创建 Pull Request
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请提交 Issue 或联系开发团队。
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
module main
|
||||
|
||||
import hono
|
||||
import net.http
|
||||
import json
|
||||
|
||||
fn main() {
|
||||
// 创建数据库管理器
|
||||
db_manager := hono.new_database_manager('auth_system.db') or {
|
||||
eprintln('Failed to create database manager: $err')
|
||||
return
|
||||
}
|
||||
|
||||
// 创建认证管理器
|
||||
mut auth_manager := hono.new_auth_manager(db_manager)
|
||||
|
||||
// 初始化认证相关表
|
||||
auth_manager.init_tables() or {
|
||||
eprintln('Failed to initialize auth tables: $err')
|
||||
return
|
||||
}
|
||||
|
||||
// 创建 Hono 应用
|
||||
mut app := hono.Hono{}
|
||||
|
||||
// 注册认证路由
|
||||
hono.register_auth_routes(mut app, mut auth_manager)
|
||||
|
||||
// 添加一些示例菜单项
|
||||
create_sample_menus(mut auth_manager)
|
||||
|
||||
// 添加一些示例用户
|
||||
create_sample_users(mut auth_manager)
|
||||
|
||||
// 添加一些受保护的路由示例
|
||||
app.get('/api/protected/user', fn [auth_manager] (mut c hono.Context) http.Response {
|
||||
token := c.req.header.get_custom('Authorization') or { '' }
|
||||
user := auth_manager.verify_token(token) or {
|
||||
c.status(401)
|
||||
return c.json(json.encode({
|
||||
'error': 'User not found'
|
||||
}))
|
||||
}
|
||||
return c.json(json.encode({
|
||||
'message': 'This is a protected user route',
|
||||
'user_id': user.id.str(),
|
||||
'username': user.username,
|
||||
'email': user.email,
|
||||
'role': user.role.str()
|
||||
}))
|
||||
})
|
||||
|
||||
app.get('/api/protected/admin', fn [auth_manager] (mut c hono.Context) http.Response {
|
||||
token := c.req.header.get_custom('Authorization') or { '' }
|
||||
user := auth_manager.verify_token(token) or {
|
||||
c.status(401)
|
||||
return c.json(json.encode({
|
||||
'error': 'User not found'
|
||||
}))
|
||||
}
|
||||
if !auth_manager.check_permission(user, 'manage') {
|
||||
c.status(403)
|
||||
return c.json(json.encode({
|
||||
'error': 'Admin access required'
|
||||
}))
|
||||
}
|
||||
return c.json(json.encode({
|
||||
'message': 'This is a protected admin route',
|
||||
'user_id': user.id.str(),
|
||||
'username': user.username,
|
||||
'email': user.email,
|
||||
'role': user.role.str()
|
||||
}))
|
||||
})
|
||||
|
||||
// 添加静态文件服务
|
||||
app.get('/', fn (mut c hono.Context) http.Response {
|
||||
return c.file('public/auth.html')
|
||||
})
|
||||
|
||||
println('🚀 用户角色菜单管理系统已启动')
|
||||
println('📱 访问地址: http://localhost:3000')
|
||||
println('')
|
||||
println('📋 示例用户:')
|
||||
println(' 用户名: admin, 密码: admin123, 角色: admin')
|
||||
println(' 用户名: manager, 密码: manager123, 角色: manager')
|
||||
println(' 用户名: user, 密码: user123, 角色: user')
|
||||
println(' 用户名: guest, 密码: guest123, 角色: guest')
|
||||
println('')
|
||||
|
||||
// 启动服务器
|
||||
app.listen(':3000')
|
||||
}
|
||||
|
||||
// 创建示例菜单
|
||||
fn create_sample_menus(mut auth_manager hono.AuthManager) {
|
||||
// 创建根菜单
|
||||
auth_manager.create_menu_item('仪表板', '/dashboard', '📊', 0, 1, ['read']) or { println('仪表板插入失败') }
|
||||
auth_manager.create_menu_item('用户管理', '/users', '👥', 0, 2, ['read', 'write', 'manage']) or { println('用户管理插入失败') }
|
||||
auth_manager.create_menu_item('系统设置', '/settings', '⚙️', 0, 3, ['manage']) or { println('系统设置插入失败') }
|
||||
auth_manager.create_menu_item('文件管理', '/files', '📁', 0, 4, ['read', 'write']) or { println('文件管理插入失败') }
|
||||
|
||||
// 获取用户管理菜单ID(动态获取)
|
||||
user_menu_id := auth_manager.get_menu_id_by_path('/users')
|
||||
|
||||
// 创建子菜单
|
||||
auth_manager.create_menu_item('用户列表', '/users/list', '📋', user_menu_id, 1, ['read']) or { println('用户列表插入失败') }
|
||||
auth_manager.create_menu_item('添加用户', '/users/add', '➕', user_menu_id, 2, ['write']) or { println('添加用户插入失败') }
|
||||
auth_manager.create_menu_item('编辑用户', '/users/edit', '✏️', user_menu_id, 3, ['write']) or { println('编辑用户插入失败') }
|
||||
auth_manager.create_menu_item('删除用户', '/users/delete', '🗑️', user_menu_id, 4, ['manage']) or { println('删除用户插入失败') }
|
||||
|
||||
// 更多测试菜单
|
||||
auth_manager.create_menu_item('报表中心', '/reports', '📈', 0, 5, ['read', 'write']) or { println('报表中心插入失败') }
|
||||
reports_menu_id := auth_manager.get_menu_id_by_path('/reports')
|
||||
auth_manager.create_menu_item('销售报表', '/reports/sales', '💹', reports_menu_id, 1, ['read']) or { println('销售报表插入失败') }
|
||||
auth_manager.create_menu_item('财务报表', '/reports/finance', '💰', reports_menu_id, 2, ['read']) or { println('财务报表插入失败') }
|
||||
auth_manager.create_menu_item('消息中心', '/messages', '📨', 0, 6, ['read']) or { println('消息中心插入失败') }
|
||||
auth_manager.create_menu_item('个人中心', '/profile', '👤', 0, 7, ['read', 'write']) or { println('个人中心插入失败') }
|
||||
auth_manager.create_menu_item('测试菜单A', '/test/a', '🅰️', 0, 8, ['read']) or { println('测试菜单A插入失败') }
|
||||
auth_manager.create_menu_item('测试菜单B', '/test/b', '🅱️', 0, 9, ['read']) or { println('测试菜单B插入失败') }
|
||||
auth_manager.create_menu_item('测试菜单C', '/test/c', '🆑', 0, 10, ['read']) or { println('测试菜单C插入失败') }
|
||||
test_a_id := auth_manager.get_menu_id_by_path('/test/a')
|
||||
auth_manager.create_menu_item('A-子菜单1', '/test/a/1', '1️⃣', test_a_id, 1, ['read']) or { println('A-子菜单1插入失败') }
|
||||
auth_manager.create_menu_item('A-子菜单2', '/test/a/2', '2️⃣', test_a_id, 2, ['read']) or { println('A-子菜单2插入失败') }
|
||||
|
||||
println('✅ 示例菜单已创建')
|
||||
println('✅ 更多测试菜单已创建')
|
||||
}
|
||||
|
||||
// 创建示例用户
|
||||
fn create_sample_users(mut auth_manager hono.AuthManager) {
|
||||
// 创建管理员用户
|
||||
auth_manager.create_user('admin', 'admin@example.com', 'admin123', hono.UserRole.admin) or { return }
|
||||
|
||||
// 创建经理用户
|
||||
auth_manager.create_user('manager', 'manager@example.com', 'manager123', hono.UserRole.manager) or { return }
|
||||
|
||||
// 创建普通用户
|
||||
auth_manager.create_user('user', 'user@example.com', 'user123', hono.UserRole.user) or { return }
|
||||
|
||||
// 创建访客用户
|
||||
auth_manager.create_user('guest', 'guest@example.com', 'guest123', hono.UserRole.guest) or { return }
|
||||
|
||||
println('✅ 示例用户已创建')
|
||||
}
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
module hono
|
||||
|
||||
import crypto.sha256
|
||||
import crypto.rand
|
||||
import time
|
||||
import json
|
||||
|
||||
// 用户角色枚举
|
||||
pub enum UserRole {
|
||||
admin
|
||||
manager
|
||||
user
|
||||
guest
|
||||
}
|
||||
|
||||
// 用户结构
|
||||
pub struct User {
|
||||
pub:
|
||||
id int
|
||||
username string
|
||||
email string
|
||||
password_hash string
|
||||
role UserRole
|
||||
status bool // true: active, false: inactive
|
||||
created_at int
|
||||
updated_at int
|
||||
}
|
||||
|
||||
// 菜单项结构
|
||||
pub struct MenuItem {
|
||||
pub:
|
||||
id int
|
||||
name string
|
||||
path string
|
||||
icon string
|
||||
parent_id int
|
||||
sort_order int
|
||||
permissions []string
|
||||
status bool
|
||||
created_at int
|
||||
updated_at int
|
||||
mut:
|
||||
children []MenuItem
|
||||
}
|
||||
|
||||
// 用户会话结构
|
||||
pub struct UserSession {
|
||||
pub:
|
||||
user_id int
|
||||
token string
|
||||
expires_at int
|
||||
created_at int
|
||||
}
|
||||
|
||||
// 认证管理器
|
||||
pub struct AuthManager {
|
||||
mut:
|
||||
db DatabaseManager
|
||||
}
|
||||
|
||||
// 创建认证管理器
|
||||
pub fn new_auth_manager(db DatabaseManager) AuthManager {
|
||||
return AuthManager{
|
||||
db: db
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化认证相关表
|
||||
pub fn (mut auth AuthManager) init_tables() ! {
|
||||
// 创建用户表
|
||||
auth.db.db.exec('CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL,
|
||||
status BOOLEAN DEFAULT 1,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);') or {
|
||||
return error('Failed to create users table: $err')
|
||||
}
|
||||
|
||||
// 创建菜单表
|
||||
auth.db.db.exec('CREATE TABLE IF NOT EXISTS menu_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
icon TEXT,
|
||||
parent_id INTEGER DEFAULT 0,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
permissions TEXT,
|
||||
status BOOLEAN DEFAULT 1,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);') or {
|
||||
return error('Failed to create menu_items table: $err')
|
||||
}
|
||||
|
||||
// 创建用户会话表
|
||||
auth.db.db.exec('CREATE TABLE IF NOT EXISTS user_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
token TEXT UNIQUE NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
);') or {
|
||||
return error('Failed to create user_sessions table: $err')
|
||||
}
|
||||
|
||||
// 创建索引
|
||||
auth.db.db.exec('CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);') or {
|
||||
return error('Failed to create index: $err')
|
||||
}
|
||||
auth.db.db.exec('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);') or {
|
||||
return error('Failed to create index: $err')
|
||||
}
|
||||
auth.db.db.exec('CREATE INDEX IF NOT EXISTS idx_menu_parent ON menu_items(parent_id);') or {
|
||||
return error('Failed to create index: $err')
|
||||
}
|
||||
auth.db.db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_token ON user_sessions(token);') or {
|
||||
return error('Failed to create index: $err')
|
||||
}
|
||||
}
|
||||
|
||||
// 密码哈希
|
||||
fn hash_password(password string) string {
|
||||
return sha256.sum(password.bytes()).hex()
|
||||
}
|
||||
|
||||
// 生成随机令牌
|
||||
fn generate_token() string {
|
||||
random_bytes := rand.bytes(32) or { return '' }
|
||||
mut token := ''
|
||||
for byte in random_bytes {
|
||||
token += '${byte:02x}'
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
pub fn (mut auth AuthManager) create_user(username string, email string, password string, role UserRole) !User {
|
||||
now := int(time.now().unix())
|
||||
password_hash := hash_password(password)
|
||||
role_str := role.str()
|
||||
|
||||
auth.db.db.exec('INSERT INTO users (username, email, password_hash, role, status, created_at, updated_at) VALUES ("$username", "$email", "$password_hash", "$role_str", 1, $now, $now)') or {
|
||||
return error('Failed to create user: $err')
|
||||
}
|
||||
|
||||
// 获取插入的用户ID
|
||||
rows := auth.db.db.exec('SELECT last_insert_rowid()') or {
|
||||
return error('Failed to get user ID: $err')
|
||||
}
|
||||
user_id := rows[0].vals[0].int()
|
||||
|
||||
return User{
|
||||
id: user_id
|
||||
username: username
|
||||
email: email
|
||||
password_hash: password_hash
|
||||
role: role
|
||||
status: true
|
||||
created_at: now
|
||||
updated_at: now
|
||||
}
|
||||
}
|
||||
|
||||
// 用户登录
|
||||
pub fn (mut auth AuthManager) login(username string, password string) !UserSession {
|
||||
password_hash := hash_password(password)
|
||||
|
||||
rows := auth.db.db.exec('SELECT id, username, email, password_hash, role, status FROM users WHERE username = "$username" AND password_hash = "$password_hash" AND status = 1') or {
|
||||
return error('Failed to query user: $err')
|
||||
}
|
||||
|
||||
if rows.len == 0 {
|
||||
return error('Invalid username or password')
|
||||
}
|
||||
|
||||
user := rows[0]
|
||||
user_id := user.vals[0].int()
|
||||
|
||||
// 生成会话令牌
|
||||
token := generate_token()
|
||||
now := int(time.now().unix())
|
||||
expires_at := now + (24 * 60 * 60) // 24小时后过期
|
||||
|
||||
// 保存会话
|
||||
auth.db.db.exec('INSERT INTO user_sessions (user_id, token, expires_at, created_at) VALUES ($user_id, "$token", $expires_at, $now)') or {
|
||||
return error('Failed to create session: $err')
|
||||
}
|
||||
|
||||
return UserSession{
|
||||
user_id: user_id
|
||||
token: token
|
||||
expires_at: expires_at
|
||||
created_at: now
|
||||
}
|
||||
}
|
||||
|
||||
// 验证令牌
|
||||
pub fn (auth AuthManager) verify_token(token string) !User {
|
||||
rows := auth.db.db.exec('SELECT u.id, u.username, u.email, u.password_hash, u.role, u.status FROM users u JOIN user_sessions s ON u.id = s.user_id WHERE s.token = "$token" AND s.expires_at > ${int(time.now().unix())} AND u.status = 1') or {
|
||||
return error('Failed to verify token: $err')
|
||||
}
|
||||
|
||||
if rows.len == 0 {
|
||||
return error('Invalid or expired token')
|
||||
}
|
||||
|
||||
user := rows[0]
|
||||
role_str := user.vals[4]
|
||||
role := match role_str {
|
||||
'admin' { UserRole.admin }
|
||||
'manager' { UserRole.manager }
|
||||
'user' { UserRole.user }
|
||||
'guest' { UserRole.guest }
|
||||
else { UserRole.guest }
|
||||
}
|
||||
|
||||
return User{
|
||||
id: user.vals[0].int()
|
||||
username: user.vals[1]
|
||||
email: user.vals[2]
|
||||
password_hash: user.vals[3]
|
||||
role: role
|
||||
status: user.vals[5].int() == 1
|
||||
created_at: 0
|
||||
updated_at: 0
|
||||
}
|
||||
}
|
||||
|
||||
// 注销会话
|
||||
pub fn (mut auth AuthManager) logout(token string) ! {
|
||||
auth.db.db.exec('DELETE FROM user_sessions WHERE token = "$token"') or {
|
||||
return error('Failed to logout: $err')
|
||||
}
|
||||
}
|
||||
|
||||
// 创建菜单项
|
||||
pub fn (mut auth AuthManager) create_menu_item(name string, path string, icon string, parent_id int, sort_order int, permissions []string) !MenuItem {
|
||||
now := int(time.now().unix())
|
||||
permissions_json := json.encode(permissions)
|
||||
permissions_sql := permissions_json.replace('"', "''")
|
||||
|
||||
auth.db.db.exec('INSERT INTO menu_items (name, path, icon, parent_id, sort_order, permissions, status, created_at, updated_at) VALUES ("$name", "$path", "$icon", $parent_id, $sort_order, "$permissions_sql", 1, $now, $now)')
|
||||
or {
|
||||
eprintln('菜单插入SQL错误: $err')
|
||||
return error('Failed to create menu item: $err')
|
||||
}
|
||||
|
||||
// 获取插入的菜单ID
|
||||
rows := auth.db.db.exec('SELECT last_insert_rowid()') or {
|
||||
return error('Failed to get menu ID: $err')
|
||||
}
|
||||
menu_id := rows[0].vals[0].int()
|
||||
|
||||
return MenuItem{
|
||||
id: menu_id
|
||||
name: name
|
||||
path: path
|
||||
icon: icon
|
||||
parent_id: parent_id
|
||||
sort_order: sort_order
|
||||
permissions: permissions
|
||||
status: true
|
||||
created_at: now
|
||||
updated_at: now
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有菜单项
|
||||
pub fn (auth AuthManager) get_all_menu_items() ![]MenuItem {
|
||||
rows := auth.db.db.exec('SELECT id, name, path, icon, parent_id, sort_order, permissions, status FROM menu_items ORDER BY parent_id, sort_order') or {
|
||||
return error('Failed to query all menu items: $err')
|
||||
}
|
||||
|
||||
mut menus := []MenuItem{}
|
||||
for row in rows {
|
||||
permissions_str := row.vals[6]
|
||||
permissions := json.decode([]string, permissions_str) or { []string{} }
|
||||
|
||||
menus << MenuItem{
|
||||
id: row.vals[0].int()
|
||||
name: row.vals[1]
|
||||
path: row.vals[2]
|
||||
icon: row.vals[3]
|
||||
parent_id: row.vals[4].int()
|
||||
sort_order: row.vals[5].int()
|
||||
permissions: permissions
|
||||
status: row.vals[7].int() == 1
|
||||
created_at: 0
|
||||
updated_at: 0
|
||||
}
|
||||
}
|
||||
|
||||
return menus
|
||||
}
|
||||
|
||||
// 获取用户菜单
|
||||
pub fn (auth AuthManager) get_user_menus(user User) ![]MenuItem {
|
||||
// 根据用户角色获取菜单
|
||||
role_permissions := match user.role {
|
||||
.admin { ['*'] } // 管理员拥有所有权限
|
||||
.manager { ['read', 'write', 'manage'] }
|
||||
.user { ['read', 'write'] }
|
||||
.guest { ['read'] }
|
||||
}
|
||||
|
||||
// 构建权限查询条件
|
||||
mut permission_conditions := []string{}
|
||||
for permission in role_permissions {
|
||||
if permission == '*' {
|
||||
permission_conditions << 'permissions LIKE "%*%"'
|
||||
} else {
|
||||
permission_conditions << 'permissions LIKE "%$permission%"'
|
||||
}
|
||||
}
|
||||
permission_where := permission_conditions.join(' OR ')
|
||||
|
||||
rows := auth.db.db.exec('SELECT id, name, path, icon, parent_id, sort_order, permissions, status FROM menu_items WHERE status = 1 AND ($permission_where) ORDER BY parent_id, sort_order') or {
|
||||
return error('Failed to query menu items: $err')
|
||||
}
|
||||
|
||||
mut menus := []MenuItem{}
|
||||
for row in rows {
|
||||
permissions_str := row.vals[6]
|
||||
permissions := json.decode([]string, permissions_str) or { []string{} }
|
||||
|
||||
menus << MenuItem{
|
||||
id: row.vals[0].int()
|
||||
name: row.vals[1]
|
||||
path: row.vals[2]
|
||||
icon: row.vals[3]
|
||||
parent_id: row.vals[4].int()
|
||||
sort_order: row.vals[5].int()
|
||||
permissions: permissions
|
||||
status: row.vals[7].int() == 1
|
||||
created_at: 0
|
||||
updated_at: 0
|
||||
}
|
||||
}
|
||||
|
||||
return menus
|
||||
}
|
||||
|
||||
// 构建菜单树
|
||||
pub fn (auth AuthManager) build_menu_tree(menus []MenuItem) []MenuItem {
|
||||
mut menu_map := map[int]MenuItem{}
|
||||
mut root_menus := []MenuItem{}
|
||||
|
||||
// 创建菜单映射
|
||||
for menu in menus {
|
||||
menu_map[menu.id] = menu
|
||||
}
|
||||
|
||||
// 构建树结构
|
||||
for menu in menus {
|
||||
if menu.parent_id == 0 {
|
||||
// 根菜单
|
||||
mut root_menu := menu
|
||||
root_menu.children = auth.get_children(menu.id, menu_map)
|
||||
root_menus << root_menu
|
||||
}
|
||||
}
|
||||
|
||||
return root_menus
|
||||
}
|
||||
|
||||
// 获取子菜单
|
||||
fn (auth AuthManager) get_children(parent_id int, menu_map map[int]MenuItem) []MenuItem {
|
||||
mut children := []MenuItem{}
|
||||
|
||||
for _, menu in menu_map {
|
||||
if menu.parent_id == parent_id {
|
||||
mut child := menu
|
||||
child.children = auth.get_children(menu.id, menu_map)
|
||||
children << child
|
||||
}
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
// 检查用户权限
|
||||
pub fn (auth AuthManager) check_permission(user User, required_permission string) bool {
|
||||
return match user.role {
|
||||
.admin { true } // 管理员拥有所有权限
|
||||
.manager { required_permission in ['read', 'write', 'manage'] }
|
||||
.user { required_permission in ['read', 'write'] }
|
||||
.guest { required_permission == 'read' }
|
||||
}
|
||||
}
|
||||
|
||||
// 获取菜单id(通过path)
|
||||
pub fn (auth AuthManager) get_menu_id_by_path(path string) int {
|
||||
rows := auth.db.db.exec('SELECT id FROM menu_items WHERE path = "$path"') or { return 0 }
|
||||
if rows.len > 0 {
|
||||
return rows[0].vals[0].int()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
module hono
|
||||
|
||||
import json
|
||||
import net.http
|
||||
|
||||
// 登录请求结构
|
||||
pub struct LoginRequest {
|
||||
pub:
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
// 注册请求结构
|
||||
pub struct RegisterRequest {
|
||||
pub:
|
||||
username string
|
||||
email string
|
||||
password string
|
||||
role string
|
||||
}
|
||||
|
||||
// 菜单创建请求结构
|
||||
pub struct MenuCreateRequest {
|
||||
pub:
|
||||
name string
|
||||
path string
|
||||
icon string
|
||||
parent_id int
|
||||
sort_order int
|
||||
permissions []string
|
||||
}
|
||||
|
||||
// 认证中间件(只做校验,不注入 user)
|
||||
pub fn auth_middleware(auth_manager AuthManager) ContextMiddleware {
|
||||
return fn [auth_manager] (mut c Context, next fn (mut Context) http.Response) http.Response {
|
||||
token := c.req.header.get_custom('Authorization') or { '' }
|
||||
if token == '' {
|
||||
c.status(401)
|
||||
return c.json(json.encode({
|
||||
'error': 'Authorization token required'
|
||||
}))
|
||||
}
|
||||
_ := auth_manager.verify_token(token) or {
|
||||
c.status(401)
|
||||
return c.json(json.encode({
|
||||
'error': 'Invalid or expired token'
|
||||
}))
|
||||
}
|
||||
return next(mut c)
|
||||
}
|
||||
}
|
||||
|
||||
// 权限检查中间件(直接校验 token 权限)
|
||||
pub fn permission_middleware(auth_manager AuthManager, required_permission string) ContextMiddleware {
|
||||
return fn [auth_manager, required_permission] (mut c Context, next fn (mut Context) http.Response) http.Response {
|
||||
token := c.req.header.get_custom('Authorization') or { '' }
|
||||
user := auth_manager.verify_token(token) or {
|
||||
c.status(401)
|
||||
return c.json(json.encode({
|
||||
'error': 'Invalid or expired token'
|
||||
}))
|
||||
}
|
||||
if !auth_manager.check_permission(user, required_permission) {
|
||||
c.status(403)
|
||||
return c.json(json.encode({
|
||||
'error': 'Insufficient permissions'
|
||||
}))
|
||||
}
|
||||
return next(mut c)
|
||||
}
|
||||
}
|
||||
|
||||
// 注册认证路由
|
||||
pub fn register_auth_routes(mut app Hono, mut auth_manager AuthManager) {
|
||||
// 登录路由
|
||||
app.post('/api/auth/login', fn [mut auth_manager] (mut c Context) http.Response {
|
||||
body := c.body
|
||||
login_req := json.decode(LoginRequest, body) or {
|
||||
c.status(400)
|
||||
return c.json(json.encode({
|
||||
'error': 'Invalid request body'
|
||||
}))
|
||||
}
|
||||
session := auth_manager.login(login_req.username, login_req.password) or {
|
||||
c.status(401)
|
||||
return c.json(json.encode({
|
||||
'error': err.msg()
|
||||
}))
|
||||
}
|
||||
return c.json(json.encode({
|
||||
'token': session.token.str()
|
||||
'expires_at': session.expires_at.str()
|
||||
}))
|
||||
})
|
||||
|
||||
// 注册路由
|
||||
app.post('/api/auth/register', fn [mut auth_manager] (mut c Context) http.Response {
|
||||
body := c.body
|
||||
register_req := json.decode(RegisterRequest, body) or {
|
||||
c.status(400)
|
||||
return c.json(json.encode({
|
||||
'error': 'Invalid request body'
|
||||
}))
|
||||
}
|
||||
role := match register_req.role {
|
||||
'admin' { UserRole.admin }
|
||||
'manager' { UserRole.manager }
|
||||
'user' { UserRole.user }
|
||||
'guest' { UserRole.guest }
|
||||
else { UserRole.user }
|
||||
}
|
||||
user := auth_manager.create_user(register_req.username, register_req.email, register_req.password, role) or {
|
||||
c.status(400)
|
||||
return c.json(json.encode({
|
||||
'error': err.msg()
|
||||
}))
|
||||
}
|
||||
return c.json(json.encode({
|
||||
'user_id': user.id.str()
|
||||
'username': user.username
|
||||
'email': user.email
|
||||
'role': user.role.str()
|
||||
}))
|
||||
})
|
||||
|
||||
// 注销路由
|
||||
app.post('/api/auth/logout', fn [mut auth_manager] (mut c Context) http.Response {
|
||||
token := c.req.header.get_custom('Authorization') or { '' }
|
||||
if token == '' {
|
||||
c.status(401)
|
||||
return c.json(json.encode({
|
||||
'error': 'Authorization token required'
|
||||
}))
|
||||
}
|
||||
auth_manager.logout(token) or {
|
||||
c.status(500)
|
||||
return c.json(json.encode({
|
||||
'error': err.msg()
|
||||
}))
|
||||
}
|
||||
return c.json(json.encode({
|
||||
'message': 'Logged out successfully'
|
||||
}))
|
||||
})
|
||||
|
||||
// 获取用户信息路由
|
||||
app.get('/api/auth/profile', fn [auth_manager] (mut c Context) http.Response {
|
||||
token := c.req.header.get_custom('Authorization') or { '' }
|
||||
user := auth_manager.verify_token(token) or {
|
||||
c.status(401)
|
||||
return c.json(json.encode({
|
||||
'error': 'Invalid or expired token'
|
||||
}))
|
||||
}
|
||||
return c.json(json.encode({
|
||||
'user_id': user.id.str()
|
||||
'username': user.username
|
||||
'email': user.email
|
||||
'role': user.role.str()
|
||||
'active': user.status.str()
|
||||
}))
|
||||
})
|
||||
|
||||
// 获取用户菜单路由
|
||||
app.get('/api/auth/menus', fn [auth_manager] (mut c Context) http.Response {
|
||||
token := c.req.header.get_custom('Authorization') or { '' }
|
||||
user := auth_manager.verify_token(token) or {
|
||||
c.status(401)
|
||||
return c.json(json.encode({
|
||||
'error': 'Invalid or expired token'
|
||||
}))
|
||||
}
|
||||
menus := auth_manager.get_user_menus(user) or {
|
||||
c.status(500)
|
||||
return c.json(json.encode({
|
||||
'error': err.msg()
|
||||
}))
|
||||
}
|
||||
return c.json(json.encode({
|
||||
'menus': menus
|
||||
}))
|
||||
})
|
||||
|
||||
// 创建菜单项路由 (需要管理员权限)
|
||||
app.post('/api/auth/menus', fn [mut auth_manager] (mut c Context) http.Response {
|
||||
body := c.body
|
||||
menu_req := json.decode(MenuCreateRequest, body) or {
|
||||
c.status(400)
|
||||
return c.json(json.encode({
|
||||
'error': 'Invalid request body'
|
||||
}))
|
||||
}
|
||||
menu := auth_manager.create_menu_item(menu_req.name, menu_req.path, menu_req.icon, menu_req.parent_id, menu_req.sort_order, menu_req.permissions) or {
|
||||
c.status(400)
|
||||
return c.json(json.encode({
|
||||
'error': err.msg()
|
||||
}))
|
||||
}
|
||||
return c.json(json.encode({
|
||||
'menu_id': menu.id.str()
|
||||
'name': menu.name
|
||||
'path': menu.path
|
||||
'icon': menu.icon
|
||||
'parent_id': menu.parent_id.str()
|
||||
'sort_order': menu.sort_order.str()
|
||||
'permissions': json.encode(menu.permissions)
|
||||
'active': menu.status.str()
|
||||
}))
|
||||
})
|
||||
|
||||
// 获取所有菜单项路由 (需要管理员权限)
|
||||
app.get('/api/auth/menus/all', fn [auth_manager] (mut c Context) http.Response {
|
||||
menus := auth_manager.get_all_menu_items() or {
|
||||
c.status(500)
|
||||
return c.json(json.encode({
|
||||
'error': err.msg()
|
||||
}))
|
||||
}
|
||||
return c.json(json.encode({
|
||||
'menus': menus
|
||||
}))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>用户角色菜单管理系统</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.content {
|
||||
padding: 30px;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
input, select, button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.result {
|
||||
margin-top: 15px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
background: #f0f0f0;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
.menu-tree {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.menu-item {
|
||||
padding: 8px;
|
||||
margin: 2px 0;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #667eea;
|
||||
}
|
||||
.menu-item .icon {
|
||||
margin-right: 8px;
|
||||
color: #667eea;
|
||||
}
|
||||
.submenu {
|
||||
margin-left: 20px;
|
||||
}
|
||||
.error {
|
||||
background: #ffebee;
|
||||
border-left-color: #f44336;
|
||||
color: #c62828;
|
||||
}
|
||||
.success {
|
||||
background: #e8f5e8;
|
||||
border-left-color: #4caf50;
|
||||
color: #2e7d32;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>用户角色菜单管理系统</h1>
|
||||
<p>基于 v-hono 框架的用户认证和权限管理</p>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<!-- 用户注册 -->
|
||||
<div class="section">
|
||||
<h2>用户注册</h2>
|
||||
<div class="form-group">
|
||||
<label for="reg-username">用户名:</label>
|
||||
<input type="text" id="reg-username" placeholder="输入用户名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="reg-email">邮箱:</label>
|
||||
<input type="email" id="reg-email" placeholder="输入邮箱">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="reg-password">密码:</label>
|
||||
<input type="password" id="reg-password" placeholder="输入密码">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="reg-role">角色:</label>
|
||||
<select id="reg-role">
|
||||
<option value="user">普通用户</option>
|
||||
<option value="manager">管理员</option>
|
||||
<option value="admin">超级管理员</option>
|
||||
<option value="guest">访客</option>
|
||||
</select>
|
||||
</div>
|
||||
<button onclick="register()">注册</button>
|
||||
<div id="reg-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 用户登录 -->
|
||||
<div class="section">
|
||||
<h2>用户登录</h2>
|
||||
<div class="form-group">
|
||||
<label for="login-username">用户名:</label>
|
||||
<input type="text" id="login-username" placeholder="输入用户名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="login-password">密码:</label>
|
||||
<input type="password" id="login-password" placeholder="输入密码">
|
||||
</div>
|
||||
<button onclick="login()">登录</button>
|
||||
<div id="login-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<div class="section">
|
||||
<h2>用户信息</h2>
|
||||
<button onclick="getProfile()">获取用户信息</button>
|
||||
<div id="profile-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 菜单管理 -->
|
||||
<div class="section">
|
||||
<h2>菜单管理</h2>
|
||||
<button onclick="getMenus()">获取用户菜单</button>
|
||||
<button onclick="getAllMenus()">获取所有菜单</button>
|
||||
<div id="menus-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 受保护的路由测试 -->
|
||||
<div class="section">
|
||||
<h2>权限测试</h2>
|
||||
<button onclick="testUserRoute()">测试用户路由</button>
|
||||
<button onclick="testAdminRoute()">测试管理员路由</button>
|
||||
<div id="test-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
|
||||
<!-- 注销 -->
|
||||
<div class="section">
|
||||
<h2>注销</h2>
|
||||
<button onclick="logout()">注销</button>
|
||||
<div id="logout-result" class="result" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let authToken = '';
|
||||
|
||||
function showResult(elementId, message, isError = false) {
|
||||
const element = document.getElementById(elementId);
|
||||
element.textContent = message;
|
||||
element.style.display = 'block';
|
||||
element.className = 'result ' + (isError ? 'error' : 'success');
|
||||
}
|
||||
|
||||
function hideResult(elementId) {
|
||||
document.getElementById(elementId).style.display = 'none';
|
||||
}
|
||||
|
||||
async function register() {
|
||||
hideResult('reg-result');
|
||||
const username = document.getElementById('reg-username').value;
|
||||
const email = document.getElementById('reg-email').value;
|
||||
const password = document.getElementById('reg-password').value;
|
||||
const role = document.getElementById('reg-role').value;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ username, email, password, role })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showResult('reg-result', '注册成功: ' + JSON.stringify(data, null, 2));
|
||||
} else {
|
||||
showResult('reg-result', '注册失败: ' + data.error, true);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('reg-result', '请求失败: ' + error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function login() {
|
||||
hideResult('login-result');
|
||||
const username = document.getElementById('login-username').value;
|
||||
const password = document.getElementById('login-password').value;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
authToken = data.token;
|
||||
showResult('login-result', '登录成功: ' + JSON.stringify(data, null, 2));
|
||||
} else {
|
||||
showResult('login-result', '登录失败: ' + data.error, true);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('login-result', '请求失败: ' + error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function getProfile() {
|
||||
hideResult('profile-result');
|
||||
if (!authToken) {
|
||||
showResult('profile-result', '请先登录', true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/profile', {
|
||||
headers: {
|
||||
'Authorization': authToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showResult('profile-result', '用户信息: ' + JSON.stringify(data, null, 2));
|
||||
} else {
|
||||
showResult('profile-result', '获取失败: ' + data.error, true);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('profile-result', '请求失败: ' + error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function getMenus() {
|
||||
hideResult('menus-result');
|
||||
if (!authToken) {
|
||||
showResult('menus-result', '请先登录', true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/menus', {
|
||||
headers: {
|
||||
'Authorization': authToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
const menuHtml = renderMenuTree(data.menus);
|
||||
showResult('menus-result', '用户菜单: ' + menuHtml);
|
||||
} else {
|
||||
showResult('menus-result', '获取失败: ' + data.error, true);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('menus-result', '请求失败: ' + error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllMenus() {
|
||||
hideResult('menus-result');
|
||||
if (!authToken) {
|
||||
showResult('menus-result', '请先登录', true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/menus/all', {
|
||||
headers: {
|
||||
'Authorization': authToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
const menuHtml = renderMenuTree(data.menus);
|
||||
showResult('menus-result', '所有菜单: ' + menuHtml);
|
||||
} else {
|
||||
showResult('menus-result', '获取失败: ' + data.error, true);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('menus-result', '请求失败: ' + error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
function renderMenuTree(menus) {
|
||||
if (!menus || menus.length === 0) {
|
||||
return '<div class="menu-tree">暂无菜单</div>';
|
||||
}
|
||||
|
||||
let html = '<div class="menu-tree">';
|
||||
for (const menu of menus) {
|
||||
html += renderMenuItem(menu);
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function renderMenuItem(menu) {
|
||||
let html = '<div class="menu-item">';
|
||||
html += '<span class="icon">' + (menu.icon || '📄') + '</span>';
|
||||
html += '<strong>' + menu.name + '</strong> (' + menu.path + ')';
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
html += '<div class="submenu">';
|
||||
for (const child of menu.children) {
|
||||
html += renderMenuItem(child);
|
||||
}
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
async function testUserRoute() {
|
||||
hideResult('test-result');
|
||||
if (!authToken) {
|
||||
showResult('test-result', '请先登录', true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/protected/user', {
|
||||
headers: {
|
||||
'Authorization': authToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showResult('test-result', '用户路由测试成功: ' + JSON.stringify(data, null, 2));
|
||||
} else {
|
||||
showResult('test-result', '测试失败: ' + data.error, true);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('test-result', '请求失败: ' + error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function testAdminRoute() {
|
||||
hideResult('test-result');
|
||||
if (!authToken) {
|
||||
showResult('test-result', '请先登录', true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/protected/admin', {
|
||||
headers: {
|
||||
'Authorization': authToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
showResult('test-result', '管理员路由测试成功: ' + JSON.stringify(data, null, 2));
|
||||
} else {
|
||||
showResult('test-result', '测试失败: ' + data.error, true);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('test-result', '请求失败: ' + error.message, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
hideResult('logout-result');
|
||||
if (!authToken) {
|
||||
showResult('logout-result', '请先登录', true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/logout', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': authToken
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
authToken = '';
|
||||
showResult('logout-result', '注销成功: ' + data.message);
|
||||
} else {
|
||||
showResult('logout-result', '注销失败: ' + data.error, true);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('logout-result', '请求失败: ' + error.message, true);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user