diff --git a/conf/rulex.ini b/conf/rulex.ini index a68699514..2c15af737 100644 --- a/conf/rulex.ini +++ b/conf/rulex.ini @@ -71,7 +71,7 @@ update_server = http://localhost:8088/rulex # # Enable # -enable = true +enable = false # # Server host, default allow all # @@ -85,6 +85,24 @@ port = 2580 # dbpath = ./rulex.db # +[plugin.api_server] +# +# Enable +# +enable = true +# +# Server host, default allow all +# +host = 0.0.0.0 +# +# Server port +# +port = 8000 +# +# Server port +# +dbpath = ./rulex.db +# # Lightweight Mqtt protocol server # [plugin.mqtt_server] diff --git a/engine/runner.go b/engine/runner.go index 64aeb572d..823cd98a1 100644 --- a/engine/runner.go +++ b/engine/runner.go @@ -1,6 +1,7 @@ package engine import ( + "github.com/hootrhino/rulex/plugin/api_server" "os" "os/signal" "syscall" @@ -45,6 +46,14 @@ func RunRulex(iniPath string) { glogger.GLogger.Error(err) return } + + // http server v2 + apiServer := api_server.NewHttpPlugin() + if err := engine.LoadPlugin("plugin.api_server", apiServer); err != nil { + glogger.GLogger.Error(err) + return + } + mqttServer := mqttserver.NewMqttServer() if err := engine.LoadPlugin("plugin.mqtt_server", mqttServer); err != nil { glogger.GLogger.Error(err) diff --git a/global/global.go b/global/global.go new file mode 100644 index 000000000..31879f7b8 --- /dev/null +++ b/global/global.go @@ -0,0 +1,5 @@ +package global + +import "gorm.io/gorm" + +var RULEX_DB *gorm.DB diff --git a/go.mod b/go.mod index 1da40b2df..358d07c3c 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/wwhai/ntp v0.3.0 github.com/wwhai/tarmserial v1.0.0 github.com/wwhai/tinycache v0.0.0-20191004192108-46f407853014 + github.com/xuri/excelize/v2 v2.7.1 go.bug.st/serial v1.5.0 go.mongodb.org/mongo-driver v1.11.6 go.uber.org/zap v1.15.0 @@ -66,7 +67,6 @@ require ( github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.3 // indirect github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect - github.com/xuri/excelize/v2 v2.7.1 // indirect github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect ) diff --git a/go.sum b/go.sum index 64e0a9852..bda9d6661 100644 --- a/go.sum +++ b/go.sum @@ -1014,6 +1014,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= diff --git a/plugin/api_server/http_plugin.go b/plugin/api_server/http_plugin.go new file mode 100644 index 000000000..144f9ed54 --- /dev/null +++ b/plugin/api_server/http_plugin.go @@ -0,0 +1,77 @@ +package api_server + +import ( + "fmt" + "github.com/hootrhino/rulex/global" + "github.com/hootrhino/rulex/plugin/api_server/initialize" + "github.com/hootrhino/rulex/typex" + "github.com/hootrhino/rulex/utils" + "gopkg.in/ini.v1" + "os" + "os/signal" + "strconv" + "syscall" +) + +var apiPort int = 8000 + +type HttpPlugin struct { + uuid string +} + +func NewHttpPlugin() *HttpPlugin { + return &HttpPlugin{ + uuid: "HTTP PLUGIN", + } +} + +func (*HttpPlugin) Init(config *ini.Section) error { + var apiConfig initialize.HttpConfig + if err := utils.InIMapToStruct(config, &apiConfig); err != nil { + return err + } + global.RULEX_DB = initialize.Gorm(apiConfig.DbPath) + apiPort = apiConfig.Port + if global.RULEX_DB != nil { + initialize.RegisterTables(global.RULEX_DB) // 初始化表 + } + + return nil +} + +func (*HttpPlugin) Start(typex.RuleX) error { + // 优雅退出程序 + router := initialize.Routers() + PORT := strconv.Itoa(apiPort) + go func() { + // 启动服务 + if err := router.Run(fmt.Sprintf(":%s", PORT)); err != nil { + fmt.Println(fmt.Sprintf("服务启动失败:%s", err.Error())) + } + }() + exit := make(chan os.Signal) + signal.Notify(exit, syscall.SIGINT, syscall.SIGTERM) + <-exit + return nil +} + +func (*HttpPlugin) Stop() error { + return nil +} + +func (hp *HttpPlugin) PluginMetaInfo() typex.XPluginMetaInfo { + return typex.XPluginMetaInfo{ + UUID: hp.uuid, + Name: "RULEX HTTP RESTFul Api Server", + Version: "v2.0.0", + Homepage: "https://hootrhino.github.io", + HelpLink: "https://hootrhino.github.io", + Author: "wwhai", + Email: "cnwwhai@gmail.com", + License: "MIT", + } +} + +func (*HttpPlugin) Service(arg typex.ServiceArg) typex.ServiceResult { + return typex.ServiceResult{Out: "HTTP API SERVER"} +} diff --git a/plugin/api_server/initialize/config.go b/plugin/api_server/initialize/config.go new file mode 100644 index 000000000..437032a57 --- /dev/null +++ b/plugin/api_server/initialize/config.go @@ -0,0 +1,8 @@ +package initialize + +type HttpConfig struct { + Enable bool `ini:"enable"` + Host string `ini:"host"` + DbPath string `ini:"dbpath"` + Port int `ini:"port"` +} diff --git a/plugin/api_server/initialize/gorm.go b/plugin/api_server/initialize/gorm.go new file mode 100644 index 000000000..44771edbc --- /dev/null +++ b/plugin/api_server/initialize/gorm.go @@ -0,0 +1,49 @@ +package initialize + +import ( + "github.com/hootrhino/rulex/core" + "github.com/hootrhino/rulex/glogger" + "github.com/hootrhino/rulex/plugin/api_server/model" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "os" +) + +// Gorm 暂时写死sqlite,可根据实际扩展其他数据库 +func Gorm(dbPath string) *gorm.DB { + return GormSqlite(dbPath) +} + +func GormSqlite(dbPath string) *gorm.DB { + if core.GlobalConfig.AppDebugMode { + if db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + SkipDefaultTransaction: false, + }); err != nil { + return nil + } else { + return db + } + } else { + if db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Error), + SkipDefaultTransaction: false, + }); err != nil { + return nil + } else { + return db + } + } +} + +// RegisterTables 注册数据库表专用 +func RegisterTables(db *gorm.DB) { + err := db.AutoMigrate( + model.MRule{}, + ) + if err != nil { + glogger.GLogger.Fatal(err) + os.Exit(0) + } +} diff --git a/plugin/api_server/initialize/router.go b/plugin/api_server/initialize/router.go new file mode 100644 index 000000000..24c4c3395 --- /dev/null +++ b/plugin/api_server/initialize/router.go @@ -0,0 +1,20 @@ +package initialize + +import ( + "github.com/gin-gonic/gin" + "github.com/hootrhino/rulex/plugin/api_server/middleware" + "github.com/hootrhino/rulex/plugin/api_server/router" +) + +func Routers() *gin.Engine { + Router := gin.Default() + // 注册全局中间件,根据实际业务需求注册 + Router.Use( + middleware.CorsMiddleWare(), // 跨域中间件 + ) + // 配置全局路径 + ApiGroup := Router.Group("/api/v2/") + // 注册路由 + router.InitRuleRouter(ApiGroup) + return Router +} diff --git a/plugin/api_server/middleware/CorsMiddleWare.go b/plugin/api_server/middleware/CorsMiddleWare.go new file mode 100644 index 000000000..84a76c541 --- /dev/null +++ b/plugin/api_server/middleware/CorsMiddleWare.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +// CorsMiddleWare 跨域中间件 +// CORS是跨源资源分享(Cross-Origin Resource Sharing)中间件 +func CorsMiddleWare() gin.HandlerFunc { + return func(ctx *gin.Context) { + //指定允许其他域名访问 + //ctx.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:8080") + ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*") //跨域:CORS(跨来源资源共享)策略 + //预检结果缓存时间 + ctx.Writer.Header().Set("Access-Control-Max-Age", "86400") + //允许的请求类型(GET,POST等) + ctx.Writer.Header().Set("Access-Control-Allow-Methods", "*") + //允许的请求头字段 + ctx.Writer.Header().Set("Access-Control-Allow-Headers", "*") + //是否允许后续请求携带认证信息(cookies),该值只能是true,否则不返回 + ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + if ctx.Request.Method == http.MethodOptions { + ctx.AbortWithStatus(200) + } else { + ctx.Next() + } + } +} diff --git a/plugin/api_server/model/base.go b/plugin/api_server/model/base.go new file mode 100644 index 000000000..7dc102b51 --- /dev/null +++ b/plugin/api_server/model/base.go @@ -0,0 +1,31 @@ +package model + +import ( + "database/sql/driver" + "gopkg.in/square/go-jose.v2/json" + "time" +) + +type BaseModel struct { + ID uint `gorm:"primarykey"` + CreatedAt time.Time +} + +type stringList []string + +func (f stringList) Value() (driver.Value, error) { + b, err := json.Marshal(f) + return string(b), err +} + +func (f *stringList) Scan(data interface{}) error { + return json.Unmarshal([]byte(data.(string)), f) +} + +func (f stringList) String() string { + b, _ := json.Marshal(f) + return string(b) +} +func (f stringList) Len() int { + return len(f) +} diff --git a/plugin/api_server/model/m_rules.go b/plugin/api_server/model/m_rules.go new file mode 100644 index 000000000..45fca3fd4 --- /dev/null +++ b/plugin/api_server/model/m_rules.go @@ -0,0 +1,19 @@ +package model + +type MRule struct { + BaseModel + UUID string `gorm:"not null"` + Name string `gorm:"not null"` + Type string // 脚本类型,目前支持"lua"和"expr"两种 + FromSource stringList `gorm:"not null type:string[]"` + FromDevice stringList `gorm:"not null type:string[]"` + Expression string `gorm:"not null"` // Expr脚本 + Actions string `gorm:"not null"` + Success string `gorm:"not null"` + Failed string `gorm:"not null"` + Description string +} + +func (*MRule) TableName() string { + return "m_rules" +} diff --git a/plugin/api_server/response/common.go b/plugin/api_server/response/common.go new file mode 100644 index 000000000..74610965b --- /dev/null +++ b/plugin/api_server/response/common.go @@ -0,0 +1,8 @@ +package response + +type PageResult struct { + List interface{} `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` +} diff --git a/plugin/api_server/response/response.go b/plugin/api_server/response/response.go new file mode 100644 index 000000000..fa953fa89 --- /dev/null +++ b/plugin/api_server/response/response.go @@ -0,0 +1,55 @@ +package response + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type Response struct { + Code int `json:"code"` + Data interface{} `json:"data"` + Msg string `json:"msg"` +} + +const ( + ERROR = 500 + SUCCESS = 200 +) + +func Result(code int, data interface{}, msg string, c *gin.Context) { + // 开始时间 + c.JSON(http.StatusOK, Response{ + code, + data, + msg, + }) +} + +func Ok(c *gin.Context) { + Result(SUCCESS, map[string]interface{}{}, "Success", c) +} + +func OkWithMessage(message string, c *gin.Context) { + Result(SUCCESS, map[string]interface{}{}, message, c) +} + +func OkWithData(data interface{}, c *gin.Context) { + Result(SUCCESS, data, "查询成功", c) +} + +func OkWithDetailed(data interface{}, message string, c *gin.Context) { + Result(SUCCESS, data, message, c) +} + +func Fail(c *gin.Context) { + Result(ERROR, map[string]interface{}{}, "error", c) +} + +func FailWithMessage(message string, c *gin.Context) { + Result(ERROR, map[string]interface{}{}, message, c) +} + +func FailWithDetailed(data interface{}, message string, c *gin.Context) { + Result(ERROR, data, message, c) +} diff --git a/plugin/api_server/router/rule.go b/plugin/api_server/router/rule.go new file mode 100644 index 000000000..7ccfc6008 --- /dev/null +++ b/plugin/api_server/router/rule.go @@ -0,0 +1,13 @@ +package router + +import ( + "github.com/gin-gonic/gin" + "github.com/hootrhino/rulex/plugin/api_server/service" +) + +func InitRuleRouter(Router *gin.RouterGroup) { + registerRouter := Router.Group("rule") + newAccount := service.NewDemoService() + registerRouter.GET("/list", newAccount.GetRuleList) // 获取数据 + +} diff --git a/plugin/api_server/service/rule.go b/plugin/api_server/service/rule.go new file mode 100644 index 000000000..2fb57490d --- /dev/null +++ b/plugin/api_server/service/rule.go @@ -0,0 +1,25 @@ +package service + +import ( + "github.com/gin-gonic/gin" + "github.com/hootrhino/rulex/global" + "github.com/hootrhino/rulex/plugin/api_server/model" + "github.com/hootrhino/rulex/plugin/api_server/response" +) + +type RuleService interface { + GetRuleList(ctx *gin.Context) +} + +type Rule struct{} + +func (d Rule) GetRuleList(ctx *gin.Context) { + //TODO 业务逻辑 + var rule model.MRule + global.RULEX_DB.First(&rule, 1) + response.OkWithData(rule, ctx) +} + +func NewDemoService() RuleService { + return Rule{} +}