Refine the directories.

This commit is contained in:
zicla
2019-04-26 02:30:04 +08:00
parent 61b14fe82b
commit b3c52ea50e
40 changed files with 214 additions and 162 deletions

363
rest/tool/download.go Normal file
View File

@ -0,0 +1,363 @@
package tool
import (
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"os"
"strconv"
"strings"
"tank/rest/result"
"time"
)
// HttpRange specifies the byte range to be sent to the client.
type HttpRange struct {
start int64
length int64
}
func (r HttpRange) contentRange(size int64) string {
return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
}
func (r HttpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
return textproto.MIMEHeader{
"Content-Range": {r.contentRange(size)},
"Content-Type": {contentType},
}
}
// CountingWriter counts how many bytes have been written to it.
type CountingWriter int64
func (w *CountingWriter) Write(p []byte) (n int, err error) {
*w += CountingWriter(len(p))
return len(p), nil
}
//检查Last-Modified头。返回true: 请求已经完成了。(言下之意,文件没有修改过) 返回false文件修改过。
func CheckLastModified(w http.ResponseWriter, r *http.Request, modifyTime time.Time) bool {
if modifyTime.IsZero() {
return false
}
// The Date-Modified header truncates sub-second precision, so
// use mtime < t+1s instead of mtime <= t to check for unmodified.
if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modifyTime.Before(t.Add(1*time.Second)) {
h := w.Header()
delete(h, "Content-Type")
delete(h, "Content-Length")
w.WriteHeader(http.StatusNotModified)
return true
}
w.Header().Set("Last-Modified", modifyTime.UTC().Format(http.TimeFormat))
return false
}
// 处理ETag标签
// CheckETag implements If-None-Match and If-Range checks.
//
// The ETag or modtime must have been previously set in the
// ResponseWriter's headers. The modtime is only compared at second
// granularity and may be the zero value to mean unknown.
//
// The return value is the effective request "Range" header to use and
// whether this request is now considered done.
func CheckETag(w http.ResponseWriter, r *http.Request, modtime time.Time) (rangeReq string, done bool) {
etag := w.Header().Get("Etag")
rangeReq = r.Header.Get("Range")
// Invalidate the range request if the entity doesn't match the one
// the client was expecting.
// "If-Range: version" means "ignore the Range: header unless version matches the
// current file."
// We only support ETag versions.
// The caller must have set the ETag on the response already.
if ir := r.Header.Get("If-Range"); ir != "" && ir != etag {
// The If-Range value is typically the ETag value, but it may also be
// the modtime date. See golang.org/issue/8367.
timeMatches := false
if !modtime.IsZero() {
if t, err := http.ParseTime(ir); err == nil && t.Unix() == modtime.Unix() {
timeMatches = true
}
}
if !timeMatches {
rangeReq = ""
}
}
if inm := r.Header.Get("If-None-Match"); inm != "" {
// Must know ETag.
if etag == "" {
return rangeReq, false
}
// (bradfitz): non-GET/HEAD requests require more work:
// sending a different status code on matches, and
// also can't use weak cache validators (those with a "W/
// prefix). But most users of ServeContent will be using
// it on GET or HEAD, so only support those for now.
if r.Method != "GET" && r.Method != "HEAD" {
return rangeReq, false
}
// (bradfitz): deal with comma-separated or multiple-valued
// list of If-None-match values. For now just handle the common
// case of a single item.
if inm == etag || inm == "*" {
h := w.Header()
delete(h, "Content-Type")
delete(h, "Content-Length")
w.WriteHeader(http.StatusNotModified)
return "", true
}
}
return rangeReq, false
}
// ParseRange parses a Range header string as per RFC 2616.
func ParseRange(s string, size int64) ([]HttpRange, error) {
if s == "" {
return nil, nil // header not present
}
const b = "bytes="
if !strings.HasPrefix(s, b) {
return nil, errors.New("invalid range")
}
var ranges []HttpRange
for _, ra := range strings.Split(s[len(b):], ",") {
ra = strings.TrimSpace(ra)
if ra == "" {
continue
}
i := strings.Index(ra, "-")
if i < 0 {
return nil, errors.New("invalid range")
}
start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
var r HttpRange
if start == "" {
// If no start is specified, end specifies the
// range start relative to the end of the file.
i, err := strconv.ParseInt(end, 10, 64)
if err != nil {
return nil, errors.New("invalid range")
}
if i > size {
i = size
}
r.start = size - i
r.length = size - r.start
} else {
i, err := strconv.ParseInt(start, 10, 64)
if err != nil || i >= size || i < 0 {
return nil, errors.New("invalid range")
}
r.start = i
if end == "" {
// If no end is specified, range extends to end of the file.
r.length = size - r.start
} else {
i, err := strconv.ParseInt(end, 10, 64)
if err != nil || r.start > i {
return nil, errors.New("invalid range")
}
if i >= size {
i = size - 1
}
r.length = i - r.start + 1
}
}
ranges = append(ranges, r)
}
return ranges, nil
}
// RangesMIMESize returns the number of bytes it takes to encode the
// provided ranges as a multipart response.
func RangesMIMESize(ranges []HttpRange, contentType string, contentSize int64) (encSize int64) {
var w CountingWriter
mw := multipart.NewWriter(&w)
for _, ra := range ranges {
_, e := mw.CreatePart(ra.mimeHeader(contentType, contentSize))
PanicError(e)
encSize += ra.length
}
e := mw.Close()
PanicError(e)
encSize += int64(w)
return
}
func SumRangesSize(ranges []HttpRange) (size int64) {
for _, ra := range ranges {
size += ra.length
}
return
}
func PanicError(err error) {
if err != nil {
panic(err)
}
}
//文件下载。具有进度功能。
//下载功能参考https://github.com/Masterminds/go-fileserver
func DownloadFile(
writer http.ResponseWriter,
request *http.Request,
filePath string,
filename string,
withContentDisposition bool) {
diskFile, err := os.Open(filePath)
PanicError(err)
defer func() {
e := diskFile.Close()
PanicError(e)
}()
//根据参数添加content-disposition。该Header会让浏览器自动下载而不是预览。
if withContentDisposition {
fileName := url.QueryEscape(filename)
writer.Header().Set("content-disposition", "attachment; filename=\""+fileName+"\"")
}
//显示文件大小。
fileInfo, err := diskFile.Stat()
if err != nil {
panic("无法从磁盘中获取文件信息")
}
modifyTime := fileInfo.ModTime()
if CheckLastModified(writer, request, modifyTime) {
return
}
rangeReq, done := CheckETag(writer, request, modifyTime)
if done {
return
}
code := http.StatusOK
// From net/http/sniff.go
// The algorithm uses at most sniffLen bytes to make its decision.
const sniffLen = 512
// If Content-Type isn't set, use the file's extension to find it, but
// if the Content-Type is unset explicitly, do not sniff the type.
ctypes, haveType := writer.Header()["Content-Type"]
var ctype string
if !haveType {
//使用mimeUtil来获取mime
ctype = GetFallbackMimeType(filename, "")
if ctype == "" {
// read a chunk to decide between utf-8 text and binary
var buf [sniffLen]byte
n, _ := io.ReadFull(diskFile, buf[:])
ctype = http.DetectContentType(buf[:n])
_, err := diskFile.Seek(0, os.SEEK_SET) // rewind to output whole file
if err != nil {
panic("无法准确定位文件")
}
}
writer.Header().Set("Content-Type", ctype)
} else if len(ctypes) > 0 {
ctype = ctypes[0]
}
size := fileInfo.Size()
// handle Content-Range header.
sendSize := size
var sendContent io.Reader = diskFile
if size >= 0 {
ranges, err := ParseRange(rangeReq, size)
if err != nil {
panic(result.CustomWebResult(result.CODE_WRAPPER_RANGE_NOT_SATISFIABLE, "range header出错"))
}
if SumRangesSize(ranges) > size {
// The total number of bytes in all the ranges
// is larger than the size of the file by
// itself, so this is probably an attack, or a
// dumb client. Ignore the range request.
ranges = nil
}
switch {
case len(ranges) == 1:
// RFC 2616, Section 14.16:
// "When an HTTP message includes the content of a single
// range (for example, a response to a request for a
// single range, or to a request for a set of ranges
// that overlap without any holes), this content is
// transmitted with a Content-Range header, and a
// Content-Length header showing the number of bytes
// actually transferred.
// ...
// A response to a request for a single range MUST NOT
// be sent using the multipart/byteranges media type."
ra := ranges[0]
if _, err := diskFile.Seek(ra.start, io.SeekStart); err != nil {
panic(result.CustomWebResult(result.CODE_WRAPPER_RANGE_NOT_SATISFIABLE, "range header出错"))
}
sendSize = ra.length
code = http.StatusPartialContent
writer.Header().Set("Content-Range", ra.contentRange(size))
case len(ranges) > 1:
sendSize = RangesMIMESize(ranges, ctype, size)
code = http.StatusPartialContent
pr, pw := io.Pipe()
mw := multipart.NewWriter(pw)
writer.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
sendContent = pr
defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
go func() {
for _, ra := range ranges {
part, err := mw.CreatePart(ra.mimeHeader(ctype, size))
if err != nil {
pw.CloseWithError(err)
return
}
if _, err := diskFile.Seek(ra.start, io.SeekStart); err != nil {
pw.CloseWithError(err)
return
}
if _, err := io.CopyN(part, diskFile, ra.length); err != nil {
pw.CloseWithError(err)
return
}
}
mw.Close()
pw.Close()
}()
}
writer.Header().Set("Accept-Ranges", "bytes")
if writer.Header().Get("Content-Encoding") == "" {
writer.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
}
}
writer.WriteHeader(code)
if request.Method != "HEAD" {
io.CopyN(writer, sendContent, sendSize)
}
}

160
rest/tool/logger.go Normal file
View File

@ -0,0 +1,160 @@
package tool
import (
"fmt"
"log"
"os"
"runtime"
"sync"
"time"
)
//日志系统必须高保
//全局唯一的日志对象(在main函数中初始化)
var LOGGER = &Logger{}
//在Logger的基础上包装一个全新的Logger.
type Logger struct {
//加锁,在维护日志期间,禁止写入日志。
sync.RWMutex
//继承logger
goLogger *log.Logger
//日志记录所在的文件
file *os.File
//每天凌晨定时整理器
maintainTimer *time.Timer
}
//处理日志的统一方法。
func (this *Logger) log(prefix string, format string, v ...interface{}) {
content := fmt.Sprintf(format+"\r\n", v...)
//控制台中打印日志,记录行号。
_, file, line, ok := runtime.Caller(2)
if !ok {
file = "???"
line = 0
}
var consoleFormat = fmt.Sprintf("%s%s %s:%d %s", prefix, ConvertTimeToTimeString(time.Now()), GetFilenameOfPath(file), line, content)
fmt.Printf(consoleFormat)
this.goLogger.SetPrefix(prefix)
//每一行我们加上换行符
err := this.goLogger.Output(3, content)
if err != nil {
fmt.Printf("occur error while logging %s \r\n", err.Error())
}
}
//处理日志的统一方法。
func (this *Logger) Debug(format string, v ...interface{}) {
this.log("[DEBUG]", format, v...)
}
func (this *Logger) Info(format string, v ...interface{}) {
this.log("[INFO ]", format, v...)
}
func (this *Logger) Warn(format string, v ...interface{}) {
this.log("[WARN ]", format, v...)
}
func (this *Logger) Error(format string, v ...interface{}) {
this.log("[ERROR]", format, v...)
}
func (this *Logger) Panic(format string, v ...interface{}) {
this.log("[PANIC]", format, v...)
panic(fmt.Sprintf(format, v...))
}
func (this *Logger) Init() {
this.openFile()
//日志需要自我备份,自我维护。明天第一秒触发
nextTime := FirstSecondOfDay(Tomorrow())
duration := nextTime.Sub(time.Now())
this.Info("下一次日志维护时间%s 距当前 %ds ", ConvertTimeToDateTimeString(nextTime), duration/time.Second)
this.maintainTimer = time.AfterFunc(duration, func() {
go SafeMethod(this.maintain)
})
}
//将日志写入到今天的日期中(该方法内必须使用异步方法记录日志,否则会引发死锁)
func (this *Logger) maintain() {
this.Info("每日维护日志")
this.Lock()
defer this.Unlock()
//首先关闭文件。
this.closeFile()
//日志归类到昨天
destPath := GetLogPath() + "/tank-" + Yesterday().Local().Format("2006-01-02") + ".log"
//直接重命名文件
err := os.Rename(this.fileName(), destPath)
if err != nil {
this.Error("重命名文件出错", err.Error())
}
//再次打开文件
this.openFile()
//准备好下次维护日志的时间。
now := time.Now()
nextTime := FirstSecondOfDay(Tomorrow())
duration := nextTime.Sub(now)
this.Info("下次维护时间:%s ", ConvertTimeToDateTimeString(nextTime))
this.maintainTimer = time.AfterFunc(duration, func() {
go SafeMethod(this.maintain)
})
}
//日志名称
func (this *Logger) fileName() string {
return GetLogPath() + "/tank.log"
}
//打开日志文件
func (this *Logger) openFile() {
//日志输出到文件中 文件打开后暂时不关闭
fmt.Printf("使用日志文件 %s\r\n", this.fileName())
f, err := os.OpenFile(this.fileName(), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
panic("日志文件无法正常打开: " + err.Error())
}
this.goLogger = log.New(f, "", log.Ltime|log.Lshortfile)
this.file = f
}
//关闭日志文件
func (this *Logger) closeFile() {
if this.file != nil {
err := this.file.Close()
if err != nil {
panic("尝试关闭日志时出错: " + err.Error())
}
}
}
func (this *Logger) Destroy() {
this.closeFile()
if this.maintainTimer != nil {
this.maintainTimer.Stop()
}
}

399
rest/tool/util_cache.go Normal file
View File

@ -0,0 +1,399 @@
package tool
import (
"errors"
"fmt"
"sort"
"sync"
"time"
)
//缓存项
//主要借鉴了cache2go https://github.com/muesli/cache2go
type CacheItem struct {
sync.RWMutex //读写锁
//缓存键
key interface{}
//缓存值
data interface{}
// 缓存项的生命期
duration time.Duration
//创建时间
createTime time.Time
//最后访问时间
accessTime time.Time
//访问次数
count int64
// 在删除缓存项之前调用的回调函数
deleteCallback func(key interface{})
}
//新建一项缓存
func NewCacheItem(key interface{}, duration time.Duration, data interface{}) *CacheItem {
t := time.Now()
return &CacheItem{
key: key,
duration: duration,
createTime: t,
accessTime: t,
count: 0,
deleteCallback: nil,
data: data,
}
}
//手动获取一下,保持该项
func (item *CacheItem) KeepAlive() {
item.Lock()
defer item.Unlock()
item.accessTime = time.Now()
item.count++
}
//返回生命周期
func (item *CacheItem) Duration() time.Duration {
return item.duration
}
//返回访问时间。可能并发,加锁
func (item *CacheItem) AccessTime() time.Time {
item.RLock()
defer item.RUnlock()
return item.accessTime
}
//返回创建时间
func (item *CacheItem) CreateTime() time.Time {
return item.createTime
}
//返回访问时间。可能并发,加锁
func (item *CacheItem) Count() int64 {
item.RLock()
defer item.RUnlock()
return item.count
}
//返回key值
func (item *CacheItem) Key() interface{} {
return item.key
}
//返回数据
func (item *CacheItem) Data() interface{} {
return item.data
}
//设置回调函数
func (item *CacheItem) SetDeleteCallback(f func(interface{})) {
item.Lock()
defer item.Unlock()
item.deleteCallback = f
}
// 统一管理缓存项的表
type CacheTable struct {
sync.RWMutex
//所有缓存项
items map[interface{}]*CacheItem
// 触发缓存清理的定时器
cleanupTimer *time.Timer
// 缓存清理周期
cleanupInterval time.Duration
// 获取一个不存在的缓存项时的回调函数
loadData func(key interface{}, args ...interface{}) *CacheItem
// 向缓存表增加缓存项时的回调函数
addedCallback func(item *CacheItem)
// 从缓存表删除一个缓存项时的回调函数
deleteCallback func(item *CacheItem)
}
// 返回缓存中存储有多少项
func (table *CacheTable) Count() int {
table.RLock()
defer table.RUnlock()
return len(table.items)
}
// 遍历所有项
func (table *CacheTable) Foreach(trans func(key interface{}, item *CacheItem)) {
table.RLock()
defer table.RUnlock()
for k, v := range table.items {
trans(k, v)
}
}
// SetDataLoader配置一个数据加载的回调当尝试去请求一个不存在的key的时候调用
func (table *CacheTable) SetDataLoader(f func(interface{}, ...interface{}) *CacheItem) {
table.Lock()
defer table.Unlock()
table.loadData = f
}
// 添加时的回调函数
func (table *CacheTable) SetAddedCallback(f func(*CacheItem)) {
table.Lock()
defer table.Unlock()
table.addedCallback = f
}
// 删除时的回调函数
func (table *CacheTable) SetDeleteCallback(f func(*CacheItem)) {
table.Lock()
defer table.Unlock()
table.deleteCallback = f
}
//终结检查,被自调整的时间触发
func (table *CacheTable) checkExpire() {
table.Lock()
if table.cleanupTimer != nil {
table.cleanupTimer.Stop()
}
if table.cleanupInterval > 0 {
table.log("Expiration check triggered after %v for table", table.cleanupInterval)
} else {
table.log("Expiration check installed for table")
}
// 为了不抢占锁采用临时的items.
items := table.items
table.Unlock()
//为了定时器更准确我们需要在每一个循环中更新now不确定是否是有效率的。
now := time.Now()
smallestDuration := 0 * time.Second
for key, item := range items {
// 取出我们需要的东西,为了不抢占锁
item.RLock()
duration := item.duration
accessTime := item.accessTime
item.RUnlock()
// 0永久有效
if duration == 0 {
continue
}
if now.Sub(accessTime) >= duration {
//缓存项已经过期
_, e := table.Delete(key)
if e != nil {
table.log("删除缓存项时出错 %v", e.Error())
}
} else {
//查找最靠近结束生命周期的项目
if smallestDuration == 0 || duration-now.Sub(accessTime) < smallestDuration {
smallestDuration = duration - now.Sub(accessTime)
}
}
}
// 为下次清理设置间隔,自触发机制
table.Lock()
table.cleanupInterval = smallestDuration
if smallestDuration > 0 {
table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
go SafeMethod(table.checkExpire)
})
}
table.Unlock()
}
// 添加缓存项
func (table *CacheTable) Add(key interface{}, duration time.Duration, data interface{}) *CacheItem {
item := NewCacheItem(key, duration, data)
// 将缓存项放入表中
table.Lock()
table.log("Adding item with key %v and lifespan of %v to table", key, duration)
table.items[key] = item
// 取出需要的东西,释放锁
expDur := table.cleanupInterval
addedItem := table.addedCallback
table.Unlock()
// 有回调函数便执行回调
if addedItem != nil {
addedItem(item)
}
// 如果我们没有设置任何心跳检查定时器或者找一个即将迫近的项目
if duration > 0 && (expDur == 0 || duration < expDur) {
table.checkExpire()
}
return item
}
// 从缓存中删除项
func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) {
table.RLock()
r, ok := table.items[key]
if !ok {
table.RUnlock()
return nil, errors.New(fmt.Sprintf("没有找到%s对应的记录", key))
}
// 取出要用到的东西,释放锁
deleteCallback := table.deleteCallback
table.RUnlock()
// 调用删除回调函数
if deleteCallback != nil {
deleteCallback(r)
}
r.RLock()
defer r.RUnlock()
if r.deleteCallback != nil {
r.deleteCallback(key)
}
table.Lock()
defer table.Unlock()
table.log("Deleting item with key %v created on %v and hit %v times from table", key, r.createTime, r.count)
delete(table.items, key)
return r, nil
}
//单纯的检查某个键是否存在
func (table *CacheTable) Exists(key interface{}) bool {
table.RLock()
defer table.RUnlock()
_, ok := table.items[key]
return ok
}
//如果存在返回false. 如果不存在,就去添加一个键并且返回true
func (table *CacheTable) NotFoundAdd(key interface{}, lifeSpan time.Duration, data interface{}) bool {
table.Lock()
if _, ok := table.items[key]; ok {
table.Unlock()
return false
}
item := NewCacheItem(key, lifeSpan, data)
table.log("Adding item with key %v and lifespan of %v to table", key, lifeSpan)
table.items[key] = item
// 取出需要的内容,释放锁
expDur := table.cleanupInterval
addedItem := table.addedCallback
table.Unlock()
// 添加回调函数
if addedItem != nil {
addedItem(item)
}
// 触发过期检查
if lifeSpan > 0 && (expDur == 0 || lifeSpan < expDur) {
table.checkExpire()
}
return true
}
//从缓存中返回一个被标记的并保持活性的值。你可以传附件的参数到DataLoader回调函数
func (table *CacheTable) Value(key interface{}, args ...interface{}) (*CacheItem, error) {
table.RLock()
r, ok := table.items[key]
loadData := table.loadData
table.RUnlock()
if ok {
// 更新访问次数和访问时间
r.KeepAlive()
return r, nil
}
// 有加载数据的方式就通过loadData函数去加载进来
if loadData != nil {
item := loadData(key, args...)
if item != nil {
table.Add(key, item.duration, item.data)
return item, nil
}
return nil, errors.New("无法加载到缓存值")
}
//没有找到任何东西返回nil.
return nil, nil
}
// 删除缓存表中的所有项目
func (table *CacheTable) Truncate() {
table.Lock()
defer table.Unlock()
table.log("Truncate table")
table.items = make(map[interface{}]*CacheItem)
table.cleanupInterval = 0
if table.cleanupTimer != nil {
table.cleanupTimer.Stop()
}
}
//辅助table中排序统计的
type CacheItemPair struct {
Key interface{}
AccessCount int64
}
type CacheItemPairList []CacheItemPair
func (p CacheItemPairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p CacheItemPairList) Len() int { return len(p) }
func (p CacheItemPairList) Less(i, j int) bool { return p[i].AccessCount > p[j].AccessCount }
// 返回缓存表中被访问最多的项目
func (table *CacheTable) MostAccessed(count int64) []*CacheItem {
table.RLock()
defer table.RUnlock()
p := make(CacheItemPairList, len(table.items))
i := 0
for k, v := range table.items {
p[i] = CacheItemPair{k, v.count}
i++
}
sort.Sort(p)
var r []*CacheItem
c := int64(0)
for _, v := range p {
if c >= count {
break
}
item, ok := table.items[v.Key]
if ok {
r = append(r, item)
}
c++
}
return r
}
// 打印日志
func (table *CacheTable) log(format string, v ...interface{}) {
//全局日志记录
//LOGGER.Info(format, v...)
}
//新建一个缓存Table
func NewCacheTable() *CacheTable {
return &CacheTable{
items: make(map[interface{}]*CacheItem),
}
}

