From 7200c6c99b202c7cb81b609b341da8e8cce0f087 Mon Sep 17 00:00:00 2001 From: HuangZiBo <1278481331@qq.com> Date: Sat, 26 Oct 2024 14:22:25 +0800 Subject: [PATCH] add --- brotato/.idea/.gitignore | 8 + brotato/.idea/brotato.iml | 9 + .../inspectionProfiles/Project_Default.xml | 5 + brotato/.idea/modules.xml | 8 + brotato/.idea/vcs.xml | 4 + brotato/go.mod | 10 + brotato/go.sum | 6 + brotato/main.go | 17 + brotato/playerInfo.json | 13 + brotato/web.go | 968 ++++++++++++++++++ 10 files changed, 1048 insertions(+) create mode 100644 brotato/.idea/.gitignore create mode 100644 brotato/.idea/brotato.iml create mode 100644 brotato/.idea/inspectionProfiles/Project_Default.xml create mode 100644 brotato/.idea/modules.xml create mode 100644 brotato/.idea/vcs.xml create mode 100644 brotato/go.mod create mode 100644 brotato/go.sum create mode 100644 brotato/main.go create mode 100644 brotato/playerInfo.json create mode 100644 brotato/web.go diff --git a/brotato/.idea/.gitignore b/brotato/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/brotato/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/brotato/.idea/brotato.iml b/brotato/.idea/brotato.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/brotato/.idea/brotato.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/brotato/.idea/inspectionProfiles/Project_Default.xml b/brotato/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..8d66637 --- /dev/null +++ b/brotato/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/brotato/.idea/modules.xml b/brotato/.idea/modules.xml new file mode 100644 index 0000000..67af9f5 --- /dev/null +++ b/brotato/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/brotato/.idea/vcs.xml b/brotato/.idea/vcs.xml new file mode 100644 index 0000000..d843f34 --- /dev/null +++ b/brotato/.idea/vcs.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/brotato/go.mod b/brotato/go.mod new file mode 100644 index 0000000..96f89d3 --- /dev/null +++ b/brotato/go.mod @@ -0,0 +1,10 @@ +module brotato + +go 1.23.1 + +require ( + github.com/go-sql-driver/mysql v1.8.1 + golang.org/x/crypto v0.28.0 +) + +require filippo.io/edwards25519 v1.1.0 // indirect diff --git a/brotato/go.sum b/brotato/go.sum new file mode 100644 index 0000000..d9ed419 --- /dev/null +++ b/brotato/go.sum @@ -0,0 +1,6 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +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= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= diff --git a/brotato/main.go b/brotato/main.go new file mode 100644 index 0000000..5248462 --- /dev/null +++ b/brotato/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "database/sql" + _ "github.com/go-sql-driver/mysql" +) + +var db *sql.DB + +func main() { + initweb() + //openFileDate() + +} + +// TIP See GoLand help at jetbrains.com/help/go/. +// Also, you can try interactive lessons for GoLand by selecting 'Help | Learn IDE Features' from the main menu. diff --git a/brotato/playerInfo.json b/brotato/playerInfo.json new file mode 100644 index 0000000..c043090 --- /dev/null +++ b/brotato/playerInfo.json @@ -0,0 +1,13 @@ + + + +{ + "id": 123456821, + "userName": "player137", + "url": "https://example.com/pic.png", + + "openId": "119", + "parentAddress": "66666", + "language": "en", + "tg_userid":"123456828player132" +} diff --git a/brotato/web.go b/brotato/web.go new file mode 100644 index 0000000..8d21920 --- /dev/null +++ b/brotato/web.go @@ -0,0 +1,968 @@ +package main + +import ( + "database/sql" + "encoding/json" + "fmt" + _ "github.com/go-sql-driver/mysql" // MySQL 驱动 + "log" + "math/rand" + "net/http" + "strconv" + "time" +) + +func initweb() { + var err error + db, err = sql.Open("mysql", "root:43994399@tcp(192.168.2.19:3306)/brotato") /*******************/ + if err != nil { + log.Fatal("数据库连接失败: ", err) + } + defer db.Close() + + err = db.Ping() + if err != nil { + log.Fatal("数据库连接不可用: ", err) + } + log.Println("数据库连接") + + // 创建 ServeMux 实例 + mux := http.NewServeMux() + + // 注册 /login 路由,绑定到 loginHandler + mux.HandleFunc("/loginHandler", loginHandler) //登录与注册 + //mux.HandleFunc("/getPerson", getPerson) + mux.HandleFunc("/showDailyTaskHandler", showDailyTaskHandler) //每日任务 + mux.HandleFunc("/inviteeListHandler", inviteeListHandler) //返回邀请好友列表(和邀请奖励) + mux.HandleFunc("/userInfoHandler", userInfoHandler) //以openId为查询返回所有用户数据的方法(会有用的) + mux.HandleFunc("/goldCommodityHandler", goldCommodityHandler) //金币商店列表 + mux.HandleFunc("/rankingListHandler", rankingListHandler) //排行榜 + mux.HandleFunc("/saveHandler", saveHandler) // 注册存档路由 + mux.HandleFunc("/loadHandler", loadHandler) // 注册加载路由 + + log.Println("服务器启动,监听端口: 8080") + + //openFileDate() // 打开 json 文件并解析数据 + err = http.ListenAndServe(":8080", mux) // 使用 mux 作为路由器 + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} + +// 打开json文件函数 +/*func openFileDate() { + // 打开 JSON 文件 + jsonFile, err := os.Open("playerInfo.json") + if err != nil { + log.Fatalf("无法打开文件: %v", err) + } + defer jsonFile.Close() + + // 读取文件的内容 + byteValue, _ := ioutil.ReadAll(jsonFile) + + // 解析 JSON 数据到结构体 + err = json.Unmarshal(byteValue, &person) + if err != nil { + log.Fatalf("解析 JSON 数据失败: %v", err) + } +}*/ +/*************************************************一个方法,以open Id为查询,能返回当前用户所有数据***************************************************/ +// User 结构体用于存储查询结果 +type User struct { + Id int `json:"id"` + UserName sql.NullString `json:"-"` // 允许 NULL 值 + Url sql.NullString `json:"-"` + Gold int `json:"gold"` + RandCode sql.NullString `json:"-"` + OpenId string `json:"openId"` + ParentAddress sql.NullString `json:"-"` + Language sql.NullString `json:"-"` + TgUserId sql.NullString `json:"-"` + LastLoginAt sql.NullString `json:"-"` + RegisteredAt sql.NullString `json:"-"` + InviteReward int `json:"inviteReward"` + InviteCode sql.NullString `json:"-"` + NickName sql.NullString `json:"-"` + Ranks sql.NullInt64 `json:"-"` + TopScore sql.NullInt64 `json:"-"` +} + +// JSONUser 结构体用于返回 JSON 数据 +type JSONUser struct { + Id int `json:"id"` + UserName string `json:"userName"` + Url string `json:"url"` + Gold int `json:"gold"` + RandCode string `json:"randCode"` + OpenId string `json:"openId"` + ParentAddress string `json:"parentAddress"` + Language string `json:"language"` + TgUserId string `json:"tg_userid"` + LastLoginAt string `json:"lastLoginAt"` + RegisteredAt string `json:"registeredAt"` + InviteReward int `json:"inviteReward"` + InviteCode string `json:"inviteCode"` + NickName string `json:"nickName"` + Ranks int `json:"ranking"` + TopScore int `json:"topScore"` +} + +// 转换函数,将 User 转换为 JSONUser,并处理 NULL 值 +func (u *User) toJSONUser() JSONUser { + return JSONUser{ + Id: u.Id, + UserName: nullStringToString(u.UserName), + Url: nullStringToString(u.Url), + Gold: u.Gold, + RandCode: nullStringToString(u.RandCode), + OpenId: u.OpenId, + ParentAddress: nullStringToString(u.ParentAddress), + Language: nullStringToString(u.Language), + TgUserId: nullStringToString(u.TgUserId), + LastLoginAt: nullStringToString(u.LastLoginAt), + RegisteredAt: nullStringToString(u.RegisteredAt), + InviteReward: u.InviteReward, + InviteCode: nullStringToString(u.InviteCode), + NickName: nullStringToString(u.NickName), + Ranks: nullIntToInt(u.Ranks), + TopScore: nullIntToInt(u.TopScore), + } +} + +// 辅助函数,将 sql.NullString 转为字符串 +func nullStringToString(ns sql.NullString) string { + if ns.Valid { + return ns.String + } + return "" +} + +// 辅助函数,将 sql.NullInt64 转为 int +func nullIntToInt(ni sql.NullInt64) int { + if ni.Valid { + return int(ni.Int64) + } + return 0 +} + +// 查询并返回用户信息 +func getUserInfoByOpenId(db *sql.DB, openId string) (User, error) { + var user User + + query := ` + SELECT id, userName, url, gold, randCode, openId, parentAddress, language, tg_userid, + lastLoginAt, registeredAt, inviteReward, inviteCode, nickName, ranks, topScore + FROM users WHERE openId = ?` + + err := db.QueryRow(query, openId).Scan( + &user.Id, + &user.UserName, + &user.Url, + &user.Gold, + &user.RandCode, + &user.OpenId, + &user.ParentAddress, + &user.Language, + &user.TgUserId, + &user.LastLoginAt, + &user.RegisteredAt, + &user.InviteReward, + &user.InviteCode, + &user.NickName, + &user.Ranks, + &user.TopScore, + ) + + if err == sql.ErrNoRows { + // 返回自定义错误消息,指示未找到记录 + return user, fmt.Errorf("用户不存在,openId: %s", openId) + } else if err != nil { + // 返回其他查询错误 + return user, fmt.Errorf("查询出错: %v", err) + } + + return user, nil +} + +// 处理请求,返回用户信息 +func userInfoHandler(w http.ResponseWriter, r *http.Request) { + var req struct { + OpenId string `json:"openId"` + } + + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + log.Printf("Error decoding request body: %v\n", err) + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + user, err := getUserInfoByOpenId(db, req.OpenId) + if err != nil { + if err.Error() == fmt.Sprintf("用户不存在,openId: %s", req.OpenId) { + log.Printf("User not found for openId: %s\n", req.OpenId) + http.Error(w, "User not found", http.StatusNotFound) + } else { + log.Printf("Error retrieving user info: %v\n", err) + http.Error(w, "Error retrieving user info", http.StatusInternalServerError) + } + return + } + + // 返回 JSON 格式的用户信息 + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(user.toJSONUser()); err != nil { + log.Printf("Error encoding user info to JSON: %v\n", err) + http.Error(w, "Error encoding user info to JSON", http.StatusInternalServerError) + } +} + +/**********************************************************登录与注册********************************************/ +var person Person + +// 定义玩家,接收所有玩家信息 +type Person struct { + Id int `json:"id"` //数据库中的标识,会自增 + UserName string `json:"userName"` //昵称(甲方要求每次登录刷新) + Url string `json:"url"` //头像的URL + Gold int `json:"gold"` //金币 + RandCode string `json:"randCode"` //随机码,注册时生成,登录会刷新 + OpenId string `json:"openId"` //openid + ParentAddress string `json:"parentAddress"` //上级地址 + Language string `json:"language"` //语言(甲方要求每次登录刷新)(刷新功能未完成) + Tg_userid string `json:"tg_userid"` //tgid + InviteCode string `json:"inviteCode"` //玩家注册时生成并插入的邀请码,不会刷新 +} + +// 检查用户是否存在 +func checkUserExists(db *sql.DB, openId string) (bool, error) { + log.Println("传入的 openId: ", openId) /**************************************************/ + var exists bool + // 查询数据库中是否存在传入的 openid + query := "SELECT COUNT(*) FROM users WHERE openId = ?" + + // 使用 db.QueryRow() 执行查询,查询结果存储在 exists 变量中 + err := db.QueryRow(query, openId).Scan(&exists) + if err != nil { + return false, err // 如果查询出错,返回错误 + } + return exists, nil // 返回查询结果,存在则为 true,不存在为 false +} + +// 登录或注册函数 +func loginOrRegister(db *sql.DB, openid, userName, url, parentAddress, language, tg_userid string) (string, error) { + // 调用 checkUserExists() 函数,检查用户是否存在 + exists, err := checkUserExists(db, openid) + if err != nil { + log.Printf("Error checking user exists: %v\n", err) + return "500", err // 如果查询出错,返回错误 + } + + // 每次登录生成新的随机码 + person.RandCode = generateRandomCode() + //甲方要求的每次登录刷新userName + person.UserName = generateRandomCode() + + // 获取当前时间作为登录和注册时间 + currentTime := time.Now() + + if exists { + // 如果用户已存在,更新数据库中的 RandCode 和 lastLoginAt + updateRandCode := "UPDATE users SET userName = ?,randCode = ?, lastLoginAt = ? WHERE openid = ?" + _, err := db.Exec(updateRandCode, person.UserName, person.RandCode, currentTime, openid) + if err != nil { + log.Printf("Error updating MySQL users") + return "500", err // 如果更新失败,返回错误 + } + log.Printf("用户 %s 登录成功, 生成的新随机码: %s,生成新的userName:%s, 登录时间: %s", openid, person.RandCode, person.UserName, currentTime.Format("2006-01-02 15:04:05")) + return "200", nil + } else { + // 如果 userName, url, parentAddress, language, tg_userid 没有传入,默认设置为空字符串 + if userName == "" { + userName = person.UserName + } + if url == "" { + url = "" + } + if parentAddress == "" { + parentAddress = "" + } + if language == "" { + language = "" + } + if tg_userid == "" { + tg_userid = "" + } + + //玩家注册时用随机码生成一个邀请码插入数据库,这个不会改变 + person.InviteCode = generateRandomCode() + // 如果用户不存在,生成新的随机码并插入相关信息,包括注册时间和登录时间 + //当玩家注册时,随机生成一个邀请码插入数据库,这个不会改变 + insertOpenId := "INSERT INTO users (openid, userName, url, randCode, parentAddress, language, tg_userid, registeredAt, lastLoginAt,inviteCode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,?)" + _, err := db.Exec(insertOpenId, openid, userName, url, person.RandCode, parentAddress, language, tg_userid, currentTime, currentTime, person.InviteCode) // 执行插入操作 + if err != nil { + log.Printf("Error inserting MySQL users") + return "500", err // 如果插入出错,返回错误 + } + log.Printf("用户 %s 注册成功, 生成的新随机码: %s,生成的userName:%s, 注册时间: %s,邀请码:%s", openid, person.RandCode, person.UserName, currentTime.Format("2006-01-02 15:04:05"), person.InviteCode) + return "200", nil + } +} + +// 生成随机码RandCode +func generateRandomCode() string { + const length = 16 // 随机码的长度 + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + randCode := make([]byte, length) + for i := range randCode { + randCode[i] = charset[rand.Intn(len(charset))] + } + return string(randCode) +} +func loginHandler(w http.ResponseWriter, r *http.Request) { + // 定义用于接收 JSON 请求体的结构体 + var req struct { + OpenId string `json:"openId"` + UserName string `json:"userName"` + Url string `json:"url"` + ParentAddress string `json:"parentAddress"` + Language string `json:"language"` + TgUserId string `json:"tg_userid"` + } + + // 使用 json.NewDecoder() 将请求体中的 JSON 数据解析为结构体 + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // 调用 loginOrRegister() 函数,传入解析出的参数 + message, err := loginOrRegister(db, req.OpenId, req.UserName, req.Url, req.ParentAddress, req.Language, req.TgUserId) + if err != nil { + http.Error(w, "登录或注册过程出错", http.StatusInternalServerError) + return + } + + // 返回 JSON 格式的响应,包含登录或注册的结果 + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{ + "message": message, + }) +} + +// ******************************************************************每日任务********************************************************************/ +// 任务列表 +type DailyTask struct { + TaskId int `json:"task_id"` + Reward string `json:"reward"` + IsComplete bool `json:"is_complete"` + EvenId int `json:"even_id"` +} + +/* +func showDailyTaskHandler(w http.ResponseWriter, r *http.Request) { + // 定义用于接收 JSON 请求体的结构体 + var req struct { + AskDailyTask string `json:"AskDailyTask"` + } + // 使用 json.NewDecoder() 将请求体中的 JSON 数据解析为结构体 + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // 调用 showDailyTask() 函数,传入解析出的参数 + tasks, err := showDailyTask(db, req.AskDailyTask) + if err != nil { + http.Error(w, "Error retrieving daily tasks", http.StatusInternalServerError) + return + } + + // 打印任务列表到服务器日志 + for _, task := range tasks { + log.Printf("Task ID: %d, Reward: %s, IsComplete: %v, Event ID: %d\n", + task.TaskId, task.Reward, task.IsComplete, task.EvenId) + } + + // 返回 JSON 格式的响应,包含每日任务表 + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(tasks) // tasks 是一个切片,因此直接编码为 JSON + if err != nil { + http.Error(w, "Error encoding response", http.StatusInternalServerError) + return + } +}*/ + +// 测试代码,功能与上面那个方法相同,但有更多日志打印。出现问题用它 +func showDailyTaskHandler(w http.ResponseWriter, r *http.Request) { + // 定义用于接收 JSON 请求体的结构体 + var req struct { + AskDailyTask string `json:"AskDailyTask"` + } + // 使用 json.NewDecoder() 将请求体中的 JSON 数据解析为结构体 + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + log.Printf("Error decoding request body: %v\n", err) // 打印解码错误 + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // 调用 showDailyTask() 函数,传入解析出的参数 + tasks, err := showDailyTask(db, req.AskDailyTask) + if err != nil { + log.Printf("Error retrieving tasks: %v\n", err) // 打印任务获取错误 + http.Error(w, "Error retrieving daily tasks", http.StatusInternalServerError) + return + } + + // 打印任务列表到服务器日志 + for _, task := range tasks { + log.Printf("Task ID: %d, Reward: %s, IsComplete: %v, Event ID: %d\n", + task.TaskId, task.Reward, task.IsComplete, task.EvenId) + } + + // 返回 JSON 格式的响应,包含每日任务表 + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(tasks) // tasks 是一个切片,因此直接编码为 JSON + if err != nil { + log.Printf("Error encoding tasks to JSON: %v\n", err) // 打印编码错误 + http.Error(w, "Error encoding response", http.StatusInternalServerError) + return + } +} + +// 展示任务列表 +func showDailyTask(db *sql.DB, string2 string) ([]DailyTask, error) { + // 查询数据库 + rows, err := db.Query("SELECT taskId, reward, isComplete, evenId FROM dailytask") + if err != nil { + return nil, fmt.Errorf("query error: %v", err) + } + defer rows.Close() + + // 存储任务列表的切片 + var tasks []DailyTask + + // 遍历查询结果 + for rows.Next() { + var task DailyTask + // 扫描每行数据到 DailyTask 结构体 + err := rows.Scan(&task.TaskId, &task.Reward, &task.IsComplete, &task.EvenId) + if err != nil { + return nil, fmt.Errorf("scan error: %v", err) + } + tasks = append(tasks, task) + } + + // 检查 rows.Next() 是否有发生错误 + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("rows iteration error: %v", err) + } + // 返回任务列表 + return tasks, nil +} + +/********************************************************返回邀请好友列表(和邀请奖励)*************************************************************/ + +type Invitee struct { + InviteeId int `json:"invitee_id"` + InviteeName string `json:"invitee_userName"` + InviteeOpenId string `json:"invitee_open_id"` + InviteeUrl string `json:"invitee_url"` + InviteReward int `json:"invite_reward"` +} + +type Inviter struct { + InviterUrl string `json:"inviter_url"` + InviterId int `json:"inviter_id"` + InviterOpenId string `json:"openId"` + InviterUserName string `json:"userName"` + InviterNickName sql.NullString `json:"nickName"` // 处理可能为 NULL 的字段 + Gold int `json:"gold"` + InviteCode string `json:"invite_code"` +} + +// 定义一个结构体,用于同时返回邀请者和被邀请者信息 +type InviteeResponse struct { + Invitees []Invitee `json:"invitees"` + Inviter Inviter `json:"inviter"` +} + +// inviteeListHandler 处理 HTTP 请求,返回某个用户邀请的好友列表和邀请者信息 +func inviteeListHandler(w http.ResponseWriter, r *http.Request) { + // 定义用于接收 JSON 请求体的结构体 + var req struct { + OpenId string `json:"openId"` + } + + // 使用 json.NewDecoder() 将请求体中的 JSON 数据解析为结构体 + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + log.Printf("Error decoding request body: %v\n", err) // 打印解码错误 + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + // 调用 inviteeList() 函数,传入解析出的参数 + invitees, err := inviteeList(db, req.OpenId) + if err != nil { + log.Printf("Error retrieving invitee list: %v\n", err) // 打印错误日志 + http.Error(w, "Error retrieving invitee list", http.StatusInternalServerError) + return + } + + // 打印邀请的好友列表信息到服务器日志 + log.Println("----- 邀请的好友列表信息 -----") + for _, invitee := range invitees { + log.Printf("好友ID: %d, 用户名: %s, OpenId: %s, 头像URL: %s, 邀请奖励: %d", invitee.InviteeId, invitee.InviteeName, invitee.InviteeOpenId, invitee.InviteeUrl, invitee.InviteReward) + } + + // 调用 inviter() 函数,传入解析出的参数 + inviterList, err := inviter(db, req.OpenId) + if err != nil { + log.Printf("Error retrieving inviter: %v\n", err) // 打印错误日志 + http.Error(w, "Error retrieving inviter", http.StatusInternalServerError) + return + } + + // 假设邀请者只会有一个,获取第一个邀请者 + var inviter Inviter + if len(inviterList) > 0 { + inviter = inviterList[0] + } + + // 打印邀请者信息到服务器日志 + log.Println("----- 邀请者信息 -----") + log.Printf("邀请者ID: %d, 用户名: %s, OpenId: %s, 称号: %s, 头像URL: %s, 金币: %d,邀请码:%s", inviter.InviterId, inviter.InviterUserName, inviter.InviterOpenId, inviter.InviterNickName.String, inviter.InviterUrl, inviter.Gold, inviter.InviteCode) + + // 创建一个结构体,包含邀请者和被邀请者信息 + response := InviteeResponse{ + Invitees: invitees, + Inviter: inviter, + } + + // 返回 JSON 格式的邀请好友列表和邀请者信息 + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(response) + if err != nil { + log.Printf("Error encoding invitee and inviter list to JSON: %v\n", err) // 打印编码错误日志 + http.Error(w, "Error encoding invitee and inviter list to JSON", http.StatusInternalServerError) + } +} + +// inviteeList 查询数据库,返回某个用户邀请的好友列表 +func inviteeList(db *sql.DB, parentAddress string) ([]Invitee, error) { + // 查询数据库,获取邀请者的好友列表 + rows, err := db.Query("SELECT url,id, userName,openId,inviteReward FROM users WHERE parentAddress = ?", parentAddress) + if err != nil { + return nil, fmt.Errorf("query error: %v", err) + } + defer rows.Close() + + // 存储好友列表的切片 + var invitees []Invitee + + // 遍历查询结果 + for rows.Next() { + var invitee Invitee + // 扫描每行数据到 Invitee 结构体 + err := rows.Scan(&invitee.InviteeUrl, &invitee.InviteeId, &invitee.InviteeName, &invitee.InviteeOpenId, &invitee.InviteReward) + if err != nil { + return nil, fmt.Errorf("scan error: %v", err) + } + invitees = append(invitees, invitee) + } + + // 检查 rows.Next() 是否有发生错误 + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("rows iteration error: %v", err) + } + // 返回好友列表 + return invitees, nil +} + +// inviter查询邀请者的信息 +func inviter(db *sql.DB, openId string) ([]Inviter, error) { + // 查询数据库,获取邀请者的好友列表 + rows, err := db.Query("SELECT url, id, openId, userName, nickName, gold,inviteCode FROM users WHERE openId = ?", openId) + if err != nil { + // 如果查询过程中发生错误,返回nil和错误信息 + return nil, fmt.Errorf("查询出错: %v", err) + } + + defer rows.Close() // 确保查询结束后关闭行集 + + var inviters []Inviter // 定义一个Inviter切片用于存储查询结果 + + // 遍历查询结果 + for rows.Next() { + var inviter Inviter + // 扫描每行数据到 Inviter 结构体,处理 nickName 为 sql.NullString 类型 + err := rows.Scan(&inviter.InviterUrl, &inviter.InviterId, &inviter.InviterOpenId, &inviter.InviterUserName, &inviter.InviterNickName, &inviter.Gold, &inviter.InviteCode) + if err != nil { + // 如果扫描过程中出现错误,返回nil和错误信息 + return nil, fmt.Errorf("扫描出错: %v", err) + } + inviters = append(inviters, inviter) // 将扫描到的用户信息追加到切片中 + } + + // 检查 rows.Next() 是否有发生错误 + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("行遍历出错: %v", err) + } + // 返回查询结果的邀请者列表 + return inviters, nil +} + +/**********************************************金币商品页面************************************************************/ +// GoldCommodity 结构体表示 goldcommodity 表的每一行 +type GoldCommodity struct { + CommodityGold int `json:"commodityGold"` + Commodity string `json:"commodity"` + Price string `json:"price"` +} + +// getGoldCommodities 从 goldcommodity 表中查询所有数据并返回 +func getGoldCommodities(db *sql.DB) ([]GoldCommodity, error) { + // 查询整个 goldcommodity 表 + rows, err := db.Query("SELECT commodityGold, commodity, price FROM goldcommodity") + if err != nil { + return nil, fmt.Errorf("查询出错: %v", err) + } + defer rows.Close() + + var commodities []GoldCommodity + + // 遍历查询结果,将每行数据扫描到 GoldCommodity 结构体中 + for rows.Next() { + var commodity GoldCommodity + err := rows.Scan(&commodity.CommodityGold, &commodity.Commodity, &commodity.Price) + if err != nil { + return nil, fmt.Errorf("扫描出错: %v", err) + } + commodities = append(commodities, commodity) + } + + // 检查 rows.Next() 是否有其他错误 + if err = rows.Err(); err != nil { + return nil, fmt.Errorf("行遍历出错: %v", err) + } + + return commodities, nil +} + +// goldCommodityHandler 处理 HTTP 请求,返回 goldcommodity 表中的所有数据 +func goldCommodityHandler(w http.ResponseWriter, r *http.Request) { + // 查询 goldcommodity 表中的所有数据 + commodities, err := getGoldCommodities(db) + if err != nil { + log.Printf("Error retrieving gold commodities: %v\n", err) + http.Error(w, "Error retrieving gold commodities", http.StatusInternalServerError) + return + } + + // 设置响应头为 JSON 格式并返回数据 + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(commodities); err != nil { + log.Printf("Error encoding commodities to JSON: %v\n", err) + http.Error(w, "Error encoding commodities to JSON", http.StatusInternalServerError) + } +} + +/******************************************************排行榜数据*************************************************************/ +// 定义 ranking 结构体 +type ranking struct { + UserId int `json:"userId"` + Rank int `json:"rank"` + TopScore int `json:"topScore"` + NickName string `json:"nickName"` +} + +// 查询排行榜数据 + +// +++++++++++++++++++++++++++++++++++返回前三+++++++++++++++++++++++++++++++++++++/ +func rankingListHandler(w http.ResponseWriter, r *http.Request) { + // 在查询排行榜数据之前,先更新排名 + err := updatePlayerRankings(db) + if err != nil { + log.Printf("Error updating player rankings: %v\n", err) + http.Error(w, "Error updating player rankings", http.StatusInternalServerError) + return + } + + // 查询 top N 排行榜数据 + // 请根据您的需要修改 TopNumber 的值 + // 例如,TopNumber = 100,则返回排行榜前 100 名 + // 例如,TopNumber = 500,则返回排行榜前 500 名 + // 注意:TopNumber 的值越大,返回的数据量越大,但也会占用更多的 CPU 和内存 + // 请根据您的应用的需要和 CPU 和内存的情况来决定 TopNumber 的值 + // 例如,TopNumber = 1000,但您的应用 CPU 和内存都有 8GB,可能需要 30-60 秒才能返回结果 + // 例如,TopNumber = 50 + TopNumber := 3 // 假设要获取前 3 名 + rows, err := db.Query("SELECT id, ranks, topScore, nickName FROM playerranking ORDER BY topScore DESC LIMIT ?", TopNumber) + if err != nil { + log.Printf("Error retrieving ranking list: %v\n", err) + http.Error(w, "Error retrieving ranking list", http.StatusInternalServerError) + return + } + defer rows.Close() + + // 定义一个 ranking 切片来存储查询结果 + var rankings []ranking + + // 遍历查询结果,将每行数据扫描到 ranking 结构体中 + for rows.Next() { + var rank ranking + err := rows.Scan(&rank.UserId, &rank.Rank, &rank.TopScore, &rank.NickName) + if err != nil { + log.Printf("Error scanning ranking data: %v\n", err) + http.Error(w, "Error processing ranking data", http.StatusInternalServerError) + return + } + rankings = append(rankings, rank) + } + + // 检查遍历行集时是否发生错误 + if err = rows.Err(); err != nil { + log.Printf("Error iterating over rows: %v\n", err) + http.Error(w, "Error retrieving ranking data", http.StatusInternalServerError) + return + } + + // 设置响应头为 JSON 格式并返回数据 + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(rankings); err != nil { + log.Printf("Error encoding ranking list to JSON: %v\n", err) + http.Error(w, "Error encoding ranking list to JSON", http.StatusInternalServerError) + } +} + +// 更新玩家排名并同步到 users 表 +func updatePlayerRankings(db *sql.DB) error { + // 1. 初始化排名变量 + _, err := db.Exec("SET @ranks := 0;") + if err != nil { + return fmt.Errorf("初始化排名变量时出错: %v", err) + } + + // 2. 更新 playerranking 表中的 rank + updateRankSQL := ` + UPDATE playerranking + SET ranks = (@ranks := @ranks + 1) + ORDER BY topScore DESC; + ` + + _, err = db.Exec(updateRankSQL) + if err != nil { + return fmt.Errorf("更新排名时出错: %v", err) + } + + // 3. 将 playerranking 表中的 rank 和 topScore 同步到 users 表 + syncRankTopScoreSQL := ` + UPDATE users u + JOIN playerranking p ON u.id = p.id + SET u.ranks = p.ranks, u.topScore = p.topScore; + ` + + _, err = db.Exec(syncRankTopScoreSQL) + if err != nil { + return fmt.Errorf("同步 rank 和 topScore 到 users 表时出错: %v", err) + } + + // 4. 将 users 表中的 nickName 同步到 playerranking 表 + syncNickNameSQL := ` + UPDATE playerranking p + JOIN users u ON p.id = u.id + SET p.nickName = u.nickName; + ` + + _, err = db.Exec(syncNickNameSQL) + if err != nil { + return fmt.Errorf("同步 nickName 到 playerranking 表时出错: %v", err) + } + + return nil +} + +/******************************************************存档****************************************************************/ + +/* +前端应传来的json格式 + + { + "save_index": 1, + "backup_index": 0, + "major_data": {"level": 5, "progress": "50%"}, + "minor_data": {"cutsceneEnded": true}, + "real_time_data": {"npcSeen": true, "patchID": 123} + } +*/ +// SaveData 结构体定义 +type SaveData struct { + ID int `json:"id"` + SaveIndex int `json:"save_index"` + BackupIndex int `json:"backup_index"` + MajorData json.RawMessage `json:"major_data"` + MinorData json.RawMessage `json:"minor_data"` + RealTimeData json.RawMessage `json:"real_time_data"` + Timestamp sql.NullTime `json:"timestamp"` // 使用 sql.NullTime +} + +// 存档数据到数据库 +func saveToDatabase(db *sql.DB, data SaveData) error { + query := ` + INSERT INTO save_data (save_index, backup_index, major_data, minor_data, real_time_data, timestamp) + VALUES (?, ?, ?, ?, ?, ?) + ` + _, err := db.Exec(query, + data.SaveIndex, + data.BackupIndex, + data.MajorData, + data.MinorData, + data.RealTimeData, + data.Timestamp, + ) + if err != nil { + return fmt.Errorf("保存数据到数据库时出错: %v", err) + } + return nil +} + +// 从数据库加载存档 +func loadFromDatabase(db *sql.DB, saveIndex, backupIndex int) (SaveData, error) { + var data SaveData + var timestampStr string // 使用字符串接收 Timestamp 字段 + + query := "SELECT save_index, backup_index, major_data, minor_data, real_time_data, timestamp FROM save_data WHERE save_index = ? AND backup_index = ?" + err := db.QueryRow(query, saveIndex, backupIndex).Scan( + &data.SaveIndex, + &data.BackupIndex, + &data.MajorData, + &data.MinorData, + &data.RealTimeData, + ×tampStr, // 将 timestamp 作为字符串接收 + ) + if err != nil { + return data, err + } + + // 手动解析 timestampStr 为 time.Time + parsedTime, err := time.Parse("2006-01-02 15:04:05", timestampStr) + if err != nil { + return data, fmt.Errorf("解析时间戳时出错: %v", err) + } + + data.Timestamp = sql.NullTime{ + Time: parsedTime, + Valid: true, + } + + return data, nil +} + +// 存档处理函数 +func saveHandler(w http.ResponseWriter, r *http.Request) { + log.Printf("请求方法: %s, 请求路径: %s\n", r.Method, r.URL.Path) // 打印请求方法和路径 + + // 检查请求方法是否为 GET + if r.Method != http.MethodGet { + http.Error(w, "请求方法错误,仅支持 GET", http.StatusMethodNotAllowed) + return + } + + // 从查询参数中获取存档数据 + saveIndexStr := r.URL.Query().Get("save_index") + backupIndexStr := r.URL.Query().Get("backup_index") + majorDataStr := r.URL.Query().Get("major_data") + minorDataStr := r.URL.Query().Get("minor_data") + realTimeDataStr := r.URL.Query().Get("real_time_data") + + // 转换 save_index 和 backup_index 为整数 + saveIndex, err := strconv.Atoi(saveIndexStr) + backupIndex, err := strconv.Atoi(backupIndexStr) + if err != nil { + http.Error(w, "索引参数错误", http.StatusBadRequest) + return + } + + // 将 JSON 字符串转换为对应的结构 + var majorData, minorData, realTimeData json.RawMessage + err = json.Unmarshal([]byte(majorDataStr), &majorData) + err = json.Unmarshal([]byte(minorDataStr), &minorData) + err = json.Unmarshal([]byte(realTimeDataStr), &realTimeData) + if err != nil { + http.Error(w, "解析 JSON 数据时出错", http.StatusBadRequest) + return + } + + // 创建存档数据结构 + data := SaveData{ + SaveIndex: saveIndex, + BackupIndex: backupIndex, + MajorData: majorData, + MinorData: minorData, + RealTimeData: realTimeData, + Timestamp: sql.NullTime{ + Time: time.Now(), + Valid: true, + }, + } + + // 存档数据到数据库 + err = saveToDatabase(db, data) + if err != nil { + http.Error(w, fmt.Sprintf("保存存档时出错: %v", err), http.StatusInternalServerError) + return + } + + // 返回成功响应 + w.WriteHeader(http.StatusCreated) + w.Write([]byte("存档成功")) +} + +// 加载处理函数 +func loadHandler(w http.ResponseWriter, r *http.Request) { + log.Printf("请求方法: %s, 请求路径: %s\n", r.Method, r.URL.Path) // 打印请求方法和路径 + + if r.Method != http.MethodGet { + http.Error(w, "请求方法错误,仅支持 GET", http.StatusMethodNotAllowed) + return + } + + saveIndexStr := r.URL.Query().Get("save_index") + backupIndexStr := r.URL.Query().Get("backup_index") + if saveIndexStr == "" || backupIndexStr == "" { + http.Error(w, "缺少 save_index 或 backup_index 参数", http.StatusBadRequest) + return + } + + saveIndex, err := strconv.Atoi(saveIndexStr) + backupIndex, err := strconv.Atoi(backupIndexStr) + if err != nil { + http.Error(w, "索引参数错误", http.StatusBadRequest) + return + } + + data, err := loadFromDatabase(db, saveIndex, backupIndex) + if err != nil { + http.Error(w, fmt.Sprintf("加载存档时出错: %v", err), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(data) + if err != nil { + http.Error(w, "编码 JSON 数据时出错", http.StatusInternalServerError) + } +} + +// 修改 loadHandler 中的时间格式化逻辑 +func formatTimestamp(timestamp sql.NullTime) string { + if timestamp.Valid { + return timestamp.Time.Format("2006-01-02 15:04:05") + } + return "" +}