content

package
v0.1.4 Latest Latest
Warning

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

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

Documentation

Overview

Package content 提供文章与独立页面。

两者共用一张 posts 表,以 type 区分;发布、修订、定时发布与可见性逻辑只写一份, 接口按类型分别挂在 /posts 与 /pages 下,权限分别走 posts:* 与 pages:*。 依赖 taxonomy 模块(分类与标签关联),装配时必须排在它之后。

Index

Constants

View Source
const ExcerptLength = 200

ExcerptLength 是自动摘要的最大字符数(按 Unicode 字符计)。

View Source
const Name = "content"

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

View Source
const RevisionsKept = 30

RevisionsKept 是每篇内容保留的修订数上限,超出后最旧的被清理。

Variables

View Source
var (
	// ErrNotFound 表示内容或修订不存在。
	ErrNotFound = errors.New("对象不存在")
	// ErrSlugTaken 表示同类型下 slug 已被占用。
	ErrSlugTaken = errors.New("slug 已被占用")
	// ErrTermNotFound 表示引用的分类或标签不存在。
	ErrTermNotFound = errors.New("分类或标签不存在")
	// ErrNotTrashed 表示内容不在回收站,无法彻底删除。
	ErrNotTrashed = errors.New("内容不在回收站")
	// ErrInvalid 表示字段违反了数据库约束。
	ErrInvalid = errors.New("字段不合法")
)

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

Functions

func Excerpt

func Excerpt(content string, limit int) string

Excerpt 从渲染后的 HTML 生成纯文本摘要:去标签、解实体、折叠空白,超长时截断并加省略号。

func Highlight

func Highlight(rendered string) string

Highlight 给渲染后的 HTML 里的代码块加上语法高亮与结构外壳。

只改写 <pre>…</pre> 这一段,其余字节原样保留——正文里其他部分不该因为 「顺手过了一遍 HTML 解析器」而被重新序列化。

func Render

func Render(rawType RawType, raw string) (string, error)

Render 把原稿渲染为主题消费的 HTML。

HTML 原稿原样返回:编辑器产出的就是规范 HTML;Markdown 经 goldmark 渲染, 标题锚点用 slug 规则生成,中文标题得到可读的中文 ID。

func Sanitize

func Sanitize(html string) string

Sanitize 按允许列表净化 HTML。仅用于没有 content:unsafe_html 权限的作者产出的正文。

func StripTags

func StripTags(s string) string

StripTags 去掉 HTML 标签(含 script 与 style 的内容),解码实体并折叠空白。

每个标签边界都产生一个空格,"<p>a</p><p>b</p>" 得到 "a b" 而不是 "ab"。 这不是净化器,只用于生成摘要与搜索文本。

Types

type AuthorView

type AuthorView struct {
	ID          int64  `json:"id"`
	Username    string `json:"username"`
	DisplayName string `json:"displayName"`
	AvatarURL   string `json:"avatarUrl"`
}

AuthorView 是嵌入内容响应的作者摘要,只含可公开的字段。

type Filter

type Filter struct {
	Type Type
	// Status 为空表示回收站之外的全部状态。
	Status Status
	// AuthorID 非 0 时只取该作者的内容。
	AuthorID int64
	// CategorySlug / TagSlug 非空时按分类或标签筛选。
	CategorySlug string
	TagSlug      string
	// Query 非空时按标题模糊匹配。
	Query string
	// PublicOnly 为真时只取已发布内容:公开的,或属于 ViewerID 的私密内容。
	PublicOnly bool
	ViewerID   int64
}

Filter 是列表筛选条件。

type Handler

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

Handler 提供内容的 Console 与 Public 接口。

func NewHandler

func NewHandler(store *Store, slugify Slugger) *Handler

NewHandler 构造 Handler;slugify 为 nil 时使用保留中文的缺省策略。

func (*Handler) Register

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

Register 为文章与页面各挂一套接口。

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) PublishDue

func (m *Module) PublishDue(ctx context.Context) (int64, error)

PublishDue 立即执行一次定时发布扫描,返回推进条数;供后台任务与测试调用。

func (*Module) Register

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

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

若 settings 模块先于本模块装配,slug 生成策略跟随站点设置;否则退回保留中文的缺省策略。

func (*Module) Routes

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

Routes 实现 app.RouteProvider。

func (*Module) Start

