This commit is contained in:
2026-03-18 10:19:42 +08:00
parent 2526ad4414
commit e58dd3529d
267 changed files with 25279 additions and 2 deletions

View File

@@ -0,0 +1,106 @@
package libResponse
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gview"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
const (
SuccessCode int = 0
ErrorCode int = -1
)
type Response struct {
// 代码
Code int `json:"code" example:"200"`
// 数据集
Data interface{} `json:"data"`
// 消息
Msg string `json:"message"`
}
var response = new(Response)
func JsonExit(r *ghttp.Request, code int, msg string, data ...interface{}) {
response.JsonExit(r, code, msg, data...)
}
func RJson(r *ghttp.Request, code int, msg string, data ...interface{}) {
response.RJson(r, code, msg, data...)
}
func SusJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) {
response.SusJson(isExit, r, msg, data...)
}
func FailJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) {
response.FailJson(isExit, r, msg, data...)
}
func WriteTpl(r *ghttp.Request, tpl string, view *gview.View, params ...gview.Params) error {
return response.WriteTpl(r, tpl, view, params...)
}
// 返回JSON数据并退出当前HTTP执行函数。
func (res *Response) JsonExit(r *ghttp.Request, code int, msg string, data ...interface{}) {
res.RJson(r, code, msg, data...)
r.ExitAll()
}
// 标准返回结果数据结构封装。
// 返回固定数据结构的JSON:
// code: 状态码(200:成功,302跳转和http请求状态码一至);
// msg: 请求结果信息;
// data: 请求结果,根据不同接口返回结果的数据结构不同;
func (res *Response) RJson(r *ghttp.Request, code int, msg string, data ...interface{}) {
responseData := interface{}(nil)
if len(data) > 0 {
responseData = data[0]
}
r.Response.WriteJson(&Response{
Code: code,
Msg: msg,
Data: responseData,
})
}
// 成功返回JSON
func (res *Response) SusJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) {
if isExit {
res.JsonExit(r, SuccessCode, msg, data...)
}
res.RJson(r, SuccessCode, msg, data...)
}
// 失败返回JSON
func (res *Response) FailJson(isExit bool, r *ghttp.Request, msg string, data ...interface{}) {
if isExit {
res.JsonExit(r, ErrorCode, msg, data...)
}
res.RJson(r, ErrorCode, msg, data...)
}
// WriteTpl 模板输出
func (res *Response) WriteTpl(r *ghttp.Request, tpl string, view *gview.View, params ...gview.Params) error {
//绑定模板中需要用到的方法
view.BindFuncMap(g.Map{
// 根据长度i来切割字符串
"subStr": func(str interface{}, i int) (s string) {
s1 := gconv.String(str)
if gstr.LenRune(s1) > i {
s = gstr.SubStrRune(s1, 0, i) + "..."
return s
}
return s1
},
})
r.Response.Write(view.Parse(r.GetCtx(), tpl, params...))
return nil
}
func (res *Response) Redirect(r *ghttp.Request, location string, code ...int) {
r.Response.RedirectTo(location, code...)
}

View File

@@ -0,0 +1,52 @@
/*
* @desc:路由处理
* @company:云南奇讯科技有限公司
* @Author: yixiaohu<yxh669@qq.com>
* @Date: 2022/11/16 11:09
*/
package libRouter
import (
"context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/text/gregex"
"reflect"
)
// RouterAutoBindBefore 收集需要被绑定的不验证用户登录状态的控制器,自动绑定
// 路由的方法命名规则必须为BeforeBindXXXController
func RouterAutoBindBefore(ctx context.Context, R interface{}, group *ghttp.RouterGroup) (err error) {
return bind(ctx, R, group, "before")
}
// RouterAutoBind 收集需要被绑定的控制器,自动绑定
// 路由的方法命名规则必须为BindXXXController
func RouterAutoBind(ctx context.Context, R interface{}, group *ghttp.RouterGroup) (err error) {
return bind(ctx, R, group)
}
func bind(ctx context.Context, R interface{}, group *ghttp.RouterGroup, option ...string) (err error) {
var rule string
if len(option) > 0 && option[0] == "before" {
rule = `^BeforeBind(.+)Controller$`
} else {
rule = `^Bind(.+)Controller$`
}
//TypeOf会返回目标数据的类型比如int/float/struct/指针等
typ := reflect.TypeOf(R)
//ValueOf返回目标数据的的值
val := reflect.ValueOf(R)
if val.Elem().Kind() != reflect.Struct {
err = gerror.New("expect struct but a " + val.Elem().Kind().String())
return
}
for i := 0; i < typ.NumMethod(); i++ {
if match := gregex.IsMatchString(rule, typ.Method(i).Name); match {
//调用绑定方法
val.Method(i).Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(group)})
}
}
return
}

