init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
113
webapp-back/common/database.go
Normal file
113
webapp-back/common/database.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
// GetDBPath returns the database path from environment or default.
|
||||
// Exported for use in tests.
|
||||
func GetDBPath() string {
|
||||
dbPath := os.Getenv("DB_PATH")
|
||||
if dbPath == "" {
|
||||
dbPath = "./data/gorm.db"
|
||||
}
|
||||
return dbPath
|
||||
}
|
||||
|
||||
// GetTestDBPath returns the test database path from environment or default.
|
||||
// Exported for use in tests.
|
||||
func GetTestDBPath() string {
|
||||
testDBPath := os.Getenv("TEST_DB_PATH")
|
||||
if testDBPath == "" {
|
||||
testDBPath = "./data/gorm_test.db"
|
||||
}
|
||||
return testDBPath
|
||||
}
|
||||
|
||||
// ensureDir creates the directory for the database file if it doesn't exist
|
||||
func ensureDir(filePath string) error {
|
||||
dir := filepath.Dir(filePath)
|
||||
if dir != "" && dir != "." {
|
||||
return os.MkdirAll(dir, 0750)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Opening a database and save the reference to `Database` struct.
|
||||
func Init() *gorm.DB {
|
||||
dbPath := GetDBPath()
|
||||
|
||||
// Ensure the directory exists
|
||||
if err := ensureDir(dbPath); err != nil {
|
||||
fmt.Println("db err: (Init - create dir) ", err)
|
||||
}
|
||||
|
||||
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
||||
if err != nil {
|
||||
fmt.Println("db err: (Init) ", err)
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
fmt.Println("db err: (Init - get sql.DB) ", err)
|
||||
} else {
|
||||
sqlDB.SetMaxIdleConns(10)
|
||||
}
|
||||
DB = db
|
||||
return DB
|
||||
}
|
||||
|
||||
// This function will create a temporarily database for running testing cases
|
||||
func TestDBInit() *gorm.DB {
|
||||
testDBPath := GetTestDBPath()
|
||||
|
||||
// Ensure the directory exists
|
||||
if err := ensureDir(testDBPath); err != nil {
|
||||
fmt.Println("db err: (TestDBInit - create dir) ", err)
|
||||
}
|
||||
|
||||
test_db, err := gorm.Open(sqlite.Open(testDBPath), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("db err: (TestDBInit) ", err)
|
||||
}
|
||||
sqlDB, err := test_db.DB()
|
||||
if err != nil {
|
||||
fmt.Println("db err: (TestDBInit - get sql.DB) ", err)
|
||||
} else {
|
||||
sqlDB.SetMaxIdleConns(3)
|
||||
}
|
||||
DB = test_db
|
||||
return DB
|
||||
}
|
||||
|
||||
// Delete the database after running testing cases.
|
||||
func TestDBFree(test_db *gorm.DB) error {
|
||||
sqlDB, err := test_db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sqlDB.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
testDBPath := GetTestDBPath()
|
||||
err = os.Remove(testDBPath)
|
||||
return err
|
||||
}
|
||||
|
||||
// Using this function to get a connection, you can create your connection pool here.
|
||||
func GetDB() *gorm.DB {
|
||||
return DB
|
||||
}
|
||||
64
webapp-back/common/database_mysql.go
Normal file
64
webapp-back/common/database_mysql.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gothinkster/golang-gin-realworld-example-app/config"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// InitMySQL initializes MySQL database connection
|
||||
func InitMySQL() *gorm.DB {
|
||||
cfg := config.AppConfig
|
||||
if cfg == nil {
|
||||
log.Fatal("Config not loaded. Please call config.Load() first")
|
||||
}
|
||||
|
||||
// Build DSN
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
cfg.DBUser,
|
||||
cfg.DBPassword,
|
||||
cfg.DBHost,
|
||||
cfg.DBPort,
|
||||
cfg.DBName,
|
||||
)
|
||||
|
||||
// Open database connection
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to connect to MySQL:", err)
|
||||
}
|
||||
|
||||
// Get underlying sql.DB
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to get sql.DB:", err)
|
||||
}
|
||||
|
||||
// Set connection pool parameters
|
||||
sqlDB.SetMaxIdleConns(10)
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||
|
||||
// Test connection
|
||||
if err := sqlDB.Ping(); err != nil {
|
||||
log.Fatal("Failed to ping MySQL:", err)
|
||||
}
|
||||
|
||||
log.Println("✓ MySQL connected successfully")
|
||||
DB = db
|
||||
return DB
|
||||
}
|
||||
|
||||
// InitRedis initializes Redis client (you'll need to add redis package)
|
||||
func InitRedis() {
|
||||
// TODO: Add Redis initialization
|
||||
// You'll need to add github.com/redis/go-redis/v9
|
||||
log.Println("✓ Redis initialization skipped (add go-redis package)")
|
||||
}
|
||||
35
webapp-back/common/test_helpers.go
Normal file
35
webapp-back/common/test_helpers.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// HeaderTokenMock adds authorization token to request header for testing
|
||||
func HeaderTokenMock(req *http.Request, u uint) {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Token %v", GenToken(u)))
|
||||
}
|
||||
|
||||
// ExtractTokenFromHeader extracts JWT token from Authorization header
|
||||
// Used for testing token extraction logic
|
||||
func ExtractTokenFromHeader(authHeader string) string {
|
||||
if len(authHeader) > 6 && authHeader[:6] == "Token " {
|
||||
return authHeader[6:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// VerifyTokenClaims verifies a JWT token and returns claims for testing
|
||||
func VerifyTokenClaims(tokenString string) (jwt.MapClaims, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(JWTSecret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token.Claims.(jwt.MapClaims), nil
|
||||
}
|
||||
368
webapp-back/common/unit_test.go
Normal file
368
webapp-back/common/unit_test.go
Normal file
@@ -0,0 +1,368 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConnectingDatabase(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
db := Init()
|
||||
dbPath := GetDBPath()
|
||||
// Test create & close DB
|
||||
_, err := os.Stat(dbPath)
|
||||
asserts.NoError(err, "Db should exist")
|
||||
sqlDB, err := db.DB()
|
||||
asserts.NoError(err, "Should get sql.DB")
|
||||
asserts.NoError(sqlDB.Ping(), "Db should be able to ping")
|
||||
|
||||
// Test get a connecting from connection pools
|
||||
connection := GetDB()
|
||||
sqlDB, err = connection.DB()
|
||||
asserts.NoError(err, "Should get sql.DB")
|
||||
asserts.NoError(sqlDB.Ping(), "Db should be able to ping")
|
||||
sqlDB.Close()
|
||||
|
||||
// Test DB exceptions
|
||||
os.Chmod(dbPath, 0000)
|
||||
db = Init()
|
||||
sqlDB, err = db.DB()
|
||||
asserts.NoError(err, "Should get sql.DB")
|
||||
asserts.Error(sqlDB.Ping(), "Db should not be able to ping")
|
||||
sqlDB.Close()
|
||||
os.Chmod(dbPath, 0644)
|
||||
}
|
||||
|
||||
func TestConnectingTestDatabase(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
// Test create & close DB
|
||||
db := TestDBInit()
|
||||
testDBPath := GetTestDBPath()
|
||||
_, err := os.Stat(testDBPath)
|
||||
asserts.NoError(err, "Db should exist")
|
||||
sqlDB, err := db.DB()
|
||||
asserts.NoError(err, "Should get sql.DB")
|
||||
asserts.NoError(sqlDB.Ping(), "Db should be able to ping")
|
||||
TestDBFree(db)
|
||||
|
||||
// Test close delete DB
|
||||
db = TestDBInit()
|
||||
TestDBFree(db)
|
||||
_, err = os.Stat(testDBPath)
|
||||
|
||||
asserts.Error(err, "Db should not exist")
|
||||
}
|
||||
|
||||
func TestDBDirCreation(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
// Set a nested path
|
||||
os.Setenv("TEST_DB_PATH", "tmp/nested/test.db")
|
||||
defer os.Unsetenv("TEST_DB_PATH")
|
||||
|
||||
db := TestDBInit()
|
||||
testDBPath := GetTestDBPath()
|
||||
_, err := os.Stat(testDBPath)
|
||||
asserts.NoError(err, "Db should exist in nested directory")
|
||||
TestDBFree(db)
|
||||
|
||||
// Cleanup directory
|
||||
os.RemoveAll("tmp/nested")
|
||||
}
|
||||
|
||||
func TestDBPathOverride(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
customPath := "./custom_test.db"
|
||||
os.Setenv("TEST_DB_PATH", customPath)
|
||||
defer os.Unsetenv("TEST_DB_PATH")
|
||||
|
||||
asserts.Equal(customPath, GetTestDBPath(), "Should use env var")
|
||||
}
|
||||
|
||||
func TestRandString(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
str := RandString(0)
|
||||
asserts.Equal(str, "", "length should be ''")
|
||||
|
||||
str = RandString(10)
|
||||
asserts.Equal(len(str), 10, "length should be 10")
|
||||
for _, ch := range str {
|
||||
asserts.Contains(letters, ch, "char should be a-z|A-Z|0-9")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandInt(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
// Test that RandInt returns a value in valid range
|
||||
val := RandInt()
|
||||
asserts.GreaterOrEqual(val, 0, "RandInt should be >= 0")
|
||||
asserts.Less(val, 1000000, "RandInt should be < 1000000")
|
||||
|
||||
// Test multiple calls return different values (statistically)
|
||||
vals := make(map[int]bool)
|
||||
for i := 0; i < 10; i++ {
|
||||
vals[RandInt()] = true
|
||||
}
|
||||
asserts.Greater(len(vals), 1, "RandInt should return varied values")
|
||||
}
|
||||
|
||||
func TestGenToken(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
token := GenToken(2)
|
||||
|
||||
asserts.IsType(token, string("token"), "token type should be string")
|
||||
asserts.Len(token, 115, "JWT's length should be 115")
|
||||
}
|
||||
|
||||
func TestGenTokenMultipleUsers(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
token1 := GenToken(1)
|
||||
token2 := GenToken(2)
|
||||
token100 := GenToken(100)
|
||||
|
||||
asserts.NotEqual(token1, token2, "Different user IDs should generate different tokens")
|
||||
asserts.NotEqual(token2, token100, "Different user IDs should generate different tokens")
|
||||
// Token length can vary by 1 character due to timestamp changes
|
||||
asserts.GreaterOrEqual(len(token1), 114, "JWT's length should be >= 114 for user 1")
|
||||
asserts.LessOrEqual(len(token1), 120, "JWT's length should be <= 120 for user 1")
|
||||
asserts.GreaterOrEqual(len(token100), 114, "JWT's length should be >= 114 for user 100")
|
||||
asserts.LessOrEqual(len(token100), 120, "JWT's length should be <= 120 for user 100")
|
||||
}
|
||||
|
||||
func TestHeaderTokenMock(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
token := GenToken(5)
|
||||
HeaderTokenMock(req, 5)
|
||||
|
||||
authHeader := req.Header.Get("Authorization")
|
||||
asserts.Equal(fmt.Sprintf("Token %s", token), authHeader, "Authorization header should be set correctly")
|
||||
}
|
||||
|
||||
func TestExtractTokenFromHeader(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
token := "valid.jwt.token"
|
||||
header := fmt.Sprintf("Token %s", token)
|
||||
|
||||
extracted := ExtractTokenFromHeader(header)
|
||||
asserts.Equal(token, extracted, "Should extract token from header")
|
||||
|
||||
invalidHeader := "Bearer " + token
|
||||
extracted = ExtractTokenFromHeader(invalidHeader)
|
||||
asserts.Empty(extracted, "Should return empty for non-Token header")
|
||||
|
||||
shortHeader := "Token"
|
||||
extracted = ExtractTokenFromHeader(shortHeader)
|
||||
asserts.Empty(extracted, "Should return empty for short header")
|
||||
}
|
||||
|
||||
func TestVerifyTokenClaims(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
// Test valid token
|
||||
userID := uint(123)
|
||||
token := GenToken(userID)
|
||||
claims, err := VerifyTokenClaims(token)
|
||||
asserts.NoError(err, "VerifyTokenClaims should not error for valid token")
|
||||
asserts.Equal(float64(userID), claims["id"], "Claims should contain correct user ID")
|
||||
|
||||
// Test invalid token
|
||||
_, err = VerifyTokenClaims("invalid.token.string")
|
||||
asserts.Error(err, "VerifyTokenClaims should error for invalid token")
|
||||
}
|
||||
|
||||
func TestNewValidatorError(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
type Login struct {
|
||||
Username string `form:"username" json:"username" binding:"required,alphanum,min=4,max=255"`
|
||||
Password string `form:"password" json:"password" binding:"required,min=8,max=255"`
|
||||
}
|
||||
|
||||
var requestTests = []struct {
|
||||
bodyData string
|
||||
expectedCode int
|
||||
responseRegexg string
|
||||
msg string
|
||||
}{
|
||||
{
|
||||
`{"username": "wangzitian0","password": "0123456789"}`,
|
||||
http.StatusOK,
|
||||
`{"status":"you are logged in"}`,
|
||||
"valid data and should return StatusCreated",
|
||||
},
|
||||
{
|
||||
`{"username": "wangzitian0","password": "01234567866"}`,
|
||||
http.StatusUnauthorized,
|
||||
`{"errors":{"user":"wrong username or password"}}`,
|
||||
"wrong login status should return StatusUnauthorized",
|
||||
},
|
||||
{
|
||||
`{"username": "wangzitian0","password": "0122"}`,
|
||||
http.StatusUnprocessableEntity,
|
||||
`{"errors":{"Password":"{min: 8}"}}`,
|
||||
"invalid password of too short and should return StatusUnprocessableEntity",
|
||||
},
|
||||
{
|
||||
`{"username": "_wangzitian0","password": "0123456789"}`,
|
||||
http.StatusUnprocessableEntity,
|
||||
`{"errors":{"Username":"{key: alphanum}"}}`,
|
||||
"invalid username of non alphanum and should return StatusUnprocessableEntity",
|
||||
},
|
||||
}
|
||||
|
||||
r := gin.Default()
|
||||
|
||||
r.POST("/login", func(c *gin.Context) {
|
||||
var json Login
|
||||
if err := Bind(c, &json); err == nil {
|
||||
if json.Username == "wangzitian0" && json.Password == "0123456789" {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
} else {
|
||||
c.JSON(http.StatusUnauthorized, NewError("user", errors.New("wrong username or password")))
|
||||
}
|
||||
} else {
|
||||
c.JSON(http.StatusUnprocessableEntity, NewValidatorError(err))
|
||||
}
|
||||
})
|
||||
|
||||
for _, testData := range requestTests {
|
||||
bodyData := testData.bodyData
|
||||
req, err := http.NewRequest("POST", "/login", bytes.NewBufferString(bodyData))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
asserts.NoError(err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
asserts.Equal(testData.expectedCode, w.Code, "Response Status - "+testData.msg)
|
||||
asserts.Regexp(testData.responseRegexg, w.Body.String(), "Response Content - "+testData.msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewError(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
db := TestDBInit()
|
||||
defer TestDBFree(db)
|
||||
|
||||
type NonExistentTable struct {
|
||||
Field string
|
||||
}
|
||||
// db.AutoMigrate(NonExistentTable{}) // Intentionally skipped to cause error
|
||||
|
||||
err := db.Find(&NonExistentTable{Field: "value"}).Error
|
||||
if err == nil {
|
||||
err = errors.New("no such table: non_existent_tables")
|
||||
}
|
||||
|
||||
commonError := NewError("database", err)
|
||||
assert.IsType(commonError, commonError, "commonError should have right type")
|
||||
// The exact error message might vary by driver, checking key presence is safer, but keeping original assertion style
|
||||
assert.Contains(commonError.Errors, "database", "commonError should contain database key")
|
||||
}
|
||||
|
||||
func TestDatabaseDirCreation(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
// Test directory creation in Init
|
||||
origDBPath := os.Getenv("DB_PATH")
|
||||
defer os.Setenv("DB_PATH", origDBPath)
|
||||
|
||||
// Create a temp dir path
|
||||
tempDir := "./tmp/test_nested/db"
|
||||
os.Setenv("DB_PATH", tempDir+"/test.db")
|
||||
|
||||
// Clean up before test
|
||||
os.RemoveAll("./tmp/test_nested")
|
||||
|
||||
// Init should create the directory
|
||||
|
||||
db := Init()
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
|
||||
asserts.NoError(err, "Should get sql.DB")
|
||||
|
||||
asserts.NoError(sqlDB.Ping(), "DB should be created in nested directory")
|
||||
|
||||
// Clean up after test
|
||||
|
||||
sqlDB.Close()
|
||||
|
||||
os.RemoveAll("./tmp/test_nested")
|
||||
|
||||
}
|
||||
|
||||
func TestDBInitDirCreation(t *testing.T) {
|
||||
|
||||
asserts := assert.New(t)
|
||||
|
||||
// Test directory creation in TestDBInit
|
||||
|
||||
origTestDBPath := os.Getenv("TEST_DB_PATH")
|
||||
|
||||
defer os.Setenv("TEST_DB_PATH", origTestDBPath)
|
||||
|
||||
// Create a temp dir path
|
||||
|
||||
tempDir := "./tmp/test_nested_testdb"
|
||||
|
||||
os.Setenv("TEST_DB_PATH", tempDir+"/test.db")
|
||||
|
||||
// Clean up before test
|
||||
|
||||
os.RemoveAll(tempDir)
|
||||
|
||||
// TestDBInit should create the directory
|
||||
|
||||
db := TestDBInit()
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
|
||||
asserts.NoError(err, "Should get sql.DB")
|
||||
|
||||
asserts.NoError(sqlDB.Ping(), "Test DB should be created in nested directory")
|
||||
|
||||
// Clean up after test
|
||||
|
||||
TestDBFree(db)
|
||||
|
||||
os.RemoveAll(tempDir)
|
||||
|
||||
}
|
||||
|
||||
func TestDatabaseWithCurrentDirectory(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
|
||||
// Test with simple filename (no directory)
|
||||
origDBPath := os.Getenv("DB_PATH")
|
||||
defer os.Setenv("DB_PATH", origDBPath)
|
||||
|
||||
os.Setenv("DB_PATH", "test_simple.db")
|
||||
|
||||
// Init should work without directory creation
|
||||
db := Init()
|
||||
sqlDB, err := db.DB()
|
||||
|
||||
asserts.NoError(err, "Should get sql.DB")
|
||||
asserts.NoError(sqlDB.Ping(), "DB should be created in current directory")
|
||||
|
||||
// Clean up
|
||||
sqlDB.Close()
|
||||
os.Remove("test_simple.db")
|
||||
}
|
||||
99
webapp-back/common/utils.go
Normal file
99
webapp-back/common/utils.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// Common tools and helper functions
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
|
||||
// A helper function to generate random string
|
||||
func RandString(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
randIdx, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b[i] = letters[randIdx.Int64()]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// A helper function to generate random int
|
||||
func RandInt() int {
|
||||
randNum, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return int(randNum.Int64())
|
||||
}
|
||||
|
||||
// Keep this two config private, it should not expose to open source
|
||||
const JWTSecret = "A String Very Very Very Strong!!@##$!@#$" // #nosec G101
|
||||
const RandomPassword = "A String Very Very Very Random!!@##$!@#4" // #nosec G101
|
||||
|
||||
// A Util function to generate jwt_token which can be used in the request header
|
||||
func GenToken(id uint) string {
|
||||
jwt_token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"id": id,
|
||||
"exp": time.Now().Add(time.Hour * 24).Unix(),
|
||||
})
|
||||
// Sign and get the complete encoded token as a string
|
||||
token, err := jwt_token.SignedString([]byte(JWTSecret))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign JWT token for id %d: %v\n", id, err)
|
||||
return ""
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// My own Error type that will help return my customized Error info
|
||||
//
|
||||
// {"database": {"hello":"no such table", error: "not_exists"}}
|
||||
type CommonError struct {
|
||||
Errors map[string]interface{} `json:"errors"`
|
||||
}
|
||||
|
||||
// To handle the error returned by c.Bind in gin framework
|
||||
// https://github.com/go-playground/validator/blob/v9/_examples/translations/main.go
|
||||
func NewValidatorError(err error) CommonError {
|
||||
res := CommonError{}
|
||||
res.Errors = make(map[string]interface{})
|
||||
errs := err.(validator.ValidationErrors)
|
||||
for _, v := range errs {
|
||||
// can translate each error one at a time.
|
||||
//fmt.Println("gg",v.NameNamespace)
|
||||
if v.Param() != "" {
|
||||
res.Errors[v.Field()] = fmt.Sprintf("{%v: %v}", v.Tag(), v.Param())
|
||||
} else {
|
||||
res.Errors[v.Field()] = fmt.Sprintf("{key: %v}", v.Tag())
|
||||
}
|
||||
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Wrap the error info in an object
|
||||
func NewError(key string, err error) CommonError {
|
||||
res := CommonError{}
|
||||
res.Errors = make(map[string]interface{})
|
||||
res.Errors[key] = err.Error()
|
||||
return res
|
||||
}
|
||||
|
||||
// Changed the c.MustBindWith() -> c.ShouldBindWith().
|
||||
// I don't want to auto return 400 when error happened.
|
||||
// origin function is here: https://github.com/gin-gonic/gin/blob/master/context.go
|
||||
func Bind(c *gin.Context, obj interface{}) error {
|
||||
b := binding.Default(c.Request.Method, c.ContentType())
|
||||
return c.ShouldBindWith(obj, b)
|
||||
}
|
||||
Reference in New Issue
Block a user