diff --git a/code/core/application.go b/code/core/application.go new file mode 100644 index 0000000..56899fa --- /dev/null +++ b/code/core/application.go @@ -0,0 +1,9 @@ +package core + +/** + * 从命令行输入的相关信息 + */ +type Application interface { + //启动整个应用 + Start() +} diff --git a/code/core/command.go b/code/core/command.go deleted file mode 100644 index eb317cb..0000000 --- a/code/core/command.go +++ /dev/null @@ -1,10 +0,0 @@ -package core - -/** - * 从命令行输入的相关信息 - */ -type Command interface { - - //判断是否为命名行模式,如果是直接按照命名行模式处理,并返回true。如果不是返回false. - Cli() bool -} diff --git a/code/core/global.go b/code/core/global.go index b051c9a..51649a9 100644 --- a/code/core/global.go +++ b/code/core/global.go @@ -3,7 +3,7 @@ package core //该文件中记录的是应用系统中全局变量。主要有日志LOGGER和上下文CONTEXT //命令行输入等相关信息 -var COMMAND Command +var APPLICATION Application //日志系统必须高保 //全局唯一的日志对象(在main函数中初始化) diff --git a/code/rest/base_model.go b/code/rest/base_model.go index 16a11d0..e5aa050 100644 --- a/code/rest/base_model.go +++ b/code/rest/base_model.go @@ -10,6 +10,9 @@ import ( const ( TRUE = "true" FALSE = "false" + + DIRECTION_ASC = "ASC" + DIRECTION_DESC = "DESC" ) type IBase interface { diff --git a/code/rest/bridge_controller.go b/code/rest/bridge_controller.go deleted file mode 100644 index 51217cd..0000000 --- a/code/rest/bridge_controller.go +++ /dev/null @@ -1,89 +0,0 @@ -package rest - -import ( - "github.com/eyebluecn/tank/code/core" - "github.com/eyebluecn/tank/code/tool/builder" - "github.com/eyebluecn/tank/code/tool/result" - "net/http" - "strconv" -) - -type BridgeController struct { - BaseController - bridgeDao *BridgeDao - shareDao *ShareDao - bridgeService *BridgeService -} - -//初始化方法 -func (this *BridgeController) Init() { - this.BaseController.Init() - - //手动装填本实例的Bean. 这里必须要用中间变量方可。 - b := core.CONTEXT.GetBean(this.bridgeDao) - if b, ok := b.(*BridgeDao); ok { - this.bridgeDao = b - } - - b = core.CONTEXT.GetBean(this.shareDao) - if b, ok := b.(*ShareDao); ok { - this.shareDao = b - } - b = core.CONTEXT.GetBean(this.bridgeService) - if b, ok := b.(*BridgeService); ok { - this.bridgeService = b - } - -} - -//注册自己的路由。 -func (this *BridgeController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) { - - routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request)) - - //每个Controller需要主动注册自己的路由。 - routeMap["/api/bridge/page"] = this.Wrap(this.Page, USER_ROLE_USER) - - return routeMap -} - -//按照分页的方式查询 -func (this *BridgeController) Page(writer http.ResponseWriter, request *http.Request) *result.WebResult { - - //如果是根目录,那么就传入root. - pageStr := request.FormValue("page") - pageSizeStr := request.FormValue("pageSize") - shareUuid := request.FormValue("shareUuid") - orderCreateTime := request.FormValue("orderCreateTime") - orderSize := request.FormValue("orderSize") - - share := this.shareDao.CheckByUuid(shareUuid) - - var page int - if pageStr != "" { - page, _ = strconv.Atoi(pageStr) - } - - pageSize := 200 - if pageSizeStr != "" { - tmp, err := strconv.Atoi(pageSizeStr) - if err == nil { - pageSize = tmp - } - } - - sortArray := []builder.OrderPair{ - { - Key: "create_time", - Value: orderCreateTime, - }, - { - Key: "size", - Value: orderSize, - }, - } - - pager := this.bridgeDao.Page(page, pageSize, share.Uuid, sortArray) - - return this.Success(pager) -} diff --git a/code/rest/bridge_dao.go b/code/rest/bridge_dao.go index c9ba4c5..1c6aa2b 100644 --- a/code/rest/bridge_dao.go +++ b/code/rest/bridge_dao.go @@ -3,6 +3,7 @@ package rest import ( "github.com/eyebluecn/tank/code/core" "github.com/eyebluecn/tank/code/tool/builder" + "github.com/eyebluecn/tank/code/tool/result" "github.com/jinzhu/gorm" "github.com/nu7hatch/gouuid" @@ -37,6 +38,18 @@ func (this *BridgeDao) CheckByUuid(uuid string) *Bridge { } +//按照shareUuid和matterUuid查找 +func (this *BridgeDao) CheckByShareUuidAndMatterUuid(shareUuid string, matterUuid string) *Bridge { + + // Read + var bridge Bridge + db := core.CONTEXT.GetDB().Where("share_uuid = ? AND matter_uuid = ?", shareUuid, matterUuid).First(&bridge) + this.PanicError(db.Error) + + return &bridge + +} + //按分页条件获取分页 func (this *BridgeDao) Page(page int, pageSize int, shareUuid string, sortArray []builder.OrderPair) *Pager { @@ -92,6 +105,47 @@ func (this *BridgeDao) Delete(bridge *Bridge) { this.PanicError(db.Error) } +//删除一个matter对应的所有缓存 +func (this *BridgeDao) DeleteByMatterUuid(matterUuid string) { + + var wp = &builder.WherePair{} + + wp = wp.And(&builder.WherePair{Query: "matter_uuid = ?", Args: []interface{}{matterUuid}}) + + //删除文件记录 + db := core.CONTEXT.GetDB().Where(wp.Query, wp.Args).Delete(Bridge{}) + this.PanicError(db.Error) +} + +//删除一个share对应的所有缓存 +func (this *BridgeDao) DeleteByShareUuid(shareUuid string) { + + var wp = &builder.WherePair{} + + wp = wp.And(&builder.WherePair{Query: "share_uuid = ?", Args: []interface{}{shareUuid}}) + + //删除文件记录 + db := core.CONTEXT.GetDB().Where(wp.Query, wp.Args).Delete(Bridge{}) + this.PanicError(db.Error) +} + +//根据shareUuid获取关联的所有matter. +func (this *BridgeDao) ListByShareUuid(shareUuid string) []*Bridge { + + if shareUuid == "" { + panic(result.BadRequest("shareUuid cannot be nil")) + } + + var bridges []*Bridge + + db := core.CONTEXT.GetDB(). + Where("share_uuid = ?", shareUuid). + Find(&bridges) + this.PanicError(db.Error) + + return bridges +} + //执行清理操作 func (this *BridgeDao) Cleanup() { this.logger.Info("[BridgeDao]执行清理:清除数据库中所有Bridge记录。") diff --git a/code/rest/install_controller.go b/code/rest/install_controller.go index c0b465d..655a2f2 100644 --- a/code/rest/install_controller.go +++ b/code/rest/install_controller.go @@ -2,7 +2,6 @@ package rest import ( "fmt" - "github.com/eyebluecn/tank/code/core" "github.com/eyebluecn/tank/code/tool/builder" "github.com/eyebluecn/tank/code/tool/result" @@ -26,6 +25,7 @@ type InstallController struct { matterService *MatterService imageCacheDao *ImageCacheDao imageCacheService *ImageCacheService + tableNames []IBase } //初始化方法 @@ -63,6 +63,20 @@ func (this *InstallController) Init() { this.imageCacheService = c } + this.tableNames = []IBase{ + &Dashboard{}, + &Bridge{}, + &DownloadToken{}, + &Footprint{}, + &ImageCache{}, + &Matter{}, + &Preference{}, + &Session{}, + &Share{}, + &UploadToken{}, + &User{}, + } + } //注册自己的路由。 @@ -185,10 +199,9 @@ func (this *InstallController) getTableMeta(gormDb *gorm.DB, entity IBase) (bool //根据表名获取建表SQL语句 func (this *InstallController) getTableMetaList(db *gorm.DB) []*InstallTableInfo { - var tableNames = []IBase{&Dashboard{}, &DownloadToken{}, &Footprint{}, &ImageCache{}, &Matter{}, &Preference{}, &Session{}, &UploadToken{}, &User{}} var installTableInfos []*InstallTableInfo - for _, iBase := range tableNames { + for _, iBase := range this.tableNames { exist, allFields, missingFields := this.getTableMeta(db, iBase) installTableInfos = append(installTableInfos, &InstallTableInfo{ Name: iBase.TableName(), @@ -247,13 +260,12 @@ func (this *InstallController) TableInfoList(writer http.ResponseWriter, request //创建缺失数据库和表 func (this *InstallController) CreateTable(writer http.ResponseWriter, request *http.Request) *result.WebResult { - var tableNames = []IBase{&Dashboard{}, &DownloadToken{}, &Footprint{}, &ImageCache{}, &Matter{}, &Preference{}, &Session{}, &UploadToken{}, &User{}} var installTableInfos []*InstallTableInfo db := this.openDbConnection(writer, request) defer this.closeDbConnection(db) - for _, iBase := range tableNames { + for _, iBase := range this.tableNames { //补全缺失字段或者创建数据库表 db1 := db.AutoMigrate(iBase) diff --git a/code/rest/matter_controller.go b/code/rest/matter_controller.go index f7751d9..8f17d5a 100644 --- a/code/rest/matter_controller.go +++ b/code/rest/matter_controller.go @@ -15,6 +15,8 @@ type MatterController struct { matterService *MatterService downloadTokenDao *DownloadTokenDao imageCacheDao *ImageCacheDao + shareDao *ShareDao + bridgeDao *BridgeDao imageCacheService *ImageCacheService } @@ -42,6 +44,12 @@ func (this *MatterController) Init() { if b, ok := b.(*ImageCacheDao); ok { this.imageCacheDao = b } + + b = core.CONTEXT.GetBean(this.shareDao) + if b, ok := b.(*ShareDao); ok { + this.shareDao = b + } + b = core.CONTEXT.GetBean(this.imageCacheService) if b, ok := b.(*ImageCacheService); ok { this.imageCacheService = b @@ -64,7 +72,7 @@ func (this *MatterController) RegisterRoutes() map[string]func(writer http.Respo routeMap["/api/matter/change/privacy"] = this.Wrap(this.ChangePrivacy, USER_ROLE_USER) routeMap["/api/matter/move"] = this.Wrap(this.Move, USER_ROLE_USER) routeMap["/api/matter/detail"] = this.Wrap(this.Detail, USER_ROLE_USER) - routeMap["/api/matter/page"] = this.Wrap(this.Page, USER_ROLE_USER) + routeMap["/api/matter/page"] = this.Wrap(this.Page, USER_ROLE_GUEST) //本地文件映射 routeMap["/api/matter/mirror"] = this.Wrap(this.Mirror, USER_ROLE_USER) @@ -114,9 +122,54 @@ func (this *MatterController) Page(writer http.ResponseWriter, request *http.Req orderName := request.FormValue("orderName") extensionsStr := request.FormValue("extensions") - user := this.checkUser(writer, request) - if user.Role != USER_ROLE_ADMINISTRATOR { - userUuid = user.Uuid + //使用分享提取码的形式授权。 + shareUuid := request.FormValue("shareUuid") + shareCode := request.FormValue("shareCode") + shareRootUuid := request.FormValue("shareRootUuid") + if shareUuid != "" { + + if puuid == "" { + panic(result.BadRequest("puuid必填!")) + } + dirMatter := this.matterDao.CheckByUuid(puuid) + if dirMatter.Dir { + panic(result.BadRequest("puuid 对应的不是文件夹")) + } + + share := this.shareDao.CheckByUuid(shareUuid) + //如果是自己的分享,可以不要提取码 + user := this.findUser(writer, request) + if user == nil { + if share.Code != shareCode { + panic(result.Unauthorized("提取码错误!")) + } + } else { + if user.Uuid != share.UserUuid { + if share.Code != shareCode { + panic(result.Unauthorized("提取码错误!")) + } + } + } + + //验证 shareRootMatter是否在被分享。 + shareRootMatter := this.matterDao.CheckByUuid(shareRootUuid) + if !shareRootMatter.Dir { + panic(result.BadRequest("只有文件夹可以浏览!")) + } + this.bridgeDao.CheckByShareUuidAndMatterUuid(share.Uuid, shareRootMatter.Uuid) + + //保证 puuid对应的matter是shareRootMatter的子文件夹。 + child := strings.HasPrefix(dirMatter.Path, shareRootMatter.Path) + if !child { + panic(result.BadRequest("%s 不是 %s 的子文件夹!", puuid, shareRootUuid)) + } + + } else { + //非分享模式要求必须登录 + user := this.checkUser(writer, request) + if user.Role != USER_ROLE_ADMINISTRATOR { + userUuid = user.Uuid + } } var page int @@ -246,10 +299,10 @@ func (this *MatterController) Upload(writer http.ResponseWriter, request *http.R //从一个Url中去爬取资源 func (this *MatterController) Crawl(writer http.ResponseWriter, request *http.Request) *result.WebResult { - userUuid := request.FormValue("userUuid") - puuid := request.FormValue("puuid") url := request.FormValue("url") - privacyStr := request.FormValue("privacy") + destPath := request.FormValue("destPath") + userUuid := request.FormValue("userUuid") + filename := request.FormValue("filename") user := this.checkUser(writer, request) if user.Role != USER_ROLE_ADMINISTRATOR { @@ -262,23 +315,17 @@ func (this *MatterController) Crawl(writer http.ResponseWriter, request *http.Re user = this.userDao.CheckByUuid(userUuid) - dirMatter := this.matterDao.CheckWithRootByUuid(puuid, user) - - privacy := false - if privacyStr == TRUE { - privacy = true - } + dirMatter := this.matterService.CreateDirectories(user, destPath) if url == "" || (!strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://")) { panic("资源url必填,并且应该以http://或者https://开头") } - filename := request.FormValue("filename") if filename == "" { - panic("文件名必传") + panic("filename 必填") } - matter := this.matterService.AtomicCrawl(url, filename, user, dirMatter, privacy) + matter := this.matterService.AtomicCrawl(url, filename, user, dirMatter, true) return this.Success(matter) } diff --git a/code/rest/matter_dao.go b/code/rest/matter_dao.go index 9d5c615..d265e41 100644 --- a/code/rest/matter_dao.go +++ b/code/rest/matter_dao.go @@ -14,6 +14,7 @@ import ( type MatterDao struct { BaseDao imageCacheDao *ImageCacheDao + bridgeDao *BridgeDao } //初始化方法 @@ -25,6 +26,12 @@ func (this *MatterDao) Init() { if b, ok := b.(*ImageCacheDao); ok { this.imageCacheDao = b } + + b = core.CONTEXT.GetBean(this.bridgeDao) + if b, ok := b.(*BridgeDao); ok { + this.bridgeDao = b + } + } //按照Id查询文件 @@ -220,6 +227,16 @@ func (this *MatterDao) List(puuid string, userUuid string, sortArray []builder.O return matters } +//根据uuid查找对应的Matters +func (this *MatterDao) ListByUuids(puuids []string, sortArray []builder.OrderPair) []*Matter { + var matters []*Matter + + db := core.CONTEXT.GetDB().Where(puuids).Order(this.GetSortString(sortArray)).Find(&matters) + this.PanicError(db.Error) + + return matters +} + //获取某个文件夹下所有的文件和子文件 func (this *MatterDao) Page(page int, pageSize int, puuid string, userUuid string, name string, dir string, extensions []string, sortArray []builder.OrderPair) *Pager { @@ -325,6 +342,9 @@ func (this *MatterDao) Delete(matter *Matter) { //删除对应的缓存图片。 this.imageCacheDao.DeleteByMatterUuid(matter.Uuid) + //删除所有的分享文件 + this.bridgeDao.DeleteByMatterUuid(matter.Uuid) + //删除文件 err := os.Remove(matter.AbsolutePath()) if err != nil { diff --git a/code/rest/matter_model.go b/code/rest/matter_model.go index 66bcbf3..58bba23 100644 --- a/code/rest/matter_model.go +++ b/code/rest/matter_model.go @@ -32,7 +32,7 @@ type Matter struct { Md5 string `json:"md5" gorm:"type:varchar(45)"` Size int64 `json:"size" gorm:"type:bigint(20) not null;default:0"` Privacy bool `json:"privacy" gorm:"type:tinyint(1) not null;default:0"` - Path string `json:"path" gorm:"type:varchar(512)"` + Path string `json:"path" gorm:"type:varchar(1024)"` Times int64 `json:"times" gorm:"type:bigint(20) not null;default:0"` Parent *Matter `json:"parent" gorm:"-"` } diff --git a/code/rest/matter_service.go b/code/rest/matter_service.go index 672f2ac..c1aa3ef 100644 --- a/code/rest/matter_service.go +++ b/code/rest/matter_service.go @@ -797,6 +797,10 @@ func (this *MatterService) AtomicCrawl(url string, filename string, user *User, panic("资源url必填,并且应该以http://或者https://开头") } + if filename == "" { + panic(result.BadRequest("filename cannot be null.")) + } + //从指定的url下载一个文件。参考:https://golangcode.com/download-a-file-from-a-url/ resp, err := http.Get(url) this.PanicError(err) diff --git a/code/rest/share_controller.go b/code/rest/share_controller.go index 7527d12..82353eb 100644 --- a/code/rest/share_controller.go +++ b/code/rest/share_controller.go @@ -54,16 +54,19 @@ func (this *ShareController) RegisterRoutes() map[string]func(writer http.Respon //每个Controller需要主动注册自己的路由。 routeMap["/api/share/create"] = this.Wrap(this.Create, USER_ROLE_USER) routeMap["/api/share/delete"] = this.Wrap(this.Delete, USER_ROLE_USER) + routeMap["/api/share/delete/batch"] = this.Wrap(this.DeleteBatch, USER_ROLE_USER) routeMap["/api/share/detail"] = this.Wrap(this.Detail, USER_ROLE_USER) routeMap["/api/share/page"] = this.Wrap(this.Page, USER_ROLE_USER) + routeMap["/api/share/browse"] = this.Wrap(this.Browse, USER_ROLE_GUEST) return routeMap } -//删除一条记录 +//创建一个分享 func (this *ShareController) Create(writer http.ResponseWriter, request *http.Request) *result.WebResult { matterUuids := request.FormValue("matterUuids") + expireInfinityStr := request.FormValue("expireInfinity") expireTimeStr := request.FormValue("expireTime") if matterUuids == "" { @@ -71,47 +74,86 @@ func (this *ShareController) Create(writer http.ResponseWriter, request *http.Re } var expireTime time.Time - if expireTimeStr == "" { - panic(result.BadRequest("时间格式错误!")) + expireInfinity := false + if expireInfinityStr == TRUE { + expireInfinity = true + expireTime = time.Now() } else { - expireTime = util.ConvertDateTimeStringToTime(expireTimeStr) - } - if expireTime.Before(time.Now()) { - panic(result.BadRequest("过期时间错误!")) + if expireTimeStr == "" { + panic(result.BadRequest("时间格式错误!")) + } else { + expireTime = util.ConvertDateTimeStringToTime(expireTimeStr) + } + + if expireTime.Before(time.Now()) { + panic(result.BadRequest("过期时间错误!")) + } + } uuidArray := strings.Split(matterUuids, ",") if len(uuidArray) == 0 { panic(result.BadRequest("请至少分享一个文件")) + } else if len(uuidArray) > SHARE_MAX_NUM { + panic(result.BadRequest("一次分享文件数不能超过 %d", SHARE_MAX_NUM)) } + var name string + shareType := SHARE_TYPE_MIX user := this.checkUser(writer, request) - for _, uuid := range uuidArray { + var puuid string + var matters []*Matter + for key, uuid := range uuidArray { matter := this.matterDao.CheckByUuid(uuid) //判断文件的所属人是否正确 if matter.UserUuid != user.Uuid { - panic(result.Unauthorized("没有权限")) + panic(result.Unauthorized("不是你的文件,没有权限")) } + + matters = append(matters, matter) + + if key == 0 { + puuid = matter.Puuid + name = matter.Name + if matter.Dir { + shareType = SHARE_TYPE_DIRECTORY + } else { + shareType = SHARE_TYPE_FILE + } + } else { + if matter.Puuid != puuid { + panic(result.Unauthorized("一次只能分享同一个文件夹中的内容")) + } + } + + } + + if len(uuidArray) > 1 { + shareType = SHARE_TYPE_MIX + name = name + "等" } //创建share记录 share := &Share{ - UserUuid: user.Uuid, - DownloadTimes: 0, - Code: util.RandomString4(), - ExpireTime: expireTime, + Name: name, + ShareType: shareType, + UserUuid: user.Uuid, + DownloadTimes: 0, + Code: util.RandomString4(), + ExpireInfinity: expireInfinity, + ExpireTime: expireTime, } this.shareDao.Create(share) //创建关联的matter - for _, matterUuid := range uuidArray { + for _, matter := range matters { bridge := &Bridge{ ShareUuid: share.Uuid, - MatterUuid: matterUuid, + MatterUuid: matter.Uuid, } this.bridgeDao.Create(bridge) } @@ -130,12 +172,42 @@ func (this *ShareController) Delete(writer http.ResponseWriter, request *http.Re share := this.shareDao.FindByUuid(uuid) if share != nil { + + //删除对应的bridge. + this.bridgeDao.DeleteByShareUuid(share.Uuid) + this.shareDao.Delete(share) } return this.Success(nil) } +//删除一系列分享 +func (this *ShareController) DeleteBatch(writer http.ResponseWriter, request *http.Request) *result.WebResult { + + uuids := request.FormValue("uuids") + if uuids == "" { + panic(result.BadRequest("uuids必填")) + } + + uuidArray := strings.Split(uuids, ",") + + for _, uuid := range uuidArray { + + imageCache := this.shareDao.FindByUuid(uuid) + + //判断图片缓存的所属人是否正确 + user := this.checkUser(writer, request) + if user.Role != USER_ROLE_ADMINISTRATOR && imageCache.UserUuid != user.Uuid { + panic(result.Unauthorized("没有权限")) + } + + this.shareDao.Delete(imageCache) + } + + return this.Success("删除成功!") +} + //查看详情。 func (this *ShareController) Detail(writer http.ResponseWriter, request *http.Request) *result.WebResult { @@ -196,3 +268,59 @@ func (this *ShareController) Page(writer http.ResponseWriter, request *http.Requ return this.Success(pager) } + +//验证提取码对应的某个shareUuid是否有效 +func (this *ShareController) CheckShare(writer http.ResponseWriter, request *http.Request) *Share { + + //如果是根目录,那么就传入root. + shareUuid := request.FormValue("shareUuid") + code := request.FormValue("code") + + share := this.shareDao.CheckByUuid(shareUuid) + //如果是自己的分享,可以不要提取码 + user := this.findUser(writer, request) + if user == nil { + if share.Code != code { + panic(result.Unauthorized("提取码错误!")) + } + } else { + if user.Uuid != share.UserUuid { + if share.Code != code { + panic(result.Unauthorized("提取码错误!")) + } + } + } + + return share + +} + +//浏览某个分享中的文件 +func (this *ShareController) Browse(writer http.ResponseWriter, request *http.Request) *result.WebResult { + + //要求传参:shareUuid,code + share := this.CheckShare(writer, request) + bridges := this.bridgeDao.ListByShareUuid(share.Uuid) + + //获取对应的 matter. + var matters []*Matter + if len(bridges) != 0 { + uuids := make([]string, 0) + for _, bridge := range bridges { + uuids = append(uuids, bridge.MatterUuid) + } + + sortArray := []builder.OrderPair{ + { + Key: "dir", + Value: DIRECTION_DESC, + }, + } + matters = this.matterDao.ListByUuids(uuids, sortArray) + + share.Matters = matters + } + + return this.Success(share) + +} diff --git a/code/rest/share_dao.go b/code/rest/share_dao.go index e052582..e789b26 100644 --- a/code/rest/share_dao.go +++ b/code/rest/share_dao.go @@ -90,6 +90,7 @@ func (this *ShareDao) Delete(share *Share) { db := core.CONTEXT.GetDB().Delete(&share) this.PanicError(db.Error) + } //执行清理操作 diff --git a/code/rest/share_model.go b/code/rest/share_model.go index ffe1618..1019443 100644 --- a/code/rest/share_model.go +++ b/code/rest/share_model.go @@ -5,15 +5,32 @@ import ( "time" ) +const ( + //单文件 + SHARE_TYPE_FILE = "FILE" + //文件夹 + SHARE_TYPE_DIRECTORY = "DIRECTORY" + //混合体 + SHARE_TYPE_MIX = "MIX" +) + +const ( + SHARE_MAX_NUM = 100 +) + /** * 分享记录 */ type Share struct { Base - UserUuid string `json:"userUuid" gorm:"type:char(36)"` - DownloadTimes int64 `json:"downloadTimes" gorm:"type:bigint(20) not null;default:0"` - Code string `json:"code" gorm:"type:varchar(45) not null"` - ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"` + Name string `json:"name" gorm:"type:varchar(255)"` + ShareType string `json:"shareType" gorm:"type:varchar(45)"` + UserUuid string `json:"userUuid" gorm:"type:char(36)"` + DownloadTimes int64 `json:"downloadTimes" gorm:"type:bigint(20) not null;default:0"` + Code string `json:"code" gorm:"type:varchar(45) not null"` + ExpireInfinity bool `json:"expireInfinity" gorm:"type:tinyint(1) not null;default:0"` + ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"` + Matters []*Matter `json:"matters" gorm:"-"` } // set File's table name to be `profiles` diff --git a/code/support/tank_application.go b/code/support/tank_application.go new file mode 100644 index 0000000..f1bd536 --- /dev/null +++ b/code/support/tank_application.go @@ -0,0 +1,256 @@ +package support + +import ( + "flag" + "fmt" + "github.com/eyebluecn/tank/code/core" + "github.com/eyebluecn/tank/code/tool/result" + "github.com/eyebluecn/tank/code/tool/util" + jsoniter "github.com/json-iterator/go" + "golang.org/x/crypto/ssh/terminal" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" + "syscall" +) + +const ( + //启动web服务,默认是这种方式 + MODE_WEB = "web" + //映射本地文件到云盘中 + MODE_MIRROR = "mirror" + //将远程的一个文件爬取到蓝眼云盘中 + MODE_CRAWL = "crawl" +) + +//命令行输入相关的对象 +type TankApplication struct { + //模式 + mode string + + //蓝眼云盘的主机,需要带上协议和端口号。默认: http://127.0.0.1:core.DEFAULT_SERVER_PORT + host string + //用户名 + username string + //密码 + password string + + //源文件/文件夹,本地绝对路径/远程资源url + src string + //目标(表示的是文件夹)路径,蓝眼云盘中的路径。相对于root的路径。 + dest string + //同名文件或文件夹是否直接替换 true 全部替换; false 跳过 + overwrite bool + //拉取文件存储的名称 + filename string +} + +//启动应用。可能是web形式,也可能是命令行工具。 +func (this *TankApplication) Start() { + + defer func() { + if err := recover(); err != nil { + fmt.Printf("ERROR:%v\r\n", err) + } + }() + + //超级管理员信息 + modePtr := flag.String("mode", this.mode, "cli mode web/mirror/crawl") + hostPtr := flag.String("host", this.username, "tank host") + usernamePtr := flag.String("username", this.username, "username") + passwordPtr := flag.String("password", this.password, "password") + srcPtr := flag.String("src", this.src, "src absolute path") + destPtr := flag.String("dest", this.dest, "destination path in tank.") + overwritePtr := flag.Bool("overwrite", this.overwrite, "whether same file overwrite") + filenamePtr := flag.String("filename", this.filename, "filename when crawl") + + //flag.Parse()方法必须要在使用之前调用。 + flag.Parse() + + this.mode = *modePtr + this.host = *hostPtr + this.username = *usernamePtr + this.password = *passwordPtr + this.src = *srcPtr + this.dest = *destPtr + this.overwrite = *overwritePtr + this.filename = *filenamePtr + + //默认采用web的形式启动 + if this.mode == "" || strings.ToLower(this.mode) == MODE_WEB { + + //直接web启动 + this.HandleWeb() + + } else { + + //准备蓝眼云盘地址 + if this.host == "" { + this.host = fmt.Sprintf("http://127.0.0.1:%d", core.DEFAULT_SERVER_PORT) + } + + //准备用户名 + if this.username == "" { + panic(result.BadRequest("%s模式下,用户名必填", this.mode)) + } + + //准备密码 + if this.password == "" { + + if util.EnvDevelopment() { + + panic(result.BadRequest("IDE中请运行请直接使用 -password yourPassword 的形式输入密码")) + + } else { + + fmt.Print("Enter Password:") + bytePassword, err := terminal.ReadPassword(int(syscall.Stdin)) + if err != nil { + panic(err) + } + + this.password = string(bytePassword) + fmt.Println() + } + } + + if strings.ToLower(this.mode) == MODE_MIRROR { + + //映射本地文件到蓝眼云盘 + this.HandleMirror() + + } else if strings.ToLower(this.mode) == MODE_CRAWL { + + //将远程文件拉取到蓝眼云盘中 + this.HandleCrawl() + + } else { + panic(result.BadRequest("不能处理命名行模式: %s \r\n", this.mode)) + } + } + +} + +//采用Web的方式启动应用 +func (this *TankApplication) HandleWeb() { + + //第1步。日志 + tankLogger := &TankLogger{} + core.LOGGER = tankLogger + tankLogger.Init() + defer tankLogger.Destroy() + + //第2步。配置 + tankConfig := &TankConfig{} + core.CONFIG = tankConfig + tankConfig.Init() + + //第3步。全局运行的上下文 + tankContext := &TankContext{} + core.CONTEXT = tankContext + tankContext.Init() + defer tankContext.Destroy() + + //第4步。启动http服务 + http.Handle("/", core.CONTEXT) + core.LOGGER.Info("App started at http://localhost:%v", core.CONFIG.ServerPort()) + + dotPort := fmt.Sprintf(":%v", core.CONFIG.ServerPort()) + err1 := http.ListenAndServe(dotPort, nil) + if err1 != nil { + log.Fatal("ListenAndServe: ", err1) + } + +} + +//处理本地映射的情形 +func (this *TankApplication) HandleMirror() { + + if this.src == "" { + panic("src 必填") + } + if this.dest == "" { + panic("dest 必填") + } + + fmt.Printf("开始映射本地文件 %s 到蓝眼云盘 %s\r\n", this.src, this.dest) + + urlString := fmt.Sprintf("%s/api/matter/mirror", this.host) + + params := url.Values{ + "srcPath": {this.src}, + "destPath": {this.dest}, + "overwrite": {fmt.Sprintf("%v", this.overwrite)}, + core.USERNAME_KEY: {this.username}, + core.PASSWORD_KEY: {this.password}, + } + + response, err := http.PostForm(urlString, params) + util.PanicError(err) + + bodyBytes, err := ioutil.ReadAll(response.Body) + + webResult := &result.WebResult{} + + err = jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(bodyBytes, webResult) + if err != nil { + fmt.Printf("返回格式错误!%s \r\n", err.Error()) + return + } + + if webResult.Code == result.CODE_WRAPPER_OK.Code { + fmt.Println("success") + } else { + fmt.Printf("error %s\r\n", webResult.Msg) + } + +} + +//拉取远程文件到本地。 +func (this *TankApplication) HandleCrawl() { + + if this.src == "" { + panic("src 必填") + } + if this.dest == "" { + panic("dest 必填") + } + + if this.filename == "" { + panic("filename 必填") + } + + fmt.Printf("开始映射拉取远程文件 %s 到蓝眼云盘 %s\r\n", this.src, this.dest) + + urlString := fmt.Sprintf("%s/api/matter/crawl", this.host) + + params := url.Values{ + "url": {this.src}, + "destPath": {this.dest}, + "filename": {this.filename}, + core.USERNAME_KEY: {this.username}, + core.PASSWORD_KEY: {this.password}, + } + + response, err := http.PostForm(urlString, params) + util.PanicError(err) + + bodyBytes, err := ioutil.ReadAll(response.Body) + + webResult := &result.WebResult{} + + err = jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(bodyBytes, webResult) + if err != nil { + fmt.Printf("返回格式错误!%s \r\n", err.Error()) + return + } + + if webResult.Code == result.CODE_WRAPPER_OK.Code { + fmt.Println("success") + } else { + fmt.Printf("error %s\r\n", webResult.Msg) + } + +} diff --git a/code/support/tank_command.go b/code/support/tank_command.go deleted file mode 100644 index 6616fa9..0000000 --- a/code/support/tank_command.go +++ /dev/null @@ -1,151 +0,0 @@ -package support - -import ( - "flag" - "fmt" - "github.com/eyebluecn/tank/code/core" - "github.com/eyebluecn/tank/code/tool/result" - "github.com/eyebluecn/tank/code/tool/util" - jsoniter "github.com/json-iterator/go" - "golang.org/x/crypto/ssh/terminal" - "io/ioutil" - "net/http" - "net/url" - "strings" - "syscall" -) - -const ( - //启动web服务,默认是这种方式 - MODE_WEB = "web" - //映射本地文件到云盘中 - MODE_MIRROR = "mirror" -) - -//命令行输入相关的对象 -type TankCommand struct { - //模式 - mode string - - //蓝眼云盘的主机,需要带上协议和端口号。默认: http://127.0.0.1:core.DEFAULT_SERVER_PORT - host string - //用户名 - username string - //密码 - password string - - //源文件/文件夹,本地绝对路径 - src string - //目标(表示的是文件夹)路径,蓝眼云盘中的路径。相对于root的路径。 - dest string - //同名文件或文件夹是否直接替换 true 全部替换; false 跳过 - overwrite bool -} - -//第三级. 从程序参数中读取配置项 -func (this *TankCommand) Cli() bool { - - //超级管理员信息 - modePtr := flag.String("mode", this.mode, "cli mode web/mirror") - hostPtr := flag.String("host", this.username, "tank host") - usernamePtr := flag.String("username", this.username, "username") - passwordPtr := flag.String("password", this.password, "password") - srcPtr := flag.String("src", this.src, "src absolute path") - destPtr := flag.String("dest", this.dest, "destination path in tank.") - overwritePtr := flag.Bool("overwrite", this.overwrite, "whether same file overwrite") - - //flag.Parse()方法必须要在使用之前调用。 - flag.Parse() - - this.mode = *modePtr - this.host = *hostPtr - this.username = *usernamePtr - this.password = *passwordPtr - this.src = *srcPtr - this.dest = *destPtr - this.overwrite = *overwritePtr - - //准备模式 - if this.mode == "" || strings.ToLower(this.mode) == MODE_WEB { - return false - } - - //准备蓝眼云盘地址 - if this.host == "" { - this.host = fmt.Sprintf("http://127.0.0.1:%d", core.DEFAULT_SERVER_PORT) - } - - //准备用户名 - if this.username == "" { - fmt.Println("用户名必填") - return true - } - - //准备密码 - if this.password == "" { - - if util.EnvDevelopment() { - - fmt.Println("IDE中请运行请直接使用 -password yourPassword 的形式输入密码") - return true - - } else { - - fmt.Print("Enter Password:") - bytePassword, err := terminal.ReadPassword(int(syscall.Stdin)) - if err != nil { - panic(err) - } - - this.password = string(bytePassword) - fmt.Println() - } - } - - if strings.ToLower(this.mode) == MODE_MIRROR { - - this.HandleMirror() - - } else { - - fmt.Printf("不能处理命名行模式: %s \r\n", this.mode) - } - - return true -} - -//处理本地映射的情形 -func (this *TankCommand) HandleMirror() { - - fmt.Printf("开始映射本地文件 %s 到蓝眼云盘 %s\r\n", this.src, this.dest) - - urlString := fmt.Sprintf("%s/api/matter/mirror", this.host) - - params := url.Values{ - "srcPath": {this.src}, - "destPath": {this.dest}, - "overwrite": {fmt.Sprintf("%v", this.overwrite)}, - core.USERNAME_KEY: {this.username}, - core.PASSWORD_KEY: {this.password}, - } - - response, err := http.PostForm(urlString, params) - util.PanicError(err) - - bodyBytes, err := ioutil.ReadAll(response.Body) - - webResult := &result.WebResult{} - - err = jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(bodyBytes, webResult) - if err != nil { - fmt.Printf("返回格式错误!%s \r\n", err.Error()) - return - } - - if webResult.Code == result.CODE_WRAPPER_OK.Code { - fmt.Println("success") - } else { - fmt.Printf("error %s\r\n", webResult.Msg) - } - -} diff --git a/code/support/tank_context.go b/code/support/tank_context.go index 42cf352..00dfcac 100644 --- a/code/support/tank_context.go +++ b/code/support/tank_context.go @@ -131,7 +131,6 @@ func (this *TankContext) registerBeans() { this.registerBean(new(rest.AlienService)) //bridge - this.registerBean(new(rest.BridgeController)) this.registerBean(new(rest.BridgeDao)) this.registerBean(new(rest.BridgeService)) diff --git a/code/support/tank_router.go b/code/support/tank_router.go index b5798a7..7b023cd 100644 --- a/code/support/tank_router.go +++ b/code/support/tank_router.go @@ -10,6 +10,7 @@ import ( "io" "net/http" "os" + "runtime" "strings" "time" ) @@ -72,7 +73,13 @@ func NewRouter() *TankRouter { func (this *TankRouter) GlobalPanicHandler(writer http.ResponseWriter, request *http.Request, startTime time.Time) { if err := recover(); err != nil { - core.LOGGER.Error("错误: %v", err) + //控制台中打印日志,记录行号。 + _, file, line, ok := runtime.Caller(2) + if !ok { + file = "???" + line = 0 + } + core.LOGGER.Error("panic on %s:%d %v", util.GetFilenameOfPath(file), line, err) var webResult *result.WebResult = nil if value, ok := err.(string); ok { diff --git a/code/tool/util/util_string.go b/code/tool/util/util_string.go index 8fd06c0..4ab6f07 100644 --- a/code/tool/util/util_string.go +++ b/code/tool/util/util_string.go @@ -51,7 +51,8 @@ func RandomNumber4() string { //获取四位随机数字 func RandomString4() string { - var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz0123456789") + //0和o,l和1难以区分,剔除掉 + var letterRunes = []rune("abcdefghijkmnpqrstuvwxyz23456789") b := make([]rune, 4) for i := range b { diff --git a/main.go b/main.go index 791d261..bc8d733 100644 --- a/main.go +++ b/main.go @@ -1,47 +1,14 @@ package main import ( - "fmt" "github.com/eyebluecn/tank/code/core" "github.com/eyebluecn/tank/code/support" _ "github.com/go-sql-driver/mysql" - "log" - "net/http" ) func main() { - //第一步。命令行工具处理 - tankCommand := &support.TankCommand{} - core.COMMAND = tankCommand - if core.COMMAND.Cli() { - return - } + core.APPLICATION = &support.TankApplication{} + core.APPLICATION.Start() - //第二步。日志 - tankLogger := &support.TankLogger{} - core.LOGGER = tankLogger - tankLogger.Init() - defer tankLogger.Destroy() - - //第三步。配置 - tankConfig := &support.TankConfig{} - core.CONFIG = tankConfig - tankConfig.Init() - - //第四步。全局运行的上下文 - tankContext := &support.TankContext{} - core.CONTEXT = tankContext - tankContext.Init() - defer tankContext.Destroy() - - //第五步。启动http服务 - http.Handle("/", core.CONTEXT) - core.LOGGER.Info("App started at http://localhost:%v", core.CONFIG.ServerPort()) - - dotPort := fmt.Sprintf(":%v", core.CONFIG.ServerPort()) - err1 := http.ListenAndServe(dotPort, nil) - if err1 != nil { - log.Fatal("ListenAndServe: ", err1) - } }