View File

@@ -0,0 +1,249 @@
package libUtils
import (
"fmt"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"reflect"
)
// ParentSonSort 有层级关系的数组,父级-》子级 排序
func ParentSonSort(list g.List, params ...interface{}) g.List {
args := make([]interface{}, 8)
for k, v := range params {
if k == 8 {
break
}
args[k] = v
}
var (
pid int //父级id
level int //层级数
fieldName string //父级id键名
id string //id键名
levelName string //层级名称
title string //标题名称
breaks int //中断层级
prefixStr string //字符串前缀
)
pid = gconv.Int(GetSliceByKey(args, 0, 0))
level = gconv.Int(GetSliceByKey(args, 1, 0))
fieldName = gconv.String(GetSliceByKey(args, 2, "pid"))
id = gconv.String(GetSliceByKey(args, 3, "id"))
levelName = gconv.String(GetSliceByKey(args, 4, "flg"))
title = gconv.String(GetSliceByKey(args, 5, "title"))
breaks = gconv.Int(GetSliceByKey(args, 6, -1))
prefixStr = gconv.String(GetSliceByKey(args, 7, "─"))
//定义一个新slice用于返回
var returnSlice g.List
for _, v := range list {
if pid == gconv.Int(v[fieldName]) {
v[levelName] = level
levelClone := level
titlePrefix := ""
for {
if levelClone < 0 {
break
}
titlePrefix += prefixStr
levelClone--
}
titlePrefix = "├" + titlePrefix
if level == 0 {
v["title_prefix"] = ""
} else {
v["title_prefix"] = titlePrefix
}
v["title_show"] = fmt.Sprintf("%s%s", v["title_prefix"], v[title])
returnSlice = append(returnSlice, v)
if breaks != -1 && breaks == level {
continue
}
args[0] = v[id]
args[1] = level + 1
newSlice2 := ParentSonSort(list, args...)
if len(newSlice2) > 0 {
returnSlice = append(returnSlice, newSlice2...)
}
}
}
return returnSlice
}
// PushSonToParent 有层级关系的数组 ,将子级压入到父级(树形结构)
func PushSonToParent(list g.List, params ...interface{}) g.List {
args := make([]interface{}, 7)
for k, v := range params {
if k == 7 {
break
}
args[k] = v
}
var (
pid string //父级id
fieldName string //父级id键名
id string //id键名
key string //子级数组键名
filter string //过滤键名
filterVal interface{} //过滤的值
showNoChild bool //是否显示不存在的子级健
)
pid = gconv.String(GetSliceByKey(args, 0, 0))
fieldName = gconv.String(GetSliceByKey(args, 1, "pid"))
id = gconv.String(GetSliceByKey(args, 2, "id"))
key = gconv.String(GetSliceByKey(args, 3, "children"))
filter = gconv.String(GetSliceByKey(args, 4, ""))
filterVal = GetSliceByKey(args, 5, nil)
showNoChild = gconv.Bool(GetSliceByKey(args, 6, true))
var returnList g.List
for _, v := range list {
if gconv.String(v[fieldName]) == pid {
if filter != "" {
if reflect.DeepEqual(v[filter], filterVal) {
args[0] = v[id]
child := PushSonToParent(list, args...)
if child != nil || showNoChild {
v[key] = child
}
returnList = append(returnList, v)
}
} else {
args[0] = v[id]
child := PushSonToParent(list, args...)
if child != nil || showNoChild {
v[key] = child
}
returnList = append(returnList, v)
}
}
}
return returnList
}
// GetSliceByKey 获取切片里的值 若为nil 可设置默认值val
func GetSliceByKey(args []interface{}, key int, val interface{}) interface{} {
var value interface{}
if args[key] != nil {
value = args[key]
} else {
value = val
}
return value
}
// FindSonByParentId 有层级关系的切片通过父级id查找所有子级id数组
// parentId 父级id
// parentIndex 父级索引名称
// idIndex id索引名称
func FindSonByParentId(list g.List, parentId interface{}, parentIndex, idIndex string) g.List {
newList := make(g.List, 0, len(list))
for _, v := range list {
if reflect.DeepEqual(v[parentIndex], parentId) {
newList = append(newList, v)
fList := FindSonByParentId(list, v[idIndex], parentIndex, idIndex)
newList = append(newList, fList...)
}
}
return newList
}
// GetTopPidList 获取最顶层 parent Id
func GetTopPidList(list g.List, parentIndex, idIndex string) *garray.Array {
arr := garray.NewArray()
for _, v1 := range list {
tag := true
for _, v2 := range list {
if v1[parentIndex] == v2[idIndex] {
tag = false
break
}
}
if tag {
arr.PushRight(v1[parentIndex])
}
}
return arr.Unique()
}
// FindParentBySonPid 有层级关系的数组通过子级fid查找所有父级数组
func FindParentBySonPid(list g.List, id int, params ...interface{}) g.List {
args := make([]interface{}, 4)
for k, v := range params {
if k == 4 {
break
}
args[k] = v
}
var (
filter = gconv.String(GetSliceByKey(args, 0, "filter")) //过滤键名
fPid = gconv.String(GetSliceByKey(args, 1, "pid")) //父级id字段键名
filterValue = GetSliceByKey(args, 2, nil) //过滤键值
fid = gconv.String(GetSliceByKey(args, 3, "id")) //id字段键名
)
rList := make(g.List, 0, len(list))
for _, v := range list {
if gconv.Int(v[fid]) == id {
if fv, ok := v[filter]; ok {
if reflect.DeepEqual(fv, filterValue) {
rList = append(rList, v)
}
} else {
rList = append(rList, v)
}
r := FindParentBySonPid(list, gconv.Int(v[fPid]), filter, fPid, filterValue, fid)
rList = append(rList, r...)
}
}
return rList
}
// FindTopParent
/**
* 根据id查询最顶层父级信息
* @param list 有层级关系的数组
* @param id 查找的id
* @param string fpid 父级id键名
* @param string fid 当前id键名
* @return g.Map
*/
func FindTopParent(list g.List, id int64, params ...interface{}) g.Map {
if len(list) == 0 {
return g.Map{}
}
args := make([]interface{}, 2)
for k, v := range params {
if k == 2 {
break
}
args[k] = v
}
var (
fPid = gconv.String(GetSliceByKey(args, 0, "pid")) //父级id字段键名
fid = gconv.String(GetSliceByKey(args, 1, "id")) //id字段键名
)
hasParent := true
top := g.Map{}
//找到要查找id值的数组
for _, v := range list {
if gconv.Int64(v[fid]) == gconv.Int64(id) {
top = v
break
}
}
for {
if !hasParent {
break
}
//查询最顶层
for _, v := range list {
if gconv.Int64(top[fPid]) == gconv.Int64(v[fid]) {
top = v
hasParent = true
break
}
hasParent = false
}
}
return top
}

