📝 开始重构数据库

This commit is contained in:
Art_Sakura 2025-04-22 19:42:09 +08:00
parent adefd3a412
commit 4cd0302426
2 changed files with 354 additions and 322 deletions

View File

@ -1,6 +1,7 @@
import math import math
import os import os
import re import re
from contextlib import asynccontextmanager
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from io import StringIO from io import StringIO
from math import e from math import e
@ -37,21 +38,34 @@ class CSqlManager:
return True return True
@classmethod @classmethod
async def getColumns(cls, tableName): @asynccontextmanager
""" 由AI生成 async def _transaction(cls):
获取表的列信息 await cls.m_pDB.execute("BEGIN;")
"""
try: try:
cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{tableName}")') yield
columns = [row[1] for row in await cursor.fetchall()] except:
return columns await cls.m_pDB.execute("ROLLBACK;")
except aiosqlite.Error as e:
logger.error(f"获取表结构失败: {str(e)}")
raise raise
else:
await cls.m_pDB.execute("COMMIT;")
@classmethod @classmethod
async def ensure_table_exists(cls, tableName, columns) -> bool: async def getTableInfo(cls, table_name: str) -> list:
"""智能创建并分析数据库表、字段是否存在 由AI生成 if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', table_name):
raise ValueError(f"Illegal table name: {table_name}")
try:
cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{table_name}")')
rows = await cursor.fetchall()
return [{"name": row[1], "type": row[2]} for row in rows]
except aiosqlite.Error:
return []
@classmethod
async def ensureTableSchema(cls, table_name: str, columns: dict) -> bool:
"""由AI生成
创建表或为已存在表添加缺失字段
返回 True 表示有变更创建或新增列False 则无操作
Args: Args:
tableName (_type_): 表名 tableName (_type_): 表名
@ -60,67 +74,40 @@ class CSqlManager:
Returns: Returns:
_type_: _description_ _type_: _description_
""" """
try:
current_columns = await cls.getColumns(tableName)
#检查表是否存在
table_exists = bool(current_columns)
#如果表不存在,直接创建
if not table_exists:
create_sql = f'''
CREATE TABLE "{tableName}" (
{", ".join(f'"{k}" {v}' for k, v in columns.items())}
);
'''
await cls.m_pDB.execute(create_sql)
await cls.m_pDB.commit() #显式提交新建表操作
return True
#表存在时的处理
columns_to_add = []
columns_to_remove = []
#检查需要添加的列
for k, v in columns.items():
if k not in current_columns:
columns_to_add.append(f'"{k}" {v}')
#检查需要移除的列
for col in current_columns:
if col not in columns.keys():
columns_to_remove.append(col)
#执行修改
if columns_to_add or columns_to_remove:
try:
#开启事务使用connection级别的事务控制
await cls.m_pDB.execute('BEGIN TRANSACTION')
#添加新列
for col_def in columns_to_add:
await cls.m_pDB.execute(f'ALTER TABLE "{tableName}" ADD COLUMN {col_def}')
#删除旧列
for col in columns_to_remove:
await cls.m_pDB.execute(f'ALTER TABLE "{tableName}" DROP COLUMN "{col}"')
#显式提交事务
await cls.m_pDB.commit()
return True
except Exception as e:
#回滚事务
await cls.m_pDB.rollback()
logger.error(f"表结构迁移失败: {str(e)}")
return False
except aiosqlite.Error as e:
logger.error(f"表结构迁移失败: {str(e)}")
info = await cls.getTableInfo(table_name)
existing = {col['name']: col['type'].upper() for col in info}
desired = {k: v.upper() for k, v in columns.items()}
if not existing:
cols_def = ", ".join(f'"{k}" {v}' for k, v in columns.items())
await cls.m_pDB.execute(f'CREATE TABLE "{table_name}" ({cols_def});')
return True
to_add = [k for k in desired if k not in existing]
to_remove = [k for k in existing if k not in desired]
type_mismatch = [k for k in desired if k in existing and existing[k] != desired[k]]
if to_add and not to_remove and not type_mismatch:
for col in to_add:
await cls.m_pDB.execute(
f'ALTER TABLE "{table_name}" ADD COLUMN "{col}" {columns[col]}'
)
return True
async with cls._transaction():
tmp_table = f"{table_name}_new"
cols_def = ", ".join(f'"{k}" {v}' for k, v in columns.items())
await cls.m_pDB.execute(f'CREATE TABLE "{tmp_table}" ({cols_def});')
common_cols = [k for k in desired if k in existing]
if common_cols:
cols_str = ", ".join(f'"{c}"' for c in common_cols)
await cls.m_pDB.execute(
f'INSERT INTO "{tmp_table}" ({cols_str}) SELECT {cols_str} FROM "{table_name}";'
)
await cls.m_pDB.execute(f'DROP TABLE "{table_name}";')
await cls.m_pDB.execute(f'ALTER TABLE "{tmp_table}" RENAME TO "{table_name}";')
return True return True
@classmethod @classmethod
async def checkDB(cls) -> bool: async def checkDB(cls) -> bool:
#1. 用户表
userInfo = { userInfo = {
"uid": "INTEGER PRIMARY KEY AUTOINCREMENT", "uid": "INTEGER PRIMARY KEY AUTOINCREMENT",
"name": "TEXT NOT NULL", "name": "TEXT NOT NULL",
@ -129,24 +116,32 @@ class CSqlManager:
"soil": "INTEGER DEFAULT 3", "soil": "INTEGER DEFAULT 3",
"stealing": "TEXT DEFAULT NULL" "stealing": "TEXT DEFAULT NULL"
} }
#2. 土地表
userStorehouse = {
"uid": "INTEGER PRIMARY KEY AUTOINCREMENT",
"item": "TEXT DEFAULT ''",
"plant": "TEXT DEFAULT ''",
"seed": "TEXT DEFAULT ''"
}
userSoilInfo = { userSoilInfo = {
"uid": "INTEGER PRIMARY KEY AUTOINCREMENT", "uid": "INTEGER PRIMARY KEY AUTOINCREMENT",
**{f"soil{i}": "TEXT DEFAULT ''" for i in range(1, 31)} **{f"soil{i}": "TEXT DEFAULT ''" for i in range(1, 31)}
} }
#3. 用户作物明细表
userPlant = {
"uid": "INTEGER NOT NULL",
"plant": "TEXT NOT NULL",
"count": "INTEGER NOT NULL DEFAULT 0",
#建联合主键保证每个品种一行
"PRIMARY KEY": "(uid, plant)"
}
#4. 用户种子明细表
userSeed = {
"uid": "INTEGER NOT NULL",
"seed": "TEXT NOT NULL",
"count": "INTEGER NOT NULL DEFAULT 0",
"PRIMARY KEY": "(uid, seed)"
}
await cls.ensure_table_exists("user", userInfo) #建表(或增列)
await cls.ensureTableSchema("user", userInfo)
await cls.ensure_table_exists("storehouse", userStorehouse) await cls.ensureTableSchema("soil", userSoilInfo)
await cls.ensureTableSchema("userPlant", userPlant)
await cls.ensure_table_exists("soil", userSoilInfo) await cls.ensureTableSchema("userSeed", userSeed)
return True return True
@ -445,205 +440,261 @@ class CSqlManager:
return await cls.executeDB(sql) return await cls.executeDB(sql)
@classmethod @classmethod
async def getUserSeedByUid(cls, uid: str) -> str: async def addUserSeedByUid(cls, uid: str, seed: str, count: int = 1) -> bool:
"""获取用户仓库种子信息 """根据用户uid添加种子信息
Args: Args:
info (list[dict]): 用户信息 uid (str): 用户uid
seed (str): 种子名称
count (int): 数量
Returns: Returns:
str: 仓库种子信息 bool: 是否添加成功
""" """
if len(uid) <= 0:
return ""
try: try:
async with cls.m_pDB.execute(f"SELECT seed FROM storehouse WHERE uid = {uid}") as cursor: async with cls._transaction():
async for row in cursor: #检查是否已存在该种子
return row[0] async with cls.m_pDB.execute(
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
) as cursor:
row = await cursor.fetchone()
return "" if row:
#如果种子已存在,则更新数量
newCount = row[0] + count
await cls.m_pDB.execute(
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
(newCount, uid, seed)
)
else:
#如果种子不存在,则插入新记录
newCount = count
await cls.m_pDB.execute(
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
(uid, seed, count)
)
#如果种子数量为 0删除记录
if newCount <= 0:
await cls.m_pDB.execute(
"DELETE FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
)
await cls.m_pDB.commit()
return True
except Exception as e: except Exception as e:
logger.warning(f"getUserSeedByUid查询失败: {e}") logger.warning(f"真寻农场addUserSeedByUid 失败: {e}")
return "" return False
@classmethod @classmethod
async def getUserSeedByName(cls, uid: str, name: str) -> int: async def getUserSeedByName(cls, uid: str, seed: str) -> Optional[int]:
"""获取用户仓库种子信息 """根据种子名称获取种子数量
Args: Args:
uid (str): 用户信息 uid (str): 用户uid
name (str): 种子名称
Returns:
int: 仓库种子信息
"""
if len(uid) <= 0:
return -1
try:
async with cls.m_pDB.execute(f"SELECT seed FROM storehouse WHERE uid = {uid}") as cursor:
async for row in cursor:
if row[0] == None or len(row[0]) == 0:
return -1
plantDict: Dict[str, int] = {}
for item in row[0].split(','):
if '|' in item:
seedName, count = item.split('|', 1)
plantDict[seedName] = int(count)
if name in plantDict:
return plantDict[name]
else:
return -1
return -1
except Exception as e:
logger.warning(f"getUserSeedByUid查询失败: {e}")
return -1
@classmethod
async def updateUserSeedByUid(cls, uid: str, seed: str) -> bool:
"""更新用户种子仓库
Args:
uid (str): 用户Uid
seed (str): 种子名称 seed (str): 种子名称
Returns: Returns:
bool: Optional[int]: 种子数量
""" """
if len(uid) <= 0:
return False
sql = f"UPDATE storehouse SET seed = '{seed}' WHERE uid = {uid}"
return await cls.executeDB(sql)
@classmethod
async def addUserSeedByPlant(cls, uid: str, seed: str, num: int) -> bool:
"""添加作物信息至仓库
Args:
uid (str): 用户Uid
seed (str): 种子名称
num(str): 种子数量
Returns:
bool:
"""
if len(uid) <= 0:
return False
seedsDict = {}
currentSeeds = await cls.getUserSeedByUid(uid)
if currentSeeds:
for item in currentSeeds.split(','):
if item.strip():
name, count = item.split('|')
seedsDict[name.strip()] = int(count.strip())
if seed in seedsDict:
seedsDict[seed] += num
if seedsDict[seed] <= 0:
del seedsDict[seed]
else:
if num > 0:
seedsDict[seed] = num
updatedSeeds = ','.join([f"{name}|{count}" for name, count in seedsDict.items()])
sql = f"UPDATE storehouse SET seed = '{updatedSeeds}' WHERE uid = {uid}"
return await cls.executeDB(sql)
@classmethod
async def getUserPlantByUid(cls, uid: str) -> str:
"""获取用户仓库作物信息
Args:
info (list[dict]): 用户信息
Returns:
str: 仓库作物信息
"""
if len(uid) <= 0:
return ""
try: try:
async with cls.m_pDB.execute(f"SELECT plant FROM storehouse WHERE uid = {uid}") as cursor: async with cls.m_pDB.execute(
async for row in cursor: "SELECT count FROM userSeed WHERE uid = ? AND seed = ?",
return row[0] (uid, seed)
) as cursor:
return "" row = await cursor.fetchone()
return row[0] if row else None
except Exception as e: except Exception as e:
logger.warning(f"getUserPlantByUid查询失败: {e}") logger.warning(f"真寻农场getUserSeedByName 查询失败: {e}")
return "" return None
@classmethod @classmethod
async def updateUserPlantByUid(cls, uid: str, plant: str) -> bool: async def getUserSeedByUid(cls, uid: str) -> dict:
"""更新用户作物仓库 """根据用户Uid获取仓库全部种子信息
Args: Args:
uid (str): 用户Uid uid (str): 用户uid
Returns:
dict: 种子信息
"""
cursor = await cls.m_pDB.execute(
"SELECT seed, count FROM userSeed WHERE uid=?",
(uid,)
)
rows = await cursor.fetchall()
return {row["seed"]: row["count"] for row in rows}
@classmethod
async def updateUserSeedByName(cls, uid: str, seed: str, count: int) -> bool:
"""根据种子名称更新种子数量
Args:
uid (str): 用户uid
seed (str): 种子名称
count (int): 种子数量
Returns:
bool: 是否成功
"""
try:
async with cls._transaction():
await cls.m_pDB.execute(
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
(count, uid, seed)
)
await cls.m_pDB.commit()
return True
except Exception as e:
logger.warning(f"真寻农场updateUserSeedByName 更新失败: {e}")
return False
@classmethod
async def deleteUserSeedByName(cls, uid: str, seed: str) -> bool:
"""根据种子名称从种子仓库中删除种子
Args:
uid (str): 用户uid
seed (str): 种子名称
Returns:
bool: 是否成功
"""
try:
async with cls._transaction():
await cls.m_pDB.execute(
"DELETE FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
)
await cls.m_pDB.commit()
return True
except Exception as e:
logger.warning(f"真寻农场deleteUserSeedByName 删除失败: {e}")
return False
@classmethod
async def addUserPlantByUid(cls, uid: str, plant: str, count: int = 1) -> bool:
"""根据用户uid添加作物信息
Args:
uid (str): 用户uid
plant (str): 作物名称
count (int): 数量
Returns:
bool: 是否添加成功
"""
try:
async with cls._transaction():
#检查是否已存在该作物
async with cls.m_pDB.execute(
"SELECT count FROM userPlant WHERE uid = ? AND plant = ?",
(uid, plant)
) as cursor:
row = await cursor.fetchone()
if row:
#如果作物已存在,则更新数量
new_count = row[0] + count
await cls.m_pDB.execute(
"UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?",
(new_count, uid, plant)
)
else:
#如果作物不存在,则插入新记录
await cls.m_pDB.execute(
"INSERT INTO userPlant (uid, plant, count) VALUES (?, ?, ?)",
(uid, plant, count)
)
await cls.m_pDB.commit()
return True
except Exception as e:
logger.warning(f"真寻农场addUserPlantByUid 失败: {e}")
return False
@classmethod
async def getUserPlantByName(cls, uid: str, plant: str) -> Optional[int]:
"""根据作物名称获取用户的作物数量
Args:
uid (str): 用户uid
plant (str): 作物名称 plant (str): 作物名称
Returns: Returns:
bool: Optional[int]: 作物数量
""" """
try:
if len(uid) <= 0: async with cls.m_pDB.execute(
return False "SELECT count FROM userPlant WHERE uid = ? AND plant = ?",
(uid, plant)
sql = f"UPDATE storehouse SET plant = '{plant}' WHERE uid = {uid}" ) as cursor:
row = await cursor.fetchone()
return await cls.executeDB(sql) return row[0] if row else None
except Exception as e:
logger.warning(f"真寻农场getUserPlantByName 查询失败: {e}")
return None
@classmethod @classmethod
async def addUserPlantByPlant(cls, uid: str, plant: str, num: int) -> bool: async def updateUserPlantByUid(cls, uid: str, plant: str, count: int) -> bool:
"""添加作物信息至仓库 """更新 userPlant 表中某个作物的数量
Args: Args:
uid (str): 用户Uid uid (str): 用户uid
plant (str): 作物名称 plant (str): 作物名称
num(str): 作物数量 count (int): 新的作物数量
Returns: Returns:
bool: bool: 是否更新成功
""" """
try:
async with cls._transaction():
#更新作物数量
await cls.m_pDB.execute(
"UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?",
(count, uid, plant)
)
if len(uid) <= 0: #如果作物数量为 0删除记录
if count <= 0:
await cls.m_pDB.execute(
"DELETE FROM userPlant WHERE uid = ? AND plant = ?",
(uid, plant)
)
await cls.m_pDB.commit()
return True
except Exception as e:
logger.warning(f"真寻农场updateUserPlantByUid 更新失败: {e}")
return False return False
plantsDict = {} @classmethod
currentPlants = await cls.getUserPlantByUid(uid) async def deleteUserPlantByUid(cls, uid: str, plant: str) -> bool:
"""从 userPlant 表中删除某个作物记录
if currentPlants: Args:
for item in currentPlants.split(','): uid (str): 用户uid
if item.strip(): plant (str): 作物名称
name, count = item.split('|')
plantsDict[name.strip()] = int(count.strip())
if plant in plantsDict: Returns:
plantsDict[plant] += num bool: 是否删除成功
if plantsDict[plant] <= 0: """
del plantsDict[plant] try:
else: async with cls._transaction():
if num > 0: await cls.m_pDB.execute(
plantsDict[plant] = num "DELETE FROM userPlant WHERE uid = ? AND plant = ?",
(uid, plant)
updatedPlants = ','.join([f"{name}|{count}" for name, count in plantsDict.items()]) )
await cls.m_pDB.commit()
sql = f"UPDATE storehouse SET plant = '{updatedPlants}' WHERE uid = {uid}" return True
except Exception as e:
return await cls.executeDB(sql) logger.warning(f"真寻农场deleteUserPlantByUid 删除失败: {e}")
return False
g_pSqlManager = CSqlManager() g_pSqlManager = CSqlManager()

