diff --git a/hkr0101/AI_answer/aianswer.go b/hkr0101/AI_answer/aianswer.go new file mode 100644 index 0000000..080cc74 --- /dev/null +++ b/hkr0101/AI_answer/aianswer.go @@ -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" +) + +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 +} + +// 生成参数 +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 // 根据实际情况修改返回的数据结构和字段名 +} + +// 创建鉴权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 +} + +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)) +} + +type Message struct { + Role string `json:"role"` + Content string `json:"content"` +} diff --git a/hkr0101/README.md b/hkr0101/README.md new file mode 100644 index 0000000..ef40a00 --- /dev/null +++ b/hkr0101/README.md @@ -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让登录信息变得 +更加"安全"。 \ No newline at end of file diff --git a/hkr0101/db/database.go b/hkr0101/db/database.go new file mode 100644 index 0000000..a78cb0d --- /dev/null +++ b/hkr0101/db/database.go @@ -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") + } +} + +// 自动迁移模型 + +func Migrate() { + err := DB.AutoMigrate( + &mymodels.User{}, + &mymodels.Question{}, + &mymodels.Answer{}, + &mymodels.OnlineUser{}, + &mymodels.AIRequest{}, + ) + if err != nil { + log.Fatal("Migration failed:", err) + } +} diff --git a/hkr0101/go.mod b/hkr0101/go.mod new file mode 100644 index 0000000..325ee7d --- /dev/null +++ b/hkr0101/go.mod @@ -0,0 +1,42 @@ +module Initial_Experience + +go 1.23.1 + +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 +) diff --git a/hkr0101/go.sum b/hkr0101/go.sum new file mode 100644 index 0000000..75ab67e --- /dev/null +++ b/hkr0101/go.sum @@ -0,0 +1,97 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= +github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sashabaranov/go-openai v1.31.0 h1:rGe77x7zUeCjtS2IS7NCY6Tp4bQviXNMhkQM6hz/UC4= +github.com/sashabaranov/go-openai v1.31.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= +golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/hkr0101/main.go b/hkr0101/main.go new file mode 100644 index 0000000..405d550 --- /dev/null +++ b/hkr0101/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "Initial_Experience/db" + "Initial_Experience/myauth" + "Initial_Experience/routes" + "github.com/gin-gonic/gin" +) + +func main() { + r := gin.Default() + db.Connect() + db.Migrate() + //用户注册 + r.POST("/register", myauth.RegisterHandler) + // 用户登录 + r.POST("/login", myauth.LoginHandler) + // 问题管理 + + //查看所有问题 + r.GET("/questions", routes.GetQuestions) + //查看某个问题 + r.GET("/questions/:question_id", routes.GetQuestionByID) + //查看某个问题的所有答案 + r.GET("/questions/:question_id/answer", routes.GetAnswerListByQuestion) + //查看某个问题的某个答案 + r.GET("/questions/answer/:answer_id", routes.GetAnswerByID) + + auth := r.Group("/api") + auth.Use(myauth.AuthMiddleware()) // 使用身份验证中间件 + { + + //创建问题 + auth.POST("/questions", routes.CreateQuestion) + //创建答案 + auth.POST("/questions/:question_id/answer", routes.CreateAnswer) + //删除问题 + auth.DELETE("/questions/:question_id", routes.DeleteQuestion) + //删除答案 + auth.DELETE("/questions/answer/:answer_id", routes.DeleteAnswer) + //更新问题 + auth.PUT("/questions/:question_id", routes.UpdateQuestion) + //更新答案 + auth.PUT("/:answer_id", routes.UpdateAnswer) + //给出当前用户的所有答案 + auth.GET("/answer", routes.GetAnswerListByUser) + //给出当前用户的所有问题 + auth.GET("/questions", routes.GetQuestionByUser) + //登出 + auth.POST("/logout", myauth.LogoutHandler) + //调用ai,未完成 + auth.POST("/chat/registerandchange", myauth.RegisterAndChangeAI) + //生成ai答案 + auth.POST("/chat/question/:question_id/answer", routes.CreateAnswerByAI) + } + r.Run(":8080") +} diff --git a/hkr0101/myModels/mymodels.go b/hkr0101/myModels/mymodels.go new file mode 100644 index 0000000..7920db6 --- /dev/null +++ b/hkr0101/myModels/mymodels.go @@ -0,0 +1,36 @@ +package mymodels + +// Question 结构体 +type Question struct { + QuestionID int `json:"question_id" gorm:"primaryKey;autoIncrement"` + Content string `json:"content" binding:"required"` + Status string `json:"status" binding:"required"` + Name string `json:"name" binding:"required"` + UserID int `json:"user_id"` // 外键,关联用户 +} + +// User 结构体 +type User struct { + UserID int `json:"user_id" gorm:"primaryKey;autoIncrement"` + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} + +type Answer struct { + AnswerID int `json:"answer_id" gorm:"primaryKey;autoIncrement"` + Content string `json:"content" binding:"required"` + QuestionID int `json:"question_id"` // 外键,关联问题 + UserID int `json:"user_id"` // 外键,关联用户 +} + +type OnlineUser struct { + UserID int `json:"user_id" gorm:"primaryKey"` +} + +type AIRequest struct { + UserID int `json:"user_id" gorm:"primaryKey"` + APPID string `json:"app_id" binding:"required"` + APISecret string `json:"api_secret" binding:"required"` + APIKey string `json:"api_key" binding:"required"` + HostUrl string `json:"host_url" binding:"required"` +} diff --git a/hkr0101/myauth/auth.go b/hkr0101/myauth/auth.go new file mode 100644 index 0000000..1dab7d2 --- /dev/null +++ b/hkr0101/myauth/auth.go @@ -0,0 +1,214 @@ +package myauth + +import ( + "Initial_Experience/db" + "Initial_Experience/myModels" + "fmt" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "golang.org/x/crypto/bcrypt" + "net/http" + "os" + "time" +) + +var jwtKey = []byte(os.Getenv("JWT_SECRET")) // JWT 密钥,可以通过环境变量配置 + +// Hash 密码 +func hashPassword(password string) (string, error) { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hashedPassword), nil +} + +// 检查密码 + +func checkPassword(hashedPassword, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) + return err == nil +} + +// 创建 JWT + +// 创建 JWT,包含用户ID和用户名 + +func GenerateJWT(user mymodels.User) (string, error) { + // 设置过期时间为24小时 + expirationTime := time.Now().Add(24 * time.Hour) + claims := &jwt.MapClaims{ + "userID": user.UserID, // 用户ID + "username": user.Username, // 用户名 + "exp": expirationTime.Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString(jwtKey) + if err != nil { + return "", err + } + + return tokenString, nil +} + +// 注册处理 + +func RegisterHandler(c *gin.Context) { + var user mymodels.User + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + // 检查用户名是否已存在 + var existingUser mymodels.User + if err := db.DB.Where("username = ?", user.Username).First(&existingUser).Error; err == nil { + c.JSON(http.StatusConflict, gin.H{"message": "Username already exists"}) + return + } + + // 创建新用户 + user.Password, _ = hashPassword(user.Password) + if err := db.DB.Create(&user).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"message": "Error creating user"}) + return + } + c.JSON(http.StatusCreated, gin.H{"message": "User registered successfully", "id": user.Username}) +} + +// 登录处理 + +func LoginHandler(c *gin.Context) { + var user mymodels.User + + if err := c.ShouldBindJSON(&user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var foundUser mymodels.User + if err := db.DB.Where("username = ?", user.Username).First(&foundUser).Error; err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"message": "Invalid username"}) + return + } + + if err := db.DB.Where("user_id = ?", foundUser.UserID).First(&mymodels.OnlineUser{}).Error; err == nil { + c.JSON(http.StatusUnauthorized, gin.H{"message": "用户已登录,无法重复登录"}) + return + } + var onlineUser mymodels.OnlineUser + onlineUser.UserID = foundUser.UserID + + if err := db.DB.Create(&onlineUser).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"message": "Error creating onlineUser"}) + return + } + + if !checkPassword(foundUser.Password, user.Password) { + c.JSON(http.StatusUnauthorized, gin.H{"message": "Invalid password"}) + return + } + + // 生成 JWT Token + + tokenString, err := GenerateJWT(foundUser) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"message": "Could not generate token"}) + return + } + + // 返回 JWT Token 给客户端 + c.JSON(http.StatusOK, gin.H{"message": "Login successful", "token": tokenString}) +} + +// 验证JWT Token + +func ValidateJWT(tokenStr string) (*jwt.RegisteredClaims, error) { + claims := &jwt.RegisteredClaims{} + + token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) { + return jwtKey, nil + }) + + if err != nil { + return nil, err + } + + if !token.Valid { + return nil, fmt.Errorf("invalid token") + } + + return claims, nil +} + +// JWT身份验证中间件 + +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // 从请求头中提取 JWT + tokenString := c.GetHeader("Authorization") + if tokenString == "" { + c.JSON(http.StatusUnauthorized, gin.H{"message": "Authorization token required"}) + c.Abort() + return + } + + // 验证并解析 Token + claims := &jwt.MapClaims{} + token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { + return jwtKey, nil + }) + + if err != nil || !token.Valid { + c.JSON(http.StatusUnauthorized, gin.H{"message": "Invalid token"}) + c.Abort() + return + } + // + // 从 claims 中提取用户信息 + userID := (*claims)["userID"].(float64) // 注意:JWT中的数字会被解析为 float64 类型 + username := (*claims)["username"].(string) + + // 设置用户信息到上下文中,供后续处理使用 + c.Set("userID", int(userID)) // 转为int类型 + c.Set("username", username) + + c.Next() + } +} + +// 登出处理 (JWT无状态不需要特殊登出逻辑,只需在客户端删除Token) + +func LogoutHandler(c *gin.Context) { + + userID, _ := c.Get("userID") + + if err := db.DB.Where("user_id = ?", userID).First(&mymodels.OnlineUser{}).Error; err != nil { + c.JSON(http.StatusBadRequest, gin.H{"message": "Logout unsuccessful"}) + return + } + + if err := db.DB.Delete(&mymodels.OnlineUser{}, userID).Error; err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Logout successful"}) +} + +// AI需求注册 + +func RegisterAndChangeAI(c *gin.Context) { + userID, _ := c.Get("userID") + id := userID.(int) + var userAI mymodels.AIRequest + if err := c.ShouldBindJSON(&userAI); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + userAI.UserID = id + if err := db.DB.Save(&userAI).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"message": "save AI"}) + return + } + c.JSON(http.StatusCreated, gin.H{"message": "User_AI registered successfully"}) +} diff --git a/hkr0101/package.json b/hkr0101/package.json new file mode 100644 index 0000000..cdfa092 --- /dev/null +++ b/hkr0101/package.json @@ -0,0 +1,4 @@ +{ + "name": "application-name", + "version": "0.1.1" +} \ No newline at end of file diff --git a/hkr0101/routes/answer.go b/hkr0101/routes/answer.go new file mode 100644 index 0000000..8b16f09 --- /dev/null +++ b/hkr0101/routes/answer.go @@ -0,0 +1,157 @@ +package routes + +import ( + "Initial_Experience/AI_answer" + "Initial_Experience/db" + "Initial_Experience/myModels" + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +//创建答案 + +func CreateAnswer(c *gin.Context) { + var answer mymodels.Answer + Curuser := getCurUser(c) + if err := c.ShouldBindJSON(&answer); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + qID, _ := strconv.Atoi(c.Param("question_id")) + if err := db.DB.Where("question_id = ?", qID).First(&mymodels.Question{}).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Question not found"}) + return + } + answer.QuestionID = qID + answer.UserID = Curuser.UserID + db.DB.Create(&answer) + c.JSON(http.StatusOK, gin.H{"data": answer}) +} + +//删除答案 + +func DeleteAnswer(c *gin.Context) { + Curuser := getCurUser(c) + id, _ := strconv.Atoi(c.Param("answer_id")) + var answer = mymodels.Answer{} + if err := db.DB.Where("answer_id = ?", id).First(&answer).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + if Curuser.UserID != answer.UserID && Curuser.Username != "admin" { + c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + return + } + if err := db.DB.Delete(&mymodels.Answer{}, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Answer not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "ok"}) +} + +//根据id查找答案 + +func GetAnswerByID(c *gin.Context) { + var answer = mymodels.Answer{} + id, _ := strconv.Atoi(c.Param("answer_id")) + if err := db.DB.Where("answer_id = ?", id).First(&answer).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "answer not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"data": answer}) +} + +//当前用户的所有答案 + +func GetAnswerListByUser(c *gin.Context) { + Curuser := getCurUser(c) + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) + user := Curuser + var answerList []mymodels.Answer + if err := db.DB.Where("user_id = ?", user.UserID).Offset((page - 1) * pageSize).Limit(pageSize).Find(&answerList).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "answer not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"data": answerList}) +} + +//当前问题的所有答案 + +func GetAnswerListByQuestion(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) + id, _ := strconv.Atoi(c.Param("answer_id")) + var answerList []mymodels.Answer + if err := db.DB.Where("question_id = ?", id).Offset((page - 1) * pageSize).Limit(pageSize).Find(&answerList).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "answer not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"data": answerList}) +} + +//更新答案 + +func UpdateAnswer(c *gin.Context) { + Curuser := getCurUser(c) + id, _ := strconv.Atoi(c.Param("answer_id")) + var answer = mymodels.Answer{} + if err := db.DB.Where("answer_id = ?", id).First(&answer).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "answer not found"}) + return + } + + if Curuser.UserID != answer.UserID && Curuser.Username != "admin" { + c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + return + } + + var newAnswer = mymodels.Answer{} + if err := c.ShouldBind(&newAnswer); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + newAnswer.AnswerID = answer.AnswerID + newAnswer.QuestionID = answer.QuestionID + newAnswer.UserID = answer.UserID + if err := db.DB.Save(&newAnswer).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": newAnswer}) +} + +// 创建一个ai回答 + +func CreateAnswerByAI(c *gin.Context) { + Curuser := getCurUser(c) + questionId, _ := strconv.Atoi(c.Param("question_id")) + var AI = mymodels.AIRequest{} + if err := db.DB.Where("user_id = ?", Curuser.UserID).First(&AI).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "AI not found"}) + return + } + var question = mymodels.Question{} + if err := db.DB.Where("question_id = ?", questionId).First(&question).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "question not found"}) + return + } + + if Curuser.UserID != question.UserID && Curuser.Username != "admin" { + c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + return + } + + answer_content, err := AI_answer.CallAI(question.Content, AI) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + var answer mymodels.Answer + answer.QuestionID = questionId + answer.UserID = question.UserID + answer.Content = answer_content + db.DB.Create(&answer) + c.JSON(http.StatusOK, gin.H{"data": answer}) +} diff --git a/hkr0101/routes/questions.go b/hkr0101/routes/questions.go new file mode 100644 index 0000000..606f8bb --- /dev/null +++ b/hkr0101/routes/questions.go @@ -0,0 +1,134 @@ +package routes + +import ( + "Initial_Experience/db" + "Initial_Experience/myModels" + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +//var user = myauth.Curuser + +func getCurUser(c *gin.Context) mymodels.User { + // 从上下文中获取用户ID和用户名 + userID, _ := c.Get("userID") + username, _ := c.Get("username") + var user mymodels.User + user.UserID = userID.(int) + user.Username = username.(string) + return user +} + +// 创建问题 + +func CreateQuestion(c *gin.Context) { + Curuser := getCurUser(c) + var question mymodels.Question + if err := c.ShouldBindJSON(&question); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + question.UserID = Curuser.UserID + //fmt.Println(myauth.Curuser.UserID) + //fmt.Println(question.UserID) + db.DB.Create(&question) + c.JSON(http.StatusOK, gin.H{"status": "ok", "question": question}) +} + +// 删除问题 + +func DeleteQuestion(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("question_id")) + Curuser := getCurUser(c) + var question mymodels.Question + //根据当前问题的id找到问题的具体内容 + if err := db.DB.Where("question_id = ?", id).First(&question).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + //如果不是你的问题 + if Curuser.UserID != question.UserID && Curuser.Username != "admin" { + c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + return + } + //删除问题 + if err := db.DB.Delete(&mymodels.Question{}, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Question not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "ok"}) +} + +// 修改问题 + +func UpdateQuestion(c *gin.Context) { + Curuser := getCurUser(c) + id, _ := strconv.Atoi(c.Param("question_id")) + var question mymodels.Question + //根据当前问题的id找到问题的具体内容 + if err := db.DB.Where("question_id = ?", id).First(&question).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + //如果不是你的问题 + if Curuser.UserID != question.UserID && Curuser.Username != "admin" { + c.JSON(http.StatusForbidden, gin.H{"error": "Forbidden"}) + return + } + //构建一个新的问题 + var newQuestion mymodels.Question + if err := c.ShouldBindJSON(&newQuestion); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + newQuestion.UserID = question.UserID + newQuestion.QuestionID = question.QuestionID + + if err := db.DB.Save(&newQuestion).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Question not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"status": "ok", "question": newQuestion}) +} + +// 获取所有问题 + +func GetQuestions(c *gin.Context) { + + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) + + var questions []mymodels.Question + db.DB.Offset((page - 1) * pageSize).Limit(pageSize).Find(&questions) + + c.JSON(http.StatusOK, questions) +} + +// 根据 ID 查询问题 + +func GetQuestionByID(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("question_id")) + var question mymodels.Question + if err := db.DB.First(&question, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Question not found"}) + return + } + c.JSON(http.StatusOK, question) +} + +//获取当前用户的所有问题 + +func GetQuestionByUser(c *gin.Context) { + Curuser := getCurUser(c) + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20")) + var question []mymodels.Question + myID := Curuser.UserID + if err := db.DB.Where("user_id = ?", myID).Offset((page - 1) * pageSize).Limit(pageSize).Find(&question).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Question not found"}) + return + } + c.JSON(http.StatusOK, question) +}