From 26b55965da1ae61899b0a13fc103cf9dc60f9d84 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Sat, 26 Aug 2023 17:01:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Ecore=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E6=A0=B8=E5=BF=83=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/datasource/datasource.go | 49 +++++++++++ lib/core/datasource/repo.go | 134 ++++++++++++++++++++++++++++ lib/core/utils/ctx/ctx.go | 29 +++++++ lib/core/utils/date/date.go | 70 +++++++++++++++ lib/core/utils/parse/parse.go | 139 ++++++++++++++++++++++++++++++ lib/core/utils/regular/regular.go | 54 ++++++++++++ lib/core/vo/result/result.go | 72 ++++++++++++++++ lib/core/vo/router.go | 17 ++++ lib/core/vo/router_meta.go | 17 ++++ lib/core/vo/treeselect.go | 33 +++++++ 10 files changed, 614 insertions(+) create mode 100644 lib/core/datasource/datasource.go create mode 100644 lib/core/datasource/repo.go create mode 100644 lib/core/utils/ctx/ctx.go create mode 100644 lib/core/utils/date/date.go create mode 100644 lib/core/utils/parse/parse.go create mode 100644 lib/core/utils/regular/regular.go create mode 100644 lib/core/vo/result/result.go create mode 100644 lib/core/vo/router.go create mode 100644 lib/core/vo/router_meta.go create mode 100644 lib/core/vo/treeselect.go diff --git a/lib/core/datasource/datasource.go b/lib/core/datasource/datasource.go new file mode 100644 index 00000000..013a1ab1 --- /dev/null +++ b/lib/core/datasource/datasource.go @@ -0,0 +1,49 @@ +package datasource + +import ( + "regexp" + + "ems.agt/lib/dborm" + "xorm.io/xorm" +) + +// 获取默认数据源 +func DefaultDB() *xorm.Engine { + return dborm.DbClient.XEngine +} + +// RawDB 原生查询语句 +func RawDB(source string, sql string, parameters []any) ([]map[string]any, error) { + // 数据源 + db := DefaultDB() + + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + + // logger.Infof("sql=> %v", fmtSql) + // logger.Infof("parameters=> %v", parameters) + + // 查询结果 + var rows []map[string]any + err := db.SQL(fmtSql, parameters...).Find(&rows) + if err != nil { + return nil, err + } + return rows, nil +} + +// ExecDB 原生执行语句 +func ExecDB(source string, sql string, parameters []any) (int64, error) { + // 数据源 + db := DefaultDB() + + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + // 执行结果 + res, err := db.Exec(fmtSql, parameters) + if err != nil { + return 0, err + } + affected, err := res.RowsAffected() + return affected, err +} diff --git a/lib/core/datasource/repo.go b/lib/core/datasource/repo.go new file mode 100644 index 00000000..ffa7ba0d --- /dev/null +++ b/lib/core/datasource/repo.go @@ -0,0 +1,134 @@ +package datasource + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +// PageNumSize 分页页码记录数 +func PageNumSize(pageNum, pageSize any) (int, int) { + // 记录起始索引 + pageNumStr := fmt.Sprintf("%v", pageNum) + num := 1 + if v, err := strconv.Atoi(pageNumStr); err == nil && v > 0 { + if num > 5000 { + num = 5000 + } + num = v + } + + // 显示记录数 + pageSizeStr := fmt.Sprintf("%v", pageSize) + size := 0 + if v, err := strconv.Atoi(pageSizeStr); err == nil && v > 0 { + if size > 1000 { + size = 1000 + } + if size < 0 { + size = 10 + } + } + return num - 1, size +} + +// SetFieldValue 判断结构体内是否存在指定字段并设置值 +func SetFieldValue(obj any, fieldName string, value any) { + // 获取结构体的反射值 + userValue := reflect.ValueOf(obj) + + // 获取字段的反射值 + fieldValue := userValue.Elem().FieldByName(fieldName) + + // 检查字段是否存在 + if fieldValue.IsValid() && fieldValue.CanSet() { + // 获取字段的类型 + fieldType := fieldValue.Type() + + // 转换传入的值类型为字段类型 + switch fieldType.Kind() { + case reflect.String: + if value == nil { + fieldValue.SetString("") + } else { + fieldValue.SetString(fmt.Sprintf("%v", value)) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + intValue, err := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64) + if err != nil { + intValue = 0 + } + fieldValue.SetInt(intValue) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + uintValue, err := strconv.ParseUint(fmt.Sprintf("%v", value), 10, 64) + if err != nil { + uintValue = 0 + } + fieldValue.SetUint(uintValue) + case reflect.Float32, reflect.Float64: + floatValue, err := strconv.ParseFloat(fmt.Sprintf("%v", value), 64) + if err != nil { + floatValue = 0 + } + fieldValue.SetFloat(floatValue) + default: + // 设置字段的值 + fieldValue.Set(reflect.ValueOf(value).Convert(fieldValue.Type())) + } + } +} + +// ConvertIdsSlice 将 []string 转换为 []any +func ConvertIdsSlice(ids []string) []any { + // 将 []string 转换为 []any + arr := make([]any, len(ids)) + for i, v := range ids { + arr[i] = v + } + return arr +} + +// 查询-参数值的占位符 +func KeyPlaceholderByQuery(sum int) string { + placeholders := make([]string, sum) + for i := 0; i < sum; i++ { + placeholders[i] = "?" + } + return strings.Join(placeholders, ",") +} + +// 插入-参数映射键值占位符 keys, placeholder, values +func KeyPlaceholderValueByInsert(params map[string]any) ([]string, string, []any) { + // 参数映射的键 + keys := make([]string, len(params)) + // 参数映射的值 + values := make([]any, len(params)) + sum := 0 + for k, v := range params { + keys[sum] = k + values[sum] = v + sum++ + } + // 参数值的占位符 + placeholders := make([]string, sum) + for i := 0; i < sum; i++ { + placeholders[i] = "?" + } + return keys, strings.Join(placeholders, ","), values +} + +// 更新-参数映射键值占位符 keys, values +func KeyValueByUpdate(params map[string]any) ([]string, []any) { + // 参数映射的键 + keys := make([]string, len(params)) + // 参数映射的值 + values := make([]any, len(params)) + sum := 0 + for k, v := range params { + keys[sum] = k + "=?" + values[sum] = v + sum++ + } + return keys, values +} diff --git a/lib/core/utils/ctx/ctx.go b/lib/core/utils/ctx/ctx.go new file mode 100644 index 00000000..f3e24581 --- /dev/null +++ b/lib/core/utils/ctx/ctx.go @@ -0,0 +1,29 @@ +package ctx + +import ( + "encoding/json" + "io" + "net/http" + + "ems.agt/lib/global" +) + +// QueryMap 查询参数转换Map +func QueryMap(r *http.Request) map[string]any { + queryValues := r.URL.Query() + queryParams := make(map[string]any) + for key, values := range queryValues { + queryParams[key] = values[0] + } + return queryParams +} + +// 读取json请求结构团体 +func ShouldBindJSON(r *http.Request, args any) error { + body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) + if err != nil { + return err + } + err = json.Unmarshal(body, args) + return err +} diff --git a/lib/core/utils/date/date.go b/lib/core/utils/date/date.go new file mode 100644 index 00000000..d2b0c23a --- /dev/null +++ b/lib/core/utils/date/date.go @@ -0,0 +1,70 @@ +package date + +import ( + "fmt" + "time" + + "github.com/go-admin-team/go-admin-core/logger" +) + +const ( + // 年 列如:2022 + YYYY = "2006" + // 年-月 列如:2022-12 + YYYY_MM = "2006-01" + // 年-月-日 列如:2022-12-30 + YYYY_MM_DD = "2006-01-02" + // 年月日时分秒 列如:20221230010159 + YYYYMMDDHHMMSS = "20060102150405" + // 年-月-日 时:分:秒 列如:2022-12-30 01:01:59 + YYYY_MM_DD_HH_MM_SS = "2006-01-02 15:04:05" +) + +// 格式时间字符串 +// +// dateStr 时间字符串 +// +// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss +func ParseStrToDate(dateStr, formatStr string) time.Time { + t, err := time.Parse(formatStr, dateStr) + if err != nil { + logger.Infof("utils ParseStrToDate err %v", err) + return time.Time{} + } + return t +} + +// 格式时间 +// +// date 可转的Date对象 +// +// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss +func ParseDateToStr(date any, formatStr string) string { + t, ok := date.(time.Time) + if !ok { + switch v := date.(type) { + case int64: + if v == 0 { + return "" + } + t = time.UnixMilli(v) + case string: + parsedTime, err := time.Parse(formatStr, v) + if err != nil { + fmt.Printf("utils ParseDateToStr err %v \n", err) + return "" + } + t = parsedTime + default: + return "" + } + } + return t.Format(formatStr) +} + +// 格式时间成日期路径 +// +// 年/月 列如:2022/12 +func ParseDatePath(date time.Time) string { + return date.Format("2006/01") +} diff --git a/lib/core/utils/parse/parse.go b/lib/core/utils/parse/parse.go new file mode 100644 index 00000000..ed2fd5dd --- /dev/null +++ b/lib/core/utils/parse/parse.go @@ -0,0 +1,139 @@ +package parse + +import ( + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/robfig/cron/v3" +) + +// Number 解析数值型 +func Number(str any) int64 { + switch str := str.(type) { + case string: + if str == "" { + return 0 + } + num, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0 + } + return num + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return reflect.ValueOf(str).Int() + case float32, float64: + return int64(reflect.ValueOf(str).Float()) + default: + return 0 + } +} + +// Boolean 解析布尔型 +func Boolean(str any) bool { + switch str := str.(type) { + case string: + if str == "" || str == "false" || str == "0" { + return false + } + // 尝试将字符串解析为数字 + if num, err := strconv.ParseFloat(str, 64); err == nil { + return num != 0 + } + return true + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + num := reflect.ValueOf(str).Int() + return num != 0 + case float32, float64: + num := reflect.ValueOf(str).Float() + return num != 0 + default: + return false + } +} + +// FirstUpper 首字母转大写 +// +// 字符串 abc_123!@# 结果 Abc_123 +func FirstUpper(str string) string { + if len(str) == 0 { + return str + } + reg := regexp.MustCompile(`[^_\w]+`) + str = reg.ReplaceAllString(str, "") + return strings.ToUpper(str[:1]) + str[1:] +} + +// Bit 比特位为单位 +func Bit(bit float64) string { + var GB, MB, KB string + + if bit > float64(1<<30) { + GB = fmt.Sprintf("%0.2f", bit/(1<<30)) + } + + if bit > float64(1<<20) && bit < (1<<30) { + MB = fmt.Sprintf("%.2f", bit/(1<<20)) + } + + if bit > float64(1<<10) && bit < (1<<20) { + KB = fmt.Sprintf("%.2f", bit/(1<<10)) + } + + if GB != "" { + return GB + "GB" + } else if MB != "" { + return MB + "MB" + } else if KB != "" { + return KB + "KB" + } else { + return fmt.Sprintf("%vB", bit) + } +} + +// CronExpression 解析 Cron 表达式,返回下一次执行的时间戳(毫秒) +// +// 【*/5 * * * * ?】 6个参数 +func CronExpression(expression string) int64 { + specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) + schedule, err := specParser.Parse(expression) + if err != nil { + fmt.Println(err) + return 0 + } + return schedule.Next(time.Now()).UnixMilli() +} + +// SafeContent 内容值进行安全掩码 +func SafeContent(value string) string { + if len(value) < 3 { + return strings.Repeat("*", len(value)) + } else if len(value) < 6 { + return string(value[0]) + strings.Repeat("*", len(value)-1) + } else if len(value) < 10 { + return string(value[0]) + strings.Repeat("*", len(value)-2) + string(value[len(value)-1]) + } else if len(value) < 15 { + return value[:2] + strings.Repeat("*", len(value)-4) + value[len(value)-2:] + } else { + return value[:3] + strings.Repeat("*", len(value)-6) + value[len(value)-3:] + } +} + +// RemoveDuplicates 数组内字符串去重 +func RemoveDuplicates(ids []string) []string { + uniqueIDs := make(map[string]bool) + uniqueIDSlice := make([]string, 0) + + for _, id := range ids { + _, ok := uniqueIDs[id] + if !ok && id != "" { + uniqueIDs[id] = true + uniqueIDSlice = append(uniqueIDSlice, id) + } + } + + return uniqueIDSlice +} diff --git a/lib/core/utils/regular/regular.go b/lib/core/utils/regular/regular.go new file mode 100644 index 00000000..e4b9cc66 --- /dev/null +++ b/lib/core/utils/regular/regular.go @@ -0,0 +1,54 @@ +package regular + +import ( + "regexp" +) + +// Replace 正则替换 +func Replace(originStr, pattern, repStr string) string { + regex := regexp.MustCompile(pattern) + return regex.ReplaceAllString(originStr, repStr) +} + +// 判断是否为有效用户名格式 +// +// 用户名不能以数字开头,可包含大写小写字母,数字,且不少于5位 +func ValidUsername(username string) bool { + if username == "" { + return false + } + pattern := `^[a-zA-Z][a-z0-9A-Z]{5,}` + match, err := regexp.MatchString(pattern, username) + if err != nil { + return false + } + return match +} + +// 判断是否为有效手机号格式,1开头的11位手机号 +func ValidMobile(mobile string) bool { + if mobile == "" { + return false + } + pattern := `^1[3|4|5|6|7|8|9][0-9]\d{8}$` + match, err := regexp.MatchString(pattern, mobile) + if err != nil { + return false + } + return match +} + +// 判断是否为http(s)://开头 +// +// link 网络链接 +func ValidHttp(link string) bool { + if link == "" { + return false + } + pattern := `^http(s)?:\/\/+` + match, err := regexp.MatchString(pattern, link) + if err != nil { + return false + } + return match +} diff --git a/lib/core/vo/result/result.go b/lib/core/vo/result/result.go new file mode 100644 index 00000000..51faf08e --- /dev/null +++ b/lib/core/vo/result/result.go @@ -0,0 +1,72 @@ +package result + +const CODE_ERROR = 0 +const MSG_ERROR = "error" +const CODE_SUCCESS = 1 +const MSG_SUCCESS = "success" + +// CodeMsg 响应结果 +func CodeMsg(code int, msg string) map[string]any { + args := make(map[string]any) + args["code"] = code + args["msg"] = msg + return args +} + +// 响应成功结果 map[string]any{} +func Ok(v map[string]any) map[string]any { + args := make(map[string]any) + args["code"] = CODE_SUCCESS + args["msg"] = MSG_SUCCESS + // v合并到args + for key, value := range v { + args[key] = value + } + return args +} + +// 响应成功结果信息 +func OkMsg(msg string) map[string]any { + args := make(map[string]any) + args["code"] = CODE_SUCCESS + args["msg"] = msg + return args +} + +// 响应成功结果数据 +func OkData(data any) map[string]any { + args := make(map[string]any) + args["code"] = CODE_SUCCESS + args["msg"] = MSG_SUCCESS + args["data"] = data + return args +} + +// 响应失败结果 map[string]any{} +func Err(v map[string]any) map[string]any { + args := make(map[string]any) + args["code"] = CODE_ERROR + args["msg"] = MSG_ERROR + // v合并到args + for key, value := range v { + args[key] = value + } + return args +} + +// 响应失败结果信息 +func ErrMsg(msg string) map[string]any { + args := make(map[string]any) + args["code"] = CODE_ERROR + args["msg"] = msg + return args +} + +// 响应失败结果数据 +func ErrData(data any) map[string]any { + args := make(map[string]any) + args["code"] = CODE_ERROR + args["msg"] = MSG_ERROR + args["data"] = data + return args +} diff --git a/lib/core/vo/router.go b/lib/core/vo/router.go new file mode 100644 index 00000000..a3cc4083 --- /dev/null +++ b/lib/core/vo/router.go @@ -0,0 +1,17 @@ +package vo + +// Router 路由信息对象 +type Router struct { + // 路由名字 英文首字母大写 + Name string `json:"name"` + // 路由地址 + Path string `json:"path"` + // 其他元素 + Meta RouterMeta `json:"meta"` + // 组件地址 + Component string `json:"component"` + // 重定向地址 + Redirect string `json:"redirect"` + // 子路由 + Children []Router `json:"children,omitempty"` +} diff --git a/lib/core/vo/router_meta.go b/lib/core/vo/router_meta.go new file mode 100644 index 00000000..b3447e05 --- /dev/null +++ b/lib/core/vo/router_meta.go @@ -0,0 +1,17 @@ +package vo + +// RouterMeta 路由元信息对象 +type RouterMeta struct { + // 设置该菜单在侧边栏和面包屑中展示的名字 + Title string `json:"title"` + // 设置该菜单的图标 + Icon string `json:"icon"` + // 设置为true,则不会被 缓存 + Cache bool `json:"cache"` + // 内链地址(http(s)://开头), 打开目标位置 '_blank' | '_self' | '' + Target string `json:"target"` + // 在菜单中隐藏子节点 + HideChildInMenu bool `json:"hideChildInMenu"` + // 在菜单中隐藏自己和子节点 + HideInMenu bool `json:"hideInMenu"` +} diff --git a/lib/core/vo/treeselect.go b/lib/core/vo/treeselect.go new file mode 100644 index 00000000..db07a29d --- /dev/null +++ b/lib/core/vo/treeselect.go @@ -0,0 +1,33 @@ +package vo + +// import sysmenu "ems.agt/features/sys_menu" + +// TreeSelect 树结构实体类 +type TreeSelect struct { + // ID 节点ID + ID string `json:"id"` + + // Label 节点名称 + Label string `json:"label"` + + // Children 子节点 + Children []TreeSelect `json:"children"` +} + +// // SysMenuTreeSelect 使用给定的 SysMenu 对象解析为 TreeSelect 对象 +// func SysMenuTreeSelect(sysMenu sysmenu.SysMenu) TreeSelect { +// t := TreeSelect{} +// t.ID = sysMenu.MenuID +// t.Label = sysMenu.MenuName + +// if len(sysMenu.Children) > 0 { +// for _, menu := range sysMenu.Children { +// child := SysMenuTreeSelect(menu) +// t.Children = append(t.Children, child) +// } +// } else { +// t.Children = []TreeSelect{} +// } + +// return t +// }