init...
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package base
|
||||
|
||||
import "time"
|
||||
|
||||
// GlobalModel 全局映射实体
|
||||
type GlobalModel struct {
|
||||
CreateTime time.Time `json:"createTime"` // 创建时间
|
||||
CreateBy string `json:"createBy"` // 创建人
|
||||
UpdateTime time.Time `json:"updateTime"` // 更新时间
|
||||
UpdateBy string `json:"updateBy"` // 更新人
|
||||
}
|
||||
|
||||
// GlobalQuery 全局Query通用条件
|
||||
type GlobalQuery struct {
|
||||
BeginTime string `form:"beginTime"` // 开始时间
|
||||
EndTime string `form:"endTime"` // 结束时间
|
||||
PageNum int `form:"pageNum"` // 当前页码
|
||||
PageSize int `form:"pageSize"` // 显示条数
|
||||
}
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"cutego/core/dao"
|
||||
"cutego/pkg/common"
|
||||
"cutego/pkg/constant"
|
||||
)
|
||||
|
||||
// RemoveList 批量根据Key删除数据
|
||||
// @Param list []string 键合集
|
||||
func RemoveList(list []string) {
|
||||
dao.RedisDB.DELALL(list)
|
||||
}
|
||||
|
||||
// RemoveKey 根据key删除
|
||||
// @Param key 键
|
||||
// @Return int 删除的数量
|
||||
func RemoveCache(key string) int {
|
||||
del, err := dao.RedisDB.DEL(key)
|
||||
if err != nil {
|
||||
common.ErrorLog(err)
|
||||
}
|
||||
return del
|
||||
}
|
||||
|
||||
// GetCache 获取缓存数据
|
||||
// @Param key 键
|
||||
// @Return string 值
|
||||
func GetCache(key string) string {
|
||||
val, err := dao.RedisDB.GET(key)
|
||||
if err != nil {
|
||||
common.ErrorLog(constant.RedisConst{}.GetRedisError(), err.Error())
|
||||
return ""
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// SetCache 设置缓存数据
|
||||
// @Param key 键
|
||||
// @Param value 值
|
||||
// @Return 新增的行数
|
||||
func SetCache(key string, value interface{}) int {
|
||||
n, err := dao.RedisDB.SET(key, common.StructToJson(value))
|
||||
if err != nil {
|
||||
common.ErrorLog(constant.RedisConst{}.GetRedisError(), err.Error())
|
||||
return 0
|
||||
}
|
||||
return int(n)
|
||||
}
|
||||
|
||||
// SetCache 设置缓存数据, 并指定过期时间
|
||||
// @Param key 键
|
||||
// @Param value 值
|
||||
// @Param sec 过期时间(单位: 秒)
|
||||
// @Return 新增的行数
|
||||
func SetCacheTTL(key string, value interface{}, sec int) {
|
||||
dao.RedisDB.SETEX(key, sec, common.StructToJson(value))
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package common
|
||||
|
||||
// RedisData 存储数据结构
|
||||
type RedisData struct {
|
||||
Key string
|
||||
Field string
|
||||
Value string
|
||||
Expire int64
|
||||
}
|
||||
|
||||
// RedisDataArray RedisData of array
|
||||
type RedisDataArray []*RedisData
|
||||
|
||||
// IRedis redis client interface
|
||||
type IRedis interface {
|
||||
// KEYS get patten key array
|
||||
KEYS(patten string) ([]string, error)
|
||||
|
||||
// SCAN get patten key array
|
||||
SCAN(patten string) ([]string, error)
|
||||
|
||||
// DEL delete k-v
|
||||
DEL(key string) (int, error)
|
||||
|
||||
// DELALL delete key array
|
||||
DELALL(key []string) (int, error)
|
||||
|
||||
// GET get k-v
|
||||
GET(key string) (string, error)
|
||||
|
||||
// SET set k-v
|
||||
//SET(key string, value string) (int64, error)
|
||||
|
||||
// SETEX set k-v expire seconds
|
||||
SETEX(key string, sec int, value string) (int64, error)
|
||||
|
||||
// EXPIRE set key expire seconds
|
||||
EXPIRE(key string, sec int64) (int64, error)
|
||||
|
||||
// HGETALL get map of key
|
||||
HGETALL(key string) (map[string]string, error)
|
||||
|
||||
// HGET get value of key-field
|
||||
HGET(key string, field string) (string, error)
|
||||
|
||||
// HSET set value of key-field
|
||||
//HSET(key string, field string, value string) (int64, error)
|
||||
|
||||
// Write 向redis中写入多组数据
|
||||
Write(data RedisDataArray)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package common
|
||||
|
||||
import "github.com/druidcaesa/gotool"
|
||||
|
||||
func ErrorLog(v ...interface{}) {
|
||||
gotool.Logs.ErrorLog().Println(v)
|
||||
}
|
||||
|
||||
func FatalfLog(format string, v ...interface{}) {
|
||||
gotool.Logs.ErrorLog().Fatalf(format, v)
|
||||
}
|
||||
|
||||
func InfoLog(v ...interface{}) {
|
||||
gotool.Logs.InfoLog().Println(v)
|
||||
}
|
||||
|
||||
func InfoLogf(format string, v ...interface{}) {
|
||||
gotool.Logs.InfoLog().Printf(format+"\n", v)
|
||||
}
|
||||
|
||||
func DebugLogf(format string, v ...interface{}) {
|
||||
gotool.Logs.DebugLog().Printf(format+"\n", v)
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"cutego/pkg/config"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// IntToString int转string
|
||||
func IntToString(n int) string {
|
||||
return strconv.Itoa(n)
|
||||
}
|
||||
func mapToBytes(data map[string]interface{}) []byte {
|
||||
bytes, _ := json.Marshal(data)
|
||||
return bytes
|
||||
}
|
||||
|
||||
// MapToStruct map转struct
|
||||
func MapToStruct(data map[string]interface{}, v interface{}) {
|
||||
_ = json.Unmarshal(mapToBytes(data), v)
|
||||
}
|
||||
|
||||
// GetDirPath 获取目录路径
|
||||
func GetDirPath(resType string) string {
|
||||
sysType := runtime.GOOS
|
||||
switch sysType {
|
||||
case "linux":
|
||||
if resType == "log" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Linux.Logs
|
||||
} else if resType == "avatar" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Linux.Avatar
|
||||
} else if resType == "file" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Linux.Path
|
||||
}
|
||||
break
|
||||
case "windows":
|
||||
if resType == "log" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Windows.Logs
|
||||
} else if resType == "avatar" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Windows.Avatar
|
||||
} else if resType == "file" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Windows.Path
|
||||
}
|
||||
break
|
||||
case "mac":
|
||||
if resType == "log" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Mac.Logs
|
||||
} else if resType == "avatar" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Mac.Avatar
|
||||
} else if resType == "file" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Mac.Path
|
||||
}
|
||||
break
|
||||
case "darwin":
|
||||
if resType == "log" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Mac.Logs
|
||||
} else if resType == "avatar" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Mac.Avatar
|
||||
} else if resType == "file" {
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Mac.Path
|
||||
}
|
||||
}
|
||||
return config.AppCoreConfig.CuteGoConfig.File.Linux.Logs
|
||||
}
|
||||
|
||||
// CreateAllDir 递归创建文件夹
|
||||
func CreateAllDir(filePath string) error {
|
||||
if !IsFileOrDirExist(filePath) {
|
||||
err := os.MkdirAll(filePath, os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Println("创建文件夹失败, error info:", err)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsFileOrDirExist 判断所给路径文件/文件夹是否存在(返回true是存在)
|
||||
func IsFileOrDirExist(path string) bool {
|
||||
// os.Stat获取文件信息
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsExist(err) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 类三元表达式
|
||||
// condition 成立条件
|
||||
// trueVal 当条件为true时返回
|
||||
// false 当条件为false时返回
|
||||
func If(condition bool, trueVal, falseVal interface{}) interface{} {
|
||||
if condition {
|
||||
return trueVal
|
||||
}
|
||||
return falseVal
|
||||
}
|
||||
|
||||
// 结构体、Map等转Json字符串
|
||||
// @Param v interface{}
|
||||
// @Return Json字符串
|
||||
func StructToJson(v interface{}) string {
|
||||
jsonBytes, err := json.Marshal(&v)
|
||||
if err != nil {
|
||||
ErrorLog(err)
|
||||
return ""
|
||||
}
|
||||
s := string(jsonBytes)
|
||||
DebugLogf("StructToJson, json=%s", s)
|
||||
return s
|
||||
}
|
||||
|
||||
// Json字符串转结构体、Map等
|
||||
//
|
||||
// 单个对象
|
||||
// s := new(models2.SysConfig)
|
||||
// return common.JsonToStruct(get, s).(*models2.SysConfig)
|
||||
//
|
||||
// 切片(interface{}.(期望类型))
|
||||
// s := make([]interface {}, 0)
|
||||
// target := common.JsonToStruct(get, s)
|
||||
// target.([]entity.SysDictData)
|
||||
//
|
||||
// @Param data Json字符串
|
||||
// @Param s 容器(结构体、Map等)
|
||||
// @Return interface{}
|
||||
func JsonToStruct(data string, s interface{}) interface{} {
|
||||
err := json.Unmarshal([]byte(data), &s)
|
||||
if err != nil {
|
||||
ErrorLog(err)
|
||||
return nil
|
||||
}
|
||||
DebugLogf("JsonToStruct, obj=%v", s)
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
config "cutego/pkg/config/models"
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// AppCoreConfig 核心配置
|
||||
AppCoreConfig *config.ApplicationCoreStruct
|
||||
// AppEnvConfig 环境配置
|
||||
AppEnvConfig *config.ApplicationEnvStruct
|
||||
)
|
||||
|
||||
// GetRootPath 获取项目根路径
|
||||
func GetRootPath() string {
|
||||
rootPath, _ := os.Getwd()
|
||||
return rootPath
|
||||
}
|
||||
|
||||
// GetPathSeparator 获取路径分隔符
|
||||
func GetPathSeparator() string {
|
||||
return string(os.PathSeparator)
|
||||
}
|
||||
|
||||
// PathExists 判断文件或文件夹是否存在
|
||||
// 如果返回的错误为nil,说明文件或文件夹存在
|
||||
// 如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在
|
||||
// 如果返回的错误为其它类型,则不确定是否在存在
|
||||
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
|
||||
}
|
||||
|
||||
// LoadYamlFile 加载yaml文件
|
||||
func LoadYamlFile(filename string, v interface{}) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
err = yaml.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 配置文件所在路径
|
||||
const BaseConfigDirPath = "configs"
|
||||
|
||||
func readAppYmlFile(resourcePath string) {
|
||||
|
||||
// 读取主配置文件
|
||||
applicationCoreFileName := BaseConfigDirPath + "/application.yml"
|
||||
applicationCoreFilePath := resourcePath + GetPathSeparator() + applicationCoreFileName
|
||||
exists, _ := PathExists(applicationCoreFilePath)
|
||||
if !exists {
|
||||
panic(applicationCoreFileName + "配置文件不存在!")
|
||||
}
|
||||
AppCoreConfig = &config.ApplicationCoreStruct{}
|
||||
// 由于要改变appConfig内部的值, 所以这里要取址
|
||||
LoadYamlFile(applicationCoreFilePath, AppCoreConfig)
|
||||
|
||||
// 读取环境文件
|
||||
applicationEnvFileName := fmt.Sprintf(BaseConfigDirPath+"/application-%s.yml", AppCoreConfig.CuteGoConfig.Active)
|
||||
applicationEnvFilePath := resourcePath + GetPathSeparator() + applicationEnvFileName
|
||||
exists, _ = PathExists(applicationEnvFilePath)
|
||||
if !exists {
|
||||
panic(applicationEnvFileName + "配置文件不存在!")
|
||||
}
|
||||
AppEnvConfig = &config.ApplicationEnvStruct{}
|
||||
// 由于要改变appConfig内部的值, 所以这里要取址
|
||||
LoadYamlFile(applicationEnvFilePath, AppEnvConfig)
|
||||
}
|
||||
|
||||
// ExecuteTip 执行日志
|
||||
func ExecuteTip(message string) {
|
||||
fmt.Println(fmt.Sprintf("-------------------- %s --------------------", message))
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 资源文件所在的路径
|
||||
resourcePath := GetRootPath()
|
||||
ExecuteTip("初始化应用配置 start")
|
||||
readAppYmlFile(resourcePath)
|
||||
ExecuteTip("初始化应用配置 end")
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package config
|
||||
|
||||
type ApplicationCoreStruct struct {
|
||||
CuteGoConfig CuteGoConfig `yaml:"cutego"`
|
||||
}
|
||||
|
||||
// CuteGoConfig 总配置
|
||||
type CuteGoConfig struct {
|
||||
// 默认激活dev配置
|
||||
Active string `yaml:"active" default:"dev"`
|
||||
// 开启演示模式
|
||||
DemoMode bool `yaml:"demo-mode"`
|
||||
Mail MailConfig `yaml:"mail"`
|
||||
TaskPool TaskPoolConfig `yaml:"task-pool"`
|
||||
Captcha CaptchaConfig `yaml:"captcha"`
|
||||
File FileConfig `yaml:"file"`
|
||||
}
|
||||
|
||||
// MailConfig 邮件
|
||||
type MailConfig struct {
|
||||
// 服务地址
|
||||
Host string `yaml:"host"`
|
||||
// 服务端口
|
||||
Port int `yaml:"port"`
|
||||
// 用户名
|
||||
Username string `yaml:"username"`
|
||||
// 密码
|
||||
Password string `yaml:"password"`
|
||||
// 默认编码
|
||||
DefaultEncoding string `yaml:"default-encoding"`
|
||||
}
|
||||
|
||||
// TaskPoolConfig 线程池
|
||||
type TaskPoolConfig struct {
|
||||
// 核心线程池大小
|
||||
CorePoolSize int `yaml:"core-pool-size"`
|
||||
// 最大线程数(尽可能的大)
|
||||
MaxPoolSize int `yaml:"max-pool-size"`
|
||||
// 活跃时间(单位: s)
|
||||
KeepAliveSeconds int `yaml:"keep-alive-seconds"`
|
||||
// 队列容量
|
||||
QueueCapacity int `yaml:"queue-capacity"`
|
||||
}
|
||||
|
||||
// CaptchaConfig 验证码有效时间(单位: s)
|
||||
type CaptchaConfig struct {
|
||||
// 邮箱
|
||||
Email int `yaml:"email"`
|
||||
// 手机短信
|
||||
Sms int `yaml:"sms"`
|
||||
}
|
||||
|
||||
// FileConfig 文件上传
|
||||
type FileConfig struct {
|
||||
// 文件大小(单位: mb)
|
||||
FileMaxSize int `yaml:"file-max-size"`
|
||||
// 头像大小(单位: mb)
|
||||
AvatarMaxSize int `yaml:"avatar-max-size"`
|
||||
Mac FilePath `yaml:"mac"`
|
||||
Linux FilePath `yaml:"linux"`
|
||||
Windows FilePath `yaml:"windows"`
|
||||
}
|
||||
type FilePath struct {
|
||||
Path string `yaml:"path"`
|
||||
Avatar string `yaml:"avatar"`
|
||||
Logs string `yaml:"logs"`
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package config
|
||||
|
||||
type ApplicationEnvStruct struct {
|
||||
Server ServerConfig `yaml:"server"`
|
||||
DataSource DataSourceConfig `yaml:"datasource"`
|
||||
Redis RedisConfig `yaml:"redis"`
|
||||
MongoDb MongoDbConfig `yaml:"mongodb"`
|
||||
Login LoginConfig `yaml:"login"`
|
||||
Jwt JwtConfig `yaml:"jwt"`
|
||||
Logger LoggerConfig `yaml:"logger"`
|
||||
}
|
||||
|
||||
// ServerConfig web服务
|
||||
type ServerConfig struct {
|
||||
// Running in "debug" mode. Switch to "release" mode in production
|
||||
RunMode string `yaml:"run-mode"`
|
||||
// web服务监听端口(生产的端口有可能不一样,所以根据环境隔离开)
|
||||
Port int `yaml:"port"`
|
||||
}
|
||||
|
||||
// DataSourceConfig 数据源
|
||||
type DataSourceConfig struct {
|
||||
// 数据库类型
|
||||
DbType string `yaml:"db-type" default:"mysql"`
|
||||
// 服务地址
|
||||
Host string `yaml:"host"`
|
||||
// 服务端口
|
||||
Port int `yaml:"port"`
|
||||
// 用户名称
|
||||
Username string `yaml:"username"`
|
||||
// 用户密码
|
||||
Password string `yaml:"password"`
|
||||
// 数据库名称
|
||||
Database string `yaml:"database"`
|
||||
// 编码
|
||||
Charset string `yaml:"charset"`
|
||||
// 空闲时的最大连接数
|
||||
MaxIdleSize int `yaml:"max-idle-size"`
|
||||
// 数据库的最大打开连接数
|
||||
MaxOpenSize int `yaml:"max-open-size"`
|
||||
}
|
||||
|
||||
// RedisConfig 缓存
|
||||
type RedisConfig struct {
|
||||
// 数据库索引
|
||||
Database int `yaml:"database"`
|
||||
// 服务地址
|
||||
Host string `yaml:"host"`
|
||||
// 服务端口
|
||||
Port int `yaml:"port"`
|
||||
// 服务密码
|
||||
Password string `yaml:"password"`
|
||||
// 连接超时时间
|
||||
Timeout int `yaml:"timeout"`
|
||||
Pool RedisPoolConfig `yaml:"pool"`
|
||||
}
|
||||
|
||||
// RedisPoolConfig redis连接池配置
|
||||
type RedisPoolConfig struct {
|
||||
// 连接池最大连接数(使用负值表示没有限制, 最佳配置为cpu核数+1)
|
||||
MaxActive int `yaml:"max-active"`
|
||||
// 连接池中的最大空闲连接
|
||||
MaxIdle int `yaml:"max-idle"`
|
||||
// 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
MaxWait int `yaml:"max-wait"`
|
||||
}
|
||||
|
||||
// MongoDbConfig MongoDB
|
||||
type MongoDbConfig struct {
|
||||
Url string `yaml:"url"`
|
||||
Port string `yaml:"port"`
|
||||
DB string `yaml:"db"`
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
}
|
||||
|
||||
// LoginConfig 登录相关
|
||||
type LoginConfig struct {
|
||||
// 是否限制单用户登录
|
||||
Single bool `yaml:"single"`
|
||||
}
|
||||
|
||||
// JwtConfig jwt参数
|
||||
type JwtConfig struct {
|
||||
// 请求头前缀
|
||||
Header string `yaml:"header" json:"header,omitempty"`
|
||||
// 令牌前缀
|
||||
TokenStartWith string `yaml:"token-start-with" json:"token_start_with,omitempty"`
|
||||
// 加密密钥
|
||||
TokenSecret string `yaml:"token-secret" json:"token_secret,omitempty"`
|
||||
// 令牌过期时间 此处单位: h , 默认1小时
|
||||
TokenExpired int `yaml:"token-expired" json:"token_expired,omitempty"`
|
||||
// token在cookie中的别称
|
||||
CookieKey string `yaml:"cookie-key"`
|
||||
// 在线用户key
|
||||
OnlineKey string `yaml:"online-key" json:"online_key,omitempty"`
|
||||
// token 续期检查时间范围(默认30分钟, 单位毫秒), 在token即将过期的一段时间内用户操作了, 则给用户的token续期
|
||||
Detect int `yaml:"detect" json:"detect,omitempty"`
|
||||
// 续期时间范围, 默认1小时, 单位毫秒
|
||||
Renew int `yaml:"renew" json:"renew,omitempty"`
|
||||
}
|
||||
|
||||
// LoggerConfig 日志文件
|
||||
type LoggerConfig struct {
|
||||
// 最大保存时间(单位: d)
|
||||
MaxSaveAge int `yaml:"max-save-age"`
|
||||
// 日志切割时间间隔(单位: d)
|
||||
RotationTime int `yaml:"rotation-time"`
|
||||
// 日志级别
|
||||
Level string `yaml:"level"`
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
RedisOnlineUserKey = "online:"
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
mysqlErrorMsg = "调用MySQL发生异常, %s"
|
||||
)
|
||||
|
||||
type MysqlConst struct{}
|
||||
|
||||
// GetMysqlError Mysql异常拼接常量
|
||||
func (c MysqlConst) GetMysqlError() string {
|
||||
return mysqlErrorMsg
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
redisErrorMsg = "调用Redis发生异常, %s"
|
||||
redisDictKey = "dict:"
|
||||
redisConfigKey = "config:"
|
||||
)
|
||||
|
||||
// RedisConst Redis相关操作常量
|
||||
type RedisConst struct{}
|
||||
|
||||
// GetRedisError Redis异常拼接常量
|
||||
func (c RedisConst) GetRedisError() string {
|
||||
return redisErrorMsg
|
||||
}
|
||||
|
||||
// GetRedisDictKey 获取Redis的Dict的key
|
||||
func (c RedisConst) GetRedisDictKey() string {
|
||||
return redisDictKey
|
||||
}
|
||||
|
||||
// GetRedisConfigKey 获取redis的config的key
|
||||
func (c RedisConst) GetRedisConfigKey() string {
|
||||
return redisConfigKey
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cronjob
|
||||
|
||||
import (
|
||||
"cutego/core/api/v1/request"
|
||||
"cutego/core/job"
|
||||
"cutego/core/service"
|
||||
"cutego/pkg/common"
|
||||
"github.com/robfig/cron"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cron表达式参考
|
||||
// 每隔5秒执行一次:*/5 * * * * ?
|
||||
// 每隔1分钟执行一次:0 */1 * * * ?
|
||||
// 每天23点执行一次:0 0 23 * * ?
|
||||
// 每天凌晨1点执行一次:0 0 1 * * ?
|
||||
// 每月1号凌晨1点执行一次:0 0 1 1 * ?
|
||||
// 每月最后一天23点执行一次:0 0 23 L * ?
|
||||
// 每周星期天凌晨1点实行一次:0 0 1 ? * L
|
||||
// 在26分、29分、33分执行一次:0 26,29,33 * * * ?
|
||||
// 每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
|
||||
|
||||
// 定时任务: 别名与调度器的映射
|
||||
var AliasCronMap = make(map[string]*cron.Cron)
|
||||
|
||||
// 停止任务, 不会停止已开始的任务
|
||||
func StopCronFunc(aliasName string) {
|
||||
common.InfoLogf("停止任务 %s ---> Start", aliasName)
|
||||
AliasCronMap[aliasName].Stop()
|
||||
common.InfoLogf("停止任务 %s ---> Finish", aliasName)
|
||||
}
|
||||
|
||||
// 开始任务
|
||||
func StartCronFunc(aliasName string) {
|
||||
common.InfoLogf("唤起任务 %s ---> Start", aliasName)
|
||||
AliasCronMap[aliasName].Start()
|
||||
common.InfoLogf("唤起任务 %s ---> Finish", aliasName)
|
||||
}
|
||||
|
||||
func init() {
|
||||
if len(job.AliasFuncMap) > 0 {
|
||||
//go test()
|
||||
index := 1
|
||||
for true {
|
||||
q := request.CronJobQuery{}
|
||||
q.PageNum = index
|
||||
data, _ := service.CronJobService{}.FindPage(q)
|
||||
if len(data) == 0 {
|
||||
break
|
||||
}
|
||||
for _, datum := range data {
|
||||
c := cron.New()
|
||||
c.AddFunc(datum.JobCron, job.AliasFuncMap[datum.FuncAlias])
|
||||
c.Start()
|
||||
|
||||
AliasCronMap[datum.FuncAlias] = c
|
||||
common.InfoLogf("调度定时任务 --- %s ---> Success", datum.JobName)
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试通过
|
||||
func test() {
|
||||
time.Sleep(time.Second * 10)
|
||||
StopCronFunc("test1")
|
||||
time.Sleep(time.Second * 10)
|
||||
StartCronFunc("test1")
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package excels
|
||||
|
||||
import (
|
||||
"github.com/druidcaesa/gotool"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 获取title头list
|
||||
func getTitle(list []map[string]string) []string {
|
||||
titleList := make([]string, 0)
|
||||
for _, item := range list {
|
||||
for _, s := range item {
|
||||
titleList = append(titleList, s)
|
||||
}
|
||||
}
|
||||
return titleList
|
||||
}
|
||||
|
||||
// ExportExcel 导出excel
|
||||
func ExportExcel(list []interface{}, title string) (error, *excelize.File) {
|
||||
// 获取标题
|
||||
headerList, expList := ExcelCreate(list[0])
|
||||
headers := getTitle(headerList)
|
||||
// 默认存在第一个工作簿是 Sheet1 首字母要大写, 否则会报错。
|
||||
// 如果想额外的创建工作簿, 可以使用, sheet2 := file.NewSheet("Sheet2"), 工作簿的名称不区分大小写。
|
||||
// 如果有多个工作簿, 可以使用 file.SetActiveSheet(index) 来指定打开文件时focus到哪个工作簿
|
||||
sheet1 := "Sheet1"
|
||||
files := excelize.NewFile()
|
||||
character := string(65 + len(headers) - 1)
|
||||
/* -------------------- 第一行大标题 -------------------- */
|
||||
// 设置行高
|
||||
err := files.SetRowHeight(sheet1, 1, 25)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
// 合并单元格
|
||||
err = files.MergeCell(sheet1, "A1", character+"1")
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
// 设置单元格样式:对齐;字体, 大小;单元格边框
|
||||
styleTitle, _ := files.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},"font":{"bold":true,"italic":false,"family":"Calibri","size":16,"color":"#000000"},"border":[{"type":"left","color":"#3FAD08","style":0},{"type":"top","color":"#3FAD08","style":0},{"type":"bottom","color":"#3FAD08","style":2},{"type":"right","color":"#3FAD08","style":0}]}`)
|
||||
err = files.SetCellStyle(sheet1, "A1", character+"1", styleTitle)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
|
||||
err = files.SetCellValue(sheet1, "A1", title)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
|
||||
/* -------------------- 字段标题 -------------------- */
|
||||
styleHeader, _ := files.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},"font":{"bold":false,"italic":false,"family":"Calibri","size":10,"color":"#000000"}}`)
|
||||
err = files.SetCellStyle(sheet1, "A2", character+"2", styleHeader)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
for k, v := range headers {
|
||||
err = files.SetCellValue(sheet1, string(65+k)+"2", v)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
}
|
||||
// 设置最后一列宽度
|
||||
err = files.SetColWidth(sheet1, "C", character, 20)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
// 冻结窗口:冻结第一行和第二行
|
||||
err = files.SetPanes(sheet1, `{"freeze":true,"split":false,"x_split":0,"y_split":2}`)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
///* -------------------- 填充行数据 -------------------- */
|
||||
line := 3
|
||||
for _, v := range list {
|
||||
var num = 0
|
||||
lineChr := strconv.Itoa(line)
|
||||
// 设置样式
|
||||
err = files.SetCellStyle(sheet1, "A"+lineChr, character+lineChr, styleHeader)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
// 反射获取数据和类型
|
||||
getValue := reflect.ValueOf(v)
|
||||
getType := reflect.TypeOf(v)
|
||||
n := getValue.NumField()
|
||||
for i := 0; i < n; i++ {
|
||||
val := getValue.Field(i)
|
||||
name := getType.Field(i).Name
|
||||
if !getIsTitle(name, headerList) {
|
||||
continue
|
||||
}
|
||||
err = files.SetCellValue(sheet1, string(65+num)+lineChr, getExp(name, expList, val.Interface()))
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
num++
|
||||
}
|
||||
line++
|
||||
}
|
||||
return nil, files
|
||||
}
|
||||
|
||||
func getIsTitle(name string, headerList []map[string]string) bool {
|
||||
flag := false
|
||||
for _, m := range headerList {
|
||||
if gotool.StrUtils.HasNotEmpty(m[name]) {
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
func getExp(name string, expList []map[string][]map[string]string, value interface{}) interface{} {
|
||||
for _, m := range expList {
|
||||
if len(m[name]) > 0 {
|
||||
maps := m[name]
|
||||
for _, m2 := range maps {
|
||||
if gotool.StrUtils.HasNotEmpty(m2[interfaceToString(value)]) {
|
||||
value = m2[interfaceToString(value)]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func interfaceToString(inter interface{}) string {
|
||||
switch inter.(type) {
|
||||
case string:
|
||||
return inter.(string)
|
||||
case int:
|
||||
return strconv.Itoa(inter.(int))
|
||||
case int64:
|
||||
return strconv.FormatInt(inter.(int64), 10)
|
||||
default:
|
||||
return inter.(string)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package excels
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 定义tagName名称
|
||||
const tagName = "excel"
|
||||
|
||||
// Excels 定义Excel接口
|
||||
type Excels interface {
|
||||
Excels(interface{}) (bool, string)
|
||||
}
|
||||
|
||||
// DefaultExcels 默认Excel配置
|
||||
type DefaultExcels struct {
|
||||
}
|
||||
|
||||
// Excels 实现接口
|
||||
func (e DefaultExcels) Excels(val interface{}) (bool, string) {
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// `excel:"name='',readConverterExp=''"`
|
||||
// 获取标题头
|
||||
func getExcelFromTagTitle(proName string, tag string) map[string]string {
|
||||
args := strings.SplitN(tag, ",", 2)
|
||||
m := make(map[string]string)
|
||||
for i := 0; i < len(args); i++ {
|
||||
contains := strings.Contains(args[i], "name")
|
||||
if contains {
|
||||
m[proName] = strings.Split(args[i], "=")[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// `excel:"name='',readConverterExp='Y=1,N=2'"`
|
||||
func getReadConverterExp(proName string, tag string) map[string][]map[string]string {
|
||||
list := make(map[string][]map[string]string, 0)
|
||||
args := strings.SplitN(tag, ",", 2)
|
||||
for i := 0; i < len(args); i++ {
|
||||
if strings.Contains(args[i], "format") {
|
||||
maps := make([]map[string]string, 0)
|
||||
converter := strings.SplitN(args[i], "=", 2)[1]
|
||||
values := strings.Split(converter, ",")
|
||||
for j := 0; j < len(values); j++ {
|
||||
split := strings.Split(values[j], "=")
|
||||
m := make(map[string]string)
|
||||
m[split[0]] = split[1]
|
||||
maps = append(maps, m)
|
||||
}
|
||||
list[proName] = maps
|
||||
break
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ExcelCreate 获取name名称和readConverterExp集合
|
||||
func ExcelCreate(s interface{}) ([]map[string]string, []map[string][]map[string]string) {
|
||||
titleList := make([]map[string]string, 0)
|
||||
expList := make([]map[string][]map[string]string, 0)
|
||||
// 获取值域
|
||||
v := reflect.ValueOf(s)
|
||||
t := reflect.TypeOf(s)
|
||||
var title map[string]string
|
||||
var exp map[string][]map[string]string
|
||||
// 遍历反射树形
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
tag := v.Type().Field(i).Tag.Get(tagName)
|
||||
if tag == "" || tag == "-" {
|
||||
continue
|
||||
}
|
||||
name := t.Field(i).Name
|
||||
title = getExcelFromTagTitle(name, tag)
|
||||
exp = getReadConverterExp(name, tag)
|
||||
titleList = append(titleList, title)
|
||||
expList = append(expList, exp)
|
||||
}
|
||||
return titleList, expList
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"github.com/druidcaesa/gotool"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
// DownloadExcel 公共下载execl方法
|
||||
func DownloadExcel(c *gin.Context, file *excelize.File) {
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Disposition", "attachment; filename="+gotool.IdUtils.IdUUIDToRan(false)+".xlsx")
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
c.Header("FileName", gotool.IdUtils.IdUUIDToRan(false)+".xlsx")
|
||||
_ = file.Write(c.Writer)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"cutego/pkg/config"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func DemoHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if config.AppCoreConfig.CuteGoConfig.DemoMode {
|
||||
request := inDisRequest()
|
||||
for i := 0; i < len(request); i++ {
|
||||
if c.Request.Method == http.MethodDelete || c.Request.Method == http.MethodPut || strings.Contains(c.Request.RequestURI, request[i]) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusInternalServerError,
|
||||
"msg": "演示模式, 不允许操作",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 禁用请求
|
||||
func inDisRequest() []string {
|
||||
return []string{
|
||||
"/remove",
|
||||
"/profile/avatar",
|
||||
"/resetPwd",
|
||||
"/edit",
|
||||
"/insert",
|
||||
"/add",
|
||||
"/delete",
|
||||
"/export",
|
||||
"/import",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 是否在放行范围内
|
||||
func doSquare(c *gin.Context) bool {
|
||||
request := inSquareRequest()
|
||||
for i := 0; i < len(request); i++ {
|
||||
replace := strings.Contains(c.Request.RequestURI, request[i])
|
||||
if replace {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 放行的请求
|
||||
func inSquareRequest() []string {
|
||||
var req []string
|
||||
// 放行登录请求
|
||||
req = append(req, "/api/v1/login")
|
||||
// 放行websocket请求
|
||||
req = append(req, "/websocket")
|
||||
return req
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"cutego/core/api/v1/response"
|
||||
"cutego/core/dao"
|
||||
"cutego/pkg/cache"
|
||||
"cutego/pkg/config"
|
||||
"errors"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// JWTAuth 中间件, 检查token
|
||||
func JWTAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 放行的请求先放行
|
||||
if doSquare(c) {
|
||||
return
|
||||
}
|
||||
authHeader := c.Request.Header.Get(config.AppEnvConfig.Jwt.Header)
|
||||
if authHeader == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusUnauthorized,
|
||||
"msg": "请求未携带token, 无权限访问",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// 按空格分割
|
||||
authHeaderSplit := strings.SplitN(authHeader, " ", 2)
|
||||
if !(len(authHeaderSplit) == 2 && authHeaderSplit[0] == config.AppEnvConfig.Jwt.TokenStartWith) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusUnauthorized,
|
||||
"msg": "请求头中Token格式有误",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// authHeaderSplit[1]是获取到的tokenString, 我们使用之前定义好的解析JWT的函数来解析它
|
||||
currentTokenStr := authHeaderSplit[1]
|
||||
claims, err := ParseToken(currentTokenStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusUnauthorized,
|
||||
"msg": err.Error(),
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
singleTag := config.AppEnvConfig.Login.Single
|
||||
if singleTag {
|
||||
token, err := dao.RedisDB.GET(claims.UserInfo.UserName)
|
||||
if err == nil {
|
||||
if !(token == currentTokenStr) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": http.StatusUnauthorized,
|
||||
"msg": "您的账号已在其他终端登录, 请重新登录",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// 继续交由下一个路由处理,并将解析出的信息传递下去
|
||||
c.Set("claims", claims)
|
||||
}
|
||||
}
|
||||
|
||||
// 一些常量
|
||||
var (
|
||||
TokenExpired error = errors.New("授权已过期")
|
||||
TokenNotValidYet error = errors.New("Token not active yet")
|
||||
TokenMalformed error = errors.New("令牌非法")
|
||||
TokenInvalid error = errors.New("Couldn't handle this token:")
|
||||
)
|
||||
|
||||
// CuteClaims 自定义声明结构体并内嵌jwt.StandardClaims
|
||||
// jwt包自带的jwt.StandardClaims只包含了官方字段
|
||||
// 我们这里需要额外记录一些字段,所以要自定义结构体
|
||||
// 如果想要保存更多信息,都可以添加到这个结构体中
|
||||
type CuteClaims struct {
|
||||
UserInfo response.UserResponse `json:"userInfo"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
// CreateToken 生成一个token
|
||||
func CreateToken(claims CuteClaims) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
// 定义Secret
|
||||
var tokenSecret = []byte(config.AppEnvConfig.Jwt.TokenSecret)
|
||||
return token.SignedString(tokenSecret)
|
||||
}
|
||||
|
||||
// CreateUserToken 生成含有用户信息的token
|
||||
func CreateUserToken(u *response.UserResponse) (string, error) {
|
||||
if config.AppEnvConfig.Jwt.TokenExpired == 0 {
|
||||
config.AppEnvConfig.Jwt.TokenExpired = 1
|
||||
}
|
||||
// 定义JWT的过期时间
|
||||
tokenExpired := time.Hour * time.Duration(config.AppEnvConfig.Jwt.TokenExpired)
|
||||
// 定义Secret
|
||||
var tokenSecret = []byte(config.AppEnvConfig.Jwt.TokenSecret)
|
||||
|
||||
// 创建我们自己的声明
|
||||
c := CuteClaims{
|
||||
UserInfo: *u, // 自定义字段
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(tokenExpired).Unix(), // 过期时间
|
||||
Issuer: "tianjun@odboy.cn", // 签发人
|
||||
},
|
||||
}
|
||||
// 使用指定的签名方法创建签名对象
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
|
||||
// 使用指定的secret签名并获得完整的编码后的字符串token
|
||||
return token.SignedString(tokenSecret)
|
||||
}
|
||||
|
||||
// ParseToken 解析Token
|
||||
func ParseToken(tokenString string) (*CuteClaims, error) {
|
||||
// 定义Secret
|
||||
var tokenSecret = []byte(config.AppEnvConfig.Jwt.TokenSecret)
|
||||
token, err := jwt.ParseWithClaims(tokenString, &CuteClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return tokenSecret, nil
|
||||
})
|
||||
if err != nil {
|
||||
if ve, ok := err.(*jwt.ValidationError); ok {
|
||||
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
|
||||
return nil, TokenMalformed
|
||||
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
|
||||
// Token 过期(授权已过期)
|
||||
return nil, TokenExpired
|
||||
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
|
||||
return nil, TokenNotValidYet
|
||||
} else {
|
||||
return nil, TokenInvalid
|
||||
}
|
||||
}
|
||||
}
|
||||
if claims, ok := token.Claims.(*CuteClaims); ok && token.Valid {
|
||||
if config.AppEnvConfig.Login.Single {
|
||||
tokenData := cache.GetCache(claims.UserInfo.UserName)
|
||||
if tokenData == "" {
|
||||
return nil, TokenExpired
|
||||
}
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
return nil, TokenInvalid
|
||||
}
|
||||
|
||||
// RefreshToken 更新token
|
||||
func RefreshToken(tokenString string) (string, error) {
|
||||
// 定义Secret
|
||||
var tokenSecret = []byte(config.AppEnvConfig.Jwt.TokenSecret)
|
||||
jwt.TimeFunc = func() time.Time {
|
||||
return time.Unix(0, 0)
|
||||
}
|
||||
token, err := jwt.ParseWithClaims(tokenString, &CuteClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return tokenSecret, nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if claims, ok := token.Claims.(*CuteClaims); ok && token.Valid {
|
||||
jwt.TimeFunc = time.Now
|
||||
claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
|
||||
return CreateToken(*claims)
|
||||
}
|
||||
return "", TokenInvalid
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"cutego/pkg/common"
|
||||
"cutego/pkg/config"
|
||||
"fmt"
|
||||
"github.com/druidcaesa/gotool"
|
||||
"github.com/gin-gonic/gin"
|
||||
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||
"github.com/rifflock/lfshook"
|
||||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LoggerToFile 日志记录到文件
|
||||
func LoggerToFile() gin.HandlerFunc {
|
||||
dirPath := common.GetDirPath("log")
|
||||
fileName := path.Join(dirPath, "application.log")
|
||||
if !common.IsFileOrDirExist(dirPath) {
|
||||
err := common.CreateAllDir(dirPath)
|
||||
if err != nil {
|
||||
common.ErrorLog(err)
|
||||
}
|
||||
}
|
||||
if !gotool.FileUtils.Exists(fileName) {
|
||||
create, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
common.ErrorLog(err)
|
||||
}
|
||||
defer create.Close()
|
||||
}
|
||||
// 写入文件
|
||||
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
|
||||
if err != nil {
|
||||
fmt.Println("err", err)
|
||||
}
|
||||
logger := logrus.New()
|
||||
// 输出源
|
||||
logger.Out = src
|
||||
switch config.AppEnvConfig.Logger.Level {
|
||||
case "debug":
|
||||
logger.SetLevel(logrus.DebugLevel)
|
||||
break
|
||||
case "warn":
|
||||
logger.SetLevel(logrus.WarnLevel)
|
||||
break
|
||||
case "info":
|
||||
logger.SetLevel(logrus.InfoLevel)
|
||||
break
|
||||
case "error":
|
||||
logger.SetLevel(logrus.ErrorLevel)
|
||||
break
|
||||
case "fatal":
|
||||
logger.SetLevel(logrus.FatalLevel)
|
||||
case "panic":
|
||||
logger.SetLevel(logrus.PanicLevel)
|
||||
break
|
||||
case "trace":
|
||||
logger.SetLevel(logrus.TraceLevel)
|
||||
default:
|
||||
logger.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
logger.SetLevel(logrus.DebugLevel)
|
||||
// 设置 rotatelogs
|
||||
if config.AppEnvConfig.Logger.MaxSaveAge == 0 {
|
||||
config.AppEnvConfig.Logger.MaxSaveAge = 7
|
||||
}
|
||||
if config.AppEnvConfig.Logger.RotationTime == 0 {
|
||||
config.AppEnvConfig.Logger.RotationTime = 1
|
||||
}
|
||||
logWriter, err := rotatelogs.New(
|
||||
// 分割后的文件名称
|
||||
fileName+".%Y%m%d.logs",
|
||||
// 生成软链, 指向最新日志文件
|
||||
rotatelogs.WithLinkName(fileName),
|
||||
rotatelogs.WithMaxAge(time.Duration(config.AppEnvConfig.Logger.MaxSaveAge)*24*time.Hour),
|
||||
rotatelogs.WithRotationTime(time.Duration(config.AppEnvConfig.Logger.RotationTime)*24*time.Hour),
|
||||
)
|
||||
writeMap := lfshook.WriterMap{
|
||||
logrus.InfoLevel: logWriter,
|
||||
logrus.FatalLevel: logWriter,
|
||||
logrus.DebugLevel: logWriter,
|
||||
logrus.WarnLevel: logWriter,
|
||||
logrus.ErrorLevel: logWriter,
|
||||
logrus.PanicLevel: logWriter,
|
||||
}
|
||||
lfHook := lfshook.NewHook(writeMap, &logrus.JSONFormatter{
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
})
|
||||
// 新增 Hook
|
||||
logger.AddHook(lfHook)
|
||||
return func(c *gin.Context) {
|
||||
// 开始时间
|
||||
startTime := time.Now()
|
||||
// 处理请求
|
||||
c.Next()
|
||||
// 结束时间
|
||||
endTime := time.Now()
|
||||
// 执行时间
|
||||
latencyTime := endTime.Sub(startTime)
|
||||
// 请求方式
|
||||
reqMethod := c.Request.Method
|
||||
// 请求路由
|
||||
reqUri := c.Request.RequestURI
|
||||
// 状态码
|
||||
statusCode := c.Writer.Status()
|
||||
// 请求IP
|
||||
clientIP := c.ClientIP()
|
||||
// 日志格式
|
||||
logger.WithFields(logrus.Fields{
|
||||
"status_code": statusCode,
|
||||
"latency_time": latencyTime,
|
||||
"client_ip": clientIP,
|
||||
"req_method": reqMethod,
|
||||
"req_uri": reqUri,
|
||||
}).Info()
|
||||
}
|
||||
}
|
||||
|
||||
// 日志记录到 MongoDB
|
||||
func LoggerToMongo() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 日志记录到 ES
|
||||
func LoggerToES() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 日志记录到 MQ
|
||||
func LoggerToMQ() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"cutego/pkg/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
func Recover(c *gin.Context) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
common.ErrorLog("panic: %v\n", r)
|
||||
debug.PrintStack()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"code": http.StatusInternalServerError,
|
||||
"msg": errorToString(r),
|
||||
"data": nil,
|
||||
})
|
||||
// 终止后续接口调用, 不加的话recover捕捉到异常后, 还会继续执行接口后续的代码
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
// 加载完 defer recover, 继续后续接口调用
|
||||
c.Next()
|
||||
}
|
||||
|
||||
// recover错误, 转string
|
||||
func errorToString(r interface{}) string {
|
||||
switch v := r.(type) {
|
||||
case error:
|
||||
return v.Error()
|
||||
default:
|
||||
return r.(string)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"cutego/pkg/common"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
// Page 分页结构体
|
||||
type Page struct {
|
||||
Size int `json:"size"` // 显示条数
|
||||
Total int64 `json:"total"` // 总条数
|
||||
List interface{} `json:"list"` // 数据
|
||||
}
|
||||
|
||||
type Start struct {
|
||||
}
|
||||
|
||||
// StartSize 获取分页偏移量
|
||||
func StartSize(pageNum int, size int) int {
|
||||
if pageNum == 0 {
|
||||
pageNum = 1
|
||||
}
|
||||
if size == 0 {
|
||||
size = 10
|
||||
}
|
||||
num := (pageNum - 1) * size
|
||||
return num
|
||||
}
|
||||
|
||||
// GetTotal 获取总条数
|
||||
func GetTotal(engine *xorm.Session, args ...interface{}) (int64, error) {
|
||||
if args != nil {
|
||||
engine.Table(args)
|
||||
}
|
||||
count, err := engine.Count()
|
||||
if err != nil {
|
||||
common.ErrorLog(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package redispool
|
||||
|
||||
import (
|
||||
"cutego/pkg/common"
|
||||
"cutego/pkg/config"
|
||||
"fmt"
|
||||
"github.com/druidcaesa/gotool"
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"time"
|
||||
)
|
||||
|
||||
// https://godoc.org/github.com/gomodule/redigo/redis#pkg-examples
|
||||
// https://github.com/gomodule/redigo
|
||||
|
||||
// RedisClient redis client instance
|
||||
type RedisClient struct {
|
||||
pool *redis.Pool
|
||||
// 数据接收
|
||||
chanRx chan common.RedisDataArray
|
||||
// 是否退出
|
||||
isExit bool
|
||||
}
|
||||
|
||||
// NewRedis new redis client
|
||||
func NewRedis() *RedisClient {
|
||||
return &RedisClient{
|
||||
pool: newPool(),
|
||||
chanRx: make(chan common.RedisDataArray, 100),
|
||||
}
|
||||
}
|
||||
|
||||
// newPool 线程池
|
||||
func newPool() *redis.Pool {
|
||||
if config.AppEnvConfig.Redis.Pool.MaxIdle == 0 {
|
||||
config.AppEnvConfig.Redis.Pool.MaxIdle = 3
|
||||
}
|
||||
return &redis.Pool{
|
||||
MaxIdle: config.AppEnvConfig.Redis.Pool.MaxIdle,
|
||||
IdleTimeout: time.Duration(config.AppEnvConfig.Redis.Pool.MaxWait) * time.Second,
|
||||
MaxActive: config.AppEnvConfig.Redis.Pool.MaxActive,
|
||||
Dial: func() (redis.Conn, error) {
|
||||
c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.AppEnvConfig.Redis.Host, config.AppEnvConfig.Redis.Port))
|
||||
if err != nil {
|
||||
common.FatalfLog("Redis.Dial: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if gotool.StrUtils.HasNotEmpty(config.AppEnvConfig.Redis.Password) {
|
||||
if _, err := c.Do("AUTH", config.AppEnvConfig.Redis.Password); err != nil {
|
||||
c.Close()
|
||||
common.FatalfLog("Redis.AUTH: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if _, err := c.Do("SELECT", config.AppEnvConfig.Redis.Database); err != nil {
|
||||
c.Close()
|
||||
common.FatalfLog("Redis.SELECT: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动接收任务协程
|
||||
func (r *RedisClient) Start() {
|
||||
r.isExit = false
|
||||
// 开启协程用于循环接收数据
|
||||
go r.loopRead()
|
||||
}
|
||||
|
||||
// Stop 停止接收任务
|
||||
func (r *RedisClient) Stop() {
|
||||
r.isExit = true
|
||||
// 关闭数据接收通道
|
||||
close(r.chanRx)
|
||||
// 关闭redis线程池
|
||||
r.pool.Close()
|
||||
}
|
||||
|
||||
// Write 向redis中写入多组数据
|
||||
func (r *RedisClient) Write(data common.RedisDataArray) {
|
||||
r.chanRx <- data
|
||||
}
|
||||
|
||||
// loopRead 循环接收数据
|
||||
func (r *RedisClient) loopRead() {
|
||||
for !r.isExit {
|
||||
select {
|
||||
case rx := <-r.chanRx:
|
||||
for _, it := range rx {
|
||||
if len(it.Key) > 0 {
|
||||
if len(it.Field) > 0 {
|
||||
if _, err := r.HSET(it.Key, it.Field, it.Value); err != nil {
|
||||
common.DebugLogf("[%s, %s, %s]: %s\n", it.Key, it.Field, it.Value, err.Error())
|
||||
}
|
||||
} else {
|
||||
if _, err := r.SET(it.Key, it.Value); err != nil {
|
||||
common.DebugLogf("[%s, %s, %s]: %s\n", it.Key, it.Field, it.Value, err.Error())
|
||||
}
|
||||
}
|
||||
if it.Expire > 0 {
|
||||
r.EXPIRE(it.Key, it.Expire)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Error get redis connect error
|
||||
func (r *RedisClient) Error() error {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return conn.Err()
|
||||
}
|
||||
|
||||
// 常用Redis操作命令的封装
|
||||
// http://redis.io/commands
|
||||
|
||||
// KEYS get patten key array
|
||||
func (r *RedisClient) KEYS(patten string) ([]string, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return redis.Strings(conn.Do("KEYS", patten))
|
||||
}
|
||||
|
||||
// SCAN 获取大量key
|
||||
func (r *RedisClient) SCAN(patten string) ([]string, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
var out []string
|
||||
var cursor uint64 = 0xffffff
|
||||
isFirst := true
|
||||
for cursor != 0 {
|
||||
if isFirst {
|
||||
cursor = 0
|
||||
isFirst = false
|
||||
}
|
||||
arr, err := conn.Do("SCAN", cursor, "MATCH", patten, "COUNT", 100)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
switch arr := arr.(type) {
|
||||
case []interface{}:
|
||||
cursor, _ = redis.Uint64(arr[0], nil)
|
||||
it, _ := redis.Strings(arr[1], nil)
|
||||
out = append(out, it...)
|
||||
}
|
||||
}
|
||||
out = gotool.StrArrayUtils.ArrayDuplication(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DEL delete k-v
|
||||
func (r *RedisClient) DEL(key string) (int, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return redis.Int(conn.Do("DEL", key))
|
||||
}
|
||||
|
||||
// DELALL delete key array
|
||||
func (r *RedisClient) DELALL(key []string) (int, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
arr := make([]interface{}, len(key))
|
||||
for i, v := range key {
|
||||
arr[i] = v
|
||||
}
|
||||
return redis.Int(conn.Do("DEL", arr...))
|
||||
}
|
||||
|
||||
// GET get k-v
|
||||
func (r *RedisClient) GET(key string) (string, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return redis.String(conn.Do("GET", key))
|
||||
}
|
||||
|
||||
// SET set k-v
|
||||
func (r *RedisClient) SET(key string, value string) (int64, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return redis.Int64(conn.Do("SET", key, value))
|
||||
}
|
||||
|
||||
// SETEX set k-v expire seconds
|
||||
func (r *RedisClient) SETEX(key string, sec int, value string) (int64, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return redis.Int64(conn.Do("SETEX", key, sec, value))
|
||||
}
|
||||
|
||||
// EXPIRE set key expire seconds
|
||||
func (r *RedisClient) EXPIRE(key string, sec int64) (int64, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return redis.Int64(conn.Do("EXPIRE", key, sec))
|
||||
}
|
||||
|
||||
// HGETALL get map of key
|
||||
func (r *RedisClient) HGETALL(key string) (map[string]string, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return redis.StringMap(conn.Do("HGETALL", key))
|
||||
}
|
||||
|
||||
// HGET get value of key-field
|
||||
func (r *RedisClient) HGET(key string, field string) (string, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return redis.String(conn.Do("HGET", key, field))
|
||||
}
|
||||
|
||||
// HSET set value of key-field
|
||||
func (r *RedisClient) HSET(key string, field string, value string) (int64, error) {
|
||||
conn := r.pool.Get()
|
||||
defer conn.Close()
|
||||
return redis.Int64(conn.Do("HSET", key, field, value))
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package resp
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Response 数据返回结构体
|
||||
type Response struct {
|
||||
Status int `json:"status"` // 返回状态值
|
||||
Msg string `json:"msg"` // 返回的提示语
|
||||
Data interface{} `json:"data"` // 返回数据
|
||||
}
|
||||
|
||||
// Success 正确返回
|
||||
func Success(data interface{}, msg ...string) *Response {
|
||||
response := Response{
|
||||
Status: http.StatusOK,
|
||||
Data: data,
|
||||
Msg: "操作成功",
|
||||
}
|
||||
if len(msg) > 0 {
|
||||
response.Msg = msg[0]
|
||||
}
|
||||
return &response
|
||||
}
|
||||
|
||||
// ErrorResp 错误返回
|
||||
func ErrorResp(data ...interface{}) *Response {
|
||||
response := Response{
|
||||
Status: http.StatusInternalServerError,
|
||||
Msg: "操作失败",
|
||||
Data: nil,
|
||||
}
|
||||
for _, value := range data {
|
||||
switch value.(type) {
|
||||
case string:
|
||||
response.Msg = value.(string)
|
||||
case int:
|
||||
response.Status = value.(int)
|
||||
case interface{}:
|
||||
response.Data = value.(interface{})
|
||||
}
|
||||
}
|
||||
return &response
|
||||
}
|
||||
|
||||
func Error(c *gin.Context, data ...interface{}) {
|
||||
response := Response{
|
||||
Status: http.StatusInternalServerError,
|
||||
Msg: "操作失败",
|
||||
Data: nil,
|
||||
}
|
||||
for _, value := range data {
|
||||
switch value.(type) {
|
||||
case string:
|
||||
response.Msg = value.(string)
|
||||
case int:
|
||||
response.Status = value.(int)
|
||||
case interface{}:
|
||||
response.Data = value.(interface{})
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
return
|
||||
}
|
||||
func ParamError(c *gin.Context, data ...interface{}) {
|
||||
response := Response{
|
||||
Status: http.StatusInternalServerError,
|
||||
Msg: "参数绑定异常",
|
||||
Data: nil,
|
||||
}
|
||||
for _, value := range data {
|
||||
switch value.(type) {
|
||||
case string:
|
||||
response.Msg = value.(string)
|
||||
case int:
|
||||
response.Status = value.(int)
|
||||
case interface{}:
|
||||
response.Data = value.(interface{})
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusBadRequest, response)
|
||||
return
|
||||
}
|
||||
func OK(c *gin.Context, data ...interface{}) {
|
||||
response := Response{
|
||||
Status: http.StatusOK,
|
||||
Msg: "操作成功",
|
||||
Data: nil,
|
||||
}
|
||||
for _, datum := range data {
|
||||
switch datum.(type) {
|
||||
case string:
|
||||
response.Msg = datum.(string)
|
||||
case interface{}:
|
||||
response.Data = datum.(interface{})
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package tree_dept
|
||||
|
||||
import (
|
||||
"cutego/core/entity"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Tree 统一定义菜单树的数据结构, 也可以自定义添加其他字段
|
||||
type Tree struct {
|
||||
Id int `json:"id"`
|
||||
Data interface{} `json:"-"` // 自定义对象
|
||||
Label string `json:"label"`
|
||||
Leaf bool `json:"-"` // 叶子节点
|
||||
Selected bool `json:"-"` // 选中
|
||||
PartialSelected bool `json:"-"` // 部分选中
|
||||
Children []Tree `json:"children"` // 子节点
|
||||
}
|
||||
|
||||
// INode 其他的结构体想要生成菜单树, 直接实现这个接口
|
||||
type INode interface {
|
||||
GetId() int
|
||||
GetLabel() string
|
||||
GetParentId() int
|
||||
GetData() interface{}
|
||||
// IsRoot 判断当前节点是否是顶层根节点
|
||||
IsRoot() bool
|
||||
}
|
||||
type INodes []INode
|
||||
|
||||
func (nodes INodes) Len() int {
|
||||
return len(nodes)
|
||||
}
|
||||
func (nodes INodes) Swap(i, j int) {
|
||||
nodes[i], nodes[j] = nodes[j], nodes[i]
|
||||
}
|
||||
func (nodes INodes) Less(i, j int) bool {
|
||||
return nodes[i].GetId() < nodes[j].GetId()
|
||||
}
|
||||
|
||||
// GenerateTree 自定义的结构体实现 INode 接口后调用此方法生成树结构
|
||||
// nodes 需要生成树的节点
|
||||
// selectedNode 生成树后选中的节点
|
||||
// menuTrees 生成成功后的树结构对象
|
||||
func GenerateTree(nodes, selectedNodes []INode) (trees []Tree) {
|
||||
trees = []Tree{}
|
||||
// 定义顶层根和子节点
|
||||
var roots, childList []INode
|
||||
for _, v := range nodes {
|
||||
if v.IsRoot() {
|
||||
// 判断顶层根节点
|
||||
roots = append(roots, v)
|
||||
}
|
||||
childList = append(childList, v)
|
||||
}
|
||||
|
||||
for _, v := range roots {
|
||||
childTree := &Tree{
|
||||
Id: v.GetId(),
|
||||
Label: v.GetLabel(),
|
||||
Data: v.GetData(),
|
||||
}
|
||||
// 递归之前, 根据父节点确认 childTree 的选中状态
|
||||
childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children)
|
||||
// 递归
|
||||
recursiveTree(childTree, childList, selectedNodes)
|
||||
// 递归之后, 根据子节点确认 childTree 的选中状态
|
||||
if !childTree.Selected {
|
||||
childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children)
|
||||
}
|
||||
// 递归之后, 根据子节点确认 childTree 的半选中状态
|
||||
childTree.PartialSelected = nodePartialSelected(childTree.Children)
|
||||
// 递归之后, 根据子节确认是否是叶子节点
|
||||
childTree.Leaf = len(childTree.Children) == 0
|
||||
trees = append(trees, *childTree)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// recursiveTree 递归生成树结构
|
||||
// tree 递归的树对象
|
||||
// nodes 递归的节点
|
||||
// selectedNodes 选中的节点
|
||||
func recursiveTree(tree *Tree, nodes, selectedNodes []INode) {
|
||||
data := tree.Data.(INode)
|
||||
|
||||
for _, v := range nodes {
|
||||
if v.IsRoot() {
|
||||
// 如果当前节点是顶层根节点就跳过
|
||||
continue
|
||||
}
|
||||
if data.GetId() == v.GetParentId() {
|
||||
childTree := &Tree{
|
||||
Id: v.GetId(),
|
||||
Label: v.GetLabel(),
|
||||
Data: v.GetData(),
|
||||
}
|
||||
// 递归之前, 根据子节点和父节点确认 childTree 的选中状态
|
||||
childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children) || tree.Selected
|
||||
recursiveTree(childTree, nodes, selectedNodes)
|
||||
|
||||
if !childTree.Selected {
|
||||
// 递归之后, 根据子节点确认 childTree 的选中状态
|
||||
childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children)
|
||||
}
|
||||
// 递归之后, 根据子节点确认 childTree 的半选中状态
|
||||
childTree.PartialSelected = nodePartialSelected(childTree.Children)
|
||||
// 递归之后, 根据子节确认是否是叶子节点
|
||||
childTree.Leaf = len(childTree.Children) == 0
|
||||
tree.Children = append(tree.Children, *childTree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindRelationNode 在 allTree 中查询 nodes 中节点的所有父节点
|
||||
// nodes 要查询父节点的子节点数组
|
||||
// allTree 所有节点数组
|
||||
func FindRelationNode(nodes, allNodes []INode) (respNodes []INode) {
|
||||
nodeMap := make(map[int]INode)
|
||||
for _, v := range nodes {
|
||||
recursiveFindRelationNode(nodeMap, allNodes, v, 0)
|
||||
}
|
||||
|
||||
for _, v := range nodeMap {
|
||||
respNodes = append(respNodes, v)
|
||||
}
|
||||
sort.Sort(INodes(respNodes))
|
||||
return
|
||||
}
|
||||
|
||||
// recursiveFindRelationNode 递归查询关联父子节点
|
||||
// nodeMap 查询结果搜集到map中
|
||||
// allNodes 所有节点
|
||||
// node 递归节点
|
||||
// t 递归查找类型:0 查找父、子节点;1 只查找父节点;2 只查找子节点
|
||||
func recursiveFindRelationNode(nodeMap map[int]INode, allNodes []INode, node INode, t int) {
|
||||
nodeMap[node.GetId()] = node
|
||||
for _, v := range allNodes {
|
||||
if _, ok := nodeMap[v.GetId()]; ok {
|
||||
continue
|
||||
}
|
||||
// 查找父节点
|
||||
if t == 0 || t == 1 {
|
||||
if node.GetParentId() == v.GetId() {
|
||||
nodeMap[v.GetId()] = v
|
||||
if v.IsRoot() {
|
||||
// 是顶层根节点时, 不再进行递归
|
||||
continue
|
||||
}
|
||||
recursiveFindRelationNode(nodeMap, allNodes, v, 1)
|
||||
}
|
||||
}
|
||||
// 查找子节点
|
||||
if t == 0 || t == 2 {
|
||||
if node.GetId() == v.GetParentId() {
|
||||
nodeMap[v.GetId()] = v
|
||||
recursiveFindRelationNode(nodeMap, allNodes, v, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nodeSelected 判断节点的选中状态
|
||||
// node 进行判断节点
|
||||
func nodeSelected(node INode, selectedNodes []INode, children []Tree) bool {
|
||||
for _, v := range selectedNodes {
|
||||
if node.GetId() == v.GetId() {
|
||||
// 1. 如果选择节点数组中存在当前节点
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if len(children) == 0 {
|
||||
// 2. 不满足前置条件1, 且没有子节点
|
||||
return false
|
||||
}
|
||||
selectedNum := 0
|
||||
for _, v := range children {
|
||||
if v.Selected {
|
||||
selectedNum++
|
||||
}
|
||||
}
|
||||
if selectedNum == len(children) {
|
||||
// 不满足前置条件1, 2 , 且子节点全部是选中状态
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// nodePartialSelected 判断节点的半选中状态
|
||||
func nodePartialSelected(trees []Tree) bool {
|
||||
selectedNum := 0
|
||||
for _, v := range trees {
|
||||
if v.Selected {
|
||||
selectedNum++
|
||||
}
|
||||
}
|
||||
if selectedNum == len(trees) || selectedNum == 0 {
|
||||
// 子节点全选中, 或一个也没有选中
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type DeptList []entity.SysDept
|
||||
|
||||
// ConvertToINodeArray 将当前数组转换成父类 INode 接口 数组
|
||||
func (s DeptList) ConvertToINodeArray(*[]entity.SysDept) (nodes []INode) {
|
||||
for _, v := range s {
|
||||
nodes = append(nodes, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTree 获取树结构
|
||||
func (s DeptList) GetTree(treeSelect *[]entity.SysDept) []Tree {
|
||||
s = *treeSelect
|
||||
array := s.ConvertToINodeArray(treeSelect)
|
||||
return GenerateTree(array, nil)
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package tree_menu
|
||||
|
||||
import (
|
||||
"cutego/core/entity"
|
||||
"github.com/druidcaesa/gotool"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Tree 统一定义菜单树的数据结构, 也可以自定义添加其他字段
|
||||
type Tree struct {
|
||||
Name string `json:"name,omitempty"` // 节点名字
|
||||
Path string `json:"path,omitempty"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Redirect string `json:"redirect,omitempty"`
|
||||
Component string `json:"component,omitempty"`
|
||||
AlwaysShow bool `json:"alwaysShow"`
|
||||
Data interface{} `json:"-"` // 自定义对象
|
||||
Meta interface{} `json:"meta"`
|
||||
Leaf bool `json:"-"` // 叶子节点
|
||||
Selected bool `json:"-"` // 选中
|
||||
PartialSelected bool `json:"-"` // 部分选中
|
||||
Children []Tree `json:"children"` // 子节点
|
||||
Id int `json:"id"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
// INode 其他的结构体想要生成菜单树, 直接实现这个接口
|
||||
type INode interface {
|
||||
// GetName 获取显示名字
|
||||
GetName() string
|
||||
// GetMenuId 获取id
|
||||
GetMenuId() int
|
||||
// GetParentId 获取父id
|
||||
GetParentId() int
|
||||
// GetData 获取附加数据
|
||||
GetData() interface{}
|
||||
// IsRoot 判断当前节点是否是顶层根节点
|
||||
IsRoot() bool
|
||||
// GetPath 路径
|
||||
GetPath() string
|
||||
GetId() int
|
||||
GetLabel() string
|
||||
}
|
||||
type INodes []INode
|
||||
|
||||
func (nodes INodes) Len() int {
|
||||
return len(nodes)
|
||||
}
|
||||
func (nodes INodes) Swap(i, j int) {
|
||||
nodes[i], nodes[j] = nodes[j], nodes[i]
|
||||
}
|
||||
func (nodes INodes) Less(i, j int) bool {
|
||||
return nodes[i].GetMenuId() < nodes[j].GetMenuId()
|
||||
}
|
||||
|
||||
// GenerateTree 自定义的结构体实现 INode 接口后调用此方法生成树结构
|
||||
// nodes 需要生成树的节点
|
||||
// selectedNode 生成树后选中的节点
|
||||
// menuTrees 生成成功后的树结构对象
|
||||
func GenerateTree(nodes, selectedNodes []INode) (trees []Tree) {
|
||||
trees = []Tree{}
|
||||
// 定义顶层根和子节点
|
||||
var roots, childs []INode
|
||||
for _, v := range nodes {
|
||||
if v.IsRoot() {
|
||||
// 判断顶层根节点
|
||||
roots = append(roots, v)
|
||||
}
|
||||
childs = append(childs, v)
|
||||
}
|
||||
|
||||
for _, v := range roots {
|
||||
var flag = false
|
||||
var component = "Layout"
|
||||
m := make(map[string]interface{})
|
||||
if v.GetData() != nil {
|
||||
menu := v.GetData().(entity.SysMenu)
|
||||
m["title"] = menu.MenuName
|
||||
m["icon"] = menu.Icon
|
||||
m["noCache"] = menu.IsCache == 1
|
||||
if menu.IsFrame == 0 {
|
||||
m["link"] = menu.Path
|
||||
} else {
|
||||
m["link"] = nil
|
||||
}
|
||||
flag = menu.Visible == "1"
|
||||
if !gotool.StrUtils.HasEmpty(menu.Component) {
|
||||
component = menu.Component
|
||||
}
|
||||
}
|
||||
childTree := &Tree{
|
||||
Name: v.GetName(),
|
||||
Data: v.GetData(),
|
||||
Path: "/" + v.GetPath(),
|
||||
Hidden: flag,
|
||||
AlwaysShow: true,
|
||||
Redirect: "noRedirect",
|
||||
Meta: m,
|
||||
Component: component,
|
||||
Id: v.GetId(),
|
||||
Label: v.GetLabel(),
|
||||
}
|
||||
// 递归之前, 根据父节点确认 childTree 的选中状态
|
||||
childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children)
|
||||
// 递归
|
||||
recursiveTree(childTree, childs, selectedNodes)
|
||||
// 递归之后, 根据子节点确认 childTree 的选中状态
|
||||
if !childTree.Selected {
|
||||
childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children)
|
||||
}
|
||||
// 递归之后, 根据子节点确认 childTree 的半选中状态
|
||||
childTree.PartialSelected = nodePartialSelected(childTree.Children)
|
||||
// 递归之后, 根据子节确认是否是叶子节点
|
||||
childTree.Leaf = len(childTree.Children) == 0
|
||||
trees = append(trees, *childTree)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// recursiveTree 递归生成树结构
|
||||
// tree 递归的树对象
|
||||
// nodes 递归的节点
|
||||
// selectedNodes 选中的节点
|
||||
func recursiveTree(tree *Tree, nodes, selectedNodes []INode) {
|
||||
data := tree.Data.(INode)
|
||||
|
||||
for _, v := range nodes {
|
||||
if v.IsRoot() {
|
||||
// 如果当前节点是顶层根节点就跳过
|
||||
continue
|
||||
}
|
||||
var flag = false
|
||||
var component = "Layout"
|
||||
m := make(map[string]interface{})
|
||||
if v.GetData() != nil {
|
||||
menu := v.GetData().(entity.SysMenu)
|
||||
flag = menu.Visible == "1"
|
||||
m["title"] = menu.MenuName
|
||||
m["icon"] = menu.Icon
|
||||
m["noCache"] = menu.IsCache == 1
|
||||
if menu.IsFrame == 0 {
|
||||
m["link"] = menu.Path
|
||||
} else {
|
||||
m["link"] = nil
|
||||
}
|
||||
if !gotool.StrUtils.HasEmpty(menu.Component) {
|
||||
component = menu.Component
|
||||
}
|
||||
}
|
||||
if data.GetMenuId() == v.GetParentId() {
|
||||
childTree := &Tree{
|
||||
Name: v.GetName(),
|
||||
Data: v.GetData(),
|
||||
Path: v.GetPath(),
|
||||
Hidden: flag,
|
||||
Meta: m,
|
||||
Component: component,
|
||||
Id: v.GetId(),
|
||||
Label: v.GetLabel(),
|
||||
}
|
||||
// 递归之前, 根据子节点和父节点确认 childTree 的选中状态
|
||||
childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children) || tree.Selected
|
||||
recursiveTree(childTree, nodes, selectedNodes)
|
||||
|
||||
if !childTree.Selected {
|
||||
// 递归之后, 根据子节点确认 childTree 的选中状态
|
||||
childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children)
|
||||
}
|
||||
// 递归之后, 根据子节点确认 childTree 的半选中状态
|
||||
childTree.PartialSelected = nodePartialSelected(childTree.Children)
|
||||
// 递归之后, 根据子节确认是否是叶子节点
|
||||
childTree.Leaf = len(childTree.Children) == 0
|
||||
tree.Children = append(tree.Children, *childTree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindRelationNode 在 allTree 中查询 nodes 中节点的所有父节点
|
||||
// nodes 要查询父节点的子节点数组
|
||||
// allTree 所有节点数组
|
||||
func FindRelationNode(nodes, allNodes []INode) (respNodes []INode) {
|
||||
nodeMap := make(map[int]INode)
|
||||
for _, v := range nodes {
|
||||
recursiveFindRelationNode(nodeMap, allNodes, v, 0)
|
||||
}
|
||||
|
||||
for _, v := range nodeMap {
|
||||
respNodes = append(respNodes, v)
|
||||
}
|
||||
sort.Sort(INodes(respNodes))
|
||||
return
|
||||
}
|
||||
|
||||
// recursiveFindRelationNode 递归查询关联父子节点
|
||||
// nodeMap 查询结果搜集到map中
|
||||
// allNodes 所有节点
|
||||
// node 递归节点
|
||||
// t 递归查找类型:0 查找父、子节点;1 只查找父节点;2 只查找子节点
|
||||
func recursiveFindRelationNode(nodeMap map[int]INode, allNodes []INode, node INode, t int) {
|
||||
nodeMap[node.GetMenuId()] = node
|
||||
for _, v := range allNodes {
|
||||
if _, ok := nodeMap[v.GetMenuId()]; ok {
|
||||
continue
|
||||
}
|
||||
// 查找父节点
|
||||
if t == 0 || t == 1 {
|
||||
if node.GetParentId() == v.GetMenuId() {
|
||||
nodeMap[v.GetMenuId()] = v
|
||||
if v.IsRoot() {
|
||||
// 是顶层根节点时, 不再进行递归
|
||||
continue
|
||||
}
|
||||
recursiveFindRelationNode(nodeMap, allNodes, v, 1)
|
||||
}
|
||||
}
|
||||
// 查找子节点
|
||||
if t == 0 || t == 2 {
|
||||
if node.GetMenuId() == v.GetParentId() {
|
||||
nodeMap[v.GetMenuId()] = v
|
||||
recursiveFindRelationNode(nodeMap, allNodes, v, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nodeSelected 判断节点的选中状态
|
||||
// node 进行判断节点
|
||||
func nodeSelected(node INode, selectedNodes []INode, children []Tree) bool {
|
||||
for _, v := range selectedNodes {
|
||||
if node.GetMenuId() == v.GetMenuId() {
|
||||
// 1. 如果选择节点数组中存在当前节点
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if len(children) == 0 {
|
||||
// 2. 不满足前置条件1, 且没有子节点
|
||||
return false
|
||||
}
|
||||
selectedNum := 0
|
||||
for _, v := range children {
|
||||
if v.Selected {
|
||||
selectedNum++
|
||||
}
|
||||
}
|
||||
if selectedNum == len(children) {
|
||||
// 不满足前置条件1, 2 , 且子节点全部是选中状态
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// nodePartialSelected 判断节点的半选中状态
|
||||
func nodePartialSelected(trees []Tree) bool {
|
||||
selectedNum := 0
|
||||
for _, v := range trees {
|
||||
if v.Selected {
|
||||
selectedNum++
|
||||
}
|
||||
}
|
||||
if selectedNum == len(trees) || selectedNum == 0 {
|
||||
// 子节点全选中, 或一个也没有选中
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type SystemMenus []entity.SysMenu
|
||||
|
||||
// ConvertToINodeArray 将当前数组转换成父类 INode 接口 数组
|
||||
func (s SystemMenus) ConvertToINodeArray(*[]entity.SysMenu) (nodes []INode) {
|
||||
for _, v := range s {
|
||||
nodes = append(nodes, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetTree 获取树结构数据
|
||||
func (s SystemMenus) GetTree(m *[]entity.SysMenu) []Tree {
|
||||
s = *m
|
||||
array := s.ConvertToINodeArray(m)
|
||||
return GenerateTree(array, nil)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"cutego/core/api/v1/response"
|
||||
"cutego/core/dao"
|
||||
"cutego/core/entity"
|
||||
"cutego/pkg/common"
|
||||
"cutego/pkg/config"
|
||||
"cutego/pkg/jwt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UserUtils struct {
|
||||
}
|
||||
|
||||
// GetUserInfo 通过jwt获取当前登录用户
|
||||
func GetUserInfo(c *gin.Context) *response.UserResponse {
|
||||
token := c.Request.Header.Get("Authorization")
|
||||
s := strings.Split(token, " ")
|
||||
// parseToken 解析token包含的信息
|
||||
claims, err := jwt.ParseToken(s[1])
|
||||
if err != nil {
|
||||
common.ErrorLog(err)
|
||||
}
|
||||
info := claims.UserInfo
|
||||
return &info
|
||||
}
|
||||
|
||||
// CheckLockToken 校验多终端登录锁
|
||||
func CheckLockToken(c *gin.Context) bool {
|
||||
if config.AppEnvConfig.Login.Single {
|
||||
// 获取redis中的token数据
|
||||
info := GetUserInfo(c)
|
||||
get, err := dao.RedisDB.GET(info.UserName)
|
||||
if err != nil {
|
||||
common.ErrorLog(err)
|
||||
return false
|
||||
}
|
||||
token := c.Request.Header.Get(config.AppEnvConfig.Jwt.Header)
|
||||
s := strings.Split(token, " ")
|
||||
if get == s[1] {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// CheckIsAdmin 判断是否是超级管理员
|
||||
func CheckIsAdmin(user *entity.SysUser) bool {
|
||||
if user.UserId == 1 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"cutego/pkg/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 连接例子
|
||||
// <script>
|
||||
// var ws = new WebSocket("ws://127.0.0.1:21366/websocket?user=admin&code=notice");
|
||||
// // 连接打开时触发
|
||||
// ws.onopen = function(evt) {
|
||||
// console.log("Connection open ...");
|
||||
// ws.send("Hello WebSockets!");
|
||||
// };
|
||||
// // 接收到消息时触发
|
||||
// ws.onmessage = function(evt) {
|
||||
// console.log("Received Message: " + evt.data);
|
||||
// };
|
||||
// // 连接关闭时触发
|
||||
// ws.onclose = function(evt) {
|
||||
// console.log("Connection closed.");
|
||||
// };
|
||||
// </script>
|
||||
|
||||
// 定义回调接口(消息类型、内容)
|
||||
type OnReceiveMessage func(messageType int, content []byte) error
|
||||
|
||||
// 分割符号
|
||||
const SignalSplitSymbol = "=_="
|
||||
|
||||
// 用户名 <--> websocket通道
|
||||
var OnlineUserMap = make(map[string]*websocket.Conn)
|
||||
|
||||
var upGrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// webSocket请求ping 返回pong
|
||||
func HandleWebSocketMessage(c *gin.Context) {
|
||||
userValue, userExist := c.GetQuery("user")
|
||||
codeValue, codeExist := c.GetQuery("code")
|
||||
|
||||
if !userExist && !codeExist {
|
||||
return
|
||||
}
|
||||
|
||||
// 升级get请求为webSocket协议
|
||||
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// defer ws.Close()
|
||||
cacheKey := userValue + SignalSplitSymbol + codeValue
|
||||
// 如果存在则踢掉之前的通道
|
||||
if OnlineUserMap[cacheKey] != nil {
|
||||
// 函数结束后关闭
|
||||
tmpConn := OnlineUserMap[cacheKey]
|
||||
defer tmpConn.Close()
|
||||
}
|
||||
OnlineUserMap[cacheKey] = ws
|
||||
// 回收监听消息
|
||||
go ListenWebSocketMessage(userValue, codeValue, HandleAdminNotice)
|
||||
}
|
||||
|
||||
// 回调函数的具体实现
|
||||
func HandleAdminNotice(messageType int, content []byte) error {
|
||||
common.InfoLog("messageType=%d\n", messageType)
|
||||
common.InfoLog("content=%s\n", string(content))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 监听到消息
|
||||
// onReceiveMessage为函数提供的回调接口, 让外部去实现
|
||||
func ListenWebSocketMessage(user string, code string, onReceiveMessage OnReceiveMessage) {
|
||||
cacheKey := user + SignalSplitSymbol + code
|
||||
ws := OnlineUserMap[cacheKey]
|
||||
if ws != nil {
|
||||
for {
|
||||
// 读取ws中的数据
|
||||
mt, message, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// 处理心跳
|
||||
if string(message) == "ping" {
|
||||
message = []byte("pong")
|
||||
// 写入ws数据
|
||||
err = ws.WriteMessage(mt, message)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
// 处理接收到的数据
|
||||
err = onReceiveMessage(mt, message)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user