朱金辉 2 years ago
commit
88ef03c178
62 changed files with 4178 additions and 0 deletions
  1. 9 0
      README.md
  2. 25 0
      config/config.local.yaml
  3. 85 0
      config/settings.go
  4. 9 0
      controller/index.go
  5. 199 0
      controller/poster.go
  6. 71 0
      controller/response.go
  7. 73 0
      controller/user.go
  8. 69 0
      entity/code.go
  9. 6 0
      entity/encrypt.go
  10. 21 0
      entity/pipeline.go
  11. 10 0
      entity/pipeline_status.go
  12. 73 0
      entity/user_poster.go
  13. 41 0
      entity/userinfo.go
  14. 62 0
      go.mod
  15. 0 0
      log/web.log
  16. 48 0
      logger/logger.go
  17. 48 0
      main.go
  18. 24 0
      middleware/auth.go
  19. 90 0
      middleware/logger_mw.go
  20. 82 0
      model/user.go
  21. 25 0
      model/user_poster.go
  22. 10 0
      repository/mysql/auto_migrate.go
  23. 31 0
      repository/mysql/mysql.go
  24. 39 0
      repository/redis/redis.go
  25. 36 0
      router/api_router.go
  26. 13 0
      router/init.go
  27. 130 0
      service/user.go
  28. 60 0
      service/user_poster.go
  29. 578 0
      util/array/array.go
  30. 200 0
      util/array/array_test.go
  31. 43 0
      util/crypto/crypto.go
  32. 18 0
      util/crypto/crypto_test.go
  33. 53 0
      util/datetime/datetime.go
  34. 23 0
      util/datetime/datetime_test.go
  35. 119 0
      util/file/file.go
  36. 27 0
      util/file/file_test.go
  37. BIN
      util/go-test.png
  38. 1 0
      util/image/avatar.go
  39. 132 0
      util/image/back.txt
  40. 38 0
      util/image/cache.go
  41. 101 0
      util/image/color.go
  42. 18 0
      util/image/color_test.go
  43. 41 0
      util/image/entity.go
  44. 1 0
      util/image/font.go
  45. 191 0
      util/image/image.go
  46. 18 0
      util/image/point.go
  47. 124 0
      util/image/poster.go
  48. 101 0
      util/image/qr.go
  49. 44 0
      util/image/scale.go
  50. 18 0
      util/image/size.go
  51. 308 0
      util/image/text.go
  52. 10 0
      util/image/tools.go
  53. 20 0
      util/jobs/cron.go
  54. 13 0
      util/jobs/test_cron.go
  55. 72 0
      util/math/math.go
  56. 24 0
      util/math/math_test.go
  57. 72 0
      util/string/string.go
  58. 26 0
      util/string/string_test.go
  59. 191 0
      util/tools.go
  60. 84 0
      util/tools_test.go
  61. 50 0
      util/url/url.go
  62. 30 0
      util/url/url_test.go

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
+## 使用第三方库 GraphicsMagick (https://github.com/gographics/gmagick)
+export PKG_CONFIG_PATH=/opt/local/lib/pkgconfig
+
+#安装imagick库 (<https://github.com/gographics/imagick>)
+mac os
+brew install ImageMagick
+
+run 之前执行,将imagick编译
+export CGO_CFLAGS_ALLOW="-Xpreprocessor"

+ 25 - 0
config/config.local.yaml

@@ -0,0 +1,25 @@
+mode: "dev"
+#mode: "release"
+port: 8084
+cache_dir: "cache"
+encrypt:
+  key: "OdeOe93Dxf"
+  expire_at: 86400
+log:
+  level: "info"
+  web_log_name: "web.log"
+  log_file_path: "./log/"
+mysql:
+  host: 127.0.0.1
+  port: 3306
+  user: "root"
+  password: "A119328118a"
+  dbname: "poster"
+  max_open_conns: 2000
+  max_idle_conns: 50
+redis:
+  host: 127.0.0.1
+  port: 6379
+  password: ""
+  db: 0
+  pool_size: 100

+ 85 - 0
config/settings.go

@@ -0,0 +1,85 @@
+package config
+
+import (
+	"fmt"
+	"github.com/fsnotify/fsnotify"
+	"github.com/spf13/viper"
+)
+
+var Conf = new(AppConfig)
+
+type AppConfig struct {
+	Mode         string `mapstructure:"mode"`
+	Port         int    `mapstructure:"port"`
+	CacheDir     string `mapstructure:"cache_dir"`
+	*Encrypt     `mapstructure:"encrypt"`
+	*LogConfig   `mapstructure:"log"`
+	*MySQLConfig `mapstructure:"mysql"`
+	*RedisConfig `mapstructure:"redis"`
+}
+
+type Encrypt struct {
+	Key      string `mapstructure:"key"`
+	ExpireAt int64  `mapstructure:"expire_at"`
+}
+
+type LogConfig struct {
+	Level       string `mapstructure:"level"`
+	WebLogName  string `mapstructure:"web_log_name"`
+	LogFilePath string `mapstructure:"log_file_path"`
+}
+
+type MySQLConfig struct {
+	Host         string `mapstructure:"host"`
+	User         string `mapstructure:"user"`
+	Password     string `mapstructure:"password"`
+	DB           string `mapstructure:"dbname"`
+	Port         int    `mapstructure:"port"`
+	MaxOpenConns int    `mapstructure:"max_open_conns"`
+	MaxIdleConns int    `mapstructure:"max_idle_conns"`
+}
+
+type RedisConfig struct {
+	Host         string `mapstructure:"host"`
+	Password     string `mapstructure:"password"`
+	Port         int    `mapstructure:"port"`
+	DB           int    `mapstructure:"db"`
+	PoolSize     int    `mapstructure:"pool_size"`
+	MinIdleConns int    `mapstructure:"min_idle_conns"`
+}
+
+var (
+	devFilePath     = "./config/config.dev.yaml"
+	releaseFilePath = "./config/config.online.yaml"
+	localFilePath   = "./config/config.local.yaml"
+)
+
+func Init(mode string) {
+	var filePath string
+	if mode == "dev" {
+		filePath = devFilePath
+	} else if mode == "release" {
+		filePath = releaseFilePath
+	} else { // local
+		filePath = localFilePath
+	}
+	viper.SetConfigFile(filePath)
+	err := viper.ReadInConfig() // 读取配置信息
+	if err != nil {
+		// 读取配置信息失败
+		panic(fmt.Sprintf("viper.ReadInConfig failed, err:%v\n", err))
+	}
+
+	// 把读取到的配置信息反序列化到 Conf 变量中
+	if err := viper.Unmarshal(Conf); err != nil {
+		fmt.Printf("viper.Unmarshal failed, err:%v\n", err)
+	}
+
+	viper.WatchConfig()
+	viper.OnConfigChange(func(in fsnotify.Event) {
+		fmt.Println("配置文件修改了...")
+		if err := viper.Unmarshal(Conf); err != nil {
+			panic(fmt.Sprintf("viper.Unmarshal failed, err:%v\n", err))
+		}
+	})
+}

+ 9 - 0
controller/index.go

@@ -0,0 +1,9 @@
+package controller
+
+import "github.com/gin-gonic/gin"
+
+func Index(c *gin.Context) {
+	s := make(map[string]interface{})
+	s["msg"] = "success"
+	ResponseSuccess(c, s)
+}

+ 199 - 0
controller/poster.go

@@ -0,0 +1,199 @@
+package controller
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"github.com/gographics/gmagick"
+	setting "icloudapp.cn/tools/config"
+	"icloudapp.cn/tools/entity"
+	"icloudapp.cn/tools/model"
+	"icloudapp.cn/tools/util/image"
+	"os"
+)
+
+func Preview(ctx *gin.Context) {
+	file := ctx.Query("file")
+	if file == "" {
+		ResponseError(ctx, entity.CodePageNotFound)
+		return
+	}
+
+	path, _ := os.Getwd()
+
+	gmagick.Initialize()
+	defer gmagick.Terminate()
+
+	thumb := image.NewImage(setting.Conf.CacheDir)
+	file, err := thumb.Thumb(fmt.Sprintf("%s/%s", path, file), image.NewSize(300, 300))
+
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, err.Error(), []interface{}{})
+		return
+	}
+
+	stream, err := os.ReadFile(file)
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, err.Error(), []interface{}{})
+		return
+	}
+	ctx.Writer.WriteString(string(stream))
+}
+
+func Image(ctx *gin.Context) {
+	jsonByte, err := os.ReadFile("data.json")
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, err.Error(), []interface{}{})
+		return
+	}
+	var posterJson image.PosterJson
+	err = json.Unmarshal(jsonByte, &posterJson)
+
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, err.Error(), []interface{}{})
+		return
+	}
+
+	poster := image.NewPoster(ctx)
+
+	file, err := poster.Create(posterJson)
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, fmt.Sprintf("poster.Create err : %s", err.Error()), []interface{}{})
+		return
+	}
+	if err = file.SetImageFormat("PNG"); err != nil {
+
+		ResponseNormal(ctx, entity.CodeSystemError, fmt.Sprintf("file.SetImageFormat err : %s", err.Error()), []interface{}{})
+		return
+	}
+	defer file.Destroy()
+	if posterJson.Quality == 0 {
+		posterJson.Quality = 50
+	}
+	if err = file.SetCompressionQuality(uint(posterJson.Quality)); err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, fmt.Sprintf("file.SetCompressionQuality err: %s", err.Error()), []interface{}{})
+		return
+	}
+	//不写入文件,直接返回给浏览器
+	_, err = ctx.Writer.Write(file.WriteImageBlob())
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, fmt.Sprintf("ctx.Writer.Write err: %s", err.Error()), []interface{}{})
+		return
+	}
+	/*
+		stream, err := os.ReadFile(file)
+		if err != nil {
+			fmt.Println("read error")
+			ResponseNormal(ctx, entity.CodeSystemError, err.Error(), []interface{}{})
+			return
+		}
+
+		ctx.Writer.WriteString(string(stream))
+	*/
+}
+
+func Cut(ctx *gin.Context) {
+	file := ctx.Query("file")
+	if file == "" {
+		ResponseError(ctx, entity.CodePageNotFound)
+		return
+	}
+
+	path, _ := os.Getwd()
+
+	gmagick.Initialize()
+	defer gmagick.Terminate()
+
+	thumb := image.NewImage(setting.Conf.CacheDir)
+	file, err := thumb.Cut(fmt.Sprintf("%s/%s", path, file), image.NewSize(400, 400), "center")
+
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, err.Error(), []interface{}{})
+		return
+	}
+
+	stream, err := os.ReadFile(file)
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, err.Error(), []interface{}{})
+		return
+	}
+
+	ctx.Writer.WriteString(string(stream))
+}
+
+func SavePoster(ctx *gin.Context) {
+
+}
+
+func PosterInfo(ctx *gin.Context) {
+	var request entity.PosterInfoRequest
+	if err := ctx.ShouldBind(&request); err != nil {
+		ResponseError(ctx, entity.CodeInvalidParam)
+		return
+	}
+	sUserPoster := model.NewUserPoster(ctx)
+	info, err := sUserPoster.Info(request.PosterID)
+
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeServerBusy, info.Msg, nil)
+		return
+	}
+	var api entity.UserPosterInfoForApi
+	api.Code = info.Code
+	api.Msg = info.Msg
+	api.Body.Poster = info.Body
+
+	ResponseNormal(ctx, api.Code, api.Msg, api.Body)
+}
+
+func DrawImage(ctx *gin.Context) {
+	var request entity.PosterInfoRequest
+	if err := ctx.ShouldBind(&request); err != nil {
+		ResponseError(ctx, entity.CodeInvalidParam)
+		return
+	}
+	sUserPoster := model.NewUserPoster(ctx)
+	info, err := sUserPoster.Info(request.PosterID)
+
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeServerBusy, info.Msg, nil)
+		return
+	}
+
+	var posterJson image.PosterJson
+	err = json.Unmarshal([]byte(info.Body.PosterJson), &posterJson)
+
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, err.Error(), []interface{}{})
+		return
+	}
+
+	poster := image.NewPoster(ctx)
+
+	file, err := poster.Create(posterJson)
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, fmt.Sprintf("poster.Create err : %s", err.Error()), []interface{}{})
+		return
+	}
+	if err = file.SetImageFormat("PNG"); err != nil {
+
+		ResponseNormal(ctx, entity.CodeSystemError, fmt.Sprintf("file.SetImageFormat err : %s", err.Error()), []interface{}{})
+		return
+	}
+	defer file.Destroy()
+	if posterJson.Quality == 0 {
+		posterJson.Quality = 50
+	}
+
+	if err = file.SetCompressionQuality(uint(posterJson.Quality)); err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, fmt.Sprintf("file.SetCompressionQuality err: %s", err.Error()), []interface{}{})
+		return
+	}
+
+	//不写入文件,直接返回给浏览器
+	_, err = ctx.Writer.Write(file.WriteImageBlob())
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeSystemError, fmt.Sprintf("ctx.Writer.Write err: %s", err.Error()), []interface{}{})
+		return
+	}
+}

+ 71 - 0
controller/response.go

@@ -0,0 +1,71 @@
+package controller
+
+import (
+	"github.com/gin-gonic/gin"
+	"icloudapp.cn/tools/entity"
+	"net/http"
+)
+
+/*
+{
+	"code": 10000, // 程序中的错误码
+	"msg": xx,     // 提示信息
+	"data": {},    // 数据
+}
+*/
+
+type ResponseData struct {
+	Code entity.ResCode `json:"code"`
+	Msg  interface{}    `json:"msg"`
+	Body interface{}    `json:"data,omitempty"`
+}
+
+type ResponseBodyData struct {
+	Code entity.ResCode `json:"code"`
+	Msg  interface{}    `json:"msg"`
+	Body interface{}    `json:"body:omitempty"`
+}
+
+// Response error
+func ResponseError(c *gin.Context, code entity.ResCode) {
+	c.JSON(http.StatusOK, &ResponseData{
+		Code: code,
+		Msg:  code.Msg(),
+		Body: nil,
+	})
+}
+
+// Response error with msg
+func ResponseErrorWithMsg(c *gin.Context, code entity.ResCode, msg interface{}) {
+	c.JSON(http.StatusOK, &ResponseData{
+		Code: code,
+		Msg:  msg,
+		Body: nil,
+	})
+}
+
+// Response success
+func ResponseSuccess(c *gin.Context, data interface{}) {
+	c.JSON(http.StatusOK, &ResponseData{
+		Code: entity.CodeSuccess,
+		Msg:  entity.CodeSuccess.Msg(),
+		Body: data,
+	})
+}
+
+// Response {code, msg, body}
+func ResponseNormal(c *gin.Context, code entity.ResCode, msg interface{}, body interface{}) {
+	c.JSON(http.StatusOK, &ResponseData{
+		Code: code,
+		Msg:  msg,
+		Body: body,
+	})
+}
+
+func ResponseBody(c *gin.Context, code entity.ResCode, msg interface{}, body interface{}) {
+	c.JSON(http.StatusOK, &ResponseBodyData{
+		Code: code,
+		Msg:  msg,
+		Body: body,
+	})
+}

+ 73 - 0
controller/user.go

@@ -0,0 +1,73 @@
+package controller
+
+import (
+	"github.com/gin-gonic/gin"
+	"icloudapp.cn/tools/entity"
+	"icloudapp.cn/tools/model"
+	string2 "icloudapp.cn/tools/util/string"
+)
+
+// Info 获取用户信息
+func Info(ctx *gin.Context) {
+	uid := string2.ConvertInt64(ctx.Query("uid"))
+
+	if uid < 1 {
+		ResponseError(ctx, entity.CodeInvalidParam)
+		return
+	}
+	mUser := model.NewUser(ctx)
+	userInfo, err := mUser.Info(uid)
+
+	if err != nil {
+		ResponseNormal(ctx, userInfo.Code, userInfo.Msg, nil)
+		return
+	}
+
+	ResponseSuccess(ctx, userInfo.Body)
+}
+
+// Login 登录
+func Login(ctx *gin.Context) {
+	var request entity.LoginRequest
+	if err := ctx.ShouldBind(&request); err != nil {
+		ResponseError(ctx, entity.CodeInvalidParam)
+		return
+	}
+	mUser := model.NewUser(ctx)
+	loginInfo, err := mUser.Login(request.UserName, request.Password)
+
+	if err != nil {
+		ResponseNormal(ctx, entity.CodeServerBusy, "系统错误", nil)
+	}
+
+	if loginInfo.Uid < 1 {
+		ResponseNormal(ctx, entity.CodeDenied, "登录失败", []interface{}{})
+		return
+	}
+	ResponseSuccess(ctx, loginInfo)
+}
+
+// Register 注册
+func Register(ctx *gin.Context) {
+	var request entity.RegisterRequest
+
+	if err := ctx.ShouldBind(&request); err != nil {
+		ResponseError(ctx, entity.CodeInvalidParam)
+		return
+	}
+
+	mUser := model.NewUser(ctx)
+
+	userInfo, err := mUser.Register(request.UserName, request.Password, request.Email)
+
+	if err != nil {
+		ResponseNormal(ctx, userInfo.Code, err.Error(), []interface{}{})
+		return
+	}
+	if userInfo.Code > 0 {
+		//ResponseNormal(ctx, entity.CodeDenied, userInfo.Msg, userInfo.Body)
+		ResponseNormal(ctx, userInfo.Code, userInfo.Msg, []interface{}{})
+		return
+	}
+	ResponseSuccess(ctx, userInfo)
+}

+ 69 - 0
entity/code.go

@@ -0,0 +1,69 @@
+package entity
+
+import "errors"
+
+type ResCode int64
+
+const (
+	CodeSuccess         ResCode = 0
+	CodeNotAllowed      ResCode = 405
+	CodeDenied          ResCode = 403
+	CodePageNotFound    ResCode = 404
+	CodeInvalidParam    ResCode = 1001
+	CodeUserExist       ResCode = 1002
+	CodeUserNotExist    ResCode = 1003
+	CodeInvalidPassword ResCode = 1004
+	CodeServerBusy      ResCode = 1005
+	CodeNeedLogin       ResCode = 1006
+	CodeInvalidToken    ResCode = 1007
+
+	CodeEmailExist    ResCode = 1008
+	CodeEmailNotExist ResCode = 1009
+
+	CodeFileNotExist ResCode = 1010
+
+	CodeSystemError      ResCode = 1011
+	CodeDataDoesNotExist ResCode = 1012
+)
+
+var codeMsgMap = map[ResCode]string{
+	CodeSuccess:          "success",
+	CodeNotAllowed:       "不被允许",
+	CodeDenied:           "拒绝访问",
+	CodePageNotFound:     "404 Not Found",
+	CodeInvalidParam:     "请求参数错误",
+	CodeUserExist:        "用户已存在",
+	CodeUserNotExist:     "用户不存在",
+	CodeInvalidPassword:  "用户名或密码错误",
+	CodeServerBusy:       "服务繁忙",
+	CodeNeedLogin:        "需要登录",
+	CodeInvalidToken:     "无效的token",
+	CodeEmailExist:       "邮箱已注册",
+	CodeEmailNotExist:    "邮箱不存在",
+	CodeFileNotExist:     "文件不存在",
+	CodeSystemError:      "系统错误",
+	CodeDataDoesNotExist: "未找到相关数据",
+}
+
+type Code struct {
+	Code ResCode
+	Msg  string
+	Body interface{}
+}
+type Response struct {
+	Code
+	Body interface {
+	}
+}
+
+func (c ResCode) Msg() string {
+	msg, ok := codeMsgMap[c]
+	if !ok {
+		msg = codeMsgMap[CodeServerBusy]
+	}
+	return msg
+}
+
+func (c ResCode) Error() error {
+	return errors.New(c.Msg())
+}

+ 6 - 0
entity/encrypt.go

@@ -0,0 +1,6 @@
+package entity
+
+type EncryptData struct {
+	Str     string
+	Expired int64
+}

+ 21 - 0
entity/pipeline.go

@@ -0,0 +1,21 @@
+package entity
+
+type Pipeline struct {
+	Id         int64  `gorm:"primary_key"  json:"id"`
+	Status     int64  `json:"status"`
+	PipelineId int    `gorm:"not null" json:"pipeline_id" binding:"required"`
+	Name       string `gorm:"size:100;not null" json:"name" binding:"required"` //设置字段的大小为255个字节
+	Desc       string `json:"desc"`
+	StatusName string `gorm:"-"  json:"status_name"`
+}
+
+func (Pipeline) TableName() string {
+	return "pipeline"
+}
+
+// 修改pipeline时携带参数
+type ParamPipeline struct {
+	Id         int64  `json:"id" binding:"required"`
+	PipelineId int    `json:"pipeline_id" binding:"required"`
+	Name       string `json:"name" binding:"required"`
+}

+ 10 - 0
entity/pipeline_status.go

@@ -0,0 +1,10 @@
+package entity
+
+type PipelineStatus struct {
+	Id     int64 `gorm:"primary_key"`
+	Status string
+}
+
+func (PipelineStatus) TableName() string {
+	return "pipeline_status"
+}

+ 73 - 0
entity/user_poster.go

@@ -0,0 +1,73 @@
+package entity
+
+import "time"
+
+type PosterInfoRequest struct {
+	PosterID int64 `form:"id" binding:"required"`
+}
+type UserPosterInfo struct {
+	Code ResCode
+	Msg  string
+	Body UserPoster
+}
+
+type UserPosterInfoForApi struct {
+	Code ResCode
+	Msg  string
+	Body struct {
+		Poster UserPoster `json:"poster"`
+	}
+}
+type UserPoster struct {
+	PosterID       int64     `gorm:"column:poster_id" json:"id"`
+	Name           string    `gorm:"column:poster_name" json:"name"`
+	UUID           string    `gorm:"column:uuid" json:"uuid"`
+	Uid            int64     `gorm:"column:uid" json:"uid"`
+	PosterKeywords string    `gorm:"column:poster_keywords" json:"poster_Keywords"`
+	PosterJson     string    `gorm:"column:poster_json" json:"json"`
+	Preview        string    `gorm:"column:preview" json:"preview"`
+	Visit          string    `gorm:"column:visit" json:"visit"`
+	Status         string    `gorm:"column:status" json:"status"`
+	CreateAt       time.Time `gorm:"column:create_at" json:"create_at"`
+	UpdateAt       time.Time `gorm:"column:update_at" json:"update_at"`
+}
+
+type PosterJson struct {
+	Id            string  `json:"id"`
+	Name          string  `json:"name"`
+	Width         uint    `json:"w"`
+	Height        uint    `json:"h"`
+	Use           string  `json:"use"`
+	Background    string  `json:"bgc"`
+	BackgroundUrl string  `json:"bgUrl"`
+	Type          string  `json:"type"`
+	Quality       int     `json:"quality"`
+	Scale         float64 `json:"scale"`
+	Key           string  `json:"key"`
+	Items         []Items `json:"items"`
+}
+type Items struct {
+	Type        string  `json:"t"`
+	Remark      string  `json:"name"`
+	UUID        string  `json:"uuid"`
+	X           int     `json:"x"`
+	Y           int     `json:"y"`
+	Width       uint    `json:"w"`
+	Height      uint    `json:"h"`
+	ZIndex      int     `json:"z"`
+	Angle       int     `json:"angle"`
+	Size        int     `json:"s"`
+	Color       string  `json:"c"`
+	Background  string  `json:"bgc"`
+	Value       string  `json:"v"`
+	Name        string  `json:"vd"`
+	Font        string  `json:"fn"`
+	St          int     `json:"st"`
+	Active      bool    `json:"active"`
+	Lock        bool    `json:"lock"`
+	AspectRatio bool    `json:"aspectRatio"`
+	P           int     `json:"p,omitempty"`
+	Align       *string `json:"a,omitempty"`
+	Wrap        *bool   `json:"wrap,omitempty"`
+	Bius        *string `json:"bius,omitempty"`
+}

+ 41 - 0
entity/userinfo.go

@@ -0,0 +1,41 @@
+package entity
+
+import "time"
+
+type Login struct {
+	Uid      int64  `json:"uid"`
+	Username string `json:"username"`
+	Type     int    `json:"type"`
+	Avatar   string `json:"avatar"`
+	Status   int8   `json:"status"`
+	Token    string `json:"token"`
+}
+
+type LoginRequest struct {
+	UserName string `form:"username" binding:"required"`
+	Password string `form:"password" binding:"required"`
+}
+
+type RegisterRequest struct {
+	UserName string `form:"username" binding:"required"`
+	Password string `form:"password" binding:"required"`
+	Email    string `form:"email"`
+}
+
+type UserInfo struct {
+	Code ResCode
+	Msg  string
+	Body User
+}
+
+type User struct {
+	Uid      int64     `gorm:"column:uid" json:"uid"`
+	Username string    `gorm:"column:nickname" json:"username"`
+	Email    string    `gorm:"column:email" json:"email"`
+	Password string    `gorm:"column:password" json:"password"`
+	Avatar   string    `gorm:"column:avatar" json:"avatar"`
+	Salt     string    `gorm:"column:salt" json:"salt"`
+	Status   int8      `gorm:"column:status" json:"status"`
+	CreateAt time.Time `gorm:"column:create_at" json:"create_at"`
+	UpdateAt time.Time `gorm:"column:update_at" json:"update_at"`
+}

+ 62 - 0
go.mod

@@ -0,0 +1,62 @@
+module icloudapp.cn/tools
+
+go 1.19
+
+require (
+	github.com/fsnotify/fsnotify v1.6.0
+	github.com/gin-gonic/gin v1.9.0
+	github.com/go-redis/redis v6.15.9+incompatible
+	github.com/gographics/gmagick v1.0.0
+	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
+	github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
+	github.com/robfig/cron v1.2.0
+	github.com/sirupsen/logrus v1.9.0
+	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
+	github.com/spf13/viper v1.15.0
+	gopkg.in/gographics/imagick.v3 v3.4.2
+	gorm.io/driver/mysql v1.4.7
+	gorm.io/gorm v1.24.5
+)
+
+require (
+	github.com/bytedance/sonic v1.8.0 // indirect
+	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.11.2 // indirect
+	github.com/go-sql-driver/mysql v1.7.0 // indirect
+	github.com/goccy/go-json v0.10.0 // indirect
+	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/jonboulle/clockwork v0.3.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+	github.com/leodido/go-urn v1.2.1 // indirect
+	github.com/lestrrat-go/strftime v1.0.6 // indirect
+	github.com/magiconair/properties v1.8.7 // indirect
+	github.com/mattn/go-isatty v0.0.17 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/onsi/ginkgo v1.16.5 // indirect
+	github.com/onsi/gomega v1.27.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.6 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/spf13/afero v1.9.3 // indirect
+	github.com/spf13/cast v1.5.0 // indirect
+	github.com/spf13/jwalterweatherman v1.1.0 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/subosito/gotenv v1.4.2 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.9 // indirect
+	golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
+	golang.org/x/crypto v0.5.0 // indirect
+	golang.org/x/net v0.7.0 // indirect
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 0 - 0
log/web.log


+ 48 - 0
logger/logger.go

@@ -0,0 +1,48 @@
+package logger
+
+import (
+	"fmt"
+	"github.com/sirupsen/logrus"
+	"os"
+	setting "icloudapp.cn/tools/config"
+)
+
+var WebLog *logrus.Logger
+
+func Init() {
+	initWebLog()
+}
+
+func initWebLog() {
+	WebLog = initLog(setting.Conf.LogConfig.WebLogName)
+}
+
+// 初始化日志句柄
+func initLog(logFileName string) *logrus.Logger {
+	log := logrus.New()
+	log.Formatter = &logrus.JSONFormatter{
+		TimestampFormat: "2006-01-02 15:04:05",
+	}
+	logFilePath := setting.Conf.LogFilePath
+	logName := logFilePath + logFileName
+	var f *os.File
+	var err error
+	//判断日志文件夹是否存在,不存在则创建
+	if _, err := os.Stat(logFilePath); os.IsNotExist(err) {
+		os.MkdirAll(logFilePath, os.ModePerm)
+	}
+	//判断日志文件是否存在,不存在则创建,否则就直接打开
+	if _, err := os.Stat(logName); os.IsNotExist(err) {
+		f, err = os.Create(logName)
+	} else {
+		f, err = os.OpenFile(logName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
+	}
+
+	if err != nil {
+		fmt.Println("open log file failed")
+	}
+
+	log.Out = f
+	log.Level = logrus.InfoLevel
+	return log
+}

+ 48 - 0
main.go

@@ -0,0 +1,48 @@
+package main
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"github.com/gographics/gmagick"
+	"os"
+
+	setting "icloudapp.cn/tools/config"
+	log "icloudapp.cn/tools/logger"
+	"icloudapp.cn/tools/repository/mysql"
+	"icloudapp.cn/tools/repository/redis"
+	"icloudapp.cn/tools/router"
+	"icloudapp.cn/tools/util/jobs"
+)
+
+func main() {
+	/*if len(os.Args) < 2 {
+		fmt.Println("need config file.eg: config.yaml")
+		return
+	}
+	// 1.加载配置
+	setting.Init(os.Args[1])
+	*/
+
+	gmagick.Initialize()
+	defer gmagick.Terminate()
+
+	gin.SetMode(gin.DebugMode)
+	setting.Init(gin.EnvGinMode)
+	// 2.初始化日志
+	log.Init()
+	// 3.初始化mysql
+	mysql.Init(setting.Conf.MySQLConfig)
+	// 4.如果参数为 migrate就初始化表结构
+	if len(os.Args) >= 3 && os.Args[2] == "migrate" {
+		mysql.AutoMigrateDB()
+		fmt.Println("run AutoMigrate success!")
+		return
+	}
+	// 5.初始化redis
+	redis.Init(setting.Conf.RedisConfig)
+	// 6.初始化定时任务
+	jobs.InitJobs()
+	// 7.注册路由
+	r := router.InitRouter()
+	r.Run(fmt.Sprintf(":%d", setting.Conf.Port))
+}

+ 24 - 0
middleware/auth.go

@@ -0,0 +1,24 @@
+package middleware
+
+import (
+	"github.com/gin-gonic/gin"
+	"icloudapp.cn/tools/controller"
+	"icloudapp.cn/tools/entity"
+)
+
+// JWTAuthMiddleware 基于JWT的认证中间件
+func AuthMiddleware() func(c *gin.Context) {
+	return func(c *gin.Context) {
+		// 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI
+		// token验证成功,返回c.Next继续,否则返回c.Abort()直接返回
+		authHeader := c.Request.Header.Get("Authorization")
+		if authHeader == "" {
+			controller.ResponseError(c, entity.CodeNeedLogin)
+			c.Abort()
+			return
+		}
+		// 将当前请求的userID信息保存到请求的上下文c上
+		//c.Set(controller.CtxUserIDKey, mc.UserID)
+		c.Next() // 后续的处理请求的函数中 可以用过c.Get(CtxUserIDKey) 来获取当前请求的用户信息
+	}
+}

+ 90 - 0
middleware/logger_mw.go

@@ -0,0 +1,90 @@
+package middleware
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
+	"github.com/rifflock/lfshook"
+	"github.com/sirupsen/logrus"
+	"os"
+	"path"
+	"time"
+)
+
+var (
+	logFilePath = "./"
+	logFileName = "system.log"
+)
+
+func LoggerMiddleware() gin.HandlerFunc {
+	// 日志文件
+	fileName := path.Join(logFilePath, logFileName)
+	// 写入文件
+	src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
+	if err != nil {
+		fmt.Println("err", err)
+	}
+	// 实例化
+	logger := logrus.New()
+	//设置日志级别
+	logger.SetLevel(logrus.DebugLevel)
+	//设置输出
+	logger.Out = src
+
+	// 设置 rotatelogs
+	logWriter, err := rotatelogs.New(
+		// 分割后的文件名称
+		fileName+".%Y%m%d.log",
+
+		// 生成软链,指向最新日志文件
+		rotatelogs.WithLinkName(fileName),
+
+		// 设置最大保存时间(7天)
+		rotatelogs.WithMaxAge(7*24*time.Hour),
+
+		// 设置日志切割时间间隔(1天)
+		rotatelogs.WithRotationTime(24*time.Hour),
+	)
+
+	writeMap := lfshook.WriterMap{
+		logrus.InfoLevel:  logWriter,
+		logrus.FatalLevel: logWriter,
+		logrus.DebugLevel: logWriter,
+		logrus.WarnLevel:  logWriter,
+		logrus.ErrorLevel: logWriter,
+		logrus.PanicLevel: logWriter,
+	}
+
+	logger.AddHook(lfshook.NewHook(writeMap, &logrus.JSONFormatter{
+		TimestampFormat: "2006-01-02 15:04:05",
+	}))
+
+	return func(c *gin.Context) {
+		//开始时间
+		startTime := time.Now()
+		//处理请求
+		c.Next()
+		//结束时间
+		endTime := time.Now()
+		// 执行时间
+		latencyTime := endTime.Sub(startTime)
+		//请求方式
+		reqMethod := c.Request.Method
+		//请求路由
+		reqUrl := c.Request.RequestURI
+		//状态码
+		statusCode := c.Writer.Status()
+		//请求ip
+		clientIP := c.ClientIP()
+
+		// 日志格式
+		logger.WithFields(logrus.Fields{
+			"status_code":  statusCode,
+			"latency_time": latencyTime,
+			"client_ip":    clientIP,
+			"req_method":   reqMethod,
+			"req_uri":      reqUrl,
+		}).Info()
+
+	}
+}

+ 82 - 0
model/user.go

@@ -0,0 +1,82 @@
+package model
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	setting "icloudapp.cn/tools/config"
+	"icloudapp.cn/tools/entity"
+	_ "icloudapp.cn/tools/repository/mysql"
+	"icloudapp.cn/tools/service"
+	"icloudapp.cn/tools/util"
+	"icloudapp.cn/tools/util/url"
+	"strconv"
+	"time"
+)
+
+type User struct {
+	ctx context.Context
+}
+
+func NewUser(ctx context.Context) *User {
+	return &User{ctx}
+}
+
+// Login 用户名 密码登录
+func (u *User) Login(username, password string) (entity.Login, error) {
+	var login entity.Login
+	sUser := service.NewUser(u.ctx)
+	userInfo, err := sUser.UserInfoByUsername(username)
+	if err != nil {
+		return login, err
+	}
+	signedPassword := util.Sign(password, userInfo.Salt)
+	//密码对不上就返回空login
+	if userInfo.Password != signedPassword {
+		return login, errors.New("密码不匹配")
+	}
+
+	encodeStr := url.HttpBuildQuery(map[string]string{
+		"uid":      strconv.FormatInt(userInfo.Uid, 10),
+		"username": userInfo.Username,
+	})
+
+	token, _ := util.EncryptWidthTime(encodeStr, setting.Conf.Encrypt.ExpireAt, setting.Conf.Encrypt.Key)
+	login.Uid = userInfo.Uid
+	login.Username = userInfo.Username
+	login.Status = userInfo.Status
+	login.Avatar = userInfo.Avatar
+	login.Type = 1
+	login.Token = token
+	return login, nil
+}
+
+// Register 通过用户名、密码或邮箱注册
+func (u *User) Register(username, password, email string) (entity.UserInfo, error) {
+	var register entity.User
+	register.Username = username
+	register.Salt = util.RandomNumberStr(4)
+	register.Password = util.Sign(password, register.Salt)
+	if email == "" {
+		email = fmt.Sprintf("%s@travelzs.com", username)
+	}
+	register.Email = email
+	register.Status = 1
+	timeParse, _ := time.Parse(time.Now().String(), "2006-01-02 15:04:05")
+
+	fmt.Println("timeParse", timeParse, time.Unix(time.Now().Unix(), 0).Format("2006-01-02 15:04"))
+	register.CreateAt = time.Now()
+	register.UpdateAt = time.Now()
+
+	sUser := service.NewUser(u.ctx)
+	return sUser.Register(register)
+}
+
+func (u *User) Info(uid int64) (entity.UserInfo, error) {
+	sUser := service.NewUser(u.ctx)
+
+	return sUser.UserInfoByID(uid)
+}
+func (*User) ChangePassword(uid int64, password string) {
+
+}

+ 25 - 0
model/user_poster.go

@@ -0,0 +1,25 @@
+package model
+
+import (
+	"context"
+	"icloudapp.cn/tools/entity"
+	"icloudapp.cn/tools/service"
+)
+
+type UserPoster struct {
+	ctx context.Context
+}
+
+func NewUserPoster(ctx context.Context) *UserPoster {
+	return &UserPoster{ctx: ctx}
+}
+
+func (p *UserPoster) Info(posterID int64) (entity.UserPosterInfo, error) {
+	sPoster := service.NewPoster(p.ctx)
+	return sPoster.InfoByID(posterID)
+}
+
+func (p *UserPoster) InfoByUUID(uuid int64) (entity.UserPosterInfo, error) {
+	sPoster := service.NewPoster(p.ctx)
+	return sPoster.InfoByUUID(uuid)
+}

+ 10 - 0
repository/mysql/auto_migrate.go

@@ -0,0 +1,10 @@
+package mysql
+
+import "icloudapp.cn/tools/entity"
+
+func AutoMigrateDB() {
+	DBConn.AutoMigrate(
+		&entity.Pipeline{},
+		&entity.PipelineStatus{},
+	)
+}

+ 31 - 0
repository/mysql/mysql.go

@@ -0,0 +1,31 @@
+package mysql
+
+import (
+	"database/sql"
+	"fmt"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	setting "icloudapp.cn/tools/config"
+)
+
+var DBConn *gorm.DB
+
+func Init(cfg *setting.MySQLConfig) {
+	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
+		cfg.User, cfg.Password, cfg.Host, cfg.DB)
+	sqlDB, err := sql.Open("mysql", dsn)
+	if err != nil {
+		panic(fmt.Sprintf("sql.Open err, \v", err))
+	}
+	sqlDB.SetMaxOpenConns(cfg.MaxOpenConns) //最大连接数
+	sqlDB.SetMaxOpenConns(cfg.MaxIdleConns)
+	gormDB, err := gorm.Open(mysql.New(mysql.Config{
+		Conn: sqlDB,
+	}), &gorm.Config{
+		DisableForeignKeyConstraintWhenMigrating: true, //禁用外键生成
+	})
+	if err != nil {
+		panic(fmt.Sprintf("链接数据库失败\v", err))
+	}
+	DBConn = gormDB
+}

+ 39 - 0
repository/redis/redis.go

@@ -0,0 +1,39 @@
+package redis
+
+import (
+	"fmt"
+	"github.com/go-redis/redis"
+	setting "icloudapp.cn/tools/config"
+)
+
+var (
+	RedisClient *redis.Client
+	Nil         = redis.Nil
+)
+
+// Init 初始化连接
+func Init(cfg *setting.RedisConfig) {
+	RedisClient = redis.NewClient(&redis.Options{
+		Addr:         fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
+		Password:     cfg.Password, // no password set
+		DB:           cfg.DB,       // use default DB
+		PoolSize:     cfg.PoolSize,
+		MinIdleConns: cfg.MinIdleConns,
+	})
+
+	_, err := RedisClient.Ping().Result()
+	if err != nil {
+		panic(fmt.Sprintf("redis connect err, \v", err))
+	}
+}
+
+func Close() {
+	_ = RedisClient.Close()
+}
+
+/*
+---- 测试写入和获取字符串 ----
+RedisClient.Set("username", "zhangsan", 0).Err()
+username, _ := RedisClient.Get("username").Result()
+fmt.Println(username)  // zhangsan
+*/

+ 36 - 0
router/api_router.go

@@ -0,0 +1,36 @@
+package router
+
+import (
+	"github.com/gin-gonic/gin"
+	"icloudapp.cn/tools/controller"
+	"icloudapp.cn/tools/middleware"
+)
+
+func SetupApiRouters(r *gin.Engine) {
+	v1 := r.Group("/api/v1")
+	v1.Use(middleware.AuthMiddleware())
+	/*
+		v1.GET("pipeline/:id", controller.GetPipelineDetailHandler)
+		v1.GET("pipeline", controller.GetPipelineListHandler)
+		v1.POST("pipeline", controller.CreatePipelineHandler)
+		v1.PUT("pipeline", controller.UpdatePipelineHandler)
+		v1.DELETE("pipeline/:id", controller.DeletePipelineHandler)
+	*/
+	r.GET("/index", controller.Index)
+
+	user := r.Group("/api/user")
+	user.GET("/info", controller.Info)
+	user.GET("/login", controller.Login)
+	user.POST("/login", controller.Login)
+
+	user.GET("/register", controller.Register)
+	user.POST("/register", controller.Register)
+
+	poster := r.Group("/api/poster")
+	poster.GET("/info", controller.PosterInfo)
+	poster.GET("/image", controller.Image)
+	poster.GET("/cut", controller.Cut)
+	poster.POST("/save", controller.SavePoster)
+	poster.GET("/preview", controller.Preview)
+	poster.GET("/draw", controller.DrawImage)
+}

+ 13 - 0
router/init.go

@@ -0,0 +1,13 @@
+package router
+
+import (
+	"github.com/gin-gonic/gin"
+	"icloudapp.cn/tools/middleware"
+)
+
+func InitRouter() *gin.Engine {
+	r := gin.Default()
+	r.Use(middleware.LoggerMiddleware())
+	SetupApiRouters(r)
+	return r
+}

+ 130 - 0
service/user.go

@@ -0,0 +1,130 @@
+package service
+
+import (
+	"context"
+	"errors"
+	"icloudapp.cn/tools/entity"
+	_ "icloudapp.cn/tools/entity"
+	"icloudapp.cn/tools/repository/mysql"
+	_ "icloudapp.cn/tools/repository/mysql"
+	"icloudapp.cn/tools/util"
+)
+
+type User struct {
+	ctx context.Context
+}
+
+func NewUser(ctx context.Context) *User {
+	return &User{ctx}
+}
+
+func (u *User) TableName() string {
+	return "user"
+}
+func (u *User) UserInfoByID(uid int64) (entity.UserInfo, error) {
+	var userInfo entity.UserInfo
+	var user entity.User
+	result := mysql.DBConn.Select(
+		"uid",
+		"nickname",
+		"password",
+		"salt",
+		"avatar",
+		"create_at",
+		"update_at",
+	).Where("uid = ?", uid).Table(u.TableName()).Find(&user)
+
+	if result.Error != nil {
+		userInfo.Code = entity.CodeServerBusy
+		userInfo.Msg = result.Error.Error()
+		return userInfo, result.Error
+	}
+	if result.RowsAffected == 0 {
+		user.Uid = 0
+		userInfo.Code = entity.CodeDataDoesNotExist
+	}
+	userInfo.Body = user
+	return userInfo, nil
+}
+
+// UserInfoByUsername 通过nickname获取用户信息
+func (u *User) UserInfoByUsername(username string) (userInfo *entity.User, err error) {
+	result := mysql.DBConn.Select(
+		"uid",
+		"nickname",
+		"password",
+		"salt",
+		"avatar",
+		"create_at",
+		"update_at",
+	).Where("nickname = ?", username).Table(u.TableName()).Find(&userInfo)
+
+	if result.Error != nil {
+		return userInfo, util.NewError(result.Error.Error())
+	}
+
+	if result.RowsAffected == 0 {
+		userInfo.Uid = 0
+	}
+	return userInfo, nil
+}
+
+// UserInfoByEmail 通过邮箱获取用户信息
+func (u *User) UserInfoByEmail(email string) (userInfo *entity.User, err error) {
+	result := mysql.DBConn.Select(
+		"uid",
+		"nickname",
+		"password",
+		"salt",
+		"avatar",
+		"create_at",
+		"update_at",
+	).Where("email = ?", email).Table(u.TableName()).Find(&userInfo)
+
+	if result.Error != nil {
+		return nil, util.NewError(result.Error.Error())
+	}
+	if result.RowsAffected == 0 {
+		userInfo.Email = ""
+	}
+	return userInfo, nil
+}
+
+// Register 用户注册
+func (u *User) Register(register entity.User) (userInfo entity.UserInfo, err error) {
+	//check nickname, email 是否已注册
+	existUsername, errInfo := u.UserInfoByUsername(register.Username)
+
+	if errInfo != nil {
+		userInfo.Msg = entity.CodeServerBusy.Msg()
+		userInfo.Code = entity.CodeServerBusy
+		return userInfo, errors.New(errInfo.Error())
+	}
+
+	if existUsername.Uid > 0 {
+		userInfo.Msg = "用户名已经被注册"
+		userInfo.Code = entity.CodeUserExist
+		userInfo.Body = *existUsername
+		return userInfo, entity.CodeUserExist.Error()
+	}
+
+	existEmail, err := u.UserInfoByEmail(register.Email)
+	if err != nil {
+		userInfo.Msg = entity.CodeServerBusy.Msg()
+		userInfo.Code = entity.CodeServerBusy
+		return userInfo, errors.New(errInfo.Error())
+	}
+	if existEmail.Uid > 0 {
+		userInfo.Msg = entity.CodeEmailExist.Msg()
+		userInfo.Code = entity.CodeEmailExist
+		userInfo.Body = *existEmail
+		return userInfo, entity.CodeEmailExist.Error()
+	}
+
+	dbErr := mysql.DBConn.Table(u.TableName()).Create(&register)
+	if dbErr.Error != nil {
+		return userInfo, errors.New(dbErr.Error.Error())
+	}
+	userInfo.Body = register
+	return userInfo, nil
+}

+ 60 - 0
service/user_poster.go

@@ -0,0 +1,60 @@
+package service
+
+import (
+	"context"
+	"errors"
+	"icloudapp.cn/tools/entity"
+	"icloudapp.cn/tools/repository/mysql"
+	"icloudapp.cn/tools/util"
+)
+
+type UserPoster struct {
+	ctx context.Context
+}
+
+func NewPoster(ctx context.Context) *UserPoster {
+	return &UserPoster{ctx}
+}
+
+func (p *UserPoster) TableName() string {
+	return "user_poster"
+}
+func (p *UserPoster) InfoByID(posterID int64) (entity.UserPosterInfo, error) {
+	var userPosterInfo entity.UserPosterInfo
+	var userPoster entity.UserPoster
+
+	userPoster.PosterID = posterID
+	result := mysql.DBConn.Where("poster_id = ?", posterID).Table(p.TableName()).Find(&userPoster)
+	if result.Error != nil {
+		userPosterInfo.Code = entity.CodeServerBusy
+		userPosterInfo.Msg = entity.CodeServerBusy.Msg()
+		return userPosterInfo, errors.New(result.Error.Error())
+	}
+	if result.RowsAffected == 0 {
+		userPosterInfo.Code = entity.CodeDataDoesNotExist
+		userPosterInfo.Msg = entity.CodeDataDoesNotExist.Msg()
+		return userPosterInfo, util.NewError("未找到相关数据")
+	}
+	userPosterInfo.Body = userPoster
+	return userPosterInfo, nil
+}
+
+func (p *UserPoster) InfoByUUID(uuid int64) (entity.UserPosterInfo, error) {
+	var userPosterInfo entity.UserPosterInfo
+	var userPoster entity.UserPoster
+
+	userPoster.PosterID = uuid
+	result := mysql.DBConn.Where("uuid = ?", uuid).Table(p.TableName()).Find(&userPoster)
+	if result.Error != nil {
+		userPosterInfo.Code = entity.CodeServerBusy
+		userPosterInfo.Msg = entity.CodeServerBusy.Msg()
+		return userPosterInfo, errors.New(result.Error.Error())
+	}
+	if result.RowsAffected == 0 {
+		userPosterInfo.Code = entity.CodeDataDoesNotExist
+		userPosterInfo.Msg = entity.CodeDataDoesNotExist.Msg()
+		return userPosterInfo, util.NewError("未找到相关数据")
+	}
+	userPosterInfo.Body = userPoster
+	return userPosterInfo, nil
+}

+ 578 - 0
util/array/array.go

@@ -0,0 +1,578 @@
+package array
+
+import (
+	"errors"
+	"fmt"
+	"reflect"
+	"regexp"
+)
+
+var simpleTypes = []string{
+	"bool", "int", "int8", "int16", "int32", "int64", "uint", "uint8",
+	"uint16", "uint32", "uint64", "uintptr", "float32", "float64", "string",
+}
+
+// Column array column to be new array
+// php array_column
+func Column(dest, input interface{}, columnKey, indexKey string) {
+	// data validate
+	if columnKey == "" && indexKey == "" {
+		panic("columnKey or indexKey must be at least one has value")
+	}
+
+	// dest interface
+	dValue := reflect.ValueOf(dest)
+	if dValue.Kind() != reflect.Ptr || dValue.IsNil() {
+		panic(fmt.Sprintf("haystack: d type[%T] error", reflect.TypeOf(dValue)))
+	}
+	dType, dElemType, dKeyType := indirectForArr(dValue)
+	if !In(dKeyType, simpleTypes) {
+		// only support 'simpleType'
+		panic("haystack: dest key type must be 'simpleType'")
+	}
+	if indexKey != "" {
+		if dType != reflect.Map {
+			panic("haystack: dest type must be map")
+		}
+	} else {
+		if dType != reflect.Slice && dType != reflect.Array {
+			panic("haystack: dest type must be slice or array ")
+		}
+	}
+
+	// input interface - columnKey and indexKey
+	inValue := reflect.ValueOf(input)
+	inPrtEValue, inType := indirectSimple(inValue)
+	inElemKind, inKeyType, inElemType := columnElemType(inPrtEValue, inType, columnKey, indexKey)
+	if inElemKind == reflect.Struct && ((columnKey != "" && !isFirstLetterUp(columnKey)) ||
+		(indexKey != "" && !isFirstLetterUp(indexKey))) {
+		panic("columnKey or indexKey must be public field")
+	}
+
+	// no relation with pkg_path
+	if inKeyType != dKeyType {
+		panic("'dest' key type does not consist with 'input[indexKey]' type")
+	}
+
+	// no relation with pkg_path
+	if inElemType != dElemType {
+		panic("'dest' elem type does not consist with 'input[columnKey]' type")
+	}
+
+	// data operation
+	dValueElem := ptrToElem(dValue)
+	var tempKey, tempColumn reflect.Value
+	switch inValue.Kind() {
+	case reflect.Slice, reflect.Array:
+		for i := 0; i < inValue.Len(); i++ {
+			tempColumn = inValue.Index(i)
+			if inElemKind == reflect.Struct {
+				if indexKey != "" {
+					tempKey = tempColumn.FieldByName(indexKey)
+				}
+
+				if columnKey != "" {
+					tempColumn = tempColumn.FieldByName(columnKey)
+				}
+			} else {
+				if indexKey != "" {
+					tempKey = tempColumn.MapIndex(reflect.ValueOf(indexKey))
+				}
+
+				if columnKey != "" {
+					tempColumn = tempColumn.MapIndex(reflect.ValueOf(columnKey))
+				}
+			}
+
+			dValueElem = columnSetValue(dType, dValueElem, tempColumn, tempKey)
+		}
+	case reflect.Map:
+		for _, k := range inValue.MapKeys() {
+			tempColumn = inValue.MapIndex(k)
+			if inElemKind == reflect.Struct {
+				if indexKey != "" {
+					tempKey = tempColumn.FieldByName(indexKey)
+				}
+
+				if columnKey != "" {
+					tempColumn = tempColumn.FieldByName(columnKey)
+				}
+			} else {
+				if indexKey != "" {
+					tempKey = tempColumn.MapIndex(reflect.ValueOf(indexKey))
+				}
+
+				if columnKey != "" {
+					tempColumn = tempColumn.MapIndex(reflect.ValueOf(columnKey))
+				}
+			}
+
+			dValueElem = columnSetValue(dType, dValueElem, tempColumn, tempKey)
+		}
+	}
+
+	// set elem data
+	dValue.Elem().Set(dValueElem)
+}
+
+// columnSetValue set column elem data
+func columnSetValue(rKind reflect.Kind, rv, column, key reflect.Value) reflect.Value {
+	switch rKind {
+	case reflect.Slice, reflect.Array:
+		rv = reflect.Append(rv, column)
+	case reflect.Map:
+		if rv.IsNil() {
+			panic("columnSetValue: reflect.Value is nil")
+		}
+		rv.SetMapIndex(key, column)
+	}
+
+	return rv
+}
+
+// columnElemType analysis column type and index type
+func columnElemType(rv reflect.Value, rt reflect.Type, columnKey, indexKey string) (reflect.Kind, string, string) {
+	var err error
+	var elemKind reflect.Kind
+	var keyType, elemType string
+	switch rv.Kind() {
+	case reflect.Slice, reflect.Array, reflect.Map:
+		// indexKey operation
+		if indexKey == "" {
+			keyType = reflect.Int.String()
+		} else {
+			elemKind, keyType, err = fieldElemType(rt.Elem(), indexKey)
+			if err != nil {
+				panic(err.Error())
+			}
+		}
+
+		// columnKey operation
+		if columnKey == "" {
+			elemType = rt.Elem().String()
+		} else {
+			elemKind, elemType, err = fieldElemType(rt.Elem(), columnKey)
+			if err != nil {
+				panic(err.Error())
+			}
+		}
+	default:
+		panic("haystack: v type must be slice, array or map")
+	}
+
+	return elemKind, keyType, elemType
+}
+
+// fieldElemType analysis field type
+func fieldElemType(elemRt reflect.Type, key string) (reflect.Kind, string, error) {
+	var err error
+	var elemType string
+	elemKind := elemRt.Kind()
+	switch elemKind {
+	case reflect.Struct:
+		field, isExist := elemRt.FieldByName(key)
+		if !isExist {
+			err = errors.New(fmt.Sprintf("input map has no column[%s]", key))
+			return elemKind, elemType, err
+		}
+		elemType = field.Type.String()
+	case reflect.Map:
+		elemType = elemRt.Elem().String()
+	default:
+		panic("haystack: elemRt type must be map or struct")
+	}
+
+	return elemKind, elemType, err
+}
+
+// set operation type
+type setOps bool
+
+const (
+	// set operation
+	instOps setOps = true
+	diffOps setOps = false
+
+	// constant data
+	uniqueOps bool = false
+)
+
+// Diff array Diff to d
+// php array_diff
+func Diff(d interface{}, dArr ...interface{}) {
+	// d interface
+	dValue := reflect.ValueOf(d)
+	if dValue.Kind() != reflect.Ptr || dValue.IsNil() {
+		panic(fmt.Sprintf("haystack: d type[%T] error", reflect.TypeOf(dValue)))
+	}
+
+	// array diff
+	diffValue := operateSetValue(dValue, diffOps, dArr...)
+
+	// set elem data
+	dValue.Elem().Set(diffValue)
+}
+
+// Intersect array Intersect to d
+// php array_intersect
+func Intersect(d interface{}, dArr ...interface{}) {
+	// d interface indirectForArr
+	dValue := reflect.ValueOf(d)
+	if dValue.Kind() != reflect.Ptr || dValue.IsNil() {
+		panic(fmt.Sprintf("haystack: d type[%T] error", reflect.TypeOf(dValue)))
+	}
+
+	// array intersect
+	instValue := operateSetValue(dValue, instOps, dArr...)
+
+	// set elem data
+	dValue.Elem().Set(instValue)
+}
+
+// operateSetValue operation set_value
+func operateSetValue(dv reflect.Value, ops setOps, dArr ...interface{}) reflect.Value {
+	// check dValue
+	if dv.Kind() == reflect.Ptr {
+		dv = dv.Elem()
+	}
+
+	// operation set
+	var newValue reflect.Value
+	dType, _ := indirect(dv)
+	indirectStr := reflect.Indirect(dv)
+	for _, arr := range dArr {
+		// type compare
+		arrValue := reflect.ValueOf(arr)
+		if arrValue.Kind() == reflect.Ptr && arrValue.IsNil() {
+			continue
+		}
+
+		// new data
+		switch dType {
+		case reflect.Slice, reflect.Array:
+			newValue = reflect.MakeSlice(indirectStr.Type(), 0, dv.Len())
+			for i := 0; i < dv.Len(); i++ {
+				if inByRValue(dv.Index(i), arrValue) == bool(ops) {
+					newValue = reflect.Append(newValue, dv.Index(i))
+				}
+			}
+		case reflect.Map:
+			newValue = reflect.MakeMap(indirectStr.Type())
+			for _, k := range dv.MapKeys() {
+				if inByRValue(dv.MapIndex(k), arrValue) == bool(ops) {
+					newValue.SetMapIndex(k, dv.MapIndex(k))
+				}
+			}
+		}
+
+		// set elem data
+		dv.Set(newValue)
+	}
+
+	return dv
+}
+
+// In check d is in 'arr'
+// php in_array
+func In(d interface{}, arr interface{}) bool {
+	dValue := reflect.ValueOf(d)
+	arrValue := reflect.ValueOf(arr)
+
+	return inByRValue(dValue, arrValue)
+}
+
+// inByRValue in data by reflect value
+func inByRValue(dV reflect.Value, arrV reflect.Value) bool {
+	dVKind, dvType := indirect(dV)
+	arrType, arrElemType, _ := indirectForArr(arrV)
+	if dvType != arrElemType {
+		// d does not consist with arr elem
+		return false
+	}
+
+	isExist := false
+	switch dVKind {
+	case reflect.Map, reflect.Array, reflect.Slice, reflect.Struct:
+		isExist = inDeepEqual(dV, arrV, arrType)
+	default:
+		isExist = inEqual(dV, arrV, arrType)
+	}
+
+	return isExist
+}
+
+// inEqual use array simple data equal
+func inEqual(dV reflect.Value, arrV reflect.Value, arrT reflect.Kind) bool {
+	isExist := false
+	dV = ptrToElem(dV)     // check ptr
+	arrV = ptrToElem(arrV) // check ptr
+	switch arrT {
+	case reflect.Slice, reflect.Array:
+		for i := 0; i < arrV.Len(); i++ {
+			if isExist = dV.Interface() == arrV.Index(i).Interface(); isExist {
+				break
+			}
+		}
+	case reflect.Map:
+		for _, k := range arrV.MapKeys() {
+			if isExist = dV.Interface() == arrV.MapIndex(k).Interface(); isExist {
+				break
+			}
+		}
+	default:
+		panic("haystack: arrV type must be slice, array or map")
+	}
+
+	return isExist
+}
+
+// inDeepEqual use array complex data equal
+func inDeepEqual(dV reflect.Value, arrV reflect.Value, arrT reflect.Kind) bool {
+	isExist := false
+	dV = ptrToElem(dV)     // check ptr
+	arrV = ptrToElem(arrV) // check ptr
+	switch arrT {
+	case reflect.Slice, reflect.Array:
+		for i := 0; i < arrV.Len(); i++ {
+			if isExist = reflect.DeepEqual(dV.Interface(), arrV.Index(i).Interface()); isExist {
+				break
+			}
+		}
+	case reflect.Map:
+		for _, k := range arrV.MapKeys() {
+			if isExist = reflect.DeepEqual(dV.Interface(), arrV.MapIndex(k).Interface()); isExist {
+				break
+			}
+		}
+	default:
+		panic("haystack: d type must be slice, array or map")
+	}
+
+	return isExist
+}
+
+// Merge array merge to d
+// php array_merge
+func Merge(d interface{}, dArr ...interface{}) {
+	// d interface indirectForArr
+	dValue := reflect.ValueOf(d)
+	if dValue.Kind() != reflect.Ptr || dValue.IsNil() {
+		panic(fmt.Sprintf("haystack: d type[%T] error", reflect.TypeOf(dValue)))
+	}
+
+	// array merge
+	dValueElem := ptrToElem(dValue)
+	dType, dElemType, dKeyType := indirectForArr(dValue)
+	for _, arr := range dArr {
+		// type compare
+		arrValue := reflect.ValueOf(arr)
+		if dValue.Kind() != reflect.Ptr || dValue.IsNil() {
+			continue
+		}
+		arrType, arrElemType, arrKeyType := indirectForArr(arrValue)
+		if arrType != dType {
+			panic("'dArr' type does not consist with 'd' type")
+		}
+		if arrElemType != dElemType {
+			panic("'dArr' elem type does not consist with 'd' elem type")
+		}
+		if dKeyType != arrKeyType {
+			panic("'dArr' key type does not consist with 'd' key type")
+		}
+
+		// data merge
+		arrValue = ptrToElem(arrValue)
+		switch dType {
+		case reflect.Slice, reflect.Array:
+			for i := 0; i < arrValue.Len(); i++ {
+				dValueElem = reflect.Append(dValueElem, arrValue.Index(i))
+			}
+		case reflect.Map:
+			for _, k := range arrValue.MapKeys() {
+				dValueElem.SetMapIndex(k, arrValue.MapIndex(k))
+			}
+		default:
+			panic("haystack: d type must be slice, array or map")
+		}
+	}
+
+	// set elem data
+	dValue.Elem().Set(dValueElem)
+}
+
+// Values get array values
+// php array_values
+func Values(d interface{}, values interface{}) {
+	// values interface indirectForArr
+	vValue := reflect.ValueOf(values)
+	if vValue.Kind() != reflect.Ptr || vValue.IsNil() {
+		panic(fmt.Sprintf("haystack: values type[%T] error", reflect.TypeOf(vValue)))
+	}
+	vType, vElemType, _ := indirectForArr(vValue)
+	if vType != reflect.Slice && vType != reflect.Array {
+		panic("haystack: values type must be slice or array")
+	}
+
+	// d interface indirectForArr
+	dValue := reflect.ValueOf(d)
+	dType, dElemType, _ := indirectForArr(dValue)
+	if vElemType != dElemType {
+		panic("'d' key type does not consist with 'keys' elem type")
+	}
+
+	vValueElem := ptrToElem(vValue)
+	switch dType {
+	case reflect.Slice, reflect.Array:
+		for i := 0; i < dValue.Len(); i++ {
+			vValueElem = reflect.Append(vValueElem, dValue.Index(i))
+		}
+	case reflect.Map:
+		for _, k := range dValue.MapKeys() {
+			vValueElem = reflect.Append(vValueElem, dValue.MapIndex(k))
+		}
+	default:
+		panic("haystack: d type must be slice, array or map")
+	}
+
+	// set elem data
+	vValue.Elem().Set(vValueElem)
+}
+
+// Keys get array keys
+// php array_keys
+func Keys(d interface{}, keys interface{}) {
+	// keys interface indirectForArr
+	keysValue := reflect.ValueOf(keys)
+	if keysValue.Kind() != reflect.Ptr || keysValue.IsNil() {
+		panic(fmt.Sprintf("haystack: keys type[%T] error", reflect.TypeOf(keysValue)))
+	}
+	keysType, keysElemType, _ := indirectForArr(keysValue)
+	if keysType != reflect.Slice && keysType != reflect.Array {
+		panic("haystack: keys type must be slice or array")
+	}
+
+	// d interface indirectForArr
+	dValue := reflect.ValueOf(d)
+	dType, _, dKeyType := indirectForArr(dValue)
+	if keysElemType != dKeyType {
+		panic("'keys' key type does not consist with 'd' elem type")
+	}
+
+	keysElem := ptrToElem(keysValue)
+	switch dType {
+	case reflect.Slice, reflect.Array:
+		for i := 0; i < dValue.Len(); i++ {
+			keysElem = reflect.Append(keysElem, reflect.ValueOf(i))
+		}
+	case reflect.Map:
+		for _, k := range dValue.MapKeys() {
+			keysElem = reflect.Append(keysElem, k)
+		}
+	default:
+		panic("haystack: d type must be slice, array or map")
+	}
+
+	// set elem data
+	keysValue.Elem().Set(keysElem)
+}
+
+// Unique array data de-duplication
+// php array_unique
+func Unique(d interface{}) {
+	// d interface indirectForArr
+	dv := reflect.ValueOf(d)
+	if dv.Kind() != reflect.Ptr || dv.IsNil() {
+		panic(fmt.Sprintf("haystack: d type[%T] error", reflect.TypeOf(dv)))
+	}
+	dvElem := ptrToElem(dv)
+
+	// unique operation
+	var newValue reflect.Value
+	dType, _ := indirect(dv)
+	indirectStr := reflect.Indirect(dv)
+	switch dType {
+	case reflect.Slice, reflect.Array:
+		newValue = reflect.MakeSlice(indirectStr.Type(), 0, dvElem.Len())
+		for i := 0; i < dvElem.Len(); i++ {
+			if inByRValue(dvElem.Index(i), newValue) == uniqueOps {
+				newValue = reflect.Append(newValue, dvElem.Index(i))
+			}
+		}
+	case reflect.Map:
+		newValue = reflect.MakeMap(indirectStr.Type())
+		for _, k := range dvElem.MapKeys() {
+			if inByRValue(dvElem.MapIndex(k), newValue) == uniqueOps {
+				newValue.SetMapIndex(k, dvElem.MapIndex(k))
+			}
+		}
+	}
+
+	// set elem data
+	dv.Elem().Set(newValue)
+}
+
+// indirect get interface kind and type
+func indirect(rv reflect.Value) (reflect.Kind, string) {
+	// get reflect value and type
+	rvValue, rvType := indirectSimple(rv)
+
+	return rvValue.Kind(), rvType.String()
+}
+
+// indirectForArr get slice, array or map type
+func indirectForArr(rv reflect.Value) (reflect.Kind, string, string) {
+	// get reflect value and type
+	rvValue, rvType := indirectSimple(rv)
+
+	vType := rvValue.Kind()
+	var vKeyType, vElemType string
+	switch vType {
+	case reflect.Slice, reflect.Array:
+		vKeyType = reflect.Int.String()
+		vElemType = rvType.Elem().String()
+	case reflect.Map:
+		vKeyType = rvType.Key().String()
+		vElemType = rvType.Elem().String()
+	default:
+		panic("haystack: v type must be slice, array or map")
+	}
+
+	return vType, vElemType, vKeyType
+}
+
+// indirectSimple indirect_simply
+func indirectSimple(rv reflect.Value) (reflect.Value, reflect.Type) {
+	// check valid
+	if !rv.IsValid() {
+		panic("indirectSimple: reflect.Value is nil")
+	}
+
+	// get reflect value and type
+	var rvValue reflect.Value
+	var rvType reflect.Type
+	switch rv.Kind() {
+	case reflect.Ptr:
+		rvValue = rv.Elem()
+		rvType = rv.Type().Elem()
+	default:
+		rvValue = rv
+		rvType = rv.Type()
+	}
+
+	return rvValue, rvType
+}
+
+// ptrToElem Ptr to elem
+func ptrToElem(v reflect.Value) reflect.Value {
+	if v.Kind() == reflect.Ptr {
+		v = v.Elem()
+	}
+
+	return v
+}
+
+// isFirstLetterUp the first letter is up
+func isFirstLetterUp(s string) bool {
+	regObj, _ := regexp.Compile("^[A-Z].*")
+	return regObj.MatchString(s)
+}

+ 200 - 0
util/array/array_test.go

@@ -0,0 +1,200 @@
+package array
+
+import (
+	"fmt"
+	"testing"
+)
+
+type keyValue struct {
+	key   string
+	value string
+}
+
+type PubKeyValue struct {
+	Key   string
+	Value string
+}
+
+// test array column
+func TestArrayColumn(t *testing.T) {
+	/*dataMap := []PubKeyValue{
+		{Key: "a", Value: "A"},
+		{Key: "b", Value: "B"},
+	}*/
+
+	/*dataMap := map[string]map[string]string{
+		"a": {"key": "a", "value": "A"},
+		"b": {"key": "b", "value": "B"},
+	}
+
+	dest := map[string]string{}*/
+
+	dataMap := []map[string]string{
+		{"key": "a", "value": "A"},
+		{"key": "b", "value": "B"},
+	}
+
+	dest := map[string]map[string]string{}
+
+	Column(&dest, dataMap, "", "value")
+	fmt.Println(dest)
+}
+
+// test array diff
+func TestArrayDiff(t *testing.T) {
+	dataMap := map[string]keyValue{
+		"a": {key: "a", value: "A"},
+		"b": {key: "b", value: "B"},
+	}
+
+	dataMap1 := []keyValue{
+		{key: "c", value: "C"},
+		{key: "b", value: "B"},
+	}
+
+	dataMap2 := []string{
+		"a", "b",
+	}
+
+	Diff(&dataMap, dataMap1, dataMap2)
+	fmt.Println("dataMap", dataMap)
+}
+
+// test array intersect
+func TestArrayIntersect(t *testing.T) {
+	dataMap := []keyValue{
+		{key: "c", value: "C"},
+		{key: "b", value: "B"},
+	}
+
+	dataMap1 := map[string]keyValue{
+		"a": {key: "a", value: "A"},
+		"b": {key: "b", value: "B"},
+		"c": {key: "c", value: "C"},
+	}
+
+	dataMap2 := []string{
+		"a", "b",
+	}
+
+	Intersect(&dataMap, dataMap1, &dataMap2)
+	fmt.Println("dataMap", dataMap)
+}
+
+// test in array
+func TestInArray(t *testing.T) {
+	/*data := map[int]string{1: "1"}
+	dataMap := map[string]map[int]string{
+		"a": {1: "1"},
+		"b": {2: "2"},
+	}*/
+
+	/*dataMap := []keyValue{
+		{key: "a", value: "A"},
+		{key: "b", value: "B"},
+	}
+	data := keyValue{key: "b", value: "A"}*/
+
+	dataMap := []string{"a", "b", "c"}
+	data := "a"
+
+	isExist := In(&data, dataMap)
+	fmt.Println(isExist)
+}
+
+// test array array_merge
+func TestArrayMergeForSlice(t *testing.T) {
+	dataMap := []map[int]string{
+		{1: "1"},
+		{2: "2"},
+	}
+
+	oDataMap1 := []map[int]string{
+		{3: "3"},
+		{4: "4"},
+	}
+
+	Merge(&dataMap, &oDataMap1)
+	fmt.Println(dataMap)
+}
+
+// test map array_merge
+func TestArrayMergeForMap(t *testing.T) {
+	dataMap := map[string]map[int]string{
+		"a": {1: "1"},
+		"b": {2: "2"},
+	}
+
+	oDataMap1 := map[string]map[int]string{
+		"c": {3: "3"},
+		"b": {4: "4"},
+	}
+
+	oDataMap2 := map[string]map[int]string{}
+
+	oDataMap3 := map[string]map[int]string{
+		"e": {5: "5"},
+		"f": {6: "6"},
+	}
+
+	Merge(&dataMap, &oDataMap1, oDataMap2, oDataMap3)
+	fmt.Println(dataMap)
+}
+
+// test slice array_values
+func TestArrayValuesForSlice(t *testing.T) {
+	dataMap := []map[int]string{
+		{1: "1", 2: "2"},
+		{1: "1", 2: "2"},
+	}
+	var valueArr []map[int]string
+	Values(dataMap, &valueArr)
+	fmt.Println(valueArr)
+}
+
+// test Map array_values
+func TestArrayValuesForMap(t *testing.T) {
+	/*dataMap := map[string]keyValue{
+		"a":{key: "a", value: "A"},
+		"b":{key: "b", value: "B"},
+	}*/
+	dataMap := map[string]map[int]string{
+		"a": {1: "1", 2: "2"},
+		"b": {1: "1", 2: "2"},
+	}
+	var valueArr []map[int]string
+	Values(dataMap, &valueArr)
+	fmt.Println(valueArr)
+
+	/*fmt.Println(vValue.Elem().Type().Elem().Name(), dValue.Type().Elem().Name())
+	fmt.Println(vValue.Elem().Type().Elem().PkgPath(), dValue.Type().Elem().PkgPath())*/
+}
+
+// test array_keys
+func TestArrayKeysForMap(t *testing.T) {
+	dataMap := map[string]keyValue{
+		"a": {key: "a", value: "A"},
+		"b": {key: "b", value: "B"},
+	}
+	var keysArr []string
+	Keys(dataMap, &keysArr)
+	fmt.Println(keysArr)
+}
+
+// test array_unique
+func TestArrayUnique(t *testing.T) {
+	/*dataMap := map[string]keyValue{
+		"a": {key: "a", value: "A"},
+		"b": {key: "b", value: "B"},
+		"c": {key: "a", value: "A"},
+	}*/
+
+	dataMap := []keyValue{
+		{key: "a", value: "A"},
+		{key: "b", value: "B"},
+		{key: "a", value: "A"},
+	}
+
+	Unique(&dataMap)
+	fmt.Println(dataMap)
+}

+ 43 - 0
util/crypto/crypto.go

@@ -0,0 +1,43 @@
+package crypto
+
+import (
+	"crypto/md5"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/hex"
+	"fmt"
+)
+
+// MD5 encode string to md5 string
+// php md5
+func MD5(str string) string {
+	data := md5.Sum([]byte(str))
+
+	// 将 []byte 转换为 32字符十六进制数
+	return fmt.Sprintf("%x", data)
+}
+
+// Sha1 encode string to sha1 string
+// php sha1
+func Sha1(str string) string {
+	hash := sha1.New()
+	hash.Write([]byte(str))
+	return hex.EncodeToString(hash.Sum(nil))
+}
+
+// Base64Encode encode string to base64 string
+// php base64_encode
+func Base64Encode(str string) string {
+	return base64.StdEncoding.EncodeToString([]byte(str))
+}
+
+// Base64Decode decode base64 string to normal string
+// php base64_decode
+func Base64Decode(str string) (string, error) {
+	data, err := base64.StdEncoding.DecodeString(str)
+	if err != nil {
+		return "", err
+	}
+
+	return hex.EncodeToString(data), nil
+}

+ 18 - 0
util/crypto/crypto_test.go

@@ -0,0 +1,18 @@
+package crypto
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestCrypto(t *testing.T) {
+	str := "你好啊"
+
+	fmt.Println("MD5: ", MD5(str))
+	fmt.Println("Sha1: ", Sha1(str))
+
+	base64Str := Base64Encode(str)
+	fmt.Println("Base64Encode: ", base64Str)
+	bStr, err := Base64Decode(base64Str)
+	fmt.Println("Base64Decode: ", bStr, ", Error: ", err)
+}

+ 53 - 0
util/datetime/datetime.go

@@ -0,0 +1,53 @@
+package datetime
+
+import (
+	"fmt"
+	"time"
+)
+
+// Time get timestamp
+// php time
+func Time() int64 {
+	return time.Now().Unix()
+}
+
+// StrToTime parse time_string to timestamp
+// php strtotime
+func StrToTime(timeStr, layout string) (int64, error) {
+	t, err := time.Parse(layout, timeStr)
+	if err != nil {
+		return 0, err
+	}
+	return t.Unix(), nil
+}
+
+// Date parse timestamp to time string
+// php date
+func Date(timestamp int64, layout string) string {
+	return time.Unix(timestamp, 0).Format(layout)
+}
+
+// CheckDate check date is right
+// php checkdate
+func CheckDate(month, day, year uint) bool {
+	layout := "2006-01-02"
+	timeStr := fmt.Sprintf("%d-%02d-%02d", year, month, day)
+	_, err := time.Parse(layout, timeStr)
+	if err != nil {
+		return false
+	}
+
+	return true
+}
+
+// Sleep pauses the current goroutine for seconds
+// php sleep
+func Sleep(seconds int64) {
+	time.Sleep(time.Duration(seconds) * time.Second)
+}
+
+// Usleep pauses the current goroutine for microseconds
+// php usleep
+func Usleep(microseconds int64) {
+	time.Sleep(time.Duration(microseconds) * time.Microsecond)
+}

+ 23 - 0
util/datetime/datetime_test.go

@@ -0,0 +1,23 @@
+package datetime
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestDatetime(t *testing.T) {
+	fmt.Println("Time: ", Time())
+
+	timeStr := "2018-01-18 08:08:08"
+	layout := "2006-01-02 15:04:05"
+	timestamp, err := StrToTime(timeStr, layout)
+	fmt.Println("StrToTime: ", timestamp, ", error: ", err)
+
+	fmt.Println("Date: ", Date(timestamp, layout))
+
+	Usleep(3000)
+	fmt.Println("....")
+	Sleep(3)
+
+	fmt.Println("CheckDate: ", CheckDate(2, 29, 2021))
+}

+ 119 - 0
util/file/file.go

@@ -0,0 +1,119 @@
+package file
+
+import (
+	"errors"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+)
+
+// Basename get file basename
+// php basename
+func Basename(fPath string) string {
+	return filepath.Base(fPath)
+}
+
+// Dirname get file dirname
+// php dirname
+func Dirname(fPath string) string {
+	return filepath.Dir(fPath)
+}
+
+// Filesize get file size
+// php filesize
+func Filesize(fPath string) int64 {
+	fInfo, err := os.Stat(fPath)
+	if err != nil && os.IsNotExist(err) {
+		return 0
+	}
+
+	fm := fInfo.Mode()
+	if fm.IsDir() {
+		return 0
+	}
+
+	return fInfo.Size()
+}
+
+// PathInfo get file all info
+// php pathinfo
+func PathInfo(fPath string) (map[string]interface{}, error) {
+	fInfoMap := map[string]interface{}{}
+	fInfo, err := os.Stat(fPath)
+	if err != nil && os.IsNotExist(err) {
+		return fInfoMap, err
+	}
+
+	fm := fInfo.Mode()
+	if fm.IsDir() {
+		return fInfoMap, errors.New("fPath is dirname")
+	}
+
+	fInfoMap["dirname"] = filepath.Dir(fPath)
+	fInfoMap["filename"] = fInfo.Name()
+	fInfoMap["size"] = fInfo.Size()
+	fInfoMap["extension"] = path.Ext(fPath)
+	// fInfoMap["mode"] = fInfo.Mode()
+	return fInfoMap, nil
+}
+
+// FileExists check file is exists
+// php file_exists
+func FileExists(fName string) bool {
+	_, err := os.Stat(fName)
+	if err != nil && os.IsNotExist(err) {
+		return false
+	}
+
+	return true
+}
+
+// IsDir fileName is dir
+// php is_dir
+func IsDir(fName string) bool {
+	fInfo, err := os.Stat(fName)
+	if err != nil {
+		return false
+	}
+
+	fm := fInfo.Mode()
+	return fm.IsDir()
+}
+
+// IsFile fileName is file
+// php is_file
+func IsFile(fName string) bool {
+	fInfo, err := os.Stat(fName)
+	if err != nil && os.IsNotExist(err) {
+		return false
+	}
+
+	fm := fInfo.Mode()
+	return !fm.IsDir()
+}
+
+// FileGetContents read file content
+//php file_get_contents
+func FileGetContents(fName string) (string, error) {
+	data, err := ioutil.ReadFile(fName)
+	return string(data), err
+}
+
+// FilePutContents write file content
+// php file_put_contents
+func FilePutContents(filename string, data string, mode os.FileMode) error {
+	return ioutil.WriteFile(filename, []byte(data), mode)
+}
+
+// Chmod chmod_file mode
+// php chmod
+func Chmod(filename string, mode os.FileMode) bool {
+	return os.Chmod(filename, mode) == nil
+}
+
+// Chown chown_file
+// php chown
+func Chown(filename string, uid, gid int) bool {
+	return os.Chown(filename, uid, gid) == nil
+}

+ 27 - 0
util/file/file_test.go

@@ -0,0 +1,27 @@
+package file
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestFileFunc(t *testing.T) {
+	fPath := "/home/anyuan/下载/php2go-1.0.0/go.mod"
+	fmt.Println("Basename: ", Basename(fPath))
+
+	fmt.Println("Dirname: ", Dirname(fPath))
+
+	fmt.Println("Filesize: ", Filesize(fPath))
+
+	fInfo, err := PathInfo(fPath)
+	fmt.Println("PathInfo: ", fInfo, err)
+
+	fmt.Println("FileExists: ", FileExists(fPath))
+
+	fPathDir := "/home/anyuan/下载/php2go-1.0.0/"
+	fmt.Println("IsDir - IsFile: ", IsDir(fPathDir), IsFile(fPathDir))
+	fmt.Println("IsFile - IsDir: ", IsFile(fPath), IsDir(fPath))
+
+	fmt.Println(FileGetContents(fPath))
+
+}

BIN
util/go-test.png


+ 1 - 0
util/image/avatar.go

@@ -0,0 +1 @@
+package image

+ 132 - 0
util/image/back.txt

@@ -0,0 +1,132 @@
+
+
+func (p *Poster) Create2(json PosterJson) (string, error) {
+	cachePath := fmt.Sprintf("%s/cache", util.Getwd())
+	image := NewImage(util.Getwd())
+	image.SetCache(cachePath)
+	if json.Use == "bgUrl" {
+		json.Background = ""
+	}
+	if json.Use == "bgc" {
+		json.BackgroundUrl = ""
+	}
+	poster, err := image.Create(json.BackgroundUrl, json.Background, *NewSize(json.Width, json.Height))
+
+	if err != nil {
+		return "", err
+	}
+	defer poster.Destroy()
+
+	itemLen := len(json.Items)
+	destChan := make(chan *PosterMW, itemLen)
+
+	defer close(destChan)
+
+	for index, item := range json.Items {
+		go func(item Items, index int) {
+			var drawErr error
+			var drawMW *gmagick.MagickWand
+			var updated Items
+			if item.Type == "text" {
+				drawMW, updated, drawErr = p.Text(item)
+			}
+			if item.Type == "image" {
+				drawMW, drawErr = image.Resize(item.Value, item.Width, item.Height, item.Angle)
+			}
+			if item.Type == "qrcode" {
+				drawMW, updated, drawErr = p.QRCode(item)
+			}
+			destChan <- &PosterMW{Index: index, Image: drawMW, Item: updated, Err: drawErr}
+		}(item, index)
+	}
+
+	var response = make([]*PosterMW, itemLen)
+	for i := 0; i < len(json.Items); i++ {
+		res := <-destChan
+		response[res.Index] = res
+	}
+	for i := 0; i < len(response); i++ {
+		draw := response[i].Image
+		errDraw := response[i].Err
+		if errDraw != nil {
+			continue
+		}
+		if errComposite := poster.CompositeImage(draw, gmagick.COMPOSITE_OP_OVER, response[i].Item.X, response[i].Item.Y); errComposite != nil {
+			fmt.Println("poster.CompositeImage text err : ", errComposite.Error())
+			continue
+		}
+	}
+
+	destFile := image.CacheFile("preview-poster", poster.GetImageFormat())
+	err = poster.WriteImage(destFile)
+
+	if err != nil {
+		return "", err
+	}
+	return destFile, nil
+}
+
+func (p *Poster) Create1(json PosterJson) (string, error) {
+	cachePath := fmt.Sprintf("%s/cache", util.Getwd())
+	image := NewImage(util.Getwd())
+	image.SetCache(cachePath)
+	if json.Use == "bgUrl" {
+		json.Background = ""
+	}
+	if json.Use == "bgc" {
+		json.BackgroundUrl = ""
+	}
+	poster, err := image.Create(json.BackgroundUrl, json.Background, *NewSize(json.Width, json.Height))
+
+	if err != nil {
+		return "", err
+	}
+	defer poster.Destroy()
+
+	for _, item := range json.Items {
+
+		if item.Type == "text" {
+			drawText, _, errTxt := p.Text(item)
+			if errTxt != nil {
+				fmt.Println("drawText err: ", errTxt.Error())
+				continue
+			}
+			if errComposite := poster.CompositeImage(drawText, gmagick.COMPOSITE_OP_OVER, item.X, item.Y); errComposite != nil {
+				fmt.Println("poster.CompositeImage text err : ", errComposite.Error())
+				continue
+			}
+		}
+
+		if item.Type == "image" {
+			imageMW, errImage := image.Resize(item.Value, item.Width, item.Height, item.Angle)
+			if errImage != nil {
+				continue
+			}
+			if errComposite := poster.CompositeImage(imageMW, gmagick.COMPOSITE_OP_OVER, item.X, item.Y); errComposite != nil {
+				fmt.Println("poster.CompositeImage image err : ", errComposite.Error())
+				continue
+			}
+		}
+
+		if item.Type == "qrcode" {
+			qrWM, _, errQr := p.QRCode(item)
+			if errQr != nil {
+				fmt.Println("Qr err", errQr.Error())
+				continue
+			}
+			if errComposite := poster.CompositeImage(qrWM, gmagick.COMPOSITE_OP_OVER, item.X, item.Y); errComposite != nil {
+				fmt.Println("poster.CompositeImage qr err : ", errComposite.Error())
+				continue
+			}
+		}
+	}
+
+	//util.RandomStr(10);
+	destFile := image.CacheFile("preview-poster", poster.GetImageFormat())
+	err = poster.WriteImage(destFile)
+
+	if err != nil {
+		return "", err
+	}
+	return destFile, nil
+}

+ 38 - 0
util/image/cache.go

@@ -0,0 +1,38 @@
+package image
+
+import (
+	"crypto/md5"
+	"fmt"
+	"icloudapp.cn/tools/util"
+	fileObj "icloudapp.cn/tools/util/file"
+	"os"
+	"strings"
+)
+
+type Cache struct {
+	Path string
+}
+
+func NewCache(path string) *Cache {
+	return &Cache{Path: path}
+}
+
+// SetCache 设置缓存目录
+func (c *Cache) SetCache(path string) {
+	c.Path = path
+}
+
+// CacheFile Cache 获取缓存文件名
+func (c *Cache) CacheFile(file string, format string) string {
+	if !fileObj.IsDir(c.Path) {
+		if err := os.Mkdir(c.Path, 0755); err != nil {
+			panic(err.Error())
+		}
+	}
+	if file == "" {
+		file = util.RandomStr(10)
+	}
+	destName := fmt.Sprintf("%x", md5.Sum([]byte(file)))
+	destFile := fmt.Sprintf("%s/%s.%s", c.Path, destName, strings.ToLower(format))
+	return destFile
+}

+ 101 - 0
util/image/color.go

@@ -0,0 +1,101 @@
+package image
+
+import (
+	"fmt"
+	"github.com/gographics/gmagick"
+	"icloudapp.cn/tools/util"
+	"strconv"
+	"strings"
+)
+
+type Color struct {
+	Red   uint32
+	Green uint32
+	Blue  uint32
+	Alpha uint32
+
+	R uint8
+	G uint8
+	B uint8
+	A uint8
+}
+
+func NewColor(color string) *Color {
+	return &Color{}
+}
+
+// PaddingColorTo8 将color补充到8位,最后两位位Alpha
+func PaddingColorTo8(color string) string {
+	hexColor := strings.Replace(color, "#", "", 1)
+	strRune := []rune(hexColor)
+	var strBuild strings.Builder
+	if len(hexColor) == 3 {
+		for i := 0; i < 3; i++ {
+			strBuild.WriteString(string(strRune[i]))
+			strBuild.WriteString(string(strRune[i]))
+		}
+		strBuild.WriteString("00")
+		hexColor = strBuild.String()
+	}
+	if len(hexColor) > 8 {
+		hexColor = util.CutString(hexColor, 0, 8)
+	}
+	strBuild.WriteString(hexColor)
+	if len(hexColor) < 8 {
+		padding := 8 - len(hexColor)
+		for i := 0; i < padding; i++ {
+			strBuild.WriteString("0")
+		}
+	}
+	return strBuild.String()
+}
+
+// Alpha rgb alpha  0 透明 -> 1 不透明
+func Alpha(color string) (string, Color, float64) {
+	// 0 透明 -> 1 不透明
+	opacity := 1.0
+	hexColor := PaddingColorTo8(color)
+
+	color = util.CutString(hexColor, 0, 6)
+	alpha := util.CutString(hexColor, 6, 8)
+	hexAlpha, _ := strconv.ParseInt(alpha, 16, 10)
+	opacity = float64(int((float64(hexAlpha)/255)*100)) / 100
+	color = fmt.Sprintf("#%s", color)
+
+	var rgb Color
+	rgbRune := []rune(hexColor)
+	r, _ := strconv.ParseInt(string(rgbRune[0:2]), 16, 10)
+	rgb.Red = uint32(r)
+
+	R, _ := strconv.ParseUint(string(rgbRune[0:2]), 16, 8)
+	rgb.R = uint8(R)
+
+	g, _ := strconv.ParseInt(string(rgbRune[2:4]), 16, 10)
+	rgb.Green = uint32(g)
+
+	G, _ := strconv.ParseUint(string(rgbRune[2:4]), 16, 8)
+	rgb.G = uint8(G)
+
+	b, _ := strconv.ParseInt(string(rgbRune[4:6]), 16, 10)
+	rgb.Blue = uint32(b)
+
+	B, _ := strconv.ParseUint(string(rgbRune[4:6]), 16, 8)
+	rgb.G = uint8(B)
+
+	a, _ := strconv.ParseInt(string(rgbRune[6:8]), 16, 10)
+	rgb.Alpha = uint32(a / 255)
+
+	A, _ := strconv.ParseUint(string(rgbRune[6:8]), 16, 8)
+	rgb.A = uint8(A)
+
+	return color, rgb, opacity
+}
+
+// Pixel PixelWand
+func Pixel(color string) *gmagick.PixelWand {
+	destColor, _, opacity := Alpha(color)
+	g := gmagick.NewPixelWand()
+	g.SetColor(destColor)
+	g.SetOpacity(opacity)
+	return g
+}

+ 18 - 0
util/image/color_test.go

@@ -0,0 +1,18 @@
+package image
+
+import (
+	"fmt"
+	"image/color"
+	"testing"
+)
+
+func TestAlpha(t *testing.T) {
+	fmt.Println(color.RGBA{R: 0x33, G: 0x33, B: 0x66, A: 0xff})
+	fmt.Println(Alpha("#e456499c"))
+
+	fmt.Println(color.RGBA{R: 0x00, G: 0x00, B: 0x00, A: 0x00})
+	fmt.Println(Alpha("#00000000"))
+
+	fmt.Println(color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
+	fmt.Println(Alpha("#ffffffff"))
+}

+ 41 - 0
util/image/entity.go

@@ -0,0 +1,41 @@
+package image
+
+type PosterJson struct {
+	Id            string  `json:"id"`
+	Name          string  `json:"name"`
+	Width         uint    `json:"w"`
+	Height        uint    `json:"h"`
+	Use           string  `json:"use"`
+	Background    string  `json:"bgc"`
+	BackgroundUrl string  `json:"bgUrl"`
+	Type          string  `json:"type"`
+	Quality       int     `json:"quality"`
+	Scale         float64 `json:"scale"`
+	Key           string  `json:"key"`
+	Items         []Items `json:"items"`
+}
+type Items struct {
+	Type        string  `json:"t"`
+	Remark      string  `json:"name"`
+	UUID        string  `json:"uuid"`
+	X           int     `json:"x"`
+	Y           int     `json:"y"`
+	Width       uint    `json:"w"`
+	Height      uint    `json:"h"`
+	ZIndex      int     `json:"z"`
+	Angle       int     `json:"angle"`
+	Size        int     `json:"s"`
+	Color       string  `json:"c"`
+	Background  string  `json:"bgc"`
+	Value       string  `json:"v"`
+	Name        string  `json:"vd"`
+	Font        string  `json:"fn"`
+	St          int     `json:"st"`
+	Active      bool    `json:"active"`
+	Lock        bool    `json:"lock"`
+	AspectRatio bool    `json:"aspectRatio"`
+	P           int     `json:"p,omitempty"`
+	Align       *string `json:"a,omitempty"`
+	Wrap        *bool   `json:"wrap,omitempty"`
+	Bius        *string `json:"bius,omitempty"`
+}

+ 1 - 0
util/image/font.go

@@ -0,0 +1 @@
+package image

+ 191 - 0
util/image/image.go

@@ -0,0 +1,191 @@
+package image
+
+import (
+	"fmt"
+	"github.com/gographics/gmagick"
+	"icloudapp.cn/tools/util"
+	fileObj "icloudapp.cn/tools/util/file"
+)
+
+type Image struct {
+	Cache
+	Path string
+}
+
+func NewImage(path string) *Image {
+	return &Image{
+		Path: path,
+	}
+}
+
+func (i *Image) Create(file, background string, size Size) (*gmagick.MagickWand, error) {
+	if file != "" {
+		file = fmt.Sprintf("%s/%s", i.Path, file)
+		if !fileObj.IsFile(file) {
+			return nil, util.NewError(fmt.Sprintf("文件 %s 不存在", file))
+		}
+	}
+	imageMW := gmagick.NewMagickWand()
+
+	if file != "" {
+		if err := imageMW.ReadImage(file); err != nil {
+			return nil, util.NewError(fmt.Sprintf("imageMW.ReadImageBlob:%s", err.Error()))
+		}
+	} else {
+		if err := imageMW.ReadImageBlob(util.TransparentPNG()); err != nil {
+			return nil, util.NewError(fmt.Sprintf("imageMW.ReadImageBlob:%s", err.Error()))
+		}
+	}
+	fmt.Println("size", size)
+	if err := imageMW.SetImageFormat("PNG"); err != nil {
+		return nil, util.NewError("imageMW.SetImageFormat", err.Error())
+	}
+	if err := imageMW.ResizeImage(size.Width, size.Height, gmagick.FILTER_LANCZOS, 1); err != nil {
+		return nil, util.NewError("imageMW.SetSize", err.Error())
+	}
+
+	if background != "" {
+		if err := imageMW.SetImageBackgroundColor(Pixel(background)); err != nil {
+			return nil, util.NewError(fmt.Sprintf("imageMW.SetImageBackgroundColor:%s", err.Error()))
+		}
+	}
+
+	return imageMW, nil
+}
+
+func (i *Image) Thumb(file string, size *Size) (string, error) {
+	if !fileObj.IsFile(file) {
+		return "", util.NewError(fmt.Sprintf("文件 %s 不存在", file))
+	}
+
+	mw := gmagick.NewMagickWand()
+	defer mw.Destroy()
+
+	var err error
+
+	if err = mw.ReadImage(file); err != nil {
+		return "", err
+	}
+
+	width := mw.GetImageWidth()
+	height := mw.GetImageHeight()
+
+	imageScale := NewScale(width, height, 1.0)
+	scale := imageScale.MinRatio(size.Width, size.Height)
+
+	if err = mw.ResizeImage(scale.Width, scale.Height, gmagick.FILTER_LANCZOS, 1); err != nil {
+		return "", err
+	}
+	//fmt.Println("DisplayImage:", os.Getenv("DISPLAY"))
+	//err = mw.DisplayImage(os.Getenv("DISPLAY"))
+
+	destFile := i.CacheFile(file, mw.GetImageFormat())
+	if err = mw.WriteImage(destFile); err != nil {
+		return "", err
+	}
+	return destFile, nil
+}
+
+func (i *Image) Cut(file string, size *Size, position string) (string, error) {
+	if position == "" {
+		position = "left"
+	}
+
+	mw := gmagick.NewMagickWand()
+	defer mw.Destroy()
+	var err error
+	err = mw.ReadImage(file)
+
+	if err != nil {
+		return "", err
+	}
+	sourceWidth := mw.GetImageWidth()
+	sourceHeight := mw.GetImageHeight()
+	imageScale := NewScale(sourceWidth, sourceHeight, 1.0)
+
+	scale := imageScale.MaxRatio(size.Width, size.Height)
+
+	if err = mw.ResizeImage(scale.Width, scale.Height, gmagick.FILTER_LANCZOS, 1); err != nil {
+		return "", err
+	}
+	x, y, width, height := i.Position(position, scale.Width, scale.Height, size)
+
+	if err = mw.CropImage(width, height, x, y); err != nil {
+		return "", err
+	}
+
+	destFile := i.CacheFile(file, mw.GetImageFormat())
+	if err = mw.WriteImage(destFile); err != nil {
+		return "", err
+	}
+
+	return destFile, nil
+}
+
+func (i *Image) Resize(image string, width, height uint, angle int) (*gmagick.MagickWand, error) {
+	imageMV := gmagick.NewMagickWand()
+
+	if err := imageMV.ReadImage(image); err != nil {
+		return nil, util.NewError("Resize", "ReadImage", err.Error())
+	}
+
+	if err := imageMV.ResizeImage(width, height, gmagick.FILTER_LANCZOS, 1); err != nil {
+		return nil, util.NewError("Resize", "ResizeImage", err.Error())
+	}
+
+	if err := imageMV.SetSize(width, height); err != nil {
+		return nil, util.NewError("Resize", "SetSize", err.Error())
+	}
+
+	return imageMV, nil
+}
+
+// 计算 裁切的x、y及宽高
+func (i *Image) Position(position string, width, height uint, size *Size) (dx, dy int, dWidth, dHeight uint) {
+	x := 0
+	y := 0
+	scale := 1.0
+
+	if width < size.Width {
+		scale = CustomScale(width, size.Width)
+		width = size.Width
+	}
+	if height < size.Height {
+		scale = CustomScale(height, size.Height)
+		height = size.Height
+	}
+	paddingLeft := width - size.Width
+	paddingTop := height - size.Height
+
+	if position == "right" {
+		x = int(paddingLeft)
+	}
+	if position == "center" {
+		x = int(paddingLeft / 2)
+		y = int(paddingTop / 2)
+	}
+
+	if position == "right|center" {
+		x = int(paddingLeft)
+		y = int(paddingTop / 2)
+	}
+	if position == "left|center" {
+		y = int(paddingTop / 2)
+	}
+	return x, y, uint(float64(size.Width) * scale), uint(float64(size.Height) * scale)
+}
+
+// Cache 获取缓存文件名
+/*func (i *Image) CacheFile(file string, format string) string {
+	if !fileObj.IsDir(i.Cache) {
+		if err := os.Mkdir(i.Cache, 0755); err != nil {
+			panic(err.Error())
+		}
+	}
+	if file == "" {
+		file = util.RandomStr(10)
+	}
+	destName := fmt.Sprintf("%x", md5.Sum([]byte(file)))
+	destFile := fmt.Sprintf("%s/%s.%s", i.Cache, destName, strings.ToLower(format))
+	return destFile
+}*/

+ 18 - 0
util/image/point.go

@@ -0,0 +1,18 @@
+package image
+
+type Point struct {
+	X float64
+	Y float64
+}
+
+func NewPoint(x, y float64) *Point {
+	return &Point{x, y}
+}
+
+func (p *Point) SetX(x float64) {
+	p.X = x
+}
+
+func (p *Point) SetY(y float64) {
+	p.Y = y
+}

+ 124 - 0
util/image/poster.go

@@ -0,0 +1,124 @@
+package image
+
+import (
+	"context"
+	"fmt"
+	"github.com/gographics/gmagick"
+	"icloudapp.cn/tools/util"
+	"strings"
+)
+
+type Poster struct {
+	ctx context.Context
+}
+
+func NewPoster(ctx context.Context) *Poster {
+	return &Poster{ctx: ctx}
+}
+
+func (p *Poster) Text(item Items) (*gmagick.MagickWand, Items, error) {
+	var updated Items
+	item.Value = strings.Replace(item.Value, "  ", " ", -1)
+	txt := NewText(item.Font, float64(item.Size), "")
+	txt.SetText(item.Value)
+	txt.SetColor(item.Color)
+	txt.SetBackground(item.Background)
+	txt.SetSize(item.Width, item.Height)
+	txt.SetPoint(float64(item.X), float64(item.Y))
+	txt.SetAlign(*item.Align)
+	txt.SetBius(*item.Bius)
+	txt.SetAngle(int64(item.Angle))
+	if *item.Wrap {
+		txt.SetWrap(*item.Wrap)
+	}
+	mw, err := txt.Create()
+
+	updated = item
+	updated.X = int(txt.Coordinate.X)
+	updated.Y = int(txt.Coordinate.Y)
+	updated.Width = txt.ViewSize.Width
+	updated.Height = txt.ViewSize.Height
+	return mw, updated, err
+}
+
+func (p *Poster) QRCode(item Items) (*gmagick.MagickWand, Items, error) {
+	var updated Items
+	cachePath := fmt.Sprintf("%s/cache", util.Getwd())
+	qr := NewQR()
+	qr.SetValue(item.Value)
+	qr.SetSize(item.Width, item.Height)
+	qr.SetBackgroundColor(item.Background)
+	qr.SetFrontColor(item.Color)
+	qr.SetCache(cachePath)
+	mw, err := qr.Create()
+
+	updated = item
+	return mw, updated, err
+}
+
+type PosterMW struct {
+	Index int
+	Image *gmagick.MagickWand
+	Item  Items
+	Err   error
+}
+
+func (p *Poster) Create(json PosterJson) (*gmagick.MagickWand, error) {
+	cachePath := fmt.Sprintf("%s/cache", util.Getwd())
+	image := NewImage(util.Getwd())
+	image.SetCache(cachePath)
+	if json.Use == "bgUrl" {
+		json.Background = ""
+	}
+	if json.Use == "bgc" {
+		json.BackgroundUrl = ""
+	}
+	poster, err := image.Create(json.BackgroundUrl, json.Background, *NewSize(json.Width, json.Height))
+
+	if err != nil {
+		return nil, err
+	}
+
+	itemLen := len(json.Items)
+	destChan := make(chan *PosterMW, itemLen)
+
+	defer close(destChan)
+
+	for index, item := range json.Items {
+		go func(item Items, index int) {
+			var drawErr error
+			var drawMW *gmagick.MagickWand
+			if item.Type == "text" {
+				drawMW, item, drawErr = p.Text(item)
+			}
+			if item.Type == "image" {
+				drawMW, drawErr = image.Resize(item.Value, item.Width, item.Height, item.Angle)
+			}
+			if item.Type == "qrcode" {
+				drawMW, item, drawErr = p.QRCode(item)
+			}
+			destChan <- &PosterMW{Index: index, Image: drawMW, Item: item, Err: drawErr}
+		}(item, index)
+	}
+
+	var response = make([]*PosterMW, itemLen)
+	for i := 0; i < len(json.Items); i++ {
+		res := <-destChan
+		response[res.Index] = res
+	}
+	for i := 0; i < len(response); i++ {
+		draw := response[i].Image
+		errDraw := response[i].Err
+		if errDraw != nil {
+			continue
+		}
+		if response[i].Item.Type == "text" {
+			fmt.Println(fmt.Sprintf("response[i].Item : %+v", response[i].Item))
+		}
+		if errComposite := poster.CompositeImage(draw, gmagick.COMPOSITE_OP_OVER, response[i].Item.X, response[i].Item.Y); errComposite != nil {
+			fmt.Println("poster.CompositeImage text err : ", errComposite.Error())
+			continue
+		}
+	}
+	return poster, nil
+}

+ 101 - 0
util/image/qr.go

@@ -0,0 +1,101 @@
+package image
+
+import (
+	"fmt"
+	"github.com/gographics/gmagick"
+	"github.com/skip2/go-qrcode"
+	"icloudapp.cn/tools/util"
+	"image/color"
+	"sync"
+)
+
+type QR struct {
+	Cache
+	Value           string
+	FrontColor      string
+	BackgroundColor string
+	QRSize          *Size
+}
+
+var QROnce sync.Once
+var QRInstance *QR
+
+func NewQR() *QR {
+	QROnce.Do(func() {
+		QRInstance = &QR{}
+	})
+	return QRInstance
+}
+
+func (q *QR) SetCache(path string) {
+	q.Cache.SetCache(path)
+}
+
+func (q *QR) SetValue(value string) {
+	q.Value = value
+}
+
+func (q *QR) SetFrontColor(frontColor string) {
+	q.FrontColor = frontColor
+}
+
+func (q *QR) SetBackgroundColor(backgroundColor string) {
+	q.BackgroundColor = backgroundColor
+}
+func (q *QR) SetSize(width, height uint) {
+	q.QRSize = NewSize(width, height)
+}
+func (q *QR) Create() (*gmagick.MagickWand, error) {
+	//file := fmt.Sprintf("%s.png", util.Sign(q.Value, "QR"))
+	file := q.CacheFile(util.Sign(q.Value, "QR"), "png")
+	if q.BackgroundColor == "" {
+		q.BackgroundColor = "#000"
+	}
+	if q.FrontColor == "" {
+		q.FrontColor = "#fff"
+	}
+	_, background, _ := Alpha(q.BackgroundColor)
+	_, front, _ := Alpha(q.FrontColor)
+
+	background.A = 1
+	backgroundColor := color.RGBA{R: background.R, G: background.G, B: background.B, A: background.A}
+
+	front.A = 1
+	frontColor := color.RGBA{R: front.R, G: front.G, B: front.B, A: front.A}
+
+	if q.QRSize.Width == 0 {
+		q.QRSize.Width = 200
+	}
+
+	qr, err := qrcode.New(q.Value, qrcode.Medium)
+	if err != nil {
+		return nil, err
+	}
+
+	qr.Image(int(q.QRSize.Width))
+	qr.DisableBorder = true
+
+	qr.ForegroundColor = color.RGBA{R: 0x33, G: 0x33, B: 0x66, A: 0xff}
+	qr.BackgroundColor = color.RGBA{R: 0xef, G: 0xef, B: 0xef, A: 0xff}
+
+	//qr.ForegroundColor = frontColor
+	//qr.BackgroundColor = backgroundColor
+	fmt.Println("background", backgroundColor, frontColor)
+
+	if err = qr.WriteFile(int(q.QRSize.Width), file); err != nil {
+		return nil, util.NewError("qrcode.WriteColorFile", err.Error())
+	}
+	/*if err := qrcode.WriteColorFile(q.Value, qrcode.Medium, int(q.QRSize.Width), color.White, color.Black, file); err != nil {
+		return nil, util.NewError("qrcode.WriteColorFile", err.Error())
+	}*/
+
+	qrWM := gmagick.NewMagickWand()
+	if err = qrWM.ReadImage(file); err != nil {
+		return nil, util.NewError("qrWM.SetImageFilename", err.Error())
+	}
+	if err = qrWM.SetSize(q.QRSize.Width, q.QRSize.Height); err != nil {
+		return nil, util.NewError("qrWM.SetSize", err.Error())
+	}
+
+	return qrWM, nil
+}

+ 44 - 0
util/image/scale.go

@@ -0,0 +1,44 @@
+package image
+
+type Scale struct {
+	Width  uint
+	Height uint
+	Scale  float64
+}
+
+func NewScale(width uint, height uint, scale float64) *Scale {
+	return &Scale{width, height, scale}
+}
+
+// MinRatio 获取缩放比例,等比缩放,以短边为准
+func (s Scale) MinRatio(width, height uint) Scale {
+	scale := 1.0
+	if s.Width/s.Height > width/height {
+		if s.Height > height {
+			scale = CustomScale(height, s.Height)
+		}
+	} else {
+		if s.Width > width {
+			scale = CustomScale(width, s.Width)
+		}
+	}
+	width = uint(scale * float64(s.Width))
+	height = uint(scale * float64(s.Height))
+
+	return Scale{Width: width, Height: height, Scale: scale}
+}
+
+// MaxRatio 裁切,以长边为准
+func (s Scale) MaxRatio(width, height uint) Scale {
+	scale := 1.0
+	if s.Width/s.Height > width/height {
+		scale = CustomScale(height, s.Height)
+	} else {
+		scale = CustomScale(width, s.Width)
+	}
+
+	width = uint(scale * float64(s.Width))
+	height = uint(scale * float64(s.Height))
+
+	return Scale{Width: width, Height: height, Scale: scale}
+}

+ 18 - 0
util/image/size.go

@@ -0,0 +1,18 @@
+package image
+
+type Size struct {
+	Width  uint
+	Height uint
+}
+
+func NewSize(width, height uint) *Size {
+	return &Size{width, height}
+}
+
+func (s *Size) SetWidth(width uint) {
+	s.Width = width
+}
+
+func (s *Size) SetHeight(height uint) {
+	s.Height = height
+}

+ 308 - 0
util/image/text.go

@@ -0,0 +1,308 @@
+package image
+
+import (
+	"fmt"
+	"github.com/gographics/gmagick"
+	"icloudapp.cn/tools/util"
+	"strings"
+)
+
+type Text struct {
+	//字体内容
+	Text string
+	//字体
+	Font string
+	//字体大小
+	FontSize float64
+	//字体格式
+	FontFamily string
+	//文字颜色
+	Color string
+	//文字区域背景颜色
+	Background string
+	//文字背景色
+	UnderColor string
+	//下划线
+	UnderLine bool
+	//删除线
+	Strike bool
+	//斜体
+	Italic bool
+	//粗体
+	Bold bool
+	//字体间隙
+	WordSpace int
+	//是否自动换行
+	Wrap bool
+	//对齐方式 left center right
+	Align string
+	//坐标
+	Coordinate *Point
+	//文字区域大小
+	ViewSize *Size
+
+	Angle int64
+}
+
+// NewText text对象
+func NewText(font string, fontSize float64, fontFamily string) *Text {
+	t := &Text{
+		Font:       font,
+		FontSize:   fontSize,
+		FontFamily: fontFamily,
+	}
+	t.Clean()
+	return t
+}
+
+func (t *Text) SetText(text string) {
+	t.Text = text
+}
+
+func (t *Text) SetFont(font string) {
+	t.Font = font
+}
+
+func (t *Text) SetFontSize(fontSize float64) {
+	t.FontSize = fontSize
+}
+
+func (t *Text) SetFontFamily(fontFamily string) {
+	t.FontFamily = fontFamily
+}
+
+func (t *Text) SetColor(color string) {
+	t.Color = color
+}
+
+func (t *Text) SetBackground(background string) {
+	t.Background = background
+}
+
+func (t *Text) SetUnderColor(underColor string) {
+	t.UnderColor = underColor
+}
+
+func (t *Text) SetUnderLine(underLine bool) {
+	t.UnderLine = underLine
+}
+
+func (t *Text) SetStrike(strike bool) {
+	t.Strike = strike
+}
+
+func (t *Text) SetBius(bius string) {
+	t.SetItalic(false)
+	t.SetBold(false)
+	t.SetUnderLine(false)
+	t.SetStrike(false)
+	for _, v := range bius {
+		if string(v) == "i" {
+			t.SetItalic(true)
+		}
+		if string(v) == "b" {
+			t.SetBold(true)
+		}
+		if string(v) == "u" {
+			t.SetUnderLine(true)
+		}
+		if string(v) == "s" {
+			t.SetStrike(true)
+		}
+	}
+
+}
+
+func (t *Text) SetItalic(italic bool) {
+	t.Italic = italic
+}
+
+func (t *Text) SetBold(bold bool) {
+	t.Bold = bold
+}
+
+func (t *Text) SetWordSpace(wordSpace int) {
+	t.WordSpace = wordSpace
+}
+
+func (t *Text) SetWrap(wrap bool) {
+	t.Wrap = wrap
+}
+
+func (t *Text) SetAlign(align string) {
+	t.Align = align
+}
+
+func (t *Text) SetPoint(x, y float64) {
+	t.Coordinate = NewPoint(x, y)
+}
+
+func (t *Text) SetSize(w, h uint) {
+	t.ViewSize = NewSize(w, h)
+}
+
+func (t *Text) SetAngle(angle int64) {
+	t.Angle = angle
+}
+
+func (t *Text) Create() (*gmagick.MagickWand, error) {
+	txtDraw := gmagick.NewDrawingWand()
+	if t.Font != "" {
+		txtDraw.SetFont(fmt.Sprintf("upload/fonts/%s", t.Font))
+	}
+
+	if t.FontSize == 0 {
+		t.FontSize = 12
+	}
+
+	txtDraw.SetFontSize(t.FontSize)
+
+	if t.FontFamily != "" {
+		txtDraw.SetFontFamily(t.FontFamily)
+	}
+	if t.Color != "" {
+		tColor := Pixel(t.Color)
+		//tColor.SetOpacity(0)
+		txtDraw.SetFillColor(tColor)
+	}
+
+	if t.Italic || t.Bold || t.UnderLine || t.Strike {
+		txtDraw.SetStrokeColor(Pixel(t.Color))
+	}
+
+	if t.Italic {
+		txtDraw.SkewX(-12)
+	}
+
+	txtDraw.SetFontStyle(gmagick.STYLE_NORMAL)
+	if t.Bold {
+		//txtDraw.SetStrokeWidth(1)
+		txtDraw.SetFontStyle(gmagick.STYLE_OBLIQUE)
+	}
+
+	if t.UnderLine {
+		txtDraw.SetTextDecoration(gmagick.DECORATION_UNDERLINE)
+	}
+
+	if t.Background != "" {
+		tb := Pixel(t.Background)
+		//tb.SetOpacity(0)
+		txtDraw.SetTextUnderColor(tb)
+	}
+	txtDraw.SetTextEncoding("UTF-8")
+
+	if t.Wrap {
+		txtArr := t.WapText(txtDraw, t.Text, t.Wrap)
+		fmt.Println("txtArr", txtArr)
+	}
+
+	//透明图片
+	transPNG := util.TransparentPNG()
+	subMw := gmagick.NewMagickWand()
+
+	if err := subMw.ReadImageBlob(transPNG); err != nil {
+		return nil, util.NewError("subMw.ReadImageBlob", err.Error())
+	}
+
+	if err := subMw.SetImageFormat("PNG"); err != nil {
+		return nil, util.NewError("subMw.SetImageFormat", err.Error())
+	}
+	if err := subMw.ResizeImage(t.ViewSize.Width, t.ViewSize.Height, gmagick.FILTER_LANCZOS, 1); err != nil {
+		return nil, util.NewError("subMw.ResizeImage", err.Error())
+	}
+
+	metrics := subMw.QueryFontMetrics(txtDraw, t.Text)
+	alignX := 0.0
+	if t.Align == "center" {
+		alignX = float64(int(((float64(t.ViewSize.Width)-metrics.TextWidth)/2)*100)) / 100
+	}
+
+	if t.Align == "right" {
+		alignX = float64(t.ViewSize.Width) - metrics.TextWidth
+	}
+
+	if t.Strike {
+		txtDraw.SetStrokeDashOffset(metrics.TextHeight)
+		txtDraw.SetTextDecoration(gmagick.DECORATION_LINE_THROUGH)
+	}
+
+	txtDraw.Annotation(alignX, metrics.Ascender, t.Text)
+	if t.Background != "" {
+		if err := subMw.SetImageBackgroundColor(Pixel(t.Background)); err != nil {
+			return nil, util.NewError("subMw.SetImageBackgroundColor", err.Error())
+		}
+	}
+	if err := subMw.DrawImage(txtDraw); err != nil {
+		return nil, util.NewError("subMw.DrawImage", err.Error())
+	}
+
+	parentMw := gmagick.NewMagickWand()
+
+	if err := parentMw.ReadImageBlob(transPNG); err != nil {
+		return nil, util.NewError("parentMw.ReadImageBlob", err.Error())
+	}
+	if err := parentMw.ResizeImage(t.ViewSize.Width, t.ViewSize.Height, gmagick.FILTER_LANCZOS, 1); err != nil {
+		return nil, util.NewError("subMw.ResizeImage", err.Error())
+	}
+	if err := parentMw.SetSize(t.ViewSize.Width, t.ViewSize.Height); err != nil {
+		return nil, util.NewError("subMw.SetSize", err.Error())
+	}
+
+	if err := parentMw.CompositeImage(subMw, gmagick.COMPOSITE_OP_OVER, 0, 0); err != nil {
+		return nil, util.NewError("parentMw.CompositeImage ", err.Error())
+	}
+	pixBackground := Pixel("#ffffffff")
+	if err := parentMw.RotateImage(pixBackground, float64(t.Angle)); err != nil {
+		return nil, util.NewError("subMw.RotateImage", err.Error())
+	}
+	//由于图片旋转,图片的长宽和坐标位置发生变化,所以需要重置坐标位置
+	if t.Angle > 0 {
+		width := parentMw.GetImageWidth()
+		height := parentMw.GetImageHeight()
+		y := t.Coordinate.Y - float64(height)/3 + float64(t.ViewSize.Height)/2
+		x := t.Coordinate.X - float64(width)/2 + float64(t.ViewSize.Width)/2
+		t.Coordinate.X = x
+		t.Coordinate.Y = y
+		t.ViewSize.Width = width
+		t.ViewSize.Height = height
+	}
+
+	return parentMw, nil
+}
+
+func (t *Text) WapText(dw *gmagick.DrawingWand, text string, autoWrap bool) []string {
+	queryMw := gmagick.NewMagickWand()
+	metrics := queryMw.QueryFontMetrics(dw, t.Text)
+	fmt.Println("metrics", metrics)
+	var lines []string
+	if uint(metrics.TextWidth) <= t.ViewSize.Width {
+		lines = append(lines, text)
+	}
+	if autoWrap {
+		txtRune := []rune(text)
+		var tmpRune []string
+		for i, v := range txtRune {
+			tmpRune = append(tmpRune, string(v))
+			metrics = queryMw.QueryFontMetrics(dw, strings.Join(tmpRune, ""))
+			if uint(metrics.TextWidth) > t.ViewSize.Width {
+				fmt.Println("tmpRune", tmpRune)
+				lines = append(lines, strings.Join(tmpRune[0:i-1], ""))
+				tmpRune = []string{}
+			}
+		}
+		if len(tmpRune) > 0 {
+			lines = append(lines, strings.Join(tmpRune, ""))
+		}
+	}
+	return lines
+}
+
+func (t *Text) Clean() {
+	t.SetColor("#ffffff00")
+	t.SetBackground("#ffffffff")
+	t.SetSize(0, 0)
+	t.SetPoint(0, 0)
+	t.SetAlign("left")
+	t.SetBius("")
+	t.SetAngle(0)
+}

+ 10 - 0
util/image/tools.go

@@ -0,0 +1,10 @@
+package image
+
+type Tool struct {
+}
+
+// CustomScale 获取缩放比例
+func CustomScale(width, dWidth uint) float64 {
+	f := float64(width) / float64(dWidth)
+	return float64(int(f*1000)) / 1000
+}

+ 20 - 0
util/jobs/cron.go

@@ -0,0 +1,20 @@
+package jobs
+
+import (
+	"github.com/robfig/cron"
+)
+
+var mainCron *cron.Cron
+
+func init() {
+	mainCron = cron.New()
+	mainCron.Start()
+}
+
+func InitJobs() {
+	// 每5s钟调度一次,并传参
+	mainCron.AddJob(
+		"*/50 * * * * ?",
+		TestJob{Id: 1, Name: "zhangsan"},
+	)
+}

+ 13 - 0
util/jobs/test_cron.go

@@ -0,0 +1,13 @@
+package jobs
+
+import "fmt"
+
+type TestJob struct {
+	Id   int
+	Name string
+}
+
+func (this TestJob) Run() {
+	fmt.Println(this.Id, this.Name)
+	fmt.Println("testJob1...")
+}

+ 72 - 0
util/math/math.go

@@ -0,0 +1,72 @@
+package math
+
+import (
+	"math"
+	"strconv"
+)
+
+// Abs absolute value
+// php abs
+func Abs(x float64) float64 {
+	return math.Abs(x)
+}
+
+// Round number round
+// php round
+func Round(x float64) int64 {
+	return int64(math.Floor(x + 0.5))
+}
+
+// Floor returns the greatest integer value less than or equal to x
+// php floor
+func Floor(x float64) float64 {
+	return math.Floor(x)
+}
+
+// Ceil returns the least integer value greater than or equal to x
+// php ceil
+func Ceil(x float64) float64 {
+	return math.Ceil(x)
+}
+
+// Max returns the max num in nums
+// php max
+func Max(nums ...float64) float64 {
+	if len(nums) < 2 {
+		panic("nums: the nums length is less than 2")
+	}
+
+	maxNum := nums[0]
+	for i := 1; i < len(nums); i++ {
+		maxNum = math.Max(maxNum, nums[i])
+	}
+
+	return maxNum
+}
+
+// Min returns the min num in nums
+// php min
+func Min(nums ...float64) float64 {
+	if len(nums) < 2 {
+		panic("nums: the nums length is less than 2")
+	}
+
+	minNum := nums[0]
+	for i := 1; i < len(nums); i++ {
+		minNum = math.Min(minNum, nums[i])
+	}
+
+	return minNum
+}
+
+// DecBin Decimal to binary
+// php decbin
+func DecBin(number int64) string {
+	return strconv.FormatInt(number, 2)
+}
+
+// DecHex Decimal to hexadecimal
+// php dechex
+func DecHex(number int64) string {
+	return strconv.FormatInt(number, 16)
+}

+ 24 - 0
util/math/math_test.go

@@ -0,0 +1,24 @@
+package math
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestRand(t *testing.T) {
+	fmt.Println("Abs: ", Abs(-45.6))
+
+	fmt.Println("Round: ", Round(3.1), "  ", Round(3.6))
+
+	fmt.Println("Floor: ", Floor(3.1), "  ", Floor(3.6))
+
+	fmt.Println("Ceil: ", Ceil(3.1), "  ", Ceil(3.6))
+
+	fmt.Println("Max: ", Max(1, 2, 3.5, 4))
+
+	fmt.Println("Min: ", Min(1, 2, 3.5, 4))
+
+	fmt.Println("DecBin: ", DecBin(10))
+
+	fmt.Println("DecHex: ", DecHex(10))
+}

+ 72 - 0
util/string/string.go

@@ -0,0 +1,72 @@
+package string
+
+import (
+	"strconv"
+	"strings"
+	"unicode/utf8"
+)
+
+// StrLen string length
+// php strlen
+func StrLen(s string) int {
+	return len(s)
+}
+
+// MbStrLen string for utf8
+// php mb_strlen
+func MbStrLen(s string) int {
+	return utf8.RuneCountInString(s)
+}
+
+// SubstrCount counts the number of substr in s
+// php substr_count
+func SubstrCount(s, substr string) int {
+	return strings.Count(s, substr)
+}
+
+// Substr get substr
+// php substr
+func Substr(s string, start int, length int) string {
+	return s[start : start+length]
+}
+
+// MbSubstr get substr for utf8
+// php mb_substr
+func MbSubstr(s string, start int, length int) string {
+	strRune := []rune(s)
+	return string(strRune[start : start+length])
+}
+
+// StrPos get first index of substr in s, from "start" index
+// php strpos
+func StrPos(s, substr string, start int) int {
+	return strings.Index(s[start:], substr)
+}
+
+// StrRPos get last index of substr in s, from "start" index
+// php strrpos
+func StrRPos(s, substr string, start int) int {
+	return strings.LastIndex(s[start:], substr)
+}
+
+// StrSplit slices s into all substrings separated by sep
+// php str_split
+func StrSplit(s, sep string) []string {
+	return strings.Split(s, sep)
+}
+
+// UCFirst toUpper the first letter
+// php ucfirst
+func UCFirst(s string) string {
+	firstChart := strings.ToUpper(s[0:1])
+	return firstChart + s[1:]
+}
+
+// Convert to int64
+func ConvertInt64(s string) int64 {
+	cInt, err := strconv.ParseInt(s, 10, 64)
+	if err != nil {
+		return 0
+	}
+	return cInt
+}

+ 26 - 0
util/string/string_test.go

@@ -0,0 +1,26 @@
+package string
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestString(t *testing.T) {
+	str := "中国88-china-88"
+	fmt.Println("StrLen: ", StrLen(str))
+	fmt.Println("MbStrLen: ", MbStrLen(str))
+
+	haystack := "I love Shanghai. Shanghai is the biggest city in china."
+	needle := "Shanghai"
+	fmt.Println("SubstrCount: ", SubstrCount(haystack, needle))
+
+	fmt.Println("Substr: ", Substr(str, 1, 2))
+	fmt.Println("MbSubstr: ", MbSubstr(str, 1, 2))
+
+	fmt.Println("StrPos: ", StrPos(str, "-", 2))
+	fmt.Println("StrRPos: ", StrRPos(str, "-", 9))
+
+	fmt.Println("StrSplit: ", StrSplit(str, "-"))
+
+	fmt.Println("UCFirst: ", UCFirst("abcdefg"))
+}

+ 191 - 0
util/tools.go

@@ -0,0 +1,191 @@
+package util
+
+import (
+	"bytes"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/md5"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"icloudapp.cn/tools/entity"
+	"math/rand"
+	"os"
+	"strings"
+	"time"
+)
+
+func Getwd() string {
+	path, err := os.Getwd()
+	if err != nil {
+		return ""
+	}
+	return path
+}
+func RandNum(n int32) int32 {
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	return r.Int31n(n)
+}
+
+func RandomStr(length int) string {
+	letterBytes := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	b := make([]byte, length)
+	letterLen := int32(len(letterBytes))
+	for i := range b {
+		b[i] = letterBytes[RandNum(letterLen)]
+	}
+	return string(b)
+}
+
+// RandomNumberStr 随机字符串
+func RandomNumberStr(length int) string {
+	numberBytes := "0123456789"
+	letterLen := int32(len(numberBytes))
+	b := make([]byte, length)
+	for i := range b {
+		b[i] = numberBytes[RandNum(letterLen)]
+	}
+	return string(b)
+}
+
+// Sign 生成可签名的字符串
+func Sign(str string, salt string) string {
+	if len(salt) == 0 {
+		salt = "K4de3"
+	}
+	var bt bytes.Buffer
+	bt.WriteString(str)
+	bt.WriteString("-")
+	bt.WriteString(salt)
+
+	crypt := md5.Sum(bt.Bytes())
+
+	return fmt.Sprintf("%x", crypt)
+}
+
+// EncryptWidthTime 生成带过期时间的字符串
+func EncryptWidthTime(str string, expired int64, key string) (string, error) {
+	currentTimestamp := time.Now().Unix()
+	arr := entity.EncryptData{Str: str, Expired: currentTimestamp + expired}
+	data, err := json.Marshal(arr)
+	if err != nil {
+		return "", err
+	}
+	return Encrypt(string(data), key), nil
+}
+
+// DecryptWithTime 解密带时间戳的地府穿
+func DecryptWithTime(str string, key string) (string, error) {
+	decode := Decrypt(str, key)
+	var data entity.EncryptData
+
+	if err := json.Unmarshal([]byte(decode), &data); err != nil {
+		return "", err
+	}
+	currentTimestamp := time.Now().Unix()
+	if currentTimestamp >= data.Expired {
+		return "", errors.New("加密串已过期")
+	}
+	return data.Str, nil
+}
+
+// Encrypt 加密字符串
+func Encrypt(str string, key string) string {
+	keyByte := []byte(key)
+
+	keyByte = pkcs5Padding(keyByte, 16)
+	block, err := aes.NewCipher(keyByte)
+	if err != nil {
+		fmt.Println(err.Error())
+	}
+	blockSize := block.BlockSize() // 获取秘钥块的长度
+
+	origData := pkcs5Padding([]byte(str), blockSize)                // 补全码
+	blockMode := cipher.NewCBCEncrypter(block, keyByte[:blockSize]) // 加密模式
+	encrypted := make([]byte, len(origData))                        // 创建数组
+	blockMode.CryptBlocks(encrypted, origData)                      // 加密
+	return hex.EncodeToString(encrypted)
+}
+
+// Decrypt 解密字符串
+func Decrypt(str string, key string) string {
+	hexDecode, _ := hex.DecodeString(str)
+	str = string(hexDecode)
+	keyByte := []byte(key)
+	keyByte = pkcs5Padding(keyByte, 16)
+	block, err := aes.NewCipher(keyByte) // 分组秘钥
+	if err != nil {
+		fmt.Println(err.Error())
+	}
+	blockSize := block.BlockSize()                                  // 获取秘钥块的长度
+	blockMode := cipher.NewCBCDecrypter(block, keyByte[:blockSize]) // 加密模式
+	decrypted := make([]byte, len(hexDecode))                       // 创建数组
+	blockMode.CryptBlocks(decrypted, hexDecode)                     // 解密
+	fmt.Println("decrypted", string(decrypted))
+	decrypted = pkcs5UnPadding(decrypted) // 去除补全码
+
+	return string(decrypted)
+}
+
+// 补齐字符串
+func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padText := bytes.Repeat([]byte{byte(padding)}, padding)
+	return append(ciphertext, padText...)
+}
+
+// 去掉补齐的字符串
+func pkcs5UnPadding(origData []byte) []byte {
+	length := len(origData)
+	unPadding := int(origData[length-1])
+	return origData[:(length - unPadding)]
+}
+
+// 自动生成key
+func GenerateKey(key []byte) (genKey []byte) {
+	genKey = make([]byte, 16)
+	copy(genKey, key)
+	for i := 16; i < len(key); {
+		for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
+			genKey[j] ^= key[i]
+		}
+	}
+	return genKey
+}
+
+// NewError error
+func NewError(args ...string) error {
+	var buffer strings.Builder
+	for i, v := range args {
+		if i > 0 {
+			buffer.WriteString(" : \t")
+		}
+		buffer.WriteString(v)
+	}
+	return errors.New(buffer.String())
+}
+
+// TransparentPNG 透明背景图
+func TransparentPNG() []byte {
+	str := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAANQTFRFAAAAp3o92gAAAAF0Uk5TAEDm2GYAAAAKSURBVHicY2AAAAACAAFIr6RxAAAAAElFTkSuQmCC"
+	trans, _ := base64.StdEncoding.DecodeString(str)
+	return trans
+}
+
+// CutString 字符串裁切
+func CutString(str string, start, end int) string {
+	if end <= 0 {
+		return ""
+	}
+	strRune := []rune(str)
+	strLen := len(strRune) //utf8.RuneCountInString(str)
+	if strLen <= start {
+		return ""
+	}
+	if strLen < end {
+		end = strLen
+	}
+	return string(strRune[start:end])
+}

+ 84 - 0
util/tools_test.go

@@ -0,0 +1,84 @@
+package util
+
+import (
+	"fmt"
+	"gopkg.in/gographics/imagick.v3/imagick"
+	"math/rand"
+	"testing"
+)
+
+func TestEncrypt(t *testing.T) {
+
+	str := Encrypt("password", "key")
+	if str == "" {
+		t.Error("加密失败")
+	}
+	fmt.Println("加密结果:", str)
+}
+
+func TestDecrypt(t *testing.T) {
+	key := "oadsoas"
+	str := "password"
+	encode := Encrypt(str, key)
+
+	decode := Decrypt(encode, key)
+
+	if str != decode {
+		t.Error("加解密失败")
+	}
+	fmt.Println("str :", str, "加密解密结果:", decode)
+}
+
+func TestRandomNumberStr(t *testing.T) {
+	s := RandomNumberStr(8)
+
+	fmt.Println("random number string", s)
+}
+
+func TestRandomStr(t *testing.T) {
+	s := RandomStr(8)
+
+	fmt.Println("random string", s)
+
+	letterBytes := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+	b := make([]byte, 8)
+	for i := range b {
+		b[i] = letterBytes[rand.Intn(len(letterBytes))]
+	}
+	fmt.Println(string(b))
+}
+
+func TestRandNum(t *testing.T) {
+	fmt.Println("rand num", RandNum(1000))
+	fmt.Println("rand num", RandNum(10000))
+
+	letterBytes := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+	b := make([]byte, 8)
+	for i := range b {
+		b[i] = letterBytes[rand.Intn(len(letterBytes))]
+	}
+	fmt.Println(string(b))
+}
+
+func TestGenerateKey(t *testing.T) {
+	fmt.Println(GenerateKey([]byte("nadsnadsfads")))
+
+	imagick.Initialize()
+	defer imagick.Terminate()
+
+	mw := imagick.NewMagickWand()
+	pw := imagick.NewPixelWand()
+	defer pw.Destroy()
+
+	// Create the initial 640x480 transparent canvas
+	pw.SetColor("none")
+
+	if err := mw.NewImage(100, 100, pw); err != nil {
+		fmt.Println("error")
+	}
+	mw.SetImageFormat("PNG")
+	mw.WriteImage("go-test.png")
+	fmt.Println(mw.GetImageBlob())
+}

+ 50 - 0
util/url/url.go

@@ -0,0 +1,50 @@
+package url
+
+import (
+	netUrl "net/url"
+	"strings"
+)
+
+// ParseUrl parse url_string
+// php parse_url
+func ParseUrl(rawURL string) (*netUrl.URL, error) {
+	return netUrl.Parse(rawURL)
+}
+
+// UrlEncode url-encode string
+// php urlencode
+func UrlEncode(str string) string {
+	return netUrl.QueryEscape(str)
+}
+
+// UrlDecode url-decode string
+// php urldecode
+func UrlDecode(str string) (string, error) {
+	return netUrl.QueryUnescape(str)
+}
+
+// RawUrlEncode raw url-encode string
+// php rawurlencode
+func RawUrlEncode(str string) string {
+	return strings.Replace(netUrl.QueryEscape(str), "+", "%20", -1)
+}
+
+// RawUrlDecode raw url-decode string
+// php rawurldecode
+func RawUrlDecode(str string) (string, error) {
+	return netUrl.QueryUnescape(strings.Replace(str, "%20", "+", -1))
+}
+
+// HttpBuildQuery http build query
+// php http_build_query
+func HttpBuildQuery(queryData map[string]string) string {
+	var uri netUrl.URL
+	q := uri.Query()
+	for key, value := range queryData {
+		if strings.TrimSpace(value) != "" {
+			q.Add(key, value)
+		}
+	}
+
+	return q.Encode()
+}

+ 30 - 0
util/url/url_test.go

@@ -0,0 +1,30 @@
+package url
+
+import (
+	"encoding/json"
+	"fmt"
+	"testing"
+)
+
+func TestUrl(t *testing.T) {
+	urlStr := "https://www.example.com/path?googleguy=googley"
+	urlData, err := ParseUrl(urlStr)
+	urlDataByte, _ := json.Marshal(urlData)
+	fmt.Println("ParseUrl: ", string(urlDataByte), ", Error: ", err)
+
+	str := "你好 啊~ "
+	urlEnStr := UrlEncode(str)
+	fmt.Println("UrlEncode: ", urlEnStr)
+	uStr, err := UrlDecode(urlEnStr)
+	fmt.Println("UrlDecode: ", uStr, ", Error: ", err)
+
+	urlREnStr := RawUrlEncode(str)
+	fmt.Println("RawUrlEncode: ", urlREnStr)
+	uRStr, err := RawUrlDecode(urlREnStr)
+	fmt.Println("RawUrlDecode: ", uRStr, ", Error: ", err)
+
+	queryData := map[string]string{
+		"a": "中国", "b": "b1",
+	}
+	fmt.Println("HttpBuildQuery: ", HttpBuildQuery(queryData))
+}