在實際開發中,需要前後端需要協商狀態碼,狀態碼用於後端返前端時使用。在一個團隊中,定義的狀態碼講道理應該是一致的,專案開始的起始階段狀態碼應該是定義了個七七八八的,隨著功能的疊加而不斷增加。此係列將圍繞我的研發之旅進行。

狀態碼推薦的專案目錄為pkg/globalcode目錄

後端返前端的json 格式為:

  1. {
  2. "code": 狀態碼,
  3. "data": [
  4. 功能邏輯後返前欄位
  5. ],
  6. "message": 狀態碼對應的message
  7. }

本片將以成功引數校驗兩個常見的狀態碼為案例進行

自定義狀態碼 目錄結構

  1. .
  2. ├── [ 96] cmd
  3.    └── [1.3K] root.go
  4. ├── [ 128] config
  5.    ├── [2.2K] cfg.go
  6.    └── [ 129] config.yaml
  7. ├── [ 160] controller
  8.    ├── [ 267] base.go
  9.    ├── [ 452] name.go
  10.    └── [ 96] validation # 校驗層
  11.    └── [ 151] name.go
  12. ├── [ 242] go.mod
  13. ├── [ 66K] go.sum
  14. ├── [ 200] main.go
  15. ├── [ 96] pkg
  16.    └── [ 128] globalcode
  17.    ├── [ 79] code.go
  18.    └── [ 148] message.go
  19. ├── [ 96] routes
  20.    └── [ 343] routes.go
  21. ├── [ 96] routes
  22.    └── [ 343] routes.go
  23. └── [ 96] service
  24. └──[ 80] name.go
  25. 8 directories, 13 files

邏輯:專案初始化 --> 前端調介面--> 後端routes層 --> 後端controller層(判斷前端傳參是否有無(如果沒有前端傳參跳過此步驟) )--> 後端service層處理功能邏輯(有可能需要model層) --> 後端controller層處理返回結果(這裡需要狀態碼處理)

專案初始化

main.go

  1. /**
  2. * @Author: zisefeizhu
  3. * @Description: code
  4. * @File: main.go
  5. * @Version: 1.0.0
  6. * @Date: 2021/9/4 10:13
  7. */
  8. package main
  9. import (
  10. "codedemo/cmd"
  11. )
  12. func main() {
  13. //入口
  14. cmd.Execute()
  15. }

cmd/root.go

  1. package cmd
  2. import (
  3. "codedemo/config"
  4. "codedemo/routes"
  5. "fmt"
  6. "os"
  7. "github.com/gin-gonic/gin"
  8. "github.com/sirupsen/logrus"
  9. "github.com/spf13/cobra"
  10. "github.com/spf13/viper"
  11. )
  12. var (
  13. cfgFile string
  14. serverPort int
  15. )
  16. var rootCmd = &cobra.Command{
  17. Use: "server",
  18. Short: "about the code",
  19. Long: "summary of status codes from zisefeizhu",
  20. Run: func(cmd *cobra.Command, args []string) {
  21. fmt.Println("啟動引數: ", args)
  22. httpServer()
  23. },
  24. }
  25. func init() {
  26. logrus.Infoln("init root.go...")
  27. cobra.OnInitialize(initConifg)
  28. rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $CURRENT_DIR/config/config.yaml)")
  29. rootCmd.Flags().IntVarP(&serverPort, "port", "p", 9001, "port on which the server will listen")
  30. }
  31. // 初始化配置
  32. func initConifg() {
  33. config.Loader(cfgFile)
  34. config.InitLog()
  35. }
  36. func httpServer() {
  37. logrus.Infoln("server start...")
  38. defer func() {
  39. logrus.Infoln("server exit..")
  40. }()
  41. //設定模式,設定模式要放在呼叫Default()函式之前
  42. gin.SetMode(viper.GetString("runmode"))
  43. logrus.Infoln("runmode: ", viper.GetString("runmode"))
  44. // 路由設定
  45. g := gin.Default()
  46. routes.Init(g)
  47. g.Run(fmt.Sprintf(":%d", serverPort))
  48. }
  49. // Execute rootCmd
  50. func Execute() {
  51. if err := rootCmd.Execute(); err != nil {
  52. logrus.Fatalln(err)
  53. os.Exit(1)
  54. }
  55. }

