Ver código fonte

jybase添加原common包部分工具类

wanghuidong 4 anos atrás
pai
commit
9db5be4c41
21 arquivos alterados com 2616 adições e 2 exclusões
  1. 709 0
      common/common.go
  2. 79 0
      date/date.go
  3. 58 0
      encrypt/encrypt_test.go
  4. 61 0
      encrypt/encryptarticle.go
  5. 135 0
      encrypt/simpleencrypt.go
  6. 6 0
      endless/doc.go
  7. 574 0
      endless/endless.go
  8. 16 0
      endless/serve.go
  9. 6 2
      go.mod
  10. 11 0
      go.sum
  11. 10 0
      image/image_test.go
  12. 163 0
      image/imageutil.go
  13. 148 0
      image/resize.go
  14. 216 0
      mail/gmail.go
  15. 131 0
      mail/mail.go
  16. 31 0
      mail/mail_test.go
  17. 56 0
      sms/sms.go
  18. 11 0
      sms/sms_test.go
  19. 147 0
      weedcl/client.go
  20. 2 0
      weedcl/weedcl.go
  21. 46 0
      weedcl/weedcl_test.go

+ 709 - 0
common/common.go

@@ -0,0 +1,709 @@
+package common
+
+import (
+	date4 "app.yhyue.com/moapp/jybase/date"
+	"app.yhyue.com/moapp/jybase/encrypt"
+	"crypto/md5"
+	cryptoRand "crypto/rand"
+	"encoding/hex"
+	"encoding/json"
+	"encoding/xml"
+	"fmt"
+	"github.com/dchest/captcha"
+	"io"
+	"log"
+	"math"
+	"math/big"
+	mathRand "math/rand"
+	"net/url"
+	"reflect"
+	"regexp"
+	"runtime"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+const (
+	tmp = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12345678900"
+)
+
+//短地址加密
+func EncodeArticleId(keys ...string) string {
+	kstr := strings.Join(keys, ",")
+	return encrypt.SE.EncodeString(kstr)
+}
+
+//短地址解密
+func DecodeArticleId(id string) []string {
+	return strings.Split(encrypt.SE.DecodeString(id), ",")
+}
+
+//短地址加密,二次加密带校验和
+func EncodeArticleId2ByCheck(keys ...string) string {
+	kstr := strings.Join(keys, ",")
+	kstr = encrypt.SE.EncodeStringByCheck(kstr)
+	return url.QueryEscape("ABC" + encrypt.SE2.EncodeStringByCheck(kstr))
+}
+
+//短地址解密,二次解密带校验和
+func DecodeArticleId2ByCheck(id string) []string {
+	if !strings.Contains(id, "+") { //新加密算法解密
+		id, _ = url.QueryUnescape(id)
+	}
+	if id[:3] == "ABC" { //前三位为ABC是新加密数据
+		kstr := encrypt.SE2.DecodeStringByCheck(id[3:])
+		return strings.Split(encrypt.SE.DecodeStringByCheck(kstr), ",")
+	} else { //历史数据
+		rep := DecodeArticleId(id)
+		oldpushid := "58f87a9561a0721f157bc74d" //剑鱼1.9发版前最后一次推送信息id
+		if rep[0] > oldpushid {
+			return []string{""}
+		} else {
+			return rep
+		}
+	}
+}
+
+func Uuid(length int) string {
+	ret := []string{}
+	r := mathRand.New(mathRand.NewSource(time.Now().UnixNano()))
+	for i := 0; i < length; i++ {
+		index := r.Intn(62)
+		ret = append(ret, tmp[index:index+1])
+	}
+	return strings.Join(ret, "")
+}
+
+//计算字符串和值
+func Sumstring(code string) (sum int) {
+	tmp := []rune(code)
+	for _, v := range tmp {
+		sum = sum + int(v)
+	}
+	return
+}
+
+//获取随机数
+func GetRandom(n int) string {
+	var idChars = []byte("0123456789")
+	b := captcha.RandomDigits(n)
+	for i, c := range b {
+		b[i] = idChars[c]
+	}
+	return string(b)
+}
+
+//获取复杂的随机数
+func GetLetterRandom(length int, flag ...bool) string {
+	var idChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
+	var mod byte = 52
+	if len(flag) > 0 && flag[0] {
+		idChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
+		mod = 26
+	}
+	b := make([]byte, length)
+	maxrb := byte(256 - (256 % int(mod)))
+	i := 0
+EXIT:
+	for {
+		r := make([]byte, length+(length/4))
+		if _, err := io.ReadFull(cryptoRand.Reader, r); err != nil {
+			panic("captcha: error reading random source: " + err.Error())
+		}
+		for _, c := range r {
+			if c > maxrb {
+				continue
+			}
+			b[i] = c % mod
+			i++
+			if i == length {
+				break EXIT
+			}
+		}
+	}
+	for i, c := range b {
+		b[i] = idChars[c]
+	}
+	return string(b)
+}
+
+/*获取复杂的随机数,数字和字母的组合
+ * c > 2 数字的个数和字母的个数随机分配
+ * n 数字的个数
+ * l 字母的个数
+ */
+func GetComplexRandom(c, n, l int) string {
+	if c < 2 && (n < 1 || l < 1) {
+		return "--"
+	}
+	r := mathRand.New(mathRand.NewSource(time.Now().UnixNano()))
+	myCommonMethod := func(flag bool) int {
+		if flag {
+			return r.Intn(c-1) + 1
+		} else {
+			return r.Intn(c)
+		}
+	}
+	if c >= 2 {
+		n = myCommonMethod(true)
+		l = c - n
+	} else {
+		c = l + n
+	}
+	value := GetRandom(n) + GetLetterRandom(l)
+	var array = strings.Split(value, "")
+	for i := 0; i < c/2; i++ {
+		r1 := myCommonMethod(false)
+		r2 := myCommonMethod(false)
+		o := array[r1]
+		array[r1] = array[r2]
+		array[r2] = o
+	}
+	return strings.Join(array, "")
+}
+
+/*隐藏部分账号
+ *返回手机号:150...70765 邮箱:...shenjun@vip.qq.com
+ */
+func EncryCode(value string) string {
+	if len(value) == 0 {
+		return value
+	} else if strings.Contains(value, "@") {
+		start := strings.Index(value, "@") / 2
+		if start == 0 {
+			start++
+		}
+		value = "...." + string(value[start:])
+	} else {
+		value = string(value[0:3]) + "..." + string(value[len(value)-4:])
+	}
+	return value
+}
+
+//生成32位md5字串
+func GetMd5String(s string) string {
+	h := md5.New()
+	h.Write([]byte(s))
+	return hex.EncodeToString(h.Sum(nil))
+}
+
+//obj(string,M)转M,查询用到
+func ObjToMap(obj interface{}) *map[string]interface{} {
+	data := make(map[string]interface{})
+	if s, ok := obj.(string); ok {
+		json.Unmarshal([]byte(strings.Replace(s, "'", "\"", -1)), &data)
+	} else if s1, ok1 := obj.(map[string]interface{}); ok1 {
+		data = s1
+	} else if s1, ok1 := obj.(*map[string]interface{}); ok1 {
+		return s1
+	} else {
+		data = nil
+	}
+	return &data
+}
+
+/*UTC类型时间转字符串
+ *flag==true,日期格式yyyy-mm-dd hh:mm:ss
+ *flag==false,日期格式yyyy-mm-dd
+ */
+func LongToDate(date interface{}, flag bool) string {
+	var int64Date int64
+	if l1, ok1 := date.(float64); ok1 {
+		int64Date = int64(l1)
+	} else if l2, ok2 := date.(int64); ok2 {
+		int64Date = l2
+	} else if l3, ok3 := date.(int); ok3 {
+		int64Date = int64(l3)
+	}
+	t := time.Unix(int64Date, 0)
+	if flag {
+		return t.Format("2006-01-02 15:04:05")
+	} else {
+		return t.Format("2006-01-02")
+	}
+}
+
+func IntAll(num interface{}) int {
+	return IntAllDef(num, 0)
+}
+
+func Int64All(num interface{}) int64 {
+	if i, ok := num.(int64); ok {
+		return int64(i)
+	} else if i0, ok0 := num.(int32); ok0 {
+		return int64(i0)
+	} else if i1, ok1 := num.(float64); ok1 {
+		return int64(i1)
+	} else if i2, ok2 := num.(int); ok2 {
+		return int64(i2)
+	} else if i3, ok3 := num.(float32); ok3 {
+		return int64(i3)
+	} else if i4, ok4 := num.(string); ok4 {
+		i64, _ := strconv.ParseInt(i4, 10, 64)
+		//in, _ := strconv.Atoi(i4)
+		return i64
+	} else if i5, ok5 := num.(int16); ok5 {
+		return int64(i5)
+	} else if i6, ok6 := num.(int8); ok6 {
+		return int64(i6)
+	} else if i7, ok7 := num.(*big.Int); ok7 {
+		in, _ := strconv.ParseInt(fmt.Sprint(i7), 10, 64)
+		return int64(in)
+	} else if i8, ok8 := num.(*big.Float); ok8 {
+		in, _ := strconv.ParseInt(fmt.Sprint(i8), 10, 64)
+		return int64(in)
+	} else {
+		return 0
+	}
+}
+
+func Float64All(num interface{}) float64 {
+	if i, ok := num.(float64); ok {
+		return float64(i)
+	} else if i0, ok0 := num.(int32); ok0 {
+		return float64(i0)
+	} else if i1, ok1 := num.(int64); ok1 {
+		return float64(i1)
+	} else if i2, ok2 := num.(int); ok2 {
+		return float64(i2)
+	} else if i3, ok3 := num.(float32); ok3 {
+		return float64(i3)
+	} else if i4, ok4 := num.(string); ok4 {
+		in, _ := strconv.ParseFloat(i4, 64)
+		return in
+	} else if i5, ok5 := num.(int16); ok5 {
+		return float64(i5)
+	} else if i6, ok6 := num.(int8); ok6 {
+		return float64(i6)
+	} else if i6, ok6 := num.(uint); ok6 {
+		return float64(i6)
+	} else if i6, ok6 := num.(uint8); ok6 {
+		return float64(i6)
+	} else if i6, ok6 := num.(uint16); ok6 {
+		return float64(i6)
+	} else if i6, ok6 := num.(uint32); ok6 {
+		return float64(i6)
+	} else if i6, ok6 := num.(uint64); ok6 {
+		return float64(i6)
+	} else if i7, ok7 := num.(*big.Float); ok7 {
+		in, _ := strconv.ParseFloat(fmt.Sprint(i7), 64)
+		return float64(in)
+	} else if i8, ok8 := num.(*big.Int); ok8 {
+		in, _ := strconv.ParseFloat(fmt.Sprint(i8), 64)
+		return float64(in)
+	} else {
+		return 0
+	}
+}
+
+func IntAllDef(num interface{}, defaultNum int) int {
+	if i, ok := num.(int); ok {
+		return int(i)
+	} else if i0, ok0 := num.(int32); ok0 {
+		return int(i0)
+	} else if i1, ok1 := num.(float64); ok1 {
+		return int(i1)
+	} else if i2, ok2 := num.(int64); ok2 {
+		return int(i2)
+	} else if i3, ok3 := num.(float32); ok3 {
+		return int(i3)
+	} else if i4, ok4 := num.(string); ok4 {
+		in, _ := strconv.Atoi(i4)
+		return int(in)
+	} else if i5, ok5 := num.(int16); ok5 {
+		return int(i5)
+	} else if i6, ok6 := num.(int8); ok6 {
+		return int(i6)
+	} else if i7, ok7 := num.(*big.Int); ok7 {
+		in, _ := strconv.Atoi(fmt.Sprint(i7))
+		return int(in)
+	} else if i8, ok8 := num.(*big.Float); ok8 {
+		in, _ := strconv.Atoi(fmt.Sprint(i8))
+		return int(in)
+	} else {
+		return defaultNum
+	}
+}
+
+func ObjToString(old interface{}) string {
+	if nil == old {
+		return ""
+	} else {
+		r, _ := old.(string)
+		return r
+	}
+}
+
+func ObjToStringDef(old interface{}, defaultstr string) string {
+	if nil == old {
+		return defaultstr
+	} else {
+		r, _ := old.(string)
+		if r == "" {
+			return defaultstr
+		}
+		return r
+	}
+}
+
+//对象数组转成string数组
+func ObjArrToStringArr(old []interface{}) []string {
+	if old != nil {
+		new := make([]string, len(old))
+		for i, v := range old {
+			new[i], _ = v.(string)
+		}
+		return new
+	} else {
+		return nil
+	}
+}
+
+//对象数组转成map数组
+func ObjArrToMapArr(old []interface{}) []map[string]interface{} {
+	if old != nil {
+		new := make([]map[string]interface{}, len(old))
+		for i, v := range old {
+			new[i], _ = v.(map[string]interface{})
+		}
+		return new
+	} else {
+		return nil
+	}
+}
+
+//map数组转成对象数组
+func MapArrToObjArr(old []map[string]interface{}) []interface{} {
+	if old != nil {
+		new := make([]interface{}, len(old))
+		for i, v := range old {
+			new[i] = v
+		}
+		return new
+	} else {
+		return nil
+	}
+}
+
+func SubstrByByte(str string, length int) string {
+	bs := []byte(str)[:length]
+	bl := 0
+	for i := len(bs) - 1; i >= 0; i-- {
+		switch {
+		case bs[i] >= 0 && bs[i] <= 127:
+			return string(bs[:i+1])
+		case bs[i] >= 128 && bs[i] <= 191:
+			bl++
+		case bs[i] >= 192 && bs[i] <= 253:
+			cl := 0
+			switch {
+			case bs[i]&252 == 252:
+				cl = 6
+			case bs[i]&248 == 248:
+				cl = 5
+			case bs[i]&240 == 240:
+				cl = 4
+			case bs[i]&224 == 224:
+				cl = 3
+			default:
+				cl = 2
+			}
+			if bl+1 == cl {
+				return string(bs[:i+cl])
+			}
+			return string(bs[:i])
+		}
+	}
+	return ""
+}
+
+func SubString(str string, begin, length int) (substr string) {
+	// 将字符串的转换成[]rune
+	rs := []rune(str)
+	lth := len(rs)
+	// 简单的越界判断
+	if begin < 0 {
+		begin = 0
+	}
+	if begin >= lth {
+		begin = lth
+	}
+	end := begin + length
+	if end > lth {
+		end = lth
+	}
+
+	// 返回子串
+	return string(rs[begin:end])
+}
+
+//捕获异常
+func Try(fun func(), handler func(interface{})) {
+	defer func() {
+		if err := recover(); err != nil {
+			for skip := 1; ; skip++ {
+				_, file, line, ok := runtime.Caller(skip)
+				if !ok {
+					break
+				}
+				go log.Printf("%v,%v\n", file, line)
+			}
+			handler(err)
+		}
+	}()
+	fun()
+}
+
+//3目运算
+func If(b bool, to, fo interface{}) interface{} {
+	if b {
+		return to
+	} else {
+		return fo
+	}
+}
+
+//HashCode值
+func HashCode(uid string) int {
+	var h uint32 = 0
+	rs := []rune(uid)
+	for i := 0; i < len(rs); i++ {
+		h = 31*h + uint32(rs[i])
+	}
+	return int(h)
+}
+
+//获取离n天的秒差
+func GetDayStartSecond(n int) int64 {
+	now := time.Now()
+	tom := time.Date(now.Year(), now.Month(), now.Day()+n, 0, 0, 0, 0, time.Local)
+	return tom.Unix()
+}
+
+func InterfaceArrTointArr(arr []interface{}) []int {
+	tmp := make([]int, 0)
+	for _, v := range arr {
+		tmp = append(tmp, int(v.(float64)))
+	}
+	return tmp
+}
+func InterfaceArrToint64Arr(arr []interface{}) []int64 {
+	tmp := make([]int64, 0)
+	for _, v := range arr {
+		tmp = append(tmp, int64(v.(float64)))
+	}
+	return tmp
+}
+
+func GetSubDay(t1 int64) int {
+	tt1 := time.Unix(t1, 0)
+	tt2 := time.Now()
+	nt1 := time.Date(tt1.Year(), tt1.Month(), tt1.Day(), 0, 0, 0, 0, time.Local)
+	nt2 := time.Date(tt2.Year(), tt2.Month(), tt2.Day(), 0, 0, 0, 0, time.Local)
+	return int((nt1.Unix() - nt2.Unix()) / 86400)
+}
+func StartWith(value, str string) bool {
+	ok, _ := regexp.MatchString("^"+str, value)
+	return ok
+}
+func EndWith(value, str string) bool {
+	ok, _ := regexp.MatchString(str+"$", value)
+	return ok
+}
+
+//出错拦截
+func Catch() {
+	if r := recover(); r != nil {
+		log.Println(r)
+		for skip := 0; ; skip++ {
+			_, file, line, ok := runtime.Caller(skip)
+			if !ok {
+				break
+			}
+			go log.Printf("%v,%v\n", file, line)
+		}
+	}
+}
+
+func ConvertFileSize(s int) string {
+	size := float64(s)
+	var kb float64 = 1024
+	var mb float64 = kb * 1024
+	var gb float64 = mb * 1024
+	if size >= gb {
+		return fmt.Sprintf("%.1f GB", float64(size/gb))
+	} else if size >= mb {
+		f := float64(size / mb)
+		if f > 100 {
+			return fmt.Sprintf("%.0f MB", f)
+		}
+		return fmt.Sprintf("%.1f MB", f)
+	} else if size >= kb {
+		f := float64(size / kb)
+		if f > 100 {
+			return fmt.Sprintf("%.0f KB", f)
+		}
+		return fmt.Sprintf("%.1f KB", f)
+	}
+	return fmt.Sprintf("%d B", s)
+}
+
+//MD5签名
+func WxSign(format string, param ...interface{}) string {
+	data := fmt.Sprintf(format, param...)
+	h := md5.New()
+	h.Write([]byte(data))
+	sign := strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
+	return sign
+}
+
+//计算时差
+func TimeDiff(date time.Time) string {
+	var date1 = date                        //开始时间
+	var date2 = time.Now()                  //结束时间
+	var date3 = date2.Unix() - date1.Unix() //时间差的毫秒数
+	//计算出相差天数
+	var days = math.Floor(float64(date3 / (24 * 3600)))
+	//计算出小时数
+	var leave1 = date3 % (24 * 3600) //计算天数后剩余的毫秒数
+	var hours = math.Floor(float64(leave1 / (3600)))
+	//计算相差分钟数
+	var leave2 = leave1 % (3600) //计算小时数后剩余的毫秒数
+	var minutes = math.Floor(float64(leave2 / (60)))
+	//计算相差秒数
+	var td = "30秒前"
+	if days > 0 {
+		if days > 10 {
+			if date1.Year() < date2.Year() {
+				td = date4.FormatDate(&date, date4.Date_Short_Layout)
+			} else {
+				td = date4.FormatDate(&date, date4.Date_Small_Layout)
+			}
+		} else {
+			td = fmt.Sprint(days) + "天前"
+		}
+	} else if hours > 0 {
+		td = fmt.Sprint(hours) + "小时前"
+	} else if minutes > 0 {
+		td = fmt.Sprint(minutes) + "分钟前"
+	}
+	return td
+}
+
+func FloatFormat(tmp float64, n int) float64 {
+	fs := fmt.Sprintf("%."+fmt.Sprint(n)+"f", tmp)
+	f, _ := strconv.ParseFloat(fs, 64)
+	return f
+}
+
+//生成微信支付的签名
+func CreateWxSign(afterStr string, obj interface{}, filter ...string) string {
+	filter = append(filter, "sign", "xml")
+	keys := []string{}
+	m := make(map[string]string)
+	t := reflect.TypeOf(obj)
+	v := reflect.ValueOf(obj)
+	k := t.Kind()
+	if t.Kind() == reflect.Ptr {
+		t = t.Elem()
+		k = t.Kind()
+		v = v.Elem()
+	}
+	if k == reflect.Map {
+		for _, key := range v.MapKeys() {
+			keys = append(keys, key.String())
+			m[key.String()] = fmt.Sprint(v.MapIndex(key).Interface())
+		}
+	} else if k == reflect.Struct {
+		for n := 0; n < t.NumField(); n++ {
+			tagName := t.Field(n).Tag.Get("xml")
+			if tagName == "" {
+				tagName = t.Field(n).Tag.Get("json")
+			}
+			if tagName == "" {
+				tagName = t.Field(n).Name
+			}
+			keys = append(keys, tagName)
+			m[tagName] = fmt.Sprint(v.Field(n))
+		}
+	}
+	sort.Strings(keys)
+	vs := []string{}
+L:
+	for _, v := range keys {
+		for _, f := range filter {
+			if f == v {
+				continue L
+			}
+		}
+		if strings.TrimSpace(m[v]) == "" {
+			continue
+		}
+		vs = append(vs, fmt.Sprintf("%s=%s", v, m[v]))
+	}
+	return WxSign(strings.Join(vs, "&") + afterStr)
+}
+
+//简单的xml转map,只有一个层级,没有多层嵌套
+func XmlToMap(input string) map[string]string {
+	var t xml.Token
+	var err error
+	inputReader := strings.NewReader(input)
+	decoder := xml.NewDecoder(inputReader)
+	isStart := false
+	nodeName := ""
+	m := make(map[string]string)
+	for t, err = decoder.Token(); err == nil; t, err = decoder.Token() {
+		switch token := t.(type) {
+		// 处理元素开始(标签)
+		case xml.StartElement:
+			isStart = true
+			nodeName = token.Name.Local
+		// 处理元素结束(标签)
+		case xml.EndElement:
+			isStart = false
+		// 处理字符数据(这里就是元素的文本)
+		case xml.CharData:
+			if isStart {
+				m[nodeName] = string([]byte(token))
+			}
+		default:
+			// ...
+		}
+	}
+	return m
+}
+
+func SimpleCrontab(flag bool, c string, f func()) {
+	array := strings.Split(c, ":")
+	if len(array) != 2 {
+		log.Fatalln("定时任务参数错误!", c)
+	}
+	if flag {
+		go f()
+	}
+	now := time.Now()
+	t := time.Date(now.Year(), now.Month(), now.Day(), IntAll(array[0]), IntAll(array[1]), 0, 0, time.Local)
+	if t.Before(now) {
+		t = t.AddDate(0, 0, 1)
+	}
+	sub := t.Sub(now)
+	log.Println(c, "run after", sub)
+	timer := time.NewTimer(sub)
+	for {
+		select {
+		case <-timer.C:
+			go f()
+			log.Println(c, "run after", 24*time.Hour)
+			timer.Reset(24 * time.Hour)
+		}
+	}
+}
+
+//v保留n为小数,n后的四舍五入
+func RetainDecimal(v float64, n int) float64 {
+	n10 := math.Pow10(n)
+	return math.Trunc((v+0.5/n10)*n10) / n10
+}

+ 79 - 0
date/date.go

@@ -0,0 +1,79 @@
+//日期处理工具类
+package date
+
+import (
+	"fmt"
+	"time"
+)
+
+const (
+	Date_Full_Layout    = "2006-01-02 15:04:05"
+	Date_Short_Layout   = "2006-01-02"
+	Date_Small_Layout   = "01-02"
+	Date_Time_Layout    = "15:04"
+	Date_yyyyMMdd       = "20060102"
+	Date_yyyyMMdd_Point = "2006.01.02"
+)
+
+//当前日期格式化
+func NowFormat(layout string) string {
+	return time.Now().Local().Format(layout)
+}
+
+//日期格式化
+func FormatDate(src *time.Time, layout string) string {
+	return (*src).Local().Format(layout)
+}
+
+//兼容从Java转换过来的数据,java生成的时间戳是按微秒算的,Go中只能按毫秒或纳秒算
+func formatDateWithInt64(src *int64, layout string) string {
+	var tmp int64
+	if *src > 0 {
+		if len(fmt.Sprint(*src)) >= 12 {
+			tmp = (*src) / 1000
+		} else {
+			tmp = (*src)
+		}
+	} else {
+		if len(fmt.Sprint(*src)) >= 13 {
+			tmp = (*src) / 1000
+		} else {
+			tmp = (*src)
+		}
+	}
+	date := time.Unix(tmp, 0)
+	return FormatDate(&date, layout)
+}
+
+func FormatDateByInt64(src *int64, layout string) string {
+	var tmp int64
+	if *src > 0 {
+		if len(fmt.Sprint(*src)) >= 12 {
+			tmp = (*src) / 1000
+		} else {
+			tmp = (*src)
+		}
+	} else {
+		if len(fmt.Sprint(*src)) >= 13 {
+			tmp = (*src) / 1000
+		} else {
+			tmp = (*src)
+		}
+	}
+	date := time.Unix(tmp, 0)
+	return FormatDate(&date, layout)
+}
+
+//支持源端多种格式
+func FormatDateWithObj(src *interface{}, layout string) string {
+	if tmp, ok := (*src).(int64); ok {
+		return formatDateWithInt64(&tmp, layout)
+	} else if tmp2, ok2 := (*src).(float64); ok2 {
+		tmpe := int64(tmp2)
+		return formatDateWithInt64(&tmpe, layout)
+	} else if tmp3, ok3 := (*src).(*time.Time); ok3 {
+		return tmp3.Format(layout)
+	} else {
+		return ""
+	}
+}

+ 58 - 0
encrypt/encrypt_test.go

@@ -0,0 +1,58 @@
+package encrypt
+
+import (
+	"fmt"
+	"log"
+	"net/url"
+	"testing"
+)
+
+//加密解密测试
+func TestEncrypt(t *testing.T) {
+	se := SimpleEncrypt{Key: "topnet2015topnet2015"}
+	mw := se.EncodeString("58db529161a0721f1553446a,123444")
+	log.Println("密文", mw)
+	txt := se.DecodeString("QVcUDFBGCwEHBBVfR1xUEgMFBAZAW0YP")
+	log.Println("解密后:", txt)
+	mw2 := se.Encode2Hex("hello world")
+	log.Println("16进制密文", mw2)
+	log.Println("解密16进制", se.Decode4Hex(mw2))
+}
+
+//加密解密测试
+func TestEncryptByCheck(t *testing.T) {
+	s := CommonEncodeArticle("content", "59df2a2440d2d9bbe802f71e")
+	ss := CommonEncodeArticle("content", s)
+	log.Println("s=", s == ss)
+	s = CommonDecodeArticle("content", s)[0]
+	log.Println("s=", s)
+	s2 := CommonDecodeArticle("content", "ABCY2ZrcC4%2FMyk7En91ZGI8DCc4QTJjR2hxKDgoPCEge35iYj8sDxlESAry") //[0]
+	log.Println("s=", s2)
+	se := SimpleEncrypt{Key: "topnet2015topnet2015"}
+	se2 := SimpleEncrypt{Key: "2017jianyu"}
+	mw1 := se.EncodeStringByCheck("59df2a2440d2d9bbe802f71e")
+	mw2 := se2.EncodeStringByCheck(mw1)
+	esc := url.QueryEscape(mw2)
+	log.Println("密文", mw1, mw2, esc)
+	uesc, _ := url.QueryUnescape(esc)
+	log.Println(mw2 == uesc)
+	txt2 := se2.DecodeStringByCheck(mw2)
+	txt1 := se.DecodeStringByCheck(txt2)
+	log.Println("解密后:", txt2, txt1)
+
+	txt := DecodeArticleId2ByCheck("ABC" + mw2)
+	//txt = DecodeArticleId2ByCheck("ABCY2ZoYikvAjg4NGN2c2UoDScoGj10XFJ+KzgCPS4wd3xwTVJUChM=")
+
+	log.Println("解密后:", txt)
+}
+func Test_fmt(t *testing.T) {
+	log.Println(GetMd5String("Top@123"))
+	s1 := "AA%sBB%s"
+	log.Println(fmt.Sprintf(s1, "#", "S"))
+	log.Println(CommonDecodeArticle("content", "ABCY2ZoYzxYNDYvEmN2c2UoDScoGj10XFJ+KS8zIzoNZ31wGAFUCbU="))
+}
+
+func Test_qfwCerNo(t *testing.T) {
+	se := SimpleEncrypt{Key: "entrelation"}
+	log.Println(se.DecodeString("UV9EQVdbUE1eVl9VXExKVV1R"))
+}

+ 61 - 0
encrypt/encryptarticle.go

@@ -0,0 +1,61 @@
+package encrypt
+
+import (
+	"app.yhyue.com/moapp/jybase/common"
+	"net/url"
+	"strings"
+)
+
+//正文
+var SE = &SimpleEncrypt{Key: "topnet2015topnet2015"}
+var SE2 = &SimpleEncrypt{Key: "2017jianyu"}
+
+//百度
+var BSE = &SimpleEncrypt{Key: "HNtopnet2017jy"}
+var BSE2 = &SimpleEncrypt{Key: "TOP2017jianyu"}
+
+//订阅推送邮件
+var ESE = &SimpleEncrypt{Key: "PEjy2018topnet"}
+var ESE2 = &SimpleEncrypt{Key: "TopnetJy2018Pe"}
+
+//通用加密
+func CommonEncodeArticle(stype string, keys ...string) (id string) {
+	switch stype {
+	case "content":
+		id = BEncodeArticleId2ByCheck("A", SE, SE2, keys...)
+	case "bdprivate":
+		id = BEncodeArticleId2ByCheck("B", BSE, BSE2, keys...)
+	case "mailprivate":
+		id = BEncodeArticleId2ByCheck("C", ESE, ESE2, keys...)
+	}
+	return
+}
+
+//通用解密
+func CommonDecodeArticle(stype string, id string) (res []string) {
+	switch stype {
+	case "content":
+		res = BDecodeArticleId2ByCheck(id, SE, SE2)
+	case "bdprivate":
+		res = BDecodeArticleId2ByCheck(id, BSE, BSE2)
+	case "mailprivate":
+		res = BDecodeArticleId2ByCheck(id, ESE, ESE2)
+	}
+	return
+}
+
+//短地址加密,二次加密带校验和
+func BEncodeArticleId2ByCheck(h string, s1, s2 *SimpleEncrypt, keys ...string) string {
+	kstr := strings.Join(keys, ",")
+	kstr = s1.EncodeStringByCheck(kstr)
+	return url.QueryEscape(h + common.GetLetterRandom(2) + s2.EncodeStringByCheck(kstr))
+}
+
+//短地址解密,二次解密带校验和
+func BDecodeArticleId2ByCheck(id string, s1, s2 *SimpleEncrypt) []string {
+	if !strings.Contains(id, "+") { //新加密算法解密
+		id, _ = url.QueryUnescape(id)
+	}
+	kstr := s2.DecodeStringByCheck(id[3:])
+	return strings.Split(s1.DecodeStringByCheck(kstr), ",")
+}

+ 135 - 0
encrypt/simpleencrypt.go

@@ -0,0 +1,135 @@
+package encrypt
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/hex"
+)
+
+/**
+加解密
+	数据结构
+	密文+sha32校验
+*/
+//
+type SimpleEncrypt struct {
+	Key string //加解密用到的key(加密key索引)+
+}
+
+//计算检验和
+func (s *SimpleEncrypt) calaCheckCode(src []byte) []byte {
+	check := 0
+	for i := 0; i < len(src); i++ {
+		check += int(src[i])
+	}
+	return []byte{byte((check >> 8) & 0xff), byte(check & 0xff)}
+}
+
+//验证数据有效性
+func (s *SimpleEncrypt) verify(src []byte) bool {
+	v := s.calaCheckCode(src[:len(src)-2])
+	return bytes.Equal(v, src[len(src)-2:])
+}
+
+//加密String
+func (s *SimpleEncrypt) EncodeString(str string) string {
+	data := []byte(str)
+	s.doEncode(data)
+	return base64.StdEncoding.EncodeToString(data)
+}
+
+//加密String,ByCheck
+func (s *SimpleEncrypt) EncodeStringByCheck(str string) string {
+	data := []byte(str)
+	s.doEncode(data)
+	v := s.calaCheckCode(data)
+	data = append(data, v...)
+	return base64.StdEncoding.EncodeToString(data)
+}
+
+//
+func (s *SimpleEncrypt) Encode2Hex(str string) string {
+	data := []byte(str)
+	s.doEncode(data)
+	return hex.EncodeToString(data)
+}
+func (s *SimpleEncrypt) Encode2HexByCheck(str string) string {
+	data := []byte(str)
+	s.doEncode(data)
+	v := s.calaCheckCode(data)
+	data = append(data, v...)
+	return hex.EncodeToString(data)
+}
+
+//解密String
+func (s *SimpleEncrypt) DecodeString(str string) string {
+	data, _ := base64.StdEncoding.DecodeString(str)
+	s.doEncode(data)
+	return string(data)
+}
+
+//解密String,ByCheck
+func (s *SimpleEncrypt) DecodeStringByCheck(str string) string {
+	data, _ := base64.StdEncoding.DecodeString(str)
+	if len(data) < 2 || !s.verify(data) {
+		return ""
+	}
+	data = data[:len(data)-2]
+	s.doEncode(data)
+	return string(data)
+}
+
+//
+func (s *SimpleEncrypt) Decode4Hex(str string) string {
+	data, _ := hex.DecodeString(str)
+	s.doEncode(data)
+	return string(data)
+}
+func (s *SimpleEncrypt) Decode4HexByCheck(str string) string {
+	data, _ := hex.DecodeString(str)
+	if len(data) < 2 || !s.verify(data) {
+		return ""
+	}
+	data = data[:len(data)-2]
+	s.doEncode(data)
+	return string(data)
+}
+
+//加密
+func (s *SimpleEncrypt) Encode(data []byte) {
+	s.doEncode(data)
+
+}
+
+func (s *SimpleEncrypt) EncodeByCheck(data []byte) {
+	s.doEncode(data)
+	v := s.calaCheckCode(data)
+	data = append(data, v...)
+}
+
+//解密
+func (s *SimpleEncrypt) Decode(data []byte) {
+	s.doEncode(data)
+}
+
+//解密
+func (s *SimpleEncrypt) DecodeByCheck(data []byte) {
+	if len(data) < 2 || !s.verify(data) {
+		data = []byte{}
+		return
+	}
+	s.doEncode(data)
+}
+
+func (s *SimpleEncrypt) doEncode(bs []byte) {
+	tmp := []byte(s.Key)
+THEFOR:
+	for i := 0; i < len(bs); {
+		for j := 0; j < len(tmp); j, i = j+1, i+1 {
+			if i >= len(bs) {
+				break THEFOR
+			}
+			bs[i] = bs[i] ^ tmp[j]
+		}
+	}
+}

+ 6 - 0
endless/doc.go

@@ -0,0 +1,6 @@
+/*
+endless provides a drop in  replacement for the `net/http` stl functions `ListenAndServe` and `ListenAndServeTLS` to achieve zero downtime restarts and code upgrades.
+
+The code is based on the excellent post [Graceful Restart in Golang](http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/) by [Grisha Trubetskoy](https://github.com/grisha). I took his code as a start. So a lot of credit to Grisha!
+*/
+package endless

+ 574 - 0
endless/endless.go

@@ -0,0 +1,574 @@
+// +build linux
+
+package endless
+
+import (
+	"crypto/tls"
+	"errors"
+	"flag"
+	"fmt"
+	"log"
+	"net"
+	"net/http"
+	"os"
+	"os/exec"
+	"os/signal"
+	"runtime"
+	"strings"
+	"sync"
+	"syscall"
+	"time"
+
+	// "github.com/fvbock/uds-go/introspect"
+)
+
+const (
+	PRE_SIGNAL = iota
+	POST_SIGNAL
+
+	STATE_INIT
+	STATE_RUNNING
+	STATE_SHUTTING_DOWN
+	STATE_TERMINATE
+)
+
+var (
+	runningServerReg     sync.RWMutex
+	runningServers       map[string]*endlessServer
+	runningServersOrder  []string
+	socketPtrOffsetMap   map[string]uint
+	runningServersForked bool
+
+	DefaultReadTimeOut    time.Duration
+	DefaultWriteTimeOut   time.Duration
+	DefaultMaxHeaderBytes int
+	DefaultHammerTime     time.Duration
+
+	isChild     bool
+	socketOrder string
+
+	hookableSignals []os.Signal
+)
+
+func init() {
+	flag.BoolVar(&isChild, "continue", false, "listen on open fd (after forking)")
+	flag.StringVar(&socketOrder, "socketorder", "", "previous initialization order - used when more than one listener was started")
+
+	runningServerReg = sync.RWMutex{}
+	runningServers = make(map[string]*endlessServer)
+	runningServersOrder = []string{}
+	socketPtrOffsetMap = make(map[string]uint)
+
+	DefaultMaxHeaderBytes = 0 // use http.DefaultMaxHeaderBytes - which currently is 1 << 20 (1MB)
+
+	// after a restart the parent will finish ongoing requests before
+	// shutting down. set to a negative value to disable
+	DefaultHammerTime = 60 * time.Second
+
+	hookableSignals = []os.Signal{
+		syscall.SIGHUP,
+		syscall.SIGUSR1,
+		syscall.SIGUSR2,
+		syscall.SIGINT,
+		syscall.SIGTERM,
+		syscall.SIGTSTP,
+	}
+}
+
+type endlessServer struct {
+	http.Server
+	EndlessListener  net.Listener
+	SignalHooks      map[int]map[os.Signal][]func()
+	tlsInnerListener *endlessListener
+	wg               sync.WaitGroup
+	sigChan          chan os.Signal
+	isChild          bool
+	state            uint8
+	lock             *sync.RWMutex
+}
+
+/*
+NewServer returns an intialized endlessServer Object. Calling Serve on it will
+actually "start" the server.
+*/
+func NewServer(addr string, handler http.Handler, fn func()) (srv *endlessServer) {
+	runningServerReg.Lock()
+	defer runningServerReg.Unlock()
+	if !flag.Parsed() {
+		flag.Parse()
+	}
+	if len(socketOrder) > 0 {
+		for i, addr := range strings.Split(socketOrder, ",") {
+			socketPtrOffsetMap[addr] = uint(i)
+		}
+	} else {
+		socketPtrOffsetMap[addr] = uint(len(runningServersOrder))
+	}
+
+	srv = &endlessServer{
+		wg:      sync.WaitGroup{},
+		sigChan: make(chan os.Signal),
+		isChild: isChild,
+		SignalHooks: map[int]map[os.Signal][]func(){
+			PRE_SIGNAL: map[os.Signal][]func(){
+				syscall.SIGHUP: []func(){
+					fn,
+				},
+				syscall.SIGUSR1: []func(){},
+				syscall.SIGUSR2: []func(){},
+				syscall.SIGINT:  []func(){},
+				syscall.SIGTERM: []func(){},
+				syscall.SIGTSTP: []func(){},
+			},
+			POST_SIGNAL: map[os.Signal][]func(){
+				syscall.SIGHUP:  []func(){},
+				syscall.SIGUSR1: []func(){},
+				syscall.SIGUSR2: []func(){},
+				syscall.SIGINT:  []func(){},
+				syscall.SIGTERM: []func(){},
+				syscall.SIGTSTP: []func(){},
+			},
+		},
+		state: STATE_INIT,
+		lock:  &sync.RWMutex{},
+	}
+
+	srv.Server.Addr = addr
+	srv.Server.ReadTimeout = DefaultReadTimeOut
+	srv.Server.WriteTimeout = DefaultWriteTimeOut
+	srv.Server.MaxHeaderBytes = DefaultMaxHeaderBytes
+	srv.Server.Handler = handler
+
+	runningServersOrder = append(runningServersOrder, addr)
+	runningServers[addr] = srv
+
+	return
+}
+
+func NetListen(addr string, handler http.Handler, fn func()) (net.Listener, error) {
+	server := NewServer(addr, handler, fn)
+	return server.ListenAndServe(false)
+}
+
+/*
+ListenAndServe listens on the TCP network address addr and then calls Serve
+with handler to handle requests on incoming connections. Handler is typically
+nil, in which case the DefaultServeMux is used.
+*/
+func ListenAndServe(addr string, handler http.Handler, fn func()) error {
+	server := NewServer(addr, handler, fn)
+	_, err := server.ListenAndServe(true)
+	return err
+}
+
+/*
+ListenAndServeTLS acts identically to ListenAndServe, except that it expects
+HTTPS connections. Additionally, files containing a certificate and matching
+private key for the server must be provided. If the certificate is signed by a
+certificate authority, the certFile should be the concatenation of the server's
+certificate followed by the CA's certificate.
+*/
+func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler, destoryfn func()) error {
+	server := NewServer(addr, handler, destoryfn)
+	return server.ListenAndServeTLS(certFile, keyFile, true)
+}
+
+func (srv *endlessServer) getState() uint8 {
+	srv.lock.RLock()
+	defer srv.lock.RUnlock()
+
+	return srv.state
+}
+
+func (srv *endlessServer) setState(st uint8) {
+	srv.lock.Lock()
+	defer srv.lock.Unlock()
+
+	srv.state = st
+}
+
+/*
+Serve accepts incoming HTTP connections on the listener l, creating a new
+service goroutine for each. The service goroutines read requests and then call
+handler to reply to them. Handler is typically nil, in which case the
+DefaultServeMux is used.
+
+In addition to the stl Serve behaviour each connection is added to a
+sync.Waitgroup so that all outstanding connections can be served before shutting
+down the server.
+*/
+func (srv *endlessServer) Serve() (err error) {
+	defer log.Println(syscall.Getpid(), "Serve() returning...")
+	srv.setState(STATE_RUNNING)
+	err = srv.Server.Serve(srv.EndlessListener)
+	log.Println(syscall.Getpid(), "Waiting for connections to finish...")
+	srv.wg.Wait()
+	srv.setState(STATE_TERMINATE)
+	return
+}
+
+/*
+ListenAndServe listens on the TCP network address srv.Addr and then calls Serve
+to handle requests on incoming connections. If srv.Addr is blank, ":http" is
+used.
+*/
+func (srv *endlessServer) ListenAndServe(isServer bool) (l net.Listener, err error) {
+	addr := srv.Addr
+	if addr == "" {
+		addr = ":http"
+	}
+
+	go srv.handleSignals(isServer)
+
+	l, err = srv.getListener(addr)
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	srv.EndlessListener = newEndlessListener(l, srv)
+
+	if srv.isChild {
+		syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
+	}
+
+	log.Println(syscall.Getpid(), srv.Addr)
+	if isServer {
+		return l, srv.Serve()
+	} else {
+		return l, err
+	}
+}
+
+/*
+ListenAndServeTLS listens on the TCP network address srv.Addr and then calls
+Serve to handle requests on incoming TLS connections.
+
+Filenames containing a certificate and matching private key for the server must
+be provided. If the certificate is signed by a certificate authority, the
+certFile should be the concatenation of the server's certificate followed by the
+CA's certificate.
+
+If srv.Addr is blank, ":https" is used.
+*/
+func (srv *endlessServer) ListenAndServeTLS(certFile, keyFile string, isServer bool) (err error) {
+	addr := srv.Addr
+	if addr == "" {
+		addr = ":https"
+	}
+
+	config := &tls.Config{}
+	if srv.TLSConfig != nil {
+		*config = *srv.TLSConfig
+	}
+	if config.NextProtos == nil {
+		config.NextProtos = []string{"http/1.1"}
+	}
+
+	config.Certificates = make([]tls.Certificate, 1)
+	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
+	if err != nil {
+		return
+	}
+
+	go srv.handleSignals(isServer)
+
+	l, err := srv.getListener(addr)
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	srv.tlsInnerListener = newEndlessListener(l, srv)
+	srv.EndlessListener = tls.NewListener(srv.tlsInnerListener, config)
+
+	if srv.isChild {
+		syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
+	}
+
+	log.Println(syscall.Getpid(), srv.Addr)
+	return srv.Serve()
+}
+
+/*
+getListener either opens a new socket to listen on, or takes the acceptor socket
+it got passed when restarted.
+*/
+func (srv *endlessServer) getListener(laddr string) (l net.Listener, err error) {
+	if srv.isChild {
+		var ptrOffset uint = 0
+		runningServerReg.RLock()
+		defer runningServerReg.RUnlock()
+		if len(socketPtrOffsetMap) > 0 {
+			ptrOffset = socketPtrOffsetMap[laddr]
+			// log.Println("laddr", laddr, "ptr offset", socketPtrOffsetMap[laddr])
+		}
+
+		f := os.NewFile(uintptr(3+ptrOffset), "")
+		l, err = net.FileListener(f)
+		if err != nil {
+			err = fmt.Errorf("net.FileListener error: %v", err)
+			return
+		}
+	} else {
+		l, err = net.Listen("tcp", laddr)
+		if err != nil {
+			err = fmt.Errorf("net.Listen error: %v", err)
+			return
+		}
+	}
+	return
+}
+
+/*
+handleSignals listens for os Signals and calls any hooked in function that the
+user had registered with the signal.
+*/
+func (srv *endlessServer) handleSignals(isServer bool) {
+	var sig os.Signal
+
+	signal.Notify(
+		srv.sigChan,
+		hookableSignals...,
+	)
+
+	pid := syscall.Getpid()
+	for {
+		sig = <-srv.sigChan
+		srv.signalHooks(PRE_SIGNAL, sig)
+		switch sig {
+		case syscall.SIGHUP:
+			log.Println(pid, "Received SIGHUP. forking.")
+			err := srv.fork()
+			if err != nil {
+				log.Println("Fork err:", err)
+			}
+		case syscall.SIGUSR1:
+			log.Println(pid, "Received SIGUSR1.")
+		case syscall.SIGUSR2:
+			log.Println(pid, "Received SIGUSR2.")
+			srv.hammerTime(0 * time.Second)
+		case syscall.SIGINT:
+			log.Println(pid, "Received SIGINT.")
+			srv.shutdown(isServer)
+		case syscall.SIGTERM:
+			log.Println(pid, "Received SIGTERM.")
+			srv.shutdown(isServer)
+		case syscall.SIGTSTP:
+			log.Println(pid, "Received SIGTSTP.")
+		default:
+			log.Printf("Received %v: nothing i care about...\n", sig)
+		}
+		srv.signalHooks(POST_SIGNAL, sig)
+	}
+}
+
+func (srv *endlessServer) signalHooks(ppFlag int, sig os.Signal) {
+	if _, notSet := srv.SignalHooks[ppFlag][sig]; !notSet {
+		return
+	}
+	for _, f := range srv.SignalHooks[ppFlag][sig] {
+		f()
+	}
+	return
+}
+
+/*
+shutdown closes the listener so that no new connections are accepted. it also
+starts a goroutine that will hammer (stop all running requests) the server
+after DefaultHammerTime.
+*/
+func (srv *endlessServer) shutdown(isServer bool) {
+	if isServer && srv.getState() != STATE_RUNNING {
+		return
+	}
+
+	srv.setState(STATE_SHUTTING_DOWN)
+	if DefaultHammerTime >= 0 {
+		go srv.hammerTime(DefaultHammerTime)
+	}
+	// disable keep-alives on existing connections
+	srv.SetKeepAlivesEnabled(false)
+	err := srv.EndlessListener.Close()
+	if err != nil {
+		log.Println(syscall.Getpid(), "Listener.Close() error:", err)
+	} else {
+		log.Println(syscall.Getpid(), srv.EndlessListener.Addr(), "Listener closed.")
+	}
+}
+
+/*
+hammerTime forces the server to shutdown in a given timeout - whether it
+finished outstanding requests or not. if Read/WriteTimeout are not set or the
+max header size is very big a connection could hang...
+
+srv.Serve() will not return until all connections are served. this will
+unblock the srv.wg.Wait() in Serve() thus causing ListenAndServe(TLS) to
+return.
+*/
+func (srv *endlessServer) hammerTime(d time.Duration) {
+	defer func() {
+		// we are calling srv.wg.Done() until it panics which means we called
+		// Done() when the counter was already at 0 and we're done.
+		// (and thus Serve() will return and the parent will exit)
+		if r := recover(); r != nil {
+			log.Println("WaitGroup at 0", r)
+		}
+	}()
+	if srv.getState() != STATE_SHUTTING_DOWN {
+		return
+	}
+	time.Sleep(d)
+	log.Println("[STOP - Hammer Time] Forcefully shutting down parent")
+	for {
+		if srv.getState() == STATE_TERMINATE {
+			break
+		}
+		srv.wg.Done()
+		runtime.Gosched()
+	}
+}
+
+func (srv *endlessServer) fork() (err error) {
+	runningServerReg.Lock()
+	defer runningServerReg.Unlock()
+
+	// only one server isntance should fork!
+	if runningServersForked {
+		return errors.New("Another process already forked. Ignoring this one.")
+	}
+
+	runningServersForked = true
+
+	var files = make([]*os.File, len(runningServers))
+	var orderArgs = make([]string, len(runningServers))
+	// get the accessor socket fds for _all_ server instances
+	for _, srvPtr := range runningServers {
+		// introspect.PrintTypeDump(srvPtr.EndlessListener)
+		switch srvPtr.EndlessListener.(type) {
+		case *endlessListener:
+			// normal listener
+			files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.EndlessListener.(*endlessListener).File()
+		default:
+			// tls listener
+			files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.tlsInnerListener.File()
+		}
+		orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
+	}
+
+	// log.Println(files)
+	path := os.Args[0]
+	var args []string
+	if len(os.Args) > 1 {
+		for _, arg := range os.Args[1:] {
+			if arg == "-continue" {
+				break
+			}
+			args = append(args, arg)
+		}
+	}
+	args = append(args, "-continue")
+	if len(runningServers) > 1 {
+		args = append(args, fmt.Sprintf(`-socketorder=%s`, strings.Join(orderArgs, ",")))
+		// log.Println(args)
+	}
+
+	cmd := exec.Command(path, args...)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	cmd.ExtraFiles = files
+	// cmd.SysProcAttr = &syscall.SysProcAttr{
+	// 	Setsid:  true,
+	// 	Setctty: true,
+	// 	Ctty:    ,
+	// }
+
+	err = cmd.Start()
+	if err != nil {
+		log.Fatalf("Restart: Failed to launch, error: %v", err)
+	}
+
+	return
+}
+
+type endlessListener struct {
+	net.Listener
+	stopped bool
+	server  *endlessServer
+}
+
+func (el *endlessListener) Accept() (c net.Conn, err error) {
+	tc, err := el.Listener.(*net.TCPListener).AcceptTCP()
+	if err != nil {
+		return
+	}
+
+	tc.SetKeepAlive(true)                  // see http.tcpKeepAliveListener
+	tc.SetKeepAlivePeriod(3 * time.Minute) // see http.tcpKeepAliveListener
+
+	c = endlessConn{
+		Conn:   tc,
+		server: el.server,
+	}
+
+	el.server.wg.Add(1)
+	return
+}
+
+func newEndlessListener(l net.Listener, srv *endlessServer) (el *endlessListener) {
+	el = &endlessListener{
+		Listener: l,
+		server:   srv,
+	}
+
+	return
+}
+
+func (el *endlessListener) Close() error {
+	if el.stopped {
+		return syscall.EINVAL
+	}
+
+	el.stopped = true
+	return el.Listener.Close()
+}
+
+func (el *endlessListener) File() *os.File {
+	// returns a dup(2) - FD_CLOEXEC flag *not* set
+	tl := el.Listener.(*net.TCPListener)
+	fl, _ := tl.File()
+	return fl
+}
+
+type endlessConn struct {
+	net.Conn
+	server *endlessServer
+}
+
+func (w endlessConn) Close() error {
+	err := w.Conn.Close()
+	if err == nil {
+		w.server.wg.Done()
+	}
+	return err
+}
+
+/*
+RegisterSignalHook registers a function to be run PRE_SIGNAL or POST_SIGNAL for
+a given signal. PRE or POST in this case means before or after the signal
+related code endless itself runs
+*/
+func (srv *endlessServer) RegisterSignalHook(prePost int, sig os.Signal, f func()) (err error) {
+	if prePost != PRE_SIGNAL && prePost != POST_SIGNAL {
+		err = fmt.Errorf("Cannot use %v for prePost arg. Must be endless.PRE_SIGNAL or endless.POST_SIGNAL.")
+		return
+	}
+	for _, s := range hookableSignals {
+		if s == sig {
+			srv.SignalHooks[prePost][sig] = append(srv.SignalHooks[prePost][sig], f)
+			return
+		}
+	}
+	err = fmt.Errorf("Signal %v is not supported.")
+	return
+}

