课程:
Vlog原型系统开发:https://www.imooc.com/learn/1131
1、环境初始化
需求:
在本地非`$GOPATH/src`位置、建立一个不发布的项目。
a.ide编译运行
mkdir /home/wwwroot/golang/wx_shop
cd /home/wwwroot/golang/wx_shop
go mod init wx_shop
//本机(deepin虚拟机)环境,pyenv已安装:
在goland内置命令行go env查看时,末尾出现“bash: pyenv: 未找到命令”,所以重新检查相关python环境。
vim ~/.bashrc
export GOPATH="/home/gowork"
export PATH="$PATH:$GOPATH/bin"
export GO111MODULE="on"
#export GOPROXY="https://mirrors.aliyun.com/goproxy/"
export GOPROXY=https://goproxy.io
source ~/.bashrc //在goland的命令行也执行一下!!(重启ide没用)
b.web请求与文件请求
package main
import "net/http"
//http框架原理梳理
//1.输出hello world
func sayHello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world!\n"))
}
func main() {
//2.注册路由处理函数
http.HandleFunc("/sayHello", sayHello)
//curl http://127.0.0.1:8081/sayHello
//2.1文件浏览请求
fileHandler := http.FileServer(http.Dir("./video"))
http.Handle("/video/", http.StripPrefix("/video/", fileHandler))
//浏览器 http://127.0.0.1:8081/video/test.mp4
//3.启动web服务
http.ListenAndServe(":8081",nil)
}
GO标准库:
http://docscn.studygolang.com/pkg/
c.开发思想
敏捷开发:快速完成简化版,功能迭代,逐步完善后期需求。
vlog系统需求分析:
- 视频播放
- 列表浏览
- 文件上传
2、前后端代码
a.后端
由于go编译后直接运行的,所以直接前(8080)、后端(8081)端口分离。如果使用nginx静态转发,可以像php-fpm一样代理处理。以下类库goland自动补全。
package main
import (
"crypto/md5"
json2 "encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
//http框架梳理
//1.输出hello world
func sayHello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world!\n"))
}
//跨域解耦 F(r,w) => C(F){ F(w,r) }
func cors(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 允许访问所有域,可以换成具体url,注意仅具体url才能带cookie信息
w.Header().Set("Access-Control-Allow-Origin", "*")
//header的类型
//w.Header().Add("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
//设置为true,允许ajax异步请求带cookie信息
w.Header().Add("Access-Control-Allow-Credentials", "true")
//允许请求方法
//w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
//返回数据格式是json
//w.Header().Set("content-type", "application/json;charset=UTF-8")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}
f(w, r)
}
}
func main() {
//2.注册路由处理函数
http.HandleFunc("/sayHello", cors(sayHello))
//curl http://127.0.0.1:8081/sayHello
//2.1文件浏览请求,静态资源服务器
fileHandler := http.FileServer(http.Dir("./video"))
http.Handle("/video/", http.StripPrefix("/video/", fileHandler))
//浏览器 http://127.0.0.1:8081/video/test.mp4
//2.2文件上传请求
http.HandleFunc("/upload", cors(uploadHandler))
//2.3列表信息浏览请求
http.HandleFunc("/api/videoList", cors(fileListHandler))
//3.启动web服务
http.ListenAndServe(":8081",nil)
}
func uploadHandler(w http.ResponseWriter, r *http.Request) {
/** a.限制上传大小为10M
returns a non-EOF error for a Read beyond the limit
*/
r.Body = http.MaxBytesReader(w, r.Body, 10*1024*1024)
err := r.ParseMultipartForm(10*1024*1024)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
//b.获取文件,并检验
file, fileHandler, err := r.FormFile("uploadFile")
defer file.Close()
fmt.Println("上传文件: ", fileHandler.Filename)
ret := strings.HasSuffix(fileHandler.Filename, ".mp4")
if ret == false {
http.Error(w, "file not mp4", http.StatusInternalServerError)
return
}
//c.生成随机名称,并保存
md5Byte := md5.Sum([]byte(fileHandler.Filename + time.Now().String()))
newFilename := fmt.Sprintf("%x", md5Byte) + ".mp4" //md5byte转换为十进制
dst, err := os.Create("./video/" + newFilename)
defer dst.Close()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err := io.Copy(dst, file); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write([]byte(fileHandler.Filename+": "+strconv.FormatInt(fileHandler.Size/1024/1024,10)+"M"))
}
func fileListHandler(w http.ResponseWriter, r *http.Request) {
files, _ := filepath.Glob("video/*")
var vlist []string
for _, file := range files {
fmt.Println(file)
vlist = append(vlist, "http://"+r.Host+"/"+file)
}
fmt.Println(w.Header())
retJson, _ := json2.Marshal(vlist)
w.Write(retJson)
return
}
b.前端
sui:https://sui.ctolog.com/getting-started/
web根目录:
git clone https://gitee.com/zoujingli/sui-mobile.git
mv sui-mobile/* .
搭建基本页面,预览调整,添加卡片、工具栏按钮。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>我的生活</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<link rel="shortcut icon" href="/favicon.ico">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="stylesheet" href=" ./dist/css/sm.min.css">
<link rel="stylesheet" href=" ./dist/css/sm-extend.min.css">
<script src='//cdn.bootcss.com/jquery/3.1.1/jquery.min.js'></script>
<script src=' ./dist/js/sm.js'></script>
<script src=' ./dist/js/sm-extend.js'></script>
<script type="text/javascript">
var json;
window.onload = function(){
getList();
};
function getList() {
$.getJSON(
"http://192.168.1.111:8081/api/videoList",
null,
function(result) {
var box = document.getElementById('MP4box');
box.innerHTML = "";
json = result
for (var i = 0; i < result.length; i++) {
box.innerHTML += `<div class="card">
<div style="background-image:url(//gqianniu.alicdn.com/bao/uploaded/i4//tfscom/i3/TB10LfcHFXXXXXKXpXXXXXXXXXX_!!0-item_pic.jpg_250x250q60.jpg)" valign="bottom" class="card-header color-white no-border">旅途的山` + (i+1) + `</div>
<div class="card-content-inner">
<p class="color-gray">XX发表于 2015/01/15</p>
<p>
<video controls="controls" preload="auto" width="100%" height="100%">
<source src="` + result[i] + `" type="video/mp4">
</video>
</p>
</div>
<div class="card-footer">
<a href="#" class="link">赞</a>
<a href="#" class="link">更多</a>
</div>
</div>`;
}
}
);
}
function selectFile(){
document.getElementById("upload_btn").click();
}
function uploadAction(){
var file = document.getElementById("upload_btn").files[0];
var formdata = new FormData();
formdata.append("uploadFile", file);
$.ajax({
"url": "http://192.168.1.111:8081/upload",
"type": "post",
"data": formdata,
"processData": false,
"contentType": false,
"success": function(result){
//console.log(result)
alert(result);
}
});
}
</script>
</head>
<body>
<div class="page-group">
<div class="page page-current">
<!-- 你的html代码 -->
<header class="bar bar-nav">
<h1 class="title">我的生活</h1>
</header>
<nav class="bar bar-tab">
<div class="row">
<div class="col-50">
<a href="#" onclick="" class="button button-big button-fill">我的</a>
</div>
<div class="col-50">
<form id="my_upload" enctype="multipart/form-data">
<input type="file" name="uploadFile" id="upload_btn" style="display: none;" onchange="uploadAction()">
</form>
<a onclick="selectFile()" class="button button-big button-fill">上传</a>
</div>
</div>
</nav>
<div class="content" id="MP4box">
<!-- 这里是页面内容区 -->
</div>
</div>
</div>
</div>
</body>
</html>
3、总结
a.使用自定义包
这里使用的是单文件,没有使用模块,自定义模块/包使用如:
import "main/controller"
controller.Max()
b.http服务执行流程
http.handle和http.handleFunc,http.ListenAndServe:
fileHandler := http.FileServer(http.Dir("./video"))
http.Handle("/video/", http.StripPrefix("/video/", fileHandler))
对于静态资源服务器的理解,http.Handle()是ServeMux结构体对象(路由信息)的一个函数,作用是规则匹配,生成muxEntry结构体对象(路由入口信息)保存到ServeMux;
c.语言特点
函数和方法的区别
方法是包含了接收者的函数,方法是有结构体等作为主体的、函数没有:
func methodHaha(x int) int {} //函数只隶属于包
func (s *structA) functionHaha int {} //方法上级有主体,struct,interface
方法接收者是类型和指针的区别
结构体指针操作:
指针-方法:地址;类型-方法:变量。指针或值操作在方法定义时指定,实例对象
方法的调用,既可以使用值,也可以使用指针,自动实例化对象,(&p).这种操作等于p.。
(数据类型即)接口以及type func() 了解
参考《go语言中type的几种使用》:
- type:定义类型
1. type name string //重命名基础类型
2. type person struct{} //定义结构体类型
//定义内嵌类型的结构体类型
type person struct{
string //匿名类型[无关联键值,默认索引]
age int
}
3. type Personer interface { 定义接口类型
name
Run(x int,y string)
}
4. type handler func(name string) int //定义函数类型(指定制参数、返回值类型,见下面http.servers可以无返回)
- http请求的响应处理
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。