From 6b99ec427cf6f5a4551392f399b6ca1eacdb484c Mon Sep 17 00:00:00 2001 From: Art_Sakura <1754798088@qq.com> Date: Mon, 28 Apr 2025 19:27:16 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20=E7=BB=A7=E7=BB=AD=E5=AF=B9?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=BF=9B=E8=A1=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database.py | 909 ------------------------------------------ database/database.py | 133 ++++++ database/user.py | 400 +++++++++++++++++++ database/userItem.py | 168 ++++++++ database/userPlant.py | 147 +++++++ database/userSeed.py | 157 ++++++++ database/userSoil.py | 261 ++++++++++++ database/userSteal.py | 228 +++++++++++ dbService.py | 29 ++ event/event.py | 103 +++++ farm/farm.py | 380 ++++++++---------- farm/shop.py | 20 +- 12 files changed, 1813 insertions(+), 1122 deletions(-) delete mode 100644 database.py create mode 100644 database/database.py create mode 100644 database/user.py create mode 100644 database/userItem.py create mode 100644 database/userPlant.py create mode 100644 database/userSeed.py create mode 100644 database/userSoil.py create mode 100644 database/userSteal.py create mode 100644 dbService.py create mode 100644 event/event.py diff --git a/database.py b/database.py deleted file mode 100644 index ba45c4c..0000000 --- a/database.py +++ /dev/null @@ -1,909 +0,0 @@ -import math -import os -import re -from contextlib import asynccontextmanager -from datetime import date, datetime, timedelta -from io import StringIO -from math import e -from typing import Any, Dict, List, Optional - -import aiosqlite - -from zhenxun.services.log import logger - -from .config import g_pJsonManager, g_sDBFilePath, g_sDBPath - - -class CSqlManager: - def __init__(self): - g_sDBPath.mkdir(parents=True, exist_ok=True) - - @classmethod - async def cleanup(cls): - if cls.m_pDB: - await cls.m_pDB.close() - - @classmethod - async def init(cls) -> bool: - bIsExist = os.path.exists(g_sDBFilePath) - - cls.m_pDB = await aiosqlite.connect(g_sDBFilePath) - cls.m_pDB.row_factory = aiosqlite.Row - - await cls.checkDB() - - return True - - @classmethod - @asynccontextmanager - async def _transaction(cls): - await cls.m_pDB.execute("BEGIN;") - try: - yield - except: - await cls.m_pDB.execute("ROLLBACK;") - raise - else: - await cls.m_pDB.execute("COMMIT;") - - @classmethod - async def getTableInfo(cls, tableName: str) -> list: - if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', tableName): - raise ValueError(f"Illegal table name: {tableName}") - try: - cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{tableName}")') - rows = await cursor.fetchall() - return [{"name": row[1], "type": row[2]} for row in rows] - except aiosqlite.Error: - return [] - - - @classmethod - async def ensureTableSchema(cls, tableName: str, columns: dict) -> bool: - """由AI生成 - 创建表或为已存在表添加缺失字段。 - 返回 True 表示有变更(创建或新增列),False 则无操作 - - Args: - tableName (_type_): 表名 - columns (_type_): 字典 - - Returns: - _type_: _description_ - """ - - info = await cls.getTableInfo(tableName) - existing = {col['name']: col['type'].upper() for col in info} - desired = {k: v.upper() for k, v in columns.items() if k != "PRIMARY KEY"} - primaryKey = columns.get("PRIMARY KEY", "") - - if not existing: - colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items()) - if primaryKey: - colsDef += f", PRIMARY KEY {primaryKey}" - await cls.m_pDB.execute(f'CREATE TABLE "{tableName}" ({colsDef});') - return True - - toAdd = [k for k in desired if k not in existing] - toRemove = [k for k in existing if k not in desired] - typeMismatch = [k for k in desired if k in existing and existing[k] != desired[k]] - - if toAdd and not toRemove and not typeMismatch: - for col in toAdd: - await cls.m_pDB.execute( - f'ALTER TABLE "{tableName}" ADD COLUMN "{col}" {columns[col]}' - ) - return True - - async with cls._transaction(): - tmpTable = f"{tableName}_new" - colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items()) - if primaryKey: - colsDef += f", PRIMARY KEY {primaryKey}" - await cls.m_pDB.execute(f'CREATE TABLE "{tmpTable}" ({colsDef});') - - commonCols = [k for k in desired if k in existing] - if commonCols: - colsStr = ", ".join(f'"{c}"' for c in commonCols) - await cls.m_pDB.execute( - f'INSERT INTO "{tmpTable}" ({colsStr}) SELECT {colsStr} FROM "{tableName}";' - ) - await cls.m_pDB.execute(f'DROP TABLE "{tableName}";') - await cls.m_pDB.execute(f'ALTER TABLE "{tmpTable}" RENAME TO "{tableName}";') - return True - - @classmethod - async def checkDB(cls) -> bool: - #1. 用户表 - userInfo = { - "uid": "INTEGER PRIMARY KEY AUTOINCREMENT", - "name": "TEXT NOT NULL", - "exp": "INTEGER DEFAULT 0", - "point": "INTEGER DEFAULT 0", - "soil": "INTEGER DEFAULT 3", - "stealing": "TEXT DEFAULT NULL" - } - #2. 土地表 - userSoilInfo = { - "uid": "INTEGER PRIMARY KEY AUTOINCREMENT", - **{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)" - } - # 5. 用户道具明细表 - userItem = { - "uid": "INTEGER NOT NULL", - "item": "TEXT NOT NULL", - "count": "INTEGER NOT NULL DEFAULT 0", - "PRIMARY KEY": "(uid, item)" - } - - #建表(或增列) - await cls.ensureTableSchema("user", userInfo) - await cls.ensureTableSchema("soil", userSoilInfo) - await cls.ensureTableSchema("userPlant", userPlant) - await cls.ensureTableSchema("userSeed", userSeed) - await cls.ensureTableSchema("userItem", userItem) - - return True - - @classmethod - async def executeDB(cls, command: str) -> bool: - """执行自定义SQL - - Args: - command (str): SQL语句 - - Returns: - bool: 是否执行成功 - """ - if len(command) <= 0: - logger.warning("数据库语句长度为空!") - return False - - try: - async with cls._transaction(): - await cls.m_pDB.execute(command) - return True - except Exception as e: - logger.warning("数据库语句执行出错:" + command) - return False - - @classmethod - async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 500): - """初始化用户信息 - - Args: - uid (str): 用户Uid - name (str): 农场名称 - exp (int): 农场经验 - point (int): 农场币 - """ - - #用户信息 - userInfo = f""" - INSERT INTO user (uid, name, exp, point, soil, stealing) VALUES ({uid}, '{name}', {exp}, {point}, 3, '{date.today()}|5') - """ - - #用户土地 - userSoilInfo = f""" - INSERT INTO soil (uid) VALUES ({uid}); - """ - - if not await cls.executeDB(userInfo): - return False - - if not await cls.executeDB(userSoilInfo): - return False - - return "开通农场成功" - - @classmethod - async def getUserInfoByUid(cls, uid: str) -> dict: - """根据用户Uid获取用户信息 - - Args: - uid (str): 用户Uid - - Returns: - list[dict]: 用户信息 - """ - if len(uid) <= 0: - return {} - - try: - async with cls.m_pDB.execute( - "SELECT * FROM user WHERE uid = ?", (uid,) - ) as cursor: - async for row in cursor: - userDict = { - "uid": row[0], - "name": row[1], - "exp": row[2], - "point": row[3], - "soil": row[4], - "stealing": row[5] - } - - return userDict - return {} - except Exception as e: - logger.warning(f"getUserInfoByUid查询失败: {e}") - return {} - - @classmethod - async def getUserNameByUid(cls, uid: str) -> str: - """根据用户uid查询用户名 - - Args: - uid (str): 用户uid - - Returns: - str: 用户名,如果失败返回空字符串 - """ - if not uid: - return "" - - try: - async with cls.m_pDB.execute( - "SELECT name FROM user WHERE uid = ?", - (uid,) - ) as cursor: - row = await cursor.fetchone() - return row["name"] if row else "" - except Exception as e: - logger.warning(f"真寻农场getUserNameByUid查询失败: {e}") - return "" - - @classmethod - async def updateUserNameByUid(cls, uid: str, name: str) -> bool: - """根据用户uid修改用户名 - - Args: - uid (str): 用户uid - name (str): 新用户名 - - Returns: - bool: 是否更新成功 - """ - if not uid or not name: - return False - - try: - async with cls._transaction(): - await cls.m_pDB.execute( - "UPDATE user SET name = ? WHERE uid = ?", - (name, uid) - ) - return True - except Exception as e: - logger.warning(f"真寻农场updateUserNameByUid失败: {e}") - return False - - @classmethod - async def getUserPointByUid(cls, uid: str) -> int: - """根据用户Uid获取用户农场币 - - Args: - uid (str): 用户Uid - - Returns: - int: 用户农场币 - """ - if len(uid) <= 0: - return -1 - - try: - async with cls.m_pDB.execute(f"SELECT point FROM user WHERE uid = {uid}") as cursor: - async for row in cursor: - return int(row[0]) - - return -1 - except Exception as e: - logger.warning(f"getUserPointByUid查询失败: {e}") - return -1 - - @classmethod - async def updateUserPointByUid(cls, uid: str, point: int) -> int: - """根据用户Uid修改用户农场币 - - Args: - uid (str): 用户Uid - point (int): 要更新的新农场币数量(需 ≥ 0) - - Returns: - int: 更新后的农场币数量(成功时),-1(失败时) - """ - - if len(uid) <= 0: - logger.warning("参数校验失败: uid为空或农场币值无效") - return -1 - - try: - return await cls.executeDB(f"UPDATE user SET point = {point} WHERE uid = {uid}") - except Exception as e: - logger.error(f"金币更新失败: {e}") - return -1 - - @classmethod - async def getUserExpByUid(cls, uid: str) -> int: - """根据用户Uid获取用户经验 - - Args: - uid (str): 用户Uid - - Returns: - int: 用户经验值 - """ - if len(uid) <= 0: - return -1 - - try: - async with cls.m_pDB.execute(f"SELECT exp FROM user WHERE uid = {uid}") as cursor: - async for row in cursor: - return int(row[0]) - - return -1 - except Exception as e: - logger.warning(f"getUserLevelByUid查询失败: {e}") - return -1 - - @classmethod - async def UpdateUserExpByUid(cls, uid: str, exp: int) -> bool: - """根据用户Uid刷新用户经验 - - Args: - uid (str): 用户Uid - - Returns: - bool: 是否成功 - """ - if len(uid) <= 0: - return False - - sql = f"UPDATE user SET exp = '{exp}' WHERE uid = {uid}" - - return await cls.executeDB(sql) - - @classmethod - async def getUserLevelByUid(cls, uid: str) -> tuple[int, int, int]: - """根据用户Uid获取用户等级 - - Args: - uid (str): 用户Uid - - Returns: - tuple[int, int, int]: (当前等级, 下级所需经验, 当前等级剩余经验) - """ - if len(uid) <= 0: - return -1, -1, -1 - - try: - async with cls.m_pDB.execute(f"SELECT exp FROM user WHERE uid = {uid}") as cursor: - async for row in cursor: - exp = int(row[0]) - - level = exp // 200 - nextLevelExp = 200 * (level + 1) - - currentLevelExp = level * 200 - remainingExp = exp - currentLevelExp - - return level, nextLevelExp, remainingExp - - return -1, -1, -1 - except Exception as e: - logger.warning(f"getUserLevelByUid查询失败: {e}") - return -1, -1, -1 - - @classmethod - async def getUserSoilByUid(cls, uid: str) -> int: - """根据用户Uid获取解锁地块 - - Args: - uid (str): 用户Uid - - Returns: - int: 解锁几块地 - """ - if len(uid) <= 0: - return 0 - - async with cls.m_pDB.execute(f"SELECT soil FROM user WHERE uid = {uid}") as cursor: - async for row in cursor: - if not row[0]: - return 0 - else: - return int(row[0]) - - return 0 - - @classmethod - async def getUserSoilStatusBySoilID(cls, uid: str, soil: str) -> tuple[bool, str]: - """根据土地块获取用户土地状态 - - Args: - uid (str): 用户Uid - soil (str): 土地id - - Returns: - tuple[bool, str]: [是否可以播种,土地信息] - """ - if len(uid) <= 0: - return False, "" - - async with cls.m_pDB.execute(f"SELECT {soil} FROM soil WHERE uid = {uid}") as cursor: - async for row in cursor: - if row[0] == None or len(row[0]) <= 0: - return True, "" - else: - return False, row[0] - - return False, "" - - @classmethod - async def updateUserSoilStatusByPlantName(cls, uid: str, soil: str, - plant: str = "", - status: int = 0) -> bool: - """根据种子名称使用户播种 - - Args: - uid (str): 用户Uid - soil (str): 土地id - plant (str): 种子名称 - - Returns: - bool: 是否更新成功 - """ - - if len(uid) <= 0: - return False - - if len(plant) <= 0 and status == 4: - s = f",,,{status}," - elif len(plant) <= 0 and status != 4: - s = "" - else: - #获取种子信息 这里能崩我吃 - plantInfo = g_pJsonManager.m_pPlant['plant'][plant] - - currentTime = datetime.now() - newTime = currentTime + timedelta(hours=int(plantInfo['time'])) - - #0: 种子名称 - #1: 种下时间 - #2: 预计成熟时间 - #3: 地状态:0:无 1:长草 2:生虫 3:缺水 4:枯萎 - #4: 是否被偷 示例:QQ号-偷取数量|QQ号-偷取数量 - #5: 土地等级 0:普通 1:红土地 2:黑土地 3:金土地 4:紫晶土地 5:蓝晶土地 6:黑晶土地 - s = f"{plant},{int(currentTime.timestamp())},{int(newTime.timestamp())},{status},," - - sql = f"UPDATE soil SET {soil} = '{s}' WHERE uid = {uid}" - - return await cls.executeDB(sql) - - - @classmethod - async def addUserSeedByUid(cls, uid: str, seed: str, count: int = 1) -> bool: - """根据用户uid添加种子信息 - - Args: - uid (str): 用户uid - seed (str): 种子名称 - count (int): 数量 - - Returns: - bool: 是否添加成功 - """ - try: - async with cls._transaction(): - #检查是否已存在该种子 - async with cls.m_pDB.execute( - "SELECT count FROM userSeed WHERE uid = ? AND seed = ?", - (uid, seed) - ) as cursor: - row = await cursor.fetchone() - - 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) - ) - return True - except Exception as e: - logger.warning(f"真寻农场addUserSeedByUid 失败: {e}") - return False - - @classmethod - async def getUserSeedByName(cls, uid: str, seed: str) -> Optional[int]: - """根据种子名称获取种子数量 - - Args: - uid (str): 用户uid - seed (str): 种子名称 - - Returns: - Optional[int]: 种子数量 - """ - - try: - async with cls.m_pDB.execute( - "SELECT count FROM userSeed WHERE uid = ? AND seed = ?", - (uid, seed) - ) as cursor: - row = await cursor.fetchone() - return row[0] if row else None - except Exception as e: - logger.warning(f"真寻农场getUserSeedByName 查询失败: {e}") - return None - - @classmethod - async def getUserSeedByUid(cls, uid: str) -> dict: - """根据用户Uid获取仓库全部种子信息 - - Args: - 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: - if count <= 0: - return await cls.deleteUserSeedByName(uid, seed) - - async with cls._transaction(): - await cls.m_pDB.execute( - "UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?", - (count, uid, seed) - ) - 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) - ) - 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) - ) - return True - except Exception as e: - logger.warning(f"真寻农场addUserPlantByUid 失败: {e}") - return False - - @classmethod - async def getUserPlantByUid(cls, uid: str) -> Dict[str, int]: - """根据用户uid获取全部作物信息 - - Args: - uid (str): 用户uid - - Returns: - Dict[str, int]: 作物名称和数量 - """ - cursor = await cls.m_pDB.execute( - "SELECT plant, count FROM userPlant WHERE uid=?", - (uid,) - ) - rows = await cursor.fetchall() - return {row["plant"]: row["count"] for row in rows} - - @classmethod - async def getUserPlantByName(cls, uid: str, plant: str) -> Optional[int]: - """根据作物名称获取用户的作物数量 - - Args: - uid (str): 用户uid - plant (str): 作物名称 - - Returns: - Optional[int]: 作物数量 - """ - try: - async with cls.m_pDB.execute( - "SELECT count FROM userPlant WHERE uid = ? AND plant = ?", - (uid, plant) - ) as cursor: - row = await cursor.fetchone() - return row[0] if row else None - except Exception as e: - logger.warning(f"真寻农场getUserPlantByName 查询失败: {e}") - return None - - @classmethod - async def updateUserPlantByName(cls, uid: str, plant: str, count: int) -> bool: - """更新 userPlant 表中某个作物的数量 - - Args: - uid (str): 用户uid - plant (str): 作物名称 - count (int): 新的作物数量 - - Returns: - bool: 是否更新成功 - """ - try: - if count <= 0: - return await cls.deleteUserPlantByName(uid, plant) - - async with cls._transaction(): - await cls.m_pDB.execute( - "UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?", - (count, uid, plant) - ) - return True - except Exception as e: - logger.warning(f"真寻农场updateUserPlantByName失败:{e}") - return False - - @classmethod - async def deleteUserPlantByName(cls, uid: str, plant: str) -> bool: - """从 userPlant 表中删除某个作物记录 - - Args: - uid (str): 用户uid - plant (str): 作物名称 - - Returns: - bool: 是否删除成功 - """ - try: - async with cls._transaction(): - await cls.m_pDB.execute( - "DELETE FROM userPlant WHERE uid = ? AND plant = ?", - (uid, plant) - ) - return True - except Exception as e: - logger.warning(f"真寻农场deleteUserPlantByName 失败: {e}") - return False - - - @classmethod - async def getUserItemByName(cls, uid: str, item: str) -> Optional[int]: - """根据道具名称查询某一项数量 - - Args: - uid (str): 用户uid - item (str): 道具名称 - - Returns: - Optional[int]: 数量(不存在返回None) - """ - if not uid or not item: - return None - try: - async with cls.m_pDB.execute( - "SELECT count FROM userItem WHERE uid = ? AND item = ?", - (uid, item) - ) as cursor: - row = await cursor.fetchone() - return row[0] if row else None - except Exception as e: - logger.warning(f"真寻农场getUserItemByName查询失败: {e}") - return None - - @classmethod - async def getUserItemByUid(cls, uid: str) -> dict: - """根据用户Uid获取全部道具信息 - - Args: - uid (str): 用户uid - - Returns: - dict: {itemName: count, ...} - """ - if not uid: - return {} - try: - cursor = await cls.m_pDB.execute( - "SELECT item, count FROM userItem WHERE uid = ?", - (uid,) - ) - rows = await cursor.fetchall() - return {row["item"]: row["count"] for row in rows} - except Exception as e: - logger.warning(f"真寻农场getUserItemByUid查询失败: {e}") - return {} - - @classmethod - async def deleteUserItemByName(cls, uid: str, item: str) -> bool: - """根据道具名删除道具 - - Args: - uid (str): 用户uid - item (str): 道具名称 - - Returns: - bool: 是否删除成功 - """ - if not uid or not item: - return False - try: - async with cls._transaction(): - await cls.m_pDB.execute( - "DELETE FROM userItem WHERE uid = ? AND item = ?", - (uid, item) - ) - return True - except Exception as e: - logger.warning(f"真寻农场deleteUserItemByName失败: {e}") - return False - - @classmethod - async def updateUserItemByName(cls, uid: str, item: str, count: int) -> bool: - """根据道具名直接更新道具数量 - - Args: - uid (str): 用户uid - item (str): 道具名称 - count (int): 要更新的新数量 - - Returns: - bool: 是否更新成功 - """ - if not uid or not item: - return False - try: - async with cls._transaction(): - if count <= 0: - await cls.m_pDB.execute( - "DELETE FROM userItem WHERE uid = ? AND item = ?", - (uid, item) - ) - else: - await cls.m_pDB.execute( - "UPDATE userItem SET count = ? WHERE uid = ? AND item = ?", - (count, uid, item) - ) - return True - except Exception as e: - logger.warning(f"真寻农场updateUserItemByName失败: {e}") - return False - - @classmethod - async def addUserItemByUid(cls, uid: str, item: str, count: int = 1) -> bool: - """根据用户uid添加道具信息 - - Args: - uid (str): 用户uid - item (str): 道具名称 - count (int, optional): 数量.Defaults to 1. - - Returns: - bool: 是否添加成功 - """ - if not uid or not item: - return False - try: - async with cls._transaction(): - async with cls.m_pDB.execute( - "SELECT count FROM userItem WHERE uid = ? AND item = ?", - (uid, item) - ) as cursor: - row = await cursor.fetchone() - - if row: - newCount = row[0] + count - if newCount <= 0: - await cls.m_pDB.execute( - "DELETE FROM userItem WHERE uid = ? AND item = ?", - (uid, item) - ) - else: - await cls.m_pDB.execute( - "UPDATE userItem SET count = ? WHERE uid = ? AND item = ?", - (newCount, uid, item) - ) - else: - if count > 0: - await cls.m_pDB.execute( - "INSERT INTO userItem (uid, item, count) VALUES (?, ?, ?)", - (uid, item, count) - ) - return True - except Exception as e: - logger.warning(f"真寻农场addUserItemByUid失败: {e}") - return False - -g_pSqlManager = CSqlManager() diff --git a/database/database.py b/database/database.py new file mode 100644 index 0000000..3c5824d --- /dev/null +++ b/database/database.py @@ -0,0 +1,133 @@ +import math +import os +import re +from contextlib import asynccontextmanager + +import aiosqlite + +from zhenxun.services.log import logger + +from ..config import g_sDBFilePath, g_sDBPath +from ..dbService import g_pDBService + + +class CSqlManager: + def __init__(self): + g_sDBPath.mkdir(parents=True, exist_ok=True) + + @classmethod + async def cleanup(cls): + if cls.m_pDB: + await cls.m_pDB.close() + + @classmethod + async def init(cls) -> bool: + bIsExist = os.path.exists(g_sDBFilePath) + + cls.m_pDB = await aiosqlite.connect(g_sDBFilePath) + cls.m_pDB.row_factory = aiosqlite.Row + + await g_pDBService.init() + + return True + + @classmethod + @asynccontextmanager + async def _transaction(cls): + await cls.m_pDB.execute("BEGIN;") + try: + yield + except: + await cls.m_pDB.execute("ROLLBACK;") + raise + else: + await cls.m_pDB.execute("COMMIT;") + + @classmethod + async def getTableInfo(cls, tableName: str) -> list: + if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', tableName): + raise ValueError(f"Illegal table name: {tableName}") + try: + cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{tableName}")') + rows = await cursor.fetchall() + return [{"name": row[1], "type": row[2]} for row in rows] + except aiosqlite.Error: + return [] + + @classmethod + async def ensureTableSchema(cls, tableName: str, columns: dict) -> bool: + """由AI生成 + 创建表或为已存在表添加缺失字段。 + 返回 True 表示有变更(创建或新增列),False 则无操作 + + Args: + tableName (_type_): 表名 + columns (_type_): 字典 + + Returns: + _type_: _description_ + """ + + info = await cls.getTableInfo(tableName) + existing = {col['name']: col['type'].upper() for col in info} + desired = {k: v.upper() for k, v in columns.items() if k != "PRIMARY KEY"} + primaryKey = columns.get("PRIMARY KEY", "") + + if not existing: + colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items()) + if primaryKey: + colsDef += f", PRIMARY KEY {primaryKey}" + await cls.m_pDB.execute(f'CREATE TABLE "{tableName}" ({colsDef});') + return True + + toAdd = [k for k in desired if k not in existing] + toRemove = [k for k in existing if k not in desired] + typeMismatch = [k for k in desired if k in existing and existing[k] != desired[k]] + + if toAdd and not toRemove and not typeMismatch: + for col in toAdd: + await cls.m_pDB.execute( + f'ALTER TABLE "{tableName}" ADD COLUMN "{col}" {columns[col]}' + ) + return True + + async with cls._transaction(): + tmpTable = f"{tableName}_new" + colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items()) + if primaryKey: + colsDef += f", PRIMARY KEY {primaryKey}" + await cls.m_pDB.execute(f'CREATE TABLE "{tmpTable}" ({colsDef});') + + commonCols = [k for k in desired if k in existing] + if commonCols: + colsStr = ", ".join(f'"{c}"' for c in commonCols) + await cls.m_pDB.execute( + f'INSERT INTO "{tmpTable}" ({colsStr}) SELECT {colsStr} FROM "{tableName}";' + ) + await cls.m_pDB.execute(f'DROP TABLE "{tableName}";') + await cls.m_pDB.execute(f'ALTER TABLE "{tmpTable}" RENAME TO "{tableName}";') + return True + + @classmethod + async def executeDB(cls, command: str) -> bool: + """执行自定义SQL + + Args: + command (str): SQL语句 + + Returns: + bool: 是否执行成功 + """ + if len(command) <= 0: + logger.warning("数据库语句长度为空!") + return False + + try: + async with cls._transaction(): + await cls.m_pDB.execute(command) + return True + except Exception as e: + logger.warning("数据库语句执行出错:" + command, e=e) + return False + +g_pSqlManager = CSqlManager() diff --git a/database/user.py b/database/user.py new file mode 100644 index 0000000..1bee7ed --- /dev/null +++ b/database/user.py @@ -0,0 +1,400 @@ +from datetime import date, datetime, timedelta +from typing import List, Union + +from database import CSqlManager + +from zhenxun.services.log import logger + + +class CUserDB(CSqlManager): + @classmethod + async def initDB(cls): + """初始化用户表结构,确保user表存在且字段完整""" + # 用户Uid + # 农场名称 + # 经验值 + # 金币 + # 解锁土地数量 + # 偷菜时间字符串 + # 剩余偷菜次数 + userInfo = { + "uid": "INTEGER PRIMARY KEY AUTOINCREMENT", + "name": "TEXT NOT NULL", + "exp": "INTEGER DEFAULT 0", + "point": "INTEGER DEFAULT 0", + "soil": "INTEGER DEFAULT 3", + "stealTime": "TEXT DEFAULT NULL", + "stealCount": "INTEGER DEFAULT 0" + } + await cls.ensureTableSchema("user", userInfo) + + @classmethod + async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 500) -> Union[bool, str]: + """初始化用户信息,包含初始偷菜时间字符串与次数 + + Args: + uid (str): 用户Uid + name (str): 农场名称 + exp (int): 农场经验 + point (int): 农场币 + + Returns: + Union[bool, str]: False 表示失败,字符串表示成功信息 + """ + nowStr = date.today().strftime('%Y-%m-%d') + sql = ( + f"INSERT INTO user (uid, name, exp, point, soil, stealTime, stealCount) " + f"VALUES ({uid}, '{name}', {exp}, {point}, 3, '{nowStr}', 5)" + ) + try: + async with cls._transaction(): + await cls.m_pDB.execute(sql) + return "开通农场成功" + except Exception as e: + logger.warning("initUserInfoByUid 事务执行失败!", e=e) + return False + + @classmethod + async def getAllUsers(cls) -> List[str]: + """获取所有用户UID列表 + + Returns: + List[str]: 用户UID列表 + """ + cursor = await cls.m_pDB.execute("SELECT uid FROM user") + rows = await cursor.fetchall() + return [row[0] for row in rows] + + @classmethod + async def getUserInfoByUid(cls, uid: str) -> dict: + """获取指定用户完整信息 + + Args: + uid (str): 用户Uid + + Returns: + dict: 包含所有用户字段的字典 + """ + if not uid: + return {} + try: + async with cls.m_pDB.execute( + "SELECT * FROM user WHERE uid = ?", (uid,) + ) as cursor: + async for row in cursor: + return { + "uid": row[0], + "name": row[1], + "exp": row[2], + "point": row[3], + "soil": row[4], + "stealTime": row[5] or "", + "stealCount": int(row[6]) + } + return {} + except Exception as e: + logger.warning("getUserInfoByUid 查询失败!", e=e) + return {} + + @classmethod + async def getUserNameByUid(cls, uid: str) -> str: + """根据用户Uid获取用户名 + + Args: + uid (str): 用户Uid + + Returns: + str: 用户名,失败返回空字符串 + """ + if not uid: + return "" + try: + async with cls.m_pDB.execute( + "SELECT name FROM user WHERE uid = ?", (uid,) + ) as cursor: + row = await cursor.fetchone() + return row["name"] if row else "" + except Exception as e: + logger.warning("getUserNameByUid 查询失败!", e=e) + return "" + + @classmethod + async def updateUserNameByUid(cls, uid: str, name: str) -> bool: + """根据用户Uid更新用户名 + + Args: + uid (str): 用户Uid + name (str): 新用户名 + + Returns: + bool: 是否更新成功 + """ + if not uid or not name: + return False + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE user SET name = ? WHERE uid = ?", (name, uid) + ) + return True + except Exception as e: + logger.warning("updateUserNameByUid 事务执行失败!", e=e) + return False + + @classmethod + async def getUserPointByUid(cls, uid: str) -> int: + """获取指定用户农场币 + + Args: + uid (str): 用户Uid + + Returns: + int: 农场币数量,失败返回 -1 + """ + if not uid: + return -1 + try: + async with cls.m_pDB.execute( + "SELECT point FROM user WHERE uid = ?", (uid,) + ) as cursor: + row = await cursor.fetchone() + return int(row[0]) if row and row[0] is not None else -1 + except Exception as e: + logger.warning("getUserPointByUid 查询失败!", e=e) + return -1 + + @classmethod + async def updateUserPointByUid(cls, uid: str, point: int) -> bool: + """根据用户Uid更新农场币数量 + + Args: + uid (str): 用户Uid + point (int): 新农场币数量 + + Returns: + bool: 是否更新成功 + """ + if not uid or point < 0: + logger.warning("updateUserPointByUid 参数校验失败!") + return False + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE user SET point = ? WHERE uid = ?", (point, uid) + ) + return True + except Exception as e: + logger.error("updateUserPointByUid 事务执行失败!", e=e) + return False + + @classmethod + async def getUserExpByUid(cls, uid: str) -> int: + """获取指定用户经验值 + + Args: + uid (str): 用户Uid + + Returns: + int: 经验值,失败返回 -1 + """ + if not uid: + return -1 + try: + async with cls.m_pDB.execute( + "SELECT exp FROM user WHERE uid = ?", (uid,) + ) as cursor: + row = await cursor.fetchone() + return int(row[0]) if row and row[0] is not None else -1 + except Exception as e: + logger.warning("getUserExpByUid 查询失败!", e=e) + return -1 + + @classmethod + async def updateUserExpByUid(cls, uid: str, exp: int) -> bool: + """根据用户Uid更新经验值 + + Args: + uid (str): 用户Uid + exp (int): 新经验值 + + Returns: + bool: 是否更新成功 + """ + if not uid: + return False + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE user SET exp = ? WHERE uid = ?", (exp, uid) + ) + return True + except Exception as e: + logger.warning("updateUserExpByUid 事务执行失败!", e=e) + return False + + @classmethod + async def getUserLevelByUid(cls, uid: str) -> tuple[int, int, int]: + """获取用户等级信息 + + Args: + uid (str): 用户Uid + + Returns: + tuple[int, int, int]: (当前等级, 下级所需经验, 当前等级剩余经验),失败返回(-1, -1, -1) + """ + if not uid: + return -1, -1, -1 + try: + async with cls.m_pDB.execute( + "SELECT exp FROM user WHERE uid = ?", (uid,) + ) as cursor: + row = await cursor.fetchone() + if row and row[0] is not None: + expVal = int(row[0]) + level = expVal // 200 + nextLevelExp = 200 * (level + 1) + currentLevelExp = level * 200 + remainingExp = expVal - currentLevelExp + return level, nextLevelExp, remainingExp + return -1, -1, -1 + except Exception as e: + logger.warning("getUserLevelByUid 查询失败!", e=e) + return -1, -1, -1 + + @classmethod + async def getUserSoilByUid(cls, uid: str) -> int: + """获取解锁土地数量 + + Args: + uid (str): 用户Uid + + Returns: + int: 解锁土地块数,失败返回0 + """ + if not uid: + return 0 + try: + async with cls.m_pDB.execute( + "SELECT soil FROM user WHERE uid = ?", (uid,) + ) as cursor: + row = await cursor.fetchone() + return int(row[0]) if row and row[0] is not None else 0 + except Exception as e: + logger.warning("getUserSoilByUid 查询失败!", e=e) + return 0 + + @classmethod + async def updateUserSoilByUid(cls, uid: str, soil: int) -> bool: + """更新指定用户解锁土地数量 + + Args: + uid (str): 用户Uid + soil (int): 新土地数量 + + Returns: + bool: 更新成功返回True,否则False + """ + if not uid or soil < 0: + return False + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE user SET soil = ? WHERE uid = ?", (soil, uid) + ) + return True + except Exception as e: + logger.warning("updateUserSoilByUid 事务执行失败!", e=e) + return False + + @classmethod + async def getStealTimeByUid(cls, uid: str) -> str: + """根据用户Uid获取偷菜时间字符串 + + Args: + uid (str): 用户Uid + + Returns: + str: 偷菜时间字符串,失败返回空字符串 + """ + if not uid: + return "" + try: + async with cls.m_pDB.execute( + "SELECT stealTime FROM user WHERE uid = ?", (uid, + ) + ) as cursor: + row = await cursor.fetchone() + return row[0] if row and row[0] else "" + except Exception as e: + logger.warning("getStealTimeByUid 查询失败!", e=e) + return "" + + @classmethod + async def updateStealTimeByUid(cls, uid: str, stealTime: str) -> bool: + """根据用户Uid更新偷菜时间字符串 + + Args: + uid (str): 用户Uid + stealTime (str): 新偷菜时间字符串 + + Returns: + bool: 是否更新成功 + """ + if not uid or not stealTime: + logger.warning("updateStealTimeByUid 参数校验失败!") + return False + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE user SET stealTime = ? WHERE uid = ?", (stealTime, uid) + ) + return True + except Exception as e: + logger.warning("updateStealTimeByUid 事务执行失败!", e=e) + return False + + @classmethod + async def getStealCountByUid(cls, uid: str) -> int: + """根据用户Uid获取剩余偷菜次数 + + Args: + uid (str): 用户Uid + + Returns: + int: 剩余偷菜次数,失败返回 -1 + """ + if not uid: + return -1 + try: + async with cls.m_pDB.execute( + "SELECT stealCount FROM user WHERE uid = ?", (uid,) + ) as cursor: + row = await cursor.fetchone() + return int(row[0]) if row and row[0] is not None else 0 + except Exception as e: + logger.warning("getStealCountByUid 查询失败!", e=e) + return -1 + + @classmethod + async def updateStealCountByUid(cls, uid: str, stealCount: int) -> bool: + """根据用户Uid更新剩余偷菜次数 + + Args: + uid (str): 用户Uid + stealCount (int): 新剩余偷菜次数 + + Returns: + bool: 是否更新成功 + """ + if not uid or stealCount < 0: + logger.warning("updateStealCountByUid 参数校验失败!") + return False + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE user SET stealCount = ? WHERE uid = ?", (stealCount, uid) + ) + return True + except Exception as e: + logger.warning("updateStealCountByUid 事务执行失败!", e=e) + return False diff --git a/database/userItem.py b/database/userItem.py new file mode 100644 index 0000000..142ce83 --- /dev/null +++ b/database/userItem.py @@ -0,0 +1,168 @@ +from typing import Optional + +from database import CSqlManager + +from zhenxun.services.log import logger + + +class CUserItemDB(CSqlManager): + @classmethod + async def initDB(cls): + #用户Uid + #物品名称 + #数量 + userItem = { + "uid": "INTEGER NOT NULL", + "item": "TEXT NOT NULL", + "count": "INTEGER NOT NULL DEFAULT 0", + "PRIMARY KEY": "(uid, item)" + } + + await cls.ensureTableSchema("userItem", userItem) + + @classmethod + async def getUserItemByName(cls, uid: str, item: str) -> Optional[int]: + """根据道具名称查询某一项数量 + + Args: + uid (str): 用户uid + item (str): 道具名称 + + Returns: + Optional[int]: 数量(不存在返回None) + """ + if not uid or not item: + return None + try: + async with cls.m_pDB.execute( + "SELECT count FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) as cursor: + row = await cursor.fetchone() + return row[0] if row else None + except Exception as e: + logger.warning(f"真寻农场getUserItemByName查询失败!", e=e) + return None + + @classmethod + async def getUserItemByUid(cls, uid: str) -> dict: + """根据用户Uid获取全部道具信息 + + Args: + uid (str): 用户uid + + Returns: + dict: {itemName: count, ...} + """ + if not uid: + return {} + try: + cursor = await cls.m_pDB.execute( + "SELECT item, count FROM userItem WHERE uid = ?", + (uid,) + ) + rows = await cursor.fetchall() + return {row["item"]: row["count"] for row in rows} + except Exception as e: + logger.warning(f"真寻农场getUserItemByUid查询失败!", e=e) + return {} + + @classmethod + async def deleteUserItemByName(cls, uid: str, item: str) -> bool: + """根据道具名删除道具 + + Args: + uid (str): 用户uid + item (str): 道具名称 + + Returns: + bool: 是否删除成功 + """ + if not uid or not item: + return False + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "DELETE FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) + return True + except Exception as e: + logger.warning(f"真寻农场deleteUserItemByName失败!", e=e) + return False + + @classmethod + async def updateUserItemByName(cls, uid: str, item: str, count: int) -> bool: + """根据道具名直接更新道具数量 + + Args: + uid (str): 用户uid + item (str): 道具名称 + count (int): 要更新的新数量 + + Returns: + bool: 是否更新成功 + """ + if not uid or not item: + return False + try: + async with cls._transaction(): + if count <= 0: + await cls.m_pDB.execute( + "DELETE FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) + else: + await cls.m_pDB.execute( + "UPDATE userItem SET count = ? WHERE uid = ? AND item = ?", + (count, uid, item) + ) + return True + except Exception as e: + logger.warning(f"真寻农场updateUserItemByName失败!", e=e) + return False + + @classmethod + async def addUserItemByUid(cls, uid: str, item: str, count: int = 1) -> bool: + """根据用户uid添加道具信息 + + Args: + uid (str): 用户uid + item (str): 道具名称 + count (int, optional): 数量.Defaults to 1. + + Returns: + bool: 是否添加成功 + """ + if not uid or not item: + return False + try: + async with cls._transaction(): + async with cls.m_pDB.execute( + "SELECT count FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) as cursor: + row = await cursor.fetchone() + + if row: + newCount = row[0] + count + if newCount <= 0: + await cls.m_pDB.execute( + "DELETE FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) + else: + await cls.m_pDB.execute( + "UPDATE userItem SET count = ? WHERE uid = ? AND item = ?", + (newCount, uid, item) + ) + else: + if count > 0: + await cls.m_pDB.execute( + "INSERT INTO userItem (uid, item, count) VALUES (?, ?, ?)", + (uid, item, count) + ) + return True + except Exception as e: + logger.warning(f"真寻农场addUserItemByUid失败!", e=e) + return False diff --git a/database/userPlant.py b/database/userPlant.py new file mode 100644 index 0000000..76445cb --- /dev/null +++ b/database/userPlant.py @@ -0,0 +1,147 @@ +from typing import Dict, Optional + +from database import CSqlManager + +from zhenxun.services.log import logger + + +class CUserPlantDB(CSqlManager): + @classmethod + async def initDB(cls): + #用户UiD + #作物名称 + #数量 + userPlant = { + "uid": "INTEGER NOT NULL", + "plant": "TEXT NOT NULL", + "count": "INTEGER NOT NULL DEFAULT 0", + "PRIMARY KEY": "(uid, plant)" + } + + await cls.ensureTableSchema("userPlant", userPlant) + + @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) + ) + return True + except Exception as e: + logger.warning(f"真寻农场addUserPlantByUid 失败!", e=e) + return False + + @classmethod + async def getUserPlantByUid(cls, uid: str) -> Dict[str, int]: + """根据用户uid获取全部作物信息 + + Args: + uid (str): 用户uid + + Returns: + Dict[str, int]: 作物名称和数量 + """ + cursor = await cls.m_pDB.execute( + "SELECT plant, count FROM userPlant WHERE uid=?", + (uid,) + ) + rows = await cursor.fetchall() + return {row["plant"]: row["count"] for row in rows} + + @classmethod + async def getUserPlantByName(cls, uid: str, plant: str) -> Optional[int]: + """根据作物名称获取用户的作物数量 + + Args: + uid (str): 用户uid + plant (str): 作物名称 + + Returns: + Optional[int]: 作物数量 + """ + try: + async with cls.m_pDB.execute( + "SELECT count FROM userPlant WHERE uid = ? AND plant = ?", + (uid, plant) + ) as cursor: + row = await cursor.fetchone() + return row[0] if row else None + except Exception as e: + logger.warning(f"真寻农场getUserPlantByName 查询失败!", e=e) + return None + + @classmethod + async def updateUserPlantByName(cls, uid: str, plant: str, count: int) -> bool: + """更新 userPlant 表中某个作物的数量 + + Args: + uid (str): 用户uid + plant (str): 作物名称 + count (int): 新的作物数量 + + Returns: + bool: 是否更新成功 + """ + try: + if count <= 0: + return await cls.deleteUserPlantByName(uid, plant) + + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?", + (count, uid, plant) + ) + return True + except Exception as e: + logger.warning(f"真寻农场updateUserPlantByName失败!", e=e) + return False + + @classmethod + async def deleteUserPlantByName(cls, uid: str, plant: str) -> bool: + """从 userPlant 表中删除某个作物记录 + + Args: + uid (str): 用户uid + plant (str): 作物名称 + + Returns: + bool: 是否删除成功 + """ + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "DELETE FROM userPlant WHERE uid = ? AND plant = ?", + (uid, plant) + ) + return True + except Exception as e: + logger.warning(f"真寻农场deleteUserPlantByName 失败!", e=e) + return False diff --git a/database/userSeed.py b/database/userSeed.py new file mode 100644 index 0000000..dc4df5f --- /dev/null +++ b/database/userSeed.py @@ -0,0 +1,157 @@ +from typing import Optional + +from database import CSqlManager + +from zhenxun.services.log import logger + + +class CUserSeedDB(CSqlManager): + @classmethod + async def initDB(cls): + #用户Uid + #种子名称 + #数量 + userSeed = { + "uid": "INTEGER NOT NULL", + "seed": "TEXT NOT NULL", + "count": "INTEGER NOT NULL DEFAULT 0", + "PRIMARY KEY": "(uid, seed)" + } + + await cls.ensureTableSchema("userSeed", userSeed) + + @classmethod + async def addUserSeedByUid(cls, uid: str, seed: str, count: int = 1) -> bool: + """根据用户uid添加种子信息 + + Args: + uid (str): 用户uid + seed (str): 种子名称 + count (int): 数量 + + Returns: + bool: 是否添加成功 + """ + try: + async with cls._transaction(): + #检查是否已存在该种子 + async with cls.m_pDB.execute( + "SELECT count FROM userSeed WHERE uid = ? AND seed = ?", + (uid, seed) + ) as cursor: + row = await cursor.fetchone() + + 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) + ) + return True + except Exception as e: + logger.warning(f"真寻农场addUserSeedByUid 失败!", e=e) + return False + + @classmethod + async def getUserSeedByName(cls, uid: str, seed: str) -> Optional[int]: + """根据种子名称获取种子数量 + + Args: + uid (str): 用户uid + seed (str): 种子名称 + + Returns: + Optional[int]: 种子数量 + """ + + try: + async with cls.m_pDB.execute( + "SELECT count FROM userSeed WHERE uid = ? AND seed = ?", + (uid, seed) + ) as cursor: + row = await cursor.fetchone() + return row[0] if row else None + except Exception as e: + logger.warning(f"真寻农场getUserSeedByName 查询失败!", e=e) + return None + + @classmethod + async def getUserSeedByUid(cls, uid: str) -> dict: + """根据用户Uid获取仓库全部种子信息 + + Args: + 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: + if count <= 0: + return await cls.deleteUserSeedByName(uid, seed) + + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?", + (count, uid, seed) + ) + return True + except Exception as e: + logger.warning(f"真寻农场updateUserSeedByName失败!", e=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) + ) + return True + except Exception as e: + logger.warning(f"真寻农场deleteUserSeedByName 删除失败!", e=e) + return False diff --git a/database/userSoil.py b/database/userSoil.py new file mode 100644 index 0000000..ca3548b --- /dev/null +++ b/database/userSoil.py @@ -0,0 +1,261 @@ +from datetime import date, datetime, timedelta +from typing import Optional + +from database import CSqlManager + +from zhenxun.services.log import logger + +from ..config import g_pJsonManager +from ..dbService import g_pDBService + + +class CUserSoilDB(CSqlManager): + @classmethod + async def initDB(cls): + #用户Uid + #地块索引从1开始 + #作物名称 + #播种时间 + #成熟时间 + #土地等级 0=普通地,1=红土地,2=黑土地,3=金土地 + #枯萎状态 0=未枯萎,1=枯萎 + #施肥状态 0=未施肥,1=施肥 + #虫害状态 0=无虫害,1=有虫害 + #杂草状态 0=无杂草,1=有杂草 + #缺水状态 0=不缺水,1=缺水 + userSoil = { + "uid": "TEXT NOT NULL", + "soilIndex": "INTEGER NOT NULL", + "plantName": "TEXT DEFAULT ''", + "plantTime": "INTEGER DEFAULT 0", + "matureTime": "INTEGER DEFAULT 0", + "soilLevel": "INTEGER DEFAULT 0", + "wiltStatus": "INTEGER DEFAULT 0", + "fertilizerStatus": "INTEGER DEFAULT 0", + "bugStatus": "INTEGER DEFAULT 0", + "weedStatus": "INTEGER DEFAULT 0", + "waterStatus": "INTEGER DEFAULT 0", + "PRIMARY KEY": "(uid, soilIndex)" + } + + await cls.ensureTableSchema("userSoil", userSoil) + + @classmethod + async def getUserFarmByUid(cls, uid: str) -> dict: + """获取指定用户的旧农场数据 + + Args: + uid (str): 用户ID + + Returns: + dict: 包含字段名-值的字典; 若无数据则返回空字典 + """ + cursor = await cls.m_pDB.execute("SELECT * FROM soil WHERE uid = ?", (uid,)) + row = await cursor.fetchone() + if not row: + return {} + columns = [description[0] for description in cursor.description] + return dict(zip(columns, row)) + + @classmethod + async def migrateOldFarmData(cls): + """迁移旧土地数据到新表 userSoil 并删除旧表 + + Raises: + aiosqlite.Error: 数据库操作失败时抛出 + + Returns: + None + """ + async with cls._transaction(): + users = await g_pDBService.user.getAllUsers() + for uid in users: + farmInfo = await cls.getUserFarmByUid(uid) + for i in range(1, 31): + soilName = f"soil{i}" + if soilName not in farmInfo: + continue + soilData = farmInfo[soilName] + if not soilData: + continue + + fields = soilData.split(',') + if len(fields) < 6: + continue + + plantName = fields[0] + plantTime = int(fields[1]) + matureTime = int(fields[2]) + soilLevel = int(fields[5]) + + # 构建新的土地数据 + soilInfo = { + "uid": uid, + "soilIndex": i, + "plantName": plantName, + "plantTime": plantTime, + "matureTime": matureTime, + "soilLevel": soilLevel, + "wiltStatus":0, + "fertilizerStatus": 0, + "bugStatus": 0, + "weedStatus": 0, + "waterStatus": 0 + } + + await cls.insertUserSoil(soilInfo) + + # 彻底清除旧表 + await cls.m_pDB.execute("DROP TABLE soil") + + @classmethod + async def insertUserSoil(cls, soilInfo: dict): + """插入一条新的 userSoil 记录 + + Args: + soilInfo (dict): 新土地数据 + + Returns: + None + """ + async with cls._transaction(): + await cls.m_pDB.execute( + "INSERT INTO userSoil (uid, soilIndex, plantName, plantTime, matureTime, soilLevel, fertilizerStatus, bugStatus, weedStatus, waterStatus) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ( + soilInfo["uid"], + soilInfo["soilIndex"], + soilInfo.get("plantName", ""), + soilInfo.get("plantTime", 0), + soilInfo.get("matureTime", 0), + soilInfo.get("soilLevel", 0), + soilInfo.get("fertilizerStatus", 0), + soilInfo.get("bugStatus", 0), + soilInfo.get("weedStatus", 0), + soilInfo.get("waterStatus", 0) + ) + ) + + @classmethod + async def getUserSoil(cls, uid: str, soilIndex: int) -> Optional[dict]: + """获取指定用户某块土地的详细信息 + + Args: + uid (str): 用户ID + soilIndex (int): 土地索引 + + Returns: + Optional[dict]: 记录存在返回字段-值字典,否则返回 None + """ + async with cls._transaction(): + cursor = await cls.m_pDB.execute( + "SELECT * FROM userSoil WHERE uid = ? AND soilIndex = ?", + (uid, soilIndex) + ) + row = await cursor.fetchone() + if not row: + return None + columns = [description[0] for description in cursor.description] + return dict(zip(columns, row)) + + @classmethod + async def updateUserSoil(cls, uid: str, soilIndex: int, field: str, value): + """更新指定用户土地的单个字段 + + Args: + uid (str): 用户ID + soilIndex (int): 土地索引 + field (str): 需更新的字段名 + value: 新值 + + Returns: + None + """ + async with cls._transaction(): + await cls.m_pDB.execute( + f"UPDATE userSoil SET {field} = ? WHERE uid = ? AND soilIndex = ?", + (value, uid, soilIndex) + ) + + @classmethod + async def deleteUserSoil(cls, uid: str, soilIndex: int): + """删除指定用户的土地记录 + + Args: + uid (str): 用户ID + soilIndex (int): 土地索引 + + Returns: + None + """ + async with cls._transaction(): + await cls.m_pDB.execute( + "DELETE FROM userSoil WHERE uid = ? AND soilIndex = ?", + (uid, soilIndex) + ) + + @classmethod + async def isSoilPlanted(cls, uid: str, soilIndex: int) -> bool: + """判断指定用户的指定土地是否已种植 + + Args: + uid (str): 用户ID + soilIndex (int): 土地索引 + + Returns: + bool: 如果 plantName 不为空且 plantTime 大于 0,则视为已种植,返回 True;否则 False + """ + soilInfo = await cls.getUserSoil(uid, soilIndex) + if not soilInfo: + return False + + return bool(soilInfo.get("plantName")) and soilInfo.get("plantTime", 0) > 0 + + @classmethod + async def sowingByPlantName(cls, uid: str, soilIndex: int, plantName: str) -> bool: + """播种指定作物到用户土地区 + + Args: + uid (str): 用户ID + soilIndex (int): 土地区索引 + plantName (str): 植物名 + + Returns: + bool: 播种成功返回 True,否则返回 False + """ + # 校验土地区是否已种植 + soilRecord = await cls.getUserSoil(uid, soilIndex) + if soilRecord and soilRecord.get("plantName"): + return False + + # 获取植物配置 + plantCfg = g_pJsonManager.m_pPlant.get("plant", {}).get(plantName) + if not plantCfg: + logger.error(f"未知植物: {plantName}") + return False + + nowTs = int(datetime.now().timestamp()) + matureTs = nowTs + int(plantCfg.get("time", 0)) * 3600 + + try: + async with cls._transaction(): + # 复用原有记录字段,保留土壤状态等信息 + prev = soilRecord or {} + await cls.deleteUserSoil(uid, soilIndex) + await cls.insertUserSoil({ + "uid": uid, + "soilIndex": soilIndex, + "plantName": plantName, + "plantTime": nowTs, + "matureTime": matureTs, + # 保留之前的土壤等级和状态字段,避免数据丢失 + "soilLevel": prev.get("soilLevel", 0), + "wiltStatus": prev.get("wiltStatus", 0), + "fertilizerStatus": prev.get("fertilizerStatus", 0), + "bugStatus": prev.get("bugStatus", 0), + "weedStatus": prev.get("weedStatus", 0), + "waterStatus": prev.get("waterStatus", 0) + }) + return True + except Exception as e: + logger.error(f"真寻农场播种失败!", e=e) + return False diff --git a/database/userSteal.py b/database/userSteal.py new file mode 100644 index 0000000..cb46e2d --- /dev/null +++ b/database/userSteal.py @@ -0,0 +1,228 @@ +from database import CSqlManager + +from zhenxun.services.log import logger + + +class CUserStealDB(CSqlManager): + @classmethod + async def initDB(cls): + # 被偷用户Uid + # 被偷的地块索引 从1开始 + # 偷菜用户Uid + # 被偷数量 + # 被偷时间 + userSteal = { + "uid": "TEXT NOT NULL", + "soilIndex": "INTEGER NOT NULL", + "stealerUid": "TEXT NOT NULL", + "stealCount": "INTEGER NOT NULL", + "stealTime": "INTEGER NOT NULL", + "PRIMARY KEY": "(uid, soilIndex, stealerUid)" + } + await cls.ensureTableSchema("userSteal", userSteal) + + @classmethod + async def addStealRecord(cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int) -> bool: + """添加偷菜记录 + + Args: + uid (str): 被偷用户Uid + soilIndex (int): 被偷地块索引 + stealerUid (str): 偷菜用户Uid + stealCount (int): 被偷数量 + stealTime (int): 被偷时间(时间戳) + + Returns: + bool: 操作是否成功 + """ + try: + async with cls._transaction(): + await cls.m_pDB.execute( + 'INSERT INTO "userSteal"(uid, soilIndex, stealerUid, stealCount, stealTime) VALUES(?, ?, ?, ?, ?);', + (uid, soilIndex, stealerUid, stealCount, stealTime) + ) + return True + except Exception as e: + logger.warning("添加偷菜记录失败", e=e) + return False + + @classmethod + async def getStealRecordsByUid(cls, uid: str) -> list: + """根据用户Uid获取所有偷菜记录 + + Args: + uid (str): 被偷用户Uid + + Returns: + list: 偷菜记录字典列表,每条包含 soilIndex, stealerUid, stealCount, stealTime + """ + try: + async with cls._transaction(): + cursor = await cls.m_pDB.execute( + 'SELECT soilIndex, stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=?;', + (uid,) + ) + rows = await cursor.fetchall() + return [ + { + "uid": uid, + "soilIndex": row[0], + "stealerUid": row[1], + "stealCount": row[2], + "stealTime": row[3] + } + for row in rows + ] + except Exception as e: + logger.warning("获取偷菜记录失败", e=e) + return [] + + @classmethod + async def getStealRecord(cls, uid: str, soilIndex: int) -> list: + """获取指定地块的所有偷菜记录 + + Args: + uid (str): 被偷用户Uid + soilIndex (int): 被偷地块索引 + + Returns: + list: 偷菜记录字典列表,每条包含 stealerUid, stealCount, stealTime + """ + try: + async with cls._transaction(): + cursor = await cls.m_pDB.execute( + 'SELECT stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=? AND soilIndex=?;', + (uid, soilIndex) + ) + rows = await cursor.fetchall() + return [ + { + "uid": uid, + "soilIndex": soilIndex, + "stealerUid": row[0], + "stealCount": row[1], + "stealTime": row[2] + } + for row in rows + ] + except Exception as e: + logger.warning("获取单地块偷菜记录失败", e=e) + return [] + + @classmethod + async def getTotalStolenCount(cls, uid: str, soilIndex: int) -> int: + """计算指定地块被偷的总数量(所有用户偷取数量之和) + + Args: + uid (str): 被偷用户Uid + soilIndex (int): 被偷地块索引 + + Returns: + int: 被偷的总数量,如果无记录则返回 0 + """ + try: + async with cls._transaction(): + cursor = await cls.m_pDB.execute( + 'SELECT SUM(stealCount) FROM "userSteal" WHERE uid=? AND soilIndex=?;', + (uid, soilIndex) + ) + row = await cursor.fetchone() + return row[0] or 0 # type: ignore + except Exception as e: + logger.warning("计算总偷菜数量失败", e=e) + return 0 + + @classmethod + async def getStealerCount(cls, uid: str, soilIndex: int) -> int: + """计算指定地块被多少人偷过(不同偷菜用户数量) + + Args: + uid (str): 被偷用户Uid + soilIndex (int): 被偷地块索引 + + Returns: + int: 偷菜者总数,如果无记录则返回 0 + """ + try: + async with cls._transaction(): + cursor = await cls.m_pDB.execute( + 'SELECT COUNT(DISTINCT stealerUid) FROM "userSteal" WHERE uid=? AND soilIndex=?;', + (uid, soilIndex) + ) + row = await cursor.fetchone() + return row[0] or 0 # type: ignore + except Exception as e: + logger.warning("计算偷菜者数量失败", e=e) + return 0 + + @classmethod + async def hasStealed(cls, uid: str, soilIndex: int, stealerUid: str) -> bool: + """判断指定用户是否曾偷取过该地块 + + Args: + uid (str): 被偷用户Uid + soilIndex (int): 被偷地块索引 + stealerUid (str): 偷菜用户Uid + + Returns: + bool: 若存在记录返回 True,否则返回 False + """ + try: + async with cls._transaction(): + cursor = await cls.m_pDB.execute( + 'SELECT 1 FROM "userSteal" WHERE uid=? AND soilIndex=? AND stealerUid=? LIMIT 1;', + (uid, soilIndex, stealerUid) + ) + row = await cursor.fetchone() + return bool(row) + except Exception as e: + logger.warning("检查偷菜记录失败", e=e) + return False + + @classmethod + async def updateStealRecord(cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int) -> bool: + """更新偷菜记录的数量和时间 + + Args: + uid (str): 被偷用户Uid + soilIndex (int): 被偷地块索引 + stealerUid (str): 偷菜用户Uid + stealCount (int): 新的偷菜数量 + stealTime (int): 新的偷菜时间(时间戳) + + Returns: + bool: 操作是否成功 + """ + try: + async with cls._transaction(): + await cls.m_pDB.execute( + 'UPDATE "userSteal" SET stealCount=?, stealTime=? WHERE uid=? AND soilIndex=? AND stealerUid=?;', + (stealCount, stealTime, uid, soilIndex, stealerUid) + ) + return True + except Exception as e: + logger.warning("更新偷菜记录失败", e=e) + return False + + @classmethod + async def deleteStealRecord(cls, uid: str, soilIndex: int, stealerUid: str) -> bool: + """删除指定偷菜记录 + + Args: + uid (str): 被偷用户Uid + soilIndex (int): 被偷地块索引 + stealerUid (str): 偷菜用户Uid + + Returns: + bool: 删除是否成功 + """ + try: + async with cls._transaction(): + await cls.m_pDB.execute( + 'DELETE FROM "userSteal" WHERE uid=? AND soilIndex=? AND stealerUid=?;', + (uid, soilIndex, stealerUid) + ) + return True + except Exception as e: + logger.warning("删除偷菜记录失败", e=e) + return False diff --git a/dbService.py b/dbService.py new file mode 100644 index 0000000..f3c0aeb --- /dev/null +++ b/dbService.py @@ -0,0 +1,29 @@ +from typing import Optional + +from .database.user import CUserDB +from .database.userItem import CUserItemDB +from .database.userPlant import CUserPlantDB +from .database.userSeed import CUserSeedDB +from .database.userSoil import CUserSoilDB +from .database.userSteal import CUserStealDB + + +class CDBService: + def __init__(self): + user: Optional["CUserDB"] = None + userSoil: Optional["CUserSoilDB"] = None + userPlant: Optional["CUserPlantDB"] = None + userSeed: Optional["CUserSeedDB"] = None + userItem: Optional["CUserItemDB"] = None + userSteal: Optional["CUserStealDB"] = None + + @classmethod + async def init(cls): + cls.user = CUserDB() + cls.userSoil = CUserSoilDB() + cls.userPlant = CUserPlantDB() + cls.userSeed = CUserSeedDB() + cls.userItem = CUserItemDB() + cls.userSteal = CUserStealDB() + +g_pDBService = CDBService() diff --git a/event/event.py b/event/event.py new file mode 100644 index 0000000..4d468db --- /dev/null +++ b/event/event.py @@ -0,0 +1,103 @@ +import asyncio +import time + +from zhenxun.services.log import logger + + +class Signal: + def __init__(self): + self._slots = [] #绑定的槽函数列表 + self._onceSlots = [] #只触发一次的槽函数列表 + + def connect(self, slot, priority=0): + if callable(slot) and not any(s[0] == slot for s in self._slots): + self._slots.append((slot, priority)) + self._slots.sort(key=lambda x: -x[1]) + + def connectOnce(self, slot, priority=0): + if callable(slot) and not any(s[0] == slot for s in self._onceSlots): + self._onceSlots.append((slot, priority)) + self._onceSlots.sort(key=lambda x: -x[1]) + + def disconnect(self, slot): + self._slots = [s for s in self._slots if s[0] != slot] + self._onceSlots = [s for s in self._onceSlots if s[0] != slot] + + async def emit(self, *args, **kwargs): + slots = list(self._slots) + onceSlots = list(self._onceSlots) + self._onceSlots.clear() + + for slot, _ in slots + onceSlots: + startTime = time.time() + try: + if asyncio.iscoroutinefunction(slot): + await slot(*args, **kwargs) + else: + slot(*args, **kwargs) + duration = (time.time() - startTime) * 1000 + logger.debug(f"事件槽 {slot.__name__} 执行完成,耗时 {duration:.2f} ms") + except Exception as e: + logger.warning(f"事件槽 {slot.__name__} 触发异常: {e}") + + + +class FarmEventManager: + def __init__(self): + + self.m_beforePlant = Signal() + """播种前信号 + + Args: + uid (str): 用户Uid + name (str): 播种种子名称 + num (int): 播种数量 + """ + + self.m_afterPlant = Signal() + """播种后信号 每块地播种都会触发该信号 + + Args: + uid (str): 用户Uid + name (str): 播种种子名称 + soilIndex (int): 播种地块索引 从1开始 + """ + + self.m_beforeHarvest = Signal() + """收获前信号 + + Args: + uid (str): 用户Uid + """ + + self.m_afterHarvest = Signal() + """收获后信号 每块地收获都会触发该信号 + + Args: + uid (str): 用户Uid + name (str): 收获作物名称 + num (int): 收获数量 + soilIndex (int): 收获地块索引 从1开始 + """ + + self.m_beforeEradicate = Signal() + """铲除前信号 + + Args: + uid (str): 用户Uid + """ + + self.m_afterEradicate = Signal() + """铲除后信号 每块地铲除都会触发该信号 + + Args: + uid (str): 用户Uid + soilIndex (index): 铲除地块索引 从1开始 + """ + + self.m_beforeExpand = Signal() + self.m_afterExpand = Signal() + self.m_beforeSteal = Signal() + self.m_afterSteal = Signal() + +g_pEventManager = FarmEventManager() diff --git a/farm/farm.py b/farm/farm.py index ff0870a..8061499 100644 --- a/farm/farm.py +++ b/farm/farm.py @@ -5,8 +5,6 @@ from datetime import date, datetime from io import StringIO from typing import Dict, List, Tuple -from numpy import number - from zhenxun.configs.config import Config from zhenxun.models.user_console import UserConsole from zhenxun.services.log import logger @@ -16,7 +14,8 @@ from zhenxun.utils.image_utils import ImageTemplate from zhenxun.utils.platform import PlatformUtils from ..config import g_pJsonManager, g_sResourcePath -from ..database import g_pSqlManager +from ..dbService import g_pDBService +from ..event.event import g_pEventManager class CFarmManager: @@ -38,15 +37,15 @@ class CFarmManager: if user.gold < deduction: return f"你的金币不足或不足承担手续费。当前手续费为{fee}" - await UserConsole.reduce_gold(uid, num, GoldHandle.PLUGIN , 'zhenxun_plugin_farm') - await UserConsole.reduce_gold(uid, fee, GoldHandle.PLUGIN , 'zhenxun_plugin_farm') + await UserConsole.reduce_gold(uid, num, GoldHandle.PLUGIN , 'zhenxun_plugin_farm') # type: ignore + await UserConsole.reduce_gold(uid, fee, GoldHandle.PLUGIN , 'zhenxun_plugin_farm') # type: ignore point = num * pro - p = await g_pSqlManager.getUserPointByUid(uid) + p = await g_pDBService.user.getUserPointByUid(uid) number = point + p - await g_pSqlManager.updateUserPointByUid(uid, int(number)) + await g_pDBService.user.updateUserPointByUid(uid, int(number)) return f"充值{point}农场币成功,手续费{tax}金币,当前农场币:{number}" @@ -73,7 +72,7 @@ class CFarmManager: soilPos = g_pJsonManager.m_pSoil['soil'] - userInfo = await g_pSqlManager.getUserInfoByUid(uid) + userInfo = await g_pDBService.user.getUserInfoByUid(uid) soilUnlock = int(userInfo['soil']) x = 0 @@ -89,7 +88,7 @@ class CFarmManager: if index < soilUnlock: await img.paste(soil, (x, y)) - isPlant, plant, isRipe= await cls.drawSoilPlant(uid, f"soil{str(index + 1)}") + isPlant, plant, isRipe= await cls.drawSoilPlant(uid, index + 1) if isPlant: await img.paste(plant, (x + soilSize[0] // 2 - plant.width // 2, @@ -134,7 +133,7 @@ class CFarmManager: await img.paste(nameImg, (300, 92)) #经验值 - level = await g_pSqlManager.getUserLevelByUid(uid) + level = await g_pDBService.user.getUserLevelByUid(uid) beginX = 309 endX = 627 @@ -171,57 +170,59 @@ class CFarmManager: return img.pic2bytes() @classmethod - async def drawSoilPlant(cls, uid: str, soilid: str) -> tuple[bool, BuildImage, bool]: + async def drawSoilPlant(cls, uid: str, soilIndex: int) -> tuple[bool, BuildImage, bool]: """绘制植物资源 Args: uid (str): 用户Uid - soilid (str): 土地id + soilIndex (int): 土地索引 从1开始 Returns: tuple[bool, BuildImage]: [绘制是否成功,资源图片, 是否成熟] """ plant = None - soilStatus, soilInfo = await g_pSqlManager.getUserSoilStatusBySoilID(uid, soilid) + soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex) - if soilStatus == True: - return False, None, False + if not soilInfo or not soilInfo.get("plantName"): + return False, None, False #type: ignore + + #是否枯萎 + if int(soilInfo.get("wiltStatus", 0)) == 1: + plant = BuildImage(background = g_sResourcePath / f"plant/basic/9.png") + await plant.resize(0, 150, 212) + return True, plant, False + + #获取作物详细信息 + plantInfo = g_pJsonManager.m_pPlant['plant'][soilInfo['plantName']] + + currentTime = datetime.now() + matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) + + #如果当前时间大于成熟时间 说明作物成熟 + if currentTime >= matureTime: + phase = int(plantInfo['phase']) + plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo['plantName']}/{phase - 1}.png") + + return True, plant, True else: - soilInfo = soilInfo.split(',') + #如果没有成熟 则根据当前阶段进行绘制 + plantedTime = datetime.fromtimestamp(int(soilInfo['plantTime'])) - if int(soilInfo[3]) == 4: - plant = BuildImage(background = g_sResourcePath / f"plant/basic/9.png") - await plant.resize(0, 150, 212) - return True, plant, False + elapsedTime = currentTime - plantedTime + elapsedHour = elapsedTime.total_seconds() / 3600 - plantInfo = g_pJsonManager.m_pPlant['plant'][soilInfo[0]] + currentStage = int(elapsedHour / (plantInfo['time'] / (plantInfo['phase'] - 1))) - currentTime = datetime.now() - matureTime = datetime.fromtimestamp(int(soilInfo[2])) - - if currentTime >= matureTime: - phase = int(plantInfo['phase']) - plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo[0]}/{phase - 1}.png") - - return True, plant, True - else: - plantedTime = datetime.fromtimestamp(int(soilInfo[1])) - - elapsedTime = currentTime - plantedTime - elapsedHour = elapsedTime.total_seconds() / 60 / 60 - - currentStage = int(elapsedHour / (plantInfo['time'] / (plantInfo['phase'] - 1))) - - if currentStage <= 0: - if plantInfo['general'] == False: - plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo[0]}/0.png") - else: - plant = BuildImage(background = g_sResourcePath / f"plant/basic/0.png") - - await plant.resize(0, 35, 58) + if currentStage <= 0: + if plantInfo['general'] == False: + plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo['plantName']}/0.png") else: - plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo[0]}/{currentStage}.png") + plant = BuildImage(background = g_sResourcePath / f"plant/basic/0.png") + + await plant.resize(0, 35, 58) + else: + plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo['plantName']}/{currentStage}.png") return True, plant, False @@ -241,8 +242,8 @@ class CFarmManager: "是否可以上架交易行" ] - # 从数据库获取结构化数据 - seedRecords = await g_pSqlManager.getUserSeedByUid(uid) or {} + #从数据库获取结构化数据 + seedRecords = await g_pDBService.userSeed.getUserSeedByUid(uid) or {} if not seedRecords: result = await ImageTemplate.table_page( @@ -295,54 +296,57 @@ class CFarmManager: str: 返回结果 """ try: - # 获取用户的种子数量 - count = await g_pSqlManager.getUserSeedByName(uid, name) + #获取用户的种子数量 + count = await g_pDBService.userSeed.getUserSeedByName(uid, name) if count is None: - count = 0 # 如果返回 None,则视为没有种子 + count = 0 #如果返回 None,则视为没有种子 if count <= 0: return f"没有在你的仓库发现{name}种子,快去买点吧!" - # 如果播种数量超过仓库种子数量 + #如果播种数量超过仓库种子数量 if count < num and num != -1: return f"仓库中的{name}种子数量不足,当前剩余{count}个种子" - # 获取用户土地数量 - soilNumber = await g_pSqlManager.getUserSoilByUid(uid) + #获取用户土地数量 + soilNumber = await g_pDBService.user.getUserSoilByUid(uid) - # 如果播种数量为 -1,表示播种所有可播种的土地 + #如果播种数量为 -1,表示播种所有可播种的土地 if num == -1: num = count - # 记录是否成功播种 + #发送播种前信号 + await g_pEventManager.m_beforePlant.emit(uid=uid, name=name, num=num) + + #记录是否成功播种 successCount = 0 for i in range(1, soilNumber + 1): if count > 0 and num > 0: - soilName = f"soil{i}" - success, message = await g_pSqlManager.getUserSoilStatusBySoilID(uid, soilName) + success = await g_pDBService.userSoil.sowingByPlantName(uid, i, name) if success: - # 更新种子数量 + #更新种子数量 num -= 1 count -= 1 - # 记录种子消耗数量 + #记录种子消耗数量 successCount += 1 - # 更新数据库 - await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soilName, name) + #发送播种后信号 + await g_pEventManager.m_afterPlant.emit(uid=uid, name=name, soilIndex=i) - # 确保用户仓库数量更新 + + #确保用户仓库数量更新 if successCount > 0: - await g_pSqlManager.updateUserSeedByName(uid, name, count) + await g_pDBService.userSeed.updateUserSeedByName(uid, name, count) - # 根据播种结果给出反馈 + #根据播种结果给出反馈 if num == 0: return f"播种{name}成功!仓库剩余{count}个种子" else: return f"播种数量超出开垦土地数量,已将可播种土地成功播种{name}!仓库剩余{count}个种子" except Exception as e: - logger.warning(f"播种操作失败: {e}") + logger.warning(f"播种操作失败!", e=e) return "播种失败,请稍后重试!" @classmethod @@ -355,61 +359,66 @@ class CFarmManager: Returns: str: 返回 """ + try: + await g_pEventManager.m_beforeHarvest.emit(uid=uid) - isStealingPlant = 0 - soilUnlock = await g_pSqlManager.getUserSoilByUid(uid) + soilNumber = await g_pDBService.user.getUserSoilByUid(uid) - soilNames = [f"soil{i + 1}" for i in range(soilUnlock)] - soilStatuses = await asyncio.gather(*[ - g_pSqlManager.getUserSoilStatusBySoilID(uid, name) - for name in soilNames - ]) + harvestRecords = [] #收获日志记录 + experience = 0 #总经验值 + harvestCount = 0 #成功收获数量 - harvestRecords: List[str] = [] - experience = 0 + for i in range(1, soilNumber + 1): + #如果没有种植 + if not await g_pDBService.userSoil.isSoilPlanted(uid, i): + continue - for (soil_name, (status, info)) in zip(soilNames, soilStatuses): - if len(info) <= 0: - continue + soilInfo = await g_pDBService.userSoil.getUserSoil(uid, i) + if not soilInfo: + continue - soilInfo = info.split(',') - if int(soilInfo[3]) == 4: - continue + #如果是枯萎状态 + if soilInfo.get("wiltStatus", 1) == 1: + continue - plantId = soilInfo[0] - plantInfo = g_pJsonManager.m_pPlant['plant'][plantId] + plantInfo = g_pJsonManager.m_pPlant.get("plant", {}).get(soilInfo['plantName']) + currentTime = datetime.now() + matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) - currentTime = datetime.now() - matureTime = datetime.fromtimestamp(int(soilInfo[2])) + if currentTime >= matureTime: + number = plantInfo['harvest'] - if currentTime >= matureTime: - number = plantInfo['harvest'] + #处理偷菜扣除数量 + stealNum = g_pDBService.userSteal.getTotalStolenCount(uid, i) - #判断该土地作物是否被透过 - if len(soilInfo[4]) > 0: - stealingStatus = soilInfo[4].split('|') - for isUser in stealingStatus: - user = isUser.split('-') - number -= int(user[1]) + number -= stealNum - isStealingPlant += 1 + if number <= 0: + continue - experience += plantInfo['experience'] - harvestRecords.append(f"收获作物:{plantId},数量为:{number},经验为:{plantInfo['experience']}") + harvestCount += 1 + experience += plantInfo['experience'] - #更新数据库操作 - await g_pSqlManager.addUserPlantByUid(uid, plantId, number) - await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soil_name, "", 4) + harvestRecords.append(f"收获作物:{soilInfo['plantName']},数量为:{number},经验为:{plantInfo['experience']}") - if experience > 0: - harvestRecords.append(f"\t累计获得经验:{experience}") - exp = await g_pSqlManager.getUserExpByUid(uid) - await g_pSqlManager.UpdateUserExpByUid(uid, exp + experience) + await g_pDBService.userPlant.addUserPlantByUid(uid, soilInfo['plantName'], number) + await g_pDBService.userSoil.updateUserSoil(uid, i, "wiltStatus", 1) - if isStealingPlant <= 0: - return "没有可收获的作物哦~ 不要试图拔苗助长" - else: - return "\n".join(harvestRecords) + await g_pEventManager.m_afterHarvest.emit(uid=uid, name=soilInfo['plantName'], num=number, soilIndex=i) + + if experience > 0: + exp = await g_pDBService.user.getUserExpByUid(uid) + await g_pDBService.user.updateUserExpByUid(uid, exp + experience) + harvestRecords.append(f"\t累计获得经验:{experience}") + + if harvestCount <= 0: + return "没有可收获的作物哦~ 不要试图拔苗助长" + else: + return "\n".join(harvestRecords) + + except Exception as e: + logger.warning(f"收获操作失败!", e=e) + return "收获失败,请稍后重试!" @classmethod async def eradicate(cls, uid: str) -> str: @@ -421,28 +430,34 @@ class CFarmManager: Returns: str: 返回 """ + soilNumber = await g_pDBService.user.getUserSoilByUid(uid) - soilUnlock = await g_pSqlManager.getUserSoilByUid(uid) - - soilNames = [f"soil{i + 1}" for i in range(soilUnlock)] - soilStatuses = await asyncio.gather(*[ - g_pSqlManager.getUserSoilStatusBySoilID(uid, name) - for name in soilNames - ]) + await g_pEventManager.m_beforeEradicate.emit(uid=uid) experience = 0 - for (soil_name, (status, info)) in zip(soilNames, soilStatuses): - if info: - soilInfo = info.split(',') - if int(soilInfo[3]) == 4: - experience += 3 + for i in range(1, soilNumber + 1): + #如果没有种植 + if not await g_pDBService.userSoil.isSoilPlanted(uid, i): + continue - #批量更新数据库操作 - await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soil_name, "", 0) + soilInfo = await g_pDBService.userSoil.getUserSoil(uid, i) + if not soilInfo: + continue + + #如果不是枯萎状态 + if soilInfo.get("wiltStatus", 0) == 0: + continue + + experience += 3 + + #批量更新数据库操作 + await g_pDBService.userSoil.deleteUserSoil(uid, i) + + await g_pEventManager.m_afterEradicate.emit(uid=uid, soilIndex=i) if experience > 0: - exp = await g_pSqlManager.getUserExpByUid(uid) - await g_pSqlManager.UpdateUserExpByUid(uid, exp + experience) + exp = await g_pDBService.user.getUserExpByUid(uid) + await g_pDBService.user.updateUserExpByUid(uid, exp + experience) return f"成功铲除荒废作物,累计获得经验:{experience}" else: @@ -468,7 +483,7 @@ class CFarmManager: "是否可以上架交易行" ] - plant = await g_pSqlManager.getUserPlantByUid(uid) + plant = await g_pDBService.userPlant.getUserPlantByUid(uid) if plant is None: result = await ImageTemplate.table_page( @@ -525,121 +540,83 @@ class CFarmManager: Returns: str: 返回 """ - #用户信息 - userInfo = await g_pSqlManager.getUserInfoByUid(uid) - #用户可偷次数 - userStealing = userInfo["stealing"].split('|') + userInfo = await g_pDBService.user.getUserInfoByUid(uid) - if userStealing[0] == '': - userStealing[0] = date.today().strftime('%Y-%m-%d') - userStealing.append(5) - elif date.fromisoformat(userStealing[0]) != date.today(): - userStealing[0] = date.today().strftime('%Y-%m-%d') - userStealing[1] = 5 + stealTime = userInfo['stealTime'] + stealCount = int(userInfo['stealCount']) - if int(userStealing[1]) <= 0: + if stealTime == '': + stealTime = date.today().strftime('%Y-%m-%d') + stealCount = 5 + elif date.fromisoformat(stealTime) != date.today(): + stealTime = date.today().strftime('%Y-%m-%d') + stealCount = 5 + + if stealCount <= 0: return "你今天可偷次数到达上限啦,手下留情吧" #获取用户解锁地块数量 - soilUnlockNum = int(userInfo["soil"]) - plant = {} - - #根据解锁土地,获取每块土地状态信息 - soilNames = [f"soil{i + 1}" for i in range(soilUnlockNum)] - soilStatuses = await asyncio.gather(*[ - g_pSqlManager.getUserSoilStatusBySoilID(target, name) - for name in soilNames - ]) - - isStealing = False + soilNumber = await g_pDBService.user.getUserSoilByUid(uid) harvestRecords: List[str] = [] isStealingNumber = 0 isStealingPlant = 0 - for (soilName, (status, info)) in zip(soilNames, soilStatuses): - isStealing = False - - if not info: + for i in range(1, soilNumber + 1): + #如果没有种植 + if not await g_pDBService.userSoil.isSoilPlanted(uid, i): continue - soilInfo = info.split(',') - if int(soilInfo[3]) == 4: + soilInfo = await g_pDBService.userSoil.getUserSoil(uid, i) + if not soilInfo: + continue + + #如果是枯萎状态 + if soilInfo.get("wiltStatus", 1) == 1: continue - #作物ID - plantId = soilInfo[0] #作物信息 - plantInfo = g_pJsonManager.m_pPlant['plant'][plantId] - + plantInfo = g_pJsonManager.m_pPlant.get("plant", {}).get(soilInfo['plantName']) currentTime = datetime.now() - matureTime = datetime.fromtimestamp(int(soilInfo[2])) + matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) - #偷窃状态 - stealingStatus: list[str] = [] - #偷窃数量 - stealingNumber = 0 if currentTime >= matureTime: - if soilInfo[4]: - #先获取用户是否偷过该土地 - stealingStatus = soilInfo[4].split('|') - - for isUser in stealingStatus: - user = isUser.split('-') - - if user[0] == uid: - isStealing = True - isStealingNumber += 1 - break - - stealingNumber -= int(user[1]) - #如果偷过,则跳过该土地 - if isStealing: + if await g_pDBService.userSteal.hasStealed(target, i, uid): continue - stealingNumber += plantInfo['harvest'] + stealingNumber = plantInfo['harvest'] - await g_pDBService.userSteal.getTotalStolenCount(target, i) randomNumber = random.choice([1, 2]) randomNumber = min(randomNumber, stealingNumber) - logger.info(f"{randomNumber}") - if randomNumber > 0: - await g_pSqlManager.addUserPlantByUid(uid, plantId, randomNumber) + await g_pDBService.userPlant.addUserPlantByUid(uid, soilInfo['plantName'], randomNumber) - harvestRecords.append(f"成功偷到作物:{plantId},数量为:{randomNumber}") - stealingStatus.append(f"{uid}-{randomNumber}") + harvestRecords.append(f"成功偷到作物:{soilInfo['plantName']},数量为:{randomNumber}") isStealingPlant += 1 #如果将作物偷完,就直接更新状态 并记录用户偷取过 if plantInfo['harvest'] - randomNumber + stealingNumber == 0: - sql = f"UPDATE soil SET {soilName} = ',,,4,{'|'.join(stealingStatus)}' WHERE uid = '{target}'" + await g_pDBService.userSoil.updateUserSoil(target, i, "wiltStatus", 1) else: - sql = f"UPDATE soil SET {soilName} = '{soilInfo[0]},{soilInfo[1]},{soilInfo[2]},{soilInfo[3]},{'|'.join(stealingStatus)}' WHERE uid = '{target}'" - - await g_pSqlManager.executeDB(sql) + await g_pDBService.userSteal.addStealRecord(target, i, uid, randomNumber, int(datetime.now().timestamp())) if isStealingPlant <= 0 and isStealingNumber <= 0: return "目标没有作物可以被偷" elif isStealingPlant <= 0 and isStealingNumber > 0: return "你已经偷过目标啦,请手下留情" else: - userStealing[1] = int(userStealing[1]) - 1 - # 转换所有元素为字符串 - userStealing_str = [str(item) for item in userStealing] + stealCount -= 1 - sql = f"UPDATE user SET stealing = '{userStealing[0]}|{userStealing[1]}' WHERE uid = {uid}" - - #更新用户每日偷取次数 - await g_pSqlManager.executeDB(sql) + await g_pDBService.user.updateStealCountByUid(uid, stealCount) return "\n".join(harvestRecords) @classmethod async def reclamationCondition(cls, uid: str) -> str: - userInfo = await g_pSqlManager.getUserInfoByUid(uid) - rec = g_pJsonManager.m_pLevel["reclamation"] + userInfo = await g_pDBService.user.getUserInfoByUid(uid) + rec = g_pJsonManager.m_pLevel['reclamation'] try: if userInfo['soil'] >= 30: @@ -663,10 +640,10 @@ class CFarmManager: @classmethod async def reclamation(cls, uid: str) -> str: - userInfo = await g_pSqlManager.getUserInfoByUid(uid) - level = await g_pSqlManager.getUserLevelByUid(uid) + userInfo = await g_pDBService.user.getUserInfoByUid(uid) + level = await g_pDBService.user.getUserLevelByUid(uid) - rec = g_pJsonManager.m_pLevel["reclamation"] + rec = g_pJsonManager.m_pLevel['reclamation'] try: if userInfo['soil'] >= 30: @@ -678,18 +655,15 @@ class CFarmManager: point = rec['point'] item = rec['item'] - logger.info(f"{level[0]}") - if level[0] < levelFileter: return f"当前用户等级{level[0]},升级所需等级为{levelFileter}" if userInfo['point'] < point: return f"当前用户农场币不足,升级所需农场币为{point}" - #TODO 缺少判断需要的Item - sql = f"UPDATE user SET point = '{userInfo['point'] - point}', soil = '{userInfo['soil'] + 1}' WHERE uid = {uid}" - - await g_pSqlManager.executeDB(sql) + #TODO 缺少判断消耗的item + await g_pDBService.user.updateUserPointByUid(uid, userInfo['point'] - point) + await g_pDBService.user.updateUserSoilByUid(uid, userInfo['soil'] + 1) return "开垦土地成功!" except Exception as e: diff --git a/farm/shop.py b/farm/shop.py index c028194..bacd703 100644 --- a/farm/shop.py +++ b/farm/shop.py @@ -6,7 +6,7 @@ from zhenxun.utils._build_image import BuildImage from zhenxun.utils.image_utils import ImageTemplate from ..config import g_pJsonManager, g_sResourcePath -from ..database import g_pSqlManager +from ..dbService import g_pDBService class CShopManager: @@ -102,12 +102,12 @@ class CShopManager: except Exception as e: return "购买出错!请检查需购买的种子名称!" - level = await g_pSqlManager.getUserLevelByUid(uid) + level = await g_pDBService.user.getUserLevelByUid(uid) if level[0] < int(plantInfo['level']): return "你的等级不够哦,努努力吧" - point = await g_pSqlManager.getUserPointByUid(uid) + point = await g_pDBService.user.getUserPointByUid(uid) total = int(plantInfo['buy']) * num logger.debug(f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}") @@ -115,9 +115,9 @@ class CShopManager: if point < total: return "你的农场币不够哦~ 快速速氪金吧!" else: - await g_pSqlManager.updateUserPointByUid(uid, point - total) + await g_pDBService.user.updateUserPointByUid(uid, point - total) - if await g_pSqlManager.addUserSeedByUid(uid, name, num) == False: + if await g_pDBService.userSeed.addUserSeedByUid(uid, name, num) == False: return "购买失败,执行数据库错误!" return f"成功购买{name},花费{total}农场币, 剩余{point - total}农场币" @@ -135,7 +135,7 @@ class CShopManager: if not isinstance(name, str) or name.strip() == "": name = "" - plant = await g_pSqlManager.getUserPlantByUid(uid) + plant = await g_pDBService.userPlant.getUserPlantByUid(uid) if not plant: return "你仓库没有可以出售的作物" @@ -147,7 +147,7 @@ class CShopManager: for plantName, count in plant.items(): plantInfo = g_pJsonManager.m_pPlant['plant'][plantName] point += plantInfo['price'] * count - await g_pSqlManager.updateUserPlantByName(uid, plantName, 0) + await g_pDBService.userPlant.updateUserPlantByName(uid, plantName, 0) else: if name not in plant: return f"出售作物{name}出错:仓库中不存在该作物" @@ -155,12 +155,12 @@ class CShopManager: sellAmount = available if isAll else min(available, num) if sellAmount <= 0: return f"出售作物{name}出错:数量不足" - await g_pSqlManager.updateUserPlantByName(uid, name, available - sellAmount) + await g_pDBService.userPlant.updateUserPlantByName(uid, name, available - sellAmount) totalSold = sellAmount totalPoint = point if name == "" else totalSold * g_pJsonManager.m_pPlant['plant'][name]['price'] - currentPoint = await g_pSqlManager.getUserPointByUid(uid) - await g_pSqlManager.updateUserPointByUid(uid, currentPoint + totalPoint) + currentPoint = await g_pDBService.user.getUserPointByUid(uid) + await g_pDBService.user.updateUserPointByUid(uid, currentPoint + totalPoint) if name == "": return f"成功出售所有作物,获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}"