短信、邮箱发送

This commit is contained in:
cqk 2025-04-30 15:07:43 +08:00
parent eba2ab2e98
commit 664714fca9
37 changed files with 2711 additions and 0 deletions

View File

@ -0,0 +1,7 @@
package params
type EmailSend struct {
Email string `json:"email" binding:"required"`
AccessKeyId string `json:"access_key_id" example:"LTAI5tBzohLQvNcEh3HZjnWi"`
AccessKeySecret string `json:"access_key_secret" example:"XMyTwsKBjubwTMHqUVyPZCYvQFuXZA"`
}

View File

@ -0,0 +1,9 @@
package params
type SmsSend struct {
SendType int `json:"send_type" binding:"required"`
Phone string `json:"phone" binding:"required"`
AccessKeyId string `json:"access_key_id" example:"LTAI5tBzohLQvNcEh3HZjnWi"`
AccessKeySecret string `json:"access_key_secret" example:"XMyTwsKBjubwTMHqUVyPZCYvQFuXZA"`
RegionId string `json:"region_id" example:"ap-southeast-1"`
}

50
common/public/err_msg.go Normal file
View File

@ -0,0 +1,50 @@
package public
import (
"net/http"
"project/common/response"
)
const (
UnknownCode = -1
RespCodeErrorAuthInvalid = -4
RespCodeErrorAuthTimeout = -2
RespCodeErrorAuthOtherLogin = -3
RespCodeErrorAuthRedisNoFind = -5
RespCodeSuccess = 0 // success
CustomErrCode = UnknownCode
RespCodeErrorParam = 1
RespCodeErrorCustom = 2
RespCodeErrorUnknown = 3
RespCodeErrorAuthTokenCheckFail = 4
RespCodeErrorAuthNoPermission = 5
RespCodeErrorBadRequest = http.StatusBadRequest
RespCodeErrorSave = 10000 // 10002
RespCodeErrorAdd = 10001
RespCodeErrorDel = 10003
RespCodeErrorDataNoFind = 10004
RespCodeErrorDecodeFail = 10005 // 10006
RespCodeErrorFind = 10008
RespCodeErrorNoPermission = 11000 // 50001
RespCodeErrorBusiness = RespCodeErrorCustom
RespCodeErrorAccountInvalid = 20001
)
func init() {
response.CodeMsgMap[RespCodeSuccess] = "success"
response.CodeMsgMap[RespCodeErrorParam] = "参数错误"
response.CodeMsgMap[RespCodeErrorUnknown] = "fail"
// 401 token 错误占位
response.CodeMsgMap[RespCodeErrorBadRequest] = "token error."
// 10000+
response.CodeMsgMap[RespCodeErrorSave] = "保存失败"
response.CodeMsgMap[RespCodeErrorAdd] = "新增失败"
response.CodeMsgMap[RespCodeErrorDel] = "删除失败"
response.CodeMsgMap[RespCodeErrorFind] = "查询失败"
response.CodeMsgMap[RespCodeErrorDataNoFind] = "无数据"
response.CodeMsgMap[RespCodeErrorDecodeFail] = "数据解析失败"
response.CodeMsgMap[RespCodeErrorNoPermission] = "无权限"
}

42
common/request/common.go Normal file
View File

@ -0,0 +1,42 @@
package request
import (
"errors"
"unsafe"
)
const (
defaultMemory = 32 << 20
formTagName = "form"
)
type Unmarshaler interface {
UnmarshalForm(v string) error
}
type SliceUnmarshaler interface {
UnmarshalForm(v []string) error
}
var (
Validate = func(obj interface{}) error { return nil }
JsonEnableDecoderUseNumber = false
JsonEnableDecoderDisallowUnknownFields = false
)
var (
ErrUnknownType = errors.New("unknown type")
ErrMapSlicesToStringsType = errors.New("cannot convert to map slices of strings")
ErrMapToStringsType = errors.New("cannot convert to map of strings")
)
// StringToBytes converts string to byte slice without a memory allocation.
func StringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(
&struct {
string
Cap int
}{s, len(s)},
))
}

View File

@ -0,0 +1,357 @@
package request
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
var (
_EmptyField = reflect.StructField{}
)
type _SetOptions struct {
isDefaultExists bool
defaultValue string
}
type _Setter interface {
TrySet(value reflect.Value, field reflect.StructField, key string, opt _SetOptions) (isSetted bool, err error)
}
func head(str, sep string) (head string, tail string) {
idx := strings.Index(str, sep)
if idx < 0 {
return str, ""
}
return str[:idx], str[idx+len(sep):]
}
func _ValueSet(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt _SetOptions) (isSetted bool, err error) {
vs, ok := form[tagValue]
if !ok && !opt.isDefaultExists {
return false, nil
}
switch value.Kind() {
case reflect.Slice:
if !ok {
vs = []string{opt.defaultValue}
}
if ok, err = _SliceUnmarshaler(value, vs); ok {
return true, err
}
return true, _SetSlice(vs, value, field)
case reflect.Array:
if !ok {
vs = []string{opt.defaultValue}
}
if ok, err = _SliceUnmarshaler(value, vs); ok {
return true, err
}
if len(vs) != value.Len() {
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
}
return true, _SetArray(vs, value, field)
default:
var val string
if !ok {
val = opt.defaultValue
}
if len(vs) > 0 {
val = vs[0]
}
return true, _SetValue(val, value, field)
}
}
func _TryToSetValue(value reflect.Value, field reflect.StructField, setter _Setter, tag string) (bool, error) {
var tagValue string
var setOpt _SetOptions
tagValue = field.Tag.Get(tag)
tagValue, opts := head(tagValue, ",")
if tagValue == "" { // default value is FieldName
tagValue = field.Name
}
if tagValue == "" { // when field is "_EmptyField" variable
return false, nil
}
var opt string
for len(opts) > 0 {
opt, opts = head(opts, ",")
if k, v := head(opt, "="); k == "default" {
setOpt.isDefaultExists = true
setOpt.defaultValue = v
}
}
return setter.TrySet(value, field, tagValue, setOpt)
}
func _Mapping(value reflect.Value, field reflect.StructField, setter _Setter, tag string) (bool, error) {
if field.Tag.Get(tag) == "-" { // just ignoring this field
return false, nil
}
var vKind = value.Kind()
if vKind == reflect.Ptr {
var isNew bool
vPtr := value
if value.IsNil() {
isNew = true
vPtr = reflect.New(value.Type().Elem())
}
isSetted, err := _Mapping(vPtr.Elem(), field, setter, tag)
if err != nil {
return false, err
}
if isNew && isSetted {
value.Set(vPtr)
}
return isSetted, nil
}
if vKind != reflect.Struct || !field.Anonymous {
ok, err := _TryToSetValue(value, field, setter, tag)
if err != nil {
return false, err
}
if ok {
return true, nil
}
}
if vKind == reflect.Struct {
tValue := value.Type()
var isSetted bool
for i := 0; i < value.NumField(); i++ {
sf := tValue.Field(i)
if sf.PkgPath != "" && !sf.Anonymous { // unexported
continue
}
ok, err := _Mapping(value.Field(i), tValue.Field(i), setter, tag)
if err != nil {
return false, err
}
isSetted = isSetted || ok
}
return isSetted, nil
}
return false, nil
}
func _SetIntField(val string, bitSize int, value reflect.Value) error {
if val == "" {
val = "0"
}
intVal, err := strconv.ParseInt(val, 10, bitSize)
if err == nil {
value.SetInt(intVal)
}
return err
}
func _SetUintField(val string, bitSize int, value reflect.Value) error {
if val == "" {
val = "0"
}
uintVal, err := strconv.ParseUint(val, 10, bitSize)
if err == nil {
value.SetUint(uintVal)
}
return err
}
func _SetBoolField(val string, value reflect.Value) error {
if val == "" {
val = "false"
}
boolVal, err := strconv.ParseBool(val)
if err == nil {
value.SetBool(boolVal)
}
return err
}
func _SetFloatField(val string, bitSize int, value reflect.Value) error {
if val == "" {
val = "0.0"
}
floatVal, err := strconv.ParseFloat(val, bitSize)
if err == nil {
value.SetFloat(floatVal)
}
return err
}
func _SetTimeField(val string, structField reflect.StructField, value reflect.Value) error {
timeFormat := structField.Tag.Get("time_format")
if timeFormat == "" {
timeFormat = time.RFC3339
}
switch tf := strings.ToLower(timeFormat); tf {
case "unix", "unixnano":
tv, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return err
}
d := time.Duration(1)
if tf == "unixnano" {
d = time.Second
}
t := time.Unix(tv/int64(d), tv%int64(d))
value.Set(reflect.ValueOf(t))
return nil
}
if val == "" {
value.Set(reflect.ValueOf(time.Time{}))
return nil
}
l := time.Local
if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
l = time.UTC
}
if locTag := structField.Tag.Get("time_location"); locTag != "" {
loc, err := time.LoadLocation(locTag)
if err != nil {
return err
}
l = loc
}
t, err := time.ParseInLocation(timeFormat, val, l)
if err != nil {
return err
}
value.Set(reflect.ValueOf(t))
return nil
}
func _SetTimeDuration(val string, value reflect.Value) error {
d, err := time.ParseDuration(val)
if err != nil {
return err
}
value.Set(reflect.ValueOf(d))
return nil
}
func _SetArray(vals []string, value reflect.Value, field reflect.StructField) error {
for i, s := range vals {
err := _SetValue(s, value.Index(i), field)
if err != nil {
return err
}
}
return nil
}
func _SetSlice(vals []string, value reflect.Value, field reflect.StructField) error {
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
err := _SetArray(vals, slice, field)
if err != nil {
return err
}
value.Set(slice)
return nil
}
func _SliceUnmarshaler(v reflect.Value, vs []string) (bool, error) {
if v.Kind() != reflect.Pointer && v.Type().Name() != "" && v.CanAddr() {
v = v.Addr()
}
if v.Type().NumMethod() > 0 && v.CanInterface() {
if u, ok := v.Interface().(SliceUnmarshaler); ok {
return true, u.UnmarshalForm(vs)
}
if u, ok := v.Interface().(Unmarshaler); ok {
var val string
if len(vs) > 0 {
val = vs[0]
}
return true, u.UnmarshalForm(val)
}
}
return false, nil
}
func _ValueUnmarshaler(v reflect.Value) Unmarshaler {
if v.Kind() != reflect.Pointer && v.Type().Name() != "" && v.CanAddr() {
v = v.Addr()
}
if v.Type().NumMethod() > 0 && v.CanInterface() {
if u, ok := v.Interface().(Unmarshaler); ok {
return u
}
}
return nil
}
func _SetValue(val string, value reflect.Value, field reflect.StructField) error {
if u := _ValueUnmarshaler(value); u != nil {
return u.UnmarshalForm(val)
}
switch value.Kind() {
case reflect.Int:
return _SetIntField(val, 0, value)
case reflect.Int8:
return _SetIntField(val, 8, value)
case reflect.Int16:
return _SetIntField(val, 16, value)
case reflect.Int32:
return _SetIntField(val, 32, value)
case reflect.Int64:
switch value.Interface().(type) {
case time.Duration:
return _SetTimeDuration(val, value)
}
return _SetIntField(val, 64, value)
case reflect.Uint:
return _SetUintField(val, 0, value)
case reflect.Uint8:
return _SetUintField(val, 8, value)
case reflect.Uint16:
return _SetUintField(val, 16, value)
case reflect.Uint32:
return _SetUintField(val, 32, value)
case reflect.Uint64:
return _SetUintField(val, 64, value)
case reflect.Bool:
return _SetBoolField(val, value)
case reflect.Float32:
return _SetFloatField(val, 32, value)
case reflect.Float64:
return _SetFloatField(val, 64, value)
case reflect.String:
value.SetString(val)
case reflect.Struct:
switch value.Interface().(type) {
case time.Time:
return _SetTimeField(val, field, value)
}
return json.Unmarshal(StringToBytes(val), value.Addr().Interface())
case reflect.Map:
return json.Unmarshal(StringToBytes(val), value.Addr().Interface())
default:
return ErrUnknownType
}
return nil
}

