Go web 基础相关知识

Go web

Web工作方式

浏览器本身是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务

img

URL和DNS解析

URL是“统一资源定位符”的英文缩写,用于描述一个网络上的资源

scheme://host[:port#]/path/.../[?query-string][#anchor]
scheme         指定低层使用的协议(例如:http, https, ftp)
host           HTTP服务器的IP地址或者域名
port#          HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如 http://www.cnblogs.com:8080/
path           访问资源的路径
query-string   发送给http服务器的数据
anchor         锚

DNS 是**“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,他用于TCP/IP网络,从事将主机名或域名转换为实际IP地址**的工作

img

DNS解析过程:

1.在浏览器中输入www.qq.com域名,操作系统会检查自己本地的hosts文件是否由这个网址映射关系,如果有,直接返回域名解析

2.如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存,是否有这个网址映射关系

3.如果本地DNS缓存器缓存都没有相应的网址映射关系,首先会找TCP/IP参数中设置的首选DNS服务器,在此我们叫他本地DNS服务器,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析

4.如果要查询的域名,不由本地DNS服务器区域解析

img

举个例子来说,你想知道某个一起上法律课的女孩的电话,并且你偷偷拍了她的照片,回到寝室告诉一个很仗义的哥们儿,这个哥们儿二话没说,拍着胸脯告诉你,甭急,我替你查(此处完成了一次递归查询,即,问询者的角色更替)。然后他拿着照片问了学院大四学长,学长告诉他,这姑娘是xx系的;然后这哥们儿马不停蹄又问了xx系的办公室主任助理同学,助理同学说是xx系yy班的,然后很仗义的哥们儿去xx系yy班的班长那里取到了该女孩儿电话。(此处完成若干次迭代查询,即,问询者角色不变,但反复更替问询对象)最后,他把号码交到了你手里。完成整个查询过程

HTTP协议详解

HTTP协议是Web工作的核心

HTTP是一种让Web服务器与浏览器通过Internet发送与接收数据的协议,它建立在TCP协议之上,一般采用TCP的80端口。

HTTP协议是无状态的,同一个客户端的这次请求和上次请求时没有对应关系,对HTTP服务器来说,它并不知道这两个请求

HTTP请求包(浏览器信息)

GET /domains/example/ HTTP/1.1        //请求行: 请求方法 请求URI HTTP协议/协议版本
Host:www.iana.org                //服务端的主机名
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4            //浏览器信息
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8    //客户端能接收的mine
Accept-Encoding:gzip,deflate,sdch        //是否支持流压缩
Accept-Charset:UTF-8,*;q=0.5        //客户端字符编码集
//空行,用于分割请求头和消息体
//消息体,请求资源参数,例如POST传递的参数

HTTP协议定义了很多与服务器交互的请求方法,最基本的有4种,分别是GET,POST,PUT,DELETE。一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE就对应着对这个资源的查,增,改,删4个操作。我们最常见的就是GET和POST了。GET一般用于获取/查询资源信息,而POST一般用于更新资源信息。

img

这个是GET信息

web工作方式 - 图5

这个是POST信息

GET和POST信息:

1.GET请求信息体为空,POST请求带有消息体

2.GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连

3.GET提交的数据大小有限制,而POST方法提交的数据没有限制

4.GET方式提交数据,会带来安全问题

如果输入用户名和密码提交数据,用户名和密码将出现在URL上,如果页面可以被缓存,将从历史记录获得该用户的账号和密码

HTTP响应包(服务器信息)

response包:

HTTP/1.1 200 OK                        //状态行
Server: nginx/1.0.8                    //服务器使用的WEB软件名及版本
Date:Date: Tue, 30 Oct 2012 04:14:25 GMT        //发送时间
Content-Type: text/html                //服务器发送信息的类型
Transfer-Encoding: chunked            //表示发送HTTP包是分段发的
Connection: keep-alive                //保持连接状态
Content-Length: 90                    //主体内容长度
//空行 用来分割消息头和主体
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... //消息体

第一行叫做状态行,由HTTP协议版本号,状态码,状态消息 三部分组成

我们可以看到200是它的状态码

  • 1XX 提示信息 - 表示请求已被成功接收,继续处理
  • 2XX 成功 - 表示请求已被成功接收,理解,接受
  • 3XX 重定向 - 要完成请求必须进行更进一步的处理
  • 4XX 客户端错误 - 请求有语法错误或请求无法实现
  • 5XX 服务器端错误 - 服务器未能实现合法的请求

web工作方式 - 图6

200是常用的,302表示跳转

HTTP协议是无状态的和Connection:keep-alive 的区别

无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。从另一方面讲,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系

从HTTP/1.1起,默认都开启了Keep-Alive保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的TCP连接。

Keep-Alive不会永久保持连接,它有一个保持时间

这是一次请求的request和response

web工作方式 - 图7

我们可以注意到

为什么一个URL请求但是左边栏里面为什么会有那么多的资源请求

这个就是浏览器的一个功能:第一次请求url,服务器端返回的是html页面,然后浏览器开始渲染HTML:当解析到HTML DOM里面的图片链接,css脚本和js脚本的链接,浏览器就会自动发起一个请求静态资源的HTTP请求,获取相对应的静态资源

3.2 Go搭建一个Web服务器

Go语言里面提供了一个完善的net/http包,通过这个包可以对Web的路由,静态文件,模板,cookie等数据进行设置和操作

package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"
)

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()       //解析参数,默认是不会解析的
    fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
    fmt.Println("path", r.URL.Path)
    fmt.Println("scheme", r.URL.Scheme)
    fmt.Println(r.Form["url_long"])
    for k, v := range r.Form {
       fmt.Println("key:", k)
       fmt.Println("val:", strings.Join(v, ""))
    }
    fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端
}
func main() {
    http.HandleFunc("/", sayhelloName)
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
       log.Fatal("ListenAndServe: ", err)
    }
}
3.3 Go如何使得Web工作

Web工作方式的几个概念

Request:用户请求的新消息,用来解析用户的请求信息,包括post,get,cookie,url等信息

Response:服务器需要反馈给客户端的信息

Conn:用户的每次请求连接

Handler:处理请求和生成返回信息的处理逻辑

分析http包运行机制

img

  1. 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。
  2. Listen Socket接受客户端的请求, 得到Client Socket, 接下来通过Client Socket与客户端通信
  3. 处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过Client Socket写给客户端。

如何监听端口?

Go通过一个 函数ListenAndServe来处理这个事情,底层:

初始化一个serve对象,然后调用net.Listen(“tcp”,addr),也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口

如何接收客户端请求?

//接下来我会调用srv.Serve(net.Listener)这个函数,这个函数就是处理接收客户端的请求信息
func (srv *Server) Serve(l net.Listener) error {
    //用来处理接收客户端的请求信息
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c, err := srv.newConn(rw)
        //创建一个Conn
        if err != nil {
            continue
        }
        go c.serve()
        //单独开了一个goroutine
    }
}

如何分配handler?

如何具体分配到相应的函数来处理请求?
conn首先会解析request:c.readRequest,然后获取对应的handler:

handler :=c.server.Handler

img

Go的http详解

Go的http有两个核心功能:Conn,ServeMux

Conn的goroutine

Go为了实现高并发和高性能,使用了goroutine来处理Conn的读写事件,这样每个请求都能保持独立

c, err := srv.newConn(rw)
if err != nil {
    continue
}
go c.serve()

客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中可以读取到相应的header信息,这样保证了每个请求的独立性

ServeMux的自定义(路由器的实现)

并发(Concurrency)是指在计算机科学中,指的是同时执行多个独立的任务或操作的能力。这些任务可能在同一时间段内交替执行,也可能在同一时间段内同时执行,但它们都是在同一个时间段内并发地进行。并发通常涉及到多个线程、进程或任务同时执行,并且这些执行之间可能相互独立,也可能存在一定的交互关系。并发通常被用来提高系统的性能、资源利用率和响应能力。

需要锁机制是为了防止只有一个进程能访问共享资源

type ServeMux struct {
    mu sync.RWMutex   //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
    m  map[string]muxEntry  // 路由规则,一个string对应一个muxEntry实体,这里的string就是注册的路由表达式
    hosts bool // 是否在任意的规则中带有host信息
}


type muxEntry struct {
    explicit bool   // 是否精确匹配
    h        Handler // 这个路由表达式对应哪个handler
    pattern  string  //匹配字符串
}
//每个路由表达式都对应着一个handler
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  // 路由实现器
}

我们调用了HandlerFunc(f),强制类型转换f称为HandlerFunc类型,这样f就拥有了ServeHTTP方法

HandlerFunc(f)-因为HandlerFunc实现了Handler接口类型

type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

默认路由器实现了ServeHTTP:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        w.Header().Set("Connection", "close")
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。

  • 首先调用Http.HandleFunc

    按顺序做了几件事:

    1 调用了DefaultServeMux的HandleFunc

    2 调用了DefaultServeMux的Handle

    3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则

  • 其次调用http.ListenAndServe(“:9090”, nil)

    按顺序做了几件事情:

    1 实例化Server

    2 调用Server的ListenAndServe()

    3 调用net.Listen(“tcp”, addr)监听端口

    4 启动一个for循环,在循环体中Accept请求

    5 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()

    6 读取每个请求的内容w, err := c.readRequest()

    7 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux

    8 调用handler的ServeHttp

    9 在这个例子中,下面就进入到DefaultServeMux.ServeHttp

    10 根据request选择handler,并且进入到这个handler的ServeHTTP

      mux.handler(r).ServeHTTP(w, r)
    

    11 选择handler:

    A 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry)

    B 如果有路由满足,调用这个路由handler的ServeHTTP

    C 如果没有路由满足,调用NotFoundHandler的ServeHTTP

表单

表单是一个包含表单元素的区域

通过表单我们可以让客户端于服务器进行数据的交互

表单元素是允许用户在表单中输入信息的元素

<form>
...
input 元素
...
</form>

img

4.1处理表单的输入

package main

import (
	"fmt"
	"log"
	"net/http"
	"text/template"
)

func sayhelloName(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	//解析url传递的参数,对于POST则解析相应包的主体
	//如果没有调用ParseForm方法,下面无法获取表单的数据
	fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
	fmt.Println("path", r.URL.Path)
	fmt.Println("scheme", r.URL.Scheme)
	fmt.Println(r.Form["url_long"])
	for k, v := range r.Form {
		fmt.Println("key:", k)
		fmt.Println("value:", v)
	}
	fmt.Fprintln(w, "Hello astaxie!")
	//这个写入到w的是输出客户端的

}
func login(w http.ResponseWriter, r *http.Request) {
	fmt.Println("method:", r.Method) //获取请求的方法
	if r.Method == "GET" {
		t, _ := template.ParseFiles("login.gtpl")
		log.Println(t.Execute(w,nil))
	}else{
		//请求的要么是登录数据,要么是执行登录的逻辑判断
        //如查询数据库,验证登录信息等
		fmt.Println("username:",r.Form["username"])
		fmt.Println("password:",r.Form["password"])
	}
}

func main() {
	http.HandleFunc("/",sayhelloName) // 设置访问的路由
	http.HandleFunc("/login",login)
	err := http.ListenAndServe("9090",nil)
	if err!=nil{
		log.Fatal("ListenAndServe:",err);
	}
}

4.2验证表单的输入

如何在服务器端验证我们从一个表单元素中得到了一个值。

//如果username是map的值必须通过这种形式
if len(r.Form["username"][0])==0{
    //为空的处理
}

我们通过r.Form.Get()来获取单个值

只能是数字

如果想要确保一个表单输入框中获取的只能是数字

getint,err:=strconv.Atoi(r.Form.Get("age"))
if err!=nil{
    //数字转化出错了,那么可能就不是数字
}
//接下来就可以判断这个数字的大小范围了
if getint >100 {
    //太大了
}
只能是中文

于中文我们目前有两种方式来验证,可以使用 unicode 包提供的 func Is(rangeTab *RangeTable, r rune) bool 来验证,也可以使用正则方式来验证,这里使用最简单的正则方式,如下代码所示

if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m {
    return false
}
英文
if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
    return false
}
电子邮箱地址
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
    fmt.Println("no")
}else{
    fmt.Println("yes")
}
手机号码
if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
    return false
}
下拉菜单

想要知道元素生成的下拉菜单中是否有被选中的项目

假设我们的select是这样一些元素

<select name="fruit">
<option value="apple">apple</option>
<option value="pear">pear</option>
<option value="banana">banana</option>
</select>
slice:=[]string{"apple","pear","banana"}
v := r.Form.Get("fruit")
for _, item := range slice {
    if item == v {
        return true
    }
}
return false
单选按钮

当我们需要判断一个单选按钮是否选中的时候

<input type="radio" name="gender" value="1"><input type="radio" name="gender" value="2">

和下拉菜单的做法一样

slice:=[]string{"1","2"}
for _, v := range slice {
    if v == r.Form.Get("gender") {
        return true
    }
}
return false
复选框

就是多选的

<input type="checkbox" name="interest" value="football">足球
<input type="checkbox" name="interest" value="basketball">篮球
<input type="checkbox" name="interest" value="tennis">网球
slice:=[]string{"football","basketball","tennis"}
a:=Slice_diff(r.Form["interest"],slice)
if a == nil{
    return true
}
return false
func Slice_diff(slice1, slice2 []interface{}) (diffslice []interface{}) {
	for _, v := range slice1 {
		if !In_slice(v, slice2) {
			diffslice = append(diffslice, v)
		}
	}
	return
}
日期和时间

你想确定日期时间是否有效

t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
fmt.Printf("Go launched at %s\n", t.Local())
身份证号码

身份证有十五位和十八位

//验证15位身份证,15位的是全部数字
if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m {
    return false
}
//验证18位身份证,18位前17位为数字,最后一位是校验位,可能为数字或字符X。
if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {
    return false
}

4.3预防跨站脚本

我们的网站包含了大量的动态内容以提高用户体验,比过去要复杂的多,所谓动态内容,就是根据用户环境的需要,Web应用程序能够输出相应的内容 。

对XSS(跨站脚本攻击)最佳的防护应该结合以下两种方法:

1.对所有输入数据进行检验

2.进行适当的处理,防止任何已经成功注入的脚本在浏览器端运行

HTML转义

HTML 转义是指将 HTML 特殊字符转换为对应的 HTML 实体,以防止恶意用户插入 HTML 或 JavaScript 代码导致的安全漏洞。HTML/template 包提供了几个函数来帮助进行 HTML 转义。

在 web 应用程序中,如果允许用户输入的内容直接嵌入到 HTML 页面中而没有经过转义处理,就可能会导致安全漏洞。这种情况被称为跨站脚本攻击(Cross-Site Scripting,XSS)

XSS 攻击的原理是攻击者向网页中注入恶意的客户端脚本,当其他用户访问包含恶意脚本的页面时,这些脚本就会在用户浏览器中执行,从而达到攻击的目的,例如盗取用户的会话信息、篡改页面内容等。

举个例子,如果一个网站允许用户在评论中输入任意内容,并且将这些内容直接显示在网页上,而不进行转义处理,那么攻击者可以在评论中插入恶意的 JavaScript 代码。当其他用户浏览这些评论时,恶意脚本就会在其浏览器中执行,可能导致用户的账户被盗取或者其他安全问题。

因此,对用户输入的内容进行适当的转义处理是防范 XSS 攻击的重要手段之一。HTML 转义函数就是一种常见的转义处理方式,它将 HTML 特殊字符转换为对应的 HTML 实体,从而使得恶意脚本失效,保护了网站的安全。

脚本

脚本(Script)是一种用于自动化和控制计算机程序行为的编程语言。脚本通常以文本形式编写,并且可以被解释器直接执行,而不需要预先编译成二进制代码。脚本语言通常用于快速编写小型程序或自动化任务,如网页交互、系统管理、数据处理等。

在 web 开发中,脚本通常指的是客户端脚本(Client-side Script),它是嵌入在网页中的脚本代码,由浏览器解释执行。常见的客户端脚本语言包括 JavaScript、VBScript 等。这些脚本可以在用户浏览网页时在浏览器中执行,用于实现网页的交互功能、动态效果等。

举个例子,当你在网页上点击一个按钮,触发了一些动作或者弹出了一个对话框,很可能是因为网页中嵌入了 JavaScript 脚本,它在用户与网页交互时被触发执行。

然而,如果恶意用户能够将自己的恶意脚本注入到网页中,并成功执行,就可能导致安全问题,比如跨站脚本攻击(XSS)或其他类型的安全漏洞。因此,开发者需要注意对用户输入数据进行适当的过滤和转义,以防止这类安全风险的发生。

MD5(Message Digest Algorithm 5)是一种常用的哈希函数,用于将任意长度的消息(或数据)转换为固定长度的哈希值(通常为128位的二进制字符串,或者32个十六进制字符)。

MD5

MD5 哈希对象通常在计算机安全、数据完整性验证等领域中使用,其主要用途包括:

数据完整性验证:** MD5 哈希函数可以将任意长度的数据转换为固定长度的哈希值。通过对原始数据进行哈希处理,可以生成一个唯一的标识符,称为哈希值。如果数据的任何部分发生了改变,其哈希值也会发生变化。因此,可以通过比较数据的哈希值来验证数据的完整性,确保数据在传输或存储过程中没有被篡改。

package main

import (
	"crypto/md5"
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"
)

func sayhelloName(w http.ResponseWriter, r *http.Request) {
	r.ParseForm() //解析url传递的参数,对于POST则解析响应包的主体(request body)
	//注意:如果没有调用ParseForm方法,下面无法获取表单的数据
	fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
	fmt.Println("path", r.URL.Path)
	fmt.Println("scheme", r.URL.Scheme)
	fmt.Println(r.Form["url_long"])
	for k, v := range r.Form {
		fmt.Println("key:", k)
		fmt.Println("val:", strings.Join(v, ""))
	}
	fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
}
func login(w http.ResponseWriter, r *http.Request) {
	fmt.Println("method:", r.Method)
	if r.Method == "GET" {
		//如果时GET请求,就返回登录页面
		crutime := time.Now().Unix()
		//获取当前时间戳(当前时刻)
		h := md5.New()
		//创建一个MD5,MD5对象可以保证这个时间戳的安全性
		io.WriteString(h, strconv.FormatInt(crutime, 10))
		//将当前时间戳转换成字符串并写入哈希对象h
		token := fmt.Sprintf("%x", h.Sum(nil))
		//fmt.Sprintf用来返回格式化的字符串
		//计算哈希值并将其格式化为十六进制字符串
		t, _ := template.ParseFiles("vote.html")
		//解析模板文件并赋值给t
		t.Execute(w, token)
		//将token(格式化的字符串)渲染到模板中
		//发送给客户端
	} else {
		//如果时POST就需要处理 用户提交的登陆数据
		r.ParseForm()
		//解析表单数据
		token := r.Form.Get("token")
		if token != "" {
			//验证token的合法性
		} else {
			//不存在token则报错
		}
		fmt.Println("username length:", len(r.Form["username"][0]))
		fmt.Println("username:", template.HTMLEscapeString(r.Form.Get(("username"))))
		//打印用户名,并适应HTML转义
		fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
		template.HTMLEscape(w, []byte(r.Form.Get("username"))) //将用户名转移之后,输出到客户端
	}
}
func main() {
	http.HandleFunc("/", sayhelloName)       //设置访问的路由
	http.HandleFunc("/login", login)         //设置访问的路由
	err := http.ListenAndServe(":9090", nil) //设置监听的端口
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

4.5处理文件上传

要使表单能够上传文件

我们要添加form的enctype属性,enctype属性有三种情况:

  1. application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
  2. multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
  3. text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
package main

import (
	"crypto/md5"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"strconv"
	"time"
)

func upload(w http.ResponseWriter, r *http.Request) {
	fmt.Println("method", r.Method) //获取请求的方法
	if r.Method == "GET" {
		crutime := time.Now().Unix()
		h := md5.New()
		io.WriteString(h, strconv.FormatInt(crutime, 10))
		token := fmt.Sprintf("%x", h.Sum(nil))

		t, _ := template.ParseFiles("upload.html")
		t.Execute(w, token)
	} else {
		r.ParseMultipartForm(32 << 20)
		//限制最大大小为32MB
		file, handler, err := r.FormFile("uploadfile")
        //文件handler是multipart.FileHeader
        //里面存储了如下结构信息
        /*
        type FileHeader struct{
        Filename string
        Header textproto.MIMEHeader
        }
        */
		if err != nil {
			fmt.Println(err)
			return
		}
		defer file.Close()
		fmt.Fprintf(w, "%v", handler.Header)
		f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			fmt.Println(err)
			return
		}
		defer f.Close()
		io.Copy(f, file)
	}
}

<!DOCTYPE html>
<html >
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>
<form enctype="multipart/form-data" action = "/upload" method = "post">

    <input type ="file" name = "uploadfile" />
    <input type ="hidden" name ="token" value="{{.}}"/>
    <input type ="submit" value="upload">
</form>
</body>
</html>

上传文件主要三步处理:

1.表单中增加enctype = "multipart/from-data"

2.服务器端调用r.ParseMultipartForm,把上传的文件存储在内存和临时文件中

3.使用r.FormFile获取文件句柄,然后对文件进行存储等处理

5 访问数据库

5.1database/sql接口

什么是数据库驱动?

数据库驱动是一种API

用于连接应用程序与数据库之间的通信

可以通过数据库驱动对数据库完成相应的查询,插入和更新

但是Go没有数据库驱动,未开发着定义了一些标准接口口,开发者可以根据定义的接口来开发相应的数据库驱动

sql.Register

  • 用来注册数据库驱动
  • 会实现init函数,在init里面调用Register(name string ,driver driver.Driver)完成本驱动的注册

在database/sql内部通过一个map来存储用户定义的相应驱动

var drivres = make(map[string]driver.Driver)

我们可以通过这个函数同时注册多个数据库驱动,只要不重复

   import (
       "database/sql"
        _ "github.com/mattn/go-sqlite3"
       /*
       _是go设计的一个巧妙的地方,它作为一个占位符。然后我们引入后面的包但是不直接使用这个包中定义的资源
       /*
       原因就是我们在引入一个包的时候会init函数初始化,而init函数又恰好注册了这个数据库驱动
       */
       */
   )

driver.Driver

Driver是一个数据库驱动的接口,他定义了一个method:Open(name string),这个方法返回一个 数据库的Conn接口

type Driver interface{
Open(name string)(Conn,error)
}

返回的Conn只能用于进行一次goroutine操作

...
go goroutineA (Conn)  //执行查询操作
go goroutineB (Conn)  //执行插入操作
...

driver.Conn

Conn是一个数据库连接的接口定义,它定义了一系列方法,这个Conn只能应用在一个goroutine里面

type Conn interface{
    Prepare(query string) (Stmt,error)
    //返回与当前连接相关的Sql的准备状态,可以进行查询、删除操作
    Close() error
    //关闭当前连接,释放资源
    Begin() (Tx,error)
    //Tx是代表事务的,通过它可以对事务进行回滚和递交
}

driver.Stmt

Stmt是一种准备好的状态,和Conn相关联,而且只能应用于一个goroutine中,不能应用于多个goroutine

type Stmt interface{
Close() error
    //Close函数关闭当前的连接状态
NumInput() int
    //这个返回的是参数个数,当返回>=0 数据库驱动就会只能检查调用者的参数。当数据库驱动包不知道预留参数的时候,返回-1
Exec(args []Value) (Result,error)
    //用来执行Prepare准备好的squl,传入参数执行update/insert等操作,返回Result数据 
Query(args []Value) (Rows,error)
    //也是执行操作,执行select操作,返回Rows操作集
}

driver.Tx

事务处理一般就两个过程,递交或者回滚。数据库驱动里面也只需要实现这两个函数就可以

type Tx interface{
Commit() error
Rollback() error
}

driver.Execer

这是一个Conn可选择实现的接口

type Execer interface{
    Exec(query string,args []Value) (Result,error)
}

如果这个接口没有定义,那么在调用DB.Exec,就会首先调用Prepare返回Stmt,然后执行Stmt的Exec,然后关闭Stmt

driver.Result

这个是执行Update/Insert等操作的结果接口定义

type Result interface{
LastInsertId() (int64,error)
    //返回由数据库执行插入操作得到的自增ID号
RowAffected() (int64,error)
    //返回query操作影响的数据条目数
}

driver.Rows

Rows是执行查询返回的结果集接口定义

type Rows interface{
    Columns() []string
    //返回查询数据库表的字段信息
    //不是返回表的所有字段
    Close() error
    //关闭Rows迭代器
    Next(dest []Value) error
    //用来返回下一条数据,把数据传给dest
    //dest里面的数据必须是driver.Value的值除了string
    //数据中的string必须要转换成[]byte
}

driver.RowsAffected

是int64的别名

实现了Result接口

type RowAffected int64
func (RowsAffected) LastInsertId() (int64,error)
func (v RowsAffected) RowsAffected() (int64,error)

driver.Value

  • 是一个空接口,可以容纳任何数据

    type Value interface{}
    

Value是驱动需要操作的value

Value只能是下面的任意一种

int64
float64
bool
[]byte
string   [*]除了Rows.Next返回的不能是string.
time.Time
nil

driver.ValueConverter

  • 将一个普通的值转变为value
type ValueConverter interface{
    ConverterValue(v interface{}) (value,error)
}

ValueConverter有很多好处:

  • 转化driver.value到数据库相应的字段
  • 把数据库查询结果转化成driver.Value值
  • 在scan函数里面如何把driver.Value值转化成用户定义的值

driver.Valuer

Valuer接口定义了返回一个driver.Value的方式

type Valuer interface{
Value() (Value,error)
}

很多类型都实现了这个Value方法,用来自身与driver.Value的转化

一个驱动只要实现了这些接口就能完成增删查改等基本操作

database/sql

提供了一个更高阶的方法,用以简化数据库操作,同时内部还建议性地实现了一个conn pool

type DB struct{
driver driver.Driver
dsn string
mu sync.Mutex
freeConn []driver.Conn
closed bool
}

Open函数返回的是DB对象,里面有一个freeConn,它就是那个简易的连接池。

Web 应用程序的一部分

我们可以看到Web应用程序时执行以下操作的一段程序:

1.通过HTTP发送请求

2.处理HTTP请求信息并执行必要的工作

3.生成HTML并在HTTP的相应消息中返回它

Web应用程序有两个不同的部分:处理程序和模板引擎

  • 处理程序接收并处理从客户端发送的HTTP请求

处理程序是控制器也是模型

我们需要了解一下MVC模式

模型-视图-控制器模式是一种用于编写Web应用的流行模式

模型是底层数据的表示

视图是模型对用户的可视化

控制器通过控制用户的输入来修改模型

请求格式通常是应用程序本身的特权

格式:

http:///?

func main() {

     //首先创建一个多路复用器,用来重定向
	mux := http.NewServeMux()
    //创建一个文件服务器,用于提供静态文件的服务,接收http.FileSystem接口类型的参数
	//返回http.Handler类型的对象
    //http.Dir("/public")表示文件服务器的根目录为"/public"目录
    files := http.FileServer(http.Dir("/public"))
    
	mux.Handle("/static/", http.StripPrefix("/static/", files))
    

	mux.HandleFunc("/", index)

	server := &http.Server{
		Addr:    "0.0.0.0:8080",
		Handler: mux,
	}
	server.ListenAndServe()
}

files := http.FileServer(http.Dir("/public"))

->http.FileServer( a http.FileSystem)

所以说http.Dir(“/public”) 会返回一个实现了http.FileSystem的对象,这个对象是一个指定路径的文件系统

表示文件服务器的根目录是当前文件系统中的”/public“目录

当客户向服务端请求静态文件,FileServer get the information and get into FileSystem.And Then got into the “/public” to find if the file exit

func index(w http.ResponseWriter, r *http.Request) {
  files := []string{"templates/layout.html",
                    "templates/navbar.html",
                    "templates/index.html",}
  templates := template.Must(template.ParseFiles(files...))
  threads, err := data.Threads(); if err == nil {

    templates.ExecuteTemplate(w, "layout", threads)
  }
}

3.7(3.8 3.10 3.14)

HTML的基本概念和知识点

1.HTML:

  • 是一个用于创建网页结构的标记语言,由一系列标签组成,用于描述页面的结构和内容

2.HTML标签:

标签格式:<>

eg :xzc就是标签的名称

3.HTML元素:

通常成对出现:

比如开始标签和结束标签:

表示段落的开始

表示段落的结束

开始标签和结束标签之间的内容就是该元素的内容

4.HTML属性:

属性位于开始标签内部,并且具有名称和值

eg:

5.常用HTML标签

-

用来定义标签

用来定义段落

用来创建链接

6.HTML文档结构

HTML文档通常由<!DOCTYPE html>声明、<html>元素、<head>元素和<body>元素组成。

<!DOCTYPE html>声明用于指定文档类型,

<html>元素是文档的根元素,

<head>元素包含了文档的元信息,如标题、样式表和脚本等,而<body>元素包含了页面的可见内容。

7.HTML注释

注释以 结尾

中国


3.创建css文件,在css文件中编写并在HTML中引进来

1.在head中引用

```css
引用方式:head中添加<link href="css文件地址" rel="stylesheet">

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
   <link href="css/01.css" rel="stylesheet"/> 
</head>
<body>
    <h1>CSS使用方法3:在head中使用linked</h1>
</body>
</html>

2.在style中引用(style位于head和body中间)

引用方式:style中添加@import url(对应CSS文件地址)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>CSS使用方法3:在style中使用 @import url(地址) </h1>
</body>
</html>

选择器

1.元素(标签)选择器

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    /*
    	所有p标签字体都会变成红色
    	(也可以有其它格式设置,本处以颜色红举例)
    	标签选择器会选择所有对应的标签,即p标签
    */
    p{
        color: red;
    }
</style>
<body>
    <p>段落1</p>
    <p>段落2</p>
    <p>段落3</p>
    <p>段落4</p>
</body>
</html>

2.类选择器

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    /* 
        标签里面加上class="名字"
        .class名字  指定的会变成红色,即段落2以红色显示
    	(也可以有其它格式设置,本处以颜色红举例)
    */
    .p2{
        color: red;
    }
</style>
<body>
    <p>段落1</p>
    <p class="p2">段落2</p>
    <p>段落3</p>
    <p>段落4</p>
</body>
</html>

3.id选择器

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    /* 
        标签里面加上id="名字"
        # id名字  指定的会变成红色,即段落2以红色显示
        (也可以有其它格式设置,本处以颜色红举例)
    */
    #p2{
        color: red;
    }
</style>
<body>
    <p>段落1</p>
    <p id="p2">段落2</p>
    <p>段落3</p>
    <p>段落4</p>
</body>
</html>

5.选择器组

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    /* 
        如果两个选择器内部设置相同,那么可以合并两个选择器,中间用逗号隔开
    */
    .p2,#p3{
        color: red;
    }
</style>
<body>
    <p>段落1</p>
    <p class="p2">段落2</p>
    <p id="p3">段落3</p>
    <p>段落4</p>
</body>
</html>

6.派生选择器

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>

    /* 
        div 标签 :所有的标签都会变红(此处以变红为例,当然也可有有其它设置)
    */
    div p{
        color: red;
    }

</style>
<body>
    <p>p1</p>
    <p>p2</p>
    <p>p3</p>
    <p>p4</p>
    <p>p5</p>
    <span>
        <p>p6</p>
        <span>
            <p>p7</p>
        </span>
    </span>
    <p>p8</p>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>

    /* 
        div > 标签 :只有最外层的标签都会变红(此处以变红为例,当然也可有有其它设置)
    */
    div > p{
        color: red;
    }

</style>
<body>
    <p>p1</p>
    <p>p2</p>
    <p>p3</p>
    <p>p4</p>
    <p>p5</p>
    <span>
        <p>p6</p>
        <span>
            <p>p7</p>
        </span>
    </span>
    <p>p8</p>
</body>
</html>

伪类

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    /* 未点击百度一下之前的颜色 */
    a:link{
        color:rgb(8, 7, 7)
    }
    /* 鼠标放在百度一下上面的样子 */
    a:hover{
        font-size: 50px;
    }
    /* 访问过百度一下的颜色 */
    a:visited{
        color: rgb(161, 15, 15);
    }
    /* 鼠标点住百度一下没松开时候的颜色 */
    a:active{
        color: lawngreen;
    }
</style>
<body>
   <a href="https://www.baidu.com">百度一下</a> 
</body>
</html>

属性

1.文本属性:

font-family:设置文本字体
font-size:设置字体尺寸
font-weight:设置字体的粗细
color:字体严肃
text-align:字体对齐方式
line-height:行高
text-decoration:文本装饰
text-indent:文本缩进

2.背景属性

background-image:url("图片地址")   设置背景图片
background-color 设置背景颜色
background-position  背景图片的位置
background-repeat  背景的平铺方式——repeat 图片在垂直水平方向上重复
——repeat-x 图片在水平方向上重复
——repeat-y 图片在垂直方向上重复
——no-repeat 图片不重复
background-attachment:fixed       背景图片固定

3.列表属性

list-style-type: none没有标记  disc默认标记
circle 标记是实心圆
dquare  标记是实心方块
decimal  标记是数字

javascript基础知识

我们在HTML元素中的

document.write("Hello World") 可以写出“Hello World”

可以插入在部分和部分都可以

引用外部脚本的方法:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/469124.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

通过nginx+xray服务搭建及本地配置

一、xray服务配置 下载&#xff1a;https://github.com/XTLS/Xray-core 进入下载界面 这里我选择的是Xray-linux-64.zip 将文件解压到 /usr/local/xray 编辑配置文件/usr/local/xray/config.json uuid可以在v2ray客服端自动生成&#xff0c;也可以在UUID v4 生成器 - KKT…

高性能 MySQL 第四版(GPT 重译)(二)

第四章&#xff1a;操作系统和硬件优化 你的 MySQL 服务器的性能只能和它最弱的环节一样好&#xff0c;而运行 MySQL 的操作系统和硬件通常是限制因素。磁盘大小、可用内存和 CPU 资源、网络以及连接它们的所有组件都限制了系统的最终容量。因此&#xff0c;你需要仔细选择硬件…

腾讯云服务器多少钱一年?听完你可别后悔!

腾讯云服务器多少钱一年&#xff1f;61元一年起。2024年最新腾讯云服务器优惠价格表&#xff0c;腾讯云轻量2核2G3M服务器61元一年、2核2G4M服务器99元一年可买三年、2核4G5M服务器165元一年、3年756元、轻量4核8M12M服务器646元15个月、4核16G10M配置32元1个月、312元一年、8核…

考研数学|张宇还是武忠祥?怎么选?

我觉得张宇老师和武忠祥老师讲课实力都差不多&#xff0c;区别就在于风格的不同 张宇老师的讲课风格比较活泼&#xff0c;擅长调动学生的思维跟着课堂一起走&#xff0c;并且张宇老师发明了很多容易记的段子&#xff0c;但是虽然张宇老师段子多&#xff0c;一点也不妨碍他讲课…

网站制作基本流程|新手必收藏

网站制作基本流程|新手必收藏 我们选择了白嫖雨云的二级域名 浏览器输入https://www.rainyun.com/z22_ 创建账号然后选择一个你喜欢的子域名我建议后缀选择ates.top的 选择自定义地址&#xff0c;类型选择cname 现在要选择记录值了&#xff0c;有a&#xff0c;aa&#xff0c;tx…

数据结构 之 哈希表习题 力扣oj(附加思路版)

哈希表用法 哈希表&#xff1a;键 值对 键&#xff1a;可以看成数组下标&#xff0c;但是哈希表中的建可以是任意类型的&#xff0c;建不能重复,可以不是连续的 值&#xff1a;可以看成数组中的元素&#xff0c;值可以重复&#xff0c;也可以是任意类型的数据 #include<iost…

prompt开发生命周期

1.定义任务场景和成功标准 任务场景可分为简单任务&#xff1a;实体抽取、qa等 复杂任务&#xff1a;代码生成、创意写作等 在定义任务后&#xff0c;就要定义模型实现该任务的成功标准&#xff1a; 模型表现和准确率&#xff1b;延迟&#xff1b;价格。 2.开发测试用例 多…

CPU设计实战—异常处理指令

异常类型以及精确异常的处理 异常有点像中断&#xff0c;处理完还要回到原来的状态&#xff0c;所以需要对之前的状态进行保存。本CPU主要实现对以下异常的处理&#xff1a; 1.外部硬件中断 2.复位异常 3.系统调用异常&#xff08;发生在译码阶段&#xff09; 4.溢出异常&…

Spring6--基于注解管理Bean / 手写IOC

1. 基于注入管理Bean概念 Java 5 引入了注解&#xff08;Annotation&#xff09;这一特性&#xff0c;它允许程序员在源代码中插入元数据&#xff0c;这些元数据以标签形式存在&#xff0c;可以被编译器、类加载器或运行时环境所识别和处理。注解可以帮助开发者在不修改业务逻…

flex属性详解

flex布局&#xff0c;父元素属性可参考&#xff1a;flex布局 &#xff0c;本文主要介绍flex添加到子元素的属性。 <div class"father"><div class"left"></div><div class"middle"></div><div class"midd…

【01】htmlcssgit网络基础知识

一、html&css 防脱发神器 一图胜千言 使用border-box控制尺寸更加直观,因此,很多网站都会加入下面的代码 * {margin: 0;padding: 0;box-sizing: border-box; }颜色的 alpha 通道 颜色的 alpha 通道标识了色彩的透明度,它是一个 0~1 之间的取值,0 标识完全透明,1…

香港科技大学(广州)先进材料学域智能制造学域可持续能源与环境学域博士招生宣讲会——北京航空航天大学专场(暨全额奖学金政策)

香港科技大学&#xff08;广州&#xff09;先进材料学域&智能制造学域&可持续能源与环境学域博士招生宣讲会——北京航空航天大学专场&#xff08;暨全额奖学金政策&#xff09; 三个学域代表教授亲临现场&#xff0c;面对面答疑解惑助攻申请&#xff01;可带简历现场…

恒驰喜讯 | 亮相华为中国合作伙伴大会2024,荣膺最佳服务一致性奖等3大奖项

3月14日至15日&#xff0c;华为中国合作伙伴大会2024在深圳隆重召开。大会以“因聚而生&#xff0c;数智有为”为主题&#xff0c;面向数智化转型的浪潮&#xff0c;华为携手伙伴共同探讨如何通过强化“伙伴华为”体系&#xff0c;帮助客户抓住数智化转型的巨大机遇&#xff0c…

python-pandas基础学习

可参考&#xff1a; pandas&#xff1a;http://pandas.pydata.org/docs/user_guide/10min.html 一、基础知识 DataFrame 方法&#xff0c;可以将一组数据&#xff08;ndarray、series, map, list, dict 等类型&#xff09;转化为表格型数据 import pandas as pd data {name: …

第十五届蓝桥杯模拟考试III_物联网设计与开发官方代码分析

目录 前言&#xff1a;显示界面部分&#xff1a;页面切换:数值的轮回调整&#xff1a;传递数据&#xff1a; 前言&#xff1a; 这次模拟的效果很不好。85分&#xff0c;4h的限时我花了两天完成&#xff0c;这个时间是远远超出要求的&#xff0c;而且最后还只拿到56分&#xff0…

Java使用Selenium实现自动化测试以及全功能爬虫

前言 工作中需要抓取一下某音频网站的音频&#xff0c;我就用了两个小时学习弄了一下&#xff0c;竟然弄出来&#xff0c;这里分享记录一下。 springboot项目 Selenium Java使用Selenium实现自动化测试以及全功能爬虫 前言1 自动化测试2 java中集成Selenium3 添加浏览器驱动4…

什么是大型语言模型(LLM)?

大型语言模型 (LLM) 是一种能够理解和生成人类语言文本的机器学习模型。它们通过分析大量语言数据集来工作。 一、什么是大型语言模型 (LLM)&#xff1f; 大型语言模型 (LLM) 是一种人工智能 (AI) 程序&#xff0c;它可以识别和生成文本以及完成其他任务。LLM 经过了庞大的数据…

Xilinx LVDS ISERDESE2

ISERDESE2 7 系列 FPGA 是一款专用的串行到并行转换器,具有特定的时钟和逻辑功能,旨在促进高速源同步应用的实现。该ISERDESE2避免了在FPGA架构中设计解串器时遇到的额外时序复杂性. ISERDESE2功能包括: 1,专用解串器/串行转换器 ISERDESE2解串器可实现高速数据传输,而无需…

ssh免密登陆更换目标主机后无法连接

在进行hadoop分布式环境搭建时&#xff08;三台机&#xff0c;master&#xff0c;slave1&#xff0c;slave2&#xff09;&#xff0c;后期slave2系统出现问题&#xff0c;更换新机后&#xff0c;master与slave2文件传输失败&#xff1a; 以为是秘钥过期的问题&#xff0c;更换…

【好书推荐2】AI提示工程实战:从零开始利用提示工程学习应用大语言模型

【好书推荐2】AI提示工程实战&#xff1a;从零开始利用提示工程学习应用大语言模型 写在最前面AI辅助研发方向一&#xff1a;AI辅助研发的技术进展方向二&#xff1a;行业应用案例方向三&#xff1a;面临的挑战与机遇方向四&#xff1a;未来趋势预测方向五&#xff1a;与法规的…
最新文章