go-zero模型操作

wxvirus2022年6月19日
大约 4 分钟

goctl 生成 model

model 指令文档open in new window

  • 通过ddl方式生成

    goctl model mysql ddl -src="./*.sql" -dir="./sql/model" -c
    

    比如你在 model 目录里写好对应的表的 SQL 语句即可

    create table user (
    	id bigint auto_increment not null,
        user_id bigint not null,
        nickname varchar(39) not null default '',
        primary key (id)
    );
    

    就会直接根据这个表DDL来生成对应的模型代码

    model
    ├── usermodel.go
    ├── usermodel_gen.go
    └── vars.go
    
  • 通过datasource生成

    goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="*"  -dir="./model"
    

    这个是表和库自己都事先创建好,然后直接指定库里的某个表生成什么模型。生成的代码是基本的 CURD 结构。

代码熟悉

package model

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

var _ UserModel = (*customUserModel)(nil)

type (
	// UserModel is an interface to be customized, add more methods here,
	// and implement the added methods in customUserModel.
	UserModel interface {
		userModel
	}

	customUserModel struct {
		*defaultUserModel
	}
)

// NewUserModel returns a model for the database table.
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) UserModel {
	return &customUserModel{
		defaultUserModel: newUserModel(conn, c),
	}
}

// Code generated by goctl. DO NOT EDIT!

package model

import (
	"context"
	"database/sql"
	"fmt"
	"github.com/zeromicro/go-zero/core/stores/builder"
	"github.com/zeromicro/go-zero/core/stores/cache"
	"github.com/zeromicro/go-zero/core/stores/sqlc"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"github.com/zeromicro/go-zero/core/stringx"
	"strings"
)

var (
	userFieldNames          = builder.RawFieldNames(&User{})    // 将User结构体的字段名称转换为字符串 ["id", "user_id", "nickname"]
	userRows                = strings.Join(userFieldNames, ",") // 使用逗号隔开字段名称
	userRowsExpectAutoSet   = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`", "`create_at`", "`update_at`"), ",")
	userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`", "`create_at`", "`update_at`"), "=?,") + "=?"

	cacheZeroDemoUserIdPrefix = "cache:zeroDemo:user:id:" // 针对单条数据作缓存
)

type (
	userModel interface {
		Insert(ctx context.Context, data *User) (sql.Result, error)
		FindOne(ctx context.Context, id int64) (*User, error)
		Update(ctx context.Context, newData *User) error
		Delete(ctx context.Context, id int64) error
	}

	// 内部操作的实体
	defaultUserModel struct {
		sqlc.CachedConn
		table string
	}

	// User 针对表对应的实体
	User struct {
		Id       int64  `db:"id"`
		UserId   int64  `db:"user_id"` //
		Nickname string `db:"nickname"`
	}
)

func newUserModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultUserModel {
	return &defaultUserModel{
		CachedConn: sqlc.NewConn(conn, c),
		table:      "`user`",
	}
}

func (m *defaultUserModel) Delete(ctx context.Context, id int64) error {
	zeroDemoUserIdKey := fmt.Sprintf("%s%v", cacheZeroDemoUserIdPrefix, id)
	_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
		return conn.ExecCtx(ctx, query, id)
	}, zeroDemoUserIdKey)
	return err
}

func (m *defaultUserModel) FindOne(ctx context.Context, id int64) (*User, error) {
	zeroDemoUserIdKey := fmt.Sprintf("%s%v", cacheZeroDemoUserIdPrefix, id)
	var resp User
	err := m.QueryRowCtx(ctx, &resp, zeroDemoUserIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
		query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userRows, m.table)
		return conn.QueryRowCtx(ctx, v, query, id)
	})
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultUserModel) Insert(ctx context.Context, data *User) (sql.Result, error) {
	zeroDemoUserIdKey := fmt.Sprintf("%s%v", cacheZeroDemoUserIdPrefix, data.Id)
	ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("insert into %s (%s) values (?, ?)", m.table, userRowsExpectAutoSet)
		return conn.ExecCtx(ctx, query, data.UserId, data.Nickname)
	}, zeroDemoUserIdKey)
	return ret, err
}

func (m *defaultUserModel) Update(ctx context.Context, data *User) error {
	zeroDemoUserIdKey := fmt.Sprintf("%s%v", cacheZeroDemoUserIdPrefix, data.Id)
	_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRowsWithPlaceHolder)
		return conn.ExecCtx(ctx, query, data.UserId, data.Nickname, data.Id)
	}, zeroDemoUserIdKey)
	return err
}

func (m *defaultUserModel) formatPrimary(primary interface{}) string {
	return fmt.Sprintf("%s%v", cacheZeroDemoUserIdPrefix, primary)
}

func (m *defaultUserModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
	query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userRows, m.table)
	return conn.QueryRowCtx(ctx, v, query, primary)
}

func (m *defaultUserModel) tableName() string {
	return m.table
}

上面生成的代码是加了缓存之后的代码。

部分缓存源码

func (c cacheNode) doTake(ctx context.Context, v interface{}, key string,
	query func(v interface{}) error, cacheVal func(v interface{}) error) error {
	logger := logx.WithContext(ctx)
	val, fresh, err := c.barrier.DoEx(key, func() (interface{}, error) {
		if err := c.doGetCache(ctx, key, v); err != nil {
			if err == errPlaceholder {
				return nil, c.errNotFound
			} else if err != c.errNotFound {
				// why we just return the error instead of query from db,
				// because we don't allow the disaster pass to the dbs.
				// fail fast, in case we bring down the dbs.
				return nil, err
			}

			if err = query(v); err == c.errNotFound {
				if err = c.setCacheWithNotFound(ctx, key); err != nil {
					logger.Error(err)
				}

				return nil, c.errNotFound
			} else if err != nil {
				c.stat.IncrementDbFails()
				return nil, err
			}

			if err = cacheVal(v); err != nil {
				logger.Error(err)
			}
		}

		return jsonx.Marshal(v)
	})
	if err != nil {
		return err
	}
	if fresh {
		return nil
	}

	// got the result from previous ongoing query.
	// why not call IncrementTotal at the beginning of this function?
	// because a shared error is returned, and we don't want to count.
	// for example, if the db is down, the query will be failed, we count
	// the shared errors with one db failure.
	c.stat.IncrementTotal()
	c.stat.IncrementHit()

	return jsonx.Unmarshal(val.([]byte), v)
}

这边有一个核心的用法就是,当用户持续在查找一个不存在的用户的时候,避免缓存击穿,会默认在redis里存 1 分钟的*符号。

逻辑代码 demo

查询用户详情接口

func (l *UserInfoLogic) UserInfo(req *types.UserInfoReq) (resp *types.UserInfoResp, err error) {
	// todo: add your logic here and delete this line
	one, err := l.svcCtx.UserModel.FindOne(l.ctx, req.UserId)
	if err != nil && err != model.ErrNotFound {
		return nil, errors.New("查询数据失败")
	}
	if one == nil {
		return nil, errors.New("用户不存在")
	}

	return &types.UserInfoResp{
		UserId:   one.UserId,
		Nickname: one.Nickname,
	}, nil
}

提示

这里我们就不能想当然的使用err != nil来判断错误,因为FindOne方法里返回的内容是不一样的。

用户服务连接配置

Name: user-api
Host: 0.0.0.0
Port: 8888

DB:
    DataSource: gulimall:root@tcp(127.0.0.1:3306)/zero-demo?charset=utf8mb4&parseTime=True&loc=Local

CacheRedis:
    - Host: 127.0.0.1:6379
      Pass: ''
      Type: node

config.go

package config

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

type Config struct {
	rest.RestConf
	DB struct{
		DataSource string
	}
	CacheRedis cache.CacheConf
}

serviceContext.go

package svc

import (
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"zero-demo/user-api/internal/config"
	"zero-demo/user-api/model"
)

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

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

Loading...