短信、邮箱发送
This commit is contained in:
parent
eba2ab2e98
commit
664714fca9
7
common/params/email_send.go
Normal file
7
common/params/email_send.go
Normal 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"`
|
||||
}
|
9
common/params/sms_send.go
Normal file
9
common/params/sms_send.go
Normal 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
50
common/public/err_msg.go
Normal 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
42
common/request/common.go
Normal 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)},
|
||||
))
|
||||
}
|
357
common/request/common_parser.go
Normal file
357
common/request/common_parser.go
Normal 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
75
common/request/form.go
Normal 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
22
common/request/header.go
Normal 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
31
common/request/json.go
Normal 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)
|
||||
}
|
74
common/request/multipart_form.go
Normal file
74
common/request/multipart_form.go
Normal 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
14
common/request/query.go
Normal 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
137
common/response/response.go
Normal 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
20
config.yaml
Executable file
@ -0,0 +1,20 @@
|
||||
listen: 8889
|
||||
runmode: local # 服务运行模式 debug/release
|
||||
logs:
|
||||
dir: ../log # 文件保存路径
|
||||
file: go-contest-api # 文件名称,实际会保存为{filename}+{datetime}
|
||||
level: 3 # 日志等级:0-error,1-warning,2-info,3-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
287
config/app_ser/app.go
Normal 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
66
config/app_ser/common.go
Normal 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
|
||||
}
|
109
config/app_ser/component/mysql.go
Normal file
109
config/app_ser/component/mysql.go
Normal 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
|
||||
}
|
107
config/app_ser/component/mysql_client.go
Normal file
107
config/app_ser/component/mysql_client.go
Normal 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
|
||||
}
|
82
config/app_ser/component/mysql_log.go
Normal file
82
config/app_ser/component/mysql_log.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
74
config/app_ser/component/redis.go
Normal file
74
config/app_ser/component/redis.go
Normal 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
|
||||
}
|
102
config/app_ser/component/redis_client.go
Normal file
102
config/app_ser/component/redis_client.go
Normal 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
65
config/app_ser/web.go
Normal 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
7
config/config.go
Normal file
@ -0,0 +1,7 @@
|
||||
package config
|
||||
|
||||
import "project/config/app_ser"
|
||||
|
||||
var (
|
||||
AppEnv *app_ser.AppConfig
|
||||
)
|
138
config/define.go
Normal file
138
config/define.go
Normal 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
23
config/logger/common.go
Normal 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
100
config/logger/console.go
Normal 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
18
config/logger/null.go
Normal 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
54
go.mod
Normal 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
189
go.sum
Normal 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=
|
71
internal/handler/api/email_send.go
Normal file
71
internal/handler/api/email_send.go
Normal 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
|
||||
}
|
||||
}
|
49
internal/handler/api/sms_send.go
Normal file
49
internal/handler/api/sms_send.go
Normal 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
64
internal/models/common.go
Normal 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
75
main.go
Normal 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)
|
||||
}
|
19
router/middleware/api-log.go
Normal file
19
router/middleware/api-log.go
Normal 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()
|
||||
//}
|
||||
}
|
20
router/middleware/options.go
Normal file
20
router/middleware/options.go
Normal 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
18
router/router.go
Normal 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
17
router/uri/auth_web.go
Normal 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
52
router/web/request.go
Normal 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
67
router/web/response.go
Normal 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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user