Initial commit

This commit is contained in:
yan.y
2024-03-27 23:25:08 +08:00
commit 0884384e91
127 changed files with 9353 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
package database
import (
"fmt"
"strings"
)
// Init initialize mysql config file
func Init() error {
var cover string
if IsExist() {
fmt.Println("Your database config is initialized , reinitialized database will cover your database config.")
fmt.Println("Did you want to do it ? [Y/N]")
fmt.Scanln(&cover)
switch strings.ToUpper(cover) {
case "Y":
case "N":
return nil
default:
}
}
err := Remove()
if err != nil {
return err
}
err = initConfig()
if err != nil {
return err
}
fmt.Println("mysql initialized finished!")
return nil
}
func initConfig() error {
var dbPath, dbName, dbUsername, dbPwd, dbLogZap, dbLogMod string
var maxIdleConns, maxOpenConns int
fmt.Println("Please input your database path: ")
fmt.Printf("Database path default is '%s'\n", CONFIG.Path)
fmt.Scanln(&dbPath)
if dbPath != "" {
CONFIG.Path = dbPath
}
fmt.Println("Please input your database db-name: ")
fmt.Printf("Database db-name default is '%s'\n", CONFIG.DbName)
fmt.Scanln(&dbName)
if dbName != "" {
CONFIG.DbName = dbName
}
fmt.Println("Please input your database username: ")
fmt.Printf("Database username default is '%s'\n", CONFIG.Username)
fmt.Scanln(&dbUsername)
if dbUsername != "" {
CONFIG.Username = dbUsername
}
fmt.Println("Please input your database password: ")
fmt.Printf("Database password default is '%s'\n", CONFIG.Password)
fmt.Scanln(&dbPwd)
if dbPwd != "" {
CONFIG.Password = dbPwd
}
fmt.Println("Please input your database log zap: ")
fmt.Printf("Database log zap default is '%s'\n", CONFIG.LogZap)
fmt.Scanln(&dbLogZap)
if dbLogZap != "" {
CONFIG.LogZap = dbLogZap
}
fmt.Println("Please input your database log mode: [Y/N]")
fmt.Println("Database log mode default is N")
fmt.Scanln(&dbLogMod)
switch strings.ToUpper(dbLogMod) {
case "Y":
CONFIG.LogMode = true
case "N":
CONFIG.LogMode = false
default:
CONFIG.LogMode = false
}
fmt.Println("Please input your database max idle conns: ")
fmt.Scanln(&maxIdleConns)
if maxIdleConns > 0 {
CONFIG.MaxIdleConns = maxIdleConns
}
fmt.Println("Please input your database max open conns: ")
fmt.Scanln(&maxOpenConns)
if maxOpenConns > 0 {
CONFIG.MaxOpenConns = maxOpenConns
}
if Instance() == nil {
return ErrDatabaseInit
}
return nil
}
+102
View File
@@ -0,0 +1,102 @@
package database
import (
"encoding/json"
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"pet-house.com/core/g"
"pet-house.com/core/server/viper_server"
)
var CONFIG = Mysql{
Path: "127.0.0.1:3306",
Config: "charset=utf8mb4&parseTime=True&loc=Local",
DbName: "iris-admin",
Username: "root",
Password: "",
MaxIdleConns: 0,
MaxOpenConns: 0,
LogMode: false,
LogZap: "error",
}
type Mysql struct {
Path string `mapstructure:"path" json:"path" yaml:"path"`
Config string `mapstructure:"config" json:"config" yaml:"config"`
DbName string `mapstructure:"db-name" json:"db-name" yaml:"db-name"`
Username string `mapstructure:"username" json:"username" yaml:"username"`
Password string `mapstructure:"password" json:"password" yaml:"password"`
MaxIdleConns int `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns"`
MaxOpenConns int `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns"`
LogMode bool `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"`
LogZap string `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"` //silent,error,warn,info,zap
}
// Dsn return mysql dsn
func (m *Mysql) Dsn() string {
return fmt.Sprintf("%s%s?%s", m.BaseDsn(), m.DbName, m.Config)
}
// Dsn return
func (m *Mysql) BaseDsn() string {
return fmt.Sprintf("%s:%s@tcp(%s)/", m.Username, m.Password, m.Path)
}
// IsExist config file is exist
func IsExist() bool {
return getViperConfig().IsFileExist()
}
// Remove remove config file
func Remove() error {
return getViperConfig().Remove()
}
// Recover
func Recover() error {
b, err := json.Marshal(CONFIG)
if err != nil {
return err
}
return getViperConfig().Recover(b)
}
// getViperConfig get viper config
func getViperConfig() viper_server.ViperConfig {
configName := "mysql"
mxIdleConns := fmt.Sprintf("%d", CONFIG.MaxIdleConns)
mxOpenConns := fmt.Sprintf("%d", CONFIG.MaxOpenConns)
logMode := fmt.Sprintf("%t", CONFIG.LogMode)
return viper_server.ViperConfig{
Debug: true,
Directory: g.ConfigDir,
Name: configName,
Type: g.ConfigType,
Watch: func(vi *viper.Viper) error {
if err := vi.Unmarshal(&CONFIG); err != nil {
return fmt.Errorf("get Unarshal error: %v", err)
}
// watch config file change
vi.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
})
vi.WatchConfig()
return nil
},
//
Default: []byte(`
{
"path": "` + CONFIG.Path + `",
"config": "` + CONFIG.Config + `",
"db-name": "` + CONFIG.DbName + `",
"username": "` + CONFIG.Username + `",
"password": "` + CONFIG.Password + `",
"max-idle-conns": ` + mxIdleConns + `,
"max-open-conns": ` + mxOpenConns + `,
"log-mode": ` + logMode + `,
"log-zap": "` + CONFIG.LogZap + `"
}`),
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"config": "charset=utf8mb4\u0026parseTime=True\u0026loc=Local",
"db-name": "iris-admin",
"log-mode": false,
"log-zap": "error",
"max-idle-conns": 0,
"max-open-conns": 0,
"password": "",
"path": "127.0.0.1:3306",
"username": "root"
}
+11
View File
@@ -0,0 +1,11 @@
{
"director": "logs",
"encode-level": "LowercaseColorLevelEncoder",
"format": "console",
"level": "debug",
"link-name": "latest_log",
"log-in-console": false,
"prefix": "[IRIS-ADMIN]",
"show-line": true,
"stacktrace-key": "stacktrace"
}
+129
View File
@@ -0,0 +1,129 @@
package database
import (
"database/sql"
"errors"
"fmt"
"sync"
"pet-house.com/core/server/viper_server"
"pet-house.com/core/server/zap_server"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var ErrDatabaseInit = errors.New("database initialize fail")
var (
once sync.Once
db *gorm.DB
)
// init
func init() {
viper_server.Init(getViperConfig())
}
// Instance
func Instance() *gorm.DB {
once.Do(func() {
db = gormMysql()
})
return db
}
// gormMysql get *gorm.DB
func gormMysql() *gorm.DB {
if CONFIG.DbName == "" {
fmt.Println("config dbname is empty")
return nil
}
/*err := createTable(CONFIG.BaseDsn(), "mysql", CONFIG.DbName)
if err != nil {
fmt.Printf("create database %s is failed %v \n", CONFIG.DbName, err)
return nil
}*/
mysqlConfig := mysql.Config{
DSN: CONFIG.Dsn(),
DefaultStringSize: 191,
DisableDatetimePrecision: true,
DontSupportRenameIndex: true,
DontSupportRenameColumn: true,
SkipInitializeWithVersion: false,
}
if db, err := gorm.Open(mysql.New(mysqlConfig), gormConfig(CONFIG.LogMode)); err != nil {
fmt.Printf("open mysql is failed %v \n", err)
return nil
} else {
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(CONFIG.MaxIdleConns)
sqlDB.SetMaxOpenConns(CONFIG.MaxOpenConns)
return db
}
}
// gormConfig get gorm config
func gormConfig(mod bool) *gorm.Config {
var config = &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true}
switch CONFIG.LogZap {
case "silent", "Silent":
config.Logger = Default.LogMode(logger.Silent)
case "error", "Error":
config.Logger = Default.LogMode(logger.Error)
case "warn", "Warn":
config.Logger = Default.LogMode(logger.Warn)
case "info", "Info":
config.Logger = Default.LogMode(logger.Info)
case "zap", "Zap":
config.Logger = Default.LogMode(logger.Info)
default:
if mod {
config.Logger = Default.LogMode(logger.Info)
break
}
config.Logger = Default.LogMode(logger.Silent)
}
return config
}
// createTable create database(mysql)
func createTable(dsn, driver, dbName string) error {
createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", dbName)
db, err := sql.Open(driver, dsn)
if err != nil {
return err
}
defer func(db *sql.DB) {
_ = db.Close()
}(db)
if err = db.Ping(); err != nil {
return err
}
_, err = db.Exec(createSql)
return err
}
func DorpDB(dsn, driver, dbName string) error {
execSql := fmt.Sprintf("DROP database if exists `%s`;", dbName)
db, err := sql.Open(driver, dsn)
if err != nil {
return err
}
if db == nil {
return errors.New("db is nil")
}
defer func(db *sql.DB) {
_ = db.Close()
}(db)
if err = db.Ping(); err != nil {
return err
}
_, err = db.Exec(execSql)
if err != nil {
return err
}
zap_server.ZAPLOG.Debug(execSql)
return nil
}
+35
View File
@@ -0,0 +1,35 @@
package database
import (
"reflect"
"testing"
"gorm.io/gorm/logger"
)
func TestInstanceMysql(t *testing.T) {
t.Run("test instance mysql", func(t *testing.T) {
mysql := Instance()
if mysql == nil {
t.Error("mysql instance is nil")
}
})
}
func TestGormMysql(t *testing.T) {
t.Run("test gorm mysql", func(t *testing.T) {
gormDb := gormMysql()
if gormDb == nil {
t.Error("gorm db is nil")
}
})
}
func TestGormConfig(t *testing.T) {
t.Run("test gorm config", func(t *testing.T) {
gormConfig := gormConfig(false)
if !reflect.DeepEqual(gormConfig.Logger, Default.LogMode(logger.Error)) {
t.Errorf("gorm config logger want %+v but get %+v", Default.LogMode(logger.Error), gormConfig.Logger)
}
})
}
+187
View File
@@ -0,0 +1,187 @@
package database
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"time"
"go.uber.org/zap"
"gorm.io/gorm/logger"
"gorm.io/gorm/utils"
"pet-house.com/core/server/zap_server"
)
// writer
type writer interface {
Printf(string, ...interface{})
}
// config
type config struct {
SlowThreshold time.Duration
Colorful bool
LogLevel logger.LogLevel
}
var (
Discard = New(log.New(ioutil.Discard, "", log.LstdFlags), config{})
Default = New(log.New(os.Stdout, "\r\n", log.LstdFlags), config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Warn,
Colorful: true,
})
Recorder = traceRecorder{Interface: Default, BeginAt: time.Now()}
)
// New
func New(writer writer, config config) logger.Interface {
var (
infoStr = "%s\n[info] "
warnStr = "%s\n[warn] "
errStr = "%s\n[error] "
traceStr = "%s\n[%.3fms] [rows:%v] %s"
traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s"
traceErrStr = "%s %s\n[%.3fms] [rows:%v] %s"
)
if config.Colorful {
infoStr = logger.Green + "%s\n" + logger.Reset + logger.Green + "[info] " + logger.Reset
warnStr = logger.BlueBold + "%s\n" + logger.Reset + logger.Magenta + "[warn] " + logger.Reset
errStr = logger.Magenta + "%s\n" + logger.Reset + logger.Red + "[error] " + logger.Reset
traceStr = logger.Green + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s"
traceWarnStr = logger.Green + "%s " + logger.Yellow + "%s\n" + logger.Reset + logger.RedBold + "[%.3fms] " + logger.Yellow + "[rows:%v]" + logger.Magenta + " %s" + logger.Reset
traceErrStr = logger.RedBold + "%s " + logger.MagentaBold + "%s\n" + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s"
}
return &customLogger{
writer: writer,
config: config,
infoStr: infoStr,
warnStr: warnStr,
errStr: errStr,
traceStr: traceStr,
traceWarnStr: traceWarnStr,
traceErrStr: traceErrStr,
}
}
// customLogger
type customLogger struct {
writer
config
infoStr, warnStr, errStr string
traceStr, traceErrStr, traceWarnStr string
}
// LogMode
func (c *customLogger) LogMode(level logger.LogLevel) logger.Interface {
newLogger := *c
newLogger.LogLevel = level
return &newLogger
}
// Info print info
func (c *customLogger) Info(ctx context.Context, message string, data ...interface{}) {
if c.LogLevel >= logger.Info {
c.Printf(c.infoStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
// Warn print warn messages
func (c *customLogger) Warn(ctx context.Context, message string, data ...interface{}) {
if c.LogLevel >= logger.Warn {
c.Printf(c.warnStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
// Error print error messages
func (c *customLogger) Error(ctx context.Context, message string, data ...interface{}) {
if c.LogLevel >= logger.Error {
c.Printf(c.errStr+message, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
// Trace print sql message
func (c *customLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
if c.LogLevel > 0 {
elapsed := time.Since(begin)
switch {
case err != nil && c.LogLevel >= logger.Error:
sql, rows := fc()
if rows == -1 {
c.Printf(c.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
} else {
c.Printf(c.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql)
}
case elapsed > c.SlowThreshold && c.SlowThreshold != 0 && c.LogLevel >= logger.Warn:
sql, rows := fc()
slowLog := fmt.Sprintf("SLOW SQL >= %v", c.SlowThreshold)
if rows == -1 {
c.Printf(c.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
} else {
c.Printf(c.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
}
case c.LogLevel >= logger.Info:
sql, rows := fc()
if rows == -1 {
c.Printf(c.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql)
} else {
c.Printf(c.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql)
}
}
}
}
// Printf
func (c *customLogger) Printf(message string, data ...interface{}) {
if CONFIG.LogZap != "" {
switch len(data) {
case 0:
zap_server.ZAPLOG.Info(message)
case 1:
zap_server.ZAPLOG.Info("gorm", zap.Any("src", data[0]))
case 2:
zap_server.ZAPLOG.Info("gorm", zap.Any("src", data[0]), zap.Any("duration", data[1]))
case 3:
zap_server.ZAPLOG.Info("gorm", zap.Any("src", data[0]), zap.Any("duration", data[1]), zap.Any("rows", data[2]))
case 4:
zap_server.ZAPLOG.Info("gorm", zap.Any("src", data[0]), zap.Any("duration", data[1]), zap.Any("rows", data[2]), zap.Any("sql", data[3]))
}
return
}
switch len(data) {
case 0:
c.writer.Printf(message, "")
case 1:
c.writer.Printf(message, data[0])
case 2:
c.writer.Printf(message, data[0], data[1])
case 3:
c.writer.Printf(message, data[0], data[1], data[2])
case 4:
c.writer.Printf(message, data[0], data[1], data[2], data[3])
case 5:
c.writer.Printf(message, data[0], data[1], data[2], data[3], data[4])
}
}
type traceRecorder struct {
logger.Interface
BeginAt time.Time
SQL string
RowsAffected int64
Err error
}
func (t traceRecorder) New() *traceRecorder {
return &traceRecorder{Interface: t.Interface, BeginAt: time.Now()}
}
func (t *traceRecorder) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
t.BeginAt = begin
t.SQL, t.RowsAffected = fc()
t.Err = err
}
+38
View File
@@ -0,0 +1,38 @@
package database
import (
_ "embed"
"fmt"
"os"
"pet-house.com/core/server/database/scope"
"testing"
"time"
)
type otheraccounts struct {
Id int64
Accid string
Apptypeid string
Unionid string
Openid string
Usertype string
}
func TestMain(m *testing.M) {
CONFIG.DbName = "pikauser"
CONFIG.Path = "172.31.65.221:3306"
CONFIG.Password = "qC#5Lp%9AF=6BmR5Ny!6aaabbb"
CONFIG.Username = "pikauser_dev"
code := m.Run()
for i := 1; i <= 10; i++ {
var account otheraccounts
Instance().Table("otheraccounts").Model(&otheraccounts{}).Scopes(scope.PaginateScope(i, 1, "desc", "createtime")).Where(" apptypeid = ?", "100001").Find(&account)
fmt.Println(account.Openid, account.Id)
time.Sleep(time.Second)
}
os.Exit(code)
}
+113
View File
@@ -0,0 +1,113 @@
package orm
import (
"errors"
"strings"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"pet-house.com/core/server/database/scope"
"pet-house.com/core/server/web/web_iris/validate"
"pet-house.com/core/server/zap_server"
)
type ErrMsg struct {
Code int64 `json:"code"`
Msg string `json:"message"`
}
var (
ErrParamValidate = errors.New("参数验证失败")
ErrPaginateParam = errors.New("分页查询参数缺失")
ErrUnSupportFramework = errors.New("不支持的框架")
)
// Model
type Model struct {
Id uint `json:"id" uri:"id" form:"id" param:"id"`
UpdatedAt string `json:"updatedAt" uri:"updatedAt" form:"updatedAt" param:"updatedAt"`
CreatedAt string `json:"createdAt" uri:"createdAt" form:"createdAt" param:"createdAt"`
DeletedAt string `json:"deletedAt" uri:"deletedAt" form:"deletedAt" param:"deletedAt"`
}
// ReqId the struct has used to get id form the context of every query
type ReqId struct {
Id uint `json:"id" uri:"id" form:"id" param:"id"`
}
// Request get id data form the context of every query
func (req *ReqId) Request(ctx interface{}) error {
if c, ok := ctx.(iris.Context); ok {
return req.irisReadParams(c)
} else if c, ok := ctx.(*gin.Context); ok {
return req.ginShouldBindUri(c)
} else {
return ErrUnSupportFramework
}
}
func (req *ReqId) irisReadParams(ctx iris.Context) error {
if err := ctx.ReadParams(req); err != nil {
zap_server.ZAPLOG.Error(err.Error())
return ErrParamValidate
}
return nil
}
func (req *ReqId) ginShouldBindUri(ctx *gin.Context) error {
if err := ctx.ShouldBindUri(req); err != nil {
zap_server.ZAPLOG.Error(err.Error())
return ErrParamValidate
}
return nil
}
// Paginate param for paginate query
type Paginate struct {
Page int `json:"page" form:"page"`
PageSize int `json:"pageSize" form:"pageSize"`
OrderBy string `json:"orderBy" form:"orderBy"`
Sort string `json:"sort" form:"sort"`
}
// Request
func (req *Paginate) Request(ctx interface{}) error {
if c, ok := ctx.(iris.Context); ok {
return req.irisReadQuerys(c)
} else if c, ok := ctx.(*gin.Context); ok {
return req.ginShouldBind(c)
} else {
return ErrUnSupportFramework
}
}
func (req *Paginate) irisReadQuerys(ctx iris.Context) error {
if err := ctx.ReadQuery(req); err != nil {
errs := validate.ValidRequest(err)
if len(errs) > 0 {
zap_server.ZAPLOG.Error(strings.Join(errs, ";"))
return ErrParamValidate
}
}
return nil
}
func (req *Paginate) ginShouldBind(ctx *gin.Context) error {
if err := ctx.ShouldBind(req); err != nil {
zap_server.ZAPLOG.Error(err.Error())
return ErrParamValidate
}
return nil
}
// PaginateScope paginate scope
func (req *Paginate) PaginateScope() func(db *gorm.DB) *gorm.DB {
return scope.PaginateScope(req.Page, req.PageSize, req.Sort, req.OrderBy)
}
// Response
type Response struct {
Status int64 `json:"status"`
Msg string `json:"message"`
Data interface{} `json:"data"`
}
+43
View File
@@ -0,0 +1,43 @@
package scope
import "gorm.io/gorm"
// IdScope
// - id uint
func IdScope(id uint) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("id = ?", id)
}
}
// InIdsScope
// - ids []uint
func InIdsScope(ids []uint) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("id in ?", ids)
}
}
// InNamesScope
// - names []string
func InNamesScope(names []string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("name in ?", names)
}
}
// InUuidsScope
// - uuids []string
func InUuidsScope(uuids []string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("uuid in ?", uuids)
}
}
// NeIdScope
// - id uint
func NeIdScope(id uint) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("id != ?", id)
}
}
+55
View File
@@ -0,0 +1,55 @@
package scope
import (
"gorm.io/gorm"
"pet-house.com/core/helper/str"
)
// PaginateScope return paginate scope for gorm
// - page int
// - pageSize int
// - sort string
// - orderBy string
func PaginateScope(page, pageSize int, sort, orderBy string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
pageSize := getPageSize(pageSize)
offset := getOffset(page, pageSize)
return db.Order(getOrderBy(sort, orderBy)).Offset(offset).Limit(pageSize)
}
}
// getOffset
func getOffset(page, pageSize int) int {
if page == 0 {
page = 1
}
offset := (page - 1) * pageSize
if page < 0 {
offset = -1
}
return offset
}
// getPageSize
func getPageSize(pageSize int) int {
switch {
case pageSize > 100:
pageSize = 100
case pageSize < 0:
pageSize = -1
case pageSize == 0:
pageSize = 10
}
return pageSize
}
// getOrderBy
func getOrderBy(sort, orderBy string) string {
if sort == "" {
sort = "desc"
}
if orderBy == "" {
orderBy = "created_at"
}
return str.Join(orderBy, " ", sort)
}