GO微服务实战 - 02用户注册

1,134 阅读4分钟

用户注册

获取数据/绑定数据

  • 前端传递数据种类:
    1. form表单:数据为 form data

    2. ajax: 数据为 json 格式。 体现成 —— Request Payload

    Screen Shot 2021-07-11 at 2.40.50 PM.png
  • 默认 postForm() 方法 只能获取 form 表单传递的数据。
  • 针对 Request Payload 数据形式,需要 使用 “数据绑定“ 来获取传递的数据。
    • ctx.Bind() 将 数据绑定到对象中。
-- web/main.go 中 添加

r1.POST("/users", controller.PostRet)

-- web/controller/user.go 中添加、实现 
// 发送注册信息
func PostRet(ctx *gin.Context) {
	/*	mobile := ctx.PostForm("mobile")
		pwd := ctx.PostForm("password")
		sms_code := ctx.PostForm("sms_code")
		fmt.Println("m = ", mobile, "pwd = ", pwd, "sms_code = ",sms_code)
	*/
	// 获取数据
	var regData struct {
		Mobile   string `json:"mobile"`
		PassWord string `json:"password"`
		SmsCode  string `json:"sms_code"`
	}
	ctx.Bind(&regData)
	fmt.Println("获取到的数据为:", regData)
}

微服务端

  1. 修改密码本 —— proto 文件

    syntax = "proto3";
    
    package go.micro.srv.user;
    
    service User {
            rpc SendSms(Request) returns (Response) {};
            rpc Register(RegRequest) returns (Response) {};
    }
    
    message Request {
            string phone = 1;
            string imgCode = 2;
            string uuid = 3;
    
    }
    
    message RegRequest {
            string mobile = 1;
            string password = 2;
            string smsCode = 3;
    }
    
    message Response {
            string errno = 1;
            string errmsg = 2;
    }
    
    
  2. make 编译生成 xxx.micro.go 文件

  3. 修改 handler/user.go

    // 添加 方法
    func (e *User) Register(ctx context.Context, req *user.RegRequest, rsp *user.Response) error {
            return nil
    }
    
  4. 需要操作MySQL数据库,拷贝 web/model/model.go 到微服务项目中。

  5. 在 service/user/model/modelFunc.go 中添加校验短信验证码函数实现

    // CheckSmsCode 校验短信验证码
    func CheckSmsCode(mobile, smsCode string) bool {
            // 连接redis
            conn := RedisPool.Get()
            defer conn.Close()
            // 查询数据
            code, err := redis.String(conn.Do("get", mobile+"_code"))
            if err != nil {
                    fmt.Println("redis查询错误:", err)
                    return false
            }
            return smsCode == code
    }
    
  6. service/user/model/modelFunc.go 中, 添加函数 SaveUserInfo,实现用户注册信息,写入MySQL数据库

    // SaveUserInfo 存储用户信息到user表
    func SaveUserInfo(mobile, password string) error {
            var newUser User
            // 如果手机号不存在,则注册新用户
            if GlobalConn.Where("mobile = ?", mobile).Find(&newUser).RecordNotFound() {
                    newUser.Name = mobile
                    newUser.Mobile = mobile
                    // 使用md5对password加密
                    m5 := md5.New()
                    m5.Write([]byte(password))
                    pwdHash := hex.EncodeToString(m5.Sum(nil))
                    newUser.Password_hash = pwdHash
                    return GlobalConn.Create(&newUser).Error
            }
            // 如果手机号已存在,则注册失败
            return errors.New("手机号已存在")
    }
    
  7. 完成 Register 函数实现

    // Register 用户注册
    func (e *User) Register(ctx context.Context, req *user.RegRequest, rsp *user.Response) error {
            // 校验验证码 -- 从redis中获取验证码,进行比对
            result := model.CheckSmsCode(req.Mobile, req.SmsCode)
            // 由于获取验证码服务有问题,这里指定result为true来测试
            result = true
            if result { // 验证码比对成功
                    // 存储用户信息到mysql数据库
                    err := model.SaveUserInfo(req.Mobile, req.Password)
                    if err != nil {
                            fmt.Println("存储用户信息错误:", err)
                            rsp.Errno = utils.RECODE_DBERR
                            rsp.Errmsg = utils.RecodeText(utils.RECODE_DBERR)
                    } else {
                            rsp.Errno = utils.RECODE_OK
                            rsp.Errmsg = utils.RecodeText(utils.RECODE_OK)
                    }
            } else { // 验证码比对失败
                    fmt.Println("验证码比对错误")
                    rsp.Errno = utils.RECODE_DATAERR
                    rsp.Errmsg = utils.RecodeText(utils.RECODE_DATAERR)
            }
            return nil
    }
    

Web端

  1. 拷贝密码本 ——proto

  2. 创建 web/utils/utils.go 文件,封装函数实现初始 consul 客户端代码

    // 初始化micro
    func InitMicro() micro.Service {
    	// 初始化客户端
    	consulReg := consul.NewRegistry()
    	return micro.NewService(
    		micro.Registry(consulReg),
    	)
    }
    
  3. 实现 web/controller/user.go 中的 PostRet 函数

    // PostRet 发送注册信息
    func PostRet(ctx *gin.Context) {
            // 获取数据
            var regData struct {
                    Mobile string `json:"mobile"`
                    Password string `json:"password"`
                    SmsCode string `json:"sms_code"`
            }
            ctx.Bind(&regData)
    
            // 调用远程函数
            microClient := userMicro.NewUserService("go.micro.srv.user", client.DefaultClient)
            resp, err := microClient.Register(context.TODO(),
                    &userMicro.RegRequest{Mobile: regData.Mobile, Password: regData.Password, SmsCode: regData.SmsCode})
            if err != nil {
                    fmt.Println("调用远程函数Register失败:", err)
                    return
            }
    
            // 发送给浏览器
            ctx.JSON(http.StatusOK, resp)
    }
    

获取地域信息

接口定义

#Request:
method: GET
url:api/v1.0/areas
#data:
no input data
#Response
#返回成功:
{
    "errno": "0",
    "errmsg":"OK",
    "data": [
    {"aid": 1, "aname": "东城区"}, 
    {"aid": 2, "aname": "西城区"}, 
    {"aid": 3, "aname": "通州区"}, 
    {"aid": 4, "aname": "顺义区"}] 
    //...
}
#注册失败:
{
    "errno": "400x",   //状态码
    "errmsg":"状态错误信息"
}

业务流程图

Screen Shot 2021-07-12 at 10.15.38 PM.png

导入sql脚本

  1. 将 home.sql 保存至 Linux 系统
  2. 登录MySQL数据库。选择数据库: use search_house;
  3. 运行脚本文件,向表插入数据。source /Users/yaxuan/home.sql

Web端

  1. 在 web/main.go 中,添加路由,设置回调。

    r1.GET("/areas", controller.GetArea)
    
  2. 在 web/controller/use.go 中,添加 GetArea() 函数。

    func GetArea(ctx *gin.Context)  {
    }
    
  3. 从数据库获取数据,提高用户感受的常见方法:先查缓存,缓存没有查MySQL,写入redis缓存。

    // 获取地域信息
    func GetArea(ctx *gin.Context)  {
    // 先从MySQL中获取数据.
    var areas []model.Area
    model.GlobalConn.Find(&areas)
    
    // 再把数据写入到 redis 中.
    conn := model.RedisPool.Get()
    conn.Do("set", "areaData", areas) //这里直接将areas数组存入redis
    
    resp := make(map[string]interface{})
    resp["errno"] = "0"
    resp["errmsg"] = utils.RecodeText(utils.RECODE_OK)
    resp["data"] = areas
    
    ctx.JSON(http.StatusOK, resp)
    }
    

    思考:按如上方法存储数据到 Redis 中 conn.Do("set", "areaData", areas), 将来使用 Do 获取数据时!不好获取!没有对应的回复助手函数来完成“类型断言”。 —— 重新选择存储 redis 的方法: 将数据转换成 josn 字节流存储。

    // GetArea 获取地域信息
    func GetArea(ctx *gin.Context) {
            // 先从redis获取数据
            conn := model.RedisPool.Get()
            areaData, _ := redis.Bytes(conn.Do("get", "areaData"))
            var areas []model.Area
            if len(areaData) == 0 { // redis中没有数据
                    // 从mysql中获取数据
                    model.GlobalConn.Find(&areas)
                    // 将数据写入redis -- 存储结构体序列化后的json串
                    areaBuf, _ := json.Marshal(areas)
                    conn.Do("set", "areaData", areaBuf)
            } else { // redis中有数据
                    json.Unmarshal(areaData, &areas)
            }
    
            resp := make(map[string]interface{})
            resp["errno"] = utils.RECODE_OK
            resp["errmsg"] = utils.RecodeText(utils.RECODE_OK)
            resp["data"] = areas
    
            ctx.JSON(http.StatusOK, resp)
    }