当前位置: 首页 > news >正文

GoWeb开发

学习目标:

本篇要达到的目的,能为后续复习提供极大便利。

(当我写下本篇博客时,已复习3遍)

一、网络通信概述

(为本篇基础核心内容)

1、什么是网络通信?

网络通信是指不同设备(如计算机、手机、服务器等)通过计算机网络进行数据交换和信息传递的过程。其核心目标是实现设备之间的互联互通,让数据能够准确、高效地从发送端传输到接收端。

2、网络通信的核心组成部分
  1. 硬件层面

    • 终端设备:发送或接收数据的设备(如手机、电脑、服务器、物联网传感器)。
    • 传输介质:数据传输的物理 / 无线通道,包括:
      • 有线介质:双绞线、同轴电缆、光纤(速度快、稳定性高)。
      • 无线介质:无线电波、微波、蓝牙、Wi-Fi、5G(灵活性高,适合移动场景)。
    • 网络设备:负责数据转发、路由、信号放大等,如路由器、交换机、调制解调器(Modem)、集线器。
  2. 软件层面

    • 通信协议:规定数据格式、传输规则和交互流程的 “语言”(如 TCP/IP、HTTP、FTP)。
    • 操作系统与应用程序:提供网络接口(如 Socket 编程接口),支持上层应用(如浏览器、邮件客户端)实现通信。
3、网络通信的工作原理
  1. 分层模型(以 TCP/IP 为例)
    为简化复杂问题,网络通信采用分层架构,每层负责特定功能,层间通过接口交互。

    • 应用层:直接为用户程序提供服务(如 HTTP 用于网页浏览,SMTP 用于邮件传输)。
    • 传输层:确保端到端的数据传输,主要协议:
      • TCP(面向连接,可靠传输,如网页加载、文件传输)。
      • UDP(无连接,不可靠但高效,如视频流、实时通信)。
    • 网络层:负责网络间的路由和寻址,核心协议是IP(为设备分配 IP 地址,确定数据传输路径)。
    • 数据链路层:在相邻设备间传输数据,处理物理地址(MAC 地址)和错误检测(如以太网协议)。
    • 物理层:定义物理设备的电气、机械特性(如电压、接口标准)。
  2. 数据传输流程

    • 发送端:数据从应用层逐层封装(添加头部信息),最终通过物理层发送。
    • 接收端:数据从物理层逐层解封装(去除头部信息),最终传递给应用层处理。
4、网络通信的主要类型
  1. 按通信对象分类

    • 点对点(Point-to-Point):两台设备直接通信(如蓝牙设备配对)。
    • 点对多点(Point-to-Multipoint):一台设备向多台设备发送数据(如广播、组播)。
    • 端到端(End-to-End):跨越多个网络设备,最终在两个终端间建立逻辑连接(如通过路由器连接的两台远程电脑)。
  2. 按通信方式分类

    • 同步 vs 异步
      • 同步:发送方等待接收方响应(如 TCP 请求 - 响应模式)。
      • 异步:发送方无需等待,直接继续执行(如 UDP 发送数据后不等待确认)。
    • 面向连接 vs 无连接
      • 面向连接:先建立连接(如 TCP 的三次握手),再传输数据(可靠但开销大)。
      • 无连接:直接发送数据(如 UDP,适合实时性要求高的场景)。
5、关键网络协议
  1. 基础协议

    • TCP/IP:互联网的核心协议簇,定义了网络通信的完整架构(包含 IP、TCP、UDP 等)。
    • IP(Internet Protocol):负责设备寻址和路由(IPv4/IPv6)。
    • TCP(Transmission Control Protocol):提供可靠的字节流传输,确保数据无丢失、无乱序。
    • UDP(User Datagram Protocol):提供轻量、快速的数据包传输(不保证可靠性)。
  2. 应用层协议

    • HTTP/HTTPS:用于网页浏览(HTTPS 加密传输)。
    • FTP/SFTP:文件传输协议(SFTP 加密)。
    • SMTP/POP3/IMAP:邮件传输与接收协议。
    • WebSocket:支持浏览器与服务器间的双向实时通信(如在线聊天、实时数据更新)。

二、Socket

基础概念
第一次复习:
如果要我解释socket,他就像一门面,封装着各种函数。
是一个接口API,正着说可能会让人误解。
反着说,socket既不是某种协议,也不是id+端口号的集合,
而是操控协议与这个地址结合的工具。
他封装着一组函数,通过接口的性质,进行通信。超级方便的哦。
(想用即拿)
第二次复习:
Socket 通信基于客户端 - 服务器(Client - Server)模型

