init
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/str"
|
||||
|
||||
"goravel/app/models"
|
||||
)
|
||||
|
||||
// GetOperationTitleFromContext 从 context 中获取操作标题
|
||||
// 优先使用权限标识(permission_slug),如果没有权限标识,则根据路径和方法生成默认标题
|
||||
func GetOperationTitleFromContext(ctx http.Context) string {
|
||||
if ctx == nil {
|
||||
return "operation.unknown"
|
||||
}
|
||||
|
||||
// 优先从 context 中获取权限标识(由权限中间件设置)
|
||||
permissionSlugValue := ctx.Value("permission_slug")
|
||||
if permissionSlugValue != nil {
|
||||
if permissionSlug, ok := permissionSlugValue.(string); ok && permissionSlug != "" {
|
||||
// 直接返回权限标识,前端多语言文件中已有对应翻译
|
||||
return permissionSlug
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有权限标识,尝试从权限表中查询匹配的权限
|
||||
method := ctx.Request().Method()
|
||||
path := ctx.Request().Path()
|
||||
|
||||
// 先从权限表中查询匹配的权限
|
||||
permissionSlug := findPermissionSlugFromDB(method, path)
|
||||
if permissionSlug != "" {
|
||||
return permissionSlug
|
||||
}
|
||||
|
||||
// 如果权限表中没有找到,根据路径和方法生成默认标题
|
||||
defaultTitle := generateDefaultTitle(method, path)
|
||||
if defaultTitle != "" {
|
||||
return defaultTitle
|
||||
}
|
||||
|
||||
// 无法生成标题时,返回未知操作
|
||||
return "operation.unknown"
|
||||
}
|
||||
|
||||
// generateDefaultTitle 根据方法和路径生成默认操作标题
|
||||
func generateDefaultTitle(method, path string) string {
|
||||
pathStr := str.Of(path)
|
||||
|
||||
// 分片上传相关(与权限配置中的 slug 保持一致)
|
||||
if pathStr.Contains("/attachments/chunk") {
|
||||
if method == "POST" || method == "GET" {
|
||||
// 权限配置中的 slug 是 attachment.chunk
|
||||
return "attachment.chunk"
|
||||
}
|
||||
}
|
||||
|
||||
// 附件上传
|
||||
if pathStr.Contains("/attachments/upload") && method == "POST" {
|
||||
return "attachment.upload"
|
||||
}
|
||||
|
||||
// 附件删除
|
||||
if pathStr.Contains("/attachments/") && pathStr.EndsWith("/batch-delete") && method == "POST" {
|
||||
return "attachment.batch_delete"
|
||||
}
|
||||
if pathStr.Contains("/attachments/") && method == "DELETE" {
|
||||
return "attachment.destroy"
|
||||
}
|
||||
|
||||
// 附件更新显示名称
|
||||
if pathStr.Contains("/attachments/") && pathStr.EndsWith("/display-name") && method == "PUT" {
|
||||
return "attachment.update_display_name"
|
||||
}
|
||||
|
||||
// 导出下载
|
||||
if pathStr.Contains("/exports/") && pathStr.EndsWith("/download") && method == "GET" {
|
||||
return "export.download"
|
||||
}
|
||||
|
||||
// 订单导入
|
||||
if pathStr.Contains("/orders/import") && method == "POST" {
|
||||
return "order.import"
|
||||
}
|
||||
|
||||
// 订单导出
|
||||
if pathStr.Contains("/orders/export") && method == "POST" {
|
||||
return "order.export"
|
||||
}
|
||||
|
||||
// 管理员解绑谷歌验证码
|
||||
if pathStr.Contains("/admins/") && pathStr.EndsWith("/unbind-google-auth") && method == "POST" {
|
||||
return "admin.unbind_google_auth"
|
||||
}
|
||||
|
||||
// 更新个人资料
|
||||
if pathStr.EndsWith("/profile") && (method == "PUT" || method == "PATCH") {
|
||||
return "profile.update"
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
if pathStr.EndsWith("/password") && (method == "PUT" || method == "PATCH") {
|
||||
return "password.update"
|
||||
}
|
||||
|
||||
// 批量删除(通用模式)
|
||||
if pathStr.EndsWith("/batch-delete") && method == "POST" {
|
||||
parts := pathStr.ChopStart("/api/admin/").Split("/")
|
||||
if len(parts) > 0 {
|
||||
module := str.Of(parts[0]).Replace("-", "_").String()
|
||||
return str.Of(module).Append(".batch_delete").String()
|
||||
}
|
||||
}
|
||||
|
||||
// 清理操作(通用模式)
|
||||
if pathStr.EndsWith("/clean") && method == "POST" {
|
||||
parts := pathStr.ChopStart("/api/admin/").Split("/")
|
||||
if len(parts) > 0 {
|
||||
module := str.Of(parts[0]).Replace("-", "_").String()
|
||||
return str.Of(module).Append(".clean").String()
|
||||
}
|
||||
}
|
||||
|
||||
// 标准 CRUD 操作(通用模式)
|
||||
parts := pathStr.ChopStart("/api/admin/").Split("/")
|
||||
if len(parts) >= 1 {
|
||||
module := str.Of(parts[0]).Replace("-", "_").String()
|
||||
switch method {
|
||||
case "POST":
|
||||
// 创建操作
|
||||
if len(parts) == 1 || (len(parts) == 2 && parts[1] != "batch-delete" && parts[1] != "clean") {
|
||||
return str.Of(module).Append(".store").String()
|
||||
}
|
||||
case "PUT", "PATCH":
|
||||
// 更新操作
|
||||
if len(parts) >= 2 {
|
||||
return str.Of(module).Append(".update").String()
|
||||
}
|
||||
case "DELETE":
|
||||
// 删除操作
|
||||
if len(parts) >= 2 {
|
||||
return str.Of(module).Append(".destroy").String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// findPermissionSlugFromDB 从权限表中查询匹配的权限标识
|
||||
// 优先匹配精确路径,然后匹配通配符路径
|
||||
func findPermissionSlugFromDB(method, path string) string {
|
||||
var permissions []models.Permission
|
||||
|
||||
// 查询所有启用的权限,方法匹配或方法为空
|
||||
query := facades.Orm().Query().Model(&models.Permission{}).
|
||||
Where("status", 1).
|
||||
Where("(method = ? OR method = '')", method)
|
||||
|
||||
if err := query.Find(&permissions); err != nil {
|
||||
// 查询失败时返回空,使用默认逻辑
|
||||
return ""
|
||||
}
|
||||
|
||||
// 优先匹配精确路径
|
||||
for _, perm := range permissions {
|
||||
if perm.Path == path {
|
||||
return perm.Slug
|
||||
}
|
||||
}
|
||||
|
||||
// 然后匹配通配符路径
|
||||
for _, perm := range permissions {
|
||||
if perm.Path != "" && perm.Path != path {
|
||||
if matchPermissionPath(perm.Path, path) {
|
||||
return perm.Slug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// matchPermissionPath 路径匹配,支持通配符(与权限中间件中的 matchPath 逻辑一致)
|
||||
func matchPermissionPath(pattern, path string) bool {
|
||||
if pattern == path {
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果模式不包含通配符,直接返回 false
|
||||
if !containsChar(pattern, '*') {
|
||||
return false
|
||||
}
|
||||
|
||||
// 将模式按 * 分割成多个部分
|
||||
parts := splitPatternString(pattern)
|
||||
if len(parts) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 如果模式以 * 开头,需要特殊处理
|
||||
if pattern[0] == '*' {
|
||||
// 检查路径是否以模式的剩余部分结尾
|
||||
if len(parts) > 1 {
|
||||
suffix := parts[1]
|
||||
return len(path) >= len(suffix) && path[len(path)-len(suffix):] == suffix
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 如果模式以 * 结尾
|
||||
if pattern[len(pattern)-1] == '*' {
|
||||
prefix := pattern[:len(pattern)-1]
|
||||
if len(path) >= len(prefix) {
|
||||
pathPrefix := path[:len(prefix)]
|
||||
if pathPrefix == prefix {
|
||||
// 如果前缀以 / 结尾,路径必须比前缀长(即后面还有内容)
|
||||
if len(prefix) > 0 && prefix[len(prefix)-1] == '/' {
|
||||
return len(path) > len(prefix)
|
||||
}
|
||||
// 如果前缀不以 / 结尾,路径可以等于或长于前缀
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 处理中间有通配符的情况,如 /api/admin/attachments/*/display-name
|
||||
// 过滤掉 "*" 标记,只保留实际的部分
|
||||
var actualParts []string
|
||||
for _, part := range parts {
|
||||
if part != "*" {
|
||||
actualParts = append(actualParts, part)
|
||||
}
|
||||
}
|
||||
|
||||
if len(actualParts) < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查路径是否以第一部分开头
|
||||
firstPart := actualParts[0]
|
||||
if len(path) < len(firstPart) || path[:len(firstPart)] != firstPart {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查路径是否以最后一部分结尾
|
||||
lastPart := actualParts[len(actualParts)-1]
|
||||
if len(path) < len(lastPart) || path[len(path)-len(lastPart):] != lastPart {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查中间部分是否存在(通配符匹配任意内容)
|
||||
remainingPath := path[len(firstPart) : len(path)-len(lastPart)]
|
||||
// 确保中间部分不为空(至少有一个字符,通常是数字ID)
|
||||
return len(remainingPath) > 0
|
||||
}
|
||||
|
||||
// containsChar 检查字符串是否包含指定字符
|
||||
func containsChar(s string, c byte) bool {
|
||||
for i := range s {
|
||||
if s[i] == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// splitPatternString 按 * 分割模式字符串
|
||||
func splitPatternString(pattern string) []string {
|
||||
var parts []string
|
||||
var current strings.Builder
|
||||
|
||||
for i := range pattern {
|
||||
if pattern[i] == '*' {
|
||||
if current.Len() > 0 {
|
||||
parts = append(parts, current.String())
|
||||
current.Reset()
|
||||
}
|
||||
parts = append(parts, "*")
|
||||
} else {
|
||||
current.WriteByte(pattern[i])
|
||||
}
|
||||
}
|
||||
|
||||
if current.Len() > 0 {
|
||||
parts = append(parts, current.String())
|
||||
}
|
||||
|
||||
return parts
|
||||
}
|
||||
Reference in New Issue
Block a user