75
common/request/form.go Normal file
View File

@ -0,0 +1,75 @@
package request
import (
"net/http"
"reflect"
)
type _FormSource map[string][]string
func (form _FormSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt _SetOptions) (isSetted bool, err error) {
return _ValueSet(value, field, form, tagValue, opt)
}
type FormBind struct {
}
func (FormBind) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
if err := req.ParseMultipartForm(defaultMemory); err != nil {
if err != http.ErrNotMultipart {
return err
}
}
if err := _FormMap(formTagName, obj, req.PostForm); err != nil {
return err
}
return Validate(obj)
}
func _FormParse(ptr interface{}, form map[string][]string) error {
el := reflect.TypeOf(ptr).Elem()
if el.Kind() == reflect.Slice {
ptrMap, ok := ptr.(map[string][]string)
if !ok {
return ErrMapSlicesToStringsType
}
for k, v := range form {
ptrMap[k] = v
}
return nil
}
ptrMap, ok := ptr.(map[string]string)
if !ok {
return ErrMapToStringsType
}
for k, v := range form {
ptrMap[k] = v[len(v)-1] // pick last
}
return nil
}
func _FormMap(tag string, ptr interface{}, form map[string][]string) error {
ptrVal := reflect.ValueOf(ptr)
var pointed interface{}
if ptrVal.Kind() == reflect.Ptr {
ptrVal = ptrVal.Elem()
pointed = ptrVal.Interface()
}
if ptrVal.Kind() == reflect.Map &&
ptrVal.Type().Key().Kind() == reflect.String {
if pointed != nil {
ptr = pointed
}
return _FormParse(ptr, form)
}
_, err := _Mapping(reflect.ValueOf(ptr), _EmptyField, _FormSource(form), tag)
return err
}

22
common/request/header.go Normal file
View File

@ -0,0 +1,22 @@
package request
import (
"net/http"
"net/textproto"
"reflect"
)
type _HeaderSource map[string][]string
func (hs _HeaderSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt _SetOptions) (isSetted bool, err error) {
return _ValueSet(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
}
type HeaderBind struct{}
func (HeaderBind) Bind(req *http.Request, obj interface{}) error {
if _, err := _Mapping(reflect.ValueOf(obj), _EmptyField, _HeaderSource(req.Header), "header"); err != nil {
return err
}
return Validate(obj)
}

31
common/request/json.go Normal file
View File

@ -0,0 +1,31 @@
package request
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type JsonBind struct{}
func (JsonBind) Bind(req *http.Request, obj interface{}) error {
if req == nil || req.Body == nil {
return fmt.Errorf("invalid request")
}
return _JsonDecode(req.Body, obj)
}
func _JsonDecode(r io.Reader, obj interface{}) error {
decoder := json.NewDecoder(r)
if JsonEnableDecoderUseNumber {
decoder.UseNumber()
}
if JsonEnableDecoderDisallowUnknownFields {
decoder.DisallowUnknownFields()
}
if err := decoder.Decode(obj); err != nil {
return err
}
return Validate(obj)
}

View File

@ -0,0 +1,74 @@
package request
import (
"errors"
"mime/multipart"
"net/http"
"reflect"
)
type _MultipartRequest http.Request
func (r *_MultipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt _SetOptions) (isSetted bool, err error) {
if files := r.MultipartForm.File[key]; len(files) != 0 {
return _MultipartFormFileSet(value, field, files)
}
return _ValueSet(value, field, r.MultipartForm.Value, key, opt)
}
type FormMultipartBind struct{}
func (FormMultipartBind) Name() string {
return "multipart/form-data"
}
func (FormMultipartBind) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseMultipartForm(defaultMemory); err != nil {
return err
}
if _, err := _Mapping(reflect.ValueOf(obj), _EmptyField, (*_MultipartRequest)(req), "form"); err != nil {
return err
}
return Validate(obj)
}
func _MultipartFormFileSet(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
switch value.Kind() {
case reflect.Ptr:
switch value.Interface().(type) {
case *multipart.FileHeader:
value.Set(reflect.ValueOf(files[0]))
return true, nil
}
case reflect.Struct:
switch value.Interface().(type) {
case multipart.FileHeader:
value.Set(reflect.ValueOf(*files[0]))
return true, nil
}
case reflect.Slice:
slice := reflect.MakeSlice(value.Type(), len(files), len(files))
isSetted, err = _SetArrayOfMultipartFormFiles(slice, field, files)
if err != nil || !isSetted {
return isSetted, err
}
value.Set(slice)
return true, nil
case reflect.Array:
return _SetArrayOfMultipartFormFiles(value, field, files)
}
return false, errors.New("unsupported field type for multipart.FileHeader")
}
func _SetArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
if value.Len() != len(files) {
return false, errors.New("unsupported len of array for []*multipart.FileHeader")
}
for i := range files {
setted, err := _MultipartFormFileSet(value.Index(i), field, files[i:i+1])
if err != nil || !setted {
return setted, err
}
}
return true, nil
}

14
common/request/query.go Normal file
View File

@ -0,0 +1,14 @@
package request
import "net/http"
type QueryBind struct {
}
func (QueryBind) Bind(req *http.Request, obj interface{}) error {
values := req.URL.Query()
if err := _FormMap(formTagName, obj, values); err != nil {
return err
}
return Validate(obj)
}

137
common/response/response.go Normal file
View File

@ -0,0 +1,137 @@
package response
import "fmt"
type Error interface {
error
Code() int
}
type ErrorDefault struct {
ErrMsg string
ErrCode int
}
func (v *ErrorDefault) Error() string {
return v.ErrMsg
}
func (v *ErrorDefault) Code() int {
return v.ErrCode
}
type Response interface {
SetErrCode(int) Response
SetCustomError(Error) Response
SetErrMsg(int, string, ...interface{}) Response
SetData(interface{}) Response
SetDataMsg(interface{}, string, ...interface{}) Response
SetMeta(interface{}) Response
AddError(...error) Response
}
func SetCode(v *int, code int) {
*v = code
}
func SetData(v *interface{}, data interface{}) {
*v = data
}
func SetMsg(v *string, msg string, msgArgs ...interface{}) {
if len(msgArgs) > 0 {
*v = fmt.Sprintf(msg, msgArgs...)
} else {
*v = msg
}
}
func SetMeta(v *interface{}, meta interface{}) {
if ShowMore {
*v = meta
}
}
func SetErrCode(c *int, m *string, code int) {
SetCode(c, code)
if msg, ok := CodeMsgMap[code]; ok {
*m = msg
} else {
*m = "fail"
}
}
func SetError(c *[]string, errs ...error) {
if ShowMore {
if *c != nil {
nLen := 4
if len(errs) > nLen {
nLen = len(errs)
}
*c = make([]string, 0, nLen)
}
for _, err := range errs {
if err != nil {
*c = append(*c, err.Error())
}
}
}
}
type _Default struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
Meta interface{} `json:"meta,omitempty"`
Errors []string `json:"errors,omitempty"`
}
var (
CodeMsgMap = map[int]string{}
ShowMore = true
)
func (r *_Default) SetErrCode(code int) Response {
SetErrCode(&r.Code, &r.Msg, code)
return r
}
func (r *_Default) SetCustomError(err Error) Response {
SetCode(&r.Code, err.Code())
return r._SetMsg(err.Error())
}
func (r *_Default) SetErrMsg(code int, msg string, msgArgs ...interface{}) Response {
SetCode(&r.Code, code)
return r._SetMsg(msg, msgArgs...)
}
func (r *_Default) _SetMsg(msg string, msgArgs ...interface{}) Response {
SetMsg(&r.Msg, msg, msgArgs...)
return r
}
func (r *_Default) SetData(v interface{}) Response {
SetData(&r.Data, v)
return r
}
func (r *_Default) SetDataMsg(v interface{}, msg string, msgArgs ...interface{}) Response {
SetData(&r.Data, v)
SetMsg(&r.Msg, msg, msgArgs...)
return r
}
func (r *_Default) SetMeta(v interface{}) Response {
SetMeta(&r.Meta, v)
return r
}
func (r *_Default) AddError(errs ...error) Response {
SetError(&r.Errors, errs...)
return r
}
func New() Response {
return &_Default{}
}

20
config.yaml Executable file
View File

@ -0,0 +1,20 @@
listen: 8889
runmode: local # 服务运行模式 debug/release
logs:
dir: ../log # 文件保存路径
file: go-contest-api # 文件名称,实际会保存为{filename}+{datetime}
level: 3 # 日志等级0-error1-warning2-info3-debug
savefile: false # 是否保存为文件置为false会输出到标准输出
mysql: # 数据库信息
addr: 192.168.2.10:3306
user: quentin
password: sEK1BhVnYCzg6HvCgYuu
db: ns9
charset: utf8mb4
max_open: 500
max_idle: 5
#redis:
# addr: 120.25.84.150:6380
# password: 123456
# db: 0
# poolsize: 5

287
config/app_ser/app.go Normal file
View File

@ -0,0 +1,287 @@
package app_ser
import (
"errors"
"fmt"
"github.com/urfave/cli/v2"
"math/rand"
"os"
"os/signal"
"project/config/logger"
"runtime"
"strings"
"sync"
"syscall"
"time"
)
const (
// Env
EnvLocal = iota // 本地
EnvDebug // 测试
EnvProd // 线上
CliAppEnv = "app-env"
CliAppLogMore = "app-log-more"
)
var (
_EnvDevStr = "local" // 本地
_EnvTestStr = "debug" // 测试
_EnvProdStr = "release" // 线上
)
var (
_EnvMap = map[int][2]string{
EnvLocal: {_EnvDevStr, "本地环境"},
EnvDebug: {_EnvTestStr, "测试环境"},
EnvProd: {_EnvProdStr, "线上环境"},
}
)
type _EnvFlag int
func (v _EnvFlag) Usage() string {
ret := make([]string, 0, len(_EnvMap))
for _, k := range []int{EnvLocal, EnvDebug, EnvProd} {
s := _EnvMap[k]
ret = append(ret, strings.Join(s[:], "-"))
}
return "env(" + strings.Join(ret, ", ") + ")"
}
func (v *_EnvFlag) Action(_ *cli.Context, sv string) error {
match := false
for ek, ev := range _EnvMap {
if sv == ev[0] {
*v, match = _EnvFlag(ek), true
break
}
}
if match != true {
return errors.New("Env Is Invalid: " + sv)
}
return nil
}
type AppConfig struct {
env _EnvFlag
version string
execDir string
logMore bool
}
func (v *AppConfig) Env() int {
return int(v.env)
}
func (v *AppConfig) EnvDesc() string {
return _EnvMap[int(v.env)][0]
}
func (v *AppConfig) SetEnv(e int) bool {
if _, ok := _EnvMap[int(v.env)]; ok {
v.env = _EnvFlag(e)
return true
}
return false
}
func (v *AppConfig) ExecDir() string {
return v.execDir
}
func (v *AppConfig) Version() string {
return v.version
}
func (v *AppConfig) LogMore() bool {
return v.logMore
}
type Component interface {
CliFlags() []cli.Flag
Init(logger.Interface, *AppConfig, *cli.Context) error
Run() error
Close() error
}
type Service interface {
CliFlags() []cli.Flag
Init(*AppConfig, *cli.Context) (logger.Interface, error)
Run(logger.Interface, *AppConfig) error
Close()
}
type Application struct {
Component map[string]Component
Log logger.Interface
Cfg AppConfig
Ser Service
}
func (app *Application) closeComponent() {
if app.Component != nil && len(app.Component) > 0 {
wg := sync.WaitGroup{}
wg.Add(len(app.Component))
for k := range app.Component {
go func(v Component) {
defer wg.Done()
_ = v.Close()
}(app.Component[k])
}
wg.Wait()
}
}
func (app *Application) shutdown() {
defer func() {
fmt.Println("Application Had Exit")
os.Exit(0)
}()
fmt.Println("Application To Exit")
}
func (app *Application) catchSignal() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
for s := range c {
switch s {
case syscall.SIGHUP:
fallthrough
case syscall.SIGINT, syscall.SIGTERM:
app.closeComponent()
app.Ser.Close()
app.shutdown()
}
}
}
func (app *Application) runComponent() error {
if app.Component != nil && len(app.Component) > 0 {
wg := sync.WaitGroup{}
lock := sync.Mutex{}
var errList []error
wg.Add(len(app.Component))
for k := range app.Component {
go func(v Component) {
defer wg.Done()
err := v.Run()
if err != nil {
lock.Lock()
if errList == nil {
errList = make([]error, 0)
}
errList = append(errList, err)
lock.Unlock()
}
}(app.Component[k])
}
wg.Wait()
if len(errList) > 0 {
errStr := make([]string, 0, len(errList))
for k := range errList {
errStr = append(errStr, errList[k].Error())
}
return errors.New("Component Run Error:" + strings.Join(errStr, ","))
}
}
return nil
}
func (app *Application) run() error {
var err error = nil
runtime.GOMAXPROCS(runtime.NumCPU())
go app.catchSignal()
// 初始化随机种子
rand.Seed(time.Now().UnixNano())
// 启动服务
err = app.runComponent()
if err != nil {
return err
}
return app.Ser.Run(app.Log, &app.Cfg)
}
func (app *Application) Start(cmd *cli.App, args []string, service Service) {
if cmd == nil {
cmd = cli.NewApp()
cmd.Version = "1.0"
cmd.Name = "ApplicationWeb"
cmd.Usage = "ApplicationWeb Server"
}
app.Ser = service
// app
cfs := make([][]cli.Flag, 0, len(app.Component)+2)
cfs = append(cfs, []cli.Flag{
// base
&cli.StringFlag{Name: CliAppEnv, Value: _EnvDevStr, Usage: app.Cfg.env.Usage(), Action: app.Cfg.env.Action},
&cli.BoolFlag{Name: CliAppLogMore, Value: false, Usage: "log more", Destination: &app.Cfg.logMore},
})
fsLen := len(cfs[0])
// service
afs := app.Ser.CliFlags()
if len(afs) > 0 {
cfs = append(cfs, afs)
fsLen += len(afs)
}
// component
for k := range app.Component {
v := app.Component[k].CliFlags()
if len(v) > 0 {
cfs = append(cfs, v)
fsLen += len(v)
}
}
// cli merge
fs := make([]cli.Flag, 0, fsLen)
for _, v := range cfs {
fs = append(fs, v...)
}
// 执行命令行
cmd.Flags = fs
cmd.Action = func(ctx *cli.Context) error {
var err error = nil
app.Cfg.execDir, err = os.Getwd()
if err == nil {
// 用命令行初始化配置
app.Log, err = app.Ser.Init(&app.Cfg, ctx)
if err == nil {
// 组件初始化
for k := range app.Component {
if err = app.Component[k].Init(app.Log, &app.Cfg, ctx); err != nil {
err = errors.New("Component Init Error: " + err.Error())
break
}
}
// 启动程序
if err == nil {
err = app.run()
}
}
} else {
err = errors.New("dir is invalid: " + app.Cfg.execDir)
}
return err
}
err := cmd.Run(args)
if err != nil {
fmt.Printf("Init Error: %s", err.Error())
os.Exit(1)
}
}

66
config/app_ser/common.go Normal file
View File

@ -0,0 +1,66 @@
package app_ser
import (
"fmt"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
"io/ioutil"
"os"
"project/config/logger"
)
type BasicService struct {
RunBefore, InitBefore func() error
}
func (c *BasicService) ParserFile(cfg interface{}) error {
// 解析文件
execDir, err := os.Getwd()
if nil != err {
return fmt.Errorf("load config (err: %s)\n", err.Error())
}
fPath := fmt.Sprintf("%s%c%s", execDir, os.PathSeparator, "config.yaml")
fmt.Printf("load config (path: %s)\n", fPath)
fp, err := os.Open(fPath)
if nil != err {
return fmt.Errorf("load config (err: %s)\n", err.Error())
}
defer fp.Close()
d, err := ioutil.ReadAll(fp)
if nil != err {
return fmt.Errorf("load config (err: %s)\n", err.Error())
}
// 解析配置
if err = yaml.Unmarshal(d, cfg); err != nil {
return fmt.Errorf("parser config failed! %s", err.Error())
}
return nil
}
func (c *BasicService) Init(runMode string, cfg *AppConfig, ctx *cli.Context) (logger.Interface, error) {
// 设置环境
if !ctx.IsSet(CliAppEnv) {
for k := range _EnvMap {
if _EnvMap[k][0] == runMode {
cfg.SetEnv(k)
break
}
}
}
// 初始化日志
v := &logger.Console{}
switch cfg.Env() {
case EnvLocal:
v.Init(logger.Info, true, os.Stdout, os.Stderr)
case EnvDebug, EnvProd:
v.Init(logger.Warn, false, os.Stdout, os.Stderr)
}
if c.InitBefore != nil {
if err := c.InitBefore(); err != nil {
return nil, err
}
}
return v, nil
}

View File

@ -0,0 +1,109 @@
package component
import (
"fmt"
"github.com/urfave/cli/v2"
"gorm.io/gorm/logger"
"project/config"
"project/config/app_ser"
glogger "project/config/logger"
"strings"
"time"
)
type MySQL struct {
Cfg config.ComponentMysql
Clts map[string]*MySQLClient
Log MysqlLogger
}
func (c *MySQL) SetLogLevel(v int) {
// log leve
nVal := logger.Error
switch v {
case glogger.Info:
nVal = logger.Info
case glogger.Warn:
nVal = logger.Warn
case glogger.Error:
nVal = logger.Error
}
c.Log.level = nVal
}
func (c *MySQL) CliFlags() []cli.Flag {
fs := make([][]cli.Flag, 0, len(c.Clts)+1)
fs = append(fs, []cli.Flag{
&cli.IntFlag{Name: "mysql-threshold", Usage: "mysql slow threshold", Value: 300, Destination: &c.Cfg.SlowThreshold},
&cli.IntFlag{Name: "mysql-op-timeout", Usage: "mysql operation timeout", Value: 15, Destination: &c.Cfg.OpTimeout},
})
for name, clt := range c.Clts {
fs = append(fs, clt.CliFlags(name))
}
l := 0
for k := range fs {
l += len(fs[k])
}
ret := make([]cli.Flag, 0, l)
for k := range fs {
ret = append(ret, fs[k]...)
}
return ret
}
func (c *MySQL) Init(l glogger.Interface, cfg *app_ser.AppConfig, _ *cli.Context) error {
// 设置日志等级
c.Log.SetLogger(l)
c.Log.slowThreshold = time.Duration(c.Cfg.SlowThreshold) * time.Millisecond
switch cfg.Env() {
case app_ser.EnvLocal:
c.SetLogLevel(glogger.Info)
case app_ser.EnvDebug:
c.SetLogLevel(glogger.Warn)
case app_ser.EnvProd:
c.SetLogLevel(glogger.Error)
}
// 打印链接信息
for k, v := range c.Clts {
msg := fmt.Sprintf("Init MySQL (%s) config", k)
v.Init(cfg.Env(), c)
if config.PrintfDSN {
var slave string
cCfg := v.Config()
if len(cCfg.Slave) > 0 {
ss := make([]string, 0, len(cCfg.Slave))
for sk := range cCfg.Slave {
ss = append(ss, cCfg.Slave[sk].GetPrintDSN())
}
slave = "[" + strings.Join(ss, ";") + "]"
}
msg = fmt.Sprintf("%s (%s%s)", msg, cCfg.Master.GetPrintDSN(), slave)
}
c.Log.log.WarnForce(msg)
}
return nil
}
func (c *MySQL) Run() error {
for name := range c.Clts {
if err := c.LoadOne(name); err != nil {
return err
}
}
return nil
}
func (c *MySQL) LoadOne(name string) (err error) {
if clt, ok := c.Clts[name]; ok {
err = clt.Load(name)
} else {
err = fmt.Errorf("MySQL (%s) config no find", name)
}
return err
}
func (c *MySQL) Close() error {
return nil
}

View File

