发布代码生成、更新20+表单组件,优化数据字典,gf版本更新到2.3.1

This commit is contained in:
孟帅
2023-01-18 16:23:39 +08:00
parent 50207ded90
commit 87c27a17a3
386 changed files with 27926 additions and 44297 deletions

View File

@@ -16,7 +16,7 @@ import (
func IsExceptAuth(ctx context.Context, path string) bool {
var pathList []string
except, _ := g.Cfg().Get(ctx, "router.admin.exceptAuth")
except := g.Cfg().MustGet(ctx, "router.admin.exceptAuth")
pathList = except.Strings()
for i := 0; i < len(pathList); i++ {
@@ -32,7 +32,7 @@ func IsExceptAuth(ctx context.Context, path string) bool {
func IsExceptLogin(ctx context.Context, path string) bool {
var pathList []string
except, _ := g.Cfg().Get(ctx, "router.admin.exceptLogin")
except := g.Cfg().MustGet(ctx, "router.admin.exceptLogin")
pathList = except.Strings()
for i := 0; i < len(pathList); i++ {

View File

@@ -69,12 +69,11 @@ func RandomCreateBytes(n int, alphabets ...byte) []byte {
// GetStack 格式化错误的堆栈信息
func GetStack(err error) []string {
stackList := gstr.Split(gerror.Stack(err), "\n")
for i := 0; i < len(stackList); i++ {
stackList[i] = gstr.Replace(stackList[i], "\t", "--> ")
stack := gstr.Split(gerror.Stack(err), "\n")
for i := 0; i < len(stack); i++ {
stack[i] = gstr.Replace(stack[i], "\t", "--> ")
}
return stackList
return stack
}
// SubstrAfter 截取指定字符后的内容

View File

@@ -6,12 +6,25 @@
//
package convert
import (
"github.com/gogf/gf/v2/errors/gerror"
"hotgo/utility/validate"
"reflect"
"strings"
"unicode"
)
var (
descTags = []string{"description", "dc", "json"} // 实体描述标签
fieldTags = []string{"json"} // 实体字段名称映射
)
// UniqueSliceInt64 切片去重
func UniqueSliceInt64(languages []int64) []int64 {
result := make([]int64, 0, len(languages))
temp := map[int64]struct{}{}
for _, item := range languages {
if _, ok := temp[item]; !ok { //如果字典中找不到元素ok=false!ok为true就往切片中append元素。
if _, ok := temp[item]; !ok {
temp[item] = struct{}{}
result = append(result, item)
}
@@ -19,28 +32,130 @@ func UniqueSliceInt64(languages []int64) []int64 {
return result
}
// InSliceInt64 元素是否存在于切片中
func InSliceInt64(slice []int64, key int64) bool {
if len(slice) == 0 {
return false
}
for i := 0; i < len(slice); i++ {
if slice[i] == key {
return true
// UniqueSliceString 切片去重
func UniqueSliceString(languages []string) []string {
result := make([]string, 0, len(languages))
temp := map[string]struct{}{}
for _, item := range languages {
if _, ok := temp[item]; !ok {
temp[item] = struct{}{}
result = append(result, item)
}
}
return false
return result
}
// InSliceInt 元素是否存在于切片中
func InSliceInt(slice []int, key int) bool {
if len(slice) == 0 {
return false
}
for i := 0; i < len(slice); i++ {
if slice[i] == key {
return true
// UnderlineToUpperCamelCase 下划线单词转为大写驼峰单词
func UnderlineToUpperCamelCase(s string) string {
s = strings.Replace(s, "_", " ", -1)
s = strings.Title(s)
return strings.Replace(s, " ", "", -1)
}
// UnderlineToLowerCamelCase 下划线单词转为小写驼峰单词
func UnderlineToLowerCamelCase(s string) string {
s = UnderlineToUpperCamelCase(s)
return string(unicode.ToLower(rune(s[0]))) + s[1:]
}
//CamelCaseToUnderline 驼峰单词转下划线单词
func CamelCaseToUnderline(s string) string {
var output []rune
for i, r := range s {
if i == 0 {
output = append(output, unicode.ToLower(r))
} else {
if unicode.IsUpper(r) {
output = append(output, '_')
}
output = append(output, unicode.ToLower(r))
}
}
return false
return string(output)
}
// GetEntityFieldTags 获取实体中的字段名称
func GetEntityFieldTags(entity interface{}) (tags []string, err error) {
var formRef = reflect.TypeOf(entity)
for i := 0; i < formRef.NumField(); i++ {
field := formRef.Field(i)
if field.Type.Kind() == reflect.Struct {
addTags, err := reflectTag(field.Type, fieldTags, []string{})
if err != nil {
return nil, err
}
tags = append(tags, addTags...)
continue
}
tags = append(tags, reflectTagName(field, fieldTags, true))
}
return
}
// GetEntityDescTags 获取实体中的描述标签
func GetEntityDescTags(entity interface{}) (tags []string, err error) {
var formRef = reflect.TypeOf(entity)
for i := 0; i < formRef.NumField(); i++ {
field := formRef.Field(i)
if field.Type.Kind() == reflect.Struct {
addTags, err := reflectTag(field.Type, descTags, []string{})
if err != nil {
return nil, err
}
tags = append(tags, addTags...)
continue
}
tags = append(tags, reflectTagName(field, descTags, true))
}
return
}
// reflectTag 层级递增解析tag
func reflectTag(reflectType reflect.Type, filterTags []string, tags []string) ([]string, error) {
if reflectType.Kind() == reflect.Ptr {
return nil, gerror.Newf("reflect type do not support reflect.Ptr yet, reflectType:%+v", reflectType)
}
if reflectType.Kind() != reflect.Struct {
return nil, nil
}
for i := 0; i < reflectType.NumField(); i++ {
tag := reflectTagName(reflectType.Field(i), filterTags, false)
if tag == "" {
addTags, err := reflectTag(reflectType.Field(i).Type, filterTags, tags)
if err != nil {
return nil, err
}
tags = append(tags, addTags...)
continue
}
tags = append(tags, tag)
}
return tags, nil
}
// reflectTagName 解析实体中的描述标签优先级description > dc > json > Name
func reflectTagName(field reflect.StructField, filterTags []string, isDef bool) string {
if validate.InSliceString(filterTags, "description") {
if description, ok := field.Tag.Lookup("description"); ok && description != "" {
return description
}
}
if validate.InSliceString(filterTags, "dc") {
if dc, ok := field.Tag.Lookup("dc"); ok && dc != "" {
return dc
}
}
if validate.InSliceString(filterTags, "json") {
if jsonName, ok := field.Tag.Lookup("json"); ok && jsonName != "" {
return jsonName
}
}
if !isDef {
return ""
}
return field.Name
}

View File

@@ -0,0 +1,42 @@
// Package encrypt
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package encrypt
import (
"encoding/base64"
"github.com/forgoer/openssl"
)
// AesECBEncrypt 加密
func AesECBEncrypt(src, key []byte) (dst []byte, err error) {
return openssl.AesECBEncrypt(src, key, openssl.PKCS7_PADDING)
}
// AesECBDecrypt 解密
func AesECBDecrypt(src, key []byte) (dst []byte, err error) {
return openssl.AesECBDecrypt(src, key, openssl.PKCS7_PADDING)
}
// MustAesECBEncryptToString
// Return the encryption result directly. Panic error
func MustAesECBEncryptToString(bytCipher, key string) string {
dst, err := AesECBEncrypt([]byte(bytCipher), []byte(key))
if err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(dst)
}
// MustAesECBDecryptToString
// Directly return decryption result, panic error
func MustAesECBDecryptToString(bytCipher, key string) string {
dst, err := AesECBDecrypt([]byte(bytCipher), []byte(key))
if err != nil {
panic(err)
}
return string(dst)
}

View File

@@ -17,6 +17,11 @@ func Md5ToString(str string) string {
return fmt.Sprintf("%x", md5.Sum([]byte(str)))
}
// Md5 生成md5
func Md5(b []byte) string {
return fmt.Sprintf("%x", md5.Sum(b))
}
func Hash32(b []byte) uint32 {
h := fnv.New32a()
h.Write(b)

View File

@@ -7,42 +7,63 @@
package excel
import (
"context"
"fmt"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/util/gconv"
"github.com/xuri/excelize/v2"
"hotgo/internal/library/contexts"
"hotgo/internal/model"
"net/url"
"reflect"
"time"
)
func ExportByStruct(w *ghttp.ResponseWriter, titleList []string, data []interface{}, fileName string, sheetName string) error {
var (
// 单元格表头
char = []string{"", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
// 默认行样式
defaultRowStyle = `{"font":{"color":"#666666","size":13,"family":"arial"},"alignment":{"vertical":"center","horizontal":"center"}}`
)
// ExportByStructs 导出切片结构体
func ExportByStructs(ctx context.Context, tags []string, list interface{}, fileName string, sheetName string) (err error) {
f := excelize.NewFile()
f.SetSheetName("Sheet1", sheetName)
_ = f.SetRowHeight("Sheet1", 1, 30)
header := make([]string, 0)
for _, v := range titleList {
for _, v := range tags {
header = append(header, v)
}
rowStyleID, _ := f.NewStyle(`{"font":{"color":"#666666","size":13,"family":"arial"},"alignment":{"vertical":"center","horizontal":"center"}}`)
rowStyleID, _ := f.NewStyle(defaultRowStyle)
if err != nil {
return
}
_ = f.SetSheetRow(sheetName, "A1", &header)
_ = f.SetRowHeight("Sheet1", 1, 30)
length := len(titleList)
headStyle := letter(length)
var lastRow string
var widthRow string
var (
length = len(tags)
headStyle = letter(length)
lastRow string
widthRow string
)
for k, v := range headStyle {
if k == length-1 {
lastRow = fmt.Sprintf("%s1", v)
widthRow = v
}
}
if err := f.SetColWidth(sheetName, "A", widthRow, 30); err != nil {
if err = f.SetColWidth(sheetName, "A", widthRow, 30); err != nil {
return err
}
rowNum := 1
for _, v := range data {
var rowNum = 1
for _, v := range gconv.Interfaces(list) {
t := reflect.TypeOf(v)
value := reflect.ValueOf(v)
row := make([]interface{}, 0)
@@ -51,32 +72,60 @@ func ExportByStruct(w *ghttp.ResponseWriter, titleList []string, data []interfac
row = append(row, val)
}
rowNum++
err := f.SetSheetRow(sheetName, "A"+gconv.String(rowNum), &row)
_ = f.SetCellStyle(sheetName, fmt.Sprintf("A%d", rowNum), fmt.Sprintf("%s", lastRow), rowStyleID)
if err != nil {
if err = f.SetSheetRow(sheetName, "A"+gconv.String(rowNum), &row); err != nil {
return err
}
if err = f.SetCellStyle(sheetName, fmt.Sprintf("A%d", rowNum), fmt.Sprintf("%s", lastRow), rowStyleID); err != nil {
return err
}
}
disposition := fmt.Sprintf("attachment; filename=%s.xlsx", url.QueryEscape(fileName))
// 强转类型
writer := ghttp.RequestFromCtx(ctx).Response.Writer
w, ok := interface{}(writer).(*ghttp.ResponseWriter)
if !ok {
return gerror.New("Response.Writer uninitialized!")
}
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", disposition)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.xlsx", url.QueryEscape(fileName)))
w.Header().Set("Content-Transfer-Encoding", "binary")
w.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
err := f.Write(w)
if err != nil {
if err := f.Write(w); err != nil {
return err
}
// 加入到上下文
contexts.SetResponse(ctx, &model.Response{
Code: gcode.CodeOK.Code(),
Message: "export successfully!",
Timestamp: time.Now().Unix(),
TraceID: gctx.CtxId(ctx),
})
return nil
}
// Letter 遍历a-z
// letter 生成完整的表头
func letter(length int) []string {
var str []string
for i := 0; i < length; i++ {
str = append(str, string(rune('A'+i)))
str = append(str, numToChars(i))
}
return str
}
// numToChars 将数字转换为具体的表格表头名称
func numToChars(num int) string {
var cols string
v := num
for v > 0 {
k := v % 26
if k == 0 {
k = 26
}
v = (v - k) / 26
cols = char[k] + cols
}
return cols
}

View File

@@ -7,6 +7,7 @@
package file
import (
"github.com/gogf/gf/v2/os/gfile"
"hotgo/utility/format"
"io/ioutil"
"os"
@@ -111,3 +112,9 @@ func DirSize(dirname string) string {
return format.FileSize(ss)
}
func MergeAbs(path string, fileName ...string) string {
var paths = []string{gfile.RealPath(path)}
paths = append(paths, fileName...)
return gfile.Join(paths...)
}

View File

@@ -27,6 +27,7 @@ const (
var (
// 图片类型
imgType = g.MapStrStr{
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"png": "image/png",
"gif": "image/gif",
@@ -107,7 +108,7 @@ func GetFileType(ext string) (string, error) {
if mime, ok := videoType[ext]; ok {
return mime, nil
}
return "", gerror.New("Invalid file type")
return "", gerror.Newf("Invalid file type:%v", ext)
}
// GetFileKind 获取文件所属分类

View File

@@ -1,18 +1,48 @@
// Package tree
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package tree
import (
"fmt"
"github.com/gogf/gf/v2/util/gconv"
"strings"
)
var pidName = "pid"
var (
pidName = "pid"
treeBeginCut = "tr_" // 树标识开头
treeEndCut = " " // 树标识结尾
)
// GenTree 生成关系树 参考https://blog.csdn.net/weixin_51546892/article/details/122876793
// GenLabel 生成关系树标识
func GenLabel(basic string, appendId int64) string {
return fmt.Sprintf("%v%v%v%v", basic, treeBeginCut, appendId, treeEndCut)
}
// GetIds 获取上级ID集合
func GetIds(tree string) (ids []int64) {
idsStr := strings.Split(tree, treeEndCut)
if len(idsStr) == 0 {
return
}
for _, v := range idsStr {
newId := gconv.Int64(strings.ReplaceAll(v, treeBeginCut, ""))
if newId > 0 {
ids = append(ids, newId)
}
}
return
}
/////////////////////////// 转换类
// GenTree 生成关系树
func GenTree(menus []map[string]interface{}) (realMenu []map[string]interface{}) {
if len(menus) < 1 {
return nil

69
server/utility/url/url.go Normal file
View File

@@ -0,0 +1,69 @@
package url
import (
"context"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/text/gstr"
"hotgo/utility/validate"
"strings"
)
// UriToMap 将URL参数转为map
func UriToMap(uri string) g.MapStrStr {
m := make(map[string]string)
if len(uri) < 1 {
return nil
}
if uri[0:1] == "?" {
uri = uri[1:]
}
pars := strings.Split(uri, "&")
for _, par := range pars {
kv := strings.Split(par, "=")
m[kv[0]] = kv[1]
}
return m
}
// MapToUri 将map转为URL参数
func MapToUri(params g.MapStrStr) string {
escape := ""
for k, v := range params {
if escape != "" {
escape = escape + "&"
}
escape = escape + k + "=" + v
}
return escape
}
// GetAddr 获取请求中的请求地址,协议+域名/IP:端口
func GetAddr(ctx context.Context) string {
r := ghttp.RequestFromCtx(ctx)
if r == nil {
return ""
}
var (
scheme = "http"
proto = r.Header.Get("X-Forwarded-Proto")
)
if r.TLS != nil || gstr.Equal(proto, "https") {
scheme = "https"
}
return fmt.Sprintf(`%s://%s`, scheme, r.Host)
}
// GetDomain 获取请求中的域名,如果请求不是域名则返回空
func GetDomain(ctx context.Context) string {
r := ghttp.RequestFromCtx(ctx)
if r == nil {
g.Log().Warningf(ctx, "GetDomain ctx not request")
return ""
}
if validate.IsDNSName(r.Host) {
return r.Host
}
return ""
}

View File

@@ -0,0 +1,24 @@
// Package validate
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package validate
import (
"context"
)
type Filter interface {
// Filter gf效验规则 https://goframe.org/pages/viewpage.action?pageId=1114367
Filter(ctx context.Context) error
}
// PreFilter 预过滤
func PreFilter(ctx context.Context, in interface{}) error {
if c, ok := in.(Filter); ok {
return c.Filter(ctx)
}
return nil
}

View File

@@ -0,0 +1,72 @@
// Package validate
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2022 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
//
package validate
import (
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"time"
)
// InSameDay 是否为同一天
func InSameDay(t1, t2 int64) bool {
y1, m1, d1 := time.Unix(t1, 0).Date()
y2, m2, d2 := time.Unix(t2, 0).Date()
return y1 == y2 && m1 == m2 && d1 == d2
}
// InSameMinute 是否为同一分钟
func InSameMinute(t1, t2 int64) bool {
d1 := time.Unix(t1, 0).Format("2006-01-02 15:04")
d2 := time.Unix(t2, 0).Format("2006-01-02 15:04")
return d1 == d2
}
// InSliceExistStr 判断字符或切片字符是否存在指定字符
func InSliceExistStr(elems interface{}, search string) bool {
switch elems.(type) {
case []string:
elem := gconv.Strings(elems)
for i := 0; i < len(elem); i++ {
if gconv.String(elem[i]) == search {
return true
}
}
default:
return gconv.String(elems) == search
}
return false
}
// InSliceInt64 元素是否存在于切片中
func InSliceInt64(slice []int64, key int64) bool {
if len(slice) == 0 {
return false
}
for i := 0; i < len(slice); i++ {
if slice[i] == key {
return true
}
}
return false
}
func InSliceInt(slice []int, key int) bool {
if len(slice) == 0 {
return false
}
for i := 0; i < len(slice); i++ {
if slice[i] == key {
return true
}
}
return false
}
func InSliceString(slice []string, key string) bool {
return gstr.InArray(slice, key)
}

View File

@@ -7,13 +7,37 @@
package validate
import (
"github.com/gogf/gf/v2/util/gconv"
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/text/gstr"
"net"
"net/url"
"regexp"
"time"
)
func IsDNSName(s string) bool {
DNSName := `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
rxDNSName := regexp.MustCompile(DNSName)
return s != "" && rxDNSName.MatchString(s)
}
func IsHTTPS(ctx context.Context) bool {
r := ghttp.RequestFromCtx(ctx)
if r == nil {
g.Log().Warningf(ctx, "IsHTTPS ctx not request")
return false
}
var (
proto = r.Header.Get("X-Forwarded-Proto")
)
if r.TLS != nil || gstr.Equal(proto, "https") {
return true
}
return false
}
// IsIp 是否为ipv4
func IsIp(ip string) bool {
if net.ParseIP(ip) != nil {
@@ -22,48 +46,37 @@ func IsIp(ip string) bool {
return false
}
// IsPublicIp 是否是公网IP
func IsPublicIp(Ip string) bool {
ip := net.ParseIP(Ip)
if ip.IsLoopback() || ip.IsPrivate() || ip.IsMulticast() || ip.IsUnspecified() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
return false
}
if ip4 := ip.To4(); ip4 != nil {
return !ip.Equal(net.IPv4bcast)
}
return true
}
// IsMobile 是否为手机号码
func IsMobile(mobile string) bool {
pattern := `^(1[2|3|4|5|6|7|8|9][0-9]\d{4,8})$`
reg := regexp.MustCompile(pattern)
return reg.MatchString(mobile)
}
// IsEmail 是否为邮箱地址
func IsEmail(email string) bool {
//pattern := `\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*` //匹配电子邮箱
pattern := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z].){1,4}[a-z]{2,4}$`
reg := regexp.MustCompile(pattern)
return reg.MatchString(email)
}
// InSameDay 是否为同一天
func InSameDay(t1, t2 int64) bool {
y1, m1, d1 := time.Unix(t1, 0).Date()
y2, m2, d2 := time.Unix(t2, 0).Date()
return y1 == y2 && m1 == m2 && d1 == d2
}
// InSameMinute 是否为同一分钟
func InSameMinute(t1, t2 int64) bool {
d1 := time.Unix(t1, 0).Format("2006-01-02 15:04")
d2 := time.Unix(t2, 0).Format("2006-01-02 15:04")
return d1 == d2
}
// InSliceExistStr 判断字符或切片字符是否存在指定字符
func InSliceExistStr(elems interface{}, search string) bool {
switch elems.(type) {
case []string:
elem := gconv.Strings(elems)
for i := 0; i < len(elem); i++ {
if gconv.String(elem[i]) == search {
return true
}
}
default:
return gconv.String(elems) == search
}
return false
}
// IsURL 是否是url地址
func IsURL(u string) bool {
_, err := url.ParseRequestURI(u)
@@ -76,3 +89,19 @@ func IsURL(u string) bool {
}
return true
}
// IsIDCard 是否为身份证
func IsIDCard(idCard string) bool {
sz := len(idCard)
if sz != 18 {
return false
}
weight := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
validate := []byte{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}
sum := 0
for i := 0; i < len(weight); i++ {
sum += weight[i] * int(byte(idCard[i])-'0')
}
m := sum % 11
return validate[m] == idCard[sz-1]
}