Skip to content
go

// Client (租户)
type Client struct {
	PK          string `dynamodbav:"PK"` // CLIENT#{client_id}
	SK          string `dynamodbav:"SK"` // METADATA
	ID          string `dynamodbav:"id"`
	Name        string `dynamodbav:"name"`
	Description string `dynamodbav:"description"`
	Status      string `dynamodbav:"status"`
	CreatedAt   string `dynamodbav:"created_at"`
	CreatedBy   string `dynamodbav:"created_by"`
	UpdatedAt   string `dynamodbav:"updated_at"`
	Type        string `dynamodbav:"Type"` // "CLIENT"
	GSI1PK      string `dynamodbav:"GSI1PK"`
	GSI1SK      string `dynamodbav:"GSI1SK"`
	GSI4PK      string `dynamodbav:"GSI4PK"`
	GSI4SK      string `dynamodbav:"GSI4SK"`
}

// Project
type Project struct {
	PK          string `dynamodbav:"PK"` // CLIENT#{client_id}
	SK          string `dynamodbav:"SK"` // PROJECT#{project_id}
	ID          string `dynamodbav:"id"`
	Name        string `dynamodbav:"name"`
	ClientID    string `dynamodbav:"client_id"`
	Description string `dynamodbav:"description"`
	Status      string `dynamodbav:"status"`
	CreatedAt   string `dynamodbav:"created_at"`
	CreatedBy   string `dynamodbav:"created_by"`
	UpdatedAt   string `dynamodbav:"updated_at"`
	Type        string `dynamodbav:"Type"` // "PROJECT"
	GSI1PK      string `dynamodbav:"GSI1PK"`
	GSI1SK      string `dynamodbav:"GSI1SK"`
	GSI4PK      string `dynamodbav:"GSI4PK"`
	GSI4SK      string `dynamodbav:"GSI4SK"`
}

// Building
type Building struct {
	PK          string `dynamodbav:"PK"` // PROJECT#{project_id}
	SK          string `dynamodbav:"SK"` // BUILDING#{building_id}
	ID          string `dynamodbav:"id"`
	Name        string `dynamodbav:"name"`
	ProjectID   string `dynamodbav:"project_id"`
	ClientID    string `dynamodbav:"client_id"`
	Address     string `dynamodbav:"address"`
	Description string `dynamodbav:"description"`
	CreatedAt   string `dynamodbav:"created_at"`
	CreatedBy   string `dynamodbav:"created_by"`
	UpdatedAt   string `dynamodbav:"updated_at"`
	Type        string `dynamodbav:"Type"` // "BUILDING"
	GSI1PK      string `dynamodbav:"GSI1PK"`
	GSI1SK      string `dynamodbav:"GSI1SK"`
	GSI4PK      string `dynamodbav:"GSI4PK"`
	GSI4SK      string `dynamodbav:"GSI4SK"`
}

// Role
type Role struct {
	PK          string `dynamodbav:"PK"` // CLIENT#{client_id}
	SK          string `dynamodbav:"SK"` // ROLE#{role_id}
	ID          string `dynamodbav:"id"`
	Name        string `dynamodbav:"name"`
	IsSystem    string `dynamodbav:"is_system"`
	Description string `dynamodbav:"description"`
	Permissions string `dynamodbav:"permissions"` // 建议用[]Permission,当前为字符串
	CreatedAt   string `dynamodbav:"created_at"`
	CreatedBy   string `dynamodbav:"created_by"`
	UpdatedAt   string `dynamodbav:"updated_at"`
	Type        string `dynamodbav:"Type"` // "ROLE"
	GSI1PK      string `dynamodbav:"GSI1PK"`
	GSI1SK      string `dynamodbav:"GSI1SK"`
	GSI4PK      string `dynamodbav:"GSI4PK"`
	GSI4SK      string `dynamodbav:"GSI4SK"`
}

// Menu (权限/菜单)
type Menu struct {
	PK          string `dynamodbav:"PK"`   // CLIENT#{client_id}
	SK          string `dynamodbav:"SK"`   // MENU#{menu_id}
	Type        string `dynamodbav:"Type"` // "MENU"
	MenuID      string `dynamodbav:"menu_id"`
	MenuName    string `dynamodbav:"menu_name"`
	ParentID    string `dynamodbav:"parent_id"`
	OrderNum    int    `dynamodbav:"order_num"`
	Path        string `dynamodbav:"path"`
	Component   string `dynamodbav:"component"`
	QueryParam  string `dynamodbav:"query_param"`
	IsFrame     int    `dynamodbav:"is_frame"`
	IsCache     int    `dynamodbav:"is_cache"`
	MenuType    string `dynamodbav:"menu_type"`
	Visible     string `dynamodbav:"visible"`
	Status      string `dynamodbav:"status"`
	Permissions string `dynamodbav:"permissions"`
	Perms       string `dynamodbav:"perms"`
	Icon        string `dynamodbav:"icon"`
	CreateDept  string `dynamodbav:"create_dept"`
	CreateBy    string `dynamodbav:"create_by"`
	CreateTime  string `dynamodbav:"create_time"`
	UpdateBy    string `dynamodbav:"update_by"`
	UpdateTime  string `dynamodbav:"update_time"`
	Remark      string `dynamodbav:"remark"`
	GSI1PK      string `dynamodbav:"GSI1PK"`
	GSI1SK      string `dynamodbav:"GSI1SK"`
	GSI2PK      string `dynamodbav:"GSI2PK"`
	GSI2SK      string `dynamodbav:"GSI2SK"`
}

// ===================== DynamoDB 查询操作接口与实现 =====================