+ 16 - 0
endless/serve.go

@@ -0,0 +1,16 @@
+// +build !linux
+
+package endless
+
+import (
+	"net"
+	"net/http"
+)
+
+func ListenAndServe(addr string, handler http.Handler, fn func()) error {
+	return http.ListenAndServe(addr, handler)
+}
+
+func NetListen(address string, handler http.Handler, fn func()) (net.Listener, error) {
+	return net.Listen("tcp", address)
+}

+ 6 - 2
go.mod

@@ -3,12 +3,16 @@ module app.yhyue.com/moapp/jybase
 go 1.13
 
 require (
+	github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
 	github.com/garyburd/redigo v1.6.2
+	github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
 	github.com/mailru/easyjson v0.7.7 // indirect
-	github.com/olivere/elastic v6.2.35+incompatible
+	github.com/olivere/elastic v6.2.35+incompatible // indirect
 	github.com/olivere/elastic/v7 v7.0.22
 	go.mongodb.org/mongo-driver v1.5.0
-	gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
+	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
+	gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
 	gorm.io/driver/mysql v1.0.3
 	gorm.io/gorm v1.20.8
 )

+ 11 - 0
go.sum

@@ -2,15 +2,20 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
 github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
+github.com/aws/aws-sdk-go v1.35.20 h1:Hs7x9Czh+MMPnZLQqHhsuZKeNFA3Vuf7pdy2r5QlVb0=
 github.com/aws/aws-sdk-go v1.35.20/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
+github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
 github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
 github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM=
 github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
+github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
+github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
 github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
@@ -174,10 +179,14 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/olivere/elastic.v1 v1.0.1 h1:ZoJwTKCI0jJdVptoGB0QEFt/4bDUs6A5Pjrmn/Zb+5g=
@@ -187,10 +196,12 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/mysql v1.0.3 h1:+JKBYPfn1tygR1/of/Fh2T8iwuVwzt+PEJmKaXzMQXg=
 gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
 gorm.io/driver/mysql v1.0.4 h1:TATTzt+kR+IV0+h3iUB3dHUe8omCvQ0rOkmfCsUBohk=
 gorm.io/driver/mysql v1.0.4/go.mod h1:MEgp8tk2n60cSBCq5iTcPDw3ns8Gs+zOva9EUhkknTs=
 gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
+gorm.io/gorm v1.20.8 h1:iToaOdZgjNvlc44NFkxfLa3U9q63qwaxt0FdNCiwOMs=
 gorm.io/gorm v1.20.8/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gorm.io/gorm v1.21.3 h1:qDFi55ZOsjZTwk5eN+uhAmHi8GysJ/qCTichM/yO7ME=

+ 10 - 0
image/image_test.go

@@ -0,0 +1,10 @@
+package image
+
+import (
+	"log"
+	"testing"
+)
+
+func Test_image(t *testing.T) {
+	log.Println(MakeResize("D://a.jpg", -1, 400, 100, 1))
+}