func (m *Module) Start(ctx context.Context) error

Start 实现 app.Starter:启动定时发布扫描,ctx 取消时退出。

扫描是幂等的单条 UPDATE,多实例部署时各实例都跑也不会重复发布。

type Post

type Post struct {
	bun.BaseModel `bun:"table:posts,alias:p"`

	ID          int64          `bun:"id,pk,autoincrement" json:"id"`
	Type        Type           `bun:"type,notnull"        json:"type" enum:"post,page"`
	Title       string         `bun:"title,notnull"       json:"title"`
	Slug        string         `bun:"slug,notnull"        json:"slug" doc:"URL 片段,同类型内唯一"`
	Status      Status         `bun:"status,notnull"      json:"status" enum:"draft,published,scheduled,trashed"`
	Visibility  Visibility     `bun:"visibility,notnull"  json:"visibility" enum:"public,private"`
	RawType     RawType        `bun:"raw_type,notnull"    json:"rawType" enum:"html,markdown" doc:"原稿格式"`
	Raw         string         `bun:"raw"                 json:"raw" doc:"原稿:Markdown 源码或规范 HTML"`
	Content     string         `bun:"content"             json:"content" doc:"渲染后的 HTML,主题只消费此字段"`
	Excerpt     string         `bun:"excerpt"             json:"excerpt"`
	ExcerptAuto bool           `bun:"excerpt_auto"        json:"excerptAuto" doc:"摘要是否由正文自动生成"`
	CoverURL    string         `bun:"cover_url"           json:"coverUrl"`
	Pinned      bool           `bun:"pinned"              json:"pinned"`
	Template    string         `bun:"template"            json:"template" doc:"页面模板名,如 page-about;空为默认"`
	AuthorID    int64          `bun:"author_id,notnull"   json:"authorId"`
	PublishedAt *time.Time     `bun:"published_at"        json:"publishedAt" doc:"发布时间;定时发布时为计划时间"`
	TrashedAt   *time.Time     `bun:"trashed_at"          json:"trashedAt"`
	Meta        map[string]any `bun:"meta,type:jsonb"     json:"meta" doc:"扩展元数据"`
	CreatedAt   time.Time      `bun:"created_at,nullzero" json:"createdAt"`
	UpdatedAt   time.Time      `bun:"updated_at,nullzero" json:"updatedAt"`

	// 以下为关联数据,由存储层按需填充,不是数据库列。
	Author     *AuthorView         `bun:"-" json:"author,omitempty"`
	Categories []taxonomy.Category `bun:"-" json:"categories"`
	Tags       []taxonomy.Tag      `bun:"-" json:"tags"`
}

Post 是文章或页面实体。

func (*Post) IsPublic

func (p *Post) IsPublic() bool

IsPublic 报告内容是否对匿名访客可见。

type PostCategory

type PostCategory struct {
	bun.BaseModel `bun:"table:post_categories,alias:pc"`

	PostID     int64 `bun:"post_id,pk"`
	CategoryID int64 `bun:"category_id,pk"`
}

PostCategory 是文章与分类的关联。

type PostTag

type PostTag struct {
	bun.BaseModel `bun:"table:post_tags,alias:pt"`

	PostID int64 `bun:"post_id,pk"`
	TagID  int64 `bun:"tag_id,pk"`
}

PostTag 是文章与标签的关联。

type RawType

type RawType string

RawType 是原稿格式。

const (
	RawHTML     RawType = "html"
	RawMarkdown RawType = "markdown"
)

原稿格式。

type Revision

type Revision struct {
	bun.BaseModel `bun:"table:post_revisions,alias:rv"`

	ID        int64     `bun:"id,pk,autoincrement" json:"id"`
	PostID    int64     `bun:"post_id,notnull"     json:"postId"`
	AuthorID  *int64    `bun:"author_id"           json:"authorId" doc:"保存该版本的用户;用户已删除时为 null"`
	Title     string    `bun:"title,notnull"       json:"title"`
	RawType   RawType   `bun:"raw_type,notnull"    json:"rawType" enum:"html,markdown"`
	Raw       string    `bun:"raw"                 json:"raw"`
	Content   string    `bun:"content"             json:"content"`
	Excerpt   string    `bun:"excerpt"             json:"excerpt"`
	CreatedAt time.Time `bun:"created_at,nullzero" json:"createdAt"`
}

