2

Preamble

We will show you a go-zero microservice example in detail through a series of articles. The whole series is divided into ten articles. The directory structure is as follows:

  1. Environment construction
  2. service split
  3. User service
  4. Product Services (this article)
  5. Order service
  6. payment service
  7. RPC Service Auth Authentication
  8. service monitoring
  9. link tracking
  10. Distributed transaction

I hope that through this series, you can quickly develop a mall system using go-zero in the Docker environment on the local machine, so that you can quickly get started with microservices.

Full sample code: https://github.com/nivin-studio/go-zero-mall

First, let's take a look at the overall service split diagram:

4. Product service (product)

  • Enter the service workspace
$ cd mall/service/product

4.1 Generate product model model

  • create sql file
$ vim model/product.sql
  • write sql file
CREATE TABLE `product` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(255)  NOT NULL DEFAULT '' COMMENT '产品名称',
    `desc` varchar(255)  NOT NULL DEFAULT '' COMMENT '产品描述',
        `stock` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产品库存',
    `amount` int(10) unsigned NOT NULL DEFAULT '0'  COMMENT '产品金额',
    `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '产品状态',
    `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
    `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4;
  • Run the template generation command
$ goctl model mysql ddl -src ./model/product.sql -dir ./model -c

4.2 Generate product api service

  • create api file
$ vim api/product.api
  • write api file
type (
    // 产品创建
    CreateRequest {
        Name   string `json:"name"`
        Desc   string `json:"desc"`
        Stock  int64  `json:"stock"`
        Amount int64  `json:"amount"`
        Status int64  `json:"status"`
    }
    CreateResponse {
        Id int64 `json:"id"`
    }
    // 产品创建

    // 产品修改
    UpdateRequest {
        Id     int64  `json:"id"`
        Name   string `json:"name,optional"`
        Desc   string `json:"desc,optional"`
        Stock  int64  `json:"stock"`
        Amount int64  `json:"amount,optional"`
        Status int64  `json:"status,optional"`
    }
    UpdateResponse {
    }
    // 产品修改

    // 产品删除
    RemoveRequest {
        Id int64 `json:"id"`
    }
    RemoveResponse {
    }
    // 产品删除

    // 产品详情
    DetailRequest {
        Id int64 `json:"id"`
    }
    DetailResponse {
        Id     int64  `json:"id"`
        Name   string `json:"name"`
        Desc   string `json:"desc"`
        Stock  int64  `json:"stock"`
        Amount int64  `json:"amount"`
        Status int64  `json:"status"`
    }
    // 产品详情
)

@server(
    jwt: Auth
)
service Product {
    @handler Create
    post /api/product/create(CreateRequest) returns (CreateResponse)
    
    @handler Update
    post /api/product/update(UpdateRequest) returns (UpdateResponse)
    
    @handler Remove
    post /api/product/remove(RemoveRequest) returns (RemoveResponse)
    
    @handler Detail
    post /api/product/detail(DetailRequest) returns (DetailResponse)
}
  • Run the template generation command
$ goctl api go -api ./api/product.api -dir ./api

4.3 Generate product rpc service

  • Create proto file
$ vim rpc/product.proto
  • write proto file
syntax = "proto3";

package productclient;

option go_package = "product";

// 产品创建
message CreateRequest {
    string Name = 1;
    string Desc = 2;
    int64 Stock = 3;
    int64 Amount = 4;
    int64 Status = 5;
}
message CreateResponse {
    int64 id = 1;
}
// 产品创建

// 产品修改
message UpdateRequest {
    int64 id = 1;
    string Name = 2;
    string Desc = 3;
    int64 Stock = 4;
    int64 Amount = 5;
    int64 Status = 6;
}
message UpdateResponse {
}
// 产品修改

// 产品删除
message RemoveRequest {
    int64 id = 1;
}
message RemoveResponse {
}
// 产品删除

// 产品详情
message DetailRequest {
    int64 id = 1;
}
message DetailResponse {
    int64 id = 1;
    string Name = 2;
    string Desc = 3;
    int64 Stock = 4;
    int64 Amount = 5;
    int64 Status = 6;
}
// 产品详情

service Product {
    rpc Create(CreateRequest) returns(CreateResponse);
    rpc Update(UpdateRequest) returns(UpdateResponse);
    rpc Remove(RemoveRequest) returns(RemoveResponse);
    rpc Detail(DetailRequest) returns(DetailResponse);
}
  • Run the template generation command
$ goctl rpc proto -src ./rpc/product.proto -dir ./rpc

4.4 Write product rpc service

4.4.1 Modify the configuration file

  • Modify the product.yaml configuration file
$ vim rpc/etc/product.yaml
  • Modify the service listening address, the port number is 0.0.0.0:9001, Etcd service configuration, Mysql service configuration, CacheRedis service configuration
Name: product.rpc
ListenOn: 0.0.0.0:9001

Etcd:
  Hosts:
  - etcd:2379
  Key: product.rpc

Mysql:
  DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

CacheRedis:
- Host: redis:6379
  Type: node # node可以不写,可以设为cluster
  # Pass: xxx # 如果有密码

4.4.2 Add product model dependencies

  • Added Mysql service configuration, instantiation of CacheRedis service configuration
$ vim rpc/internal/config/config.go
package config

import (
    "github.com/tal-tech/go-zero/core/stores/cache"
    "github.com/tal-tech/go-zero/zrpc"
)

type Config struct {
    zrpc.RpcServerConf

    Mysql struct {
        DataSource string
    }
  
    CacheRedis cache.CacheConf
}
  • Dependency of registered service context product model
$ vim rpc/internal/svc/servicecontext.go
package svc

import (
    "mall/service/product/model"
    "mall/service/product/rpc/internal/config"

    "github.com/tal-tech/go-zero/core/stores/sqlx"
)

type ServiceContext struct {
    Config config.Config
    
    ProductModel model.ProductModel
}

func NewServiceContext(c config.Config) *ServiceContext {
    conn := sqlx.NewMysql(c.Mysql.DataSource)
    return &ServiceContext{
        Config:       c,
        ProductModel: model.NewProductModel(conn, c.CacheRedis),
    }
}

4.4.3 Add product creation logic Create

$ vim rpc/internal/logic/createlogic.go
package logic

import (
    "context"

    "mall/service/product/model"
    "mall/service/product/rpc/internal/svc"
    "mall/service/product/rpc/product"

    "github.com/tal-tech/go-zero/core/logx"
    "google.golang.org/grpc/status"
)

type CreateLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateLogic {
    return &CreateLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *CreateLogic) Create(in *product.CreateRequest) (*product.CreateResponse, error) {
    newProduct := model.Product{
        Name:   in.Name,
        Desc:   in.Desc,
        Stock:  in.Stock,
        Amount: in.Amount,
        Status: in.Status,
    }

    res, err := l.svcCtx.ProductModel.Insert(&newProduct)
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    newProduct.Id, err = res.LastInsertId()
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    return &product.CreateResponse{
        Id: newProduct.Id,
    }, nil
}

4.4.4 Add product details logic Detail

$ vim rpc/internal/logic/detaillogic.go
package logic

import (
    "context"

    "mall/service/product/model"
    "mall/service/product/rpc/internal/svc"
    "mall/service/product/rpc/product"

    "github.com/tal-tech/go-zero/core/logx"
    "google.golang.org/grpc/status"
)

type DetailLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailLogic {
    return &DetailLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *DetailLogic) Detail(in *product.DetailRequest) (*product.DetailResponse, error) {
    // 查询产品是否存在
    res, err := l.svcCtx.ProductModel.FindOne(in.Id)
    if err != nil {
        if err == model.ErrNotFound {
            return nil, status.Error(100, "产品不存在")
        }
        return nil, status.Error(500, err.Error())
    }

    return &product.DetailResponse{
        Id:     res.Id,
        Name:   res.Name,
        Desc:   res.Desc,
        Stock:  res.Stock,
        Amount: res.Amount,
        Status: res.Status,
    }, nil
}

4.4.5 Add product update logic Update

$ vim rpc/internal/logic/updatelogic.go
package logic

import (
    "context"

    "mall/service/product/model"
    "mall/service/product/rpc/internal/svc"
    "mall/service/product/rpc/product"

    "github.com/tal-tech/go-zero/core/logx"
    "google.golang.org/grpc/status"
)

type UpdateLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogic {
    return &UpdateLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *UpdateLogic) Update(in *product.UpdateRequest) (*product.UpdateResponse, error) {
    // 查询产品是否存在
    res, err := l.svcCtx.ProductModel.FindOne(in.Id)
    if err != nil {
        if err == model.ErrNotFound {
            return nil, status.Error(100, "产品不存在")
        }
        return nil, status.Error(500, err.Error())
    }

    if in.Name != "" {
        res.Name = in.Name
    }
    if in.Desc != "" {
        res.Desc = in.Desc
    }
    if in.Stock != 0 {
        res.Stock = in.Stock
    }
    if in.Amount != 0 {
        res.Amount = in.Amount
    }
    if in.Status != 0 {
        res.Status = in.Status
    }

    err = l.svcCtx.ProductModel.Update(res)
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    return &product.UpdateResponse{}, nil
}

4.4.6 Add product delete logic Remove

$ vim rpc/internal/logic/removelogic.go
package logic

import (
    "context"

    "mall/service/product/model"
    "mall/service/product/rpc/internal/svc"
    "mall/service/product/rpc/product"

    "github.com/tal-tech/go-zero/core/logx"
    "google.golang.org/grpc/status"
)

type RemoveLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveLogic {
    return &RemoveLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

func (l *RemoveLogic) Remove(in *product.RemoveRequest) (*product.RemoveResponse, error) {
    // 查询产品是否存在
    res, err := l.svcCtx.ProductModel.FindOne(in.Id)
    if err != nil {
        if err == model.ErrNotFound {
            return nil, status.Error(100, "产品不存在")
        }
        return nil, status.Error(500, err.Error())
    }

    err = l.svcCtx.ProductModel.Delete(res.Id)
    if err != nil {
        return nil, status.Error(500, err.Error())
    }

    return &product.RemoveResponse{}, nil
}

4.5 Write product api service

4.5.1 Modify the configuration file

  • Modify the product.yaml configuration file
$ vim api/etc/product.yaml
  • Modify the service address, the port number is 0.0.0.0:8001, Mysql service configuration, CacheRedis service configuration, Auth verification configuration
Name: Product
Host: 0.0.0.0
Port: 8001

Mysql:
  DataSource: root:123456@tcp(mysql:3306)/mall?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai

CacheRedis:
- Host: redis:6379
  Type: node # node可以不写,可以设为cluster
  # Pass: xxx # 如果有密码

Auth:
  AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl
  AccessExpire: 86400

4.5.2 Add product rpc dependency

  • Add product rpc service configuration
$ vim api/etc/product.yaml
Name: Product
Host: 0.0.0.0
Port: 8001
...
ProductRpc:
  Etcd:
    Hosts:
    - etcd:2379
    Key: product.rpc
  • Add instantiation of product rpc service configuration
$ vim api/internal/config/config.go
package config

import (
    "github.com/tal-tech/go-zero/rest"
    "github.com/tal-tech/go-zero/zrpc"
)

type Config struct {
    rest.RestConf

    Auth struct {
        AccessSecret string
        AccessExpire int64
    }

    ProductRpc zrpc.RpcClientConf
}
  • Dependency of registered service context product rpc
$ vim api/internal/svc/servicecontext.go
package svc

import (
    "mall/service/product/api/internal/config"
    "mall/service/product/rpc/productclient"

    "github.com/tal-tech/go-zero/zrpc"
)

type ServiceContext struct {
    Config config.Config
    
    ProductRpc productclient.Product
}

func NewServiceContext(c config.Config) *ServiceContext {
    return &ServiceContext{
        Config:     c,
        ProductRpc: productclient.NewProduct(zrpc.MustNewClient(c.ProductRpc)),
    }
}

4.5.3 Add product creation logic Create

$ vim api/internal/logic/createlogic.go
package logic

import (
    "context"

    "mall/service/product/api/internal/svc"
    "mall/service/product/api/internal/types"
    "mall/service/product/rpc/product"

    "github.com/tal-tech/go-zero/core/logx"
)

type CreateLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateLogic {
    return CreateLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *CreateLogic) Create(req types.CreateRequest) (resp *types.CreateResponse, err error) {
    res, err := l.svcCtx.ProductRpc.Create(l.ctx, &product.CreateRequest{
        Name:   req.Name,
        Desc:   req.Desc,
        Stock:  req.Stock,
        Amount: req.Amount,
        Status: req.Status,
    })
    if err != nil {
        return nil, err
    }

    return &types.CreateResponse{
        Id: res.Id,
    }, nil
}

4.5.4 Add product details logic Detail

$ vim api/internal/logic/detaillogic.go
package logic

import (
    "context"

    "mall/service/product/api/internal/svc"
    "mall/service/product/api/internal/types"
    "mall/service/product/rpc/product"

    "github.com/tal-tech/go-zero/core/logx"
)

type DetailLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) DetailLogic {
    return DetailLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *DetailLogic) Detail(req types.DetailRequest) (resp *types.DetailResponse, err error) {
    res, err := l.svcCtx.ProductRpc.Detail(l.ctx, &product.DetailRequest{
        Id: req.Id,
    })
    if err != nil {
        return nil, err
    }

    return &types.DetailResponse{
        Id:     res.Id,
        Name:   res.Name,
        Desc:   res.Desc,
        Stock:  res.Stock,
        Amount: res.Amount,
        Status: res.Status,
    }, nil
}

4.5.5 Add product update logic Update

$ vim api/internal/logic/updatelogic.go
package logic

import (
    "context"

    "mall/service/product/api/internal/svc"
    "mall/service/product/api/internal/types"
    "mall/service/product/rpc/product"

    "github.com/tal-tech/go-zero/core/logx"
)

type UpdateLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) UpdateLogic {
    return UpdateLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *UpdateLogic) Update(req types.UpdateRequest) (resp *types.UpdateResponse, err error) {
    _, err = l.svcCtx.ProductRpc.Update(l.ctx, &product.UpdateRequest{
        Id:     req.Id,
        Name:   req.Name,
        Desc:   req.Desc,
        Stock:  req.Stock,
        Amount: req.Amount,
        Status: req.Status,
    })
    if err != nil {
        return nil, err
    }

    return &types.UpdateResponse{}, nil
}

4.5.6 Add product delete logic Remove

$ vim api/internal/logic/removelogic.go
package logic

import (
    "context"

    "mall/service/product/api/internal/svc"
    "mall/service/product/api/internal/types"
    "mall/service/product/rpc/product"

    "github.com/tal-tech/go-zero/core/logx"
)

type RemoveLogic struct {
    logx.Logger
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func NewRemoveLogic(ctx context.Context, svcCtx *svc.ServiceContext) RemoveLogic {
    return RemoveLogic{
        Logger: logx.WithContext(ctx),
        ctx:    ctx,
        svcCtx: svcCtx,
    }
}

func (l *RemoveLogic) Remove(req types.RemoveRequest) (resp *types.RemoveResponse, err error) {
    _, err = l.svcCtx.ProductRpc.Remove(l.ctx, &product.RemoveRequest{
        Id: req.Id,
    })
    if err != nil {
        return nil, err
    }

    return &types.RemoveResponse{}, nil
}

4.6 Start product rpc service

Tip: Starting the service needs to be started in the golang container
$ cd mall/service/product/rpc
$ go run product.go -f etc/product.yaml
Starting rpc server at 127.0.0.1:9001...

4.7 Start the product api service

Tip: Starting the service needs to be started in the golang container
$ cd mall/service/product/api
$ go run product.go -f etc/product.yaml
Starting server at 0.0.0.0:8001...

project address

https://github.com/zeromicro/go-zero

Welcome to go-zero and star to support us!

WeChat exchange group

Follow the official account of " Practice " and click exchange group to get the QR code of the community group.


kevinwan
931 声望3.5k 粉丝

go-zero作者