├── .gitignore ├── app ├── gateway │ └── main.go ├── storage │ ├── handler │ │ ├── basic.go │ │ ├── net.go │ │ ├── storage.go │ │ ├── http.go │ │ └── node.go │ ├── main.go │ └── conf │ │ └── conf.go ├── master │ ├── admin │ │ ├── static │ │ │ └── vendor │ │ │ │ └── bootstrap │ │ │ │ ├── fonts │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ │ ├── js │ │ │ │ └── npm.js │ │ │ │ └── css │ │ │ │ └── bootstrap-theme.min.css │ │ └── views │ │ │ └── console.html │ ├── main.go │ ├── handler │ │ ├── admin.go │ │ ├── node.go │ │ └── net.go │ └── conf │ │ └── conf.go └── api │ ├── handler │ ├── basic.go │ ├── node.go │ └── api.go │ ├── main.go │ └── conf │ └── conf.go ├── test ├── 1.gif ├── 2.png ├── timg.jpeg ├── index.html └── main.go ├── lib ├── packet │ ├── auth.go │ ├── packet.go │ └── node.go ├── code │ └── code.go ├── hardware │ ├── linux │ │ ├── cpu.go │ │ └── memory.go │ └── hardware.go ├── filetype │ └── filetype.go ├── command │ └── command.go ├── cmd │ └── cmd.go ├── protocol │ └── protocol.go ├── ini │ └── ini.go ├── utils.go └── logd │ └── log.go ├── vendor ├── github.com │ └── jinzhu │ │ └── gorm │ │ ├── dialects │ │ └── mysql │ │ │ └── mysql.go │ │ ├── test_all.sh │ │ ├── model.go │ │ ├── interface.go │ │ ├── callback_row_query.go │ │ ├── License │ │ ├── field.go │ │ ├── wercker.yml │ │ ├── errors.go │ │ ├── README.md │ │ ├── callback_delete.go │ │ ├── callback_query.go │ │ ├── dialect_sqlite3.go │ │ ├── logger.go │ │ ├── callback_save.go │ │ ├── callback_update.go │ │ ├── dialect.go │ │ ├── dialect_postgres.go │ │ ├── search.go │ │ ├── dialect_common.go │ │ ├── dialect_mysql.go │ │ ├── callback_create.go │ │ ├── join_table_handler.go │ │ ├── utils.go │ │ ├── callback.go │ │ ├── callback_query_preload.go │ │ └── association.go └── vendor.json ├── README.md ├── conf ├── master.template ├── store.ini.template └── api.template ├── db ├── user.go ├── model.go ├── bucket.go ├── metadata.go └── db.go └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | conf/*.ini 3 | log/ 4 | .idea -------------------------------------------------------------------------------- /app/gateway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | 5 | } -------------------------------------------------------------------------------- /test/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goss-io/goss/HEAD/test/1.gif -------------------------------------------------------------------------------- /test/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goss-io/goss/HEAD/test/2.png -------------------------------------------------------------------------------- /test/timg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goss-io/goss/HEAD/test/timg.jpeg -------------------------------------------------------------------------------- /lib/packet/auth.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | //Auth 建立授权. 4 | func Auth() { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/dialects/mysql/mysql.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import _ "github.com/go-sql-driver/mysql" 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goss 2 | 分布式对象存储 3 | 4 | ### api 5 | `api` 为上传和访问服务 6 | ### storage 7 | `storage` 为存储服务 8 | ### master 9 | `master` 为集群管理服务 -------------------------------------------------------------------------------- /lib/code/code.go: -------------------------------------------------------------------------------- 1 | package code 2 | 3 | const ( 4 | SUCCESS = 1000 //状态正常. 5 | READ_TCP_ERROR = 1001 //读取tcp内容失败. 6 | PARSE_JSON_ERROR = 1002 //解析json失败. 7 | ) 8 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/test_all.sh: -------------------------------------------------------------------------------- 1 | dialects=("postgres" "mysql" "mssql" "sqlite") 2 | 3 | for dialect in "${dialects[@]}" ; do 4 | GORM_DIALECT=${dialect} go test 5 | done 6 | -------------------------------------------------------------------------------- /app/storage/handler/basic.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | //StorageService 存储节点信息. 4 | type StorageService struct { 5 | Port string 6 | Addr string 7 | MasterNode string 8 | } 9 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Hello Goss

9 | 10 | -------------------------------------------------------------------------------- /conf/master.template: -------------------------------------------------------------------------------- 1 | #节点配置 2 | node_name=master_node 3 | node_ip = 127.0.0.1 4 | node_port = 8000 5 | node_web_port=8080 6 | 7 | #基础配置 8 | log_path = ../log/ 9 | token = 202cb962ac59075b964b07152d234b70 -------------------------------------------------------------------------------- /app/master/admin/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goss-io/goss/HEAD/app/master/admin/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/master/admin/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goss-io/goss/HEAD/app/master/admin/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/master/admin/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goss-io/goss/HEAD/app/master/admin/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/master/admin/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goss-io/goss/HEAD/app/master/admin/static/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /app/api/handler/basic.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | //APIService Api服务信息. 4 | type APIService struct { 5 | Port string 6 | Addr string 7 | MasterNode string 8 | Storage []string 9 | } 10 | -------------------------------------------------------------------------------- /db/user.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | type User struct { 4 | Model 5 | Name string 6 | Account string 7 | Password string 8 | } 9 | 10 | //TableName. 11 | func (User) TableName() string { 12 | return "user" 13 | } 14 | -------------------------------------------------------------------------------- /conf/store.ini.template: -------------------------------------------------------------------------------- 1 | 2 | #节点配置 3 | node_name=storage_node_1 4 | node_ip = 127.0.0.1 5 | node_port = 9001 6 | 7 | #基础配置 8 | master_node=127.0.0.1:8000 9 | storage_root=/disk/storage/ 10 | log_path = ../log/ 11 | token = 202cb962ac59075b964b07152d234b70 -------------------------------------------------------------------------------- /conf/api.template: -------------------------------------------------------------------------------- 1 | #数据库配置 2 | db_host = 127.0.0.1 3 | db_user = root 4 | db_name = goss 5 | db_pwd = 123 6 | db_port = 3306 7 | 8 | #节点配置 9 | node_name=api_node_1 10 | node_ip = 127.0.0.1 11 | node_port = 80 12 | 13 | #基础配置 14 | log_path = ../log/ 15 | master_node=127.0.0.1:8000 16 | token = 202cb962ac59075b964b07152d234b70 -------------------------------------------------------------------------------- /db/model.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "time" 4 | 5 | //Model . 6 | type Model struct { 7 | ID int `gorm:"primary_key" json:"id"` 8 | CreatedAt time.Time `gorm:"created_at"` 9 | UpdatedAt time.Time `gorm:"created_at"` 10 | CreateTime string `gorm:"-"` 11 | UpdateTime string `gorm:"-"` 12 | } 13 | -------------------------------------------------------------------------------- /lib/hardware/linux/cpu.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import "github.com/Goss-io/goss/lib/command" 4 | 5 | //CpuNum Cpu核数. 6 | func CpuNum() string { 7 | num, err := command.Exec("cat /proc/cpuinfo | grep 'cpu cores' | head -1 | awk -F ':' '{print $2}'") 8 | if err != nil { 9 | num = "0" 10 | } 11 | 12 | num = num + "核" 13 | return num 14 | } 15 | -------------------------------------------------------------------------------- /app/master/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/Goss-io/goss/app/master/conf" 7 | "github.com/Goss-io/goss/app/master/handler" 8 | "github.com/Goss-io/goss/lib/cmd" 9 | ) 10 | 11 | func main() { 12 | cmd := cmd.New() 13 | 14 | //加载配置文件. 15 | conf.Load(cmd) 16 | log.Println("node name:", conf.Conf.Node.Name) 17 | 18 | master := handler.NewMaster() 19 | master.Start() 20 | } 21 | -------------------------------------------------------------------------------- /lib/filetype/filetype.go: -------------------------------------------------------------------------------- 1 | package filetype 2 | 3 | import "strings" 4 | 5 | var tyeList = map[string]string{ 6 | "47494638": "image/gif", 7 | "ffd8ffe": "image/jpeg", 8 | "89504e47": "image/png", 9 | "4740111": "video/mp2t", 10 | "23455854": "audio/mpegurl", 11 | } 12 | 13 | func Parse(str string) string { 14 | for k, v := range tyeList { 15 | if strings.Contains(str, k) { 16 | return v 17 | } 18 | } 19 | return "other" 20 | } 21 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/model.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import "time" 4 | 5 | // Model base model definition, including fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`, which could be embedded in your models 6 | // type User struct { 7 | // gorm.Model 8 | // } 9 | type Model struct { 10 | ID uint `gorm:"primary_key"` 11 | CreatedAt time.Time 12 | UpdatedAt time.Time 13 | DeletedAt *time.Time `sql:"index"` 14 | } 15 | -------------------------------------------------------------------------------- /lib/hardware/linux/memory.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Goss-io/goss/lib" 7 | "github.com/Goss-io/goss/lib/command" 8 | ) 9 | 10 | //MemSize 内存大小. 11 | func MemSize() string { 12 | num, err := command.Exec("cat /proc/meminfo | grep 'MemTotal' | awk -F ':' '{print $2}'") 13 | if err != nil { 14 | num = "0" 15 | } 16 | 17 | size := (lib.ParseInt(num) / (1000 * 1000)) 18 | mem := fmt.Sprintf("%dG", size) 19 | 20 | return mem 21 | } 22 | -------------------------------------------------------------------------------- /app/storage/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/Goss-io/goss/app/storage/conf" 7 | "github.com/Goss-io/goss/app/storage/handler" 8 | "github.com/Goss-io/goss/lib/cmd" 9 | ) 10 | 11 | func main() { 12 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) 13 | 14 | cmd := cmd.New() 15 | 16 | //加载配置文件. 17 | conf.Load(cmd) 18 | log.Println("node name:", conf.Conf.Node.Name) 19 | 20 | storage := handler.NewStorageService() 21 | storage.Start() 22 | } 23 | -------------------------------------------------------------------------------- /lib/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "os/exec" 5 | "strings" 6 | ) 7 | 8 | func Exec(shell string) (msg string, err error) { 9 | cmd := exec.Command("/bin/bash", "-c", shell) 10 | b, err := cmd.Output() 11 | if err != nil { 12 | return msg, err 13 | } 14 | 15 | msg = string(b) 16 | msg = strings.Replace(msg, " ", "", -1) 17 | msg = strings.Replace(msg, "\n", "", -1) 18 | msg = strings.Replace(msg, "\t", "", -1) 19 | msg = strings.Replace(msg, "kB", "", -1) 20 | return msg, nil 21 | } 22 | -------------------------------------------------------------------------------- /db/bucket.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | ) 6 | 7 | type Bucket struct { 8 | Model 9 | AccessKey string 10 | SecretKey string 11 | Name string 12 | UserID int 13 | Host string 14 | } 15 | 16 | func (Bucket) TableName() string { 17 | return "bucket" 18 | } 19 | 20 | //Query . 21 | func (b *Bucket) Query() error { 22 | err := Db.Model(&b).Where("host = ?", b.Host).First(&b).Error 23 | if err != nil && err != gorm.ErrRecordNotFound { 24 | return err 25 | } 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /lib/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | ) 8 | 9 | func (c *Command) parse() { 10 | 11 | var confPath string 12 | flag.StringVar(&confPath, "conf", "", "conf") 13 | 14 | flag.Parse() 15 | 16 | //conf. 17 | c.Conf = confPath 18 | if confPath == "" { 19 | log.Println("请指定配置文件 -conf") 20 | os.Exit(0) 21 | } 22 | } 23 | 24 | type Command struct { 25 | Conf string 26 | } 27 | 28 | func New() *Command { 29 | command := &Command{} 30 | command.parse() 31 | 32 | return command 33 | } 34 | -------------------------------------------------------------------------------- /app/master/admin/static/vendor/bootstrap/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /app/api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/Goss-io/goss/app/api/conf" 7 | "github.com/Goss-io/goss/app/api/handler" 8 | "github.com/Goss-io/goss/db" 9 | "github.com/Goss-io/goss/lib/cmd" 10 | ) 11 | 12 | func main() { 13 | log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) 14 | cmd := cmd.New() 15 | 16 | //加载配置文件. 17 | conf.Load(cmd) 18 | log.Println("node name:", conf.Conf.Node.Name) 19 | 20 | if err := db.Connection(); err != nil { 21 | log.Panicln(err) 22 | } 23 | 24 | apiSrv := handler.NewAPI() 25 | apiSrv.Start() 26 | } 27 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "IqQmIPZbwgzbZD4k7YX6Rj230pY=", 7 | "path": "github.com/jinzhu/gorm", 8 | "revision": "2a1463811ee1dc85d168fd639a2d4251d030e6e5", 9 | "revisionTime": "2017-07-03T13:49:54Z" 10 | }, 11 | { 12 | "checksumSHA1": "eECxSkwh0WmhLwaxHfkvcLOWS08=", 13 | "path": "github.com/jinzhu/gorm/dialects/mysql", 14 | "revision": "2a1463811ee1dc85d168fd639a2d4251d030e6e5", 15 | "revisionTime": "2017-07-03T13:49:54Z" 16 | } 17 | ], 18 | "rootPath": "goss.io/goss" 19 | } 20 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/interface.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import "database/sql" 4 | 5 | // SQLCommon is the minimal database connection functionality gorm requires. Implemented by *sql.DB. 6 | type SQLCommon interface { 7 | Exec(query string, args ...interface{}) (sql.Result, error) 8 | Prepare(query string) (*sql.Stmt, error) 9 | Query(query string, args ...interface{}) (*sql.Rows, error) 10 | QueryRow(query string, args ...interface{}) *sql.Row 11 | } 12 | 13 | type sqlDb interface { 14 | Begin() (*sql.Tx, error) 15 | } 16 | 17 | type sqlTx interface { 18 | Commit() error 19 | Rollback() error 20 | } 21 | -------------------------------------------------------------------------------- /lib/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | //GossProtocol 传输协议号. 4 | type GossProtocol int 5 | 6 | const ( 7 | CONN_AUTH GossProtocol = 1000 //连接授权. 8 | REPORT_NODE_INFO GossProtocol = 1001 //上报节点信息. 9 | MSG GossProtocol = 1002 //发送消息. 10 | ADD_NODE GossProtocol = 1003 //新增节点. 11 | REMOVE_NODE GossProtocol = 1004 //删除节点. 12 | SEND_FILE GossProtocol = 1005 //发送文件. 13 | READ_FILE GossProtocol = 1006 //读取文件. 14 | ) 15 | 16 | //NodeInfo 节点信息. 17 | type NodeInfo struct { 18 | Types string `json:"types"` 19 | CpuNum string `json:"cpu_num"` 20 | MemSize string `json:"mem_size"` 21 | SourceIP string `json:"source_ip"` 22 | Name string `json:"name"` 23 | } 24 | -------------------------------------------------------------------------------- /lib/hardware/hardware.go: -------------------------------------------------------------------------------- 1 | package hardware 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/Goss-io/goss/lib/hardware/linux" 7 | ) 8 | 9 | type cpu struct { 10 | Num string 11 | } 12 | type mem struct { 13 | Total string 14 | } 15 | 16 | //Hardware . 17 | type Hardware struct { 18 | Cpu cpu 19 | Mem mem 20 | } 21 | 22 | func New() Hardware { 23 | h := Hardware{ 24 | Cpu: cpu{ 25 | Num: "8核", 26 | }, 27 | Mem: mem{ 28 | Total: "16G", 29 | }, 30 | } 31 | 32 | system := checkSystem() 33 | if system == "linux" { 34 | h.Cpu.Num = linux.CpuNum() 35 | h.Mem.Total = linux.MemSize() 36 | } 37 | 38 | return h 39 | } 40 | 41 | func checkSystem() string { 42 | system := runtime.GOOS 43 | return system 44 | } 45 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/callback_row_query.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import "database/sql" 4 | 5 | // Define callbacks for row query 6 | func init() { 7 | DefaultCallback.RowQuery().Register("gorm:row_query", rowQueryCallback) 8 | } 9 | 10 | type RowQueryResult struct { 11 | Row *sql.Row 12 | } 13 | 14 | type RowsQueryResult struct { 15 | Rows *sql.Rows 16 | Error error 17 | } 18 | 19 | // queryCallback used to query data from database 20 | func rowQueryCallback(scope *Scope) { 21 | if result, ok := scope.InstanceGet("row_query_result"); ok { 22 | scope.prepareQuerySQL() 23 | 24 | if rowResult, ok := result.(*RowQueryResult); ok { 25 | rowResult.Row = scope.SQLDB().QueryRow(scope.SQL, scope.SQLVars...) 26 | } else if rowsResult, ok := result.(*RowsQueryResult); ok { 27 | rowsResult.Rows, rowsResult.Error = scope.SQLDB().Query(scope.SQL, scope.SQLVars...) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/storage/handler/net.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Goss-io/goss/app/storage/conf" 7 | "github.com/Goss-io/goss/lib/ini" 8 | "github.com/Goss-io/goss/lib/logd" 9 | ) 10 | 11 | //NewStorageService 初始化存储服务. 12 | func NewStorageService() *StorageService { 13 | s := &StorageService{ 14 | Port: fmt.Sprintf(":%d", conf.Conf.Node.Port), 15 | Addr: fmt.Sprintf("%s:%d", ini.GetString("node_ip"), ini.GetInt("node_port")), 16 | MasterNode: ini.GetString("master_node"), 17 | } 18 | return s 19 | } 20 | 21 | //Start . 22 | func (s *StorageService) Start() { 23 | s.checkStoragePath() 24 | go s.connMaster() 25 | s.httpSrv() 26 | } 27 | 28 | //checkStoragePath 检查存储路径. 29 | func (s *StorageService) checkStoragePath() { 30 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "初始化存储路径") 31 | if err := s.InitStoragePath(conf.Conf.Node.StorageRoot); err != nil { 32 | panic(err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | st := time.Now().Unix() 14 | for i := 1; i <= 1; i++ { 15 | upload() 16 | } 17 | 18 | et := time.Now().Unix() 19 | 20 | log.Println("共耗时:", et-st, "秒") 21 | } 22 | 23 | func upload() { 24 | filename := "./index.html" 25 | b, err := ioutil.ReadFile(filename) 26 | if err != nil { 27 | log.Printf("%+v\n", err) 28 | return 29 | } 30 | 31 | client := http.Client{} 32 | fname := fmt.Sprintf("http://blog.houzhongjian.com/%s", "index.html") 33 | req, err := http.NewRequest("PUT", fname, bytes.NewBuffer(b)) 34 | if err != nil { 35 | log.Printf("%+v\n", err) 36 | return 37 | } 38 | req.Header.Add("AccessKey", "202cb962ac59075b964b07152d234b70") 39 | req.Header.Add("SecretKey", "202cb962ac59075b964b07152d234b70") 40 | _, err = client.Do(req) 41 | if err != nil { 42 | log.Printf("%+v\n", err) 43 | return 44 | } 45 | 46 | log.Println(fname) 47 | } 48 | -------------------------------------------------------------------------------- /db/metadata.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | ) 6 | 7 | //Metadata 元数据. 8 | type Metadata struct { 9 | Model 10 | Name string `gorm:"index"` 11 | Type string 12 | Size int64 13 | Hash string `gorm:"index"` 14 | StoreNode string 15 | StorePath string 16 | Usable bool `gorm:"index"` //节点是否可用. 17 | BucketID int `gorm:"index"` 18 | } 19 | 20 | //TableName . 21 | func (Metadata) TableName() string { 22 | return "metadata" 23 | } 24 | 25 | //Create 创建. 26 | func (m *Metadata) Create() error { 27 | return Db.Create(&m).Error 28 | } 29 | 30 | //Query. 31 | func (m *Metadata) QueryNodeIP() (list []string, err error) { 32 | metaList := []Metadata{} 33 | err = Db.Where("name = ? and bucket_id = ?", m.Name, m.BucketID).Find(&metaList).Error 34 | if err != nil && err != gorm.ErrRecordNotFound { 35 | return list, err 36 | } 37 | 38 | for _, v := range metaList { 39 | list = append(list, v.StoreNode) 40 | m.Size = v.Size 41 | m.Hash = v.Hash 42 | m.StorePath = v.StorePath 43 | m.Type = v.Type 44 | } 45 | 46 | return list, nil 47 | } 48 | -------------------------------------------------------------------------------- /app/master/handler/admin.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/Goss-io/goss/lib/ini" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | type AdminService struct { 13 | WebPort string 14 | } 15 | 16 | func NewAdmin() { 17 | adm := &AdminService{ 18 | WebPort: fmt.Sprintf(":%d", ini.GetInt("node_web_port")), 19 | } 20 | adm.Start() 21 | } 22 | 23 | //Start . 24 | func (a *AdminService) Start() { 25 | r := gin.Default() 26 | r.Static("/img", "./admin/static/img/") 27 | r.Static("/css", "./admin/static/css/") 28 | r.Static("/vendor", "./admin/static/vendor/") 29 | r.LoadHTMLGlob("./admin/views/*") 30 | 31 | r.GET("/console", a.handleConsole) 32 | if err := r.Run(a.WebPort); err != nil { 33 | log.Panicln(err) 34 | } 35 | } 36 | 37 | //handleConsole . 38 | func (a *AdminService) handleConsole(c *gin.Context) { 39 | 40 | //获取所有的api节点. 41 | apiList := GetApiList() 42 | 43 | //获取所有的存储节点. 44 | storageList := GetStorageList() 45 | 46 | c.HTML(http.StatusOK, "console.html", map[string]interface{}{"apiList": apiList, "storageList": storageList}) 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Goss-io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/License: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-NOW Jinzhu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/master/handler/node.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/Goss-io/goss/lib" 5 | "github.com/Goss-io/goss/lib/packet" 6 | "github.com/Goss-io/goss/lib/protocol" 7 | ) 8 | 9 | //GossNode 节点信息. 10 | var GossNode = []Node{} 11 | 12 | type Node struct { 13 | Types packet.NodeTypes 14 | IP string 15 | SourceIP string //所属ip. 16 | CreateAt string 17 | Name string 18 | CpuNum string 19 | MemSize string 20 | } 21 | 22 | //GetStoreList 获取所有的存储节点. 23 | func GetStorageList() []Node { 24 | list := []Node{} 25 | for _, v := range GossNode { 26 | if v.Types == packet.NodeTypes_Storage { 27 | list = append(list, v) 28 | } 29 | } 30 | 31 | return list 32 | } 33 | 34 | //获取所有的api节点. 35 | func GetApiList() []Node { 36 | list := []Node{} 37 | for _, v := range GossNode { 38 | if v.Types == packet.NodeTypes_Api { 39 | list = append(list, v) 40 | } 41 | } 42 | 43 | return list 44 | } 45 | 46 | //RemoveNode 移除某一个切片. 47 | func RemoveNode(n *MasterService, ip string) { 48 | //根据访问ip获取节点ip. 49 | for index, v := range GossNode { 50 | if v.IP == ip { 51 | //通知对应的节点与故障节点断开连接. 52 | pkt := packet.New([]byte(v.SourceIP), lib.Hash(v.SourceIP), protocol.REMOVE_NODE) 53 | n.Conn[v.SourceIP].Write(pkt) 54 | 55 | //从NodeInfo中移除当前. 56 | GossNode = append(GossNode[:index], GossNode[index+1:]...) 57 | 58 | //删除对应的连接数据. 59 | delete(n.Conn, v.SourceIP) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/Goss-io/goss/lib/ini" 8 | _ "github.com/go-sql-driver/mysql" 9 | "github.com/jinzhu/gorm" 10 | ) 11 | 12 | //Db . 13 | var Db *gorm.DB 14 | 15 | //DbConfig . 16 | type DbConfig struct { 17 | Host string 18 | User string 19 | Password string 20 | Name string 21 | Port int 22 | Charset string 23 | } 24 | 25 | //Init . 26 | func Connection() error { 27 | cf := DbConfig{ 28 | Host: ini.GetString("db_host"), 29 | User: ini.GetString("db_user"), 30 | Password: ini.GetString("db_pwd"), 31 | Name: ini.GetString("db_name"), 32 | Port: ini.GetInt("db_port"), 33 | Charset: ini.GetString("db_charset"), 34 | } 35 | 36 | return conndb(cf) 37 | } 38 | 39 | //conndb . 40 | func conndb(cf DbConfig) error { 41 | args := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true", 42 | cf.User, 43 | cf.Password, 44 | cf.Host, 45 | cf.Port, 46 | cf.Name, 47 | ) 48 | 49 | log.Println("args:", args) 50 | db, err := gorm.Open("mysql", args) 51 | if err != nil { 52 | log.Printf("%+v\n", err) 53 | return err 54 | } 55 | 56 | db.SingularTable(true) 57 | db.LogMode(true) 58 | 59 | autoMigrate(db) 60 | Db = db 61 | 62 | return nil 63 | } 64 | 65 | //autoMigrate . 66 | func autoMigrate(db *gorm.DB) { 67 | db.AutoMigrate( 68 | &User{}, 69 | &Bucket{}, 70 | &Metadata{}, 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/field.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "database/sql" 5 | "errors" 6 | "fmt" 7 | "reflect" 8 | ) 9 | 10 | // Field model field definition 11 | type Field struct { 12 | *StructField 13 | IsBlank bool 14 | Field reflect.Value 15 | } 16 | 17 | // Set set a value to the field 18 | func (field *Field) Set(value interface{}) (err error) { 19 | if !field.Field.IsValid() { 20 | return errors.New("field value not valid") 21 | } 22 | 23 | if !field.Field.CanAddr() { 24 | return ErrUnaddressable 25 | } 26 | 27 | reflectValue, ok := value.(reflect.Value) 28 | if !ok { 29 | reflectValue = reflect.ValueOf(value) 30 | } 31 | 32 | fieldValue := field.Field 33 | if reflectValue.IsValid() { 34 | if reflectValue.Type().ConvertibleTo(fieldValue.Type()) { 35 | fieldValue.Set(reflectValue.Convert(fieldValue.Type())) 36 | } else { 37 | if fieldValue.Kind() == reflect.Ptr { 38 | if fieldValue.IsNil() { 39 | fieldValue.Set(reflect.New(field.Struct.Type.Elem())) 40 | } 41 | fieldValue = fieldValue.Elem() 42 | } 43 | 44 | if reflectValue.Type().ConvertibleTo(fieldValue.Type()) { 45 | fieldValue.Set(reflectValue.Convert(fieldValue.Type())) 46 | } else if scanner, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { 47 | err = scanner.Scan(reflectValue.Interface()) 48 | } else { 49 | err = fmt.Errorf("could not convert argument of field %s from %s to %s", field.Name, reflectValue.Type(), fieldValue.Type()) 50 | } 51 | } 52 | } else { 53 | field.Field.Set(reflect.Zero(field.Field.Type())) 54 | } 55 | 56 | field.IsBlank = isBlank(field.Field) 57 | return err 58 | } 59 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/wercker.yml: -------------------------------------------------------------------------------- 1 | # use the default golang container from Docker Hub 2 | box: golang 3 | 4 | services: 5 | - id: mariadb:10.0 6 | env: 7 | MYSQL_DATABASE: gorm 8 | MYSQL_USER: gorm 9 | MYSQL_PASSWORD: gorm 10 | MYSQL_RANDOM_ROOT_PASSWORD: "yes" 11 | - id: postgres 12 | env: 13 | POSTGRES_USER: gorm 14 | POSTGRES_PASSWORD: gorm 15 | POSTGRES_DB: gorm 16 | 17 | # The steps that will be executed in the build pipeline 18 | build: 19 | # The steps that will be executed on build 20 | steps: 21 | # Sets the go workspace and places you package 22 | # at the right place in the workspace tree 23 | - setup-go-workspace 24 | 25 | # Gets the dependencies 26 | - script: 27 | name: go get 28 | code: | 29 | cd $WERCKER_SOURCE_DIR 30 | go version 31 | go get -t ./... 32 | 33 | # Build the project 34 | - script: 35 | name: go build 36 | code: | 37 | go build ./... 38 | 39 | # Test the project 40 | - script: 41 | name: test sqlite 42 | code: | 43 | go test ./... 44 | 45 | - script: 46 | name: test mysql 47 | code: | 48 | GORM_DIALECT=mysql GORM_DBADDRESS=mariadb:3306 go test ./... 49 | 50 | - script: 51 | name: test postgres 52 | code: | 53 | GORM_DIALECT=postgres GORM_DBHOST=postgres go test ./... 54 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/errors.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | // ErrRecordNotFound record not found error, happens when haven't find any matched data when looking up with a struct 10 | ErrRecordNotFound = errors.New("record not found") 11 | // ErrInvalidSQL invalid SQL error, happens when you passed invalid SQL 12 | ErrInvalidSQL = errors.New("invalid SQL") 13 | // ErrInvalidTransaction invalid transaction when you are trying to `Commit` or `Rollback` 14 | ErrInvalidTransaction = errors.New("no valid transaction") 15 | // ErrCantStartTransaction can't start transaction when you are trying to start one with `Begin` 16 | ErrCantStartTransaction = errors.New("can't start transaction") 17 | // ErrUnaddressable unaddressable value 18 | ErrUnaddressable = errors.New("using unaddressable value") 19 | ) 20 | 21 | // Errors contains all happened errors 22 | type Errors []error 23 | 24 | // GetErrors gets all happened errors 25 | func (errs Errors) GetErrors() []error { 26 | return errs 27 | } 28 | 29 | // Add adds an error 30 | func (errs Errors) Add(newErrors ...error) Errors { 31 | for _, err := range newErrors { 32 | if errors, ok := err.(Errors); ok { 33 | errs = errs.Add(errors...) 34 | } else { 35 | ok = true 36 | for _, e := range errs { 37 | if err == e { 38 | ok = false 39 | } 40 | } 41 | if ok { 42 | errs = append(errs, err) 43 | } 44 | } 45 | } 46 | return errs 47 | } 48 | 49 | // Error format happened errors 50 | func (errs Errors) Error() string { 51 | var errors = []string{} 52 | for _, e := range errs { 53 | errors = append(errors, e.Error()) 54 | } 55 | return strings.Join(errors, "; ") 56 | } 57 | -------------------------------------------------------------------------------- /lib/ini/ini.go: -------------------------------------------------------------------------------- 1 | package ini 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "log" 7 | "os" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | var config map[string]string 13 | 14 | //Load 加载. 15 | func Load(name string) error { 16 | conf := map[string]string{} 17 | f, err := os.Open(name) 18 | if err != nil { 19 | return err 20 | } 21 | defer f.Close() 22 | br := bufio.NewReader(f) 23 | for { 24 | b, _, err := br.ReadLine() 25 | if err != nil && err == io.EOF { 26 | break 27 | } 28 | line := string(b) 29 | 30 | //替换掉当前行里面所有的空格. 31 | line = strings.Replace(line, " ", "", -1) 32 | 33 | //判断是否为注释. 34 | if strings.HasPrefix(line, "#") { 35 | continue 36 | } 37 | 38 | //去掉换行符. 39 | if len(line) < 1 { 40 | continue 41 | } 42 | 43 | key, value := parseLine(line) 44 | if err != nil { 45 | return err 46 | } 47 | conf[key] = value 48 | } 49 | config = conf 50 | return nil 51 | } 52 | 53 | //解析每一行的配置. 54 | func parseLine(line string) (key string, value string) { 55 | key = line 56 | index := strings.Index(line, "=") 57 | if index > 0 { 58 | key = line[0:index] 59 | value = line[index+1 : len(line)] 60 | } 61 | return key, value 62 | } 63 | 64 | func GetString(key string) string { 65 | return config[key] 66 | } 67 | 68 | func GetInt(key string) int { 69 | if len(config[key]) < 1 { 70 | return 0 71 | } 72 | num, err := strconv.Atoi(config[key]) 73 | if err != nil { 74 | log.Printf("获取int错误:%+v\n", err) 75 | } 76 | return num 77 | } 78 | 79 | func GetBool(key string) bool { 80 | if len(config[key]) < 1 { 81 | return false 82 | } 83 | b, err := strconv.ParseBool(config[key]) 84 | if err != nil { 85 | log.Printf("获取bool错误%+v\n", err) 86 | } 87 | return b 88 | } 89 | -------------------------------------------------------------------------------- /lib/packet/packet.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | 8 | "github.com/Goss-io/goss/lib/protocol" 9 | ) 10 | 11 | const PROROCOL_LEN = 4 12 | const HEADER_LEN = 4 13 | const HASH_LEN = 32 14 | 15 | type Packet struct { 16 | Protocol protocol.GossProtocol 17 | Size int64 18 | Hash string 19 | Body []byte 20 | } 21 | 22 | func New(content, fileHash []byte, num protocol.GossProtocol) []byte { 23 | buffer := make([]byte, HEADER_LEN+len(content)+len(fileHash)+PROROCOL_LEN) 24 | //0-4 为协议号. 25 | //4-8 为文件大小. 26 | //8-40 为文件hash. 27 | //>40 为文件内容. 28 | binary.BigEndian.PutUint32(buffer[0:4], uint32(num)) 29 | binary.BigEndian.PutUint32(buffer[4:8], uint32(len(content))) 30 | copy(buffer[8:40], fileHash) 31 | copy(buffer[40:], content) 32 | return buffer 33 | } 34 | 35 | //Parse 解析网络数据包. 36 | func Parse(conn net.Conn) (pkt Packet, err error) { 37 | //获取协议号. 38 | var num = make([]byte, PROROCOL_LEN) 39 | _, err = io.ReadFull(conn, num) 40 | if err != nil { 41 | return pkt, err 42 | } 43 | pkt.Protocol = protocol.GossProtocol(int(binary.BigEndian.Uint32(num))) 44 | 45 | //获取文件长度. 46 | var header = make([]byte, HEADER_LEN) 47 | _, err = io.ReadFull(conn, header) 48 | if err != nil { 49 | return pkt, err 50 | } 51 | pkt.Size = int64(binary.BigEndian.Uint32(header)) 52 | 53 | //获取hash. 54 | var fhash = make([]byte, HASH_LEN) 55 | _, err = io.ReadFull(conn, fhash) 56 | if err != nil { 57 | return pkt, err 58 | } 59 | pkt.Hash = string(fhash) 60 | 61 | //获取文件 62 | var buf = make([]byte, pkt.Size) 63 | _, err = io.ReadFull(conn, buf) 64 | if err != nil { 65 | return pkt, err 66 | } 67 | pkt.Body = buf 68 | 69 | return pkt, nil 70 | } 71 | -------------------------------------------------------------------------------- /lib/utils.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "encoding/json" 7 | "log" 8 | "net/http" 9 | "os" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | func FileHash(body []byte) string { 15 | h := md5.New() 16 | h.Write(body) 17 | b := h.Sum(nil) 18 | return hex.EncodeToString(b) 19 | } 20 | 21 | //Hash . 22 | func Hash(body string) []byte { 23 | h := md5.New() 24 | h.Write([]byte(body)) 25 | b := h.Sum(nil) 26 | return []byte(hex.EncodeToString(b)) 27 | } 28 | 29 | //IsExists 判断ini是否存在. 30 | func IsExists(ini string) bool { 31 | _, err := os.Stat(ini) 32 | if err != nil { 33 | return false 34 | } 35 | return true 36 | } 37 | 38 | //Time 获取当前时间. 39 | func Time() string { 40 | return time.Now().Format("2006-01-02 15:04:05") 41 | } 42 | 43 | //ParseInt . 44 | func ParseInt(str string) int { 45 | num, err := strconv.Atoi(str) 46 | if err != nil { 47 | return 0 48 | } 49 | return num 50 | } 51 | 52 | func InArray(item string, list []string) bool { 53 | for _, v := range list { 54 | if v == item { 55 | return true 56 | } 57 | } 58 | return false 59 | } 60 | 61 | type Resp struct { 62 | Status bool 63 | Msg interface{} 64 | Body []byte 65 | } 66 | 67 | //Response . 68 | func Response(w http.ResponseWriter, status bool, msg interface{}, body ...[]byte) { 69 | w.Header().Set("Content-Type", "application/json") 70 | dist := map[string]interface{}{ 71 | "status": status, 72 | "msg": msg, 73 | } 74 | if len(body) > 0 { 75 | dist["body"] = body[0] 76 | } 77 | b, err := json.Marshal(dist) 78 | if err != nil { 79 | log.Printf("err:%+v\n", err) 80 | return 81 | } 82 | 83 | w.Write(b) 84 | } 85 | 86 | //ParseMsg . 87 | func ParseMsg(b []byte) Resp { 88 | r := Resp{} 89 | json.Unmarshal(b, &r) 90 | return r 91 | } 92 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/README.md: -------------------------------------------------------------------------------- 1 | # GORM 2 | 3 | The fantastic ORM library for Golang, aims to be developer friendly. 4 | 5 | [![Join the chat at https://gitter.im/jinzhu/gorm](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jinzhu/gorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | [![wercker status](https://app.wercker.com/status/0cb7bb1039e21b74f8274941428e0921/s/master "wercker status")](https://app.wercker.com/project/bykey/0cb7bb1039e21b74f8274941428e0921) 7 | [![GoDoc](https://godoc.org/github.com/jinzhu/gorm?status.svg)](https://godoc.org/github.com/jinzhu/gorm) 8 | 9 | ## Overview 10 | 11 | * Full-Featured ORM (almost) 12 | * Associations (Has One, Has Many, Belongs To, Many To Many, Polymorphism) 13 | * Callbacks (Before/After Create/Save/Update/Delete/Find) 14 | * Preloading (eager loading) 15 | * Transactions 16 | * Composite Primary Key 17 | * SQL Builder 18 | * Auto Migrations 19 | * Logger 20 | * Extendable, write Plugins based on GORM callbacks 21 | * Every feature comes with tests 22 | * Developer Friendly 23 | 24 | ## Getting Started 25 | 26 | * GORM Guides [jinzhu.github.com/gorm](http://jinzhu.github.io/gorm) 27 | 28 | ## Upgrading To V1.0 29 | 30 | * [CHANGELOG](http://jinzhu.github.io/gorm/changelog.html) 31 | 32 | ## Supporting the project 33 | 34 | [![http://patreon.com/jinzhu](http://patreon_public_assets.s3.amazonaws.com/sized/becomeAPatronBanner.png)](http://patreon.com/jinzhu) 35 | 36 | ## Author 37 | 38 | **jinzhu** 39 | 40 | * 41 | * 42 | * 43 | 44 | ## Contributors 45 | 46 | https://github.com/jinzhu/gorm/graphs/contributors 47 | 48 | ## License 49 | 50 | Released under the [MIT License](https://github.com/jinzhu/gorm/blob/master/License). 51 | -------------------------------------------------------------------------------- /lib/logd/log.go: -------------------------------------------------------------------------------- 1 | package logd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "runtime" 8 | "time" 9 | 10 | "github.com/Goss-io/goss/lib" 11 | "github.com/Goss-io/goss/lib/ini" 12 | ) 13 | 14 | //Level . 15 | type LogLevel string 16 | 17 | const ( 18 | Level_INFO LogLevel = "INFO" 19 | Level_DEBUG = "DEBUG" 20 | Level_WARNING = "WARNING" 21 | Level_ERROR = "ERROR" 22 | ) 23 | 24 | var logList = make(chan string, 4096) 25 | 26 | func init() { 27 | go func() { 28 | for { 29 | select { 30 | case logmsg := <-logList: 31 | logFile := logFile() 32 | f, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_RDONLY|os.O_WRONLY, 0777) 33 | if err != nil { 34 | log.Printf("%+v\n", err) 35 | f.Close() 36 | return 37 | } 38 | 39 | _, err = f.WriteString(logmsg) 40 | if err != nil { 41 | log.Printf("%+v\n", err) 42 | } 43 | f.Close() 44 | } 45 | } 46 | }() 47 | } 48 | 49 | func Make(level LogLevel, logpath string, msg interface{}) { 50 | makelog(level, logpath, msg) 51 | } 52 | 53 | func makelog(level LogLevel, logpath string, msg interface{}) { 54 | nodename := ini.GetString("node_name") 55 | content := fmt.Sprintf("%s %s:[%s] %s [%v]\n", lib.Time(), nodename, level, logpath, msg) 56 | 57 | println(content) 58 | logList <- content 59 | } 60 | 61 | //getLogpath 获取产生日志的路径. 62 | func GetLogpath() string { 63 | _, file, line, _ := runtime.Caller(1) 64 | return fmt.Sprintf("%s:%d", file, line) 65 | } 66 | 67 | func logFile() string { 68 | now := time.Now() 69 | year := now.Year() 70 | month := int(now.Month()) 71 | day := now.Day() 72 | hour := now.Hour() 73 | 74 | path := fmt.Sprintf("%s%d/%d/%d/", ini.GetString("log_path"), year, month, day) 75 | 76 | //判断存储路径是否存在. 77 | if !lib.IsExists(path) { 78 | os.MkdirAll(path, 0777) 79 | } 80 | 81 | return fmt.Sprintf("%s%d.log", path, hour) 82 | } 83 | -------------------------------------------------------------------------------- /lib/packet/node.go: -------------------------------------------------------------------------------- 1 | package packet 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "strings" 10 | 11 | "github.com/Goss-io/goss/lib/protocol" 12 | ) 13 | 14 | type NodeTypes string 15 | 16 | const ( 17 | NodeTypes_Api NodeTypes = "api" 18 | NodeTypes_Storage = "stprage" 19 | NodeTypes_Master = "master" 20 | ) 21 | 22 | //NodePacket 节点管理数据. 23 | type NodePacket struct { 24 | Protocol protocol.GossProtocol //协议号. 25 | Types NodeTypes //节点类型. 26 | IP string //节点ip. 27 | Size int64 28 | } 29 | 30 | //NewNode. 31 | func NewNode(types NodeTypes, ip string, proto protocol.GossProtocol) []byte { 32 | body := fmt.Sprintf("%s,%s", types, ip) 33 | buffer := make([]byte, HEADER_LEN+len(body)+PROROCOL_LEN) 34 | //0-4 为协议号. 35 | //4-8 为数据大小. 36 | //>8 为节点数据. 37 | binary.BigEndian.PutUint32(buffer[0:4], uint32(proto)) 38 | binary.BigEndian.PutUint32(buffer[4:8], uint32(len(body))) 39 | copy(buffer[8:], body) 40 | return buffer 41 | } 42 | 43 | func ParseNode(conn net.Conn) (pkt NodePacket, err error) { 44 | pkt = NodePacket{} 45 | //获取协议号. 46 | var num = make([]byte, PROROCOL_LEN) 47 | _, err = io.ReadFull(conn, num) 48 | if err != nil { 49 | log.Printf("%+v\n", err) 50 | return pkt, err 51 | } 52 | pkt.Protocol = protocol.GossProtocol(int(binary.BigEndian.Uint32(num))) 53 | 54 | //获取数据长度. 55 | var bodyBuf = make([]byte, 4) 56 | _, err = io.ReadFull(conn, bodyBuf) 57 | if err != nil { 58 | log.Printf("%+v\n", err) 59 | return pkt, err 60 | } 61 | pkt.Size = int64(binary.BigEndian.Uint32(bodyBuf)) 62 | 63 | //获取数据内容. 64 | var body = make([]byte, pkt.Size) 65 | _, err = io.ReadFull(conn, body) 66 | if err != nil { 67 | log.Printf("%+v\n", err) 68 | return pkt, err 69 | } 70 | 71 | //拆分字符串. 72 | sArr := strings.Split(string(body), ",") 73 | pkt.Types = NodeTypes(sArr[0]) 74 | pkt.IP = sArr[1] 75 | 76 | return pkt, nil 77 | } 78 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/callback_delete.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // Define callbacks for deleting 9 | func init() { 10 | DefaultCallback.Delete().Register("gorm:begin_transaction", beginTransactionCallback) 11 | DefaultCallback.Delete().Register("gorm:before_delete", beforeDeleteCallback) 12 | DefaultCallback.Delete().Register("gorm:delete", deleteCallback) 13 | DefaultCallback.Delete().Register("gorm:after_delete", afterDeleteCallback) 14 | DefaultCallback.Delete().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback) 15 | } 16 | 17 | // beforeDeleteCallback will invoke `BeforeDelete` method before deleting 18 | func beforeDeleteCallback(scope *Scope) { 19 | if scope.DB().HasBlockGlobalUpdate() && !scope.hasConditions() { 20 | scope.Err(errors.New("Missing WHERE clause while deleting")) 21 | return 22 | } 23 | if !scope.HasError() { 24 | scope.CallMethod("BeforeDelete") 25 | } 26 | } 27 | 28 | // deleteCallback used to delete data from database or set deleted_at to current time (when using with soft delete) 29 | func deleteCallback(scope *Scope) { 30 | if !scope.HasError() { 31 | var extraOption string 32 | if str, ok := scope.Get("gorm:delete_option"); ok { 33 | extraOption = fmt.Sprint(str) 34 | } 35 | 36 | deletedAtField, hasDeletedAtField := scope.FieldByName("DeletedAt") 37 | 38 | if !scope.Search.Unscoped && hasDeletedAtField { 39 | scope.Raw(fmt.Sprintf( 40 | "UPDATE %v SET %v=%v%v%v", 41 | scope.QuotedTableName(), 42 | scope.Quote(deletedAtField.DBName), 43 | scope.AddToVars(NowFunc()), 44 | addExtraSpaceIfExist(scope.CombinedConditionSql()), 45 | addExtraSpaceIfExist(extraOption), 46 | )).Exec() 47 | } else { 48 | scope.Raw(fmt.Sprintf( 49 | "DELETE FROM %v%v%v", 50 | scope.QuotedTableName(), 51 | addExtraSpaceIfExist(scope.CombinedConditionSql()), 52 | addExtraSpaceIfExist(extraOption), 53 | )).Exec() 54 | } 55 | } 56 | } 57 | 58 | // afterDeleteCallback will invoke `AfterDelete` method after deleting 59 | func afterDeleteCallback(scope *Scope) { 60 | if !scope.HasError() { 61 | scope.CallMethod("AfterDelete") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/storage/handler/storage.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "hash/crc32" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "strings" 10 | 11 | "github.com/Goss-io/goss/app/storage/conf" 12 | "github.com/Goss-io/goss/lib" 13 | ) 14 | 15 | var storageList []string 16 | 17 | //SelectPath 选择存储路径. 18 | func (s *StorageService) SelectPath(hash string) string { 19 | n := crc32.ChecksumIEEE([]byte(hash)) 20 | num := int64(n) % int64(len(storageList)) 21 | return storageList[num] 22 | } 23 | 24 | //InitStoragePath 初始化存储目录. 25 | func (s *StorageService) InitStoragePath(path string) error { 26 | //判断路径是否存在. 27 | if !lib.IsExists(path) { 28 | if err := os.MkdirAll(path, 0777); err != nil { 29 | return err 30 | } 31 | } 32 | if _, err := s.makeDir(path); err != nil { 33 | log.Printf("%+v\n", err) 34 | return err 35 | } 36 | files, err := ioutil.ReadDir(path) 37 | if err != nil { 38 | log.Printf("%+v\n", err) 39 | return err 40 | } 41 | 42 | list := []string{} 43 | for _, f := range files { 44 | if f.IsDir() { 45 | //创建文件夹. 46 | path := fmt.Sprintf("%s%s/", path, f.Name()) 47 | dirList, err := s.makeDir(path) 48 | if err != nil { 49 | log.Printf("%+v\n", err) 50 | return err 51 | } 52 | list = append(list, dirList...) 53 | } 54 | } 55 | 56 | storageList = list 57 | return nil 58 | } 59 | 60 | //makeDir 创建存储目录. 61 | func (s *StorageService) makeDir(path string) (dirList []string, err error) { 62 | str := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" 63 | sarr := strings.Split(str, "") 64 | var num = 0 65 | for _, v := range sarr { 66 | for _, val := range sarr { 67 | if num > 255 { 68 | break 69 | } 70 | num++ 71 | dirname := fmt.Sprintf("%s%s%s/", path, v, val) 72 | if lib.IsExists(dirname) { 73 | dirname = strings.Replace(dirname, conf.Conf.Node.StorageRoot, "", -1) 74 | dirList = append(dirList, dirname) 75 | continue 76 | } 77 | if err := os.Mkdir(dirname, 0777); err != nil { 78 | log.Printf("%+v\n", err) 79 | return dirList, err 80 | } 81 | 82 | dirname = strings.Replace(dirname, conf.Conf.Node.StorageRoot, "", -1) 83 | dirList = append(dirList, dirname) 84 | } 85 | } 86 | return dirList, nil 87 | } 88 | -------------------------------------------------------------------------------- /app/master/conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/Goss-io/goss/lib" 8 | 9 | "github.com/Goss-io/goss/lib/cmd" 10 | "github.com/Goss-io/goss/lib/ini" 11 | ) 12 | 13 | type Config struct { 14 | Node *nodeConfig 15 | Base *baseConfig 16 | } 17 | 18 | type nodeConfig struct { 19 | IP string 20 | Port int 21 | WebPort int 22 | Name string 23 | Token string 24 | } 25 | 26 | type baseConfig struct { 27 | LogPath string 28 | } 29 | 30 | var Conf *Config 31 | 32 | //Load . 33 | func Load(cmd *cmd.Command) { 34 | iniPath := cmd.Conf 35 | if !lib.IsExists(cmd.Conf) { 36 | log.Println("配置文件不存在=>", iniPath) 37 | os.Exit(0) 38 | return 39 | } 40 | 41 | if err := ini.Load(iniPath); err != nil { 42 | log.Printf("%+v\n", err) 43 | return 44 | } 45 | 46 | cf := &Config{ 47 | Node: parseNodeConfig(cmd), 48 | Base: parseBaseConfig(), 49 | } 50 | 51 | Conf = cf 52 | } 53 | 54 | //node. 55 | func parseNodeConfig(cmd *cmd.Command) *nodeConfig { 56 | name := ini.GetString("node_name") 57 | if len(name) < 1 { 58 | log.Println("node_name 不能为空") 59 | os.Exit(0) 60 | } 61 | 62 | storeip := ini.GetString("node_ip") 63 | if len(storeip) < 1 { 64 | log.Println("node_ip 不能为空") 65 | os.Exit(0) 66 | } 67 | storeport := ini.GetInt("node_port") 68 | if storeport < 1 { 69 | log.Println("node_port 不能为空") 70 | os.Exit(0) 71 | } 72 | webport := ini.GetInt("node_web_port") 73 | if webport < 1 { 74 | log.Println("node_web_port 不能为空") 75 | os.Exit(0) 76 | } 77 | token := ini.GetString("token") 78 | if len(token) < 1 { 79 | log.Println("token 不能为空") 80 | os.Exit(0) 81 | } 82 | 83 | nodeconf := &nodeConfig{ 84 | IP: storeip, 85 | Port: storeport, 86 | WebPort: webport, 87 | Name: name, 88 | Token: token, 89 | } 90 | 91 | return nodeconf 92 | } 93 | 94 | //parseBaseConfig 解析基础配置. 95 | func parseBaseConfig() *baseConfig { 96 | logpath := ini.GetString("log_path") 97 | if len(logpath) < 1 { 98 | log.Println("log_path 不能为空") 99 | os.Exit(0) 100 | } 101 | base := baseConfig{ 102 | LogPath: logpath, 103 | } 104 | return &base 105 | } 106 | -------------------------------------------------------------------------------- /app/storage/conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/Goss-io/goss/lib" 8 | "github.com/Goss-io/goss/lib/cmd" 9 | "github.com/Goss-io/goss/lib/ini" 10 | ) 11 | 12 | type Config struct { 13 | Node *nodeConfig 14 | Base *baseConfig 15 | } 16 | 17 | type nodeConfig struct { 18 | IP string 19 | Port int 20 | Name string 21 | StorageRoot string 22 | MasterNode string 23 | Token string 24 | } 25 | 26 | type baseConfig struct { 27 | LogPath string 28 | } 29 | 30 | var Conf *Config 31 | 32 | //Load . 33 | func Load(cmd *cmd.Command) { 34 | iniPath := cmd.Conf 35 | if !lib.IsExists(cmd.Conf) { 36 | log.Println("配置文件不存在=>", iniPath) 37 | os.Exit(0) 38 | return 39 | } 40 | 41 | if err := ini.Load(iniPath); err != nil { 42 | log.Printf("%+v\n", err) 43 | return 44 | } 45 | 46 | cf := &Config{ 47 | Node: parseNodeConfig(cmd), 48 | Base: parseBaseConfig(), 49 | } 50 | 51 | Conf = cf 52 | } 53 | 54 | //node. 55 | func parseNodeConfig(cmd *cmd.Command) *nodeConfig { 56 | name := ini.GetString("node_name") 57 | if len(name) < 1 { 58 | log.Println("node_name 不能为空") 59 | os.Exit(0) 60 | } 61 | 62 | storageip := ini.GetString("node_ip") 63 | if len(storageip) < 1 { 64 | log.Println("node_ip 不能为空") 65 | os.Exit(0) 66 | } 67 | storageport := ini.GetInt("node_port") 68 | if storageport < 1 { 69 | log.Println("node_port 不能为空") 70 | os.Exit(0) 71 | } 72 | 73 | storageRoot := ini.GetString("storage_root") 74 | if len(storageRoot) < 1 { 75 | log.Println("storage_root 不能为空") 76 | os.Exit(0) 77 | } 78 | masterNode := ini.GetString("master_node") 79 | if len(masterNode) < 1 { 80 | log.Println("master_node 不能为空") 81 | os.Exit(0) 82 | } 83 | token := ini.GetString("token") 84 | if len(token) < 1 { 85 | log.Println("token 不能为空") 86 | os.Exit(0) 87 | } 88 | 89 | nodeconf := &nodeConfig{ 90 | IP: storageip, 91 | Port: storageport, 92 | Name: name, 93 | StorageRoot: storageRoot, 94 | MasterNode: masterNode, 95 | Token: token, 96 | } 97 | 98 | return nodeconf 99 | } 100 | 101 | //parseBaseConfig 解析基础配置. 102 | func parseBaseConfig() *baseConfig { 103 | logpath := ini.GetString("log_path") 104 | if len(logpath) < 1 { 105 | log.Println("log_path 不能为空") 106 | os.Exit(0) 107 | } 108 | base := &baseConfig{ 109 | LogPath: logpath, 110 | } 111 | return base 112 | } 113 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/callback_query.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // Define callbacks for querying 10 | func init() { 11 | DefaultCallback.Query().Register("gorm:query", queryCallback) 12 | DefaultCallback.Query().Register("gorm:preload", preloadCallback) 13 | DefaultCallback.Query().Register("gorm:after_query", afterQueryCallback) 14 | } 15 | 16 | // queryCallback used to query data from database 17 | func queryCallback(scope *Scope) { 18 | defer scope.trace(NowFunc()) 19 | 20 | var ( 21 | isSlice, isPtr bool 22 | resultType reflect.Type 23 | results = scope.IndirectValue() 24 | ) 25 | 26 | if orderBy, ok := scope.Get("gorm:order_by_primary_key"); ok { 27 | if primaryField := scope.PrimaryField(); primaryField != nil { 28 | scope.Search.Order(fmt.Sprintf("%v.%v %v", scope.QuotedTableName(), scope.Quote(primaryField.DBName), orderBy)) 29 | } 30 | } 31 | 32 | if value, ok := scope.Get("gorm:query_destination"); ok { 33 | results = indirect(reflect.ValueOf(value)) 34 | } 35 | 36 | if kind := results.Kind(); kind == reflect.Slice { 37 | isSlice = true 38 | resultType = results.Type().Elem() 39 | results.Set(reflect.MakeSlice(results.Type(), 0, 0)) 40 | 41 | if resultType.Kind() == reflect.Ptr { 42 | isPtr = true 43 | resultType = resultType.Elem() 44 | } 45 | } else if kind != reflect.Struct { 46 | scope.Err(errors.New("unsupported destination, should be slice or struct")) 47 | return 48 | } 49 | 50 | scope.prepareQuerySQL() 51 | 52 | if !scope.HasError() { 53 | scope.db.RowsAffected = 0 54 | if str, ok := scope.Get("gorm:query_option"); ok { 55 | scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str)) 56 | } 57 | 58 | if rows, err := scope.SQLDB().Query(scope.SQL, scope.SQLVars...); scope.Err(err) == nil { 59 | defer rows.Close() 60 | 61 | columns, _ := rows.Columns() 62 | for rows.Next() { 63 | scope.db.RowsAffected++ 64 | 65 | elem := results 66 | if isSlice { 67 | elem = reflect.New(resultType).Elem() 68 | } 69 | 70 | scope.scan(rows, columns, scope.New(elem.Addr().Interface()).Fields()) 71 | 72 | if isSlice { 73 | if isPtr { 74 | results.Set(reflect.Append(results, elem.Addr())) 75 | } else { 76 | results.Set(reflect.Append(results, elem)) 77 | } 78 | } 79 | } 80 | 81 | if err := rows.Err(); err != nil { 82 | scope.Err(err) 83 | } else if scope.db.RowsAffected == 0 && !isSlice { 84 | scope.Err(ErrRecordNotFound) 85 | } 86 | } 87 | } 88 | } 89 | 90 | // afterQueryCallback will invoke `AfterFind` method after querying 91 | func afterQueryCallback(scope *Scope) { 92 | if !scope.HasError() { 93 | scope.CallMethod("AfterFind") 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/storage/handler/http.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | 9 | "github.com/Goss-io/goss/app/storage/conf" 10 | "github.com/Goss-io/goss/lib" 11 | "github.com/Goss-io/goss/lib/logd" 12 | ) 13 | 14 | //httpSrv . 15 | func (s *StorageService) httpSrv() { 16 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 17 | //检查token. 18 | if !s.checkAuth(r.Header.Get("token")) { 19 | lib.Response(w, false, "未授权的访问") 20 | return 21 | } 22 | if r.Method == "PUT" { 23 | //文件内容. 24 | fbody, err := ioutil.ReadAll(r.Body) 25 | if err != nil { 26 | log.Printf("%+v\n", err) 27 | return 28 | } 29 | log.Printf("r.Body:%+v\n", string(fbody)) 30 | 31 | fhash := r.Header.Get("fhash") 32 | fPath, err := s.put(fhash, fbody) 33 | if err != nil { 34 | lib.Response(w, false, err.Error()) 35 | return 36 | } 37 | 38 | lib.Response(w, true, fPath) 39 | return 40 | } 41 | 42 | if r.Method == "GET" { 43 | fpath := r.Header.Get("fpath") 44 | b, err := s.get(fpath) 45 | if err != nil { 46 | log.Printf("err:%+v\n", err) 47 | lib.Response(w, false, err.Error()) 48 | return 49 | } 50 | lib.Response(w, true, "获取成功", b) 51 | return 52 | } 53 | 54 | if r.Method == "DELETE" { 55 | // s.delete(fname) 56 | return 57 | } 58 | 59 | lib.Response(w, false, "禁止访问") 60 | }) 61 | http.ListenAndServe(s.Port, nil) 62 | } 63 | 64 | //checkAuth . 65 | func (s *StorageService) checkAuth(token string) bool { 66 | if token != conf.Conf.Node.Token { 67 | return false 68 | } 69 | return true 70 | } 71 | 72 | //put 记录文件. 73 | func (s *StorageService) put(hash string, fbody []byte) (fPath string, err error) { 74 | //计算文件hash. 75 | fHash := lib.FileHash(fbody) 76 | //验证文件是否损坏. 77 | if fHash != hash { 78 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), "文件hash不一致") 79 | return fPath, errors.New("文件hash不一致") 80 | } 81 | 82 | fPath = s.SelectPath(fHash) + fHash 83 | storePath := conf.Conf.Node.StorageRoot + fPath 84 | err = ioutil.WriteFile(storePath, fbody, 0777) 85 | if err != nil { 86 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), "创建文件失败"+err.Error()) 87 | return fPath, err 88 | } 89 | 90 | return fPath, nil 91 | } 92 | 93 | //获取文件. 94 | func (s *StorageService) get(fpath string) (fbody []byte, err error) { 95 | fpath = conf.Conf.Node.StorageRoot + fpath 96 | fbody, err = ioutil.ReadFile(fpath) 97 | if err != nil { 98 | log.Printf("err:%+v\n", err) 99 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), "读取文件失败:"+err.Error()) 100 | return fbody, err 101 | } 102 | 103 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "文件发送成功") 104 | return fbody, nil 105 | } 106 | -------------------------------------------------------------------------------- /app/storage/handler/node.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net" 7 | "time" 8 | 9 | "github.com/Goss-io/goss/lib" 10 | "github.com/Goss-io/goss/lib/hardware" 11 | "github.com/Goss-io/goss/lib/ini" 12 | "github.com/Goss-io/goss/lib/logd" 13 | "github.com/Goss-io/goss/lib/packet" 14 | "github.com/Goss-io/goss/lib/protocol" 15 | ) 16 | 17 | //connMaster 连接管理节点. 18 | func (s *StorageService) connMaster() { 19 | //上报节点信息 20 | conn := s.conn(s.MasterNode) 21 | 22 | //连接初始化. 23 | if err := s.connInit(conn); err != nil { 24 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), err.Error()) 25 | s.connMaster() 26 | return 27 | } 28 | 29 | for { 30 | var buf = make([]byte, 1024) 31 | _, err := conn.Read(buf) 32 | if err != nil { 33 | s.connMaster() 34 | return 35 | } 36 | } 37 | } 38 | 39 | func (s *StorageService) conn(node string) net.Conn { 40 | conn, err := net.Dial("tcp4", node) 41 | if err != nil { 42 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), "master节点连接失败,稍后重新连接") 43 | time.Sleep(time.Second * 1) 44 | return s.conn(node) 45 | } 46 | 47 | return conn 48 | } 49 | 50 | //connInit 连接初始化. 51 | func (s *StorageService) connInit(conn net.Conn) error { 52 | if err := s.sendAuth(conn); err != nil { 53 | return err 54 | } 55 | 56 | if err := s.sendNodeInfo(conn); err != nil { 57 | return err 58 | } 59 | return nil 60 | } 61 | 62 | //auth 发送授权信息. 63 | func (s *StorageService) sendAuth(conn net.Conn) error { 64 | tokenBuf := []byte(ini.GetString("token")) 65 | buf := packet.New(tokenBuf, tokenBuf, protocol.CONN_AUTH) 66 | _, err := conn.Write(buf) 67 | if err != nil { 68 | return errors.New("授权信息发送失败") 69 | } 70 | 71 | pkt, err := packet.Parse(conn) 72 | if err != nil { 73 | return errors.New("授权失败") 74 | } 75 | 76 | if string(pkt.Body) == "fail" { 77 | return errors.New("授权信息验证失败") 78 | } 79 | 80 | return nil 81 | } 82 | 83 | //sendNodeInfo 上报节点信息. 84 | func (s *StorageService) sendNodeInfo(conn net.Conn) error { 85 | h := hardware.New() 86 | nodeInfo := protocol.NodeInfo{ 87 | Types: string(packet.NodeTypes_Storage), 88 | CpuNum: h.Cpu.Num, 89 | MemSize: h.Mem.Total, 90 | SourceIP: s.Addr, 91 | Name: ini.GetString("node_name"), 92 | } 93 | 94 | content, err := json.Marshal(nodeInfo) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | hashBuf := lib.Hash(string(content)) 100 | buf := packet.New(content, hashBuf, protocol.REPORT_NODE_INFO) 101 | _, err = conn.Write(buf) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | pkt, err := packet.Parse(conn) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | if string(pkt.Body) == "fail" { 112 | return errors.New("发送节点信息失败") 113 | } 114 | 115 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "上报节点信息成功") 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /app/api/conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/Goss-io/goss/lib" 8 | "github.com/Goss-io/goss/lib/cmd" 9 | "github.com/Goss-io/goss/lib/ini" 10 | ) 11 | 12 | type Config struct { 13 | Node *nodeConfig 14 | Db *dbConfig 15 | Base *baseConfig 16 | } 17 | 18 | type dbConfig struct { 19 | Host string 20 | User string 21 | Port int 22 | Name string 23 | Password string 24 | } 25 | 26 | type nodeConfig struct { 27 | IP string 28 | Port int 29 | Name string 30 | Token string 31 | // StoreAddrs []string 32 | } 33 | 34 | type baseConfig struct { 35 | LogPath string //日志存放路径. 36 | } 37 | 38 | var Conf *Config 39 | 40 | //Load . 41 | func Load(cmd *cmd.Command) { 42 | iniPath := cmd.Conf 43 | if !lib.IsExists(cmd.Conf) { 44 | log.Println("配置文件不存在=>", iniPath) 45 | os.Exit(0) 46 | return 47 | } 48 | 49 | if err := ini.Load(iniPath); err != nil { 50 | log.Printf("%+v\n", err) 51 | return 52 | } 53 | 54 | cf := &Config{ 55 | Db: parseDbConfig(), 56 | Node: parseNodeConfig(cmd), 57 | Base: parseBaseConfig(), 58 | } 59 | 60 | Conf = cf 61 | } 62 | 63 | //node. 64 | func parseNodeConfig(cmd *cmd.Command) *nodeConfig { 65 | masterip := ini.GetString("node_ip") 66 | if len(masterip) < 1 { 67 | log.Println("node_ip 不能为空") 68 | os.Exit(0) 69 | } 70 | 71 | masterport := ini.GetInt("node_port") 72 | if masterport < 1 { 73 | log.Println("node_port 不能为空") 74 | os.Exit(0) 75 | } 76 | 77 | name := ini.GetString("node_name") 78 | if len(name) < 1 { 79 | log.Println("node_name 不能为空") 80 | os.Exit(0) 81 | } 82 | 83 | token := ini.GetString("token") 84 | if len(token) < 1 { 85 | log.Println("token 不能为空") 86 | os.Exit(0) 87 | } 88 | // storeAddrs := strings.Split(nodeStoreAddr, ",") 89 | 90 | nodeconf := &nodeConfig{ 91 | IP: masterip, 92 | Port: masterport, 93 | Name: name, 94 | Token: token, 95 | // StoreAddrs: storeAddrs, 96 | } 97 | 98 | return nodeconf 99 | } 100 | 101 | //db. 102 | func parseDbConfig() *dbConfig { 103 | dbHost := ini.GetString("db_host") 104 | if len(dbHost) < 1 { 105 | log.Println("db_host 不能为空") 106 | os.Exit(0) 107 | } 108 | 109 | dbUser := ini.GetString("db_user") 110 | if len(dbUser) < 1 { 111 | log.Println("db_user 不能为空") 112 | os.Exit(0) 113 | } 114 | 115 | dbPort := ini.GetInt("db_port") 116 | if dbPort < 1 { 117 | log.Println("db_port 不能为空") 118 | os.Exit(0) 119 | } 120 | 121 | dbName := ini.GetString("db_name") 122 | if len(dbName) < 1 { 123 | log.Println("db_name 不能为空") 124 | os.Exit(0) 125 | } 126 | 127 | dbPwd := ini.GetString("db_pwd") 128 | if len(dbPwd) < 1 { 129 | log.Println("db_pwd 不能为空") 130 | os.Exit(0) 131 | } 132 | 133 | dbconf := &dbConfig{ 134 | Host: dbHost, 135 | User: dbUser, 136 | Port: dbPort, 137 | Name: dbName, 138 | Password: dbPwd, 139 | } 140 | return dbconf 141 | } 142 | 143 | //parseBaseConfig 解析基础配置. 144 | func parseBaseConfig() *baseConfig { 145 | logpath := ini.GetString("log_path") 146 | if len(logpath) < 1 { 147 | log.Println("log_path 不能为空") 148 | os.Exit(0) 149 | } 150 | base := baseConfig{ 151 | LogPath: logpath, 152 | } 153 | return &base 154 | } 155 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/dialect_sqlite3.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type sqlite3 struct { 11 | commonDialect 12 | } 13 | 14 | func init() { 15 | RegisterDialect("sqlite3", &sqlite3{}) 16 | } 17 | 18 | func (sqlite3) GetName() string { 19 | return "sqlite3" 20 | } 21 | 22 | // Get Data Type for Sqlite Dialect 23 | func (s *sqlite3) DataTypeOf(field *StructField) string { 24 | var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field, s) 25 | 26 | if sqlType == "" { 27 | switch dataValue.Kind() { 28 | case reflect.Bool: 29 | sqlType = "bool" 30 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 31 | if field.IsPrimaryKey { 32 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 33 | sqlType = "integer primary key autoincrement" 34 | } else { 35 | sqlType = "integer" 36 | } 37 | case reflect.Int64, reflect.Uint64: 38 | if field.IsPrimaryKey { 39 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 40 | sqlType = "integer primary key autoincrement" 41 | } else { 42 | sqlType = "bigint" 43 | } 44 | case reflect.Float32, reflect.Float64: 45 | sqlType = "real" 46 | case reflect.String: 47 | if size > 0 && size < 65532 { 48 | sqlType = fmt.Sprintf("varchar(%d)", size) 49 | } else { 50 | sqlType = "text" 51 | } 52 | case reflect.Struct: 53 | if _, ok := dataValue.Interface().(time.Time); ok { 54 | sqlType = "datetime" 55 | } 56 | default: 57 | if IsByteArrayOrSlice(dataValue) { 58 | sqlType = "blob" 59 | } 60 | } 61 | } 62 | 63 | if sqlType == "" { 64 | panic(fmt.Sprintf("invalid sql type %s (%s) for sqlite3", dataValue.Type().Name(), dataValue.Kind().String())) 65 | } 66 | 67 | if strings.TrimSpace(additionalType) == "" { 68 | return sqlType 69 | } 70 | return fmt.Sprintf("%v %v", sqlType, additionalType) 71 | } 72 | 73 | func (s sqlite3) HasIndex(tableName string, indexName string) bool { 74 | var count int 75 | s.db.QueryRow(fmt.Sprintf("SELECT count(*) FROM sqlite_master WHERE tbl_name = ? AND sql LIKE '%%INDEX %v ON%%'", indexName), tableName).Scan(&count) 76 | return count > 0 77 | } 78 | 79 | func (s sqlite3) HasTable(tableName string) bool { 80 | var count int 81 | s.db.QueryRow("SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?", tableName).Scan(&count) 82 | return count > 0 83 | } 84 | 85 | func (s sqlite3) HasColumn(tableName string, columnName string) bool { 86 | var count int 87 | s.db.QueryRow(fmt.Sprintf("SELECT count(*) FROM sqlite_master WHERE tbl_name = ? AND (sql LIKE '%%\"%v\" %%' OR sql LIKE '%%%v %%');\n", columnName, columnName), tableName).Scan(&count) 88 | return count > 0 89 | } 90 | 91 | func (s sqlite3) CurrentDatabase() (name string) { 92 | var ( 93 | ifaces = make([]interface{}, 3) 94 | pointers = make([]*string, 3) 95 | i int 96 | ) 97 | for i = 0; i < 3; i++ { 98 | ifaces[i] = &pointers[i] 99 | } 100 | if err := s.db.QueryRow("PRAGMA database_list").Scan(ifaces...); err != nil { 101 | return 102 | } 103 | if pointers[1] != nil { 104 | name = *pointers[1] 105 | } 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/logger.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "database/sql/driver" 5 | "fmt" 6 | "log" 7 | "os" 8 | "reflect" 9 | "regexp" 10 | "time" 11 | "unicode" 12 | ) 13 | 14 | var ( 15 | defaultLogger = Logger{log.New(os.Stdout, "\r\n", 0)} 16 | sqlRegexp = regexp.MustCompile(`\?`) 17 | numericPlaceHolderRegexp = regexp.MustCompile(`\$\d+`) 18 | ) 19 | 20 | func isPrintable(s string) bool { 21 | for _, r := range s { 22 | if !unicode.IsPrint(r) { 23 | return false 24 | } 25 | } 26 | return true 27 | } 28 | 29 | var LogFormatter = func(values ...interface{}) (messages []interface{}) { 30 | if len(values) > 1 { 31 | var ( 32 | sql string 33 | formattedValues []string 34 | level = values[0] 35 | currentTime = "\n\033[33m[" + NowFunc().Format("2006-01-02 15:04:05") + "]\033[0m" 36 | source = fmt.Sprintf("\033[35m(%v)\033[0m", values[1]) 37 | ) 38 | 39 | messages = []interface{}{source, currentTime} 40 | 41 | if level == "sql" { 42 | // duration 43 | messages = append(messages, fmt.Sprintf(" \033[36;1m[%.2fms]\033[0m ", float64(values[2].(time.Duration).Nanoseconds()/1e4)/100.0)) 44 | // sql 45 | 46 | for _, value := range values[4].([]interface{}) { 47 | indirectValue := reflect.Indirect(reflect.ValueOf(value)) 48 | if indirectValue.IsValid() { 49 | value = indirectValue.Interface() 50 | if t, ok := value.(time.Time); ok { 51 | formattedValues = append(formattedValues, fmt.Sprintf("'%v'", t.Format("2006-01-02 15:04:05"))) 52 | } else if b, ok := value.([]byte); ok { 53 | if str := string(b); isPrintable(str) { 54 | formattedValues = append(formattedValues, fmt.Sprintf("'%v'", str)) 55 | } else { 56 | formattedValues = append(formattedValues, "''") 57 | } 58 | } else if r, ok := value.(driver.Valuer); ok { 59 | if value, err := r.Value(); err == nil && value != nil { 60 | formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value)) 61 | } else { 62 | formattedValues = append(formattedValues, "NULL") 63 | } 64 | } else { 65 | formattedValues = append(formattedValues, fmt.Sprintf("'%v'", value)) 66 | } 67 | } else { 68 | formattedValues = append(formattedValues, "NULL") 69 | } 70 | } 71 | 72 | // differentiate between $n placeholders or else treat like ? 73 | if numericPlaceHolderRegexp.MatchString(values[3].(string)) { 74 | sql = values[3].(string) 75 | for index, value := range formattedValues { 76 | placeholder := fmt.Sprintf(`\$%d([^\d]|$)`, index+1) 77 | sql = regexp.MustCompile(placeholder).ReplaceAllString(sql, value+"$1") 78 | } 79 | } else { 80 | formattedValuesLength := len(formattedValues) 81 | for index, value := range sqlRegexp.Split(values[3].(string), -1) { 82 | sql += value 83 | if index < formattedValuesLength { 84 | sql += formattedValues[index] 85 | } 86 | } 87 | } 88 | 89 | messages = append(messages, sql) 90 | } else { 91 | messages = append(messages, "\033[31;1m") 92 | messages = append(messages, values[2:]...) 93 | messages = append(messages, "\033[0m") 94 | } 95 | } 96 | 97 | return 98 | } 99 | 100 | type logger interface { 101 | Print(v ...interface{}) 102 | } 103 | 104 | // LogWriter log writer interface 105 | type LogWriter interface { 106 | Println(v ...interface{}) 107 | } 108 | 109 | // Logger default logger 110 | type Logger struct { 111 | LogWriter 112 | } 113 | 114 | // Print format & print log 115 | func (logger Logger) Print(values ...interface{}) { 116 | logger.Println(LogFormatter(values...)...) 117 | } 118 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/callback_save.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import "reflect" 4 | 5 | func beginTransactionCallback(scope *Scope) { 6 | scope.Begin() 7 | } 8 | 9 | func commitOrRollbackTransactionCallback(scope *Scope) { 10 | scope.CommitOrRollback() 11 | } 12 | 13 | func saveFieldAsAssociation(scope *Scope, field *Field) (bool, *Relationship) { 14 | if scope.changeableField(field) && !field.IsBlank && !field.IsIgnored { 15 | if value, ok := field.TagSettings["SAVE_ASSOCIATIONS"]; !ok || (value != "false" && value != "skip") { 16 | if relationship := field.Relationship; relationship != nil { 17 | return true, relationship 18 | } 19 | } 20 | } 21 | return false, nil 22 | } 23 | 24 | func saveBeforeAssociationsCallback(scope *Scope) { 25 | if !scope.shouldSaveAssociations() { 26 | return 27 | } 28 | for _, field := range scope.Fields() { 29 | if ok, relationship := saveFieldAsAssociation(scope, field); ok && relationship.Kind == "belongs_to" { 30 | fieldValue := field.Field.Addr().Interface() 31 | scope.Err(scope.NewDB().Save(fieldValue).Error) 32 | if len(relationship.ForeignFieldNames) != 0 { 33 | // set value's foreign key 34 | for idx, fieldName := range relationship.ForeignFieldNames { 35 | associationForeignName := relationship.AssociationForeignDBNames[idx] 36 | if foreignField, ok := scope.New(fieldValue).FieldByName(associationForeignName); ok { 37 | scope.Err(scope.SetColumn(fieldName, foreignField.Field.Interface())) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | func saveAfterAssociationsCallback(scope *Scope) { 46 | if !scope.shouldSaveAssociations() { 47 | return 48 | } 49 | for _, field := range scope.Fields() { 50 | if ok, relationship := saveFieldAsAssociation(scope, field); ok && 51 | (relationship.Kind == "has_one" || relationship.Kind == "has_many" || relationship.Kind == "many_to_many") { 52 | value := field.Field 53 | 54 | switch value.Kind() { 55 | case reflect.Slice: 56 | for i := 0; i < value.Len(); i++ { 57 | newDB := scope.NewDB() 58 | elem := value.Index(i).Addr().Interface() 59 | newScope := newDB.NewScope(elem) 60 | 61 | if relationship.JoinTableHandler == nil && len(relationship.ForeignFieldNames) != 0 { 62 | for idx, fieldName := range relationship.ForeignFieldNames { 63 | associationForeignName := relationship.AssociationForeignDBNames[idx] 64 | if f, ok := scope.FieldByName(associationForeignName); ok { 65 | scope.Err(newScope.SetColumn(fieldName, f.Field.Interface())) 66 | } 67 | } 68 | } 69 | 70 | if relationship.PolymorphicType != "" { 71 | scope.Err(newScope.SetColumn(relationship.PolymorphicType, relationship.PolymorphicValue)) 72 | } 73 | 74 | scope.Err(newDB.Save(elem).Error) 75 | 76 | if joinTableHandler := relationship.JoinTableHandler; joinTableHandler != nil { 77 | scope.Err(joinTableHandler.Add(joinTableHandler, newDB, scope.Value, newScope.Value)) 78 | } 79 | } 80 | default: 81 | elem := value.Addr().Interface() 82 | newScope := scope.New(elem) 83 | if len(relationship.ForeignFieldNames) != 0 { 84 | for idx, fieldName := range relationship.ForeignFieldNames { 85 | associationForeignName := relationship.AssociationForeignDBNames[idx] 86 | if f, ok := scope.FieldByName(associationForeignName); ok { 87 | scope.Err(newScope.SetColumn(fieldName, f.Field.Interface())) 88 | } 89 | } 90 | } 91 | 92 | if relationship.PolymorphicType != "" { 93 | scope.Err(newScope.SetColumn(relationship.PolymorphicType, relationship.PolymorphicValue)) 94 | } 95 | scope.Err(scope.NewDB().Save(elem).Error) 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/callback_update.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // Define callbacks for updating 10 | func init() { 11 | DefaultCallback.Update().Register("gorm:assign_updating_attributes", assignUpdatingAttributesCallback) 12 | DefaultCallback.Update().Register("gorm:begin_transaction", beginTransactionCallback) 13 | DefaultCallback.Update().Register("gorm:before_update", beforeUpdateCallback) 14 | DefaultCallback.Update().Register("gorm:save_before_associations", saveBeforeAssociationsCallback) 15 | DefaultCallback.Update().Register("gorm:update_time_stamp", updateTimeStampForUpdateCallback) 16 | DefaultCallback.Update().Register("gorm:update", updateCallback) 17 | DefaultCallback.Update().Register("gorm:save_after_associations", saveAfterAssociationsCallback) 18 | DefaultCallback.Update().Register("gorm:after_update", afterUpdateCallback) 19 | DefaultCallback.Update().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback) 20 | } 21 | 22 | // assignUpdatingAttributesCallback assign updating attributes to model 23 | func assignUpdatingAttributesCallback(scope *Scope) { 24 | if attrs, ok := scope.InstanceGet("gorm:update_interface"); ok { 25 | if updateMaps, hasUpdate := scope.updatedAttrsWithValues(attrs); hasUpdate { 26 | scope.InstanceSet("gorm:update_attrs", updateMaps) 27 | } else { 28 | scope.SkipLeft() 29 | } 30 | } 31 | } 32 | 33 | // beforeUpdateCallback will invoke `BeforeSave`, `BeforeUpdate` method before updating 34 | func beforeUpdateCallback(scope *Scope) { 35 | if scope.DB().HasBlockGlobalUpdate() && !scope.hasConditions() { 36 | scope.Err(errors.New("Missing WHERE clause while updating")) 37 | return 38 | } 39 | if _, ok := scope.Get("gorm:update_column"); !ok { 40 | if !scope.HasError() { 41 | scope.CallMethod("BeforeSave") 42 | } 43 | if !scope.HasError() { 44 | scope.CallMethod("BeforeUpdate") 45 | } 46 | } 47 | } 48 | 49 | // updateTimeStampForUpdateCallback will set `UpdatedAt` when updating 50 | func updateTimeStampForUpdateCallback(scope *Scope) { 51 | if _, ok := scope.Get("gorm:update_column"); !ok { 52 | scope.SetColumn("UpdatedAt", NowFunc()) 53 | } 54 | } 55 | 56 | // updateCallback the callback used to update data to database 57 | func updateCallback(scope *Scope) { 58 | if !scope.HasError() { 59 | var sqls []string 60 | 61 | if updateAttrs, ok := scope.InstanceGet("gorm:update_attrs"); ok { 62 | for column, value := range updateAttrs.(map[string]interface{}) { 63 | sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(column), scope.AddToVars(value))) 64 | } 65 | } else { 66 | for _, field := range scope.Fields() { 67 | if scope.changeableField(field) { 68 | if !field.IsPrimaryKey && field.IsNormal { 69 | sqls = append(sqls, fmt.Sprintf("%v = %v", scope.Quote(field.DBName), scope.AddToVars(field.Field.Interface()))) 70 | } else if relationship := field.Relationship; relationship != nil && relationship.Kind == "belongs_to" { 71 | for _, foreignKey := range relationship.ForeignDBNames { 72 | if foreignField, ok := scope.FieldByName(foreignKey); ok && !scope.changeableField(foreignField) { 73 | sqls = append(sqls, 74 | fmt.Sprintf("%v = %v", scope.Quote(foreignField.DBName), scope.AddToVars(foreignField.Field.Interface()))) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | var extraOption string 83 | if str, ok := scope.Get("gorm:update_option"); ok { 84 | extraOption = fmt.Sprint(str) 85 | } 86 | 87 | if len(sqls) > 0 { 88 | scope.Raw(fmt.Sprintf( 89 | "UPDATE %v SET %v%v%v", 90 | scope.QuotedTableName(), 91 | strings.Join(sqls, ", "), 92 | addExtraSpaceIfExist(scope.CombinedConditionSql()), 93 | addExtraSpaceIfExist(extraOption), 94 | )).Exec() 95 | } 96 | } 97 | } 98 | 99 | // afterUpdateCallback will invoke `AfterUpdate`, `AfterSave` method after updating 100 | func afterUpdateCallback(scope *Scope) { 101 | if _, ok := scope.Get("gorm:update_column"); !ok { 102 | if !scope.HasError() { 103 | scope.CallMethod("AfterUpdate") 104 | } 105 | if !scope.HasError() { 106 | scope.CallMethod("AfterSave") 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/dialect.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // Dialect interface contains behaviors that differ across SQL database 12 | type Dialect interface { 13 | // GetName get dialect's name 14 | GetName() string 15 | 16 | // SetDB set db for dialect 17 | SetDB(db SQLCommon) 18 | 19 | // BindVar return the placeholder for actual values in SQL statements, in many dbs it is "?", Postgres using $1 20 | BindVar(i int) string 21 | // Quote quotes field name to avoid SQL parsing exceptions by using a reserved word as a field name 22 | Quote(key string) string 23 | // DataTypeOf return data's sql type 24 | DataTypeOf(field *StructField) string 25 | 26 | // HasIndex check has index or not 27 | HasIndex(tableName string, indexName string) bool 28 | // HasForeignKey check has foreign key or not 29 | HasForeignKey(tableName string, foreignKeyName string) bool 30 | // RemoveIndex remove index 31 | RemoveIndex(tableName string, indexName string) error 32 | // HasTable check has table or not 33 | HasTable(tableName string) bool 34 | // HasColumn check has column or not 35 | HasColumn(tableName string, columnName string) bool 36 | 37 | // LimitAndOffsetSQL return generated SQL with Limit and Offset, as mssql has special case 38 | LimitAndOffsetSQL(limit, offset interface{}) string 39 | // SelectFromDummyTable return select values, for most dbs, `SELECT values` just works, mysql needs `SELECT value FROM DUAL` 40 | SelectFromDummyTable() string 41 | // LastInsertIdReturningSuffix most dbs support LastInsertId, but postgres needs to use `RETURNING` 42 | LastInsertIDReturningSuffix(tableName, columnName string) string 43 | 44 | // BuildForeignKeyName returns a foreign key name for the given table, field and reference 45 | BuildForeignKeyName(tableName, field, dest string) string 46 | 47 | // CurrentDatabase return current database name 48 | CurrentDatabase() string 49 | } 50 | 51 | var dialectsMap = map[string]Dialect{} 52 | 53 | func newDialect(name string, db SQLCommon) Dialect { 54 | if value, ok := dialectsMap[name]; ok { 55 | dialect := reflect.New(reflect.TypeOf(value).Elem()).Interface().(Dialect) 56 | dialect.SetDB(db) 57 | return dialect 58 | } 59 | 60 | fmt.Printf("`%v` is not officially supported, running under compatibility mode.\n", name) 61 | commontDialect := &commonDialect{} 62 | commontDialect.SetDB(db) 63 | return commontDialect 64 | } 65 | 66 | // RegisterDialect register new dialect 67 | func RegisterDialect(name string, dialect Dialect) { 68 | dialectsMap[name] = dialect 69 | } 70 | 71 | // ParseFieldStructForDialect get field's sql data type 72 | var ParseFieldStructForDialect = func(field *StructField, dialect Dialect) (fieldValue reflect.Value, sqlType string, size int, additionalType string) { 73 | // Get redirected field type 74 | var ( 75 | reflectType = field.Struct.Type 76 | dataType = field.TagSettings["TYPE"] 77 | ) 78 | 79 | for reflectType.Kind() == reflect.Ptr { 80 | reflectType = reflectType.Elem() 81 | } 82 | 83 | // Get redirected field value 84 | fieldValue = reflect.Indirect(reflect.New(reflectType)) 85 | 86 | if gormDataType, ok := fieldValue.Interface().(interface { 87 | GormDataType(Dialect) string 88 | }); ok { 89 | dataType = gormDataType.GormDataType(dialect) 90 | } 91 | 92 | // Get scanner's real value 93 | var getScannerValue func(reflect.Value) 94 | getScannerValue = func(value reflect.Value) { 95 | fieldValue = value 96 | if _, isScanner := reflect.New(fieldValue.Type()).Interface().(sql.Scanner); isScanner && fieldValue.Kind() == reflect.Struct { 97 | getScannerValue(fieldValue.Field(0)) 98 | } 99 | } 100 | getScannerValue(fieldValue) 101 | 102 | // Default Size 103 | if num, ok := field.TagSettings["SIZE"]; ok { 104 | size, _ = strconv.Atoi(num) 105 | } else { 106 | size = 255 107 | } 108 | 109 | // Default type from tag setting 110 | additionalType = field.TagSettings["NOT NULL"] + " " + field.TagSettings["UNIQUE"] 111 | if value, ok := field.TagSettings["DEFAULT"]; ok { 112 | additionalType = additionalType + " DEFAULT " + value 113 | } 114 | 115 | return fieldValue, dataType, size, strings.TrimSpace(additionalType) 116 | } 117 | -------------------------------------------------------------------------------- /app/master/admin/views/console.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Goss控制台 5 | 6 | 7 | 8 | 9 | 10 | 33 |
34 |

api节点

35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {{range .apiList}} 48 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | {{end}} 60 | 61 |
节点名称节点IPcpu数量内存大小操作
{{.Name}}{{.SourceIP}}{{.CpuNum}}{{.MemSize}} 54 | 55 | 56 | 57 |
62 |
63 | 64 |

存储节点

65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | {{range .storageList}} 78 | 79 | 80 | 81 | 82 | 83 | 88 | 89 | {{end}} 90 | 91 |
节点名称节点IPcpu数量内存大小操作
{{.Name}}{{.SourceIP}}{{.CpuNum}}{{.MemSize}} 84 | 85 | 86 | 87 |
92 |
93 |
94 | 95 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/dialect_postgres.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type postgres struct { 11 | commonDialect 12 | } 13 | 14 | func init() { 15 | RegisterDialect("postgres", &postgres{}) 16 | } 17 | 18 | func (postgres) GetName() string { 19 | return "postgres" 20 | } 21 | 22 | func (postgres) BindVar(i int) string { 23 | return fmt.Sprintf("$%v", i) 24 | } 25 | 26 | func (s *postgres) DataTypeOf(field *StructField) string { 27 | var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field, s) 28 | 29 | if sqlType == "" { 30 | switch dataValue.Kind() { 31 | case reflect.Bool: 32 | sqlType = "boolean" 33 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 34 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { 35 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 36 | sqlType = "serial" 37 | } else { 38 | sqlType = "integer" 39 | } 40 | case reflect.Int64, reflect.Uint64: 41 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { 42 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 43 | sqlType = "bigserial" 44 | } else { 45 | sqlType = "bigint" 46 | } 47 | case reflect.Float32, reflect.Float64: 48 | sqlType = "numeric" 49 | case reflect.String: 50 | if _, ok := field.TagSettings["SIZE"]; !ok { 51 | size = 0 // if SIZE haven't been set, use `text` as the default type, as there are no performance different 52 | } 53 | 54 | if size > 0 && size < 65532 { 55 | sqlType = fmt.Sprintf("varchar(%d)", size) 56 | } else { 57 | sqlType = "text" 58 | } 59 | case reflect.Struct: 60 | if _, ok := dataValue.Interface().(time.Time); ok { 61 | sqlType = "timestamp with time zone" 62 | } 63 | case reflect.Map: 64 | if dataValue.Type().Name() == "Hstore" { 65 | sqlType = "hstore" 66 | } 67 | default: 68 | if IsByteArrayOrSlice(dataValue) { 69 | sqlType = "bytea" 70 | } else if isUUID(dataValue) { 71 | sqlType = "uuid" 72 | } 73 | } 74 | } 75 | 76 | if sqlType == "" { 77 | panic(fmt.Sprintf("invalid sql type %s (%s) for postgres", dataValue.Type().Name(), dataValue.Kind().String())) 78 | } 79 | 80 | if strings.TrimSpace(additionalType) == "" { 81 | return sqlType 82 | } 83 | return fmt.Sprintf("%v %v", sqlType, additionalType) 84 | } 85 | 86 | func (s postgres) HasIndex(tableName string, indexName string) bool { 87 | var count int 88 | s.db.QueryRow("SELECT count(*) FROM pg_indexes WHERE tablename = $1 AND indexname = $2", tableName, indexName).Scan(&count) 89 | return count > 0 90 | } 91 | 92 | func (s postgres) HasForeignKey(tableName string, foreignKeyName string) bool { 93 | var count int 94 | s.db.QueryRow("SELECT count(con.conname) FROM pg_constraint con WHERE $1::regclass::oid = con.conrelid AND con.conname = $2 AND con.contype='f'", tableName, foreignKeyName).Scan(&count) 95 | return count > 0 96 | } 97 | 98 | func (s postgres) HasTable(tableName string) bool { 99 | var count int 100 | s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.tables WHERE table_name = $1 AND table_type = 'BASE TABLE'", tableName).Scan(&count) 101 | return count > 0 102 | } 103 | 104 | func (s postgres) HasColumn(tableName string, columnName string) bool { 105 | var count int 106 | s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.columns WHERE table_name = $1 AND column_name = $2", tableName, columnName).Scan(&count) 107 | return count > 0 108 | } 109 | 110 | func (s postgres) CurrentDatabase() (name string) { 111 | s.db.QueryRow("SELECT CURRENT_DATABASE()").Scan(&name) 112 | return 113 | } 114 | 115 | func (s postgres) LastInsertIDReturningSuffix(tableName, key string) string { 116 | return fmt.Sprintf("RETURNING %v.%v", tableName, key) 117 | } 118 | 119 | func (postgres) SupportLastInsertID() bool { 120 | return false 121 | } 122 | 123 | func isUUID(value reflect.Value) bool { 124 | if value.Kind() != reflect.Array || value.Type().Len() != 16 { 125 | return false 126 | } 127 | typename := value.Type().Name() 128 | lower := strings.ToLower(typename) 129 | return "uuid" == lower || "guid" == lower 130 | } 131 | -------------------------------------------------------------------------------- /app/api/handler/node.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io" 7 | "math/rand" 8 | "net" 9 | "time" 10 | 11 | "github.com/Goss-io/goss/lib" 12 | "github.com/Goss-io/goss/lib/hardware" 13 | "github.com/Goss-io/goss/lib/ini" 14 | "github.com/Goss-io/goss/lib/logd" 15 | "github.com/Goss-io/goss/lib/packet" 16 | "github.com/Goss-io/goss/lib/protocol" 17 | ) 18 | 19 | //connMaster . 20 | func (a *APIService) connMaster() { 21 | conn := a.conn(a.MasterNode) 22 | //连接初始化 23 | if err := a.conninit(conn); err != nil { 24 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), err.Error()) 25 | time.Sleep(time.Second * 1) 26 | a.connMaster() 27 | return 28 | } 29 | 30 | for { 31 | pkt, err := packet.Parse(conn) 32 | if err != nil && err == io.EOF { 33 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), "master节点断开连接,稍后重新连接master节点") 34 | a.connMaster() 35 | return 36 | } 37 | 38 | //判断协议类型. 39 | if pkt.Protocol == protocol.ADD_NODE { 40 | ip := string(pkt.Body) 41 | //新增节点. 42 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "新增存储节点:"+ip) 43 | a.Storage = append(a.Storage, ip) 44 | } 45 | 46 | if pkt.Protocol == protocol.REMOVE_NODE { 47 | ip := string(pkt.Body) 48 | //删除节点. 49 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "收到master节点要求与:"+ip+"节点断开的消息") 50 | a.RemoveStorageNode(ip) 51 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "断开成功") 52 | } 53 | } 54 | } 55 | 56 | func (a *APIService) RemoveStorageNode(nodeip string) { 57 | for index, v := range a.Storage { 58 | if v == nodeip { 59 | a.Storage = append(a.Storage[:index], a.Storage[index+1:]...) 60 | } 61 | } 62 | } 63 | 64 | //conn . 65 | func (a *APIService) conn(node string) net.Conn { 66 | conn, err := net.Dial("tcp4", node) 67 | if err != nil { 68 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), "master节点连接失败,稍后重新连接") 69 | time.Sleep(time.Second * 1) 70 | return a.conn(node) 71 | } 72 | 73 | return conn 74 | } 75 | 76 | //connInit 连接初始化. 77 | func (a *APIService) conninit(conn net.Conn) error { 78 | //向主节点发送授权信息. 79 | if err := a.sendAuth(conn); err != nil { 80 | return err 81 | } 82 | 83 | //发送节点信息. 84 | if err := a.sendNodeInfo(conn); err != nil { 85 | return err 86 | } 87 | return nil 88 | } 89 | 90 | //auth 发送授权信息. 91 | func (a *APIService) sendAuth(conn net.Conn) error { 92 | tokenBuf := []byte(ini.GetString("token")) 93 | buf := packet.New(tokenBuf, tokenBuf, protocol.CONN_AUTH) 94 | _, err := conn.Write(buf) 95 | if err != nil { 96 | return errors.New("授权信息发送失败") 97 | } 98 | 99 | pkt, err := packet.Parse(conn) 100 | if err != nil { 101 | return errors.New("授权失败") 102 | } 103 | 104 | if string(pkt.Body) == "fail" { 105 | return errors.New("授权信息验证失败") 106 | } 107 | 108 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "授权成功") 109 | return nil 110 | } 111 | 112 | //sendNodeInfo 上报节点信息. 113 | func (a *APIService) sendNodeInfo(conn net.Conn) error { 114 | h := hardware.New() 115 | nodeInfo := protocol.NodeInfo{ 116 | Types: string(packet.NodeTypes_Api), 117 | CpuNum: h.Cpu.Num, 118 | MemSize: h.Mem.Total, 119 | SourceIP: a.Addr, 120 | Name: ini.GetString("node_name"), 121 | } 122 | 123 | content, err := json.Marshal(nodeInfo) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | hashBuf := lib.Hash(string(content)) 129 | buf := packet.New(content, hashBuf, protocol.REPORT_NODE_INFO) 130 | _, err = conn.Write(buf) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | pkt, err := packet.Parse(conn) 136 | if err != nil { 137 | return err 138 | } 139 | 140 | if string(pkt.Body) == "fail" { 141 | return errors.New("发送节点信息失败") 142 | } 143 | 144 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "上报节点信息成功") 145 | return nil 146 | } 147 | 148 | func (a *APIService) SelectNode(nodenum int) []string { 149 | rand.Seed(time.Now().UnixNano()) 150 | list := []string{} 151 | for _, v := range a.Storage { 152 | list = append(list, v) 153 | } 154 | 155 | nodeipList := []string{} 156 | num := 0 157 | for { 158 | if num >= nodenum { 159 | break 160 | } 161 | index := rand.Int() % len(list) 162 | addr := list[index] 163 | 164 | if !lib.InArray(addr, nodeipList) { 165 | num++ 166 | nodeipList = append(nodeipList, addr) 167 | } 168 | } 169 | return nodeipList 170 | } 171 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/search.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | type search struct { 9 | db *DB 10 | whereConditions []map[string]interface{} 11 | orConditions []map[string]interface{} 12 | notConditions []map[string]interface{} 13 | havingConditions []map[string]interface{} 14 | joinConditions []map[string]interface{} 15 | initAttrs []interface{} 16 | assignAttrs []interface{} 17 | selects map[string]interface{} 18 | omits []string 19 | orders []interface{} 20 | preload []searchPreload 21 | offset interface{} 22 | limit interface{} 23 | group string 24 | tableName string 25 | raw bool 26 | Unscoped bool 27 | ignoreOrderQuery bool 28 | } 29 | 30 | type searchPreload struct { 31 | schema string 32 | conditions []interface{} 33 | } 34 | 35 | func (s *search) clone() *search { 36 | clone := *s 37 | return &clone 38 | } 39 | 40 | func (s *search) Where(query interface{}, values ...interface{}) *search { 41 | s.whereConditions = append(s.whereConditions, map[string]interface{}{"query": query, "args": values}) 42 | return s 43 | } 44 | 45 | func (s *search) Not(query interface{}, values ...interface{}) *search { 46 | s.notConditions = append(s.notConditions, map[string]interface{}{"query": query, "args": values}) 47 | return s 48 | } 49 | 50 | func (s *search) Or(query interface{}, values ...interface{}) *search { 51 | s.orConditions = append(s.orConditions, map[string]interface{}{"query": query, "args": values}) 52 | return s 53 | } 54 | 55 | func (s *search) Attrs(attrs ...interface{}) *search { 56 | s.initAttrs = append(s.initAttrs, toSearchableMap(attrs...)) 57 | return s 58 | } 59 | 60 | func (s *search) Assign(attrs ...interface{}) *search { 61 | s.assignAttrs = append(s.assignAttrs, toSearchableMap(attrs...)) 62 | return s 63 | } 64 | 65 | func (s *search) Order(value interface{}, reorder ...bool) *search { 66 | if len(reorder) > 0 && reorder[0] { 67 | s.orders = []interface{}{} 68 | } 69 | 70 | if value != nil && value != "" { 71 | s.orders = append(s.orders, value) 72 | } 73 | return s 74 | } 75 | 76 | var distinctSQLRegexp = regexp.MustCompile(`(?i)distinct[^a-z]+[a-z]+`) 77 | 78 | func (s *search) Select(query interface{}, args ...interface{}) *search { 79 | if distinctSQLRegexp.MatchString(fmt.Sprint(query)) { 80 | s.ignoreOrderQuery = true 81 | } 82 | 83 | s.selects = map[string]interface{}{"query": query, "args": args} 84 | return s 85 | } 86 | 87 | func (s *search) Omit(columns ...string) *search { 88 | s.omits = columns 89 | return s 90 | } 91 | 92 | func (s *search) Limit(limit interface{}) *search { 93 | s.limit = limit 94 | return s 95 | } 96 | 97 | func (s *search) Offset(offset interface{}) *search { 98 | s.offset = offset 99 | return s 100 | } 101 | 102 | func (s *search) Group(query string) *search { 103 | s.group = s.getInterfaceAsSQL(query) 104 | return s 105 | } 106 | 107 | func (s *search) Having(query string, values ...interface{}) *search { 108 | s.havingConditions = append(s.havingConditions, map[string]interface{}{"query": query, "args": values}) 109 | return s 110 | } 111 | 112 | func (s *search) Joins(query string, values ...interface{}) *search { 113 | s.joinConditions = append(s.joinConditions, map[string]interface{}{"query": query, "args": values}) 114 | return s 115 | } 116 | 117 | func (s *search) Preload(schema string, values ...interface{}) *search { 118 | var preloads []searchPreload 119 | for _, preload := range s.preload { 120 | if preload.schema != schema { 121 | preloads = append(preloads, preload) 122 | } 123 | } 124 | preloads = append(preloads, searchPreload{schema, values}) 125 | s.preload = preloads 126 | return s 127 | } 128 | 129 | func (s *search) Raw(b bool) *search { 130 | s.raw = b 131 | return s 132 | } 133 | 134 | func (s *search) unscoped() *search { 135 | s.Unscoped = true 136 | return s 137 | } 138 | 139 | func (s *search) Table(name string) *search { 140 | s.tableName = name 141 | return s 142 | } 143 | 144 | func (s *search) getInterfaceAsSQL(value interface{}) (str string) { 145 | switch value.(type) { 146 | case string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: 147 | str = fmt.Sprintf("%v", value) 148 | default: 149 | s.db.AddError(ErrInvalidSQL) 150 | } 151 | 152 | if str == "-1" { 153 | return "" 154 | } 155 | return 156 | } 157 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/dialect_common.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // DefaultForeignKeyNamer contains the default foreign key name generator method 13 | type DefaultForeignKeyNamer struct { 14 | } 15 | 16 | type commonDialect struct { 17 | db SQLCommon 18 | DefaultForeignKeyNamer 19 | } 20 | 21 | func init() { 22 | RegisterDialect("common", &commonDialect{}) 23 | } 24 | 25 | func (commonDialect) GetName() string { 26 | return "common" 27 | } 28 | 29 | func (s *commonDialect) SetDB(db SQLCommon) { 30 | s.db = db 31 | } 32 | 33 | func (commonDialect) BindVar(i int) string { 34 | return "$$$" // ? 35 | } 36 | 37 | func (commonDialect) Quote(key string) string { 38 | return fmt.Sprintf(`"%s"`, key) 39 | } 40 | 41 | func (s *commonDialect) DataTypeOf(field *StructField) string { 42 | var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field, s) 43 | 44 | if sqlType == "" { 45 | switch dataValue.Kind() { 46 | case reflect.Bool: 47 | sqlType = "BOOLEAN" 48 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 49 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok { 50 | sqlType = "INTEGER AUTO_INCREMENT" 51 | } else { 52 | sqlType = "INTEGER" 53 | } 54 | case reflect.Int64, reflect.Uint64: 55 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok { 56 | sqlType = "BIGINT AUTO_INCREMENT" 57 | } else { 58 | sqlType = "BIGINT" 59 | } 60 | case reflect.Float32, reflect.Float64: 61 | sqlType = "FLOAT" 62 | case reflect.String: 63 | if size > 0 && size < 65532 { 64 | sqlType = fmt.Sprintf("VARCHAR(%d)", size) 65 | } else { 66 | sqlType = "VARCHAR(65532)" 67 | } 68 | case reflect.Struct: 69 | if _, ok := dataValue.Interface().(time.Time); ok { 70 | sqlType = "TIMESTAMP" 71 | } 72 | default: 73 | if _, ok := dataValue.Interface().([]byte); ok { 74 | if size > 0 && size < 65532 { 75 | sqlType = fmt.Sprintf("BINARY(%d)", size) 76 | } else { 77 | sqlType = "BINARY(65532)" 78 | } 79 | } 80 | } 81 | } 82 | 83 | if sqlType == "" { 84 | panic(fmt.Sprintf("invalid sql type %s (%s) for commonDialect", dataValue.Type().Name(), dataValue.Kind().String())) 85 | } 86 | 87 | if strings.TrimSpace(additionalType) == "" { 88 | return sqlType 89 | } 90 | return fmt.Sprintf("%v %v", sqlType, additionalType) 91 | } 92 | 93 | func (s commonDialect) HasIndex(tableName string, indexName string) bool { 94 | var count int 95 | s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema = ? AND table_name = ? AND index_name = ?", s.CurrentDatabase(), tableName, indexName).Scan(&count) 96 | return count > 0 97 | } 98 | 99 | func (s commonDialect) RemoveIndex(tableName string, indexName string) error { 100 | _, err := s.db.Exec(fmt.Sprintf("DROP INDEX %v", indexName)) 101 | return err 102 | } 103 | 104 | func (s commonDialect) HasForeignKey(tableName string, foreignKeyName string) bool { 105 | return false 106 | } 107 | 108 | func (s commonDialect) HasTable(tableName string) bool { 109 | var count int 110 | s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = ? AND table_name = ?", s.CurrentDatabase(), tableName).Scan(&count) 111 | return count > 0 112 | } 113 | 114 | func (s commonDialect) HasColumn(tableName string, columnName string) bool { 115 | var count int 116 | s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = ? AND table_name = ? AND column_name = ?", s.CurrentDatabase(), tableName, columnName).Scan(&count) 117 | return count > 0 118 | } 119 | 120 | func (s commonDialect) CurrentDatabase() (name string) { 121 | s.db.QueryRow("SELECT DATABASE()").Scan(&name) 122 | return 123 | } 124 | 125 | func (commonDialect) LimitAndOffsetSQL(limit, offset interface{}) (sql string) { 126 | if limit != nil { 127 | if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 { 128 | sql += fmt.Sprintf(" LIMIT %d", parsedLimit) 129 | } 130 | } 131 | if offset != nil { 132 | if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset >= 0 { 133 | sql += fmt.Sprintf(" OFFSET %d", parsedOffset) 134 | } 135 | } 136 | return 137 | } 138 | 139 | func (commonDialect) SelectFromDummyTable() string { 140 | return "" 141 | } 142 | 143 | func (commonDialect) LastInsertIDReturningSuffix(tableName, columnName string) string { 144 | return "" 145 | } 146 | 147 | func (DefaultForeignKeyNamer) BuildForeignKeyName(tableName, field, dest string) string { 148 | keyName := fmt.Sprintf("%s_%s_%s_foreign", tableName, field, dest) 149 | keyName = regexp.MustCompile("(_*[^a-zA-Z]+_*|_+)").ReplaceAllString(keyName, "_") 150 | return keyName 151 | } 152 | 153 | // IsByteArrayOrSlice returns true of the reflected value is an array or slice 154 | func IsByteArrayOrSlice(value reflect.Value) bool { 155 | return (value.Kind() == reflect.Array || value.Kind() == reflect.Slice) && value.Type().Elem() == reflect.TypeOf(uint8(0)) 156 | } 157 | -------------------------------------------------------------------------------- /app/master/handler/net.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net" 10 | 11 | "github.com/Goss-io/goss/lib" 12 | "github.com/Goss-io/goss/lib/ini" 13 | "github.com/Goss-io/goss/lib/logd" 14 | "github.com/Goss-io/goss/lib/packet" 15 | "github.com/Goss-io/goss/lib/protocol" 16 | ) 17 | 18 | type NodeParams struct { 19 | Conn net.Conn 20 | Types packet.NodeTypes 21 | } 22 | 23 | type MasterService struct { 24 | Conn map[string]net.Conn 25 | Auth map[string]bool 26 | Port string 27 | } 28 | 29 | //NewMaster . 30 | func NewMaster() *MasterService { 31 | return &MasterService{ 32 | Conn: make(map[string]net.Conn), 33 | Auth: make(map[string]bool), 34 | Port: fmt.Sprintf(":%d", ini.GetInt("node_port")), 35 | } 36 | } 37 | 38 | //Start. 39 | func (m *MasterService) Start() { 40 | go NewAdmin() 41 | m.listen() 42 | select {} 43 | } 44 | 45 | //listen . 46 | func (m *MasterService) listen() { 47 | listener, err := net.Listen("tcp4", m.Port) 48 | if err != nil { 49 | log.Panicln(err) 50 | } 51 | 52 | defer listener.Close() 53 | for { 54 | conn, err := listener.Accept() 55 | if err != nil { 56 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), err.Error()) 57 | continue 58 | } 59 | 60 | //验证授权信息. 61 | ip := conn.RemoteAddr().String() 62 | if err := m.connInit(conn, ip); err != nil { 63 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), err.Error()) 64 | continue 65 | } 66 | 67 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "收到来自:"+ip+"的连接请求") 68 | go m.handler(ip, conn) 69 | } 70 | } 71 | 72 | //connInit 连接初始化. 73 | func (m *MasterService) connInit(conn net.Conn, ip string) error { 74 | //验证授权信息. 75 | if err := m.checkAuth(conn, ip); err != nil { 76 | buf := packet.New([]byte("fail"), lib.Hash("fail"), protocol.MSG) 77 | conn.Write(buf) 78 | return err 79 | } 80 | buf := packet.New([]byte("success"), lib.Hash("success"), protocol.MSG) 81 | _, err := conn.Write(buf) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | //接收节点信息. 87 | if err := m.parseNodeInfo(conn, ip); err != nil { 88 | buf := packet.New([]byte("fail"), lib.Hash("fail"), protocol.MSG) 89 | conn.Write(buf) 90 | return err 91 | } 92 | return nil 93 | } 94 | 95 | //parseNodeInfo . 96 | func (m *MasterService) parseNodeInfo(conn net.Conn, ip string) error { 97 | pkt, err := packet.Parse(conn) 98 | if err != nil { 99 | return err 100 | } 101 | //判读协议类型. 102 | if pkt.Protocol == protocol.REPORT_NODE_INFO { 103 | n := protocol.NodeInfo{} 104 | if err = json.Unmarshal(pkt.Body, &n); err != nil { 105 | return err 106 | } 107 | 108 | node := Node{ 109 | Name: n.Name, 110 | SourceIP: n.SourceIP, 111 | CpuNum: n.CpuNum, 112 | MemSize: n.MemSize, 113 | IP: ip, 114 | CreateAt: lib.Time(), 115 | Types: packet.NodeTypes(n.Types), 116 | } 117 | 118 | GossNode = append(GossNode, node) 119 | m.Conn[node.SourceIP] = conn 120 | 121 | buf := packet.New([]byte("success"), lib.Hash("success"), protocol.MSG) 122 | _, err = conn.Write(buf) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | //新存储节点上线,通知所有的api节点. 128 | if node.Types == packet.NodeTypes_Storage { 129 | //通知api节点. 130 | apiList := GetApiList() 131 | for _, v := range apiList { 132 | pkt := packet.New([]byte(node.SourceIP), lib.Hash(node.SourceIP), protocol.ADD_NODE) 133 | _, err = m.Conn[v.SourceIP].Write(pkt) 134 | if err != nil { 135 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), "通知api节点:"+node.SourceIP+"新增storage节点失败,稍后重新通知") 136 | RemoveNode(m, ip) 137 | return err 138 | } 139 | 140 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "通知api节点,新增存储节点:"+node.SourceIP+"成功") 141 | } 142 | } 143 | 144 | if node.Types == packet.NodeTypes_Api { 145 | //告知新上线的api节点多有的storage节点ip. 146 | storageList := GetStorageList() 147 | for _, v := range storageList { 148 | pktMsg := packet.New([]byte(v.SourceIP), lib.Hash(v.SourceIP), protocol.ADD_NODE) 149 | _, err = m.Conn[node.SourceIP].Write(pktMsg) 150 | if err != nil { 151 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), "通知api节点:"+v.SourceIP+"storage节点失败,稍后重新通知") 152 | RemoveNode(m, ip) 153 | return err 154 | } 155 | 156 | logd.Make(logd.Level_INFO, logd.GetLogpath(), "通知api节点,新增存储节点:"+v.SourceIP+"成功") 157 | } 158 | } 159 | } 160 | return nil 161 | } 162 | 163 | //checkAuth . 164 | func (m *MasterService) checkAuth(conn net.Conn, ip string) error { 165 | pkt, err := packet.Parse(conn) 166 | if err != nil { 167 | return err 168 | } 169 | 170 | //判读协议. 171 | if pkt.Protocol != protocol.CONN_AUTH { 172 | return errors.New("协议错误") 173 | } 174 | 175 | //验证授权信息是否正确. 176 | if string(pkt.Body) != ini.GetString("token") { 177 | return errors.New("授权失败") 178 | } 179 | 180 | m.Auth[ip] = true 181 | return nil 182 | } 183 | 184 | //handler . 185 | func (m *MasterService) handler(ip string, conn net.Conn) { 186 | defer conn.Close() 187 | for { 188 | //验证是否已经授权. 189 | if !m.Auth[ip] { 190 | conn.Write([]byte("fail")) 191 | return 192 | } 193 | 194 | pkt, err := packet.ParseNode(conn) 195 | if err != nil && err == io.EOF { 196 | logd.Make(logd.Level_WARNING, logd.GetLogpath(), ip+"断开连接") 197 | //从节点列表中移除. 198 | RemoveNode(m, ip) 199 | return 200 | } 201 | 202 | //判断协议. 203 | if pkt.Protocol == protocol.ADD_NODE { 204 | //新增节点信息. 205 | info := Node{ 206 | Types: pkt.Types, 207 | IP: ip, 208 | SourceIP: pkt.IP, 209 | CreateAt: lib.Time(), 210 | } 211 | GossNode = append(GossNode, info) 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/dialect_mysql.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "crypto/sha1" 5 | "fmt" 6 | "reflect" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | "time" 11 | "unicode/utf8" 12 | ) 13 | 14 | type mysql struct { 15 | commonDialect 16 | } 17 | 18 | func init() { 19 | RegisterDialect("mysql", &mysql{}) 20 | } 21 | 22 | func (mysql) GetName() string { 23 | return "mysql" 24 | } 25 | 26 | func (mysql) Quote(key string) string { 27 | return fmt.Sprintf("`%s`", key) 28 | } 29 | 30 | // Get Data Type for MySQL Dialect 31 | func (s *mysql) DataTypeOf(field *StructField) string { 32 | var dataValue, sqlType, size, additionalType = ParseFieldStructForDialect(field, s) 33 | 34 | // MySQL allows only one auto increment column per table, and it must 35 | // be a KEY column. 36 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok { 37 | if _, ok = field.TagSettings["INDEX"]; !ok && !field.IsPrimaryKey { 38 | delete(field.TagSettings, "AUTO_INCREMENT") 39 | } 40 | } 41 | 42 | if sqlType == "" { 43 | switch dataValue.Kind() { 44 | case reflect.Bool: 45 | sqlType = "boolean" 46 | case reflect.Int8: 47 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { 48 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 49 | sqlType = "tinyint AUTO_INCREMENT" 50 | } else { 51 | sqlType = "tinyint" 52 | } 53 | case reflect.Int, reflect.Int16, reflect.Int32: 54 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { 55 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 56 | sqlType = "int AUTO_INCREMENT" 57 | } else { 58 | sqlType = "int" 59 | } 60 | case reflect.Uint8: 61 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { 62 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 63 | sqlType = "tinyint unsigned AUTO_INCREMENT" 64 | } else { 65 | sqlType = "tinyint unsigned" 66 | } 67 | case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uintptr: 68 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { 69 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 70 | sqlType = "int unsigned AUTO_INCREMENT" 71 | } else { 72 | sqlType = "int unsigned" 73 | } 74 | case reflect.Int64: 75 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { 76 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 77 | sqlType = "bigint AUTO_INCREMENT" 78 | } else { 79 | sqlType = "bigint" 80 | } 81 | case reflect.Uint64: 82 | if _, ok := field.TagSettings["AUTO_INCREMENT"]; ok || field.IsPrimaryKey { 83 | field.TagSettings["AUTO_INCREMENT"] = "AUTO_INCREMENT" 84 | sqlType = "bigint unsigned AUTO_INCREMENT" 85 | } else { 86 | sqlType = "bigint unsigned" 87 | } 88 | case reflect.Float32, reflect.Float64: 89 | sqlType = "double" 90 | case reflect.String: 91 | if size > 0 && size < 65532 { 92 | sqlType = fmt.Sprintf("varchar(%d)", size) 93 | } else { 94 | sqlType = "longtext" 95 | } 96 | case reflect.Struct: 97 | if _, ok := dataValue.Interface().(time.Time); ok { 98 | if _, ok := field.TagSettings["NOT NULL"]; ok { 99 | sqlType = "timestamp" 100 | } else { 101 | sqlType = "timestamp NULL" 102 | } 103 | } 104 | default: 105 | if IsByteArrayOrSlice(dataValue) { 106 | if size > 0 && size < 65532 { 107 | sqlType = fmt.Sprintf("varbinary(%d)", size) 108 | } else { 109 | sqlType = "longblob" 110 | } 111 | } 112 | } 113 | } 114 | 115 | if sqlType == "" { 116 | panic(fmt.Sprintf("invalid sql type %s (%s) for mysql", dataValue.Type().Name(), dataValue.Kind().String())) 117 | } 118 | 119 | if strings.TrimSpace(additionalType) == "" { 120 | return sqlType 121 | } 122 | return fmt.Sprintf("%v %v", sqlType, additionalType) 123 | } 124 | 125 | func (s mysql) RemoveIndex(tableName string, indexName string) error { 126 | _, err := s.db.Exec(fmt.Sprintf("DROP INDEX %v ON %v", indexName, s.Quote(tableName))) 127 | return err 128 | } 129 | 130 | func (s mysql) LimitAndOffsetSQL(limit, offset interface{}) (sql string) { 131 | if limit != nil { 132 | if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 { 133 | sql += fmt.Sprintf(" LIMIT %d", parsedLimit) 134 | 135 | if offset != nil { 136 | if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset >= 0 { 137 | sql += fmt.Sprintf(" OFFSET %d", parsedOffset) 138 | } 139 | } 140 | } 141 | } 142 | return 143 | } 144 | 145 | func (s mysql) HasForeignKey(tableName string, foreignKeyName string) bool { 146 | var count int 147 | s.db.QueryRow("SELECT count(*) FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=? AND TABLE_NAME=? AND CONSTRAINT_NAME=? AND CONSTRAINT_TYPE='FOREIGN KEY'", s.CurrentDatabase(), tableName, foreignKeyName).Scan(&count) 148 | return count > 0 149 | } 150 | 151 | func (s mysql) CurrentDatabase() (name string) { 152 | s.db.QueryRow("SELECT DATABASE()").Scan(&name) 153 | return 154 | } 155 | 156 | func (mysql) SelectFromDummyTable() string { 157 | return "FROM DUAL" 158 | } 159 | 160 | func (s mysql) BuildForeignKeyName(tableName, field, dest string) string { 161 | keyName := s.commonDialect.BuildForeignKeyName(tableName, field, dest) 162 | if utf8.RuneCountInString(keyName) <= 64 { 163 | return keyName 164 | } 165 | h := sha1.New() 166 | h.Write([]byte(keyName)) 167 | bs := h.Sum(nil) 168 | 169 | // sha1 is 40 digits, keep first 24 characters of destination 170 | destRunes := []rune(regexp.MustCompile("(_*[^a-zA-Z]+_*|_+)").ReplaceAllString(dest, "_")) 171 | if len(destRunes) > 24 { 172 | destRunes = destRunes[:24] 173 | } 174 | 175 | return fmt.Sprintf("%s%x", string(destRunes), bs) 176 | } 177 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/callback_create.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Define callbacks for creating 9 | func init() { 10 | DefaultCallback.Create().Register("gorm:begin_transaction", beginTransactionCallback) 11 | DefaultCallback.Create().Register("gorm:before_create", beforeCreateCallback) 12 | DefaultCallback.Create().Register("gorm:save_before_associations", saveBeforeAssociationsCallback) 13 | DefaultCallback.Create().Register("gorm:update_time_stamp", updateTimeStampForCreateCallback) 14 | DefaultCallback.Create().Register("gorm:create", createCallback) 15 | DefaultCallback.Create().Register("gorm:force_reload_after_create", forceReloadAfterCreateCallback) 16 | DefaultCallback.Create().Register("gorm:save_after_associations", saveAfterAssociationsCallback) 17 | DefaultCallback.Create().Register("gorm:after_create", afterCreateCallback) 18 | DefaultCallback.Create().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback) 19 | } 20 | 21 | // beforeCreateCallback will invoke `BeforeSave`, `BeforeCreate` method before creating 22 | func beforeCreateCallback(scope *Scope) { 23 | if !scope.HasError() { 24 | scope.CallMethod("BeforeSave") 25 | } 26 | if !scope.HasError() { 27 | scope.CallMethod("BeforeCreate") 28 | } 29 | } 30 | 31 | // updateTimeStampForCreateCallback will set `CreatedAt`, `UpdatedAt` when creating 32 | func updateTimeStampForCreateCallback(scope *Scope) { 33 | if !scope.HasError() { 34 | now := NowFunc() 35 | scope.SetColumn("CreatedAt", now) 36 | scope.SetColumn("UpdatedAt", now) 37 | } 38 | } 39 | 40 | // createCallback the callback used to insert data into database 41 | func createCallback(scope *Scope) { 42 | if !scope.HasError() { 43 | defer scope.trace(NowFunc()) 44 | 45 | var ( 46 | columns, placeholders []string 47 | blankColumnsWithDefaultValue []string 48 | ) 49 | 50 | for _, field := range scope.Fields() { 51 | if scope.changeableField(field) { 52 | if field.IsNormal { 53 | if field.IsBlank && field.HasDefaultValue { 54 | blankColumnsWithDefaultValue = append(blankColumnsWithDefaultValue, scope.Quote(field.DBName)) 55 | scope.InstanceSet("gorm:blank_columns_with_default_value", blankColumnsWithDefaultValue) 56 | } else if !field.IsPrimaryKey || !field.IsBlank { 57 | columns = append(columns, scope.Quote(field.DBName)) 58 | placeholders = append(placeholders, scope.AddToVars(field.Field.Interface())) 59 | } 60 | } else if field.Relationship != nil && field.Relationship.Kind == "belongs_to" { 61 | for _, foreignKey := range field.Relationship.ForeignDBNames { 62 | if foreignField, ok := scope.FieldByName(foreignKey); ok && !scope.changeableField(foreignField) { 63 | columns = append(columns, scope.Quote(foreignField.DBName)) 64 | placeholders = append(placeholders, scope.AddToVars(foreignField.Field.Interface())) 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | var ( 72 | returningColumn = "*" 73 | quotedTableName = scope.QuotedTableName() 74 | primaryField = scope.PrimaryField() 75 | extraOption string 76 | ) 77 | 78 | if str, ok := scope.Get("gorm:insert_option"); ok { 79 | extraOption = fmt.Sprint(str) 80 | } 81 | 82 | if primaryField != nil { 83 | returningColumn = scope.Quote(primaryField.DBName) 84 | } 85 | 86 | lastInsertIDReturningSuffix := scope.Dialect().LastInsertIDReturningSuffix(quotedTableName, returningColumn) 87 | 88 | if len(columns) == 0 { 89 | scope.Raw(fmt.Sprintf( 90 | "INSERT INTO %v DEFAULT VALUES%v%v", 91 | quotedTableName, 92 | addExtraSpaceIfExist(extraOption), 93 | addExtraSpaceIfExist(lastInsertIDReturningSuffix), 94 | )) 95 | } else { 96 | scope.Raw(fmt.Sprintf( 97 | "INSERT INTO %v (%v) VALUES (%v)%v%v", 98 | scope.QuotedTableName(), 99 | strings.Join(columns, ","), 100 | strings.Join(placeholders, ","), 101 | addExtraSpaceIfExist(extraOption), 102 | addExtraSpaceIfExist(lastInsertIDReturningSuffix), 103 | )) 104 | } 105 | 106 | // execute create sql 107 | if lastInsertIDReturningSuffix == "" || primaryField == nil { 108 | if result, err := scope.SQLDB().Exec(scope.SQL, scope.SQLVars...); scope.Err(err) == nil { 109 | // set rows affected count 110 | scope.db.RowsAffected, _ = result.RowsAffected() 111 | 112 | // set primary value to primary field 113 | if primaryField != nil && primaryField.IsBlank { 114 | if primaryValue, err := result.LastInsertId(); scope.Err(err) == nil { 115 | scope.Err(primaryField.Set(primaryValue)) 116 | } 117 | } 118 | } 119 | } else { 120 | if primaryField.Field.CanAddr() { 121 | if err := scope.SQLDB().QueryRow(scope.SQL, scope.SQLVars...).Scan(primaryField.Field.Addr().Interface()); scope.Err(err) == nil { 122 | primaryField.IsBlank = false 123 | scope.db.RowsAffected = 1 124 | } 125 | } else { 126 | scope.Err(ErrUnaddressable) 127 | } 128 | } 129 | } 130 | } 131 | 132 | // forceReloadAfterCreateCallback will reload columns that having default value, and set it back to current object 133 | func forceReloadAfterCreateCallback(scope *Scope) { 134 | if blankColumnsWithDefaultValue, ok := scope.InstanceGet("gorm:blank_columns_with_default_value"); ok { 135 | db := scope.DB().New().Table(scope.TableName()).Select(blankColumnsWithDefaultValue.([]string)) 136 | for _, field := range scope.Fields() { 137 | if field.IsPrimaryKey && !field.IsBlank { 138 | db = db.Where(fmt.Sprintf("%v = ?", field.DBName), field.Field.Interface()) 139 | } 140 | } 141 | db.Scan(scope.Value) 142 | } 143 | } 144 | 145 | // afterCreateCallback will invoke `AfterCreate`, `AfterSave` method after creating 146 | func afterCreateCallback(scope *Scope) { 147 | if !scope.HasError() { 148 | scope.CallMethod("AfterCreate") 149 | } 150 | if !scope.HasError() { 151 | scope.CallMethod("AfterSave") 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/api/handler/api.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strings" 11 | "time" 12 | 13 | "github.com/Goss-io/goss/app/api/conf" 14 | "github.com/Goss-io/goss/db" 15 | "github.com/Goss-io/goss/lib" 16 | "github.com/Goss-io/goss/lib/filetype" 17 | "github.com/Goss-io/goss/lib/ini" 18 | ) 19 | 20 | //NewAPI . 21 | func NewAPI() *APIService { 22 | cf := conf.Conf.Node 23 | apiSrv := APIService{ 24 | Port: fmt.Sprintf(":%d", cf.Port), 25 | Addr: fmt.Sprintf("%s:%d", ini.GetString("node_ip"), ini.GetInt("node_port")), 26 | MasterNode: ini.GetString("master_node"), 27 | } 28 | return &apiSrv 29 | } 30 | 31 | //Start . 32 | func (a *APIService) Start() { 33 | go a.connMaster() 34 | a.httpSrv() 35 | } 36 | 37 | //httpSrv . 38 | func (a *APIService) httpSrv() { 39 | http.HandleFunc("/", a.handler) 40 | if err := http.ListenAndServe(a.Port, nil); err != nil { 41 | log.Panicf("%+v\n", err) 42 | } 43 | } 44 | 45 | //handler . 46 | func (a *APIService) handler(w http.ResponseWriter, r *http.Request) { 47 | if r.Method == http.MethodGet { 48 | a.get(w, r) 49 | return 50 | } 51 | 52 | if r.Method == http.MethodPut { 53 | a.put(w, r) 54 | return 55 | } 56 | 57 | if r.Method == http.MethodDelete { 58 | a.delete(w, r) 59 | return 60 | } 61 | 62 | w.WriteHeader(http.StatusNotFound) 63 | } 64 | 65 | //get. 66 | func (a *APIService) get(w http.ResponseWriter, r *http.Request) { 67 | //验证bucket是否存在. 68 | bkt := db.Bucket{ 69 | Host: r.Host, 70 | } 71 | if err := bkt.Query(); err != nil { 72 | log.Printf("err:%+v\n", err) 73 | w.Write([]byte(err.Error())) 74 | return 75 | } 76 | if bkt.ID < 1 { 77 | w.Write([]byte("不存在")) 78 | return 79 | } 80 | 81 | //获取访问的文件. 82 | name, err := a.getParse(r.URL.EscapedPath()) 83 | if err != nil { 84 | w.Write([]byte(err.Error())) 85 | return 86 | } 87 | 88 | meta := db.Metadata{ 89 | Name: name, 90 | BucketID: bkt.ID, 91 | } 92 | list, err := meta.QueryNodeIP() 93 | if err != nil { 94 | log.Printf("%+v\n", err) 95 | w.Write([]byte(err.Error())) 96 | return 97 | } 98 | if len(list) < 1 { 99 | w.Write([]byte("not found")) 100 | return 101 | } 102 | 103 | buf := make(chan []byte, meta.Size) 104 | var errnum = 0 105 | for _, nodeip := range list { 106 | b, err := a.Read(meta.StorePath, nodeip) 107 | if err != nil { 108 | errnum++ 109 | log.Printf("%+v\n", err) 110 | continue 111 | } 112 | buf <- b 113 | break 114 | } 115 | 116 | msg := <-buf 117 | //如果msg为空的话,则判断是否errnum > 0. 118 | if len(msg) > 0 { 119 | w.Header().Set("Content-Type", meta.Type) 120 | _, err = w.Write(msg) 121 | if err != nil { 122 | log.Printf("err:%+v\n", err) 123 | } 124 | return 125 | } 126 | if errnum > 0 { 127 | w.Write([]byte("获取失败")) 128 | return 129 | } 130 | w.Write([]byte("not found")) 131 | } 132 | 133 | //getParse get请求解析文件名. 134 | //兼容目录结构,host以后的路径都为文件名. 135 | func (a *APIService) getParse(url string) (name string, err error) { 136 | path := strings.TrimLeft(url, "/") 137 | if len(path) < 1 { 138 | return name, errors.New("not fount") 139 | } 140 | 141 | return path, nil 142 | } 143 | 144 | //put. 145 | func (a *APIService) put(w http.ResponseWriter, r *http.Request) { 146 | //验证bucket是否存在. 147 | bkt := db.Bucket{ 148 | Host: r.Host, 149 | } 150 | if err := bkt.Query(); err != nil { 151 | log.Printf("err:%+v\n", err) 152 | w.Write([]byte(err.Error())) 153 | w.WriteHeader(http.StatusNotFound) 154 | return 155 | } 156 | if bkt.ID < 1 { 157 | w.Write([]byte("不存在")) 158 | w.WriteHeader(http.StatusNotFound) 159 | return 160 | } 161 | 162 | //验证AccessKey和SecretKey是否正确. 163 | ak := r.Header.Get("AccessKey") 164 | sk := r.Header.Get("SecretKey") 165 | if len(ak) != 32 || len(sk) != 32 || bkt.AccessKey != ak || bkt.SecretKey != sk { 166 | w.Write([]byte("授权失败")) 167 | w.WriteHeader(http.StatusForbidden) 168 | return 169 | } 170 | 171 | //获取文件名称,文件大小,文件类型,文件hash. 172 | //元数据. 173 | name, err := a.getParse(r.URL.EscapedPath()) 174 | if err != nil { 175 | w.Write([]byte(err.Error())) 176 | w.WriteHeader(http.StatusNotFound) 177 | return 178 | } 179 | 180 | fBody, err := ioutil.ReadAll(r.Body) 181 | if err != nil { 182 | log.Printf("%+v\n", err) 183 | w.Write([]byte("fail")) 184 | return 185 | } 186 | 187 | //获取文件类型. 188 | f16 := fmt.Sprintf("%x", fBody) 189 | ft := filetype.Parse(f16[:10]) 190 | 191 | //计算文件hash. 192 | fhash := lib.FileHash(fBody) 193 | 194 | //采用用强一致性来记录文件. 195 | nodeipList := a.SelectNode(3) 196 | log.Printf("nodeipList:%+v\n", nodeipList) 197 | 198 | //开启事物操作,防止节点数据不一致. 199 | tx := db.Db.Begin() 200 | for _, nodeip := range nodeipList { 201 | storePath, err := a.Write(fhash, fBody, nodeip) 202 | if err != nil { 203 | log.Printf("%+v\n", err) 204 | w.Write([]byte("fail")) 205 | tx.Rollback() 206 | //todo 删除已经记录的文件. 207 | return 208 | } 209 | 210 | //记录文件元数据. 211 | metadata := db.Metadata{ 212 | Name: name, 213 | Type: ft, 214 | Size: int64(len(fBody)), 215 | Hash: fhash, 216 | StoreNode: nodeip, 217 | StorePath: storePath, 218 | Usable: true, 219 | BucketID: bkt.ID, 220 | } 221 | if err = tx.Create(&metadata).Error; err != nil { 222 | log.Printf("%+v\n", err) 223 | w.Write([]byte("fail")) 224 | tx.Rollback() 225 | return 226 | } 227 | } 228 | 229 | if err := tx.Commit().Error; err != nil { 230 | log.Printf("%+v\n", err) 231 | w.Write([]byte("fail")) 232 | return 233 | } 234 | 235 | w.Write([]byte("success")) 236 | } 237 | 238 | //delete. 239 | func (a *APIService) delete(w http.ResponseWriter, r *http.Request) { 240 | 241 | } 242 | 243 | //Write 发送消息. 244 | func (a *APIService) Write(fhash string, body []byte, nodeip string) (storePath string, err error) { 245 | url := fmt.Sprintf("http://%s/", nodeip) 246 | req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(body)) 247 | if err != nil { 248 | log.Printf("err:%+v\n", err) 249 | return storePath, err 250 | } 251 | req.Header.Set("token", conf.Conf.Node.Token) 252 | req.Header.Set("fhash", fhash) 253 | client := http.Client{} 254 | response, err := client.Do(req) 255 | if err != nil { 256 | log.Printf("err:%+v\n", err) 257 | return storePath, err 258 | } 259 | 260 | b, err := ioutil.ReadAll(response.Body) 261 | if err != nil { 262 | log.Printf("err:%+v\n", err) 263 | return storePath, err 264 | } 265 | 266 | resp := lib.ParseMsg(b) 267 | if !resp.Status { 268 | return storePath, errors.New(resp.Msg.(string)) 269 | } 270 | 271 | return resp.Msg.(string), nil 272 | } 273 | 274 | //Read 读取消息. 275 | func (a *APIService) Read(fpath, nodeip string) (fbody []byte, err error) { 276 | url := fmt.Sprintf("http://%s/", nodeip) 277 | req, err := http.NewRequest(http.MethodGet, url, nil) 278 | if err != nil { 279 | log.Printf("err:%+v\n", err) 280 | return fbody, err 281 | } 282 | req.Header.Set("fpath", fpath) 283 | req.Header.Set("token", conf.Conf.Node.Token) 284 | client := http.Client{ 285 | Timeout: time.Second * 1, 286 | } 287 | response, err := client.Do(req) 288 | if err != nil { 289 | log.Printf("err:%+v\n", err) 290 | return fbody, nil 291 | } 292 | 293 | b, err := ioutil.ReadAll(response.Body) 294 | if err != nil { 295 | log.Printf("err:%+v\n", err) 296 | return fbody, nil 297 | } 298 | 299 | resp := lib.ParseMsg(b) 300 | if !resp.Status { 301 | return fbody, errors.New(resp.Msg.(string)) 302 | } 303 | return resp.Body, nil 304 | } 305 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/join_table_handler.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | // JoinTableHandlerInterface is an interface for how to handle many2many relations 11 | type JoinTableHandlerInterface interface { 12 | // initialize join table handler 13 | Setup(relationship *Relationship, tableName string, source reflect.Type, destination reflect.Type) 14 | // Table return join table's table name 15 | Table(db *DB) string 16 | // Add create relationship in join table for source and destination 17 | Add(handler JoinTableHandlerInterface, db *DB, source interface{}, destination interface{}) error 18 | // Delete delete relationship in join table for sources 19 | Delete(handler JoinTableHandlerInterface, db *DB, sources ...interface{}) error 20 | // JoinWith query with `Join` conditions 21 | JoinWith(handler JoinTableHandlerInterface, db *DB, source interface{}) *DB 22 | // SourceForeignKeys return source foreign keys 23 | SourceForeignKeys() []JoinTableForeignKey 24 | // DestinationForeignKeys return destination foreign keys 25 | DestinationForeignKeys() []JoinTableForeignKey 26 | } 27 | 28 | // JoinTableForeignKey join table foreign key struct 29 | type JoinTableForeignKey struct { 30 | DBName string 31 | AssociationDBName string 32 | } 33 | 34 | // JoinTableSource is a struct that contains model type and foreign keys 35 | type JoinTableSource struct { 36 | ModelType reflect.Type 37 | ForeignKeys []JoinTableForeignKey 38 | } 39 | 40 | // JoinTableHandler default join table handler 41 | type JoinTableHandler struct { 42 | TableName string `sql:"-"` 43 | Source JoinTableSource `sql:"-"` 44 | Destination JoinTableSource `sql:"-"` 45 | } 46 | 47 | // SourceForeignKeys return source foreign keys 48 | func (s *JoinTableHandler) SourceForeignKeys() []JoinTableForeignKey { 49 | return s.Source.ForeignKeys 50 | } 51 | 52 | // DestinationForeignKeys return destination foreign keys 53 | func (s *JoinTableHandler) DestinationForeignKeys() []JoinTableForeignKey { 54 | return s.Destination.ForeignKeys 55 | } 56 | 57 | // Setup initialize a default join table handler 58 | func (s *JoinTableHandler) Setup(relationship *Relationship, tableName string, source reflect.Type, destination reflect.Type) { 59 | s.TableName = tableName 60 | 61 | s.Source = JoinTableSource{ModelType: source} 62 | s.Source.ForeignKeys = []JoinTableForeignKey{} 63 | for idx, dbName := range relationship.ForeignFieldNames { 64 | s.Source.ForeignKeys = append(s.Source.ForeignKeys, JoinTableForeignKey{ 65 | DBName: relationship.ForeignDBNames[idx], 66 | AssociationDBName: dbName, 67 | }) 68 | } 69 | 70 | s.Destination = JoinTableSource{ModelType: destination} 71 | s.Destination.ForeignKeys = []JoinTableForeignKey{} 72 | for idx, dbName := range relationship.AssociationForeignFieldNames { 73 | s.Destination.ForeignKeys = append(s.Destination.ForeignKeys, JoinTableForeignKey{ 74 | DBName: relationship.AssociationForeignDBNames[idx], 75 | AssociationDBName: dbName, 76 | }) 77 | } 78 | } 79 | 80 | // Table return join table's table name 81 | func (s JoinTableHandler) Table(db *DB) string { 82 | return s.TableName 83 | } 84 | 85 | func (s JoinTableHandler) getSearchMap(db *DB, sources ...interface{}) map[string]interface{} { 86 | values := map[string]interface{}{} 87 | 88 | for _, source := range sources { 89 | scope := db.NewScope(source) 90 | modelType := scope.GetModelStruct().ModelType 91 | 92 | if s.Source.ModelType == modelType { 93 | for _, foreignKey := range s.Source.ForeignKeys { 94 | if field, ok := scope.FieldByName(foreignKey.AssociationDBName); ok { 95 | values[foreignKey.DBName] = field.Field.Interface() 96 | } 97 | } 98 | } else if s.Destination.ModelType == modelType { 99 | for _, foreignKey := range s.Destination.ForeignKeys { 100 | if field, ok := scope.FieldByName(foreignKey.AssociationDBName); ok { 101 | values[foreignKey.DBName] = field.Field.Interface() 102 | } 103 | } 104 | } 105 | } 106 | return values 107 | } 108 | 109 | // Add create relationship in join table for source and destination 110 | func (s JoinTableHandler) Add(handler JoinTableHandlerInterface, db *DB, source interface{}, destination interface{}) error { 111 | scope := db.NewScope("") 112 | searchMap := s.getSearchMap(db, source, destination) 113 | 114 | var assignColumns, binVars, conditions []string 115 | var values []interface{} 116 | for key, value := range searchMap { 117 | assignColumns = append(assignColumns, scope.Quote(key)) 118 | binVars = append(binVars, `?`) 119 | conditions = append(conditions, fmt.Sprintf("%v = ?", scope.Quote(key))) 120 | values = append(values, value) 121 | } 122 | 123 | for _, value := range values { 124 | values = append(values, value) 125 | } 126 | 127 | quotedTable := scope.Quote(handler.Table(db)) 128 | sql := fmt.Sprintf( 129 | "INSERT INTO %v (%v) SELECT %v %v WHERE NOT EXISTS (SELECT * FROM %v WHERE %v)", 130 | quotedTable, 131 | strings.Join(assignColumns, ","), 132 | strings.Join(binVars, ","), 133 | scope.Dialect().SelectFromDummyTable(), 134 | quotedTable, 135 | strings.Join(conditions, " AND "), 136 | ) 137 | 138 | return db.Exec(sql, values...).Error 139 | } 140 | 141 | // Delete delete relationship in join table for sources 142 | func (s JoinTableHandler) Delete(handler JoinTableHandlerInterface, db *DB, sources ...interface{}) error { 143 | var ( 144 | scope = db.NewScope(nil) 145 | conditions []string 146 | values []interface{} 147 | ) 148 | 149 | for key, value := range s.getSearchMap(db, sources...) { 150 | conditions = append(conditions, fmt.Sprintf("%v = ?", scope.Quote(key))) 151 | values = append(values, value) 152 | } 153 | 154 | return db.Table(handler.Table(db)).Where(strings.Join(conditions, " AND "), values...).Delete("").Error 155 | } 156 | 157 | // JoinWith query with `Join` conditions 158 | func (s JoinTableHandler) JoinWith(handler JoinTableHandlerInterface, db *DB, source interface{}) *DB { 159 | var ( 160 | scope = db.NewScope(source) 161 | tableName = handler.Table(db) 162 | quotedTableName = scope.Quote(tableName) 163 | joinConditions []string 164 | values []interface{} 165 | ) 166 | 167 | if s.Source.ModelType == scope.GetModelStruct().ModelType { 168 | destinationTableName := db.NewScope(reflect.New(s.Destination.ModelType).Interface()).QuotedTableName() 169 | for _, foreignKey := range s.Destination.ForeignKeys { 170 | joinConditions = append(joinConditions, fmt.Sprintf("%v.%v = %v.%v", quotedTableName, scope.Quote(foreignKey.DBName), destinationTableName, scope.Quote(foreignKey.AssociationDBName))) 171 | } 172 | 173 | var foreignDBNames []string 174 | var foreignFieldNames []string 175 | 176 | for _, foreignKey := range s.Source.ForeignKeys { 177 | foreignDBNames = append(foreignDBNames, foreignKey.DBName) 178 | if field, ok := scope.FieldByName(foreignKey.AssociationDBName); ok { 179 | foreignFieldNames = append(foreignFieldNames, field.Name) 180 | } 181 | } 182 | 183 | foreignFieldValues := scope.getColumnAsArray(foreignFieldNames, scope.Value) 184 | 185 | var condString string 186 | if len(foreignFieldValues) > 0 { 187 | var quotedForeignDBNames []string 188 | for _, dbName := range foreignDBNames { 189 | quotedForeignDBNames = append(quotedForeignDBNames, tableName+"."+dbName) 190 | } 191 | 192 | condString = fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, quotedForeignDBNames), toQueryMarks(foreignFieldValues)) 193 | 194 | keys := scope.getColumnAsArray(foreignFieldNames, scope.Value) 195 | values = append(values, toQueryValues(keys)) 196 | } else { 197 | condString = fmt.Sprintf("1 <> 1") 198 | } 199 | 200 | return db.Joins(fmt.Sprintf("INNER JOIN %v ON %v", quotedTableName, strings.Join(joinConditions, " AND "))). 201 | Where(condString, toQueryValues(foreignFieldValues)...) 202 | } 203 | 204 | db.Error = errors.New("wrong source type for join table handler") 205 | return db 206 | } 207 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/utils.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "bytes" 5 | "database/sql/driver" 6 | "fmt" 7 | "reflect" 8 | "regexp" 9 | "runtime" 10 | "strings" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | // NowFunc returns current time, this function is exported in order to be able 16 | // to give the flexibility to the developer to customize it according to their 17 | // needs, e.g: 18 | // gorm.NowFunc = func() time.Time { 19 | // return time.Now().UTC() 20 | // } 21 | var NowFunc = func() time.Time { 22 | return time.Now() 23 | } 24 | 25 | // Copied from golint 26 | var commonInitialisms = []string{"API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SSH", "TLS", "TTL", "UI", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XSRF", "XSS"} 27 | var commonInitialismsReplacer *strings.Replacer 28 | 29 | var goSrcRegexp = regexp.MustCompile(`jinzhu/gorm/.*.go`) 30 | var goTestRegexp = regexp.MustCompile(`jinzhu/gorm/.*test.go`) 31 | 32 | func init() { 33 | var commonInitialismsForReplacer []string 34 | for _, initialism := range commonInitialisms { 35 | commonInitialismsForReplacer = append(commonInitialismsForReplacer, initialism, strings.Title(strings.ToLower(initialism))) 36 | } 37 | commonInitialismsReplacer = strings.NewReplacer(commonInitialismsForReplacer...) 38 | } 39 | 40 | type safeMap struct { 41 | m map[string]string 42 | l *sync.RWMutex 43 | } 44 | 45 | func (s *safeMap) Set(key string, value string) { 46 | s.l.Lock() 47 | defer s.l.Unlock() 48 | s.m[key] = value 49 | } 50 | 51 | func (s *safeMap) Get(key string) string { 52 | s.l.RLock() 53 | defer s.l.RUnlock() 54 | return s.m[key] 55 | } 56 | 57 | func newSafeMap() *safeMap { 58 | return &safeMap{l: new(sync.RWMutex), m: make(map[string]string)} 59 | } 60 | 61 | var smap = newSafeMap() 62 | 63 | type strCase bool 64 | 65 | const ( 66 | lower strCase = false 67 | upper strCase = true 68 | ) 69 | 70 | // ToDBName convert string to db name 71 | func ToDBName(name string) string { 72 | if v := smap.Get(name); v != "" { 73 | return v 74 | } 75 | 76 | if name == "" { 77 | return "" 78 | } 79 | 80 | var ( 81 | value = commonInitialismsReplacer.Replace(name) 82 | buf = bytes.NewBufferString("") 83 | lastCase, currCase, nextCase strCase 84 | ) 85 | 86 | for i, v := range value[:len(value)-1] { 87 | nextCase = strCase(value[i+1] >= 'A' && value[i+1] <= 'Z') 88 | if i > 0 { 89 | if currCase == upper { 90 | if lastCase == upper && nextCase == upper { 91 | buf.WriteRune(v) 92 | } else { 93 | if value[i-1] != '_' && value[i+1] != '_' { 94 | buf.WriteRune('_') 95 | } 96 | buf.WriteRune(v) 97 | } 98 | } else { 99 | buf.WriteRune(v) 100 | if i == len(value)-2 && nextCase == upper { 101 | buf.WriteRune('_') 102 | } 103 | } 104 | } else { 105 | currCase = upper 106 | buf.WriteRune(v) 107 | } 108 | lastCase = currCase 109 | currCase = nextCase 110 | } 111 | 112 | buf.WriteByte(value[len(value)-1]) 113 | 114 | s := strings.ToLower(buf.String()) 115 | smap.Set(name, s) 116 | return s 117 | } 118 | 119 | // SQL expression 120 | type expr struct { 121 | expr string 122 | args []interface{} 123 | } 124 | 125 | // Expr generate raw SQL expression, for example: 126 | // DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100)) 127 | func Expr(expression string, args ...interface{}) *expr { 128 | return &expr{expr: expression, args: args} 129 | } 130 | 131 | func indirect(reflectValue reflect.Value) reflect.Value { 132 | for reflectValue.Kind() == reflect.Ptr { 133 | reflectValue = reflectValue.Elem() 134 | } 135 | return reflectValue 136 | } 137 | 138 | func toQueryMarks(primaryValues [][]interface{}) string { 139 | var results []string 140 | 141 | for _, primaryValue := range primaryValues { 142 | var marks []string 143 | for range primaryValue { 144 | marks = append(marks, "?") 145 | } 146 | 147 | if len(marks) > 1 { 148 | results = append(results, fmt.Sprintf("(%v)", strings.Join(marks, ","))) 149 | } else { 150 | results = append(results, strings.Join(marks, "")) 151 | } 152 | } 153 | return strings.Join(results, ",") 154 | } 155 | 156 | func toQueryCondition(scope *Scope, columns []string) string { 157 | var newColumns []string 158 | for _, column := range columns { 159 | newColumns = append(newColumns, scope.Quote(column)) 160 | } 161 | 162 | if len(columns) > 1 { 163 | return fmt.Sprintf("(%v)", strings.Join(newColumns, ",")) 164 | } 165 | return strings.Join(newColumns, ",") 166 | } 167 | 168 | func toQueryValues(values [][]interface{}) (results []interface{}) { 169 | for _, value := range values { 170 | for _, v := range value { 171 | results = append(results, v) 172 | } 173 | } 174 | return 175 | } 176 | 177 | func fileWithLineNum() string { 178 | for i := 2; i < 15; i++ { 179 | _, file, line, ok := runtime.Caller(i) 180 | if ok && (!goSrcRegexp.MatchString(file) || goTestRegexp.MatchString(file)) { 181 | return fmt.Sprintf("%v:%v", file, line) 182 | } 183 | } 184 | return "" 185 | } 186 | 187 | func isBlank(value reflect.Value) bool { 188 | switch value.Kind() { 189 | case reflect.String: 190 | return value.Len() == 0 191 | case reflect.Bool: 192 | return !value.Bool() 193 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 194 | return value.Int() == 0 195 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 196 | return value.Uint() == 0 197 | case reflect.Float32, reflect.Float64: 198 | return value.Float() == 0 199 | case reflect.Interface, reflect.Ptr: 200 | return value.IsNil() 201 | } 202 | 203 | return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface()) 204 | } 205 | 206 | func toSearchableMap(attrs ...interface{}) (result interface{}) { 207 | if len(attrs) > 1 { 208 | if str, ok := attrs[0].(string); ok { 209 | result = map[string]interface{}{str: attrs[1]} 210 | } 211 | } else if len(attrs) == 1 { 212 | if attr, ok := attrs[0].(map[string]interface{}); ok { 213 | result = attr 214 | } 215 | 216 | if attr, ok := attrs[0].(interface{}); ok { 217 | result = attr 218 | } 219 | } 220 | return 221 | } 222 | 223 | func equalAsString(a interface{}, b interface{}) bool { 224 | return toString(a) == toString(b) 225 | } 226 | 227 | func toString(str interface{}) string { 228 | if values, ok := str.([]interface{}); ok { 229 | var results []string 230 | for _, value := range values { 231 | results = append(results, toString(value)) 232 | } 233 | return strings.Join(results, "_") 234 | } else if bytes, ok := str.([]byte); ok { 235 | return string(bytes) 236 | } else if reflectValue := reflect.Indirect(reflect.ValueOf(str)); reflectValue.IsValid() { 237 | return fmt.Sprintf("%v", reflectValue.Interface()) 238 | } 239 | return "" 240 | } 241 | 242 | func makeSlice(elemType reflect.Type) interface{} { 243 | if elemType.Kind() == reflect.Slice { 244 | elemType = elemType.Elem() 245 | } 246 | sliceType := reflect.SliceOf(elemType) 247 | slice := reflect.New(sliceType) 248 | slice.Elem().Set(reflect.MakeSlice(sliceType, 0, 0)) 249 | return slice.Interface() 250 | } 251 | 252 | func strInSlice(a string, list []string) bool { 253 | for _, b := range list { 254 | if b == a { 255 | return true 256 | } 257 | } 258 | return false 259 | } 260 | 261 | // getValueFromFields return given fields's value 262 | func getValueFromFields(value reflect.Value, fieldNames []string) (results []interface{}) { 263 | // If value is a nil pointer, Indirect returns a zero Value! 264 | // Therefor we need to check for a zero value, 265 | // as FieldByName could panic 266 | if indirectValue := reflect.Indirect(value); indirectValue.IsValid() { 267 | for _, fieldName := range fieldNames { 268 | if fieldValue := indirectValue.FieldByName(fieldName); fieldValue.IsValid() { 269 | result := fieldValue.Interface() 270 | if r, ok := result.(driver.Valuer); ok { 271 | result, _ = r.Value() 272 | } 273 | results = append(results, result) 274 | } 275 | } 276 | } 277 | return 278 | } 279 | 280 | func addExtraSpaceIfExist(str string) string { 281 | if str != "" { 282 | return " " + str 283 | } 284 | return "" 285 | } 286 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/callback.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // DefaultCallback default callbacks defined by gorm 8 | var DefaultCallback = &Callback{} 9 | 10 | // Callback is a struct that contains all CRUD callbacks 11 | // Field `creates` contains callbacks will be call when creating object 12 | // Field `updates` contains callbacks will be call when updating object 13 | // Field `deletes` contains callbacks will be call when deleting object 14 | // Field `queries` contains callbacks will be call when querying object with query methods like Find, First, Related, Association... 15 | // Field `rowQueries` contains callbacks will be call when querying object with Row, Rows... 16 | // Field `processors` contains all callback processors, will be used to generate above callbacks in order 17 | type Callback struct { 18 | creates []*func(scope *Scope) 19 | updates []*func(scope *Scope) 20 | deletes []*func(scope *Scope) 21 | queries []*func(scope *Scope) 22 | rowQueries []*func(scope *Scope) 23 | processors []*CallbackProcessor 24 | } 25 | 26 | // CallbackProcessor contains callback informations 27 | type CallbackProcessor struct { 28 | name string // current callback's name 29 | before string // register current callback before a callback 30 | after string // register current callback after a callback 31 | replace bool // replace callbacks with same name 32 | remove bool // delete callbacks with same name 33 | kind string // callback type: create, update, delete, query, row_query 34 | processor *func(scope *Scope) // callback handler 35 | parent *Callback 36 | } 37 | 38 | func (c *Callback) clone() *Callback { 39 | return &Callback{ 40 | creates: c.creates, 41 | updates: c.updates, 42 | deletes: c.deletes, 43 | queries: c.queries, 44 | rowQueries: c.rowQueries, 45 | processors: c.processors, 46 | } 47 | } 48 | 49 | // Create could be used to register callbacks for creating object 50 | // db.Callback().Create().After("gorm:create").Register("plugin:run_after_create", func(*Scope) { 51 | // // business logic 52 | // ... 53 | // 54 | // // set error if some thing wrong happened, will rollback the creating 55 | // scope.Err(errors.New("error")) 56 | // }) 57 | func (c *Callback) Create() *CallbackProcessor { 58 | return &CallbackProcessor{kind: "create", parent: c} 59 | } 60 | 61 | // Update could be used to register callbacks for updating object, refer `Create` for usage 62 | func (c *Callback) Update() *CallbackProcessor { 63 | return &CallbackProcessor{kind: "update", parent: c} 64 | } 65 | 66 | // Delete could be used to register callbacks for deleting object, refer `Create` for usage 67 | func (c *Callback) Delete() *CallbackProcessor { 68 | return &CallbackProcessor{kind: "delete", parent: c} 69 | } 70 | 71 | // Query could be used to register callbacks for querying objects with query methods like `Find`, `First`, `Related`, `Association`... 72 | // Refer `Create` for usage 73 | func (c *Callback) Query() *CallbackProcessor { 74 | return &CallbackProcessor{kind: "query", parent: c} 75 | } 76 | 77 | // RowQuery could be used to register callbacks for querying objects with `Row`, `Rows`, refer `Create` for usage 78 | func (c *Callback) RowQuery() *CallbackProcessor { 79 | return &CallbackProcessor{kind: "row_query", parent: c} 80 | } 81 | 82 | // After insert a new callback after callback `callbackName`, refer `Callbacks.Create` 83 | func (cp *CallbackProcessor) After(callbackName string) *CallbackProcessor { 84 | cp.after = callbackName 85 | return cp 86 | } 87 | 88 | // Before insert a new callback before callback `callbackName`, refer `Callbacks.Create` 89 | func (cp *CallbackProcessor) Before(callbackName string) *CallbackProcessor { 90 | cp.before = callbackName 91 | return cp 92 | } 93 | 94 | // Register a new callback, refer `Callbacks.Create` 95 | func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *Scope)) { 96 | if cp.kind == "row_query" { 97 | if cp.before == "" && cp.after == "" && callbackName != "gorm:row_query" { 98 | fmt.Printf("Registing RowQuery callback %v without specify order with Before(), After(), applying Before('gorm:row_query') by default for compatibility...\n", callbackName) 99 | cp.before = "gorm:row_query" 100 | } 101 | } 102 | 103 | cp.name = callbackName 104 | cp.processor = &callback 105 | cp.parent.processors = append(cp.parent.processors, cp) 106 | cp.parent.reorder() 107 | } 108 | 109 | // Remove a registered callback 110 | // db.Callback().Create().Remove("gorm:update_time_stamp_when_create") 111 | func (cp *CallbackProcessor) Remove(callbackName string) { 112 | fmt.Printf("[info] removing callback `%v` from %v\n", callbackName, fileWithLineNum()) 113 | cp.name = callbackName 114 | cp.remove = true 115 | cp.parent.processors = append(cp.parent.processors, cp) 116 | cp.parent.reorder() 117 | } 118 | 119 | // Replace a registered callback with new callback 120 | // db.Callback().Create().Replace("gorm:update_time_stamp_when_create", func(*Scope) { 121 | // scope.SetColumn("Created", now) 122 | // scope.SetColumn("Updated", now) 123 | // }) 124 | func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *Scope)) { 125 | fmt.Printf("[info] replacing callback `%v` from %v\n", callbackName, fileWithLineNum()) 126 | cp.name = callbackName 127 | cp.processor = &callback 128 | cp.replace = true 129 | cp.parent.processors = append(cp.parent.processors, cp) 130 | cp.parent.reorder() 131 | } 132 | 133 | // Get registered callback 134 | // db.Callback().Create().Get("gorm:create") 135 | func (cp *CallbackProcessor) Get(callbackName string) (callback func(scope *Scope)) { 136 | for _, p := range cp.parent.processors { 137 | if p.name == callbackName && p.kind == cp.kind && !cp.remove { 138 | return *p.processor 139 | } 140 | } 141 | return nil 142 | } 143 | 144 | // getRIndex get right index from string slice 145 | func getRIndex(strs []string, str string) int { 146 | for i := len(strs) - 1; i >= 0; i-- { 147 | if strs[i] == str { 148 | return i 149 | } 150 | } 151 | return -1 152 | } 153 | 154 | // sortProcessors sort callback processors based on its before, after, remove, replace 155 | func sortProcessors(cps []*CallbackProcessor) []*func(scope *Scope) { 156 | var ( 157 | allNames, sortedNames []string 158 | sortCallbackProcessor func(c *CallbackProcessor) 159 | ) 160 | 161 | for _, cp := range cps { 162 | // show warning message the callback name already exists 163 | if index := getRIndex(allNames, cp.name); index > -1 && !cp.replace && !cp.remove { 164 | fmt.Printf("[warning] duplicated callback `%v` from %v\n", cp.name, fileWithLineNum()) 165 | } 166 | allNames = append(allNames, cp.name) 167 | } 168 | 169 | sortCallbackProcessor = func(c *CallbackProcessor) { 170 | if getRIndex(sortedNames, c.name) == -1 { // if not sorted 171 | if c.before != "" { // if defined before callback 172 | if index := getRIndex(sortedNames, c.before); index != -1 { 173 | // if before callback already sorted, append current callback just after it 174 | sortedNames = append(sortedNames[:index], append([]string{c.name}, sortedNames[index:]...)...) 175 | } else if index := getRIndex(allNames, c.before); index != -1 { 176 | // if before callback exists but haven't sorted, append current callback to last 177 | sortedNames = append(sortedNames, c.name) 178 | sortCallbackProcessor(cps[index]) 179 | } 180 | } 181 | 182 | if c.after != "" { // if defined after callback 183 | if index := getRIndex(sortedNames, c.after); index != -1 { 184 | // if after callback already sorted, append current callback just before it 185 | sortedNames = append(sortedNames[:index+1], append([]string{c.name}, sortedNames[index+1:]...)...) 186 | } else if index := getRIndex(allNames, c.after); index != -1 { 187 | // if after callback exists but haven't sorted 188 | cp := cps[index] 189 | // set after callback's before callback to current callback 190 | if cp.before == "" { 191 | cp.before = c.name 192 | } 193 | sortCallbackProcessor(cp) 194 | } 195 | } 196 | 197 | // if current callback haven't been sorted, append it to last 198 | if getRIndex(sortedNames, c.name) == -1 { 199 | sortedNames = append(sortedNames, c.name) 200 | } 201 | } 202 | } 203 | 204 | for _, cp := range cps { 205 | sortCallbackProcessor(cp) 206 | } 207 | 208 | var sortedFuncs []*func(scope *Scope) 209 | for _, name := range sortedNames { 210 | if index := getRIndex(allNames, name); !cps[index].remove { 211 | sortedFuncs = append(sortedFuncs, cps[index].processor) 212 | } 213 | } 214 | 215 | return sortedFuncs 216 | } 217 | 218 | // reorder all registered processors, and reset CRUD callbacks 219 | func (c *Callback) reorder() { 220 | var creates, updates, deletes, queries, rowQueries []*CallbackProcessor 221 | 222 | for _, processor := range c.processors { 223 | if processor.name != "" { 224 | switch processor.kind { 225 | case "create": 226 | creates = append(creates, processor) 227 | case "update": 228 | updates = append(updates, processor) 229 | case "delete": 230 | deletes = append(deletes, processor) 231 | case "query": 232 | queries = append(queries, processor) 233 | case "row_query": 234 | rowQueries = append(rowQueries, processor) 235 | } 236 | } 237 | } 238 | 239 | c.creates = sortProcessors(creates) 240 | c.updates = sortProcessors(updates) 241 | c.deletes = sortProcessors(deletes) 242 | c.queries = sortProcessors(queries) 243 | c.rowQueries = sortProcessors(rowQueries) 244 | } 245 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/callback_query_preload.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // preloadCallback used to preload associations 12 | func preloadCallback(scope *Scope) { 13 | 14 | if _, ok := scope.Get("gorm:auto_preload"); ok { 15 | autoPreload(scope) 16 | } 17 | 18 | if scope.Search.preload == nil || scope.HasError() { 19 | return 20 | } 21 | 22 | var ( 23 | preloadedMap = map[string]bool{} 24 | fields = scope.Fields() 25 | ) 26 | 27 | for _, preload := range scope.Search.preload { 28 | var ( 29 | preloadFields = strings.Split(preload.schema, ".") 30 | currentScope = scope 31 | currentFields = fields 32 | ) 33 | 34 | for idx, preloadField := range preloadFields { 35 | var currentPreloadConditions []interface{} 36 | 37 | if currentScope == nil { 38 | continue 39 | } 40 | 41 | // if not preloaded 42 | if preloadKey := strings.Join(preloadFields[:idx+1], "."); !preloadedMap[preloadKey] { 43 | 44 | // assign search conditions to last preload 45 | if idx == len(preloadFields)-1 { 46 | currentPreloadConditions = preload.conditions 47 | } 48 | 49 | for _, field := range currentFields { 50 | if field.Name != preloadField || field.Relationship == nil { 51 | continue 52 | } 53 | 54 | switch field.Relationship.Kind { 55 | case "has_one": 56 | currentScope.handleHasOnePreload(field, currentPreloadConditions) 57 | case "has_many": 58 | currentScope.handleHasManyPreload(field, currentPreloadConditions) 59 | case "belongs_to": 60 | currentScope.handleBelongsToPreload(field, currentPreloadConditions) 61 | case "many_to_many": 62 | currentScope.handleManyToManyPreload(field, currentPreloadConditions) 63 | default: 64 | scope.Err(errors.New("unsupported relation")) 65 | } 66 | 67 | preloadedMap[preloadKey] = true 68 | break 69 | } 70 | 71 | if !preloadedMap[preloadKey] { 72 | scope.Err(fmt.Errorf("can't preload field %s for %s", preloadField, currentScope.GetModelStruct().ModelType)) 73 | return 74 | } 75 | } 76 | 77 | // preload next level 78 | if idx < len(preloadFields)-1 { 79 | currentScope = currentScope.getColumnAsScope(preloadField) 80 | if currentScope != nil { 81 | currentFields = currentScope.Fields() 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | func autoPreload(scope *Scope) { 89 | for _, field := range scope.Fields() { 90 | if field.Relationship == nil { 91 | continue 92 | } 93 | 94 | if val, ok := field.TagSettings["PRELOAD"]; ok { 95 | if preload, err := strconv.ParseBool(val); err != nil { 96 | scope.Err(errors.New("invalid preload option")) 97 | return 98 | } else if !preload { 99 | continue 100 | } 101 | } 102 | 103 | scope.Search.Preload(field.Name) 104 | } 105 | } 106 | 107 | func (scope *Scope) generatePreloadDBWithConditions(conditions []interface{}) (*DB, []interface{}) { 108 | var ( 109 | preloadDB = scope.NewDB() 110 | preloadConditions []interface{} 111 | ) 112 | 113 | for _, condition := range conditions { 114 | if scopes, ok := condition.(func(*DB) *DB); ok { 115 | preloadDB = scopes(preloadDB) 116 | } else { 117 | preloadConditions = append(preloadConditions, condition) 118 | } 119 | } 120 | 121 | return preloadDB, preloadConditions 122 | } 123 | 124 | // handleHasOnePreload used to preload has one associations 125 | func (scope *Scope) handleHasOnePreload(field *Field, conditions []interface{}) { 126 | relation := field.Relationship 127 | 128 | // get relations's primary keys 129 | primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value) 130 | if len(primaryKeys) == 0 { 131 | return 132 | } 133 | 134 | // preload conditions 135 | preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions) 136 | 137 | // find relations 138 | query := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys)) 139 | values := toQueryValues(primaryKeys) 140 | if relation.PolymorphicType != "" { 141 | query += fmt.Sprintf(" AND %v = ?", scope.Quote(relation.PolymorphicDBName)) 142 | values = append(values, relation.PolymorphicValue) 143 | } 144 | 145 | results := makeSlice(field.Struct.Type) 146 | scope.Err(preloadDB.Where(query, values...).Find(results, preloadConditions...).Error) 147 | 148 | // assign find results 149 | var ( 150 | resultsValue = indirect(reflect.ValueOf(results)) 151 | indirectScopeValue = scope.IndirectValue() 152 | ) 153 | 154 | if indirectScopeValue.Kind() == reflect.Slice { 155 | for j := 0; j < indirectScopeValue.Len(); j++ { 156 | for i := 0; i < resultsValue.Len(); i++ { 157 | result := resultsValue.Index(i) 158 | foreignValues := getValueFromFields(result, relation.ForeignFieldNames) 159 | if indirectValue := indirect(indirectScopeValue.Index(j)); equalAsString(getValueFromFields(indirectValue, relation.AssociationForeignFieldNames), foreignValues) { 160 | indirectValue.FieldByName(field.Name).Set(result) 161 | break 162 | } 163 | } 164 | } 165 | } else { 166 | for i := 0; i < resultsValue.Len(); i++ { 167 | result := resultsValue.Index(i) 168 | scope.Err(field.Set(result)) 169 | } 170 | } 171 | } 172 | 173 | // handleHasManyPreload used to preload has many associations 174 | func (scope *Scope) handleHasManyPreload(field *Field, conditions []interface{}) { 175 | relation := field.Relationship 176 | 177 | // get relations's primary keys 178 | primaryKeys := scope.getColumnAsArray(relation.AssociationForeignFieldNames, scope.Value) 179 | if len(primaryKeys) == 0 { 180 | return 181 | } 182 | 183 | // preload conditions 184 | preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions) 185 | 186 | // find relations 187 | query := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.ForeignDBNames), toQueryMarks(primaryKeys)) 188 | values := toQueryValues(primaryKeys) 189 | if relation.PolymorphicType != "" { 190 | query += fmt.Sprintf(" AND %v = ?", scope.Quote(relation.PolymorphicDBName)) 191 | values = append(values, relation.PolymorphicValue) 192 | } 193 | 194 | results := makeSlice(field.Struct.Type) 195 | scope.Err(preloadDB.Where(query, values...).Find(results, preloadConditions...).Error) 196 | 197 | // assign find results 198 | var ( 199 | resultsValue = indirect(reflect.ValueOf(results)) 200 | indirectScopeValue = scope.IndirectValue() 201 | ) 202 | 203 | if indirectScopeValue.Kind() == reflect.Slice { 204 | preloadMap := make(map[string][]reflect.Value) 205 | for i := 0; i < resultsValue.Len(); i++ { 206 | result := resultsValue.Index(i) 207 | foreignValues := getValueFromFields(result, relation.ForeignFieldNames) 208 | preloadMap[toString(foreignValues)] = append(preloadMap[toString(foreignValues)], result) 209 | } 210 | 211 | for j := 0; j < indirectScopeValue.Len(); j++ { 212 | object := indirect(indirectScopeValue.Index(j)) 213 | objectRealValue := getValueFromFields(object, relation.AssociationForeignFieldNames) 214 | f := object.FieldByName(field.Name) 215 | if results, ok := preloadMap[toString(objectRealValue)]; ok { 216 | f.Set(reflect.Append(f, results...)) 217 | } else { 218 | f.Set(reflect.MakeSlice(f.Type(), 0, 0)) 219 | } 220 | } 221 | } else { 222 | scope.Err(field.Set(resultsValue)) 223 | } 224 | } 225 | 226 | // handleBelongsToPreload used to preload belongs to associations 227 | func (scope *Scope) handleBelongsToPreload(field *Field, conditions []interface{}) { 228 | relation := field.Relationship 229 | 230 | // preload conditions 231 | preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions) 232 | 233 | // get relations's primary keys 234 | primaryKeys := scope.getColumnAsArray(relation.ForeignFieldNames, scope.Value) 235 | if len(primaryKeys) == 0 { 236 | return 237 | } 238 | 239 | // find relations 240 | results := makeSlice(field.Struct.Type) 241 | scope.Err(preloadDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relation.AssociationForeignDBNames), toQueryMarks(primaryKeys)), toQueryValues(primaryKeys)...).Find(results, preloadConditions...).Error) 242 | 243 | // assign find results 244 | var ( 245 | resultsValue = indirect(reflect.ValueOf(results)) 246 | indirectScopeValue = scope.IndirectValue() 247 | ) 248 | 249 | for i := 0; i < resultsValue.Len(); i++ { 250 | result := resultsValue.Index(i) 251 | if indirectScopeValue.Kind() == reflect.Slice { 252 | value := getValueFromFields(result, relation.AssociationForeignFieldNames) 253 | for j := 0; j < indirectScopeValue.Len(); j++ { 254 | object := indirect(indirectScopeValue.Index(j)) 255 | if equalAsString(getValueFromFields(object, relation.ForeignFieldNames), value) { 256 | object.FieldByName(field.Name).Set(result) 257 | } 258 | } 259 | } else { 260 | scope.Err(field.Set(result)) 261 | } 262 | } 263 | } 264 | 265 | // handleManyToManyPreload used to preload many to many associations 266 | func (scope *Scope) handleManyToManyPreload(field *Field, conditions []interface{}) { 267 | var ( 268 | relation = field.Relationship 269 | joinTableHandler = relation.JoinTableHandler 270 | fieldType = field.Struct.Type.Elem() 271 | foreignKeyValue interface{} 272 | foreignKeyType = reflect.ValueOf(&foreignKeyValue).Type() 273 | linkHash = map[string][]reflect.Value{} 274 | isPtr bool 275 | ) 276 | 277 | if fieldType.Kind() == reflect.Ptr { 278 | isPtr = true 279 | fieldType = fieldType.Elem() 280 | } 281 | 282 | var sourceKeys = []string{} 283 | for _, key := range joinTableHandler.SourceForeignKeys() { 284 | sourceKeys = append(sourceKeys, key.DBName) 285 | } 286 | 287 | // preload conditions 288 | preloadDB, preloadConditions := scope.generatePreloadDBWithConditions(conditions) 289 | 290 | // generate query with join table 291 | newScope := scope.New(reflect.New(fieldType).Interface()) 292 | preloadDB = preloadDB.Table(newScope.TableName()).Model(newScope.Value).Select("*") 293 | preloadDB = joinTableHandler.JoinWith(joinTableHandler, preloadDB, scope.Value) 294 | 295 | // preload inline conditions 296 | if len(preloadConditions) > 0 { 297 | preloadDB = preloadDB.Where(preloadConditions[0], preloadConditions[1:]...) 298 | } 299 | 300 | rows, err := preloadDB.Rows() 301 | 302 | if scope.Err(err) != nil { 303 | return 304 | } 305 | defer rows.Close() 306 | 307 | columns, _ := rows.Columns() 308 | for rows.Next() { 309 | var ( 310 | elem = reflect.New(fieldType).Elem() 311 | fields = scope.New(elem.Addr().Interface()).Fields() 312 | ) 313 | 314 | // register foreign keys in join tables 315 | var joinTableFields []*Field 316 | for _, sourceKey := range sourceKeys { 317 | joinTableFields = append(joinTableFields, &Field{StructField: &StructField{DBName: sourceKey, IsNormal: true}, Field: reflect.New(foreignKeyType).Elem()}) 318 | } 319 | 320 | scope.scan(rows, columns, append(fields, joinTableFields...)) 321 | 322 | var foreignKeys = make([]interface{}, len(sourceKeys)) 323 | // generate hashed forkey keys in join table 324 | for idx, joinTableField := range joinTableFields { 325 | if !joinTableField.Field.IsNil() { 326 | foreignKeys[idx] = joinTableField.Field.Elem().Interface() 327 | } 328 | } 329 | hashedSourceKeys := toString(foreignKeys) 330 | 331 | if isPtr { 332 | linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem.Addr()) 333 | } else { 334 | linkHash[hashedSourceKeys] = append(linkHash[hashedSourceKeys], elem) 335 | } 336 | } 337 | 338 | if err := rows.Err(); err != nil { 339 | scope.Err(err) 340 | } 341 | 342 | // assign find results 343 | var ( 344 | indirectScopeValue = scope.IndirectValue() 345 | fieldsSourceMap = map[string][]reflect.Value{} 346 | foreignFieldNames = []string{} 347 | ) 348 | 349 | for _, dbName := range relation.ForeignFieldNames { 350 | if field, ok := scope.FieldByName(dbName); ok { 351 | foreignFieldNames = append(foreignFieldNames, field.Name) 352 | } 353 | } 354 | 355 | if indirectScopeValue.Kind() == reflect.Slice { 356 | for j := 0; j < indirectScopeValue.Len(); j++ { 357 | object := indirect(indirectScopeValue.Index(j)) 358 | key := toString(getValueFromFields(object, foreignFieldNames)) 359 | fieldsSourceMap[key] = append(fieldsSourceMap[key], object.FieldByName(field.Name)) 360 | } 361 | } else if indirectScopeValue.IsValid() { 362 | key := toString(getValueFromFields(indirectScopeValue, foreignFieldNames)) 363 | fieldsSourceMap[key] = append(fieldsSourceMap[key], indirectScopeValue.FieldByName(field.Name)) 364 | } 365 | for source, link := range linkHash { 366 | for i, field := range fieldsSourceMap[source] { 367 | //If not 0 this means Value is a pointer and we already added preloaded models to it 368 | if fieldsSourceMap[source][i].Len() != 0 { 369 | continue 370 | } 371 | field.Set(reflect.Append(fieldsSourceMap[source][i], link...)) 372 | } 373 | 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /vendor/github.com/jinzhu/gorm/association.go: -------------------------------------------------------------------------------- 1 | package gorm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // Association Mode contains some helper methods to handle relationship things easily. 10 | type Association struct { 11 | Error error 12 | scope *Scope 13 | column string 14 | field *Field 15 | } 16 | 17 | // Find find out all related associations 18 | func (association *Association) Find(value interface{}) *Association { 19 | association.scope.related(value, association.column) 20 | return association.setErr(association.scope.db.Error) 21 | } 22 | 23 | // Append append new associations for many2many, has_many, replace current association for has_one, belongs_to 24 | func (association *Association) Append(values ...interface{}) *Association { 25 | if association.Error != nil { 26 | return association 27 | } 28 | 29 | if relationship := association.field.Relationship; relationship.Kind == "has_one" { 30 | return association.Replace(values...) 31 | } 32 | return association.saveAssociations(values...) 33 | } 34 | 35 | // Replace replace current associations with new one 36 | func (association *Association) Replace(values ...interface{}) *Association { 37 | if association.Error != nil { 38 | return association 39 | } 40 | 41 | var ( 42 | relationship = association.field.Relationship 43 | scope = association.scope 44 | field = association.field.Field 45 | newDB = scope.NewDB() 46 | ) 47 | 48 | // Append new values 49 | association.field.Set(reflect.Zero(association.field.Field.Type())) 50 | association.saveAssociations(values...) 51 | 52 | // Belongs To 53 | if relationship.Kind == "belongs_to" { 54 | // Set foreign key to be null when clearing value (length equals 0) 55 | if len(values) == 0 { 56 | // Set foreign key to be nil 57 | var foreignKeyMap = map[string]interface{}{} 58 | for _, foreignKey := range relationship.ForeignDBNames { 59 | foreignKeyMap[foreignKey] = nil 60 | } 61 | association.setErr(newDB.Model(scope.Value).UpdateColumn(foreignKeyMap).Error) 62 | } 63 | } else { 64 | // Polymorphic Relations 65 | if relationship.PolymorphicDBName != "" { 66 | newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(relationship.PolymorphicDBName)), relationship.PolymorphicValue) 67 | } 68 | 69 | // Delete Relations except new created 70 | if len(values) > 0 { 71 | var associationForeignFieldNames, associationForeignDBNames []string 72 | if relationship.Kind == "many_to_many" { 73 | // if many to many relations, get association fields name from association foreign keys 74 | associationScope := scope.New(reflect.New(field.Type()).Interface()) 75 | for idx, dbName := range relationship.AssociationForeignFieldNames { 76 | if field, ok := associationScope.FieldByName(dbName); ok { 77 | associationForeignFieldNames = append(associationForeignFieldNames, field.Name) 78 | associationForeignDBNames = append(associationForeignDBNames, relationship.AssociationForeignDBNames[idx]) 79 | } 80 | } 81 | } else { 82 | // If has one/many relations, use primary keys 83 | for _, field := range scope.New(reflect.New(field.Type()).Interface()).PrimaryFields() { 84 | associationForeignFieldNames = append(associationForeignFieldNames, field.Name) 85 | associationForeignDBNames = append(associationForeignDBNames, field.DBName) 86 | } 87 | } 88 | 89 | newPrimaryKeys := scope.getColumnAsArray(associationForeignFieldNames, field.Interface()) 90 | 91 | if len(newPrimaryKeys) > 0 { 92 | sql := fmt.Sprintf("%v NOT IN (%v)", toQueryCondition(scope, associationForeignDBNames), toQueryMarks(newPrimaryKeys)) 93 | newDB = newDB.Where(sql, toQueryValues(newPrimaryKeys)...) 94 | } 95 | } 96 | 97 | if relationship.Kind == "many_to_many" { 98 | // if many to many relations, delete related relations from join table 99 | var sourceForeignFieldNames []string 100 | 101 | for _, dbName := range relationship.ForeignFieldNames { 102 | if field, ok := scope.FieldByName(dbName); ok { 103 | sourceForeignFieldNames = append(sourceForeignFieldNames, field.Name) 104 | } 105 | } 106 | 107 | if sourcePrimaryKeys := scope.getColumnAsArray(sourceForeignFieldNames, scope.Value); len(sourcePrimaryKeys) > 0 { 108 | newDB = newDB.Where(fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(sourcePrimaryKeys)), toQueryValues(sourcePrimaryKeys)...) 109 | 110 | association.setErr(relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, newDB, relationship)) 111 | } 112 | } else if relationship.Kind == "has_one" || relationship.Kind == "has_many" { 113 | // has_one or has_many relations, set foreign key to be nil (TODO or delete them?) 114 | var foreignKeyMap = map[string]interface{}{} 115 | for idx, foreignKey := range relationship.ForeignDBNames { 116 | foreignKeyMap[foreignKey] = nil 117 | if field, ok := scope.FieldByName(relationship.AssociationForeignFieldNames[idx]); ok { 118 | newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(foreignKey)), field.Field.Interface()) 119 | } 120 | } 121 | 122 | fieldValue := reflect.New(association.field.Field.Type()).Interface() 123 | association.setErr(newDB.Model(fieldValue).UpdateColumn(foreignKeyMap).Error) 124 | } 125 | } 126 | return association 127 | } 128 | 129 | // Delete remove relationship between source & passed arguments, but won't delete those arguments 130 | func (association *Association) Delete(values ...interface{}) *Association { 131 | if association.Error != nil { 132 | return association 133 | } 134 | 135 | var ( 136 | relationship = association.field.Relationship 137 | scope = association.scope 138 | field = association.field.Field 139 | newDB = scope.NewDB() 140 | ) 141 | 142 | if len(values) == 0 { 143 | return association 144 | } 145 | 146 | var deletingResourcePrimaryFieldNames, deletingResourcePrimaryDBNames []string 147 | for _, field := range scope.New(reflect.New(field.Type()).Interface()).PrimaryFields() { 148 | deletingResourcePrimaryFieldNames = append(deletingResourcePrimaryFieldNames, field.Name) 149 | deletingResourcePrimaryDBNames = append(deletingResourcePrimaryDBNames, field.DBName) 150 | } 151 | 152 | deletingPrimaryKeys := scope.getColumnAsArray(deletingResourcePrimaryFieldNames, values...) 153 | 154 | if relationship.Kind == "many_to_many" { 155 | // source value's foreign keys 156 | for idx, foreignKey := range relationship.ForeignDBNames { 157 | if field, ok := scope.FieldByName(relationship.ForeignFieldNames[idx]); ok { 158 | newDB = newDB.Where(fmt.Sprintf("%v = ?", scope.Quote(foreignKey)), field.Field.Interface()) 159 | } 160 | } 161 | 162 | // get association's foreign fields name 163 | var associationScope = scope.New(reflect.New(field.Type()).Interface()) 164 | var associationForeignFieldNames []string 165 | for _, associationDBName := range relationship.AssociationForeignFieldNames { 166 | if field, ok := associationScope.FieldByName(associationDBName); ok { 167 | associationForeignFieldNames = append(associationForeignFieldNames, field.Name) 168 | } 169 | } 170 | 171 | // association value's foreign keys 172 | deletingPrimaryKeys := scope.getColumnAsArray(associationForeignFieldNames, values...) 173 | sql := fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.AssociationForeignDBNames), toQueryMarks(deletingPrimaryKeys)) 174 | newDB = newDB.Where(sql, toQueryValues(deletingPrimaryKeys)...) 175 | 176 | association.setErr(relationship.JoinTableHandler.Delete(relationship.JoinTableHandler, newDB, relationship)) 177 | } else { 178 | var foreignKeyMap = map[string]interface{}{} 179 | for _, foreignKey := range relationship.ForeignDBNames { 180 | foreignKeyMap[foreignKey] = nil 181 | } 182 | 183 | if relationship.Kind == "belongs_to" { 184 | // find with deleting relation's foreign keys 185 | primaryKeys := scope.getColumnAsArray(relationship.AssociationForeignFieldNames, values...) 186 | newDB = newDB.Where( 187 | fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)), 188 | toQueryValues(primaryKeys)..., 189 | ) 190 | 191 | // set foreign key to be null if there are some records affected 192 | modelValue := reflect.New(scope.GetModelStruct().ModelType).Interface() 193 | if results := newDB.Model(modelValue).UpdateColumn(foreignKeyMap); results.Error == nil { 194 | if results.RowsAffected > 0 { 195 | scope.updatedAttrsWithValues(foreignKeyMap) 196 | } 197 | } else { 198 | association.setErr(results.Error) 199 | } 200 | } else if relationship.Kind == "has_one" || relationship.Kind == "has_many" { 201 | // find all relations 202 | primaryKeys := scope.getColumnAsArray(relationship.AssociationForeignFieldNames, scope.Value) 203 | newDB = newDB.Where( 204 | fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)), 205 | toQueryValues(primaryKeys)..., 206 | ) 207 | 208 | // only include those deleting relations 209 | newDB = newDB.Where( 210 | fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, deletingResourcePrimaryDBNames), toQueryMarks(deletingPrimaryKeys)), 211 | toQueryValues(deletingPrimaryKeys)..., 212 | ) 213 | 214 | // set matched relation's foreign key to be null 215 | fieldValue := reflect.New(association.field.Field.Type()).Interface() 216 | association.setErr(newDB.Model(fieldValue).UpdateColumn(foreignKeyMap).Error) 217 | } 218 | } 219 | 220 | // Remove deleted records from source's field 221 | if association.Error == nil { 222 | if field.Kind() == reflect.Slice { 223 | leftValues := reflect.Zero(field.Type()) 224 | 225 | for i := 0; i < field.Len(); i++ { 226 | reflectValue := field.Index(i) 227 | primaryKey := scope.getColumnAsArray(deletingResourcePrimaryFieldNames, reflectValue.Interface())[0] 228 | var isDeleted = false 229 | for _, pk := range deletingPrimaryKeys { 230 | if equalAsString(primaryKey, pk) { 231 | isDeleted = true 232 | break 233 | } 234 | } 235 | if !isDeleted { 236 | leftValues = reflect.Append(leftValues, reflectValue) 237 | } 238 | } 239 | 240 | association.field.Set(leftValues) 241 | } else if field.Kind() == reflect.Struct { 242 | primaryKey := scope.getColumnAsArray(deletingResourcePrimaryFieldNames, field.Interface())[0] 243 | for _, pk := range deletingPrimaryKeys { 244 | if equalAsString(primaryKey, pk) { 245 | association.field.Set(reflect.Zero(field.Type())) 246 | break 247 | } 248 | } 249 | } 250 | } 251 | 252 | return association 253 | } 254 | 255 | // Clear remove relationship between source & current associations, won't delete those associations 256 | func (association *Association) Clear() *Association { 257 | return association.Replace() 258 | } 259 | 260 | // Count return the count of current associations 261 | func (association *Association) Count() int { 262 | var ( 263 | count = 0 264 | relationship = association.field.Relationship 265 | scope = association.scope 266 | fieldValue = association.field.Field.Interface() 267 | query = scope.DB() 268 | ) 269 | 270 | if relationship.Kind == "many_to_many" { 271 | query = relationship.JoinTableHandler.JoinWith(relationship.JoinTableHandler, query, scope.Value) 272 | } else if relationship.Kind == "has_many" || relationship.Kind == "has_one" { 273 | primaryKeys := scope.getColumnAsArray(relationship.AssociationForeignFieldNames, scope.Value) 274 | query = query.Where( 275 | fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.ForeignDBNames), toQueryMarks(primaryKeys)), 276 | toQueryValues(primaryKeys)..., 277 | ) 278 | } else if relationship.Kind == "belongs_to" { 279 | primaryKeys := scope.getColumnAsArray(relationship.ForeignFieldNames, scope.Value) 280 | query = query.Where( 281 | fmt.Sprintf("%v IN (%v)", toQueryCondition(scope, relationship.AssociationForeignDBNames), toQueryMarks(primaryKeys)), 282 | toQueryValues(primaryKeys)..., 283 | ) 284 | } 285 | 286 | if relationship.PolymorphicType != "" { 287 | query = query.Where( 288 | fmt.Sprintf("%v.%v = ?", scope.New(fieldValue).QuotedTableName(), scope.Quote(relationship.PolymorphicDBName)), 289 | relationship.PolymorphicValue, 290 | ) 291 | } 292 | 293 | if err := query.Model(fieldValue).Count(&count).Error; err != nil { 294 | association.Error = err 295 | } 296 | return count 297 | } 298 | 299 | // saveAssociations save passed values as associations 300 | func (association *Association) saveAssociations(values ...interface{}) *Association { 301 | var ( 302 | scope = association.scope 303 | field = association.field 304 | relationship = field.Relationship 305 | ) 306 | 307 | saveAssociation := func(reflectValue reflect.Value) { 308 | // value has to been pointer 309 | if reflectValue.Kind() != reflect.Ptr { 310 | reflectPtr := reflect.New(reflectValue.Type()) 311 | reflectPtr.Elem().Set(reflectValue) 312 | reflectValue = reflectPtr 313 | } 314 | 315 | // value has to been saved for many2many 316 | if relationship.Kind == "many_to_many" { 317 | if scope.New(reflectValue.Interface()).PrimaryKeyZero() { 318 | association.setErr(scope.NewDB().Save(reflectValue.Interface()).Error) 319 | } 320 | } 321 | 322 | // Assign Fields 323 | var fieldType = field.Field.Type() 324 | var setFieldBackToValue, setSliceFieldBackToValue bool 325 | if reflectValue.Type().AssignableTo(fieldType) { 326 | field.Set(reflectValue) 327 | } else if reflectValue.Type().Elem().AssignableTo(fieldType) { 328 | // if field's type is struct, then need to set value back to argument after save 329 | setFieldBackToValue = true 330 | field.Set(reflectValue.Elem()) 331 | } else if fieldType.Kind() == reflect.Slice { 332 | if reflectValue.Type().AssignableTo(fieldType.Elem()) { 333 | field.Set(reflect.Append(field.Field, reflectValue)) 334 | } else if reflectValue.Type().Elem().AssignableTo(fieldType.Elem()) { 335 | // if field's type is slice of struct, then need to set value back to argument after save 336 | setSliceFieldBackToValue = true 337 | field.Set(reflect.Append(field.Field, reflectValue.Elem())) 338 | } 339 | } 340 | 341 | if relationship.Kind == "many_to_many" { 342 | association.setErr(relationship.JoinTableHandler.Add(relationship.JoinTableHandler, scope.NewDB(), scope.Value, reflectValue.Interface())) 343 | } else { 344 | association.setErr(scope.NewDB().Select(field.Name).Save(scope.Value).Error) 345 | 346 | if setFieldBackToValue { 347 | reflectValue.Elem().Set(field.Field) 348 | } else if setSliceFieldBackToValue { 349 | reflectValue.Elem().Set(field.Field.Index(field.Field.Len() - 1)) 350 | } 351 | } 352 | } 353 | 354 | for _, value := range values { 355 | reflectValue := reflect.ValueOf(value) 356 | indirectReflectValue := reflect.Indirect(reflectValue) 357 | if indirectReflectValue.Kind() == reflect.Struct { 358 | saveAssociation(reflectValue) 359 | } else if indirectReflectValue.Kind() == reflect.Slice { 360 | for i := 0; i < indirectReflectValue.Len(); i++ { 361 | saveAssociation(indirectReflectValue.Index(i)) 362 | } 363 | } else { 364 | association.setErr(errors.New("invalid value type")) 365 | } 366 | } 367 | return association 368 | } 369 | 370 | func (association *Association) setErr(err error) *Association { 371 | if err != nil { 372 | association.Error = err 373 | } 374 | return association 375 | } 376 | -------------------------------------------------------------------------------- /app/master/admin/static/vendor/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ --------------------------------------------------------------------------------