init the project

This commit is contained in:
Zic 2017-12-23 18:02:11 +08:00
commit 81e14d12ea
54 changed files with 6829 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
#idea file
.idea
dist

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# 蓝眼云盘

View File

@ -0,0 +1,12 @@
server{
listen 80;
server_name tank.zicpo.cn;
location / {
proxy_pass http://127.0.0.1:9090;
proxy_set_header host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass_request_headers on;
client_max_body_size 2048m;
}
}

12
build/conf/tank.json Normal file
View File

@ -0,0 +1,12 @@
{
"ServerPort": 9090,
"LogToConsole": true,
"MysqlPort": 3306,
"MysqlHost": "127.0.0.1",
"MysqlSchema": "tank",
"MysqlUserName": "tank",
"MysqlPassword": "tank123",
"AdminUsername": "admin",
"AdminEmail": "admin@tank.eyeblue.cn",
"AdminPassword": "123456"
}

1
build/html/index.html Normal file
View File

@ -0,0 +1 @@
<!DOCTYPE html><html><head><title>tank-front</title><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="user-scalable=no,width=device-width,initial-scale=1,maximum-scale=1"><meta name=msapplication-tap-highlight content=no><meta name=apple-mobile-web-app-capable content=yes><link href=/static/css/app.cfd8f574917fd86fad8effb02f5563b2.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.67df5041971dde987922.js></script><script type=text/javascript src=/static/js/vendor.392879af4bb24a810ba3.js></script><script type=text/javascript src=/static/js/app.64233513a41451689d54.js></script></body></html>

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
!function(e){function __webpack_require__(r){if(_[r])return _[r].exports;var t=_[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,__webpack_require__),t.l=!0,t.exports}var r=window.webpackJsonp;window.webpackJsonp=function(_,n,o){for(var c,a,i,u=0,p=[];u<_.length;u++)a=_[u],t[a]&&p.push(t[a][0]),t[a]=0;for(c in n)Object.prototype.hasOwnProperty.call(n,c)&&(e[c]=n[c]);for(r&&r(_,n,o);p.length;)p.shift()();if(o)for(u=0;u<o.length;u++)i=__webpack_require__(__webpack_require__.s=o[u]);return i};var _={},t={2:0};__webpack_require__.e=function(e){function onScriptComplete(){o.onerror=o.onload=null,clearTimeout(c);var r=t[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),t[e]=void 0)}var r=t[e];if(0===r)return new Promise(function(e){e()});if(r)return r[2];var _=new Promise(function(_,n){r=t[e]=[_,n]});r[2]=_;var n=document.getElementsByTagName("head")[0],o=document.createElement("script");o.type="text/javascript",o.charset="utf-8",o.async=!0,o.timeout=12e4,__webpack_require__.nc&&o.setAttribute("nonce",__webpack_require__.nc),o.src=__webpack_require__.p+"static/js/"+e+"."+{0:"392879af4bb24a810ba3",1:"64233513a41451689d54"}[e]+".js";var c=setTimeout(onScriptComplete,12e4);return o.onerror=o.onload=onScriptComplete,n.appendChild(o),_},__webpack_require__.m=e,__webpack_require__.c=_,__webpack_require__.d=function(exports,e,r){__webpack_require__.o(exports,e)||Object.defineProperty(exports,e,{configurable:!1,enumerable:!0,get:r})},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,"a",r),r},__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},__webpack_require__.p="/",__webpack_require__.oe=function(e){throw console.error(e),e}}([]);
//# sourceMappingURL=manifest.67df5041971dde987922.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

68
build/script/build.bat Normal file
View File

@ -0,0 +1,68 @@
@if "%DEBUG%" == "" echo off
@rem ##########################################################################
@rem
@rem Tank build script for Windows
@rem
@rem ##########################################################################
@REM ==== START VALIDATION ====
if "%GOPATH%"=="" (
echo The GOPATH environment variable is not defined correctly
goto end
)
set PRE_DIR=%cd%
cd %GOPATH%
echo golang.org . Please download from: https://github.com/MXi4oyu/golang.org and put in the directory with same level of github.com
@rem echo go get golang.org/x
@rem go get golang.org/x
@rem resize image
echo go get github.com/disintegration/imaging
go get github.com/disintegration/imaging
@rem json parser
echo go get github.com/json-iterator/go
go get github.com/json-iterator/go
@rem mysql
echo go get github.com/go-sql-driver/mysql
go get github.com/go-sql-driver/mysql
@rem dao database
echo go get github.com/jinzhu/gorm
go get github.com/jinzhu/gorm
@rem uuid
echo go get github.com/nu7hatch/gouuid
go get github.com/nu7hatch/gouuid
echo build tank ...
go install tank
echo packaging
set distPath=%GOPATH%\src\tank\dist
if exist %distPath% (
echo clear %distPath%
rmdir /s/q %distPath%
)
echo create directory %distPath%
md %distPath%
echo copying tank.exe
copy %GOPATH%\bin\tank.exe %distPath%
echo copying build
xcopy %GOPATH%\src\tank\build %distPath% /e/h
cd %PRE_DIR%
echo check the dist file in %distPath%
echo finish!

14
build/script/shutdown.sh Normal file
View File

@ -0,0 +1,14 @@
#!/bin/bash
EXE_PATH=$GOPATH/bin/tank
EDASPID=`ps -ef | grep "$EXE_PATH"|grep -v grep |head -n 1 | awk '{print $2}'`
if [ -z $EDASPID ];
then
echo "Cannot find $EXE_PATH."
else
kill -9 $EDASPID
echo $EXE_PATH
echo 'Shutdown successfully.'
fi

22
build/script/startup.sh Normal file
View File

@ -0,0 +1,22 @@
#!/bin/bash
# executable path
EXE_PATH=$GOPATH/bin/tank
# execute arguments
MysqlHost=127.0.0.1
MysqlPort=3306
MysqlSchema=tank
MysqlUserName=tank
MysqlPassword=Tank_123
AdminUsername=admin
AdminEmail=lish516@126.com
AdminPassword=123456
if [ -f "$EXE_PATH" ]; then
nohup $EXE_PATH -MysqlHost=$MysqlHost -MysqlPort=$MysqlPort -MysqlSchema=$MysqlSchema -MysqlUserName=$MysqlUserName -MysqlPassword=$MysqlPassword -AdminUsername=$AdminUsername -AdminEmail=$AdminEmail -AdminPassword=$AdminPassword >/dev/null 2>&1 &
else
echo 'Cannot find $EXE_PATH.'
exit 1
fi

27
build/script/update.sh Normal file
View File

@ -0,0 +1,27 @@
#!/bin/sh
homePath=$GOPATH/src/tank
oldPath=$(pwd)
echo "cd homePath"
cd $homePath
echo "shutdown tank"
source $homePath/doc/script/shutdown.sh
echo "git reset"
git reset --hard HEAD
echo "git pull"
git pull
echo "go install tank"
go install tank
cd $oldPath
echo "startup tank"
source $homePath/doc/script/startup.sh

29
main.go Normal file
View File

@ -0,0 +1,29 @@
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"log"
"net/http"
"tank/rest"
)
func main() {
//将运行时参数装填到config中去。
rest.PrepareConfigs()
context := rest.NewContext()
defer context.Destroy()
http.Handle("/", context.Router)
dotPort := fmt.Sprintf(":%v", rest.CONFIG.ServerPort)
info := fmt.Sprintf("App started at http://localhost%v", dotPort)
rest.LogInfo(info)
err := http.ListenAndServe(dotPort, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}

459
rest/alien_controller.go Normal file
View File

@ -0,0 +1,459 @@
package rest
import (
"fmt"
"github.com/disintegration/imaging"
"io"
"net/http"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"time"
)
type AlienController struct {
BaseController
uploadTokenDao *UploadTokenDao
downloadTokenDao *DownloadTokenDao
matterDao *MatterDao
matterService *MatterService
}
//初始化方法
func (this *AlienController) Init(context *Context) {
this.BaseController.Init(context)
//手动装填本实例的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
}
}
//注册自己的路由。
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)
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/download/{uuid}/{filename}
reg := regexp.MustCompile(`^/api/alien/download/([^/]+)/([^/]+)$`)
strs := reg.FindStringSubmatch(path)
if len(strs) != 3 {
return nil, false
} else {
var f = func(writer http.ResponseWriter, request *http.Request) {
this.Download(writer, request, strs[1], strs[2])
}
return f, true
}
}
//直接使用邮箱和密码获取用户
func (this *AlienController) CheckRequestUser(email, password string) *User {
if email == "" {
panic("邮箱必填啦")
}
if password == "" {
panic("密码必填")
}
//验证用户身份合法性。
user := this.userDao.FindByEmail(email)
if user == nil {
panic(`邮箱或密码错误`)
} else {
if !MatchBcrypt(password, user.Password) {
panic(`邮箱或密码错误`)
}
}
return user
}
//系统中的用户x要获取一个UploadToken用于提供给x信任的用户上传文件。
func (this *AlienController) FetchUploadToken(writer http.ResponseWriter, request *http.Request) *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(request.FormValue("email"), request.FormValue("password"))
dirUuid := this.matterService.GetDirUuid(user.Uuid, dir)
mm, _ := time.ParseDuration(fmt.Sprintf("%ds", expire))
uploadToken := &UploadToken{
UserUuid: user.Uuid,
FolderUuid: dirUuid,
MatterUuid: "",
ExpireTime: time.Now().Add(mm),
Filename: filename,
Privacy: privacy,
Size: size,
Ip: GetIpAddress(request),
}
uploadToken = this.uploadTokenDao.Create(uploadToken)
return this.Success(uploadToken)
}
//系统中的用户x 拿着某个文件的uuid来确认是否其信任的用户已经上传好了。
func (this *AlienController) Confirm(writer http.ResponseWriter, request *http.Request) *WebResult {
matterUuid := request.FormValue("matterUuid")
if matterUuid == "" {
panic("matterUuid必填")
}
user := this.CheckRequestUser(request.FormValue("email"), request.FormValue("password"))
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) *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)
request.ParseMultipartForm(32 << 20)
file, handler, err := request.FormFile("file")
this.PanicError(err)
defer file.Close()
if handler.Filename != uploadToken.Filename {
panic("文件名称不正确")
}
if handler.Size != uploadToken.Size {
panic("文件大小不正确")
}
matter := this.matterService.Upload(file, user, uploadToken.FolderUuid, uploadToken.Filename, uploadToken.Privacy)
//更新这个uploadToken的信息.
uploadToken.ExpireTime = time.Now()
this.uploadTokenDao.Save(uploadToken)
return this.Success(matter)
}
//系统中的用户x要获取一个DownloadToken用于提供给x信任的用户下载文件。
func (this *AlienController) FetchDownloadToken(writer http.ResponseWriter, request *http.Request) *WebResult {
matterUuid := request.FormValue("matterUuid")
if matterUuid == "" {
panic("matterUuid必填")
}
user := this.CheckRequestUser(request.FormValue("email"), request.FormValue("password"))
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: GetIpAddress(request),
}
downloadToken = this.downloadTokenDao.Create(downloadToken)
return this.Success(downloadToken)
}
//下载一个文件。既可以使用登录的方式下载,也可以使用授权的方式下载。
func (this *AlienController) Download(writer http.ResponseWriter, request *http.Request, uuid string, filename string) {
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_UNAUTHORIZED)
}
//下载之后立即过期掉。
downloadToken.ExpireTime = time.Now()
this.downloadTokenDao.Save(downloadToken)
} else {
//判断文件的所属人是否正确
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != user.Uuid {
panic(RESULT_CODE_UNAUTHORIZED)
}
}
}
diskFile, err := os.Open(GetFilePath() + matter.Path)
this.PanicError(err)
defer diskFile.Close()
// 防止中文乱码
fileName := url.QueryEscape(matter.Name)
writer.Header().Set("Content-Type", GetMimeType(fileName))
//如果是图片或者文本就直接打开。
mimeType := GetMimeType(matter.Name)
if strings.Index(mimeType, "image") != 0 && strings.Index(mimeType, "text") != 0 {
writer.Header().Set("content-disposition", "attachment; filename=\""+fileName+"\"")
}
//对图片做缩放处理。
imageProcess := request.FormValue("imageProcess")
if imageProcess == "resize" {
//当前的文件是否是图片,只有图片才能处理。
extension := 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,
}
format, ok := formats[extension]
if !ok {
panic("该图片格式不支持处理")
}
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")
}
}
//单边缩略
if imageResizeM == "fit" {
//将图缩略成宽度为100高度按比例处理。
if imageResizeW > 0 {
src, err := imaging.Decode(diskFile)
this.PanicError(err)
dst := imaging.Resize(src, imageResizeW, 0, imaging.Lanczos)
err = imaging.Encode(writer, dst, format)
this.PanicError(err)
} else if imageResizeH > 0 {
//将图缩略成高度为100宽度按比例处理。
src, err := imaging.Decode(diskFile)
this.PanicError(err)
dst := imaging.Resize(src, 0, imageResizeH, imaging.Lanczos)
err = imaging.Encode(writer, dst, format)
this.PanicError(err)
} else {
panic("单边缩略必须指定imageResizeW或imageResizeH")
}
} else if imageResizeM == "fill" {
//固定宽高,自动裁剪
if imageResizeW > 0 && imageResizeH > 0 {
src, err := imaging.Decode(diskFile)
this.PanicError(err)
dst := imaging.Fill(src, imageResizeW, imageResizeH, imaging.Center, imaging.Lanczos)
err = imaging.Encode(writer, dst, format)
this.PanicError(err)
} else {
panic("固定宽高,自动裁剪 必须同时指定imageResizeW和imageResizeH")
}
} else if imageResizeM == "fixed" {
//强制宽高缩略
if imageResizeW > 0 && imageResizeH > 0 {
src, err := imaging.Decode(diskFile)
this.PanicError(err)
dst := imaging.Resize(src, imageResizeW, imageResizeH, imaging.Lanczos)
err = imaging.Encode(writer, dst, format)
this.PanicError(err)
} else {
panic("强制宽高缩略必须同时指定imageResizeW和imageResizeH")
}
}
} else {
_, err = io.Copy(writer, diskFile)
this.PanicError(err)
}
}

169
rest/base_controller.go Normal file
View File

@ -0,0 +1,169 @@
package rest
import (
"fmt"
"github.com/json-iterator/go"
"go/types"
"net/http"
"time"
)
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(context *Context) {
this.Bean.Init(context)
//手动装填本实例的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) *WebResult, role string) func(w http.ResponseWriter, r *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
//writer和request赋值给自己。
var webResult *WebResult = nil
//只有游客接口不需要登录
if role != USER_ROLE_GUEST {
user := this.checkUser(writer, request)
if role == USER_ROLE_ADMINISTRATOR && user.Role != USER_ROLE_ADMINISTRATOR {
webResult = ConstWebResult(RESULT_CODE_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的方式输出返回值。
var json = jsoniter.ConfigCompatibleWithStandardLibrary
b, _ := json.Marshal(webResult)
if webResult.Code == RESULT_CODE_OK {
writer.WriteHeader(http.StatusOK)
} else {
writer.WriteHeader(http.StatusBadRequest)
}
fmt.Fprintf(writer, string(b))
} else {
//输出的内容是二进制的。
}
}
}
//返回成功的结果。
func (this *BaseController) Success(data interface{}) *WebResult {
var webResult *WebResult = nil
if value, ok := data.(string); ok {
webResult = &WebResult{Code: RESULT_CODE_OK, Msg: value}
} else if value, ok := data.(*WebResult); ok {
webResult = value
} else if _, ok := data.(types.Nil); ok {
webResult = ConstWebResult(RESULT_CODE_OK)
} else {
webResult = &WebResult{Code: RESULT_CODE_OK, Data: data}
}
return webResult
}
//返回错误的结果。
func (this *BaseController) Error(err interface{}) *WebResult {
var webResult *WebResult = nil
if value, ok := err.(string); ok {
webResult = &WebResult{Code: RESULT_CODE_UTIL_EXCEPTION, Msg: value}
} else if value, ok := err.(int); ok {
webResult = ConstWebResult(value)
} else if value, ok := err.(*WebResult); ok {
webResult = value
} else if value, ok := err.(error); ok {
webResult = &WebResult{Code: RESULT_CODE_UTIL_EXCEPTION, Msg: value.Error()}
} else {
webResult = &WebResult{Code: RESULT_CODE_UTIL_EXCEPTION, Msg: "服务器未知错误"}
}
return webResult
}
func (this *BaseController) checkLogin(writer http.ResponseWriter, request *http.Request) (*Session, *User) {
//验证用户是否已经登录。
sessionCookie, err := request.Cookie(COOKIE_AUTH_KEY)
if err != nil {
panic(ConstWebResult(RESULT_CODE_LOGIN))
}
session := this.sessionDao.FindByUuid(sessionCookie.Value)
if session == nil {
panic(ConstWebResult(RESULT_CODE_LOGIN))
} else {
if session.ExpireTime.Before(time.Now()) {
panic(ConstWebResult(RESULT_CODE_LOGIN_EXPIRED))
} else {
user := this.userDao.FindByUuid(session.UserUuid)
if user == nil {
panic(ConstWebResult(RESULT_CODE_LOGIN_INVALID))
} else {
return session, user
}
}
}
}
func (this *BaseController) checkUser(writer http.ResponseWriter, request *http.Request) *User {
_, user := this.checkLogin(writer, request)
return user
}
//允许跨域请求
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")
}

12
rest/base_dao.go Normal file
View File

@ -0,0 +1,12 @@
package rest
import (
_ "github.com/jinzhu/gorm/dialects/mysql"
)
type BaseDao struct {
Bean
}

48
rest/base_model.go Normal file
View File

@ -0,0 +1,48 @@
package rest
import (
"time"
"reflect"
"math"
)
type Time time.Time
type Base struct {
Uuid string `gorm:"primary_key" json:"uuid"`
Sort int64 `json:"sort"`
ModifyTime time.Time `json:"modifyTime"`
CreateTime time.Time `json:"createTime"`
}
//将 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
}
//分页类
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,
}
}

21
rest/bean.go Normal file
View File

@ -0,0 +1,21 @@
package rest
type IBean interface {
Init(context *Context)
PanicError(err error);
}
type Bean struct {
context *Context
}
func (this *Bean) Init(context *Context) {
this.context = context
}
//处理错误的统一方法
func (this *Bean) PanicError(err error) {
if err != nil {
panic(err)
}
}

222
rest/config.go Normal file
View File

@ -0,0 +1,222 @@
package rest
import (
"fmt"
"github.com/json-iterator/go"
"time"
"unsafe"
"io/ioutil"
"encoding/json"
"flag"
)
const (
//用户身份的cookie字段名
COOKIE_AUTH_KEY = "_ak"
//数据库表前缀 tank100表示当前应用版本是tank:1.0.x版数据库结构发生变化必然是中型升级
TABLE_PREFIX = "tank10_"
//当前版本
VERSION = "1.0.0"
)
var (
CONFIG = &Config{
//以下内容是默认配置项。
//默认监听端口号
ServerPort: 9090,
//将日志输出到控制台。
LogToConsole: true,
//mysql相关配置。
//数据库端口
MysqlPort: 3306,
//数据库Host
MysqlHost: "127.0.0.1",
//数据库名字
MysqlSchema: "tank",
//用户名
MysqlUserName: "tank",
//密码
MysqlPassword: "tank123",
//数据库连接信息。
MysqlUrl: "%MysqlUserName:%MysqlPassword@tcp(%MysqlHost:%MysqlPort)/%MysqlSchema?charset=utf8&parseTime=True&loc=Local",
//超级管理员用户名,只能包含英文和数字
AdminUsername: "admin",
//超级管理员邮箱
AdminEmail: "admin@tank.eyeblue.cn",
//超级管理员密码
AdminPassword: "123456",
}
)
//依赖外部定义的变量。
type Config struct {
//默认监听端口号
ServerPort int
//将日志输出到控制台。
LogToConsole bool
//mysql相关配置。
//数据库端口
MysqlPort int
//数据库Host
MysqlHost string
//数据库名字
MysqlSchema string
//用户名
MysqlUserName string
//密码
MysqlPassword string
//数据库连接信息。
MysqlUrl string
//超级管理员用户名,只能包含英文和数字
AdminUsername string
//超级管理员邮箱
AdminEmail string
//超级管理员密码
AdminPassword string
}
//验证配置文件的正确性。
func (this *Config) validate() {
if this.ServerPort == 0 {
LogPanic("ServerPort 未配置")
}
if this.MysqlUserName == "" {
LogPanic("MysqlUserName 未配置")
}
if this.MysqlPassword == "" {
LogPanic("MysqlPassword 未配置")
}
if this.MysqlHost == "" {
LogPanic("MysqlHost 未配置")
}
if this.MysqlPort == 0 {
LogPanic("MysqlPort 未配置")
}
if this.MysqlSchema == "" {
LogPanic("MysqlSchema 未配置")
}
this.MysqlUrl = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", this.MysqlUserName, this.MysqlPassword, this.MysqlHost, this.MysqlPort, this.MysqlSchema)
}
//init方法只要这个包被引入了就一定会执行。
func init() {
//json中需要去特殊处理时间。
jsoniter.RegisterTypeDecoderFunc("time.Time", func(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
//如果使用time.UTC那么时间会相差8小时
t, err := time.ParseInLocation("2006-01-02 15:04:05", iter.ReadString(), time.Local)
if err != nil {
iter.Error = err
return
}
*((*time.Time)(ptr)) = t
})
jsoniter.RegisterTypeEncoderFunc("time.Time", func(ptr unsafe.Pointer, stream *jsoniter.Stream) {
t := *((*time.Time)(ptr))
//如果使用time.UTC那么时间会相差8小时
stream.WriteString(t.Local().Format("2006-01-02 15:04:05"))
}, nil)
}
//从conf/tank.json中获取变量。
//从flag中或者conf/tank.json中装填变量
func PrepareConfigs() {
//读取配置文件
filePath := GetConfPath() + "/tank.json"
content, err := ioutil.ReadFile(filePath)
if err != nil {
LogWarning(fmt.Sprintf("无法找到配置文件:%s,%v", filePath, err))
} else {
// 用 json.Unmarshal
err := json.Unmarshal(content, CONFIG)
if err != nil {
LogPanic("配置文件格式错误!")
}
}
//从运行时参数中读取,运行时参数具有更高优先级。
//系统端口号
ServerPortPtr := flag.Int("ServerPort", CONFIG.ServerPort, "server port")
//系统端口号
LogToConsolePtr := flag.Bool("LogToConsole", CONFIG.LogToConsole, "write log to console. for debug.")
//mysql相关配置。
MysqlPortPtr := flag.Int("MysqlPort", CONFIG.MysqlPort, "mysql port")
MysqlHostPtr := flag.String("MysqlHost", CONFIG.MysqlHost, "mysql host")
MysqlSchemaPtr := flag.String("MysqlSchema", CONFIG.MysqlSchema, "mysql schema")
MysqlUserNamePtr := flag.String("MysqlUserName", CONFIG.MysqlUserName, "mysql username")
MysqlPasswordPtr := flag.String("MysqlPassword", CONFIG.MysqlPassword, "mysql password")
//超级管理员信息
AdminUsernamePtr := flag.String("AdminUsername", CONFIG.AdminUsername, "administrator username")
AdminEmailPtr := flag.String("AdminEmail", CONFIG.AdminEmail, "administrator email")
AdminPasswordPtr := flag.String("AdminPassword", CONFIG.AdminPassword, "administrator password")
//flag.Parse()方法必须要在使用之前调用。
flag.Parse()
if *ServerPortPtr != CONFIG.ServerPort {
CONFIG.ServerPort = *ServerPortPtr
}
if *LogToConsolePtr != CONFIG.LogToConsole {
CONFIG.LogToConsole = *LogToConsolePtr
}
if *MysqlPortPtr != CONFIG.MysqlPort {
CONFIG.MysqlPort = *MysqlPortPtr
}
if *MysqlHostPtr != CONFIG.MysqlHost {
CONFIG.MysqlHost = *MysqlHostPtr
}
if *MysqlSchemaPtr != CONFIG.MysqlSchema {
CONFIG.MysqlSchema = *MysqlSchemaPtr
}
if *MysqlUserNamePtr != CONFIG.MysqlUserName {
CONFIG.MysqlUserName = *MysqlUserNamePtr
}
if *MysqlPasswordPtr != CONFIG.MysqlPassword {
CONFIG.MysqlPassword = *MysqlPasswordPtr
}
if *AdminUsernamePtr != CONFIG.AdminUsername {
CONFIG.AdminUsername = *AdminUsernamePtr
}
if *AdminEmailPtr != CONFIG.AdminEmail {
CONFIG.AdminEmail = *AdminEmailPtr
}
if *AdminPasswordPtr != CONFIG.AdminPassword {
CONFIG.AdminPassword = *AdminPasswordPtr
}
//验证配置项的正确性
CONFIG.validate()
//安装程序开始导入初始表和初始数据。
InstallDatabase()
}

