go语言ent ORM框架增强-自定义排序

默认的排序方法

ent支持两种排序方法:

go 复制代码
users, err := client.User.Query().
    Order(ent.Asc(user.FieldName, user.FieldNickName)).
    All(ctx)

v0.12.0开始,支持如下排序方法:

go 复制代码
// 正序排序
users, err := client.User.Query().
    Order(
        user.ByName(),
        user.ByNickname(),
    ).
    All(ctx)

// 倒序排序
users, err := client.User.Query().
    Order(
        user.ByNickname(
            sql.OrderDesc(),
        ),
    ).
    All(ctx)

自定义排序方法

分页查询请求参数:

go 复制代码
package page

import (
	"github.com/gin-gonic/gin"
)

const defaultPageSize = 10

type Pagination struct {
	PageNo   int `form:"pageNo" json:"page_no" binding:"required"`
	PageSize int `form:"pageSize" json:"page_size" binding:"required"`
	// 排序规则,格式: `a,b,c ASC;d,e DESC;f`, 默认ASC,ASC可以省略。
	// 字段名称为小写字母开头的驼峰格式,如userName, 会自动转换为蛇形user_name,
	// 如果字段在数据表中不存在,则忽略该排序字段。
	OrderBy string `form:"orderBy" json:"order_by"`
}

func NewPagination() *Pagination {
	return &Pagination{}
}

// GetTerm return Calculated query conditions
func (p *Pagination) GetTerm() (int, int) {
	if p.PageSize == 0 {
		p.PageSize = defaultPageSize
	}
	if p.PageNo <= 0 {
		p.PageNo = 1
	}

	offset := (p.PageNo - 1) * p.PageSize
	limit := p.PageSize
	return offset, limit
}

func BindPagination(c *gin.Context) (*Pagination, error) {
	p := NewPagination()

	if err := c.ShouldBindQuery(&p); err != nil {
		return nil, err
	}

	return p, nil
}

使用ent的模板功能,为每个模型都绑定OrderByExpr方法,模板内容如下:

go 复制代码
{{ define "order_by" }}
// Code generated by ent, DO NOT EDIT.

package entcore


const orderAsc = "ASC"
const orderDesc = "DESC"

// ==========================
// 通用排序解析
// ==========================
type OrderItem struct {
	Fields    []string
	Direction string
}

// camelToSnake 将 CamelCase 转为 snake_case
func camelToSnake(input string) string {
	// 正则匹配大写字母前插入下划线
	// 特别处理:多个大写字母(如 ID、IP)保持一组处理
	re := regexp.MustCompile(`([a-z0-9])([A-Z])`)
	snake := re.ReplaceAllString(input, `${1}_${2}`)

	// 处理 ID、URL 等缩写后带大写的问题,如:UserIDNumber → user_id_number
	re2 := regexp.MustCompile(`([A-Z]+)([A-Z][a-z0-9]+)`)
	snake = re2.ReplaceAllString(snake, `${1}_${2}`)

	// 全部转小写
	return strings.ToLower(snake)
}

// parseSortExpr 解析排序表达式,例如:
// "a,b,c ASC; d,e DESC"
func parseSortExpr(expr string) ([]OrderItem, error) {
	items := []OrderItem{}
	parts := strings.Split(expr, ";")
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}

        dir := orderAsc
        var fields []string
		segs := strings.Fields(part)
		len := len(segs)
		if len == 0 {
			continue
		} else if len == 1 {
			_fields := strings.Split(segs[0], ",")
			for i := range _fields {
				fields = append(fields, camelToSnake(strings.TrimSpace(_fields[i])))
			}
		} else if len == 2 {
			_fields := strings.Split(segs[0], ",")
			for i := range _fields {
				fields = append(fields, camelToSnake(strings.TrimSpace(_fields[i])))
			}

			if strings.ToUpper(segs[1]) == orderDesc {
			    dir = orderDesc
			}
		} else {
		    continue
		}

		items = append(items, OrderItem{
			Fields:    fields,
			Direction: dir,
		})
	}
	return items, nil
}

// ==========================
// 每个模型的 OrderByExpr 方法
// ==========================
{{ range $n, $t := $.Nodes }}

// OrderByExpr 支持多字段、多方向排序
func (q *{{ $t.Name }}Query) OrderByExpr(expr string) *{{ $t.Name }}Query {
	if expr == "" {
		return q
	}

	items, _ := parseSortExpr(expr)

	for _, item := range items {
	    var fields []string
		for _, f := range item.Fields {
			ok := {{ $t.Name | lower }}.ValidColumn(f)
			if !ok {
				continue
			}

			fields = append(fields, f)
		}

        if len(fields) > 0 {
            if item.Direction == orderAsc {
		    	q = q.Order(Asc(fields...))
		    } else {
		    	q = q.Order(Desc(fields...))
		    }
        }
	}
	return q
}
 
// 按ID正序排序
func (q *{{ $t.Name }}Query) OrderByID() *{{ $t.Name }}Query {
	return q.Order({{ $t.Name | lower }}.ByID())
}

// 按ID倒序排序
func (q *{{ $t.Name }}Query) OrderByIDDesc() *{{ $t.Name }}Query {
	return q.Order({{ $t.Name | lower }}.ByID(sql.OrderDesc()))
}

{{ end }}

{{ end }}

此外,还添加了OrderByIDOrderByIDDesc方法。

生成代码

在生成代码的命令中加入--template选项指定模板:

shell 复制代码
go run -mod=mod entgo.io/ent/cmd/ent generate --feature intercept,schema/snapshot ./schema  --target ./schema/entcode --template glob="./template/*.tmpl"

生成的代码:

go 复制代码
const orderAsc = "ASC"
const orderDesc = "DESC"

// ==========================
// 通用排序解析
// ==========================
type OrderItem struct {
	Fields    []string
	Direction string
}

// camelToSnake 将 CamelCase 转为 snake_case
func camelToSnake(input string) string {
	// 正则匹配大写字母前插入下划线
	// 特别处理:多个大写字母(如 ID、IP)保持一组处理
	re := regexp.MustCompile(`([a-z0-9])([A-Z])`)
	snake := re.ReplaceAllString(input, `${1}_${2}`)

	// 处理 ID、URL 等缩写后带大写的问题,如:UserIDNumber → user_id_number
	re2 := regexp.MustCompile(`([A-Z]+)([A-Z][a-z0-9]+)`)
	snake = re2.ReplaceAllString(snake, `${1}_${2}`)

	// 全部转小写
	return strings.ToLower(snake)
}

// parseSortExpr 解析排序表达式,例如:
// "a,b,c ASC; d,e DESC"
func parseSortExpr(expr string) ([]OrderItem, error) {
	items := []OrderItem{}
	parts := strings.Split(expr, ";")
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}

		dir := orderAsc
		var fields []string
		segs := strings.Fields(part)
		len := len(segs)
		if len == 0 {
			continue
		} else if len == 1 {
			_fields := strings.Split(segs[0], ",")
			for i := range _fields {
				fields = append(fields, camelToSnake(strings.TrimSpace(_fields[i])))
			}
		} else if len == 2 {
			_fields := strings.Split(segs[0], ",")
			for i := range _fields {
				fields = append(fields, camelToSnake(strings.TrimSpace(_fields[i])))
			}

			if strings.ToUpper(segs[1]) == orderDesc {
				dir = orderDesc
			}
		} else {
			continue
		}

		items = append(items, OrderItem{
			Fields:    fields,
			Direction: dir,
		})
	}
	return items, nil
}

// OrderByExpr 支持多字段、多方向排序
func (q *SysUserQuery) OrderByExpr(expr string) *SysUserQuery {
	if expr == "" {
		return q
	}

	items, _ := parseSortExpr(expr)

	for _, item := range items {
		var fields []string
		for _, f := range item.Fields {
			ok := sysuser.ValidColumn(f)
			if !ok {
				continue
			}

			fields = append(fields, f)
		}

		if len(fields) > 0 {
			if item.Direction == orderAsc {
				q = q.Order(Asc(fields...))
			} else {
				q = q.Order(Desc(fields...))
			}
		}
	}
	return q
}

// 按ID正序排序
func (q *SysUserQuery) OrderByID() *SysUserQuery {
	return q.Order(sysuser.ByID())
}

// 按ID倒序排序
func (q *SysUserQuery) OrderByIDDesc() *SysUserQuery {
	return q.Order(sysuser.ByID(sql.OrderDesc()))
}

排序条件orderBy中的字段为小写字母开头的驼峰格式。会使用camelToSnake方法将字段转为蛇形,并用ValidColumn方法校验,以防字段在数据表中不存在而报错。ValidColumn是ent自动生成的校验字段在数据表中是否存在的方法,如不存在,则该字段会被忽略。

相关推荐
程序员爱钓鱼11 小时前
Go 语言实战 从 PDF 批量提取条码的自动化工具开发全流程解析
后端·go·trae
程序员爱钓鱼11 小时前
Go 语言爬虫实战:基于 Colly 的高性能采集框架指南
后端·go·trae
百锦再17 小时前
[特殊字符] HBuilder uni-app UI 组件库全方位对比
android·java·开发语言·ui·rust·uni-app·go
用户7227868123441 天前
go ants pool 协程池学习笔记
go
哈茶真的c2 天前
【书籍心得】左耳听风:传奇程序员练级攻略
java·c语言·python·go
心月狐的流火号2 天前
Go sync.Mutex 源码解析:设计哲学与工程智慧
go·源码阅读
Anthony_49262 天前
【踩坑】gorm 回写主键不正确
mysql·go·orm
IUGEI3 天前
【MySQL】SQL慢查询如何排查?从慢查询排查到最终优化完整流程
java·数据库·后端·mysql·go
Daydreamer3 天前
Trpc配置插件
go