go微服务开发:go-zero入门教程(一)

以下内容,参考了go-zero官方文档,是对官方文档的进阶指南章节的梳理汇总。

go-zero的进阶指南,请参考 https://go-zero.dev/cn/docs/advance/business-dev

 

通过本文,你将学习到如下知识点:

1.如何使用go-zero定义api文件

2.如何为定义的api文件生成api服务

3.如何编写模块业务逻辑

4.go-zero开发注意实现,参见这里 https://www.cnblogs.com/jamstack/p/17223639.html

 

在开始之前,假设你已经对go-zero有了基本的了解,并且了解go-zero编写api文件的语法。如果还不了解,建议先阅读这里 https://go-zero.dev/cn/docs/design/grammar/ 

 

开发环境:

Windows 11

Terminal preview

go 1.19

 

go-zero的进阶指南的演示工程,共包含2个模块:user和search,本篇讲述的是user模块,search模块请参见:go微服务开发:go-zero入门教程(二)

 

第一步:下载演示工程、设计数据库表的ddl、生成数据库表和model文件(如果是已有的数据库表,也可以逆向生成model)

  1.下载并解压go-zero进阶指南提供的演示工程,下载地址为 https://go-zero.dev/cn/assets/files/book-3d0b9e679f9e502cb07685b701c450cf.zip

  2.假设我们使用的是一个新的测试库,不存在数据库表,user模块的model文件夹是我们定义数据库表ddl的地方,这里存在着一个名为user.sql的ddl文件,我们为这个ddl生成数据库表,执行如下命令:

goctl model mysql ddl -src user.sql -dir . -c

  3.向生成的数据库表user,写入测试数据

INSERT INTO `user` (number,name,password,gender)values ('666','小明','123456','男');

  

  题外话:如果user表已经存在,我们要为user表生成model,可以执行如下命令:

goctl model mysql datasource -url="$datasource" -table="user" -c -dir .

第二步:定义api文件、生成api服务

  1.定义user.api

  cd service/user/api/,转到user模块的api文件夹下,定义一个名为user.api的api文件,代码如下:

type (
    LoginReq {
        Username string `json:"username"`
        Password string `json:"password"`
    }

    LoginReply {
        Id           int64 `json:"id"`
        Name         string `json:"name"`
        Gender       string `json:"gender"`
        AccessToken  string `json:"accessToken"`
        AccessExpire int64 `json:"accessExpire"`
        RefreshAfter int64 `json:"refreshAfter"`
    }
)

service user-api {
    @handler login
    post /user/login (LoginReq) returns (LoginReply)
}

  2.为user.api生成api服务

goctl api go -api user.api -dir . 

第三步:编写user模块的业务代码

  1.定义数据库的配置

  cd service/user/api/internal/config/,编辑config.go,代码如下:

package config

import (
	"github.com/zeromicro/go-zero/rest"
	"github.com/zeromicro/go-zero/core/stores/cache"
)


type Config struct {
	rest.RestConf
	Mysql struct{
		DataSource string
	}

	CacheRedis cache.CacheConf
	Auth	struct{
		AccessSecret	string
		AccessExpire	int64
	}
}

  2.填写数据库的yaml配置参数

  cd service/user/api/etc/,编辑user-api.yaml,代码如下:

Name: user-api
Host: 0.0.0.0
Port: 8888
Mysql:
  DataSource: root:dev@123456@tcp(127.0.0.1)/book?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
  - Host: 127.0.0.1:6379
    Pass:
    Type: node
Auth:
  AccessSecret: abcdefgh
  AccessExpire: 3600

  3.为servicecontext完善服务依赖

  cd service/user/api/internal/svc/,编辑servicecontext.go,代码如下:

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

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

  我们可以看到,servicecontext.go里定义了一个名为ServiceContext的结构体,ServiceContext的结构体承载着config.Config和model.UserModel,分别对应的是user模块的数据库配置和UserModel。

   4.编写user模块的业务逻辑,这里对应的是处理登录的业务逻辑

  cd service/user/api/internal/logic/,编辑loginlogic.go,代码如下:

package logic

import (
	"book/service/user/model"
	"context"
	"errors"
	"github.com/golang-jwt/jwt/v4"
	"strings"
	"time"

	"book/service/user/api/internal/svc"
	"book/service/user/api/internal/types"

	"github.com/zeromicro/go-zero/core/logx"
)

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

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

func (l *LoginLogic) Login(req *types.LoginReq) (*types.LoginReply, error) {
	if len(strings.TrimSpace(req.Username)) == 0 || len(strings.TrimSpace(req.Password)) == 0 {
		return nil, errors.New("参数错误")
	}

	userInfo, err := l.svcCtx.UserModel.FindOneByNumber(l.ctx, req.Username)
	switch err {
	case nil:
	case model.ErrNotFound:
		return nil, errors.New("用户名不存在")
	default:
		return nil, err
	}

	if userInfo.Password != req.Password {
		return nil, errors.New("用户密码不正确")
	}

	// ---start---
	now := time.Now().Unix()
	accessExpire := l.svcCtx.Config.Auth.AccessExpire
	jwtToken, err := l.getJwtToken(l.svcCtx.Config.Auth.AccessSecret, now, l.svcCtx.Config.Auth.AccessExpire, userInfo.Id)
	if err != nil {
		return nil, err
	}
	// ---end---

	return &types.LoginReply{
		Id:           userInfo.Id,
		Name:         userInfo.Name,
		Gender:       userInfo.Gender,
		AccessToken:  jwtToken,
		AccessExpire: now + accessExpire,
		RefreshAfter: now + accessExpire/2,
	}, nil
}

func (l *LoginLogic) getJwtToken(secretKey string, iat, seconds, userId int64) (string, error) {
	claims := make(jwt.MapClaims)
	claims["exp"] = iat + seconds
	claims["iat"] = iat
	claims["userId"] = userId
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims = claims
	return token.SignedString([]byte(secretKey))
}

第四步:启动user模块

  cd service/user/api,执行如下命令:

go run user.go -f etc/user-api.yaml

第五步:使用cmd测试user模块

curl -i -X POST http://127.0.0.1:8888/user/login -H "Content-Type: application/json" -d "{ \"username\":\"666\", \"password\":\"123456\" }"

  可以看到/user/login接口以json的形式返回了对应的用户信息,但是并不包含password字段,如果我们想得到password字段,该怎么实现呢?使用go-zero的好处就体现出来了,我们不需要编辑生成的api服务,我们只需要在user.api文件的LoginReply结构体里加上Password,重新生成api服务,重新生成api服务时不会覆盖已经生成的文件:

type (
	LoginReq {
		Username string `json:"username"`
		Password string `json:"password"`
	}

	LoginReply {
		Id           int64  `json:"id"`
		Name         string `json:"name"`
		Password     string `json:"password"`
		Gender       string `json:"gender"`
		AccessToken  string `json:"accessToken"`
		AccessExpire int64  `json:"accessExpire"`
		RefreshAfter int64  `json:"refreshAfter"`
	}
)

service     user-api    {
	@handler login
	post /user/login    (LoginReq)  returns (LoginReply)
}

  

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
posted @ 2023-03-16 11:41  jamstack  阅读(593)  评论(0编辑  收藏  举报