ueditor

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Dec 4, 2022 License: MIT Imports: 30 Imported by: 0

README

go with ueditor

一站式 ueditor go后端

API文档: GoDoc Go Report Card

虽然官方 ueditor 已经停止维护,但个人觉得现在功能比较完善的前端富文本编辑器中,还是 ueditor 比较好用 ^outdate。在类似管理后台或某些不对外开放的场景,功能强大的富文本编辑器用起来总是比较省心,如果开放给普通用户使用,多少还是有些不放心。

受支持环境:

一、安装

go get -u github.com/hellflame/ueditor

二、概念

作为一款前端工具,在使用时首先需要在页面中引入正确的 资源路径,该路径包含 ueditor 所需的js、css等资源。该 资源路径 可直接使用后端提供的文件服务,默认在 /assets/ 路径下提供 ueditor 所需前端资源服务,资源为官方最后一次打包的 1.4.3.3 版本[^version]

除了 ueditor 在初始化时默认向后端请求的 配置接口 外,编辑器的基本编辑功能都无需再请求后端接口(图片和附件等可以通过外部链接引入)。极端情况下,配置接口可直接通过文件服务的形式给出,做到无后端驱动就基本可用的编辑器。

ueditor 的后端实现主要提供了三部分接口,配置接口文件上传接口文件列表接口配置接口 主要为接下来的各类文件相关操作提供参数控制;文件上传接口 为不同类型的文件上传提供支持,虽然上传的类型和限制参数不同,但主要目的还是将文件保存到服务器并返回可在页面访问的链接;文件列表接口 为编辑器提供已上传文件数据的展示功能,编辑器可服用列表中的文件而不用再次上传。上传后的文件需要能被访问到,所以后端也提供了基本的 资源服务

三、使用

资源引入

如上一节概念中所说,首先需要在页面中使用正确的 资源路径 ,如默认的 /assets/ ,并引入编辑器入口文件(ueditor.all.min.js)、配置文件(config.js)和本地化文件(lang/zh-cn/zh-cn.js),如 demo.html 中所示:

<script type="text/javascript" src="/assets/config.js"></script>
<script type="text/javascript" src="/assets/ueditor.all.min.js"></script>
<script type="text/javascript" src="/assets/lang/zh-cn/zh-cn.js"></script>

可以使用 CDN 替代[^cdn]

后端服务

此处使用 go 自带的 http 库 + 本地存储 并使用默认配置 作为示例:

// 创建以本地存储作为介质的 editor 实例,文件将存储到本地 uploads 目录中
editor := ueditor.NewEditor(nil, ueditor.NewLocalStorage("uploads"))

// 将后端接口服务与资源服务与默认的 http 服务绑定
ueditor.BindHTTP(nil, nil, editor)

完整示例

具体使用方法可参考示例 examples/plain/

# 启动示例
go run serve.go

启动成功后默认访问 http://localhost:8080/demo

> 更多后端服务配置
  • 绑定路由到 gin
- ueditor.BindHTTP(nil, nil, editor)
+ router := gin.Default()
+ ueditor.BindGin(router, nil, editor)

完整示例

  • 绑定路由到 gorilla/mux
- ueditor.BindHTTP(nil, nil, editor)
+ router := mux.NewRouter()
+ ueditor.BindMux(router, nil, editor)

完整示例

> 更多存储服务配置
  • 本地存储可以基于sqlite作为数据索引
// 此处选择该sqlite驱动,理论上可以选择支持database/sql的驱动均可
import _ "github.com/mattn/go-sqlite3"

...

// 连接sqlite数据库 resource.db
db, e := sql.Open("sqlite3", "resource.db")
if e != nil {
  panic(e)
}
defer db.Close()

// 创建以本地存储作为介质的 editor 实例,文件将存储到本地 uploads 目录中,使用sqlite作为文件索引
// sqlite 会在这里初始化时自动检查表或创建表
editor := ueditor.NewEditor(nil, ueditor.NewSqliteStorage("uploads", db))

...


仅切换绑定到 UEditorStorage 实现

该方式仅将文件元信息等数据存储到 sqlite 中,文件实体内容依然存储在本地,为避免文件过于集中,此时将资源文件按照 hash 前两个字符分割到不同目录中

  • 本地存储可以通过orm框架 gorm 映射mysql、sqlite、psq等数据存储服务存储数据索引
import (
  "github.com/hellflame/ueditor"
  "gorm.io/driver/sqlite"
  "gorm.io/gorm"
)

...

// 此处使用gorm提供的sqlite驱动
db, _ := gorm.Open(sqlite.Open("resource.db"))

