🎨 将plant.json迁移至sqlite本地数据库

📝 新增部分作物
This commit is contained in:
Art_Sakura 2025-05-08 17:56:45 +08:00
parent 0cd9b9d8db
commit 55394e3590
36 changed files with 249 additions and 330 deletions

View File

@ -86,3 +86,5 @@ async def start():
@driver.on_shutdown @driver.on_shutdown
async def shutdown(): async def shutdown():
await g_pSqlManager.cleanup() await g_pSqlManager.cleanup()
await g_pDBService.cleanup()

View File

@ -6,3 +6,4 @@ g_sDBPath = DATA_PATH / "farm_db"
g_sDBFilePath = DATA_PATH / "farm_db/farm.db" g_sDBFilePath = DATA_PATH / "farm_db/farm.db"
g_sResourcePath = Path(__file__).resolve().parent / "resource" g_sResourcePath = Path(__file__).resolve().parent / "resource"
g_sPlantPath = g_sResourcePath / "db/plant.db"

View File

@ -1,276 +0,0 @@
{
"version": 0.01,
"zhuShi":
[
"level: 解锁等级",
"buy: 购买价格",
"limit: 限制等级 0普通土地 1红土地 2黄土地 3黑土地",
"experience: 收获经验",
"harvest: 收获数量",
"price: 果实售价",
"time: 成熟时间 小时",
"crop: 作物可以收几次",
"again: 再次成熟时间 单位:小时",
"phase: 阶段 目前为 成熟时间 / 阶段 来显示每阶段图片",
"general: 第一阶段是否为通用阶段素材",
"sell: 是否可以上架交易行"
],
"plant":
{
"胡萝卜":
{
"level": 0,
"buy": 163,
"limit": 0,
"experience": 18,
"harvest": 17,
"price": 21,
"time": 13,
"crop": 1,
"again": 0,
"phase": 5,
"general": true,
"sell": false
},
"白萝卜":
{
"level": 0,
"buy": 125,
"limit": 0,
"experience": 15,
"harvest": 16,
"price": 17,
"time": 10,
"crop": 1,
"again": 0,
"phase": 5,
"general": true,
"sell": false
},
"牧草":
{
"level": 0,
"buy": 80,
"limit": 0,
"experience": 10,
"harvest": 25,
"price": 6,
"time": 8,
"crop": 1,
"again": 0,
"phase": 5,
"general": true,
"sell": false
},
"大白菜":
{
"level": 1,
"buy": 168,
"limit": 0,
"experience": 19,
"harvest": 17,
"price": 22,
"time": 14,
"crop": 1,
"again": 0,
"phase": 5,
"general": true,
"sell": false
},
"大蒜":
{
"level": 1,
"buy": 169,
"limit": 0,
"experience": 19,
"harvest": 17,
"price": 22,
"time": 14,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"水稻":
{
"level": 2,
"buy": 168,
"limit": 0,
"experience": 19,
"harvest": 18,
"price": 21,
"time": 14,
"crop": 1,
"again": 0,
"phase": 5,
"general": false,
"sell": false
},
"小麦":
{
"level": 2,
"buy": 168,
"limit": 0,
"experience": 19,
"harvest": 18,
"price": 21,
"time": 14,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"玉米":
{
"level": 3,
"buy": 175,
"limit": 0,
"experience": 19,
"harvest": 17,
"price": 23,
"time": 14,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"油菜":
{
"level": 4,
"buy": 194,
"limit": 0,
"experience": 29,
"harvest": 23,
"price": 24,
"time": 17,
"crop": 1,
"again": 0,
"phase": 5,
"general": true,
"sell": false
},
"生菜":
{
"level": 4,
"buy": 195,
"limit": 0,
"experience": 25,
"harvest": 21,
"price": 24,
"time": 19,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"小白菜":
{
"level": 4,
"buy": 195,
"limit": 0,
"experience": 15,
"harvest": 18,
"price": 21,
"time": 11,
"crop": 1,
"again": 0,
"phase": 5,
"general": true,
"sell": false
},
"红枣":
{
"level": 5,
"buy": 237,
"limit": 0,
"experience": 21,
"harvest": 20,
"price": 25,
"time": 16,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"茄子":
{
"level": 5,
"buy": 237,
"limit": 0,
"experience": 21,
"harvest": 20,
"price": 25,
"time": 16,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"番茄":
{
"level": 6,
"buy": 251,
"limit": 0,
"experience": 22,
"harvest": 21,
"price": 26,
"time": 17,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"ATTomato":
{
"level": 6,
"buy": 99999,
"limit": 0,
"experience": 22,
"harvest": 21,
"price": 7000,
"time": 17,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"红玫瑰":
{
"level": 7,
"buy": 251,
"limit": 0,
"experience": 23,
"harvest": 22,
"price": 27,
"time": 18,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"豌豆":
{
"level": 7,
"buy": 266,
"limit": 0,
"experience": 23,
"harvest": 22,
"price": 27,
"time": 18,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
}
}
}

View File

@ -1,4 +1,31 @@
{ {
"date": "202505", "date": "202505",
"" "point_max": 5000,
"point_min": 200,
"continuou":
{
"1":
{
"point": 5000,
"exp": 20
},
"3":
{
"point": 7000,
"exp": 25,
"plant":
{
"胡萝卜": 3
}
},
"5":
{
"point": 9000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
}
}
} }

View File

@ -12,21 +12,26 @@ from ..config import g_sDBFilePath, g_sDBPath
class CSqlManager: class CSqlManager:
def __init__(self): def __init__(self):
g_sDBPath.mkdir(parents=True, exist_ok=True) try:
os.mkdir(g_sDBFilePath)
except FileExistsError:
pass
@classmethod @classmethod
async def cleanup(cls): async def cleanup(cls):
if cls.m_pDB: if hasattr(cls, "m_pDB") and cls.m_pDB:
await cls.m_pDB.close() await cls.m_pDB.close()
@classmethod @classmethod
async def init(cls) -> bool: async def init(cls) -> bool:
bIsExist = os.path.exists(g_sDBFilePath) try:
_ = os.path.exists(g_sDBFilePath)
cls.m_pDB = await aiosqlite.connect(g_sDBFilePath) cls.m_pDB = await aiosqlite.connect(str(g_sDBFilePath))
cls.m_pDB.row_factory = aiosqlite.Row cls.m_pDB.row_factory = aiosqlite.Row
return True
return True except Exception as e:
logger.warning("初始化总数据库失败", e=e)
return False
@classmethod @classmethod
@asynccontextmanager @asynccontextmanager
@ -115,7 +120,7 @@ class CSqlManager:
Returns: Returns:
bool: 是否执行成功 bool: 是否执行成功
""" """
if len(command) <= 0: if not command:
logger.warning("数据库语句长度为空!") logger.warning("数据库语句长度为空!")
return False return False
@ -124,7 +129,7 @@ class CSqlManager:
await cls.m_pDB.execute(command) await cls.m_pDB.execute(command)
return True return True
except Exception as e: except Exception as e:
logger.warning("数据库语句执行出错:" + command, e=e) logger.warning(f"数据库语句执行出错: {command}", e=e)
return False return False
g_pSqlManager = CSqlManager() g_pSqlManager = CSqlManager()

143
database/plant.py Normal file
View File

@ -0,0 +1,143 @@
import os
import re
from contextlib import asynccontextmanager
import aiosqlite
from zhenxun.services.log import logger
from ..config import g_sPlantPath
class CPlantManager:
def __init__(self):
try:
os.mkdir(g_sPlantPath)
except FileExistsError:
pass
@classmethod
async def cleanup(cls):
if hasattr(cls, "m_pDB") and cls.m_pDB:
await cls.m_pDB.close()
@classmethod
async def init(cls) -> bool:
try:
_ = os.path.exists(g_sPlantPath)
cls.m_pDB = await aiosqlite.connect(str(g_sPlantPath))
cls.m_pDB.row_factory = aiosqlite.Row
return True
except Exception as e:
logger.warning("初始化植物数据库失败", e=e)
return False
@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 executeDB(cls, command: str) -> bool:
"""执行自定义SQL
Args:
command (str): SQL语句
Returns:
bool: 是否执行成功
"""
if not command:
logger.warning("数据库语句长度为空!")
return False
try:
async with cls._transaction():
await cls.m_pDB.execute(command)
return True
except Exception as e:
logger.warning(f"数据库语句执行出错: {command}", e=e)
return False
@classmethod
async def getPlantByName(cls, name: str) -> dict | None:
"""根据作物名称查询记录
Args:
name (str): 作物名称
Returns:
dict | None: 返回记录字典未找到返回None
"""
try:
async with cls.m_pDB.execute(
"SELECT * FROM plant WHERE name = ?", (name,)
) as cursor:
row = await cursor.fetchone()
return dict(row) if row else None
except Exception as e:
logger.warning(f"查询作物失败: {name}", e=e)
return None
@classmethod
async def existsPlant(cls, name: str) -> bool:
"""判断作物是否存在
Args:
name (str): 作物名称
Returns:
bool: 存在返回True否则False
"""
try:
async with cls.m_pDB.execute(
"SELECT 1 FROM plant WHERE name = ? LIMIT 1", (name,)
) as cursor:
row = await cursor.fetchone()
return True if row else False
except Exception as e:
logger.warning(f"检查作物存在性失败: {name}", e=e)
return False
@classmethod
async def countPlants(cls, onlyBuy: bool = False) -> int:
"""获取作物总数
Args:
onlyBuy (bool): 若为True仅统计isBuy=1的记录默认False
Returns:
int: 符合条件的记录数
"""
try:
if onlyBuy:
sql = "SELECT COUNT(*) FROM plant WHERE isBuy = 1"
params: tuple = ()
else:
sql = "SELECT COUNT(*) FROM plant"
params: tuple = ()
async with cls.m_pDB.execute(sql, params) as cursor:
row = await cursor.fetchone()
return row[0] if row else 0
except Exception as e:
logger.warning(f"统计作物数量失败, onlyBuy={onlyBuy}", e=e)
return 0
@classmethod
async def listPlants(cls) -> list[dict]:
"""查询所有作物记录"""
try:
async with cls.m_pDB.execute("SELECT * FROM plant") as cursor:
rows = await cursor.fetchall()
return [dict(r) for r in rows]
except Exception as e:
logger.warning("查询所有作物失败", e=e)
return []

View File

@ -295,7 +295,7 @@ class CUserSoilDB(CSqlManager):
return False return False
# 获取植物配置 # 获取植物配置
plantCfg = g_pJsonManager.m_pPlant.get("plant", {}).get(plantName) plantCfg = await g_pDBService.plant.getPlantByName(plantName)
if not plantCfg: if not plantCfg:
logger.error(f"未知植物: {plantName}") logger.error(f"未知植物: {plantName}")
return False return False

View File

@ -4,6 +4,7 @@ from typing import Optional
class CDBService: class CDBService:
@classmethod @classmethod
async def init(cls): async def init(cls):
from .database.plant import CPlantManager
from .database.user import CUserDB from .database.user import CUserDB
from .database.userItem import CUserItemDB from .database.userItem import CUserItemDB
from .database.userPlant import CUserPlantDB from .database.userPlant import CUserPlantDB
@ -11,6 +12,9 @@ class CDBService:
from .database.userSoil import CUserSoilDB from .database.userSoil import CUserSoilDB
from .database.userSteal import CUserStealDB from .database.userSteal import CUserStealDB
cls.plant = CPlantManager()
await cls.plant.init()
cls.user = CUserDB() cls.user = CUserDB()
await cls.user.initDB() await cls.user.initDB()
@ -32,4 +36,8 @@ class CDBService:
#迁移旧数据库 #迁移旧数据库
await cls.userSoil.migrateOldFarmData() await cls.userSoil.migrateOldFarmData()
@classmethod
async def cleanup(cls):
await cls.plant.cleanup()
g_pDBService = CDBService() g_pDBService = CDBService()

View File

@ -195,7 +195,10 @@ class CFarmManager:
return True, plant, False return True, plant, False
#获取作物详细信息 #获取作物详细信息
plantInfo = g_pJsonManager.m_pPlant['plant'][soilInfo['plantName']] plantInfo = await g_pDBService.plant.getPlantByName(soilInfo['plantName'])
if not plantInfo:
logger.error(f"绘制植物资源失败: {soilInfo['plantName']}")
return False, None, False #type: ignore
currentTime = datetime.now() currentTime = datetime.now()
matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) matureTime = datetime.fromtimestamp(int(soilInfo['matureTime']))
@ -257,7 +260,10 @@ class CFarmManager:
for seedName, count in seedRecords.items(): for seedName, count in seedRecords.items():
try: try:
plantInfo = g_pJsonManager.m_pPlant['plant'][seedName] plantInfo = await g_pDBService.plant.getPlantByName(seedName)
if not plantInfo:
continue
iconPath = g_sResourcePath / f"plant/{seedName}/icon.png" iconPath = g_sResourcePath / f"plant/{seedName}/icon.png"
icon = (iconPath, 33, 33) if iconPath.exists() else "" icon = (iconPath, 33, 33) if iconPath.exists() else ""
sellable = "可以" if plantInfo['again'] else "不可以" sellable = "可以" if plantInfo['again'] else "不可以"
@ -382,7 +388,10 @@ class CFarmManager:
if soilInfo.get("wiltStatus", 1) == 1: if soilInfo.get("wiltStatus", 1) == 1:
continue continue
plantInfo = g_pJsonManager.m_pPlant.get("plant", {}).get(soilInfo['plantName']) plantInfo = await g_pDBService.plant.getPlantByName(soilInfo['plantName'])
if not plantInfo:
continue
currentTime = datetime.now() currentTime = datetime.now()
matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) matureTime = datetime.fromtimestamp(int(soilInfo['matureTime']))
@ -497,7 +506,10 @@ class CFarmManager:
sell = "" sell = ""
for name, count in plant.items(): for name, count in plant.items():
plantInfo = g_pJsonManager.m_pPlant['plant'][name] plantInfo = await g_pDBService.plant.getPlantByName(name)
if not plantInfo:
continue
icon = "" icon = ""
icon_path = g_sResourcePath / f"plant/{name}/icon.png" icon_path = g_sResourcePath / f"plant/{name}/icon.png"
if icon_path.exists(): if icon_path.exists():
@ -577,7 +589,10 @@ class CFarmManager:
continue continue
#作物信息 #作物信息
plantInfo = g_pJsonManager.m_pPlant.get("plant", {}).get(soilInfo['plantName']) plantInfo = await g_pDBService.plant.getPlantByName(soilInfo['plantName'])
if not plantInfo:
continue
currentTime = datetime.now() currentTime = datetime.now()
matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) matureTime = datetime.fromtimestamp(int(soilInfo['matureTime']))

View File

@ -1,4 +1,6 @@
import math import math
import plistlib
from itertools import islice
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils._build_image import BuildImage from zhenxun.utils._build_image import BuildImage
@ -35,14 +37,18 @@ class CShopManager:
] ]
sell = "" sell = ""
plants = list(g_pJsonManager.m_pPlant['plant'].items()) plants = await g_pDBService.plant.listPlants()
start = (num - 1) * 15 plantSize = await g_pDBService.plant.countPlants(True)
maxItems = min(len(plants) - start, 15)
items = plants[start:start + maxItems] start = (num - 1) * 15
items = islice(plants, start, start + 15)
for plant in items:
if plant['isBuy'] == 0:
continue
for key, plant in items:
icon = "" icon = ""
icon_path = g_sResourcePath / f"plant/{key}/icon.png" icon_path = g_sResourcePath / f"plant/{plant['name']}/icon.png"
if icon_path.exists(): if icon_path.exists():
icon = (icon_path, 33, 33) icon = (icon_path, 33, 33)
@ -54,7 +60,7 @@ class CShopManager:
data_list.append( data_list.append(
[ [
icon, icon,
key, plant['name'],
plant['buy'], plant['buy'],
plant['level'], plant['level'],
plant['price'], plant['price'],
@ -67,7 +73,7 @@ class CShopManager:
] ]
) )
count = math.ceil(len(g_pJsonManager.m_pPlant['plant']) / 15) count = math.ceil(plantSize / 15)
title = f"种子商店 页数: {num}/{count}" title = f"种子商店 页数: {num}/{count}"
result = await ImageTemplate.table_page( result = await ImageTemplate.table_page(
@ -95,11 +101,8 @@ class CShopManager:
if num <= 0: if num <= 0:
return "请输入购买数量!" return "请输入购买数量!"
plantInfo = None plantInfo = await g_pDBService.plant.getPlantByName(name)
if not plantInfo:
try:
plantInfo = g_pJsonManager.m_pPlant['plant'][name]
except Exception as e:
return "购买出错!请检查需购买的种子名称!" return "购买出错!请检查需购买的种子名称!"
level = await g_pDBService.user.getUserLevelByUid(uid) level = await g_pDBService.user.getUserLevelByUid(uid)
@ -145,7 +148,10 @@ class CShopManager:
if name == "": if name == "":
for plantName, count in plant.items(): for plantName, count in plant.items():
plantInfo = g_pJsonManager.m_pPlant['plant'][plantName] plantInfo = await g_pDBService.plant.getPlantByName(plantName)
if not plantInfo:
continue
point += plantInfo['price'] * count point += plantInfo['price'] * count
await g_pDBService.userPlant.updateUserPlantByName(uid, plantName, 0) await g_pDBService.userPlant.updateUserPlantByName(uid, plantName, 0)
else: else:
@ -158,7 +164,17 @@ class CShopManager:
await g_pDBService.userPlant.updateUserPlantByName(uid, name, available - sellAmount) await g_pDBService.userPlant.updateUserPlantByName(uid, name, available - sellAmount)
totalSold = sellAmount totalSold = sellAmount
totalPoint = point if name == "" else totalSold * g_pJsonManager.m_pPlant['plant'][name]['price'] if name == "":
totalPoint = point
else:
plantInfo = await g_pDBService.plant.getPlantByName(name)
if not plantInfo:
price = 0
else:
price = plantInfo['price']
totalPoint = totalSold * price
currentPoint = await g_pDBService.user.getUserPointByUid(uid) currentPoint = await g_pDBService.user.getUserPointByUid(uid)
await g_pDBService.user.updateUserPointByUid(uid, currentPoint + totalPoint) await g_pDBService.user.updateUserPointByUid(uid, currentPoint + totalPoint)

22
json.py
View File

@ -7,7 +7,6 @@ from zhenxun.services.log import logger
class CJsonManager: class CJsonManager:
def __init__(self): def __init__(self):
self.m_pItem = {} self.m_pItem = {}
self.m_pPlant = {}
self.m_pLevel = {} self.m_pLevel = {}
self.m_pSoil = {} self.m_pSoil = {}
@ -15,9 +14,6 @@ class CJsonManager:
if not await self.initItem(): if not await self.initItem():
return False return False
if not await self.initPlant():
return False
if not await self.initLevel(): if not await self.initLevel():
return False return False
@ -44,24 +40,6 @@ class CJsonManager:
logger.warning(f"item.json JSON格式错误: {e}") logger.warning(f"item.json JSON格式错误: {e}")
return False return False
async def initPlant(self) -> bool:
current_file_path = Path(__file__)
try:
with open(
current_file_path.resolve().parent / "config/plant.json",
encoding="utf-8",
) as file:
self.m_pPlant = json.load(file)
return True
except FileNotFoundError:
logger.warning("plant.json 打开失败")
return False
except json.JSONDecodeError as e:
logger.warning(f"plant.json JSON格式错误: {e}")
return False
async def initLevel(self) -> bool: async def initLevel(self) -> bool:
current_file_path = Path(__file__) current_file_path = Path(__file__)

BIN
resource/db/plant.db Normal file

Binary file not shown.

BIN
resource/plant/土豆/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
resource/plant/土豆/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
resource/plant/土豆/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
resource/plant/土豆/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
resource/plant/土豆/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
resource/plant/花菜/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
resource/plant/花菜/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
resource/plant/花菜/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
resource/plant/花菜/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
resource/plant/花菜/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
resource/plant/莲藕/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
resource/plant/莲藕/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
resource/plant/莲藕/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
resource/plant/莲藕/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
resource/plant/莲藕/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
resource/plant/韭菜/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

BIN
resource/plant/韭菜/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
resource/plant/韭菜/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
resource/plant/韭菜/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
resource/plant/韭菜/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB