Refine the rest structure.

This commit is contained in:
zicla
2019-04-26 11:43:54 +08:00
parent c55e8699b4
commit e88930e13a
49 changed files with 201 additions and 183 deletions

View File

@ -0,0 +1,426 @@
package rest
import (
"fmt"
"net/http"
"regexp"
"strconv"
"tank/code/tool/result"
"tank/code/tool/util"
"time"
)
type AlienController struct {
BaseController
uploadTokenDao *UploadTokenDao
downloadTokenDao *DownloadTokenDao
matterDao *MatterDao
matterService *MatterService
imageCacheDao *ImageCacheDao
imageCacheService *ImageCacheService
alienService *AlienService
}
//初始化方法
func (this *AlienController) Init() {
this.BaseController.Init()
//手动装填本实例的Bean.
b := CONTEXT.GetBean(this.uploadTokenDao)
if c, ok := b.(*UploadTokenDao); ok {
this.uploadTokenDao = c
}
b = CONTEXT.GetBean(this.downloadTokenDao)
if c, ok := b.(*DownloadTokenDao); ok {
this.downloadTokenDao = c
}
b = CONTEXT.GetBean(this.matterDao)
if c, ok := b.(*MatterDao); ok {
this.matterDao = c
}
b = CONTEXT.GetBean(this.matterService)
if c, ok := b.(*MatterService); ok {
this.matterService = c
}
b = CONTEXT.GetBean(this.imageCacheDao)
if c, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = c
}
b = CONTEXT.GetBean(this.imageCacheService)
if c, ok := b.(*ImageCacheService); ok {
this.imageCacheService = c
}
b = CONTEXT.GetBean(this.alienService)
if c, ok := b.(*AlienService); ok {
this.alienService = c
}
}
//注册自己的路由。
func (this *AlienController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
//每个Controller需要主动注册自己的路由。
routeMap["/api/alien/fetch/upload/token"] = this.Wrap(this.FetchUploadToken, USER_ROLE_GUEST)
routeMap["/api/alien/fetch/download/token"] = this.Wrap(this.FetchDownloadToken, USER_ROLE_GUEST)
routeMap["/api/alien/confirm"] = this.Wrap(this.Confirm, USER_ROLE_GUEST)
routeMap["/api/alien/upload"] = this.Wrap(this.Upload, USER_ROLE_GUEST)
routeMap["/api/alien/crawl/token"] = this.Wrap(this.CrawlToken, USER_ROLE_GUEST)
routeMap["/api/alien/crawl/direct"] = this.Wrap(this.CrawlDirect, USER_ROLE_GUEST)
return routeMap
}
//处理一些特殊的接口,比如参数包含在路径中,一般情况下controller不将参数放在url路径中
func (this *AlienController) HandleRoutes(writer http.ResponseWriter, request *http.Request) (func(writer http.ResponseWriter, request *http.Request), bool) {
path := request.URL.Path
//匹配 /api/alien/preview/{uuid}/{filename} (响应头不包含 content-disposition)
reg := regexp.MustCompile(`^/api/alien/preview/([^/]+)/([^/]+)$`)
strs := reg.FindStringSubmatch(path)
if len(strs) == 3 {
var f = func(writer http.ResponseWriter, request *http.Request) {
this.Preview(writer, request, strs[1], strs[2])
}
return f, true
}
//匹配 /api/alien/download/{uuid}/{filename} (响应头包含 content-disposition)
reg = regexp.MustCompile(`^/api/alien/download/([^/]+)/([^/]+)$`)
strs = reg.FindStringSubmatch(path)
if len(strs) == 3 {
var f = func(writer http.ResponseWriter, request *http.Request) {
this.Download(writer, request, strs[1], strs[2])
}
return f, true
}
return nil, false
}
//直接从cookie中获取用户信息或者使用邮箱和密码获取用户
func (this *AlienController) CheckRequestUser(writer http.ResponseWriter, request *http.Request) *User {
//根据用户登录信息取
user := this.findUser(writer, request)
if user != nil {
return user;
}
email := request.FormValue("email")
if email == "" {
panic("邮箱必填啦")
}
password := request.FormValue("password")
if password == "" {
panic("密码必填")
}
//验证用户身份合法性。
user = this.userDao.FindByEmail(email)
if user == nil {
panic(`邮箱或密码错误`)
} else {
if !util.MatchBcrypt(password, user.Password) {
panic(`邮箱或密码错误`)
}
}
return user
}
//系统中的用户x要获取一个UploadToken用于提供给x信任的用户上传文件。
func (this *AlienController) FetchUploadToken(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//文件名。
filename := request.FormValue("filename")
if filename == "" {
panic("文件名必填")
} else if m, _ := regexp.MatchString(`[<>|*?/\\]`, filename); m {
panic(fmt.Sprintf(`【%s】不符合要求文件名中不能包含以下特殊符号< > | * ? / \`, filename))
}
//什么时间后过期默认24h
expireStr := request.FormValue("expire")
expire := 24 * 60 * 60
if expireStr != "" {
var err error
expire, err = strconv.Atoi(expireStr)
if err != nil {
panic(`过期时间不符合规范`)
}
if expire < 1 {
panic(`过期时间不符合规范`)
}
}
//文件公有或私有
privacyStr := request.FormValue("privacy")
var privacy bool
if privacyStr == "" {
panic(`文件公有性必填`)
} else {
if privacyStr == TRUE {
privacy = true
} else if privacyStr == "false" {
privacy = false
} else {
panic(`文件公有性不符合规范`)
}
}
//文件大小
sizeStr := request.FormValue("size")
var size int64
if sizeStr == "" {
panic(`文件大小必填`)
} else {
var err error
size, err = strconv.ParseInt(sizeStr, 10, 64)
if err != nil {
panic(`文件大小不符合规范`)
}
if size < 1 {
panic(`文件大小不符合规范`)
}
}
//文件夹路径,以 / 开头。
dir := request.FormValue("dir")
user := this.CheckRequestUser(writer, request)
dirMatter := this.matterService.CreateDirectories(user, dir)
mm, _ := time.ParseDuration(fmt.Sprintf("%ds", expire))
uploadToken := &UploadToken{
UserUuid: user.Uuid,
FolderUuid: dirMatter.Uuid,
MatterUuid: "",
ExpireTime: time.Now().Add(mm),
Filename: filename,
Privacy: privacy,
Size: size,
Ip: util.GetIpAddress(request),
}
uploadToken = this.uploadTokenDao.Create(uploadToken)
return this.Success(uploadToken)
}
//系统中的用户x 拿着某个文件的uuid来确认是否其信任的用户已经上传好了。
func (this *AlienController) Confirm(writer http.ResponseWriter, request *http.Request) *result.WebResult {
matterUuid := request.FormValue("matterUuid")
if matterUuid == "" {
panic("matterUuid必填")
}
user := this.CheckRequestUser(writer, request)
matter := this.matterDao.CheckByUuid(matterUuid)
if matter.UserUuid != user.Uuid {
panic("文件不属于你")
}
return this.Success(matter)
}
//系统中的用户x 信任的用户上传文件。这个接口需要支持跨域。
func (this *AlienController) Upload(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//允许跨域请求。
this.allowCORS(writer)
if request.Method == "OPTIONS" {
return this.Success("OK")
}
uploadTokenUuid := request.FormValue("uploadTokenUuid")
if uploadTokenUuid == "" {
panic("uploadTokenUuid必填")
}
uploadToken := this.uploadTokenDao.FindByUuid(uploadTokenUuid)
if uploadToken == nil {
panic("uploadTokenUuid无效")
}
if uploadToken.ExpireTime.Before(time.Now()) {
panic("uploadToken已失效")
}
user := this.userDao.CheckByUuid(uploadToken.UserUuid)
err := request.ParseMultipartForm(32 << 20)
this.PanicError(err)
file, handler, err := request.FormFile("file")
this.PanicError(err)
defer func() {
e := file.Close()
this.PanicError(e)
}()
if handler.Filename != uploadToken.Filename {
panic("文件名称不正确")
}
if handler.Size != uploadToken.Size {
panic("文件大小不正确")
}
dirMatter := this.matterDao.CheckWithRootByUuid(uploadToken.FolderUuid, user)
matter := this.matterService.AtomicUpload(file, user, dirMatter, uploadToken.Filename, uploadToken.Privacy)
//更新这个uploadToken的信息.
uploadToken.ExpireTime = time.Now()
this.uploadTokenDao.Save(uploadToken)
return this.Success(matter)
}
//给一个指定的url从该url中去拉取文件回来。此处采用uploadToken的模式。
func (this *AlienController) CrawlToken(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//允许跨域请求。
this.allowCORS(writer)
if request.Method == "OPTIONS" {
return this.Success("OK")
}
uploadTokenUuid := request.FormValue("uploadTokenUuid")
url := request.FormValue("url")
if uploadTokenUuid == "" {
panic("uploadTokenUuid必填")
}
uploadToken := this.uploadTokenDao.FindByUuid(uploadTokenUuid)
if uploadToken == nil {
panic("uploadTokenUuid无效")
}
if uploadToken.ExpireTime.Before(time.Now()) {
panic("uploadToken已失效")
}
user := this.userDao.CheckByUuid(uploadToken.UserUuid)
dirMatter := this.matterDao.CheckWithRootByUuid(uploadToken.FolderUuid, user)
matter := this.matterService.AtomicCrawl(url, uploadToken.Filename, user, dirMatter, uploadToken.Privacy)
//更新这个uploadToken的信息.
uploadToken.ExpireTime = time.Now()
this.uploadTokenDao.Save(uploadToken)
return this.Success(matter)
}
//通过一个url直接上传无需借助uploadToken.
func (this *AlienController) CrawlDirect(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//文件名。
filename := request.FormValue("filename")
//文件公有或私有
privacyStr := request.FormValue("privacy")
//文件夹路径,以 / 开头。
dir := request.FormValue("dir")
url := request.FormValue("url")
if filename == "" {
panic("文件名必填")
} else if m, _ := regexp.MatchString(`[<>|*?/\\]`, filename); m {
panic(fmt.Sprintf(`【%s】不符合要求文件名中不能包含以下特殊符号< > | * ? / \`, filename))
}
var privacy bool
if privacyStr == "" {
panic(`文件公有性必填`)
} else {
if privacyStr == TRUE {
privacy = true
} else if privacyStr == FALSE {
privacy = false
} else {
panic(`文件公有性不符合规范`)
}
}
user := this.CheckRequestUser(writer, request)
dirMatter := this.matterService.CreateDirectories(user, dir)
matter := this.matterService.AtomicCrawl(url, filename, user, dirMatter, privacy)
return this.Success(matter)
}
//系统中的用户x要获取一个DownloadToken用于提供给x信任的用户下载文件。
func (this *AlienController) FetchDownloadToken(writer http.ResponseWriter, request *http.Request) *result.WebResult {
matterUuid := request.FormValue("matterUuid")
if matterUuid == "" {
panic("matterUuid必填")
}
user := this.CheckRequestUser(writer, request)
matter := this.matterDao.CheckByUuid(matterUuid)
if matter.UserUuid != user.Uuid {
panic("文件不属于你")
}
if matter.Dir {
panic("不支持下载文件夹")
}
//什么时间后过期默认24h
expireStr := request.FormValue("expire")
expire := 24 * 60 * 60
if expireStr != "" {
var err error
expire, err = strconv.Atoi(expireStr)
if err != nil {
panic(`过期时间不符合规范`)
}
if expire < 1 {
panic(`过期时间不符合规范`)
}
}
mm, _ := time.ParseDuration(fmt.Sprintf("%ds", expire))
downloadToken := &DownloadToken{
UserUuid: user.Uuid,
MatterUuid: matterUuid,
ExpireTime: time.Now().Add(mm),
Ip: util.GetIpAddress(request),
}
downloadToken = this.downloadTokenDao.Create(downloadToken)
return this.Success(downloadToken)
}
//预览一个文件。既可以使用登录的方式,也可以使用授权的方式
func (this *AlienController) Preview(writer http.ResponseWriter, request *http.Request, uuid string, filename string) {
this.alienService.PreviewOrDownload(writer, request, uuid, filename, false)
}
//下载一个文件。既可以使用登录的方式,也可以使用授权的方式
func (this *AlienController) Download(writer http.ResponseWriter, request *http.Request, uuid string, filename string) {
this.alienService.PreviewOrDownload(writer, request, uuid, filename, true)
}

141
code/rest/alien_service.go Normal file
View File

@ -0,0 +1,141 @@
package rest
import (
"fmt"
"net/http"
"tank/code/tool/result"
"tank/code/tool/util"
"time"
)
//@Service
type AlienService struct {
Bean
matterDao *MatterDao
matterService *MatterService
userDao *UserDao
uploadTokenDao *UploadTokenDao
downloadTokenDao *DownloadTokenDao
imageCacheDao *ImageCacheDao
imageCacheService *ImageCacheService
}
//初始化方法
func (this *AlienService) Init() {
this.Bean.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.matterDao)
if b, ok := b.(*MatterDao); ok {
this.matterDao = b
}
b = CONTEXT.GetBean(this.matterService)
if b, ok := b.(*MatterService); ok {
this.matterService = b
}
b = CONTEXT.GetBean(this.userDao)
if b, ok := b.(*UserDao); ok {
this.userDao = b
}
b = CONTEXT.GetBean(this.uploadTokenDao)
if c, ok := b.(*UploadTokenDao); ok {
this.uploadTokenDao = c
}
b = CONTEXT.GetBean(this.downloadTokenDao)
if c, ok := b.(*DownloadTokenDao); ok {
this.downloadTokenDao = c
}
b = CONTEXT.GetBean(this.imageCacheDao)
if c, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = c
}
b = CONTEXT.GetBean(this.imageCacheService)
if c, ok := b.(*ImageCacheService); ok {
this.imageCacheService = c
}
}
//预览或者下载的统一处理.
func (this *AlienService) PreviewOrDownload(
writer http.ResponseWriter,
request *http.Request,
uuid string,
filename string,
withContentDisposition bool) {
matter := this.matterDao.CheckByUuid(uuid)
//判断是否是文件夹
if matter.Dir {
panic("不支持下载文件夹")
}
if matter.Name != filename {
panic("文件信息错误")
}
//验证用户的权限问题。
//文件如果是私有的才需要权限
if matter.Privacy {
//1.如果带有downloadTokenUuid那么就按照token的信息去获取。
downloadTokenUuid := request.FormValue("downloadTokenUuid")
if downloadTokenUuid != "" {
downloadToken := this.downloadTokenDao.CheckByUuid(downloadTokenUuid)
if downloadToken.ExpireTime.Before(time.Now()) {
panic("downloadToken已失效")
}
if downloadToken.MatterUuid != uuid {
panic("token和文件信息不一致")
}
tokenUser := this.userDao.CheckByUuid(downloadToken.UserUuid)
if matter.UserUuid != tokenUser.Uuid {
panic(result.CODE_WRAPPER_UNAUTHORIZED)
}
//下载之后立即过期掉。如果是分块下载的,必须以最终获取到完整的数据为准。
downloadToken.ExpireTime = time.Now()
this.downloadTokenDao.Save(downloadToken)
} else {
//判断文件的所属人是否正确
operator := this.findUser(writer, request)
if operator == nil || (operator.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != operator.Uuid) {
panic(result.CODE_WRAPPER_UNAUTHORIZED)
}
}
}
//对图片处理。
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)
}
//文件下载次数加一,为了加快访问速度,异步进行
go util.SafeMethod(func() {
this.matterDao.TimesIncrement(uuid)
})
}

View File

@ -0,0 +1,126 @@
package rest
import (
"fmt"
"github.com/json-iterator/go"
"go/types"
"net/http"
"tank/code/tool/result"
)
type IController interface {
IBean
//注册自己固定的路由。
RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request)
//处理一些特殊的路由。
HandleRoutes(writer http.ResponseWriter, request *http.Request) (func(writer http.ResponseWriter, request *http.Request), bool)
}
type BaseController struct {
Bean
userDao *UserDao
sessionDao *SessionDao
}
func (this *BaseController) Init() {
this.Bean.Init()
//手动装填本实例的Bean.
b := CONTEXT.GetBean(this.userDao)
if b, ok := b.(*UserDao); ok {
this.userDao = b
}
b = CONTEXT.GetBean(this.sessionDao)
if b, ok := b.(*SessionDao); ok {
this.sessionDao = b
}
}
//注册自己的路由。
func (this *BaseController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
//每个Controller需要主动注册自己的路由。
return make(map[string]func(writer http.ResponseWriter, request *http.Request))
}
//处理一些特殊的接口,比如参数包含在路径中,一般情况下controller不将参数放在url路径中
func (this *BaseController) HandleRoutes(writer http.ResponseWriter, request *http.Request) (func(writer http.ResponseWriter, request *http.Request), bool) {
return nil, false
}
//需要进行登录验证的wrap包装
func (this *BaseController) Wrap(f func(writer http.ResponseWriter, request *http.Request) *result.WebResult, qualifiedRole string) func(w http.ResponseWriter, r *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
//writer和request赋值给自己。
var webResult *result.WebResult = nil
//只有游客接口不需要登录
if qualifiedRole != USER_ROLE_GUEST {
user := this.checkUser(writer, request)
if user.Status == USER_STATUS_DISABLED {
//判断用户是否被禁用。
webResult = result.ConstWebResult(result.CODE_WRAPPER_USER_DISABLED)
} else {
if qualifiedRole == USER_ROLE_ADMINISTRATOR && user.Role != USER_ROLE_ADMINISTRATOR {
webResult = result.ConstWebResult(result.CODE_WRAPPER_UNAUTHORIZED)
} else {
webResult = f(writer, request)
}
}
} else {
webResult = f(writer, request)
}
//输出的是json格式
if webResult != nil {
//返回的内容申明是jsonutf-8
writer.Header().Set("Content-Type", "application/json;charset=UTF-8")
//用json的方式输出返回值。
b, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(webResult)
this.PanicError(err)
writer.WriteHeader(result.FetchHttpStatus(webResult.Code))
_, err = fmt.Fprintf(writer, string(b))
this.PanicError(err)
} else {
//输出的内容是二进制的。
}
}
}
//返回成功的结果。支持放置三种类型 1.字符串 2. WebResult对象 3.空指针 4.任意类型
func (this *BaseController) Success(data interface{}) *result.WebResult {
var webResult *result.WebResult = nil
if value, ok := data.(string); ok {
//返回一句普通的消息
webResult = &result.WebResult{Code: result.CODE_WRAPPER_OK.Code, Msg: value}
} else if value, ok := data.(*result.WebResult); ok {
//返回一个webResult对象
webResult = value
} else if _, ok := data.(types.Nil); ok {
//返回一个空指针
webResult = result.ConstWebResult(result.CODE_WRAPPER_OK)
} else {
//返回的类型不明确。
webResult = &result.WebResult{Code: result.CODE_WRAPPER_OK.Code, Data: data}
}
return webResult
}
//允许跨域请求
func (this *BaseController) allowCORS(writer http.ResponseWriter) {
writer.Header().Add("Access-Control-Allow-Origin", "*")
writer.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
writer.Header().Add("Access-Control-Max-Age", "3600")
}

26
code/rest/base_dao.go Normal file
View File

@ -0,0 +1,26 @@
package rest
import "tank/code/tool/builder"
type BaseDao struct {
Bean
}
//根据一个sortMap获取到order字符串
func (this *BaseDao) GetSortString(sortArray []builder.OrderPair) string {
if sortArray == nil || len(sortArray) == 0 {
return ""
}
str := ""
for _, pair := range sortArray {
if pair.Value == "DESC" || pair.Value == "ASC" {
if str != "" {
str = str + ","
}
str = str + " " + pair.Key + " " + pair.Value
}
}
return str
}

64
code/rest/base_model.go Normal file
View File

@ -0,0 +1,64 @@
package rest
import (
"math"
"reflect"
"tank/code/config"
"time"
)
const (
TRUE = "true"
FALSE = "false"
)
type Time time.Time
type IBase interface {
//返回其对应的数据库表名
TableName() string
}
//Mysql 5.5只支持一个CURRENT_TIMESTAMP的默认值因此时间的默认值都使用蓝眼云盘第一个发布版本时间 2018-01-01 00:00:00
type Base struct {
Uuid string `json:"uuid" gorm:"type:char(36);primary_key;unique"`
Sort int64 `json:"sort" gorm:"type:bigint(20) not null"`
UpdateTime time.Time `json:"updateTime" gorm:"type:timestamp not null;default:CURRENT_TIMESTAMP"`
CreateTime time.Time `json:"createTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
}
//将 Struct 转换成map[string]interface{}类型
func (this *Base) Map() map[string]interface{} {
t := reflect.TypeOf(this)
v := reflect.ValueOf(this)
var data = make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
data[t.Field(i).Name] = v.Field(i).Interface()
}
return data
}
func (this *Base) TableName() string {
return config.TABLE_PREFIX + "base"
}
//分页类
type Pager struct {
Page int `json:"page"`
PageSize int `json:"pageSize"`
TotalItems int `json:"totalItems"`
TotalPages int `json:"totalPages"`
Data interface{} `json:"data"`
}
func NewPager(page int, pageSize int, totalItems int, data interface{}) *Pager {
return &Pager{
Page: page,
PageSize: pageSize,
TotalItems: totalItems,
TotalPages: int(math.Ceil(float64(totalItems) / float64(pageSize))),
Data: data,
}
}

85
code/rest/bean.go Normal file
View File

@ -0,0 +1,85 @@
package rest
import (
"net/http"
"tank/code/config"
"tank/code/logger"
"tank/code/tool/result"
"tank/code/tool/util"
)
type IBean interface {
//初始化方法
Init()
//系统清理方法
Cleanup()
//所有配置都加载完成后调用的方法,包括数据库加载完毕
ConfigPost()
//快速的Panic方法
PanicError(err error)
}
type Bean struct {
logger *logger.Logger
}
func (this *Bean) Init() {
this.logger = logger.LOGGER
}
func (this *Bean) ConfigPost() {
}
//系统大清理,一般时产品即将上线时,清除脏数据,只执行一次。
func (this *Bean) Cleanup() {
}
//处理错误的统一方法 可以省去if err!=nil 这段代码
func (this *Bean) PanicError(err error) {
if err != nil {
panic(err)
}
}
//能找到一个user就找到一个
func (this *Bean) findUser(writer http.ResponseWriter, request *http.Request) *User {
//验证用户是否已经登录。
//登录身份有效期以数据库中记录的为准
sessionId := util.GetSessionUuidFromRequest(request, config.COOKIE_AUTH_KEY)
if sessionId == "" {
return nil
}
//去缓存中捞取看看
cacheItem, err := CONTEXT.SessionCache.Value(sessionId)
if err != nil {
this.logger.Warn("获取缓存时出错了" + err.Error())
return nil
}
if cacheItem == nil || cacheItem.Data() == nil {
this.logger.Warn("cache item中已经不存在了 ")
return nil
}
if value, ok := cacheItem.Data().(*User); ok {
return value
} else {
this.logger.Error("cache item中的类型不是*User ")
}
return nil
}
//获取当前登录的用户,找不到就返回登录错误
func (this *Bean) checkUser(writer http.ResponseWriter, request *http.Request) *User {
if this.findUser(writer, request) == nil {
panic(result.ConstWebResult(result.CODE_WRAPPER_LOGIN))
} else {
return this.findUser(writer, request)
}
}

198
code/rest/context.go Normal file
View File

@ -0,0 +1,198 @@
package rest
import (
"fmt"
"github.com/jinzhu/gorm"
"reflect"
"tank/code/config"
"tank/code/logger"
cache2 "tank/code/tool/cache"
)
//全局唯一的上下文(在main函数中初始化)
var CONTEXT = &Context{}
//上下文管理数据库连接管理所有路由请求管理所有的单例component.
type Context struct {
//数据库连接
DB *gorm.DB
//session缓存
SessionCache *cache2.Table
//各类的Bean Map。这里面是包含ControllerMap中所有元素
BeanMap map[string]IBean
//只包含了Controller的map
ControllerMap map[string]IController
//处理所有路由请求
Router *Router
}
//初始化上下文
func (this *Context) Init() {
//创建一个用于存储session的缓存。
this.SessionCache = cache2.NewTable()
//初始化Map
this.BeanMap = make(map[string]IBean)
this.ControllerMap = make(map[string]IController)
//注册各类Beans.在这个方法里面顺便把Controller装入ControllerMap中去。
this.registerBeans()
//初始化每个bean.
this.initBeans()
//初始化Router. 这个方法要在Bean注册好了之后才能。
this.Router = NewRouter()
//如果数据库信息配置好了,就直接打开数据库连接 同时执行Bean的ConfigPost方法
this.InstallOk()
}
func (this *Context) OpenDb() {
var err error = nil
this.DB, err = gorm.Open("mysql", config.CONFIG.MysqlUrl)
if err != nil {
logger.LOGGER.Panic("failed to connect mysql database")
}
//是否打开sql日志(在调试阶段可以打开以方便查看执行的SQL)
this.DB.LogMode(false)
}
func (this *Context) CloseDb() {
if this.DB != nil {
err := this.DB.Close()
if err != nil {
fmt.Println("关闭数据库连接出错", err)
}
}
}
//注册一个Bean
func (this *Context) registerBean(bean IBean) {
typeOf := reflect.TypeOf(bean)
typeName := typeOf.String()
if element, ok := bean.(IBean); ok {
err := fmt.Sprintf("【%s】已经被注册了跳过。", typeName)
if _, ok := this.BeanMap[typeName]; ok {
logger.LOGGER.Error(fmt.Sprintf(err))
} else {
this.BeanMap[typeName] = element
//看看是不是controller类型如果是那么单独放在ControllerMap中。
if controller, ok1 := bean.(IController); ok1 {
this.ControllerMap[typeName] = controller
}
}
} else {
logger.LOGGER.Panic("注册的【%s】不是Bean类型。", typeName)
}
}
//注册各个Beans
func (this *Context) registerBeans() {
//alien
this.registerBean(new(AlienController))
this.registerBean(new(AlienService))
//dashboard
this.registerBean(new(DashboardController))
this.registerBean(new(DashboardDao))
this.registerBean(new(DashboardService))
//downloadToken
this.registerBean(new(DownloadTokenDao))
//imageCache
this.registerBean(new(ImageCacheController))
this.registerBean(new(ImageCacheDao))
this.registerBean(new(ImageCacheService))
//install
this.registerBean(new(InstallController))
//matter
this.registerBean(new(MatterController))
this.registerBean(new(MatterDao))
this.registerBean(new(MatterService))
//preference
this.registerBean(new(PreferenceController))
this.registerBean(new(PreferenceDao))
this.registerBean(new(PreferenceService))
//footprint
this.registerBean(new(FootprintController))
this.registerBean(new(FootprintDao))
this.registerBean(new(FootprintService))
//session
this.registerBean(new(SessionDao))
this.registerBean(new(SessionService))
//uploadToken
this.registerBean(new(UploadTokenDao))
//user
this.registerBean(new(UserController))
this.registerBean(new(UserDao))
this.registerBean(new(UserService))
//webdav
this.registerBean(new(DavController))
this.registerBean(new(DavService))
}
//从Map中获取某个Bean.
func (this *Context) GetBean(bean IBean) IBean {
typeOf := reflect.TypeOf(bean)
typeName := typeOf.String()
if val, ok := this.BeanMap[typeName]; ok {
return val
} else {
logger.LOGGER.Panic("【%s】没有注册。", typeName)
return nil
}
}
//初始化每个Bean
func (this *Context) initBeans() {
for _, bean := range this.BeanMap {
bean.Init()
}
}
//系统如果安装好了就调用这个方法。
func (this *Context) InstallOk() {
if config.CONFIG.Installed {
this.OpenDb()
for _, bean := range this.BeanMap {
bean.ConfigPost()
}
}
}
//销毁的方法
func (this *Context) Destroy() {
this.CloseDb()
}

View File

@ -0,0 +1,104 @@
package rest
import (
"net/http"
"strconv"
"tank/code/tool/builder"
"tank/code/tool/result"
)
type DashboardController struct {
BaseController
dashboardDao *DashboardDao
dashboardService *DashboardService
}
//初始化方法
func (this *DashboardController) Init() {
this.BaseController.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.dashboardDao)
if b, ok := b.(*DashboardDao); ok {
this.dashboardDao = b
}
b = CONTEXT.GetBean(this.dashboardService)
if b, ok := b.(*DashboardService); ok {
this.dashboardService = b
}
}
//注册自己的路由。
func (this *DashboardController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
//每个Controller需要主动注册自己的路由。
routeMap["/api/dashboard/page"] = this.Wrap(this.Page, USER_ROLE_ADMINISTRATOR)
routeMap["/api/dashboard/active/ip/top10"] = this.Wrap(this.ActiveIpTop10, USER_ROLE_ADMINISTRATOR)
return routeMap
}
//过去七天分时调用量
func (this *DashboardController) InvokeList(writer http.ResponseWriter, request *http.Request) *result.WebResult {
return this.Success("")
}
//按照分页的方式获取某个图片缓存夹下图片缓存和子图片缓存夹的列表,通常情况下只有一页。
func (this *DashboardController) Page(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//如果是根目录那么就传入root.
pageStr := request.FormValue("page")
pageSizeStr := request.FormValue("pageSize")
orderCreateTime := request.FormValue("orderCreateTime")
orderUpdateTime := request.FormValue("orderUpdateTime")
orderSort := request.FormValue("orderSort")
orderDt := request.FormValue("orderDt")
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: "update_time",
value: orderUpdateTime,
},
{
key: "sort",
value: orderSort,
},
{
key: "dt",
value: orderDt,
},
}
pager := this.dashboardDao.Page(page, pageSize, "", sortArray)
return this.Success(pager)
}
func (this *DashboardController) ActiveIpTop10(writer http.ResponseWriter, request *http.Request) *result.WebResult {
list := this.dashboardDao.ActiveIpTop10()
return this.Success(list)
}

120
code/rest/dashboard_dao.go Normal file
View File

@ -0,0 +1,120 @@
package rest
import (
"github.com/jinzhu/gorm"
"github.com/nu7hatch/gouuid"
"tank/code/tool/builder"
"time"
)
type DashboardDao struct {
BaseDao
}
//创建
func (this *DashboardDao) Create(dashboard *Dashboard) *Dashboard {
timeUUID, _ := uuid.NewV4()
dashboard.Uuid = string(timeUUID.String())
dashboard.CreateTime = time.Now()
dashboard.UpdateTime = time.Now()
dashboard.Sort = time.Now().UnixNano() / 1e6
db := CONTEXT.DB.Create(dashboard)
this.PanicError(db.Error)
return dashboard
}
//修改一条记录
func (this *DashboardDao) Save(dashboard *Dashboard) *Dashboard {
dashboard.UpdateTime = time.Now()
db := CONTEXT.DB.Save(dashboard)
this.PanicError(db.Error)
return dashboard
}
//删除一条记录
func (this *DashboardDao) Delete(dashboard *Dashboard) {
db := CONTEXT.DB.Delete(&dashboard)
this.PanicError(db.Error)
}
//按照dt查询
func (this *DashboardDao) FindByDt(dt string) *Dashboard {
// Read
var dashboard Dashboard
db := CONTEXT.DB.Where(&Dashboard{Dt: dt}).First(&dashboard)
if db.Error != nil {
return nil
}
return &dashboard
}
//获取某个文件夹下所有的文件和子文件
func (this *DashboardDao) Page(page int, pageSize int, dt string, sortArray []builder.OrderPair) *Pager {
var wp = &builder.WherePair{}
if dt != "" {
wp = wp.And(&builder.WherePair{Query: "dt = ?", Args: []interface{}{dt}})
}
var conditionDB *gorm.DB
conditionDB = CONTEXT.DB.Model(&Dashboard{}).Where(wp.Query, wp.Args...)
count := 0
db := conditionDB.Count(&count)
this.PanicError(db.Error)
var dashboards []*Dashboard
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&dashboards)
this.PanicError(db.Error)
pager := NewPager(page, pageSize, count, dashboards)
return pager
}
//获取最活跃的前10个ip
func (this *DashboardDao) ActiveIpTop10() []*DashboardIpTimes {
var dashboardIpTimes []*DashboardIpTimes
sortArray := []builder.OrderPair{
{
key: "times",
value: "DESC",
},
}
rows, err := CONTEXT.DB.Model(&Footprint{}).
Select("ip,COUNT(uuid) as times").
Group("ip").
Order(this.GetSortString(sortArray)).
Offset(0).
Limit(10).
Rows()
this.PanicError(err)
for rows.Next() {
var ip string;
var times int64 = 0;
rows.Scan(&ip, &times)
item := &DashboardIpTimes{
Ip: ip,
Times: times,
}
dashboardIpTimes = append(dashboardIpTimes, item)
}
return dashboardIpTimes
}
//执行清理操作
func (this *DashboardDao) Cleanup() {
this.logger.Info("[DashboardDao]执行清理清除数据库中所有Dashboard记录。")
db := CONTEXT.DB.Where("uuid is not null").Delete(Dashboard{})
this.PanicError(db.Error)
}

View File

@ -0,0 +1,33 @@
package rest
import "tank/code/config"
/**
* 系统的所有访问记录均记录在此
*/
type Dashboard struct {
Base
InvokeNum int64 `json:"invokeNum" gorm:"type:bigint(20) not null"` //当日访问量
TotalInvokeNum int64 `json:"totalInvokeNum" gorm:"type:bigint(20) not null;default:0"` //截至目前总访问量
Uv int64 `json:"uv" gorm:"type:bigint(20) not null;default:0"` //当日UV
TotalUv int64 `json:"totalUv" gorm:"type:bigint(20) not null;default:0"` //截至目前总UV
MatterNum int64 `json:"matterNum" gorm:"type:bigint(20) not null;default:0"` //文件数量
TotalMatterNum int64 `json:"totalMatterNum" gorm:"type:bigint(20) not null;default:0"` //截至目前文件数量
FileSize int64 `json:"fileSize" gorm:"type:bigint(20) not null;default:0"` //当日文件大小
TotalFileSize int64 `json:"totalFileSize" gorm:"type:bigint(20) not null;default:0"` //截至目前文件总大小
AvgCost int64 `json:"avgCost" gorm:"type:bigint(20) not null;default:0"` //请求平均耗时 ms
Dt string `json:"dt" gorm:"type:varchar(45) not null;index:idx_dt"` //日期
}
// set File's table name to be `profiles`
func (this *Dashboard) TableName() string {
return config.TABLE_PREFIX + "dashboard"
}
/**
* 统计IP活跃数的
*/
type DashboardIpTimes struct {
Ip string `json:"ip"`
Times int64 `json:"times"`
}

View File

@ -0,0 +1,141 @@
package rest
import (
"tank/code/tool/util"
"time"
)
//@Service
type DashboardService struct {
Bean
dashboardDao *DashboardDao
footprintDao *FootprintDao
matterDao *MatterDao
imageCacheDao *ImageCacheDao
userDao *UserDao
//每天凌晨定时整理器
maintainTimer *time.Timer
}
//初始化方法
func (this *DashboardService) Init() {
this.Bean.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.dashboardDao)
if b, ok := b.(*DashboardDao); ok {
this.dashboardDao = b
}
b = CONTEXT.GetBean(this.footprintDao)
if b, ok := b.(*FootprintDao); ok {
this.footprintDao = b
}
b = CONTEXT.GetBean(this.matterDao)
if b, ok := b.(*MatterDao); ok {
this.matterDao = b
}
b = CONTEXT.GetBean(this.imageCacheDao)
if b, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = b
}
b = CONTEXT.GetBean(this.userDao)
if b, ok := b.(*UserDao); ok {
this.userDao = b
}
}
//系统启动,数据库配置完毕后会调用该方法
func (this *DashboardService) ConfigPost() {
//立即执行数据清洗任务
go util.SafeMethod(this.maintain)
}
//每日清洗离线数据表。
func (this *DashboardService) maintain() {
//准备好下次维护日志的时间。
now := time.Now()
nextTime := util.FirstMinuteOfDay(util.Tomorrow())
duration := nextTime.Sub(now)
this.logger.Info("每日数据汇总,下次时间:%s ", util.ConvertTimeToDateTimeString(nextTime))
this.maintainTimer = time.AfterFunc(duration, func() {
go util.SafeMethod(this.maintain)
})
//准备日期开始结尾
startTime := util.FirstSecondOfDay(util.Yesterday())
endTime := util.LastSecondOfDay(util.Yesterday())
dt := util.ConvertTimeToDateString(startTime)
longTimeAgo := time.Now()
longTimeAgo = longTimeAgo.AddDate(-20, 0, 0)
this.logger.Info("统计汇总表 %s -> %s", util.ConvertTimeToDateTimeString(startTime), util.ConvertTimeToDateTimeString(endTime))
//判断昨天的记录是否已经生成,如果生成了就直接删除掉
dbDashboard := this.dashboardDao.FindByDt(dt)
if dbDashboard != nil {
this.logger.Info(" %s 的汇总已经存在了,删除以进行更新", dt)
this.dashboardDao.Delete(dbDashboard)
}
invokeNum := this.footprintDao.CountBetweenTime(startTime, endTime)
this.logger.Info("调用数:%d", invokeNum)
totalInvokeNum := this.footprintDao.CountBetweenTime(longTimeAgo, endTime)
this.logger.Info("历史总调用数:%d", totalInvokeNum)
uv := this.footprintDao.UvBetweenTime(startTime, endTime)
this.logger.Info("UV%d", uv)
totalUv := this.footprintDao.UvBetweenTime(longTimeAgo, endTime)
this.logger.Info("历史总UV%d", totalUv)
matterNum := this.matterDao.CountBetweenTime(startTime, endTime)
this.logger.Info("文件数量数:%d", matterNum)
totalMatterNum := this.matterDao.CountBetweenTime(longTimeAgo, endTime)
this.logger.Info("历史文件总数:%d", totalMatterNum)
var matterSize int64
if matterNum != 0 {
matterSize = this.matterDao.SizeBetweenTime(startTime, endTime)
}
this.logger.Info("文件大小:%d", matterSize)
var totalMatterSize int64
if totalMatterNum != 0 {
totalMatterSize = this.matterDao.SizeBetweenTime(longTimeAgo, endTime)
}
this.logger.Info("历史文件总大小:%d", totalMatterSize)
cacheSize := this.imageCacheDao.SizeBetweenTime(startTime, endTime)
this.logger.Info("缓存大小:%d", cacheSize)
totalCacheSize := this.imageCacheDao.SizeBetweenTime(longTimeAgo, endTime)
this.logger.Info("历史缓存总大小:%d", totalCacheSize)
avgCost := this.footprintDao.AvgCostBetweenTime(startTime, endTime)
this.logger.Info("平均耗时:%d ms", avgCost)
dashboard := &Dashboard{
InvokeNum: invokeNum,
TotalInvokeNum: totalInvokeNum,
Uv: uv,
TotalUv: totalUv,
MatterNum: matterNum,
TotalMatterNum: totalMatterNum,
FileSize: matterSize + cacheSize,
TotalFileSize: totalMatterSize + totalCacheSize,
AvgCost: avgCost,
Dt: dt,
}
this.dashboardDao.Create(dashboard)
}

174
code/rest/dav_controller.go Normal file
View File

@ -0,0 +1,174 @@
package rest
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
"tank/code/tool/result"
"tank/code/tool/util"
)
/**
*
* WebDav协议文档
* https://tools.ietf.org/html/rfc4918
* http://www.webdav.org/specs/rfc4918.html
*
*/
type DavController struct {
BaseController
uploadTokenDao *UploadTokenDao
downloadTokenDao *DownloadTokenDao
matterDao *MatterDao
matterService *MatterService
imageCacheDao *ImageCacheDao
imageCacheService *ImageCacheService
davService *DavService
}
//初始化方法
func (this *DavController) Init() {
this.BaseController.Init()
//手动装填本实例的Bean.
b := CONTEXT.GetBean(this.uploadTokenDao)
if c, ok := b.(*UploadTokenDao); ok {
this.uploadTokenDao = c
}
b = CONTEXT.GetBean(this.downloadTokenDao)
if c, ok := b.(*DownloadTokenDao); ok {
this.downloadTokenDao = c
}
b = CONTEXT.GetBean(this.matterDao)
if c, ok := b.(*MatterDao); ok {
this.matterDao = c
}
b = CONTEXT.GetBean(this.matterService)
if c, ok := b.(*MatterService); ok {
this.matterService = c
}
b = CONTEXT.GetBean(this.imageCacheDao)
if c, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = c
}
b = CONTEXT.GetBean(this.imageCacheService)
if c, ok := b.(*ImageCacheService); ok {
this.imageCacheService = c
}
b = CONTEXT.GetBean(this.davService)
if c, ok := b.(*DavService); ok {
this.davService = c
}
}
//通过BasicAuth的方式授权。
func (this *DavController) CheckCurrentUser(writer http.ResponseWriter, request *http.Request) *User {
username, password, ok := request.BasicAuth()
if !ok {
//要求前端使用Basic的形式授权
writer.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
panic(result.ConstWebResult(result.CODE_WRAPPER_LOGIN))
}
user := this.userDao.FindByUsername(username)
if user == nil {
panic(result.BadRequest("邮箱或密码错误"))
} else {
if !util.MatchBcrypt(password, user.Password) {
panic(result.BadRequest("邮箱或密码错误"))
}
}
return user
}
//注册自己的路由。
func (this *DavController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
return routeMap
}
//处理一些特殊的接口,比如参数包含在路径中,一般情况下controller不将参数放在url路径中
func (this *DavController) HandleRoutes(writer http.ResponseWriter, request *http.Request) (func(writer http.ResponseWriter, request *http.Request), bool) {
path := request.URL.Path
//匹配 /api/dav{subPath}
pattern := fmt.Sprintf(`^%s(.*)$`, WEBDAV_PREFFIX)
reg := regexp.MustCompile(pattern)
strs := reg.FindStringSubmatch(path)
if len(strs) == 2 {
var f = func(writer http.ResponseWriter, request *http.Request) {
subPath := strs[1]
//保证subPath不是以/结尾的。
//最后多余的/要去掉
if strings.HasSuffix(subPath, "/") {
subPath = subPath[0 : len(subPath)-1]
}
this.Index(writer, request, subPath)
}
return f, true
}
return nil, false
}
//完成系统安装
func (this *DavController) Index(writer http.ResponseWriter, request *http.Request, subPath string) {
/*打印所有HEADER以及请求参数*/
fmt.Printf("\n------ 请求: %s -- %s ------\n", request.URL, subPath)
fmt.Printf("\n------Method------\n")
fmt.Println(request.Method)
fmt.Printf("\n------Header------\n")
for key, value := range request.Header {
fmt.Printf("%s = %s\n", key, value)
}
fmt.Printf("\n------请求参数:------\n")
for key, value := range request.Form {
fmt.Printf("%s = %s\n", key, value)
}
fmt.Printf("\n------Body------\n")
//ioutil.ReadAll 不可重复读,第二次读的时候就什么都没有了。
bodyBytes, err := ioutil.ReadAll(request.Body)
if err != nil {
fmt.Println("读取body时出错" + err.Error())
}
fmt.Println(string(bodyBytes))
//关闭之后再重新赋值
err = request.Body.Close()
if err != nil {
panic(err)
}
request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
fmt.Println("------------------")
//获取请求者
user := this.CheckCurrentUser(writer, request)
this.davService.HandleDav(writer, request, user, subPath)
}

123
code/rest/dav_model.go Normal file
View File