// 通过 NewGormStorage 绑定 gorm 实例
// gorm 会在此时检查表或创建表
editor := ueditor.NewEditor(nil, ueditor.NewGormStorage("uploads", db))

...

完整示例

  • minio 存储
// 使用自己的 minio 实例
client, e := minio.New("192.168.1.8:9000", &minio.Options{
  Creds: credentials.NewStaticV4("minioadmin", "minioadmin", ""),
})
if e != nil {
  panic(e)
}
// create editor with storage
editor := ueditor.NewEditor(nil, ueditor.NewMinioStorage(client))

...

完整示例

此时本地不存储上传的文件,返回给 ueditor 的资源链接将使用 minio 链接。minio endpoint 将用于生成资源链接,所以需要注意其可访问性

四、说明

架构

后端接口在设计上主要分为三部分,UEditorStorage 以及 Bridge

UEditor 包含配置与接口数据的处理,并通过 Storage 接口进行资源数据的存储与查询,也是一个与 http 服务打交道的数据结构 (struct)。其所提供的配置主要为前端编辑器与 Storage 提供依据,联系着http服务与存储服务。

Storage 定义了存储相关的接口,除了可将资源数据存储到本地外,还可根据该接口协议实现 minio 等外部资源的存储。可根据需要进行实现 (如配合 mysql, mongo 等数据库)。

基本的 本地存储 将上传文件存储到不同的类型目录下(图片、文件、视频等),以文件的md5作为文件名和元数据文件名存储实际的文件内容,同一个文件上传多次只会存储一个副本。原始数据文件与元数据文件需要同时存在才能提供完整的资源服务。

Bridge 主要目的为将实现的接口添加到当前 http 接口服务中,由于不同后端框架 (http、gin、mux) 有自己的路由定义和响应方式,需要使用对应的桥接方法,比如示例中的 BindHTTP

条件编译
1. 资源路径

该后端服务在编译时默认会将 资源路径 进行内嵌,若已使用外部资源[^external],可添加编译条件 external 降低发布尺寸。

# 取消嵌入资源路径,此时ueditor前端资源需要外部引入
go build -tags external
2. 存储实现

存储实现在默认构建时会将所有资源存储代码都打包,如本地存储,sqlite等,若想仅保留一种存储实现:

可首先添加编译条件 nostorage 以禁用所有资源存储代码,再添加如下条件选择需要保留的资源存储方式

  1. onlylocal : 仅保留本地存储 + 本地索引代码
  2. onlysqlite : 仅保留本地存储 + sqlite 索引代码
  3. onlygorm:仅保留本地存储 + gorm 框架代码
  4. onlyminio : 仅保留minio存储
# 仅保留本地存储 + 本地索引
go build -tags "nostorage onlylocal"
3. 桥接实现

后端在构建时默认会将所有桥接代码都进行构建,将 http,gin 等框架均打包进可执行文件中

可首先添加编译条件 nobridge 以禁用所有桥接代码,再添加如下条件选择需要保留的框架

  1. onlyhttp : 仅保留 http 框架代码
  2. onlygin : 仅保留 gin 框架代码
  3. onlymux : 仅保留 mux 框架代码
# 仅保留 gin 框架入口
go build -tags "nobridge onlygin"

五、示例

为了减少不必要的包依赖,示例服务入口文件默认无法直接构建,可去除头部编译条件 //go:build ignore 进行构建,调试时通过 go run serve.go 直接运行,如果提示依赖缺失,需要 go get 安装依赖

1. http + 纯本地存储
2. http + 本地存储 + sqlite索引
3. http + 本地存储 + gorm(sqlite)索引
4. http + minio
5. gin + 纯本地存储
6. mux + 纯本地存储

[^cdn]: 如果使用 CDN 或其他 资源路径 ,此处需要注意 config.js 中所给 serverUrl 的值需与后端接口保持一致 [^version]: 本来想用最新的开发版 dev-1.5.0,但打包后发现部分功能存在问题,所以还是用了最后一个发布版本

[^external]: 比如使用其他版本的 ueditor 、修复版本或同接口的兼容编辑器

Documentation

Index

Constants

View Source
const (
	ImageSaveBase  = "images"
	FileSaveBase   = "files"
	VideoSaveBase  = "videos"
	ScrawlSaveBase = "scrawls"
)
View Source
const (
	StateOK             = "SUCCESS"
	StateFileSizeExceed = "文件大小超出限制"
)

Variables

View Source
var (
	ErrFileAsDir       = errors.New("file used as dir")
	ErrPathMalform     = errors.New("file path incorrect")
	ErrFileMissing     = errors.New("target file not found")
	ErrFileMetaMissing = errors.New("file meta not found")
	ErrNotImpled       = errors.New("method not implemented")
)

Functions

func BindGin

func BindGin(engine *gin.Engine, c *ServiceConfig, editor *UEditor)

绑定路由到 gin 环境

func BindHTTP

func BindHTTP(mux *http.ServeMux, c *ServiceConfig, editor *UEditor) *http.ServeMux

绑定路由到系统库 http

func BindMux added in v0.3.0

func BindMux(mux *mux.Router, c *ServiceConfig, editor *UEditor)

绑定路由到 gorrila/mux

func LowerCamelMarshal added in v0.6.0

func LowerCamelMarshal(i any) []byte

json序列化时将首字母转小写

func NewGormStorage added in v0.4.0

func NewGormStorage(base string, db *gorm.DB) *gormStorage

NewGormStorage create a *gormStorage instance which implemented Storage interface

File info is stored in given database instance, using table 'resources'

func NewMinioStorage added in v0.5.0

func NewMinioStorage(client *minio.Client) *minioStorage

func NewSqliteStorage added in v0.3.0

func NewSqliteStorage(base string, db *sql.DB) *sqliteStorage

NewSqliteStorage create a *sqliteStorage instance which implemented Storage interface

File info is stored in given sqlite database, using table 'resources'

func SendJsonPResponse added in v0.6.0

func SendJsonPResponse(w http.ResponseWriter, callback string, resp []byte)

func SendJsonResponse added in v0.3.0

func SendJsonResponse(w http.ResponseWriter, resp []byte)

Types

type Actions

type Actions struct {
	Config string

	UploadImage string
	UploadFile  string

	UploadVideo  string
	UploadScrawl string

	ListImages string
	ListFiles  string
}

ueditor 文件上传所用参数集合

type Config

type Config struct {
	ConfigActionName string `default:"config"`

	ImageActionName     string   `default:"up-image"`
	ImageFieldName      string   `default:"upfile"`
	ImageMaxSize        int      `default:"5000000"`
	ImageAllowFiles     []string `default:".png|.jpg|.jpeg|.gif|.bmp"`
	ImageCompressEnable bool     `default:"true"`
	ImageCompressBorder int      `default:"1600"`
	ImageInsertAlign    string   `default:"none"`
	ImageUrlPrefix      string

	ScrawlActionName string `default:"up-scrawl"`
	ScrawlFieldName  string `default:"upfile"`
	// ScrawlPathFormat  string
	ScrawlMaxSize     int `default:"2000000"`
	ScrawlUrlPrefix   string
	ScrawlInsertAlign string   `default:"none"`
	ScrawlAllowFiles  []string `default:".png"`

	SnapscreenActionName string `default:"up-image"`
	// SnapscreenPathFormat  string
	SnapscreenUrlPrefix   string
	SnapscreenMaxSize     int    `default:"2000000"`
	SnapscreenInsertAlign string `default:"none"`

	CatcherLocalDomain []string
	CatcherActionName  string `default:"catch-image"`
	CatcherFieldName   string `default:"source"`
	// CatcherPathFormat  string
	CatcherUrlPrefix  string
	CatcherMaxSize    int      `default:"5000000"`
	CatcherAllowFiles []string `default:".png|.jpg|.jpeg|.gif|.bmp"`

	VideoActionName string `default:"up-video"`
	VideoFieldName  string `default:"upfile"`
	// VideoPathFormatstring string
	VideoUrlPrefix  string
	VideoMaxSize    int      `default:"100000000"`
	VideoAllowFiles []string `default:".flv|.swf|.mkv|.avi|.rm|.rmvb|.mpeg|.mpg|.ogg|.ogv|.mov|.wmv|.mp4|.webm|.mp3|.wav|.mid"`

	FileActionName string `default:"up-file"`
	FileFieldName  string `default:"upfile"`
	// FilePathFormat string
	FileUrlPrefix  string
	FileMaxSize    int      `default:"50000000"`
	FileAllowFiles []string `` /* 212-byte string literal not displayed */

	ImageManagerActionName string `default:"list-image"`
	// ImageManagerListPath    string
	ImageManagerListSize    int `default:"20"`
	ImageManagerUrlPrefix   string
	ImageManagerInsertAlign string   `default:"none"`
	ImageManagerAllowFiles  []string `default:".png|.jpg|.jpeg|.gif|.bmp"`

	FileManagerActionName string `default:"list-file"`
	// FileManagerListPath   string
	FileManagerUrlPrefix  string
	FileManagerListSize   int      `default:"20"`
	FileManagerAllowFiles []string `` /* 212-byte string literal not displayed */
}

