功能和背景介绍

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

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

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

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

用户数据结构体定义

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

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,Cache-Control,Content-Language,Content-Type,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请求。如下图所示:

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

  1. 01 . Go之Gin+Vue开发一个线上外卖应用

    项目介绍 我们将开始使用Gin框架开发一个api项目,我们起名为:云餐厅.如同饿了么,美团外卖等生活服务类应用一样,云餐厅是一个线上的外卖应用,应用的用户可以在线浏览商家,商品并下单. 该项目分为客户 ...

  2. 02 . 02 . Go之Gin+Vue开发一个线上外卖应用(集成第三方发送短信和xorm生成存储数据库表)

    集成第三方发送短信 介绍 用户登录 用户登录有两种方式: 短信登录,密码登录 短信登录是使用手机号和验证码进行登录 短信平台 很多云平台,比如阿里云,腾讯云,七牛云等云厂商,向程序开发者提供了短信验证 ...

  3. 04 . Go+Vue开发一个线上外卖应用(用户名密码和图形验证码)

    图形化验证码生成和验证 功能介绍 在使用用户名和密码登录功能时,需要填写验证码,验证码是以图形化的方式进行获取和展示的. 验证码使用原理 验证码的使用流程和原理为:在服务器端负责生成图形化验证码,并以 ...

  4. 05 . Go+Vue开发一个线上外卖应用(Session集成及修改用户头像到Fastdfs)

    用户头像上传 功能介绍 在用户中心中,允许用户更换自己的头像.因此,我们开发上传一张图片到服务器,并保存成为用户的头像. 接口解析 在用户模块的控制器MemberController中,解析头像上传的 ...

  5. 用vue开发一个app(4,一个久等了的文章)H5直播平台登录注册(1)

    我上一篇关于vue的文章和这一篇时间隔了有点久了.最近终于写完了. 因为我一直想写个有点实绩的东西,而不是随便写一个教程一样东西.结合最近在项目中学到的经验和我的一点创意. 首先介绍下这是个什么! H ...

  6. 用Vue开发一个实时性时间转换功能,看这篇文章就够了

    前言 最近有一个说法,如果你看见某个网站的某个功能,你就大概能猜出背后的业务逻辑是怎么样的,以及你能动手开发一个一毛一样的功能,那么你的前端技能算是进阶中高级水平了.比如咱们今天要聊的这个话题:如何用 ...

  7. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! 是否有一个全局视角来查看系统的运行状况? 有什么办法可以监控到JVM的实时运行状态?

    https://alibaba.github.io/arthas/ Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱. 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决 ...

  8. 基于Vue开发的门户网站展示和后台数据管理系统

    基于Vue的前端框架有很多,这几年随着前端技术的官方应用,总有是学不完的前端知识在等着我们,一个人的精力也是有限,不可能一一掌握,不过我们学习很大程度都会靠兴趣驱动,或者目标导向,最终是可以以点破面, ...

  9. 在Web.Config文件中使用configSource,避免动态修改web.config导致asp.net重启(另添加一个Config文件用于管理用户数据)

    原文:在Web.Config文件中使用configSource,避免动态修改web.config导致asp.net重启(另添加一个Config文件用于管理用户数据) 我们都知道,在asp.net中修改 ...

随机推荐

  1. leetcode1558题解【贪心】

    leetcode1558.得到目标数组的最少函数调用次数 题目链接 算法 贪心 时间复杂度O(nlogN),N为数组中最大的那个数. 1.题意就是给定一个函数,该函数有两种功能,一种就是将数组中的所有 ...

  2. 跟我一起学.NetCore之Swagger让前后端不再烦恼及界面自定义

    前言 随着前后端分离开发模式的流行,接口对接.联调成为常事,前端同事会经常问:我需要调哪个接口?这个接口数据格式是啥?条件都传啥? 对于一些紧急接口可能会采取沟通对接,然后补文档,其他的都会回一句:看 ...

  3. Tomcat 8.5中获取客户端真实IP及协议

    获取客户端真实IP ServletRequest接口提供了getRemoteAddr方法用于获取客户端IP,但是当客户端通过代理服务器访问后端服务器的时候,服务器调用getRemoteAddr方法会返 ...

  4. python单元测试框架pytest

    首先祝大家国庆节日快乐,这个假期因为我老婆要考注会,我也跟着天天去图书馆学了几天,学习的感觉还是非常不错的,这是一篇总结. 这篇博客准备讲解一下pytest测试框架,这个框架是当前最流行的python ...

  5. Java知识日常收集整理001Java获取变量的数据类型的实现方法

    一.具体情况区分 对于简单类型变量,是无法直接获得变量类型的:要想获取,必须自定义函数进行返回. 对于包装类型变量,是可以直接获得的,变量名称.getClass().getName(); 二.代码实现 ...

  6. 把python文件打包成可执行文件(win10实验成功)

    总是有人来找我帮看下工单状态,又懒得写页面展示出来,干脆打包成exe文件好啦 打包很简单,难点在于安装pyinstaller这个依赖包,主要是网络问题~ 我也是参考别人的博文,别人的文章写得很详细,我 ...

  7. Oracle 按不同时间分组统计

    1.按年 select to_char(record_date,'yyyy'), sum(col_8) as total_money from table_name where group by to ...

  8. Redis 的完整安装过程

    Windos 版本安装 Redis 官方并不支持 Window 版本,但是微软公司在 Github 上维护了一个 Windows 版本的 Redis 项目,供 Windows 用户下载使用. 下载地址 ...

  9. Mybatis老手复习文档

    Mybatis学习笔记 再次学习Mybatis,日后,有时间会把这个文档更新,改的越来越好,然后,改成新手老手通用的文档 1.我的认识 Mybatis 是一个持久层框架,(之前 我虽然学了这个myba ...

  10. 远程IO

    远程io 远程io ZLAN6842,ZLAN6844是8路远程O控制器.含有8路DI.8路DO,8路AI输入.其中DI支持干节点和湿节点,带光耦隔离:DO为继电器输出,具有5A 250VAC或5A ...