add server

Change-Id: I0760f17f6a01c0121b59fcbfafc666032dbc30af
This commit is contained in:
liuhaijun 2024-09-18 18:34:47 +08:00
parent 3f5f28d785
commit e94826ce29
358 changed files with 27583 additions and 20 deletions

View File

@ -20,31 +20,14 @@ schedule:
postgres: postgres:
data: data:
enable: ${POSTGRES_DATA_ENABLE||false} enable: ${POSTGRES_DATA_ENABLE||false}
host: ${POSTGRES_DATA_HOST||10.110.63.138}
port: ${POSTGRES_DATA_PORT||5432}
database: ${POSTGRES_DATA_DATABASE||postgres}
username: ${POSTGRES_DATA_USERNAME||postgres}
password: ${POSTGRES_DATA_PASSWORD||Mypostgres!23}
log_level: ${LOG_LEVEL||4}
auth: auth:
enable: ${POSTGRES_AUTH_ENABLE||false} enable: ${POSTGRES_AUTH_ENABLE||false}
host: ${POSTGRES_AUTH_HOST||10.110.63.138}
port: ${POSTGRES_AUTH_PORT||5432}
database: ${POSTGRES_AUTH_DATABASE||auth}
username: ${POSTGRES_AUTH_USERNAME||postgres}
password: ${POSTGRES_AUTH_PASSWORD||Mypostgres!23}
log_level: ${LOG_LEVEL||4}
redis: redis:
enable: ${REDIS_ENABLE||false} enable: ${REDIS_ENABLE||false}
host: ${REDIS_HOST||127.0.0.1}
port: ${REDIS_POST||6379}
password: ${REDIS_PASSWORD||}
database: ${REDIS_DATABASE||0}
nats_config: nats_config:
# 10.110.63.81:30529 192.168.12.110 url: ${NATS_URL||}
url: ${NATS_URL||nats://10.110.63.81:30529} user: ${NATS_USER||}
user: ${NATS_USER||admin} password: ${NATS_PASSWORD||}
password: ${NATS_PASSWORD||T0pS3cr3tFGThjjds56}
logger: logger:

119
server/README.md Normal file
View File

@ -0,0 +1,119 @@
# go project种子项目
本项目基于 gin 框架为核心的一个脚手架搭建而成,可以基于本项目快速完成业务开发,开箱📦 即用。
HTTP 运行
拉取代码后在项目根目录执行如下命令:
# 本地有 go 环境,版本 >= 1.18.1
# 建议开启GO111MODULE
# go env -w GO111MODULE=on
# 配置Go Mod引用私有库
go env -w GOPRIVATE=git.inspur.com
go env -w GOINSECURE=git.inspur.com
go env -w GOPROXY="http://nexus.inspur.local/repository/go-public/,direct"
# 依赖管理
go mod tidy
# 依赖库下载
go mod vendor
# 首次运行会自动复制一份示例配置config/config.example.yaml文件到config目录(config/config.yaml)
go run main.go
# 项目起来后执行下面命令访问示例路由
curl "http://127.0.0.1:8899/api/v1/hello-world"
# {"code":0,"message":"OK","data":{"result":"hello gin-layout"},"cost":"6.151µs"}
curl "http://127.0.0.1:8899/api/v1/hello-world?name=world"
# {"code":0,"message":"OK","data":{"result":"hello world"},"cost":"6.87µs"}
部署
# 打包项目如何打包其他os平台的包自行 google
go build -o cmd/ main.go
# 运行时请配置指定config文件的位置否则可能会出现找不到配置的情况修改完配置请重启
cmd/main.go -c="指定配置文件位置(/home/config.yaml"
# nginx 反向代理配置示例
server {
listen 80;
server_name api.xxx.com;
location / {
proxy_set_header Host $host;
proxy_pass http://172.0.0.1:8899;
}
}
目录结构
.
|——.gitignore
|——go.mod
|——go.sum
|——main.go // 项目入口 main 包
|——LICENSE
|——README.md
|——doc // 设计和用户文档
|——db // 数据库脚本
|——hack // 用于执行各种构建,安装,分析等操作的脚本。
|——boot // 项目初始化目录
| └──boot.go
|——config // 这里通常维护一些本地调试用的样例配置文件
| └──autoload // 配置文件的结构体定义包
| └──app.go
| └──logger.go
| └──mysql.go
| └──redis.go
| └──server.go
| └──config.example.ini // .ini 配置示例文件
| └──config.example.yaml // .yaml 配置示例文件
| └──config.go // 配置初始化文件
|——data // 数据初始化目录
| └──data.go
| └──mysql.go
| └──redis.go
|——internal // 该服务所有不对外暴露的代码通常的业务逻辑都在这下面使用internal避免错误引用
| └──controller // 控制器代码
| └──v1
| └──auth.go // 完整流程演示代码,包含数据库表的操作
| └──helloword.go // 基础演示代码
| └──base.go
| └──middleware // 中间件目录
| └──cors.go
| └──logger.go
| └──recovery.go
| └──requestCost.go
| └──model // 业务数据访问
| └──admin_users.go
| └──base.go
| └──pkg // 内部使用包
| └──errors // 错误定义
| └──code.go
| └──en-us.go
| └──zh-cn.go
| └──logger // 日志处理
| └──logger.go
| └──response // 统一响应输出
| └──response.go
| └──routers // 路由定义
| └──apiRouter.go
| └──router.go
| └──service // 业务逻辑
| └──auth.go
| └──validator // 请求参数验证器
| └──form // 表单参数定义
| └──auth.go
| └──validator.go
|——pkg // 可以被外部使用的包
| └──convert // 数据类型转换
| └──convert.go
| └──utils // 帮助函数
| └──utils.go
生产环境注意事项
在构建生产环境时,请配置好 .yaml 文件中基础路径 base_path所有的日志记录文件会保存在该目录下的 {base_path}/gin-layout/logs/ 里面,该基础路径默认为执行命令的目录
其他说明
项目中使用到的包
核心gin
配置gopkg.in/yaml.v3、gopkg.in/ini.v1
参数验证github.com/go-playground/validator/v10
日志go.uber.org/zap、github.com/natefinch/lumberjack、github.com/lestrrat-go/file-rotatelogs
数据库gorm.io/gorm、go-redis/v8
还有其他不一一列举更多请查看go.mod文件

54
server/boot/boot.go Normal file
View File

@ -0,0 +1,54 @@
package boot
import (
"flag"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/data"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/event_engine"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"os"
)
var (
configPath string
printVersion bool
Run string
)
func init() {
flag.StringVar(&Run, "r", "http", "执行命令默认运行http服务")
flag.StringVar(&configPath, "c", "", "请输入配置文件绝对路径")
flag.BoolVar(&printVersion, "version", false, "查看版本")
flag.Parse()
if printVersion {
// 打印版本号
println(version)
os.Exit(0)
}
// 1、初始化配置
// 从环境变量读取配置文件路径
if envConfigPath := os.Getenv("CFN_SCHEDULE_CONFIG_PATH"); envConfigPath != "" {
configPath = envConfigPath
}
config.InitConfig(configPath)
// 2、初始化zap日志
log.InitLogger()
// 3、初始化数据库
data.InitData()
// 4、初始化验证器
validator.InitValidatorTrans("zh")
service.Start()
event_engine.Init()
app_manage.Init()
}

4
server/boot/version.go Normal file
View File

@ -0,0 +1,4 @@
package boot
// version is the current gin-layout version.
const version = "0.2.3"

26
server/config/app.go Normal file
View File

@ -0,0 +1,26 @@
package config
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/convert"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/utils"
)
type AppConfig struct {
AppEnv string `ini:"app_env" yaml:"app_env" mapstructure:"app_env"`
Debug bool `ini:"debug" yaml:"debug" mapstructure:"debug"`
Language string `ini:"language" yaml:"language" mapstructure:"language"`
StaticBasePath string `ini:"base_path" yaml:"base_path" mapstructure:"base_path"`
}
var App = AppConfig{
AppEnv: "local",
Debug: true,
Language: "zh_CN",
StaticBasePath: getDefaultPath(),
}
func getDefaultPath() (path string) {
path, _ = utils.GetDefaultPath()
path = convert.GetString(utils.If(path != "", path, "/tmp"))
return
}

15
server/config/auth.go Normal file
View File

@ -0,0 +1,15 @@
package config
type AuthConfig struct {
Enable bool `ini:"enable" yaml:"enable" mapstructure:"enable"`
Url string `ini:"url" yaml:"url" mapstructure:"url"`
LoginInfo string `ini:"login_info" yaml:"login_info" mapstructure:"login_info"`
LoginWithCode string `ini:"login_with_code" yaml:"login_with_code" mapstructure:"login_with_code"`
}
var Auth = AuthConfig{
Enable: true,
Url: "",
LoginInfo: "",
LoginWithCode: "",
}

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDozCCAougAwIBAgIUYr8WFHmf63SG6G2DjGBn4Nu0T1YwDQYJKoZIhvcNAQEL
BQAwYDELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUNob25nUWluZzEOMAwGA1UEBwwF
WXVCZWkxDzANBgNVBAoMBkluc3B1cjEOMAwGA1UECwwFcHRqc2IxDDAKBgNVBAMM
A3ljYjAgFw0yMzExMTUwNzQ0NTFaGA8yMTIzMTAyMjA3NDQ1MVowYDELMAkGA1UE
BhMCQ04xEjAQBgNVBAgMCUNob25nUWluZzEOMAwGA1UEBwwFWXVCZWkxDzANBgNV
BAoMBkluc3B1cjEOMAwGA1UECwwFcHRqc2IxDDAKBgNVBAMMA3ljYjCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBANjEiLH20u8zacfn++ytfsYi3p+ygtHN
KvXBYEntxYmURngKndOYWwR0v1wy5RQkaCmOjn6lIIoUf7G9uNT/mrumLJRi98Sx
AR7AGhgUjHo9cuF/NjtIHstc64F2gGdzJeEfg0IEk1T5SSOMOs0FH0jWC24iu8Xa
Jz5kFGFJy0iwHdQWcasdx0Dg8TxbGJkBsn6P+Li5xDWCrYu9jEgmm5c4Yd+cN8uF
eJGUWqjEDCG/DfSZNzgi5wQAGIU5Zc738SJVsnqJzuutrYXiuk+68EoHwQ2/nlI0
fIl4hAT7MmxrlXJNT/kdCPaj1xMkCxMlxmdXVrtWJexZ7X/MkJLgVycCAwEAAaNT
MFEwHQYDVR0OBBYEFKcn6Z9mtF2cvTNT0IY/xbtNhmM5MB8GA1UdIwQYMBaAFKcn
6Z9mtF2cvTNT0IY/xbtNhmM5MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAC3GKL46KfY/5dCDpZK2Az70pQ+LUMAzteZn7wuTmhLghKUdGRgoVz4b
j6IvV6gr5cR64Y8+55rP9TqbGZC8XF/dwxl9kn/hp2aPcrLroyCqqRWZ4TECRh9R
/7IIMX9+vwt3FLM0bJaQchiOLMawwWXeizakB/+6LZHssK8L/Qiwx1uXUq24mJox
hgfvxUtYoF3BLv3zqHojqN/zAqKZrsiYEQMv0HgCBCOuWRXRsWMiKY7US6UrhLY4
kaooXtGN8e6wTGnB4LN2fhQ+oAvVOzJUEnCR+AUwWMjbzOk0JZBFFxe2zpANa4yX
4iGDpSkm4XgtV0USyx+T+iiA5BEvqps=
-----END CERTIFICATE-----

125
server/config/config.go Normal file
View File

@ -0,0 +1,125 @@
package config
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/config/customized"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/utils"
"github.com/spf13/viper"
"os"
"path/filepath"
"sync"
)
// The valid auth strategies and values for cookie handling
const (
// These constants are used for external services auth (Prometheus ...) ;
AuthTypeBasic = "basic"
AuthTypeBearer = "bearer"
AuthTypeNone = "none"
)
// Conf 配置项主结构体
// mapstructure(配置文件属性名称无法与类属性名称直接对应)
type Conf struct {
AppConfig `yaml:"app" mapstructure:"app"`
Server ServerConfig `yaml:"server" mapstructure:"server"`
WebEmbed bool `yaml:"web_embed" mapstructure:"web_embed"`
DatabaseConfig DatabaseConfig `yaml:"database" mapstructure:"database"`
Redis RedisConfig `yaml:"redis" mapstructure:"redis"`
Logger LoggerConfig `yaml:"logger" mapstructure:"logger"`
Minio MinioConfig `yaml:"minio" mapstructure:"minio"`
Nats NatsConfig `yaml:"nats" mapstructure:"nats"`
Prometheus PrometheusConfig `yaml:"prometheus" mapstructure:"prometheus"`
XxlJob XxlJobConfig `yaml:"xxljob" mapstructure:"xxljob"`
SchemeService SchemeServiceConfig `yaml:"scheme_service" mapstructure:"scheme_service"`
Auth AuthConfig `yaml:"auth" mapstructure:"auth"`
CfnConfig customized.CfnConfig `yaml:"cfn_config" mapstructure:"cfn_config"`
}
var Config = &Conf{
AppConfig: App,
Server: Server,
DatabaseConfig: Database,
Redis: Redis,
Logger: Logger,
Minio: Minio,
Nats: Nats,
Prometheus: Prometheus,
XxlJob: XxlJob,
SchemeService: SchemeService,
Auth: Auth,
CfnConfig: customized.Cfn,
}
var once sync.Once
func InitConfig(configPath string) {
once.Do(func() {
// 加载 .yaml 配置
loadYaml(configPath)
})
}
// todo 环境变量注入配置 or 与nacos集成
func loadYaml(configPath string) {
var yamlConfig string
if configPath == "" {
yamlConfig = filepath.Join(utils.GetRunPath(), "config/config.yaml")
} else {
yamlConfig = filepath.Join(configPath)
}
viper.SetConfigFile(yamlConfig)
viper.SetConfigType("yaml")
err := viper.ReadInConfig()
if err != nil {
panic("Failed to read configuration file:" + err.Error())
}
for _, key := range viper.AllKeys() {
value := viper.GetString(key)
realValue := expandValueEnv(value)
if value != realValue {
viper.Set(key, realValue)
}
}
err = viper.Unmarshal(Config)
if err != nil {
panic("Failed to load configuration:" + err.Error())
}
}
func expandValueEnv(value string) (realValue string) {
realValue = value
vLen := len(value)
// 3 = ${}
if vLen < 3 {
return
}
// Need start with "${" and end with "}", then return.
if value[0] != '$' || value[1] != '{' || value[vLen-1] != '}' {
return
}
key := ""
defaultV := ""
// value start with "${"
for i := 2; i < vLen; i++ {
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
key = value[2:i]
defaultV = value[i+2 : vLen-1] // other string is default value.
break
} else if value[i] == '}' {
key = value[2:i]
break
}
}
realValue = os.Getenv(key)
if realValue == "" {
realValue = defaultV
}
return
}

View File

@ -0,0 +1,13 @@
package customized
type CfnConfig struct {
Enable bool `ini:"enable" yaml:"enable" mapstructure:"enable"`
CfnWorkSpaceId string `ini:"cfn_workspace_id" yaml:"cfn_workspace_id" mapstructure:"cfn_workspace_id"`
AgnetSilentUpgrade bool `ini:"agent_silent_upgrade" yaml:"agent_silent_upgrade" mapstructure:"agent_silent_upgrade"`
}
var Cfn = CfnConfig{
Enable: false,
CfnWorkSpaceId: "cfn_workspace_id_default",
AgnetSilentUpgrade: false,
}

13
server/config/database.go Normal file
View File

@ -0,0 +1,13 @@
package config
type DatabaseConfig struct {
Type string `ini:"type" yaml:"type" mapstructure:"type"`
SqliteConfig SqliteConfig `ini:"sqlite" yaml:"sqlite" mapstructure:"sqlite"`
PostgreSqlConfig PostgreSqlConfigMap `ini:"postgres" yaml:"postgres" mapstructure:"postgres"`
}
var Database = DatabaseConfig{
Type: "sqlite",
SqliteConfig: Sqlite,
PostgreSqlConfig: PostgreSql,
}

35
server/config/logger.go Normal file
View File

@ -0,0 +1,35 @@
package config
type DivisionTime struct {
MaxAge int `ini:"max_age" yaml:"max_age" mapstructure:"max_age"` // 保留旧文件的最大天数,单位天
RotationTime int `ini:"rotation_time" yaml:"rotation_time" mapstructure:"rotation_time"` // 多长时间切割一次文件,单位小时
}
type DivisionSize struct {
MaxSize int `ini:"max_size" yaml:"max_size" mapstructure:"max_size"` // 在进行切割之前日志文件的最大大小以MB为单位
MaxBackups int `ini:"max_backups" yaml:"max_backups" mapstructure:"max_backups"` // 保留旧文件的最大个数
MaxAge int `ini:"max_age" yaml:"max_age" mapstructure:"max_age"` // 保留旧文件的最大天数
Compress bool `ini:"compress" yaml:"compress"` // 是否压缩/归档旧文件
}
type LoggerConfig struct {
DefaultDivision string `ini:"default_division" yaml:"default_division" mapstructure:"default_division"`
Filename string `ini:"file_name" yaml:"file_name" mapstructure:"file_name"`
DivisionTime DivisionTime `ini:"division_time" yaml:"division_time" mapstructure:"division_time"`
DivisionSize DivisionSize `ini:"division_size" yaml:"division_size" mapstructure:"division_size"`
}
var Logger = LoggerConfig{
DefaultDivision: "time", // time 按时间切割,默认一天, size 按文件大小切割
Filename: "sys.log",
DivisionTime: DivisionTime{
MaxAge: 15,
RotationTime: 24,
},
DivisionSize: DivisionSize{
MaxSize: 2,
MaxBackups: 2,
MaxAge: 15,
Compress: false,
},
}

13
server/config/minio.go Normal file
View File

@ -0,0 +1,13 @@
package config
type MinioConfig struct {
Endpoint string `ini:"endpoint" yaml:"endpoint"`
AccessKey string `ini:"access_key" yaml:"access_key" mapstructure:"access_key"`
SecretAccessKey string `ini:"secret_access_key" yaml:"secret_access_key" mapstructure:"secret_access_key"`
}
var Minio = MinioConfig{
Endpoint: "192.168.12.78:208670",
AccessKey: "xxxxxxxx",
SecretAccessKey: "yyyyyyyy",
}

13
server/config/nats.go Normal file
View File

@ -0,0 +1,13 @@
package config
type NatsConfig struct {
Url string `ini:"url" yaml:"url"`
User string `ini:"user" yaml:"user"`
Password string `ini:"password" yaml:"password"`
}
var Nats = NatsConfig{
Url: "nats://192.168.12.110:30529",
User: "admin",
Password: "T0pS3cr3tFGThjjds56",
}

37
server/config/postgres.go Normal file
View File

@ -0,0 +1,37 @@
package config
type PostgreSqlConfig struct {
Host string `ini:"host" yaml:"host"`
Username string `ini:"username" yaml:"username"`
Password string `ini:"password" yaml:"password"`
Port uint16 `ini:"port" yaml:"port"`
Database string `ini:"database" yaml:"database"`
TablePrefix string `ini:"table_prefix" yaml:"table_prefix" mapstructure:"table_prefix"`
LogLevel int `ini:"log_level" yaml:"log_level" mapstructure:"log_level"`
PrintSql bool `ini:"print_sql" yaml:"print_sql" mapstructure:"print_sql"`
}
type PostgreSqlConfigMap map[string]PostgreSqlConfig
var PostgreSql = PostgreSqlConfigMap{
"data": {
Host: "127.0.0.1",
Username: "root",
Password: "root1234",
Port: 3306,
Database: "test",
TablePrefix: "",
LogLevel: 4,
PrintSql: false,
},
"auth": {
Host: "127.0.0.1",
Username: "root",
Password: "root1234",
Port: 3306,
Database: "test",
TablePrefix: "",
LogLevel: 4,
PrintSql: false,
},
}

View File

@ -0,0 +1,15 @@
package config
type PrometheusConfig struct {
Url string `ini:"url" yaml:"url" mapstructure:"url"`
AuthEnable bool `ini:"auth_enable" yaml:"auth_enable" mapstructure:"auth_enable"`
User string `ini:"user" yaml:"user" mapstructure:"user"`
Password string `ini:"password" yaml:"password" mapstructure:"password"`
}
var Prometheus = PrometheusConfig{
Url: "http://127.0.0.1:9090",
AuthEnable: false,
User: "admin",
Password: "admin",
}

17
server/config/redis.go Normal file
View File

@ -0,0 +1,17 @@
package config
type RedisConfig struct {
Enable bool `ini:"enable" yaml:"enable"`
Host string `ini:"host" yaml:"host"`
Port string `ini:"port" yaml:"port"`
Password string `ini:"password" yaml:"password"`
Database int `ini:"database" yaml:"database"`
}
var Redis = RedisConfig{
Enable: false,
Host: "127.0.0.1",
Password: "root1234",
Port: "6379",
Database: 0,
}

View File

@ -0,0 +1,12 @@
package config
// ServerConfig 定义项目配置
type SchemeServiceConfig struct {
BaseUrl string `ini:"base_url" yaml:"base_url" mapstructure:"base_url"`
Token string `ini:"token" yaml:"token" mapstructure:"token"`
}
var SchemeService = SchemeServiceConfig{
BaseUrl: "127.0.0.1:9999",
Token: "mock dkf98123",
}

12
server/config/server.go Normal file
View File

@ -0,0 +1,12 @@
package config
// ServerConfig 定义项目配置
type ServerConfig struct {
Host string `ini:"host" yaml:"host" mapstructure:"host"`
Port uint64 `ini:"port" yaml:"port" mapstructure:"port"`
}
var Server = ServerConfig{
Host: "127.0.0.1",
Port: 9999,
}

9
server/config/sqlite.go Normal file
View File

@ -0,0 +1,9 @@
package config
type SqliteConfig struct {
Data string `ini:"data" yaml:"data" mapstructure:"data"`
}
var Sqlite = SqliteConfig{
Data: "data/sqlite.db",
}

11
server/config/xxljob.go Normal file
View File

@ -0,0 +1,11 @@
package config
type XxlJobConfig struct {
Url string `ini:"url" yaml:"url"`
Token string `ini:"token" yaml:"token"`
}
var XxlJob = XxlJobConfig{
Url: "http://127.0.0.1/xxl-job-admin",
Token: "xxx",
}

26
server/data/data.go Normal file
View File

@ -0,0 +1,26 @@
package data
import (
c "git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"sync"
)
var once sync.Once
func InitData() {
once.Do(func() {
if c.Config.DatabaseConfig.Type == "sqlite" {
initSqlite()
} else {
initPostgres()
}
initNats()
initPrometheus()
initXxlJob()
if c.Config.Redis.Enable {
// 初始化 redis
initRedis()
}
})
}

43
server/data/nats.go Normal file
View File

@ -0,0 +1,43 @@
package data
import (
c "git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/nats-io/nats.go"
"time"
)
var Nc *nats.Conn
func initNats() {
var err error
// 此种链接方式在Ubuntu桌面版上报错dial tcp: missing address
//url := fmt.Sprintf("nats://%s:%s@%s", c.Config.Nats.User, c.Config.Nats.Password, c.Config.Nats.Url)
//Nc, err = nats.Connect(url)
opts := []nats.Option{nats.Name("NATS Sample Publisher")}
opts = append(opts, nats.UserInfo(c.Config.Nats.User, c.Config.Nats.Password))
log.Infof("Nats connect url: %s", "nats://"+c.Config.Nats.Url)
log.Infof("Nats connect options: %v", opts)
Nc, err = nats.Connect("nats://"+c.Config.Nats.Url, opts...)
if err != nil {
log.Errorf("连接nats失败%s, %s, %s", c.Config.Nats.Url, c.Config.Nats.User, c.Config.Nats.Password)
panic("Nats connection failed" + err.Error())
}
go func() {
defer func() {
if r := recover(); r != nil {
log.Errorf("recover check nats connection: %v", r)
}
}()
const interval = time.Minute * 2
ticker := time.NewTicker(interval)
for _ = range ticker.C {
if !Nc.IsConnected() {
Nc, _ = nats.Connect("nats://"+c.Config.Nats.Url, opts...)
}
}
}()
}

70
server/data/postgres.go Normal file
View File

@ -0,0 +1,70 @@
package data
import (
"fmt"
c "git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
var PostgreSqlDB = make(map[string]*gorm.DB)
type Writer interface {
Printf(string, ...interface{})
}
type WriterLog struct {
}
func (w WriterLog) Printf(format string, args ...interface{}) {
log.Logger.Sugar().Infof(format, args...)
}
func initPostgres() {
var err error
postgreSqlConfigMap := c.Config.DatabaseConfig.PostgreSqlConfig
if c.Config.DatabaseConfig.PostgreSqlConfig != nil && len(postgreSqlConfigMap) > 0 {
for name, postgreSqlConfig := range postgreSqlConfigMap {
var writerLog WriterLog
if postgreSqlConfig.PrintSql {
writerLog = WriterLog{}
}
logConfig := logger.New(
writerLog,
logger.Config{
SlowThreshold: 0, // 慢 SQL 阈值
LogLevel: logger.LogLevel(postgreSqlConfig.LogLevel), // 日志级别
IgnoreRecordNotFoundError: false, // 忽略ErrRecordNotFound记录未找到错误
Colorful: false, // 是否启用彩色打印
},
)
configs := &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: postgreSqlConfig.TablePrefix, // 表名前缀
// SingularTable: true, // 使用单数表名
},
Logger: logConfig,
}
dsn := fmt.Sprintf("host=%s port=%d user=%s dbname=%s password=%s timezone='Asia/Shanghai'",
postgreSqlConfig.Host,
postgreSqlConfig.Port,
postgreSqlConfig.Username,
postgreSqlConfig.Database,
postgreSqlConfig.Password,
)
log.Infof("postgres客户端初始化, 配置: %s", dsn)
PostgreSqlDB[name], err = gorm.Open(postgres.Open(dsn), configs)
if err != nil {
panic("postgres connection failed" + err.Error())
}
}
}
}

61
server/data/prometheus.go Normal file
View File

@ -0,0 +1,61 @@
package data
import (
"encoding/base64"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/prometheus/client_golang/api"
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
"net"
"net/http"
"time"
)
var V1api v1.API
type authRoundTripper struct {
auth string
originalRT http.RoundTripper
}
func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", rt.auth)
return rt.originalRT.RoundTrip(req)
}
func newAuthRoundTripper(auth *config.PrometheusConfig, rt http.RoundTripper) http.RoundTripper {
encoded := base64.StdEncoding.EncodeToString([]byte(auth.User + ":" + auth.Password))
return &authRoundTripper{auth: "Basic " + encoded, originalRT: rt}
}
func initPrometheus() {
if !config.Config.CfnConfig.Enable {
return
}
apiConfig := api.Config{
Address: config.Config.Prometheus.Url,
}
if config.Config.Prometheus.AuthEnable {
roundTripper := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
}
apiConfig.RoundTripper = newAuthRoundTripper(&config.Config.Prometheus, roundTripper)
}
log.Infof("Prometheus客户端初始化, 配置: %s", apiConfig.Address)
client, err := api.NewClient(apiConfig)
if err != nil {
log.Info("Error creating prometheus client: ", err.Error())
}
V1api = v1.NewAPI(client)
}

23
server/data/redis.go Normal file
View File

@ -0,0 +1,23 @@
package data
import (
"context"
c "git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"github.com/go-redis/redis/v8"
)
var Rdb *redis.Client
func initRedis() {
Rdb = redis.NewClient(&redis.Options{
Addr: c.Config.Redis.Host + ":" + c.Config.Redis.Port,
Password: c.Config.Redis.Password,
DB: c.Config.Redis.Database,
})
var ctx = context.Background()
_, err := Rdb.Ping(ctx).Result()
if err != nil {
panic("Redis connection failed" + err.Error())
}
}

22
server/data/sqlite.go Normal file
View File

@ -0,0 +1,22 @@
package data
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var SqliteDB *gorm.DB
func initSqlite() {
var err error
sqliteConfig := config.Config.DatabaseConfig.SqliteConfig
log.Infof("sqlite客户端初始化, 配置: %s", sqliteConfig.Data)
SqliteDB, err = gorm.Open(sqlite.Open(sqliteConfig.Data), &gorm.Config{})
if err != nil {
panic("sqlite connection failed" + err.Error())
}
}

50
server/data/xxljob.go Normal file
View File

@ -0,0 +1,50 @@
package data
import (
"fmt"
c "git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/xxl-job/xxl-job-executor-go"
"strconv"
)
var Xxljob xxl.Executor
func initXxlJob() {
if !c.Config.CfnConfig.Enable {
return
}
//初始化执行器
Xxljob = xxl.NewExecutor(
xxl.ServerAddr(c.Config.XxlJob.Url),
xxl.AccessToken(c.Config.XxlJob.Token),
xxl.ExecutorPort(strconv.FormatUint(c.Config.Server.Port, 10)), //默认9999此处要与gin服务启动port必需一至
xxl.RegistryKey("cfn-schedule"), //执行器名称
xxl.SetLogger(&clogger{}), //自定义日志
)
Xxljob.Init()
//设置日志查看handler
Xxljob.LogHandler(customLogHandle)
}
// 自定义日志处理器
func customLogHandle(req *xxl.LogReq) *xxl.LogRes {
return &xxl.LogRes{Code: xxl.SuccessCode, Msg: "", Content: xxl.LogResContent{
FromLineNum: req.FromLineNum,
ToLineNum: 2,
LogContent: "这个是自定义日志handler",
IsEnd: true,
}}
}
// xxl.Logger接口实现
type clogger struct{}
func (l *clogger) Info(format string, a ...interface{}) {
log.Info(fmt.Sprintf("xxl-job日志 - "+format, a...))
}
func (l *clogger) Error(format string, a ...interface{}) {
log.Error(fmt.Sprintf("xxl-job日志 - "+format, a...))
}

112
server/go.mod Normal file
View File

@ -0,0 +1,112 @@
module git.inspur.com/sbg-jszt/cfn/cfn-schedule
go 1.23
toolchain go1.23.0
require (
github.com/araujo88/lambda-go v0.0.0-20240731183007-b33464d3e215
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.8.1
github.com/go-playground/locales v0.14.0
github.com/go-playground/universal-translator v0.18.0
github.com/go-playground/validator/v10 v10.11.1
github.com/go-redis/redis/v8 v8.11.5
github.com/go-resty/resty/v2 v2.10.0
github.com/golang/glog v1.0.0
github.com/google/uuid v1.4.0
github.com/gorilla/websocket v1.5.3
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/minio/minio-go v6.0.14+incompatible
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/nats-io/nats.go v1.31.0
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/common v0.44.0
github.com/renstrom/shortuuid v3.0.0+incompatible
github.com/robfig/cron/v3 v3.0.1
github.com/segmentio/ksuid v1.0.4
github.com/spf13/viper v1.17.0
github.com/xuri/excelize/v2 v2.8.0
github.com/xxl-job/xxl-job-executor-go v1.2.0
github.com/zeromicro/go-zero v1.6.0
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.21.0
gorm.io/datatypes v1.2.0
gorm.io/driver/postgres v1.5.2
gorm.io/driver/sqlite v1.4.3
gorm.io/gorm v1.25.0
gorm.io/plugin/soft_delete v1.1.0
)
require (
github.com/fatih/color v1.15.0 // indirect
github.com/go-basic/ipv4 v1.0.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/nats-io/nkeys v0.4.6 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/richardlehane/mscfb v1.0.4 // indirect
github.com/richardlehane/msoleps v1.0.3 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
golang.org/x/time v0.5.0 // indirect
gorm.io/driver/mysql v1.4.7 // indirect
)
require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/ahmetb/go-linq v3.0.0+incompatible
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/goccy/go-json v0.9.11
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.4.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lestrrat-go/strftime v1.0.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/gomega v1.20.1 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pkg/errors v0.9.1
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.14.0
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

773
server/go.sum Normal file
View File

@ -0,0 +1,773 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ahmetb/go-linq v3.0.0+incompatible h1:qQkjjOXKrKOTy83X8OpRmnKflXKQIL/mC/gMVVDMhOA=
github.com/ahmetb/go-linq v3.0.0+incompatible/go.mod h1:PFffvbdbtw+QTB0WKRP0cNht7vnCfnGlEpak/DVg5cY=
github.com/araujo88/lambda-go v0.0.0-20240731183007-b33464d3e215 h1:cuA5Yg1IPMUxWXwuu4dW7tZhjgFI2Lkv1qIaMhHV/TM=
github.com/araujo88/lambda-go v0.0.0-20240731183007-b33464d3e215/go.mod h1:aFEe3R1EyXWn1ikzW0zgjqWUvhHxwwfhLvhXMb/z354=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/go-basic/ipv4 v1.0.0 h1:gjyFAa1USC1hhXTkPOwBWDPfMcUaIM+tvo1XzV9EZxs=
github.com/go-basic/ipv4 v1.0.0/go.mod h1:etLBnaxbidQfuqE6wgZQfs38nEWNmzALkxDZe4xY8Dg=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o=
github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E=
github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8=
github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY=
github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/renstrom/shortuuid v3.0.0+incompatible h1:F6T1U7bWlI3FTV+JE8HyeR7bkTeYZJntqQLA9ST4HOQ=
github.com/renstrom/shortuuid v3.0.0+incompatible/go.mod h1:n18Ycpn8DijG+h/lLBQVnGKv1BCtTeXo8KKSbBOrQ8c=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg=
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.8.0 h1:Vd4Qy809fupgp1v7X+nCS/MioeQmYVVzi495UCTqB7U=
github.com/xuri/excelize/v2 v2.8.0/go.mod h1:6iA2edBTKxKbZAa7X5bDhcCg51xdOn1Ar5sfoXRGrQg=
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a h1:Mw2VNrNNNjDtw68VsEj2+st+oCSn4Uz7vZw6TbhcV1o=
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/xxl-job/xxl-job-executor-go v1.2.0 h1:MTl2DpwrK2+hNjRRks2k7vB3oy+3onqm9OaSarneeLQ=
github.com/xxl-job/xxl-job-executor-go v1.2.0/go.mod h1:bUFhz/5Irp9zkdYk5MxhQcDDT6LlZrI8+rv5mHtQ1mo=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeromicro/go-zero v1.6.0 h1:UwSOR1lGZ2g7L0S07PM8RoneAcubtd5x//EfbuNucQ0=
github.com/zeromicro/go-zero v1.6.0/go.mod h1:E9GCFPb0SwsTKFBcFr9UynGvXiDMmfc6fI5F15vqvAQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco=
gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04=
gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y=
gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/plugin/soft_delete v1.1.0 h1:LcE4L+GD29RkkMLxMYHpT4wQCJ/9945FsdU/mHGaDuE=
gorm.io/plugin/soft_delete v1.1.0/go.mod h1:Zv7vQctOJTGOsJ/bWgrN1n3od0GBAZgnLjEx+cApLGk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -0,0 +1,30 @@
package command
import (
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/func_make"
)
var (
commandMap = map[string]interface{}{
"demo": demo,
}
funcMake = func_make.New()
)
func Register() {
err := funcMake.Registers(commandMap)
if err != nil {
panic("failed to register console command: " + err.Error())
}
}
func Run(funcName string) {
Register()
_, err := funcMake.Call(funcName)
if err != nil {
fmt.Printf("execution failed, error cause: %v \n", err.Error())
return
}
fmt.Printf("complete! \n")
}

View File

@ -0,0 +1,7 @@
package command
import "fmt"
func demo() {
fmt.Println("hello console!")
}

View File

@ -0,0 +1,54 @@
package controller
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
r "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/response"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// Success 业务成功响应
func Success(c *gin.Context, data ...any) {
response := r.Resp()
if data != nil {
response.WithDataSuccess(c, data[0])
return
}
response.Success(c)
}
// FailCode 业务失败响应
func FailCode(c *gin.Context, code int, err error, data ...any) {
response := r.Resp()
if err != nil {
log.Errorf("异常: %v", err.Error())
}
if data != nil {
response.WithData(data[0]).FailCode(c, code)
return
}
response.FailCode(c, code)
}
// Fail 业务失败响应
func Fail(c *gin.Context, code int, message string, data ...any) {
response := r.Resp()
if data != nil {
response.WithData(data[0]).FailCode(c, code, message)
return
}
response.FailCode(c, code, message)
}
// Err 判断错误类型是自定义类型则自动返回错误中携带的code和message否则返回服务器错误
func Err(c *gin.Context, e error) {
businessError, err := errors.AsBusinessError(e)
if err != nil {
log.Logger.Warn("Unknown error:", zap.Any("Error reason:", err))
FailCode(c, errors.ServerError, err)
return
}
Fail(c, businessError.GetCode(), businessError.GetMessage())
}

View File

@ -0,0 +1,137 @@
package v1
import (
"encoding/json"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/agent"
devc "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/device"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"github.com/gin-gonic/gin"
"strconv"
"strings"
)
// 获取最新版本列表
func ListLastVersion(c *gin.Context) {
aget := &agent.AgentInfo{}
agents, err := aget.ListLastVersion()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, agents)
}
// 发布新版本
func Release(c *gin.Context) {
aget := &agent.AgentInfo{}
err := validator.CheckPostParams(c, aget)
if aget.BucketName == "" {
aget.BucketName = "cpn"
}
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
//转换版本号
masterVersion := "10"
secondVersion := "0"
minVersion := "0"
version := strings.Split(aget.Version, ".")
if len(version[0]) < 2 {
masterVersion = masterVersion + version[0]
} else {
masterVersion = "1" + version[0]
}
if len(version[1]) < 2 {
secondVersion = secondVersion + version[1]
} else {
secondVersion = version[1]
}
if len(version[2]) < 2 {
minVersion = minVersion + version[2]
} else {
minVersion = version[2]
}
versionNum, err := strconv.Atoi(masterVersion + secondVersion + minVersion)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
agents := make([]agent.AgentInfo, 0)
agetJson, _ := json.Marshal(aget)
for _, t := range strings.Split(aget.OsType, ",") {
fields := map[string]interface{}{}
fields["os_type"] = t
fields["arch_type"] = aget.ArchType
fields["version_num"] = versionNum
b, err := aget.HasNewVersion(fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
if b {
controller.FailCode(c, errors.InvalidParameter, err, "版本号重复或小于历史版本!")
return
}
agt := agent.AgentInfo{}
json.Unmarshal(agetJson, &agt)
agt.OsType = t
agt.VersionNum = int64(versionNum)
if t == agent.OS_WINDOWS {
agt.OsPlatform = agent.OS_WINDOWS
} else if strings.Contains(agent.OS_LINUX_LIST, t) {
agt.OsPlatform = agent.OS_LINUX
} else {
agt.OsPlatform = "Other"
}
//不同平台+不同操作系统+不同架构添加多条记录
agents = append(agents, agt)
}
_, err = aget.CreateBatch(agents)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
go func() {
_ = service.SendUpgradeCommand(agents)
}()
go func() {
dr := &devc.DeviceUpgradeRule{}
rules, err := dr.ListAll(nil)
if err == nil && len(rules) > 0 {
_ = service.SendUpgradePlans(rules, "create")
}
}()
controller.Success(c, "")
}
type PageBodyVersion struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
Keyword string `json:"keyword"`
}
// 根据扩展key获取规则
func PageVersions(c *gin.Context) {
var pageBody PageBodyVersion
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
agt := &agent.AgentInfo{}
fields := map[string]interface{}{}
fields["keyword"] = pageBody.Keyword
page := &model.Page[agent.AgentInfo]{}
page.Covert(pageBody.PageInfo)
err = agt.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}

