我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!
需求的提出
大家是不是有数据库加密的需求,但是又不希望在业务代码中强编码实现数据的加密和解密?
使用java的同事可以使用mybatis的拦截器快速实现数据的加密和解密,那么golang有没有这么便捷的方法呢?
恭喜你,gorm的钩子也可以轻易实现数据的加密存储
GORM的钩子
钩子是在创建、查询、更新、删除等操作之前、之后调用的函数。
如果您已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误,GORM 将停止后续的操作并回滚事务。
注意 在 GORM 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果您的钩子返回了任何错误,则修改将被回滚。
1. 插入数据可以使用的勾子
```
// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务
```
复制代码
2. 更新数据可以使用的勾子
```
// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务
```
复制代码
3. 删除数据可以使用的勾子
```
// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务
```
复制代码
4. 查询数据时可以使用的勾子
```
// 从 db 中加载数据
// Preloading (eager loading)
AfterFind
```
复制代码
代码演示
下面演示的代码是我的一个日记应用,主要是加密了用户的日记信息
// 日志对象定义
import (
"encoding/base64"
"errors"
"fmt"
"sms/service/src/utils"
"time"
"github.com/jinzhu/gorm"
)
type UserDiary struct {
Id int64 `json:"id" ` //id serial primary key,
Userid int64 `json:"userid" ` //userid integer not null ,
Year int64 `json:"year" ` //year integer not null ,
Month int64 `json:"month" ` //month integer not null ,
Day int64 `json:"day" ` //day integer not null ,
CreateTime time.Time `json:"create_time" ` //create_time timestamp default CURRENT_TIMESTAMP ,
UpdateTime time.Time `json:"update_time" ` //update_time timestamp default CURRENT_TIMESTAMP,
Status int64 `json:"status" ` //status int not null default 0,
Color string `json:"color" ` //color varchar(32) not null default 'orange',
Diary string `json:"diary" enc:"aes"` //diary text
}
//保存前加密数据
func (diary *UserDiary) BeforeSave(db *gorm.DB) (err error) {
utils.Log.Infof("Call UserDiary BeforeSave [%s]", diary.Diary)
if diary.Diary != "" {
keyname := fmt.Sprintf("user_code_%v", diary.Userid)
key, ok := utils.GetCache(keyname) //每个用户都有自己独立的密钥,这里的缓存密钥来自用户登陆时
if !ok {
utils.Log.Errorf("GetCache(%s) failed!", keyname)
return errors.New("获取用户密钥code失败!")
}
rs, err := utils.AesEcrypt([]byte(diary.Diary), []byte(utils.MD5(key.(string))))
if err != nil {
utils.Log.Errorf("AesEcrypt(%s) [%d] failed! %v", diary.Diary, diary.Userid, err)
return err
}
diary.Diary = base64.StdEncoding.EncodeToString(rs)
}
return
}
//返回前解密数据
func (diary *UserDiary) AfterFind(tx *gorm.DB) (err error) {
utils.Log.Info("Call UserDiary AfterFind")
if diary.Diary != "" {
b, err := base64.StdEncoding.DecodeString(diary.Diary)
if err == nil {
keyname := fmt.Sprintf("user_code_%v", diary.Userid)
key, ok := utils.GetCache(keyname)
if !ok {
utils.Log.Errorf("GetCache(%s) failed!", keyname)
return errors.New("获取用户密钥code失败!")
}
rs, err := utils.AesDeCrypt(b, []byte(utils.MD5(key.(string))))
if err == nil {
diary.Diary = string(rs)
} else {
utils.Log.Errorf("AesDeCrypt(%s) [%d] failed! %v", diary.Diary, diary.Userid, err)
}
} else {
utils.Log.Errorf("DecodeStringB64(%s) failed! %v", diary.Diary, err)
}
}
return
}
复制代码
代码就这么多,没有了!
真的,其他代码就和原来的一样不需要改变,是不是超棒!只要在你的数据模型类上加上加密和解密就可以了,是不超赞!快点使用使用失败吧~
这样就满足了么?
当然不!
这样只是初级的使用罢了,我这里还有更高级的方案哦!
1. 搭配golang的反射使用
注意我上面结构题定义的tag了么?
Diary string `json:"diary" enc:"aes"` //diary text
复制代码
我加了一个enc的tag,这个可以用来干啥呢? 通过反射读取tag,我们可以定义加密算法,或者在数据库外层调用解密,也就可以延迟解密,好处是啥?
你可以在你想要的地方解密,而不是在数据返回时就解密!
下面是反射的代码
func (diary *UserDiary) DoDec() {
t := reflect.TypeOf(diary)
for i := 0; i < t.NumField(); i++ {
tag := t.Field(i).Tag.Get("enc")
if tag != "" {
b, err := base64.StdEncoding.DecodeString(diary.Diary)
if err == nil {
keyname := fmt.Sprintf("user_code_%v", diary.Userid)
key, ok := utils.GetCache(keyname)
if !ok {
utils.Log.Errorf("GetCache(%s) failed!", keyname)
return
}
rs, err := utils.AesDeCrypt(b, []byte(utils.MD5(key.(string))))
if err == nil {
diary.Diary = string(rs)
} else {
utils.Log.Errorf("AesDeCrypt(%s) [%d] failed! %v", diary.Diary, diary.Userid, err)
}
} else {
utils.Log.Errorf("DecodeStringB64(%s) failed! %v", diary.Diary, err)
}
}
}
}
复制代码
2. 有了tag还可以干啥?
- 自定义函数
- 数据鉴权
- 删除数据时自动备份
- 数据脱敏
更多的我就不在这里罗列了
想象空间
其实gorm的勾子和mybatis的拦截器没啥区别,struct的tag也可以和java的注解也没啥区别。借鉴gorm的勾子,我们是不是也可以在golang下搞一波面向切面(AOP)编程。
今天就到这里吧,have a nice weekend!