├── cache └── redis │ └── conn.go ├── common ├── code.go └── flags.go ├── config-example ├── db.go ├── oss.go ├── rabiitmq.go ├── redis.go ├── service.go ├── store.go └── user.go ├── db ├── file.go ├── mysql │ └── conn.go ├── user.go └── userfile.go ├── doc ├── ci-cd.png ├── structure.png └── table.sql ├── go.mod ├── go.sum ├── handler ├── Gin-handler │ ├── auth.go │ ├── mpupload.go │ ├── upload.go │ └── user.go ├── auth.go ├── mpupload.go ├── upload.go └── user.go ├── meta └── filemeta.go ├── mq ├── conn.go ├── consumer.go ├── define.go └── producer.go ├── readme.md ├── route └── router.go ├── service ├── Gin │ └── main.go ├── Microservice │ ├── account │ │ ├── handler │ │ │ ├── user.go │ │ │ └── user_file.go │ │ ├── main.go │ │ └── proto │ │ │ ├── user.micro.go │ │ │ ├── user.pb.go │ │ │ └── user.proto │ ├── apigw │ │ ├── handler │ │ │ └── user.go │ │ ├── main.go │ │ └── route │ │ │ └── router.go │ ├── dbproxy │ │ ├── client │ │ │ ├── client.go │ │ │ └── sort.go │ │ ├── config │ │ │ └── db.go │ │ ├── conn │ │ │ └── conn.go │ │ ├── main.go │ │ ├── mapper │ │ │ └── mapper.go │ │ ├── orm │ │ │ ├── define.go │ │ │ ├── file.go │ │ │ ├── user.go │ │ │ └── userfile.go │ │ ├── proto │ │ │ ├── proxy.micro.go │ │ │ ├── proxy.pb.go │ │ │ └── proxy.proto │ │ └── rpc │ │ │ └── proxy.go │ ├── download │ │ ├── api │ │ │ └── download.go │ │ ├── config │ │ │ └── config.go │ │ ├── main.go │ │ ├── proto │ │ │ ├── download.micro.go │ │ │ ├── download.pb.go │ │ │ └── download.proto │ │ ├── route │ │ │ └── router.go │ │ └── rpc │ │ │ └── entry.go │ ├── transfer │ │ ├── main.go │ │ └── process │ │ │ └── transfer.go │ └── upload │ │ ├── api │ │ ├── mpupload.go │ │ └── upload.go │ │ ├── config │ │ └── config.go │ │ ├── main.go │ │ ├── proto │ │ ├── upload.micro.go │ │ ├── upload.pb.go │ │ └── upload.proto │ │ ├── route │ │ └── router.go │ │ └── rpc │ │ └── entry.go └── normal │ ├── transfer │ └── main.go │ └── upload │ └── main.go ├── static ├── css │ ├── bootstrap.min.css │ └── fileinput.min.css ├── img │ ├── avatar.jpeg │ └── loading.gif ├── js │ ├── FileSaver.js │ ├── StreamSaver.js │ ├── auth.js │ ├── bootstrap.min.js │ ├── fileinput.min.js │ ├── jquery-3.2.1.min.js │ ├── layui.js │ ├── piexif.min.js │ ├── polyfill.min.js │ ├── popper.min.js │ ├── purify.min.js │ ├── sortable.min.js │ ├── sw.js │ └── theme.js └── view │ ├── download.html │ ├── home.html │ ├── signin.html │ ├── signup.html │ └── upload.html ├── store └── oss │ └── oss_conn.go ├── test ├── test_ceph.go └── test_mpupload.go ├── tree.md └── util ├── resp.go ├── shell.go └── util.go /cache/redis/conn.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | cfg "FileStore-Server/config" 5 | "fmt" 6 | "github.com/garyburd/redigo/redis" 7 | "time" 8 | ) 9 | 10 | var ( 11 | pool *redis.Pool 12 | redisHost = cfg.RedisHost 13 | redisPass = cfg.RedisPass 14 | ) 15 | 16 | //newRedisPool: 17 | func newRedisPool() *redis.Pool { 18 | return &redis.Pool{ 19 | MaxIdle: 50, 20 | MaxActive: 30, 21 | IdleTimeout: 300 * time.Second, 22 | // 返回一个redis连接 23 | Dial: func() (redis.Conn, error) { 24 | // 1. 打开连接 25 | c, err := redis.Dial("tcp", redisHost) 26 | if err != nil { 27 | fmt.Println(err) 28 | return nil, err 29 | } 30 | // 2. 访问认证 31 | if _, err = c.Do("AUTH", redisPass); err != nil { 32 | c.Close() 33 | return nil, err 34 | } 35 | return c, nil 36 | }, 37 | // 检查redis连接是否可用 38 | TestOnBorrow: func(conn redis.Conn, t time.Time) error { 39 | if time.Since(t) < time.Minute { 40 | return nil 41 | } 42 | _, err := conn.Do("PING") 43 | return err 44 | }, 45 | } 46 | } 47 | 48 | func init() { 49 | pool = newRedisPool() 50 | } 51 | 52 | func RedisPool() *redis.Pool { 53 | return pool 54 | } 55 | -------------------------------------------------------------------------------- /common/code.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | // ErrorCode : 错误码 4 | type ErrorCode int32 5 | 6 | const ( 7 | _ int32 = iota + 9999 8 | // StatusOK : 正常 9 | StatusOK 10 | // StatusParamInvalid : 请求参数无效 11 | StatusParamInvalid 12 | // StatusServerError : 服务出错 13 | StatusServerError 14 | // StatusRegisterFailed : 注册失败 15 | StatusRegisterFailed 16 | // StatusLoginFailed : 登录失败 17 | StatusLoginFailed 18 | // StatusTokenInvalid : 10005 token无效 19 | StatusTokenInvalid 20 | // StatusUserNotExists: 10006 用户不存在 21 | StatusUserNotExists 22 | ) 23 | -------------------------------------------------------------------------------- /common/flags.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "github.com/micro/cli" 4 | 5 | // CustomFlags : 自定义命令行参数 6 | var CustomFlags = []cli.Flag{ 7 | cli.StringFlag{ 8 | Name: "dbhost", 9 | Value: "127.0.0.1", 10 | Usage: "database address", 11 | }, 12 | cli.StringFlag{ 13 | Name: "mqhost", 14 | Value: "127.0.0.1", 15 | Usage: "mq(rabbitmq) address", 16 | }, 17 | cli.StringFlag{ 18 | Name: "cachehost", 19 | Value: "127.0.0.1", 20 | Usage: "cache(redis) address", 21 | }, 22 | cli.StringFlag{ 23 | Name: "cephhost", 24 | Value: "127.0.0.1", 25 | Usage: "ceph address", 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /config-example/db.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | DriverName = "mysql" 5 | DataSourceName = "root:root@tcp(localhost:3306)/fileserver?charset=utf8" 6 | ) -------------------------------------------------------------------------------- /config-example/oss.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | // OSSBucket : oss bucket名 5 | OSSBucket = "fileserver-go" 6 | 7 | // OSSEndpoint : oss endpoint 8 | OSSEndpoint = "oss-cn-shenzhen.aliyuncs.com" 9 | 10 | // OSSAccesskeyID : oss访问key 11 | OSSAccesskeyID = "<你的AccesskeyId>" 12 | 13 | // OSSAccessKeySecret : oss访问key secret 14 | OSSAccessKeySecret = "<你的AccessKeySecret>" 15 | ) 16 | -------------------------------------------------------------------------------- /config-example/rabiitmq.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | // AsyncTransferEnable : 是否开启文件异步转移(默认同步) 5 | AsyncTransferEnable = true 6 | // TransExchangeName : 用于文件transfer的交换机 7 | TransExchangeName = "uploadserver.name" 8 | // TransOSSQueueName : oss转移队列名 9 | TransOSSQueueName = "uploadserver.name.oss" 10 | // TransOSSErrQueueName : oss转移失败后写入另一个队列的队列名 11 | TransOSSErrQueueName = "uploadserver.name.oss.err" 12 | // TransOSSRoutingKey : routingkey 13 | TransOSSRoutingKey = "name" 14 | ) 15 | 16 | var ( 17 | // RabbitURL : rabbitmq服务的入口url 18 | RabbitURL = "amqp://guest:guest@localhost:5672/" 19 | ) -------------------------------------------------------------------------------- /config-example/redis.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | RedisHost = "localhost:port" 5 | RedisPass = "" 6 | ) 7 | -------------------------------------------------------------------------------- /config-example/service.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | // UploadServiceHost : 上传服务监听的地址 5 | UploadServiceHost = "0.0.0.0:8000" 6 | ) -------------------------------------------------------------------------------- /config-example/store.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | // TempLocalRootDir : 本地临时存储地址的路径 5 | TempLocalRootDir = "/data/fileserver/" 6 | // TempPartRootDir : 分块文件在本地临时存储地址的路径 7 | TempPartRootDir = "/data/fileserver_part/" 8 | // OSSRootDir : OSS的存储路径prefix 9 | OSSRootDir = "oss/" 10 | ) 11 | -------------------------------------------------------------------------------- /config-example/user.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | //用于加密的盐值(自定义) 5 | pwdSalt = "*#890" 6 | ) 7 | -------------------------------------------------------------------------------- /db/file.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | mydb "FileStore-Server/db/mysql" 6 | "fmt" 7 | ) 8 | 9 | //OnFileUploadFinished: 文件上传完成,meta保存到数据库 10 | func OnFileUploadFinished(filehash string,filename string,filesize int64,fileaddr string) bool { 11 | stmt,err := mydb.DBConn().Prepare( 12 | "insert ignore into tbl_file (`file_sha1`,`file_name`,`file_size`," + 13 | "`file_addr`,`status`) values (?,?,?,?,1)") 14 | if err != nil { 15 | fmt.Println("Failed to prepare statement, err: " + err.Error()) 16 | return false 17 | } 18 | defer stmt.Close() 19 | 20 | ret,err := stmt.Exec(filehash,filename,filesize,fileaddr) 21 | if err != nil { 22 | fmt.Println(err.Error()) 23 | return false 24 | } 25 | 26 | // 判断结果是否成功插入了 27 | if rf,err := ret.RowsAffected(); nil == err { 28 | if rf <= 0 { 29 | fmt.Printf("File with hash:%s has been upload before. ",filehash) 30 | } 31 | return true 32 | } 33 | 34 | return false 35 | } 36 | 37 | // TableFile : 文件表结构体 38 | type TableFile struct { 39 | FileHash string 40 | FileName sql.NullString 41 | FileSize sql.NullInt64 42 | FileAddr sql.NullString 43 | } 44 | 45 | // GetFileMeta: 从mysql获取文件元信息 46 | func GetFileMeta(filehash string) (*TableFile,error) { 47 | stmt,err := mydb.DBConn().Prepare( 48 | "select file_sha1,file_addr,file_name,file_size from tbl_file " + 49 | "where file_sha1=? and status=1 limit 1;") 50 | if err != nil { 51 | fmt.Println(err.Error()) 52 | return nil,err 53 | } 54 | defer stmt.Close() 55 | 56 | tfile := TableFile{} 57 | err = stmt.QueryRow(filehash).Scan(&tfile.FileHash,&tfile.FileAddr,&tfile.FileName,&tfile.FileSize) 58 | if err != nil { 59 | if err == sql.ErrNoRows { 60 | // 查不到对应记录, 返回参数及错误均为nil 61 | return nil, nil 62 | } else { 63 | fmt.Println(err.Error()) 64 | return nil, err 65 | } 66 | } 67 | return &tfile,nil 68 | } 69 | 70 | 71 | // UpdateFileLocation : 更新文件的存储地址(如文件被转移了) 72 | func UpdateFileLocation(filehash string, fileaddr string) bool { 73 | stmt, err := mydb.DBConn().Prepare( 74 | "update tbl_file set`file_addr`=? where `file_sha1`=? limit 1") 75 | if err != nil { 76 | fmt.Println("预编译sql失败, err:" + err.Error()) 77 | return false 78 | } 79 | defer stmt.Close() 80 | 81 | ret, err := stmt.Exec(fileaddr, filehash) 82 | if err != nil { 83 | fmt.Println(err.Error()) 84 | return false 85 | } 86 | if rf, err := ret.RowsAffected(); nil == err { 87 | if rf <= 0 { 88 | fmt.Printf("更新文件location失败, filehash:%s", filehash) 89 | } 90 | return true 91 | } 92 | return false 93 | } -------------------------------------------------------------------------------- /db/mysql/conn.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | cfg "FileStore-Server/config" 5 | "database/sql" 6 | "fmt" 7 | _ "github.com/go-sql-driver/mysql" 8 | "log" 9 | "os" 10 | ) 11 | 12 | var db *sql.DB 13 | 14 | func init() { 15 | //db, _ = sql.Open("mysql", "root:root@tcp(localhost:3306)/fileserver?charset=utf8") 16 | db, _ = sql.Open(cfg.DriverName, cfg.DataSourceName) 17 | db.SetMaxOpenConns(1000) 18 | 19 | err := db.Ping() 20 | if err != nil { 21 | fmt.Println("Failed to connect to mysql, err: " + err.Error()) 22 | os.Exit(1) 23 | } 24 | } 25 | 26 | // DBConn: 返回数据库连接对象 27 | func DBConn() *sql.DB { 28 | return db 29 | } 30 | 31 | func ParseRows(rows *sql.Rows) []map[string]interface{} { 32 | columns, _ := rows.Columns() 33 | scanArgs := make([]interface{}, len(columns)) 34 | values := make([]interface{}, len(columns)) 35 | for j := range values { 36 | scanArgs[j] = &values[j] 37 | } 38 | 39 | record := make(map[string]interface{}) 40 | records := make([]map[string]interface{}, 0) 41 | for rows.Next() { 42 | //将行数据保存到record字典 43 | err := rows.Scan(scanArgs...) 44 | checkErr(err) 45 | 46 | for i, col := range values { 47 | if col != nil { 48 | record[columns[i]] = col 49 | } 50 | } 51 | records = append(records, record) 52 | } 53 | return records 54 | } 55 | 56 | func checkErr(err error) { 57 | if err != nil { 58 | log.Fatal(err) 59 | panic(err) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /db/user.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | mydb "FileStore-Server/db/mysql" 5 | "fmt" 6 | ) 7 | 8 | //User: 用户表model 9 | type User struct { 10 | Username string 11 | Email string 12 | Phone string 13 | SignupAt string 14 | LastActiveAt string 15 | Status int 16 | } 17 | 18 | //UserSignUp: 通过用户名和密码完成注册 19 | func UserSignUp(username string,passwd string)bool { 20 | //预编译sql语句 21 | stmt,err := mydb.DBConn().Prepare( 22 | "insert ignore into tbl_user (`user_name`,`user_pwd`) values (?,?)") 23 | if err != nil { 24 | fmt.Println("Failed to insert, err: "+err.Error()) 25 | return false 26 | } 27 | defer stmt.Close() 28 | 29 | //进行数据库插入 30 | ret,err := stmt.Exec(username,passwd) 31 | if err != nil { 32 | fmt.Println("Failed to insert, err: "+err.Error()) 33 | } 34 | 35 | //判断注册结果并返回 36 | if rowsAffected, err := ret.RowsAffected(); nil == err && rowsAffected > 0 { 37 | return true 38 | } 39 | return false 40 | } 41 | 42 | //UserSignin: 验证登录 43 | func UserSignin(username string,encpwd string) bool { 44 | //预编译sql语句 45 | stmt,err := mydb.DBConn().Prepare( 46 | "select * from tbl_user where user_name = ? limit 1") 47 | if err != nil { 48 | fmt.Println(err.Error()) 49 | return false 50 | } 51 | defer stmt.Close() 52 | 53 | //进行数据库查询,并处理错误 54 | rows,err := stmt.Query(username) 55 | if err != nil { 56 | fmt.Println(err.Error()) 57 | return false 58 | } else if rows == nil { 59 | fmt.Printf("username:%s not found: \n",username) 60 | return false 61 | } 62 | 63 | //将查询结果转换为map数组 64 | pRows := mydb.ParseRows(rows) 65 | //判断查询结果长度以及密码是否匹配 66 | if len(pRows)>0 && string(pRows[0]["user_pwd"].([]byte)) == encpwd { 67 | return true 68 | } 69 | return false 70 | } 71 | 72 | //UpdateToken: 刷新用户登录token 73 | func UpdateToken(username string,token string) bool { 74 | stmt,err := mydb.DBConn().Prepare( 75 | "replace into tbl_user_token (`user_name`,`user_token`) values (?,?)") 76 | if err != nil { 77 | fmt.Println(err.Error()) 78 | return false 79 | } 80 | defer stmt.Close() 81 | 82 | _,err = stmt.Query(username,token) 83 | if err != nil { 84 | fmt.Println(err.Error()) 85 | return false 86 | } 87 | 88 | return true 89 | } 90 | 91 | //GetUserInfo: 查询用户信息 92 | func GetUserInfo(username string) (User,error) { 93 | user := User{} 94 | 95 | stmt,err := mydb.DBConn().Prepare( 96 | "select user_name,signup_at from tbl_user where user_name=? limit 1") 97 | if err != nil { 98 | return user,err 99 | } 100 | defer stmt.Close() 101 | 102 | //执行查询 103 | err = stmt.QueryRow(username).Scan(&user.Username,&user.SignupAt) 104 | if err != nil { 105 | return user,err 106 | } 107 | 108 | return user,nil 109 | } -------------------------------------------------------------------------------- /db/userfile.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | mydb "FileStore-Server/db/mysql" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | //UserFile: 用户文件表结构体 10 | type UserFile struct { 11 | UserName string 12 | FileHash string 13 | FileName string 14 | FileSize int64 15 | UploadAt string 16 | LastUpdated string 17 | } 18 | 19 | //OnUserFileUploadFinished: 更新用户文件表 20 | func OnUserFileUploadFinished(username,filehash,filename string,filesize int64) bool { 21 | stmt,err := mydb.DBConn().Prepare( 22 | "insert ignore into tbl_user_file (`user_name`,`file_sha1`,`file_name`," + 23 | "`file_size`,`upload_at`) values (?,?,?,?,?)") 24 | if err != nil { 25 | fmt.Println(err.Error()) 26 | return false 27 | } 28 | defer stmt.Close() 29 | 30 | _,err = stmt.Exec(username,filehash,filename,filesize,time.Now()) 31 | if err != nil { 32 | fmt.Println(err.Error()) 33 | return false 34 | } 35 | return true 36 | } 37 | 38 | //QueryUserFileMetas: 批量获取用户文件信息 39 | func QueryUserFileMetas(username string,limit int) ([]UserFile,error) { 40 | stmt, err := mydb.DBConn().Prepare( 41 | "select file_sha1,file_name,file_size,upload_at,last_update from" + 42 | " tbl_user_file where user_name=? limit ?") 43 | if err != nil { 44 | return nil, err 45 | } 46 | defer stmt.Close() 47 | 48 | rows, err := stmt.Query(username, limit) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | var userFiles []UserFile 54 | for rows.Next() { 55 | ufile := UserFile{} 56 | err = rows.Scan(&ufile.FileHash, &ufile.FileName, &ufile.FileSize, 57 | &ufile.UploadAt, &ufile.LastUpdated) 58 | if err != nil { 59 | fmt.Println(err.Error()) 60 | break 61 | } 62 | 63 | userFiles = append(userFiles,ufile) 64 | } 65 | 66 | return userFiles,nil 67 | } -------------------------------------------------------------------------------- /doc/ci-cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanChunStone/FileServer-Golang/06041052219136367131df818fcf0ea5366b2a9b/doc/ci-cd.png -------------------------------------------------------------------------------- /doc/structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanChunStone/FileServer-Golang/06041052219136367131df818fcf0ea5366b2a9b/doc/structure.png -------------------------------------------------------------------------------- /doc/table.sql: -------------------------------------------------------------------------------- 1 | -- 创建文件表 2 | CREATE TABLE `tbl_file` ( 3 | `id` int(11) NOT NULL AUTO_INCREMENT, 4 | `file_sha1` char(40) NOT NULL DEFAULT '' COMMENT '文件hash', 5 | `file_name` varchar(256) NOT NULL DEFAULT '' COMMENT '文件名', 6 | `file_size` bigint(20) DEFAULT '0' COMMENT '文件大小', 7 | `file_addr` varchar(1024) NOT NULL DEFAULT '' COMMENT '文件存储位置', 8 | `create_at` datetime default NOW() COMMENT '创建日期', 9 | `update_at` datetime default NOW() on update current_timestamp() COMMENT '更新日期', 10 | `status` int(11) NOT NULL DEFAULT '0' COMMENT '状态(可用/禁用/已删除等状态)', 11 | `ext1` int(11) DEFAULT '0' COMMENT '备用字段1', 12 | `ext2` text COMMENT '备用字段2', 13 | PRIMARY KEY (`id`), 14 | UNIQUE KEY `idx_file_hash` (`file_sha1`), 15 | KEY `idx_status` (`status`) 16 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 17 | 18 | -- 创建用户表 19 | CREATE TABLE `tbl_user` ( 20 | `id` int(11) NOT NULL AUTO_INCREMENT, 21 | `user_name` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名', 22 | `user_pwd` varchar(256) NOT NULL DEFAULT '' COMMENT '用户encoded密码', 23 | `email` varchar(64) DEFAULT '' COMMENT '邮箱', 24 | `phone` varchar(128) DEFAULT '' COMMENT '手机号', 25 | `email_validated` tinyint(1) DEFAULT 0 COMMENT '邮箱是否已验证', 26 | `phone_validated` tinyint(1) DEFAULT 0 COMMENT '手机号是否已验证', 27 | `signup_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册日期', 28 | `last_active` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后活跃时间戳', 29 | `profile` text COMMENT '用户属性', 30 | `status` int(11) NOT NULL DEFAULT '0' COMMENT '账户状态(启用/禁用/锁定/标记删除等)', 31 | PRIMARY KEY (`id`), 32 | UNIQUE KEY `idx_username` (`user_name`), 33 | KEY `idx_status` (`status`) 34 | ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4; 35 | 36 | -- 创建用户token表 37 | CREATE TABLE `tbl_user_token` ( 38 | `id` int(11) NOT NULL AUTO_INCREMENT, 39 | `user_name` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名', 40 | `user_token` char(40) NOT NULL DEFAULT '' COMMENT '用户登录token', 41 | PRIMARY KEY (`id`), 42 | UNIQUE KEY `idx_username` (`user_name`) 43 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 44 | 45 | -- 创建用户文件表 46 | CREATE TABLE `tbl_user_file` ( 47 | `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT, 48 | `user_name` varchar(64) NOT NULL, 49 | `file_sha1` varchar(64) NOT NULL DEFAULT '' COMMENT '文件hash', 50 | `file_size` bigint(20) DEFAULT '0' COMMENT '文件大小', 51 | `file_name` varchar(256) NOT NULL DEFAULT '' COMMENT '文件名', 52 | `upload_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间', 53 | `last_update` datetime DEFAULT CURRENT_TIMESTAMP 54 | ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', 55 | `status` int(11) NOT NULL DEFAULT '0' COMMENT '文件状态(0正常1已删除2禁用)', 56 | UNIQUE KEY `idx_user_file` (`user_name`, `file_sha1`), 57 | KEY `idx_status` (`status`), 58 | KEY `idx_user_id` (`user_name`) 59 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module FileStore-Server 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/aliyun/aliyun-oss-go-sdk v2.0.1+incompatible 7 | github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect 8 | github.com/garyburd/redigo v1.6.0 9 | github.com/gin-contrib/cors v1.3.0 10 | github.com/gin-gonic/gin v1.4.0 11 | github.com/go-sql-driver/mysql v1.4.1 12 | github.com/golang/protobuf v1.3.2 13 | github.com/micro/cli v0.2.0 14 | github.com/micro/go-micro v1.8.0 15 | github.com/micro/go-plugins v1.2.0 16 | github.com/mitchellh/mapstructure v1.1.2 17 | github.com/satori/go.uuid v1.2.0 // indirect 18 | github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 19 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 20 | ) 21 | -------------------------------------------------------------------------------- /handler/Gin-handler/auth.go: -------------------------------------------------------------------------------- 1 | package GinHandler 2 | 3 | import ( 4 | "FileStore-Server/common" 5 | "FileStore-Server/util" 6 | "github.com/gin-gonic/gin" 7 | "log" 8 | "net/http" 9 | nativeHandler "FileStore-Server/handler" 10 | ) 11 | 12 | //HTTPInterceptor: http请求拦截器 13 | func HTTPInterceptor() gin.HandlerFunc { 14 | return func(c *gin.Context) { 15 | username := c.Request.FormValue("username") 16 | token := c.Request.FormValue("token") 17 | 18 | log.Println("\nusername:"+username+"\ntoken:"+token) 19 | 20 | // 验证token是否有效 21 | if len(username)<3 || !nativeHandler.IsTokenValid(token) { 22 | // 如果token校验失败,则跳转到直接返回失败提示 23 | c.Abort() 24 | c.JSON(http.StatusOK,util.NewRespMsg(int(common.StatusTokenInvalid), "token无效", nil, )) 25 | return 26 | } 27 | 28 | // 如果校验通过,则将请求转到下一个handler处理 29 | c.Next() 30 | } 31 | } -------------------------------------------------------------------------------- /handler/Gin-handler/mpupload.go: -------------------------------------------------------------------------------- 1 | package GinHandler 2 | 3 | import ( 4 | "fmt" 5 | "FileStore-Server/util" 6 | "github.com/garyburd/redigo/redis" 7 | "github.com/gin-gonic/gin" 8 | "log" 9 | "math" 10 | "net/http" 11 | "os" 12 | "path" 13 | "strconv" 14 | "strings" 15 | "time" 16 | nativeHandler "FileStore-Server/handler" 17 | rPool "filestore-server/cache/redis" 18 | dblayer "FileStore-Server/db" 19 | ) 20 | 21 | //InitialMultipartUploadHandler: 初始化分块上传 22 | func InitialMultipartUploadHandler(c *gin.Context) { 23 | // 解析用户参数 24 | username := c.Request.FormValue("username") 25 | filehash := c.Request.FormValue("filehash") 26 | filesize, err := strconv.Atoi(c.Request.FormValue("filesize")) 27 | if err != nil { 28 | log.Println(err.Error()) 29 | c.JSON(http.StatusOK,gin.H{ 30 | "msg":"解析参数失败", 31 | "code":-1, 32 | }) 33 | return 34 | } 35 | 36 | // 获得redis的一个连接 37 | rConn := rPool.RedisPool().Get() 38 | defer rConn.Close() 39 | 40 | // 生成分块上传的初始化信息 41 | upInfo := nativeHandler.MultipartUploadInfo{ 42 | FileHash:filehash, 43 | FileSize:filesize, 44 | UploadID:username+fmt.Sprintf("%x",time.Now().UnixNano()), 45 | ChunkSize:5*1024*1024, 46 | ChunkCount:int(math.Ceil(float64(filesize)/(5*1024*1024))), 47 | } 48 | 49 | // 将初始化信息写入redis 50 | rConn.Do("HSET", "MP_"+upInfo.UploadID, "chunkcount", upInfo.ChunkCount) 51 | rConn.Do("HSET", "MP_"+upInfo.UploadID, "filehash", upInfo.FileHash) 52 | rConn.Do("HSET", "MP_"+upInfo.UploadID, "filesize", upInfo.FileSize) 53 | 54 | // 将响应初始化数据返回到客户端 55 | c.Data(http.StatusOK,"application/json",util.NewRespMsg(0, "OK", upInfo).JSONBytes()) 56 | } 57 | 58 | //UploadPartHandler: 上传文件分块 59 | func UploadPartHandler(c *gin.Context) { 60 | // 解析用户请求参数 61 | // username := c.Request.FormValue("username") 62 | uploadID := c.Request.FormValue("uploadid") 63 | chunkIndex := c.Request.FormValue("index") 64 | 65 | // 获得redis连接池中的一个连接 66 | rConn := rPool.RedisPool().Get() 67 | defer rConn.Close() 68 | 69 | // 获得文件句柄,用于存储分块内容 70 | fpath := "tempFiles/data/" + uploadID + "/" + chunkIndex 71 | os.MkdirAll(path.Dir(fpath), 0744) 72 | 73 | fd, err := os.Create(fpath) 74 | if err != nil { 75 | log.Println(err.Error()) 76 | c.JSON(http.StatusOK,gin.H{ 77 | "msg":"Upload part failed", 78 | "code":-1, 79 | }) 80 | return 81 | } 82 | defer fd.Close() 83 | 84 | buf := make([]byte, 1024*1024) 85 | for { 86 | n, err := c.Request.Body.Read(buf) 87 | fd.Write(buf[:n]) 88 | if err != nil { 89 | break 90 | } 91 | } 92 | 93 | // 更新redis缓存状态 94 | rConn.Do("HSET", "MP_"+uploadID, "chkidx_"+chunkIndex, 1) 95 | 96 | // 返回处理结果到客户端 97 | c.Data(http.StatusOK,"application/json",util.NewRespMsg(0, "OK", nil).JSONBytes()) 98 | } 99 | 100 | //CompleteUploadHandler: 通知上传合并 101 | func CompleteUploadHandler(c *gin.Context) { 102 | // 解析请求参数 103 | upid := c.Request.FormValue("uploadid") 104 | username := c.Request.FormValue("username") 105 | filehash := c.Request.FormValue("filehash") 106 | filesize := c.Request.FormValue("filesize") 107 | filename := c.Request.FormValue("filename") 108 | 109 | // 获得redis连接池中的一个连接 110 | rConn := rPool.RedisPool().Get() 111 | defer rConn.Close() 112 | 113 | // 通过uploadid查询redis并判断是否所有分块上传完成 114 | data,err := redis.Values(rConn.Do("HGETALL","MP_"+upid)) 115 | if err != nil{ 116 | log.Println(err.Error()) 117 | c.JSON(http.StatusOK,gin.H{ 118 | "msg":"complete upload failed", 119 | "code":-1, 120 | }) 121 | return 122 | } 123 | 124 | totalCount := 0 //所有块的数量 125 | chunkCount := 0 //已完成的块的数量 126 | for i:=0;i go run service/normal/transfer/main.go 116 | 117 | - 原生Go语言模式云盘服务端启动 118 | 119 | > go run service/normal/upload/main.go 120 | 121 | - Gin框架模式云盘服务端启动 122 | 123 | > go run service/Gin/main.go 124 | 125 | - 微服务模式 126 | 127 | - 账户相关微服务启动 128 | 129 | > go run service/Microservice/account/main.go --registry=consul 130 | 131 | - API网关服务启动 132 | 133 | > go run service/Microservice/apigw/main.go --registry=consul 134 | 135 | -------------------------------------------------------------------------------- /route/router.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | handler "FileStore-Server/handler/Gin-handler" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func Router() *gin.Engine { 9 | // gin framework, 包括Logger, Recovery 10 | router := gin.Default() 11 | 12 | // 处理静态资源 13 | router.Static("/static/","./static") 14 | 15 | // 无需验证就能访问的接口 16 | router.GET("/user/signup",handler.SignupHandler) 17 | router.POST("/user/signup",handler.DoSignupHandler) 18 | router.GET("/user/signin",handler.SignInHandler) 19 | router.POST("/user/signin",handler.DoSignInHandler) 20 | router.GET("/", handler.SignInHandler) 21 | 22 | // 加入中间件,用于验证token的拦截器 23 | router.Use(handler.HTTPInterceptor()) 24 | 25 | // Use 中间件之后的接口,都需要通过拦截器 26 | // 用户信息 27 | router.POST("/user/info",handler.UserInfoHandler) 28 | 29 | // 上传文件 30 | router.GET("/file/upload",handler.UploadHandler) 31 | router.POST("/file/upload",handler.DoUploadHandler) 32 | router.POST("/file/fastupload",handler.TryFastUploadHandler) 33 | // 查询文件 34 | router.POST("/file/meta",handler.GetFileMetaHandler) 35 | router.POST("/file/query",handler.FileQueryHandler) 36 | // 下载文件 37 | router.POST("/file/download",handler.DownloadHandler) 38 | router.POST("/file/downloadurl",handler.DownloadURLHandler) 39 | // 更新文件 40 | router.POST("/file/update",handler.FileMetaUpdateHandler) 41 | // 删除文件 42 | router.POST("/file/delete",handler.FileDeleteHandler) 43 | 44 | // 分块上传 45 | router.POST("/file/mpupload/init",handler.InitialMultipartUploadHandler) 46 | router.POST("/file/mpupload/uppart",handler.UploadPartHandler) 47 | router.POST("/file/mpupload/complete",handler.CompleteUploadHandler) 48 | 49 | return router 50 | } -------------------------------------------------------------------------------- /service/Gin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "FileStore-Server/config" 5 | "FileStore-Server/route" 6 | ) 7 | 8 | func main() { 9 | // gin框架 10 | router := route.Router() 11 | router.Run(config.UploadServiceHost) 12 | } -------------------------------------------------------------------------------- /service/Microservice/account/handler/user.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "FileStore-Server/common" 5 | "FileStore-Server/config" 6 | dblayer "FileStore-Server/db" 7 | proto "FileStore-Server/service/Microservice/account/proto" 8 | "FileStore-Server/util" 9 | "context" 10 | "fmt" 11 | "time" 12 | ) 13 | 14 | type User struct {} 15 | 16 | // GenToken : 生成token 17 | func GenToken(username string) string { 18 | // 40位字符:md5(username+timestamp+token_salt)+timestamp[:8] 19 | ts := fmt.Sprintf("%x", time.Now().Unix()) 20 | tokenPrefix := util.MD5([]byte(username + ts + "_tokensalt")) 21 | return tokenPrefix + ts[:8] 22 | } 23 | 24 | //Signup: 用户注册 25 | func (u *User) Signup(ctx context.Context, req *proto.ReqSignup, resp *proto.RespSignup) error { 26 | username := req.Username 27 | passwd := req.Password 28 | 29 | if len(username) < 3 || len(passwd) < 5 { 30 | resp.Code = common.StatusParamInvalid 31 | resp.Message = "注册参数无效" 32 | return nil 33 | } 34 | 35 | //对密码进行加盐及取Sha1值加密 36 | encPasswd := util.Sha1([]byte(passwd+config.PwdSalt)) 37 | ok := dblayer.UserSignUp(username,encPasswd) 38 | if ok { 39 | resp.Code = common.StatusOK 40 | resp.Message = "注册成功" 41 | }else { 42 | resp.Code = common.StatusRegisterFailed 43 | resp.Message = "注册失败" 44 | } 45 | return nil 46 | } 47 | 48 | // 用户登录 49 | func (u *User) Signin(context.Context, *proto.ReqSignin, *proto.RespSignin) error { 50 | 51 | } 52 | 53 | // 获取用户信息 54 | func (u *User) UserInfo(context.Context, *proto.ReqUserInfo, *proto.RespUserInfo) error { 55 | 56 | } -------------------------------------------------------------------------------- /service/Microservice/account/handler/user_file.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "FileStore-Server/common" 5 | "context" 6 | "encoding/json" 7 | 8 | proto "FileStore-Server/service/Microservice/account/proto" 9 | dbcli "FileStore-Server/service/Microservice/dbproxy/client" 10 | ) 11 | 12 | // UserFiles : 获取用户文件列表 13 | func (u *User) UserFiles(ctx context.Context, req *proto.ReqUserFile, res *proto.RespUserFile) error { 14 | dbResp, err := dbcli.QueryUserFileMetas(req.Username, int(req.Limit)) 15 | if err != nil || !dbResp.Suc { 16 | res.Code = common.StatusServerError 17 | return err 18 | } 19 | 20 | userFiles := dbcli.ToTableUserFiles(dbResp.Data) 21 | data, err := json.Marshal(userFiles) 22 | if err != nil { 23 | res.Code = common.StatusServerError 24 | return nil 25 | } 26 | 27 | res.FileData = data 28 | return nil 29 | } 30 | 31 | // UserFiles: 用户文件重命名 32 | func (u *User) UserFileRename(ctx context.Context, req *proto.ReqUserFileRename, res *proto.RespUserFileRename) error { 33 | dbResp, err := dbcli.RenameFileName(req.Username, req.Filehash, req.NewFileName) 34 | if err != nil || !dbResp.Suc { 35 | res.Code = common.StatusServerError 36 | return err 37 | } 38 | 39 | userFiles := dbcli.ToTableUserFiles(dbResp.Data) 40 | data, err := json.Marshal(userFiles) 41 | if err != nil { 42 | res.Code = common.StatusServerError 43 | return nil 44 | } 45 | 46 | res.FileData = data 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /service/Microservice/account/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "FileStore-Server/service/Microservice/account/handler" 5 | proto "FileStore-Server/service/Microservice/account/proto" 6 | micro "github.com/micro/go-micro" 7 | "github.com/micro/go-micro/registry" 8 | "github.com/micro/go-micro/registry/consul" 9 | "log" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | // 修改consul地址 15 | reg := consul.NewRegistry(func(op *registry.Options){ 16 | op.Addrs = []string{ 17 | "47.95.253.230:8500", 18 | } 19 | }) 20 | 21 | // 创建一个service 22 | service := micro.NewService( 23 | micro.Registry(reg), 24 | micro.Name("go.micro.service.user"), 25 | micro.RegisterTTL(time.Second*30), 26 | micro.RegisterInterval(time.Second*10), 27 | ) 28 | service.Init() 29 | 30 | proto.RegisterUserServiceHandler(service.Server(),new(handler.User)) 31 | 32 | if err := service.Run(); err != nil { 33 | log.Println(err) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /service/Microservice/account/proto/user.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: user.proto 3 | 4 | package go_micro_service_user 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | client "github.com/micro/go-micro/client" 15 | server "github.com/micro/go-micro/server" 16 | ) 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var _ = proto.Marshal 20 | var _ = fmt.Errorf 21 | var _ = math.Inf 22 | 23 | // This is a compile-time assertion to ensure that this generated file 24 | // is compatible with the proto package it is being compiled against. 25 | // A compilation error at this line likely means your copy of the 26 | // proto package needs to be updated. 27 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 28 | 29 | // Reference imports to suppress errors if they are not otherwise used. 30 | var _ context.Context 31 | var _ client.Option 32 | var _ server.Option 33 | 34 | // Client API for UserService service 35 | 36 | type UserService interface { 37 | // 用户注册 38 | Signup(ctx context.Context, in *ReqSignup, opts ...client.CallOption) (*RespSignup, error) 39 | // 用户登录 40 | Signin(ctx context.Context, in *ReqSignin, opts ...client.CallOption) (*RespSignin, error) 41 | // 获取用户信息 42 | UserInfo(ctx context.Context, in *ReqUserInfo, opts ...client.CallOption) (*RespUserInfo, error) 43 | // 获取用户文件 44 | UserFiles(ctx context.Context, in *ReqUserFile, opts ...client.CallOption) (*RespUserFile, error) 45 | // 重命名文件 46 | UserFileRename(ctx context.Context, in *ReqUserFileRename, opts ...client.CallOption) (*RespUserFileRename, error) 47 | } 48 | 49 | type userService struct { 50 | c client.Client 51 | name string 52 | } 53 | 54 | func NewUserService(name string, c client.Client) UserService { 55 | if c == nil { 56 | c = client.NewClient() 57 | } 58 | if len(name) == 0 { 59 | name = "go.micro.service.user" 60 | } 61 | return &userService{ 62 | c: c, 63 | name: name, 64 | } 65 | } 66 | 67 | func (c *userService) Signup(ctx context.Context, in *ReqSignup, opts ...client.CallOption) (*RespSignup, error) { 68 | req := c.c.NewRequest(c.name, "UserService.Signup", in) 69 | out := new(RespSignup) 70 | err := c.c.Call(ctx, req, out, opts...) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return out, nil 75 | } 76 | 77 | func (c *userService) Signin(ctx context.Context, in *ReqSignin, opts ...client.CallOption) (*RespSignin, error) { 78 | req := c.c.NewRequest(c.name, "UserService.Signin", in) 79 | out := new(RespSignin) 80 | err := c.c.Call(ctx, req, out, opts...) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return out, nil 85 | } 86 | 87 | func (c *userService) UserInfo(ctx context.Context, in *ReqUserInfo, opts ...client.CallOption) (*RespUserInfo, error) { 88 | req := c.c.NewRequest(c.name, "UserService.UserInfo", in) 89 | out := new(RespUserInfo) 90 | err := c.c.Call(ctx, req, out, opts...) 91 | if err != nil { 92 | return nil, err 93 | } 94 | return out, nil 95 | } 96 | 97 | func (c *userService) UserFiles(ctx context.Context, in *ReqUserFile, opts ...client.CallOption) (*RespUserFile, error) { 98 | req := c.c.NewRequest(c.name, "UserService.UserFiles", in) 99 | out := new(RespUserFile) 100 | err := c.c.Call(ctx, req, out, opts...) 101 | if err != nil { 102 | return nil, err 103 | } 104 | return out, nil 105 | } 106 | 107 | func (c *userService) UserFileRename(ctx context.Context, in *ReqUserFileRename, opts ...client.CallOption) (*RespUserFileRename, error) { 108 | req := c.c.NewRequest(c.name, "UserService.UserFileRename", in) 109 | out := new(RespUserFileRename) 110 | err := c.c.Call(ctx, req, out, opts...) 111 | if err != nil { 112 | return nil, err 113 | } 114 | return out, nil 115 | } 116 | 117 | // Server API for UserService service 118 | 119 | type UserServiceHandler interface { 120 | // 用户注册 121 | Signup(context.Context, *ReqSignup, *RespSignup) error 122 | // 用户登录 123 | Signin(context.Context, *ReqSignin, *RespSignin) error 124 | // 获取用户信息 125 | UserInfo(context.Context, *ReqUserInfo, *RespUserInfo) error 126 | // 获取用户文件 127 | UserFiles(context.Context, *ReqUserFile, *RespUserFile) error 128 | // 重命名文件 129 | UserFileRename(context.Context, *ReqUserFileRename, *RespUserFileRename) error 130 | } 131 | 132 | func RegisterUserServiceHandler(s server.Server, hdlr UserServiceHandler, opts ...server.HandlerOption) error { 133 | type userService interface { 134 | Signup(ctx context.Context, in *ReqSignup, out *RespSignup) error 135 | Signin(ctx context.Context, in *ReqSignin, out *RespSignin) error 136 | UserInfo(ctx context.Context, in *ReqUserInfo, out *RespUserInfo) error 137 | UserFiles(ctx context.Context, in *ReqUserFile, out *RespUserFile) error 138 | UserFileRename(ctx context.Context, in *ReqUserFileRename, out *RespUserFileRename) error 139 | } 140 | type UserService struct { 141 | userService 142 | } 143 | h := &userServiceHandler{hdlr} 144 | return s.Handle(s.NewHandler(&UserService{h}, opts...)) 145 | } 146 | 147 | type userServiceHandler struct { 148 | UserServiceHandler 149 | } 150 | 151 | func (h *userServiceHandler) Signup(ctx context.Context, in *ReqSignup, out *RespSignup) error { 152 | return h.UserServiceHandler.Signup(ctx, in, out) 153 | } 154 | 155 | func (h *userServiceHandler) Signin(ctx context.Context, in *ReqSignin, out *RespSignin) error { 156 | return h.UserServiceHandler.Signin(ctx, in, out) 157 | } 158 | 159 | func (h *userServiceHandler) UserInfo(ctx context.Context, in *ReqUserInfo, out *RespUserInfo) error { 160 | return h.UserServiceHandler.UserInfo(ctx, in, out) 161 | } 162 | 163 | func (h *userServiceHandler) UserFiles(ctx context.Context, in *ReqUserFile, out *RespUserFile) error { 164 | return h.UserServiceHandler.UserFiles(ctx, in, out) 165 | } 166 | 167 | func (h *userServiceHandler) UserFileRename(ctx context.Context, in *ReqUserFileRename, out *RespUserFileRename) error { 168 | return h.UserServiceHandler.UserFileRename(ctx, in, out) 169 | } 170 | -------------------------------------------------------------------------------- /service/Microservice/account/proto/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package go.micro.service.user; 4 | 5 | // 编译命令(service/Microservice目录下执行): 6 | // protoc --proto_path=./account/proto --go_out=./account/proto --micro_out=./account/proto ./account/proto/user.proto 7 | 8 | service UserService { 9 | // 用户注册 10 | rpc Signup(ReqSignup) returns (RespSignup) {} 11 | // 用户登录 12 | rpc Signin(ReqSignin) returns (RespSignin) {} 13 | // 获取用户信息 14 | rpc UserInfo(ReqUserInfo) returns (RespUserInfo) {} 15 | // 获取用户文件 16 | rpc UserFiles(ReqUserFile) returns (RespUserFile) {} 17 | // 重命名文件 18 | rpc UserFileRename(ReqUserFileRename) returns (RespUserFileRename) {} 19 | } 20 | 21 | message ReqSignup { 22 | string username = 1; 23 | string password = 2; 24 | } 25 | 26 | message RespSignup { 27 | int32 code = 1; 28 | string message = 2; 29 | } 30 | 31 | message ReqSignin { 32 | string username = 1; 33 | string password = 2; 34 | } 35 | 36 | message RespSignin { 37 | int32 code = 1; 38 | string token = 2; 39 | string message = 3; 40 | } 41 | 42 | message ReqUserInfo { 43 | string username = 1; 44 | } 45 | 46 | message RespUserInfo { 47 | int32 code = 1; 48 | string message =2; 49 | string username =3; 50 | string email = 4; 51 | string phone = 5; 52 | string signupAt = 6; 53 | string lastActiveAt = 7; 54 | int32 status = 8; 55 | } 56 | 57 | message ReqUserFile { 58 | string username = 1; 59 | int32 limit = 2; 60 | } 61 | 62 | message RespUserFile { 63 | int32 code = 1; 64 | string message =2; 65 | bytes fileData = 3; 66 | } 67 | 68 | message ReqUserFileRename { 69 | string username = 1; 70 | string filehash = 2; 71 | string newFileName = 3; 72 | } 73 | 74 | message RespUserFileRename { 75 | int32 code = 1; 76 | string message =2; 77 | bytes fileData = 3; 78 | } -------------------------------------------------------------------------------- /service/Microservice/apigw/handler/user.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | proto "FileStore-Server/service/Microservice/account/proto" 5 | "github.com/gin-gonic/gin" 6 | "github.com/micro/go-micro/registry" 7 | "github.com/micro/go-micro/registry/consul" 8 | micro "github.com/micro/go-micro" 9 | "golang.org/x/net/context" 10 | "log" 11 | "net/http" 12 | ) 13 | 14 | var ( 15 | userCli proto.UserService 16 | ) 17 | 18 | func init() { 19 | // 修改consul地址 20 | reg := consul.NewRegistry(func(op *registry.Options){ 21 | op.Addrs = []string{ 22 | "47.95.253.230:8500", 23 | } 24 | }) 25 | 26 | service := micro.NewService(micro.Registry(reg), micro.Name("go.micro.api.user")) 27 | service.Init() 28 | 29 | // 初始化一个rpcClient 30 | userCli = proto.NewUserService("go.micro.service.user",service.Client()) 31 | } 32 | 33 | //SignupHandler: 返回注册页面 34 | func SignupHandler(c *gin.Context) { 35 | c.Redirect(http.StatusFound,"/static/view/signup.html") 36 | } 37 | 38 | //DoSignupHandler: 处理用户注册 39 | func DoSignupHandler(c *gin.Context) { 40 | username := c.Request.FormValue("username") 41 | passwd := c.Request.FormValue("password") 42 | 43 | 44 | resp,err := userCli.Signup(context.TODO(),&proto.ReqSignup{ 45 | Username:username, 46 | Password:passwd, 47 | }) 48 | 49 | if err != nil { 50 | log.Println(err.Error()) 51 | c.Status(http.StatusInternalServerError) 52 | return 53 | } 54 | 55 | c.JSON(http.StatusOK,gin.H{ 56 | "code":resp.Code, 57 | "msg":resp.Message, 58 | }) 59 | } -------------------------------------------------------------------------------- /service/Microservice/apigw/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "FileStore-Server/config" 5 | "FileStore-Server/service/Microservice/apigw/route" 6 | ) 7 | 8 | func main() { 9 | r := route.Router() 10 | r.Run(config.UploadServiceHost) 11 | } -------------------------------------------------------------------------------- /service/Microservice/apigw/route/router.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "FileStore-Server/service/Microservice/apigw/handler" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func Router() *gin.Engine { 9 | router := gin.Default() 10 | 11 | // 处理静态资源 12 | router.Static("/static/","./static") 13 | 14 | router.GET("/user/signup",handler.SignupHandler) 15 | router.POST("/user/signup",handler.DoSignupHandler) 16 | 17 | return router 18 | } -------------------------------------------------------------------------------- /service/Microservice/dbproxy/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "github.com/mitchellh/mapstructure" 8 | 9 | "github.com/micro/go-micro" 10 | 11 | "FileStore-Server/service/Microservice/dbproxy/orm" 12 | dbProto "FileStore-Server/service/Microservice/dbproxy/proto" 13 | ) 14 | 15 | // FileMeta : 文件元信息结构 16 | type FileMeta struct { 17 | FileSha1 string 18 | FileName string 19 | FileSize int64 20 | Location string 21 | UploadAt string 22 | } 23 | 24 | var ( 25 | dbCli dbProto.DBProxyService 26 | ) 27 | 28 | func Init(service micro.Service) { 29 | // 初始化一个dbproxy服务的客户端 30 | dbCli = dbProto.NewDBProxyService("go.micro.service.dbproxy", service.Client()) 31 | } 32 | 33 | func TableFileToFileMeta(tfile orm.TableFile) FileMeta { 34 | return FileMeta{ 35 | FileSha1: tfile.FileHash, 36 | FileName: tfile.FileName.String, 37 | FileSize: tfile.FileSize.Int64, 38 | Location: tfile.FileAddr.String, 39 | } 40 | } 41 | 42 | // execAction : 向dbproxy请求执行action 43 | func execAction(funcName string, paramJson []byte) (*dbProto.RespExec, error) { 44 | return dbCli.ExecuteAction(context.TODO(), &dbProto.ReqExec{ 45 | Action: []*dbProto.SingleAction{ 46 | &dbProto.SingleAction{ 47 | Name: funcName, 48 | Params: paramJson, 49 | }, 50 | }, 51 | }) 52 | } 53 | 54 | // parseBody : 转换rpc返回的结果 55 | func parseBody(resp *dbProto.RespExec) *orm.ExecResult { 56 | if resp == nil || resp.Data == nil { 57 | return nil 58 | } 59 | resList := []orm.ExecResult{} 60 | _ = json.Unmarshal(resp.Data, &resList) 61 | // TODO: 62 | if len(resList) > 0 { 63 | return &resList[0] 64 | } 65 | return nil 66 | } 67 | 68 | func ToTableUser(src interface{}) orm.TableUser { 69 | user := orm.TableUser{} 70 | mapstructure.Decode(src, &user) 71 | return user 72 | } 73 | 74 | func ToTableFile(src interface{}) orm.TableFile { 75 | file := orm.TableFile{} 76 | mapstructure.Decode(src, &file) 77 | return file 78 | } 79 | 80 | func ToTableFiles(src interface{}) []orm.TableFile { 81 | file := []orm.TableFile{} 82 | mapstructure.Decode(src, &file) 83 | return file 84 | } 85 | 86 | func ToTableUserFile(src interface{}) orm.TableUserFile { 87 | ufile := orm.TableUserFile{} 88 | mapstructure.Decode(src, &ufile) 89 | return ufile 90 | } 91 | 92 | func ToTableUserFiles(src interface{}) []orm.TableUserFile { 93 | ufile := []orm.TableUserFile{} 94 | mapstructure.Decode(src, &ufile) 95 | return ufile 96 | } 97 | 98 | func GetFileMeta(filehash string) (*orm.ExecResult, error) { 99 | uInfo, _ := json.Marshal([]interface{}{filehash}) 100 | res, err := execAction("/file/GetFileMeta", uInfo) 101 | return parseBody(res), err 102 | } 103 | 104 | func GetFileMetaList(limitCnt int) (*orm.ExecResult, error) { 105 | uInfo, _ := json.Marshal([]interface{}{limitCnt}) 106 | res, err := execAction("/file/GetFileMetaList", uInfo) 107 | return parseBody(res), err 108 | } 109 | 110 | // OnFileUploadFinished : 新增/更新文件元信息到mysql中 111 | func OnFileUploadFinished(fmeta FileMeta) (*orm.ExecResult, error) { 112 | uInfo, _ := json.Marshal([]interface{}{fmeta.FileSha1, fmeta.FileName, fmeta.FileSize, fmeta.Location}) 113 | res, err := execAction("/file/OnFileUploadFinished", uInfo) 114 | return parseBody(res), err 115 | } 116 | 117 | func UpdateFileLocation(filehash, location string) (*orm.ExecResult, error) { 118 | uInfo, _ := json.Marshal([]interface{}{filehash, location}) 119 | res, err := execAction("/file/UpdateFileLocation", uInfo) 120 | return parseBody(res), err 121 | } 122 | 123 | func UserSignup(username, encPasswd string) (*orm.ExecResult, error) { 124 | uInfo, _ := json.Marshal([]interface{}{username, encPasswd}) 125 | res, err := execAction("/user/UserSignup", uInfo) 126 | return parseBody(res), err 127 | } 128 | 129 | func UserSignin(username, encPasswd string) (*orm.ExecResult, error) { 130 | uInfo, _ := json.Marshal([]interface{}{username, encPasswd}) 131 | res, err := execAction("/user/UserSignin", uInfo) 132 | return parseBody(res), err 133 | } 134 | 135 | func GetUserInfo(username string) (*orm.ExecResult, error) { 136 | uInfo, _ := json.Marshal([]interface{}{username}) 137 | res, err := execAction("/user/GetUserInfo", uInfo) 138 | return parseBody(res), err 139 | } 140 | 141 | func UserExist(username string) (*orm.ExecResult, error) { 142 | uInfo, _ := json.Marshal([]interface{}{username}) 143 | res, err := execAction("/user/UserExist", uInfo) 144 | return parseBody(res), err 145 | } 146 | 147 | func UpdateToken(username, token string) (*orm.ExecResult, error) { 148 | uInfo, _ := json.Marshal([]interface{}{username, token}) 149 | res, err := execAction("/user/UpdateToken", uInfo) 150 | return parseBody(res), err 151 | } 152 | 153 | func QueryUserFileMeta(username, filehash string) (*orm.ExecResult, error) { 154 | uInfo, _ := json.Marshal([]interface{}{username, filehash}) 155 | res, err := execAction("/ufile/QueryUserFileMeta", uInfo) 156 | return parseBody(res), err 157 | } 158 | 159 | func QueryUserFileMetas(username string, limit int) (*orm.ExecResult, error) { 160 | uInfo, _ := json.Marshal([]interface{}{username, limit}) 161 | res, err := execAction("/ufile/QueryUserFileMetas", uInfo) 162 | return parseBody(res), err 163 | } 164 | 165 | // OnUserFileUploadFinished : 新增/更新文件元信息到mysql中 166 | func OnUserFileUploadFinished(username string, fmeta FileMeta) (*orm.ExecResult, error) { 167 | uInfo, _ := json.Marshal([]interface{}{username, fmeta.FileSha1, 168 | fmeta.FileName, fmeta.FileSize}) 169 | res, err := execAction("/ufile/OnUserFileUploadFinished", uInfo) 170 | return parseBody(res), err 171 | } 172 | 173 | func RenameFileName(username, filehash, filename string) (*orm.ExecResult, error) { 174 | uInfo, _ := json.Marshal([]interface{}{username, filehash, filename}) 175 | res, err := execAction("/ufile/RenameFileName", uInfo) 176 | return parseBody(res), err 177 | } 178 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/client/sort.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "time" 4 | 5 | const baseFormat = "2006-01-02 15:04:05" 6 | 7 | type ByUploadTime []FileMeta 8 | 9 | func (a ByUploadTime) Len() int { 10 | return len(a) 11 | } 12 | 13 | func (a ByUploadTime) Swap(i, j int) { 14 | a[i], a[j] = a[j], a[i] 15 | } 16 | 17 | func (a ByUploadTime) Less(i, j int) bool { 18 | iTime, _ := time.Parse(baseFormat, a[i].UploadAt) 19 | jTime, _ := time.Parse(baseFormat, a[j].UploadAt) 20 | return iTime.UnixNano() > jTime.UnixNano() 21 | } 22 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/config/db.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "FileStore-Server/config" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | // MySQLSource : 要连接的数据库源; 10 | // 其中test:test 是用户名密码; 11 | // 127.0.0.1:3306 是ip及端口; 12 | // fileserver 是数据库名; 13 | // charset=utf8 指定了数据以utf8字符编码进行传输 14 | //MySQLSource = "root:test@tcp(127.0.0.1:3306)/fileserver?charset=utf8" 15 | MySQLSource = config.DataSourceName 16 | ) 17 | 18 | func UpdateDBHost(host string) { 19 | MySQLSource = fmt.Sprintf("test:test@tcp(%s)/fileserver?charset=utf8", host) 20 | } 21 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/conn/conn.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | cfg "FileStore-Server/service/Microservice/dbproxy/config" 10 | 11 | _ "github.com/go-sql-driver/mysql" 12 | ) 13 | 14 | var db *sql.DB 15 | 16 | func InitDBConn() { 17 | db, _ = sql.Open("mysql", cfg.MySQLSource) 18 | db.SetMaxOpenConns(1000) 19 | err := db.Ping() 20 | if err != nil { 21 | fmt.Println("Failed to connect to mysql, err:" + err.Error()) 22 | os.Exit(1) 23 | } 24 | } 25 | 26 | // DBConn : 返回数据库连接对象 27 | func DBConn() *sql.DB { 28 | return db 29 | } 30 | 31 | func ParseRows(rows *sql.Rows) []map[string]interface{} { 32 | columns, _ := rows.Columns() 33 | scanArgs := make([]interface{}, len(columns)) 34 | values := make([]interface{}, len(columns)) 35 | for j := range values { 36 | scanArgs[j] = &values[j] 37 | } 38 | 39 | record := make(map[string]interface{}) 40 | records := make([]map[string]interface{}, 0) 41 | for rows.Next() { 42 | //将行数据保存到record字典 43 | err := rows.Scan(scanArgs...) 44 | checkErr(err) 45 | 46 | for i, col := range values { 47 | if col != nil { 48 | record[columns[i]] = col 49 | } 50 | } 51 | records = append(records, record) 52 | } 53 | return records 54 | } 55 | 56 | func checkErr(err error) { 57 | if err != nil { 58 | log.Fatal(err) 59 | panic(err) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "FileStore-Server/common" 5 | "FileStore-Server/service/Microservice/dbproxy/config" 6 | "log" 7 | "time" 8 | 9 | "github.com/micro/cli" 10 | "github.com/micro/go-micro" 11 | _ "github.com/micro/go-plugins/registry/kubernetes" 12 | 13 | dbConn "FileStore-Server/service/Microservice/dbproxy/conn" 14 | dbProxy "FileStore-Server/service/Microservice/dbproxy/proto" 15 | dbRpc "FileStore-Server/service/Microservice/dbproxy/rpc" 16 | ) 17 | 18 | func startRpcService() { 19 | service := micro.NewService( 20 | micro.Name("go.micro.service.dbproxy"), // 在注册中心中的服务名称 21 | micro.RegisterTTL(time.Second*10), // 声明超时时间, 避免consul不主动删掉已失去心跳的服务节点 22 | micro.RegisterInterval(time.Second*5), 23 | micro.Flags(common.CustomFlags...), 24 | ) 25 | 26 | service.Init( 27 | micro.Action(func(c *cli.Context) { 28 | // 检查是否指定dbhost 29 | dbhost := c.String("dbhost") 30 | if len(dbhost) > 0 { 31 | log.Println("custom db address: " + dbhost) 32 | config.UpdateDBHost(dbhost) 33 | } 34 | }), 35 | ) 36 | 37 | // 初始化db connection 38 | dbConn.InitDBConn() 39 | 40 | dbProxy.RegisterDBProxyServiceHandler(service.Server(), new(dbRpc.DBProxy)) 41 | if err := service.Run(); err != nil { 42 | log.Println(err) 43 | } 44 | } 45 | 46 | func main() { 47 | startRpcService() 48 | } 49 | 50 | // res, err := mapper.FuncCall("/user/UserExist", []interface{}{"haha"}...) 51 | // log.Printf("error: %+v\n", err) 52 | // log.Printf("result: %+v\n", res[0].Interface()) 53 | 54 | // res, err = mapper.FuncCall("/user/UserExist", []interface{}{"admin"}...) 55 | // log.Printf("error: %+v\n", err) 56 | // log.Printf("result: %+v\n", res[0].Interface()) 57 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/mapper/mapper.go: -------------------------------------------------------------------------------- 1 | package mapper 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | 7 | "filestore-server/service/dbproxy/orm" 8 | ) 9 | 10 | var funcs = map[string]interface{}{ 11 | "/file/OnFileUploadFinished": orm.OnFileUploadFinished, 12 | "/file/GetFileMeta": orm.GetFileMeta, 13 | "/file/GetFileMetaList": orm.GetFileMetaList, 14 | "/file/UpdateFileLocation": orm.UpdateFileLocation, 15 | 16 | "/user/UserSignup": orm.UserSignup, 17 | "/user/UserSignin": orm.UserSignin, 18 | "/user/UpdateToken": orm.UpdateToken, 19 | "/user/GetUserInfo": orm.GetUserInfo, 20 | "/user/UserExist": orm.UserExist, 21 | 22 | "/ufile/OnUserFileUploadFinished": orm.OnUserFileUploadFinished, 23 | "/ufile/QueryUserFileMetas": orm.QueryUserFileMetas, 24 | "/ufile/DeleteUserFile": orm.DeleteUserFile, 25 | "/ufile/RenameFileName": orm.RenameFileName, 26 | "/ufile/QueryUserFileMeta": orm.QueryUserFileMeta, 27 | } 28 | 29 | func FuncCall(name string, params ...interface{}) (result []reflect.Value, err error) { 30 | if _, ok := funcs[name]; !ok { 31 | err = errors.New("函数名不存在.") 32 | return 33 | } 34 | 35 | // 通过反射可以动态调用对象的导出方法 36 | f := reflect.ValueOf(funcs[name]) 37 | if len(params) != f.Type().NumIn() { 38 | err = errors.New("传入参数数量与被调用方法要求的数量不一致.") 39 | return 40 | } 41 | 42 | // 构造一个 Value的slice, 用作Call()方法的传入参数 43 | in := make([]reflect.Value, len(params)) 44 | for k, param := range params { 45 | in[k] = reflect.ValueOf(param) 46 | } 47 | 48 | // 执行方法f, 并将方法结果赋值给result 49 | result = f.Call(in) 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/orm/define.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import "database/sql" 4 | 5 | // TableFile : 文件表结构体 6 | type TableFile struct { 7 | FileHash string 8 | FileName sql.NullString 9 | FileSize sql.NullInt64 10 | FileAddr sql.NullString 11 | } 12 | 13 | // TableUser : 用户表model 14 | type TableUser struct { 15 | Username string 16 | Email string 17 | Phone string 18 | SignupAt string 19 | LastActiveAt string 20 | Status int 21 | } 22 | 23 | // TableUserFile : 用户文件表结构体 24 | type TableUserFile struct { 25 | UserName string 26 | FileHash string 27 | FileName string 28 | FileSize int64 29 | UploadAt string 30 | LastUpdated string 31 | } 32 | 33 | // ExecResult: sql函数执行的结果 34 | type ExecResult struct { 35 | Suc bool `json:"suc"` 36 | Code int `json:"code"` 37 | Msg string `json:"msg"` 38 | Data interface{} `json:"data"` 39 | } 40 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/orm/file.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | 7 | mydb "FileStore-Server/service/Microservice/dbproxy/conn" 8 | ) 9 | 10 | // OnFileUploadFinished : 文件上传完成,保存meta 11 | func OnFileUploadFinished(filehash string, filename string, 12 | filesize int64, fileaddr string) (res ExecResult) { 13 | stmt, err := mydb.DBConn().Prepare( 14 | "insert ignore into tbl_file (`file_sha1`,`file_name`,`file_size`," + 15 | "`file_addr`,`status`) values (?,?,?,?,1)") 16 | if err != nil { 17 | log.Println("Failed to prepare statement, err:" + err.Error()) 18 | res.Suc = false 19 | return 20 | } 21 | defer stmt.Close() 22 | 23 | ret, err := stmt.Exec(filehash, filename, filesize, fileaddr) 24 | if err != nil { 25 | log.Println(err.Error()) 26 | res.Suc = false 27 | return 28 | } 29 | if rf, err := ret.RowsAffected(); nil == err { 30 | if rf <= 0 { 31 | log.Printf("File with hash:%s has been uploaded before", filehash) 32 | } 33 | res.Suc = true 34 | return 35 | } 36 | res.Suc = false 37 | return 38 | } 39 | 40 | // GetFileMeta : 从mysql获取文件元信息 41 | func GetFileMeta(filehash string) (res ExecResult) { 42 | stmt, err := mydb.DBConn().Prepare( 43 | "select file_sha1,file_addr,file_name,file_size from tbl_file " + 44 | "where file_sha1=? and status=1 limit 1") 45 | if err != nil { 46 | log.Println(err.Error()) 47 | res.Suc = false 48 | res.Msg = err.Error() 49 | return 50 | } 51 | defer stmt.Close() 52 | 53 | tfile := TableFile{} 54 | err = stmt.QueryRow(filehash).Scan( 55 | &tfile.FileHash, &tfile.FileAddr, &tfile.FileName, &tfile.FileSize) 56 | if err != nil { 57 | if err == sql.ErrNoRows { 58 | // 查不到对应记录, 返回参数及错误均为nil 59 | res.Suc = true 60 | res.Data = nil 61 | return 62 | } else { 63 | log.Println(err.Error()) 64 | res.Suc = false 65 | res.Msg = err.Error() 66 | return 67 | } 68 | } 69 | res.Suc = true 70 | res.Data = tfile 71 | return 72 | } 73 | 74 | // GetFileMetaList : 从mysql批量获取文件元信息 75 | func GetFileMetaList(limit int64) (res ExecResult) { 76 | stmt, err := mydb.DBConn().Prepare( 77 | "select file_sha1,file_addr,file_name,file_size from tbl_file " + 78 | "where status=1 limit ?") 79 | if err != nil { 80 | log.Println(err.Error()) 81 | res.Suc = false 82 | res.Msg = err.Error() 83 | return 84 | } 85 | defer stmt.Close() 86 | 87 | rows, err := stmt.Query(limit) 88 | if err != nil { 89 | log.Println(err.Error()) 90 | res.Suc = false 91 | res.Msg = err.Error() 92 | return 93 | } 94 | 95 | cloumns, _ := rows.Columns() 96 | values := make([]sql.RawBytes, len(cloumns)) 97 | var tfiles []TableFile 98 | for i := 0; i < len(values) && rows.Next(); i++ { 99 | tfile := TableFile{} 100 | err = rows.Scan(&tfile.FileHash, &tfile.FileAddr, 101 | &tfile.FileName, &tfile.FileSize) 102 | if err != nil { 103 | log.Println(err.Error()) 104 | break 105 | } 106 | tfiles = append(tfiles, tfile) 107 | } 108 | // log.Println(len(tfiles)) 109 | res.Suc = true 110 | res.Data = tfiles 111 | return 112 | } 113 | 114 | // UpdateFileLocation : 更新文件的存储地址(如文件被转移了) 115 | func UpdateFileLocation(filehash string, fileaddr string) (res ExecResult) { 116 | stmt, err := mydb.DBConn().Prepare( 117 | "update tbl_file set`file_addr`=? where `file_sha1`=? limit 1") 118 | if err != nil { 119 | log.Println("预编译sql失败, err:" + err.Error()) 120 | res.Suc = false 121 | res.Msg = err.Error() 122 | return 123 | } 124 | defer stmt.Close() 125 | 126 | ret, err := stmt.Exec(fileaddr, filehash) 127 | if err != nil { 128 | log.Println(err.Error()) 129 | res.Suc = false 130 | res.Msg = err.Error() 131 | return 132 | } 133 | 134 | if rf, err := ret.RowsAffected(); nil == err { 135 | if rf <= 0 { 136 | log.Printf("更新文件location失败, filehash:%s", filehash) 137 | res.Suc = false 138 | res.Msg = "无记录更新" 139 | return 140 | } 141 | res.Suc = true 142 | return 143 | } else { 144 | res.Suc = false 145 | res.Msg = err.Error() 146 | return 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/orm/user.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "log" 5 | 6 | mydb "FileStore-Server/service/Microservice/dbproxy/conn" 7 | ) 8 | 9 | // UserSignup : 通过用户名及密码完成user表的注册操作 10 | func UserSignup(username string, passwd string) (res ExecResult) { 11 | stmt, err := mydb.DBConn().Prepare( 12 | "insert ignore into tbl_user (`user_name`,`user_pwd`) values (?,?)") 13 | if err != nil { 14 | log.Println("Failed to insert, err:" + err.Error()) 15 | res.Suc = false 16 | res.Msg = err.Error() 17 | return 18 | } 19 | defer stmt.Close() 20 | 21 | ret, err := stmt.Exec(username, passwd) 22 | if err != nil { 23 | log.Println("Failed to insert, err:" + err.Error()) 24 | res.Suc = false 25 | res.Msg = err.Error() 26 | return 27 | } 28 | if rowsAffected, err := ret.RowsAffected(); nil == err && rowsAffected > 0 { 29 | res.Suc = true 30 | return 31 | } 32 | 33 | res.Suc = false 34 | res.Msg = "无记录更新" 35 | return 36 | } 37 | 38 | // UserSignin : 判断密码是否一致 39 | func UserSignin(username string, encpwd string) (res ExecResult) { 40 | stmt, err := mydb.DBConn().Prepare("select * from tbl_user where user_name=? limit 1") 41 | if err != nil { 42 | log.Println(err.Error()) 43 | res.Suc = false 44 | res.Msg = err.Error() 45 | return 46 | } 47 | defer stmt.Close() 48 | 49 | rows, err := stmt.Query(username) 50 | if err != nil { 51 | log.Println(err.Error()) 52 | res.Suc = false 53 | res.Msg = err.Error() 54 | return 55 | } else if rows == nil { 56 | log.Println("username not found: " + username) 57 | res.Suc = false 58 | res.Msg = "用户名未注册" 59 | return 60 | } 61 | 62 | pRows := mydb.ParseRows(rows) 63 | if len(pRows) > 0 && string(pRows[0]["user_pwd"].([]byte)) == encpwd { 64 | res.Suc = true 65 | res.Data = true 66 | return 67 | } 68 | res.Suc = false 69 | res.Msg = "用户名/密码不匹配" 70 | return 71 | } 72 | 73 | // UpdateToken : 刷新用户登录的token 74 | func UpdateToken(username string, token string) (res ExecResult) { 75 | stmt, err := mydb.DBConn().Prepare( 76 | "replace into tbl_user_token (`user_name`,`user_token`) values (?,?)") 77 | if err != nil { 78 | log.Println(err.Error()) 79 | res.Suc = false 80 | res.Msg = err.Error() 81 | return 82 | } 83 | defer stmt.Close() 84 | 85 | _, err = stmt.Exec(username, token) 86 | if err != nil { 87 | log.Println(err.Error()) 88 | res.Suc = false 89 | res.Msg = err.Error() 90 | return 91 | } 92 | res.Suc = true 93 | return 94 | } 95 | 96 | // GetUserInfo : 查询用户信息 97 | func GetUserInfo(username string) (res ExecResult) { 98 | user := TableUser{} 99 | 100 | stmt, err := mydb.DBConn().Prepare( 101 | "select user_name,signup_at from tbl_user where user_name=? limit 1") 102 | if err != nil { 103 | log.Println(err.Error()) 104 | // error不为nil, 返回时user应当置为nil 105 | //return user, err 106 | res.Suc = false 107 | res.Msg = err.Error() 108 | return 109 | } 110 | defer stmt.Close() 111 | 112 | // 执行查询的操作 113 | err = stmt.QueryRow(username).Scan(&user.Username, &user.SignupAt) 114 | if err != nil { 115 | res.Suc = false 116 | res.Msg = err.Error() 117 | return 118 | } 119 | res.Suc = true 120 | res.Data = user 121 | return 122 | } 123 | 124 | // UserExist : 查询用户是否存在 125 | func UserExist(username string) (res ExecResult) { 126 | stmt, err := mydb.DBConn().Prepare( 127 | "select 1 from tbl_user where user_name=? limit 1") 128 | if err != nil { 129 | log.Println(err.Error()) 130 | res.Suc = false 131 | res.Msg = err.Error() 132 | return 133 | } 134 | defer stmt.Close() 135 | 136 | rows, err := stmt.Query(username) 137 | if err != nil { 138 | res.Suc = false 139 | res.Msg = err.Error() 140 | return 141 | } 142 | res.Suc = true 143 | res.Data = map[string]bool{ 144 | "exists": rows.Next(), 145 | } 146 | return 147 | } 148 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/orm/userfile.go: -------------------------------------------------------------------------------- 1 | package orm 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | mydb "FileStore-Server/service/Microservice/dbproxy/conn" 8 | ) 9 | 10 | // OnUserFileUploadFinished : 更新用户文件表 11 | func OnUserFileUploadFinished(username, filehash, filename string, filesize int64) (res ExecResult) { 12 | stmt, err := mydb.DBConn().Prepare( 13 | "insert ignore into tbl_user_file (`user_name`,`file_sha1`,`file_name`," + 14 | "`file_size`,`upload_at`) values (?,?,?,?,?)") 15 | if err != nil { 16 | log.Println(err.Error()) 17 | res.Suc = false 18 | res.Msg = err.Error() 19 | return 20 | } 21 | defer stmt.Close() 22 | 23 | _, err = stmt.Exec(username, filehash, filename, filesize, time.Now()) 24 | if err != nil { 25 | log.Println(err.Error()) 26 | res.Suc = false 27 | res.Msg = err.Error() 28 | return 29 | } 30 | res.Suc = true 31 | return 32 | } 33 | 34 | // QueryUserFileMetas : 批量获取用户文件信息 35 | func QueryUserFileMetas(username string, limit int64) (res ExecResult) { 36 | stmt, err := mydb.DBConn().Prepare( 37 | "select file_sha1,file_name,file_size,upload_at," + 38 | "last_update from tbl_user_file where user_name=? limit ?") 39 | if err != nil { 40 | log.Println(err.Error()) 41 | res.Suc = false 42 | res.Msg = err.Error() 43 | return 44 | } 45 | defer stmt.Close() 46 | 47 | rows, err := stmt.Query(username, limit) 48 | if err != nil { 49 | log.Println(err.Error()) 50 | res.Suc = false 51 | res.Msg = err.Error() 52 | return 53 | } 54 | 55 | var userFiles []TableUserFile 56 | for rows.Next() { 57 | ufile := TableUserFile{} 58 | err = rows.Scan(&ufile.FileHash, &ufile.FileName, &ufile.FileSize, 59 | &ufile.UploadAt, &ufile.LastUpdated) 60 | if err != nil { 61 | log.Println(err.Error()) 62 | break 63 | } 64 | userFiles = append(userFiles, ufile) 65 | } 66 | res.Suc = true 67 | res.Data = userFiles 68 | return 69 | } 70 | 71 | // DeleteUserFile : 删除文件(标记删除) 72 | func DeleteUserFile(username, filehash string) (res ExecResult) { 73 | stmt, err := mydb.DBConn().Prepare( 74 | "update tbl_user_file set status=2 where user_name=? and file_sha1=? limit 1") 75 | if err != nil { 76 | log.Println(err.Error()) 77 | res.Suc = false 78 | res.Msg = err.Error() 79 | return 80 | } 81 | defer stmt.Close() 82 | 83 | _, err = stmt.Exec(username, filehash) 84 | if err != nil { 85 | log.Println(err.Error()) 86 | res.Suc = false 87 | res.Msg = err.Error() 88 | return 89 | } 90 | res.Suc = true 91 | return 92 | } 93 | 94 | // RenameFileName : 文件重命名 95 | func RenameFileName(username, filehash, filename string) (res ExecResult) { 96 | stmt, err := mydb.DBConn().Prepare( 97 | "update tbl_user_file set file_name=? where user_name=? and file_sha1=? limit 1") 98 | if err != nil { 99 | log.Println(err.Error()) 100 | res.Suc = false 101 | res.Msg = err.Error() 102 | return 103 | } 104 | defer stmt.Close() 105 | 106 | _, err = stmt.Exec(filename, username, filehash) 107 | if err != nil { 108 | log.Println(err.Error()) 109 | res.Suc = false 110 | res.Msg = err.Error() 111 | return 112 | } 113 | res.Suc = true 114 | return 115 | } 116 | 117 | // QueryUserFileMeta : 获取用户单个文件信息 118 | func QueryUserFileMeta(username string, filehash string) (res ExecResult) { 119 | stmt, err := mydb.DBConn().Prepare( 120 | "select file_sha1,file_name,file_size,upload_at," + 121 | "last_update from tbl_user_file where user_name=? and file_sha1=? limit 1") 122 | if err != nil { 123 | res.Suc = false 124 | res.Msg = err.Error() 125 | return 126 | } 127 | defer stmt.Close() 128 | 129 | rows, err := stmt.Query(username, filehash) 130 | if err != nil { 131 | res.Suc = false 132 | res.Msg = err.Error() 133 | return 134 | } 135 | 136 | ufile := TableUserFile{} 137 | if rows.Next() { 138 | err = rows.Scan(&ufile.FileHash, &ufile.FileName, &ufile.FileSize, 139 | &ufile.UploadAt, &ufile.LastUpdated) 140 | if err != nil { 141 | log.Println(err.Error()) 142 | res.Suc = false 143 | res.Msg = err.Error() 144 | return 145 | } 146 | } 147 | 148 | res.Suc = true 149 | res.Data = ufile 150 | return 151 | } 152 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/proto/proxy.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: proxy.proto 3 | 4 | package go_micro_service_dbproxy 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | client "github.com/micro/go-micro/client" 15 | server "github.com/micro/go-micro/server" 16 | ) 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var _ = proto.Marshal 20 | var _ = fmt.Errorf 21 | var _ = math.Inf 22 | 23 | // This is a compile-time assertion to ensure that this generated file 24 | // is compatible with the proto package it is being compiled against. 25 | // A compilation error at this line likely means your copy of the 26 | // proto package needs to be updated. 27 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 28 | 29 | // Reference imports to suppress errors if they are not otherwise used. 30 | var _ context.Context 31 | var _ client.Option 32 | var _ server.Option 33 | 34 | // Client API for DBProxyService service 35 | 36 | type DBProxyService interface { 37 | // 请求执行sql动作 38 | ExecuteAction(ctx context.Context, in *ReqExec, opts ...client.CallOption) (*RespExec, error) 39 | } 40 | 41 | type dBProxyService struct { 42 | c client.Client 43 | name string 44 | } 45 | 46 | func NewDBProxyService(name string, c client.Client) DBProxyService { 47 | if c == nil { 48 | c = client.NewClient() 49 | } 50 | if len(name) == 0 { 51 | name = "go.micro.service.dbproxy" 52 | } 53 | return &dBProxyService{ 54 | c: c, 55 | name: name, 56 | } 57 | } 58 | 59 | func (c *dBProxyService) ExecuteAction(ctx context.Context, in *ReqExec, opts ...client.CallOption) (*RespExec, error) { 60 | req := c.c.NewRequest(c.name, "DBProxyService.ExecuteAction", in) 61 | out := new(RespExec) 62 | err := c.c.Call(ctx, req, out, opts...) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return out, nil 67 | } 68 | 69 | // Server API for DBProxyService service 70 | 71 | type DBProxyServiceHandler interface { 72 | // 请求执行sql动作 73 | ExecuteAction(context.Context, *ReqExec, *RespExec) error 74 | } 75 | 76 | func RegisterDBProxyServiceHandler(s server.Server, hdlr DBProxyServiceHandler, opts ...server.HandlerOption) error { 77 | type dBProxyService interface { 78 | ExecuteAction(ctx context.Context, in *ReqExec, out *RespExec) error 79 | } 80 | type DBProxyService struct { 81 | dBProxyService 82 | } 83 | h := &dBProxyServiceHandler{hdlr} 84 | return s.Handle(s.NewHandler(&DBProxyService{h}, opts...)) 85 | } 86 | 87 | type dBProxyServiceHandler struct { 88 | DBProxyServiceHandler 89 | } 90 | 91 | func (h *dBProxyServiceHandler) ExecuteAction(ctx context.Context, in *ReqExec, out *RespExec) error { 92 | return h.DBProxyServiceHandler.ExecuteAction(ctx, in, out) 93 | } 94 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/proto/proxy.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: proxy.proto 3 | 4 | package go_micro_service_dbproxy 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 22 | 23 | type SingleAction struct { 24 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 25 | Params []byte `protobuf:"bytes,2,opt,name=params,proto3" json:"params,omitempty"` 26 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 27 | XXX_unrecognized []byte `json:"-"` 28 | XXX_sizecache int32 `json:"-"` 29 | } 30 | 31 | func (m *SingleAction) Reset() { *m = SingleAction{} } 32 | func (m *SingleAction) String() string { return proto.CompactTextString(m) } 33 | func (*SingleAction) ProtoMessage() {} 34 | func (*SingleAction) Descriptor() ([]byte, []int) { 35 | return fileDescriptor_700b50b08ed8dbaf, []int{0} 36 | } 37 | 38 | func (m *SingleAction) XXX_Unmarshal(b []byte) error { 39 | return xxx_messageInfo_SingleAction.Unmarshal(m, b) 40 | } 41 | func (m *SingleAction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 42 | return xxx_messageInfo_SingleAction.Marshal(b, m, deterministic) 43 | } 44 | func (m *SingleAction) XXX_Merge(src proto.Message) { 45 | xxx_messageInfo_SingleAction.Merge(m, src) 46 | } 47 | func (m *SingleAction) XXX_Size() int { 48 | return xxx_messageInfo_SingleAction.Size(m) 49 | } 50 | func (m *SingleAction) XXX_DiscardUnknown() { 51 | xxx_messageInfo_SingleAction.DiscardUnknown(m) 52 | } 53 | 54 | var xxx_messageInfo_SingleAction proto.InternalMessageInfo 55 | 56 | func (m *SingleAction) GetName() string { 57 | if m != nil { 58 | return m.Name 59 | } 60 | return "" 61 | } 62 | 63 | func (m *SingleAction) GetParams() []byte { 64 | if m != nil { 65 | return m.Params 66 | } 67 | return nil 68 | } 69 | 70 | type ReqExec struct { 71 | Sequence bool `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` 72 | Transaction bool `protobuf:"varint,2,opt,name=transaction,proto3" json:"transaction,omitempty"` 73 | ResultType int32 `protobuf:"varint,3,opt,name=resultType,proto3" json:"resultType,omitempty"` 74 | Action []*SingleAction `protobuf:"bytes,4,rep,name=action,proto3" json:"action,omitempty"` 75 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 76 | XXX_unrecognized []byte `json:"-"` 77 | XXX_sizecache int32 `json:"-"` 78 | } 79 | 80 | func (m *ReqExec) Reset() { *m = ReqExec{} } 81 | func (m *ReqExec) String() string { return proto.CompactTextString(m) } 82 | func (*ReqExec) ProtoMessage() {} 83 | func (*ReqExec) Descriptor() ([]byte, []int) { 84 | return fileDescriptor_700b50b08ed8dbaf, []int{1} 85 | } 86 | 87 | func (m *ReqExec) XXX_Unmarshal(b []byte) error { 88 | return xxx_messageInfo_ReqExec.Unmarshal(m, b) 89 | } 90 | func (m *ReqExec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 91 | return xxx_messageInfo_ReqExec.Marshal(b, m, deterministic) 92 | } 93 | func (m *ReqExec) XXX_Merge(src proto.Message) { 94 | xxx_messageInfo_ReqExec.Merge(m, src) 95 | } 96 | func (m *ReqExec) XXX_Size() int { 97 | return xxx_messageInfo_ReqExec.Size(m) 98 | } 99 | func (m *ReqExec) XXX_DiscardUnknown() { 100 | xxx_messageInfo_ReqExec.DiscardUnknown(m) 101 | } 102 | 103 | var xxx_messageInfo_ReqExec proto.InternalMessageInfo 104 | 105 | func (m *ReqExec) GetSequence() bool { 106 | if m != nil { 107 | return m.Sequence 108 | } 109 | return false 110 | } 111 | 112 | func (m *ReqExec) GetTransaction() bool { 113 | if m != nil { 114 | return m.Transaction 115 | } 116 | return false 117 | } 118 | 119 | func (m *ReqExec) GetResultType() int32 { 120 | if m != nil { 121 | return m.ResultType 122 | } 123 | return 0 124 | } 125 | 126 | func (m *ReqExec) GetAction() []*SingleAction { 127 | if m != nil { 128 | return m.Action 129 | } 130 | return nil 131 | } 132 | 133 | type RespExec struct { 134 | Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 135 | Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` 136 | Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` 137 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 138 | XXX_unrecognized []byte `json:"-"` 139 | XXX_sizecache int32 `json:"-"` 140 | } 141 | 142 | func (m *RespExec) Reset() { *m = RespExec{} } 143 | func (m *RespExec) String() string { return proto.CompactTextString(m) } 144 | func (*RespExec) ProtoMessage() {} 145 | func (*RespExec) Descriptor() ([]byte, []int) { 146 | return fileDescriptor_700b50b08ed8dbaf, []int{2} 147 | } 148 | 149 | func (m *RespExec) XXX_Unmarshal(b []byte) error { 150 | return xxx_messageInfo_RespExec.Unmarshal(m, b) 151 | } 152 | func (m *RespExec) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 153 | return xxx_messageInfo_RespExec.Marshal(b, m, deterministic) 154 | } 155 | func (m *RespExec) XXX_Merge(src proto.Message) { 156 | xxx_messageInfo_RespExec.Merge(m, src) 157 | } 158 | func (m *RespExec) XXX_Size() int { 159 | return xxx_messageInfo_RespExec.Size(m) 160 | } 161 | func (m *RespExec) XXX_DiscardUnknown() { 162 | xxx_messageInfo_RespExec.DiscardUnknown(m) 163 | } 164 | 165 | var xxx_messageInfo_RespExec proto.InternalMessageInfo 166 | 167 | func (m *RespExec) GetCode() int32 { 168 | if m != nil { 169 | return m.Code 170 | } 171 | return 0 172 | } 173 | 174 | func (m *RespExec) GetMsg() string { 175 | if m != nil { 176 | return m.Msg 177 | } 178 | return "" 179 | } 180 | 181 | func (m *RespExec) GetData() []byte { 182 | if m != nil { 183 | return m.Data 184 | } 185 | return nil 186 | } 187 | 188 | func init() { 189 | proto.RegisterType((*SingleAction)(nil), "go.micro.service.dbproxy.SingleAction") 190 | proto.RegisterType((*ReqExec)(nil), "go.micro.service.dbproxy.ReqExec") 191 | proto.RegisterType((*RespExec)(nil), "go.micro.service.dbproxy.RespExec") 192 | } 193 | 194 | func init() { proto.RegisterFile("proxy.proto", fileDescriptor_700b50b08ed8dbaf) } 195 | 196 | var fileDescriptor_700b50b08ed8dbaf = []byte{ 197 | // 281 bytes of a gzipped FileDescriptorProto 198 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0xb1, 0x4e, 0xc3, 0x30, 199 | 0x10, 0x86, 0x09, 0x4d, 0x43, 0x7a, 0x09, 0x08, 0x79, 0x40, 0x51, 0x07, 0x14, 0x32, 0xa0, 0x4c, 200 | 0x1e, 0xca, 0xc6, 0x80, 0x04, 0x2a, 0x3b, 0xba, 0x32, 0xb0, 0xba, 0xce, 0x29, 0x0a, 0x6a, 0xe2, 201 | 0xd4, 0x76, 0x50, 0xfb, 0x44, 0xbc, 0x26, 0x8a, 0x13, 0x50, 0x96, 0xb2, 0xfd, 0x77, 0xbe, 0xbb, 202 | 0xff, 0xbb, 0x33, 0x44, 0xad, 0x56, 0x87, 0x23, 0x6f, 0xb5, 0xb2, 0x8a, 0x25, 0xa5, 0xe2, 0x75, 203 | 0x25, 0xb5, 0xe2, 0x86, 0xf4, 0x57, 0x25, 0x89, 0x17, 0x5b, 0xf7, 0x9e, 0x3d, 0x42, 0xbc, 0xa9, 204 | 0x9a, 0x72, 0x47, 0xcf, 0xd2, 0x56, 0xaa, 0x61, 0x0c, 0xfc, 0x46, 0xd4, 0x94, 0x78, 0xa9, 0x97, 205 | 0x2f, 0xd0, 0x69, 0x76, 0x03, 0x41, 0x2b, 0xb4, 0xa8, 0x4d, 0x72, 0x9e, 0x7a, 0x79, 0x8c, 0x63, 206 | 0x94, 0x7d, 0x7b, 0x70, 0x81, 0xb4, 0x7f, 0x3d, 0x90, 0x64, 0x4b, 0x08, 0x0d, 0xed, 0x3b, 0x6a, 207 | 0xe4, 0xd0, 0x1b, 0xe2, 0x5f, 0xcc, 0x52, 0x88, 0xac, 0x16, 0x8d, 0x11, 0xce, 0xc2, 0x0d, 0x09, 208 | 0x71, 0x9a, 0x62, 0xb7, 0x00, 0x9a, 0x4c, 0xb7, 0xb3, 0xef, 0xc7, 0x96, 0x92, 0x59, 0xea, 0xe5, 209 | 0x73, 0x9c, 0x64, 0xd8, 0x13, 0x04, 0x63, 0xb3, 0x9f, 0xce, 0xf2, 0x68, 0x75, 0xcf, 0x4f, 0x2d, 210 | 0xc4, 0xa7, 0xdb, 0xe0, 0xd8, 0x95, 0xad, 0x21, 0x44, 0x32, 0xad, 0x23, 0x65, 0xe0, 0x4b, 0x55, 211 | 0x0c, 0x94, 0x73, 0x74, 0x9a, 0x5d, 0xc3, 0xac, 0x36, 0xa5, 0x23, 0x5b, 0x60, 0x2f, 0xfb, 0xaa, 212 | 0x42, 0x58, 0xe1, 0x58, 0x62, 0x74, 0x7a, 0xf5, 0x09, 0x57, 0xeb, 0x97, 0xb7, 0xde, 0x65, 0x33, 213 | 0x98, 0xb2, 0x0f, 0xb8, 0xec, 0x67, 0x76, 0xf6, 0xf7, 0x7c, 0x77, 0xa7, 0xc1, 0xc6, 0x4b, 0x2d, 214 | 0xb3, 0xff, 0x4a, 0x06, 0xc6, 0xec, 0x6c, 0x1b, 0xb8, 0x8f, 0x7b, 0xf8, 0x09, 0x00, 0x00, 0xff, 215 | 0xff, 0x77, 0x62, 0x76, 0xe7, 0xc7, 0x01, 0x00, 0x00, 216 | } 217 | -------------------------------------------------------------------------------- /service/Microservice/dbproxy/proto/proxy.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package go.micro.service.dbproxy; 4 | 5 | service DBProxyService { 6 | // 请求执行sql动作 7 | rpc ExecuteAction(ReqExec) returns (RespExec) {} 8 | } 9 | 10 | message SingleAction { 11 | string name = 1; 12 | bytes params = 2; // 请求参数列表, json-encoded 13 | } 14 | 15 | message ReqExec { 16 | bool sequence = 1; // 是否严格按照给定顺序串行执行 17 | bool transaction = 2; // 所有action是否在一个事务里执行 18 | int32 resultType = 3; // 0表示每个sql函数的结果都返回; 1表示只返回最后一个执行sql的结果(要求sequence执行) 19 | repeated SingleAction action = 4; // 一个或多个sql函数 20 | } 21 | 22 | message RespExec { 23 | int32 code = 1; 24 | string msg = 2; 25 | bytes data = 3; // 执行的结果 26 | } -------------------------------------------------------------------------------- /service/Microservice/dbproxy/rpc/proxy.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | 8 | "FileStore-Server/service/Microservice/dbproxy/mapper" 9 | "FileStore-Server/service/Microservice/dbproxy/orm" 10 | dbProxy "FileStore-Server/service/Microservice/dbproxy/proto" 11 | ) 12 | 13 | // DBProxy : DBProxy结构体 14 | type DBProxy struct{} 15 | 16 | // ExecuteAction : 请求执行sql函数 17 | func (db *DBProxy) ExecuteAction(ctx context.Context, req *dbProxy.ReqExec, res *dbProxy.RespExec) error { 18 | resList := make([]orm.ExecResult, len(req.Action)) 19 | 20 | // TODO: 检查 req.Sequence req.Transaction两个参数,执行不同的流程 21 | for idx, singleAction := range req.Action { 22 | params := []interface{}{} 23 | dec := json.NewDecoder(bytes.NewReader(singleAction.Params)) 24 | dec.UseNumber() 25 | // 避免int/int32/int64等自动转换为float64 26 | //if err := json.Unmarshal(singleAction.Params, ¶ms); err != nil { 27 | if err := dec.Decode(¶ms); err != nil { 28 | resList[idx] = orm.ExecResult{ 29 | Suc: false, 30 | Msg: "请求参数有误", 31 | } 32 | continue 33 | } 34 | 35 | for k, v := range params { 36 | if _, ok := v.(json.Number); ok { 37 | params[k], _ = v.(json.Number).Int64() 38 | } 39 | } 40 | 41 | // 默认串行执行sql函数 42 | execRes, err := mapper.FuncCall(singleAction.Name, params...) 43 | if err != nil { 44 | resList[idx] = orm.ExecResult{ 45 | Suc: false, 46 | Msg: "函数调用有误", 47 | } 48 | continue 49 | } 50 | resList[idx] = execRes[0].Interface().(orm.ExecResult) 51 | } 52 | 53 | // TODO: 处理异常 54 | res.Data, _ = json.Marshal(resList) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /service/Microservice/download/api/download.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gin-gonic/gin" 10 | 11 | "FileStore-Server/common" 12 | cfg "FileStore-Server/config" 13 | dbcli "FileStore-Server/service/Microservice/dbproxy/client" 14 | "FileStore-Server/store/oss" 15 | // dlcfg "filestore-server/service/download/config" 16 | ) 17 | 18 | // DownloadURLHandler: 生成文件的下载地址 19 | func DownloadURLHandler(c *gin.Context) { 20 | filehash := c.Request.FormValue("filehash") 21 | // 从文件表查找记录 22 | dbResp, err := dbcli.GetFileMeta(filehash) 23 | if err != nil { 24 | c.JSON( 25 | http.StatusOK, 26 | gin.H{ 27 | "code": common.StatusServerError, 28 | "msg": "server error", 29 | }) 30 | return 31 | } 32 | 33 | tblFile := dbcli.ToTableFile(dbResp.Data) 34 | 35 | // TODO: 判断文件存在OSS,还是在本地 36 | if strings.HasPrefix(tblFile.FileAddr.String, cfg.TempLocalRootDir) { 37 | username := c.Request.FormValue("username") 38 | token := c.Request.FormValue("token") 39 | tmpURL := fmt.Sprintf("http://%s/file/download?filehash=%s&username=%s&token=%s", 40 | c.Request.Host, filehash, username, token) 41 | c.Data(http.StatusOK, "application/octet-stream", []byte(tmpURL)) 42 | } else if strings.HasPrefix(tblFile.FileAddr.String, cfg.OSSRootDir) { 43 | // oss下载url 44 | signedURL := oss.DownloadURL(tblFile.FileAddr.String) 45 | log.Println(tblFile.FileAddr.String) 46 | c.Data(http.StatusOK, "application/octet-stream", []byte(signedURL)) 47 | } 48 | } 49 | 50 | // DownloadHandler: 文件下载接口 51 | func DownloadHandler(c *gin.Context) { 52 | fsha1 := c.Request.FormValue("filehash") 53 | username := c.Request.FormValue("username") 54 | // TODO: 处理异常情况 55 | fResp, ferr := dbcli.GetFileMeta(fsha1) 56 | ufResp, uferr := dbcli.QueryUserFileMeta(username, fsha1) 57 | if ferr != nil || uferr != nil || !fResp.Suc || !ufResp.Suc { 58 | c.JSON( 59 | http.StatusOK, 60 | gin.H{ 61 | "code": common.StatusServerError, 62 | "msg": "server error", 63 | }) 64 | return 65 | } 66 | uniqFile := dbcli.ToTableFile(fResp.Data) 67 | userFile := dbcli.ToTableUserFile(ufResp.Data) 68 | 69 | if strings.HasPrefix(uniqFile.FileAddr.String, cfg.TempLocalRootDir) { 70 | // 本地文件, 直接下载 71 | c.FileAttachment(uniqFile.FileAddr.String, userFile.FileName) 72 | } else{ 73 | c.JSON( 74 | http.StatusOK, 75 | gin.H{ 76 | "code": common.StatusServerError, 77 | "msg": "server error", 78 | }) 79 | return 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /service/Microservice/download/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // UploadEntry : 配置上传入口地址 4 | var DownloadEntry = "localhost:38000" 5 | 6 | // DownloadServiceHost : 上传服务监听的地址 7 | var DownloadServiceHost = "0.0.0.0:38000" 8 | -------------------------------------------------------------------------------- /service/Microservice/download/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | micro "github.com/micro/go-micro" 8 | _ "github.com/micro/go-plugins/registry/kubernetes" 9 | 10 | "FileStore-Server/common" 11 | dbproxy "FileStore-Server/service/Microservice/dbproxy/client" 12 | cfg "FileStore-Server/service/Microservice/download/config" 13 | dlProto "FileStore-Server/service/Microservice/download/proto" 14 | "FileStore-Server/service/Microservice/download/route" 15 | dlRpc "FileStore-Server/service/Microservice/download/rpc" 16 | ) 17 | 18 | func startRPCService() { 19 | service := micro.NewService( 20 | micro.Name("go.micro.service.download"), // 在注册中心中的服务名称 21 | micro.RegisterTTL(time.Second*10), 22 | micro.RegisterInterval(time.Second*5), 23 | micro.Flags(common.CustomFlags...), 24 | ) 25 | service.Init() 26 | 27 | // 初始化dbproxy client 28 | dbproxy.Init(service) 29 | 30 | dlProto.RegisterDownloadServiceHandler(service.Server(), new(dlRpc.Download)) 31 | if err := service.Run(); err != nil { 32 | fmt.Println(err) 33 | } 34 | } 35 | 36 | func startAPIService() { 37 | router := route.Router() 38 | router.Run(cfg.DownloadServiceHost) 39 | } 40 | 41 | func main() { 42 | // api 服务 43 | go startAPIService() 44 | 45 | // rpc 服务 46 | startRPCService() 47 | } 48 | -------------------------------------------------------------------------------- /service/Microservice/download/proto/download.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: download.proto 3 | 4 | package go_micro_service_download 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | client "github.com/micro/go-micro/client" 15 | server "github.com/micro/go-micro/server" 16 | ) 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var _ = proto.Marshal 20 | var _ = fmt.Errorf 21 | var _ = math.Inf 22 | 23 | // This is a compile-time assertion to ensure that this generated file 24 | // is compatible with the proto package it is being compiled against. 25 | // A compilation error at this line likely means your copy of the 26 | // proto package needs to be updated. 27 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 28 | 29 | // Reference imports to suppress errors if they are not otherwise used. 30 | var _ context.Context 31 | var _ client.Option 32 | var _ server.Option 33 | 34 | // Client API for DownloadService service 35 | 36 | type DownloadService interface { 37 | // 获取下载入口地址 38 | DownloadEntry(ctx context.Context, in *ReqEntry, opts ...client.CallOption) (*RespEntry, error) 39 | } 40 | 41 | type downloadService struct { 42 | c client.Client 43 | name string 44 | } 45 | 46 | func NewDownloadService(name string, c client.Client) DownloadService { 47 | if c == nil { 48 | c = client.NewClient() 49 | } 50 | if len(name) == 0 { 51 | name = "go.micro.service.download" 52 | } 53 | return &downloadService{ 54 | c: c, 55 | name: name, 56 | } 57 | } 58 | 59 | func (c *downloadService) DownloadEntry(ctx context.Context, in *ReqEntry, opts ...client.CallOption) (*RespEntry, error) { 60 | req := c.c.NewRequest(c.name, "DownloadService.DownloadEntry", in) 61 | out := new(RespEntry) 62 | err := c.c.Call(ctx, req, out, opts...) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return out, nil 67 | } 68 | 69 | // Server API for DownloadService service 70 | 71 | type DownloadServiceHandler interface { 72 | // 获取下载入口地址 73 | DownloadEntry(context.Context, *ReqEntry, *RespEntry) error 74 | } 75 | 76 | func RegisterDownloadServiceHandler(s server.Server, hdlr DownloadServiceHandler, opts ...server.HandlerOption) error { 77 | type downloadService interface { 78 | DownloadEntry(ctx context.Context, in *ReqEntry, out *RespEntry) error 79 | } 80 | type DownloadService struct { 81 | downloadService 82 | } 83 | h := &downloadServiceHandler{hdlr} 84 | return s.Handle(s.NewHandler(&DownloadService{h}, opts...)) 85 | } 86 | 87 | type downloadServiceHandler struct { 88 | DownloadServiceHandler 89 | } 90 | 91 | func (h *downloadServiceHandler) DownloadEntry(ctx context.Context, in *ReqEntry, out *RespEntry) error { 92 | return h.DownloadServiceHandler.DownloadEntry(ctx, in, out) 93 | } 94 | -------------------------------------------------------------------------------- /service/Microservice/download/proto/download.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: download.proto 3 | 4 | package go_micro_service_download 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 22 | 23 | type ReqEntry struct { 24 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 25 | XXX_unrecognized []byte `json:"-"` 26 | XXX_sizecache int32 `json:"-"` 27 | } 28 | 29 | func (m *ReqEntry) Reset() { *m = ReqEntry{} } 30 | func (m *ReqEntry) String() string { return proto.CompactTextString(m) } 31 | func (*ReqEntry) ProtoMessage() {} 32 | func (*ReqEntry) Descriptor() ([]byte, []int) { 33 | return fileDescriptor_f7ce52b48c9eea83, []int{0} 34 | } 35 | 36 | func (m *ReqEntry) XXX_Unmarshal(b []byte) error { 37 | return xxx_messageInfo_ReqEntry.Unmarshal(m, b) 38 | } 39 | func (m *ReqEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 40 | return xxx_messageInfo_ReqEntry.Marshal(b, m, deterministic) 41 | } 42 | func (m *ReqEntry) XXX_Merge(src proto.Message) { 43 | xxx_messageInfo_ReqEntry.Merge(m, src) 44 | } 45 | func (m *ReqEntry) XXX_Size() int { 46 | return xxx_messageInfo_ReqEntry.Size(m) 47 | } 48 | func (m *ReqEntry) XXX_DiscardUnknown() { 49 | xxx_messageInfo_ReqEntry.DiscardUnknown(m) 50 | } 51 | 52 | var xxx_messageInfo_ReqEntry proto.InternalMessageInfo 53 | 54 | type RespEntry struct { 55 | Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 56 | Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` 57 | Entry string `protobuf:"bytes,3,opt,name=entry,proto3" json:"entry,omitempty"` 58 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 59 | XXX_unrecognized []byte `json:"-"` 60 | XXX_sizecache int32 `json:"-"` 61 | } 62 | 63 | func (m *RespEntry) Reset() { *m = RespEntry{} } 64 | func (m *RespEntry) String() string { return proto.CompactTextString(m) } 65 | func (*RespEntry) ProtoMessage() {} 66 | func (*RespEntry) Descriptor() ([]byte, []int) { 67 | return fileDescriptor_f7ce52b48c9eea83, []int{1} 68 | } 69 | 70 | func (m *RespEntry) XXX_Unmarshal(b []byte) error { 71 | return xxx_messageInfo_RespEntry.Unmarshal(m, b) 72 | } 73 | func (m *RespEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 74 | return xxx_messageInfo_RespEntry.Marshal(b, m, deterministic) 75 | } 76 | func (m *RespEntry) XXX_Merge(src proto.Message) { 77 | xxx_messageInfo_RespEntry.Merge(m, src) 78 | } 79 | func (m *RespEntry) XXX_Size() int { 80 | return xxx_messageInfo_RespEntry.Size(m) 81 | } 82 | func (m *RespEntry) XXX_DiscardUnknown() { 83 | xxx_messageInfo_RespEntry.DiscardUnknown(m) 84 | } 85 | 86 | var xxx_messageInfo_RespEntry proto.InternalMessageInfo 87 | 88 | func (m *RespEntry) GetCode() int32 { 89 | if m != nil { 90 | return m.Code 91 | } 92 | return 0 93 | } 94 | 95 | func (m *RespEntry) GetMessage() string { 96 | if m != nil { 97 | return m.Message 98 | } 99 | return "" 100 | } 101 | 102 | func (m *RespEntry) GetEntry() string { 103 | if m != nil { 104 | return m.Entry 105 | } 106 | return "" 107 | } 108 | 109 | func init() { 110 | proto.RegisterType((*ReqEntry)(nil), "go.micro.service.download.ReqEntry") 111 | proto.RegisterType((*RespEntry)(nil), "go.micro.service.download.RespEntry") 112 | } 113 | 114 | func init() { proto.RegisterFile("download.proto", fileDescriptor_f7ce52b48c9eea83) } 115 | 116 | var fileDescriptor_f7ce52b48c9eea83 = []byte{ 117 | // 172 bytes of a gzipped FileDescriptorProto 118 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4b, 0xc9, 0x2f, 0xcf, 119 | 0xcb, 0xc9, 0x4f, 0x4c, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x4c, 0xcf, 0xd7, 0xcb, 120 | 0xcd, 0x4c, 0x2e, 0xca, 0xd7, 0x2b, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x83, 0x29, 0x50, 121 | 0xe2, 0xe2, 0xe2, 0x08, 0x4a, 0x2d, 0x74, 0xcd, 0x2b, 0x29, 0xaa, 0x54, 0xf2, 0xe7, 0xe2, 0x0c, 122 | 0x4a, 0x2d, 0x2e, 0x00, 0x73, 0x84, 0x84, 0xb8, 0x58, 0x92, 0xf3, 0x53, 0x52, 0x25, 0x18, 0x15, 123 | 0x18, 0x35, 0x58, 0x83, 0xc0, 0x6c, 0x21, 0x09, 0x2e, 0xf6, 0xdc, 0xd4, 0xe2, 0xe2, 0xc4, 0xf4, 124 | 0x54, 0x09, 0x26, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x18, 0x57, 0x48, 0x84, 0x8b, 0x35, 0x15, 0xa4, 125 | 0x4d, 0x82, 0x19, 0x2c, 0x0e, 0xe1, 0x18, 0xe5, 0x73, 0xf1, 0xbb, 0x40, 0x2d, 0x0a, 0x86, 0x58, 126 | 0x2c, 0x14, 0xc3, 0xc5, 0x0b, 0x13, 0x82, 0xd8, 0xa3, 0xac, 0x87, 0xd3, 0x71, 0x7a, 0x30, 0x97, 127 | 0x49, 0xa9, 0xe0, 0x55, 0x04, 0x75, 0xb2, 0x12, 0x43, 0x12, 0x1b, 0xd8, 0xbf, 0xc6, 0x80, 0x00, 128 | 0x00, 0x00, 0xff, 0xff, 0x41, 0x36, 0x4a, 0x64, 0x01, 0x01, 0x00, 0x00, 129 | } 130 | -------------------------------------------------------------------------------- /service/Microservice/download/proto/download.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package go.micro.service.download; 4 | 5 | service DownloadService { 6 | // 获取下载入口地址 7 | rpc DownloadEntry(ReqEntry) returns (RespEntry) {} 8 | } 9 | 10 | message ReqEntry { 11 | } 12 | 13 | message RespEntry { 14 | int32 code = 1; 15 | string message = 2; 16 | string entry = 3; 17 | } -------------------------------------------------------------------------------- /service/Microservice/download/route/router.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "FileStore-Server/service/Microservice/download/api" 5 | 6 | "github.com/gin-contrib/cors" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | // Router : 路由表配置 11 | func Router() *gin.Engine { 12 | // gin framework, 包括Logger, Recovery 13 | router := gin.Default() 14 | 15 | // 处理静态资源 16 | router.Static("/static/", "./static") 17 | 18 | // // 加入中间件,用于校验token的拦截器(将会从account微服务中验证) 19 | // router.Use(handler.HTTPInterceptor()) 20 | 21 | // 使用gin插件支持跨域请求 22 | router.Use(cors.New(cors.Config{ 23 | AllowOrigins: []string{"*"}, // []string{"http://localhost:8080"}, 24 | AllowMethods: []string{"GET", "POST", "OPTIONS"}, 25 | AllowHeaders: []string{"Origin", "Range", "x-requested-with", "content-Type"}, 26 | ExposeHeaders: []string{"Content-Length", "Accept-Ranges", "Content-Range", "Content-Disposition"}, 27 | // AllowCredentials: true, 28 | })) 29 | 30 | // Use之后的所有handler都会经过拦截器进行token校验 31 | 32 | // 文件下载相关接口 33 | router.GET("/file/download", api.DownloadHandler) 34 | router.POST("/file/downloadurl", api.DownloadURLHandler) 35 | 36 | return router 37 | } 38 | -------------------------------------------------------------------------------- /service/Microservice/download/rpc/entry.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | cfg "FileStore-Server/service/Microservice/download/config" 6 | dlProto "FileStore-Server/service/Microservice/download/proto" 7 | ) 8 | 9 | // Dwonload :download结构体 10 | type Download struct{} 11 | 12 | // DownloadEntry : 获取下载入口 13 | func (u *Download) DownloadEntry( 14 | ctx context.Context, 15 | req *dlProto.ReqEntry, 16 | res *dlProto.RespEntry) error { 17 | 18 | res.Entry = cfg.DownloadEntry 19 | return nil 20 | } 21 | -------------------------------------------------------------------------------- /service/Microservice/transfer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "FileStore-Server/common" 9 | "FileStore-Server/config" 10 | "FileStore-Server/mq" 11 | dbproxy "FileStore-Server/service/Microservice/dbproxy/client" 12 | "FileStore-Server/service/Microservice/transfer/process" 13 | 14 | "github.com/micro/cli" 15 | micro "github.com/micro/go-micro" 16 | _ "github.com/micro/go-plugins/registry/kubernetes" 17 | ) 18 | 19 | func startRPCService() { 20 | service := micro.NewService( 21 | micro.Name("go.micro.service.transfer"), // 服务名称 22 | micro.RegisterTTL(time.Second*10), // TTL指定从上一次心跳间隔起,超过这个时间服务会被服务发现移除 23 | micro.RegisterInterval(time.Second*5), // 让服务在指定时间内重新注册,保持TTL获取的注册时间有效 24 | micro.Flags(common.CustomFlags...), 25 | ) 26 | service.Init( 27 | micro.Action(func(c *cli.Context) { 28 | // 检查是否指定mqhost 29 | mqhost := c.String("mqhost") 30 | if len(mqhost) > 0 { 31 | log.Println("custom mq address: " + mqhost) 32 | mq.UpdateRabbitHost(mqhost) 33 | } 34 | }), 35 | ) 36 | 37 | // 初始化dbproxy client 38 | dbproxy.Init(service) 39 | 40 | if err := service.Run(); err != nil { 41 | fmt.Println(err) 42 | } 43 | } 44 | 45 | func startTranserService() { 46 | if !config.AsyncTransferEnable { 47 | log.Println("异步转移文件功能目前被禁用,请检查相关配置") 48 | return 49 | } 50 | log.Println("文件转移服务启动中,开始监听转移任务队列...") 51 | 52 | // 初始化mq client 53 | mq.Init() 54 | 55 | mq.StartConsume( 56 | config.TransOSSQueueName, 57 | "transfer_oss", 58 | process.Transfer) 59 | } 60 | 61 | func main() { 62 | // 文件转移服务 63 | go startTranserService() 64 | 65 | // rpc 服务 66 | startRPCService() 67 | } 68 | -------------------------------------------------------------------------------- /service/Microservice/transfer/process/transfer.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "log" 7 | "os" 8 | 9 | "FileStore-Server/mq" 10 | dbcli "FileStore-Server/service/Microservice/dbproxy/client" 11 | "FileStore-Server/store/oss" 12 | ) 13 | 14 | // Transfer : 处理文件转移 15 | func Transfer(msg []byte) bool { 16 | log.Println(string(msg)) 17 | 18 | pubData := mq.TransferData{} 19 | err := json.Unmarshal(msg, &pubData) 20 | if err != nil { 21 | log.Println(err.Error()) 22 | return false 23 | } 24 | 25 | fin, err := os.Open(pubData.CurLocation) 26 | if err != nil { 27 | log.Println(err.Error()) 28 | return false 29 | } 30 | 31 | err = oss.Bucket().PutObject( 32 | pubData.DestLocation, 33 | bufio.NewReader(fin)) 34 | if err != nil { 35 | log.Println(err.Error()) 36 | return false 37 | } 38 | 39 | resp, err := dbcli.UpdateFileLocation( 40 | pubData.FileHash, 41 | pubData.DestLocation) 42 | if err != nil { 43 | log.Println(err.Error()) 44 | return false 45 | } 46 | if !resp.Suc { 47 | log.Println("更新数据库异常,请检查:" + pubData.FileHash) 48 | return false 49 | } 50 | return true 51 | } 52 | -------------------------------------------------------------------------------- /service/Microservice/upload/api/mpupload.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "FileStore-Server/config" 5 | "FileStore-Server/util" 6 | "fmt" 7 | "github.com/garyburd/redigo/redis" 8 | "github.com/gin-gonic/gin" 9 | "log" 10 | "math" 11 | "net/http" 12 | "os" 13 | "path" 14 | "strconv" 15 | "strings" 16 | "time" 17 | 18 | rPool "FileStore-Server/cache/redis" 19 | dbcli "FileStore-Server/service/Microservice/dbproxy/client" 20 | ) 21 | 22 | // MultipartUploadInfo : 初始化信息 23 | type MultipartUploadInfo struct { 24 | FileHash string 25 | FileSize int 26 | UploadID string 27 | ChunkSize int 28 | ChunkCount int 29 | } 30 | 31 | func init() { 32 | os.MkdirAll(config.TempPartRootDir, 0744) 33 | } 34 | 35 | // InitialMultipartUploadHandler : 初始化分块上传 36 | func InitialMultipartUploadHandler(c *gin.Context) { 37 | // 1. 解析用户请求参数 38 | username := c.Request.FormValue("username") 39 | filehash := c.Request.FormValue("filehash") 40 | filesize, err := strconv.Atoi(c.Request.FormValue("filesize")) 41 | if err != nil { 42 | c.JSON( 43 | http.StatusOK, 44 | gin.H{ 45 | "code": -1, 46 | "msg": "params invalid", 47 | }) 48 | return 49 | } 50 | 51 | // 2. 获得redis的一个连接 52 | rConn := rPool.RedisPool().Get() 53 | defer rConn.Close() 54 | 55 | // 3. 生成分块上传的初始化信息 56 | upInfo := MultipartUploadInfo{ 57 | FileHash: filehash, 58 | FileSize: filesize, 59 | UploadID: username + fmt.Sprintf("%x", time.Now().UnixNano()), 60 | ChunkSize: 5 * 1024 * 1024, // 5MB 61 | ChunkCount: int(math.Ceil(float64(filesize) / (5 * 1024 * 1024))), 62 | } 63 | 64 | // 4. 将初始化信息写入到redis缓存 65 | rConn.Do("HSET", "MP_"+upInfo.UploadID, "chunkcount", upInfo.ChunkCount) 66 | rConn.Do("HSET", "MP_"+upInfo.UploadID, "filehash", upInfo.FileHash) 67 | rConn.Do("HSET", "MP_"+upInfo.UploadID, "filesize", upInfo.FileSize) 68 | 69 | // 5. 将响应初始化数据返回到客户端 70 | c.JSON( 71 | http.StatusOK, 72 | gin.H{ 73 | "code": 0, 74 | "msg": "OK", 75 | "data": upInfo, 76 | }) 77 | } 78 | 79 | // UploadPartHandler : 上传文件分块 80 | func UploadPartHandler(c *gin.Context) { 81 | // 1. 解析用户请求参数 82 | // username := c.Request.FormValue("username") 83 | uploadID := c.Request.FormValue("uploadid") 84 | chunkIndex := c.Request.FormValue("index") 85 | 86 | // 2. 获得redis连接池中的一个连接 87 | rConn := rPool.RedisPool().Get() 88 | defer rConn.Close() 89 | 90 | // 3. 获得文件句柄,用于存储分块内容 91 | fpath := config.TempPartRootDir + uploadID + "/" + chunkIndex 92 | os.MkdirAll(path.Dir(fpath), 0744) 93 | fd, err := os.Create(fpath) 94 | if err != nil { 95 | c.JSON( 96 | http.StatusOK, 97 | gin.H{ 98 | "code": 0, 99 | "msg": "Upload part failed", 100 | "data": nil, 101 | }) 102 | return 103 | } 104 | defer fd.Close() 105 | 106 | buf := make([]byte, 1024*1024) 107 | for { 108 | n, err := c.Request.Body.Read(buf) 109 | fd.Write(buf[:n]) 110 | if err != nil { 111 | break 112 | } 113 | } 114 | 115 | // 4. 更新redis缓存状态 116 | rConn.Do("HSET", "MP_"+uploadID, "chkidx_"+chunkIndex, 1) 117 | 118 | // 5. 返回处理结果到客户端 119 | c.JSON( 120 | http.StatusOK, 121 | gin.H{ 122 | "code": 0, 123 | "msg": "OK", 124 | "data": nil, 125 | }) 126 | } 127 | 128 | // CompleteUploadHandler : 通知上传合并 129 | func CompleteUploadHandler(c *gin.Context) { 130 | // 1. 解析请求参数 131 | upid := c.Request.FormValue("uploadid") 132 | username := c.Request.FormValue("username") 133 | filehash := c.Request.FormValue("filehash") 134 | filesize := c.Request.FormValue("filesize") 135 | filename := c.Request.FormValue("filename") 136 | 137 | // 2. 获得redis连接池中的一个连接 138 | rConn := rPool.RedisPool().Get() 139 | defer rConn.Close() 140 | 141 | // 3. 通过uploadid查询redis并判断是否所有分块上传完成 142 | data, err := redis.Values(rConn.Do("HGETALL", "MP_"+upid)) 143 | if err != nil { 144 | c.JSON( 145 | http.StatusOK, 146 | gin.H{ 147 | "code": -1, 148 | "msg": "服务错误", 149 | "data": nil, 150 | }) 151 | return 152 | } 153 | totalCount := 0 154 | chunkCount := 0 155 | for i := 0; i < len(data); i += 2 { 156 | k := string(data[i].([]byte)) 157 | v := string(data[i+1].([]byte)) 158 | if k == "chunkcount" { 159 | totalCount, _ = strconv.Atoi(v) 160 | } else if strings.HasPrefix(k, "chkidx_") && v == "1" { 161 | chunkCount++ 162 | } 163 | } 164 | if totalCount != chunkCount { 165 | c.JSON( 166 | http.StatusOK, 167 | gin.H{ 168 | "code": -2, 169 | "msg": "分块不完整", 170 | "data": nil, 171 | }) 172 | return 173 | } 174 | 175 | // 4. TODO:合并分块, 可以将ceph当临时存储,合并时将文件写入ceph; 176 | // 也可以不用在本地进行合并,转移的时候将分块append到ceph/oss即可 177 | srcPath := config.TempPartRootDir + upid + "/" 178 | destPath := config.TempLocalRootDir + filehash 179 | cmd := fmt.Sprintf("cd %s && ls | sort -n | xargs cat > %s", srcPath, destPath) 180 | mergeRes, err := util.ExecLinuxShell(cmd) 181 | if err != nil { 182 | log.Println(err) 183 | c.JSON( 184 | http.StatusOK, 185 | gin.H{ 186 | "code": -2, 187 | "msg": "合并失败", 188 | "data": nil, 189 | }) 190 | return 191 | } 192 | log.Println(mergeRes) 193 | 194 | // 5. 更新唯一文件表及用户文件表 195 | fsize, _ := strconv.Atoi(filesize) 196 | 197 | fmeta := dbcli.FileMeta{ 198 | FileSha1: filehash, 199 | FileName: filename, 200 | FileSize: int64(fsize), 201 | Location: destPath, 202 | } 203 | _, ferr := dbcli.OnFileUploadFinished(fmeta) 204 | _, uferr := dbcli.OnUserFileUploadFinished(username, fmeta) 205 | if ferr != nil || uferr != nil { 206 | log.Println(err) 207 | c.JSON( 208 | http.StatusOK, 209 | gin.H{ 210 | "code": -2, 211 | "msg": "数据更新失败", 212 | "data": nil, 213 | }) 214 | return 215 | } 216 | 217 | // 6. 响应处理结果 218 | c.JSON( 219 | http.StatusOK, 220 | gin.H{ 221 | "code": 0, 222 | "msg": "OK", 223 | "data": nil, 224 | }) 225 | } -------------------------------------------------------------------------------- /service/Microservice/upload/api/upload.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "FileStore-Server/config" 5 | "FileStore-Server/db" 6 | "FileStore-Server/meta" 7 | "FileStore-Server/mq" 8 | dbcli "FileStore-Server/service/Microservice/dbproxy/client" 9 | "FileStore-Server/service/Microservice/dbproxy/orm" 10 | "FileStore-Server/util" 11 | "encoding/json" 12 | "github.com/gin-gonic/gin" 13 | "io" 14 | "log" 15 | "net/http" 16 | "os" 17 | "time" 18 | ) 19 | 20 | //DoUploadHandler: 处理文件上传 21 | func DoUploadHandler(c *gin.Context) { 22 | errCode := 0 23 | defer func() { 24 | // 处理跨域请求 25 | c.Header("Access-Control-Allow-Origin", "*") // 支持所有来源 26 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") // 支持所有http方法 27 | // 处理上传结果 28 | if errCode < 0 { 29 | c.JSON(http.StatusOK, gin.H{ 30 | "code": errCode, 31 | "msg": "上传失败", 32 | }) 33 | } else { 34 | c.JSON(http.StatusOK, gin.H{ 35 | "code": errCode, 36 | "msg": "上传成功", 37 | }) 38 | } 39 | }() 40 | 41 | //获取表单上传的文件,并打开 42 | file,head,err := c.Request.FormFile("file") 43 | if err != nil { 44 | log.Printf("Failed to get data, err: %s\n",err.Error()) 45 | errCode = -1 46 | return 47 | } 48 | defer file.Close() 49 | 50 | //创建文件元信息实例 51 | fileMeta := meta.FileMeta{ 52 | FileName:head.Filename, 53 | Location:"tempFiles/"+head.Filename, 54 | UploadAt:time.Now().Format("2006-01-02 15:04:05"), 55 | } 56 | 57 | //创建本地文件 58 | localFile,err := os.Create(fileMeta.Location) 59 | if err != nil { 60 | log.Printf("Failed to create file, err: %s\n",err.Error()) 61 | errCode = -2 62 | return 63 | } 64 | defer localFile.Close() 65 | 66 | //复制文件信息到本地文件 67 | fileMeta.FileSize,err = io.Copy(localFile,file) 68 | if err != nil { 69 | log.Printf("Failed to save data into file, err: %s\n",err.Error()) 70 | errCode = -3 71 | return 72 | } 73 | 74 | //计算文件哈希值 75 | localFile.Seek(0,0) 76 | fileMeta.FileSha1 = util.FileSha1(localFile) 77 | 78 | // 游标重新回到文件头部 79 | localFile.Seek(0, 0) 80 | 81 | // 将文件写入阿里云OSS 82 | ossPath := "oss/" + fileMeta.FileSha1 + fileMeta.FileName 83 | 84 | // 将转移任务添加到rabbitmq队列中 85 | data := mq.TransferData{ 86 | FileHash:fileMeta.FileSha1, 87 | CurLocation:fileMeta.Location, 88 | DestLocation:ossPath, 89 | } 90 | pubData, _ := json.Marshal(data) 91 | pubSuc := mq.Publish( 92 | config.TransExchangeName, 93 | config.TransOSSRoutingKey, 94 | pubData, 95 | ) 96 | if !pubSuc { 97 | // TODO: 当前发送转移信息失败,稍后重试 98 | } 99 | 100 | //将文件元信息添加到mysql中 101 | _ = meta.UpdateFileMetaDB(fileMeta) 102 | 103 | //更新用户文件表记录 104 | username := c.Request.FormValue("username") 105 | ok := db.OnUserFileUploadFinished(username,fileMeta.FileSha1,fileMeta.FileName,fileMeta.FileSize) 106 | if ok { 107 | errCode = 0 108 | }else { 109 | errCode = -4 110 | } 111 | } 112 | 113 | // TryFastUploadHandler : 尝试秒传接口 114 | func TryFastUploadHandler(c *gin.Context) { 115 | // 1. 解析请求参数 116 | username := c.Request.FormValue("username") 117 | filehash := c.Request.FormValue("filehash") 118 | filename := c.Request.FormValue("filename") 119 | //filesize, _ := strconv.Atoi(c.Request.FormValue("filesize")) 120 | 121 | // 2. 从文件表中查询相同hash的文件记录 122 | fileMetaResp, err := dbcli.GetFileMeta(filehash) 123 | if err != nil { 124 | log.Println(err.Error()) 125 | c.Status(http.StatusInternalServerError) 126 | return 127 | } 128 | 129 | // 3. 查不到记录则返回秒传失败 130 | if !fileMetaResp.Suc { 131 | resp := util.RespMsg{ 132 | Code: -1, 133 | Msg: "秒传失败,请访问普通上传接口", 134 | } 135 | c.Data(http.StatusOK, "application/json", resp.JSONBytes()) 136 | return 137 | } 138 | 139 | // 4. 上传过则将文件信息写入用户文件表, 返回成功 140 | fmeta := dbcli.TableFileToFileMeta(fileMetaResp.Data.(orm.TableFile)) 141 | fmeta.FileName = filename 142 | upRes, err := dbcli.OnUserFileUploadFinished(username, fmeta) 143 | if err == nil && upRes.Suc { 144 | resp := util.RespMsg{ 145 | Code: 0, 146 | Msg: "秒传成功", 147 | } 148 | c.Data(http.StatusOK, "application/json", resp.JSONBytes()) 149 | return 150 | } 151 | resp := util.RespMsg{ 152 | Code: -2, 153 | Msg: "秒传失败,请稍后重试", 154 | } 155 | c.Data(http.StatusOK, "application/json", resp.JSONBytes()) 156 | return 157 | } -------------------------------------------------------------------------------- /service/Microservice/upload/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // UploadEntry : 配置上传入口地址 4 | var UploadEntry = "localhost:28000" 5 | 6 | // UploadServiceHost : 上传服务监听的地址 7 | var UploadServiceHost = "0.0.0.0:28000" 8 | -------------------------------------------------------------------------------- /service/Microservice/upload/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | cfg "FileStore-Server/service/Microservice/upload/config" 5 | upProto "FileStore-Server/service/Microservice/upload/proto" 6 | "FileStore-Server/service/Microservice/upload/route" 7 | upRpc "FileStore-Server/service/Microservice/upload/rpc" 8 | "github.com/micro/go-micro" 9 | "github.com/micro/go-micro/registry" 10 | "github.com/micro/go-micro/registry/consul" 11 | "log" 12 | "time" 13 | ) 14 | 15 | func startRPCService() { 16 | // 修改consul地址 17 | reg := consul.NewRegistry(func(op *registry.Options){ 18 | op.Addrs = []string{ 19 | "47.95.253.230:8500", 20 | } 21 | }) 22 | 23 | service := micro.NewService( 24 | micro.Registry(reg), 25 | micro.Name("go.micro.service.upload"), // 服务名称 26 | micro.RegisterTTL(time.Second*10), // TTL指定从上一次心跳间隔起,超过这个时间服务会被服务发现移除 27 | micro.RegisterInterval(time.Second*5), // 让服务在指定时间内重新注册,保持TTL获取的注册时间有效 28 | ) 29 | service.Init() 30 | 31 | upProto.RegisterUploadServiceHandler(service.Server(), new(upRpc.Upload)) 32 | if err := service.Run(); err != nil { 33 | log.Println(err) 34 | } 35 | } 36 | 37 | // 启动api服务 38 | func startAPIService() { 39 | router := route.Router() 40 | router.Run(cfg.UploadServiceHost) 41 | } 42 | 43 | func main() { 44 | go startRPCService() 45 | startAPIService() 46 | } -------------------------------------------------------------------------------- /service/Microservice/upload/proto/upload.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: upload.proto 3 | 4 | package go_micro_service_upload 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | client "github.com/micro/go-micro/client" 15 | server "github.com/micro/go-micro/server" 16 | ) 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var _ = proto.Marshal 20 | var _ = fmt.Errorf 21 | var _ = math.Inf 22 | 23 | // This is a compile-time assertion to ensure that this generated file 24 | // is compatible with the proto package it is being compiled against. 25 | // A compilation error at this line likely means your copy of the 26 | // proto package needs to be updated. 27 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 28 | 29 | // Reference imports to suppress errors if they are not otherwise used. 30 | var _ context.Context 31 | var _ client.Option 32 | var _ server.Option 33 | 34 | // Client API for UploadService service 35 | 36 | type UploadService interface { 37 | // 获取上传入口地址 38 | UploadEntry(ctx context.Context, in *ReqEntry, opts ...client.CallOption) (*RespEntry, error) 39 | } 40 | 41 | type uploadService struct { 42 | c client.Client 43 | name string 44 | } 45 | 46 | func NewUploadService(name string, c client.Client) UploadService { 47 | if c == nil { 48 | c = client.NewClient() 49 | } 50 | if len(name) == 0 { 51 | name = "go.micro.service.upload" 52 | } 53 | return &uploadService{ 54 | c: c, 55 | name: name, 56 | } 57 | } 58 | 59 | func (c *uploadService) UploadEntry(ctx context.Context, in *ReqEntry, opts ...client.CallOption) (*RespEntry, error) { 60 | req := c.c.NewRequest(c.name, "UploadService.UploadEntry", in) 61 | out := new(RespEntry) 62 | err := c.c.Call(ctx, req, out, opts...) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return out, nil 67 | } 68 | 69 | // Server API for UploadService service 70 | 71 | type UploadServiceHandler interface { 72 | // 获取上传入口地址 73 | UploadEntry(context.Context, *ReqEntry, *RespEntry) error 74 | } 75 | 76 | func RegisterUploadServiceHandler(s server.Server, hdlr UploadServiceHandler, opts ...server.HandlerOption) error { 77 | type uploadService interface { 78 | UploadEntry(ctx context.Context, in *ReqEntry, out *RespEntry) error 79 | } 80 | type UploadService struct { 81 | uploadService 82 | } 83 | h := &uploadServiceHandler{hdlr} 84 | return s.Handle(s.NewHandler(&UploadService{h}, opts...)) 85 | } 86 | 87 | type uploadServiceHandler struct { 88 | UploadServiceHandler 89 | } 90 | 91 | func (h *uploadServiceHandler) UploadEntry(ctx context.Context, in *ReqEntry, out *RespEntry) error { 92 | return h.UploadServiceHandler.UploadEntry(ctx, in, out) 93 | } 94 | -------------------------------------------------------------------------------- /service/Microservice/upload/proto/upload.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: upload.proto 3 | 4 | package go_micro_service_upload 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | // Reference imports to suppress errors if they are not otherwise used. 13 | var _ = proto.Marshal 14 | var _ = fmt.Errorf 15 | var _ = math.Inf 16 | 17 | // This is a compile-time assertion to ensure that this generated file 18 | // is compatible with the proto package it is being compiled against. 19 | // A compilation error at this line likely means your copy of the 20 | // proto package needs to be updated. 21 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 22 | 23 | type ReqEntry struct { 24 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 25 | XXX_unrecognized []byte `json:"-"` 26 | XXX_sizecache int32 `json:"-"` 27 | } 28 | 29 | func (m *ReqEntry) Reset() { *m = ReqEntry{} } 30 | func (m *ReqEntry) String() string { return proto.CompactTextString(m) } 31 | func (*ReqEntry) ProtoMessage() {} 32 | func (*ReqEntry) Descriptor() ([]byte, []int) { 33 | return fileDescriptor_91b94b655bd2a7e5, []int{0} 34 | } 35 | 36 | func (m *ReqEntry) XXX_Unmarshal(b []byte) error { 37 | return xxx_messageInfo_ReqEntry.Unmarshal(m, b) 38 | } 39 | func (m *ReqEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 40 | return xxx_messageInfo_ReqEntry.Marshal(b, m, deterministic) 41 | } 42 | func (m *ReqEntry) XXX_Merge(src proto.Message) { 43 | xxx_messageInfo_ReqEntry.Merge(m, src) 44 | } 45 | func (m *ReqEntry) XXX_Size() int { 46 | return xxx_messageInfo_ReqEntry.Size(m) 47 | } 48 | func (m *ReqEntry) XXX_DiscardUnknown() { 49 | xxx_messageInfo_ReqEntry.DiscardUnknown(m) 50 | } 51 | 52 | var xxx_messageInfo_ReqEntry proto.InternalMessageInfo 53 | 54 | type RespEntry struct { 55 | Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` 56 | Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` 57 | Entry string `protobuf:"bytes,3,opt,name=entry,proto3" json:"entry,omitempty"` 58 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 59 | XXX_unrecognized []byte `json:"-"` 60 | XXX_sizecache int32 `json:"-"` 61 | } 62 | 63 | func (m *RespEntry) Reset() { *m = RespEntry{} } 64 | func (m *RespEntry) String() string { return proto.CompactTextString(m) } 65 | func (*RespEntry) ProtoMessage() {} 66 | func (*RespEntry) Descriptor() ([]byte, []int) { 67 | return fileDescriptor_91b94b655bd2a7e5, []int{1} 68 | } 69 | 70 | func (m *RespEntry) XXX_Unmarshal(b []byte) error { 71 | return xxx_messageInfo_RespEntry.Unmarshal(m, b) 72 | } 73 | func (m *RespEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 74 | return xxx_messageInfo_RespEntry.Marshal(b, m, deterministic) 75 | } 76 | func (m *RespEntry) XXX_Merge(src proto.Message) { 77 | xxx_messageInfo_RespEntry.Merge(m, src) 78 | } 79 | func (m *RespEntry) XXX_Size() int { 80 | return xxx_messageInfo_RespEntry.Size(m) 81 | } 82 | func (m *RespEntry) XXX_DiscardUnknown() { 83 | xxx_messageInfo_RespEntry.DiscardUnknown(m) 84 | } 85 | 86 | var xxx_messageInfo_RespEntry proto.InternalMessageInfo 87 | 88 | func (m *RespEntry) GetCode() int32 { 89 | if m != nil { 90 | return m.Code 91 | } 92 | return 0 93 | } 94 | 95 | func (m *RespEntry) GetMessage() string { 96 | if m != nil { 97 | return m.Message 98 | } 99 | return "" 100 | } 101 | 102 | func (m *RespEntry) GetEntry() string { 103 | if m != nil { 104 | return m.Entry 105 | } 106 | return "" 107 | } 108 | 109 | func init() { 110 | proto.RegisterType((*ReqEntry)(nil), "go.micro.service.upload.ReqEntry") 111 | proto.RegisterType((*RespEntry)(nil), "go.micro.service.upload.RespEntry") 112 | } 113 | 114 | func init() { proto.RegisterFile("upload.proto", fileDescriptor_91b94b655bd2a7e5) } 115 | 116 | var fileDescriptor_91b94b655bd2a7e5 = []byte{ 117 | // 170 bytes of a gzipped FileDescriptorProto 118 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0x2d, 0xc8, 0xc9, 119 | 0x4f, 0x4c, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4f, 0xcf, 0xd7, 0xcb, 0xcd, 0x4c, 120 | 0x2e, 0xca, 0xd7, 0x2b, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0xd5, 0x83, 0x48, 0x2b, 0x71, 0x71, 121 | 0x71, 0x04, 0xa5, 0x16, 0xba, 0xe6, 0x95, 0x14, 0x55, 0x2a, 0xf9, 0x73, 0x71, 0x06, 0xa5, 0x16, 122 | 0x17, 0x80, 0x39, 0x42, 0x42, 0x5c, 0x2c, 0xc9, 0xf9, 0x29, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 123 | 0xac, 0x41, 0x60, 0xb6, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, 0x62, 0x7a, 0xaa, 0x04, 124 | 0x93, 0x02, 0xa3, 0x06, 0x67, 0x10, 0x8c, 0x2b, 0x24, 0xc2, 0xc5, 0x9a, 0x0a, 0xd2, 0x26, 0xc1, 125 | 0x0c, 0x16, 0x87, 0x70, 0x8c, 0xd2, 0xb9, 0x78, 0x43, 0xc1, 0xd6, 0x04, 0x43, 0x2c, 0x15, 0x0a, 126 | 0xe3, 0xe2, 0x86, 0x08, 0x40, 0xec, 0x50, 0xd4, 0xc3, 0xe1, 0x2c, 0x3d, 0x98, 0x9b, 0xa4, 0x94, 127 | 0xf0, 0x28, 0x81, 0x3a, 0x55, 0x89, 0x21, 0x89, 0x0d, 0xec, 0x4b, 0x63, 0x40, 0x00, 0x00, 0x00, 128 | 0xff, 0xff, 0x0e, 0x2c, 0xfc, 0xad, 0xf5, 0x00, 0x00, 0x00, 129 | } 130 | -------------------------------------------------------------------------------- /service/Microservice/upload/proto/upload.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package go.micro.service.upload; 4 | 5 | // 编译命令(service/Microservice目录下执行): 6 | // protoc --proto_path=./upload/proto --go_out=./upload/proto --micro_out=./upload/proto ./upload/proto/upload.proto 7 | 8 | service UploadService { 9 | // 获取上传入口地址 10 | rpc UploadEntry(ReqEntry) returns (RespEntry) {} 11 | } 12 | 13 | message ReqEntry { 14 | } 15 | 16 | message RespEntry { 17 | int32 code = 1; 18 | string message = 2; 19 | string entry = 3; 20 | } -------------------------------------------------------------------------------- /service/Microservice/upload/route/router.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "FileStore-Server/service/Microservice/upload/api" 5 | "github.com/gin-contrib/cors" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Router() *gin.Engine { 10 | // gin framework, 包括Logger, Recovery 11 | router := gin.Default() 12 | 13 | // 处理静态资源 14 | router.Static("/static/","./static") 15 | 16 | //// 加入中间件,用于验证token的拦截器 17 | //router.Use(handler.HTTPInterceptor()) 18 | 19 | // 上传文件 20 | router.POST("/file/upload",api.DoUploadHandler) 21 | //// 支持跨域 22 | //router.OPTIONS("/file/upload", func(c *gin.Context) { 23 | // c.Header("Access-Control-Allow-Origin", "*") // 支持所有来源 24 | // c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") // 支持所有http方法 25 | // c.Status(204) // 告诉前端请求成功,但body为空,页面不用刷新 26 | //}) 27 | // 使用gin插件支持跨域请求 28 | router.Use(cors.New(cors.Config{ 29 | AllowOrigins: []string{"*"}, // []string{"http://localhost:8080"}, 30 | AllowMethods: []string{"GET", "POST", "OPTIONS"}, 31 | AllowHeaders: []string{"Origin", "Range", "x-requested-with", "content-Type"}, 32 | ExposeHeaders: []string{"Content-Length", "Accept-Ranges", "Content-Range", "Content-Disposition"}, 33 | // AllowCredentials: true, 34 | })) 35 | 36 | // 文件上传相关接口 37 | router.POST("/file/upload", api.DoUploadHandler) 38 | // 秒传接口 39 | router.POST("/file/fastupload", api.TryFastUploadHandler) 40 | 41 | // 分块上传接口 42 | router.POST("/file/mpupload/init", api.InitialMultipartUploadHandler) 43 | router.POST("/file/mpupload/uppart", api.UploadPartHandler) 44 | router.POST("/file/mpupload/complete", api.CompleteUploadHandler) 45 | 46 | return router 47 | } -------------------------------------------------------------------------------- /service/Microservice/upload/rpc/entry.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "FileStore-Server/service/Microservice/upload/config" 5 | uploadProto "FileStore-Server/service/Microservice/upload/proto" 6 | "context" 7 | ) 8 | 9 | type Upload struct {} 10 | 11 | // 获取上传入口地址 12 | func (u *Upload) UploadEntry(ctx context.Context,req *uploadProto.ReqEntry,resp *uploadProto.RespEntry) error { 13 | resp.Entry = config.UploadEntry 14 | return nil 15 | } -------------------------------------------------------------------------------- /service/normal/transfer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "FileStore-Server/config" 5 | "FileStore-Server/db" 6 | "FileStore-Server/mq" 7 | "FileStore-Server/store/oss" 8 | "bufio" 9 | "encoding/json" 10 | "log" 11 | "os" 12 | ) 13 | 14 | //ProcessTransfer: 处理文件转移 15 | func ProcessTransfer(msg []byte) bool { 16 | log.Println(string(msg)) 17 | 18 | // 解析msg 19 | pubData := mq.TransferData{} 20 | err := json.Unmarshal(msg, &pubData) 21 | if err != nil { 22 | log.Println(err.Error()) 23 | return false 24 | } 25 | 26 | // 根据msg临时存储位置,并创建文件句柄 27 | filed, err := os.Open(pubData.CurLocation) 28 | if err != nil { 29 | log.Println(err.Error()) 30 | return false 31 | } 32 | 33 | // 通过文件句柄将文件内容转移到oss 34 | err = oss.Bucket().PutObject(pubData.DestLocation,bufio.NewReader(filed)) 35 | if err != nil { 36 | log.Println(err.Error()) 37 | return false 38 | } 39 | 40 | // 更新文件的存储路径到数据库文件表 41 | ok := db.UpdateFileLocation(pubData.FileHash,pubData.DestLocation) 42 | if !ok { 43 | return false 44 | } 45 | 46 | return true 47 | } 48 | 49 | func main() { 50 | log.Println("开始监听转移任务队列") 51 | mq.StartConsume(config.TransOSSQueueName, 52 | "transfer_oss", 53 | ProcessTransfer, 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /service/normal/upload/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "FileStore-Server/config" 5 | "FileStore-Server/handler" 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | // 静态资源处理 12 | http.Handle("/static/", 13 | http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))) 14 | 15 | // 文件相关 16 | http.HandleFunc("/file/upload",handler.HTTPInterceptor(handler.UploadHandler)) 17 | http.HandleFunc("/file/upload/suc",handler.HTTPInterceptor(handler.UploadSucHandler)) 18 | http.HandleFunc("/file/meta",handler.HTTPInterceptor(handler.GetFileMetaHandler)) 19 | http.HandleFunc("/file/download",handler.HTTPInterceptor(handler.DownloadHandler)) 20 | http.HandleFunc("/file/update",handler.HTTPInterceptor(handler.FileMetaUpdateHandler)) 21 | http.HandleFunc("/file/delete",handler.HTTPInterceptor(handler.FileDeleteHandler)) 22 | http.HandleFunc("/file/query",handler.HTTPInterceptor(handler.FileQueryHandler)) 23 | // 秒传接口 24 | http.HandleFunc("/file/fastupload",handler.HTTPInterceptor(handler.TryFastUploadHandler)) 25 | 26 | http.HandleFunc("/file/downloadurl",handler.HTTPInterceptor(handler.DownloadURLHandler)) 27 | 28 | // 分块上传 29 | http.HandleFunc("/file/mpupload/init",handler.HTTPInterceptor(handler.InitialMultipartUploadHandler)) 30 | http.HandleFunc("/file/mpupload/uppart",handler.HTTPInterceptor(handler.UploadPartHandler)) 31 | http.HandleFunc("/file/mpupload/complete",handler.HTTPInterceptor(handler.CompleteUploadHandler)) 32 | 33 | // 用户相关 34 | http.HandleFunc("/", handler.SignInHandler) 35 | http.HandleFunc("/user/signup",handler.SignupHandler) 36 | http.HandleFunc("/user/signin",handler.SignInHandler) 37 | http.HandleFunc("/user/info",handler.HTTPInterceptor(handler.UserInfoHandler)) 38 | 39 | fmt.Printf("上传服务启动中,开始监听监听[%s]...\n", config.UploadServiceHost) 40 | 41 | err := http.ListenAndServe(config.UploadServiceHost,nil) 42 | if err != nil { 43 | fmt.Println("Failed to start server, err: %s",err.Error()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /static/css/fileinput.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * bootstrap-fileinput v4.4.9 3 | * http://plugins.krajee.com/file-input 4 | * 5 | * Krajee default styling for bootstrap-fileinput. 6 | * 7 | * Author: Kartik Visweswaran 8 | * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com 9 | * 10 | * Licensed under the BSD 3-Clause 11 | * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md 12 | */.btn-file input[type=file],.file-caption-icon,.file-no-browse,.file-preview .fileinput-remove,.file-zoom-dialog .btn-navigate,.file-zoom-dialog .floating-buttons,.krajee-default .file-thumb-progress{position:absolute}.file-loading input[type=file],input[type=file].file-loading{width:0;height:0}.file-no-browse{left:50%;bottom:20%;width:1px;height:1px;font-size:0;opacity:0;border:none;background:0 0;outline:0;box-shadow:none}.file-caption-icon,.file-input-ajax-new .fileinput-remove-button,.file-input-ajax-new .fileinput-upload-button,.file-input-ajax-new .no-browse .input-group-btn,.file-input-new .close,.file-input-new .file-preview,.file-input-new .fileinput-remove-button,.file-input-new .fileinput-upload-button,.file-input-new .glyphicon-file,.file-input-new .no-browse .input-group-btn,.file-zoom-dialog .modal-header:after,.file-zoom-dialog .modal-header:before,.hide-content .kv-file-content,.kv-hidden{display:none}.btn-file,.file-caption,.file-input,.file-loading:before,.file-preview,.file-zoom-dialog .modal-dialog,.krajee-default .file-thumbnail-footer,.krajee-default.file-preview-frame{position:relative}.file-error-message pre,.file-error-message ul,.krajee-default .file-actions,.krajee-default .file-other-error{text-align:left}.file-error-message pre,.file-error-message ul{margin:0}.krajee-default .file-drag-handle,.krajee-default .file-upload-indicator{float:left;margin:5px 0 -5px;width:16px;height:16px}.krajee-default .file-thumb-progress .progress,.krajee-default .file-thumb-progress .progress-bar{height:11px;font-family:Verdana,Helvetica,sans-serif;font-size:9px}.krajee-default .file-caption-info,.krajee-default .file-size-info{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:160px;height:15px;margin:auto}.file-zoom-content>.file-object.type-flash,.file-zoom-content>.file-object.type-image,.file-zoom-content>.file-object.type-video{max-width:100%;max-height:100%;width:auto}.file-zoom-content>.file-object.type-flash,.file-zoom-content>.file-object.type-video{height:100%}.file-zoom-content>.file-object.type-default,.file-zoom-content>.file-object.type-html,.file-zoom-content>.file-object.type-pdf,.file-zoom-content>.file-object.type-text{width:100%}.file-loading:before{content:" Loading...";display:inline-block;padding-left:20px;line-height:16px;font-size:13px;font-variant:small-caps;color:#999;background:url(../img/loading.gif) top left no-repeat}.file-object{margin:0 0 -5px;padding:0}.btn-file{overflow:hidden}.btn-file input[type=file]{top:0;left:0;min-width:100%;min-height:100%;text-align:right;opacity:0;background:none;cursor:inherit;display:block}.btn-file ::-ms-browse{font-size:10000px;width:100%;height:100%}.file-caption .file-caption-name{width:100%;margin:0;padding:0;box-shadow:none;border:none;background:0 0;outline:0}.file-caption.icon-visible .file-caption-icon{display:inline-block}.file-caption.icon-visible .file-caption-name{padding-left:15px}.file-caption-icon{left:8px}.file-error-message{color:#a94442;background-color:#f2dede;margin:5px;border:1px solid #ebccd1;border-radius:4px;padding:15px}.file-error-message pre{margin:5px 0}.file-caption-disabled{background-color:#eee;cursor:not-allowed;opacity:1}.file-preview{border-radius:5px;border:1px solid #ddd;padding:8px;width:100%;margin-bottom:5px}.file-preview .btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.file-preview .fileinput-remove{top:1px;right:1px;line-height:10px}.file-preview .clickable{cursor:pointer}.file-preview-image{font:40px Impact,Charcoal,sans-serif;color:green}.krajee-default.file-preview-frame{margin:8px;border:1px solid #ddd;box-shadow:1px 1px 5px 0 #a2958a;padding:6px;float:left;text-align:center}.krajee-default.file-preview-frame .kv-file-content{width:213px;height:160px}.krajee-default.file-preview-frame .kv-file-content.kv-pdf-rendered{width:400px}.krajee-default.file-preview-frame .file-thumbnail-footer{height:70px}.krajee-default.file-preview-frame:not(.file-preview-error):hover{box-shadow:3px 3px 5px 0 #333}.krajee-default .file-preview-text{display:block;color:#428bca;border:1px solid #ddd;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;outline:0;padding:8px;resize:none}.krajee-default .file-preview-html{border:1px solid #ddd;padding:8px;overflow:auto}.krajee-default .file-other-icon{font-size:6em}.krajee-default .file-footer-buttons{float:right}.krajee-default .file-footer-caption{display:block;text-align:center;padding-top:4px;font-size:11px;color:#777;margin-bottom:15px}.krajee-default .file-preview-error{opacity:.65;box-shadow:none}.krajee-default .file-thumb-progress{height:11px;top:37px;left:0;right:0}.krajee-default.kvsortable-ghost{background:#e1edf7;border:2px solid #a1abff}.krajee-default .file-preview-other:hover{opacity:.8}.krajee-default .file-preview-frame:not(.file-preview-error) .file-footer-caption:hover{color:#000}.kv-upload-progress .progress{height:20px;margin:10px 0;overflow:hidden}.kv-upload-progress .progress-bar{height:20px;font-family:Verdana,Helvetica,sans-serif}.file-zoom-dialog .file-other-icon{font-size:22em;font-size:50vmin}.file-zoom-dialog .modal-dialog{width:auto}.file-zoom-dialog .modal-header{display:flex;align-items:center;justify-content:space-between}.file-zoom-dialog .btn-navigate{padding:0;margin:0;background:0 0;text-decoration:none;outline:0;opacity:.7;top:45%;font-size:4em;color:#1c94c4}.file-zoom-dialog .btn-navigate:not([disabled]):hover{outline:0;box-shadow:none;opacity:.6}.file-zoom-dialog .floating-buttons{top:5px;right:10px}.file-zoom-dialog .btn-navigate[disabled]{opacity:.3}.file-zoom-dialog .btn-prev{left:1px}.file-zoom-dialog .btn-next{right:1px}.file-zoom-dialog .kv-zoom-title{font-weight:300;color:#999;max-width:50%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.file-input-ajax-new .no-browse .form-control,.file-input-new .no-browse .form-control{border-top-right-radius:4px;border-bottom-right-radius:4px}.file-caption-main{width:100%}.file-thumb-loading{background:url(../img/loading.gif) center center no-repeat content-box!important}.file-drop-zone{border:1px dashed #aaa;border-radius:4px;height:100%;text-align:center;vertical-align:middle;margin:12px 15px 12px 12px;padding:5px}.file-drop-zone.clickable:hover{border:2px dashed #999}.file-drop-zone.clickable:focus{border:2px solid #5acde2}.file-drop-zone .file-preview-thumbnails{cursor:default}.file-drop-zone-title{color:#aaa;font-size:1.6em;padding:85px 10px;cursor:default}.file-highlighted{border:2px dashed #999!important;background-color:#eee}.file-uploading{background:url(../img/loading-sm.gif) center bottom 10px no-repeat;opacity:.65}@media (min-width:576px){.file-zoom-dialog .modal-dialog{max-width:500px}}@media (min-width:992px){.file-zoom-dialog .modal-lg{max-width:800px}}.file-zoom-fullscreen.modal{position:fixed;top:0;right:0;bottom:0;left:0}.file-zoom-fullscreen .modal-dialog{position:fixed;margin:0;padding:0;width:100%;height:100%;max-width:100%;max-height:100%}.file-zoom-fullscreen .modal-content{border-radius:0;box-shadow:none}.file-zoom-fullscreen .modal-body{overflow-y:auto}.floating-buttons{z-index:3000}.floating-buttons .btn-kv{margin-left:3px;z-index:3000}.file-zoom-content{height:480px;text-align:center}.file-zoom-content .file-preview-image,.file-zoom-content .file-preview-video{max-height:100%}.file-zoom-content>.file-object.type-image{height:auto;min-height:inherit}.file-zoom-content>.file-object.type-audio{width:auto;height:30px}@media screen and (max-width:767px){.file-preview-thumbnails{display:flex;justify-content:center;align-items:center;flex-direction:column}.file-zoom-dialog .modal-header{flex-direction:column}}@media screen and (max-width:350px){.krajee-default.file-preview-frame .kv-file-content{width:160px}}.file-loading[dir=rtl]:before{background:url(../img/loading.gif) top right no-repeat;padding-left:0;padding-right:20px}.file-sortable .file-drag-handle{cursor:move;opacity:1}.file-sortable .file-drag-handle:hover{opacity:.7}.clickable .file-drop-zone-title{cursor:pointer}.kv-zoom-actions .btn-kv{margin-left:3px}.file-preview-initial.sortable-chosen{background-color:#d9edf7} -------------------------------------------------------------------------------- /static/img/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanChunStone/FileServer-Golang/06041052219136367131df818fcf0ea5366b2a9b/static/img/avatar.jpeg -------------------------------------------------------------------------------- /static/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanChunStone/FileServer-Golang/06041052219136367131df818fcf0ea5366b2a9b/static/img/loading.gif -------------------------------------------------------------------------------- /static/js/FileSaver.js: -------------------------------------------------------------------------------- 1 | /* 2 | * FileSaver.js 3 | * A saveAs() FileSaver implementation. 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * 7 | * License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) 8 | * source : http://purl.eligrey.com/github/FileSaver.js 9 | */ 10 | 11 | 12 | // The one and only way of getting global scope in all environments 13 | // https://stackoverflow.com/q/3277182/1008999 14 | var _global = typeof window === 'object' && window.window === window ? 15 | window : typeof self === 'object' && self.self === self ? 16 | self : typeof global === 'object' && global.global === global ? 17 | global : 18 | this 19 | 20 | function bom(blob, opts) { 21 | if (typeof opts === 'undefined') opts = { 22 | autoBom: false 23 | } 24 | else if (typeof opts !== 'object') { 25 | console.warn('Deprecated: Expected third argument to be a object') 26 | opts = { 27 | autoBom: !opts 28 | } 29 | } 30 | 31 | // prepend BOM for UTF-8 XML and text/* types (including HTML) 32 | // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF 33 | if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { 34 | return new Blob([String.fromCharCode(0xFEFF), blob], { 35 | type: blob.type 36 | }) 37 | } 38 | return blob 39 | } 40 | 41 | function download(url, name, opts) { 42 | var xhr = new XMLHttpRequest() 43 | xhr.open('GET', url) 44 | xhr.responseType = 'blob' 45 | xhr.onload = function () { 46 | saveAs(xhr.response, name, opts) 47 | } 48 | xhr.onerror = function () { 49 | console.error('could not download file') 50 | } 51 | xhr.send() 52 | } 53 | 54 | function corsEnabled(url) { 55 | var xhr = new XMLHttpRequest() 56 | // use sync to avoid popup blocker 57 | xhr.open('HEAD', url, false) 58 | xhr.send() 59 | return xhr.status >= 200 && xhr.status <= 299 60 | } 61 | 62 | // `a.click()` doesn't work for all browsers (#465) 63 | function click(node) { 64 | try { 65 | node.dispatchEvent(new MouseEvent('click')) 66 | } catch (e) { 67 | var evt = document.createEvent('MouseEvents') 68 | evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, 69 | 20, false, false, false, false, 0, null) 70 | node.dispatchEvent(evt) 71 | } 72 | } 73 | 74 | var saveAs = _global.saveAs || ( 75 | // probably in some web worker 76 | (typeof window !== 'object' || window !== _global) ? 77 | function saveAs() { 78 | /* noop */ } 79 | 80 | // Use download attribute first if possible (#193 Lumia mobile) 81 | : 82 | 'download' in HTMLAnchorElement.prototype ? 83 | function saveAs(blob, name, opts) { 84 | var URL = _global.URL || _global.webkitURL 85 | var a = document.createElement('a') 86 | name = name || blob.name || 'download' 87 | 88 | a.download = name 89 | a.rel = 'noopener' // tabnabbing 90 | 91 | // TODO: detect chrome extensions & packaged apps 92 | // a.target = '_blank' 93 | 94 | if (typeof blob === 'string') { 95 | // Support regular links 96 | a.href = blob 97 | if (a.origin !== location.origin) { 98 | corsEnabled(a.href) ? 99 | download(blob, name, opts) : 100 | click(a, a.target = '_blank') 101 | } else { 102 | click(a) 103 | } 104 | } else { 105 | // Support blobs 106 | a.href = URL.createObjectURL(blob) 107 | setTimeout(function () { 108 | URL.revokeObjectURL(a.href) 109 | }, 4E4) // 40s 110 | setTimeout(function () { 111 | click(a) 112 | }, 0) 113 | } 114 | } 115 | 116 | // Use msSaveOrOpenBlob as a second approach 117 | : 118 | 'msSaveOrOpenBlob' in navigator ? 119 | function saveAs(blob, name, opts) { 120 | name = name || blob.name || 'download' 121 | 122 | if (typeof blob === 'string') { 123 | if (corsEnabled(blob)) { 124 | download(blob, name, opts) 125 | } else { 126 | var a = document.createElement('a') 127 | a.href = blob 128 | a.target = '_blank' 129 | setTimeout(function () { 130 | click(a) 131 | }) 132 | } 133 | } else { 134 | navigator.msSaveOrOpenBlob(bom(blob, opts), name) 135 | } 136 | } 137 | 138 | // Fallback to using FileReader and a popup 139 | : 140 | function saveAs(blob, name, opts, popup) { 141 | // Open a popup immediately do go around popup blocker 142 | // Mostly only available on user interaction and the fileReader is async so... 143 | popup = popup || open('', '_blank') 144 | if (popup) { 145 | popup.document.title = 146 | popup.document.body.innerText = 'downloading...' 147 | } 148 | 149 | if (typeof blob === 'string') return download(blob, name, opts) 150 | 151 | var force = blob.type === 'application/octet-stream' 152 | var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari 153 | var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent) 154 | 155 | if ((isChromeIOS || (force && isSafari)) && typeof FileReader === 'object') { 156 | // Safari doesn't allow downloading of blob URLs 157 | var reader = new FileReader() 158 | reader.onloadend = function () { 159 | var url = reader.result 160 | url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;') 161 | if (popup) popup.location.href = url 162 | else location = url 163 | popup = null // reverse-tabnabbing #460 164 | } 165 | reader.readAsDataURL(blob) 166 | } else { 167 | var URL = _global.URL || _global.webkitURL 168 | var url = URL.createObjectURL(blob) 169 | if (popup) popup.location = url 170 | else location.href = url 171 | popup = null // reverse-tabnabbing #460 172 | setTimeout(function () { 173 | URL.revokeObjectURL(url) 174 | }, 4E4) // 40s 175 | } 176 | } 177 | ) 178 | 179 | _global.saveAs = saveAs.saveAs = saveAs 180 | 181 | if (typeof module !== 'undefined') { 182 | module.exports = saveAs; 183 | } -------------------------------------------------------------------------------- /static/js/StreamSaver.js: -------------------------------------------------------------------------------- 1 | /* global location WritableStream ReadableStream define MouseEvent MessageChannel TransformStream */ ; 2 | ((name, definition) => { 3 | typeof module !== 'undefined' ? 4 | module.exports = definition() : 5 | typeof define === 'function' && typeof define.amd === 'object' ? 6 | define(definition) : 7 | this[name] = definition() 8 | })('streamSaver', () => { 9 | 'use strict' 10 | 11 | const secure = location.protocol === 'https:' || 12 | location.protocol === 'chrome-extension:' || 13 | location.hostname === 'localhost' 14 | let iframe 15 | let loaded 16 | let transfarableSupport = false 17 | let streamSaver = { 18 | createWriteStream, 19 | supported: false, 20 | version: { 21 | full: '1.2.0', 22 | major: 1, 23 | minor: 2, 24 | dot: 0 25 | } 26 | } 27 | 28 | streamSaver.mitm = 'https://jimmywarting.github.io/StreamSaver.js/mitm.html?version=' + 29 | streamSaver.version.full 30 | 31 | try { 32 | // Some browser has it but ain't allowed to construct a stream yet 33 | streamSaver.supported = 'serviceWorker' in navigator && !!new ReadableStream() 34 | } catch (err) {} 35 | 36 | try { 37 | const { 38 | readable 39 | } = new TransformStream() 40 | const mc = new MessageChannel() 41 | mc.port1.postMessage(readable, [readable]) 42 | mc.port1.close() 43 | mc.port2.close() 44 | transfarableSupport = readable.locked === true 45 | } catch (err) { 46 | // Was first enabled in chrome v73 47 | } 48 | 49 | function createWriteStream(filename, queuingStrategy, size) { 50 | // normalize arguments 51 | if (Number.isFinite(queuingStrategy)) { 52 | [size, queuingStrategy] = [queuingStrategy, size] 53 | } 54 | 55 | let channel = new MessageChannel() 56 | let popup 57 | let setupChannel = readableStream => new Promise(resolve => { 58 | const args = [{ 59 | filename, 60 | size 61 | }, '*', [channel.port2]] 62 | 63 | // Pass along transfarable stream 64 | if (readableStream) { 65 | args[0].readableStream = readableStream 66 | args[2].push(readableStream) 67 | } 68 | 69 | channel.port1.onmessage = evt => { 70 | // Service worker sent us a link from where 71 | // we recive the readable link (stream) 72 | if (evt.data.download) { 73 | resolve() // Signal that the writestream are ready to recive data 74 | if (!secure) popup.close() // don't need the popup any longer 75 | if (window.chrome && chrome.extension && 76 | chrome.extension.getBackgroundPage && 77 | chrome.extension.getBackgroundPage() === window) { 78 | chrome.tabs.create({ 79 | url: evt.data.download, 80 | active: false 81 | }) 82 | } else { 83 | window.location = evt.data.download 84 | } 85 | 86 | // Cleanup 87 | if (readableStream) { 88 | // We don't need postMessages now when stream are transferable 89 | channel.port1.close() 90 | channel.port2.close() 91 | } 92 | 93 | channel.port1.onmessage = null 94 | } 95 | } 96 | 97 | if (secure && !iframe) { 98 | iframe = document.createElement('iframe') 99 | iframe.src = streamSaver.mitm 100 | iframe.hidden = true 101 | document.body.appendChild(iframe) 102 | } 103 | 104 | if (secure && !loaded) { 105 | let fn 106 | iframe.addEventListener('load', fn = () => { 107 | loaded = true 108 | iframe.removeEventListener('load', fn) 109 | iframe.contentWindow.postMessage(...args) 110 | }) 111 | } 112 | 113 | if (secure && loaded) { 114 | iframe.contentWindow.postMessage(...args) 115 | } 116 | 117 | if (!secure) { 118 | popup = window.open(streamSaver.mitm, Math.random()) 119 | let onready = evt => { 120 | if (evt.source === popup) { 121 | popup.postMessage(...args) 122 | window.removeEventListener('message', onready) 123 | } 124 | } 125 | 126 | // Another problem that cross origin don't allow is scripting 127 | // so popup.onload() don't work but postMessage still dose 128 | // work cross origin 129 | window.addEventListener('message', onready) 130 | } 131 | }) 132 | 133 | if (transfarableSupport) { 134 | const ts = new TransformStream({ 135 | start() { 136 | return new Promise(resolve => 137 | setTimeout(() => setupChannel(ts.readable).then(resolve)) 138 | ) 139 | } 140 | }, queuingStrategy) 141 | 142 | return ts.writable 143 | } 144 | 145 | return new WritableStream({ 146 | start() { 147 | // is called immediately, and should perform any actions 148 | // necessary to acquire access to the underlying sink. 149 | // If this process is asynchronous, it can return a promise 150 | // to signal success or failure. 151 | return setupChannel() 152 | }, 153 | write(chunk) { 154 | // is called when a new chunk of data is ready to be written 155 | // to the underlying sink. It can return a promise to signal 156 | // success or failure of the write operation. The stream 157 | // implementation guarantees that this method will be called 158 | // only after previous writes have succeeded, and never after 159 | // close or abort is called. 160 | 161 | // TODO: Kind of important that service worker respond back when 162 | // it has been written. Otherwise we can't handle backpressure 163 | // EDIT: Transfarable streams solvs this... 164 | channel.port1.postMessage(chunk) 165 | }, 166 | close() { 167 | channel.port1.postMessage('end') 168 | }, 169 | abort() { 170 | channel.port1.postMessage('abort') 171 | } 172 | }, queuingStrategy) 173 | } 174 | 175 | return streamSaver 176 | }) -------------------------------------------------------------------------------- /static/js/auth.js: -------------------------------------------------------------------------------- 1 | function queryParams() { 2 | var username = localStorage.getItem("username"); 3 | var token = localStorage.getItem("token"); 4 | return 'username=' + username + '&token=' + token; 5 | } 6 | 7 | String.prototype.format = function (args) { 8 | var result = this; 9 | if (arguments.length > 0) { 10 | if (arguments.length == 1 && typeof (args) == "object") { 11 | for (var key in args) { 12 | if (args[key] != undefined) { 13 | var reg = new RegExp("({" + key + "})", "g"); 14 | result = result.replace(reg, args[key]); 15 | } 16 | } 17 | } else { 18 | for (var i = 0; i < arguments.length; i++) { 19 | if (arguments[i] != undefined) { 20 | var reg = new RegExp("({)" + i + "(})", "g"); 21 | result = result.replace(reg, arguments[i]); 22 | } 23 | } 24 | } 25 | } 26 | return result; 27 | } -------------------------------------------------------------------------------- /static/js/layui.js: -------------------------------------------------------------------------------- 1 | /** layui-v2.4.5 MIT License By https://www.layui.com */ 2 | ;!function(e){"use strict";var t=document,o={modules:{},status:{},timeout:10,event:{}},n=function(){this.v="2.4.5"},r=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,o=t.scripts,n=o.length-1,r=n;r>0;r--)if("interactive"===o[r].readyState){e=o[r].src;break}return e||o[n].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),i=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},a="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",tree:"modules/tree",table:"modules/table",element:"modules/element",rate:"modules/rate",colorpicker:"modules/colorpicker",slider:"modules/slider",carousel:"modules/carousel",flow:"modules/flow",util:"modules/util",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};n.prototype.cache=o,n.prototype.define=function(e,t){var n=this,r="function"==typeof e,i=function(){var e=function(e,t){layui[e]=t,o.status[e]=!0};return"function"==typeof t&&t(function(n,r){e(n,r),o.callback[n]=function(){t(e)}}),this};return r&&(t=e,e=[]),layui["layui.all"]||!layui["layui.all"]&&layui["layui.mobile"]?i.call(n):(n.use(e,i),n)},n.prototype.use=function(e,n,l){function s(e,t){var n="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||n.test((e.currentTarget||e.srcElement).readyState))&&(o.modules[f]=t,d.removeChild(v),function r(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void(o.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),n,l):"function"==typeof n&&n.apply(layui,l)}var y=this,p=o.dir=o.dir?o.dir:r,d=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,o){"jquery"===o&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],o.host=o.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[f]||!layui["layui.all"]&&layui["layui.mobile"]&&u[f])return c(),y;if(o.modules[f])!function g(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void("string"==typeof o.modules[f]&&o.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[f]?p+"lay/":/^\{\/\}/.test(y.modules[f])?"":o.base||"")+(y.modules[f]||f)+".js";h=h.replace(/^\{\/\}/,""),v.async=!0,v.charset="utf-8",v.src=h+function(){var e=o.version===!0?o.v||(new Date).getTime():o.version||"";return e?"?v="+e:""}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||a?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),o.modules[f]=h}return y},n.prototype.getStyle=function(t,o){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](o)},n.prototype.link=function(e,n,r){var a=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof n&&(r=n);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(o.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof n?a:(function p(){return++y>1e3*o.timeout/100?i(e+" timeout"):void(1989===parseInt(a.getStyle(t.getElementById(c),"width"))?function(){n()}():setTimeout(p,100))}(),a)},o.callback={},n.prototype.factory=function(e){if(layui[e])return"function"==typeof o.callback[e]?o.callback[e]:null},n.prototype.addcss=function(e,t,n){return layui.link(o.dir+"css/"+e,t,n)},n.prototype.img=function(e,t,o){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,"function"==typeof t&&t(n)},void(n.onerror=function(e){n.onerror=null,"function"==typeof o&&o(e)}))},n.prototype.config=function(e){e=e||{};for(var t in e)o[t]=e[t];return this},n.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),n.prototype.extend=function(e){var t=this;e=e||{};for(var o in e)t[o]||t.modules[o]?i("模块名 "+o+" 已被占用"):t.modules[o]=e[o];return t},n.prototype.router=function(e){var t=this,e=e||location.hash,o={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,""),o.href="/"+e,e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),o.search[t[0]]=t[1]}():o.path.push(t)}),o):o},n.prototype.data=function(t,o,n){if(t=t||"layui",n=n||localStorage,e.JSON&&e.JSON.parse){if(null===o)return delete n[t];o="object"==typeof o?o:{key:o};try{var r=JSON.parse(n[t])}catch(i){var r={}}return"value"in o&&(r[o.key]=o.value),o.remove&&delete r[o.key],n[t]=JSON.stringify(r),o.key?r[o.key]:r}},n.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},n.prototype.device=function(t){var o=navigator.userAgent.toLowerCase(),n=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(o.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(o)?"windows":/linux/.test(o)?"linux":/iphone|ipod|ipad|ios/.test(o)?"ios":/mac/.test(o)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((o.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:n("micromessenger")};return t&&!r[t]&&(r[t]=n(t)),r.android=/android/.test(o),r.ios="ios"===r.os,r},n.prototype.hint=function(){return{error:i}},n.prototype.each=function(e,t){var o,n=this;if("function"!=typeof t)return n;if(e=e||[],e.constructor===Object){for(o in e)if(t.call(e[o],o,e[o]))break}else for(o=0;oi?1:r { 8 | // We send a heartbeat every x secound to keep the 9 | // service worker alive 10 | if (event.data === 'ping') { 11 | return 12 | } 13 | 14 | // Create a uniq link for the download 15 | const uniqLink = self.registration.scope + 'intercept-me-nr' + Math.random() 16 | const port = event.ports[0] 17 | 18 | const stream = event.data.readableStream || createStream(port) 19 | map.set(uniqLink, [stream, event.data]) 20 | port.postMessage({ 21 | download: uniqLink, 22 | ping: self.registration.scope + 'ping' 23 | }) 24 | 25 | // Mistage adding this and have streamsaver.js rely on it 26 | // depricated as from 0.2.1 27 | port.postMessage({ 28 | debug: 'Mocking a download request' 29 | }) 30 | } 31 | 32 | function createStream(port) { 33 | // ReadableStream is only supported by chrome 52 34 | return new ReadableStream({ 35 | start(controller) { 36 | // When we receive data on the messageChannel, we write 37 | port.onmessage = ({ 38 | data 39 | }) => { 40 | if (data === 'end') { 41 | return controller.close() 42 | } 43 | 44 | if (data === 'abort') { 45 | controller.error('Aborted the download') 46 | return 47 | } 48 | 49 | controller.enqueue(data) 50 | } 51 | }, 52 | cancel() { 53 | console.log('user aborted') 54 | } 55 | }) 56 | } 57 | 58 | self.onfetch = event => { 59 | const url = event.request.url 60 | 61 | if (url.endsWith('/ping')) { 62 | return event.respondWith(new Response('pong', { 63 | headers: { 64 | 'Access-Control-Allow-Origin': '*' 65 | } 66 | })) 67 | } 68 | 69 | const hijacke = map.get(url) 70 | 71 | if (!hijacke) return null 72 | 73 | const [stream, data] = hijacke 74 | 75 | map.delete(url) 76 | 77 | // Make filename RFC5987 compatible 78 | const filename = encodeURIComponent(typeof data === 'string' ? data : data.filename) 79 | .replace(/['()]/g, escape) 80 | .replace(/\*/g, '%2A') 81 | 82 | const headers = { 83 | 'Content-Type': 'application/octet-stream; charset=utf-8', 84 | 'Content-Disposition': "attachment; filename*=UTF-8''" + filename 85 | } 86 | 87 | if (data.size) headers['Content-Length'] = data.size 88 | 89 | event.respondWith(new Response(stream, { 90 | headers 91 | })) 92 | } -------------------------------------------------------------------------------- /static/js/theme.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bootstrap-fileinput v4.4.9 3 | * http://plugins.krajee.com/file-input 4 | * 5 | * Font Awesome icon theme configuration for bootstrap-fileinput. Requires font awesome assets to be loaded. 6 | * 7 | * Author: Kartik Visweswaran 8 | * Copyright: 2014 - 2018, Kartik Visweswaran, Krajee.com 9 | * 10 | * Licensed under the BSD 3-Clause 11 | * https://github.com/kartik-v/bootstrap-fileinput/blob/master/LICENSE.md 12 | */ 13 | (function ($) { 14 | "use strict"; 15 | 16 | $.fn.fileinputThemes.fa = { 17 | fileActionSettings: { 18 | removeIcon: '', 19 | uploadIcon: '', 20 | uploadRetryIcon: '', 21 | downloadIcon: '', 22 | zoomIcon: '', 23 | dragIcon: '', 24 | indicatorNew: '', 25 | indicatorSuccess: '', 26 | indicatorError: '', 27 | indicatorLoading: '' 28 | }, 29 | layoutTemplates: { 30 | fileIcon: ' ' 31 | }, 32 | previewZoomButtonIcons: { 33 | prev: '', 34 | next: '', 35 | toggleheader: '', 36 | fullscreen: '', 37 | borderless: '', 38 | close: '' 39 | }, 40 | previewFileIcon: '', 41 | browseIcon: '', 42 | removeIcon: '', 43 | cancelIcon: '', 44 | uploadIcon: '', 45 | msgValidationErrorIcon: ' ' 46 | }; 47 | })(window.jQuery); 48 | -------------------------------------------------------------------------------- /static/view/download.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 105 | 106 | 107 | 108 |
[下载进度] 0%
109 | 111 | -------------------------------------------------------------------------------- /static/view/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
27 | 文件云盘首页 28 |
29 | 30 | 31 | 32 | 40 | 43 | 65 | 66 | 67 |
33 |
34 |
35 | 用户名:

36 | 注册时间:

37 |
38 |
39 |
41 |
42 |
44 |
45 | 文件列表 46 | 48 |
49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
文件hash文件名文件大小上传时间最近更新操作
63 |
64 |
68 |
69 | 70 | 71 | 234 | 235 | -------------------------------------------------------------------------------- /static/view/signin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 12 | 13 | 14 | 17 | 18 | 19 | 20 |
21 |
22 |
用户登录
23 |
24 | 25 | 26 | 27 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 59 | 60 | 61 |
28 | * 29 | 30 | 32 | 33 |

41 | * 42 | 43 | 45 | 46 |

54 | 56 | 58 |
62 |
63 |
64 | 65 | 66 | 92 | 93 | -------------------------------------------------------------------------------- /static/view/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 12 | 13 | 14 | 17 | 19 | 20 | 21 | 22 |
23 |
24 |
用户注册
25 |
26 | 27 | 28 | 29 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 70 | 75 | 76 | 77 |
30 | * 31 | 32 | 34 | 35 |

43 | * 44 | 45 | 47 | 48 |

56 | * 57 | 58 | 60 | 61 |

69 | 71 | 73 | 74 |
78 |
79 |
80 | 81 | 82 | 108 | 109 | -------------------------------------------------------------------------------- /static/view/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 21 | 22 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
37 | 文件上传 38 |
39 |
40 |
41 | 42 | 43 | 44 |
45 |
46 |
47 | 48 | 49 | 87 | 88 | -------------------------------------------------------------------------------- /store/oss/oss_conn.go: -------------------------------------------------------------------------------- 1 | package oss 2 | 3 | import ( 4 | cfg "FileStore-Server/config" 5 | "fmt" 6 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 7 | ) 8 | 9 | var ossCli *oss.Client 10 | 11 | // Client: 创建oss client对象 12 | func Client() *oss.Client { 13 | if ossCli != nil { 14 | return ossCli 15 | } 16 | ossCli, err := oss.New(cfg.OSSEndpoint, 17 | cfg.OSSAccesskeyID, cfg.OSSAccessKeySecret) 18 | if err != nil { 19 | fmt.Println(err.Error()) 20 | return nil 21 | } 22 | return ossCli 23 | } 24 | 25 | //Bucket: 获取bucket存储空间 26 | func Bucket() *oss.Bucket { 27 | cli := Client() 28 | if cli != nil { 29 | bucket,err := cli.Bucket(cfg.OSSBucket) 30 | if err != nil { 31 | fmt.Println(err.Error()) 32 | return nil 33 | } 34 | return bucket 35 | } 36 | return nil 37 | } 38 | 39 | //DownloadURL: 获取临时授权下载URL 40 | func DownloadURL(objName string) string { 41 | signedURL,err := Bucket().SignURL(objName,oss.HTTPGet,3600) 42 | if err != nil { 43 | fmt.Println(err.Error()) 44 | return "" 45 | } 46 | return signedURL 47 | } -------------------------------------------------------------------------------- /test/test_ceph.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "FileStore-Server/handler" 5 | "fmt" 6 | ) 7 | 8 | // 9 | //import ( 10 | // "filestore-server/store/ceph" 11 | // "fmt" 12 | // "os" 13 | //) 14 | // 15 | //func main() { 16 | // bucket := ceph.GetCephBucket("userfile") 17 | // 18 | // d, _ := bucket.Get("/ceph/866cc7c87c9b612dd8904d2c5dd07d6f6c22b834") 19 | // tmpFile, _ := os.Create("/tmp/test_file") 20 | // tmpFile.Write(d) 21 | // return 22 | // 23 | // // // 创建一个新的bucket 24 | // // err := bucket.PutBucket(s3.PublicRead) 25 | // // fmt.Printf("create bucket err: %v\n", err) 26 | // 27 | // // 查询这个bucket下面指定条件的object keys 28 | // res, _ := bucket.List("", "", "", 100) 29 | // fmt.Printf("object keys: %+v\n", res) 30 | // 31 | // // // 新上传一个对象 32 | // // err = bucket.Put("/testupload/a.txt", []byte("just for test"), "octet-stream", s3.PublicRead) 33 | // // fmt.Printf("upload err: %+v\n", err) 34 | // 35 | // // // 查询这个bucket下面指定条件的object keys 36 | // // res, err = bucket.List("", "", "", 100) 37 | // // fmt.Printf("object keys: %+v\n", res) 38 | //} 39 | 40 | func main(){ 41 | fmt.Println(handler.GenToken("danchun")) 42 | } -------------------------------------------------------------------------------- /test/test_mpupload.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "strconv" 13 | 14 | jsonit "github.com/json-iterrator/go" 15 | ) 16 | 17 | func multipartUpload(filename string, targetURL string, chunkSize int) error { 18 | f, err := os.Open(filename) 19 | if err != nil { 20 | fmt.Println(err) 21 | return err 22 | } 23 | defer f.Close() 24 | 25 | bfRd := bufio.NewReader(f) 26 | index := 0 27 | 28 | ch := make(chan int) 29 | buf := make([]byte, chunkSize) //每次读取chunkSize大小的内容 30 | for { 31 | n, err := bfRd.Read(buf) 32 | if n <= 0 { 33 | break 34 | } 35 | index++ 36 | 37 | bufCopied := make([]byte, 5*1048576) 38 | copy(bufCopied, buf) 39 | 40 | go func(b []byte, curIdx int) { 41 | fmt.Printf("upload_size: %d\n", len(b)) 42 | 43 | resp, err := http.Post( 44 | targetURL+"&index="+strconv.Itoa(curIdx), 45 | "multipart/form-data", 46 | bytes.NewReader(b)) 47 | if err != nil { 48 | fmt.Println(err) 49 | } 50 | 51 | body, er := ioutil.ReadAll(resp.Body) 52 | fmt.Printf("%+v %+v\n", string(body), er) 53 | resp.Body.Close() 54 | 55 | ch <- curIdx 56 | }(bufCopied[:n], index) 57 | 58 | //遇到任何错误立即返回,并忽略 EOF 错误信息 59 | if err != nil { 60 | if err == io.EOF { 61 | break 62 | } else { 63 | fmt.Println(err.Error()) 64 | } 65 | } 66 | } 67 | 68 | for idx := 0; idx < index; idx++ { 69 | select { 70 | case res := <-ch: 71 | fmt.Println(res) 72 | } 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func main() { 79 | username := "danchun" 80 | token := "adca31a37aff92e3aa7d41da39f049915d2f1ae0" 81 | filehash := "dfa39cac093a7a9c94d25130671ec474d51a2995" 82 | 83 | // 1. 请求初始化分块上传接口 84 | resp, err := http.PostForm( 85 | "http://localhost:8000/file/mpupload/init", 86 | url.Values{ 87 | "username": {username}, 88 | "token": {token}, 89 | "filehash": {filehash}, 90 | "filesize": {"24435869"}, 91 | }) 92 | 93 | if err != nil { 94 | fmt.Println(err.Error()) 95 | os.Exit(-1) 96 | } 97 | 98 | defer resp.Body.Close() 99 | body, err := ioutil.ReadAll(resp.Body) 100 | if err != nil { 101 | fmt.Println(err.Error()) 102 | os.Exit(-1) 103 | } 104 | 105 | // 2. 得到uploadID以及服务端指定的分块大小chunkSize 106 | uploadID := jsonit.Get(body, "data").Get("UploadID").ToString() 107 | chunkSize := jsonit.Get(body, "data").Get("ChunkSize").ToInt() 108 | fmt.Printf("uploadid: %s chunksize: %d\n", uploadID, chunkSize) 109 | 110 | // 3. 请求分块上传接口 111 | filename := "F:\\1" 112 | tURL := "http://localhost:8000/file/mpupload/uppart?" + 113 | "username="+username+"&token=" + token + "&uploadid=" + uploadID 114 | multipartUpload(filename, tURL, chunkSize) 115 | 116 | // 4. 请求分块完成接口 117 | resp, err = http.PostForm( 118 | "http://localhost:8000/file/mpupload/complete", 119 | url.Values{ 120 | "username": {username}, 121 | "token": {token}, 122 | "filehash": {filehash}, 123 | "filesize": {"24435869"}, 124 | "filename": {`F:\1`}, 125 | "uploadid": {uploadID}, 126 | }) 127 | 128 | if err != nil { 129 | fmt.Println(err.Error()) 130 | os.Exit(-1) 131 | } 132 | 133 | defer resp.Body.Close() 134 | body, err = ioutil.ReadAll(resp.Body) 135 | if err != nil { 136 | fmt.Println(err.Error()) 137 | os.Exit(-1) 138 | } 139 | fmt.Printf("complete result: %s\n", string(body)) 140 | } 141 | -------------------------------------------------------------------------------- /tree.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanChunStone/FileServer-Golang/06041052219136367131df818fcf0ea5366b2a9b/tree.md -------------------------------------------------------------------------------- /util/resp.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | ) 8 | 9 | // RespMsg: http响应数据的通用结构 10 | type RespMsg struct { 11 | Code int `json:"code"` 12 | Msg string `json:"msg"` 13 | Data interface{} `json:"data"` 14 | } 15 | 16 | // NewRespMsg: 生成response对象 17 | func NewRespMsg(code int, msg string, data interface{}) *RespMsg { 18 | return &RespMsg{ 19 | Code: code, 20 | Msg: msg, 21 | Data: data, 22 | } 23 | } 24 | 25 | // JSONBytes: 对象转json格式的二进制数组 26 | func (resp *RespMsg) JSONBytes() []byte { 27 | r, err := json.Marshal(resp) 28 | if err != nil { 29 | log.Println(err) 30 | } 31 | return r 32 | } 33 | 34 | // JSONString: 对象转json格式的string 35 | func (resp *RespMsg) JSONString() string { 36 | r, err := json.Marshal(resp) 37 | if err != nil { 38 | log.Println(err) 39 | } 40 | return string(r) 41 | } 42 | 43 | // GenSimpleRespStream: 只包含code和message的响应体([]byte) 44 | func GenSimpleRespStream(code int, msg string) []byte { 45 | return []byte(fmt.Sprintf(`{"code":%d,"msg":"%s"}`, code, msg)) 46 | } 47 | 48 | // GenSimpleRespString: 只包含code和message的响应体(string) 49 | func GenSimpleRespString(code int, msg string) string { 50 | return fmt.Sprintf(`{"code":%d,"msg":"%s"}`, code, msg) 51 | } -------------------------------------------------------------------------------- /util/shell.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "os/exec" 6 | ) 7 | 8 | // 执行 linux shell command 9 | func ExecLinuxShell(s string) (string, error) { 10 | //函数返回一个io.Writer类型的*Cmd 11 | cmd := exec.Command("/bin/bash", "-c", s) 12 | 13 | //通过bytes.Buffer将byte类型转化为string类型 14 | var result bytes.Buffer 15 | cmd.Stdout = &result 16 | 17 | //Run执行cmd包含的命令,并阻塞直至完成 18 | err := cmd.Run() 19 | if err != nil { 20 | return "", err 21 | } 22 | 23 | return result.String(), err 24 | } 25 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha1" 6 | "encoding/hex" 7 | "hash" 8 | "io" 9 | "os" 10 | "path/filepath" 11 | ) 12 | 13 | type Sha1Stream struct { 14 | _sha1 hash.Hash 15 | } 16 | 17 | func (obj *Sha1Stream) Update(data []byte) { 18 | if obj._sha1 == nil { 19 | obj._sha1 = sha1.New() 20 | } 21 | obj._sha1.Write(data) 22 | } 23 | 24 | func (obj *Sha1Stream) Sum() string { 25 | return hex.EncodeToString(obj._sha1.Sum([]byte(""))) 26 | } 27 | 28 | func Sha1(data []byte) string { 29 | _sha1 := sha1.New() 30 | _sha1.Write(data) 31 | return hex.EncodeToString(_sha1.Sum([]byte(""))) 32 | } 33 | 34 | func FileSha1(file *os.File) string { 35 | _sha1 := sha1.New() 36 | io.Copy(_sha1, file) 37 | return hex.EncodeToString(_sha1.Sum(nil)) 38 | } 39 | 40 | func MD5(data []byte) string { 41 | _md5 := md5.New() 42 | _md5.Write(data) 43 | return hex.EncodeToString(_md5.Sum([]byte(""))) 44 | } 45 | 46 | func FileMD5(file *os.File) string { 47 | _md5 := md5.New() 48 | io.Copy(_md5, file) 49 | return hex.EncodeToString(_md5.Sum(nil)) 50 | } 51 | 52 | func PathExists(path string) (bool, error) { 53 | _, err := os.Stat(path) 54 | if err == nil { 55 | return true, nil 56 | } 57 | if os.IsNotExist(err) { 58 | return false, nil 59 | } 60 | return false, err 61 | } 62 | 63 | func GetFileSize(filename string) int64 { 64 | var result int64 65 | filepath.Walk(filename, func(path string, f os.FileInfo, err error) error { 66 | result = f.Size() 67 | return nil 68 | }) 69 | return result 70 | } 71 | --------------------------------------------------------------------------------