@ -0,0 +1,107 @@
package component
import (
"database/sql"
"fmt"
"github.com/urfave/cli/v2"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"gorm.io/plugin/dbresolver"
"project/config"
"strings"
)
type MySQLClient struct {
DB *gorm.DB
Cfg config.EntityMysql
ref *MySQL
}
func (c *MySQLClient) Ref() *MySQL {
return c.ref
}
func (c *MySQLClient) CliFlags(name string) []cli.Flag {
return []cli.Flag{
&cli.IntFlag{
Name: fmt.Sprintf("mysql-%s-open-limit", name),
Usage: fmt.Sprintf("mysql(%s) max open limit", name),
Action: func(_ *cli.Context, v int) error { c.Cfg.MaxOpen = v; return nil },
},
&cli.IntFlag{
Name: fmt.Sprintf("mysql-%s-idle-limit", name),
Usage: fmt.Sprintf("mysql(%s) idle open limit", name),
Action: func(_ *cli.Context, v int) error { c.Cfg.MaxIdle = v; return nil },
},
&cli.StringFlag{
Name: fmt.Sprintf("mysql-%s-dsn", name),
Usage: fmt.Sprintf("mysql(%s) DSN (format: `{{user_name}}:{{password}}@tcp({{host}}:{{port}})/{{db_name}}?{{connect_options}}`)", name),
Action: func(_ *cli.Context, val string) (err error) {
dsn := strings.Split(val, ";")
if err = c.Cfg.Master.ParserDSN(dsn[0]); err == nil {
if len(dsn) > 1 {
c.Cfg.Slave = make([]config.ConnectMysql, 0, len(dsn)-1)
for i, l := 1, len(dsn); i < l; i++ {
v := config.ConnectMysql{}
if err = v.ParserDSN(dsn[i]); err != nil {
break
}
c.Cfg.Slave = append(c.Cfg.Slave, v)
}
}
}
if err != nil {
err = fmt.Errorf("Mysql DSN is invalid(%s)", err.Error())
}
return err
},
},
}
}
func (c *MySQLClient) Init(env int, cc *MySQL) {
c.ref = cc
}
func (c *MySQLClient) Config() *config.EntityMysql {
return &c.Cfg
}
func (c *MySQLClient) Load(name string) (err error) {
mastDB := mysql.Open(c.Cfg.Master.GetConnectDSN())
gormDB, err := gorm.Open(mastDB, &gorm.Config{
SkipDefaultTransaction: true,
Logger: &c.ref.Log,
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
if err == nil {
if len(c.Cfg.Slave) > 0 {
slaveDBs := make([]gorm.Dialector, 0, len(c.Cfg.Slave))
for k := range c.Cfg.Slave {
slaveDBs = append(slaveDBs, mysql.Open(c.Cfg.Slave[k].GetConnectDSN()))
}
readWritePlugin := dbresolver.Register(dbresolver.Config{
Sources: []gorm.Dialector{mastDB},
Replicas: slaveDBs,
Policy: dbresolver.RandomPolicy{},
})
readWritePlugin.SetMaxOpenConns(c.Cfg.MaxOpen)
readWritePlugin.SetMaxIdleConns(c.Cfg.MaxIdle)
err = gormDB.Use(readWritePlugin)
} else {
var sqlDB *sql.DB
if sqlDB, err = gormDB.DB(); err == nil {
sqlDB.SetMaxOpenConns(c.Cfg.MaxOpen)
sqlDB.SetMaxIdleConns(c.Cfg.MaxIdle)
}
}
}
if err != nil {
gormDB, err = nil, fmt.Errorf("Load Mysql (%s) Failed (Error: %s) ", name, err.Error())
}
c.DB = gormDB
return err
}

View File

@ -0,0 +1,82 @@
package component
import (
"context"
"errors"
"fmt"
"gorm.io/gorm/logger"
"gorm.io/gorm/utils"
glogger "project/config/logger"
"time"
)
const (
_TraceStr = "\n\t%s\n\t[%.3fms] [rows:%v] %s"
_TraceErrStr = "\n\t%s %s\n\t[%.3fms] [rows:%v] %s"
_TraceWarnStr = "\n\t%s %s\n\t[%.3fms] [rows:%v] %s"
)
type MysqlLogger struct {
log glogger.Interface
level logger.LogLevel
slowThreshold time.Duration
}
func (l *MysqlLogger) SetLogger(log glogger.Interface) {
l.log = log
}
func (l *MysqlLogger) LogMode(level logger.LogLevel) logger.Interface {
l.level = level
return l
}
func (l *MysqlLogger) Info(_ context.Context, msg string, data ...interface{}) {
if l.level >= logger.Info {
l.log.Info(msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
func (l *MysqlLogger) Warn(_ context.Context, msg string, data ...interface{}) {
if l.level >= logger.Warn {
l.log.Warn(msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
func (l *MysqlLogger) Error(_ context.Context, msg string, data ...interface{}) {
if l.level >= logger.Error {
l.log.Error(msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
func (l *MysqlLogger) Trace(_ context.Context, begin time.Time, fc func() (string, int64), err error) {
if l.level <= logger.Silent {
return
}
elapsed := time.Since(begin)
switch {
case err != nil && l.level >= logger.Error && !errors.Is(err, logger.ErrRecordNotFound):
sql, rows := fc()
if rows == -1 {
l.log.Error(_TraceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
} else {
l.log.Error(_TraceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql)
}
case elapsed > l.slowThreshold && l.slowThreshold != 0 && l.level >= logger.Warn:
sql, rows := fc()
slowLog := fmt.Sprintf("SLOW SQL >= %v", l.slowThreshold)
if rows == -1 {
l.log.Warn(_TraceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
} else {
l.log.Warn(_TraceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
}
case l.level == logger.Info:
sql, rows := fc()
if rows == -1 {
l.log.Info(_TraceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql)
} else {
l.log.Info(_TraceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql)
}
}
}

View File

@ -0,0 +1,74 @@
package component
import (
"fmt"
"github.com/urfave/cli/v2"
"project/config"
"project/config/app_ser"
"project/config/logger"
)
type Redis struct {
Cfg config.ComponentRedis
Clts map[string]*RedisClient
Log logger.Interface
}
func (c *Redis) CliFlags() []cli.Flag {
fs := make([][]cli.Flag, 0, len(c.Clts)+1)
fs = append(fs, []cli.Flag{
&cli.IntFlag{Name: "redis-sr-timeout", Usage: "redis connect socket read timeout", Value: 15, Destination: &c.Cfg.MaxReadTime},
&cli.IntFlag{Name: "redis-sw-timeout", Usage: "redis connect socket write timeout", Value: 15, Destination: &c.Cfg.MaxWriteTime},
&cli.IntFlag{Name: "redis-cr-timeout", Usage: "redis client request timeout", Value: 15, Destination: &c.Cfg.MaxRequestTime},
&cli.IntFlag{Name: "redis-check-idle-time", Usage: "redis idle timeout", Value: 30, Destination: &c.Cfg.IdleCheckTime},
&cli.IntFlag{Name: "redis-threshold", Usage: "redis slow request threshold", Value: 100, Destination: &c.Cfg.SlowThreshold},
})
for name, clt := range c.Clts {
fs = append(fs, clt.CliFlags(name))
}
l := 0
for k := range fs {
l += len(fs[k])
}
ret := make([]cli.Flag, 0, l)
for k := range fs {
ret = append(ret, fs[k]...)
}
return ret
}
func (c *Redis) Init(l logger.Interface, cfg *app_ser.AppConfig, _ *cli.Context) error {
c.Log = l
// 打印链接信息
for k, v := range c.Clts {
v.Init(cfg.Env(), c)
msg := fmt.Sprintf("Init Redis (%s) config", k)
if config.PrintfDSN {
msg = fmt.Sprintf("%s (%s/%d)", msg, v.Config().Addr, v.Config().DB)
}
l.WarnForce(msg)
}
return nil
}
func (c *Redis) Run() (err error) {
for name := range c.Clts {
if err = c.LoadOne(name); err != nil {
break
}
}
return err
}
func (c *Redis) LoadOne(name string) (err error) {
if clt, ok := c.Clts[name]; ok {
err = clt.Load(name)
} else {
err = fmt.Errorf("Redis (%s) config no find", name)
}
return err
}
func (c *Redis) Close() error {
return nil
}

View File

@ -0,0 +1,102 @@
package component
import (
"bytes"
"context"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/urfave/cli/v2"
"net/url"
"project/config"
"strconv"
"strings"
"time"
)
type RedisClient struct {
DB *redis.Client
Cfg config.EntityRedis
ref *Redis
}
func (c *RedisClient) Ref() *Redis {
return c.ref
}
func (c *RedisClient) CliFlags(name string) []cli.Flag {
return []cli.Flag{
&cli.IntFlag{
Name: fmt.Sprintf("redis-%s-open-limit", name),
Usage: fmt.Sprintf("redis(%s) max open limit", name),
Action: func(_ *cli.Context, v int) error { c.Cfg.PoolSize = v; return nil },
},
&cli.StringFlag{
Name: fmt.Sprintf("redis-%s-dsn", name),
Usage: fmt.Sprintf("redis(%s) DSN (format: `user:{{password}}@{{ip}}:{{port}}/{{db}}`)", name),
Action: func(_ *cli.Context, dsn string) error {
pv, err := url.Parse("redis://" + dsn)
if err == nil {
c.Cfg.Addr = pv.Host
c.Cfg.Pwd, _ = pv.User.Password()
c.Cfg.DB, _ = strconv.Atoi(strings.Trim(pv.Path, "/"))
}
if err != nil {
return fmt.Errorf("redis DSN is invalid(%s)[url parse faild err_msg:%s]", dsn, err.Error())
}
return nil
},
},
}
}
func (c *RedisClient) Init(env int, cc *Redis) {
c.ref = cc
}
func (c *RedisClient) Config() *config.EntityRedis {
return &c.Cfg
}
func (c *RedisClient) Load(name string) error {
c.DB = redis.NewClient(&redis.Options{
Addr: c.Cfg.Addr,
Password: c.Cfg.Pwd,
DB: c.Cfg.DB,
PoolSize: c.Cfg.PoolSize,
MinIdleConns: c.Cfg.PoolSize/3 + 1,
IdleTimeout: time.Second * time.Duration(c.ref.Cfg.IdleCheckTime),
ReadTimeout: time.Second * time.Duration(c.ref.Cfg.MaxReadTime),
WriteTimeout: time.Second * time.Duration(c.ref.Cfg.MaxWriteTime),
})
if _, err := c.DB.Ping(context.Background()).Result(); err != nil {
c.DB = nil
return fmt.Errorf("Load Redis (%s) Failed (Ping-Error: %s) ", name, err.Error())
}
return nil
}
func (c *RedisClient) ApiLog(args [][2]interface{}) func() {
bTm := time.Now()
return func() {
spendTime := time.Now().Sub(bTm) / time.Millisecond
var handler func(string, ...interface{})
//if spendTime > time.Duration(c.ref.Cfg.SlowThreshold) { // 查询超过阈值
// handler = c.ref.Log
//} else if c.ref.Log.Level() == logger.Info {
// handler = c.ref.Log.InfoForce
//}
if handler != nil {
var format bytes.Buffer
format.WriteString("Redis[%d ms]: ")
fArgs := make([]interface{}, 0, 2+len(args)*2)
fArgs = append(fArgs, spendTime)
for i, l := 0, len(args); i < l; i++ {
format.WriteString("\n\t%s: %+v")
fArgs = append(fArgs, args[0][0], args[0][1])
}
handler(format.String(), fArgs...)
}
}
}

65
config/app_ser/web.go Normal file
View File

@ -0,0 +1,65 @@
package app_ser
import (
"fmt"
"github.com/urfave/cli/v2"
"net/http"
"project/config/logger"
)
const (
CliWebIP = "web-ip"
CliWebPort = "web-port"
)
type WebService struct {
BasicService
Cfg struct {
IP string `yaml:"ip"` // 监听端口
Port int `yaml:"listen"` // 监听端口
RunMode string `yaml:"runmode"` // 服务运行模式
}
Handler http.Handler
}
func (c *WebService) CliFlags() []cli.Flag {
return []cli.Flag{
// web
&cli.StringFlag{
Name: CliWebIP,
Usage: "web service ip",
Action: func(_ *cli.Context, s string) error { c.Cfg.IP = s; return nil },
},
&cli.IntFlag{
Name: CliWebPort,
Usage: "web service port",
Action: func(_ *cli.Context, s int) error { c.Cfg.Port = s; return nil },
},
}
}
func (c *WebService) Init(cfg *AppConfig, ctx *cli.Context) (logger.Interface, error) {
return c.BasicService.Init(c.Cfg.RunMode, cfg, ctx)
}
func (c *WebService) Run(log logger.Interface, cfg *AppConfig) error {
if c.RunBefore != nil {
if err := c.RunBefore(); err != nil {
return err
}
}
if c.Cfg.IP == "" {
log.WarnForce("Listening Server [[[ Run-Mode: %s ]]] http://127.0.0.1:%d\n", cfg.EnvDesc(), c.Cfg.Port)
} else {
log.WarnForce("Listening Server [[[ Run-Mode: %s ]]] http://%s:%d\n", cfg.EnvDesc(), c.Cfg.IP, c.Cfg.Port)
}
srv := &http.Server{
Addr: fmt.Sprintf("%s:%d", c.Cfg.IP, c.Cfg.Port),
Handler: c.Handler,
}
srv.SetKeepAlivesEnabled(true)
return srv.ListenAndServe()
}
func (c *WebService) Close() {}

7
config/config.go Normal file
View File

@ -0,0 +1,7 @@
package config
import "project/config/app_ser"
var (
AppEnv *app_ser.AppConfig
)

138
config/define.go Normal file
View File

@ -0,0 +1,138 @@
package config
import (
"fmt"
"github.com/go-sql-driver/mysql"
"net/url"
"strings"
)
var (
PrintfDSN = true
)
type FileCfg struct {
Listen *int `yaml:"listen"` // 监听端口
RunMode *string `yaml:"runmode"` // 服务运行模式
Mysql *EntityMysql `yaml:"mysql"` //
Redis *EntityRedis `yaml:"redis"` // redis
}
type EntityMysql struct {
MaxOpen int `yaml:"max_open"`
MaxIdle int `yaml:"max_idle"`
Master ConnectMysql `yaml:",inline"`
Slave []ConnectMysql `yaml:"slave"`
}
type ComponentMysql struct {
SlowThreshold int
OpTimeout int
}
type ConnectMysql struct {
Addr string `yaml:"addr"`
UserName string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"db"`
Charset string `yaml:"charset"`
Options string `yaml:"options"`
initOpt bool
}
type EntityRedis struct {
ConnectRedis `yaml:",inline"`
PoolSize int `yaml:"poolsize"`
}
// redis
type ConnectRedis struct {
Addr string `yaml:"addr"`
Pwd string `yaml:"password"`
DB int `yaml:"db"`
}
type ComponentRedis struct {
IdleCheckTime,
MaxReadTime,
MaxWriteTime,
MaxRequestTime,
SlowThreshold int
}
func (c *ConnectMysql) GetPrintDSN() string {
if !c.initOpt {
var params map[string]string
var optValues []string
if c.Options != "" {
optValues = strings.Split(c.Options, "&")
params = make(map[string]string, len(optValues))
for _, v := range optValues {
v = strings.Trim(v, " ")
if v != "" {
kv := strings.Split(v, "=")
if len(kv) > 1 {
params[strings.Trim(kv[0], " ")] = strings.Trim(kv[1], " ")
} else {
params[strings.Trim(kv[0], " ")] = ""
}
}
}
}
c._setOption(params)
}
return fmt.Sprintf("mysql://%s/%s?%s", c.Addr, c.Database, c.Options)
}
func (c *ConnectMysql) ParserDSN(val string) error {
cfg, err := mysql.ParseDSN(val)
if err != nil {
return err
}
var params map[string]string
u := strings.Split(val, "?")
if len(u) > 1 {
if q, err := url.ParseQuery(u[1]); err == nil {
params = make(map[string]string, len(q))
for k := range q {
params[k] = q.Get(k)
}
}
}
*c = ConnectMysql{}
c.Addr, c.Database, c.UserName, c.Password = cfg.Addr, cfg.DBName, cfg.User, cfg.Passwd
c._setOption(params)
return nil
}
func (c *ConnectMysql) _setOption(params map[string]string) {
if params == nil {
params = make(map[string]string)
}
//if _, ok := params["parseTime"]; !ok {
// params["parseTime"] = "true"
//}
//if _, ok := params["loc"]; !ok {
// params["loc"] = "Local"
//}
if _, ok := params["charset"]; !ok {
if c.Charset == "" {
c.Charset = "utf8mb4"
}
params["charset"] = c.Charset
}
optValues := make([]string, 0, len(params))
for k := range params {
optValues = append(optValues, k+"="+params[k])
}
c.Options = strings.Join(optValues, "&")
c.initOpt = true
}
func (c *ConnectMysql) GetConnectDSN() string {
return fmt.Sprintf("%s:%s@tcp(%s)/%s?%s", c.UserName, c.Password, c.Addr, c.Database, c.Options)
}

23
config/logger/common.go Normal file
View File

@ -0,0 +1,23 @@
package logger
const (
Debug = iota
Info
Warn
Error
)
type Interface interface {
Level() int
SetLevel(int) int
Debug(format string, v ...interface{})
Info(format string, v ...interface{})
Warn(format string, v ...interface{})
Error(format string, v ...interface{})
DebugForce(format string, v ...interface{})
InfoForce(format string, v ...interface{})
WarnForce(format string, v ...interface{})
ErrorForce(format string, v ...interface{})
}

100
config/logger/console.go Normal file
View File

@ -0,0 +1,100 @@
package logger
import (
"bytes"
"fmt"
"io"
"log"
"runtime"
)
const (
// Colors
_LogColorReset = "\033[0m"
_LogColorRedBold = "\033[1;31m"
_LogColorGreenBold = "\033[1;32m"
_LogColorYellowBold = "\033[1;33m"
_LogColorBlueBold = "\033[1;34m"
_LogColorMagentaBold = "\033[1;35m"
_LogColorCyanBold = "\033[1;36m"
_LogColorWhiteBold = "\033[1;37m"
)
type Console struct {
level int
stdOutput *log.Logger
errOutput *log.Logger
debugStr, infoStr, warnStr, errStr string
}
func (output *Console) Level() int {
return output.level
}
func (output *Console) SetLevel(level int) int {
old := output.level
output.level = level
return old
}
func (output *Console) Init(level int, colorful bool, normal, err io.Writer) {
output.level = level
output.stdOutput = log.New(normal, "", log.Ldate|log.Lmicroseconds)
output.errOutput = log.New(err, "", log.Ldate|log.Lmicroseconds)
if colorful {
output.debugStr = _LogColorWhiteBold + "[Debug] " + _LogColorReset
output.infoStr = _LogColorBlueBold + "[Info] " + _LogColorReset
output.warnStr = _LogColorMagentaBold + "[Warn] " + _LogColorReset
output.errStr = _LogColorRedBold + "[Error] " + _LogColorReset
} else {
output.infoStr = "[Debug] "
output.infoStr = "[Info] "
output.warnStr = "[Warn] "
output.errStr = "[Error] "
}
}
func (output *Console) Debug(format string, v ...interface{}) {
if Debug >= output.level {
output.DebugForce(format, v...)
}
}
func (output *Console) Info(format string, v ...interface{}) {
if Info >= output.level {
output.InfoForce(format, v...)
}
}
func (output *Console) Warn(format string, v ...interface{}) {
if Warn >= output.level {
output.WarnForce(format, v...)
}
}
func (output *Console) Error(format string, v ...interface{}) {
if Error >= output.level {
output.ErrorForce(format, v...)
}
}
func (output *Console) DebugForce(format string, v ...interface{}) {
var buff bytes.Buffer
buff.WriteString(output.debugStr)
buff.WriteString(format)
output.stdOutput.Printf(buff.String(), v...)
}
func (output *Console) InfoForce(format string, v ...interface{}) {
var buff bytes.Buffer
buff.WriteString(output.infoStr)
buff.WriteString(format)
output.stdOutput.Printf(buff.String(), v...)
}
func (output *Console) WarnForce(format string, v ...interface{}) {
var buff bytes.Buffer
buff.WriteString(output.warnStr)
buff.WriteString(format)
output.stdOutput.Printf(buff.String(), v...)
}
func (output *Console) ErrorForce(format string, v ...interface{}) {
_, file, line, _ := runtime.Caller(2)
output.errOutput.Printf(fmt.Sprintf("%s%s\n\tfile: %s:%d", output.errStr, format, file, line), v...)
}

18
config/logger/null.go Normal file
View File

@ -0,0 +1,18 @@
package logger
// ToNull
type ToNull int
func (output ToNull) Level() int { return int(output) }
func (output *ToNull) SetLevel(v int) int { *output = ToNull(v); return int(*output) }
func (output ToNull) Debug(string, ...interface{}) {}
func (output ToNull) Info(string, ...interface{}) {}
func (output ToNull) Warn(string, ...interface{}) {}
func (output ToNull) Error(string, ...interface{}) {}
func (output ToNull) DebugForce(string, ...interface{}) {}
func (output ToNull) InfoForce(string, ...interface{}) {}
func (output ToNull) WarnForce(string, ...interface{}) {}
func (output ToNull) ErrorForce(string, ...interface{}) {}

54
go.mod Normal file
View File

@ -0,0 +1,54 @@
module project
go 1.23.3
require (
github.com/gin-contrib/pprof v1.5.3
github.com/gin-gonic/gin v1.10.0
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.9.2
github.com/urfave/cli/v2 v2.27.6
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.26.0
gorm.io/plugin/dbresolver v1.6.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 // indirect
github.com/bytedance/sonic v1.13.2 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/arch v0.16.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

189
go.sum Normal file
View File

@ -0,0 +1,189 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 h1:qagvUyrgOnBIlVRQWOyCZGVKUIYbMBdGdJ104vBpRFU=
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/pprof v1.5.3 h1:Bj5SxJ3kQDVez/s/+f9+meedJIqLS+xlkIVDe/lcvgM=
github.com/gin-contrib/pprof v1.5.3/go.mod h1:0+LQSZ4SLO0B6+2n6JBzaEygpTBxe/nI+YEYpfQQ6xY=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.26.0 h1:9lqQVPG5aNNS6AyHdRiwScAVnXHg/L/Srzx55G5fOgs=
gorm.io/gorm v1.26.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gorm.io/plugin/dbresolver v1.6.0 h1:XvKDeOtTn1EIX6s4SrKpEH82q0gXVemhYjbYZFGFVcw=
gorm.io/plugin/dbresolver v1.6.0/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -0,0 +1,71 @@
package api
import (
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/gin-gonic/gin"
"project/common/params"
"project/common/public"
"project/router/web"
)
func EmailXSend(c *gin.Context) {
result := web.NewResponse()
defer web.SendResponse(c, result)()
pm := params.EmailSend{}
err := c.ShouldBindJSON(&pm)
if err != nil {
result.SetErrCode(public.RespCodeErrorParam).AddError(err)
return
}
// 初始化客户端
credentialsProvider := credentials.NewStaticAKCredentialsProvider(pm.AccessKeyId, pm.AccessKeySecret)
client, err := sdk.NewClientWithOptions(
"cn-hangzhou", // 区域 ID例如 cn-hangzhou
sdk.NewConfig(), // 请求配置
credentialsProvider, //
)
if err != nil {
panic(err)
}
// 创建请求
request := requests.NewCommonRequest()
request.Method = "POST"
request.Scheme = "https"
request.Domain = "dm.aliyuncs.com" // 邮件推送服务域名
request.Version = "2015-11-23" // API 版本
request.ApiName = "SingleSendMail" // 接口名称
// 设置请求参数
request.QueryParams["RegionId"] = "cn-hangzhou"
request.QueryParams["AccountName"] = "sender@your-domain.com" // 发件人地址(需验证)
request.QueryParams["AddressType"] = "1" // 0: 随机账号; 1: 发件人地址
request.QueryParams["ReplyToAddress"] = "true" // 是否允许回复
request.QueryParams["ToAddress"] = "recipient@example.com" // 收件人地址
request.QueryParams["Subject"] = "Test Email from Aliyun" // 邮件主题
request.QueryParams["HtmlBody"] = "<h1>Hello</h1><p>This is a test email .</p>" // HTML 邮件正文
// request.QueryParams["TextBody"] = "This is a test email." // 纯文本正文(可选,优先使用 HtmlBody
// 发送请求
res, err := client.ProcessCommonRequest(request)
if err != nil {
panic(err)
}
// 输出响应
//fmt.Println(res.GetHttpContentString())
// 4. 处理响应
if res.IsSuccess() {
result.SetData(fmt.Sprintf("发送失败: %s", res.GetHttpContentString()))
return
} else {
result.SetErrMsg(
public.RespCodeErrorUnknown,
fmt.Sprintf("发送失败: %s", res.GetHttpContentString())).AddError()
return
}
}

View File

@ -0,0 +1,49 @@
package api
import (
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
"github.com/gin-gonic/gin"
"project/common/params"
"project/common/public"
"project/router/web"
)
func SmsXSend(c *gin.Context) {
result := web.NewResponse()
defer web.SendResponse(c, result)()
pm := params.SmsSend{}
err := c.ShouldBindJSON(&pm)
if err != nil {
result.SetErrCode(public.RespCodeErrorParam).AddError(err)
return
}
client, err := dysmsapi.NewClientWithAccessKey(pm.RegionId, pm.AccessKeyId, pm.AccessKeySecret)
if err != nil {
fmt.Printf("初始化客户端失败: %v\n", err)
return
}
// 2. 设置请求参数
request := dysmsapi.CreateSendMessageToGlobeRequest()
request.To = "目标手机号码" // 国际号码格式,例如:+85212345678
request.Message = "1234" // 短信内容
//request.From = "你的短信签名" // 短信签名
// 3. 发送短信
res, err := client.SendMessageToGlobe(request)
if err != nil {
fmt.Printf("发送短信失败: %v\n", err)
return
}
// 4. 处理响应
if res.IsSuccess() {
fmt.Println("短信发送成功:", res.MessageId)
} else {
result.SetErrMsg(public.RespCodeErrorUnknown, fmt.Sprintf("短信发送失败: %s", res.ResponseDescription)).AddError(err)
return
}
result.SetData(nil)
}

64
internal/models/common.go Normal file
View File

@ -0,0 +1,64 @@
package mysql
import (
"fmt"
"project/config/app_ser/component"
"gorm.io/gorm"
)
var (
Ns9 = component.MySQLClient{}
)
type Model interface {
TableName() string
DB() *gorm.DB
}
func GetNs9DB() *gorm.DB {
return Ns9.DB
}
func GetById(m Model, id int) error {
tx := m.DB().Where("id=?", id).Take(m)
return tx.Error
}
func Add(m Model) error {
tx := m.DB().Create(m)
return tx.Error
}
func Save(m Model, id int, columns []string) error {
tx := m.DB().Select(columns)
tx.Where("id=?", id).Updates(m)
return tx.Error
}
func OrderBySQL(orderBy string, order int, isNullBottom bool) string {
if orderBy != "" {
if isNullBottom {
orderBy = fmt.Sprintf("IF(%s IS NULL, 0, 1) desc, %s", orderBy, orderBy)
}
if order == 0 { // 为0倒序否则升序
return fmt.Sprintf("%s desc", orderBy)
} else {
return fmt.Sprintf("%s asc ", orderBy)
}
}
return ""
}
func GetOrderByStr(by string, order int, isNullBottom bool) string {
if by != "" {
if isNullBottom {
by = fmt.Sprintf("IF(%s IS NULL, 0, 1) desc, %s", by, by)
}
if order == 1 { // 为0倒序否则升序
return fmt.Sprintf("%s desc ", by)
} else {
return fmt.Sprintf("%s asc ", by)
}
}
return ""
}

75
main.go Normal file
View File

@ -0,0 +1,75 @@
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"os"
"project/config"
"project/config/app_ser"
"project/config/app_ser/component"
mysql "project/internal/models"
"project/router"
)
func main() {
app := app_ser.Application{}
gin.SetMode(gin.ReleaseMode)
webRouter := gin.Default()
//cRedis := component.Redis{
// Clts: map[string]*component.RedisClient{"default": &redis.Default},
//}
var cMysql component.MySQL
cMysql = component.MySQL{
Clts: map[string]*component.MySQLClient{"ns9": &mysql.Ns9},
}
// 关联配置
ser := &app_ser.WebService{
BasicService: app_ser.BasicService{
InitBefore: func() error {
config.PrintfDSN = true
return nil
},
RunBefore: func() (err error) {
// 慢查询阈值
cMysql.Cfg.SlowThreshold = 500
// 初始化
//logs.Instance = app.Log
config.AppEnv = &app.Cfg
// init local cache
//if err = cache.Init(); err != nil {
// return err
//}
// 初始化
router.Router(webRouter)
return nil
},
},
Handler: webRouter,
}
// 解析配置
if err := ser.ParserFile(&config.FileCfg{
Listen: &ser.Cfg.Port,
RunMode: &ser.Cfg.RunMode,
Mysql: &mysql.Ns9.Cfg,
//Redis: &redis.Default.Cfg,
}); err != nil {
fmt.Printf("Init Error: %s", err.Error())
os.Exit(1)
}
// 运行程序
app.Component = map[string]app_ser.Component{
"mysql": &cMysql,
//"redis": &cRedis,
//"cron": &cache.Default,
}
app.Start(nil, os.Args, ser)
}

View File

@ -0,0 +1,19 @@
package middleware
import (
"github.com/gin-gonic/gin"
)
func LogApiReq(c *gin.Context) {
//if config.AppEnv.Env() != gsf.EnvLocal {
// start := time.Now()
// c.Next()
// url := "contest::" + c.Request.URL.Path
// timeData := time.Since(start)
// date := time.Now().Format("20060102-15")
// sec := int(timeData / time.Second)
// _ = redis.ApiLog(url, date, sec)
//} else {
c.Next()
//}
}

View File

@ -0,0 +1,20 @@
package middleware
import (
"github.com/gin-gonic/gin"
)
func OPTIONS(c *gin.Context) {
//// 跨域
//if config.AppEnv.Env() == gsf.EnvLocal {
// c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
// c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
//}
//// POST options 预请求
//if c.Request.Method == "OPTIONS" {
// c.AbortWithStatus(204)
// return
//}
c.Next()
}

18
router/router.go Normal file
View File

@ -0,0 +1,18 @@
package router
import (
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
"project/router/middleware"
"project/router/uri"
)
func Router(router *gin.Engine) {
pprof.Register(router) // 性能
router.Use(middleware.OPTIONS)
router.Use(middleware.LogApiReq)
uri.InitAuthRouter(router.Group("/"))
}

17
router/uri/auth_web.go Normal file
View File

@ -0,0 +1,17 @@
package uri
import (
"github.com/gin-gonic/gin"
"project/internal/handler/api"
)
func authWeb(router *gin.RouterGroup) {
{
router.POST("/sms/send", api.SmsXSend) // 发送短信
router.POST("/email/send", api.EmailXSend) // 发送邮件
}
}
func InitAuthRouter(router *gin.RouterGroup) {
authWeb(router.Group("/"))
}

52
router/web/request.go Normal file
View File

@ -0,0 +1,52 @@
package web
import (
"github.com/gin-gonic/gin/binding"
"project/common/request"
)
func init() {
request.Validate = binding.Validator.ValidateStruct
}
var (
BindForm = _FormBind{}
BindMultipartForm = _FormMultipartBind{}
BindQuery = _QueryBind{}
BindHead = _HeadHead{}
)
/**
**************************************************/
type _FormBind struct {
request.FormBind
}
func (_FormBind) Name() string {
return "custom_form"
}
type _QueryBind struct {
request.QueryBind
}
func (_QueryBind) Name() string {
return "custom_query"
}
type _FormMultipartBind struct {
request.FormMultipartBind
}
func (_FormMultipartBind) Name() string {
return "custom_form_multipart"
}
type _HeadHead struct {
request.HeaderBind
}
func (_HeadHead) Name() string {
return "custom_head"
}

67
router/web/response.go Normal file
View File

@ -0,0 +1,67 @@
package web
import (
"net/http"
"github.com/gin-gonic/gin"
"project/common/response"
)
type _Default struct {
Code int `json:"error_code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
Meta interface{} `json:"meta,omitempty"`
Errors []string `json:"errors,omitempty"`
}
func (r *_Default) SetCustomError(err response.Error) response.Response {
response.SetCode(&r.Code, err.Code())
return r._SetMsg(err.Error())
}
func (r *_Default) SetErrCode(code int) response.Response {
response.SetErrCode(&r.Code, &r.Msg, code)
return r
}
func (r *_Default) SetErrMsg(code int, msg string, msgArgs ...interface{}) response.Response {
response.SetCode(&r.Code, code)
return r._SetMsg(msg, msgArgs...)
}
func (r *_Default) _SetMsg(msg string, msgArgs ...interface{}) response.Response {
response.SetMsg(&r.Msg, msg, msgArgs...)
return r
}
func (r *_Default) SetData(v interface{}) response.Response {
response.SetData(&r.Data, v)
return r
}
func (r *_Default) SetDataMsg(v interface{}, msg string, msgArgs ...interface{}) response.Response {
response.SetData(&r.Data, v)
response.SetMsg(&r.Msg, msg, msgArgs...)
return r
}
func (r *_Default) SetMeta(v interface{}) response.Response {
response.SetMeta(&r.Meta, v)
return r
}
func (r *_Default) AddError(errs ...error) response.Response {
response.SetError(&r.Errors, errs...)
return r
}
func NewResponse() response.Response {
return &_Default{}
}
func SendResponse(c *gin.Context, req response.Response) func() {
return func() {
c.JSON(http.StatusOK, req)
}
}