Documentation
¶
Overview ¶
Package media 提供附件与图片处理。
上传的文件经 Storage 接口落到本地目录或 S3 兼容对象存储;图片额外生成多档 WebP 缩略图。 存储位置由 storage 设置分组决定,可在运行时切换;S3 密钥只从环境变量读取,不入库。
Index ¶
- Constants
- Variables
- func AllowedExtensions() []string
- func S3Credentials() (accessKey, secretKey string, err error)
- type ErrContentMismatch
- type ErrUnsupportedType
- type Filter
- type Handler
- type ImageInfo
- type ImageProcessor
- type Kind
- type LocalStorage
- type Media
- type Module
- func (m *Module) Migrations() fs.FS
- func (m *Module) Name() string
- func (m *Module) Navigation() app.Navigation
- func (m *Module) Permissions() []app.Permission
- func (m *Module) Register(a *app.App) error
- func (m *Module) Routes(r app.Router)
- func (m *Module) Service() *Service
- func (m *Module) Settings() []app.SettingGroup
- type S3Config
- type S3Storage
- type Service
- type ServiceOptions
- type Storage
- type StorageSettings
- type Store
- func (s *Store) Create(ctx context.Context, m *Media) error
- func (s *Store) Delete(ctx context.Context, id int64) error
- func (s *Store) Get(ctx context.Context, id int64) (*Media, error)
- func (s *Store) Page(ctx context.Context, filter Filter, params api.PageParams) ([]Media, int, error)
- func (s *Store) UpdateMeta(ctx context.Context, m *Media) error
- type Thumbnail
- type ThumbnailSpec
- type UploadParams
- type UploaderView
- type Variant
Constants ¶
const ( EnvS3AccessKey = "LUMO_S3_ACCESS_KEY" EnvS3SecretKey = "LUMO_S3_SECRET_KEY" )
S3 凭据的兜底环境变量。
密钥的正规去处是后台「附件存储」里的两个字段(加密入库、接口不回传), 环境变量留给不便改后台的部署。两处都有时以后台为准。
const DriverLocal = "local"
DriverLocal 是本地文件系统驱动名。
const DriverS3 = "s3"
DriverS3 是 S3 兼容对象存储驱动名。
const GroupStorage = "storage"
GroupStorage 是附件存储设置分组。
const Name = "media"
Name 是模块名,也是迁移版本表的后缀(goose_db_version_media)。
const UploadsDirName = "uploads"
UploadsDirName 是本地驱动在工作目录下的子目录名(与 workdir.Subdirs 一致)。
const UploadsURLPrefix = "/" + UploadsDirName
UploadsURLPrefix 是本地驱动的对外访问前缀,由核心路由挂载静态文件服务。
Variables ¶
var ( // ErrNotFound 表示附件不存在。 ErrNotFound = errors.New("附件不存在") // ErrKeyTaken 表示对象键已被占用。 ErrKeyTaken = errors.New("对象键已被占用") // ErrInvalid 表示字段违反了数据库约束。 ErrInvalid = errors.New("字段不合法") )
错误哨兵。处理器据此映射为 404 / 409。
var DefaultThumbnails = []ThumbnailSpec{
{Name: "thumb", MaxEdge: 320},
{Name: "medium", MaxEdge: 768},
{Name: "large", MaxEdge: 1600},
}
DefaultThumbnails 是默认的缩略图档位。
三档覆盖列表缩略图、正文内联图与全宽大图;原图长边不足某档时跳过该档, 绝不放大——放大只会得到更大的模糊文件。
var ErrInvalidKey = errors.New("对象键不合法")
ErrInvalidKey 表示对象键不合法。
var ErrMissingS3Credentials = fmt.Errorf( "没有对象存储访问密钥:请在「设置 → 附件存储」里填写,或设置环境变量 %s 与 %s", EnvS3AccessKey, EnvS3SecretKey)
ErrMissingS3Credentials 表示没有可用的 S3 凭据。
var ErrTooLarge = errors.New("文件超过允许的大小")
ErrTooLarge 表示上传内容超过服务端允许的大小。
Functions ¶
func AllowedExtensions ¶
func AllowedExtensions() []string
AllowedExtensions 返回全部允许的扩展名,按字典序,供接口文档与前端提示使用。
func S3Credentials ¶
S3Credentials 从环境变量读取访问密钥,作为后台未配置时的兜底。
Types ¶
type ErrContentMismatch ¶
ErrContentMismatch 表示文件内容与扩展名声明的类型不符。
type ErrUnsupportedType ¶
type ErrUnsupportedType struct{ Ext string }
ErrUnsupportedType 表示扩展名不在白名单内。
type Filter ¶
type Filter struct {
// Kind 非空时只返回该分类的附件。
Kind Kind
// UploaderID 非零时只返回该用户上传的附件。
UploaderID int64
// Q 非空时按原始文件名模糊筛选。
Q string
}
Filter 是附件列表的筛选条件。
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler 提供附件的 Console 接口。
type ImageProcessor ¶
type ImageProcessor interface {
// Name 返回实现名,写入启动日志便于排查。
Name() string
// Process 解码图片(含 EXIF 方向纠正)并按 specs 生成缩略图。
//
// 返回的尺寸是**纠正方向之后**的,与缩略图和前台布局一致。
Process(data []byte, specs []ThumbnailSpec) (ImageInfo, []Variant, error)
}
ImageProcessor 从原始图片字节生成缩略图。
抽成接口是为了给 libvips 留位置:默认实现是纯 Go 的(CGO_ENABLED=0 可编译, 单二进制不依赖系统库),大站可以换成 vips 实现以换取速度与更好的编码质量。 接入方式:新增 image_vips.go 并加 //go:build vips,同时给 image_purego.go 加 //go:build !vips。
type LocalStorage ¶
type LocalStorage struct {
// contains filtered or unexported fields
}
LocalStorage 把对象写到工作目录下的 uploads 子目录,由核心路由以静态文件提供。
func NewLocalStorage ¶
func NewLocalStorage(root, urlPrefix string) (*LocalStorage, error)
NewLocalStorage 构造本地驱动。root 为存放目录,urlPrefix 为对外访问前缀(如 /uploads)。
func (*LocalStorage) Delete ¶
func (s *LocalStorage) Delete(_ context.Context, key string) error
Delete 实现 Storage。
func (*LocalStorage) URL ¶
func (s *LocalStorage) URL(key string) string
URL 实现 Storage:本地驱动给出站内相对地址,换域名或加 CDN 时无需改库。
type Media ¶
type Media struct {
bun.BaseModel `bun:"table:media,alias:m"`
ID int64 `bun:"id,pk,autoincrement" json:"id"`
Filename string `bun:"filename,notnull" json:"filename" doc:"存储用的随机化文件名"`
OriginalName string `bun:"original_name" json:"originalName" doc:"上传时的原始文件名,仅作展示"`
MIME string `bun:"mime,notnull" json:"mime"`
Kind Kind `bun:"kind,notnull" json:"kind" enum:"image,video,audio,document,other"`
Size int64 `bun:"size" json:"size" doc:"字节数"`
Width int `bun:"width" json:"width" doc:"图片宽度;非图片为 0"`
Height int `bun:"height" json:"height" doc:"图片高度;非图片为 0"`
Driver string `bun:"driver,notnull" json:"driver" doc:"写入时使用的存储驱动"`
StorageKey string `bun:"storage_key,notnull" json:"storageKey" doc:"存储内的对象键"`
URL string `bun:"url" json:"url" doc:"公开访问地址"`
Thumbnails []Thumbnail `bun:"thumbnails,type:jsonb" json:"thumbnails" doc:"按最长边生成的多档 WebP 缩略图"`
Checksum string `bun:"checksum" json:"checksum" doc:"原文件的 SHA-256"`
UploaderID int64 `bun:"uploader_id,notnull" json:"uploaderId"`
Alt string `bun:"alt" json:"alt" doc:"无障碍替代文本"`
Title string `bun:"title" json:"title" doc:"标题"`
CreatedAt time.Time `bun:"created_at,nullzero" json:"createdAt"`
UpdatedAt time.Time `bun:"updated_at,nullzero" json:"updatedAt"`
// Uploader 由存储层按需填充,不是数据库列。
Uploader *UploaderView `bun:"-" json:"uploader,omitempty"`
}
Media 是一条附件记录。
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
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) Settings ¶
func (m *Module) Settings() []app.SettingGroup
Settings 实现 app.SettingsProvider。
type S3Config ¶
type S3Config struct {
// Endpoint 是服务地址,可带协议(https://minio.example.com)也可只写 host:port。
Endpoint string
Bucket string
Region string
// PathStyle 为真时用 endpoint/bucket/key 形式寻址。自建 MinIO、Ceph 一般需要它;
// 各家兼容程度不一,故不做自动探测,交由部署者显式指定。
PathStyle bool
UseSSL bool
// PublicURL 是对象的公开访问前缀(如 CDN 域名);留空则由 endpoint 与 bucket 推导。
PublicURL string
}
S3Config 是 S3 兼容存储的连接参数,来自 storage 设置分组(密钥除外)。
type S3Storage ¶
type S3Storage struct {
// contains filtered or unexported fields
}
S3Storage 是基于 minio-go 的 S3 兼容驱动。
func NewS3Storage ¶
NewS3Storage 构造 S3 驱动。
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service 编排上传:类型判定、写存储、生成缩略图、落库。
func (*Service) Delete ¶
Delete 删除附件记录并清理其占用的全部对象。
先删库再删文件:反过来一旦删完文件后落库失败,就会留下指向空文件的记录, 那比留下几个孤儿文件更糟——前台会拿到 404 图片。
type ServiceOptions ¶
type ServiceOptions struct {
Store *Store
// Settings 可为 nil:此时一律使用本地存储的默认配置。
Settings *settings.Service
// Images 可为 nil:缺省用纯 Go 实现。
Images ImageProcessor
// DataDir 是工作目录,本地存储写在它的 uploads 子目录下。
DataDir string
Logger *slog.Logger
// MaxUpload 是单个文件的字节上限,0 表示不额外限制(仍受路由层请求体上限约束)。
MaxUpload int64
}
ServiceOptions 是构造 Service 的依赖。
type Storage ¶
type Storage interface {
// Driver 返回驱动名,随附件记录入库。
Driver() string
// Put 写入对象;size 为负表示长度未知。同名对象存在时覆盖。
Put(ctx context.Context, key string, r io.Reader, size int64, contentType string) error
// Delete 删除对象;对象不存在不算错误。
Delete(ctx context.Context, key string) error
// URL 返回对象的公开访问地址。
URL(key string) string
}
Storage 是附件的对象存储抽象。
实现必须把 key 当作**不可信输入**再校验一次:调用方虽然只传自己生成的键, 但删除走的是库里的历史记录,多一道校验才能保证路径穿越在任何路径上都不成立。
type StorageSettings ¶
type StorageSettings struct {
Driver string `json:"driver"`
S3Endpoint string `json:"s3Endpoint"`
S3Bucket string `json:"s3Bucket"`
S3AccessKey string `json:"s3AccessKey"`
S3SecretKey string `json:"s3SecretKey"`
S3Region string `json:"s3Region"`
S3PathStyle bool `json:"s3PathStyle"`
S3UseSSL bool `json:"s3UseSsl"`
S3PublicURL string `json:"s3PublicUrl"`
}
StorageSettings 是 storage 分组的有效值。
两个密钥字段是解密后的明文,只在这条链路里流转:设置服务 → S3 客户端。 出接口时它们恒为空串,见 settings.Group.Mask。
func (*StorageSettings) Credentials ¶
func (st *StorageSettings) Credentials() (accessKey, secretKey string, err error)
Credentials 返回本次连接该用的访问密钥:后台填过就用后台的,否则回退环境变量。
只有一边填了就用一边、另一边去环境变量里凑是不行的——那样拼出来的是一个 谁也没配过的密钥对,错误信息还会指向「密钥不对」,而真正的问题是少填了一格。
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store 提供附件的持久化操作。
type Thumbnail ¶
type Thumbnail struct {
Name string `json:"name" doc:"档位名,如 thumb / medium / large"`
Key string `json:"key" doc:"存储内的对象键"`
URL string `json:"url" doc:"公开访问地址"`
Width int `json:"width"`
Height int `json:"height"`
Size int64 `json:"size" doc:"字节数"`
}
Thumbnail 是一档缩略图。
type ThumbnailSpec ¶
type ThumbnailSpec struct {
// Name 是档位名,写入缩略图记录并出现在文件名里。
Name string
// MaxEdge 是缩放后最长边的像素上限。
MaxEdge int
}
ThumbnailSpec 是一档缩略图的规格:按最长边等比缩放。
type UploadParams ¶
type UploadParams struct {
// OriginalName 是客户端给出的文件名,只用于取扩展名与展示,绝不参与路径拼接。
OriginalName string
// Size 是内容字节数的客户端声明值:用于超限预判与 S3 分片决策,落库以实际读到的字节数为准。
Size int64
Content io.Reader
Alt string
Title string
// UploaderID 是上传者的用户 ID。
UploaderID int64
}
UploadParams 描述一次上传。