记录与ChatGPT学习的go练习题(2)
接着上一期的文章,我们继续完成CahtGPT提出的20个问题
5.实现一个简单的REST API,用于创建、读取、更新和删除用户的信息。
代码如下:
//todo 两种实现方法 1.使用gin框架编写handler,很简单的编写Restful风格的接口 2.只使用net/http包,自行实现Rest接口风格
//以下是第二种的做法
//todo 编写具有Restful风格的接口
//因为原本的net/http的包并不支持动态参数,所以需要自己简易实现一个判断参数的的方法
type UserSlice struct {
userMap map[string]string
}
func (us *UserSlice) createUser(writer http.ResponseWriter, request *http.Request) {
// 在Restful风格中创建应该使用post方法
// todo 判断方法的提交方式
if request.Method == "POST" {
//获取输入的参数
name := request.PostFormValue("name")
id := request.PostFormValue("id")
//将其存入只指定的位置
if _, ok := us.userMap[name]; ok {
_, err := writer.Write([]byte("用户已经存在!"))
if err != nil {
return
}
return
}
// 模拟入值
us.userMap[id] = name
} else {
_, err := writer.Write([]byte("error !"))
if err != nil {
return
}
}
}
func (us *UserSlice) queryUser(writer http.ResponseWriter, request *http.Request) {
if request.Method == "GET" {
parts := strings.Split(request.URL.Path, "/")
if len(parts) < 3 {
http.Error(writer, "Invalid URL", http.StatusBadRequest)
return
}
id := parts[2]
if _, ok := us.userMap[id]; ok {
response1 := "成功查询:" + us.userMap[id]
_, _ = writer.Write([]byte(response1))
return
} else {
_, _ = writer.Write([]byte("此用户不存在"))
return
}
}
}
func (us *UserSlice) updateUser(writer http.ResponseWriter, request *http.Request) {
if request.Method == "PUT" {
parts := strings.Split(request.URL.Path, "/")
if len(parts) < 3 {
http.Error(writer, "Invalid URL", http.StatusBadRequest)
return
}
id := parts[2]
flag := 0
newName := request.PostFormValue("name")
for k, _ := range us.userMap {
if k == id {
flag = 1
us.userMap[k] = newName
}
}
if flag == 1 {
_, _ = writer.Write([]byte("更改成功"))
return
} else {
_, _ = writer.Write([]byte("更改的值不存在"))
return
}
}
}
func (us *UserSlice) deleteUser(writer http.ResponseWriter, request *http.Request) {
if request.Method == "DELETE" {
parts := strings.Split(request.URL.Path, "/")
if len(parts) < 3 {
http.Error(writer, "Invalid URL", http.StatusBadRequest)
return
}
id := parts[2]
if _, ok := us.userMap[id]; ok {
delete(us.userMap, id)
_, _ = writer.Write([]byte("删除成功"))
return
} else {
_, _ = writer.Write([]byte("要删除的用户不存在"))
return
}
}
}
func main() {
// 编写网站的三大要点 1.url 2.接受参数 3.返回响应
// 编写的基本步骤 1.编写响应函数 2.将 响应函数与路径进行绑定 3.指定并开始监听端口
userMap := make(map[string]string)
us := UserSlice{
userMap: userMap,
}
// todo 函数与路径进行绑定,完全实现restful的话就使用一个handlefunc,之后通过switch case 判断
http.HandleFunc("/create", us.createUser)
http.HandleFunc("/user/", us.queryUser)
http.HandleFunc("/user1/", us.updateUser)
http.HandleFunc("/user2/", us.deleteUser)
err := http.ListenAndServe("localhost:8080", nil)
if err != nil {
return
}
}
闲聊:这道题其实就已经是在做基本的web开发的,所有web应用的核心功能无非是做信息的操作:增删改查。之后引入其他的中间件或者工具来处理高并发以及多人的场景。例如通过redis来缓解原本数据库的访问压力,利用消息队列如rabbitMQ来实现订阅-发布以及异步处理,流量削峰的需求。如果过于庞大,没有什么是建立集群解决不了的,于是就开始了集群的信息同步等等一系列的开发,而在这些中间件之后,执行的其实都是对数据库的增删改查操作。所以,chatgpt的第五个问题会让我设计一个restful风格的接口也无可厚非。
总结:在这一道题中,我是用map来模拟键值对类型的数据库,通过对map进行增删改查模拟业务操作。主要是需要解决动态参数问题,就很好解决这一编码风格了。当然,这里还不是完美的restful,如果想要实现完全版的restful风格,可以由以下改进:
(1)将对同一对象的操作作为一组,将router按照分组的想法设计
(2)将操作集成到一个func里面,通过对响应的方法判断要进行什么类型的操作。(可以利用switch case或者select case)
以上的思路可以进一步优化上述代码,但是这里就不优化了,留给读者进行探索。开始写下一题去了…
代码如下:
//本次需求中需要用到fsnotify包来监视文件的变化
func main() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
// 指定要监视的目录
//两个//表示一个/
directoryToWatch := "D:\\文档"
if err := watcher.Add(directoryToWatch); err != nil {
log.Fatal(err)
}
fmt.Printf("Monitoring directory: %s\n", directoryToWatch)
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
// 打印事件类型
fmt.Printf("Event: %s\n", event.Op)
// 在文件修改事件时触发操作
if event.Op&fsnotify.Write == fsnotify.Write {
fmt.Printf("File modified: %s\n", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
fmt.Printf("Error: %v\n", err)
}
}
}
分析:这道题主要利用的第三方库类完成,通过watcher来实时监听文件的变化情况。但是只了解到这个深度还远远不够。我们来扒一下这个库类的部分源码,吸收里面的知识。
首先,fsnotify中的newWatcher的实现如下所示,我们需要对w的结构进行分析:
func NewBufferedWatcher(sz uint) (*Watcher, error) {
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
if err != nil {
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
}
w := &Watcher{
//端口号,通过源码跟踪该端口号是寻找计算机未被占用的端口自动赋值,调用了dll_windows里的find方法
port: port,
// 存放需要监控的文件信息
watches: make(watchMap),
//输入管道
input: make(chan *input, 1),
//处理完后输出的事件管道流
Events: make(chan Event, sz),
//错误类型
Errors: make(chan error),
// 结束管道
quit: make(chan chan<- error, 1),
}
// 异步执行监听事件
go w.readEvents()
return w, nil
}
其中readEvent过于庞大,这里我就不贴源码了总结就是干了以下的事情:
(1)利用管道持续监听
(2)调用windows API获取信息
(3)根据获取的信息判断操作类型,并将其操作类型返回
(4)将信息放入event管道,之后用户就可以使用events来获取相关的操作信息或者失败信息
为了简单起见,我们通过不断对文件进行轮询查询,判断状态来降级实现这一要求
代码如下:
package main
import (
"fmt"
"io/ioutil"
"time"
)
func main() {
dirToWatch := "." // 要监视的目录
interval := 1 * time.Second // 轮询间隔
for {
files, err := ioutil.ReadDir(dirToWatch)
if err != nil {
fmt.Println("无法读取目录:", err)
return
}
for _, file := range files {
if file.ModTime().After(lastModified[file.Name()]) {
lastModified[file.Name()] = file.ModTime()
fmt.Printf("文件修改: %s\n", file.Name())
}
}
time.Sleep(interval)
}
}
var lastModified = make(map[string]time.Time)
7.创建一个HTTP服务器,使用JSON Web Tokens (JWT) 对请求进行身份验证和授权。
代码如下:
var signingKey = []byte("secret_key")
func main() {
// 创建路由
r := mux.NewRouter()
// 设置路由
r.Handle("/auth", AuthHandler).Methods("POST")
r.Handle("/protected", JWTAuthMiddleware(http.HandlerFunc(ProtectedHandler))).Methods("GET")
// 启动HTTP服务器
http.Handle("/", r)
http.ListenAndServe(":8080", nil)
}
// AuthHandler 处理用户身份验证并生成JWT令牌
var AuthHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 处理用户身份验证,检查用户名和密码等
if r.PostFormValue("username") != "111" && r.PostFormValue("password") != "111"{
_, err := w.Write([]byte("fail to auth!"))
if err != nil {
return
}
return
}
// 如果身份验证成功,生成JWT令牌
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["username"] = "user123"
tokenString, _ := token.SignedString(signingKey)
w.Write([]byte(tokenString))
})
// JWTAuthMiddleware 是用于JWT验证的中间件
func JWTAuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return signingKey, nil
})
if err != nil || !token.Valid {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 如果验证通过,继续处理请求
next.ServeHTTP(w, r)
})
}
总结:基本jwt的写法,jwt可以想象成一个辨识码,这个辨识码双方都知道,签发方使用不同的加密算法对辨识码进行加密,接收端可以对其进行解密。以此就可以判断出用户的身份,并相应相应的数据,不仅如此,JWT 可用于授权用户访问某些资源或执行某些操作。在 JWT 中可以包含一些声明,例如用户的角色或权限信息。服务器可以根据这些声明来判断用户是否有权限执行特定操作。
其中的基本思路就是:
(1)双方协商好使用什么方式进行加密
(2)服务端验证完身份后,通过已经商定好的方式对用户的相关信息或者权限进行加密,发送
(3)用户之后的请求便会附加上这一个token
(4)服务器之后会对用户的请求基于这个token进行验证,来判断该用户是否有权限可以访问下述内容
8. 编写一个程序,使用Go的反射功能检查结构体中的字段和标签。
代码如下:
type User struct {
userId int
name string
}
func main() {
//检查相关字段与值
user := User{
userId: 1,
name: "逸",
}
// 获取结构体的反射值
userValue := reflect.ValueOf(user)
// 获取结构体的类型
userType := userValue.Type()
// 遍历结构体的字段
for i := 0; i < userType.NumField(); i++ {
field := userType.Field(i)
fieldValue := userValue.Field(i)
// 获取字段名和标签
fieldName := field.Name
fmt.Printf("字段为: %s 值为:%v\n", fieldName, fieldValue)
}
}
总结:回归本质,go的反射十分强大。反射的基本内容我不再次总结,以下的反射内容由chat生成
Go 语言中的反射(reflection)是一种强大的机制,允许你在运行时检查和操作程序的结构,包括变量、类型、字段和方法。反射在某些情况下是非常有用的,例如在处理通用数据、解析外部数据格式(如 JSON、XML)或在编写测试框架时。
反射主要依赖于 reflect
包,该包提供了用于检查和操作 Go 类型系统的功能。下面是如何使用反射的基本示例:
-
获取反射值:要使用反射,首先需要获取要检查的值的反射值。你可以使用
reflect.ValueOf()
函数来获取值的反射值。import "reflect" value := 42 reflectValue := reflect.ValueOf(value)
-
检查类型:你可以使用反射值的方法来检查值的类型。例如,使用
Type()
方法获取类型信息:valueType := reflectValue.Type() fmt.Println("类型:", valueType)
-
获取字段和方法:如果值是结构体,你可以使用反射获取结构体的字段和方法。使用
NumField()
获取字段的数量,然后使用Field()
获取特定字段。同样,你可以使用NumMethod()
和Method()
获取方法。type Person struct { Name string Age int } person := Person{"Alice", 30} reflectValue := reflect.ValueOf(person) for i := 0; i < reflectValue.NumField(); i++ { field := reflectValue.Field(i) fmt.Println("字段:", field) }
-
修改值:反射允许你修改某些可修改的值。你可以使用
CanSet()
方法来检查是否可以修改该值,然后使用SetXXX()
方法来进行修改。intValue := 42 reflectValue := reflect.ValueOf(&intValue).Elem() if reflectValue.CanSet() { reflectValue.SetInt(100) } fmt.Println(intValue) // 输出: 100
-
检查和调用方法:你可以使用反射检查和调用对象的方法。使用
MethodByName()
查找方法,然后使用Call()
调用它。type Person struct { Name string } func (p Person) Greet() string { return "Hello, " + p.Name } person := Person{"Alice"} reflectValue := reflect.ValueOf(person) greetMethod := reflectValue.MethodByName("Greet") result := greetMethod.Call(nil) fmt.Println(result[0].Interface().(string)) // 输出: "Hello, Alice"
请注意,反射应该谨慎使用,因为它会引入运行时开销,使代码更复杂。在大多数情况下,可以通过静态类型检查和接口来避免使用反射。只有在必要的情况下才应该使用反射。另外,反射的错误处理也需要特别注意,因为它可能引发 panic。
今天也就写那么多了,剩下的以后再进行更新…