first commit
This commit is contained in:
33
internal/pkg/config/config.go
Normal file
33
internal/pkg/config/config.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
DBDriver string `mapstructure:"DB_DRIVER"`
|
||||
DBSource string `mapstructure:"DB_SOURCE"`
|
||||
ServerAddress string `mapstructure:"SERVER_ADDRESS"`
|
||||
TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"`
|
||||
AccessTokenDuration time.Duration `mapstructure:"ACCESS_TOKEN_DURATION"`
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
viper.AddConfigPath(path)
|
||||
viper.SetConfigName("app")
|
||||
viper.SetConfigType("env")
|
||||
|
||||
// 自动识别加载环境变量
|
||||
viper.AutomaticEnv()
|
||||
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
err = viper.Unmarshal(&config)
|
||||
return &config, err
|
||||
}
|
||||
24
internal/pkg/convert/convert.go
Normal file
24
internal/pkg/convert/convert.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ConvertHLS(savePath, filePath string) error {
|
||||
binary, err := exec.LookPath("ffmpeg")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ffmpeg -i web/statics/git.mp4 -profile:v baseline -level 3.0 -s 1920x1080 -start_number 0 -hls_time 10 -hls_list_size 0 -hls_segment_filename %d.ts -f hls web/statics/git.m3u8
|
||||
command := "-i " + filePath + " -profile:v baseline -level 3.0 -s 1920x1080 -start_number 0 -hls_time 10 -hls_list_size 0 -f hls " + savePath + "index.m3u8"
|
||||
args := strings.Split(command, " ")
|
||||
cmd := exec.Command(binary, args...)
|
||||
_, err = cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
14
internal/pkg/fileutil/fileutil.go
Normal file
14
internal/pkg/fileutil/fileutil.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package fileutil
|
||||
|
||||
import "os"
|
||||
|
||||
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
|
||||
}
|
||||
33
internal/pkg/logger/logger.go
Normal file
33
internal/pkg/logger/logger.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"github.com/natefinch/lumberjack"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
var Logger *zap.SugaredLogger
|
||||
|
||||
func NewLogger() {
|
||||
core := zapcore.NewCore(getEncoder(), getLogWriter(), zapcore.DebugLevel)
|
||||
logger := zap.New(core, zap.AddCaller())
|
||||
Logger = logger.Sugar()
|
||||
}
|
||||
|
||||
func getEncoder() zapcore.Encoder {
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
return zapcore.NewConsoleEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
func getLogWriter() zapcore.WriteSyncer {
|
||||
lumberJackLogger := &lumberjack.Logger{
|
||||
Filename: "./log/run.log", // 日志文件的位置
|
||||
MaxSize: 10, // 在进行切割之前,日志文件的最大大小(以MB为单位)
|
||||
MaxBackups: 100, // 保留旧文件的最大个数
|
||||
MaxAge: 365, // 保留旧文件的最大天数
|
||||
Compress: false, // 是否压缩/归档旧文件
|
||||
}
|
||||
return zapcore.AddSync(lumberJackLogger)
|
||||
}
|
||||
79
internal/pkg/password/password.go
Normal file
79
internal/pkg/password/password.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package password
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
// ******************** scrypt ********************
|
||||
|
||||
// ScryptHashPassword scrypt 加密
|
||||
// password 原始密码
|
||||
func ScryptHashPassword(password string) (string, error) {
|
||||
// example for making salt - https://play.golang.org/p/_Aw6WeWC42I
|
||||
salt := make([]byte, 32)
|
||||
_, err := rand.Read(salt)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// using recommended cost parameters from - https://godoc.org/golang.org/x/crypto/scrypt
|
||||
shash, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// return hex-encoded string with salt appended to password
|
||||
hashedPW := fmt.Sprintf("%s.%s", hex.EncodeToString(shash), hex.EncodeToString(salt))
|
||||
|
||||
return hashedPW, nil
|
||||
}
|
||||
|
||||
// ScryptComparePassword 判断密码是否正确
|
||||
// storedPassword 加密密码
|
||||
// suppliedPassword 原始密码
|
||||
func ScryptComparePassword(storedPassword string, suppliedPassword string) error {
|
||||
pwsalt := strings.Split(storedPassword, ".")
|
||||
|
||||
// check supplied password salted with hash
|
||||
salt, err := hex.DecodeString(pwsalt[1])
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to verify user password")
|
||||
}
|
||||
|
||||
shash, err := scrypt.Key([]byte(suppliedPassword), salt, 32768, 8, 1, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hex.EncodeToString(shash) == pwsalt[0] {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("password error")
|
||||
}
|
||||
|
||||
// ******************** bcrypt ********************
|
||||
|
||||
// BcryptHashPassword bcrypt 加密
|
||||
// password 原始密码
|
||||
func BcryptHashPassword(password string) (string, error) {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
// BcryptComparePassword 判断密码是否正确
|
||||
// hashedPassword 加密密码
|
||||
// password 原始密码
|
||||
func BcryptComparePassword(hashedPassword string, password string) error {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
}
|
||||
55
internal/pkg/token/jwt_maker.go
Normal file
55
internal/pkg/token/jwt_maker.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
const minSecretKeySize = 32
|
||||
|
||||
// JWTMaker JSON Web Token
|
||||
type JWTMaker struct {
|
||||
secretKey string
|
||||
}
|
||||
|
||||
// NewJWTMaker 创建一个新的JWTMaker
|
||||
func NewJWTMaker(secretKey string) (Maker, error) {
|
||||
if len(secretKey) < minSecretKeySize {
|
||||
return nil, fmt.Errorf("invalid key size: must be at least %d characters", minSecretKeySize)
|
||||
}
|
||||
return &JWTMaker{secretKey}, nil
|
||||
}
|
||||
|
||||
// CreateToken 根据用户名和时间创建一个新的token
|
||||
func (maker *JWTMaker) CreateToken(id string, username string, duration time.Duration) (string, *Payload, error) {
|
||||
payload := NewPayload(id, username, duration)
|
||||
|
||||
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
|
||||
token, err := jwtToken.SignedString([]byte(maker.secretKey))
|
||||
return token, payload, err
|
||||
}
|
||||
|
||||
// VerifyToken checks if the token is valid or not
|
||||
func (maker *JWTMaker) VerifyToken(t string) (*Payload, error) {
|
||||
keyFunc := func(tk *jwt.Token) (interface{}, error) {
|
||||
_, ok := tk.Method.(*jwt.SigningMethodHMAC)
|
||||
if !ok {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
return []byte(maker.secretKey), nil
|
||||
}
|
||||
|
||||
jwtToken, err := jwt.ParseWithClaims(t, &Payload{}, keyFunc)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
payload, ok := jwtToken.Claims.(*Payload)
|
||||
if !ok {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
14
internal/pkg/token/maker.go
Normal file
14
internal/pkg/token/maker.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Maker 管理token的接口定义
|
||||
type Maker interface {
|
||||
// CreateToken 根据用户名和时间创建一个新的token
|
||||
CreateToken(id string, username string, duration time.Duration) (string, *Payload, error)
|
||||
|
||||
// VerifyToken 校验token是否正确
|
||||
VerifyToken(token string) (*Payload, error)
|
||||
}
|
||||
53
internal/pkg/token/paseto_maker.go
Normal file
53
internal/pkg/token/paseto_maker.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aead/chacha20poly1305"
|
||||
"github.com/o1egl/paseto"
|
||||
)
|
||||
|
||||
// PasetoMaker is a PASETO token maker
|
||||
type PasetoMaker struct {
|
||||
paseto *paseto.V2
|
||||
symmetricKey []byte
|
||||
}
|
||||
|
||||
// NewPasetoMaker creates a new PasetoMaker
|
||||
func NewPasetoMaker(symmetricKey string) (Maker, error) {
|
||||
if len(symmetricKey) != chacha20poly1305.KeySize {
|
||||
return nil, fmt.Errorf("invalid key size: must be exactly %d characters", chacha20poly1305.KeySize)
|
||||
}
|
||||
|
||||
maker := &PasetoMaker{
|
||||
paseto: paseto.NewV2(),
|
||||
symmetricKey: []byte(symmetricKey),
|
||||
}
|
||||
|
||||
return maker, nil
|
||||
}
|
||||
|
||||
// CreateToken creates a new token for a specific username and duration
|
||||
func (maker *PasetoMaker) CreateToken(id string, username string, duration time.Duration) (string, *Payload, error) {
|
||||
payload := NewPayload(id, username, duration)
|
||||
token, err := maker.paseto.Encrypt(maker.symmetricKey, payload, nil)
|
||||
return token, payload, err
|
||||
}
|
||||
|
||||
// VerifyToken checks if the token is valid or not
|
||||
func (maker *PasetoMaker) VerifyToken(t string) (*Payload, error) {
|
||||
payload := &Payload{}
|
||||
|
||||
err := maker.paseto.Decrypt(t, maker.symmetricKey, payload, nil)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
err = payload.Valid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
43
internal/pkg/token/payload.go
Normal file
43
internal/pkg/token/payload.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// Different types of error returned by the VerifyToken function
|
||||
var (
|
||||
ErrInvalidToken = errors.New("token is invalid")
|
||||
ErrExpiredToken = errors.New("token has expired")
|
||||
)
|
||||
|
||||
// Payload contains the payload data of the token
|
||||
type Payload struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// NewPayload creates a new token payload with a specific username and duration
|
||||
func NewPayload(id string, username string, duration time.Duration) *Payload {
|
||||
payload := &Payload{
|
||||
ID: id,
|
||||
Username: username,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(duration)), // 过期时间
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()), // 签发时间
|
||||
NotBefore: jwt.NewNumericDate(time.Now()), // 生效时间
|
||||
},
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
// Valid checks if the token payload is valid or not
|
||||
func (payload *Payload) Valid() error {
|
||||
if time.Now().After(payload.ExpiresAt.Time) {
|
||||
return ErrExpiredToken
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user