View File

@ -0,0 +1,504 @@
package assets
import (
"bytes"
"encoding/json"
err1 "errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
node2 "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/workspace"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/assets"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"github.com/gin-gonic/gin"
"strconv"
"strings"
)
type MachinePageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
Name string `json:"name"`
Version string `json:"version"`
GroupName string `json:"groupName"`
}
type T struct {
GroupName string `json:"groupName"`
Version string `json:"version"`
Name string `json:"name"`
PageInfo struct {
CurrentPage int `json:"currentPage"`
Order string `json:"order"`
OrderField string `json:"order_field"`
PageSize int `json:"pageSize"`
} `json:"pageInfo"`
}
func PageMachine(c *gin.Context) {
var pageBody MachinePageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
//pageBody.PageInfo.Order = "create_time_millis desc"
n := node2.NewMachine()
fields := map[string]interface{}{}
if pageBody.Name != "" {
fields["name"] = pageBody.Name
}
if pageBody.Version != "" {
fields["cfn_agent_version"] = pageBody.Version
}
if pageBody.GroupName != "" {
fields["group_name"] = pageBody.GroupName
}
page := &model.PageConfig{}
page.Covert(pageBody.PageInfo)
result, err := n.Page(n, page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
page.Data = result
controller.Success(c, page)
}
func EditMachine(c *gin.Context) {
n := &node2.MachineNode{}
err := validator.CheckPostParams(c, n)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if n.Id == "" {
n.Handler = n
fields := map[string]interface{}{}
fields["cfn_agent_url"] = n.CfnAgentUrl
list, err := n.GetAll(n, fields, nil)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
if len(list.([]node2.MachineNode)) > 0 {
controller.FailCode(c, errors.InvalidParameter, err, "对应的节点已经存在啦")
return
}
}
err = assets.TestMachineNode(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "节点连接失败,请检查节点是否在线")
return
}
status := 1
n.Status = status
// testHttpProxy()
if n.Id == "" {
m := 0
n.TransportMode = m
n.SetUserName(service.GetUserName(c))
_, err = n.Create(n)
} else {
n.SetModifyUserName(service.GetUserName(c))
column := []string{"name", "group_name", "cfn_agent_url", "cfn_agent_username", "cfn_agent_password", "template_node", "cfn_agent_timeout", "cfn_agent_http_proxy", "cfn_agent_http_proxy_type", "transport_encryption"}
_, err = n.Update(n, column)
}
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, "")
}
func DeleteMachine(c *gin.Context) {
id := c.PostForm("id")
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
m := &node2.MachineNode{}
m.Id = id
err := m.GetById(m)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := node2.New()
fields := make(map[string]interface{})
fields["machine_id"] = id
list, err := n.GetAll(n, fields, nil)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
count := len(list.([]node2.Node))
if count > 0 {
controller.FailCode(c, errors.ServerError, err, "当前机器还关联"+strconv.Itoa(count)+"个节点,不能删除")
return
}
_, err = m.Delete(m)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, "")
}
func Distribute(c *gin.Context) {
ids := c.PostForm("ids")
if ids == "" {
controller.Success(c, "")
return
}
mids := strings.Split(ids, ",")
if len(mids) == 0 {
controller.Success(c, "")
return
}
workspaceId := c.PostForm("workspaceId")
w := &workspace.Workspace{}
w.Id = workspaceId
err := w.GetById(w)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "工作空间不存在!")
return
}
fields := map[string]interface{}{}
fields["workspace_id"] = workspaceId
for _, mid := range mids {
m := &node2.MachineNode{}
m.Id = mid
err = m.GetById(m)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "机器不存在!")
return
}
n := node2.New()
fields["machine_id"] = mid
list, _ := n.GetAll(n, fields, nil)
if len(list.([]node2.Node)) > 0 {
continue
}
n = &node2.Node{}
n.MachineId = m.Id
n.WorkspaceId = workspaceId
n.Name = m.Name
n.OpenStatus = 1
n.Group = m.Group
n.Url = m.CfnAgentUrl
n.LoginName = m.CfnAgentUsername
n.LoginPwd = m.CfnAgentPassword
n.SetUserName(service.GetUserName(c))
_, err = n.Create(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
}
controller.Success(c, "")
}
func ListMachineGroup(c *gin.Context) {
m := &node2.MachineNode{}
result, _ := m.ListGroup()
controller.Success(c, result)
}
func ListTemplateNode(c *gin.Context) {
m := node2.NewMachine()
fields := map[string]interface{}{}
fields["template_node"] = 1
result, err := m.GetAll(m, fields, nil)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, result)
}
func Whitelist(c *gin.Context) {
machineId := c.PostForm("machineId")
m := &node2.MachineNode{}
m.Id = machineId
err := m.GetById(m)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的机器")
return
}
result := assets.GetAgentWhitelist(m)
controller.Success(c, result)
}
func SaveWhitelist(c *gin.Context) {
ids := c.PostForm("ids")
if ids == "" {
controller.Success(c, "")
return
}
mids := strings.Split(ids, ",")
if len(mids) == 0 {
controller.Success(c, "")
return
}
formData := make(map[string]string)
for key, value := range c.Request.PostForm {
formData[key] = value[0]
}
body, _ := json.Marshal(formData)
for _, mid := range mids {
m := &node2.MachineNode{}
m.Id = mid
err := m.GetById(m)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的机器")
return
}
resp, err := common.Request(m, "", common.WhitelistDirectory_Submit, nil, nil, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "分发节点失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), "分发节点失败")
return
}
}
controller.Success(c, "")
}
const config_data = `jpom:
# jpom 数据存储路径, 如果调试模式运行默认路径为${user.home}/jpom/,安装运行默认为jar包文件的父级
path:
# 集群配置
cluster:
# 集群Id默认为 default 不区分大小写只能是字母或者数字长度小于 20
id: default
# 心跳监控时间 需要大于零 单位秒 最小配置 5
heart-second: 30
node:
# 节点心跳监控时间 需要大于零 单位秒 最小配置 5
heart-second: 30
# 节点统计日志保留天数如果小于等于 0 不自动删除
stat-log-keep-days: 3
# 上传文件的超时时间 单位秒,最短5秒钟
upload-file-timeout: 300
# 节点文件分片上传大小单位 M建议小于 5MB需要考虑插件端上传文件大小限制
upload-file-slice-size: 1
# 节点文件分片上传并发数,最小1 最大 服务端 CPU 核心数
upload-file-concurrent: 2
# web socket 消息最大长度
web-socket-message-size-limit: 5MB
system:
# cron 定时器是否开启匹配秒
timer-match-second: false
# 旧包文件保留个数
old-jars-count: 2
# 系统日志编码格式
log-charset: UTF-8
# 控制台编码格式
console-charset:
# 在线升级允许降级-操作
allowed-downgrade: false
user:
# 用户连续登录失败次数超过此数将被限制登录
always-login-error: 5
# IP连续登录失败次数超过此数将被限制登录
always-ip-login-error: 10
# 当ip连续登录失败锁定对应IP时长5h1d
ip-error-lock-time: 5h
# 是否强制提醒用户开启 mfa
force-mfa: false
#登录token失效时间(单位小时),默认为24
token-expired: 24
#登录token失效后自动续签时间单位分钟默认为60不自动续签
token-renewal: 60
# jwt 签名(加密)的key 长度建议控制到 16
token-jwt-key:
web:
# 前端接口 超时时间 单位秒(最小 5 )
api-timeout: 20
# 禁用页面引导导航
disabled-guide: false
# 禁用登录图形验证码 一般用于服务器没有字体或者开启了两部验证后才关闭图形验证码
disabled-captcha: false
# 前端消息弹出位置可选 topLeft topRight bottomLeft bottomRight
notification-placement:
# 前端消息传输需要编码或者加密 目前支持NONEBASE64
transport-encryption: NONE
# 查看日志时初始读取最后多少行默认100不读取
init-read-line: 10
db:
# 数据库默认 支持 H2MYSQL
mode: H2
# 日志存储条数将自动清理旧数据,配置小于等于零则不清理
log-storage-count: 10000
# H2 模式无需配置 mysql 配置 jdbc 地址
url:
# 数据库账号 默认 jpom
user-name: jpom
# 数据库密码 默认 jpom 如果自行配置请保证密码强度
user-pwd: jpom
# h2 数据库缓存大小 kilobyte 1KB 1,024 megabyte 1MB 1,048,576
cache-size: 50MB
# 自动备份间隔天数 小于等于 0 不自动备份
auto-backup-interval-day: 1
# 自动备份保留天数 小于等于 0不自动删除自动备份数据
auto-backup-reserve-day: 5
# 数据库连接池相关配置
max-active: 500
initial-size: 10
max-wait: 10
min-idle: 1
# 控制台是否打印 sql 信息
show-sql: false
# 构建相关配置
build:
# 最多保存多少份历史记录
max-history-count: 1000
# 单个最多保存多少份历史记录
item-max-history-count: 50
# 构建命令是否检查 存在删除命令
check-delete-command: true
# 构建线程池大小,小于 1 则为不限制默认大小为 5
pool-size: 5
# 构建任务等待数量超过此数量将取消构建任务值最小为 1
pool-wait-queue: 10
# 日志显示 压缩折叠显示进度比例 范围 1-100
log-reduce-progress-ratio: 5
file-storage:
# 文件中心存储路径
save-pah:
scan-static-dir-cron: 0 0/1 * * *
# 是否开启静态文件目录监听
watch-monitor-static-dir: true
# 监听静态文件目录层级
watch-monitor-max-depth: 5
assets:
# 监控线程池大小,小于等于0 为CPU核心数
monitor-pool-size: 0
# 监控任务等待数量超过此数量将取消监控任务值最小为 1
monitor-pool-wait-queue: 500
# ssh 资产
ssh:
# 监控频率
monitor-cron: 0 0/1 * * * ?
# 指定分组不启用监控功能如果想禁用所有配置 * 即可
disable-monitor-group-name:
- 禁用监控
# docker 资产
docker:
# 监控频率
monitor-cron: 0 0/1 * * * ?
server:
#运行端口号
port: 2122
servlet:
session:
cookie:
name: JPOMID-SERVER
timeout: 1H
encoding:
charset: UTF-8
force: true
enabled: true
forceRequest: true
forceResponse: true
compression:
# gzip 压缩
enabled: true
#mime-types: application/json,application/xml,text/html,text/xml,text/plain,application/javascript,image/png
min-response-size: 2048
tomcat:
uri-encoding: UTF-8
spring:
web:
resources:
static-locations: classpath:/dist/
cache:
period: 1D
mvc:
throw-exception-if-no-handler-found: true
log-request-details: true
servlet:
multipart:
# 上传文件大小限制
max-request-size: 2GB
max-file-size: 1GB
h2:
console:
# 是否开启 web 访问数据库url: http://${ip}:${port}/h2-console
enabled: false
# 是否允许远程访问开启此配置有安全风险默认为 false当部署到服务器上之后是否可以通过其他浏览器访问数据库
settings:
web-allow-others: false
`
func ConfigData(c *gin.Context) {
machineId := c.PostForm("machineId")
m := &node2.MachineNode{}
m.Id = machineId
err := m.GetById(m)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的机器")
return
}
resp, err := common.Request3(m, "", common.SystemGetConfig, nil)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "分发节点失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), "分发节点失败")
return
}
controller.Success(c, resp.Result.Data)
}
// todo 暂不允许修改运行配置
func SaveConfig(c *gin.Context) {
ids := c.PostForm("ids")
if ids == "" {
controller.Success(c, "")
return
}
mids := strings.Split(ids, ",")
if len(mids) == 0 {
controller.Success(c, "")
return
}
content := c.PostForm("content")
restart := c.PostForm("restart")
formData := make(map[string]string)
formData["content"] = content
formData["restart"] = restart
for _, mid := range mids {
m := &node2.MachineNode{}
m.Id = mid
err := m.GetById(m)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的机器")
return
}
resp, err := common.Request4(m, common.SystemSaveConfig, formData)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "分发节点失败")
return
}
if !resp.IsSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), "分发节点失败")
return
}
}
controller.Success(c, "修改成功")
}

View File

@ -0,0 +1,322 @@
package app_manage
import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/response"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/gin-gonic/gin"
"io"
"io/ioutil"
"mime/multipart"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
)
func GetWorkspaceId(c *gin.Context) string {
return c.GetHeader("workspaceId")
}
var ShardingIdMap = make(map[string]bool, 200)
func TryGetNode(body map[string]interface{}) (*node.Node, error) {
nodeId, _ := body["nodeId"].(string)
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
return nil, err
}
return n, nil
}
// String[] removeKeys, String... appendData
// application/json类型请求
func TryRequestNode(machineId string, body map[string]interface{}, nodeUrl string, pars ...string) *response.Response {
n, _ := TryGetNode(body)
marshal, _ := json.Marshal(body)
if n != nil {
resp, err := common.Request5(n, nodeUrl, bytes.NewReader(marshal))
if err != nil {
log.Errorf("发送请求失败: %s", n.Url+"/"+nodeUrl)
resp = &response.Response{
HttpCode: errors.ServerError,
Result: &response.Result{
Msg: "发送请求失败:" + err.Error(),
Data: nil,
Cost: "",
Now: time.Now(),
Code: errors.ServerError,
},
}
return resp
}
return resp
} else {
if len(machineId) > 0 {
m := &node.MachineNode{}
m.Id = machineId
err := m.GetById(m)
if err != nil {
log.Errorf("发送请求失败: %s", machineId+":"+nodeUrl)
resp := &response.Response{
HttpCode: errors.ServerError,
Result: &response.Result{
Msg: "发送请求失败:" + err.Error(),
Data: nil,
Cost: "",
Now: time.Now(),
Code: errors.ServerError,
},
}
return resp
}
resp, err := common.Request(m, "", nodeUrl, nil, nil, bytes.NewReader(marshal))
if err != nil {
log.Errorf("发送请求失败: %s", n.Url+"/"+nodeUrl)
resp = &response.Response{
HttpCode: errors.ServerError,
Result: &response.Result{
Msg: "发送请求失败:" + err.Error(),
Data: nil,
Cost: "",
Now: time.Now(),
Code: errors.ServerError,
},
}
return resp
}
return resp
} else {
log.Errorf("没有找到对应的机器: %s", machineId)
resp := &response.Response{
HttpCode: errors.ServerError,
Result: &response.Result{
Msg: fmt.Sprintf("没有找到对应的机器: %s", machineId),
Data: nil,
Cost: "",
Now: time.Now(),
Code: errors.ServerError,
},
}
return resp
}
}
}
/**
* 上传保存分片信息
*
* @param file 上传的文件信息
* @param tempPath 临时保存目录
* @param sliceId 分片id
* @param totalSlice 累积分片
* @param nowSlice 当前分片
* @param fileSumMd5 文件签名信息
* @throws IOException 异常
*/
func UploadShardingImpl(file multipart.File, filename, tempPath, sliceID, totalSliceStr, nowSliceStr, fileSumMD5 string, extNames []string) error {
if fileSumMD5 == "" {
return fmt.Errorf("没有文件签名信息")
}
if sliceID == "" {
return fmt.Errorf("没有分片 id 信息")
}
totalSlice, err := strconv.Atoi(totalSliceStr)
if err != nil {
return fmt.Errorf("上传信息不完整totalSlice %s", err)
}
nowSlice, err := strconv.Atoi(nowSliceStr)
if err != nil {
return fmt.Errorf("上传信息不完整nowSlice %s", err)
}
if totalSlice <= 0 || nowSlice < 0 || totalSlice < nowSlice {
return fmt.Errorf("当前上传的分片信息错误")
}
slicePath := filepath.Join(tempPath, "slice", sliceID)
sliceItemPath := filepath.Join(slicePath, "items")
if err := os.MkdirAll(sliceItemPath, 0755); err != nil {
return fmt.Errorf("创建保存路径失败:%s", err)
}
originalFilename := filename
// 截断序号 xxxxx.avi.1
realName := subBefore(originalFilename, ".", true)
if len(extNames) > 0 {
extName := filepath.Ext(realName)
for _, allowedExt := range extNames {
if strings.ToLower(extName) == strings.ToLower(allowedExt) {
break
}
}
if !strings.Contains(strings.ToLower(extName), strings.ToLower(strings.Join(extNames, ","))) {
return fmt.Errorf("不支持的文件类型:%s", extName)
}
}
slice := filepath.Join(sliceItemPath, originalFilename)
if err := os.MkdirAll(filepath.Dir(slice), 0755); err != nil {
return fmt.Errorf("创建分片保存路径失败:%s", err)
}
// 将文件写入到slice
targetFile, err := os.Create(slice)
if err != nil {
return fmt.Errorf("创建目标文件失败:%s", err)
}
defer targetFile.Close()
_, err = io.Copy(targetFile, file)
if err != nil {
return fmt.Errorf("保存分片文件失败:%s", err)
}
return nil
}
/**
* 合并分片
*
* @param tempPath 临时保存目录
* @param sliceId 上传id
* @param totalSlice 累积分片
* @param fileSumMd5 文件签名
* @return 合并后的文件
* @throws IOException io
*/
func ShardingTryMergeImpl(tempPath, sliceID string, totalSlice int, fileSumMD5 string) (*os.File, error) {
if fileSumMD5 == "" {
return nil, fmt.Errorf("没有文件签名信息")
}
if sliceID == "" {
return nil, fmt.Errorf("没有分片 id 信息")
}
if totalSlice == 0 {
return nil, fmt.Errorf("上传信息不完成totalSlice")
}
// 保存路径
slicePath := filepath.Join(tempPath, "slice", sliceID)
sliceItemPath := filepath.Join(slicePath, "items")
// 获取分片文件
files, err := ioutil.ReadDir(sliceItemPath)
if err != nil {
return nil, err
}
if len(files) != totalSlice {
return nil, fmt.Errorf("文件上传失败,存在分片丢失的情况, %d != %d", len(files), totalSlice)
}
// 获取文件真实名称
name := files[0].Name()
name = subBefore(name, ".", true)
// 创建合并后的文件
successFile, err := os.OpenFile(filepath.Join(slicePath, name), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0755)
if err != nil {
return nil, err
}
defer successFile.Close()
// 排序并合并分片
fileOrder := make([]int, len(files))
for i, file := range files {
ext := filepath.Ext(file.Name())
fileOrder[i] = mustToInt(ext)
}
sort.Slice(fileOrder, func(i, j int) bool {
return fileOrder[i] < fileOrder[j]
})
for _, index := range fileOrder {
partFile := filepath.Join(sliceItemPath, fmt.Sprintf("%s.%d", name, index))
part, err := os.Open(partFile)
if err != nil {
return nil, err
}
if _, err = io.Copy(successFile, part); err != nil {
part.Close()
return nil, err
}
part.Close()
}
// 删除分片文件
if err = os.RemoveAll(sliceItemPath); err != nil {
return nil, err
}
// 计算新文件的MD5
// 读取整个文件内容
file, err := os.Open(successFile.Name())
if err != nil {
return nil, fmt.Errorf("打开文件失败: %s", err.Error())
}
defer file.Close()
hasher := md5.New()
if _, err := io.Copy(hasher, file); err != nil {
return nil, fmt.Errorf("md5Sum error: %s", err.Error())
}
newMD5 := hex.EncodeToString(hasher.Sum(nil))
if !strings.EqualFold(newMD5, fileSumMD5) {
log.Infof("文件合并异常 %s:%x -> %s", successFile.Name(), newMD5, fileSumMD5)
return nil, fmt.Errorf("文件合并后异常,文件不完整可能被损坏")
}
return successFile, nil
}
func subBefore(s, sep string, isLastSeparator bool) string {
if sep == "" {
return ""
}
pos := -1
if isLastSeparator {
pos = strings.LastIndex(s, sep)
} else {
pos = strings.Index(s, sep)
}
if pos == -1 {
return s
} else if pos == 0 {
return ""
}
return s[:pos]
}
// todo 确定是16进制还是字符串
func md5Sum(content []byte) string {
// 创建一个新的md5哈希器
hasher := md5.New()
// 获取哈希值并转换为16进制字符串
hashBytes := hasher.Sum(content)
md5Hash := hex.EncodeToString(hashBytes)
return md5Hash
}
func mustToInt(s string) int {
num, _ := strconv.Atoi(strings.Trim(s, "."))
return num
}

View File

@ -0,0 +1,131 @@
package manage
import (
"bytes"
"encoding/json"
err1 "errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/project_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/assets"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
node2 "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/node"
"github.com/gin-gonic/gin"
)
func GetProjectData(c *gin.Context) {
nodeId := c.PostForm("nodeId")
id := c.PostForm("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string]string)
jsonBody["id"] = id
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.Manage_GetProjectItem, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "查询失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}
// ProjectAccessList 同/machine/white-list
func ProjectAccessList(c *gin.Context) {
machineId := c.PostForm("machineId")
m := &node.MachineNode{}
m.Id = machineId
err := m.GetById(m)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的机器")
return
}
result := assets.GetAgentWhitelist(m)
controller.Success(c, result)
}
// http://10.110.141.50:2122/node/manage/saveProject
// nodeId=895457ceee8340279d9d92e10242d77c&id=DQWKRY&name=test&runMode=Jar&whitelistDirectory=%2Fhome%2Ftmn%2Fjpom%2Fproject%2F&lib=test&logPath=
func SaveProject(c *gin.Context) {
//params, _ := request.GetPostFormParams(c)
//if params["nodeId"] == "" {
// controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
// return
//}
p := project_info.ProjectInfo{}
err := c.ShouldBindJSON(&p)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
nodeId := p.NodeId
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
id := p.Id
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := &node.Node{}
n.Id = nodeId
err = n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
p.SetUserName(service.GetUserName(c))
p.WorkspaceId = n.WorkspaceId
p.NodeName = n.Name
body, _ := json.Marshal(p)
resp, err := common.Request5(n, common.Manage_SaveProject, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "创建项目失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), "创建项目失败-逻辑节点")
return
}
node2.SyncNodeProjectInfo(n, id)
controller.Success(c, "")
}
func ReleaseOutgiving(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
response := app_manage.TryRequestNode("", bodyToMap, common.Manage_ReleaseOutGiving)
if response.Result.Code == errors.SUCCESS {
projectId, _ := bodyToMap["id"].(string)
n, _ := app_manage.TryGetNode(bodyToMap)
node2.SyncNodeProjectInfo(n, projectId)
controller.Success(c, response.Result.Data)
} else {
controller.FailCode(c, response.Result.Code, errors.NewBusinessError(response.Result.Code, response.Result.Msg), response.Result.Data)
}
}

View File

@ -0,0 +1,369 @@
package file
import (
"bytes"
"encoding/json"
err1 "errors"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"io"
"mime/multipart"
"strconv"
"strings"
"sync"
)
func GetFileList(c *gin.Context) {
nodeId := c.PostForm("nodeId")
id := c.PostForm("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string]string)
jsonBody["id"] = id
jsonBody["path"] = c.PostForm("path")
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.Manage_File_GetFileList, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "查询失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}
var mutex sync.Mutex
func GenerateShardingId(c *gin.Context) {
mutex.Lock()
if len(app_manage.ShardingIdMap) > 100 {
controller.FailCode(c, errors.ServerError, nil, "分片id最大同时使用100个")
return
}
id := strings.ReplaceAll(uuid.New().String(), "-", "")
app_manage.ShardingIdMap[id] = true
mutex.Unlock()
controller.Success(c, id)
}
// 1aaa34e5be524c868928a6053d487e2d
func UploadSharding(c *gin.Context) {
nodeId := c.PostForm("nodeId")
id := c.PostForm("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
sliceId := c.PostForm("sliceId")
if _, ok := app_manage.ShardingIdMap[sliceId]; !ok {
controller.FailCode(c, errors.InvalidParameter, nil, "不合法的分片id")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
_, err = c.MultipartForm()
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
file, header, err := c.Request.FormFile("file")
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
defer file.Close()
body := new(bytes.Buffer)
mpw := multipart.NewWriter(body)
fw, err := mpw.CreateFormFile("file", header.Filename)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
_, err = io.Copy(fw, file)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
_ = mpw.WriteField("nowSlice", c.PostForm("nowSlice"))
_ = mpw.WriteField("totalSlice", c.PostForm("totalSlice"))
_ = mpw.WriteField("sliceId", sliceId)
_ = mpw.WriteField("fileSumMd5", c.PostForm("fileSumMd5"))
_ = mpw.WriteField("id", id)
_ = mpw.WriteField("levelName", c.PostForm("levelName"))
_ = mpw.Close()
h := make(map[string]string)
h["Content-Type"] = mpw.FormDataContentType()
resp, err := common.Request1(n, common.Manage_File_Upload_Sharding, h, nil, body)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "上传失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Msg)
}
func ShardingMerge(c *gin.Context) {
nodeId := c.PostForm("nodeId")
id := c.PostForm("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
sliceId := c.PostForm("sliceId")
if _, ok := app_manage.ShardingIdMap[sliceId]; !ok {
controller.FailCode(c, errors.InvalidParameter, nil, "不合法的分片id")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string]interface{})
nowSlice := c.PostForm("nowSlice")
var nowSliceI int
if nowSlice != "" {
nowSliceI, _ = strconv.Atoi(nowSlice)
}
totalSlice := c.PostForm("totalSlice")
var totalSliceI int
if totalSlice != "" {
totalSliceI, _ = strconv.Atoi(totalSlice)
}
stripComponents := c.PostForm("stripComponents")
var stripComponentsI int
if stripComponents != "" {
stripComponentsI, _ = strconv.Atoi(stripComponents)
}
jsonBody["nowSlice"] = nowSliceI
jsonBody["totalSlice"] = totalSliceI
jsonBody["sliceId"] = sliceId
jsonBody["fileSumMd5"] = c.PostForm("fileSumMd5")
jsonBody["id"] = id
jsonBody["levelName"] = c.PostForm("levelName")
jsonBody["stripComponents"] = stripComponentsI
type_ := c.PostForm("type")
if type_ != "" {
jsonBody["type"] = type_
}
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.Manage_File_Sharding_Merge, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "合并失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
delete(app_manage.ShardingIdMap, sliceId)
controller.Success(c, resp.Result.Msg)
}
// remote-download
func RemoteDownload(c *gin.Context) {
nodeId := c.PostForm("nodeId")
id := c.PostForm("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string]string)
jsonBody["id"] = id
jsonBody["url"] = c.PostForm("url")
jsonBody["levelName"] = c.PostForm("levelName")
jsonBody["unzip"] = c.PostForm("unzip")
jsonBody["stripComponents"] = c.PostForm("stripComponents")
jsonBody["filename"] = c.PostForm("filename")
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.Manage_File_Remote_Download, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "下载失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}
func DeleteFile(c *gin.Context) {
nodeId := c.PostForm("nodeId")
id := c.PostForm("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string]string)
jsonBody["id"] = id
jsonBody["levelName"] = c.PostForm("levelName")
jsonBody["filename"] = c.PostForm("filename")
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.Manage_File_DeleteFile, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}
func NewFileFolder(c *gin.Context) {
nodeId := c.Query("nodeId")
id := c.Query("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string]string)
jsonBody["id"] = id
jsonBody["levelName"] = c.Query("levelName")
jsonBody["filename"] = c.Query("filename")
jsonBody["unFolder"] = c.Query("unFolder")
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.MANAGE_FILE_NEW_FILE_FOLDER, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "创建失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}
// ?nodeId=28d5792b2d3d11ef854b9e95766d6418&id=78XBM1&filePath=/gway&filename=manifest.yml
func ReadFile(c *gin.Context) {
workspaceId := app_manage.GetWorkspaceId(c)
nodeId := c.Query("nodeId")
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "未查询到节点信息")
return
}
query := c.Request.URL.RawQuery
query = fmt.Sprintf("%s?%s", common.Manage_File_ReadFile, query)
resp, err := common.RequestGet(n, workspaceId, query)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "未查询到节点信息:")
return
}
if resp.Result.Code != errors.SUCCESS {
controller.Fail(c, errors.ServerError, resp.Result.Msg)
return
}
controller.Success(c, resp.Result.Data)
}
// nodeId: this.nodeId,
//
// id: this.projectId,
// filePath: this.uploadPath,
// filename: this.filename,
// fileText: this.fileContent,
func UpdateConfigFile(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
n, err := app_manage.TryGetNode(bodyToMap)
if err != nil {
return
}
resp := app_manage.TryRequestNode(n.MachineId, bodyToMap, common.Manage_File_UpdateConfigFile)
if resp.Result.Code == errors.SUCCESS {
controller.Success(c, resp.Result.Data)
return
}
controller.Fail(c, errors.ServerError, resp.Result.Msg)
}

View File

@ -0,0 +1,36 @@
package log
import (
"bytes"
"encoding/json"
err1 "errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"github.com/gin-gonic/gin"
)
func LogSize(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
n, err := app_manage.TryGetNode(bodyToMap)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "获取节点失败")
return
}
body, _ := json.Marshal(bodyToMap)
resp, err := common.Request5(n, common.Manage_Log_LogSize, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "获取日志大小失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}

View File

@ -0,0 +1,239 @@
package manage
import (
"bytes"
"encoding/json"
err1 "errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/project_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
node2 "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/outgiving"
"github.com/gin-gonic/gin"
)
// GS4Z2X
func GetProjectPort(c *gin.Context) {
nodeId := c.PostForm("nodeId")
ids := c.PostForm("ids")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if ids == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
var idArray []string
err := json.Unmarshal([]byte(ids), &idArray)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "请检查参数!")
return
}
if len(idArray) == 0 {
controller.FailCode(c, errors.ServerError, err, "请检查参数!")
return
}
n := &node.Node{}
n.Id = nodeId
err = n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string][]string)
jsonBody["ids"] = idArray
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.Manage_GetProjectPort, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "查询失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}
type ProjectPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
NodeId string `json:"nodeId"`
Group string `json:"group"`
Keyword string `json:"keyword"`
RunMode string `json:"runMode"`
}
func PageProject(c *gin.Context) {
var pageBody ProjectPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if pageBody.NodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
p := project_info.New()
fields := map[string]interface{}{}
fields["node_id"] = pageBody.NodeId
if len(pageBody.Group) > 0 {
fields["\"group\""] = pageBody.Group
}
if len(pageBody.RunMode) > 0 {
fields["run_mode"] = pageBody.RunMode
}
workspaceId := app_manage.GetWorkspaceId(c)
fields["workspace_id"] = workspaceId
page := &model.PageConfig{}
page.Covert(pageBody.PageInfo)
result, err := p.Page(p, page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
page.Data = result
controller.Success(c, page)
}
func DeleteProject(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
n, err := app_manage.TryGetNode(bodyToMap)
if err != nil {
return
}
projectId, _ := bodyToMap["id"].(string)
if projectId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数:projectId")
return
}
workspaceId := app_manage.GetWorkspaceId(c)
err = outgiving.CheckNodeProject(n.Id, projectId, workspaceId, "当前项目存在节点分发,不能直接删除")
if err != nil {
controller.FailCode(c, errors.ServerError, err, "当前项目存在节点分发,不能直接删除")
return
}
// todo 检查日志阅读 项目监控
// logReadServer.checkNodeProject(nodeModel.getId(), id, request, "当前项目存在日志阅读,不能直接删除");
resp := app_manage.TryRequestNode("", bodyToMap, common.Manage_DeleteProject)
if resp.Result.Code == errors.SUCCESS {
node2.SyncExecuteNode(n)
controller.Success(c, resp.Result.Data)
return
}
controller.Fail(c, errors.ServerError, resp.Result.Msg)
}
func StartProject(c *gin.Context) {
nodeId := c.PostForm("nodeId")
id := c.PostForm("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数:nodeId")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数:id")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string]string)
jsonBody["id"] = id
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.Manage_StartProject, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "下载失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}
func StopProject(c *gin.Context) {
nodeId := c.PostForm("nodeId")
id := c.PostForm("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string]string)
jsonBody["id"] = id
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.Manage_StopProject, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "下载失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}
func RestartProject(c *gin.Context) {
nodeId := c.PostForm("nodeId")
id := c.PostForm("id")
if nodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的节点")
return
}
jsonBody := make(map[string]string)
jsonBody["id"] = id
body, _ := json.Marshal(jsonBody)
resp, err := common.Request5(n, common.Manage_RestartProject, bytes.NewReader(body))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "下载失败")
return
}
if !resp.IsBusinessSuccess() {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), resp.Result.Data)
return
}
controller.Success(c, resp.Result.Data)
}

View File

@ -0,0 +1,302 @@
package node
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
node2 "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/out_giving"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/project_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/script_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"github.com/gin-gonic/gin"
"strings"
)
func GetAllNodesByWorkspaceId(c *gin.Context) {
workspaceId := c.Query("workspaceId")
if workspaceId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := node2.New()
fields := map[string]interface{}{}
fields["workspace_id"] = workspaceId
result, err := n.GetAll(n, fields, nil)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
controller.Success(c, result)
}
type NodePageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
WorkspaceId string `json:"workspaceId"`
Name string `json:"name"`
Group string `json:"group"`
Keyword string `json:"keyword"`
}
func PageNodesByWorkspaceId(c *gin.Context) {
var pageBody NodePageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if pageBody.WorkspaceId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := node2.New()
fields := map[string]interface{}{}
fields["workspace_id"] = pageBody.WorkspaceId
if pageBody.Name != "" {
fields["name"] = pageBody.Name
}
if pageBody.Group != "" {
fields["\"group\""] = pageBody.Group
}
page := &model.PageConfig{}
page.Covert(pageBody.PageInfo)
result, err := n.Page(n, page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
nodes := result.([]node2.Node)
if len(nodes) > 0 {
for i, node := range nodes {
m := &node2.MachineNode{}
m.Id = node.MachineId
err = m.GetById(m)
projects, _ := project_info.GetProjectByNodeId(node.Id)
scripts, err := script_info.GetNodeScriptByNodeId(node.Id)
if err == nil {
m.CfnAgentProjectCount = len(projects)
m.CfnAgentScriptCount = len(scripts)
nodes[i].MachineNodeData = m
}
}
}
page.Data = nodes
controller.Success(c, page)
}
func SyncToWorkspace(c *gin.Context) {
ids := c.Query("ids")
if ids == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
workspaceId := c.Query("workspaceId")
if workspaceId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
fields := map[string]interface{}{}
idArray := strings.Split(ids, ",")
username := service.GetUserName(c)
for _, id := range idArray {
nd := node2.New()
nd.Id = id
err := nd.GetById(nd)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, nil, "节点不存在!")
return
}
fields = map[string]interface{}{}
fields["workspace_id"] = workspaceId
fields["machine_id"] = nd.MachineId
result, _ := nd.GetAll(nd, fields, nil)
if len(result.([]node2.Node)) > 0 {
controller.FailCode(c, errors.InvalidParameter, nil, "目标工作空间已存在该节点!")
return
}
nd.Id = ""
nd.WorkspaceId = workspaceId
nd.CreateUser = username
nd.ModifyUser = username
nd.LoginName = ""
nd.Url = ""
nd.LoginPwd = ""
nd.Protocol = ""
nd.HttpProxy = ""
nd.HttpProxyType = ""
_, err = nd.Create(nd)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
}
controller.Success(c, "")
}
func EditNode(c *gin.Context) {
n := &node2.Node{}
err := validator.CheckPostParams(c, n)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
n1 := &node2.Node{}
err = n.GetById(n1)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
n.ModifyUser = service.GetUserName(c)
_, err = n.Update(n, nil)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
// TODO projectInfoCacheService.syncNode(nodeModel);
controller.Success(c, "")
}
func ListNodeGroup(c *gin.Context) {
n := &node2.Node{}
result, _ := n.ListGroup()
controller.Success(c, result)
}
type UnbindNodeBody struct {
Id string `json:"id"`
}
func UnbindNode(c *gin.Context) {
var body UnbindNodeBody
err := c.ShouldBindJSON(&body)
id := body.Id
if id == "" {
controller.Fail(c, errors.InvalidParameter, "id为空")
return
}
workspaceId := app_manage.GetWorkspaceId(c)
//
{
exists, _ := out_giving.ExistsOutGivingModel(workspaceId, id)
if exists {
controller.FailCode(c, errors.InvalidParameter, nil, "该节点存在分发项目,不能删除")
return
}
}
{
exists, _ := project_info.Exists_1(workspaceId, id)
if exists {
controller.FailCode(c, errors.InvalidParameter, nil, "该节点下还存在项目,不能删除")
return
}
}
{
exists, _ := script_info.ExistsNodeScriptCacheModel(workspaceId, id)
if exists {
controller.FailCode(c, errors.InvalidParameter, nil, "该节点下还存在脚本模版,不能删除")
return
}
}
n := &node2.Node{}
n.Id = id
err = n.GetById(n)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
_, err = n.Delete(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
script_info.DeleteNodeScriptExecuteLogByNodeIdAndWorkspaceIds(id, []string{workspaceId, "GLOBAL"})
controller.Success(c, "")
}
func DeleteNode(c *gin.Context) {
id := c.PostForm("id")
if id == "" {
controller.Success(c, "")
return
}
workspaceId := app_manage.GetWorkspaceId(c)
//
{
exists, _ := out_giving.ExistsOutGivingModel(workspaceId, id)
if exists {
controller.FailCode(c, errors.InvalidParameter, nil, "该节点存在分发项目,不能删除")
return
}
}
_, err := project_info.DeleteProjectByNodeIdAndWorkspaceId(id, workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除项目失败!")
return
}
_, err = script_info.DeleteNodeScriptByNodeIdAndWorkspaceId(id, workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除脚本失败!")
return
}
n := &node2.Node{}
n.Id = id
err = n.GetById(n)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
_, err = n.Delete(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
script_info.DeleteNodeScriptExecuteLogByNodeIdAndWorkspaceIds(id, []string{workspaceId, "GLOBAL"})
controller.Success(c, "")
}
func SortItem(c *gin.Context) {
/*n := &node.Node{}
err := validator.CheckPostParams(c, n)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
fields["id"] = n.Id
_, err = n.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
userName := service.GetUserName(c)
n.ModifyUser = userName
var updateColumns []string
_, err = n.Update(updateColumns)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, "")*/
}

View File

@ -0,0 +1,108 @@
package node
import (
"encoding/json"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/project_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/node"
"github.com/gin-gonic/gin"
)
type ProjectPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
NodeId string `json:"nodeId"`
Group string `json:"group"`
Keyword string `json:"keyword"`
RunMode string `json:"runMode"`
}
// ProjectList 加载节点项目列表
func ProjectList(c *gin.Context) {
var pageBody *ProjectPageBody
body := request.GetBody(c)
err := json.Unmarshal(body, &pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
p := project_info.New()
fields := map[string]interface{}{}
if len(pageBody.NodeId) > 0 {
fields["node_id"] = pageBody.NodeId
}
if len(pageBody.Group) > 0 {
fields["\"group\""] = pageBody.Group
}
if len(pageBody.RunMode) > 0 {
fields["run_mode"] = pageBody.RunMode
}
workspaceId := app_manage.GetWorkspaceId(c)
fields["workspace_id"] = workspaceId
page := &model.PageConfig{}
page.Covert(pageBody.PageInfo)
result, err := p.Page(p, page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
page.Data = result
controller.Success(c, page)
}
// ProjectListAll 加载节点项目列表
func ProjectListAll(c *gin.Context) {
p := project_info.New()
fields := map[string]interface{}{}
workspaceId := app_manage.GetWorkspaceId(c)
fields["workspace_id"] = workspaceId
all, err := p.GetAll(p, fields, nil)
if err != nil {
return
}
controller.Success(c, all)
}
// ListProjectGroup 查询所有的分组
func ListProjectGroup(c *gin.Context) {
p := &project_info.ProjectInfo{}
result, _ := p.ListGroup()
controller.Success(c, result)
}
// SyncProject 同步节点项目
func SyncProject(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
n, err := app_manage.TryGetNode(bodyToMap)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
workspaceId := c.Request.Header.Get("workspaceId")
count, err := project_info.DeleteProjectByNodeIdAndWorkspaceId(n.Id, workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除节点缓存项目信息失败!")
return
}
msg := node.SyncExecuteNode(n)
controller.Success(c, fmt.Sprintf("主动清除:%d个项目, 同步信息:%s", count, msg))
}
func ClearAll(c *gin.Context) {
all, err := project_info.ClearAll()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除节点缓存项目失败!")
return
}
controller.Success(c, fmt.Sprintf("成功删除 %d 条项目缓存", all))
}

View File

@ -0,0 +1,37 @@
package node
import (
"encoding/json"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"github.com/gin-gonic/gin"
"os"
)
func FastInstallNode(c *gin.Context) {
result := make(map[string]interface{})
result["key"] = "--auto-push-to-server"
result["token"] = os.Getenv("JPOM_SERVER_TEMP_TOKEN")
shUrls := `[
{
"name": "主地址(默认安装)",
"url": "curl -fsSL https://jpom.top/docs/install.sh | bash -s Agent jdk+default"
},
{
"name": "备用地址(默认安装)",
"url": "curl -fsSL https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh | bash -s Agent jdk+default"
},
{
"name": "主地址(自定义安装)",
"url": "yum install -y wget && wget -O install.sh https://jpom.top/docs/install.sh && bash install.sh Agent jdk"
},
{
"name": "备用地址(自定义安装)",
"url": "yum install -y wget && wget -O install.sh https://gitee.com/dromara/Jpom/raw/docs/docs/.vuepress/public/docs/install.sh && bash install.sh Agent jdk"
}
]`
var shUrlsArray []map[string]string
json.Unmarshal([]byte(shUrls), &shUrlsArray)
result["shUrls"] = shUrlsArray
result["url"] = "/api/node/receive_push"
controller.Success(c, result)
}

View File

@ -0,0 +1,162 @@
package script
import (
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/script_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/node/script"
"github.com/gin-gonic/gin"
)
type ProjectPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
NodeId string `json:"nodeId"`
Group string `json:"group"`
Keyword string `json:"keyword"`
RunMode string `json:"runMode"`
}
func ScriptList(c *gin.Context) {
var pageBody ProjectPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if pageBody.NodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
n := script_info.NewNodeScriptCacheModel()
fields := map[string]interface{}{}
fields["node_id"] = pageBody.NodeId
workspaceId := app_manage.GetWorkspaceId(c)
fields["workspace_id"] = []string{workspaceId, "GLOBAL"}
page := &model.PageConfig{}
page.Covert(pageBody.PageInfo)
scripts, err := n.Page(n, page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
page.Data = scripts
controller.Success(c, page)
}
func ScriptListAll(c *gin.Context) {
var pageBody ProjectPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
n := script_info.NewNodeScriptCacheModel()
fields := map[string]interface{}{}
if pageBody.NodeId != "" {
fields["node_id"] = pageBody.NodeId
}
workspaceId := app_manage.GetWorkspaceId(c)
fields["workspace_id"] = []string{workspaceId, "GLOBAL"}
page := &model.PageConfig{}
page.Covert(pageBody.PageInfo)
scripts, err := n.Page(n, page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
page.Data = scripts
controller.Success(c, page)
}
func Item(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
response := app_manage.TryRequestNode("", bodyToMap, common.Script_Item)
if response.Result.Code == errors.SUCCESS {
// todo 同步服务端
controller.Success(c, response.Result.Data)
} else {
controller.FailCode(c, response.Result.Code, errors.NewBusinessError(response.Result.Code, response.Result.Msg), response.Result.Data)
}
}
func Save(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
machineId, _ := bodyToMap["machineId"].(string)
response := app_manage.TryRequestNode(machineId, bodyToMap, common.Script_Save)
if response.Result.Code == errors.SUCCESS {
// todo 同步服务端
node, _ := app_manage.TryGetNode(bodyToMap)
go script.SyncExecuteNode(node)
controller.Success(c, response.Result.Data)
} else {
controller.FailCode(c, response.Result.Code, errors.NewBusinessError(response.Result.Code, response.Result.Msg), response.Result.Data)
}
}
func Del(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
response := app_manage.TryRequestNode("", bodyToMap, common.Script_Del)
if response.Result.Code == errors.SUCCESS {
// todo 同步服务端
node, _ := app_manage.TryGetNode(bodyToMap)
go script.SyncExecuteNode(node)
controller.Success(c, response.Result.Data)
} else {
controller.FailCode(c, response.Result.Code, errors.NewBusinessError(response.Result.Code, response.Result.Msg), response.Result.Data)
}
}
func SyncScript(c *gin.Context) {
nodeId := c.Query("nodeId")
workspaceId := c.Request.Header.Get("workspaceId")
node, err := app_manage.TryGetNode(map[string]interface{}{"nodeId": nodeId})
if err != nil {
controller.FailCode(c, errors.ServerError, err, "节点信息有误")
return
}
_, err = script_info.DeleteNodeScriptByNodeIdAndWorkspaceId(nodeId, workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除脚本失败")
return
}
res := script.SyncExecuteNode(node)
controller.Success(c, res)
}
func ClearAll(c *gin.Context) {
count, err := script_info.DeleteAllNodeScript()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除脚本失败")
return
}
controller.Success(c, fmt.Sprintf("成功删除%d条脚本模版缓存", count))
}
func Unbind(c *gin.Context) {
id := c.Query("id")
workspaceId := app_manage.GetWorkspaceId(c)
_, err := script_info.DeleteNodeScriptByIdAndWorkspaceId(id, workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "解绑失败:删除脚本失败")
return
}
controller.Success(c, "解绑成功")
}

View File

@ -0,0 +1,116 @@
package script
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/middleware"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/script_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/base_service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"github.com/gin-gonic/gin"
)
type LogPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
NodeId string `json:"nodeId"`
Name string `json:"%name%"`
TriggerExecType string `json:"triggerExecType"`
}
// ScriptLogList 处理器
func ScriptLogList(c *gin.Context) {
var pageBody LogPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if pageBody.NodeId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!(nodeId)")
return
}
fields := map[string]interface{}{}
fields["node_id"] = pageBody.NodeId
if len(pageBody.TriggerExecType) != 0 {
fields["trigger_exec_type"] = pageBody.TriggerExecType
}
if pageBody.Name != "" {
fields["%script_name%"] = pageBody.Name
}
page := &model.Page[script_info.NodeScriptExecuteLogCacheModel]{
CurrentPage: pageBody.PageInfo.CurrentPage,
PageSize: pageBody.PageInfo.PageSize,
Order: pageBody.PageInfo.Order,
}
log := script_info.NewNodeScriptExecuteLogCacheModel()
err = log.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "查询脚本日志失败!")
return
}
controller.Success(c, page)
}
// Log 处理器
func Log(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
machineId, _ := bodyToMap["machineId"].(string)
response := app_manage.TryRequestNode(machineId, bodyToMap, common.SCRIPT_LOG)
if response.Result.Code == errors.SUCCESS {
controller.Success(c, response.Result.Data)
} else {
controller.FailCode(c, response.Result.Code, errors.NewBusinessError(response.Result.Code, response.Result.Msg), response.Result.Data)
}
}
// DelLog 处理器
/**
* @param id 模版ID
* @param executeId 日志ID
*/
func DelLog(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
id, _ := bodyToMap["id"].(string)
executeId, _ := bodyToMap["executeId"].(string)
workspaceId := app_manage.GetWorkspaceId(c)
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
hasWorkspace := base_service.CheckUserWorkspace(workspaceId, userModel)
if !hasWorkspace {
controller.FailCode(c, errors.RBACError, nil, "没有对应的工作空间权限!")
return
}
executeLogModel, err := script_info.GetNodeScriptExecuteLogByIdAndWorkspaceIds(executeId, []string{workspaceId, "GLOBAL"})
if err != nil || executeLogModel.Id == "" {
controller.FailCode(c, errors.ServerError, err, "没有对应的执行日志!")
return
}
if executeLogModel.ScriptId != id {
controller.FailCode(c, errors.InvalidParameter, nil, "数据关联的id 不一致!")
return
}
response := app_manage.TryRequestNode("", bodyToMap, common.SCRIPT_DEL_LOG)
if response.Result.Code == errors.SUCCESS {
script_info.DeleteNodeScriptExecuteLogByIdAndWorkspaceIds(executeId, []string{workspaceId, "GLOBAL"})
controller.Success(c, response.Result.Data)
} else {
controller.FailCode(c, response.Result.Code, errors.NewBusinessError(response.Result.Code, response.Result.Msg), response.Result.Data)
}
}

View File

@ -0,0 +1,65 @@
package system
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"github.com/gin-gonic/gin"
)
// WhiteList 同/machine/white-list
func WhiteList(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
machineId, _ := bodyToMap["machineId"].(string)
response := app_manage.TryRequestNode(machineId, bodyToMap, common.WhitelistDirectory_data, "")
if response.Result.Code == errors.SUCCESS {
controller.Success(c, response.Result.Data)
} else {
controller.FailCode(c, response.Result.Code, errors.NewBusinessError(response.Result.Code, response.Result.Msg), response.Result.Data)
}
//bodyToMap := request.GetBodyToMap(c)
//nodeId, _ := bodyToMap["nodeId"].(string)
//n := &node.Node{}
//n.Id = nodeId
//err := n.GetById(n)
//if err != nil {
// machineId, _ := bodyToMap["machineId"].(string)
// m := &node.MachineNode{}
// m.Id = machineId
// err = m.GetById(m)
// if err != nil {
// controller.FailCode(c, errors.ServerError, err, "获取白名单失败")
// return
// }
// response, err := common.Request(m, "", common.WhitelistDirectory_data, nil, nil, nil)
// if err != nil {
// controller.FailCode(c, errors.ServerError, err, "获取白名单失败")
// return
// }
//
// controller.Success(c, response.Result.Data)
//} else {
// response, err := common.Request5(n, common.WhitelistDirectory_data, nil)
// if err != nil {
// controller.FailCode(c, errors.ServerError, err, "获取白名单失败")
// return
// }
// controller.Success(c, response.Result.Data)
//}
}
func WhitelistDirectorySubmit(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
machineId, _ := bodyToMap["machineId"].(string)
response := app_manage.TryRequestNode(machineId, bodyToMap, common.WhitelistDirectory_Submit, "")
if response.Result.Code == errors.SUCCESS {
controller.Success(c, response.Result.Data)
} else {
controller.FailCode(c, response.Result.Code, errors.NewBusinessError(response.Result.Code, response.Result.Msg), response.Result.Data)
}
}

View File

@ -0,0 +1,65 @@
package app_manage
import (
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/device"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"github.com/gin-gonic/gin"
)
func GetNodeBySerialNo(c *gin.Context) {
serialNo := c.Query("serialno")
workspaceId := c.Query("workspaceId")
d := &device.DeviceInfo{}
deviceInfo, err := d.GetBySerialNo(serialNo)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "未查询到节点信息")
return
}
// todo 临时
n := node.New()
fields := map[string]interface{}{}
fields["url"] = fmt.Sprintf("%s:%d", deviceInfo.IPAddress, deviceInfo.Port)
fields["workspace_id"] = workspaceId
result, err := n.GetAll(n, fields, nil)
nodes := result.([]node.Node)
if len(nodes) == 0 {
controller.FailCode(c, errors.InvalidParameter, nil, "未查询到节点信息!")
return
}
controller.Success(c, nodes[0])
}
func GetRandomPorts(c *gin.Context) {
nodeId := c.Query("nodeId")
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "未查询到节点信息")
return
}
query := c.Request.URL.RawQuery
query = fmt.Sprintf("%s?%s", common.RANDOM_PORTS, query)
resp, err := common.RequestGet(n, "", query)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "未查询到节点信息:")
return
}
if resp.Result.Code != errors.SUCCESS {
controller.Fail(c, errors.ServerError, resp.Result.Msg)
return
}
controller.Success(c, resp.Result.Data)
}

View File

@ -0,0 +1,354 @@
package outgiving
import (
"bytes"
"encoding/json"
err1 "errors"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/middleware"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/out_giving"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/project_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/utils"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"github.com/gin-gonic/gin"
"regexp"
"strings"
"time"
)
type OutGivgingPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
Group string `json:"group"`
OutGivingProject string `json:"outGivingProject"`
Status string `json:"status"`
}
func DispatchList(c *gin.Context) {
var pageBody OutGivgingPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
if pageBody.Group != "" {
fields["\"group\""] = pageBody.Group
}
if pageBody.OutGivingProject != "" {
// 将字符串转化为布尔值
toBool := utils.StringToBool(pageBody.OutGivingProject)
fields["outGivingProject"] = toBool
}
if pageBody.Status != "" {
fields["status"] = pageBody.Status
}
workspaceId := app_manage.GetWorkspaceId(c)
fields["workspace_id"] = workspaceId
page := &model.Page[out_giving.OutGivingModel]{
CurrentPage: pageBody.PageInfo.CurrentPage,
PageSize: pageBody.PageInfo.PageSize,
Order: pageBody.PageInfo.Order,
}
model := out_giving.NewOutGivingModel()
err = model.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "查询分发列表失败!")
return
}
controller.Success(c, page)
}
func DispatchListAll(c *gin.Context) {
workspaceId := app_manage.GetWorkspaceId(c)
models := out_giving.ListOutGivingModelByWorkspace(workspaceId)
controller.Success(c, models)
}
// 关联分发项目
func SaveConnected(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
typeStr, _ := bodyToMap["type"].(string)
id, _ := bodyToMap["id"].(string)
workspaceId := app_manage.GetWorkspaceId(c)
bodyToMap["workspace_id"] = workspaceId
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
if "add" == typeStr {
checkId := strings.ReplaceAll(id, "-", "")
checkId = strings.ReplaceAll(checkId, "_", "")
err := validateID(checkId)
if err != nil {
controller.FailCode(c, errors.ServerError, err, err.Error())
return
}
addOutGivingConnected(c, id, bodyToMap, userModel)
} else {
updateGivingConnected(c, id, bodyToMap, userModel)
}
}
// validateID 验证项目ID
func validateID(id string) error {
re := regexp.MustCompile(`^[a-zA-Z0-9_]{2,50}$`)
matchString := re.MatchString(id)
if !matchString {
return fmt.Errorf("项目id 长度范围2-%d英文字母 、数字和下划线)", 20)
}
return nil
}
func addOutGivingConnected(c *gin.Context, id string, body map[string]interface{}, userObj *user.UserObj) {
byId := out_giving.GetOutGivingModelById(id)
if byId.Id != "" {
controller.FailCode(c, errors.ServerError, nil, "项目id已存在")
return
}
outGivingModel := out_giving.NewOutGivingModel()
outGivingModel.Id = id
outGivingModel.CreateUser = userObj.UserID
outGivingModel.ModifyUser = userObj.UserID
err := doDataConnected(outGivingModel, body)
if err != nil {
controller.FailCode(c, errors.ServerError, err, err.Error())
return
}
_, err = outGivingModel.Create(outGivingModel)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "项目id已存在")
return
}
controller.Success(c, "添加成功")
}
func updateGivingConnected(c *gin.Context, id string, body map[string]interface{}, userObj *user.UserObj) {
outGivingModel := out_giving.GetOutGivingModelById(id)
if outGivingModel == nil {
controller.Fail(c, errors.ServerError, "没有找到对应的分发id")
return
}
err := doDataConnected(outGivingModel, body)
if err != nil {
controller.FailCode(c, errors.ServerError, err, err.Error())
return
}
outGivingModel.ModifyUser = userObj.UserID
outGivingModel.ModifyTimeMillis = time.Now().UnixMilli()
err = out_giving.UpdateOutGivingModel(outGivingModel, []string{"name", "group", "workspace_id", "after_opt", "secondary_directory",
"out_giving_node_project_list", "clear_old", "upload_close_first", "secondary_directory", "modify_user", "modify_time_millis"})
if err != nil {
controller.FailCode(c, errors.ServerError, err, "修改失败")
return
}
controller.Success(c, "修改成功")
}
// {
// "type": "add",
// "id": "0K1GRY",
// "name": "sdafas",
// "projectId": "",
// "group": "gg",
// "afterOpt": "0",
// "secondaryDirectory": "tssss",
// "clearOld": "true",
// "uploadCloseFirst": "true",
// "node_56e192c8e76742fa85c72342212ef466_0": "27G909",
// "node_5722cd7a4e7a475cac61da2507088a9d_1": "T88633"
// }
func doDataConnected(outGivingModel *out_giving.OutGivingModel, body map[string]interface{}) error {
outGivingModel.Name = body["name"].(string)
outGivingModel.WorkspaceId = body["workspace_id"].(string)
if outGivingModel.Name == "" {
return fmt.Errorf("分发名称不能为空")
}
if groupStr, ok := body["group"]; ok {
if groupStr != nil {
s := groupStr.(string)
outGivingModel.Group = &s
}
}
outGivingModels := out_giving.ListOutGivingModel()
paramMap := make(map[string]string)
for k, v := range body {
if strings.HasPrefix(k, "node_") {
paramMap[k] = v.(string)
}
}
outGivingNodeProjects := make([]out_giving.OutGivingNodeProject, 0)
for key, projectId := range paramMap {
// "node_5722cd7a4e7a475cac61da2507088a9d_1": "T88633"
if strings.HasPrefix(key, "node_") {
nodeId := strings.TrimPrefix(key, "node_")
nodeId = strings.Split(nodeId, "_")[0]
nodeModel := node.GetNodeById(nodeId)
if nodeModel.Id == "" {
return fmt.Errorf("不存在对应的节点: %s", nodeId)
}
exists, _ := project_info.Exists(nodeModel.WorkspaceId, nodeModel.Id, projectId)
if !exists {
return fmt.Errorf("没有找到对应的项目id: %s", projectId)
}
outGivingNodeProjectList := outGivingModel.GetOutGivingNodeProjectList()
outGivingNodeProject := outGivingModel.GetNodeProject(outGivingNodeProjectList, nodeModel.Id, projectId)
if outGivingNodeProject == nil {
outGivingNodeProject = &out_giving.OutGivingNodeProject{}
}
outGivingNodeProject.NodeId = nodeModel.Id
outGivingNodeProject.ProjectId = projectId
// 检查项目是否已被使用
for _, model := range outGivingModels {
if strings.EqualFold(model.Id, outGivingModel.Id) {
continue
}
contains := model.CheckContains(outGivingNodeProject.NodeId, outGivingNodeProject.ProjectId)
if contains {
return fmt.Errorf("已经存在相同的分发项目: %s", outGivingNodeProject.ProjectId)
}
}
outGivingNodeProjects = append(outGivingNodeProjects, *outGivingNodeProject)
}
}
if len(outGivingNodeProjects) == 0 {
return fmt.Errorf("至少选择1个节点项目")
}
outGivingModel.SetOutGivingNodeProjectList(outGivingNodeProjects)
if afterOpt, ok := body["afterOpt"]; ok {
afterOptInt := int(afterOpt.(float64))
outGivingModel.AfterOpt = afterOptInt
} else {
return fmt.Errorf("请选择分发后的操作")
}
outGivingModel.IntervalTime = 10
if clearOld, ok := body["clearOld"]; ok {
outGivingModel.ClearOld = clearOld.(bool)
}
if secondaryDirectory, ok := body["secondaryDirectory"]; ok {
if secondaryDirectory != nil {
s := secondaryDirectory.(string)
outGivingModel.SecondaryDirectory = &s
}
}
if uploadCloseFirst, ok := body["uploadCloseFirst"]; ok {
outGivingModel.UploadCloseFirst = uploadCloseFirst.(bool)
}
return nil
}
// 释放关联分发项目
func ReleaseDel(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
id, ok := bodyToMap["id"]
if !ok {
controller.Fail(c, errors.ServerError, "id不能为空")
return
}
// todo 验证是否需要workspaceId
outGivingModel := out_giving.GetOutGivingModelById(id.(string))
if outGivingModel == nil {
controller.Fail(c, errors.ServerError, "没有找到对应的分发id")
return
}
outGivingNodeProjectList := outGivingModel.GetOutGivingNodeProjectList()
for _, outGivingNodeProject := range outGivingNodeProjectList {
nodeModel := node.GetNodeById(outGivingNodeProject.NodeId)
if nodeModel.Id == "" {
controller.Fail(c, errors.ServerError, "没有找到对应的节点")
return
}
body := map[string]string{
"id": outGivingNodeProject.ProjectId,
}
body1, _ := json.Marshal(body)
resp, err := common.Request5(nodeModel, common.Manage_ReleaseOutGiving, bytes.NewReader(body1))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "释放节点项目失败")
return
}
if resp.Result.Code != errors.SUCCESS {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), "释放节点项目失败: "+resp.Result.Msg)
return
}
}
rows, err := out_giving.DeleteOutGivingModelById(id.(string))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除分发失败")
return
}
if rows >= 0 {
out_giving.DeleteOutGivingLogByOutGivingId(id.(string))
}
controller.Success(c, "释放成功")
}
// 解绑关联分发项目
func Unbind(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
id, ok := bodyToMap["id"]
if !ok {
controller.Fail(c, errors.ServerError, "id不能为空")
return
}
outGivingModel := out_giving.GetOutGivingModelById(id.(string))
if outGivingModel == nil {
controller.Fail(c, errors.ServerError, "没有找到对应的分发id")
return
}
rows, err := out_giving.DeleteOutGivingModelById(id.(string))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除分发失败")
return
}
if rows >= 0 {
out_giving.DeleteOutGivingLogByOutGivingId(id.(string))
}
// todo 删除日志
controller.Success(c, "解绑成功")
}

View File

@ -0,0 +1,75 @@
package outgiving
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/out_giving"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"github.com/gin-gonic/gin"
)
type OutGivingLogPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
NodeId string `json:"nodeId"`
OutGivingId string `json:"outGivingId"`
Status string `json:"status"`
CreateTimeMillis string `json:"createTimeMillis"`
}
// {
// "page": "1",
// "limit": "10",
// "total": "0",
// "nodeId": "56e192c8e76742fa85c72342212ef466",
// "outGivingId": "8KY1Y1",
// "status": "0",
// }
func ListData(c *gin.Context) {
var pageBody OutGivingLogPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
if pageBody.NodeId != "" {
fields["node_id"] = pageBody.NodeId
}
if pageBody.OutGivingId != "" {
fields["out_giving_id"] = pageBody.OutGivingId
}
if pageBody.Status != "" {
fields["status"] = pageBody.Status
}
workspaceId := app_manage.GetWorkspaceId(c)
fields["workspace_id"] = workspaceId
page := &model.Page[out_giving.OutGivingLog]{
CurrentPage: pageBody.PageInfo.CurrentPage,
PageSize: pageBody.PageInfo.PageSize,
Order: pageBody.PageInfo.Order,
}
log := out_giving.NewOutGivingLog()
err = log.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "查询分发日志失败!")
return
}
controller.Success(c, page)
}
func GetLogsIdByExecId(c *gin.Context) {
value := c.Query("execId")
id, err := out_giving.GetOutGivingExecLogById(value)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "查询分发日志失败!")
return
}
controller.Success(c, id)
}

View File

@ -0,0 +1,44 @@
{
"id": "6MH5DC",
"name": "asdf",
"type": "edit",
"afterOpt": "0",
"runMode": "Jar",
"whitelistDirectory": "/home/tmn/jpom/project/ds/",
"lib": "aaaaa",
"logPath": "",
"intervalTime": "10",
"clearOld": "false",
"secondaryDirectory": "",
"uploadCloseFirst": "false",
"56e192c8e76742fa85c72342212ef466_jvm": "-Xms=512m",
"56e192c8e76742fa85c72342212ef466_token": "",
"56e192c8e76742fa85c72342212ef466_args": "--server.port=8080",
"56e192c8e76742fa85c72342212ef466_autoStart": "true",
"56e192c8e76742fa85c72342212ef466_dslEnv": "key1=v1",
"5722cd7a4e7a475cac61da2507088a9d_jvm": "-Xms=512m",
"5722cd7a4e7a475cac61da2507088a9d_token": "",
"5722cd7a4e7a475cac61da2507088a9d_args": "--server.port=8080",
"5722cd7a4e7a475cac61da2507088a9d_autoStart": "true",
"5722cd7a4e7a475cac61da2507088a9d_dslEnv": "",
"nodeIds": "56e192c8e76742fa85c72342212ef466,5722cd7a4e7a475cac61da2507088a9d"
},
{
"type": "add",
"id": "D5ZQF4",
"name": "dsldsl",
"afterOpt": "0",
"runMode": "Dsl",
"whitelistDirectory": "/home/tmn/jpom/project/ds/",
"lib": "dsfsadd",
"clearOld": "true",
"dslContent": "# scriptId 可以是项目路径下脚本文件名或者系统中的脚本模版ID\ndescription: 测试\nrun:\n start:\n# scriptId: project.sh\n scriptId: \n scriptArgs: start\n scriptEnv:\n \"boot_active\": test\n status:\n# scriptId: project.sh\n scriptId: \n scriptArgs: status\n stop:\n# scriptId: project.sh\n scriptId: \n scriptArgs: stop\n# restart:\n## scriptId: project.sh\n# scriptId: \n# scriptArgs: restart\n# scriptEnv:\n# \"boot_active\": test\nfile:\n# 备份文件保留个数\n# backupCount: 5\n# 限制备份指定文件后缀(支持正则)\n# backupSuffix: [ '.jar','.html','^.+\\.(?i)(txt)$' ]\n# 项目文件备份路径\n# backupPath: /data/jpom_backup\nconfig:\n# 是否开启日志备份功能\n# autoBackToFile: true\n\n",
"secondaryDirectory": "/second",
"group": "gg",
"logPath": "",
"uploadCloseFirst": "true",
"56e192c8e76742fa85c72342212ef466_dslEnv": "k1=v1",
"5722cd7a4e7a475cac61da2507088a9d_dslEnv": "k1=v2",
"nodeIds": "56e192c8e76742fa85c72342212ef466,5722cd7a4e7a475cac61da2507088a9d"
}

View File

@ -0,0 +1,458 @@
package outgiving
import (
"encoding/json"
err1 "errors"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/middleware"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/out_giving"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/server_white_list"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/system_parameters"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/response"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
node2 "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/gin-gonic/gin"
"strings"
"time"
)
// 创建分发项目
func SavePorject(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
typeStr, _ := bodyToMap["type"].(string)
id, _ := bodyToMap["id"].(string)
workspaceId := app_manage.GetWorkspaceId(c)
bodyToMap["workspace_id"] = workspaceId
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
if "add" == typeStr {
checkId := strings.ReplaceAll(id, "-", "")
checkId = strings.ReplaceAll(checkId, "_", "")
err := validateID(checkId)
if err != nil {
controller.FailCode(c, errors.ServerError, err, err.Error())
return
}
addOutGiving(c, id, bodyToMap, userModel)
} else {
updateGiving(c, id, bodyToMap, userModel)
}
}
func DeletePorject(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
id, ok := bodyToMap["id"]
if !ok {
controller.Fail(c, errors.ServerError, "id不能为空")
return
}
thorough, ok := bodyToMap["thorough"]
if !ok {
controller.Fail(c, errors.ServerError, "id不能为空")
return
}
outGivingModel := out_giving.GetOutGivingModelById(id.(string))
if outGivingModel == nil {
controller.Fail(c, errors.ServerError, "没有找到对应的分发项目")
return
}
// 该项目不是节点分发项目,不能在此次删除
if !outGivingModel.OutGivingProject {
controller.Fail(c, errors.ServerError, "该项目不是节点分发项目,不能在此处删除")
return
}
outGivingNodeProjectList := outGivingModel.GetOutGivingNodeProjectList()
for _, outGivingNodeProject := range outGivingNodeProjectList {
nodeModel := node.GetNodeById(outGivingNodeProject.NodeId)
if nodeModel.Id == "" {
controller.Fail(c, errors.ServerError, "没有找到对应的节点")
return
}
resp := deleteNodeProject(nodeModel, outGivingModel.Id, thorough.(string))
if resp.Result.Code != errors.SUCCESS {
controller.FailCode(c, errors.ServerError, err1.New(resp.Result.Msg), nodeModel.Name+"节点失败: "+resp.Result.Msg)
return
}
}
rows, err := out_giving.DeleteOutGivingModelById(id.(string))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除分发失败")
return
}
if rows >= 0 {
out_giving.DeleteOutGivingLogByOutGivingId(id.(string))
}
controller.Success(c, "删除成功")
}
func addOutGiving(c *gin.Context, id string, body map[string]interface{}, userObj *user.UserObj) {
byId := out_giving.GetOutGivingModelById(id)
if byId.Id != "" {
controller.FailCode(c, errors.ServerError, nil, "项目id已存在")
return
}
outGivingModel := out_giving.NewOutGivingModel()
outGivingModel.Id = id
outGivingModel.OutGivingProject = true
outGivingModel.CreateUser = userObj.UserID
outGivingModel.ModifyUser = userObj.UserID
tuples, err := doData(outGivingModel, body, false)
if err != nil {
controller.FailCode(c, errors.ServerError, err, err.Error())
return
}
_, err = outGivingModel.Create(outGivingModel)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "项目id已存在")
return
}
// saveNodeData
res := saveNodeData(outGivingModel, tuples, false)
if res != nil {
controller.Fail(c, errors.ServerError, res.Result.Msg)
return
}
controller.Success(c, "添加成功")
}
func updateGiving(c *gin.Context, id string, body map[string]interface{}, userObj *user.UserObj) {
outGivingModel := out_giving.GetOutGivingModelById(id)
if outGivingModel == nil {
controller.Fail(c, errors.ServerError, "没有找到对应的分发id")
return
}
tuples, err := doData(outGivingModel, body, true)
if err != nil {
controller.FailCode(c, errors.ServerError, err, err.Error())
return
}
outGivingModel.ModifyUser = userObj.UserID
outGivingModel.ModifyTimeMillis = time.Now().UnixMilli()
err = out_giving.UpdateOutGivingModel(outGivingModel, []string{"name", "group", "workspace_id", "after_opt", "secondary_directory",
"out_giving_node_project_list", "clear_old", "upload_close_first", "secondary_directory", "modify_user", "modify_time_millis"})
if err != nil {
controller.FailCode(c, errors.ServerError, err, "修改失败")
return
}
// saveNodeData 保存节点项目数据
res := saveNodeData(outGivingModel, tuples, true)
if res != nil {
controller.Fail(c, errors.ServerError, res.Result.Msg)
return
}
controller.Success(c, "修改成功")
}
// 保存节点项目数据
func saveNodeData(outGivingModel *out_giving.OutGivingModel, tuples map[string]map[string]interface{}, edit bool) *response.Response {
successTuples := map[string]interface{}{}
fail := false
for key, value := range tuples {
nodeModelId := key
n, _ := app_manage.TryGetNode(map[string]interface{}{"nodeId": nodeModelId})
dataJSON := value
jsonMessage := sendData(n, dataJSON, true)
if jsonMessage.Result.Code != errors.SUCCESS {
if !edit {
out_giving.DeleteOutGivingModelById(outGivingModel.Id)
fail = true
}
if fail {
for nodeModelId, _ = range successTuples {
resp := deleteNodeProject(n, outGivingModel.Id, "")
if resp.Result.Code != errors.SUCCESS {
log.Errorf("还原项目失败:" + resp.Result.Msg)
}
}
}
jsonMessage.Result.Msg = n.Name + "节点失败:" + jsonMessage.Result.Msg
return jsonMessage
}
successTuples[key] = value
// 同步项目信息
node2.SyncNodeProjectInfo(n, outGivingModel.Id)
}
return nil
}
// 创建项目管理的默认数据
func getDefData(outGivingModel *out_giving.OutGivingModel, body map[string]interface{}, edit bool) (map[string]interface{}, error) {
defData := make(map[string]interface{})
defData["id"] = outGivingModel.Id
defData["name"] = outGivingModel.Name
defData["group"] = outGivingModel.Group
runMode := body["runMode"]
defData["runMode"] = runMode
if runMode == "Dsl" {
dslContent := body["dslContent"]
defData["dslContent"] = dslContent
}
whitelistDirectory := body["whitelistDirectory"]
// 获取白名单
s := &system_parameters.SystemParameters{}
s.Id = server_white_list.GetId(outGivingModel.WorkspaceId)
_ = s.GetById(s)
serverWhitelist := &server_white_list.ServerWhitelist{}
if s.Value != "" {
err := json.Unmarshal([]byte(s.Value), serverWhitelist)
if err != nil {
return nil, err
}
}
var outGivingArray []string
if strings.Contains(serverWhitelist.OutGiving, "\r\n") == true {
outGivingArray = strings.Split(serverWhitelist.OutGiving, "\r\n")
} else {
outGivingArray = strings.Split(serverWhitelist.OutGiving, "\n")
}
checkPath := server_white_list.CheckPath(outGivingArray, whitelistDirectory.(string))
if !checkPath {
return nil, fmt.Errorf("请选择正确的项目路径,或者还没有配置白名单")
}
defData["whitelistDirectory"] = whitelistDirectory
logPath := body["logPath"]
if logPath != nil && strings.TrimSpace(logPath.(string)) != "" {
checkPath = server_white_list.CheckPath(outGivingArray, whitelistDirectory.(string))
if !checkPath {
return nil, fmt.Errorf("请选择正确的日志路径,或者还没有配置白名单")
}
defData["logPath"] = logPath
}
lib := body["lib"]
defData["lib"] = lib
if edit {
defData["edit"] = "on"
}
defData["previewData"] = true
return defData, nil
}
// 参考outGivingProjectEditController.example.json
func doData(outGivingModel *out_giving.OutGivingModel, body map[string]interface{}, edit bool) (map[string]map[string]interface{}, error) {
tuples := make(map[string]map[string]interface{})
outGivingModel.Name = body["name"].(string)
if outGivingModel.Name == "" {
return tuples, fmt.Errorf("分发名称不能为空")
}
outGivingModel.WorkspaceId = body["workspace_id"].(string)
if groupStr, ok := body["group"]; ok {
if groupStr != nil {
s := groupStr.(string)
outGivingModel.Group = &s
}
}
if afterOpt, ok := body["afterOpt"]; ok {
afterOptInt, err := afterOpt.(float64)
if err != true {
return tuples, fmt.Errorf("参数有误afterOpt")
}
outGivingModel.AfterOpt = int(afterOptInt)
} else {
return tuples, fmt.Errorf("请选择分发后的操作")
}
outGivingModel.IntervalTime = 10
if clearOld, ok := body["clearOld"]; ok {
//toBool := utils.StringToBool(clearOld.(string))
outGivingModel.ClearOld = clearOld.(bool)
}
if secondaryDirectory, ok := body["secondaryDirectory"]; ok {
if secondaryDirectory != nil {
s := secondaryDirectory.(string)
outGivingModel.SecondaryDirectory = &s
}
}
if uploadCloseFirst, ok := body["uploadCloseFirst"]; ok {
//toBool := utils.StringToBool(uploadCloseFirst.(string))
outGivingModel.UploadCloseFirst = uploadCloseFirst.(bool)
}
// 填充其他数据
defData, err := getDefData(outGivingModel, body, edit)
if err != nil {
return tuples, err
}
//
nodeIdsStr := body["nodeIds"]
nodeIds := strings.Split(nodeIdsStr.(string), ",")
if len(nodeIds) == 0 {
return tuples, fmt.Errorf("没有任何节点信息")
}
outGivingModels := out_giving.ListOutGivingModel()
outGivingNodeProjects := make([]out_giving.OutGivingNodeProject, 0)
var outGivingNodeProject *out_giving.OutGivingNodeProject
for _, nodeId := range nodeIds {
nodeModel, err := app_manage.TryGetNode(map[string]interface{}{"nodeId": nodeId})
if err != nil {
return tuples, fmt.Errorf("对应的节点不存在")
}
// 判断项目是否已被使用,还要兼容新增和编辑两种场景!
if outGivingModels != nil {
for _, model := range outGivingModels {
if strings.EqualFold(model.Id, outGivingModel.Id) {
continue
}
contains := model.CheckContains(nodeId, outGivingModel.Id)
if contains {
return tuples, fmt.Errorf("已经存在相同的分发项目: %s", outGivingModel.Id)
}
}
}
nodeProjectList := outGivingModel.GetOutGivingNodeProjectList()
// 分发 ID 等同于项目 ID
outGivingNodeProject = outGivingModel.GetNodeProject(nodeProjectList, nodeModel.Id, outGivingModel.Id)
if outGivingNodeProject == nil {
outGivingNodeProject = &out_giving.OutGivingNodeProject{}
}
outGivingNodeProject.NodeId = nodeModel.Id
// 分发id为项目id
outGivingNodeProject.ProjectId = outGivingModel.Id
outGivingNodeProjects = append(outGivingNodeProjects, *outGivingNodeProject)
// 准备并检查数据
allData := cloneMap(defData)
token := body[(fmt.Sprintf("%s_token", nodeModel.Id))]
allData["token"] = token
jvm := body[fmt.Sprintf("%s_jvm", nodeModel.Id)]
allData["jvm"] = jvm
args := body[fmt.Sprintf("%s_args", nodeModel.Id)]
allData["args"] = args
autoStart := body[fmt.Sprintf("%s_autoStart", nodeModel.Id)]
//allData["autoStart"] = autoStart.(bool)
if _, ok := autoStart.(bool); ok {
allData["autoStart"] = autoStart.(bool)
}
dslEnv := body[fmt.Sprintf("%s_dslEnv", nodeModel.Id)]
allData["dslEnv"] = dslEnv
jsonMessage := sendData(nodeModel, allData, false)
if jsonMessage.Result.Code != errors.SUCCESS {
return tuples, fmt.Errorf("%s节点失败%s", nodeModel.Name, jsonMessage.Result.Msg)
}
tuples[nodeModel.Id] = allData
}
if len(outGivingNodeProjects) == 0 {
return tuples, fmt.Errorf("至少选择1个节点项目")
}
// 删除已经删除的项目
err = deleteProject(outGivingModel, outGivingNodeProjects)
if err != nil {
return nil, fmt.Errorf("删除项目失败: %s", err.Error())
}
outGivingModel.SetOutGivingNodeProjectList(outGivingNodeProjects)
return tuples, nil
}
// 删除已经删除过的项目
func deleteProject(outGivingModel *out_giving.OutGivingModel, outGivingNodeProjects []out_giving.OutGivingNodeProject) error {
// 获取需要删除的节点项目
deleteNodeProjects := outGivingModel.GetDelete(outGivingNodeProjects)
if deleteNodeProjects != nil {
for _, outGivingNodeProject := range deleteNodeProjects {
nodeModel, err := app_manage.TryGetNode(map[string]interface{}{"nodeId": outGivingNodeProject.NodeId})
if err != nil {
return fmt.Errorf("节点不存在")
}
//outGivingNodeProject.GetNodeData(true) // 假设这是获取某些数据的操作,需根据实际情况实现或忽略
// 彻底删除节点项目
jsonMessage := deleteNodeProject(nodeModel, outGivingNodeProject.ProjectId, "thorough")
if jsonMessage.Result.Code != errors.SUCCESS {
return fmt.Errorf("%s节点失败%s", nodeModel.Name, jsonMessage.Result.Msg)
}
}
}
return nil
}
// 删除项目
func deleteNodeProject(nodeModel *node.Node, projectId, thorough string) *response.Response {
data := make(map[string]interface{})
data["id"] = projectId
data["thorough"] = thorough
data["nodeId"] = nodeModel.Id
resp := app_manage.TryRequestNode("", data, common.Manage_DeleteProject)
if resp.Result.Code == errors.SUCCESS {
node2.SyncNodeProjectInfo(nodeModel, projectId)
}
return resp
}
func sendData(nodeModel *node.Node, data map[string]interface{}, save bool) *response.Response {
if save {
delete(data, "previewData")
}
data["outGivingProject"] = true
data["nodeId"] = nodeModel.Id
return app_manage.TryRequestNode("", data, common.Manage_SaveProject)
}
func cloneMap(body map[string]interface{}) map[string]interface{} {
clone := make(map[string]interface{})
for k, v := range body {
// 注意这里仅实现了浅拷贝如果value本身是复杂的数据结构如嵌套的map或slice
// 那么这个拷贝过程不会递归拷贝内部结构,它们仍然会被共享。
clone[k] = v
}
return clone
}

