🐛解决退出时数据库重复关闭导致卡进程的BUG

This commit is contained in:
2025-12-02 11:06:13 +08:00
parent a6691595e5
commit 78762eceae
8 changed files with 192 additions and 28 deletions
+7
View File
@@ -57,6 +57,13 @@ __plugin_meta__ = PluginMetadata(
help="我的农场返回图片的清晰度, [low, medium, hight, original]", help="我的农场返回图片的清晰度, [low, medium, hight, original]",
default_value="low", default_value="low",
), ),
RegisterConfig(
key="动态背景",
value=True,
help="是否开启根据季节和节日变化的动态背景",
type=bool,
default_value=True,
),
RegisterConfig( RegisterConfig(
key="签到图片样式", key="签到图片样式",
value="text", value="text",
+28 -5
View File
@@ -35,10 +35,10 @@ class CSqlManager(ABC):
if cls.m_pDB is None: if cls.m_pDB is None:
cls.m_pDB = await aiosqlite.connect(config.g_sDBFilePath) cls.m_pDB = await aiosqlite.connect(config.g_sDBFilePath)
cls.m_pDB.row_factory = aiosqlite.Row cls.m_pDB.row_factory = aiosqlite.Row
logger.info("真寻农场数据库连接初始化成功") logger.debug("真寻农场数据库连接初始化成功")
return True return True
else: else:
logger.info("真寻农场数据库连接已存在,跳过初始化") logger.debug("真寻农场数据库连接已存在,跳过初始化")
return True return True
except Exception as e: except Exception as e:
logger.error(f"真寻农场数据库连接初始化失败: {e}") logger.error(f"真寻农场数据库连接初始化失败: {e}")
@@ -46,10 +46,33 @@ class CSqlManager(ABC):
@classmethod @classmethod
async def cleanup(cls): async def cleanup(cls):
if cls.m_pDB: try:
await cls.m_pDB.close() if not cls.isConnectionValid():
logger.warning("真寻农场数据库连接已关闭或无效,跳过清理")
return
if hasattr(cls, "m_pDB") and cls.m_pDB:
await cls.m_pDB.close()
cls.m_pDB = None
cls._initialized = False
logger.debug("真寻农场数据库连接已安全关闭")
else:
logger.debug("真寻农场数据库连接已不存在,无需关闭")
except Exception as e:
logger.error(f"真寻农场数据库清理过程中出错: {e}")
cls.m_pDB = None cls.m_pDB = None
logger.info("真寻农场数据库连接已关闭") cls._initialized = False
@classmethod
def isConnectionValid(cls) -> bool:
if not hasattr(cls, "m_pDB") or cls.m_pDB is None:
return False
try:
return hasattr(cls.m_pDB, "_conn") and cls.m_pDB._conn is not None
except:
return False
@classmethod @classmethod
def getDB(cls) -> aiosqlite.Connection: def getDB(cls) -> aiosqlite.Connection:
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

+4
View File
@@ -1,3 +1,6 @@
from . import utils
def getConfig(): def getConfig():
from . import config from . import config
@@ -34,4 +37,5 @@ __all__ = [
"getJsonManager", "getJsonManager",
"getRequestManager", "getRequestManager",
"getToolManager", "getToolManager",
"utils",
] ]
+88 -23
View File
@@ -4,8 +4,9 @@ from zhenxun.utils._build_image import BuildImage
from zhenxun.utils.image_utils import ImageTemplate from zhenxun.utils.image_utils import ImageTemplate
from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.platform import PlatformUtils
from ...core import CPlayer
from ...core.dbService import g_pDBService from ...core.dbService import g_pDBService
from .. import config, getJsonManager, getToolManager from .. import config, getJsonManager, getToolManager, utils
class CDarwFarm: class CDarwFarm:
@@ -19,7 +20,9 @@ class CDarwFarm:
Returns: Returns:
bytes: 返回绘制结果 bytes: 返回绘制结果
""" """
img, soilSize, soilPos, grass = await cls.createBaseCanvas() dynamic = Config.get_config("zhenxun_plugin_farm", "动态背景")
img, soilSize, soilPos, grass = await cls.createBaseCanvas(dynamic)
player = await getToolManager().getPlayerByUid(uid) player = await getToolManager().getPlayerByUid(uid)
if not player: if not player:
@@ -48,20 +51,44 @@ class CDarwFarm:
isFirstExpansion = False isFirstExpansion = False
await cls.drawUserInfo(img, player, uid) await cls.drawUserInfo(img, player, uid)
await cls.applyDefinitionResize(img) await cls.applyDefinitionResize(img)
return img.pic2bytes() return img.pic2bytes()
@classmethod @classmethod
async def createBaseCanvas(cls) -> tuple[BuildImage, tuple, dict, BuildImage]: async def createBaseCanvas(
cls, dynamic: bool
) -> tuple[BuildImage, tuple, dict, BuildImage]:
"""创建基础画布并返回 (img, soilSize, soilPos, grass) """创建基础画布并返回 (img, soilSize, soilPos, grass)
Returns: Returns:
tuple: (主画布, 土地尺寸, 土地坐标映射, 草地图) tuple: (主画布, 土地尺寸, 土地坐标映射, 草地图)
""" """
img = BuildImage(
background=config.g_sResourcePath / "background/background.jpg" if dynamic:
) season = getToolManager().getSeason()
backgroundPath = config.g_sResourcePath / "background/decorateIcon"
# 节日优先级更高
if season == utils.Season.SPRING:
bgFile = backgroundPath / "decorate_spring_1.jpg"
elif season == utils.Season.SUMMER:
bgFile = backgroundPath / "decorate_summer_1.jpg"
elif season == utils.Season.AUTUMN:
bgFile = backgroundPath / "decorate_autumn_1.jpg"
elif season == utils.Season.WINTER:
bgFile = backgroundPath / "decorate_winter_1.jpg"
else:
bgFile = backgroundPath / "decorate_default_1.jpg"
img = BuildImage(background=bgFile)
else:
img = BuildImage(
background=config.g_sResourcePath
/ "background/decorateIcon/decorate_default_1jpg"
)
soil = await getJsonManager().getSoil() soil = await getJsonManager().getSoil()
soilSize = soil["size"] soilSize = soil["size"]
@@ -76,17 +103,17 @@ class CDarwFarm:
async def drawUnlockedTile( async def drawUnlockedTile(
cls, img: BuildImage, uid: str, soilIndex: int, x: int, y: int, soilSize cls, img: BuildImage, uid: str, soilIndex: int, x: int, y: int, soilSize
) -> bool: ) -> bool:
"""绘制已解锁土地格(包含土地贴图与作物),返回该格是否可收获。 """绘制已解锁土地格(包含土地贴图与作物),返回该格是否可收获
Args: Args:
img (BuildImage): 主画布。 img (BuildImage): 主画布
uid (str): 用户UID。 uid (str): 用户UID
soilIndex (int): 土地索引(从1开始)。 soilIndex (int): 土地索引(从1开始)
x (int), y (int): 贴图位置。 x (int), y (int): 贴图位置
soilSize: 土地贴图尺寸([w,h] 或 (w,h))。 soilSize: 土地贴图尺寸([w,h] 或 (w,h))
Returns: Returns:
bool: 是否有可收获作物(isRipe)。 bool: 是否有可收获作物(isRipe)
""" """
soilUrl = "soil/普通土地.png" soilUrl = "soil/普通土地.png"
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex) soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex)
@@ -128,10 +155,10 @@ class CDarwFarm:
grass: BuildImage, grass: BuildImage,
isFirstExpansion: bool, isFirstExpansion: bool,
) -> bool: ) -> bool:
"""绘制未解锁格子并在首次出现时绘制扩建图标。 """绘制未解锁格子并在首次出现时绘制扩建图标
Returns: Returns:
bool: 是否在此格绘制了扩建图标(仅首次返回 True)。 bool: 是否在此格绘制了扩建图标(仅首次返回 True)
""" """
await img.paste(grass, (x, y)) await img.paste(grass, (x, y))
@@ -153,7 +180,7 @@ class CDarwFarm:
@classmethod @classmethod
async def pasteRipeOnce(cls, img: BuildImage, x: int, y: int, soilSize): async def pasteRipeOnce(cls, img: BuildImage, x: int, y: int, soilSize):
"""首次发现成熟作物时贴上成熟提示图(ripe)。""" """首次发现成熟作物时贴上成熟提示图(ripe)"""
ripe = BuildImage(background=config.g_sResourcePath / "background/ripe.png") ripe = BuildImage(background=config.g_sResourcePath / "background/ripe.png")
await img.paste( await img.paste(
ripe, ripe,
@@ -161,22 +188,32 @@ class CDarwFarm:
) )
@classmethod @classmethod
async def drawUserInfo(cls, img: BuildImage, player, uid: str): async def drawUserInfo(cls, img: BuildImage, player: CPlayer, uid: str):
"""绘制左上角用户信息(头像、框、昵称、经验、等级、货币等)。 """绘制左上角用户信息(头像、框、昵称、经验、等级、货币等)
TODO 需要详细的绘制头像框 TODO 需要详细的绘制头像框
Args: Args:
img (BuildImage): 主画布。 img (BuildImage): 主画布
player: 用户对象(包含 user 字段)。 player(CPlayer): 用户
uid (str): 用户UID。 uid (str): 用户Uid
""" """
# 背景板
background = BuildImage(
background=config.g_sResourcePath / "background/userInfo.png"
)
await img.paste(background, (150, 30))
# 头像
image = await PlatformUtils.get_user_avatar(uid, "qq") image = await PlatformUtils.get_user_avatar(uid, "qq")
if image: if image:
avatar = BuildImage(background=image) avatar = BuildImage(background=image)
await avatar.resize(0, 140, 150) await avatar.resize(0, 140, 150)
await img.paste(avatar, (125, 85)) await img.paste(avatar, (125, 85))
frame = BuildImage(background=config.g_sResourcePath / "background/frame.png") # 头像框
frame = BuildImage(
background=config.g_sResourcePath / "background/frame/frame.png"
)
await img.paste(frame, (75, 44)) await img.paste(frame, (75, 44))
nameImg = await BuildImage.build_text_image( nameImg = await BuildImage.build_text_image(
@@ -214,9 +251,37 @@ class CDarwFarm:
) )
await img.paste(bondsImg, (570, 255)) await img.paste(bondsImg, (570, 255))
@classmethod
async def drawUserDecorateIcon(cls, img: BuildImage, player: CPlayer) -> bool:
"""绘制用户装饰图标
Args:
img (BuildImage): 主画布
player (CPlayer): 用户
Returns:
bool: 是否成功绘制
"""
decorateIconId = player.user.get("decorateIcon", 0)
if decorateIconId <= 0:
return False
iconPath = (
config.g_sResourcePath / f"decorateIcon/DecorateIcon{decorateIconId}.png"
)
if not iconPath.exists():
return False
icon = BuildImage(background=iconPath)
await icon.resize(0, 100, 100)
await img.paste(icon, (x, y))
return True
@classmethod @classmethod
async def applyDefinitionResize(cls, img: BuildImage): async def applyDefinitionResize(cls, img: BuildImage):
"""根据配置调整绘制清晰度(original/medium/hight/其他)。""" """根据配置调整绘制清晰度(original/medium/hight/其他)"""
definition = Config.get_config("zhenxun_plugin_farm", "绘制农场清晰度") definition = Config.get_config("zhenxun_plugin_farm", "绘制农场清晰度")
if definition == "medium": if definition == "medium":
await img.resize(0.6) await img.resize(0.6)
+43
View File
@@ -5,11 +5,13 @@ from zoneinfo import ZoneInfo
from jinja2 import Template from jinja2 import Template
from playwright.async_api import async_playwright from playwright.async_api import async_playwright
from zhdate import ZhDate
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils.message import MessageUtils from zhenxun.utils.message import MessageUtils
from ..core import CPlayer, g_pUserPool from ..core import CPlayer, g_pUserPool
from . import utils
class CToolManager: class CToolManager:
@@ -110,6 +112,47 @@ class CToolManager:
tz = ZoneInfo("Asia/Shanghai") tz = ZoneInfo("Asia/Shanghai")
return datetime.now(tz) return datetime.now(tz)
@classmethod
def getSeason(cls) -> utils.Season:
month = cls.dateTime().month
if month in [3, 4, 5]:
return utils.Season.SPRING
elif month in [6, 7, 8]:
return utils.Season.SUMMER
elif month in [9, 10, 11]:
return utils.Season.AUTUMN
elif month in [12, 1, 2]:
return utils.Season.WINTER
else:
return utils.Season.NO
@classmethod
def getFestival(cls) -> utils.Festival:
now = cls.dateTime()
lunarDate = ZhDate.from_datetime(now)
lunarMonth = lunarDate.lunar_month
lunarDay = lunarDate.lunar_day
festivalChecks = [
(1, lambda: lunarMonth == 8 and lunarDay == 15), # 中秋节
(2, lambda: (now.month, now.day) == (10, 1)), # 国庆节
(3, lambda: lunarMonth == 1 and lunarDay == 15), # 元宵节
(4, lambda: lunarMonth == 5 and lunarDay == 5), # 端午节
(5, lambda: lunarMonth == 1 and lunarDay == 1), # 春节
(6, lambda: (now.month, now.day) == (10, 31)), # 万圣节
(7, lambda: (now.month, now.day) == (12, 25)), # 圣诞节
(8, lambda: (now.month, now.day) == (1, 1)), # 新年
(9, lambda: (now.month, now.day) == (2, 14)), # 情人节
]
for code, checkFunc in festivalChecks:
if checkFunc():
return utils.Festival(code)
return utils.Festival.NO
@classmethod @classmethod
def renderHtmlToFile( def renderHtmlToFile(
cls, path: Path | str, context: dict, output: Path | str cls, path: Path | str, context: dict, output: Path | str
+22
View File
@@ -0,0 +1,22 @@
from enum import Enum
class Festival(Enum):
NO = 0 # 无节日
MIDAUTUMN = 1 # 中秋节
NATIONALDAY = 2 # 国庆节
LANTERNFESTIVAL = 3 # 元宵节
DRAGONBOAT = 4 # 端午节
SPRINGFESTIVAL = 5 # 春节
HALLOWEEN = 6 # 万圣节
CHRISTMAS = 7 # 圣诞节
NEWYEAR = 8 # 新年
VALENTINE = 9 # 情人节
class Season(Enum):
NO = 0 # 无季节
SPRING = 1 # 春季
SUMMER = 2 # 夏季
AUTUMN = 3 # 秋季
WINTER = 4 # 冬季