Appearance
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
}