├── default.log ├── shutdown.bat ├── resource ├── excel │ └── 字典_20211201154616.xlsx ├── rbac_model.conf ├── template │ ├── js │ │ └── api.template │ └── go │ │ └── entity.template ├── default.pem └── default.key ├── pkg ├── config │ ├── queue.go │ ├── casbin.go │ ├── redis.go │ ├── app.go │ ├── oss.go │ ├── taos.go │ ├── gen.go │ ├── mqtt.go │ ├── jwt.go │ ├── log.go │ ├── server.go │ ├── config.go │ └── db.go ├── tool │ ├── base_test.go │ ├── conv.go │ ├── device.go │ ├── base.go │ ├── excel.go │ └── local.go ├── transport │ ├── transport.go │ ├── grpc_server.go │ └── http_server.go ├── rule_engine │ ├── nodes │ │ ├── template_engine.go │ │ ├── input_node.go │ │ ├── init.go │ │ ├── filter_message_type_node.go │ │ ├── filter_script_node.go │ │ ├── filter_message_type_switch_node.go │ │ ├── transform_script_node.go │ │ ├── filter_device_type_switch_node.go │ │ ├── action_rpc_request_node.go │ │ ├── filter_switch_node.go │ │ ├── action_rpc_respond_node.go │ │ ├── metadata.go │ │ ├── external_rule_chain_node.go │ │ ├── action_save_attributes_node.go │ │ ├── external_send_sms_node.go │ │ ├── action_save_timeseries_node.go │ │ ├── action_log_node.go │ │ ├── external_nats_node.go │ │ ├── factory.go │ │ ├── transform_delete_key_node.go │ │ ├── action_clear_alarm_node.go │ │ ├── action_generator_node.go │ │ ├── transform_rename_key_node.go │ │ ├── external_wechat_node.go │ │ ├── action_delay_node.go │ │ └── action_create_alarm_node.go │ ├── instance.go │ ├── instance_test.go │ ├── manifest │ │ └── manifest.go │ └── message │ │ └── message1.go ├── middleware │ ├── escape_html.go │ ├── cors.go │ ├── rate.go │ ├── oper.go │ ├── swagger.go │ ├── permission.go │ └── log.go ├── global │ ├── global.go │ ├── global_model.go │ └── const_device.go ├── initialize │ ├── event.go │ ├── table.go │ └── router.go ├── websocket │ └── socket_server_pool.go ├── tdengine │ ├── TDengineModel.go │ ├── tdengine_event.go │ └── tdengine_log.go └── mqtt │ └── mqtt.go ├── apps ├── job │ ├── api │ │ ├── from │ │ │ └── job.go │ │ └── log_job.go │ ├── jobs │ │ ├── type.go │ │ └── examples.go │ ├── entity │ │ ├── log_job.go │ │ └── job.go │ ├── services │ │ └── log_job.go │ └── router │ │ └── job_log.go ├── system │ ├── entity │ │ ├── role_dept.go │ │ ├── role_menu.go │ │ ├── api.go │ │ ├── tenant.go │ │ ├── notice.go │ │ ├── post.go │ │ ├── config.go │ │ ├── dept.go │ │ ├── role.go │ │ ├── dict.go │ │ ├── menu.go │ │ └── user.go │ ├── router │ │ ├── system.go │ │ ├── upload.go │ │ └── notice.go │ ├── api │ │ ├── form │ │ │ ├── role.go │ │ │ └── user.go │ │ ├── vo │ │ │ ├── metaVo.go │ │ │ ├── routerVo.go │ │ │ └── systemVo.go │ │ ├── notice.go │ │ ├── config.go │ │ ├── api.go │ │ ├── tenant.go │ │ ├── upload.go │ │ ├── post.go │ │ └── system.go │ └── services │ │ ├── role_dept.go │ │ └── notice.go ├── develop │ ├── api │ │ ├── vo │ │ │ └── tableVo.go │ │ └── gen.go │ ├── router │ │ └── gen.go │ └── entity │ │ └── dev_gen_table.go ├── rule │ ├── entity │ │ ├── rulechain_data.go │ │ └── rulechain.go │ ├── api │ │ └── rulechain_log.go │ ├── router │ │ └── rulechain_log.go │ └── services │ │ └── rulechain_log.go ├── device │ ├── entity │ │ ├── device_vo.go │ │ └── device_exp.go │ ├── api │ │ ├── device_alarm.go │ │ ├── product_ota.go │ │ ├── device_cmd.go │ │ └── device_group.go │ ├── router │ │ ├── device_cmd.go │ │ └── device_alarm.go │ └── tsl │ │ └── tsl.go └── log │ ├── entity │ ├── log_oper.go │ └── log_login.go │ ├── api │ ├── log_oper.go │ └── log_login.go │ ├── services │ ├── log_oper.go │ └── log_login.go │ └── router │ └── oper_log.go ├── uploads └── file │ ├── 626213519d0907c2f2ab12919fbd7779_20230414142538.png │ ├── 9b37cd4ca37090649adcee8bf17cfdcc_20230414141350.png │ └── dc49aad85da7ae4c37374bf79e29134c_20230414144346.png ├── deploy ├── manifest │ ├── namespace.yaml │ ├── kustomization.yaml │ ├── service.yaml │ ├── rbac.yaml │ └── deployment.yaml ├── mysql │ ├── namespace.yaml │ ├── kustomization.yaml │ ├── mysql-pv.yaml │ └── deployment-service.yaml ├── README.md ├── deploy.yaml └── mysql.yaml ├── fatal.log ├── .idea ├── vcs.xml ├── .gitignore ├── modules.xml └── PandaX.iml ├── iothub ├── iothub_session.go ├── reverse_control.go ├── grpc_server.go ├── topic.go └── README.md ├── .gitignore ├── Dockerfile └── config.yml /default.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shutdown.bat: -------------------------------------------------------------------------------- 1 | taskkill /pid 25952 -t -f -------------------------------------------------------------------------------- /resource/excel/字典_20211201154616.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaGoAdmin/PandaX/HEAD/resource/excel/字典_20211201154616.xlsx -------------------------------------------------------------------------------- /pkg/config/queue.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Queue struct { 4 | Enable bool `yaml:" enable"` 5 | Num int64 `yaml:" num"` //并发数 6 | } 7 | -------------------------------------------------------------------------------- /apps/job/api/from/job.go: -------------------------------------------------------------------------------- 1 | package from 2 | 3 | type JobStatus struct { 4 | JobId string `json:"jobId"` 5 | Status string `json:"status"` 6 | } 7 | -------------------------------------------------------------------------------- /pkg/config/casbin.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Casbin struct { 4 | ModelPath string `mapstructure:"model-path" json:"model-path" yaml:"model-path"` 5 | } 6 | -------------------------------------------------------------------------------- /pkg/tool/base_test.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import "testing" 4 | 5 | func TestToCamelCase(t *testing.T) { 6 | camelCase := ToCamelCase("hello_world") 7 | t.Log(camelCase) 8 | } 9 | -------------------------------------------------------------------------------- /uploads/file/626213519d0907c2f2ab12919fbd7779_20230414142538.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaGoAdmin/PandaX/HEAD/uploads/file/626213519d0907c2f2ab12919fbd7779_20230414142538.png -------------------------------------------------------------------------------- /uploads/file/9b37cd4ca37090649adcee8bf17cfdcc_20230414141350.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaGoAdmin/PandaX/HEAD/uploads/file/9b37cd4ca37090649adcee8bf17cfdcc_20230414141350.png -------------------------------------------------------------------------------- /uploads/file/dc49aad85da7ae4c37374bf79e29134c_20230414144346.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PandaGoAdmin/PandaX/HEAD/uploads/file/dc49aad85da7ae4c37374bf79e29134c_20230414144346.png -------------------------------------------------------------------------------- /deploy/manifest/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: pandax 6 | app.kubernetes.io/version: master 7 | name: pandax -------------------------------------------------------------------------------- /deploy/mysql/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: mysql 6 | app.kubernetes.io/version: master 7 | name: pandax-db -------------------------------------------------------------------------------- /fatal.log: -------------------------------------------------------------------------------- 1 | 2 | 2023-08-22 15:15:49-------------------------------- 3 | 4 | 2023-08-22 15:51:54-------------------------------- 5 | 6 | 2023-08-23 16:11:33-------------------------------- 7 | -------------------------------------------------------------------------------- /deploy/mysql/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - namespace.yaml 3 | - mysql-pv.yaml 4 | - deployment-service.yaml 5 | 6 | commonLabels: 7 | app.kubernetes.io/version: 1.0.0 8 | namespace: pandax-db 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /pkg/config/redis.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Redis struct { 4 | Host string `yaml:"host"` 5 | Port int `yaml:"port"` 6 | Password string `yaml:"password"` 7 | Db int `yaml:"db"` 8 | } 9 | -------------------------------------------------------------------------------- /deploy/manifest/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - namespace.yaml 3 | - rbac.yaml 4 | - service.yaml 5 | - deployment.yaml 6 | 7 | commonLabels: 8 | app.kubernetes.io/version: 1.0.0 9 | namespace: pandax 10 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | ./idea -------------------------------------------------------------------------------- /iothub/iothub_session.go: -------------------------------------------------------------------------------- 1 | package iothub 2 | 3 | type DeviceEventInfo struct { 4 | DeviceId string `json:"deviceId"` 5 | Datas string `json:"datas"` 6 | Type string `json:"type"` 7 | RequestId string `json:"requestId"` 8 | } 9 | -------------------------------------------------------------------------------- /apps/system/entity/role_dept.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type SysRoleDept struct { 4 | RoleId int64 `gorm:"type:int"` 5 | DeptId int64 `gorm:"type:int"` 6 | Id int64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id" form:"id"` 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | *.idea 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out -------------------------------------------------------------------------------- /pkg/config/app.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "fmt" 4 | 5 | type App struct { 6 | Name string `yaml:"name"` 7 | Version string `yaml:"version"` 8 | } 9 | 10 | func (a *App) GetAppInfo() string { 11 | return fmt.Sprintf("[%s:%s]", a.Name, a.Version) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/config/oss.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Oss struct { 4 | Endpoint string `yaml:"endpoint"` 5 | AccessKey string `yaml:"accessKey"` 6 | SecretKey string `yaml:"secretKey"` 7 | BucketName string `yaml:"bucketName"` 8 | UseSSL bool `yaml:"useSSL"` 9 | } 10 | -------------------------------------------------------------------------------- /pkg/config/taos.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Taos struct { 4 | Host string `yaml:"host"` // 服务器地址:端口 5 | Username string `yaml:"username"` // 数据库用户名 6 | Password string `yaml:"password"` // 数据库密码 7 | Database string `yaml:"database"` 8 | Config string `yaml:"config"` 9 | } 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pkg/config/gen.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | /** 4 | * @Description 5 | * @Author Panda 6 | * @Date 2021/12/31 15:13 7 | **/ 8 | type Gen struct { 9 | Dbname string `mapstructure:"dbname" json:"dbname" yaml:"dbname"` 10 | Frontpath string `mapstructure:"frontpath" json:"frontpath" yaml:"frontpath"` 11 | } 12 | -------------------------------------------------------------------------------- /resource/rbac_model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [policy_effect] 8 | e = some(where (p.eft == allow)) 9 | 10 | [matchers] 11 | m = r.sub == p.sub && (keyMatch2(r.obj, p.obj) || keyMatch(r.obj, p.obj)) && ( r.act == p.act || p.act == '*') -------------------------------------------------------------------------------- /apps/develop/api/vo/tableVo.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import "pandax/apps/develop/entity" 4 | 5 | /** 6 | * @Description 7 | * @Author 熊猫 8 | * @Date 2022/8/4 15:52 9 | **/ 10 | 11 | type TableInfoVo struct { 12 | List []entity.DevGenTableColumn `json:"list"` 13 | Info entity.DevGenTable `json:"info"` 14 | } 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | LABEL MAINTAINER="PandaX" 3 | 4 | WORKDIR /go/src/panda 5 | COPY ./pandax ./ 6 | COPY ./config.yml ./ 7 | COPY ./resource ./resource 8 | COPY ./uploads ./uploads 9 | 10 | RUN chmod 755 ./pandax 11 | 12 | EXPOSE 7788 13 | EXPOSE 9001 14 | EXPOSE 8801 15 | EXPOSE 5060/udp 16 | 17 | ENTRYPOINT ./pandax -------------------------------------------------------------------------------- /deploy/manifest/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: pandax 5 | labels: 6 | app.kubernetes.io/name: pandax 7 | spec: 8 | type: LoadBalancer 9 | ports: 10 | - port: 80 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | app.kubernetes.io/name: pandax 16 | -------------------------------------------------------------------------------- /apps/job/jobs/type.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import "github.com/robfig/cron/v3" 4 | 5 | type Job interface { 6 | Run() 7 | addJob(*cron.Cron) (int, error) 8 | } 9 | 10 | type JobsExec interface { 11 | Exec(arg any, content any) error 12 | } 13 | 14 | func CallExec(e JobsExec, arg any, content any) error { 15 | return e.Exec(arg, content) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/config/mqtt.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Mqtt struct { 4 | Broker string `mapstructure:"broker" json:"broker" yaml:"broker"` 5 | Qos int `mapstructure:"qos" json:"qos" yaml:"qos"` 6 | Username string `mapstructure:"username" json:"username" yaml:"username"` 7 | Password string `mapstructure:"password" json:"password" yaml:"password"` 8 | } 9 | -------------------------------------------------------------------------------- /.idea/PandaX.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pkg/transport/transport.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Type string 8 | 9 | const ( 10 | TypeHTTP Type = "HTTP" 11 | TypeGRPC Type = "GRPC" 12 | ) 13 | 14 | // Server is transport server. 15 | type Server interface { 16 | Type() Type 17 | Start(context.Context) error 18 | Stop(context.Context) error 19 | } 20 | -------------------------------------------------------------------------------- /apps/system/entity/role_menu.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type SysRoleMenu struct { 4 | RoleId int64 `gorm:"type:int"` 5 | MenuId int64 `gorm:"type:int"` 6 | RoleName string `gorm:"type:varchar(128)"` 7 | Id int64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id" form:"id"` 8 | } 9 | 10 | type MenuPath struct { 11 | Path string `json:"path"` 12 | } 13 | -------------------------------------------------------------------------------- /pkg/config/jwt.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/biz" 5 | ) 6 | 7 | type Jwt struct { 8 | Key string `yaml:"key"` 9 | ExpireTime int64 `yaml:"expire-time"` // 过期时间,单位分钟 10 | } 11 | 12 | func (j *Jwt) Valid() { 13 | biz.IsTrue(j.Key != "", "config.yml之 [jwt.key] 不能为空") 14 | biz.IsTrue(j.ExpireTime != 0, "config.yml之 [jwt.expire-time] 不能为空") 15 | } 16 | -------------------------------------------------------------------------------- /apps/system/router/system.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/emicklei/go-restful/v3" 5 | "pandax/apps/system/api" 6 | ) 7 | 8 | func InitSystemRouter(container *restful.Container) { 9 | s := &api.System{} 10 | ws := new(restful.WebService) 11 | ws.Path("/system").Produces(restful.MIME_JSON) 12 | ws.Route(ws.GET("/").To(s.ConnectWs)) 13 | ws.Route(ws.GET("/server").To(s.ServerInfo)) 14 | container.Add(ws) 15 | } 16 | -------------------------------------------------------------------------------- /apps/job/jobs/examples.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | type CronDeviceHandle struct { 8 | } 9 | 10 | func (t CronDeviceHandle) Exec(arg any, content any) error { 11 | log.Println("执行设备任务", arg, content) 12 | 13 | return nil 14 | } 15 | 16 | type CronProductHandle struct { 17 | } 18 | 19 | func (t CronProductHandle) Exec(arg any, content any) error { 20 | log.Println("执行产品任务", arg, content) 21 | 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/template_engine.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "bytes" 5 | "text/template" 6 | ) 7 | 8 | func ParseTemplate(content string, data map[string]interface{}) (string, error) { 9 | tmpl, err := template.New("template").Parse(content) 10 | if err != nil { 11 | return "", err 12 | } 13 | buffer := &bytes.Buffer{} 14 | err = tmpl.Execute(buffer, data) 15 | if err != nil { 16 | return "", err 17 | } 18 | return buffer.String(), nil 19 | } 20 | -------------------------------------------------------------------------------- /pkg/middleware/escape_html.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/emicklei/go-restful/v3" 5 | "html" 6 | ) 7 | 8 | // 防止XSS攻击 9 | func EscapeHTML(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { 10 | // 获取请求参数中的HTML标签 11 | for _, p := range req.Request.URL.Query() { 12 | escaped := html.EscapeString(p[0]) 13 | // 将转义后的参数重新设置到请求参数中 14 | req.Request.URL.Query().Set(p[0], escaped) 15 | } 16 | chain.ProcessFilter(req, resp) 17 | } 18 | -------------------------------------------------------------------------------- /deploy/manifest/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: pandax 5 | labels: 6 | app.kubernetes.io/name: pandax 7 | --- 8 | kind: ClusterRoleBinding 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | metadata: 11 | name: pandax-rolebinding 12 | labels: 13 | app.kubernetes.io/name: pandax 14 | roleRef: 15 | apiGroup: rbac.authorization.k8s.io 16 | kind: ClusterRole 17 | name: cluster-admin 18 | subjects: 19 | - kind: ServiceAccount 20 | name: pandax 21 | -------------------------------------------------------------------------------- /apps/system/api/form/role.go: -------------------------------------------------------------------------------- 1 | package form 2 | 3 | // 分配角色资源表单信息 4 | type RoleResourceForm struct { 5 | Id int 6 | ResourceIds string 7 | } 8 | 9 | // 保存角色信息表单 10 | type RoleForm struct { 11 | Id int 12 | Status int `json:"status"` // 1:可用;-1:不可用 13 | Name string `binding:"required"` 14 | Code string `binding:"required"` 15 | Remark string `json:"remark"` 16 | } 17 | 18 | // 账号分配角色表单 19 | type AccountRoleForm struct { 20 | Id int `binding:"required"` 21 | RoleIds string 22 | } 23 | -------------------------------------------------------------------------------- /apps/system/entity/api.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/PandaXGO/PandaKit/model" 4 | 5 | type SysApi struct { 6 | model.BaseAutoModel 7 | Path string `json:"path" gorm:"comment:api路径" binding:"required"` // api路径 8 | Description string `json:"description" gorm:"comment:api中文描述"` // api中文描述 9 | ApiGroup string `json:"apiGroup" gorm:"comment:api组"` // api组 10 | Method string `json:"method" gorm:"comment:方法"` // 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE 11 | } 12 | -------------------------------------------------------------------------------- /apps/job/entity/log_job.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "pandax/pkg/global" 5 | ) 6 | 7 | type JobLog struct { 8 | global.BaseAuthModel 9 | Name string `json:"name" gorm:"type:varchar(128);comment:任务名称"` 10 | EntryId int `json:"entryId" gorm:"type:int;comment:任务id"` 11 | TargetInvoke string `json:"targetInvoke" gorm:"type:varchar(128);comment:调用方法"` 12 | LogInfo string `json:"logInfo" gorm:"type:varchar(255);comment:日志信息"` 13 | Status string `json:"status" gorm:"type:varchar(1);comment:状态"` 14 | } 15 | -------------------------------------------------------------------------------- /pkg/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/rediscli" 5 | "github.com/sirupsen/logrus" 6 | "gorm.io/gorm" 7 | "pandax/pkg/config" 8 | "pandax/pkg/events" 9 | "pandax/pkg/mqtt" 10 | "pandax/pkg/tdengine" 11 | ) 12 | 13 | var ( 14 | Log *logrus.Logger // 日志 15 | Db *gorm.DB // gorm 16 | RedisDb *rediscli.RedisDB 17 | TdDb *tdengine.TdEngine 18 | Conf *config.Config 19 | MqttClient *mqtt.IothubMqttClient 20 | ) 21 | var EventEmitter = events.EventEmitter{} 22 | -------------------------------------------------------------------------------- /deploy/mysql/mysql-pv.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolume 3 | metadata: 4 | name: mysql-pv-volume 5 | labels: 6 | type: local 7 | spec: 8 | storageClassName: manual 9 | capacity: 10 | storage: 20Gi 11 | accessModes: 12 | - ReadWriteOnce 13 | hostPath: 14 | path: "/mnt/data" 15 | --- 16 | apiVersion: v1 17 | kind: PersistentVolumeClaim 18 | metadata: 19 | name: mysql-pv-claim 20 | spec: 21 | storageClassName: manual 22 | accessModes: 23 | - ReadWriteOnce 24 | resources: 25 | requests: 26 | storage: 20Gi -------------------------------------------------------------------------------- /apps/system/entity/tenant.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "time" 6 | ) 7 | 8 | /** 9 | * @Description 10 | * @Author 熊猫 11 | * @Date 2022/7/14 16:14 12 | **/ 13 | 14 | type SysTenants struct { 15 | model.BaseAutoModel 16 | TenantName string `json:"tenantName" gorm:"type:varchar(255);comment:租户名"` 17 | Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"` 18 | ExpireTime time.Time `json:"expireTime" gorm:"comment:过期时间"` 19 | } 20 | 21 | func (SysTenants) TableName() string { 22 | return "sys_tenants" 23 | } 24 | -------------------------------------------------------------------------------- /apps/system/entity/notice.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/PandaXGO/PandaKit/model" 4 | 5 | type SysNotice struct { 6 | NoticeId int64 `json:"noticeId" gorm:"primary_key;AUTO_INCREMENT"` 7 | Title string `json:"title" gorm:"type:varchar(128);comment:标题"` 8 | Content string `json:"content" gorm:"type:text;comment:标题"` 9 | NoticeType string `json:"noticeType" gorm:"type:varchar(1);comment:通知类型"` 10 | DeptId int64 `json:"deptId" gorm:"type:int;comment:部门Id,部门及子部门"` 11 | UserName string `json:"userName" gorm:"type:varchar(64);comment:发布人"` 12 | 13 | DeptIds []int64 `json:"deptIds" gorm:"-"` 14 | model.BaseModel 15 | } 16 | -------------------------------------------------------------------------------- /deploy/manifest/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: pandax 5 | labels: 6 | app.kubernetes.io/name: pandax 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/name: pandax 12 | template: 13 | metadata: 14 | labels: 15 | app.kubernetes.io/name: pandax 16 | spec: 17 | serviceAccountName: pandax 18 | containers: 19 | - name: pandax 20 | image: xmadmin/pandax:v1.0 21 | imagePullPolicy: IfNotPresent 22 | ports: 23 | - name: http 24 | containerPort: 8080 25 | protocol: TCP 26 | -------------------------------------------------------------------------------- /apps/rule/entity/rulechain_data.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type RuleDataJson struct { 8 | LfData struct { 9 | GlobalColor string `json:"globalColor"` 10 | DataCode map[string]interface{} `json:"dataCode"` 11 | OpenRule bool `json:"openRule"` 12 | Setting map[string]interface{} `json:"setting"` 13 | } `json:"lfData"` 14 | } 15 | 16 | // 序列化 17 | func (m *RuleDataJson) MarshalBinary() (data []byte, err error) { 18 | return json.Marshal(m) 19 | } 20 | 21 | // 反序列化 22 | func (m *RuleDataJson) UnmarshalBinary(data []byte) error { 23 | return json.Unmarshal(data, m) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/config/log.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "path" 4 | 5 | type Log struct { 6 | Level string `yaml:"level"` 7 | File *LogFile `yaml:"file"` 8 | } 9 | 10 | type LogFile struct { 11 | Name string `yaml:"name"` 12 | Path string `yaml:"path"` 13 | } 14 | 15 | // 获取完整路径文件名 16 | func (l *LogFile) GetFilename() string { 17 | var filepath, filename string 18 | if l == nil { 19 | return "" 20 | } 21 | if fp := l.Path; fp == "" { 22 | filepath = "./" 23 | } else { 24 | filepath = fp 25 | } 26 | if fn := l.Name; fn == "" { 27 | filename = "default.log" 28 | } else { 29 | filename = fn 30 | } 31 | 32 | return path.Join(filepath, filename) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/emicklei/go-restful/v3" 5 | ) 6 | 7 | // 处理跨域请求,支持options访问 8 | func Cors(wsContainer *restful.Container) restful.CrossOriginResourceSharing { 9 | cors := restful.CrossOriginResourceSharing{ 10 | ExposeHeaders: []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"}, 11 | AllowedHeaders: []string{"Content-Type", "AccessToken", "X-CSRF-Token", "Authorization", "Token", "X-Token", "X-User-Id"}, 12 | AllowedMethods: []string{"POST", "GET", "OPTIONS", "DELETE", "PUT"}, 13 | CookiesAllowed: false, 14 | Container: wsContainer} 15 | 16 | return cors 17 | } 18 | -------------------------------------------------------------------------------- /apps/system/entity/post.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/PandaXGO/PandaKit/model" 4 | 5 | type SysPost struct { 6 | PostId int64 `gorm:"primary_key;AUTO_INCREMENT" json:"postId"` 7 | PostName string `gorm:"type:varchar(128);comment:岗位名称" json:"postName"` 8 | PostCode string `gorm:"type:varchar(128);comment:岗位代码" json:"postCode"` 9 | Sort int64 `gorm:"type:int;comment:岗位排序" json:"sort"` 10 | Status string `gorm:"type:varchar(1);comment:状态" json:"status"` 11 | Remark string `gorm:"type:varchar(255);comment:描述" json:"remark"` 12 | CreateBy string `gorm:"type:varchar(128);" json:"createBy"` 13 | UpdateBy string `gorm:"type:varchar(128);" json:"updateBy"` 14 | model.BaseModel 15 | } 16 | -------------------------------------------------------------------------------- /pkg/middleware/rate.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/didip/tollbooth" 5 | "github.com/emicklei/go-restful/v3" 6 | "pandax/pkg/global" 7 | ) 8 | 9 | /** 10 | * @Description 添加qq群467890197 交流学习 11 | * @Author 熊猫 12 | * @Date 2022/1/19 8:28 13 | **/ 14 | 15 | //Rate 限流中间件 16 | func Rate(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { 17 | lmt := tollbooth.NewLimiter(global.Conf.Server.Rate.RateNum, nil) 18 | lmt.SetMessage("已经超出接口请求限制,稍后再试.") 19 | httpError := tollbooth.LimitByRequest(lmt, resp, req.Request) 20 | if httpError != nil { 21 | resp.WriteErrorString(httpError.StatusCode, httpError.Message) 22 | return 23 | } 24 | chain.ProcessFilter(req, resp) 25 | } 26 | -------------------------------------------------------------------------------- /apps/system/entity/config.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/PandaXGO/PandaKit/model" 4 | 5 | type SysConfig struct { 6 | ConfigId int64 `json:"configId" gorm:"primaryKey;AUTO_INCREMENT;comment:主键编码"` 7 | ConfigName string `json:"configName" gorm:"type:varchar(128);comment:ConfigName"` 8 | ConfigKey string `json:"configKey" gorm:"type:varchar(128);comment:ConfigKey"` 9 | ConfigValue string `json:"configValue" gorm:"type:varchar(255);comment:ConfigValue"` 10 | ConfigType string `json:"configType" gorm:"type:varchar(64);comment:是否系统内置0,1"` 11 | IsFrontend string `json:"isFrontend" gorm:"type:varchar(1);comment:是否前台"` 12 | Remark string `json:"remark" gorm:"type:varchar(128);comment:Remark"` 13 | model.BaseModel 14 | } 15 | -------------------------------------------------------------------------------- /pkg/initialize/event.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "pandax/apps/device/entity" 5 | "pandax/apps/device/services" 6 | "pandax/pkg/events" 7 | "pandax/pkg/global" 8 | "time" 9 | ) 10 | 11 | // 初始化事件监听 12 | func InitEvents() { 13 | // 监听规则链改变 更新所有绑定改规则链的产品 14 | global.EventEmitter.On(events.ProductChainRuleEvent, func(ruleId, codeData string) { 15 | global.Log.Infof("规则链%s变更", ruleId) 16 | list := services.ProductModelDao.FindList(entity.Product{ 17 | RuleChainId: ruleId, 18 | }) 19 | if list != nil { 20 | for _, product := range *list { 21 | err := global.RedisDb.Set(product.Id, codeData, time.Hour*24*365) 22 | if err != nil { 23 | global.Log.Errorf("事件监听执行错误:%s", err.Error()) 24 | } 25 | } 26 | } 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/global/global_model.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import "time" 4 | 5 | type BaseModel struct { 6 | Id string `json:"id" gorm:"primary_key;"` 7 | CreatedAt time.Time `gorm:"column:create_time" json:"createTime" form:"create_time"` 8 | UpdatedAt time.Time `gorm:"column:update_time" json:"updateTime" form:"update_time"` 9 | } 10 | 11 | type BaseAuthModel struct { 12 | Id string `json:"id" gorm:"primary_key;"` 13 | Owner string `json:"owner" gorm:"type:varchar(64);comment:创建者,所有者"` 14 | OrgId string `json:"orgId" gorm:"type:varchar(64);comment:机构ID"` 15 | CreatedAt time.Time `gorm:"column:create_time" json:"createTime" form:"create_time"` 16 | UpdatedAt time.Time `gorm:"column:update_time" json:"updateTime" form:"update_time"` 17 | } 18 | -------------------------------------------------------------------------------- /apps/develop/api/gen.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/restfulx" 5 | "pandax/apps/develop/gen" 6 | "pandax/apps/develop/services" 7 | ) 8 | 9 | type GenApi struct { 10 | GenTableApp services.SysGenTableModel 11 | } 12 | 13 | // Preview 代码视图 14 | func (e *GenApi) Preview(rc *restfulx.ReqCtx) { 15 | tableId := restfulx.PathParamInt(rc, "tableId") 16 | rc.ResData = gen.Preview(int64(tableId)) 17 | } 18 | 19 | // GenCode 代码生成 20 | func (e *GenApi) GenCode(rc *restfulx.ReqCtx) { 21 | tableId := restfulx.PathParamInt(rc, "tableId") 22 | gen.GenCode(int64(tableId)) 23 | } 24 | 25 | // GenConfigure 配置生成 26 | func (e *GenApi) GenConfigure(rc *restfulx.ReqCtx) { 27 | tableId := restfulx.PathParamInt(rc, "tableId") 28 | menuId := restfulx.QueryInt(rc, "menuId", 0) 29 | gen.GenConfigure(tableId, menuId) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/global/const_device.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | // 告警等级 4 | const ( 5 | CRITICAL = "CRITICAL" // 危险 6 | MAJOR = "MAJOR" // 重要 7 | MINOR = "MINOR" // 次要 8 | WARNING = "WARNING" // 警告 9 | INDETERMINATE = "INDETERMINATE" // 不确定 10 | ) 11 | 12 | // 告警状态 13 | const ( 14 | ALARMING = "0" // 告警中 15 | CONFIRMED = "1" // 已确认 16 | CLEARED = "2" // 已清除 17 | CLOSED = "3" // 已关闭 18 | ) 19 | 20 | // 设备状态 21 | const ( 22 | INACTIVE = "inactive" //未激活 23 | ONLINE = "online" //在线 24 | OFFLINE = "offline" // 离线 25 | ) 26 | 27 | // 设备类型 28 | const ( 29 | DIRECT = "direct" //直连设备 30 | GATEWAY = "gateway" //网关设备 31 | GATEWAYS = "gatewayS" // 网关子设备 32 | MONITOR = "monitor" // 监控设备 33 | ) 34 | 35 | // 设备命令状态 36 | const ( 37 | CMDSUCCESS = "0" //执行成功 38 | CMDFAIL = "1" //执行失败 39 | CMDRUNNING = "2" // 执行中 40 | ) 41 | -------------------------------------------------------------------------------- /deploy/README.md: -------------------------------------------------------------------------------- 1 | ## K3S 2 | 1. 安装 3 | curl -sfL http://rancher-mirror.cnrancher.com/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn INSTALL_K3S_VERSION="v1.23.1+k3s1" sh - 4 | 2. 卸载 5 | sudo /usr/local/bin/k3s-uninstall.sh 6 | 7 | ## mysql 安装 8 | 1. kubectl kustomize ./deploy/mysql -o ./deploy/mysql.yaml 9 | 2. kubectl apply -f ./deploy/mysql.yaml 10 | 11 | ## k3s部署流程 12 | 1. 设置打包环境 13 | go env -w GOOS=linux 14 | go env -w GOARCH=amd64 15 | 2. 构建Linux执行命令 16 | go build -o pandax . 17 | 4. 构建docker镜像 (修改版本号 xmadmin/pandax:v1.0) 18 | docker build -t xmadmin/pandax:v1.4 --rm . 19 | 5. 上传daocker镜像 20 | docker push xmadmin/pandax:v1.4 21 | 6. 生成 deploy.yaml 22 | kubectl kustomize deploy/manifest -o deploy/deploy.yaml 23 | 7. k8s安装yaml 24 | kubectl apply -f deploy/deploy.yaml 25 | 26 | ## 查看部署状态 27 | 8. 查看 yaml 的安装状态 28 | kubectl get pods -n pandax 29 | kubectl get services -n pandax -------------------------------------------------------------------------------- /apps/job/entity/job.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "pandax/pkg/global" 5 | ) 6 | 7 | type SysJob struct { 8 | global.BaseAuthModel 9 | JobName string `json:"jobName" gorm:"type:varchar(255);"` // 名称 10 | TargetInvoke string `json:"targetInvoke" gorm:"type:varchar(64);comment:目标类型"` //调用目标 设备还是产品 11 | TargetArgs string `json:"targetArgs" gorm:"type:varchar(64);comment:目标Id"` //目标传参 设备或者产品id 12 | JobContent string `json:"jobContent" gorm:"type:json;comment:任务内容"` //目标传参 要执行的内容 13 | CronExpression string `json:"cronExpression" gorm:"type:varchar(255);"` // cron表达式 14 | MisfirePolicy string `json:"misfirePolicy" gorm:"type:varchar(1);"` // 执行策略 15 | Status string `json:"status" gorm:"type:varchar(1);"` // 状态 16 | EntryId int `json:"entryId" gorm:"type:int;"` // job启动时返回的id 17 | } 18 | -------------------------------------------------------------------------------- /apps/device/entity/device_vo.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type DeviceStatusVo struct { 4 | Name string `json:"name"` 5 | Key string `json:"key"` 6 | Type string `json:"type"` 7 | Define any `json:"define"` 8 | Value any `json:"value"` 9 | Time any `json:"time"` 10 | } 11 | 12 | type VisualClass struct { 13 | ClassId string `json:"classId"` 14 | Name string `json:"name"` 15 | Attrs []VisualTwinAttr `json:"attrs"` 16 | } 17 | 18 | type VisualTwinAttr struct { 19 | Key string `json:"key"` 20 | Name string `json:"name"` 21 | Type string `json:"type"` 22 | Rw string `json:"rw"` //属性的操作权限 23 | } 24 | 25 | type VisualTwin struct { 26 | TwinId string `json:"twinId"` 27 | Name string `json:"name"` 28 | } 29 | 30 | // 发送数据 31 | type VisualTwinSendAttrs struct { 32 | TwinId string `json:"twinId"` 33 | Attrs map[string]interface{} `json:"attrs"` 34 | } 35 | -------------------------------------------------------------------------------- /apps/system/api/vo/metaVo.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | /**s 4 | * 路由meta对象参数说明 5 | * meta: { 6 | * title: 菜单栏及 tagsView 栏、菜单搜索名称 7 | * isLink: 是否超链接菜单,开启外链条件,`1、isLink:true 2、链接地址不为空` 8 | * isHide: 是否隐藏此路由 9 | * isKeepAlive: 是否缓存组件状态 10 | * isAffix: 是否固定在 tagsView 栏上 11 | * isFrame: 是否内嵌窗口,,开启条件,`1、isFrame:true 2、链接地址不为空` 12 | * auth: 当前路由权限标识(多个请用逗号隔开),最后转成数组格式,用于与当前用户权限进行对比,控制路由显示、隐藏 13 | * icon: 菜单、tagsView 图标,阿里:加 `iconfont xxx`,fontawesome:加 `fa xxx` 14 | * } 15 | */ 16 | type MetaVo struct { 17 | Title string `json:"title"` 18 | IsLink string `json:"isLink"` 19 | IsHide bool `json:"isHide"` 20 | IsKeepAlive bool `json:"isKeepAlive"` 21 | IsAffix bool `json:"isAffix"` 22 | IsIframe bool `json:"isIframe"` 23 | Auth []string `json:"auth"` 24 | Icon string `json:"icon"` 25 | } 26 | -------------------------------------------------------------------------------- /iothub/reverse_control.go: -------------------------------------------------------------------------------- 1 | package iothub 2 | 3 | // 指令下发 4 | /*func Control(assets, thingModel, device_name, parameter string, operation bool) error { 5 | topic := fmt.Sprintf("control/%s/%s", assets, device_name) 6 | log.Println(topic) 7 | payload := fmt.Sprintf(`{"method":"control","data":{"parameter": "%s","operation":%t}}`, parameter, operation) 8 | //Publish(*global.GVA_MQTT, topic, 1, payload) 9 | return nil 10 | } 11 | 12 | func ControlState(assets, thingModel, device_name string) (map[string]interface{}, error) { 13 | topic := fmt.Sprintf("control/%s/%s", assets, device_name) 14 | payload := fmt.Sprintf(`{"method":"state","data":{}}`) 15 | if Publish(*global.GVA_MQTT, topic, 1, payload) != nil { 16 | return nil, errors.New("下发获取状态参数指令失败") 17 | } 18 | select { 19 | case state := <-controlState: 20 | return state, nil 21 | case <-time.After(10 * time.Second): 22 | return nil, errors.New("请求指令状态超时") 23 | } 24 | } 25 | */ 26 | -------------------------------------------------------------------------------- /apps/log/entity/log_oper.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | ) 6 | 7 | type LogOper struct { 8 | OperId int64 `json:"operId" gorm:"primary_key;AUTO_INCREMENT"` //主键 9 | Title string `json:"title" gorm:"type:varchar(128);comment:操作的模块"` 10 | BusinessType string `json:"businessType" gorm:"type:varchar(1);comment:0其它 1新增 2修改 3删除"` 11 | Method string `json:"method" gorm:"type:varchar(255);comment:请求方法"` 12 | OperName string `json:"operName" gorm:"type:varchar(255);comment:操作人员"` 13 | OperUrl string `json:"operUrl" gorm:"type:varchar(255);comment:操作url"` 14 | OperIp string `json:"operIp" gorm:"type:varchar(255);comment:操作IP"` 15 | OperLocation string `json:"operLocation" gorm:"type:varchar(255);comment:操作地点"` 16 | OperParam string `json:"operParam" gorm:"type:varchar(255);comment:请求参数"` // 17 | Status string `json:"status" gorm:"type:varchar(1);comment:0=正常,1=异常"` 18 | model.BaseModel 19 | } 20 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/input_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | ) 7 | 8 | const InputNodeName = "InputNode" 9 | 10 | type inputNode struct { 11 | bareNode 12 | } 13 | 14 | type inputNodeFactory struct{} 15 | 16 | func (f inputNodeFactory) Name() string { return "InputNode" } 17 | func (f inputNodeFactory) Category() string { return NODE_CATEGORY_OTHERS } 18 | func (f inputNodeFactory) Labels() []string { return []string{"True"} } 19 | func (f inputNodeFactory) Create(id string, meta Metadata) (Node, error) { 20 | node := &inputNode{ 21 | bareNode: newBareNode(InputNodeName, id, meta, f.Labels()), 22 | } 23 | return node, nil 24 | } 25 | 26 | func (n *inputNode) Handle(msg *message.Message) error { 27 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 28 | 29 | nodes := n.GetLinkedNodes() 30 | for _, node := range nodes { 31 | return node.Handle(msg) 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/websocket/socket_server_pool.go: -------------------------------------------------------------------------------- 1 | package websocket 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | "pandax/pkg/global" 6 | ) 7 | 8 | var Wsp = make(map[string]*Websocket) 9 | 10 | // GetWebSocketByScreenId 获取WebSocket 11 | func GetWebSocketByScreenId(screenId string) *Websocket { 12 | return Wsp[screenId] 13 | } 14 | 15 | // AddWebSocketByScreenId 添加WebSocket 16 | func AddWebSocketByScreenId(screenId string, webs *Websocket) { 17 | Wsp[screenId] = webs 18 | } 19 | 20 | // RemoveWebSocket 移除WebSocket 21 | func RemoveWebSocket(screenId string) bool { 22 | if ws, ok := Wsp[screenId]; ok { 23 | ws.Conn.Close() 24 | delete(Wsp, screenId) 25 | global.Log.Info("已经断开websocket:" + screenId) 26 | return true 27 | } 28 | return false 29 | } 30 | 31 | // SendMessage 向特定场景id发送消息,同一场景代码有可能在多台客户机上连接 ,这时就会在多台客户机接受到了数据 32 | func SendMessage(message, screenId string) { 33 | ws := GetWebSocketByScreenId(screenId) 34 | if ws != nil { 35 | ws.Conn.WriteMessage(websocket.TextMessage, []byte(message)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/transport/grpc_server.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "google.golang.org/grpc" 7 | "net" 8 | "pandax/pkg/global" 9 | ) 10 | 11 | type GrpcServer struct { 12 | Addr string 13 | srv *grpc.Server 14 | } 15 | 16 | func NewServer(addr string) *GrpcServer { 17 | return &GrpcServer{ 18 | Addr: addr, 19 | srv: grpc.NewServer(), 20 | } 21 | } 22 | 23 | func (s *GrpcServer) GetServe() *grpc.Server { 24 | return s.srv 25 | } 26 | 27 | func (s *GrpcServer) Type() Type { 28 | return TypeGRPC 29 | } 30 | 31 | func (s *GrpcServer) Start(ctx context.Context) error { 32 | l, err := net.Listen("tcp", s.Addr) 33 | if err != nil { 34 | return fmt.Errorf("error listen addr: %w", err) 35 | } 36 | global.Log.Debugf("GRPC Server listen: %s", s.Addr) 37 | go func() { 38 | if err := s.srv.Serve(l); err != nil { 39 | global.Log.Errorf("error http serve: %s", err) 40 | } 41 | }() 42 | return nil 43 | } 44 | 45 | func (s *GrpcServer) Stop(ctx context.Context) error { 46 | s.srv.Stop() 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /iothub/grpc_server.go: -------------------------------------------------------------------------------- 1 | package iothub 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "google.golang.org/grpc" 7 | "net" 8 | "pandax/pkg/global" 9 | ) 10 | 11 | const DefaultPort = ":9001" 12 | 13 | type Server struct { 14 | Addr string 15 | srv *grpc.Server 16 | } 17 | 18 | func NewServer(addr string) *Server { 19 | if addr == "" { 20 | addr = DefaultPort 21 | } 22 | return &Server{ 23 | Addr: addr, 24 | srv: grpc.NewServer(), 25 | } 26 | } 27 | 28 | func (s *Server) GetServe() *grpc.Server { 29 | return s.srv 30 | } 31 | 32 | func (s *Server) Type() string { 33 | return "GRPC" 34 | } 35 | 36 | func (s *Server) Start(ctx context.Context) error { 37 | l, err := net.Listen("tcp", s.Addr) 38 | if err != nil { 39 | return fmt.Errorf("error listen addr: %w", err) 40 | } 41 | go func() { 42 | if err := s.srv.Serve(l); err != nil { 43 | global.Log.Error(fmt.Sprintf("error http serve: %s", err)) 44 | } 45 | }() 46 | return nil 47 | } 48 | 49 | func (s *Server) Stop(ctx context.Context) error { 50 | s.srv.Stop() 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/tdengine/TDengineModel.go: -------------------------------------------------------------------------------- 1 | package tdengine 2 | 3 | import "time" 4 | 5 | type TDEngineTablesList struct { 6 | TableName string `json:"tableName" description:"表名"` 7 | DbName string `json:"dbName" description:"数据库名"` 8 | StableName string `json:"stableName" description:"超级表名"` 9 | CreateTime *time.Time `json:"createTime" description:"创建时间"` 10 | } 11 | 12 | type TDEngineTableInfo struct { 13 | Field string `json:"field" description:"字段名"` 14 | Type string `json:"type" description:"类型"` 15 | Length int `json:"length" description:"长度"` 16 | Note string `json:"note" description:"note"` 17 | } 18 | 19 | type TableDataInfo struct { 20 | Filed []string `json:"filed" description:"字段"` 21 | Info []map[string]interface{} `json:"info" description:"数据"` 22 | } 23 | 24 | // 日志 TDengine 25 | type TdLog struct { 26 | Ts string `json:"ts" dc:"时间"` 27 | Device string `json:"device" dc:"设备标识"` 28 | Type string `json:"type" dc:"日志类型"` 29 | Content string `json:"content" dc:"日志内容"` 30 | } 31 | -------------------------------------------------------------------------------- /deploy/mysql/deployment-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mysql 5 | spec: 6 | type: LoadBalancer 7 | ports: 8 | - port: 3306 9 | targetPort: mysql 10 | protocol: TCP 11 | selector: 12 | app: mysql 13 | --- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: mysql 18 | spec: 19 | selector: 20 | matchLabels: 21 | app: mysql 22 | strategy: 23 | type: Recreate 24 | template: 25 | metadata: 26 | labels: 27 | app: mysql 28 | spec: 29 | containers: 30 | - image: mysql:8.0.23 31 | name: mysql 32 | env: 33 | - name: MYSQL_ROOT_PASSWORD 34 | value: pandax 35 | ports: 36 | - containerPort: 3306 37 | name: mysql 38 | protocol: TCP 39 | volumeMounts: 40 | - name: mysql-persistent-storage 41 | mountPath: /var/lib/mysql 42 | volumes: 43 | - name: mysql-persistent-storage 44 | persistentVolumeClaim: 45 | claimName: mysql-pv-claim -------------------------------------------------------------------------------- /apps/system/api/vo/routerVo.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | /** 4 | * 路由对象参数说明 5 | * { 6 | * component: 组件地址 7 | * redirect: 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 8 | * path: 路由地址 9 | * name: 路由名字 10 | * // 路由meta对象参数说明 11 | * meta: { 12 | * title: 菜单栏及 tagsView 栏、菜单搜索名称(国际化) 13 | * isLink: 是否超链接菜单,开启外链条件,`1、isLink:true 2、链接地址不为空` 14 | * isHide: 是否隐藏此路由 15 | * isKeepAlive: 是否缓存组件状态 16 | * isAffix: 是否固定在 tagsView 栏上 17 | * isFrame: 是否内嵌窗口,,开启条件,`1、isIframe:true 2、链接地址不为空` 18 | * auth: 当前路由权限标识(多个请用逗号隔开),最后转成数组格式,用于与当前用户权限进行对比,控制路由显示、隐藏 19 | * icon: 菜单、tagsView 图标,阿里:加 `iconfont xxx`,fontawesome:加 `fa xxx` 20 | * } 21 | * } 22 | * 23 | */ 24 | type RouterVo struct { 25 | Name string `json:"name"` 26 | Path string `json:"path"` 27 | Redirect string `json:"redirect"` 28 | Component string `json:"component"` 29 | Meta MetaVo `json:"meta"` 30 | Children []RouterVo `json:"children"` 31 | } 32 | -------------------------------------------------------------------------------- /apps/system/services/role_dept.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PandaXGO/PandaKit/biz" 6 | "pandax/apps/system/entity" 7 | "pandax/pkg/global" 8 | ) 9 | 10 | type ( 11 | SysRoleDeptModel interface { 12 | Insert(roleId int64, deptIds []int64) bool 13 | Delete(rm entity.SysRoleDept) 14 | } 15 | 16 | sysRoleDeptImpl struct { 17 | table string 18 | } 19 | ) 20 | 21 | var SysRoleDeptModelDao SysRoleDeptModel = &sysRoleDeptImpl{ 22 | table: `sys_role_depts`, 23 | } 24 | 25 | func (m *sysRoleDeptImpl) Insert(roleId int64, deptIds []int64) bool { 26 | sql := "INSERT INTO sys_role_depts (role_id, dept_id) VALUES " 27 | 28 | for i := 0; i < len(deptIds); i++ { 29 | if len(deptIds)-1 == i { 30 | //最后一条数据 以分号结尾 31 | sql += fmt.Sprintf("(%d,%d);", roleId, deptIds[i]) 32 | } else { 33 | sql += fmt.Sprintf("(%d,%d),", roleId, deptIds[i]) 34 | } 35 | } 36 | global.Db.Exec(sql) 37 | return true 38 | } 39 | 40 | func (m *sysRoleDeptImpl) Delete(rm entity.SysRoleDept) { 41 | biz.ErrIsNil(global.Db.Table(m.table).Where("role_id = ?", rm.RoleId).Delete(&rm).Error, "删除角色失败") 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /resource/template/js/api.template: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | // 查询{{.FunctionName}}列表 4 | export function list{{.FunctionName}}(query:any) { 5 | return request({ 6 | url: '/{{.PackageName}}/{{.BusinessName}}/list', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | // 查询{{.FunctionName}}详细 13 | export function get{{.FunctionName}}({{.PkJsonField}}:number) { 14 | return request({ 15 | url: '/{{.PackageName}}/{{.BusinessName}}/' + {{.PkJsonField}}, 16 | method: 'get' 17 | }) 18 | } 19 | 20 | // 新增{{.FunctionName}} 21 | export function add{{.FunctionName}}(data:any) { 22 | return request({ 23 | url: '/{{.PackageName}}/{{.BusinessName}}', 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | // 修改{{.FunctionName}} 30 | export function update{{.FunctionName}}(data:any) { 31 | return request({ 32 | url: '/{{.PackageName}}/{{.BusinessName}}', 33 | method: 'put', 34 | data: data 35 | }) 36 | } 37 | 38 | // 删除{{.FunctionName}} 39 | export function del{{.FunctionName}}({{.PkJsonField}}: string) { 40 | return request({ 41 | url: '/{{.PackageName}}/{{.BusinessName}}/' + {{.PkJsonField}}, 42 | method: 'delete' 43 | }) 44 | } -------------------------------------------------------------------------------- /pkg/initialize/table.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/biz" 5 | devEntity "pandax/apps/develop/entity" 6 | jobEntity "pandax/apps/job/entity" 7 | logEntity "pandax/apps/log/entity" 8 | systemEntity "pandax/apps/system/entity" 9 | "pandax/pkg/global" 10 | ) 11 | 12 | // 初始化时如果没有表创建表 13 | func InitTable() { 14 | m := global.Conf.Server 15 | if m.IsInitTable { 16 | biz.ErrIsNil( 17 | global.Db.AutoMigrate( 18 | //casbin.CasbinRule{}, 19 | systemEntity.SysDept{}, 20 | systemEntity.SysApi{}, 21 | systemEntity.SysConfig{}, 22 | systemEntity.SysDictType{}, 23 | systemEntity.SysDictData{}, 24 | systemEntity.SysUser{}, 25 | systemEntity.SysTenants{}, 26 | systemEntity.SysRole{}, 27 | systemEntity.SysMenu{}, 28 | systemEntity.SysPost{}, 29 | systemEntity.SysRoleMenu{}, 30 | systemEntity.SysRoleDept{}, 31 | systemEntity.SysNotice{}, 32 | 33 | logEntity.LogLogin{}, 34 | logEntity.LogOper{}, 35 | jobEntity.SysJob{}, 36 | devEntity.DevGenTable{}, 37 | devEntity.DevGenTableColumn{}, 38 | ), 39 | "初始化表失败") 40 | } 41 | err := global.TdDb.CreateEventTable() 42 | if err != nil { 43 | global.Log.Panic(err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/job/api/log_job.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | "github.com/PandaXGO/PandaKit/utils" 7 | "pandax/apps/job/entity" 8 | "pandax/apps/job/services" 9 | ) 10 | 11 | type JobLogApi struct { 12 | JobLogApp services.JobLogModel 13 | } 14 | 15 | // GetJobLogList Job日志列表 16 | func (l *JobLogApi) GetJobLogList(rc *restfulx.ReqCtx) { 17 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 18 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 19 | name := restfulx.QueryParam(rc, "name") 20 | status := restfulx.QueryParam(rc, "status") 21 | 22 | list, total := l.JobLogApp.FindListPage(pageNum, pageSize, entity.JobLog{Name: name, Status: status}) 23 | rc.ResData = model.ResultPage{ 24 | Total: total, 25 | PageNum: int64(pageNum), 26 | PageSize: int64(pageNum), 27 | Data: list, 28 | } 29 | } 30 | 31 | // DeleteJobLog 批量删除登录日志 32 | func (l *JobLogApi) DeleteJobLog(rc *restfulx.ReqCtx) { 33 | logIds := restfulx.QueryParam(rc, "logId") 34 | group := utils.IdsStrToIdsIntGroup(logIds) 35 | l.JobLogApp.Delete(group) 36 | } 37 | 38 | // DeleteAll 清空登录日志 39 | func (l *JobLogApi) DeleteAll(rc *restfulx.ReqCtx) { 40 | l.JobLogApp.DeleteAll() 41 | } 42 | -------------------------------------------------------------------------------- /apps/system/api/form/user.go: -------------------------------------------------------------------------------- 1 | package form 2 | 3 | // User register structure 4 | type Register struct { 5 | Username string `json:"userName"` 6 | Password string `json:"passWord"` 7 | NickName string `json:"nickName" gorm:"default:'QMPlusUser'"` 8 | HeaderImg string `json:"headerImg" gorm:"default:'http://www.henrongyi.top/avatar/lufu.jpg'"` 9 | AuthorityId string `json:"authorityId" gorm:"default:888"` 10 | } 11 | 12 | // User login structure 13 | type Login struct { 14 | Username string `json:"username" ` // 用户名 15 | Password string `json:"password"` // 密码 16 | Captcha string `json:"captcha"` // 验证码 17 | CaptchaId string `json:"captchaId"` // 验证码ID 18 | } 19 | 20 | // Modify password structure 21 | type ChangePasswordStruct struct { 22 | Username string `json:"username"` // 用户名 23 | Password string `json:"password"` // 密码 24 | NewPassword string `json:"newPassword"` // 新密码 25 | } 26 | 27 | type UserSearch struct { 28 | Username string `json:"username"` // 用户UUID 29 | NickName string `json:"nickName"` // 角色ID 30 | Status int64 `json:"status"` // 角色ID 31 | Phone string `json:"phone"` // 角色ID 32 | PostId int64 `json:"postId"` // 角色ID 33 | DeptId int64 `json:"deptId"` // 角色ID 34 | } 35 | -------------------------------------------------------------------------------- /apps/system/entity/dept.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/PandaXGO/PandaKit/model" 4 | 5 | // 部门组织 6 | type SysDept struct { 7 | DeptId int64 `json:"deptId" gorm:"primary_key;AUTO_INCREMENT"` //部门编码 8 | ParentId int64 `json:"parentId" gorm:"type:int;comment:上级部门"` 9 | DeptPath string `json:"deptPath" gorm:"type:varchar(255);comment:部门路径"` 10 | DeptName string `json:"deptName" gorm:"type:varchar(128);comment:部门名称"` 11 | Sort int64 `json:"sort" gorm:"type:int;comment:排序"` 12 | Leader string `json:"leader" gorm:"type:varchar(64);comment:负责人"` // userId 13 | Phone string `json:"phone" gorm:"type:varchar(11);comment:手机"` 14 | Email string `json:"email" gorm:"type:varchar(64);comment:邮箱"` 15 | Status string `json:"status" gorm:"type:varchar(1);comment:状态"` 16 | CreateBy string `json:"createBy" gorm:"type:varchar(64);comment:创建人"` 17 | UpdateBy string `json:"updateBy" gorm:"type:varchar(64);comment:修改人"` 18 | Children []SysDept `json:"children" gorm:"-"` 19 | model.BaseModel 20 | } 21 | 22 | type DeptLable struct { 23 | DeptId int64 `gorm:"-" json:"deptId"` 24 | DeptName string `gorm:"-" json:"deptName"` 25 | Children []DeptLable `gorm:"-" json:"children"` 26 | } 27 | -------------------------------------------------------------------------------- /pkg/middleware/oper.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/restfulx" 5 | "github.com/PandaXGO/PandaKit/utils" 6 | "net/http" 7 | "pandax/apps/log/entity" 8 | "pandax/apps/log/services" 9 | ) 10 | 11 | func OperationHandler(rc *restfulx.ReqCtx) error { 12 | c := rc.Request 13 | // 请求操作不做记录 14 | if c.Request.Method == http.MethodGet || rc.LoginAccount == nil { 15 | return nil 16 | } 17 | if rc.RequiredPermission == nil || !rc.RequiredPermission.NeedToken { 18 | return nil 19 | } 20 | go func() { 21 | oper := entity.LogOper{ 22 | Title: rc.LogInfo.Description, 23 | BusinessType: "0", 24 | Method: c.Request.Method, 25 | OperName: rc.LoginAccount.UserName, 26 | OperUrl: c.Request.URL.Path, 27 | OperIp: c.Request.RemoteAddr, 28 | OperLocation: utils.GetRealAddressByIP(c.Request.RemoteAddr), 29 | OperParam: "", 30 | Status: "0", 31 | } 32 | if c.Request.Method == "POST" { 33 | oper.BusinessType = "1" 34 | } else if c.Request.Method == "PUT" { 35 | oper.BusinessType = "2" 36 | } else if c.Request.Method == "DELETE" { 37 | oper.BusinessType = "3" 38 | } 39 | services.LogOperModelDao.Insert(oper) 40 | }() 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /apps/log/entity/log_login.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "time" 6 | ) 7 | 8 | type LogLogin struct { 9 | InfoId int64 `json:"infoId" gorm:"primary_key;AUTO_INCREMENT"` //主键 10 | Username string `json:"username" gorm:"type:varchar(128);comment:用户名"` 11 | Status string `json:"status" gorm:"type:varchar(1);comment:状态"` 12 | Ipaddr string `json:"ipaddr" gorm:"type:varchar(255);comment:ip地址"` 13 | LoginLocation string `json:"loginLocation" gorm:"type:varchar(255);comment:归属地"` 14 | Browser string `json:"browser" gorm:"type:varchar(255);comment:浏览器"` 15 | Os string `json:"os" gorm:"type:varchar(255);comment:系统"` 16 | Platform string `json:"platform" gorm:"type:varchar(255);comment:固件"` 17 | LoginTime time.Time `json:"loginTime" gorm:"type:timestamp;comment:登录时间"` 18 | CreateBy string `json:"createBy" gorm:"type:varchar(128);comment:创建人"` 19 | UpdateBy string `json:"updateBy" gorm:"type:varchar(128);comment:更新者"` 20 | Params string `json:"params" gorm:"-"` 21 | Remark string `json:"remark" gorm:"type:varchar(255);"` //备注 22 | Msg string `json:"msg" gorm:"type:varchar(255);"` 23 | model.BaseModel 24 | } 25 | -------------------------------------------------------------------------------- /pkg/middleware/swagger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | restfulspec "github.com/emicklei/go-restful-openapi/v2" 5 | "github.com/emicklei/go-restful/v3" 6 | "github.com/go-openapi/spec" 7 | ) 8 | 9 | /** 10 | * @Description 11 | * @Author 熊猫 12 | * @Date 2022/8/3 9:16 13 | **/ 14 | 15 | func SwaggerConfig(wsContainer *restful.Container) { 16 | config := restfulspec.Config{ 17 | WebServices: wsContainer.RegisteredWebServices(), 18 | APIPath: "/apidocs.json", 19 | PostBuildSwaggerObjectHandler: enrichSwaggerObject} 20 | wsContainer.Add(restfulspec.NewOpenAPIService(config)) 21 | } 22 | 23 | func enrichSwaggerObject(swo *spec.Swagger) { 24 | swo.Info = &spec.Info{ 25 | InfoProps: spec.InfoProps{ 26 | Title: "PandaX 框架的API文档", 27 | Description: "这是PandaX框架文档,根据文档调用API", 28 | Contact: &spec.ContactInfo{ 29 | ContactInfoProps: spec.ContactInfoProps{ 30 | Name: "PandaX 熊猫", 31 | Email: "2417920382@qq.com", 32 | URL: "https://github.com/XM-GO/PandaX", 33 | }, 34 | }, 35 | License: &spec.License{ 36 | LicenseProps: spec.LicenseProps{ 37 | Name: "MIT", 38 | URL: "https://github.com/XM-GO/PandaX", 39 | }, 40 | }, 41 | Version: "1.0.0", 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkg/config/server.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "fmt" 4 | 5 | type Server struct { 6 | Port int `yaml:"port"` 7 | GrpcPort int `yaml:"grpc-port"` 8 | Model string `yaml:"model"` 9 | Cors bool `yaml:"cors"` 10 | Rate *Rate `yaml:"rate"` 11 | IsInitTable bool `yaml:"isInitTable"` 12 | DbType string `yaml:"db-type"` 13 | ExcelDir string `yaml:"excel-dir"` 14 | Tls *Tls `yaml:"tls"` 15 | Static *[]*Static `yaml:"static"` 16 | StaticFile *[]*StaticFile `yaml:"static-file"` 17 | } 18 | 19 | func (s *Server) GetPort() string { 20 | return fmt.Sprintf(":%d", s.Port) 21 | } 22 | 23 | type Static struct { 24 | RelativePath string `yaml:"relative-path"` 25 | Root string `yaml:"root"` 26 | } 27 | 28 | type StaticFile struct { 29 | RelativePath string `yaml:"relative-path"` 30 | Filepath string `yaml:"filepath"` 31 | } 32 | 33 | type Tls struct { 34 | Enable bool `yaml:"enable"` // 是否启用tls 35 | KeyFile string `yaml:"key-file"` // 私钥文件路径 36 | CertFile string `yaml:"cert-file"` // 证书文件路径 37 | } 38 | 39 | type Rate struct { 40 | Enable bool `yaml:"enable"` // 是否限流 41 | RateNum float64 `yaml:"rate-num"` // 限流数量 42 | } 43 | -------------------------------------------------------------------------------- /apps/rule/api/rulechain_log.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | "pandax/apps/rule/entity" 7 | "pandax/apps/rule/services" 8 | "pandax/pkg/rule_engine/nodes" 9 | "strings" 10 | ) 11 | 12 | type RuleChainMsgLogApi struct { 13 | RuleChainMsgLogApp services.RuleChainMsgLogModel 14 | } 15 | 16 | func (r *RuleChainMsgLogApi) GetNodeLabels(rc *restfulx.ReqCtx) { 17 | rc.ResData = nodes.GetCategory() 18 | } 19 | 20 | // GetRuleChainMsgLogList 列表数据 21 | func (p *RuleChainMsgLogApi) GetRuleChainMsgLogList(rc *restfulx.ReqCtx) { 22 | data := entity.RuleChainMsgLog{} 23 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 24 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 25 | data.DeviceName = restfulx.QueryParam(rc, "deviceName") 26 | list, total := p.RuleChainMsgLogApp.FindListPage(pageNum, pageSize, data) 27 | 28 | rc.ResData = model.ResultPage{ 29 | Total: total, 30 | PageNum: int64(pageNum), 31 | PageSize: int64(pageNum), 32 | Data: list, 33 | } 34 | } 35 | 36 | // DeleteRuleChainMsgLog 删除规则链 37 | func (p *RuleChainMsgLogApi) DeleteRuleChainMsgLog(rc *restfulx.ReqCtx) { 38 | id := restfulx.PathParam(rc, "id") 39 | ids := strings.Split(id, ",") 40 | p.RuleChainMsgLogApp.Delete(ids) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/tdengine/tdengine_event.go: -------------------------------------------------------------------------------- 1 | package tdengine 2 | 3 | import ( 4 | "fmt" 5 | "github.com/kakuilan/kgo" 6 | "strings" 7 | ) 8 | 9 | type ConnectInfo struct { 10 | Ts string `json:"ts"` 11 | ClientID string `json:"clientId"` 12 | Type string `json:"type"` // 连接类型 13 | PeerHost string `json:"peerHost"` 14 | SocketPort string `json:"sockPort"` 15 | Protocol string `json:"protocol"` 16 | DeviceId string `json:"deviceId"` 17 | } 18 | 19 | // CreateEventTable 创建设备连接事件表 20 | func (s *TdEngine) CreateEventTable() (err error) { 21 | sql := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s.device_connect (ts TIMESTAMP,deviceId NCHAR(64), 22 | type NCHAR(64),clientId NCHAR(64),peerHost NCHAR(64),sockPort NCHAR(64),protocol NCHAR(64))`, s.dbName) 23 | _, err = s.db.Exec(sql) 24 | return 25 | } 26 | 27 | func (s *TdEngine) InsertEvent(data map[string]any) (err error) { 28 | if len(data) == 0 { 29 | return 30 | } 31 | var ( 32 | field = []string{} 33 | value = []string{} 34 | ) 35 | for k, v := range data { 36 | field = append(field, k) 37 | value = append(value, "'"+kgo.KConv.ToStr(v)+"'") 38 | } 39 | 40 | sql := "INSERT INTO ? (?) VALUES (?)" 41 | _, err = s.db.Exec(sql, "device_connect", strings.Join(field, ","), strings.Join(value, ",")) 42 | return err 43 | } 44 | -------------------------------------------------------------------------------- /apps/rule/entity/rulechain.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "pandax/pkg/global" 5 | "time" 6 | ) 7 | 8 | type RuleChainBaseLabel struct { 9 | Id string `json:"id"` 10 | Root string `json:"root"` 11 | RuleName string `json:"ruleName"` 12 | } 13 | 14 | type RuleChainBase struct { 15 | global.BaseAuthModel 16 | Root string `json:"root" gorm:"comment:是否根节点,1 根链 0 普通链"` 17 | RuleName string `gorm:"ruleName;type:varchar(50);comment:名称" json:"ruleName"` 18 | RuleBase64 string `gorm:"ruleBase64;type:longtext;comment:Base64缩略图" json:"ruleBase64"` //缩略图 base64 19 | RuleRemark string `gorm:"ruleRemark;type:varchar(256);comment:说明" json:"ruleRemark"` 20 | } 21 | 22 | type RuleChain struct { 23 | RuleChainBase 24 | RuleDataJson string `gorm:"ruleDataJson;type:longtext;comment:Json数据" json:"ruleDataJson"` 25 | } 26 | 27 | func (RuleChain) TableName() string { 28 | return "rule_chain" 29 | } 30 | 31 | type RuleChainMsgLog struct { 32 | MessageId string `json:"message_id"` 33 | MsgType string `json:"msg_type"` 34 | DeviceName string `json:"device_name"` 35 | Ts time.Time `json:"ts"` 36 | Content string `json:"content"` 37 | CreatedAt time.Time // 创建时间 38 | } 39 | 40 | func (RuleChainMsgLog) TableName() string { 41 | return "rule_chain_msg_log" 42 | } 43 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/init.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | // init register all node's factory 4 | func init() { 5 | RegisterFactory(inputNodeFactory{}) 6 | RegisterFactory(switchFilterNodeFactory{}) 7 | RegisterFactory(scriptFilterNodeFactory{}) 8 | RegisterFactory(messageTypeFilterNodeFactory{}) 9 | RegisterFactory(messageTypeSwitchNodeFactory{}) 10 | 11 | RegisterFactory(transformDeleteKeyNodeFactory{}) 12 | RegisterFactory(transformRenameKeyNodeFactory{}) 13 | RegisterFactory(transformScriptNodeFactory{}) 14 | 15 | RegisterFactory(createAlarmNodeFactory{}) 16 | RegisterFactory(clearAlarmNodeFactory{}) 17 | RegisterFactory(logNodeFactory{}) 18 | RegisterFactory(saveAttributesNodeFactory{}) 19 | RegisterFactory(saveTimeSeriesNodeFactory{}) 20 | RegisterFactory(delayNodeFactory{}) 21 | 22 | RegisterFactory(externalDingNodeFactory{}) 23 | RegisterFactory(externalWechatNodeFactory{}) 24 | RegisterFactory(externalKafkaNodeFactory{}) 25 | RegisterFactory(externalNatsNodeFactory{}) 26 | RegisterFactory(externalMqttNodeFactory{}) 27 | RegisterFactory(externalRestapiNodeFactory{}) 28 | RegisterFactory(externalSendEmailNodeFactory{}) 29 | RegisterFactory(externalSendSmsNodeFactory{}) 30 | RegisterFactory(externalRuleChainNodeFactory{}) 31 | RegisterFactory(rpcRespondFactory{}) 32 | RegisterFactory(rpcRequestNodeFactory{}) 33 | } 34 | -------------------------------------------------------------------------------- /resource/default.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDejCCAmICCQDQU4ZRt2G46TANBgkqhkiG9w0BAQsFADB/MQswCQYDVQQGEwJ6 3 | aDEPMA0GA1UECAwGbWF5Zmx5MQswCQYDVQQHDAJ4bTEPMA0GA1UECgwGbWF5Zmx5 4 | MQ8wDQYDVQQLDAZtYXlmbHkxDzANBgNVBAMMBm1heWZseTEfMB0GCSqGSIb3DQEJ 5 | ARYQOTU0NTM3NDczQHFxLmNvbTAeFw0yMTA2MjQwMzI2MzBaFw0zMTA2MjIwMzI2 6 | MzBaMH8xCzAJBgNVBAYTAnpoMQ8wDQYDVQQIDAZtYXlmbHkxCzAJBgNVBAcMAnht 7 | MQ8wDQYDVQQKDAZtYXlmbHkxDzANBgNVBAsMBm1heWZseTEPMA0GA1UEAwwGbWF5 8 | Zmx5MR8wHQYJKoZIhvcNAQkBFhA5NTQ1Mzc0NzNAcXEuY29tMIIBIjANBgkqhkiG 9 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0CsawvEZl42Vf+0BlTuZ3Dp10yW8Oty1tjim 10 | xUj3s0WPeKil6+TehnQELS8vGJfek+yT99nyrt+bkRmg1kxZ57FtQFEuthG4OQZo 11 | aMDUz6Ab+8P1PQ9VH0XimnnYabxztJiQjl8HdJt6N4WP35kGlcul7qQ+Qc7iwjhS 12 | adfAhXVycqVIcGQyHiPPfbmYRjueAIC4czmMUxwFKCwjepGYkwzWuGkpMD0hg/SI 13 | XpFJE2dcqYPR2nCah1gxZZG00lHU1X2pehNmmgeHRkB5S7mrsCdyyV/33SAYk6T6 14 | PT7dOqY54bfnh3C0k+T7IzvKTXKG76eG63STmxVa6luVoKMvxwIDAQABMA0GCSqG 15 | SIb3DQEBCwUAA4IBAQB/e8EO2XEtkYBxebR1w6i50vaegLsxQJR3l5qm7rsHu3Cr 16 | smJXGsc56axKCAqJ4XvSI65BT51FghAoGn62QNyiQgc0YoS99nwCCGFtnhZ2lmSe 17 | pfhUHegN/Qo4I8FemEMD+o9kGeAzwrnaIVIT/cNOEQgm+RzrgHHJh5QBn2XgJalU 18 | NeFTWaimyefwSezSa/vPbyMoAl9HkT6kdvnms/yOth4AOle6+5pM2StWjmMi4yx4 19 | 16y3NvLTku6nAUazaHTOOu/MCqLWL2/qYTk3r7OCop2jr9Rp+HLbg5AfKLUIVXjG 20 | /1fnXJIuD+2u9qgDLN5PZNgz4MlU86ugtmYPFkVt 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /apps/system/entity/role.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/casbin" 5 | "github.com/PandaXGO/PandaKit/model" 6 | ) 7 | 8 | type SysRole struct { 9 | model.BaseModel 10 | RoleId int64 `json:"roleId" gorm:"primary_key;AUTO_INCREMENT"` 11 | RoleName string `json:"roleName" gorm:"type:varchar(128);comment:角色名称"` 12 | Status string `json:"status" gorm:"type:varchar(1);comment:状态"` 13 | RoleKey string `json:"roleKey" gorm:"type:varchar(128);comment:角色代码"` 14 | RoleSort int64 `json:"roleSort" gorm:"type:int;comment:角色排序"` 15 | DataScope string `json:"dataScope" gorm:"type:varchar(1);comment:数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)"` 16 | CreateBy string `json:"createBy" gorm:"type:varchar(128);comment:创建人"` 17 | UpdateBy string `json:"updateBy" gorm:"type:varchar(128);comment:修改人"` 18 | Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"` 19 | ApiIds []casbin.CasbinRule `json:"apiIds" gorm:"-"` 20 | MenuIds []int64 `json:"menuIds" gorm:"-"` 21 | DeptIds []int64 `json:"deptIds" gorm:"-"` 22 | } 23 | 24 | type MenuIdList struct { 25 | MenuId int64 `json:"menuId"` 26 | } 27 | 28 | type DeptIdList struct { 29 | DeptId int64 `json:"deptId"` 30 | } 31 | -------------------------------------------------------------------------------- /iothub/topic.go: -------------------------------------------------------------------------------- 1 | package iothub 2 | 3 | import ( 4 | "pandax/pkg/rule_engine/message" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | // Topic 上行 10 | RowTopic = `v1/devices/me/row` 11 | TelemetryTopic = `v1/devices/me/telemetry` 12 | AttributesTopic = `v1/devices/me/attributes` 13 | 14 | //网关子设备 15 | AttributesGatewayTopic = "v1/gateway/attributes" 16 | TelemetryGatewayTopic = "v1/gateway/telemetry" 17 | ConnectGatewayTopic = "v1/gateway/connect" 18 | DisconnectGatewayTopic = "v1/gateway/disconnect" 19 | 20 | RpcReqReg = `v1/devices/me/rpc/request/(.*?)$` 21 | ) 22 | 23 | var IotHubTopic = NewIotHubTopic() 24 | 25 | type TopicMeg map[string]string 26 | 27 | // 消息的来源类型 28 | func NewIotHubTopic() TopicMeg { 29 | return map[string]string{ 30 | AttributesTopic: message.AttributesMes, 31 | RowTopic: message.RowMes, 32 | TelemetryTopic: message.TelemetryMes, 33 | AttributesGatewayTopic: message.GATEWAY, 34 | TelemetryGatewayTopic: message.GATEWAY, 35 | ConnectGatewayTopic: message.GATEWAY, 36 | DisconnectGatewayTopic: message.GATEWAY, 37 | } 38 | } 39 | 40 | func (iht TopicMeg) GetMessageType(topic string) string { 41 | if meg, ok := iht[topic]; ok { 42 | return meg 43 | } 44 | if strings.Contains(topic, "v1/devices/me/rpc/request") { 45 | return message.RpcRequestMes 46 | } 47 | return "" 48 | } 49 | -------------------------------------------------------------------------------- /pkg/middleware/permission.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/biz" 5 | "github.com/PandaXGO/PandaKit/casbin" 6 | "github.com/PandaXGO/PandaKit/restfulx" 7 | "github.com/PandaXGO/PandaKit/token" 8 | "github.com/dgrijalva/jwt-go" 9 | "pandax/pkg/global" 10 | ) 11 | 12 | func PermissionHandler(rc *restfulx.ReqCtx) error { 13 | permission := rc.RequiredPermission 14 | // 如果需要的权限信息不为空,并且不需要token,则不返回错误,继续后续逻辑 15 | if permission != nil && !permission.NeedToken { 16 | return nil 17 | } 18 | tokenStr := rc.Request.Request.Header.Get("X-TOKEN") 19 | // header不存在则从查询参数token中获取 20 | if tokenStr == "" { 21 | tokenStr = rc.Request.QueryParameter("token") 22 | } 23 | if tokenStr == "" { 24 | return biz.PermissionErr 25 | } 26 | j := token.NewJWT("", []byte(global.Conf.Jwt.Key), jwt.SigningMethodHS256) 27 | loginAccount, err := j.ParseToken(tokenStr) 28 | if err != nil || loginAccount == nil { 29 | return biz.PermissionErr 30 | } 31 | rc.LoginAccount = loginAccount 32 | 33 | if !permission.NeedCasbin { 34 | return nil 35 | } 36 | 37 | ca := casbin.CasbinS{ModelPath: global.Conf.Casbin.ModelPath} 38 | e := ca.Casbin() 39 | // 判断策略中是否存在 40 | success, err := e.Enforce(loginAccount.RoleKey, rc.Request.Request.URL.Path, rc.Request.Request.Method) 41 | if !success { 42 | return biz.CasbinErr 43 | } 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/transport/http_server.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "context" 5 | "github.com/PandaXGO/PandaKit/logger" 6 | "github.com/emicklei/go-restful/v3" 7 | "net/http" 8 | "pandax/pkg/global" 9 | ) 10 | 11 | type HttpServer struct { 12 | Addr string 13 | srv *http.Server 14 | 15 | Container *restful.Container 16 | } 17 | 18 | func NewHttpServer(addr string) *HttpServer { 19 | c := restful.NewContainer() 20 | c.EnableContentEncoding(true) 21 | restful.TraceLogger(&httpLog{}) 22 | restful.SetLogger(&httpLog{}) 23 | return &HttpServer{ 24 | Addr: addr, 25 | Container: c, 26 | srv: &http.Server{ 27 | Addr: addr, 28 | Handler: c, 29 | }, 30 | } 31 | } 32 | 33 | func (s *HttpServer) Type() Type { 34 | return TypeHTTP 35 | } 36 | 37 | func (s *HttpServer) Start(ctx context.Context) error { 38 | global.Log.Infof("HTTP Server listen: %s", s.Addr) 39 | go func() { 40 | s.srv.ListenAndServe() 41 | /*if err := s.srv.ListenAndServe(); err != nil { 42 | global.Log.Errorf("error http serve: %s", err) 43 | }*/ 44 | }() 45 | return nil 46 | } 47 | 48 | func (s *HttpServer) Stop(ctx context.Context) error { 49 | return s.srv.Shutdown(ctx) 50 | } 51 | 52 | type httpLog struct{} 53 | 54 | func (t *httpLog) Print(v ...any) { 55 | logger.Log.Debug(v...) 56 | } 57 | 58 | func (t *httpLog) Printf(format string, v ...any) { 59 | logger.Log.Debugf(format, v...) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/rule_engine/instance.go: -------------------------------------------------------------------------------- 1 | package rule_engine 2 | 3 | import ( 4 | "context" 5 | "github.com/sirupsen/logrus" 6 | "pandax/pkg/rule_engine/manifest" 7 | "pandax/pkg/rule_engine/message" 8 | "pandax/pkg/rule_engine/nodes" 9 | ) 10 | 11 | type ruleChainInstance struct { 12 | firstRuleNodeId string 13 | nodes map[string]nodes.Node 14 | } 15 | 16 | func NewRuleChainInstance(data []byte) (*ruleChainInstance, []error) { 17 | errors := make([]error, 0) 18 | 19 | manifest, err := manifest.New(data) 20 | if err != nil { 21 | errors = append(errors, err) 22 | logrus.WithError(err).Errorf("invalidi manifest file") 23 | return nil, errors 24 | } 25 | return newInstanceWithManifest(manifest) 26 | } 27 | 28 | // newWithManifest create rule chain by user's manifest file 29 | func newInstanceWithManifest(m *manifest.Manifest) (*ruleChainInstance, []error) { 30 | errs := make([]error, 0) 31 | nodes, err := nodes.GetNodes(m) 32 | if err != nil { 33 | errs = append(errs, err) 34 | return nil, errs 35 | } 36 | r := &ruleChainInstance{ 37 | firstRuleNodeId: m.FirstRuleNodeId, 38 | nodes: nodes, 39 | } 40 | return r, errs 41 | } 42 | 43 | // StartRuleChain TODO 是否需要添加context 44 | func (c *ruleChainInstance) StartRuleChain(context context.Context, message *message.Message) error { 45 | if node, found := c.nodes[c.firstRuleNodeId]; found { 46 | node.Handle(message) 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/filter_message_type_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | ) 7 | 8 | type messageTypeFilterNode struct { 9 | bareNode 10 | MessageTypes []string `json:"messageTypes" yaml:"messageTypes"` 11 | } 12 | 13 | type messageTypeFilterNodeFactory struct{} 14 | 15 | func (f messageTypeFilterNodeFactory) Name() string { return "MessageTypeNode" } 16 | func (f messageTypeFilterNodeFactory) Category() string { return NODE_CATEGORY_FILTER } 17 | func (f messageTypeFilterNodeFactory) Labels() []string { return []string{"True", "False"} } 18 | 19 | func (f messageTypeFilterNodeFactory) Create(id string, meta Metadata) (Node, error) { 20 | node := &messageTypeFilterNode{ 21 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 22 | MessageTypes: []string{}, 23 | } 24 | return decodePath(meta, node) 25 | } 26 | 27 | func (n *messageTypeFilterNode) Handle(msg *message.Message) error { 28 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 29 | 30 | trueLabelNode := n.GetLinkedNode("True") 31 | falseLabelNode := n.GetLinkedNode("False") 32 | messageType := msg.MsgType 33 | 34 | for _, filterType := range n.MessageTypes { 35 | if filterType == messageType && trueLabelNode != nil { 36 | return trueLabelNode.Handle(msg) 37 | } 38 | } 39 | if falseLabelNode != nil { 40 | return falseLabelNode.Handle(msg) 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/filter_script_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | ) 7 | 8 | const ScriptFilterNodeName = "ScriptFilterNode" 9 | 10 | type scriptFilterNode struct { 11 | bareNode 12 | Script string `json:"script" yaml:"script"` 13 | } 14 | 15 | type scriptFilterNodeFactory struct{} 16 | 17 | func (f scriptFilterNodeFactory) Name() string { return ScriptFilterNodeName } 18 | func (f scriptFilterNodeFactory) Category() string { return NODE_CATEGORY_FILTER } 19 | func (f scriptFilterNodeFactory) Labels() []string { return []string{"True", "False"} } 20 | func (f scriptFilterNodeFactory) Create(id string, meta Metadata) (Node, error) { 21 | node := &scriptFilterNode{ 22 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 23 | } 24 | return decodePath(meta, node) 25 | } 26 | 27 | func (n *scriptFilterNode) Handle(msg *message.Message) error { 28 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 29 | 30 | trueLabelNode := n.GetLinkedNode("True") 31 | falseLabelNode := n.GetLinkedNode("False") 32 | scriptEngine := NewScriptEngine(*msg, "Filter", n.Script) 33 | isTrue, error := scriptEngine.ScriptOnFilter() 34 | if isTrue == true && error == nil && trueLabelNode != nil { 35 | return trueLabelNode.Handle(msg) 36 | } else { 37 | if falseLabelNode != nil { 38 | return falseLabelNode.Handle(msg) 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/filter_message_type_switch_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | ) 7 | 8 | type messageTypeSwitchNode struct { 9 | bareNode 10 | } 11 | type messageTypeSwitchNodeFactory struct{} 12 | 13 | func (f messageTypeSwitchNodeFactory) Name() string { return "MessageTypeSwitchNode" } 14 | func (f messageTypeSwitchNodeFactory) Category() string { return NODE_CATEGORY_FILTER } 15 | func (f messageTypeSwitchNodeFactory) Labels() []string { 16 | return []string{ 17 | message.RowMes, 18 | message.AttributesMes, 19 | message.TelemetryMes, 20 | message.RpcRequestMes, 21 | message.AlarmMes, 22 | message.UpEventMes, 23 | message.ConnectMes, 24 | message.ConnectMes, 25 | message.DisConnectMes, 26 | } 27 | } 28 | func (f messageTypeSwitchNodeFactory) Create(id string, meta Metadata) (Node, error) { 29 | node := &messageTypeSwitchNode{ 30 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 31 | } 32 | return decodePath(meta, node) 33 | } 34 | 35 | func (n *messageTypeSwitchNode) Handle(msg *message.Message) error { 36 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 37 | msg.Metadata = map[string]interface{}{"AA": "BB", "deviceName": "fff"} 38 | nodes := n.GetLinkedNodes() 39 | messageType := msg.MsgType 40 | for label, node := range nodes { 41 | if messageType == label { 42 | return node.Handle(msg) 43 | } 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/transform_script_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | ) 7 | 8 | type transformScriptNode struct { 9 | bareNode 10 | Script string `json:"script" yaml:"script"` 11 | } 12 | 13 | type transformScriptNodeFactory struct{} 14 | 15 | func (f transformScriptNodeFactory) Name() string { return "ScriptKeyNode" } 16 | func (f transformScriptNodeFactory) Category() string { return NODE_CATEGORY_TRANSFORM } 17 | func (f transformScriptNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 18 | func (f transformScriptNodeFactory) Create(id string, meta Metadata) (Node, error) { 19 | node := &transformScriptNode{ 20 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 21 | } 22 | return decodePath(meta, node) 23 | } 24 | 25 | func (n *transformScriptNode) Handle(msg *message.Message) error { 26 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 27 | 28 | successLabelNode := n.GetLinkedNode("Success") 29 | failureLabelNode := n.GetLinkedNode("Failure") 30 | 31 | scriptEngine := NewScriptEngine(*msg, "Transform", n.Script) 32 | newMessage, err := scriptEngine.ScriptOnMessage() 33 | if err != nil { 34 | if failureLabelNode != nil { 35 | return failureLabelNode.Handle(msg) 36 | } else { 37 | return err 38 | } 39 | } 40 | if successLabelNode != nil { 41 | return successLabelNode.Handle(newMessage) 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /apps/log/api/log_oper.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | "github.com/PandaXGO/PandaKit/utils" 7 | "log" 8 | "pandax/apps/log/entity" 9 | "pandax/apps/log/services" 10 | ) 11 | 12 | type LogOperApi struct { 13 | LogOperApp services.LogOperModel 14 | } 15 | 16 | func (l *LogOperApi) GetOperLogList(rc *restfulx.ReqCtx) { 17 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 18 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 19 | businessType := restfulx.QueryParam(rc, "businessType") 20 | operName := restfulx.QueryParam(rc, "operName") 21 | title := restfulx.QueryParam(rc, "title") 22 | list, total := l.LogOperApp.FindListPage(pageNum, pageSize, entity.LogOper{BusinessType: businessType, OperName: operName, Title: title}) 23 | rc.ResData = model.ResultPage{ 24 | Total: total, 25 | PageNum: int64(pageNum), 26 | PageSize: int64(pageNum), 27 | Data: list, 28 | } 29 | } 30 | 31 | func (l *LogOperApi) GetOperLog(rc *restfulx.ReqCtx) { 32 | operId := restfulx.PathParamInt(rc, "operId") 33 | rc.ResData = l.LogOperApp.FindOne(int64(operId)) 34 | } 35 | 36 | func (l *LogOperApi) DeleteOperLog(rc *restfulx.ReqCtx) { 37 | operIds := restfulx.PathParam(rc, "operId") 38 | group := utils.IdsStrToIdsIntGroup(operIds) 39 | log.Println("group", group) 40 | l.LogOperApp.Delete(group) 41 | } 42 | 43 | func (l *LogOperApi) DeleteAll(rc *restfulx.ReqCtx) { 44 | l.LogOperApp.DeleteAll() 45 | } 46 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/filter_device_type_switch_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | ) 7 | 8 | // 检查关联关系 9 | // 该消息来自与哪个实体或到那个实体 10 | type deviceTypeSwitchNode struct { 11 | bareNode 12 | } 13 | 14 | type deviceTypeSwitchNodeFactory struct{} 15 | 16 | func (f deviceTypeSwitchNodeFactory) Name() string { return "DeviceTypeSwitchNode" } 17 | func (f deviceTypeSwitchNodeFactory) Category() string { return NODE_CATEGORY_FILTER } 18 | func (f deviceTypeSwitchNodeFactory) Labels() []string { 19 | return []string{message.DEVICE, message.GATEWAY} 20 | } 21 | func (f deviceTypeSwitchNodeFactory) Create(id string, meta Metadata) (Node, error) { 22 | node := &deviceTypeSwitchNode{ 23 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 24 | } 25 | return decodePath(meta, node) 26 | } 27 | 28 | func (n *deviceTypeSwitchNode) Handle(msg *message.Message) error { 29 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 30 | 31 | deviceLabelNode := n.GetLinkedNode(message.DEVICE) 32 | gatewayLabelNode := n.GetLinkedNode(message.GATEWAY) 33 | 34 | if msg.Metadata.GetValue("deviceType").(string) == message.DEVICE { 35 | if deviceLabelNode != nil { 36 | return deviceLabelNode.Handle(msg) 37 | } 38 | } 39 | if msg.Metadata.GetValue("deviceType").(string) == message.GATEWAY { 40 | if gatewayLabelNode != nil { 41 | return gatewayLabelNode.Handle(msg) 42 | } 43 | } 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /apps/system/entity/dict.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/PandaXGO/PandaKit/model" 4 | 5 | type SysDictData struct { 6 | DictCode int64 `json:"dictCode" gorm:"primary_key;AUTO_INCREMENT"` 7 | DictSort int `json:"dictSort" gorm:"type:int;comment:排序"` 8 | DictLabel string `json:"dictLabel" gorm:"type:varchar(64);comment:标签"` 9 | DictValue string `json:"dictValue" gorm:"type:varchar(64);comment:值"` 10 | DictType string `json:"dictType" gorm:"type:varchar(64);comment:字典类型"` 11 | Status string `json:"status" gorm:"type:varchar(1);comment:状态(0正常 1停用)"` 12 | CssClass string `json:"cssClass" gorm:"type:varchar(128);comment:CssClass"` 13 | ListClass string `json:"listClass" gorm:"type:varchar(128);comment:ListClass"` 14 | IsDefault string `json:"isDefault" gorm:"type:varchar(8);comment:IsDefault"` 15 | CreateBy string `json:"createBy"` 16 | UpdateBy string `json:"updateBy"` 17 | Remark string `json:"remark" gorm:"type:varchar(256);comment:备注"` 18 | model.BaseModel 19 | } 20 | 21 | type SysDictType struct { 22 | DictId int64 `json:"dictId" gorm:"primary_key;AUTO_INCREMENT"` 23 | DictName string `json:"dictName" gorm:"type:varchar(64);comment:名称"` 24 | DictType string `json:"dictType" gorm:"type:varchar(64);comment:类型"` 25 | Status string `json:"status" gorm:"type:varchar(1);comment:状态"` 26 | CreateBy string `json:"createBy"` 27 | UpdateBy string `json:"updateBy"` 28 | Remark string `json:"remark" gorm:"type:varchar(256);comment:备注"` 29 | model.BaseModel 30 | } 31 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/action_rpc_request_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "pandax/pkg/global" 5 | "pandax/pkg/mqtt" 6 | "pandax/pkg/rule_engine/message" 7 | ) 8 | 9 | type rpcRequestNode struct { 10 | bareNode 11 | Timeout int `json:"timeout"` 12 | Payload mqtt.RpcPayload `json:"payload"` 13 | } 14 | 15 | type rpcRequestNodeFactory struct{} 16 | 17 | func (f rpcRequestNodeFactory) Name() string { return "RpcRequestNode" } 18 | func (f rpcRequestNodeFactory) Category() string { return NODE_CATEGORY_ACTION } 19 | func (f rpcRequestNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 20 | func (f rpcRequestNodeFactory) Create(id string, meta Metadata) (Node, error) { 21 | node := &rpcRequestNode{ 22 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 23 | } 24 | return decodePath(meta, node) 25 | } 26 | 27 | func (n *rpcRequestNode) Handle(msg *message.Message) error { 28 | successLableNode := n.GetLinkedNode("Success") 29 | failureLableNode := n.GetLinkedNode("Failure") 30 | 31 | var rpc = &mqtt.RpcRequest{Client: global.MqttClient, Mode: "double", Timeout: n.Timeout} 32 | rpc.GetRequestId() 33 | respPayload, err := rpc.RequestCmd(n.Payload) 34 | if err != nil { 35 | if failureLableNode != nil { 36 | return failureLableNode.Handle(msg) 37 | } else { 38 | return err 39 | } 40 | } 41 | msgM := msg.Msg 42 | msgM["payload"] = respPayload 43 | msg.Msg = msgM 44 | if successLableNode != nil { 45 | return successLableNode.Handle(msg) 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /apps/device/api/device_alarm.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ========================================================================== 4 | import ( 5 | "github.com/PandaXGO/PandaKit/model" 6 | "github.com/PandaXGO/PandaKit/restfulx" 7 | "strings" 8 | 9 | "pandax/apps/device/entity" 10 | "pandax/apps/device/services" 11 | ) 12 | 13 | type DeviceAlarmApi struct { 14 | DeviceAlarmApp services.DeviceAlarmModel 15 | } 16 | 17 | // GetDeviceAlarmList 告警列表数据 18 | func (p *DeviceAlarmApi) GetDeviceAlarmList(rc *restfulx.ReqCtx) { 19 | data := entity.DeviceAlarm{} 20 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 21 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 22 | data.DeviceId = restfulx.QueryParam(rc, "deviceId") 23 | data.Type = restfulx.QueryParam(rc, "type") 24 | data.Level = restfulx.QueryParam(rc, "level") 25 | data.State = restfulx.QueryParam(rc, "state") 26 | 27 | list, total := p.DeviceAlarmApp.FindListPage(pageNum, pageSize, data) 28 | 29 | rc.ResData = model.ResultPage{ 30 | Total: total, 31 | PageNum: int64(pageNum), 32 | PageSize: int64(pageNum), 33 | Data: list, 34 | } 35 | } 36 | 37 | // UpdateDeviceAlarm 修改告警 38 | func (p *DeviceAlarmApi) UpdateDeviceAlarm(rc *restfulx.ReqCtx) { 39 | var data entity.DeviceAlarm 40 | restfulx.BindJsonAndValid(rc, &data) 41 | p.DeviceAlarmApp.Update(data) 42 | } 43 | 44 | // DeleteDeviceAlarm 删除告警 45 | func (p *DeviceAlarmApi) DeleteDeviceAlarm(rc *restfulx.ReqCtx) { 46 | id := restfulx.PathParam(rc, "id") 47 | ids := strings.Split(id, ",") 48 | p.DeviceAlarmApp.Delete(ids) 49 | } 50 | -------------------------------------------------------------------------------- /apps/log/api/log_login.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | "github.com/PandaXGO/PandaKit/utils" 7 | "pandax/apps/log/entity" 8 | "pandax/apps/log/services" 9 | ) 10 | 11 | type LogLoginApi struct { 12 | LogLoginApp services.LogLoginModel 13 | } 14 | 15 | func (l *LogLoginApi) GetLoginLogList(rc *restfulx.ReqCtx) { 16 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 17 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 18 | loginLocation := restfulx.QueryParam(rc, "loginLocation") 19 | username := restfulx.QueryParam(rc, "username") 20 | list, total := l.LogLoginApp.FindListPage(pageNum, pageSize, entity.LogLogin{LoginLocation: loginLocation, Username: username}) 21 | rc.ResData = model.ResultPage{ 22 | Total: total, 23 | PageNum: int64(pageNum), 24 | PageSize: int64(pageNum), 25 | Data: list, 26 | } 27 | } 28 | 29 | func (l *LogLoginApi) GetLoginLog(rc *restfulx.ReqCtx) { 30 | infoId := restfulx.PathParamInt(rc, "infoId") 31 | rc.ResData = l.LogLoginApp.FindOne(int64(infoId)) 32 | } 33 | 34 | func (l *LogLoginApi) UpdateLoginLog(rc *restfulx.ReqCtx) { 35 | var log entity.LogLogin 36 | restfulx.BindQuery(rc, &log) 37 | l.LogLoginApp.Update(log) 38 | } 39 | 40 | func (l *LogLoginApi) DeleteLoginLog(rc *restfulx.ReqCtx) { 41 | infoIds := restfulx.PathParam(rc, "infoId") 42 | group := utils.IdsStrToIdsIntGroup(infoIds) 43 | l.LogLoginApp.Delete(group) 44 | } 45 | 46 | func (l *LogLoginApi) DeleteAll(rc *restfulx.ReqCtx) { 47 | l.LogLoginApp.DeleteAll() 48 | } 49 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/filter_switch_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | ) 7 | 8 | type switchFilterNode struct { 9 | bareNode 10 | Script string `json:"script" yaml:"script"` 11 | } 12 | 13 | type switchFilterNodeFactory struct{} 14 | 15 | func (f switchFilterNodeFactory) Name() string { return "SwitchNode" } 16 | func (f switchFilterNodeFactory) Category() string { return NODE_CATEGORY_FILTER } 17 | func (f switchFilterNodeFactory) Labels() []string { 18 | return []string{ 19 | "True", "False", 20 | message.RowMes, 21 | message.AttributesMes, 22 | message.TelemetryMes, 23 | message.RpcRequestMes, 24 | message.AlarmMes, 25 | message.UpEventMes, 26 | message.ConnectMes, 27 | message.DisConnectMes, 28 | } 29 | } 30 | func (f switchFilterNodeFactory) Create(id string, meta Metadata) (Node, error) { 31 | node := &switchFilterNode{ 32 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 33 | } 34 | return decodePath(meta, node) 35 | } 36 | 37 | func (n *switchFilterNode) Handle(msg *message.Message) error { 38 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 39 | 40 | scriptEngine := NewScriptEngine(*msg, "Switch", n.Script) 41 | SwitchResults, err := scriptEngine.ScriptOnSwitch() 42 | if err != nil { 43 | return err 44 | } 45 | nodes := n.GetLinkedNodes() 46 | for label, node := range nodes { 47 | for _, switchresult := range SwitchResults { 48 | if label == switchresult { 49 | go node.Handle(msg) 50 | } 51 | } 52 | } 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/tdengine/tdengine_log.go: -------------------------------------------------------------------------------- 1 | package tdengine 2 | 3 | import "time" 4 | 5 | // CreateLogStable 添加LOG超级表 6 | func (s *TdEngine) CreateLogStable() (err error) { 7 | var name string 8 | err = s.db.QueryRow("SELECT stable_name FROM information_schema.ins_stables WHERE stable_name = 'device_log' LIMIT 1").Scan(&name) 9 | if name != "" { 10 | return 11 | } 12 | sql := "CREATE STABLE device_log (ts TIMESTAMP, type VARCHAR(20), content VARCHAR(1000)) TAGS (device VARCHAR(255))" 13 | _, err = s.db.Exec(sql) 14 | return 15 | } 16 | 17 | // InsertLog 写入数据 18 | func (s *TdEngine) InsertLog(log *TdLog) (err error) { 19 | sql := "INSERT INTO ? USING device_log TAGS ('?') VALUES ('?', '?', '?')" 20 | _, err = s.db.Exec(sql, "log_"+log.Device, log.Device, log.Ts, log.Type, log.Content) 21 | 22 | return 23 | } 24 | 25 | // ClearLog 清理过期数据 26 | func (s *TdEngine) ClearLog() (err error) { 27 | ts := time.Now().Add(-7 * 24 * time.Hour).Format("2006-01-02") 28 | 29 | sql := "DELETE FROM device_log WHERE ts < '" + ts + "'" 30 | _, err = s.db.Exec(sql) 31 | 32 | return 33 | } 34 | 35 | // GetAllLog 超级表查询,多条数据 36 | func (s *TdEngine) GetAllLog(sql string, args ...any) (list []TdLog, err error) { 37 | rows, err := s.db.Query(sql, args...) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer rows.Close() 42 | 43 | for rows.Next() { 44 | var log TdLog 45 | 46 | err = rows.Scan(&log.Ts, &log.Type, &log.Content, &log.Device) 47 | if err != nil { 48 | return nil, err 49 | } 50 | log.Ts = s.Time(log.Ts) 51 | 52 | list = append(list, log) 53 | } 54 | 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/action_rpc_respond_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "pandax/pkg/global" 5 | "pandax/pkg/mqtt" 6 | "pandax/pkg/rule_engine/message" 7 | ) 8 | 9 | type rpcRespondNode struct { 10 | bareNode 11 | RequestId int `json:"requestId"` 12 | } 13 | 14 | type rpcRespondFactory struct{} 15 | 16 | func (f rpcRespondFactory) Name() string { return "RpcRespondNode" } 17 | func (f rpcRespondFactory) Category() string { return NODE_CATEGORY_ACTION } 18 | func (f rpcRespondFactory) Labels() []string { return []string{"Success", "Failure"} } 19 | func (f rpcRespondFactory) Create(id string, meta Metadata) (Node, error) { 20 | node := &rpcRespondNode{ 21 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 22 | } 23 | return decodePath(meta, node) 24 | } 25 | 26 | func (n *rpcRespondNode) Handle(msg *message.Message) error { 27 | successLableNode := n.GetLinkedNode("Success") 28 | failureLableNode := n.GetLinkedNode("Failure") 29 | RequestId := n.RequestId 30 | if RequestId == 0 { 31 | RequestId = int(msg.Metadata.GetValue("requestId").(float64)) 32 | } 33 | var datas = mqtt.RpcPayload{ 34 | Method: msg.Msg.GetValue("method").(string), 35 | Params: msg.Msg.GetValue("params"), 36 | } 37 | rpc := &mqtt.RpcRequest{Client: global.MqttClient, RequestId: RequestId} 38 | err := rpc.RespondTpc(datas) 39 | if err != nil { 40 | if failureLableNode != nil { 41 | return failureLableNode.Handle(msg) 42 | } else { 43 | return err 44 | } 45 | } 46 | if successLableNode != nil { 47 | return successLableNode.Handle(msg) 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /apps/rule/router/rulechain_log.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | restfulspec "github.com/emicklei/go-restful-openapi/v2" 7 | "github.com/emicklei/go-restful/v3" 8 | "pandax/apps/rule/api" 9 | "pandax/apps/rule/services" 10 | ) 11 | 12 | func InitRuleChainMsgLogRouter(container *restful.Container) { 13 | s := &api.RuleChainMsgLogApi{ 14 | RuleChainMsgLogApp: services.RuleChainMsgLogModelDao, 15 | } 16 | 17 | ws := new(restful.WebService) 18 | ws.Path("/rule/chain/log").Produces(restful.MIME_JSON) 19 | tags := []string{"RuleChainMsgLog"} 20 | 21 | ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { 22 | restfulx.NewReqCtx(request, response).WithLog("获取规则引擎分页列表").Handle(s.GetRuleChainMsgLogList) 23 | }). 24 | Doc("获取规则引擎分页列表"). 25 | Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). 26 | Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). 27 | Param(ws.QueryParameter("ruleName", "规则名").Required(false).DataType("string")). 28 | Metadata(restfulspec.KeyOpenAPITags, tags). 29 | Writes(model.ResultPage{}). 30 | Returns(200, "OK", model.ResultPage{})) 31 | 32 | ws.Route(ws.DELETE("/{id}").To(func(request *restful.Request, response *restful.Response) { 33 | restfulx.NewReqCtx(request, response).WithLog("删除规则引擎信息").Handle(s.DeleteRuleChainMsgLog) 34 | }). 35 | Doc("删除规则链日志信息"). 36 | Metadata(restfulspec.KeyOpenAPITags, tags). 37 | Param(ws.PathParameter("id", "多id 1,2,3").DataType("string"))) 38 | 39 | container.Add(ws) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/metadata.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "fmt" 5 | "github.com/mitchellh/mapstructure" 6 | ) 7 | 8 | const ( 9 | NODE_CONFIG_MESSAGE_TYPE_KEY = "messageTypeKey" 10 | NODE_CONFIG_ORIGINATOR_TYPE_KEY = "originatorTypeKey" 11 | ) 12 | 13 | // Metadata 前端 参数 Properties 14 | type Metadata interface { 15 | Keys() []string 16 | With(key string, val interface{}) Metadata 17 | Value(key string) (interface{}, error) 18 | DecodePath(rawVal interface{}) error 19 | } 20 | 21 | type nodeMetadata struct { 22 | keypairs map[string]interface{} 23 | } 24 | 25 | func NewMetadata() Metadata { 26 | return &nodeMetadata{ 27 | keypairs: make(map[string]interface{}), 28 | } 29 | } 30 | 31 | func NewMetadataWithString(vals string) Metadata { 32 | return &nodeMetadata{} 33 | } 34 | 35 | func NewMetadataWithValues(vals map[string]interface{}) Metadata { 36 | return &nodeMetadata{ 37 | keypairs: vals, 38 | } 39 | } 40 | 41 | func (c *nodeMetadata) Keys() []string { 42 | keys := []string{} 43 | for key, _ := range c.keypairs { 44 | keys = append(keys, key) 45 | } 46 | return keys 47 | } 48 | 49 | func (c *nodeMetadata) Value(key string) (interface{}, error) { 50 | if val, found := c.keypairs[key]; found { 51 | return val, nil 52 | } 53 | return nil, fmt.Errorf("key '%s' not found", key) 54 | } 55 | 56 | func (c *nodeMetadata) With(key string, val interface{}) Metadata { 57 | c.keypairs[key] = val 58 | return c 59 | } 60 | 61 | func (c *nodeMetadata) DecodePath(rawVal interface{}) error { 62 | //return utils.Map2Struct(c.keypairs, rawVal) 63 | return mapstructure.Decode(c.keypairs, rawVal) 64 | } 65 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/external_rule_chain_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "pandax/apps/rule/services" 5 | "pandax/pkg/rule_engine/message" 6 | 7 | "errors" 8 | "fmt" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | type externalRuleChainNode struct { 13 | bareNode 14 | RuleId string `json:"ruleId" yaml:"ruleId"` 15 | } 16 | 17 | type externalRuleChainNodeFactory struct{} 18 | 19 | func (f externalRuleChainNodeFactory) Name() string { return "RuleChainNode" } 20 | func (f externalRuleChainNodeFactory) Category() string { return NODE_CATEGORY_FLOWS } 21 | func (f externalRuleChainNodeFactory) Labels() []string { return []string{} } 22 | func (f externalRuleChainNodeFactory) Create(id string, meta Metadata) (Node, error) { 23 | node := &externalRuleChainNode{ 24 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 25 | } 26 | 27 | return decodePath(meta, node) 28 | } 29 | 30 | func (n *externalRuleChainNode) Handle(msg *message.Message) error { 31 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 32 | data := services.RuleChainModelDao.FindOne(n.RuleId) 33 | if data == nil { 34 | return errors.New(fmt.Sprintf("节点 %s ,获取规则链失败", n.Name())) 35 | } 36 | 37 | /*code, _ := json.Marshal(data.RuleDataJson.LfData.DataCode) 38 | m, err := manifest.New(code) 39 | if err != nil { 40 | logrus.WithError(err).Errorf("invalidi manifest file") 41 | return err 42 | } 43 | nodes, err := GetNodes(m) 44 | if err != nil { 45 | return errors.New(fmt.Sprintf("节点 %s ,构建节点失败", n.Name())) 46 | } 47 | if node, found := nodes[m.FirstRuleNodeId]; found { 48 | go node.Handle(msg) 49 | }*/ 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/action_save_attributes_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/global" 6 | "pandax/pkg/rule_engine/message" 7 | ) 8 | 9 | type saveAttributesNode struct { 10 | bareNode 11 | } 12 | 13 | type saveAttributesNodeFactory struct{} 14 | 15 | func (f saveAttributesNodeFactory) Name() string { return "SaveAttributesNode" } 16 | func (f saveAttributesNodeFactory) Category() string { return NODE_CATEGORY_ACTION } 17 | func (f saveAttributesNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 18 | func (f saveAttributesNodeFactory) Create(id string, meta Metadata) (Node, error) { 19 | node := &saveAttributesNode{ 20 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 21 | } 22 | return decodePath(meta, node) 23 | } 24 | 25 | func (n *saveAttributesNode) Handle(msg *message.Message) error { 26 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 27 | successLabelNode := n.GetLinkedNode("Success") 28 | failureLabelNode := n.GetLinkedNode("Failure") 29 | if msg.MsgType != message.AttributesMes { 30 | if failureLabelNode != nil { 31 | return failureLabelNode.Handle(msg) 32 | } else { 33 | return nil 34 | } 35 | } 36 | //deviceId := msg.GetMetadata().GetValues()["deviceId"].(string) 37 | deviceName := msg.Metadata["deviceName"].(string) 38 | err := global.TdDb.InsertDevice(deviceName+"_attributes", msg.Msg) 39 | if err != nil { 40 | if failureLabelNode != nil { 41 | return failureLabelNode.Handle(msg) 42 | } 43 | } 44 | if successLabelNode != nil { 45 | return successLabelNode.Handle(msg) 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /apps/job/services/log_job.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/biz" 5 | "pandax/apps/job/entity" 6 | "pandax/pkg/global" 7 | ) 8 | 9 | type ( 10 | JobLogModel interface { 11 | Insert(data entity.JobLog) *entity.JobLog 12 | FindListPage(page, pageSize int, data entity.JobLog) (*[]entity.JobLog, int64) 13 | Delete(infoId []int64) 14 | DeleteAll() 15 | } 16 | 17 | JobLogModelImpl struct { 18 | table string 19 | } 20 | ) 21 | 22 | var JobLogModelDao JobLogModel = &JobLogModelImpl{ 23 | table: `job_logs`, 24 | } 25 | 26 | func (m *JobLogModelImpl) Insert(data entity.JobLog) *entity.JobLog { 27 | global.Db.Table(m.table).Create(&data) 28 | return &data 29 | } 30 | 31 | func (m *JobLogModelImpl) FindListPage(page, pageSize int, data entity.JobLog) (*[]entity.JobLog, int64) { 32 | list := make([]entity.JobLog, 0) 33 | var total int64 = 0 34 | offset := pageSize * (page - 1) 35 | db := global.Db.Table(m.table) 36 | // 此处填写 where参数判断 37 | if data.Status != "" { 38 | db = db.Where("status = ?", data.Status) 39 | } 40 | if data.Name != "" { 41 | db = db.Where("name like ?", "%"+data.Name+"%") 42 | } 43 | err := db.Count(&total).Error 44 | err = db.Order("create_time desc").Limit(pageSize).Offset(offset).Find(&list).Error 45 | 46 | biz.ErrIsNil(err, "查询登录分页日志信息失败") 47 | return &list, total 48 | } 49 | 50 | func (m *JobLogModelImpl) Delete(logIds []int64) { 51 | err := global.Db.Table(m.table).Delete(&entity.JobLog{}, "id in (?)", logIds).Error 52 | biz.ErrIsNil(err, "删除登录日志信息失败") 53 | return 54 | } 55 | 56 | func (m *JobLogModelImpl) DeleteAll() { 57 | global.Db.Exec("DELETE FROM log_jobs") 58 | } 59 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/external_send_sms_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | ) 7 | 8 | type externalSendSmsNode struct { 9 | bareNode 10 | SecretId string `json:"secretId" yaml:"secretId"` 11 | SecretKey string `json:"secretKey" yaml:"secretKey"` 12 | SdkAppId string `json:"sdkAppId" yaml:"sdkAppId"` //应用Id(腾讯) 或 签名名称(阿里) 13 | PhoneNumber string `json:"phoneNumber" yaml:"phoneNumber"` //发送到手机号 14 | TemplateId string `json:"templateId" yaml:"templateId"` //短信模板Id 15 | TemplateParam map[string]interface{} `json:"templateParam" yaml:"templateParam"` //模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空*/ 16 | } 17 | 18 | type externalSendSmsNodeFactory struct{} 19 | 20 | func (f externalSendSmsNodeFactory) Name() string { return "SendSmsNode" } 21 | func (f externalSendSmsNodeFactory) Category() string { return NODE_CATEGORY_EXTERNAL } 22 | func (f externalSendSmsNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 23 | func (f externalSendSmsNodeFactory) Create(id string, meta Metadata) (Node, error) { 24 | node := &externalSendSmsNode{ 25 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 26 | } 27 | return decodePath(meta, node) 28 | } 29 | 30 | func (n *externalSendSmsNode) Handle(msg *message.Message) error { 31 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 32 | 33 | successLabelNode := n.GetLinkedNode("Success") 34 | //failureLabelNode := n.GetLinkedNode("Failure") 35 | 36 | return successLabelNode.Handle(msg) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/action_save_timeseries_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "log" 6 | "pandax/pkg/global" 7 | "pandax/pkg/rule_engine/message" 8 | ) 9 | 10 | type saveTimeSeriesNode struct { 11 | bareNode 12 | } 13 | 14 | type saveTimeSeriesNodeFactory struct{} 15 | 16 | func (f saveTimeSeriesNodeFactory) Name() string { return "SaveTimeSeriesNode" } 17 | func (f saveTimeSeriesNodeFactory) Category() string { return NODE_CATEGORY_ACTION } 18 | func (f saveTimeSeriesNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 19 | func (f saveTimeSeriesNodeFactory) Create(id string, meta Metadata) (Node, error) { 20 | node := &saveTimeSeriesNode{ 21 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 22 | } 23 | return decodePath(meta, node) 24 | } 25 | 26 | func (n *saveTimeSeriesNode) Handle(msg *message.Message) error { 27 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 28 | successLabelNode := n.GetLinkedNode("Success") 29 | failureLabelNode := n.GetLinkedNode("Failure") 30 | if msg.MsgType != message.TelemetryMes { 31 | if failureLabelNode != nil { 32 | return failureLabelNode.Handle(msg) 33 | } else { 34 | return nil 35 | } 36 | } 37 | //deviceId := msg.GetMetadata().GetValues()["deviceId"].(string) 38 | deviceName := msg.Metadata["deviceName"].(string) 39 | log.Println(msg.Msg) 40 | log.Println(msg.Metadata) 41 | err := global.TdDb.InsertDevice(deviceName+"_telemetry", msg.Msg) 42 | log.Println(err) 43 | if err != nil { 44 | if failureLabelNode != nil { 45 | return failureLabelNode.Handle(msg) 46 | } 47 | } 48 | if successLabelNode != nil { 49 | return successLabelNode.Handle(msg) 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/PandaXGO/PandaKit/biz" 7 | "github.com/PandaXGO/PandaKit/utils" 8 | "path/filepath" 9 | ) 10 | 11 | func InitConfig(configFilePath string) *Config { 12 | // 获取启动参数中,配置文件的绝对路径 13 | path, _ := filepath.Abs(configFilePath) 14 | startConfigParam = &CmdConfigParam{ConfigFilePath: path} 15 | // 读取配置文件信息 16 | yc := &Config{} 17 | if err := utils.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil { 18 | panic(any(fmt.Sprintf("读取配置文件[%s]失败: %s", startConfigParam.ConfigFilePath, err.Error()))) 19 | } 20 | // 校验配置文件内容信息 21 | yc.Valid() 22 | 23 | return yc 24 | 25 | } 26 | 27 | // 启动配置参数 28 | type CmdConfigParam struct { 29 | ConfigFilePath string // -e 配置文件路径 30 | } 31 | 32 | // 启动可执行文件时的参数 33 | var startConfigParam *CmdConfigParam 34 | 35 | // yaml配置文件映射对象 36 | type Config struct { 37 | App *App `yaml:"app"` 38 | Server *Server `yaml:"server"` 39 | Queue *Queue `yaml:"queue"` 40 | Jwt *Jwt `yaml:"jwt"` 41 | Redis *Redis `yaml:"redis"` 42 | Mysql *Mysql `yaml:"mysql"` 43 | Oss *Oss `yaml:"oss"` 44 | Taos *Taos `yaml:"taos"` 45 | Mqtt *Mqtt `yaml:"mqtt"` 46 | Casbin *Casbin `yaml:"casbin"` 47 | Gen *Gen `yaml:"gen"` 48 | Log *Log `yaml:"log"` 49 | } 50 | 51 | // 配置文件内容校验 52 | func (c *Config) Valid() { 53 | biz.IsTrue(c.Jwt != nil, "配置文件的[jwt]信息不能为空") 54 | c.Jwt.Valid() 55 | } 56 | 57 | // 获取执行可执行文件时,指定的启动参数 58 | func getStartConfig() *CmdConfigParam { 59 | configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录") 60 | flag.Parse() 61 | // 获取配置文件绝对路径 62 | path, _ := filepath.Abs(*configFilePath) 63 | sc := &CmdConfigParam{ConfigFilePath: path} 64 | return sc 65 | } 66 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/action_log_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "pandax/apps/rule/entity" 5 | "pandax/apps/rule/services" 6 | "pandax/pkg/global" 7 | "pandax/pkg/rule_engine/message" 8 | ) 9 | 10 | type logNode struct { 11 | bareNode 12 | Script string `json:"script"` 13 | } 14 | 15 | type logNodeFactory struct{} 16 | 17 | func (f logNodeFactory) Name() string { return "LogNode" } 18 | func (f logNodeFactory) Category() string { return NODE_CATEGORY_ACTION } 19 | func (f logNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 20 | func (f logNodeFactory) Create(id string, meta Metadata) (Node, error) { 21 | node := &logNode{ 22 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 23 | } 24 | return decodePath(meta, node) 25 | } 26 | 27 | func (n *logNode) Handle(msg *message.Message) error { 28 | successLableNode := n.GetLinkedNode("Success") 29 | failureLableNode := n.GetLinkedNode("Failure") 30 | 31 | scriptEngine := NewScriptEngine(*msg, "ToString", n.Script) 32 | logMessage, err := scriptEngine.ScriptToString() 33 | if err != nil { 34 | if failureLableNode != nil { 35 | return failureLableNode.Handle(msg) 36 | } else { 37 | return err 38 | } 39 | } 40 | services.RuleChainMsgLogModelDao.Insert(entity.RuleChainMsgLog{ 41 | MessageId: msg.Id, 42 | MsgType: msg.MsgType, 43 | DeviceName: msg.Metadata["deviceName"].(string), 44 | Ts: msg.Ts, 45 | Content: logMessage, 46 | }) 47 | global.Log.Info(logMessage) 48 | if err != nil { 49 | if failureLableNode != nil { 50 | return failureLableNode.Handle(msg) 51 | } else { 52 | return err 53 | } 54 | } 55 | if successLableNode != nil { 56 | return successLableNode.Handle(msg) 57 | } 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/tool/conv.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | ) 7 | 8 | // SnakeString snake string, XxYy to xx_yy , XxYY to xx_y_y 9 | func SnakeString(s string) string { 10 | data := make([]byte, 0, len(s)*2) 11 | j := false 12 | num := len(s) 13 | for i := 0; i < num; i++ { 14 | d := s[i] 15 | if i > 0 && d >= 'A' && d <= 'Z' && j { 16 | data = append(data, '_') 17 | } 18 | if d != '_' { 19 | j = true 20 | } 21 | data = append(data, d) 22 | } 23 | return strings.ToLower(string(data)) 24 | } 25 | 26 | // CamelString camel string, xx_yy to XxYy 27 | func CamelString(s string) string { 28 | data := make([]byte, 0, len(s)) 29 | flag, num := true, len(s)-1 30 | for i := 0; i <= num; i++ { 31 | d := s[i] 32 | if d == '_' { 33 | flag = true 34 | continue 35 | } else if flag { 36 | if d >= 'a' && d <= 'z' { 37 | d = d - 32 38 | } 39 | flag = false 40 | } 41 | data = append(data, d) 42 | } 43 | return string(data) 44 | } 45 | 46 | func FirstLowCamelString(s string) string { 47 | data := make([]byte, 0, len(s)) 48 | flag, num := true, len(s)-1 49 | for i := 0; i <= num; i++ { 50 | d := s[i] 51 | if d == '_' { 52 | flag = true 53 | continue 54 | } else if flag { 55 | if d >= 'a' && d <= 'z' { 56 | d = d - 32 57 | } 58 | flag = false 59 | } 60 | data = append(data, d) 61 | } 62 | if len(data) > 0 && data[0] >= 65 && data[0] <= 90 { 63 | data[0] = data[0] + 32 64 | } 65 | return string(data) 66 | } 67 | 68 | func MapToStruct(m map[string]interface{}, s interface{}) error { 69 | data, err := json.Marshal(m) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | err = json.Unmarshal(data, s) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /pkg/tool/device.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "github.com/google/uuid" 8 | "pandax/pkg/global" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type DeviceAuth struct { 14 | User string `json:"user"` 15 | DeviceId string `json:"device_id"` 16 | DeviceType string `json:"device_type"` 17 | ProductId string `json:"product_id"` 18 | RuleChainId string `json:"rule_chain_id"` 19 | Name string `json:"name"` 20 | Token string `json:"token"` 21 | CreatedAt int64 `json:"created_at"` 22 | ExpiredAt int64 `json:"expired_at"` 23 | } 24 | 25 | func (entity *DeviceAuth) CreateDeviceToken() (err error) { 26 | 27 | return nil 28 | } 29 | 30 | func (entity *DeviceAuth) GetDeviceToken(key string) error { 31 | if err := global.RedisDb.Get(key, entity); err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | 37 | func (token *DeviceAuth) MD5ID() string { 38 | buf := bytes.NewBufferString(token.DeviceId) 39 | buf.WriteString(token.DeviceType) 40 | buf.WriteString(strconv.FormatInt(token.CreatedAt, 10)) 41 | access := base64.URLEncoding.EncodeToString([]byte(uuid.NewMD5(uuid.Must(uuid.NewRandom()), buf.Bytes()).String())) 42 | access = strings.TrimRight(access, "=") 43 | return access 44 | } 45 | func (token *DeviceAuth) GetMarshal() string { 46 | marshal, _ := json.Marshal(*token) 47 | return string(marshal) 48 | } 49 | 50 | func (token *DeviceAuth) GetUnMarshal(data []byte) error { 51 | return json.Unmarshal(data, token) 52 | } 53 | 54 | // 序列化 55 | func (m *DeviceAuth) MarshalBinary() (data []byte, err error) { 56 | return json.Marshal(m) 57 | } 58 | 59 | // 反序列化 60 | func (m *DeviceAuth) UnmarshalBinary(data []byte) error { 61 | return json.Unmarshal(data, m) 62 | } 63 | -------------------------------------------------------------------------------- /pkg/config/db.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "fmt" 4 | 5 | type Mysql struct { 6 | Host string `mapstructure:"host" json:"host" yaml:"host"` 7 | Config string `mapstructure:"config" json:"config" yaml:"config"` 8 | Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"` 9 | Username string `mapstructure:"username" json:"username" yaml:"username"` 10 | Password string `mapstructure:"password" json:"password" yaml:"password"` 11 | MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` 12 | MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` 13 | LogMode bool `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"` 14 | LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"` 15 | } 16 | 17 | func (m *Mysql) Dsn() string { 18 | return m.Username + ":" + m.Password + "@tcp(" + m.Host + ")/" + m.Dbname + "?" + m.Config 19 | } 20 | 21 | type Postgresql struct { 22 | Host string `mapstructure:"host" json:"host" yaml:"host"` 23 | Port int `mapstructure:"port" json:"port" yaml:"port"` 24 | Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"` 25 | Username string `mapstructure:"username" json:"username" yaml:"username"` 26 | Password string `mapstructure:"password" json:"password" yaml:"password"` 27 | MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` 28 | MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` 29 | } 30 | 31 | func (m *Postgresql) PgDsn() string { 32 | return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", m.Host, m.Port, m.Username, m.Password, m.Dbname) 33 | } 34 | -------------------------------------------------------------------------------- /resource/default.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpgIBAAKCAQEA0CsawvEZl42Vf+0BlTuZ3Dp10yW8Oty1tjimxUj3s0WPeKil 3 | 6+TehnQELS8vGJfek+yT99nyrt+bkRmg1kxZ57FtQFEuthG4OQZoaMDUz6Ab+8P1 4 | PQ9VH0XimnnYabxztJiQjl8HdJt6N4WP35kGlcul7qQ+Qc7iwjhSadfAhXVycqVI 5 | cGQyHiPPfbmYRjueAIC4czmMUxwFKCwjepGYkwzWuGkpMD0hg/SIXpFJE2dcqYPR 6 | 2nCah1gxZZG00lHU1X2pehNmmgeHRkB5S7mrsCdyyV/33SAYk6T6PT7dOqY54bfn 7 | h3C0k+T7IzvKTXKG76eG63STmxVa6luVoKMvxwIDAQABAoIBAQCI2Y2CUpYMd9us 8 | edbskH4ZtaT35nrUB3y+Cog4cjvE8xnarKRHa/KOWX7VZYuEk3KTtJeh/Pn51K6k 9 | uUBvIUqJcq7r9XLL5uJBOuEw3HQK+qrq3GxAc+/12y+Zdji7alR2iUWfEwIHup6i 10 | GX/38tXNbE/tjrQO9z9Dh1tGkbvS/66tPn/T/oMxsRvZB6mCjB7yuOlEIwYTomYB 11 | pUFemELt8T5RtfxRa8T1VoITbfuj7zvecqlThW0H8UizsFxvrOCUaga7jtsJOCHo 12 | bcW5WvWwazoOfQ2BGpksKkBDf1N6pj85e4kOoYcVG9UN03ZwDvAGfQPWUlHB4YzW 13 | PybMwIQBAoGBAPfuOQ+ukVmkiEKj6wCBe3Z0pYeNqBGec9aj62bKFh79BCE3ZopS 14 | 7JtGs8VfBKkBAaOy+MDuvJ2fvRNRtHT4BYe1U6ZRsmVFqHScACOaO/7TR0tz0ihL 15 | 0QLCkbSwsXExG6bYbwP4jMHkhHArT7Hy8WXvup8PffjSiEs1A1uGvYSBAoGBANbx 16 | lHo+39nsc1OO8TUAWZChIQUib2hFIwzQYngSzINdfXQaGFT/omOsudAtfdjvp+qO 17 | Tr7WpwgFEFveDFsdJfZ2Kc2x9a3ty7IYIWaAjK2ghkAKz3Tt4gClreB6qG2SBycP 18 | 4C2ImbY6hMaFHz3ENtTEzzTMdD1ByxQVMvoem3BHAoGBAJdaTmtMXl8jGivUdXnx 19 | kbVWsFZ4G8nluUGm/+XYKHjybLr6XxbCWL7SApzSzL1/Z8jPURw2od53za0li8x8 20 | PKQEBfTamtVIGPZW5Z7WYRnHURa2tezzm7zbmqd71lcLa54HMn5yFTuojVEMn7I6 21 | ZTOdjYfcpUJpA9slmc8eCkQBAoGBAJEIbxRRaoBEQMkH8Y++zbB+WKZ7RssHo4/Y 22 | 6Ch3HtIg+i6mEPcBitRQzww+NeV0SExHe7Dfa9NIf3JNkO7F60CzGJ/3zXtvsftY 23 | tujQIpxhbVS3NqaCgPXI1VtbyFwupW7hEnYG7xj7wW2mk578z7afmeTZdDGFPH8v 24 | krccgeuvAoGBAPAwiqbZlXNx+ueI1B3T8VpXnG0ozKxG+l5B71kssZWa7xcv9yRd 25 | c15l2PSXNtnoT/mBID7+dqQOmfYxsDHAkUdd/BrxhXtdi9FR3AfHSEQz+VKsogAD 26 | uLyRd7jWTYqqGa2UToF/CBV+c6QyMB+6pzFNk5DmUEm4Gd6jcHDITYeI 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/external_nats_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/nats-io/nats.go" 5 | "github.com/sirupsen/logrus" 6 | "pandax/pkg/rule_engine/message" 7 | ) 8 | 9 | type externalNatsNode struct { 10 | bareNode 11 | Url string `json:"url"` 12 | Subject string `json:"subject"` 13 | Body string 14 | client *nats.Conn 15 | } 16 | 17 | type externalNatsNodeFactory struct{} 18 | 19 | func (f externalNatsNodeFactory) Name() string { return "NatsNode" } 20 | func (f externalNatsNodeFactory) Category() string { return NODE_CATEGORY_EXTERNAL } 21 | func (f externalNatsNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 22 | func (f externalNatsNodeFactory) Create(id string, meta Metadata) (Node, error) { 23 | node := &externalNatsNode{ 24 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 25 | } 26 | _, err := decodePath(meta, node) 27 | if err != nil { 28 | return node, err 29 | } 30 | connect, err := nats.Connect(node.Url) 31 | if err != nil { 32 | return node, err 33 | } 34 | node.client = connect 35 | return node, nil 36 | } 37 | 38 | func (n *externalNatsNode) Handle(msg *message.Message) error { 39 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 40 | defer n.client.Close() 41 | successLabelNode := n.GetLinkedNode("Success") 42 | failureLabelNode := n.GetLinkedNode("Failure") 43 | template, err := ParseTemplate(n.Body, msg.GetAllMap()) 44 | if err != nil { 45 | return err 46 | } 47 | err = n.client.Publish(n.Subject, []byte(template)) 48 | if err != nil { 49 | if failureLabelNode != nil { 50 | return failureLabelNode.Handle(msg) 51 | } else { 52 | return err 53 | } 54 | } 55 | if successLabelNode != nil { 56 | return successLabelNode.Handle(msg) 57 | } 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /resource/template/go/entity.template: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // 生成日期:{{.CreatedAt}} 3 | // 生成路径: apps/{{.PackageName}}/entity/{{.TableName}}.go 4 | // 生成人:{{.FunctionAuthor}} 5 | // ========================================================================== 6 | package entity 7 | 8 | {{$hasGTime:=false}} 9 | {{- range $index, $column := .Columns -}} 10 | {{- if eq $column.GoType "Time" -}} 11 | {{$hasGTime = true}} 12 | {{- end -}} 13 | {{- end -}} 14 | {{if $hasGTime -}}import "time"{{- end }} 15 | 16 | type {{.ClassName}} struct { 17 | {{- range $index, $column := .Columns}} 18 | {{- if eq $column.IsPk "1" }} 19 | {{$column.GoField}} {{if eq $column.GoType "Time"}}time.Time{{else}}{{$column.GoType}}{{end}} `gorm:"primary_key;{{if eq $column.IsIncrement "1"}}AUTO_INCREMENT{{end}}" json:"{{$column.JsonField}}"` // {{$column.ColumnComment}} 20 | {{- else if ne $column.LinkTableName ""}} 21 | {{$column.GoField}} {{if eq $column.GoType "Time"}}time.Time{{else}}{{$column.GoType}}{{end}} `gorm:"{{$column.ColumnName}};type:{{$column.ColumnType}};comment:{{$column.ColumnComment}}" json:"{{$column.JsonField}}"` 22 | {{$column.LinkTableClass}} {{$column.LinkTableClass}} `json:"{{$column.LinkTablePackage}}" gorm:"foreignKey:{{$column.LinkLabelName}};references:{{$column.GoField}};"` 23 | {{- else }} 24 | {{$column.GoField}} {{if eq $column.GoType "Time"}}time.Time{{else}}{{$column.GoType}}{{end}} `gorm:"{{$column.ColumnName}};type:{{$column.ColumnType}};comment:{{$column.ColumnComment}}" json:"{{$column.JsonField}}" {{if eq $column.IsRequired "1"}}binding:"required"{{- end }}` // {{$column.ColumnComment}} 25 | {{- end -}} 26 | {{end}} 27 | } 28 | 29 | func ({{.ClassName}}) TableName() string { 30 | return "{{.TableName}}" 31 | } 32 | -------------------------------------------------------------------------------- /apps/system/api/vo/systemVo.go: -------------------------------------------------------------------------------- 1 | package vo 2 | 3 | import "pandax/apps/system/entity" 4 | 5 | /** 6 | * @Description 7 | * @Author 熊猫 8 | * @Date 2022/8/4 15:25 9 | **/ 10 | 11 | type DeptTreeVo struct { 12 | Depts []entity.DeptLable `json:"depts"` 13 | CheckedKeys []int64 `json:"checkedKeys"` 14 | } 15 | 16 | type MenuTreeVo struct { 17 | Menus []entity.MenuLable `json:"menus"` 18 | CheckedKeys []int64 `json:"checkedKeys"` 19 | } 20 | 21 | type MenuPermisVo struct { 22 | Menus []RouterVo `json:"menus"` 23 | Permissions []string `json:"permissions"` 24 | } 25 | 26 | type CaptchaVo struct { 27 | Base64Captcha string `json:"base64Captcha"` 28 | CaptchaId string `json:"captchaId"` 29 | } 30 | 31 | type TokenVo struct { 32 | Token string `json:"token"` 33 | Expire int64 `json:"expire"` 34 | } 35 | 36 | type AuthVo struct { 37 | User entity.SysUserView `json:"user"` 38 | Role entity.SysRole `json:"role"` 39 | Permissions []string `json:"permissions"` 40 | Menus []RouterVo `json:"menus"` 41 | } 42 | 43 | type UserProfileVo struct { 44 | Data any `json:"data"` 45 | PostIds []int64 `json:"postIds"` 46 | RoleIds []int64 `json:"roleIds"` 47 | Roles []entity.SysRole `json:"roles"` 48 | Posts []entity.SysPost `json:"posts"` 49 | Dept []entity.SysDept `json:"dept"` 50 | } 51 | 52 | type UserVo struct { 53 | Data any `json:"data"` 54 | PostIds string `json:"postIds"` 55 | RoleIds string `json:"roleIds"` 56 | Roles []entity.SysRole `json:"roles"` 57 | Posts []entity.SysPost `json:"posts"` 58 | Depts []entity.SysDept `json:"depts"` 59 | } 60 | 61 | type UserRolePost struct { 62 | Roles []entity.SysRole `json:"roles"` 63 | Posts []entity.SysPost `json:"posts"` 64 | } 65 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/factory.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | NODE_CATEGORY_FILTER = "filter" 9 | NODE_CATEGORY_ACTION = "action" 10 | NODE_CATEGORY_ENRICHMENT = "enrichment" 11 | NODE_CATEGORY_TRANSFORM = "transform" 12 | NODE_CATEGORY_EXTERNAL = "external" 13 | NODE_CATEGORY_OTHERS = "others" 14 | NODE_CATEGORY_FLOWS = "flows" 15 | ) 16 | 17 | type Factory interface { 18 | Name() string 19 | Category() string 20 | Labels() []string 21 | Create(id string, meta Metadata) (Node, error) 22 | } 23 | 24 | var ( 25 | // 所有节点对应关系 26 | allNodeFactories map[string]Factory = make(map[string]Factory) 27 | 28 | // 所有节点的类型MAP 29 | allNodeCategories map[string][]map[string]interface{} = make(map[string][]map[string]interface{}) 30 | allCategories []map[string]interface{} = make([]map[string]interface{}, 0) 31 | ) 32 | 33 | func RegisterFactory(f Factory) { 34 | allNodeFactories[f.Name()] = f 35 | 36 | if allNodeCategories[f.Category()] == nil { 37 | allNodeCategories[f.Category()] = []map[string]interface{}{} 38 | } 39 | allNodeCategories[f.Category()] = append(allNodeCategories[f.Category()], map[string]interface{}{"name": f.Name(), "labels": f.Labels()}) 40 | allCategories = append(allCategories, map[string]interface{}{"name": f.Name(), "labels": f.Labels()}) 41 | } 42 | 43 | // NewNode is the only way to create a new node 44 | func NewNode(nodeType string, id string, meta Metadata) (Node, error) { 45 | if f, found := allNodeFactories[nodeType]; found { 46 | return f.Create(id, meta) 47 | } 48 | return nil, fmt.Errorf("invalid node type '%s'", nodeType) 49 | } 50 | 51 | // GetCategoryNodes 获取所有分类节点 52 | func GetCategoryNodes() map[string][]map[string]interface{} { return allNodeCategories } 53 | 54 | func GetCategory() []map[string]interface{} { return allCategories } 55 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/transform_delete_key_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | "strings" 7 | ) 8 | 9 | type transformDeleteKeyNode struct { 10 | bareNode 11 | FormType string `json:"formType" yaml:"formType"` //msg metadata 12 | Keys string `json:"keys" yaml:"keys"` 13 | } 14 | type transformDeleteKeyNodeFactory struct{} 15 | 16 | func (f transformDeleteKeyNodeFactory) Name() string { return "DeleteKeyNode" } 17 | func (f transformDeleteKeyNodeFactory) Category() string { return NODE_CATEGORY_TRANSFORM } 18 | func (f transformDeleteKeyNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 19 | func (f transformDeleteKeyNodeFactory) Create(id string, meta Metadata) (Node, error) { 20 | node := &transformDeleteKeyNode{ 21 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 22 | } 23 | return decodePath(meta, node) 24 | } 25 | 26 | func (n *transformDeleteKeyNode) Handle(msg *message.Message) error { 27 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 28 | 29 | successLabelNode := n.GetLinkedNode("Success") 30 | failureLabelNode := n.GetLinkedNode("Failure") 31 | keys := strings.Split(n.Keys, ",") 32 | if n.FormType == "msg" { 33 | data := msg.Msg 34 | for _, key := range keys { 35 | if _, found := data[key]; found { 36 | delete(data, key) 37 | msg.Msg = data 38 | } 39 | } 40 | } else if n.FormType == "metadata" { 41 | data := msg.Metadata 42 | for _, key := range keys { 43 | if data.GetValue(key) != nil { 44 | delete(data, key) 45 | msg.Metadata = data 46 | } 47 | } 48 | } else { 49 | if failureLabelNode != nil { 50 | return failureLabelNode.Handle(msg) 51 | } 52 | } 53 | if successLabelNode != nil { 54 | return successLabelNode.Handle(msg) 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /apps/job/router/job_log.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | restfulspec "github.com/emicklei/go-restful-openapi/v2" 7 | "github.com/emicklei/go-restful/v3" 8 | "pandax/apps/job/api" 9 | "pandax/apps/job/services" 10 | ) 11 | 12 | func InitJobLogRouter(container *restful.Container) { 13 | // Job日志 14 | s := &api.JobLogApi{ 15 | JobLogApp: services.JobLogModelDao, 16 | } 17 | 18 | ws := new(restful.WebService) 19 | ws.Path("/job/log").Produces(restful.MIME_JSON) 20 | tags := []string{"JobLog"} 21 | 22 | ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { 23 | restfulx.NewReqCtx(request, response).WithLog("获取操作日志列表").Handle(s.GetJobLogList) 24 | }). 25 | Doc("获取操作日志列表"). 26 | Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). 27 | Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). 28 | Param(ws.QueryParameter("status", "status").DataType("string")). 29 | Param(ws.QueryParameter("name", "name").DataType("string")). 30 | Metadata(restfulspec.KeyOpenAPITags, tags). 31 | Writes(model.ResultPage{}). 32 | Returns(200, "OK", model.ResultPage{})) 33 | 34 | ws.Route(ws.DELETE("/{logId}").To(func(request *restful.Request, response *restful.Response) { 35 | restfulx.NewReqCtx(request, response).WithLog("删除操作日志信息").Handle(s.DeleteJobLog) 36 | }). 37 | Doc("删除操作日志信息"). 38 | Metadata(restfulspec.KeyOpenAPITags, tags). 39 | Param(ws.PathParameter("logId", "多id 1,2,3").DataType("string"))) 40 | 41 | ws.Route(ws.DELETE("/all").To(func(request *restful.Request, response *restful.Response) { 42 | restfulx.NewReqCtx(request, response).WithLog("清空操作日志信息").Handle(s.DeleteAll) 43 | }). 44 | Doc("清空操作日志信息"). 45 | Metadata(restfulspec.KeyOpenAPITags, tags)) 46 | 47 | container.Add(ws) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/action_clear_alarm_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/sirupsen/logrus" 6 | "log" 7 | "pandax/apps/device/services" 8 | "pandax/pkg/global" 9 | "pandax/pkg/rule_engine/message" 10 | ) 11 | 12 | const ClearAlarmNodeName = "ClearAlarmNode" 13 | 14 | type clearAlarmNodeFactory struct{} 15 | 16 | type clearAlarmNode struct { 17 | bareNode 18 | Script string `json:"script" yaml:"script"` 19 | AlarmType string `json:"alarmType" yaml:"alarmType"` 20 | } 21 | 22 | func (f clearAlarmNodeFactory) Name() string { return ClearAlarmNodeName } 23 | func (f clearAlarmNodeFactory) Category() string { return NODE_CATEGORY_ACTION } 24 | func (f clearAlarmNodeFactory) Labels() []string { return []string{"Cleared", "Failure"} } 25 | func (f clearAlarmNodeFactory) Create(id string, meta Metadata) (Node, error) { 26 | node := &clearAlarmNode{ 27 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 28 | } 29 | return decodePath(meta, node) 30 | } 31 | 32 | func (n *clearAlarmNode) Handle(msg *message.Message) error { 33 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 34 | cleared := n.GetLinkedNode("Cleared") 35 | failure := n.GetLinkedNode("Failure") 36 | 37 | alarm := services.DeviceAlarmModelDao.FindOneByType(msg.Metadata.GetValue("deviceId").(string), n.AlarmType, "0") 38 | if alarm.DeviceId != "" { 39 | log.Println("清除告警") 40 | alarm.State = global.CLEARED 41 | marshal, _ := json.Marshal(msg.Msg) 42 | alarm.Details = string(marshal) 43 | err := services.DeviceAlarmModelDao.Update(*alarm) 44 | if err != nil { 45 | if failure != nil { 46 | return failure.Handle(msg) 47 | } 48 | } else { 49 | if cleared != nil { 50 | return cleared.Handle(msg) 51 | } 52 | } 53 | } else { 54 | if failure != nil { 55 | return failure.Handle(msg) 56 | } 57 | } 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /apps/system/router/upload.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/restfulx" 5 | restfulspec "github.com/emicklei/go-restful-openapi/v2" 6 | "github.com/emicklei/go-restful/v3" 7 | "pandax/apps/system/api" 8 | ) 9 | 10 | func InitUploadRouter(container *restful.Container) { 11 | s := &api.UploadApi{} 12 | ws := new(restful.WebService) 13 | ws.Path("/upload").Produces(restful.MIME_JSON) 14 | tags := []string{"upload"} 15 | 16 | ws.Route(ws.POST("/up").To(func(request *restful.Request, response *restful.Response) { 17 | restfulx.NewReqCtx(request, response).WithLog("上传图片").Handle(s.UploadImage) 18 | }). 19 | Doc("上传图片"). 20 | Param(ws.FormParameter("imagefile", "文件")). 21 | Metadata(restfulspec.KeyOpenAPITags, tags). 22 | Returns(200, "OK", map[string]string{})) 23 | 24 | ws.Route(ws.POST("/up/oss").To(func(request *restful.Request, response *restful.Response) { 25 | restfulx.NewReqCtx(request, response).WithLog("上传图片").Handle(s.UplaodToOss) 26 | }). 27 | Doc("上传图片到oss"). 28 | Param(ws.FormParameter("imagefile", "文件")). 29 | Metadata(restfulspec.KeyOpenAPITags, tags). 30 | Returns(200, "OK", map[string]string{})) 31 | 32 | ws.Route(ws.GET("/get/{subpath}").To(func(request *restful.Request, response *restful.Response) { 33 | restfulx.NewReqCtx(request, response).WithNeedToken(false).WithNeedCasbin(false).WithLog("获取图片").Handle(s.GetImage) 34 | }). 35 | Doc("获取图片"). 36 | Param(ws.PathParameter("subpath", "文件名")). 37 | Metadata(restfulspec.KeyOpenAPITags, tags)) 38 | 39 | ws.Route(ws.DELETE("/delete").To(func(request *restful.Request, response *restful.Response) { 40 | restfulx.NewReqCtx(request, response).WithLog("删除图片").Handle(s.DeleteImage) 41 | }). 42 | Doc("删除图片"). 43 | Metadata(restfulspec.KeyOpenAPITags, tags). 44 | Param(ws.QueryParameter("fileName", "文件名称").DataType("string"))) 45 | 46 | container.Add(ws) 47 | } 48 | -------------------------------------------------------------------------------- /apps/develop/router/gen.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/restfulx" 5 | restfulspec "github.com/emicklei/go-restful-openapi/v2" 6 | "github.com/emicklei/go-restful/v3" 7 | "pandax/apps/develop/api" 8 | "pandax/apps/develop/services" 9 | ) 10 | 11 | func InitGenRouter(container *restful.Container) { 12 | 13 | // 登录日志 14 | s := &api.GenApi{ 15 | GenTableApp: services.DevGenTableModelDao, 16 | } 17 | 18 | ws := new(restful.WebService) 19 | ws.Path("/develop/code/gen").Produces(restful.MIME_JSON) 20 | tags := []string{"codegen"} 21 | 22 | ws.Route(ws.GET("/preview/{tableId}").To(func(request *restful.Request, response *restful.Response) { 23 | restfulx.NewReqCtx(request, response).WithLog("获取生成代码视图").Handle(s.Preview) 24 | }). 25 | Doc("获取生成代码视图"). 26 | Param(ws.PathParameter("tableId", "Id").DataType("int").DefaultValue("1")). 27 | Metadata(restfulspec.KeyOpenAPITags, tags). 28 | Returns(200, "OK", map[string]any{}). 29 | Returns(404, "Not Found", nil)) 30 | 31 | ws.Route(ws.GET("/code/{tableId}").To(func(request *restful.Request, response *restful.Response) { 32 | restfulx.NewReqCtx(request, response).WithLog("生成代码").Handle(s.GenCode) 33 | }). 34 | Doc("生成代码"). 35 | Param(ws.PathParameter("tableId", "Id").DataType("int").DefaultValue("1")). 36 | Metadata(restfulspec.KeyOpenAPITags, tags). 37 | Returns(200, "OK", map[string]any{}). 38 | Returns(404, "Not Found", nil)) 39 | 40 | ws.Route(ws.GET("/configure/{tableId}").To(func(request *restful.Request, response *restful.Response) { 41 | restfulx.NewReqCtx(request, response).WithLog("生成配置").Handle(s.GenConfigure) 42 | }). 43 | Doc("生成配置"). 44 | Param(ws.PathParameter("tableId", "Id").DataType("int").DefaultValue("1")). 45 | Metadata(restfulspec.KeyOpenAPITags, tags). 46 | Returns(200, "OK", map[string]any{}). 47 | Returns(404, "Not Found", nil)) 48 | 49 | container.Add(ws) 50 | } 51 | -------------------------------------------------------------------------------- /apps/device/api/product_ota.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ========================================================================== 4 | import ( 5 | "github.com/PandaXGO/PandaKit/model" 6 | "github.com/PandaXGO/PandaKit/restfulx" 7 | "github.com/kakuilan/kgo" 8 | "strings" 9 | 10 | "pandax/apps/device/entity" 11 | "pandax/apps/device/services" 12 | ) 13 | 14 | type ProductOtaApi struct { 15 | ProductOtaApp services.ProductOtaModel 16 | } 17 | 18 | // GetProductOtaList Ota列表数据 19 | func (p *ProductOtaApi) GetProductOtaList(rc *restfulx.ReqCtx) { 20 | data := entity.ProductOta{} 21 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 22 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 23 | data.Name = restfulx.QueryParam(rc, "name") 24 | data.Pid = restfulx.QueryParam(rc, "pid") 25 | 26 | list, total := p.ProductOtaApp.FindListPage(pageNum, pageSize, data) 27 | 28 | rc.ResData = model.ResultPage{ 29 | Total: total, 30 | PageNum: int64(pageNum), 31 | PageSize: int64(pageNum), 32 | Data: list, 33 | } 34 | } 35 | 36 | // GetProductOta 获取Ota 37 | func (p *ProductOtaApi) GetProductOta(rc *restfulx.ReqCtx) { 38 | id := restfulx.PathParam(rc, "id") 39 | rc.ResData = p.ProductOtaApp.FindOne(id) 40 | } 41 | 42 | // InsertProductOta 添加Ota 43 | func (p *ProductOtaApi) InsertProductOta(rc *restfulx.ReqCtx) { 44 | var data entity.ProductOta 45 | restfulx.BindJsonAndValid(rc, &data) 46 | data.Id = kgo.KStr.Uniqid("ota_") 47 | p.ProductOtaApp.Insert(data) 48 | } 49 | 50 | // UpdateProductOta 修改Ota 51 | func (p *ProductOtaApi) UpdateProductOta(rc *restfulx.ReqCtx) { 52 | var data entity.ProductOta 53 | restfulx.BindJsonAndValid(rc, &data) 54 | 55 | p.ProductOtaApp.Update(data) 56 | } 57 | 58 | // DeleteProductOta 删除Ota 59 | func (p *ProductOtaApi) DeleteProductOta(rc *restfulx.ReqCtx) { 60 | id := restfulx.PathParam(rc, "id") 61 | ids := strings.Split(id, ",") 62 | p.ProductOtaApp.Delete(ids) 63 | } 64 | -------------------------------------------------------------------------------- /apps/device/entity/device_exp.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | // 说明 6 | // 设备上报属性,遥测,连接事件存储到时序数据库中 7 | // 其他存在MySQL中 8 | 9 | // DeviceAlarm 设备告警表 需要更改告警状态不能存在时序数据库中 10 | type DeviceAlarm struct { 11 | Id string `json:"id" gorm:"primary_key;"` 12 | Time time.Time `gorm:"comment:告警时间" json:"time"` 13 | Name string `gorm:"type:varchar(64);comment:告警名称" json:"name"` 14 | DeviceId string `gorm:"type:varchar(64);comment:所属设备" json:"deviceId"` 15 | ProductId string `gorm:"type:varchar(64);comment:所属产品" json:"productId"` 16 | Type string `gorm:"type:varchar(64);comment:告警类型" json:"type"` 17 | Level string `gorm:"type:varchar(64);comment:告警级别" json:"level"` // 危险 重要 次要 警告 不确定 18 | State string `gorm:"type:varchar(1);comment:告警状态" json:"state"` // 告警中 0 已确认 1 已清除 2 已关闭 3 19 | Details string `gorm:"type:varchar(255);comment:告警详情" json:"details"` 20 | } 21 | 22 | type DeviceCmdLog struct { 23 | Id string `json:"id" gorm:"primary_key;"` 24 | DeviceId string `gorm:"type:varchar(64);comment:所属设备" json:"deviceId"` 25 | CmdName string `gorm:"type:varchar(64);comment:命令名称" json:"cmdName"` 26 | CmdContent string `gorm:"type:longtext;comment:命令内容" json:"cmdContent"` 27 | State string `gorm:"type:varchar(1);comment:命令状态" json:"state"` 28 | Type string `gorm:"type:varchar(1);comment:命令类型" json:"type"` // 0 自定义 1 命令 29 | ResponseContent string `gorm:"type:longtext;comment:响应内容" json:"responseContent"` 30 | RequestTime string `gorm:"comment:命令下发时间" json:"requestTime"` 31 | ResponseTime string `gorm:"comment:命令响应时间" json:"responseTime"` 32 | } 33 | 34 | func (DeviceCmdLog) TableName() string { 35 | return "device_cmd_log" 36 | } 37 | 38 | // DeviceStatistics 设备统计表 39 | // 每小时、每天、每周、每月的平均值、最大值、最小值等。可以使用定时任务或实时计算框架来更新数据统计表。 40 | type DeviceStatistics struct { 41 | Time time.Time `gorm:"comment:时间" json:"time"` 42 | } 43 | -------------------------------------------------------------------------------- /apps/system/api/notice.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | "github.com/PandaXGO/PandaKit/utils" 7 | "pandax/apps/system/entity" 8 | "pandax/apps/system/services" 9 | "strings" 10 | ) 11 | 12 | type NoticeApi struct { 13 | DeptApp services.SysDeptModel 14 | NoticeApp services.SysNoticeModel 15 | } 16 | 17 | // GetNoticeList 通知列表数据 18 | func (p *NoticeApi) GetNoticeList(rc *restfulx.ReqCtx) { 19 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 20 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 21 | noticeType := restfulx.QueryParam(rc, "noticeType") 22 | title := restfulx.QueryParam(rc, "title") 23 | 24 | // 获取部门的子部门id 25 | one := p.DeptApp.FindOne(rc.LoginAccount.DeptId) 26 | split := strings.Split(strings.Trim(one.DeptPath, "/"), "/") 27 | // 获取所有父部门id 28 | ids := utils.DeptPCIds(split, rc.LoginAccount.DeptId, true) 29 | notice := entity.SysNotice{NoticeType: noticeType, Title: title, DeptIds: ids} 30 | list, total := p.NoticeApp.FindListPage(pageNum, pageSize, notice) 31 | 32 | rc.ResData = model.ResultPage{ 33 | Total: total, 34 | PageNum: int64(pageNum), 35 | PageSize: int64(pageNum), 36 | Data: list, 37 | } 38 | } 39 | 40 | // InsertNotice 添加通知 41 | func (p *NoticeApi) InsertNotice(rc *restfulx.ReqCtx) { 42 | var notice entity.SysNotice 43 | restfulx.BindJsonAndValid(rc, ¬ice) 44 | notice.UserName = rc.LoginAccount.UserName 45 | p.NoticeApp.Insert(notice) 46 | } 47 | 48 | // UpdateNotice 修改通知 49 | func (p *NoticeApi) UpdateNotice(rc *restfulx.ReqCtx) { 50 | var notice entity.SysNotice 51 | restfulx.BindJsonAndValid(rc, ¬ice) 52 | 53 | p.NoticeApp.Update(notice) 54 | } 55 | 56 | // DeleteNotice 删除通知 57 | func (p *NoticeApi) DeleteNotice(rc *restfulx.ReqCtx) { 58 | noticeId := restfulx.PathParam(rc, "noticeId") 59 | noticeIds := utils.IdsStrToIdsIntGroup(noticeId) 60 | p.NoticeApp.Delete(noticeIds) 61 | } 62 | -------------------------------------------------------------------------------- /apps/system/entity/menu.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/PandaXGO/PandaKit/model" 4 | 5 | type SysMenu struct { 6 | MenuId int64 `json:"menuId" gorm:"primary_key;AUTO_INCREMENT"` 7 | MenuName string `json:"menuName" gorm:"type:varchar(128);"` 8 | Title string `json:"title" gorm:"type:varchar(64);"` 9 | ParentId int64 `json:"parentId" gorm:"type:int;"` 10 | Sort int64 `json:"sort" gorm:"type:int;"` 11 | Icon string `json:"icon" gorm:"type:varchar(128);"` 12 | Path string `json:"path" gorm:"type:varchar(128);"` 13 | Component string `json:"component" gorm:"type:varchar(255);"` // 组件路径 14 | IsIframe string `json:"isIframe" gorm:"type:varchar(1);"` //是否为内嵌 15 | IsLink string `json:"isLink" gorm:"type:varchar(255);"` //是否超链接菜单 16 | MenuType string `json:"menuType" gorm:"type:varchar(1);"` //菜单类型(M目录 C菜单 F按钮) 17 | IsHide string `json:"isHide" gorm:"type:varchar(1);"` //显示状态(0显示 1隐藏) 18 | IsKeepAlive string `json:"isKeepAlive" gorm:"type:varchar(1);"` //是否缓存组件状态(0是 1否) 19 | IsAffix string `json:"isAffix" gorm:"type:varchar(1);"` //是否固定在 tagsView 栏上(0是 1否) 20 | Permission string `json:"permission" gorm:"type:varchar(32);"` //权限标识 21 | Status string `json:"status" gorm:"type:varchar(1);` // 菜单状态(0正常 1停用) 22 | CreateBy string `json:"createBy" gorm:"type:varchar(128);"` 23 | UpdateBy string `json:"updateBy" gorm:"type:varchar(128);"` 24 | Remark string `json:"remark" gorm:"type:varchar(256);` // 备注 25 | Children []SysMenu `json:"children" gorm:"-"` 26 | model.BaseModel 27 | } 28 | 29 | type MenuLable struct { 30 | MenuId int64 `json:"menuId" gorm:"-"` 31 | MenuName string `json:"menuName" gorm:"-"` 32 | Children []MenuLable `json:"children" gorm:"-"` 33 | } 34 | 35 | type MenuRole struct { 36 | SysMenu 37 | IsSelect bool `json:"is_select" gorm:"-"` 38 | } 39 | -------------------------------------------------------------------------------- /pkg/tool/base.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "log" 5 | "reflect" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | func ToCamelCase(s string) string { 11 | re := regexp.MustCompile(`[_\W]+`) 12 | words := re.Split(s, -1) 13 | for i := range words { 14 | if i != 0 { 15 | words[i] = strings.Title(words[i]) 16 | } 17 | } 18 | return strings.Join(words, "") 19 | } 20 | 21 | func RegexpKey(str string) []string { 22 | // 定义正则表达式 23 | re := regexp.MustCompile(`\${([^}]+)}`) 24 | matches := re.FindAllStringSubmatch(str, -1) 25 | // 提取匹配项的内容 26 | var results []string 27 | for _, match := range matches { 28 | if len(match) >= 2 { 29 | results = append(results, match[1]) 30 | } 31 | } 32 | return results 33 | } 34 | 35 | func RegexpGetSql(str string) string { 36 | // 定义正则表达式 37 | re := regexp.MustCompile(`\${([^}]+)}`) 38 | return re.ReplaceAllString(str, "?") 39 | } 40 | 41 | func GetStructKeys(obj interface{}) []string { 42 | val := reflect.ValueOf(obj) 43 | typ := val.Type() 44 | 45 | keys := make([]string, 0, typ.NumField()) 46 | 47 | for i := 0; i < typ.NumField(); i++ { 48 | field := typ.Field(i) 49 | keys = append(keys, field.Name) 50 | } 51 | 52 | return keys 53 | } 54 | 55 | func GetMapKeys(obj map[string]interface{}) []string { 56 | keys := make([]string, 0, len(obj)) 57 | 58 | for key := range obj { 59 | keys = append(keys, key) 60 | } 61 | 62 | return keys 63 | } 64 | 65 | func CheckInterfaceIsArray(data interface{}) (bool, []map[string]interface{}) { 66 | if data == nil { 67 | return false, nil 68 | } 69 | valueType := reflect.TypeOf(data) 70 | // 判断类型是否为数组或切片 71 | if valueType.Kind() == reflect.Slice || valueType.Kind() == reflect.Array { 72 | var maps []map[string]interface{} 73 | for _, item := range data.([]interface{}) { 74 | log.Println("item", item) 75 | if m, ok := item.(map[string]interface{}); ok { 76 | maps = append(maps, m) 77 | } 78 | } 79 | return true, maps 80 | } 81 | return false, nil 82 | } 83 | -------------------------------------------------------------------------------- /deploy/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: pandax 6 | app.kubernetes.io/version: 1.0.0 7 | name: pandax 8 | --- 9 | apiVersion: v1 10 | kind: ServiceAccount 11 | metadata: 12 | labels: 13 | app.kubernetes.io/name: pandax 14 | app.kubernetes.io/version: 1.0.0 15 | name: pandax 16 | namespace: pandax 17 | --- 18 | apiVersion: rbac.authorization.k8s.io/v1 19 | kind: ClusterRoleBinding 20 | metadata: 21 | labels: 22 | app.kubernetes.io/name: pandax 23 | app.kubernetes.io/version: 1.0.0 24 | name: pandax-rolebinding 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: cluster-admin 29 | subjects: 30 | - kind: ServiceAccount 31 | name: pandax 32 | namespace: pandax 33 | --- 34 | apiVersion: v1 35 | kind: Service 36 | metadata: 37 | labels: 38 | app.kubernetes.io/name: pandax 39 | app.kubernetes.io/version: 1.0.0 40 | name: pandax 41 | namespace: pandax 42 | spec: 43 | ports: 44 | - name: http 45 | port: 8080 46 | protocol: TCP 47 | targetPort: http 48 | selector: 49 | app.kubernetes.io/name: pandax 50 | app.kubernetes.io/version: 1.0.0 51 | type: LoadBalancer 52 | --- 53 | apiVersion: apps/v1 54 | kind: Deployment 55 | metadata: 56 | labels: 57 | app.kubernetes.io/name: pandax 58 | app.kubernetes.io/version: 1.0.0 59 | name: pandax 60 | namespace: pandax 61 | spec: 62 | replicas: 1 63 | selector: 64 | matchLabels: 65 | app.kubernetes.io/name: pandax 66 | app.kubernetes.io/version: 1.0.0 67 | template: 68 | metadata: 69 | labels: 70 | app.kubernetes.io/name: pandax 71 | app.kubernetes.io/version: 1.0.0 72 | spec: 73 | containers: 74 | - image: xmadmin/pandax:v1.4 75 | imagePullPolicy: Always 76 | name: pandax 77 | ports: 78 | - containerPort: 7788 79 | name: http 80 | protocol: TCP 81 | serviceAccountName: pandax 82 | -------------------------------------------------------------------------------- /apps/system/api/config.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/biz" 5 | "github.com/PandaXGO/PandaKit/model" 6 | "github.com/PandaXGO/PandaKit/restfulx" 7 | "github.com/PandaXGO/PandaKit/utils" 8 | entity "pandax/apps/system/entity" 9 | services "pandax/apps/system/services" 10 | ) 11 | 12 | type ConfigApi struct { 13 | ConfigApp services.SysConfigModel 14 | } 15 | 16 | func (p *ConfigApi) GetConfigList(rc *restfulx.ReqCtx) { 17 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 18 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 19 | configName := rc.Request.QueryParameter("configName") 20 | configKey := rc.Request.QueryParameter("configKey") 21 | configType := rc.Request.QueryParameter("configType") 22 | config := entity.SysConfig{ConfigName: configName, ConfigKey: configKey, ConfigType: configType} 23 | list, total := p.ConfigApp.FindListPage(pageNum, pageSize, config) 24 | 25 | rc.ResData = model.ResultPage{ 26 | Total: total, 27 | PageNum: int64(pageNum), 28 | PageSize: int64(pageNum), 29 | Data: list, 30 | } 31 | } 32 | 33 | func (p *ConfigApi) GetConfigListByKey(rc *restfulx.ReqCtx) { 34 | configKey := rc.Request.QueryParameter("configKey") 35 | biz.IsTrue(configKey != "", "请传入配置Key") 36 | rc.ResData = p.ConfigApp.FindList(entity.SysConfig{ConfigKey: configKey}) 37 | } 38 | 39 | func (p *ConfigApi) GetConfig(rc *restfulx.ReqCtx) { 40 | id := restfulx.PathParamInt(rc, "configId") 41 | p.ConfigApp.FindOne(int64(id)) 42 | } 43 | 44 | func (p *ConfigApi) InsertConfig(rc *restfulx.ReqCtx) { 45 | var config entity.SysConfig 46 | restfulx.BindJsonAndValid(rc, &config) 47 | 48 | p.ConfigApp.Insert(config) 49 | } 50 | 51 | func (p *ConfigApi) UpdateConfig(rc *restfulx.ReqCtx) { 52 | var post entity.SysConfig 53 | restfulx.BindJsonAndValid(rc, &post) 54 | p.ConfigApp.Update(post) 55 | } 56 | 57 | func (p *ConfigApi) DeleteConfig(rc *restfulx.ReqCtx) { 58 | configId := rc.Request.PathParameter("configId") 59 | p.ConfigApp.Delete(utils.IdsStrToIdsIntGroup(configId)) 60 | } 61 | -------------------------------------------------------------------------------- /pkg/middleware/log.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/PandaXGO/PandaKit/biz" 7 | "github.com/PandaXGO/PandaKit/logger" 8 | "github.com/PandaXGO/PandaKit/restfulx" 9 | "github.com/PandaXGO/PandaKit/utils" 10 | "github.com/sirupsen/logrus" 11 | "reflect" 12 | "runtime/debug" 13 | ) 14 | 15 | func LogHandler(rc *restfulx.ReqCtx) error { 16 | li := rc.LogInfo 17 | if li == nil { 18 | return nil 19 | } 20 | 21 | lfs := logrus.Fields{} 22 | if la := rc.LoginAccount; la != nil { 23 | lfs["uid"] = la.UserId 24 | lfs["uname"] = la.UserName 25 | } 26 | 27 | req := rc.Request.Request 28 | lfs[req.Method] = req.URL.Path 29 | 30 | if err := rc.Err; err != nil { 31 | logger.Log.WithFields(lfs).Error(getErrMsg(rc, err)) 32 | return nil 33 | } 34 | logger.Log.WithFields(lfs).Info(getLogMsg(rc)) 35 | return nil 36 | } 37 | 38 | func getLogMsg(rc *restfulx.ReqCtx) string { 39 | msg := rc.LogInfo.Description + fmt.Sprintf(" ->%dms", rc.Timed) 40 | if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) { 41 | rb, _ := json.Marshal(rc.ReqParam) 42 | msg = msg + fmt.Sprintf("\n--> %s", string(rb)) 43 | } 44 | 45 | // 返回结果不为空,则记录返回结果 46 | if rc.LogInfo.LogResp && !utils.IsBlank(reflect.ValueOf(rc.ResData)) { 47 | respB, _ := json.Marshal(rc.ResData) 48 | msg = msg + fmt.Sprintf("\n<-- %s", string(respB)) 49 | } 50 | return msg 51 | } 52 | 53 | func getErrMsg(rc *restfulx.ReqCtx, err any) string { 54 | msg := rc.LogInfo.Description 55 | if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) { 56 | rb, _ := json.Marshal(rc.ReqParam) 57 | msg = msg + fmt.Sprintf("\n--> %s", string(rb)) 58 | } 59 | 60 | var errMsg string 61 | switch t := err.(type) { 62 | case *biz.BizError: 63 | errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error()) 64 | case error: 65 | errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t.Error(), string(debug.Stack())) 66 | case string: 67 | errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t, string(debug.Stack())) 68 | } 69 | return (msg + errMsg) 70 | } 71 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/action_generator_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | "time" 7 | ) 8 | 9 | type messageGeneratorNode struct { 10 | bareNode 11 | Script string `json:"script" yaml:"script"` 12 | PeriodSecond int64 `json:"periodSecond" yaml:"periodSecond"` //周期 13 | MessageCount int64 `json:"messageCount" yaml:"messageCount"` 14 | } 15 | 16 | type messageGeneratorNodeFactory struct{} 17 | 18 | func (f messageGeneratorNodeFactory) Name() string { return "MessageGeneratorNode" } 19 | func (f messageGeneratorNodeFactory) Category() string { return NODE_CATEGORY_ACTION } 20 | func (f messageGeneratorNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 21 | func (f messageGeneratorNodeFactory) Create(id string, meta Metadata) (Node, error) { 22 | node := &messageGeneratorNode{ 23 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 24 | } 25 | return decodePath(meta, node) 26 | } 27 | 28 | func (n *messageGeneratorNode) Handle(msg *message.Message) error { 29 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 30 | 31 | successLabelNode := n.GetLinkedNode("Success") 32 | failureLabelNode := n.GetLinkedNode("Failure") 33 | 34 | ticker := time.NewTicker(time.Duration(n.PeriodSecond) * time.Second) 35 | count := 0 36 | 37 | go func() { 38 | for { 39 | <-ticker.C 40 | count++ 41 | if int64(count) == n.MessageCount { 42 | ticker.Stop() 43 | return 44 | } 45 | scriptEngine := NewScriptEngine(*msg, "Generate", n.Script) 46 | generate, err := scriptEngine.ScriptGenerate() 47 | if err != nil { 48 | if failureLabelNode != nil { 49 | go failureLabelNode.Handle(msg) 50 | } 51 | return 52 | } 53 | msg.Msg = generate["msg"].(message.Msg) 54 | msg.Metadata = generate["metadata"].(message.Metadata) 55 | msg.MsgType = generate["msgType"].(string) 56 | if successLabelNode != nil { 57 | go successLabelNode.Handle(msg) 58 | } 59 | } 60 | }() 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/transform_rename_key_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "pandax/pkg/rule_engine/message" 6 | ) 7 | 8 | type transformRenameKeyNode struct { 9 | bareNode 10 | FormType string `json:"formType" yaml:"formType"` //msg metadata 11 | Keys []KeyName `json:"keys" yaml:"keys"` 12 | } 13 | type KeyName struct { 14 | OldName string `json:"oldName" yaml:"oldName"` 15 | NewName string `json:"newName" yaml:"newName"` 16 | } 17 | type transformRenameKeyNodeFactory struct{} 18 | 19 | func (f transformRenameKeyNodeFactory) Name() string { return "RenameKeyNode" } 20 | func (f transformRenameKeyNodeFactory) Category() string { return NODE_CATEGORY_TRANSFORM } 21 | func (f transformRenameKeyNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 22 | func (f transformRenameKeyNodeFactory) Create(id string, meta Metadata) (Node, error) { 23 | node := &transformRenameKeyNode{ 24 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 25 | } 26 | return decodePath(meta, node) 27 | } 28 | 29 | func (n *transformRenameKeyNode) Handle(msg *message.Message) error { 30 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 31 | 32 | successLabelNode := n.GetLinkedNode("Success") 33 | failureLabelNode := n.GetLinkedNode("Failure") 34 | if n.FormType == "msg" { 35 | data := msg.Msg 36 | for _, key := range n.Keys { 37 | if _, found := data[key.OldName]; found { 38 | data[key.NewName] = data[key.OldName] 39 | delete(data, key.OldName) 40 | msg.Msg = data 41 | } 42 | } 43 | } else if n.FormType == "metadata" { 44 | data := msg.Metadata 45 | for _, key := range n.Keys { 46 | if data.GetValue(key.OldName) != nil { 47 | data[key.NewName] = data[key.OldName] 48 | delete(data, key.OldName) 49 | msg.Metadata = data 50 | } 51 | } 52 | } else { 53 | if failureLabelNode != nil { 54 | return failureLabelNode.Handle(msg) 55 | } 56 | } 57 | if successLabelNode != nil { 58 | return successLabelNode.Handle(msg) 59 | } 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | app: 2 | name: pandax 3 | version: 1.0.0 4 | 5 | server: 6 | # debug release test 7 | model: release 8 | port: 7788 9 | # iothub使用的rpc端口 9000 9001 可能与minio端口冲突 10 | grpc-port: 9001 11 | cors: true 12 | # 数据上报 队列池 13 | queue-num: 1000 14 | # 接口限流 15 | rate: 16 | enable: true 17 | rate-num: 100 18 | db-type: mysql 19 | # 是否开启数据库表初始化 20 | isInitTable: false 21 | excel-dir: ./resource/excel/ 22 | tls: 23 | enable: false 24 | key-file: ./resource/default.key 25 | cert-file: ./resource/default.pem 26 | jwt: 27 | key: PandaX 28 | # 过期时间单位秒 7天 29 | expire-time: 604800 30 | #数据上报并发识别任务数量限制 31 | queue: 32 | enable: false 33 | num: 3000 34 | redis: 35 | host: 127.0.0.1 36 | password: root 37 | port: 6379 38 | 39 | mysql: 40 | host: 127.0.0.1:3306 41 | username: root 42 | password: '!MyEMS1' 43 | db-name: pandax_iot 44 | config: charset=utf8&loc=Local&parseTime=true 45 | # mini0 46 | oss: 47 | endpoint: 127.0.0.1:9000 48 | accessKey: minioadmin 49 | secretKey: minioadmin 50 | bucketName: pandaxiot 51 | useSSL: false 52 | 53 | taos: 54 | username: "root" 55 | password: "taosdata" 56 | host: "127.0.0.1:6041" 57 | database: "iot" 58 | config: "" 59 | 60 | mqtt: 61 | broker: 127.0.0.1:1883 62 | qos: 1 63 | username: pandax 64 | password: pandax 65 | 66 | casbin: 67 | model-path: './resource/rbac_model.conf' 68 | 69 | gen: 70 | # 代码生成读取的数据库名称 71 | dbname: pandax_iot 72 | # 代码生成是使用前端代码存放位置,需要指定到src文件夹,相对路径 73 | frontpath: ../PandaUi/src 74 | 75 | log: 76 | # 日志等级, trace, debug, info, warn, error, fatal 77 | level: info 78 | file: 79 | path: ./ 80 | # name: panda_log.log 81 | 82 | # 视频服务器使用的全局配置 83 | global: 84 | http: 85 | listenaddr: :8801 # 网关地址,用于访问API 86 | listenaddrtls: :8443 # 用于HTTPS方式访问API的端口配置 87 | 88 | gb28181: 89 | #sip服务器地址 默认 自动适配设备网段 90 | sipip: "" 91 | serial: "34020000002000000001" 92 | realm: "3402000000" 93 | password: "pandax" 94 | #sip服务器端口 95 | port: 96 | sip: udp:5060 -------------------------------------------------------------------------------- /apps/rule/services/rulechain_log.go: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // 生成日期:2023-03-29 20:01:11 +0800 CST 3 | // 生成路径: apps/visual/services/rulechain.go 4 | // 生成人:panda 5 | // ========================================================================== 6 | 7 | package services 8 | 9 | import ( 10 | "github.com/PandaXGO/PandaKit/biz" 11 | "pandax/apps/rule/entity" 12 | "pandax/pkg/global" 13 | ) 14 | 15 | type ( 16 | RuleChainMsgLogModel interface { 17 | Insert(data entity.RuleChainMsgLog) *entity.RuleChainMsgLog 18 | FindListPage(page, pageSize int, data entity.RuleChainMsgLog) (*[]entity.RuleChainMsgLog, int64) 19 | Delete(ids []string) 20 | } 21 | 22 | ruleChainLogModelImpl struct { 23 | table string 24 | } 25 | ) 26 | 27 | var RuleChainMsgLogModelDao RuleChainMsgLogModel = &ruleChainLogModelImpl{ 28 | table: `rule_chain_msg_log`, 29 | } 30 | 31 | func (m *ruleChainLogModelImpl) Insert(data entity.RuleChainMsgLog) *entity.RuleChainMsgLog { 32 | err := global.Db.Table(m.table).Create(&data).Error 33 | biz.ErrIsNil(err, "添加规则链失败") 34 | return &data 35 | } 36 | 37 | func (m *ruleChainLogModelImpl) FindListPage(page, pageSize int, data entity.RuleChainMsgLog) (*[]entity.RuleChainMsgLog, int64) { 38 | list := make([]entity.RuleChainMsgLog, 0) 39 | var total int64 = 0 40 | offset := pageSize * (page - 1) 41 | db := global.Db.Table(m.table) 42 | // 此处填写 where参数判断 43 | if data.DeviceName != "" { 44 | db = db.Where("device_name = ?", data.DeviceName) 45 | } 46 | if data.MessageId != "" { 47 | db = db.Where("message_id = ?", data.MessageId) 48 | } 49 | if data.MsgType != "" { 50 | db = db.Where("msg_type = ?", data.MsgType) 51 | } 52 | err := db.Count(&total).Error 53 | err = db.Order("create_at").Limit(pageSize).Offset(offset).Find(&list).Error 54 | biz.ErrIsNil(err, "查询规则链分页列表失败") 55 | return &list, total 56 | } 57 | 58 | func (m *ruleChainLogModelImpl) Delete(ids []string) { 59 | biz.ErrIsNil(global.Db.Table(m.table).Delete(&entity.RuleChainMsgLog{}, "id in (?)", ids).Error, "删除规则链失败") 60 | } 61 | -------------------------------------------------------------------------------- /apps/device/api/device_cmd.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // ========================================================================== 4 | import ( 5 | "github.com/PandaXGO/PandaKit/biz" 6 | "github.com/PandaXGO/PandaKit/model" 7 | "github.com/PandaXGO/PandaKit/restfulx" 8 | "github.com/kakuilan/kgo" 9 | "pandax/pkg/global" 10 | "pandax/pkg/mqtt" 11 | "strings" 12 | "time" 13 | 14 | "pandax/apps/device/entity" 15 | "pandax/apps/device/services" 16 | ) 17 | 18 | type DeviceCmdLogApi struct { 19 | DeviceCmdLogApp services.DeviceCmdLogModel 20 | } 21 | 22 | // GetDeviceCmdLogList 告警列表数据 23 | func (p *DeviceCmdLogApi) GetDeviceCmdLogList(rc *restfulx.ReqCtx) { 24 | data := entity.DeviceCmdLog{} 25 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 26 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 27 | data.DeviceId = restfulx.QueryParam(rc, "deviceId") 28 | data.State = restfulx.QueryParam(rc, "state") 29 | data.Type = restfulx.QueryParam(rc, "type") 30 | 31 | list, total := p.DeviceCmdLogApp.FindListPage(pageNum, pageSize, data) 32 | 33 | rc.ResData = model.ResultPage{ 34 | Total: total, 35 | PageNum: int64(pageNum), 36 | PageSize: int64(pageNum), 37 | Data: list, 38 | } 39 | } 40 | 41 | // InsertDeviceCmdLog 添加DeviceCmdLog 42 | func (p *DeviceCmdLogApi) InsertDeviceCmdLog(rc *restfulx.ReqCtx) { 43 | var data entity.DeviceCmdLog 44 | restfulx.BindJsonAndValid(rc, &data) 45 | data.Id = kgo.KStr.Uniqid("cmd_") 46 | data.State = "2" 47 | data.RequestTime = time.Now().Format("2006-01-02 15:04:05") 48 | err := p.DeviceCmdLogApp.Insert(data) 49 | biz.ErrIsNil(err, "添加指令记录失败") 50 | // 下发指令 51 | var rpc = &mqtt.RpcRequest{Client: global.MqttClient, Mode: "single"} 52 | rpc.GetRequestId() 53 | _, err = rpc.RequestCmd(mqtt.RpcPayload{Method: data.CmdName, Params: data.CmdContent}) 54 | if err != nil { 55 | global.Log.Error("指令下发失败") 56 | } 57 | } 58 | 59 | // DeleteDeviceCmdLog 删除告警 60 | func (p *DeviceCmdLogApi) DeleteDeviceCmdLog(rc *restfulx.ReqCtx) { 61 | id := restfulx.PathParam(rc, "id") 62 | ids := strings.Split(id, ",") 63 | p.DeviceCmdLogApp.Delete(ids) 64 | } 65 | -------------------------------------------------------------------------------- /apps/device/router/device_cmd.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | "pandax/apps/device/api" 7 | "pandax/apps/device/entity" 8 | "pandax/apps/device/services" 9 | 10 | restfulspec "github.com/emicklei/go-restful-openapi/v2" 11 | "github.com/emicklei/go-restful/v3" 12 | ) 13 | 14 | func InitDeviceCmdLogRouter(container *restful.Container) { 15 | s := &api.DeviceCmdLogApi{ 16 | DeviceCmdLogApp: services.DeviceCmdLogModelDao, 17 | } 18 | 19 | ws := new(restful.WebService) 20 | ws.Path("/device/cmd").Produces(restful.MIME_JSON) 21 | tags := []string{"cmd"} 22 | 23 | ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { 24 | restfulx.NewReqCtx(request, response).WithLog("获取命令下发分页列表").Handle(s.GetDeviceCmdLogList) 25 | }). 26 | Doc("获取命令下发分页列表"). 27 | Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). 28 | Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). 29 | Param(ws.QueryParameter("deviceId", "设备Id").Required(false).DataType("string")). 30 | Param(ws.QueryParameter("type", "命令下发分类").Required(false).DataType("string")). 31 | Param(ws.QueryParameter("state", "命令下发状态").Required(false).DataType("string")). 32 | Metadata(restfulspec.KeyOpenAPITags, tags). 33 | Writes(model.ResultPage{}). 34 | Returns(200, "OK", model.ResultPage{})) 35 | 36 | ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { 37 | restfulx.NewReqCtx(request, response).WithLog("命令下发").Handle(s.InsertDeviceCmdLog) 38 | }). 39 | Doc("命令下发"). 40 | Metadata(restfulspec.KeyOpenAPITags, tags). 41 | Reads(entity.DeviceCmdLog{})) 42 | 43 | ws.Route(ws.DELETE("/{id}").To(func(request *restful.Request, response *restful.Response) { 44 | restfulx.NewReqCtx(request, response).WithLog("删除命令下发信息").Handle(s.DeleteDeviceCmdLog) 45 | }). 46 | Doc("删除命令下发信息"). 47 | Metadata(restfulspec.KeyOpenAPITags, tags). 48 | Param(ws.PathParameter("id", "多id 1,2,3").DataType("string"))) 49 | 50 | container.Add(ws) 51 | } 52 | -------------------------------------------------------------------------------- /iothub/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 原始数据上报格式 3 | ```text 4 | 010304026C00883BF0 5 | ``` 6 | iothub将原始数据序列化成,在规则链中 msg就是以下数据。 7 | ```json 8 | { 9 | "rowdata": "010304026C00883BF0" 10 | } 11 | ``` 12 | 需要在规则链中通过函数键 进行解析脚本 13 | ```js 14 | /*直连设备:tempVal是产品物模型中所定义属性的标识符*/ 15 | var tempVal = msg.rowdata; 16 | /*物模型温度标识符*/ 17 | msg.temperature = (parseInt('0x'+tempVal.substr(10, 4))*0.1).toFixed(2); 18 | /*物模型湿度标识符*/ 19 | msg.humidity = (parseInt('0x'+tempVal.substr(6, 4))*0.1).toFixed(2); 20 | return {msg: msg, metadata: metadata, msgType: msgType}; 21 | ``` 22 | ## 属性上报格式 23 | ```json 24 | { 25 | "attribute1": "value1", 26 | "attribute2": 0 27 | } 28 | ``` 29 | 30 | ## 遥测上报格式 31 | 32 | ```json 33 | { 34 | "ts": 1689837909000, 35 | "values": { 36 | "telemetry1": "value1", 37 | "telemetry2": 0 38 | } 39 | } 40 | ``` 41 | 如果边缘无法获取时间 42 | ```json 43 | { 44 | "telemetry1": "value1", 45 | "telemetry2": 0 46 | } 47 | ``` 48 | 49 | ## 网关子设备属性上报格式 50 | devA 为设备ID 51 | ```json 52 | { 53 | "devA": { 54 | "attribute1": "value1", 55 | "attribute2": 0 56 | }, 57 | "devB": { 58 | "attribute1": "value1", 59 | "attribute2": 0 60 | } 61 | } 62 | ``` 63 | 64 | ## 网关子设备遥测上报格式 65 | devA 为设备ID 66 | ```json 67 | { 68 | "devA": [ 69 | { 70 | "ts": 1689837909000, 71 | "values": { 72 | "telemetry1": "value1", 73 | "telemetry2": 0 74 | } 75 | } 76 | ] 77 | } 78 | ``` 79 | 80 | ## 网关子设备连接或断开上报格式 81 | ```json 82 | { 83 | "devA": "online", 84 | "devB": "offline" 85 | } 86 | ``` 87 | ## 命令下发格式 88 | 下发的ID和相应的ID相同 89 | ```json 90 | { 91 | "id": "2343", 92 | "cmd": "restart", 93 | "content": { 94 | "firmware_address": "http://xxx.yyy.com", 95 | "version": "latest", 96 | "secret": "****", 97 | "http_method": "GET" 98 | } 99 | } 100 | ``` 101 | 102 | ## 命令响应的格式 103 | success 返回结果必传 content代表输出参数可选 104 | ```json 105 | { 106 | "id": "2343", 107 | "success": true, 108 | "content": { 109 | "aa": "2" 110 | } 111 | } 112 | ``` 113 | -------------------------------------------------------------------------------- /apps/device/router/device_alarm.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | "pandax/apps/device/api" 7 | "pandax/apps/device/entity" 8 | "pandax/apps/device/services" 9 | 10 | restfulspec "github.com/emicklei/go-restful-openapi/v2" 11 | "github.com/emicklei/go-restful/v3" 12 | ) 13 | 14 | func InitDeviceAlarmRouter(container *restful.Container) { 15 | s := &api.DeviceAlarmApi{ 16 | DeviceAlarmApp: services.DeviceAlarmModelDao, 17 | } 18 | 19 | ws := new(restful.WebService) 20 | ws.Path("/device/alarm").Produces(restful.MIME_JSON) 21 | tags := []string{"alarm"} 22 | 23 | ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { 24 | restfulx.NewReqCtx(request, response).WithLog("获取告警分页列表").Handle(s.GetDeviceAlarmList) 25 | }). 26 | Doc("获取告警分页列表"). 27 | Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). 28 | Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). 29 | Param(ws.QueryParameter("deviceId", "设备Id").Required(false).DataType("string")). 30 | Param(ws.QueryParameter("type", "告警类型").Required(false).DataType("string")). 31 | Param(ws.QueryParameter("level", "告警等级").Required(false).DataType("string")). 32 | Param(ws.QueryParameter("state", "告警状态").Required(false).DataType("string")). 33 | Metadata(restfulspec.KeyOpenAPITags, tags). 34 | Writes(model.ResultPage{}). 35 | Returns(200, "OK", model.ResultPage{})) 36 | 37 | ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { 38 | restfulx.NewReqCtx(request, response).WithLog("修改告警信息").Handle(s.UpdateDeviceAlarm) 39 | }). 40 | Doc("修改告警信息"). 41 | Metadata(restfulspec.KeyOpenAPITags, tags). 42 | Reads(entity.DeviceAlarm{})) 43 | 44 | ws.Route(ws.DELETE("/{id}").To(func(request *restful.Request, response *restful.Response) { 45 | restfulx.NewReqCtx(request, response).WithLog("删除告警信息").Handle(s.DeleteDeviceAlarm) 46 | }). 47 | Doc("删除告警信息"). 48 | Metadata(restfulspec.KeyOpenAPITags, tags). 49 | Param(ws.PathParameter("id", "多id 1,2,3").DataType("string"))) 50 | 51 | container.Add(ws) 52 | } 53 | -------------------------------------------------------------------------------- /apps/system/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/casbin" 5 | "github.com/PandaXGO/PandaKit/model" 6 | "github.com/PandaXGO/PandaKit/restfulx" 7 | "github.com/PandaXGO/PandaKit/utils" 8 | entity "pandax/apps/system/entity" 9 | services "pandax/apps/system/services" 10 | "pandax/pkg/global" 11 | ) 12 | 13 | type SystemApiApi struct { 14 | ApiApp services.SysApiModel 15 | } 16 | 17 | func (s *SystemApiApi) CreateApi(rc *restfulx.ReqCtx) { 18 | var api entity.SysApi 19 | restfulx.BindJsonAndValid(rc, &api) 20 | s.ApiApp.Insert(api) 21 | } 22 | 23 | func (s *SystemApiApi) DeleteApi(rc *restfulx.ReqCtx) { 24 | ids := rc.Request.PathParameter("id") 25 | s.ApiApp.Delete(utils.IdsStrToIdsIntGroup(ids)) 26 | } 27 | 28 | func (s *SystemApiApi) GetApiList(rc *restfulx.ReqCtx) { 29 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 30 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 31 | path := rc.Request.QueryParameter("path") 32 | description := rc.Request.QueryParameter("description") 33 | method := rc.Request.QueryParameter("method") 34 | apiGroup := rc.Request.QueryParameter("apiGroup") 35 | api := entity.SysApi{Path: path, Description: description, Method: method, ApiGroup: apiGroup} 36 | list, total := s.ApiApp.FindListPage(pageNum, pageSize, api) 37 | rc.ResData = model.ResultPage{ 38 | Total: total, 39 | PageNum: int64(pageNum), 40 | PageSize: int64(pageNum), 41 | Data: list, 42 | } 43 | } 44 | 45 | func (s *SystemApiApi) GetApiById(rc *restfulx.ReqCtx) { 46 | id := restfulx.QueryInt(rc, "id", 0) 47 | rc.ResData = s.ApiApp.FindOne(int64(id)) 48 | 49 | } 50 | 51 | func (s *SystemApiApi) UpdateApi(rc *restfulx.ReqCtx) { 52 | var api entity.SysApi 53 | restfulx.BindJsonAndValid(rc, &api) 54 | s.ApiApp.Update(api) 55 | } 56 | 57 | func (s *SystemApiApi) GetAllApis(rc *restfulx.ReqCtx) { 58 | rc.ResData = s.ApiApp.FindList(entity.SysApi{}) 59 | } 60 | 61 | func (s *SystemApiApi) GetPolicyPathByRoleId(rc *restfulx.ReqCtx) { 62 | roleKey := rc.Request.QueryParameter("roleKey") 63 | ca := casbin.CasbinS{ModelPath: global.Conf.Casbin.ModelPath} 64 | rc.ResData = ca.GetPolicyPathByRoleId(roleKey) 65 | } 66 | -------------------------------------------------------------------------------- /deploy/mysql.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: mysql 6 | app.kubernetes.io/version: 1.0.0 7 | name: pandax-db 8 | --- 9 | apiVersion: v1 10 | kind: Service 11 | metadata: 12 | labels: 13 | app.kubernetes.io/version: 1.0.0 14 | name: mysql 15 | namespace: pandax-db 16 | spec: 17 | ports: 18 | - port: 3306 19 | protocol: TCP 20 | targetPort: mysql 21 | selector: 22 | app: mysql 23 | app.kubernetes.io/version: 1.0.0 24 | type: LoadBalancer 25 | --- 26 | apiVersion: v1 27 | kind: PersistentVolume 28 | metadata: 29 | labels: 30 | app.kubernetes.io/version: 1.0.0 31 | type: local 32 | name: mysql-pv-volume 33 | spec: 34 | accessModes: 35 | - ReadWriteOnce 36 | capacity: 37 | storage: 20Gi 38 | hostPath: 39 | path: /mnt/data 40 | storageClassName: manual 41 | --- 42 | apiVersion: v1 43 | kind: PersistentVolumeClaim 44 | metadata: 45 | labels: 46 | app.kubernetes.io/version: 1.0.0 47 | name: mysql-pv-claim 48 | namespace: pandax-db 49 | spec: 50 | accessModes: 51 | - ReadWriteOnce 52 | resources: 53 | requests: 54 | storage: 20Gi 55 | storageClassName: manual 56 | --- 57 | apiVersion: apps/v1 58 | kind: Deployment 59 | metadata: 60 | labels: 61 | app.kubernetes.io/version: 1.0.0 62 | name: mysql 63 | namespace: pandax-db 64 | spec: 65 | selector: 66 | matchLabels: 67 | app: mysql 68 | app.kubernetes.io/version: 1.0.0 69 | strategy: 70 | type: Recreate 71 | template: 72 | metadata: 73 | labels: 74 | app: mysql 75 | app.kubernetes.io/version: 1.0.0 76 | spec: 77 | containers: 78 | - env: 79 | - name: MYSQL_ROOT_PASSWORD 80 | value: pandax 81 | image: mysql:8.0.23 82 | name: mysql 83 | ports: 84 | - containerPort: 3306 85 | name: mysql 86 | protocol: TCP 87 | volumeMounts: 88 | - mountPath: /var/lib/mysql 89 | name: mysql-persistent-storage 90 | volumes: 91 | - name: mysql-persistent-storage 92 | persistentVolumeClaim: 93 | claimName: mysql-pv-claim 94 | -------------------------------------------------------------------------------- /apps/system/api/tenant.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | /** 4 | * @Description 5 | * @Author 熊猫 6 | * @Date 2022/7/14 17:55 7 | **/ 8 | import ( 9 | "github.com/PandaXGO/PandaKit/model" 10 | "github.com/PandaXGO/PandaKit/restfulx" 11 | "github.com/PandaXGO/PandaKit/utils" 12 | "pandax/apps/system/entity" 13 | "pandax/apps/system/services" 14 | ) 15 | 16 | type SysTenantsApi struct { 17 | SysTenantsApp services.SysTenantsModel 18 | } 19 | 20 | func (p *SysTenantsApi) GetSysTenantsList(rc *restfulx.ReqCtx) { 21 | data := entity.SysTenants{} 22 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 23 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 24 | 25 | list, total := p.SysTenantsApp.FindListPage(pageNum, pageSize, data) 26 | 27 | rc.ResData = model.ResultPage{ 28 | Total: total, 29 | PageNum: int64(pageNum), 30 | PageSize: int64(pageNum), 31 | Data: list, 32 | } 33 | } 34 | 35 | func (p *SysTenantsApi) GetSysTenantsAll(rc *restfulx.ReqCtx) { 36 | list := make([]entity.SysTenants, 0) 37 | if rc.LoginAccount.RoleKey == "admin" { 38 | data := entity.SysTenants{} 39 | list = *p.SysTenantsApp.FindList(data) 40 | } else { 41 | list = append(list, *p.SysTenantsApp.FindOne(rc.LoginAccount.TenantId)) 42 | } 43 | rc.ResData = list 44 | } 45 | 46 | func (p *SysTenantsApi) GetSysTenants(rc *restfulx.ReqCtx) { 47 | tenantId := restfulx.PathParamInt(rc, "tenantId") 48 | p.SysTenantsApp.FindOne(int64(tenantId)) 49 | } 50 | 51 | func (p *SysTenantsApi) InsertSysTenants(rc *restfulx.ReqCtx) { 52 | var data entity.SysTenants 53 | restfulx.BindQuery(rc, &data) 54 | 55 | p.SysTenantsApp.Insert(data) 56 | } 57 | 58 | func (p *SysTenantsApi) UpdateSysTenants(rc *restfulx.ReqCtx) { 59 | var data entity.SysTenants 60 | restfulx.BindQuery(rc, &data) 61 | 62 | p.SysTenantsApp.Update(data) 63 | } 64 | 65 | func (p *SysTenantsApi) DeleteSysTenants(rc *restfulx.ReqCtx) { 66 | tenantId := rc.Request.PathParameter("tenantId") 67 | tenantIds := utils.IdsStrToIdsIntGroup(tenantId) 68 | p.SysTenantsApp.Delete(tenantIds) 69 | } 70 | 71 | // IsTenantAdmin 是否为主租户 72 | func IsTenantAdmin(tenantId int64) bool { 73 | if tenantId == 1 { 74 | return true 75 | } 76 | return false 77 | } 78 | -------------------------------------------------------------------------------- /apps/system/api/upload.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PandaXGO/PandaKit/biz" 6 | "github.com/PandaXGO/PandaKit/oss" 7 | "github.com/PandaXGO/PandaKit/restfulx" 8 | "net/http" 9 | "os" 10 | "pandax/pkg/config" 11 | "pandax/pkg/global" 12 | "pandax/pkg/tool" 13 | "path" 14 | "time" 15 | ) 16 | 17 | type UploadApi struct{} 18 | 19 | const filePath = "uploads/file" 20 | 21 | // UploadImage 图片上传 22 | func (up *UploadApi) UploadImage(rc *restfulx.ReqCtx) { 23 | _, fileHeader, err := rc.Request.Request.FormFile("file") 24 | biz.ErrIsNil(err, "请传入文件") 25 | local := &tool.Local{Path: filePath} 26 | link, fileName, err := local.UploadFile(fileHeader) 27 | biz.ErrIsNil(err, "文件上传失败") 28 | rc.ResData = map[string]string{"fileName": fileName, "filePath": link} 29 | } 30 | 31 | // UplaodResOsses 上传文件ResOsses 32 | func (p *UploadApi) UplaodToOss(rc *restfulx.ReqCtx) { 33 | _, handler, _ := rc.Request.Request.FormFile("file") 34 | yunFileTmpPath := "uploads/" + time.Now().Format("2006-01-02") + "/" + handler.Filename 35 | // 读取本地文件。 36 | f, openError := handler.Open() 37 | biz.ErrIsNil(openError, "function file.Open() Failed") 38 | config := global.Conf.Oss 39 | biz.ErrIsNil(NewOss(*config).PutObj(yunFileTmpPath, f), "上传OSS失败") 40 | 41 | rc.ResData = fmt.Sprintf("http://%s/%s/%s", config.Endpoint, config.BucketName, yunFileTmpPath) 42 | } 43 | 44 | func (up *UploadApi) GetImage(rc *restfulx.ReqCtx) { 45 | actual := path.Join(filePath, restfulx.PathParam(rc, "subpath")) 46 | http.ServeFile( 47 | rc.Response.ResponseWriter, 48 | rc.Request.Request, 49 | actual) 50 | } 51 | 52 | func (up *UploadApi) DeleteImage(rc *restfulx.ReqCtx) { 53 | fileName := restfulx.QueryParam(rc, "fileName") 54 | biz.NotEmpty(fileName, "请传要删除的图片名") 55 | err := os.Remove(fmt.Sprintf("%s/%s", filePath, fileName)) 56 | biz.ErrIsNil(err, "文件删除失败") 57 | } 58 | 59 | func NewOss(ens config.Oss) oss.Driver { 60 | return oss.NewMiniOss(oss.MiniOConfig{ 61 | BucketName: ens.BucketName, 62 | Endpoint: ens.Endpoint, 63 | AccessKeyID: ens.AccessKey, 64 | SecretAccessKey: ens.SecretKey, 65 | UseSSL: ens.UseSSL, 66 | Location: "cn-north-1", 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /pkg/rule_engine/instance_test.go: -------------------------------------------------------------------------------- 1 | package rule_engine 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "pandax/pkg/rule_engine/message" 7 | "pandax/pkg/rule_engine/nodes" 8 | "testing" 9 | ) 10 | 11 | func TestNewRuleChainInstance(t *testing.T) { 12 | buf, err := ioutil.ReadFile("./manifest/manifest_sample.json") 13 | if err != nil { 14 | t.Error(err) 15 | } 16 | instance, errs := NewRuleChainInstance(buf) 17 | if len(errs) > 0 { 18 | t.Error(errs[0]) 19 | } 20 | 21 | metadata := message.Metadata{ 22 | "deviceName": "ws432", 23 | "deviceId": "d_1928b99619910dae5a001fa7", 24 | "deviceType": "direct", 25 | "productId": "p_3ba460634520cf4590dc90e5", 26 | } 27 | msg := message.NewMessage("1", message.TelemetryMes, message.Msg{"temperature": 60.4, "humidity": 32.5}, metadata) 28 | t.Log("开始执行力流程") 29 | err = instance.StartRuleChain(context.Background(), msg) 30 | if err != nil { 31 | t.Log(err) 32 | } 33 | } 34 | 35 | func TestScriptEngine(t *testing.T) { 36 | msg := message.NewMessage("1", message.UpEventMes, map[string]interface{}{"aa": 5}, map[string]interface{}{"device": "aa"}) 37 | const baseScript = ` 38 | function nextRelation(metadata, msg) { 39 | return ['one','nine']; 40 | } 41 | if(metadata.device === 'aa') { 42 | return ['six']; 43 | } 44 | if(msgType === 'Post telemetry') { 45 | return ['two']; 46 | } 47 | return nextRelation(metadata, msg); 48 | ` 49 | scriptEngine := nodes.NewScriptEngine(*msg, "Switch", baseScript) 50 | SwitchResults, err := scriptEngine.ScriptOnSwitch() 51 | 52 | if err != nil { 53 | t.Error(err) 54 | } 55 | t.Log(SwitchResults) 56 | } 57 | 58 | func TestScriptOnMessage(t *testing.T) { 59 | msg := message.NewMessage("1", message.UpEventMes, map[string]interface{}{"aa": 5}, map[string]interface{}{"device": "aa"}) 60 | 61 | const baseScript = ` 62 | msg.bb = "33" 63 | metadata.event = 55 64 | return {msg: msg, metadata: metadata, msgType: msgType}; 65 | ` 66 | scriptEngine := nodes.NewScriptEngine(*msg, "Transform", baseScript) 67 | ScriptOnMessageResults, err := scriptEngine.ScriptOnMessage() 68 | 69 | if err != nil { 70 | t.Error(err) 71 | } 72 | t.Log(ScriptOnMessageResults.Metadata) 73 | } 74 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/external_wechat_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/PandaXGO/PandaKit/httpclient" 6 | "github.com/sirupsen/logrus" 7 | "pandax/pkg/rule_engine/message" 8 | ) 9 | 10 | type externalWechatNode struct { 11 | bareNode 12 | WebHook string `json:"webHook" yaml:"webHook"` 13 | MsgType string `json:"msgType" yaml:"msgType"` 14 | Content string `json:"content" yaml:"content"` 15 | IsAtAll bool `json:"isAtAll" yaml:"isAtAll"` 16 | AtMobiles []string `json:"atMobiles" yaml:"atMobiles"` 17 | } 18 | 19 | type externalWechatNodeFactory struct{} 20 | 21 | func (f externalWechatNodeFactory) Name() string { return "WechatNode" } 22 | func (f externalWechatNodeFactory) Category() string { return NODE_CATEGORY_EXTERNAL } 23 | func (f externalWechatNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 24 | func (f externalWechatNodeFactory) Create(id string, meta Metadata) (Node, error) { 25 | node := &externalWechatNode{ 26 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 27 | } 28 | return decodePath(meta, node) 29 | } 30 | 31 | func (n *externalWechatNode) Handle(msg *message.Message) error { 32 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 33 | 34 | successLabelNode := n.GetLinkedNode("Success") 35 | failureLabelNode := n.GetLinkedNode("Failure") 36 | template, err := ParseTemplate(n.Content, msg.GetAllMap()) 37 | sendData := map[string]interface{}{ 38 | "msgtype": "text", 39 | "text": map[string]interface{}{"content": template}, 40 | } 41 | if n.IsAtAll { 42 | sendData["text"].(map[string]interface{})["mentioned_mobile_list"] = []string{"@all"} 43 | } else { 44 | sendData["text"].(map[string]interface{})["mentioned_mobile_list"] = n.AtMobiles 45 | } 46 | marshal, _ := json.Marshal(sendData) 47 | postJson := httpclient.NewRequest(n.WebHook).Header("Content-Type", "application/json").PostJson(string(marshal)) 48 | if postJson.StatusCode != 200 { 49 | if failureLabelNode != nil { 50 | return failureLabelNode.Handle(msg) 51 | } else { 52 | return err 53 | } 54 | } 55 | if successLabelNode != nil { 56 | return successLabelNode.Handle(msg) 57 | } 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /apps/develop/entity/dev_gen_table.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/PandaXGO/PandaKit/model" 4 | 5 | type DevGenTable struct { 6 | TableId int64 `gorm:"primaryKey;autoIncrement" json:"tableId"` // 编号 7 | TableName string `gorm:"table_name" json:"tableName"` // 表名称 8 | TableComment string `gorm:"table_comment" json:"tableComment"` // 表描述 9 | ClassName string `gorm:"class_name" json:"className"` // 实体类名称 10 | TplCategory string `gorm:"tpl_category" json:"tplCategory"` // 使用的模板(crud单表操作 tree树表操作) 11 | PackageName string `gorm:"package_name" json:"packageName"` // 生成包路径 12 | ModuleName string `gorm:"module_name" json:"moduleName"` // 生成模块名 13 | BusinessName string `gorm:"business_name" json:"businessName"` // 生成业务名 14 | FunctionName string `gorm:"function_name" json:"functionName"` // 生成功能名 15 | FunctionAuthor string `gorm:"function_author" json:"functionAuthor"` // 生成功能作者 16 | Options string `gorm:"options" json:"options"` // 其它生成选项 17 | Remark string `gorm:"remark" json:"remark"` // 备注 18 | PkColumn string `gorm:"pk_column;" json:"pkColumn"` 19 | PkGoField string `gorm:"pk_go_field" json:"pkGoField"` 20 | PkJsonField string `gorm:"pk_json_field" json:"pkJsonField"` 21 | Columns []DevGenTableColumn `gorm:"-" json:"columns"` // 字段信息 22 | model.BaseModel 23 | } 24 | 25 | type DBTables struct { 26 | TableName string `gorm:"column:TABLE_NAME" json:"tableName"` 27 | Engine string `gorm:"column:ENGINE" json:"engine"` 28 | TableRows string `gorm:"column:TABLE_ROWS" json:"tableRows"` 29 | TableCollation string `gorm:"column:TABLE_COLLATION" json:"tableCollation"` 30 | CreateTime string `gorm:"column:CREATE_TIME" json:"createTime"` 31 | UpdateTime string `gorm:"column:UPDATE_TIME" json:"updateTime"` 32 | TableComment string `gorm:"column:TABLE_COMMENT" json:"tableComment"` 33 | } 34 | -------------------------------------------------------------------------------- /pkg/rule_engine/manifest/manifest.go: -------------------------------------------------------------------------------- 1 | package manifest 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/sirupsen/logrus" 6 | ) 7 | 8 | type Node struct { 9 | Type string `json:"type" yaml:"type"` 10 | Id string `json:"id" yaml:"id"` 11 | Properties map[string]interface{} `json:"properties" yaml:"properties"` //debugMode 12 | } 13 | 14 | type Edge struct { 15 | SourceNodeId string `json:"sourceNodeId" yaml:"sourceNodeId"` 16 | TargetNodeId string `json:"targetNodeId" yaml:"targetNodeId"` 17 | Type string `json:"type" yaml:"type"` //success or fail 18 | Properties map[string]interface{} `json:"properties" yaml:"properties"` //debugMode 19 | } 20 | 21 | type Manifest struct { 22 | FirstRuleNodeId string `json:"firstRuleNodeId" yaml:"firstRuleNodeId"` 23 | Nodes []Node `json:"nodes" yaml:"nodes"` 24 | Edges []Edge `json:"edges" yaml:"edges"` 25 | } 26 | 27 | func New(data []byte) (*Manifest, error) { 28 | firstRuleNodeId := "" 29 | manifest := make(map[string]interface{}) 30 | if err := json.Unmarshal(data, &manifest); err != nil { 31 | logrus.WithError(err).Errorf("invalid node chain manifest file") 32 | return nil, err 33 | } 34 | nodes := make([]Node, 0) 35 | for _, mn := range manifest["nodes"].([]interface{}) { 36 | node := mn.(map[string]interface{}) 37 | if node["type"].(string) == "InputNode" { 38 | firstRuleNodeId = node["id"].(string) 39 | } 40 | nodes = append(nodes, Node{ 41 | Id: node["id"].(string), 42 | Type: node["type"].(string), 43 | Properties: node["properties"].(map[string]interface{}), 44 | }) 45 | } 46 | edges := make([]Edge, 0) 47 | for _, en := range manifest["edges"].([]interface{}) { 48 | edge := en.(map[string]interface{}) 49 | edges = append(edges, Edge{ 50 | Type: edge["type"].(string), 51 | Properties: edge["properties"].(map[string]interface{}), 52 | SourceNodeId: edge["sourceNodeId"].(string), 53 | TargetNodeId: edge["targetNodeId"].(string), 54 | }) 55 | } 56 | m := &Manifest{ 57 | FirstRuleNodeId: firstRuleNodeId, 58 | Nodes: nodes, 59 | Edges: edges, 60 | } 61 | return m, nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/tool/excel.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "fmt" 5 | "github.com/xuri/excelize/v2" 6 | ) 7 | 8 | // 读取数据表 9 | func ReadExcel(filename string) ([]string, []map[string]interface{}) { 10 | ret := make([]map[string]interface{}, 0) 11 | f, err := excelize.OpenFile(filename) 12 | if err != nil { 13 | fmt.Println("读取excel文件出错", err.Error()) 14 | return nil, ret 15 | } 16 | sheets := f.GetSheetMap() 17 | sheet1 := sheets[1] 18 | rows, err := f.GetRows(sheet1) 19 | cols := make([]string, 0) 20 | isHead := true 21 | for _, row := range rows { 22 | if isHead { //取得第一行的所有数据---execel表头 23 | if len(row) == 0 { 24 | continue 25 | } 26 | for _, colCell := range row { 27 | cols = append(cols, colCell) 28 | } 29 | isHead = false 30 | } else { 31 | theRow := map[string]interface{}{} 32 | for j, colCell := range row { 33 | k := cols[j] 34 | theRow[k] = colCell 35 | } 36 | ret = append(ret, theRow) 37 | } 38 | } 39 | return cols, ret 40 | } 41 | 42 | /* 43 | func ReadExcelByFilter(filename string, data entity.DataSetDataReq) ([]string, []map[string]interface{}) { 44 | dataDs := make([]string, 0) 45 | for _, ds := range data.DataDs { 46 | dataDs = append(dataDs, ds.Value) 47 | } 48 | 49 | ret := make([]map[string]interface{}, 0) 50 | f, err := excelize.OpenFile(filename) 51 | if err != nil { 52 | fmt.Println("读取excel文件出错", err.Error()) 53 | return nil, ret 54 | } 55 | sheets := f.GetSheetMap() 56 | sheet1 := sheets[1] 57 | rows, err := f.GetRows(sheet1) 58 | cols := make([]string, 0) 59 | colsIndex := make([]int, 0) 60 | isHead := true 61 | count := 0 62 | for _, row := range rows { 63 | if data.ShowNumType == "2" { 64 | if count == int(data.ShowNum) { 65 | break 66 | } 67 | } 68 | if isHead { //取得第一行的所有数据---execel表头 69 | if len(row) == 0 { 70 | continue 71 | } 72 | for i, colCell := range row { 73 | cols = append(cols, colCell) 74 | colsIndex = append(colsIndex, i) 75 | } 76 | isHead = false 77 | } else { 78 | theRow := map[string]interface{}{} 79 | for j, colCell := range row { 80 | k := cols[j] 81 | theRow[k] = colCell 82 | 83 | } 84 | ret = append(ret, theRow) 85 | } 86 | count++ 87 | } 88 | return cols, ret 89 | } 90 | */ 91 | -------------------------------------------------------------------------------- /apps/log/services/log_oper.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/biz" 5 | "pandax/apps/log/entity" 6 | "pandax/pkg/global" 7 | ) 8 | 9 | type ( 10 | LogOperModel interface { 11 | Insert(data entity.LogOper) *entity.LogOper 12 | FindOne(infoId int64) *entity.LogOper 13 | FindListPage(page, pageSize int, data entity.LogOper) (*[]entity.LogOper, int64) 14 | Delete(infoId []int64) 15 | DeleteAll() 16 | } 17 | 18 | logLogOperModelImpl struct { 19 | table string 20 | } 21 | ) 22 | 23 | var LogOperModelDao LogOperModel = &logLogOperModelImpl{ 24 | table: `log_opers`, 25 | } 26 | 27 | func (m *logLogOperModelImpl) Insert(data entity.LogOper) *entity.LogOper { 28 | global.Db.Table(m.table).Create(&data) 29 | return &data 30 | } 31 | 32 | func (m *logLogOperModelImpl) FindOne(operId int64) *entity.LogOper { 33 | resData := new(entity.LogOper) 34 | err := global.Db.Table(m.table).Where("oper_id = ?", operId).First(resData).Error 35 | biz.ErrIsNil(err, "查询操作日志信息失败") 36 | return resData 37 | } 38 | 39 | func (m *logLogOperModelImpl) FindListPage(page, pageSize int, data entity.LogOper) (*[]entity.LogOper, int64) { 40 | list := make([]entity.LogOper, 0) 41 | var total int64 = 0 42 | offset := pageSize * (page - 1) 43 | db := global.Db.Table(m.table) 44 | // 此处填写 where参数判断 45 | if data.BusinessType != "" { 46 | db = db.Where("business_type = ?", data.BusinessType) 47 | } 48 | if data.OperLocation != "" { 49 | db = db.Where("oper_location like ?", "%"+data.OperLocation+"%") 50 | } 51 | if data.Title != "" { 52 | db = db.Where("title like ?", "%"+data.Title+"%") 53 | } 54 | if data.OperName != "" { 55 | db = db.Where("oper_name like ?", "%"+data.OperName+"%") 56 | } 57 | err := db.Where("delete_time IS NULL").Count(&total).Error 58 | err = db.Order("create_time desc").Limit(pageSize).Offset(offset).Find(&list).Error 59 | 60 | biz.ErrIsNil(err, "查询操作分页日志信息失败") 61 | return &list, total 62 | } 63 | 64 | func (m *logLogOperModelImpl) Delete(operIds []int64) { 65 | err := global.Db.Table(m.table).Delete(&entity.LogOper{}, "oper_id in (?)", operIds).Error 66 | biz.ErrIsNil(err, "删除操作日志信息失败") 67 | return 68 | } 69 | 70 | func (m *logLogOperModelImpl) DeleteAll() { 71 | global.Db.Exec("DELETE FROM log_opers") 72 | } 73 | -------------------------------------------------------------------------------- /pkg/mqtt/mqtt.go: -------------------------------------------------------------------------------- 1 | package mqtt 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | mqtt "github.com/eclipse/paho.mqtt.golang" 7 | "github.com/sirupsen/logrus" 8 | "time" 9 | ) 10 | 11 | const DefaultDownStreamClientId = `@panda.iothub.internal.clientId` 12 | 13 | type IothubMqttClient struct { 14 | Client mqtt.Client 15 | } 16 | 17 | var connectHandler mqtt.OnConnectHandler = func(client mqtt.Client) { 18 | logrus.Infof("Connected") 19 | } 20 | 21 | var connectLostHandler mqtt.ConnectionLostHandler = func(client mqtt.Client, err error) { 22 | logrus.Infof("Connect lost: %v", err) 23 | } 24 | 25 | func InitMqtt(broker, username, password string) *IothubMqttClient { 26 | server := fmt.Sprintf("tcp://%s", broker) 27 | client := GetMqttClinent(server, username, password) 28 | return &IothubMqttClient{ 29 | Client: client, 30 | } 31 | } 32 | 33 | func GetMqttClinent(server, username, password string) mqtt.Client { 34 | opts := mqtt.NewClientOptions().AddBroker(server) 35 | time.Now().Unix() 36 | // 默认下行ID iothub会过滤掉改Id 37 | opts.SetClientID(DefaultDownStreamClientId) 38 | if username != "" { 39 | opts.SetUsername(username) 40 | } 41 | if password != "" { 42 | opts.SetPassword(password) 43 | } 44 | opts.OnConnect = connectHandler 45 | opts.OnConnectionLost = connectLostHandler 46 | client := mqtt.NewClient(opts) 47 | if token := client.Connect(); token.Wait() && token.Error() != nil { 48 | panic(token.Error()) 49 | } 50 | return client 51 | } 52 | 53 | // 订阅 54 | func (imc *IothubMqttClient) Sub(topic string, qos byte, handler mqtt.MessageHandler) { 55 | if token := imc.Client.Subscribe(topic, qos, handler); token.Wait() && token.Error() != nil { 56 | logrus.Infof(token.Error().Error()) 57 | } 58 | } 59 | 60 | // 取消订阅 61 | func (imc *IothubMqttClient) UnSub(topic string) { 62 | if token := imc.Client.Unsubscribe(topic); token.Wait() && token.Error() != nil { 63 | logrus.Infof(token.Error().Error()) 64 | } 65 | } 66 | 67 | // 发布 68 | func (imc *IothubMqttClient) Pub(topic string, qos byte, payload string) error { 69 | token := imc.Client.Publish(topic, qos, false, payload) 70 | if token.WaitTimeout(2*time.Second) == false { 71 | return errors.New("推送消息超时") 72 | } 73 | if token.Error() != nil { 74 | return token.Error() 75 | } 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /apps/system/router/notice.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | restfulspec "github.com/emicklei/go-restful-openapi/v2" 7 | "github.com/emicklei/go-restful/v3" 8 | "pandax/apps/system/api" 9 | "pandax/apps/system/entity" 10 | "pandax/apps/system/services" 11 | ) 12 | 13 | func InitNoticeRouter(container *restful.Container) { 14 | s := &api.NoticeApi{ 15 | DeptApp: services.SysDeptModelDao, 16 | NoticeApp: services.SysNoticeModelDao, 17 | } 18 | ws := new(restful.WebService) 19 | ws.Path("/system/notice").Produces(restful.MIME_JSON) 20 | tags := []string{"notice"} 21 | 22 | ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { 23 | restfulx.NewReqCtx(request, response).WithLog("获取通知分页列表").Handle(s.GetNoticeList) 24 | }). 25 | Doc("获取通知分页列表"). 26 | Metadata(restfulspec.KeyOpenAPITags, tags). 27 | Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). 28 | Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). 29 | Param(ws.QueryParameter("noticeType", "noticeType").DataType("string")). 30 | Param(ws.QueryParameter("title", "title").DataType("string")). 31 | Writes(model.ResultPage{}). 32 | Returns(200, "OK", model.ResultPage{})) 33 | 34 | ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { 35 | restfulx.NewReqCtx(request, response).WithLog("添加通知信息").Handle(s.InsertNotice) 36 | }). 37 | Doc("添加通知信息"). 38 | Metadata(restfulspec.KeyOpenAPITags, tags). 39 | Reads(entity.SysNotice{})) 40 | 41 | ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { 42 | restfulx.NewReqCtx(request, response).WithLog("修改通知信息").Handle(s.UpdateNotice) 43 | }). 44 | Doc("修改通知信息"). 45 | Metadata(restfulspec.KeyOpenAPITags, tags). 46 | Reads(entity.SysNotice{})) 47 | 48 | ws.Route(ws.DELETE("/{noticeId}").To(func(request *restful.Request, response *restful.Response) { 49 | restfulx.NewReqCtx(request, response).WithLog("删除通知信息").Handle(s.DeleteNotice) 50 | }). 51 | Doc("删除通知信息"). 52 | Metadata(restfulspec.KeyOpenAPITags, tags). 53 | Param(ws.PathParameter("noticeId", "多id 1,2,3").DataType("string"))) 54 | 55 | container.Add(ws) 56 | 57 | } 58 | -------------------------------------------------------------------------------- /apps/system/entity/user.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "github.com/PandaXGO/PandaKit/model" 4 | 5 | type LoginM struct { 6 | Username string `gorm:"type:varchar(64)" json:"username"` 7 | Password string `gorm:"type:varchar(128)" json:"password"` 8 | } 9 | 10 | type SysUserId struct { 11 | UserId int64 `gorm:"primary_key;AUTO_INCREMENT" json:"userId"` // 编码 12 | } 13 | 14 | type SysUserB struct { 15 | NickName string `gorm:"type:varchar(128)" json:"nickName"` // 昵称 16 | Phone string `gorm:"type:varchar(11)" json:"phone"` // 手机号 17 | RoleId int64 `gorm:"type:int" json:"roleId"` // 角色编码 18 | Salt string `gorm:"type:varchar(255)" json:"salt"` //盐 19 | Avatar string `gorm:"type:varchar(255)" json:"avatar"` //头像 20 | Sex string `gorm:"type:varchar(255)" json:"sex"` //性别 21 | Email string `gorm:"type:varchar(128)" json:"email"` //邮箱 22 | DeptId int64 `gorm:"type:int" json:"deptId"` //部门编码 23 | PostId int64 `gorm:"type:int" json:"postId"` //职位编码 24 | RoleIds string `gorm:"type:varchar(255)" json:"roleIds"` //多角色 25 | PostIds string `gorm:"type:varchar(255)" json:"postIds"` // 多岗位 26 | CreateBy string `gorm:"type:varchar(128)" json:"createBy"` // 27 | UpdateBy string `gorm:"type:varchar(128)" json:"updateBy"` // 28 | Remark string `gorm:"type:varchar(255)" json:"remark"` //备注 29 | Status string `gorm:"type:varchar(1);" json:"status"` 30 | model.BaseModel 31 | } 32 | 33 | type SysUser struct { 34 | SysUserId 35 | SysUserB 36 | LoginM 37 | } 38 | 39 | type SysUserPwd struct { 40 | OldPassword string `json:"oldPassword" form:"oldPassword"` 41 | NewPassword string `json:"newPassword" form:"newPassword"` 42 | } 43 | 44 | type SysUserPage struct { 45 | SysUserId 46 | SysUserB 47 | LoginM 48 | DeptName string `gorm:"-" json:"deptName"` 49 | } 50 | 51 | type Login struct { 52 | Username string `form:"username" json:"username" binding:"required"` 53 | Password string `form:"password" json:"password" binding:"required"` 54 | Code string `form:"code" json:"code" binding:"required"` 55 | UUID string `form:"UUID" json:"uuid" binding:"required"` 56 | } 57 | 58 | type SysUserView struct { 59 | SysUserId 60 | SysUserB 61 | LoginM 62 | RoleName string `gorm:"column:role_name" json:"role_name"` 63 | } 64 | -------------------------------------------------------------------------------- /apps/system/services/notice.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/biz" 5 | "pandax/apps/system/entity" 6 | "pandax/pkg/global" 7 | ) 8 | 9 | type ( 10 | SysNoticeModel interface { 11 | Insert(data entity.SysNotice) *entity.SysNotice 12 | FindOne(postId int64) *entity.SysNotice 13 | FindListPage(page, pageSize int, data entity.SysNotice) (*[]entity.SysNotice, int64) 14 | Update(data entity.SysNotice) *entity.SysNotice 15 | Delete(postId []int64) 16 | } 17 | 18 | sysNoticeModelImpl struct { 19 | table string 20 | } 21 | ) 22 | 23 | var SysNoticeModelDao SysNoticeModel = &sysNoticeModelImpl{ 24 | table: `sys_notices`, 25 | } 26 | 27 | func (m *sysNoticeModelImpl) Insert(data entity.SysNotice) *entity.SysNotice { 28 | err := global.Db.Table(m.table).Create(&data).Error 29 | biz.ErrIsNil(err, "添加通知失败") 30 | return &data 31 | } 32 | 33 | func (m *sysNoticeModelImpl) FindOne(postId int64) *entity.SysNotice { 34 | resData := new(entity.SysNotice) 35 | err := global.Db.Table(m.table).Where("post_id = ?", postId).First(resData).Error 36 | biz.ErrIsNil(err, "查询通知失败") 37 | return resData 38 | } 39 | 40 | func (m *sysNoticeModelImpl) FindListPage(page, pageSize int, data entity.SysNotice) (*[]entity.SysNotice, int64) { 41 | list := make([]entity.SysNotice, 0) 42 | var total int64 = 0 43 | offset := pageSize * (page - 1) 44 | db := global.Db.Table(m.table) 45 | // 此处填写 where参数判断 46 | if data.Title != "" { 47 | db = db.Where("title like ?", "%"+data.Title+"%") 48 | } 49 | if data.NoticeType != "" { 50 | db = db.Where("notice_type = ?", data.NoticeType) 51 | } 52 | if len(data.DeptIds) > 0 { 53 | db = db.Where("dept_id in (?)", data.DeptIds) 54 | } 55 | db.Where("delete_time IS NULL") 56 | err := db.Count(&total).Error 57 | err = db.Order("create_time").Limit(pageSize).Offset(offset).Find(&list).Error 58 | biz.ErrIsNil(err, "查询通知分页列表失败") 59 | return &list, total 60 | } 61 | 62 | func (m *sysNoticeModelImpl) Update(data entity.SysNotice) *entity.SysNotice { 63 | biz.ErrIsNil(global.Db.Table(m.table).Updates(&data).Error, "修改通知失败") 64 | return &data 65 | } 66 | 67 | func (m *sysNoticeModelImpl) Delete(postIds []int64) { 68 | biz.ErrIsNil(global.Db.Table(m.table).Delete(&entity.SysNotice{}, "notice_id in (?)", postIds).Error, "删除通知失败") 69 | } 70 | -------------------------------------------------------------------------------- /pkg/tool/local.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "errors" 7 | "io" 8 | "mime/multipart" 9 | "os" 10 | "pandax/pkg/global" 11 | "path" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | type Local struct { 17 | Path string 18 | } 19 | 20 | //@object: *Local 21 | //@function: UploadFile 22 | //@description: 上传文件 23 | //@param: file *multipart.FileHeader 24 | //@return: string, string, error 25 | 26 | func (local *Local) UploadFile(file *multipart.FileHeader) (string, string, error) { 27 | // 读取文件后缀 28 | ext := path.Ext(file.Filename) 29 | // 读取文件名并加密 30 | name := strings.TrimSuffix(file.Filename, ext) 31 | name = MD5V([]byte(name)) 32 | // 拼接新文件名 33 | filename := name + "_" + time.Now().Format("20060102150405") + ext 34 | // 尝试创建此路径 35 | mkdirErr := os.MkdirAll(local.Path, os.ModePerm) 36 | if mkdirErr != nil { 37 | global.Log.Error("function os.MkdirAll() Filed", mkdirErr.Error()) 38 | return "", "", errors.New("function os.MkdirAll() Filed, err:" + mkdirErr.Error()) 39 | } 40 | // 拼接路径和文件名 41 | p := local.Path + "/" + filename 42 | 43 | f, openError := file.Open() // 读取文件 44 | if openError != nil { 45 | global.Log.Error("function file.Open() Filed", openError.Error()) 46 | return "", "", errors.New("function file.Open() Filed, err:" + openError.Error()) 47 | } 48 | defer f.Close() // 创建文件 defer 关闭 49 | 50 | out, createErr := os.Create(p) 51 | if createErr != nil { 52 | global.Log.Error("function os.Create() Filed", createErr.Error()) 53 | return "", "", errors.New("function os.Create() Filed, err:" + createErr.Error()) 54 | } 55 | defer out.Close() // 创建文件 defer 关闭 56 | 57 | _, copyErr := io.Copy(out, f) // 传输(拷贝)文件 58 | if copyErr != nil { 59 | global.Log.Error("function io.Copy() Filed", copyErr.Error()) 60 | return "", "", errors.New("function io.Copy() Filed, err:" + copyErr.Error()) 61 | } 62 | return p, filename, nil 63 | } 64 | 65 | //@object: *Local 66 | //@function: DeleteFile 67 | //@description: 删除文件 68 | //@param: key string 69 | //@return: error 70 | 71 | func (local *Local) DeleteFile(key string) error { 72 | p := local.Path + "/" + key 73 | if strings.Contains(p, local.Path) { 74 | if err := os.Remove(p); err != nil { 75 | return errors.New("本地文件删除失败, err:" + err.Error()) 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | func MD5V(str []byte) string { 82 | h := md5.New() 83 | h.Write(str) 84 | return hex.EncodeToString(h.Sum(nil)) 85 | } 86 | -------------------------------------------------------------------------------- /apps/log/services/log_login.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/biz" 5 | "pandax/apps/log/entity" 6 | "pandax/pkg/global" 7 | ) 8 | 9 | type ( 10 | LogLoginModel interface { 11 | Insert(data entity.LogLogin) *entity.LogLogin 12 | FindOne(infoId int64) *entity.LogLogin 13 | FindListPage(page, pageSize int, data entity.LogLogin) (*[]entity.LogLogin, int64) 14 | Update(data entity.LogLogin) *entity.LogLogin 15 | Delete(infoId []int64) 16 | DeleteAll() 17 | } 18 | 19 | logLoginModelImpl struct { 20 | table string 21 | } 22 | ) 23 | 24 | var LogLoginModelDao LogLoginModel = &logLoginModelImpl{ 25 | table: `log_logins`, 26 | } 27 | 28 | func (m *logLoginModelImpl) Insert(data entity.LogLogin) *entity.LogLogin { 29 | data.CreateBy = "0" 30 | data.UpdateBy = "0" 31 | global.Db.Table(m.table).Create(&data) 32 | return &data 33 | } 34 | 35 | func (m *logLoginModelImpl) FindOne(infoId int64) *entity.LogLogin { 36 | resData := new(entity.LogLogin) 37 | err := global.Db.Table(m.table).Where("info_id = ?", infoId).First(resData).Error 38 | biz.ErrIsNil(err, "查询登录日志信息失败") 39 | return resData 40 | } 41 | 42 | func (m *logLoginModelImpl) FindListPage(page, pageSize int, data entity.LogLogin) (*[]entity.LogLogin, int64) { 43 | list := make([]entity.LogLogin, 0) 44 | var total int64 = 0 45 | offset := pageSize * (page - 1) 46 | db := global.Db.Table(m.table) 47 | // 此处填写 where参数判断 48 | if data.Status != "" { 49 | db = db.Where("status = ?", data.Status) 50 | } 51 | if data.LoginLocation != "" { 52 | db = db.Where("login_location like ?", "%"+data.LoginLocation+"%") 53 | } 54 | if data.Username != "" { 55 | db = db.Where("username like ?", "%"+data.Username+"%") 56 | } 57 | err := db.Where("delete_time IS NULL").Count(&total).Error 58 | err = db.Order("info_id desc").Limit(pageSize).Offset(offset).Find(&list).Error 59 | 60 | biz.ErrIsNil(err, "查询登录分页日志信息失败") 61 | return &list, total 62 | } 63 | 64 | func (m *logLoginModelImpl) Update(data entity.LogLogin) *entity.LogLogin { 65 | err := global.Db.Table(m.table).Updates(&data).Error 66 | biz.ErrIsNil(err, "修改登录日志信息失败") 67 | return &data 68 | } 69 | 70 | func (m *logLoginModelImpl) Delete(infoIds []int64) { 71 | err := global.Db.Table(m.table).Delete(&entity.LogLogin{}, "info_id in (?)", infoIds).Error 72 | biz.ErrIsNil(err, "删除登录日志信息失败") 73 | return 74 | } 75 | 76 | func (m *logLoginModelImpl) DeleteAll() { 77 | global.Db.Exec("DELETE FROM log_logins") 78 | } 79 | -------------------------------------------------------------------------------- /apps/system/api/post.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/PandaXGO/PandaKit/biz" 7 | "github.com/PandaXGO/PandaKit/model" 8 | "github.com/PandaXGO/PandaKit/restfulx" 9 | "github.com/PandaXGO/PandaKit/utils" 10 | "pandax/apps/system/entity" 11 | "pandax/apps/system/services" 12 | "pandax/pkg/global" 13 | ) 14 | 15 | type PostApi struct { 16 | PostApp services.SysPostModel 17 | UserApp services.SysUserModel 18 | RoleApp services.SysRoleModel 19 | } 20 | 21 | // GetPostList 职位列表数据 22 | func (p *PostApi) GetPostList(rc *restfulx.ReqCtx) { 23 | 24 | pageNum := restfulx.QueryInt(rc, "pageNum", 1) 25 | pageSize := restfulx.QueryInt(rc, "pageSize", 10) 26 | status := restfulx.QueryParam(rc, "status") 27 | postName := restfulx.QueryParam(rc, "postName") 28 | postCode := restfulx.QueryParam(rc, "postCode") 29 | post := entity.SysPost{Status: status, PostName: postName, PostCode: postCode} 30 | 31 | list, total := p.PostApp.FindListPage(pageNum, pageSize, post) 32 | 33 | rc.ResData = model.ResultPage{ 34 | Total: total, 35 | PageNum: int64(pageNum), 36 | PageSize: int64(pageNum), 37 | Data: list, 38 | } 39 | } 40 | 41 | // GetPost 获取职位 42 | func (p *PostApi) GetPost(rc *restfulx.ReqCtx) { 43 | postId := restfulx.PathParamInt(rc, "postId") 44 | p.PostApp.FindOne(int64(postId)) 45 | } 46 | 47 | // InsertPost 添加职位 48 | func (p *PostApi) InsertPost(rc *restfulx.ReqCtx) { 49 | var post entity.SysPost 50 | restfulx.BindJsonAndValid(rc, &post) 51 | post.CreateBy = rc.LoginAccount.UserName 52 | p.PostApp.Insert(post) 53 | } 54 | 55 | // UpdatePost 修改职位 56 | func (p *PostApi) UpdatePost(rc *restfulx.ReqCtx) { 57 | var post entity.SysPost 58 | restfulx.BindJsonAndValid(rc, &post) 59 | 60 | post.CreateBy = rc.LoginAccount.UserName 61 | p.PostApp.Update(post) 62 | } 63 | 64 | // DeletePost 删除职位 65 | func (p *PostApi) DeletePost(rc *restfulx.ReqCtx) { 66 | postId := restfulx.PathParam(rc, "postId") 67 | postIds := utils.IdsStrToIdsIntGroup(postId) 68 | 69 | deList := make([]int64, 0) 70 | for _, id := range postIds { 71 | user := entity.SysUser{} 72 | user.PostId = id 73 | list := p.UserApp.FindList(user) 74 | if len(*list) == 0 { 75 | deList = append(deList, id) 76 | } else { 77 | global.Log.Info(fmt.Sprintf("dictId: %d 存在岗位绑定用户无法删除", id)) 78 | } 79 | } 80 | if len(deList) == 0 { 81 | biz.ErrIsNil(errors.New("所有岗位都已绑定用户,无法删除"), "所有岗位都已绑定用户,无法删除") 82 | } 83 | p.PostApp.Delete(deList) 84 | } 85 | -------------------------------------------------------------------------------- /pkg/initialize/router.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "pandax/apps/job/jobs" 5 | ruleRouter "pandax/apps/rule/router" 6 | "pandax/pkg/global" 7 | "pandax/pkg/transport" 8 | 9 | devRouter "pandax/apps/develop/router" 10 | deviceRouter "pandax/apps/device/router" 11 | jobRouter "pandax/apps/job/router" 12 | logRouter "pandax/apps/log/router" 13 | sysRouter "pandax/apps/system/router" 14 | "pandax/pkg/middleware" 15 | ) 16 | 17 | func InitRouter() *transport.HttpServer { 18 | // server配置 19 | serverConfig := global.Conf.Server 20 | server := transport.NewHttpServer(serverConfig.GetPort()) 21 | 22 | container := server.Container 23 | // 防止XSS 24 | container.Filter(middleware.EscapeHTML) 25 | // 是否允许跨域 26 | if serverConfig.Cors { 27 | container.Filter(middleware.Cors(container).Filter) 28 | } 29 | // 流量限制 30 | if serverConfig.Rate.Enable { 31 | container.Filter(middleware.Rate) 32 | } 33 | // 设置路由组 34 | { 35 | sysRouter.InitSystemRouter(container) 36 | sysRouter.InitDeptRouter(container) 37 | sysRouter.InitConfigRouter(container) 38 | sysRouter.InitApiRouter(container) 39 | sysRouter.InitDictRouter(container) 40 | sysRouter.InitMenuRouter(container) 41 | sysRouter.InitRoleRouter(container) 42 | sysRouter.InitPostRouter(container) 43 | sysRouter.InitUserRouter(container) 44 | sysRouter.InitNoticeRouter(container) 45 | //本地图片上传接口 46 | sysRouter.InitUploadRouter(container) 47 | } 48 | //日志系统 49 | { 50 | logRouter.InitOperLogRouter(container) 51 | logRouter.InitLoginLogRouter(container) 52 | } 53 | // 代码生成 54 | { 55 | devRouter.InitGenTableRouter(container) 56 | devRouter.InitGenRouter(container) 57 | } 58 | //设备 59 | { 60 | deviceRouter.InitProductCategoryRouter(container) 61 | deviceRouter.InitDeviceGroupRouter(container) 62 | deviceRouter.InitProductRouter(container) 63 | deviceRouter.InitProductTemplateRouter(container) 64 | deviceRouter.InitProductOtaRouter(container) 65 | deviceRouter.InitDeviceRouter(container) 66 | deviceRouter.InitDeviceAlarmRouter(container) 67 | deviceRouter.InitDeviceCmdLogRouter(container) 68 | } 69 | { 70 | jobRouter.InitJobRouter(container) 71 | jobRouter.InitJobLogRouter(container) 72 | } 73 | { 74 | ruleRouter.InitRuleChainRouter(container) 75 | ruleRouter.InitRuleChainMsgLogRouter(container) 76 | } 77 | // api接口 78 | middleware.SwaggerConfig(container) 79 | // 开启调度任务 80 | go func() { 81 | jobs.InitJob() 82 | }() 83 | 84 | return server 85 | } 86 | -------------------------------------------------------------------------------- /apps/log/router/oper_log.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/model" 5 | "github.com/PandaXGO/PandaKit/restfulx" 6 | restfulspec "github.com/emicklei/go-restful-openapi/v2" 7 | "github.com/emicklei/go-restful/v3" 8 | "pandax/apps/log/api" 9 | "pandax/apps/log/entity" 10 | "pandax/apps/log/services" 11 | ) 12 | 13 | func InitOperLogRouter(container *restful.Container) { 14 | // 操作日志 15 | s := &api.LogOperApi{ 16 | LogOperApp: services.LogOperModelDao, 17 | } 18 | 19 | ws := new(restful.WebService) 20 | ws.Path("/log/logOper").Produces(restful.MIME_JSON) 21 | tags := []string{"logOper"} 22 | 23 | ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { 24 | restfulx.NewReqCtx(request, response).WithLog("获取操作日志列表").Handle(s.GetOperLogList) 25 | }). 26 | Doc("获取操作日志列表"). 27 | Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). 28 | Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). 29 | Param(ws.QueryParameter("businessType", "businessType").DataType("string")). 30 | Param(ws.QueryParameter("operName", "operName").DataType("string")). 31 | Param(ws.QueryParameter("title", "title").DataType("string")). 32 | Metadata(restfulspec.KeyOpenAPITags, tags). 33 | Writes(model.ResultPage{}). 34 | Returns(200, "OK", model.ResultPage{})) 35 | 36 | ws.Route(ws.GET("/{operId}").To(func(request *restful.Request, response *restful.Response) { 37 | restfulx.NewReqCtx(request, response).WithLog("获取操作日志信息").Handle(s.GetOperLog) 38 | }). 39 | Doc("获取操作日志信息"). 40 | Param(ws.PathParameter("operId", "Id").DataType("int")). 41 | Metadata(restfulspec.KeyOpenAPITags, tags). 42 | Writes(entity.LogOper{}). // on the response 43 | Returns(200, "OK", entity.LogOper{}). 44 | Returns(404, "Not Found", nil)) 45 | 46 | ws.Route(ws.DELETE("/{operId}").To(func(request *restful.Request, response *restful.Response) { 47 | restfulx.NewReqCtx(request, response).WithLog("删除操作日志信息").Handle(s.DeleteOperLog) 48 | }). 49 | Doc("删除操作日志信息"). 50 | Metadata(restfulspec.KeyOpenAPITags, tags). 51 | Param(ws.PathParameter("operId", "多id 1,2,3").DataType("string"))) 52 | 53 | ws.Route(ws.DELETE("/all").To(func(request *restful.Request, response *restful.Response) { 54 | restfulx.NewReqCtx(request, response).WithLog("清空操作日志信息").Handle(s.DeleteAll) 55 | }). 56 | Doc("清空操作日志信息"). 57 | Metadata(restfulspec.KeyOpenAPITags, tags)) 58 | 59 | container.Add(ws) 60 | } 61 | -------------------------------------------------------------------------------- /apps/system/api/system.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PandaXGO/PandaKit/biz" 6 | "github.com/PandaXGO/PandaKit/restfulx" 7 | "github.com/PandaXGO/PandaKit/ws" 8 | "github.com/emicklei/go-restful/v3" 9 | "github.com/gorilla/websocket" 10 | "github.com/kakuilan/kgo" 11 | "pandax/pkg/middleware" 12 | "runtime" 13 | ) 14 | 15 | type System struct{} 16 | 17 | const ( 18 | B = 1 19 | KB = 1024 * B 20 | MB = 1024 * KB 21 | GB = 1024 * MB 22 | ) 23 | 24 | func (s *System) ServerInfo(request *restful.Request, response *restful.Response) { 25 | osDic := make(map[string]any, 0) 26 | osDic["goOs"] = runtime.GOOS 27 | osDic["arch"] = runtime.GOARCH 28 | osDic["mem"] = runtime.MemProfileRate 29 | osDic["compiler"] = runtime.Compiler 30 | osDic["version"] = runtime.Version() 31 | osDic["numGoroutine"] = runtime.NumGoroutine() 32 | 33 | info := kgo.KOS.GetSystemInfo() 34 | diskDic := make(map[string]any, 0) 35 | diskDic["total"] = info.DiskTotal / GB 36 | diskDic["free"] = info.DiskFree / GB 37 | diskDic["used"] = info.DiskUsed / GB 38 | diskDic["progress"] = int64((float64(info.DiskUsed) / float64(info.DiskTotal)) * 100) 39 | 40 | memDic := make(map[string]any, 0) 41 | memDic["total"] = info.MemTotal / GB 42 | memDic["used"] = info.MemUsed / GB 43 | memDic["free"] = info.MemFree / GB 44 | memDic["progress"] = int64((float64(info.MemUsed) / float64(info.MemTotal)) * 100) 45 | 46 | cpuDic := make(map[string]any, 0) 47 | cpuDic["num"] = info.CpuNum 48 | cpuDic["used"] = fmt.Sprintf("%.2f", info.CpuUser*100) 49 | cpuDic["free"] = fmt.Sprintf("%.2f", info.CpuFree*100) 50 | 51 | response.WriteEntity(map[string]any{ 52 | "code": 200, 53 | "os": osDic, 54 | "mem": memDic, 55 | "cpu": cpuDic, 56 | "disk": diskDic, 57 | }) 58 | } 59 | 60 | // ConnectWs 连接websocket 61 | func (s *System) ConnectWs(request *restful.Request, response *restful.Response) { 62 | wsConn, err := ws.Upgrader.Upgrade(response, request.Request, nil) 63 | defer func() { 64 | if err := recover(); &err != nil { 65 | wsConn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("websocket 失败: %v", err))) 66 | wsConn.Close() 67 | } 68 | }() 69 | 70 | if err != nil { 71 | panic(any(biz.NewBizErr("升级websocket失败"))) 72 | } 73 | // 权限校验 74 | rc := restfulx.NewReqCtx(request, response) 75 | if err = middleware.PermissionHandler(rc); err != nil { 76 | panic(any(biz.NewBizErr("没有权限"))) 77 | } 78 | 79 | // 登录账号信息 80 | la := rc.LoginAccount 81 | ws.Put(uint64(la.UserId), wsConn) 82 | } 83 | -------------------------------------------------------------------------------- /apps/device/api/device_group.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/PandaXGO/PandaKit/restfulx" 5 | "github.com/kakuilan/kgo" 6 | "pandax/apps/device/entity" 7 | "pandax/apps/device/services" 8 | "strings" 9 | ) 10 | 11 | type DeviceGroupApi struct { 12 | DeviceGroupApp services.DeviceGroupModel 13 | } 14 | 15 | // GetDeviceGroupTree DeviceGroup 树 16 | func (p *DeviceGroupApi) GetDeviceGroupTree(rc *restfulx.ReqCtx) { 17 | name := restfulx.QueryParam(rc, "name") 18 | status := restfulx.QueryParam(rc, "status") 19 | id := restfulx.QueryParam(rc, "id") 20 | 21 | sg := entity.DeviceGroup{Name: name, Status: status} 22 | sg.Id = id 23 | rc.ResData = p.DeviceGroupApp.SelectDeviceGroup(sg) 24 | } 25 | 26 | func (p *DeviceGroupApi) GetDeviceGroupTreeLabel(rc *restfulx.ReqCtx) { 27 | sg := entity.DeviceGroup{} 28 | rc.ResData = p.DeviceGroupApp.SelectDeviceGroupLabel(sg) 29 | } 30 | 31 | func (p *DeviceGroupApi) GetDeviceGroupList(rc *restfulx.ReqCtx) { 32 | name := restfulx.QueryParam(rc, "name") 33 | status := restfulx.QueryParam(rc, "status") 34 | id := restfulx.QueryParam(rc, "id") 35 | 36 | sg := entity.DeviceGroup{Name: name, Status: status} 37 | sg.Id = id 38 | if sg.Name == "" { 39 | rc.ResData = p.DeviceGroupApp.SelectDeviceGroup(sg) 40 | } else { 41 | rc.ResData = p.DeviceGroupApp.FindList(sg) 42 | } 43 | } 44 | 45 | // GetDeviceGroupAllList 查询所有 46 | func (p *DeviceGroupApi) GetDeviceGroupAllList(rc *restfulx.ReqCtx) { 47 | var vsg entity.DeviceGroup 48 | rc.ResData = p.DeviceGroupApp.FindList(vsg) 49 | } 50 | 51 | // GetDeviceGroup 获取DeviceGroup 52 | func (p *DeviceGroupApi) GetDeviceGroup(rc *restfulx.ReqCtx) { 53 | id := restfulx.PathParam(rc, "id") 54 | rc.ResData = p.DeviceGroupApp.FindOne(id) 55 | } 56 | 57 | // InsertDeviceGroup 添加DeviceGroup 58 | func (p *DeviceGroupApi) InsertDeviceGroup(rc *restfulx.ReqCtx) { 59 | var data entity.DeviceGroup 60 | restfulx.BindJsonAndValid(rc, &data) 61 | data.Id = kgo.KStr.Uniqid("dg_") 62 | data.Owner = rc.LoginAccount.UserName 63 | p.DeviceGroupApp.Insert(data) 64 | } 65 | 66 | // UpdateDeviceGroup 修改DeviceGroup 67 | func (p *DeviceGroupApi) UpdateDeviceGroup(rc *restfulx.ReqCtx) { 68 | var data entity.DeviceGroup 69 | restfulx.BindJsonAndValid(rc, &data) 70 | 71 | p.DeviceGroupApp.Update(data) 72 | } 73 | 74 | // DeleteDeviceGroup 删除DeviceGroup 75 | func (p *DeviceGroupApi) DeleteDeviceGroup(rc *restfulx.ReqCtx) { 76 | id := restfulx.PathParam(rc, "id") 77 | ids := strings.Split(id, ",") 78 | p.DeviceGroupApp.Delete(ids) 79 | } 80 | -------------------------------------------------------------------------------- /apps/device/tsl/tsl.go: -------------------------------------------------------------------------------- 1 | package tsl 2 | 3 | const ( 4 | TypeEnum = "enum" // 枚举类型 5 | TypeInt = "int64" 6 | TypeString = "string" 7 | TypeBool = "bool" 8 | TypeFloat = "float64" 9 | TypeDate = "date" 10 | TypeStruct = "struct" 11 | ) 12 | 13 | // DefineAttribute 属性拓展 14 | type DefineAttribute struct { 15 | DefaultValue *string `json:"defaultValue"` // 属性时 16 | Rw *string `json:"rw"` 17 | } 18 | 19 | // DefineBase 基础类型参数 20 | type DefineBase struct { 21 | Max *float64 `json:"max" ` // 最大,数字类型:int64、float64 22 | Min *float64 `json:"min" ` // 最小,数字类型:int64、float64 23 | Step *float64 `json:"step"` // 小数位数,数字类型:int64、float64 24 | Decimals *int `json:"decimals"` // 小数位数,数字类型:float64 25 | Unit *string `json:"unit"` // 单位,数字类型: int64、float64 26 | 27 | MaxLength *int `json:"maxLength"` // 最大长度,字符类型:string 28 | 29 | DefineBool []DefineBool `json:"boolDefine"` 30 | Enums []DefineEnum `json:"enumDefine"` // 枚举类型:enum 31 | Struct []DefineStruct `json:"structDefine" ` // 对象类型:Struct 32 | } 33 | 34 | type DefineBool struct { 35 | Key string `json:"key"` // 键 0、1 36 | Value string `json:"value"` // 枚举值 37 | } 38 | 39 | type DefineEnum struct { 40 | Key string `json:"key"` // 键 0、1 41 | Value string `json:"value"` // 枚举值 42 | } 43 | 44 | // DefineStruct 扩展类型参数:对象型 45 | type DefineStruct struct { 46 | Key string `json:"key"` //参数标识 47 | Name string `json:"name"` // 参数名称 48 | ValueType ValueType `json:"valueType"` // 参数值 49 | Desc string `json:"desc"` // 描述 50 | } 51 | 52 | type ValueType struct { 53 | Type string `json:"type" dc:"数据类型" v:"required#请选择数据类型"` // 类型 54 | DefineBase // 参数 55 | } 56 | 57 | // DefineCommands 命令 58 | type DefineCommands struct { 59 | Key string `json:"key" dc:"功能标识" v:"required|regex:^[A-Za-z_]+[\\w]*$#请输入功能标识|标识由字母、数字和下划线组成,且不能以数字开头"` 60 | Name string `json:"name" dc:"功能名称" v:"required#请输入功能名称"` // 功能名称 61 | Inputs []DefineCommandsInput `json:"inputs" dc:"输入参数"` // 输入参数 62 | Output ValueType `json:"output" dc:"输出参数"` // 输出参数 63 | Desc string `json:"desc" dc:"描述"` // 描述 64 | } 65 | 66 | // DefineCommandsInput 命令:输入参数 67 | type DefineCommandsInput struct { 68 | Key string `json:"key"` 69 | Name string `json:"name"` // 输入参数名称 70 | ValueType ValueType `json:"valueType"` // 参数值 71 | Desc string `json:"desc"` // 描述 72 | } 73 | -------------------------------------------------------------------------------- /pkg/rule_engine/message/message1.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/google/uuid" 6 | "time" 7 | ) 8 | 9 | // 消息类型 10 | const ( 11 | ConnectMes = "Connect" 12 | DisConnectMes = "Disconnect" 13 | RpcRequestMes = "RpcRequest" 14 | UpEventMes = "Event" 15 | AlarmMes = "Alarm" 16 | RowMes = "Row" 17 | TelemetryMes = "Telemetry" 18 | AttributesMes = "Attributes" 19 | ) 20 | 21 | // 数据类型Originator 22 | const ( 23 | DEVICE = "DEVICE" 24 | GATEWAY = "GATEWAY" 25 | MONITOR = "MONITOR" //监控 26 | ) 27 | 28 | type Msg map[string]interface{} 29 | type Metadata map[string]interface{} 30 | 31 | type Message struct { 32 | Id string //uuid 消息Id 33 | Ts time.Time //时间戳 34 | MsgType string //消息类型, attributes(参数),telemetry(遥测),Connect连接事件 35 | UserId string //客户Id UUID 设备发布人 36 | Msg Msg //数据 数据结构JSON 设备原始数据 msg 37 | Metadata Metadata //消息的元数据 包括设备Id,设备类型,产品ID等 38 | } 39 | 40 | // NewMessage ... 41 | func NewMessage(userId, messageType string, msg Msg, metadata Metadata) *Message { 42 | return &Message{ 43 | Id: uuid.New().String(), 44 | Ts: time.Now(), 45 | UserId: userId, 46 | MsgType: messageType, 47 | Msg: msg, 48 | Metadata: metadata, 49 | } 50 | } 51 | 52 | func (t *Message) GetAllMap() map[string]interface{} { 53 | data := make(map[string]interface{}) 54 | for msgKey, msgValue := range t.Msg { 55 | for metaKey, metaValue := range t.Metadata { 56 | if msgKey == metaKey { 57 | data[msgKey] = metaValue 58 | } else { 59 | if _, ok := data[msgKey]; !ok { 60 | data[msgKey] = msgValue 61 | } 62 | if _, ok := data[metaKey]; !ok { 63 | data[metaKey] = metaValue 64 | } 65 | } 66 | } 67 | } 68 | return data 69 | } 70 | 71 | func (t *Message) MarshalBinary() ([]byte, error) { 72 | return json.Marshal(t) 73 | } 74 | 75 | func (meta *Metadata) Keys() []string { 76 | keys := make([]string, 0) 77 | for key := range *meta { 78 | keys = append(keys, key) 79 | } 80 | return keys 81 | } 82 | 83 | func (meta *Metadata) GetValue(key string) any { 84 | if _, found := (*meta)[key]; !found { 85 | return nil 86 | } 87 | return (*meta)[key] 88 | } 89 | 90 | func (meta *Metadata) SetValue(key string, val interface{}) { 91 | (*meta)[key] = val 92 | } 93 | 94 | func (msg *Msg) GetValue(key string) any { 95 | if _, found := (*msg)[key]; !found { 96 | return nil 97 | } 98 | return (*msg)[key] 99 | } 100 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/action_delay_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "fmt" 5 | "pandax/pkg/rule_engine/message" 6 | "sync" 7 | "time" 8 | 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | const DelayNodeName = "DelayNode" 13 | 14 | type delayNode struct { 15 | bareNode 16 | PeriodTs int `json:"periodTs" yaml:"periodTs" jpath:"periodTs"` //周期时间 17 | MaxPendingMessages int `json:"maxPendingMessages" yaml:"maxPendingMessages" jpath:"maxPendingMessages"` //最大等待消息数 18 | messageQueue []*message.Message `jpath:"-"` 19 | delayTimer *time.Timer `jpath:"-"` 20 | lock sync.Mutex `jpath:"-"` 21 | } 22 | 23 | type delayNodeFactory struct{} 24 | 25 | func (f delayNodeFactory) Name() string { return DelayNodeName } 26 | func (f delayNodeFactory) Category() string { return NODE_CATEGORY_ACTION } 27 | func (f delayNodeFactory) Labels() []string { return []string{"Success", "Failure"} } 28 | func (f delayNodeFactory) Create(id string, meta Metadata) (Node, error) { 29 | node := &delayNode{ 30 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 31 | lock: sync.Mutex{}, 32 | } 33 | _, err := decodePath(meta, node) 34 | node.messageQueue = make([]*message.Message, node.MaxPendingMessages) 35 | return node, err 36 | } 37 | 38 | func (n *delayNode) Handle(msg *message.Message) error { 39 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 40 | 41 | successLabelNode := n.GetLinkedNode("Success") 42 | failureLabelNode := n.GetLinkedNode("Failure") 43 | if successLabelNode == nil || failureLabelNode == nil { 44 | return fmt.Errorf("no valid label linked node in %s", n.Name()) 45 | } 46 | 47 | // check wethere the time had already been started, queue message if started 48 | if n.delayTimer == nil { 49 | n.messageQueue = append(n.messageQueue, msg) 50 | n.delayTimer = time.NewTimer(time.Duration(n.PeriodTs) * time.Second) 51 | 52 | go func(n *delayNode) error { 53 | defer n.delayTimer.Stop() 54 | for { 55 | <-n.delayTimer.C 56 | n.lock.Lock() 57 | defer n.lock.Unlock() 58 | if len(n.messageQueue) > 0 { 59 | msg := n.messageQueue[0] 60 | n.messageQueue = n.messageQueue[0:] 61 | return successLabelNode.Handle(msg) 62 | } 63 | } 64 | }(n) 65 | return nil 66 | } 67 | n.lock.Lock() 68 | defer n.lock.Unlock() 69 | if len(n.messageQueue) == n.MaxPendingMessages { 70 | return failureLabelNode.Handle(msg) 71 | } 72 | n.messageQueue = append(n.messageQueue, msg) 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /pkg/rule_engine/nodes/action_create_alarm_node.go: -------------------------------------------------------------------------------- 1 | package nodes 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/kakuilan/kgo" 6 | "github.com/sirupsen/logrus" 7 | "pandax/apps/device/entity" 8 | "pandax/apps/device/services" 9 | "pandax/pkg/global" 10 | "pandax/pkg/rule_engine/message" 11 | "time" 12 | ) 13 | 14 | type createAlarmNode struct { 15 | bareNode 16 | AlarmType string `json:"alarmType" yaml:"alarmType"` 17 | AlarmSeverity string `json:"alarmSeverity" yaml:"alarmSeverity"` 18 | } 19 | 20 | type createAlarmNodeFactory struct{} 21 | 22 | func (f createAlarmNodeFactory) Name() string { return "CreateAlarmNode" } 23 | func (f createAlarmNodeFactory) Category() string { return NODE_CATEGORY_ACTION } 24 | func (f createAlarmNodeFactory) Labels() []string { return []string{"Created", "Updated", "Failure"} } 25 | func (f createAlarmNodeFactory) Create(id string, meta Metadata) (Node, error) { 26 | node := &createAlarmNode{ 27 | bareNode: newBareNode(f.Name(), id, meta, f.Labels()), 28 | } 29 | return decodePath(meta, node) 30 | } 31 | 32 | func (n *createAlarmNode) Handle(msg *message.Message) error { 33 | logrus.Infof("%s handle message '%s'", n.Name(), msg.MsgType) 34 | 35 | created := n.GetLinkedNode("Created") 36 | updated := n.GetLinkedNode("Updated") 37 | failure := n.GetLinkedNode("Failure") 38 | 39 | alarm := services.DeviceAlarmModelDao.FindOneByType(msg.Metadata.GetValue("deviceId").(string), n.AlarmType, "0") 40 | if alarm.DeviceId != "" { 41 | marshal, _ := json.Marshal(msg.Msg) 42 | alarm.Details = string(marshal) 43 | err := services.DeviceAlarmModelDao.Update(*alarm) 44 | if err != nil { 45 | if failure != nil { 46 | return failure.Handle(msg) 47 | } 48 | } else { 49 | if updated != nil { 50 | return updated.Handle(msg) 51 | } 52 | } 53 | } else { 54 | alarm = &entity.DeviceAlarm{} 55 | alarm.Id = kgo.KStr.Uniqid("a") 56 | alarm.DeviceId = msg.Metadata.GetValue("deviceId").(string) 57 | alarm.ProductId = msg.Metadata.GetValue("productId").(string) 58 | alarm.Name = msg.Metadata.GetValue("deviceName").(string) 59 | alarm.Level = n.AlarmSeverity 60 | alarm.State = global.ALARMING 61 | alarm.Type = n.AlarmType 62 | alarm.Time = time.Now() 63 | marshal, _ := json.Marshal(msg.Msg) 64 | alarm.Details = string(marshal) 65 | err := services.DeviceAlarmModelDao.Insert(*alarm) 66 | if err != nil { 67 | if failure != nil { 68 | return failure.Handle(msg) 69 | } 70 | } else { 71 | if created != nil { 72 | return created.Handle(msg) 73 | } 74 | } 75 | } 76 | return nil 77 | } 78 | --------------------------------------------------------------------------------