// ***** 接口定义 *****
``` go
// ClientRepository 租户数据访问接口
type ClientRepository interface {
    // 获取单个租户
    GetByID(ctx context.Context, clientID string) (*Client, error)
    // 获取所有租户
    GetAll(ctx context.Context) ([]Client, error)
    // 获取特定状态的租户
    GetByStatus(ctx context.Context, status string) ([]Client, error)
    // 创建或更新租户
    Save(ctx context.Context, client *Client) error
    // 软删除租户
    Delete(ctx context.Context, clientID string) error
}

// ProjectRepository 项目数据访问接口
type ProjectRepository interface {
    // 获取单个项目
    GetByID(ctx context.Context, clientID, projectID string) (*Project, error)
    // 获取租户下的所有项目
    GetByClientID(ctx context.Context, clientID string) ([]Project, error)
    // 创建或更新项目
    Save(ctx context.Context, project *Project) error
    // 软删除项目
    Delete(ctx context.Context, clientID, projectID string) error
}

// BuildingRepository 楼宇数据访问接口
type BuildingRepository interface {
    // 获取单个楼宇
    GetByID(ctx context.Context, projectID, buildingID string) (*Building, error)
    // 获取项目下的所有楼宇
    GetByProjectID(ctx context.Context, projectID string) ([]Building, error)
    // 获取租户下的所有楼宇
    GetByClientID(ctx context.Context, clientID string) ([]Building, error)
    // 创建或更新楼宇
    Save(ctx context.Context, building *Building) error
    // 软删除楼宇
    Delete(ctx context.Context, projectID, buildingID string) error
}

// RoleRepository 角色数据访问接口
type RoleRepository interface {
    // 获取单个角色
    GetByID(ctx context.Context, clientID, roleID string) (*Role, error)
    // 获取租户下的所有角色
    GetByClientID(ctx context.Context, clientID string) ([]Role, error)
    // 获取系统角色
    GetSystemRoles(ctx context.Context, clientID string) ([]Role, error)
    // 创建或更新角色
    Save(ctx context.Context, role *Role) error
    // 软删除角色
    Delete(ctx context.Context, clientID, roleID string) error
}

// MenuRepository 菜单数据访问接口
type MenuRepository interface {
    // 获取单个菜单
    GetByID(ctx context.Context, clientID, menuID string) (*Menu, error)
    // 获取租户下的所有菜单
    GetByClientID(ctx context.Context, clientID string) ([]Menu, error)
    // 根据父ID获取子菜单
    GetByParentID(ctx context.Context, clientID, parentID string) ([]Menu, error)
    // 获取指定类型的菜单
    GetByType(ctx context.Context, menuType string) ([]Menu, error)
    // 创建或更新菜单
    Save(ctx context.Context, menu *Menu) error
    // 软删除菜单
    Delete(ctx context.Context, clientID, menuID string) error
}

// ***** 接口实现 *****

go
// DynamoDBClientRepository 实现 ClientRepository 接口
type DynamoDBClientRepository struct {
    client    *dynamodb.Client
    tableName string
}

// NewDynamoDBClientRepository 创建 ClientRepository 实例
func NewDynamoDBClientRepository(client *dynamodb.Client, tableName string) ClientRepository {
    return &DynamoDBClientRepository{
        client:    client,
        tableName: tableName,
    }
}

// GetByID 根据ID获取租户
func (r *DynamoDBClientRepository) GetByID(ctx context.Context, clientID string) (*Client, error) {
    input := &dynamodb.GetItemInput{
        TableName: aws.String(r.tableName),
        Key: map[string]types.AttributeValue{
            "PK": &types.AttributeValueMemberS{Value: "CLIENT#" + clientID},
            "SK": &types.AttributeValueMemberS{Value: "METADATA"},
        },
    }
    
    result, err := r.client.GetItem(ctx, input)
    if err != nil {
        return nil, fmt.Errorf("获取租户失败: %w", err)
    }
    
    if result.Item == nil {
        return nil, nil // 租户不存在
    }
    
    var client Client
    if err := attributevalue.UnmarshalMap(result.Item, &client); err != nil {
        return nil, fmt.Errorf("解析租户数据失败: %w", err)
    }
    
    return &client, nil
}

// GetAll 获取所有租户
func (r *DynamoDBClientRepository) GetAll(ctx context.Context) ([]Client, error) {
    input := &dynamodb.ScanInput{
        TableName:        aws.String(r.tableName),
        FilterExpression: aws.String("begins_with(PK, :pk) AND SK = :sk"),
        ExpressionAttributeValues: map[string]types.AttributeValue{
            ":pk": &types.AttributeValueMemberS{Value: "CLIENT#"},
            ":sk": &types.AttributeValueMemberS{Value: "METADATA"},
        },
    }
    
    result, err := r.client.Scan(ctx, input)
    if err != nil {
        return nil, fmt.Errorf("获取所有租户失败: %w", err)
    }
    
    var clients []Client
    if err := attributevalue.UnmarshalListOfMaps(result.Items, &clients); err != nil {
        return nil, fmt.Errorf("解析租户列表失败: %w", err)
    }
    
    return clients, nil
}

// GetByStatus 获取特定状态的租户
func (r *DynamoDBClientRepository) GetByStatus(ctx context.Context, status string) ([]Client, error) {
    input := &dynamodb.ScanInput{
        TableName:        aws.String(r.tableName),
        FilterExpression: aws.String("begins_with(PK, :pk) AND SK = :sk AND #status = :status"),
        ExpressionAttributeNames: map[string]string{
            "#status": "status",
        },
        ExpressionAttributeValues: map[string]types.AttributeValue{
            ":pk":     &types.AttributeValueMemberS{Value: "CLIENT#"},
            ":sk":     &types.AttributeValueMemberS{Value: "METADATA"},
            ":status": &types.AttributeValueMemberS{Value: status},
        },
    }
    
    result, err := r.client.Scan(ctx, input)
    if err != nil {
        return nil, fmt.Errorf("获取状态为 %s 的租户失败: %w", status, err)
    }
    
    var clients []Client
    if err := attributevalue.UnmarshalListOfMaps(result.Items, &clients); err != nil {
        return nil, fmt.Errorf("解析租户列表失败: %w", err)
    }
    
    return clients, nil
}

// Save 保存租户
func (r *DynamoDBClientRepository) Save(ctx context.Context, client *Client) error {
    // 确保设置了主键和排序键
    client.PK = "CLIENT#" + client.ID
    client.SK = "METADATA"
    client.Type = "CLIENT"
    
    // 设置GSI键
    client.GSI1PK = "CLIENT#" + client.ID
    client.GSI1SK = "CLIENT#" + client.ID
    client.GSI4PK = "CLIENT#" + client.ID
    
    // 设置时间戳
    now := time.Now().Format(time.RFC3339)
    if client.CreatedAt == "" {
        client.CreatedAt = now
    }
    client.UpdatedAt = now
    
    item, err := attributevalue.MarshalMap(client)
    if err != nil {
        return fmt.Errorf("编码租户数据失败: %w", err)
    }
    
    input := &dynamodb.PutItemInput{
        TableName: aws.String(r.tableName),
        Item:      item,
    }
    
    _, err = r.client.PutItem(ctx, input)
    if err != nil {
        return fmt.Errorf("保存租户失败: %w", err)
    }
    
    return nil
}

// Delete 软删除租户
func (r *DynamoDBClientRepository) Delete(ctx context.Context, clientID string) error {
    input := &dynamodb.UpdateItemInput{
        TableName: aws.String(r.tableName),
        Key: map[string]types.AttributeValue{
            "PK": &types.AttributeValueMemberS{Value: "CLIENT#" + clientID},
            "SK": &types.AttributeValueMemberS{Value: "METADATA"},
        },
        UpdateExpression: aws.String("SET #status = :status, UpdatedAt = :updatedAt"),
        ExpressionAttributeNames: map[string]string{
            "#status": "status",
        },
        ExpressionAttributeValues: map[string]types.AttributeValue{
            ":status":    &types.AttributeValueMemberS{Value: "deleted"},
            ":updatedAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
        },
    }
    
    _, err := r.client.UpdateItem(ctx, input)
    if err != nil {
        return fmt.Errorf("删除租户失败: %w", err)
    }
    
    return nil
}

// DynamoDBProjectRepository 实现 ProjectRepository 接口
type DynamoDBProjectRepository struct {
    client    *dynamodb.Client
    tableName string
}

// NewDynamoDBProjectRepository 创建 ProjectRepository 实例
func NewDynamoDBProjectRepository(client *dynamodb.Client, tableName string) ProjectRepository {
    return &DynamoDBProjectRepository{
        client:    client,
        tableName: tableName,
    }
}

// GetByID 根据ID获取项目
func (r *DynamoDBProjectRepository) GetByID(ctx context.Context, clientID, projectID string) (*Project, error) {
    input := &dynamodb.GetItemInput{
        TableName: aws.String(r.tableName),
        Key: map[string]types.AttributeValue{
            "PK": &types.AttributeValueMemberS{Value: "CLIENT#" + clientID},
            "SK": &types.AttributeValueMemberS{Value: "PROJECT#" + projectID},
        },
    }
    
    result, err := r.client.GetItem(ctx, input)
    if err != nil {
        return nil, fmt.Errorf("获取项目失败: %w", err)
    }
    
    if result.Item == nil {
        return nil, nil // 项目不存在
    }
    
    var project Project
    if err := attributevalue.UnmarshalMap(result.Item, &project); err != nil {
        return nil, fmt.Errorf("解析项目数据失败: %w", err)
    }
    
    return &project, nil
}

// GetByClientID 获取租户下的所有项目
func (r *DynamoDBProjectRepository) GetByClientID(ctx context.Context, clientID string) ([]Project, error) {
    input := &dynamodb.QueryInput{
        TableName:              aws.String(r.tableName),
        KeyConditionExpression: aws.String("PK = :pk AND begins_with(SK, :sk)"),
        ExpressionAttributeValues: map[string]types.AttributeValue{
            ":pk": &types.AttributeValueMemberS{Value: "CLIENT#" + clientID},
            ":sk": &types.AttributeValueMemberS{Value: "PROJECT#"},
        },
    }
    
    result, err := r.client.Query(ctx, input)
    if err != nil {
        return nil, fmt.Errorf("获取租户 %s 的项目列表失败: %w", clientID, err)
    }
    
    var projects []Project
    if err := attributevalue.UnmarshalListOfMaps(result.Items, &projects); err != nil {
        return nil, fmt.Errorf("解析项目列表失败: %w", err)
    }
    
    return projects, nil
}

// Save 保存项目
func (r *DynamoDBProjectRepository) Save(ctx context.Context, project *Project) error {
    // 确保设置了主键和排序键
    project.PK = "CLIENT#" + project.ClientID
    project.SK = "PROJECT#" + project.ID
    project.Type = "PROJECT"
    
    // 设置GSI键
    project.GSI1PK = "CLIENT#" + project.ClientID
    project.GSI1SK = "PROJECT#" + project.ID
    project.GSI4PK = "CLIENT#" + project.ClientID
    
    // 设置时间戳
    now := time.Now().Format(time.RFC3339)
    if project.CreatedAt == "" {
        project.CreatedAt = now
    }
    project.UpdatedAt = now
    
    item, err := attributevalue.MarshalMap(project)
    if err != nil {
        return fmt.Errorf("编码项目数据失败: %w", err)
    }
    
    input := &dynamodb.PutItemInput{
        TableName: aws.String(r.tableName),
        Item:      item,
    }
    
    _, err = r.client.PutItem(ctx, input)
    if err != nil {
        return fmt.Errorf("保存项目失败: %w", err)
    }
    
    return nil
}

// Delete 软删除项目
func (r *DynamoDBProjectRepository) Delete(ctx context.Context, clientID, projectID string) error {
    input := &dynamodb.UpdateItemInput{
        TableName: aws.String(r.tableName),
        Key: map[string]types.AttributeValue{
            "PK": &types.AttributeValueMemberS{Value: "CLIENT#" + clientID},
            "SK": &types.AttributeValueMemberS{Value: "PROJECT#" + projectID},
        },
        UpdateExpression: aws.String("SET #status = :status, UpdatedAt = :updatedAt"),
        ExpressionAttributeNames: map[string]string{
            "#status": "status",
        },
        ExpressionAttributeValues: map[string]types.AttributeValue{
            ":status":    &types.AttributeValueMemberS{Value: "deleted"},
            ":updatedAt": &types.AttributeValueMemberS{Value: time.Now().Format(time.RFC3339)},
        },
    }
    
    _, err := r.client.UpdateItem(ctx, input)
    if err != nil {
        return fmt.Errorf("删除项目失败: %w", err)
    }
    
    return nil
}

// 可以按照同样的模式实现其他实体的仓库接口
// 例如 DynamoDBBuildingRepository, DynamoDBRoleRepository, DynamoDBMenuRepository 等

// ===================== 查询用例示例 =====================

// 获取租户的所有菜单树
func GetClientMenuTree(ctx context.Context, menuRepo MenuRepository, clientID string) ([]MenuTreeNode, error) {
    // 获取租户的所有菜单
    menus, err := menuRepo.GetByClientID(ctx, clientID)
    if err != nil {
        return nil, err
    }
    
    // 构建菜单树
    return buildMenuTree(menus, "0"), nil
}

// 菜单树节点
type MenuTreeNode struct {
    Menu     Menu           `json:"menu"`
    Children []MenuTreeNode `json:"children"`
}

// 构建菜单树
func buildMenuTree(menus []Menu, parentID string) []MenuTreeNode {
    var nodes []MenuTreeNode
    
    for _, menu := range menus {
        if menu.ParentID == parentID {
            node := MenuTreeNode{
                Menu:     menu,
                Children: buildMenuTree(menus, menu.MenuID),
            }
            nodes = append(nodes, node)
        }
    }
    
    // 按 OrderNum 排序
    sort.Slice(nodes, func(i, j int) bool {
        return nodes[i].Menu.OrderNum < nodes[j].Menu.OrderNum
    })
    
    return nodes
}

// 获取用户的所有角色和权限
func GetUserRolesAndPermissions(ctx context.Context, roleRepo RoleRepository, userRoleRepo UserRoleRepository, userID, clientID string) ([]Role, []string, error) {
    // 获取用户的角色ID列表
    userRoles, err := userRoleRepo.GetByUserID(ctx, userID)
    if err != nil {
        return nil, nil, err
    }
    
    // 获取角色详情
    var roles []Role
    var allPermissions []string
    
    for _, userRole := range userRoles {
        role, err := roleRepo.GetByID(ctx, clientID, userRole.RoleID)
        if err != nil {
            return nil, nil, err
        }
        
        if role != nil {
            roles = append(roles, *role)
            
            // 解析权限
            if role.Permissions != "" {
                // 假设权限是JSON格式的字符串
                var perms []map[string]interface{}
                if err := json.Unmarshal([]byte(role.Permissions), &perms); err == nil {
                    for _, perm := range perms {
                        if action, ok := perm["action"]; ok {
                            if resource, ok := perm["resource"]; ok {
                                permission := fmt.Sprintf("%v:%v", action, resource)
                                allPermissions = append(allPermissions, permission)
                            }
                        }
                    }
                }
            }
        }
    }
    
    return roles, allPermissions, nil
}

// ===================== 辅助函数 =====================

go
// 生成唯一ID
func GenerateID() string {
    return uuid.New().String()
}

// 获取当前时间戳
func GetCurrentTimestamp() string {
    return time.Now().Format(time.RFC3339)
}

// 检查用户是否有权限
func HasPermission(permissions []string, requiredPermission string) bool {
    for _, perm := range permissions {
        if perm == requiredPermission || perm == "*:*" {
            return true
        }
        
        // 支持通配符权限检查
        if strings.Contains(perm, "*") {
            pattern := strings.Replace(perm, "*", ".*", -1)
            matched, _ := regexp.MatchString("^"+pattern+"$", requiredPermission)
            if matched {
                return true
            }
        }
    }
    return false
}