@ -0,0 +1,123 @@
package rest
import (
"fmt"
"net/http"
"path"
"strconv"
"tank/code/tool/dav"
"tank/code/tool/dav/xml"
)
//访问前缀,这个是特殊入口
var WEBDAV_PREFFIX = "/api/dav"
//动态的文件属性
type LiveProp struct {
findFn func(user *User, matter *Matter) string
dir bool
}
//所有的动态属性定义及其值的获取方式
var LivePropMap = map[xml.Name]LiveProp{
{Space: "DAV:", Local: "resourcetype"}: {
findFn: func(user *User, matter *Matter) string {
if matter.Dir {
return `<D:collection xmlns:D="DAV:"/>`
} else {
return ""
}
},
dir: true,
},
{Space: "DAV:", Local: "displayname"}: {
findFn: func(user *User, matter *Matter) string {
if path.Clean("/"+matter.Name) == "/" {
return ""
} else {
return dav.EscapeXML(matter.Name)
}
},
dir: true,
},
{Space: "DAV:", Local: "getcontentlength"}: {
findFn: func(user *User, matter *Matter) string {
return strconv.FormatInt(matter.Size, 10)
},
dir: false,
},
{Space: "DAV:", Local: "getlastmodified"}: {
findFn: func(user *User, matter *Matter) string {
return matter.UpdateTime.UTC().Format(http.TimeFormat)
},
// http://webdav.org/specs/rfc4918.html#PROPERTY_getlastmodified
// suggests that getlastmodified should only apply to GETable
// resources, and this package does not support GET on directories.
//
// Nonetheless, some WebDAV clients expect child directories to be
// sortable by getlastmodified date, so this value is true, not false.
// See golang.org/issue/15334.
dir: true,
},
{Space: "DAV:", Local: "creationdate"}: {
findFn: nil,
dir: false,
},
{Space: "DAV:", Local: "getcontentlanguage"}: {
findFn: nil,
dir: false,
},
{Space: "DAV:", Local: "getcontenttype"}: {
findFn: func(user *User, matter *Matter) string {
if matter.Dir {
return ""
} else {
return dav.EscapeXML(matter.Name)
}
},
dir: false,
},
{Space: "DAV:", Local: "getetag"}: {
findFn: func(user *User, matter *Matter) string {
return fmt.Sprintf(`"%x%x"`, matter.UpdateTime.UnixNano(), matter.Size)
},
// findETag implements ETag as the concatenated hex values of a file's
// modification time and size. This is not a reliable synchronization
// mechanism for directories, so we do not advertise getetag for DAV
// collections.
dir: false,
},
// TODO: The lockdiscovery property requires LockSystem to list the
// active locks on a resource.
{Space: "DAV:", Local: "lockdiscovery"}: {},
{Space: "DAV:", Local: "supportedlock"}: {
findFn: func(user *User, matter *Matter) string {
return `` +
`<D:lockentry xmlns:D="DAV:">` +
`<D:lockscope><D:exclusive/></D:lockscope>` +
`<D:locktype><D:write/></D:locktype>` +
`</D:lockentry>`
},
dir: true,
},
{Space: "DAV:", Local: "quota-available-bytes"}: {
findFn: func(user *User, matter *Matter) string {
var size int64 = 0
if user.SizeLimit >= 0 {
size = user.SizeLimit
} else {
//没有限制默认100G
size = 100 * 1024 * 1024 * 1024
}
return fmt.Sprintf(`%d`, size)
},
dir: true,
},
{Space: "DAV:", Local: "quota-used-bytes"}: {
findFn: func(user *User, matter *Matter) string {
//已使用大小默认0
return fmt.Sprintf(`%d`, 0)
},
dir: true,
},
}

488
code/rest/dav_service.go Normal file
View File

@ -0,0 +1,488 @@
package rest
import (
"fmt"
"net/http"
"net/url"
"path"
"regexp"
"strings"
dav2 "tank/code/tool/dav"
xml2 "tank/code/tool/dav/xml"
"tank/code/tool/result"
"tank/code/tool/util"
)
/**
*
* WebDav协议文档
* https://tools.ietf.org/html/rfc4918
* 主要参考 golang.org/x/net/webdav
* 测试机http://www.webdav.org/neon/litmus/
*/
//@Service
type DavService struct {
Bean
matterDao *MatterDao
matterService *MatterService
}
//初始化方法
func (this *DavService) Init() {
this.Bean.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.matterDao)
if b, ok := b.(*MatterDao); ok {
this.matterDao = b
}
b = CONTEXT.GetBean(this.matterService)
if b, ok := b.(*MatterService); ok {
this.matterService = b
}
}
//获取Header头中的Depth值暂不支持 infinity
func (this *DavService) ParseDepth(request *http.Request) int {
depth := 1
if hdr := request.Header.Get("Depth"); hdr != "" {
switch hdr {
case "0":
return 0
case "1":
return 1
case "infinity":
return 1
}
} else {
panic(result.BadRequest("必须指定Header Depth"))
}
return depth
}
func (this *DavService) makePropstatResponse(href string, pstats []dav2.Propstat) *dav2.Response {
resp := dav2.Response{
Href: []string{(&url.URL{Path: href}).EscapedPath()},
Propstat: make([]dav2.SubPropstat, 0, len(pstats)),
}
for _, p := range pstats {
var xmlErr *dav2.XmlError
if p.XMLError != "" {
xmlErr = &dav2.XmlError{InnerXML: []byte(p.XMLError)}
}
resp.Propstat = append(resp.Propstat, dav2.SubPropstat{
Status: fmt.Sprintf("HTTP/1.1 %d %s", p.Status, dav2.StatusText(p.Status)),
Prop: p.Props,
ResponseDescription: p.ResponseDescription,
Error: xmlErr,
})
}
return &resp
}
//从一个matter中获取其 []dav.Propstat
func (this *DavService) PropstatsFromXmlNames(user *User, matter *Matter, xmlNames []xml2.Name) []dav2.Propstat {
propstats := make([]dav2.Propstat, 0)
var properties []dav2.Property
for _, xmlName := range xmlNames {
//TODO: deadprops尚未考虑
// Otherwise, it must either be a live property or we don't know it.
if liveProp := LivePropMap[xmlName]; liveProp.findFn != nil && (liveProp.dir || !matter.Dir) {
innerXML := liveProp.findFn(user, matter)
properties = append(properties, dav2.Property{
XMLName: xmlName,
InnerXML: []byte(innerXML),
})
} else {
this.logger.Info("%s的%s无法完成", matter.Path, xmlName.Local)
}
}
if len(properties) == 0 {
panic(result.BadRequest("请求的属性项无法解析!"))
}
okPropstat := dav2.Propstat{Status: http.StatusOK, Props: properties}
propstats = append(propstats, okPropstat)
return propstats
}
//从一个matter中获取所有的propsNames
func (this *DavService) AllPropXmlNames(matter *Matter) []xml2.Name {
pnames := make([]xml2.Name, 0)
for pn, prop := range LivePropMap {
if prop.findFn != nil && (prop.dir || !matter.Dir) {
pnames = append(pnames, pn)
}
}
return pnames
}
//从一个matter中获取其 []dav.Propstat
func (this *DavService) Propstats(user *User, matter *Matter, propfind *dav2.Propfind) []dav2.Propstat {
propstats := make([]dav2.Propstat, 0)
if propfind.Propname != nil {
panic(result.BadRequest("propfind.Propname != nil 尚未处理"))
} else if propfind.Allprop != nil {
//TODO: 如果include中还有内容那么包含进去。
xmlNames := this.AllPropXmlNames(matter)
propstats = this.PropstatsFromXmlNames(user, matter, xmlNames)
} else {
propstats = this.PropstatsFromXmlNames(user, matter, propfind.Prop)
}
return propstats
}
//列出文件夹或者目录详情
func (this *DavService) HandlePropfind(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
fmt.Printf("PROPFIND %s\n", subPath)
//获取请求的层数。暂不支持 infinity
depth := this.ParseDepth(request)
//读取请求参数。按照用户的参数请求返回内容。
propfind := dav2.ReadPropfind(request.Body)
//寻找符合条件的matter.
//如果是空或者/就是请求根目录
matter := this.matterDao.CheckWithRootByPath(subPath, user)
var matters []*Matter
if depth == 0 {
matters = []*Matter{matter}
} else {
// len(matters) == 0 表示该文件夹下面是空文件夹
matters = this.matterDao.List(matter.Uuid, user.Uuid, nil)
//将当前的matter添加到头部
matters = append([]*Matter{matter}, matters...)
}
//准备一个输出结果的Writer
multiStatusWriter := &dav2.MultiStatusWriter{Writer: writer}
for _, matter := range matters {
fmt.Printf("处理Matter %s\n", matter.Path)
propstats := this.Propstats(user, matter, propfind)
path := fmt.Sprintf("%s%s", WEBDAV_PREFFIX, matter.Path)
response := this.makePropstatResponse(path, propstats)
err := multiStatusWriter.Write(response)
this.PanicError(err)
}
//闭合
err := multiStatusWriter.Close()
this.PanicError(err)
fmt.Printf("%v %v \n", subPath, propfind.Prop)
}
//请求文件详情(下载)
func (this *DavService) HandleGetHeadPost(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
fmt.Printf("GET %s\n", subPath)
matter := this.matterDao.CheckWithRootByPath(subPath, user)
//如果是文件夹,相当于是 Propfind
if matter.Dir {
this.HandlePropfind(writer, request, user, subPath)
return
}
//下载一个文件。
this.matterService.DownloadFile(writer, request, matter.AbsolutePath(), matter.Name, false)
}
//上传文件
func (this *DavService) HandlePut(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
fmt.Printf("PUT %s\n", subPath)
filename := util.GetFilenameOfPath(subPath)
dirPath := util.GetDirOfPath(subPath)
//寻找符合条件的matter.
dirMatter := this.matterDao.CheckWithRootByPath(dirPath, user)
//如果存在,那么先删除再说。
srcMatter := this.matterDao.findByUserUuidAndPath(user.Uuid, subPath)
if srcMatter != nil {
this.matterService.AtomicDelete(srcMatter)
}
this.matterService.AtomicUpload(request.Body, user, dirMatter, filename, true)
}
//删除文件
func (this *DavService) HandleDelete(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
fmt.Printf("DELETE %s\n", subPath)
//寻找符合条件的matter.
matter := this.matterDao.CheckWithRootByPath(subPath, user)
this.matterService.AtomicDelete(matter)
}
//创建文件夹
func (this *DavService) HandleMkcol(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
fmt.Printf("MKCOL %s\n", subPath)
thisDirName := util.GetFilenameOfPath(subPath)
dirPath := util.GetDirOfPath(subPath)
//寻找符合条件的matter.
dirMatter := this.matterDao.CheckWithRootByPath(dirPath, user)
this.matterService.AtomicCreateDirectory(dirMatter, thisDirName, user)
}
//跨域请求的OPTIONS询问
func (this *DavService) HandleOptions(w http.ResponseWriter, r *http.Request, user *User, subPath string) {
fmt.Printf("OPTIONS %s\n", subPath)
//寻找符合条件的matter.
matter := this.matterDao.CheckWithRootByPath(subPath, user)
allow := "OPTIONS, LOCK, PUT, MKCOL"
if matter.Dir {
allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
} else {
allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT"
}
w.Header().Set("Allow", allow)
// http://www.webdav.org/specs/rfc4918.html#dav.compliance.classes
w.Header().Set("DAV", "1, 2")
// http://msdn.microsoft.com/en-au/library/cc250217.aspx
w.Header().Set("MS-Author-Via", "DAV")
}
//移动或者复制的准备工作
func (this *DavService) prepareMoveCopy(
writer http.ResponseWriter,
request *http.Request,
user *User, subPath string) (
srcMatter *Matter,
destDirMatter *Matter,
srcDirPath string,
destinationDirPath string,
destinationName string,
overwrite bool) {
//解析出目标路径。
destinationStr := request.Header.Get("Destination")
//解析出Overwrite。
overwriteStr := request.Header.Get("Overwrite")
//有前缀的目标path
var fullDestinationPath string
//去掉前缀的目标path
var destinationPath string
if destinationStr == "" {
panic(result.BadRequest("Header Destination必填"))
}
//如果是重命名那么就不是http开头了。
if strings.HasPrefix(destinationStr, WEBDAV_PREFFIX) {
fullDestinationPath = destinationStr
} else {
destinationUrl, err := url.Parse(destinationStr)
this.PanicError(err)
if destinationUrl.Host != request.Host {
panic(result.BadRequest("Destination Host不一致. %s %s != %s", destinationStr, destinationUrl.Host, request.Host))
}
fullDestinationPath = destinationUrl.Path
}
//去除 路径中的相对路径 比如 /a/b/../ => /a/
fullDestinationPath = path.Clean(fullDestinationPath)
//去除前缀
pattern := fmt.Sprintf(`^%s(.*)$`, WEBDAV_PREFFIX)
reg := regexp.MustCompile(pattern)
strs := reg.FindStringSubmatch(fullDestinationPath)
if len(strs) == 2 {
destinationPath = strs[1]
} else {
panic(result.BadRequest("目标前缀必须为:%s", WEBDAV_PREFFIX))
}
destinationName = util.GetFilenameOfPath(destinationPath)
destinationDirPath = util.GetDirOfPath(destinationPath)
srcDirPath = util.GetDirOfPath(subPath)
overwrite = false
if overwriteStr == "T" {
overwrite = true
}
//如果前后一致,那么相当于没有改变
if destinationPath == subPath {
return
}
//源matter.
//寻找符合条件的matter.
srcMatter = this.matterDao.CheckWithRootByPath(subPath, user)
//如果是空或者/就是请求根目录
if srcMatter.Uuid == MATTER_ROOT {
panic(result.BadRequest("你不能移动根目录!"))
}
//寻找目标文件夹matter
destDirMatter = this.matterDao.CheckWithRootByPath(destinationDirPath, user)
return srcMatter, destDirMatter, srcDirPath, destinationDirPath, destinationName, overwrite
}
//移动或者重命名
func (this *DavService) HandleMove(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
fmt.Printf("MOVE %s\n", subPath)
srcMatter, destDirMatter, srcDirPath, destinationDirPath, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath)
//移动到新目录中去。
if destinationDirPath == srcDirPath {
//文件夹没变化,相当于重命名。
this.matterService.AtomicRename(srcMatter, destinationName, user)
} else {
this.matterService.AtomicMove(srcMatter, destDirMatter, overwrite)
}
this.logger.Info("完成移动 %s => %s", subPath, destDirMatter.Path)
}
//复制文件/文件夹
func (this *DavService) HandleCopy(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
fmt.Printf("COPY %s\n", subPath)
srcMatter, destDirMatter, _, _, destinationName, overwrite := this.prepareMoveCopy(writer, request, user, subPath)
//复制到新目录中去。
this.matterService.AtomicCopy(srcMatter, destDirMatter, destinationName, overwrite)
this.logger.Info("完成复制 %s => %s", subPath, destDirMatter.Path)
}
//加锁
func (this *DavService) HandleLock(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
panic(result.BadRequest("不支持LOCK方法"))
}
//解锁
func (this *DavService) HandleUnlock(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
panic(result.BadRequest("不支持UNLOCK方法"))
}
//修改文件属性
func (this *DavService) HandleProppatch(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
panic(result.BadRequest("不支持PROPPATCH方法"))
}
//处理所有的请求
func (this *DavService) HandleDav(writer http.ResponseWriter, request *http.Request, user *User, subPath string) {
method := request.Method
if method == "OPTIONS" {
//跨域问询
this.HandleOptions(writer, request, user, subPath)
} else if method == "GET" || method == "HEAD" || method == "POST" {
//请求文件详情(下载)
this.HandleGetHeadPost(writer, request, user, subPath)
} else if method == "DELETE" {
//删除文件
this.HandleDelete(writer, request, user, subPath)
} else if method == "PUT" {
//上传文件
this.HandlePut(writer, request, user, subPath)
} else if method == "MKCOL" {
//创建文件夹
this.HandleMkcol(writer, request, user, subPath)
} else if method == "COPY" {
//复制文件/文件夹
this.HandleCopy(writer, request, user, subPath)
} else if method == "MOVE" {
//移动(重命名)文件/文件夹
this.HandleMove(writer, request, user, subPath)
} else if method == "LOCK" {
//加锁
this.HandleLock(writer, request, user, subPath)
} else if method == "UNLOCK" {
//释放锁
this.HandleUnlock(writer, request, user, subPath)
} else if method == "PROPFIND" {
//列出文件夹或者目录详情
this.HandlePropfind(writer, request, user, subPath)
} else if method == "PROPPATCH" {
//修改文件属性
this.HandleProppatch(writer, request, user, subPath)
} else {
panic(result.BadRequest("该方法还不支持。%s", method))
}
}

View File

@ -0,0 +1,67 @@
package rest
import (
"github.com/nu7hatch/gouuid"
"time"
)
type DownloadTokenDao struct {
BaseDao
}
//按照Id查询
func (this *DownloadTokenDao) FindByUuid(uuid string) *DownloadToken {
// Read
var downloadToken = &DownloadToken{}
db := CONTEXT.DB.Where(&DownloadToken{Base: Base{Uuid: uuid}}).First(downloadToken)
if db.Error != nil {
return nil
}
return downloadToken
}
//按照Id查询
func (this *DownloadTokenDao) CheckByUuid(uuid string) *DownloadToken {
// Read
var downloadToken = &DownloadToken{}
db := CONTEXT.DB.Where(&DownloadToken{Base: Base{Uuid: uuid}}).First(downloadToken)
this.PanicError(db.Error)
return downloadToken
}
//创建一个session并且持久化到数据库中。
func (this *DownloadTokenDao) Create(downloadToken *DownloadToken) *DownloadToken {
timeUUID, _ := uuid.NewV4()
downloadToken.Uuid = string(timeUUID.String())
downloadToken.CreateTime = time.Now()
downloadToken.UpdateTime = time.Now()
downloadToken.Sort = time.Now().UnixNano() / 1e6
db := CONTEXT.DB.Create(downloadToken)
this.PanicError(db.Error)
return downloadToken
}
//修改一个downloadToken
func (this *DownloadTokenDao) Save(downloadToken *DownloadToken) *DownloadToken {
downloadToken.UpdateTime = time.Now()
db := CONTEXT.DB.Save(downloadToken)
this.PanicError(db.Error)
return downloadToken
}
//执行清理操作
func (this *DownloadTokenDao) Cleanup() {
this.logger.Info("[DownloadTokenDao]执行清理清除数据库中所有DownloadToken记录。")
db := CONTEXT.DB.Where("uuid is not null").Delete(DownloadToken{})
this.PanicError(db.Error)
}

View File

@ -0,0 +1,18 @@
package rest
import (
"tank/code/config"
"time"
)
type DownloadToken struct {
Base
UserUuid string `json:"userUuid" gorm:"type:char(36) not null"`
MatterUuid string `json:"matterUuid" gorm:"type:char(36) not null;index:idx_mu"`
ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
}
func (this *DownloadToken) TableName() string {
return config.TABLE_PREFIX + "download_token"
}

View File

@ -0,0 +1,127 @@
package rest
import (
"net/http"
"strconv"
"tank/code/tool/builder"
"tank/code/tool/result"
)
type FootprintController struct {
BaseController
footprintDao *FootprintDao
footprintService *FootprintService
}
//初始化方法
func (this *FootprintController) Init() {
this.BaseController.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.footprintDao)
if b, ok := b.(*FootprintDao); ok {
this.footprintDao = b
}
b = CONTEXT.GetBean(this.footprintService)
if b, ok := b.(*FootprintService); ok {
this.footprintService = b
}
}
//注册自己的路由。
func (this *FootprintController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
//每个Controller需要主动注册自己的路由。
routeMap["/api/footprint/delete"] = this.Wrap(this.Delete, USER_ROLE_USER)
routeMap["/api/footprint/detail"] = this.Wrap(this.Detail, USER_ROLE_USER)
routeMap["/api/footprint/page"] = this.Wrap(this.Page, USER_ROLE_USER)
return routeMap
}
//查看详情。
func (this *FootprintController) Detail(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
if uuid == "" {
panic(result.BadRequest("图片缓存的uuid必填"))
}
footprint := this.footprintService.Detail(uuid)
//验证当前之人是否有权限查看这么详细。
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
if footprint.UserUuid != user.Uuid {
panic("没有权限查看该图片缓存")
}
}
return this.Success(footprint)
}
//按照分页的方式查询
func (this *FootprintController) Page(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//如果是根目录那么就传入root.
pageStr := request.FormValue("page")
pageSizeStr := request.FormValue("pageSize")
userUuid := request.FormValue("userUuid")
orderCreateTime := request.FormValue("orderCreateTime")
orderSize := request.FormValue("orderSize")
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
userUuid = user.Uuid
}
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.footprintDao.Page(page, pageSize, userUuid, sortArray)
return this.Success(pager)
}
//删除一条记录
func (this *FootprintController) Delete(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
if uuid == "" {
panic(result.BadRequest("uuid必填"))
}
footprint := this.footprintDao.FindByUuid(uuid)
if footprint != nil {
this.footprintDao.Delete(footprint)
}
return this.Success("删除成功!")
}

128
code/rest/footprint_dao.go Normal file
View File

@ -0,0 +1,128 @@
package rest
import (
"github.com/jinzhu/gorm"
"tank/code/tool/builder"
"github.com/nu7hatch/gouuid"
"time"
)
type FootprintDao struct {
BaseDao
}
//按照Id查询文件
func (this *FootprintDao) FindByUuid(uuid string) *Footprint {
// Read
var footprint Footprint
db := CONTEXT.DB.Where(&Footprint{Base: Base{Uuid: uuid}}).First(&footprint)
if db.Error != nil {
return nil
}
return &footprint
}
//按照Id查询文件
func (this *FootprintDao) CheckByUuid(uuid string) *Footprint {
// Read
var footprint Footprint
db := CONTEXT.DB.Where(&Footprint{Base: Base{Uuid: uuid}}).First(&footprint)
this.PanicError(db.Error)
return &footprint
}
//按分页条件获取分页
func (this *FootprintDao) Page(page int, pageSize int, userUuid string, sortArray []builder.OrderPair) *Pager {
var wp = &builder.WherePair{}
if userUuid != "" {
wp = wp.And(&builder.WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}})
}
var conditionDB *gorm.DB
conditionDB = CONTEXT.DB.Model(&Footprint{}).Where(wp.Query, wp.Args...)
count := 0
db := conditionDB.Count(&count)
this.PanicError(db.Error)
var footprints []*Footprint
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&footprints)
this.PanicError(db.Error)
pager := NewPager(page, pageSize, count, footprints)
return pager
}
//创建
func (this *FootprintDao) Create(footprint *Footprint) *Footprint {
timeUUID, _ := uuid.NewV4()
footprint.Uuid = string(timeUUID.String())
footprint.CreateTime = time.Now()
footprint.UpdateTime = time.Now()
footprint.Sort = time.Now().UnixNano() / 1e6
db := CONTEXT.DB.Create(footprint)
this.PanicError(db.Error)
return footprint
}
//修改一条记录
func (this *FootprintDao) Save(footprint *Footprint) *Footprint {
footprint.UpdateTime = time.Now()
db := CONTEXT.DB.Save(footprint)
this.PanicError(db.Error)
return footprint
}
//删除一条记录
func (this *FootprintDao) Delete(footprint *Footprint) {
db := CONTEXT.DB.Delete(&footprint)
this.PanicError(db.Error)
}
//获取一段时间中,总的数量
func (this *FootprintDao) CountBetweenTime(startTime time.Time, endTime time.Time) int64 {
var count int64
db := CONTEXT.DB.Model(&Footprint{}).Where("create_time >= ? AND create_time <= ?", startTime, endTime).Count(&count)
this.PanicError(db.Error)
return count
}
//获取一段时间中UV的数量
func (this *FootprintDao) UvBetweenTime(startTime time.Time, endTime time.Time) int64 {
var count int64
db := CONTEXT.DB.Model(&Footprint{}).Where("create_time >= ? AND create_time <= ?", startTime, endTime).Select("COUNT(DISTINCT(ip))")
this.PanicError(db.Error)
row := db.Row()
row.Scan(&count)
return count
}
//获取一段时间中平均耗时
func (this *FootprintDao) AvgCostBetweenTime(startTime time.Time, endTime time.Time) int64 {
var cost float64
db := CONTEXT.DB.Model(&Footprint{}).Where("create_time >= ? AND create_time <= ?", startTime, endTime).Select("AVG(cost)")
this.PanicError(db.Error)
row := db.Row()
row.Scan(&cost)
return int64(cost)
}
//执行清理操作
func (this *FootprintDao) Cleanup() {
this.logger.Info("[FootprintDao]执行清理清除数据库中所有Footprint记录。")
db := CONTEXT.DB.Where("uuid is not null").Delete(Footprint{})
this.PanicError(db.Error)
}

