Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions QA/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
我做了更新
41 changes: 41 additions & 0 deletions QA/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module QA

go 1.23.2
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

Invalid Go version specified

The Go version specified (1.23.2) is not a valid Go version. As of October 2024, the latest stable version is 1.21.x.

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

-go 1.23.2
+go 1.21

This will ensure compatibility with the Go toolchain and avoid potential issues.

📝 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 QA
go 1.23.2
module QA
go 1.21


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/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/gin-contrib/cors v1.7.2 // 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/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/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
)
95 changes: 95 additions & 0 deletions QA/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
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/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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/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=
217 changes: 217 additions & 0 deletions QA/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package main

import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"net/http"
"strconv"
)

type User struct {
Name string
ID string
Password string
Salt string // 用于存储盐
}
type Question struct {
QuestionID int `json:"QuestionID" gorm:"primaryKey;AUTO_INCREMENT"`
ID string `json:"ID"`
Title string `json:"title"`
Content string `json:"content"`
Answers []Answer `json:"answers" gorm:"foreignKey:QuestionID"`
}
type Answer struct {
ID string `json:"ID"`
QuestionID int `json:"QuestionID"`
Content string `json:"content"`
}
type AIResponse struct {
Response string `json:"response"`
}

// 生成随机盐
func GenerateSalt() string {
bytes := make([]byte, 16)
if _, err := rand.Read(bytes); err != nil {
panic("Error generating salt")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid Using panic in GenerateSalt; Return an Error Instead

Using panic can cause your entire application to crash, which is undesirable in a production environment. It's better to return an error and handle it gracefully.

Modify the function as follows:

-func GenerateSalt() string {
+func GenerateSalt() (string, error) {
     bytes := make([]byte, 16)
     if _, err := rand.Read(bytes); err != nil {
-        panic("Error generating salt")
+        return "", err
     }
     return hex.EncodeToString(bytes), nil
 }

Update the places where GenerateSalt is called to handle the returned error appropriately.

Committable suggestion was skipped due to low confidence.

}
return hex.EncodeToString(bytes)
}

// 哈希加盐
func HashPassword(password, salt string) string {
saltedPassword := password + salt
hash := sha256.New()
hash.Write([]byte(saltedPassword))
return hex.EncodeToString(hash.Sum(nil))
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use a Secure Password Hashing Algorithm Instead of SHA-256

Using SHA-256 for password hashing is not recommended because it's designed for speed and is vulnerable to brute-force attacks. It's better to use a specialized password hashing algorithm like bcrypt, scrypt, or Argon2.

[security]

Apply this diff to switch to bcrypt:

+import (
+    "golang.org/x/crypto/bcrypt"
+    ...
+)

-func HashPassword(password, salt string) string {
-    saltedPassword := password + salt
-    hash := sha256.New()
-    hash.Write([]byte(saltedPassword))
-    return hex.EncodeToString(hash.Sum(nil))
-}
+func HashPassword(password string) (string, error) {
+    hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+    return string(hashedBytes), err
+}

+func CheckPasswordHash(password, hash string) bool {
+    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
+    return err == nil
+}

Update the registration and login logic accordingly to handle errors and remove the use of salt.

📝 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
func HashPassword(password, salt string) string {
saltedPassword := password + salt
hash := sha256.New()
hash.Write([]byte(saltedPassword))
return hex.EncodeToString(hash.Sum(nil))
}
import (
"golang.org/x/crypto/bcrypt"
// ... (other imports)
)
func HashPassword(password string) (string, error) {
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(hashedBytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}


// 验证注册时用户名学号密码的合理性
func yes(c *gin.Context, u User) bool {
const (
minNameLen = 2
maxNameLen = 5
StudentIdLen = 8
minPasswordLen = 6
maxPasswordLen = 20
)

if len(u.Name) < minNameLen || len(u.Name) > maxNameLen {
c.JSON(http.StatusBadRequest, gin.H{"error": "用户名请控制在2-5字内"})
return false
}

if len(u.ID) != StudentIdLen {
c.JSON(http.StatusBadRequest, gin.H{"error": "请使用你本人的学号"})
return false
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Validate That Student IDs Are Numeric

Currently, the code checks only the length of the Student ID but doesn't verify if it contains only digits. This can lead to invalid IDs being accepted.

Apply this diff to validate the Student ID format:

+import (
+    ...
+    "regexp"
+)

 func yes(c *gin.Context, u User) bool {
     const (
         ...
     )

     if len(u.ID) != StudentIdLen {
         c.JSON(http.StatusBadRequest, gin.H{"error": "请使用你本人的学号"})
         return false
     }
+    if !regexp.MustCompile(`^\d+$`).MatchString(u.ID) {
+        c.JSON(http.StatusBadRequest, gin.H{"error": "学号只能包含数字"})
+        return false
+    }

This ensures that only valid numeric Student IDs are accepted.

Committable suggestion was skipped due to low confidence.


if len(u.Password) < minPasswordLen || len(u.Password) > maxPasswordLen {
c.JSON(http.StatusBadRequest, gin.H{"error": "密码请控制在6-20字内"})
return false
}

return true
}

// 问答网站各个函数
func postQuestion(c *gin.Context) {
var question Question
c.BindJSON(&question)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle Errors Returned by c.BindJSON

Ignoring errors from c.BindJSON can lead to runtime exceptions if the JSON payload is malformed. It's important to handle these errors to ensure the robustness of your application.

Apply this diff to handle the error:

-    c.BindJSON(&question)
+    if err := c.BindJSON(&question); err != nil {
+        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+        return
+    }

Repeat similar changes for the following lines:

  • Line 102 in postAnswer function.
  • Line 176 in the /hdu.wiki/register route.
  • Line 197 in the /hdu.wiki/login route.

This ensures that the application responds appropriately to invalid input.

Also applies to: 102-102, 176-176, 197-197

if len(question.Title) == 0 || len(question.Title) > 10 {
c.JSON(http.StatusBadRequest, gin.H{
"message": "标题请设置在1-10字内",
})
return
}
if len(question.Content) < 10 || len(question.Content) > 100 {
c.JSON(http.StatusBadRequest, gin.H{"error": "问题内容需要控制在10-100字内"})
return
}
db2.Create(&question)
c.JSON(http.StatusCreated, question)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Associate Questions with the Authenticated User

In the postQuestion function, the question.ID field (which represents the user ID) should be set to the ID of the authenticated user. This ensures that each question is correctly associated with the user who posted it. Currently, this association is missing.

Apply this diff to set the question.ID to the authenticated user's ID:

 func postQuestion(c *gin.Context) {
     var question Question
     c.BindJSON(&question)
+    userID, exists := c.Get("userID")
+    if !exists {
+        c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
+        return
+    }
+    question.ID = userID.(string)
     if len(question.Title) == 0 || len(question.Title) > 10 {
         c.JSON(http.StatusBadRequest, gin.H{
             "message": "标题请设置在1-10字内",
         })
         return
     }
     if len(question.Content) < 10 || len(question.Content) > 100 {
         c.JSON(http.StatusBadRequest, gin.H{"error": "问题内容需要控制在10-100字内"})
         return
     }
     db2.Create(&question)
     c.JSON(http.StatusCreated, question)
 }

Also, ensure that the route is protected with JWTMiddleware to enforce authentication:

-server.POST("/hdu.wiki/question", postQuestion)
+server.POST("/hdu.wiki/question", JWTMiddleware(), postQuestion)

Committable suggestion was skipped due to low confidence.

func postAnswer(c *gin.Context) {
var answer Answer
c.BindJSON(&answer)
var existingquestion Question
res := db2.Where("question_id= ?", answer.QuestionID).First(&existingquestion)
if res.RowsAffected == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"message": "未找到该问题",
})
return
}
if len(answer.Content) == 0 || len(answer.Content) > 1000 {
c.JSON(http.StatusBadRequest, gin.H{
"message": "回答请设置在1-1000字内",
})
return
}
db2.Create(&answer)
c.JSON(http.StatusCreated, answer)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Associate Answers with the Authenticated User

In the postAnswer function, the answer.ID field (which represents the user ID) should be set to the ID of the authenticated user. This ensures that each answer is correctly associated with the user who posted it. Currently, this association is missing.

Apply this diff to set the answer.ID to the authenticated user's ID:

 func postAnswer(c *gin.Context) {
     var answer Answer
     c.BindJSON(&answer)
+    userID, exists := c.Get("userID")
+    if !exists {
+        c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
+        return
+    }
+    answer.ID = userID.(string)
     var existingquestion Question
     res := db2.Where("question_id= ?", answer.QuestionID).First(&existingquestion)
     if res.RowsAffected == 0 {
         c.JSON(http.StatusBadRequest, gin.H{
             "message": "未找到该问题",
         })
         return
     }
     if len(answer.Content) == 0 || len(answer.Content) > 1000 {
         c.JSON(http.StatusBadRequest, gin.H{
             "message": "回答请设置在1-1000字内",
         })
         return
     }
     db2.Create(&answer)
     c.JSON(http.StatusCreated, answer)
 }

Also, update the route to include JWTMiddleware:

-server.POST("/hdu.wiki/answers", postAnswer)
+server.POST("/hdu.wiki/answers", JWTMiddleware(), postAnswer)

Committable suggestion was skipped due to low confidence.

func listQuestions(c *gin.Context) {
var questions []Question
db2.Preload("Answers").Find(&questions)
c.JSON(http.StatusOK, questions)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement Pagination in listQuestions to Improve Performance

Fetching all questions and preloading their answers without pagination can lead to performance issues as the data grows.

Consider adding pagination parameters to limit the number of records returned per request:

 func listQuestions(c *gin.Context) {
     var questions []Question
+    page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
+    pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
+    offset := (page - 1) * pageSize
-    db2.Preload("Answers").Find(&questions)
+    db2.Preload("Answers").Limit(pageSize).Offset(offset).Find(&questions)
     c.JSON(http.StatusOK, questions)
 }

Ensure to handle errors when parsing query parameters.

Committable suggestion was skipped due to low confidence.

}
func deleteQuestion(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle Errors When Parsing IDs from Route Parameters

Ignoring errors when converting route parameters to integers with strconv.Atoi can lead to unexpected behavior if the parameter is not a valid integer.

Apply this diff to handle the error:

-    id, _ := strconv.Atoi(c.Param("id"))
+    idStr := c.Param("id")
+    id, err := strconv.Atoi(idStr)
+    if err != nil {
+        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
+        return
+    }

Implement this change in both the deleteQuestion and deleteAnswer functions to ensure robust error handling.

Also applies to: 135-135

result := db2.Delete(&Question{}, id)
if result.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"message": "这个问题不存在!"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "这个问题已被删除"})
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Restrict Question Deletion to the Owner

In the deleteQuestion function, any authenticated user can delete any question. To enhance security, only the user who posted the question should be allowed to delete it. Currently, this check is not implemented.

Apply this diff to verify question ownership before deletion:

 func deleteQuestion(c *gin.Context) {
     id, _ := strconv.Atoi(c.Param("id"))
+    userID, exists := c.Get("userID")
+    if !exists {
+        c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
+        return
+    }
+    var question Question
+    result := db2.Where("question_id = ?", id).First(&question)
+    if result.RowsAffected == 0 {
+        c.JSON(http.StatusNotFound, gin.H{"message": "这个问题不存在!"})
+        return
+    }
+    if question.ID != userID.(string) {
+        c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized to delete this question"})
+        return
+    }
     db2.Delete(&question)
     c.JSON(http.StatusOK, gin.H{"message": "这个问题已被删除"})
 }

Also, ensure the route uses JWTMiddleware:

-server.DELETE("/hdu.wiki/questions/:id", deleteQuestion)
+server.DELETE("/hdu.wiki/questions/:id", JWTMiddleware(), deleteQuestion)

Committable suggestion was skipped due to low confidence.

func deleteAnswer(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
result := db2.Delete(&Answer{}, id)
if result.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "Answer not found"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Answer deleted"})
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Restrict Answer Deletion to the Owner

In the deleteAnswer function, any authenticated user can delete any answer. To ensure security, only the user who posted the answer should be allowed to delete it. This ownership check is currently missing.

Apply this diff to verify answer ownership before deletion:

 func deleteAnswer(c *gin.Context) {
     id, _ := strconv.Atoi(c.Param("id"))
+    userID, exists := c.Get("userID")
+    if !exists {
+        c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
+        return
+    }
+    var answer Answer
+    result := db2.Where("id = ?", id).First(&answer)
+    if result.RowsAffected == 0 {
+        c.JSON(http.StatusNotFound, gin.H{"error": "Answer not found"})
+        return
+    }
+    if answer.ID != userID.(string) {
+        c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized to delete this answer"})
+        return
+    }
     db2.Delete(&answer)
     c.JSON(http.StatusOK, gin.H{"message": "Answer deleted"})
 }

Update the route to include JWTMiddleware:

-server.DELETE("/hdu.wiki/answers/:id", deleteAnswer)
+server.DELETE("/hdu.wiki/answers/:id", JWTMiddleware(), deleteAnswer)

Committable suggestion was skipped due to low confidence.

func searchQuestions(c *gin.Context) {
query := c.Query("query")
var results []Question
db2.Where("title LIKE ? OR content LIKE ?", "%"+query+"%", "%"+query+"%").Find(&results)
c.JSON(http.StatusOK, results)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle Empty Search Queries Appropriately

In the searchQuestions function, if the query parameter is empty, the function returns all questions. This might not be the intended behavior and could lead to performance issues as the data grows.

Consider returning an error when the search query is empty:

 func searchQuestions(c *gin.Context) {
     query := c.Query("query")
+    if strings.TrimSpace(query) == "" {
+        c.JSON(http.StatusBadRequest, gin.H{"error": "Search query cannot be empty"})
+        return
+    }
     var results []Question
     db2.Where("title LIKE ? OR content LIKE ?", "%"+query+"%", "%"+query+"%").Find(&results)
     c.JSON(http.StatusOK, results)
 }

This ensures that the client provides a valid search term.

📝 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
func searchQuestions(c *gin.Context) {
query := c.Query("query")
var results []Question
db2.Where("title LIKE ? OR content LIKE ?", "%"+query+"%", "%"+query+"%").Find(&results)
c.JSON(http.StatusOK, results)
}
func searchQuestions(c *gin.Context) {
query := c.Query("query")
if strings.TrimSpace(query) == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Search query cannot be empty"})
return
}
var results []Question
db2.Where("title LIKE ? OR content LIKE ?", "%"+query+"%", "%"+query+"%").Find(&results)
c.JSON(http.StatusOK, results)
}


var db1 *gorm.DB
var db2 *gorm.DB
var err error

func main() {
dsn1 := "root:123456@tcp(127.0.0.1:3306)/mydatabase?charset=utf8mb4&parseTime=True&loc=Local"
db1, err := gorm.Open(mysql.Open(dsn1), &gorm.Config{
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid Hardcoding Database Credentials

Hardcoding sensitive information like database usernames and passwords is a security risk. It's recommended to use environment variables or configuration files to manage sensitive data. This approach enhances security and makes it easier to manage different environments (development, testing, production).

Apply this diff to refactor the code:

-import (
+import (
     ...
+    "os"
 )

 ...

-    dsn1 := "root:123456@tcp(127.0.0.1:3306)/mydatabase?charset=utf8mb4&parseTime=True&loc=Local"
+    dsn1 := os.Getenv("DB_DSN1")
     db1, err := gorm.Open(mysql.Open(dsn1), &gorm.Config{
         Logger: logger.Default.LogMode(logger.Info),
     })

 ...

-    dsn2 := "root:123456@tcp(127.0.0.1:3306)/mydatabase?charset=utf8mb4&parseTime=True&loc=Local"
+    dsn2 := os.Getenv("DB_DSN2")
     db2, err = gorm.Open(mysql.Open(dsn2), &gorm.Config{
         Logger: logger.Default.LogMode(logger.Info),
     })

Ensure that you set the environment variables DB_DSN1 and DB_DSN2 appropriately in your deployment environment.

Also applies to: 163-164

Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
panic("failed to connect database")
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Combine Database Connections to Optimize Resource Usage

Both db1 and db2 are connecting to the same database using identical DSNs. Maintaining two separate connections is unnecessary and could lead to increased resource usage. Consider using a single database connection for both user management and question-answer management.

Apply this diff to use a single database connection:

-var db1 *gorm.DB
-var db2 *gorm.DB
+var db *gorm.DB
 var err error

 func main() {
-    dsn1 := os.Getenv("DB_DSN1")
-    db1, err := gorm.Open(mysql.Open(dsn1), &gorm.Config{
+    dsn := os.Getenv("DB_DSN")
+    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
         Logger: logger.Default.LogMode(logger.Info),
     })
     if err != nil {
         panic("failed to connect database")
     }
-    db1.AutoMigrate(&User{})
-
-    dsn2 := os.Getenv("DB_DSN2")
-    db2, err = gorm.Open(mysql.Open(dsn2), &gorm.Config{
-        Logger: logger.Default.LogMode(logger.Info),
-    })
-    if err != nil {
-        panic("failed to connect database")
-    }
-    db2.AutoMigrate(&Question{}, &Answer{})
+    db.AutoMigrate(&User{}, &Question{}, &Answer{})

Update all references from db1 and db2 to db throughout your code:

-    res := db1.Where("id = ?", u.ID).First(&existingUser)
+    res := db.Where("id = ?", u.ID).First(&existingUser)

-    db2.Create(&question)
+    db.Create(&question)

This change simplifies your code and reduces overhead.

Also applies to: 163-169

db1.AutoMigrate(&User{})
dsn2 := "root:123456@tcp(127.0.0.1:3306)/mydatabase?charset=utf8mb4&parseTime=True&loc=Local"
db2, err = gorm.Open(mysql.Open(dsn2), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
panic("failed to connect database")
}
db2.AutoMigrate(&Question{}, &Answer{})
server := gin.Default()
server.Use(cors.Default())

server.POST("/hdu.wiki/register", func(c *gin.Context) {
var u User
c.BindJSON(&u)
if !yes(c, u) {
return
}
var existingUser User
res := db1.Where("id = ?", u.ID).First(&existingUser)
if res.RowsAffected != 0 {
c.JSON(http.StatusBadRequest, gin.H{
"message": "该学号已被注册!",
})
} else {
u.Salt = GenerateSalt()
u.Password = HashPassword(u.Password, u.Salt)
db1.Create(&u)
c.JSON(http.StatusOK, gin.H{
"message": "注册成功!",
})
}
})
server.POST("/hdu.wiki/login", func(c *gin.Context) {
var u User
c.BindJSON(&u)
var existingUser User
res := db1.Where("id = ?", u.ID).First(&existingUser)
if res.RowsAffected != 0 && HashPassword(u.Password, existingUser.Salt) == existingUser.Password {
c.JSON(http.StatusOK, gin.H{
"message": "登录成功!",
})
} else {
c.JSON(http.StatusBadRequest, gin.H{
"error": "登录失败,学号或密码不正确!",
})
}
})
server.POST("/hdu.wiki/question", postQuestion)
server.POST("/hdu.wiki/answers", postAnswer)
server.GET("/hdu.wiki/questions", listQuestions)
server.DELETE("/hdu.wiki/questions/:id", deleteQuestion)
server.DELETE("/hdu.wiki/answers/:id", deleteAnswer)
server.GET("/hdu.wiki/search", searchQuestions)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement Authentication and Authorization for Protected Routes

All users can currently access protected routes like posting and deleting questions or answers without any authentication. This poses a significant security risk.

[security]

Consider implementing authentication mechanisms (e.g., JWT tokens or session-based authentication) and middleware to protect these routes. Only authenticated users should be able to perform actions like posting or deleting content.

Also applies to: 84-99, 100-119, 125-133, 134-142

server.Run(":8080")
}
Binary file added QA/static/background.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added QA/static/logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading