文章目录
框架设计
结构化输入输出
输出输出用结构体来定义结构,避免在方法中硬编码,并且方便生成接口文档
数据模型
包括MySQL、Redis、MongoDB、Kafka在内的持久化数据库的数据结构,是框架自动生成的,在/app/dao/internal/model/entity目录下
隐式初始化与显式初始化
main.go里import的包里自带的init方法是隐式初始化,goframe框架的很多模块使用隐式初始化;boot/boot.go里调用的方法是显式初始化吗,一般是与业务相关的用显式初始化
context
在一次请求中,协程共享的变量,类似于java的threadlocal
Quickstart
安装swagger / 更新swagger文档
gf swagger --pack
更改安装的swagger版本
go get -u github.com/swaggo/swag/cmd/swag@v1.8.6
(通过go.mod来执行的)
go get xxx.cn/gaea-server/x/core@master
来拉取分支
也可以来更新goframe版本
go get -u github.com/gogf/gf/v2
删除安装模块的缓存:
go clean --modcache
日志管理
没啥要看的
错误处理
没有用gerror,还是原生的error处理
数据校验
类似Laravel里面的validator : p - params , v - valid
,项目里没有自定义错误
缓存管理
有gcache但是主要还是配置的redis
因为ORM的*gcache.Cache缓存对象提供的是单进程内存缓存,如果服务采用多节点部署,多节点之间的缓存可能会产生数据不一致的情况,因此通过Redis服务器来实现对数据库查询数据的缓存,将单进程内存缓存改为分布式的Redis缓存
fileDb.GetCache().SetAdapter(redis.NewRedis(redis2.DefaultClient()))
数据库ORM
能够自动生成DAO代码
gf gen dao -path ./app/ -c config/config.yml -g fic(group名,可见.sql的上层文件夹名) -t user_view_menu(表名) -modelFile test.go(输出文件名)
yaml配置文件里,放了两个数据库分组的配置信息,由于没有配置主从,所有读写都是在master节点上执行
orm 时间相关方法
.WhereLT(dao.EntExportQueue.Columns.CreatedAt, time.Now().UTC().Add(-7*24*time.Hour))
可是查出7天前数据
数据库升级脚本
存储过程示例
-- 创建删除索引的存储过程
DROP PROCEDURE IF EXISTS del_index;
CREATE PROCEDURE del_index(IN target_table_name VARCHAR(100),IN target_index_name VARCHAR(100))
BEGIN
IF EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = target_table_name AND index_name = target_index_name) THEN
set @statement =( select concat ('alter table ',target_table_name, ' drop key ',target_index_name ));
PREPARE STMT FROM @statement;
EXECUTE STMT;
END IF;
END;
call del_index('tag','name');
call del_index('tag','idx_name_ttype');
存储过程参考 https://blog.csdn.net/scdncby/article/details/125738444
-- t 表名;i 索引名称;v 创建索引执行的sql ddl语句
-- 创建删除索引的存储过程
DROP PROCEDURE IF EXISTS del_index;
DELIMITER $
CREATE PROCEDURE del_index(IN t VARCHAR(100),IN i VARCHAR(100),IN v VARCHAR(255))
BEGIN
DECLARE target_database VARCHAR(100);
DECLARE target_table_name VARCHAR(100);
DECLARE target_column_name VARCHAR(100);
DECLARE target_index_name VARCHAR(100);
set target_table_name = t;
set target_index_name = i;
SELECT DATABASE() INTO target_database;
IF EXISTS (SELECT * FROM information_schema.statistics WHERE table_schema = target_database AND table_name = target_table_name AND index_name = target_index_name) THEN
set @statement = v;
PREPARE STMT FROM @statement;
EXECUTE STMT;
END IF;
END;
$
DELIMITER ;
call del_index('tag','name',"alter table tag drop key name");
call del_index('tag','name',"alter table tag drop key idx_name_ttype");
实用sql
# 查询表索引
SELECT * FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'tag';
# 查数据库mode
select @@session.sql_mode;
默认值
OmitEmptyData()让框架忽略零值
0, nil, false, "", len(slice/map/chan) == 0
,插入数据库默认值
所以禁止在数据库里填默认值为非0的,以防想写入0却被orm忽略插入数据库默认值;在数据库里默认为0,业务逻辑里写非0的默认值,在数据库里没有该条数据的时候直接返回默认值。
oMitEmptyData()会忽略model里没填入的字段,但是会导致无法将某字段更新为0值,此时只能先查出旧的model数据,指定更新某个字段,并且不使用oMitEmptyData()
会忽略掉deleted有值的字段即sql默认加上where deletedAt is not null; 加上Unscoped()就不加上判断deleted的字段
模型创建
Model方法用于创建基于数据表的Model对象
g.DB().Model("user") // g代表一个DAO对象
由于链式操作的每一个方法会对Model属性进行修改,造成Model对象不能重复使用
user := g.Model("user")
user.Where("status", g.Slice{1,2,3})
if vip {
// 查询条件自动叠加,修改当前模型对象
user.Where("money>=?", 1000000)
} else {
// 查询条件自动叠加,修改当前模型对象
user.Where("money<?", 1000000)
}
// vip: SELECT * FROM user WHERE status IN(1,2,3) AND money >= 1000000
// !vip: SELECT * FROM user WHERE status IN(1,2,3) AND money < 1000000
r, err := user.All()
// vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money >= 1000000
// !vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money < 1000000
n, err := user.Count()
所以通过Safe方法对每一个链式操作都返回一个新的Model操作,该Model对象可以重复使用。(项目中就用的这种方式)
// 定义一个用户模型单例
user := g.Model("user").Safe()
m := user.Where("status", g.Slice{1,2,3})
if vip {
// 查询条件通过赋值叠加
m = m.And("money>=?", 1000000)
} else {
// 查询条件通过赋值叠加
m = m.And("money<?", 1000000)
}
// vip: SELECT * FROM user WHERE status IN(1,2,3) AND money >= 1000000
// !vip: SELECT * FROM user WHERE status IN(1,2,3) AND money < 1000000
r, err := m.All()
// vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money >= 1000000
// !vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money < 1000000
n, err := m.Count()
事务处理
用闭包方式来使用事务
func (db DB) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error)
func (dao *UserViewMenuDao) Transaction(ctx context.Context, f func(ctx context.Context, tx *gdb.TX) error) (err error) {
return dao.Ctx(ctx).Transaction(ctx, f)
}
是一种事务使用方式吗??(没看到哪个方法使用到了),反正只在web项目中看到只有2个方法显示用到了事务.Transaction
嵌套事务????
微服务
每个功能抽成服务,服务间通过rpc来访问
rpc示例:
服务端,注册服务
package main
import (
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
type Params struct {
Width, Height int
}
type Rect struct {
}
func (r *Rect) Area(p Params, ret *int) error {
*ret = p.Width * p.Height
return nil
}
func (r *Rect) Perimeter(p Params, ret *int) error {
*ret = (p.Height + p.Width) * 2
return nil
}
func main() {
rpc.Register(new(Rect))
lis, err := net.Listen("tcp", ":8080")
if err != nil {
log.Panicln(err)
}
for {
conn, err := lis.Accept()
if err != nil {
continue
}
go func(conn net.Conn) {
fmt.Println("new client")
jsonrpc.ServeConn(conn)
}(conn)
}
}
客户端,调用服务
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
)
type Params struct {
Width, Height int
}
func main() {
conn, err := jsonrpc.Dial("tcp", ":8080")
if err != nil {
log.Panicln(err)
}
ret := 0
err2 := conn.Call("Rect.Area", Params{50, 100}, &ret)
if err2 != nil {
log.Panicln(err2)
}
fmt.Println("面积:", ret)
err3 := conn.Call("Rect.Perimeter", Params{50, 100}, &ret)
if err3 != nil {
log.Panicln(err3)
}
fmt.Println("周长:", ret)
}
其他常识
go mod tidy后go.mod还是标红,用go clean --modcache解决;
go.mod不标红,但是代码中有标红,清ide缓存