View File

@ -0,0 +1,22 @@
package rest
import "tank/code/config"
/**
* 系统的所有访问记录均记录在此
*/
type Footprint struct {
Base
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
Ip string `json:"ip" gorm:"type:varchar(128) not null;index:idx_dt"`
Host string `json:"host" gorm:"type:varchar(45) not null"`
Uri string `json:"uri" gorm:"type:varchar(255) not null"`
Params string `json:"params" gorm:"type:text"`
Cost int64 `json:"cost" gorm:"type:bigint(20) not null;default:0"`
Success bool `json:"success" gorm:"type:tinyint(1) not null;default:0"`
}
// set File's table name to be `profiles`
func (this *Footprint) TableName() string {
return config.TABLE_PREFIX + "footprint"
}

View File

@ -0,0 +1,90 @@
package rest
import (
"encoding/json"
"net/http"
"tank/code/config"
"tank/code/tool/util"
"time"
)
//@Service
type FootprintService struct {
Bean
footprintDao *FootprintDao
userDao *UserDao
}
//初始化方法
func (this *FootprintService) Init() {
this.Bean.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.footprintDao)
if b, ok := b.(*FootprintDao); ok {
this.footprintDao = b
}
b = CONTEXT.GetBean(this.userDao)
if b, ok := b.(*UserDao); ok {
this.userDao = b
}
}
//获取某个文件的详情,会把父级依次倒着装进去。如果中途出错,直接抛出异常。
func (this *FootprintService) Detail(uuid string) *Footprint {
footprint := this.footprintDao.CheckByUuid(uuid)
return footprint
}
//记录访问记录
func (this *FootprintService) Trace(writer http.ResponseWriter, request *http.Request, duration time.Duration, success bool) {
params := make(map[string][]string)
//POST请求参数
values := request.PostForm
for key, val := range values {
params[key] = val
}
//GET请求参数
values1 := request.URL.Query()
for key, val := range values1 {
params[key] = val
}
//用json的方式输出返回值。
paramsString := "{}"
paramsData, err := json.Marshal(params)
if err == nil {
paramsString = string(paramsData)
}
//将文件信息存入数据库中。
footprint := &Footprint{
Ip: util.GetIpAddress(request),
Host: request.Host,
Uri: request.URL.Path,
Params: paramsString,
Cost: int64(duration / time.Millisecond),
Success: success,
}
//有可能DB尚且没有配置 直接打印出内容,并且退出
if config.CONFIG.Installed {
user := this.findUser(writer, request)
userUuid := ""
if user != nil {
userUuid = user.Uuid
}
footprint.UserUuid = userUuid
footprint = this.footprintDao.Create(footprint)
}
//用json的方式输出返回值。
this.logger.Info("Ip:%s Host:%s Uri:%s Params:%s Cost:%d", footprint.Ip, footprint.Host, footprint.Uri, paramsString, int64(duration/time.Millisecond))
}

View File

@ -0,0 +1,173 @@
package rest
import (
"net/http"
"strconv"
"strings"
"tank/code/tool/builder"
"tank/code/tool/result"
)
type ImageCacheController struct {
BaseController
imageCacheDao *ImageCacheDao
imageCacheService *ImageCacheService
}
//初始化方法
func (this *ImageCacheController) Init() {
this.BaseController.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.imageCacheDao)
if b, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = b
}
b = CONTEXT.GetBean(this.imageCacheService)
if b, ok := b.(*ImageCacheService); ok {
this.imageCacheService = b
}
}
//注册自己的路由。
func (this *ImageCacheController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
//每个Controller需要主动注册自己的路由。
routeMap["/api/image/cache/delete"] = this.Wrap(this.Delete, USER_ROLE_USER)
routeMap["/api/image/cache/delete/batch"] = this.Wrap(this.DeleteBatch, USER_ROLE_USER)
routeMap["/api/image/cache/detail"] = this.Wrap(this.Detail, USER_ROLE_USER)
routeMap["/api/image/cache/page"] = this.Wrap(this.Page, USER_ROLE_USER)
return routeMap
}
//查看某个图片缓存的详情。
func (this *ImageCacheController) Detail(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
if uuid == "" {
panic(result.BadRequest("图片缓存的uuid必填"))
}
imageCache := this.imageCacheService.Detail(uuid)
//验证当前之人是否有权限查看这么详细。
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
if imageCache.UserUuid != user.Uuid {
panic("没有权限查看该图片缓存")
}
}
return this.Success(imageCache)
}
//按照分页的方式获取某个图片缓存夹下图片缓存和子图片缓存夹的列表,通常情况下只有一页。
func (this *ImageCacheController) Page(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//如果是根目录那么就传入root.
pageStr := request.FormValue("page")
pageSizeStr := request.FormValue("pageSize")
orderCreateTime := request.FormValue("orderCreateTime")
orderUpdateTime := request.FormValue("orderUpdateTime")
orderSort := request.FormValue("orderSort")
userUuid := request.FormValue("userUuid")
matterUuid := request.FormValue("matterUuid")
orderSize := request.FormValue("orderSize")
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
userUuid = user.Uuid
}
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: "update_time",
value: orderUpdateTime,
},
{
key: "sort",
value: orderSort,
},
{
key: "size",
value: orderSize,
},
}
pager := this.imageCacheDao.Page(page, pageSize, userUuid, matterUuid, sortArray)
return this.Success(pager)
}
//删除一个图片缓存
func (this *ImageCacheController) Delete(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
if uuid == "" {
panic(result.BadRequest("图片缓存的uuid必填"))
}
imageCache := this.imageCacheDao.FindByUuid(uuid)
//判断图片缓存的所属人是否正确
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && imageCache.UserUuid != user.Uuid {
panic(result.Unauthorized("没有权限"))
}
this.imageCacheDao.Delete(imageCache)
return this.Success("删除成功!")
}
//删除一系列图片缓存。
func (this *ImageCacheController) 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.imageCacheDao.FindByUuid(uuid)
//判断图片缓存的所属人是否正确
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && imageCache.UserUuid != user.Uuid {
panic(result.Unauthorized("没有权限"))
}
this.imageCacheDao.Delete(imageCache)
}
return this.Success("删除成功!")
}

View File

@ -0,0 +1,210 @@
package rest
import (
"fmt"
"github.com/jinzhu/gorm"
"github.com/nu7hatch/gouuid"
"os"
"path/filepath"
"tank/code/tool/builder"
"tank/code/tool/util"
"time"
)
type ImageCacheDao struct {
BaseDao
}
//按照Id查询文件
func (this *ImageCacheDao) FindByUuid(uuid string) *ImageCache {
// Read
var imageCache ImageCache
db := CONTEXT.DB.Where(&ImageCache{Base: Base{Uuid: uuid}}).First(&imageCache)
if db.Error != nil {
return nil
}
return &imageCache
}
//按照Id查询文件
func (this *ImageCacheDao) CheckByUuid(uuid string) *ImageCache {
// Read
var imageCache ImageCache
db := CONTEXT.DB.Where(&ImageCache{Base: Base{Uuid: uuid}}).First(&imageCache)
this.PanicError(db.Error)
return &imageCache
}
//按照名字查询文件夹
func (this *ImageCacheDao) FindByMatterUuidAndMode(matterUuid string, mode string) *ImageCache {
var wp = &builder.WherePair{}
if matterUuid != "" {
wp = wp.And(&builder.WherePair{Query: "matter_uuid = ?", Args: []interface{}{matterUuid}})
}
if mode != "" {
wp = wp.And(&builder.WherePair{Query: "mode = ?", Args: []interface{}{mode}})
}
var imageCache = &ImageCache{}
db := CONTEXT.DB.Model(&ImageCache{}).Where(wp.Query, wp.Args...).First(imageCache)
if db.Error != nil {
return nil
}
return imageCache
}
//按照id和userUuid来查找。找不到抛异常。
func (this *ImageCacheDao) CheckByUuidAndUserUuid(uuid string, userUuid string) *ImageCache {
// Read
var imageCache = &ImageCache{}
db := CONTEXT.DB.Where(&ImageCache{Base: Base{Uuid: uuid}, UserUuid: userUuid}).First(imageCache)
this.PanicError(db.Error)
return imageCache
}
//获取某个用户的某个文件夹下的某个名字的文件(或文件夹)列表
func (this *ImageCacheDao) ListByUserUuidAndPuuidAndDirAndName(userUuid string) []*ImageCache {
var imageCaches []*ImageCache
db := CONTEXT.DB.
Where(ImageCache{UserUuid: userUuid}).
Find(&imageCaches)
this.PanicError(db.Error)
return imageCaches
}
//获取某个文件夹下所有的文件和子文件
func (this *ImageCacheDao) Page(page int, pageSize int, userUuid string, matterUuid string, sortArray []builder.OrderPair) *Pager {
var wp = &builder.WherePair{}
if userUuid != "" {
wp = wp.And(&builder.WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}})
}
if matterUuid != "" {
wp = wp.And(&builder.WherePair{Query: "matter_uuid = ?", Args: []interface{}{matterUuid}})
}
var conditionDB *gorm.DB
conditionDB = CONTEXT.DB.Model(&ImageCache{}).Where(wp.Query, wp.Args...)
count := 0
db := conditionDB.Count(&count)
this.PanicError(db.Error)
var imageCaches []*ImageCache
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&imageCaches)
this.PanicError(db.Error)
pager := NewPager(page, pageSize, count, imageCaches)
return pager
}
//创建
func (this *ImageCacheDao) Create(imageCache *ImageCache) *ImageCache {
timeUUID, _ := uuid.NewV4()
imageCache.Uuid = string(timeUUID.String())
imageCache.CreateTime = time.Now()
imageCache.UpdateTime = time.Now()
imageCache.Sort = time.Now().UnixNano() / 1e6
db := CONTEXT.DB.Create(imageCache)
this.PanicError(db.Error)
return imageCache
}
//修改一个文件
func (this *ImageCacheDao) Save(imageCache *ImageCache) *ImageCache {
imageCache.UpdateTime = time.Now()
db := CONTEXT.DB.Save(imageCache)
this.PanicError(db.Error)
return imageCache
}
//删除一个文件包括文件夹
func (this *ImageCacheDao) deleteFileAndDir(imageCache *ImageCache) {
filePath := GetUserCacheRootDir(imageCache.Username) + imageCache.Path
dirPath := filepath.Dir(filePath)
//删除文件
err := os.Remove(filePath)
if err != nil {
this.logger.Error(fmt.Sprintf("删除磁盘上的文件%s出错 %s", filePath, err.Error()))
}
//如果这一层文件夹是空的,那么删除文件夹本身。
util.DeleteEmptyDirRecursive(dirPath)
}
//删除一个文件,数据库中删除,物理磁盘上删除。
func (this *ImageCacheDao) Delete(imageCache *ImageCache) {
db := CONTEXT.DB.Delete(&imageCache)
this.PanicError(db.Error)
this.deleteFileAndDir(imageCache)
}
//删除一个matter对应的所有缓存
func (this *ImageCacheDao) DeleteByMatterUuid(matterUuid string) {
var wp = &builder.WherePair{}
wp = wp.And(&builder.WherePair{Query: "matter_uuid = ?", Args: []interface{}{matterUuid}})
//查询出即将删除的图片缓存
var imageCaches []*ImageCache
db := CONTEXT.DB.Where(wp.Query, wp.Args).Find(&imageCaches)
this.PanicError(db.Error)
//删除文件记录
db = CONTEXT.DB.Where(wp.Query, wp.Args).Delete(ImageCache{})
this.PanicError(db.Error)
//删除文件实体
for _, imageCache := range imageCaches {
this.deleteFileAndDir(imageCache)
}
}
//获取一段时间中文件总大小
func (this *ImageCacheDao) SizeBetweenTime(startTime time.Time, endTime time.Time) int64 {
var size int64
db := CONTEXT.DB.Model(&ImageCache{}).Where("create_time >= ? AND create_time <= ?", startTime, endTime).Select("SUM(size)")
this.PanicError(db.Error)
row := db.Row()
row.Scan(&size)
return size
}
//执行清理操作
func (this *ImageCacheDao) Cleanup() {
this.logger.Info("[ImageCacheDao]执行清理清除数据库中所有ImageCache记录。")
db := CONTEXT.DB.Where("uuid is not null").Delete(ImageCache{})
this.PanicError(db.Error)
}

View File

@ -0,0 +1,32 @@
package rest
import (
"tank/code/config"
)
/**
* 图片缓存,对于那些处理过的图片,统一管理在这里。
*/
type ImageCache struct {
Base
Name string `json:"name" gorm:"type:varchar(255) not null"`
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
Username string `json:"username" gorm:"type:varchar(45) not null"`
MatterUuid string `json:"matterUuid" gorm:"type:char(36);index:idx_mu"`
MatterName string `json:"matterName" gorm:"type:varchar(255) not null"`
Mode string `json:"mode" gorm:"type:varchar(512)"`
Md5 string `json:"md5" gorm:"type:varchar(45)"`
Size int64 `json:"size" gorm:"type:bigint(20) not null;default:0"`
Path string `json:"path" gorm:"type:varchar(512)"`
Matter *Matter `json:"matter" gorm:"-"`
}
// set File's table name to be `profiles`
func (this *ImageCache) TableName() string {
return config.TABLE_PREFIX + "image_cache"
}
// 获取该ImageCache的绝对路径。path代表的是相对路径。
func (this *ImageCache) AbsolutePath() string {
return GetUserCacheRootDir(this.Username) + this.Path
}

View File

@ -0,0 +1,244 @@
package rest
import (
"fmt"
"github.com/disintegration/imaging"
"image"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"tank/code/tool/util"
)
//@Service
type ImageCacheService struct {
Bean
imageCacheDao *ImageCacheDao
userDao *UserDao
matterDao *MatterDao
}
//初始化方法
func (this *ImageCacheService) Init() {
this.Bean.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.imageCacheDao)
if b, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = b
}
b = CONTEXT.GetBean(this.userDao)
if b, ok := b.(*UserDao); ok {
this.userDao = b
}
b = CONTEXT.GetBean(this.matterDao)
if b, ok := b.(*MatterDao); ok {
this.matterDao = b
}
}
//获取某个文件的详情,会把父级依次倒着装进去。如果中途出错,直接抛出异常。
func (this *ImageCacheService) Detail(uuid string) *ImageCache {
imageCache := this.imageCacheDao.CheckByUuid(uuid)
return imageCache
}
//获取预处理时必要的参数
func (this *ImageCacheService) ResizeParams(request *http.Request) (needProcess bool, resizeMode string, resizeWidth int, resizeHeight int) {
var err error
//1.0 模式准备逐步废弃掉
if request.FormValue("imageProcess") == "resize" {
//老模式使用 imageResizeM,imageResizeW,imageResizeH
imageResizeM := request.FormValue("imageResizeM")
if imageResizeM == "" {
imageResizeM = "fit"
} else if imageResizeM != "fit" && imageResizeM != "fill" && imageResizeM != "fixed" {
panic("imageResizeM参数错误")
}
imageResizeWStr := request.FormValue("imageResizeW")
var imageResizeW int
if imageResizeWStr != "" {
imageResizeW, err = strconv.Atoi(imageResizeWStr)
this.PanicError(err)
if imageResizeW < 1 || imageResizeW > 4096 {
panic("缩放尺寸不能超过4096")
}
}
imageResizeHStr := request.FormValue("imageResizeH")
var imageResizeH int
if imageResizeHStr != "" {
imageResizeH, err = strconv.Atoi(imageResizeHStr)
this.PanicError(err)
if imageResizeH < 1 || imageResizeH > 4096 {
panic("缩放尺寸不能超过4096")
}
}
return true, imageResizeM, imageResizeW, imageResizeH
} else if request.FormValue("ir") != "" {
//新模式使用 mode_w_h 如果w或者h为0表示这项值不设置
imageResizeStr := request.FormValue("ir")
arr := strings.Split(imageResizeStr, "_")
if len(arr) != 3 {
panic("参数不符合规范格式要求为mode_w_h")
}
imageResizeM := arr[0]
if imageResizeM == "" {
imageResizeM = "fit"
} else if imageResizeM != "fit" && imageResizeM != "fill" && imageResizeM != "fixed" {
panic("imageResizeM参数错误")
}
imageResizeWStr := arr[1]
var imageResizeW int
if imageResizeWStr != "" {
imageResizeW, err = strconv.Atoi(imageResizeWStr)
this.PanicError(err)
if imageResizeW < 0 || imageResizeW > 4096 {
panic("缩放尺寸不能超过4096")
}
}
imageResizeHStr := arr[2]
var imageResizeH int
if imageResizeHStr != "" {
imageResizeH, err = strconv.Atoi(imageResizeHStr)
this.PanicError(err)
if imageResizeH < 0 || imageResizeH > 4096 {
panic("缩放尺寸不能超过4096")
}
}
return true, imageResizeM, imageResizeW, imageResizeH
} else {
return false, "", 0, 0
}
}
//图片预处理功能。
func (this *ImageCacheService) ResizeImage(request *http.Request, filePath string) *image.NRGBA {
diskFile, err := os.Open(filePath)
this.PanicError(err)
defer func() {
e := diskFile.Close()
this.PanicError(e)
}()
_, imageResizeM, imageResizeW, imageResizeH := this.ResizeParams(request)
//单边缩略
if imageResizeM == "fit" {
//将图缩略成宽度为100高度按比例处理。
if imageResizeW != 0 {
src, err := imaging.Decode(diskFile)
this.PanicError(err)
return imaging.Resize(src, imageResizeW, 0, imaging.Lanczos)
} else if imageResizeH != 0 {
//将图缩略成高度为100宽度按比例处理。
src, err := imaging.Decode(diskFile)
this.PanicError(err)
return imaging.Resize(src, 0, imageResizeH, imaging.Lanczos)
} else {
panic("单边缩略必须指定宽或者高")
}
} else if imageResizeM == "fill" {
//固定宽高,自动裁剪
if imageResizeW > 0 && imageResizeH > 0 {
src, err := imaging.Decode(diskFile)
this.PanicError(err)
return imaging.Fill(src, imageResizeW, imageResizeH, imaging.Center, imaging.Lanczos)
} else {
panic("固定宽高,自动裁剪 必须同时指定宽和高")
}
} else if imageResizeM == "fixed" {
//强制宽高缩略
if imageResizeW > 0 && imageResizeH > 0 {
src, err := imaging.Decode(diskFile)
this.PanicError(err)
return imaging.Resize(src, imageResizeW, imageResizeH, imaging.Lanczos)
} else {
panic("强制宽高缩略必须同时指定宽和高")
}
} else {
panic("不支持" + imageResizeM + "处理模式")
}
}
//缓存一张图片
func (this *ImageCacheService) cacheImage(writer http.ResponseWriter, request *http.Request, matter *Matter) *ImageCache {
//当前的文件是否是图片,只有图片才能处理。
extension := util.GetExtension(matter.Name)
formats := map[string]imaging.Format{
".jpg": imaging.JPEG,
".jpeg": imaging.JPEG,
".png": imaging.PNG,
".tif": imaging.TIFF,
".tiff": imaging.TIFF,
".bmp": imaging.BMP,
".gif": imaging.GIF,
}
_, imageResizeM, imageResizeW, imageResizeH := this.ResizeParams(request)
mode := fmt.Sprintf("%s_%d_%d", imageResizeM, imageResizeW, imageResizeH)
format, ok := formats[extension]
if !ok {
panic("该图片格式不支持处理")
}
user := this.userDao.FindByUuid(matter.UserUuid)
//resize图片
dstImage := this.ResizeImage(request, matter.AbsolutePath())
cacheImageName := util.GetSimpleFileName(matter.Name) + "_" + mode + extension
cacheImageRelativePath := util.GetSimpleFileName(matter.Path) + "_" + mode + extension
cacheImageAbsolutePath := GetUserCacheRootDir(user.Username) + util.GetSimpleFileName(matter.Path) + "_" + mode + extension
//创建目录。
dir := filepath.Dir(cacheImageAbsolutePath)
util.MakeDirAll(dir)
fileWriter, err := os.Create(cacheImageAbsolutePath)
this.PanicError(err)
defer func() {
e := fileWriter.Close()
this.PanicError(e)
}()
//处理后的图片存放在本地
err = imaging.Encode(fileWriter, dstImage, format)
this.PanicError(err)
//获取新文件的大小
fileInfo, err := fileWriter.Stat()
this.PanicError(err)
//相关信息写到缓存中去
imageCache := &ImageCache{
Name: cacheImageName,
UserUuid: matter.UserUuid,
Username: user.Username,
MatterUuid: matter.Uuid,
MatterName: matter.Name,
Mode: mode,
Size: fileInfo.Size(),
Path: cacheImageRelativePath,
}
this.imageCacheDao.Create(imageCache)
return imageCache
}

View File

