2026-03-18 19:12:38 +08:00
|
|
|
|
# 权限系统设计文档 (Crawlful Hub)
|
|
|
|
|
|
|
|
|
|
|
|
> **定位**:Crawlful Hub 权限系统设计文档 - 定义基于角色的访问控制(RBAC)和数据级权限。
|
|
|
|
|
|
> **更新日期**: 2026-03-18
|
|
|
|
|
|
> **最高优先级参考**: [Service_Design.md](./Service_Design.md)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 权限系统概述
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 核心概念
|
|
|
|
|
|
|
|
|
|
|
|
- **角色(Role)**:一组权限的集合
|
|
|
|
|
|
- **权限(Permission)**:对资源的操作许可
|
|
|
|
|
|
- **用户(User)**:系统的使用者
|
|
|
|
|
|
- **资源(Resource)**:系统中的对象,如商品、订单、用户等
|
|
|
|
|
|
- **数据级权限**:基于数据属性的访问控制
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 重要性
|
|
|
|
|
|
|
|
|
|
|
|
良好的权限系统可以:
|
|
|
|
|
|
- 保护系统安全
|
|
|
|
|
|
- 确保数据隐私
|
|
|
|
|
|
- 实现职责分离
|
|
|
|
|
|
- 满足合规要求
|
|
|
|
|
|
- 提供可审计性
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. RBAC 模型
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 角色定义
|
|
|
|
|
|
|
|
|
|
|
|
| 角色 | 描述 | 权限范围 |
|
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
|
| **ADMIN** | 系统管理员 | 所有资源的所有操作 |
|
|
|
|
|
|
| **MANAGER** | 运营主管 | 大部分资源的管理操作 |
|
|
|
|
|
|
| **OPERATOR** | 运营专员 | 基础运营操作 |
|
|
|
|
|
|
| **FINANCE** | 财务主管 | 财务相关操作 |
|
|
|
|
|
|
| **SOURCING** | 采购专家 | 采购相关操作 |
|
|
|
|
|
|
| **LOGISTICS** | 物流专家 | 物流相关操作 |
|
|
|
|
|
|
| **ANALYST** | 数据分析师 | 数据分析相关操作 |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 权限定义
|
|
|
|
|
|
|
|
|
|
|
|
| 权限 | 描述 | 适用资源 |
|
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
|
| **CREATE** | 创建资源 | 所有资源 |
|
|
|
|
|
|
| **READ** | 读取资源 | 所有资源 |
|
|
|
|
|
|
| **UPDATE** | 更新资源 | 所有资源 |
|
|
|
|
|
|
| **DELETE** | 删除资源 | 所有资源 |
|
|
|
|
|
|
| **APPROVE** | 审批操作 | 订单、结算等 |
|
|
|
|
|
|
| **EXPORT** | 导出数据 | 报表、数据等 |
|
|
|
|
|
|
| **IMPORT** | 导入数据 | 商品、用户等 |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3 权限分配
|
|
|
|
|
|
|
|
|
|
|
|
**示例**:
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 角色权限映射
|
|
|
|
|
|
const rolePermissions = {
|
|
|
|
|
|
ADMIN: ['*'], // 所有权限
|
|
|
|
|
|
MANAGER: [
|
|
|
|
|
|
'CREATE:product', 'READ:product', 'UPDATE:product', 'DELETE:product',
|
|
|
|
|
|
'CREATE:order', 'READ:order', 'UPDATE:order', 'APPROVE:order',
|
|
|
|
|
|
'READ:report', 'EXPORT:report'
|
|
|
|
|
|
],
|
|
|
|
|
|
OPERATOR: [
|
|
|
|
|
|
'READ:product', 'UPDATE:product',
|
|
|
|
|
|
'READ:order', 'UPDATE:order'
|
|
|
|
|
|
],
|
|
|
|
|
|
FINANCE: [
|
|
|
|
|
|
'READ:order', 'READ:settlement', 'APPROVE:settlement',
|
|
|
|
|
|
'READ:report', 'EXPORT:report'
|
|
|
|
|
|
],
|
|
|
|
|
|
SOURCING: [
|
|
|
|
|
|
'CREATE:product', 'READ:product', 'UPDATE:product',
|
|
|
|
|
|
'READ:supplier', 'CREATE:supplier', 'UPDATE:supplier'
|
|
|
|
|
|
],
|
|
|
|
|
|
LOGISTICS: [
|
|
|
|
|
|
'READ:order', 'UPDATE:order',
|
|
|
|
|
|
'READ:inventory', 'UPDATE:inventory'
|
|
|
|
|
|
],
|
|
|
|
|
|
ANALYST: [
|
|
|
|
|
|
'READ:report', 'EXPORT:report'
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 数据级权限
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 定义
|
|
|
|
|
|
|
|
|
|
|
|
数据级权限是指基于数据属性的访问控制,确保用户只能访问和操作与自己相关的数据。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 实现方法
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.2.1 租户隔离
|
|
|
|
|
|
|
|
|
|
|
|
**定义**:不同租户的数据相互隔离
|
|
|
|
|
|
|
|
|
|
|
|
**实现**:
|
|
|
|
|
|
- 在所有数据表中添加 `tenantId` 字段
|
|
|
|
|
|
- 在所有查询中默认带上 `tenantId` 过滤
|
|
|
|
|
|
- 确保跨租户操作的安全性
|
|
|
|
|
|
|
|
|
|
|
|
**示例**:
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 数据访问层
|
|
|
|
|
|
class ProductRepository {
|
|
|
|
|
|
async findAll(tenantId: string, params?: any): Promise<Product[]> {
|
|
|
|
|
|
return await Product.findAll({
|
|
|
|
|
|
where: {
|
|
|
|
|
|
tenantId,
|
|
|
|
|
|
...params
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async findById(id: string, tenantId: string): Promise<Product | null> {
|
|
|
|
|
|
return await Product.findOne({
|
|
|
|
|
|
where: {
|
|
|
|
|
|
id,
|
|
|
|
|
|
tenantId
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.2.2 店铺隔离
|
|
|
|
|
|
|
|
|
|
|
|
**定义**:同一租户下不同店铺的数据相互隔离
|
|
|
|
|
|
|
|
|
|
|
|
**实现**:
|
|
|
|
|
|
- 在相关数据表中添加 `shopId` 字段
|
|
|
|
|
|
- 在查询中根据用户的店铺权限过滤
|
|
|
|
|
|
- 确保用户只能访问自己有权限的店铺数据
|
|
|
|
|
|
|
|
|
|
|
|
**示例**:
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 权限服务
|
|
|
|
|
|
class RBACService {
|
|
|
|
|
|
async checkShopAccess(userId: string, shopId: string): Promise<boolean> {
|
|
|
|
|
|
const user = await this.userRepository.findById(userId);
|
|
|
|
|
|
const userShops = await this.userShopRepository.findByUserId(userId);
|
|
|
|
|
|
return userShops.some(us => us.shopId === shopId) || user.role === 'ADMIN';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 订单服务
|
|
|
|
|
|
class OrderService {
|
|
|
|
|
|
async getOrders(userId: string, params?: any): Promise<Order[]> {
|
|
|
|
|
|
const user = await this.userRepository.findById(userId);
|
|
|
|
|
|
|
|
|
|
|
|
if (user.role === 'ADMIN') {
|
|
|
|
|
|
return await this.orderRepository.findAll(params);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const userShops = await this.userShopRepository.findByUserId(userId);
|
|
|
|
|
|
const shopIds = userShops.map(us => us.shopId);
|
|
|
|
|
|
|
|
|
|
|
|
return await this.orderRepository.findAll({
|
|
|
|
|
|
...params,
|
|
|
|
|
|
shopId: shopIds
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.2.3 字段级权限
|
|
|
|
|
|
|
|
|
|
|
|
**定义**:基于字段的访问控制
|
|
|
|
|
|
|
|
|
|
|
|
**实现**:
|
|
|
|
|
|
- 定义字段级权限规则
|
|
|
|
|
|
- 在返回数据时过滤敏感字段
|
|
|
|
|
|
- 确保用户只能看到和修改自己有权限的字段
|
|
|
|
|
|
|
|
|
|
|
|
**示例**:
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 字段级权限
|
|
|
|
|
|
const fieldPermissions = {
|
|
|
|
|
|
ADMIN: {
|
|
|
|
|
|
product: ['*'], // 所有字段
|
|
|
|
|
|
user: ['*']
|
|
|
|
|
|
},
|
|
|
|
|
|
MANAGER: {
|
|
|
|
|
|
product: ['id', 'name', 'price', 'stock'],
|
|
|
|
|
|
user: ['id', 'name', 'email']
|
|
|
|
|
|
},
|
|
|
|
|
|
OPERATOR: {
|
|
|
|
|
|
product: ['id', 'name', 'price'],
|
|
|
|
|
|
user: ['id', 'name']
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 数据过滤
|
|
|
|
|
|
function filterFields(data: any, fields: string[]): any {
|
|
|
|
|
|
if (fields.includes('*')) {
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const filtered: any = {};
|
|
|
|
|
|
fields.forEach(field => {
|
|
|
|
|
|
if (data[field] !== undefined) {
|
|
|
|
|
|
filtered[field] = data[field];
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return filtered;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 最佳实践
|
|
|
|
|
|
|
|
|
|
|
|
- **默认带租户ID**:所有查询默认带 `tenantId` 过滤
|
|
|
|
|
|
- **权限检查前置**:在业务逻辑开始前检查权限
|
|
|
|
|
|
- **统一权限服务**:使用统一的权限服务处理权限检查
|
|
|
|
|
|
- **缓存权限**:缓存用户权限,提高性能
|
|
|
|
|
|
- **审计日志**:记录权限相关操作的审计日志
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 权限验证
|
|
|
|
|
|
|
|
|
|
|
|
### 4.1 实现方法
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.1.1 中间件验证
|
|
|
|
|
|
|
|
|
|
|
|
**适用场景**:API 路由权限验证
|
|
|
|
|
|
|
|
|
|
|
|
**实现**:
|
|
|
|
|
|
- 创建权限验证中间件
|
|
|
|
|
|
- 在路由中应用中间件
|
|
|
|
|
|
- 验证用户是否有权限访问资源
|
|
|
|
|
|
|
|
|
|
|
|
**示例**:
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 权限中间件
|
|
|
|
|
|
const authorize = (requiredPermission: string) => {
|
|
|
|
|
|
return (req, res, next) => {
|
|
|
|
|
|
const user = req.user;
|
|
|
|
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!user.permissions.includes(requiredPermission) && user.role !== 'ADMIN') {
|
|
|
|
|
|
return res.status(403).json({ error: 'Forbidden' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
next();
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 使用
|
|
|
|
|
|
app.get('/products', authorize('READ:product'), productController.getProducts);
|
|
|
|
|
|
app.post('/products', authorize('CREATE:product'), productController.createProduct);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 4.1.2 服务层验证
|
|
|
|
|
|
|
|
|
|
|
|
**适用场景**:业务逻辑权限验证
|
|
|
|
|
|
|
|
|
|
|
|
**实现**:
|
|
|
|
|
|
- 在服务层检查权限
|
|
|
|
|
|
- 验证用户是否有权限操作资源
|
|
|
|
|
|
- 验证用户是否有权限访问数据
|
|
|
|
|
|
|
|
|
|
|
|
**示例**:
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
async updateProduct(productId: string, updates: Partial<Product>, userId: string): Promise<Product> {
|
|
|
|
|
|
// 检查操作权限
|
|
|
|
|
|
if (!this.rbacService.hasPermission(userId, 'UPDATE:product')) {
|
|
|
|
|
|
throw new ForbiddenException('You do not have permission to update products');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取商品
|
|
|
|
|
|
const product = await this.productRepository.findById(productId);
|
|
|
|
|
|
if (!product) {
|
|
|
|
|
|
throw new NotFoundException('Product not found');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查数据权限
|
|
|
|
|
|
if (!this.rbacService.hasDataAccess(userId, product.tenantId, product.shopId)) {
|
|
|
|
|
|
throw new ForbiddenException('You do not have access to this product');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新商品
|
|
|
|
|
|
Object.assign(product, updates);
|
|
|
|
|
|
return await this.productRepository.save(product);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4.2 最佳实践
|
|
|
|
|
|
|
|
|
|
|
|
- **多层验证**:在中间件和服务层都进行权限验证
|
|
|
|
|
|
- **明确的错误信息**:返回清晰的权限错误信息
|
|
|
|
|
|
- **权限缓存**:缓存用户权限,提高验证性能
|
|
|
|
|
|
- **定期权限检查**:定期检查权限配置的正确性
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 权限管理
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 角色管理
|
|
|
|
|
|
|
|
|
|
|
|
- **创建角色**:定义新角色
|
|
|
|
|
|
- **修改角色**:修改角色的权限
|
|
|
|
|
|
- **删除角色**:删除不需要的角色
|
|
|
|
|
|
- **分配角色**:为用户分配角色
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 权限管理
|
|
|
|
|
|
|
|
|
|
|
|
- **定义权限**:定义新的权限
|
|
|
|
|
|
- **分配权限**:为角色分配权限
|
|
|
|
|
|
- **回收权限**:从角色中回收权限
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 用户管理
|
|
|
|
|
|
|
|
|
|
|
|
- **创建用户**:创建新用户并分配角色
|
|
|
|
|
|
- **修改用户**:修改用户的角色和权限
|
|
|
|
|
|
- **删除用户**:删除用户
|
|
|
|
|
|
- **禁用用户**:暂时禁用用户
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 6. 安全考虑
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 防止权限提升
|
|
|
|
|
|
|
|
|
|
|
|
- **最小权限原则**:用户只获得必要的权限
|
|
|
|
|
|
- **权限审计**:定期审计权限配置
|
|
|
|
|
|
- **权限边界**:明确权限的边界
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 防止数据泄露
|
|
|
|
|
|
|
|
|
|
|
|
- **数据加密**:加密敏感数据
|
|
|
|
|
|
- **访问控制**:严格控制数据访问
|
|
|
|
|
|
- **审计日志**:记录数据访问日志
|
|
|
|
|
|
|
|
|
|
|
|
### 6.3 防止暴力破解
|
|
|
|
|
|
|
|
|
|
|
|
- **密码策略**:强密码要求
|
|
|
|
|
|
- **登录限制**:限制登录尝试次数
|
|
|
|
|
|
- **验证码**:使用验证码防止暴力破解
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 7. 测试策略
|
|
|
|
|
|
|
|
|
|
|
|
### 7.1 单元测试
|
|
|
|
|
|
|
|
|
|
|
|
- 测试权限验证逻辑
|
|
|
|
|
|
- 测试角色权限映射
|
|
|
|
|
|
- 测试数据级权限过滤
|
|
|
|
|
|
|
|
|
|
|
|
### 7.2 集成测试
|
|
|
|
|
|
|
|
|
|
|
|
- 测试完整的权限流程
|
|
|
|
|
|
- 测试不同角色的访问控制
|
|
|
|
|
|
- 测试数据级权限的有效性
|
|
|
|
|
|
|
|
|
|
|
|
### 7.3 安全测试
|
|
|
|
|
|
|
|
|
|
|
|
- 测试权限提升攻击
|
|
|
|
|
|
- 测试数据泄露风险
|
|
|
|
|
|
- 测试暴力破解防护
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 8. 相关文档
|
|
|
|
|
|
|
|
|
|
|
|
- [Service_Design.md](./Service_Design.md)
|
|
|
|
|
|
- [Data_Consistency.md](./Data_Consistency.md)
|
|
|
|
|
|
- [API_Specs](./API_Specs/)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-23 12:41:35 +08:00
|
|
|
|
*本文档基于服务设计文档,最后更新: 2026-03-18*
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 9. 组织层级与数据范围(Organization & Data Scope)
|
|
|
|
|
|
|
|
|
|
|
|
### 9.1 组织层级模型
|
|
|
|
|
|
|
|
|
|
|
|
**核心原则**:
|
|
|
|
|
|
> ❗ 权限 = 能做什么(Permission)
|
|
|
|
|
|
> ❗ 数据范围 = 能操作谁的数据(Scope)
|
|
|
|
|
|
> 👉 两者必须同时存在
|
|
|
|
|
|
|
|
|
|
|
|
**多租户层级结构**:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
平台(Platform)
|
|
|
|
|
|
↓
|
|
|
|
|
|
商户/公司(Tenant/Organization)
|
|
|
|
|
|
↓
|
|
|
|
|
|
组织结构(Org Tree)
|
|
|
|
|
|
├── 部门A(主管)
|
|
|
|
|
|
│ ├── 组A1(组长)
|
|
|
|
|
|
│ │ ├── 员工1
|
|
|
|
|
|
│ │ └── 员工2
|
|
|
|
|
|
│ └── 组A2(组长)
|
|
|
|
|
|
│ └── 员工3
|
|
|
|
|
|
└── 部门B(主管)
|
|
|
|
|
|
└── ...
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 9.2 数据范围定义
|
|
|
|
|
|
|
|
|
|
|
|
| 范围类型 | 英文 | 说明 | SQL过滤条件 |
|
|
|
|
|
|
|---------|------|------|------------|
|
|
|
|
|
|
| **SELF** | Self | 只看自己 | `WHERE created_by = {userId}` |
|
|
|
|
|
|
| **TEAM** | Team | 看自己组 | `WHERE team_id IN ({userTeams})` |
|
|
|
|
|
|
| **DEPT** | Department | 看自己部门 | `WHERE dept_id IN ({userDepts})` |
|
|
|
|
|
|
| **ORG** | Organization | 看整个公司 | `WHERE tenant_id = {tenantId}` |
|
|
|
|
|
|
| **ALL** | All | 全平台(超管) | 无过滤 |
|
|
|
|
|
|
|
|
|
|
|
|
### 9.3 数据范围实现
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
interface DataScopeContext {
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
tenantId: string;
|
|
|
|
|
|
orgId: string;
|
|
|
|
|
|
scopeType: 'SELF' | 'TEAM' | 'DEPT' | 'ORG' | 'ALL';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class DataScopeService {
|
|
|
|
|
|
async buildScopeQuery(ctx: DataScopeContext): Promise<WhereClause> {
|
|
|
|
|
|
switch (ctx.scopeType) {
|
|
|
|
|
|
case 'SELF':
|
|
|
|
|
|
return { created_by: ctx.userId };
|
|
|
|
|
|
|
|
|
|
|
|
case 'TEAM':
|
|
|
|
|
|
const teams = await this.getUserTeams(ctx.userId);
|
|
|
|
|
|
return { team_id: { in: teams } };
|
|
|
|
|
|
|
|
|
|
|
|
case 'DEPT':
|
|
|
|
|
|
const depts = await this.getUserDepts(ctx.userId);
|
|
|
|
|
|
return { dept_id: { in: depts } };
|
|
|
|
|
|
|
|
|
|
|
|
case 'ORG':
|
|
|
|
|
|
return { tenant_id: ctx.tenantId };
|
|
|
|
|
|
|
|
|
|
|
|
case 'ALL':
|
|
|
|
|
|
return {};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 9.4 店铺角色权限
|
|
|
|
|
|
|
|
|
|
|
|
| 角色 | 描述 | 权限范围 |
|
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
|
| **owner** | 拥有者 | 删除店铺、管理授权、管理成员、所有权限 |
|
|
|
|
|
|
| **admin** | 管理员 | 管理商品、管理价格、管理订单、不可删除店铺 |
|
|
|
|
|
|
| **operator** | 运营 | 刊登、改价、查看数据 |
|
|
|
|
|
|
| **viewer** | 只读 | 查看数据、不可操作 |
|
|
|
|
|
|
|
|
|
|
|
|
### 9.5 授权模型
|
|
|
|
|
|
|
|
|
|
|
|
**核心原则**:
|
|
|
|
|
|
> ❗ 授权属于店铺,不属于用户
|
|
|
|
|
|
> ❗ 店铺属于主体(Owner),用户只是被授权使用
|
|
|
|
|
|
|
|
|
|
|
|
**店铺授权结构**:
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Organization(公司)
|
|
|
|
|
|
↓
|
|
|
|
|
|
Shop(店铺)
|
|
|
|
|
|
↓
|
|
|
|
|
|
Auth(授权)
|
|
|
|
|
|
↑
|
|
|
|
|
|
User(使用者)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**授权类型**:
|
|
|
|
|
|
|
|
|
|
|
|
| 类型 | 适用平台 | 存储内容 |
|
|
|
|
|
|
|------|----------|----------|
|
|
|
|
|
|
| **API授权** | Shopify、Amazon | access_token, refresh_token, expire_time |
|
|
|
|
|
|
| **Agent授权** | TikTok、Shopee | cookies, proxy, device_id, user_agent |
|
|
|
|
|
|
|
|
|
|
|
|
### 9.6 权限校验中间件
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
const authorize = (requiredPermission: string) => {
|
|
|
|
|
|
return async (req, res, next) => {
|
|
|
|
|
|
const user = req.user;
|
|
|
|
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const hasPermission = await rbacService.checkPermission(
|
|
|
|
|
|
user.id,
|
|
|
|
|
|
requiredPermission,
|
|
|
|
|
|
req.params.shopId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (!hasPermission) {
|
|
|
|
|
|
return res.status(403).json({ error: 'Forbidden' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const scopeQuery = await dataScopeService.buildScopeQuery({
|
|
|
|
|
|
userId: user.id,
|
|
|
|
|
|
tenantId: user.tenantId,
|
|
|
|
|
|
orgId: user.orgId,
|
|
|
|
|
|
scopeType: user.dataScope
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
req.scopeQuery = scopeQuery;
|
|
|
|
|
|
next();
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|