This commit is contained in:
Sun
2023-11-08 21:53:07 +08:00
commit 211c3071dc
245 changed files with 39293 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
package cache
import (
"time"
)
// 缓存接口-支持Redis和内存使用
type Cacher[T any] interface {
// 设置
Set(k string, v T, d time.Duration)
// 取值
Get(k string) (T, bool)
// 设置-过期时间采用默认值
SetDefault(k string, v T)
// 删除
Delete(k string)
// 只有在给定Key项尚未存在,或者现有项已过期时,才能将项添加到缓存中。否则返回错误。
// Add(k string, v T, d time.Duration)
// IncrementInt(k string, n int) (num int, err error)
// 设置值,但不重置过期时间
SetKeepExpiration(k string, v T)
// 项目总数
ItemCount() (int64, error)
// 清空
Flush()
}
+96
View File
@@ -0,0 +1,96 @@
package cache
import (
"time"
"github.com/patrickmn/go-cache"
)
// 参考:https://blog.csdn.net/u014459543/article/details/108429469
type GoCacheStruct[T any] struct {
gocahce *cache.Cache
Result T
}
type GoCacheValue[T any] struct {
Value T
}
// 创建一个goCache结构体
// cache.New(5*time.Minute, 60*time.Second),清理过期的item间隔 0.不清理
func NewGoCache[T any](defaultExpiration time.Duration, cleanupInterval time.Duration) *GoCacheStruct[T] {
cacheAdapter := cache.New(defaultExpiration, cleanupInterval)
return &GoCacheStruct[T]{
gocahce: cacheAdapter,
}
}
func (c *GoCacheStruct[T]) Set(k string, x T, d time.Duration) {
c.gocahce.Set(k, GoCacheValue[T]{Value: x}, d)
}
func (c *GoCacheStruct[T]) Get(k string) (T, bool) {
if v, ok := c.gocahce.Get(k); ok {
if value, okv := v.(GoCacheValue[T]); okv {
return value.Value, true
}
}
return c.Result, false
}
// 设置cache 无时间参数
func (c *GoCacheStruct[T]) SetDefault(k string, v T) {
c.gocahce.SetDefault(k, GoCacheValue[T]{Value: v})
}
// 设置并保持原始的过期时间
func (c *GoCacheStruct[T]) SetKeepExpiration(k string, v T) {
_, expirationTime, ok := c.gocahce.GetWithExpiration(k)
now := time.Now()
differ := expirationTime.Sub(now)
// 如果 过期值不为零值 && 未过期 && 过期时间大于现在的时间
// 将保持不变原始的过期时间来计算时间
if !expirationTime.IsZero() && ok && differ > 0 {
// newExpiration := now.Unix() + int64(math.Round(differ.Seconds()))
// fmt.Println("旧的过期时间", expirationTime.Unix())
// fmt.Println("时间限制差", math.Round(differ.Seconds()))
// fmt.Println("新的过期时间", newExpiration)
c.gocahce.Set(k, GoCacheValue[T]{Value: v}, differ)
} else {
c.gocahce.SetDefault(k, GoCacheValue[T]{Value: v})
}
}
// 删除 cache
func (c *GoCacheStruct[T]) Delete(k string) {
c.gocahce.Delete(k)
}
// Add() 加入缓存
func (c *GoCacheStruct[T]) Add(k string, v T, d time.Duration) {
c.gocahce.Add(k, GoCacheValue[T]{Value: v}, d)
}
// IncrementInt() 对已存在的key 值自增n
func (c *GoCacheStruct[T]) IncrementInt(k string, n int) (num int, err error) {
return c.gocahce.IncrementInt(k, n)
}
// ItemCount 获取已存在key的数量
func (c *GoCacheStruct[T]) ItemCount() (int64, error) {
return int64(c.gocahce.ItemCount()), nil
}
// Flush 删除当前已存在的所有key
func (c *GoCacheStruct[T]) Flush() {
c.gocahce.Flush()
}
// func (c *GoCacheStruct[T]) encode(value T) ([]byte, error) {
// return json.Marshal(value)
// }
// func (c *GoCacheStruct[T]) decode(valueByte []byte, value T) error {
// return json.Unmarshal(valueByte, value)
// }
+195
View File
@@ -0,0 +1,195 @@
package cache
import (
"context"
"encoding/json"
"time"
redis "github.com/redis/go-redis/v9"
)
type RedisCacheStruct[T any] struct {
Redis *redis.Client
Ctx context.Context
HashKey string
Result T
DefaultExpiration time.Duration
CleanupInterval time.Duration
}
type RedisValue[T any] struct {
ExpirationTimeStamp int64
IsExpiration bool // 是否有过期时间 false // 不过期
Value T
}
// cache.New(5*time.Minute, 60*time.Second)
func NewRedisCache[T any](redisDb *redis.Client, hashKey string, defaultExpiration time.Duration, cleanupInterval time.Duration) *RedisCacheStruct[T] {
obj := RedisCacheStruct[T]{
Redis: redisDb,
Ctx: context.Background(),
HashKey: hashKey,
DefaultExpiration: defaultExpiration,
CleanupInterval: cleanupInterval,
}
// 创建定时器判断是否过期
if obj.CleanupInterval.Seconds() > 0 {
go obj.expirationVerification()
}
return &obj
}
func (r *RedisCacheStruct[T]) Set(k string, v T, d time.Duration) {
valueEncode := ""
value := RedisValue[T]{}
// 设置过期时间
if d.Seconds() > 0 {
value.IsExpiration = true
value.ExpirationTimeStamp = time.Now().Add(d).Unix()
} else {
value.IsExpiration = false // 不过期
}
value.Value = v
if j, e := json.Marshal(value); e == nil {
valueEncode = string(j)
}
r.Redis.HSet(r.Ctx, r.HashKey, k, valueEncode)
// second := d.Seconds()
// if second > 0 {
// // 设置过期时间
// err := r.Redis.Do(r.Ctx, "SETEX", r.HashKey+k, second, valueEncode).Err()
// fmt.Println("设置结果", err)
// } else {
// r.Redis.HSet(r.Ctx, r.HashKey, k, valueEncode)
// }
}
func (r *RedisCacheStruct[T]) Get(k string) (T, bool) {
var valueEncode []byte
value := RedisValue[T]{}
cmd := r.Redis.HGet(r.Ctx, r.HashKey, k)
if err := cmd.Scan(&valueEncode); err != nil {
// log.Println(err)
return r.Result, false
}
if err := json.Unmarshal(valueEncode, &value); err != nil {
// log.Println(err)
return r.Result, false
}
// 已过期,清理掉key
if value.IsExpiration && time.Now().Unix() > value.ExpirationTimeStamp {
r.Delete(k)
return r.Result, false
}
return value.Value, true
}
// 设置cache 无时间参数
func (r *RedisCacheStruct[T]) SetDefault(k string, v T) {
r.Set(k, v, r.DefaultExpiration)
}
// 设置并保持原始的过期时间
func (r *RedisCacheStruct[T]) SetKeepExpiration(k string, v T) {
var valueEncode []byte
value := RedisValue[T]{}
cmd := r.Redis.HGet(r.Ctx, r.HashKey, k)
if err := cmd.Scan(&valueEncode); err != nil {
// fmt.Println("使用默认的过期时间")
r.SetDefault(k, v)
return
}
if err := json.Unmarshal(valueEncode, &value); err != nil {
// fmt.Println("使用默认的过期时间")
r.SetDefault(k, v)
return
}
now := time.Now()
timeDiffer := value.ExpirationTimeStamp - now.Unix()
// 如果设置了过期时间并且过期时间大于现在将保留原始的过期时间
if value.IsExpiration && timeDiffer > 0 {
// fmt.Println("重新计算过期时间")
// fmt.Println("旧的过期时间", value.ExpirationTimeStamp)
// fmt.Println("时间限制差", timeDiffer)
// fmt.Println("新的过期时间", now.Unix()+timeDiffer)
r.Set(k, v, time.Second*time.Duration(timeDiffer))
} else {
// fmt.Println("使用默认的过期时间")
r.SetDefault(k, v)
}
}
// 删除 cache
func (r *RedisCacheStruct[T]) Delete(k string) {
r.Redis.HDel(r.Ctx, r.HashKey, k)
}
// Add() 加入缓存
// func (r *RedisCacheStruct[T]) Add(k string, v T, d time.Duration) {
// c.gocahce.Add(k, x, d)
// }
// IncrementInt() 对已存在的key 值自增n
// func (r *RedisCacheStruct[T]) IncrementInt(k string, n int) (num int, err error) {
// if err := r.Redis.HIncrBy(r.Ctx, r.HashKey, k, int64(n)).Err(); err != nil {
// return num, err
// }
// if v, ok := r.Get(k); ok {
// switch T {
// case int:
// }
// if vint, okint := v.(int); okint {
// }
// }
// return c.gocahce.IncrementInt(k, n)
// }
// ItemCount 获取已存在key的数量
func (r *RedisCacheStruct[T]) ItemCount() (int64, error) {
if count, err := r.Redis.HLen(r.Ctx, r.HashKey).Result(); err != nil {
return 0, err
} else {
return count, nil
}
}
// Flush 删除当前已存在的所有key
func (r *RedisCacheStruct[T]) Flush() {
r.Redis.Del(r.Ctx, r.HashKey)
}
// 定时清理过期验证
func (r *RedisCacheStruct[T]) expirationVerification() {
ticker := time.NewTicker(r.CleanupInterval)
for {
select {
case <-ticker.C:
if fields, err := r.Redis.HKeys(r.Ctx, r.HashKey).Result(); err == nil {
for _, v := range fields {
// r.Redis.HGet(r.Ctx, r.HashKey, v)
r.Get(v)
// fmt.Println("redis定时器", v)
}
}
// case <-j.stop:
// ticker.Stop()
// return
}
}
}
+52
View File
@@ -0,0 +1,52 @@
package captcha
import (
"sun-panel/global"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
)
var Store = base64Captcha.DefaultMemStore
func NewDriver(width, height int) *base64Captcha.DriverString {
driver := new(base64Captcha.DriverString)
driver.Height = height
driver.Width = width
driver.NoiseCount = 0
driver.ShowLineOptions = base64Captcha.OptionShowSlimeLine | base64Captcha.OptionShowHollowLine
driver.Length = 4
driver.Source = "1234567890qwertyuipkjhgfdsazxcvbnm"
driver.Fonts = []string{"wqy-microhei.ttc"}
return driver
}
// 生成图形验证码
func GenerateCaptchaHandler(id string, width, height int) string {
var driver = NewDriver(width, height).ConvertFonts()
c := base64Captcha.NewCaptcha(driver, Store)
_, content, answer := c.Driver.GenerateIdQuestionAnswer()
item, _ := c.Driver.DrawCaptcha(content)
c.Store.Set(id, answer)
return item.EncodeB64string()
}
// 验证
func CaptchaVerifyHandle(id, vcode string) bool {
return Store.Verify(id, vcode, true)
}
// 根据key获取验证码ID
func CaptchaGetIdByCookieHeader(c *gin.Context, key string) (captchaId string, err error) {
captchaId, err = c.Cookie("CaptchaId")
if err != nil {
global.Logger.Errorf("failed to get captchaId from cookie, err:%+v\n", err)
return captchaId, err
}
if captchaId == "" {
captchaId = c.GetHeader(key)
}
return
}
+207
View File
@@ -0,0 +1,207 @@
package cmn
import (
// "calendar-note-gin/assets"
"crypto/md5"
"encoding/hex"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path"
"strconv"
"strings"
"sun-panel/assets"
"time"
)
const (
// 时间格式
TimeFormatMode1 = "2006-01-02 15:04:05" // 标准格式
TimeFormatMode4 = "2006-01-02 15:04" // 标准格式 无秒
TimeFormatMode2 = "Mon Jan 2 15:04:05 -0700 MST 2006"
TimeFormatMode3 = "Mon, 2 Jan 2006 15:04:05 -0700 MST" // webdav格式
TimeYYYY_mm_dd = "2006-01-02"
TIME_MODE_REMINDER_TIME = "200601021504" // 提醒定时器的执行时间格式
// 随机码字典
RAND_CODE_MODE1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" // 大写,小写,数字
RAND_CODE_MODE2 = "abcdefghijklmnopqrstuvwxyz0123456789" // 小写,数字
RAND_CODE_MODE3 = "0123456789" // 数字
)
type Version_Info struct {
Version string
Version_code int
}
func GetTime() string {
return time.Unix(time.Now().Unix(), 0).Format(TimeFormatMode1)
}
// 字符串转时间
func StrToTime(timeMode, formatTimeStr string) (t time.Time, err error) {
loc, err := time.LoadLocation("Local")
if err != nil {
return
}
t, err = time.ParseInLocation(timeMode, formatTimeStr, loc) //使用模板在对应时区转化为time.time类型
return
}
// md5获取
func Md5(str string) string {
md5Byte := md5.Sum([]byte(str))
return hex.EncodeToString(md5Byte[:])
}
func RandNum(n int) int {
rand.Seed(time.Now().Unix())
return rand.Intn(n)
}
// 随机生成编码
// 随机码字典内容 参考常量 RAND_CODE_MODE*
func BuildRandCode(count int, secret_content string) (code string) {
return BuildRandCodeBySeed(count, secret_content, time.Now().UnixNano()+int64(rand.Intn(100)))
}
// 随机生成编码 参考常量 RAND_CODE_MODE*
func BuildRandCodeBySeed(count int, secret_content string, seed int64) (code string) {
// 获取纳秒作为随机数种子
rand.Seed(seed)
if secret_content == "" {
secret_content = RAND_CODE_MODE1
}
for i := 0; i < count; i++ {
code += string(secret_content[rand.Intn(len(secret_content))])
}
return code
}
func InSlice(items []string, item string) bool {
for _, eachItem := range items {
if eachItem == item {
return true
}
}
return false
}
// 字符串转int
func StrToInt(str string) int {
intStr, _ := strconv.Atoi(str)
return intStr
}
// uint 转string
func UintToStr(c uint) string {
return strconv.FormatUint(uint64(c), 10)
}
// uint 转string
func StrToUint(s string) uint {
// i, _ := strconv.Atoi(s)
u, _ := strconv.ParseUint(s, 10, 64)
return uint(u)
}
// 获取系统信息
func GetSysVersionInfo() Version_Info {
cBytes, _ := assets.Asset("assets/version")
c := string(cBytes)
info := strings.Split(c, "|")
return Version_Info{
Version_code: StrToInt(info[0]),
Version: info[1],
}
}
// 文件是否存在
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
}
// 截取字符串,支持多字节字符
// start:起始下标,负数从从尾部开始,最后一个为-1
// length:截取长度,负数表示截取到末尾
func SubRuneStr(str string, start int, length int) (result string) {
s := []rune(str)
total := len(s)
if total == 0 {
return
}
// 允许从尾部开始计算
if start < 0 {
start = total + start
if start < 0 {
return
}
}
if start > total {
return
}
// 到末尾
if length < 0 {
length = total
}
end := start + length
if end > total {
result = string(s[start:])
} else {
result = string(s[start:end])
}
return
}
// 字符串长度
func RuneStrLen(str string) int {
return len([]rune(str))
}
// 是否在数组中
func InStringArray(arr []string, item string) bool {
for _, v := range arr {
if v == item {
return true
}
}
return true
}
// 从Assets文件夹中抽取文件保存到路劲
// AssetsTakeFileToPath("config.ini", targetPath string)
func AssetsTakeFileToPath(assetsPath, targetPath string) error {
bytes, _ := assets.Asset("assets/" + assetsPath)
targetPathPath := path.Dir(targetPath)
exists, err := PathExists(targetPathPath)
if err != nil {
return err
}
if !exists {
if err := os.MkdirAll(targetPathPath, 0777); err != nil {
fmt.Println(456)
return err
}
}
return ioutil.WriteFile(targetPath, bytes, 0666)
}
// 密码加密
func PasswordEncryption(password string) string {
return Md5(Md5(Md5(password)))
}
+241
View File
@@ -0,0 +1,241 @@
package cmn
import (
"fmt"
"io"
"log"
"os"
"path"
"time"
"github.com/fatih/color"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type LogStruct struct {
Writer io.Writer
File *os.File
Print_cfg bool // 此条打印到控制台
Separator string
}
type LogFileld map[string]string
var (
LOG_DEBUG = "Debug"
LOG_ERROR = "Error"
LOG_Info = "Info"
LOG_WARNING = "Warning"
)
// 日志颜色
var colors = map[string]func(a ...interface{}) string{
"Warning": color.New(color.FgYellow).Add(color.Bold).SprintFunc(),
"Panic": color.New(color.BgRed).Add(color.Bold).SprintFunc(),
"Error": color.New(color.FgRed).Add(color.Bold).SprintFunc(),
"Info": color.New(color.FgCyan).Add(color.Bold).SprintFunc(),
"Debug": color.New(color.FgWhite).Add(color.Bold).SprintFunc(),
}
// 不同级别前缀与时间的间隔,保持宽度一致
var spaces = map[string]string{
"Warning": "",
"Panic": " ",
"Error": " ",
"Info": " ",
"Debug": " ",
}
// 运行日志静态类
var runLogStatic = LogStruct{}
// 控制台打印
// Warning Panic Error Info Debug
func Pln(prefix string, msg string) {
fmt.Printf(
"%s%s %s %s\n",
colors[prefix]("["+prefix+"]"),
spaces[prefix],
time.Now().Format(TimeFormatMode1),
msg,
)
}
// 控制台打印,支持颜色
func Print(color, key, msg string) {
fmt.Printf(
"%s%s %s\n",
colors[color](key),
time.Now().Format(TimeFormatMode1),
msg,
)
}
// func Debug(a ...interface{}) {
// fmt.Print("[Debug] ")
// fmt.Println(a...)
// }
// // 错误并退出
// func ErrorExit(err_title, err_msg string) {
// newLog := NewLog("err.log")
// Pln("Error", err_title+err_msg)
// newLog.Error(err_title, err_msg)
// os.Exit(1)
// }
// 写入日志的文件
func NewLog(log_file_name string) *LogStruct {
logStruct := &LogStruct{}
logStruct.Separator = ""
logDir := path.Dir(log_file_name)
ok, _ := PathExists(logDir)
if !ok {
if err := os.MkdirAll(logDir, 0666); err != nil {
fmt.Println("创建日志文件错误", err.Error())
}
}
_, err := os.Stat(log_file_name)
if err != nil {
f, _ := os.Create(log_file_name)
logStruct.File = f
logStruct.Writer = io.MultiWriter(f)
} else {
f, _ := os.OpenFile(log_file_name, os.O_APPEND|os.O_WRONLY, 0666)
logStruct.File = f
logStruct.Writer = io.MultiWriter(f)
}
return logStruct
}
// 运行日志直接静态
func RunLog() *LogStruct {
// 按小时/日/月/年
// 先判断文件(夹)是否存在。否多级创建
log_file := "res/runtime/log/"
ok, _ := PathExists(log_file)
if !ok {
os.MkdirAll(log_file, 0777)
}
log_file_name := log_file + time.Unix(time.Now().Unix(), 1).Format("2006-01-02") + ".log"
_, err := os.Stat(log_file_name)
runLogStatic.Separator = "|"
if err != nil {
f, _ := os.Create(log_file_name)
runLogStatic.File = f
runLogStatic.Writer = io.MultiWriter(f)
} else {
if runLogStatic.File == nil {
f, _ := os.OpenFile(log_file_name, os.O_APPEND|os.O_WRONLY, 0666)
runLogStatic.File = f
runLogStatic.Writer = io.MultiWriter(f)
}
}
return &runLogStatic
}
func (t *LogStruct) Write(content string) (n int, err error) {
return io.WriteString(t.Writer, content)
}
func (t *LogStruct) Format(log_type string, content string) (n int, err error) {
content = log_type + spaces[log_type] + " " + GetTime() + " " + content + "\n"
return t.Write(content)
}
func (t *LogStruct) Info(content ...string) (n int, err error) {
str := ""
for i := 0; i < len(content); i++ {
if i != 0 {
str += t.Separator + content[i]
} else {
str += content[i]
}
}
n, err = t.Format("Info", str)
if t.Print_cfg == true {
Pln("Info", str)
t.Print_cfg = false
}
return
}
func (t *LogStruct) Debug(content string) {
t.Format("Debug", content)
if t.Print_cfg == true {
Pln("Debug", content)
t.Print_cfg = false
}
}
func (t *LogStruct) Error(content ...string) {
content_str := ""
for i := 0; i < len(content); i++ {
if i != 0 {
content_str += t.Separator + content[i]
} else {
content_str += content[i]
}
}
t.Format("Error", content_str)
if t.Print_cfg == true {
Pln("Error", content_str)
t.Print_cfg = false
}
}
// // 打印错误
// func (t *LogStruct) ErrorPrint(key, value string) {
// t.Print_cfg = true
// t.Error(key, value)
// }
// // 打印Debug
// func (t *LogStruct) DebugPrint(key, value string) {
// t.Print_cfg = true
// content := key + " " + value
// t.Debug(content)
// }
// func (t *LogStruct) Print() *LogStruct {
// t.Print_cfg = true
// return t
// }
// func (t *LogStruct) FormatFileld(field LogFileld) string {
// str := ""
// for k, v := range field {
// str += k + ":\"" + v + "\"" + t.Separator
// }
// if len(str) != 0 {
// str = str[0 : len(str)-1]
// }
// return str
// }
// TODO(GgoCoder) 日志轮转
func InitLogger(fileName string, level zapcore.LevelEnabler) *zap.SugaredLogger {
fileWriteSyncer := getLogWriter(fileName)
encoder := getEncoder()
core := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(fileWriteSyncer, zapcore.AddSync(os.Stdout)), level)
logger := zap.New(core, zap.AddCaller())
return logger.Sugar()
}
func getEncoder() zapcore.Encoder {
logConf := zap.NewProductionEncoderConfig()
logConf.EncodeTime = zapcore.ISO8601TimeEncoder
logConf.EncodeLevel = zapcore.CapitalLevelEncoder
return zapcore.NewConsoleEncoder(logConf)
}
func getLogWriter(fileName string) zapcore.WriteSyncer {
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
if err != nil {
log.Panic("failed to create log file", fileName)
}
return zapcore.AddSync(file)
}
@@ -0,0 +1,99 @@
package systemSetting
import (
"encoding/json"
"errors"
"sun-panel/lib/cache"
"sun-panel/models"
"gorm.io/gorm"
)
const (
SYSTEM_APPLICATION = "system_application"
SYSTEM_EMAIL = "system_email"
DISCLAIMER = "disclaimer" // 免责声明 储存类型:字符串
WEB_ABOUT_DESCRIPTION = "web_about_description" // 关于的描述信息
)
type SystemSettingCache struct {
Cache cache.Cacher[interface{}]
}
type Email struct {
Host string `json:"host" binding:"required"`
Port int `json:"port" binding:"required"`
Mail string `json:"mail" binding:"required,email"`
Password string `json:"password" binding:"required"`
}
type Register struct {
EmailSuffix string `json:"emailSuffix"` // 注册邮箱后缀
OpenRegister bool `json:"openRegister"` // 开放注册
}
type Login struct {
LoginCaptcha bool `json:"loginCaptcha"` // 登录验证码
}
type ApplicationSetting struct {
Register
Login
WebSiteUrl string `json:"webSiteUrl"` // 站点地址
}
var (
ErrorNoExists = errors.New("no exists")
)
// 系统配置启用缓存功能
func (s *SystemSettingCache) GetValueString(configName string) (result string, err error) {
if v, ok := s.Cache.Get(configName); ok {
if v1, ok1 := v.(string); ok1 {
// fmt.Println("读取缓存")
return v1, nil
}
}
mSetting := models.SystemSetting{}
result, err = mSetting.Get(configName)
if err == gorm.ErrRecordNotFound {
err = ErrorNoExists
}
// 查询出来,缓存起来
s.Cache.SetDefault(configName, result)
return
}
// value 需为指针
func (s *SystemSettingCache) GetValueByInterface(configName string, value interface{}) error {
if v, ok := s.Cache.Get(configName); ok {
// fmt.Println("缓存")
if s, sok := v.(string); sok {
if err := json.Unmarshal([]byte(s), value); err != nil {
return err
}
return nil
}
}
mSetting := models.SystemSetting{}
result, err := mSetting.Get(configName)
if err == gorm.ErrRecordNotFound {
err = ErrorNoExists
return err
}
err = json.Unmarshal([]byte(result), value)
if err != nil {
return err
}
s.Cache.SetDefault(configName, result)
return nil
}
func (s *SystemSettingCache) Set(configName string, configValue interface{}) error {
s.Cache.Delete(configName)
mSetting := models.SystemSetting{}
err := mSetting.Set(configName, configValue)
return err
}
+20
View File
@@ -0,0 +1,20 @@
package cmn
import "regexp"
// 公式
const (
VERIFY_EXP_USERNAME = `^[a-zA-Z0-9_\.\@]{5,80}$`
VERIFY_EXP_PASSWORD = `^[a-zA-Z0-9_\.\&\@]{6,16}$`
)
// 正则验证
func VerifyFormat(exp, str string) bool {
reg := regexp.MustCompile(exp)
return reg.MatchString(str)
}
// 验证邮箱
func VerifyEmail(email string) bool {
return VerifyFormat(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`, email)
}
+92
View File
@@ -0,0 +1,92 @@
package computerInfo
import (
"log"
"time"
"github.com/shirou/gopsutil/mem"
"github.com/shirou/gopsutil/v3/cpu"
"gitlab.com/tingshuo/go-diskstate/diskstate"
)
type Storage struct {
Name string
FileSystem string
Total uint64
Free uint64
}
type storageInfo struct {
Name string
Size uint64
FreeSpace uint64
FileSystem string
Used uint64
}
// func GetStorageInfo() {
// var storageinfo []storageInfo
// var loaclStorages []Storage
// err := wmi.Query("Select * from Win32_LogicalDisk", &storageinfo)
// if err != nil {
// return
// }
// for _, storage := range storageinfo {
// info := Storage{
// Name: storage.Name,
// FileSystem: storage.FileSystem,
// Total: storage.Size / 1024 / 1024 / 1024,
// Free: storage.FreeSpace / 1024 / 1024 / 1024,
// }
// if info.Total >= 1 {
// fmt.Printf("%s总大小%dG,可用%dG\n", info.Name, info.Total, info.Free)
// loaclStorages = append(loaclStorages, info)
// }
// }
// fmt.Printf("localStorages:= %v\n", loaclStorages)
// }
func GetCurrentStorageInfo(path string) storageInfo {
state := diskstate.DiskUsage(path)
info := storageInfo{}
info.Size = uint64(state.All / diskstate.B)
info.FreeSpace = uint64(state.Free / diskstate.B)
info.Used = uint64(state.Used / diskstate.B)
// fmt.Printf("All=%dM, Free=%dM, Available=%dM, Used=%dM, Usage=%d%%",
// state.All/diskstate.B, state.Free/diskstate.MB, state.Available/diskstate.MB, state.Used/diskstate.MB, 100*state.Used/state.All)
return info
}
type ComputerMonitor struct {
CPU float64 `json:"cpu"`
Mem float64 `json:"mem"`
}
// GetCPUPercent 获取CPU使用率
func GetCPUPercent() float64 {
percent, err := cpu.Percent(time.Second, false)
if err != nil {
log.Fatalln(err.Error())
return -1
}
return percent[0]
}
// GetMemPercent 获取内存使用率
func GetMemPercent() float64 {
memInfo, err := mem.VirtualMemory()
if err != nil {
log.Fatalln(err.Error())
return -1
}
return memInfo.UsedPercent
}
func GetCpuMem() ComputerMonitor {
var res ComputerMonitor
res.CPU = GetCPUPercent()
res.Mem = GetMemPercent()
return res
}
+73
View File
@@ -0,0 +1,73 @@
package iniConfig
import (
"gopkg.in/ini.v1"
)
type IniConfig struct {
Err error
Config *ini.File
Default map[string]map[string]string // 默认配置
FileName string
}
// 获取配置
func (t *IniConfig) GetValue(section string, name string) *ini.Key {
return t.Config.Section(section).Key(name)
}
// 设置配置
func (t *IniConfig) SetValue(section string, name string, value string) error {
t.Config.Section(section).Key(name).SetValue(value)
return t.Config.SaveTo(t.FileName)
}
// 获取配置
func (t *IniConfig) GetValueString(section string, name string) string {
return t.Config.Section(section).Key(name).String()
}
// 获取字符串配置,如果没有会查找默认值
func (t *IniConfig) GetValueStringOrDefault(section string, name string) string {
value := t.GetValueString(section, name)
if value == "" && t.Default[section] != nil && t.Default[section][name] != "" {
return t.Default[section][name]
} else {
return value
}
}
// 获取配置
func (t *IniConfig) GetValueInt(section string, name string) int {
return t.Config.Section(section).Key(name).MustInt()
}
// 获取组配置
func (t *IniConfig) GetSection(section string, result interface{}) error {
if group, err := t.Config.GetSection(section); err != nil {
return err
} else {
if err := group.MapTo(result); err != nil {
return err
} else {
return nil
}
}
}
// 删除组
func (t *IniConfig) DeleteSection(section string) {
t.Config.DeleteSection(section)
t.Config.SaveTo(t.FileName)
}
// 创建一个配置对象
func NewIniConfig(filename string) *IniConfig {
config, err := ini.Load(filename)
return &IniConfig{
Err: err,
Config: config,
FileName: filename,
}
}
+21
View File
@@ -0,0 +1,21 @@
package jsonConfig
type SpecialDayModel struct {
ConfigModel
Data SpecialDayDataModel
}
type SpecialDayDataModel struct {
OnlyId string `json:"onlyId"` // 唯一ID
Name string `json:"name"` // 名称
UpdateTime string `json:"updateTime"` // 更新时间
// Year int `json:"year"` // 年份
StartDate string `json:"startDate"` // 开始日期
EndDate string `json:"endDate"` // 结束日期
Days map[string]SpecialDayDataDaysModel `json:"days"` // 天数据
}
type SpecialDayDataDaysModel struct {
Holiday bool `json:"holiday"` // 唯一ID
Name string `json:"name"` // 名称
}
+19
View File
@@ -0,0 +1,19 @@
package jsonConfig
// 事件风格数据
type EventStyleDataModel struct {
Title string `json:"title"` // 标题
ClassName string `json:"className"` // 类名称
TextColor string `json:"textColor"` // 字体颜色
BackgroundColor string `json:"backgroundColor"` // 背景颜色
BorderColor string `json:"borderColor"` // 边框颜色
}
type EventStyleModel struct {
ConfigModel
Data []EventStyleDataModel
}
func (e *EventStyleModel) GetImportData() error {
return nil
}
+101
View File
@@ -0,0 +1,101 @@
package jsonConfig
import (
"encoding/json"
"github.com/gin-gonic/gin"
)
type JsonConfiger interface {
GetImportData() error
// ExportFile()
}
type ConfigModel struct {
AppName string `json:"AppName"`
Ability string `json:"Ability"`
Version string `json:"Version"`
AbilityVersion string `json:"AbilityVersion"`
AppAllowLowVersion string `json:"AppAllowLowVersion"`
Data interface{} `json:"Data"`
}
var expoprtSuffix = ".lcn.json"
const (
ABILITY_MODE_EVENT_STYLE = "EventStyle" // 时间风格
ABILITY_MODE_SPECIAL_DAY = "SpecialDay" // 特殊的日期
)
// 生成输出文件
func BuildExportFile(cfgModel *ConfigModel) ([]byte, error) {
content, err := json.Marshal(cfgModel)
return content, err
}
func Write(ctx *gin.Context, fileName string, content []byte) {
ctx.Writer.Header().Add("Content-Type", "application/octet-stream")
ctx.Writer.Header().Add("Content-disposition", "attachment;filename="+fileName+expoprtSuffix)
ctx.Writer.Header().Add("Content-Transfer-Encoding", "binary")
ctx.Writer.Write(content)
}
func GetImportData(JsonConfiger) {
}
func NewConfigModel(ability, abilityVersion string) *ConfigModel {
return &ConfigModel{
AppName: "Li-Calendar",
Version: "1",
AppAllowLowVersion: "1",
Ability: ability,
AbilityVersion: abilityVersion,
}
}
// 验证配置模型数据是否相同
func ConfigModelCheck(data *ConfigModel, ability, abilityVersion string) bool {
newData := NewConfigModel(ability, abilityVersion)
if *data != *newData {
return false
}
return true
}
// func InportConfigFile(f multipart.FileHeader, eventStyle EventStyleModel) (EventStyleModel, error) {
// src, err := f.Open()
// defer src.Close()
// if err != nil {
// return err
// }
// contentByte, err := ioutil.ReadAll(src)
// if err != nil {
// return err
// }
// configFile := ConfigModel{}
// if err := json.Unmarshal(contentByte, &configFile); err != nil {
// return err
// }
// v, ok := configFile.Data.(EventStyleModel)
// return errors.New("格式")
// if !ok {
// return errors.New("格式错误")
// }
// if err := json.Unmarshal(contentByte, &configFile); err != nil {
// return err
// }
// fileExt := strings.ToLower(path.Ext(f.Filename))
// fileName := cmn.Md5(fmt.Sprintf("%s%s", f.Filename, time.Now().String()))
// fildDir := fmt.Sprintf("%s/%d/%d/%d/", configUpload, time.Now().Year(), time.Now().Month(), time.Now().Day())
// isExist, _ := cmn.PathExists(fildDir)
// if !isExist {
// os.MkdirAll(fildDir, os.ModePerm)
// }
// filepath := fmt.Sprintf("%s%s%s", fildDir, fileName, fileExt)
// }
+60
View File
@@ -0,0 +1,60 @@
package language
import (
"os"
"strings"
"sun-panel/lib/cmn"
"sun-panel/lib/iniConfig"
)
type LangStructObj struct {
LangContet *iniConfig.IniConfig
}
func NewLang(langPath string) *LangStructObj {
langObj := LangStructObj{}
exists, _ := cmn.PathExists(langPath)
if exists {
langObj.LangContet = iniConfig.NewIniConfig(langPath) // 读取配置
} else {
cmn.Pln(cmn.LOG_ERROR, "language file does not exist:"+langPath)
os.Exit(1)
}
return &langObj
}
// 获取
// common.lang
// 配置文件格式
// [common]
// lang=zh-cn
func (l *LangStructObj) Get(key string) string {
if key == "" {
return key
}
keyArr := strings.Split(key, ".")
if len(keyArr) < 2 {
return l.LangContet.GetValueString(keyArr[0], "NOT EMPTY")
} else {
return l.LangContet.GetValueString(keyArr[0], keyArr[1])
}
}
// 获取并替换字段
func (l *LangStructObj) GetWithFields(key string, fields map[string]string) string {
c := l.Get(key)
for k, v := range fields {
c = strings.ReplaceAll(c, `{`+k+`}`, v)
}
return c
}
// 获取值并向后追加
func (l *LangStructObj) GetAndInsert(key string, insertContent ...string) string {
content := l.Get(key) + " "
for _, v := range insertContent {
content += v
}
return content
}
+141
View File
@@ -0,0 +1,141 @@
package mail
import (
"sun-panel/global"
"sun-panel/models"
"gopkg.in/gomail.v2"
)
type EmailInfo struct {
Username string // 账号
Password string // 密码
Host string // 服务器地址
Port int // 端口 默认465
}
type Emailer struct {
EmailInfo EmailInfo
Dialer *gomail.Dialer
}
func NewEmailer(emailInfo EmailInfo) *Emailer {
dialer := gomail.NewDialer(emailInfo.Host, emailInfo.Port, emailInfo.Username, emailInfo.Password)
return &Emailer{
Dialer: dialer,
EmailInfo: emailInfo,
}
}
func (e *Emailer) Send(mailTo []string, send_name, title, body string) error {
m := gomail.NewMessage()
m.SetHeader("From", e.EmailInfo.Username)
//这种方式可以添加别名,即“XX官方”
//说明:如果是用网易邮箱账号发送,以下方法别名可以是中文,如果是qq企业邮箱,以下方法用中文别名,会报错,需要用上面此方法转码
//m.SetHeader("From", "FB Sample"+"<"+mailConn["user"]+">") //这种方式可以添加别名,即“FB Sample”, 也可以直接用<code>m.SetHeader("From",mailConn["user"])</code> 读者可以自行实验下效果
//m.SetHeader("From", mailConn["user"])
m.SetHeader("To", mailTo...) //发送给多个用户
m.SetHeader("Subject", title) //设置邮件主题
m.SetBody("text/html", body) //设置邮件正文
return e.Dialer.DialAndSend(m)
}
// 发送邮件
func (m *Emailer) SendMail(mailTo string, title, content string) error {
fromUrl := "127.0.0.1"
appName := global.Lang.Get("common.app_name")
mUser := models.User{Mail: mailTo}
userInfo := mUser.GetUserInfoByMail()
nickName := ""
if userInfo != nil && userInfo.Name != "" {
nickName = " " + userInfo.Name
}
body := `<meta charset="utf-8">
<table width="600px" style="max-width: 600px;" align="center">
<tr style="width: 600px;background-color: rgb(28, 197, 249);">
<td align="left" style="width: 600px;padding: 22px 18px 11px;display: inline-block;">
<div style="font-weight: 900;font-size: 18px;">
<p>Hi` + nickName + `:</p>
</div>
</td>
<td style="width: 100%;display: inline-block;border-top: 4px dashed rgb(255, 255, 255);"> </td>
<td style="width: 600px;padding: 18px;display: inline-block;">
<div align="left" style="color: rgb(57, 57, 57); line-height: 1.6; font-size: 14px; margin: 0px;font-weight: bolder;">
` + content + `
</div>
</td>
<td style="width: 600px;padding: 18px;display: inline-block;">
<div align="rignt">
<div style="font-size: 14px; margin: 0px;text-align: right;font-size: 14px; font-weight: bolder;">
-- ` + global.Lang.Get("mail.from") + `[<a href="` + fromUrl + `" style="color: #575757;">` + appName + `</a>]</div>
</div>
</td>
</tr>
</table>`
return SendMail(m, []string{mailTo}, appName, title, body)
}
// 发送链接邮件
func (m *Emailer) SendMailOfLink(mailTo, title, content, btn_name, url string) error {
content = content + getLabelHtmlBtn(btn_name, url)
return m.SendMail(mailTo, title, content)
}
// 发送注册邮件
func (m *Emailer) SendMailOfRegister(mailTo, key string) error {
fromUrl := "127.0.0.1"
appName := global.Lang.Get("common.app_name")
title := global.Lang.GetWithFields("mail.register_title", map[string]string{
"AppName": appName,
})
content := global.Lang.GetWithFields("mail.register_content", map[string]string{
"AppName": appName,
})
return m.SendMailOfLink(mailTo, title, content, global.Lang.Get("mail.register_click_btn"), fromUrl+"/profile/auth.html#/linkRegister?code="+key)
}
// 发送验证码邮件
func (m *Emailer) SendMailOfVCode(mailTo, title, content, vCode string) error {
// appName := global.Lang.Get("common.app_name")
content = content + getLabelHtmlVcode(vCode)
return m.SendMail(mailTo, title, content)
}
// 发送邮件
//
// @param emailer
// @param mailTo
// @param send_name
// @param title
// @param body
// @return error
func SendMail(emailer *Emailer, mailTo []string, send_name, title, body string) error {
//定义邮箱服务器连接信息,如果是网易邮箱 pass填密码,qq邮箱填授权码
if emailer.EmailInfo.Port == 0 {
emailer.EmailInfo.Port = 465
}
m := gomail.NewMessage()
m.SetHeader("From", m.FormatAddress(emailer.EmailInfo.Username, send_name))
//这种方式可以添加别名,即“XX官方”
//说明:如果是用网易邮箱账号发送,以下方法别名可以是中文,如果是qq企业邮箱,以下方法用中文别名,会报错,需要用上面此方法转码
//m.SetHeader("From", "FB Sample"+"<"+mailConn["user"]+">") //这种方式可以添加别名,即“FB Sample”, 也可以直接用<code>m.SetHeader("From",mailConn["user"])</code> 读者可以自行实验下效果
//m.SetHeader("From", mailConn["user"])
m.SetHeader("To", mailTo...) //发送给多个用户
m.SetHeader("Subject", title) //设置邮件主题
m.SetBody("text/html", body) //设置邮件正文
// d.TLSConfig = &tls.Config{InsecureSkipVerify: true} // 跳过证书验证,不推荐
err := emailer.Dialer.DialAndSend(m)
return err
}
func getLabelHtmlBtn(btn_name string, href string) string {
return `<div><a style="color: #fff;background-color: #2e2e2e;display: inline-block;padding: 10px 30px;border-radius: 5px;" href="` + href + `">` + btn_name + `</a></div>`
}
func getLabelHtmlVcode(vcode string) string {
return `<p><div style="color: #fff;background-color: #2e2e2e;display: inline-block;padding: 10px 30px;border-radius: 5px;">` + vcode + `</div></p>`
}
+72
View File
@@ -0,0 +1,72 @@
package mail
import (
"sun-panel/global"
)
// 发送注册验证码
//
// @param emailer
// @param mailTo 收件人
// @param vcode 验证码
// @return error
func SendRegisterEmail(emailer *Emailer, mailTo, vcode string) error {
appName := global.Lang.Get("common.app_name")
title := global.Lang.GetWithFields("mail.register_vcode_title", map[string]string{
"AppName": appName,
})
content := global.Lang.GetWithFields("mail.register_vcode_content", map[string]string{
"AppName": appName,
"Minute": "10",
})
err := emailer.SendMailOfVCode(mailTo, title, content, vcode)
if err != nil {
global.Logger.Errorf("failed to send email to %s, err:%+v\n", mailTo, err)
}
return err
}
// 发送重置密码验证码
//
// @param emailer
// @param mailTo
// @param vcode
// @return error
func SendResetPasswordVCode(emailer *Emailer, mailTo, vcode string) error {
title := global.Lang.Get("mail.reset_password_password_title")
content := global.Lang.Get("mail.reset_password_password_content")
err := emailer.SendMailOfVCode(mailTo, title, content, vcode)
if err != nil {
global.Logger.Errorf("failed to send email to %s, err:%+v\n", mailTo, err)
}
return err
}
// // 事件提醒
// //
// // @param emailer
// // @param mailTo
// // @param eventReminder
// // @return error
// func SendEventReminder(emailer *Emailer, mailTo string, eventReminder models.EventReminder) error {
// startTime := eventReminder.Event.StartTime.Time.Format(cmn.TimeFormatMode4)
// endTime := eventReminder.Event.EndTime.Time.Format(cmn.TimeFormatMode4)
// title := global.Lang.GetWithFields("mail.reminder_title", map[string]string{
// "Title": eventReminder.Event.Title,
// "Time": startTime,
// })
// contentParam := map[string]string{
// "ItemTitle": eventReminder.Event.Item.Title,
// "Time": startTime,
// }
// content := "<p>" + global.Lang.GetWithFields("mail.reminder_content", contentParam) + "</p>"
// content += "<p>" + global.Lang.Get("mail.reminder_event_title") + " : " + eventReminder.Event.Title + "</p>"
// content += "<p>" + global.Lang.Get("common.start_time") + " : " + startTime + "</p>"
// content += "<p>" + global.Lang.Get("common.end_time") + " : " + endTime + "</p>"
// err := emailer.SendMail(mailTo, title, content)
// if err != nil {
// global.Logger.Errorf("failed to send email to %s, err:%+v\n", mailTo, err)
// }
// return err
// }
+21
View File
@@ -0,0 +1,21 @@
package queue
// 队列器
type Queuer interface {
// 左侧插入
LPush(value ...interface{}) error
// 右侧插入
RPush(value ...interface{}) error
// 删除元素
Delete(value interface{}) error
// 使用下标获取值
GetByIndex(index int64, v interface{}) error
// 左侧读取并删除
LPop(v interface{}) error
// 右侧读取并删除
RPop(v interface{}) error
// 队列长度
Length() (int64, error)
// 清空队列
Flush() error
}
+105
View File
@@ -0,0 +1,105 @@
package queueMemory
import (
"encoding/json"
"errors"
"reflect"
"sync"
)
type Pool struct {
Values [][]byte
Lock sync.RWMutex
}
func New() *Pool {
return &Pool{}
}
func (k *Pool) LPush(value ...interface{}) error {
k.Lock.Lock()
defer k.Lock.Unlock()
for i := 0; i < len(value); i++ {
v, _ := json.Marshal(value[i])
k.Values = append([][]byte{v}, k.Values...)
}
return nil
}
func (k *Pool) RPush(value ...interface{}) error {
k.Lock.Lock()
defer k.Lock.Unlock()
for i := 0; i < len(value); i++ {
v, _ := json.Marshal(value[i])
k.Values = append(k.Values, v)
}
return nil
}
func (k *Pool) Delete(value interface{}) error {
k.Lock.Lock()
defer k.Lock.Unlock()
var index int64
v, _ := json.Marshal(value)
for i, item := range k.Values {
if reflect.DeepEqual(item, v) {
index = int64(i)
k.removeIndex(index)
return nil
}
}
return nil
}
// 取出值
func (k *Pool) GetByIndex(index int64, v interface{}) error {
k.Lock.RLock()
defer k.Lock.RUnlock()
if int64(len(k.Values)) >= index {
json.Unmarshal(k.Values[index], v)
return nil
} else {
return errors.New("index non-existent")
}
}
// 左-取出并删除
func (k *Pool) LPop(v interface{}) error {
if err := k.GetByIndex(0, v); err != nil {
return err
} else {
k.removeIndex(0)
}
return nil
}
// 右-取出并删除
func (k *Pool) RPop(v interface{}) error {
index := int64(len(k.Values) - 1)
if err := k.GetByIndex(index, v); err != nil {
return err
} else {
k.removeIndex(index)
}
return nil
}
func (k *Pool) removeIndex(index int64) error {
k.Lock.Lock()
defer k.Lock.Unlock()
k.Values = append(k.Values[:index], k.Values[index+1:]...)
return nil
}
func (k *Pool) Length() (int64, error) {
return int64(len(k.Values)), nil
}
func (k *Pool) Flush() error {
k.Lock.Lock()
defer k.Lock.Unlock()
k.Values = nil
return nil
}
+96
View File
@@ -0,0 +1,96 @@
package queueRedis
import (
"context"
"encoding/json"
"github.com/redis/go-redis/v9"
)
type Pool struct {
Ctx context.Context
Redis *redis.Client
Name string
}
func New(redisDb *redis.Client, name string) *Pool {
ctx := context.Background()
return &Pool{
Ctx: ctx,
Redis: redisDb,
Name: name,
}
}
func (k *Pool) LPush(value ...interface{}) error {
var values []interface{}
for _, v := range value {
values = append(values, k.encode(v))
}
return k.Redis.LPush(k.Ctx, k.Name, values...).Err()
}
func (k *Pool) RPush(value ...interface{}) error {
var values []interface{}
for _, v := range value {
values = append(values, k.encode(v))
}
return k.Redis.RPush(k.Ctx, k.Name, values...).Err()
}
func (k *Pool) Delete(value interface{}) error {
return k.Redis.LRem(k.Ctx, k.Name, 0, k.encode(value)).Err()
}
// 取出值
func (k *Pool) GetByIndex(index int64, v interface{}) error {
if d, err := k.Redis.LIndex(k.Ctx, k.Name, index).Result(); err != nil {
return err
} else {
k.decode(d, v)
return nil
}
}
// 左-取出并删除
func (k *Pool) LPop(v interface{}) error {
if d, err := k.Redis.LPop(k.Ctx, k.Name).Result(); err != nil {
return err
} else {
k.decode(d, v)
return nil
}
}
// 右-取出并删除
func (k *Pool) RPop(v interface{}) error {
if d, err := k.Redis.RPop(k.Ctx, k.Name).Result(); err != nil {
return err
} else {
k.decode(d, v)
return nil
}
}
func (k *Pool) encode(value any) string {
data, err := json.Marshal(value)
if err != nil {
return "{}"
}
return string(data)
}
func (k *Pool) decode(v string, value interface{}) {
err := json.Unmarshal([]byte(v), value)
_ = err
}
func (k *Pool) Length() (int64, error) {
r := k.Redis.LLen(k.Ctx, k.Name)
return r.Result()
}
func (k *Pool) Flush() error {
return k.Redis.Del(k.Ctx, k.Name).Err()
}
+10
View File
@@ -0,0 +1,10 @@
package file
import (
"github.com/gin-gonic/gin"
)
func Logout(c *gin.Context) {
c.SetCookie("cloud_tk", "", 0, "/source/", "", false, true)
}