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

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
}