课程:
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请求的响应处理

参考《深入理解_Golang_之_http_server


沧浪水
97 声望12 粉丝