146
rest/context.go Normal file
View File

@ -0,0 +1,146 @@
package rest
import (
"fmt"
"github.com/jinzhu/gorm"
"reflect"
)
//上下文管理数据库连接管理所有路由请求管理所有的单例component.
type Context struct {
//数据库连接
DB *gorm.DB
//处理所有路由请求
Router *Router
//各类的Bean Map。这里面是包含ControllerMap中所有元素
BeanMap map[string]IBean
//只包含了Controller的map
ControllerMap map[string]IController
}
func (this *Context) OpenDb() {
var err error = nil
this.DB, err = gorm.Open("mysql", CONFIG.MysqlUrl)
//是否打开sql日志
this.DB.LogMode(false)
if err != nil {
panic("failed to connect mysql database")
}
}
func (this *Context) CloseDb() {
if this.DB != nil {
this.DB.Close()
}
}
//构造方法
func NewContext() *Context {
context := &Context{}
//处理数据库连接的开关。
context.OpenDb()
//初始化Map
context.BeanMap = make(map[string]IBean)
context.ControllerMap = make(map[string]IController)
//注册各类Beans.在这个方法里面顺便把Controller装入ControllerMap中去。
context.registerBeans()
//初始化每个bean.
context.initBeans()
//初始化Router. 这个方法要在Bean注册好了之后才能。
context.Router = NewRouter(context)
return context
}
//注册一个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 {
LogError(fmt.Sprintf(err))
} else {
this.BeanMap[typeName] = element
//看看是不是controller类型如果是那么单独放在ControllerMap中。
if controller, ok1 := bean.(IController); ok1 {
this.ControllerMap[typeName] = controller
}
}
} else {
err := fmt.Sprintf("注册的【%s】不是Bean类型。", typeName)
panic(err)
}
}
//注册各个Beans
func (this *Context) registerBeans() {
//alien
this.registerBean(new(AlienController))
//downloadToken
this.registerBean(new(DownloadTokenDao))
//matter
this.registerBean(new(MatterController))
this.registerBean(new(MatterDao))
this.registerBean(new(MatterService))
//session
this.registerBean(new(SessionDao))
//uploadToken
this.registerBean(new(UploadTokenDao))
//user
this.registerBean(new(UserController))
this.registerBean(new(UserDao))
}
//从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 {
err := fmt.Sprintf("【%s】没有注册。", typeName)
panic(err)
}
}
//初始化每个Bean
func (this *Context) initBeans() {
for _, bean := range this.BeanMap {
bean.Init(this)
}
}
//销毁的方法
func (this *Context) Destroy() {
this.CloseDb()
}

View File

@ -0,0 +1,60 @@
package rest
import (
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/nu7hatch/gouuid"
"time"
)
type DownloadTokenDao struct {
BaseDao
}
//按照Id查询
func (this *DownloadTokenDao) FindByUuid(uuid string) *DownloadToken {
// Read
var downloadToken = &DownloadToken{}
db := this.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 := this.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.ModifyTime = time.Now()
db := this.context.DB.Create(downloadToken)
this.PanicError(db.Error)
return downloadToken
}
//修改一个downloadToken
func (this *DownloadTokenDao) Save(downloadToken *DownloadToken) *DownloadToken {
downloadToken.ModifyTime = time.Now()
db := this.context.DB.Save(downloadToken)
this.PanicError(db.Error)
return downloadToken
}

View File

@ -0,0 +1,17 @@
package rest
import (
"time"
)
type DownloadToken struct {
Base
UserUuid string `json:"userUuid"`
MatterUuid string `json:"matterUuid"`
ExpireTime time.Time `json:"expireTime"`
Ip string `json:"ip"`
}
func (DownloadToken) TableName() string {
return TABLE_PREFIX + "download_token"
}

117
rest/install.go Normal file
View File

@ -0,0 +1,117 @@
package rest
import (
"fmt"
"github.com/jinzhu/gorm"
"github.com/nu7hatch/gouuid"
"time"
"regexp"
)
//首次运行的时候,将自动安装数据库等内容。
func InstallDatabase() {
db, err := gorm.Open("mysql", CONFIG.MysqlUrl)
if err != nil {
LogPanic(fmt.Sprintf("无法打开%s", CONFIG.MysqlUrl))
}
if db != nil {
defer db.Close()
}
//这个方法只会简单查看表是否存在,不会去比照每个字段的。因此如果用户自己修改表结构将会出现不可预测的错误。
var hasTable = true
downloadToken := &DownloadToken{}
hasTable = db.HasTable(downloadToken)
if !hasTable {
createDownloadToken := "CREATE TABLE `tank10_download_token` (`uuid` char(36) NOT NULL,`user_uuid` char(36) DEFAULT NULL COMMENT '用户uuid',`matter_uuid` char(36) DEFAULT NULL COMMENT '文件标识',`expire_time` timestamp NULL DEFAULT NULL COMMENT '授权访问的次数',`ip` varchar(45) DEFAULT NULL COMMENT '消费者的ip',`sort` bigint(20) DEFAULT NULL,`modify_time` timestamp NULL DEFAULT NULL,`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`uuid`),UNIQUE KEY `id_UNIQUE` (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='下载的token表';"
db = db.Exec(createDownloadToken)
if db.Error != nil {
LogPanic(db.Error)
}
LogInfo("创建DownloadToken表")
}
matter := &Matter{}
hasTable = db.HasTable(matter)
if !hasTable {
createMatter := "CREATE TABLE `tank10_matter` (`uuid` char(36) NOT NULL,`puuid` varchar(45) DEFAULT NULL COMMENT '上一级的uuid',`user_uuid` char(36) DEFAULT NULL COMMENT '上传的用户id',`dir` tinyint(1) DEFAULT NULL COMMENT '是否是文件夹',`name` varchar(255) DEFAULT NULL COMMENT '文件名称',`md5` varchar(45) DEFAULT NULL COMMENT '文件的md5值',`size` bigint(20) DEFAULT '0' COMMENT '文件大小',`privacy` tinyint(1) DEFAULT '0' COMMENT '文件是否是公有的',`path` varchar(255) DEFAULT NULL,`sort` bigint(20) DEFAULT NULL,`modify_time` timestamp NULL DEFAULT NULL,`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`uuid`),UNIQUE KEY `id_UNIQUE` (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='file表';"
db = db.Exec(createMatter)
if db.Error != nil {
LogPanic(db.Error)
}
LogInfo("创建Matter表")
}
session := &Session{}
hasTable = db.HasTable(session)
if !hasTable {
createSession := "CREATE TABLE `tank10_session` (`uuid` char(36) NOT NULL,`authentication` char(36) DEFAULT NULL COMMENT '认证身份存放在cookie中',`user_uuid` char(36) DEFAULT NULL COMMENT '用户uuid',`ip` varchar(45) DEFAULT NULL COMMENT '用户的ip地址',`expire_time` timestamp NULL DEFAULT NULL,`sort` bigint(20) DEFAULT NULL,`modify_time` timestamp NULL DEFAULT NULL,`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`uuid`),UNIQUE KEY `id_UNIQUE` (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='session表';"
db = db.Exec(createSession)
if db.Error != nil {
LogPanic(db.Error)
}
LogInfo("创建Session表")
}
uploadToken := &UploadToken{}
hasTable = db.HasTable(uploadToken)
if !hasTable {
createUploadToken := "CREATE TABLE `tank10_upload_token` (`uuid` char(36) NOT NULL,`user_uuid` char(36) DEFAULT NULL COMMENT '用户uuid',`folder_uuid` char(36) DEFAULT NULL,`matter_uuid` char(36) DEFAULT NULL,`filename` varchar(255) DEFAULT NULL COMMENT '文件后缀名的过滤,可以只允许用户上传特定格式的文件。',`privacy` tinyint(1) DEFAULT '1',`size` bigint(20) DEFAULT '0',`expire_time` timestamp NULL DEFAULT NULL,`ip` varchar(45) DEFAULT NULL COMMENT '消费者的ip',`sort` bigint(20) DEFAULT NULL,`modify_time` timestamp NULL DEFAULT NULL,`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`uuid`),UNIQUE KEY `id_UNIQUE` (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='上传的token表';"
db = db.Exec(createUploadToken)
if db.Error != nil {
LogPanic(db.Error)
}
LogInfo("创建UploadToken表")
}
user := &User{}
hasTable = db.HasTable(user)
if !hasTable {
//验证超级管理员的信息
if m, _ := regexp.MatchString(`^[0-9a-zA-Z_]+$`, CONFIG.AdminUsername); !m {
LogPanic(`超级管理员用户名必填,且只能包含字母,数字和'_''`)
}
if len(CONFIG.AdminPassword) < 6 {
LogPanic(`超级管理员密码长度至少为6位`)
}
if CONFIG.AdminEmail == "" {
LogPanic("超级管理员邮箱必填!")
}
createUser := "CREATE TABLE `tank10_user` (`uuid` char(36) NOT NULL,`role` varchar(45) DEFAULT 'USER',`username` varchar(255) DEFAULT NULL COMMENT '昵称',`password` varchar(255) DEFAULT NULL COMMENT '密码',`email` varchar(45) DEFAULT NULL COMMENT '邮箱',`phone` varchar(45) DEFAULT NULL COMMENT '电话',`gender` varchar(45) DEFAULT 'UNKNOWN' COMMENT '性别,默认未知',`city` varchar(45) DEFAULT NULL COMMENT '城市',`avatar_url` varchar(255) DEFAULT NULL COMMENT '头像链接',`last_time` datetime DEFAULT NULL COMMENT '上次登录使劲按',`last_ip` varchar(45) DEFAULT NULL,`status` varchar(45) DEFAULT 'OK',`sort` bigint(20) DEFAULT NULL,`modify_time` timestamp NULL DEFAULT NULL,`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',PRIMARY KEY (`uuid`),UNIQUE KEY `id_UNIQUE` (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表描述';"
db = db.Exec(createUser)
if db.Error != nil {
LogPanic(db.Error)
}
LogInfo("创建User表")
user := &User{}
timeUUID, _ := uuid.NewV4()
user.Uuid = string(timeUUID.String())
user.CreateTime = time.Now()
user.ModifyTime = time.Now()
user.LastTime = time.Now()
user.Sort = time.Now().UnixNano() / 1e6
user.Role = USER_ROLE_ADMINISTRATOR
user.Username = CONFIG.AdminUsername
user.Password = GetBcrypt(CONFIG.AdminPassword)
user.Email = CONFIG.AdminEmail
user.Phone = ""
user.Gender = USER_GENDER_UNKNOWN
user.Status = USER_STATUS_OK
db.Create(user)
}
}

384
rest/matter_controller.go Normal file
View File

@ -0,0 +1,384 @@
package rest
import (
"net/http"
"regexp"
"strconv"
"strings"
)
type MatterController struct {
BaseController
matterDao *MatterDao
matterService *MatterService
downloadTokenDao *DownloadTokenDao
}
//初始化方法
func (this *MatterController) Init(context *Context) {
this.BaseController.Init(context)
//手动装填本实例的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
}
}
//注册自己的路由。
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/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/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) *WebResult {
uuid := request.FormValue("uuid")
if uuid == "" {
return this.Error("文件的uuid必填")
}
matter := this.matterDao.FindByUuid(uuid)
//组装file的内容展示其父组件。
puuid := matter.Puuid
tmpMatter := matter
for puuid != "root" {
pFile := this.matterDao.FindByUuid(puuid)
tmpMatter.Parent = pFile
tmpMatter = pFile
puuid = pFile.Puuid
}
return this.Success(matter)
}
//创建一个文件夹。
func (this *MatterController) CreateDirectory(writer http.ResponseWriter, request *http.Request) *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)
//验证参数。
if name == "" {
return this.Error("name参数必填")
}
if m, _ := regexp.MatchString(`[<>|*?/\\]`, name); m {
return this.Error(`名称中不能包含以下特殊符号:< > | * ? / \`)
}
if puuid != "" && puuid != "root" {
//找出上一级的文件夹。
this.matterDao.FindByUuidAndUserUuid(puuid, user.Uuid)
}
//判断同级文件夹中是否有同名的文件。
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, puuid, true, name)
if count > 0 {
return this.Error("【" + name + "】已经存在了,请使用其他名称。")
}
matter := &Matter{
Puuid: puuid,
UserUuid: user.Uuid,
Dir: true,
Name: name,
}
matter = this.matterDao.Create(matter)
return this.Success(matter)
}
//按照分页的方式获取某个文件夹下文件和子文件夹的列表,通常情况下只有一页。
func (this *MatterController) Page(writer http.ResponseWriter, request *http.Request) *WebResult {
//如果是根目录那么就传入root.
puuid := request.FormValue("puuid")
pageStr := request.FormValue("page")
pageSizeStr := request.FormValue("pageSize")
userUuid := request.FormValue("userUuid")
name := request.FormValue("name")
dir := request.FormValue("dir")
orderDir := request.FormValue("orderDir")
orderCreateTime := request.FormValue("orderCreateTime")
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, ",")
}
//文件列表默认文件夹始终在文件的前面。
if orderDir == "" {
orderDir = "DESC"
}
sortArray := []OrderPair{
{
key: "dir",
value: orderDir,
},
{
key: "create_time",
value: orderCreateTime,
},
{
key: "size",
value: orderSize,
},
{
key: "name",
value: orderName,
},
}
pager := this.matterDao.Page(page, pageSize, puuid, userUuid, name, dir, extensions, sortArray)
return this.Success(pager)
}
//上传文件
func (this *MatterController) Upload(writer http.ResponseWriter, request *http.Request) *WebResult {
userUuid := request.FormValue("userUuid")
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
userUuid = user.Uuid
}
user = this.userDao.CheckByUuid(userUuid)
puuid := request.FormValue("puuid")
if puuid == "" {
return this.Error("puuid必填")
} else {
if puuid != "root" {
//找出上一级的文件夹。
this.matterDao.FindByUuidAndUserUuid(puuid, userUuid)
}
}
request.ParseMultipartForm(32 << 20)
file, handler, err := request.FormFile("file")
this.PanicError(err)
defer file.Close()
matter := this.matterService.Upload(file, user, puuid, handler.Filename, true)
return this.Success(matter)
}
//删除一个文件
func (this *MatterController) Delete(writer http.ResponseWriter, request *http.Request) *WebResult {
uuid := request.FormValue("uuid")
if uuid == "" {
return this.Error("文件的uuid必填")
}
matter := this.matterDao.FindByUuid(uuid)
//判断文件的所属人是否正确
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != user.Uuid {
return this.Error(RESULT_CODE_UNAUTHORIZED)
}
this.matterDao.Delete(matter)
return this.Success("删除成功!")
}
//删除一系列文件。
func (this *MatterController) DeleteBatch(writer http.ResponseWriter, request *http.Request) *WebResult {
uuids := request.FormValue("uuids")
if uuids == "" {
return this.Error("文件的uuids必填")
}
uuidArray := strings.Split(uuids, ",")
for _, uuid := range uuidArray {
matter := this.matterDao.FindByUuid(uuid)
//判断文件的所属人是否正确
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != user.Uuid {
return this.Error(RESULT_CODE_UNAUTHORIZED)
}
this.matterDao.Delete(matter)
}
return this.Success("删除成功!")
}
//重命名一个文件或一个文件夹
func (this *MatterController) Rename(writer http.ResponseWriter, request *http.Request) *WebResult {
uuid := request.FormValue("uuid")
name := request.FormValue("name")
//验证参数。
if name == "" {
return this.Error("name参数必填")
}
if m, _ := regexp.MatchString(`[<>|*?/\\]`, name); m {
return this.Error(`名称中不能包含以下特殊符号:< > | * ? / \`)
}
//找出该文件或者文件夹
matter := this.matterDao.FindByUuid(uuid)
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR && matter.UserUuid != user.Uuid {
return this.Error(RESULT_CODE_UNAUTHORIZED)
}
if name == matter.Name {
return this.Error("新名称和旧名称一样,操作失败!")
}
//判断同级文件夹中是否有同名的文件
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, matter.Puuid, matter.Dir, name)
if count > 0 {
return this.Error("【" + name + "】已经存在了,请使用其他名称。")
}
matter.Name = name
matter = this.matterDao.Save(matter)
return this.Success(matter)
}
//将一个文件夹或者文件移入到另一个文件夹下。
func (this *MatterController) Move(writer http.ResponseWriter, request *http.Request) *WebResult {
srcUuidsStr := request.FormValue("srcUuids")
destUuid := request.FormValue("destUuid")
var srcUuids []string
//验证参数。
if srcUuidsStr == "" {
return this.Error("srcUuids参数必填")
} else {
srcUuids = strings.Split(srcUuidsStr, ",")
}
userUuid := request.FormValue("userUuid")
user := this.checkUser(writer, request)
if user.Role != USER_ROLE_ADMINISTRATOR {
userUuid = user.Uuid
}
if userUuid == "" {
userUuid = user.Uuid
}
user = this.userDao.CheckByUuid(userUuid)
//验证dest是否有问题
if destUuid == "" {
return this.Error("destUuid参数必填")
} else {
if destUuid != "root" {
destMatter := this.matterDao.FindByUuid(destUuid)
if user.Role != USER_ROLE_ADMINISTRATOR && destMatter.UserUuid != user.Uuid {
return this.Error(RESULT_CODE_UNAUTHORIZED)
}
}
}
var srcMatters []*Matter
//验证src是否有问题。
for _, uuid := range srcUuids {
//找出该文件或者文件夹
srcMatter := this.matterDao.FindByUuid(uuid)
if user.Role != USER_ROLE_ADMINISTRATOR && srcMatter.UserUuid != user.Uuid {
return this.Error(RESULT_CODE_UNAUTHORIZED)
}
if srcMatter.Puuid == destUuid {
return this.Error("没有进行移动,操作无效!")
}
//判断同级文件夹中是否有同名的文件
count := this.matterDao.CountByUserUuidAndPuuidAndDirAndName(user.Uuid, destUuid, srcMatter.Dir, srcMatter.Name)
if count > 0 {
return this.Error("【" + srcMatter.Name + "】在目标文件夹已经存在了,操作失败。")
}
srcMatters = append(srcMatters, srcMatter)
}
for _, srcMatter := range srcMatters {
srcMatter.Puuid = destUuid
srcMatter = this.matterDao.Save(srcMatter)
}
return this.Success(nil)
}

237
rest/matter_dao.go Normal file
View File

@ -0,0 +1,237 @@
package rest
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/nu7hatch/gouuid"
"os"
"time"
)
type MatterDao struct {
BaseDao
}
//按照Id查询文件
func (this *MatterDao) FindByUuid(uuid string) *Matter {
// Read
var matter Matter
db := this.context.DB.Where(&Matter{Base: Base{Uuid: uuid}}).First(&matter)
if db.Error != nil {
return nil
}
return &matter
}
//按照Id查询文件
func (this *MatterDao) CheckByUuid(uuid string) *Matter {
// Read
var matter Matter
db := this.context.DB.Where(&Matter{Base: Base{Uuid: uuid}}).First(&matter)
this.PanicError(db.Error)
return &matter
}
//按照名字查询文件夹
func (this *MatterDao) FindByUserUuidAndPuuidAndNameAndDirTrue(userUuid string, puuid string, name string) *Matter {
var wp = &WherePair{}
if userUuid != "" {
wp = wp.And(&WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}})
}
if puuid != "" {
wp = wp.And(&WherePair{Query: "puuid = ?", Args: []interface{}{puuid}})
}
if name != "" {
wp = wp.And(&WherePair{Query: "name = ?", Args: []interface{}{name}})
}
wp = wp.And(&WherePair{Query: "dir = ?", Args: []interface{}{1}})
var matter = &Matter{}
db := this.context.DB.Model(&Matter{}).Where(wp.Query, wp.Args...).First(matter)
if db.Error != nil {
return nil
}
return matter
}
//按照id和userUuid来查找。
func (this *MatterDao) FindByUuidAndUserUuid(uuid string, userUuid string) *Matter {
// Read
var matter = &Matter{}
db := this.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 = &WherePair{}
if puuid != "" {
wp = wp.And(&WherePair{Query: "puuid = ?", Args: []interface{}{puuid}})
}
if userUuid != "" {
wp = wp.And(&WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}})
}
if name != "" {
wp = wp.And(&WherePair{Query: "name = ?", Args: []interface{}{name}})
}
wp = wp.And(&WherePair{Query: "dir = ?", Args: []interface{}{dir}})
db := this.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 := this.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 []OrderPair) []*Matter {
var matters []*Matter
db := this.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, extensions []string, sortArray []OrderPair) *Pager {
var wp = &WherePair{}
if puuid != "" {
wp = wp.And(&WherePair{Query: "puuid = ?", Args: []interface{}{puuid}})
}
if userUuid != "" {
wp = wp.And(&WherePair{Query: "user_uuid = ?", Args: []interface{}{userUuid}})
}
if name != "" {
wp = wp.And(&WherePair{Query: "name LIKE ?", Args: []interface{}{"%" + name + "%"}})
}
if dir == "true" {
wp = wp.And(&WherePair{Query: "dir = ?", Args: []interface{}{1}})
} else if dir == "false" {
wp = wp.And(&WherePair{Query: "dir = ?", Args: []interface{}{0}})
}
var conditionDB *gorm.DB
if extensions != nil && len(extensions) > 0 {
var orWp = &WherePair{}
for _, v := range extensions {
orWp = orWp.Or(&WherePair{Query: "name LIKE ?", Args: []interface{}{"%." + v}})
}
conditionDB = this.context.DB.Model(&Matter{}).Where(wp.Query, wp.Args...).Where(orWp.Query, orWp.Args...)
} else {
conditionDB = this.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.ModifyTime = time.Now()
db := this.context.DB.Create(matter)
this.PanicError(db.Error)
return matter
}
//修改一个文件
func (this *MatterDao) Save(matter *Matter) *Matter {
matter.ModifyTime = time.Now()
db := this.context.DB.Save(matter)
this.PanicError(db.Error)
return matter
}
//删除一个文件,数据库中删除,物理磁盘上删除。
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 := this.context.DB.Delete(&matter)
this.PanicError(db.Error)
} else {
db := this.context.DB.Delete(&matter)
this.PanicError(db.Error)
//删除文件
err := os.Remove(GetFilePath() + matter.Path)
LogError(fmt.Sprintf("删除磁盘上的文件出错,不做任何处理"))
this.PanicError(err)
}
}

19
rest/matter_model.go Normal file
View File

@ -0,0 +1,19 @@
package rest
type Matter struct {
Base
Puuid string `json:"puuid"`
UserUuid string `json:"userUuid"`
Dir bool `json:"dir"`
Name string `json:"name"`
Md5 string `json:"md5"`
Size int64 `json:"size"`
Privacy bool `json:"privacy"`
Path string `json:"path"`
Parent *Matter `gorm:"-" json:"parent"`
}
// set File's table name to be `profiles`
func (Matter) TableName() string {
return TABLE_PREFIX + "matter"
}

117
rest/matter_service.go Normal file
View File

@ -0,0 +1,117 @@
package rest
import (
"io"
"mime/multipart"
"os"
"regexp"
"strings"
)
//@Service
type MatterService struct {
Bean
matterDao *MatterDao
}
//初始化方法
func (this *MatterService) Init(context *Context) {
//手动装填本实例的Bean. 这里必须要用中间变量方可。
b := context.GetBean(this.matterDao)
if b, ok := b.(*MatterDao); ok {
this.matterDao = b
}
}
//根据一个文件夹路径找到最后一个文件夹的uuid如果中途出错返回err.
func (this *MatterService) GetDirUuid(userUuid string, dir string) string {
if dir == "" {
panic(`文件夹不能为空`)
} else if dir[0:1] != "/" {
panic(`文件夹必须以/开头`)
} else if strings.Index(dir, "//") != -1 {
panic(`文件夹不能出现连续的//`)
} else if m, _ := regexp.MatchString(`[<>|*?\\]`, dir); m {
panic(`文件夹中不能包含以下特殊符号:< > | * ? \`)
}
if dir == "/" {
return "root"
}
if dir[len(dir)-1] == '/' {
dir = dir[:len(dir)-1]
}
//递归找寻文件的上级目录uuid.
folders := strings.Split(dir, "/")
puuid := "root"
for k, name := range folders {
if k == 0 {
continue
}
matter := this.matterDao.FindByUserUuidAndPuuidAndNameAndDirTrue(userUuid, puuid, name)
if matter == nil {
//创建一个文件夹。
matter = &Matter{
Puuid: puuid,
UserUuid: userUuid,
Dir: true,
Name: name,
}
matter = this.matterDao.Create(matter)
}
puuid = matter.Uuid
}
return puuid
}
//开始上传文件
//上传文件
func (this *MatterService) Upload(file multipart.File, user *User, puuid string, filename string, privacy bool) *Matter {
//获取文件应该存放在的物理路径的绝对路径和相对路径。
absolutePath, relativePath := GetUserFilePath(user.Username)
absolutePath = absolutePath + "/" + filename
relativePath = relativePath + "/" + filename
distFile, err := os.OpenFile(absolutePath, os.O_WRONLY|os.O_CREATE, 0777)
this.PanicError(err)
defer distFile.Close()
written, err := io.Copy(distFile, file)
this.PanicError(err)
//查找文件夹下面是否有同名文件。
matters := this.matterDao.ListByUserUuidAndPuuidAndDirAndName(user.Uuid, puuid, false, filename)
//如果有同名的文件,那么我们直接覆盖同名文件。
for _, dbFile := range matters {
this.matterDao.Delete(dbFile)
}
//将文件信息存入数据库中。
matter := &Matter{
Puuid: puuid,
UserUuid: user.Uuid,
Dir: false,
Name: filename,
Md5: "",
Size: written,
Privacy: privacy,
Path: relativePath,
}
matter = this.matterDao.Create(matter)
return matter
}

138
rest/router.go Normal file
View File

@ -0,0 +1,138 @@
package rest
import (
"fmt"
"github.com/json-iterator/go"
"io"
"net/http"
"os"
"strings"
)
//用于处理所有前来的请求
type Router struct {
context *Context
routeMap map[string]func(writer http.ResponseWriter, request *http.Request)
}
//构造方法
func NewRouter(context *Context) *Router {
router := &Router{
context: context,
routeMap: make(map[string]func(writer http.ResponseWriter, request *http.Request)),
}
for _, controller := range context.ControllerMap {
routes := controller.RegisterRoutes()
for k, v := range routes {
router.routeMap[k] = v
}
}
return router
}
//全局的异常捕获
func (this *Router) GlobalPanicHandler(writer http.ResponseWriter, request *http.Request) {
if err := recover(); err != nil {
LogError(fmt.Sprintf("全局异常: %v", err))
var webResult *WebResult = nil
if value, ok := err.(string); ok {
webResult = &WebResult{Code: RESULT_CODE_UTIL_EXCEPTION, Msg: value}
} else if value, ok := err.(int); ok {
webResult = ConstWebResult(value)
} else if value, ok := err.(*WebResult); ok {
webResult = value
} else if value, ok := err.(WebResult); ok {
webResult = &value
} else if value, ok := err.(error); ok {
webResult = &WebResult{Code: RESULT_CODE_UTIL_EXCEPTION, Msg: value.Error()}
} else {
webResult = &WebResult{Code: RESULT_CODE_UTIL_EXCEPTION, Msg: "服务器未知错误"}
}
//输出的是json格式 返回的内容申明是jsonutf-8
writer.Header().Set("Content-Type", "application/json;charset=UTF-8")
//用json的方式输出返回值。
var json = jsoniter.ConfigCompatibleWithStandardLibrary
b, _ := json.Marshal(webResult)
if webResult.Code == RESULT_CODE_OK {
writer.WriteHeader(http.StatusOK)
} else {
writer.WriteHeader(http.StatusBadRequest)
}
fmt.Fprintf(writer, string(b))
}
}
//让Router具有处理请求的功能。
func (this *Router) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
//每个请求的入口在这里
//全局异常处理。
defer this.GlobalPanicHandler(writer, request)
path := request.URL.Path
if strings.HasPrefix(path, "/api") {
if handler, ok := this.routeMap[path]; ok {
handler(writer, request)
} else {
//直接将请求扔给每个controller看看他们能不能处理如果都不能处理那就算了。
canHandle := false
for _, controller := range this.context.ControllerMap {
if handler, exist := controller.HandleRoutes(writer, request); exist {
canHandle = true
handler(writer, request)
break
}
}
if !canHandle {
panic(fmt.Sprintf("没有找到能够处理%s的方法\n", path))
}
}
} else {
//当作静态资源处理。默认从当前文件下面的static文件夹中取东西。
dir := GetHtmlPath()
requestURI := request.RequestURI
if requestURI == "" || request.RequestURI == "/" {
requestURI = "index.html"
}
filePath := dir + requestURI
exists, _ := PathExists(filePath)
if !exists {
filePath = dir + "/index.html"
exists, _ = PathExists(filePath)
if !exists {
panic("404 not found")
}
}
writer.Header().Set("Content-Type", GetMimeType(GetExtension(filePath)))
diskFile, err := os.Open(filePath)
if err != nil {
panic("cannot get file.")
}
defer diskFile.Close()
_, err = io.Copy(writer, diskFile)
if err != nil {
panic("cannot get file.")
}
}
}

75
rest/session_dao.go Normal file
View File

@ -0,0 +1,75 @@
package rest
import (
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/nu7hatch/gouuid"
"time"
)
type SessionDao struct {
BaseDao
}
//构造函数
func NewSessionDao(context *Context) *SessionDao {
var sessionDao = &SessionDao{}
sessionDao.Init(context)
return sessionDao
}
//按照Id查询session.
func (this *SessionDao) FindByUuid(uuid string) *Session {
// Read
var session = &Session{}
db := this.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 := this.context.DB.Where(&Session{Base: Base{Uuid: uuid}}).First(session)
this.PanicError(db.Error)
return session
}
//按照authentication查询用户。
func (this *SessionDao) FindByAuthentication(authentication string) *Session {
var session = &Session{}
db := this.context.DB.Where(&Session{Authentication: authentication}).First(session)
if db.Error != nil {
return nil
}
return session
}
//创建一个session并且持久化到数据库中。
func (this *SessionDao) Create(session *Session) *Session {
timeUUID, _ := uuid.NewV4()
session.Uuid = string(timeUUID.String())
db := this.context.DB.Create(session)
this.PanicError(db.Error)
return session
}
func (this *SessionDao) Delete(uuid string) {
session := this.CheckByUuid(uuid)
session.ExpireTime = time.Now()
db := this.context.DB.Delete(session)
this.PanicError(db.Error)
}

18
rest/session_model.go Normal file
View File

@ -0,0 +1,18 @@
package rest
import (
"time"
)
type Session struct {
Base
Authentication string `json:"authentication"`
UserUuid string `json:"userUuid"`
Ip string `json:"ip"`
ExpireTime time.Time `json:"expireTime"`
}
// set User's table name to be `profiles`
func (Session) TableName() string {
return TABLE_PREFIX + "session"
}

48
rest/sql_builder.go Normal file
View File

@ -0,0 +1,48 @@
package rest
type OrderPair struct {
key string
value string
}
type WherePair struct {
Query string
Args []interface{}
}
func (this *WherePair) And(where *WherePair) *WherePair {
if this.Query == "" {
return where
} else {
return &WherePair{Query: this.Query + " AND " + where.Query, Args: append(this.Args, where.Args...)}
}
}
func (this *WherePair) Or(where *WherePair) *WherePair {
if this.Query == "" {
return where
} else {
return &WherePair{Query: this.Query + " OR " + where.Query, Args: append(this.Args, where.Args...)}
}
}
//根据一个sortMap获取到order字符串
func (this *BaseDao) GetSortString(sortArray []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
}

50
rest/upload_token_dao.go Normal file
View File

@ -0,0 +1,50 @@
package rest
import (
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/nu7hatch/gouuid"
"time"
)
type UploadTokenDao struct {
BaseDao
}
//按照Id查询
func (this *UploadTokenDao) FindByUuid(uuid string) *UploadToken {
// Read
var uploadToken = &UploadToken{}
db := this.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.ModifyTime = time.Now()
db := this.context.DB.Create(uploadToken)
this.PanicError(db.Error)
return uploadToken
}
//修改一个uploadToken
func (this *UploadTokenDao) Save(uploadToken *UploadToken) *UploadToken {
uploadToken.ModifyTime = time.Now()
db := this.context.DB.Save(uploadToken)
this.PanicError(db.Error)
return uploadToken
}

View File

@ -0,0 +1,21 @@
package rest
import (
"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 (UploadToken) TableName() string {
return TABLE_PREFIX + "upload_token"
}

322
rest/user_controller.go Normal file
View File

@ -0,0 +1,322 @@
package rest
import (
"net/http"
"regexp"
"strconv"
"time"
)
type UserController struct {
BaseController
}
//初始化方法
func (this *UserController) Init(context *Context) {
this.BaseController.Init(context)
}
//注册自己的路由。
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_ADMINISTRATOR)
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_USER)
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) *WebResult {
email := request.FormValue("email")
password := request.FormValue("password")
if "" == email || "" == password {
return this.Error("请输入邮箱和密码")
}
user := this.userDao.FindByEmail(email)
if user == nil {
return this.Error("邮箱或密码错误")
} else {
if !MatchBcrypt(password, user.Password) {
return this.Error("邮箱或密码错误")
}
}
//登录成功设置Cookie。有效期7天。
expiration := time.Now()
expiration = expiration.AddDate(0, 0, 7)
//持久化用户的session.
session := &Session{
UserUuid: user.Uuid,
Ip: GetIpAddress(request),
ExpireTime: expiration,
}
session.ModifyTime = time.Now()
session.CreateTime = time.Now()
session = this.sessionDao.Create(session)
//设置用户的cookie.
cookie := http.Cookie{
Name: COOKIE_AUTH_KEY,
Path: "/",
Value: session.Uuid,
Expires: expiration}
http.SetCookie(writer, &cookie)
return this.Success(user)
}
//创建一个用户
func (this *UserController) Create(writer http.ResponseWriter, request *http.Request) *WebResult {
username := request.FormValue("username")
if m, _ := regexp.MatchString(`^[0-9a-zA-Z_]+$`, username); !m {
return this.Error(`用户名必填,且只能包含字母,数字和'_''`)
}
password := request.FormValue("password")
if len(password) < 6 {
return this.Error(`密码长度至少为6位`)
}
email := request.FormValue("email")
if email == "" {
return this.Error("邮箱必填!")
}
phone := request.FormValue("phone")
gender := request.FormValue("gender")
role := request.FormValue("role")
city := request.FormValue("city")
//判断重名。
if this.userDao.CountByUsername(username) > 0 {
return this.Error(username + "已经被其他用户占用。")
}
//判断邮箱重名
if this.userDao.CountByEmail(email) > 0 {
return this.Error(email + "已经被其他用户占用。")
}
user := &User{
Role: GetRole(role),
Username: username,
Password: GetBcrypt(password),
Email: email,
Phone: phone,
Gender: gender,
City: city,
Status: USER_STATUS_OK,
}
user = this.userDao.Create(user)
return this.Success(user)
}
//编辑一个用户的资料。
func (this *UserController) Edit(writer http.ResponseWriter, request *http.Request) *WebResult {
uuid := request.FormValue("uuid")
phone := request.FormValue("phone")
gender := request.FormValue("gender")
city := request.FormValue("city")
currentUser := this.checkUser(writer, request)
if currentUser.Role != USER_ROLE_ADMINISTRATOR {
if currentUser.Uuid != uuid {
return this.Error(RESULT_CODE_UNAUTHORIZED)
}
}
user := this.userDao.CheckByUuid(uuid)
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) *WebResult {
uuid := request.FormValue("uuid")
user := this.userDao.CheckByUuid(uuid)
return this.Success(user)
}
//退出登录
func (this *UserController) Logout(writer http.ResponseWriter, request *http.Request) *WebResult {
session, _ := this.checkLogin(writer, request)
//删除session
this.sessionDao.Delete(session.Uuid)
//清空客户端的cookie.
expiration := time.Now()
expiration = expiration.AddDate(-1, 0, 0)
cookie := http.Cookie{
Name: COOKIE_AUTH_KEY,
Path: "/",
Value: session.Uuid,
Expires: expiration}
http.SetCookie(writer, &cookie)
return this.Success("退出成功!")
}
//获取用户列表 管理员的权限。
func (this *UserController) Page(writer http.ResponseWriter, request *http.Request) *WebResult {
//如果是根目录那么就传入root.
pageStr := request.FormValue("page")
pageSizeStr := request.FormValue("pageSize")
username := request.FormValue("username")
email := request.FormValue("email")
phone := request.FormValue("phone")
orderLastTime := request.FormValue("orderLastTime")
orderCreateTime := request.FormValue("orderCreateTime")
var page int
if pageStr != "" {
page, _ = strconv.Atoi(pageStr)
}
pageSize := 200
if pageSizeStr != "" {
tmp, err := strconv.Atoi(pageSizeStr)
if err == nil {
pageSize = tmp
}
}
sortArray := []OrderPair{
{
key: "last_time",
value: orderLastTime,
},
{
key: "create_time",
value: orderCreateTime,
},
}
pager := this.userDao.Page(page, pageSize, username, email, phone, sortArray)
return this.Success(pager)
}
//禁用用户
func (this *UserController) Disable(writer http.ResponseWriter, request *http.Request) *WebResult {
uuid := request.FormValue("uuid")
user := this.userDao.CheckByUuid(uuid)
if user.Status == USER_STATUS_DISABLED {
return this.Error("用户已经被禁用,操作无效。")
}
user.Status = USER_STATUS_DISABLED
user = this.userDao.Save(user)
return this.Success(user)
}
//启用用户
func (this *UserController) Enable(writer http.ResponseWriter, request *http.Request) *WebResult {
uuid := request.FormValue("uuid")
user := this.userDao.CheckByUuid(uuid)
if user.Status == USER_STATUS_OK {
return this.Error("用户已经是正常状态,操作无效。")
}
user.Status = USER_STATUS_OK
user = this.userDao.Save(user)
return this.Success(user)
}
//用户修改密码
func (this *UserController) ChangePassword(writer http.ResponseWriter, request *http.Request) *WebResult {
oldPassword := request.FormValue("oldPassword")
newPassword := request.FormValue("newPassword")
if oldPassword == "" || newPassword == "" {
return this.Error("旧密码和新密码都不能为空")
}
user := this.checkUser(writer, request)
if !MatchBcrypt(oldPassword, user.Password) {
return this.Error("旧密码不正确!")
}
user.Password = GetBcrypt(newPassword)
user = this.userDao.Save(user)
return this.Success(user)
}
//管理员重置用户密码
func (this *UserController) ResetPassword(writer http.ResponseWriter, request *http.Request) *WebResult {
userUuid := request.FormValue("userUuid")
password := request.FormValue("password")
if userUuid == "" {
return this.Error("用户不能为空")
}
if password == "" {
return this.Error("密码不能为空")
}
currentUser := this.checkUser(writer, request)
if currentUser.Role != USER_ROLE_ADMINISTRATOR {
return this.Error(RESULT_CODE_UNAUTHORIZED)
}
user := this.userDao.CheckByUuid(userUuid)
user.Password = GetBcrypt(password)
user = this.userDao.Save(user)
return this.Success(currentUser)
}

132
rest/user_dao.go Normal file
View File

@ -0,0 +1,132 @@
package rest
import (
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/nu7hatch/gouuid"
"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.ModifyTime = time.Now()
user.LastTime = time.Now()
user.Sort = time.Now().UnixNano() / 1e6
db := this.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 := this.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 {
// Read
var user *User = &User{}
db := this.context.DB.Where(&User{Base: Base{Uuid: uuid}}).First(user)
this.PanicError(db.Error)
return user
}
//按照邮箱查询用户。
func (this *UserDao) FindByEmail(email string) *User {
var user *User = &User{}
db := this.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, sortArray []OrderPair) *Pager {
var wp = &WherePair{}
if username != "" {
wp = wp.And(&WherePair{Query: "username LIKE ?", Args: []interface{}{"%" + username + "%"}})
}
if email != "" {
wp = wp.And(&WherePair{Query: "email LIKE ?", Args: []interface{}{"%" + email + "%"}})
}
if phone != "" {
wp = wp.And(&WherePair{Query: "phone = ?", Args: []interface{}{phone}})
}
count := 0
db := this.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 = this.context.DB.Where(wp.Query, wp.Args...).Offset(page * pageSize).Limit(pageSize).Find(&users)
} else {
db = this.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 := this.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 := this.context.DB.
Model(&User{}).
Where("email = ?", email).
Count(&count)
this.PanicError(db.Error)
return count
}
//保存用户
func (this *UserDao) Save(user *User) *User {
user.ModifyTime = time.Now()
db := this.context.DB.
Save(user)
this.PanicError(db.Error)
return user
}

63
rest/user_model.go Normal file
View File

@ -0,0 +1,63 @@
package rest
import (
"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"`
Username string `json:"username"`
Password string `json:"-"`
Email string `json:"email"`
Phone string `json:"phone"`
Gender string `json:"gender"`
City string `json:"city"`
AvatarUrl string `json:"avatarUrl"`
LastIp string `json:"lastIp"`
LastTime time.Time `json:"lastTime"`
Status string `json:"status"`
}
// set User's table name to be `profiles`
func (User) TableName() string {
return 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
}
}

30
rest/util_encode.go Normal file
View File

@ -0,0 +1,30 @@
package rest
import (
"golang.org/x/crypto/bcrypt"
"fmt"
"crypto/md5"
)
//给密码字符串加密
func GetMd5(raw string) string {
return fmt.Sprintf("%x", md5.Sum([]byte(raw)))
}
func GetBcrypt(raw string) string {
password := []byte(raw)
hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
if err != nil {
panic(err)
}
return string(hashedPassword)
}
func MatchBcrypt(raw string, bcryptStr string) bool {
err := bcrypt.CompareHashAndPassword([]byte(bcryptStr), []byte(raw))
return err == nil
}

50
rest/util_log.go Normal file
View File

@ -0,0 +1,50 @@
package rest
import (
"fmt"
"log"
"os"
"time"
)
func Log(prefix string, content string) {
//日志输出到文件中
filePath := GetLogPath() + "/tank-" + time.Now().Local().Format("2006-01-02") + ".log"
f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Errorf("error opening file: %v", err)
}
defer f.Close()
log.SetOutput(f)
log.SetPrefix(prefix)
log.Println(content)
//如果需要输出到控制台。
if CONFIG.LogToConsole {
fmt.Println(content)
}
}
func LogDebug(content string) {
go Log("[Debug]", content)
}
func LogInfo(content string) {
go Log("[Info]", content)
}
func LogWarning(content string) {
go Log("[Warning]", content)
}
func LogError(content string) {
go Log("[Error]", content)
}
func LogPanic(content interface{}) {
Log("[Panic]", fmt.Sprintf("%v", content))
panic(content)
}

634
rest/util_mime.go Normal file
View File

@ -0,0 +1,634 @@
package rest
import (
"path/filepath"
"strings"
)
//根据文件名字获取后缀名,均是小写。
func GetExtension(filename string) string {
var extension = filepath.Ext(filename)
return strings.ToLower(extension)
}
//根据一个后缀名获取MimeType
func GetMimeType(filename string) string {
extension := GetExtension(filename)
mimeMap := map[string]string{
".323": "text/h323",
".3g2": "video/3gpp2",
".3gp": "video/3gpp",
".3gp2": "video/3gpp2",
".3gpp": "video/3gpp",
".7z": "application/x-7z-compressed",
".aa": "audio/audible",
".AAC": "audio/aac",
".aaf": "application/octet-stream",
".aax": "audio/vnd.audible.aax",
".ac3": "audio/ac3",
".aca": "application/octet-stream",
".accda": "application/msaccess.addin",
".accdb": "application/msaccess",
".accdc": "application/msaccess.cab",
".accde": "application/msaccess",
".accdr": "application/msaccess.runtime",
".accdt": "application/msaccess",
".accdw": "application/msaccess.webapplication",
".accft": "application/msaccess.ftemplate",
".acx": "application/internet-property-stream",
".AddIn": "text/xml",
".ade": "application/msaccess",
".adobebridge": "application/x-bridge-url",
".adp": "application/msaccess",
".ADT": "audio/vnd.dlna.adts",
".ADTS": "audio/aac",
".afm": "application/octet-stream",
".ai": "application/postscript",
".aif": "audio/aiff",
".aifc": "audio/aiff",
".aiff": "audio/aiff",
".air": "application/vnd.adobe.air-application-installer-package+zip",
".amc": "application/mpeg",
".anx": "application/annodex",
".apk": "application/vnd.android.package-archive",
".application": "application/x-ms-application",
".art": "image/x-jg",
".asa": "application/xml",
".asax": "application/xml",
".ascx": "application/xml",
".asd": "application/octet-stream",
".asf": "video/x-ms-asf",
".ashx": "application/xml",
".asi": "application/octet-stream",
".asm": "text/plain",
".asmx": "application/xml",
".aspx": "application/xml",
".asr": "video/x-ms-asf",
".asx": "video/x-ms-asf",
".atom": "application/atom+xml",
".au": "audio/basic",
".avi": "video/x-msvideo",
".axa": "audio/annodex",
".axs": "application/olescript",
".axv": "video/annodex",
".bas": "text/plain",
".bcpio": "application/x-bcpio",
".bin": "application/octet-stream",
".bmp": "image/bmp",
".c": "text/plain",
".cab": "application/octet-stream",
".caf": "audio/x-caf",
".calx": "application/vnd.ms-office.calx",
".cat": "application/vnd.ms-pki.seccat",
".cc": "text/plain",
".cd": "text/plain",
".cdda": "audio/aiff",
".cdf": "application/x-cdf",
".cer": "application/x-x509-ca-cert",
".cfg": "text/plain",
".chm": "application/octet-stream",
".class": "application/x-java-applet",
".clp": "application/x-msclip",
".cmd": "text/plain",
".cmx": "image/x-cmx",
".cnf": "text/plain",
".cod": "image/cis-cod",
".config": "application/xml",
".contact": "text/x-ms-contact",
".coverage": "application/xml",
".cpio": "application/x-cpio",
".cpp": "text/plain",
".crd": "application/x-mscardfile",
".crl": "application/pkix-crl",
".crt": "application/x-x509-ca-cert",
".cs": "text/plain",
".csdproj": "text/plain",
".csh": "application/x-csh",
".csproj": "text/plain",
".css": "text/css",
".csv": "text/csv",
".cur": "application/octet-stream",
".cxx": "text/plain",
".dat": "application/octet-stream",
".datasource": "application/xml",
".dbproj": "text/plain",
".dcr": "application/x-director",
".def": "text/plain",
".deploy": "application/octet-stream",
".der": "application/x-x509-ca-cert",
".dgml": "application/xml",
".dib": "image/bmp",
".dif": "video/x-dv",
".dir": "application/x-director",
".disco": "text/xml",
".divx": "video/divx",
".dll": "application/x-msdownload",
".dll.config": "text/xml",
".dlm": "text/dlm",
".doc": "application/msword",
".docm": "application/vnd.ms-word.document.macroEnabled.12",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".dot": "application/msword",
".dotm": "application/vnd.ms-word.template.macroEnabled.12",
".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
".dsp": "application/octet-stream",
".dsw": "text/plain",
".dtd": "text/xml",
".dtsConfig": "text/xml",
".dv": "video/x-dv",
".dvi": "application/x-dvi",
".dwf": "drawing/x-dwf",
".dwg": "application/acad",
".dwp": "application/octet-stream",
".dxf": "application/x-dxf",
".dxr": "application/x-director",
".eml": "message/rfc822",
".emz": "application/octet-stream",
".eot": "application/vnd.ms-fontobject",
".eps": "application/postscript",
".etl": "application/etl",
".etx": "text/x-setext",
".evy": "application/envoy",
".exe": "application/octet-stream",
".exe.config": "text/xml",
".fdf": "application/vnd.fdf",
".fif": "application/fractals",
".filters": "application/xml",
".fla": "application/octet-stream",
".flac": "audio/flac",
".flr": "x-world/x-vrml",
".flv": "video/x-flv",
".fsscript": "application/fsharp-script",
".fsx": "application/fsharp-script",
".generictest": "application/xml",
".gif": "image/gif",
".gpx": "application/gpx+xml",
".group": "text/x-ms-group",
".gsm": "audio/x-gsm",
".gtar": "application/x-gtar",
".gz": "application/x-gzip",
".h": "text/plain",
".hdf": "application/x-hdf",
".hdml": "text/x-hdml",
".hhc": "application/x-oleobject",
".hhk": "application/octet-stream",
".hhp": "application/octet-stream",
".hlp": "application/winhlp",
".hpp": "text/plain",
".hqx": "application/mac-binhex40",
".hta": "application/hta",
".htc": "text/x-component",
".htm": "text/html",
".html": "text/html",
".htt": "text/webviewhtml",
".hxa": "application/xml",
".hxc": "application/xml",
".hxd": "application/octet-stream",
".hxe": "application/xml",
".hxf": "application/xml",
".hxh": "application/octet-stream",
".hxi": "application/octet-stream",
".hxk": "application/xml",
".hxq": "application/octet-stream",
".hxr": "application/octet-stream",
".hxs": "application/octet-stream",
".hxt": "text/html",
".hxv": "application/xml",
".hxw": "application/octet-stream",
".hxx": "text/plain",
".i": "text/plain",
".ico": "image/x-icon",
".ics": "application/octet-stream",
".idl": "text/plain",
".ief": "image/ief",
".iii": "application/x-iphone",
".inc": "text/plain",
".inf": "application/octet-stream",
".ini": "text/plain",
".inl": "text/plain",
".ins": "application/x-internet-signup",
".ipa": "application/x-itunes-ipa",
".ipg": "application/x-itunes-ipg",
".ipproj": "text/plain",
".ipsw": "application/x-itunes-ipsw",
".iqy": "text/x-ms-iqy",
".isp": "application/x-internet-signup",
".ite": "application/x-itunes-ite",
".itlp": "application/x-itunes-itlp",
".itms": "application/x-itunes-itms",
".itpc": "application/x-itunes-itpc",
".IVF": "video/x-ivf",
".jar": "application/java-archive",
".java": "application/octet-stream",
".jck": "application/liquidmotion",
".jcz": "application/liquidmotion",
".jfif": "image/pjpeg",
".jnlp": "application/x-java-jnlp-file",
".jpb": "application/octet-stream",
".jpe": "image/jpeg",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "application/javascript",
".json": "application/json",
".jsx": "text/jscript",
".jsxbin": "text/plain",
".latex": "application/x-latex",
".library-ms": "application/windows-library+xml",
".lit": "application/x-ms-reader",
".loadtest": "application/xml",
".lpk": "application/octet-stream",
".lsf": "video/x-la-asf",
".lst": "text/plain",
".lsx": "video/x-la-asf",
".lzh": "application/octet-stream",
".m13": "application/x-msmediaview",
".m14": "application/x-msmediaview",
".m1v": "video/mpeg",
".m2t": "video/vnd.dlna.mpeg-tts",
".m2ts": "video/vnd.dlna.mpeg-tts",
".m2v": "video/mpeg",
".m3u": "audio/x-mpegurl",
".m3u8": "audio/x-mpegurl",
".m4a": "audio/m4a",
".m4b": "audio/m4b",
".m4p": "audio/m4p",
".m4r": "audio/x-m4r",
".m4v": "video/x-m4v",
".mac": "image/x-macpaint",
".mak": "text/plain",
".man": "application/x-troff-man",
".manifest": "application/x-ms-manifest",
".map": "text/plain",
".master": "application/xml",
".mbox": "application/mbox",
".mda": "application/msaccess",
".mdb": "application/x-msaccess",
".mde": "application/msaccess",
".mdp": "application/octet-stream",
".me": "application/x-troff-me",
".mfp": "application/x-shockwave-flash",
".mht": "message/rfc822",
".mhtml": "message/rfc822",
".mid": "audio/mid",
".midi": "audio/mid",
".mix": "application/octet-stream",
".mk": "text/plain",
".mmf": "application/x-smaf",
".mno": "text/xml",
".mny": "application/x-msmoney",
".mod": "video/mpeg",
".mov": "video/quicktime",
".movie": "video/x-sgi-movie",
".mp2": "video/mpeg",
".mp2v": "video/mpeg",
".mp3": "audio/mpeg",
".mp4": "video/mp4",
".mp4v": "video/mp4",
".mpa": "video/mpeg",
".mpe": "video/mpeg",
".mpeg": "video/mpeg",
".mpf": "application/vnd.ms-mediapackage",
".mpg": "video/mpeg",
".mpp": "application/vnd.ms-project",
".mpv2": "video/mpeg",
".mqv": "video/quicktime",
".ms": "application/x-troff-ms",
".msg": "application/vnd.ms-outlook",
".msi": "application/octet-stream",
".mso": "application/octet-stream",
".mts": "video/vnd.dlna.mpeg-tts",
".mtx": "application/xml",
".mvb": "application/x-msmediaview",
".mvc": "application/x-miva-compiled",
".mxp": "application/x-mmxp",
".nc": "application/x-netcdf",
".nsc": "video/x-ms-asf",
".nws": "message/rfc822",
".ocx": "application/octet-stream",
".oda": "application/oda",
".odb": "application/vnd.oasis.opendocument.database",
".odc": "application/vnd.oasis.opendocument.chart",
".odf": "application/vnd.oasis.opendocument.formula",
".odg": "application/vnd.oasis.opendocument.graphics",
".odh": "text/plain",
".odi": "application/vnd.oasis.opendocument.image",
".odl": "text/plain",
".odm": "application/vnd.oasis.opendocument.text-master",
".odp": "application/vnd.oasis.opendocument.presentation",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odt": "application/vnd.oasis.opendocument.text",
".oga": "audio/ogg",
".ogg": "audio/ogg",
".ogv": "video/ogg",
".ogx": "application/ogg",
".one": "application/onenote",
".onea": "application/onenote",
".onepkg": "application/onenote",
".onetmp": "application/onenote",
".onetoc": "application/onenote",
".onetoc2": "application/onenote",
".opus": "audio/ogg",
".orderedtest": "application/xml",
".osdx": "application/opensearchdescription+xml",
".otf": "application/font-sfnt",
".otg": "application/vnd.oasis.opendocument.graphics-template",
".oth": "application/vnd.oasis.opendocument.text-web",
".otp": "application/vnd.oasis.opendocument.presentation-template",
".ots": "application/vnd.oasis.opendocument.spreadsheet-template",
".ott": "application/vnd.oasis.opendocument.text-template",
".oxt": "application/vnd.openofficeorg.extension",
".p10": "application/pkcs10",
".p12": "application/x-pkcs12",
".p7b": "application/x-pkcs7-certificates",
".p7c": "application/pkcs7-mime",
".p7m": "application/pkcs7-mime",
".p7r": "application/x-pkcs7-certreqresp",
".p7s": "application/pkcs7-signature",
".pbm": "image/x-portable-bitmap",
".pcast": "application/x-podcast",
".pct": "image/pict",
".pcx": "application/octet-stream",
".pcz": "application/octet-stream",
".pdf": "application/pdf",
".pfb": "application/octet-stream",
".pfm": "application/octet-stream",
".pfx": "application/x-pkcs12",
".pgm": "image/x-portable-graymap",
".pic": "image/pict",
".pict": "image/pict",
".pkgdef": "text/plain",
".pkgundef": "text/plain",
".pko": "application/vnd.ms-pki.pko",
".pls": "audio/scpls",
".pma": "application/x-perfmon",
".pmc": "application/x-perfmon",
".pml": "application/x-perfmon",
".pmr": "application/x-perfmon",
".pmw": "application/x-perfmon",
".png": "image/png",
".pnm": "image/x-portable-anymap",
".pnt": "image/x-macpaint",
".pntg": "image/x-macpaint",
".pnz": "image/png",
".pot": "application/vnd.ms-powerpoint",
".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12",
".potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
".ppa": "application/vnd.ms-powerpoint",
".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12",
".ppm": "image/x-portable-pixmap",
".pps": "application/vnd.ms-powerpoint",
".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
".ppt": "application/vnd.ms-powerpoint",
".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".prf": "application/pics-rules",
".prm": "application/octet-stream",
".prx": "application/octet-stream",
".ps": "application/postscript",
".psc1": "application/PowerShell",
".psd": "application/octet-stream",
".psess": "application/xml",
".psm": "application/octet-stream",
".psp": "application/octet-stream",
".pst": "application/vnd.ms-outlook",
".pub": "application/x-mspublisher",
".pwz": "application/vnd.ms-powerpoint",
".qht": "text/x-html-insertion",
".qhtm": "text/x-html-insertion",
".qt": "video/quicktime",
".qti": "image/x-quicktime",
".qtif": "image/x-quicktime",
".qtl": "application/x-quicktimeplayer",
".qxd": "application/octet-stream",
".ra": "audio/x-pn-realaudio",
".ram": "audio/x-pn-realaudio",
".rar": "application/x-rar-compressed",
".ras": "image/x-cmu-raster",
".rat": "application/rat-file",
".rc": "text/plain",
".rc2": "text/plain",
".rct": "text/plain",
".rdlc": "application/xml",
".reg": "text/plain",
".resx": "application/xml",
".rf": "image/vnd.rn-realflash",
".rgb": "image/x-rgb",
".rgs": "text/plain",
".rm": "application/vnd.rn-realmedia",
".rmi": "audio/mid",
".rmp": "application/vnd.rn-rn_music_package",
".roff": "application/x-troff",
".rpm": "audio/x-pn-realaudio-plugin",
".rqy": "text/x-ms-rqy",
".rtf": "application/rtf",
".rtx": "text/richtext",
".rvt": "application/octet-stream",
".ruleset": "application/xml",
".s": "text/plain",
".safariextz": "application/x-safari-safariextz",
".scd": "application/x-msschedule",
".scr": "text/plain",
".sct": "text/scriptlet",
".sd2": "audio/x-sd2",
".sdp": "application/sdp",
".sea": "application/octet-stream",
".searchConnector-ms": "application/windows-search-connector+xml",
".setpay": "application/set-payment-initiation",
".setreg": "application/set-registration-initiation",
".settings": "application/xml",
".sgimb": "application/x-sgimb",
".sgml": "text/sgml",
".sh": "application/x-sh",
".shar": "application/x-shar",
".shtml": "text/html",
".sit": "application/x-stuffit",
".sitemap": "application/xml",
".skin": "application/xml",
".skp": "application/x-koan",
".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12",
".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
".slk": "application/vnd.ms-excel",
".sln": "text/plain",
".slupkg-ms": "application/x-ms-license",
".smd": "audio/x-smd",
".smi": "application/octet-stream",
".smx": "audio/x-smd",
".smz": "audio/x-smd",
".snd": "audio/basic",
".snippet": "application/xml",
".snp": "application/octet-stream",
".sol": "text/plain",
".sor": "text/plain",
".spc": "application/x-pkcs7-certificates",
".spl": "application/futuresplash",
".spx": "audio/ogg",
".src": "application/x-wais-source",
".srf": "text/plain",
".SSISDeploymentManifest": "text/xml",
".ssm": "application/streamingmedia",
".sst": "application/vnd.ms-pki.certstore",
".stl": "application/vnd.ms-pki.stl",
".sv4cpio": "application/x-sv4cpio",
".sv4crc": "application/x-sv4crc",
".svc": "application/xml",
".svg": "image/svg+xml",
".swf": "application/x-shockwave-flash",
".step": "application/step",
".stp": "application/step",
".t": "application/x-troff",
".tar": "application/x-tar",
".tcl": "application/x-tcl",
".testrunconfig": "application/xml",
".testsettings": "application/xml",
".tex": "application/x-tex",
".texi": "application/x-texinfo",
".texinfo": "application/x-texinfo",
".tgz": "application/x-compressed",
".thmx": "application/vnd.ms-officetheme",
".thn": "application/octet-stream",
".tif": "image/tiff",
".tiff": "image/tiff",
".tlh": "text/plain",
".tli": "text/plain",
".toc": "application/octet-stream",
".tr": "application/x-troff",
".trm": "application/x-msterminal",
".trx": "application/xml",
".ts": "video/vnd.dlna.mpeg-tts",
".tsv": "text/tab-separated-values",
".ttf": "application/font-sfnt",
".tts": "video/vnd.dlna.mpeg-tts",
".txt": "text/plain",
".u32": "application/octet-stream",
".uls": "text/iuls",
".user": "text/plain",
".ustar": "application/x-ustar",
".vb": "text/plain",
".vbdproj": "text/plain",
".vbk": "video/mpeg",
".vbproj": "text/plain",
".vbs": "text/vbscript",
".vcf": "text/x-vcard",
".vcproj": "application/xml",
".vcs": "text/plain",
".vcxproj": "application/xml",
".vddproj": "text/plain",
".vdp": "text/plain",
".vdproj": "text/plain",
".vdx": "application/vnd.ms-visio.viewer",
".vml": "text/xml",
".vscontent": "application/xml",
".vsct": "text/xml",
".vsd": "application/vnd.visio",
".vsi": "application/ms-vsi",
".vsix": "application/vsix",
".vsixlangpack": "text/xml",
".vsixmanifest": "text/xml",
".vsmdi": "application/xml",
".vspscc": "text/plain",
".vss": "application/vnd.visio",
".vsscc": "text/plain",
".vssettings": "text/xml",
".vssscc": "text/plain",
".vst": "application/vnd.visio",
".vstemplate": "text/xml",
".vsto": "application/x-ms-vsto",
".vsw": "application/vnd.visio",
".vsx": "application/vnd.visio",
".vtx": "application/vnd.visio",
".wav": "audio/wav",
".wave": "audio/wav",
".wax": "audio/x-ms-wax",
".wbk": "application/msword",
".wbmp": "image/vnd.wap.wbmp",
".wcm": "application/vnd.ms-works",
".wdb": "application/vnd.ms-works",
".wdp": "image/vnd.ms-photo",
".webarchive": "application/x-safari-webarchive",
".webm": "video/webm",
".webp": "image/webp", /* https"://en.wikipedia.org/wiki/WebP */
".webtest": "application/xml",
".wiq": "application/xml",
".wiz": "application/msword",
".wks": "application/vnd.ms-works",
".WLMP": "application/wlmoviemaker",
".wlpginstall": "application/x-wlpg-detect",
".wlpginstall3": "application/x-wlpg3-detect",
".wm": "video/x-ms-wm",
".wma": "audio/x-ms-wma",
".wmd": "application/x-ms-wmd",
".wmf": "application/x-msmetafile",
".wml": "text/vnd.wap.wml",
".wmlc": "application/vnd.wap.wmlc",
".wmls": "text/vnd.wap.wmlscript",
".wmlsc": "application/vnd.wap.wmlscriptc",
".wmp": "video/x-ms-wmp",
".wmv": "video/x-ms-wmv",
".wmx": "video/x-ms-wmx",
".wmz": "application/x-ms-wmz",
".woff": "application/font-woff",
".wpl": "application/vnd.ms-wpl",
".wps": "application/vnd.ms-works",
".wri": "application/x-mswrite",
".wrl": "x-world/x-vrml",
".wrz": "x-world/x-vrml",
".wsc": "text/scriptlet",
".wsdl": "text/xml",
".wvx": "video/x-ms-wvx",
".x": "application/directx",
".xaf": "x-world/x-vrml",
".xaml": "application/xaml+xml",
".xap": "application/x-silverlight-app",
".xbap": "application/x-ms-xbap",
".xbm": "image/x-xbitmap",
".xdr": "text/plain",
".xht": "application/xhtml+xml",
".xhtml": "application/xhtml+xml",
".xla": "application/vnd.ms-excel",
".xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
".xlc": "application/vnd.ms-excel",
".xld": "application/vnd.ms-excel",
".xlk": "application/vnd.ms-excel",
".xll": "application/vnd.ms-excel",
".xlm": "application/vnd.ms-excel",
".xls": "application/vnd.ms-excel",
".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xlt": "application/vnd.ms-excel",
".xltm": "application/vnd.ms-excel.template.macroEnabled.12",
".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
".xlw": "application/vnd.ms-excel",
".xml": "text/xml",
".xmp": "application/octet-stream",
".xmta": "application/xml",
".xof": "x-world/x-vrml",
".XOML": "text/plain",
".xpm": "image/x-xpixmap",
".xps": "application/vnd.ms-xpsdocument",
".xrm-ms": "text/xml",
".xsc": "application/xml",
".xsd": "text/xml",
".xsf": "text/xml",
".xsl": "text/xml",
".xslt": "text/xml",
".xsn": "application/octet-stream",
".xss": "application/xml",
".xspf": "application/xspf+xml",
".xtp": "application/octet-stream",
".xwd": "image/x-xwindowdump",
".z": "application/x-compress",
".zip": "application/zip"}
if mimeType, ok := mimeMap[extension]; ok {
return mimeType
} else {
return "application/octet-stream"
}
}

26
rest/util_network.go Normal file
View File

@ -0,0 +1,26 @@
package rest
import (
"net/http"
"strings"
)
//根据一个请求获取ip.
func GetIpAddress(r *http.Request) string {
var ipAddress string
ipAddress = r.RemoteAddr
if ipAddress != "" {
ipAddress = strings.Split(ipAddress, ":")[0]
}
for _, h := range []string{"X-Forwarded-For", "X-Real-Ip"} {
for _, ip := range strings.Split(r.Header.Get(h), ",") {
if ip != "" {
ipAddress = ip
}
}
}
return ipAddress
}

138
rest/util_path.go Normal file
View File

@ -0,0 +1,138 @@
package rest
import (
"fmt"
"os"
"time"
"path/filepath"
)
//判断文件或文件夹是否已经存在
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
//获取该应用可执行文件的位置。
//例如C:\Users\lishuang\AppData\Local\Temp
func GetHomePath() string {
ex, err := os.Executable()
if err != nil {
panic(err)
}
exPath := filepath.Dir(ex)
return exPath
}
//获取前端静态资源的位置。如果你在开发模式下可以将这里直接返回tank/build下面的html路径。
//例如C:/Users/lishuang/AppData/Local/Temp/html
func GetHtmlPath() string {
homePath := GetHomePath()
filePath := homePath + "/html"
exists, err := PathExists(filePath)
if err != nil {
panic("判断上传文件是否存在时出错!")
}
if !exists {
err = os.MkdirAll(filePath, 0777)
if err != nil {
panic("创建上传文件夹时出错!")
}
}
return filePath
}
//获取上传文件存放的位置。
//例如C:\Users\lishuang\AppData\Local\Temp/matter
func GetFilePath() string {
homePath := GetHomePath()
filePath := homePath + "/matter"
exists, err := PathExists(filePath)
if err != nil {
panic("判断上传文件是否存在时出错!")
}
if !exists {
err = os.MkdirAll(filePath, 0777)
if err != nil {
panic("创建上传文件夹时出错!")
}
}
return filePath
}
//获取日志存放的位置。
//例如C:\Users\lishuang\AppData\Local\Temp/log
func GetLogPath() string {
homePath := GetHomePath()
filePath := homePath + "/log"
exists, err := PathExists(filePath)
if err != nil {
panic("判断日志文件夹是否存在时出错!")
}
if !exists {
err = os.MkdirAll(filePath, 0666)
if err != nil {
panic("创建日志文件夹时出错!")
}
}
return filePath
}
//获取配置文件存放的位置
//例如C:\Users\lishuang\AppData\Local\Temp/conf
func GetConfPath() string {
homePath := GetHomePath()
filePath := homePath + "/conf"
exists, err := PathExists(filePath)
if err != nil {
panic("判断日志文件夹是否存在时出错!")
}
if !exists {
err = os.MkdirAll(filePath, 0666)
if err != nil {
panic("创建日志文件夹时出错!")
}
}
return filePath
}
//获取某个用户文件应该存放的位置。这个是相对GetFilePath的路径
//例如:/zicla/2006-01-02/1510122428000
func GetUserFilePath(username string) (string, string) {
now := time.Now()
datePath := now.Format("2006-01-02")
//毫秒时间戳
timestamp := now.UnixNano() / 1e6
filePath := GetFilePath()
absolutePath := fmt.Sprintf("%s/%s/%s/%d", filePath, username, datePath, timestamp)
relativePath := fmt.Sprintf("/%s/%s/%d", username, datePath, timestamp)
exists, err := PathExists(absolutePath)
if err != nil {
panic("判断上传文件是否存在时出错!")
}
if !exists {
err = os.MkdirAll(absolutePath, 0777)
if err != nil {
panic("创建上传文件夹时出错!")
}
}
return absolutePath, relativePath
}

115
rest/web_result.go Normal file
View File

@ -0,0 +1,115 @@
package rest
type WebResult struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func (this *WebResult) Error() string {
return this.Msg
}
const (
//正常
RESULT_CODE_OK = 200
//未登录
RESULT_CODE_LOGIN = -400
//没有权限
RESULT_CODE_UNAUTHORIZED = -401
//请求错误
RESULT_CODE_BAD_REQUEST = -402
//没有找到
RESULT_CODE_NOT_FOUND = -404
//登录过期
RESULT_CODE_LOGIN_EXPIRED = -405
//该登录用户不是有效用户
RESULT_CODE_LOGIN_INVALID = -406
//提交的表单验证不通过
RESULT_CODE_FORM_INVALID = -410
//请求太频繁
RESULT_CODE_FREQUENCY = -420
//服务器出错。
RESULT_CODE_SERVER_ERROR = -500
//远程服务不可用
RESULT_CODE_NOT_AVAILABLE = -501
//并发异常
RESULT_CODE_CONCURRENCY = -511
//远程微服务没有找到
RESULT_CODE_SERVICE_NOT_FOUND = -600
//远程微服务连接超时
RESULT_CODE_SERVICE_TIME_OUT = -610
//通用的异常
RESULT_CODE_UTIL_EXCEPTION = -700
)
func ConstWebResult(code int) *WebResult {
wr := &WebResult{}
switch code {
//正常
case RESULT_CODE_OK:
wr.Msg = "成功"
//未登录
case RESULT_CODE_LOGIN:
wr.Msg = "没有登录,禁止访问"
//没有权限
case RESULT_CODE_UNAUTHORIZED:
wr.Msg = "没有权限"
//请求错误
case RESULT_CODE_BAD_REQUEST:
wr.Msg = "请求错误"
//没有找到
case RESULT_CODE_NOT_FOUND:
wr.Msg = "没有找到"
//登录过期
case RESULT_CODE_LOGIN_EXPIRED:
wr.Msg = "登录过期"
//该登录用户不是有效用户
case RESULT_CODE_LOGIN_INVALID:
wr.Msg = "该登录用户不是有效用户"
//提交的表单验证不通过
case RESULT_CODE_FORM_INVALID:
wr.Msg = "提交的表单验证不通过"
//请求太频繁
case RESULT_CODE_FREQUENCY:
wr.Msg = "请求太频繁"
//服务器出错。
case RESULT_CODE_SERVER_ERROR:
wr.Msg = "服务器出错"
//远程服务不可用
case RESULT_CODE_NOT_AVAILABLE:
wr.Msg = "远程服务不可用"
//并发异常
case RESULT_CODE_CONCURRENCY:
wr.Msg = "并发异常"
//远程微服务没有找到
case RESULT_CODE_SERVICE_NOT_FOUND:
wr.Msg = "远程微服务没有找到"
//远程微服务连接超时
case RESULT_CODE_SERVICE_TIME_OUT:
wr.Msg = "远程微服务连接超时"
default:
code = RESULT_CODE_UTIL_EXCEPTION
wr.Msg = "服务器未知错误"
}
wr.Code = code
return wr
}