30
rest/tool/util_encode.go Normal file
View File

@ -0,0 +1,30 @@
package tool
import (
"crypto/md5"
"fmt"
"golang.org/x/crypto/bcrypt"
)
//给密码字符串加密
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
}

View File

@ -0,0 +1,17 @@
package tool
//带有panic恢复的方法
func PanicHandler() {
if err := recover(); err != nil {
//TODO 全局日志记录
//LOGGER.Error("异步任务错误: %v", err)
}
}
//带有panic恢复的方法
func SafeMethod(f func()) {
defer PanicHandler()
//执行函数
f()
}

664
rest/tool/util_mime.go Normal file
View File

@ -0,0 +1,664 @@
package tool
import (
"os"
"path/filepath"
"strings"
)
var allMimeMap = 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",
".bat": "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": "application/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",
".go": "text/plain",
".gpx": "application/gpx+xml",
".group": "text/x-ms-group",
".gsm": "audio/x-gsm",
".gradle": "text/plain",
".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": "text/plain",
".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",
".py": "text/plain",
".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": "text/plain",
".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",
".sql": "text/plain",
".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"}
//根据文件名字获取后缀名,均是小写。
func GetExtension(filename string) string {
var extension = filepath.Ext(filename)
return strings.ToLower(extension)
}
//根据文件名字获取去除后缀的名称
func GetSimpleFileName(filename string) string {
for i := len(filename) - 1; i >= 0 && !os.IsPathSeparator(filename[i]); i-- {
if filename[i] == '.' {
return filename[:i]
}
}
return filename
}
//根据一个后缀名获取MimeType获取不到默认返回 "application/octet-stream"
func GetMimeType(filename string) string {
extension := GetExtension(filename)
if mimeType, ok := allMimeMap[extension]; ok {
return mimeType
} else {
return "application/octet-stream"
}
}
//根据一个后缀名获取MimeType获取不到默认返回fallback.
func GetFallbackMimeType(filename string, fallback string) string {
extension := GetExtension(filename)
if mimeType, ok := allMimeMap[extension]; ok {
return mimeType
} else {
return fallback
}
}

66
rest/tool/util_network.go Normal file
View File

@ -0,0 +1,66 @@
package tool
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
}
//根据一个请求获取host
func GetHostFromRequest(r *http.Request) string {
return r.Host
}
//根据一个请求获取authenticationId
func GetSessionUuidFromRequest(request *http.Request, cookieAuthKey string) string {
//验证用户是否已经登录。
sessionCookie, err := request.Cookie(cookieAuthKey)
var sessionId string
if err != nil {
//从入参中捞取
sessionId = request.FormValue(cookieAuthKey)
} else {
sessionId = sessionCookie.Value
}
return sessionId
}
//允许跨域请求
func 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")
writer.Header().Add("Access-Control-Allow-Headers", "content-type")
}
//禁用缓存
func DisableCache(writer http.ResponseWriter) {
//对于IE浏览器会自动缓存因此设置不缓存Header.
writer.Header().Set("Pragma", "No-cache")
writer.Header().Set("Cache-Control", "no-cache")
writer.Header().Set("Expires", "0")
}

255
rest/tool/util_path.go Normal file
View File

@ -0,0 +1,255 @@
package tool
import (
"fmt"
"go/build"
"io"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"strings"
)
//判断文件或文件夹是否已经存在
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
}
//获取GOPATH路径
func GetGoPath() string {
return build.Default.GOPATH
}
//获取该应用可执行文件的位置。
//例如C:\Users\lishuang\AppData\Local\Temp
func GetHomePath() string {
ex, err := os.Executable()
if err != nil {
panic(err)
}
exPath := filepath.Dir(ex)
//如果exPath中包含了 /private/var/folders 我们认为是在Mac的开发环境中
macDev := strings.HasPrefix(exPath, "/private/var/folders")
if macDev {
exPath = GetGoPath() + "/src/tank/tmp"
}
//如果exPath中包含了 \\AppData\\Local\\Temp 我们认为是在Win的开发环境中
systemUser, err := user.Current()
winDev := strings.HasPrefix(exPath, systemUser.HomeDir+"\\AppData\\Local\\Temp")
if winDev {
exPath = GetGoPath() + "/src/tank/tmp"
}
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
}
//如果文件夹存在就不管,不存在就创建。 例如:/var/www/matter
func MakeDirAll(dirPath string) string {
exists, err := PathExists(dirPath)
if err != nil {
panic("判断文件是否存在时出错!")
}
if !exists {
//TODO:文件权限需要进一步考虑
err = os.MkdirAll(dirPath, 0777)
if err != nil {
panic("创建文件夹时出错!")
}
}
return dirPath
}
//获取到一个Path的文件夹路径eg /var/www/xx.log -> /var/www
func GetDirOfPath(fullPath string) string {
index1 := strings.LastIndex(fullPath, "/")
//可能是windows的环境
index2 := strings.LastIndex(fullPath, "\\")
index := index1
if index2 > index1 {
index = index2
}
return fullPath[:index]
}
//获取到一个Path 中的文件名eg /var/www/xx.log -> xx.log
func GetFilenameOfPath(fullPath string) string {
index1 := strings.LastIndex(fullPath, "/")
//可能是windows的环境
index2 := strings.LastIndex(fullPath, "\\")
index := index1
if index2 > index1 {
index = index2
}
return fullPath[index+1:]
}
//尝试删除空文件夹 true表示删掉了一个空文件夹false表示没有删掉任何东西
func DeleteEmptyDir(dirPath string) bool {
dir, err := ioutil.ReadDir(dirPath)
if err != nil {
LOGGER.Error("尝试读取目录%s时出错 %s", dirPath, err.Error())
panic("尝试读取目录时出错 " + err.Error())
}
if len(dir) == 0 {
//空文件夹
err = os.Remove(dirPath)
if err != nil {
LOGGER.Error("删除磁盘上的文件夹%s出错 %s", dirPath, err.Error())
}
return true
} else {
LOGGER.Info("文件夹不为空,%v", len(dir))
}
return false
}
//递归尝试删除空文件夹,一直空就一直删,直到不空为止
func DeleteEmptyDirRecursive(dirPath string) {
fmt.Printf("递归删除删 %v \n", dirPath)
tmpPath := dirPath
for DeleteEmptyDir(tmpPath) {
dir := GetDirOfPath(tmpPath)
fmt.Printf("尝试删除 %v\n", dir)
tmpPath = dir
}
}
//移除某个文件夹。 例如:/var/www/matter => /var/www
func RemoveDirectory(dirPath string) string {
exists, err := PathExists(dirPath)
if err != nil {
panic("判断文件是否存在时出错!")
}
if exists {
err = os.Remove(dirPath)
if err != nil {
panic("删除文件夹时出错!")
}
}
return dirPath
}
//获取配置文件存放的位置
//例如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, 0777)
if err != nil {
panic("创建日志文件夹时出错!")
}
}
return filePath
}
//获取日志的路径
//例如:默认存放于 home/log
func GetLogPath() string {
homePath := GetHomePath()
filePath := homePath + "/log"
exists, err := PathExists(filePath)
if err != nil {
panic("判断日志文件夹是否存在时出错!")
}
if !exists {
err = os.MkdirAll(filePath, 0777)
if err != nil {
panic("创建日志文件夹时出错!")
}
}
return filePath
}
//复制文件
func CopyFile(srcPath string, destPath string) (nBytes int64) {
srcFileStat, err := os.Stat(srcPath)
if err != nil {
panic(err)
}
if !srcFileStat.Mode().IsRegular() {
panic(fmt.Errorf("%s is not a regular file", srcPath))
}
srcFile, err := os.Open(srcPath)
if err != nil {
panic(err)
}
defer func() {
err = srcFile.Close()
if err != nil {
panic(err)
}
}()
destFile, err := os.Create(destPath)
if err != nil {
panic(err)
}
defer func() {
err = destFile.Close()
if err != nil {
panic(err)
}
}()
nBytes, err = io.Copy(destFile, srcFile)
return nBytes
}

42
rest/tool/util_string.go Normal file
View File

@ -0,0 +1,42 @@
package tool
import (
"fmt"
"strconv"
)
//把一个大小转变成方便读的格式
//human readable file size
func HumanFileSize(bytes int64) string {
var thresh int64 = 1024
if bytes < 0 {
bytes = 0
}
if bytes < thresh {
return fmt.Sprintf("%dB", bytes)
}
var units = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
var u = 0
var tmp = float64(bytes)
var standard = float64(thresh)
for tmp >= standard && u < len(units)-1 {
tmp /= float64(standard)
u++
}
numStr := strconv.FormatFloat(tmp, 'f', 1, 64)
return fmt.Sprintf("%s%s", numStr, units[u])
}
//获取MySQL的URL
func GetMysqlUrl(
mysqlPort int,
mysqlHost string,
mysqlSchema string,
mysqlUsername string,
mysqlPassword string) string {
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", mysqlUsername, mysqlPassword, mysqlHost, mysqlPort, mysqlSchema)
}

63
rest/tool/util_time.go Normal file
View File

@ -0,0 +1,63 @@
package tool
import (
"fmt"
"time"
)
//将一个时间字符串转换成时间对象(yyyy-MM-dd HH:mm:ss)
func ConvertDateTimeStringToTime(timeString string) time.Time {
local, _ := time.LoadLocation("Local")
t, err := time.ParseInLocation("2006-01-02 15:04:05", timeString, local)
if err != nil {
panic(fmt.Sprintf("不能将%s转为时间类型", timeString))
}
return t
}
//将一个时间字符串转换成日期时间对象(yyyy-MM-dd HH:mm:ss)
func ConvertTimeToDateTimeString(time time.Time) string {
return time.Local().Format("2006-01-02 15:04:05")
}
//将一个时间字符串转换成日期时间对象(yyyy-MM-dd HH:mm:ss)
func ConvertTimeToTimeString(time time.Time) string {
return time.Local().Format("15:04:05")
}
//将一个时间字符串转换成日期对象(yyyy-MM-dd)
func ConvertTimeToDateString(time time.Time) string {
return time.Local().Format("2006-01-02")
}
//一天中的最后一秒钟
func LastSecondOfDay(day time.Time) time.Time {
local, _ := time.LoadLocation("Local")
return time.Date(day.Year(), day.Month(), day.Day(), 23, 59, 59, 0, local)
}
//一天中的第一秒钟
func FirstSecondOfDay(day time.Time) time.Time {
local, _ := time.LoadLocation("Local")
return time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, local)
}
//一天中的第一分钟
func FirstMinuteOfDay(day time.Time) time.Time {
local, _ := time.LoadLocation("Local")
return time.Date(day.Year(), day.Month(), day.Day(), 0, 1, 0, 0, local)
}
//明天此刻的时间
func Tomorrow() time.Time {
tomorrow := time.Now()
tomorrow = tomorrow.AddDate(0, 0, 1)
return tomorrow
}
//昨天此刻的时间
func Yesterday() time.Time {
tomorrow := time.Now()
tomorrow = tomorrow.AddDate(0, 0, -1)
return tomorrow
}

View File

@ -0,0 +1,10 @@
package tool
import (
"regexp"
)
func ValidateEmail(email string) bool {
emailRegexp := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
return emailRegexp.MatchString(email)
}