后端开发
本文档详细介绍 ElasticView 插件后端开发的架构设计、API 开发、数据库操作和最佳实践。
技术栈
- Go 1.20+ - 编程语言
- eve-plugin-sdk-go - ElasticView 官方后端 SDK
- Gin - HTTP Web 框架(SDK内置)
- SQLite/MySQL - 数据持久化支持
项目结构
插件项目根目录/
├── backend/ # 后端代码目录
│ ├── router/ # 路由配置
│ │ └── router.go # 路由注册文件
│ ├── migrate/ # 数据库迁移(可选)
│ │ └── v0_0_1.go # 版本迁移文件
│ └── main.go # 程序入口
├── frontend/ # 前端代码目录
├── plugin.json # 插件配置文件(必需)
├── logo.png # 插件图标(必需)
├── go.mod
├── go.sum
└── README.md
入口文件
go
package main
import (
"context"
"embed"
_ "embed"
"ev-plugin/backend/migrate"
"ev-plugin/backend/router"
"ev-plugin/frontend"
"flag"
"github.com/1340691923/eve-plugin-sdk-go/backend/plugin_server"
"github.com/1340691923/eve-plugin-sdk-go/build"
)
//go:embed plugin.json
var pluginJsonBytes []byte
//go:embed logo.png
var logoPng embed.FS
func main() {
// 必须先解析参数
flag.Parse()
// 启动插件服务
plugin_server.Serve(plugin_server.ServeOpts{
Assets: &plugin_server.Assets{
PluginJsonBytes: pluginJsonBytes, // plugin.json 插件配置,必填
FrontendFiles: frontend.StatisFs, // 前端工程编译后产物,必填
Icon: logoPng, // logo图标,必填
},
ReadyCallBack: func(ctx context.Context) {
// 插件就绪后的回调,可以在这里进行初始化操作,可以监听ctx管道从而实现优雅退出
},
Migration: &build.Gormigrate{Migrations: []*build.Migration{
migrate.V0_0_1(),
}}, // 插件存储的版本迁移配置,可选
RegisterRoutes: router.NewRouter, // 注册插件http接口路由,必填
})
}
插件api路由注册
文件地址:router/router.go
go
package router
import (
"github.com/1340691923/eve-plugin-sdk-go/backend/web_engine"
"github.com/1340691923/eve-plugin-sdk-go/ev_api"
)
// NewRouter 注册插件路由
func NewRouter(engine *web_engine.WebEngine) {
// 创建API路由组
userGroup := engine.Group("用户管理", "/api/user")
// 参数从左到右依次为:是否鉴权,接口备注,路由path,handler
userGroup.POST(true, "获取用户列表", "/list", getUserList)//为true则需ElasticView进行权限控制
userGroup.POST(false, "健康检查", "/health", healthCheck)//为false则需ElasticView进行权限控制
}
插件数据持久化
查询操作
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// 获取全局EV API实例(内部已处理成单例)
api := ev_api.GetEvApi()
// 查询多条记录
var users []User
err := api.StoreSelect(ctx, &users, "SELECT * FROM users WHERE age > ?", 18)
// 查询单条记录
var user User
err := api.StoreFirst(ctx, &user, "SELECT * FROM users WHERE id = ?", userId)
增删改操作
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// 获取全局EV API实例(内部已处理成单例)
api := ev_api.GetEvApi()
// 执行SQL语句(INSERT、UPDATE、DELETE) 参数从左到右是 上下文,SQL,SQL执行参数
rowsAffected, err := api.StoreExec(ctx, "UPDATE users SET name = ? WHERE id = ?", "新名称", userId)
// 批量执行(内部有事务处理,要么全执行成功,要么全执行失败)
sqls := []dto.ExecSql{
{Sql: "INSERT INTO users (name) VALUES (?)", Args: []interface{}{"用户1"}},
{Sql: "INSERT INTO users (name) VALUES (?)", Args: []interface{}{"用户2"}},
}
err := api.StoreMoreExec(ctx, sqls)
类orm操作
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// 获取全局EV API实例(内部已处理成单例)
api := ev_api.GetEvApi()
// 保存数据 参数从左到右是 上下文,表名,实体数据
err := api.StoreSave(ctx, "tableName", map[string]interface{}{
"name": "张三",
"email": "zhangsan@example.com",
"age": 25,
})
// 更新数据 参数从左到右是 上下文,表名,需修改实体数据,where条件SQL,SQL执行参数(对应 ? 传参)
rowsAffected, err := api.StoreUpdate(ctx, "tableName",
map[string]interface{}{"name": "李四"},
"id = ?", userId)
// 删除数据 参数从左到右是 上下文,表名,where条件SQL,SQL执行参数(对应 ? 传参)
rowsAffected, err := api.StoreDelete(ctx, "users", "id = ?", userId)
// 插入或更新(Upsert) 参数从左到右是 上下文,表名,需插入or更新实体数据,唯一键字段
err := api.StoreInsertOrUpdate(ctx, "tableName",
map[string]interface{}{
"setting_key": "theme",
"setting_value": "dark",
}, "setting_key")
插件数据migrate
这是插件版本升级时,表结构需要变更的migrate操作,如果需要数据存储,可以创建版本迁移文件:
go
// migrate/v0_0_1.go
package migrate
import "github.com/1340691923/eve-plugin-sdk-go/build"
// V0_0_1 创建版本迁移,分别为 sqlite 和 mysql
func V0_0_1() *build.Migration {
return &build.Migration{
ID: "v0.0.1", // 版本号
SqliteMigrateSqls: []*build.ExecSql{ //当ev存储类型为sqlite时执行
{
Sql: `CREATE TABLE IF NOT EXISTS user_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
setting_key TEXT NOT NULL,
setting_value TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`,
Args: []interface{}{},
},
{
Sql: "CREATE INDEX idx_user_settings_user_id ON user_settings(user_id)",
Args: []interface{}{},
},
},
MysqlMigrateSqls: []*build.ExecSql{//当ev存储类型为mysql时执行
{
Sql: `CREATE TABLE IF NOT EXISTS user_settings (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
setting_key VARCHAR(255) NOT NULL,
setting_value TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`,
Args: []interface{}{},
},
{
Sql: "CREATE INDEX idx_user_settings_user_id ON user_settings(user_id)",
Args: []interface{}{},
},
},
SqliteRollback: []*build.ExecSql{
{Sql: "DROP TABLE IF EXISTS user_settings", Args: []interface{}{}},
},
MysqlRollback: []*build.ExecSql{
{Sql: "DROP TABLE IF EXISTS user_settings", Args: []interface{}{}},
},
}
}
//V0_0_1用于 plugin_server.ServeOpts的Migration属性中
func main() {
// 启动插件服务
plugin_server.Serve(plugin_server.ServeOpts{
...
Migration: &build.Gormigrate{Migrations: []*build.Migration{
migrate.V0_0_1(),
}} // 插件存储的版本迁移配置,可选
})
}
SQL构建工具
SDK提供了便捷的SQL构建器,可实现类orm操作:
go
import "github.com/1340691923/eve-plugin-sdk-go/sql_builder"
// 使用SQL构建器
query := sql_builder.SqlBuilder.
Select("id", "name", "email").
From("users").
Where(sql_builder.And{
sql_builder.Eq{"status": 1},
sql_builder.Gt{"age": 18},
}).
OrderBy("created_at DESC").
Limit(10).
Offset(sql_builder.CreatePage(2, 10)) // 第2页,每页10条
sql, args, err := query.ToSql()
if err != nil {
// 处理错误
}
// 执行查询
var users []User
err = api.StoreSelect(ctx, &users, sql, args...)
获取数据源类型
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// dataSourceId 为数据源id,一般为插件前端传递的 sdk.GetSelectEsConnID()
api := ev_api.NewEvWrapApi(dataSourceId, util.GetEvUserID(ctx))
res,err := api.DsType()
/***
数据源类型目前有
ElasticSearch6 = "elasticsearch6.x"
ElasticSearch7 = "elasticsearch7.x"
ElasticSearch8 = "elasticsearch8.x"
Mysql = "mysql"
Redis = "redis"
ClickHouse = "clickhouse"
Postgres = "postgres"
Mongo = "mongo"
Dameng = "dameng"
Oracle = "oracle"
Sqlserver = "sqlserver"
Mariadb = "mariadb"
Hive = "hive"
*/
Elasticsearch数据源操作
获取es版本
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// dataSourceId 为数据源id,一般为插件前端传递的 sdk.GetSelectEsConnID()
api := ev_api.NewEvWrapApi(dataSourceId, util.GetEvUserID(ctx))
// 获取ES版本
version, err := api.EsVersion()
ES查询操作
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// ES搜索操作
searchRes, err := api.EsSearch(ctx, dto.SearchReq{
SearchReqData: dto.SearchReqData{
SearchRequest: proto.SearchRequest{
Index: []string{"my-index"},
Size: proto.IntPtr(100),
From: proto.IntPtr(0),
},
Query: map[string]interface{}{
"match_all": map[string]interface{}{},
},
},
})
// 复杂查询示例
complexSearchRes, err := api.EsSearch(ctx, dto.SearchReq{
SearchReqData: dto.SearchReqData{
SearchRequest: proto.SearchRequest{
Index: []string{"logs-*"},
Size: proto.IntPtr(50),
},
Query: map[string]interface{}{
"bool": map[string]interface{}{
"must": []interface{}{
map[string]interface{}{
"range": map[string]interface{}{
"@timestamp": map[string]interface{}{
"gte": "now-1h",
},
},
},
map[string]interface{}{
"term": map[string]interface{}{
"level": "ERROR",
},
},
},
},
},
},
})
SQL类型数据库数据源操作
该api操作可用于以下数据源(mysql,clickhouse,达梦,hive,MariaDB,oracle,Postgresql,sqlserver)
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// dataSourceId 为数据源id,一般为插件前端传递的 sdk.GetSelectEsConnID()
api := ev_api.NewEvWrapApi(dataSourceId, util.GetEvUserID(ctx))
// 查询多条记录 参数从左到右分别为 上下文,数据库名,执行sql语句,sql参数
columns, result, err := api.MysqlSelectSql(ctx,"my_database",
"SELECT * FROM products WHERE price >= ? and price <= ? ",100,110})
// 查询单条记录 参数从左到右分别为 上下文,数据库名,执行sql语句,sql参数
result, err := api.MysqlFirstSql(ctx, "my_database",
"SELECT * FROM users WHERE id = ?",userId,
})
// 执行增删改的SQL 参数从左到右分别为 上下文,数据库名,执行sql语句,sql参数
rowsAffected, err := api.MysqlExecSql(ctx, "my_database",
"UPDATE products SET price = 240 WHERE category = ?","electronics",
})
Redis操作
通过key获取value
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// dataSourceId 为数据源id,一般为插件前端传递的 sdk.GetSelectEsConnID()
api := ev_api.NewEvWrapApi(dataSourceId, util.GetEvUserID(ctx))
// Redis命令执行 参数从左到右分别为 上下文,数据库名,Redis命令参数
data, err := api.RedisExecCommand(ctx,0,"GET", "user:123"})
/***
data 响应值类型为interface{} 以上代码相等于
import "github.com/go-redis/redis/v8"
data,err = redis.NewClient(options).Do(ctx, args...).Result()
*/
新增一个延迟删除的key
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// Redis SET操作 参数从左到右分别为 上下文,数据库名,Redis命令参数
data, err := api.RedisExecCommand(ctx, 0,"SET", "user:123", "张三", "EX", 3600)
Redis Hash操作
go
import "github.com/1340691923/eve-plugin-sdk-go/ev_api"
// Redis Hash操作 参数从左到右分别为 上下文,数据库名,Redis命令参数
data, err := api.RedisExecCommand(ctx, 0, "HGETALL", "user:profile:123")
MongoDB操作
go
import (
"github.com/1340691923/eve-plugin-sdk-go/ev_api"
"github.com/1340691923/eve-plugin-sdk-go/ev_api/bson"
"github.com/1340691923/eve-plugin-sdk-go/util"
)
// dataSourceId 为数据源id,一般为插件前端传递的 sdk.GetSelectEsConnID()
api := ev_api.NewEvWrapApi(dataSourceId, util.GetEvUserID(ctx))
// 执行MongoDB命令 参数从左到右分别为 上下文,数据库名,MongoDB命令,超时时间
result, err := api.ExecMongoCommand(ctx,
"dbname", bson.D{
{"find", "users"},
{"filter", bson.D{
{"age", bson.D{{"$gt", 18}}},
{"status", "active"},
}},
{"limit", 100},
}, 30 * time.Second,
})
执行mongodb插入多条文档命令 参数从左到右分别为 上下文,数据库名,多条文档内容
go
res, err := api.MongoInsertManyDocuments(ctx, "test", "dt_world_chat",
[]bson.M{
bson.M{
"msg": "666",
"send_time": "17:43",
"send_user_name": "散人55",
"send_user_id": "orBdl5HIW_V1kMQbA5mUT4h5YzLI",
"avatar": "",
"time": time.Now(),
},
bson.M{
"msg": "222",
"send_time": "17:43",
"send_user_name": "散人55",
"send_user_id": "orBdl5HIW_V1kMQbA5mUT4h5YzLI",
"avatar": "",
"time": time.Now(),
},
})
执行mongodb插入文档命令 参数从左到右分别为 上下文,数据库名,文档内容
go
res, err := api.MongoInsertDocument(ctx, "test", "dt_world_chat",
bson.M{
"msg": "666",
"send_time": "17:43",
"send_user_name": "散人55",
"send_user_id": "orBdl5HIW_V1kMQbA5mUT4h5YzLI",
"avatar": "",
"time": "2025-07-21T15:31:39.226+08:00",
})
执行mongodb修改文档命令 参数从左到右分别为 上下文,数据库名,集合名,文档id(非id过滤修改时传nil),修改命令
go
//返回 匹配条数,已修改条数,upserted条数,upsertedID,err
update := bson.M{
"$set": bson.M{"time": time.Now().AddDate(0, 0, -1)},
}
matchedCount , modifiedCount , upsertedCount , upsertedID, err := api.MongoUpdateDocument(ctx, "test", "dt_world_chat",
nil, bson.M{
"time": bson.M{
"$gte": time.Now().AddDate(0, 0, -1),
},
}, update)
执行mongodb聚合查询命令,入参是 ctx,数据库名,集合名,bson命令
go
pipeline2 := bson.Pipeline{
{
{"$group", bson.M{
"_id": "$msg",
"count": bson.M{"$sum": 1},
}},
},
{
{"$count", "total_distinct_msgs"},
},
}
res, err := api.MongoAggregateDocuments(ctx, "test", "dt_world_chat",pipeline2)
执行mongodb查询命令,入参是 ctx,数据库名,集合名,projection,filter,_id,skip,page
go
filter := bson.M{"_id": "6597cf39014ee43759061433"}
res, err := api.MongoFindDocuments(ctx, "test", "dt_world_chat",
bson.M{"_id": 1}, filter, nil, 0, 0)
执行mongodb删除命令,入参是 ctx,数据库名,集合名,_id,filter的bson
go
res, err := api.MongoDeleteDocument(ctx, "test", "dt_world_chat", "6597b53f014ee43759061141",nil)
执行mongodb总数查询命令,入参是 ctx,数据库名,集合名,filter的bson
go
res,err := api.MongoCountDocuments(ctx,"test","dt_world_chat",nil)
执行mongodb总数查询命令,入参是 ctx,数据库名,集合名,filter的bson
go
res,err := api.MongoCountDocuments(ctx,"test","dt_world_chat",nil)
获取mongodb数据库列表,入参是 ctx
go
res,err := api.ShowMongoDbs(ctx)
获取mongodb集合列表,入参是 ctx,数据库名称
go
res,err := api.MongoGetCollections(ctx,dbName)
### Q: 为什么我的代码没有问题,但是页面一片空白?
A: 可能是因为plugin.json中的frontend_debug选项没有设置为true。
## 注意事项
1. **必须调用 flag.Parse()** - 在主程序开始时必须先调用
2. **资源嵌入** - 使用 `//go:embed` 嵌入必要的资源文件
## 下一步
- [前端开发](/plugin-dev/frontend):深入了解前端开发细节
- [插件打包与发布](/plugin-dev/publish):了解如何打包和发布插件
- [API 参考](/plugin-dev/api):查看完整的API文档