小demo

根据本图,写相应的代码:

server
package mainimport ("fmt""net""strings"
)type User struct {Username  stringOthername stringMsg       stringServerMsg string
}var (user    = new(User)userMap = make(map[string]net.Conn)
)func main() {// 地址addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8889")lis, _ := net.ListenTCP("tcp4", addr)// 循环接收连接for {conn, _ := lis.Accept()go func() {for {b := make([]byte, 1024)count, _ := conn.Read(b)array := strings.Split(string(b[:count]), "-")user.Username = array[0]user.Othername = array[1]user.Msg = array[2]user.ServerMsg = array[3]// 加入对方userMap[user.Username] = connif v, ok := userMap[user.Othername]; ok && v != nil { // 存在 且 不为空n, err := v.Write([]byte(fmt.Sprintf("%s-%s-%s-%s", user.Username, user.Othername, user.Msg, user.ServerMsg)))// 关闭连接if n <= 0 || err != nil {fmt.Println("无效格式")delete(userMap, user.Othername)conn.Close()return} else {fmt.Println("发送成功")}} else {user.ServerMsg = "对方不在线"conn.Write([]byte(fmt.Sprintf("%s-%s-%s-%s", user.Username, user.Othername, user.Msg, user.ServerMsg)))}void }}()}
}
client
package mainimport ("fmt""net""os""strings""sync"
)type User struct {Username  stringOthername stringMsg       stringServerMsg string
}var (user = new(User)wg   sync.WaitGroup
)func main() {wg.Add(1)fmt.Println("请输入你的账号")fmt.Scanln(&user.Username)fmt.Println("请输入你要给谁发送消息")fmt.Scanln(&user.Othername)addr, _ := net.ResolveTCPAddr("tcp4", "localhost:8889")conn, _ := net.DialTCP("tcp4", nil, addr)// 发送go func() {for {fmt.Println("请输入,你要发给谁?仅仅只提示一次")fmt.Scanln(&user.Msg)if user.Msg == "exit" {conn.Close()wg.Done()os.Exit(0)}n, _ := conn.Write([]byte(fmt.Sprintf("%s-%s-%s-%s", user.Username, user.Othername, user.Msg, user.ServerMsg)))fmt.Println(n, "发送成功")}}()// 接收go func() {for {rb := make([]byte, 1024)c, _ := conn.Read(rb)user2 := new(User)array := strings.Split(string(rb[:c]), "-")if len(array) < 3 {fmt.Println("无效格式:", array)break}user2.Username = array[0]user2.Othername = array[1]user2.Msg = array[2]user2.ServerMsg = array[3]if user2.ServerMsg != "" {fmt.Println("\t\t\t服务器消息:", user2.ServerMsg)} else {fmt.Println(user2.Username, ":", user2.Msg)}}}()wg.Wait()
}

三、Mysql

对数据库的操作:

开始之前的基操
create database goWeb;
use goWeb;
create table people(id int primary key auto_increment,name varchar(20),address varchar(100)
);
desc people;
select * from people;
增:
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql"
)/*数据库的连接是一个非常有趣的玩意[user[:password]@][net[(addr)]]/dbname[?param1=value1&param2=value2...]还有一个奇怪的点,就是必须要导入_ "github.com/go-sql-driver/mysql"因为,他中的init的函数,是sql与go之间的桥梁,起到注册作用register但是,我没有理解透,感觉好难受
*/func main() {// 1、打开链接db, err := sql.Open("mysql", "root:1234@tcp(localhost:3306)/goweb")db.Ping()defer func() {if db != nil {db.Close()}}()if err != nil {fmt.Println("数据库连接错误", err)}// 2、预处理SQL// ?表示占位符stmt, err := db.Prepare("insert into people values(default,?,?)")if err != nil {fmt.Println("预处理失败:", err)}defer func() {if stmt != nil {stmt.Close()}}()r, _ := stmt.Exec("张三", "上海")// 3、获取结果count, _ := r.RowsAffected()fmt.Println("修改了:", count)// 获取最后修改的主键fmt.Println(r.LastInsertId())
}
删:
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql"
)func main() {// 连接db, err := sql.Open("mysql", "root:1234@tcp(localhost:3306)/goWeb")if err != nil {fmt.Println("连接失败", err)}defer db.Close()// 预处理stmt, err := db.Prepare("delete from people where id = 2")if err != nil {fmt.Println("预处理失败:", err)}defer stmt.Close()r, _ := stmt.Exec()// 预处理count, _ := r.RowsAffected()if count > 0 {fmt.Println("删除成功")} else {fmt.Println("负责删除失败")}}
改:
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql"
)/*
这里有一个很有趣的事情,如果修改没变化,则修改失败
*/
func main() {// 连接db, err := sql.Open("mysql", "root:1234@tcp(localhost:3306)/goWeb")db.Ping()if err != nil {fmt.Println("失败啦", err)}defer db.Close()// 预处理stmt, err := db.Prepare("update people set name = ?,address = ? where id=3 ;")if err != nil {fmt.Println("预处理失败:", err)}defer stmt.Close()r, _ := stmt.Exec("朝阳", "新乡")// 查看修改情况count, _ := r.RowsAffected()if count > 0 {fmt.Println("修改成功")} else {fmt.Println("修改失败")}
}
查:
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql"
)/*
写完之后,没啥感受,只是在想,这玩意咋都长一个样,背背方法就过去了,
可是好像了解了解底层呐
*/
func main() {// 连接db, err := sql.Open("mysql", "root:1234@tcp(localhost:3306)/goWeb")if err != nil {fmt.Println("连接失败", err)}defer db.Close()// 预处理stmt, err := db.Prepare("select * from people")if err != nil {fmt.Println("预处理失败:", err)}defer func() {if stmt != nil {stmt.Close()}}()// 获取rows, err := stmt.Query()if err != nil {fmt.Println("获取值出错", err)}for rows.Next() {var id intvar name, address stringrows.Scan(&id, &name, &address)fmt.Println(id, " ", name, " ", address)}defer rows.Close() // 关闭结果集
}

四、goWeb

控制器

当你再次回来看时,希望这个能加深你的理解:以下三个函数,的区别

Handler 接口 定义 HTTP 处理逻辑的规范(要求实现 ServeHTTP 方法) 所有 HTTP 处理程序必须直接或间接实现此接口

Handle 函数 将一个 Handler 实例绑定到 URL 路径模式(pattern)

参数 handler 必须是 Handler 接口的实现

HandleFunc 函数 便捷方式:将一个函数包装为 Handler 接口的实现,并绑定到 URL 路径模式

本质是 Handle(pattern, HandlerFunc(handler)),简化了手动实现接口的步骤

拓展,实现了handler接口的对象实例,都能被Handle调用。

单控制器
package mainimport ("net/http"
)/*何其抽象,这只是一个但控制器其实就是用结构体,实现一个端口
*/type MyHander struct {
}func (m *MyHander) ServeHTTP(w http.ResponseWriter, r *http.Request) {w.Write([]byte("返回的数据"))
}// 单控制器
func main() {m := MyHander{}server := http.Server{Addr:    "localhost:8081",Handler: &m, // 一旦绑定在这里,无论访问什么路径,结果都是这个}server.ListenAndServe()//if err := server.ListenAndServe(); err != nil {// fmt.Printf("服务器启动失败: %v\n", err) // 打印具体错误(如端口冲突)//}}
多控制器
package mainimport "net/http"/*好抽象的呢,既然是重写函数,却要重写的一模一样,抽象啦简直气死我了捋一捋思路,发现就是1、先建立服务器2、注册路由3、监听函数
*/
/*
func first(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "first")
}
func second(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "second")
}func main() {// 多控制函数server := http.Server{Addr: "localhost:8081",}// 注册路由http.HandleFunc("/first", first)http.HandleFunc("/second", second)server.ListenAndServe()}
*/// 第二套就是绑定结构体
/*其实多控制器,用结构体,我觉得有点累赘和臃肿首先重写多个结构体,实现接口,然后将每个结构体,依次绑定到服务器上。与其用Handle绑定,不如直接用HandleFunc直接绑定但控制器,就是绑定一个url,多控制器就是绑定多个url。
*/
type Handle struct {
}func (m *Handle) ServeHTTP(w http.ResponseWriter, r *http.Request) {w.Write([]byte("一号"))
}type Handler struct{}func (m *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {w.Write([]byte("二号"))
}func main() {h1 := Handle{}h2 := Handler{}// 重写结构体server := http.Server{Addr: "localhost:8081",}http.Handle("/first", &h1)http.Handle("/second", &h2)// 监听server.ListenAndServe()}

请求头与请求参数

请求头
package main
import ("fmt""net/http"
)func param(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "第一个")// 请求头//fmt.Fprintln(w, r.Header)// 为了便于读写代码var acc []string = r.Header["Accept"]for _, n := range acc {fmt.Fprintln(w, "Accepth内容", n)}
}func main() {// 建立服务器server := http.Server{Addr: "localhost:8081",}// 绑定urlhttp.HandleFunc("/param", param)// 开启监听模式server.ListenAndServe()
}
请求参数

(可在url 或 请求体中)

package mainimport ("fmt""net/http"
)
/*
这里有个有意思的事情,是要用ParseForm去解析表单,才能用r.Form查到
因为,需要ParseForm更新并放置于了Form中
*/
func param(w http.ResponseWriter, r *http.Request) { // 我的名字叫做解析// 对请求头解析h := r.Header // header是一个map类型,选中key值后,返回的是一个string类型的切片fmt.Fprintln(w, h["Accept-Encoding"][0])// 必须先解析成formr.ParseForm()fmt.Fprintln(w, r.Form)}func main() {// 建立服务器server := http.Server{Addr: "localhost:8081",}http.HandleFunc("/param", param)// 开始作为服务端监听server.ListenAndServe()
}

html模板与静态资源

main
package mainimport ("fmt""html/template""net/http"
)/*第一遍学习时:这个解析模板,我有点不理解不是啊,哥们,这有点抽象第一遍复习:其实,我很无奈,因为我的目录与课程目录不同倒逼我去理解,某些函数的作用开始时,我最苦恼的是,StripPerfex与FileServer的作用。原来他的作用,就是解析。FileServer起一个拼接的作用,一旦出现 /static/ 拼接的作用,就开始显现url中如Handle的作用
*/// 与其绑定的url操作
func welcome(w http.ResponseWriter, r *http.Request) {t, err := template.ParseFiles("GoWebDevelopment/basis/htmlTest/view/index.html")if err != nil {fmt.Println("出错了: ", err)}t.Execute(w, nil)
}func main() {// 服务器server := http.Server{Addr: "localhost:8081",}http.HandleFunc("/1", welcome)http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("GoWebDevelopment/basis/htmlTest/static"))))// 开启监听server.ListenAndServe()
}
html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript" src="/static/js/index.js"></script>
</head>
<body>你好啊,哥们<button onClick="myClick()"></button>
</body>
</html>
js
function myClick(){alert("您点击了按钮")
}

函数/数据->模板

难道看到这里你不好奇吗?
模板--数据,难道你不好奇吗,中间是怎么传输数据的?中间的具体过程,以及底层实现
第一次复习:我当然知道,不就是reflect嘛,但是我不希望就此止步于此
第二次复习:采用的都是链式调用(使用方法的精髓)
数据->模板
1、向模板传递参数

2、向模板传递结构体

3、向模板传递map

函数->模板

main
package mainimport ("fmt""html/template""net/http""time"
)/*说实话走到这里有一个非常抽象的事情,就是模板时间,你所设置的模板时间必须与这个一模一样这个是模板时间的整体性:"2006-01-02 15:04:05"其实最抽象的是FuncMap这个绑定的函数,我在这里错了好久。起因却是因为key-value出了问题。html文件中,用的函数名,就是key值,我之前写的却是fm := template.FuncMap{"mt": GetTime}--“mt” 但是,html中绑定的确是fm,这完全是混洗了概念的如果不明白上述说的啥,请让ai回溯
*/func GetTime(t time.Time) string {return t.Format("2006-01-02")
}func welcome(w http.ResponseWriter, r *http.Request) {curtime := time.Date(2018, 1, 2, 3, 4, 5, 0, time.Local)// time.Format:大致意思,就是你给他一个格式,他按照你给的格式编辑。// 解析成合适的函数fm := template.FuncMap{"fm": GetTime}// 绑定t := template.New("index.html").Funcs(fm)t, err := t.ParseFiles("GoWebDevelopment/basis/htmlTest1/PassFunction/view/index.html")if err != nil {fmt.Println("调用失败:", err)}t.Execute(w, curtime) // 这个是暂时的
}// 打着这个旗号
func main() {// 创建服务器server := http.Server{Addr: "localhost:8082",}http.HandleFunc("/2", welcome)server.ListenAndServe()
}
html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>这是是北京时间:{{.}}<br>今天是{{.Year}}年<br>格式化输出就是{{.Format "2006-01-02"}}<br>{{fm .}}
</body>
</html>

Action

if使用

二元比较(隶属于if)

if..else..if...else

range

main 
package mainimport ("fmt""html/template""net/http"
)/*
1、测试变量-$
2、测试 if and if else
3、测试 range
*/func welcome(w http.ResponseWriter, r *http.Request) {t, err := template.ParseFiles("GoWebDevelopment/basis/action/view/index.html")if err != nil {fmt.Println("解析出错: ", err)}// []stringvarInt := []int{1, 2, 3, 4, 5}t.Execute(w, varInt)
}func main() {server := http.Server{Addr: "localhost:8081",}http.HandleFunc("/1", welcome)server.ListenAndServe()
}
html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><!--第一目,变量-->
{{$n:=100}}<!-- 第二目,if-else -->{{if gt $n 101}}你好呀<br>{{else}}好遗憾,他没看到呢<br>{{end}}<!-- 第三目,遍历 -->
{{range .}}
{{.}}<br> <!-- 遍历内部参数 -->
{{end}}</body>
</html>

模板嵌套

若我没猜错,你一定会回来看的。

用我复习3遍的经验告诉你,你可以直接看代码

或许你看着他们特别的复杂,其实除了主main函数

三个html函数,都依靠着各自的后背

head index(被定义为了layout) end 以index为主体,通过define定义,以template为连接。将他们链接在一起。

main 
package main
import ("fmt""html/template""net/http"
)
func welcome(w http.ResponseWriter, r *http.Request) {t, err := template.ParseFiles("GoWebDevelopment/basis/Nesting/view/index.html","GoWebDevelopment/basis/Nesting/view/head.html", "GoWebDevelopment/basis/Nesting/view/end.html")if err != nil {fmt.Println("这里出错啦:", err)}t.ExecuteTemplate(w, "layout", nil)
}
func main() {// 设置服务器server := http.Server{Addr: "localhost:8081"}// 开启http.HandleFunc("/", welcome)// 开启监听server.ListenAndServe()
}
head
如果爆红,请不要紧张,编辑器的bug,不怪咱
{{define "head"}}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>我是头部
</body>
</html>
{{end}}
index
{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>{{template "head"}}<br>你好<br>{{template "end"}}<br>
</body>
</html>
{{end}}
end
{{define "end"}}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>这里是结尾呦
</body>
</html>
{{end}}

文件上传

在这里,html表单中的enctype是主角,

我个人感觉,后端只需要接收传递过来的信息,附带加工即可。

html-form表单
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>文件上传</title>
</head>
<body>
<!--这是一非常有趣的表单,form的作用,就是提取表单action:将表单提取到指定urlmethod代表应用method方法input--type:类型,name:input提交内容的名字
-->
<!--文件上传upload,表达提交--> 
<form action="upload" enctype="multipart/form-data" method="post">用户名:<input type="text" name="username"><br>上传照片:<input type="file" name="photo"><br><input type="submit" value="注册">
</form>
</body>
</html>
main
package main
import ("fmt""html/template""io/ioutil""net/http""strings"
)
/*第一遍感悟:从->r.Request接收数据FormValue接收值-到-FormFile接收文件,文件又有File(粗略)与FileHeader(详细)两个方向解析其实到最后,还有一个奇葩的问题,就是form没数据,硬传,定报错,所以要用err第二遍感悟:先从http中获取名字(FormValue),在获取文件接口(FormFile),转为2进制(WriteFile),存入本地
*/
func welcome(w http.ResponseWriter, r *http.Request) {t, err := template.ParseFiles("GoWebDevelopment/basis/upload/view/index.html")if err != nil {fmt.Println("解析模板出错", err)}t.Execute(w, nil)
}func upload(w http.ResponseWriter, r *http.Request) {fileName := r.FormValue("name")fmt.Fprintln(w, fileName)// 检查文件上传错误(关键修正)file, fileHeader, err := r.FormFile("photo")if err != nil {fmt.Fprintln(w, "错误:未上传文件或请求不合法")return // 终止函数,避免后续空指针操作}defer file.Close() // 及时关闭文件流,释放资源b, err := ioutil.ReadAll(file) // 建议改为 io.ReadAll(file)(ioutil 已弃用)if err != nil {fmt.Fprintln(w, "错误:读取文件内容失败", err)return}// 获取后缀suffix := fileHeader.Filename[strings.LastIndex(fileHeader.Filename, "."):]// 建议改为 os.WriteFile(ioutil 已弃用)err = ioutil.WriteFile("D:\\workspace_go\\practice\\GoWebDevelopment\\basis\\upload\\file"+fileName+suffix, b, 0777)if err != nil {fmt.Fprintln(w, "错误:保存文件失败", err)return}    t, err := template.ParseFiles("GoWebDevelopment/basis/upload/view/success.html")if err != nil {fmt.Fprintln(w, "错误:解析模板失败", err)return}t.Execute(w, nil)
}func main() {server := http.Server{Addr: "localhost:8081",}http.HandleFunc("/1", welcome)http.HandleFunc("/upload", upload)server.ListenAndServe()
}

文件下载

  • 如果照片看不懂,就看我写的简介。
    MIME类似标签,多用途互联网邮件扩展类型,是一种用于标识文档、文件或字节流的性质和格式的标准
基础概念

MIME

MIME类似标签,多用途互联网邮件扩展类型,是一种用于标识文档、文件或字节流的性质和格式的标准

  • 在 HTTP 通信中,服务器通过响应头中的 MIME 类型,让浏览器判断是直接显示内容(如 text/html 类型的网页、image/jpeg 类型的图片),还是提示用户下载(如 application/pdf 类型的文件)。
  • 邮件程序通过 MIME 类型检测附件格式,选择对应程序打开;文件管理器依据 MIME 类型,用合适的应用打开文件、显示文件类型描述及图标等。
main函数
package main
import ("fmt""html/template""net/http""os"
)
/*
关于请求下载,是一件非常有趣的事情
老样子,启动服务器,开启监听,绑定路由,用一个页面将基础内容展示出来
其次才是新东西,在html中,设置a标签。href设置成请求下载url,启动download路由
获取filename参数。开始申请本地文件,通过os.ReadAll转化为2进制,传递到客户端
并通过设置客户端标头,Head()...用set改变各种参数content-type与Disposition
是客户端下载下来
*/
func welcome(w http.ResponseWriter, r *http.Request) {t, _ := template.ParseFiles("GoWebDevelopment/basis/download/view/index.html")t.Execute(w, nil)
}func download(w http.ResponseWriter, r *http.Request) { // 人家这个函数,只是接收的一个请求而已filename := r.FormValue("filename")// 获取值b, err := os.ReadFile("GoWebDevelopment/basis/upload/" + filename)if err != nil {fmt.Fprintf(w, "下载失败:", err)return}h := w.Header()h.Set("Content-Type", "application/octet-stream")h.Set("Content-Disposition", "attachment;filename="+filename)w.Write(b)/*我知道,以后看到这里的时候,你一定会有疑惑,(w.Write()与Fprintln(w,)的区别)没事,我替你解决:若需要精确控制输出内容(如二进制数据、JSON、无额外字符的文本),选 w.Write([]byte);若需要快速拼接并输出文本(如调试信息、多变量组合输出),选 fmt.Fprintln(w, ...)。*/
}func main() {server := http.Server{Addr: "localhost:8081",}http.HandleFunc("/1", welcome)http.HandleFunc("/download", download)server.ListenAndServe()
}
index函数
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>下载链接</title>
</head>
<body><!--前提是,我可没有这个文件--><a href="download?filename=file.png">点击我下载</a>
</body>
</html>

ajax请求返回json数据

main
package mainimport ("encoding/json""fmt""html/template""net/http"
)/*
我幸运的孩子呐,如果你下载jquery不幸运落坑,这时我建议你,Ctrl+S试试
*/type User struct {Name stringAge  int
}// 显示主页面
func welcome(w http.ResponseWriter, r *http.Request) {t, _ := template.ParseFiles("GoWebDevelopment/basis/ajax/view/index.html")t.Execute(w, nil)
}// 响应ajax请求
func showUser(w http.ResponseWriter, r *http.Request) {us := make([]User, 0)us = append(us, User{"张三", 12})us = append(us, User{"李四", 13})us = append(us, User{"王五", 14})b, _ := json.Marshal(us) // 转化成了二进制w.Header().Set("Content-Type", "application/json;charset=utf-8")fmt.Fprintln(w, string(b))/*我知道,很多天后,你肯定会出现疑惑!为什么要用string(b),而不直接用b呢??哈哈,我来教你:这是符合 Content-Type: application/json 的正确输出方式,客户端(如前端 Ajax)可以直接解析这个 JSON 字符串。其他底层的,是与前端接轨的,我一个后端的,暂时不研究*/
}func main() {// 启动服务器server := http.Server{Addr: ":8888",}// 处理静态资源http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("GoWebDevelopment/basis/ajax/static/"))))http.HandleFunc("/1", welcome)http.HandleFunc("/showUser", showUser)// 同步监听server.ListenAndServe()
}
html
html<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>用户数据展示</title><script type="text/javascript" src="/static/jquery-3.7.1.min.js"></script><script type="text/javascript">$(function () {$("button").click(function () {$.ajax({url: "/showUser",type: "GET",data: {}, // 若后端需要参数(如分页),可在此添加 {page: 1}success: function (data) {var res = "";// 遍历服务器返回的 data 数组(假设 data 是 [{Name: "张三", Age: 20}, ...])for (let i = 0; i < data.length; i++) {// 修正:使用 data[i] 而非 resKey[i]res += "<tr>";res += "<td>" + data[i].Name + "</td>"; // 拼接姓名res += "<td>" + data[i].Age + "</td>";  // 拼接年龄res += "</tr>";}// 将拼接好的行插入到 tbody#mybody 中$("#mybody").html(res);},error: function (xhr, status, error) {// 错误提示alert("数据加载失败!错误:" + error);}});});});</script>
</head>
<body>
<button>加载数据</button>
<!-- 修正:<tbody> 移到 <table> 内部 -->
<table border="1"><!-- 表头用 <thead> 包裹(可选但推荐) --><thead><tr><td>姓名</td><td>年龄</td></tr></thead><!-- 表体用 <tbody id="mybody"> 包裹 --><tbody id="mybody"><!-- 数据行将通过 AJAX 动态插入到这里 --></tbody>
</table>
</body>
</html>
非常有趣的小玩意

解释:

  • 如果是浏览器可直接渲染的内容(如 text/html、image/png、text/plain 等):
    • 浏览器会直接处理并显示。例如:
      • 当响应体是 HTML 内容(Content-Type: text/html),浏览器会解析并渲染成网页。
      • 当响应体是图片(Content-Type: image/png),浏览器会直接显示图片。
      • 当响应体是纯文本(Content-Type: text/plain),浏览器会显示原始文本。
  • 如果是数据格式(如 application/json、application/xml、text/csv 等):
    • 浏览器不会直接渲染,而是将数据 “交给” 前端 JavaScript(如通过 AJAX/Fetch 请求获取),由脚本解析后再决定如何显示(例如更新页面 DOM、弹出提示等)。

这个是关键:

  • 直接显示的情况:当响应体是浏览器可直接渲染的内容(如 HTML、图片、文本),且通过普通导航请求获取时,会直接显示在界面上。
  • 需前端处理的情况:当响应体是数据格式(如 JSON、XML),或通过 AJAX/Fetch 异步获取时,需前端脚本解析并手动更新页面显示。

正则表达式

这里,我建议,最好的学习方式,是看看菜鸟文档 :: 我的笔记 ::

自己的小小测试,掌握到这里就足够了,当然要结合菜鸟看看基础知识点

::可以自己尝试写一个邮箱,用来测试自己::

package mainimport ("fmt""regexp"
)
/*
第一次复习的时候,巩固知识点,收获
1、动态编译(Compile)
2、静态编译(MustCompile)
1、匹配(MatchString)、查找(FindAllString)、分割(Split)、替换(ReplaceAllString)
*/
func main() {// ^与$ 两者结合起来的用法。\D的用法,反斜杠``转义的用法flag, _ := regexp.MatchString(`^\D+$`, "abs")fmt.Println(flag)/*创建一个regexp对象,然后调用方法*/r := regexp.MustCompile(`\d`)flag = r.MatchString("fsaf")fmt.Println(flag)// 返回所有切片,-1返回所有,1返回第一个,2返回前两个,3返回前三个str := r.FindAllString("234", -1)fmt.Println(str)// 按照规则切割,没有的话,返回空。n==0返回空,n<0返回所有,n>0返回对应个数+剩余个数str = r.Split("d12jkj231dd", -1)fmt.Println(str[0], str)// 就是起到一个替换的作用st := r.ReplaceAllString("d1w2k3k3", "美女")fmt.Println(st)
}

Cookie

设置(set)、获取(get) Cookie
package mainimport ("fmt""html/template""net/http""net/url"
)/*
为了写这个,真是命运多舛呐
cookie本来即使一个很好写的东西
无非就是创建cookie,然后通过响应传回去
并入到,请求标头中
通过request接受这个信息
!!! 但前提有一个重要的原因是,必须编译与解码!你可以把url.QueryFiles的作用是将信息转换成%+16进制
*/
func welcome(w http.ResponseWriter, r *http.Request) {t, _ := template.ParseFiles("GoWebDevelopment/basis/Cookie/setAndGetCookie/view/index.html")t.Execute(w, nil)
}func setCookie(w http.ResponseWriter, r *http.Request) {encodeValue := url.QueryEscape("成功了")c := http.Cookie{Name: "name", Value: encodeValue, Path: "/"}http.SetCookie(w, &c)t, _ := template.ParseFiles("GoWebDevelopment/basis/Cookie/setAndGetCookie/view/index.html")t.Execute(w, nil)
}func getCookie(w http.ResponseWriter, r *http.Request) {res := r.Cookies()for n, v := range res {str, _ := url.QueryUnescape(v.Value)fmt.Println(n, ":", str)}t, _ := template.ParseFiles("GoWebDevelopment/basis/Cookie/setAndGetCookie/view/index.html")t.Execute(w, res)
}
func main() {server := http.Server{Addr: ":8888",}http.HandleFunc("/1", welcome)http.HandleFunc("/setCookie", setCookie)http.HandleFunc("/getCookie", getCookie)server.ListenAndServe()
}
拓展:(HttpOnly、Path、MaxAge、Expires、Domain)
package mainimport ("net/http""time"
)func serv(w http.ResponseWriter, r *http.Response) {// 验证httpOnly// true不允许获得,false允许js脚本获得c1 := http.Cookie{Name: "myname", Value: "myvalue", HttpOnly: true}// /abc/ 代表能访问的路径,必须以/abc/以跟路径c2 := http.Cookie{Name: "myname", Value: "myvalue", Path: "/abc/"}// 设置存货时间c3 := http.Cookie{Name: "myname", Value: "myvalue", MaxAge: 10}t := time.Now() // 获取现在时间c4 := http.Cookie{Name: "myname", Value: "myvalue", Expires: t}// 必须以这个指定域名结尾,才可使用c5 := http.Cookie{Name: "myname", Value: "myvalue", Domain: ".bjsxt.com"}
}func main() {server := http.Server{Addr: ":8888",}http.HandleFunc("/1", serv)server.ListenAndServe()
}

第三方实现Restful风格

简而言之,第三个库实现了一个路由


借鉴博客:

1、我自己的笔记 

2、菜鸟文档


 ::有道云笔记点击入口::

如果有帮助、记得点赞+收藏呐(〃 ̄︶ ̄)人( ̄︶ ̄〃)

相关文章:

  • R1-Searcher:用强化学习解锁大语言模型检索新能力!
  • 【ArcGIS技巧】用地块生成界址点去重、顺时针编号挂接DKBM属性
  • [python] 函数基础
  • B站pwn教程笔记-8
  • 【微信小程序开发】从0开始的一点点小记录
  • 电厂参与全球能源效率排名的方法
  • Python实现中文数字与阿拉伯数字映射生成器(支持0-9999)
  • 《开源先锋Apache软件基金会:历史沿革、顶级项目与行业影响》
  • Android单例模式知识总结
  • 使用迁移学习的自动驾驶汽车信息物理系统安全策略
  • Java数据结构——Queue
  • LeetCode热题100--54.螺旋矩阵--中等
  • 商业中的人工智能 (AI) 是什么?
  • 大疆无人机(全系列,包括mini)拉流至电脑,实现直播
  • 【链表扫盲】FROM GPT
  • 第四章 OpenCV篇—图像梯度与边缘检测—Python
  • Rust包、crate与模块管理
  • 【 Redis | 实战篇 短信登录 】
  • CSS:元素显示模式与背景
  • 【图片合并PDF】一次性将多个文件夹里的图片批量按文件夹为单位合并PDF,多个文件夹图片合并PDF,基于WPF的实现方案
  • 中日有关部门就日本水产品输华问题进行第三次谈判,外交部回应
  • 外卖员投资失败负疚离家流浪,经民警劝回后泣不成声给父母下跪
  • 技术派|伊朗展示新型弹道导弹,美“萨德”系统真的拦不住?
  • 特色业务多点开花,苏州银行擦亮金融为民底色
  • 花20万骑自行车?CityRide带火“骑行经济”
  • 美联储宣布维持基准利率不变