golang 用户管理系统
最近开始接触golang,写了一个简单的用户管理系统练手:
- webserver 基于gin开发
- tcpserver 基于grpc
- redis / db
支持登入、登出;上传头像、修改昵称;尽量编写了足够的单元测试,并且代码都通过golint和go vet的检查。
项目地址在这:Git地址
代码结构说明如下:
|-- conf // yaml格式的http server, tcp server配置;及配置解析代码
|-- doc // 包括设计文档、测试文档
|-- gpool // grpc 连接池
|-- code // 定义的错误码和提示消息
|-- utils // 公共函数
|-- httpserver // http server代码
| |-- static // web相关代码 html/css/js
| |-- upload/images // 头像存储地址
| |-- handler.go // 请求处理handler
| |-- testhandler.go // 供压测和测试用的接口
| `-- main.go // 入口文件
|-- proto // grpc 协议文件
|-- rpcclient // grpc 客户端代码
|-- tcpserver
|-- cache.go // redis
|-- db.go // db 操作
|-- user_dao.go // redis/db 操作交互
|-- userserver.go // grpc server
`-- main.go // 入口文件
主要功能点
- 认证
用户登录时,会生成唯一的sessionid作为token,作为key缓存到redis中。其对应的value是全部的用户信息。这样做除了加快查询外,后续的其他接口的操作都需要用户传递用户名过来(假设用户名是全局唯一的),鉴权的时候不仅校验token,同时也比对用户名,防止token冲突的情况发生。用户登录的时候密码都以md5加密传输过来,然后在db中存储的用户密码是采用“密码加盐”的方式。
当然,这样没有办法避免中间人攻击。更可靠的是采用https传输。同时在登录的时候,前后端通过协商的方式生成秘钥对,进行加解密。
- 日志
日志采用beego/logs,开始的时候考虑用open tracing与grpc结合起来做调用链追踪。后来为了简化起见,只是在请求一到达webserver的时候就生成唯一的请求ID,同时约定日志的第一个字段就是该ID。
然后通过context的metadata传递到grpc server。后者取出这个ID,以同样的方式记录日志。做到单个请求可追溯,同时又不破坏正常请求的传输。
- MySQL
mysql采用gorm,由于考虑到数据量大的情况,可能需要做分表。因此在gorm db操作的时候要注意:
// User gorm user object
type User struct {
ID int32 `gorm:"type:int(11);primary key"`
Username string `gorm:"type:varchar(64);unique;not null"`
Nickname string `gorm:"type:varchar(128)"`
Passwd string `gorm:"type:varchar(32);not null"`
Skey string `gorm:"type:varchar(16);not null"`
Headurl string `gorm:"type:varchar(128);unique;not null"`
Uptime int64 `gorm:"type:datetime"`
}
// TableName gorm use this to get tablename
// NOTE : it only works int where caulse
func (u User) TableName() string {
var value int
for _, c := range []rune(u.Username) {
value = value + int(c)
}
return fmt.Sprintf("userinfo_tab_%d", value % 20)
}
在查询的时候也可以指定tab名字:
db.Table(user.TableName()).Where(&User{Username:username}).First(&quser)
配置文件采用yaml的方式,redis则使用go-redis
- grpc连接池
grpc虽然支持http2.0多路复用,但是并发高的时候,还是需要连接池来做连接复用。
之前打算用sync.Pool来做连接池,后来有看到说sync.Pool主要是作为对象池,用来做连接池不太合适。于是边用chan 封装了一个简单的连接池。