基于go-micro微服务的实战-实现用户服务的注册和登录(四)
文章最后附带完整代码
基于go-micro实现用户服务实的注册和登录
工程目录上,每个往后的小节都是基于前一个小节的基础,环环相扣。所以这节就是基于第3节的工程目录扩展。
这节主要就是如何去实现用户的注册和登录,依赖的第三方库的相关使用以及业务逻辑。
第三方库
gorm.io/gorm
github.com/astaxie/beego/validation
第一步:编写proto文件,新增grpc接口,编译生成
在user.proto
上继续新增接口和对应参数内容
service UserService{
rpc UserReg(RegReq) returns(RegResp){}
rpc UserLogin(LoginReq) returns(LoginResp){}
}
message RegReq{
string name = 1;
string phone =2;
string email =3;
string pwd = 4;
string confir_pwd = 5;
}
message RegResp{
int32 status = 1;
string msg = 2;
}
message LoginReq{
string phone =1;
string pwd = 2;
}
message LoginResp{
int32 status = 1;
string user_id = 2;
string msg = 3;
}
执行pb.bat或者pb.sh快速编译生成,grpc_gateway和grpc_user的proto都要同步添加和执行
第二步:新增user数据表和对应结构体定义
创建用户表,sql语句在user.sql
CREATE TABLE `user` (
`user_id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户id',
`name` varchar(50) NOT NULL COMMENT '名字',
`phone` varchar(50) NOT NULL COMMENT '手机号',
`pwd` varchar(50) NOT NULL COMMENT '密码',
`reg_time` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '注册时间',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8 COMMENT='用户表';
user结构体定义,在models下新建user_model.go。其中的tag标识中,
gorm:"column:user_id;primaryKey"
,代表指定user_id为主键,并且User.Id对应的是user_id字段。gorm:"-"
,代表写入db时候忽略该字段,gorm的语法规则valid:"Required;MinSize(4);MaxSize(12)"
,valid标识的则是使用beego的validtion验证规则。
//用户
type User struct {
Id int32 `gorm:"column:user_id;primaryKey"`
Name string `valid:"Required;MinSize(4);MaxSize(12)"`
Phone string `valid:"Mobile;Required"`
Email string `valid:"Email;MaxSize(50)"`
Pwd string `valid:"Required;MinSize(4);MaxSize(6)"`
ConfirePwd string `gorm:"-" valid:"Required;MinSize(4);MaxSize(6)"`
RegTime int64
}
第三步:用户服务的逻辑处理器实现新增的注册和登录接口
注册接口
func (u *UserHandler) UserReg(ctx context.Context, req *pb.RegReq, resp *pb.RegResp) error
请求数据的基础验证,使用github.com/astaxie/beego/validation
验证非空,手机号,邮箱格式等等
user := models.User{
Name: req.Name,
Phone: req.Phone,
Email: req.Email,
Pwd:req.Pwd,
ConfirePwd:req.ConfirPwd,
RegTime: time.Now().Unix(),
}
log.Printf("UserReg Params:%#v\n", user)
//验证规则
valid := validation.Validation{}
b, err := valid.Valid(&user)
if err !=nil{
return err
}
if !b {
for _, err := range valid.Errors {
log.Println(err.Key, err.Message)
resp.Status = common.RESP_ERROR
resp.Msg = err.Message
return nil
}
}
手机号和邮箱的唯一性判断
//手机号是否未被注册
var count int64
models.Db.Table("user").Where("phone = ?", req.Phone).Count(&count)
if count > 0 {
resp.Status = 2
resp.Msg = "phone is exists"
return nil
}
//邮箱是否未被注册
models.Db.Table("user").Where("email = ?", req.Email).Count(&count)
if count > 0 {
resp.Status = 2
resp.Msg = "email is exists"
return nil
}
注册信息入库
//写进数据库
user.Pwd = utils.Md5(user.Pwd)
result := models.Db.Create(&user)
if result.Error != nil{
return result.Error
}
响应信息
resp.Status = common.RESP_SUCCESS
resp.Msg = "success"
登录接口
func (u *UserHandler) UserLogin(ctx context.Context, req *pb.LoginReq, resp *pb.LoginResp) error
请求数据的基础验证,使用github.com/astaxie/beego/validation
验证手机号和密码的正确性
valid := validation.Validation{}
valid.Required(req.Phone, "phone")
valid.Phone(req.Phone, "phone")
valid.Required(req.Pwd, "pwd")
valid.MinSize(req.Pwd, 4, "pwd")
valid.MaxSize(req.Pwd, 6, "pwd")
if valid.HasErrors() {
for _, err := range valid.Errors {
log.Println(err.Key, err.Message)
resp.Status = common.RESP_ERROR
resp.Msg = err.Message
return nil
}
}
通过手机号和数据库的密码校验
//获取数据库信息
type result struct {
UserId int32
Pwd string
}
var res result
models.Db.Table("user").Select("user.user_id, user.pwd").Where("user.phone = ?", req.Phone).Scan(&res)
if utils.Md5(req.Pwd) != res.Pwd {
resp.Status = common.RESP_ERROR
resp.Msg = "auth error"
return nil
}
响应信息,返回用户id
resp.Status = common.RESP_SUCCESS
resp.UserId = res.UserId
resp.Msg = "success"
第四步:注册网关Gateway的路由
在/grpc_gateway/router/router.go
新增注册和登录路由。具体路由基础规则可看第3节
//注册路由
router.POST("/user/reg", handler.RoleReg)
//登录路由
router.POST("/user/login", handler.RoleLogin)
第五步:网关层注册和登录Http Api的请求处理
在/grpc_gateway/handler/user_handler.go
新增注册和登录处理
注册接口
func RoleReg(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
//解析参数
var request map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
log.Println("RoleReq ParseForm err:", err)
http.Error(w, err.Error(), 500)
return
}
log.Println(request)
service := pb.NewUserService("emicro.user", common.Service.Client())
resp, err := service.UserReg(context.Background(), &pb.RoleReg{
Name: utils.GetString(request["name"]),
Phone:utils.GetString(request["phone"]),
Email: utils.GetString(request["email"]),
Pwd: utils.GetString(request["pwd"]),
ConfirPwd: utils.GetString(request["confire_pwd"]),
})
if err !=nil{
log.Println("RoleReq err:", err)
http.Error(w, err.Error(), 500)
return
}
log.Println(resp.Status, resp.Msg)
res := fmt.Sprintf("status:%d,msg:%v", resp.Status, resp.Msg)
w.Write([]byte(res))
}
登录接口
func RoleLogin(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
//解析参数
var request map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
log.Println("RoleLogin ParseForm err:", err)
http.Error(w, err.Error(), 500)
return
}
log.Println(request)
service := pb.NewUserService("emicro.user", common.Service.Client())
resp, err := service.UserLogin(context.Background(), &pb.LoginReq{
Phone:utils.GetString(request["phone"]),
Pwd: utils.GetString(request["pwd"]),
})
if err !=nil{
log.Println("RoleLogin err:", err)
http.Error(w, err.Error(), 500)
return
}
log.Println(resp.Status, resp.UserId, resp.Msg)
res := fmt.Sprintf("status:%d,user_id:%d,msg:%v", resp.Status, resp.UserId, resp.Msg)
w.Write([]byte(res))
}
第六步:启动网关服务和用户服务,采用Apipost测试
启动服务基于前面几节细讲,执行gateway.bat
或者gateway.sh
启动网关服务,执行user.bat
或者user.sh
启动用户服务
Apipost请求效果如图:
注册成功效果:
注册失败效果,邮件格式不正确:
登录成功效果:
登录失败效果,密码不正确: