refactor: 重构数据库逻辑

This commit is contained in:
lixiangwuxian
2024-10-11 00:14:11 +08:00
parent 7aebdeae56
commit 769308389a
16 changed files with 289 additions and 164 deletions

View File

@@ -7,16 +7,11 @@ import (
"strconv"
"time"
"git.lxtend.com/qqbot/sqlite3"
_ "github.com/mattn/go-sqlite3"
)
func initDB() {
db, err := sql.Open("sqlite3", "./bindss.db")
if err != nil {
log.Print(err)
}
defer db.Close()
func init() {
createBindTableSQL := `CREATE TABLE IF NOT EXISTS ssBind (
id INTEGER PRIMARY KEY AUTOINCREMENT,
qqid TEXT UNIQUE,
@@ -60,6 +55,7 @@ func initDB() {
missed_notes INT,
max_combo INT,
score INT,
max_score INT,
full_combo BOOLEAN,
device_hmd VARCHAR(100),
device_controller_left VARCHAR(100),
@@ -67,37 +63,18 @@ func initDB() {
generated_time TEXT
);`
_, err = db.Exec(createBindTableSQL)
if err != nil {
log.Print(err)
}
_, err = db.Exec(createScoreTableSQL)
if err != nil {
log.Print(err)
}
_, err = db.Exec(createRecordTableSQL)
if err != nil {
log.Print(err)
}
sqlite3.TryCreateTable(createBindTableSQL)
sqlite3.TryCreateTable(createScoreTableSQL)
sqlite3.TryCreateTable(createRecordTableSQL)
}
var SSQuery = &ssQuery{}
type ssQuery struct {
db *sql.DB
}
func init() {
initDB()
db, err := sql.Open("sqlite3", "./bindss.db")
if err != nil {
log.Print(err)
}
SSQuery = &ssQuery{db: db}
}
func (ss *ssQuery) BindSS(qqId string, ssId string) (reply string) {
tx, err := ss.db.Begin()
tx, err := sqlite3.GetTran()
if err != nil {
log.Print(err)
}
@@ -129,7 +106,7 @@ func (ss *ssQuery) BindSS(qqId string, ssId string) (reply string) {
}
func (ss *ssQuery) UnbindSS(qqId string) (reply string) {
tx, err := ss.db.Begin()
tx, err := sqlite3.GetTran()
if err != nil {
log.Print(err)
}
@@ -152,24 +129,29 @@ func (ss *ssQuery) UnbindSS(qqId string) (reply string) {
return "解绑成功,重新绑定请输入\"绑定ss [ssId]\""
}
func (ss *ssQuery) GetScore(qqId string) (reply string) {
tx, err := ss.db.Begin()
func (ss *ssQuery) GetScore(qqId string) (reply string, err error) {
db := sqlite3.GetDB() // 假设 sqlite3.GetDB() 返回 *sqlx.DB
tx, err := db.Beginx()
if err != nil {
log.Print(err)
return "数据库连接失败,请稍后重试", err
}
defer tx.Rollback()
//是否已绑定
if rows, err := tx.Query("SELECT * FROM ssBind WHERE qqid = ?", qqId); err == nil {
if !rows.Next() {
return "您未绑定ss账号输入\"绑定ss [ssId]\"绑定"
}
}
// 检查是否已绑定
var ssId string
tx.QueryRow("SELECT ssid FROM ssBind WHERE qqid = ?", qqId).Scan(&ssId)
err = tx.Get(&ssId, "SELECT ssid FROM ssBind WHERE qqid = ?", qqId)
if err != nil {
return "您未绑定ss账号输入\"绑定ss [ssId]\"绑定", nil
}
// 查询玩家数据
data, _ := FetchPlayerData(ssId)
if data == nil {
return "查询出错,服务器返回了空数据"
return "查询出错,服务器返回了空数据", errors.New("查询出错,服务器返回了空数据")
}
// 构建 PlayerDataLite 结构体
dataLite := PlayerDataLite{
ID: data.ID,
Name: data.Name,
@@ -183,31 +165,60 @@ func (ss *ssQuery) GetScore(qqId string) (reply string) {
TotalPlayCount: data.ScoreStats.TotalPlayCount,
RankedPlayCount: data.ScoreStats.RankedPlayCount,
ReplaysWatched: data.ScoreStats.ReplaysWatched,
GeneratedTime: time.Now(),
GeneratedTime: time.Now().Format("2006-01-02 15:04:05.999999999-07:00"),
}
// 查询最近的玩家数据
var lastDataLite PlayerDataLite
tx.QueryRow("SELECT * FROM ssData WHERE id = ? ORDER BY generated_time DESC LIMIT 1", dataLite.ID).Scan(&lastDataLite.ID, &lastDataLite.Name, &lastDataLite.Country, &lastDataLite.PP, &lastDataLite.Rank, &lastDataLite.CountryRank, &lastDataLite.TotalScore, &lastDataLite.TotalRankedScore, &lastDataLite.AverageRankedAccuracy, &lastDataLite.TotalPlayCount, &lastDataLite.RankedPlayCount, &lastDataLite.ReplaysWatched, &lastDataLite.GeneratedTime)
err = tx.Get(&lastDataLite, "SELECT * FROM ssData WHERE id = ? ORDER BY generated_time DESC LIMIT 1", dataLite.ID)
if err != nil && err != sql.ErrNoRows {
log.Print(err)
return "查询历史数据时出错", err
}
// 如果有新的数据,则插入
if lastDataLite.TotalPlayCount != dataLite.TotalPlayCount {
tx.Exec("INSERT INTO ssData(id, name, country, pp, rank, country_rank, total_score, total_ranked_score, average_ranked_accuracy, total_play_count, ranked_play_count, replays_watched, generated_time) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)", dataLite.ID, dataLite.Name, dataLite.Country, dataLite.PP, dataLite.Rank, dataLite.CountryRank, dataLite.TotalScore, dataLite.TotalRankedScore, dataLite.AverageRankedAccuracy, dataLite.TotalPlayCount, dataLite.RankedPlayCount, dataLite.ReplaysWatched, dataLite.GeneratedTime)
_, err = tx.NamedExec(`INSERT INTO ssData (id, name, country, pp, rank, country_rank, total_score,
total_ranked_score, average_ranked_accuracy, total_play_count, ranked_play_count, replays_watched, generated_time)
VALUES (:id, :name, :country, :pp, :rank, :country_rank, :total_score,
:total_ranked_score, :average_ranked_accuracy, :total_play_count, :ranked_play_count, :replays_watched, :generated_time)`, dataLite)
if err != nil {
log.Print(err)
return "插入新数据时出错", err
}
// 提交事务
err = tx.Commit()
if err != nil {
return "SQL事务提交失败请重试"
log.Print(err)
return "SQL事务提交失败请重试", err
}
return data.LastDiffToString(lastDataLite)
// 返回差异信息
return data.LastDiffToString(lastDataLite), nil
}
// 如果没有新数据,直接提交事务
err = tx.Commit()
if err != nil {
return "SQL事务提交失败请重试"
log.Print(err)
return "SQL事务提交失败请重试", err
}
return data.ToString()
// 返回当前数据的字符串表示
return data.ToString(), nil
}
func (ss *ssQuery) SaveRecord(cmdData CommandData) {
tx, err := ss.db.Begin()
db := sqlite3.GetDB() // 假设 sqlite3.GetDB() 返回 *sqlx.DB
tx, err := db.Beginx()
if err != nil {
log.Print(err)
return
}
defer tx.Rollback()
// 创建 RecordDataLite 结构体实例
dataLite := RecordDataLite{
ScoreID: cmdData.Score.ID,
SsID: cmdData.Score.LeaderboardPlayerInfo.ID,
@@ -226,6 +237,7 @@ func (ss *ssQuery) SaveRecord(cmdData CommandData) {
Multiplier: cmdData.Score.Multiplier,
BadCuts: cmdData.Score.BadCuts,
Score: cmdData.Score.ModifiedScore,
MaxScore: cmdData.Leaderboard.MaxScore,
MissedNotes: cmdData.Score.MissedNotes,
MaxCombo: cmdData.Score.MaxCombo,
FullCombo: cmdData.Score.FullCombo,
@@ -234,6 +246,8 @@ func (ss *ssQuery) SaveRecord(cmdData CommandData) {
DeviceControllerRight: "",
GeneratedTime: time.Now().Format("2006-01-02 15:04:05.999999999-07:00"),
}
// 检查设备信息并设置
if cmdData.Score.DeviceHmd != nil {
dataLite.DeviceHmd = *cmdData.Score.DeviceHmd
}
@@ -243,9 +257,25 @@ func (ss *ssQuery) SaveRecord(cmdData CommandData) {
if cmdData.Score.DeviceControllerRight != nil {
dataLite.DeviceControllerRight = *cmdData.Score.DeviceControllerRight
}
if _, err = tx.Exec("INSERT INTO ssRecordData(score_id, ss_id, name, country, song_name, song_sub_name, song_author_name, song_hash, cover_image, difficulty_raw, pp, stars, weight, modifiers, multiplier, bad_cuts, missed_notes, max_combo,score, full_combo, device_hmd, device_controller_left, device_controller_right, generated_time) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23,?24)", dataLite.ScoreID, dataLite.SsID, dataLite.Name, dataLite.Country, dataLite.SongName, dataLite.SongSubName, dataLite.SongAuthorName, dataLite.SongHash, dataLite.CoverImage, dataLite.DifficultyRaw, dataLite.PP, dataLite.Stars, dataLite.Weight, dataLite.Modifiers, dataLite.Multiplier, dataLite.BadCuts, dataLite.MissedNotes, dataLite.MaxCombo, dataLite.Score, dataLite.FullCombo, dataLite.DeviceHmd, dataLite.DeviceControllerLeft, dataLite.DeviceControllerRight, dataLite.GeneratedTime); err != nil {
// 使用 NamedExec 插入数据
_, err = tx.NamedExec(`INSERT INTO ssRecordData
(score_id, ss_id, name, country, song_name, song_sub_name, song_author_name,
song_hash, cover_image, difficulty_raw, pp, stars, weight, modifiers,
multiplier, bad_cuts, missed_notes, max_combo, score, max_score,
full_combo, device_hmd, device_controller_left, device_controller_right,
generated_time)
VALUES (:score_id, :ss_id, :name, :country, :song_name, :song_sub_name,
:song_author_name, :song_hash, :cover_image, :difficulty_raw, :pp, :stars,
:weight, :modifiers, :multiplier, :bad_cuts, :missed_notes, :max_combo,
:score, :max_score, :full_combo, :device_hmd, :device_controller_left,
:device_controller_right, :generated_time)`, dataLite)
if err != nil {
log.Print(err)
return
}
// 提交事务
err = tx.Commit()
if err != nil {
log.Print(err)
@@ -253,37 +283,42 @@ func (ss *ssQuery) SaveRecord(cmdData CommandData) {
}
func (ss *ssQuery) GetRecentScores(count int, qqId string) ([]RecordDataLite, error) {
tx, err := ss.db.Begin()
db := sqlite3.GetDB() // 假设 sqlite3.GetDB() 返回 *sqlx.DB
tx, err := db.Beginx()
if err != nil {
log.Print(err)
return nil, errors.New("数据库连接失败,请稍后重试")
}
defer tx.Rollback()
// 查询绑定的 ssId
var ssId string
tx.QueryRow("SELECT ssid FROM ssBind WHERE qqid = ?", qqId).Scan(&ssId)
if ssId == "" {
return nil, errors.New("未绑定ss账号输入\"绑定ss [ssId]\"绑定")
err = tx.Get(&ssId, "SELECT ssid FROM ssBind WHERE qqid = ?", qqId)
if err != nil {
if err == sql.ErrNoRows {
return nil, errors.New("未绑定ss账号输入\"绑定ss [ssId]\"绑定")
}
log.Println("查询 ssId 出错:", err)
return nil, errors.New("查询 ssId 失败")
}
rows, err := tx.Query("SELECT * FROM ssRecordData WHERE ss_id = ? ORDER BY generated_time DESC LIMIT ?", ssId, count)
// 查询记录
var records []RecordDataLite
err = tx.Select(&records, "SELECT * FROM ssRecordData WHERE ss_id = ? ORDER BY generated_time DESC LIMIT ?", ssId, count)
if err != nil {
if err == sql.ErrNoRows {
return nil, errors.New("未查询到数据")
}
log.Println("Query error:", err)
return nil, errors.New("")
}
defer rows.Close()
var records []RecordDataLite
for rows.Next() {
var record RecordDataLite
if err := rows.Scan(&record.ID, &record.ScoreID, &record.SsID, &record.Name, &record.Country, &record.SongName, &record.SongSubName, &record.SongAuthorName, &record.SongHash, &record.CoverImage, &record.DifficultyRaw, &record.PP, &record.Stars, &record.Weight, &record.Modifiers, &record.Multiplier, &record.BadCuts, &record.MissedNotes, &record.MaxCombo, &record.Score, &record.FullCombo, &record.DeviceHmd, &record.DeviceControllerLeft, &record.DeviceControllerRight, &record.GeneratedTime); err != nil {
log.Println("Scan error:", err)
return nil, err
}
records = append(records, record)
log.Println("查询数据出错:", err)
return nil, errors.New("查询记录失败")
}
// 提交事务
err = tx.Commit()
if err != nil {
log.Print(err)
return nil, errors.New("提交事务失败")
}
return records, nil
}

View File

@@ -0,0 +1,5 @@
package scoresaber
func (data *PlayerData) ToPicture() (outputImgPath string) {
return ""
}

View File

@@ -2,9 +2,9 @@ package scoresaber
import (
"log"
"sync"
"time"
"git.lxtend.com/qqbot/sqlite3"
"github.com/gorilla/websocket"
)
@@ -13,10 +13,8 @@ const wsURL = "wss://scoresaber.com/ws"
var ScoresManager = scoresManager{}
type scoresManager struct {
recentScores []Command
mu sync.Mutex
conn *websocket.Conn
retryTimes int
conn *websocket.Conn
retryTimes int
}
func init() {
@@ -33,7 +31,6 @@ func (sm *scoresManager) connect() error {
return err
}
sm.retryTimes = 0
sm.recentScores = make([]Command, 0)
go sm.receiveData()
return nil
}
@@ -58,25 +55,24 @@ func (sm *scoresManager) receiveData() {
continue
}
SSQuery.SaveRecord(cmd.CommandData)
if cmd.CommandData.Score.LeaderboardPlayerInfo.Country != "CN" {
continue
}
sm.mu.Lock()
if len(sm.recentScores) >= 50 {
sm.recentScores = sm.recentScores[1:]
}
sm.recentScores = append(sm.recentScores, cmd)
sm.mu.Unlock()
}
}
func (sm *scoresManager) GetRecentScores(count int) []Command {
sm.mu.Lock()
defer sm.mu.Unlock()
if count > len(sm.recentScores) {
count = len(sm.recentScores)
func (sm *scoresManager) GetRecentScores(count int, predict string) []RecordDataLite {
db := sqlite3.GetDB() // 假设 sqlite3.GetDB() 返回 *sqlx.DB
scoresCopy := make([]RecordDataLite, 0, count)
query := "SELECT * FROM ssRecordData"
if predict != "" {
query += " " + predict
}
scoresCopy := make([]Command, count)
copy(scoresCopy, sm.recentScores[len(sm.recentScores)-count:])
query += " ORDER BY generated_time DESC LIMIT ?"
err := db.Select(&scoresCopy, query, count)
if err != nil {
log.Print(err)
return nil
}
return scoresCopy
}

View File

@@ -129,6 +129,7 @@ type RecordDataLite struct {
MissedNotes int `json:"missedNotes" db:"missed_notes"`
MaxCombo int `json:"maxCombo" db:"max_combo"`
Score int `json:"score" db:"score"`
MaxScore int `json:"maxScore" db:"max_score"`
FullCombo bool `json:"fullCombo" db:"full_combo"`
DeviceHmd string `json:"deviceHmd" db:"device_hmd"`
DeviceControllerLeft string `json:"deviceControllerLeft" db:"device_controller_left"`
@@ -137,19 +138,19 @@ type RecordDataLite struct {
}
func (r RecordDataLite) ToString() string {
formatedStrRanked := "使用 %s 在 %s 的 %s 难度(%.1f星级)中获得了 %d 分pp 为 %.2f。"
formatedStrUnranked := "使用 %s 在 %s 的 %s 难度中获得了 %d 分。"
formatedStrWithoutDevice := "在 %s 的 %s 难度(%.1f星级)中获得了 %d 分pp 为 %.2f。"
formatedStrWithoutDeviceAndRank := "在 %s 的 %s 难度(%.1f星级)中获得了 %d 分。"
formatedStrRanked := "%s 使用 %s 在 %s 的 %s 难度(%.1f星级)中获得了 %d 分pp 为 %.2f,准度为 %s。"
formatedStrUnranked := "%s 使用 %s 在 %s 的 %s 难度中获得了 %d 分,准度为 %s。"
formatedStrWithoutDevice := "%s 在 %s 的 %s 难度(%.1f星级)中获得了 %d 分pp 为 %.2f,准度为 %s。"
formatedStrWithoutDeviceAndRank := "%s 在 %s 的 %s 难度(%.1f星级)中获得了 %d 分,准度为 %s。"
hardStr := strings.Split(r.DifficultyRaw, "_")[1]
if r.Stars == 0 && r.DeviceHmd != "" {
return fmt.Sprintf(formatedStrUnranked, r.DeviceHmd, r.SongName, hardStr, r.Score)
return fmt.Sprintf(formatedStrUnranked, r.Name, r.DeviceHmd, r.SongName, hardStr, r.Score, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100))
} else if r.Stars != 0 && r.DeviceHmd != "" {
return fmt.Sprintf(formatedStrRanked, r.DeviceHmd, r.SongName, hardStr, r.Stars, r.Score, r.PP)
return fmt.Sprintf(formatedStrRanked, r.Name, r.DeviceHmd, r.SongName, hardStr, r.Stars, r.Score, r.PP, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100))
} else if r.Stars != 0 && r.DeviceHmd == "" {
return fmt.Sprintf(formatedStrWithoutDevice, r.SongName, hardStr, r.Stars, r.Score, r.PP)
return fmt.Sprintf(formatedStrWithoutDevice, r.Name, r.SongName, hardStr, r.Stars, r.Score, r.PP, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100))
} else {
return fmt.Sprintf(formatedStrWithoutDeviceAndRank, r.SongName, hardStr, r.Stars, r.Score)
return fmt.Sprintf(formatedStrWithoutDeviceAndRank, r.Name, r.SongName, hardStr, r.Stars, r.Score, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100))
}
}
@@ -186,20 +187,20 @@ type PlayerData struct {
}
type PlayerDataLite struct {
ID string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Country string `json:"country" db:"country"`
Device string `json:"device" db:"device"`
PP float64 `json:"pp" db:"pp"`
Rank int `json:"rank" db:"rank"`
CountryRank int `json:"countryRank" db:"country_rank"`
TotalScore int `json:"totalScore" db:"total_score"`
TotalRankedScore int `json:"totalRankedScore" db:"total_ranked_score"`
AverageRankedAccuracy float64 `json:"averageRankedAccuracy" db:"average_ranked_accuracy"`
TotalPlayCount int `json:"totalPlayCount" db:"total_play_count"`
RankedPlayCount int `json:"rankedPlayCount" db:"ranked_play_count"`
ReplaysWatched int `json:"replaysWatched" db:"replays_watched"`
GeneratedTime time.Time `json:"generatedTime" db:"generated_time"`
ID string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Country string `json:"country" db:"country"`
Device string `json:"device" db:"device"`
PP float64 `json:"pp" db:"pp"`
Rank int `json:"rank" db:"rank"`
CountryRank int `json:"countryRank" db:"country_rank"`
TotalScore int `json:"totalScore" db:"total_score"`
TotalRankedScore int `json:"totalRankedScore" db:"total_ranked_score"`
AverageRankedAccuracy float64 `json:"averageRankedAccuracy" db:"average_ranked_accuracy"`
TotalPlayCount int `json:"totalPlayCount" db:"total_play_count"`
RankedPlayCount int `json:"rankedPlayCount" db:"ranked_play_count"`
ReplaysWatched int `json:"replaysWatched" db:"replays_watched"`
GeneratedTime string `json:"generatedTime" db:"generated_time"`
}
func (p PlayerData) ToString() string {