go-zero模型操作
2022年6月19日
goctl 生成 model
通过
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...