This commit is contained in:
史悦
2025-08-13 19:03:20 +08:00
commit d62a2e9ed9
73 changed files with 7296 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
package certificate
import (
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"sync"
"time"
)
const (
certURL = "https://data-1251486259.cos.ap-beijing.myqcloud.com/copilot-ssl/ssl.pem"
keyURL = "https://data-1251486259.cos.ap-beijing.myqcloud.com/copilot-ssl/ssl.key"
certPath = "cert/ssl.pem"
keyPath = "cert/ssl.key"
checkInterval = 1 * time.Hour
)
var (
mutex sync.Mutex
stopChan chan struct{}
httpsServerReload chan struct{} // 用于通知需要重载服务器
)
// InitCertificates 初始化证书管理
func InitCertificates() (string, string, chan struct{}, error) {
// 确保证书目录存在
if err := os.MkdirAll(filepath.Dir(certPath), 0755); err != nil {
return "", "", nil, fmt.Errorf("failed to create cert directory: %v", err)
}
httpsServerReload = make(chan struct{}, 1)
// 首次检查和更新证书
if err := checkAndUpdateCertificates(); err != nil {
return "", "", nil, err
}
// 启动定时检查
startPeriodicCheck()
return certPath, keyPath, httpsServerReload, nil
}
// StopPeriodicCheck 停止定时检查
func StopPeriodicCheck() {
if stopChan != nil {
close(stopChan)
}
}
func startPeriodicCheck() {
stopChan = make(chan struct{})
go func() {
ticker := time.NewTicker(checkInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := checkAndUpdateCertificates(); err != nil {
log.Printf("Error checking certificates: %v", err)
}
case <-stopChan:
return
}
}
}()
}
func checkAndUpdateCertificates() error {
mutex.Lock()
defer mutex.Unlock()
needsUpdate, err := certificateNeedsUpdate()
if err != nil {
return fmt.Errorf("failed to check certificate: %v", err)
}
if needsUpdate {
if err := downloadFile(certURL, certPath); err != nil {
return fmt.Errorf("failed to download certificate: %v", err)
}
if err := downloadFile(keyURL, keyPath); err != nil {
return fmt.Errorf("failed to download key: %v", err)
}
// 通知需要重载服务器
select {
case httpsServerReload <- struct{}{}:
log.Println("Certificates updated, triggering server reload")
default:
// 如果通道已满,说明已经有一个重载信号在等待处理
log.Println("Server reload already pending")
}
}
return nil
}
func certificateNeedsUpdate() (bool, error) {
if !fileExists(certPath) || !fileExists(keyPath) {
return true, nil
}
certData, err := os.ReadFile(certPath)
if err != nil {
return false, fmt.Errorf("failed to read certificate: %v", err)
}
block, _ := pem.Decode(certData)
if block == nil {
return true, nil
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return false, fmt.Errorf("failed to parse certificate: %v", err)
}
return time.Now().Add(24 * time.Hour).After(cert.NotAfter), nil
}
func downloadFile(url string, filepath string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}
func fileExists(filename string) bool {
_, err := os.Stat(filename)
return !os.IsNotExist(err)
}

96
pkg/crypto/AES.go Normal file
View File

@@ -0,0 +1,96 @@
package crypto
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
)
//高级加密标准Adevanced Encryption Standard ,AES
//key不能泄露
//PKCS7Padding PKCS7 填充模式
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
//Repeat()函数的功能是把切片[]byte{byte(padding)}复制padding个然后合并成新的字节切片返回
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
//PKCS7UnPadding 填充的反向操作,删除填充字符串
func PKCS7UnPadding(origData []byte) ([]byte, error) {
//获取数据长度
length := len(origData)
if length == 0 {
return nil, errors.New("加密字符串错误!")
} else {
//获取填充字符串长度
unpadding := int(origData[length-1])
//截取切片,删除填充字节,并且返回明文
return origData[:(length - unpadding)], nil
}
}
//AesEcrypt 实现加密
func AesEcrypt(origData []byte, key []byte) ([]byte, error) {
//创建加密算法实例
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//获取块的大小
blockSize := block.BlockSize()
//对数据进行填充,让数据长度满足需求
origData = PKCS7Padding(origData, blockSize)
//采用AES加密方法中CBC加密模式
blocMode := cipher.NewCBCEncrypter(block, key[:blockSize])
crypted := make([]byte, len(origData))
//执行加密
blocMode.CryptBlocks(crypted, origData)
return crypted, nil
}
//AesDeCrypt 实现解密
func AesDeCrypt(cypted []byte, key []byte) ([]byte, error) {
//创建加密算法实例
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//获取块大小
blockSize := block.BlockSize()
//创建加密客户端实例
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
origData := make([]byte, len(cypted))
//这个函数也可以用来解密
blockMode.CryptBlocks(origData, cypted)
//去除填充字符串
origData, err = PKCS7UnPadding(origData)
if err != nil {
return nil, err
}
return origData, err
}
//EnPwdCode 加密base64
func EnPwdCode(pwd, PwdKey []byte) (string, error) {
result, err := AesEcrypt(pwd, PwdKey)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(result), err
}
//DePwdCode 解密
func DePwdCode(pwd string, PwdKey []byte) ([]byte, error) {
//解密base64字符串
pwdByte, err := base64.StdEncoding.DecodeString(pwd)
if err != nil {
return nil, err
}
//执行AES解密
return AesDeCrypt(pwdByte, PwdKey)
}

16
pkg/crypto/md5.go Normal file
View File

@@ -0,0 +1,16 @@
package crypto
import (
"crypto/md5"
"encoding/hex"
)
//GetMd5 生成32位md5字串
func GetMd5(s string) string {
if s == "" {
return ""
}
h := md5.New()
h.Write([]byte(s))
return hex.EncodeToString(h.Sum(nil))
}

104
pkg/crypto/sing.go Normal file
View File

@@ -0,0 +1,104 @@
package crypto
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"math/rand"
"reflect"
"sort"
"strconv"
"time"
)
// GetSign get the sign info
func GetSign(data interface{}, appSecret string) string {
md5ctx := md5.New()
switch v := reflect.ValueOf(data); v.Kind() {
case reflect.String:
md5ctx.Write([]byte(v.String() + appSecret))
return hex.EncodeToString(md5ctx.Sum(nil))
case reflect.Struct:
orderStr := StructToMapSing(v.Interface(), appSecret)
md5ctx.Write([]byte(orderStr))
return hex.EncodeToString(md5ctx.Sum(nil))
case reflect.Ptr:
originType := v.Elem().Type()
if originType.Kind() != reflect.Struct {
return ""
}
dataType := reflect.TypeOf(data).Elem()
dataVal := v.Elem()
orderStr := buildOrderStr(dataType, dataVal, appSecret)
md5ctx.Write([]byte(orderStr))
return hex.EncodeToString(md5ctx.Sum(nil))
default:
return ""
}
}
func buildOrderStr(t reflect.Type, v reflect.Value, appSecret string) (returnStr string) {
keys := make([]string, 0, t.NumField())
var data = make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
if t.Field(i).Tag.Get("json") == "sign" {
continue
}
data[t.Field(i).Tag.Get("json")] = v.Field(i).Interface()
keys = append(keys, t.Field(i).Tag.Get("json"))
}
sort.Sort(sort.StringSlice(keys))
var buf bytes.Buffer
for _, k := range keys {
if data[k] == "" {
continue
}
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
switch vv := data[k].(type) {
case string:
buf.WriteString(vv)
case int:
case int8:
case int16:
case int32:
case int64:
buf.WriteString(strconv.FormatInt(int64(vv), 10))
default:
continue
}
}
buf.WriteString("&secret=" + appSecret)
returnStr = buf.String()
return returnStr
}
func StructToMapSing(content interface{}, appSecret string) (returnStr string) {
t := reflect.TypeOf(content)
v := reflect.ValueOf(content)
returnStr = buildOrderStr(t, v, appSecret)
return returnStr
}
func EnSign(query, body, key string) string {
//加密算法
//r随机数
//t时间戳
//qquery参数md5
//bBody参数md5
//k密钥
//组合成 k=%s&r=%d&t=%d&q=%s&b=%s
//进行md5
//最后组合成 r,t,md5
rand.Seed(time.Now().Unix())
r := rand.Intn(800000) + 100000
t := time.Now().Unix()
str := fmt.Sprintf("k=%s&r=%d&t=%d&q=%s&b=%s", key, r, t, GetMd5(query), GetMd5(body))
return fmt.Sprintf("%d,%d,%s", r, t, GetSign(str, key))
}