206
library/libUtils/utils.go Normal file
View File

@@ -0,0 +1,206 @@
/*
* @desc:工具
* @company:云南奇讯科技有限公司
* @Author: yixiaohu
* @Date: 2022/3/4 22:16
*/
package libUtils
import (
"context"
"fmt"
"io"
"net"
"net/http"
"os"
"path"
"strings"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/encoding/gcharset"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/encoding/gurl"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/text/gstr"
"github.com/tiger1103/gfast/v3/internal/app/common/consts"
)
// EncryptPassword 密码加密
func EncryptPassword(password, salt string) string {
return gmd5.MustEncryptString(gmd5.MustEncryptString(password) + gmd5.MustEncryptString(salt))
}
// GetDomain 获取当前请求接口域名
func GetDomain(ctx context.Context) string {
r := g.RequestFromCtx(ctx)
host := r.Header.Get("X-Forwarded-Host")
if host == "" {
host = r.Header.Get("X-Host")
}
if host == "" {
host = r.Host
}
scheme := r.Header.Get("X-Scheme")
if scheme == "" {
scheme = r.GetSchema()
}
return fmt.Sprintf("%s://%s", scheme, host)
}
// GetClientIp 获取客户端IP
func GetClientIp(ctx context.Context) string {
return g.RequestFromCtx(ctx).GetClientIp()
}
// GetUserAgent 获取user-agent
func GetUserAgent(ctx context.Context) string {
return ghttp.RequestFromCtx(ctx).Header.Get("User-Agent")
}
// GetLocalIP 服务端ip
func GetLocalIP() (ip string, err error) {
var addrs []net.Addr
addrs, err = net.InterfaceAddrs()
if err != nil {
return
}
for _, addr := range addrs {
ipAddr, ok := addr.(*net.IPNet)
if !ok {
continue
}
if ipAddr.IP.IsLoopback() {
continue
}
if !ipAddr.IP.IsGlobalUnicast() {
continue
}
return ipAddr.IP.String(), nil
}
return
}
// GetCityByIp 获取ip所属城市
func GetCityByIp(ip string) string {
if ip == "" {
return ""
}
if ip == "::1" || ip == "127.0.0.1" {
return "内网IP"
}
url := "http://whois.pconline.com.cn/ipJson.jsp?json=true&ip=" + ip
bytes := g.Client().GetBytes(context.TODO(), url)
src := string(bytes)
srcCharset := "GBK"
tmp, _ := gcharset.ToUTF8(srcCharset, src)
json, err := gjson.DecodeToJson(tmp)
if err != nil {
return ""
}
if json.Get("code").Int() == 0 {
city := fmt.Sprintf("%s %s", json.Get("pro").String(), json.Get("city").String())
return city
} else {
return ""
}
}
// 写入文件
func WriteToFile(fileName string, content string) error {
f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
if err != nil {
return err
}
n, _ := f.Seek(0, io.SeekEnd)
_, err = f.WriteAt([]byte(content), n)
defer f.Close()
return err
}
// 文件或文件夹是否存在
func FileIsExisted(filename string) bool {
existed := true
if _, err := os.Stat(filename); os.IsNotExist(err) {
existed = false
}
return existed
}
// 解析路径获取文件名称及后缀
func ParseFilePath(pathStr string) (fileName string, fileType string) {
fileNameWithSuffix := path.Base(pathStr)
fileType = path.Ext(fileNameWithSuffix)
fileName = strings.TrimSuffix(fileNameWithSuffix, fileType)
return
}
// IsNotExistMkDir 检查文件夹是否存在
// 如果不存在则新建文件夹
func IsNotExistMkDir(src string) error {
if exist := !FileIsExisted(src); exist == false {
if err := MkDir(src); err != nil {
return err
}
}
return nil
}
// MkDir 新建文件夹
func MkDir(src string) error {
err := os.MkdirAll(src, os.ModePerm)
if err != nil {
return err
}
return nil
}
// 获取文件后缀
func GetExt(fileName string) string {
return path.Ext(fileName)
}
// GetType 获取文件类型
func GetType(p string) (result string, err error) {
file, err := os.Open(p)
if err != nil {
g.Log().Error(context.TODO(), err)
return
}
defer file.Close()
buff := make([]byte, 512)
_, err = file.Read(buff)
if err != nil {
g.Log().Error(context.TODO(), err)
return
}
filetype := http.DetectContentType(buff)
return filetype, nil
}
// GetFilesPath 获取附件相对路径
func GetFilesPath(ctx context.Context, fileUrl string) (path string, err error) {
upType := g.Cfg().MustGet(ctx, "upload.default").Int()
if upType != 0 || (upType == 0 && !gstr.ContainsI(fileUrl, consts.UploadPath)) {
path = fileUrl
return
}
pathInfo, err := gurl.ParseURL(fileUrl, 32)
if err != nil {
g.Log().Error(ctx, err)
err = gerror.New("解析附件路径失败")
return
}
pos := gstr.PosI(pathInfo["path"], consts.UploadPath)
if pos >= 0 {
path = gstr.SubStr(pathInfo["path"], pos)
}
return
}

49
library/liberr/err.go Normal file
View File

@@ -0,0 +1,49 @@
/*
* @desc:错误处理
* @company:云南奇讯科技有限公司
* @Author: yixiaohu
* @Date: 2022/3/2 14:53
*/
package liberr
import (
"context"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/tiger1103/gfast/v3/internal/consts"
)
func ErrIsNil(ctx context.Context, err error, msg ...string) {
if !g.IsNil(err) {
if len(msg) > 0 {
g.Log().Error(ctx, err.Error())
panic(NewCode(consts.CodeError, msg[0]))
} else {
panic(NewCode(consts.CodeError, err.Error()))
}
}
}
func ValueIsNil(value interface{}, msg string) {
if g.IsNil(value) {
panic(msg)
}
}
func NewCode(code int, msg string) error {
return gerror.NewCode(gcode.New(code, msg, nil))
}
func ErrIsNilCode(ctx context.Context, err error, code int, msg ...string) {
if !g.IsNil(err) {
if len(msg) > 0 {
g.Log().Error(ctx, err.Error())
panic(NewCode(code, msg[0]))
} else {
panic(NewCode(code, err.Error()))
}
}
}

164
library/liberr/err_test.go Normal file
View File

@@ -0,0 +1,164 @@
/*
* @desc:错误处理测试
* @company:云南奇讯科技有限公司
* @Author: yixiaohu
* @Date: 2022/3/2 14:53
*/
package liberr
import (
"context"
"errors"
"testing"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
)
func TestErrIsNil(t *testing.T) {
ctx := context.Background()
// Test case 1: 验证传入 nil 错误时不会 panic
t.Run("nil error", func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Errorf("ErrIsNil should not panic with nil error, but got: %v", r)
}
}()
ErrIsNil(ctx, nil)
})
// Test case 2: 验证传入非 nil 错误且无自定义消息时会 panic 并抛出原始错误
t.Run("non-nil error without message", func(t *testing.T) {
testErr := errors.New("test error")
defer func() {
if r := recover(); r == nil {
t.Error("ErrIsNil should panic with non-nil error")
} else if r != testErr {
t.Errorf("Expected panic with error %v, but got: %v", testErr, r)
}
}()
ErrIsNil(ctx, testErr)
})
// Test case 3: 验证传入非 nil 错误和自定义消息时会 panic 并抛出自定义消息
t.Run("non-nil error with custom message", func(t *testing.T) {
testErr := errors.New("test error")
customMsg := "custom error message"
defer func() {
if r := recover(); r == nil {
t.Error("ErrIsNil should panic with non-nil error and custom message")
} else if r != customMsg {
t.Errorf("Expected panic with message %q, but got: %v", customMsg, r)
}
}()
ErrIsNil(ctx, testErr, customMsg)
})
// Test case 4: 验证传入多个消息时使用第一个消息
t.Run("non-nil error with multiple messages", func(t *testing.T) {
testErr := errors.New("test error")
firstMsg := "first message"
secondMsg := "second message"
defer func() {
if r := recover(); r == nil {
t.Error("ErrIsNil should panic with non-nil error and messages")
} else if r != firstMsg {
t.Errorf("Expected panic with first message %q, but got: %v", firstMsg, r)
}
}()
ErrIsNil(ctx, testErr, firstMsg, secondMsg)
})
// Test case 5: 验证 gf 框架的 gerror.NewCode 错误处理 - 验证码场景
t.Run("gerror with CodeValidationFailed", func(t *testing.T) {
testErr := gerror.NewCode(gcode.CodeValidationFailed, "验证码已过期或不存在")
defer func() {
if r := recover(); r == nil {
t.Error("ErrIsNil should panic with gerror")
} else if r != testErr {
t.Errorf("Expected panic with gerror %v, but got: %v", testErr, r)
}
}()
ErrIsNil(ctx, testErr)
})
// Test case 6: 验证 gerror.NewCode 错误与自定义消息
t.Run("gerror with custom message", func(t *testing.T) {
testErr := gerror.NewCode(gcode.CodeValidationFailed, "验证码已过期或不存在")
customMsg := "验证失败"
defer func() {
if r := recover(); r == nil {
t.Error("ErrIsNil should panic with gerror and custom message")
} else if r != customMsg {
t.Errorf("Expected panic with message %q, but got: %v", customMsg, r)
}
}()
ErrIsNil(ctx, testErr, customMsg)
})
// Test case 7: 验证不同类型的 gcode 错误
t.Run("gerror with CodeInternalError", func(t *testing.T) {
testErr := gerror.NewCode(gcode.CodeInternalError, "内部服务错误")
defer func() {
if r := recover(); r == nil {
t.Error("ErrIsNil should panic with CodeInternalError")
} else if r != testErr {
t.Errorf("Expected panic with gerror %v, but got: %v", testErr, r)
}
}()
ErrIsNil(ctx, testErr)
})
// Test case 8: 验证多种验证失败场景
t.Run("multiple validation scenarios", func(t *testing.T) {
scenarios := []struct {
name string
err error
msg string
}{
{
name: "验证码过期",
err: gerror.NewCode(gcode.CodeValidationFailed, "验证码已过期或不存在"),
msg: "验证码验证失败",
},
{
name: "参数验证失败",
err: gerror.NewCode(gcode.CodeValidationFailed, "参数格式不正确"),
msg: "请求参数错误",
},
{
name: "业务验证失败",
err: gerror.NewCode(gcode.CodeValidationFailed, "用户状态异常"),
msg: "",
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Error("ErrIsNil should panic")
} else {
if scenario.msg != "" {
if r != scenario.msg {
t.Errorf("Expected panic with message %q, but got: %v", scenario.msg, r)
}
} else {
if r != scenario.err {
t.Errorf("Expected panic with error %v, but got: %v", scenario.err, r)
}
}
}
}()
if scenario.msg != "" {
ErrIsNil(ctx, scenario.err, scenario.msg)
} else {
ErrIsNil(ctx, scenario.err)
}
})
}
})
}