diff --git a/build/doc/README_en.md b/build/doc/README_en.md index 8adbfd1..72304ca 100644 --- a/build/doc/README_en.md +++ b/build/doc/README_en.md @@ -3,4 +3,11 @@ > The document is comming... -go build -mod=readonly \ No newline at end of file +go build -mod=readonly + + + +### Program Arguments + +-mode=mirror -username=admin -password=123456 -src=/Users/fusu/d/temp -dest=/morning + diff --git a/code/core/command.go b/code/core/command.go new file mode 100644 index 0000000..eb317cb --- /dev/null +++ b/code/core/command.go @@ -0,0 +1,10 @@ +package core + +/** + * 从命令行输入的相关信息 + */ +type Command interface { + + //判断是否为命名行模式,如果是直接按照命名行模式处理,并返回true。如果不是返回false. + Cli() bool +} diff --git a/code/core/config.go b/code/core/config.go index d100c76..b8593f0 100644 --- a/code/core/config.go +++ b/code/core/config.go @@ -4,6 +4,13 @@ const ( //用户身份的cookie字段名 COOKIE_AUTH_KEY = "_ak" + //使用用户名密码给接口授权key + USERNAME_KEY = "authUsername" + PASSWORD_KEY = "authPassword" + + //默认端口号 + DEFAULT_SERVER_PORT = 6010 + //数据库表前缀 tank200表示当前应用版本是tank:2.0.x版,数据库结构发生变化必然是中型升级 TABLE_PREFIX = "tank20_" @@ -12,7 +19,6 @@ const ( ) type Config interface { - //是否已经安装 Installed() bool //启动端口 diff --git a/code/core/global.go b/code/core/global.go index 1310a17..b051c9a 100644 --- a/code/core/global.go +++ b/code/core/global.go @@ -2,6 +2,9 @@ package core //该文件中记录的是应用系统中全局变量。主要有日志LOGGER和上下文CONTEXT +//命令行输入等相关信息 +var COMMAND Command + //日志系统必须高保 //全局唯一的日志对象(在main函数中初始化) var LOGGER Logger diff --git a/code/core/logger.go b/code/core/logger.go index b8d31aa..d59b516 100644 --- a/code/core/logger.go +++ b/code/core/logger.go @@ -1,6 +1,7 @@ package core type Logger interface { + //处理日志的统一方法。 Log(prefix string, format string, v ...interface{}) diff --git a/code/rest/alien_service.go b/code/rest/alien_service.go index d83d265..968ca2a 100644 --- a/code/rest/alien_service.go +++ b/code/rest/alien_service.go @@ -72,11 +72,6 @@ func (this *AlienService) PreviewOrDownload( matter := this.matterDao.CheckByUuid(uuid) - //判断是否是文件夹 - if matter.Dir { - panic("不支持下载文件夹") - } - if matter.Name != filename { panic("文件信息错误") } @@ -117,21 +112,33 @@ func (this *AlienService) PreviewOrDownload( } } - //对图片处理。 - needProcess, imageResizeM, imageResizeW, imageResizeH := this.imageCacheService.ResizeParams(request) - if needProcess { + //文件夹下载 + if matter.Dir { - //如果是图片,那么能用缓存就用缓存 - imageCache := this.imageCacheDao.FindByMatterUuidAndMode(matter.Uuid, fmt.Sprintf("%s_%d_%d", imageResizeM, imageResizeW, imageResizeH)) - if imageCache == nil { - imageCache = this.imageCacheService.cacheImage(writer, request, matter) - } + this.logger.Info("准备下载文件夹 %s", matter.Name) - //直接使用缓存中的信息 - this.matterService.DownloadFile(writer, request, GetUserCacheRootDir(imageCache.Username)+imageCache.Path, imageCache.Name, withContentDisposition) + //目标地点 + this.matterService.AtomicDownloadDirectory(writer, request, matter) } else { - this.matterService.DownloadFile(writer, request, matter.AbsolutePath(), matter.Name, withContentDisposition) + + //对图片处理。 + needProcess, imageResizeM, imageResizeW, imageResizeH := this.imageCacheService.ResizeParams(request) + if needProcess { + + //如果是图片,那么能用缓存就用缓存 + imageCache := this.imageCacheDao.FindByMatterUuidAndMode(matter.Uuid, fmt.Sprintf("%s_%d_%d", imageResizeM, imageResizeW, imageResizeH)) + if imageCache == nil { + imageCache = this.imageCacheService.cacheImage(writer, request, matter) + } + + //直接使用缓存中的信息 + this.matterService.DownloadFile(writer, request, GetUserCacheRootDir(imageCache.Username)+imageCache.Path, imageCache.Name, withContentDisposition) + + } else { + this.matterService.DownloadFile(writer, request, matter.AbsolutePath(), matter.Name, withContentDisposition) + } + } //文件下载次数加一,为了加快访问速度,异步进行 diff --git a/code/rest/base_bean.go b/code/rest/base_bean.go index 040222b..d3787aa 100644 --- a/code/rest/base_bean.go +++ b/code/rest/base_bean.go @@ -59,6 +59,7 @@ func (this *BaseBean) findUser(writer http.ResponseWriter, request *http.Request } return nil + } //获取当前登录的用户,找不到就返回登录错误 diff --git a/code/rest/install_controller.go b/code/rest/install_controller.go index c012a32..c0b465d 100644 --- a/code/rest/install_controller.go +++ b/code/rest/install_controller.go @@ -128,10 +128,7 @@ func (this *InstallController) getCreateSQLFromFile(tableName string) string { //1. 从当前安装目录db下去寻找建表文件。 homePath := util.GetHomePath() filePath := homePath + "/db/" + tableName + ".sql" - exists, err := util.PathExists(filePath) - if err != nil { - panic(result.Server("从安装目录判断建表语句文件是否存在时出错!")) - } + exists := util.PathExists(filePath) //2. 从GOPATH下面去找,因为可能是开发环境 if !exists { @@ -140,10 +137,7 @@ func (this *InstallController) getCreateSQLFromFile(tableName string) string { filePath1 := filePath filePath = build.Default.GOPATH + "/src/tank/build/db/" + tableName + ".sql" - exists, err = util.PathExists(filePath) - if err != nil { - panic(result.Server("从GOPATH判断建表语句文件是否存在时出错!")) - } + exists = util.PathExists(filePath) if !exists { panic(result.Server("%s 或 %s 均不存在,请检查你的安装情况。", filePath1, filePath)) diff --git a/code/rest/matter_controller.go b/code/rest/matter_controller.go index b4e97fb..0d93d23 100644 --- a/code/rest/matter_controller.go +++ b/code/rest/matter_controller.go @@ -66,6 +66,9 @@ func (this *MatterController) RegisterRoutes() map[string]func(writer http.Respo 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/mirror"] = this.Wrap(this.Mirror, USER_ROLE_USER) + return routeMap } @@ -443,3 +446,27 @@ func (this *MatterController) Move(writer http.ResponseWriter, request *http.Req return this.Success(nil) } + +//将本地文件映射到蓝眼云盘中去。 +func (this *MatterController) Mirror(writer http.ResponseWriter, request *http.Request) *result.WebResult { + + srcPath := request.FormValue("srcPath") + destPath := request.FormValue("destPath") + overwriteStr := request.FormValue("overwrite") + + if srcPath == "" { + panic(result.BadRequest("srcPath必填")) + } + + overwrite := false + if overwriteStr == TRUE { + overwrite = true + } + + user := this.userDao.checkUser(writer, request) + + this.matterService.AtomicMirror(srcPath, destPath, overwrite, user) + + return this.Success(nil) + +} diff --git a/code/rest/matter_dao.go b/code/rest/matter_dao.go index f535052..3ec3e25 100644 --- a/code/rest/matter_dao.go +++ b/code/rest/matter_dao.go @@ -163,6 +163,40 @@ func (this *MatterDao) CountByUserUuidAndPuuidAndDirAndName(userUuid string, puu return count } +//统计某个用户的某个文件夹下的某个名字的文件(或文件夹)数量。 +func (this *MatterDao) FindByUserUuidAndPuuidAndDirAndName(userUuid string, puuid string, dir bool, name string) *Matter { + + var matter = &Matter{} + + var wp = &builder.WherePair{} + + if puuid != "" { + wp = wp.And(&builder.WherePair{Query: "puuid = ?", Args: []interface{}{puuid}}) + } + + if userUuid != "" { + wp = wp.And(&builder.WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}}) + } + + if name != "" { + wp = wp.And(&builder.WherePair{Query: "name = ?", Args: []interface{}{name}}) + } + + wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{dir}}) + + db := core.CONTEXT.GetDB().Where(wp.Query, wp.Args...).First(matter) + + if db.Error != nil { + if db.Error.Error() == result.DB_ERROR_NOT_FOUND { + return nil + } else { + this.PanicError(db.Error) + } + } + + return matter +} + //获取某个用户的某个文件夹下的某个名字的文件(或文件夹)列表 func (this *MatterDao) ListByUserUuidAndPuuidAndDirAndName(userUuid string, puuid string, dir bool, name string) []*Matter { @@ -318,6 +352,8 @@ func (this *MatterDao) CountBetweenTime(startTime time.Time, endTime time.Time) //获取一段时间中文件总大小 func (this *MatterDao) SizeBetweenTime(startTime time.Time, endTime time.Time) int64 { + + //TODO: 所有函数汇总的SQL均需要先count询问,再处理。 var size int64 db := core.CONTEXT.GetDB().Model(&Matter{}).Where("create_time >= ? AND create_time <= ?", startTime, endTime).Select("SUM(size)") this.PanicError(db.Error) @@ -360,6 +396,28 @@ func (this *MatterDao) checkByUserUuidAndPath(userUuid string, path string) *Mat return matter } +//获取一个文件夹中文件总大小 +func (this *MatterDao) SumSizeByUserUuidAndPath(userUuid string, path string) int64 { + + var wp = &builder.WherePair{Query: "user_uuid = ? AND path like ?", Args: []interface{}{userUuid, path + "%"}} + + var count int64 + db := core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...).Count(&count) + if count == 0 { + return 0 + } + + var sumSize int64 + db = core.CONTEXT.GetDB().Model(&Matter{}).Where(wp.Query, wp.Args...).Select("SUM(size)") + this.PanicError(db.Error) + row := db.Row() + err := row.Scan(&sumSize) + util.PanicError(err) + + return sumSize + +} + //执行清理操作 func (this *MatterDao) Cleanup() { this.logger.Info("[MatterDao]执行清理:清除数据库中所有Matter记录。删除磁盘中所有Matter文件。") diff --git a/code/rest/matter_model.go b/code/rest/matter_model.go index 9e24bb0..66bcbf3 100644 --- a/code/rest/matter_model.go +++ b/code/rest/matter_model.go @@ -3,7 +3,6 @@ package rest import ( "fmt" "github.com/eyebluecn/tank/code/core" - "github.com/eyebluecn/tank/code/tool/util" ) @@ -12,6 +11,8 @@ const ( MATTER_ROOT = "root" //cache文件夹名称 MATTER_CACHE = "cache" + //压缩文件的临时目录 + MATTER_ZIP = "zip" //matter名称最大长度 MATTER_NAME_MAX_LENGTH = 200 //matter文件夹最大深度 @@ -80,3 +81,11 @@ func GetUserCacheRootDir(username string) (rootDirPath string) { return rootDirPath } + +//获取到用户压缩临时文件的根目录。 +func GetUserZipRootDir(username string) (rootDirPath string) { + + rootDirPath = fmt.Sprintf("%s/%s/%s", core.CONFIG.MatterPath(), username, MATTER_ZIP) + + return rootDirPath +} diff --git a/code/rest/matter_service.go b/code/rest/matter_service.go index f1b228a..6f3d5b2 100644 --- a/code/rest/matter_service.go +++ b/code/rest/matter_service.go @@ -1,15 +1,21 @@ package rest import ( + "archive/zip" + "fmt" "github.com/eyebluecn/tank/code/core" + "github.com/eyebluecn/tank/code/tool/builder" "github.com/eyebluecn/tank/code/tool/download" "github.com/eyebluecn/tank/code/tool/result" "github.com/eyebluecn/tank/code/tool/util" "io" + "io/ioutil" "net/http" "os" + "path/filepath" "regexp" "strings" + "time" ) /** @@ -69,6 +75,132 @@ func (this *MatterService) DownloadFile( download.DownloadFile(writer, request, filePath, filename, withContentDisposition) } +//下载文件夹 +func (this *MatterService) AtomicDownloadDirectory( + writer http.ResponseWriter, + request *http.Request, + matter *Matter) { + + if matter == nil { + panic(result.BadRequest("matter不能为nil")) + } + + if !matter.Dir { + panic(result.BadRequest("matter 只能是文件夹")) + } + + //操作锁 + this.userService.MatterLock(matter.UserUuid) + defer this.userService.MatterUnlock(matter.UserUuid) + + //验证文件夹中文件总大小。 + sumSize := this.matterDao.SumSizeByUserUuidAndPath(matter.UserUuid, matter.Path) + this.logger.Info("文件夹 %s 大小为 %s", matter.Name, util.HumanFileSize(sumSize)) + + //TODO: 文件夹下载的大小限制 + //准备zip放置的目录。 + destZipDirPath := fmt.Sprintf("%s/%d", GetUserZipRootDir(matter.Username), time.Now().UnixNano()/1e6) + util.MakeDirAll(destZipDirPath) + + destZipName := fmt.Sprintf("%s.zip", matter.Name) + + destZipPath := fmt.Sprintf("%s/%s", destZipDirPath, destZipName) + + //destZipFile, err := os.Create(destZipPath) + //util.PanicError(err) + // + //defer func() { + // err := destZipFile.Close() + // util.PanicError(err) + //}() + // + //zipWriter := zip.NewWriter(destZipFile) + //defer func() { + // err := zipWriter.Close() + // util.PanicError(err) + //}() + + //this.zipCompress(matter, GetUserFileRootDir(matter.Username), zipWriter) + + util.Zip(matter.AbsolutePath(), destZipPath) + + //下载 + download.DownloadFile(writer, request, destZipPath, destZipName, true) + + //TODO: 删除临时压缩文件 + +} + +//zip压缩一个matter. +func (this *MatterService) zipCompress(matter *Matter, prefix string, zipWriter *zip.Writer) { + + if matter == nil { + panic(result.BadRequest("matter不能为nil")) + } + + fileInfo, err := os.Stat(matter.AbsolutePath()) + this.PanicError(err) + + fmt.Println("遍历文件: " + matter.AbsolutePath()) + + if matter.Dir { + + //获取下一级的文件。 + prefix = prefix + "/" + matter.Name + + sortArray := []builder.OrderPair{ + { + Key: "path", + Value: "ASC", + }, + } + matters := this.matterDao.List(matter.Uuid, matter.UserUuid, sortArray) + + // 通过文件信息,创建 zip 的文件信息 + fileHeader, err := zip.FileInfoHeader(fileInfo) + this.PanicError(err) + + //写入头信息。目录无需写入内容。目录的头部要去掉斜杠,尾部要加上斜杠。 + fileHeader.Name = strings.TrimPrefix(prefix+"/", string(filepath.Separator)) + + fmt.Println("文件夹头: " + fileHeader.Name) + + // 写入文件信息,并返回一个 Write 结构 + _, err = zipWriter.CreateHeader(fileHeader) + this.PanicError(err) + + for _, subMatter := range matters { + this.zipCompress(subMatter, prefix, zipWriter) + } + + } else { + + fileHeader, err := zip.FileInfoHeader(fileInfo) + if err != nil { + panic(err) + } + + //第一个斜杠不需要。 + fileHeader.Name = strings.TrimPrefix(prefix+"/"+matter.Name, string(filepath.Separator)) + + fmt.Println("文件头部: " + fileHeader.Name) + + writer, err := zipWriter.CreateHeader(fileHeader) + if err != nil { + panic(err) + } + + file, err := os.Open(matter.AbsolutePath()) + _, err = io.Copy(writer, file) + defer func() { + err := file.Close() + this.PanicError(err) + }() + + } + +} + //删除文件 func (this *MatterService) AtomicDelete(matter *Matter) { @@ -117,8 +249,7 @@ func (this *MatterService) Upload(file io.Reader, user *User, dirMatter *Matter, util.MakeDirAll(dirAbsolutePath) //如果文件已经存在了,那么直接覆盖。 - exist, err := util.PathExists(fileAbsolutePath) - this.PanicError(err) + exist := util.PathExists(fileAbsolutePath) if exist { this.logger.Error("%s已经存在,将其删除", fileAbsolutePath) removeError := os.Remove(fileAbsolutePath) @@ -214,12 +345,10 @@ func (this *MatterService) createDirectory(dirMatter *Matter, name string, user panic(result.BadRequest(`名称中不能包含以下特殊符号:< > | * ? / \`)) } - //判断同级文件夹中是否有同名的文件夹 - count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, true, name) - - if count > 0 { - - panic(result.BadRequest("%s 已经存在了,请使用其他名称。", name)) + //判断同级文件夹中是否有同名的文件夹。存在了直接返回即可。 + matter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, true, name) + if matter != nil { + return matter } parts := strings.Split(dirMatter.Path, "/") @@ -240,7 +369,7 @@ func (this *MatterService) createDirectory(dirMatter *Matter, name string, user this.logger.Info("Create Directory: %s", dirPath) //数据库中创建文件夹。 - matter := &Matter{ + matter = &Matter{ Puuid: dirMatter.Uuid, UserUuid: user.Uuid, Username: user.Username, @@ -569,7 +698,96 @@ func (this *MatterService) AtomicRename(matter *Matter, name string, user *User) return } -//根据一个文件夹路径,依次创建,找到最后一个文件夹的matter,如果中途出错,返回err. +//将本地文件映射到蓝眼云盘中去。 +func (this *MatterService) AtomicMirror(srcPath string, destPath string, overwrite bool, user *User) { + + if user == nil { + panic(result.BadRequest("user cannot be nil")) + } + + //操作锁 + this.userService.MatterLock(user.Uuid) + defer this.userService.MatterUnlock(user.Uuid) + + //验证参数。 + if destPath == "" { + panic(result.BadRequest("dest 参数必填")) + } + + destDirMatter := this.CreateDirectories(user, destPath) + + this.mirror(srcPath, destDirMatter, overwrite, user) +} + +//将本地文件/文件夹映射到蓝眼云盘中去。 +func (this *MatterService) mirror(srcPath string, destDirMatter *Matter, overwrite bool, user *User) { + + if user == nil { + panic(result.BadRequest("user cannot be nil")) + } + + fileStat, err := os.Stat(srcPath) + if err != nil { + + if os.IsNotExist(err) { + panic(result.BadRequest("srcPath %s not exist", srcPath)) + } else { + + panic(result.BadRequest("srcPath err %s %s", srcPath, err.Error())) + } + + } + + this.logger.Info("mirror srcPath = %s destPath = %s", srcPath, destDirMatter.Path) + + if fileStat.IsDir() { + + //判断当前文件夹下,文件是否已经存在了。 + srcDirMatter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, destDirMatter.Uuid, true, fileStat.Name()) + + if srcDirMatter == nil { + srcDirMatter = this.createDirectory(destDirMatter, fileStat.Name(), user) + } + + fileInfos, err := ioutil.ReadDir(srcPath) + this.PanicError(err) + + //递归处理本文件夹下的文件或文件夹 + for _, fileInfo := range fileInfos { + + path := fmt.Sprintf("%s/%s", srcPath, fileInfo.Name()) + this.mirror(path, srcDirMatter, overwrite, user) + } + + } else { + + //判断当前文件夹下,文件是否已经存在了。 + matter := this.matterDao.FindByUserUuidAndPuuidAndDirAndName(user.Uuid, destDirMatter.Uuid, false, fileStat.Name()) + if matter != nil { + //如果是覆盖,那么删除之前的文件 + if overwrite { + this.matterDao.Delete(matter) + } else { + //直接完成。 + return + } + } + + //准备直接从本地上传了。 + file, err := os.Open(srcPath) + this.PanicError(err) + defer func() { + err := file.Close() + this.PanicError(err) + }() + + this.Upload(file, user, destDirMatter, fileStat.Name(), true) + + } + +} + +//根据一个文件夹路径,依次创建,找到最后一个文件夹的matter,如果中途出错,返回err. 如果存在了那就直接返回即可。 func (this *MatterService) CreateDirectories(user *User, dirPath string) *Matter { if dirPath == "" { diff --git a/code/rest/preference_controller.go b/code/rest/preference_controller.go index 8b0e6c4..1cb6c6f 100644 --- a/code/rest/preference_controller.go +++ b/code/rest/preference_controller.go @@ -36,6 +36,7 @@ func (this *PreferenceController) RegisterRoutes() map[string]func(writer http.R routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request)) //每个Controller需要主动注册自己的路由。 + routeMap["/api/preference/ping"] = this.Wrap(this.Ping, USER_ROLE_GUEST) routeMap["/api/preference/fetch"] = this.Wrap(this.Fetch, USER_ROLE_GUEST) routeMap["/api/preference/edit"] = this.Wrap(this.Edit, USER_ROLE_ADMINISTRATOR) routeMap["/api/preference/system_cleanup"] = this.Wrap(this.SystemCleanup, USER_ROLE_ADMINISTRATOR) @@ -43,6 +44,13 @@ func (this *PreferenceController) RegisterRoutes() map[string]func(writer http.R return routeMap } +//简单验证蓝眼云盘服务是否已经启动了。 +func (this *PreferenceController) Ping(writer http.ResponseWriter, request *http.Request) *result.WebResult { + + return this.Success(nil) + +} + //查看某个偏好设置的详情。 func (this *PreferenceController) Fetch(writer http.ResponseWriter, request *http.Request) *result.WebResult { diff --git a/code/rest/user_controller.go b/code/rest/user_controller.go index e87340f..7fa4c2e 100644 --- a/code/rest/user_controller.go +++ b/code/rest/user_controller.go @@ -42,28 +42,30 @@ func (this *UserController) RegisterRoutes() map[string]func(writer http.Respons //使用用户名和密码进行登录。 //参数: -// @email:邮箱 +// @username:用户名(也可以输入邮箱) // @password:密码 func (this *UserController) Login(writer http.ResponseWriter, request *http.Request) *result.WebResult { - email := request.FormValue("email") + username := request.FormValue("username") password := request.FormValue("password") - if "" == email || "" == password { + if "" == username || "" == password { - panic(result.BadRequest("请输入邮箱和密码")) + panic(result.BadRequest("请输入用户名和密码")) } - user := this.userDao.FindByEmail(email) + user := this.userDao.FindByUsername(username) if user == nil { - - panic(result.BadRequest("邮箱或密码错误")) - - } else { - if !util.MatchBcrypt(password, user.Password) { - - panic(result.BadRequest("邮箱或密码错误")) + user = this.userDao.FindByEmail(username) + if user == nil { + panic(result.BadRequest("用户名或密码错误")) } + + } + + if !util.MatchBcrypt(password, user.Password) { + + panic(result.BadRequest("用户名或密码错误")) } //登录成功,设置Cookie。有效期30天。 diff --git a/code/rest/user_service.go b/code/rest/user_service.go index 8aa7719..df7f439 100644 --- a/code/rest/user_service.go +++ b/code/rest/user_service.go @@ -4,6 +4,8 @@ import ( "github.com/eyebluecn/tank/code/core" "github.com/eyebluecn/tank/code/tool/cache" "github.com/eyebluecn/tank/code/tool/result" + "github.com/eyebluecn/tank/code/tool/util" + uuid "github.com/nu7hatch/gouuid" "net/http" "time" ) @@ -76,36 +78,68 @@ func (this *UserService) PreHandle(writer http.ResponseWriter, request *http.Req //登录身份有效期以数据库中记录的为准 //验证用户是否已经登录。 - sessionCookie, err := request.Cookie(core.COOKIE_AUTH_KEY) - if err != nil { - return - } + sessionId := util.GetSessionUuidFromRequest(request, core.COOKIE_AUTH_KEY) - sessionId := sessionCookie.Value + if sessionId != "" { - //去缓存中捞取 - cacheItem, err := core.CONTEXT.GetSessionCache().Value(sessionId) - if err != nil { - this.logger.Error("获取缓存时出错了" + err.Error()) - } + //去缓存中捞取 + cacheItem, err := core.CONTEXT.GetSessionCache().Value(sessionId) + if err != nil { + this.logger.Error("获取缓存时出错了" + err.Error()) + } - //缓存中没有,尝试去数据库捞取 - if cacheItem == nil || cacheItem.Data() == nil { - session := this.sessionDao.FindByUuid(sessionCookie.Value) - if session != nil { - duration := session.ExpireTime.Sub(time.Now()) - if duration <= 0 { - this.logger.Error("登录信息已过期") - } else { - user := this.userDao.FindByUuid(session.UserUuid) - if user != nil { - //将用户装填进缓存中 - core.CONTEXT.GetSessionCache().Add(sessionCookie.Value, duration, user) + //缓存中没有,尝试去数据库捞取 + if cacheItem == nil || cacheItem.Data() == nil { + session := this.sessionDao.FindByUuid(sessionId) + if session != nil { + duration := session.ExpireTime.Sub(time.Now()) + if duration <= 0 { + this.logger.Error("登录信息已过期") } else { - this.logger.Error("没有找到对应的user " + session.UserUuid) + user := this.userDao.FindByUuid(session.UserUuid) + if user != nil { + //将用户装填进缓存中 + core.CONTEXT.GetSessionCache().Add(sessionId, duration, user) + } else { + this.logger.Error("没有找到对应的user %s", session.UserUuid) + } } } } } + //再尝试读取一次,这次从 USERNAME_KEY PASSWORD_KEY 中装填用户登录信息 + cacheItem, err := core.CONTEXT.GetSessionCache().Value(sessionId) + if err != nil { + this.logger.Error("获取缓存时出错了" + err.Error()) + } + + if cacheItem == nil || cacheItem.Data() == nil { + username := request.FormValue(core.USERNAME_KEY) + password := request.FormValue(core.PASSWORD_KEY) + + if username != "" && password != "" { + + user := this.userDao.FindByUsername(username) + if user == nil { + this.logger.Error("%s 用户名或密码错误", core.USERNAME_KEY) + } else { + + if !util.MatchBcrypt(password, user.Password) { + this.logger.Error("%s 用户名或密码错误", core.USERNAME_KEY) + } else { + //装填一个临时的session用作后续使用。 + this.logger.Info("准备装载一个临时的用作。") + timeUUID, _ := uuid.NewV4() + uuidStr := string(timeUUID.String()) + request.Form[core.COOKIE_AUTH_KEY] = []string{uuidStr} + + //将用户装填进缓存中 + core.CONTEXT.GetSessionCache().Add(uuidStr, 10*time.Second, user) + } + } + + } + } + } diff --git a/code/support/tank_command.go b/code/support/tank_command.go new file mode 100644 index 0000000..6616fa9 --- /dev/null +++ b/code/support/tank_command.go @@ -0,0 +1,151 @@ +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_config.go b/code/support/tank_config.go index ea6c1ed..7128b6e 100644 --- a/code/support/tank_config.go +++ b/code/support/tank_config.go @@ -107,8 +107,8 @@ func (this *TankConfig) Init() { stream.WriteString(t.Local().Format("2006-01-02 15:04:05")) }, nil) - //默认从6010端口启动 - this.serverPort = 6010 + //默认从core.DEFAULT_SERVER_PORT端口启动 + this.serverPort = core.DEFAULT_SERVER_PORT this.ReadFromConfigFile() diff --git a/code/support/tank_logger.go b/code/support/tank_logger.go index 1ef0a1a..48e74d6 100644 --- a/code/support/tank_logger.go +++ b/code/support/tank_logger.go @@ -116,8 +116,7 @@ func (this *TankLogger) maintain() { this.Log("删除日志文件 %s", oldDestPath) //删除文件 - exists, err := util.PathExists(oldDestPath) - util.PanicError(err) + exists := util.PathExists(oldDestPath) if exists { err = os.Remove(oldDestPath) if err != nil { diff --git a/code/support/tank_router.go b/code/support/tank_router.go index 38c6571..b5798a7 100644 --- a/code/support/tank_router.go +++ b/code/support/tank_router.go @@ -2,7 +2,6 @@ package support import ( "fmt" - "github.com/eyebluecn/tank/code/core" "github.com/eyebluecn/tank/code/rest" "github.com/eyebluecn/tank/code/tool/result" @@ -171,6 +170,7 @@ func (this *TankRouter) ServeHTTP(writer http.ResponseWriter, request *http.Requ } } else { + //当作静态资源处理。默认从当前文件下面的static文件夹中取东西。 dir := util.GetHtmlPath() @@ -180,10 +180,10 @@ func (this *TankRouter) ServeHTTP(writer http.ResponseWriter, request *http.Requ } filePath := dir + requestURI - exists, _ := util.PathExists(filePath) + exists := util.PathExists(filePath) if !exists { filePath = dir + "/index.html" - exists, _ = util.PathExists(filePath) + exists = util.PathExists(filePath) if !exists { panic(fmt.Sprintf("404 not found:%s", filePath)) } diff --git a/code/test/main_test.go b/code/test/main_test.go index d00eec6..b0bdc36 100644 --- a/code/test/main_test.go +++ b/code/test/main_test.go @@ -50,3 +50,10 @@ func TestDayAgo(t *testing.T) { fmt.Printf("%s\n", util.ConvertTimeToDateTimeString(thenDay)) } + +//测试 打包 +func TestZip(t *testing.T) { + + util.Zip("/Users/fusu/d/group/eyeblue/tank/tmp/matter/admin/root/morning", "/Users/fusu/d/group/eyeblue/tank/tmp/log/morning.zip") + +} diff --git a/code/tool/util/util.zip.go b/code/tool/util/util.zip.go new file mode 100644 index 0000000..ba646df --- /dev/null +++ b/code/tool/util/util.zip.go @@ -0,0 +1,95 @@ +package util + +import ( + "archive/zip" + "fmt" + "github.com/eyebluecn/tank/code/tool/result" + "io" + "log" + "os" + "path/filepath" + "strings" +) + +func Zip(srcPath string, destPath string) { + + if PathExists(destPath) { + panic(result.BadRequest("%s 已经存在了", destPath)) + } + + // 创建准备写入的文件 + fileWriter, err := os.Create(destPath) + PanicError(err) + defer func() { + err := fileWriter.Close() + PanicError(err) + }() + + // 通过 fileWriter 来创建 zip.Write + zipWriter := zip.NewWriter(fileWriter) + defer func() { + // 检测一下是否成功关闭 + if err := zipWriter.Close(); err != nil { + log.Fatalln(err) + } + }() + + // 下面来将文件写入 zipWriter ,因为有可能会有很多个目录及文件,所以递归处理 + err = filepath.Walk(srcPath, func(path string, fileInfo os.FileInfo, errBack error) (err error) { + if errBack != nil { + return errBack + } + + fmt.Println("遍历文件: " + path) + + // 通过文件信息,创建 zip 的文件信息 + fileHeader, err := zip.FileInfoHeader(fileInfo) + if err != nil { + return + } + + // 替换文件信息中的文件名 + fileHeader.Name = strings.TrimPrefix(path, string(filepath.Separator)) + + // 目录加上/ + if fileInfo.IsDir() { + fileHeader.Name += "/" + } + + fmt.Println("头部情况: " + fileHeader.Name) + + // 写入文件信息,并返回一个 Write 结构 + writer, err := zipWriter.CreateHeader(fileHeader) + if err != nil { + return + } + + // 检测,如果不是标准文件就只写入头信息,不写入文件数据到 writer + // 如目录,也没有数据需要写 + if !fileHeader.Mode().IsRegular() { + return nil + } + + // 打开要压缩的文件 + fileToBeZip, err := os.Open(path) + defer func() { + err = fileToBeZip.Close() + PanicError(err) + }() + if err != nil { + return + } + + // 将打开的文件 Copy 到 writer + _, err = io.Copy(writer, fileToBeZip) + if err != nil { + return + } + + // 输出压缩的内容 + //fmt.Printf("成功压缩文件: %s, 共写入了 %d 个字符的数据\n", path, n) + + return nil + }) + PanicError(err) +} diff --git a/code/tool/util/util_env.go b/code/tool/util/util_env.go new file mode 100644 index 0000000..0b9492a --- /dev/null +++ b/code/tool/util/util_env.go @@ -0,0 +1,46 @@ +package util + +import ( + "os" + "os/user" + "strings" +) + +//是否为win开发环境 +func EnvWinDevelopment() bool { + + ex, err := os.Executable() + if err != nil { + panic(err) + } + + //如果exPath中包含了 \\AppData\\Local\\Temp 我们认为是在Win的开发环境中 + systemUser, err := user.Current() + if systemUser != nil { + + return strings.HasPrefix(ex, systemUser.HomeDir+"\\AppData\\Local\\Temp") + + } + + return false + +} + +//是否为mac开发环境 +func EnvMacDevelopment() bool { + + ex, err := os.Executable() + if err != nil { + panic(err) + } + + return strings.HasPrefix(ex, "/private/var/folders") + +} + +//是否为开发环境 (即是否在IDE中运行) +func EnvDevelopment() bool { + + return EnvWinDevelopment() || EnvMacDevelopment() + +} diff --git a/code/tool/util/util_file.go b/code/tool/util/util_file.go index 746169b..10a48eb 100644 --- a/code/tool/util/util_file.go +++ b/code/tool/util/util_file.go @@ -7,22 +7,23 @@ import ( "io" "io/ioutil" "os" - "os/user" "path/filepath" "runtime" "strings" ) //判断文件或文件夹是否已经存在 -func PathExists(path string) (bool, error) { +func PathExists(path string) bool { _, err := os.Stat(path) if err == nil { - return true, nil + return true + } else { + if os.IsNotExist(err) { + return false + } else { + panic(result.BadRequest(err.Error())) + } } - if os.IsNotExist(err) { - return false, nil - } - return false, err } //获取GOPATH路径 @@ -58,21 +59,12 @@ func GetHomePath() string { } exPath := filepath.Dir(ex) - GetDevHomePath() - - //如果exPath中包含了 /private/var/folders 我们认为是在Mac的开发环境中 - macDev := strings.HasPrefix(exPath, "/private/var/folders") - if macDev { + if EnvMacDevelopment() { exPath = GetDevHomePath() + "/tmp" } - //如果exPath中包含了 \\AppData\\Local\\Temp 我们认为是在Win的开发环境中 - systemUser, err := user.Current() - if systemUser != nil { - winDev := strings.HasPrefix(exPath, systemUser.HomeDir+"\\AppData\\Local\\Temp") - if winDev { - exPath = GetDevHomePath() + "/tmp" - } + if EnvWinDevelopment() { + exPath = GetDevHomePath() + "/tmp" } return exPath @@ -84,12 +76,10 @@ func GetHtmlPath() string { homePath := GetHomePath() filePath := homePath + "/html" - exists, err := PathExists(filePath) - if err != nil { - panic("判断上传文件是否存在时出错!") - } + exists := PathExists(filePath) + if !exists { - err = os.MkdirAll(filePath, 0777) + err := os.MkdirAll(filePath, 0777) if err != nil { panic("创建上传文件夹时出错!") } @@ -101,13 +91,11 @@ func GetHtmlPath() string { //如果文件夹存在就不管,不存在就创建。 例如:/var/www/matter func MakeDirAll(dirPath string) string { - exists, err := PathExists(dirPath) - if err != nil { - panic("判断文件是否存在时出错!") - } + exists := PathExists(dirPath) + if !exists { //TODO:文件权限需要进一步考虑 - err = os.MkdirAll(dirPath, 0777) + err := os.MkdirAll(dirPath, 0777) if err != nil { panic("创建文件夹时出错!") } @@ -178,16 +166,13 @@ func DeleteEmptyDirRecursive(dirPath string) { } } -//移除某个文件夹。 例如:/var/www/matter => /var/www +//移除某个文件夹。 func RemoveDirectory(dirPath string) string { - exists, err := PathExists(dirPath) - if err != nil { - panic("判断文件是否存在时出错!") - } + exists := PathExists(dirPath) if exists { - err = os.Remove(dirPath) + err := os.Remove(dirPath) if err != nil { panic("删除文件夹时出错!") } @@ -202,12 +187,10 @@ func GetConfPath() string { homePath := GetHomePath() filePath := homePath + "/conf" - exists, err := PathExists(filePath) - if err != nil { - panic("判断日志文件夹是否存在时出错!") - } + exists := PathExists(filePath) + if !exists { - err = os.MkdirAll(filePath, 0777) + err := os.MkdirAll(filePath, 0777) if err != nil { panic("创建日志文件夹时出错!") } @@ -222,12 +205,10 @@ func GetLogPath() string { homePath := GetHomePath() filePath := homePath + "/log" - exists, err := PathExists(filePath) - if err != nil { - panic("判断日志文件夹是否存在时出错!") - } + exists := PathExists(filePath) + if !exists { - err = os.MkdirAll(filePath, 0777) + err := os.MkdirAll(filePath, 0777) if err != nil { panic("创建日志文件夹时出错!") } diff --git a/go.sum b/go.sum index b30f5fc..6c657f1 100644 --- a/go.sum +++ b/go.sum @@ -159,6 +159,7 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go index 72899ff..791d261 100644 --- a/main.go +++ b/main.go @@ -11,24 +11,31 @@ import ( func main() { - //第一步。日志 + //第一步。命令行工具处理 + tankCommand := &support.TankCommand{} + core.COMMAND = tankCommand + if core.COMMAND.Cli() { + return + } + + //第二步。日志 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服务 http.Handle("/", core.CONTEXT) core.LOGGER.Info("App started at http://localhost:%v", core.CONFIG.ServerPort())