├── README.md ├── mxshop-api ├── .DS_Store ├── .idea │ ├── .gitignore │ ├── .name │ ├── modules.xml │ ├── mxshop-api.iml │ ├── vcs.xml │ └── watcherTasks.xml ├── go.mod ├── go.sum ├── goods-web │ ├── api │ │ ├── banner │ │ │ └── banner.go │ │ ├── base.go │ │ ├── brands │ │ │ └── brands.go │ │ ├── category │ │ │ └── category.go │ │ └── goods │ │ │ └── goods.go │ ├── config-debug.yaml │ ├── config │ │ └── config.go │ ├── forms │ │ ├── banner.go │ │ ├── brands.go │ │ ├── category.go │ │ └── goods.go │ ├── global │ │ ├── global.go │ │ └── response │ │ │ └── user.go │ ├── initialize │ │ ├── config.go │ │ ├── logger.go │ │ ├── router.go │ │ ├── sentinel.go │ │ ├── srv_conn.go │ │ └── validator.go │ ├── main.go │ ├── middlewares │ │ ├── admin.go │ │ ├── cors.go │ │ ├── jwt.go │ │ └── tracing.go │ ├── models │ │ └── request.go │ ├── proto │ │ ├── goods.pb.go │ │ ├── goods.proto │ │ ├── inventory.pb.go │ │ └── inventory.proto │ ├── router │ │ ├── banner.go │ │ ├── brands.go │ │ ├── category.go │ │ └── goods.go │ ├── utils │ │ ├── otgrpc │ │ │ ├── README.md │ │ │ ├── client.go │ │ │ ├── errors.go │ │ │ ├── errors_test.go │ │ │ ├── options.go │ │ │ ├── package.go │ │ │ ├── server.go │ │ │ ├── shared.go │ │ │ └── test │ │ │ │ ├── interceptor_test.go │ │ │ │ └── otgrpc_testing │ │ │ │ ├── test.pb.go │ │ │ │ └── test.proto │ │ ├── register │ │ │ └── consul │ │ │ │ └── register.go │ │ └── utils.go │ └── validator │ │ └── validator.go ├── order-web │ ├── .DS_Store │ ├── api │ │ ├── base.go │ │ ├── order │ │ │ └── order.go │ │ ├── pay │ │ │ └── alipay.go │ │ └── shop_cart │ │ │ └── shop_cart.go │ ├── config-debug.yaml │ ├── config │ │ └── config.go │ ├── forms │ │ ├── order.go │ │ └── shop_cart.go │ ├── global │ │ ├── global.go │ │ └── response │ │ │ └── user.go │ ├── initialize │ │ ├── config.go │ │ ├── logger.go │ │ ├── router.go │ │ ├── sentinel.go │ │ ├── srv_conn.go │ │ └── validator.go │ ├── main.go │ ├── middlewares │ │ ├── admin.go │ │ ├── cors.go │ │ ├── jwt.go │ │ └── tracing.go │ ├── models │ │ └── request.go │ ├── proto │ │ ├── goods.pb.go │ │ ├── goods.proto │ │ ├── inventory.pb.go │ │ ├── inventory.proto │ │ ├── order.pb.go │ │ └── order.proto │ ├── router │ │ ├── order.go │ │ └── shop_cart.go │ ├── utils │ │ ├── otgrpc │ │ │ ├── README.md │ │ │ ├── client.go │ │ │ ├── errors.go │ │ │ ├── errors_test.go │ │ │ ├── options.go │ │ │ ├── package.go │ │ │ ├── server.go │ │ │ ├── shared.go │ │ │ └── test │ │ │ │ ├── interceptor_test.go │ │ │ │ └── otgrpc_testing │ │ │ │ ├── test.pb.go │ │ │ │ └── test.proto │ │ ├── register │ │ │ └── consul │ │ │ │ └── register.go │ │ └── utils.go │ └── validator │ │ └── validator.go ├── oss-web │ ├── config-debug.yaml │ ├── config-pro.yaml │ ├── config │ │ └── config.go │ ├── global │ │ └── global.go │ ├── handler │ │ └── oss.go │ ├── initialize │ │ ├── config.go │ │ ├── logger.go │ │ ├── router.go │ │ └── validator.go │ ├── main.go │ ├── middlewares │ │ ├── admin.go │ │ ├── cors.go │ │ └── jwt.go │ ├── models │ │ └── request.go │ ├── router │ │ └── oss.go │ ├── static │ │ ├── css │ │ │ └── style.css │ │ ├── js │ │ │ └── upload.js │ │ └── lib │ │ │ └── plupload-2.1.2 │ │ │ ├── js │ │ │ ├── Moxie.swf │ │ │ ├── Moxie.xap │ │ │ ├── moxie.js │ │ │ ├── moxie.min.js │ │ │ ├── plupload.dev.js │ │ │ ├── plupload.full.min.js │ │ │ └── plupload.min.js │ │ │ ├── license.txt │ │ │ └── readme.md │ ├── templates │ │ └── index.html │ └── utils │ │ ├── addr.go │ │ ├── oss.go │ │ └── register │ │ └── consul │ │ └── register.go ├── tmp │ └── nacos │ │ ├── cache │ │ └── config │ │ │ ├── goods-web.json@@dev@@1667c9ca-e13b-46b8-aa9f-a376de301f6f │ │ │ ├── goods-web.json@@dev@@fe432146-9559-44d1-8245-3541feacc9cf │ │ │ ├── order-web.json@@dev@@64c5ff72-e7ca-4ac8-b605-895ce39b6271 │ │ │ ├── order-web.json@@dev@@f9fc4b72-18a3-4335-94fb-f612c133ff59 │ │ │ ├── user-web.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f │ │ │ ├── user-web.json@@dev@@7ae18f62-e2b9-48bd-bff2-a49e7443f5bc │ │ │ ├── userop-web.json@@dev@@a66fc619-7970-4bdf-b703-9659ed4b2e29 │ │ │ └── userop-web.json@@dev@@c52838c8-880e-49ac-8478-d4c94e0ed154 │ │ └── log │ │ └── nacos-sdk.log ├── user-web │ ├── api │ │ ├── chaptcha.go │ │ ├── redis_test │ │ │ └── main.go │ │ ├── sms.go │ │ ├── test │ │ │ └── main.go │ │ └── user.go │ ├── config-debug.yaml │ ├── config │ │ └── config.go │ ├── forms │ │ ├── sms.go │ │ └── user.go │ ├── global │ │ ├── global.go │ │ └── response │ │ │ └── user.go │ ├── index.html │ ├── initialize │ │ ├── config.go │ │ ├── logger.go │ │ ├── router.go │ │ ├── srv_conn.go │ │ └── validator.go │ ├── main.go │ ├── middlewares │ │ ├── admin.go │ │ ├── cors.go │ │ ├── jwt.go │ │ └── tracing.go │ ├── models │ │ └── request.go │ ├── proto │ │ ├── user.pb.go │ │ └── user.proto │ ├── router │ │ ├── base.go │ │ └── user.go │ ├── tmp │ │ └── nacos │ │ │ ├── cache │ │ │ └── config │ │ │ │ ├── user-web.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f │ │ │ │ └── user-web@@DEV@@555d81b1-6cbf-4b69-b48a-a034c282c69f │ │ │ └── log │ │ │ └── nacos-sdk.log │ ├── utils │ │ ├── otgrpc │ │ │ ├── README.md │ │ │ ├── client.go │ │ │ ├── errors.go │ │ │ ├── errors_test.go │ │ │ ├── options.go │ │ │ ├── package.go │ │ │ ├── server.go │ │ │ ├── shared.go │ │ │ └── test │ │ │ │ ├── interceptor_test.go │ │ │ │ └── otgrpc_testing │ │ │ │ ├── test.pb.go │ │ │ │ └── test.proto │ │ ├── register │ │ │ └── consul │ │ │ │ └── register.go │ │ └── utils.go │ └── validator │ │ └── validator.go └── userop-web │ ├── api │ ├── address │ │ └── address.go │ ├── base.go │ ├── message │ │ └── message.go │ └── userfav │ │ └── userfav.go │ ├── config-debug.yaml │ ├── config │ └── config.go │ ├── forms │ ├── address.go │ ├── message.go │ └── userfav.go │ ├── global │ ├── global.go │ └── response │ │ └── user.go │ ├── initialize │ ├── config.go │ ├── logger.go │ ├── router.go │ ├── srv_conn.go │ └── validator.go │ ├── main.go │ ├── middlewares │ ├── admin.go │ ├── cors.go │ └── jwt.go │ ├── models │ └── request.go │ ├── proto │ ├── address.pb.go │ ├── address.proto │ ├── goods.pb.go │ ├── goods.proto │ ├── message.pb.go │ ├── message.proto │ ├── userfav.pb.go │ └── userfav.proto │ ├── router │ ├── address.go │ ├── message.go │ └── userfav.go │ ├── utils │ ├── register │ │ └── consul │ │ │ └── register.go │ └── utils.go │ └── validator │ └── validator.go └── mxshop_srvs ├── .DS_Store ├── go.mod ├── go.sum ├── goods_srv ├── config-debug.yaml ├── config │ └── config.go ├── global │ └── global.go ├── handler │ ├── banner.go │ ├── base.go │ ├── brand.go │ ├── category.go │ ├── category_brand.go │ └── goods.go ├── initialize │ ├── config.go │ ├── db.go │ ├── es.go │ └── logger.go ├── main.go ├── model │ ├── base.go │ ├── es_goods.go │ ├── goods.go │ └── main │ │ └── main.go ├── proto │ ├── goods.pb.go │ └── goods.proto ├── test │ ├── Banner.go │ ├── Brand.go │ ├── base.go │ ├── category.go │ ├── category_brand.go │ └── goods.go ├── tmp │ └── nacos │ │ └── cache │ │ └── config │ │ └── user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f └── utils │ └── utils.go ├── inventory_srv ├── config-debug.yaml ├── config │ └── config.go ├── global │ └── global.go ├── handler │ └── inventory.go ├── initialize │ ├── config.go │ ├── db.go │ └── logger.go ├── main.go ├── model │ ├── base.go │ ├── inventory.go │ └── main │ │ └── main.go ├── proto │ ├── inventory.pb.go │ └── inventory.proto ├── test │ └── base.go ├── tmp │ └── nacos │ │ └── cache │ │ └── config │ │ └── user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f └── utils │ ├── redisync │ └── main.go │ ├── register │ └── consul │ │ └── register.go │ └── utils.go ├── order_srv ├── config-debug.yaml ├── config │ └── config.go ├── global │ └── global.go ├── handler │ └── order.go ├── initialize │ ├── config.go │ ├── db.go │ ├── logger.go │ ├── rocketMQ.go │ └── srv_conn.go ├── main.go ├── model │ ├── base.go │ ├── main │ │ └── main.go │ └── order.go ├── proto │ ├── goods.pb.go │ ├── goods.proto │ ├── inventory.pb.go │ ├── inventory.proto │ ├── order.pb.go │ └── order.proto ├── test │ └── base.go ├── tmp │ └── nacos │ │ └── cache │ │ └── config │ │ └── user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f └── utils │ ├── otgrpc │ ├── README.md │ ├── client.go │ ├── errors.go │ ├── errors_test.go │ ├── options.go │ ├── package.go │ ├── server.go │ ├── shared.go │ └── test │ │ ├── interceptor_test.go │ │ └── otgrpc_testing │ │ ├── test.pb.go │ │ └── test.proto │ ├── redisync │ └── main.go │ ├── register │ └── consul │ │ └── register.go │ └── utils.go ├── tmp └── nacos │ ├── cache │ └── config │ │ ├── goods-srv.json@@dev@@1667c9ca-e13b-46b8-aa9f-a376de301f6f │ │ ├── goods-srv.json@@dev@@fe432146-9559-44d1-8245-3541feacc9cf │ │ ├── inventory-srv.json@@dev@@3ecfe162-5701-4759-81e2-2f494e97db06 │ │ ├── inventory.json@@dev@@47800a50-0d82-4a47-a961-28e5a6b6c4e0 │ │ ├── order-srv.json@@dev@@64c5ff72-e7ca-4ac8-b605-895ce39b6271 │ │ ├── order_srv.json@@dev@@f9fc4b72-18a3-4335-94fb-f612c133ff59 │ │ ├── user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f │ │ ├── user-srv.json@@dev@@7ae18f62-e2b9-48bd-bff2-a49e7443f5bc │ │ ├── userop-srv.json@@dev@@a66fc619-7970-4bdf-b703-9659ed4b2e29 │ │ └── userop-srv.json@@dev@@c52838c8-880e-49ac-8478-d4c94e0ed154 │ └── log │ └── nacos-sdk.log ├── user_srv ├── config-debug.yaml ├── config │ └── config.go ├── global │ └── global.go ├── handler │ └── user.go ├── initialize │ ├── config.go │ ├── db.go │ └── logger.go ├── main.go ├── model │ ├── main │ │ └── main.go │ └── user.go ├── proto │ ├── user.pb.go │ └── user.proto ├── test │ └── user.go ├── tmp │ └── nacos │ │ ├── cache │ │ └── config │ │ │ └── user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f │ │ └── log │ │ └── nacos-sdk.log └── utils │ └── utils.go └── userop_srv ├── config-debug.yaml ├── config └── config.go ├── global └── global.go ├── handler ├── address.go ├── base.go ├── message.go └── userfav.go ├── initialize ├── config.go ├── db.go └── logger.go ├── main.go ├── model ├── base.go ├── main │ └── main.go └── userop.go ├── proto ├── address.pb.go ├── address.proto ├── message.pb.go ├── message.proto ├── userfav.pb.go └── userfav.proto ├── test └── base.go ├── tmp └── nacos │ └── cache │ └── config │ └── user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f └── utils ├── redisync └── main.go ├── register └── consul │ └── register.go └── utils.go /README.md: -------------------------------------------------------------------------------- 1 | 2 | # mxshop电商系统 3 | ### 主要技术栈:Go、Grpc、Gin、Mysql、Redis、Elasticsearch、RocketMQ、Nacos、Consul、Jaeger、Sentinel 4 | ### Consul的安装(docker) 5 | ```shell 6 | docker run -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8600:8600/udp consul consul agent -dev -client=0.0.0.0 7 | ``` 8 | 开机启动consul 9 | ```shell 10 | docker container update --restart=always 容器名字 11 | ``` 12 | 浏览器访问 127.0.0.1:8500 13 | 14 | ### Nacos的安装(docker) 15 | ```shell 16 | docker run --name nacos-standalone -e MODE=standalone -e JVM_XMS=512m -e JVM_XMX=512m -e JVM_XMN=256m -p 8848:8848 -d nacos/nacos-server:latest 17 | ``` 18 | 访问:http://192.168.1.103:8848/nacos/index.html 19 | 用户名/密码:nacos/nacos 20 | 21 | 配置开机启动: 22 | ```shell 23 | docker container update --restart=always xxx 24 | ``` 25 | 26 | ### Elasticsearch的安装(docker) 27 | [Elasticsearch安装](https://learnku.com/articles/72845) 28 | 29 | ### 项目介绍 30 | 网站地址:http://iceymoss.top/mxshop 31 | * 基于JWT做访问鉴权token,Gin做路由分发、表单验证、解决跨域等。 32 | 33 | * 登录/注册功能:采用sever和web双层架构、使用viper包做配置解析、web层基于Gin做路由转发、使用redis实现注册验证码缓存服务、使用base64生成验证码图片做登录验证、srv层使用MD5盐值加密保证密码注册者知道的唯一性。 34 | 35 | * 商品服务功能:基于Elasticsearch实现商品搜索;完成如下接口:1.商品相关、2.商品品牌相关、3.商品分类类目相关、4.商品分类相关、5.商品主页轮播图相关。 36 | * 图片文件使用aliyun对象存储,使用服务端签名直传文件。 37 | * 库存服务:库存服务的核心在于保持数据的一致性,可用性,高性能,解决在分布式高并发场景下,如何保证数据一致性,库存服务引入了Redis锁和RocketMQ,来实现分布式高并发场景下的数据一致性,如何扣减库存,库存超时归还,重复归还商品问题以及接口需要幂等性。 38 | * 订单服务:基于grpc实现订单相关服务及购物车相关服务等各类接口,使用本地mysql事务保证本地数据一致性,从使用rocketMQ从订单服务到查询商品服务(跨服务),调用库存服务扣减库存(跨服务)的跨微服务调用,保证信息一致性。 39 | * 用户接口服务: 为用户提供操作接口其中实现了简单的地址,留言, 收藏等。 40 | * 基于Jaeger做微服务间链路追踪,使用Sentinel实现限流。 41 | -------------------------------------------------------------------------------- /mxshop-api/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iceymoss/mxshop/4613a07130e2c878a33457ac640216e501c9cc67/mxshop-api/.DS_Store -------------------------------------------------------------------------------- /mxshop-api/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /mxshop-api/.idea/.name: -------------------------------------------------------------------------------- 1 | mxshop-api -------------------------------------------------------------------------------- /mxshop-api/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mxshop-api/.idea/mxshop-api.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /mxshop-api/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mxshop-api/.idea/watcherTasks.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /mxshop-api/go.mod: -------------------------------------------------------------------------------- 1 | module mxshop-api 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect 7 | github.com/alibaba/sentinel-golang v1.0.4 8 | github.com/aliyun/alibaba-cloud-sdk-go v1.61.1662 9 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 10 | github.com/gin-gonic/gin v1.8.1 11 | github.com/go-playground/locales v0.14.0 12 | github.com/go-playground/universal-translator v0.18.0 13 | github.com/go-playground/validator/v10 v10.11.0 14 | github.com/go-redis/redis/v8 v8.11.5 15 | github.com/goccy/go-json v0.9.8 // indirect 16 | github.com/golang/protobuf v1.5.2 17 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 18 | github.com/hashicorp/consul/api v1.12.0 19 | github.com/mbobakov/grpc-consul-resolver v1.4.4 20 | github.com/mojocn/base64Captcha v1.3.5 21 | github.com/nacos-group/nacos-sdk-go v1.1.1 22 | github.com/opentracing/opentracing-go v1.2.0 23 | github.com/pelletier/go-toml/v2 v2.0.2 // indirect 24 | github.com/satori/go.uuid v1.2.0 25 | github.com/smartwalle/alipay/v3 v3.1.8 // indirect 26 | github.com/spf13/viper v1.12.0 27 | github.com/stretchr/testify v1.7.2 28 | github.com/uber/jaeger-client-go v2.30.0+incompatible 29 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 30 | go.uber.org/zap v1.21.0 31 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect 32 | golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect 33 | golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e 34 | golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect 35 | google.golang.org/grpc v1.47.0 36 | google.golang.org/protobuf v1.28.0 37 | 38 | ) 39 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/api/base.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/go-playground/validator/v10" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | 12 | "mxshop-api/goods-web/global" 13 | ) 14 | 15 | func removeTopStruct(fileds map[string]string) map[string]string { 16 | rsp := map[string]string{} 17 | for field, err := range fileds { 18 | rsp[field[strings.Index(field, ".")+1:]] = err 19 | } 20 | return rsp 21 | } 22 | 23 | //HandleValidatorErr 表单验证错误处理返回 24 | func HandleValidatorErr(c *gin.Context, err error) { 25 | errs, ok := err.(validator.ValidationErrors) 26 | if !ok { 27 | c.JSON(http.StatusOK, gin.H{ 28 | "msg": err.Error(), 29 | }) 30 | } 31 | c.JSON(http.StatusBadRequest, gin.H{ 32 | "error": removeTopStruct(errs.Translate(global.Trans)), 33 | }) 34 | return 35 | } 36 | 37 | //HandleGrpcErrToHttp grpc状态码转http 38 | func HandleGrpcErrToHttp(err error, c *gin.Context) { 39 | if err != nil { 40 | if e, ok := status.FromError(err); ok { 41 | switch e.Code() { 42 | case codes.NotFound: 43 | c.JSON(http.StatusNotFound, gin.H{ 44 | "msg": e.Message(), 45 | }) 46 | case codes.Internal: 47 | c.JSON(http.StatusInternalServerError, gin.H{ 48 | "msg": "内部错误", 49 | }) 50 | case codes.InvalidArgument: 51 | c.JSON(http.StatusBadRequest, gin.H{ 52 | "msg": "参数错误", 53 | }) 54 | case codes.Unavailable: 55 | c.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": "用户服务不可用", 57 | }) 58 | default: 59 | c.JSON(http.StatusInternalServerError, gin.H{ 60 | "msg": "其他错误", 61 | }) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/config-debug.yaml: -------------------------------------------------------------------------------- 1 | #name: 'goods-web' 2 | #port: 8081 3 | #user_srv: 4 | # name: 'user_srv' 5 | # host: '192.168.3.105' 6 | # port: 8080 7 | # 8 | #jwt: 9 | # key: '2Fsa$VCdnQi' 10 | # 11 | #sms: 12 | # key: 'LTAIHRo3a' 13 | # secret: 'AaRNRvZCZ' 14 | # 15 | #params: 16 | # sign_name: '生鲜小店' 17 | # code: 'SMS_2453481' 18 | # 19 | #redis: 20 | # host: '127.0.0.1' 21 | # port: 6379 22 | # expir: 300 23 | # 24 | #verify: 25 | # width: 5 26 | # 27 | #consul: 28 | # host: '192.168.3.105' 29 | # port: 8500 30 | 31 | 32 | host: '10.2.69.164' 33 | port: 8848 34 | namespace_id: 'fe432146-9559-44d1-8245-3541feacc9cf' 35 | user: 'nacos' 36 | password: 'nacos' 37 | data_id: 'goods-web.json' 38 | group: 'dev' 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/forms/banner.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | //BannerForm 轮播图表单验证 4 | //type BannerForm struct { 5 | // Index int `form:"index" json:"index" binding:"required"` 6 | // Image string `form:"image" json:"image" binding:"url"` 7 | // Url string `form:"url" json:"url" binding:"url"` 8 | //} 9 | 10 | type BannerForm struct { 11 | Image string `form:"image" json:"image" binding:"url"` 12 | Index int `form:"index" json:"index" binding:"required"` 13 | Url string `form:"url" json:"url" binding:"url"` 14 | } 15 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/forms/brands.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | //BrandForm 品牌表单验证 4 | type BrandForm struct { 5 | Name string `form:"name" json:"name" binding:"required,min=3,max=10"` 6 | Logo string `form:"logo" json:"logo" binding:"url"` 7 | } 8 | 9 | //CategoryBrandForm 品牌分类验证 10 | type CategoryBrandForm struct { 11 | CategoryId int `form:"category_id" json:"category_id" binding:"required"` 12 | BrandId int `form:"brand_id" json:"brand_id" binding:"required"` 13 | } 14 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/forms/category.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | //CategoryForm 商品分类表达验证 4 | type CategoryForm struct { 5 | Name string `form:"name" json:"name" binding:"required,min=3,max=20"` 6 | ParentCategory int32 `form:"parent" json:"parent"` 7 | Level int32 `form:"level" json:"level" binding:"required,oneof=1 2 3"` 8 | IsTab *bool `form:"is_tab" json:"is_tab" binding:"required"` 9 | } 10 | 11 | type UpdateCategoryForm struct { 12 | Name string `form:"name" json:"name" binding:"required,min=3,max=20"` 13 | IsTab *bool `form:"is_tab" json:"is_tab" binding:"required"` 14 | } 15 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/forms/goods.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | //GoodsFrom 表单验证 4 | type GoodsFrom struct { 5 | Name string `form:"name" json:"name" binding:"required,min=2,max=100"` 6 | GoodsSn string `form:"goods_sn" json:"goods_sn" binding:"required,min=2,lt=20"` 7 | Stocks int32 `form:"stocks" json:"stocks" binding:"required,min=1"` 8 | CategoryId int32 `form:"category" json:"category" binding:"required"` 9 | MarketPrice float32 `form:"market_price" json:"market_price" binding:"required,min=0"` 10 | ShopPrice float32 `form:"shop_price" json:"shop_price" binding:"required,min=0"` 11 | GoodsBrief string `form:"goods_brief" json:"goods_brief" binding:"required,min=3"` 12 | Images []string `form:"images" json:"images" binding:"required,min=1"` 13 | DescImages []string `form:"desc_images" json:"desc_images" binding:"required,min=1"` 14 | //GoodsDesc string `form:"desc" json:"desc" binding:"required,min=3"` 15 | ShipFree *bool `form:"ship_free" json:"ship_free" binding:"required"` 16 | FrontImage string `form:"front_image" json:"front_image" binding:"required,url"` 17 | Brand int32 `form:"brand" json:"brand" binding:"required"` 18 | } 19 | 20 | //GoodsStatusForm 商品状态表单验证 21 | type GoodsStatusForm struct { 22 | IsNew *bool `form:"new" json:"new" binding:"required"` 23 | IsHot *bool `form:"hot" json:"hot" binding:"required"` 24 | OnSale *bool `form:"sale" json:"sale" binding:"required"` 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop-api/goods-web/config" 5 | "mxshop-api/goods-web/proto" 6 | 7 | ut "github.com/go-playground/universal-translator" 8 | ) 9 | 10 | //需要的全局变量 11 | var ( 12 | Trans ut.Translator //声明一个全局翻译器 13 | ServerConfig *config.ServerConfig = &config.ServerConfig{} //声明配置信息 14 | GoodsSrvClient proto.GoodsClient //商品 grpc Client 15 | InventoryClient proto.InventoryClient //库存 grpc Client 16 | NacosConfig *config.NacosConfig = &config.NacosConfig{} 17 | ) 18 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/global/response/user.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | //时间格式转换 9 | type JsonTime time.Time 10 | 11 | //当数据调用c.JSON时,MarshalJSON会自动被调用 12 | func (j JsonTime) MarshalJSON() ([]byte, error) { 13 | stdtime := fmt.Sprintf("\"%s\"", time.Time(j).Format("2022-01-01")) 14 | return []byte(stdtime), nil 15 | } 16 | 17 | type UserResponse struct { 18 | Id int32 `json:"id"` 19 | NickName string `json:"name"` 20 | BirthDay JsonTime `json:"birthday"` 21 | Gender string `json:"gender"` 22 | Mobile string `json:"mobile"` 23 | } 24 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | // 1. zap.S可以配置一个全局的sugar,用来配置全局logger 10 | // 2. 日志级别:debug、info、warn、error、fetal 11 | // 3. S函数和L函数可以配置全局的安全的logger 12 | 13 | //InitLogger 初始化日志 14 | func InitLogger() { 15 | //初始化日志 16 | logger, err := zap.NewDevelopment() 17 | if err != nil { 18 | log.Fatal("日志初始化失败", err.Error()) 19 | } 20 | //使用全局logger 21 | zap.ReplaceGlobals(logger) 22 | } 23 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/initialize/router.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "mxshop-api/goods-web/middlewares" 5 | "mxshop-api/goods-web/router" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | //Routers 初始化及路由分发 12 | func Routers() *gin.Engine { 13 | Router := gin.Default() 14 | 15 | //配置跨越 16 | Router.Use(middlewares.Cors()) 17 | 18 | ApiGroup := Router.Group("g") 19 | //分发路由 20 | ApiGroup = ApiGroup.Group("v1") 21 | 22 | router.InitGoodsRouter(ApiGroup) //商品 23 | router.InitCategory(ApiGroup) //商品分类 24 | router.InitBanner(ApiGroup) //商品轮播图 25 | router.InitBrands(ApiGroup) //品牌,分类-品牌 26 | 27 | //健康检查 28 | Router.GET("/health", func(c *gin.Context) { 29 | c.JSON(http.StatusOK, gin.H{ 30 | "message": "health", 31 | }) 32 | }) 33 | 34 | return Router 35 | } 36 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/initialize/sentinel.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/flow" 8 | ) 9 | 10 | //InitSentinel 初始化sentinel 11 | func InitSentinel() { 12 | //基于sentinel的qps限流 13 | //必须初始化 14 | err := sentinel.InitDefault() 15 | if err != nil { 16 | log.Fatalf("Unexpected error: %+v", err) 17 | } 18 | 19 | //配置限流规则 20 | _, err = flow.LoadRules([]*flow.Rule{ 21 | { 22 | Resource: "goods-list", 23 | TokenCalculateStrategy: flow.Direct, 24 | ControlBehavior: flow.Reject, //超过直接拒绝 25 | Threshold: 3, //请求次数 26 | StatIntervalInMs: 2000, //允许时间内 27 | }, 28 | { 29 | Resource: "goods-stock", 30 | TokenCalculateStrategy: flow.Direct, 31 | ControlBehavior: flow.Reject, //超过直接拒绝 32 | Threshold: 3, //请求次数 33 | StatIntervalInMs: 2000, //允许时间内 34 | }, 35 | }) 36 | 37 | if err != nil { 38 | log.Fatalf("Unexpected error: %+v", err) 39 | return 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/initialize/srv_conn.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "mxshop-api/goods-web/global" 6 | "mxshop-api/goods-web/proto" 7 | "mxshop-api/goods-web/utils/otgrpc" 8 | 9 | _ "github.com/mbobakov/grpc-consul-resolver" // It's important 10 | "github.com/opentracing/opentracing-go" 11 | "go.uber.org/zap" 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | //InitSrvConn 连接到consul注册中心并对服务做负载均衡 16 | func InitSrvConn() { 17 | consul := global.ServerConfig.ConsulInfo 18 | conn, err := grpc.Dial( 19 | fmt.Sprintf("consul://%s:%d/%s?wait=14s&tag=srv", consul.Host, consul.Port, global.ServerConfig.UserSerInfo.Name), 20 | grpc.WithInsecure(), 21 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), 22 | grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())), 23 | ) 24 | 25 | fmt.Println(global.ServerConfig) 26 | 27 | if err != nil { 28 | zap.S().Errorw("[GetUserList] 连接 【用户服务失败", err.Error()) 29 | return 30 | } 31 | goodsClient := proto.NewGoodsClient(conn) 32 | global.GoodsSrvClient = goodsClient 33 | 34 | InvtConn, err := grpc.Dial( 35 | fmt.Sprintf("consul://%s:%d/%s?wait=14s&tag=srv", consul.Host, consul.Port, global.ServerConfig.InventSerInfo.Name), 36 | grpc.WithInsecure(), 37 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), 38 | grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())), 39 | ) 40 | 41 | fmt.Println(global.ServerConfig) 42 | 43 | if err != nil { 44 | zap.S().Errorw("[GetUserList] 连接 【用户服务失败", err.Error()) 45 | return 46 | } 47 | global.InventoryClient = proto.NewInventoryClient(InvtConn) 48 | global.GoodsSrvClient = goodsClient 49 | 50 | } 51 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/initialize/validator.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "mxshop-api/goods-web/global" 9 | 10 | "github.com/gin-gonic/gin/binding" 11 | "github.com/go-playground/locales/en" 12 | "github.com/go-playground/locales/zh" 13 | ut "github.com/go-playground/universal-translator" 14 | "github.com/go-playground/validator/v10" 15 | enTranslations "github.com/go-playground/validator/v10/translations/en" 16 | zhTranslations "github.com/go-playground/validator/v10/translations/zh" 17 | ) 18 | 19 | //RemoveTopStruct 去除以"."及其左部分内容 20 | func RemoveTopStruct(fields map[string]string) map[string]string { 21 | res := map[string]string{} 22 | for field, value := range fields { 23 | res[field[strings.Index(field, ".")+1:]] = value 24 | } 25 | return res 26 | } 27 | 28 | //InitTrans 初始化翻译器 29 | func InitTrans(locale string) (err error) { 30 | // 修改gin框架中的Validator引擎属性,实现自定制 31 | if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 32 | //注册一个获取json的自定义方法 33 | v.RegisterTagNameFunc(func(field reflect.StructField) string { 34 | name := strings.SplitN(field.Tag.Get("json"), ",", 2)[0] 35 | if name == "-" { 36 | return "" 37 | } 38 | return name 39 | }) 40 | zhT := zh.New() // 中文翻译器 41 | enT := en.New() // 英文翻译器 42 | 43 | // 第一个参数是备用(fallback)的语言环境 44 | // 后面的参数是应该支持的语言环境(支持多个) 45 | // uni := ut.New(zhT, zhT) 也是可以的 46 | uni := ut.New(enT, zhT, enT) 47 | 48 | // locale 通常取决于 http 请求头的 'Accept-Language' 49 | var ok bool 50 | // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 51 | global.Trans, ok = uni.GetTranslator(locale) 52 | if !ok { 53 | return fmt.Errorf("uni.GetTranslator(%s) failed", locale) 54 | } 55 | 56 | // 注册翻译器 57 | switch locale { 58 | case "en": 59 | err = enTranslations.RegisterDefaultTranslations(v, global.Trans) 60 | case "zh": 61 | err = zhTranslations.RegisterDefaultTranslations(v, global.Trans) 62 | default: 63 | err = enTranslations.RegisterDefaultTranslations(v, global.Trans) 64 | } 65 | return 66 | } 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/middlewares/admin.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "mxshop-api/goods-web/models" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | //IsAdmin IsAdmin 识别用户身份类型 11 | func IsAdmin() gin.HandlerFunc { 12 | return func(ctx *gin.Context) { 13 | //将用户信息拿出 14 | claims, _ := ctx.Get("claims") 15 | currentUser := claims.(*models.CustomClaims) 16 | if currentUser.AuthorityId != 2 { 17 | ctx.JSON(http.StatusForbidden, gin.H{ 18 | "msg": "无权限", 19 | }) 20 | ctx.Abort() 21 | return 22 | } 23 | ctx.Next() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/middlewares/cors.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | //Cors 解决浏览器跨越问题,后端解决方法 10 | func Cors() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | Method := c.Request.Method 13 | 14 | //返回给浏览器,告诉浏览器header可以填什么内容 15 | c.Header("Access-Control-Allow-Origin", "*") 16 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token") 17 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT") 18 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") 19 | c.Header("Access-Control-Allow-Credentials", "true") 20 | 21 | if Method == "OPTIONS" { 22 | c.AbortWithStatus(http.StatusNoContent) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/middlewares/tracing.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "fmt" 5 | 6 | "mxshop-api/goods-web/global" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/opentracing/opentracing-go" 10 | "github.com/uber/jaeger-client-go" 11 | jaegercfg "github.com/uber/jaeger-client-go/config" 12 | ) 13 | 14 | //Trace 链路追踪 15 | func Trace() gin.HandlerFunc { 16 | return func(c *gin.Context) { 17 | //初始化配置 18 | cfg := jaegercfg.Configuration{ 19 | Sampler: &jaegercfg.SamplerConfig{ 20 | Type: jaeger.SamplerTypeConst, 21 | Param: 1, 22 | }, 23 | Reporter: &jaegercfg.ReporterConfig{ 24 | LogSpans: true, 25 | LocalAgentHostPort: fmt.Sprintf("%s:%d", global.ServerConfig.Tracing.Host, global.ServerConfig.Tracing.Port), 26 | }, 27 | ServiceName: global.ServerConfig.Tracing.Name, 28 | } 29 | 30 | //初始化跟踪连 31 | Tracer, Closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger)) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | opentracing.SetGlobalTracer(Tracer) 37 | defer Closer.Close() 38 | 39 | startSpan := Tracer.StartSpan(c.Request.URL.Path) 40 | defer startSpan.Finish() 41 | 42 | c.Set("tracer", Tracer) 43 | c.Set("parentSpan", startSpan) 44 | 45 | c.Next() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/models/request.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | ) 6 | 7 | type CustomClaims struct { 8 | ID uint 9 | NickName string 10 | AuthorityId uint 11 | jwt.StandardClaims 12 | } 13 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/proto/inventory.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | service Inventory { 6 | rpc SetInv(GoodsInventoryInfo) returns (google.protobuf.Empty); //设置库存 7 | rpc InvDetail(GoodsInventoryInfo) returns (GoodsInventoryInfo); //查询库存 8 | rpc Sell(SellInfo) returns (google.protobuf.Empty); //扣减库存 9 | rpc Reback(SellInfo) returns (google.protobuf.Empty); //归还库存 10 | 11 | } 12 | 13 | //库存信息 14 | message GoodsInventoryInfo { 15 | int32 goodsId = 1; 16 | int32 num = 2; 17 | } 18 | 19 | //批量库存信息 20 | message SellInfo { 21 | repeated GoodsInventoryInfo goodsInfo = 1; 22 | string orderSn = 2; 23 | } 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/router/banner.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/goods-web/api/banner" 5 | "mxshop-api/goods-web/middlewares" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitBanner(router *gin.RouterGroup) { 11 | BannerRouter := router.Group("banners").Use(middlewares.Trace()) 12 | { 13 | BannerRouter.GET("", banner.BannerList) //获取轮播图列表 14 | BannerRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdmin(), banner.NewBanner) //添加轮播图 15 | BannerRouter.DELETE("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), banner.DeleteBanner) //删除轮播图 16 | BannerRouter.PUT("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), banner.UpdateBanner) //更新轮播图 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/router/brands.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/goods-web/api/brands" 5 | "mxshop-api/goods-web/middlewares" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitBrands(router *gin.RouterGroup) { 11 | BrandsRouter := router.Group("brands").Use(middlewares.Trace()) 12 | { 13 | BrandsRouter.GET("", brands.BrandList) //获取品牌列表 14 | BrandsRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdmin(), brands.NewBrand) //添加品牌 15 | BrandsRouter.DELETE("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), brands.DeleteBrand) //删除品牌 16 | BrandsRouter.PUT("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), brands.UpdateBrand) //修改品牌 17 | } 18 | 19 | CategoryBrandRouter := router.Group("categorybrands").Use(middlewares.Trace()) 20 | { 21 | CategoryBrandRouter.GET("", brands.CategoryBrandList) // 类别品牌列表页 22 | CategoryBrandRouter.DELETE("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), brands.DeleteCategoryBrand) // 删除类别品牌 23 | CategoryBrandRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdmin(), brands.NewCategoryBrand) //新建类别品牌 24 | CategoryBrandRouter.PUT("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), brands.UpdateCategoryBrand) //修改类别品牌 25 | CategoryBrandRouter.GET("/:id", brands.GetCategoryBrandList) //获取分类的品牌 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/router/category.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/goods-web/api/category" 5 | "mxshop-api/goods-web/middlewares" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitCategory(router *gin.RouterGroup) { 11 | CategoryRouter := router.Group("categorys").Use(middlewares.Trace()) 12 | { 13 | CategoryRouter.GET("", category.List) //商品分类列表 14 | CategoryRouter.GET("/:id", category.Detail) //获取商品分类详情 15 | CategoryRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdmin(), category.New) //添加分类 16 | CategoryRouter.DELETE("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), category.Delete) //删除分类 17 | CategoryRouter.PUT("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), category.Update) //更新分类 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/router/goods.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/goods-web/api/goods" 5 | "mxshop-api/goods-web/middlewares" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitGoodsRouter(router *gin.RouterGroup) { 11 | GoodsRouter := router.Group("goods").Use(middlewares.Trace()) 12 | { 13 | //中间件的参数位置需要按业务需求而定 14 | GoodsRouter.GET("", goods.List) //商品列表 15 | GoodsRouter.GET("/:id", goods.Detail) //获取商品详情 16 | GoodsRouter.GET("/:id/stocks", goods.Stocks) //获取库存 17 | GoodsRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdmin(), goods.New) //添加商品 18 | GoodsRouter.DELETE("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), goods.Delete) //删除商品 19 | GoodsRouter.PATCH("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), goods.UpdateStatus) //更新商品状态 20 | GoodsRouter.PUT("/:id", middlewares.JWTAuth(), middlewares.IsAdmin(), goods.Update) //更新商品信息 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/utils/otgrpc/README.md: -------------------------------------------------------------------------------- 1 | # OpenTracing support for gRPC in Go 2 | 3 | The `otgrpc` package makes it easy to add OpenTracing support to gRPC-based 4 | systems in Go. 5 | 6 | ## Installation 7 | 8 | ``` 9 | go get github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc 10 | ``` 11 | 12 | ## Documentation 13 | 14 | See the basic usage examples below and the [package documentation on 15 | godoc.org](https://godoc.org/github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc). 16 | 17 | ## Client-side usage example 18 | 19 | Wherever you call `grpc.Dial`: 20 | 21 | ```go 22 | // You must have some sort of OpenTracing Tracer instance on hand. 23 | var tracer opentracing.Tracer = ... 24 | ... 25 | 26 | // Set up a connection to the server peer. 27 | conn, err := grpc.Dial( 28 | address, 29 | ... // other options 30 | grpc.WithUnaryInterceptor( 31 | otgrpc.OpenTracingClientInterceptor(tracer)), 32 | grpc.WithStreamInterceptor( 33 | otgrpc.OpenTracingStreamClientInterceptor(tracer))) 34 | 35 | // All future RPC activity involving `conn` will be automatically traced. 36 | ``` 37 | 38 | ## Server-side usage example 39 | 40 | Wherever you call `grpc.NewServer`: 41 | 42 | ```go 43 | // You must have some sort of OpenTracing Tracer instance on hand. 44 | var tracer opentracing.Tracer = ... 45 | ... 46 | 47 | // Initialize the gRPC server. 48 | s := grpc.NewServer( 49 | ... // other options 50 | grpc.UnaryInterceptor( 51 | otgrpc.OpenTracingServerInterceptor(tracer)), 52 | grpc.StreamInterceptor( 53 | otgrpc.OpenTracingStreamServerInterceptor(tracer))) 54 | 55 | // All future RPC activity involving `s` will be automatically traced. 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/utils/otgrpc/errors.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "github.com/opentracing/opentracing-go" 5 | "github.com/opentracing/opentracing-go/ext" 6 | "google.golang.org/grpc/codes" 7 | "google.golang.org/grpc/status" 8 | ) 9 | 10 | // A Class is a set of types of outcomes (including errors) that will often 11 | // be handled in the same way. 12 | type Class string 13 | 14 | const ( 15 | Unknown Class = "0xx" 16 | // Success represents outcomes that achieved the desired results. 17 | Success Class = "2xx" 18 | // ClientError represents errors that were the client's fault. 19 | ClientError Class = "4xx" 20 | // ServerError represents errors that were the server's fault. 21 | ServerError Class = "5xx" 22 | ) 23 | 24 | // ErrorClass returns the class of the given error 25 | func ErrorClass(err error) Class { 26 | if s, ok := status.FromError(err); ok { 27 | switch s.Code() { 28 | // Success or "success" 29 | case codes.OK, codes.Canceled: 30 | return Success 31 | 32 | // Client errors 33 | case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, 34 | codes.PermissionDenied, codes.Unauthenticated, codes.FailedPrecondition, 35 | codes.OutOfRange: 36 | return ClientError 37 | 38 | // Server errors 39 | case codes.DeadlineExceeded, codes.ResourceExhausted, codes.Aborted, 40 | codes.Unimplemented, codes.Internal, codes.Unavailable, codes.DataLoss: 41 | return ServerError 42 | 43 | // Not sure 44 | case codes.Unknown: 45 | fallthrough 46 | default: 47 | return Unknown 48 | } 49 | } 50 | return Unknown 51 | } 52 | 53 | // SetSpanTags sets one or more tags on the given span according to the 54 | // error. 55 | func SetSpanTags(span opentracing.Span, err error, client bool) { 56 | c := ErrorClass(err) 57 | code := codes.Unknown 58 | if s, ok := status.FromError(err); ok { 59 | code = s.Code() 60 | } 61 | span.SetTag("response_code", code) 62 | span.SetTag("response_class", c) 63 | if err == nil { 64 | return 65 | } 66 | if client || c == ServerError { 67 | ext.Error.Set(span, true) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/utils/otgrpc/errors_test.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/opentracing/opentracing-go/mocktracer" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | const ( 14 | firstCode = codes.OK 15 | lastCode = codes.DataLoss 16 | ) 17 | 18 | func TestSpanTags(t *testing.T) { 19 | tracer := mocktracer.New() 20 | for code := firstCode; code <= lastCode; code++ { 21 | // Client error 22 | tracer.Reset() 23 | span := tracer.StartSpan("test-trace-client") 24 | err := status.Error(code, "") 25 | SetSpanTags(span, err, true) 26 | span.Finish() 27 | 28 | // Assert added tags 29 | rawSpan := tracer.FinishedSpans()[0] 30 | expectedTags := map[string]interface{}{ 31 | "response_code": code, 32 | "response_class": ErrorClass(err), 33 | } 34 | if err != nil { 35 | expectedTags["error"] = true 36 | } 37 | assert.Equal(t, expectedTags, rawSpan.Tags()) 38 | 39 | // Server error 40 | tracer.Reset() 41 | span = tracer.StartSpan("test-trace-server") 42 | err = status.Error(code, "") 43 | SetSpanTags(span, err, false) 44 | span.Finish() 45 | 46 | // Assert added tags 47 | rawSpan = tracer.FinishedSpans()[0] 48 | expectedTags = map[string]interface{}{ 49 | "response_code": code, 50 | "response_class": ErrorClass(err), 51 | } 52 | if err != nil && ErrorClass(err) == ServerError { 53 | expectedTags["error"] = true 54 | } 55 | assert.Equal(t, expectedTags, rawSpan.Tags()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/utils/otgrpc/package.go: -------------------------------------------------------------------------------- 1 | // Package otgrpc provides OpenTracing support for any gRPC client or server. 2 | // 3 | // See the README for simple usage examples: 4 | // https://github.com/grpc-ecosystem/grpc-opentracing/blob/master/go/otgrpc/README.md 5 | package otgrpc 6 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/utils/otgrpc/shared.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "strings" 5 | 6 | opentracing "github.com/opentracing/opentracing-go" 7 | "github.com/opentracing/opentracing-go/ext" 8 | "google.golang.org/grpc/metadata" 9 | ) 10 | 11 | var ( 12 | // Morally a const: 13 | gRPCComponentTag = opentracing.Tag{string(ext.Component), "gRPC"} 14 | ) 15 | 16 | // metadataReaderWriter satisfies both the opentracing.TextMapReader and 17 | // opentracing.TextMapWriter interfaces. 18 | type metadataReaderWriter struct { 19 | metadata.MD 20 | } 21 | 22 | func (w metadataReaderWriter) Set(key, val string) { 23 | // The GRPC HPACK implementation rejects any uppercase keys here. 24 | // 25 | // As such, since the HTTP_HEADERS format is case-insensitive anyway, we 26 | // blindly lowercase the key (which is guaranteed to work in the 27 | // Inject/Extract sense per the OpenTracing spec). 28 | key = strings.ToLower(key) 29 | w.MD[key] = append(w.MD[key], val) 30 | } 31 | 32 | func (w metadataReaderWriter) ForeachKey(handler func(key, val string) error) error { 33 | for k, vals := range w.MD { 34 | for _, v := range vals { 35 | if err := handler(k, v); err != nil { 36 | return err 37 | } 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/utils/otgrpc/test/otgrpc_testing/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package otgrpc.testing; 4 | 5 | message SimpleRequest { 6 | int32 payload = 1; 7 | } 8 | 9 | message SimpleResponse { 10 | int32 payload = 1; 11 | } 12 | 13 | service TestService { 14 | rpc UnaryCall(SimpleRequest) returns (SimpleResponse); 15 | 16 | rpc StreamingOutputCall(SimpleRequest) returns (stream SimpleResponse); 17 | 18 | rpc StreamingInputCall(stream SimpleRequest) returns (SimpleResponse); 19 | 20 | rpc StreamingBidirectionalCall(stream SimpleRequest) returns (stream SimpleResponse); 21 | } 22 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/utils/register/consul/register.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | type Registry struct { 10 | Host string 11 | Port int 12 | } 13 | 14 | type RegistryClient interface { 15 | Register(address string, port int, name string, tags []string, id string) error 16 | DeRegister(serverId string) error 17 | } 18 | 19 | func NewRegistryClient(host string, port int) RegistryClient { 20 | return &Registry{ 21 | Host: host, 22 | Port: port, 23 | } 24 | } 25 | 26 | //Register 注册服务至注册中心 27 | func (r *Registry) Register(address string, port int, name string, tags []string, id string) error { 28 | //DefaultConfig 返回客户端的默认配置 29 | cfg := api.DefaultConfig() 30 | //Address为consul的host和port 31 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 32 | 33 | client, err := api.NewClient(cfg) 34 | if err != nil { 35 | panic(err) 36 | } 37 | //对外host和ip 38 | //生成对应的检查对象 39 | check := &api.AgentServiceCheck{ 40 | HTTP: fmt.Sprintf("http://%s:%d/health", address, port), 41 | Timeout: "5s", 42 | Interval: "5s", 43 | DeregisterCriticalServiceAfter: "10s", 44 | } 45 | 46 | //生成注册对象 47 | registration := new(api.AgentServiceRegistration) 48 | registration.Name = name 49 | registration.ID = id 50 | registration.Port = port 51 | registration.Tags = tags 52 | registration.Address = address 53 | registration.Check = check 54 | 55 | //注册 56 | err = client.Agent().ServiceRegister(registration) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return nil 61 | } 62 | 63 | //DeRegister 注销服务 64 | func (r *Registry) DeRegister(serverId string) error { 65 | //DefaultConfig 返回客户端的默认配置 66 | cfg := api.DefaultConfig() 67 | 68 | //Address为consul的host和port 69 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 70 | 71 | client, err := api.NewClient(cfg) 72 | if err != nil { 73 | return err 74 | } 75 | //注销 76 | err = client.Agent().ServiceDeregister(serverId) 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | //GetFreePort 分配线上随机端口 8 | func GetFreePort() (int, error) { 9 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 10 | if err != nil { 11 | return 0, err 12 | } 13 | 14 | l, err := net.ListenTCP("tcp", addr) 15 | if err != nil { 16 | return 0, err 17 | } 18 | defer l.Close() 19 | return l.Addr().(*net.TCPAddr).Port, nil 20 | } 21 | -------------------------------------------------------------------------------- /mxshop-api/goods-web/validator/validator.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | //ValidatorMobile 自定义validator 10 | func ValidatorMobile(fl validator.FieldLevel) bool { 11 | mobile := fl.Field().String() 12 | //使用正则表达式准标准库:regexp 13 | ok, _ := regexp.MatchString(`^1([38][0-9]|14[579]|5[^4]|16[6]|7[1-35-8]|9[189])\d{8}$`, mobile) 14 | if !ok { 15 | return false 16 | } 17 | return true 18 | } 19 | -------------------------------------------------------------------------------- /mxshop-api/order-web/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iceymoss/mxshop/4613a07130e2c878a33457ac640216e501c9cc67/mxshop-api/order-web/.DS_Store -------------------------------------------------------------------------------- /mxshop-api/order-web/api/base.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/go-playground/validator/v10" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | 12 | "mxshop-api/order-web/global" 13 | ) 14 | 15 | func removeTopStruct(fileds map[string]string) map[string]string { 16 | rsp := map[string]string{} 17 | for field, err := range fileds { 18 | rsp[field[strings.Index(field, ".")+1:]] = err 19 | } 20 | return rsp 21 | } 22 | 23 | //HandleValidatorErr 表单验证错误处理返回 24 | func HandleValidatorErr(c *gin.Context, err error) { 25 | errs, ok := err.(validator.ValidationErrors) 26 | if !ok { 27 | c.JSON(http.StatusOK, gin.H{ 28 | "msg": err.Error(), 29 | }) 30 | } 31 | c.JSON(http.StatusBadRequest, gin.H{ 32 | "error": removeTopStruct(errs.Translate(global.Trans)), 33 | }) 34 | return 35 | } 36 | 37 | //HandleGrpcErrToHttp grpc状态码转http 38 | func HandleGrpcErrToHttp(err error, c *gin.Context) { 39 | if err != nil { 40 | if e, ok := status.FromError(err); ok { 41 | switch e.Code() { 42 | case codes.NotFound: 43 | c.JSON(http.StatusNotFound, gin.H{ 44 | "msg": e.Message(), 45 | }) 46 | case codes.Internal: 47 | c.JSON(http.StatusInternalServerError, gin.H{ 48 | "msg": "内部错误", //内部 49 | }) 50 | case codes.InvalidArgument: 51 | c.JSON(http.StatusBadRequest, gin.H{ 52 | "msg": "参数错误", //参数 53 | }) 54 | case codes.Unavailable: 55 | c.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": "用户服务不可用", 57 | }) 58 | default: 59 | c.JSON(http.StatusInternalServerError, gin.H{ 60 | "msg": e.Message(), 61 | }) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mxshop-api/order-web/api/pay/alipay.go: -------------------------------------------------------------------------------- 1 | package pay 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "mxshop-api/order-web/global" 8 | "mxshop-api/order-web/proto" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/smartwalle/alipay/v3" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | func Notify(ctx *gin.Context) { 16 | //支付宝回调通知 17 | client, err := alipay.New(global.ServerConfig.Alipay.AppID, global.ServerConfig.Alipay.PrivateKey, false) 18 | if err != nil { 19 | zap.S().Errorw("实例化支付宝失败") 20 | ctx.JSON(http.StatusInternalServerError, gin.H{ 21 | "msg": err.Error(), 22 | }) 23 | return 24 | } 25 | err = client.LoadAliPayPublicKey((global.ServerConfig.Alipay.AlipayPublicKey)) 26 | if err != nil { 27 | zap.S().Errorw("加载支付宝的公钥失败") 28 | ctx.JSON(http.StatusInternalServerError, gin.H{ 29 | "msg": err.Error(), 30 | }) 31 | return 32 | } 33 | 34 | //接收alipay回调信息,回调信息有调用支付接口上传的所有信息 35 | noti, err := client.GetTradeNotification(ctx.Request) 36 | if err != nil { 37 | ctx.JSON(http.StatusInternalServerError, gin.H{}) 38 | return 39 | } 40 | 41 | //更新订单状态 42 | _, err = global.OrderSrvClient.UpdateOrderStatus(context.Background(), &proto.OrderStatus{ 43 | OrderSn: noti.OutTradeNo, 44 | Status: string(noti.TradeStatus), 45 | }) 46 | if err != nil { 47 | ctx.JSON(http.StatusInternalServerError, gin.H{}) 48 | return 49 | } 50 | 51 | //向alipay回复确认 52 | ctx.String(http.StatusOK, "success") 53 | } 54 | -------------------------------------------------------------------------------- /mxshop-api/order-web/config-debug.yaml: -------------------------------------------------------------------------------- 1 | host: '10.2.74.65' 2 | port: 8848 3 | namespace_id: '64c5ff72-e7ca-4ac8-b605-895ce39b6271' 4 | user: 'nacos' 5 | password: 'nacos' 6 | data_id: 'order-web.json' 7 | group: 'dev' 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /mxshop-api/order-web/forms/order.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | type OrderForms struct { 4 | Name string `json:"name" form:"name" binding:"required"` 5 | Address string `json:"address" form:"address" binding:"required"` 6 | Mobile string `json:"mobile" form:"mobile" binding:"required,mobile"` 7 | Post string `json:"post" form:"post" binding:"required"` 8 | } 9 | -------------------------------------------------------------------------------- /mxshop-api/order-web/forms/shop_cart.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | //ShopCartForm 购物车表单 4 | type ShopCartForm struct { 5 | GoodsId int32 `json:"goods_id" form:"goods_id" binding:"required"` 6 | Nums int32 `json:"nums" form:"nums" binding:"required,min=1"` 7 | } 8 | 9 | //UpdateShopCartForm 更新购物车状态 10 | type UpdateShopCartForm struct { 11 | Nums int32 `json:"nums" form:"nums" binding:"required,min=1"` 12 | Checked *bool `json:"checked" form:"checked"` 13 | } 14 | -------------------------------------------------------------------------------- /mxshop-api/order-web/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop-api/order-web/config" 5 | "mxshop-api/order-web/proto" 6 | 7 | ut "github.com/go-playground/universal-translator" 8 | ) 9 | 10 | //需要的全局变量 11 | var ( 12 | Trans ut.Translator //声明一个全局翻译器 13 | ServerConfig *config.ServerConfig = &config.ServerConfig{} //声明配置信息 14 | GoodsSrvClient proto.GoodsClient //grpc Client 15 | OrderSrvClient proto.OrderClient 16 | InventorySrvClient proto.InventoryClient 17 | NacosConfig *config.NacosConfig = &config.NacosConfig{} 18 | ) 19 | -------------------------------------------------------------------------------- /mxshop-api/order-web/global/response/user.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | //时间格式转换 9 | type JsonTime time.Time 10 | 11 | //当数据调用c.JSON时,MarshalJSON会自动被调用 12 | func (j JsonTime) MarshalJSON() ([]byte, error) { 13 | stdtime := fmt.Sprintf("\"%s\"", time.Time(j).Format("2022-01-01")) 14 | return []byte(stdtime), nil 15 | } 16 | 17 | type UserResponse struct { 18 | Id int32 `json:"id"` 19 | NickName string `json:"name"` 20 | BirthDay JsonTime `json:"birthday"` 21 | Gender string `json:"gender"` 22 | Mobile string `json:"mobile"` 23 | } 24 | -------------------------------------------------------------------------------- /mxshop-api/order-web/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | // 1. zap.S可以配置一个全局的sugar,用来配置全局logger 10 | // 2. 日志级别:debug、info、warn、error、fetal 11 | // 3. S函数和L函数可以配置全局的安全的logger 12 | 13 | //InitLogger 初始化日志 14 | func InitLogger() { 15 | //初始化日志 16 | logger, err := zap.NewDevelopment() 17 | if err != nil { 18 | log.Fatal("日志初始化失败", err.Error()) 19 | } 20 | //使用全局logger 21 | zap.ReplaceGlobals(logger) 22 | } 23 | -------------------------------------------------------------------------------- /mxshop-api/order-web/initialize/router.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "net/http" 5 | 6 | "mxshop-api/order-web/middlewares" 7 | "mxshop-api/order-web/router" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | //Routers 初始化及路由分发 13 | func Routers() *gin.Engine { 14 | Router := gin.Default() 15 | 16 | //配置跨越 17 | Router.Use(middlewares.Cors()) 18 | 19 | ApiGroup := Router.Group("o") 20 | //分发路由 21 | ApiGroup = ApiGroup.Group("v1") 22 | 23 | router.InitShopCartRouter(ApiGroup) //购物车 24 | router.InitOrderRouter(ApiGroup) //订单 25 | 26 | //健康检查 27 | Router.GET("/health", func(c *gin.Context) { 28 | c.JSON(http.StatusOK, gin.H{ 29 | "message": "health", 30 | }) 31 | }) 32 | 33 | return Router 34 | } 35 | -------------------------------------------------------------------------------- /mxshop-api/order-web/initialize/sentinel.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | sentinel "github.com/alibaba/sentinel-golang/api" 7 | "github.com/alibaba/sentinel-golang/core/flow" 8 | ) 9 | 10 | //InitSentinel 初始化sentinel 11 | func InitSentinel() { 12 | //基于sentinel的qps限流 13 | //必须初始化 14 | err := sentinel.InitDefault() 15 | if err != nil { 16 | log.Fatalf("Unexpected error: %+v", err) 17 | } 18 | 19 | //配置限流规则 20 | _, err = flow.LoadRules([]*flow.Rule{ 21 | { 22 | Resource: "shopping_cart_list", 23 | TokenCalculateStrategy: flow.Direct, 24 | ControlBehavior: flow.Reject, //超过直接拒绝 25 | Threshold: 3, //请求次数 26 | StatIntervalInMs: 2000, //允许时间内 27 | }, 28 | { 29 | Resource: "CreateCarItem", 30 | TokenCalculateStrategy: flow.Direct, 31 | ControlBehavior: flow.Reject, //超过直接拒绝 32 | Threshold: 3, //请求次数 33 | StatIntervalInMs: 2000, //允许时间内 34 | }, 35 | { 36 | Resource: "order_list", 37 | TokenCalculateStrategy: flow.Direct, 38 | ControlBehavior: flow.Reject, //超过直接拒绝 39 | Threshold: 3, //请求次数 40 | StatIntervalInMs: 2000, //允许时间内 41 | }, 42 | { 43 | Resource: "create_order", 44 | TokenCalculateStrategy: flow.Direct, 45 | ControlBehavior: flow.Reject, //超过直接拒绝 46 | Threshold: 3, //请求次数 47 | StatIntervalInMs: 2000, //允许时间内 48 | }, 49 | }) 50 | 51 | if err != nil { 52 | log.Fatalf("Unexpected error: %+v", err) 53 | return 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /mxshop-api/order-web/initialize/validator.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "mxshop-api/order-web/global" 9 | 10 | "github.com/gin-gonic/gin/binding" 11 | "github.com/go-playground/locales/en" 12 | "github.com/go-playground/locales/zh" 13 | ut "github.com/go-playground/universal-translator" 14 | "github.com/go-playground/validator/v10" 15 | enTranslations "github.com/go-playground/validator/v10/translations/en" 16 | zhTranslations "github.com/go-playground/validator/v10/translations/zh" 17 | ) 18 | 19 | //RemoveTopStruct 去除以"."及其左部分内容 20 | func RemoveTopStruct(fields map[string]string) map[string]string { 21 | res := map[string]string{} 22 | for field, value := range fields { 23 | res[field[strings.Index(field, ".")+1:]] = value 24 | } 25 | return res 26 | } 27 | 28 | //InitTrans 初始化翻译器 29 | func InitTrans(locale string) (err error) { 30 | // 修改gin框架中的Validator引擎属性,实现自定制 31 | if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 32 | //注册一个获取json的自定义方法 33 | v.RegisterTagNameFunc(func(field reflect.StructField) string { 34 | name := strings.SplitN(field.Tag.Get("json"), ",", 2)[0] 35 | if name == "-" { 36 | return "" 37 | } 38 | return name 39 | }) 40 | zhT := zh.New() // 中文翻译器 41 | enT := en.New() // 英文翻译器 42 | 43 | // 第一个参数是备用(fallback)的语言环境 44 | // 后面的参数是应该支持的语言环境(支持多个) 45 | // uni := ut.New(zhT, zhT) 也是可以的 46 | uni := ut.New(enT, zhT, enT) 47 | 48 | // locale 通常取决于 http 请求头的 'Accept-Language' 49 | var ok bool 50 | // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 51 | global.Trans, ok = uni.GetTranslator(locale) 52 | if !ok { 53 | return fmt.Errorf("uni.GetTranslator(%s) failed", locale) 54 | } 55 | 56 | // 注册翻译器 57 | switch locale { 58 | case "en": 59 | err = enTranslations.RegisterDefaultTranslations(v, global.Trans) 60 | case "zh": 61 | err = zhTranslations.RegisterDefaultTranslations(v, global.Trans) 62 | default: 63 | err = enTranslations.RegisterDefaultTranslations(v, global.Trans) 64 | } 65 | return 66 | } 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /mxshop-api/order-web/middlewares/admin.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "mxshop-api/order-web/models" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | //IsAdmin IsAdmin 识别用户身份类型 11 | func IsAdmin() gin.HandlerFunc { 12 | return func(ctx *gin.Context) { 13 | //将用户信息拿出 14 | claims, _ := ctx.Get("claims") 15 | currentUser := claims.(*models.CustomClaims) 16 | if currentUser.AuthorityId != 2 { 17 | ctx.JSON(http.StatusForbidden, gin.H{ 18 | "msg": "无权限", 19 | }) 20 | ctx.Abort() 21 | return 22 | } 23 | ctx.Next() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/order-web/middlewares/cors.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | //Cors 解决浏览器跨越问题,后端解决方法 10 | func Cors() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | Method := c.Request.Method 13 | 14 | //返回给浏览器,告诉浏览器header可以填什么内容 15 | c.Header("Access-Control-Allow-Origin", "*") 16 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token") 17 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT") 18 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") 19 | c.Header("Access-Control-Allow-Credentials", "true") 20 | 21 | if Method == "OPTIONS" { 22 | c.AbortWithStatus(http.StatusNoContent) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/order-web/middlewares/tracing.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "fmt" 5 | 6 | "mxshop-api/order-web/global" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/opentracing/opentracing-go" 10 | "github.com/uber/jaeger-client-go" 11 | jaegercfg "github.com/uber/jaeger-client-go/config" 12 | ) 13 | 14 | //Trace 链路追踪 15 | func Trace() gin.HandlerFunc { 16 | return func(c *gin.Context) { 17 | //初始化配置 18 | cfg := jaegercfg.Configuration{ 19 | Sampler: &jaegercfg.SamplerConfig{ 20 | Type: jaeger.SamplerTypeConst, 21 | Param: 1, 22 | }, 23 | Reporter: &jaegercfg.ReporterConfig{ 24 | LogSpans: true, 25 | LocalAgentHostPort: fmt.Sprintf("%s:%d", global.ServerConfig.Tracing.Host, global.ServerConfig.Tracing.Port), 26 | }, 27 | ServiceName: global.ServerConfig.Tracing.Name, 28 | } 29 | 30 | //初始化跟踪连 31 | Tracer, Closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger)) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | opentracing.SetGlobalTracer(Tracer) 37 | defer Closer.Close() 38 | 39 | startSpan := Tracer.StartSpan(c.Request.URL.Path) 40 | defer startSpan.Finish() 41 | 42 | c.Set("tracer", Tracer) 43 | c.Set("parentSpan", startSpan) 44 | 45 | c.Next() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mxshop-api/order-web/models/request.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | ) 6 | 7 | type CustomClaims struct { 8 | ID uint 9 | NickName string 10 | AuthorityId uint 11 | jwt.StandardClaims 12 | } 13 | -------------------------------------------------------------------------------- /mxshop-api/order-web/proto/inventory.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | service Inventory { 6 | rpc SetInv(GoodsInventoryInfo) returns (google.protobuf.Empty); //设置库存 7 | rpc InvDetail(GoodsInventoryInfo) returns (GoodsInventoryInfo); //查询库存 8 | rpc Sell(SellInfo) returns (google.protobuf.Empty); //扣减库存 9 | rpc Reback(SellInfo) returns (google.protobuf.Empty); //归还库存 10 | 11 | } 12 | 13 | //库存信息 14 | message GoodsInventoryInfo { 15 | int32 goodsId = 1; 16 | int32 num = 2; 17 | } 18 | 19 | //批量库存信息 20 | message SellInfo { 21 | repeated GoodsInventoryInfo goodsInfo = 1; 22 | } 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mxshop-api/order-web/router/order.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/order-web/api/order" 5 | "mxshop-api/order-web/api/pay" 6 | "mxshop-api/order-web/middlewares" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func InitOrderRouter(router *gin.RouterGroup) { 12 | OrderRouter := router.Group("order").Use(middlewares.JWTAuth()).Use(middlewares.Trace()) 13 | { 14 | //中间件的参数位置需要按业务需求而定 15 | OrderRouter.GET("", order.List) //订单列表 16 | OrderRouter.GET("/:id", order.DetailOrder) //获取订单详细 17 | OrderRouter.POST("", order.CreatOrder) //新建订单 18 | OrderRouter.PATCH("", order.UpdateOrder) //更新订单 19 | } 20 | 21 | PayRouter := router.Group("pay") 22 | { 23 | PayRouter.POST("alipay/notify", pay.Notify) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/order-web/router/shop_cart.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/order-web/api/shop_cart" 5 | "mxshop-api/order-web/middlewares" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitShopCartRouter(router *gin.RouterGroup) { 11 | ShopCartRouter := router.Group("shopcarts").Use(middlewares.JWTAuth()).Use(middlewares.Trace()) 12 | { 13 | ShopCartRouter.GET("", shop_cart.List) //获取购物车列表 14 | ShopCartRouter.POST("", shop_cart.CreateCarItem) //加入购物车 15 | ShopCartRouter.PATCH("/:id", shop_cart.UpdateCarItem) //更新购物车 16 | ShopCartRouter.DELETE("/:id", shop_cart.DeleteCarItem) //移除购物车 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mxshop-api/order-web/utils/otgrpc/README.md: -------------------------------------------------------------------------------- 1 | # OpenTracing support for gRPC in Go 2 | 3 | The `otgrpc` package makes it easy to add OpenTracing support to gRPC-based 4 | systems in Go. 5 | 6 | ## Installation 7 | 8 | ``` 9 | go get github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc 10 | ``` 11 | 12 | ## Documentation 13 | 14 | See the basic usage examples below and the [package documentation on 15 | godoc.org](https://godoc.org/github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc). 16 | 17 | ## Client-side usage example 18 | 19 | Wherever you call `grpc.Dial`: 20 | 21 | ```go 22 | // You must have some sort of OpenTracing Tracer instance on hand. 23 | var tracer opentracing.Tracer = ... 24 | ... 25 | 26 | // Set up a connection to the server peer. 27 | conn, err := grpc.Dial( 28 | address, 29 | ... // other options 30 | grpc.WithUnaryInterceptor( 31 | otgrpc.OpenTracingClientInterceptor(tracer)), 32 | grpc.WithStreamInterceptor( 33 | otgrpc.OpenTracingStreamClientInterceptor(tracer))) 34 | 35 | // All future RPC activity involving `conn` will be automatically traced. 36 | ``` 37 | 38 | ## Server-side usage example 39 | 40 | Wherever you call `grpc.NewServer`: 41 | 42 | ```go 43 | // You must have some sort of OpenTracing Tracer instance on hand. 44 | var tracer opentracing.Tracer = ... 45 | ... 46 | 47 | // Initialize the gRPC server. 48 | s := grpc.NewServer( 49 | ... // other options 50 | grpc.UnaryInterceptor( 51 | otgrpc.OpenTracingServerInterceptor(tracer)), 52 | grpc.StreamInterceptor( 53 | otgrpc.OpenTracingStreamServerInterceptor(tracer))) 54 | 55 | // All future RPC activity involving `s` will be automatically traced. 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /mxshop-api/order-web/utils/otgrpc/errors_test.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/opentracing/opentracing-go/mocktracer" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | const ( 14 | firstCode = codes.OK 15 | lastCode = codes.DataLoss 16 | ) 17 | 18 | func TestSpanTags(t *testing.T) { 19 | tracer := mocktracer.New() 20 | for code := firstCode; code <= lastCode; code++ { 21 | // Client error 22 | tracer.Reset() 23 | span := tracer.StartSpan("test-trace-client") 24 | err := status.Error(code, "") 25 | SetSpanTags(span, err, true) 26 | span.Finish() 27 | 28 | // Assert added tags 29 | rawSpan := tracer.FinishedSpans()[0] 30 | expectedTags := map[string]interface{}{ 31 | "response_code": code, 32 | "response_class": ErrorClass(err), 33 | } 34 | if err != nil { 35 | expectedTags["error"] = true 36 | } 37 | assert.Equal(t, expectedTags, rawSpan.Tags()) 38 | 39 | // Server error 40 | tracer.Reset() 41 | span = tracer.StartSpan("test-trace-server") 42 | err = status.Error(code, "") 43 | SetSpanTags(span, err, false) 44 | span.Finish() 45 | 46 | // Assert added tags 47 | rawSpan = tracer.FinishedSpans()[0] 48 | expectedTags = map[string]interface{}{ 49 | "response_code": code, 50 | "response_class": ErrorClass(err), 51 | } 52 | if err != nil && ErrorClass(err) == ServerError { 53 | expectedTags["error"] = true 54 | } 55 | assert.Equal(t, expectedTags, rawSpan.Tags()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mxshop-api/order-web/utils/otgrpc/package.go: -------------------------------------------------------------------------------- 1 | // Package otgrpc provides OpenTracing support for any gRPC client or server. 2 | // 3 | // See the README for simple usage examples: 4 | // https://github.com/grpc-ecosystem/grpc-opentracing/blob/master/go/otgrpc/README.md 5 | package otgrpc 6 | -------------------------------------------------------------------------------- /mxshop-api/order-web/utils/otgrpc/shared.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "strings" 5 | 6 | opentracing "github.com/opentracing/opentracing-go" 7 | "github.com/opentracing/opentracing-go/ext" 8 | "google.golang.org/grpc/metadata" 9 | ) 10 | 11 | var ( 12 | // Morally a const: 13 | gRPCComponentTag = opentracing.Tag{string(ext.Component), "gRPC"} 14 | ) 15 | 16 | // metadataReaderWriter satisfies both the opentracing.TextMapReader and 17 | // opentracing.TextMapWriter interfaces. 18 | type metadataReaderWriter struct { 19 | metadata.MD 20 | } 21 | 22 | func (w metadataReaderWriter) Set(key, val string) { 23 | // The GRPC HPACK implementation rejects any uppercase keys here. 24 | // 25 | // As such, since the HTTP_HEADERS format is case-insensitive anyway, we 26 | // blindly lowercase the key (which is guaranteed to work in the 27 | // Inject/Extract sense per the OpenTracing spec). 28 | key = strings.ToLower(key) 29 | w.MD[key] = append(w.MD[key], val) 30 | } 31 | 32 | func (w metadataReaderWriter) ForeachKey(handler func(key, val string) error) error { 33 | for k, vals := range w.MD { 34 | for _, v := range vals { 35 | if err := handler(k, v); err != nil { 36 | return err 37 | } 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /mxshop-api/order-web/utils/otgrpc/test/otgrpc_testing/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package otgrpc.testing; 4 | 5 | message SimpleRequest { 6 | int32 payload = 1; 7 | } 8 | 9 | message SimpleResponse { 10 | int32 payload = 1; 11 | } 12 | 13 | service TestService { 14 | rpc UnaryCall(SimpleRequest) returns (SimpleResponse); 15 | 16 | rpc StreamingOutputCall(SimpleRequest) returns (stream SimpleResponse); 17 | 18 | rpc StreamingInputCall(stream SimpleRequest) returns (SimpleResponse); 19 | 20 | rpc StreamingBidirectionalCall(stream SimpleRequest) returns (stream SimpleResponse); 21 | } 22 | -------------------------------------------------------------------------------- /mxshop-api/order-web/utils/register/consul/register.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | type Registry struct { 10 | Host string 11 | Port int 12 | } 13 | 14 | type RegistryClient interface { 15 | Register(address string, port int, name string, tags []string, id string) error 16 | DeRegister(serverId string) error 17 | } 18 | 19 | func NewRegistryClient(host string, port int) RegistryClient { 20 | return &Registry{ 21 | Host: host, 22 | Port: port, 23 | } 24 | } 25 | 26 | //Register 注册服务至注册中心 27 | func (r *Registry) Register(address string, port int, name string, tags []string, id string) error { 28 | //DefaultConfig 返回客户端的默认配置 29 | cfg := api.DefaultConfig() 30 | //Address为consul的host和port 31 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 32 | 33 | client, err := api.NewClient(cfg) 34 | if err != nil { 35 | panic(err) 36 | } 37 | //对外host和ip 38 | //生成对应的检查对象 39 | check := &api.AgentServiceCheck{ 40 | HTTP: fmt.Sprintf("http://%s:%d/health", address, port), 41 | Timeout: "5s", 42 | Interval: "5s", 43 | DeregisterCriticalServiceAfter: "10s", 44 | } 45 | 46 | //生成注册对象 47 | registration := new(api.AgentServiceRegistration) 48 | registration.Name = name 49 | registration.ID = id 50 | registration.Port = port 51 | registration.Tags = tags 52 | registration.Address = address 53 | registration.Check = check 54 | 55 | //注册 56 | err = client.Agent().ServiceRegister(registration) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return nil 61 | } 62 | 63 | //DeRegister 注销服务 64 | func (r *Registry) DeRegister(serverId string) error { 65 | //DefaultConfig 返回客户端的默认配置 66 | cfg := api.DefaultConfig() 67 | 68 | //Address为consul的host和port 69 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 70 | 71 | client, err := api.NewClient(cfg) 72 | if err != nil { 73 | return err 74 | } 75 | //注销 76 | err = client.Agent().ServiceDeregister(serverId) 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /mxshop-api/order-web/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | //GetFreePort 分配线上随机端口 8 | func GetFreePort() (int, error) { 9 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 10 | if err != nil { 11 | return 0, err 12 | } 13 | 14 | l, err := net.ListenTCP("tcp", addr) 15 | if err != nil { 16 | return 0, err 17 | } 18 | defer l.Close() 19 | return l.Addr().(*net.TCPAddr).Port, nil 20 | } 21 | -------------------------------------------------------------------------------- /mxshop-api/order-web/validator/validator.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | //ValidatorMobile 自定义validator 10 | func ValidatorMobile(fl validator.FieldLevel) bool { 11 | mobile := fl.Field().String() 12 | //使用正则表达式准标准库:regexp 13 | ok, _ := regexp.MatchString(`^1([38][0-9]|14[579]|5[^4]|16[6]|7[1-35-8]|9[189])\d{8}$`, mobile) 14 | if !ok { 15 | return false 16 | } 17 | return true 18 | } 19 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/config-debug.yaml: -------------------------------------------------------------------------------- 1 | host: '10.2.103.145' 2 | port: 8848 3 | namespace: 'fe432146-9559-44d1-8245-3541feacc9cf' 4 | user: 'nacos' 5 | password: 'nacos' 6 | dataid: 'oss-web.json' 7 | group: 'dev' 8 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/config-pro.yaml: -------------------------------------------------------------------------------- 1 | host: '192.168.0.104' 2 | port: 8848 3 | namespace: 'c1872978-d51c-4188-a497-4e0cd20b97d5' 4 | user: 'nacos' 5 | password: 'nacos' 6 | dataid: 'oss-web.json' 7 | group: 'pro' 8 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type JWTConfig struct { 4 | SigningKey string `mapstructure:"key" json:"key"` 5 | } 6 | 7 | type ConsulConfig struct { 8 | Host string `mapstructure:"host" json:"host"` 9 | Port int `mapstructure:"port" json:"port"` 10 | } 11 | 12 | type ServerConfig struct { 13 | Name string `mapstructure:"name" json:"name"` 14 | Host string `mapstructure:"host" json:"host"` 15 | Tags []string `mapstructure:"tags" json:"tags"` 16 | Port int `mapstructure:"port" json:"port"` 17 | JWTInfo JWTConfig `mapstructure:"jwt" json:"jwt"` 18 | ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"` 19 | OssInfo OssConfig `mapstructure:"oss" json:"oss"` 20 | } 21 | 22 | type OssConfig struct { 23 | ApiKey string `mapstructure:"key" json:"key"` 24 | ApiSecrect string `mapstructure:"secrect" json:"secrect"` 25 | Host string `mapstructure:"host" json:"host"` 26 | CallBackUrl string `mapstructure:"callback_url" json:"callback_url"` 27 | UploadDir string `mapstructure:"upload_dir" json:"upload_dir"` 28 | } 29 | 30 | type NacosConfig struct { 31 | Host string `mapstructure:"host"` 32 | Port uint64 `mapstructure:"port"` 33 | Namespace string `mapstructure:"namespace"` 34 | User string `mapstructure:"user"` 35 | Password string `mapstructure:"password"` 36 | DataId string `mapstructure:"dataid"` 37 | Group string `mapstructure:"group"` 38 | } 39 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop-api/oss-web/config" 5 | 6 | ut "github.com/go-playground/universal-translator" 7 | ) 8 | 9 | var ( 10 | Trans ut.Translator 11 | 12 | ServerConfig *config.ServerConfig = &config.ServerConfig{} 13 | 14 | NacosConfig *config.NacosConfig = &config.NacosConfig{} 15 | ) 16 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/handler/oss.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "mxshop-api/oss-web/global" 6 | "net/http" 7 | "net/url" 8 | "strings" 9 | 10 | "mxshop-api/oss-web/utils" 11 | 12 | "github.com/gin-gonic/gin" 13 | ) 14 | 15 | func Token(c *gin.Context) { 16 | response := utils.Get_policy_token() 17 | c.Header("Access-Control-Allow-Methods", "POST") 18 | c.Header("Access-Control-Allow-Origin", "*") 19 | c.String(200, response) 20 | } 21 | 22 | func HandlerRequest(ctx *gin.Context) { 23 | fmt.Println("\nHandle Post Request ... ") 24 | // Get PublicKey bytes 25 | bytePublicKey, err := utils.GetPublicKey(ctx) 26 | if err != nil { 27 | utils.ResponseFailed(ctx) 28 | return 29 | } 30 | 31 | // Get Authorization bytes : decode from Base64String 32 | byteAuthorization, err := utils.GetAuthorization(ctx) 33 | if err != nil { 34 | utils.ResponseFailed(ctx) 35 | return 36 | } 37 | 38 | // Get MD5 bytes from Newly Constructed Authrization String. 39 | byteMD5, bodyStr, err := utils.GetMD5FromNewAuthString(ctx) 40 | if err != nil { 41 | utils.ResponseFailed(ctx) 42 | return 43 | } 44 | 45 | decodeurl, err := url.QueryUnescape(bodyStr) 46 | if err != nil { 47 | fmt.Println(err) 48 | } 49 | fmt.Println(decodeurl) 50 | params := make(map[string]string) 51 | datas := strings.Split(decodeurl, "&") 52 | for _, v := range datas { 53 | sdatas := strings.Split(v, "=") 54 | fmt.Println(v) 55 | params[sdatas[0]] = sdatas[1] 56 | } 57 | fileName := params["filename"] 58 | fileUrl := fmt.Sprintf("%s/%s", global.ServerConfig.OssInfo.Host, fileName) 59 | 60 | // verifySignature and response to client 61 | if utils.VerifySignature(bytePublicKey, byteMD5, byteAuthorization) { 62 | // do something you want accoding to callback_body ... 63 | ctx.JSON(http.StatusOK, gin.H{ 64 | "url": fileUrl, 65 | }) 66 | //utils.ResponseSuccess(ctx) // response OK : 200 67 | } else { 68 | utils.ResponseFailed(ctx) // response FAILED : 400 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import "go.uber.org/zap" 4 | 5 | func InitLogger() { 6 | logger, _ := zap.NewDevelopment() 7 | zap.ReplaceGlobals(logger) 8 | } 9 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/initialize/router.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "mxshop-api/oss-web/middlewares" 6 | "mxshop-api/oss-web/router" 7 | "net/http" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func Routers() *gin.Engine { 13 | Router := gin.Default() 14 | Router.GET("/health", func(c *gin.Context) { 15 | c.JSON(http.StatusOK, gin.H{ 16 | "code": http.StatusOK, 17 | "success": true, 18 | }) 19 | }) 20 | 21 | Router.LoadHTMLFiles(fmt.Sprintf("oss-web/templates/index.html")) 22 | // 配置静态文件夹路径 第一个参数是api,第二个是文件夹路径 23 | Router.StaticFS("/static", http.Dir(fmt.Sprintf("oss-web/static"))) 24 | // GET:请求方式;/hello:请求的路径 25 | // 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数 26 | Router.GET("", func(c *gin.Context) { 27 | // c.JSON:返回JSON格式的数据 28 | c.HTML(http.StatusOK, "index.html", gin.H{ 29 | "title": "posts/index", 30 | }) 31 | }) 32 | 33 | //配置跨域 34 | Router.Use(middlewares.Cors()) 35 | 36 | ApiGroup := Router.Group("/oss/v1") 37 | router.InitOssRouter(ApiGroup) 38 | 39 | return Router 40 | } 41 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/initialize/validator.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "mxshop-api/oss-web/global" 6 | "reflect" 7 | "strings" 8 | 9 | "github.com/gin-gonic/gin/binding" 10 | "github.com/go-playground/locales/en" 11 | "github.com/go-playground/locales/zh" 12 | ut "github.com/go-playground/universal-translator" 13 | "github.com/go-playground/validator/v10" 14 | en_translations "github.com/go-playground/validator/v10/translations/en" 15 | zh_translations "github.com/go-playground/validator/v10/translations/zh" 16 | ) 17 | 18 | func InitTrans(locale string) (err error) { 19 | //修改gin框架中的validator引擎属性, 实现定制 20 | if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 21 | //注册一个获取json的tag的自定义方法 22 | v.RegisterTagNameFunc(func(fld reflect.StructField) string { 23 | name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] 24 | if name == "-" { 25 | return "" 26 | } 27 | return name 28 | }) 29 | 30 | zhT := zh.New() //中文翻译器 31 | enT := en.New() //英文翻译器 32 | //第一个参数是备用的语言环境,后面的参数是应该支持的语言环境 33 | uni := ut.New(enT, zhT, enT) 34 | global.Trans, ok = uni.GetTranslator(locale) 35 | if !ok { 36 | return fmt.Errorf("uni.GetTranslator(%s)", locale) 37 | } 38 | 39 | switch locale { 40 | case "en": 41 | en_translations.RegisterDefaultTranslations(v, global.Trans) 42 | case "zh": 43 | zh_translations.RegisterDefaultTranslations(v, global.Trans) 44 | default: 45 | en_translations.RegisterDefaultTranslations(v, global.Trans) 46 | } 47 | return 48 | } 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "mxshop-api/oss-web/global" 6 | "mxshop-api/oss-web/initialize" 7 | "mxshop-api/oss-web/utils" 8 | "mxshop-api/oss-web/utils/register/consul" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | 13 | uuid "github.com/satori/go.uuid" 14 | "github.com/spf13/viper" 15 | "go.uber.org/zap" 16 | ) 17 | 18 | func main() { 19 | //1. 初始化logger 20 | initialize.InitLogger() 21 | 22 | //2. 初始化配置文件 23 | initialize.InitConfig() 24 | 25 | //3. 初始化routers 26 | Router := initialize.Routers() 27 | //4. 初始化翻译 28 | if err := initialize.InitTrans("zh"); err != nil { 29 | panic(err) 30 | } 31 | 32 | viper.AutomaticEnv() 33 | //如果是本地开发环境端口号固定,线上环境启动获取端口号 34 | debug := viper.GetBool("MXSHOP_DEBUG") 35 | if !debug { 36 | port, err := utils.GetFreePort() 37 | if err == nil { 38 | global.ServerConfig.Port = port 39 | } 40 | } 41 | 42 | //服务注册 43 | register_client := consul.NewRegistryClient(global.ServerConfig.ConsulInfo.Host, global.ServerConfig.ConsulInfo.Port) 44 | serviceId := fmt.Sprintf("%s", uuid.NewV4()) 45 | err := register_client.Register(global.ServerConfig.Host, global.ServerConfig.Port, global.ServerConfig.Name, global.ServerConfig.Tags, serviceId) 46 | if err != nil { 47 | zap.S().Panic("服务注册失败:", err.Error()) 48 | } 49 | 50 | /* 51 | 1. S()可以获取一个全局的sugar,可以让我们自己设置一个全局的logger 52 | 2. 日志是分级别的,debug, info , warn, error, fetal 53 | 3. S函数和L函数很有用, 提供了一个全局的安全访问logger的途径 54 | */ 55 | zap.S().Debugf("启动服务器, 端口: %d", global.ServerConfig.Port) 56 | go func() { 57 | if err := Router.Run(fmt.Sprintf(":%d", global.ServerConfig.Port)); err != nil { 58 | zap.S().Panic("启动失败:", err.Error()) 59 | } 60 | }() 61 | 62 | //接收终止信号 63 | quit := make(chan os.Signal) 64 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 65 | <-quit 66 | if err = register_client.DeRegister(serviceId); err != nil { 67 | zap.S().Info("注销失败:", err.Error()) 68 | } else { 69 | zap.S().Info("注销成功:") 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/middlewares/admin.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "mxshop-api/oss-web/models" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func IsAdminAuth() gin.HandlerFunc { 11 | return func(ctx *gin.Context) { 12 | claims, _ := ctx.Get("claims") 13 | currentUser := claims.(*models.CustomClaims) 14 | 15 | if currentUser.AuthorityId != 2 { 16 | ctx.JSON(http.StatusForbidden, gin.H{ 17 | "msg": "无权限", 18 | }) 19 | ctx.Abort() 20 | return 21 | } 22 | ctx.Next() 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/middlewares/cors.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Cors() gin.HandlerFunc { 10 | return func(c *gin.Context) { 11 | method := c.Request.Method 12 | 13 | c.Header("Access-Control-Allow-Origin", "*") 14 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token") 15 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT") 16 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") 17 | c.Header("Access-Control-Allow-Credentials", "true") 18 | 19 | if method == "OPTIONS" { 20 | c.AbortWithStatus(http.StatusNoContent) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/models/request.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | ) 6 | 7 | type CustomClaims struct { 8 | ID uint 9 | NickName string 10 | AuthorityId uint 11 | jwt.StandardClaims 12 | } 13 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/router/oss.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/oss-web/handler" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func InitOssRouter(Router *gin.RouterGroup) { 10 | OssRouter := Router.Group("oss") 11 | { 12 | //OssRouter.GET("token", middlewares.JWTAuth(), middlewares.IsAdminAuth(), handler.Token) 13 | OssRouter.GET("token", handler.Token) 14 | OssRouter.POST("/callback", handler.HandlerRequest) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/static/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | .btn{ 3 | color: #fff; 4 | background-color: #337ab7; 5 | border-color: #2e6da4; 6 | display: inline-block; 7 | padding: 6px 12px; 8 | margin-bottom: 0; 9 | font-size: 14px; 10 | font-weight: 400; 11 | line-height: 1.42857143; 12 | text-align: center; 13 | white-space: nowrap; 14 | text-decoration: none; 15 | vertical-align: middle; 16 | -ms-touch-action: manipulation; 17 | touch-action: manipulation; 18 | cursor: pointer; 19 | -webkit-user-select: none; 20 | -moz-user-select: none; 21 | -ms-user-select: none; 22 | user-select: none; 23 | background-image: none; 24 | border: 1px solid transparent; 25 | border-radius: 4px; 26 | } 27 | a.btn:hover{ 28 | background-color: #3366b7; 29 | } 30 | .progress{ 31 | margin-top:2px; 32 | width: 200px; 33 | height: 14px; 34 | margin-bottom: 10px; 35 | overflow: hidden; 36 | background-color: #f5f5f5; 37 | border-radius: 4px; 38 | -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,.1); 39 | box-shadow: inset 0 1px 2px rgba(0,0,0,.1); 40 | } 41 | .progress-bar{ 42 | background-color: rgb(92, 184, 92); 43 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.14902) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.14902) 50%, rgba(255, 255, 255, 0.14902) 75%, transparent 75%, transparent); 44 | background-size: 40px 40px; 45 | box-shadow: rgba(0, 0, 0, 0.14902) 0px -1px 0px 0px inset; 46 | box-sizing: border-box; 47 | color: rgb(255, 255, 255); 48 | display: block; 49 | float: left; 50 | font-size: 12px; 51 | height: 20px; 52 | line-height: 20px; 53 | text-align: center; 54 | transition-delay: 0s; 55 | transition-duration: 0.6s; 56 | transition-property: width; 57 | transition-timing-function: ease; 58 | width: 266.188px; 59 | } -------------------------------------------------------------------------------- /mxshop-api/oss-web/static/lib/plupload-2.1.2/js/Moxie.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iceymoss/mxshop/4613a07130e2c878a33457ac640216e501c9cc67/mxshop-api/oss-web/static/lib/plupload-2.1.2/js/Moxie.swf -------------------------------------------------------------------------------- /mxshop-api/oss-web/static/lib/plupload-2.1.2/js/Moxie.xap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iceymoss/mxshop/4613a07130e2c878a33457ac640216e501c9cc67/mxshop-api/oss-web/static/lib/plupload-2.1.2/js/Moxie.xap -------------------------------------------------------------------------------- /mxshop-api/oss-web/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OSS web直传 6 | 7 | 8 | 9 | 10 | 11 |

OSS web直传---在服务端php签名, OSS会在文件上传成功,回调用户设置的回调url

12 |
    13 |
  1. 基于plupload封装
  2. 14 |
  3. 支持html5,flash,silverlight,html4 等协议上传
  4. 15 |
  5. 可以运行在PC浏览器,手机浏览器,微信
  6. 16 |
  7. 签名在服务端(php)完成, 安全可靠, 推荐使用!
  8. 17 |
  9. 显示上传进度条
  10. 18 |
  11. 可以控制上传文件的大小,允许上传文件的类型,本例子设置了,只能上传jpg,png,gif结尾和zip,rar文件,最大大小是10M
  12. 19 |
  13. 最关键的是,让你10分钟之内就能移植到你的系统,实现以上牛逼的功能!
  14. 20 |
  15. 注意一点:bucket必须设置了Cors(Post打勾),不然没有办法上传
  16. 21 |
  17. 注意一点:此例子默认是上传到user-dir目录下面,这个目录的设置是在php/get.php, $dir变量!
  18. 22 |
  19. 注意一点:把php/get.php里面的callbackUrl变量改成你自己的url
  20. 23 |
  21. 注意一点:这里返回的success,是OSS已经回调应用服务器,应用服务已经返回200!
  22. 24 |
  23. 点击查看详细文档
  24. 25 |
26 |
27 |
28 | 上传文件名字保持本地文件名字 29 | 上传文件名字是随机文件名, 后缀保留 30 |
31 | 32 |

您所选择的文件列表:

33 |
你的浏览器不支持flash,Silverlight或者HTML5!
34 | 35 |
36 | 37 | 38 |
39 | 选择文件 40 | 开始上传 41 |
42 | 43 |

44 | 
45 | 

 

46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/utils/addr.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func GetFreePort() (int, error) { 8 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 9 | if err != nil { 10 | return 0, err 11 | } 12 | 13 | l, err := net.ListenTCP("tcp", addr) 14 | if err != nil { 15 | return 0, err 16 | } 17 | defer l.Close() 18 | return l.Addr().(*net.TCPAddr).Port, nil 19 | } 20 | -------------------------------------------------------------------------------- /mxshop-api/oss-web/utils/register/consul/register.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | type Registry struct { 10 | Host string 11 | Port int 12 | } 13 | 14 | type RegistryClient interface { 15 | Register(address string, port int, name string, tags []string, id string) error 16 | DeRegister(serviceId string) error 17 | } 18 | 19 | func NewRegistryClient(host string, port int) RegistryClient { 20 | return &Registry{ 21 | Host: host, 22 | Port: port, 23 | } 24 | } 25 | 26 | func (r *Registry) Register(address string, port int, name string, tags []string, id string) error { 27 | cfg := api.DefaultConfig() 28 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 29 | 30 | client, err := api.NewClient(cfg) 31 | if err != nil { 32 | panic(err) 33 | } 34 | //生成对应的检查对象 35 | check := &api.AgentServiceCheck{ 36 | HTTP: fmt.Sprintf("http://%s:%d/health", address, port), 37 | Timeout: "5s", 38 | Interval: "5s", 39 | DeregisterCriticalServiceAfter: "10s", 40 | } 41 | 42 | //生成注册对象 43 | registration := new(api.AgentServiceRegistration) 44 | registration.Name = name 45 | registration.ID = id 46 | registration.Port = port 47 | registration.Tags = tags 48 | registration.Address = address 49 | registration.Check = check 50 | 51 | err = client.Agent().ServiceRegister(registration) 52 | if err != nil { 53 | panic(err) 54 | } 55 | return nil 56 | } 57 | 58 | func (r *Registry) DeRegister(serviceId string) error { 59 | cfg := api.DefaultConfig() 60 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 61 | 62 | client, err := api.NewClient(cfg) 63 | if err != nil { 64 | return err 65 | } 66 | err = client.Agent().ServiceDeregister(serviceId) 67 | return err 68 | } 69 | -------------------------------------------------------------------------------- /mxshop-api/tmp/nacos/cache/config/goods-web.json@@dev@@1667c9ca-e13b-46b8-aa9f-a376de301f6f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goods-web", 3 | "host": "10.2.94.231", 4 | "port": 9091, 5 | "tag": ["mxshop", "goods", "ice_moss", "web"], 6 | "goods_srv": { 7 | "name": "goods_srv", 8 | "host": "10.2.94.231", 9 | "port": 8081 10 | }, 11 | "jwt": { 12 | "key": "2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi" 13 | }, 14 | "sms": { 15 | "key": "LTAI5tCjkwb5rRfZtUmHRo3a", 16 | "secret": "AaRNRBSeTzplpIzaDIYprdnvsIvZCZ" 17 | }, 18 | "consul": { 19 | "host": "10.2.94.231", 20 | "port": 8500 21 | } 22 | } -------------------------------------------------------------------------------- /mxshop-api/tmp/nacos/cache/config/goods-web.json@@dev@@fe432146-9559-44d1-8245-3541feacc9cf: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "goods-web", 4 | "host": "10.2.69.164", 5 | "port": 9092, 6 | "tags":["iceymoss", "goods", "golang", "web"], 7 | "goods_srv":{ 8 | "name": "goods-srv", 9 | "host": "10.2.69.164", 10 | "port": 8082 11 | }, 12 | "jwt":{ 13 | "key": "2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi" 14 | }, 15 | "sms":{ 16 | "key": "LTAI5tCjkwb5rRfZtUmHRo3a", 17 | "secret": "AaRNRBSeTzplpIzaDIYprdnvsIvZCZ" 18 | }, 19 | "consul":{ 20 | "host": "10.2.69.164", 21 | "port": 8500 22 | }, 23 | "tracing":{ 24 | "host": "127.0.0.1", 25 | "port": 6831, 26 | "name": "mxshop" 27 | } 28 | } -------------------------------------------------------------------------------- /mxshop-api/tmp/nacos/cache/config/order-web.json@@dev@@64c5ff72-e7ca-4ac8-b605-895ce39b6271: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order-web", 3 | "host": "10.2.69.164", 4 | "port": 9093, 5 | "tags":["iceymoss", "order", "golang", "srv"], 6 | "goods_srv":{ 7 | "name": "goods-srv", 8 | "host": "10.2.69.164", 9 | "port": 8082 10 | }, 11 | "order_srv":{ 12 | "name":"order-srv", 13 | "host": "10.2.69.164", 14 | "port": 8083 15 | }, 16 | "inventory_srv":{ 17 | "name": "inventory-srv", 18 | "host": "10.2.69.164", 19 | "port": 8084 20 | }, 21 | "jwt":{ 22 | "key": "2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi" 23 | }, 24 | "sms":{ 25 | "key": "LTAI5tCjkwb5rRfZtUmHRo3a", 26 | "secret": "AaRNRBSeTzplpIzaDIYprdnvsIvZCZ" 27 | }, 28 | "consul":{ 29 | "host": "10.2.69.164", 30 | "port": 8500 31 | }, 32 | "tracing":{ 33 | "host": "127.0.0.1", 34 | "port": 6831, 35 | "name": "mxshop" 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /mxshop-api/tmp/nacos/cache/config/order-web.json@@dev@@f9fc4b72-18a3-4335-94fb-f612c133ff59: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order-web", 3 | "host": "10.2.94.231", 4 | "port": 9092, 5 | "tag": ["mxshop", "order", "ice_moss", "web"], 6 | "goods_srv": { 7 | "name": "goods_srv", 8 | "host": "10.2.94.231", 9 | "port": 8081 10 | }, 11 | "order_srv": { 12 | "name": "order_srv", 13 | "host":"10.2.94.231", 14 | "port":8083 15 | }, 16 | "inventory_srv":{ 17 | "name": "inventory_srv", 18 | "host":"10.2.94.231", 19 | "port":8082 20 | }, 21 | "jwt": { 22 | "key": "2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi" 23 | }, 24 | "sms": { 25 | "key": "LTAI5tCjkwb5rRfZtUmHRo3a", 26 | "secret": "AaRNRBSeTzplpIzaDIYprdnvsIvZCZ" 27 | }, 28 | "consul": { 29 | "host": "10.2.94.231", 30 | "port": 8500 31 | } 32 | } -------------------------------------------------------------------------------- /mxshop-api/tmp/nacos/cache/config/user-web.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-web", 3 | "host": "10.2.111.34", 4 | "port": 9095, 5 | "tag": ["mxshop", "goods", "ice_moss", "web"], 6 | "user_srv": { 7 | "name": "user_srv", 8 | "host": "10.2.111.34", 9 | "port": 8080 10 | }, 11 | "jwt": { 12 | "key": "2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi" 13 | }, 14 | "sms": { 15 | "key": "LTAI5tCjkwb5rRfZtUmHRo3a", 16 | "secret": "AaRNRBSeTzplpIzaDIYprdnvsIvZCZ" 17 | }, 18 | "params": { 19 | "sign_name": "生鲜小店", 20 | "code": "SMS_244610581" 21 | }, 22 | "redis": { 23 | "host": "127.0.0.1", 24 | "port": 6379, 25 | "expir": 300 26 | }, 27 | "verify": { 28 | "width": 5 29 | }, 30 | "consul": { 31 | "host": "10.2.111.34", 32 | "port": 8500 33 | } 34 | } -------------------------------------------------------------------------------- /mxshop-api/tmp/nacos/cache/config/user-web.json@@dev@@7ae18f62-e2b9-48bd-bff2-a49e7443f5bc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-web", 3 | "host": "10.2.106.169", 4 | "port": 9091, 5 | "tags":["iceymoss", "goods", "golang", "web"], 6 | "user_srv":{ 7 | "name": "user-srv", 8 | "host": "10.2.106.169", 9 | "port": 8081 10 | }, 11 | "jwt":{ 12 | "key": "2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi" 13 | }, 14 | "sms":{ 15 | "key": "LTAI5tCjkwb5rRfZtUmHRo3a", 16 | "secret": "AaRNRBSeTzplpIzaDIYprdnvsIvZCZ" 17 | }, 18 | "params":{ 19 | "sign_name": "生鲜小店", 20 | "code": "SMS_244610581" 21 | }, 22 | "redis":{ 23 | "host": "127.0.0.1", 24 | "port": 6379, 25 | "expir": 300 26 | }, 27 | "verify":{ 28 | "width": 5 29 | }, 30 | "consul":{ 31 | "host": "10.2.106.169", 32 | "port": 8500 33 | } 34 | } -------------------------------------------------------------------------------- /mxshop-api/tmp/nacos/cache/config/userop-web.json@@dev@@a66fc619-7970-4bdf-b703-9659ed4b2e29: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userop-web", 3 | "host": "10.2.94.231", 4 | "port": 9093, 5 | "tag": ["mxshop", "userop", "ice_moss", "web"], 6 | "goods_srv": { 7 | "name": "goods_srv", 8 | "host": "10.2.94.231", 9 | "port": 8081 10 | }, 11 | "userop_srv": { 12 | "name": "userop_srv", 13 | "host":"10.2.94.231", 14 | "port":8084 15 | }, 16 | "jwt": { 17 | "key": "2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi" 18 | }, 19 | "consul": { 20 | "host": "10.2.94.231", 21 | "port": 8500 22 | } 23 | } -------------------------------------------------------------------------------- /mxshop-api/tmp/nacos/cache/config/userop-web.json@@dev@@c52838c8-880e-49ac-8478-d4c94e0ed154: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userop-web", 3 | "host": "10.2.105.13", 4 | "port": 9094, 5 | "tags":["iceymoss", "userop", "golang", "web"], 6 | "goods_srv":{ 7 | "name": "goods-srv", 8 | "host": "10.2.105.13", 9 | "port": 8082 10 | }, 11 | "userOp_srv":{ 12 | "name":"userop-srv", 13 | "host": "10.2.105.13", 14 | "port": 8085 15 | }, 16 | "jwt":{ 17 | "key": "2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi" 18 | }, 19 | "consul":{ 20 | "host": "10.2.105.13", 21 | "port": 8500 22 | } 23 | } -------------------------------------------------------------------------------- /mxshop-api/user-web/api/chaptcha.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/mojocn/base64Captcha" 8 | "go.uber.org/zap" 9 | ) 10 | 11 | //图片验证对象 12 | var store = base64Captcha.DefaultMemStore 13 | 14 | //GetChaptcha 生成图片验证码 15 | func GetChaptcha(ctx *gin.Context) { 16 | digit := base64Captcha.NewDriverDigit(80, 240, 4, 0.7, 80) 17 | cp := base64Captcha.NewCaptcha(digit, store) 18 | id, base64, err := cp.Generate() 19 | if err != nil { 20 | zap.S().Info("生成验证码失败", err.Error()) 21 | ctx.JSON(http.StatusInternalServerError, gin.H{ 22 | "msg": "生成图片验证码失败", 23 | }) 24 | return 25 | } 26 | ctx.JSON(http.StatusOK, gin.H{ 27 | "captcha": base64, 28 | "captcha_id": id, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /mxshop-api/user-web/api/redis_test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "mxshop-api/user-web/global" 8 | "mxshop-api/user-web/initialize" 9 | 10 | "github.com/go-redis/redis/v8" 11 | ) 12 | 13 | func main() { 14 | //验证码验证 15 | initialize.InitConfig() 16 | rdb := redis.NewClient(&redis.Options{ 17 | Addr: fmt.Sprintf("%s:%d", global.ServerConfig.Redis.Host, global.ServerConfig.Redis.Port), 18 | }) 19 | mobile := "17585610985" 20 | code := "67122" 21 | rsp := rdb.Get(context.Background(), mobile) 22 | value, err := rsp.Result() 23 | if err != nil { 24 | log.Fatal("获取验证码失败", err) 25 | return 26 | } 27 | 28 | if code != value { 29 | log.Fatal("验证码错误") 30 | return 31 | } 32 | fmt.Println("验证码验证通过") 33 | fmt.Println(value) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /mxshop-api/user-web/api/test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "mxshop-api/user-web/global" 7 | "mxshop-api/user-web/initialize" 8 | "strings" 9 | "time" 10 | 11 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" 12 | "github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi" 13 | ) 14 | 15 | //GenerateSmsCode 生成width长度的验证码 16 | func GenerateSmsCode(width int) string { 17 | number := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 18 | r := len(number) 19 | rand.Seed(time.Now().UnixNano()) 20 | 21 | var sb strings.Builder 22 | for i := 0; i < width; i++ { 23 | fmt.Fprintf(&sb, "%d", number[rand.Intn(r)]) 24 | } 25 | return sb.String() 26 | } 27 | 28 | func main() { 29 | initialize.InitConfig() 30 | 31 | client, err := dysmsapi.NewClientWithAccessKey("cn-beijing", global.ServerConfig.AliSms.Apikey, global.ServerConfig.AliSms.ApiSecret) 32 | if err != nil { 33 | panic(err) 34 | } 35 | fmt.Println("配置:", global.ServerConfig.AliSms.Apikey, global.ServerConfig.AliSms.ApiSecret) 36 | 37 | width := global.ServerConfig.Verify.Width 38 | request := requests.NewCommonRequest() 39 | request.Method = "POST" 40 | request.Scheme = "https" // https | http 41 | request.Domain = "dysmsapi.aliyuncs.com" 42 | request.Version = "2017-05-25" 43 | request.ApiName = "SendSms" 44 | request.QueryParams["RegionId"] = "cn-beijing" 45 | request.QueryParams["PhoneNumbers"] = "17585610985" //手机号 46 | request.QueryParams["SignName"] = global.ServerConfig.Params.SignName //阿里云验证过的项目名 自己设置 47 | request.QueryParams["TemplateCode"] = global.ServerConfig.Params.TemplateCode //阿里云的短信模板号 自己设置 48 | request.QueryParams["TemplateParam"] = "{\"code\":" + GenerateSmsCode(width) + "}" //短信模板中的验证码内容 自己生成 之前试过直接返回,但是失败,加上code成功。 49 | response, err := client.ProcessCommonRequest(request) 50 | fmt.Print(client.DoAction(request, response)) 51 | // fmt.Print(response) 52 | if err != nil { 53 | fmt.Print(err.Error()) 54 | } 55 | fmt.Printf("response is %#v\n", response) 56 | //json数据解析 57 | } 58 | -------------------------------------------------------------------------------- /mxshop-api/user-web/config-debug.yaml: -------------------------------------------------------------------------------- 1 | host: '10.2.106.169' 2 | port: 8848 3 | namespace_id: '7ae18f62-e2b9-48bd-bff2-a49e7443f5bc' 4 | user: 'nacos' 5 | password: 'nacos' 6 | data_id: 'user-web.json' 7 | group: 'dev' 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /mxshop-api/user-web/forms/sms.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | type SendSmsForm struct { 4 | Mobile string `from:"mobile" json:"mobile" binding:"required,mobile"` //电话号码有什么规律可寻,需要自定义validator 5 | Type uint `from:"type" json:"type" binding:"required,oneof=1 2"` //1表示注册,2表示登录 需要使用type区别,验证码的业务类别 6 | } 7 | -------------------------------------------------------------------------------- /mxshop-api/user-web/forms/user.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | type PassWordLoginForm struct { 4 | Mobile string `from:"mobile" json:"mobile" binding:"required,mobile"` //电话号码有什么规律可寻,需要自定义validator 5 | Password string `from:"password" json:"password" binding:"required,min=3,max=20"` 6 | Captcha string `from:"captcha" json:"captcha" binding:"required,min=4,max=4"` //验证码 7 | CaptchaId string `from:"captcha_id" json:"captcha_id" binding:"required"` //验证码id 8 | } 9 | 10 | type RegisterForm struct { 11 | Mobile string `from:"mobile" json:"mobile" binding:"required,mobile"` //电话号码有什么规律可寻,需要自定义validator 12 | Password string `from:"password" json:"password" binding:"required,min=3,max=20"` 13 | Code string `from:"code" json:"code" binding:"required,min=5,max=5"` 14 | } 15 | -------------------------------------------------------------------------------- /mxshop-api/user-web/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop-api/user-web/config" 5 | "mxshop-api/user-web/proto" 6 | 7 | ut "github.com/go-playground/universal-translator" 8 | ) 9 | 10 | var ( 11 | Trans ut.Translator //声明一个全局翻译器 12 | ServerConfig *config.ServerConfig = &config.ServerConfig{} //声明配置信息 13 | UserClient proto.UserClient //grpc Client 14 | NacosConfig *config.NacosConfig = &config.NacosConfig{} 15 | ) 16 | -------------------------------------------------------------------------------- /mxshop-api/user-web/global/response/user.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | //时间格式转换 9 | type JsonTime time.Time 10 | 11 | //当数据调用c.JSON时,MarshalJSON会自动被调用 12 | func (j JsonTime) MarshalJSON() ([]byte, error) { 13 | stdtime := fmt.Sprintf("\"%s\"", time.Time(j).Format("2022-01-01")) 14 | return []byte(stdtime), nil 15 | } 16 | 17 | type UserResponse struct { 18 | Id int32 `json:"id"` 19 | NickName string `json:"name"` 20 | BirthDay JsonTime `json:"birthday"` 21 | Gender string `json:"gender"` 22 | Mobile string `json:"mobile"` 23 | } 24 | -------------------------------------------------------------------------------- /mxshop-api/user-web/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | // 1. zap.S可以配置一个全局的sugar,用来配置全局logger 10 | // 2. 日志级别:debug、info、warn、error、fetal 11 | // 3. S函数和L函数可以配置全局的安全的logger 12 | 13 | //InitLogger 初始化日志 14 | func InitLogger() { 15 | //初始化日志 16 | logger, err := zap.NewDevelopment() 17 | if err != nil { 18 | log.Fatal("日志初始化失败", err.Error()) 19 | } 20 | //使用全局logger 21 | zap.ReplaceGlobals(logger) 22 | } 23 | -------------------------------------------------------------------------------- /mxshop-api/user-web/initialize/router.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "mxshop-api/user-web/middlewares" 5 | "mxshop-api/user-web/router" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | //Routers 初始化及路由分发 12 | func Routers() *gin.Engine { 13 | Router := gin.Default() 14 | 15 | //配置跨越 16 | Router.Use(middlewares.Cors()) 17 | 18 | ApiGroup := Router.Group("u") 19 | //分发路由 20 | ApiGroup = ApiGroup.Group("v1") 21 | 22 | router.InitUserRouter(ApiGroup) 23 | 24 | router.InitBaseRouter(ApiGroup) 25 | 26 | Router.GET("/health", func(c *gin.Context) { 27 | c.JSON(http.StatusOK, gin.H{ 28 | "message": "health", 29 | }) 30 | }) 31 | 32 | return Router 33 | } 34 | -------------------------------------------------------------------------------- /mxshop-api/user-web/initialize/validator.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "mxshop-api/user-web/global" 9 | 10 | "github.com/gin-gonic/gin/binding" 11 | "github.com/go-playground/locales/en" 12 | "github.com/go-playground/locales/zh" 13 | ut "github.com/go-playground/universal-translator" 14 | "github.com/go-playground/validator/v10" 15 | enTranslations "github.com/go-playground/validator/v10/translations/en" 16 | zhTranslations "github.com/go-playground/validator/v10/translations/zh" 17 | ) 18 | 19 | //RemoveTopStruct 去除以"."及其左部分内容 20 | func RemoveTopStruct(fields map[string]string) map[string]string { 21 | res := map[string]string{} 22 | for field, value := range fields { 23 | res[field[strings.Index(field, ".")+1:]] = value 24 | } 25 | return res 26 | } 27 | 28 | //InitTrans 初始化翻译器 29 | func InitTrans(locale string) (err error) { 30 | // 修改gin框架中的Validator引擎属性,实现自定制 31 | if v, ok := binding.Validator.Engine().(*validator.Validate); ok { 32 | //注册一个获取json的自定义方法 33 | v.RegisterTagNameFunc(func(field reflect.StructField) string { 34 | name := strings.SplitN(field.Tag.Get("json"), ",", 2)[0] 35 | if name == "-" { 36 | return "" 37 | } 38 | return name 39 | }) 40 | zhT := zh.New() // 中文翻译器 41 | enT := en.New() // 英文翻译器 42 | 43 | // 第一个参数是备用(fallback)的语言环境 44 | // 后面的参数是应该支持的语言环境(支持多个) 45 | // uni := ut.New(zhT, zhT) 也是可以的 46 | uni := ut.New(enT, zhT, enT) 47 | 48 | // locale 通常取决于 http 请求头的 'Accept-Language' 49 | var ok bool 50 | // 也可以使用 uni.FindTranslator(...) 传入多个locale进行查找 51 | global.Trans, ok = uni.GetTranslator(locale) 52 | if !ok { 53 | return fmt.Errorf("uni.GetTranslator(%s) failed", locale) 54 | } 55 | 56 | // 注册翻译器 57 | switch locale { 58 | case "en": 59 | err = enTranslations.RegisterDefaultTranslations(v, global.Trans) 60 | case "zh": 61 | err = zhTranslations.RegisterDefaultTranslations(v, global.Trans) 62 | default: 63 | err = enTranslations.RegisterDefaultTranslations(v, global.Trans) 64 | } 65 | return 66 | } 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /mxshop-api/user-web/middlewares/admin.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "mxshop-api/user-web/models" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | //IsAdmin IsAdmin 识别用户身份类型 11 | func IsAdmin() gin.HandlerFunc { 12 | return func(ctx *gin.Context) { 13 | //将用户信息拿出 14 | claims, _ := ctx.Get("claims") 15 | currentUser := claims.(*models.CustomClaims) 16 | if currentUser.AuthorityId != 2 { 17 | ctx.JSON(http.StatusForbidden, gin.H{ 18 | "msg": "无权限", 19 | }) 20 | ctx.Abort() 21 | return 22 | } 23 | ctx.Next() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/user-web/middlewares/cors.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | //Cors 解决浏览器跨越问题,后端解决方法 10 | func Cors() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | Method := c.Request.Method 13 | 14 | //返回给浏览器,告诉浏览器header可以填什么内容 15 | c.Header("Access-Control-Allow-Origin", "*") 16 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token") 17 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT") 18 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") 19 | c.Header("Access-Control-Allow-Credentials", "true") 20 | 21 | if Method == "OPTIONS" { 22 | c.AbortWithStatus(http.StatusNoContent) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/user-web/middlewares/tracing.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "fmt" 5 | 6 | "mxshop-api/user-web/global" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/opentracing/opentracing-go" 10 | "github.com/uber/jaeger-client-go" 11 | jaegercfg "github.com/uber/jaeger-client-go/config" 12 | ) 13 | 14 | //Trace 链路追踪 15 | func Trace() gin.HandlerFunc { 16 | return func(c *gin.Context) { 17 | //初始化配置 18 | cfg := jaegercfg.Configuration{ 19 | Sampler: &jaegercfg.SamplerConfig{ 20 | Type: jaeger.SamplerTypeConst, 21 | Param: 1, 22 | }, 23 | Reporter: &jaegercfg.ReporterConfig{ 24 | LogSpans: true, 25 | LocalAgentHostPort: fmt.Sprintf("%s:%d", global.ServerConfig.Tracing.Host, global.ServerConfig.Tracing.Port), 26 | }, 27 | ServiceName: global.ServerConfig.Tracing.Name, 28 | } 29 | 30 | //初始化跟踪连 31 | Tracer, Closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger)) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | opentracing.SetGlobalTracer(Tracer) 37 | defer Closer.Close() 38 | 39 | startSpan := Tracer.StartSpan(c.Request.URL.Path) 40 | defer startSpan.Finish() 41 | 42 | c.Set("tracer", Tracer) 43 | c.Set("parentSpan", startSpan) 44 | 45 | c.Next() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mxshop-api/user-web/models/request.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | ) 6 | 7 | type CustomClaims struct { 8 | ID uint 9 | NickName string 10 | AuthorityId uint 11 | jwt.StandardClaims 12 | } 13 | -------------------------------------------------------------------------------- /mxshop-api/user-web/proto/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | //页面信息 6 | message PageInfo { 7 | uint32 pn = 1; 8 | uint32 psize = 2; 9 | } 10 | 11 | //用户信息 12 | message UserInfoResponse { 13 | int32 id = 1; 14 | string password = 2; 15 | string mobile = 3; 16 | string nickName = 4; 17 | uint64 birthday = 5; 18 | string gender = 6; 19 | int32 role = 7; 20 | } 21 | 22 | //用户信息列表 23 | message UserListResponse { 24 | int32 total = 1; 25 | repeated UserInfoResponse data = 2; 26 | } 27 | 28 | //手机号 29 | message MobileRequest { 30 | string mobile = 1; 31 | } 32 | 33 | //id 34 | message IdRequest { 35 | int32 id = 1; 36 | } 37 | 38 | //创建用户 39 | message CreateUserInfo { 40 | string nickName = 1; 41 | string password = 2; 42 | string mobile = 3; 43 | } 44 | 45 | //更新用户信息 46 | message UpdateUserInfo { 47 | int32 id = 1; 48 | string nickName = 2; 49 | string gender = 3; 50 | uint64 birthday = 4; 51 | } 52 | 53 | //用户密码 54 | message PasswordCheckInfo { 55 | string password = 1; 56 | string encryptedPassword = 2; 57 | } 58 | 59 | //核实密码响应 60 | message CheckResponse { 61 | bool success = 1; 62 | } 63 | 64 | service User{ 65 | rpc GetUserInfoList(PageInfo) returns (UserListResponse); //用户列表 66 | rpc GetUserByMobile(MobileRequest) returns (UserInfoResponse); //通过mobile查询用户 67 | rpc GetUserById(IdRequest) returns (UserInfoResponse); //通过id查询用户 68 | rpc CreateUser(CreateUserInfo) returns (UserInfoResponse); //创建用户 69 | rpc UpdateUser(UpdateUserInfo) returns (google.protobuf.Empty); //更新用户 70 | rpc CheckPassWord(PasswordCheckInfo) returns (CheckResponse); //检查密码 71 | } 72 | 73 | -------------------------------------------------------------------------------- /mxshop-api/user-web/router/base.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/user-web/api" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | //InitBaseRouter 注册图片验证码路由 10 | func InitBaseRouter(router *gin.RouterGroup) { 11 | //设置路由 12 | BaseRouter := router.Group("base") 13 | { 14 | BaseRouter.GET("captcha", api.GetChaptcha) 15 | BaseRouter.POST("send_sms", api.SendSms) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mxshop-api/user-web/router/user.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/user-web/api" 5 | "mxshop-api/user-web/middlewares" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitUserRouter(router *gin.RouterGroup) { 11 | UserRouter := router.Group("user") 12 | { 13 | //中间件的参数位置需要按业务需求而定 14 | UserRouter.GET("list", middlewares.JWTAuth(), middlewares.IsAdmin(), api.GetUserList) 15 | UserRouter.POST("login", api.PassWordLogin) 16 | UserRouter.POST("register", api.Register) 17 | } 18 | 19 | StoreRouter := router.Group("store") 20 | StoreRouter.Use(middlewares.JWTAuth()) 21 | { 22 | StoreRouter.GET("storelist", api.GetStoreList) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mxshop-api/user-web/tmp/nacos/cache/config/user-web.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-web", 3 | "port": 8081, 4 | "user_srv": { 5 | "name": "user_srv", 6 | "host": "192.168.3.105", 7 | "port": 8080 8 | }, 9 | "jwt": { 10 | "key": "2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi" 11 | }, 12 | "sms": { 13 | "key": "LTAI5tCjkwb5rRfZtUmHRo3a", 14 | "secret": "AaRNRBSeTzplpIzaDIYprdnvsIvZCZ" 15 | }, 16 | "params": { 17 | "sign_name": "生鲜小店", 18 | "code": "SMS_244610581" 19 | }, 20 | "redis": { 21 | "host": "127.0.0.1", 22 | "port": 6379, 23 | "expir": 300 24 | }, 25 | "verify": { 26 | "width": 5 27 | }, 28 | "consul": { 29 | "host": "192.168.3.105", 30 | "port": 8500 31 | } 32 | } -------------------------------------------------------------------------------- /mxshop-api/user-web/tmp/nacos/cache/config/user-web@@DEV@@555d81b1-6cbf-4b69-b48a-a034c282c69f: -------------------------------------------------------------------------------- 1 | name: 'user-web' 2 | port: 8081 3 | user_srv: 4 | name: 'user_srv' 5 | host: '192.168.3.105' 6 | port: 8080 7 | 8 | jwt: 9 | key: '2Fsa$Xx2WpTOI^Yl!FwohPd6XmVCdnQi' 10 | 11 | sms: 12 | key: 'LTAI5tCjkwb5rRfZtUmHRo3a' 13 | secret: 'AaRNRBSeTzplpIzaDIYprdnvsIvZCZ' 14 | 15 | params: 16 | sign_name: '生鲜小店' 17 | code: 'SMS_244610581' 18 | 19 | redis: 20 | host: '127.0.0.1' 21 | port: 6379 22 | expir: 300 23 | 24 | verify: 25 | width: 4 26 | 27 | consul: 28 | host: '192.168.3.105' 29 | port: 8500 -------------------------------------------------------------------------------- /mxshop-api/user-web/utils/otgrpc/README.md: -------------------------------------------------------------------------------- 1 | # OpenTracing support for gRPC in Go 2 | 3 | The `otgrpc` package makes it easy to add OpenTracing support to gRPC-based 4 | systems in Go. 5 | 6 | ## Installation 7 | 8 | ``` 9 | go get github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc 10 | ``` 11 | 12 | ## Documentation 13 | 14 | See the basic usage examples below and the [package documentation on 15 | godoc.org](https://godoc.org/github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc). 16 | 17 | ## Client-side usage example 18 | 19 | Wherever you call `grpc.Dial`: 20 | 21 | ```go 22 | // You must have some sort of OpenTracing Tracer instance on hand. 23 | var tracer opentracing.Tracer = ... 24 | ... 25 | 26 | // Set up a connection to the server peer. 27 | conn, err := grpc.Dial( 28 | address, 29 | ... // other options 30 | grpc.WithUnaryInterceptor( 31 | otgrpc.OpenTracingClientInterceptor(tracer)), 32 | grpc.WithStreamInterceptor( 33 | otgrpc.OpenTracingStreamClientInterceptor(tracer))) 34 | 35 | // All future RPC activity involving `conn` will be automatically traced. 36 | ``` 37 | 38 | ## Server-side usage example 39 | 40 | Wherever you call `grpc.NewServer`: 41 | 42 | ```go 43 | // You must have some sort of OpenTracing Tracer instance on hand. 44 | var tracer opentracing.Tracer = ... 45 | ... 46 | 47 | // Initialize the gRPC server. 48 | s := grpc.NewServer( 49 | ... // other options 50 | grpc.UnaryInterceptor( 51 | otgrpc.OpenTracingServerInterceptor(tracer)), 52 | grpc.StreamInterceptor( 53 | otgrpc.OpenTracingStreamServerInterceptor(tracer))) 54 | 55 | // All future RPC activity involving `s` will be automatically traced. 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /mxshop-api/user-web/utils/otgrpc/errors_test.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/opentracing/opentracing-go/mocktracer" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | const ( 14 | firstCode = codes.OK 15 | lastCode = codes.DataLoss 16 | ) 17 | 18 | func TestSpanTags(t *testing.T) { 19 | tracer := mocktracer.New() 20 | for code := firstCode; code <= lastCode; code++ { 21 | // Client error 22 | tracer.Reset() 23 | span := tracer.StartSpan("test-trace-client") 24 | err := status.Error(code, "") 25 | SetSpanTags(span, err, true) 26 | span.Finish() 27 | 28 | // Assert added tags 29 | rawSpan := tracer.FinishedSpans()[0] 30 | expectedTags := map[string]interface{}{ 31 | "response_code": code, 32 | "response_class": ErrorClass(err), 33 | } 34 | if err != nil { 35 | expectedTags["error"] = true 36 | } 37 | assert.Equal(t, expectedTags, rawSpan.Tags()) 38 | 39 | // Server error 40 | tracer.Reset() 41 | span = tracer.StartSpan("test-trace-server") 42 | err = status.Error(code, "") 43 | SetSpanTags(span, err, false) 44 | span.Finish() 45 | 46 | // Assert added tags 47 | rawSpan = tracer.FinishedSpans()[0] 48 | expectedTags = map[string]interface{}{ 49 | "response_code": code, 50 | "response_class": ErrorClass(err), 51 | } 52 | if err != nil && ErrorClass(err) == ServerError { 53 | expectedTags["error"] = true 54 | } 55 | assert.Equal(t, expectedTags, rawSpan.Tags()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mxshop-api/user-web/utils/otgrpc/package.go: -------------------------------------------------------------------------------- 1 | // Package otgrpc provides OpenTracing support for any gRPC client or server. 2 | // 3 | // See the README for simple usage examples: 4 | // https://github.com/grpc-ecosystem/grpc-opentracing/blob/master/go/otgrpc/README.md 5 | package otgrpc 6 | -------------------------------------------------------------------------------- /mxshop-api/user-web/utils/otgrpc/shared.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "strings" 5 | 6 | opentracing "github.com/opentracing/opentracing-go" 7 | "github.com/opentracing/opentracing-go/ext" 8 | "google.golang.org/grpc/metadata" 9 | ) 10 | 11 | var ( 12 | // Morally a const: 13 | gRPCComponentTag = opentracing.Tag{string(ext.Component), "gRPC"} 14 | ) 15 | 16 | // metadataReaderWriter satisfies both the opentracing.TextMapReader and 17 | // opentracing.TextMapWriter interfaces. 18 | type metadataReaderWriter struct { 19 | metadata.MD 20 | } 21 | 22 | func (w metadataReaderWriter) Set(key, val string) { 23 | // The GRPC HPACK implementation rejects any uppercase keys here. 24 | // 25 | // As such, since the HTTP_HEADERS format is case-insensitive anyway, we 26 | // blindly lowercase the key (which is guaranteed to work in the 27 | // Inject/Extract sense per the OpenTracing spec). 28 | key = strings.ToLower(key) 29 | w.MD[key] = append(w.MD[key], val) 30 | } 31 | 32 | func (w metadataReaderWriter) ForeachKey(handler func(key, val string) error) error { 33 | for k, vals := range w.MD { 34 | for _, v := range vals { 35 | if err := handler(k, v); err != nil { 36 | return err 37 | } 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /mxshop-api/user-web/utils/otgrpc/test/otgrpc_testing/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package otgrpc.testing; 4 | 5 | message SimpleRequest { 6 | int32 payload = 1; 7 | } 8 | 9 | message SimpleResponse { 10 | int32 payload = 1; 11 | } 12 | 13 | service TestService { 14 | rpc UnaryCall(SimpleRequest) returns (SimpleResponse); 15 | 16 | rpc StreamingOutputCall(SimpleRequest) returns (stream SimpleResponse); 17 | 18 | rpc StreamingInputCall(stream SimpleRequest) returns (SimpleResponse); 19 | 20 | rpc StreamingBidirectionalCall(stream SimpleRequest) returns (stream SimpleResponse); 21 | } 22 | -------------------------------------------------------------------------------- /mxshop-api/user-web/utils/register/consul/register.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | type Registry struct { 10 | Host string 11 | Port int 12 | } 13 | 14 | type RegistryClient interface { 15 | Register(address string, port int, name string, tags []string, id string) error 16 | DeRegister(serverId string) error 17 | } 18 | 19 | func NewRegistryClient(host string, port int) RegistryClient { 20 | return &Registry{ 21 | Host: host, 22 | Port: port, 23 | } 24 | } 25 | 26 | //Register 注册服务至注册中心 27 | func (r *Registry) Register(address string, port int, name string, tags []string, id string) error { 28 | //DefaultConfig 返回客户端的默认配置 29 | cfg := api.DefaultConfig() 30 | //Address为consul的host和port 31 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 32 | 33 | client, err := api.NewClient(cfg) 34 | if err != nil { 35 | panic(err) 36 | } 37 | //对外host和ip 38 | //生成对应的检查对象 39 | check := &api.AgentServiceCheck{ 40 | HTTP: fmt.Sprintf("http://%s:%d/health", address, port), 41 | Timeout: "5s", 42 | Interval: "5s", 43 | DeregisterCriticalServiceAfter: "10s", 44 | } 45 | 46 | //生成注册对象 47 | registration := new(api.AgentServiceRegistration) 48 | registration.Name = name 49 | registration.ID = id 50 | registration.Port = port 51 | registration.Tags = tags 52 | registration.Address = address 53 | registration.Check = check 54 | 55 | //注册 56 | err = client.Agent().ServiceRegister(registration) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return nil 61 | } 62 | 63 | //DeRegister 注销服务 64 | func (r *Registry) DeRegister(serverId string) error { 65 | //DefaultConfig 返回客户端的默认配置 66 | cfg := api.DefaultConfig() 67 | 68 | //Address为consul的host和port 69 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 70 | 71 | client, err := api.NewClient(cfg) 72 | if err != nil { 73 | return err 74 | } 75 | //注销 76 | err = client.Agent().ServiceDeregister(serverId) 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /mxshop-api/user-web/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | //GetFreePort 分配线上随机端口 8 | func GetFreePort() (int, error) { 9 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 10 | if err != nil { 11 | return 0, err 12 | } 13 | 14 | l, err := net.ListenTCP("tcp", addr) 15 | if err != nil { 16 | return 0, err 17 | } 18 | defer l.Close() 19 | return l.Addr().(*net.TCPAddr).Port, nil 20 | } 21 | -------------------------------------------------------------------------------- /mxshop-api/user-web/validator/validator.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | //ValidatorMobile 自定义validator 10 | func ValidatorMobile(fl validator.FieldLevel) bool { 11 | mobile := fl.Field().String() 12 | //使用正则表达式准标准库:regexp 13 | ok, _ := regexp.MatchString(`^1([38][0-9]|14[579]|5[^4]|16[6]|7[1-35-8]|9[189])\d{8}$`, mobile) 14 | if !ok { 15 | return false 16 | } 17 | return true 18 | } 19 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/api/base.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "mxshop-api/userop-web/global" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/go-playground/validator/v10" 11 | "google.golang.org/grpc/codes" 12 | "google.golang.org/grpc/status" 13 | ) 14 | 15 | func removeTopStruct(fileds map[string]string) map[string]string { 16 | rsp := map[string]string{} 17 | for field, err := range fileds { 18 | rsp[field[strings.Index(field, ".")+1:]] = err 19 | } 20 | return rsp 21 | } 22 | 23 | //HandleValidatorErr 表单验证错误处理返回 24 | func HandleValidatorErr(c *gin.Context, err error) { 25 | errs, ok := err.(validator.ValidationErrors) 26 | if !ok { 27 | c.JSON(http.StatusOK, gin.H{ 28 | "msg": err.Error(), 29 | }) 30 | } 31 | c.JSON(http.StatusBadRequest, gin.H{ 32 | "error": removeTopStruct(errs.Translate(global.Trans)), 33 | }) 34 | return 35 | } 36 | 37 | //HandleGrpcErrToHttp grpc状态码转http 38 | func HandleGrpcErrToHttp(err error, c *gin.Context) { 39 | if err != nil { 40 | if e, ok := status.FromError(err); ok { 41 | switch e.Code() { 42 | case codes.NotFound: 43 | c.JSON(http.StatusNotFound, gin.H{ 44 | "msg": e.Message(), 45 | }) 46 | case codes.Internal: 47 | c.JSON(http.StatusInternalServerError, gin.H{ 48 | "msg": "内部错误", 49 | }) 50 | case codes.InvalidArgument: 51 | c.JSON(http.StatusBadRequest, gin.H{ 52 | "msg": "参数错误", 53 | }) 54 | case codes.Unavailable: 55 | c.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": "用户服务不可用", 57 | }) 58 | default: 59 | c.JSON(http.StatusInternalServerError, gin.H{ 60 | "msg": "其他错误", 61 | }) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/config-debug.yaml: -------------------------------------------------------------------------------- 1 | host: '10.2.105.13' 2 | port: 8848 3 | namespace_id: 'c52838c8-880e-49ac-8478-d4c94e0ed154' 4 | user: 'nacos' 5 | password: 'nacos' 6 | data_id: 'userop-web.json' 7 | group: 'dev' 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | //GoodsSerConfig 映射商品配置 4 | type GoodsSerConfig struct { 5 | Name string `mapstructure:"name" json:"name"` 6 | Host string `mapstructure:"host" json:"host"` 7 | Port int `mapstructure:"port" json:"port"` 8 | } 9 | 10 | //JWTConfig 映射token配置 11 | type JWTConfig struct { 12 | SigningKey string `mapstructure:"key" json:"key"` 13 | } 14 | 15 | //AliSmsConfig 阿里秘钥 16 | type AliSmsConfig struct { 17 | Apikey string `mapstructure:"key" json:"key"` 18 | ApiSecret string `mapstructure:"secret" json:"secret"` 19 | } 20 | 21 | //ConsulConfig 注册中心配置 22 | type ConsulConfig struct { 23 | Host string `mapstructure:"host" json:"host"` 24 | Port int `mapstructure:"port" json:"port"` 25 | } 26 | 27 | //ServerConfig 映射服务配置 28 | type ServerConfig struct { 29 | Name string `mapstructure:"name" json:"name"` 30 | Host string `mapstructure:"host" json:"host"` 31 | Port int `mapstructure:"port" json:"port"` 32 | Tag []string `mapstructure:"tag" json:"tag"` 33 | GoodsSerInfo GoodsSerConfig `mapstructure:"goods_srv" json:"goods_srv"` 34 | UserOpSerInfo GoodsSerConfig `mapstructure:"userOp_srv" json:"userOp_srv"` 35 | JWTInfo JWTConfig `mapstructure:"jwt" json:"jwt"` 36 | ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"` 37 | } 38 | 39 | //NacosConfig 配置中心配置 40 | type NacosConfig struct { 41 | Host string `mapstructure:"host" json:"host"` 42 | Port uint64 `mapstructure:"port" json:"port"` 43 | NamespaceId string `mapstructure:"namespace_id" json:"namespace_id"` 44 | User string `mapstructure:"user" json:"user"` 45 | Password string `mapstructure:"password" json:"password"` 46 | DataId string `mapstructure:"data_id" json:"data_id"` 47 | Group string `mapstructure:"group" json:"group"` 48 | } 49 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/forms/address.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | type AddressForm struct { 4 | Province string `form:"province" json:"province" binding:"required"` 5 | City string `form:"city" json:"city" binding:"required"` 6 | District string `form:"district" json:"district" binding:"required"` 7 | Address string `form:"address" json:"address" binding:"required"` 8 | SignerName string `form:"signer_name" json:"signer_name" binding:"required"` 9 | SignerMobile string `form:"signer_mobile" json:"signer_mobile" binding:"required"` 10 | } 11 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/forms/message.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | type MessageForm struct { 4 | MessageType int32 `form:"type" json:"type" binding:"required,oneof=1 2 3 4 5"` 5 | Subject string `form:"subject" json:"subject" binding:"required"` 6 | Message string `form:"message" json:"message" binding:"required"` 7 | File string `form:"file" json:"file" binding:"required"` 8 | } 9 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/forms/userfav.go: -------------------------------------------------------------------------------- 1 | package forms 2 | 3 | type UserFavForm struct { 4 | GoodsId int32 `form:"goods" json:"goods" binding:"required"` 5 | } 6 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop-api/userop-web/config" 5 | "mxshop-api/userop-web/proto" 6 | 7 | ut "github.com/go-playground/universal-translator" 8 | ) 9 | 10 | //需要的全局变量 11 | var ( 12 | Trans ut.Translator //声明一个全局翻译器 13 | ServerConfig *config.ServerConfig = &config.ServerConfig{} //声明配置信息 14 | GoodsSrvClient proto.GoodsClient //grpc Client 15 | MessageSrvClient proto.MessageClient 16 | AddressSrvClient proto.AddressClient 17 | UserFavSrvClient proto.UserFavClient 18 | NacosConfig *config.NacosConfig = &config.NacosConfig{} 19 | ) 20 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/global/response/user.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | //JsonTime 时间格式转换 9 | type JsonTime time.Time 10 | 11 | //MarshalJSON 当数据调用c.JSON时,MarshalJSON会自动被调用 12 | func (j JsonTime) MarshalJSON() ([]byte, error) { 13 | stdtime := fmt.Sprintf("\"%s\"", time.Time(j).Format("2022-01-01")) 14 | return []byte(stdtime), nil 15 | } 16 | 17 | type UserResponse struct { 18 | Id int32 `json:"id"` 19 | NickName string `json:"name"` 20 | BirthDay JsonTime `json:"birthday"` 21 | Gender string `json:"gender"` 22 | Mobile string `json:"mobile"` 23 | } 24 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | // 1. zap.S可以配置一个全局的sugar,用来配置全局logger 10 | // 2. 日志级别:debug、info、warn、error、fetal 11 | // 3. S函数和L函数可以配置全局的安全的logger 12 | 13 | //InitLogger 初始化日志 14 | func InitLogger() { 15 | //初始化日志 16 | logger, err := zap.NewDevelopment() 17 | if err != nil { 18 | log.Fatal("日志初始化失败", err.Error()) 19 | } 20 | //使用全局logger 21 | zap.ReplaceGlobals(logger) 22 | } 23 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/initialize/router.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "net/http" 5 | 6 | "mxshop-api/userop-web/middlewares" 7 | "mxshop-api/userop-web/router" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | //Routers 初始化及路由分发 13 | func Routers() *gin.Engine { 14 | Router := gin.Default() 15 | 16 | //配置跨越 17 | Router.Use(middlewares.Cors()) 18 | 19 | ApiGroup := Router.Group("up") 20 | //分发路由 21 | ApiGroup = ApiGroup.Group("v1") 22 | 23 | router.InitMessageRouter(ApiGroup) //留言 24 | router.InitUserFavRouter(ApiGroup) //商品收藏 25 | router.InitAddressRouter(ApiGroup) //收货地址 26 | 27 | //健康检查 28 | Router.GET("/health", func(c *gin.Context) { 29 | c.JSON(http.StatusOK, gin.H{ 30 | "message": "health", 31 | }) 32 | }) 33 | 34 | return Router 35 | } 36 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/initialize/srv_conn.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | 6 | "mxshop-api/userop-web/global" 7 | "mxshop-api/userop-web/proto" 8 | 9 | _ "github.com/mbobakov/grpc-consul-resolver" // It's important 10 | "go.uber.org/zap" 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | //InitSrvConn 连接到consul注册中心并对服务做负载均衡 15 | func InitSrvConn() { 16 | consul := global.ServerConfig.ConsulInfo 17 | //连接用户操作服务 18 | UserOpConn, err := grpc.Dial( 19 | fmt.Sprintf("consul://%s:%d/%s?wait=14s&tag=srv", consul.Host, consul.Port, global.ServerConfig.UserOpSerInfo.Name), 20 | grpc.WithInsecure(), 21 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), 22 | ) 23 | 24 | fmt.Println(global.ServerConfig) 25 | 26 | if err != nil { 27 | zap.S().Errorw("[InitSrvConn] 连接 【订单服务失败】", err.Error()) 28 | return 29 | } 30 | MessageClient := proto.NewMessageClient(UserOpConn) 31 | global.MessageSrvClient = MessageClient 32 | 33 | AddressClient := proto.NewAddressClient(UserOpConn) 34 | global.AddressSrvClient = AddressClient 35 | 36 | UserFavClient := proto.NewUserFavClient(UserOpConn) 37 | global.UserFavSrvClient = UserFavClient 38 | 39 | //连接商品服务 40 | Goodsconn, err := grpc.Dial( 41 | fmt.Sprintf("consul://%s:%d/%s?wait=14s&tag=srv", consul.Host, consul.Port, global.ServerConfig.GoodsSerInfo.Name), 42 | grpc.WithInsecure(), 43 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), 44 | ) 45 | 46 | fmt.Println(global.ServerConfig) 47 | 48 | if err != nil { 49 | zap.S().Errorw("[InitSrvConn] 连接 【商品服务失败】", err.Error()) 50 | return 51 | } 52 | goodsClient := proto.NewGoodsClient(Goodsconn) 53 | global.GoodsSrvClient = goodsClient 54 | } 55 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/middlewares/admin.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "mxshop-api/userop-web/models" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | //IsAdmin IsAdmin 识别用户身份类型 11 | func IsAdmin() gin.HandlerFunc { 12 | return func(ctx *gin.Context) { 13 | //将用户信息拿出 14 | claims, _ := ctx.Get("claims") 15 | currentUser := claims.(*models.CustomClaims) 16 | if currentUser.AuthorityId != 2 { 17 | ctx.JSON(http.StatusForbidden, gin.H{ 18 | "msg": "无权限", 19 | }) 20 | ctx.Abort() 21 | return 22 | } 23 | ctx.Next() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/middlewares/cors.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | //Cors 解决浏览器跨越问题,后端解决方法 10 | func Cors() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | Method := c.Request.Method 13 | 14 | //返回给浏览器,告诉浏览器header可以填什么内容 15 | c.Header("Access-Control-Allow-Origin", "*") 16 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token, x-token") 17 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PATCH, PUT") 18 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") 19 | c.Header("Access-Control-Allow-Credentials", "true") 20 | 21 | if Method == "OPTIONS" { 22 | c.AbortWithStatus(http.StatusNoContent) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/models/request.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | ) 6 | 7 | type CustomClaims struct { 8 | ID uint 9 | NickName string 10 | AuthorityId uint 11 | jwt.StandardClaims 12 | } 13 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/proto/address.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | service Address { 6 | rpc GetAddressList(GetAddressRequest) returns (AddressListResponse); //获取收货地址 7 | rpc CreateAddress(GetAddressRequest) returns (AddressResponse); //新建收货地址 8 | rpc UpdateAddress(GetAddressRequest) returns (google.protobuf.Empty); //更新收货地址 9 | rpc DeleteAddress(GetAddressRequest) returns (google.protobuf.Empty); //删除收货地址 10 | } 11 | 12 | message GetAddressRequest { 13 | int32 id = 1; 14 | int32 userId = 2; 15 | string province = 3; 16 | string city = 4; 17 | string district = 5; 18 | string address = 6; 19 | string signerName = 7; 20 | string signerMobile = 8; 21 | } 22 | 23 | message AddressResponse{ 24 | int32 id = 1; 25 | int32 userId = 2; 26 | string province = 3; 27 | string city = 4; 28 | string district = 5; 29 | string address = 6; 30 | string signerName = 7; 31 | string signerMobile = 8; 32 | } 33 | 34 | message AddressListResponse { 35 | int32 total = 1; 36 | repeated AddressResponse data = 2; 37 | } -------------------------------------------------------------------------------- /mxshop-api/userop-web/proto/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | option go_package = "/.;proto"; 3 | 4 | service Message { 5 | rpc GetMessageList(MessageRequest) returns (MessageListResponse); //获取留言列表 6 | rpc CreateMessage(MessageRequest) returns (MessageResponse); //新建留言 7 | 8 | } 9 | 10 | message MessageRequest { 11 | int32 id = 1; 12 | int32 userId = 2; 13 | int32 messageType = 3; 14 | string subject = 4; 15 | string message = 5; 16 | string file = 6; 17 | } 18 | 19 | message MessageResponse { 20 | int32 id = 1; 21 | int32 userId = 2; 22 | int32 messageType = 3; 23 | string subject = 4; 24 | string message = 5; 25 | string file = 6; 26 | } 27 | 28 | message MessageListResponse { 29 | int32 total = 1; 30 | repeated MessageResponse data = 2; 31 | } 32 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/proto/userfav.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | service UserFav{ 6 | rpc GetFavList(UserFavRequest) returns(UserFavListResponse); //过滤收藏信息 7 | rpc AddUserFav(UserFavRequest) returns(google.protobuf.Empty); //添加收藏 8 | rpc DeleteUserFav(UserFavRequest) returns(google.protobuf.Empty); //删除收藏 9 | rpc GetUserFavDetail(UserFavRequest) returns(google.protobuf.Empty); //查看用户是否已经收藏某件商品 10 | } 11 | 12 | message UserFavRequest{ 13 | int32 userId = 1; 14 | int32 goodsId = 2; 15 | } 16 | message UserFavResponse{ 17 | int32 userId = 1; 18 | int32 goodsId = 2; 19 | } 20 | 21 | message UserFavListResponse { 22 | int32 total = 1; 23 | repeated UserFavResponse data = 2; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/router/address.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/userop-web/api/address" 5 | "mxshop-api/userop-web/middlewares" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitAddressRouter(router *gin.RouterGroup) { 11 | AddressRouter := router.Group("address").Use(middlewares.JWTAuth()) 12 | { 13 | AddressRouter.GET("", address.List) //获取收货地址列表 14 | AddressRouter.POST("", address.New) //新建收货地址 15 | AddressRouter.PUT("/:id", address.Update) //更新收货地址 16 | AddressRouter.DELETE("/:id", address.Delete) //删除收货地址 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/router/message.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/userop-web/api/message" 5 | "mxshop-api/userop-web/middlewares" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitMessageRouter(router *gin.RouterGroup) { 11 | MessageRouter := router.Group("message").Use(middlewares.JWTAuth()) 12 | { 13 | //中间件的参数位置需要按业务需求而定 14 | MessageRouter.GET("", message.List) //留言列表 15 | MessageRouter.POST("", message.New) //新建留言 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/router/userfav.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "mxshop-api/userop-web/api/userfav" 5 | "mxshop-api/userop-web/middlewares" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func InitUserFavRouter(router *gin.RouterGroup) { 11 | UserFavRouter := router.Group("userfavs").Use(middlewares.JWTAuth()) 12 | { 13 | UserFavRouter.GET("", middlewares.JWTAuth(), userfav.List) //获取当前用户的收藏 14 | UserFavRouter.GET("/:id", middlewares.JWTAuth(), userfav.Detail) // 获取收藏记录 15 | UserFavRouter.POST("", middlewares.JWTAuth(), userfav.New) //新建收藏记录 16 | UserFavRouter.DELETE("/:id", middlewares.JWTAuth(), userfav.Delete) // 删除收藏记录 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/utils/register/consul/register.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/consul/api" 7 | ) 8 | 9 | type Registry struct { 10 | Host string 11 | Port int 12 | } 13 | 14 | type RegistryClient interface { 15 | Register(address string, port int, name string, tags []string, id string) error 16 | DeRegister(serverId string) error 17 | } 18 | 19 | func NewRegistryClient(host string, port int) RegistryClient { 20 | return &Registry{ 21 | Host: host, 22 | Port: port, 23 | } 24 | } 25 | 26 | //Register 注册服务至注册中心 27 | func (r *Registry) Register(address string, port int, name string, tags []string, id string) error { 28 | //DefaultConfig 返回客户端的默认配置 29 | cfg := api.DefaultConfig() 30 | //Address为consul的host和port 31 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 32 | 33 | client, err := api.NewClient(cfg) 34 | if err != nil { 35 | panic(err) 36 | } 37 | //对外host和ip 38 | //生成对应的检查对象 39 | check := &api.AgentServiceCheck{ 40 | HTTP: fmt.Sprintf("http://%s:%d/health", address, port), 41 | Timeout: "5s", 42 | Interval: "5s", 43 | DeregisterCriticalServiceAfter: "10s", 44 | } 45 | 46 | //生成注册对象 47 | registration := new(api.AgentServiceRegistration) 48 | registration.Name = name 49 | registration.ID = id 50 | registration.Port = port 51 | registration.Tags = tags 52 | registration.Address = address 53 | registration.Check = check 54 | 55 | //注册 56 | err = client.Agent().ServiceRegister(registration) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return nil 61 | } 62 | 63 | //DeRegister 注销服务 64 | func (r *Registry) DeRegister(serverId string) error { 65 | //DefaultConfig 返回客户端的默认配置 66 | cfg := api.DefaultConfig() 67 | 68 | //Address为consul的host和port 69 | cfg.Address = fmt.Sprintf("%s:%d", r.Host, r.Port) 70 | 71 | client, err := api.NewClient(cfg) 72 | if err != nil { 73 | return err 74 | } 75 | //注销 76 | err = client.Agent().ServiceDeregister(serverId) 77 | return err 78 | } 79 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | //GetFreePort 分配线上随机端口 8 | func GetFreePort() (int, error) { 9 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 10 | if err != nil { 11 | return 0, err 12 | } 13 | 14 | l, err := net.ListenTCP("tcp", addr) 15 | if err != nil { 16 | return 0, err 17 | } 18 | defer l.Close() 19 | return l.Addr().(*net.TCPAddr).Port, nil 20 | } 21 | -------------------------------------------------------------------------------- /mxshop-api/userop-web/validator/validator.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | //ValidatorMobile 自定义validator 10 | func ValidatorMobile(fl validator.FieldLevel) bool { 11 | mobile := fl.Field().String() 12 | //使用正则表达式准标准库:regexp 13 | ok, _ := regexp.MatchString(`^1([38][0-9]|14[579]|5[^4]|16[6]|7[1-35-8]|9[189])\d{8}$`, mobile) 14 | if !ok { 15 | return false 16 | } 17 | return true 18 | } 19 | -------------------------------------------------------------------------------- /mxshop_srvs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iceymoss/mxshop/4613a07130e2c878a33457ac640216e501c9cc67/mxshop_srvs/.DS_Store -------------------------------------------------------------------------------- /mxshop_srvs/go.mod: -------------------------------------------------------------------------------- 1 | module mxshop_srvs 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect 7 | github.com/anaskhan96/go-password-encoder v0.0.0-20201010210601-c765b799fd72 8 | github.com/apache/rocketmq-client-go/v2 v2.1.1 9 | github.com/gin-gonic/gin v1.8.1 10 | github.com/go-redis/redis/v8 v8.11.5 11 | github.com/go-redsync/redsync/v4 v4.6.0 12 | github.com/golang/protobuf v1.5.2 13 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 14 | github.com/hashicorp/consul/api v1.12.0 15 | github.com/mbobakov/grpc-consul-resolver v1.4.4 16 | github.com/nacos-group/nacos-sdk-go v1.1.1 17 | github.com/olivere/elastic/v7 v7.0.32 18 | github.com/opentracing/opentracing-go v1.2.0 19 | github.com/satori/go.uuid v1.2.0 20 | github.com/spf13/viper v1.12.0 21 | github.com/stretchr/testify v1.7.1 22 | github.com/uber/jaeger-client-go v2.30.0+incompatible 23 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 24 | go.uber.org/zap v1.21.0 25 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect 26 | golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 27 | google.golang.org/grpc v1.47.0 28 | google.golang.org/protobuf v1.28.0 29 | gorm.io/driver/mysql v1.3.4 30 | gorm.io/gorm v1.23.6 31 | ) 32 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/config-debug.yaml: -------------------------------------------------------------------------------- 1 | #name: 'goods_srv' 2 | #host: '192.168.3.105' 3 | #port: 8080 4 | #mysql: 5 | # host: '127.0.0.1' 6 | # port: 3306 7 | # db: 'mxshop_goods_srv' 8 | # user: 'root' 9 | # password: 'dhfdjfdfng' 10 | # 11 | #consul: 12 | # host: '192.168.3.105' 13 | # port: 8500 14 | # 15 | 16 | nacos: 17 | host: '10.2.69.164' 18 | port: 8848 19 | namespace_id: 'fe432146-9559-44d1-8245-3541feacc9cf' 20 | user: 'nacos' 21 | password: 'nacos' 22 | data_id: 'goods-srv.json' 23 | group: 'dev' 24 | 25 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | //MysqlConfig mysql信息配置 4 | type MysqlConfig struct { 5 | Host string `mapstructure:"host" json:"host"` 6 | Port int `mapstructure:"port" json:"port"` 7 | Name string `mapstructure:"db" json:"db"` 8 | User string `mapstructure:"user" json:"user"` 9 | Password string `mapstructure:"password" json:"password"` 10 | } 11 | 12 | //ConsulConfig consul配置 13 | type ConsulConfig struct { 14 | Host string `mapstructure:"host" json:"host"` 15 | Port int `mapstructure:"port" json:"port"` 16 | } 17 | 18 | //EsConfig es配置 19 | type EsConfig struct { 20 | Host string `mapstructure:"host" json:"host"` 21 | Port int `mapstructure:"port" json:"port"` 22 | } 23 | 24 | //ServerConfig 服务配置 25 | type ServerConfig struct { 26 | Name string `mapstructure:"name" json:"name"` 27 | Host string `mapstructure:"host" json:"host"` 28 | Port int `mapstructure:"port" json:"port"` 29 | MysqlInfo MysqlConfig `mapstructure:"mysql" json:"mysql"` 30 | ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"` 31 | EsConfig EsConfig `mapstructure:"es" json:"es"` 32 | Tags []string `mapstructure:"tags" json:"tags"` 33 | } 34 | 35 | //NacosConfig 配置中心配置 36 | type NacosConfig struct { 37 | Host string `mapstructure:"host" json:"host"` 38 | Port uint64 `mapstructure:"port" json:"port"` 39 | NamespaceId string `mapstructure:"namespace_id" json:"namespace_id"` 40 | User string `mapstructure:"user" json:"user"` 41 | Password string `mapstructure:"password" json:"password"` 42 | DataId string `mapstructure:"data_id" json:"data_id"` 43 | Group string `mapstructure:"group" json:"group"` 44 | } 45 | 46 | //NacosServer 服务配置中心 47 | type NacosServer struct { 48 | NacosInfo NacosConfig `mapstructure:"nacos" json:"nacos"` 49 | } 50 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop_srvs/goods_srv/config" 5 | 6 | "github.com/olivere/elastic/v7" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | var ( 11 | DB *gorm.DB 12 | ServerConfig config.ServerConfig 13 | NacosConfig *config.NacosServer = &config.NacosServer{} 14 | EsClient *elastic.Client 15 | ) 16 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/handler/base.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import "mxshop_srvs/goods_srv/proto" 4 | 5 | type GoodsServer struct { 6 | proto.UnimplementedGoodsServer 7 | } 8 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/initialize/db.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "mxshop_srvs/goods_srv/global" 7 | "os" 8 | "time" 9 | 10 | "gorm.io/gorm/schema" 11 | 12 | "gorm.io/driver/mysql" 13 | "gorm.io/gorm" 14 | "gorm.io/gorm/logger" 15 | ) 16 | 17 | func InitDB() { 18 | //dsn := "root:hidfidfidfng@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local" 19 | c := global.ServerConfig.MysqlInfo 20 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", c.User, c.Password, c.Host, c.Port, c.Name) 21 | //用于输出使用的sql语句 22 | newLogger := logger.New( 23 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 24 | logger.Config{ 25 | SlowThreshold: time.Second, // 慢 SQL 阈值 26 | LogLevel: logger.Info, // 日志级别 27 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 28 | Colorful: true, // 禁用彩色打印 29 | }, 30 | ) 31 | var err error 32 | 33 | fmt.Println(dsn) 34 | //打开mysql服务中对应的数据库 35 | global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ 36 | 37 | //配置原名 38 | NamingStrategy: schema.NamingStrategy{ 39 | SingularTable: true, 40 | }, 41 | Logger: newLogger, 42 | }) 43 | if err != nil { 44 | panic(err) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/initialize/es.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "mxshop_srvs/goods_srv/global" 10 | "mxshop_srvs/goods_srv/model" 11 | 12 | "github.com/olivere/elastic/v7" 13 | "go.uber.org/zap" 14 | ) 15 | 16 | //InitEs 初始化es服务 17 | func InitEs() { 18 | EsConfig := global.ServerConfig.EsConfig 19 | url := fmt.Sprintf("http://%s:%d/", EsConfig.Host, EsConfig.Port) 20 | logger := log.New(os.Stdout, "mxshop", log.LstdFlags) 21 | 22 | var err error 23 | global.EsClient, err = elastic.NewClient(elastic.SetURL(url), elastic.SetSniff(false), elastic.SetTraceLog(logger)) 24 | if err != nil { 25 | zap.S().Info("初始化EsClient失败:", err) 26 | } 27 | 28 | //新建index和mapping 29 | //先查询index是否存在 30 | exists, err := global.EsClient.IndexExists(model.EsGoods{}.GetIndexName()).Do(context.Background()) 31 | if err != nil { 32 | panic(err) 33 | } 34 | //新建索引和mapping 35 | if !exists { 36 | _, err = global.EsClient.CreateIndex(model.EsGoods{}.GetIndexName()).BodyString(model.EsGoods{}.GetMapping()).Do(context.Background()) 37 | if err != nil { 38 | panic(err) 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | //InitLogger 初始化日志 10 | func InitLogger() { 11 | //初始化日志 12 | logger, err := zap.NewDevelopment() 13 | if err != nil { 14 | log.Fatal("日志初始化失败", err.Error()) 15 | } 16 | //使用全局logger 17 | zap.ReplaceGlobals(logger) 18 | } 19 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/model/base.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "time" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | //GormList gorm自定义模型 12 | type GormList []string 13 | 14 | func (g GormList) Value() (driver.Value, error) { 15 | return json.Marshal(g) 16 | } 17 | 18 | func (g *GormList) Scan(value interface{}) error { 19 | return json.Unmarshal(value.([]byte), &g) 20 | } 21 | 22 | //BaseModel 公共字段 23 | type BaseModel struct { 24 | ID int32 `gorm:"primarykey;type:int" json:"id"` 25 | CreatedAt time.Time `gorm:"column:add_time" json:"-"` //column 定义别名 26 | UpdatedAt time.Time `gorm:"column:update_time" json:"-"` 27 | DeletedAt gorm.DeletedAt `json:"-"` 28 | IsDeleted bool `json:"-"` 29 | } 30 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/model/es_goods.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | //EsGoods es中字段需要和mysql中goods一致,尤其是ID 4 | type EsGoods struct { 5 | ID int32 `json:"id"` 6 | CategoryID int32 `json:"category_id"` 7 | BrandsID int32 `json:"brand_id"` 8 | OnSale bool `json:"on_sale"` 9 | ShipFree bool `json:"ship_free"` 10 | IsNew bool `json:"is_new"` 11 | IsHot bool `json:"is_hot"` 12 | 13 | Name string `json:"name"` 14 | ClickNum int32 `json:"click_num"` 15 | SoldNum int32 `json:"sold_num"` 16 | FavNum int32 `json:"fav_num"` 17 | MarketPrice float32 `json:"market_price"` 18 | GoodsBrief string `json:"goods_brief"` 19 | ShopPrice float32 `json:"shop_price"` 20 | } 21 | 22 | func (EsGoods) GetIndexName() string { 23 | return "goods" 24 | } 25 | 26 | //GetMapping 获取es中的mapping 27 | func (EsGoods) GetMapping() string { 28 | goodsMapping := ` 29 | { 30 | "mappings" : { 31 | "properties" : { 32 | "brands_id" : { 33 | "type" : "integer" 34 | }, 35 | "category_id" : { 36 | "type" : "integer" 37 | }, 38 | "click_num" : { 39 | "type" : "integer" 40 | }, 41 | "fav_num" : { 42 | "type" : "integer" 43 | }, 44 | "id" : { 45 | "type" : "integer" 46 | }, 47 | "is_hot" : { 48 | "type" : "boolean" 49 | }, 50 | "is_new" : { 51 | "type" : "boolean" 52 | }, 53 | "market_price" : { 54 | "type" : "float" 55 | }, 56 | "name" : { 57 | "type" : "text", 58 | "analyzer":"ik_max_word" 59 | }, 60 | "goods_brief" : { 61 | "type" : "text", 62 | "analyzer":"ik_max_word" 63 | }, 64 | "on_sale" : { 65 | "type" : "boolean" 66 | }, 67 | "ship_free" : { 68 | "type" : "boolean" 69 | }, 70 | "shop_price" : { 71 | "type" : "float" 72 | }, 73 | "sold_num" : { 74 | "type" : "long" 75 | } 76 | } 77 | } 78 | }` 79 | return goodsMapping 80 | } 81 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/test/Banner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "mxshop_srvs/goods_srv/proto" 8 | 9 | "google.golang.org/protobuf/types/known/emptypb" 10 | ) 11 | 12 | //var BrandClient proto.GoodsClient 13 | //var Conn *grpc.ClientConn 14 | // 15 | //func Init() { 16 | // var err error 17 | // //使用grpc.Dial()进行拨号, grpc.WithInsecure()使用不安全的方式连接 18 | // Conn, err = grpc.Dial("192.168.3.103:8080", grpc.WithInsecure()) 19 | // if err != nil { 20 | // log.Panicln("连接失败", err) 21 | // } 22 | // BrandClient = proto.NewGoodsClient(Conn) 23 | //} 24 | 25 | func TestGetBannerList() { 26 | c := context.Background() 27 | Rsp, err := BrandClient.BannerList(c, &emptypb.Empty{}) 28 | if err != nil { 29 | log.Fatal("获取轮播图列表失败", err) 30 | } 31 | for _, value := range Rsp.Data { 32 | fmt.Println("url:", value.Url) 33 | } 34 | fmt.Println("总数:", Rsp.Total) 35 | } 36 | 37 | func TestCreateBanner() { 38 | c := context.Background() 39 | res, err := BrandClient.CreateBanner(c, &proto.BannerRequest{ 40 | Id: 6, 41 | Index: 6, 42 | Image: "http://shop.projectsedu.com/media/banner/banner2_GmcsBvj.jpg", 43 | Url: "http://shop.projectsedu.com/media/banner", 44 | }) 45 | if err != nil { 46 | log.Fatal("新增轮播图失败", err) 47 | } 48 | fmt.Println("轮播图信息:", res) 49 | } 50 | 51 | func TestDeleteBanner() { 52 | c := context.Background() 53 | _, err := BrandClient.DeleteBanner(c, &proto.BannerRequest{ 54 | Id: 5, 55 | }) 56 | if err != nil { 57 | log.Fatal("删除失败", err.Error()) 58 | } 59 | } 60 | 61 | func TestUpdateBanner() { 62 | c := context.Background() 63 | _, err := BrandClient.UpdateBanner(c, &proto.BannerRequest{ 64 | Id: 7, 65 | Index: 7, 66 | Image: "http://shop.projectsedu.com/banner2_GmcsBvj.jpg", 67 | Url: "http:ice_moos.top/dhfidf/dhfidhf", 68 | }) 69 | if err != nil { 70 | log.Fatal("更新失败", err) 71 | } 72 | } 73 | 74 | //func main() { 75 | // Init() 76 | // TestGetBannerList() 77 | // TestCreateBanner() 78 | // //TestDeleteBanner() 79 | // TestUpdateBanner() 80 | //} 81 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/test/base.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "mxshop_srvs/goods_srv/proto" 6 | 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | var BrandClient proto.GoodsClient 11 | var Conn *grpc.ClientConn 12 | 13 | func Init() { 14 | var err error 15 | //使用grpc.Dial()进行拨号, grpc.WithInsecure()使用不安全的方式连接 16 | Conn, err = grpc.Dial("10.2.121.49:8081", grpc.WithInsecure()) 17 | if err != nil { 18 | log.Panicln("连接失败", err) 19 | } 20 | BrandClient = proto.NewGoodsClient(Conn) 21 | } 22 | 23 | func main() { 24 | Init() 25 | //TestGetCategoryAllList() 26 | //TestGetSubCategory() 27 | //TestCreateCategory() 28 | //TestDeleteCategory() 29 | 30 | //TestUpdateCategory() 31 | 32 | //TestCategoryBrandList() 33 | //TestGetCategoryBrandList() 34 | //TestCreateBrand() 35 | 36 | //TestCreateCategoryBrand() 37 | //TestDeleteCategoryBrand() 38 | //TestUpdateCategoryBrand() 39 | //TestGetCategoryBrandList() 40 | 41 | //TestGoodsList() 42 | 43 | //TestGetGoodsDetail() 44 | 45 | //TestGetGoodsDetail() 46 | 47 | //TestBatchGetGoods() 48 | 49 | //TestCreateGoods() 50 | //TestUpdateGoods() 51 | 52 | //TestUpdateBanner() 53 | 54 | TestGetCategoryBrandList() 55 | 56 | Conn.Close() 57 | 58 | } 59 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/test/category.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "mxshop_srvs/goods_srv/proto" 8 | 9 | "google.golang.org/protobuf/types/known/emptypb" 10 | ) 11 | 12 | func TestGetCategoryAllList() { 13 | c := context.Background() 14 | Rsp, err := BrandClient.GetAllCategorysList(c, &emptypb.Empty{}) 15 | if err != nil { 16 | log.Fatal("获取分类列表失败", err) 17 | } 18 | fmt.Println(Rsp.JsonData) 19 | 20 | } 21 | 22 | func TestGetSubCategory() { 23 | c := context.Background() 24 | Rsp, err := BrandClient.GetSubCategory(c, &proto.CategoryListRequest{ 25 | Id: 238014, 26 | }) 27 | if err != nil { 28 | log.Fatal("获取分类列表失败", err) 29 | } 30 | fmt.Println("当前分类", Rsp.Info) 31 | fmt.Println("子分类", Rsp.SubCategorys) 32 | 33 | } 34 | 35 | func TestCreateCategory() { 36 | c := context.Background() 37 | Rsp, err := BrandClient.CreateCategory(c, &proto.CategoryInfoRequest{ 38 | Name: "鲜制牛肉", 39 | ParentCategory: 238014, 40 | Level: 3, 41 | IsTab: true, 42 | }) 43 | if err != nil { 44 | log.Fatal("添加分类失败", err) 45 | } 46 | fmt.Println(Rsp.Id) 47 | } 48 | 49 | func TestDeleteCategory() { 50 | c := context.Background() 51 | _, err := BrandClient.DeleteCategory(c, &proto.DeleteCategoryRequest{ 52 | Id: 238015, 53 | }) 54 | if err != nil { 55 | log.Fatal("删除分类失败", err) 56 | } 57 | } 58 | 59 | func TestUpdateCategory() { 60 | c := context.Background() 61 | _, err := BrandClient.UpdateCategory(c, &proto.CategoryInfoRequest{ 62 | Id: 238014, 63 | Name: "优惠牛肉", 64 | Level: 2, 65 | IsTab: true, 66 | }) 67 | if err != nil { 68 | log.Fatal("更新分类失败", err) 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/test/category_brand.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "mxshop_srvs/goods_srv/proto" 8 | ) 9 | 10 | func TestCategoryBrandList() { 11 | c := context.Background() 12 | Rsp, err := BrandClient.CategoryBrandList(c, &proto.CategoryBrandFilterRequest{ 13 | PagePerNums: 100, 14 | Pages: 1, 15 | }) 16 | if err != nil { 17 | log.Fatal("获取品牌分类失败", err) 18 | } 19 | fmt.Println("共计", Rsp.Total) 20 | for _, value := range Rsp.Data { 21 | fmt.Println("分类", value.Category.Name) 22 | fmt.Println("品牌", value.Brand.Name) 23 | } 24 | 25 | } 26 | 27 | func TestGetCategoryBrandList() { 28 | fmt.Println("GetCategoryBrandList begin:") 29 | c := context.Background() 30 | Rsp, err := BrandClient.GetCategoryBrandList(c, &proto.CategoryInfoRequest{ 31 | Id: int32(130368), 32 | }) 33 | if err != nil { 34 | log.Fatal("获取品牌失败", err) 35 | } 36 | fmt.Println("总数:", Rsp.Total) 37 | for _, value := range Rsp.Data { 38 | fmt.Println("品牌", value.Name) 39 | } 40 | } 41 | 42 | func TestCreateCategoryBrand() { 43 | c := context.Background() 44 | BrandClient.CreateCategoryBrand(c, &proto.CategoryBrandRequest{ 45 | CategoryId: 238014, 46 | BrandId: 1113, 47 | }) 48 | } 49 | 50 | func TestDeleteCategoryBrand() { 51 | c := context.Background() 52 | BrandClient.DeleteCategoryBrand(c, &proto.CategoryBrandRequest{ 53 | Id: 25799, 54 | }) 55 | } 56 | 57 | func TestUpdateCategoryBrand() { 58 | c := context.Background() 59 | BrandClient.UpdateCategoryBrand(c, &proto.CategoryBrandRequest{ 60 | Id: 25800, 61 | CategoryId: 238014, 62 | BrandId: 1113, 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/tmp/nacos/cache/config/user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goods_srv", 3 | "host": "192.168.3.105", 4 | "port": 8080, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_goods_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "192.168.3.105", 14 | "port": 8500 15 | } 16 | } -------------------------------------------------------------------------------- /mxshop_srvs/goods_srv/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func GetFreePort() (int, error) { 8 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 9 | if err != nil { 10 | return 0, err 11 | } 12 | 13 | l, err := net.ListenTCP("tcp", addr) 14 | if err != nil { 15 | return 0, err 16 | } 17 | defer l.Close() 18 | return l.Addr().(*net.TCPAddr).Port, nil 19 | } 20 | -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/config-debug.yaml: -------------------------------------------------------------------------------- 1 | #name: 'inventory_srv' 2 | #host: '192.168.3.105' 3 | #port: 8080 4 | #mysql: 5 | # host: '127.0.0.1' 6 | # port: 3306 7 | # db: 'mxshop_inventory_srv' 8 | # user: 'root' 9 | # password: 'dfidhfidfXi' 10 | # 11 | #consul: 12 | # host: '192.168.3.105' 13 | # port: 8500 14 | # 15 | 16 | nacos: 17 | host: '10.2.69.164' 18 | port: 8848 19 | namespace_id: '3ecfe162-5701-4759-81e2-2f494e97db06' 20 | user: 'nacos' 21 | password: 'nacos' 22 | data_id: 'inventory-srv.json' 23 | group: 'dev' 24 | 25 | -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop_srvs/inventory_srv/config" 5 | 6 | "github.com/go-redsync/redsync/v4" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | var ( 11 | DB *gorm.DB 12 | ServerConfig *config.ServerConfig = &config.ServerConfig{} 13 | NacosConfig *config.NacosServer = &config.NacosServer{} 14 | Rs *redsync.Redsync 15 | ) 16 | -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/initialize/db.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "mxshop_srvs/inventory_srv/global" 7 | "os" 8 | "time" 9 | 10 | goredislib "github.com/go-redis/redis/v8" 11 | "github.com/go-redsync/redsync/v4" 12 | "github.com/go-redsync/redsync/v4/redis/goredis/v8" 13 | 14 | "gorm.io/gorm/schema" 15 | 16 | "gorm.io/driver/mysql" 17 | "gorm.io/gorm" 18 | "gorm.io/gorm/logger" 19 | ) 20 | 21 | func InitDB() { 22 | //dsn := "root:Qq/2013XiaoKUang@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local" 23 | c := global.ServerConfig.MysqlInfo 24 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", c.User, c.Password, c.Host, c.Port, c.Name) 25 | //用于输出使用的sql语句 26 | newLogger := logger.New( 27 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 28 | logger.Config{ 29 | SlowThreshold: time.Second, // 慢 SQL 阈值 30 | LogLevel: logger.Info, // 日志级别 31 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 32 | Colorful: true, // 禁用彩色打印 33 | }, 34 | ) 35 | var err error 36 | 37 | fmt.Println(dsn) 38 | //打开mysql服务中对应的数据库 39 | global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ 40 | 41 | //配置原名 42 | NamingStrategy: schema.NamingStrategy{ 43 | SingularTable: true, 44 | }, 45 | Logger: newLogger, 46 | }) 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | } 52 | 53 | func InitRs() { 54 | client := goredislib.NewClient(&goredislib.Options{ 55 | Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port), 56 | }) 57 | pool := goredis.NewPool(client) 58 | 59 | global.Rs = redsync.New(pool) 60 | } 61 | -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | //InitLogger 初始化日志 10 | func InitLogger() { 11 | //初始化日志 12 | logger, err := zap.NewDevelopment() 13 | if err != nil { 14 | log.Fatal("日志初始化失败", err.Error()) 15 | } 16 | //使用全局logger 17 | zap.ReplaceGlobals(logger) 18 | 19 | } 20 | -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/model/base.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "time" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | //GormList gorm自定义模型 12 | type GormList []string 13 | 14 | func (g GormList) Value() (driver.Value, error) { 15 | return json.Marshal(g) 16 | } 17 | 18 | func (g *GormList) Scan(value interface{}) error { 19 | return json.Unmarshal(value.([]byte), &g) 20 | } 21 | 22 | //BaseModel 公共字段 23 | type BaseModel struct { 24 | ID int32 `gorm:"primarykey;type:int" json:"id"` 25 | CreatedAt time.Time `gorm:"column:add_time" json:"-"` //column 定义别名 26 | UpdatedAt time.Time `gorm:"column:update_time" json:"-"` 27 | DeletedAt gorm.DeletedAt `json:"-"` 28 | IsDeleted bool `json:"-"` 29 | } 30 | -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/model/inventory.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | ) 7 | 8 | //Inventory 库存 9 | type Inventory struct { 10 | BaseModel 11 | Goods int32 `gorm:"type:int comment '商品id';index"` 12 | Stocks int32 `gorm:"type:int comment '商品库存'"` 13 | Version int32 `gorm:"type:int"` //分布式乐观锁,用来判断并发情况下库存是否扣减一致 14 | } 15 | 16 | //GoodsDetail 商品详细 17 | type GoodsDetail struct { 18 | Goods int32 //商品id 19 | Num int32 //商品数量 20 | } 21 | 22 | //GoodsDetailList gorm自定义模型 23 | type GoodsDetailList []GoodsDetail 24 | 25 | func (g GoodsDetailList) Value() (driver.Value, error) { 26 | return json.Marshal(g) 27 | } 28 | 29 | func (g *GoodsDetailList) Scan(value interface{}) error { 30 | return json.Unmarshal(value.([]byte), &g) 31 | } 32 | 33 | //StockSellDetail 库存扣减细节 记录扣减库存历史记录 34 | type StockSellDetail struct { 35 | OrderSn string `gorm:"type:varchar(200);index:idx_order_sn,unique"` //订单号uuid,平台自己生成的订单号 36 | Status int32 `gorm:"type:varchar(200)"` //1 表示库存已扣减 2 表示库存已归还 37 | Detail GoodsDetailList `gorm:"type:varchar(200)"` 38 | } 39 | 40 | func (StockSellDetail) TableName() string { 41 | return "stockselldetail" 42 | } 43 | -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/proto/inventory.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | service Inventory { 6 | rpc SetInv(GoodsInventoryInfo) returns (google.protobuf.Empty); //设置库存 7 | rpc InvDetail(GoodsInventoryInfo) returns (GoodsInventoryInfo); //查询库存 8 | rpc Sell(SellInfo) returns (google.protobuf.Empty); //扣减库存 9 | rpc Reback(SellInfo) returns (google.protobuf.Empty); //归还库存 10 | 11 | } 12 | 13 | //库存信息 14 | message GoodsInventoryInfo { 15 | int32 goodsId = 1; 16 | int32 num = 2; 17 | } 18 | 19 | //批量库存信息 20 | message SellInfo { 21 | repeated GoodsInventoryInfo goodsInfo = 1; 22 | string orderSn = 2; 23 | } 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/tmp/nacos/cache/config/user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inventory_srv", 3 | "host": "192.168.3.105", 4 | "port": 8080, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_inventory_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "192.168.3.105", 14 | "port": 8500 15 | } 16 | } -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/utils/redisync/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | goredislib "github.com/go-redis/redis/v8" 9 | "github.com/go-redsync/redsync/v4" 10 | "github.com/go-redsync/redsync/v4/redis/goredis/v8" 11 | ) 12 | 13 | func main() { 14 | // Create a pool with go-redis (or redigo) which is the pool redisync will 15 | // use while communicating with Redis. This can also be any pool that 16 | // implements the `redis.Pool` interface. 17 | client := goredislib.NewClient(&goredislib.Options{ 18 | Addr: "localhost:6379", 19 | }) 20 | pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...) 21 | 22 | // Create an instance of redisync to be used to obtain a mutual exclusion 23 | // lock. 24 | rs := redsync.New(pool) 25 | 26 | var wg sync.WaitGroup 27 | wg.Add(3) 28 | for i := 0; i < 3; i++ { 29 | go func() { 30 | defer wg.Done() 31 | mutexname := fmt.Sprintf("mytest_%s", i) 32 | mutex := rs.NewMutex(mutexname) 33 | if err := mutex.Lock(); err != nil { 34 | panic(err) 35 | } 36 | fmt.Printf("获取锁成功\n") 37 | 38 | time.Sleep(time.Second * 1) 39 | fmt.Printf("执行结束\n") 40 | 41 | if ok, err := mutex.Unlock(); !ok || err != nil { 42 | panic("unlock failed") 43 | } 44 | fmt.Printf("释放锁成功\n") 45 | }() 46 | } 47 | wg.Wait() 48 | } 49 | -------------------------------------------------------------------------------- /mxshop_srvs/inventory_srv/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func GetFreePort() (int, error) { 8 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 9 | if err != nil { 10 | return 0, err 11 | } 12 | 13 | l, err := net.ListenTCP("tcp", addr) 14 | if err != nil { 15 | return 0, err 16 | } 17 | defer l.Close() 18 | return l.Addr().(*net.TCPAddr).Port, nil 19 | } 20 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/config-debug.yaml: -------------------------------------------------------------------------------- 1 | #name: 'order_srv' 2 | #host: '192.168.3.105' 3 | #port: 8080 4 | #mysql: 5 | # host: '127.0.0.1' 6 | # port: 3306 7 | # db: 'mxshop_order_srv' 8 | # user: 'root' 9 | # password: 'dhfidhfid' 10 | # 11 | #consul: 12 | # host: '192.168.3.105' 13 | # port: 8500 14 | # 15 | 16 | nacos: 17 | host: '10.2.69.164' 18 | port: 8848 19 | namespace_id: '64c5ff72-e7ca-4ac8-b605-895ce39b6271' 20 | user: 'nacos' 21 | password: 'nacos' 22 | data_id: 'order-srv.json' 23 | group: 'dev' 24 | 25 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop_srvs/order_srv/config" 5 | "mxshop_srvs/order_srv/proto" 6 | 7 | "github.com/apache/rocketmq-client-go/v2" 8 | 9 | "github.com/apache/rocketmq-client-go/v2/producer" 10 | 11 | "github.com/go-redsync/redsync/v4" 12 | "gorm.io/gorm" 13 | ) 14 | 15 | var ( 16 | DB *gorm.DB 17 | ServerConfig *config.ServerConfig = &config.ServerConfig{} 18 | NacosConfig *config.NacosServer = &config.NacosServer{} 19 | GoodsSrvClient proto.GoodsClient 20 | InventorySrvClient proto.InventoryClient 21 | Rs *redsync.Redsync 22 | 23 | //tset 24 | GroupInventory producer.Option 25 | GroupOrder producer.Option 26 | MQOrder rocketmq.Producer 27 | MQInventory rocketmq.Producer 28 | ) 29 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/initialize/db.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "mxshop_srvs/order_srv/global" 7 | "os" 8 | "time" 9 | 10 | goredislib "github.com/go-redis/redis/v8" 11 | "github.com/go-redsync/redsync/v4" 12 | "github.com/go-redsync/redsync/v4/redis/goredis/v8" 13 | 14 | "gorm.io/gorm/schema" 15 | 16 | "gorm.io/driver/mysql" 17 | "gorm.io/gorm" 18 | "gorm.io/gorm/logger" 19 | ) 20 | 21 | func InitDB() { 22 | //dsn := "root:jidfhidjfdig@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local" 23 | c := global.ServerConfig.MysqlInfo 24 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", c.User, c.Password, c.Host, c.Port, c.Name) 25 | //用于输出使用的sql语句 26 | newLogger := logger.New( 27 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 28 | logger.Config{ 29 | SlowThreshold: time.Second, // 慢 SQL 阈值 30 | LogLevel: logger.Info, // 日志级别 31 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 32 | Colorful: true, // 禁用彩色打印 33 | }, 34 | ) 35 | var err error 36 | 37 | fmt.Println(dsn) 38 | //打开mysql服务中对应的数据库 39 | global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ 40 | 41 | //配置原名 42 | NamingStrategy: schema.NamingStrategy{ 43 | SingularTable: true, 44 | }, 45 | Logger: newLogger, 46 | }) 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | } 52 | 53 | func InitRs() { 54 | client := goredislib.NewClient(&goredislib.Options{ 55 | Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port), 56 | }) 57 | pool := goredis.NewPool(client) 58 | 59 | global.Rs = redsync.New(pool) 60 | } 61 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | //InitLogger 初始化日志 10 | func InitLogger() { 11 | //初始化日志 12 | logger, err := zap.NewDevelopment() 13 | if err != nil { 14 | log.Fatal("日志初始化失败", err.Error()) 15 | } 16 | //使用全局logger 17 | zap.ReplaceGlobals(logger) 18 | 19 | } 20 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/initialize/rocketMQ.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "mxshop_srvs/order_srv/global" 6 | 7 | "github.com/apache/rocketmq-client-go/v2" 8 | "github.com/apache/rocketmq-client-go/v2/producer" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | func InitMQ() { 13 | //test 14 | global.GroupInventory = producer.WithGroupName("mxshop-inventory") 15 | global.GroupOrder = producer.WithGroupName("mxshop-order") 16 | 17 | socket := fmt.Sprintf("%s:%d", global.ServerConfig.MqInfo.Host, global.ServerConfig.MqInfo.Port) 18 | fmt.Println(socket) 19 | global.MQInventory = InitMQNewProducer(global.GroupInventory, socket) 20 | global.MQOrder = InitMQNewProducer(global.GroupOrder, socket) 21 | } 22 | 23 | func InitMQNewProducer(Producer producer.Option, Socket string) rocketmq.Producer { 24 | p, err := rocketmq.NewProducer(producer.WithNameServer([]string{Socket}), 25 | Producer) 26 | if err != nil { 27 | zap.S().Errorf("初始化生产者失败%s", err) 28 | } 29 | if err = p.Start(); err != nil { 30 | zap.S().Errorf("启动生产者失败%s", err) 31 | } 32 | return p 33 | } 34 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/initialize/srv_conn.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "mxshop_srvs/order_srv/global" 6 | "mxshop_srvs/order_srv/proto" 7 | 8 | _ "github.com/mbobakov/grpc-consul-resolver" // It's important 9 | "go.uber.org/zap" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | //InitSrvConn 连接到consul注册中心并对服务做负载均衡 14 | func InitSrvConn() { 15 | //连接商品服务 16 | consul := global.ServerConfig.ConsulInfo 17 | GoodsConn, err := grpc.Dial( 18 | fmt.Sprintf("consul://%s:%d/%s?wait=14s&tag=srv", consul.Host, consul.Port, global.ServerConfig.GoodsSerInfo.Name), 19 | grpc.WithInsecure(), 20 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), 21 | ) 22 | 23 | fmt.Println(global.ServerConfig) 24 | 25 | if err != nil { 26 | zap.S().Errorw("[ InitSrvConn] 连接 【商品服务】失败", err.Error()) 27 | return 28 | } 29 | goodsClient := proto.NewGoodsClient(GoodsConn) 30 | global.GoodsSrvClient = goodsClient 31 | 32 | //连接库存服务 33 | InvConn, err := grpc.Dial( 34 | fmt.Sprintf("consul://%s:%d/%s?wait=14s&tag=srv", consul.Host, consul.Port, global.ServerConfig.InventorySerInfo.Name), 35 | grpc.WithInsecure(), 36 | grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`), 37 | ) 38 | 39 | fmt.Println(global.ServerConfig) 40 | 41 | if err != nil { 42 | zap.S().Errorw("[ InitSrvConn] 连接 【库存服务】失败", err.Error()) 43 | return 44 | } 45 | inventoryClient := proto.NewInventoryClient(InvConn) 46 | global.InventorySrvClient = inventoryClient 47 | } 48 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/model/base.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | //BaseModel 公共字段 10 | type BaseModel struct { 11 | ID int32 `gorm:"primarykey;type:int" json:"id"` 12 | CreatedAt time.Time `gorm:"column:add_time" json:"-"` //column 定义别名 13 | UpdatedAt time.Time `gorm:"column:update_time" json:"-"` 14 | DeletedAt gorm.DeletedAt `json:"-"` 15 | IsDeleted bool `json:"-"` 16 | } 17 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/model/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | 8 | "mxshop_srvs/order_srv/model" 9 | 10 | "gorm.io/driver/mysql" 11 | "gorm.io/gorm" 12 | "gorm.io/gorm/logger" 13 | "gorm.io/gorm/schema" 14 | ) 15 | 16 | //func Md5(code string) string { 17 | // //实例化一个md5的对象,将code写入其中 18 | // MD5 := md5.New() 19 | // _, _ = io.WriteString(MD5, code) 20 | // return hex.EncodeToString(MD5.Sum(nil)) 21 | //} 22 | 23 | // Paginate 将数据进行分页 24 | func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB { 25 | return func(db *gorm.DB) *gorm.DB { 26 | if page == 0 { 27 | page = 1 28 | } 29 | 30 | switch { 31 | case pageSize > 100: 32 | pageSize = 100 33 | case pageSize <= 0: 34 | pageSize = 10 35 | } 36 | 37 | offset := (page - 1) * pageSize 38 | return db.Offset(offset).Limit(pageSize) 39 | } 40 | } 41 | 42 | func main() { 43 | dsn := "root:ifjidfhidfng@tcp(127.0.0.1:3306)/mxshop_order_srv?charset=utf8mb4&parseTime=True&loc=Local" 44 | 45 | //用于输出使用的sql语句 46 | newLogger := logger.New( 47 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 48 | logger.Config{ 49 | SlowThreshold: time.Second, // 慢 SQL 阈值 50 | LogLevel: logger.Info, // 日志级别 51 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 52 | Colorful: true, // 禁用彩色打印 53 | }, 54 | ) 55 | 56 | //打开mysql服务中对应的数据库 57 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ 58 | //以自定义名称表名写入数据库 59 | NamingStrategy: schema.NamingStrategy{ 60 | SingularTable: true, 61 | }, 62 | Logger: newLogger, 63 | }) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | db.AutoMigrate(&model.ShoppingCart{}) 69 | 70 | } 71 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/model/order.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type ShoppingCart struct { 6 | BaseModel 7 | User int32 `gorm:"type:int;index"` //用户索引,用来快速当前用户的购物车记录 8 | Goods int32 `gorm:"type:int;index"` //加索引:需要查询的时候 9 | Nums int32 `gorm:"type:int"` //商品数量 10 | Checked bool //是否选中 11 | } 12 | 13 | //TableName 表名 14 | func (ShoppingCart) TableName() string { 15 | return "shoppingcart" 16 | } 17 | 18 | //OrderInfo 订单信息 19 | type OrderInfo struct { 20 | BaseModel 21 | 22 | User int32 `gorm:"type:int;index"` 23 | OrderSn string `gorm:"type:varchar(30);index"` //订单号,平台自己生成的订单号 24 | PayType string `gorm:"type:varchar(20) comment 'alipay(支付宝), wechat(微信)'"` 25 | 26 | //status大家可以考虑使用iota来做 27 | Status string `gorm:"type:varchar(20) comment 'PAYING(待支付), TRADE_SUCCESS(成功), TRADE_CLOSED(超时关闭), WAIT_BUYER_PAY(交易创建), TRADE_FINISHED(交易结束)'"` 28 | TradeNo string `gorm:"type:varchar(100) comment '交易号'"` //交易号,其实就是支付宝或者微信的的订单号,用于查账 29 | OrderMount float32 //订单金额 30 | PayTime *time.Time `gorm:"type:datetime"` 31 | 32 | Address string `gorm:"type:varchar(100)"` 33 | SignerName string `gorm:"type:varchar(20)"` 34 | SingerMobile string `gorm:"type:varchar(11)"` 35 | Post string `gorm:"type:varchar(20)"` //留言信息 36 | } 37 | 38 | func (OrderInfo) TableName() string { 39 | return "orderinfo" 40 | } 41 | 42 | //OrderGoods 订单商品信息 43 | type OrderGoods struct { 44 | BaseModel 45 | 46 | Order int32 `gorm:"type:int;index"` 47 | Goods int32 `gorm:"type:int;index"` 48 | 49 | //把商品信息保存下来,但是字段冗余,不符合mysql三范式,但是高并发系统一般都不遵循三范式 50 | GoodsName string `gorm:"type:varchar(100);index"` 51 | GoodsImage string `gorm:"type:varchar(200)"` 52 | GoodsPrice float32 53 | Nums int32 `gorm:"type:int"` 54 | } 55 | 56 | func (OrderGoods) TableName() string { 57 | return "ordergoods" 58 | } 59 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/proto/inventory.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | service Inventory { 6 | rpc SetInv(GoodsInventoryInfo) returns (google.protobuf.Empty); //设置库存 7 | rpc InvDetail(GoodsInventoryInfo) returns (GoodsInventoryInfo); //查询库存 8 | rpc Sell(SellInfo) returns (google.protobuf.Empty); //扣减库存 9 | rpc Reback(SellInfo) returns (google.protobuf.Empty); //归还库存 10 | 11 | } 12 | 13 | //库存信息 14 | message GoodsInventoryInfo { 15 | int32 goodsId = 1; 16 | int32 num = 2; 17 | } 18 | 19 | //批量库存信息 20 | message SellInfo { 21 | repeated GoodsInventoryInfo goodsInfo = 1; 22 | string orderSn = 2; 23 | } 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/tmp/nacos/cache/config/user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order_srv", 3 | "host": "192.168.3.105", 4 | "port": 8080, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_order_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "192.168.3.105", 14 | "port": 8500 15 | } 16 | } -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/utils/otgrpc/README.md: -------------------------------------------------------------------------------- 1 | # OpenTracing support for gRPC in Go 2 | 3 | The `otgrpc` package makes it easy to add OpenTracing support to gRPC-based 4 | systems in Go. 5 | 6 | ## Installation 7 | 8 | ``` 9 | go get github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc 10 | ``` 11 | 12 | ## Documentation 13 | 14 | See the basic usage examples below and the [package documentation on 15 | godoc.org](https://godoc.org/github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc). 16 | 17 | ## Client-side usage example 18 | 19 | Wherever you call `grpc.Dial`: 20 | 21 | ```go 22 | // You must have some sort of OpenTracing Tracer instance on hand. 23 | var tracer opentracing.Tracer = ... 24 | ... 25 | 26 | // Set up a connection to the server peer. 27 | conn, err := grpc.Dial( 28 | address, 29 | ... // other options 30 | grpc.WithUnaryInterceptor( 31 | otgrpc.OpenTracingClientInterceptor(tracer)), 32 | grpc.WithStreamInterceptor( 33 | otgrpc.OpenTracingStreamClientInterceptor(tracer))) 34 | 35 | // All future RPC activity involving `conn` will be automatically traced. 36 | ``` 37 | 38 | ## Server-side usage example 39 | 40 | Wherever you call `grpc.NewServer`: 41 | 42 | ```go 43 | // You must have some sort of OpenTracing Tracer instance on hand. 44 | var tracer opentracing.Tracer = ... 45 | ... 46 | 47 | // Initialize the gRPC server. 48 | s := grpc.NewServer( 49 | ... // other options 50 | grpc.UnaryInterceptor( 51 | otgrpc.OpenTracingServerInterceptor(tracer)), 52 | grpc.StreamInterceptor( 53 | otgrpc.OpenTracingStreamServerInterceptor(tracer))) 54 | 55 | // All future RPC activity involving `s` will be automatically traced. 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/utils/otgrpc/errors_test.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/opentracing/opentracing-go/mocktracer" 9 | "google.golang.org/grpc/codes" 10 | "google.golang.org/grpc/status" 11 | ) 12 | 13 | const ( 14 | firstCode = codes.OK 15 | lastCode = codes.DataLoss 16 | ) 17 | 18 | func TestSpanTags(t *testing.T) { 19 | tracer := mocktracer.New() 20 | for code := firstCode; code <= lastCode; code++ { 21 | // Client error 22 | tracer.Reset() 23 | span := tracer.StartSpan("test-trace-client") 24 | err := status.Error(code, "") 25 | SetSpanTags(span, err, true) 26 | span.Finish() 27 | 28 | // Assert added tags 29 | rawSpan := tracer.FinishedSpans()[0] 30 | expectedTags := map[string]interface{}{ 31 | "response_code": code, 32 | "response_class": ErrorClass(err), 33 | } 34 | if err != nil { 35 | expectedTags["error"] = true 36 | } 37 | assert.Equal(t, expectedTags, rawSpan.Tags()) 38 | 39 | // Server error 40 | tracer.Reset() 41 | span = tracer.StartSpan("test-trace-server") 42 | err = status.Error(code, "") 43 | SetSpanTags(span, err, false) 44 | span.Finish() 45 | 46 | // Assert added tags 47 | rawSpan = tracer.FinishedSpans()[0] 48 | expectedTags = map[string]interface{}{ 49 | "response_code": code, 50 | "response_class": ErrorClass(err), 51 | } 52 | if err != nil && ErrorClass(err) == ServerError { 53 | expectedTags["error"] = true 54 | } 55 | assert.Equal(t, expectedTags, rawSpan.Tags()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/utils/otgrpc/package.go: -------------------------------------------------------------------------------- 1 | // Package otgrpc provides OpenTracing support for any gRPC client or server. 2 | // 3 | // See the README for simple usage examples: 4 | // https://github.com/grpc-ecosystem/grpc-opentracing/blob/master/go/otgrpc/README.md 5 | package otgrpc 6 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/utils/otgrpc/shared.go: -------------------------------------------------------------------------------- 1 | package otgrpc 2 | 3 | import ( 4 | "strings" 5 | 6 | opentracing "github.com/opentracing/opentracing-go" 7 | "github.com/opentracing/opentracing-go/ext" 8 | "google.golang.org/grpc/metadata" 9 | ) 10 | 11 | var ( 12 | // Morally a const: 13 | gRPCComponentTag = opentracing.Tag{string(ext.Component), "gRPC"} 14 | ) 15 | 16 | // metadataReaderWriter satisfies both the opentracing.TextMapReader and 17 | // opentracing.TextMapWriter interfaces. 18 | type metadataReaderWriter struct { 19 | metadata.MD 20 | } 21 | 22 | func (w metadataReaderWriter) Set(key, val string) { 23 | // The GRPC HPACK implementation rejects any uppercase keys here. 24 | // 25 | // As such, since the HTTP_HEADERS format is case-insensitive anyway, we 26 | // blindly lowercase the key (which is guaranteed to work in the 27 | // Inject/Extract sense per the OpenTracing spec). 28 | key = strings.ToLower(key) 29 | w.MD[key] = append(w.MD[key], val) 30 | } 31 | 32 | func (w metadataReaderWriter) ForeachKey(handler func(key, val string) error) error { 33 | for k, vals := range w.MD { 34 | for _, v := range vals { 35 | if err := handler(k, v); err != nil { 36 | return err 37 | } 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/utils/otgrpc/test/otgrpc_testing/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package otgrpc.testing; 4 | 5 | message SimpleRequest { 6 | int32 payload = 1; 7 | } 8 | 9 | message SimpleResponse { 10 | int32 payload = 1; 11 | } 12 | 13 | service TestService { 14 | rpc UnaryCall(SimpleRequest) returns (SimpleResponse); 15 | 16 | rpc StreamingOutputCall(SimpleRequest) returns (stream SimpleResponse); 17 | 18 | rpc StreamingInputCall(stream SimpleRequest) returns (SimpleResponse); 19 | 20 | rpc StreamingBidirectionalCall(stream SimpleRequest) returns (stream SimpleResponse); 21 | } 22 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/utils/redisync/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | goredislib "github.com/go-redis/redis/v8" 9 | "github.com/go-redsync/redsync/v4" 10 | "github.com/go-redsync/redsync/v4/redis/goredis/v8" 11 | ) 12 | 13 | func main() { 14 | // Create a pool with go-redis (or redigo) which is the pool redisync will 15 | // use while communicating with Redis. This can also be any pool that 16 | // implements the `redis.Pool` interface. 17 | client := goredislib.NewClient(&goredislib.Options{ 18 | Addr: "localhost:6379", 19 | }) 20 | pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...) 21 | 22 | // Create an instance of redisync to be used to obtain a mutual exclusion 23 | // lock. 24 | rs := redsync.New(pool) 25 | 26 | var wg sync.WaitGroup 27 | wg.Add(3) 28 | for i := 0; i < 3; i++ { 29 | go func() { 30 | defer wg.Done() 31 | mutexname := fmt.Sprintf("mytest_%s", i) 32 | mutex := rs.NewMutex(mutexname) 33 | if err := mutex.Lock(); err != nil { 34 | panic(err) 35 | } 36 | fmt.Printf("获取锁成功\n") 37 | 38 | time.Sleep(time.Second * 1) 39 | fmt.Printf("执行结束\n") 40 | 41 | if ok, err := mutex.Unlock(); !ok || err != nil { 42 | panic("unlock failed") 43 | } 44 | fmt.Printf("释放锁成功\n") 45 | }() 46 | } 47 | wg.Wait() 48 | } 49 | -------------------------------------------------------------------------------- /mxshop_srvs/order_srv/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func GetFreePort() (int, error) { 8 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 9 | if err != nil { 10 | return 0, err 11 | } 12 | 13 | l, err := net.ListenTCP("tcp", addr) 14 | if err != nil { 15 | return 0, err 16 | } 17 | defer l.Close() 18 | return l.Addr().(*net.TCPAddr).Port, nil 19 | } 20 | -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/goods-srv.json@@dev@@1667c9ca-e13b-46b8-aa9f-a376de301f6f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goods_srv", 3 | "host": "10.2.94.231", 4 | "port": 8081, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_goods_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "10.2.94.231", 14 | "port": 8500 15 | } 16 | } -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/goods-srv.json@@dev@@fe432146-9559-44d1-8245-3541feacc9cf: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goods-srv", 3 | "host": "10.2.69.164", 4 | "port": 8082, 5 | "mysql":{ 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_goods_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul":{ 13 | "host": "10.2.69.164", 14 | "port": 8500 15 | }, 16 | "es":{ 17 | "host": "127.0.0.1", 18 | "port": 9200 19 | }, 20 | "tags":["iceymoss", "goods", "golang", "srv"] 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/inventory-srv.json@@dev@@3ecfe162-5701-4759-81e2-2f494e97db06: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inventory-srv", 3 | "host": "10.2.69.164", 4 | "port": 8084, 5 | "mysql":{ 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_inventory_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul":{ 13 | "host": "10.2.69.164", 14 | "port": 8500 15 | }, 16 | "redis":{ 17 | "host": "127.0.0.1", 18 | "port": 6379 19 | }, 20 | "tags":["iceymoss", "inventory", "golang", "srv"], 21 | "rocket":{ 22 | "host": "1.14.180.202", 23 | "port": 9876, 24 | "group_name": "mxshop-inventory", 25 | "topic":"order-reback" 26 | } 27 | } -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/inventory.json@@dev@@47800a50-0d82-4a47-a961-28e5a6b6c4e0: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inventory_srv", 3 | "host": "10.2.94.231", 4 | "port": 8081, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_inventory_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "10.2.94.231", 14 | "port": 8500 15 | }, 16 | "redis":{ 17 | "host":"10.2.94.231", 18 | "port":6379 19 | } 20 | } -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/order-srv.json@@dev@@64c5ff72-e7ca-4ac8-b605-895ce39b6271: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order-srv", 3 | "host": "10.2.69.164", 4 | "port": 8082, 5 | "mysql":{ 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_order_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul":{ 13 | "host": "10.2.69.164", 14 | "port": 8500 15 | }, 16 | "redis":{ 17 | "host": "127.0.0.1", 18 | "port": 6379 19 | }, 20 | "tags":["iceymoss", "order", "golang", "srv"], 21 | "goods_srv":{ 22 | "name": "goods-srv" 23 | }, 24 | "inventory_srv":{ 25 | "name": "inventory-srv" 26 | }, 27 | "rocket":{ 28 | "host": "1.14.180.202", 29 | "port": 9876, 30 | "group_name": "mxshop-order", 31 | "topic":"order_timeout" 32 | }, 33 | "tracing":{ 34 | "host": "127.0.0.1", 35 | "port": 6831, 36 | "name": "mxshop" 37 | } 38 | } -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/order_srv.json@@dev@@f9fc4b72-18a3-4335-94fb-f612c133ff59: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order_srv", 3 | "host": "10.2.94.231", 4 | "port": 8083, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_order_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "10.2.94.231", 14 | "port": 8500 15 | }, 16 | "redis":{ 17 | "host":"10.2.94.231", 18 | "port":6379 19 | }, 20 | "goods_srv":{ 21 | "name":"goods_srv" 22 | }, 23 | "inventory_srv":{ 24 | "name":"inventory_srv" 25 | } 26 | } -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user_srv", 3 | "host": "10.2.111.34", 4 | "port": 8080, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_user_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "10.2.111.34", 14 | "port": 8500 15 | }, 16 | "tags":["goods, ice_moss, server"] 17 | } -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/user-srv.json@@dev@@7ae18f62-e2b9-48bd-bff2-a49e7443f5bc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-srv", 3 | "host": "10.2.106.169", 4 | "port": 8081, 5 | "consul":{ 6 | "host": "10.2.106.169", 7 | "port": 8500 8 | }, 9 | "mysql":{ 10 | "host": "127.0.0.1", 11 | "port": 3306, 12 | "db": "mxshop_user_srv", 13 | "user": "root", 14 | "password": "Qq/2013XiaoKUang" 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/userop-srv.json@@dev@@a66fc619-7970-4bdf-b703-9659ed4b2e29: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userop_srv", 3 | "host": "10.2.94.231", 4 | "port": 8084, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_userop_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "10.2.94.231", 14 | "port": 8500 15 | }, 16 | "redis":{ 17 | "host":"10.2.94.231", 18 | "port":6379 19 | } 20 | } -------------------------------------------------------------------------------- /mxshop_srvs/tmp/nacos/cache/config/userop-srv.json@@dev@@c52838c8-880e-49ac-8478-d4c94e0ed154: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userop-srv", 3 | "host": "10.2.105.13", 4 | "port": 8085, 5 | "mysql":{ 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_userop_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul":{ 13 | "host": "10.2.105.13", 14 | "port": 8500 15 | }, 16 | "redis":{ 17 | "host": "127.0.0.1", 18 | "port": 6379 19 | }, 20 | "tags":["iceymoss", "userop", "golang", "srv"] 21 | } -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/config-debug.yaml: -------------------------------------------------------------------------------- 1 | #name: 'user_srv' 2 | #host: '192.168.3.105' 3 | #port: 8080 4 | #mysql: 5 | # host: '127.0.0.1' 6 | # port: 3306 7 | # db: 'mxshop_user_srv' 8 | # user: 'root' 9 | # password: 'hidfhdifhg' 10 | # 11 | #consul: 12 | # host: '192.168.3.105' 13 | # port: 8500 14 | # 15 | 16 | nacos: 17 | host: '10.2.106.169' 18 | port: 8848 19 | namespace_id: '7ae18f62-e2b9-48bd-bff2-a49e7443f5bc' 20 | user: 'nacos' 21 | password: 'nacos' 22 | data_id: 'user-srv.json' 23 | group: 'dev' 24 | 25 | -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | //MysqlConfig mysql信息配置 4 | type MysqlConfig struct { 5 | Host string `mapstructure:"host" json:"host"` 6 | Port int `mapstructure:"port" json:"port"` 7 | Name string `mapstructure:"db" json:"db"` 8 | User string `mapstructure:"user" json:"user"` 9 | Password string `mapstructure:"password" json:"password"` 10 | } 11 | 12 | //ConsulConfig consul配置 13 | type ConsulConfig struct { 14 | Host string `mapstructure:"host" json:"host"` 15 | Port int `mapstructure:"port" json:"port"` 16 | } 17 | 18 | //ServerConfig 服务配置 19 | type ServerConfig struct { 20 | Name string `mapstructure:"name" json:"name"` 21 | Host string `mapstructure:"host" json:"host"` 22 | Port int `mapstructure:"port" json:"port"` 23 | MysqlInfo MysqlConfig `mapstructure:"mysql" json:"mysql"` 24 | ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"` 25 | } 26 | 27 | //NacosConfig 配置中心配置 28 | type NacosConfig struct { 29 | Host string `mapstructure:"host" json:"host"` 30 | Port uint64 `mapstructure:"port" json:"port"` 31 | NamespaceId string `mapstructure:"namespace_id" json:"namespace_id"` 32 | User string `mapstructure:"user" json:"user"` 33 | Password string `mapstructure:"password" json:"password"` 34 | DataId string `mapstructure:"data_id" json:"data_id"` 35 | Group string `mapstructure:"group" json:"group"` 36 | } 37 | 38 | //NacosServer 配置中心 39 | type NacosServer struct { 40 | NacosInfo NacosConfig `mapstructure:"nacos" json:"nacos"` 41 | } 42 | -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop_srvs/user_srv/config" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | var ( 10 | DB *gorm.DB 11 | ServerConfig *config.ServerConfig = &config.ServerConfig{} 12 | NacosConfig *config.NacosServer = &config.NacosServer{} 13 | ) 14 | -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/initialize/db.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "mxshop_srvs/user_srv/global" 10 | 11 | "gorm.io/driver/mysql" 12 | "gorm.io/gorm" 13 | "gorm.io/gorm/logger" 14 | "gorm.io/gorm/schema" 15 | ) 16 | 17 | func InitDB() { 18 | //dsn := "root:Qq/2013XiaoKUang@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local" 19 | c := global.ServerConfig.MysqlInfo 20 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", c.User, c.Password, c.Host, c.Port, c.Name) 21 | //用于输出使用的sql语句 22 | newLogger := logger.New( 23 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 24 | logger.Config{ 25 | SlowThreshold: time.Second, // 慢 SQL 阈值 26 | LogLevel: logger.Info, // 日志级别 27 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 28 | Colorful: true, // 禁用彩色打印 29 | }, 30 | ) 31 | var err error 32 | 33 | fmt.Println(dsn) 34 | //打开mysql服务中对应的数据库 35 | global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ 36 | //配置原名 37 | NamingStrategy: schema.NamingStrategy{ 38 | SingularTable: true, 39 | }, 40 | Logger: newLogger, 41 | }) 42 | if err != nil { 43 | panic(err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | //InitLogger 初始化日志 10 | func InitLogger() { 11 | //初始化日志 12 | logger, err := zap.NewDevelopment() 13 | if err != nil { 14 | log.Fatal("日志初始化失败", err.Error()) 15 | } 16 | //使用全局logger 17 | zap.ReplaceGlobals(logger) 18 | } 19 | -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | //BaseModel 公共字段 10 | type BaseModel struct { 11 | ID int32 `gorm:"primarykey"` 12 | CreatedAt time.Time `gorm:"column:add_time"` //column 定义别名 13 | UpdatedAt time.Time `gorm:"column:update_time"` 14 | DeletedAt gorm.DeletedAt 15 | IsDeleted bool 16 | } 17 | 18 | //User 定义用户信息 19 | type User struct { 20 | BaseModel 21 | Mobile string `gorm:"idx_mobile;unique;type:varchar(11) comment 'idx_mobile表示索引用于快速查找';not null"` 22 | PassWord string `gorm:"type:varchar(100) comment '加密后的密码';not null"` 23 | NickName string `gorm:"type: varchar(20) comment '昵称' "` 24 | Birthday *time.Time `gorm:"type:datetime"` 25 | Gender string `gorm:"column:gender;default:male;type:varchar(6) comment 'male表示男, famale表示女'"` 26 | Role int `gorm:"column:role;default:1;type:int comment '1表示用户, 2表示管理员'"` 27 | } 28 | -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/proto/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | //页面信息 6 | message PageInfo { 7 | uint32 pn = 1; 8 | uint32 psize = 2; 9 | } 10 | 11 | //用户信息 12 | message UserInfoResponse { 13 | int32 id = 1; 14 | string password = 2; 15 | string mobile = 3; 16 | string nickName = 4; 17 | uint64 birthday = 5; 18 | string gender = 6; 19 | int32 role = 7; 20 | } 21 | 22 | //用户信息列表 23 | message UserListResponse { 24 | int32 total = 1; 25 | repeated UserInfoResponse data = 2; 26 | } 27 | 28 | //手机号 29 | message MobileRequest { 30 | string mobile = 1; 31 | } 32 | 33 | //id 34 | message IdRequest { 35 | int32 id = 1; 36 | } 37 | 38 | //创建用户 39 | message CreateUserInfo { 40 | string nickName = 1; 41 | string password = 2; 42 | string mobile = 3; 43 | } 44 | 45 | //更新用户信息 46 | message UpdateUserInfo { 47 | int32 id = 1; 48 | string nickName = 2; 49 | string gender = 3; 50 | uint64 birthday = 4; 51 | } 52 | 53 | //用户密码 54 | message PasswordCheckInfo { 55 | string password = 1; 56 | string encryptedPassword = 2; 57 | } 58 | 59 | //核实密码响应 60 | message CheckResponse { 61 | bool success = 1; 62 | } 63 | 64 | service User{ 65 | rpc GetUserInfoList(PageInfo) returns (UserListResponse); //用户列表 66 | rpc GetUserByMobile(MobileRequest) returns (UserInfoResponse); //通过mobile查询用户 67 | rpc GetUserById(IdRequest) returns (UserInfoResponse); //通过id查询用户 68 | rpc CreateUser(CreateUserInfo) returns (UserInfoResponse); //创建用户 69 | rpc UpdateUser(UpdateUserInfo) returns (google.protobuf.Empty); //更新用户 70 | rpc CheckPassWord(PasswordCheckInfo) returns (CheckResponse); //检查密码 71 | } 72 | 73 | -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/tmp/nacos/cache/config/user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user_srv", 3 | "host": "192.168.3.107", 4 | "port": 8080, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_user_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "192.168.3.107", 14 | "port": 8500 15 | }, 16 | "tags":["goods, ice_moss, server"] 17 | } -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/tmp/nacos/log/nacos-sdk.log: -------------------------------------------------------------------------------- 1 | 2022-07-14T20:00:51.896+0800 ERROR nacos_server/nacos_server.go:194 api,method:, params:<{"dataId":"user-srv.json","group":"dev","tenant":"555d81b1-6cbf-4b69-b48a-a034c282c69f"}>, call domain error: , result:<> 2 | 2022-07-14T20:00:51.897+0800 ERROR nacos_server/nacos_server.go:194 api,method:, params:<{"dataId":"user-srv.json","group":"dev","tenant":"555d81b1-6cbf-4b69-b48a-a034c282c69f"}>, call domain error: , result:<> 3 | 2022-07-14T20:00:51.898+0800 ERROR nacos_server/nacos_server.go:194 api,method:, params:<{"dataId":"user-srv.json","group":"dev","tenant":"555d81b1-6cbf-4b69-b48a-a034c282c69f"}>, call domain error: , result:<> 4 | 2022-07-14T20:00:51.899+0800 ERROR config_client/config_client.go:191 get config from server error:Get "http://192.168.3.105:8848/nacos/v1/cs/configs?dataId=user-srv.json&group=dev&tenant=555d81b1-6cbf-4b69-b48a-a034c282c69f": dial tcp 192.168.3.105:8848: connect: host is down 5 | -------------------------------------------------------------------------------- /mxshop_srvs/user_srv/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | //GetFreePort 生成可用端口号 8 | func GetFreePort() (int, error) { 9 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 10 | if err != nil { 11 | return 0, err 12 | } 13 | 14 | l, err := net.ListenTCP("tcp", addr) 15 | if err != nil { 16 | return 0, err 17 | } 18 | defer l.Close() 19 | return l.Addr().(*net.TCPAddr).Port, nil 20 | } 21 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/config-debug.yaml: -------------------------------------------------------------------------------- 1 | #name: 'userop_srv' 2 | #host: '192.168.3.105' 3 | #port: 8080 4 | #mysql: 5 | # host: '127.0.0.1' 6 | # port: 3306 7 | # db: 'mxshop_userop_srv' 8 | # user: 'root' 9 | # password: 'hdifhdifhdidg' 10 | # 11 | #consul: 12 | # host: '192.168.3.105' 13 | # port: 8500 14 | # 15 | 16 | nacos: 17 | host: '10.2.105.13' 18 | port: 8848 19 | namespace_id: 'c52838c8-880e-49ac-8478-d4c94e0ed154' 20 | user: 'nacos' 21 | password: 'nacos' 22 | data_id: 'userop-srv.json' 23 | group: 'dev' 24 | 25 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | //MysqlConfig mysql信息配置 4 | type MysqlConfig struct { 5 | Host string `mapstructure:"host" json:"host"` 6 | Port int `mapstructure:"port" json:"port"` 7 | Name string `mapstructure:"db" json:"db"` 8 | User string `mapstructure:"user" json:"user"` 9 | Password string `mapstructure:"password" json:"password"` 10 | } 11 | 12 | //ConsulConfig consul配置 13 | type ConsulConfig struct { 14 | Host string `mapstructure:"host" json:"host"` 15 | Port int `mapstructure:"port" json:"port"` 16 | } 17 | 18 | type RedisConfig struct { 19 | Host string `mapstructure:"host" json:"host"` 20 | Port int `mapstructure:"port" json:"port"` 21 | } 22 | 23 | //ServerConfig 服务配置 24 | type ServerConfig struct { 25 | Name string `mapstructure:"name" json:"name"` 26 | Host string `mapstructure:"host" json:"host"` 27 | Port int `mapstructure:"port" json:"port"` 28 | MysqlInfo MysqlConfig `mapstructure:"mysql" json:"mysql"` 29 | ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"` 30 | RedisInfo RedisConfig `mapstructure:"redis" json:"redis"` 31 | Tags []string `mapstructure:"tags" json:"tags"` 32 | } 33 | 34 | //NacosConfig 配置中心配置 35 | type NacosConfig struct { 36 | Host string `mapstructure:"host" json:"host"` 37 | Port uint64 `mapstructure:"port" json:"port"` 38 | NamespaceId string `mapstructure:"namespace_id" json:"namespace_id"` 39 | User string `mapstructure:"user" json:"user"` 40 | Password string `mapstructure:"password" json:"password"` 41 | DataId string `mapstructure:"data_id" json:"data_id"` 42 | Group string `mapstructure:"group" json:"group"` 43 | } 44 | 45 | //NacosServer 配置中心 46 | type NacosServer struct { 47 | NacosInfo NacosConfig `mapstructure:"nacos" json:"nacos"` 48 | } 49 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "mxshop_srvs/userop_srv/config" 5 | 6 | "github.com/go-redsync/redsync/v4" 7 | 8 | "gorm.io/gorm" 9 | ) 10 | 11 | var ( 12 | DB *gorm.DB 13 | ServerConfig *config.ServerConfig = &config.ServerConfig{} 14 | NacosConfig *config.NacosServer = &config.NacosServer{} 15 | Rs *redsync.Redsync 16 | ) 17 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/handler/base.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import "mxshop_srvs/userop_srv/proto" 4 | 5 | type UserOpServer struct { 6 | proto.UnimplementedAddressServer 7 | proto.UnimplementedUserFavServer 8 | proto.UnimplementedMessageServer 9 | } 10 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/handler/message.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "context" 5 | 6 | "mxshop_srvs/userop_srv/global" 7 | "mxshop_srvs/userop_srv/model" 8 | "mxshop_srvs/userop_srv/proto" 9 | ) 10 | 11 | //GetMessageList 获取留言 12 | func (u *UserOpServer) GetMessageList(ctx context.Context, req *proto.MessageRequest) (*proto.MessageListResponse, error) { 13 | var rsp proto.MessageListResponse 14 | var messages []model.LeavingMessages 15 | var messageList []*proto.MessageResponse 16 | 17 | result := global.DB.Where(&model.LeavingMessages{User: req.UserId}).Find(&messages) 18 | rsp.Total = int32(result.RowsAffected) 19 | 20 | for _, message := range messages { 21 | messageList = append(messageList, &proto.MessageResponse{ 22 | Id: message.ID, 23 | UserId: message.User, 24 | MessageType: message.MessageType, 25 | Subject: message.Subject, 26 | Message: message.Message, 27 | File: message.File, 28 | }) 29 | } 30 | 31 | rsp.Data = messageList 32 | return &rsp, nil 33 | } 34 | 35 | //CreateMessage 新建留言 36 | func (u *UserOpServer) CreateMessage(ctx context.Context, req *proto.MessageRequest) (*proto.MessageResponse, error) { 37 | var message model.LeavingMessages 38 | 39 | message.User = req.UserId 40 | message.MessageType = req.MessageType 41 | message.Subject = req.Subject 42 | message.Message = req.Message 43 | message.File = req.File 44 | 45 | global.DB.Save(&message) 46 | 47 | return &proto.MessageResponse{Id: message.ID}, nil 48 | } 49 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/initialize/db.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "mxshop_srvs/userop_srv/global" 7 | "os" 8 | "time" 9 | 10 | goredislib "github.com/go-redis/redis/v8" 11 | "github.com/go-redsync/redsync/v4" 12 | "github.com/go-redsync/redsync/v4/redis/goredis/v8" 13 | 14 | "gorm.io/gorm/schema" 15 | 16 | "gorm.io/driver/mysql" 17 | "gorm.io/gorm" 18 | "gorm.io/gorm/logger" 19 | ) 20 | 21 | func InitDB() { 22 | //dsn := "root:Qq/2013XiaoKUang@tcp(127.0.0.1:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local" 23 | c := global.ServerConfig.MysqlInfo 24 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", c.User, c.Password, c.Host, c.Port, c.Name) 25 | //用于输出使用的sql语句 26 | newLogger := logger.New( 27 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 28 | logger.Config{ 29 | SlowThreshold: time.Second, // 慢 SQL 阈值 30 | LogLevel: logger.Info, // 日志级别 31 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 32 | Colorful: true, // 禁用彩色打印 33 | }, 34 | ) 35 | var err error 36 | 37 | fmt.Println(dsn) 38 | //打开mysql服务中对应的数据库 39 | global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ 40 | 41 | //配置原名 42 | NamingStrategy: schema.NamingStrategy{ 43 | SingularTable: true, 44 | }, 45 | Logger: newLogger, 46 | }) 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | } 52 | 53 | func InitRs() { 54 | client := goredislib.NewClient(&goredislib.Options{ 55 | Addr: fmt.Sprintf("%s:%d", global.ServerConfig.RedisInfo.Host, global.ServerConfig.RedisInfo.Port), 56 | }) 57 | pool := goredis.NewPool(client) 58 | 59 | global.Rs = redsync.New(pool) 60 | } 61 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/initialize/logger.go: -------------------------------------------------------------------------------- 1 | package initialize 2 | 3 | import ( 4 | "log" 5 | 6 | "go.uber.org/zap" 7 | ) 8 | 9 | //InitLogger 初始化日志 10 | func InitLogger() { 11 | //初始化日志 12 | logger, err := zap.NewDevelopment() 13 | if err != nil { 14 | log.Fatal("日志初始化失败", err.Error()) 15 | } 16 | //使用全局logger 17 | zap.ReplaceGlobals(logger) 18 | } 19 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/model/base.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "gorm.io/gorm" 7 | ) 8 | 9 | //BaseModel 公共字段 10 | type BaseModel struct { 11 | ID int32 `gorm:"primarykey;type:int" json:"id"` 12 | CreatedAt time.Time `gorm:"column:add_time" json:"-"` //column 定义别名 13 | UpdatedAt time.Time `gorm:"column:update_time" json:"-"` 14 | DeletedAt gorm.DeletedAt `json:"-"` 15 | IsDeleted bool `json:"-"` 16 | } 17 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/model/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | 8 | "mxshop_srvs/userop_srv/model" 9 | 10 | "gorm.io/driver/mysql" 11 | "gorm.io/gorm" 12 | "gorm.io/gorm/logger" 13 | "gorm.io/gorm/schema" 14 | ) 15 | 16 | // Paginate 将数据进行分页 17 | func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB { 18 | return func(db *gorm.DB) *gorm.DB { 19 | if page == 0 { 20 | page = 1 21 | } 22 | 23 | switch { 24 | case pageSize > 100: 25 | pageSize = 100 26 | case pageSize <= 0: 27 | pageSize = 10 28 | } 29 | 30 | offset := (page - 1) * pageSize 31 | return db.Offset(offset).Limit(pageSize) 32 | } 33 | } 34 | 35 | func main() { 36 | dsn := "root:hihfidhfid@tcp(127.0.0.1:3306)/mxshop_userop_srv?charset=utf8mb4&parseTime=True&loc=Local" 37 | 38 | //用于输出使用的sql语句 39 | newLogger := logger.New( 40 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注) 41 | logger.Config{ 42 | SlowThreshold: time.Second, // 慢 SQL 阈值 43 | LogLevel: logger.Info, // 日志级别 44 | IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 45 | Colorful: true, // 禁用彩色打印 46 | }, 47 | ) 48 | 49 | //打开mysql服务中对应的数据库 50 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ 51 | //以自定义名称表名写入数据库 52 | NamingStrategy: schema.NamingStrategy{ 53 | SingularTable: true, 54 | }, 55 | Logger: newLogger, 56 | }) 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | db.AutoMigrate(&model.LeavingMessages{}, &model.Address{}, &model.UserFav{}) 62 | 63 | } 64 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/model/userop.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | //LeavingMessages 用户留言 4 | type LeavingMessages struct { 5 | BaseModel 6 | User int32 `gorm:"type:int;index"` 7 | MessageType int32 `gorm:"type:int comment '留言类型: 1(留言),2(投诉),3(询问),4(售后),5(求购)'"` 8 | Subject string `gorm:"type:varchar(100)"` 9 | 10 | Message string 11 | File string `gorm:"type:varchar(200)"` //上传图片url 12 | } 13 | 14 | func (LeavingMessages) TableName() string { 15 | return "leavingmessages" 16 | } 17 | 18 | //Address 收货信息 19 | type Address struct { 20 | BaseModel 21 | 22 | User int32 `gorm:"type:int;index"` 23 | Province string `gorm:"type:varchar(10)"` 24 | City string `gorm:"type:varchar(10)"` 25 | District string `gorm:"type:varchar(20)"` 26 | Address string `gorm:"type:varchar(100)"` 27 | SignerName string `gorm:"type:varchar(20)"` 28 | SignerMobile string `gorm:"type:varchar(11)"` 29 | } 30 | 31 | //UserFav 用户收藏 32 | type UserFav struct { 33 | BaseModel 34 | 35 | //用户不能多次收藏相同商品 36 | User int32 `gorm:"type:int;index:idx_user_goods,unique"` 37 | Goods int32 `gorm:"type:int;index:idx_user_goods,unique"` 38 | } 39 | 40 | func (UserFav) TableName() string { 41 | return "userfav" 42 | } 43 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/proto/address.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | service Address { 6 | rpc GetAddressList(GetAddressRequest) returns (AddressListResponse); //获取收货地址 7 | rpc CreateAddress(GetAddressRequest) returns (AddressResponse); //新建收货地址 8 | rpc UpdateAddress(GetAddressRequest) returns (google.protobuf.Empty); //更新收货地址 9 | rpc DeleteAddress(GetAddressRequest) returns (google.protobuf.Empty); //删除收货地址 10 | } 11 | 12 | message GetAddressRequest { 13 | int32 id = 1; 14 | int32 userId = 2; 15 | string province = 3; 16 | string city = 4; 17 | string district = 5; 18 | string address = 6; 19 | string signerName = 7; 20 | string signerMobile = 8; 21 | } 22 | 23 | message AddressResponse{ 24 | int32 id = 1; 25 | int32 userId = 2; 26 | string province = 3; 27 | string city = 4; 28 | string district = 5; 29 | string address = 6; 30 | string signerName = 7; 31 | string signerMobile = 8; 32 | } 33 | 34 | message AddressListResponse { 35 | int32 total = 1; 36 | repeated AddressResponse data = 2; 37 | } -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/proto/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | option go_package = "/.;proto"; 3 | 4 | service Message { 5 | rpc GetMessageList(MessageRequest) returns (MessageListResponse); //获取留言列表 6 | rpc CreateMessage(MessageRequest) returns (MessageResponse); //新建留言 7 | 8 | } 9 | 10 | message MessageRequest { 11 | int32 id = 1; 12 | int32 userId = 2; 13 | int32 messageType = 3; 14 | string subject = 4; 15 | string message = 5; 16 | string file = 6; 17 | } 18 | 19 | message MessageResponse { 20 | int32 id = 1; 21 | int32 userId = 2; 22 | int32 messageType = 3; 23 | string subject = 4; 24 | string message = 5; 25 | string file = 6; 26 | } 27 | 28 | message MessageListResponse { 29 | int32 total = 1; 30 | repeated MessageResponse data = 2; 31 | } 32 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/proto/userfav.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | option go_package = "/.;proto"; 4 | 5 | service UserFav{ 6 | rpc GetFavList(UserFavRequest) returns(UserFavListResponse); //过滤收藏信息 7 | rpc AddUserFav(UserFavRequest) returns(google.protobuf.Empty); //添加收藏 8 | rpc DeleteUserFav(UserFavRequest) returns(google.protobuf.Empty); //删除收藏 9 | rpc GetUserFavDetail(UserFavRequest) returns(google.protobuf.Empty); //查看用户是否已经收藏某件商品 10 | } 11 | 12 | message UserFavRequest{ 13 | int32 userId = 1; 14 | int32 goodsId = 2; 15 | } 16 | message UserFavResponse{ 17 | int32 userId = 1; 18 | int32 goodsId = 2; 19 | } 20 | 21 | message UserFavListResponse { 22 | int32 total = 1; 23 | repeated UserFavResponse data = 2; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/tmp/nacos/cache/config/user-srv.json@@dev@@555d81b1-6cbf-4b69-b48a-a034c282c69f: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userop_srv", 3 | "host": "192.168.3.105", 4 | "port": 8080, 5 | "mysql": { 6 | "host": "127.0.0.1", 7 | "port": 3306, 8 | "db": "mxshop_userop_srv", 9 | "user": "root", 10 | "password": "Qq/2013XiaoKUang" 11 | }, 12 | "consul": { 13 | "host": "192.168.3.105", 14 | "port": 8500 15 | } 16 | } -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/utils/redisync/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | goredislib "github.com/go-redis/redis/v8" 9 | "github.com/go-redsync/redsync/v4" 10 | "github.com/go-redsync/redsync/v4/redis/goredis/v8" 11 | ) 12 | 13 | func main() { 14 | // Create a pool with go-redis (or redigo) which is the pool redisync will 15 | // use while communicating with Redis. This can also be any pool that 16 | // implements the `redis.Pool` interface. 17 | client := goredislib.NewClient(&goredislib.Options{ 18 | Addr: "localhost:6379", 19 | }) 20 | pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...) 21 | 22 | // Create an instance of redisync to be used to obtain a mutual exclusion 23 | // lock. 24 | rs := redsync.New(pool) 25 | 26 | var wg sync.WaitGroup 27 | wg.Add(3) 28 | for i := 0; i < 3; i++ { 29 | go func() { 30 | defer wg.Done() 31 | mutexname := fmt.Sprintf("mytest_%s", i) 32 | mutex := rs.NewMutex(mutexname) 33 | if err := mutex.Lock(); err != nil { 34 | panic(err) 35 | } 36 | fmt.Printf("获取锁成功\n") 37 | 38 | time.Sleep(time.Second * 1) 39 | fmt.Printf("执行结束\n") 40 | 41 | if ok, err := mutex.Unlock(); !ok || err != nil { 42 | panic("unlock failed") 43 | } 44 | fmt.Printf("释放锁成功\n") 45 | }() 46 | } 47 | wg.Wait() 48 | } 49 | -------------------------------------------------------------------------------- /mxshop_srvs/userop_srv/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func GetFreePort() (int, error) { 8 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 9 | if err != nil { 10 | return 0, err 11 | } 12 | 13 | l, err := net.ListenTCP("tcp", addr) 14 | if err != nil { 15 | return 0, err 16 | } 17 | defer l.Close() 18 | return l.Addr().(*net.TCPAddr).Port, nil 19 | } 20 | --------------------------------------------------------------------------------