Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: finish task #9

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions hkr0101/AI_answer/aianswer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package AI_answer

import (
mymodels "Initial_Experience/myModels"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"

"github.com/gorilla/websocket"
)

var Answer = ""

/**
* WebAPI 接口调用示例 接口文档(必看):https://www.xfyun.cn/doc/spark/Web.html
* 错误码链接:https://www.xfyun.cn/doc/spark/%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E.html(code返回错误码时必看)
* @author iflytek
*/

var (
hostUrl = "wss://aichat.xf-yun.com/v1/chat"
appid = "XXXXXXXX"
apiSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
apiKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
)
Comment on lines +19 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve security and concurrency handling.

  1. The global variable Answer might lead to race conditions if multiple goroutines access it concurrently. Consider passing it as a parameter or using a thread-safe data structure.

  2. Hardcoding API credentials (appid, apiSecret, apiKey) is a security risk. Instead, use environment variables or a secure configuration management system.

Replace the hardcoded credentials with environment variables:

var (
    hostUrl   = os.Getenv("AI_HOST_URL")
    appid     = os.Getenv("AI_APP_ID")
    apiSecret = os.Getenv("AI_API_SECRET")
    apiKey    = os.Getenv("AI_API_KEY")
)

Ensure to handle cases where these environment variables are not set.


func CallAI(question string, request mymodels.AIRequest) (string, error) {
// fmt.Println(HmacWithShaTobase64("hmac-sha256", "hello\nhello", "hello"))
// st := time.Now()
hostUrl = request.HostUrl
appid = request.APPID
apiSecret = request.APISecret
apiKey = request.APIKey

d := websocket.Dialer{
HandshakeTimeout: 5 * time.Second,
}
//握手并建立websocket 连接
conn, resp, err := d.Dial(assembleAuthUrl1(hostUrl, apiKey, apiSecret), nil)
if err != nil {
panic(readResp(resp) + err.Error())
return "", err
} else if resp.StatusCode != 101 {
panic(readResp(resp) + err.Error())
}

go func() {

data := genParams1(appid, question)
conn.WriteJSON(data)

}()

//var answer = ""
//获取返回的数据
for {
_, msg, err := conn.ReadMessage()
if err != nil {
fmt.Println("read message error:", err)
break
}

var data map[string]interface{}
err1 := json.Unmarshal(msg, &data)
if err1 != nil {
fmt.Println("Error parsing JSON:", err)
return "", err1
}
fmt.Println(string(msg))
//解析数据
payload := data["payload"].(map[string]interface{})
choices := payload["choices"].(map[string]interface{})
header := data["header"].(map[string]interface{})
code := header["code"].(float64)

if code != 0 {
fmt.Println(data["payload"])
return "", err
}
status := choices["status"].(float64)
fmt.Println(status)
text := choices["text"].([]interface{})
content := text[0].(map[string]interface{})["content"].(string)
if status != 2 {
Answer += content
} else {
fmt.Println("收到最终结果")
Answer += content
usage := payload["usage"].(map[string]interface{})
temp := usage["text"].(map[string]interface{})
totalTokens := temp["total_tokens"].(float64)
fmt.Println("total_tokens:", totalTokens)
conn.Close()
break
}

}
//输出返回结果
//fmt.Println(answer)

time.Sleep(1 * time.Second)
return Answer, nil
}
Comment on lines +34 to +110
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Refactor CallAI function for better error handling and concurrency.

  1. Replace panic with proper error handling. Return errors instead of panicking.

  2. Break down the function into smaller, more manageable functions for better readability and maintainability.

  3. Avoid using the global Answer variable. Instead, use a local variable and return it.

  4. Consider adding a context for cancellation and timeout handling.

Here's a sketch of how you could refactor this function:

func CallAI(ctx context.Context, question string, request mymodels.AIRequest) (string, error) {
    conn, err := establishConnection(ctx, request)
    if err != nil {
        return "", fmt.Errorf("failed to establish connection: %w", err)
    }
    defer conn.Close()

    if err := sendQuestion(conn, question, request.APPID); err != nil {
        return "", fmt.Errorf("failed to send question: %w", err)
    }

    answer, err := receiveAnswer(ctx, conn)
    if err != nil {
        return "", fmt.Errorf("failed to receive answer: %w", err)
    }

    return answer, nil
}

func establishConnection(ctx context.Context, request mymodels.AIRequest) (*websocket.Conn, error) {
    // Implementation here
}

func sendQuestion(conn *websocket.Conn, question, appid string) error {
    // Implementation here
}

func receiveAnswer(ctx context.Context, conn *websocket.Conn) (string, error) {
    // Implementation here
}

This refactoring improves error handling, removes the global variable, and makes the code more modular and easier to test.


