浏览代码

add:卓创红期文件监听

zqbao 8 月之前
当前提交
684a04338c

+ 13 - 0
.gitignore

@@ -0,0 +1,13 @@
+/.idea/
+/log
+latest_log
+/config/*.yaml
+.DS_Store
+go.sum
+/binlog
+latest_binlog
+hongze_yb
+/rdlucklog
+/*.exe
+/test/
+.vscode/

+ 37 - 0
cache/index_cache.go

@@ -0,0 +1,37 @@
+package cache
+
+import (
+	"fmt"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/utils"
+)
+
+// AddIndexHandleExcel 添加处理excel变更队列
+func AddIndexHandleExcel(filePath string) bool {
+	if global.Re == nil {
+		if global.Rc != nil {
+			err := global.Rc.LPush(utils.HANDLE_HONGQI_EXCEL, filePath)
+			if err != nil {
+				fmt.Println("Add Index Handle Excel LPush Err:" + err.Error())
+			}
+			return true
+		}
+		return true
+	}
+	return false
+}
+
+// AddIndexRefreshExcel 添加刷新excel队列
+func AddIndexRefreshExcel(filePath string) bool {
+	if global.Re == nil {
+		if global.Rc != nil {
+			err := global.Rc.LPush(utils.REFRESH_HONGQI_EXCEL, filePath)
+			if err != nil {
+				fmt.Println("Add Index Reresh Excel LPush Err:" + err.Error())
+			}
+			return true
+		}
+		return true
+	}
+	return false
+}

+ 91 - 0
config/config.go

@@ -0,0 +1,91 @@
+package config
+
+import "time"
+
+type Config struct {
+	Log      Log      `mapstructure:"log" json:"log" yaml:"log"`
+	Serve    Serve    `mapstructure:"serve" json:"serve" yaml:"serve"`
+	Mysql    Mysql    `mapstructure:"mysql" json:"mysql" yaml:"mysql"`
+	Redis    Redis    `mapstructure:"redis" json:"redis" yaml:"redis"`
+	AliOss   AliOss   `mapstructure:"ali-oss" json:"ali-oss" yaml:"ali-oss"`
+	EsClient EsClient `mapstructure:"es_client" json:"es_client" yaml:"es_client"`
+}
+
+// Serve gin服务配置
+type Serve struct {
+	Port            int    `mapstructure:"port" json:"port" yaml:"port" description:"gin开启监听http服务的端口"`
+	RunMode         string `mapstructure:"run-mode" json:"run-mode" yaml:"run-mode" description:"gin运行模式的默认,枚举值:debug,release"`
+	UseRedis        bool   `mapstructure:"use-redis" json:"use-redis" yaml:"use-redis" description:"是否使用redis"`
+	AppName         string `mapstructure:"app-name" json:"app-name" yaml:"app-name" description:"项目名称"`
+	StaticDir       string `mapstructure:"static-dir" json:"static-dir" yaml:"static-dir" description:"上传的文件存储目录地址"`
+	EdbLibUrl       string `mapstructure:"edb-lib-url" json:"edb-lib-url" yaml:"edb-lib-url" description:"公共指标库的地址"`
+	ListenExcelPath string `mapstructure:"listen-excel-path" json:"listen-excel-path" yaml:"listen-excel-path" description:"监听文件夹的路径"`
+	RefreshTime     string `mapstructure:"refresh_time" json:"refresh_time" yaml:"refresh_time" description:"刷新指标数据的时间间隔"`
+	TerminalCode    string `mapstructure:"terminal_code" json:"terminal_code" yaml:"terminal_code" description:"终端代码"`
+	AppEdbLibNameEn string `mapstructure:"app_edb_lib_name_en" json:"app_edb_lib_name_en" yaml:"app_edb_lib_name_en" description:"公共指标库的英文名称"`
+	EdbLibMd5Key    string `mapstructure:"edb_lib_md5_key" json:"edb_lib_md5_key" yaml:"edb_lib_md5_key" description:"公共指标库的md5值"`
+}
+
+// Log 日志配置
+type Log struct {
+	Prefix         string `mapstructure:"prefix" json:"prefix" yaml:"prefix" description:"日志输出前缀"`
+	LogFile        bool   `mapstructure:"log-file" json:"logFile" yaml:"log-file" description:""`
+	Stdout         string `mapstructure:"stdout" json:"stdout" yaml:"stdout" description:""`
+	FileStdout     string `mapstructure:"file-stdout" json:"file-stdout" yaml:"file-stdout" description:""`
+	SaveMaxDay     int    `mapstructure:"save-max-day" json:"save-max-day" yaml:"save-max-day" description:"最多保留多少天的日志"`
+	CuttingDay     int    `mapstructure:"cutting-day" json:"cutting-day" yaml:"cutting-day" description:"相隔几天切割文件"`
+	LogDirPath     string `mapstructure:"log-dir-path" json:"log-dir-path" yaml:"log-dir-path" description:"日志目录"`
+	LogSoftLink    string `mapstructure:"log-soft-link" json:"log-soft-link" yaml:"log-soft-link" description:"日志软链接"`
+	BinlogDirPath  string `mapstructure:"binlog-dir-path" json:"binlog-dir-path" yaml:"binlog-dir-path" description:"binlog日志目录"`
+	BinlogSoftLink string `mapstructure:"binlog-soft-link" json:"binlog-soft-link" yaml:"binlog-soft-link" description:"binlog日志软链接"`
+}
+
+// Mysql 数据库配置
+type Mysql struct {
+	//LogMode             bool        `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode" description:"是否开启日志"`
+	Stdout              bool        `mapstructure:"stdout" json:"stdout" yaml:"stdout" description:"日志是否输出在控制台"`
+	DefaultDsnAliasName string      `mapstructure:"default-dsn-alias-name" json:"default-dsn-alias-name" yaml:"default-dsn-alias-name" description:"默认的数据库连接别名"`
+	List                []MysqlConn `mapstructure:"list" json:"list" yaml:"list" description:"数据库链接配置列表"`
+}
+
+// MysqlConn mysql数据链接配置
+type MysqlConn struct {
+	Dsn          string `mapstructure:"dsn" json:"dsc" yaml:"dsn" description:"数据库连接配置"`
+	AliasName    string `mapstructure:"alias-name" json:"alias-name" yaml:"alias-name" description:"数据库别名"`
+	MaxIdleConns int    `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns" description:"最大空闲连接"`
+	MaxOpenConns int    `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns" description:"最大连接数"`
+}
+
+// Redis redis配置
+type Redis struct {
+	Address  string `mapstructure:"address" json:"address" yaml:"address" description:"redis服务链接地址"`
+	Password string `mapstructure:"password" json:"password" yaml:"password" description:"redis服务密码"`
+	Db       int    `mapstructure:"db" json:"db" yaml:"db" description:"默认使用的redis库"`
+}
+
+// AliOss aliOss配置
+type AliOss struct {
+	BucketName      string `mapstructure:"bucket-name" json:"bucket-name" yaml:"bucket-name"`
+	EndPoint        string `mapstructure:"end-point" json:"end-point" yaml:"end-point"`
+	ImgHost         string `mapstructure:"img-host" json:"img-host" yaml:"img-host" description:"阿里oss主机地址"`
+	UploadDir       string `mapstructure:"upload-dir" json:"upload-dir" yaml:"upload-dir" description:"图片上传的文件目录"`
+	UploadAudioDir  string `mapstructure:"upload-audio-dir" json:"upload-audio-dir" yaml:"upload-audio-dir" description:"视频上传的文件目录"`
+	AccessKeyId     string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id" description:"access-key-id"`
+	AccessKeySecret string `mapstructure:"access-key-secret" json:"access-key-secret" yaml:"access-key-secret" description:"access-key-secret"`
+}
+
+// EsClient es客户端配置
+type EsClient struct {
+	// 终端地址
+	Endpoints string `json:"endpoints" yaml:"endpoints"`
+	// 用户
+	Username string `json:"username" yaml:"username"`
+	// 密码
+	Password string `json:"password" yaml:"password"`
+	// 超时时间,单位ms
+	Timeout time.Duration `json:"timeout" yaml:"timeout"`
+	// es日志目录
+	Log string `json:"log" yaml:"log"`
+	// 索引前缀
+	Prefix string `json:"prefix" yaml:"prefix"`
+}

+ 14 - 0
controller/auth.go

@@ -0,0 +1,14 @@
+package controller
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hongtao3_watch/controller/resp"
+)
+
+type AuthController struct {
+}
+
+func (a *AuthController) Login(c *gin.Context) {
+	resp.OkData("登录成功", nil, c)
+	return
+}

+ 144 - 0
controller/index/index.go

@@ -0,0 +1,144 @@
+package index
+
+type IndexController struct {
+}
+
+//func (s *IndexController) ServerCheck(c *gin.Context) {
+//	resp.OkData("检测成功", 1, c)
+//	return
+//}
+//
+//// Add 生成指标
+//func (s *IndexController) Create(c *gin.Context) {
+//	req := new(index.IndexAddReq)
+//	err := c.Bind(&req)
+//	if err != nil {
+//		errs, ok := err.(validator.ValidationErrors)
+//		if !ok {
+//			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+//			return
+//		}
+//		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+//		return
+//	}
+//	fmt.Println("indexCode:" + req.IndexCode)
+//	if req.RunMode == "" {
+//		req.RunMode = "debug"
+//	}
+//	//fileName := req.IndexName + "_" + req.IndexCode + ".xlsx"
+//	var fileName string
+//	if req.UpdateWeek != "" {
+//		fileName = req.IndexCode + "_" + req.UpdateWeek + "_" + req.RunMode + ".xlsx" //保存的文件名称
+//	} else {
+//		fileName = req.IndexCode + "_" + req.RunMode + ".xlsx" //保存的文件名称
+//	}
+//	filePath := utils.IndexSaveDir + fileName
+//
+//	templatePath := utils.IndexSaveDir + "index_template.xlsx"
+//	templateFile, err := excelize.OpenFile(templatePath)
+//	if err != nil {
+//		resp.FailData("打开文件失败", "打开文件失败,Err:"+err.Error(), c)
+//		return
+//	}
+//	defer func() {
+//		templateFile.Close()
+//	}()
+//
+//	//timeTag := time.Now().UnixNano() / 1e6
+//	//timeTagStr := fmt.Sprintf("%d", timeTag)
+//
+//	//UpdateMode:1:增量更新,0:全量更新
+//	//commentStr := `"BlankValue":"0","CanMark":true,"ChartLineType":"0","DateBlock":0,"DateBlockCount":1,"DateFormat":0,"DateTimeTag":` + timeTagStr + `,"EndDate":"",
+//	//               "ExportType":0,"HasDescription":true,"HasEmptyRows":false,"HasFrequency":true,"HasIndexID":true,"HasLastDate":true,"HasSourceName":true,"HasTimeInterval":true,
+//	//               "HasUnit":true,"HasUpdateDate":true,"IsCreateChart":false,"IsDataSort":true,"IsNewSheet":false,"IsNewWorkbook":false,
+//	//               "Models":[{"DataFormat":0,"DataStartDate":"2011-03-25","DefineName":"","DefineUnit":"","DisplayIndexCode":"` + req.IndexCode + `",
+//	//               "IndexCode":"` + req.IndexCode + `","IndexFormula":"` + req.IndexCode + `","PointValue":0,"UnionStart":""}],"Position":"A1","RangeData":"A2:B2791",
+//	//               "ShowBlankLines":false,"StartDate":"","Transpose":false,"UpdateMode":1,"lookModel":{"IsLast":false,"LookValue":0,"lookType":0},"ver":3}`
+//
+//	startDate := "1990-01-01"
+//	commentStr := `"BlankValue":"0","CanMark":true,"ChartLineType":"0","DateBlock":0,"DateBlockCount":1,"DateFormat":0,"DateTimeTag":"","EndDate":"","ExportType":0,"HasDescription":true,"HasEmptyRows":false,"HasFrequency":true,"HasIndexID":true,"HasLastDate":true,"HasSourceName":true,"HasTimeInterval":true,"HasUnit":true,"HasUpdateDate":true,"IsCreateChart":false,"IsDataSort":true,"IsNewSheet":false,"IsNewWorkbook":false,"Models":[{"DataFormat":0,"DataStartDate":"` + startDate + `","DefineName":"","DefineUnit":"","DisplayIndexCode":"` + req.IndexCode + `","IndexCode":"` + req.IndexCode + `","IndexFormula":"` + req.IndexCode + `","PointValue":0,"UnionStart":""}],"Position":"A1","RangeData":"A2:B280","ShowBlankLines":false,"StartDate":"","Transpose":false,"UpdateMode":1,"lookModel":{"IsLast":false,"LookValue":0,"lookType":0},"ver":3}
+//`
+//	commentMap := make(map[string]interface{})
+//	commentMap["author"] = "{"
+//	commentMap["text"] = commentStr
+//	//commentMap["text"] = commentItem
+//
+//	commentJson, err := json.Marshal(commentMap)
+//	if err != nil {
+//		fmt.Println("json.Marshal err:" + err.Error())
+//	}
+//
+//	fmt.Println("commentJson")
+//	fmt.Println(string(commentJson))
+//	templateFile.DeleteComment("Sheet1", "A1")
+//	templateFile.AddComment("Sheet1", "A1", string(commentJson))
+//	if err := templateFile.SaveAs(filePath); err != nil {
+//		fmt.Println(err)
+//		resp.FailData("保存失败", "保存失败,Err:"+err.Error(), c)
+//		return
+//	}
+//	time.Sleep(1 * time.Second)
+//	fmt.Println("start MysteelChemicalRefresh")
+//	//services.MysteelChemicalRefresh(filePath)
+//	services.AddIndexRefreshToLpush(filePath)
+//	resp.Ok("保存成功", c)
+//	return
+//}
+//
+//// Delete 删除指标文件
+//func (s *IndexController) Delete(c *gin.Context) {
+//	req := new(index.IndexAddReq)
+//	err := c.Bind(&req)
+//	if err != nil {
+//		errs, ok := err.(validator.ValidationErrors)
+//		if !ok {
+//			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+//			return
+//		}
+//		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+//		return
+//	}
+//	fmt.Println("params start")
+//	fmt.Println(req.RunMode)
+//	fmt.Println("params end")
+//	var fileName string
+//	if req.UpdateWeek != "" {
+//		fileName = req.IndexCode + "_" + req.UpdateWeek + "_" + req.RunMode + ".xlsx" //保存的文件名称
+//	} else {
+//		fileName = req.IndexCode + "_" + req.RunMode + ".xlsx" //保存的文件名称
+//	}
+//	filePath := utils.IndexSaveDir + fileName
+//	err = os.Remove(filePath)
+//	if err != nil {
+//		fmt.Println("filePath:" + filePath)
+//		fmt.Println("err:" + err.Error())
+//		resp.Ok("删除失败", c)
+//		return
+//	}
+//	resp.Ok("删除成功", c)
+//	return
+//}
+//
+//func (s *IndexController) Test(c *gin.Context) {
+//	resp.OkData("检测成功", 1, c)
+//	return
+//}
+//
+//// Add 刷新指标
+//func (s *IndexController) Refresh(c *gin.Context) {
+//	req := new(index.IndexRefreshReq)
+//	err := c.Bind(&req)
+//	if err != nil {
+//		errs, ok := err.(validator.ValidationErrors)
+//		if !ok {
+//			resp.FailData("参数解析失败", "Err:"+err.Error(), c)
+//			return
+//		}
+//		resp.FailData("参数解析失败", errs.Translate(global.Trans), c)
+//		return
+//	}
+//	//services.MysteelChemicalRefresh(req.MergeFilePath)
+//	services.AddIndexRefreshToLpush(req.MergeFilePath)
+//	resp.Ok("保存成功", c)
+//	return
+//}

+ 154 - 0
controller/resp/base.go

@@ -0,0 +1,154 @@
+package resp
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/utils"
+	"strings"
+)
+
+var (
+	OK_CODE            = 200  //业务成功
+	FAIL_CODE          = 400  //业务错误
+	TOKEN_ERROR_CODE   = 401  //toke异常
+	NO_AUTH            = 403  //没有权限
+	SPECIFIC_FAIL_CODE = 4001 // 业务指定错误
+)
+
+type ResultData struct {
+	Code   int         `json:"code" description:"状态码"`
+	Msg    string      `json:"msg" description:"提示信息"`
+	Data   interface{} `json:"data" description:"返回数据"`
+	ErrMsg string      `json:"-" description:"错误信息,不用返回给前端,只是做日志记录"`
+}
+
+func result(code int, resultData ResultData, c *gin.Context) {
+	jsonByte, _ := json.Marshal(resultData)
+	token := c.Request.Header.Get("Authorization")
+	if token == "" {
+		token = c.DefaultQuery("authorization", "")
+		if token == "" {
+			token = c.DefaultQuery("Authorization", "")
+		}
+	}
+	logSlice := make([]string, 0)
+	logSlice = append(logSlice, fmt.Sprint("Url:", c.Request.RequestURI))
+	logSlice = append(logSlice, fmt.Sprint("Token:", token))
+	logSlice = append(logSlice, fmt.Sprint("resultData:", string(jsonByte)))
+
+	//记录错误日志
+	if resultData.ErrMsg != "" {
+		logSlice = append(logSlice, fmt.Sprint("ErrMsg:", resultData.ErrMsg))
+		//global.LOG.Info(strings.Join(logSlice, ";"))
+	}
+
+	// 测试环境不加密
+	if global.CONFIG.Serve.RunMode == "debug" {
+		c.JSON(code, resultData)
+	} else {
+		global.LOG.Info(strings.Join(logSlice, ";"))
+		encryptResult := utils.DesBase64Encrypt(jsonByte)
+		c.JSON(code, string(encryptResult))
+	}
+	c.Abort()
+}
+
+// OK 操作成功
+func Ok(msg string, c *gin.Context) {
+	resultData := ResultData{
+		Code: OK_CODE,
+		Msg:  msg,
+	}
+	result(200, resultData, c)
+}
+
+// OkData 成功返回数据
+func OkData(msg string, data interface{}, c *gin.Context) {
+	resultData := ResultData{
+		Code: OK_CODE,
+		Msg:  msg,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// Fail 操作失败
+func Fail(msg string, c *gin.Context) {
+	resultData := ResultData{
+		Code: FAIL_CODE,
+		Msg:  msg,
+	}
+	result(200, resultData, c)
+}
+
+// FailData 成功返回数据
+func FailData(msg string, data interface{}, c *gin.Context) {
+	resultData := ResultData{
+		Code: FAIL_CODE,
+		Msg:  msg,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// Custom 自定义状态码+操作成功
+func Custom(code int, msg string, c *gin.Context) {
+	resultData := ResultData{
+		Code: code,
+		Msg:  msg,
+	}
+	result(200, resultData, c)
+}
+
+// CustomData 自定义状态码+返回数据
+func CustomData(code int, msg string, data interface{}, c *gin.Context) {
+	resultData := ResultData{
+		Code: code,
+		Msg:  msg,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// TokenError token异常
+func TokenError(data interface{}, message, errMsg string, c *gin.Context) {
+	resultData := ResultData{
+		Code:   TOKEN_ERROR_CODE,
+		Msg:    message,
+		Data:   data,
+		ErrMsg: errMsg,
+	}
+	result(200, resultData, c)
+}
+
+// AuthError 没有权限
+func AuthError(data interface{}, message string, c *gin.Context) {
+	resultData := ResultData{
+		Code: NO_AUTH,
+		Msg:  message,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// SpecificFail 业务指定错误
+func SpecificFail(data interface{}, message string, c *gin.Context) {
+	resultData := ResultData{
+		Code: SPECIFIC_FAIL_CODE,
+		Msg:  message,
+		Data: data,
+	}
+	result(200, resultData, c)
+}
+
+// FailMsg 操作失败
+func FailMsg(msg, errMsg string, c *gin.Context) {
+	resultData := ResultData{
+		Code:   FAIL_CODE,
+		Msg:    msg,
+		ErrMsg: errMsg,
+	}
+	result(200, resultData, c)
+}

+ 38 - 0
core/config.go

@@ -0,0 +1,38 @@
+package core
+
+//
+//const ConfigFile = "config/config.yaml"                                //本地(测试)环境下的配置文件地址
+//const ProConfigFile = "/home/code/config/hongze_yb/config/config.yaml" //生产环境下的配置文件地址
+//
+//func init() {
+//	v := viper.New()
+//
+//	configFilePath := ConfigFile
+//
+//	//如果不存在该配置文件,那么应该是线上环境,那么去寻找线上配置文件的路径
+//	if !utils.FileIsExist(configFilePath) {
+//		configFilePath = ProConfigFile
+//	}
+//
+//	//设置配置文件
+//	v.SetConfigFile(configFilePath)
+//
+//	err := v.ReadInConfig()
+//	if err != nil {
+//		panic(fmt.Errorf("读取配置失败,Err: %s \n", err))
+//	}
+//
+//	//持续监听文件
+//	v.WatchConfig()
+//
+//	v.OnConfigChange(func(e fsnotify.Event) {
+//		fmt.Println("配置文件变更:", e.Name)
+//		if err := v.Unmarshal(&global.CONFIG); err != nil {
+//			fmt.Println("配置重赋值失败,Err:", err)
+//		}
+//		fmt.Println(global.CONFIG)
+//	})
+//	if err := v.Unmarshal(&global.CONFIG); err != nil {
+//		fmt.Println("配置初始化赋值失败,Err:", err)
+//	}
+//}

+ 136 - 0
core/log.go

@@ -0,0 +1,136 @@
+package core
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
+	oplogging "github.com/op/go-logging"
+	"hongze/hongtao3_watch/config"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/utils"
+	"io"
+	"os"
+	"strings"
+	"time"
+)
+
+const (
+	Module = "hongtao3_watch"
+)
+
+var (
+	defaultFormatter = ` %{time:2006/01/02 - 15:04:05.000} %{longfile} %{color:bold}▶ [%{level:.6s}] %{message}%{color:reset}`
+)
+
+// 初始化日志
+func init() {
+	logConfig := global.CONFIG.Log
+	logger := oplogging.MustGetLogger(Module)
+	var backends []oplogging.Backend
+	//注册输出
+	registerStdout(logConfig, &backends)
+
+	//注册框架输出(日志文件)
+	fileWriter := registerFile(logConfig, &backends, logConfig.LogDirPath, logConfig.LogSoftLink)
+	if fileWriter != nil {
+		if global.CONFIG.Serve.RunMode == "debug" {
+			gin.DefaultWriter = io.MultiWriter(fileWriter, os.Stdout)
+		} else {
+			gin.DefaultWriter = io.MultiWriter(fileWriter)
+		}
+	}
+	oplogging.SetBackend(backends...)
+	global.LOG = logger
+
+	//初始化mysql数据库日志
+	initMysqlLog()
+}
+
+// initMysqlLog 初始化mysql数据库日志
+func initMysqlLog() {
+	logConfig := global.CONFIG.Log
+	var backends []oplogging.Backend
+	//注册输出
+	registerStdout(logConfig, &backends)
+
+	//注册框架输出(日志文件)
+	fileWriter := registerFile(logConfig, &backends, logConfig.BinlogDirPath, logConfig.BinlogSoftLink)
+	global.MYSQL_LOG = fileWriter
+}
+
+func registerStdout(c config.Log, backends *[]oplogging.Backend) {
+	if c.Stdout != "" {
+		level, err := oplogging.LogLevel(c.Stdout)
+		if err != nil {
+			fmt.Println(err)
+		}
+		*backends = append(*backends, createBackend(os.Stdout, c, level))
+	}
+}
+
+// registerFile 注册文件日志
+func registerFile(c config.Log, backends *[]oplogging.Backend, logDir, logSoftLink string) io.Writer {
+	if c.FileStdout != "" {
+		if ok, _ := utils.PathExists(logDir); !ok {
+			// directory not exist
+			fmt.Println("create log directory")
+			_ = os.Mkdir(logDir, os.ModePerm)
+		}
+
+		//日志保留时间
+		saveMaxTime := time.Duration(global.CONFIG.Log.SaveMaxDay) * 24 * time.Hour
+		//日志切割时间(每隔多久切割一次)
+		cuttingTime := time.Duration(global.CONFIG.Log.CuttingDay) * 24 * time.Hour
+
+		//使用rotatelogs
+		fileWriter, err := rotatelogs.New(
+			logDir+string(os.PathSeparator)+"%Y-%m-%d-%H-%M.log",
+			// 生成软链,指向最新日志文件
+			rotatelogs.WithLinkName(logSoftLink),
+			// 以下两个配置不能同时设置,一个是保留天数,一个是保留分拣份数
+			rotatelogs.WithMaxAge(saveMaxTime), //日志保留多久,最小分钟为单位
+			//rotatelogs.WithRotationCount(5),        //number 默认7份 大于7份 或到了清理时间 开始清理
+			// time period of log file switching
+			rotatelogs.WithRotationTime(cuttingTime), //rotate 最小为1分钟轮询。默认60s  低于1分钟就按1分钟来
+		)
+		if err != nil {
+			fmt.Println(err)
+		}
+		level, err := oplogging.LogLevel(c.FileStdout)
+		if err != nil {
+			fmt.Println(err)
+		}
+		*backends = append(*backends, createBackend(fileWriter, c, level))
+
+		return fileWriter
+	}
+	return nil
+}
+
+// createBackend 创建一个新的日志记录器
+func createBackend(w io.Writer, c config.Log, level oplogging.Level) oplogging.Backend {
+	backend := oplogging.NewLogBackend(w, c.Prefix, 0)
+	stdoutWriter := false
+	if w == os.Stdout {
+		stdoutWriter = true
+	}
+	format := getLogFormatter(c, stdoutWriter)
+	backendLeveled := oplogging.AddModuleLevel(oplogging.NewBackendFormatter(backend, format))
+	backendLeveled.SetLevel(level, Module)
+	return backendLeveled
+}
+
+func getLogFormatter(c config.Log, stdoutWriter bool) oplogging.Formatter {
+	pattern := defaultFormatter
+	if !stdoutWriter {
+		// Color is only required for console output
+		// Other writers don't need %{color} tag
+		pattern = strings.Replace(pattern, "%{color:bold}", "", -1)
+		pattern = strings.Replace(pattern, "%{color:reset}", "", -1)
+	}
+	if !c.LogFile {
+		// Remove %{logfile} tag
+		pattern = strings.Replace(pattern, "%{longfile}", "", -1)
+	}
+	return oplogging.MustStringFormatter(pattern)
+}

+ 35 - 0
core/run_server.go

@@ -0,0 +1,35 @@
+package core
+
+import (
+	"fmt"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/init_serve"
+)
+
+func RunServe() {
+	//初始化路由
+	r := init_serve.InitRouter()
+	//初始化mysql数据库
+	init_serve.Mysql()
+	//如果配置了redis,那么链接redis
+	fmt.Println("redis")
+	fmt.Println(global.CONFIG.Serve.UseRedis)
+	if global.CONFIG.Serve.UseRedis {
+		//初始化redis
+		init_serve.RedisTool()
+	}
+	//初始化验证器
+	//if err := global.InitTrans("zh"); err != nil {
+	//	fmt.Printf("init trans failed, err:%v\n", err)
+	//	return
+	//}
+	//启动任务
+	init_serve.InitTask()
+	// 3.监听端口,默认在8080
+	// Run("里面不指定端口号默认为8080")
+	fmt.Println("port:", global.CONFIG.Serve.Port)
+	err := r.Run(fmt.Sprint("0.0.0.0:", global.CONFIG.Serve.Port)) // 监听并在 0.0.0.0:8080 上启动服务
+	if err != nil {
+		panic(fmt.Errorf("服务启动失败,Err:%s", err))
+	}
+}

+ 89 - 0
docs/docs.go

@@ -0,0 +1,89 @@
+// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
+// This file was generated by swaggo/swag
+package docs
+
+import (
+	"bytes"
+	"encoding/json"
+	"strings"
+	"text/template"
+
+	"github.com/swaggo/swag"
+)
+
+var doc = `{
+    "schemes": {{ marshal .Schemes }},
+    "swagger": "2.0",
+    "info": {
+        "description": "{{escape .Description}}",
+        "title": "{{.Title}}",
+        "termsOfService": "https://www.hzinsights.com/",
+        "contact": {
+            "name": "www.hzinsights.com/",
+            "url": "https://www.hzinsights.com/",
+            "email": "pyan@hzinsights.com"
+        },
+        "license": {
+            "name": "Apache 2.0",
+            "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+        },
+        "version": "{{.Version}}"
+    },
+    "host": "{{.Host}}",
+    "basePath": "{{.BasePath}}",
+    "paths": {}
+}`
+
+type swaggerInfo struct {
+	Version     string
+	Host        string
+	BasePath    string
+	Schemes     []string
+	Title       string
+	Description string
+}
+
+// SwaggerInfo holds exported Swagger Info so clients can modify it
+var SwaggerInfo = swaggerInfo{
+	Version:     "1.0",
+	Host:        "127.0.0.1:8607",
+	BasePath:    "/",
+	Schemes:     []string{},
+	Title:       "弘则人力资源管理系统API接口文档",
+	Description: "弘则人力资源管理系统API接口文档",
+}
+
+type s struct{}
+
+func (s *s) ReadDoc() string {
+	sInfo := SwaggerInfo
+	sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1)
+
+	t, err := template.New("swagger_info").Funcs(template.FuncMap{
+		"marshal": func(v interface{}) string {
+			a, _ := json.Marshal(v)
+			return string(a)
+		},
+		"escape": func(v interface{}) string {
+			// escape tabs
+			str := strings.Replace(v.(string), "\t", "\\t", -1)
+			// replace " with \", and if that results in \\", replace that with \\\"
+			str = strings.Replace(str, "\"", "\\\"", -1)
+			return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
+		},
+	}).Parse(doc)
+	if err != nil {
+		return doc
+	}
+
+	var tpl bytes.Buffer
+	if err := t.Execute(&tpl, sInfo); err != nil {
+		return doc
+	}
+
+	return tpl.String()
+}
+
+func init() {
+	swag.Register("swagger", &s{})
+}

+ 21 - 0
docs/swagger.json

@@ -0,0 +1,21 @@
+{
+    "swagger": "2.0",
+    "info": {
+        "description": "弘则人力资源管理系统API接口文档",
+        "title": "弘则人力资源管理系统API接口文档",
+        "termsOfService": "https://www.hzinsights.com/",
+        "contact": {
+            "name": "www.hzinsights.com/",
+            "url": "https://www.hzinsights.com/",
+            "email": "pyan@hzinsights.com"
+        },
+        "license": {
+            "name": "Apache 2.0",
+            "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+        },
+        "version": "1.0"
+    },
+    "host": "127.0.0.1:8607",
+    "basePath": "/",
+    "paths": {}
+}

+ 16 - 0
docs/swagger.yaml

@@ -0,0 +1,16 @@
+basePath: /
+host: 127.0.0.1:8607
+info:
+  contact:
+    email: pyan@hzinsights.com
+    name: www.hzinsights.com/
+    url: https://www.hzinsights.com/
+  description: 弘则人力资源管理系统API接口文档
+  license:
+    name: Apache 2.0
+    url: http://www.apache.org/licenses/LICENSE-2.0.html
+  termsOfService: https://www.hzinsights.com/
+  title: 弘则人力资源管理系统API接口文档
+  version: "1.0"
+paths: {}
+swagger: "2.0"

+ 66 - 0
global/global.go

@@ -0,0 +1,66 @@
+package global
+
+import (
+	"fmt"
+	"github.com/fsnotify/fsnotify"
+	"github.com/go-redis/redis/v8"
+	"github.com/olivere/elastic/v7"
+	oplogging "github.com/op/go-logging"
+	"github.com/spf13/viper"
+	"gorm.io/gorm"
+	"hongze/hongtao3_watch/config"
+	"hongze/hongtao3_watch/utils"
+	"io"
+
+	"github.com/rdlucklib/rdluck_tools/cache"
+)
+
+var (
+	CONFIG        config.Config //配置文件
+	LOG           *oplogging.Logger
+	MYSQL         map[string]*gorm.DB //数据库连接配置
+	MYSQL_LOG     io.Writer
+	DEFAULT_MYSQL *gorm.DB      //默认数据库连接配置
+	Redis         *redis.Client //redis链接
+	EsClient      *elastic.Client
+
+	Rc *cache.Cache //redis缓存
+	Re error        //redis错误
+)
+
+const ConfigFile = "config/config_debug.yaml" //本地(测试)环境下的配置文件地址
+const ProConfigFile = "config/config.yaml"    //生产环境下的配置文件地址
+
+func init() {
+	v := viper.New()
+
+	configFilePath := ConfigFile
+
+	//如果不存在该配置文件,那么应该是线上环境,那么去寻找线上配置文件的路径
+	if !utils.FileIsExist(configFilePath) {
+		configFilePath = ProConfigFile
+	}
+
+	fmt.Println("configFilePath->", configFilePath)
+	//设置配置文件
+	v.SetConfigFile(configFilePath)
+
+	err := v.ReadInConfig()
+	if err != nil {
+		panic(fmt.Errorf("读取配置失败,Err: %s \n", err))
+	}
+
+	//持续监听文件
+	v.WatchConfig()
+
+	v.OnConfigChange(func(e fsnotify.Event) {
+		fmt.Println("配置文件变更:", e.Name)
+		if err := v.Unmarshal(&CONFIG); err != nil {
+			fmt.Println("配置重赋值失败,Err:", err)
+		}
+		fmt.Println(CONFIG)
+	})
+	if err := v.Unmarshal(&CONFIG); err != nil {
+		fmt.Println("配置初始化赋值失败,Err:", err)
+	}
+}

+ 45 - 0
global/validator.go

@@ -0,0 +1,45 @@
+package global
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin/binding"
+	"github.com/go-playground/locales/en"
+	"github.com/go-playground/locales/zh"
+	ut "github.com/go-playground/universal-translator"
+	"github.com/go-playground/validator/v10"
+	enTranslations "github.com/go-playground/validator/v10/translations/en"
+	zhTranslations "github.com/go-playground/validator/v10/translations/zh"
+)
+
+// 定义一个全局翻译器T
+var Trans ut.Translator
+// InitTrans 初始化翻译器
+func InitTrans(locale string) (err error) {
+	// 修改gin框架中的Validator引擎属性,实现自定制
+	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
+		zhT := zh.New() // 中文翻译器
+		enT := en.New() // 英文翻译器
+		// 第一个参数是备用(fallback)的语言环境
+		// 后面的参数是应该支持的语言环境(支持多个)
+		// uni := ut.New(zhT, zhT) 也是可以的
+		uni := ut.New(enT, zhT, enT)
+		// locale 通常取决于 http 请求头的 'Accept-Language'
+		var ok bool
+		// 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找
+		Trans, ok = uni.GetTranslator(locale)
+		if !ok {
+			return fmt.Errorf("uni.GetTranslator(%s) failed", locale)
+		}
+		// 注册翻译器
+		switch locale {
+		case "en":
+			err = enTranslations.RegisterDefaultTranslations(v, Trans)
+		case "zh":
+			err = zhTranslations.RegisterDefaultTranslations(v, Trans)
+		default:
+			err = enTranslations.RegisterDefaultTranslations(v, Trans)
+		}
+		return
+	}
+	return
+}

+ 81 - 0
go.mod

@@ -0,0 +1,81 @@
+module hongze/hongtao3_watch
+
+go 1.19
+
+require (
+	github.com/fsnotify/fsnotify v1.6.0
+	github.com/gin-gonic/gin v1.9.0
+	github.com/go-playground/locales v0.14.1
+	github.com/go-playground/universal-translator v0.18.1
+	github.com/go-playground/validator/v10 v10.11.2
+	github.com/go-redis/redis/v8 v8.11.5
+	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
+	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
+	github.com/olivere/elastic/v7 v7.0.32
+	github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
+	github.com/rdlucklib/rdluck_tools v1.0.3
+	github.com/robfig/cron/v3 v3.0.1
+	github.com/spf13/viper v1.15.0
+	github.com/swaggo/swag v1.16.1
+	github.com/xuri/excelize/v2 v2.7.1
+	golang.org/x/image v0.7.0
+	gorm.io/driver/mysql v1.5.0
+	gorm.io/gorm v1.25.0
+)
+
+require (
+	github.com/KyleBanks/depth v1.2.1 // indirect
+	github.com/PuerkitoBio/purell v1.1.1 // indirect
+	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
+	github.com/bytedance/sonic v1.8.0 // indirect
+	github.com/cespare/xxhash/v2 v2.1.2 // indirect
+	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/garyburd/redigo v1.6.3 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-openapi/jsonpointer v0.19.5 // indirect
+	github.com/go-openapi/jsonreference v0.19.6 // indirect
+	github.com/go-openapi/spec v0.20.4 // indirect
+	github.com/go-openapi/swag v0.19.15 // 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.4.0 // indirect
+	github.com/josharian/intern v1.0.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/mailru/easyjson v0.7.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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.6 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/richardlehane/mscfb v1.0.4 // indirect
+	github.com/richardlehane/msoleps v1.0.3 // 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
+	github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
+	github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
+	golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
+	golang.org/x/crypto v0.8.0 // indirect
+	golang.org/x/net v0.9.0 // indirect
+	golang.org/x/sys v0.7.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
+	golang.org/x/tools v0.7.0 // indirect
+	google.golang.org/protobuf v1.28.1 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 74 - 0
init_serve/mysql.go

@@ -0,0 +1,74 @@
+package init_serve
+
+import (
+	"fmt"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	"gorm.io/gorm/logger"
+	"hongze/hongtao3_watch/global"
+	"io"
+	"log"
+	"os"
+	"time"
+)
+
+// Mysql 数据库初始化
+func Mysql() {
+	mysqlConf := global.CONFIG.Mysql
+	if len(mysqlConf.List) <= 0 {
+		return
+		global.LOG.Error("mysql链接未配置")
+		panic(fmt.Errorf("mysql链接未配置"))
+	}
+
+	//开启日志
+	logWriter := io.MultiWriter(global.MYSQL_LOG) //binlog日志,记录到文件中去
+	if global.CONFIG.Mysql.Stdout {
+		logWriter = io.MultiWriter(global.MYSQL_LOG, os.Stdout)
+	}
+	newLogger := logger.New(log.New(logWriter, "\r\n", log.LstdFlags), logger.Config{
+		SlowThreshold:             200 * time.Millisecond, //慢sql :200ms
+		LogLevel:                  logger.Info,            //记录的日志类型,info代表所有信息都记录
+		IgnoreRecordNotFoundError: true,                   //是否忽略找不到数据错误信息(只是日志记录记录成err还是普通的输出的区别,并不影响业务代码中的:找不到数据行error)
+		Colorful:                  true,                   //是否颜色输出
+	})
+
+	mysqlMap := make(map[string]*gorm.DB)
+	for _, conf := range mysqlConf.List {
+		dsn := conf.Dsn
+
+		db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
+			Logger: newLogger,
+		})
+		if err != nil {
+			global.LOG.Errorf("mysql 启动异常,数据库:", conf.AliasName, ";Err:", err)
+			panic(fmt.Errorf("mysql 启动异常,数据库:", conf.AliasName, "Err:%s", err))
+		}
+
+		//创建连接池
+		sqlDB, err := db.DB()
+		if err != nil {
+			global.LOG.Errorf("mysql 创建连接池失败,数据库:", conf.AliasName, ";Err:", err)
+			panic(fmt.Errorf("mysql 创建连接池失败,数据库:", conf.AliasName, "Err:%s", err))
+		}
+
+		mysqlMap[conf.AliasName] = db
+
+		//默认数据库连接
+		if mysqlConf.DefaultDsnAliasName == conf.AliasName {
+			global.DEFAULT_MYSQL = db
+		}
+
+		// SetMaxIdleConns 设置空闲连接池中连接的最大数量
+		sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
+
+		// SetMaxOpenConns 设置打开数据库连接的最大数量。
+		sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
+
+		// SetConnMaxLifetime 设置了连接可复用的最大时间。
+		//sqlDB.SetConnMaxLifetime(time.Hour)
+	}
+
+	//全局赋值数据库链接
+	global.MYSQL = mysqlMap
+}

+ 41 - 0
init_serve/redis.go

@@ -0,0 +1,41 @@
+package init_serve
+
+import (
+	"context"
+	"fmt"
+	"github.com/go-redis/redis/v8"
+	"hongze/hongtao3_watch/global"
+
+	"github.com/rdlucklib/rdluck_tools/cache"
+)
+
+func Redis() {
+	redisConf := global.CONFIG.Redis
+	client := redis.NewClient(&redis.Options{
+		Addr:     redisConf.Address,
+		Password: redisConf.Password,
+		DB:       redisConf.Db,
+		//PoolSize: 10, //连接池最大socket连接数,默认为10倍CPU数, 10 * runtime.NumCPU(暂不配置)
+	})
+	_, err := client.Ping(context.TODO()).Result()
+	if err != nil {
+		global.LOG.Error("redis 链接失败:", err)
+		panic("redis 链接失败:" + err.Error())
+	}
+
+	//全局赋值redis链接
+	global.Redis = client
+}
+
+func RedisTool() {
+	fmt.Println("init RedisTool")
+	redisConf := global.CONFIG.Redis
+	fmt.Println(redisConf)
+	REDIS_CACHE := fmt.Sprintf(`{"key":"redis","conn":"%s","password":"%s"}`, redisConf.Address, redisConf.Password)
+	fmt.Println("REDIS_CACHE:"+REDIS_CACHE)
+	global.Rc, global.Re = cache.NewCache(REDIS_CACHE) //初始化缓存
+	if global.Re != nil {
+		fmt.Println(global.Re)
+		panic(global.Re)
+	}
+}

+ 31 - 0
init_serve/router.go

@@ -0,0 +1,31 @@
+package init_serve
+
+import (
+	"github.com/gin-gonic/gin"
+	_ "hongze/hongtao3_watch/docs"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/middleware"
+	"hongze/hongtao3_watch/routers"
+)
+
+// InitRouter 初始化路由
+func InitRouter() (r *gin.Engine) {
+	//设置
+	gin.SetMode(global.CONFIG.Serve.RunMode)
+	// 1.创建路由
+	r = gin.Default()
+	r.Use(middleware.Cors())
+	//r.Use(gin.Recovery())
+	r.Use(middleware.Recover())
+
+	// 公共的中间件
+	r.Use(middleware.Common())
+
+	//swagger界面访问地址 http://localhost:8390/swagger/index.html
+	//r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
+	rBase := r.Group("api/")
+	//系统相关路由
+	indexGroup := rBase.Group("index/")
+	routers.InitIndex(indexGroup)
+	return
+}

+ 55 - 0
init_serve/task.go

@@ -0,0 +1,55 @@
+package init_serve
+
+import (
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/services"
+	"hongze/hongtao3_watch/watch"
+
+	"github.com/robfig/cron/v3"
+)
+
+func InitTask() {
+	//services.RefreshExcel()
+	c := cron.New(cron.WithSeconds())
+
+	// 每天下午5点开始刷新excel表格
+	_, err := c.AddFunc(global.CONFIG.Serve.RefreshTime, services.RefreshExcel)
+	//_, err := c.AddFunc("0 01 15 * * *", services.RefreshExcel)
+	if err != nil {
+		global.LOG.Info("RefreshExcel err" + err.Error())
+	}
+	c.Start()
+
+	// 监听文件
+	go watch.ListenFolderNew()
+
+	// 处理excel
+	go services.HandleFileUpdate()
+
+	// 处理调用刷新excel的服务(调用python)
+	go services.HandleRefreshExcel()
+
+	////redis 队列刷新指标
+	//go services.AutoRefresh()
+
+	//CheckIndexCreate()
+	//services.Merge()
+	//yearFile="year.xlsx"
+
+	//filePath := utils.IndexMsergeSaveDir + "season.xlsx"
+	//services.DataAnalysis(filePath)
+
+	//services.Merge()
+}
+
+//检测指标数据是否生成
+//func CheckIndexCreate() {
+//	var err error
+//	defer func() {
+//		if err != nil && err.Error() != "record not found" {
+//			global.LOG.Info(utils.APPNAME + " 定时任务 出错" + time.Now().Format("2006-01-02 15:04:05") + ";Err:" + err.Error())
+//			go alarm_msg.SendAlarmMsg(utils.APPNAME+" 定时任务 出错"+time.Now().Format("2006-01-02 15:04:05")+";Err:"+err.Error(), 3)
+//		}
+//	}()
+//	err = services.IndexCreateCheck()
+//}

+ 0 - 0
logic/logic.txt


+ 74 - 0
main.go

@@ -0,0 +1,74 @@
+package main
+
+import (
+	"fmt"
+	"hongze/hongtao3_watch/core"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+)
+
+// @BasePath /
+func main() {
+	core.RunServe()
+}
+
+// 检测指标文件
+//
+//x:1034
+//y:598
+func TestFileName(filePath string) {
+	fmt.Println("filePath:", filePath)
+	var newFilePath string
+	defer func() {
+		//重命名文件
+		fmt.Println("newFilePath")
+		fmt.Println(newFilePath)
+		err := os.Rename(filePath, newFilePath)
+		if err != nil {
+			fmt.Println("os.Rename Err:" + err.Error())
+		}
+	}()
+	var runMode string
+	if strings.Contains(filePath, "debug") {
+		runMode = "debug"
+	} else {
+		runMode = "release"
+	}
+	fmt.Println(runMode)
+
+	//处理文件名
+	dir, fp := filepath.Split(filePath)
+	fmt.Println(dir)
+	fmt.Println(fp)
+	frequency := "年"
+
+	var wg = sync.WaitGroup{}
+	wg.Add(1)
+	go func() {
+		var frequencyStr string
+		if strings.Contains(frequency, "日") {
+			frequencyStr = "day"
+		} else if strings.Contains(frequency, "周") {
+			frequencyStr = "week"
+		} else if strings.Contains(frequency, "月") {
+			frequencyStr = "month"
+		} else if strings.Contains(frequency, "年") {
+			frequencyStr = "year"
+		}
+		if !strings.Contains(filePath, frequencyStr) {
+			fpArr := strings.Split(fp, "_")
+			for k, v := range fpArr {
+				if k == 0 {
+					newFilePath = v + "_" + frequencyStr
+				} else {
+					newFilePath = newFilePath + "_" + v
+				}
+			}
+		}
+		wg.Done()
+	}()
+	wg.Wait()
+	newFilePath = dir + newFilePath
+}

+ 28 - 0
middleware/check_base_auth.go

@@ -0,0 +1,28 @@
+package middleware
+
+import (
+	"github.com/gin-gonic/gin"
+)
+
+// CheckBaseAuth 基本权限校验
+func CheckBaseAuth() gin.HandlerFunc {
+
+	return func(c *gin.Context) {
+
+		//userInfo := user.GetInfoByClaims(c)
+		//
+		//ok, checkInfo, _, err := company.CheckBaseFiccPermission(userInfo.CompanyID, int(userInfo.UserID))
+		//if err != nil {
+		//	resp.FailMsg("用户权限验证失败", "CheckBaseAuth-用户权限验证失败" + err.Error(), c)
+		//	c.Abort()
+		//	return
+		//}
+		//if !ok {
+		//	resp.AuthError(checkInfo, "暂无权限", c)
+		//	c.Abort()
+		//	return
+		//}
+
+		c.Next()
+	}
+}

+ 27 - 0
middleware/common.go

@@ -0,0 +1,27 @@
+package middleware
+
+import (
+	"github.com/gin-gonic/gin"
+	"strconv"
+)
+
+// Common 公共中间件
+func Common() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		var currPage, pageSize int
+		reqPage := c.DefaultQuery("curr_page", "0")
+		currPage, _ = strconv.Atoi(reqPage)
+		if currPage <= 0 {
+			currPage = 1
+		}
+
+		reqPageSize := c.DefaultQuery("page_size", "0")
+		pageSize, _ = strconv.Atoi(reqPageSize)
+		if pageSize <= 0 {
+			pageSize = 20
+		}
+		c.Set("curr_page", currPage)
+		c.Set("page_size", pageSize)
+		c.Next()
+	}
+}

+ 27 - 0
middleware/cors.go

@@ -0,0 +1,27 @@
+package middleware
+
+import (
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
+
+// Cors 处理跨域请求,支持options访问
+func Cors() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		method := c.Request.Method
+
+		c.Header("Access-Control-Allow-Origin", "*")
+		//c.Header("Access-Control-Allow-Origin", c.Request.Referer())
+		c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id")
+		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
+		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
+		c.Header("Access-Control-Allow-Credentials", "true")
+
+		// 放行所有OPTIONS方法
+		if method == "OPTIONS" {
+			c.AbortWithStatus(http.StatusNoContent)
+		}
+		// 处理请求
+		c.Next()
+	}
+}

+ 57 - 0
middleware/recover.go

@@ -0,0 +1,57 @@
+package middleware
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"hongze/hongtao3_watch/controller/resp"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/services/alarm_msg"
+	"hongze/hongtao3_watch/utils"
+	"net/http"
+	"runtime"
+	"time"
+)
+
+// Recover 异常处理
+func Recover() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		contentType := c.ContentType()
+		// 为 multipart forms 设置较低的内存限制(50M) (默认是 32 MiB)
+		if contentType == "multipart/form-data" {
+			err := c.Request.ParseMultipartForm(10 << 20)
+			if err != nil {
+				resp.Custom(http.StatusRequestEntityTooLarge, "上传文件太大,err:"+err.Error(), c)
+				c.Abort()
+				return
+			}
+		}
+		defer func() {
+			if err := recover(); err != any(nil) {
+				stack := ""
+
+				msg := fmt.Sprintf("The request url is  %v", c.Request.RequestURI)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				msg = fmt.Sprintf("The request data is %v", c.Params)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				msg = fmt.Sprintf("Handler crashed with error %v", err)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				for i := 1; ; i++ {
+					_, file, line, ok := runtime.Caller(i)
+					if !ok {
+						break
+					}
+					global.LOG.Critical(fmt.Sprintf("%s:%d", file, line))
+					stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d</br>", file, line))
+				}
+				fmt.Println("stack:", stack)
+				resp.Custom(http.StatusInternalServerError, "系统异常", c)
+				go alarm_msg.SendAlarmMsg(utils.APPNAME+"崩了"+time.Now().Format("2006-01-02 15:04:05")+";Err:"+stack, 3)
+				return
+			}
+		}()
+		c.Next()
+	}
+}

+ 27 - 0
middleware/token.go

@@ -0,0 +1,27 @@
+package middleware
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"hongze/hongtao3_watch/controller/resp"
+)
+
+func Token() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		token := c.Request.Header.Get("Authorization")
+		if token == "" {
+			token = c.DefaultQuery("authorization", "")
+			if token == "" {
+				token = c.DefaultQuery("Authorization", "")
+			}
+		}
+		if token == "" {
+			resp.TokenError(nil, "未登录或非法访问", "未登录或非法访问", c)
+			c.Abort()
+			return
+		}
+		fmt.Println("token:" + token)
+		//c.Set("adminInfo", admin)
+		c.Next()
+	}
+}

+ 80 - 0
middleware/token_no_login.go

@@ -0,0 +1,80 @@
+package middleware
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hongtao3_watch/controller/resp"
+)
+
+func TokenNoLogin() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		token := c.Request.Header.Get("Authorization")
+		if token == "" {
+			token = c.GetString("authorization")
+			if token == "" {
+				token = c.GetString("Authorization")
+			}
+		}
+		if token == "" {
+			resp.TokenError(nil, "未登录或非法访问", "token为空", c)
+			c.Abort()
+			return
+		}
+		//sessionInfo, err := session.GetTokenByToken(token)
+		//if err != nil {
+		//	if err == utils.ErrNoRow {
+		//		resp.TokenError(nil, "信息已变更,请重新登陆!", "找不到session", c)
+		//		c.Abort()
+		//		return
+		//	}
+		//	resp.TokenError(nil, "网络异常,请稍后重试!", err.Error(), c)
+		//	c.Abort()
+		//	return
+		//}
+		//
+		//if sessionInfo == nil {
+		//	resp.TokenError(nil, "网络异常,请稍后重试!", "session为空", c)
+		//	c.Abort()
+		//	return
+		//}
+		//
+		//var userInfo services.UserInfo
+		//
+		//if sessionInfo.OpenID != "" {
+		//	tmpUserInfo, tmpErr := services.GetWxUserItemByOpenId(sessionInfo.OpenID)
+		//	userInfo = tmpUserInfo
+		//	err = tmpErr
+		//	if err != nil && err != services.ERR_NO_USER_RECORD && err != services.ERR_USER_NOT_BIND{
+		//		resp.TokenError(nil, "数据异常!", "openid查询用户信息错误", c)
+		//		c.Abort()
+		//		return
+		//	}
+		//} else {
+		//	//判断pc端登录的情况
+		//	tmpUserInfo, tmpErr := services.GetWxUserItemByUserId(int(sessionInfo.UserID), 3)
+		//	userInfo = tmpUserInfo
+		//	err = tmpErr
+		//	if err != nil {
+		//		resp.TokenError(nil, "数据异常!", "userID查询用户信息错误", c)
+		//		c.Abort()
+		//		return
+		//	}
+		//}
+		//
+		////如果查询异常,且异常信息不是:用户openid查询出来发现没有绑定用户
+		//if err != nil && err != services.ERR_USER_NOT_BIND {
+		//	//没有找到记录
+		//	if err == utils.ErrNoRow {
+		//		resp.TokenError(nil, "信息已变更,请重新登陆!", err.Error(), c)
+		//		c.Abort()
+		//		return
+		//	}
+		//
+		//	resp.TokenError(nil, "网络异常,请稍后重试!", err.Error(), c)
+		//	c.Abort()
+		//	return
+		//}
+		//
+		//c.Set("userInfo", userInfo)
+		c.Next()
+	}
+}

+ 171 - 0
models/base/base.go

@@ -0,0 +1,171 @@
+package base
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"gorm.io/gorm"
+)
+
+var globalIsRelated bool = true // 全局预加载
+
+// prepare for other
+type BaseMgr struct {
+	*gorm.DB
+	Ctx       context.Context
+	cancel    context.CancelFunc
+	timeout   time.Duration
+	isRelated bool
+}
+
+// SetTimeOut set timeout
+func (obj *BaseMgr) SetTimeOut(timeout time.Duration) {
+	obj.Ctx, obj.cancel = context.WithTimeout(context.Background(), timeout)
+	obj.timeout = timeout
+}
+
+// SetCtx set context
+func (obj *BaseMgr) SetCtx(c context.Context) {
+	if c != nil {
+		obj.Ctx = c
+	}
+}
+
+// GetCtx get context
+func (obj *BaseMgr) GetCtx() context.Context {
+	return obj.Ctx
+}
+
+// Cancel cancel context
+func (obj *BaseMgr) Cancel(c context.Context) {
+	obj.cancel()
+}
+
+// GetDB get gorm.DB info
+func (obj *BaseMgr) GetDB() *gorm.DB {
+	return obj.DB
+}
+
+// UpdateDB update gorm.DB info
+func (obj *BaseMgr) UpdateDB(db *gorm.DB) {
+	obj.DB = db
+}
+
+// GetIsRelated Query foreign key Association.获取是否查询外键关联(gorm.Related)
+func (obj *BaseMgr) GetIsRelated() bool {
+	return obj.isRelated
+}
+
+// SetIsRelated Query foreign key Association.设置是否查询外键关联(gorm.Related)
+func (obj *BaseMgr) SetIsRelated(b bool) {
+	obj.isRelated = b
+}
+
+// New new gorm.新gorm,重置条件
+func (obj *BaseMgr) New() {
+	obj.DB = obj.NewDB()
+}
+
+// NewDB new gorm.新gorm
+func (obj *BaseMgr) NewDB() *gorm.DB {
+	return obj.DB.Session(&gorm.Session{NewDB: true, Context: obj.Ctx})
+}
+
+type Options struct {
+	Query map[string]interface{}
+}
+
+// Option overrides behavior of Connect.
+type Option interface {
+	Apply(*Options)
+}
+
+type OptionFunc func(*Options)
+
+func (f OptionFunc) apply(o *Options) {
+	f(o)
+}
+
+// OpenRelated 打开全局预加载
+func OpenRelated() {
+	globalIsRelated = true
+}
+
+// CloseRelated 关闭全局预加载
+func CloseRelated() {
+	globalIsRelated = true
+}
+
+// 自定义sql查询
+type Condition struct {
+	list []*conditionInfo
+}
+
+func (c *Condition) AndWithCondition(condition bool, column string, cases string, value interface{}) *Condition {
+	if condition {
+		c.list = append(c.list, &conditionInfo{
+			andor:  "and",
+			column: column, // 列名
+			case_:  cases,  // 条件(and,or,in,>=,<=)
+			value:  value,
+		})
+	}
+	return c
+}
+
+// And a Condition by and .and 一个条件
+func (c *Condition) And(column string, cases string, value interface{}) *Condition {
+	return c.AndWithCondition(true, column, cases, value)
+}
+
+func (c *Condition) OrWithCondition(condition bool, column string, cases string, value interface{}) *Condition {
+	if condition {
+		c.list = append(c.list, &conditionInfo{
+			andor:  "or",
+			column: column, // 列名
+			case_:  cases,  // 条件(and,or,in,>=,<=)
+			value:  value,
+		})
+	}
+	return c
+}
+
+// Or a Condition by or .or 一个条件
+func (c *Condition) Or(column string, cases string, value interface{}) *Condition {
+	return c.OrWithCondition(true, column, cases, value)
+}
+
+func (c *Condition) Get() (where string, out []interface{}) {
+	firstAnd := -1
+	for i := 0; i < len(c.list); i++ { // 查找第一个and
+		if c.list[i].andor == "and" {
+			where = fmt.Sprintf("`%v` %v ?", c.list[i].column, c.list[i].case_)
+			out = append(out, c.list[i].value)
+			firstAnd = i
+			break
+		}
+	}
+
+	if firstAnd < 0 && len(c.list) > 0 { // 补刀
+		where = fmt.Sprintf("`%v` %v ?", c.list[0].column, c.list[0].case_)
+		out = append(out, c.list[0].value)
+		firstAnd = 0
+	}
+
+	for i := 0; i < len(c.list); i++ { // 添加剩余的
+		if firstAnd != i {
+			where += fmt.Sprintf(" %v `%v` %v ?", c.list[i].andor, c.list[i].column, c.list[i].case_)
+			out = append(out, c.list[i].value)
+		}
+	}
+
+	return
+}
+
+type conditionInfo struct {
+	andor  string
+	column string // 列名
+	case_  string // 条件(in,>=,<=)
+	value  interface{}
+}

+ 165 - 0
models/base/page.go

@@ -0,0 +1,165 @@
+package base
+
+import (
+	"fmt"
+	"strings"
+)
+
+type IPage interface {
+	GetTotal() int64             //获取总记录数
+	SetTotal(int64)              //设置总记录数
+	GetCurrent() int64           //获取当前页
+	SetCurrent(int64)            //设置当前页
+	GetPageSize() int64          //获取每页显示大小
+	SetPageSize(int64)           //设置每页显示大小
+	AddOrderItem(OrderItem)      // 设置排序条件
+	AddOrderItems([]OrderItem)   // 批量设置排序条件
+	GetOrderItemsString() string // 将排序条件拼接成字符串
+	Offset() int64               // 获取偏移量
+	GetPages() int64             // 获取总的分页数
+}
+
+type OrderItem struct {
+	column string // 需要排序的字段
+	asc    bool   // 是否正序排列,默认true
+}
+
+type BaseData struct {
+	Page *Page        `json:"page"`
+	List interface{} `json:"list"` // 查询数据列表
+}
+
+type Page struct {
+	Total    int64       `json:"total"`     // 总的记录数
+	Pages    int64       `json:"pages"`     //总页数
+	PageSize int64       `json:"page_size"` // 每页显示的大小
+	Current  int64       `json:"current"`   // 当前页
+	orders   []OrderItem `json:"orders"`    // 排序条件
+}
+
+type PageReq struct {
+	PageSize int64 `json:"page_size" form:"page_size,default=20"`
+	Current  int64 `json:"current" form:"current,default=1"`
+}
+
+func (b *BaseData) GetList() interface{} {
+	return b.List
+}
+
+func (b *BaseData) SetList(list interface{}) {
+	b.List = list
+}
+
+func (b *BaseData) GetPage() *Page {
+	return b.Page
+}
+func (b *BaseData) SetPage(page *Page) {
+	b.Page = page
+}
+
+func (page *Page) GetTotal() int64 {
+	return page.Total
+}
+
+func (page *Page) SetTotal(total int64) {
+	page.Total = total
+	page.SetPages()
+}
+
+func (page *Page) GetCurrent() int64 {
+	return page.Current
+}
+
+func (page *Page) SetCurrent(current int64) {
+	page.Current = current
+}
+
+func (page *Page) GetPageSize() int64 {
+	return page.PageSize
+}
+func (page *Page) SetPageSize(size int64) {
+	page.PageSize = size
+}
+
+func (page *Page) AddOrderItem(orderItem OrderItem) {
+	page.orders = append(page.orders, orderItem)
+}
+
+func (page *Page) AddOrderItems(orderItems []OrderItem) {
+	page.orders = append(page.orders, orderItems...)
+}
+
+func (page *Page) GetOrderItemsString() string {
+	arr := make([]string, 0)
+	var order string
+
+	for _, val := range page.orders {
+		if val.asc {
+			order = ""
+		} else {
+			order = "desc"
+		}
+		arr = append(arr, fmt.Sprintf("%s %s", val.column, order))
+	}
+	return strings.Join(arr, ",")
+}
+
+func (page *Page) Offset() int64 {
+	if page.GetCurrent() > 0 {
+		return (page.GetCurrent() - 1) * page.GetPageSize()
+	} else {
+		return 0
+	}
+}
+
+func (page *Page) GetPages() int64 {
+	if page.GetPageSize() == 0 {
+		return 0
+	}
+	pages := page.GetTotal() / page.GetPageSize()
+	if page.GetTotal()%page.PageSize != 0 {
+		pages++
+	}
+
+	return pages
+}
+
+func (page *Page) SetPages() {
+	if page.GetPageSize() == 0 {
+		page.Pages = 0
+	} else {
+		pages := page.GetTotal() / page.GetPageSize()
+		if page.GetTotal()%page.PageSize != 0 {
+			pages++
+		}
+		page.Pages = pages
+	}
+}
+
+func BuildAsc(column string) OrderItem {
+	return OrderItem{column: column, asc: true}
+}
+
+func BuildDesc(column string) OrderItem {
+	return OrderItem{column: column, asc: false}
+}
+
+func BuildAscs(columns ...string) []OrderItem {
+	items := make([]OrderItem, 0)
+	for _, val := range columns {
+		items = append(items, BuildAsc(val))
+	}
+	return items
+}
+
+func BuildDescs(columns ...string) []OrderItem {
+	items := make([]OrderItem, 0)
+	for _, val := range columns {
+		items = append(items, BuildDesc(val))
+	}
+	return items
+}
+
+func NewPage(pageSize, current int64, orderItems ...OrderItem) *Page {
+	return &Page{PageSize: pageSize, Current: current, orders: orderItems}
+}

+ 13 - 0
models/base/time_base.go

@@ -0,0 +1,13 @@
+package base
+
+import "time"
+
+type TimeBase struct {
+	CreateTime time.Time `gorm:"autoCreateTime;column:create_time" json:"create_time"`       //创建时间
+	ModifyTime time.Time `gorm:"autoUpdateTime:milli;column:modify_time" json:"modify_time"` //最后更新时间
+}
+
+func (item *TimeBase) Set() {
+	item.CreateTime = time.Now()
+	item.ModifyTime = time.Now()
+}

+ 13 - 0
routers/auth.go

@@ -0,0 +1,13 @@
+package routers
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hongtao3_watch/controller"
+)
+
+func InitAuth(baseGroup *gin.RouterGroup) {
+	//登录
+	authController := new(controller.AuthController)
+	authGroup := baseGroup.Group("auth/")
+	authGroup.POST("login", authController.Login)
+}

+ 15 - 0
routers/index.go

@@ -0,0 +1,15 @@
+package routers
+
+import (
+	"github.com/gin-gonic/gin"
+)
+
+func InitIndex(group *gin.RouterGroup) {
+	//指标
+	//indexController := new(index.IndexController)
+	//group.GET("server_check",indexController.ServerCheck)
+	//group.GET("test",indexController.Test)
+	//group.POST("create", indexController.Create)
+	//group.POST("delete", indexController.Delete)
+	//group.POST("refresh", indexController.Refresh)
+}

+ 32 - 0
services/alarm_msg/alarm_msg.go

@@ -0,0 +1,32 @@
+package alarm_msg
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/http"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/utils"
+)
+
+var (
+	AlarmMsgUrl = "http://47.102.213.75:8606/api/alarm/send"
+)
+
+//projectName-项目名称
+//runMode-运行模式
+//msgBody-消息内容
+//level:消息基本,1:提示消息,2:警告消息,3:严重错误信息,默认为1 提示消息
+func SendAlarmMsg(msgBody string, level int) {
+	params := make(map[string]interface{})
+	params["ProjectName"] = utils.APPNAME
+	params["RunMode"] = "release"//global.CONFIG.Serve.RunMode
+	params["MsgBody"] = msgBody
+	params["Level"] = level
+	param, err := json.Marshal(params)
+	if err != nil {
+		global.LOG.Critical("SendAlarmMsg json.Marshal Err:" + err.Error())
+		return
+	}
+	r, e := http.Post(AlarmMsgUrl, string(param))
+	fmt.Println("SendAlarmMsg", string(r), e)
+}

+ 27 - 0
services/api_tool.go

@@ -0,0 +1,27 @@
+package services
+
+import (
+	"fmt"
+	"net/url"
+
+	"github.com/rdlucklib/rdluck_tools/http"
+)
+
+const (
+	// RefreshUrl 值示例:http://127.0.0.1:7007/hongtao/refresh?FilePath=E:\hz\hongtao3_data\excel\Oneline\%E7%83%A7%E7%A2%B1.xlsx
+	RefreshUrl = "http://127.0.0.1:7007/hongqi/refresh"
+)
+
+// InvokeRefreshServer 调用卓创红期刷新数据的服务(目前是python)
+func InvokeRefreshServer(filePath string) {
+	var refreshUrl string
+	//filePathStr:=url.PathEscape(filePath)
+	refreshUrl = RefreshUrl + "?FilePath=" + url.QueryEscape(filePath)
+	fmt.Println("卓创红期刷新,文件路径:", filePath, ";刷新URL:"+refreshUrl)
+	body, err := http.Get(refreshUrl)
+	if err != nil {
+		fmt.Println("卓创红期刷新 Err:" + err.Error())
+		return
+	}
+	fmt.Println("卓创红期刷新,文件路径:", filePath, " ,Result:"+string(body))
+}

+ 130 - 0
services/hq_index.go

@@ -0,0 +1,130 @@
+package services
+
+import (
+	"encoding/json"
+	"fmt"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/utils"
+	"strings"
+	"time"
+
+	"github.com/xuri/excelize/v2"
+)
+
+type SciHqExcel struct {
+	IndexName      string
+	ExcelIndexCode string
+	Frequency      string
+	Unit           string
+	TerminalCode   string
+	FilePath       string
+	Data           map[string]string
+}
+
+var dateFormats = []string{"01-02-06", utils.FormatDate}
+
+func ReadHqExcel(filePath string) {
+	// time.Sleep(time.Second)
+
+	excelInfo, err := excelize.OpenFile(filePath)
+	if err != nil {
+		fmt.Println("OpenFile excel err:" + err.Error())
+		return
+	}
+	fmt.Println("开始读取EXCEL了")
+
+	defer func() {
+		excelInfo.Close()
+	}()
+
+	sheetList := excelInfo.GetSheetList()
+
+	excelInfoList := make([]*SciHqExcel, 0)
+	for _, sheet := range sheetList {
+		rows, err := excelInfo.GetRows(sheet)
+		if err != nil {
+			fmt.Println("err:", err)
+			continue
+		}
+		if len(rows) < 6 {
+			fmt.Println("错误的excel表")
+			continue
+		}
+		indexLen := len(rows[0])
+
+		// 没有指标
+		if indexLen <= 1 {
+			fmt.Println("没有指标")
+			continue
+		}
+		for col := 1; col < indexLen; col++ {
+			indexInfo := new(SciHqExcel)
+			dataLen := len(rows)
+			if rows[0][0] != "数据名称" || rows[1][0] != "数据项ID" || rows[2][0] != "发布周期" || rows[3][0] != "单位" || rows[4][0] != "频率" {
+				fmt.Println("sheet错误,ErrSheetName:", sheet)
+				break
+			} else {
+				indexInfo.IndexName = rows[0][col]
+				indexInfo.ExcelIndexCode = rows[1][col]
+				indexInfo.Unit = rows[3][col]
+				if strings.Contains(rows[4][col], "日度") {
+					indexInfo.Frequency = "日度"
+				} else if strings.Contains(rows[4][col], "周度") {
+					indexInfo.Frequency = "周度"
+				} else if strings.Contains(rows[4][col], "旬度") {
+					indexInfo.Frequency = "旬度"
+				} else if strings.Contains(rows[4][col], "月度") {
+					indexInfo.Frequency = "月度"
+				} else if strings.Contains(rows[4][col], "季度") {
+					indexInfo.Frequency = "季度"
+				} else if strings.Contains(rows[4][col], "年度") {
+					indexInfo.Frequency = "年度"
+				} else {
+					indexInfo.Frequency = "周度"
+				}
+				indexInfo.Frequency = rows[4][col]
+				indexInfo.Data = make(map[string]string)
+				indexInfo.FilePath = filePath
+				indexInfo.TerminalCode = global.CONFIG.Serve.TerminalCode
+			}
+			for row := 5; row < dataLen; row++ {
+				var newTime time.Time
+				var tmpErr error
+				for _, v := range dateFormats {
+					newTime, tmpErr = time.Parse(v, rows[row][0])
+					if !newTime.IsZero() {
+						break
+					}
+					if tmpErr != nil {
+						continue
+					}
+				}
+				if newTime.IsZero() {
+					fmt.Println("日期类型错误,ErrDate:", rows[row][0])
+					continue
+				}
+				// 没有数据
+				if rows[row][col] == "" {
+					continue
+				}
+				indexInfo.Data[newTime.Format(utils.FormatDate)] = rows[row][col]
+			}
+			excelInfoList = append(excelInfoList, indexInfo)
+		}
+	}
+
+	resp, err := HandleHqExcelDataByEdbLib(excelInfoList)
+	if err != nil {
+		global.LOG.Info("调用指标库公共服务处理数据失败,err:" + err.Error())
+		return
+	}
+	if resp.Ret != 200 {
+		b, err := json.Marshal(resp)
+		if err != nil {
+			fmt.Println("json.Marshal err:" + err.Error())
+			return
+		}
+		fmt.Println("调用指标库公共服务处理数据失败, return:" + string(b))
+		global.LOG.Info("调用指标库公共服务处理数据失败,return:" + string(b))
+	}
+}

+ 1106 - 0
services/index_merge.go

@@ -0,0 +1,1106 @@
+package services
+//
+//import (
+//	"encoding/json"
+//	"fmt"
+//	"hongze/hongtao3_watch/global"
+//	"hongze/hongtao3_watch/models/index"
+//	"hongze/hongtao3_watch/utils"
+//	"strconv"
+//	"strings"
+//	"sync"
+//	"time"
+//
+//	"github.com/rdlucklib/rdluck_tools/paging"
+//	"github.com/xuri/excelize/v2"
+//)
+//
+//// Merge 合并指标
+//func Merge() {
+//	fmt.Println("merge start")
+//	/*
+//		年底,季度,存放在一个excel中
+//		月度30个指标存一个excel
+//		周度25个指标存放一个excel
+//		日度20个指标存一个excel
+//	*/
+//	//年度->38
+//	IndexYearMerge()
+//	//季度->5
+//	IndexSeasonMerge()
+//	//月度->86
+//	IndexMonthMerge()
+//	//周度->292
+//	IndexWeekMerge()
+//	//日度->114
+//	IndexDayMerge()
+//	//merge_file_path
+//	fmt.Println("merge end")
+//}
+//
+////年度
+//func IndexYearMerge() {
+//	frequency := "年度"
+//	fileName := "year" + ".xlsx" //保存的文件名称
+//	yearIndexFilePath := utils.IndexMsergeSaveDir + fileName
+//
+//	var err error
+//
+//	indexObj := new(index.BaseFromMysteelChemicalIndex)
+//	yearList, err := indexObj.GetIndexByFrequency(frequency)
+//	if err != nil {
+//		fmt.Println("GetIndexByFrequency Err:" + err.Error())
+//		return
+//	}
+//
+//	if len(yearList) <= 0 {
+//		return
+//	}
+//
+//	commentResult, err := GetIndexComment(yearIndexFilePath, yearList, 1)
+//	if err != nil {
+//		fmt.Println("GetIndexComment Err:" + err.Error())
+//		return
+//	}
+//	if utils.FileIsExist(yearIndexFilePath) { //修改文件
+//		fileObj, err := excelize.OpenFile(yearIndexFilePath)
+//		if err != nil {
+//			fmt.Println("打开文件失败,Err:" + err.Error())
+//			return
+//		}
+//
+//		fileObj.DeleteComment("Sheet1", "A1")
+//		fileObj.AddComment("Sheet1", "A1", commentResult)
+//		if err := fileObj.SaveAs(yearIndexFilePath); err != nil {
+//			fmt.Println("保存失败,Err:" + err.Error())
+//			fileObj.Close()
+//			return
+//		}
+//		fileObj.Close()
+//	} else { //新增文件
+//		templatePath := utils.IndexSaveDir + "index_template.xlsx"
+//		templateFile, err := excelize.OpenFile(templatePath)
+//		if err != nil {
+//			fmt.Println("打开文件失败,Err:" + err.Error())
+//			return
+//		}
+//		templateFile.DeleteComment("Sheet1", "A1")
+//		templateFile.AddComment("Sheet1", "A1", commentResult)
+//		if err := templateFile.SaveAs(yearIndexFilePath); err != nil {
+//			fmt.Println("保存失败,Err:" + err.Error())
+//			templateFile.Close()
+//			return
+//		}
+//		templateFile.Close()
+//	}
+//	//刷新数据
+//	//MysteelChemicalRefresh(yearIndexFilePath)
+//	AddIndexRefreshToLpush(yearIndexFilePath)
+//}
+//
+////季度
+//func IndexSeasonMerge() {
+//	frequency := "季度"
+//	fileName := "season" + ".xlsx" //保存的文件名称
+//	seasonIndexFilePath := utils.IndexMsergeSaveDir + fileName
+//
+//	var err error
+//
+//	indexObj := new(index.BaseFromMysteelChemicalIndex)
+//	seasonList, err := indexObj.GetIndexByFrequency(frequency)
+//	if err != nil {
+//		fmt.Println("GetIndexByFrequency Err:" + err.Error())
+//		return
+//	}
+//
+//	if len(seasonList) <= 0 {
+//		return
+//	}
+//
+//	commentResult, err := GetIndexComment(seasonIndexFilePath, seasonList, 1)
+//	if err != nil {
+//		fmt.Println("GetIndexComment Err:" + err.Error())
+//		return
+//	}
+//	if utils.FileIsExist(seasonIndexFilePath) { //修改文件
+//		fileObj, err := excelize.OpenFile(seasonIndexFilePath)
+//		if err != nil {
+//			fmt.Println("打开文件失败,Err:" + err.Error())
+//			return
+//		}
+//
+//		fileObj.DeleteComment("Sheet1", "A1")
+//		fileObj.AddComment("Sheet1", "A1", commentResult)
+//		if err := fileObj.SaveAs(seasonIndexFilePath); err != nil {
+//			fmt.Println("保存失败,Err:" + err.Error())
+//			fileObj.Close()
+//			return
+//		}
+//		fileObj.Close()
+//	} else { //新增文件
+//		templatePath := utils.IndexSaveDir + "index_template.xlsx"
+//		templateFile, err := excelize.OpenFile(templatePath)
+//		if err != nil {
+//			fmt.Println("打开文件失败,Err:" + err.Error())
+//			return
+//		}
+//		templateFile.DeleteComment("Sheet1", "A1")
+//		templateFile.AddComment("Sheet1", "A1", commentResult)
+//		if err := templateFile.SaveAs(seasonIndexFilePath); err != nil {
+//			fmt.Println("保存失败,Err:" + err.Error())
+//			templateFile.Close()
+//			return
+//		}
+//		templateFile.Close()
+//	}
+//	//刷新数据
+//	//MysteelChemicalRefresh(seasonIndexFilePath)
+//	AddIndexRefreshToLpush(seasonIndexFilePath)
+//}
+//
+////月度
+////func IndexMonthMerge() {
+////	frequency := "月度"
+////	filePre := "month"
+////	pageSize := 30
+////	var err error
+////
+////	indexObj := new(index.BaseFromMysteelChemicalIndex)
+////	total, err := indexObj.GetIndexByFrequencyCount(frequency)
+////	if err != nil {
+////		fmt.Println("GetIndexByFrequencyCount Err:" + err.Error())
+////		return
+////	}
+////
+////	maxFileIndexItem, err := indexObj.GetMaxFileIndex(frequency)
+////	if err != nil {
+////		fmt.Println("GetMaxFileIndex Err:" + err.Error())
+////		return
+////	}
+////
+////	maxFileIndex := maxFileIndexItem.FileIndex
+////	if maxFileIndex <= 0 {
+////		maxFileIndex = 1
+////	}
+////
+////	fileName := filePre + "_" + strconv.Itoa(maxFileIndex) + ".xlsx" //保存的文件名称
+////	monthIndexFilePath := utils.IndexMsergeSaveDir + fileName
+////
+////	indexTotal, err := indexObj.GetIndexCountByMergeFilePath(monthIndexFilePath)
+////	if err != nil {
+////		fmt.Println("GetIndexCountByMergeFilePath Err:" + err.Error())
+////		return
+////	}
+////
+////	if (indexTotal > int64(pageSize)/2) && maxFileIndex != 1 {
+////		maxFileIndex += 1
+////	}
+////
+////	fmt.Println("maxFileIndex:", maxFileIndex)
+////	totalPage := paging.PageCount(int(total), pageSize)
+////	fmt.Println("total:", total)
+////	fmt.Println("totalPage:", totalPage)
+////	for i := 1; i <= totalPage; i++ {
+////
+////		offset := (i - 1) * pageSize
+////
+////		fileName = filePre + "_" + strconv.Itoa(maxFileIndex) + ".xlsx" //保存的文件名称
+////		monthIndexFilePath := utils.IndexMsergeSaveDir + fileName
+////		fmt.Println(monthIndexFilePath)
+////
+////		fmt.Println(offset, pageSize)
+////		pageList, err := indexObj.GetIndexByFrequencyPage(frequency, offset, pageSize)
+////		if err != nil {
+////			fmt.Println("GetIndexByFrequency Err:" + err.Error())
+////			return
+////		}
+////
+////		commentResult, err := GetIndexComment(monthIndexFilePath, pageList, maxFileIndex)
+////		if err != nil {
+////			fmt.Println("GetIndexComment Err:" + err.Error())
+////			return
+////		}
+////		if utils.FileIsExist(monthIndexFilePath) { //修改文件
+////			fileObj, err := excelize.OpenFile(monthIndexFilePath)
+////			if err != nil {
+////				fmt.Println("打开文件失败,Err:" + err.Error())
+////				return
+////			}
+////
+////			fileObj.DeleteComment("Sheet1", "A1")
+////			fileObj.AddComment("Sheet1", "A1", commentResult)
+////			if err := fileObj.SaveAs(monthIndexFilePath); err != nil {
+////				fmt.Println("保存失败,Err:" + err.Error())
+////				fileObj.Close()
+////				return
+////			}
+////			fileObj.Close()
+////		} else { //新增文件
+////			templatePath := utils.IndexSaveDir + "index_template.xlsx"
+////			templateFile, err := excelize.OpenFile(templatePath)
+////			if err != nil {
+////				fmt.Println("打开文件失败,Err:" + err.Error())
+////				return
+////			}
+////			templateFile.DeleteComment("Sheet1", "A1")
+////			templateFile.AddComment("Sheet1", "A1", commentResult)
+////			if err := templateFile.SaveAs(monthIndexFilePath); err != nil {
+////				fmt.Println("保存失败,Err:" + err.Error())
+////				templateFile.Close()
+////				return
+////			}
+////			templateFile.Close()
+////		}
+////		maxFileIndex += 1
+////		time.Sleep(1 * time.Second)
+////	}
+////}
+//
+//func IndexMonthMerge() {
+//	frequency := "月度"
+//	filePre := "month"
+//	pageSize := 30
+//	err := indexMerge(frequency, filePre, pageSize)
+//	if err != nil {
+//		fmt.Println("合并月度指标失败,err:", err)
+//	}
+//}
+//
+////日度
+////func IndexDayMerge() {
+////	frequency := "日度"
+////	filePre := "day"
+////	pageSize := 30
+////	var err error
+////
+////	indexObj := new(index.BaseFromMysteelChemicalIndex)
+////	total, err := indexObj.GetIndexByFrequencyCount(frequency)
+////	if err != nil {
+////		fmt.Println("GetIndexByFrequencyCount Err:" + err.Error())
+////		return
+////	}
+////
+////	maxFileIndexItem, err := indexObj.GetMaxFileIndex(frequency)
+////	if err != nil {
+////		fmt.Println("GetMaxFileIndex Err:" + err.Error())
+////		return
+////	}
+////
+////	maxFileIndex := maxFileIndexItem.FileIndex
+////	if maxFileIndex <= 0 {
+////		maxFileIndex = 1
+////	}
+////
+////	fileName := filePre + "_" + strconv.Itoa(maxFileIndex) + ".xlsx" //保存的文件名称
+////	monthIndexFilePath := utils.IndexMsergeSaveDir + fileName
+////
+////	indexTotal, err := indexObj.GetIndexCountByMergeFilePath(monthIndexFilePath)
+////	if err != nil {
+////		fmt.Println("GetIndexCountByMergeFilePath Err:" + err.Error())
+////		return
+////	}
+////
+////	if (indexTotal > int64(pageSize)/2) && maxFileIndex != 1 {
+////		maxFileIndex += 1
+////	}
+////
+////	fmt.Println("maxFileIndex:", maxFileIndex)
+////	totalPage := paging.PageCount(int(total), pageSize)
+////	fmt.Println("total:", total)
+////	fmt.Println("totalPage:", totalPage)
+////	for i := 1; i <= totalPage; i++ {
+////
+////		offset := (i - 1) * pageSize
+////
+////		fileName = filePre + "_" + strconv.Itoa(maxFileIndex) + ".xlsx" //保存的文件名称
+////		monthIndexFilePath := utils.IndexMsergeSaveDir + fileName
+////		fmt.Println(monthIndexFilePath)
+////
+////		fmt.Println(offset, pageSize)
+////		pageList, err := indexObj.GetIndexByFrequencyPage(frequency, offset, pageSize)
+////		if err != nil {
+////			fmt.Println("GetIndexByFrequency Err:" + err.Error())
+////			return
+////		}
+////
+////		commentResult, err := GetIndexComment(monthIndexFilePath, pageList, maxFileIndex)
+////		if err != nil {
+////			fmt.Println("GetIndexComment Err:" + err.Error())
+////			return
+////		}
+////		if utils.FileIsExist(monthIndexFilePath) { //修改文件
+////			fileObj, err := excelize.OpenFile(monthIndexFilePath)
+////			if err != nil {
+////				fmt.Println("打开文件失败,Err:" + err.Error())
+////				return
+////			}
+////
+////			fileObj.DeleteComment("Sheet1", "A1")
+////			fileObj.AddComment("Sheet1", "A1", commentResult)
+////			if err := fileObj.SaveAs(monthIndexFilePath); err != nil {
+////				fmt.Println("保存失败,Err:" + err.Error())
+////				fileObj.Close()
+////				return
+////			}
+////			fileObj.Close()
+////		} else { //新增文件
+////			templatePath := utils.IndexSaveDir + "index_template.xlsx"
+////			templateFile, err := excelize.OpenFile(templatePath)
+////			if err != nil {
+////				fmt.Println("打开文件失败,Err:" + err.Error())
+////				return
+////			}
+////			templateFile.DeleteComment("Sheet1", "A1")
+////			templateFile.AddComment("Sheet1", "A1", commentResult)
+////			if err := templateFile.SaveAs(monthIndexFilePath); err != nil {
+////				fmt.Println("保存失败,Err:" + err.Error())
+////				templateFile.Close()
+////				return
+////			}
+////			templateFile.Close()
+////		}
+////		maxFileIndex += 1
+////		time.Sleep(1 * time.Second)
+////	}
+////}
+//
+//// IndexDayMerge 日度指标合并
+//func IndexDayMerge() {
+//	frequency := "日度"
+//	filePre := "day"
+//	pageSize := 30 //每个excel表需要的指标数
+//	err := indexMerge(frequency, filePre, pageSize)
+//	if err != nil {
+//		fmt.Println("合并日度指标失败,err:", err)
+//	}
+//}
+//
+//// IndexWeekMerge 周度指标合并
+//func IndexWeekMerge() {
+//	frequency := "周度"
+//	filePre := "week"
+//	pageSize := 30 //每个excel表需要的指标数
+//	err := indexMerge(frequency, filePre, pageSize)
+//	if err != nil {
+//		fmt.Println("合并周度指标失败,err:", err)
+//	}
+//}
+//
+////func IndexWeekMerge() {
+////	frequency := "周度"
+////	filePre := "week"
+////	pageSize := 30	//每个excel表需要的指标数
+////	firstIndexCount := pageSize	//第一个添加的excel表需要的指标数
+////	var err error
+////
+////	indexObj := new(index.BaseFromMysteelChemicalIndex)
+////
+////	// 获取未合并的指标总数量
+////	total, err := indexObj.GetNoMergeIndexByFrequencyCount(frequency)
+////	if err != nil {
+////		fmt.Println("GetIndexByFrequencyCount Err:" + err.Error())
+////		return
+////	}
+////	//未合并的指标总数量小于等于0,说明没有需要合并的指标
+////	if total <=0{
+////		return
+////	}
+////
+////	// 获取最大的文件编号下标
+////	maxFileIndexItem, err := indexObj.GetMaxFileIndex(frequency)
+////	if err != nil {
+////		fmt.Println("GetMaxFileIndex Err:" + err.Error())
+////		return
+////	}
+////
+////	maxFileIndex := maxFileIndexItem.FileIndex
+////	if maxFileIndex <= 0 {
+////		maxFileIndex = 1
+////	}
+////
+////	fileName := filePre + "_" + strconv.Itoa(maxFileIndex) + ".xlsx" //保存的文件名称
+////	maxMergeIndexFilePath := utils.IndexMsergeSaveDir + fileName
+////
+////	// 根据文件下标获取该文件下标已经存在多少的指标
+////	indexTotal, err := indexObj.GetIndexCountByMergeFilePath(maxMergeIndexFilePath)
+////	if err != nil {
+////		fmt.Println("GetIndexCountByMergeFilePath Err:" + err.Error())
+////		return
+////	}
+////
+////	// 如果最近的excel表的指标数量 少于 每页需要的指标数
+////	if int(indexTotal) < pageSize {
+////		// 最近的excel表的待添加指标数 = 每页需要的指标数 - 已经添加的指标
+////		firstIndexCount = pageSize - int(indexTotal)
+////	}
+////
+////	fmt.Println("maxFileIndex:", maxFileIndex)
+////
+////	// 除去第一个excel表的剩余需要添加的指标数量
+////	nextTotal := int(total)-firstIndexCount
+////	// 总文件数
+////	totalPage := paging.PageCount(nextTotal, pageSize)
+////	totalPage = totalPage+1	//需要加上除去在外面的指标(第一张excel)
+////	fmt.Println("total:", total)
+////	fmt.Println("totalPage:", totalPage)
+////	for i := 1; i <= totalPage; i++ {
+////		//offset := (i - 1) * pageSize
+////
+////		fileName = filePre + "_" + strconv.Itoa(maxFileIndex) + ".xlsx" //保存的文件名称
+////		mergeIndexFilePath := utils.IndexMsergeSaveDir + fileName
+////		fmt.Println(mergeIndexFilePath)
+////
+////		var list []*index.BaseFromMysteelChemicalIndex
+////		// 需要查询的指标数
+////		size := pageSize
+////		if i == 1{
+////			size = firstIndexCount	// 第一页需要合并的新指标数
+////
+////			// 第一页需要的指标列表
+////			tmpList ,tmpErr := indexObj.GetIndexByFrequencyListByMergeFilePath(frequency,mergeIndexFilePath)
+////			if tmpErr != nil{
+////				fmt.Println("GetIndexByFrequencyListByMergeFilePath Err:" + err.Error())
+////				return
+////			}
+////			list = tmpList
+////		}
+////		fmt.Println("需要查询",size,"个指标")
+////
+////		//fmt.Println(offset, pageSize)
+////
+////		// 当下需要合并到excel的指标
+////		pageList, err := indexObj.GetNoMergeIndexByFrequencyPage(frequency, size)
+////		if err != nil {
+////			fmt.Println("GetIndexByFrequency Err:" + err.Error())
+////			return
+////		}
+////
+////		if len(list)>0{
+////			pageList = append(pageList,list...)
+////		}
+////
+////		commentResult, err := GetIndexComment(mergeIndexFilePath, pageList, maxFileIndex)
+////		if err != nil {
+////			fmt.Println("GetIndexComment Err:" + err.Error())
+////			return
+////		}
+////		if utils.FileIsExist(mergeIndexFilePath) { //修改文件
+////			fileObj, err := excelize.OpenFile(mergeIndexFilePath)
+////			if err != nil {
+////				fmt.Println("打开文件失败,Err:" + err.Error())
+////				return
+////			}
+////
+////			fileObj.DeleteComment("Sheet1", "A1")
+////			fileObj.AddComment("Sheet1", "A1", commentResult)
+////			if err := fileObj.SaveAs(mergeIndexFilePath); err != nil {
+////				fmt.Println("保存失败,Err:" + err.Error())
+////				fileObj.Close()
+////				return
+////			}
+////			fileObj.Close()
+////		} else { //新增文件
+////			templatePath := utils.IndexSaveDir + "index_template.xlsx"
+////			templateFile, err := excelize.OpenFile(templatePath)
+////			if err != nil {
+////				fmt.Println("打开文件失败,Err:" + err.Error())
+////				return
+////			}
+////			templateFile.DeleteComment("Sheet1", "A1")
+////			templateFile.AddComment("Sheet1", "A1", commentResult)
+////			if err := templateFile.SaveAs(mergeIndexFilePath); err != nil {
+////				fmt.Println("保存失败,Err:" + err.Error())
+////				templateFile.Close()
+////				return
+////			}
+////			templateFile.Close()
+////		}
+////		maxFileIndex += 1
+////		time.Sleep(1 * time.Second)
+////	}
+////}
+//
+//// indexMerge 指标合并
+//// @params frequency string 频度
+//// @params filePre string 文件名前缀
+//// @params pageSize int 每个excel表中的指标数量
+//func indexMerge(frequency, filePre string, pageSize int) (err error) {
+//	firstIndexCount := pageSize //第一个添加的excel表需要的指标数
+//	indexObj := new(index.BaseFromMysteelChemicalIndex)
+//
+//	// 获取未合并的指标总数量
+//	total, err := indexObj.GetNoMergeIndexByFrequencyCount(frequency)
+//	if err != nil {
+//		fmt.Println("GetIndexByFrequencyCount Err:" + err.Error())
+//		return
+//	}
+//	//未合并的指标总数量小于等于0,说明没有需要合并的指标
+//	if total <= 0 {
+//		return
+//	}
+//
+//	// 获取最大的文件编号下标
+//	maxFileIndexItem, err := indexObj.GetMaxFileIndex(frequency)
+//	if err != nil {
+//		fmt.Println("GetMaxFileIndex Err:" + err.Error())
+//		return
+//	}
+//
+//	maxFileIndex := maxFileIndexItem.FileIndex
+//	if maxFileIndex <= 0 {
+//		maxFileIndex = 1
+//	}
+//
+//	fileName := filePre + "_" + strconv.Itoa(maxFileIndex) + ".xlsx" //保存的文件名称
+//	maxMergeIndexFilePath := utils.IndexMsergeSaveDir + fileName
+//
+//	// 根据文件下标获取该文件下标已经存在多少的指标
+//	indexTotal, err := indexObj.GetIndexCountByMergeFilePath(maxMergeIndexFilePath)
+//	if err != nil {
+//		fmt.Println("GetIndexCountByMergeFilePath Err:" + err.Error())
+//		return
+//	}
+//
+//	// 如果最近的excel表的指标数量 少于 每页需要的指标数
+//	if int(indexTotal) < pageSize {
+//		// 最近的excel表的待添加指标数 = 每页需要的指标数 - 已经添加的指标
+//		firstIndexCount = pageSize - int(indexTotal)
+//	}
+//
+//	fmt.Println("maxFileIndex:", maxFileIndex)
+//
+//	// 除去第一个excel表的剩余需要添加的指标数量
+//	nextTotal := int(total) - firstIndexCount
+//	// 总文件数
+//	totalPage := paging.PageCount(nextTotal, pageSize)
+//	totalPage = totalPage + 1 //需要加上除去在外面的指标(第一张excel)
+//	fmt.Println("total:", total)
+//	fmt.Println("totalPage:", totalPage)
+//	for i := 1; i <= totalPage; i++ {
+//		//offset := (i - 1) * pageSize
+//
+//		fileName = filePre + "_" + strconv.Itoa(maxFileIndex) + ".xlsx" //保存的文件名称
+//		mergeIndexFilePath := utils.IndexMsergeSaveDir + fileName
+//		fmt.Println(mergeIndexFilePath)
+//
+//		//var list []*index.BaseFromMysteelChemicalIndex
+//		// 需要查询的指标数
+//		size := pageSize
+//		if i == 1 {
+//			size = firstIndexCount // 第一页需要合并的新指标数
+//
+//			// 第一页需要的指标列表
+//			//tmpList, tmpErr := indexObj.GetIndexByFrequencyListByMergeFilePath(frequency, mergeIndexFilePath)
+//			//if tmpErr != nil {
+//			//	fmt.Println("GetIndexByFrequencyListByMergeFilePath Err:" + tmpErr.Error())
+//			//	err = tmpErr
+//			//	return
+//			//}
+//			//list = tmpList
+//		}
+//		fmt.Println("需要查询", size, "个指标")
+//
+//		//fmt.Println(offset, pageSize)
+//
+//		// 当下需要合并到excel的指标
+//		pageList, tmpErr := indexObj.GetNoMergeIndexByFrequencyPage(frequency, size)
+//		if tmpErr != nil {
+//			fmt.Println("GetIndexByFrequency Err:" + tmpErr.Error())
+//			err = tmpErr
+//			return
+//		}
+//
+//		//if len(list) > 0 {
+//		//	pageList = append(pageList, list...)
+//		//}
+//
+//		commentResult, tmpErr := GetIndexComment(mergeIndexFilePath, pageList, maxFileIndex)
+//		if tmpErr != nil {
+//			fmt.Println("GetIndexComment Err:" + tmpErr.Error())
+//			err = tmpErr
+//			return
+//		}
+//		if utils.FileIsExist(mergeIndexFilePath) { //修改文件
+//			fileObj, tmpErr := excelize.OpenFile(mergeIndexFilePath)
+//			if tmpErr != nil {
+//				fmt.Println("打开文件失败,Err:" + tmpErr.Error())
+//				err = tmpErr
+//				return
+//			}
+//
+//			fileObj.DeleteComment("Sheet1", "A1")
+//			fileObj.AddComment("Sheet1", "A1", commentResult)
+//			if err = fileObj.SaveAs(mergeIndexFilePath); err != nil {
+//				fmt.Println("保存失败,Err:" + err.Error())
+//				fileObj.Close()
+//				return
+//			}
+//			fileObj.Close()
+//		} else { //新增文件
+//			templatePath := utils.IndexSaveDir + "index_template.xlsx"
+//			templateFile, tmpErr := excelize.OpenFile(templatePath)
+//			if tmpErr != nil {
+//				fmt.Println("打开文件失败,Err:" + tmpErr.Error())
+//				err = tmpErr
+//				return
+//			}
+//			templateFile.DeleteComment("Sheet1", "A1")
+//			templateFile.AddComment("Sheet1", "A1", commentResult)
+//			if err = templateFile.SaveAs(mergeIndexFilePath); err != nil {
+//				fmt.Println("保存失败,Err:" + err.Error())
+//				templateFile.Close()
+//				return
+//			}
+//			templateFile.Close()
+//		}
+//		maxFileIndex += 1
+//		time.Sleep(1 * time.Second)
+//		//刷新数据
+//		//MysteelChemicalRefresh(mergeIndexFilePath)
+//		AddIndexRefreshToLpush(mergeIndexFilePath)
+//	}
+//	return
+//}
+//
+//func GetIndexComment(indexFilePath string, list []*index.BaseFromMysteelChemicalIndex, fileIndex int) (commentResult string, err error) {
+//	runMode := "release"
+//	indexInfo := new(IndexObj)
+//	if utils.FileIsExist(indexFilePath) { //文件存在
+//		fmt.Println("utils.FileIsExist")
+//		getCommentStr := GetComment(indexFilePath)
+//		fmt.Println(getCommentStr)
+//
+//		err = json.Unmarshal([]byte(getCommentStr), &indexInfo)
+//		if err != nil {
+//			fmt.Println("json.Unmarshal err:" + err.Error())
+//			return
+//		}
+//	} else { //文件不存在,
+//		indexInfo.BlankValue = "0"
+//		indexInfo.CanMark = true
+//		indexInfo.ChartLineType = "0"
+//		indexInfo.DateBlock = 0
+//		indexInfo.DateBlockCount = 1
+//		indexInfo.DateFormat = 0
+//		indexInfo.DateTimeTag = "637973605613980000"
+//		indexInfo.EndDate = ""
+//		indexInfo.ExportType = 0
+//		indexInfo.HasDescription = true
+//		indexInfo.HasEmptyRows = false
+//		indexInfo.HasFrequency = true
+//		indexInfo.HasIndexID = true
+//		indexInfo.HasLastDate = true
+//		indexInfo.HasSourceName = true
+//		indexInfo.HasTimeInterval = true
+//		indexInfo.HasUnit = true
+//		indexInfo.HasUpdateDate = true
+//		indexInfo.IsCreateChart = false
+//		indexInfo.IsDataSort = true
+//		indexInfo.IsNewSheet = false
+//		indexInfo.IsNewWorkbook = false
+//		indexInfo.Position = "A1"
+//		indexInfo.ShowBlankLines = false
+//		indexInfo.StartDate = ""
+//		indexInfo.Transpose = false
+//		indexInfo.UpdateMode = 1
+//		indexInfo.LookModel.IsLast = false
+//		indexInfo.LookModel.LookValue = 0
+//		indexInfo.LookModel.LookType = 0
+//		indexInfo.Ver = 3
+//	}
+//	modelsList := make([]IndexModels, 0)
+//	startDate := "1990-01-01"
+//
+//	indexObj := new(index.BaseFromMysteelChemicalIndex)
+//
+//	modelsList = append(modelsList, indexInfo.Models...)
+//
+//	for k, v := range list {
+//		fmt.Println(k, v)
+//		item := new(IndexModels)
+//		item.DataFormat = 0
+//		if v.IndexName == "" {
+//			item.DataStartDate = startDate
+//		} else {
+//			item.DataStartDate = v.StartDate.Format(utils.FormatDate)
+//		}
+//		item.DefineName = ""
+//		item.DefineUnit = ""
+//		item.DisplayIndexCode = v.IndexCode
+//		item.IndexCode = v.IndexCode
+//		item.IndexFormula = v.IndexCode
+//		item.PointValue = 0
+//		item.UnionStart = ""
+//		modelsList = append(modelsList, *item)
+//
+//		indexObj.IndexCode = v.IndexCode
+//		indexObj.MergeFilePath = indexFilePath
+//		indexObj.FileIndex = fileIndex
+//
+//		updateColsArr := make([]string, 0)
+//		updateColsArr = append(updateColsArr, "merge_file_path")
+//		updateColsArr = append(updateColsArr, "file_index")
+//
+//		err = indexObj.Update(runMode, updateColsArr)
+//		if err != nil {
+//			fmt.Println("indexObj.Update err:" + err.Error())
+//			return
+//		}
+//	}
+//
+//	indexInfo.Models = modelsList
+//
+//	indexStr, err := json.Marshal(indexInfo)
+//	if err != nil {
+//		fmt.Println("json.Marshal err:" + err.Error())
+//		return
+//	}
+//	text := string(indexStr)
+//	text = strings.Trim(text, "{")
+//
+//	commentMap := make(map[string]interface{})
+//	commentMap["author"] = "{"
+//	commentMap["text"] = text
+//	//commentMap["text"] = commentItem
+//
+//	commentJson, err := json.Marshal(commentMap)
+//	if err != nil {
+//		fmt.Println("json.Marshal err:" + err.Error())
+//	}
+//	commentResult = string(commentJson)
+//	return
+//}
+//
+//type IndexObj struct {
+//	BlankValue      string        `json:"BlankValue"`
+//	CanMark         bool          `json:"CanMark"`
+//	ChartLineType   string        `json:"ChartLineType"`
+//	DateBlock       int64         `json:"DateBlock"`
+//	DateBlockCount  int64         `json:"DateBlockCount"`
+//	DateFormat      int64         `json:"DateFormat"`
+//	DateTimeTag     string        `json:"DateTimeTag"`
+//	EndDate         string        `json:"EndDate"`
+//	ExportType      int64         `json:"ExportType"`
+//	HasDescription  bool          `json:"HasDescription"`
+//	HasEmptyRows    bool          `json:"HasEmptyRows"`
+//	HasFrequency    bool          `json:"HasFrequency"`
+//	HasIndexID      bool          `json:"HasIndexID"`
+//	HasLastDate     bool          `json:"HasLastDate"`
+//	HasSourceName   bool          `json:"HasSourceName"`
+//	HasTimeInterval bool          `json:"HasTimeInterval"`
+//	HasUnit         bool          `json:"HasUnit"`
+//	HasUpdateDate   bool          `json:"HasUpdateDate"`
+//	IsCreateChart   bool          `json:"IsCreateChart"`
+//	IsDataSort      bool          `json:"IsDataSort"`
+//	IsNewSheet      bool          `json:"IsNewSheet"`
+//	IsNewWorkbook   bool          `json:"IsNewWorkbook"`
+//	Models          []IndexModels `json:"Models"`
+//	Position        string        `json:"Position"`
+//	RangeData       string        `json:"RangeData"`
+//	ShowBlankLines  bool          `json:"ShowBlankLines"`
+//	StartDate       string        `json:"StartDate"`
+//	Transpose       bool          `json:"Transpose"`
+//	UpdateMode      int64         `json:"UpdateMode"`
+//	LookModel       struct {
+//		IsLast    bool  `json:"IsLast"`
+//		LookValue int64 `json:"LookValue"`
+//		LookType  int64 `json:"lookType"`
+//	} `json:"lookModel"`
+//	Ver int64 `json:"ver"`
+//}
+//
+//type IndexModels struct {
+//	DataFormat       int64  `json:"DataFormat"`
+//	DataStartDate    string `json:"DataStartDate"`
+//	DefineName       string `json:"DefineName"`
+//	DefineUnit       string `json:"DefineUnit"`
+//	DisplayIndexCode string `json:"DisplayIndexCode"`
+//	IndexCode        string `json:"IndexCode"`
+//	IndexFormula     string `json:"IndexFormula"`
+//	PointValue       int64  `json:"PointValue"`
+//	UnionStart       string `json:"UnionStart"`
+//}
+//
+//func DataAnalysis(filePath string) {
+//
+//	runMode := "debug"
+//	//runMode = "release"
+//
+//	fmt.Println("filePath:", filePath)
+//	time.Sleep(1 * time.Second)
+//	if !utils.FileIsExist(filePath) {
+//		fmt.Println("filePath is not exist:" + filePath)
+//		return
+//	}
+//	//读取文件内容
+//	global.LOG.Info("WatchFile:" + filePath)
+//	f, err := excelize.OpenFile(filePath)
+//	global.LOG.Info("OpenFile:" + filePath)
+//	if err != nil {
+//		fmt.Println("OpenFile:" + filePath + ",Err:" + err.Error())
+//		return
+//	}
+//	defer func() {
+//		if err := f.Close(); err != nil {
+//			fmt.Println("FileClose Err:" + err.Error())
+//			return
+//		}
+//	}()
+//
+//	indexObj := new(index.BaseFromMysteelChemicalIndex)
+//	var wg = sync.WaitGroup{}
+//	wg.Add(1)
+//	go func() {
+//		sheetList := f.GetSheetList()
+//		for _, sv := range sheetList {
+//			rows, err := f.GetRows(sv)
+//			if err != nil {
+//				fmt.Println("f.GetRows:err:" + err.Error())
+//				return
+//			}
+//			var nameArr []string
+//			unitArr := make([]string, 0)
+//			sourceArr := make([]string, 0)
+//			codeArr := make([]string, 0)
+//			frequencyArr := make([]string, 0)
+//			dateArr := make([]string, 0)
+//			describeArr := make([]string, 0)
+//			endDateArr := make([]string, 0)
+//			updateArr := make([]string, 0)
+//			indexDateArr := make([]string, 0)
+//
+//			dataMap := make(map[string][]string)
+//
+//			var nameLen int
+//			for rk, row := range rows {
+//				if rk == 0 {
+//					continue
+//				}
+//				if rk == 1 {
+//					for ck, colCell := range row {
+//						if ck >= 1 {
+//							nameArr = append(nameArr, colCell)
+//						}
+//					}
+//					nameLen = len(nameArr)
+//					unitArr = make([]string, nameLen)
+//					sourceArr = make([]string, nameLen)
+//					codeArr = make([]string, nameLen)
+//					frequencyArr = make([]string, nameLen)
+//					dateArr = make([]string, nameLen)
+//					describeArr = make([]string, nameLen)
+//					endDateArr = make([]string, nameLen)
+//					updateArr = make([]string, nameLen)
+//				} else if rk == 2 {
+//					for ck, colCell := range row {
+//						if ck >= 1 {
+//							unitArr[ck-1] = colCell
+//						}
+//					}
+//				} else if rk == 3 {
+//					for ck, colCell := range row {
+//						if ck >= 1 {
+//							//sourceArr = append(sourceArr, colCell)
+//							sourceArr[ck-1] = colCell
+//						}
+//					}
+//				} else if rk == 4 {
+//					for ck, colCell := range row {
+//						if ck >= 1 {
+//							//codeArr = append(codeArr, colCell)
+//							codeArr[ck-1] = colCell
+//						}
+//					}
+//				} else if rk == 5 {
+//					for ck, colCell := range row {
+//						if ck >= 1 {
+//							//frequencyArr = append(frequencyArr, colCell)
+//							frequencyArr[ck-1] = colCell
+//						}
+//					}
+//				} else if rk == 6 {
+//					for ck, colCell := range row {
+//						if ck >= 1 {
+//							//dateArr = append(dateArr, colCell)
+//							dateArr[ck-1] = colCell
+//						}
+//					}
+//				} else if rk == 7 {
+//					for ck, colCell := range row {
+//						if ck >= 1 {
+//							//describeArr = append(describeArr, colCell)
+//							describeArr[ck-1] = colCell
+//						}
+//					}
+//				} else if rk == 8 {
+//					for ck, colCell := range row {
+//						if ck >= 1 {
+//							//endDateArr = append(endDateArr, colCell)
+//							endDateArr[ck-1] = colCell
+//						}
+//					}
+//				} else if rk == 9 {
+//					for ck, colCell := range row {
+//						if ck >= 1 {
+//							//updateArr = append(updateArr, colCell)
+//							updateArr[ck-1] = colCell
+//						}
+//					}
+//				} else {
+//					var date string
+//					dataArr := make([]string, nameLen)
+//					for ck, colCell := range row {
+//						if ck == 0 {
+//							date = colCell
+//							indexDateArr = append(indexDateArr, date)
+//						} else {
+//							if colCell != "" {
+//								dataArr[ck-1] = colCell
+//							}
+//						}
+//					}
+//					dataMap[date] = dataArr
+//				}
+//			}
+//
+//			var indexId int64
+//			existDataMap := make(map[string]string)
+//			for k, v := range nameArr {
+//				indexName := v
+//				indexCode := codeArr[k]
+//				unit := unitArr[k]
+//				source := sourceArr[k]
+//				describe := describeArr[k]
+//				dateStr := dateArr[k]
+//				frequency := frequencyArr[k]
+//				//判断指标是否存在
+//				var isAdd int
+//				item, err := indexObj.GetIndexItem(runMode, indexCode)
+//				if err != nil {
+//					if err.Error() == "record not found" {
+//						isAdd = 1
+//					} else {
+//						isAdd = -1
+//						fmt.Println("GetIndexItem Err:" + err.Error())
+//						return
+//					}
+//				}
+//				if item != nil && item.BaseFromMysteelChemicalIndexId > 0 {
+//					fmt.Println("item:", item)
+//					isAdd = 2
+//				} else {
+//					isAdd = 1
+//				}
+//
+//				var startDate, endDate string
+//				startEndArr := strings.Split(dateStr, "~")
+//				if len(startEndArr) >= 2 {
+//					startDate = startEndArr[0]
+//					endDate = startEndArr[1]
+//				}
+//
+//				if !strings.Contains(frequency, "度") {
+//					frequency = frequency + "度"
+//				}
+//
+//				indexItem := new(index.BaseFromMysteelChemicalIndex)
+//				if isAdd == 1 {
+//					indexItem.IndexCode = indexCode
+//					indexItem.IndexName = indexName
+//					indexItem.Unit = unit
+//					indexItem.Source = source
+//					indexItem.Describe = describe
+//					indexItem.StartDate, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+//					indexItem.EndDate, _ = time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+//					indexItem.Frequency = frequency
+//					indexItem.FilePath = filePath
+//					indexItem.MergeFilePath = filePath
+//					err = indexItem.Add(runMode)
+//					if err != nil {
+//						fmt.Println("indexItem.add err:" + err.Error())
+//						return
+//					}
+//					indexId = indexObj.BaseFromMysteelChemicalIndexId
+//				} else if isAdd == 2 {
+//					indexItem.IndexCode = indexCode
+//					indexItem.IndexName = indexName
+//					indexItem.Unit = unit
+//					indexItem.Source = source
+//					indexItem.Describe = describe
+//					indexItem.StartDate, _ = time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+//					indexItem.EndDate, _ = time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+//					indexItem.Frequency = frequency
+//					indexItem.FilePath = filePath
+//					indexItem.ModifyTime = time.Now()
+//					indexId = item.BaseFromMysteelChemicalIndexId
+//					//修改数据
+//					updateColsArr := make([]string, 0)
+//					updateColsArr = append(updateColsArr, "index_name")
+//					updateColsArr = append(updateColsArr, "unit")
+//					updateColsArr = append(updateColsArr, "source")
+//					updateColsArr = append(updateColsArr, "frequency")
+//					updateColsArr = append(updateColsArr, "start_date")
+//					updateColsArr = append(updateColsArr, "end_date")
+//					updateColsArr = append(updateColsArr, "describe")
+//					updateColsArr = append(updateColsArr, "end_date")
+//					updateColsArr = append(updateColsArr, "modify_time")
+//					updateColsArr = append(updateColsArr, "file_path")
+//
+//					err = indexItem.Update(runMode, updateColsArr)
+//					if err != nil {
+//						fmt.Println("indexObj.Update err:" + err.Error())
+//						return
+//					}
+//
+//					dataObj := new(index.BaseFromMysteelChemicalData)
+//					//获取已存在的所有数据
+//					dataList, err := dataObj.GetIndexDataList(runMode, indexCode)
+//					if err != nil {
+//						fmt.Println("GetIndexDataList Err:" + err.Error())
+//						return
+//					}
+//					fmt.Println("dataListLen:", len(dataList))
+//					for _, v := range dataList {
+//						dateStr := v.DataTime.Format(utils.FormatDate)
+//						existDataMap[dateStr] = v.Value
+//					}
+//				}
+//
+//				dataList := make([]index.BaseFromMysteelChemicalData, 0)
+//				for _, dv := range indexDateArr {
+//					dataArr := dataMap[dv]
+//					dataVal := dataArr[k]
+//					updateDate := updateArr[k]
+//					if dataVal != "" {
+//						if _, ok := existDataMap[dv]; !ok {
+//							dateTime, err := time.ParseInLocation(utils.FormatDate, dv, time.Local)
+//							if err != nil {
+//								fmt.Println("time.ParseInLocation Err:" + err.Error())
+//								return
+//							}
+//							dataItem := new(index.BaseFromMysteelChemicalData)
+//							dataItem.BaseFromMysteelChemicalIndexId = indexId
+//							dataItem.IndexCode = indexCode
+//							dataItem.DataTime = dateTime
+//							dataItem.Value = dataVal
+//							dataItem.UpdateDate = updateDate
+//							dataItem.CreateTime = time.Now()
+//							dataItem.ModifyTime = time.Now()
+//							dataList = append(dataList, *dataItem)
+//						}
+//					}
+//				}
+//				if len(dataList) > 0 {
+//					dataObj := new(index.BaseFromMysteelChemicalData)
+//					err = dataObj.Add(runMode, dataList)
+//					if err != nil {
+//						fmt.Println("dataObj.Add() Err:" + err.Error())
+//						return
+//					}
+//				}
+//			}
+//		}
+//		wg.Done()
+//	}()
+//	wg.Wait()
+//}

+ 61 - 0
services/index_queue.go

@@ -0,0 +1,61 @@
+package services
+
+import (
+	"fmt"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/utils"
+	"path/filepath"
+	"strings"
+)
+
+// HandleFileUpdate 处理文件变化
+func HandleFileUpdate() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("[AutoHandle]", err)
+		}
+	}()
+	for {
+		global.Rc.Brpop(utils.HANDLE_HONGQI_EXCEL, func(b []byte) {
+			updateFilePath := string(b)
+			// 移除多余的双引号和\
+			updateFilePath = strings.Replace(updateFilePath, `"`, "", -1)
+			fileExt := filepath.Ext(updateFilePath)
+			//fmt.Println("fileExt:", fileExt)
+
+			if fileExt != ".xlsx" && fileExt != ".xls" {
+				//fmt.Println("不是excel文件")
+				return
+			}
+			// 读取excel内容并操作入库
+			ReadHqExcel(updateFilePath)
+		})
+	}
+
+}
+
+// HandleRefreshExcel 处理excel刷新(实际去调用刷新)
+func HandleRefreshExcel() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("[AutoRefresh]", err)
+		}
+	}()
+	for {
+		global.Rc.Brpop(utils.REFRESH_HONGQI_EXCEL, func(b []byte) {
+			updateFilePath := string(b)
+			// 移除多余的双引号
+			updateFilePath = strings.Replace(updateFilePath, `"`, "", -1)
+			fileExt := filepath.Ext(updateFilePath)
+			//fmt.Println("fileExt:", fileExt)
+
+			if fileExt != ".xlsx" && fileExt != ".xls" {
+				//fmt.Println("不是excel文件")
+				return
+			}
+			// 读取excel内容并操作入库
+			InvokeRefreshServer(updateFilePath)
+		})
+	}
+
+}

+ 72 - 0
services/refresh_excel.go

@@ -0,0 +1,72 @@
+package services
+
+import (
+	"fmt"
+	"hongze/hongtao3_watch/cache"
+	"hongze/hongtao3_watch/global"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+// RefreshExcel 刷新excel文件
+func RefreshExcel() {
+	global.LOG.Info("开始刷新了")
+	weekDay := time.Now().Weekday()
+	// 周末不刷新数据
+	if weekDay == 0 || weekDay == 6 {
+		return
+	}
+
+	// 如果没有配置就不监听了
+	if global.CONFIG.Serve.ListenExcelPath == `` {
+		return
+	}
+
+	fileList, err := GetExcelFilePath(global.CONFIG.Serve.ListenExcelPath)
+	if err != nil {
+		fmt.Println("获取excel文件失败,ERR:", err)
+		global.LOG.Info("开始刷新;获取excel文件失败,ERR:", err)
+		return
+	}
+	for _, v := range fileList {
+		fmt.Println(v)
+		cache.AddIndexRefreshExcel(v)
+	}
+}
+
+// GetExcelFilePath 获取需要刷新的excel路径
+func GetExcelFilePath(dirPath string) (fileList []string, err error) {
+	fileList = make([]string, 0)
+	list, err := os.ReadDir(dirPath)
+	if err != nil {
+		return
+	}
+	for _, v := range list {
+		tmpFilePath := dirPath + `\\` + v.Name()
+		// 如果是目录的话,那么就查询子目录,然后返回
+		if v.IsDir() {
+			tmpFileList, tmpErr := GetExcelFilePath(tmpFilePath)
+			if tmpErr != nil {
+				return
+			}
+			fileList = append(fileList, tmpFileList...)
+			continue
+		}
+		fileName := v.Name()
+		////临时文件过滤
+		if strings.Contains(fileName, "tmp") ||
+			strings.Contains(fileName, ".TMP") ||
+			strings.Contains(fileName, "~") {
+			continue
+		}
+
+		fileExt := filepath.Ext(tmpFilePath)
+		if fileExt == ".xlsx" || fileExt == ".xls" {
+			fileList = append(fileList, tmpFilePath)
+		}
+	}
+
+	return
+}

+ 90 - 0
services/sync_edb_lib.go

@@ -0,0 +1,90 @@
+package services
+
+import (
+	"encoding/json"
+	"fmt"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/utils"
+	"io/ioutil"
+	"net/http"
+	"strings"
+)
+
+type BaseResponse struct {
+	Ret         int
+	Msg         string
+	ErrMsg      string
+	ErrCode     string
+	Data        interface{}
+	Success     bool `description:"true 执行成功,false 执行失败"`
+	IsSendEmail bool `json:"-" description:"true 发送邮件,false 不发送邮件"`
+	IsAddLog    bool `json:"-" description:"true 新增操作日志,false 不新增操作日志" `
+}
+
+// postRefreshEdbData 刷新指标数据
+func postRefreshEdbData(param map[string]interface{}, urlStr string) (resp *BaseResponse, err error) {
+	postUrl := global.CONFIG.Serve.EdbLibUrl + urlStr
+	postData, err := json.Marshal(param)
+	if err != nil {
+		return
+	}
+	result, err := HttpPost(postUrl, string(postData), "application/json")
+	if err != nil {
+		return
+	}
+	if result != nil {
+		global.LOG.Info(" Refresh Result: " + string(result))
+		err = json.Unmarshal(result, &resp)
+		if err != nil {
+			return
+		}
+		return resp, nil
+	}
+	return nil, err
+}
+
+func HttpPost(url, postData string, params ...string) ([]byte, error) {
+	body := ioutil.NopCloser(strings.NewReader(postData))
+	client := &http.Client{}
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	contentType := "application/x-www-form-urlencoded;charset=utf-8"
+	if len(params) > 0 && params[0] != "" {
+		contentType = params[0]
+	}
+	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("authorization", utils.MD5(global.CONFIG.Serve.AppEdbLibNameEn+global.CONFIG.Serve.EdbLibMd5Key))
+	resp, err := client.Do(req)
+	if resp != nil {
+		defer resp.Body.Close()
+		b, err := ioutil.ReadAll(resp.Body)
+		fmt.Println("HttpPost:" + string(b))
+		return b, err
+	}
+	return nil, err
+}
+
+// 调用指标库公共服务处理卓创红期指标
+func HandleHqExcelDataByEdbLib(indexDataList []*SciHqExcel) (resp *BaseResponse, err error) {
+	urlStr := "sci_hq/handle/excel_data"
+	postUrl := global.CONFIG.Serve.EdbLibUrl + urlStr
+	postData, err := json.Marshal(indexDataList)
+	if err != nil {
+		return
+	}
+	result, err := HttpPost(postUrl, string(postData), "application/json")
+	if err != nil {
+		return
+	}
+	if result != nil {
+		global.LOG.Info(" Refresh Result: " + string(result))
+		err = json.Unmarshal(result, &resp)
+		if err != nil {
+			return
+		}
+		return resp, nil
+	}
+	return
+}

+ 0 - 0
static/static.txt


+ 72 - 0
task/task.go

@@ -0,0 +1,72 @@
+package task
+
+import (
+	"fmt"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/services/alarm_msg"
+	"hongze/hongtao3_watch/utils"
+	"os"
+	"runtime"
+	"sync"
+	"time"
+)
+
+type TaskFunc func(params ...interface{})
+
+var taskList chan *Executor //任务列表
+
+var once sync.Once
+
+func GetTaskList() chan *Executor {
+	once.Do(func() {
+		taskList = make(chan *Executor, 1000)
+	})
+	return taskList
+}
+
+type Executor struct {
+	f      TaskFunc
+	params []interface{}
+}
+
+func (e *Executor) Exec() { //执行任务
+	go func() {
+		defer func() {
+			if err := recover(); err != any(nil) {
+				stack := ""
+				msg := fmt.Sprintf("当前进程pid:%d; 父进程ppid:%d", os.Getpid(), os.Getppid())
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				msg = fmt.Sprintf("The params data is %v", e.params)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				msg = fmt.Sprintf("Handler crashed with error %v", err)
+				stack += msg + "</br>"
+				global.LOG.Critical(msg)
+				for i := 1; ; i++ {
+					_, file, line, ok := runtime.Caller(i)
+					if !ok {
+						break
+					}
+					global.LOG.Critical(fmt.Sprintf("%s:%d", file, line))
+					stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d</br>", file, line))
+				}
+				fmt.Println("stack:", stack)
+				//go services.SendEmail(utils.APPNAME+"崩了"+time.Now().Format("2006-01-02 15:04:05"), stack, utils.EmailSendToUsers)
+				go alarm_msg.SendAlarmMsg(utils.APPNAME+"崩了"+time.Now().Format("2006-01-02 15:04:05")+";Msg:"+stack, 2)
+			}
+		}()
+		//time.Sleep(60*time.Second)
+		//fmt.Println("i am here new")
+		e.f(e.params...)
+	}()
+
+}
+
+func NewExecutor(f TaskFunc, params []interface{}) *Executor {
+	return &Executor{f: f, params: params}
+}
+
+func Task(f TaskFunc, params ...interface{}) {
+	taskList <- NewExecutor(f, params)
+}

+ 1070 - 0
utils/common.go

@@ -0,0 +1,1070 @@
+package utils
+
+import (
+	"bufio"
+	"crypto/md5"
+	"crypto/sha1"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"gorm.io/gorm"
+	"image"
+	"image/png"
+	"io"
+	"math"
+	"math/rand"
+	"net/http"
+	"os"
+	"os/exec"
+	"path"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var ErrNoRow = gorm.ErrRecordNotFound
+
+/*var (
+	KEY = []byte("OsL5s35Xv6")
+)*/
+
+// 发放token
+func GenToken(account string) (accessToken string, err error) {
+	/*token := jwt.New(jwt.SigningMethodHS256)
+	token.Claims = &jwt.StandardClaims{
+		NotBefore: int64(time.Now().Unix()),
+		ExpiresAt: int64(time.Now().Unix() + 90*24*60*60),
+		Issuer:    "hongtao3_watch",
+		Subject:   account,
+	}
+	accessToken, err = token.SignedString(KEY)*/
+	timeUnix := time.Now().Unix()
+	timeUnixStr := strconv.FormatInt(timeUnix, 10)
+	accessToken = MD5(account) + MD5(timeUnixStr)
+	return
+}
+
+//随机数种子
+var rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
+
+// GetRandString 获取随机字符串
+func GetRandString(size int) string {
+	allLetterDigit := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "!", "@", "#", "$", "%", "^", "&", "*"}
+	randomSb := ""
+	digitSize := len(allLetterDigit)
+	for i := 0; i < size; i++ {
+		randomSb += allLetterDigit[rnd.Intn(digitSize)]
+	}
+	return randomSb
+}
+
+// GetRandStringNoSpecialChar
+func GetRandStringNoSpecialChar(size int) string {
+	allLetterDigit := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
+	randomSb := ""
+	digitSize := len(allLetterDigit)
+	for i := 0; i < size; i++ {
+		randomSb += allLetterDigit[rnd.Intn(digitSize)]
+	}
+	return randomSb
+}
+
+// StringsToJSON
+func StringsToJSON(str string) string {
+	rs := []rune(str)
+	jsons := ""
+	for _, r := range rs {
+		rint := int(r)
+		if rint < 128 {
+			jsons += string(r)
+		} else {
+			jsons += "\\u" + strconv.FormatInt(int64(rint), 16) // json
+		}
+	}
+	return jsons
+}
+
+// ToString 序列化
+func ToString(v interface{}) string {
+	data, _ := json.Marshal(v)
+	return string(data)
+}
+
+// MD5 md5加密
+func MD5(data string) string {
+	m := md5.Sum([]byte(data))
+	return hex.EncodeToString(m[:])
+}
+
+// GetRandDigit 获取数字随机字符
+func GetRandDigit(n int) string {
+	return fmt.Sprintf("%0"+strconv.Itoa(n)+"d", rnd.Intn(int(math.Pow10(n))))
+}
+
+// GetRandNumber 获取随机数
+func GetRandNumber(n int) int {
+	return rnd.Intn(n)
+}
+
+// GetRandInt
+func GetRandInt(min, max int) int {
+	if min >= max || min == 0 || max == 0 {
+		return max
+	}
+	return rand.Intn(max-min) + min
+}
+
+// GetToday 获取今天的随机字符
+func GetToday(format string) string {
+	today := time.Now().Format(format)
+	return today
+}
+
+// GetTodayLastSecond 获取今天剩余秒数
+func GetTodayLastSecond() time.Duration {
+	today := GetToday(FormatDate) + " 23:59:59"
+	end, _ := time.ParseInLocation(FormatDateTime, today, time.Local)
+	return time.Duration(end.Unix()-time.Now().Local().Unix()) * time.Second
+}
+
+// GetBrithDate 处理出生日期函数
+func GetBrithDate(idcard string) string {
+	l := len(idcard)
+	var s string
+	if l == 15 {
+		s = "19" + idcard[6:8] + "-" + idcard[8:10] + "-" + idcard[10:12]
+		return s
+	}
+	if l == 18 {
+		s = idcard[6:10] + "-" + idcard[10:12] + "-" + idcard[12:14]
+		return s
+	}
+	return GetToday(FormatDate)
+}
+
+// WhichSexByIdcard 处理性别
+func WhichSexByIdcard(idcard string) string {
+	var sexs = [2]string{"女", "男"}
+	length := len(idcard)
+	if length == 18 {
+		sex, _ := strconv.Atoi(string(idcard[16]))
+		return sexs[sex%2]
+	} else if length == 15 {
+		sex, _ := strconv.Atoi(string(idcard[14]))
+		return sexs[sex%2]
+	}
+	return "男"
+}
+
+// SubFloatToString 截取小数点后几位
+func SubFloatToString(f float64, m int) string {
+	n := strconv.FormatFloat(f, 'f', -1, 64)
+	if n == "" {
+		return ""
+	}
+	if m >= len(n) {
+		return n
+	}
+	newn := strings.Split(n, ".")
+	if m == 0 {
+		return newn[0]
+	}
+	if len(newn) < 2 || m >= len(newn[1]) {
+		return n
+	}
+	return newn[0] + "." + newn[1][:m]
+}
+
+// SubFloatToFloat 截取小数点后几位
+func SubFloatToFloat(f float64, m int) float64 {
+	newn := SubFloatToString(f, m)
+	newf, _ := strconv.ParseFloat(newn, 64)
+	return newf
+}
+
+// SubFloatToFloatStr 截取小数点后几位
+func SubFloatToFloatStr(f float64, m int) string {
+	newn := SubFloatToString(f, m)
+	return newn
+}
+
+// GetYearDiffer 获取相差时间-年
+func GetYearDiffer(start_time, end_time string) int {
+	t1, _ := time.ParseInLocation("2006-01-02", start_time, time.Local)
+	t2, _ := time.ParseInLocation("2006-01-02", end_time, time.Local)
+	age := t2.Year() - t1.Year()
+	if t2.Month() < t1.Month() || (t2.Month() == t1.Month() && t2.Day() < t1.Day()) {
+		age--
+	}
+	return age
+}
+
+// GetSecondDifferByTime 获取相差时间-秒
+func GetSecondDifferByTime(start_time, end_time time.Time) int64 {
+	diff := end_time.Unix() - start_time.Unix()
+	return diff
+}
+
+// FixFloat
+func FixFloat(f float64, m int) float64 {
+	newn := SubFloatToString(f+0.00000001, m)
+	newf, _ := strconv.ParseFloat(newn, 64)
+	return newf
+}
+
+// StrListToString 将字符串数组转化为逗号分割的字符串形式  ["str1","str2","str3"] >>> "str1,str2,str3"
+func StrListToString(strList []string) (str string) {
+	if len(strList) > 0 {
+		for k, v := range strList {
+			if k == 0 {
+				str = v
+			} else {
+				str = str + "," + v
+			}
+		}
+		return
+	}
+	return ""
+}
+
+// ValidateEmailFormatat 校验邮箱格式
+func ValidateEmailFormatat(email string) bool {
+	reg := regexp.MustCompile(RegularEmail)
+	return reg.MatchString(email)
+}
+
+// ValidateMobileFormatat 验证是否是手机号
+func ValidateMobileFormatat(mobileNum string) bool {
+	reg := regexp.MustCompile(RegularMobile)
+	return reg.MatchString(mobileNum)
+}
+
+// FileIsExist 判断文件是否存在
+func FileIsExist(filePath string) bool {
+	_, err := os.Stat(filePath)
+	return err == nil || os.IsExist(err)
+}
+
+// GetImgExt 获取图片扩展名
+func GetImgExt(file string) (ext string, err error) {
+	var headerByte []byte
+	headerByte = make([]byte, 8)
+	fd, err := os.Open(file)
+	if err != nil {
+		return "", err
+	}
+	defer fd.Close()
+	_, err = fd.Read(headerByte)
+	if err != nil {
+		return "", err
+	}
+	xStr := fmt.Sprintf("%x", headerByte)
+	switch {
+	case xStr == "89504e470d0a1a0a":
+		ext = ".png"
+	case xStr == "0000010001002020":
+		ext = ".ico"
+	case xStr == "0000020001002020":
+		ext = ".cur"
+	case xStr[:12] == "474946383961" || xStr[:12] == "474946383761":
+		ext = ".gif"
+	case xStr[:10] == "0000020000" || xStr[:10] == "0000100000":
+		ext = ".tga"
+	case xStr[:8] == "464f524d":
+		ext = ".iff"
+	case xStr[:8] == "52494646":
+		ext = ".ani"
+	case xStr[:4] == "4d4d" || xStr[:4] == "4949":
+		ext = ".tiff"
+	case xStr[:4] == "424d":
+		ext = ".bmp"
+	case xStr[:4] == "ffd8":
+		ext = ".jpg"
+	case xStr[:2] == "0a":
+		ext = ".pcx"
+	default:
+		ext = ""
+	}
+	return ext, nil
+}
+
+// SaveImage 保存图片
+func SaveImage(path string, img image.Image) (err error) {
+	//需要保持的文件
+	imgfile, err := os.Create(path)
+	defer imgfile.Close()
+	// 以PNG格式保存文件
+	err = png.Encode(imgfile, img)
+	return err
+}
+
+// DownloadImage 下载图片
+func DownloadImage(imgUrl string) (filePath string, err error) {
+	imgPath := "./static/imgs/"
+	fileName := path.Base(imgUrl)
+	res, err := http.Get(imgUrl)
+	if err != nil {
+		fmt.Println("A error occurred!")
+		return
+	}
+	defer res.Body.Close()
+	// 获得get请求响应的reader对象
+	reader := bufio.NewReaderSize(res.Body, 32*1024)
+
+	filePath = imgPath + fileName
+	file, err := os.Create(filePath)
+	if err != nil {
+		return
+	}
+	// 获得文件的writer对象
+	writer := bufio.NewWriter(file)
+
+	written, _ := io.Copy(writer, reader)
+	fmt.Printf("Total length: %d \n", written)
+	return
+}
+
+// SaveBase64ToFile 保存base64数据为文件
+func SaveBase64ToFile(content, path string) error {
+	data, err := base64.StdEncoding.DecodeString(content)
+	if err != nil {
+		return err
+	}
+	f, err := os.Create(path)
+	defer f.Close()
+	if err != nil {
+		return err
+	}
+	f.Write(data)
+	return nil
+}
+
+// SaveBase64ToFileBySeek
+func SaveBase64ToFileBySeek(content, path string) (err error) {
+	data, err := base64.StdEncoding.DecodeString(content)
+	exist, err := PathExists(path)
+	if err != nil {
+		return
+	}
+	if !exist {
+		f, err := os.Create(path)
+		if err != nil {
+			return err
+		}
+		n, _ := f.Seek(0, 2)
+		// 从末尾的偏移量开始写入内容
+		_, err = f.WriteAt([]byte(data), n)
+		defer f.Close()
+	} else {
+		f, err := os.OpenFile(path, os.O_WRONLY, 0644)
+		if err != nil {
+			return err
+		}
+		n, _ := f.Seek(0, 2)
+		// 从末尾的偏移量开始写入内容
+		_, err = f.WriteAt([]byte(data), n)
+		defer f.Close()
+	}
+
+	return nil
+}
+
+// StartIndex 开始下标
+func StartIndex(page, pagesize int) int {
+	if page > 1 {
+		return (page - 1) * pagesize
+	}
+	return 0
+}
+
+// PageCount
+func PageCount(count, pagesize int) int {
+	if count%pagesize > 0 {
+		return count/pagesize + 1
+	} else {
+		return count / pagesize
+	}
+}
+
+// TrimHtml
+func TrimHtml(src string) string {
+	//将HTML标签全转换成小写
+	re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
+	src = re.ReplaceAllStringFunc(src, strings.ToLower)
+
+	re, _ = regexp.Compile("\\<img[\\S\\s]+?\\>")
+	src = re.ReplaceAllString(src, "")
+
+	re, _ = regexp.Compile("class[\\S\\s]+?>")
+	src = re.ReplaceAllString(src, "")
+	re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
+	src = re.ReplaceAllString(src, "")
+	return strings.TrimSpace(src)
+}
+
+//1556164246  ->  2019-04-25 03:50:46 +0000
+//timestamp
+// TimeToTimestamp
+func TimeToTimestamp() {
+	fmt.Println(time.Unix(1556164246, 0).Format("2006-01-02 15:04:05"))
+}
+
+func TimeTransferString(format string, t time.Time) string {
+	str := t.Format(format)
+	var emptyT time.Time
+	if str == emptyT.Format(format) {
+		return ""
+	}
+	return str
+}
+
+// ToUnicode
+func ToUnicode(text string) string {
+	textQuoted := strconv.QuoteToASCII(text)
+	textUnquoted := textQuoted[1 : len(textQuoted)-1]
+	return textUnquoted
+}
+
+// VersionToInt
+func VersionToInt(version string) int {
+	version = strings.Replace(version, ".", "", -1)
+	n, _ := strconv.Atoi(version)
+	return n
+}
+
+// IsCheckInList
+func IsCheckInList(list []int, s int) bool {
+	for _, v := range list {
+		if v == s {
+			return true
+		}
+	}
+	return false
+
+}
+
+// round
+func round(num float64) int {
+	return int(num + math.Copysign(0.5, num))
+}
+
+// toFixed
+func toFixed(num float64, precision int) float64 {
+	output := math.Pow(10, float64(precision))
+	return float64(round(num*output)) / output
+}
+
+// GetWilsonScore returns Wilson Score
+func GetWilsonScore(p, n float64) float64 {
+	if p == 0 && n == 0 {
+		return 0
+	}
+
+	return toFixed(((p+1.9208)/(p+n)-1.96*math.Sqrt(p*n/(p+n)+0.9604)/(p+n))/(1+3.8416/(p+n)), 2)
+}
+
+//将中文数字转化成数字,比如 第三百四十五章,返回第345章 不支持一亿及以上
+func ChangeWordsToNum(str string) (numStr string) {
+	words := ([]rune)(str)
+	num := 0
+	n := 0
+	for i := 0; i < len(words); i++ {
+		word := string(words[i : i+1])
+		switch word {
+		case "万":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 10000
+			num = num*10000 + n
+			n = 0
+		case "千":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 1000
+			num += n
+			n = 0
+		case "百":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 100
+			num += n
+			n = 0
+		case "十":
+			if n == 0 {
+				n = 1
+			}
+			n = n * 10
+			num += n
+			n = 0
+		case "一":
+			n += 1
+		case "二":
+			n += 2
+		case "三":
+			n += 3
+		case "四":
+			n += 4
+		case "五":
+			n += 5
+		case "六":
+			n += 6
+		case "七":
+			n += 7
+		case "八":
+			n += 8
+		case "九":
+			n += 9
+		case "零":
+		default:
+			if n > 0 {
+				num += n
+				n = 0
+			}
+			if num == 0 {
+				numStr += word
+			} else {
+				numStr += strconv.Itoa(num) + word
+				num = 0
+			}
+		}
+	}
+	if n > 0 {
+		num += n
+		n = 0
+	}
+	if num != 0 {
+		numStr += strconv.Itoa(num)
+	}
+	return
+}
+
+// Sha1
+func Sha1(data string) string {
+	sha1 := sha1.New()
+	sha1.Write([]byte(data))
+	return hex.EncodeToString(sha1.Sum([]byte("")))
+}
+
+// GetVideoPlaySeconds
+func GetVideoPlaySeconds(videoPath string) (playSeconds float64, err error) {
+	cmd := `ffmpeg -i ` + videoPath + `  2>&1 | grep 'Duration' | cut -d ' ' -f 4 | sed s/,//`
+	out, err := exec.Command("bash", "-c", cmd).Output()
+	if err != nil {
+		return
+	}
+	outTimes := string(out)
+	fmt.Println("outTimes:", outTimes)
+	if outTimes != "" {
+		timeArr := strings.Split(outTimes, ":")
+		h := timeArr[0]
+		m := timeArr[1]
+		s := timeArr[2]
+		hInt, err := strconv.Atoi(h)
+		if err != nil {
+			return playSeconds, err
+		}
+
+		mInt, err := strconv.Atoi(m)
+		if err != nil {
+			return playSeconds, err
+		}
+		s = strings.Trim(s, " ")
+		s = strings.Trim(s, "\n")
+		sInt, err := strconv.ParseFloat(s, 64)
+		if err != nil {
+			return playSeconds, err
+		}
+		playSeconds = float64(hInt)*3600 + float64(mInt)*60 + float64(sInt)
+	}
+	return
+}
+
+// GetMaxTradeCode
+func GetMaxTradeCode(tradeCode string) (maxTradeCode string, err error) {
+	tradeCode = strings.Replace(tradeCode, "W", "", -1)
+	tradeCode = strings.Trim(tradeCode, " ")
+	tradeCodeInt, err := strconv.Atoi(tradeCode)
+	if err != nil {
+		return
+	}
+	tradeCodeInt = tradeCodeInt + 1
+	maxTradeCode = fmt.Sprintf("W%06d", tradeCodeInt)
+	return
+}
+
+// ConvertToFormatDay excel日期字段格式化 yyyy-mm-dd
+func ConvertToFormatDay(excelDaysString string) string {
+	// 2006-01-02 距离 1900-01-01的天数
+	baseDiffDay := 38719 //在网上工具计算的天数需要加2天,什么原因没弄清楚
+	curDiffDay := excelDaysString
+	b, _ := strconv.Atoi(curDiffDay)
+	// 获取excel的日期距离2006-01-02的天数
+	realDiffDay := b - baseDiffDay
+	//fmt.Println("realDiffDay:",realDiffDay)
+	// 距离2006-01-02 秒数
+	realDiffSecond := realDiffDay * 24 * 3600
+	//fmt.Println("realDiffSecond:",realDiffSecond)
+	// 2006-01-02 15:04:05距离1970-01-01 08:00:00的秒数 网上工具可查出
+	baseOriginSecond := 1136185445
+	resultTime := time.Unix(int64(baseOriginSecond+realDiffSecond), 0).Format("2006-01-02")
+	return resultTime
+}
+
+// CheckPwd
+func CheckPwd(pwd string) bool {
+	compile := `([0-9a-z]+){6,12}|(a-z0-9]+){6,12}`
+	reg := regexp.MustCompile(compile)
+	flag := reg.MatchString(pwd)
+	return flag
+}
+
+// GetMonthStartAndEnd
+func GetMonthStartAndEnd(myYear string, myMonth string) (startDate, endDate string) {
+	// 数字月份必须前置补零
+	if len(myMonth) == 1 {
+		myMonth = "0" + myMonth
+	}
+	yInt, _ := strconv.Atoi(myYear)
+
+	timeLayout := "2006-01-02 15:04:05"
+	loc, _ := time.LoadLocation("Local")
+	theTime, _ := time.ParseInLocation(timeLayout, myYear+"-"+myMonth+"-01 00:00:00", loc)
+	newMonth := theTime.Month()
+
+	t1 := time.Date(yInt, newMonth, 1, 0, 0, 0, 0, time.Local).Format("2006-01-02")
+	t2 := time.Date(yInt, newMonth+1, 0, 0, 0, 0, 0, time.Local).Format("2006-01-02")
+	return t1, t2
+}
+
+//TrimStr 移除字符串中的空格
+func TrimStr(str string) (str2 string) {
+	return strings.Replace(str, " ", "", -1)
+}
+
+// StrTimeToTime 字符串转换为time
+func StrTimeToTime(strTime string) time.Time {
+	timeLayout := "2006-01-02 15:04:05"  //转化所需模板
+	loc, _ := time.LoadLocation("Local") //重要:获取时区
+	resultTime, _ := time.ParseInLocation(timeLayout, strTime, loc)
+	return resultTime
+}
+
+// StrDateTimeToWeek 字符串类型时间转周几
+func StrDateTimeToWeek(strTime string) string {
+	var WeekDayMap = map[string]string{
+		"Monday":    "周一",
+		"Tuesday":   "周二",
+		"Wednesday": "周三",
+		"Thursday":  "周四",
+		"Friday":    "周五",
+		"Saturday":  "周六",
+		"Sunday":    "周日",
+	}
+	var ctime = StrTimeToTime(strTime).Format("2006-01-02")
+	startday, _ := time.Parse("2006-01-02", ctime)
+	staweek_int := startday.Weekday().String()
+	return WeekDayMap[staweek_int]
+}
+
+// TimeToStrYmd 时间格式转年月日字符串
+func TimeToStrYmd(time2 time.Time) string {
+	var Ymd string
+	year := time2.Year()
+	month := time2.Format("1")
+	day1 := time.Now().Day()
+	Ymd = strconv.Itoa(year) + "年" + month + "月" + strconv.Itoa(day1) + "日"
+	return Ymd
+}
+
+// TimeRemoveHms 时间格式去掉时分秒
+func TimeRemoveHms(strTime string) string {
+	var Ymd string
+	var resultTime = StrTimeToTime(strTime)
+	year := resultTime.Year()
+	month := resultTime.Format("01")
+	day1 := resultTime.Day()
+	Ymd = strconv.Itoa(year) + "." + month + "." + strconv.Itoa(day1)
+	return Ymd
+}
+
+// ArticleLastTime 文章上一次编辑时间
+func ArticleLastTime(strTime string) string {
+	var newTime string
+	stamp, _ := time.ParseInLocation("2006-01-02 15:04:05", strTime, time.Local)
+	diffTime := time.Now().Unix() - stamp.Unix()
+	if diffTime <= 60 {
+		newTime = "当前"
+	} else if diffTime < 60*60 {
+		newTime = strconv.FormatInt(diffTime/60, 10) + "分钟前"
+	} else if diffTime < 24*60*60 {
+		newTime = strconv.FormatInt(diffTime/(60*60), 10) + "小时前"
+	} else if diffTime < 30*24*60*60 {
+		newTime = strconv.FormatInt(diffTime/(24*60*60), 10) + "天前"
+	} else if diffTime < 12*30*24*60*60 {
+		newTime = strconv.FormatInt(diffTime/(30*24*60*60), 10) + "月前"
+	} else {
+		newTime = "1年前"
+	}
+	return newTime
+}
+
+// ConvertNumToCny 人民币小写转大写
+func ConvertNumToCny(num float64) (str string, err error) {
+	strNum := strconv.FormatFloat(num*100, 'f', 0, 64)
+	sliceUnit := []string{"仟", "佰", "拾", "亿", "仟", "佰", "拾", "万", "仟", "佰", "拾", "元", "角", "分"}
+	// log.Println(sliceUnit[:len(sliceUnit)-2])
+	s := sliceUnit[len(sliceUnit)-len(strNum):]
+	upperDigitUnit := map[string]string{"0": "零", "1": "壹", "2": "贰", "3": "叁", "4": "肆", "5": "伍", "6": "陆", "7": "柒", "8": "捌", "9": "玖"}
+	for k, v := range strNum[:] {
+		str = str + upperDigitUnit[string(v)] + s[k]
+	}
+	reg, err := regexp.Compile(`零角零分$`)
+	str = reg.ReplaceAllString(str, "整")
+
+	reg, err = regexp.Compile(`零角`)
+	str = reg.ReplaceAllString(str, "零")
+
+	reg, err = regexp.Compile(`零分$`)
+	str = reg.ReplaceAllString(str, "整")
+
+	reg, err = regexp.Compile(`零[仟佰拾]`)
+	str = reg.ReplaceAllString(str, "零")
+
+	reg, err = regexp.Compile(`零{2,}`)
+	str = reg.ReplaceAllString(str, "零")
+
+	reg, err = regexp.Compile(`零亿`)
+	str = reg.ReplaceAllString(str, "亿")
+
+	reg, err = regexp.Compile(`零万`)
+	str = reg.ReplaceAllString(str, "万")
+
+	reg, err = regexp.Compile(`零*元`)
+	str = reg.ReplaceAllString(str, "元")
+
+	reg, err = regexp.Compile(`亿零{0, 3}万`)
+	str = reg.ReplaceAllString(str, "^元")
+
+	reg, err = regexp.Compile(`零元`)
+	str = reg.ReplaceAllString(str, "零")
+	return
+}
+
+// GetNowWeekMonday 获取本周周一的时间
+func GetNowWeekMonday() time.Time {
+	offset := int(time.Monday - time.Now().Weekday())
+	if offset == 1 { //正好是周日,但是按照中国人的理解,周日是一周最后一天,而不是一周开始的第一天
+		offset = -6
+	}
+	mondayTime := time.Now().AddDate(0, 0, offset)
+	mondayTime = time.Date(mondayTime.Year(), mondayTime.Month(), mondayTime.Day(), 0, 0, 0, 0, mondayTime.Location())
+	return mondayTime
+}
+
+// GetNowWeekLastDay 获取本周最后一天的时间
+func GetNowWeekLastDay() time.Time {
+	offset := int(time.Monday - time.Now().Weekday())
+	if offset == 1 { //正好是周日,但是按照中国人的理解,周日是一周最后一天,而不是一周开始的第一天
+		offset = -6
+	}
+	firstDayTime := time.Now().AddDate(0, 0, offset)
+	firstDayTime = time.Date(firstDayTime.Year(), firstDayTime.Month(), firstDayTime.Day(), 0, 0, 0, 0, firstDayTime.Location()).AddDate(0, 0, 6)
+	lastDayTime := time.Date(firstDayTime.Year(), firstDayTime.Month(), firstDayTime.Day(), 23, 59, 59, 0, firstDayTime.Location())
+
+	return lastDayTime
+}
+
+// GetNowMonthFirstDay 获取本月第一天的时间
+func GetNowMonthFirstDay() time.Time {
+	nowMonthFirstDay := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Now().Location())
+	return nowMonthFirstDay
+}
+
+// GetNowMonthLastDay 获取本月最后一天的时间
+func GetNowMonthLastDay() time.Time {
+	nowMonthLastDay := time.Date(time.Now().Year(), time.Now().Month(), 1, 0, 0, 0, 0, time.Now().Location()).AddDate(0, 1, -1)
+	nowMonthLastDay = time.Date(nowMonthLastDay.Year(), nowMonthLastDay.Month(), nowMonthLastDay.Day(), 23, 59, 59, 0, nowMonthLastDay.Location())
+	return nowMonthLastDay
+}
+
+// GetNowQuarterFirstDay 获取本季度第一天的时间
+func GetNowQuarterFirstDay() time.Time {
+	month := int(time.Now().Month())
+	var nowQuarterFirstDay time.Time
+	if month >= 1 && month <= 3 {
+		//1月1号
+		nowQuarterFirstDay = time.Date(time.Now().Year(), 1, 1, 0, 0, 0, 0, time.Now().Location())
+	} else if month >= 4 && month <= 6 {
+		//4月1号
+		nowQuarterFirstDay = time.Date(time.Now().Year(), 4, 1, 0, 0, 0, 0, time.Now().Location())
+	} else if month >= 7 && month <= 9 {
+		nowQuarterFirstDay = time.Date(time.Now().Year(), 7, 1, 0, 0, 0, 0, time.Now().Location())
+	} else {
+		nowQuarterFirstDay = time.Date(time.Now().Year(), 10, 1, 0, 0, 0, 0, time.Now().Location())
+	}
+	return nowQuarterFirstDay
+}
+
+// GetNowQuarterLastDay 获取本季度最后一天的时间
+func GetNowQuarterLastDay() time.Time {
+	month := int(time.Now().Month())
+	var nowQuarterLastDay time.Time
+	if month >= 1 && month <= 3 {
+		//03-31 23:59:59
+		nowQuarterLastDay = time.Date(time.Now().Year(), 3, 31, 23, 59, 59, 0, time.Now().Location())
+	} else if month >= 4 && month <= 6 {
+		//06-30 23:59:59
+		nowQuarterLastDay = time.Date(time.Now().Year(), 6, 30, 23, 59, 59, 0, time.Now().Location())
+	} else if month >= 7 && month <= 9 {
+		//09-30 23:59:59
+		nowQuarterLastDay = time.Date(time.Now().Year(), 9, 30, 23, 59, 59, 0, time.Now().Location())
+	} else {
+		//12-31 23:59:59
+		nowQuarterLastDay = time.Date(time.Now().Year(), 12, 31, 23, 59, 59, 0, time.Now().Location())
+	}
+	return nowQuarterLastDay
+}
+
+// GetNowHalfYearFirstDay 获取当前半年的第一天的时间
+func GetNowHalfYearFirstDay() time.Time {
+	month := int(time.Now().Month())
+	var nowHalfYearLastDay time.Time
+	if month >= 1 && month <= 6 {
+		//03-31 23:59:59
+		nowHalfYearLastDay = time.Date(time.Now().Year(), 1, 1, 0, 0, 0, 0, time.Now().Location())
+	} else {
+		//12-31 23:59:59
+		nowHalfYearLastDay = time.Date(time.Now().Year(), 7, 1, 0, 0, 0, 0, time.Now().Location())
+	}
+	return nowHalfYearLastDay
+}
+
+// GetNowHalfYearLastDay 获取当前半年的最后一天的时间
+func GetNowHalfYearLastDay() time.Time {
+	month := int(time.Now().Month())
+	var nowHalfYearLastDay time.Time
+	if month >= 1 && month <= 6 {
+		//03-31 23:59:59
+		nowHalfYearLastDay = time.Date(time.Now().Year(), 6, 30, 23, 59, 59, 0, time.Now().Location())
+	} else {
+		//12-31 23:59:59
+		nowHalfYearLastDay = time.Date(time.Now().Year(), 12, 31, 23, 59, 59, 0, time.Now().Location())
+	}
+	return nowHalfYearLastDay
+}
+
+// GetNowYearFirstDay 获取当前年的最后一天的时间
+func GetNowYearFirstDay() time.Time {
+	//12-31 23:59:59
+	nowYearFirstDay := time.Date(time.Now().Year(), 1, 1, 0, 0, 0, 0, time.Now().Location())
+	return nowYearFirstDay
+}
+
+// GetNowYearLastDay 获取当前年的最后一天的时间
+func GetNowYearLastDay() time.Time {
+	//12-31 23:59:59
+	nowYearLastDay := time.Date(time.Now().Year(), 12, 31, 23, 59, 59, 0, time.Now().Location())
+	return nowYearLastDay
+}
+
+// CalculationDate 计算两个日期之间相差n年m月y天
+func CalculationDate(startDate, endDate time.Time) (beetweenDay string, err error) {
+	//startDate := time.Date(2021, 3, 28, 0, 0, 0, 0, time.Now().Location())
+	//endDate := time.Date(2022, 3, 31, 0, 0, 0, 0, time.Now().Location())
+	numYear := endDate.Year() - startDate.Year()
+
+	numMonth := int(endDate.Month()) - int(startDate.Month())
+
+	numDay := 0
+	//获取截止月的总天数
+	endDateDays := getMonthDay(endDate.Year(), int(endDate.Month()))
+
+	//获取截止月的前一个月
+	endDatePrevMonthDate := endDate.AddDate(0, -1, 0)
+	//获取截止日期的上一个月的总天数
+	endDatePrevMonthDays := getMonthDay(endDatePrevMonthDate.Year(), int(endDatePrevMonthDate.Month()))
+	//获取开始日期的的月份总天数
+	startDateMonthDays := getMonthDay(startDate.Year(), int(startDate.Month()))
+
+	//判断,截止月是否完全被选中,如果相等,那么代表截止月份全部天数被选择
+	if endDate.Day() == endDateDays {
+		numDay = startDateMonthDays - startDate.Day() + 1
+
+		//如果剩余天数正好与开始日期的天数是一致的,那么月份加1
+		if numDay == startDateMonthDays {
+			numMonth++
+			numDay = 0
+			//超过月份了,那么年份加1
+			if numMonth == 12 {
+				numYear++
+				numMonth = 0
+			}
+		}
+	} else {
+		numDay = endDate.Day() - startDate.Day() + 1
+	}
+
+	//天数小于0,那么向月份借一位
+	if numDay < 0 {
+		//向上一个月借一个月的天数
+		numDay += endDatePrevMonthDays
+
+		//总月份减去一个月
+		numMonth = numMonth - 1
+	}
+
+	//月份小于0,那么向年份借一位
+	if numMonth < 0 {
+		//向上一个年借12个月
+		numMonth += 12
+
+		//总年份减去一年
+		numYear = numYear - 1
+	}
+	if numYear < 0 {
+		err = errors.New("日期异常")
+		return
+	}
+
+	if numYear > 0 {
+		beetweenDay += fmt.Sprint(numYear, "年")
+	}
+	if numMonth > 0 {
+		beetweenDay += fmt.Sprint(numMonth, "个月")
+	}
+	if numDay > 0 {
+		beetweenDay += fmt.Sprint(numDay, "天")
+	}
+	return
+}
+
+// getMonthDay 获取某年某月有多少天
+func getMonthDay(year, month int) (days int) {
+	if month != 2 {
+		if month == 4 || month == 6 || month == 9 || month == 11 {
+			days = 30
+
+		} else {
+			days = 31
+		}
+	} else {
+		if ((year%4) == 0 && (year%100) != 0) || (year%400) == 0 {
+			days = 29
+		} else {
+			days = 28
+		}
+	}
+	return
+}
+
+// InArray 是否在切片(数组/map)中含有该值,目前只支持:string、int 、 int64,其他都是返回false
+func InArray(needle interface{}, hyStack interface{}) bool {
+	switch key := needle.(type) {
+	case string:
+		for _, item := range hyStack.([]string) {
+			if key == item {
+				return true
+			}
+		}
+	case int:
+		for _, item := range hyStack.([]int) {
+			if key == item {
+				return true
+			}
+		}
+	case int64:
+		for _, item := range hyStack.([]int64) {
+			if key == item {
+				return true
+			}
+		}
+	default:
+		return false
+	}
+	return false
+}
+
+// bit转MB 保留小数
+func Bit2MB(bitSize int64, prec int) (size float64) {
+	mb := float64(bitSize) / float64(1024*1024)
+	size, _ = strconv.ParseFloat(strconv.FormatFloat(mb, 'f', prec, 64), 64)
+	return
+}
+
+// SubStr 截取字符串(中文)
+func SubStr(str string, subLen int) string {
+	strRune := []rune(str)
+	bodyRuneLen := len(strRune)
+	if bodyRuneLen > subLen {
+		bodyRuneLen = subLen
+	}
+	str = string(strRune[:bodyRuneLen])
+	return str
+}
+
+func GetUpdateWeekEn(updateWeek string) string {
+	switch updateWeek {
+	case "周一":
+		updateWeek = "monday"
+	case "周二":
+		updateWeek = "tuesday"
+	case "周三":
+		updateWeek = "wednesday"
+	case "周四":
+		updateWeek = "thursday"
+	case "周五":
+		updateWeek = "friday"
+	case "周六":
+		updateWeek = "saturday"
+	case "周日":
+		updateWeek = "sunday"
+	}
+	return updateWeek
+}
+
+func GetWeekZn(updateWeek string) (week string) {
+	switch updateWeek {
+	case "Monday":
+		week = "周一"
+	case "Tuesday":
+		week = "周二"
+	case "Wednesday":
+		week = "周三"
+	case "Thursday":
+		week = "周四"
+	case "Friday":
+		week = "周五"
+	case "Saturday":
+		week = "周六"
+	case "Sunday":
+		week = "周日"
+	}
+	return week
+}
+
+func GetWeekDay() (string, string) {
+	now := time.Now()
+	offset := int(time.Monday - now.Weekday())
+	//周日做特殊判断 因为time.Monday = 0
+	if offset > 0 {
+		offset = -6
+	}
+
+	lastoffset := int(time.Saturday - now.Weekday())
+	//周日做特殊判断 因为time.Monday = 0
+	if lastoffset == 6 {
+		lastoffset = -1
+	}
+
+	firstOfWeek := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset)
+	lastOfWeeK := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, lastoffset+1)
+	f := firstOfWeek.Unix()
+	l := lastOfWeeK.Unix()
+	return time.Unix(f, 0).Format("2006-01-02") + " 00:00:00", time.Unix(l, 0).Format("2006-01-02") + " 23:59:59"
+}

+ 37 - 0
utils/constants.go

@@ -0,0 +1,37 @@
+package utils
+
+// 常量定义
+const (
+	FormatTime            = "15:04:05"                //时间格式
+	FormatDate            = "2006-01-02"              //日期格式
+	FormatDateCN          = "2006年01月02日"             //日期格式(中文)
+	FormatDateUnSpace     = "20060102"                //日期格式
+	FormatDateTime        = "2006-01-02 15:04:05"     //完整时间格式
+	HlbFormatDateTime     = "2006-01-02_15:04:05.999" //完整时间格式
+	FormatDateTimeUnSpace = "20060102150405"          //完整时间格式
+	PageSize15            = 15                        //列表页每页数据量
+	PageSize5             = 5
+	PageSize10            = 10
+	PageSize20            = 20
+	PageSize30            = 30
+)
+
+// 手机号,电子邮箱正则
+const (
+	RegularMobile = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0-9])|(17[0-9])|(16[0-9])|(19[0-9]))\\d{8}$" //手机号码
+	RegularEmail  = `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`                                             //匹配电子邮箱
+)
+
+const (
+	APPNAME = "卓创红期数据生成服务"
+)
+
+const (
+	HANDLE_HONGQI_EXCEL  = "hongqi_excel:list:handle"  // 处理红桃三excel的队列
+	REFRESH_HONGQI_EXCEL = "hongqi_excel:list:refresh" // 刷新红桃三excel的队列
+)
+
+var (
+	APP_EDB_LIB_NAME_EN = "hongze_edb_lib"
+	EDB_LIB_Md5_KEY     = "GuRaB6dY1bXOJcwG"
+)

+ 191 - 0
utils/des3.go

@@ -0,0 +1,191 @@
+//加密工具类,用了3des和base64
+package utils
+
+import (
+	"bytes"
+	"crypto/cipher"
+	"crypto/des"
+	"encoding/base64"
+	"encoding/hex"
+	"errors"
+	"strings"
+)
+
+const (
+	key = ""
+)
+
+//des3 + base64 encrypt
+func DesBase64Encrypt(origData []byte) []byte {
+	result, err := TripleDesEncrypt(origData, []byte(key))
+	if err != nil {
+		panic(err)
+	}
+	return []byte(base64.StdEncoding.EncodeToString(result))
+}
+
+func DesBase64Decrypt(crypted []byte) []byte {
+	result, _ := base64.StdEncoding.DecodeString(string(crypted))
+	remain := len(result) % 8
+	if remain > 0 {
+		mod := 8 - remain
+		for i := 0; i < mod; i++ {
+			result = append(result, 0)
+		}
+	}
+	origData, err := TripleDesDecrypt(result, []byte(key))
+	if err != nil {
+		panic(err)
+	}
+	return origData
+}
+
+// 3DES加密
+func TripleDesEncrypt(origData, key []byte) ([]byte, error) {
+	block, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	origData = PKCS5Padding(origData, block.BlockSize())
+	// origData = ZeroPadding(origData, block.BlockSize())
+	blockMode := cipher.NewCBCEncrypter(block, key[:8])
+	crypted := make([]byte, len(origData))
+	blockMode.CryptBlocks(crypted, origData)
+	return crypted, nil
+}
+
+// 3DES解密
+func TripleDesDecrypt(crypted, key []byte) ([]byte, error) {
+	block, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	blockMode := cipher.NewCBCDecrypter(block, key[:8])
+	origData := make([]byte, len(crypted))
+	// origData := crypted
+	blockMode.CryptBlocks(origData, crypted)
+	origData = PKCS5UnPadding(origData)
+	// origData = ZeroUnPadding(origData)
+	return origData, nil
+}
+
+func ZeroPadding(ciphertext []byte, blockSize int) []byte {
+	padding := blockSize - len(ciphertext)%blockSize
+	padtext := bytes.Repeat([]byte{0}, padding)
+	return append(ciphertext, padtext...)
+}
+
+func ZeroUnPadding(origData []byte) []byte {
+	length := len(origData)
+	unpadding := int(origData[length-1])
+	return origData[:(length - unpadding)]
+}
+
+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 次
+	unpadding := int(origData[length-1])
+	return origData[:(length - unpadding)]
+}
+
+//DES加密
+func DesEncrypt(content string, key string) string {
+	contents := []byte(content)
+	keys := []byte(key)
+	block, err := des.NewCipher(keys)
+	if err != nil {
+		return ""
+	}
+	contents = PKCS5Padding(contents, block.BlockSize())
+	blockMode := cipher.NewCBCEncrypter(block, keys)
+	crypted := make([]byte, len(contents))
+	blockMode.CryptBlocks(crypted, contents)
+	return byteToHexString(crypted)
+}
+
+func byteToHexString(bytes []byte) string {
+	str := ""
+	for i := 0; i < len(bytes); i++ {
+		sTemp := hex.EncodeToString([]byte{bytes[i]})
+		if len(sTemp) < 2 {
+			str += string(0)
+		}
+		str += strings.ToUpper(sTemp)
+	}
+	return str
+}
+
+//DES解密
+func DesDecrypt(content string, key string) string {
+	contentBytes, err := hex.DecodeString(content)
+	if err != nil {
+		return "字符串转换16进制数组失败" + err.Error()
+	}
+	keys := []byte(key)
+	block, err := des.NewCipher(keys)
+	if err != nil {
+		return "解密失败" + err.Error()
+	}
+	blockMode := cipher.NewCBCDecrypter(block, keys)
+	origData := contentBytes
+	blockMode.CryptBlocks(origData, contentBytes)
+	origData = ZeroUnPadding(origData)
+	return string(origData)
+}
+
+// DES ECB PKCK5Padding
+func EntryptDesECB(data, key []byte) (string, error) {
+	if len(key) > 8 {
+		key = key[:8]
+	}
+	block, err := des.NewCipher(key)
+	if err != nil {
+		return "", errors.New("des.NewCipher " + err.Error())
+	}
+	bs := block.BlockSize()
+	data = PKCS5Padding(data, bs)
+	if len(data)%bs != 0 {
+		return "", errors.New("EntryptDesECB Need a multiple of the blocksize")
+	}
+	out := make([]byte, len(data))
+	dst := out
+	for len(data) > 0 {
+		block.Encrypt(dst, data[:bs])
+		data = data[bs:]
+		dst = dst[bs:]
+	}
+	return base64.StdEncoding.EncodeToString(out), nil
+}
+
+func DecryptDESECB(d string, key []byte) ([]byte, error) {
+	data, err := base64.StdEncoding.DecodeString(d)
+	if err != nil {
+		return nil, errors.New("decodebase64 " + err.Error())
+	}
+	if len(key) > 8 {
+		key = key[:8]
+	}
+	block, err := des.NewCipher(key)
+	if err != nil {
+		return nil, errors.New("des.NewCipher " + err.Error())
+	}
+	bs := block.BlockSize()
+	if len(data)%bs != 0 {
+		return nil, errors.New("DecryptDES crypto/cipher: input not full blocks")
+	}
+	out := make([]byte, len(data))
+	dst := out
+	for len(data) > 0 {
+		block.Decrypt(dst, data[:bs])
+		data = data[bs:]
+		dst = dst[bs:]
+	}
+	out = PKCS5UnPadding(out)
+	return out, nil
+}

+ 48 - 0
utils/directory.go

@@ -0,0 +1,48 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+)
+
+// @title    PathExists
+// @description   文件目录是否存在
+// @auth                     (2020/04/05  20:22)
+// @param     path            string
+// @return    err             error
+
+func PathExists(path string) (bool, error) {
+	_, err := os.Stat(path)
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
+
+// @title    createDir
+// @description   批量创建文件夹
+// @auth                     (2020/04/05  20:22)
+// @param     dirs            string
+// @return    err             error
+
+func CreateDir(dirs ...string) (err error) {
+	for _, v := range dirs {
+		exist, err := PathExists(v)
+		if err != nil {
+			return err
+		}
+		if !exist {
+			fmt.Println("create directory ", v)
+			//global.LOG.Debug("create directory ", v)
+			err = os.MkdirAll(v, os.ModePerm)
+			if err != nil {
+				fmt.Println("create directory", v, " error:", err)
+				//global.LOG.Error("create directory", v, " error:", err)
+			}
+		}
+	}
+	return err
+}

+ 357 - 0
utils/drawtext.go

@@ -0,0 +1,357 @@
+package utils
+
+import (
+	"bytes"
+	"fmt"
+	"image"
+	"image/color"
+	"image/draw"
+	"image/gif"
+	"image/jpeg"
+	"image/png"
+	"io/ioutil"
+	"net/http"
+	"os"
+
+	"github.com/golang/freetype"
+	"github.com/golang/freetype/truetype"
+	"golang.org/x/image/font"
+)
+
+//DrawTextInfo 图片绘字信息
+type DrawTextInfo struct {
+	Text 		string
+	X    		int
+	Y    		int
+	FontSize	int
+}
+
+//DrawRectInfo 图片画框信息
+type DrawRectInfo struct {
+	X1 int
+	Y1 int
+	X2 int
+	Y2 int
+}
+
+//TextBrush 字体相关
+type TextBrush struct {
+	FontType  *truetype.Font
+	FontSize  float64
+	FontColor *image.Uniform
+	TextWidth int
+}
+
+//NewTextBrush 新生成笔刷
+func NewTextBrush(FontFilePath string, FontSize float64, FontColor *image.Uniform, textWidth int) (*TextBrush, error) {
+	fontFile, err := ioutil.ReadFile(FontFilePath)
+	if err != nil {
+		return nil, err
+	}
+	fontType, err := truetype.Parse(fontFile)
+	if err != nil {
+		return nil, err
+	}
+	if textWidth <= 0 {
+		textWidth = 20
+	}
+	return &TextBrush{FontType: fontType, FontSize: FontSize, FontColor: FontColor, TextWidth: textWidth}, nil
+}
+
+//DrawFontOnRGBA 图片插入文字
+func (fb *TextBrush) DrawFontOnRGBA(rgba *image.RGBA, pt image.Point, content string) {
+
+	c := freetype.NewContext()
+	c.SetDPI(72)
+	c.SetFont(fb.FontType)
+	c.SetHinting(font.HintingFull)
+	c.SetFontSize(fb.FontSize)
+	c.SetClip(rgba.Bounds())
+	c.SetDst(rgba)
+	c.SetSrc(fb.FontColor)
+
+	c.DrawString(content, freetype.Pt(pt.X, pt.Y))
+
+}
+
+//Image2RGBA Image2RGBA
+func Image2RGBA(img image.Image) *image.RGBA {
+
+	baseSrcBounds := img.Bounds().Max
+
+	newWidth := baseSrcBounds.X
+	newHeight := baseSrcBounds.Y
+
+	des := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight)) // 底板
+	//首先将一个图片信息存入jpg
+	draw.Draw(des, des.Bounds(), img, img.Bounds().Min, draw.Over)
+
+	return des
+}
+
+type FontRGBA struct {
+	R uint8
+	G uint8
+	B uint8
+	A uint8
+}
+
+//DrawStringOnImageAndSave 图片上写文字
+func DrawStringOnImageAndSave(imagePath string, imageData []byte, infos []*DrawTextInfo, colorRGBA FontRGBA) (err error) {
+	//判断图片类型
+	var backgroud image.Image
+	filetype := http.DetectContentType(imageData)
+	switch filetype {
+	case "image/jpeg", "image/jpg":
+		backgroud, err = jpeg.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			fmt.Println("jpeg error")
+			return err
+		}
+
+	case "image/gif":
+		backgroud, err = gif.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return err
+		}
+
+	case "image/png":
+		backgroud, err = png.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return err
+		}
+	default:
+		return err
+	}
+	des := Image2RGBA(backgroud)
+
+	//新建笔刷
+	ttfPath := "static/ttf/songti.ttf"
+	textBrush, _ := NewTextBrush(ttfPath, 25, image.Black, 50)
+
+	//Px Py 绘图开始坐标 text要绘制的文字
+	//调整颜色
+	c := freetype.NewContext()
+	c.SetDPI(72)
+	c.SetFont(textBrush.FontType)
+	c.SetHinting(font.HintingFull)
+	c.SetFontSize(textBrush.FontSize)
+	c.SetClip(des.Bounds())
+	c.SetDst(des)
+	textBrush.FontColor = image.NewUniform(color.RGBA{
+		R: colorRGBA.R,
+		G: colorRGBA.G,
+		B: colorRGBA.B,
+		A: colorRGBA.A,
+	})
+	c.SetSrc(textBrush.FontColor)
+
+	for _, info := range infos {
+		c.DrawString(info.Text, freetype.Pt(info.X, info.Y))
+	}
+
+	//保存图片
+	fSave, err := os.Create(imagePath)
+	if err != nil {
+		return err
+	}
+	defer fSave.Close()
+
+	err = jpeg.Encode(fSave, des, nil)
+
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+//DrawRectOnImageAndSave 图片上画多个框
+func DrawRectOnImageAndSave(imagePath string, imageData []byte, infos []*DrawRectInfo) (err error) {
+	//判断图片类型
+	var backgroud image.Image
+	filetype := http.DetectContentType(imageData)
+	switch filetype {
+	case "image/jpeg", "image/jpg":
+		backgroud, err = jpeg.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			fmt.Println("jpeg error")
+			return err
+		}
+
+	case "image/gif":
+		backgroud, err = gif.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return err
+		}
+
+	case "image/png":
+		backgroud, err = png.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return err
+		}
+	default:
+		return err
+	}
+	des := Image2RGBA(backgroud)
+	//新建笔刷
+	textBrush, _ := NewTextBrush("arial.ttf", 15, image.Black, 15)
+	for _, info := range infos {
+		var c *freetype.Context
+		c = freetype.NewContext()
+		c.SetDPI(72)
+		c.SetFont(textBrush.FontType)
+		c.SetHinting(font.HintingFull)
+		c.SetFontSize(textBrush.FontSize)
+		c.SetClip(des.Bounds())
+		c.SetDst(des)
+		cGreen := image.NewUniform(color.RGBA{
+			R: 0,
+			G: 0xFF,
+			B: 0,
+			A: 255,
+		})
+
+		c.SetSrc(cGreen)
+		for i := info.X1; i < info.X2; i++ {
+			c.DrawString("·", freetype.Pt(i, info.Y1))
+			c.DrawString("·", freetype.Pt(i, info.Y2))
+		}
+		for j := info.Y1; j < info.Y2; j++ {
+			c.DrawString("·", freetype.Pt(info.X1, j))
+			c.DrawString("·", freetype.Pt(info.X2, j))
+		}
+	}
+
+	//保存图片
+	fSave, err := os.Create(imagePath)
+	if err != nil {
+		return err
+	}
+	defer fSave.Close()
+
+	err = jpeg.Encode(fSave, des, nil)
+
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+//DrawStringOnImage 生成图片
+func DrawStringOnImage(imageData []byte, infos []*DrawTextInfo, colorRGBA FontRGBA, fontSize float64, fontWidth int) (picBytes bytes.Buffer, err error) {
+	//判断图片类型
+	var backgroud image.Image
+	filetype := http.DetectContentType(imageData)
+	switch filetype {
+	case "image/jpeg", "image/jpg":
+		backgroud, err = jpeg.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			fmt.Println("jpeg error")
+			return
+		}
+
+	case "image/gif":
+		backgroud, err = gif.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return
+		}
+
+	case "image/png":
+		backgroud, err = png.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return
+		}
+	default:
+		return
+	}
+	des := Image2RGBA(backgroud)
+
+	//新建笔刷
+	ttfPath := "static/ttf/songti.ttf"
+	textBrush, _ := NewTextBrush(ttfPath, fontSize, image.Black, fontWidth)
+
+	//Px Py 绘图开始坐标 text要绘制的文字
+	//调整颜色
+	c := freetype.NewContext()
+	c.SetDPI(72)
+	c.SetFont(textBrush.FontType)
+	c.SetHinting(font.HintingFull)
+	c.SetFontSize(textBrush.FontSize)
+	c.SetClip(des.Bounds())
+	c.SetDst(des)
+	textBrush.FontColor = image.NewUniform(color.RGBA{
+		R: colorRGBA.R,
+		G: colorRGBA.G,
+		B: colorRGBA.B,
+		A: colorRGBA.A,
+	})
+	c.SetSrc(textBrush.FontColor)
+
+	for _, info := range infos {
+		c.DrawString(info.Text, freetype.Pt(info.X, info.Y))
+	}
+
+	err = jpeg.Encode(&picBytes, des, nil)
+
+	return
+}
+
+
+// DrawStringRowsOnImage 生成图片
+func DrawStringRowsOnImage(imageData []byte, infos []*DrawTextInfo, colorRGBA FontRGBA, fontSize float64, fontWidth int) (picBytes bytes.Buffer, err error) {
+	//判断图片类型
+	var backgroud image.Image
+	filetype := http.DetectContentType(imageData)
+	switch filetype {
+	case "image/jpeg", "image/jpg":
+		backgroud, err = jpeg.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			fmt.Println("jpeg error")
+			return
+		}
+
+	case "image/gif":
+		backgroud, err = gif.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return
+		}
+
+	case "image/png":
+		backgroud, err = png.Decode(bytes.NewReader(imageData))
+		if err != nil {
+			return
+		}
+	default:
+		return
+	}
+	des := Image2RGBA(backgroud)
+
+	//新建笔刷
+	ttfPath := "static/ttf/songti.ttf"
+	textBrush, _ := NewTextBrush(ttfPath, fontSize, image.Black, fontWidth)
+
+	//Px Py 绘图开始坐标 text要绘制的文字
+	//调整颜色
+
+	for _, info := range infos {
+		c := freetype.NewContext()
+		c.SetDPI(72)
+		c.SetFont(textBrush.FontType)
+		c.SetHinting(font.HintingFull)
+		c.SetFontSize(float64(info.FontSize))
+		c.SetClip(des.Bounds())
+		c.SetDst(des)
+		textBrush.FontColor = image.NewUniform(color.RGBA{
+			R: colorRGBA.R,
+			G: colorRGBA.G,
+			B: colorRGBA.B,
+			A: colorRGBA.A,
+		})
+		c.SetSrc(textBrush.FontColor)
+		c.DrawString(info.Text, freetype.Pt(info.X, info.Y))
+	}
+
+	err = jpeg.Encode(&picBytes, des, nil)
+
+	return
+}