View File

@ -0,0 +1,520 @@
package outgiving
import (
"bytes"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/middleware"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/out_giving"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/out_giving/status"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/project_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/file_download_client"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/response"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/secure"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/utils"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/utils/file_utils"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/outgiving"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"io/ioutil"
"net/http"
"path/filepath"
"strings"
"time"
)
// 分发文件管理
func GetItemData(c *gin.Context) {
workspaceId := app_manage.GetWorkspaceId(c)
bodyToMap := request.GetBodyToMap(c)
id, ok := bodyToMap["id"].(string)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误id")
return
}
outGivingModel := out_giving.GetOutGivingModelById(id)
if outGivingModel.Id == "" {
controller.Fail(c, http.StatusBadRequest, "没有数据")
return
}
outGivingNodeProjectList := outGivingModel.GetOutGivingNodeProjectList()
nodeMap := mapNodeModels(outGivingNodeProjectList)
projectMap := mapProjectInfoCacheModels(outGivingNodeProjectList, workspaceId)
collect := transformOutGivingNodeProjects(outGivingNodeProjectList, workspaceId, nodeMap, projectMap, id)
data := map[string]interface{}{
"data": outGivingModel,
"projectList": collect,
}
controller.Success(c, data)
}
func mapNodeModels(outGivingNodeProjectList []out_giving.OutGivingNodeProject) map[string]node.Node {
var nodeIds []string
for _, project := range outGivingNodeProjectList {
nodeIds = append(nodeIds, project.NodeId)
}
nodeModels := node.GetNodeByIds(nodeIds)
nodeMap := make(map[string]node.Node)
for _, model := range nodeModels {
nodeMap[model.Id] = model
}
return nodeMap
}
func mapProjectInfoCacheModels(outGivingNodeProjectList []out_giving.OutGivingNodeProject, workspaceId string) map[string]project_info.ProjectInfo {
var ids []string
for _, project := range outGivingNodeProjectList {
projectId := secure.Sha1Encrypt(workspaceId + project.NodeId + project.ProjectId)
// todo 检查projectId是否能对得上
ids = append(ids, projectId)
}
var projectInfoCacheModels = project_info.GetProjectByIds(ids)
projectMap := make(map[string]project_info.ProjectInfo)
for _, model := range projectInfoCacheModels {
projectMap[model.Id] = model
}
return projectMap
}
func transformOutGivingNodeProjects(outGivingNodeProjectList []out_giving.OutGivingNodeProject, workspaceId string, nodeMap map[string]node.Node, projectMap map[string]project_info.ProjectInfo, id string) []map[string]interface{} {
var collect []map[string]interface{}
for _, outGivingNodeProject := range outGivingNodeProjectList {
nodeModel, exists := nodeMap[outGivingNodeProject.NodeId]
if !exists {
continue
}
// 填充数据ID项目数据是先保存至逻辑节点后又同步的思路所以NodeScriptCacheModel在逻辑节点的Id就是ProjectId在服务端保存时将Id保存至ProjectId字段
// 然后根据secure.Sha1Encrypt(item.WorkspaceId + item.NodeId + item.ProjectId)生成Id
fullId := secure.Sha1Encrypt(workspaceId + outGivingNodeProject.NodeId + outGivingNodeProject.ProjectId)
projectInfoCacheModel, exists := projectMap[fullId]
var cacheProjectName interface{}
if exists {
cacheProjectName = projectInfoCacheModel.Name
}
outGivingLog, _ := outgiving.GetByProject(id, outGivingNodeProject)
var outGivingStatus, outGivingResult, lastTime, fileSize, progressSize interface{}
if outGivingLog != nil {
outGivingStatus = outGivingLog.Status
outGivingResult = outGivingLog.Result
lastTime = outGivingLog.CreateTimeMillis
fileSize = outGivingLog.FileSize
progressSize = outGivingLog.ProgressSize
}
jsonObject := map[string]interface{}{
"sortValue": outGivingNodeProject.SortValue,
"disabled": outGivingNodeProject.Disabled,
"nodeId": outGivingNodeProject.NodeId,
"projectId": outGivingNodeProject.ProjectId,
"nodeName": nodeModel.Name,
"id": fullId,
"cacheProjectName": cacheProjectName,
"outGivingStatus": outGivingStatus,
"outGivingResult": outGivingResult,
"lastTime": lastTime,
"fileSize": fileSize,
"progressSize": progressSize,
}
collect = append(collect, jsonObject)
}
return collect
}
func UploadSharding(c *gin.Context) {
// 获取FormData
form, err := c.MultipartForm()
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "解析请求参数失败")
return
}
id := form.Value["id"][0]
// 状态判断
_, err = check(id, func(statusCode int, o *out_giving.OutGivingModel) error {
if statusCode == status.ING {
return fmt.Errorf("当前还在分发中,请等待分发结束")
}
return nil
})
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "当前还在分发中,请等待分发结束")
return
}
// 提取表单字段
nowSlice := form.Value["nowSlice"][0]
totalSlice := form.Value["totalSlice"][0]
sliceID := form.Value["sliceId"][0]
fileSumMD5 := form.Value["fileSumMd5"][0]
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "Failed to get file")
return
}
defer file.Close()
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
tempPathName, err := utils.GetUserTempPath(userModel.UserID)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "上传失败:创建临时目录失败")
return
}
err = app_manage.UploadShardingImpl(file, header.Filename, tempPathName, sliceID, totalSlice, nowSlice, fileSumMD5, nil)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, err.Error())
return
}
controller.Success(c, "上传成功")
}
func check(id string, consumer func(int, *out_giving.OutGivingModel) error) (*out_giving.OutGivingModel, error) {
outGivingModel := out_giving.GetOutGivingModelById(id)
if outGivingModel.Id == "" {
return nil, fmt.Errorf("上传失败,没有找到对应的分发项目: %s", id)
}
// 检查状态
statusCode := outGivingModel.Status
return outGivingModel, consumer(statusCode, outGivingModel)
}
func ShardingMerge(c *gin.Context) {
var (
autoUnzip bool
secondaryDirectory string
stripComponents float64
sliceID string
totalSlice float64
fileSumMD5 string
afterOpt float64
clearOld bool
id string
selectProject string
)
bodyToMap := request.GetBodyToMap(c)
autoUnzip, ok := bodyToMap["autoUnzip"].(bool) // type
secondaryDirectory, ok = bodyToMap["secondaryDirectory"].(string)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误levelName")
return
}
stripComponents, ok = bodyToMap["stripComponents"].(float64)
sliceID, ok = bodyToMap["sliceId"].(string)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误sliceID")
return
}
totalSlice, ok = bodyToMap["totalSlice"].(float64)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误totalSlice")
return
}
fileSumMD5, ok = bodyToMap["fileSumMd5"].(string)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误fileSumMD5")
return
}
afterOpt, ok = bodyToMap["afterOpt"].(float64)
id, ok = bodyToMap["id"].(string)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误id")
return
}
clearOld, ok = bodyToMap["clearOld"].(bool)
selectProject, ok = bodyToMap["selectProject"].(string)
// 状态判断
_, err := check(id, func(statusCode int, o *out_giving.OutGivingModel) error {
if statusCode == status.ING {
return fmt.Errorf("当前还在分发中,请等待分发结束")
}
return nil
})
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "当前还在分发中,请等待分发结束")
return
}
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
tempPathName, err := utils.GetUserTempPath(userModel.UserID)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "上传失败:创建临时目录失败[tempPathName]")
return
}
path, err := utils.GetDataPath()
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "上传失败:创建临时目录失败[data]")
return
}
destDir := filepath.Join(path, "outGiving", id)
err = file_utils.MkdirAll(destDir)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "上传失败:创建临时目录失败[outGiving Dir]")
return
}
successFile, err := app_manage.ShardingTryMergeImpl(tempPathName, sliceID, int(totalSlice), fileSumMD5)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, err.Error())
return
}
// 从绝对路径successFile.Name中获取文件名
successFileName := filepath.Base(successFile.Name())
destFile := filepath.Join(destDir, successFileName)
destFile, err = checkZip(destFile, autoUnzip)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, err.Error())
return
}
err = file_utils.MoveFileOrDir(successFile.Name(), destDir, true)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "上传失败:创建临时目录失败[outGiving dest]")
return
}
outGivingModel := out_giving.NewOutGivingModel()
outGivingModel.Id = id
outGivingModel.ClearOld = clearOld
after := int(afterOpt)
outGivingModel.AfterOpt = after
outGivingModel.SecondaryDirectory = &secondaryDirectory
outGivingModel.ModifyUser = userModel.UserID
outGivingModel.ModifyTimeMillis = time.Now().UnixMilli()
out_giving.UpdateOutGivingModel(outGivingModel, []string{"after_opt", "secondary_directory", "clear_old", "secondary_directory", "modify_user", "modify_time_millis"})
stripComponentsInt := int(stripComponents)
outGivingExecId := uuid.New().String()
outGivingRun := outgiving.NewOutGivingRun(id, destFile, userModel, autoUnzip, stripComponentsInt, true)
go outGivingRun.StartRun(selectProject, outGivingExecId)
//outGivingRun.StartRun(selectProject)
dataResp := response.Resp()
dataResp.SetCode(http.StatusOK)
dataResp.Result.Data = map[string]string{
"result": "上传成功,开始分发!",
"outGivingExecId": outGivingExecId,
}
dataResp.Success(c)
//controller.Success(c, "上传成功,开始分发!")
}
func RemoteDownload(c *gin.Context) {
var (
id string
afterOpt float64
clearOld bool
url string
autoUnzip bool
secondaryDirectory string
stripComponents float64
selectProject string
filename string
)
bodyToMap := request.GetBodyToMap(c)
id, ok := bodyToMap["id"].(string)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误id")
return
}
afterOpt, ok = bodyToMap["afterOpt"].(float64)
clearOld, ok = bodyToMap["clearOld"].(bool)
url, ok = bodyToMap["url"].(string)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误url")
return
}
autoUnzip, ok = bodyToMap["autoUnzip"].(bool) // type
secondaryDirectory, ok = bodyToMap["secondaryDirectory"].(string)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误secondaryDirectory")
return
}
stripComponents, ok = bodyToMap["stripComponents"].(float64)
selectProject, ok = bodyToMap["selectProject"].(string)
filename, ok = bodyToMap["filename"].(string)
// 状态判断
outGivingModel, err := check(id, func(statusCode int, o *out_giving.OutGivingModel) error {
if statusCode == status.ING {
return fmt.Errorf("当前还在分发中,请等待分发结束")
}
return nil
})
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "当前还在分发中,请等待分发结束")
return
}
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
outGivingModel.ClearOld = clearOld
after := afterOpt
outGivingModel.AfterOpt = int(after)
outGivingModel.SecondaryDirectory = &secondaryDirectory
outGivingModel.ModifyUser = userModel.UserID
outGivingModel.ModifyTimeMillis = time.Now().UnixMilli()
out_giving.UpdateOutGivingModel(outGivingModel, []string{"after_opt", "secondary_directory", "clear_old", "secondary_directory", "modify_user", "modify_time_millis"})
// 下载
path, err := utils.GetDataPath()
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "上传失败:创建临时目录失败[data]")
return
}
destDir := filepath.Join(path, "outGiving", id)
err = file_utils.MkdirAll(destDir)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "上传失败:创建临时目录失败[outGiving Dir]")
return
}
var destFile string
if len(filename) == 0 {
destFile = filepath.Join(destDir, uuid.New().String())
} else {
destFile = filepath.Join(destDir, filename)
}
err = file_download_client.DownloadFileToLocal(url, destFile, 2)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "下载文件失败")
return
}
destFile, err = CheckType(destFile, autoUnzip)
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, err.Error())
return
}
// 开启
stripComponentsInt := int(stripComponents)
outGivingExecId := uuid.New().String()
outGivingRun := outgiving.NewOutGivingRun(id, destFile, userModel, autoUnzip, stripComponentsInt, true)
go outGivingRun.StartRun(selectProject, outGivingExecId)
dataResp := response.Resp()
dataResp.SetCode(http.StatusOK)
dataResp.Result.Data = map[string]string{
"result": "上传成功,开始分发!",
"outGivingExecId": outGivingExecId,
}
dataResp.Success(c)
//controller.Success(c, "上传成功,开始分发!")
}
func CheckType(filePath string, unzip bool) (string, error) {
if !unzip {
return filePath, nil
}
data, err := ioutil.ReadFile(filePath)
if err != nil {
log.Errorf("Error reading file: %v", err)
return "", fmt.Errorf("读取文件失败: %s", filePath)
}
switch {
case bytes.HasPrefix(data, []byte{0x50, 0x4b, 0x03, 0x04}): // ZIP format
return filePath, nil
case bytes.HasPrefix(data, []byte{0x1f, 0x8b}):
return filePath, nil
default:
log.Errorf("UnSupported file type.")
return "", fmt.Errorf("不支持的文件类型: %s", filePath)
}
}
var PACKAGE_EXT = []string{"tar.gz", "zip"}
// 检查并处理ZIP文件
func checkZip(path string, unzip bool) (string, error) {
if unzip {
zip := false
for _, ext := range PACKAGE_EXT {
if strings.HasSuffix(strings.ToLower(path), strings.ToLower(ext)) {
zip = true
break
}
}
if !zip {
return "", fmt.Errorf("不支持的文件类型: %s", filepath.Base(path))
}
}
// 这里仅返回文件对象,未执行解压逻辑,如需解压还需额外实现
return path, nil
}
func Cancel(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
id, ok := bodyToMap["id"].(string)
if !ok {
controller.Fail(c, http.StatusBadRequest, "参数错误id")
return
}
// 状态判断
outGigving, err := check(id, func(statusCode int, o *out_giving.OutGivingModel) error {
if statusCode != status.ING {
return fmt.Errorf("当前状态不是分发中")
}
return nil
})
if err != nil {
controller.FailCode(c, http.StatusInternalServerError, err, "当前状态不是分发中")
return
}
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
outgiving.Cancel(outGigving.Id, userModel)
controller.Success(c, "取消成功")
}

View File

@ -0,0 +1,65 @@
package outgiving
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/server_white_list"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/system_parameters"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/workspace"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
func GetWhiteList(c *gin.Context) {
workspaceId := c.Query("workspaceId")
if workspaceId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
s := &system_parameters.SystemParameters{}
s.Id = server_white_list.GetId(workspaceId)
_ = s.GetById(s)
controller.Success(c, s.Value)
}
func SaveWhiteList(c *gin.Context) {
workspaceId := c.PostForm("workspaceId")
if workspaceId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
w := &workspace.Workspace{}
w.Id = workspaceId
err := w.GetById(w)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
value := c.PostForm("value")
s := &system_parameters.SystemParameters{}
s.Id = server_white_list.GetId(workspaceId)
err = s.GetById(s)
if err == nil {
s.Value = value
s.SetModifyUserName(service.GetUserName(c))
_, err = s.Update(s, []string{"value"})
} else if err == gorm.ErrRecordNotFound {
s.Value = value
s.SetUserName(service.GetUserName(c))
_, err = s.Create(s)
} else {
controller.FailCode(c, errors.InvalidParameter, err, "服务异常!")
return
}
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "服务异常!")
return
}
controller.Success(c, "")
}

View File

@ -0,0 +1,367 @@
package server_script
import (
"encoding/json"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/middleware"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
node2 "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/script_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/workspace"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/utils/collect"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/utils/string_utils"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/base_service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/node/script"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/server_script"
"github.com/gin-gonic/gin"
"os"
"strconv"
"strings"
"time"
)
type ProjectPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
}
func ScriptList(c *gin.Context) {
var pageBody ProjectPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
workspaceId := app_manage.GetWorkspaceId(c)
fields["workspace_id"] = []string{workspaceId, "GLOBAL"}
page := &model.Page[script_info.ScriptModel]{
CurrentPage: pageBody.PageInfo.CurrentPage,
PageSize: pageBody.PageInfo.PageSize,
Order: pageBody.PageInfo.Order,
}
model := script_info.NewScriptModel()
err = model.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "查询脚本失败!")
return
}
controller.Success(c, page)
}
func ScriptListAll(c *gin.Context) {
fields := map[string]interface{}{}
workspaceId := app_manage.GetWorkspaceId(c)
fields["workspace_id"] = []string{workspaceId, "GLOBAL"}
model := script_info.NewScriptModel()
all, err := model.GetAll(model, fields, nil)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "查询脚本失败!")
return
}
controller.Success(c, all)
}
// Save 保存脚本
// 1.服务端保存脚本 2.分发脚本
func Save(c *gin.Context) {
body := request.GetBody(c)
model := script_info.NewScriptModel()
err := json.Unmarshal(body, model)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "请检查参数!")
return
}
if len(model.Context) == 0 {
controller.FailCode(c, errors.ServerError, err, "内容为空!")
}
err = string_utils.CheckCron(model.AutoExecCron)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "cron表达式错误")
return
}
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
var oldNodeIds string
workspaceId := app_manage.GetWorkspaceId(c)
// 检查用户工作空间权限? 好像没用
checkUserWorkspace := base_service.CheckUserWorkspace(workspaceId, userModel)
if !checkUserWorkspace {
controller.Fail(c, errors.ServerError, "没有对应的工作空间权限")
}
toMap := request.GetBodyToMap(c)
global := toMap["global"].(bool)
if !global {
model.WorkspaceId = workspaceId
} else {
model.WorkspaceId = "GLOBAL"
}
if len(model.Id) == 0 {
model.ModifyUser = userModel.UserID
model.CreateUser = userModel.UserID
model.CreateTimeMillis = time.Now().UnixNano() / 1e6
model.ModifyTimeMillis = time.Now().UnixNano() / 1e6
_, err = model.Create(model)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "保存失败")
return
}
} else {
scriptModel, err := script_info.GetServerScriptModelByIdAndWorkspaceIds(model.Id, []string{workspaceId, "GLOBAL"})
if err != nil {
controller.FailCode(c, errors.ServerError, err, "没有对应的数据")
return
}
if scriptModel.Id == "" {
controller.FailCode(c, errors.ServerError, err, "没有对应的数据")
return
}
oldNodeIds = scriptModel.NodeIds
model.ModifyUser = userModel.UserID
model.ModifyTimeMillis = time.Now().UnixNano() / 1e6
_, err = model.Update(model, []string{"name", "context", "auto_exec_cron", "def_args", "description", "node_ids", "workspace_id", "modify_user", "modify_time_millis"})
if err != nil {
controller.FailCode(c, errors.ServerError, err, "更新失败")
return
}
}
err = syncNodeScript(model, oldNodeIds)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "同步脚本失败")
return
}
controller.Success(c, "修改成功")
}
func syncDelNodeScript(scriptModel *script_info.ScriptModel, delNode []string) {
for _, nodeId := range delNode {
bodyToMap := map[string]interface{}{
"id": scriptModel.Id,
"nodeId": nodeId,
}
node, err := app_manage.TryGetNode(bodyToMap)
if err != nil {
continue
}
response := app_manage.TryRequestNode(node.MachineId, bodyToMap, common.Script_Del)
if response.Result.Code == errors.SUCCESS {
// 每个涉及到的节点均需要刷新
go script.SyncExecuteNode(node)
}
}
}
func syncNodeScript(scriptModel *script_info.ScriptModel, oldNode string) error {
oldNodeIds := strings.Split(strings.TrimSpace(oldNode), ",") // [1,2,3]
newNodeIds := strings.Split(strings.TrimSpace(scriptModel.NodeIds), ",") // [2,3,4]
// 删除oldNodeIds中的空字符串
oldNodeIds = collect.SubtractCollections(oldNodeIds, []string{"", " "})
newNodeIds = collect.SubtractCollections(newNodeIds, []string{"", " "})
delNode := collect.SubtractCollections(oldNodeIds, newNodeIds) // [1]
// 同步删除了次脚本的节点 // [1]
syncDelNodeScript(scriptModel, delNode)
// 更新 // [2,3,4]
for _, newNodeId := range newNodeIds {
bodyToMap := map[string]interface{}{
"nodeId": newNodeId,
}
node, err := app_manage.TryGetNode(bodyToMap)
if err != nil {
return err
}
bodyToMap = map[string]interface{}{
"id": scriptModel.Id,
"type": "sync",
"context": scriptModel.Context,
"autoExecCron": scriptModel.AutoExecCron,
"defArgs": scriptModel.DefArgs,
"description": scriptModel.Description,
"name": scriptModel.Name,
"workspaceId": node.WorkspaceId,
"global": strconv.FormatBool(scriptModel.Global()),
"nodeId": node.Id,
}
response := app_manage.TryRequestNode(node.MachineId, bodyToMap, common.Script_Save)
if response.Result.Code == errors.SUCCESS {
go script.SyncExecuteNode(node)
} else {
return fmt.Errorf("处理 %s 节点同步脚本失败: %s", node.Name, response.Result.Msg)
}
}
return nil
}
func Del(c *gin.Context) {
bodyToMap := request.GetBodyToMap(c)
id, _ := bodyToMap["id"].(string)
workspaceId := app_manage.GetWorkspaceId(c)
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
scriptModel, err := server_script.GetByKeyAndGlobal(id, workspaceId, userModel)
if err != nil {
controller.FailCode(c, errors.ServerError, err, err.Error())
return
}
// server.scriptPath() 的逻辑需要根据实际情况实现
file, _ := scriptModel.ScriptPath()
err = os.RemoveAll(file)
if err != nil {
controller.FailCode(c, errors.ServerError, err, fmt.Sprintf("清理脚本文件失败: %v", err))
return
}
// nodeIds 的分割逻辑
nodeIds := scriptModel.NodeIds
delNode := strings.Split(strings.TrimSpace(nodeIds), ",")
// syncDelNodeScript 和 scriptServer.delByKey 的逻辑需要根据实际情况实现
syncDelNodeScript(scriptModel, delNode)
_, err = script_info.DeleteServerScriptByIdAndWorkspaceIds(scriptModel.Id, []string{workspaceId, "GLOBAL"})
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除脚本失败:")
return
}
//// scriptExecuteLogServer.delByWorkspace 的逻辑需要根据实际情况实现
//entity := map[string]interface{}{"scriptId": id}
//err = scriptExecuteLogServer.delByWorkspace(request, entity)
//if err != nil {
// panic(err)
//}
controller.Success(c, "删除成功")
}
func Get(c *gin.Context) {
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
workspaceId := app_manage.GetWorkspaceId(c)
checkUserWorkspace := base_service.CheckUserWorkspace(workspaceId, userModel)
if !checkUserWorkspace {
controller.Fail(c, errors.ServerError, "没有对应的工作空间权限")
}
id := c.Query("id")
scriptModel, err := server_script.GetByKeyAndGlobal(id, workspaceId, userModel)
if err != nil {
controller.FailCode(c, errors.ServerError, err, err.Error())
return
}
nodeIds := scriptModel.NodeIds
newNodeIds := strings.Split(strings.TrimSpace(nodeIds), ",")
nodeList := make([]gin.H, 0, len(newNodeIds))
for _, nodeId := range newNodeIds {
node := node2.GetNodeById(nodeId)
if node.Id == "" {
nodeList = append(nodeList, gin.H{"nodeName": "未知(数据丢失)", "nodeId": nodeId, "workspaceId": nodeId})
} else {
jsonObj := gin.H{
"nodeName": node.Name,
"nodeId": node.Id,
"workspaceId": node.WorkspaceId,
}
ws, err := workspace.GetWorkspaceById(node.WorkspaceId)
if err != nil || ws == nil {
jsonObj["workspaceName"] = "未知(数据丢失)"
} else {
jsonObj["workspaceName"] = ws.Name
}
nodeList = append(nodeList, jsonObj)
}
}
// 判断是否可以编辑节点不属于自己管理的node,则不具备管理权权限
var prohibitSync bool
for _, n := range nodeList {
if wsID, ok := n["workspaceId"].(string); ok && wsID != workspaceId {
prohibitSync = true
break
}
}
jsonOut := gin.H{
"data": scriptModel,
"nodeList": nodeList,
"prohibitSync": prohibitSync,
}
controller.Success(c, jsonOut)
}
func Unbind(c *gin.Context) {
id := c.Query("id")
fields := map[string]interface{}{}
fields["node_ids"] = ""
_, err := script_info.UpdateServerScriptById(id, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "解绑失败")
return
}
controller.Success(c, "解绑成功")
}
func SyncToWorkspace(c *gin.Context) {
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
nowWorkspaceId := app_manage.GetWorkspaceId(c)
ids := c.Query("ids")
toWorkspaceId := c.Query("toWorkspaceId")
checkUserWorkspace := base_service.CheckUserWorkspace(nowWorkspaceId, userModel)
if !checkUserWorkspace {
controller.Fail(c, errors.ServerError, "没有对应的工作空间权限")
}
checkUserWorkspace = base_service.CheckUserWorkspace(toWorkspaceId, userModel)
if !checkUserWorkspace {
controller.Fail(c, errors.ServerError, "没有对应的工作空间权限")
}
err := server_script.SyncToWorkspace(ids, nowWorkspaceId, toWorkspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "同步失败")
return
}
controller.Success(c, "同步成功")
}

View File

@ -0,0 +1,355 @@
package system
import (
err1 "errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/middleware"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/out_giving"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/project_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/script_info"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/workspace"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/workspace_env"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/assets"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/base_service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"github.com/gin-gonic/gin"
"strings"
)
func EditWorkspace(c *gin.Context) {
w := &workspace.Workspace{}
err := validator.CheckPostParams(c, w)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if w.Name == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "名称不能为空!")
return
}
err = checkWorkspaceInfo(w.Id, w.Name)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, err.Error())
return
}
if w.Id == "" {
w.SetUserName(service.GetUserName(c))
_, err = w.Create(w)
} else {
w.SetModifyUserName(service.GetUserName(c))
_, err = w.Update(w, nil)
}
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, "")
}
func checkWorkspaceInfo(id, name string) error {
w := workspace.New()
fields := make(map[string]interface{}, 1)
fields["name"] = name
var notFields map[string]interface{}
if id != "" {
notFields = make(map[string]interface{}, 1)
notFields["id"] = id
}
result, _ := w.GetAll(w, fields, notFields)
if len(result.([]workspace.Workspace)) > 0 {
return err1.New("工作空间名称已存在")
}
return nil
}
func ListWorkSpaceGroup(c *gin.Context) {
w := &workspace.Workspace{}
result, _ := w.ListGroup()
controller.Success(c, result)
}
type WorkspacePageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
Id string `json:"id"`
Name string `json:"name"`
Group string `json:"group"`
}
func PageWorkspaces(c *gin.Context) {
var pageBody WorkspacePageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
w := workspace.New()
fields := map[string]interface{}{}
if pageBody.Id != "" {
fields["id"] = pageBody.Id
}
if pageBody.Name != "" {
fields["name"] = pageBody.Name
}
if pageBody.Group != "" {
fields["\"group\""] = pageBody.Group
}
page := &model.PageConfig{}
page.Covert(pageBody.PageInfo)
result, err := w.Page(w, page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
page.Data = result
controller.Success(c, page)
}
func ListAllWorkspaces(c *gin.Context) {
userObject, _ := c.Get(middleware.LoginUserKey)
userModel := userObject.(*user.UserObj)
list := base_service.GetUserWorkspaceList(userModel)
controller.Success(c, list)
//w := workspace.New()
//result, err := w.GetAll(w, nil, nil)
//if err != nil {
// controller.FailCode(c, errors.ServerError, err, "内部错误!")
// return
//}
//controller.Success(c, result)
}
// TreeNode represents a node in a tree structure.
type TreeNode struct {
Id string `json:"id"`
ParentId string `json:"parentId"`
Weight int `json:"weight"`
Name string `json:"name"`
WorkspaceBind int `json:"workspaceBind"`
Count int `json:"count"`
Children []*TreeNode `json:"children"`
}
// NewTreeNode creates a new TreeNode with given parameters.
func NewTreeNode(id, parent, name string, weight, workspaceBind, count int) *TreeNode {
treeNode := &TreeNode{
Id: id,
ParentId: parent,
Weight: weight,
Name: name,
WorkspaceBind: workspaceBind,
Count: count,
Children: nil,
}
return treeNode
}
// Tree is a placeholder for the Tree structure.
type Tree struct {
Name string `json:"name"`
Id string `json:"id"`
Children []*TreeNode `json:"children"`
}
// BuildSingle builds a tree from a list of nodes.
func BuildSingle(nodes []*TreeNode) *Tree {
// Implementation goes here.
return &Tree{
Name: "",
Id: "",
Children: nodes,
}
}
// PreCheckDelete 删除工作空间前检查
func PreCheckDelete(c *gin.Context) {
workspaceId := c.Query("id")
var nodes []*TreeNode
{
// 节点信息
cnt, err := node.CountNodeByWorkspaceId(workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "节点信息获取失败!")
return
}
treeNode := NewTreeNode("node", "", "节点信息", 0, 1, int(cnt))
nodes = append(nodes, treeNode)
}
{
// 工作空间环境变量
cnt, err := workspace_env.CountWorkspaceEnvVarByWorkspaceId(workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "工作空间环境变量获取失败!")
return
}
treeNode := NewTreeNode("workspace_env_var", "", "工作空间环境变量", 0, 2, int(cnt))
nodes = append(nodes, treeNode)
}
{
// 项目信息
cnt, err := project_info.CountProjectByWorkspaceId(workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "项目信息获取失败!")
return
}
treeNode := NewTreeNode("project_info", "", "项目信息", 0, 1, int(cnt))
nodes = append(nodes, treeNode)
}
{
// 节点脚本模版
// 节点脚本模版执行记录
cnt1, err := script_info.CountNodeScriptByWorkspaceId(workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "节点脚本模版获取失败!")
return
}
cnt2, err := script_info.CountNodeScriptExecuteLogByWorkspaceId(workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "节点脚本模版执行记录获取失败!")
return
}
treeNode := NewTreeNode("node_script_execute_log", "node_script_cache", "节点脚本模版执行记录", 0, 1, int(cnt2))
newTreeNode := NewTreeNode("node_script_cache", "", "节点脚本模版", 0, 1, int(cnt1))
newTreeNode.Children = append(newTreeNode.Children, treeNode)
nodes = append(nodes, newTreeNode)
}
{
// 节点分发
// 分发日志
cnt1, err := out_giving.CountOutGivingModelByWorkspaceId(workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "节点分发获取失败")
return
}
cnt2, err := out_giving.CountOutGivingLogByWorkspaceId(workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "分发日志获取失败")
return
}
treeNode := NewTreeNode("out_giving_log", "", "分发日志", 0, 3, int(cnt2))
newTreeNode := NewTreeNode("out_giving_model", "", "节点分发", 0, 1, int(cnt1))
newTreeNode.Children = append(newTreeNode.Children, treeNode)
nodes = append(nodes, newTreeNode)
}
{
// 脚本模版
cnt, err := script_info.CountScriptByWorkspaceId(workspaceId)
if err != nil {
controller.FailCode(c, errors.ServerError, nil, "脚本模版获取失败")
return
}
treeNode := NewTreeNode("script", "", "脚本模版", 0, 1, int(cnt))
nodes = append(nodes, treeNode)
}
stringTree := BuildSingle(nodes)
controller.Success(c, stringTree)
}
func DeleteWorkspace(c *gin.Context) {
workspaceId := c.Param("id")
if workspaceId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数workspaceId")
return
}
if workspaceId == assets.DefaultWorkspace {
controller.Fail(c, errors.InvalidParameter, "默认命名空间不允许删除!", "默认命名空间不允许删除!")
return
}
{
// 节点信息
cnt, _ := node.CountNodeByWorkspaceId(workspaceId)
if cnt != 0 {
controller.FailCode(c, errors.ServerError, nil, "当前工作空间下还存在关联数据:节点信息")
return
}
// 项目信息
cnt, _ = project_info.CountProjectByWorkspaceId(workspaceId)
if cnt != 0 {
controller.FailCode(c, errors.ServerError, nil, "当前工作空间下还存在关联数据:项目信息")
return
}
// 节点脚本模版
cnt, _ = script_info.CountNodeScriptByWorkspaceId(workspaceId)
if cnt != 0 {
controller.FailCode(c, errors.ServerError, nil, "当前工作空间下还存在关联数据:节点脚本模版")
return
}
// 节点脚本模版执行记录
cnt, _ = script_info.CountNodeScriptExecuteLogByWorkspaceId(workspaceId)
if cnt != 0 {
controller.FailCode(c, errors.ServerError, nil, "当前工作空间下还存在关联数据:节点脚本模版执行记录")
return
}
// 节点分发
cnt, _ = out_giving.CountOutGivingModelByWorkspaceId(workspaceId)
if cnt != 0 {
controller.FailCode(c, errors.ServerError, nil, "当前工作空间下还存在关联数据:节点分发")
return
}
// 脚本模版
cnt, _ = script_info.CountScriptByWorkspaceId(workspaceId)
if cnt != 0 {
controller.FailCode(c, errors.ServerError, nil, "当前工作空间下还存在关联数据:脚本模版")
return
}
}
// 判断用户绑定关系
allUsers, _ := user.NewUserModel().GetAll(nil, nil)
var users []string
for _, allUser := range allUsers {
contains := strings.Contains(allUser.Namespace, workspaceId)
if contains {
users = append(users, allUser.Displayname)
}
}
if len(users) > 0 {
controller.FailCode(c, errors.ServerError, nil, "当前工作空间下还存在关联数据:用户绑定关系,请先解除绑定关系!"+strings.Join(users, ","))
return
}
// 删除环境变量
workspace_env.DeleteWorkspaceEnvVarByWorkspaceId(workspaceId)
// 删除分发日志
out_giving.DeleteOutGivingLogByWorkspaceId(workspaceId)
w := &workspace.Workspace{}
w.Id = workspaceId
err := w.GetById(w)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
_, err = w.Delete(w)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "删除失败!")
return
}
controller.Success(c, "")
}

View File