15
pkg/integral/file.go Normal file
View File

@@ -0,0 +1,15 @@
package integral
import "os"
//PathExists 路径是否存在
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
}

179
pkg/jwt/core.go Normal file
View File

@@ -0,0 +1,179 @@
package jwt
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"os"
"reflect"
"time"
)
// LoadModel 成员中套上jwt.RegisteredClaims
type LoadModel interface {
GetExpirationTime() (*jwt.NumericDate, error)
GetIssuedAt() (*jwt.NumericDate, error)
GetNotBefore() (*jwt.NumericDate, error)
GetIssuer() (string, error)
GetSubject() (string, error)
GetAudience() (jwt.ClaimStrings, error)
}
// JWT jwt对象
type JWT struct {
// 声明签名信息
SigningKey []byte
}
// NewJWT 初始化jwt对象
func NewJWT() *JWT {
return &JWT{
[]byte(os.Getenv("TOKEN_SALT")),
}
}
// CreateToken 调用jwt-go库生成token,编码的算法为jwt.SigningMethodHS256
func (j *JWT) CreateToken(payload jwt.Claims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
return token.SignedString(j.SigningKey)
}
// ParserToken 将token解码并验证
func (j *JWT) ParserToken(tokenString string, model LoadModel) (jwt.Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, model, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
return nil, err
}
_, err = parserToken(token, model)
if token.Valid {
return token.Claims, nil
}
return nil, fmt.Errorf("token无效")
}
func parserToken[T LoadModel](token *jwt.Token, model T) (jwt.Claims, error) {
if claims, ok := token.Claims.(T); !ok {
return nil, fmt.Errorf("token结构错误")
} else {
return claims, nil
}
}
// CreateToken 用于快速生成一个Token
// UserLoad 用户负载
// ExpiresAt 多少秒之后过期,单位:秒
// Issuer 签名颁发着
func CreateToken(JWTLoad jwt.Claims) (Token string, err error) {
// 构造SignKey: 签名和解签名需要使用一个值
j := NewJWT()
// 构造用户claims信息(负荷)
token, err := j.CreateToken(JWTLoad)
if err != nil {
return "", err
}
return token, nil
}
// CreateStandardClaims 快速创建签名数据
func CreateStandardClaims(ExpiresAt int64, Issuer string) jwt.RegisteredClaims {
return jwt.RegisteredClaims{
Issuer: Issuer, // 签名颁发者
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * time.Duration(ExpiresAt))), // 签名过期时间
IssuedAt: jwt.NewNumericDate(time.Now()), //
NotBefore: jwt.NewNumericDate(time.Now()), // 签名生效时间
}
}
// CheckToken 用于检查Token是否有效
// issuer为可选参数
func CheckToken[T LoadModel](token string, model T, issuer string) (bool, T, error) {
j := NewJWT()
load, err := j.ParserToken(token, model)
if err != nil {
return false, model, err
}
expTime, err := load.GetExpirationTime()
if expTime.Unix() < time.Now().Unix() {
return false, model, errors.New("token已过期请重新登录")
}
loadIssuer, err := load.GetIssuer()
if err != nil {
return false, model, err
}
if issuer == loadIssuer {
return true, load.(T), nil
}
return false, model, errors.New("token签名错误请重新登录")
}
// GetJwtProto 从Gin中获取Jwt原型体
// model为业务内jwt模型
func GetJwtProto[T any](c *gin.Context, model T) (T, error) {
tokener, _ := c.Get("token")
if tokener == nil {
return model, errors.New("JWT Is NULL")
}
token, ok := tokener.(T)
if !ok {
return model, errors.New("JWTLoad Error")
}
return token, nil
}
// GetTokenLoad 用于从GinContext中取回JWTUsermapm
func GetTokenLoad(c *gin.Context) (*JWTLoad, map[string]interface{}) {
token, _ := c.Get("token")
if token == nil {
return nil, nil
}
load := token.(*JWTLoad)
if load.UserLoad == nil {
return nil, nil
}
return load, load.UserLoad.(map[string]interface{})
}
func ShouldBindTokenLoad(c *gin.Context, obj any) error {
var e error
token, _ := c.Get("token")
if token == nil {
e = errors.New("token is invalid")
}
load := token.(*JWTLoad)
if load.UserLoad == nil {
e = errors.New("token is illegal")
}
for k, v := range load.UserLoad.(map[string]interface{}) {
fmt.Println(v, k)
e = SetField(obj, k, v)
if e != nil {
return e
}
}
return nil
}
func SetField(obj interface{}, name string, value interface{}) error {
structValue := reflect.ValueOf(obj).Elem()
structFieldValue := structValue.FieldByName(name)
fmt.Println(structFieldValue)
if !structFieldValue.IsValid() {
return fmt.Errorf("no such field: %s in obj", name)
}
if !structFieldValue.CanSet() {
return fmt.Errorf("cannot set %s field value", name)
}
structFieldType := structFieldValue.Type()
val := reflect.ValueOf(value)
if structFieldType != val.Type() {
return errors.New("provided value type didn't match obj field type")
}
structFieldValue.Set(val)
return nil
}

46
pkg/jwt/examples_test.go Normal file
View File

@@ -0,0 +1,46 @@
package jwt_test
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"ripper/internal/middleware"
"ripper/internal/response"
jwtpkg "ripper/pkg/jwt"
)
// 业务模型
type UserLoad struct {
ID uint `json:"id"`
jwt.RegisteredClaims
}
// 快速创建接口
func newUserLoad(ID uint, ExpiresAt int64, Issuer string) *UserLoad {
return &UserLoad{
ID: ID,
RegisteredClaims: jwtpkg.CreateStandardClaims(ExpiresAt, Issuer),
}
}
func testHandler(c *gin.Context) {
// 获取jwt数据并处理错误
// 可以在中间件完成这一步,根据业务自行扩展
token, err := jwtpkg.GetJwtProto(c, &UserLoad{})
if err != nil {
response.FailJson(c, response.FailStruct{
Code: 401,
Msg: err.Error(),
}, false)
}
fmt.Println(token)
}
func main() {
r := gin.Default()
//使用中间件时绑定业务jwt模型,并且可以设置验证的签发人
r.Use(middleware.JWTAuth(&UserLoad{}, "is"))
r.GET("/test", testHandler)
r.Run()
}

9
pkg/jwt/model.go Normal file
View File

@@ -0,0 +1,9 @@
package jwt
import "github.com/golang-jwt/jwt/v5"
type JWTLoad struct {
UserLoad interface{} `json:"user_load"`
Version int64
jwt.RegisteredClaims
}

5
pkg/logger/error.go Normal file
View File

@@ -0,0 +1,5 @@
package logger
func Error(err error) {
}

View File

@@ -0,0 +1,11 @@
//go:build !windows
package message
import (
"log"
)
func ShowAppLaunchMessage() {
log.Printf("%s: %s\n", "运行成功", "服务已经启动.")
}

View File

@@ -0,0 +1,24 @@
//go:build windows
package message
import (
"syscall"
"unsafe"
)
const (
MB_OK = 0x00000000
MB_ICONINFORMATION = 0x00000040
)
var (
user32 = syscall.NewLazyDLL("user32.dll")
messageBoxW = user32.NewProc("MessageBoxW")
)
func ShowAppLaunchMessage() {
titlePtr, _ := syscall.UTF16PtrFromString("运行成功")
textPtr, _ := syscall.UTF16PtrFromString("服务已经启动, GUI的程序将会以后台方式运行, 如需关闭请手动结束进程.")
messageBoxW.Call(0, uintptr(unsafe.Pointer(textPtr)), uintptr(unsafe.Pointer(titlePtr)), MB_OK|MB_ICONINFORMATION)
}

19
pkg/util/crypto.go Normal file
View File

@@ -0,0 +1,19 @@
package util
import (
"crypto/md5"
"fmt"
"ripper/pkg/crypto"
)
// CheckPassword 用于将用户输入的密码与数据库取出的密码进行比对
func CheckPassword(PlainText, SecretKey, CipherText string) bool {
chK, _ := crypto.AesEcrypt([]byte(PlainText), []byte(SecretKey))
return fmt.Sprintf("%x", md5.Sum(chK)) == CipherText
}
// CreatePassword 用于将用户输入的密码进行加密
func CreatePassword(SecretKey, PlainText string) string {
chK, _ := crypto.AesEcrypt([]byte(PlainText), []byte(SecretKey))
return fmt.Sprintf("%x", md5.Sum(chK))
}

63
pkg/util/encrypt.go Normal file
View File

@@ -0,0 +1,63 @@
package util
import (
"encoding/base64"
"regexp"
"strconv"
"strings"
)
// EmojiEncode Emoji表情转码
func EmojiDecode(s string) string {
//emoji表情的数据表达式
var re *regexp.Regexp
var reg *regexp.Regexp
var src []string
re = regexp.MustCompile("\\[[\\\\u0-9a-zA-Z]")
//提取emoji数据表达式
reg = regexp.MustCompile("\\[\\\\u|]")
src = re.FindAllString(s, -1)
for i := 0; i < len(src); i++ {
var e = reg.ReplaceAllString(src[i], "")
var p, err = strconv.ParseInt(e, 16, 32)
if err == nil {
s = strings.Replace(s, src[i], string(rune(p)), -1)
}
}
return s
}
// 表情转换
func EmojiCode(s string) string {
var ret string
var rs []rune
rs = []rune(s)
for i := 0; i < len(rs); i++ {
if len(string(rs[i])) == 4 {
ret += `[\u` + strconv.FormatInt(int64(rs[i]), 16) + `]`
} else {
ret += string(rs[i])
}
}
return ret
}
// BaseEncode Base64编码
func BaseEncode(s string) string {
data := []byte(s)
dst := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
base64.StdEncoding.Encode(dst, data)
return string(dst)
}
// BaseDecode Base64解码
func BaseDecode(s string) string {
data := []byte(s)
dst := make([]byte, base64.StdEncoding.DecodedLen(len(data)))
n, err := base64.StdEncoding.Decode(dst, data)
if err != nil {
return ""
}
return string(dst[:n])
}

9
pkg/util/memory.go Normal file
View File

@@ -0,0 +1,9 @@
package util
import "unsafe"
func DeepCoyp(s string) string {
b := make([]byte, len(s))
copy(b, s)
return *(*string)(unsafe.Pointer(&b))
}

50
pkg/util/misc.go Normal file
View File

@@ -0,0 +1,50 @@
package util
import (
"github.com/gofrs/uuid"
"math/rand"
"time"
"unsafe"
)
// Ifs 三目运算的函数
func Ifs[T any](a bool, b, c T) T {
if a {
return b
}
return c
}
func GetUUID() (string, error) {
u2, err := uuid.NewV4()
return u2.String(), err
}
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
var src = rand.NewSource(time.Now().UnixNano())
const (
// 6 bits to represent a letter index
letterIdBits = 6
// All 1-bits as many as letterIdBits
letterIdMask = 1<<letterIdBits - 1
letterIdMax = 63 / letterIdBits
)
func RandomStr(n int) string {
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdMax letters!
for i, cache, remain := n-1, src.Int63(), letterIdMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdMax
}
if idx := int(cache & letterIdMask); idx < len(letters) {
b[i] = letters[idx]
i--
}
cache >>= letterIdBits
remain--
}
return *(*string)(unsafe.Pointer(&b))
}