记录与ChatGPT学习的go练习题(2)

记录与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)

以上的思路可以进一步优化上述代码,但是这里就不优化了,留给读者进行探索。开始写下一题去了…

  1. 编写一个程序,监视指定目录中的文件变化。

代码如下:

//本次需求中需要用到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 类型系统的功能。下面是如何使用反射的基本示例:

  1. 获取反射值:要使用反射,首先需要获取要检查的值的反射值。你可以使用 reflect.ValueOf() 函数来获取值的反射值。

    import "reflect"
    
    value := 42
    reflectValue := reflect.ValueOf(value)
    
  2. 检查类型:你可以使用反射值的方法来检查值的类型。例如,使用 Type() 方法获取类型信息:

    valueType := reflectValue.Type()
    fmt.Println("类型:", valueType)
    
  3. 获取字段和方法:如果值是结构体,你可以使用反射获取结构体的字段和方法。使用 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)
    }
    
  4. 修改值:反射允许你修改某些可修改的值。你可以使用 CanSet() 方法来检查是否可以修改该值,然后使用 SetXXX() 方法来进行修改。

    intValue := 42
    reflectValue := reflect.ValueOf(&intValue).Elem()
    
    if reflectValue.CanSet() {
        reflectValue.SetInt(100)
    }
    fmt.Println(intValue) // 输出: 100
    
  5. 检查和调用方法:你可以使用反射检查和调用对象的方法。使用 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。

今天也就写那么多了,剩下的以后再进行更新…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值