@ -0,0 +1,275 @@
package system
import (
"bytes"
"encoding/json"
err1 "errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller/v1/app_manage"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/workspace_env"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"github.com/gin-gonic/gin"
"strings"
)
func EditWorkspaceEnv(c *gin.Context) {
w := &workspace_env.WorkspaceEnvVar{}
err := validator.CheckPostParams(c, w)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if w.Id == "" {
if w.Name == "" || w.Value == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
}
err = checkInfo(w.Name, w.WorkspaceId)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, err.Error())
return
}
w.SetUserName(service.GetUserName(c))
_, err = w.Create(w)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
if w.NodeIds == "" {
return
}
err = syncUpdateNodeEnvVar(w, strings.Split(w.NodeIds, ","))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
} else {
w1 := &workspace_env.WorkspaceEnvVar{}
w1.Id = w.Id
err = w1.GetById(w1)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据不存在!")
return
}
if w1.Privacy == 0 && w.Value == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
}
w1.Description = w.Description
if w.Value != "" {
w1.Value = w.Value
}
w1.Name = w.Name
oldNodeIds := w1.NodeIds
w1.NodeIds = w.NodeIds
w1.SetModifyUserName(service.GetUserName(c))
_, err = w.Update(w1, nil)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
if w.NodeIds != "" {
newNodeIdArray := strings.Split(w.NodeIds, ",")
if oldNodeIds != "" && oldNodeIds != w.NodeIds {
var delNodeIds []string
oldNodeIdArray := strings.Split(oldNodeIds, ",")
for _, oldId := range oldNodeIdArray {
find := false
for _, newId := range newNodeIdArray {
if oldId == newId {
find = true
break
}
}
if !find {
delNodeIds = append(delNodeIds, oldId)
}
}
if len(delNodeIds) > 0 {
err = syncDelNodeEnvVar(w1.Name, delNodeIds)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除节点变量异常!")
return
}
}
}
err = syncUpdateNodeEnvVar(w, strings.Split(w.NodeIds, ","))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "更新节点变量异常!")
return
}
} else {
if oldNodeIds != "" {
err = syncDelNodeEnvVar(w1.Name, strings.Split(oldNodeIds, ","))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除节点变量异常!")
return
}
}
}
}
controller.Success(c, "")
}
func syncUpdateNodeEnvVar(w *workspace_env.WorkspaceEnvVar, nodeIds []string) error {
if len(nodeIds) == 0 {
return nil
}
form := make(map[string]interface{}, 1)
form["name"] = w.Name
form["value"] = w.Value
form["description"] = w.Description
form["privacy"] = w.Privacy
jsonBody, _ := json.Marshal(form)
for _, nodeId := range nodeIds {
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
return err1.New("数据不存在")
}
resp, err := common.Request5(n, common.Workspace_EnvVar_Update, bytes.NewReader(jsonBody))
if err != nil {
return err
}
if !resp.IsBusinessSuccess() {
return err1.New(resp.Result.Msg)
}
}
return nil
}
func syncDelNodeEnvVar(name string, nodeIds []string) error {
if len(nodeIds) == 0 {
return nil
}
body := make(map[string]string, 1)
body["name"] = name
jsonBody, _ := json.Marshal(body)
for _, nodeId := range nodeIds {
n := &node.Node{}
n.Id = nodeId
err := n.GetById(n)
if err != nil {
return err1.New("数据不存在")
}
resp, err := common.Request5(n, common.Workspace_EnvVar_Delete, bytes.NewReader(jsonBody))
if err != nil {
return err
}
if !resp.IsBusinessSuccess() {
return err1.New(resp.Result.Msg)
}
}
return nil
}
func checkInfo(name, workspaceId string) error {
w := workspace_env.New()
fields := make(map[string]interface{}, 2)
fields["name"] = name
if workspaceId != "GLOBAL" {
fields["workspace_id"] = []string{workspaceId, "GLOBAL"}
}
result, _ := w.GetAll(w, fields, nil)
if len(result.([]workspace_env.WorkspaceEnvVar)) > 0 {
return err1.New("数据已存在")
}
return nil
}
func DeleteWorkspaceEnv(c *gin.Context) {
id := c.Query("id")
if id == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
w := &workspace_env.WorkspaceEnvVar{}
w.Id = id
err := w.GetById(w)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
if w.NodeIds != "" {
err = syncDelNodeEnvVar(w.Name, strings.Split(w.NodeIds, ","))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除节点变量异常!")
return
}
}
_, err = w.Delete(w)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, "")
}
type WorkspaceEnvPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
WorkspaceId string `json:"workspaceId"`
Name string `json:"name"`
Value string `json:"value"`
Description string `json:"description"`
}
func PageWorkspaceEnvs(c *gin.Context) {
var pageBody WorkspaceEnvPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if pageBody.WorkspaceId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
w := workspace_env.New()
fields := map[string]interface{}{}
workspaceId := app_manage.GetWorkspaceId(c)
if len(workspaceId) == 0 {
split := strings.Split(pageBody.WorkspaceId, ",")
fields["workspace_id"] = split
} else {
fields["workspace_id"] = []string{workspaceId, "GLOBAL"}
}
if pageBody.Name != "" {
fields["name"] = pageBody.Name
}
if pageBody.Value != "" {
fields["value"] = pageBody.Value
}
if pageBody.Description != "" {
fields["description"] = pageBody.Description
}
page := &model.PageConfig{}
page.Covert(pageBody.PageInfo)
tResult, err := w.Page(w, page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
result := tResult.([]workspace_env.WorkspaceEnvVar)
if result != nil && len(result) > 0 {
for i := range result {
if result[i].Privacy == 1 {
result[i].Value = ""
}
}
}
page.Data = result
controller.Success(c, page)
}

View File

@ -0,0 +1,126 @@
package app_manage
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/workspace"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"github.com/gin-gonic/gin"
"strings"
)
type UserPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
Id string `json:"id"`
Name string `json:"name"`
}
func PageUsers(c *gin.Context) {
var pageBody UserPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
um := user.NewUserModel()
fields := map[string]interface{}{}
if pageBody.Id != "" {
fields["user_id"] = pageBody.Id
}
if pageBody.Name != "" {
fields["user_name"] = pageBody.Name
}
page := &model.Page[user.UserModel]{}
page.Covert(pageBody.PageInfo)
err = um.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
func GetUserWorkSpace(c *gin.Context) {
userId := c.Query("userId")
if userId == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
um := &user.UserModel{}
um.UserID = userId
err := um.Get()
if err != nil {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
if um.AdminRole == 1 {
w := workspace.New()
result, err := w.GetAll(w, nil, nil)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, result)
return
}
namespace := um.Namespace
if namespace == "" {
controller.Success(c, "")
return
}
ns := strings.Split(namespace, ",")
if len(ns) == 0 {
controller.Success(c, "")
return
}
w := workspace.New()
fields := make(map[string]interface{}, 1)
fields["id"] = ns
result, err := w.GetAll(w, fields, nil)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, result)
}
type EditUserWorkSpaceBody struct {
UserId string `json:"userId"`
WorkspaceIds string `json:"workspaceIds"`
}
func EditUserWorkSpace(c *gin.Context) {
var body EditUserWorkSpaceBody
err := c.ShouldBindJSON(&body)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if body.UserId == "" {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
um := user.NewUserModel()
um.UserID = body.UserId
err = um.Get()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
um.Namespace = body.WorkspaceIds
err = um.Update([]string{"namespace"})
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, "")
}

View File

@ -0,0 +1,21 @@
package v1
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"github.com/gin-gonic/gin"
)
func HelloWorld(c *gin.Context) {
//str, ok := c.GetQuery("name")
//if !ok {
// str = "gin-layout"
//}
//err := service.EventHandler(0, "")
//if err != nil {
// controller.FailCode(c, errors.ServerError, err, "内部错误!")
// return
//}
controller.Success(c, "res")
}

View File

@ -0,0 +1,353 @@
package v1
import (
"encoding/base64"
"encoding/json"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/data"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/agent"
devc "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/device"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/message"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"github.com/gin-gonic/gin"
"strings"
"time"
)
// 每30s的健康检查接口
func CheckHeartBeat(c *gin.Context) {
device := &devc.DeviceInfo{}
err := validator.CheckPostParams(c, device)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
_, err = device.SaveHealth()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, "保存成功!")
}
// 设备基础信息上报
func PushDeviceInfo(c *gin.Context) {
device := &devc.DeviceInfo{}
err := validator.CheckPostParams(c, device)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
_, err = device.SaveBase()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, "保存成功!")
}
type PageBodyDevice struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
Name string `json:"name"`
IsNew string `json:"isNew"`
}
// 分页查询设备信息-关联资产属性
func PageDevices(c *gin.Context) {
var pageBody PageBodyDevice
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
device := &devc.AssetDeviceInfo{}
fields := map[string]interface{}{}
if pageBody.Name != "" {
fields["pr.name"] = pageBody.Name
}
if pageBody.IsNew != "" {
fields["is_new"] = pageBody.IsNew
}
page := &model.Page[devc.AssetDeviceInfo]{}
page.Covert(pageBody.PageInfo)
err = device.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
// 统计设备升级数统计
func CountDeviceUpgrade(c *gin.Context) {
fields := map[string]interface{}{}
device := &devc.DeviceInfo{}
result, err := device.CountUpgradeNum(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, result)
}
// 获取设备基本信息
func GetDeviceInfo(c *gin.Context) {
device := &devc.AssetDeviceInfo{}
serialNo := c.Param("serialNo")
fields := map[string]interface{}{}
fields["serial_no"] = serialNo
asset, err := device.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, asset)
}
// 获取设备的组件列表信息
func ListDeviceConfigs(c *gin.Context) {
serialNo := c.Param("serialNo")
device := &devc.AssetDeviceInfo{}
fields := map[string]interface{}{}
fields["serial_no"] = serialNo
_, err := device.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "未找到设备!")
return
}
sub := strings.ReplaceAll(message.TO_AGENT_UNICAST_SUBJECT, "{rid}", serialNo)
dat := message.MsgModel{
Body: nil,
Func: message.FUNC_COMPONENTINFO,
Rid: serialNo,
Version: "v1",
}
payload, _ := json.Marshal(dat)
msg, err := data.Nc.Request(sub, payload, 6*time.Second)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
res, err := message.UnmarshalMsgRespModel(msg.Data)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "无法解析的消息格式!")
return
}
if res.Code != 200 {
controller.FailCode(c, errors.ServerError, err, "小助手消息错误!")
return
}
deStr, err := base64.StdEncoding.DecodeString(fmt.Sprint(res.Data))
result := make([]map[string]interface{}, 0)
json.Unmarshal(deStr, &result)
controller.Success(c, result)
}
// 创建设备升级记录
func CreateDeviceUpgradeRecord(c *gin.Context) {
record := &devc.DeviceUpgradeRecord{}
err := validator.CheckPostParams(c, record)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
_, err = record.Create()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, "")
}
type PageBodyRecord struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
}
// 获取设备升级记录
func PageDeviceUpgradeRecord(c *gin.Context) {
var pageBody PageBodyRecord
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
serialNo := c.Param("serialNo")
fields := map[string]interface{}{}
fields["serial_no"] = serialNo
record := &devc.DeviceUpgradeRecord{}
page := &model.Page[devc.UpgradeRecord]{}
page.Covert(pageBody.PageInfo)
err = record.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
// 创建设备升级规则
func CreateDeviceUpgradeRule(c *gin.Context) {
rule := &devc.DeviceUpgradeRule{}
err := validator.CheckPostParams(c, rule)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
//企业+一体机类型唯一
fields := map[string]interface{}{}
fields["vendor_id"] = rule.VendorId
rules, err := rule.ListAll(fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
if len(rules) > 0 && rule.DeviceType == "0" {
controller.FailCode(c, errors.InvalidParameter, err, "与已有公司规则冲突!")
return
}
for _, r := range rules {
if r.DeviceType == rule.DeviceType || r.DeviceType == "0" {
controller.FailCode(c, errors.InvalidParameter, err, "与已有公司规则冲突!")
return
}
}
num, err := rule.Create()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "创建失败!")
return
}
if num > 0 {
go func(ru devc.DeviceUpgradeRule) {
rules := make([]devc.DeviceUpgradeRule, 0)
rules = append(rules, ru)
if err == nil && len(rules) > 0 {
_ = service.SendUpgradePlans(rules, "create")
}
}(*rule)
}
controller.Success(c, "保存成功!")
}
type PageRule struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
VendorId string `json:"vendorId"`
}
// 获取设备升级规则
func PageDeviceUpgradeRule(c *gin.Context) {
var pageBody PageRule
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
if pageBody.VendorId != "" {
fields["vendor_id"] = pageBody.VendorId
}
rule := &devc.DeviceUpgradeRule{}
page := &model.Page[devc.DeviceUpgradeRule]{}
page.Covert(pageBody.PageInfo)
err = rule.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
type DeleteUpgradeRuleBody struct {
Ids []int64 `json:"ids" binding:"required"`
}
// 删除设备升级规则
func DeleteDeviceUpgradeRule(c *gin.Context) {
rule := &devc.DeviceUpgradeRule{}
var deleteBody DeleteUpgradeRuleBody
err := c.ShouldBindJSON(&deleteBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
rules, err := rule.GetBatch(deleteBody.Ids)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
var num int64
if len(rules) > 0 {
num, err = rule.DeleteBatch(deleteBody.Ids)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "删除失败!")
return
}
}
if num > 0 {
go func(rules []devc.DeviceUpgradeRule) {
_ = service.SendUpgradePlans(rules, "delete")
}(rules)
}
controller.Success(c, "删除成功!")
}
// 触发升级命令
func DoDeviceUpgrade(c *gin.Context) {
serialNo := c.Param("serialNo")
device := &devc.AssetDeviceInfo{}
fields := map[string]interface{}{}
fields["serial_no"] = serialNo
d, err := device.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "未找到设备!")
return
}
aget := &agent.AgentInfo{}
agents, err := aget.ListLastVersion()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "未查询到最新版本!")
return
}
for _, a := range agents {
if strings.ToLower(a.OsType) == strings.ToLower(d.OsType) && strings.ToLower(a.ArchType) == strings.ToLower(d.ArchType) {
aget = &a
break
}
}
plan, err := service.GetUpgradeCommandNow(aget)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "未找到小助手包!")
return
}
body, _ := json.Marshal(plan)
dat := message.MsgModel{
Body: body,
Func: message.FUNC_UPGRADEIMMEDIATELY,
Rid: serialNo,
Version: "v1",
}
payload, _ := json.Marshal(dat)
sub := strings.ReplaceAll(message.TO_AGENT_UNICAST_SUBJECT, "{rid}", serialNo)
msg, err := data.Nc.Request(sub, payload, 6*time.Second)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误,下发通知失败!")
return
}
res, err := message.UnmarshalMsgRespModel(msg.Data)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "消息格式错误,下发通知失败!")
return
}
if res.Code != 200 {
controller.FailCode(c, errors.ServerError, err, "下发通知失败!")
return
}
controller.Success(c, "已通知,自动升级!")
}

View File

@ -0,0 +1,124 @@
package v1
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/event"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/event_engine"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"github.com/gin-gonic/gin"
)
func CreateEvent(c *gin.Context) {
event := &event.Event{}
err := validator.CheckPostParams(c, event)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
_, err = event.Create()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, "")
}
func EditEvent(c *gin.Context) {
event := &event.Event{}
err := validator.CheckPostParams(c, event)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
fields["id"] = event.Id
_, err = event.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
var updateColumns []string
_, err = event.Update(updateColumns)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, "")
}
func GetEventById(c *gin.Context) {
event := &event.Event{}
eventId := c.Param("eventId")
fields := map[string]interface{}{}
fields["id"] = eventId
mRule, err := event.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, mRule)
}
func GetEventByName(c *gin.Context) {
event := &event.Event{}
eventName := c.Param("eventName")
fields := map[string]interface{}{}
fields["event_name"] = eventName
result, err := event.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, result)
}
type EventPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
EventName string `json:"eventName"`
}
func PageEvents(c *gin.Context) {
var pageBody EventPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
e := &event.Event{}
fields := map[string]interface{}{}
fields["event_name"] = pageBody.EventName
page := &model.Page[event.Event]{}
page.Covert(pageBody.PageInfo)
err = e.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
func DeleteEvent(c *gin.Context) {
event := &event.Event{}
eventId := c.Param("eventId")
fields := map[string]interface{}{}
fields["id"] = eventId
result, err := event.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "事件不存在!")
return
}
result.Delete()
controller.Success(c, "")
}
func SyncEventFromThreshold(c *gin.Context) {
rowsAffected, err := event_engine.BatchSaveEventFromThreshold()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "同步门限定义至事件定义失败!")
return
}
controller.Success(c, rowsAffected)
}

View File

@ -0,0 +1,166 @@
package v1
import (
"encoding/csv"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/event"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"github.com/gin-gonic/gin"
"time"
)
func GetEventRecordById(c *gin.Context) {
er := &event.EventRecordStruct{}
eventId := c.Param("eventId")
mRule, err := er.GetById(eventId)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, mRule)
}
type EventRecordCondition struct {
EventName string `json:"eventName"`
ResourceName string `json:"resourceName"`
Status string `json:"status"`
}
type EventRecordPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
EventRecordCondition
}
func PageEventRecords(c *gin.Context) {
var pageBody EventRecordPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
er := &event.EventRecordStruct{}
fields := map[string]interface{}{}
if pageBody.EventName != "" {
fields["event_name"] = pageBody.EventName
}
if pageBody.ResourceName != "" {
fields["resource_name"] = pageBody.ResourceName
}
if pageBody.Status != "" {
fields["task_status"] = pageBody.Status
}
page := &model.Page[event.EventRecordStruct]{}
page.Covert(pageBody.PageInfo)
err = er.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
func ExportEventRecords(c *gin.Context) {
var condition EventRecordCondition
err := c.ShouldBindJSON(&condition)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
er := &event.EventRecordStruct{}
fields := map[string]interface{}{}
if condition.EventName != "" {
fields["event_name"] = condition.EventName
}
if condition.ResourceName != "" {
fields["resource_name"] = condition.ResourceName
}
if condition.Status != "" {
fields["task_status"] = condition.Status
}
count, err := er.Count(fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务器错误!")
return
}
if count > 1000 {
controller.FailCode(c, errors.InvalidParameter, nil, "数据量过大,请调整查询范围!")
return
}
events, err := er.GetEvents(fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务器错误!")
return
}
c.Writer.Header().Set("Content-Disposition", "attachment; filename=events.xlsx")
c.Writer.Header().Set("Content-Type", "application/octet-stream")
w := csv.NewWriter(c.Writer)
c.Writer.Write([]byte("\xEF\xBB\xBF"))
w.Write([]string{"事件等级", "事件编号", "事件名称", "资源名称", "资源类型", "所在算力节点", "发生时间", "事件内容", "处理状态"})
for _, record := range events {
w.Write([]string{GetLevel(record.Level), record.Id, record.EventName, record.ResourceName, record.ResourceType, record.ResourceNode, GetTimeStr(record.EventTime), record.EventInfo, GetStatus(record.TaskStatus)})
}
w.Flush()
c.Writer.Flush()
}
func GetLevel(level string) string {
if level == "WARN" {
return "警告"
} else if level == "CRITICAL" {
return "严重"
} else {
return "信息"
}
}
func GetStatus(status string) string {
if status == "0" {
return "初始化"
} else if status == "1" {
return "成功"
} else if status == "2" {
return "失败"
} else if status == "3" {
return "执行中"
} else if status == "4" {
return "触发失败"
} else {
return "未处理"
}
}
func GetTimeStr(t time.Time) string {
return t.Local().Format("2006-01-02 15:04:05")
}
func EventRecordsStatistic(c *gin.Context) {
var condition EventRecordCondition
err := c.ShouldBindJSON(&condition)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
if condition.EventName != "" {
fields["event_name"] = condition.EventName
}
if condition.ResourceName != "" {
fields["resource_name"] = condition.ResourceName
}
if condition.Status != "" {
fields["task_status"] = condition.Status
}
er := &event.EventRecordStruct{}
statistic, err := er.Statistic(fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务器错误!")
return
}
controller.Success(c, statistic)
}

View File

@ -0,0 +1,176 @@
package v1
import (
"encoding/csv"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/event"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"github.com/gin-gonic/gin"
"time"
)
func GetHistoryEventRecordById(c *gin.Context) {
her := &event.HistoryEventRecordStruct{}
eventId := c.Param("eventId")
mRule, err := her.GetById(eventId)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, mRule)
}
type HistoryEventRecordCondition struct {
EventName string `json:"eventName"`
Level string `json:"level"`
ResourceName string `json:"resourceName"`
ResourceType string `json:"resourceType"`
ResourceNode string `json:"ResourceNode"`
StartTime *time.Time `json:"StartTime"`
EndTime *time.Time `json:"endTime"`
}
type HistoryEventRecordPageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
HistoryEventRecordCondition
}
func PageHistoryEventRecords(c *gin.Context) {
var pageBody HistoryEventRecordPageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
her := &event.HistoryEventRecordStruct{}
fields := map[string]interface{}{}
if pageBody.EventName != "" {
fields["event_name"] = pageBody.EventName
}
if pageBody.Level != "" {
fields["level"] = pageBody.Level
}
if pageBody.ResourceName != "" {
fields["resource_name"] = pageBody.ResourceName
}
if pageBody.ResourceType != "" {
fields["resource_type"] = pageBody.ResourceType
}
if pageBody.ResourceNode != "" {
fields["resource_node"] = pageBody.ResourceNode
}
if pageBody.StartTime != nil {
fields["start_time"] = pageBody.StartTime
}
if pageBody.EndTime != nil {
fields["end_time"] = pageBody.EndTime
}
page := &model.Page[event.HistoryEventRecordStruct]{}
page.Covert(pageBody.PageInfo)
err = her.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
func ExportHistoryEventRecords(c *gin.Context) {
var condition HistoryEventRecordCondition
err := c.ShouldBindJSON(&condition)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
if condition.EventName != "" {
fields["event_name"] = condition.EventName
}
if condition.Level != "" {
fields["level"] = condition.Level
}
if condition.ResourceName != "" {
fields["resource_name"] = condition.ResourceName
}
if condition.ResourceType != "" {
fields["resource_type"] = condition.ResourceType
}
if condition.ResourceNode != "" {
fields["resource_node"] = condition.ResourceNode
}
if condition.StartTime != nil {
fields["start_time"] = condition.StartTime
}
if condition.EndTime != nil {
fields["end_time"] = condition.EndTime
}
her := &event.HistoryEventRecordStruct{}
count, err := her.Count(fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务器错误!")
return
}
if count > 1000 {
controller.FailCode(c, errors.InvalidParameter, nil, "数据量过大,请调整查询范围!")
return
}
events, err := her.GetEvents(fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务器错误!")
return
}
c.Writer.Header().Set("Content-Disposition", "attachment; filename=history_events.xlsx")
c.Writer.Header().Set("Content-Type", "application/octet-stream")
w := csv.NewWriter(c.Writer)
c.Writer.Write([]byte("\xEF\xBB\xBF"))
w.Write([]string{"事件等级", "事件编号", "事件名称", "资源名称", "资源类型", "所在算力节点", "发生时间", "事件内容", "处理状态"})
for _, record := range events {
w.Write([]string{GetLevel(record.Level), record.Id, record.EventName, record.ResourceName, record.ResourceType, record.ResourceNode, GetTimeStr(record.EventTime), record.EventInfo, GetStatus(record.TaskStatus)})
}
w.Flush()
c.Writer.Flush()
}
func HistoryEventRecordsStatistic(c *gin.Context) {
var condition HistoryEventRecordCondition
err := c.ShouldBindJSON(&condition)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
if condition.EventName != "" {
fields["event_name"] = condition.EventName
}
if condition.Level != "" {
fields["level"] = condition.Level
}
if condition.ResourceName != "" {
fields["resource_name"] = condition.ResourceName
}
if condition.ResourceType != "" {
fields["resource_type"] = condition.ResourceType
}
if condition.ResourceNode != "" {
fields["resource_node"] = condition.ResourceNode
}
if condition.StartTime != nil {
fields["start_time"] = condition.StartTime
}
if condition.EndTime != nil {
fields["end_time"] = condition.EndTime
}
her := &event.HistoryEventRecordStruct{}
statistic, err := her.Statistic(fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务器错误!")
return
}
controller.Success(c, statistic)
}

View File

@ -0,0 +1,275 @@
package v1
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/middleware"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/auth"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/gin-gonic/gin"
"github.com/golang/glog"
"gorm.io/gorm"
"io"
"net/http"
"time"
)
const AUTH_HEADER = "Authorization"
type UserInfo struct {
*user.UserObj
Token string `json:"token"`
}
func LoginInfo(c *gin.Context) {
u, exists := c.Get(middleware.LoginUserKey)
if !exists {
controller.FailCode(c, errors.AuthorizationError, nil)
return
}
userObj, ok := u.(*user.UserObj)
if !ok {
controller.FailCode(c, errors.AuthorizationError, nil)
return
}
if !config.Config.Auth.Enable {
controller.FailCode(c, errors.ServerError, nil, "login is disabled!")
return
}
url := config.Config.Auth.Url
if url == "" {
controller.FailCode(c, errors.ServerError, nil, "login config is not set!")
return
}
token := c.Request.Header.Get(AUTH_HEADER)
var client *http.Client
var request *http.Request
var resp *http.Response
client = &http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}}
request, err := http.NewRequest("GET", url+config.Config.Auth.LoginInfo, nil)
if err != nil {
glog.Errorln("failed to create request", err)
controller.FailCode(c, errors.ServerError, err, "failed to create request!")
return
}
request.Header.Add(AUTH_HEADER, token)
resp, err = client.Do(request)
if err != nil {
glog.Errorln("failed to request", err)
controller.FailCode(c, errors.ServerError, err, "failed to request!")
return
}
defer resp.Body.Close()
defer client.CloseIdleConnections()
if resp.StatusCode != 200 {
glog.Errorln("response error")
controller.FailCode(c, errors.ServerError, nil, "response error!")
return
}
var respBody map[string]interface{}
respStr, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(respStr, &respBody)
if err != nil {
glog.Errorln("failed to decode response", err)
controller.FailCode(c, errors.ServerError, err, "failed to decode response!")
return
}
data := respBody["data"]
if data == nil {
meta := respBody["meta"]
controller.FailCode(c, errors.ServerError, nil, meta.(map[string]interface{})["message"].(string))
return
}
userMap := data.(map[string]interface{})["user"]
mobile := userMap.(map[string]interface{})["mobile"]
if mobile != nil {
userObj.Mobile = mobile.(string)
}
provinceId := userMap.(map[string]interface{})["provinceId"]
if provinceId != nil {
userObj.ProvinceId = provinceId.(string)
}
userObj.Status = userMap.(map[string]interface{})["status"].(string)
userinfo := UserInfo{
UserObj: userObj,
}
controller.Success(c, userinfo)
}
type LoginBody struct {
AppCode string `json:"appCode"`
Password string `json:"password"`
SingleLogin bool `json:"singleLogin"`
TextCode string `json:"textCode"`
UserName string `json:"username"`
Uuid string `json:"uuid"`
}
func Login(c *gin.Context) {
if !config.Config.Auth.Enable {
controller.FailCode(c, errors.ServerError, nil, "login is disabled!")
return
}
url := config.Config.Auth.Url
if url == "" {
controller.FailCode(c, errors.ServerError, nil, "login config is not set!")
return
}
loginBody := LoginBody{}
err := validator.CheckPostParams(c, &loginBody)
if err != nil || (loginBody.AppCode == "" || loginBody.Password == "" || loginBody.TextCode == "" ||
loginBody.UserName == "" || loginBody.Uuid == "") {
controller.FailCode(c, errors.ServerError, err, "请检查参数!")
return
}
client := &http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}}
authPath := config.Config.Auth.LoginWithCode
reqBody, err := json.Marshal(loginBody)
resp, err := client.Post(url+authPath, "application/json", bytes.NewReader(reqBody))
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
defer resp.Body.Close()
defer client.CloseIdleConnections()
var respBody map[string]interface{}
respBytes, _ := io.ReadAll(resp.Body)
log.Info("auth response:" + string(respBytes))
if resp.StatusCode != 200 {
glog.Errorln("response error:" + string(respBytes))
controller.FailCode(c, errors.ServerError, nil, "服务异常!")
return
}
err = json.Unmarshal(respBytes, &respBody)
if err != nil {
glog.Errorln("failed to decode response", err.Error())
controller.FailCode(c, errors.ServerError, nil, "服务异常!")
return
}
data := respBody["data"]
if data == nil {
meta := respBody["meta"]
controller.FailCode(c, errors.ServerError, nil, meta.(map[string]interface{})["message"].(string))
return
}
accountMap := data.(map[string]interface{})["account"]
userName := accountMap.(map[string]interface{})["loginName"].(string)
currentUser := user.NewUserModel()
err = currentUser.GetByUserName(userName)
if err != nil {
if err == gorm.ErrRecordNotFound {
glog.Warningln("User does not exist in database registry")
currentUser = nil
} else {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
}
if currentUser == nil {
glog.Infoln("Moving user to database, user: ", loginBody.UserName)
spec := user.UserSpec{}
spec.UserID = accountMap.(map[string]interface{})["accountId"].(string)
spec.UserName = userName
userMap := data.(map[string]interface{})["user"]
displayName := userMap.(map[string]interface{})["cn"]
if displayName != nil {
spec.DisplayName = displayName.(string)
} else {
spec.DisplayName = userName
}
if userName == "admin" {
spec.Role = 1
} else {
spec.Role = 0
}
if config.Config.CfnConfig.Enable {
spec.Namespace = fmt.Sprintf("DEFAULT,%s", config.Config.CfnConfig.CfnWorkSpaceId)
} else {
spec.Namespace = "DEFAULT"
}
_, err = user.CreateUser(spec)
if err != nil {
glog.Errorln("failed to create user, err: " + err.Error())
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
}
detail, err := user.GetUserByUsername(userName)
if err != nil || detail == nil {
glog.Errorln("failed to get user, err: " + err.Error())
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
userMap := data.(map[string]interface{})["user"]
mobile := userMap.(map[string]interface{})["mobile"]
if mobile != nil {
detail.Mobile = mobile.(string)
}
provinceId := userMap.(map[string]interface{})["provinceId"]
if provinceId != nil {
detail.ProvinceId = provinceId.(string)
}
detail.Status = userMap.(map[string]interface{})["status"].(string)
tokenMap := data.(map[string]interface{})["token"]
authToken := tokenMap.(map[string]interface{})["accessToken"].(string)
go func() {
userModel := user.NewUserModel()
userModel.UserID = detail.UserID
userModel.LastLoginTime = time.Now()
userModel.LoginFrequency = detail.LoginFrequency + 1
_, err = userModel.UpdateLastLoginTime()
if err != nil {
glog.Errorln("Failed to update last_login_time", err)
}
}()
token, err := auth.Authorize(authToken, detail)
if err != nil || token == "" {
glog.Errorln("Failed to get token", err)
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
userinfo := UserInfo{
UserObj: detail,
Token: token,
}
controller.Success(c, userinfo)
}

View File

@ -0,0 +1,41 @@
package v1
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
devc "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/device"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/message"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"github.com/gin-gonic/gin"
"github.com/golang/glog"
)
// 获取设备运行日志
func GetDeviceLogs(c *gin.Context) {
device := &devc.AssetDeviceInfo{}
serialNo := c.Param("serialNo")
comType := c.Param("comType")
fields := map[string]interface{}{}
fields["serial_no"] = serialNo
_, err := device.Get(fields)
if err != nil {
glog.Error(err)
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
loglines := []string{}
switch comType {
case "agent":
loglines, err = service.GetDeviceLogs(message.FUNC_AGENTLOG, serialNo, 100)
break
case "syslog":
break
case "telegraf":
loglines, err = service.GetDeviceLogs(message.FUNC_TELEGRAFLOG, serialNo, 100)
break
}
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
}
controller.Success(c, loglines)
}

View File

@ -0,0 +1,467 @@
package v1
import (
"context"
"encoding/json"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/data"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/device"
mc "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/metric"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/gin-gonic/gin"
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
"github.com/prometheus/common/model"
"strings"
"time"
)
type QueryBody struct {
Rids string `json:"rids"`
OrgId string `json:"orgId"`
AssetType string `json:"assetType"`
RuleNos string `json:"ruleNo"`
Start string `json:"start"`
End string `json:"end"`
Step time.Duration `json:"step"`
}
type Metric struct {
RuleNo string `json:"ruleNo"`
RuleName string `json:"ruleName"`
Data interface{} `json:"data"`
}
type Pair struct {
Time model.Time `json:"time"`
Value interface{} `json:"value"`
}
func QueryMetric(c *gin.Context) {
body := request.GetBody(c)
var b QueryBody
err := json.Unmarshal(body, &b)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数(body)是否符合要求!")
return
}
var end time.Time
if b.End != "" {
end, err = time.Parse(time.RFC3339, b.End)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "invalid end")
return
}
} else {
end = time.Now()
}
var start time.Time
if b.Start != "" {
start, err = time.Parse(time.RFC3339, b.Start)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "invalid start")
return
}
} else if b.End == "" {
start = end
}
if end.Before(start) {
controller.FailCode(c, errors.InvalidParameter, err, "invalid start and end")
return
}
if b.Step == 0 {
duration := end.Sub(start)
durationSeconds := duration.Seconds()
b.Step = time.Duration(durationSeconds/6) * time.Second
if b.Step == 0 {
b.Step = time.Second
}
}
ruleNos := strings.Split(b.RuleNos, ",")
if len(ruleNos) == 0 {
controller.Success(c, nil)
return
}
rids := strings.Split(b.Rids, ",")
if len(rids) == 0 {
controller.Success(c, nil)
return
}
if data.V1api == nil {
log.Info("prometheus client is nil")
controller.Success(c, nil)
return
}
device := &device.DeviceInfo{}
devices, err := device.GetBySerialNos(rids)
if len(devices) == 0 {
controller.FailCode(c, errors.InvalidParameter, err, "rids are not exist")
return
}
osType := make(map[string]string, len(devices))
for _, info := range devices {
osType[info.SerialNo] = info.OsType
}
rule := mc.MetricsRule{}
rules, err := rule.GetByRuleNo(ruleNos)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
if len(rules) == 0 {
controller.Success(c, nil)
return
}
result := make(map[string][]Metric, len(rids))
for _, rid := range rids {
var isWindows bool
if osType[rid] == "" || strings.ToLower(osType[rid]) == "windows" {
isWindows = true
} else {
isWindows = false
}
result[rid] = []Metric{}
for _, metricsRule := range rules {
var ruleQl string
if isWindows {
ruleQl = metricsRule.RuleQlWindows
} else {
ruleQl = metricsRule.RuleQl
}
promQl := strings.ReplaceAll(ruleQl, "$rid", rid)
queryRange, _, err := data.V1api.QueryRange(context.TODO(), promQl, v1.Range{
Start: start,
End: end,
Step: b.Step,
})
if err != nil {
log.Info("failed to query range, err: ", err.Error())
continue
}
var data interface{}
resultType := queryRange.Type()
switch resultType {
case model.ValScalar:
scalar := queryRange.(*model.Scalar)
data = []Pair{
{
scalar.Timestamp,
scalar.Value,
},
}
case model.ValMatrix:
matrix := queryRange.(model.Matrix)
size := len(matrix)
if size > 1 {
temp := make(map[string][]Pair)
for _, stream := range matrix {
var pairs []Pair
for _, value := range stream.Values {
pairs = append(pairs, Pair{
value.Timestamp,
value.Value,
})
}
key, _ := json.Marshal(stream.Metric)
temp[string(key)] = pairs
}
data = temp
} else if size > 0 {
var pairs []Pair
values := matrix[0].Values
for i := range values {
pairs = append(pairs, Pair{
values[i].Timestamp,
values[i].Value,
})
}
data = pairs
}
case model.ValString:
str := queryRange.(*model.String)
data = []Pair{
{
str.Timestamp,
str.Value,
},
}
case model.ValVector:
vector := queryRange.(model.Vector)
size := len(vector)
if size > 1 {
temp := make(map[string][]Pair)
for _, sample := range vector {
key, _ := json.Marshal(sample.Metric.String())
temp[string(key)] = []Pair{{
sample.Timestamp,
sample.Value},
}
}
data = temp
} else if size > 0 {
data = []Pair{
{
vector[0].Timestamp,
vector[0].Value,
},
}
}
default:
continue
}
result[rid] = append(result[rid], Metric{
metricsRule.RuleNo,
metricsRule.RuleName,
data,
})
}
}
controller.Success(c, result)
}
type ProcessMetricQueryBody struct {
Rid string `json:"rid"`
PName string `json:"pName"`
Start string `json:"start"`
End string `json:"end"`
Step time.Duration `json:"step"`
}
func QueryProcessMetric(c *gin.Context) {
body := request.GetBody(c)
var b ProcessMetricQueryBody
err := json.Unmarshal(body, &b)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数(body)是否符合要求!")
return
}
if b.Rid == "" || b.PName == "" {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数(body)是否符合要求!")
}
var end time.Time
if b.End != "" {
end, err = time.Parse(time.RFC3339, b.End)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "invalid end")
return
}
} else {
end = time.Now()
}
var start time.Time
if b.Start != "" {
start, err = time.Parse(time.RFC3339, b.Start)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "invalid start")
return
}
} else if b.End == "" {
start = end
}
if end.Before(start) {
controller.FailCode(c, errors.InvalidParameter, err, "invalid start and end")
return
}
if b.Step == 0 {
duration := end.Sub(start)
durationSeconds := duration.Seconds()
b.Step = time.Duration(durationSeconds/6) * time.Second
if b.Step == 0 {
b.Step = time.Second
}
}
d := &device.DeviceInfo{}
r, err := d.GetBySerialNo(b.Rid)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "rid are not exist")
return
}
isWindows := strings.ToLower(r.OsType) == "windows"
promQLs := make(map[string]string)
if isWindows {
promQLs["read"] = fmt.Sprintf(
`rate(windows_process_io_bytes_total{rid="%s", process="%s", mode="read"}[5m])`, b.Rid, b.PName)
promQLs["write"] = fmt.Sprintf(
`rate(windows_process_io_bytes_total{rid="%s", process="%s", mode="write"}[5m])`, b.Rid, b.PName)
promQLs["cpu"] = fmt.Sprintf(
`sum(rate(windows_process_cpu_time_total{rid="%s", process="%s"}[5m]))`, b.Rid, b.PName)
promQLs["memory"] = fmt.Sprintf(
`sum(windows_process_page_file_bytes{rid="%s", process="%s"})`, b.Rid, b.PName)
} else {
promQLs["read"] = fmt.Sprintf(
`rate(namedprocess_namegroup_read_bytes_total{rid="%s",groupname="%s"}[5m])`, b.Rid, b.PName)
promQLs["write"] = fmt.Sprintf(
`rate(namedprocess_namegroup_write_bytes_total{rid="%s",groupname="%s"}[5m])`, b.Rid, b.PName)
promQLs["cpu"] = fmt.Sprintf(
`sum(rate(namedprocess_namegroup_cpu_seconds_total{rid="%s",groupname="%s"}[5m]))`, b.Rid, b.PName)
promQLs["memory"] = fmt.Sprintf(
`sum(namedprocess_namegroup_memory_bytes{rid="%s",groupname="%s"})`, b.Rid, b.PName)
}
result := make(map[string][]Pair)
for key, promQL := range promQLs {
queryRange, _, err := data.V1api.QueryRange(context.TODO(), promQL, v1.Range{
Start: start,
End: end,
Step: b.Step,
})
if err != nil {
log.Info("failed to query range, err: ", err.Error())
continue
}
var pairs []Pair
resultType := queryRange.Type()
switch resultType {
case model.ValScalar:
scalar := queryRange.(*model.Scalar)
pairs = append(pairs, Pair{
scalar.Timestamp,
scalar.Value,
})
case model.ValMatrix:
matrix := queryRange.(model.Matrix)
if len(matrix) == 0 {
continue
}
values := matrix[0].Values
for i := range values {
pairs = append(pairs, Pair{
values[i].Timestamp,
values[i].Value,
})
}
case model.ValString:
str := queryRange.(*model.String)
pairs = append(pairs, Pair{
str.Timestamp,
str.Value,
})
case model.ValVector:
vector := queryRange.(model.Vector)
for i := range vector {
pairs = append(pairs, Pair{
vector[i].Timestamp,
vector[i].Value,
})
}
default:
continue
}
result[key] = pairs
}
controller.Success(c, result)
}
func QueryProcessListMetric(c *gin.Context) {
rid := c.Param("rid")
d := &device.DeviceInfo{}
r, err := d.GetBySerialNo(rid)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "rid are not exist")
return
}
isWindows := strings.ToLower(r.OsType) == "windows"
promQLs := make(map[string]string)
if isWindows {
promQLs["pName"] = fmt.Sprintf(
`sum(windows_process_start_time{rid="%s"}) by (process)`, rid)
promQLs["state"] = fmt.Sprintf(
`sum(windows_process_threads{rid="%s"}) by (process) > 0`, rid)
promQLs["cpu"] = fmt.Sprintf(
`sum(rate(windows_process_cpu_time_total{rid="%s"}[5m])) by (process)`, rid)
promQLs["memory"] = fmt.Sprintf(
`sum(windows_process_page_file_bytes{rid="%s"}) by (process)`, rid)
promQLs["diskIO"] = fmt.Sprintf(
`sum(rate(windows_process_io_bytes_total{rid="%s", mode="read"}[5m])) by (process)`, rid)
} else {
promQLs["pName"] = fmt.Sprintf(
`namedprocess_namegroup_num_procs{rid="%s"} == 1`, rid)
promQLs["state"] = fmt.Sprintf(
`sum(namedprocess_namegroup_states{rid="%s", state=~"Other|Zombie"}) by (groupname) == 0`, rid)
promQLs["cpu"] = fmt.Sprintf(
`sum(rate(namedprocess_namegroup_cpu_seconds_total{rid="%s"}[5m])) by (groupname)`, rid)
promQLs["memory"] = fmt.Sprintf(
`sum(namedprocess_namegroup_memory_bytes{rid="%s"}) by (groupname)`, rid)
promQLs["diskIO"] = fmt.Sprintf(
`rate(namedprocess_namegroup_read_bytes_total{rid="%s"}[5m]) + rate(namedprocess_namegroup_write_bytes_total{rid="%s"}[5m])`, rid, rid)
}
temp := make(map[string][]*model.Sample)
time := time.Now()
for key, promQL := range promQLs {
queryRange, _, err := data.V1api.Query(context.TODO(), promQL, time)
if err != nil {
log.Info("failed to query range, err: ", err.Error())
continue
}
if queryRange.Type() == model.ValVector {
temp[key] = queryRange.(model.Vector)
}
}
samples := temp["pName"]
result := make(map[string]map[string]interface{}, len(samples))
var processName model.LabelName
if isWindows {
processName = "process"
} else {
processName = "groupname"
}
for _, sample := range samples {
result[string(sample.Metric[processName])] = make(map[string]interface{}, 3)
}
delete(temp, "pName")
samples = temp["state"]
for _, sample := range samples {
pName := string(sample.Metric[processName])
if _, ok := result[pName]; ok {
result[pName]["status"] = true
}
}
delete(temp, "state")
for key, value := range temp {
for _, sample := range value {
pName := string(sample.Metric[processName])
if _, ok := result[pName]; !ok {
continue
}
result[pName][key] = sample.Value
}
}
controller.Success(c, result)
}

