Archived
1

DataCache and changes for game update

Recent game update changed pokememes view in pokedeks, so we need to
reflect it by updating parser.

Introducing DataCache - a silver bullet for eliminating lags linked to
database queries. Less queries, more in RAM, faster work. Needs testing
in production environment.
This commit is contained in:
2018-01-29 23:50:25 +04:00
parent 074fc4a1e3
commit b8226d8aa8
36 changed files with 1294 additions and 700 deletions

View File

@@ -4,62 +4,10 @@
package users
import (
"github.com/go-telegram-bot-api/telegram-bot-api"
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
"time"
"github.com/go-telegram-bot-api/telegram-bot-api"
)
// GetProfile returns last saved profile of player
func (u *Users) GetProfile(playerID int) (dbmapping.Profile, bool) {
profileRaw := dbmapping.Profile{}
err := c.Db.Get(&profileRaw, c.Db.Rebind("SELECT * FROM profiles WHERE player_id=? ORDER BY created_at DESC LIMIT 1"), playerID)
if err != nil {
c.Log.Error(err)
return profileRaw, false
}
return profileRaw, true
}
// GetPlayerByID returns dbmapping.Player instance with given ID.
func (u *Users) GetPlayerByID(playerID int) (dbmapping.Player, bool) {
playerRaw := dbmapping.Player{}
err := c.Db.Get(&playerRaw, c.Db.Rebind("SELECT * FROM players WHERE id=?"), playerID)
if err != nil {
c.Log.Error(err.Error())
return playerRaw, false
}
return playerRaw, true
}
// GetOrCreatePlayer seeks for player in database via Telegram ID.
// In case, when there is no player with such ID, new player will be created.
func (u *Users) GetOrCreatePlayer(telegramID int) (dbmapping.Player, bool) {
playerRaw := dbmapping.Player{}
err := c.Db.Get(&playerRaw, c.Db.Rebind("SELECT * FROM players WHERE telegram_id=? ORDER BY created_at desc LIMIT 1"), telegramID)
if err != nil {
c.Log.Error("Message user not found in database.")
c.Log.Error(err.Error())
// Create "nobody" user
playerRaw.TelegramID = telegramID
playerRaw.LeagueID = 0
playerRaw.Status = "nobody"
playerRaw.CreatedAt = time.Now().UTC()
playerRaw.UpdatedAt = time.Now().UTC()
_, err = c.Db.NamedExec("INSERT INTO players VALUES(NULL, :telegram_id, :league_id, :status, :created_at, :updated_at)", &playerRaw)
if err != nil {
c.Log.Error(err.Error())
return playerRaw, false
}
} else {
c.Log.Debug("Message user found in database.")
}
return playerRaw, true
}
// GetPrettyName returns "pretty" name of user (first_name + last name or username)
func (u *Users) GetPrettyName(user *tgbotapi.User) string {
userName := user.FirstName

View File

@@ -15,15 +15,14 @@ import (
// Internal functions
func (u *Users) fillProfilePokememe(profileID int, meme string, attack string, rarity string) {
spkRaw := dbmapping.Pokememe{}
err := c.Db.Get(&spkRaw, c.Db.Rebind("SELECT * FROM pokememes WHERE name='"+meme+"';"))
spkRaw, err := c.DataCache.GetPokememeByName(meme)
if err != nil {
c.Log.Error(err.Error())
} else {
attackInt := c.Statistics.GetPoints(attack)
ppk := dbmapping.ProfilePokememe{}
ppk.ProfileID = profileID
ppk.PokememeID = spkRaw.ID
ppk.PokememeID = spkRaw.Pokememe.ID
ppk.PokememeAttack = attackInt
ppk.PokememeRarity = rarity
ppk.CreatedAt = time.Now().UTC()
@@ -75,12 +74,13 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe
currentString := string(profileRunesArray[i])
currentRunes := profileRunesArray[i]
if strings.HasPrefix(currentString, "🈸") || strings.HasPrefix(currentString, "🈳 ") || strings.HasPrefix(currentString, "🈵") {
err1 := c.Db.Get(&league, c.Db.Rebind("SELECT * FROM leagues WHERE symbol='"+string(currentRunes[0])+"'"))
if err1 != nil {
c.Log.Error(err1.Error())
leagueRaw, err := c.DataCache.GetLeagueBySymbol(string(currentRunes[0]))
if err != nil {
c.Log.Error(err.Error())
u.profileAddFailureMessage(update)
return "fail"
}
league = *leagueRaw
for j := range currentRunes {
if j > 1 {
nickname += string(currentRunes[j])
@@ -209,10 +209,9 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe
}
// Information is gathered, let's create profile in database!
weaponRaw := dbmapping.Weapon{}
err2 := c.Db.Get(&weaponRaw, c.Db.Rebind("SELECT * FROM weapons WHERE name='"+weapon+"'"))
if err2 != nil {
c.Log.Error(err2.Error())
weaponRaw, err := c.DataCache.GetWeaponTypeByName(weapon)
if err != nil {
c.Log.Error(err.Error())
}
if playerRaw.LeagueID == 0 {
@@ -221,26 +220,17 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe
if playerRaw.Status == "nobody" {
playerRaw.Status = "common"
}
_, err4 := c.Db.NamedExec("UPDATE `players` SET league_id=:league_id, status=:status WHERE id=:id", &playerRaw)
if err4 != nil {
c.Log.Error(err4.Error())
_, err = c.DataCache.UpdatePlayerFields(playerRaw)
if err != nil {
u.profileAddFailureMessage(update)
return "fail"
}
} else if playerRaw.LeagueID != league.ID {
// Duplicate profile: user changed league, beware!
// User changed league, beware!
playerRaw.LeagueID = league.ID
playerRaw.Status = "league_changed"
playerRaw.CreatedAt = time.Now().UTC()
_, err5 := c.Db.NamedExec("INSERT INTO players VALUES(NULL, :telegram_id, :league_id, :status, :created_at, :updated_at)", &playerRaw)
if err5 != nil {
c.Log.Error(err5.Error())
u.profileAddFailureMessage(update)
return "fail"
}
err6 := c.Db.Get(&playerRaw, c.Db.Rebind("SELECT * FROM players WHERE telegram_id='"+strconv.Itoa(playerRaw.TelegramID)+"' AND league_id='"+strconv.Itoa(league.ID)+"';"))
if err6 != nil {
c.Log.Error(err6.Error())
_, err = c.DataCache.UpdatePlayerFields(playerRaw)
if err != nil {
u.profileAddFailureMessage(update)
return "fail"
}
@@ -261,25 +251,22 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe
profileRaw.Crystalls = crystallsInt
profileRaw.CreatedAt = time.Now().UTC()
_, err3 := c.Db.NamedExec("INSERT INTO `profiles` VALUES(NULL, :player_id, :nickname, :telegram_nickname, :level_id, :pokeballs, :wealth, :pokememes_wealth, :exp, :egg_exp, :power, :weapon_id, :crystalls, :created_at)", &profileRaw)
if err3 != nil {
c.Log.Error(err3.Error())
newProfileID, err := c.DataCache.AddProfile(&profileRaw)
if err != nil {
c.Log.Error(err.Error())
u.profileAddFailureMessage(update)
return "fail"
}
err8 := c.Db.Get(&profileRaw, c.Db.Rebind("SELECT * FROM profiles WHERE player_id=? AND created_at=?"), profileRaw.PlayerID, profileRaw.CreatedAt)
if err8 != nil {
c.Log.Error(err8.Error())
c.Log.Error("Profile isn't added!")
_, err = c.DataCache.GetProfileByID(newProfileID)
if err != nil {
c.Log.Error(err.Error())
u.profileAddFailureMessage(update)
return "fail"
}
playerRaw.UpdatedAt = time.Now().UTC()
_, err7 := c.Db.NamedExec("UPDATE `players` SET updated_at=:updated_at WHERE id=:id", &playerRaw)
if err7 != nil {
c.Log.Error(err7.Error())
err = c.DataCache.UpdatePlayerTimestamp(playerRaw.ID)
if err != nil {
u.profileAddFailureMessage(update)
return "fail"
}
@@ -304,11 +291,11 @@ func (u *Users) ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Playe
rarity = "super liber"
meme = strings.Replace(meme, "🔷", "", 1)
}
if strings.HasPrefix(meme, "❄") {
if strings.HasPrefix(meme, "❄") {
rarity = "new year"
meme = strings.Replace(meme, "❄", "", 1)
meme = strings.Replace(meme, "❄", "", 1)
}
u.fillProfilePokememe(profileRaw.ID, meme, attack, rarity)
u.fillProfilePokememe(newProfileID, meme, attack, rarity)
}
u.profileAddSuccessMessage(update, league.ID, profileRaw.LevelID)

View File

@@ -29,12 +29,9 @@ func (u *Users) FindByLevel(update *tgbotapi.Update) string {
return "fail"
}
usersArray, ok := u.findUsersByLevel(levelID)
if !ok {
return "fail"
}
users := u.findUsersByLevel(levelID)
u.foundUsersMessage(update, usersArray)
u.foundUsersMessage(update, users)
return "ok"
}
@@ -47,12 +44,9 @@ func (u *Users) FindByName(update *tgbotapi.Update) string {
return "fail"
}
usersArray, ok := u.findUserByName(commandArgs)
if !ok {
return "fail"
}
users := u.findUserByName(commandArgs)
u.foundUsersMessage(update, usersArray)
u.foundUsersMessage(update, users)
return "ok"
}
@@ -66,20 +60,22 @@ func (u *Users) ForeignProfileMessage(update *tgbotapi.Update) string {
return "fail"
}
playerRaw, ok := u.GetPlayerByID(userID)
if !ok {
playerRaw, err := c.DataCache.GetPlayerByID(userID)
if err != nil {
c.Log.Error(err.Error())
return "fail"
}
_, ok = u.GetProfile(playerRaw.ID)
if !ok {
_, err = c.DataCache.GetProfileByPlayerID(playerRaw.ID)
if err != nil {
c.Log.Error(err.Error())
return c.Talkers.BotError(update)
}
return u.ProfileMessage(update, &playerRaw)
return u.ProfileMessage(update, playerRaw)
}
// ProfileAddEffectsMesage shows when user tries to post profile with effects enabled
// ProfileAddEffectsMessage shows when user tries to post profile with effects enabled
func (u *Users) ProfileAddEffectsMessage(update *tgbotapi.Update) string {
message := "*Наркоман, штоле?*\n\n"
message += "Бот не принимает профили во время активированных эффектов. Закончи свои дела и принеси чистый профиль через полчаса."
@@ -94,12 +90,13 @@ func (u *Users) ProfileAddEffectsMessage(update *tgbotapi.Update) string {
// ProfileMessage shows current player's profile
func (u *Users) ProfileMessage(update *tgbotapi.Update, playerRaw *dbmapping.Player) string {
profileRaw, ok := u.GetProfile(playerRaw.ID)
if !ok {
profileRaw, err := c.DataCache.GetProfileByPlayerID(playerRaw.ID)
if err != nil {
c.Log.Error(err.Error())
return c.Talkers.AnyMessageUnauthorized(update)
}
league := dbmapping.League{}
err := c.Db.Get(&league, c.Db.Rebind("SELECT * FROM leagues WHERE id=?"), playerRaw.LeagueID)
err = c.Db.Get(&league, c.Db.Rebind("SELECT * FROM leagues WHERE id=?"), playerRaw.LeagueID)
if err != nil {
c.Log.Error(err)
}
@@ -210,11 +207,8 @@ func (u *Users) UsersList(update *tgbotapi.Update) string {
if page == 0 {
page = 1
}
usersArray, ok := u.getUsersWithProfiles()
if !ok {
return c.Talkers.BotError(update)
}
users := c.DataCache.GetPlayersWithCurrentProfiles()
u.usersList(update, page, usersArray)
u.usersList(update, page, users)
return "ok"
}

View File

@@ -6,84 +6,70 @@ package users
import (
"git.wtfteam.pro/fat0troll/i2_bot/lib/dbmapping"
"github.com/go-telegram-bot-api/telegram-bot-api"
"sort"
"strconv"
"strings"
"time"
)
// Internal functions for Users package
func (u *Users) getUsersWithProfiles() ([]dbmapping.PlayerProfile, bool) {
usersArray := []dbmapping.PlayerProfile{}
players := []dbmapping.Player{}
err := c.Db.Select(&players, "SELECT * FROM players")
if err != nil {
c.Log.Error(err)
return usersArray, false
}
func (u *Users) findUsersByLevel(levelID int) map[int]*dbmapping.PlayerProfile {
selectedUsers := make(map[int]*dbmapping.PlayerProfile)
allUsers := c.DataCache.GetPlayersWithCurrentProfiles()
for i := range players {
playerWithProfile := dbmapping.PlayerProfile{}
profile, ok := u.GetProfile(players[i].ID)
if !ok {
playerWithProfile.HaveProfile = false
} else {
playerWithProfile.HaveProfile = true
}
playerWithProfile.Profile = profile
playerWithProfile.Player = players[i]
league := dbmapping.League{}
if players[i].LeagueID != 0 {
err = c.Db.Get(&league, c.Db.Rebind("SELECT * FROM leagues WHERE id=?"), players[i].LeagueID)
if err != nil {
c.Log.Error(err.Error())
return usersArray, false
for i := range allUsers {
if allUsers[i].Profile.LevelID == levelID {
if allUsers[i].Player.UpdatedAt.After(time.Now().UTC().Add(-72 * time.Hour)) {
selectedUsers[i] = allUsers[i]
}
}
playerWithProfile.League = league
usersArray = append(usersArray, playerWithProfile)
}
return usersArray, true
return selectedUsers
}
func (u *Users) findUsersByLevel(levelID int) ([]dbmapping.ProfileWithAddons, bool) {
selectedUsers := []dbmapping.ProfileWithAddons{}
func (u *Users) findUserByName(pattern string) map[int]*dbmapping.PlayerProfile {
selectedUsers := make(map[int]*dbmapping.PlayerProfile)
allUsers := c.DataCache.GetPlayersWithCurrentProfiles()
err := c.Db.Select(&selectedUsers, c.Db.Rebind("SELECT p.*, l.symbol AS league_symbol, l.id AS league_id, pl.telegram_id FROM players pl, profiles p, leagues l WHERE pl.id = p.player_id AND l.id = pl.league_id AND p.created_at > NOW() - INTERVAL 72 HOUR AND p.level_id = ? GROUP BY player_id"), levelID)
if err != nil {
c.Log.Error(err.Error())
return selectedUsers, false
for i := range allUsers {
matchedPattern := false
if strings.Contains(strings.ToLower(allUsers[i].Profile.Nickname), strings.ToLower(pattern)) {
matchedPattern = true
}
if strings.Contains(strings.ToLower(allUsers[i].Profile.TelegramNickname), strings.ToLower(pattern)) {
matchedPattern = true
}
if matchedPattern {
selectedUsers[i] = allUsers[i]
}
}
return selectedUsers, true
return selectedUsers
}
func (u *Users) findUserByName(pattern string) ([]dbmapping.ProfileWithAddons, bool) {
selectedUsers := []dbmapping.ProfileWithAddons{}
err := c.Db.Select(&selectedUsers, c.Db.Rebind("SELECT * FROM (SELECT p.*, l.symbol AS league_symbol, l.id AS league_id, pl.telegram_id FROM players pl, profiles p, leagues l WHERE p.player_id = pl.id AND l.id = pl.league_id AND (p.nickname LIKE ? OR p.telegram_nickname LIKE ?) ORDER BY p.id DESC LIMIT 100000) AS find_users_table GROUP BY player_id"), "%"+pattern+"%", "%"+pattern+"%")
if err != nil {
c.Log.Error(err.Error())
return selectedUsers, false
func (u *Users) foundUsersMessage(update *tgbotapi.Update, users map[int]*dbmapping.PlayerProfile) {
var keys []int
for i := range users {
keys = append(keys, i)
}
sort.Ints(keys)
return selectedUsers, true
}
func (u *Users) foundUsersMessage(update *tgbotapi.Update, usersArray []dbmapping.ProfileWithAddons) {
message := "*Найденные игроки:*\n"
for i := range usersArray {
message += "#" + strconv.Itoa(usersArray[i].PlayerID)
message += " " + usersArray[i].LeagueSymbol
message += " " + usersArray[i].Nickname
if usersArray[i].TelegramNickname != "" {
message += " (@" + u.FormatUsername(usersArray[i].TelegramNickname) + ")"
for _, i := range keys {
message += "#" + strconv.Itoa(users[i].Player.ID)
if users[i].HaveProfile {
message += " " + users[i].League.Symbol
message += " " + users[i].Profile.Nickname
if users[i].Profile.TelegramNickname != "" {
message += " (@" + u.FormatUsername(users[i].Profile.TelegramNickname) + ")"
}
}
message += " /profile" + strconv.Itoa(usersArray[i].PlayerID) + "\n"
message += "Telegram ID: " + strconv.Itoa(usersArray[i].TelegramID) + "\n"
message += "Последнее обновление: " + usersArray[i].CreatedAt.Format("02.01.2006 15:04:05") + "\n"
message += " /profile" + strconv.Itoa(users[i].Player.ID) + "\n"
message += "Telegram ID: " + strconv.Itoa(users[i].Player.TelegramID) + "\n"
message += "Последнее обновление: " + users[i].Player.CreatedAt.Format("02.01.2006 15:04:05") + "\n"
if len(message) > 2000 {
msg := tgbotapi.NewMessage(update.Message.Chat.ID, message)
@@ -130,12 +116,12 @@ func (u *Users) profileAddFailureMessage(update *tgbotapi.Update) {
c.Bot.Send(msg)
}
func (u *Users) usersList(update *tgbotapi.Update, page int, usersArray []dbmapping.PlayerProfile) {
func (u *Users) usersList(update *tgbotapi.Update, page int, users map[int]*dbmapping.PlayerProfile) {
message := "*Зарегистрированные пользователи бота*\n"
message += "Список отсортирован по ID регистрации.\n"
message += "Количество зарегистрированных пользователей: " + strconv.Itoa(len(usersArray)) + "\n"
message += "Количество зарегистрированных пользователей: " + strconv.Itoa(len(users)) + "\n"
message += "Отображаем пользователей с " + strconv.Itoa(((page-1)*25)+1) + " по " + strconv.Itoa(page*25) + "\n"
if len(usersArray) > page*25 {
if len(users) > page*25 {
message += "Переход на следующую страницу: /users" + strconv.Itoa(page+1)
}
if page > 1 {
@@ -143,30 +129,36 @@ func (u *Users) usersList(update *tgbotapi.Update, page int, usersArray []dbmapp
}
message += "\n\n"
for i := range usersArray {
var keys []int
for i := range users {
keys = append(keys, i)
}
sort.Ints(keys)
for _, i := range keys {
if (i+1 > 25*(page-1)) && (i+1 < (25*page)+1) {
message += "#" + strconv.Itoa(usersArray[i].Player.ID)
if usersArray[i].HaveProfile {
message += " " + usersArray[i].League.Symbol
message += " " + usersArray[i].Profile.Nickname
if usersArray[i].Profile.TelegramNickname != "" {
message += " (@" + u.FormatUsername(usersArray[i].Profile.TelegramNickname) + ")"
message += "#" + strconv.Itoa(users[i].Player.ID)
if users[i].HaveProfile {
message += " " + users[i].League.Symbol
message += " " + users[i].Profile.Nickname
if users[i].Profile.TelegramNickname != "" {
message += " (@" + u.FormatUsername(users[i].Profile.TelegramNickname) + ")"
}
message += " /profile" + strconv.Itoa(usersArray[i].Player.ID) + "\n"
message += "Telegram ID: " + strconv.Itoa(usersArray[i].Player.TelegramID) + "\n"
message += "Последнее обновление: " + usersArray[i].Profile.CreatedAt.Format("02.01.2006 15:04:05") + "\n"
message += " /profile" + strconv.Itoa(users[i].Player.ID) + "\n"
message += "Telegram ID: " + strconv.Itoa(users[i].Player.TelegramID) + "\n"
message += "Последнее обновление: " + users[i].Profile.CreatedAt.Format("02.01.2006 15:04:05") + "\n"
} else {
if usersArray[i].Player.Status == "special" {
if users[i].Player.Status == "special" {
message += " _суперюзер_\n"
} else {
message += " _без профиля_\n"
}
message += "Telegram ID: " + strconv.Itoa(usersArray[i].Player.TelegramID) + "\n"
message += "Telegram ID: " + strconv.Itoa(users[i].Player.TelegramID) + "\n"
}
}
}
if len(usersArray) > page*25 {
if len(users) > page*25 {
message += "\n"
message += "Переход на следующую страницу: /users" + strconv.Itoa(page+1)
}

View File

@@ -15,9 +15,6 @@ type UsersInterface interface {
ParseProfile(update *tgbotapi.Update, playerRaw *dbmapping.Player) string
GetPrettyName(user *tgbotapi.User) string
GetProfile(playerID int) (dbmapping.Profile, bool)
GetOrCreatePlayer(telegramID int) (dbmapping.Player, bool)
GetPlayerByID(playerID int) (dbmapping.Player, bool)
PlayerBetterThan(playerRaw *dbmapping.Player, powerLevel string) bool
FindByLevel(update *tgbotapi.Update) string