@ -0,0 +1,466 @@
package rest
import (
"fmt"
"github.com/jinzhu/gorm"
"github.com/json-iterator/go"
"github.com/nu7hatch/gouuid"
"go/build"
"io/ioutil"
"net/http"
"os"
"regexp"
"strconv"
"tank/code/config"
"tank/code/tool/builder"
"tank/code/tool/result"
"tank/code/tool/util"
"time"
)
//安装程序的接口,只有安装阶段可以访问。
type InstallController struct {
BaseController
uploadTokenDao *UploadTokenDao
downloadTokenDao *DownloadTokenDao
matterDao *MatterDao
matterService *MatterService
imageCacheDao *ImageCacheDao
imageCacheService *ImageCacheService
}
//初始化方法
func (this *InstallController) Init() {
this.BaseController.Init()
//手动装填本实例的Bean.
b := CONTEXT.GetBean(this.uploadTokenDao)
if c, ok := b.(*UploadTokenDao); ok {
this.uploadTokenDao = c
}
b = CONTEXT.GetBean(this.downloadTokenDao)
if c, ok := b.(*DownloadTokenDao); ok {
this.downloadTokenDao = c
}
b = CONTEXT.GetBean(this.matterDao)
if c, ok := b.(*MatterDao); ok {
this.matterDao = c
}
b = CONTEXT.GetBean(this.matterService)
if c, ok := b.(*MatterService); ok {
this.matterService = c
}
b = CONTEXT.GetBean(this.imageCacheDao)
if c, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = c
}
b = CONTEXT.GetBean(this.imageCacheService)
if c, ok := b.(*ImageCacheService); ok {
this.imageCacheService = c
}
}
//注册自己的路由。
func (this *InstallController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
//每个Controller需要主动注册自己的路由。
routeMap["/api/install/verify"] = this.Wrap(this.Verify, USER_ROLE_GUEST)
routeMap["/api/install/table/info/list"] = this.Wrap(this.TableInfoList, USER_ROLE_GUEST)
routeMap["/api/install/create/table"] = this.Wrap(this.CreateTable, USER_ROLE_GUEST)
routeMap["/api/install/admin/list"] = this.Wrap(this.AdminList, USER_ROLE_GUEST)
routeMap["/api/install/create/admin"] = this.Wrap(this.CreateAdmin, USER_ROLE_GUEST)
routeMap["/api/install/validate/admin"] = this.Wrap(this.ValidateAdmin, USER_ROLE_GUEST)
routeMap["/api/install/finish"] = this.Wrap(this.Finish, USER_ROLE_GUEST)
return routeMap
}
//获取数据库连接
func (this *InstallController) openDbConnection(writer http.ResponseWriter, request *http.Request) *gorm.DB {
mysqlPortStr := request.FormValue("mysqlPort")
mysqlHost := request.FormValue("mysqlHost")
mysqlSchema := request.FormValue("mysqlSchema")
mysqlUsername := request.FormValue("mysqlUsername")
mysqlPassword := request.FormValue("mysqlPassword")
var mysqlPort int
if mysqlPortStr != "" {
tmp, err := strconv.Atoi(mysqlPortStr)
this.PanicError(err)
mysqlPort = tmp
}
mysqlUrl := util.GetMysqlUrl(mysqlPort, mysqlHost, mysqlSchema, mysqlUsername, mysqlPassword)
this.logger.Info("连接MySQL %s", mysqlUrl)
var err error = nil
db, err := gorm.Open("mysql", mysqlUrl)
this.PanicError(err)
db.LogMode(false)
return db
}
//关闭数据库连接
func (this *InstallController) closeDbConnection(db *gorm.DB) {
if db != nil {
err := db.Close()
if err != nil {
this.logger.Error("关闭数据库连接出错 %v", err)
}
}
}
//根据表名获取建表SQL语句
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("从安装目录判断建表语句文件是否存在时出错!"))
}
//2. 从GOPATH下面去找因为可能是开发环境
if !exists {
this.logger.Info("GOPATH = %s", build.Default.GOPATH)
filePath1 := filePath
filePath = build.Default.GOPATH + "/src/tank/build/db/" + tableName + ".sql"
exists, err = util.PathExists(filePath)
if err != nil {
panic(result.Server("从GOPATH判断建表语句文件是否存在时出错"))
}
if !exists {
panic(result.Server("%s 或 %s 均不存在,请检查你的安装情况。", filePath1, filePath))
}
}
//读取文件内容.
bytes, err := ioutil.ReadFile(filePath)
this.PanicError(err)
return string(bytes)
}
//根据表名获取建表SQL语句
func (this *InstallController) getTableMeta(gormDb *gorm.DB, entity IBase) (bool, []*gorm.StructField, []*gorm.StructField) {
//挣扎一下,尝试获取建表语句。
db := gormDb.Unscoped()
scope := db.NewScope(entity)
tableName := scope.TableName()
modelStruct := scope.GetModelStruct()
allFields := modelStruct.StructFields
var missingFields = make([]*gorm.StructField, 0)
if !scope.Dialect().HasTable(tableName) {
missingFields = append(missingFields, allFields...)
return false, allFields, missingFields
} else {
for _, field := range allFields {
if !scope.Dialect().HasColumn(tableName, field.DBName) {
if field.IsNormal {
missingFields = append(missingFields, field)
}
}
}
return true, allFields, missingFields
}
}
//根据表名获取建表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 {
exist, allFields, missingFields := this.getTableMeta(db, iBase)
installTableInfos = append(installTableInfos, &InstallTableInfo{
Name: iBase.TableName(),
TableExist: exist,
AllFields: allFields,
MissingFields: missingFields,
})
}
return installTableInfos
}
//验证表结构是否完整。会直接抛出异常
func (this *InstallController) validateTableMetaList(tableInfoList []*InstallTableInfo) {
for _, tableInfo := range tableInfoList {
if tableInfo.TableExist {
if len(tableInfo.MissingFields) != 0 {
var strs []string
for _, v := range tableInfo.MissingFields {
strs = append(strs, v.DBName)
}
panic(result.BadRequest(fmt.Sprintf("%s 表的以下字段缺失:%v", tableInfo.Name, strs)))
}
} else {
panic(result.BadRequest(tableInfo.Name + "表不存在"))
}
}
}
//验证数据库连接
func (this *InstallController) Verify(writer http.ResponseWriter, request *http.Request) *result.WebResult {
db := this.openDbConnection(writer, request)
defer this.closeDbConnection(db)
this.logger.Info("Ping一下数据库")
err := db.DB().Ping()
this.PanicError(err)
return this.Success("OK")
}
//获取需要安装的数据库表
func (this *InstallController) TableInfoList(writer http.ResponseWriter, request *http.Request) *result.WebResult {
db := this.openDbConnection(writer, request)
defer this.closeDbConnection(db)
return this.Success(this.getTableMetaList(db))
}
//创建缺失数据库和表
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 {
//补全缺失字段或者创建数据库表
db1 := db.AutoMigrate(iBase)
this.PanicError(db1.Error)
exist, allFields, missingFields := this.getTableMeta(db, iBase)
installTableInfos = append(installTableInfos, &InstallTableInfo{
Name: iBase.TableName(),
TableExist: exist,
AllFields: allFields,
MissingFields: missingFields,
})
}
return this.Success(installTableInfos)
}
//获取管理员列表(10条记录)
func (this *InstallController) AdminList(writer http.ResponseWriter, request *http.Request) *result.WebResult {
db := this.openDbConnection(writer, request)
defer this.closeDbConnection(db)
var wp = &builder.WherePair{}
wp = wp.And(&builder.WherePair{Query: "role = ?", Args: []interface{}{USER_ROLE_ADMINISTRATOR}})
var users []*User
db = db.Where(wp.Query, wp.Args...).Offset(0).Limit(10).Find(&users)
this.PanicError(db.Error)
return this.Success(users)
}
//创建管理员
func (this *InstallController) CreateAdmin(writer http.ResponseWriter, request *http.Request) *result.WebResult {
db := this.openDbConnection(writer, request)
defer this.closeDbConnection(db)
adminUsername := request.FormValue("adminUsername")
adminEmail := request.FormValue("adminEmail")
adminPassword := request.FormValue("adminPassword")
//验证超级管理员的信息
if m, _ := regexp.MatchString(`^[0-9a-zA-Z_]+$`, adminUsername); !m {
panic(result.BadRequest(`超级管理员用户名必填,且只能包含字母,数字和'_''`))
}
if len(adminPassword) < 6 {
panic(result.BadRequest(`超级管理员密码长度至少为6位`))
}
if adminEmail == "" {
panic(result.BadRequest(`超级管理员邮箱必填`))
}
//检查是否有重复。
var count1 int64
db1 := db.Model(&User{}).Where("email = ?", adminEmail).Count(&count1)
this.PanicError(db1.Error)
if count1 > 0 {
panic(result.BadRequest(`该邮箱已存在`))
}
var count2 int64
db2 := db.Model(&User{}).Where("username = ?", adminUsername).Count(&count2)
this.PanicError(db2.Error)
if count2 > 0 {
panic(result.BadRequest(`该用户名已存在`))
}
user := &User{}
timeUUID, _ := uuid.NewV4()
user.Uuid = string(timeUUID.String())
user.CreateTime = time.Now()
user.UpdateTime = time.Now()
user.LastTime = time.Now()
user.Sort = time.Now().UnixNano() / 1e6
user.Role = USER_ROLE_ADMINISTRATOR
user.Username = adminUsername
user.Password = util.GetBcrypt(adminPassword)
user.Email = adminEmail
user.Phone = ""
user.Gender = USER_GENDER_UNKNOWN
user.SizeLimit = -1
user.Status = USER_STATUS_OK
db3 := db.Create(user)
this.PanicError(db3.Error)
return this.Success("OK")
}
//(如果数据库中本身存在管理员了)验证管理员
func (this *InstallController) ValidateAdmin(writer http.ResponseWriter, request *http.Request) *result.WebResult {
db := this.openDbConnection(writer, request)
defer this.closeDbConnection(db)
adminEmail := request.FormValue("adminEmail")
adminPassword := request.FormValue("adminPassword")
//验证超级管理员的信息
if adminEmail == "" {
panic(result.BadRequest(`超级管理员邮箱必填`))
}
if len(adminPassword) < 6 {
panic(result.BadRequest(`超级管理员密码长度至少为6位`))
}
var existEmailUser = &User{}
db = db.Where(&User{Email: adminEmail}).First(existEmailUser)
if db.Error != nil {
panic(result.BadRequest(fmt.Sprintf("%s对应的用户不存在", adminEmail)))
}
if !util.MatchBcrypt(adminPassword, existEmailUser.Password) {
panic(result.BadRequest("邮箱或密码错误"))
}
if existEmailUser.Role != USER_ROLE_ADMINISTRATOR {
panic(result.BadRequest("该账号不是管理员"))
}
return this.Success("OK")
}
//完成系统安装
func (this *InstallController) Finish(writer http.ResponseWriter, request *http.Request) *result.WebResult {
mysqlPortStr := request.FormValue("mysqlPort")
mysqlHost := request.FormValue("mysqlHost")
mysqlSchema := request.FormValue("mysqlSchema")
mysqlUsername := request.FormValue("mysqlUsername")
mysqlPassword := request.FormValue("mysqlPassword")
var mysqlPort int
if mysqlPortStr != "" {
tmp, err := strconv.Atoi(mysqlPortStr)
this.PanicError(err)
mysqlPort = tmp
}
//要求数据库连接通畅
db := this.openDbConnection(writer, request)
defer this.closeDbConnection(db)
//要求数据库完整。
tableMetaList := this.getTableMetaList(db)
this.validateTableMetaList(tableMetaList)
//要求至少有一名管理员。
var count1 int64
db1 := db.Model(&User{}).Where("role = ?", USER_ROLE_ADMINISTRATOR).Count(&count1)
this.PanicError(db1.Error)
if count1 == 0 {
panic(result.BadRequest(`请至少配置一名管理员`))
}
var configItem = &config.ConfigItem{
//默认监听端口号
ServerPort: config.CONFIG.ServerPort,
//上传的文件路径,要求不以/结尾。如果没有指定默认在根目录下的matter文件夹中。eg: /var/www/matter
MatterPath: config.CONFIG.MatterPath,
//mysql相关配置。
//数据库端口
MysqlPort: mysqlPort,
//数据库Host
MysqlHost: mysqlHost,
//数据库名字
MysqlSchema: mysqlSchema,
//用户名
MysqlUsername: mysqlUsername,
//密码
MysqlPassword: mysqlPassword,
}
//用json的方式输出返回值。为了让格式更好看。
jsonStr, _ := jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent(configItem, "", " ")
//写入到配置文件中不能使用os.O_APPEND 否则会追加)
filePath := util.GetConfPath() + "/tank.json"
f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0777)
this.PanicError(err)
_, err = f.Write(jsonStr)
this.PanicError(err)
err = f.Close()
this.PanicError(err)
//通知配置文件安装完毕。
config.CONFIG.InstallOk()
//通知全局上下文,说系统安装好了
CONTEXT.InstallOk()
return this.Success("OK")
}

View File

@ -0,0 +1,13 @@
package rest
import "github.com/jinzhu/gorm"
/**
* 表名对应的表结构
*/
type InstallTableInfo struct {
Name string `json:"name"`
TableExist bool `json:"tableExist"`
AllFields []*gorm.StructField `json:"allFields"`
MissingFields []*gorm.StructField `json:"missingFields"`
}

View File

@ -0,0 +1,444 @@
package rest
import (
"net/http"
"strconv"
"strings"
"tank/code/tool/builder"
"tank/code/tool/result"
)
type MatterController struct {
BaseController
matterDao *MatterDao
matterService *MatterService
downloadTokenDao *DownloadTokenDao
imageCacheDao *ImageCacheDao
imageCacheService *ImageCacheService
}
//初始化方法 start to develop v3.
func (this *MatterController) Init() {
this.BaseController.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.matterDao)
if b, ok := b.(*MatterDao); ok {
this.matterDao = b
}
b = CONTEXT.GetBean(this.matterService)
if b, ok := b.(*MatterService); ok {
this.matterService = b
}
b = CONTEXT.GetBean(this.downloadTokenDao)
if b, ok := b.(*DownloadTokenDao); ok {
this.downloadTokenDao = b
}
b = CONTEXT.GetBean(this.imageCacheDao)
if b, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = b
}
b = CONTEXT.GetBean(this.imageCacheService)
if b, ok := b.(*ImageCacheService); ok {
this.imageCacheService = b
}
}
//注册自己的路由。
func (this *MatterController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
//每个Controller需要主动注册自己的路由。
routeMap["/api/matter/create/directory"] = this.Wrap(this.CreateDirectory, USER_ROLE_USER)
routeMap["/api/matter/upload"] = this.Wrap(this.Upload, USER_ROLE_USER)
routeMap["/api/matter/crawl"] = this.Wrap(this.Crawl, USER_ROLE_USER)
routeMap["/api/matter/delete"] = this.Wrap(this.Delete, USER_ROLE_USER)
routeMap["/api/matter/delete/batch"] = this.Wrap(this.DeleteBatch, USER_ROLE_USER)
routeMap["/api/matter/rename"] = this.Wrap(this.Rename, USER_ROLE_USER)
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)
return routeMap
}
//查看某个文件的详情。
func (this *MatterController) Detail(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
if uuid == "" {
panic(result.BadRequest("文件的uuid必填"))
}
matter := this.matterService.Detail(uuid)
//验证当前之人是否有权限查看这么详细。
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
if matter.UserUuid != user.Uuid {
panic("没有权限查看该文件")
}
}
return this.Success(matter)
}
//按照分页的方式获取某个文件夹下文件和子文件夹的列表,通常情况下只有一页。
func (this *MatterController) Page(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//如果是根目录那么就传入root.
pageStr := request.FormValue("page")
pageSizeStr := request.FormValue("pageSize")
orderCreateTime := request.FormValue("orderCreateTime")
orderUpdateTime := request.FormValue("orderUpdateTime")
orderSort := request.FormValue("orderSort")
orderTimes := request.FormValue("orderTimes")
puuid := request.FormValue("puuid")
userUuid := request.FormValue("userUuid")
name := request.FormValue("name")
dir := request.FormValue("dir")
alien := request.FormValue("alien")
orderDir := request.FormValue("orderDir")
orderSize := request.FormValue("orderSize")
orderName := request.FormValue("orderName")
extensionsStr := request.FormValue("extensions")
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
userUuid = user.Uuid
}
var page int
if pageStr != "" {
page, _ = strconv.Atoi(pageStr)
}
pageSize := 200
if pageSizeStr != "" {
tmp, err := strconv.Atoi(pageSizeStr)
if err == nil {
pageSize = tmp
}
}
//筛选后缀名
var extensions []string
if extensionsStr != "" {
extensions = strings.Split(extensionsStr, ",")
}
sortArray := []builder.OrderPair{
{
key: "dir",
value: orderDir,
},
{
key: "create_time",
value: orderCreateTime,
},
{
key: "update_time",
value: orderUpdateTime,
},
{
key: "sort",
value: orderSort,
},
{
key: "size",
value: orderSize,
},
{
key: "name",
value: orderName,
},
{
key: "times",
value: orderTimes,
},
}
pager := this.matterDao.Page(page, pageSize, puuid, userUuid, name, dir, alien, extensions, sortArray)
return this.Success(pager)
}
//创建一个文件夹。
func (this *MatterController) CreateDirectory(writer http.ResponseWriter, request *http.Request) *result.WebResult {
puuid := request.FormValue("puuid")
name := request.FormValue("name")
userUuid := request.FormValue("userUuid")
//管理员可以指定给某个用户创建文件夹。
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
userUuid = user.Uuid
}
user = this.userDao.CheckByUuid(userUuid)
//找到父级matter
var dirMatter *Matter
if puuid == MATTER_ROOT {
dirMatter = NewRootMatter(user)
} else {
dirMatter = this.matterDao.CheckByUuid(puuid)
}
matter := this.matterService.AtomicCreateDirectory(dirMatter, name, user);
return this.Success(matter)
}
//上传文件
func (this *MatterController) Upload(writer http.ResponseWriter, request *http.Request) *result.WebResult {
userUuid := request.FormValue("userUuid")
puuid := request.FormValue("puuid")
privacyStr := request.FormValue("privacy")
file, handler, err := request.FormFile("file")
this.PanicError(err)
defer func() {
err := file.Close()
this.PanicError(err)
}()
user := this.checkUser(writer, request)
//管理员可以传到指定用户的目录下。
if user.Role != USER_ROLE_ADMINISTRATOR {
userUuid = user.Uuid
}
user = this.userDao.CheckByUuid(userUuid)
privacy := privacyStr == TRUE
err = request.ParseMultipartForm(32 << 20)
this.PanicError(err)
//对于IE浏览器filename可能包含了路径。
fileName := handler.Filename
pos := strings.LastIndex(fileName, "\\")
if pos != -1 {
fileName = fileName[pos+1:]
}
pos = strings.LastIndex(fileName, "/")
if pos != -1 {
fileName = fileName[pos+1:]
}
dirMatter := this.matterDao.CheckWithRootByUuid(puuid, user)
matter := this.matterService.AtomicUpload(file, user, dirMatter, fileName, privacy)
return this.Success(matter)
}
//从一个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")
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
userUuid = user.Uuid
} else {
if userUuid == "" {
userUuid = user.Uuid
}
}
user = this.userDao.CheckByUuid(userUuid)
dirMatter := this.matterDao.CheckWithRootByUuid(puuid, user)
privacy := false
if privacyStr == TRUE {
privacy = true
}
if url == "" || (!strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://")) {
panic("资源url必填并且应该以http://或者https://开头")
}
filename := request.FormValue("filename")
if filename == "" {
panic("文件名必传")
}
matter := this.matterService.AtomicCrawl(url, filename, user, dirMatter, privacy)
return this.Success(matter)
}
//删除一个文件
func (this *MatterController) Delete(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
if uuid == "" {
panic(result.BadRequest("文件的uuid必填"))
}
matter := this.matterDao.CheckByUuid(uuid)
//判断文件的所属人是否正确
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != user.Uuid {
panic(result.Unauthorized("没有权限"))
}
this.matterService.AtomicDelete(matter)
return this.Success("删除成功!")
}
//删除一系列文件。
func (this *MatterController) 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 {
matter := this.matterDao.FindByUuid(uuid)
//如果matter已经是nil了直接跳过
if matter == nil {
this.logger.Warn("%s 对应的文件记录已经不存在了", uuid)
continue
}
//判断文件的所属人是否正确
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != user.Uuid {
panic(result.Unauthorized("没有权限"))
}
this.matterService.AtomicDelete(matter)
}
return this.Success("删除成功!")
}
//重命名一个文件或一个文件夹
func (this *MatterController) Rename(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
name := request.FormValue("name")
user := this.checkUser(writer, request)
//找出该文件或者文件夹
matter := this.matterDao.CheckByUuid(uuid)
if user.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != user.Uuid {
panic(result.Unauthorized("没有权限"))
}
this.matterService.AtomicRename(matter, name, user)
return this.Success(matter)
}
//改变一个文件的公私有属性
func (this *MatterController) ChangePrivacy(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
privacyStr := request.FormValue("privacy")
privacy := false
if privacyStr == TRUE {
privacy = true
}
//找出该文件或者文件夹
matter := this.matterDao.CheckByUuid(uuid)
if matter.Privacy == privacy {
panic("公私有属性没有改变!")
}
//权限验证
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != user.Uuid {
panic(result.Unauthorized("没有权限"))
}
matter.Privacy = privacy
this.matterDao.Save(matter)
return this.Success("设置成功")
}
//将一个文件夹或者文件移入到另一个文件夹下。
func (this *MatterController) Move(writer http.ResponseWriter, request *http.Request) *result.WebResult {
srcUuidsStr := request.FormValue("srcUuids")
destUuid := request.FormValue("destUuid")
userUuid := request.FormValue("userUuid")
var srcUuids []string
//验证参数。
if srcUuidsStr == "" {
panic(result.BadRequest("srcUuids参数必填"))
} else {
srcUuids = strings.Split(srcUuidsStr, ",")
}
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR || userUuid == "" {
userUuid = user.Uuid
}
user = this.userDao.CheckByUuid(userUuid)
//验证dest是否有问题
var destMatter = this.matterDao.CheckWithRootByUuid(destUuid, user)
if !destMatter.Dir {
panic(result.BadRequest("目标不是文件夹"))
}
if user.Role != USER_ROLE_ADMINISTRATOR && destMatter.UserUuid != user.Uuid {
panic(result.Unauthorized("没有权限"))
}
var srcMatters []*Matter
//验证src是否有问题。
for _, uuid := range srcUuids {
//找出该文件或者文件夹
srcMatter := this.matterDao.CheckByUuid(uuid)
if srcMatter.Puuid == destMatter.Uuid {
panic(result.BadRequest("没有进行移动,操作无效!"))
}
//判断同级文件夹中是否有同名的文件
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, destMatter.Uuid, srcMatter.Dir, srcMatter.Name)
if count > 0 {
panic(result.BadRequest("【" + srcMatter.Name + "】在目标文件夹已经存在了,操作失败。"))
}
//判断和目标文件夹是否是同一个主人。
if srcMatter.UserUuid != destMatter.UserUuid {
panic("文件和目标文件夹的拥有者不是同一人")
}
srcMatters = append(srcMatters, srcMatter)
}
this.matterService.AtomicMoveBatch(srcMatters, destMatter)
return this.Success(nil)
}