View File

@ -0,0 +1,176 @@
package v1
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
mc "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/metric"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"github.com/gin-gonic/gin"
)
// 创建指标规则
func CreateMetricRule(c *gin.Context) {
rule := &mc.MetricsRule{}
err := validator.CheckPostParams(c, rule)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
queryNo := map[string]interface{}{
"rule_no": rule.RuleNo,
}
r, _ := rule.Get(queryNo)
if r.RuleNo != "" {
controller.FailCode(c, errors.InvalidParameter, err, "编号重复!")
return
}
if rule.RuleKey != "" {
queryKey := map[string]interface{}{
"rule_key": rule.RuleKey,
}
rr, _ := rule.Get(queryKey)
if rr.RuleKey != "" {
controller.FailCode(c, errors.InvalidParameter, err, "KEY值重复")
return
}
}
_, err = rule.Create()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, "")
}
// 编辑指标规则
func EditMetricRule(c *gin.Context) {
rule := &mc.MetricsRule{}
err := validator.CheckPostParams(c, rule)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
fields["id"] = rule.Id
oldRule, err := rule.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
if rule.RuleNo != oldRule.RuleNo {
controller.FailCode(c, errors.InvalidParameter, err, "编号不允许修改!")
return
}
if rule.RuleKey != "" {
queryKey := map[string]interface{}{
"rule_key": rule.RuleKey,
}
rr, _ := rule.Get(queryKey)
if rr.RuleKey != "" && rr.RuleKey != oldRule.RuleKey {
controller.FailCode(c, errors.InvalidParameter, err, "KEY值重复")
return
}
}
updateColumns := []string{}
_, err = rule.Update(updateColumns)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, "")
}
// 根据编号获取规则
func GetMetricRule(c *gin.Context) {
rule := &mc.MetricsRule{}
ruleNo := c.Param("ruleNo")
fields := map[string]interface{}{}
fields["rule_no"] = ruleNo
mRule, err := rule.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, mRule)
}
// 根据扩展key获取规则
func GetMetricRuleByKey(c *gin.Context) {
rule := &mc.MetricsRule{}
ruleKey := c.Param("ruleKey")
fields := map[string]interface{}{}
fields["rule_key"] = ruleKey
mRule, err := rule.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, mRule)
}
type PageBody struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
Keyword string `json:"keyword"`
}
// 根据扩展key获取规则
func PageMetrics(c *gin.Context) {
var pageBody PageBody
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
rule := &mc.MetricsRule{}
fields := map[string]interface{}{}
fields["keyword"] = pageBody.Keyword
page := &model.Page[mc.MetricsRule]{}
page.Covert(pageBody.PageInfo)
err = rule.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
// 删除指标规则
func DeleteMetric(c *gin.Context) {
rule := &mc.MetricsRule{}
ruleNo := c.Param("ruleNo")
fields := map[string]interface{}{}
fields["rule_no"] = ruleNo
mRule, err := rule.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
mRule.Delete()
controller.Success(c, "")
}
type DeleteBody struct {
RuleNos []string `json:"ruleNos" binding:"required"`
}
// 批量删除指标规则
func DeleteBatchMetric(c *gin.Context) {
var deleteBody DeleteBody
err := c.ShouldBindJSON(&deleteBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
rule := &mc.MetricsRule{}
_, err = rule.DeleteBatch(deleteBody.RuleNos)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "删除失败!")
return
}
controller.Success(c, "删除成功!")
}

View File

@ -0,0 +1,91 @@
package v1
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service"
"github.com/gin-gonic/gin"
"log"
"os"
)
func init() {
_ = os.Mkdir("/tmp", os.FileMode(0777))
}
func GetPresignedUrl(c *gin.Context) {
filename := c.Param("filename")
if filename == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
url, err := service.GetPresignedUrl("cpn", filename, 10000)
if err != nil {
log.Println(err)
controller.FailCode(c, errors.ServerError, err, "minio connection fail")
return
}
controller.Success(c, url)
}
func PostPresignedUrl(c *gin.Context) {
filename := c.Param("filename")
if filename == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
formData, err := service.PostPresignedUrl("cpn", filename, 10000)
if err != nil {
log.Println(err)
controller.FailCode(c, errors.ServerError, err, "minio connection fail")
return
}
controller.Success(c, formData)
}
func PutPresignedUrl(c *gin.Context) {
filename := c.Param("filename")
if filename == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
data, err := service.PutPresignedUrl("cpn", filename, 10000)
if err != nil {
log.Println(err)
controller.FailCode(c, errors.ServerError, err, "minio connection fail")
return
}
controller.Success(c, data)
}
func UploadFile(c *gin.Context) {
_, headers, err := c.Request.FormFile("file")
if err != nil {
log.Printf("Error when try to get file: %v", err)
}
filename := c.Request.PostForm.Get("filename")
if filename == "" {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
//headers.Size 获取文件大小
if headers.Size > 1024*1024*200 {
controller.FailCode(c, errors.InvalidParameter, err, "文件大小不能超过200M")
return
}
err = c.SaveUploadedFile(headers, "/tmp/"+headers.Filename)
if err != nil {
log.Printf("Error upload file: %v", err)
controller.FailCode(c, errors.ServerError, err, "上传文件失败!")
return
}
err = service.UploadFile("cpn", filename, "/tmp/"+headers.Filename)
if err != nil {
log.Printf("Error upload minio file: %v", err)
controller.FailCode(c, errors.ServerError, err, "上传文件失败!")
return
}
controller.Success(c, "")
}

View File

@ -0,0 +1,549 @@
package v1
import (
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/middleware"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/event"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/id"
sc "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/schedule"
tk "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/schedule"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
excel "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/utils"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/schedule_engine"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/validator"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/gin-gonic/gin"
"time"
)
func CreateSchedRule(c *gin.Context) {
rule := &sc.SchedRule{}
err := validator.CheckPostParams(c, rule)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if ur, bl := c.Get(middleware.LoginUserKey); bl {
if u, b := ur.(*user.User); b {
rule.CreateBy = u.Name
}
}
if _, err = rule.Create(); err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, "")
}
// 编辑调度规则
func EditSchedRule(c *gin.Context) {
rule := &sc.SchedRule{}
err := validator.CheckPostParams(c, rule)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
fields := map[string]interface{}{}
fields["id"] = rule.Id
_, err = rule.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "无法查找到对象!")
return
}
updateColumns := []string{}
_, err = rule.Update(updateColumns)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "数据库错误!")
return
}
controller.Success(c, "")
}
// 根据ID获取规则
func GetSchedRule(c *gin.Context) {
rule := &sc.SchedRule{}
id := c.Param("id")
fields := map[string]interface{}{}
fields["id"] = id
result, err := rule.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, result)
}
func StatisticSchedRule(c *gin.Context) {
rule := &sc.SchedRule{}
total, err := rule.Count()
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "数据库异常!")
return
}
now := time.Now()
lastYear := now.AddDate(-1, 1, 0)
startDate := time.Date(lastYear.Year(), lastYear.Month(), 1, 0, 0, 0, 0, time.Local)
tmp, err := rule.CountByMonth(startDate)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "数据库异常!")
return
}
group := make(map[string]interface{})
for _, item := range tmp {
dateStr := item["date"].(string)
group[dateStr] = item["count"]
}
for startDate.Before(now) {
dateStr := startDate.Format("2006-01")
if _, exist := group[dateStr]; !exist {
group[dateStr] = 0
}
startDate = startDate.AddDate(0, 1, 0)
}
result := map[string]interface{}{
"total": total,
"group": group,
}
controller.Success(c, result)
}
type PageBodySchedRule struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
Keyword string `json:"keyword"`
Status string `json:"status"`
}
// 分页获取规则
func PageSchedRules(c *gin.Context) {
var pageBody PageBodySchedRule
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
rule := &sc.SchedRule{}
fields := map[string]interface{}{}
fields["keyword"] = pageBody.Keyword
if pageBody.Status != "" {
fields["rule_status"] = pageBody.Status
}
page := &model.Page[sc.SchedRule]{}
page.Covert(pageBody.PageInfo)
err = rule.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
type DeleteBodySchedRule struct {
Ids []int64 `json:"ids" binding:"required"`
}
// 批量删除规则
func DeleteSchedRules(c *gin.Context) {
var deleteBody DeleteBodySchedRule
err := c.ShouldBindJSON(&deleteBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
rule := &sc.SchedRule{}
_, err = rule.DeleteBatch(deleteBody.Ids)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "删除失败!")
return
}
controller.Success(c, "删除成功!")
}
// 停用启用操作
func DoSchedRule(c *gin.Context) {
rule := &sc.SchedRule{}
id := c.Param("id")
option := c.Param("option")
fields := map[string]interface{}{}
fields["id"] = id
result, err := rule.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if option == "0" {
//todo 开启任务
} else if option == "1" {
//todo 停止任务
} else {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
result.RuleStatus = option
result.Update(nil)
controller.Success(c, "执行成功!")
}
type PageBodySchedTask struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
ListBodySchedTask
}
type ListBodySchedTask struct {
TaskName string `json:"taskName"`
TaskStatus string `json:"taskStatus"`
StartTime *time.Time `json:"startTime"`
EndTime *time.Time `json:"endTime"`
}
// 分页获取规则
func PageSchedTasks(c *gin.Context) {
var pageBody PageBodySchedTask
err := c.ShouldBindJSON(&pageBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
task := &sc.SchedTask{}
fields := map[string]interface{}{}
fields["task_name"] = pageBody.TaskName
if pageBody.TaskStatus != "" {
fields["task_status"] = pageBody.TaskStatus
}
if pageBody.StartTime == nil {
fields["start_time"] = time.Now().Add(-30 * 24 * time.Hour)
} else {
fields["start_time"] = pageBody.StartTime
}
if pageBody.EndTime == nil {
fields["end_time"] = time.Now()
} else {
fields["end_time"] = pageBody.EndTime
}
page := &model.Page[sc.SchedTask]{}
page.Covert(pageBody.PageInfo)
err = task.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
controller.Success(c, page)
}
// 分页获取规则
func ExportSchedTasks(c *gin.Context) {
var listBody ListBodySchedTask
err := c.ShouldBindJSON(&listBody)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
task := &sc.SchedTask{}
fields := map[string]interface{}{}
fields["task_name"] = listBody.TaskName
if listBody.TaskStatus != "" {
fields["task_status"] = listBody.TaskStatus
}
fields["start_time"] = listBody.StartTime
fields["end_time"] = listBody.EndTime
tasks, err := task.ListAll(fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "未找到记录!")
return
}
rows := [][]interface{}{}
for _, t := range tasks {
row := []interface{}{
t.TaskName, t.Id, t.StartTime, t.EndTime, tk.TriggerTypeMap[t.TriggerType], "", tk.TaskStatusMap[t.TaskStatus],
}
rows = append(rows, row)
}
header := []string{"任务名称", "任务编号", "开始时间", "结束时间", "触发方式", "触发事件", "状态"}
f, err := excel.ExportExcel("调度任务", header, rows)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "生成excel文件失败")
return
}
f.SetColWidth("调度任务", "A", "A", 40)
f.SetColWidth("调度任务", "C", "D", 15)
f.SetColWidth("调度任务", "F", "F", 40)
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
if err := f.SaveAs("/tmp/task.xlsx"); err != nil {
fmt.Println(err)
controller.FailCode(c, errors.ServerError, err, "生成下载文件失败!")
return
}
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename=task.xlsx")
c.Header("Content-Transfer-Encoding", "binary")
c.File("/tmp/task.xlsx")
return
}
// 根据ID获取调度任务
func GetSchedTask(c *gin.Context) {
task := &sc.SchedTask{}
id := c.Param("id")
fields := map[string]interface{}{}
fields["id"] = id
result, err := task.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, result)
}
// 根据EventId获取调度任务
func GetSchedTaskByEvent(c *gin.Context) {
task := &sc.SchedTask{}
eventId := c.Param("eventId")
fields := map[string]interface{}{}
fields["event_id"] = eventId
result, err := task.Get(fields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, result)
}
type TriggerTaskBody struct {
PlanId string `json:"planId" validate:"required"`
HandleAdvice string `json:"handleAdvice"`
// 资源Id
ResourceID string `json:"resourceId"`
// 资源名称(cpCode)
ResourceName string `json:"resourceName"`
// 资源节点
ResourceNode string `json:"resourceNode"`
// 资源类型
ResourceType string `json:"resourceType"`
// 方案参数
SchemeParams string `json:"schemeParams"`
}
// 手动触发调度任务
func TriggerSchedTask_UserManual(c *gin.Context) {
var body TriggerTaskBody
err := c.ShouldBindJSON(&body)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
// 暂不入库
record := event.EventRecord{
ResourceType: body.ResourceType,
ResourceNode: body.ResourceNode,
ResourceName: body.ResourceName,
ResourceId: body.ResourceID,
Id: id.NewEventRecord(),
}
task := &sc.SchedTask{}
t := time.Now()
sno := fmt.Sprintf("%d-%d%d-%s", t.Year(), t.Month(), t.Day(), record.Id)
task.Id = record.ResourceType + "-" + sno
task.TaskName = ""
task.EventId = record.Id
task.StartTime = time.Now()
task.PlanId = body.PlanId
task.CreateTime = time.Now()
task.HandleAdvice = body.HandleAdvice
task.HandleTime = time.Now()
err = schedule_engine.PlanHandle(task.PlanId, task.Id, body.SchemeParams, record)
if err != nil {
//log.Errorf("%v", err)
controller.FailCode(c, errors.ServerError, err, "调度方案失败!")
return
}
task.TaskStatus = sc.TASK_STATUS_ING
task.TriggerType = sc.TRIGGER_TYPE_MANUAL
_, err = task.Create()
if err != nil {
log.Errorf("生成调度任务失败:%v事件ID:%s", err, record.Id)
controller.FailCode(c, errors.ServerError, err, "生成调度任务失败!")
return
}
controller.Success(c, "")
}
// 手动触发调度任务
func TriggerSchedTask(c *gin.Context) {
eventId := c.Param("eventId")
event := &event.EventRecord{}
eventFields := map[string]interface{}{}
eventFields["id"] = eventId
record, err := event.Get(eventFields)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
var body TriggerTaskBody
err = c.ShouldBindJSON(&body)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
task := &sc.SchedTask{}
fields := map[string]interface{}{}
fields["event_id"] = eventId
taskRecord, err := task.Get(fields)
if ur, bl := c.Get(middleware.LoginUserKey); bl {
if u, b := ur.(*user.User); b {
task.HandleUsername = u.Name
taskRecord.HandleUsername = u.Name
}
}
if err != nil {
t := time.Now()
sno := fmt.Sprintf("%d-%d%d-%s", t.Year(), t.Month(), t.Day(), eventId)
task.Id = record.ResourceType + "-" + sno
task.TaskName = ""
task.EventId = eventId
task.StartTime = time.Now()
task.PlanId = body.PlanId
task.CreateTime = time.Now()
task.HandleAdvice = body.HandleAdvice
task.HandleTime = time.Now()
err = schedule_engine.PlanHandle(task.PlanId, task.Id, "", record)
if err != nil {
log.Errorf("%v", err)
controller.FailCode(c, errors.ServerError, err, "调度方案失败!")
return
}
task.TaskStatus = sc.TASK_STATUS_ING
task.TriggerType = sc.TRIGGER_TYPE_MANUAL
_, err = task.Create()
if err != nil {
log.Errorf("生成调度任务失败:%v事件ID:%s", err, eventId)
controller.FailCode(c, errors.ServerError, err, "生成调度任务失败!")
return
}
} else {
taskRecord.TriggerType = sc.TRIGGER_TYPE_MANUAL
taskRecord.StartTime = time.Now()
taskRecord.EventId = eventId
taskRecord.PlanId = body.PlanId
taskRecord.HandleAdvice = body.HandleAdvice
taskRecord.HandleTime = time.Now()
err = schedule_engine.PlanHandle(taskRecord.PlanId, taskRecord.Id, "", record)
if err != nil {
log.Errorf("%v", err)
controller.FailCode(c, errors.ServerError, err, "调度失败!")
return
}
taskRecord.TaskStatus = sc.TASK_STATUS_ING
_, err = taskRecord.Update(nil)
if err != nil {
log.Errorf("生成调度任务失败:%v事件ID:%s", err, eventId)
controller.FailCode(c, errors.ServerError, err, "生成调度任务失败!")
return
}
}
controller.Success(c, "")
}
type TaskCallBackBody struct {
Status string `json:"status" validate:"required"` // 1:成功2:失败
EndTime int64 `json:"endTime"` //秒级时间戳
}
// 调度任务回调
func TaskCallBack(c *gin.Context) {
taskId := c.Param("taskId")
var body TaskCallBackBody
err := c.ShouldBindJSON(&body)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
task := &sc.SchedTask{}
fields := map[string]interface{}{}
fields["id"] = taskId
taskRecord, err := task.Get(fields)
if err != nil {
log.Errorf("回调任务失败:%v", err)
controller.FailCode(c, errors.InvalidParameter, err, "未找到调度任务,请检查参数!")
return
}
taskRecord.TaskStatus = body.Status
if body.EndTime <= 0 {
taskRecord.EndTime = time.Now()
} else {
taskRecord.EndTime = time.Unix(body.EndTime, 22)
}
_, err = taskRecord.Update(nil)
// todo 将事件更新至事件历史表(一删一增)
if err != nil {
log.Errorf("更新回调任务失败:%v", err)
controller.FailCode(c, errors.ServerError, err, "内部错误!")
return
}
go func() {
evt := &event.EventRecord{}
f := map[string]interface{}{}
f["id"] = taskRecord.EventId
e, err := evt.Get(f)
if err == nil {
eh := &event.HistoryEventRecord{
Id: e.Id,
EventName: e.EventName,
EventKey: e.EventKey,
Level: e.Level,
ResourceId: e.ResourceId,
ResourceName: e.ResourceName,
ResourceType: e.ResourceType,
ResourceNode: e.ResourceNode,
EventTime: e.EventTime,
EventInfo: e.EventInfo,
OperatorUser: taskRecord.HandleUsername,
OperatorFinishTime: taskRecord.EndTime,
CreateTime: time.Now(),
}
rows, _ := eh.SaveOrUpdate()
if rows > 0 {
e.Delete()
}
}
}()
controller.Success(c, "回调成功")
}
// 任务状态数统计
func CountTaskStatus(c *gin.Context) {
task := &sc.SchedTask{}
result, err := task.CountStatusNum()
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
controller.Success(c, result)
}
// 调度任务自动化率
func AutoRatio(c *gin.Context) {
task := &sc.SchedTask{}
result, err := task.AutoRatio()
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "数据库异常!")
return
}
controller.Success(c, result)
}

View File

@ -0,0 +1,232 @@
package v1
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/controller"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/device"
devc "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/device"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"github.com/gin-gonic/gin"
"strconv"
"strings"
"time"
)
func GetDeviceOnlineStatistic(c *gin.Context) {
duration := c.Query("duration")
if len(duration) <= 1 {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
var timeDuration time.Duration
if strings.HasSuffix(duration, "h") {
timeDuration = time.Hour
} else if strings.HasSuffix(duration, "m") {
timeDuration = time.Minute
} else {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
timeInt, err := strconv.ParseInt(duration[0:len(duration)-1], 10, 64)
if err != nil || timeInt <= 0 {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
if timeInt > 1440 || (timeDuration == time.Hour && timeInt > 24) {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
result := make(map[string]int)
now := time.Now()
todayBegin := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
timeInterval := time.Duration((timeInt - 1) * timeDuration.Nanoseconds())
timeIntervalStart := todayBegin
var timeIntervalEnd time.Time
sql := "SELECT CASE "
for timeIntervalStart.Before(now) {
timeIntervalEnd = timeIntervalStart.Add(timeInterval)
timeStartStr := timeIntervalStart.Format("200601021504")
timeEndStr := timeIntervalEnd.Format("200601021504")
sql += "WHEN id BETWEEN '" + timeStartStr + "' AND '" + timeEndStr + "' THEN '" + timeStartStr + "' "
result[timeStartStr] = 0
timeIntervalStart = timeIntervalEnd.Add(timeDuration)
}
sql += "END AS time, MAX(count) AS count FROM perc_device_online_count WHERE id >= '" + todayBegin.Format("200601021504") + "' AND id <= '" + now.Format("200601021504") + "' GROUP BY time;"
d := device.DeviceOnlineCount{}
tResult, err := d.Query(sql)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
for _, item := range tResult {
result[item.Time] = item.Count
}
delete(result, "")
controller.Success(c, result)
}
func GetDeviceUsageRate(c *gin.Context) {
d := &devc.DeviceInfo{}
result, err := d.GetHistoryUsageRate()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
result1, err := d.GetTodayUsageRate()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
result["today"] = result1["today"]
controller.Success(c, result)
}
type DeviceOperateLogCondition struct {
PageInfo *request.PageInfo `json:"pageInfo" binding:"required"`
StartTime *time.Time `json:"startTime"`
EndTime *time.Time `json:"endTime"`
Operate string `json:"operate"`
}
func GetDeviceOperateLog(c *gin.Context) {
serialNo := c.Param("serialNo")
var body DeviceOperateLogCondition
err := c.ShouldBindJSON(&body)
if err != nil {
controller.FailCode(c, errors.InvalidParameter, err, "请检查参数!")
return
}
op := &devc.DeviceOperateLog{}
fields := make(map[string]interface{})
fields["serial_no"] = serialNo
if body.StartTime != nil {
fields["operate_time >"] = body.StartTime
} else {
now := time.Now()
todayBegin := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
fields["operate_time >"] = todayBegin
}
if body.EndTime != nil {
fields["operate_time <"] = body.EndTime
}
if len(body.Operate) > 0 {
fields["operate"] = body.Operate
}
page := &model.Page[devc.DeviceOperateLog]{
CurrentPage: body.PageInfo.CurrentPage,
PageSize: body.PageInfo.PageSize,
Order: body.PageInfo.Order,
}
err = op.Page(page, fields)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
if page.Data == nil {
page.Data = []devc.DeviceOperateLog{}
}
controller.Success(c, page)
}
func GetDeviceRunTime(c *gin.Context) {
serialNo := c.Param("serialNo")
duration := c.Query("duration")
result := make(map[string]int)
d := &devc.DeviceRunTime{}
now := time.Now()
var startDate string
if duration == "day" {
nowStr := now.Format("20060102")
tResult, err := d.GetRunTimeByDay(serialNo, nowStr)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
result[nowStr] = tResult.RunTime
} else if duration == "month" {
startDate = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).Format("20060102")
endDate := now.Format("20060102")
tmp, err := d.StatisticRunTimeByDuration(serialNo, startDate, endDate)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
tDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
for tDate.Before(now) {
result[tDate.Format("20060102")] = 0
tDate = tDate.Add(24 * time.Hour)
}
for _, runTime := range tmp {
result[runTime.Date] = runTime.RunTime
}
} else {
controller.FailCode(c, errors.InvalidParameter, nil, "请检查参数!")
return
}
controller.Success(c, result)
}
func GetAllDeviceRunTime(c *gin.Context) {
d := &devc.DeviceRunTime{}
result, err := d.StatisticRunTimeByAllDevice()
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
controller.Success(c, result)
}
func GetDeviceBootCount(c *gin.Context) {
serialNo := c.Param("serialNo")
d := &devc.DeviceOperateLog{}
now := time.Now()
monthBegin := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
startDate := monthBegin.Format("2006-01-02 15:04:05.999999999 -0700")
endDate := now.Format("2006-01-02 15:04:05.999999999 -0700")
tResult, err := d.StatisticDeviceBootCountByDuration(serialNo, startDate, endDate)
if err != nil {
controller.FailCode(c, errors.ServerError, err, "服务异常!")
return
}
result := make(map[string]int)
tDate := monthBegin
for tDate.Before(now) {
result[tDate.Format("20060102")] = 0
tDate = tDate.Add(24 * time.Hour)
}
for _, dateCount := range tResult {
result[dateCount.Date] = dateCount.Count
}
controller.Success(c, result)
}

View File

@ -0,0 +1,27 @@
package middleware
import (
"github.com/gin-gonic/gin"
)
const (
CLUSTERID = "clusterId"
NAMESPACE = "namespace"
)
func AppContextHandler() gin.HandlerFunc {
return func(c *gin.Context) {
ns := c.Request.Header.Get("namespace")
if len(ns) != 0 {
c.Set(NAMESPACE, ns)
}
clusterId := c.Param("cluster")
if len(clusterId) != 0 {
c.Set(CLUSTERID, clusterId)
}
c.Next()
}
}

View File

@ -0,0 +1,230 @@
package middleware
import (
"crypto/tls"
"encoding/json"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/user"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/auth"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/authen"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/response"
"github.com/gin-gonic/gin"
"github.com/golang/glog"
"gorm.io/gorm"
"io"
"net/http"
"regexp"
)
const (
LoginUserKey = "login-user"
)
func AuthenticationHandler() gin.HandlerFunc {
return func(c *gin.Context) {
match, _ := regexp.MatchString("/healthz", c.Request.RequestURI)
if match {
c.Next()
return
}
match, _ = regexp.MatchString("/run", c.Request.RequestURI)
if match {
c.Next()
return
}
match, _ = regexp.MatchString("/login", c.Request.RequestURI)
if match {
c.Next()
return
}
match, _ = regexp.MatchString("/api/v1/socket/*", c.Request.RequestURI)
if match {
c.Next()
return
}
match, _ = regexp.MatchString("/api/v1/*", c.Request.RequestURI)
if match {
rawToken := c.Request.Header.Get("Authorization")
if rawToken == "" {
glog.Warning("unauthorized access, token not specified")
response.Resp().FailCode(c, errors.AuthorizationError, "token should be specified in header with 'Authorization' key")
return
}
//todo 临时用
if config.Config.AppConfig.Debug {
if rawToken == "mock token" {
u, _ := user.GetUserByUsername("admin")
c.Set(LoginUserKey, u)
c.Next()
return
}
}
var username, apiToken string
ok, bearerToken := authen.JWTAuthorizer.IsBearerToken(rawToken)
if ok {
loginInfo, err := auth.Validate(bearerToken)
//marshal, _ := json.Marshal(loginInfo)
//fmt.Print(string(marshal))
if err != nil {
if authen.JWTAuthorizer.IsTokenExpired(err) {
glog.Warning("unauthorized access, bearer token expired")
response.Resp().FailCode(c, errors.AuthorizationError, "bearer token expired")
return
}
glog.Warningf("validate bearer token failed, %s", err)
response.Resp().FailCode(c, errors.AuthorizationError, fmt.Sprint("validate bearer token failed, %s", err))
return
}
username = loginInfo.Username
apiToken = loginInfo.Token
} else {
glog.Warningf("validate bearer token failed")
response.Resp().FailCode(c, errors.AuthorizationError, "validate bearer token failed")
return
}
u, err := user.GetUserByUsername(username)
if err != nil {
// 适配算网用户,支持单点登录
if config.Config.CfnConfig.Enable {
u, err = CheckUserLogin(c)
if err != nil {
glog.Errorf("unauthorized access, user not found or not login, %s", username)
response.Resp().FailCode(c, errors.AuthorizationError, "user not found or not login")
return
}
// todo
u.APIToken = apiToken
} else {
if err == gorm.ErrRecordNotFound {
glog.Errorf("unauthorized access, user not found, %s", username)
response.Resp().FailCode(c, errors.AuthorizationError, "user not found")
return
}
glog.Errorf("get user from db failed, user %s, %s", username, err)
response.Resp().FailCode(c, errors.ServerError, fmt.Sprintf("get user from db failed, user %s, %s", username, err))
return
}
}
if apiToken != "" && apiToken != u.APIToken {
glog.Warningf("unauthorized access, password mismatch, user %s", username)
response.Resp().FailCode(c, errors.AuthorizationError, "password mismatch")
return
}
c.Set(LoginUserKey, u)
c.Next()
}
//match, _ = regexp.MatchString("/", c.Request.RequestURI)
//if match {
// c.Next()
// return
//}
}
}
const AUTH_HEADER = "Authorization"
func CheckUserLogin(c *gin.Context) (*user.UserObj, error) {
url := config.Config.Auth.Url
if url == "" {
return nil, errors.NewBusinessError(errors.ServerError, "login config is not set!")
}
token := c.Request.Header.Get(AUTH_HEADER)
var client *http.Client
var request *http.Request
var resp *http.Response
client = &http.Client{Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}}
request, err := http.NewRequest("GET", url+config.Config.Auth.LoginInfo, nil)
if err != nil {
glog.Errorln("failed to create request [Get LoginInfo]!", err)
return nil, errors.NewBusinessError(errors.ServerError, "failed to create request [Get LoginInfo]!")
}
request.Header.Add(AUTH_HEADER, token)
resp, err = client.Do(request)
if err != nil {
glog.Errorln("failed to request [Get LoginInfo]!", err)
return nil, errors.NewBusinessError(errors.ServerError, "failed to request [Get LoginInfo]!")
}
defer resp.Body.Close()
defer client.CloseIdleConnections()
if resp.StatusCode != 200 {
glog.Errorln("response error [Get LoginInfo]!")
glog.Errorln("response status code: %d, %s", resp.StatusCode, resp.Body)
return nil, errors.NewBusinessError(errors.ServerError, "response error [Get LoginInfo]!")
}
var respBody map[string]interface{}
respStr, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(respStr, &respBody)
if err != nil {
glog.Errorln("failed to decode response [Get LoginInfo]!", err)
return nil, errors.NewBusinessError(errors.ServerError, "failed to decode response [Get LoginInfo]!")
}
data := respBody["data"]
if data == nil {
meta := respBody["meta"]
return nil, fmt.Errorf("failed to get user info from login server! %s", meta)
}
userObj := &user.UserObj{}
userMap := data.(map[string]interface{})["user"]
userId := userMap.(map[string]interface{})["userId"]
if userId != nil {
userObj.UserID = userId.(string)
}
cn := userMap.(map[string]interface{})["cn"]
if cn != nil {
userObj.DisplayName = cn.(string)
}
mobile := userMap.(map[string]interface{})["mobile"]
if mobile != nil {
userObj.Mobile = mobile.(string)
}
provinceId := userMap.(map[string]interface{})["provinceId"]
if provinceId != nil {
userObj.ProvinceId = provinceId.(string)
}
userObj.Status = userMap.(map[string]interface{})["status"].(string)
accountMap := data.(map[string]interface{})["account"]
userName := accountMap.(map[string]interface{})["loginName"]
if userName != nil {
userObj.UserName = userName.(string)
}
if userName == "admin" {
userObj.Role = 1
} else {
userObj.Role = 0
}
if config.Config.CfnConfig.Enable {
userObj.Namespace = fmt.Sprintf("DEFAULT,%s", config.Config.CfnConfig.CfnWorkSpaceId)
} else {
userObj.Namespace = "DEFAULT"
}
return userObj, nil
}

View File

@ -0,0 +1,49 @@
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/golang/glog"
"regexp"
)
func AuthorizationHandler() gin.HandlerFunc {
return func(c *gin.Context) {
match, _ := regexp.MatchString("/healthz", c.Request.RequestURI)
if match {
c.Next()
return
}
match, _ = regexp.MatchString("/run", c.Request.RequestURI)
if match {
c.Next()
return
}
match, _ = regexp.MatchString("/login", c.Request.RequestURI)
if match {
c.Next()
return
}
match, _ = regexp.MatchString("/api/v1/socket/*", c.Request.RequestURI)
if match {
c.Next()
return
}
match, _ = regexp.MatchString("/api/v1/*", c.Request.RequestURI)
if match {
_, exist := c.Get(LoginUserKey)
if !exist {
glog.Fatal("Authorization middleware should work together with Authentication middleware")
c.Next()
return
}
c.Next()
}
}
}

View File

@ -0,0 +1,10 @@
package middleware
import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func CorsHandler() gin.HandlerFunc {
return cors.Default()
}

View File

@ -0,0 +1,53 @@
package middleware
import (
"bytes"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"time"
)
type responseWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w responseWriter) Write(b []byte) (int, error) {
w.body.Write(b)
return w.ResponseWriter.Write(b)
}
func (w responseWriter) WriteString(s string) (int, error) {
w.body.WriteString(s)
return w.ResponseWriter.WriteString(s)
}
// CustomLogger 接收gin框架默认的日志
func CustomLogger() gin.HandlerFunc {
return func(c *gin.Context) {
blw := &responseWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = blw
// 读取body数据
//body := request.GetBody(c)
c.Next()
cost := time.Since(c.GetTime("requestStartTime"))
if config.Config.AppEnv != "production" {
path := c.Request.URL.Path
log.Logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", c.Request.URL.RawQuery),
//zap.Any("body", string(body)),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.String("cost", cost.String()),
//zap.String("response", blw.body.String()),
)
}
}
}

View File

@ -0,0 +1,40 @@
package middleware
import (
"errors"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
e "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors"
response2 "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/response"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log"
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
// CustomRecovery 自定义错误 (panic) 拦截中间件、对可能发生的错误进行拦截、统一记录
func CustomRecovery() gin.HandlerFunc {
DefaultErrorWriter := &PanicExceptionRecord{}
return gin.RecoveryWithWriter(DefaultErrorWriter, func(c *gin.Context, err interface{}) {
// 这里针对发生的panic等异常进行统一响应即
// 这里针对发生的panic等异常进行统一响应即
errStr := ""
if config.Config.Debug == true {
errStr = fmt.Sprintf("%v", err)
}
response2.Resp().SetHttpCode(http.StatusInternalServerError).FailCode(c, e.ServerError, errStr)
})
}
// PanicExceptionRecord panic等异常记录
type PanicExceptionRecord struct{}
func (p *PanicExceptionRecord) Write(b []byte) (n int, err error) {
s1 := "An error occurred in the server's internal code"
var build strings.Builder
build.WriteString(s1)
build.Write(b)
errStr := build.String()
log.Error(errStr)
return len(errStr), errors.New(errStr)
}

View File

@ -0,0 +1,13 @@
package middleware
import (
"github.com/gin-gonic/gin"
"time"
)
func RequestCostHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("requestStartTime", time.Now())
c.Next()
}
}

View File

@ -0,0 +1,93 @@
package middleware
import (
"embed"
"github.com/gin-gonic/gin"
"io/fs"
"net/http"
"os"
"path"
"strings"
)
const INDEX = "index.html"
type ServeFileSystem interface {
http.FileSystem
Exists(prefix string, path string) bool
}
type localFileSystem struct {
http.FileSystem
root string
indexes bool
}
func LocalFile(root string, indexes bool) *localFileSystem {
return &localFileSystem{
FileSystem: gin.Dir(root, indexes),
root: root,
indexes: indexes,
}
}
func (l *localFileSystem) Exists(prefix string, filepath string) bool {
if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
name := path.Join(l.root, p)
stats, err := os.Stat(name)
if err != nil {
return false
}
if stats.IsDir() {
if !l.indexes {
index := path.Join(name, INDEX)
_, err := os.Stat(index)
if err != nil {
return false
}
}
}
return true
}
return false
}
func ServeRoot(urlPrefix, root string) gin.HandlerFunc {
return Serve(urlPrefix, LocalFile(root, false))
}
// Static returns a middleware handler that serves static files in the given directory.
func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
fileserver := http.FileServer(fs)
if urlPrefix != "" {
fileserver = http.StripPrefix(urlPrefix, fileserver)
}
return func(c *gin.Context) {
if fs.Exists(urlPrefix, c.Request.URL.Path) {
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
}
type embedFileSystem struct {
http.FileSystem
}
func (e embedFileSystem) Exists(prefix string, path string) bool {
_, err := e.Open(path)
if err != nil {
return false
}
return true
}
func EmbedFolder(fsEmbed embed.FS, targetPath string) ServeFileSystem {
fsys, err := fs.Sub(fsEmbed, targetPath)
if err != nil {
panic(err)
}
return embedFileSystem{
FileSystem: http.FS(fsys),
}
}

View File

@ -0,0 +1,127 @@
package agent
import (
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/utils"
"gorm.io/gorm"
"time"
)
const (
OS_WINDOWS = "Windows"
OS_LINUX = "Linux"
OS_LINUX_LIST = "Ubuntu,CentOS,KylinOS"
OS_LINUX_UBUNTU = "Ubuntu"
OS_LINUX_CENTOS = "CentOS"
OS_LINUX_KylinOS = "KylinOS"
)
// 小助手信息
// 表名:perc_agent_info
type AgentInfo struct {
Id string `gorm:"column:id;primaryKey" json:"id"` //type:string comment:主键 version:2023-9-11 14:25
Version string `gorm:"column:version" json:"version" validate:"required"` //type:string comment:版本 version:2023-9-11 14:25
VersionNum int64 `gorm:"column:version_num" json:"versionNum"` //type:int comment:版本排序号
OsPlatform string `gorm:"column:os_platform" json:"osPlatform"` //type:string comment:操作系统平台;字典Windows\Linux
OsType string `gorm:"column:os_type" json:"osType" validate:"required"` //type:string comment:操作系统类型;字典Windows\Ubuntu\CentOS\KylinOS version:2023-9-11 14:25
ArchType string `gorm:"column:arch_type" json:"archType" validate:"required"` //type:string comment:芯片架构类型;字典: X86\ARM version:2023-9-11 14:25
PackageUrl string `gorm:"column:package_url" json:"packageUrl"` //type:string comment:程序包地址;oss地址 version:2023-9-11 14:25
BucketName string `gorm:"column:bucket_name" json:"bucketName"` //type:string comment:桶名称
Filename string `gorm:"column:filename" json:"filename"` //type:string comment:文件名称
CompressionType string `gorm:"column:compression_type" json:"compressionType"` //type:string comment:压缩格式
Command string `gorm:"column:command" json:"command"` //type:string comment:启动命令
Describe string `gorm:"column:describe" json:"describe"` //type:string comment:描述 version:2023-9-11 14:25
CreateBy string `gorm:"column:create_by" json:"createBy"` //type:string comment:创建人
CreateTime time.Time `gorm:"column:create_time" json:"createTime"` //type:*time.Time comment:创建时间 version:2023-9-11 14:25
UpdateTime time.Time `gorm:"column:update_time" json:"updateTime"` //type:*time.Time comment:更新时间
}
func (AgentInfo) TableName() string {
return "perc_agent_info"
}
func (a *AgentInfo) Create() (int64, error) {
a.CreateTime = time.Now()
a.UpdateTime = time.Now()
a.Id = utils.NewAgentID()
tx := model.DB().Create(a)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
func (a *AgentInfo) CreateBatch(agents []AgentInfo) (int64, error) {
for i, _ := range agents {
agents[i].CreateTime = time.Now()
agents[i].UpdateTime = time.Now()
agents[i].Id = utils.NewAgentID()
}
tx := model.DB().CreateInBatches(agents, len(agents))
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
func (a *AgentInfo) Delete() (int64, error) {
tx := model.DB().Delete(a)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
func (a *AgentInfo) Update(fields []string) (int64, error) {
a.UpdateTime = time.Now()
tx := model.DB().Model(&a).Select(fields).Updates(a)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
func (a *AgentInfo) Get(fields map[string]interface{}) (AgentInfo, error) {
agent := AgentInfo{}
tx := model.DB().Where(fields).First(&agent)
if tx.Error != nil && tx.Error != gorm.ErrRecordNotFound {
return agent, tx.Error
}
return agent, nil
}
func (a *AgentInfo) HasNewVersion(fields map[string]interface{}) (bool, error) {
cout := int64(0)
tx := model.DB().Model(a).Where("os_type=? and arch_type=? and version_num >=?", fields["os_type"], fields["arch_type"], fields["version_num"]).Count(&cout)
if tx.Error != nil || cout > int64(0) {
return true, tx.Error
}
return false, nil
}
// 获取最近的版本
func (a *AgentInfo) ListLastVersion() ([]AgentInfo, error) {
agents := []AgentInfo{}
tx := model.DB().Raw("SELECT * FROM perc_agent_info A INNER JOIN (SELECT os_type,arch_type,MAX (version_num) AS version_num FROM perc_agent_info GROUP BY os_type,arch_type) b ON A.arch_type=b.arch_type AND A.os_type=b.os_type AND A.version_num=b.version_num").Find(&agents)
if tx.Error != nil {
return nil, tx.Error
}
return agents, nil
}
func (a *AgentInfo) Page(page *model.Page[AgentInfo], fields map[string]interface{}) error {
keyword := fields["keyword"]
query := model.DB()
if keyword != "" {
keyword = fmt.Sprintf("%%%s%%", keyword)
query = query.Where("concat(version,os_platform,os_type,arch_type,describe) like ?", keyword)
}
err := page.SelectPages(query)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,6 @@
package data
type AgentWhitelist struct {
// 项目目录白名单、日志文件白名单
Project []string `json:"project"`
}

View File

@ -0,0 +1,96 @@
package node
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/secure"
"gorm.io/gorm"
)
type MachineNode struct {
model.BaseUserModifyDbModel
Name string `gorm:"column:name" json:"name"`
HostName string `gorm:"column:hostname" json:"hostName"`
OsName string `gorm:"column:os_name" json:"osName"`
OsOccupyCpu float64 `gorm:"column:os_occupy_cpu" json:"osOccupyCpu"`
OsOccupyMemory float64 `gorm:"column:os_occupy_memory" json:"osOccupyMemory"`
OsOccupyDisk float64 `gorm:"column:os_occupy_disk" json:"osOccupyDisk"`
HostIpv4s string `gorm:"column:host_ipv4s" json:"hostIpv4s"`
Status int `gorm:"column:status" json:"status,omitempty"` // 假设可以为null
StatusMsg string `gorm:"column:status_msg" json:"statusMsg"`
TransportMode int `gorm:"column:transport_mode" json:"transportMode,omitempty"`
CfnAgentUrl string `gorm:"column:cfn_agent_url" json:"cfnAgentUrl"`
CfnAgentUsername string `gorm:"column:cfn_agent_username" json:"cfnAgentUsername"`
CfnAgentPassword string `gorm:"column:cfn_agent_password" json:"cfnAgentPassword"`
CfnAgentProtocol string `gorm:"column:cfn_agent_protocol" json:"cfnAgentProtocol"`
CfnAgentTimeout int `gorm:"column:cfn_agent_timeout" json:"cfnAgentTimeout,omitempty"`
CfnAgentHttpProxy string `gorm:"column:cfn_agent_http_proxy" json:"cfnAgentHttpProxy"`
CfnAgentHttpProxyType string `gorm:"column:cfn_agent_http_proxy_type" json:"cfnAgentHttpProxyType"`
CfnAgentVersion string `gorm:"column:cfn_agent_version" json:"cfnAgentVersion"`
CfnAgentUptime int64 `gorm:"column:cfn_agent_uptime" json:"cfnAgentUptime"`
CfnAgentBuildTime int64 `gorm:"column:cfn_agent_build_time" json:"cfnAgentBuildTime"`
CfnAgentProjectCount int `gorm:"column:cfn_agent_project_count" json:"cfnAgentProjectCount,omitempty"`
CfnAgentScriptCount int `gorm:"column:cfn_agent_script_count" json:"cfnAgentScriptCount,omitempty"`
JavaVersion string `gorm:"column:java_version" json:"javaVersion"`
JvmTotalMemory int64 `gorm:"column:jvm_total_memory" json:"jvmTotalMemory,omitempty"`
JvmFreeMemory int64 `gorm:"column:jvm_free_memory" json:"jvmFreeMemory,omitempty"`
TemplateNode int `gorm:"column:template_node" json:"templateNode,omitempty"`
InstallId string `gorm:"column:install_id" json:"installId"`
TransportEncryption int `gorm:"column:transport_encryption" json:"transportEncryption,omitempty"` // 传输加密方式 0 不加密 1 BASE64 2 AES
Group string `gorm:"column:group_name" json:"groupName"`
}
func NewMachine() *MachineNode {
n := &MachineNode{}
n.Handler = n
return n
}
func (*MachineNode) TableName() string {
return "sched_machine_node_info"
}
func (m *MachineNode) ListGroup() ([]string, error) {
var result []string
tx := model.DB().Table(m.TableName()).Select("group_name").Distinct().Scan(&result)
return result, tx.Error
}
func (*MachineNode) FindResult(db *gorm.DB) (interface{}, error) {
var result []MachineNode
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
func (m *MachineNode) Authorize() string {
return secure.Sha1Encrypt(m.CfnAgentUsername + "@" + m.CfnAgentPassword)
}
// 根据Name查询MachineNode是否存在
func (m *MachineNode) ExistMachineNodeByName(name string) (bool, error) {
var count int64
tx := model.DB().Table(m.TableName()).Where("name = ?", name).Count(&count)
return count > 0, tx.Error
}
// 根据CfnAgentUrl查询MachineNode是否存在
func (m *MachineNode) ExistMachineNodeByCfnAgentUrl(url string) (bool, error) {
var count int64
tx := model.DB().Table(m.TableName()).Where("cfn_agent_url = ?", url).Count(&count)
return count > 0, tx.Error
}
// 根据CfnAgentUrl查询MachineNode
func (m *MachineNode) GetMachineNodeByCfnAgentUrl(url string) (*MachineNode, error) {
var result MachineNode
tx := model.DB().Table(m.TableName()).Where("cfn_agent_url = ?", url).First(&result)
return &result, tx.Error
}
// 更新MachineNode的CfnAgentPassword
func (m *MachineNode) UpdateMachineNodeCfnAgentPassword(url, password string) error {
tx := model.DB().Table(m.TableName()).Where("cfn_agent_url = ?", url).Update("cfn_agent_password", password)
return tx.Error
}

View File

@ -0,0 +1,72 @@
package node
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"gorm.io/gorm"
)
type Node struct {
model.BaseMachineModel
Url string `gorm:"column:url" json:"url"`
LoginName string `gorm:"column:login_name" json:"loginName"`
LoginPwd string `gorm:"column:login_pwd" json:"loginPwd"`
Name string `gorm:"column:name" json:"name"`
Protocol string `gorm:"column:protocol" json:"protocol"`
OpenStatus int `gorm:"column:open_status" json:"openStatus"`
TimeOut int `gorm:"column:timeout" json:"timeOut"`
HttpProxy string `gorm:"column:http_proxy" json:"httpProxy"`
HttpProxyType string `gorm:"column:http_proxy_type" json:"httpProxyType"`
SortValue float32 `gorm:"column:sort_value" json:"sortValue"`
MachineNodeData *MachineNode `gorm:"-" json:"machineNodeData"`
}
func New() *Node {
n := &Node{}
n.Handler = n
return n
}
func (*Node) TableName() string {
return "sched_node_info"
}
func (n *Node) ListGroup() ([]string, error) {
var result []string
tx := model.DB().Table(n.TableName()).Select("\"group\"").Distinct().Scan(&result)
return result, tx.Error
}
func (n *Node) FindResult(db *gorm.DB) (interface{}, error) {
var result []Node
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
// 根据节点id获取节点信息
func GetNodeById(id string) *Node {
node := New()
model.DB().Where("id = ?", id).First(node)
return node
}
// 根据节点id批量获取节点信息
func GetNodeByIds(ids []string) []Node {
var nodes []Node
model.DB().Where("id in (?)", ids).Find(&nodes)
return nodes
}
// 根据工作空间(WorkspaceId)计算节点数量
func CountNodeByWorkspaceId(workspaceId string) (int64, error) {
var count int64
tx := model.DB().Table(New().TableName()).Where("workspace_id = ?", workspaceId).Count(&count)
return count, tx.Error
}
// 根据MachineId更新Node的LoginPwd
func UpdateNodeLoginPwdByMachineId(machineId string, loginPwd string) error {
return model.DB().Table(New().TableName()).Where("machine_id = ?", machineId).Update("login_pwd", loginPwd).Error
}

View File

@ -0,0 +1,8 @@
package after_opt
const (
No = 0
Restart = 1
Order_Must_Restart = 2
Order_Restart = 3
)

View File

@ -0,0 +1,39 @@
package out_giving
import (
"errors"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"gorm.io/gorm"
)
type OutGivingExecLog struct {
Id string `gorm:"column:id;primary_key" json:"id"`
LogsId string `gorm:"column:logs_id" json:"logsId"`
}
func (o *OutGivingExecLog) TableName() string {
return "sched_out_giving_instance_log"
}
// 保存一条OutGivingExecLog
func SaveOutGivingExecLog(outGivingExecLog *OutGivingExecLog) error {
return model.DB().Create(outGivingExecLog).Error
}
//// 根据Id查询OutGivingExecLog
//func GetOutGivingExecLogById(id string) (*OutGivingExecLog, error) {
// outGivingExecLog := new(OutGivingExecLog)
// return outGivingExecLog, model.DB().Where("id = ?", id).First(outGivingExecLog).Error
//}
// 根据Id查询OutGivingExecLog
func GetOutGivingExecLogById(id string) (*OutGivingExecLog, error) {
outGivingExecLog := new(OutGivingExecLog)
if err := model.DB().Where("id = ?", id).First(outGivingExecLog).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil // 如果记录不存在返回nil, nil
}
return nil, err // 其他类型的错误仍然返回
}
return outGivingExecLog, nil
}

View File

@ -0,0 +1,94 @@
package out_giving
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"gorm.io/gorm"
"time"
)
// OutGivingLog 项目分发日志
type OutGivingLog struct {
model.BaseWorkspaceModel
OutGivingID string `gorm:"column:out_giving_id;comment:'分发id'" json:"outGivingId"` // 分发id
Status int `gorm:"column:status;comment:'状态'" json:"status"` // 状态
StartTime time.Time `gorm:"column:start_time" json:"startTime"`
EndTime time.Time `gorm:"column:end_time" json:"endTime"`
Result string `gorm:"column:result;comment:'处理消息'" json:"result"` // 处理消息
NodeID string `gorm:"column:node_id;comment:'节点id'" json:"nodeId"` // 节点id
ProjectID string `gorm:"column:project_id;comment:'项目id'" json:"projectId"` // 项目id
FileSize int64 `gorm:"column:file_size;comment:'文件大小'" json:"fileSize"` // 文件大小
ProgressSize int64 `gorm:"column:progress_size;comment:'进度信息'" json:"progressSize"` // 进度信息
// 由于Go不支持泛型且GORM没有直接的表关联继承模型的方式此处省略了直接的“parents = OutGivingModel.class”等Java特定注解的转换。
// 在Go中通常会通过外键或者关联查询来实现类似的关系。
}
func NewOutGivingLog() *OutGivingLog {
n := &OutGivingLog{}
n.Handler = n
return n
}
// TableName 自定义表名
func (o *OutGivingLog) TableName() string {
return "sched_outgiving_log"
}
func (o *OutGivingLog) FindResult(db *gorm.DB) (interface{}, error) {
var result []OutGivingLog
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
func (o *OutGivingLog) Page(page *model.Page[OutGivingLog], fields map[string]interface{}) error {
query := model.DB()
if fields != nil {
//query = query.Where(fields)
for key := range fields {
value := fields[key]
switch value.(type) {
case []string, []int, []int64, []interface{}:
query = query.Where(key+" in (?)", value)
default:
query = query.Where(key+" = ?", value)
}
}
}
err := page.SelectPages(query)
if err != nil {
return err
}
return nil
}
// 根据outGivingId删除OutGivingLog
func DeleteOutGivingLogByOutGivingId(outGivingId string) error {
return model.DB().Table(NewOutGivingLog().TableName()).Where("out_giving_id = ?", outGivingId).Delete(&OutGivingLog{}).Error
}
// 批量保存OutGivingLog
func SaveOutGivingLog(outGivingLogs []OutGivingLog) error {
return model.DB().Create(&outGivingLogs).Error
}
// 更新OutGivingLog
func UpdateOutGivingLog(outGivingLog *OutGivingLog) error {
return model.DB().Model(outGivingLog).Updates(outGivingLog).Error
}
func CountOutGivingLogByWorkspaceId(workspaceId string) (int64, error) {
var result int64
tx := model.DB().Table(NewOutGivingLog().TableName()).Where("workspace_id = ?", workspaceId).Count(&result)
if tx.Error != nil {
return 0, tx.Error
}
return result, nil
}
// DeleteOutGivingLogByWorkspaceId
func DeleteOutGivingLogByWorkspaceId(workspaceId string) error {
return model.DB().Table(NewOutGivingLog().TableName()).Where("workspace_id = ?", workspaceId).Delete(&OutGivingLog{}).Error
}

View File

@ -0,0 +1,305 @@
package out_giving
import (
"encoding/json"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/file_utils"
"gorm.io/gorm"
"sort"
"strings"
)
const (
// 不做任何操作
No = 0
// 并发重启
Restart = 1
// 完整顺序重启(有重启失败将结束本次)
Order_Must_Restart = 2
// 顺序重启(有重启失败将继续)
Order_Restart = 3
)
// OutGivingModel 分发实体
// 单文件分发或单压缩包分发
type OutGivingModel struct {
model.BaseWorkspaceModel
Group *string `gorm:"column:group" json:"group"`
/**
* 名称
*/
Name string `gorm:"column:name" json:"name"`
/**
* 分发间隔时间
*/
IntervalTime int `gorm:"column:interval_time" json:"intervalTime"`
/**
* 节点下的项目列表
*/
OutGivingNodeProjectList string `gorm:"column:out_giving_node_project_list" json:"outGivingNodeProjectList"`
/**
* 分发后的操作
*/
AfterOpt int `gorm:"column:after_opt" json:"afterOpt"`
/**
* 是否清空旧包发布
*/
ClearOld bool `gorm:"column:clear_old" json:"clearOld"`
/**
* 是否为单独创建的分发项目
*/
OutGivingProject bool `gorm:"column:out_giving_project" json:"outGivingProject"`
/**
* 状态
*/
Status int `gorm:"column:status" json:"status"`
/**
* 二级目录LevelName
*/
SecondaryDirectory *string `gorm:"column:secondary_directory" json:"secondaryDirectory"`
/**
* 保存项目文件前先关闭
*/
UploadCloseFirst bool `gorm:"column:upload_close_first" json:"uploadCloseFirst"`
/**
* 状态消息
*/
StatusMsg string `gorm:"column:status_msg" json:"statusMsg"`
/**
* 构建发布状态通知
*/
Webhook string `gorm:"column:webhook" json:"webhook"`
}
// TableName sets the table name for GORM
func (*OutGivingModel) TableName() string {
return "sched_out_giving"
}
func NewOutGivingModel() *OutGivingModel {
n := &OutGivingModel{}
n.Handler = n
return n
}
func (o *OutGivingModel) FindResult(db *gorm.DB) (interface{}, error) {
var result []OutGivingModel
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
func (o *OutGivingModel) Page(page *model.Page[OutGivingModel], fields map[string]interface{}) error {
query := model.DB()
if fields != nil {
//query = query.Where(fields)
for key := range fields {
value := fields[key]
switch value.(type) {
case []string, []int, []int64, []interface{}:
query = query.Where(key+" in (?)", value)
default:
query = query.Where(key+" = ?", value)
}
}
}
err := page.SelectPages(query)
if err != nil {
return err
}
return nil
}
// 查询所有分发模型
func ListOutGivingModel() []OutGivingModel {
var outGivingModel []OutGivingModel
model.DB().Table(NewOutGivingModel().TableName()).Find(&outGivingModel)
return outGivingModel
}
// 根据Id 查询OutGivingModel
func GetOutGivingModelById(id string) *OutGivingModel {
var outGivingModel OutGivingModel
model.DB().Table(NewOutGivingModel().TableName()).Where("id = ?", id).First(&outGivingModel)
return &outGivingModel
}
// 根据Id和WorkspaceId删除OutGivingModel,并返回受影响行数
func DeleteOutGivingModelById(id string) (int64, error) {
tx := model.DB().Table(NewOutGivingModel().TableName()).Where("id = ?", id).Delete(&OutGivingModel{})
return tx.RowsAffected, tx.Error
}
// 更新分发模型
func UpdateOutGivingModel(outGivingModel *OutGivingModel, updateFields []string) error {
return model.DB().Table(NewOutGivingModel().TableName()).Where("id = ?", outGivingModel.Id).Select(updateFields).Updates(outGivingModel).Error
}
// ListByWorkspace 根据Workspace查询OutGivingModel
func ListOutGivingModelByWorkspace(workspaceId string) []OutGivingModel {
var outGivingModel []OutGivingModel
model.DB().Table(NewOutGivingModel().TableName()).Where("workspace_id = ?", workspaceId).Find(&outGivingModel)
return outGivingModel
}
// 更新分发模型将状态为out_giving.DONE的状态改为out_giving.ING
func UpdateOutGivingModelStatus(oldStatus int, newStatus int) (int64, error) {
updateSql := `UPDATE ` + NewOutGivingModel().TableName() + ` SET status=? WHERE status=?`
tx := model.DB().Table(NewOutGivingModel().TableName()).Exec(updateSql, newStatus, oldStatus)
return tx.RowsAffected, tx.Error
}
// 指定workspaceId, nodeId计算符合条件的项目数
func ExistsOutGivingModel(workspaceId, nodeId string) (bool, error) {
var result int64
tx := model.DB().Table(NewOutGivingModel().TableName()).Where("workspace_id = ? and node_id = ?", workspaceId, nodeId).Count(&result)
if tx.Error != nil {
return false, tx.Error
}
return result > 0, nil
}
func CountOutGivingModelByWorkspaceId(workspaceId string) (int64, error) {
var result int64
tx := model.DB().Table(NewOutGivingModel().TableName()).Where("workspace_id = ?", workspaceId).Count(&result)
if tx.Error != nil {
return 0, tx.Error
}
return result, nil
}
// ****************************************
// SetSecondaryDirectory validates and sets the secondary directory
func (o *OutGivingModel) SetSecondaryDirectory(secondaryDirectory string) error {
if secondaryDirectory != "" {
err := file_utils.CheckFilePathSlip(secondaryDirectory)
if err != nil {
return fmt.Errorf("二级目录不能越级: %w", err)
}
}
space := strings.TrimSpace(secondaryDirectory)
o.SecondaryDirectory = &space
return nil
}
// GetOutGivingNodeProjectList converts the list string to a slice of OutGivingNodeProject
func (o *OutGivingModel) GetOutGivingNodeProjectList() []OutGivingNodeProject {
return o.GetOutGivingNodeProjectListBySelect("")
}
// GetOutGivingNodeProjectListBySelect filters and sorts the list
func (o *OutGivingModel) GetOutGivingNodeProjectListBySelect(selectStr string) []OutGivingNodeProject {
var outGivingNodeProjects []OutGivingNodeProject
bytes := []byte(o.OutGivingNodeProjectList)
err := json.Unmarshal(bytes, &outGivingNodeProjects)
if err != nil {
return outGivingNodeProjects
}
if outGivingNodeProjects == nil {
return outGivingNodeProjects
}
list := strings.Split(strings.TrimSpace(selectStr), ",")
for i := range outGivingNodeProjects {
if outGivingNodeProjects[i].SortValue != nil {
if outGivingNodeProjects[i].SortValue == nil {
outGivingNodeProjects[i].SortValue = &i
}
}
}
filteredProjects := filterAndSortProjects(outGivingNodeProjects, list)
return filteredProjects
}
// filterAndSortProjects filters projects based on the selection and sorts them
func filterAndSortProjects(projects []OutGivingNodeProject, selection []string) []OutGivingNodeProject {
if len(selection) == 0 {
return projects
}
selectedProjects := projects[:0]
for _, project := range projects {
projectKey := fmt.Sprintf("%s@%s", project.ProjectId, project.NodeId)
if contains(selection, projectKey) {
selectedProjects = append(selectedProjects, project)
}
}
//sortedProjects := sortProjects(selectedProjects)
return selectedProjects
}
// contains checks if the selection contains the given key
func contains(selection []string, key string) bool {
for _, s := range selection {
if s == "" {
return true
}
if s == key {
return true
}
}
return false
}
// sortProjects sorts projects by their sort value
func sortProjects(projects []OutGivingNodeProject) []OutGivingNodeProject {
sort.SliceStable(projects, func(i, j int) bool {
return *projects[i].SortValue < *projects[j].SortValue
})
return projects
}
// SetOutGivingNodeProjectList updates the list string from a slice of OutGivingNodeProject
func (o *OutGivingModel) SetOutGivingNodeProjectList(outGivingNodeProjects []OutGivingNodeProject) {
if outGivingNodeProjects == nil {
o.OutGivingNodeProjectList = ""
} else {
marshal, err := json.Marshal(outGivingNodeProjects)
if err != nil {
o.OutGivingNodeProjectList = ""
return
}
o.OutGivingNodeProjectList = string(marshal)
}
}
// CheckContains checks if the model contains a project with the given node and project IDs
// 判断是否包含某个项目id
func (o *OutGivingModel) CheckContains(nodeId, projectId string) bool {
nodeProjectList := o.GetOutGivingNodeProjectList()
project := o.GetNodeProject(nodeProjectList, nodeId, projectId)
return project != nil
}
// GetNodeProject gets a project from the list with the given node and project IDs
// 从指定数组中获取对应信息
func (o *OutGivingModel) GetNodeProject(projects []OutGivingNodeProject, nodeId, projectId string) *OutGivingNodeProject {
for _, project := range projects {
if strings.EqualFold(project.NodeId, nodeId) && strings.EqualFold(project.ProjectId, projectId) {
return &project
}
}
return nil
}
// GetDelete compares the old and new projects and returns deleted ones
func (o *OutGivingModel) GetDelete(newProjects []OutGivingNodeProject) []OutGivingNodeProject {
oldProjects := o.GetOutGivingNodeProjectList()
if oldProjects == nil || len(oldProjects) == 0 {
return nil
}
var deletedProjects []OutGivingNodeProject
// 如果在newsProject中不存在则将该节点项目添加到delete列表中
for _, oldProject := range oldProjects {
if o.GetNodeProject(newProjects, oldProject.NodeId, oldProject.ProjectId) != nil {
continue
}
deletedProjects = append(deletedProjects, oldProject)
}
return deletedProjects
}

View File

@ -0,0 +1,12 @@
package out_giving
import (
"fmt"
"strings"
"testing"
)
func TestOutGivingModel_GetOutGivingNodeProjectListBySelect(t *testing.T) {
list := strings.Split("", ",")
fmt.Print(list)
}

View File

@ -0,0 +1,53 @@
package out_giving
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"gorm.io/gorm"
)
// OutGivingNodeProject 结构体扩展了BaseNodeProject包含了排序值和是否禁用的字段
type OutGivingNodeProject struct {
NodeId string `json:"nodeId"`
ProjectId string `json:"projectId"`
SortValue *int `json:"sortValue"`
Disabled bool `json:"disabled,omitempty"`
}
// GetDisabled 返回默认禁用状态如果Disabled为nil则返回false
func (o *OutGivingNodeProject) GetDisabled() bool {
// 如果Disabled为nil则返回false其他情况返回Disabled的值
if o.Disabled != true {
return false
}
return o.Disabled
}
func (o *OutGivingNodeProject) FindResult(db *gorm.DB) (interface{}, error) {
var result []OutGivingNodeProject
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
func (o *OutGivingNodeProject) Page(page *model.Page[OutGivingNodeProject], fields map[string]interface{}) error {
query := model.DB()
if fields != nil {
//query = query.Where(fields)
for key := range fields {
value := fields[key]
switch value.(type) {
case []string, []int, []int64, []interface{}:
query = query.Where(key+" in (?)", value)
default:
query = query.Where(key+" = ?", value)
}
}
}
err := page.SelectPages(query)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,23 @@
package status
// Status 枚举表示状态
type Status int
const (
// NO 未分发
NO = 0
// ING 分发中
ING = 1
// 分发成功
Ok = 2
// DONE 分发结束
DONE = 2
// CANCEL 已取消 // 系统取消分发
CANCEL = 3
// FAIL 分发失败
FAIL = 4
// 准备分发
Prepare = 5
// 手动取消分发
ArtificialCancel = 6
)

View File

@ -0,0 +1,160 @@
package project_info
import (
"database/sql"
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"github.com/araujo88/lambda-go/pkg/core"
"gorm.io/gorm"
)
type ProjectInfo struct {
model.BaseWorkspaceModel
ProjectId string `gorm:"column:project_id" json:"projectId"`
NodeId string `gorm:"column:node_id" json:"nodeId"`
Name string `gorm:"column:name" json:"name"`
Lib string `gorm:"column:lib" json:"lib"`
WhitelistDirectory string `gorm:"column:whitelist_directory" json:"whitelistDirectory"`
LogPath string `gorm:"column:log_path" json:"logPath"`
Jvm string `gorm:"column:jvm" json:"jvm"`
Args string `gorm:"column:args" json:"args"`
Token string `gorm:"column:token" json:"token"`
RunMode string `gorm:"column:run_mode" json:"runMode"`
OutGivingProject bool `gorm:"column:out_giving_project" json:"outGivingProject"`
//SortValue string `gorm:"column:sort_value" json:"sortValue"`
TriggerToken string `gorm:"column:trigger_token" json:"triggerToken"`
Group *string `gorm:"column:group" json:"group"`
DslContent string `gorm:"column:dsl_content" json:"dslContent"`
DslEnv string `gorm:"column:dsl_env" json:"dslEnv"`
AutoStart bool `gorm:"column:auto_start" json:"autoStart"`
NodeName string `gorm:"column:node_name" json:"nodeName"`
WorkspaceName string `gorm:"column:workspace_name" json:"workspaceName"`
}
func New() *ProjectInfo {
n := &ProjectInfo{}
n.Handler = n
return n
}
func (*ProjectInfo) TableName() string {
return "sched_project_info"
}
// 删除所有项目
func ClearAll() (int64, error) {
return model.DB().Where("1 = 1").Delete(&ProjectInfo{}).RowsAffected, nil
}
// 根据Id删除项目
func DeleteProjectById(id string) (int64, error) {
return model.DB().Where("id = ?", id).Delete(&ProjectInfo{}).RowsAffected, nil
}
// 根据Id查询项目
func GetProjectById(id string) (ProjectInfo, error) {
var project ProjectInfo
err := model.DB().Where("id = ?", id).First(&project).Error
return project, err
}
// 根据Id批量查询项目
func GetProjectByIds(ids []string) []ProjectInfo {
var projects []ProjectInfo
model.DB().Where("id in (?)", ids).Find(&projects)
return projects
}
// 根据nodeId和workspace_id删除项目
func DeleteProjectByNodeIdAndWorkspaceId(nodeId, workspaceId string) (int64, error) {
return model.DB().Where("node_id = ? and workspace_id = ?", nodeId, workspaceId).Delete(&ProjectInfo{}).RowsAffected, nil
}
// 根据nodeId删除项目
func DeleteProjectByNodeId(nodeId string) (int64, error) {
return model.DB().Where("node_id = ?", nodeId).Delete(&ProjectInfo{}).RowsAffected, nil
}
// 根据nodeId查询项目
func GetProjectByNodeId(nodeId string) ([]ProjectInfo, error) {
var result []ProjectInfo
err := model.DB().Table(New().TableName()).Table(New().TableName()).Where("node_id = ?", nodeId).Find(&result).Error
if err != nil {
return nil, err
}
return result, nil
}
// 指定workspaceId, nodeId计算符合条件的项目数
func Exists_1(workspaceId, nodeId string) (bool, error) {
var result int64
tx := model.DB().Table(New().TableName()).Where("workspace_id = ? and node_id = ?", workspaceId, nodeId).Count(&result)
if tx.Error != nil {
return false, tx.Error
}
return result > 0, nil
}
// 指定workspaceId, nodeId, projectId计算符合条件的项目数
func Exists(workspaceId, nodeId, projectId string) (bool, error) {
var result int64
tx := model.DB().Table(New().TableName()).Where("workspace_id = ? and node_id = ? and project_id = ?", workspaceId, nodeId, projectId).Count(&result)
if tx.Error != nil {
return false, tx.Error
}
return result > 0, nil
}
// 指定Id计算符合条件的项目数
func ExistsById(id string) (bool, error) {
var result int64
tx := model.DB().Table(New().TableName()).Where("id = ?", id).Count(&result)
if tx.Error != nil {
return false, tx.Error
}
return result > 0, nil
}
func (p *ProjectInfo) ListGroup() ([]string, error) {
var result []sql.NullString
tx := model.DB().Table(p.TableName()).Select("\"group\"").Distinct().Scan(&result)
if len(result) > 0 {
us := core.Map(result, func(s sql.NullString) string {
if s.Valid {
return s.String
}
return ""
})
return us, tx.Error
}
return []string{}, tx.Error
}
func (p *ProjectInfo) CreateOrUpdate() (err error) {
_, err = p.Create(p)
if err != nil {
fmt.Println(err.Error())
_, err = p.Update(p, nil)
}
return err
}
func (p *ProjectInfo) FindResult(db *gorm.DB) (interface{}, error) {
var result []ProjectInfo
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
// 根据工作空间(WorkspaceId)计算项目数量
func CountProjectByWorkspaceId(workspaceId string) (int64, error) {
var result int64
tx := model.DB().Table(New().TableName()).Where("workspace_id = ?", workspaceId).Count(&result)
if tx.Error != nil {
return 0, tx.Error
}
return result, nil
}

View File

@ -0,0 +1,136 @@
package script_info
import (
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"gorm.io/gorm"
)
// 脚本模版实体 对应NodeScriptCacheModel
type NodeScriptCacheModel struct {
model.BaseWorkspaceModel
ScriptId string `gorm:"column:script_id" json:"scriptId"`
Name string `gorm:"column:name" json:"name"`
LastRunUser string `gorm:"column:last_run_user" json:"lastRunUser"`
AutoExecCron string `gorm:"column:auto_exec_cron" json:"autoExecCron"`
DefArgs string `gorm:"column:def_args" json:"defArgs"`
Description string `gorm:"column:description" json:"description"`
ScriptType string `gorm:"column:script_type" json:"scriptType"`
TriggerToken string `gorm:"column:trigger_token" json:"trigger_token"`
NodeId string `gorm:"column:node_id" json:"nodeId"`
NodeName string `gorm:"column:node_name" json:"nodeName"`
WorkspaceName string `gorm:"column:workspace_name" json:"workspaceName"`
}
func NewNodeScriptCacheModel() *NodeScriptCacheModel {
n := &NodeScriptCacheModel{}
n.Handler = n
return n
}
func (*NodeScriptCacheModel) TableName() string {
return "sched_script_info"
}
func (s *NodeScriptCacheModel) ListGroup() ([]string, error) {
var result []string
tx := model.DB().Table(s.TableName()).Select("\"group\"").Distinct().Scan(&result)
return result, tx.Error
}
func (s *NodeScriptCacheModel) CreateOrUpdate() (err error) {
_, err = s.Create(s)
if err != nil {
fmt.Println(err.Error())
_, err = s.Update(s, nil)
}
return err
}
// 根据Id删除脚本
func DeleteNodeScriptById(id string) (int64, error) {
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).Where("id = ?", id).Delete(&NodeScriptCacheModel{})
return tx.RowsAffected, tx.Error
}
// 根据nodeId删除脚本
func DeleteNodeScriptByNodeId(nodeId string) (int64, error) {
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).Where("node_id = ?", nodeId).Delete(&NodeScriptCacheModel{})
return tx.RowsAffected, tx.Error
}
// 根据nodeId查询脚本
func GetNodeScriptByNodeId(nodeId string) ([]NodeScriptCacheModel, error) {
var result []NodeScriptCacheModel
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).Where("node_id = ?", nodeId).Find(&result)
return result, tx.Error
}
// 根据Id查询脚本
func GetNodeScriptById(id string) (NodeScriptCacheModel, error) {
var result NodeScriptCacheModel
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).Where("id = ?", id).First(&result)
return result, tx.Error
}
// 根据IdworkspaceId删除脚本
func DeleteNodeScriptByIdAndWorkspaceId(id, workspaceId string) (int64, error) {
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).Where("id = ? and workspace_id = ?", id, workspaceId).Delete(&NodeScriptCacheModel{})
return tx.RowsAffected, tx.Error
}
// 根据NodeId和workspaceId删除脚本
func DeleteNodeScriptByNodeIdAndWorkspaceId(nodeId, workspaceId string) (int64, error) {
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).Where("node_id = ? and workspace_id = ?", nodeId, workspaceId).Delete(&NodeScriptCacheModel{})
return tx.RowsAffected, tx.Error
}
// 删除所有脚本
func DeleteAllNodeScript() (int64, error) {
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).Where("1 = 1").Delete(&NodeScriptCacheModel{})
return tx.RowsAffected, tx.Error
}
// 批量保存脚本
func SaveNodeScript(scripts []NodeScriptCacheModel) (int64, error) {
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).CreateInBatches(scripts, 100)
return tx.RowsAffected, tx.Error
}
func (s *NodeScriptCacheModel) FindResult(db *gorm.DB) (interface{}, error) {
var result []NodeScriptCacheModel
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
func (s *NodeScriptCacheModel) HasScriptNode() ([]string, error) {
var nodeIds = make([]string, 0)
tx := model.DB().Table(s.TableName()).Select("node_id").Distinct().Scan(&nodeIds)
if tx.Error != nil {
return nodeIds, tx.Error
}
return nodeIds, nil
}
// 指定workspaceId, nodeId计算符合条件的项目数
func ExistsNodeScriptCacheModel(workspaceId, nodeId string) (bool, error) {
var result int64
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).Where("workspace_id = ? and node_id = ?", workspaceId, nodeId).Count(&result)
if tx.Error != nil {
return false, tx.Error
}
return result > 0, nil
}
func CountNodeScriptByWorkspaceId(workspaceId string) (int64, error) {
var result int64
tx := model.DB().Table(NewNodeScriptCacheModel().TableName()).Where("workspace_id = ?", workspaceId).Count(&result)
if tx.Error != nil {
return 0, tx.Error
}
return result, nil
}

View File

