一、搭建前端服务
从上图可以看到,前端服务响应浏览器的ajax请求,然后把请求转发给api模块执行业务处理。api模块处理完成后返回结果给前端服务,前端服务再向浏览器输出响应信息。
1.1 配置路由
新建web目录,该目录存在前端服务相关的文件。然后在该目录下新建两个文件:defs.go和main.go。
defs.go文件内容如下:
package main
// 该结构体封装了前端服务和api模块之间要传递的数据
type ApiBody struct {
Url string `json:"url"`
Method string `json:"method"`
ReqBody string `json:"req_body"`
}
// 异常处理
type ErrBody struct {
Error string `json:"error"`
ErrorCode string `json:"error_code"`
}
// 定义常见的错误类型
var (
ErrorRequestNotRecognized = ErrBody{"请求方式不正确", "001"}
ErrorRequestBodyParseFailed = ErrBody{"请求体格式不正确", "002"}
ErrorInternalFaults = ErrBody{"服务器内部错误", "003"}
)
main.go文件内容如下:
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/julienschmidt/httprouter"
"io"
"io/ioutil"
"net/http"
)
func main() {
// 创建router对象
router := RegisterHandler()
// 启动服务
http.ListenAndServe(":9000", router)
}
func RegisterHandler() *httprouter.Router {
router := httprouter.New()
router.POST("/api", ApiHandler)
return router
}
func ApiHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
/*// 校验
if r.Method != http.MethodPost {
errBody, _ := json.Marshal(ErrorRequestNotRecognized)
io.WriteString(w, string(errBody))
return
}*/
// 构建ApiBody对象
res, _ := ioutil.ReadAll(r.Body)
apiBody := &ApiBody{}
if err := json.Unmarshal(res, apiBody); err != nil {
errBody, _ := json.Marshal(ErrorRequestBodyParseFailed)
io.WriteString(w, string(errBody))
return
}
//fmt.Println(apiBody)
// 转发请求到api模块
request(apiBody, w, r)
}
var httpClient *http.Client
func init() {
httpClient = &http.Client{}
}
// 前端服务向api模块发送请求
func request(body *ApiBody, writer http.ResponseWriter, request *http.Request) {
switch body.Method {
case http.MethodPost:
// 创建一个新的Request对象
req, _ := http.NewRequest("POST", body.Url, bytes.NewBuffer([]byte(body.ReqBody)))
// 把浏览器发送的请求头设置到转发请求的Header中
req.Header = request.Header
// 转发请求
if res, err := httpClient.Do(req); err != nil {
fmt.Println("转发请求失败,原因:", err)
return
} else {
fmt.Println("正在转发请求...")
normalResponse(writer, res)
}
case http.MethodGet:
// 创建一个新的Request对象
req, _ := http.NewRequest("GET", body.Url, nil)
// 把浏览器发送的请求头设置到转发请求的Header中
req.Header = request.Header
// 转发请求
if res, err := httpClient.Do(req); err != nil {
fmt.Println("转发请求失败,原因:", err)
return
} else {
fmt.Println("正在转发请求...")
normalResponse(writer, res)
}
case http.MethodDelete:
// 创建一个新的Request对象
req, _ := http.NewRequest("DELETE", body.Url, nil)
// 把浏览器发送的请求头设置到转发请求的Header中
req.Header = request.Header
// 转发请求
if res, err := httpClient.Do(req); err != nil {
fmt.Println("转发请求失败,原因:", err)
return
} else {
fmt.Println("正在转发请求...")
normalResponse(writer, res)
}
}
}
// 读取api模块返回给前端服务的响应
func normalResponse(w http.ResponseWriter, response *http.Response) {
// 读取响应体内容
res, err := ioutil.ReadAll(response.Body)
// 处理异常情况
if err != nil {
errMsg, _ := json.Marshal(ErrorInternalFaults)
w.WriteHeader(500)
io.WriteString(w, string(errMsg))
return
}
// 如果没有发生异常,则向浏览器发送响应结果
w.WriteHeader(response.StatusCode)
io.WriteString(w, string(res))
}
分别启动api和web模块,然后在postman工具中进行测试,测试效果如下图所示:
1.2 导入页面
资源下载:https://pan.baidu.com/s/1lOlhBIExrE-ieGG9ZSd6JQ
下载完成后,将templates目录下文件复制到项目的templates目录中即可。
在路由上配置页面的访问路径:
func main() {
// 创建router对象
router := RegisterHandler()
// 配置静态文件的访问路径
router.ServeFiles("/statics/*filepath", http.Dir("./templates"))
...
}
启动web服务,页面效果如下图所示:
二、校验用户名
2.1 前端分析
打开home.js文件,查找validateUser方法,该方法实现如下所示:
// Async ajax methods
function validateUser(callback) {
var username = $("#username").val();
var reqBody = {
'user_name': username,
}
var dat = {
'url': 'http://' + window.location.hostname + ':8000/validateuser/'+username,
'method': 'POST',
'req_body': JSON.stringify(reqBody)
};
$.ajax({
url: 'http://' + window.location.hostname + ':8080/api',
type: 'post',
data: JSON.stringify(dat),
statusCode: {
500: function () {
callback(null, "internal error");
}
},
complete: function (xhr, textStatus) {
if (xhr.status >= 400) {
callback(null, "Error of Signin");
return;
}
}
}).done(function (data, statusText, xhr) {
if (xhr.status >= 400) {
callback(null, "Error of register");
return;
}
callback(data, null);
});
}
通过分析可以得到的信息:
- 请求url:http://localhost:8080/api
- 请求方式:POST
- 请求参数:{
‘url’: ‘http://’ + window.location.hostname + ‘:8000/validateuser/’ + username,
‘method’: ‘POST’,
‘req_body’: JSON.stringify(reqBody)
}
其中,req_body中包含user_name参数。 - 响应状态:500代表服务器内部错误,>= 400 代表登录失败。
2.2 后台实现
修改api目录下的main.go文件,配置用户名校验的路由地址。
router.POST("/validateuser/:user_name", ValidateUserHandler)
定义ValidateUserHandler方法,用于对用户名进行校验。
// 校验用户名是否重复
func ValidateUserHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
name := p.ByName("user_name")
isOk, err := dbops.CheckUserName(name)
if err != nil {
sendErrorResponse(w, defs.ErrorDbError)
return
}
if (!isOk) {
sendErrorResponse(w, defs.ErrorUserNameDup)
return
}
sendNormalResponse(w, "", 201)
}
修改errs.go文件,增加用户名重复的错误类别。
var (
...
// 用户名重复
ErrorUserNameDup = ErrResponse{405, Err{"用户名已经存在", "005"}}
)
三、用户注册
3.1 测试用户注册
输入用户名和密码后,点击Register按钮提交注册。如果注册成功,浏览器或跳转到http://localhost:8080/userhome地址,并且把注册用户名和session存储到cookie中。
3.2 跳转用户播放视频页面
修改web模块的main.go文件,配置播放视频页面的路由地址。
router.POST("/userhome", UserHomeHandler)
router.GET("/userhome", UserHomeHandler)
定义UserHomeHandler方法,该方法从cookie中获取用户名,然后重定向到userhome.html页面上。
// 该结构体用于封装用户名
type UserPage struct {
Name string
}
// 处理加载视频播放页的请求
func UserHomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
c1, err1 := r.Cookie("username")
_, err2 := r.Cookie("session")
if err1 != nil || err2 != nil {
// 重定向到登录页
http.Redirect(w, r,"/", http.StatusFound)
return
}
var userPage *UserPage
// 如果cookie中没有username参数,则把它封装到UserPage结构体中
// 如果cookie中没有username参数,则从表单的username字段中获取该参数,并封装到UserPage结构体中
if len(c1.Value) != 0 {
userPage = &UserPage{c1.Value}
} else {
// 获取username请求参数
uname := r.FormValue("username")
if len(uname) != 0 {
userPage = &UserPage{uname}
}
}
// 创建userhome.html模版
t, err := template.ParseFiles("./templates/userhome.html")
if err != nil {
fmt.Println("解析./templates/userhome.html文件时发生异常")
return
}
// 把模版内容输出到ResponseWriter中
t.Execute(w, userPage)
}
运行效果:
3.3 配置默认路由
第一步:修改RegisterHandler函数,添加以下配置;
router.POST("/", HomeHandler)
router.GET("/", HomeHandler)
第二步:定义HomeHandler方法;
func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// 判断用户是否登录,如果已登录,则直接跳转到视频播放页面;如果没有登录,则跳转到登录页面
c1, err1 := r.Cookie("username")
c2, err2 := r.Cookie("session")
if err1 != nil || err2 != nil {
gotoHome(w)
return
}
// 如果访问过程中,发现cookie中存储了username或session的信息
// 则直接跳转到userhome.html页面
if len(c1.Value) != 0 && len(c2.Value) != 0 {
// 重定向到登录页
http.Redirect(w, r,"/userhome", http.StatusFound)
} else {
gotoHome(w)
}
}
// 跳转到登录页
func gotoHome(w http.ResponseWriter) {
userPage := &UserPage{"呵呵"} // 填充到页面的{{name}}标签中
w.Header().Set("Content-Type", "text/html")
t, err := template.ParseFiles("./templates/home.html")
if err != nil {
fmt.Println("解析模版./template/home.html出错!")
return
}
t.Execute(w, userPage)
}
四、测试用户登录
打开home.js文件,查找signinUser方法,该方法实现如下所示:
//用户登录
function signinUser(callback) {
var username = $("#susername").val();
var pwd = $("#spwd").val();
console.log("username=", username)
console.log("pwd=", pwd)
var apiUrl = window.location.hostname + ':8080/api';
if (username == '' || pwd == '') {
callback(null, err);
}
var reqBody = {
'user_name': username,
'pwd': pwd
}
var dat = {
'url': 'http://' + window.location.hostname + ':8000/user/' + username,
'method': 'POST',
'req_body': JSON.stringify(reqBody)
};
$.ajax({
url: 'http://' + window.location.hostname + ':8080/api',
type: 'post',
data: JSON.stringify(dat),
//如果响应码是500,则会调用500匹配的方法
statusCode: {
500: function () {
callback(null, "Internal error");
}
},
//请求完成(无论成功与否),xhr中包含了请求响应码,text
complete: function (xhr, textStatus) {
console.log("complete================")
if (xhr.status >= 400) {
callback(null, "Error of Signin");
return;
}
}
}).done(function (data, statusText, xhr) {
//请求成功调用的方法
console.log("done data = ", data,);
console.log("done statusText = ", statusText,);
console.log("done xhr = ", xhr.status,);
if (xhr.status >= 400) {
callback(null, "Error of Signin");
return;
}
uname = username;
//请求成功的数据,在data中
callback(data, null);
});
}
通过分析可以得到的信息:
- 请求url:http://localhost:8080/api
- 请求方式:POST
- 请求参数:url、method、req_body,其中req_body包含了user_name和pwd两个参数,它们分别代表用户名和密码;
- 响应状态:500代表服务器内部错误,> 400 代表登录失败。
测试登录:
点击登录按钮后,如果登录成功,可以看到cookie中存储了session和username的值。