+ 10 - 0
utils/index_files.go

@@ -0,0 +1,10 @@
+package utils
+
+const (
+	IndexSaveDir       = "D:\\mysteel_data\\"
+	IndexMsergeSaveDir = "D:\\mysteel_data\\merge\\"
+
+	//IndexSaveDir       = "/Users/hyc/Documents/rdluck/mysteel_data/"
+	//IndexMsergeSaveDir = "/Users/hyc/Documents/rdluck/mysteel_data/merge/"
+	//IndexMsergeSaveDir = "/Users/roc/go/src/hongze/hongtao3_watch"
+)

+ 214 - 0
utils/validator.go

@@ -0,0 +1,214 @@
+package utils
+
+import (
+	"errors"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+type Rules map[string][]string
+
+type RulesMap map[string]Rules
+
+var CustomizeMap = make(map[string]Rules)
+
+// 注册自定义规则方案建议在路由初始化层即注册
+func RegisterRule(key string, rule Rules) (err error) {
+	if CustomizeMap[key] != nil {
+		return errors.New(key + "已注册,无法重复注册")
+	} else {
+		CustomizeMap[key] = rule
+		return nil
+	}
+}
+
+// 非空 不能为其对应类型的0值
+func NotEmpty() string {
+	return "notEmpty"
+}
+
+// 小于入参(<) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Lt(mark string) string {
+	return "lt=" + mark
+}
+
+// 小于等于入参(<=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Le(mark string) string {
+	return "le=" + mark
+}
+
+// 等于入参(==) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Eq(mark string) string {
+	return "eq=" + mark
+}
+
+// 不等于入参(!=)  如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Ne(mark string) string {
+	return "ne=" + mark
+}
+
+// 大于等于入参(>=) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Ge(mark string) string {
+	return "ge=" + mark
+}
+
+// 大于入参(>) 如果为string array Slice则为长度比较 如果是 int uint float 则为数值比较
+func Gt(mark string) string {
+	return "gt=" + mark
+}
+
+// 校验方法 接收两个参数  入参实例,规则map
+func Verify(st interface{}, roleMap Rules) (err error) {
+	compareMap := map[string]bool{
+		"lt": true,
+		"le": true,
+		"eq": true,
+		"ne": true,
+		"ge": true,
+		"gt": true,
+	}
+
+	typ := reflect.TypeOf(st)
+	val := reflect.ValueOf(st) // 获取reflect.Type类型
+
+	kd := val.Kind() // 获取到st对应的类别
+	if kd != reflect.Struct {
+		return errors.New("expect struct")
+	}
+	num := val.NumField()
+	// 遍历结构体的所有字段
+	for i := 0; i < num; i++ {
+		tagVal := typ.Field(i)
+		val := val.Field(i)
+
+		//如果有填写form字段的话,那么返回提示使用该字段
+		tagName := tagVal.Tag.Get("form")
+		if tagName == "" {
+			tagName = tagVal.Name
+		}
+
+		if len(roleMap[tagVal.Name]) > 0 {
+			for _, v := range roleMap[tagVal.Name] {
+				switch {
+				case v == "notEmpty":
+					if isBlank(val) {
+						return errors.New(tagName + "值不能为空")
+					}
+				case compareMap[strings.Split(v, "=")[0]]:
+					if !compareVerify(val, v) {
+						return errors.New(tagName + "长度或值不在合法范围," + v)
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// 长度和数字的校验方法 根据类型自动校验
+func compareVerify(value reflect.Value, VerifyStr string) bool {
+	switch value.Kind() {
+	case reflect.String, reflect.Slice, reflect.Array:
+		return compare(value.Len(), VerifyStr)
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return compare(value.Uint(), VerifyStr)
+	case reflect.Float32, reflect.Float64:
+		return compare(value.Float(), VerifyStr)
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return compare(value.Int(), VerifyStr)
+	default:
+		return false
+	}
+}
+
+// 非空校验
+func isBlank(value reflect.Value) bool {
+	switch value.Kind() {
+	case reflect.String:
+		return value.Len() == 0
+	case reflect.Bool:
+		return !value.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return value.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return value.Uint() == 0
+	case reflect.Float32, reflect.Float64:
+		return value.Float() == 0
+	case reflect.Interface, reflect.Ptr:
+		return value.IsNil()
+	}
+	return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface())
+}
+
+func compare(value interface{}, VerifyStr string) bool {
+	VerifyStrArr := strings.Split(VerifyStr, "=")
+	val := reflect.ValueOf(value)
+	switch val.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		VInt, VErr := strconv.ParseInt(VerifyStrArr[1], 10, 64)
+		if VErr != nil {
+			return false
+		}
+		switch {
+		case VerifyStrArr[0] == "lt":
+			return val.Int() < VInt
+		case VerifyStrArr[0] == "le":
+			return val.Int() <= VInt
+		case VerifyStrArr[0] == "eq":
+			return val.Int() == VInt
+		case VerifyStrArr[0] == "ne":
+			return val.Int() != VInt
+		case VerifyStrArr[0] == "ge":
+			return val.Int() >= VInt
+		case VerifyStrArr[0] == "gt":
+			return val.Int() > VInt
+		default:
+			return false
+		}
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		VInt, VErr := strconv.Atoi(VerifyStrArr[1])
+		if VErr != nil {
+			return false
+		}
+		switch {
+		case VerifyStrArr[0] == "lt":
+			return val.Uint() < uint64(VInt)
+		case VerifyStrArr[0] == "le":
+			return val.Uint() <= uint64(VInt)
+		case VerifyStrArr[0] == "eq":
+			return val.Uint() == uint64(VInt)
+		case VerifyStrArr[0] == "ne":
+			return val.Uint() != uint64(VInt)
+		case VerifyStrArr[0] == "ge":
+			return val.Uint() >= uint64(VInt)
+		case VerifyStrArr[0] == "gt":
+			return val.Uint() > uint64(VInt)
+		default:
+			return false
+		}
+	case reflect.Float32, reflect.Float64:
+		VFloat, VErr := strconv.ParseFloat(VerifyStrArr[1], 64)
+		if VErr != nil {
+			return false
+		}
+		switch {
+		case VerifyStrArr[0] == "lt":
+			return val.Float() < VFloat
+		case VerifyStrArr[0] == "le":
+			return val.Float() <= VFloat
+		case VerifyStrArr[0] == "eq":
+			return val.Float() == VFloat
+		case VerifyStrArr[0] == "ne":
+			return val.Float() != VFloat
+		case VerifyStrArr[0] == "ge":
+			return val.Float() >= VFloat
+		case VerifyStrArr[0] == "gt":
+			return val.Float() > VFloat
+		default:
+			return false
+		}
+	default:
+		return false
+	}
+}

+ 88 - 0
utils/whereBuild.go

@@ -0,0 +1,88 @@
+package utils
+
+import (
+	"fmt"
+	"strings"
+)
+
+type NullType byte
+
+const (
+	_ NullType = iota
+	// IsNull the same as `is null`
+	IsNull
+	// IsNotNull the same as `is not null`
+	IsNotNull
+)
+
+// sql生成工具
+func WhereBuild(where map[string]interface{}) (whereSQL string, vals []interface{}, err error) {
+	for k, v := range where {
+		ks := strings.Split(k, " ")
+		if len(ks) > 2 {
+			return "", nil, fmt.Errorf("Error in query condition: %s. ", k)
+		}
+
+		if whereSQL != "" {
+			whereSQL += " AND "
+		}
+		strings.Join(ks, ",")
+		switch len(ks) {
+		case 1:
+			//fmt.Println(reflect.TypeOf(v))
+			switch v := v.(type) {
+			case NullType:
+				if v == IsNotNull {
+					whereSQL += fmt.Sprint(k, " IS NOT NULL")
+				} else {
+					whereSQL += fmt.Sprint(k, " IS NULL")
+				}
+			default:
+				whereSQL += fmt.Sprint(k, "=?")
+				vals = append(vals, v)
+			}
+			break
+		case 2:
+			k = ks[0]
+			switch ks[1] {
+			case "=":
+				whereSQL += fmt.Sprint(k, "=?")
+				vals = append(vals, v)
+				break
+			case ">":
+				whereSQL += fmt.Sprint(k, ">?")
+				vals = append(vals, v)
+				break
+			case ">=":
+				whereSQL += fmt.Sprint(k, ">=?")
+				vals = append(vals, v)
+				break
+			case "<":
+				whereSQL += fmt.Sprint(k, "<?")
+				vals = append(vals, v)
+				break
+			case "<=":
+				whereSQL += fmt.Sprint(k, "<=?")
+				vals = append(vals, v)
+				break
+			case "!=":
+				whereSQL += fmt.Sprint(k, "!=?")
+				vals = append(vals, v)
+				break
+			case "<>":
+				whereSQL += fmt.Sprint(k, "!=?")
+				vals = append(vals, v)
+				break
+			case "in":
+				whereSQL += fmt.Sprint(k, " in (?)")
+				vals = append(vals, v)
+				break
+			case "like":
+				whereSQL += fmt.Sprint(k, " like ?")
+				vals = append(vals, v)
+			}
+			break
+		}
+	}
+	return
+}

+ 164 - 0
watch/watch.go

@@ -0,0 +1,164 @@
+package watch
+
+import (
+	"fmt"
+	"hongze/hongtao3_watch/cache"
+	"hongze/hongtao3_watch/global"
+	"hongze/hongtao3_watch/services"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/fsnotify/fsnotify"
+)
+
+/*
+CREATE动作即临时文件的创建
+WRITE写文件动作
+CHMOD修改文件属性
+REMOVE删除临时文件。
+*/
+
+func ListenFolderNew() {
+	// 如果没有配置就不监听了
+	if global.CONFIG.Serve.ListenExcelPath == `` {
+		return
+	}
+
+	fmt.Println("-----文件夹监听-------")
+	watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		fmt.Println("fsnotify.NewWatcher err:" + err.Error())
+		//log.Fatal(err)
+	}
+	defer watcher.Close()
+
+	done2 := make(chan bool)
+	go func() {
+		for {
+			select {
+			case event, ok := <-watcher.Events:
+				//fmt.Println("event.Name", event.Name)
+				//fmt.Println(event.Op)
+				//if ok && event.Op == fsnotify.Create &&
+				//	!strings.Contains(event.Name, "tmp") &&
+				//	!strings.Contains(event.Name, ".TMP") &&
+				//	!strings.Contains(event.Name, "~") &&
+				//	(strings.Contains(event.Name, "xlsx") || strings.Contains(event.Name, "xls")) {
+				//	WatchIndexFileRelease(event.Name)
+				//	fmt.Println("op:event.Name", event.Name)
+				//}
+
+				log.Println("event:", event)
+				// 如果是临时文件,那么就忽略
+				if strings.Contains(event.Name, "tmp") ||
+					strings.Contains(event.Name, ".TMP") ||
+					strings.Contains(event.Name, "~") {
+					continue
+				}
+
+				if ok && event.Op&fsnotify.Create == fsnotify.Create {
+					//fmt.Println("新增文件 : ", event.Name)
+
+					// 判断是否属于文件夹,如果是文件夹,那么就加上该文件夹的监听权限
+					fi, err := os.Stat(event.Name)
+					if err == nil {
+						if fi.IsDir() {
+							watcherDir(watcher, event.Name)
+							fmt.Println("添加文件夹,添加监控 : ", event.Name)
+						} else {
+							// 读取文件
+							cache.AddIndexHandleExcel(event.Name)
+						}
+					}
+
+				}
+				if ok && event.Op&fsnotify.Write == fsnotify.Write {
+					//fmt.Println("写入文件 : ", event.Name)
+					// 读取文件
+
+					cache.AddIndexHandleExcel(event.Name)
+				}
+				if ok && event.Op&fsnotify.Remove == fsnotify.Remove {
+					//fmt.Println("删除文件 : ", event.Name)
+					//如果删除文件是目录,则移除监控
+					fi, err := os.Stat(event.Name)
+					if err == nil && fi.IsDir() {
+						_ = watcher.Remove(event.Name)
+						fmt.Println("删除文件夹,删除监控 : ", event.Name)
+					}
+				}
+				if ok && event.Op&fsnotify.Rename == fsnotify.Rename {
+					//fmt.Println("重命名文件 : ", event.Name)
+					//如果重命名文件是目录,则移除监控
+					//注意这里无法使用os.Stat来判断是否是目录了
+					//因为重命名后,go已经无法找到原文件来获取信息了
+					//所以这里就简单粗爆的直接remove好了
+					_ = watcher.Remove(event.Name)
+					//fmt.Println("更改文件夹名称,删除监控 : ", event.Name)
+				}
+				if ok && event.Op&fsnotify.Chmod == fsnotify.Chmod {
+					fmt.Println("修改权限 : ", event.Name)
+				}
+			case err := <-watcher.Errors:
+				if err != nil {
+					fmt.Println("watcher.Errors:", err)
+					//log.Println("error:", err)
+				}
+			case <-time.After(60 * time.Second):
+				continue
+			}
+		}
+	}()
+
+	// 开始监听
+	watcherDir(watcher, global.CONFIG.Serve.ListenExcelPath)
+
+	<-done2
+}
+
+var watcherDirMap map[string]string
+
+// 添加监听目录
+func watcherDir(watcher *fsnotify.Watcher, dirPath string) {
+	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
+		//这里判断是否为目录,只需监控目录即可
+		//目录下的文件也在监控范围内,不需要我们一个一个加
+		if info.IsDir() {
+			subpath, e := filepath.Abs(path)
+			if e != nil {
+				return e
+			}
+			err = watcher.Add(subpath)
+			if err != nil {
+				return err
+			}
+			// 入库
+			if watcherDirMap == nil {
+				watcherDirMap = make(map[string]string)
+			}
+			watcherDirMap[subpath] = subpath
+			fmt.Println("监控 : ", subpath)
+		}
+		return nil
+	})
+	if err != nil {
+		fmt.Println("watcher.Add:" + err.Error())
+		//log.Fatal(err)
+	}
+}
+
+// HandleFileUpdate 处理文件变化
+func HandleFileUpdate(updateFilePath string) {
+	fileExt := filepath.Ext(updateFilePath)
+
+	if fileExt != ".xlsx" && fileExt != ".xls" {
+		fmt.Println("不是excel文件")
+		return
+	}
+
+	// 读取excel内容并操作入库
+	services.ReadHqExcel(updateFilePath)
+}