372
code/rest/matter_dao.go Normal file
View File

@ -0,0 +1,372 @@
package rest
import (
"github.com/jinzhu/gorm"
"github.com/nu7hatch/gouuid"
"os"
"tank/code/config"
"tank/code/tool/builder"
"tank/code/tool/result"
"tank/code/tool/util"
"time"
)
type MatterDao struct {
BaseDao
imageCacheDao *ImageCacheDao
}
//初始化方法
func (this *MatterDao) Init() {
this.BaseDao.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.imageCacheDao)
if b, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = b
}
}
//按照Id查询文件
func (this *MatterDao) FindByUuid(uuid string) *Matter {
// Read
var matter Matter
db := CONTEXT.DB.Where(&Matter{Base: Base{Uuid: uuid}}).First(&matter)
if db.Error != nil {
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
return nil
} else {
this.PanicError(db.Error)
}
}
return &matter
}
//按照Id查询文件
func (this *MatterDao) CheckByUuid(uuid string) *Matter {
matter := this.FindByUuid(uuid)
if matter == nil {
panic(result.NotFound("%s 对应的matter不存在", uuid))
}
return matter
}
//按照uuid查找一个文件夹可能返回root对应的matter.
func (this *MatterDao) CheckWithRootByUuid(uuid string, user *User) *Matter {
if uuid == "" {
panic(result.BadRequest("uuid cannot be nil."))
}
var matter *Matter
if uuid == MATTER_ROOT {
if user == nil {
panic(result.BadRequest("user cannot be nil."))
}
matter = NewRootMatter(user)
} else {
matter = this.CheckByUuid(uuid)
}
return matter
}
//按照path查找一个matter可能返回root对应的matter.
func (this *MatterDao) CheckWithRootByPath(path string, user *User) *Matter {
var matter *Matter
if user == nil {
panic(result.BadRequest("user cannot be nil."))
}
//目标文件夹matter
if path == "" || path == "/" {
matter = NewRootMatter(user)
} else {
matter = this.checkByUserUuidAndPath(user.Uuid, path)
}
return matter
}
//按照名字查询文件夹
func (this *MatterDao) FindByUserUuidAndPuuidAndNameAndDirTrue(userUuid string, puuid string, name string) *Matter {
var wp = &builder.WherePair{}
if userUuid != "" {
wp = wp.And(&builder.WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}})
}
if puuid != "" {
wp = wp.And(&builder.WherePair{Query: "puuid = ?", Args: []interface{}{puuid}})
}
if name != "" {
wp = wp.And(&builder.WherePair{Query: "name = ?", Args: []interface{}{name}})
}
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{1}})
var matter = &Matter{}
db := CONTEXT.DB.Model(&Matter{}).Where(wp.Query, wp.Args...).First(matter)
if db.Error != nil {
return nil
}
return matter
}
//按照id和userUuid来查找。找不到抛异常。
func (this *MatterDao) CheckByUuidAndUserUuid(uuid string, userUuid string) *Matter {
// Read
var matter = &Matter{}
db := CONTEXT.DB.Where(&Matter{Base: Base{Uuid: uuid}, UserUuid: userUuid}).First(matter)
this.PanicError(db.Error)
return matter
}
//统计某个用户的某个文件夹下的某个名字的文件(或文件夹)数量。
func (this *MatterDao) CountByUserUuidAndPuuidAndDirAndName(userUuid string, puuid string, dir bool, name string) int {
var matter Matter
var count int
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 := CONTEXT.DB.
Model(&matter).
Where(wp.Query, wp.Args...).
Count(&count)
this.PanicError(db.Error)
return count
}
//获取某个用户的某个文件夹下的某个名字的文件(或文件夹)列表
func (this *MatterDao) ListByUserUuidAndPuuidAndDirAndName(userUuid string, puuid string, dir bool, name string) []*Matter {
var matters []*Matter
db := CONTEXT.DB.
Where(Matter{UserUuid: userUuid, Puuid: puuid, Dir: dir, Name: name}).
Find(&matters)
this.PanicError(db.Error)
return matters
}
//获取某个文件夹下所有的文件和子文件
func (this *MatterDao) List(puuid string, userUuid string, sortArray []builder.OrderPair) []*Matter {
var matters []*Matter
db := CONTEXT.DB.Where(Matter{UserUuid: userUuid, Puuid: puuid}).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, alien string, extensions []string, sortArray []builder.OrderPair) *Pager {
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 LIKE ?", Args: []interface{}{"%" + name + "%"}})
}
if dir == TRUE {
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{1}})
} else if dir == FALSE {
wp = wp.And(&builder.WherePair{Query: "dir = ?", Args: []interface{}{0}})
}
if alien == TRUE {
wp = wp.And(&builder.WherePair{Query: "alien = ?", Args: []interface{}{1}})
} else if alien == FALSE {
wp = wp.And(&builder.WherePair{Query: "alien = ?", Args: []interface{}{0}})
}
var conditionDB *gorm.DB
if extensions != nil && len(extensions) > 0 {
var orWp = &builder.WherePair{}
for _, v := range extensions {
orWp = orWp.Or(&builder.WherePair{Query: "name LIKE ?", Args: []interface{}{"%." + v}})
}
conditionDB = CONTEXT.DB.Model(&Matter{}).Where(wp.Query, wp.Args...).Where(orWp.Query, orWp.Args...)
} else {
conditionDB = CONTEXT.DB.Model(&Matter{}).Where(wp.Query, wp.Args...)
}
count := 0
db := conditionDB.Count(&count)
this.PanicError(db.Error)
var matters []*Matter
db = conditionDB.Order(this.GetSortString(sortArray)).Offset(page * pageSize).Limit(pageSize).Find(&matters)
this.PanicError(db.Error)
pager := NewPager(page, pageSize, count, matters)
return pager
}
//创建
func (this *MatterDao) Create(matter *Matter) *Matter {
timeUUID, _ := uuid.NewV4()
matter.Uuid = string(timeUUID.String())
matter.CreateTime = time.Now()
matter.UpdateTime = time.Now()
matter.Sort = time.Now().UnixNano() / 1e6
db := CONTEXT.DB.Create(matter)
this.PanicError(db.Error)
return matter
}
//修改一个文件
func (this *MatterDao) Save(matter *Matter) *Matter {
matter.UpdateTime = time.Now()
db := CONTEXT.DB.Save(matter)
this.PanicError(db.Error)
return matter
}
//计数器加一
func (this *MatterDao) TimesIncrement(matterUuid string) {
db := CONTEXT.DB.Model(&Matter{}).Where("uuid = ?", matterUuid).Update("times", gorm.Expr("times + 1"))
this.PanicError(db.Error)
}
//删除一个文件,数据库中删除,物理磁盘上删除。
func (this *MatterDao) Delete(matter *Matter) {
//目录的话递归删除。
if matter.Dir {
matters := this.List(matter.Uuid, matter.UserUuid, nil)
for _, f := range matters {
this.Delete(f)
}
//删除数据库中文件夹本身
db := CONTEXT.DB.Delete(&matter)
this.PanicError(db.Error)
//从磁盘中删除该文件夹。
util.DeleteEmptyDir(matter.AbsolutePath())
} else {
//删除数据库中文件记录
db := CONTEXT.DB.Delete(&matter)
this.PanicError(db.Error)
//删除对应的缓存图片。
this.imageCacheDao.DeleteByMatterUuid(matter.Uuid)
//删除文件
err := os.Remove(matter.AbsolutePath())
if err != nil {
this.logger.Error("删除磁盘上的文件出错 %s", err.Error())
}
//由于目录和物理结构一一对应,这里不能删除上级文件夹。
}
}
//获取一段时间中,总的数量
func (this *MatterDao) CountBetweenTime(startTime time.Time, endTime time.Time) int64 {
var count int64
db := CONTEXT.DB.Model(&Matter{}).Where("create_time >= ? AND create_time <= ?", startTime, endTime).Count(&count)
this.PanicError(db.Error)
return count
}
//获取一段时间中文件总大小
func (this *MatterDao) SizeBetweenTime(startTime time.Time, endTime time.Time) int64 {
var size int64
db := CONTEXT.DB.Model(&Matter{}).Where("create_time >= ? AND create_time <= ?", startTime, endTime).Select("SUM(size)")
this.PanicError(db.Error)
row := db.Row()
err := row.Scan(&size)
this.PanicError(err)
return size
}
//根据userUuid和path来查找
func (this *MatterDao) findByUserUuidAndPath(userUuid string, path string) *Matter {
var wp = &builder.WherePair{Query: "user_uuid = ? AND path = ?", Args: []interface{}{userUuid, path}}
var matter = &Matter{}
db := CONTEXT.DB.Model(&Matter{}).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
}
//根据userUuid和path来查找
func (this *MatterDao) checkByUserUuidAndPath(userUuid string, path string) *Matter {
if path == "" {
panic(result.BadRequest("path 不能为空"))
}
matter := this.findByUserUuidAndPath(userUuid, path)
if matter == nil {
panic(result.NotFound("path = %s 不存在", path))
}
return matter
}
//执行清理操作
func (this *MatterDao) Cleanup() {
this.logger.Info("[MatterDao]执行清理清除数据库中所有Matter记录。删除磁盘中所有Matter文件。")
db := CONTEXT.DB.Where("uuid is not null").Delete(Matter{})
this.PanicError(db.Error)
err := os.RemoveAll(config.CONFIG.MatterPath)
this.PanicError(err)
}

84
code/rest/matter_model.go Normal file
View File

@ -0,0 +1,84 @@
package rest
import (
"fmt"
"tank/code/config"
"tank/code/tool/util"
)
const (
//根目录的uuid
MATTER_ROOT = "root"
//cache文件夹名称
MATTER_CACHE = "cache"
//matter名称最大长度
MATTER_NAME_MAX_LENGTH = 200
//matter文件夹最大深度
MATTER_NAME_MAX_DEPTH = 32
)
/**
* 文件。
*/
type Matter struct {
Base
Puuid string `json:"puuid" gorm:"type:char(36);index:idx_puuid"`
UserUuid string `json:"userUuid" gorm:"type:char(36);index:idx_uu"`
Username string `json:"username" gorm:"type:varchar(45) not null"`
Dir bool `json:"dir" gorm:"type:tinyint(1) not null;default:0"`
Name string `json:"name" gorm:"type:varchar(255) not null"`
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)"`
Times int64 `json:"times" gorm:"type:bigint(20) not null;default:0"`
Parent *Matter `json:"parent" gorm:"-"`
}
// set File's table name to be `profiles`
func (Matter) TableName() string {
return config.TABLE_PREFIX + "matter"
}
// 获取该Matter的绝对路径。path代表的是相对路径。
func (this *Matter) AbsolutePath() string {
return GetUserFileRootDir(this.Username) + this.Path
}
// 获取该Matter的MimeType
func (this *Matter) MimeType() string {
return util.GetMimeType(util.GetExtension(this.Name))
}
//创建一个 ROOT 的matter主要用于统一化处理移动复制等内容。
func NewRootMatter(user *User) *Matter {
matter := &Matter{}
matter.Uuid = MATTER_ROOT
matter.UserUuid = user.Uuid
matter.Username = user.Username
matter.Dir = true
matter.Path = ""
matter.CreateTime = user.CreateTime
matter.UpdateTime = user.UpdateTime
return matter
}
//获取到用户文件的根目录。
func GetUserFileRootDir(username string) (rootDirPath string) {
rootDirPath = fmt.Sprintf("%s/%s/%s", config.CONFIG.MatterPath, username, MATTER_ROOT)
return rootDirPath
}
//获取到用户缓存的根目录。
func GetUserCacheRootDir(username string) (rootDirPath string) {
rootDirPath = fmt.Sprintf("%s/%s/%s", config.CONFIG.MatterPath, username, MATTER_CACHE)
return rootDirPath
}

690
code/rest/matter_service.go Normal file
View File