@ -0,0 +1,148 @@
package script_info
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/secure"
"gorm.io/gorm"
"strings"
)
// NodeScriptExecuteLogCacheModel 结构体
type NodeScriptExecuteLogCacheModel struct {
model.BaseNodeModel // 继承自 BaseNodeModel
// 使用 GORM 标签映射字段到数据库列
Name string `gorm:"-" json:"name"` // 不会映射到数据库因为有PropIgnore注解
ScriptId string `gorm:"column:script_id" json:"scriptId"`
ScriptName string `gorm:"column:script_name" json:"scriptName"`
// 触发类型 {0手动1 自动触发}
TriggerExecType int `gorm:"column:trigger_exec_type" json:"triggerExecType"`
}
// 实现 fullId 方法
func (n *NodeScriptExecuteLogCacheModel) fullId() string {
encrypt := secure.Sha1Encrypt(n.WorkspaceId + n.NodeId + n.ScriptId)
return encrypt
}
// SetName 方法
func (n *NodeScriptExecuteLogCacheModel) SetName(name string) {
n.Name = name
n.ScriptName = name
}
// SetScriptName 方法
func (n *NodeScriptExecuteLogCacheModel) SetScriptName(scriptName string) {
n.ScriptName = scriptName
n.Name = scriptName
}
func NewNodeScriptExecuteLogCacheModel() *NodeScriptExecuteLogCacheModel {
n := &NodeScriptExecuteLogCacheModel{}
n.Handler = n
return n
}
func (*NodeScriptExecuteLogCacheModel) TableName() string {
return "sched_script_execute_log"
}
func (n *NodeScriptExecuteLogCacheModel) FindResult(db *gorm.DB) (interface{}, error) {
var result []NodeScriptExecuteLogCacheModel
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
// Page 可以使用类似 {"age >": 30} 这样的字段映射来过滤年龄大于30的结果
func (n *NodeScriptExecuteLogCacheModel) Page(page *model.Page[NodeScriptExecuteLogCacheModel], fields map[string]interface{}) error {
query := model.DB()
if fields != nil {
for key := range fields {
value := fields[key]
switch value.(type) {
case []string, []int, []int64, []interface{}:
query = query.Where(key+" in (?)", value)
default:
// 支持模糊查询
if strings.Contains(key, "%") {
kk := strings.ReplaceAll(key, "%", "")
query = query.Where(kk+" like ?", "%"+value.(string)+"%")
continue
}
// 检查字段是否包含操作符
if strings.Contains(key, ">") || strings.Contains(key, "<") {
// 分割字段名和操作符
parts := strings.Split(key, " ")
if len(parts) == 2 {
field := parts[0]
op := parts[1]
switch op {
case ">":
query = query.Where(field+" > ?", value)
case "<":
query = query.Where(field+" < ?", value)
default:
query = query.Where(key+" = ?", value)
}
} else {
query = query.Where(key+" = ?", value)
}
} else {
query = query.Where(key+" = ?", value)
}
}
}
}
err := page.SelectPages(query)
if err != nil {
return err
}
return nil
}
// 根据ids批量删除NodeScriptExecuteLogCacheModel
func DeleteNodeScriptExecuteLogByIds(ids []string) (int64, error) {
return model.DB().Where("id in (?)", ids).Delete(&NodeScriptExecuteLogCacheModel{}).RowsAffected, nil
}
// 根据Id和 []workspace 数组获取脚本
func GetNodeScriptExecuteLogByIdAndWorkspaceIds(id string, workspaceIds []string) (NodeScriptExecuteLogCacheModel, error) {
var script NodeScriptExecuteLogCacheModel
err := model.DB().Where("id = ? and workspace_id in (?)", id, workspaceIds).First(&script).Error
return script, err
}
// 根据Id workspaceId删除脚本
func DeleteNodeScriptExecuteLogByIdAndWorkspaceIds(id string, workspaceIds []string) (int64, error) {
var script NodeScriptExecuteLogCacheModel
tx := model.DB().Where("id = ? and workspace_id in (?)", id, workspaceIds).Delete(&script)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
// 根据NodeId workspaceId删除脚本
func DeleteNodeScriptExecuteLogByNodeIdAndWorkspaceIds(nodeId string, workspaceIds []string) (int64, error) {
var script NodeScriptExecuteLogCacheModel
tx := model.DB().Where("node_id = ? and workspace_id in (?)", nodeId, workspaceIds).Delete(&script)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
func CountNodeScriptExecuteLogByWorkspaceId(workspaceId string) (int64, error) {
var result int64
tx := model.DB().Table(NewNodeScriptExecuteLogCacheModel().TableName()).Where("workspace_id = ?", workspaceId).Count(&result)
if tx.Error != nil {
return 0, tx.Error
}
return result, nil
}

View File

@ -0,0 +1,154 @@
package script_info
import (
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/command_utils"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/utils"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/utils/file_utils"
"github.com/google/uuid"
"gorm.io/gorm"
"path/filepath"
)
// ScriptModel represents the script template model in the database.
type ScriptModel struct {
model.BaseWorkspaceModel
Name string `gorm:"column:name" json:"name"`
LastRunUser string `gorm:"column:last_run_user" json:"lastRunUser"`
AutoExecCron string `gorm:"column:auto_exec_cron" json:"autoExecCron"`
DefArgs string `gorm:"column:def_args" json:"defArgs"`
Description string `gorm:"column:description" json:"description"`
Context string `gorm:"column:context" json:"context"`
NodeIds string `gorm:"column:node_ids" json:"nodeIds"`
TriggerToken string `gorm:"column:trigger_token" json:"triggerToken"`
}
func NewScriptModel() *ScriptModel {
n := &ScriptModel{}
n.Handler = n
return n
}
func (*ScriptModel) TableName() string {
return "sched_server_script_info"
}
func (s *ScriptModel) FindResult(db *gorm.DB) (interface{}, error) {
var result []ScriptModel
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
func (s *ScriptModel) Page(page *model.Page[ScriptModel], fields map[string]interface{}) error {
query := model.DB()
if fields != nil {
for key := range fields {
value := fields[key]
switch value.(type) {
case []string, []int, []int64, []interface{}:
query = query.Where(key+" in (?)", value)
default:
query = query.Where(key+" = ?", value)
}
}
}
err := page.SelectPages(query)
if err != nil {
return err
}
return nil
}
// 根据查询条件获取单个脚本(First),如果没有则返回空
func GetServerScriptModel(fields map[string]interface{}) (ScriptModel, error) {
var script ScriptModel
err := model.DB().Where(fields).First(&script).Error
return script, err
}
// 根据Id和获取脚本
func GetServerScriptModelById(id string) (ScriptModel, error) {
var script ScriptModel
err := model.DB().Where("id = ?", id).First(&script).Error
return script, err
}
// 根据Id和 []workspace 数组获取脚本
func GetServerScriptModelByIdAndWorkspaceIds(id string, workspaceIds []string) (ScriptModel, error) {
var script ScriptModel
err := model.DB().Where("id = ? and workspace_id in (?)", id, workspaceIds).First(&script).Error
return script, err
}
// 根据Id和 []workspace 数组删除脚本
func DeleteServerScriptByIdAndWorkspaceIds(id string, workspaceIds []string) (int64, error) {
var script ScriptModel
tx := model.DB().Where("id = ? and workspace_id in (?)", id, workspaceIds).Delete(&script)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
// 根据Id更新脚本
func UpdateServerScriptById(id string, fields map[string]interface{}) (int64, error) {
tx := model.DB().Model(&ScriptModel{}).Where("id = ?", id).Updates(fields)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
func CountScriptByWorkspaceId(workspaceId string) (int64, error) {
var result int64
tx := model.DB().Table(NewScriptModel().TableName()).Where("workspace_id = ?", workspaceId).Count(&result)
if tx.Error != nil {
return 0, tx.Error
}
return result, nil
}
/***********************************************/
// ScriptPath generates the script's path based on its ID.
func (s *ScriptModel) ScriptPath() (string, error) {
// Implement your logic to get the script path based on your application's setup.
// JpomApplication and other dependencies would need to be replaced with Go equivalents.
if s.Id == "" {
return "", fmt.Errorf("id 为空")
}
path, err := utils.GetDataPath()
if err != nil {
return "", fmt.Errorf("获取数据目录失败: %v", err)
}
scriptPath := filepath.Join(path, "script", s.Id)
// Return a hypothetical path for demonstration purposes.
return scriptPath, nil
}
// LogFilePath generates the log file's path given the script ID and execution ID.
func (s *ScriptModel) LogFilePath(executeId string) (string, error) {
scriptFolderPath, err := s.ScriptPath()
if err != nil {
return "", err
}
scriptLogFilePath := filepath.Join(scriptFolderPath, "log", executeId) + ".log"
return scriptLogFilePath, nil
}
func (s *ScriptModel) ScriptFile() (string, error) {
dataPath, _ := utils.GetDataPath()
fileName := fmt.Sprintf("%s.%s", uuid.New().String(), command_utils.SUFFIX)
scriptFile := filepath.Join(dataPath, "script_run_cache", fileName)
err := file_utils.WriteScript(s.Context, scriptFile)
if err != nil {
return "", err
}
return scriptFile, nil
}

View File

@ -0,0 +1,33 @@
package server_white_list
import "path/filepath"
type ServerWhitelist struct {
OutGiving string `json:"outGiving"`
//AllowRemoteDownloadHost []string `json:"allowRemoteDownloadHost"`
//StaticDir []string `json:"staticDir"`
}
func GetId(workspaceId string) string {
return "OUTGIVING_WHITELIST-" + workspaceId
}
// CheckPath 用于判断给定路径是否在白名单列表中
func CheckPath(list []string, path string) bool {
if list == nil || len(list) == 0 {
return false
}
if path == "" {
return false
}
normalizedPath := filepath.Clean(path)
for _, item := range list {
normalizedItem := filepath.Clean(item)
if normalizedPath == normalizedItem {
return true
}
}
return false
}

View File

@ -0,0 +1,36 @@
package system_parameters
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
)
type SystemParameters struct {
model.BaseUserModifyDbModel
Value string `gorm:"column:value" json:"value"`
Description string `gorm:"column:description" json:"description"`
}
func (*SystemParameters) TableName() string {
return "sched_system_parameters"
}
func (w *SystemParameters) Page(page *model.Page[SystemParameters], fields map[string]interface{}) error {
query := model.DB()
if fields != nil {
//query = query.Where(fields)
for key := range fields {
value := fields[key]
switch value.(type) {
case []string, []int, []int64, []interface{}:
query = query.Where(key+" in (?)", value)
default:
query = query.Where(key+" = ?", value)
}
}
}
err := page.SelectPages(query)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,71 @@
package workspace
import (
"database/sql"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"github.com/araujo88/lambda-go/pkg/core"
"gorm.io/gorm"
)
type Workspace struct {
model.BaseUserModifyDbModel
Name string `gorm:"column:name" json:"name"` //type:string comment:名称 version:2024-02-12 14:22
Description string `gorm:"column:description" json:"description"` //type:string comment:描述 version:2024-02-12 14:22
Group *string `gorm:"column:group" json:"group"` //type:string comment:分组 version:2024-02-12 14:22
}
func New() *Workspace {
w := &Workspace{}
w.Handler = w
return w
}
// 说明:
func (*Workspace) TableName() string {
return "sched_workspace"
}
func (w *Workspace) ListGroup() ([]string, error) {
var result []sql.NullString
tx := model.DB().Table(w.TableName()).Select("\"group\"").Distinct().Scan(&result)
if len(result) > 0 {
us := core.Map(result, func(s sql.NullString) string {
if s.Valid {
return s.String
}
return ""
})
return us, tx.Error
}
return []string{}, tx.Error
}
func (w *Workspace) FindResult(db *gorm.DB) (interface{}, error) {
var result []Workspace
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
// 根据Id判断是否存在
func (w *Workspace) ExistById(id string) (bool, error) {
var count int64
err := model.DB().Table(w.TableName()).Where("id = ?", id).Count(&count).Error
return count > 0, err
}
// 根据Id获取环境变量
func GetWorkspaceById(id string) (*Workspace, error) {
var workspace Workspace
err := model.DB().Table(workspace.TableName()).Where("id = ?", id).First(&workspace).Error
return &workspace, err
}
// 根据Id列表获取环境变量
func GetWorkspaceByIds(ids []string) ([]Workspace, error) {
var workspaces []Workspace
err := model.DB().Table(New().TableName()).Where("id in (?)", ids).Find(&workspaces).Error
return workspaces, err
}

View File

@ -0,0 +1,54 @@
package workspace_env
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"gorm.io/gorm"
)
type WorkspaceEnvVar struct {
model.BaseWorkspaceModel
Name string `gorm:"column:name" json:"name"`
Description string `gorm:"column:description" json:"description"`
Value string `gorm:"column:value" json:"value"`
NodeIds string `gorm:"column:node_ids" json:"nodeIds"`
Privacy int `gorm:"column:privacy" json:"privacy"`
TriggerToken string `gorm:"column:trigger_token" json:"triggerToken"`
}
func New() *WorkspaceEnvVar {
w := &WorkspaceEnvVar{}
w.Handler = w
return w
}
func (*WorkspaceEnvVar) TableName() string {
return "sched_workspace_env_var"
}
func (w *WorkspaceEnvVar) FindResult(db *gorm.DB) (interface{}, error) {
var result []WorkspaceEnvVar
if db == nil {
return result, nil
}
err := db.Find(&result).Error
return result, err
}
// 根据工作空间(WorkspaceId)计算环境变量数量
func CountWorkspaceEnvVarByWorkspaceId(workspaceId string) (int64, error) {
var result int64
tx := model.DB().Table(New().TableName()).Where("workspace_id = ?", workspaceId).Count(&result)
if tx.Error != nil {
return 0, tx.Error
}
return result, nil
}
// 根据工作空间(WorkspaceId)删除环境变量
func DeleteWorkspaceEnvVarByWorkspaceId(workspaceId string) (int64, error) {
tx := model.DB().Table(New().TableName()).Where("workspace_id = ?", workspaceId).Delete(nil)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}

View File

@ -0,0 +1,342 @@
package model
import (
"errors"
c "git.inspur.com/sbg-jszt/cfn/cfn-schedule/config"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/data"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/request"
"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"gorm.io/plugin/soft_delete"
"strings"
"time"
)
type BaseIdModel struct {
Handler ResultHandler `gorm:"-" json:"-"`
Id string `gorm:"column:id;not null;primarykey" json:"id"`
}
type ResultHandler interface {
FindResult(*gorm.DB) (interface{}, error)
}
func (b *BaseIdModel) Create(t schema.Tabler) (int64, error) {
b.GenerateId()
tx := DB().Table(t.TableName()).Create(t)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
func (b *BaseIdModel) Update(t schema.Tabler, column []string) (int64, error) {
if b.Id == "" {
return 0, errors.New("id cannot be null")
}
db := DB().Table(t.TableName())
if column != nil && len(column) > 0 {
db = db.Select(column)
}
tx := db.Updates(t)
return tx.RowsAffected, tx.Error
}
func (b *BaseIdModel) GetById(t schema.Tabler) error {
if b.Id == "" {
return errors.New("id cannot be null")
}
tx := DB().Table(t.TableName()).Take(t)
return tx.Error
}
func (b *BaseIdModel) Delete(t schema.Tabler) (int64, error) {
if b.Id == "" {
return 0, errors.New("id cannot be null")
}
tx := DB().Table(t.TableName()).Delete(t)
return tx.RowsAffected, tx.Error
}
func (b *BaseIdModel) GetAll(t schema.Tabler, fields map[string]interface{}, notFields map[string]interface{}) (interface{}, error) {
query := DB().Table(t.TableName())
if fields != nil {
for key := range fields {
value := fields[key]
switch value.(type) {
case []string, []int, []int64, []interface{}:
query = query.Where(key+" in (?)", value)
default:
query = query.Where(key+" = ?", value)
}
}
}
if notFields != nil {
for key := range notFields {
value := notFields[key]
if items, ok := value.([]interface{}); ok {
if len(items) > 0 {
query = query.Where(key+" not in (?)", items)
} else {
query = query.Where(key+" <> ?", items[0])
}
} else {
query = query.Where(key+" <> ?", value)
}
}
//query = query.Where(fields)
}
return b.Handler.FindResult(query)
}
// IN db.Where("name in (?)", []string{"jinzhu", "jinzhu 2"}).Find(&users)
func (b *BaseIdModel) Page(t schema.Tabler, page *PageConfig, fields map[string]interface{}) (interface{}, error) {
query := DB()
if fields != nil {
//query = query.Where(fields)
for key := range fields {
value := fields[key]
switch value.(type) {
case []string, []int, []int64, []interface{}:
query = query.Where(key+" in (?)", value)
default:
query = query.Where(key+" = ?", value)
}
}
}
return b.Handler.FindResult(page.SelectPages(t, query))
}
func (b *BaseIdModel) GenerateId() {
if b.Id == "" {
id, _ := uuid.NewUUID()
b.Id = strings.ReplaceAll(id.String(), "-", "")
}
}
type BaseDbModel struct {
BaseIdModel
CreateTimeMillis int64 `gorm:"column:create_time_millis" json:"createTimeMillis"` //type:timestamptz comment:数据创建时间 version:2024-02-12 14:22
ModifyTimeMillis int64 `gorm:"column:modify_time_millis" json:"modifyTimeMillis"` //type:timestamptz comment:数据修改时间 version:2024-02-12 14:22
}
func (b *BaseDbModel) Create(t schema.Tabler) (int64, error) {
b.GenerateTime()
return b.BaseIdModel.Create(t)
}
func (b *BaseDbModel) Update(t schema.Tabler, column []string) (int64, error) {
b.GenerateModifyTime()
return b.BaseIdModel.Update(t, column)
}
func (b *BaseDbModel) GenerateTime() {
t := time.Now().UnixMilli()
b.CreateTimeMillis = t
b.ModifyTimeMillis = t
}
func (b *BaseDbModel) GenerateModifyTime() {
b.ModifyTimeMillis = time.Now().UnixMilli()
}
type BaseUserModifyDbModel struct {
BaseDbModel
CreateUser string `gorm:"column:create_user" json:"createUser"`
ModifyUser string `gorm:"column:modify_user" json:"modifyUser"`
}
func (b *BaseUserModifyDbModel) SetUserName(userName string) {
b.CreateUser = userName
b.ModifyUser = userName
}
func (b *BaseUserModifyDbModel) SetModifyUserName(userName string) {
b.ModifyUser = userName
}
type BaseWorkspaceModel struct {
BaseUserModifyDbModel
WorkspaceId string `gorm:"column:workspace_id" json:"workspaceId"`
}
func (b *BaseWorkspaceModel) CountByWorkspaceId(t schema.Tabler) (int64, error) {
if b.WorkspaceId == "" {
return 0, errors.New("id cannot be null")
}
var count int64
tx := DB().Table(t.TableName()).Where("workspace_id = ?", b.WorkspaceId).Count(&count)
return count, tx.Error
}
func (b *BaseWorkspaceModel) Global() bool {
return strings.EqualFold(b.WorkspaceId, "GLOBAL")
}
type BaseNodeModel struct {
BaseWorkspaceModel
NodeId string `gorm:"column:node_id" json:"nodeId"`
NodeName string `gorm:"column:node_name" json:"nodeName"`
WorkspaceName string `gorm:"column:workspace_name" json:"workspaceName"`
}
type BaseGroupModel struct {
BaseWorkspaceModel
Group string `gorm:"column:group" json:"group"`
}
type BaseMachineModel struct {
BaseGroupModel
MachineId string `gorm:"column:machine_id" json:"machineId"`
}
type BaseModel struct {
ID string `gorm:"column:id;not null;primarykey" json:"id"`
CreateTime time.Time `gorm:"column:create_time;type:timestamp;<-:create" json:"createTime"`
UpdateTime time.Time `gorm:"column:update_time;type:timestamp" json:"updateTime"`
CreatorID string `gorm:"column:creator_id;type:varchar(64);not null;<-:create" json:"creatorID"`
Modifier string `gorm:"column:modifier;type:varchar(64);not null;" json:"modifier"`
}
func (model *BaseModel) DB() *gorm.DB {
return DB()
}
type ContainsDeleteBaseModel struct {
BaseModel
DeletedAt soft_delete.DeletedAt `gorm:"column:deleted_at;type:int(11) unsigned;not null;default:0;index" json:"-"`
}
func DB() *gorm.DB {
if c.Config.DatabaseConfig.Type == "sqlite" {
return data.SqliteDB
} else {
return data.PostgreSqlDB["data"]
}
}
//func DBAuth() *gorm.DB {
// return data.PostgreSqlDB["auth"]
//}
type Page[T any] struct {
CurrentPage int64 `json:"currentPage"`
PageSize int64 `json:"pageSize"`
Total int64 `json:"total"`
Pages int64 `json:"pages"`
Order string `json:"order"` //db.Order("name DESC")
Data []T `json:"data"`
}
func (page *Page[T]) SelectPages(query *gorm.DB) (e error) {
var model T
query.Model(&model).Count(&page.Total)
if page.Total == 0 {
page.Data = []T{}
return
}
e = query.Model(&model).Scopes(Paginate(page)).Find(&page.Data).Error
return
}
func (page *Page[T]) ScanPages(query *gorm.DB) (e error) {
var model T
query.Model(&model).Count(&page.Total)
if page.Total == 0 {
page.Data = []T{}
return
}
e = query.Model(&model).Scopes(Paginate(page)).Scan(&page.Data).Error
return
}
func Paginate[T any](page *Page[T]) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if page.CurrentPage <= 0 {
page.CurrentPage = 1
}
switch {
case page.PageSize > 1000:
page.PageSize = 1000 // 限制一下分页大小
case page.PageSize <= 0:
page.PageSize = 10
}
page.Pages = page.Total / page.PageSize
if page.Total%page.PageSize != 0 {
page.Pages++
}
size := page.PageSize
offset := int((page.CurrentPage - 1) * size)
return db.Order(page.Order).Offset(offset).Limit(int(size))
}
}
func (page *Page[T]) Covert(pageInfo *request.PageInfo) {
if pageInfo.CurrentPage <= 0 {
pageInfo.CurrentPage = 1
}
switch {
case pageInfo.PageSize > 1000:
pageInfo.PageSize = 1000 // 限制一下分页大小
case pageInfo.PageSize <= 0:
pageInfo.PageSize = 10
}
page.CurrentPage = pageInfo.CurrentPage
page.PageSize = pageInfo.PageSize
page.Order = pageInfo.Order
}
// PageConfig 需要配套实现FindResultData需要回填。推荐使用Page[T any]
type PageConfig struct {
CurrentPage int64 `json:"currentPage"`
PageSize int64 `json:"pageSize"`
Total int64 `json:"total"`
Pages int64 `json:"pages"`
Order string `json:"order"` //db.Order("name DESC")
Data any `json:"data"` // 需要回填,不好用
}
func (page *PageConfig) SelectPages(t schema.Tabler, query *gorm.DB) *gorm.DB {
query.Table(t.TableName()).Count(&page.Total)
if page.Total == 0 {
return nil
}
return query.Table(t.TableName()).Scopes(PaginateConfig(page))
}
func PaginateConfig(page *PageConfig) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if page.CurrentPage <= 0 {
page.CurrentPage = 1
}
switch {
case page.PageSize > 1000:
page.PageSize = 1000 // 限制一下分页大小
case page.PageSize <= 0:
page.PageSize = 10
}
page.Pages = page.Total / page.PageSize
if page.Total%page.PageSize != 0 {
page.Pages++
}
size := page.PageSize
offset := int((page.CurrentPage - 1) * size)
return db.Order(page.Order).Offset(offset).Limit(int(size))
}
}
func (page *PageConfig) Covert(pageInfo *request.PageInfo) {
if pageInfo.CurrentPage <= 0 {
pageInfo.CurrentPage = 1
}
switch {
case pageInfo.PageSize > 1000:
pageInfo.PageSize = 1000 // 限制一下分页大小
case pageInfo.PageSize <= 0:
pageInfo.PageSize = 10
}
page.CurrentPage = pageInfo.CurrentPage
page.PageSize = pageInfo.PageSize
page.Order = pageInfo.Order
}

View File

@ -0,0 +1,304 @@
package common
import (
"fmt"
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/common/sql"
. "github.com/ahmetb/go-linq"
"strings"
)
const (
FilterTypeLikeSuffix = "__like"
FilterTypeEqualSuffix = "__eq"
FilterTypeNotEqualSuffix = "__neq"
FilterTypeInSuffix = "__in"
)
const (
FilterTypeLike = 0
FilterTypeEqual = 1
FilterTypeNotEqual = 2
FilterTypeIn = 3
)
type FilterType int
// PaginationQuery defines
type PaginationQuery struct {
From int
Size int
}
// NoPagination is the option for no pigination, currently a very large page
var NoPagination = NewPaginationQuery(-1, -1)
// DefaultPagination is the default options for pagination
var DefaultPagination = NewPaginationQuery(0, 10)
// NewPaginationQuery creates a new PaginationQuery
func NewPaginationQuery(from, size int) *PaginationQuery {
return &PaginationQuery{
From: from,
Size: size,
}
}
// IsValid checks if a PaginationQuery is valid
func (pq *PaginationQuery) IsValid() bool {
if pq == nil {
return false
}
if pq.From == -1 && pq.Size == -1 {
return true
}
return pq.From >= 0 && pq.Size > 0
}
func (pq *PaginationQuery) String() string {
if pq == nil {
return ""
}
if pq.From == -1 || pq.Size == -1 {
return ""
}
return fmt.Sprintf(" LIMIT %d OFFSET %d ", pq.Size, pq.From)
}
// SortQuery holds options for sort functionality of data select.
type SortQuery struct {
SortByList []SortBy
}
// SortBy holds the name of the property that should be sorted and whether order should be ascending or descending.
type SortBy struct {
Property string
Ascending bool
}
// NoSort is as option for no sort.
var NoSort = &SortQuery{
SortByList: []SortBy{},
}
// NewSortQuery takes raw sort options list and returns SortQuery object. For example:
// ["a", "parameter1", "d", "parameter2"] - means that the data should be sorted by
// parameter1 (ascending) and later - for results that return equal under parameter 1 sort - by parameter2 (descending)
func NewSortQuery(sortByListRaw []string) *SortQuery {
if sortByListRaw == nil || len(sortByListRaw)%2 == 1 {
return &SortQuery{SortByList: []SortBy{}}
}
sortByList := []SortBy{}
for i := 0; i+1 < len(sortByListRaw); i += 2 {
// parse order option
var ascending bool
orderOption := sortByListRaw[i]
if orderOption == "a" {
ascending = true
} else if orderOption == "d" {
ascending = false
} else {
// Invalid order option. Only ascending (a), descending (d) options are supported
return &SortQuery{SortByList: []SortBy{}}
}
// parse property name
propertyName := sortByListRaw[i+1]
sortBy := SortBy{
Property: propertyName,
Ascending: ascending,
}
// Add to the sort options.
sortByList = append(sortByList, sortBy)
}
return &SortQuery{
SortByList: sortByList,
}
}
func (sq *SortQuery) String() string {
if sq == nil {
return ""
}
if len(sq.SortByList) == 0 {
return ""
}
res := fmt.Sprintf(" ORDER BY %s ", sq.SortByList[0].Property)
if sq.SortByList[0].Ascending == false {
res += "desc "
}
return res
}
// FilterQuery holds options for filter functionality of data select.
// currently it only supports string match
type FilterQuery struct {
FilterByList []FilterBy
Extend string
}
// FilterBy holds the name of the property that should be .
type FilterBy struct {
Property string
Type FilterType
Value string
values []string
}
// NoFilter is as option for no filter.
var NoFilter = &FilterQuery{
FilterByList: []FilterBy{},
}
// NewFilterQuery takes raw filter options list and returns FilterQuery object. For example:
// ["user_name", "test"] - means that the data should be filtered by
// user_name (%test%), means user_name contains 'test'
func NewFilterQuery(filterByListRaw []string) *FilterQuery {
if filterByListRaw == nil || len(filterByListRaw)%2 == 1 {
return &FilterQuery{FilterByList: []FilterBy{}}
}
filterByList := parseFilter(filterByListRaw)
return &FilterQuery{
FilterByList: filterByList,
}
}
func (fq *FilterQuery) Add(filterByListRaw []string) {
filterByList := parseFilter(filterByListRaw)
fq.FilterByList = append(fq.FilterByList, filterByList...)
}
func parseFilter(filterByListRaw []string) []FilterBy {
filterByList := []FilterBy{}
for i := 0; i+1 < len(filterByListRaw); i += 2 {
// parse property name and value
propertyName := filterByListRaw[i]
propertyValue := filterByListRaw[i+1]
propertyValueArray := strings.Split(propertyValue, " ")
filterBy := FilterBy{
Property: propertyName,
Type: FilterTypeLike,
Value: strings.Join(propertyValueArray, ","),
}
if strings.HasSuffix(propertyName, FilterTypeEqualSuffix) {
filterBy.Type = FilterTypeEqual
filterBy.Property = strings.TrimSuffix(filterBy.Property, FilterTypeEqualSuffix)
} else if strings.HasSuffix(propertyName, FilterTypeNotEqualSuffix) {
filterBy.Type = FilterTypeNotEqual
filterBy.Property = strings.TrimSuffix(filterBy.Property, FilterTypeNotEqualSuffix)
} else if strings.HasSuffix(propertyName, FilterTypeInSuffix) {
filterBy.Type = FilterTypeIn
filterBy.Property = strings.TrimSuffix(filterBy.Property, FilterTypeInSuffix)
} else {
filterBy.Property = strings.TrimSuffix(filterBy.Property, FilterTypeLikeSuffix)
}
// Add to the sort options.
filterByList = append(filterByList, filterBy)
}
return filterByList
}
func (fq *FilterQuery) String() string {
if fq == nil || len(fq.FilterByList) == 0 {
return fmt.Sprintf(" '1' = '1' %s ", fq.Extend)
}
type index struct {
property string
operator FilterType
}
indexed := make(map[index][]*FilterBy)
From(fq.FilterByList).
GroupByT(
func(fb FilterBy) index { return index{property: fb.Property, operator: fb.Type} },
func(fb FilterBy) *FilterBy { return &fb }).
ToMapByT(&indexed,
func(g Group) index { return g.Key.(index) },
func(g Group) []*FilterBy {
var fbs []*FilterBy
From(g.Group).ToSlice(&fbs)
return fbs
})
var list []*FilterBy
From(indexed).SelectT(func(kv KeyValue) *FilterBy {
k := kv.Key.(index)
v := kv.Value.([]*FilterBy)
var values []string
From(v).SelectT(func(fb *FilterBy) string { return fb.Value }).ToSlice(&values)
return &FilterBy{
Property: k.property,
Type: k.operator,
values: values,
}
}).ToSlice(&list)
var items []string
for _, filterBy := range list {
var constraints []string
if filterBy.Type == FilterTypeEqual {
From(filterBy.values).SelectT(func(v string) string {
return fmt.Sprintf(" %s='%s' ", filterBy.Property, sql.EscapeStringBackslash(v))
}).ToSlice(&constraints)
items = append(items, fmt.Sprintf(" (%s) ", strings.Join(constraints, " or ")))
} else if filterBy.Type == FilterTypeNotEqual {
From(filterBy.values).SelectT(func(v string) string {
return fmt.Sprintf(" %s!='%s' ", filterBy.Property, sql.EscapeStringBackslash(v))
}).ToSlice(&constraints)
items = append(items, strings.Join(constraints, " and "))
} else if filterBy.Type == FilterTypeIn {
From(filterBy.values).SelectT(func(v string) string {
return fmt.Sprintf("%s", strings.Replace(sql.EscapeStringBackslash(v), ",", "','", -1))
}).ToSlice(&constraints)
items = append(items, fmt.Sprintf(` (%s in ('%s')) `, filterBy.Property, strings.Join(constraints, ",")))
} else {
From(filterBy.values).SelectT(func(v string) string {
if v == "%" {
v = "\\%"
}
return fmt.Sprintf(" %s like '%%%s%%' ", filterBy.Property, sql.EscapeUnderlineInLikeStatement(v))
}).ToSlice(&constraints)
items = append(items, fmt.Sprintf(" (%s) ", strings.Join(constraints, " or ")))
}
}
return fmt.Sprintf("%s %s", strings.Join(items, "and"), fq.Extend)
}
// DataSelectQuery currently included only Pagination and Sort options.
type DataSelectQuery struct {
PaginationQuery *PaginationQuery
SortQuery *SortQuery
FilterQuery *FilterQuery
}
// NoDataSelect fetches all items with no sort.
var NoDataSelect = NewDataSelectQuery(NoPagination, NoSort, NoFilter)
// DefaultDataSelect fetches first 10 items from page 1 with no sort.
var DefaultDataSelect = NewDataSelectQuery(DefaultPagination, NoSort, NoFilter)
// NewDataSelectQuery creates DataSelectQuery object from simpler data select queries.
func NewDataSelectQuery(paginationQuery *PaginationQuery, sortQuery *SortQuery, filterQuery *FilterQuery) *DataSelectQuery {
dataselect := &DataSelectQuery{
PaginationQuery: &PaginationQuery{From: 0, Size: 10},
SortQuery: &SortQuery{SortByList: []SortBy{}},
FilterQuery: &FilterQuery{FilterByList: []FilterBy{}},
}
if paginationQuery != nil {
pq := *paginationQuery
dataselect.PaginationQuery = &pq
}
if sortQuery != nil && len(sortQuery.SortByList) > 0 {
dataselect.SortQuery.SortByList = append(dataselect.SortQuery.SortByList, sortQuery.SortByList...)
}
if filterQuery != nil && len(filterQuery.FilterByList) > 0 {
dataselect.FilterQuery.FilterByList = append(dataselect.FilterQuery.FilterByList, filterQuery.FilterByList...)
}
if filterQuery != nil {
dataselect.FilterQuery.Extend = filterQuery.Extend
if len(filterQuery.FilterByList) > 0 {
dataselect.FilterQuery.FilterByList = append(dataselect.FilterQuery.FilterByList, filterQuery.FilterByList...)
}
}
return dataselect
}

View File

@ -0,0 +1,138 @@
package common
import (
"errors"
"testing"
)
func TestNewPaginationQuery(t *testing.T) {
resource := []struct {
label string
from int
size int
}{
{
"normal",
1,
10,
},
{
"normal",
0,
10,
},
{
"sizeError",
0,
0,
},
{
"fromError",
-1,
1,
},
{
"doubleError",
-1,
-1,
},
}
for _, item := range resource {
pgq := NewPaginationQuery(item.from, item.size)
flag := pgq.IsValid()
if !flag && item.label == "normal" {
t.Error(errors.New("IsValid error"))
}
str := pgq.String()
if str == "" && item.label == "normal" {
t.Error(errors.New("string error"))
}
}
if !NoPagination.IsValid() {
t.Error(errors.New("IsValid error"))
}
if NoPagination.String() != "" {
t.Error(errors.New("string error"))
}
}
func TestNewSortQuery(t *testing.T) {
resource := []string{"a", "param1", "d", "param2", "d", "param3"}
fake1 := []string{}
fake2 := []string{"a"}
fake3 := []string{"a", "p1", "b", "p2"}
sq := NewSortQuery(resource)
if sq.String() == "" {
t.Error(errors.New("string error"))
}
fakeSq1 := NewSortQuery(fake1)
if fakeSq1.String() != "" {
t.Error(errors.New("string error"))
}
fakeSq2 := NewSortQuery(fake2)
if fakeSq2.String() != "" {
t.Error(errors.New("string error"))
}
fakeSq3 := NewSortQuery(fake3)
if fakeSq3.String() != "" {
t.Error(errors.New("string error"))
}
if NoSort.String() != "" {
t.Error(errors.New("string error"))
}
}
func TestNewFilterQuery(t *testing.T) {
resource := []string{"type", "param"}
fake1 := []string{}
fake2 := []string{"type"}
fake3 := []string{"_neqtype", "param"}
fake4 := []string{"_eqtype", "param"}
fq := NewFilterQuery(resource)
if fq.String() == " '1' = '1' " {
t.Error(errors.New("string error"))
}
fqFake1 := NewFilterQuery(fake1)
if fqFake1.String() != " '1' = '1' " {
t.Error(errors.New("string error"))
}
fqFake2 := NewFilterQuery(fake2)
if fqFake2.String() != " '1' = '1' " {
t.Error(errors.New("string error"))
}
fqFake3 := NewFilterQuery(fake3)
if fqFake3.String() == " '1' = '1' " {
t.Error(errors.New("string error"))
}
fqFake4 := NewFilterQuery(fake4)
if fqFake4.String() == " '1' = '1' " {
t.Error(errors.New("string error"))
}
}
func TestNewDataSelectQuery(t *testing.T) {
resource1 := []string{"type", "param"}
resource2 := []string{"a", "param"}
pgq := NewPaginationQuery(0, 10)
sq := NewSortQuery(resource2)
fq := NewFilterQuery(resource1)
NewDataSelectQuery(nil, nil, nil)
NewDataSelectQuery(pgq, nil, nil)
NewDataSelectQuery(pgq, sq, nil)
NewDataSelectQuery(pgq, sq, fq)
}

View File

@ -0,0 +1,71 @@
package sql
// EscapeStringBackslash escapes special characters.
func EscapeStringBackslash(v string) string {
buf := make([]byte, len(v)*2, len(v)*2)
pos := 0
for i := 0; i < len(v); i++ {
c := v[i]
switch c {
case '\x00':
buf[pos] = '\\'
buf[pos+1] = '0'
pos += 2
case '\n':
buf[pos] = '\\'
buf[pos+1] = 'n'
pos += 2
case '\r':
buf[pos] = '\\'
buf[pos+1] = 'r'
pos += 2
case '\x1a':
buf[pos] = '\\'
buf[pos+1] = 'Z'
pos += 2
case '\'':
buf[pos] = '\\'
buf[pos+1] = '\''
pos += 2
case '"':
buf[pos] = '\\'
buf[pos+1] = '"'
pos += 2
case '\\':
buf[pos] = '\\'
buf[pos+1] = '\\'
pos += 2
default:
buf[pos] = c
pos++
}
}
return string(buf[:pos])
}
func EscapeSliceForInjection(src []string) []string {
out := make([]string, 0, len(src))
for i := range src {
out = append(out, EscapeStringBackslash(src[i]))
}
return out
}
func EscapeUnderlineInLikeStatement(v string) string {
v = EscapeStringBackslash(v)
buf := make([]byte, len(v)*2, len(v)*2)
pos := 0
for i := 0; i < len(v); i++ {
if v[i] == '_' {
buf[pos] = '\\'
buf[pos+1] = '_'
pos += 2
} else {
buf[pos] = v[i]
pos++
}
}
return string(buf[:pos])
}

View File

@ -0,0 +1,59 @@
package device
import (
"git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model"
"time"
)
// 设备健康检测记录。
// 表名:perc_device_health_record
type DeviceHealthRecord struct {
Id int64 `gorm:"column:id;primaryKey" json:"id"` //type:*int comment:主键 version:2023-9-11 14:28
SerialNo string `gorm:"column:serial_no" json:"serialNo" validate:"required"` //type:string comment:序列号 version:2023-9-11 14:28
BatchNo string `gorm:"column:batch_no" json:"batchNo" validate:"required"` //type:string comment:检测批次号 version:2023-9-11 14:28
ItemName string `gorm:"column:item_name" json:"itemName" validate:"required"` //type:string comment:检测项 version:2023-9-11 14:28
ItemScore string `gorm:"column:item_score" json:"itemScore"` //type:string comment:检测得分 version:2023-9-11 14:28
ItemResult string `gorm:"column:item_result" json:"itemResult"` //type:string comment:检测结果 version:2023-9-11 14:28
CheckTime time.Time `gorm:"column:check_time" json:"checkTime" validate:"required"` //type:*time.Time comment:检测时间 version:2023-9-11 14:28
}
func (DeviceHealthRecord) TableName() string {
return "perc_device_health_record"
}
func (d *DeviceHealthRecord) Create() (int64, error) {
d.CheckTime = time.Now()
tx := model.DB().Create(d)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
func (d *DeviceHealthRecord) Delete() (int64, error) {
tx := model.DB().Delete(d)
if tx.Error != nil {
return 0, tx.Error
}
return tx.RowsAffected, nil
}
func (d *DeviceHealthRecord) Get(fields map[string]interface{}) (DeviceHealthRecord, error) {
device := DeviceHealthRecord{}
tx := model.DB().Where(fields).First(&device)
if tx.Error != nil {
return device, tx.Error
}
return device, nil
}
func (d *DeviceHealthRecord) List(limit int, offset int, fields map[string]interface{}) ([]DeviceHealthRecord, int64, error) {
devices := []DeviceHealthRecord{}
tx := model.DB().Limit(limit).Offset(offset).Where(fields).Find(&devices)
if tx.Error != nil {
return nil, 0, tx.Error
}
var total int64
model.DB().Model(DeviceHealthRecord{}).Where(fields).Count(&total)
return devices, total, nil
}

Some files were not shown because too many files have changed in this diff Show More