【go项目01_学习记录10】

操作数据库

  • 1 插入数据
  • 2 显示文章
    • 2.1 修改 articlesShowHandler() 函数
    • 2.2 代码解析
  • 3 编辑文章
    • 3.1 添加路由
    • 3.2 编辑articlesEditHandler()
    • 3.3 新建 edit 模板
    • 3.4 代码重构
    • 3.5 完善articlesUpdateHandler()
    • 3.6 测试更新
    • 3.7 封装表单验证

1 插入数据

.
.
.
func articlesStoreHandler(w http.ResponseWriter, r *http.Request) {...// 检查是否有错误if len(errors) == 0 {lastInsertID, err := saveArticleToDB(title, body)if lastInsertID > 0 {fmt.Fprint(w, "插入成功,ID 为"+strconv.FormatInt(lastInsertID, 10))} else {checkError(err)w.WriteHeader(http.StatusInternalServerError)fmt.Fprint(w,  "500 服务器内部错误")}} else {...}
}func saveArticleToDB(title string, body string) (int64, error) {// 变量初始化var (id   int64err  errorrs   sql.Resultstmt *sql.Stmt)// 1. 获取一个 prepare 声明语句stmt, err = db.Prepare("INSERT INTO articles (title, body) VALUES(?,?)")// 例行的错误检测if err != nil {return 0, err}// 2. 在此函数运行结束后关闭此语句,防止占用 SQL 连接defer stmt.Close()// 3. 执行请求,传参进入绑定的内容rs, err = stmt.Exec(title, body)if err != nil {return 0, err}// 4. 插入成功的话,会返回自增 IDif id, err = rs.LastInsertId(); id > 0 {return id, nil}return 0, err
}
.
.
.

在Go语言中,defer stmt.Close()是一种常见的用法,用于在函数执行完毕后延迟关闭资源。具体来说,defer关键字用于延迟执行一个函数调用,这个函数调用通常是用来释放资源或执行清理操作。在这个例子中,stmt.Close()表示在当前函数执行完毕后,会调用stmt对象的Close()方法来关闭资源。
这种用法对于确保资源的正确释放非常有用,因为无论函数是通过正常返回还是发生异常终止,defer语句都会被执行。这样可以避免资源泄漏和确保程序的稳定性。

fmt.Fprint(w, “插入成功,ID 为”+strconv.FormatInt(lastInsertID, 10))
具体来说,strconv.FormatInt函数的第一个参数是要转换的整数值,即lastInsertID,第二个参数是指定转换的进制。在这里,第二个参数是10,表示要将整数转换为10进制的字符串。
例如,如果lastInsertID的值为123,那么strconv.FormatInt(123, 10)将返回字符串"123"。

rs, err = stmt.Exec(title, body)
返回值是一个 sql.Result 对象rs,定义如下

type Result interface {
// 使用 INSERT 向数据插入记录,数据表有自增 ID 时,该函数有返回值
LastInsertId() (int64, error)
// 表示影响的数据表行数,常用于 UPDATE/DELETE 等 SQL 语句中
RowsAffected() (int64, error)
}

因为我们的 articles 表里有设置 id 字段为自增 ID,故在我们的代码中,使用 rs.LastInsertId() 来判断是否执行成功,成功的话就返回这条新创建数据的 ID:

// 4. 插入成功的话,会返回自增 ID
if id, err = rs.LastInsertId(); id > 0 {return id, nil
}

访问localhost:3000/articles/create并填入测试数据
在这里插入图片描述

在这里插入图片描述

数据库中的表

2 显示文章

显示文章分两个步骤:
(1)读取数据
(2)渲染模板

2.1 修改 articlesShowHandler() 函数

.
.
.// Article  对应一条文章数据
type Article struct {Title, Body stringID          int64
}func articlesShowHandler(w http.ResponseWriter, r *http.Request) {// 1. 获取 URL 参数vars := mux.Vars(r)id := vars["id"]// 2. 读取对应的文章数据article := Article{}query := "SELECT * FROM articles WHERE id = ?"err := db.QueryRow(query, id).Scan(&article.ID, &article.Title, &article.Body)// 3. 如果出现错误if err != nil {if err == sql.ErrNoRows {// 3.1 数据未找到w.WriteHeader(http.StatusNotFound)fmt.Fprint(w, "404 文章未找到")} else {// 3.2 数据库错误checkError(err)w.WriteHeader(http.StatusInternalServerError)fmt.Fprint(w, "500 服务器内部错误")}} else {// 4. 读取成功fmt.Fprint(w, "读取成功,文章标题 —— "+article.Title)}
}
.
.
.

2.2 代码解析

QueryRow() 来读取单条数据

// 2. 读取对应的文章数据
article := Article{}
query := “SELECT * FROM articles WHERE id = ?”
err := db.QueryRow(query, id).Scan(&article.ID, &article.Title, &article.Body)

在这里插入图片描述
渲染模板
修改代码

.
.
.
func articlesShowHandler(w http.ResponseWriter, r *http.Request) {...// 3. 如果出现错误if err != nil {...} else {// 4. 读取成功,显示文章tmpl, err := template.ParseFiles("resources/views/articles/show.gohtml")checkError(err)err = tmpl.Execute(w, article)checkError(err)}
}
.
.
.

模板文件resources/views/articles/show.gohtml

<!DOCTYPE html>
<html lang="en">
<head><title>{{ .Title }} —— 我的技术博客</title><style type="text/css">.error {color: red;}</style>
</head>
<body><p>ID: {{ .ID }}</p><p>标题: {{ .Title }}</p><p>内容:{{ .Body }}</p>
</body>
</html>

保存成功后访问 localhost:3000/articles/1
在这里插入图片描述

log.Fatal(err):这是一个标准库log包中的函数,用于输出错误信息并终止程序执行。它会将err作为参数输出到标准错误输出,并调用os.Exit(1)来终止程序执行。这样可以确保在遇到严重错误时,程序会立即停止运行。

3 编辑文章

3.1 添加路由

在这里插入图片描述

3.2 编辑articlesEditHandler()

.
.
.func articlesEditHandler(w http.ResponseWriter, r *http.Request) {// 1. 获取 URL 参数vars := mux.Vars(r)id := vars["id"]// 2. 读取对应的文章数据article := Article{}query := "SELECT * FROM articles WHERE id = ?"err := db.QueryRow(query, id).Scan(&article.ID, &article.Title, &article.Body)// 3. 如果出现错误if err != nil {if err == sql.ErrNoRows {// 3.1 数据未找到w.WriteHeader(http.StatusNotFound)fmt.Fprint(w, "404 文章未找到")} else {// 3.2 数据库错误checkError(err)w.WriteHeader(http.StatusInternalServerError)fmt.Fprint(w, "500 服务器内部错误")}} else {// 4. 读取成功,显示表单updateURL, _ := router.Get("articles.update").URL("id", id)data := ArticlesFormData{Title:  article.Title,Body:   article.Body,URL:    updateURL,Errors: nil,}tmpl, err := template.ParseFiles("resources/views/articles/edit.gohtml")checkError(err)err = tmpl.Execute(w, data)checkError(err)}
}func articlesUpdateHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "更新文章")
}func articlesIndexHandler(w http.ResponseWriter, r *http.Request) {
.
.
.

3.3 新建 edit 模板

<!DOCTYPE html>
<html lang="en">
<head><title>编辑文章 —— 我的技术博客</title><style type="text/css">.error {color: red;}</style>
</head>
<body><form action="{{ .URL }}" method="post"><p><input type="text" name="title" value="{{ .Title }}"></p>{{ with .Errors.title }}<p class="error">{{ . }}</p>{{ end }}<p><textarea name="body" cols="30" rows="10">{{ .Body }}</textarea></p>{{ with .Errors.body }}<p class="error">{{ . }}</p>{{ end }}<p><button type="submit">更新</button></p></form>
</body>
</html>

测试http://localhost:3000/articles/1/edit
在这里插入图片描述
在这里插入图片描述

3.4 代码重构

articlesEditHandler()和articlesShowHandler()中有重复的代码,将其提取出来封装成函数。这样做不仅可以少写代码,也可以提高代码的可维护性。
一般不需要封装会影响返回结果的逻辑处理,所以注释 3 中的错误处理与模板渲染部分我们保持不变。
在这里插入图片描述
重构articlesEditHandler()和articlesShowHandler () 函数

在这里插入图片描述

在这里插入图片描述

3.5 完善articlesUpdateHandler()

.
.
.
func articlesUpdateHandler(w http.ResponseWriter, r *http.Request) {// 1. 获取 URL 参数id := getRouteVariable("id", r)// 2. 读取对应的文章数据_, err := getArticleByID(id)// 3. 如果出现错误if err != nil {if err == sql.ErrNoRows {// 3.1 数据未找到w.WriteHeader(http.StatusNotFound)fmt.Fprint(w, "404 文章未找到")} else {// 3.2 数据库错误checkError(err)w.WriteHeader(http.StatusInternalServerError)fmt.Fprint(w, "500 服务器内部错误")}} else {// 4. 未出现错误// 4.1 表单验证title := r.PostFormValue("title")body := r.PostFormValue("body")errors := make(map[string]string)// 验证标题if title == "" {errors["title"] = "标题不能为空"} else if utf8.RuneCountInString(title) < 3 || utf8.RuneCountInString(title) > 40 {errors["title"] = "标题长度需介于 3-40"}// 验证内容if body == "" {errors["body"] = "内容不能为空"} else if utf8.RuneCountInString(body) < 10 {errors["body"] = "内容长度需大于或等于 10 个字节"}if len(errors) == 0 {// 4.2 表单验证通过,更新数据query := "UPDATE articles SET title = ?, body = ? WHERE id = ?"rs, err := db.Exec(query, title, body, id)if err != nil {checkError(err)w.WriteHeader(http.StatusInternalServerError)fmt.Fprint(w, "500 服务器内部错误")}// √ 更新成功,跳转到文章详情页if n, _ := rs.RowsAffected(); n > 0 {showURL, _ := router.Get("articles.show").URL("id", id)http.Redirect(w, r, showURL.String(), http.StatusFound)} else {fmt.Fprint(w, "您没有做任何更改!")}} else {// 4.3 表单验证不通过,显示理由updateURL, _ := router.Get("articles.update").URL("id", id)data := ArticlesFormData{Title:  title,Body:   body,URL:    updateURL,Errors: errors,}tmpl, err := template.ParseFiles("resources/views/articles/edit.gohtml")checkError(err)err = tmpl.Execute(w, data)checkError(err)}}
}
.
.
.

在这里插入图片描述

3.6 测试更新

访问 localhost:3000/articles/1/edit ,修改内容:
在这里插入图片描述

在这里插入图片描述
再次访问 localhost:3000/articles/1/edit ,将标题修改到只有两个字,点击更新:
在这里插入图片描述
在这里插入图片描述

3.7 封装表单验证

articlesUpdateHandler() 中的表单验证与 articlesStoreHandler() 使用同一套代码,将其抽出来作为单独的函数。

//封装表单验证
func validateArticleFormData(title string, body string) map[string]string {errors := make(map[string]string)//验证标题if title == "" {errors["title"] = "标题不能为空"} else if utf8.RuneCountInString(title) < 3 || utf8.RuneCountInString(title) > 40 {errors["title"] = "标题长度需介于 3-40"}//验证内容if body == "" {errors["body"] = "内容不能为空"} else if utf8.RuneCountInString(body) < 10 {errors["body"] = "内容长度需大于或等于10个字节"}return errors
}

validateArticleFormData() 函数应用到上文提到的两个函数中
在这里插入图片描述
在这里插入图片描述
测试 localhost:3000/articles/1/edit , 都正常。


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

厚德提问大佬答4:AI绘画生成的心得

遇到难题不要怕&#xff01;厚德提问大佬答&#xff01; 厚德提问大佬答 你是否对AI绘画感兴趣却无从下手&#xff1f;是否有很多疑问却苦于没有大佬解答带你飞&#xff1f;从此刻开始这些问题都将迎刃而解&#xff01;你感兴趣的话题&#xff0c;厚德云替你问&#xff0c;你解…

车载电子电器架构 —— 应用软件开发(中)

车载电子电器架构 —— 应用软件开发(中) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…

算法设计课第五周(贪心法实现活动选择问题)

目录 一、【实验目的】 二、【实验内容】 三、实验源代码 一、【实验目的】 &#xff08;1&#xff09;熟悉贪心法的设计思想 &#xff08;2&#xff09;理解贪心法的最优解与正确性证明之间的关系 &#xff08;3&#xff09;比较活动选择的各种“贪心”策略&#xff0c;…

动态IP避坑指南:如何挑选合适的动态代理IP?

在如今的网络环境中&#xff0c;使用动态IP代理成为实现隐私保护、访问受限内容和提高网络效率的一种常见方式&#xff0c;选择合适的国外动态IP代理可以让我们的业务处理事半功倍。面对市面上琳琅满目的选择&#xff0c;如何挑选购买适合自己的动态IP代理服务呢&#xff1f;在…

【数据结构】手把手带你玩转线性表

前言&#xff1a; 哈喽大家好&#xff0c;我是野生的编程萌新&#xff0c;首先感谢大家的观看。数据结构的学习者大多有这样的想法&#xff1a;数据结构很重要&#xff0c;一定要学好&#xff0c;但数据结构比较抽象&#xff0c;有些算法理解起来很困难&#xff0c;学的很累。我…

弱监督语义分割-对CAM的生成过程进行改进1

一、仿射变换图像结合正则项优化CAM生成 论文&#xff1a;Self-supervised Equivariant Attention Mechanism for Weakly Supervised Semantic Segmentation &#xff08;CVPR,2020&#xff09; 1.SEAM方法 孪生网络架构&#xff08;Siamese Network Architecture&#xff09…

2024.5.10

TCP服务器端 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//设置窗口大小和窗口大小固定this->resize(727,879);this->setFixedSize(727,879);//创建…

STC8增强型单片机开发【定时器Timer⭐】

目录 一、引言 二、定时器基础知识 三、STC8定时器配置 四、代码示例 五、总结 一、引言 在单片机开发中&#xff0c;定时器&#xff08;Timer&#xff09;是一个极其重要的组件&#xff0c;它允许开发者基于时间触发各种事件或任务。STC8增强型单片机作为一款功能丰富的…

使用2G内存求20亿个数字中出现次数最多的N个

又是一个TOP -N的题目 我看了一下CSDN上大多数人的回答和GPT说的差不多&#xff0c;都是说使用哈希之类的&#xff1b; 我今天说一下我的解法&#xff0c;首先说一下不太快的基础解法 20亿数字使用uint32需要80GB&#xff0c; &#xff08;1&#xff09;分为40块读取&#…

c++opencv Project3 - License Plate Detector

俄罗斯车牌识别案例&#xff1a;实时识别车牌&#xff0c;并且读取到指定文件夹中。 惯例先展示结果图&#xff1a; 对于摄像头读取图片进行车牌匹配&#xff0c;原理和人脸识别其实是一致的。 利用训练好的模型进行匹配即可。可参考&#xff1a; 对视频实现人脸识别-CSDN博…

电能表远程抄表系统是什么?

电能表远程抄表系统是一种优秀的电力管理方法&#xff0c;它通过自动化的形式搜集、解决与分析电能表的信息&#xff0c;进而取代了传统人工抄水表方法。其主要原理是运用物联网技术、通讯技术和大数据处理技术&#xff0c;完成对电度表数据信息实时、远程控制获取和管理方法。…

探索无界知识:用 ChatGPT 的原理学习任何事物!

为避免文章重复&#xff0c;您的文本已通过更改句式、用词以及句子结构进行了修改。现在的文本应该能更好地满足去重的需求&#xff1a; 从ChatGPT原理出发&#xff0c;我们探讨GPT如何启发人类学习和构建个人知识体系。 1. 明确学习目标 机器学习必须依靠目标函数。同样&…

【Qt 开发基础体系】QMap 类和 QHash 类以及 QVector 类

文章目录 1.QMap 详解1.1 QMap 的介绍1.2 QMap 的具体用法如下1.3 QmultiMap类 2.QHash 详解3. QMap 和 QHash 的对比4. QVector 详解 1.QMap 详解 1.1 QMap 的介绍 &#x1f427;① QMap<key,T>提供一个从类型为Key的键到类型为T的值的映射。通常&#xff0c;QMap存储的…

STC8增强型单片机开发【串口调试UART⭐⭐】

目录 一、引言 二、UART基础知识 三、STC8 UART配置 四、代码示例 上列代码中所需的库函数文件&#xff1a; 引入库函数的流程&#xff1a; 五、总结 一、引言 在单片机开发中&#xff0c;串口调试&#xff08;UART&#xff09;是一种常用的通信方式&#xff0c;用于实现…

linux grep命令搜索指定路径

在Linux开发的过程中grep这个搜索命令&#xff0c;是必不可少的存在。它可以快速的搜索出来我们需要的关键字所在的位置。 有助于我们快速分析定位问题。 下面&#xff0c;分享一个简单实用的小技巧。 原始grep 最终grep grep过滤掉二进制的文件 -I选项 结论 这样子是不…

126.删除链表的倒数第N个节点(力扣)

题目描述 代码解决&#xff08;双指针&#xff09; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, Li…

全视通助力珠海市井岸镇卫生院新院,建设智慧病房

5月6日&#xff0c;位于珠海市斗门区的井岸镇卫生院新院正式启用&#xff0c;面向市民开诊。新院各诊区就医秩序井然&#xff0c;总体情况良好。据统计&#xff0c;截至开诊当天11点30分&#xff0c;新院门诊共接诊347人次&#xff0c;预防接种81人次&#xff0c;儿童体检33人次…

网络相关笔记

IPv4地址 IPv4地址通常以“点分十进制”形式书写&#xff0c;即四个0-255之间的十进制数&#xff0c;各数之间用英文句点&#xff08;.&#xff09;分隔&#xff0c;例如&#xff1a;192.0.2.1。总共32位的地址空间可以表示大约42亿个不同的地址。 IPv4地址结构包括&#xff…

HashSet扩容机制

HashSet底层是HashMap,第一次添加的时候,table数组扩容到16,临界值是16*加载因子(默认是0.75),到达临界值进行扩容。 HashSet<Integer> hashSet = new HashSet<>();hashSet.add(5);hashSet.add(2);hashSet.add(5);hashSet.add(8);hashSet.add(1);当new一个H…

【C++STL详解(十)】--------priority_queue的模拟实现

目录 前言 一、堆的向上调整算法 二、堆的向下调整算法 三、优先队列模拟实现 Ⅰ、接口总览 Ⅱ、各个接口实现 1.构造函数 2.仿函数 3.向上调整 4.向下调整 5.其余接口 Ⅲ、完成代码 前言 上节内容我们简单的介绍了关于priority_queue的使用内容&#xff0c;我们明白…