流媒体网站开发(三)

一、搭建前端服务

在这里插入图片描述
从上图可以看到,前端服务响应浏览器的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的值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值