// 生成参数
func genParams1(appid, question string) map[string]interface{} { // 根据实际情况修改返回的数据结构和字段名

messages := []Message{
{Role: "user", Content: question},
}

data := map[string]interface{}{ // 根据实际情况修改返回的数据结构和字段名
"header": map[string]interface{}{ // 根据实际情况修改返回的数据结构和字段名
"app_id": appid, // 根据实际情况修改返回的数据结构和字段名
},
"parameter": map[string]interface{}{ // 根据实际情况修改返回的数据结构和字段名
"chat": map[string]interface{}{ // 根据实际情况修改返回的数据结构和字段名
"domain": "general", // 根据实际情况修改返回的数据结构和字段名
"temperature": float64(0.8), // 根据实际情况修改返回的数据结构和字段名
"top_k": int64(6), // 根据实际情况修改返回的数据结构和字段名
"max_tokens": int64(2048), // 根据实际情况修改返回的数据结构和字段名
"auditing": "default", // 根据实际情况修改返回的数据结构和字段名
},
},
"payload": map[string]interface{}{ // 根据实际情况修改返回的数据结构和字段名
"message": map[string]interface{}{ // 根据实际情况修改返回的数据结构和字段名
"text": messages, // 根据实际情况修改返回的数据结构和字段名
},
},
}
return data // 根据实际情况修改返回的数据结构和字段名
}
Comment on lines +113 to +139
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance flexibility of genParams1 function.

The function is well-structured, but it uses hardcoded values for several parameters, which might not be suitable for all use cases.

Consider making the function more flexible by allowing these parameters to be passed as arguments:

func genParams1(appid, question string, domain string, temperature float64, topK, maxTokens int64, auditing string) map[string]interface{} {
    // ... (rest of the function remains the same, but use the passed parameters instead of hardcoded values)
}

This change allows callers to customize the AI request parameters as needed, making the function more versatile.


// 创建鉴权url apikey 即 hmac username
func assembleAuthUrl1(hosturl string, apiKey, apiSecret string) string {
ul, err := url.Parse(hosturl)
if err != nil {
fmt.Println(err)
}
//签名时间
date := time.Now().UTC().Format(time.RFC1123)
//date = "Tue, 28 May 2019 09:10:42 MST"
//参与签名的字段 host ,date, request-line
signString := []string{"host: " + ul.Host, "date: " + date, "GET " + ul.Path + " HTTP/1.1"}
//拼接签名字符串
sgin := strings.Join(signString, "\n")
// fmt.Println(sgin)
//签名结果
sha := HmacWithShaTobase64("hmac-sha256", sgin, apiSecret)
// fmt.Println(sha)
//构建请求参数 此时不需要urlencoding
authUrl := fmt.Sprintf("hmac username=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey,
"hmac-sha256", "host date request-line", sha)
//将请求参数使用base64编码
authorization := base64.StdEncoding.EncodeToString([]byte(authUrl))

v := url.Values{}
v.Add("host", ul.Host)
v.Add("date", date)
v.Add("authorization", authorization)
//将编码后的字符串url encode后添加到url后面
callurl := hosturl + "?" + v.Encode()
return callurl
}
Comment on lines +142 to +171
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Minor improvements for assembleAuthUrl1 function.

The function is well-implemented, but there are a few areas for improvement:

  1. Error handling: The function panics if there's an error parsing the URL. It's better to return the error.
  2. Unused variable: The err variable on line 145 is declared but not used.

Consider modifying the function signature and error handling:

func assembleAuthUrl1(hosturl, apiKey, apiSecret string) (string, error) {
    ul, err := url.Parse(hosturl)
    if err != nil {
        return "", fmt.Errorf("failed to parse URL: %w", err)
    }
    // ... (rest of the function remains the same)
    return callurl, nil
}

This change improves error handling and allows the caller to handle URL parsing errors appropriately.


func HmacWithShaTobase64(algorithm, data, key string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(data))
encodeData := mac.Sum(nil)
return base64.StdEncoding.EncodeToString(encodeData)
}

func readResp(resp *http.Response) string {
if resp == nil {
return ""
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
return fmt.Sprintf("code=%d,body=%s", resp.StatusCode, string(b))
}
Comment on lines +173 to +189
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve error handling in readResp function.

While the HmacWithShaTobase64 function looks good, the readResp function uses panic for error handling, which is not ideal for a helper function.

Consider modifying the readResp function to return an error instead of panicking:

func readResp(resp *http.Response) (string, error) {
    if resp == nil {
        return "", errors.New("response is nil")
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", fmt.Errorf("failed to read response body: %w", err)
    }
    return fmt.Sprintf("code=%d,body=%s", resp.StatusCode, string(b)), nil
}

This change allows the caller to handle errors more gracefully and prevents the application from crashing due to unexpected issues when reading the response.


type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
27 changes: 27 additions & 0 deletions hkr0101/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 简单的代码说明
实现了账户的注册、登录、登出,其中在储存密码时运用了简单的哈希函数。给予了admin
账号足够的权限。
实现了在登录情况下添加、删除、修改、查看自己的问题,以
及在所有的情况下查看所有/特定问题。在登录情况下添加、删除、修改、查看自
己的答案,以及在所有的情况下查看某一个问题的答案
一个小翻页,默认在显示一系列答案或者问题时每页20条内容
在github上找到了一个关于调用chatgpt的项目用于生成ai答案,但是由于
我没有国外的手机号,无法获得chatgpt的key,这个内容仅仅停留在未测试可行性
* main.go是主程序
* routes中的是操作中涉及的函数
* mymodels中是三个实体Question、User、Answer
* myauth中是登录与登出的操作
* db中的是连接数据库以及在数据库中自动生成实体
* AI_answer中便是前文中提到的尚未完成的ai生成答案部分
api文档:https://apifox.com/apidoc/shared-86117e10-c314-4e57-a13f-494295b93689



10/5 更新,增加了调用讯飞星火的功能,把之前的chatgpt的部分去掉了,同时通过数据库储存了在线的账号,虽然我认为这
个功能可以通过在主程序中存一个切片就可以了。而且存在数据库里每次退出时如果没有登出操作
还要手动清空这个数据库。但是我认为存在库里是稳妥的,而且方便其他程序的访问,所以没有改。
* 增加了实体 AIRequest 用来存储登录AI的必要信息
* 修了可以给不存在的问题增加答案的bug

10/7 发现了一个愚蠢的事情,登陆后的id直接存在路由里,太愚蠢了。现在简单地试用了一下JWT让登录信息变得
更加"安全"。
37 changes: 37 additions & 0 deletions hkr0101/db/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package db

import (
"Initial_Experience/myModels"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
)

var DB *gorm.DB

// 数据库的连接
func Connect() {
var err error
dsn := "root:123456@tcp(localhost:3306)/initial_experience"
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to the database:", err)
} else {
log.Println("Successfully connected to the database")
}
}
Comment on lines +13 to +22
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve database connection handling and configuration.

The Connect function has several areas for improvement:

  1. The Data Source Name (DSN) is hardcoded, which is not secure or flexible.
  2. Using log.Fatal will terminate the program, which might not be the desired behavior in all cases.
  3. The else block is unnecessary.

Consider the following improvements:

  1. Use environment variables or a configuration file for the DSN:

    dsn := os.Getenv("DATABASE_DSN")
  2. Return an error instead of using log.Fatal:

    func Connect() error {
        var err error
        dsn := os.Getenv("DATABASE_DSN")
        DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
        if err != nil {
            return fmt.Errorf("failed to connect to the database: %w", err)
        }
        log.Println("Successfully connected to the database")
        return nil
    }
  3. Remove the else block as it's not needed after the if statement returns.

These changes will make the function more flexible, secure, and easier to use in different contexts.


// 自动迁移模型

func Migrate() {
err := DB.AutoMigrate(
&mymodels.User{},
&mymodels.Question{},
&mymodels.Answer{},
&mymodels.OnlineUser{},
&mymodels.AIRequest{},
)
if err != nil {
log.Fatal("Migration failed:", err)
}
}
42 changes: 42 additions & 0 deletions hkr0101/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Initial_Experience
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve module name to follow Go conventions

The current module name "Initial_Experience" doesn't follow Go naming conventions. In Go, it's recommended to use lowercase letters and hyphens for module names.

Consider changing the module name to something like:

-module Initial_Experience
+module github.com/hduhelp/backend_2024_freshman_task

This assumes the repository is hosted on GitHub under the "hduhelp" organization. Adjust the path accordingly if it's different.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
module Initial_Experience
module github.com/hduhelp/backend_2024_freshman_task


go 1.23.1
Comment on lines +1 to +3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update Go version to a valid release

The specified Go version 1.23.1 is not a valid released version. As of October 2024, the latest stable version is in the 1.21.x series.

Please update the Go version to a valid, stable release. For example:

-go 1.23.1
+go 1.21.5

Make sure to test your code with the updated version to ensure compatibility.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
module Initial_Experience
go 1.23.1
module Initial_Experience
go 1.21.5


require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.12.3 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.10.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/sashabaranov/go-openai v1.31.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.10.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/gorm v1.25.12 // indirect
)
Comment on lines +5 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Review and update dependency specifications

While the list of dependencies seems appropriate for your project, all dependencies are currently marked as indirect. This might not be accurate for key packages that your code directly imports and uses.

Consider reviewing your imports and updating the go.mod file to specify direct dependencies. For example:

 require (
-	github.com/gin-gonic/gin v1.10.0 // indirect
+	github.com/gin-gonic/gin v1.10.0
-	github.com/gorilla/websocket v1.5.3 // indirect
+	github.com/gorilla/websocket v1.5.3
-	gorm.io/driver/mysql v1.5.7 // indirect
+	gorm.io/driver/mysql v1.5.7
-	gorm.io/gorm v1.25.12 // indirect
+	gorm.io/gorm v1.25.12
	// ... other dependencies ...
 )

After making these changes, run go mod tidy to ensure all dependencies are correctly specified and up-to-date.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.12.3 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.10.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/sashabaranov/go-openai v1.31.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.10.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.7 // indirect
gorm.io/gorm v1.25.12 // indirect
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.12.3 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/gorilla/websocket v1.5.3
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/sashabaranov/go-openai v1.31.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.10.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12
)

Loading