View File

@ -224,17 +224,9 @@ class CFarmManager:
@classmethod @classmethod
async def getUserSeedByUid(cls, uid: str) -> bytes: async def getUserSeedByUid(cls, uid: str) -> bytes:
"""获取用户种子仓库 """获取用户种子仓库"""
dataList = []
Args: columnNames = [
uid (str): 用户Uid
Returns:
bytes: 返回图片
"""
data_list = []
column_name = [
"-", "-",
"种子名称", "种子名称",
"数量", "数量",
@ -246,59 +238,45 @@ class CFarmManager:
"是否可以上架交易行" "是否可以上架交易行"
] ]
seed = await g_pSqlManager.getUserSeedByUid(uid) # 从数据库获取结构化数据
seedRecords = await g_pSqlManager.getUserSeedByUid(uid) or {}
if seed == None: if not seedRecords:
result = await ImageTemplate.table_page( result = await ImageTemplate.table_page(
"种子仓库", "种子仓库",
"播种示例:@小真寻 播种 大白菜 [数量]", "播种示例:@小真寻 播种 大白菜 [数量]",
column_name, columnNames,
data_list, dataList,
) )
return result.pic2bytes() return result.pic2bytes()
sell = "" for seedName, count in seedRecords.items():
for item in seed.split(','): try:
if '|' in item: plantInfo = g_pJsonManager.m_pPlant['plant'][seedName]
seedName, count = item.split('|', 1) #分割一次,避免多竖线问题 iconPath = g_sResourcePath / f"plant/{seedName}/icon.png"
try: icon = (iconPath, 33, 33) if iconPath.exists() else ""
plantInfo = g_pJsonManager.m_pPlant['plant'][seedName] sellable = "可以" if plantInfo['again'] else "不可以"
icon = "" dataList.append([
icon_path = g_sResourcePath / f"plant/{seedName}/icon.png" icon,
if icon_path.exists(): seedName,
icon = (icon_path, 33, 33) count,
plantInfo['experience'],
if plantInfo['again'] == True: plantInfo['harvest'],
sell = "可以" plantInfo['time'],
else: plantInfo['crop'],
sell = "不可以" plantInfo['again'],
sellable
data_list.append( ])
[ except KeyError:
icon, continue
seedName,
count,
plantInfo['experience'],
plantInfo['harvest'],
plantInfo['time'],
plantInfo['crop'],
plantInfo['again'],
sell
]
)
except Exception as e:
continue
result = await ImageTemplate.table_page( result = await ImageTemplate.table_page(
"种子仓库", "种子仓库",
"播种示例:@小真寻 播种 大白菜 [数量]", "播种示例:@小真寻 播种 大白菜 [数量]",
column_name, columnNames,
data_list, dataList,
) )
return result.pic2bytes() return result.pic2bytes()
@classmethod @classmethod
@ -311,55 +289,58 @@ class CFarmManager:
num (int, optional): 播种数量 num (int, optional): 播种数量
Returns: Returns:
str: str: 返回结果
""" """
try:
# 获取用户的种子数量
count = await g_pSqlManager.getUserSeedByName(uid, name)
if count is None:
count = 0 # 如果返回 None则视为没有种子
number = 0 if count <= 0:
count = await g_pSqlManager.getUserSeedByName(uid, name) return f"没有在你的仓库发现{name}种子,快去买点吧!"
if count <= 0: # 如果播种数量超过仓库种子数量
return f"没有在你的仓库发现{name}种子,快去买点吧!" if count < num and num != -1:
return f"仓库中的{name}种子数量不足,当前剩余{count}个种子"
#如果播种超过仓库种子 # 获取用户土地数量
isMax = False soilNumber = await g_pSqlManager.getUserSoilByUid(uid)
if count < num:
isMax = True
#如果播种全部 # 如果播种数量为 -1表示播种所有可播种的土地
isAll = False if num == -1:
if num == -1: num = count
isAll = True
soilNumber = await g_pSqlManager.getUserSoilByUid(uid) # 记录是否成功播种
successCount = 0
for i in range(1, soilNumber + 1): for i in range(1, soilNumber + 1):
if count > 0: if count > 0 and num > 0:
if isAll or num > 0:
soilName = f"soil{i}" soilName = f"soil{i}"
success, message = await g_pSqlManager.getUserSoilStatusBySoilID(uid, soilName) success, message = await g_pSqlManager.getUserSoilStatusBySoilID(uid, soilName)
if success: if success:
#更新种子数量 # 更新种子数量
num -= 1 num -= 1
count -= 1 count -= 1
#记录种子消耗数量 # 记录种子消耗数量
number -= 1 successCount += 1
#更新数据库 # 更新数据库
await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soilName, name) await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soilName, name)
str = "" # 确保用户仓库数量更新
if successCount > 0:
await g_pSqlManager.updateUserSeedByName(uid, name, count)
if isMax: # 根据播种结果给出反馈
str = f"仓库数量不够那么多,已将剩余数量全部播种!" if num == 0:
elif num > 0: return f"播种{name}成功!仓库剩余{count}个种子"
str = f"播种数量超出开垦土地数量,已将可播种土地成功播种{name}!仓库还剩下{count}个种子" else:
else: return f"播种数量超出开垦土地数量,已将可播种土地成功播种{name}!仓库剩余{count}个种子"
str = f"播种{name}成功!仓库还剩下{count}个种子"
await g_pSqlManager.addUserSeedByPlant(uid, name, number) except Exception as e:
logger.warning(f"播种操作失败: {e}")
return str return "播种失败,请稍后重试!"
@classmethod @classmethod
async def harvest(cls, uid: str) -> str: async def harvest(cls, uid: str) -> str: