加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

03 . Gin+Vue开发一个线上外卖应用(用户数据创建,插入,跨域处理)

发布时间:2020-12-16 09:19:10 所属栏目:大数据 来源:网络整理
导读:功能和背景介绍 在项目的登录功能中,如果在登录时发现用户名和密码在用户表中不存在,会自动将用户名和密码保存在用户表中,创建一个新的用户。 因此,除了使用手机号和验证码登录以外,还支持使用用户名、密码进行登录。 如果首次使用手机号和验证码进行登

功能和背景介绍

在项目的登录功能中,如果在登录时发现用户名和密码在用户表中不存在,会自动将用户名和密码保存在用户表中,创建一个新的用户。

因此,除了使用手机号和验证码登录以外,还支持使用用户名、密码进行登录。

如果首次使用手机号和验证码进行登录,会默认将手机号作为用户名创建新的用户,将用户结构体对象的数据保存在数据库中。

因此,我们有必要创建用户表。

用户数据结构体定义

在项目中,使用结构体定义用户数据结构。结构体定义如下所示:

type Member struct {
	Id           int64   `xorm:"pk autoincr" json:"id"`
	UserName     string  `xorm:"varchar(20)" json:"user_name"`
	Mobile       string  `xorm:"varchar(11)" json:"mobile"`
	Password     string  `xorm:"varchar(255)" json:"password"`
	RegisterTime int64   `xorm:"bigint" json:"register_time"`
	Avatar       string  `xorm:"varchar(255)" json:"avatar"`
	Balance      float64 `xorm:"double" json:"balance"`
	IsActive     int8    `xorm:"tinyint" json:"is_active"`
	City         string  `xorm:"varchar(10)" json:"city"`
}

通过定义Member结构体,表示应用的用户信息。通过TAG中的xorm来指定结构体在数据库表中的约束。

ORM映射

通过engine.Sync2方法将Member同步映射成为数据库中的member表:

err = engine.Sync2(new(model.Member),new(model.SmsCode))
if err != nil {
    return nil,err
}

插入数据

当用户获取完验证码,并填写验证码以后,用户点击登录,会发起登录请求。因此,我们需要来完成登录相关的逻辑操作和处理。用户手机号码和验证码登录的接口是api/login_sms,因此我们在已经创建的MemberController中解析短信验证码接口。如下所示:

func (mc *MemberController) Router(engine *gin.Engine) {
    ...
    //发送手机验证码
	engine.GET("/api/sendcode",mc.sendSmsCode)
	//手机号和短信登录
	engine.OPTIONS("/api/login_sms",mc.smsLogin)
}

在MemberController中创建smsLogin方法完成用户手机号和密码登录的逻辑,详细实现如下:

//短信登录
func (mc *MemberController) smsLogin(context *gin.Context) {

	var smsParam param.SmsLoginParam
	err := toolbox.Decode(context.Request.Body,&smsParam)

	fmt.Println(err.Error())

	fmt.Println(context.PostForm("phone"))
	fmt.Println(context.Query("code"))
	if err != nil {
		toolbox.Failed(context,"参数解析错误")
		return
	}

	us := service.NewMemberService()
	member := us.SmsLogin(smsParam)
	if member != nil {
		toolbox.Success(context,member)
		return
	}
	toolbox.Failed(context,"登录失败")
}

用户服务层

在MemberService.go文件中,编写SmsLogin方法完成手机号和密码登录。

func (msi *MemberService) SmsLogin(param param.SmsLoginParam) *model.Member {

	dao := dao.NewMemberDao()
	sms := dao.ValidateSmsCode(param.Phone,param.Code)

	if sms == nil || time.Now().Unix()-sms.CreateTime > 300 {
		return nil
	}

	member := dao.QueryByPhone(param.Phone)
	if member != nil {
		return member
	}

	user := model.Member{}
	user.UserName = param.Phone
	user.Mobile = param.Phone
	user.RegisterTime = time.Now().Unix()

	user.Id = dao.InsertMember(user)
	return &user
}

在MemberService中,首先验证手机号和验证码是否正确。如果通过了手机号和验证码的验证,通过手机号查询用户是否已经存在。如果用户记录不存在,则创建新的用户记录并保存到数据库中,如果用户记录已经存在,则表示登录成功,返回用户信息。

数据库操作的MemberDao实现如下

在MemberDao中,实现用户模块的数据库操作。
首先是手机验证码验证功能,如下所示:

func (md *MemberDao) ValidateSmsCode(phone string,code string) *model.SmsCode {
	var sms model.SmsCode

	if err := md.Where(" phone = ? and code = ? ",phone,code).Find(&sms); err != nil {
		toolbox.Error(err.Error())
	}
	return &sms
}

其次是根据手机号查询用户数据库表中是否存在手机号对应的用户,如下所示:

func (md *MemberDao) QueryByPhone(phone string) *model.Member {
	var member model.Member

	if err := md.Where(" phone = ? ",phone).Find(&member); err != nil {
		toolbox.Error(err.Error())
	}
	return &member
}

