Example-Golang-CRUD-Gin
Executive Summary
核心观点(金字塔原理)
结论先行: 本文通过一个完整的Employee管理系统示例,展示了如何使用Go语言结合MySQL数据库实现Web应用的CRUD操作。
支撑论点:
- 技术栈清晰:使用database/sql + go-sql-driver/mysql + html/template构建完整Web应用
- 代码结构规范:分离数据库连接、业务处理函数、模板渲染,便于理解和扩展
- 实战导向:提供完整可运行的代码示例,包括SQL建表语句和HTML模板
SWOT 分析
| 维度 | 分析 |
|---|---|
| S 优势 | 代码简洁易懂、使用标准库无额外依赖、模板分离便于维护 |
| W 劣势 | 缺少错误处理最佳实践、数据库连接未使用连接池优化、无输入验证 |
| O 机会 | 可作为Go Web开发入门项目、可扩展集成Gin框架实现更复杂功能 |
| T 威胁 | 直接使用panic处理错误不适合生产环境、数据库凭据硬编码存在安全风险 |
适用场景
- Go语言Web开发入门学习
- 快速搭建内部管理系统原型
- 学习Go与MySQL数据库交互的基本模式
Example-Golang-CRUD-Gin
- 涉及内容,Golang, http/template, database/sql, go-sql-driver/mysql, net/http, gin;
- 目标: step1完成CRUD,step2结合Gin完成权限管理,文本上传等
step 1 完成基本的CRUD
go get -u github.com/go-sql-driver/mysql //安装依赖
- 创建表
CREATE TABLE `employee` (
`id` int(6) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
`city` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
- 创建main.go,写CRUD逻辑
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"html/template"
"net/http"
"log"
"strconv"
)
var templ = template.Must(template.ParseGlob("page/*"))
type Employee struct {
Id int
Name string
City string
}
func dbConn() (db *sql.DB) {
dbDriver := "mysql"
dbUser := "root"
dbPasswd := "root123456"
dbIP := "127.0.0.1"
dbPort := "3306"
dbName := "db_home"
db, err := sql.Open(dbDriver, dbUser+":"+dbPasswd+"@tcp("+dbIP+":"+dbPort+")/"+dbName)
if err != nil {
panic(err.Error())
}
return db
}
func Index(w http.ResponseWriter, r *http.Request) {
db := dbConn()
rowsData, err := db.Query("SELECT * FROM EMPLOYEE ORDER BY ID DESC ")
if err != nil {
panic(err.Error())
}
emp := Employee{}
res := []Employee{}
for rowsData.Next() {
var id int
var name, city string
err = rowsData.Scan(&id, &name, &city)
if err != nil {
panic(err.Error())
}
emp.Id = id
emp.Name = name
emp.City = city
res = append(res, emp)
}
templ.ExecuteTemplate(w, "Index", res)
defer db.Close()
}
func Show(w http.ResponseWriter, r *http.Request) {
db := dbConn()
keyId := r.URL.Query().Get("id")
rowsData, err := db.Query("SELECT * FROM EMPLOYEE WHERE id=?", keyId)
if err != nil {
panic(err.Error())
}
emp := Employee{}
for rowsData.Next() {
var id int
var name, city string
err := rowsData.Scan(&id, &name, &city)
if err != nil {
panic(err.Error())
}
emp.Id = id
emp.Name = name
emp.City = city
}
templ.ExecuteTemplate(w, "Show", emp)
defer db.Close()
}
func New(w http.ResponseWriter, r *http.Request) {
templ.ExecuteTemplate(w, "New", nil)
}
func Edit(w http.ResponseWriter, r *http.Request) {
db := dbConn()
keyId := r.URL.Query().Get("id")
rowsData, err :=db.Query("select * from employee where id=?", keyId)
if err != nil {
panic(err.Error())
}
emp := Employee{}
for rowsData.Next() {
var id int
var name, city string
err := rowsData.Scan(&id, &name, &city)
if err != nil {
panic(err.Error())
}
emp.Id = id
emp.Name = name
emp.City = city
}
templ.ExecuteTemplate(w, "Edit", emp)
defer db.Close()
}
func Insert(w http.ResponseWriter, r *http.Request) {
db := dbConn()
if r.Method == "POST" {
name := r.FormValue("name")
city := r.FormValue("city")
stmt, err:= db.Prepare("insert into employee(name,city) values (?,?)")
if err != nil {
panic(err.Error())
}
result, err := stmt.Exec(name, city)
if err != nil {
panic(err.Error())
}
LastInsertId, err := result.LastInsertId()
log.Println("INSERT: Name: " + name +",City: " + city +",LastInsertId:" + strconv.FormatInt(LastInsertId, 10))
}
defer db.Close()
http.Redirect(w, r, "/", 301)
}
func Update(w http.ResponseWriter, r *http.Request) {
db := dbConn()
if r.Method == "POST" {
name := r.FormValue("name")
city := r.FormValue("city")
id := r.FormValue("uid")
insForm, err := db.Prepare("UPDATE Employee SET name=?, city=? WHERE id=?")
if err != nil {
panic(err.Error())
}
result, e := insForm.Exec(name, city, id)
if e != nil {
panic(e.Error())
}
RowsAffected, e := result.RowsAffected()
log.Println("UPDATE: Name: " + name + " | City: " + city +",RowsAffected:"+strconv.FormatInt(RowsAffected, 10))
}
defer db.Close()
http.Redirect(w, r, "/", 301)
}
func Delete(w http.ResponseWriter, r *http.Request) {
db := dbConn()
empId := r.URL.Query().Get("id")
delForm, err := db.Prepare("DELETE FROM Employee WHERE id=?")
if err != nil {
panic(err.Error())
}
result, e := delForm.Exec(empId)
if e != nil {
panic(e.Error())
}
RowsAffected, e := result.RowsAffected()
log.Println("DELETE, id:", empId , ", RowsAffected:"+strconv.FormatInt(RowsAffected, 10))
defer db.Close()
http.Redirect(w, r, "/", 301)
}
func main() {
log.Println("Server started on: http://localhost:8080")
http.HandleFunc("/", Index)
http.HandleFunc("/show", Show)
http.HandleFunc("/new", New)
http.HandleFunc("/edit", Edit)
http.HandleFunc("/insert", Insert)
http.HandleFunc("/update", Update)
http.HandleFunc("/delete", Delete)
http.ListenAndServe(":8080", nil)
}
-
创建
page目录与main.go同级目录,并写好模版 -
Index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{{ define "Index" }}
{{ template "Header" }}
{{ template "Menu" }}
<h2> Registered </h2>
<table border="1">
<thead>
<tr>
<td>ID</td>
<td>Name</td>
<td>City</td>
<td>View</td>
<td>Edit</td>
<td>Delete</td>
</tr>
</thead>
<tbody>
{{ range . }}
<tr>
<td>{{ .Id }}</td>
<td>{{ .Name }}</td>
<td>{{ .City }}</td>
<td><a href="/show?id={{ .Id }}">View</a></td>
<td><a href="/edit?id={{ .Id }}">Edit</a></td>
<td><a href="/delete?id={{ .Id }}">Del</a></td>
</tr>
{{ end }}
{{ template "Footer" }}
{{ end }}
Header
1
2
3
4
5
6
7
8
9
10
{{ define "Header" }}
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Golang Gin Mysql Curd Example</title>
<meta charset="UTF-8" />
</head>
<body>
<h1>Golang Mysql Curd Example</h1>
{{ end }}
Menu
1
2
3
4
{{ define "Menu" }}
<a href="/">HOME</a> |
<a href="/new">NEW</a>
{{ end }}
Footer
1
2
3
4
{{ define "Footer" }}
</body>
</html>
{{ end }}
Show
1
2
3
4
5
6
7
8
{{ define "Show" }}
{{ template "Header" }}
{{ template "Menu" }}
<h2> Register {{ .Id }} </h2>
<p>Name: {{ .Name }}</p>
<p>City: {{ .City }}</p><br /> <a href="/edit?id={{ .Id }}">Edit</a></p>
{{ template "Footer" }}
{{ end }}
New
1
2
3
4
5
6
7
8
9
10
11
{{ define "New" }}
{{ template "Header" }}
{{ template "Menu" }}
<h2>New Name and City</h2>
<form method="POST" action="insert">
<label> Name </label><input type="text" name="name" /><br />
<label> City </label><input type="text" name="city" /><br />
<input type="submit" value="Save user" />
</form>
{{ template "Footer" }}
{{ end }}
Edit
1
2
3
4
5
6
7
8
9
10
11
12
{{ define "Edit" }}
{{ template "Header" }}
{{ template "Menu" }}
<h2>Edit Name and City</h2>
<form method="POST" action="update">
<input type="hidden" name="uid" value="{{ .Id }}" />
<label> Name </label><input type="text" name="name" value="{{ .Name }}" /><br />
<label> City </label><input type="text" name="city" value="{{ .City }}" /><br />
<input type="submit" value="Save user" />
</form><br />
{{ template "Footer" }}
{{ end }}
step2 结合Gin完成权限管理,文本上传等
Gin 框架简介
- Gin 是基于 httprouter 的高性能 Go Web 框架,支持中间件、路由分组、JSON 验证等特性,适合快速构建 RESTful API。
路由分组与中间件
- 使用
r.Group("/api/v1")实现版本化路由管理,便于 API 迭代升级;gin.Logger()和gin.Recovery()作为默认中间件,分别提供请求日志记录和 panic 恢复能力。
权限管理实现思路
- 通过 JWT 中间件拦截请求,验证 Token 合法性并将用户信息注入上下文
c.Set("userId", id),后续 Handler 可通过c.Get("userId")获取;RBAC 权限模型可借助 Casbin 库实现,灵活定义角色与资源的访问策略。
文件上传
- 使用
c.FormFile("file")获取客户端上传的文件,c.SaveUploadedFile(file, dst)将文件保存到指定路径;支持多文件上传场景,通过c.MultipartForm()批量处理。
参数绑定与验证
c.ShouldBindJSON(&req)可自动将请求体绑定到结构体,配合binding:"required,min=1"等 struct tag 实现参数校验,减少手动解析和验证的样板代码。
统一响应格式
- 封装
Response(c, code, data, msg)工具函数,统一 JSON 返回结构{"code": 0, "data": {}, "message": "success"},保证接口风格一致,方便前端对接与错误处理。