diff --git a/pkg/app/app.go b/pkg/app/app.go new file mode 100644 index 0000000..cebc839 --- /dev/null +++ b/pkg/app/app.go @@ -0,0 +1,49 @@ +package app + +import ( + "blog/pkg/errorcode" + "github.com/gin-gonic/gin" + "net/http" +) + +type Response struct { + Ctx *gin.Context +} + +type Pager struct { + Page int `json:"page"` + PageSize int `json:"page_size"` + TotalRows int `json:"total_rows"` +} + +func NewResponse(ctx *gin.Context) *Response { + return &Response{Ctx: ctx} +} + +func (r *Response) ToResponse(data interface{}) { + if data == nil { + data = gin.H{} + } + r.Ctx.JSON(http.StatusOK, data) +} + +func (r *Response) ToResponseList(list interface{}, totalRows int) { + r.Ctx.JSON(http.StatusOK, gin.H{ + "list": list, + "pager": Pager{ + Page: GetPage(r.Ctx), + PageSize: GetPageSize(r.Ctx), + TotalRows: totalRows, + }, + }) +} + +func (r *Response) ToErrorResponse(err *errorcode.Error) { + response := gin.H{"code": err.Code(), "msg": err.Msg()} + details := err.Details() + if len(details) > 0 { + response["details"] = details + } + + r.Ctx.JSON(err.StatusCode(), response) +} diff --git a/pkg/app/form.go b/pkg/app/form.go new file mode 100644 index 0000000..4dab37e --- /dev/null +++ b/pkg/app/form.go @@ -0,0 +1,56 @@ +package app + +import ( + "github.com/gin-gonic/gin" + ut "github.com/go-playground/universal-translator" + val "github.com/go-playground/validator/v10" + "strings" +) + +type ValidError struct { + Key string + Message string +} + +type ValidErrors []*ValidError + +func (v *ValidError) Error() string { + return v.Message +} + +func (v ValidErrors) Error() string { + return strings.Join(v.Errors(), ",") +} + +func (v ValidErrors) Errors() []string { + var errs []string + for _, err := range v { + errs = append(errs, err.Error()) + } + + return errs +} + +func BindAndValid(c *gin.Context, v interface{}) (bool, ValidErrors) { + var errs ValidErrors + err := c.ShouldBind(v) + if err != nil { + v := c.Value("trans") + trans, _ := v.(ut.Translator) + verrs, ok := err.(val.ValidationErrors) + if !ok { + return false, errs + } + + for key, value := range verrs.Translate(trans) { + errs = append(errs, &ValidError{ + Key: key, + Message: value, + }) + } + + return false, errs + } + + return true, nil +} diff --git a/pkg/app/pagination.go b/pkg/app/pagination.go new file mode 100644 index 0000000..9050fb8 --- /dev/null +++ b/pkg/app/pagination.go @@ -0,0 +1,37 @@ +package app + +import ( + "blog/global" + "blog/pkg/convert" + "github.com/gin-gonic/gin" +) + +func GetPage(c *gin.Context) int { + page := convert.StrTo(c.Query("page")).MustInt() + if page <= 0 { + return 1 + } + + return page +} + +func GetPageSize(c *gin.Context) int { + pageSize := convert.StrTo(c.Query("page_size")).MustInt() + if pageSize <= 0 { + return global.AppSetting.DefaultPageSize + } + if pageSize > global.AppSetting.MaxPageSize { + return global.AppSetting.MaxPageSize + } + + return pageSize +} + +func GetPageOffset(page, pageSize int) int { + result := 0 + if page > 0 { + result = (page - 1) * pageSize + } + + return result +} diff --git a/pkg/convert/convert.go b/pkg/convert/convert.go new file mode 100644 index 0000000..52e75fa --- /dev/null +++ b/pkg/convert/convert.go @@ -0,0 +1,29 @@ +package convert + +import "strconv" + +type StrTo string + +func (s StrTo) String() string { + return string(s) +} + +func (s StrTo) Int() (int, error) { + v, err := strconv.Atoi(s.String()) + return v, err +} + +func (s StrTo) MustInt() int { + v, _ := s.Int() + return v +} + +func (s StrTo) UInt32() (uint32, error) { + v, err := strconv.Atoi(s.String()) + return uint32(v), err +} + +func (s StrTo) MustUInt32() uint32 { + v, _ := s.UInt32() + return v +} diff --git a/pkg/errorcode/common_code.go b/pkg/errorcode/common_code.go new file mode 100644 index 0000000..0eac776 --- /dev/null +++ b/pkg/errorcode/common_code.go @@ -0,0 +1,87 @@ +package errorcode + +import ( + "fmt" + "net/http" +) + +var ( + Success = NewError(0, "成功") + ServerError = NewError(10000000, "服务内部错误") + InvalidParams = NewError(10000001, "入参错误") + NotFound = NewError(10000002, "找不到") + UnauthorizedAuthNotExist = NewError(10000003, "鉴权失败,找不到对应的 AppKey 和 AppSecret") + UnauthorizedTokenError = NewError(10000004, "鉴权失败,Token 错误") + UnauthorizedTokenTimeout = NewError(10000005, "鉴权失败,Token 超时") + UnauthorizedTokenGenerate = NewError(10000006, "鉴权失败,Token 生成失败") + TooManyRequests = NewError(10000007, "请求过多") +) + +type Error struct { + code int `json:"code"` + msg string `json:"msg"` + details []string `json:"details"` +} + +var codes = map[int]string{} + +func NewError(code int, msg string) *Error { + if _, ok := codes[code]; ok { + panic(fmt.Sprintf("错误码 %d 已经存在,请更换一个", code)) + } + codes[code] = msg + return &Error{code: code, msg: msg} +} + +func (e *Error) Error() string { + return fmt.Sprintf("错误码:%d, 错误信息::%s", e.Code(), e.Msg()) +} + +func (e *Error) Code() int { + return e.code +} + +func (e *Error) Msg() string { + return e.msg +} + +func (e *Error) Msgf(args []interface{}) string { + return fmt.Sprintf(e.msg, args...) +} + +func (e *Error) Details() []string { + return e.details +} + +func (e *Error) WithDetails(details ...string) *Error { + newError := *e + newError.details = []string{} + for _, d := range details { + newError.details = append(newError.details, d) + } + + return &newError +} + +func (e *Error) StatusCode() int { + switch e.Code() { + case Success.Code(): + return http.StatusOK + case ServerError.Code(): + return http.StatusInternalServerError + case InvalidParams.Code(): + return http.StatusBadRequest + case UnauthorizedAuthNotExist.Code(): + fallthrough + case UnauthorizedTokenError.Code(): + fallthrough + case UnauthorizedTokenGenerate.Code(): + fallthrough + case UnauthorizedTokenTimeout.Code(): + return http.StatusUnauthorized + case TooManyRequests.Code(): + return http.StatusTooManyRequests + } + + return http.StatusInternalServerError +} diff --git a/pkg/errorcode/module_code.go b/pkg/errorcode/module_code.go new file mode 100644 index 0000000..d5600de --- /dev/null +++ b/pkg/errorcode/module_code.go @@ -0,0 +1,10 @@ +package errorcode + +var ( + ErrorGetTagListFail = NewError(20010001, "获取标签列表失败") + ErrorCreateTagFail = NewError(20010002, "创建标签失败") + ErrorUpdateTagFail = NewError(20010003, "更新标签失败") + ErrorDeleteTagFail = NewError(20010004, "删除标签失败") + ErrorCountTagFail = NewError(20010005, "统计标签失败") + ErrorUploadFileFail = NewError(20030001, "上传文件失败") +) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..a9b13cd --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,165 @@ +package logger + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "runtime" + "time" +) + +type Level int8 + +type Fields map[string]interface{} + +const ( + LevelDebug Level = iota + LevelInfo + LevelWarn + LevelError + LevelFatal + LevelPanic +) + +func (l Level) String() string { + switch l { + case LevelDebug: + return "debug" + case LevelInfo: + return "info" + case LevelWarn: + return "warn" + case LevelError: + return "error" + case LevelFatal: + return "fatal" + case LevelPanic: + return "panic" + } + return "" +} + +type Logger struct { + newLogger *log.Logger + ctx context.Context + fields Fields + callers []string +} + +func NewLogger(w io.Writer, prefix string, flag int) *Logger { + l := log.New(w, prefix, flag) + return &Logger{newLogger: l} +} + +func (l *Logger) clone() *Logger { + nl := *l + return &nl +} + +func (l *Logger) WithFields(f Fields) *Logger { + ll := l.clone() + if ll.fields == nil { + ll.fields = make(Fields) + } + for k, v := range f { + ll.fields[k] = v + } + return ll +} + +func (l *Logger) WithContext(ctx context.Context) *Logger { + ll := l.clone() + ll.ctx = ctx + return ll +} + +func (l *Logger) WithCaller(skip int) *Logger { + ll := l.clone() + pc, file, line, ok := runtime.Caller(skip) + if ok { + f := runtime.FuncForPC(pc) + ll.callers = []string{fmt.Sprintf("%s: %d %s", file, line, f.Name())} + } + + return ll +} + +func (l *Logger) WithCallersFrames() *Logger { + maxCallerDepth := 25 + minCallerDepth := 1 + callers := []string{} + pcs := make([]uintptr, maxCallerDepth) + depth := runtime.Callers(minCallerDepth, pcs) + frames := runtime.CallersFrames(pcs[:depth]) + for frame, more := frames.Next(); more; frame, more = frames.Next() { + callers = append(callers, fmt.Sprintf("%s: %d %s", frame.File, frame.Line, frame.Function)) + if !more { + break + } + } + ll := l.clone() + ll.callers = callers + return ll +} + +func (l *Logger) JSONFormat(level Level, message string) map[string]interface{} { + data := make(Fields, len(l.fields)+4) + data["level"] = level.String() + data["time"] = time.Now().Local().UnixNano() + data["message"] = message + data["callers"] = l.callers + if len(l.fields) > 0 { + for k, v := range l.fields { + if _, ok := data[k]; !ok { + data[k] = v + } + } + } + + return data +} + +func (l *Logger) Output(level Level, message string) { + body, _ := json.Marshal(l.JSONFormat(level, message)) + content := string(body) + switch level { + case LevelDebug: + l.newLogger.Print(content) + case LevelInfo: + l.newLogger.Print(content) + case LevelWarn: + l.newLogger.Print(content) + case LevelError: + l.newLogger.Print(content) + case LevelFatal: + l.newLogger.Fatal(content) + case LevelPanic: + l.newLogger.Panic(content) + } +} + +func (l *Logger) Info(v ...interface{}) { + l.Output(LevelInfo, fmt.Sprint(v...)) +} + +func (l *Logger) Infof(format string, v ...interface{}) { + l.Output(LevelInfo, fmt.Sprintf(format, v...)) +} + +func (l *Logger) Fatal(v ...interface{}) { + l.Output(LevelFatal, fmt.Sprint(v...)) +} + +func (l *Logger) Fatalf(format string, v ...interface{}) { + l.Output(LevelFatal, fmt.Sprintf(format, v...)) +} + +func (l *Logger) Error(v ...interface{}) { + l.Output(LevelError, fmt.Sprint(v...)) +} + +func (l *Logger) Errorf(format string, v ...interface{}) { + l.Output(LevelError, fmt.Sprintf(format, v...)) +} diff --git a/pkg/setting/section.go b/pkg/setting/section.go new file mode 100644 index 0000000..d3594cc --- /dev/null +++ b/pkg/setting/section.go @@ -0,0 +1,44 @@ +package setting + +import "time" + +type ServerSettingS struct { + RunMode string + HttpPort string + ReadTimeout time.Duration + WriteTimeout time.Duration +} + +type AppSettingS struct { + DefaultPageSize int + MaxPageSize int + LogSavePath string + LogFileName string + LogFileExt string + UploadSavePath string + UploadServerUrl string + UploadImageMaxSize int + UploadImageAllowExts []string +} + +type DatabaseSettingS struct { + DBType string + UserName string + Password string + Host string + DBName string + TablePrefix string + Charset string + ParseTime bool + MaxIdleConns int + MaxOpenConns int +} + +func (s *Setting) ReadSection(k string, v interface{}) error { + err := s.vp.UnmarshalKey(k, v) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go new file mode 100644 index 0000000..60fc934 --- /dev/null +++ b/pkg/setting/setting.go @@ -0,0 +1,20 @@ +package setting + +import "github.com/spf13/viper" + +type Setting struct { + vp *viper.Viper +} + +func NewSetting() (*Setting, error) { + vp := viper.New() + vp.SetConfigName("config") + vp.AddConfigPath("configs/") + vp.SetConfigType("yaml") + err := vp.ReadInConfig() + if err != nil { + return nil, err + } + + return &Setting{vp}, nil +} diff --git a/pkg/upload/file.go b/pkg/upload/file.go new file mode 100644 index 0000000..af4b9a2 --- /dev/null +++ b/pkg/upload/file.go @@ -0,0 +1,101 @@ +package upload + +import ( + "blog/global" + "blog/pkg/util" + "io" + "io/ioutil" + "mime/multipart" + "os" + "path" + "strings" +) + +type FileType int + +const ( + TypeImage FileType = iota + 1 + TypeExcel + TypeTxt +) + +func GetFileName(name string) string { + ext := GetFileExt(name) + fileName := strings.TrimSuffix(name, ext) + fileName = util.EncodeMD5(fileName) + + return fileName + ext +} + +func GetFileExt(name string) string { + return path.Ext(name) +} + +func GetSavePath() string { + return global.AppSetting.UploadSavePath +} + +func CheckSavePath(dst string) bool { + _, err := os.Stat(dst) + return os.IsNotExist(err) +} + +func CheckContainExt(t FileType, name string) bool { + ext := GetFileExt(name) + ext = strings.ToUpper(ext) + switch t { + case TypeImage: + for _, allowExt := range global.AppSetting.UploadImageAllowExts { + if strings.ToUpper(allowExt) == ext { + return true + } + } + + } + + return false +} + +func CheckMaxSize(t FileType, f multipart.File) bool { + content, _ := ioutil.ReadAll(f) + size := len(content) + switch t { + case TypeImage: + if size >= global.AppSetting.UploadImageMaxSize*1024*1024 { + return true + } + } + + return false +} + +func CheckPermission(dst string) bool { + _, err := os.Stat(dst) + return os.IsPermission(err) +} + +func CreateSavePath(dst string, perm os.FileMode) error { + err := os.MkdirAll(dst, perm) + if err != nil { + return err + } + + return nil +} + +func SaveFile(file *multipart.FileHeader, dst string) error { + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, src) + return err +} diff --git a/pkg/util/md5.go b/pkg/util/md5.go new file mode 100644 index 0000000..8a00f54 --- /dev/null +++ b/pkg/util/md5.go @@ -0,0 +1,13 @@ +package util + +import ( + "crypto/md5" + "encoding/hex" +) + +func EncodeMD5(value string) string { + m := md5.New() + m.Write([]byte(value)) + + return hex.EncodeToString(m.Sum(nil)) +}