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

【Go语言成长之路】编写web应用

文章目录

  • 编写Web应用
    • 一、介绍
    • 二、创建项目
      • 2.1 创建wiki数据结构
      • 2.2 介绍net/http包(小插曲)
      • 2.3 使用 net/http 提供 wiki 页面
      • 2.4 编辑Pages
      • 2.5 html/template包
      • 2.6 处理不存在的页面
      • 2.7 保存页面
      • 2.8 错误处理
      • 2.9 模板缓存
      • 2.10 验证
      • 2.11 函数文字和闭包简介

编写Web应用

一、介绍

本教程涵盖:

  • 创建具有load和save方法的data结构体
  • 使用net/http包来构建web应用
  • 使用html/template包处理HTML模板
  • 使用regexp包验证用户的输入
  • 使用闭包

二、创建项目

~$ mkdir gowiki
~$ cd gowiki
~/gowiki$ go mod init example.com/wiki
go: creating new go.mod: module example.com/wiki
~/gowiki$ touch wiki.go # 在gowiki包内创建wiki.go文件

2.1 创建wiki数据结构

让我们从定义数据结构开始。wiki由多个相互关联的页面组成,每个页面都有一个标题和一个正文(页面内容)。在这里,我们将 Page 定义为一个结构体,具有表示标题和正文的字段。

type Page struct {Title stringBody  []byte // Body 元素是一个 []byte 而不是字符串,因为这是我们将使用的 io 库所期望的类型
}

Page描述了数据如何在内存中存储,但是如何持久存储呢?我们可以直接通过创建save方法来实现该功能:

// 这是一个名为 save 的方法,它接受一个指向 Page 的指针 p 作为其接收者。它不接受任何参数,并返回一个类型为 error 的值。此方法会将页面的正文保存到文本文件中。
// 若出现错误,则会返回相应的错误以便处理,如果一切顺利,Page.save() 将返回nil(指针、接口和其他一些类型的零值)。
func (p *Page) save() error {// 为了简单起见,使用标题作为文件名fileName := p.Title + ".txt"// 0600 作为第三个参数传递给 WriteFile,指示应仅为当前用户创建具有读写权限的文件。return os.WriteFile(fileName, p.Body, 0600)
}

除了保存页面之外,我们还需要加载页面:

// 返回指向使用正确的标题和主体值构造的 Page 文字的指针。
// 该函数的调用者现在可以检查第二个参数;如果为零,则已成功加载页面。如果不是,这将是一个错误,可以由调用者处理
func loadPage(title string) (*Page, error) {fileName := title + ".txt"body, err := os.ReadFile(fileName)if err != nil {return nil, err}return &Page{Title: title, Body: body}, nil
}

接下来就是编写main函数的内容,用于测试save和load的功能:

func main() {p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}p1.save()p2, _ := loadPage("TestPage")fmt.Println(string(p2.Body))
}

编译并执行此代码后,将创建一个名为 TestPage.txt 的文件,其中包含 p1 的内容。然后该文件将被读入 结构体p2,并将其 Body 元素打印到屏幕上。

~/gowiki$ go build wiki.go
~/gowiki$ ./wiki
This is a sample Page.

附: wiki.go的源码如下:

package mainimport ("fmt""os"
)type Page struct {Title stringBody  []byte
}func (p *Page) save() error {fileName := p.Title + ".txt"return os.WriteFile(fileName, p.Body, 0600)
}func loadPage(title string) (*Page, error) {fileName := title + ".txt"body, err := os.ReadFile(fileName)if err != nil {return nil, err}return &Page{Title: title, Body: body}, nil
}func main() {p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}p1.save()p2, _ := loadPage("TestPage")fmt.Println(string(p2.Body))
}

2.2 介绍net/http包(小插曲)

首先来看一个简单的web服务器的例子:

package mainimport ("fmt""log""net/http"
)// 函数处理程序的类型为http.HandlerFunc。它采用 http.ResponseWriter 和 http.Request 作为其参数。
// http.ResponseWriter 值组装 HTTP 服务器的响应;通过写入它,我们将数据发送到 HTTP 客户端。
// http.Request 是表示客户端 HTTP 请求的数据结构。 r.URL.Path 是请求 URL 的路径组成部分。结尾的 [1:] 表示“创建从第一个字符到末尾的 Path 子片段”。这会删除路径名中的前导“/”。
func handler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}func main() {// main 函数首先调用 http.HandleFunc,它告诉 http 包使用处理程序处理对 Web 根(“/”)的所有请求。http.HandleFunc("/", handler)// 然后,它调用 http.ListenAndServe,指定它应该侦听任何接口上的端口 8080(“:8080”)该函数将阻塞,直到程序终止。// ListenAndServe 始终返回错误,因为它仅在发生意外错误时返回。为了记录该错误,我们用 log.Fatal 包装函数调用。log.Fatal(http.ListenAndServe(":8080", nil))
}

如果您运行该程序并访问 URL:

http://localhost:8080/monkeys

该程序将显示一个页面,其中包含:

Hi there, I love monkeys!

2.3 使用 net/http 提供 wiki 页面

首先,让我们创建一个处理程序 viewHandler,它将允许用户查看 wiki 页面。它将处理前缀为“/view/”的 URL:

func viewHandler(w http.ResponseWriter, r *http.Request) {// 首先,该函数从 r.URL.Path(请求 URL 的路径组成部分)中提取页面标题。使用 [len("/view/"):] 重新分割路径,以删除请求路径的前导“/view/”组件。这是因为路径总是以“/view/”开头,这不是页面标题的一部分。title := r.URL.Path[len("/view/"):]p, _ := loadPage(title)// 然后该函数加载页面数据,使用一串简单的 HTML 格式化页面,并将其写入 w(http.ResponseWriter)。fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
}

之后重新编写main函数的内容以初始化该handler:

func main() {http.HandleFunc("/view/", viewHandler)log.Fatal(http.ListenAndServe(":8080", nil))
}

创建并且编辑test.txt文件,向该文件编写hello,world, 之后运行代码:

~/gowiki$ go build wiki.go
~/gowiki$ ./wiki

运行此 Web 服务器后,访问 http://localhost:8080/view/test 应显示一个标题为“test”的页面,其中包含“helloworld”一词。

附: wiki.go源码:

package mainimport ("fmt""log""net/http""os"
)type Page struct {Title stringBody  []byte
}func (p *Page) save() error {fileName := p.Title + ".txt"return os.WriteFile(fileName, p.Body, 0600)
}func loadPage(title string) (*Page, error) {fileName := title + ".txt"body, err := os.ReadFile(fileName)if err != nil {return nil, err}return &Page{Title: title, Body: body}, nil
}func viewHandler(w http.ResponseWriter, r *http.Request) {// 首先,该函数从 r.URL.Path(请求 URL 的路径组成部分)中提取页面标题。使用 [len("/view/"):] 重新分割路径,以删除请求路径的前导“/view/”组件。这是因为路径总是以“/view/”开头,这不是页面标题的一部分。title := r.URL.Path[len("/view/"):]p, _ := loadPage(title)// 然后该函数加载页面数据,使用一串简单的 HTML 格式化页面,并将其写入 w(http.ResponseWriter)。fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
}func main() {http.HandleFunc("/view/", viewHandler)log.Fatal(http.ListenAndServe(":8080", nil))
}

2.4 编辑Pages

让我们创建两个新的处理程序:一个名为 editHandler 来显示“编辑页面”表单,另一个名为 saveHandler 来保存通过表单输入的数据。

​ 首先将handler添加到main函数内:

func main() {http.HandleFunc("/view/", viewHandler)http.HandleFunc("/edit/", editHandler)http.HandleFunc("/save/", saveHandler)log.Fatal(http.ListenAndServe(":8080", nil))
}

​ 函数 editHandler 加载页面(或者,如果不存在,则创建一个空的 Page 结构),并显示 HTML 表单。

func editHandler(w http.ResponseWriter, r *http.Request) {title := r.URL.Path[len("/edit/"):]p, err := loadPage(title)if err != nil {p = &Page{Title: title}}fmt.Fprintf(w, "<h1>Editing %s</h1>"+"<form action=\"/save/%s\" method=\"POST\">"+"<textarea name=\"body\">%s</textarea><br>"+"<input type=\"submit\" value=\"Save\">"+"</form>",p.Title, p.Title, p.Body)
}

注:这个函数可以正常工作,但是所有硬编码的 HTML 都很丑陋。当然,还有更好的方法。

2.5 html/template包

​ html/template 包是 Go 标准库的一部分。我们可以使用 html/template 将 HTML 保存在单独的文件中,从而允许我们更改编辑页面的布局,而无需修改底层 Go 代码。

​ 让我们创建一个包含 HTML 表单的模板文件。打开一个名为 edit.html 的新文件,并添加以下行:

<h1>Editing {{.Title}}</h1><form action="/save/{{.Title}}" method="POST">
<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea></div>
<div><input type="submit" value="Save"></div>
</form>

注: 模板指令括在双花括号内。 printf “%s” .Body 指令是一个函数调用,它将 .Body 作为字符串而不是字节流输出,与对 fmt.Printf 的调用相同。 html/template 包有助于保证模板操作仅生成安全且外观正确的 HTML。

修改 editHandler 以使用模板,而不是硬编码的 HTML:

func editHandler(w http.ResponseWriter, r *http.Request) {title := r.URL.Path[len("/edit/"):]p, err := loadPage(title)if err != nil {p = &Page{Title: title}}// 函数 template.ParseFiles 将读取 edit.html 的内容并返回 *template.Template。t, _ := template.ParseFiles("edit.html")// 方法 t.Execute 执行模板,将生成的 HTML 写入 http.ResponseWriter。Title 和 .Body 点标识符指的是 p.Title 和 p.Body。t.Execute(w, p)
}

由于我们现在正在使用模板,因此让我们为 viewHandler 创建一个名为 view.html 的模板:

<h1>{{.Title}}</h1><p>[<a href="/edit/{{.Title}}">edit</a>]</p><div>{{printf "%s" .Body}}</div>

相应地修改viewHandler:

func viewHandler(w http.ResponseWriter, r *http.Request) {title := r.URL.Path[len("/view/"):]p, _ := loadPage(title)t, _ := template.ParseFiles("view.html")t.Execute(w, p)
}

**请注意,我们在两个处理程序中使用了几乎完全相同的模板代码。让我们通过将模板代码移动到它自己的函数来删除这种重复, **并修改处理程序以使用该函数:

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {t, _ := template.ParseFiles(tmpl + ".html")t.Execute(w, p)
}
func viewHandler(w http.ResponseWriter, r *http.Request) {title := r.URL.Path[len("/view/"):]p, _ := loadPage(title)renderTemplate(w, "view", p)
}
func editHandler(w http.ResponseWriter, r *http.Request) {title := r.URL.Path[len("/edit/"):]p, err := loadPage(title)if err != nil {p = &Page{Title: title}}renderTemplate(w, "edit", p)
}

2.6 处理不存在的页面

如果访问 /view/APageThatDoesntExist 会怎样?您将看到一个包含 HTML 的页面。这是因为它忽略了 loadPage 的错误返回值,并继续尝试填充没有数据的模板。相反,如果请求的页面不存在,它应该将客户端重定向到编辑页面,以便可以创建内容:

func viewHandler(w http.ResponseWriter, r *http.Request) {title := r.URL.Path[len("/view/"):]p, err := loadPage(title)if err != nil {// http.Redirect 函数将 HTTP 状态代码 http.StatusFound (302) 和 Location 标头添加到 HTTP 响应中。http.Redirect(w, r, "/edit/"+title, http.StatusFound)return}renderTemplate(w, "view", p)
}

2.7 保存页面

函数 saveHandler 将处理位于编辑页面上的表单的提交,让我们实现处理程序:

func saveHandler(w http.ResponseWriter, r *http.Request) {title := r.URL.Path[len("/save/"):]body := r.FormValue("body")p := &Page{Title: title, Body: []byte(body)}p.save()http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

2.8 错误处理

我们的程序中有几个地方会忽略错误。这是不好的做法,尤其是因为当错误确实发生时,程序将出现意外的行为。更好的解决方案是处理错误并向用户返回错误消息。这样,如果出现问题,服务器将按照我们想要的方式运行,并且可以通知用户。

​ 首先,我们来处理一下renderTemplate中的错误:

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {t, err := template.ParseFiles(tmpl + ".html")if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}err = t.Execute(w, p)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)}
}

http.Error 函数发送指定的 HTTP 响应代码(在本例中为“内部服务器错误”)和错误消息。将其放在一个单独的函数中的决定已经得到了回报。

​ 现在让我们修复 saveHandler:

func saveHandler(w http.ResponseWriter, r *http.Request) {title := r.URL.Path[len("/save/"):]body := r.FormValue("body")p := &Page{Title: title, Body: []byte(body)}err := p.save()if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

2.9 模板缓存

​ 这段代码效率低下:每次渲染页面时,renderTemplate 都会调用 ParseFiles。更好的方法是在程序初始化时调用 ParseFiles 一次,将所有模板解析为单个 *Template。然后我们可以使用 ExecuteTemplate 方法来渲染特定的模板。

​ 首先我们创建一个名为 templates 的全局变量,并使用 ParseFiles 对其进行初始化

var templates = template.Must(template.ParseFiles("edit.html", "view.html")) // 函数 template.Must 是一个方便的包装器,当传递一个非零错误值时,它会发生恐慌,否则返回不改变的 *Template 。恐慌在这里是适当的;如果无法加载模板,唯一明智的做法是退出程序。
// ParseFiles 函数采用任意数量的字符串参数来标识我们的模板文件,并将这些文件解析为以基本文件名命名的模板。如果我们要向程序添加更多模板,我们会将它们的名称添加到 ParseFiles 调用的参数中。

​ 然后我们修改 renderTemplate 函数以使用适当模板的名称调用 templates.ExecuteTemplate 方法:

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {err := templates.ExecuteTemplate(w, tmpl+".html", p) // 请注意,模板名称是模板文件名,因此我们必须将“.html”附加到 tmpl 参数中。if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)}
}

2.10 验证

正如您可能已经观察到的,该程序存在严重的安全缺陷:用户可以提供在服务器上读取/写入的任意路径。为了缓解这种情况,我们可以编写一个函数来使用正则表达式验证标题。

首先,将“regexp”添加到导入列表中。然后我们可以创建一个全局变量来存储我们的验证表达式:

var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$") // 函数 regexp.MustCompile 将解析并编译正则表达式,并返回一个 regexp.Regexp。 MustCompile 与 Compile 的不同之处在于,如果表达式编译失败,它会发生混乱,而 Compile 将返回一个错误作为第二个参数。

现在,让我们编写一个使用 validPath 表达式来验证路径并提取页面标题的函数:

// 如果标题有效,它将与零错误值一起返回。如果标题无效,该函数将向 HTTP 连接写入“404 Not Found”错误,并向处理程序返回错误。要创建新错误,我们必须导入错误包。
func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {m := validPath.FindStringSubmatch(r.URL.Path)if m == nil {http.NotFound(w, r)return "", errors.New("invalid Page Title")}return m[2], nil // The title is the second subexpression.
}

让我们在每个处理程序中调用 getTitle:

func viewHandler(w http.ResponseWriter, r *http.Request) {title, err := getTitle(w, r)if err != nil {return}p, err := loadPage(title)if err != nil {http.Redirect(w, r, "/edit/"+title, http.StatusFound)return}renderTemplate(w, "view", p)
}
func editHandler(w http.ResponseWriter, r *http.Request) {title, err := getTitle(w, r)if err != nil {return}p, err := loadPage(title)if err != nil {p = &Page{Title: title}}renderTemplate(w, "edit", p)
}
func saveHandler(w http.ResponseWriter, r *http.Request) {title, err := getTitle(w, r)if err != nil {return}body := r.FormValue("body")p := &Page{Title: title, Body: []byte(body)}err = p.save()if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

2.11 函数文字和闭包简介

​ 捕获每个处理程序中的错误条件会引入大量重复代码。如果我们可以将每个处理程序包装在一个执行验证和错误检查的函数中怎么办? Go 的函数字面量提供了一种强大的抽象功能的方法,可以帮助我们。

​ 首先,我们重写每个处理程序的函数定义以接受标题字符串:

func viewHandler(w http.ResponseWriter, r *http.Request, title string)
func editHandler(w http.ResponseWriter, r *http.Request, title string)
func saveHandler(w http.ResponseWriter, r *http.Request, title string)

​ 现在让我们定义一个包装函数,它接受上述类型的函数,并返回 http.HandlerFunc 类型的函数(适合传递给函数 http.HandleFunc):

func makeHandler(fn func (http.ResponseWriter, *http.Request, string)) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {// Here we will extract the page title from the Request,// and call the provided handler 'fn'}
}

返回的函数称为闭包,因为它包含在其外部定义的值。在本例中,变量 fn (makeHandler 的单个参数)包含在闭包中。变量 fn 将是我们的保存、编辑或查看处理程序之一。

现在我们可以从 getTitle 获取代码并在此处使用它(进行一些小的修改):

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {m := validPath.FindStringSubmatch(r.URL.Path)if m == nil {http.NotFound(w, r)return}fn(w, r, m[2])}
}

​ makeHandler 返回的闭包是一个带有 http.ResponseWriter 和 http.Request 的函数(换句话说,是一个 http.HandlerFunc)。闭包从请求路径中提取标题,并使用 validPath 正则表达式对其进行验证。如果标题无效,将使用 http.NotFound 函数将错误写入 ResponseWriter。如果标题有效,则将使用 ResponseWriter、Request 和标题作为参数来调用随附的处理函数 fn。

​ 现在我们可以在 main 中使用 makeHandler 包装处理函数,然后再将它们注册到 http 包中:

func main() {http.HandleFunc("/view/", makeHandler(viewHandler))http.HandleFunc("/edit/", makeHandler(editHandler))http.HandleFunc("/save/", makeHandler(saveHandler))log.Fatal(http.ListenAndServe(":8080", nil))
}

最后,我们从处理函数中删除了对 getTitle 的调用,使它们变得更加简单:

func viewHandler(w http.ResponseWriter, r *http.Request, title string) {p, err := loadPage(title)if err != nil {http.Redirect(w, r, "/edit/"+title, http.StatusFound)return}renderTemplate(w, "view", p)
}
func editHandler(w http.ResponseWriter, r *http.Request, title string) {p, err := loadPage(title)if err != nil {p = &Page{Title: title}}renderTemplate(w, "edit", p)
}
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {body := r.FormValue("body")p := &Page{Title: title, Body: []byte(body)}err := p.save()if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

最后,重新运行一遍代码:

~/gowiki$ go build wiki.go
~/gowiki$ ./wiki

访问 http://localhost:8080/view/ANewPage 应该会向您显示页面编辑表单。然后,您应该能够输入一些文本,单击“保存”,然后重定向到新创建的页面。

附: wiki.go源码:

package mainimport ("html/template""log""net/http""os""regexp"
)type Page struct {Title stringBody  []byte
}func (p *Page) save() error {filename := p.Title + ".txt"return os.WriteFile(filename, p.Body, 0600)
}func loadPage(title string) (*Page, error) {filename := title + ".txt"body, err := os.ReadFile(filename)if err != nil {return nil, err}return &Page{Title: title, Body: body}, nil
}func viewHandler(w http.ResponseWriter, r *http.Request, title string) {p, err := loadPage(title)if err != nil {http.Redirect(w, r, "/edit/"+title, http.StatusFound)return}renderTemplate(w, "view", p)
}func editHandler(w http.ResponseWriter, r *http.Request, title string) {p, err := loadPage(title)if err != nil {p = &Page{Title: title}}renderTemplate(w, "edit", p)
}func saveHandler(w http.ResponseWriter, r *http.Request, title string) {body := r.FormValue("body")p := &Page{Title: title, Body: []byte(body)}err := p.save()if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}http.Redirect(w, r, "/view/"+title, http.StatusFound)
}var templates = template.Must(template.ParseFiles("edit.html", "view.html"))func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {err := templates.ExecuteTemplate(w, tmpl+".html", p)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)}
}var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {m := validPath.FindStringSubmatch(r.URL.Path)if m == nil {http.NotFound(w, r)return}fn(w, r, m[2])}
}func main() {http.HandleFunc("/view/", makeHandler(viewHandler))http.HandleFunc("/edit/", makeHandler(editHandler))http.HandleFunc("/save/", makeHandler(saveHandler))log.Fatal(http.ListenAndServe(":8080", nil))
}

相关文章:

【Go语言成长之路】编写web应用

文章目录 编写Web应用一、介绍二、创建项目2.1 创建wiki数据结构2.2 介绍net/http包&#xff08;小插曲&#xff09;2.3 使用 net/http 提供 wiki 页面2.4 编辑Pages2.5 html/template包2.6 处理不存在的页面2.7 保存页面2.8 错误处理2.9 模板缓存2.10 验证2.11 函数文字和闭包…...

Java 对象头、Mark Word、monitor与synchronized关联关系以及synchronized锁优化

1. 对象在内存中的布局分为三块区域&#xff1a; &#xff08;1&#xff09;对象头&#xff08;Mark Word、元数据指针和数组长度&#xff09; 对象头&#xff1a;在32位虚拟机中&#xff0c;1个机器码等于4字节&#xff0c;也就是32bit&#xff0c;在64位虚拟机中&#xff0…...

安宝特分享 | 如何利用AR技术革新医疗实践:从远程急救到多学科协作

AR技术在国内外医院的应用 在现代医疗环境中&#xff0c;患者面临的挑战依然严峻&#xff1a;看病难、看病远、看病急。这些问题不仅影响了患者的治疗效果&#xff0c;也让医务工作者倍感压力。幸运的是&#xff0c;随着增强现实&#xff08;AR&#xff09;技术的发展&#xf…...

小米note pro一代(leo)线刷、twrp、magisk、TODO: android源码编译

本文主要说android5 整体思路 android 5.1 twrp magisk Zygisk(Riru) Dreamland(xposed) Riru不支持android5.1, 因此只能选择Zygisk : 如果你正在使用 Android 5&#xff0c;你必须使用 Zygisk 因为 Riru 并不支持 Android 5. 基于magisk之上的xposed 其中提到的 作者…...

vue2-基础核心

vue简介 动态构建用户界面的渐进式 JavaScript 框架 vue的特点: 遵循MVVM模式 采用组件化模式&#xff0c;提高代码复用率&#xff0c;让代码更好维护 声明式编码&#xff0c;无需直接操作DOM&#xff0c;提高开发效率&#xff0c;编码简洁、体积小&#xff0c;运行效率高 本…...

使用 前端技术 创建 QR 码生成器 API1

前言 QR码&#xff08;Quick Response Code&#xff09;是一种二维码&#xff0c;于1994年开发。它能快速存储和识别数据&#xff0c;包含黑白方块图案&#xff0c;常用于扫描获取信息。QR码具有高容错性和快速读取的优点&#xff0c;广泛应用于广告、支付、物流等领域。通过扫…...

天云数据参编行业标准|《Maas模型服务协议要求》标准正式发布

随着各行业对大模型的应用需求日益增多&#xff0c;模型即服务&#xff08;MaaS&#xff09;发展迅速&#xff0c;MaaS将AI模型以服务的方式提供给用户&#xff0c;降低模型使用门槛。当前产业界已推出诸多MaaS产品&#xff0c;并集成和提供了大量模型服务&#xff0c;然而对于…...

观察者模式和订阅模式

观察者模式和订阅模式在概念上是相似的&#xff0c;它们都涉及到一个对象&#xff08;通常称为“主题”或“发布者”&#xff09;和多个依赖对象&#xff08;称为“观察者”或“订阅者”&#xff09;之间的关系。然而&#xff0c;尽管它们有相似之处&#xff0c;但在某些方面也…...

Mac设置java环境变量

Mac电脑中存在多个jdk版本,如何配置java环境变量为指定版本jdk? 一、查看所有已安装的 JDK 版本 /usr/libexec/java_home -V二、临时设置 export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)三、永久设置 如果需要永久使用指定版...

Sentinel服务保护

Sentinel是阿里巴巴开源的一款服务保护框架&#xff0c;目前已经加入SpringCloudAlibaba中。官方网站&#xff1a; home | Sentinel Sentinel 的使用可以分为两个部分: 核心库&#xff08;Jar包&#xff09;&#xff1a;不依赖任何框架/库&#xff0c;能够运行于 Java 8 及以…...

Linux内核USB2.0驱动框架分析--USB包

一&#xff0c; 包的组成 每个包都由SOP&#xff08;包起始域&#xff09;、SYNC&#xff08;同步域&#xff09;、Packet Content&#xff08;包内容&#xff09;、EOP&#xff08;包结束域&#xff09;四部分组成&#xff0c;其中SOP、SYNC、EOP为所有包共有的域&#xff0c…...

SpringCloud Gateway转发请求到同一个服务的不同端口

SpringCloud Gateway默认不支持将请求路由到一个服务的多个端口 本文将结合Gateway的处理流程&#xff0c;提供一些解决思路 需求背景 公司有一个IM项目&#xff0c;对外暴露了两个端口8081和8082&#xff0c;8081是springboot启动使用的端口&#xff0c;对外提供一些http接口…...

win10局域网加密共享设置

1、创建共享账户 我的电脑右键选择管理 选择本地用户和组 -> 用户 双击用户 在空白区域右键,新建用户 然后创建用户 点击创建后 2、设置网络 右下角网络右键...

论文阅读——Performance Evaluation of Passive Tag to Tag Communications(一)

文章目录 摘要一、互耦对监听器标签输入阻抗的影响A. 无限细偶极子互阻抗的理论研究B. 电细偶极子的情况&#xff1a;理论与模拟C. 印刷偶极子的情况&#xff1a;电磁模拟与测量 二、T2T 通信系统的性能评估总结 论文来源&#xff1a;https://ieeexplore.ieee.org/document/970…...

Docker Registry(镜像仓库)详解

Docker Registry&#xff08;镜像仓库&#xff09;详解 Docker Registry&#xff0c;即Docker镜像仓库&#xff0c;是Docker生态系统中一个至关重要的组件。它负责存储、管理和分发Docker镜像&#xff0c;为Docker容器提供镜像资源。本文将深入探讨Docker Registry的功能、结构…...

17. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--主币种设置

记账模块是我们项目的核心模块&#xff0c;也是用户使用最多的模块&#xff0c;因此这个模块的东西比较多&#xff0c;我们要分为多个部分编写代码。 一、需求 币种设置的需求涉及到了我们前面编写的代码&#xff0c;我们来具体看一下需求。 编号需求说明1主币种设置1. 用户…...

ChatPaper.ai:通过阅读文献高效学习英语的新思路

前言 作为一名学习者&#xff0c;我们常常会遇到这样的困境&#xff1a;想提高英语水平&#xff0c;但单纯背单词缺乏语境&#xff1b;想读专业文献&#xff0c;却被英语障碍所困扰。今天我要分享一个工具 ChatPaper.ai&#xff0c;它让我找到了将英语学习和专业阅读完美结合的…...

.NET9 - 新功能体验(二)

书接上回&#xff0c;我们继续来聊聊.NET9和C#13带来的新变化。 01、新的泛型约束 allows ref struct 这是在 C# 13 中&#xff0c;引入的一项新的泛型约束功能&#xff0c;允许对泛型类型参数应用 ref struct 约束。 可能这样说不够直观&#xff0c;简单来说就是Span、ReadO…...

探索Python PDF处理的奥秘:pdfrw库揭秘

文章目录 探索Python PDF处理的奥秘&#xff1a;pdfrw库揭秘1. 背景&#xff1a;为何选择pdfrw&#xff1f;2. pdfrw是什么&#xff1f;3. 如何安装pdfrw&#xff1f;4. 五个简单的库函数使用方法4.1 读取PDF信息4.2 修改PDF元数据4.3 旋转PDF页面4.4 提取PDF中的图片4.5 合并P…...

网络爬虫——爬虫项目案例

本节将全面讲解如何通过实战爬虫项目解决复杂问题。结合最新技术和实际开发需求&#xff0c;案例将涵盖完整开发流程&#xff0c;包括需求分析、实现代码、优化方法和常见问题解决。力求实现高效、可扩展的爬虫项目架构&#xff0c;帮助开发者提升实战能力。 案例 1&#xff1a…...

JAVA中的Lamda表达式

JAVA中的Lamda表达式 Lambda 表达式的语法使用场景示例代码1.代替匿名内部类2. 带参数的 Lambda 表达式3. 与集合框架结合使用4. 使用 Stream 操作 总结 Java 的 Lambda 表达式是 Java 8 引入的一个新特性&#xff0c;用于简化代码&#xff0c;特别是在处理函数式编程时。Lambd…...

经典游戏:飞机大战游戏python设计与实现

《飞机大战》是一款经典的二维飞行射击游戏&#xff0c;其核心玩法是控制玩家飞机与敌机作战&#xff0c;通过击落敌机获取分数并尽量避免被敌机击中。根据提供的代码&#xff0c;飞机大战的设计和实现可以分为以下几个主要部分&#xff1a;游戏初始化、游戏界面设计、玩家控制…...

网络爬虫——常见问题与调试技巧

在开发网络爬虫的过程中&#xff0c;开发者常常会遇到各种问题&#xff0c;例如网页加载失败、数据提取错误、反爬机制限制等。以下内容将结合实际经验和技术方案&#xff0c;详细介绍解决常见错误的方法&#xff0c;以及如何高效调试和优化爬虫代码。 1. 爬虫过程中常见的错误…...

深入理解TensorFlow中的形状处理函数

摘要 在深度学习模型的构建过程中&#xff0c;张量&#xff08;Tensor&#xff09;的形状管理是一项至关重要的任务。特别是在使用TensorFlow等框架时&#xff0c;确保张量的形状符合预期是保证模型正确运行的基础。本文将详细介绍几个常用的形状处理函数&#xff0c;包括get_…...

macOS 无法安装第三方app,启用任何来源的方法

升级新版本 MacOS 后&#xff0c;安装下载的软件时&#xff0c;不能在 ”安全性与隐私” 中找不到 ”任何来源” 选项。 1. 允许展示任何来源 点击 启动器 (Launchpad) – 其他 (Other) – 终端 (Terminal)&#xff1a; 打开终端后&#xff0c;输入以下代码回车&#xff1a; …...

Leetcode148. 排序链表(HOT100)

链接 我写的错误代码&#xff1a; class Solution { public:ListNode* sortList(ListNode* head) {if (!head || !head->next)return head;ListNode* fast head;ListNode* slow head;while (fast&&fast->next) {fast fast->next->next;slow slow->…...

Linux线程_线程互斥_线程同步

一.线程互斥 1.进程线程间的互斥相关概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区互斥&#xff1a;任何时刻&#xff0c;互斥保证有且只有一个执行流进入临界区&…...

【Spiffo】环境配置:VScode+Windows开发环境

摘要&#xff1a; 在Linux下直接开发有时候不习惯快捷键和操作逻辑&#xff0c;用Windows的话其插件和工具都更齐全、方便&#xff0c;所以配置一个Windows的开发环境能一定程度提升效率。 思路&#xff1a; 自己本地网络内远程连接自己的虚拟机&#xff08;假定用的是虚拟机…...

DevExpress控件 基本使用

DevExpress控件 一、DevExpress简介 1、所有编辑器的公共功能 全部都可以绑定数据&#xff1b; 全部都可以独立使用或用于由 Developer Express 提供的容器控件 (XtraGrid、XtraVerticalGrid、XtraTreeList 和 XtraBars) 内的内置编辑&#xff1b; 全部都使用相同的样式、外…...

设计模式——装饰器模式

装饰器模式是结构型设计模式&#xff0c;在Python中有一个非常著名的装饰器wrapper&#xff0c;它的实现方法就是使用了该设计模式&#xff0c;装饰器可以修饰类也可以修饰函数。 从类的设计上说&#xff0c;他的本质是在不定义子类的情况下动态的给对象添加一些额外的功能。举…...

【编程题目】列表、元组及集合

一.列表的题目 题目1:列表反转与排序 描述:给定一个整数列表,首先反转该列表,然后对其进行升序排序。最后输出处理后的列表。输入:一个整数列表,例如 [3, 1, 4, 1, 5, 9]输出:处理后的列表,例如 [1, 1, 3, 4, 5, 9]示例:input_list = [3, 1, 4, 1, 5, 9] # 你的代码 …...

【大数据学习 | Spark-Core】RDD的缓存(cache and checkpoint)

1. 单应用缓存&#xff1a;cache 1.1 cache算子 cache算子能够缓存中间结果数据到各个executor中&#xff0c;后续的任务如果需要这部分数据就可以直接使用避免大量的重复执行和运算。 rdd 存储级别中默认使用的算子cache算子&#xff0c;cache算子的底层调用的是persist算子…...

自主研发,基于PHP+ vue2+element+ laravel8+ mysql5.7+ vscode开发的不良事件管理系统源码,不良事件管理系统源码

不良事件上报系统源码&#xff0c;不良事件管理系统源码&#xff0c;PHP源码 不良事件上报系统通过 “事前的人员知识培训管理和制度落地促进”、“事中的事件上报和跟进处理”、 以及 “事后的原因分析和工作持续优化”&#xff0c;结合预存上百套已正在使用的模板&#xff0…...

哈希表理解与底层模拟实现

内容摘要 本文内容包括红黑树和哈希表的性能比较逻辑分析及实现、哈希表的概念、哈希表映射关系建立的最常用的两种方法直接地址法和除留余数法介绍、介绍了哈希冲突的原因以及解决解决哈希冲突的方法、负载因子的概念、哈希表的扩容、开散列实现哈希表的思路及代码实现、闭散列…...

docker compose的安装和使用

1. Docker Compose 简介 Docker Compose 是一个工具&#xff0c;用于定义和运行多容器的 Docker 应用。通过编写一个 docker-compose.yml 文件&#xff0c;可以一次性启动所有容器&#xff0c;并且方便管理容器之间的依赖。 2. 安装 Docker Compose 前提条件 确保已安装 Do…...

17种Kubernetes安全检测工具详解

随着Kubernetes的广泛应用,确保其安全性就显得尤为重要。好在现有很多优秀的安全检测工具,可以帮助我们发现和修复Kubernetes集群中的安全隐患。本文将全面介绍17种常用的Kubernetes安全检测工具。 Kube-benchKube-hunterKubesec.ioTrivyKube-auditKube-secKube-vulnkube-scana…...

Python遥感开发之CGCS2000转换WGS84地理坐标系

Python遥感开发之CGCS2000转换WGS84地理坐标系 1 CGC2000坐标系介绍2 WGS84地理坐标系介绍3 代码实现CGCS2000转换WGS84地理坐标系 前言&#xff1a;主要借助pyproj实现从CGCS2000高斯-克吕格3度带&#xff08;EPSG:4547&#xff09;转换到WGS84地理坐标系&#xff08;EPSG:432…...

FAX动作文件优化脚本(MAX清理多余关键帧插件)

大较好,为大家介绍一个节省FBX容量的插件!只保留有用的动画轴向,其他不参与动画运动的清除! 一.插件目的:: 1.我们使用的U3D引擎产生的游戏资源包容量太大,故全方位优化动画资源; 2.在max曲线编辑器内,点取轴向太过麻烦,费事,直观清除帧大大提高效率。 如: 二:…...

Springboot集成ElasticSearch实现minio文件内容全文检索

一、docker安装Elasticsearch &#xff08;1&#xff09;springboot和Elasticsearch的版本对应关系如下&#xff0c;请看版本对应&#xff1a; 注意安装对应版本&#xff0c;否则可能会出现一些未知的错误。 &#xff08;2&#xff09;拉取镜像 docker pull elasticsearch:7…...

Python 中的 | 符号

Python 中的 | 符号 正文用法 1用法2 正文 今天遇到了一个符号 |&#xff0c;本文将对符号 | 的意思进行说明。 x: int 1 print(x) # 1上述代码中&#xff0c;:int 表示的是注释内容&#xff0c;这个在 python 中的注释 一文中我们已经进行了说明。 用法 1 有些时候我们会…...

网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务

网安基础知识|IDS入侵检测系统|IPS入侵防御系统|堡垒机|VPN|EDR|CC防御|云安全-VDC/VPC|安全服务 IDS入侵检测系统 Intrusion Detection System 安全检测系统&#xff0c;通过监控网络流量、系统日志等信息&#xff0c;来检测系统中的安全漏洞、异常行为和入侵行为。 分为&am…...

记录elasticsearch-analysis-dynamic-synonym从8.15.0升级到8.16.0所遇到的问题

记录elasticsearch-analysis-dynamic-synonym从8.15.0升级到8.16.0所遇到的问题 一、打包步骤 步骤一、linux系统下执行elasticsearch-module中的build.sh脚本 步骤二、maven环境下elasticsearch-cluster-runner执行maven install命令安装到本地maven仓库。 步骤三、修改版…...

IDEA怎么定位java类所用maven依赖版本及引用位置

在实际开发中&#xff0c;我们可能会遇到需要搞清楚代码所用依赖版本号及引用位置的场景&#xff0c;便于排查问题&#xff0c;怎么通过IDEA实现呢&#xff1f; 可以在IDEA中打开项目&#xff0c;右键点击maven的pom.xml文件&#xff0c;或者在maven窗口下选中项目&#xff0c;…...

react native 安装好apk后无法打开

react native 打包好apk安装完成&#xff0c;没有打开app按钮&#xff0c; 在AndroidManifest.xml中 <intent-filter><action android:name"android.intent.action.MAIN" /><category android:name"android.intent.category.LAUNCHER" /&…...

HTML5 SVG

HTML5 SVG SVG(Scalable Vector Graphics)是一种基于XML的图像格式,用于在网页上创建矢量图形。与传统的位图图像(如PNG和JPEG)不同,SVG图像可以无限放大而不失真,因为它们是由直线和曲线定义的数学路径,而不是像素点。HTML5支持直接在网页中嵌入SVG,使得网页设计更加…...

【LeetCode每日一题】——485.最大连续 1 的个数

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时空频度】九【代码实现】十【提交结果】 一【题目类别】 数组 二【题目难度】 LeetCode 三【题目编号】 485.最大连续 1 的个数 四【题目描述】 给定…...

Python语言就业方向有哪些?

Python语言的就业方向非常广泛&#xff0c;涵盖了多个领域和职位&#xff0c;主要包括&#xff1a; Web开发 Python在Web开发领域有着广泛的应用&#xff0c;特别是通过框架如Django、Flask等。Python可以帮助开发者快速搭建网站&#xff0c;与各种前端技术集成&#xff0c;从而…...

SpringSecurity创建一个简单的自定义表单的认证应用

1、SpringSecurity 自定义表单 在 Spring Security 中创建自定义表单认证应用是一个常见的需求&#xff0c;特别是在需要自定义登录页面、认证逻辑或添加额外的表单字段时。以下是一个详细的步骤指南&#xff0c;帮助你创建一个自定义表单认证应用。 2、基于 SpringSecurity 的…...

wpf 事件转命令的方式

1&#xff0c;方式1 <StackPanel Background"Transparent"><StackPanel.InputBindings><KeyBinding Command"{Binding ChangeColorCommand}"CommandParameter"{Binding ElementNamecolorPicker, PathSelectedItem}"Key"{Bi…...

Docker 容器的初始化设置

虽然现在Conntainerd 大有取代Docker作为容器运行时的趋势&#xff0c;但是docker还是有自己的优势在。尤其是对于开发者来讲&#xff0c;使用Docker 比使用 containerd 方便很多&#xff0c;尤其是在Docker Desktop等工具的加持下。 本文主要面向Docker的初、中级学者&#xf…...