表格排序器增加关联表支持和字段映射

This commit is contained in:
孟帅
2023-08-14 16:25:47 +08:00
parent 122af88992
commit 2a1e0c811c
6 changed files with 293 additions and 15 deletions

View File

@@ -1,30 +1,95 @@
// Package handler
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package handler
import (
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gutil"
"hotgo/internal/consts"
"hotgo/internal/model/input/form"
"hotgo/utility/convert"
"regexp"
)
// ISorter 排序器接口实现该接口即可使用Handler匹配排序支持多字段排序
type ISorter interface {
GetSorters() []form.Sorter
GetSorters() []*form.Sorter
}
// Sorter 排序器
func Sorter(in ISorter) func(m *gdb.Model) *gdb.Model {
return func(m *gdb.Model) *gdb.Model {
hasSort := false
masterTable, ts := convert.GetModelTable(m)
fields, err := m.TableFields(masterTable)
if err != nil {
g.Log().Panicf(m.GetCtx(), "failed to sorter TableFields err:%+v", err)
}
sorters := in.GetSorters()
aliases := extractTableAliases(ts)
if len(aliases) > 0 {
var newSorters []*form.Sorter
var removeIndex []int
for as, table := range aliases {
// 关联表
fds, err := m.TableFields(table)
if err != nil {
g.Log().Panicf(m.GetCtx(), "failed to sorter TableFields err2:%+v", err)
}
var sorter2 []*form.Sorter
for k, sorter := range sorters {
if gstr.HasPrefix(sorter.ColumnKey, as) {
sorter2 = append(sorter2, &form.Sorter{
ColumnKey: gstr.Replace(sorter.ColumnKey, as, ""),
Order: sorter.Order,
})
removeIndex = append(removeIndex, k)
}
}
if len(sorter2) > 0 {
sorter2 = mappingAndFilterToTableFields(fds, sorter2)
for _, v := range sorter2 {
v.ColumnKey = fmt.Sprintf("`%v`.`%v`", as, v.ColumnKey)
}
newSorters = append(newSorters, sorter2...)
}
}
// 移除关联表字段
sorters = mappingAndFilterToTableFields(fields, removeSorterIndexes(sorters, removeIndex))
for _, v := range sorters {
v.ColumnKey = fmt.Sprintf("`%v`.`%v`", masterTable, v.ColumnKey)
}
sorters = append(newSorters, sorters...)
} else {
// 单表
sorters = mappingAndFilterToTableFields(fields, sorters)
for _, v := range sorters {
v.ColumnKey = fmt.Sprintf("`%v`.`%v`", masterTable, v.ColumnKey)
}
}
hasSort := false
for _, sorter := range sorters {
if len(sorter.ColumnKey) == 0 || !sorter.Sorter {
if len(sorter.ColumnKey) == 0 {
continue
}
switch sorter.Order {
case "descend":
case "descend": // 降序
hasSort = true
m = m.OrderDesc(sorter.ColumnKey)
case "ascend":
case "ascend": // 升序
hasSort = true
m = m.OrderAsc(sorter.ColumnKey)
default:
@@ -32,10 +97,98 @@ func Sorter(in ISorter) func(m *gdb.Model) *gdb.Model {
}
}
// 不存在排序条件
if !hasSort {
// ...
if hasSort {
return m
}
return m
// 不存在排序条件,默认使用主表主键做降序排序
var pk string
for name, field := range fields {
if gstr.ContainsI(field.Key, consts.GenCodesIndexPK) {
pk = name
break
}
}
// 没有主键
if len(pk) == 0 {
return m
}
// 存在别名,优先匹配别名
if len(aliases) > 0 {
for as, table := range aliases {
if table == masterTable {
return m.OrderDesc(fmt.Sprintf("`%v`.`%v`", as, pk))
}
}
}
return m.OrderDesc(fmt.Sprintf("`%v`.`%v`", masterTable, pk))
}
}
// extractTableAliases 解析关联条件中的关联表别名
func extractTableAliases(ts string) map[string]string {
re := regexp.MustCompile("`?([^`\\s]+)`?\\s+AS\\s+`?([^`\\s]+)`?\\s")
matches := re.FindAllStringSubmatch(ts, -1)
result := make(map[string]string)
for _, match := range matches {
result[match[2]] = match[1]
}
return result
}
// removeSorterIndexes 移除指定索引的排序器
func removeSorterIndexes(slice []*form.Sorter, indexes []int) []*form.Sorter {
removed := make([]*form.Sorter, 0)
indexMap := make(map[int]bool)
for _, index := range indexes {
indexMap[index] = true
}
for i, value := range slice {
if !indexMap[i] {
removed = append(removed, value)
}
}
return removed
}
// mappingAndFilterToTableFields 将排序字段映射为实际的表字段
func mappingAndFilterToTableFields(fieldsMap map[string]*gdb.TableField, sorters []*form.Sorter) (ser []*form.Sorter) {
if len(fieldsMap) == 0 {
return
}
var fields []string
for _, v := range sorters {
fields = append(fields, v.ColumnKey)
}
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
for k := range fieldsMap {
fieldsKeyMap[k] = nil
}
var inputFieldsArray = gstr.SplitAndTrim(gstr.Join(fields, ","), ",")
for _, field := range inputFieldsArray {
if _, ok := fieldsKeyMap[field]; ok {
continue
}
if !gregex.IsMatchString(`^[\w\-]+$`, field) {
continue
}
if foundKey, _ := gutil.MapPossibleItemByKey(fieldsKeyMap, field); foundKey != "" {
for _, v := range sorters {
if v.ColumnKey == field {
v.ColumnKey = foundKey
}
}
}
}
return sorters
}

View File

@@ -0,0 +1,96 @@
// Package handler
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package handler_test
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"github.com/gogf/gf/v2/os/gctx"
"hotgo/internal/dao"
"hotgo/internal/library/hgorm"
"hotgo/internal/library/hgorm/handler"
"hotgo/internal/model/input/form"
"testing"
)
type SorterInput struct {
form.Sorters
}
// TestSorterDefault 默认排序
func TestSorterDefault(t *testing.T) {
in := &SorterInput{} // 不存在排序条件,默认使用主表主键降序排序
_, err := dao.SysGenCurdDemo.Ctx(gctx.New()).Handler(handler.Sorter(in)).All()
if err != nil {
t.Error(err)
return
}
}
// TestSorter 多字段排序
func TestSorter(t *testing.T) {
in := &SorterInput{
Sorters: form.Sorters{
Sorters: []*form.Sorter{
{
ColumnKey: "id",
Order: "descend", // 降序
},
{
ColumnKey: "categoryId", // 自动转换为下划线。categoryId -> category_id
Order: false, // 不参与排序
},
{
ColumnKey: "created_at",
Order: "descend", // 降序
},
},
},
}
_, err := dao.SysGenCurdDemo.Ctx(gctx.New()).Handler(handler.Sorter(in)).All()
if err != nil {
t.Error(err)
return
}
}
// TestSorterJoinTable 关联表多字段排序
func TestSorterJoinTable(t *testing.T) {
in := &SorterInput{
Sorters: form.Sorters{
Sorters: []*form.Sorter{
{
ColumnKey: "id",
Order: "descend", // 降序
},
{
ColumnKey: "categoryId", // 自动转换为下划线。categoryId -> category_id
Order: false, // 不参与排序
},
{
ColumnKey: "created_at",
Order: "descend", // 降序
},
{
ColumnKey: "testCategoryName", // 自动识别关联表别名。 testCategoryName -> testCategory.name
Order: "ascend", // 升序
},
},
},
}
_, err := dao.SysGenCurdDemo.Ctx(gctx.New()).
LeftJoin(hgorm.GenJoinOnRelation(
dao.SysGenCurdDemo.Table(), dao.SysGenCurdDemo.Columns().CategoryId, // 主表表名,关联字段
dao.TestCategory.Table(), "testCategory", dao.TestCategory.Columns().Id, // 关联表表名,别名,关联字段
)...).
Handler(handler.Sorter(in)).All()
if err != nil {
t.Error(err)
return
}
}