最后,对于新手机号,新建用户,插入到数据库中:

func (md *MemberDao) InsertMember(member model.Member) int64 {
	result,err := md.InsertOne(&member)
	if err != nil {
		toolbox.Error(err.Error())
	}
	return result
}

跨域

我们项目是使用gin开发一个接口项目,前端是使用vue+webpack进行开发和编译运行的。
可以通过如下命令运行为大家提供的前端工程代码,在前端项目的根目录下执行:

npm run dev

在浏览器中访问http://localhost:8080即可进入应用的首页,切换到用户登录界面。

同时后端程序的运行端口是8090。

当使用我们上面两节课已经开发完成的手机号+验证码的方式进行用户登录时。会发现遇到一个问题,如下图所示:

之前我们已经开发完成了手机号+验证码登录的功能,并且使用Postman已经测试成功了,为什么现在在浏览器中会出现这个问题呢?

跨域访问的问题

先了解一下什么是跨域访问。
在浏览器中的任意一个页面地址,或者访问后台的api接口url,其实都包含几个相同的部分:

/*
	* 1、通信协议:又称protocol,有很多通信协议,比如http,					tcp/ip协议等等。
	* 2、主机:也就是常说的host。
	* 3、端口:即服务所监听的端口号。
	* 4、资源路径:端口号后面的内容即是路径。
*/

当在一个页面中发起一个新的请求时,如果通信协议、主机和端口,这三部分内容中的任意一个与原页面的不相同,就被称之为跨域访问。

如,在gin接口项目中,前端使用nodejs开发,运行在8080端口,我们访问的应用首页是:http://localhost:8080。 在使用gin框架开发的api项目中,服务端的监听端口为8090。

一个端口数8080,一个是8090,两者端口不同,因此按照规定,发生了跨域访问。

OPTIONS请求

如上文所述,前端vue开发的功能,使用axios发送POST登录请求。在请求时发生了跨域访问,因此浏览器为了安全起见,会首先发起一个请求测试一下此次访问是否安全,这种测试的请求类型为OPTIONS,又称之为options嗅探,同时在header中会带上origin,用来判断是否有跨域请求权限。

然后服务器相应Access-Control-Allow-Origin的值,该值会与浏览器的origin值进行匹配,如果能够匹配通过,则表示有跨域访问的权限。

跨域访问权限检查通过,会正式发送POST请求。

服务端设置跨域访问

可以在gin服务端,编写程序进行全局设置。通过中间件的方式设置全局跨域访问,用以返回Access-Control-Allow-Origin和浏览器进行匹配。

在服务端编写跨域访问中间件,详细内容如下:

func Cors() gin.HandlerFunc {
	return func(context *gin.Context) {
		method := context.Request.Method
		origin := context.Request.Header.Get("Origin")
		var headerKeys []string
		for k,_ := range context.Request.Header {
			headerKeys = append(headerKeys,k)
		}
		headerStr := strings.Join(headerKeys,",")
		if headerStr != "" {
			headerStr = fmt.Sprintf("access-control-allow-origin,access-control-allow-headers,%s",headerStr)
		} else {
			headerStr = "access-control-allow-origin,access-control-allow-headers"
		}

		if origin != "" {
			context.Writer.Header().Set("Access-Control-Allow-Origin","*")
			context.Header("Access-Control-Allow-Origin","*") // 设置允许访问所有域
			context.Header("Access-Control-Allow-Methods","POST,GET,OPTIONS,PUT,DELETE,UPDATE")
			context.Header("Access-Control-Allow-Headers","Authorization,Content-Length,X-CSRF-Token,Token,session,X_Requested_With,Accept,Origin,Host,Connection,Accept-Encoding,Accept-Language,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Pragma")
			context.Header("Access-Control-Expose-Headers","Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Language,Expires,Last-Modified,Pragma,FooBar")
			context.Header("Access-Control-Max-Age","172800")
			context.Header("Access-Control-Allow-Credentials","false")
			context.Set("content-type","application/json") //// 设置返回格式是json
		}

		if method == "OPTIONS" {
			context.JSON(http.StatusOK,"Options Request!")
		}
		//处理请求
		context.Next()
	}
}

其中的Access-Control-Allow-Origin的设置,表示允许进行跨域访问,*表示可以访问所有域。同时,通过Header方法进行了其他的设置。

最后context.Next()是中间件使用的标准用法,表示继续处理请求。

服务器设置跨域调用

在main函数中,调用编写好的跨域访问。调用如下:

func main(){
    ...
    app := gin.Default()
    app.Use(Cors())
    ...
}

/*
		调用app.Use方法,设置跨域访问
*/
功能演示

服务器设置好跨域访问以后,重新启动服务器api程序,并在浏览器端重新访问。可以看到正常发送了OPTIONS嗅探后,正常发送了POST请求。如下图所示:

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读