@ -0,0 +1,690 @@
package rest
import (
"io"
"net/http"
"os"
"regexp"
"strings"
download2 "tank/code/tool/download"
"tank/code/tool/result"
"tank/code/tool/util"
)
/**
* 操作文件的Service
* 以 Atomic 开头的方法带有操作锁这种方法不能被其他的Atomic方法调用只能提供给外部调用。
*/
//@Service
type MatterService struct {
Bean
matterDao *MatterDao
userDao *UserDao
userService *UserService
imageCacheDao *ImageCacheDao
imageCacheService *ImageCacheService
}
//初始化方法
func (this *MatterService) Init() {
this.Bean.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.matterDao)
if b, ok := b.(*MatterDao); ok {
this.matterDao = b
}
b = CONTEXT.GetBean(this.userDao)
if b, ok := b.(*UserDao); ok {
this.userDao = b
}
b = CONTEXT.GetBean(this.userService)
if b, ok := b.(*UserService); ok {
this.userService = b
}
b = CONTEXT.GetBean(this.imageCacheDao)
if b, ok := b.(*ImageCacheDao); ok {
this.imageCacheDao = b
}
b = CONTEXT.GetBean(this.imageCacheService)
if b, ok := b.(*ImageCacheService); ok {
this.imageCacheService = b
}
}
//文件下载。支持分片下载
func (this *MatterService) DownloadFile(
writer http.ResponseWriter,
request *http.Request,
filePath string,
filename string,
withContentDisposition bool) {
download2.DownloadFile(writer, request, filePath, filename, withContentDisposition)
}
//删除文件
func (this *MatterService) AtomicDelete(matter *Matter) {
if matter == nil {
panic(result.BadRequest("matter不能为nil"))
}
//操作锁
this.userService.MatterLock(matter.UserUuid)
defer this.userService.MatterUnlock(matter.UserUuid)
this.matterDao.Delete(matter)
}
//上传文件
func (this *MatterService) Upload(file io.Reader, user *User, dirMatter *Matter, filename string, privacy bool) *Matter {
if user == nil {
panic(result.BadRequest("user cannot be nil."))
}
//验证dirMatter
if dirMatter == nil {
panic(result.BadRequest("dirMatter cannot be nil."))
}
//文件名不能太长。
if len(filename) > MATTER_NAME_MAX_LENGTH {
panic(result.BadRequest("文件名不能超过%s", MATTER_NAME_MAX_LENGTH))
}
//文件夹路径
dirAbsolutePath := dirMatter.AbsolutePath()
dirRelativePath := dirMatter.Path
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, false, filename)
if count > 0 {
panic(result.BadRequest("该目录下%s已经存在了", filename))
}
//获取文件应该存放在的物理路径的绝对路径和相对路径。
fileAbsolutePath := dirAbsolutePath + "/" + filename
fileRelativePath := dirRelativePath + "/" + filename
//创建父文件夹
util.MakeDirAll(dirAbsolutePath)
//如果文件已经存在了,那么直接覆盖。
exist, err := util.PathExists(fileAbsolutePath)
this.PanicError(err)
if exist {
this.logger.Error("%s已经存在将其删除", fileAbsolutePath)
removeError := os.Remove(fileAbsolutePath)
this.PanicError(removeError)
}
destFile, err := os.OpenFile(fileAbsolutePath, os.O_WRONLY|os.O_CREATE, 0777)
this.PanicError(err)
defer func() {
err := destFile.Close()
this.PanicError(err)
}()
fileSize, err := io.Copy(destFile, file)
this.PanicError(err)
this.logger.Info("上传文件 %s 大小为 %v ", filename, util.HumanFileSize(fileSize))
//判断用户自身上传大小的限制。
if user.SizeLimit >= 0 {
if fileSize > user.SizeLimit {
//删除上传过来的内容
err = os.Remove(fileAbsolutePath)
this.PanicError(err)
panic(result.BadRequest("文件大小超出限制 %s > %s ", util.HumanFileSize(user.SizeLimit), util.HumanFileSize(fileSize)))
}
}
//将文件信息存入数据库中。
matter := &Matter{
Puuid: dirMatter.Uuid,
UserUuid: user.Uuid,
Username: user.Username,
Dir: false,
Name: filename,
Md5: "",
Size: fileSize,
Privacy: privacy,
Path: fileRelativePath,
}
matter = this.matterDao.Create(matter)
return matter
}
//上传文件
func (this *MatterService) AtomicUpload(file io.Reader, user *User, dirMatter *Matter, filename string, privacy bool) *Matter {
if user == nil {
panic(result.BadRequest("user cannot be nil."))
}
//操作锁
this.userService.MatterLock(user.Uuid)
defer this.userService.MatterUnlock(user.Uuid)
return this.Upload(file, user, dirMatter, filename, privacy)
}
//内部创建文件,不带操作锁。
func (this *MatterService) createDirectory(dirMatter *Matter, name string, user *User) *Matter {
//父级matter必须存在
if dirMatter == nil {
panic(result.BadRequest("dirMatter必须指定"))
}
//必须是文件夹
if !dirMatter.Dir {
panic(result.BadRequest("dirMatter必须是文件夹"))
}
if dirMatter.UserUuid != user.Uuid {
panic(result.BadRequest("dirMatter的userUuid和user不一致"))
}
name = strings.TrimSpace(name)
//验证参数。
if name == "" {
panic(result.BadRequest("name参数必填并且不能全是空格"))
}
if len(name) > MATTER_NAME_MAX_LENGTH {
panic(result.BadRequest("name长度不能超过%d", MATTER_NAME_MAX_LENGTH))
}
if m, _ := regexp.MatchString(`[<>|*?/\\]`, name); m {
panic(result.BadRequest(`名称中不能包含以下特殊符号:< > | * ? / \`))
}
//判断同级文件夹中是否有同名的文件夹
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, dirMatter.Uuid, true, name)
if count > 0 {
panic(result.BadRequest("%s 已经存在了,请使用其他名称。", name))
}
parts := strings.Split(dirMatter.Path, "/")
this.logger.Info("%s的层数%d", dirMatter.Name, len(parts))
if len(parts) > 32 {
panic(result.BadRequest("文件夹最多%d层", MATTER_NAME_MAX_DEPTH))
}
//绝对路径
absolutePath := GetUserFileRootDir(user.Username) + dirMatter.Path + "/" + name
//相对路径
relativePath := dirMatter.Path + "/" + name
//磁盘中创建文件夹。
dirPath := util.MakeDirAll(absolutePath)
this.logger.Info("Create Directory: %s", dirPath)
//数据库中创建文件夹。
matter := &Matter{
Puuid: dirMatter.Uuid,
UserUuid: user.Uuid,
Username: user.Username,
Dir: true,
Name: name,
Path: relativePath,
}
matter = this.matterDao.Create(matter)
return matter
}
//在dirMatter中创建文件夹 返回刚刚创建的这个文件夹
func (this *MatterService) AtomicCreateDirectory(dirMatter *Matter, name string, user *User) *Matter {
//操作锁
this.userService.MatterLock(user.Uuid)
defer this.userService.MatterUnlock(user.Uuid)
matter := this.createDirectory(dirMatter, name, user)
return matter
}
//处理 移动和复制时可能存在的覆盖问题。
func (this *MatterService) handleOverwrite(userUuid string, destinationPath string, overwrite bool) {
//目标matter。因为有可能已经存在了
destMatter := this.matterDao.findByUserUuidAndPath(userUuid, destinationPath)
//如果目标matter存在了。
if destMatter != nil {
//如果目标matter还存在了。
if overwrite {
//要求覆盖。那么删除。
this.matterDao.Delete(destMatter)
} else {
panic(result.BadRequest("%s已经存在操作失败", destMatter.Path))
}
}
}
//将一个srcMatter放置到另一个destMatter(必须为文件夹)下 不关注 overwrite 和 lock.
func (this *MatterService) move(srcMatter *Matter, destDirMatter *Matter) {
if srcMatter == nil {
panic(result.BadRequest("srcMatter cannot be nil."))
}
if !destDirMatter.Dir {
panic(result.BadRequest("目标必须为文件夹"))
}
if srcMatter.Dir {
//如果源是文件夹
destAbsolutePath := destDirMatter.AbsolutePath() + "/" + srcMatter.Name
srcAbsolutePath := srcMatter.AbsolutePath()
//物理文件一口气移动
err := os.Rename(srcAbsolutePath, destAbsolutePath)
this.PanicError(err)
//修改数据库中信息
srcMatter.Puuid = destDirMatter.Uuid
srcMatter.Path = destDirMatter.Path + "/" + srcMatter.Name
srcMatter = this.matterDao.Save(srcMatter)
//调整该文件夹下文件的Path.
matters := this.matterDao.List(srcMatter.Uuid, srcMatter.UserUuid, nil)
for _, m := range matters {
this.adjustPath(m, srcMatter)
}
} else {
//如果源是普通文件
destAbsolutePath := destDirMatter.AbsolutePath() + "/" + srcMatter.Name
srcAbsolutePath := srcMatter.AbsolutePath()
//物理文件进行移动
err := os.Rename(srcAbsolutePath, destAbsolutePath)
this.PanicError(err)
//删除对应的缓存。
this.imageCacheDao.DeleteByMatterUuid(srcMatter.Uuid)
//修改数据库中信息
srcMatter.Puuid = destDirMatter.Uuid
srcMatter.Path = destDirMatter.Path + "/" + srcMatter.Name
srcMatter = this.matterDao.Save(srcMatter)
}
return
}
//将一个srcMatter放置到另一个destMatter(必须为文件夹)下
func (this *MatterService) AtomicMove(srcMatter *Matter, destDirMatter *Matter, overwrite bool) {
if srcMatter == nil {
panic(result.BadRequest("srcMatter cannot be nil."))
}
//操作锁
this.userService.MatterLock(srcMatter.UserUuid)
defer this.userService.MatterUnlock(srcMatter.UserUuid)
if destDirMatter == nil {
panic(result.BadRequest("destDirMatter cannot be nil."))
}
if !destDirMatter.Dir {
panic(result.BadRequest("目标必须为文件夹"))
}
//文件夹不能把自己移入到自己中,也不可以移入到自己的子文件夹下。
destDirMatter = this.WrapDetail(destDirMatter)
tmpMatter := destDirMatter
for tmpMatter != nil {
if srcMatter.Uuid == tmpMatter.Uuid {
panic("文件夹不能把自己移入到自己中,也不可以移入到自己的子文件夹下。")
}
tmpMatter = tmpMatter.Parent
}
//处理覆盖的问题
destinationPath := destDirMatter.Path + "/" + srcMatter.Name
this.handleOverwrite(srcMatter.UserUuid, destinationPath, overwrite)
//做move操作。
this.move(srcMatter, destDirMatter)
}
//将一个srcMatter放置到另一个destMatter(必须为文件夹)下
func (this *MatterService) AtomicMoveBatch(srcMatters []*Matter, destDirMatter *Matter) {
if destDirMatter == nil {
panic(result.BadRequest("destDirMatter cannot be nil."))
}
//操作锁
this.userService.MatterLock(destDirMatter.UserUuid)
defer this.userService.MatterUnlock(destDirMatter.UserUuid)
if srcMatters == nil {
panic(result.BadRequest("srcMatters cannot be nil."))
}
if !destDirMatter.Dir {
panic(result.BadRequest("目标必须为文件夹"))
}
//文件夹不能把自己移入到自己中,也不可以移入到自己的子文件夹下。
destDirMatter = this.WrapDetail(destDirMatter)
for _, srcMatter := range srcMatters {
tmpMatter := destDirMatter
for tmpMatter != nil {
if srcMatter.Uuid == tmpMatter.Uuid {
panic("文件夹不能把自己移入到自己中,也不可以移入到自己的子文件夹下。")
}
tmpMatter = tmpMatter.Parent
}
}
for _, srcMatter := range srcMatters {
this.move(srcMatter, destDirMatter)
}
}
//内部移动一个文件(提供给Copy调用)无需关心overwrite问题。
func (this *MatterService) copy(srcMatter *Matter, destDirMatter *Matter, name string) {
if srcMatter.Dir {
//如果源是文件夹
//在目标地址创建新文件夹。
newMatter := &Matter{
Puuid: destDirMatter.Uuid,
UserUuid: srcMatter.UserUuid,
Username: srcMatter.Username,
Dir: srcMatter.Dir,
Name: name,
Md5: "",
Size: srcMatter.Size,
Privacy: srcMatter.Privacy,
Path: destDirMatter.Path + "/" + name,
}
newMatter = this.matterDao.Create(newMatter)
//复制子文件或文件夹
matters := this.matterDao.List(srcMatter.Uuid, srcMatter.UserUuid, nil)
for _, m := range matters {
this.copy(m, newMatter, m.Name)
}
} else {
//如果源是普通文件
destAbsolutePath := destDirMatter.AbsolutePath() + "/" + name
srcAbsolutePath := srcMatter.AbsolutePath()
//物理文件进行复制
util.CopyFile(srcAbsolutePath, destAbsolutePath)
//创建新文件的数据库信息。
newMatter := &Matter{
Puuid: destDirMatter.Uuid,
UserUuid: srcMatter.UserUuid,
Username: srcMatter.Username,
Dir: srcMatter.Dir,
Name: name,
Md5: "",
Size: srcMatter.Size,
Privacy: srcMatter.Privacy,
Path: destDirMatter.Path + "/" + name,
}
newMatter = this.matterDao.Create(newMatter)
}
}
//将一个srcMatter复制到另一个destMatter(必须为文件夹)下名字叫做name
func (this *MatterService) AtomicCopy(srcMatter *Matter, destDirMatter *Matter, name string, overwrite bool) {
if srcMatter == nil {
panic(result.BadRequest("srcMatter cannot be nil."))
}
//操作锁
this.userService.MatterLock(srcMatter.UserUuid)
defer this.userService.MatterUnlock(srcMatter.UserUuid)
if !destDirMatter.Dir {
panic(result.BadRequest("目标必须为文件夹"))
}
destinationPath := destDirMatter.Path + "/" + name
this.handleOverwrite(srcMatter.UserUuid, destinationPath, overwrite)
this.copy(srcMatter, destDirMatter, name)
}
//将一个matter 重命名为 name
func (this *MatterService) AtomicRename(matter *Matter, name string, user *User) {
if user == nil {
panic(result.BadRequest("user cannot be nil"))
}
//操作锁
this.userService.MatterLock(user.Uuid)
defer this.userService.MatterUnlock(user.Uuid)
//验证参数。
if name == "" {
panic(result.BadRequest("name参数必填"))
}
if m, _ := regexp.MatchString(`[<>|*?/\\]`, name); m {
panic(result.BadRequest(`名称中不能包含以下特殊符号:< > | * ? / \`))
}
if len(name) > 200 {
panic("name长度不能超过200")
}
if name == matter.Name {
panic(result.BadRequest("新名称和旧名称一样,操作失败!"))
}
//判断同级文件夹中是否有同名的文件
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, matter.Puuid, matter.Dir, name)
if count > 0 {
panic(result.BadRequest("【" + name + "】已经存在了,请使用其他名称。"))
}
if matter.Dir {
//如果源是文件夹
oldAbsolutePath := matter.AbsolutePath()
absoluteDirPath := util.GetDirOfPath(oldAbsolutePath)
relativeDirPath := util.GetDirOfPath(matter.Path)
newAbsolutePath := absoluteDirPath + "/" + name
//物理文件一口气移动
err := os.Rename(oldAbsolutePath, newAbsolutePath)
this.PanicError(err)
//修改数据库中信息
matter.Name = name
matter.Path = relativeDirPath + "/" + name
matter = this.matterDao.Save(matter)
//调整该文件夹下文件的Path.
matters := this.matterDao.List(matter.Uuid, matter.UserUuid, nil)
for _, m := range matters {
this.adjustPath(m, matter)
}
} else {
//如果源是普通文件
oldAbsolutePath := matter.AbsolutePath()
absoluteDirPath := util.GetDirOfPath(oldAbsolutePath)
relativeDirPath := util.GetDirOfPath(matter.Path)
newAbsolutePath := absoluteDirPath + "/" + name
//物理文件进行移动
err := os.Rename(oldAbsolutePath, newAbsolutePath)
this.PanicError(err)
//删除对应的缓存。
this.imageCacheDao.DeleteByMatterUuid(matter.Uuid)
//修改数据库中信息
matter.Name = name
matter.Path = relativeDirPath + "/" + name
matter = this.matterDao.Save(matter)
}
return
}
//根据一个文件夹路径依次创建找到最后一个文件夹的matter如果中途出错返回err.
func (this *MatterService) CreateDirectories(user *User, dirPath string) *Matter {
if dirPath == "" {
panic(`文件夹不能为空`)
} else if dirPath[0:1] != "/" {
panic(`文件夹必须以/开头`)
} else if strings.Index(dirPath, "//") != -1 {
panic(`文件夹不能出现连续的//`)
} else if m, _ := regexp.MatchString(`[<>|*?\\]`, dirPath); m {
panic(`文件夹中不能包含以下特殊符号:< > | * ? \`)
}
if dirPath == "/" {
return NewRootMatter(user)
}
//如果最后一个符号为/自动忽略
if dirPath[len(dirPath)-1] == '/' {
dirPath = dirPath[:len(dirPath)-1]
}
//递归找寻文件的上级目录uuid.
folders := strings.Split(dirPath, "/")
if len(folders) > MATTER_NAME_MAX_DEPTH {
panic(result.BadRequest("文件夹最多%d层。", MATTER_NAME_MAX_DEPTH))
}
var dirMatter *Matter
for k, name := range folders {
//split的第一个元素为空字符串忽略。
if k == 0 {
dirMatter = NewRootMatter(user)
continue
}
dirMatter = this.createDirectory(dirMatter, name, user)
}
return dirMatter
}
//包装某个matter的详情。会把父级依次倒着装进去。如果中途出错直接抛出异常。
func (this *MatterService) WrapDetail(matter *Matter) *Matter {
if matter == nil {
panic(result.BadRequest("matter cannot be nil."))
}
//组装file的内容展示其父组件。
puuid := matter.Puuid
tmpMatter := matter
for puuid != MATTER_ROOT {
pFile := this.matterDao.CheckByUuid(puuid)
tmpMatter.Parent = pFile
tmpMatter = pFile
puuid = pFile.Puuid
}
return matter
}
//获取某个文件的详情,会把父级依次倒着装进去。如果中途出错,直接抛出异常。
func (this *MatterService) Detail(uuid string) *Matter {
matter := this.matterDao.CheckByUuid(uuid)
return this.WrapDetail(matter)
}
//去指定的url中爬文件
func (this *MatterService) AtomicCrawl(url string, filename string, user *User, dirMatter *Matter, privacy bool) *Matter {
if user == nil {
panic(result.BadRequest("user cannot be nil."))
}
//操作锁
this.userService.MatterLock(user.Uuid)
defer this.userService.MatterUnlock(user.Uuid)
if url == "" || (!strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://")) {
panic("资源url必填并且应该以http://或者https://开头")
}
//从指定的url下载一个文件。参考https://golangcode.com/download-a-file-from-a-url/
resp, err := http.Get(url)
this.PanicError(err)
return this.Upload(resp.Body, user, dirMatter, filename, privacy)
}
//调整一个Matter的path值
func (this *MatterService) adjustPath(matter *Matter, parentMatter *Matter) {
if matter.Dir {
//如果源是文件夹
//首先调整好自己
matter.Path = parentMatter.Path + "/" + matter.Name
matter = this.matterDao.Save(matter)
//调整该文件夹下文件的Path.
matters := this.matterDao.List(matter.Uuid, matter.UserUuid, nil)
for _, m := range matters {
this.adjustPath(m, matter)
}
} else {
//如果源是普通文件
//删除该文件的所有缓存
this.imageCacheDao.DeleteByMatterUuid(matter.Uuid)
//调整path
matter.Path = parentMatter.Path + "/" + matter.Name
matter = this.matterDao.Save(matter)
}
}

View File

@ -0,0 +1,103 @@
package rest
import (
"net/http"
"tank/code/tool/result"
"tank/code/tool/util"
)
type PreferenceController struct {
BaseController
preferenceDao *PreferenceDao
preferenceService *PreferenceService
}
//初始化方法
func (this *PreferenceController) Init() {
this.BaseController.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.preferenceDao)
if b, ok := b.(*PreferenceDao); ok {
this.preferenceDao = b
}
b = CONTEXT.GetBean(this.preferenceService)
if b, ok := b.(*PreferenceService); ok {
this.preferenceService = b
}
}
//注册自己的路由。
func (this *PreferenceController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
//每个Controller需要主动注册自己的路由。
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)
return routeMap
}
//查看某个偏好设置的详情。
func (this *PreferenceController) Fetch(writer http.ResponseWriter, request *http.Request) *result.WebResult {
preference := this.preferenceService.Fetch()
return this.Success(preference)
}
//修改
func (this *PreferenceController) Edit(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//验证参数。
name := request.FormValue("name")
if name == "" {
panic("name参数必填")
}
logoUrl := request.FormValue("logoUrl")
faviconUrl := request.FormValue("faviconUrl")
footerLine1 := request.FormValue("footerLine1")
footerLine2 := request.FormValue("footerLine2")
showAlienStr := request.FormValue("showAlien")
preference := this.preferenceDao.Fetch()
preference.Name = name
preference.LogoUrl = logoUrl
preference.FaviconUrl = faviconUrl
preference.FooterLine1 = footerLine1
preference.FooterLine2 = footerLine2
if showAlienStr == TRUE {
preference.ShowAlien = true
} else if showAlienStr == FALSE {
preference.ShowAlien = false
}
preference = this.preferenceDao.Save(preference)
//重置缓存中的偏好
this.preferenceService.Reset()
return this.Success(preference)
}
//清扫系统,所有数据全部丢失。一定要非常慎点,非常慎点!只在系统初始化的时候点击!
func (this *PreferenceController) SystemCleanup(writer http.ResponseWriter, request *http.Request) *result.WebResult {
user := this.checkUser(writer, request)
password := request.FormValue("password")
if !util.MatchBcrypt(password, user.Password) {
panic(result.BadRequest("密码错误,不能执行!"))
}
for _, bean := range CONTEXT.BeanMap {
bean.Cleanup()
}
return this.Success("OK")
}

View File

@ -0,0 +1,65 @@
package rest
import (
"github.com/nu7hatch/gouuid"
"tank/code/tool/result"
"time"
)
type PreferenceDao struct {
BaseDao
}
//按照Id查询偏好设置
func (this *PreferenceDao) Fetch() *Preference {
// Read
var preference = &Preference{}
db := CONTEXT.DB.First(preference)
if db.Error != nil {
if db.Error.Error() == result.DB_ERROR_NOT_FOUND {
preference.Name = "蓝眼云盘"
preference.ShowAlien = true
this.Create(preference)
return preference
} else {
return nil
}
}
return preference
}
//创建
func (this *PreferenceDao) Create(preference *Preference) *Preference {
timeUUID, _ := uuid.NewV4()
preference.Uuid = string(timeUUID.String())
preference.CreateTime = time.Now()
preference.UpdateTime = time.Now()
preference.Sort = time.Now().UnixNano() / 1e6
db := CONTEXT.DB.Create(preference)
this.PanicError(db.Error)
return preference
}
//修改一个偏好设置
func (this *PreferenceDao) Save(preference *Preference) *Preference {
preference.UpdateTime = time.Now()
db := CONTEXT.DB.Save(preference)
this.PanicError(db.Error)
return preference
}
//执行清理操作
func (this *PreferenceDao) Cleanup() {
this.logger.Info("[PreferenceDao]执行清理清除数据库中所有Preference记录。")
db := CONTEXT.DB.Where("uuid is not null").Delete(Preference{})
this.PanicError(db.Error)
}

View File

@ -0,0 +1,18 @@
package rest
import "tank/code/config"
type Preference struct {
Base
Name string `json:"name" gorm:"type:varchar(45)"`
LogoUrl string `json:"logoUrl" gorm:"type:varchar(255)"`
FaviconUrl string `json:"faviconUrl" gorm:"type:varchar(255)"`
FooterLine1 string `json:"footerLine1" gorm:"type:varchar(1024)"`
FooterLine2 string `json:"footerLine2" gorm:"type:varchar(1024)"`
ShowAlien bool `json:"showAlien" gorm:"type:tinyint(1) not null;default:1"`
}
// set File's table name to be `profiles`
func (this *Preference) TableName() string {
return config.TABLE_PREFIX + "preference"
}

View File

@ -0,0 +1,45 @@
package rest
//@Service
type PreferenceService struct {
Bean
preferenceDao *PreferenceDao
preference *Preference
}
//初始化方法
func (this *PreferenceService) Init() {
this.Bean.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.preferenceDao)
if b, ok := b.(*PreferenceDao); ok {
this.preferenceDao = b
}
}
//获取单例的配置。
func (this *PreferenceService) Fetch() *Preference {
if this.preference == nil {
this.preference = this.preferenceDao.Fetch()
}
return this.preference
}
//清空单例配置。
func (this *PreferenceService) Reset() {
this.preference = nil
}
//执行清理操作
func (this *PreferenceService) Cleanup() {
this.logger.Info("[PreferenceService]执行清理重置缓存中的preference。")
this.Reset()
}

210
code/rest/router.go Normal file
View File

@ -0,0 +1,210 @@
package rest
import (
"fmt"
"github.com/json-iterator/go"
"io"
"net/http"
"os"
"strings"
"tank/code/config"
"tank/code/logger"
"tank/code/tool/result"
"tank/code/tool/util"
"time"
)
//用于处理所有前来的请求
type Router struct {
installController *InstallController
footprintService *FootprintService
userService *UserService
routeMap map[string]func(writer http.ResponseWriter, request *http.Request)
installRouteMap map[string]func(writer http.ResponseWriter, request *http.Request)
}
//构造方法
func NewRouter() *Router {
router := &Router{
routeMap: make(map[string]func(writer http.ResponseWriter, request *http.Request)),
installRouteMap: make(map[string]func(writer http.ResponseWriter, request *http.Request)),
}
//installController.
b := CONTEXT.GetBean(router.installController)
if b, ok := b.(*InstallController); ok {
router.installController = b
}
//装载userService.
b = CONTEXT.GetBean(router.userService)
if b, ok := b.(*UserService); ok {
router.userService = b
}
//装载footprintService
b = CONTEXT.GetBean(router.footprintService)
if b, ok := b.(*FootprintService); ok {
router.footprintService = b
}
//将Controller中的路由规则装载进来InstallController中的除外
for _, controller := range CONTEXT.ControllerMap {
if controller == router.installController {
routes := controller.RegisterRoutes()
for k, v := range routes {
router.installRouteMap[k] = v
}
} else {
routes := controller.RegisterRoutes()
for k, v := range routes {
router.routeMap[k] = v
}
}
}
return router
}
//全局的异常捕获
func (this *Router) GlobalPanicHandler(writer http.ResponseWriter, request *http.Request, startTime time.Time) {
if err := recover(); err != nil {
logger.LOGGER.Error("错误: %v", err)
var webResult *result.WebResult = nil
if value, ok := err.(string); ok {
//一个字符串,默认是请求错误。
webResult = result.CustomWebResult(result.CODE_WRAPPER_BAD_REQUEST, value)
} else if value, ok := err.(*result.WebResult); ok {
//一个WebResult对象
webResult = value
} else if value, ok := err.(*result.CodeWrapper); ok {
//一个WebResult对象
webResult = result.ConstWebResult(value)
} else if value, ok := err.(error); ok {
//一个普通的错误对象
webResult = result.CustomWebResult(result.CODE_WRAPPER_UNKNOWN, value.Error())
} else {
//其他不能识别的内容
webResult = result.ConstWebResult(result.CODE_WRAPPER_UNKNOWN)
}
//修改http code码
writer.WriteHeader(result.FetchHttpStatus(webResult.Code))
//输出的是json格式 返回的内容申明是jsonutf-8
writer.Header().Set("Content-Type", "application/json;charset=UTF-8")
//用json的方式输出返回值。
b, _ := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(webResult)
//写到输出流中
_, err := fmt.Fprintf(writer, string(b))
if err != nil {
fmt.Printf("输出结果时出错了\n")
}
//错误情况记录。
go util.SafeMethod(func() {
this.footprintService.Trace(writer, request, time.Now().Sub(startTime), false)
})
}
}
//让Router具有处理请求的功能。
func (this *Router) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
startTime := time.Now()
//每个请求的入口在这里
//全局异常处理。
defer this.GlobalPanicHandler(writer, request, startTime)
path := request.URL.Path
if strings.HasPrefix(path, "/api") {
//对于IE浏览器会自动缓存因此设置不缓存Header.
writer.Header().Set("Pragma", "No-cache")
writer.Header().Set("Cache-Control", "no-cache")
writer.Header().Set("Expires", "0")
if config.CONFIG.Installed {
//已安装的模式
//统一处理用户的身份信息。
this.userService.bootstrap(writer, request)
if handler, ok := this.routeMap[path]; ok {
handler(writer, request)
} else {
//直接将请求扔给每个controller看看他们能不能处理如果都不能处理那就抛出找不到的错误
canHandle := false
for _, controller := range CONTEXT.ControllerMap {
if handler, exist := controller.HandleRoutes(writer, request); exist {
canHandle = true
handler(writer, request)
break
}
}
if !canHandle {
panic(result.CustomWebResult(result.CODE_WRAPPER_NOT_FOUND, fmt.Sprintf("没有找到能够处理%s的方法", path)))
}
}
//正常的访问记录会落到这里。
go util.SafeMethod(func() {
this.footprintService.Trace(writer, request, time.Now().Sub(startTime), true)
})
} else {
//未安装模式
if handler, ok := this.installRouteMap[path]; ok {
handler(writer, request)
} else {
panic(result.ConstWebResult(result.CODE_WRAPPER_NOT_INSTALLED))
}
}
} else {
//当作静态资源处理。默认从当前文件下面的static文件夹中取东西。
dir := util.GetHtmlPath()
requestURI := request.RequestURI
if requestURI == "" || request.RequestURI == "/" {
requestURI = "index.html"
}
filePath := dir + requestURI
exists, _ := util.PathExists(filePath)
if !exists {
filePath = dir + "/index.html"
exists, _ = util.PathExists(filePath)
if !exists {
panic(fmt.Sprintf("404 not found:%s", filePath))
}
}
writer.Header().Set("Content-Type", util.GetMimeType(util.GetExtension(filePath)))
diskFile, err := os.Open(filePath)
if err != nil {
panic("cannot get file.")
}
defer func() {
err := diskFile.Close()
if err != nil {
panic(err)
}
}()
_, err = io.Copy(writer, diskFile)
if err != nil {
panic("cannot get file.")
}
}
}

78
code/rest/session_dao.go Normal file
View File

@ -0,0 +1,78 @@
package rest
import (
"github.com/nu7hatch/gouuid"
"time"
)
type SessionDao struct {
BaseDao
}
//按照Id查询session.
func (this *SessionDao) FindByUuid(uuid string) *Session {
// Read
var session = &Session{}
db := CONTEXT.DB.Where(&Session{Base: Base{Uuid: uuid}}).First(session)
if db.Error != nil {
return nil
}
return session
}
//按照Id查询session.
func (this *SessionDao) CheckByUuid(uuid string) *Session {
// Read
var session = &Session{}
db := CONTEXT.DB.Where(&Session{Base: Base{Uuid: uuid}}).First(session)
this.PanicError(db.Error)
return session
}
//创建一个session并且持久化到数据库中。
func (this *SessionDao) Create(session *Session) *Session {
timeUUID, _ := uuid.NewV4()
session.Uuid = string(timeUUID.String())
session.CreateTime = time.Now()
session.UpdateTime = time.Now()
session.Sort = time.Now().UnixNano() / 1e6
db := CONTEXT.DB.Create(session)
this.PanicError(db.Error)
return session
}
//修改一个session
func (this *SessionDao) Save(session *Session) *Session {
session.UpdateTime = time.Now()
db := CONTEXT.DB.Save(session)
this.PanicError(db.Error)
return session
}
func (this *SessionDao) Delete(uuid string) {
session := this.CheckByUuid(uuid)
session.ExpireTime = time.Now()
db := CONTEXT.DB.Delete(session)
this.PanicError(db.Error)
}
//执行清理操作
func (this *SessionDao) Cleanup() {
this.logger.Info("[SessionDao]执行清理清除数据库中所有Session记录。")
db := CONTEXT.DB.Where("uuid is not null").Delete(Session{})
this.PanicError(db.Error)
}

View File

@ -0,0 +1,18 @@
package rest
import (
"tank/code/config"
"time"
)
type Session struct {
Base
UserUuid string `json:"userUuid" gorm:"type:char(36)"`
Ip string `json:"ip" gorm:"type:varchar(128) not null"`
ExpireTime time.Time `json:"expireTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
}
// set User's table name to be `profiles`
func (this *Session) TableName() string {
return config.TABLE_PREFIX + "session"
}

View File

@ -0,0 +1,33 @@
package rest
//@Service
type SessionService struct {
Bean
userDao *UserDao
sessionDao *SessionDao
}
//初始化方法
func (this *SessionService) Init() {
this.Bean.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.userDao)
if b, ok := b.(*UserDao); ok {
this.userDao = b
}
b = CONTEXT.GetBean(this.sessionDao)
if b, ok := b.(*SessionDao); ok {
this.sessionDao = b
}
}
//执行清理操作
func (this *SessionService) Cleanup() {
this.logger.Info("[SessionService]执行清理清除缓存中所有Session记录共%d条。", CONTEXT.SessionCache.Count())
CONTEXT.SessionCache.Truncate()
}

View File

@ -0,0 +1,50 @@
package rest
import (
"github.com/nu7hatch/gouuid"
"time"
)
type UploadTokenDao struct {
BaseDao
}
//按照Id查询
func (this *UploadTokenDao) FindByUuid(uuid string) *UploadToken {
// Read
var uploadToken = &UploadToken{}
db := CONTEXT.DB.Where(&UploadToken{Base: Base{Uuid: uuid}}).First(uploadToken)
if db.Error != nil {
return nil
}
return uploadToken
}
//创建一个session并且持久化到数据库中。
func (this *UploadTokenDao) Create(uploadToken *UploadToken) *UploadToken {
timeUUID, _ := uuid.NewV4()
uploadToken.Uuid = string(timeUUID.String())
uploadToken.CreateTime = time.Now()
uploadToken.UpdateTime = time.Now()
uploadToken.Sort = time.Now().UnixNano() / 1e6
db := CONTEXT.DB.Create(uploadToken)
this.PanicError(db.Error)
return uploadToken
}
//修改一个uploadToken
func (this *UploadTokenDao) Save(uploadToken *UploadToken) *UploadToken {
uploadToken.UpdateTime = time.Now()
db := CONTEXT.DB.Save(uploadToken)
this.PanicError(db.Error)
return uploadToken
}

View File

@ -0,0 +1,22 @@
package rest
import (
"tank/code/config"
"time"
)
type UploadToken struct {
Base
UserUuid string `json:"userUuid"`
FolderUuid string `json:"folderUuid"`
MatterUuid string `json:"matterUuid"`
ExpireTime time.Time `json:"expireTime"`
Filename string `json:"filename"`
Privacy bool `json:"privacy"`
Size int64 `json:"size"`
Ip string `json:"ip"`
}
func (this *UploadToken) TableName() string {
return config.TABLE_PREFIX + "upload_token"
}

View File

@ -0,0 +1,403 @@
package rest
import (
"net/http"
"regexp"
"strconv"
"tank/code/config"
"tank/code/tool/builder"
"tank/code/tool/result"
"tank/code/tool/util"
"time"
)
type UserController struct {
BaseController
}
//初始化方法
func (this *UserController) Init() {
this.BaseController.Init()
}
//注册自己的路由。
func (this *UserController) RegisterRoutes() map[string]func(writer http.ResponseWriter, request *http.Request) {
routeMap := make(map[string]func(writer http.ResponseWriter, request *http.Request))
//每个Controller需要主动注册自己的路由。
routeMap["/api/user/create"] = this.Wrap(this.Create, USER_ROLE_ADMINISTRATOR)
routeMap["/api/user/edit"] = this.Wrap(this.Edit, USER_ROLE_USER)
routeMap["/api/user/change/password"] = this.Wrap(this.ChangePassword, USER_ROLE_USER)
routeMap["/api/user/reset/password"] = this.Wrap(this.ResetPassword, USER_ROLE_ADMINISTRATOR)
routeMap["/api/user/login"] = this.Wrap(this.Login, USER_ROLE_GUEST)
routeMap["/api/user/logout"] = this.Wrap(this.Logout, USER_ROLE_GUEST)
routeMap["/api/user/detail"] = this.Wrap(this.Detail, USER_ROLE_USER)
routeMap["/api/user/page"] = this.Wrap(this.Page, USER_ROLE_ADMINISTRATOR)
routeMap["/api/user/disable"] = this.Wrap(this.Disable, USER_ROLE_ADMINISTRATOR)
routeMap["/api/user/enable"] = this.Wrap(this.Enable, USER_ROLE_ADMINISTRATOR)
return routeMap
}
//使用用户名和密码进行登录。
//参数:
// @email:邮箱
// @password:密码
func (this *UserController) Login(writer http.ResponseWriter, request *http.Request) *result.WebResult {
email := request.FormValue("email")
password := request.FormValue("password")
if "" == email || "" == password {
panic(result.BadRequest("请输入邮箱和密码"))
}
user := this.userDao.FindByEmail(email)
if user == nil {
panic(result.BadRequest("邮箱或密码错误"))
} else {
if !util.MatchBcrypt(password, user.Password) {
panic(result.BadRequest("邮箱或密码错误"))
}
}
//登录成功设置Cookie。有效期30天。
expiration := time.Now()
expiration = expiration.AddDate(0, 0, 30)
//持久化用户的session.
session := &Session{
UserUuid: user.Uuid,
Ip: util.GetIpAddress(request),
ExpireTime: expiration,
}
session.UpdateTime = time.Now()
session.CreateTime = time.Now()
session = this.sessionDao.Create(session)
//设置用户的cookie.
cookie := http.Cookie{
Name: config.COOKIE_AUTH_KEY,
Path: "/",
Value: session.Uuid,
Expires: expiration}
http.SetCookie(writer, &cookie)
//更新用户上次登录时间和ip
user.LastTime = time.Now()
user.LastIp = util.GetIpAddress(request)
this.userDao.Save(user)
return this.Success(user)
}
//创建一个用户
func (this *UserController) Create(writer http.ResponseWriter, request *http.Request) *result.WebResult {
username := request.FormValue("username")
if m, _ := regexp.MatchString(`^[0-9a-zA-Z_]+$`, username); !m {
panic(`用户名必填,且只能包含字母,数字和'_''`)
}
password := request.FormValue("password")
if len(password) < 6 {
panic(`密码长度至少为6位`)
}
email := request.FormValue("email")
if email == "" {
panic("邮箱必填!")
}
avatarUrl := request.FormValue("avatarUrl")
phone := request.FormValue("phone")
gender := request.FormValue("gender")
role := request.FormValue("role")
city := request.FormValue("city")
//判断用户上传大小限制。
sizeLimitStr := request.FormValue("sizeLimit")
var sizeLimit int64 = 0
if sizeLimitStr == "" {
panic("用户上传限制必填!")
} else {
intsizeLimit, err := strconv.Atoi(sizeLimitStr)
if err != nil {
this.PanicError(err)
}
sizeLimit = int64(intsizeLimit)
}
//判断重名。
if this.userDao.CountByUsername(username) > 0 {
panic(username + "已经被其他用户占用。")
}
//判断邮箱重名
if this.userDao.CountByEmail(email) > 0 {
panic(email + "已经被其他用户占用。")
}
user := &User{
Role: GetRole(role),
Username: username,
Password: util.GetBcrypt(password),
Email: email,
Phone: phone,
Gender: gender,
City: city,
AvatarUrl: avatarUrl,
SizeLimit: sizeLimit,
Status: USER_STATUS_OK,
}
user = this.userDao.Create(user)
return this.Success(user)
}
//编辑一个用户的资料。
func (this *UserController) Edit(writer http.ResponseWriter, request *http.Request) *result.WebResult {
avatarUrl := request.FormValue("avatarUrl")
uuid := request.FormValue("uuid")
phone := request.FormValue("phone")
gender := request.FormValue("gender")
city := request.FormValue("city")
currentUser := this.checkUser(writer, request)
user := this.userDao.CheckByUuid(uuid)
if currentUser.Role == USER_ROLE_ADMINISTRATOR {
//只有管理员可以改变用户上传的大小
//判断用户上传大小限制。
sizeLimitStr := request.FormValue("sizeLimit")
var sizeLimit int64 = 0
if sizeLimitStr == "" {
panic("用户上传限制必填!")
} else {
intsizeLimit, err := strconv.Atoi(sizeLimitStr)
if err != nil {
this.PanicError(err)
}
sizeLimit = int64(intsizeLimit)
}
user.SizeLimit = sizeLimit
} else {
if currentUser.Uuid != uuid {
panic(result.Unauthorized("没有权限"))
}
}
user.AvatarUrl = avatarUrl
user.Phone = phone
user.Gender = GetGender(gender)
user.City = city
user = this.userDao.Save(user)
return this.Success(user)
}
//获取用户详情
func (this *UserController) Detail(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
user := this.userDao.CheckByUuid(uuid)
return this.Success(user)
}
//退出登录
func (this *UserController) Logout(writer http.ResponseWriter, request *http.Request) *result.WebResult {
//session置为过期
sessionCookie, err := request.Cookie(config.COOKIE_AUTH_KEY)
if err != nil {
return this.Success("已经退出登录了!")
}
sessionId := sessionCookie.Value
user := this.findUser(writer, request)
if user != nil {
session := this.sessionDao.FindByUuid(sessionId)
session.ExpireTime = time.Now()
this.sessionDao.Save(session)
}
//删掉session缓存
_, err = CONTEXT.SessionCache.Delete(sessionId)
if err != nil {
this.logger.Error("删除用户session缓存时出错")
}
//清空客户端的cookie.
expiration := time.Now()
expiration = expiration.AddDate(-1, 0, 0)
cookie := http.Cookie{
Name: config.COOKIE_AUTH_KEY,
Path: "/",
Value: sessionId,
Expires: expiration}
http.SetCookie(writer, &cookie)
return this.Success("退出成功!")
}
//获取用户列表 管理员的权限。
func (this *UserController) Page(writer http.ResponseWriter, request *http.Request) *result.WebResult {
pageStr := request.FormValue("page")
pageSizeStr := request.FormValue("pageSize")
orderCreateTime := request.FormValue("orderCreateTime")
orderUpdateTime := request.FormValue("orderUpdateTime")
orderSort := request.FormValue("orderSort")
username := request.FormValue("username")
email := request.FormValue("email")
phone := request.FormValue("phone")
status := request.FormValue("status")
orderLastTime := request.FormValue("orderLastTime")
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: "update_time",
value: orderUpdateTime,
},
{
key: "sort",
value: orderSort,
},
{
key: "last_time",
value: orderLastTime,
},
}
pager := this.userDao.Page(page, pageSize, username, email, phone, status, sortArray)
return this.Success(pager)
}
//禁用用户
func (this *UserController) Disable(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
user := this.userDao.CheckByUuid(uuid)
loginUser := this.checkUser(writer, request)
if uuid == loginUser.Uuid {
panic(result.BadRequest("你不能操作自己的状态。"))
}
if user.Status == USER_STATUS_DISABLED {
panic(result.BadRequest("用户已经被禁用,操作无效。"))
}
user.Status = USER_STATUS_DISABLED
user = this.userDao.Save(user)
return this.Success(user)
}
//启用用户
func (this *UserController) Enable(writer http.ResponseWriter, request *http.Request) *result.WebResult {
uuid := request.FormValue("uuid")
user := this.userDao.CheckByUuid(uuid)
loginUser := this.checkUser(writer, request)
if uuid == loginUser.Uuid {
panic(result.BadRequest("你不能操作自己的状态。"))
}
if user.Status == USER_STATUS_OK {
panic(result.BadRequest("用户已经是正常状态,操作无效。"))
}
user.Status = USER_STATUS_OK
user = this.userDao.Save(user)
return this.Success(user)
}
//用户修改密码
func (this *UserController) ChangePassword(writer http.ResponseWriter, request *http.Request) *result.WebResult {
oldPassword := request.FormValue("oldPassword")
newPassword := request.FormValue("newPassword")
if oldPassword == "" || newPassword == "" {
panic(result.BadRequest("旧密码和新密码都不能为空"))
}
user := this.checkUser(writer, request)
//如果是demo账号不提供修改密码的功能。
if user.Username == "demo" {
return this.Success(user)
}
if !util.MatchBcrypt(oldPassword, user.Password) {
panic(result.BadRequest("旧密码不正确!"))
}
user.Password = util.GetBcrypt(newPassword)
user = this.userDao.Save(user)
return this.Success(user)
}
//管理员重置用户密码
func (this *UserController) ResetPassword(writer http.ResponseWriter, request *http.Request) *result.WebResult {
userUuid := request.FormValue("userUuid")
password := request.FormValue("password")
if userUuid == "" {
panic(result.BadRequest("用户不能为空"))
}
if password == "" {
panic(result.BadRequest("密码不能为空"))
}
currentUser := this.checkUser(writer, request)
if currentUser.Role != USER_ROLE_ADMINISTRATOR {
panic(result.Unauthorized("没有权限"))
}
user := this.userDao.CheckByUuid(userUuid)
user.Password = util.GetBcrypt(password)
user = this.userDao.Save(user)
return this.Success(currentUser)
}

158
code/rest/user_dao.go Normal file
View File

@ -0,0 +1,158 @@
package rest
import (
"github.com/nu7hatch/gouuid"
"tank/code/tool/builder"
"time"
)
type UserDao struct {
BaseDao
}
//创建用户
func (this *UserDao) Create(user *User) *User {
if user == nil {
panic("参数不能为nil")
}
timeUUID, _ := uuid.NewV4()
user.Uuid = string(timeUUID.String())
user.CreateTime = time.Now()
user.UpdateTime = time.Now()
user.LastTime = time.Now()
user.Sort = time.Now().UnixNano() / 1e6
db := CONTEXT.DB.Create(user)
this.PanicError(db.Error)
return user
}
//按照Id查询用户找不到返回nil
func (this *UserDao) FindByUuid(uuid string) *User {
// Read
var user *User = &User{}
db := CONTEXT.DB.Where(&User{Base: Base{Uuid: uuid}}).First(user)
if db.Error != nil {
return nil
}
return user
}
//按照Id查询用户,找不到抛panic
func (this *UserDao) CheckByUuid(uuid string) *User {
if uuid == "" {
panic("uuid必须指定")
}
// Read
var user = &User{}
db := CONTEXT.DB.Where(&User{Base: Base{Uuid: uuid}}).First(user)
this.PanicError(db.Error)
return user
}
//按照邮箱查询用户。
func (this *UserDao) FindByUsername(username string) *User {
var user = &User{}
db := CONTEXT.DB.Where(&User{Username: username}).First(user)
if db.Error != nil {
return nil
}
return user
}
//按照邮箱查询用户。
func (this *UserDao) FindByEmail(email string) *User {
var user *User = &User{}
db := CONTEXT.DB.Where(&User{Email: email}).First(user)
if db.Error != nil {
return nil
}
return user
}
//显示用户列表。
func (this *UserDao) Page(page int, pageSize int, username string, email string, phone string, status string, sortArray []builder.OrderPair) *Pager {
var wp = &builder.WherePair{}
if username != "" {
wp = wp.And(&builder.WherePair{Query: "username LIKE ?", Args: []interface{}{"%" + username + "%"}})
}
if email != "" {
wp = wp.And(&builder.WherePair{Query: "email LIKE ?", Args: []interface{}{"%" + email + "%"}})
}
if phone != "" {
wp = wp.And(&builder.WherePair{Query: "phone = ?", Args: []interface{}{phone}})
}
if status != "" {
wp = wp.And(&builder.WherePair{Query: "status = ?", Args: []interface{}{status}})
}
count := 0
db := CONTEXT.DB.Model(&User{}).Where(wp.Query, wp.Args...).Count(&count)
this.PanicError(db.Error)
var users []*User
orderStr := this.GetSortString(sortArray)
if orderStr == "" {
db = CONTEXT.DB.Where(wp.Query, wp.Args...).Offset(page * pageSize).Limit(pageSize).Find(&users)
} else {
db = CONTEXT.DB.Where(wp.Query, wp.Args...).Order(orderStr).Offset(page * pageSize).Limit(pageSize).Find(&users)
}
this.PanicError(db.Error)
pager := NewPager(page, pageSize, count, users)
return pager
}
//查询某个用户名是否已经有用户了
func (this *UserDao) CountByUsername(username string) int {
var count int
db := CONTEXT.DB.
Model(&User{}).
Where("username = ?", username).
Count(&count)
this.PanicError(db.Error)
return count
}
//查询某个邮箱是否已经有用户了
func (this *UserDao) CountByEmail(email string) int {
var count int
db := CONTEXT.DB.
Model(&User{}).
Where("email = ?", email).
Count(&count)
this.PanicError(db.Error)
return count
}
//保存用户
func (this *UserDao) Save(user *User) *User {
user.UpdateTime = time.Now()
db := CONTEXT.DB.
Save(user)
this.PanicError(db.Error)
return user
}
//执行清理操作
func (this *UserDao) Cleanup() {
this.logger.Info("[UserDao]执行清理清除数据库中所有User记录。")
db := CONTEXT.DB.Where("uuid is not null and role != ?", USER_ROLE_ADMINISTRATOR).Delete(User{})
this.PanicError(db.Error)
}

67
code/rest/user_model.go Normal file
View File

@ -0,0 +1,67 @@
package rest
import (
"tank/code/config"
"time"
)
const (
//游客身份
USER_ROLE_GUEST = "GUEST"
//普通注册用户
USER_ROLE_USER = "USER"
//管理员
USER_ROLE_ADMINISTRATOR = "ADMINISTRATOR"
)
const (
USER_GENDER_MALE = "MALE"
USER_GENDER_FEMALE = "FEMALE"
USER_GENDER_UNKNOWN = "UNKNOWN"
)
const (
//正常状态
USER_STATUS_OK = "OK"
//被禁用
USER_STATUS_DISABLED = "DISABLED"
)
type User struct {
Base
Role string `json:"role" gorm:"type:varchar(45)"`
Username string `json:"username" gorm:"type:varchar(45) not null;unique"`
Password string `json:"-" gorm:"type:varchar(255)"`
Email string `json:"email" gorm:"type:varchar(45) not null;unique"`
Phone string `json:"phone" gorm:"type:varchar(45)"`
Gender string `json:"gender" gorm:"type:varchar(45)"`
City string `json:"city" gorm:"type:varchar(45)"`
AvatarUrl string `json:"avatarUrl" gorm:"type:varchar(255)"`
LastIp string `json:"lastIp" gorm:"type:varchar(128)"`
LastTime time.Time `json:"lastTime" gorm:"type:timestamp not null;default:'2018-01-01 00:00:00'"`
SizeLimit int64 `json:"sizeLimit" gorm:"type:bigint(20) not null;default:-1"`
Status string `json:"status" gorm:"type:varchar(45)"`
}
// set User's table name to be `profiles`
func (this *User) TableName() string {
return config.TABLE_PREFIX + "user"
}
//通过一个字符串获取性别
func GetGender(genderString string) string {
if genderString == USER_GENDER_MALE || genderString == USER_GENDER_FEMALE || genderString == USER_GENDER_UNKNOWN {
return genderString
} else {
return USER_GENDER_UNKNOWN
}
}
//通过一个字符串获取角色
func GetRole(roleString string) string {
if roleString == USER_ROLE_USER || roleString == USER_ROLE_ADMINISTRATOR {
return roleString
} else {
return USER_ROLE_USER
}
}

114
code/rest/user_service.go Normal file
View File

@ -0,0 +1,114 @@
package rest
import (
"net/http"
"tank/code/config"
cache2 "tank/code/tool/cache"
"tank/code/tool/result"
"time"
)
//@Service
type UserService struct {
Bean
userDao *UserDao
sessionDao *SessionDao
//操作文件的锁。
locker *cache2.Table
}
//初始化方法
func (this *UserService) Init() {
this.Bean.Init()
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := CONTEXT.GetBean(this.userDao)
if b, ok := b.(*UserDao); ok {
this.userDao = b
}
b = CONTEXT.GetBean(this.sessionDao)
if b, ok := b.(*SessionDao); ok {
this.sessionDao = b
}
//创建一个用于存储用户文件锁的缓存。
this.locker = cache2.NewTable()
}
//对某个用户进行加锁。加锁阶段用户是不允许操作文件的。
func (this *UserService) MatterLock(userUuid string) {
//如果已经是锁住的状态,直接报错
//去缓存中捞取
cacheItem, err := this.locker.Value(userUuid)
if err != nil {
this.logger.Error("获取缓存时出错了" + err.Error())
}
//当前被锁住了。
if cacheItem != nil && cacheItem.Data() != nil {
panic(result.BadRequest("当前正在进行文件操作,请稍后再试!"))
}
//添加一把新锁有效期为12小时
duration := 12 * time.Hour
this.locker.Add(userUuid, duration, true)
}
//对某个用户解锁,解锁后用户可以操作文件。
func (this *UserService) MatterUnlock(userUuid string) {
exist := this.locker.Exists(userUuid)
if exist {
_, err := this.locker.Delete(userUuid)
this.PanicError(err)
} else {
this.logger.Error("%s已经不存在matter锁了解锁错误。", userUuid)
}
}
//装载session信息如果session没有了根据cookie去装填用户信息。
//在所有的路由最初会调用这个方法
func (this *UserService) bootstrap(writer http.ResponseWriter, request *http.Request) {
//登录身份有效期以数据库中记录的为准
//验证用户是否已经登录。
sessionCookie, err := request.Cookie(config.COOKIE_AUTH_KEY)
if err != nil {
return
}
sessionId := sessionCookie.Value
//去缓存中捞取
cacheItem, err := CONTEXT.SessionCache.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 {
//将用户装填进缓存中
CONTEXT.SessionCache.Add(sessionCookie.Value, duration, user)
} else {
this.logger.Error("没有找到对应的user " + session.UserUuid)
}
}
}
}
}