前端 ueditor 配置参数,如上传文件大小限制、文件后缀等

type FShard

type FShard struct {
	Url   string
	Mtime int
}

文件列表响应单个元素

type FileInfo

type FileInfo struct {
	Name   string
	Path   string
	Modify int
}

文件列表数据结构,与 http 服务输出相关联

type ListResp

type ListResp struct {
	State string
	List  []FShard
	Start int
	Total int
}

ueditor 文件列表响应结构

type LocalStorage

type LocalStorage struct {
	Base string
}

func NewLocalStorage

func NewLocalStorage(base string) *LocalStorage

NewLocalStorage create a *LocalStorage instance which implemented Storage interface

LocalStorage stores file & meta info on local file system, file & meta file are in pair

func (*LocalStorage) List

func (l *LocalStorage) List(prefix string, offset, limit int) (files []FileInfo, total int)

func (*LocalStorage) Read

func (l *LocalStorage) Read(p string) (*MetaInfo, []byte, error)

func (*LocalStorage) Save

func (l *LocalStorage) Save(prefix string, h *multipart.FileHeader, f io.Reader) (string, error)

type MetaInfo

type MetaInfo struct {
	Filename string
	MimeType string
	Size     int64
}

文件元信息数据结构,关联元数据存储与 http 服务输出

type Resources added in v0.4.0

type Resources struct {
	Category string `gorm:"uniqueIndex:ukey,size:50"`
	Hash     string `gorm:"uniqueIndex:ukey,size:50"`
	Filename string `gorm:"size:256"`
	Mimetype string `gorm:"size:50"`
	Size     int64
	Created  int64
	Chunks   int `gorm:"default:0"`
}

gorm 模型定义 - 表结构

type ServiceConfig

type ServiceConfig struct {
	// ueditor编辑器基地址路径,其他 js, css资源等从该路径获取
	EditorHome string `default:"/assets/"`
	// 资源文件,默认使用内嵌资源文件
	Asset fs.FS
	// 编辑器所用上传等功能的接口地址,默认地址已与编辑器配置保持一致,如编辑器资源有变,此处需修改
	ApiPath string `default:"/ueditor-api"`
	// 本地提供上传资源的文件服务时使用该路径
	SrcServePrefix string `default:"/resource/"`
}

type Storage

type Storage interface {
	Save(prefix string, h *multipart.FileHeader, f io.Reader) (path string, e error)
	Read(path string) (meta *MetaInfo, content []byte, e error)
	List(prefix string, offset, limit int) (files []FileInfo, total int)
}

存储接口,用于支持多种存储方式

type UEditor

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

func NewEditor

func NewEditor(c *Config, s Storage) *UEditor

NewEditor create *UEditor for binding use

func (*UEditor) GetActions

func (u *UEditor) GetActions() Actions

func (*UEditor) GetConfig

func (u *UEditor) GetConfig() []byte

func (*UEditor) GetUploadFieldName

func (u *UEditor) GetUploadFieldName() string

func (*UEditor) ListFiles

func (u *UEditor) ListFiles(prefix string, offset, size int) ListResp

func (*UEditor) OnListFiles

func (u *UEditor) OnListFiles(offset, size int) ListResp

func (*UEditor) OnListImages

func (u *UEditor) OnListImages(offset, size int) ListResp

func (*UEditor) OnUploadFile

func (u *UEditor) OnUploadFile(h *multipart.FileHeader, f io.Reader) UploadResp

func (*UEditor) OnUploadImage

func (u *UEditor) OnUploadImage(h *multipart.FileHeader, f io.Reader) UploadResp

func (*UEditor) OnUploadScrawl

func (u *UEditor) OnUploadScrawl(h *multipart.FileHeader, f io.Reader) UploadResp

func (*UEditor) OnUploadVideo

func (u *UEditor) OnUploadVideo(h *multipart.FileHeader, f io.Reader) UploadResp

func (*UEditor) ReadFile

func (u *UEditor) ReadFile(path string) (meta *MetaInfo, raw []byte, e error)

func (*UEditor) SaveFile

func (u *UEditor) SaveFile(prefix string, h *multipart.FileHeader, f io.Reader) UploadResp

func (*UEditor) SetSrvPrefix

func (u *UEditor) SetSrvPrefix(prefix string)

type UploadResp

type UploadResp struct {
	State    string
	Url      string
	Title    string
	Original string
	Type     string
	Size     int
}

ueditor 上传后响应结构

Jump to

Keyboard shortcuts

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