Help us shape the future of Go! Take the 2026 Go Developer Survey

menu

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: AGPL-3.0 Imports: 18 Imported by: 0

Documentation

Overview

Package menu 提供导航菜单。

菜单分两层:menus 是菜单本身(主题按 slug 引用),menu_items 是它的条目树。 条目分两类——自定义链接手填地址,站内条目(文章 / 页面 / 分类 / 标签)指向记录 ID, 地址在读取时解析,记录改名或改 slug 后菜单自动跟随。

Index

Constants

View Source
const Name = "menu"

Name 是模块名,也是迁移版本表的后缀(goose_db_version_menu)。

Variables

View Source
var (
	// ErrNotFound 表示菜单不存在。
	ErrNotFound = errors.New("菜单不存在")
	// ErrSlugTaken 表示菜单 slug 已被占用。
	ErrSlugTaken = errors.New("菜单标识已被占用")
	// ErrInvalid 表示字段违反了数据库约束。
	ErrInvalid = errors.New("字段不合法")
	// ErrCycle 表示条目树的父子关系成环。
	ErrCycle = errors.New("父条目不能是自身或其后代")
	// ErrTargetNotFound 表示条目指向的站内记录不存在。
	ErrTargetNotFound = errors.New("条目指向的内容不存在")
)

错误哨兵。处理器据此映射为 400 / 404 / 409。

Functions

func CountItems

func CountItems(nodes []*ItemNode) int

CountItems 统计树中的条目数。

Types

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler 提供菜单的 Console 与 Public 接口。

func NewHandler

func NewHandler(store *Store, resolver *resolver, log logger) *Handler

NewHandler 构造 Handler。

func (*Handler) Register

func (h *Handler) Register(console, public huma.API)

Register 挂载接口。

读写都要求 menus:manage:菜单是站点级配置,不存在「自己的菜单」这种所有权概念。

type Item

type Item struct {
	bun.BaseModel `bun:"table:menu_items,alias:mi"`

	ID       int64    `bun:"id,pk,autoincrement" json:"id"`
	MenuID   int64    `bun:"menu_id,notnull"     json:"menuId"`
	ParentID *int64   `bun:"parent_id"           json:"parentId" doc:"父条目 ID,一级菜单为 null"`
	Position int      `bun:"position"            json:"position" doc:"同级排序,越小越靠前"`
	Label    string   `bun:"label,notnull"       json:"label"`
	Type     ItemType `bun:"type,notnull"        json:"type" enum:"custom,post,page,category,tag"`
	// TargetID 是站内记录的 ID,仅 post / page / category / tag 类型有值。
	TargetID *int64 `bun:"target_id" json:"targetId" doc:"站内记录 ID,自定义链接为 null"`
	// URL 对自定义链接是手填值;对站内条目是由记录解析出的地址。
	URL    string `bun:"url"     json:"url"`
	Target string `bun:"target"  json:"target" enum:",_blank" doc:"空串为当前窗口,_blank 为新窗口"`
	Rel    string `bun:"rel"     json:"rel" doc:"额外的 rel 属性,如 nofollow"`
	// Visible 为假时前台不渲染该条目,但后台仍保留。
	Visible   bool      `bun:"visible"            json:"visible"`
	CreatedAt time.Time `bun:"created_at,nullzero" json:"createdAt"`
	UpdatedAt time.Time `bun:"updated_at,nullzero" json:"updatedAt"`
}

Item 是菜单里的一个条目。

type ItemNode

type ItemNode struct {
	Item
	Children []*ItemNode `json:"children" doc:"子条目,按 position 排序"`
}

ItemNode 是菜单树的节点:条目本身加上按 position 排序的子条目。

func BuildTree

func BuildTree(items []Item) []*ItemNode

BuildTree 把平铺的条目组装成树。

只保留能追溯到根节点的条目:父项被删时子项已由外键级联删除, 但脏数据仍可能存在,这里再兜一层。

func VisibleTree

func VisibleTree(nodes []*ItemNode) []*ItemNode

VisibleTree 返回只含可见条目的树,供前台渲染。

父项隐藏时整支不渲染:让一个隐藏的父项还留着可见的子项, 会在前台产生一串没有归属的链接。

type ItemType

type ItemType string

ItemType 是菜单条目的类型,决定 URL 从哪来。

const (
	// TypeCustom 是手填地址的条目。
	TypeCustom ItemType = "custom"
	// 以下四种指向站内已有记录,URL 由服务端解析,记录改名或改 slug 后菜单自动跟随。
	TypePost     ItemType = "post"
	TypePage     ItemType = "page"
	TypeCategory ItemType = "category"
	TypeTag      ItemType = "tag"
)

条目类型。

type Menu struct {
	bun.BaseModel `bun:"table:menus,alias:mn"`

	ID          int64     `bun:"id,pk,autoincrement" json:"id"`
	Name        string    `bun:"name,notnull"        json:"name"`
	Slug        string    `bun:"slug,notnull"        json:"slug" doc:"主题按此名取菜单,全局唯一"`
	Description string    `bun:"description"         json:"description"`
	CreatedAt   time.Time `bun:"created_at,nullzero" json:"createdAt"`
	UpdatedAt   time.Time `bun:"updated_at,nullzero" json:"updatedAt"`

	// Items 仅在按 slug 取整棵树时填充,不是数据库列。
	Items []*ItemNode `bun:"-" json:"items,omitempty"`
	// ItemCount 是条目数,供后台列表显示。
	ItemCount int `bun:"-" json:"itemCount"`
}

Menu 是一组菜单。

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module 是菜单模块。

func New

func New() *Module

New 构造模块。

func (*Module) Migrations

func (m *Module) Migrations() fs.FS

Migrations 实现 app.Migrator。

func (*Module) Name

func (m *Module) Name() string

Name 实现 app.Module。

func (*Module) Navigation

func (m *Module) Navigation() app.Navigation

Navigation 实现 app.NavigationProvider。

func (*Module) Permissions

func (m *Module) Permissions() []app.Permission

Permissions 实现 app.PermissionProvider。

func (*Module) Register

func (m *Module) Register(a *app.App) error

Register 实现 app.Module:只做装配,不访问数据库。

func (*Module) Routes

func (m *Module) Routes(r app.Router)

Routes 实现 app.RouteProvider。

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store 提供菜单与条目的持久化操作。

func NewStore

func NewStore(db *bun.DB) *Store

NewStore 构造 Store。

func (*Store) Create

func (s *Store) Create(ctx context.Context, m *Menu) error

Create 创建菜单;成功后回填 ID 与时间戳。

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, id int64) error

Delete 删除菜单;其条目由外键级联删除。

func (*Store) Get

func (s *Store) Get(ctx context.Context, id int64) (*Menu, error)

Get 按 ID 查菜单。

func (*Store) GetBySlug

func (s *Store) GetBySlug(ctx context.Context, slug string) (*Menu, error)

GetBySlug 按 slug 查菜单。

func (*Store) Items

func (s *Store) Items(ctx context.Context, menuID int64) ([]Item, error)

Items 返回某菜单的全部条目,按父项与 position 排序。

func (*Store) List

func (s *Store) List(ctx context.Context) ([]Menu, error)

List 返回全部菜单,按名称排序,并带上条目数。

func (*Store) ReplaceItems

func (s *Store) ReplaceItems(ctx context.Context, menuID int64, items []Item, parents []int) error

ReplaceItems 用给定条目整体替换菜单的条目树。

整体替换而不是逐条 diff:菜单是「一次性编辑、一次性保存」的表单, 逐条同步要处理移动、重排、删除与重建的交叉情形,出错的概率远大于收益。 代价是条目 ID 每次保存都会变——主题按 label 与 url 渲染,不依赖 ID。

items 须为深度优先序(父项一定排在其子项之前),parents[i] 给出第 i 项在 items 中的父项下标,-1 表示一级条目。父项的行号只有在插入后才能拿到, 所以按下标而不是按客户端给的 ID 建立父子关系。

事务开头先对菜单父行加行锁(SELECT ... FOR UPDATE),把同一菜单的整树替换串行化: 两个并发事务若都先 DELETE 再各自 INSERT,最终表里会同时留下两棵树 (DELETE 看不见对方尚未提交的插入),而两个请求都返回成功。

func (*Store) Update

func (s *Store) Update(ctx context.Context, m *Menu) error

Update 更新菜单的基本信息。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL