完成gin小任务
参考文档:
https://www.kancloud.cn/jiajunxi/ginweb100/1801414
https://github.com/hanjialeOK/going
最终代码地址:https://github.com/qinliangql/gin_mini_test.git
学习
1.安装go
wget https://dl.google.com/go/go1.20.2.linux-amd64.tar.gz
tar -C /usr/local -zxf go1.20.2.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
// All Tools installed by go are in $GOPATH.
export GOPATH=$PATH:/usr/local/go
export PATH=$PATH:$GOPATH/bin
// https://goproxy.cn is faster than aliyun.
export GOPROXY=https://goproxy.cn
验证安装是否成功
go version
hello world
mkdir hello && cd /hello
go mod init example/hello
创建一个hello.go
package mainimport "fmt"func main() {fmt.Println("Hello, World!")
}
在hello下运行
go run hello.go
做一个映射出来的
安装
go get github.com/gin-gonic/gin
我映射了端口8001
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {c.String(http.StatusOK, "Hello World")})router.Run(":8001")
}
go run hello.go
2.安装MySQL
// For ubuntu 18.04
sudo apt install mysql-server
// start mysql
service mysql start
Change the password for mysql
// login
mysql -u root
// change the password for mysql
use mysql;
update user set authentication_string='' where user='root';
// newpassword: 123456
alter user 'root'@'localhost' identified with mysql_native_password by '123456';
// exit
quit
// restart mysql
service mysql restart
Create database and table.
mysql -u root -p
create database gobase;
use gobase;
CREATE TABLE `account` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(20) NOT NULL,`email` varchar(50) NOT NULL,`password` varchar(30) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
查看数据库信息(先quit再重新打开mysql)
show databases;
查看table
use gobase;
\\ List Table info
show tables;
\\ Look structure of table
desc account;
Task
1.使用Gin创建一个API,允许用户通过提供姓名,电子邮件和密码来创建新账户。该API应验证所有字段是否存在以及电子邮件格式是否正确。
安装依赖
go get github.com/gin-gonic/gin
go get gorm.io/driver/mysql
go get gorm.io/gorm
代码:
package mainimport ("fmt""net/http""net/mail""github.com/gin-gonic/gin""gorm.io/driver/mysql""gorm.io/gorm"// "regexp"
)type UserInfo struct {ID int `form:"id"`Name string `form:"name" binding:"required"`Email string `form:"email" binding:"required,email"`Password string `form:"password" binding:"required,min=6"`
}func main() {// 创建一个默认的 Gin 路由器r := gin.Default()// 创建一个路由,允许用户提交新账户信息r.POST("/api/register", create)// 启动 Gin 服务器,默认监听在 8001 端口r.Run(":8001")
}// isValidEmail 检查电子邮件格式是否正确
func isValidEmail(email string) bool {_, err := mail.ParseAddress(email)// 如果解析正确,err会变为nilreturn err == nil
}func create(c *gin.Context) {// 绑定请求体到 UserInfo 结构体类型的form var form UserInfoif err := c.ShouldBindJSON(&form); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 检查电子邮件格式是否正确if !isValidEmail(form.Email) {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email format"})return}// 打开数据库,3306是MySQL默认端口dsn := "root:123456@tcp(127.0.0.1:3306)/gobase"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// check whether the name exists.var user UserInfodb.Table("account").Where("name = ?", form.Name).First(&user)if (user != UserInfo{}) {c.JSON(http.StatusBadRequest, gin.H{"error": "name exists!"})return}// check whether the email has been used.db.Table("account").Where("email = ?", form.Email).First(&user)if (user != UserInfo{}) {c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("email %s has been used!", form.Email)})return}// 保存用户到数据库(:= 对于没有声明的变量会自动声明)user = UserInfo{Name: form.Name, Email: form.Email, Password: form.Password}result := db.Table("account").Create(&user)if result.Error != nil {c.JSON(http.StatusBadRequest, gin.H{"error": result.Error})return}// 这里只是返回成功消息c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Account %s created successfully!",user.Name)})
}
测试代码:
-X 指定发送的请求类型为 POST 请求
-H 设置请求头,指定请求的内容类型为 JSON 格式
curl -X POST -H "Content-Type: application/json" -d '{"name":"QL","email":"qinl@example.com","password":"password"}' http://localhost:8001/api/register
查看数据库当前情况
service mysql start
mysql -u root -p
show databases;
use gobase;
show tables;
SELECT * FROM account;
quit
2.使用Gin实现一个API,允许用户通过提供电子邮件和密码来检索其帐户信息。该API应验证电子邮件和密码是否正确,然后返回用户信息。
新加代码:
// 新增路由,允许用户通过提供电子邮件和密码来检索其帐户信息,相当于登陆
r.POST("/api/login", login)/*
// 处理登录请求的函数 (这种必须要求输入完整的UserInfo类型信息才行,所以放弃)
func login(c *gin.Context) {// 绑定请求体到 UserInfo 结构体类型的 formvar form UserInfoif err := c.ShouldBindJSON(&form); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 打开数据库dsn := "root:123456@tcp(127.0.0.1:3306)/gobase"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 查询数据库中是否存在匹配的用户记录var user UserInforesult := db.Table("account").Where("email = ? AND password = ?", form.Email, form.Password).First(&user)if result.Error != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email or password"})return}// 返回匹配的用户信息c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Welcom user %s !", user.Name)})
}
*/func login(c *gin.Context) {// 绑定请求体到 UserInfo 结构体类型的 formemail := c.Query("email")password := c.Query("password")// 打开数据库dsn := "root:123456@tcp(127.0.0.1:3306)/gobase"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 查询数据库中是否存在匹配的用户记录var user UserInforesult := db.Table("account").Where("email = ? AND password = ?", email, password).First(&user)if result.Error != nil {c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid email:%s or password:%s",email,password)})return}// 返回匹配的用户信息c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Welcom user %s !", user.Name)})
}
测试代码:
# 正确案例
curl -X POST "http://localhost:8001/api/login?email=qinl@example.com&password=password"
# 错误案例
curl -X POST "http://localhost:8001/api/login?email=qinl@example.com&password=654321"
3.添加一个API,使用Gin允许用户更新其帐户信息。该API应验证用户是否已通过身份验证并且所有字段是否存在,然后将用户信息更新到数据库中。
安装依赖
go get github.com/golang-jwt/jwt/v5
更新的代码:
package mainimport ("fmt""net/http""net/mail""time""github.com/gin-gonic/gin""gorm.io/driver/mysql""gorm.io/gorm""github.com/golang-jwt/jwt/v5"
)// 创建一个 JWT(JSON Web Token)字符串。
// JWT 是一种用于在网络应用之间安全传递声明的开放标准。var jwtKey = []byte("my_secret_key") // 用于签名和验证 JWT 的密钥// input: email || output:string,error
func createToken(email string) (string, error) {token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{"email": email,"exp": time.Now().Add(time.Minute * 10).Unix(), // 有效时长10分钟})return token.SignedString(jwtKey)
}func validateToken(tokenString string) (*jwt.Token, error) {token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])}return jwtKey, nil})if err != nil {return nil, err}if _, ok := token.Claims.(jwt.MapClaims); !ok || !token.Valid {return nil, fmt.Errorf("bad token.")}return token, nil
}func validateCookie(c *gin.Context) (*jwt.Token, error) {tokenString, err := c.Cookie("gin_cookie")if err != nil {return nil, err}token, err := validateToken(tokenString)if err != nil {return nil, err}return token, nil
}func main() {// 创建一个默认的 Gin 路由器r := gin.Default()// 创建一个路由,允许用户提交新账户信息r.POST("/api/register", create) // 新增路由,允许用户通过提供电子邮件和密码来检索其帐户信息,相当于登陆r.POST("/api/login", login)r.POST("/api/update", update) // 新增的路由,用于更新用户信息// 启动 Gin 服务器,默认监听在 8001 端口r.Run(":8001")
}func login(c *gin.Context) {// ...// Create the JWT string.token, err := createToken(user.Email)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create token"})return}// tell the user it's ok. 3600 是最大存活时间1hc.SetCookie("gin_cookie", token, 3600, "/", "", false, true)// 返回匹配的用户信息c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Welcom user %s !", user.Name)})
}func update(c *gin.Context) {token, err := validateCookie(c)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// get email from cookieemail := token.Claims.(jwt.MapClaims)["email"]// new passwordpassword := c.Query("password")name := c.Query("name")// updatedsn := "root:123456@tcp(127.0.0.1:3306)/gobase"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// retrieve the user by email.var user UserInfodb.Table("account").Where("email = ?", email).First(&user)if (user == UserInfo{}) {c.JSON(http.StatusBadRequest, gin.H{"error": "this email has not been registered."})return}// 更新用户信息if name != "" {user.Name = name}if password != "" {user.Password = password}// 保存更新后的用户信息到数据库if err := db.Table("account").Save(&user).Error; err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update user"})return}// 返回更新成功的消息c.JSON(http.StatusOK, gin.H{"message": "User information updated successfully"})
}
测试代码,用 -i 可以更细致的看到信息,目前还是在curl里面传递cookie的
curl -i -X POST "http://localhost:8001/api/login?email=qinl@example.com&password=password"
curl -i -X POST "http://localhost:8001/api/update?password=newpassword" --cookie "gin_cookie=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InFpbmxAZXhhbXBsZS5jb20iLCJleHAiOjE3MTI4MDU0NTl9.VTLihEYTy-kWZjZhLrwpwIMuPgimpm-DVnYwvhoKnNs"
修改后的数据库
4. 使用Gin实现一个API,允许用户通过提供电子邮件和密码来删除其帐户。该API应验证电子邮件和密码是否正确,然后从数据库中删除用户帐户。
新加代码:
r.POST("/api/delete",delete) // 删除用户信息func delete(c *gin.Context) {email := c.Query("email")password := c.Query("password")// 打开数据库dsn := "root:123456@tcp(127.0.0.1:3306)/gobase"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 查询数据库中是否存在匹配的用户记录var user UserInforesult := db.Table("account").Where("email = ? AND password = ?", email, password).First(&user)if result.Error != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email or password"})return}// 删除用户账户if err := db.Table("account").Delete(&user).Error; err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete account"})return}// 返回删除成功的消息c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Account %s deleted successfully",user.Name)})
}
测试代码:
curl -X POST "http://localhost:8001/api/delete?email=qinl@example.com&password=newpassword"
5. 添加一个API,使用Gin允许用户检索数据库中所有帐户的列表。
新加代码:
func show_all(c *gin.Context) {if _, err := validateCookie(c); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 打开数据库dsn := "root:123456@tcp(127.0.0.1:3306)/gobase"db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}// 查询数据库,获取所有帐户列表var accountNames []stringif err := db.Table("account").Pluck("name", &accountNames).Error; err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve accounts"})return}// 返回帐户列表给客户端c.JSON(http.StatusOK, accountNames)
}
测试代码:
# 先加两个用户
curl -X POST -H "Content-Type: application/json" -d '{"name":"QL","email":"qinl@example.com","password":"password"}' http://localhost:8001/api/register
curl -X POST -H "Content-Type: application/json" -d '{"name":"ZS","email":"zs@example.com","password":"password"}' http://localhost:8001/api/register
# 登录一个用户获取token
curl -i -X POST "http://localhost:8001/api/login?email=qinl@example.com&password=password"
# 用token,获取用户信息
curl -X POST "http://localhost:8001/api/show_all" --cookie "gin_cookie=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InFpbmxAZXhhbXBsZS5jb20iLCJleHAiOjE3MTI4MDc0OTB9.hoB7TY0TorAlmdtm-jyqb8U5nSAehmP7d2n5EjSE1r8"
[“QL”,“ZS”]
6. 调研了解目前主流的登录鉴权方式,选择一个在你的项目中进行实现。
主流鉴权方式:
-
基于Token的认证(Token-based Authentication):用户在登录成功后会收到一个令牌(Token),之后每次请求都需要在请求头中携带这个令牌进行验证。常见的Token包括JWT(JSON Web Token)和OAuth2 Token。
-
OAuth2认证(OAuth2 Authentication):OAuth2是一种开放标准,允许用户授权第三方应用访问其资源。用户可以通过第三方认证提供者(如Google、Facebook等)进行登录,然后授权给应用访问其资源的权限。
-
基于Session的认证(Session-based Authentication):用户在登录成功后,服务器会为其创建一个会话(Session),并将会话ID存储在Cookie中,用户在后续的请求中都会携带这个会话ID。服务器通过会话ID来验证用户的身份和权限。
原本的是基于token做的,这里改为基于session做
安装依赖:
go get github.com/gorilla/sessions
新代码:
// 定义一个全局的会话存储对象
var store = sessions.NewCookieStore([]byte("my-secret"))// 创建会话并将用户ID存储在会话中
session, _ := store.Get(c.Request, "session-name")
session.Values["userID"] = user.ID
session.Save(c.Request, c.Writer)// 获取会话中的用户IDsession, _ := store.Get(c.Request, "session-name")_, ok := session.Values["userID"].(int)if !ok {c.JSON(http.StatusBadRequest, gin.H{"error": "User not authenticated"})return}
测试代码:
# 登录一个用户
curl -i -X POST "http://localhost:8001/api/login?email=qinl@example.com&password=password"
# 用token,获取用户信息
curl -X POST "http://localhost:8001/api/show_all" --cookie "session-name=MTcxMjgyNjI1NHxEdi1CQkFFQ180SUFBUkFCRUFBQUhmLUNBQUVHYzNSeWFXNW5EQWdBQm5WelpYSkpSQU5wYm5RRUFnQUV8hXB2Vl_OaBCvi71u6KLgrh96uhu-Irt-CAzSbxFBMeY="
K8S 概念理解
核心
-
Namespace(命名空间):
- Namespace是Kubernetes中用于将集群中的资源划分为不同逻辑组的一种机制。
- 它允许用户在同一集群中创建多个虚拟集群。
- 命名空间提供了一种在不同组织或项目之间隔离资源的方法,以及控制资源访问和使用的方法。
- 默认情况下,Kubernetes提供了一些预定义的命名空间,如"default"用于用户创建的资源,以及"kube-system"用于Kubernetes系统组件。
-
Pod(Pod):
- Pod是Kubernetes中最小的部署和管理单位。
- 它是一个或多个相关容器的组合,共享网络和存储资源,并在同一宿主机上运行。
- Pod可以包含单个容器(单容器Pod)或多个容器(多容器Pod),这些容器可以共享网络命名空间和存储卷。
- Pod可以用来运行应用程序、批处理作业或其他需要共享资源的任务。
-
Deployment(部署):
- Deployment是Kubernetes中用于管理Pod副本的控制器。
- 它定义了应用程序或服务的期望状态,并确保集群中运行的Pod数量符合该状态。
- Deployment允许用户轻松地创建、更新和扩展Pod副本,以及执行滚动升级和回滚操作。
- 通过Deployment,用户可以指定应用程序的副本数量、容器镜像和更新策略等参数。
-
Service(服务):
- Service是Kubernetes中用于将Pod公开为网络服务的抽象。
- 它为Pod提供了一个稳定的网络端点,允许其他应用程序或服务通过网络访问它们。
- Service可以通过标签选择器将一组Pod组合成一个逻辑集,并将流量负载均衡到这些Pod之间。
- Kubernetes支持多种类型的Service,包括ClusterIP、NodePort、LoadBalancer和ExternalName等。
综上所述,Namespace用于组织和隔离资源,Pod是最小的部署单元,Deployment用于管理Pod副本,Service提供网络访问入口,使应用程序或服务可以在集群内部或外部可达。这些概念共同构成了Kubernetes的核心功能,为容器化应用程序的部署、管理和扩展提供了强大的支持。
了解
-
Node(节点):
- Node是Kubernetes集群中的工作节点,用于运行应用程序和服务的实际工作负载。
- 每个Node都是一个单独的物理机器或虚拟机,它们一起组成了Kubernetes集群。
- Node由Kubernetes Master进行管理和监控,并承载了容器运行时引擎(如Docker或containerd)来运行Pod。
-
Cluster(集群):
- Cluster是由一组Node组成的Kubernetes环境,用于运行和管理容器化应用程序和服务。
- 它包括一个Master节点和一组工作节点,所有这些节点共同协作以提供高可用性、自动扩展和自我修复的特性。
-
Resource Quota(资源配额):
- Resource Quota是Kubernetes中的一种资源管理机制,用于限制Namespace中资源的使用量。
- 它允许集群管理员为每个Namespace设置配额,以控制Pod、CPU、内存、存储等资源的使用量,防止资源耗尽和过度使用。
-
ConfigMap(配置映射):
- ConfigMap是Kubernetes中用于存储配置数据的API对象。
- 它允许将配置数据(如环境变量、配置文件等)从应用程序中解耦,并将其存储在集群中的ConfigMap对象中。
- ConfigMap可以在Pod中作为卷或环境变量挂载,使应用程序可以动态地读取配置数据。
-
Role(角色)和RoleBinding(角色绑定):
- Role和RoleBinding是Kubernetes中用于授权和访问控制的两个重要对象。
- Role定义了一组权限规则,允许用户或服务账户对指定的资源执行特定的操作。
- RoleBinding将Role绑定到用户、组或ServiceAccount,并为其分配相应的权限,以控制对资源的访问。
-
Service Account(服务账户):
- Service Account是Kubernetes中用于身份验证和授权的一种机制。
- 它为Pod中运行的应用程序提供了一个身份标识,并允许它们与Kubernetes API进行交互。
- 每个Namespace都有一个默认的Service Account,可以为Pod分配具有不同权限的Service Account。
-
PersistentVolume(持久化卷)和PersistentVolumeClaim(持久化卷声明):
- PersistentVolume(PV)是Kubernetes中用于存储数据的持久化存储资源。
- PersistentVolumeClaim(PVC)是用户向Kubernetes请求PV的一种机制,它定义了用户对存储资源的需求和要求。
- PVC允许用户独立于底层存储实现和配置,并根据需要动态地请求和释放PV。
配置.kube文件
mkdir ~/.kube
# 需要管理员权限才能复制配置文件
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
# 把文件权限改为自己,xxx就是自己的用户名
sudo chown -R qinl:qinl ~/.kube/
# 修改为自己的命名空间,vim编辑~/.kube/config文件,在context一栏加上一行,然后保存并退出即可:
nano ~/.kube/config
- context:cluster: kubernetesuser: kubernetes-adminnamespace: xxx # 这里加上自己的命名空间xxx,一般用自己的用户名name: kubernetes-admin@kubernetes
# 创建自己的命名空间,和上面的xxx对应
kubectl create ns qinl
测试
kubectl get po