一.项目初始化
1.目录结构
C:. │ config-debug.yaml │ config-pro.yaml │ main.go │ ├─api │ └─goods │ goods.go │ ├─config │ config.go │ ├─forms │ goods.go │ ├─global │ golbal.go │ ├─initialize │ config.go │ logger.go │ router.go │ srv_conn.go │ validator.go │ ├─middlewares │ admin.go │ cors.go │ jwt.go │ ├─models │ request.go │ ├─proto │ goods.pb.go │ goods.proto │ ├─router │ goods.go │ ├─utils │ addr.go │ ├─validator └─zap_test │ main.go │ └─zap_file main.go
2.代码
(1)接口处理及返回代码
goods-web/api/goods/goods.go
package goods import ( "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "mxshop-api/goods-web/global" "net/http" "strings" ) func removeTopStruct(fileds map[string]string) map[string]string { rsp := map[string]string{} for field, err := range fileds { rsp[field[strings.Index(field, ".")+1:]] = err } return rsp } func HandleGrpcErrorToHttp(err error, c *gin.Context) { // 将GRPC的code转换成HTTP的状态码 if err != nil { if e, ok := status.FromError(err); ok { switch e.Code() { case codes.NotFound: c.JSON(http.StatusNotFound, gin.H{ "msg": e.Message(), }) case codes.Internal: c.JSON(http.StatusInternalServerError, gin.H{ "msg": "内部错误", }) case codes.InvalidArgument: c.JSON(http.StatusBadRequest, gin.H{ "msg": "参数错误", }) case codes.Unavailable: c.JSON(http.StatusInternalServerError, gin.H{ "msg": "用户服务不可用", }) default: c.JSON(http.StatusInternalServerError, gin.H{ "msg": "其他错误", }) } return } } } func HandleValidatorError(ctx *gin.Context, err error) { //定义统一返回的报错处理 errs, ok := err.(validator.ValidationErrors) if !ok { ctx.JSON(http.StatusOK, gin.H{ "msg": err.Error(), }) } ctx.JSON(http.StatusBadRequest, gin.H{ "error": removeTopStruct(errs.Translate(global.Trans)), }) return } func List(ctx *gin.Context) { ctx.JSON(http.StatusOK, "ww") }
(2)配置文件
goods-web/config/config.go
package config type GoodsSrvConfig struct { Host string `mapstructure:"host" json:"host"` Port int `mapstructure:"port" json:"port"` Name string `mapstructure:"name" json:"name"` } type ServerConfig struct { Name string `mapstructure:"name" json:"name"` Port int `mapstructure:"port" json:"port"` UserSrvInfo GoodsSrvConfig `mapstructure:"goods_srv" json:"goods_srv"` JWTInfo JWTConfig `mapstructure:"jwt" json:"jwt"` ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"` } type JWTConfig struct { SigningKey string `mapstructure:"key" json:"key"` } type ConsulConfig struct { Host string `mapstructure:"host" json:"host"` Port int `mapstructure:"port" json:"port"` } type Nacos struct { Host string `mapstructure:"host" json:"host"` Port uint64 `mapstructure:"port" json:"port"` User string `mapstructure:"user" json:"user"` Password string `mapstructure:"password" json:"password"` NameSpace string `mapstructure:"namespace" json:"namespace"` DataId string `mapstructure:"dataid" json:"dataid"` Group string `mapstructure:"group" json:"group"` }
(3)全局变量
goods-web/global/golbal.go
package global import ( ut "github.com/go-playground/universal-translator" "mxshop-api/goods-web/config" "mxshop-api/goods-web/proto" ) var ( Trans ut.Translator ServerConfig *config.ServerConfig = &config.ServerConfig{} GoodsSrvClient proto.GoodsClient NacosConfig *config.Nacos = &config.Nacos{} )
(3)初始化
配置初始化:goods-web/initialize/config.go
package initialize import ( "encoding/json" "fmt" "github.com/nacos-group/nacos-sdk-go/clients" "github.com/nacos-group/nacos-sdk-go/common/constant" "github.com/nacos-group/nacos-sdk-go/vo" "github.com/spf13/viper" "go.uber.org/zap" "mxshop-api/goods-web/global" ) func GetEnvInfo(env string) bool { viper.AutomaticEnv() return viper.GetBool(env) //刚才设置的环境变量 想要生效 我们必须得重启goland } //func InitConfig() { //读取本地配置文件时候的配置 // fmt.Println(GetEnvInfo("MXSHOP_DEBUG")) // //配置环境变量 // debug := GetEnvInfo("MXSHOP_DEBUG") // configFilePrefix := "config" // configFileName := fmt.Sprintf("goods-web/%s-pro.yaml", configFilePrefix) // if debug { // configFileName = fmt.Sprintf("goods-web/%s-debug.yaml", configFilePrefix) // } // // v := viper.New() // //文件的路径如何设置 // v.SetConfigFile(configFileName) // if err := v.ReadInConfig(); err != nil { // panic(err) // } // if err := v.Unmarshal(global.ServerConfig); err != nil { // panic(err) // } // zap.S().Infof("配置文件:&v", configFileName) // // zap.S().Infof("配置信息:&v", global.ServerConfig) // // //viper的功能 - 动态监控变化 // v.WatchConfig() // v.OnConfigChange(func(e fsnotify.Event) { // zap.S().Infof("配置产生变化:&v", e.Name) // // fmt.Println("config file channed: ", e.Name) // _ = v.ReadInConfig() // _ = v.Unmarshal(&global.ServerConfig) // zap.S().Infof("配置信息:&v", global.ServerConfig) // }) //} func InitConfig() { //读取nacos的配置 fmt.Println(GetEnvInfo("MXSHOP_DEBUG")) //配置环境变量 debug := GetEnvInfo("MXSHOP_DEBUG") configFilePrefix := "config" configFileName := fmt.Sprintf("goods-web/%s-pro.yaml", configFilePrefix) if debug { configFileName = fmt.Sprintf("goods-web/%s-debug.yaml", configFilePrefix) } v := viper.New() //文件的路径如何设置 v.SetConfigFile(configFileName) if err := v.ReadInConfig(); err != nil { panic(err) } if err := v.Unmarshal(global.NacosConfig); err != nil { panic(err) } zap.S().Infof("配置信息:&v", global.NacosConfig) // 从nacos中读取配置信息 sc := []constant.ServerConfig{ { IpAddr: global.NacosConfig.Host, Port: global.NacosConfig.Port, }, } cc := constant.ClientConfig{ NamespaceId: global.NacosConfig.NameSpace, // 如果需要支持多namespace,我们可以场景多个client,它们有不同的NamespaceId TimeoutMs: 5000, NotLoadCacheAtStart: true, LogDir: "tmp/nacos/log", CacheDir: "tmp/nacos/cache", LogLevel: "debug", } configClient, err := clients.CreateConfigClient(map[string]interface{}{ "serverConfigs": sc, "clientConfig": cc, }) if err != nil { panic(err) } content, err := configClient.GetConfig(vo.ConfigParam{ DataId: global.NacosConfig.DataId, Group: global.NacosConfig.Group}) if err != nil { panic(err) } //读取配置 //fmt.Println(content) //fmt.Println(content) //字符串 - yaml //想要将一个json字符串转换成struct,需要去设置这个struct的tag err = json.Unmarshal([]byte(content), &global.ServerConfig) if err != nil { zap.S().Fatalf("服务nacos配置失败:%s", err.Error()) } fmt.Println(&global.ServerConfig) }
日志初始化:goods-web/initialize/logger.go
package initialize import "go.uber.org/zap" func InitLogger() { logger, _ := zap.NewProduction() zap.ReplaceGlobals(logger) }
路由初始化:goods-web/initialize/router.go
package initialize import ( "github.com/gin-gonic/gin" "mxshop-api/goods-web/middlewares" "mxshop-api/goods-web/router" "net/http" ) func Routers() *gin.Engine { Router := gin.Default() Router.GET("health", func(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "success": true, }) }) //配置跨域 Router.Use(middlewares.Cors()) ApiGroup := Router.Group("/g/v1") router.InitGoodsRoute(ApiGroup) return Router }
连接grpc初始化:goods-web/initialize/srv_conn.go
package initialize import ( "fmt" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "mxshop-api/goods-web/global" "mxshop-api/goods-web/proto" _ "github.com/mbobakov/grpc-consul-resolver" // It's important ) func InitSrvConn() { userConn, err := grpc.Dial( fmt.Sprintf("consul://%s:%d/%s?wait=14s", global.ServerConfig.ConsulInfo.Host, global.ServerConfig.ConsulInfo.Port, global.ServerConfig.UserSrvInfo.Name), //grpc.WithInsecure(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), ) if err != nil { zap.S().Fatal("【InitSrvConn】链接【用户失败】") } global.GoodsSrvClient = proto.NewGoodsClient(userConn) }
翻译初始化goods-web/initialize/validator.go
package initialize import ( "fmt" "mxshop-api/goods-web/global" "reflect" "strings" "github.com/gin-gonic/gin/binding" "github.com/go-playground/locales/en" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" en_translations "github.com/go-playground/validator/v10/translations/en" zh_translations "github.com/go-playground/validator/v10/translations/zh" ) func InitTrans(locale string) (err error) { //修改gin框架中的validator引擎属性, 实现定制 if v, ok := binding.Validator.Engine().(*validator.Validate); ok { //注册一个获取json的tag的自定义方法 v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) zhT := zh.New() //中文翻译器 enT := en.New() //英文翻译器 //第一个参数是备用的语言环境,后面的参数是应该支持的语言环境 uni := ut.New(enT, zhT, enT) global.Trans, ok = uni.GetTranslator(locale) if !ok { return fmt.Errorf("uni.GetTranslator(%s)", locale) } switch locale { case "en": en_translations.RegisterDefaultTranslations(v, global.Trans) case "zh": zh_translations.RegisterDefaultTranslations(v, global.Trans) default: en_translations.RegisterDefaultTranslations(v, global.Trans) } return } return }
(3)认证相关代码
权限:goods-web/middlewares/admin.go
package middlewares import ( "github.com/gin-gonic/gin" "mxshop-api/goods-web/models" "net/http" ) func IsAdminAuth() gin.HandlerFunc { return func(ctx *gin.Context) { claims, _ := ctx.Get("claims") currentUser := claims.(*models.CustomClaims) if currentUser.AuthorityId != 2 { // 管理员 ctx.JSON(http.StatusForbidden, gin.H{ "msg": "无权限", }) ctx.Abort() return } ctx.Next() } }
跨域:goods-web/middlewares/cors.go
package middlewares import ( "github.com/gin-gonic/gin" "net/http" ) func Cors() gin.HandlerFunc { return func(c *gin.Context) { method := c.Request.Method c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token") c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT") c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") c.Header("Access-Control-Allow-Credentials", "true") if method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) } } }
认证:goods-web/middlewares/jwt.go
package middlewares import ( "errors" "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "mxshop-api/goods-web/global" "mxshop-api/goods-web/models" "net/http" "time" ) func JWTAuth() gin.HandlerFunc { return func(c *gin.Context) { // 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localSstorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录 token := c.Request.Header.Get("x-token") if token == "" { c.JSON(http.StatusUnauthorized, map[string]string{ "msg": "请登录", }) c.Abort() return } j := NewJWT() // parseToken 解析token包含的信息 claims, err := j.ParseToken(token) if err != nil { if err == TokenExpired { if err == TokenExpired { c.JSON(http.StatusUnauthorized, map[string]string{ "msg": "授权已过期", }) c.Abort() return } } c.JSON(http.StatusUnauthorized, "未登陆") c.Abort() return } c.Set("claims", claims) c.Set("userId", claims.ID) c.Next() } } type JWT struct { SigningKey []byte } var ( TokenExpired = errors.New("Token is expired") TokenNotValidYet = errors.New("Token not active yet") TokenMalformed = errors.New("That's not even a token") TokenInvalid = errors.New("Couldn't handle this token:") ) func NewJWT() *JWT { return &JWT{ []byte(global.ServerConfig.JWTInfo.SigningKey), //可以设置过期时间 } } // 创建一个token func (j *JWT) CreateToken(claims models.CustomClaims) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(j.SigningKey) } // 解析 token func (j *JWT) ParseToken(tokenString string) (*models.CustomClaims, error) { token, err := jwt.ParseWithClaims(tokenString, &models.CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) { return j.SigningKey, nil }) if err != nil { if ve, ok := err.(*jwt.ValidationError); ok { if ve.Errors&jwt.ValidationErrorMalformed != 0 { return nil, TokenMalformed } else if ve.Errors&jwt.ValidationErrorExpired != 0 { // Token is expired return nil, TokenExpired } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { return nil, TokenNotValidYet } else { return nil, TokenInvalid } } } if token != nil { if claims, ok := token.Claims.(*models.CustomClaims); ok && token.Valid { return claims, nil } return nil, TokenInvalid } else { return nil, TokenInvalid } } // 更新token func (j *JWT) RefreshToken(tokenString string) (string, error) { jwt.TimeFunc = func() time.Time { return time.Unix(0, 0) } token, err := jwt.ParseWithClaims(tokenString, &models.CustomClaims{}, func(token *jwt.Token) (interface{}, error) { return j.SigningKey, nil }) if err != nil { return "", err } if claims, ok := token.Claims.(*models.CustomClaims); ok && token.Valid { jwt.TimeFunc = time.Now claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix() return j.CreateToken(*claims) } return "", TokenInvalid }
(3)model
goods-web/models/request.go
package models import ( "github.com/dgrijalva/jwt-go" ) type CustomClaims struct { ID uint NickName string AuthorityId uint jwt.StandardClaims }
(3)proto文件
goods-web/proto/goods.proto
syntax = "proto3"; import "google/protobuf/empty.proto"; option go_package = ".;proto"; service Goods{ //商品接口 rpc GoodsList(GoodsFilterRequest) returns(GoodsListResponse); //现在用户提交订单有多个商品GoodsListResponse,你得批量查询商品的信息吧 rpc BatchGetGoods(BatchGoodsIdInfo) returns(GoodsListResponse); //批量获取商品信息 rpc CreateGoods(CreateGoodsInfo) returns (GoodsInfoResponse); rpc DeleteGoods(DeleteGoodsInfo) returns (google.protobuf.Empty); rpc UpdateGoods(CreateGoodsInfo) returns (google.protobuf.Empty); rpc GetGoodsDetail(GoodInfoRequest) returns(GoodsInfoResponse); //商品分类 rpc GetAllCategorysList(google.protobuf.Empty) returns(CategoryListResponse); //获取所有的分类 //获取子分类 rpc GetSubCategory(CategoryListRequest) returns(SubCategoryListResponse); rpc CreateCategory(CategoryInfoRequest) returns(CategoryInfoResponse); //新建分类信息 rpc DeleteCategory(DeleteCategoryRequest) returns(google.protobuf.Empty); //删除分类 rpc UpdateCategory(CategoryInfoRequest) returns(google.protobuf.Empty); //修改分类信息 //品牌和轮播图 rpc BrandList(BrandFilterRequest) returns(BrandListResponse); //批量获取品牌信息 rpc CreateBrand(BrandRequest) returns(BrandInfoResponse); //新建品牌信息 rpc DeleteBrand(BrandRequest) returns(google.protobuf.Empty); //删除品牌 rpc UpdateBrand(BrandRequest) returns(google.protobuf.Empty); //修改品牌信息 //轮播图 rpc BannerList(google.protobuf.Empty) returns(BannerListResponse); //获取轮播列表信息 rpc CreateBanner(BannerRequest) returns(BannerResponse); //添加banner图 rpc DeleteBanner(BannerRequest) returns(google.protobuf.Empty); //删除轮播图 rpc UpdateBanner(BannerRequest) returns(google.protobuf.Empty); //修改轮播图 //品牌分类 rpc CategoryBrandList(CategoryBrandFilterRequest) returns(CategoryBrandListResponse); //获取轮播列表信息 //通过category获取brands rpc GetCategoryBrandList(CategoryInfoRequest) returns(BrandListResponse); rpc CreateCategoryBrand(CategoryBrandRequest) returns(CategoryBrandResponse); //添加banner图 rpc DeleteCategoryBrand(CategoryBrandRequest) returns(google.protobuf.Empty); //删除轮播图 rpc UpdateCategoryBrand(CategoryBrandRequest) returns(google.protobuf.Empty); //修改轮播图 } message CategoryListRequest { int32 id = 1; int32 level = 2; } message CategoryInfoRequest { int32 id = 1; string name = 2; int32 parentCategory = 3; int32 level = 4; bool isTab = 5; } message DeleteCategoryRequest { int32 id = 1; } message QueryCategoryRequest { int32 id = 1; string name = 2; } message CategoryInfoResponse { int32 id = 1; string name = 2; int32 parentCategory = 3; int32 level = 4; bool isTab = 5; } message CategoryListResponse { int32 total = 1; repeated CategoryInfoResponse data = 2; string jsonData = 3; } message SubCategoryListResponse { int32 total = 1; CategoryInfoResponse info = 2; repeated CategoryInfoResponse subCategorys = 3; } message CategoryBrandFilterRequest { int32 pages = 1; int32 pagePerNums = 2; } message FilterRequest { int32 pages = 1; int32 pagePerNums = 2; } message CategoryBrandRequest{ int32 id = 1; int32 categoryId = 2; int32 brandId = 3; } message CategoryBrandResponse{ int32 id = 1; BrandInfoResponse brand = 2; CategoryInfoResponse category = 3; } message BannerRequest { int32 id = 1; int32 index = 2; string image = 3; string url = 4; } message BannerResponse { int32 id = 1; int32 index = 2; string image = 3; string url = 4; } message BrandFilterRequest { int32 pages = 1; int32 pagePerNums = 2; } message BrandRequest { int32 id = 1; string name = 2; string logo = 3; } message BrandInfoResponse { int32 id = 1; string name = 2; string logo = 3; } message BrandListResponse { int32 total = 1; repeated BrandInfoResponse data = 2; } message BannerListResponse { int32 total = 1; repeated BannerResponse data = 2; } message CategoryBrandListResponse { int32 total = 1; repeated CategoryBrandResponse data = 2; } message BatchGoodsIdInfo { repeated int32 id = 1; } message DeleteGoodsInfo { int32 id = 1; } message CategoryBriefInfoResponse { int32 id = 1; string name = 2; } message CategoryFilterRequest { int32 id = 1; bool isTab = 2; } message GoodInfoRequest { int32 id = 1; } message CreateGoodsInfo { int32 id = 1; string name = 2; string goodsSn = 3; int32 stocks = 7; //库存, float marketPrice = 8; float shopPrice = 9; string goodsBrief = 10; string goodsDesc = 11; bool shipFree = 12; repeated string images = 13; repeated string descImages = 14; string goodsFrontImage = 15; bool isNew = 16; bool isHot = 17; bool onSale = 18; int32 categoryId = 19; int32 brandId = 20; } message GoodsReduceRequest { int32 GoodsId = 1; int32 nums = 2; } message BatchCategoryInfoRequest { repeated int32 id = 1; int32 goodsNums = 2; int32 brandNums = 3; } message GoodsFilterRequest { int32 priceMin = 1; int32 priceMax = 2; bool isHot = 3; bool isNew = 4; bool isTab = 5; int32 topCategory = 6; int32 pages = 7; int32 pagePerNums = 8; string keyWords = 9; int32 brand = 10; } message GoodsInfoResponse { int32 id = 1; int32 categoryId = 2; string name = 3; string goodsSn = 4; int32 clickNum = 5; int32 soldNum = 6; int32 favNum = 7; float marketPrice = 9; float shopPrice = 10; string goodsBrief = 11; string goodsDesc = 12; bool shipFree = 13; repeated string images = 14; repeated string descImages = 15; string goodsFrontImage = 16; bool isNew = 17; bool isHot = 18; bool onSale = 19; int64 addTime = 20; CategoryBriefInfoResponse category = 21; BrandInfoResponse brand = 22; } message GoodsListResponse { int32 total = 1; repeated GoodsInfoResponse data = 2; } //安装pip install grpcio -i https://pypi.douban.com/simple //pip install grpcio-tools -i https://pypi.douban.com/simple //生成proto: python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I . user.proto // // go: protoc -I . goods.proto --go_out=plugins=grpc:.
(3)路由配置
goods-web/router/goods.go
package router import ( "github.com/gin-gonic/gin" "mxshop-api/goods-web/api/goods" "mxshop-api/goods-web/middlewares" ) func InitGoodsRoute(Router *gin.RouterGroup) { GoodsRouter := Router.Group("goods") { GoodsRouter.GET("list", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.List) } }
(3)获取地址
goods-web/utils/addr.go
package utils import "net" func GetFreePort() (int, error) { addr, err := net.ResolveTCPAddr("tcp", "localhost:0") if addr != nil { return 0, nil } l, err := net.ListenTCP("tcp", addr) if err != nil { return 0, nil } defer l.Close() return l.Addr().(*net.TCPAddr).Port, nil }
(3)配置文件
goods-web/config-debug.yaml
#nacos: host: ' port: 80 namespace: '3aaf14b9-6c20-4dab-89df-a15082ba6301' user: 'nacos' password: '' dataid: 'goods-web.json' group: 'dev'
(3)主入口
goods-web/main.go
package main import ( "fmt" "github.com/spf13/viper" "go.uber.org/zap" "mxshop-api/goods-web/global" "mxshop-api/goods-web/utils" "mxshop-api/goods-web/initialize" ) func main() { //初始化logger initialize.InitLogger() //初始化配置文件 initialize.InitConfig() //初始化routes Router := initialize.Routers() //初始化翻译 if err := initialize.InitTrans("zh"); err != nil { panic(err) } //初始化srv的链接 initialize.InitSrvConn() //如果是本地开发环境,端口固定 viper.AutomaticEnv() debug := viper.GetBool("MXSHOP_DEBUG") fmt.Println(debug) if !debug { port, err := utils.GetFreePort() if err == nil { global.ServerConfig.Port = port } } /* 1. S()可以获取一个全局的sugar,可以让我们自己设置一个全局的logger 2. 日志是分级别的,debug, info , warn, error, fetal 3. S函数和L函数很有用, 提供了一个全局的安全访问logger的途径 */ zap.S().Infof("启动服务") //port := 8881 if err := Router.Run(fmt.Sprintf(":%d", global.ServerConfig.Port)); err != nil { zap.S().Panic("启动失败:", err.Error()) } }
标签:web,goods,服务,string,err,int32,go From: https://www.cnblogs.com/wlike/p/16893500.html