Revision 是一次内容快照。

type RevisionSummary

type RevisionSummary struct {
	bun.BaseModel `bun:"table:post_revisions,alias:rv"`

	ID        int64     `bun:"id,pk"            json:"id"`
	PostID    int64     `bun:"post_id"          json:"postId"`
	AuthorID  *int64    `bun:"author_id"        json:"authorId"`
	Title     string    `bun:"title"            json:"title"`
	RawType   RawType   `bun:"raw_type"         json:"rawType" enum:"html,markdown"`
	CreatedAt time.Time `bun:"created_at,nullzero" json:"createdAt"`
}

RevisionSummary 是修订列表项,不含正文以免列表过大。

type Slugger

type Slugger func(ctx context.Context, text string) string

Slugger 由文本生成 slug;站点设置决定保留中文还是转拼音。

type Status

type Status string

Status 是内容的生命周期状态。

const (
	// StatusDraft 是草稿:仅作者与有权限者可见。
	StatusDraft Status = "draft"
	// StatusPublished 已发布。
	StatusPublished Status = "published"
	// StatusScheduled 定时发布:到 PublishedAt 时由后台任务推进为已发布。
	StatusScheduled Status = "scheduled"
	// StatusTrashed 在回收站:可恢复,也可彻底删除。
	StatusTrashed Status = "trashed"
)

内容状态。

type Store

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

Store 提供内容的持久化操作。

func NewStore

func NewStore(db *bun.DB, users *auth.Store) *Store

NewStore 构造 Store。

func (*Store) Create

func (s *Store) Create(ctx context.Context, p *Post, categoryIDs, tagIDs []int64) error

Create 创建内容、写入分类与标签关联,并记录首个修订;成功后回填 ID、时间戳与关联。

func (*Store) DeletePermanently

func (s *Store) DeletePermanently(ctx context.Context, typ Type, id int64) error

DeletePermanently 彻底删除内容,关联与修订随外键级联消失。

删除条件里带上 status = 'trashed',让「检查是否在回收站」与「删除」成为同一条语句: 先读后删之间存在窗口,用户在这期间点了「恢复」,刚恢复的内容就会被永久删除, 连同它的修订与评论一起不可逆地消失。条件不满足时返回 ErrNotTrashed。

func (*Store) Get

func (s *Store) Get(ctx context.Context, typ Type, id int64) (*Post, error)

Get 按 ID 取内容(含关联)。

func (*Store) GetBySlug

func (s *Store) GetBySlug(ctx context.Context, typ Type, slug string) (*Post, error)

GetBySlug 按 slug 取内容(含关联)。

func (*Store) Page

func (s *Store) Page(ctx context.Context, f *Filter, params api.PageParams) ([]Post, int, error)

Page 分页返回内容并附上作者、分类与标签。

前台按置顶与发布时间排序,后台按更新时间排序——编辑者关心的是最近动过的内容。

func (*Store) PublishDue

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

PublishDue 把计划时间已到的定时内容推进为已发布,返回推进条数。

单条幂等 UPDATE:多实例同时执行也不会重复发布或互相干扰。

func (*Store) Revision

func (s *Store) Revision(ctx context.Context, postID, revisionID int64) (*Revision, error)

Revision 取指定修订的完整内容。

func (*Store) Revisions

func (s *Store) Revisions(ctx context.Context, postID int64) ([]RevisionSummary, error)

Revisions 返回内容的修订列表,新的在前。

func (*Store) Update

func (s *Store) Update(ctx context.Context, p *Post, categoryIDs, tagIDs []int64, snapshot bool, editorID int64) error

Update 更新内容的可编辑字段与关联;snapshot 为真时追加修订并清理超出上限的旧修订。

func (*Store) UpdateStatus

func (s *Store) UpdateStatus(ctx context.Context, p *Post) error

UpdateStatus 只写入状态相关列:status、published_at、trashed_at 与 updated_at。

type Type

type Type string

Type 区分文章与独立页面,两者共用一张表。

const (
	TypePost Type = "post"
	TypePage Type = "page"
)

内容类型。

type Visibility

type Visibility string

Visibility 是内容对前台的可见性。

const (
	VisibilityPublic  Visibility = "public"
	VisibilityPrivate Visibility = "private"
)

可见性。

Jump to

Keyboard shortcuts

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