config/cfg.go

  1. package config
  2. import (
  3. "fmt"
  4. "github.com/gin-gonic/gin"
  5. "github.com/sirupsen/logrus"
  6. "github.com/spf13/viper"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. )
  12. // Loader 載入配置檔案
  13. func Loader(cfgFile string) {
  14. if cfgFile == "" {
  15. path, _ := os.Getwd()
  16. cfgFile = path + "/config/config.yaml"
  17. fmt.Println(cfgFile)
  18. }
  19. viper.SetConfigFile(cfgFile) //用來指定配置檔案的名稱
  20. viper.SetEnvPrefix("ENV") //SetEnvPrefix會設定一個環境變數的字首名
  21. viper.AutomaticEnv() //會獲取所有的環境變數,同時如果設定過了字首則會自動補全字首名
  22. replacer := strings.NewReplacer(".", "_") //NewReplacer() 使用提供的多組old、new字串對建立並返回一個*Replacer
  23. viper.SetEnvKeyReplacer(replacer)
  24. if err := viper.ReadInConfig(); err != nil {
  25. fmt.Printf("config file error: %s\n", err)
  26. os.Exit(1)
  27. }
  28. }
  29. // InitLog 初始化日誌
  30. func InitLog() {
  31. // log.logrus_json
  32. if viper.GetBool("log.logrus_json") {
  33. logrus.SetFormatter(&logrus.JSONFormatter{})
  34. }
  35. // log.logrus_level
  36. switch viper.GetString("log.logrus_level") {
  37. case "trace":
  38. logrus.SetLevel(logrus.TraceLevel)
  39. case "debug":
  40. logrus.SetLevel(logrus.DebugLevel)
  41. case "info":
  42. logrus.SetLevel(logrus.InfoLevel)
  43. case "warn":
  44. logrus.SetLevel(logrus.WarnLevel)
  45. case "error":
  46. logrus.SetLevel(logrus.ErrorLevel)
  47. }
  48. // log.logrus_file
  49. if viper.GetBool("log.file") {
  50. logrusFile := viper.GetString("log.logrus_file")
  51. os.MkdirAll(filepath.Dir(logrusFile), os.ModePerm)
  52. file, err := os.OpenFile(logrusFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
  53. if err == nil {
  54. if viper.GetBool("log.logrus_console") {
  55. logrus.SetOutput(io.MultiWriter(file, os.Stdout))
  56. } else {
  57. logrus.SetOutput(file)
  58. }
  59. }
  60. // log.gin_file & log.gin_console
  61. ginFile := viper.GetString("log.gin_file")
  62. os.MkdirAll(filepath.Dir(ginFile), os.ModePerm)
  63. file, err = os.OpenFile(ginFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
  64. if err == nil {
  65. if viper.GetBool("log.gin_console") {
  66. gin.DefaultWriter = io.MultiWriter(file, os.Stdout)
  67. } else {
  68. gin.DefaultWriter = io.MultiWriter(file)
  69. }
  70. }
  71. }
  72. // default
  73. logrus.SetReportCaller(true)
  74. }

config/config.yaml

  1. prefix_path: /zisefeizhu/api/v1 #api路徑
  2. gormlog: true # gorm 的日誌模式, true 是詳細日誌, false 不記錄日誌

routes層

routes.go

  1. package routes
  2. import (
  3. "codedemo/controller"
  4. "github.com/gin-contrib/cors"
  5. "github.com/gin-gonic/gin"
  6. "github.com/spf13/viper"
  7. )
  8. func Init(g *gin.Engine) {
  9. prefixPath := viper.GetString("prefix_path")
  10. g.Use(cors.Default())
  11. // 叢集
  12. codedemo := g.Group(prefixPath + "/code")
  13. {
  14. codedemo.GET("/codedemo",controller.GetName)
  15. }
  16. }

controller 層

name.go

  1. package controller
  2. import (
  3. "codedemo/controller/validation"
  4. "codedemo/pkg/globalcode"
  5. "codedemo/service"
  6. "github.com/gin-gonic/gin"
  7. "strings"
  8. )
  9. func GetName(c *gin.Context)() {
  10. var param validation.GetNameRepos
  11. if err := c.ShouldBind(&param); err != nil {
  12. Response(c, globalcode.PARAMETER_ERR, err.Error())
  13. return
  14. }
  15. param.Name = strings.TrimSpace(param.Name)
  16. if param.Name == "zise" {
  17. name := service.CodeDemo(param.Name)
  18. Response(c, globalcode.SUCCESS ,name)
  19. } else {
  20. // 模仿自定義狀態碼然後返回前端
  21. }
  22. }

validation/name.go

  1. package validation
  2. // GetNameRepos 前端param
  3. type GetNameRepos struct {
  4. Name string `json:"name" form:"name" binding:"required"` // 姓名不能為空
  5. }

base.go

  1. package controller
  2. import (
  3. "codedemo/pkg/globalcode"
  4. "github.com/gin-gonic/gin"
  5. "net/http"
  6. )
  7. func Response(c *gin.Context, code int, data interface{}) {
  8. c.JSON(http.StatusOK, gin.H{
  9. "code": code,
  10. "message": globalcode.Msg[code],
  11. "data": data,
  12. })
  13. }

service 層

name.go

  1. package service
  2. func CodeDemo(name string) string {
  3. return name + "feizhu"
  4. }

pkg/globalcode 層

  • 狀態碼處理,本篇的重心

code.go

  • 定義狀態碼
  1. package globalcode
  2. var (
  3. SUCCESS = 200
  4. PARAMETER_ERR = 10000
  5. )

message.go

  • 狀態碼對應的資訊
  1. package globalcode
  2. // Msg 全域性狀態碼
  3. var Msg = map[int]string{
  4. SUCCESS: "請求成功",
  5. PARAMETER_ERR: "請求引數錯誤",

驗證

    1. 錯誤code

    1. 正確code