+ 163 - 0
image/imageutil.go

@@ -0,0 +1,163 @@
+package image
+
+import (
+	"image"
+	"image/jpeg"
+	"image/png"
+	"log"
+	"os"
+	"runtime"
+	"strings"
+)
+
+func LoadImg(filepath string) (img image.Image, err error) {
+	file, err := os.Open(filepath)
+	if err != nil {
+		return
+	}
+	defer file.Close()
+	img, _, err = image.Decode(file)
+	return
+}
+
+func MakeResize(path string, newX, newY, quality int, t int) (newName string, err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			log.Println("[E]", r)
+			for skip := 1; ; skip++ {
+				_, file, line, ok := runtime.Caller(skip)
+				if !ok {
+					break
+				}
+				go log.Printf("%v,%v\n", file, line)
+			}
+		}
+	}()
+	img, err := LoadImg(path)
+	if nil == err {
+		path := strings.Replace(strings.Replace(path, "\\\\", "\\", -1), "\\", "/", -1)
+		strs := strings.Split(path, "/")
+		oname := strs[len(strs)-1]
+		onames := strings.Split(oname, ".")
+		// 产生缩略图,等比例缩放
+		b := img.Bounds()
+		//判断大小
+		var newBounds image.Rectangle
+		if newX < 0 && newY < 0 {
+			newBounds = b
+		} else if newX > 0 && newY < 0 {
+			newYY := (float32(newX) / float32(b.Dx())) * float32(b.Dy())
+			newBounds = image.Rect(0, 0, newX, int(newYY))
+		} else if newY > 0 && newX < 0 {
+			newXX := (float32(newY) / float32(b.Dy())) * float32(b.Dx())
+			newBounds = image.Rect(0, 0, int(newXX), newY)
+		} else {
+			newBounds = image.Rect(0, 0, newX, newY)
+		}
+		var max int
+		if newBounds.Dx() >= newBounds.Dy() {
+			max = newBounds.Dx()
+		} else {
+			max = newBounds.Dy()
+		}
+		// If it's gigantic, it's more efficient to downsample first
+		// and then resize; resizing will smooth out the roughness.
+		var i1 *image.RGBA
+		if b.Dx() > 4*max || b.Dy() > 4*max {
+			w, h := 2*max, 2*max
+			if b.Dx() > b.Dy() {
+				h = b.Dy() * h / b.Dx()
+			} else {
+				w = b.Dx() * w / b.Dy()
+			}
+			i1 = Resample(img, b, w, h)
+		} else {
+			// "Resample" to same size, just to convert to RGBA.
+			i1 = Resample(img, b, b.Dx(), b.Dy())
+		}
+		b = i1.Bounds()
+
+		// Encode to PNG.
+		dx, dy := newBounds.Dx(), newBounds.Dy()
+		log.Println(b.Dx(), b.Dy(), dx, dy, max)
+		if b.Dx() > b.Dy() {
+			dy = b.Dy() * dx / b.Dx()
+		} else {
+			dx = b.Dx() * dy / b.Dy()
+		}
+		i128 := ResizeRGBA(i1, i1.Bounds(), dx, dy)
+		var s = "/s_"
+		if t == 1 {
+			s = "/"
+		}
+		newF, err1 := os.Create(strings.Join(strs[:(len(strs)-1)], "/") + s + oname)
+		defer newF.Close()
+		var err error
+		switch strings.ToLower(onames[1]) {
+		case "jpg":
+			err = jpeg.Encode(newF, i128, &jpeg.Options{quality})
+		case "jpeg":
+			err = jpeg.Encode(newF, i128, &jpeg.Options{quality})
+		case "png":
+			err = png.Encode(newF, i128)
+		}
+		if err != nil {
+			log.Println("生成缩略图出错", err)
+		}
+		return strings.Replace(s, "/", "", -1) + oname, err1
+	}
+
+	return "", err
+}
+
+//是否达到压缩条件
+func IsCompress(path string) string {
+	img, err := LoadImg(path)
+	log.Println(err)
+	if nil == err {
+		x := img.Bounds().Dx()
+		y := img.Bounds().Dy()
+		if x > 1920 {
+			if x > y {
+				return "x"
+			} else {
+				return "y"
+			}
+		} else {
+			if y > 1080 {
+				return "y"
+			}
+		}
+
+	}
+	return ""
+}
+
+func MakeShareImage(path string) string {
+	path = "./web/staticres" + path
+	image, err := LoadImg(path)
+	if err != nil {
+		return "f"
+	}
+	x := image.Bounds().Dx()
+	y := image.Bounds().Dy()
+	if x < 300 && y < 300 {
+		if x > y {
+			x = -1
+			y = 350
+		} else {
+			y = -1
+			x = 350
+		}
+	} else if x < 300 && y > 300 {
+		y = -1
+		x = 350
+	} else if x > 300 && y < 300 {
+		x = -1
+		y = 350
+	}
+	log.Println("x:", x, "y:", y)
+	rs, _ := MakeResize(path, x, y, 100, 0)
+	log.Println("rs:", rs)
+	return rs
+}

+ 148 - 0
image/resize.go

@@ -0,0 +1,148 @@
+package image
+
+import (
+	"image"
+	"image/color"
+)
+
+// average convert the sums to averages and returns the result.
+func average(sum []uint64, w, h int, n uint64) *image.RGBA {
+	ret := image.NewRGBA(image.Rect(0, 0, w, h))
+	for y := 0; y < h; y++ {
+		for x := 0; x < w; x++ {
+			index := 4 * (y*w + x)
+			pix := ret.Pix[y*ret.Stride+x*4:]
+			pix[0] = uint8(sum[index+0] / n)
+			pix[1] = uint8(sum[index+1] / n)
+			pix[2] = uint8(sum[index+2] / n)
+			pix[3] = uint8(sum[index+3] / n)
+		}
+	}
+	return ret
+}
+
+// ResizeRGBA returns a scaled copy of the RGBA image slice r of m.
+// The returned image has width w and height h.
+func ResizeRGBA(m *image.RGBA, r image.Rectangle, w, h int) *image.RGBA {
+	ww, hh := uint64(w), uint64(h)
+	dx, dy := uint64(r.Dx()), uint64(r.Dy())
+	// See comment in Resize.
+	n, sum := dx*dy, make([]uint64, 4*w*h)
+	for y := r.Min.Y; y < r.Max.Y; y++ {
+		pix := m.Pix[(y-r.Min.Y)*m.Stride:]
+		for x := r.Min.X; x < r.Max.X; x++ {
+			// Get the source pixel.
+			p := pix[(x-r.Min.X)*4:]
+			r64 := uint64(p[0])
+			g64 := uint64(p[1])
+			b64 := uint64(p[2])
+			a64 := uint64(p[3])
+			// Spread the source pixel over 1 or more destination rows.
+			py := uint64(y) * hh
+			for remy := hh; remy > 0; {
+				qy := dy - (py % dy)
+				if qy > remy {
+					qy = remy
+				}
+				// Spread the source pixel over 1 or more destination columns.
+				px := uint64(x) * ww
+				index := 4 * ((py/dy)*ww + (px / dx))
+				for remx := ww; remx > 0; {
+					qx := dx - (px % dx)
+					if qx > remx {
+						qx = remx
+					}
+					qxy := qx * qy
+					sum[index+0] += r64 * qxy
+					sum[index+1] += g64 * qxy
+					sum[index+2] += b64 * qxy
+					sum[index+3] += a64 * qxy
+					index += 4
+					px += qx
+					remx -= qx
+				}
+				py += qy
+				remy -= qy
+			}
+		}
+	}
+	return average(sum, w, h, n)
+}
+
+// ResizeNRGBA returns a scaled copy of the RGBA image slice r of m.
+// The returned image has width w and height h.
+func ResizeNRGBA(m *image.NRGBA, r image.Rectangle, w, h int) *image.RGBA {
+	ww, hh := uint64(w), uint64(h)
+	dx, dy := uint64(r.Dx()), uint64(r.Dy())
+	// See comment in Resize.
+	n, sum := dx*dy, make([]uint64, 4*w*h)
+	for y := r.Min.Y; y < r.Max.Y; y++ {
+		pix := m.Pix[(y-r.Min.Y)*m.Stride:]
+		for x := r.Min.X; x < r.Max.X; x++ {
+			// Get the source pixel.
+			p := pix[(x-r.Min.X)*4:]
+			r64 := uint64(p[0])
+			g64 := uint64(p[1])
+			b64 := uint64(p[2])
+			a64 := uint64(p[3])
+			r64 = (r64 * a64) / 255
+			g64 = (g64 * a64) / 255
+			b64 = (b64 * a64) / 255
+			// Spread the source pixel over 1 or more destination rows.
+			py := uint64(y) * hh
+			for remy := hh; remy > 0; {
+				qy := dy - (py % dy)
+				if qy > remy {
+					qy = remy
+				}
+				// Spread the source pixel over 1 or more destination columns.
+				px := uint64(x) * ww
+				index := 4 * ((py/dy)*ww + (px / dx))
+				for remx := ww; remx > 0; {
+					qx := dx - (px % dx)
+					if qx > remx {
+						qx = remx
+					}
+					qxy := qx * qy
+					sum[index+0] += r64 * qxy
+					sum[index+1] += g64 * qxy
+					sum[index+2] += b64 * qxy
+					sum[index+3] += a64 * qxy
+					index += 4
+					px += qx
+					remx -= qx
+				}
+				py += qy
+				remy -= qy
+			}
+		}
+	}
+	return average(sum, w, h, n)
+}
+
+// Resample returns a resampled copy of the image slice r of m.
+// The returned image has width w and height h.
+func Resample(m image.Image, r image.Rectangle, w, h int) *image.RGBA {
+	if w < 0 || h < 0 {
+		return nil
+	}
+	if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 {
+		return image.NewRGBA(image.Rect(0, 0, w, h))
+	}
+	curw, curh := r.Dx(), r.Dy()
+	img := image.NewRGBA(image.Rect(0, 0, w, h))
+	for y := 0; y < h; y++ {
+		for x := 0; x < w; x++ {
+			// Get a source pixel.
+			subx := x * curw / w
+			suby := y * curh / h
+			r32, g32, b32, a32 := m.At(subx, suby).RGBA()
+			r := uint8(r32 >> 8)
+			g := uint8(g32 >> 8)
+			b := uint8(b32 >> 8)
+			a := uint8(a32 >> 8)
+			img.SetRGBA(x, y, color.RGBA{r, g, b, a})
+		}
+	}
+	return img
+}

+ 216 - 0
mail/gmail.go

@@ -0,0 +1,216 @@
+package mail
+
+import (
+	"app.yhyue.com/moapp/jybase/common"
+	"crypto/tls"
+	"log"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/go-gomail/gomail"
+)
+
+type GmailAuth struct {
+	SmtpHost string //邮箱服务器
+	SmtpPort int    //邮箱端口
+	User     string //用户
+	Pwd      string //密码
+	PoolChan chan *gomail.Dialer
+	PoolSize int
+	ReTry    int
+}
+
+var locker = &sync.Mutex{}
+
+func getDialer(flag bool, auth *GmailAuth, to string) *gomail.Dialer {
+	if auth.PoolChan == nil {
+		locker.Lock()
+		defer locker.Unlock()
+		if auth.PoolChan == nil {
+			if auth.PoolSize == 0 {
+				auth.PoolSize = 1
+			}
+			auth.PoolChan = make(chan *gomail.Dialer, auth.PoolSize)
+			for i := 0; i < auth.PoolSize; i++ {
+				dialer := gomail.NewPlainDialer(auth.SmtpHost, auth.SmtpPort, auth.User, auth.Pwd) // 发送邮件服务器、端口、发件人账号、发件人密码
+				dialer.TLSConfig = &tls.Config{ServerName: auth.SmtpHost, InsecureSkipVerify: true}
+				auth.PoolChan <- dialer
+			}
+		}
+	}
+	if flag {
+		log.Println(auth.User, to, "发送邮件getDialer:get new gomail Dialer")
+		dialer := gomail.NewPlainDialer(auth.SmtpHost, auth.SmtpPort, auth.User, auth.Pwd) // 发送邮件服务器、端口、发件人账号、发件人密码
+		dialer.TLSConfig = &tls.Config{ServerName: auth.SmtpHost, InsecureSkipVerify: true}
+		auth.PoolChan <- dialer
+	}
+	return <-auth.PoolChan
+}
+
+//发送普通
+func GSendMail(from, to, cc, bcc, subject, body, fname, rename string, auth *GmailAuth) bool {
+	m := gomail.NewMessage()
+	m.SetAddressHeader("From", auth.User, from) // 发件人
+	m.SetHeader("To",
+		m.FormatAddress(to, "收件人")) // 收件人
+	if cc != "" {
+		m.SetHeader("Cc", m.FormatAddress(cc, "收件人")) //抄送
+	}
+	if bcc != "" {
+		m.SetHeader("Bcc", m.FormatAddress(bcc, "收件人")) // 暗送
+	}
+	m.SetHeader("Subject", subject) // 主题
+	m.SetBody("text/html", body)    // 正文
+	if fname != "" {
+		h := map[string][]string{"Content-Type": {"text/plain; charset=UTF-8"}}
+		m.Attach(fname, gomail.Rename(rename), gomail.SetHeader(h)) //添加附件
+		//m.Attach(fname) //添加附件
+	}
+	reTry := auth.ReTry
+	if reTry == 0 {
+		reTry = 3
+	}
+	return gSend(reTry, auth, m, to)
+}
+
+//如果附件是byte,用这个
+func GSendMail_B(from, to, cc, bcc, subject, body, fname string, fb []byte, auth *GmailAuth) bool {
+	m := gomail.NewMessage()
+	m.SetAddressHeader("From", auth.User, from) // 发件人
+	m.SetHeader("To",
+		m.FormatAddress(to, "收件人")) // 收件人
+	if cc != "" {
+		m.SetHeader("Cc", m.FormatAddress(cc, "收件人")) //抄送
+	}
+	if bcc != "" {
+		m.SetHeader("Bcc", m.FormatAddress(bcc, "收件人")) // 暗送
+	}
+	m.SetHeader("Subject", subject) // 主题
+	m.SetBody("text/html", body)    // 正文
+	if fname != "" {
+		h := map[string][]string{"Content-Type": {"text/plain; charset=UTF-8"}}
+		m.Attach_new(fb, gomail.Rename(fname), gomail.SetHeader(h)) //添加附件
+		//m.Attach(fname) //添加附件
+	}
+	reTry := auth.ReTry
+	if reTry == 0 {
+		reTry = 1
+	}
+	return gSend(reTry, auth, m, to)
+}
+
+//如果附件是byte,用这个,这个是为企业级服务修改的
+//20191206 @ren  to用|分隔的是抄送用,分隔的是并列
+func GSendMail_Bq(from, to, cc, bcc, subject, body, fname string, fb []byte, auth *GmailAuth) bool {
+	m := gomail.NewMessage()
+	m.SetAddressHeader("From", auth.User, from) // 发件人
+	tos := strings.Split(to, "|")
+	if len(tos) > 0 {
+		tos1 := strings.Split(tos[0], ",")
+		m.SetHeader("To", tos1...) // 收件人
+	}
+	if len(tos) > 1 {
+		tos2 := strings.Split(tos[1], ",")
+		if cc != "" {
+			tos2 = append(tos2, cc)
+		}
+		m.SetHeader("Cc", tos2...) // 收件人
+	} else {
+		if cc != "" {
+			m.SetHeader("Cc", m.FormatAddress(cc, "收件人")) //抄送
+		}
+	}
+	if len(tos) > 2 {
+		tos3 := strings.Split(tos[2], ",")
+		if bcc != "" {
+			tos3 = append(tos3, cc)
+		}
+		m.SetHeader("Bcc", tos3...) // 收件人
+	} else {
+		if bcc != "" {
+			m.SetHeader("Bcc", m.FormatAddress(bcc, "收件人")) // 暗送
+		}
+	}
+	m.SetHeader("Subject", subject) // 主题
+	m.SetBody("text/html", body)    // 正文
+	if fname != "" {
+		h := map[string][]string{"Content-Type": {"text/plain; charset=UTF-8"}}
+		m.Attach_new(fb, gomail.Rename(fname), gomail.SetHeader(h)) //添加附件
+		//m.Attach(fname) //添加附件
+	}
+	reTry := auth.ReTry
+	if reTry == 0 {
+		reTry = 1
+	}
+	return gSend(reTry, auth, m, to)
+}
+
+//发送普通这个是为企业级服务修改的
+//20191206 @ren  to用|分隔的是抄送,分隔的是并列
+func GSendMail_q(from, to, cc, bcc, subject, body, fname, rename string, auth *GmailAuth) bool {
+	m := gomail.NewMessage()
+	m.SetAddressHeader("From", auth.User, from) // 发件人
+	tos := strings.Split(to, "|")
+	if len(tos) > 0 {
+		tos1 := strings.Split(tos[0], ",")
+		m.SetHeader("To", tos1...) // 收件人
+	}
+	if len(tos) > 1 {
+		tos2 := strings.Split(tos[1], ",")
+		if cc != "" {
+			tos2 = append(tos2, cc)
+		}
+		m.SetHeader("Cc", tos2...) // 收件人
+	} else {
+		if cc != "" {
+			m.SetHeader("Cc", m.FormatAddress(cc, "收件人")) //抄送
+		}
+	}
+	if len(tos) > 2 {
+		tos3 := strings.Split(tos[2], ",")
+		if bcc != "" {
+			tos3 = append(tos3, cc)
+		}
+		m.SetHeader("Bcc", tos3...) // 收件人
+	} else {
+		if bcc != "" {
+			m.SetHeader("Bcc", m.FormatAddress(bcc, "收件人")) // 暗送
+		}
+	}
+	m.SetHeader("Subject", subject) // 主题
+	m.SetBody("text/html", body)    // 正文
+	if fname != "" {
+		h := map[string][]string{"Content-Type": {"text/plain; charset=UTF-8"}}
+		m.Attach(fname, gomail.Rename(rename), gomail.SetHeader(h)) //添加附件
+		//m.Attach(fname) //添加附件
+	}
+	reTry := auth.ReTry
+	if reTry == 0 {
+		reTry = 3
+	}
+	return gSend(reTry, auth, m, to)
+}
+
+//
+func gSend(retry int, auth *GmailAuth, m *gomail.Message, to string) bool {
+	defer common.Catch()
+	dialer := getDialer(false, auth, to)
+	defer func() {
+		auth.PoolChan <- dialer
+	}()
+	status := false
+	for i := 0; i < retry; i++ {
+		if err := dialer.DialAndSend(m); err != nil {
+			dialer = getDialer(true, auth, to)
+			if retry > 0 {
+				log.Println(auth.User, to, "第", i+1, "次发送邮件gSend error:", err)
+				time.Sleep(200 * time.Millisecond)
+			}
+		} else {
+			status = true
+			break
+		}
+	}
+	return status
+}

+ 131 - 0
mail/mail.go

@@ -0,0 +1,131 @@
+/*
+ 邮件发送操作包装,<br/>
+ 暂支持邮件发送功能。<br/>
+ 邮件内容若想模板化,请在应用程序中处理。<br/>
+ 暂不支持附件
+*/
+package mail
+
+import (
+	"bytes"
+	"crypto/tls"
+	"fmt"
+	"log"
+	"net"
+	"net/smtp"
+	"strings"
+)
+
+const (
+	SPLIT = "\r\n"
+)
+
+//邮箱认证信息
+type MailAuth struct {
+	SmtpHost string //邮箱服务器
+	SmtpPort int    //邮箱端口
+	User     string //用户
+	Pwd      string //密码
+}
+
+//邮件消息
+type Message struct {
+	Subject string   //主题
+	From    string   //来自
+	To      []string //接收人,可以是多个接收人
+	Body    string   //html邮件内容
+}
+
+//发送邮件
+func SendMail(auth *MailAuth, mes *Message) {
+	go sendMailJob(auth, mes)
+}
+
+//发送邮件
+func sendMailJob(auth *MailAuth, mes *Message) error {
+	buf := bytes.NewBuffer(nil)
+	buf.WriteString("Subject: " + mes.Subject + SPLIT)
+	buf.WriteString("MIME-Version: 1.0" + SPLIT)
+	buf.WriteString("From: " + mes.From + SPLIT)
+	buf.WriteString("To: " + strings.Join(mes.To, ";") + SPLIT)
+	buf.WriteString("Content-Type: " + "text/html; charset=UTF-8" + SPLIT)
+	buf.WriteString(SPLIT + mes.Body)
+	smtpauth := smtp.PlainAuth(
+		"",
+		auth.User,
+		auth.Pwd,
+		auth.SmtpHost,
+	)
+	err := sendMailUsingTLS(
+		fmt.Sprintf("%s:%d", auth.SmtpHost, auth.SmtpPort),
+		smtpauth,
+		auth.User,
+		mes.To,
+		buf.Bytes(),
+	)
+	if err != nil {
+		fmt.Println(err.Error())
+	}
+	return err
+}
+
+func dial(addr string) (*smtp.Client, error) {
+	config := &tls.Config{ServerName: addr, InsecureSkipVerify: true}
+	conn, err := tls.Dial("tcp", addr, config)
+	if err != nil {
+		log.Println("Dialing Error:", err)
+		return nil, err
+	}
+	//分解主机端口字符串
+	host, _, _ := net.SplitHostPort(addr)
+	return smtp.NewClient(conn, host)
+}
+
+//安全协议发送邮件
+func sendMailUsingTLS(addr string, auth smtp.Auth, from string,
+	to []string, msg []byte) (err error) {
+
+	//create smtp client
+	c, err := dial(addr)
+	if err != nil {
+		log.Println("Create smpt client error:", err)
+		return err
+	}
+	defer c.Close()
+
+	if auth != nil {
+		if ok, _ := c.Extension("AUTH"); ok {
+			if err = c.Auth(auth); err != nil {
+				log.Println("Error during AUTH", err)
+				return err
+			}
+		}
+	}
+
+	if err = c.Mail(from); err != nil {
+		return err
+	}
+
+	for _, addr := range to {
+		if err = c.Rcpt(addr); err != nil {
+			return err
+		}
+	}
+
+	w, err := c.Data()
+	if err != nil {
+		return err
+	}
+
+	_, err = w.Write(msg)
+	if err != nil {
+		return err
+	}
+
+	err = w.Close()
+	if err != nil {
+		return err
+	}
+
+	return c.Quit()
+}

+ 31 - 0
mail/mail_test.go

@@ -0,0 +1,31 @@
+package mail
+
+import (
+	"testing"
+	"time"
+)
+
+func TestSendMail(t *testing.T) {
+	//SendMail(&MailAuth{
+	//	"smtp.exmail.qq.com", 465, "jy@aaree.cn", "Jianyu@123"}, &Message{"测试数据", "未知网络", []string{"renzheng@topnet.net.cn"}, "<font size=3>测试数据</font>"})
+
+	SendMail(&MailAuth{
+		"39.105.157.10", 465, "jy@jianyu360.cn", "TopnetJy2018"}, &Message{`您的关键词"工程"有新的招标信息`, "剑鱼招标订阅", []string{"renzheng@topnet.net.cn"}, `1. 2018年黑石乡高标准农田建设项目5标段
+	2. 2018年黑石乡高标准农田建设项目3标段
+	3. 2018年黑石乡高标准农田建设项目4标段`})
+	//发送邮件为并发协程发送,主进程需要等待其执行完成
+	time.Sleep(20 * time.Second)
+
+}
+
+func TestGmail(t *testing.T) {
+	gmails := &GmailAuth{
+		SmtpHost: "39.105.157.10",
+		SmtpPort: 465,
+		User:     "jy@jianyu360.cn",
+		Pwd:      "TopnetJy2018",
+	}
+
+	GSendMail_q("剑鱼标讯", "26523978@qq.com", "admin@aaree.cn", "", "主题", "", "a.txt", "附件", gmails)
+
+}

+ 56 - 0
sms/sms.go

@@ -0,0 +1,56 @@
+/*
+短信发送工具类
+暂定使用云片短信发送平台,
+后期确定下来后,再修改此工具类即可
+TODO 发送短信应作一下发送记录,并在前台(action层)控制单一用户发送次数,如每天发送5次。发送多浪费大家成本啊。
+*/
+package sms
+
+import (
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/url"
+)
+
+const (
+	//取用户信息地址
+	getUserInfoUrl = "http://yunpian.com/v1/user/get.json"
+	//发送短信地址
+	sendSmsUrl = "http://yunpian.com/v1/sms/tpl_send.json" //"http://yunpian.com/v1/sms/send.json"
+	//购买服务后,提供的KEY
+	key = "950090a3dfefb4f821ed8f0221482fcd "
+)
+
+//开启多协程发送短信
+func SendSms(tomobile, tpl, params string) {
+	go sendTextSms(tomobile, tpl, params)
+}
+
+//发短信
+func sendTextSms(tomobile, tpl /*模板标识*/, params /*url参数模板*/ string) error {
+	content, _ := url.Parse(params)
+	param := url.Values{"apikey": []string{key}, "mobile": []string{tomobile}, "tpl_id": []string{tpl}, "tpl_value": []string{content.String()}}
+	res, err := http.PostForm(sendSmsUrl, param)
+	if err != nil {
+		log.Println(err.Error())
+		return err
+	}
+	defer res.Body.Close()
+	bs, _ := ioutil.ReadAll(res.Body)
+	log.Println("返回内容:" + string(bs))
+	return nil
+}
+
+//取得用户信息
+func GetUserInfo() (string, error) {
+	param := url.Values{"apikey": []string{key}}
+	res, err := http.PostForm(getUserInfoUrl, param)
+	if err != nil {
+		log.Println(err.Error())
+		return "", err
+	}
+	defer res.Body.Close()
+	bs, _ := ioutil.ReadAll(res.Body)
+	return string(bs), nil
+}

+ 11 - 0
sms/sms_test.go

@@ -0,0 +1,11 @@
+package sms
+
+import (
+	"testing"
+	"time"
+)
+
+func TestSendSms(t *testing.T) {
+	SendSms("15639297172", "1", "#company#=企业服务网&#code#=D323")
+	time.Sleep(2 * time.Minute)
+}

+ 147 - 0
weedcl/client.go

@@ -0,0 +1,147 @@
+package weedcl
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"mime/multipart"
+	"net/http"
+	"os"
+)
+
+//
+type Client struct {
+	httpclient *http.Client
+	MasterUrl  string // 示例http://fs.qmx.top:9333
+}
+
+//
+func NewClient(masterurl string) *Client {
+	return &Client{
+		httpclient: http.DefaultClient,
+		MasterUrl:  masterurl}
+}
+
+//申请分配,取得FID
+func (c *Client) assign() (fid, url, publicurl string, err error) {
+	u := fmt.Sprintf("%s/dir/assign", c.MasterUrl)
+	resp, err := c.httpclient.Get(u)
+	if err != nil {
+		return
+	}
+	defer resp.Body.Close()
+	tmp := map[string]interface{}{}
+	bs, _ := ioutil.ReadAll(resp.Body)
+	err = json.Unmarshal(bs, &tmp)
+	if err != nil {
+		return
+	}
+	fid, _ = tmp["fid"].(string)
+	publicurl, _ = tmp["publicUrl"].(string)
+	url, _ = tmp["url"].(string)
+	return
+}
+
+//创建附件上传的表单头
+func createMultiPart(filename string, body io.Reader) (io.Reader, string, error) {
+	buff := &bytes.Buffer{}
+	mpW := multipart.NewWriter(buff)
+	defer mpW.Close()
+	filePart, err := mpW.CreateFormFile("file", filename)
+	_, err = io.Copy(filePart, body)
+	if err != nil {
+		return nil, "", err
+	}
+	return buff, mpW.FormDataContentType(), nil
+}
+
+//文件上传
+func (c *Client) Upload(path string) (string, string, error) {
+	fi, err := os.Open(path)
+	if err != nil {
+		return "", "", err
+	}
+	defer fi.Close()
+	return c.UploadFromReader(fi.Name(), fi)
+}
+
+//根据IO流来上传,方便bytes流,网络下载流等
+func (c *Client) UploadFromReader(filename string, r io.Reader) ( /*fid*/ string /*publicurl*/, string, error) {
+	return c.UpdateFromReader(filename, "", "", r)
+}
+
+//删除文件
+func (c *Client) Delete(fid, url string) (err error) {
+	req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s/%s", url, fid), nil)
+	if err != nil {
+		return
+	}
+	resp, err := c.httpclient.Do(req)
+	if err != nil {
+		return
+	}
+	defer resp.Body.Close()
+	return
+}
+
+//删除文件
+func (c *Client) NewDelete(fid, url string) ([]byte, error) {
+	req, err := http.NewRequest("DELETE", fmt.Sprintf("http://%s/%s", url, fid), nil)
+	if err != nil {
+		return nil, err
+	}
+	resp, err := c.httpclient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	return ioutil.ReadAll(resp.Body)
+}
+
+//更新文件
+func (c *Client) Update(path, url, fid string) (string, string, error) {
+	fi, err := os.Open(path)
+	if err != nil {
+		return "", "", err
+	}
+	defer fi.Close()
+	return c.UpdateFromReader(fi.Name(), url, fid, fi)
+}
+
+//更新文件,重新覆盖一遍即可
+func (c *Client) UpdateFromReader(filename, url, fid string, r io.Reader) ( /*fid*/ string /*publicurl*/, string, error) {
+	var publicurl string
+	var err error
+	if fid == "" {
+		fid, url, publicurl, err = c.assign()
+		if err != nil {
+			return "", "", err
+		}
+	}
+	mp, contentType, err := createMultiPart(filename, r)
+	if err != nil {
+		return "", "", err
+	}
+	posturl := fmt.Sprintf("http://%s/%s", url, fid)
+	req, err := http.NewRequest("POST", posturl, mp)
+	if err != nil {
+		return "", "", err
+	}
+	req.Header.Set("Content-type", contentType)
+	resp, err := c.httpclient.Do(req)
+	if err != nil {
+		return "", "", err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode < 200 || resp.StatusCode > 299 {
+		if err != nil {
+			return "", "", err
+		}
+		return "", "", fmt.Errorf("error during upload (%s) ", resp.StatusCode)
+	}
+	return fid, publicurl, nil
+}
+
+//TODO 其他相应接口暂时用不上,先不提供了

+ 2 - 0
weedcl/weedcl.go

@@ -0,0 +1,2 @@
+//weedfs 客户端封装
+package weedcl

+ 46 - 0
weedcl/weedcl_test.go

@@ -0,0 +1,46 @@
+package weedcl
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"testing"
+)
+
+func Test_assign(t *testing.T) {
+	c := NewClient("http://192.168.3.14:9333")
+	fid, url, publicurl, err := c.assign()
+	fmt.Println(fid, url, publicurl, err)
+}
+
+func Test_upload(t *testing.T) {
+	c := NewClient("http://192.168.3.14:9333")
+	fid, publicurl, err := c.Upload("/Users/zhp/projects/jy/app.json")
+	log.Println(fid, publicurl, err)
+}
+
+//上传网络资源
+func Test_upload2(t *testing.T) {
+	resp, _ := http.Get("http://fanyi.baidu.com/static/translation/img/header/logo_cbfea26.png")
+	defer resp.Body.Close()
+
+	c := NewClient("http://192.168.3.14:9333")
+	fid, publicurl, err := c.UploadFromReader("baidu_icon.png", resp.Body)
+	log.Println(fid, publicurl, err)
+
+}
+
+func Test_delete(t *testing.T) {
+	c := NewClient("http://192.168.3.14:9333")
+	err := c.Delete("27,0e6846633a", "192.168.3.14:9888")
+	log.Println(err)
+}
+
+func Test_update(t *testing.T) {
+	resp, _ := http.Get("http://cdn.chinaz.com/tools/images/public/logos/logo-index.png")
+	defer resp.Body.Close()
+
+	c := NewClient("http://192.168.3.14:9333")
+	fid, publicurl, err := c.UpdateFromReader("baidu_icon.png", "192.168.3.14:9888", "23,0f970140b1", resp.Body)
+	log.Println(fid, publicurl, err)
+}