从零创建一个go-zero项目 - 不定时更新

时间:2024-01-26 18:38:50

go-zero项目准备

创建module

go mod init afs

创建api文件

#cd path/api

#vim user.api

#以下为文件内容

syntax = "v1" // 标记 api 语言的版本

// 接口请求参数 - 通过form请求接收uid参数
type UserInfoRequest {
	Uid int64 `form:"uid"`
}

// 接口相应参数
type UserInfoResponse {
	Status  int                    `json:"status"`
	Message string                 `json:"message"`
	Data    map[string]interface{} `json:"data"`
}

// 定义一个http服务 - get请求/user/info
// 服务接收参数 - UserInfoRequest
// 服务响应参数 - UserInfoResponse
service afs-api {

	/**
	* @handler - 语句是对单个路由的 handler 信息控制
	*/
	@handler UserInfoHandler
	get /user/info (UserInfoRequest) returns (UserInfoResponse)
}

goctl初始化go-zero项目

goctl api go --api ./api/user.api --dir .

修改项目配置文件

修改公共配置文件

#vim path/etc/afs-api.yaml

App:
  Name: afs-api
  Host: 0.0.0.0
  Port: 8888

修改配置文件结构体

#vim path/internal/config/config.go

package config

import "github.com/zeromicro/go-zero/rest"

type Config struct {
	App rest.RestConf
}

修改入口文件

#vim path/afs.go

package main

import (
	"flag"
	"fmt"

	"afs/internal/config"
	"afs/internal/handler"
	"afs/internal/svc"

	"github.com/zeromicro/go-zero/core/conf"
	"github.com/zeromicro/go-zero/rest"
)

// 接收命令行中 -f 参数
var configFile = flag.String("f", "etc/afs-api.yaml", "the config file")

func main() {

	// 解析命令行
	flag.Parse()

	// 加载配置
	var c config.Config
	conf.MustLoad(*configFile, &c)
	
	// 注册server配置 - 修改行
	server := rest.MustNewServer(c.App)
	defer server.Stop()

	// 初始化项目 - 全局变量、路由等
	ctx := svc.NewServiceContext(c)
	handler.RegisterHandlers(server, ctx)

	// 启动服务 - 修改行
	fmt.Printf("Starting server at %s:%d...\n", c.App.Host, c.App.Port)
	server.Start()
}

下载依赖包

go mod tidy

启动项目

go run afs.go

增加mysql链接

表结构

CREATE TABLE `afs`.`afs_user`(  
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `userid` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` CHAR(32) NOT NULL DEFAULT '' COMMENT '密码',
  `header` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像',
  `real_name` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '真实姓名',
  `mobile` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '电话',
  `cash` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '可用余额',
  `frozen_cash` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '冻结资金',
  `is_lock` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否锁定',
  `zt` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '用户状态0普通用户',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  INDEX `idx_userid` (`userid`),
  INDEX `idx_mobile` (`mobile`)
)
COMMENT='用户表';

使用goctl生成相关文件

goctl model mysql datasource -url="用户名:密码@tcp(数据库地址:端口号)/数据库名" --table tableName --dir ./internal/model

修改公共配置文件 - 增加mysql配置

#vim path/etc/afs-api.yaml

App:
  Name: afs-api
  Host: 0.0.0.0
  Port: 8888
Mysql:
  afs:
    Host: 127.0.0.1
    Port: 3306
    User: root
    Pass: pass
    Dbname: afs
    OutTime: 60
    MaxTime: 3600
    MaxIdle: 2
    MaxOpen: 10

修改配置文件结构体 - 增加mysql配置

#vim path/internal/config/config.go

package config

import "github.com/zeromicro/go-zero/rest"

type Config struct {
	App rest.RestConf
	Mysql map[string]struct{
		Host string
		Port int
		User string
		Pass string
		Dbname string
		OutTime int
		MaxTime int
		MaxIdle int
		MaxOpen int
	}
}

修改服务初始化文件 - 初始化mysql链接

#vim path/internal/svc/servicecontext.go

package svc

import (
	"fmt"
	"time"
	"afs/internal/config"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
)

type ServiceContext struct {
	Config config.Config
	Mysql map[string]sqlx.SqlConn
}

func NewServiceContext(c config.Config) *ServiceContext {
	// 全局变量
	svc := ServiceContext{
		Config: c,
	}
	//初始化mysql
	svc.initMysql();
	return &svc;
}

// 初始化mysql
func (svc *ServiceContext) initMysql(){

	// 构造mysql
	svc.Mysql = make(map[string]sqlx.SqlConn);

	// 初始化链接
	for k, v := range svc.Config.Mysql {
		dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local",v.User,v.Pass,v.Host,v.Port,v.Dbname);
		conn := sqlx.NewMysql(dsn);
		// 获取数据库句柄
		db,err := conn.RawDB()
		if err != nil{
			fmt.Println(k + " 数据库链接失败")
		}else{
			fmt.Println(k + " 数据库链接成功")
			// 设置连接池信息
			db.SetConnMaxIdleTime(time.Duration(v.OutTime) * time.Second) // SetConnMaxIdleTime() - 最大空闲时间
			db.SetConnMaxLifetime(time.Duration(v.MaxTime) * time.Second) // SetConnMaxLifetime() - 最大可用时间
			db.SetMaxIdleConns(v.MaxIdle) // SetMaxIdleConns() - 最大空闲链接数
			db.SetMaxOpenConns(v.MaxOpen) // SetMaxIdleConns() - 最大链接数
		}
		svc.Mysql[k] = conn
	}
}

修改afs_user表Model文件

#vim internal/model/afsusermodel.go

package model

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

var _ AfsUserModel = (*customAfsUserModel)(nil)

type (
	// AfsUserModel is an interface to be customized, add more methods here,
	// and implement the added methods in customAfsUserModel.
	AfsUserModel interface {
		afsUserModel
		withSession(session sqlx.Session) AfsUserModel
		GetInfoById(ctx context.Context, id int64) (*AfsUser, error)
	}

	customAfsUserModel struct {
		*defaultAfsUserModel
	}
)

// NewAfsUserModel returns a model for the database table.
func NewAfsUserModel(conn sqlx.SqlConn) AfsUserModel {
	return &customAfsUserModel{
		defaultAfsUserModel: newAfsUserModel(conn),
	}
}

func (m *customAfsUserModel) withSession(session sqlx.Session) AfsUserModel {
	return NewAfsUserModel(sqlx.NewSqlConnFromSession(session))
}

// 增加方法 - 通过用户id获取用户信息
func (m *customAfsUserModel) GetInfoById(ctx context.Context, id int64)  (*AfsUser, error){
	var info AfsUser;
	sql := fmt.Sprintf("SELECT * FROM afs_user WHERE id = %v limit 1",id);
	err := m.conn.QueryRowCtx(context.Background(), &info, sql)
	if err == nil{
		return &info,nil;
	}else{
		return nil,err;
	}
}

修改afs_user表Model_gen文件

#vim internal/model/afsusermodel_gen.go

// Code generated by goctl. DO NOT EDIT.

package model

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/zeromicro/go-zero/core/stores/builder"
	// "github.com/zeromicro/go-zero/core/stores/sqlc" // 移除行
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"github.com/zeromicro/go-zero/core/stringx"
)

var (
	afsUserFieldNames          = builder.RawFieldNames(&AfsUser{})
	afsUserRows                = strings.Join(afsUserFieldNames, ",")
	afsUserRowsExpectAutoSet   = strings.Join(stringx.Remove(afsUserFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",")
	afsUserRowsWithPlaceHolder = strings.Join(stringx.Remove(afsUserFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?"
)

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

	defaultAfsUserModel struct {
		conn  sqlx.SqlConn
		table string
	}

	AfsUser struct {
		Id         int64     `db:"id"`          // 用户id
		Userid     string    `db:"userid"`      // 用户名
		Password   string    `db:"password"`    // 密码
		Header     string    `db:"header"`      // 头像
		RealName   string    `db:"real_name"`   // 真实姓名
		Mobile     string    `db:"mobile"`      // 电话
		Cash       float64   `db:"cash"`        // 可用余额
		FrozenCash float64   `db:"frozen_cash"` // 冻结资金
		IsLock     int64     `db:"is_lock"`     // 是否锁定
		Zt         int64     `db:"zt"`          // 用户状态0普通用户
		CreateTime time.Time `db:"create_time"` // 添加时间
		UpdateTime time.Time `db:"update_time"` // 修改时间
	}
)

func newAfsUserModel(conn sqlx.SqlConn) *defaultAfsUserModel {
	return &defaultAfsUserModel{
		conn:  conn,
		table: "`afs_user`",
	}
}

func (m *defaultAfsUserModel) Delete(ctx context.Context, id int64) error {
	query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
	_, err := m.conn.ExecCtx(ctx, query, id)
	return err
}

func (m *defaultAfsUserModel) FindOne(ctx context.Context, id int64) (*AfsUser, error) {
	query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", afsUserRows, m.table)
	var resp AfsUser
	err := m.conn.QueryRowCtx(ctx, &resp, query, id)
	switch err {
	case nil:
		return &resp, nil
	case sqlx.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultAfsUserModel) Insert(ctx context.Context, data *AfsUser) (sql.Result, error) {
	query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, afsUserRowsExpectAutoSet)
	ret, err := m.conn.ExecCtx(ctx, query, data.Userid, data.Password, data.Header, data.RealName, data.Mobile, data.Cash, data.FrozenCash, data.IsLock, data.Zt)
	return ret, err
}

func (m *defaultAfsUserModel) Update(ctx context.Context, data *AfsUser) error {
	query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, afsUserRowsWithPlaceHolder)
	_, err := m.conn.ExecCtx(ctx, query, data.Userid, data.Password, data.Header, data.RealName, data.Mobile, data.Cash, data.FrozenCash, data.IsLock, data.Zt, data.Id)
	return err
}

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

修改逻辑层数据 - 从数据库中查询用户信息

#vim internal/logic/userinfologic.go

package logic

import (
	"context"
	"fmt"
	"afs/internal/svc"
	"afs/internal/types"
	"afs/internal/model"
	"github.com/zeromicro/go-zero/core/logx"
)

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

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

func (l *UserInfoLogic) UserInfo(req *types.UserInfoRequest) (resp *types.UserInfoResponse, err error) {
	// todo: add your logic here and delete this line
	resp = new(types.UserInfoResponse);
	info,err := model.NewAfsUserModel(l.svcCtx.Mysql["afs"]).GetInfoById(l.ctx,req.Uid);
	if err != nil{
		resp.Status = 0;
		resp.Message = "用户查询失败";
	}else{
		resp.Status = 1;
		resp.Message = "ok";
		resp.Data = make(map[string]interface{})
		resp.Data["info"] = info;
	}
	fmt.Printf("%+v",info)
	return
}

下载依赖包

go mod tidy

启动项目

go run afs.go

增加redis链接

修改公共配置文件 - 增加redis配置

#vim path/etc/afs-api.yaml

App:
  Name: afs-api
  Host: 0.0.0.0
  Port: 8888
Mysql:
  afs:
    Host: 127.0.0.1
    Port: 3306
    User: root
    Pass: pass
    Dbname: afs
    OutTime: 60
    MaxTime: 3600
    MaxIdle: 2
    MaxOpen: 10
Redis:
  aliyun:
    Host: 127.0.0.1
    Port: 6379
    Pass: 
    Db: 0
    OutTime: 60
    MaxIdle: 2
    MaxOpen: 10

修改配置文件结构体 - 增加redis配置

#vim path/internal/config/config.go

package config

import "github.com/zeromicro/go-zero/rest"

type Config struct {
	App rest.RestConf
	Mysql map[string]struct{
		Host string
		Port int
		User string
		Pass string
		Dbname string
		OutTime int
		MaxTime int
		MaxIdle int
		MaxOpen int
	}
	Redis map[string]struct{
		Host string
		Port int
		Pass string
		Db int
		OutTime int
		MaxIdle int
		MaxOpen int
	}
}

修改服务初始化文件 - 初始化redis链接

#vim path/internal/svc/servicecontext.go

package svc

import (
	"fmt"
	"time"
	"context"
	"afs/internal/config"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"github.com/go-redis/redis/v8"
)

type ServiceContext struct {
	Config config.Config
	Mysql map[string]sqlx.SqlConn
	Redis map[string]*redis.Client
}

func NewServiceContext(c config.Config) *ServiceContext {
	// 全局变量
	svc := ServiceContext{
		Config: c,
	}
	//初始化mysql
	svc.initMysql();
	//初始化redis
	svc.initRedis();
	return &svc;
}

// 初始化mysql
func (svc *ServiceContext) initMysql(){

	// 构造mysql
	svc.Mysql = make(map[string]sqlx.SqlConn);

	// 初始化链接
	for k, v := range svc.Config.Mysql {
		dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local",v.User,v.Pass,v.Host,v.Port,v.Dbname);
		conn := sqlx.NewMysql(dsn);
		// 获取数据库句柄
		db,err := conn.RawDB()
		if err != nil{
			fmt.Println(k + " mysql链接失败")
		}else{
			fmt.Println(k + " mysql链接成功")
			// 设置连接池信息
			db.SetConnMaxIdleTime(time.Duration(v.OutTime) * time.Second) // SetConnMaxIdleTime() - 最大空闲时间
			db.SetConnMaxLifetime(time.Duration(v.MaxTime) * time.Second) // SetConnMaxLifetime() - 最大可用时间
			db.SetMaxIdleConns(v.MaxIdle) // SetMaxIdleConns() - 最大空闲链接数
			db.SetMaxOpenConns(v.MaxOpen) // SetMaxIdleConns() - 最大链接数
		}
		svc.Mysql[k] = conn
	}
}

// 初始化Redis
func (svc *ServiceContext) initRedis(){

	// 构造Redis
	svc.Redis = make(map[string]*redis.Client);

	// 初始化链接
	for k, v := range svc.Config.Redis {
		dsn := fmt.Sprintf("%v:%v",v.Host,v.Port);

		rdb := redis.NewClient(&redis.Options{
			Addr: dsn,
			Password: v.Pass,
			DB: v.Db, 
			PoolSize: v.MaxOpen,
			MinIdleConns: v.MaxIdle,
			IdleTimeout: time.Duration(v.OutTime) * time.Second,
		})

		_, err := rdb.Ping(context.Background()).Result()
		if err != nil{
			fmt.Println(k + " redis链接失败")
		}else{
			fmt.Println(k + " redis链接成功")
		}
		svc.Redis[k] = rdb
	}

}

修改逻辑层数据 - 从redis中查询用户信息

#vim internal/logic/userinfologic.go

package logic

import (
	"context"
	"encoding/json"
	"fmt"
	"time"
	"afs/internal/svc"
	"afs/internal/types"
	"afs/internal/model"
	"github.com/zeromicro/go-zero/core/logx"
)

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

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

func (l *UserInfoLogic) UserInfo(req *types.UserInfoRequest) (resp *types.UserInfoResponse, err error) {

	// todo: add your logic here and delete this line

	redis_key := fmt.Sprintf("afs:afs_user:id:%v",req.Uid);
	value,err1 := l.svcCtx.Redis["aliyun"].Get(l.ctx, redis_key).Result()
	userInfo := new(model.AfsUser);
	if err1 != nil{
		fmt.Println("直接读库");
		info,err2 := model.NewAfsUserModel(l.svcCtx.Mysql["afs"]).GetInfoById(l.ctx,req.Uid);
		if err2 == nil{
			userInfo = info;
			jsonByte,err3 := json.Marshal(info);
			if err3 == nil{
				l.svcCtx.Redis["aliyun"].Set(l.ctx, redis_key,jsonByte, time.Duration(300) * time.Second)
			}
		}
	}else{
		fmt.Println("使用缓存");
		json.Unmarshal([]byte(value),userInfo)
	}

	resp = new(types.UserInfoResponse)

	if userInfo.Id > 0{
		resp.Status = 1;
		resp.Message = "ok";
		resp.Data = make(map[string]interface{})
		resp.Data["info"] = userInfo;
	}else{
		resp.Status = 0;
		resp.Message = "用户获取失败";
	}

	return
}

下载依赖包

go mod tidy

启动项目

go run afs.go