6 Commits
Author SHA1 Message Date
Diuse b5334dfc01 ✨ 完成对数据库的翻新 2025-04-27 17:00:58 +08:00
Diuse 72baa5d095 Merge remote-tracking branch 'pr/main' 2025-04-27 10:56:39 +08:00
Diuse 719494e4e5 ✨ 更新数据库逻辑 2025-04-27 10:52:28 +08:00
quanquan 5d3d8a4ef1 Update command.py 2025-04-26 10:29:59 +08:00
quanquan b72190cc45 Update command.py 2025-04-26 10:25:06 +08:00
Diuse 4cd0302426 📝 开始重构数据库 2025-04-22 19:42:09 +08:00
29 changed files with 1114 additions and 536 deletions
+19 -9
View File
@@ -6,7 +6,7 @@
## 如何安装 ## 如何安装
方法一(推荐):在小真寻后台的插件商店下载即可。 方法一(推荐):在小真寻后台的插件商店下载即可<br>
方法二:下载源码放在小真寻`plugin`目录下 方法二:下载源码放在小真寻`plugin`目录下
## 使用指令 ## 使用指令
@@ -22,22 +22,32 @@
| 收获 | 收获成熟作物 | | | 收获 | 收获成熟作物 | |
| 铲除 | 铲除荒废作物 | | | 铲除 | 铲除荒废作物 | |
| 我的作物 | | | | 我的作物 | | |
| 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 作物不填默认清空仓库 数量不填默认全部 | | 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售 |
| @美波理 偷菜 | 偷别人的菜 | 每人每天只能偷5次 | | @美波理 偷菜 | 偷别人的菜 | 每人每天只能偷5次 |
| 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为1:2 手续费默认20% | | 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为1:2 手续费默认20% |
--- ---
## 更新日志[详细](./log/log.md):
- 感谢[quanquan1014](https://github.com/quanquan1014)对农场名称中包含特殊字符的处理。
- 更正数据库写法,但是会导致V1.0用户的作物和种子丢失
- 完善我的农场图片资源,现在会在左上角显示经验、等级、金币等详细信息了
- 完善出售作物逻辑,现在可以空置作物名称来一键出售全部作物了,也可以选择空置数量来一键出售仓库种指定作物
- 完善播种逻辑,现在可以空置数量来一键播种指定作物了
- 新增更改农场名指令
- 改进对土地开垦条件判断
---
## 待办事宜 `Todo` 列表 ## 待办事宜 `Todo` 列表
- [x] 完善我的农场图片,例如左上角显示用户数据 - [x] 完善我的农场图片,例如左上角显示用户数据
- [x] 完善升级数据、作物数据、作物图片 - [ ] 完善升级数据、作物数据、作物图片
- [x] 签到功能 - [ ] 签到功能
- [x] 添加渔场功能 - [ ] 添加渔场功能
- [x] 增加活动、交易行功能 - [ ] 增加活动、交易行功能
- [x] 增加交易行总行功能 - [ ] 增加交易行总行功能
- [x] 添加其他游戏种子素材 - [ ] 添加其他游戏种子素材
- [x] 想不到了,想到再说 - [ ] 想不到了,想到再说
--- ---
+5 -4
View File
@@ -24,18 +24,19 @@ __plugin_meta__ = PluginMetadata(
种子商店 [页数] 种子商店 [页数]
购买种子 [作物/种子名称] [数量] 购买种子 [作物/种子名称] [数量]
我的种子 我的种子
播种 [作物/种子名称] [数量] 播种 [作物/种子名称] [数量] (数量不填默认将最大可能播种
收获 收获
铲除 铲除
我的作物 我的作物
出售作物 [作物/种子名称] [数量] 出售作物 [作物/种子名称] [数量] (不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售
偷菜 at 偷菜 at (每人每天只能偷5次
开垦 开垦
购买农场币 [数量] 数量为消耗金币的数量 购买农场币 [数量] 数量为消耗金币的数量
更改农场名 [新农场名]
""".strip(), """.strip(),
extra=PluginExtraData( extra=PluginExtraData(
author="Art_Sakura", author="Art_Sakura",
version="1.0", version="1.1",
commands=[Command(command="我的农场")], commands=[Command(command="我的农场")],
menu_type="群内小游戏", menu_type="群内小游戏",
configs=[ configs=[
+114 -18
View File
@@ -1,9 +1,9 @@
from nonebot.adapters import Event, MessageTemplate from nonebot.adapters import Event, MessageTemplate
from nonebot.rule import to_me from nonebot.rule import to_me
from nonebot.typing import T_State from nonebot.typing import T_State
from nonebot_plugin_alconna import (Alconna, AlconnaQuery, Args, Arparma, At, from nonebot_plugin_alconna import (Alconna, AlconnaMatch, AlconnaQuery, Args,
Match, MultiVar, Option, Query, Subcommand, Arparma, At, Match, MultiVar, Option,
on_alconna, store_true) Query, Subcommand, on_alconna, store_true)
from nonebot_plugin_uninfo import Uninfo from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_waiter import waiter from nonebot_plugin_waiter import waiter
@@ -34,17 +34,89 @@ diuse_register = on_alconna(
) )
@diuse_register.handle() @diuse_register.handle()
async def _(session: Uninfo): async def handle_register(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
user = await g_pSqlManager.getUserInfoByUid(uid) user = await g_pSqlManager.getUserInfoByUid(uid)
if user: if user:
await MessageUtils.build_message("你已经有啦").send(reply_to=True) await MessageUtils.build_message("🎉 您已经开通农场啦~").send(reply_to=True)
else: return
aaa = await g_pSqlManager.initUserInfoByUid(uid, str(session.user.name), 0, 100)
await MessageUtils.build_message(str(aaa)).send(reply_to=True) try:
# 获取原始用户名并安全处理
raw_name = str(session.user.name)
safe_name = sanitize_username(raw_name)
# 初始化用户信息
success = await g_pSqlManager.initUserInfoByUid(
uid=uid,
name=safe_name,
exp=0,
point=500
)
msg = (
"✅ 农场开通成功!\n💼 初始资金:500农场币"
if success
else "⚠️ 开通失败,请稍后再试"
)
logger.info(f"用户注册 {'成功' if success else '失败'}:{uid}")
except Exception as e:
msg = "⚠️ 系统繁忙,请稍后再试"
logger.error(f"注册异常 | UID:{uid} | 错误:{str(e)}")
await MessageUtils.build_message(msg).send(reply_to=True)
def sanitize_username(username: str, max_length: int = 15) -> str:
"""
安全处理用户名
功能:
1. 移除首尾空白
2. 过滤危险字符
3. 转义单引号
4. 处理空值
5. 限制长度
"""
# 处理空值
if not username:
return "神秘农夫"
# 基础清洗
cleaned = username.strip()
# 允许的字符白名单(可自定义扩展)
safe_chars = {
'_', '-', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
'+', '=', '.', ',', '~', '·', ' ',
'a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z',
'A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'0','1','2','3','4','5','6','7','8','9',
}
# 添加常用中文字符(Unicode范围)
safe_chars.update(chr(c) for c in range(0x4E00, 0x9FFF+1))
# 过滤危险字符
filtered = [
c if c in safe_chars or 0x4E00 <= ord(c) <= 0x9FFF
else ''
for c in cleaned
]
# 合并处理结果
safe_str = ''.join(filtered)
# 转义单引号(双重保障)
escaped = safe_str.replace("'", "''")
# 处理空结果
if not escaped:
return "神秘农夫"
# 长度限制
return escaped[:max_length]
diuse_farm = on_alconna( diuse_farm = on_alconna(
Alconna( Alconna(
@@ -58,24 +130,24 @@ diuse_farm = on_alconna(
Subcommand("harvest", help_text="收获"), Subcommand("harvest", help_text="收获"),
Subcommand("eradicate", help_text="铲除"), Subcommand("eradicate", help_text="铲除"),
Subcommand("my-plant", help_text="我的作物"), Subcommand("my-plant", help_text="我的作物"),
# Subcommand("reclamation", Args["isBool?", str], help_text="开垦"),
Subcommand("sell-plant", Args["name?", str]["num?", int], help_text="出售作物"), Subcommand("sell-plant", Args["name?", str]["num?", int], help_text="出售作物"),
Subcommand("stealing", Args["target?", At], help_text="偷菜"), Subcommand("stealing", Args["target?", At], help_text="偷菜"),
Subcommand("buy-point", Args["num?", int], help_text="购买农场币"), Subcommand("buy-point", Args["num?", int], help_text="购买农场币"),
#Subcommand("sell-point", Args["num?", int], help_text="转换金币") #Subcommand("sell-point", Args["num?", int], help_text="转换金币")
Subcommand("change-name", Args["name?", str], help_text="更改农场名")
), ),
priority=5, priority=5,
block=True, block=True,
) )
@diuse_farm.assign("$main") @diuse_farm.assign("$main")
async def _(session: Uninfo, nickname: str = UserName()): async def _(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
if await isRegisteredByUid(uid) == False: if await isRegisteredByUid(uid) == False:
return return
image = await g_pFarmManager.drawFarmByUid(uid, nickname) image = await g_pFarmManager.drawFarmByUid(uid)
await MessageUtils.build_message(image).send(reply_to=True) await MessageUtils.build_message(image).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
@@ -267,12 +339,7 @@ diuse_farm.shortcut(
) )
@diuse_farm.assign("sell-plant") @diuse_farm.assign("sell-plant")
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", 1),): async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1),):
if not name.available:
await MessageUtils.build_message(
"请在指令后跟需要出售的作物名称"
).finish(reply_to=True)
uid = str(session.user.id) uid = str(session.user.id)
if await isRegisteredByUid(uid) == False: if await isRegisteredByUid(uid) == False:
@@ -329,3 +396,32 @@ async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
result = await g_pFarmManager.buyPointByUid(uid, num.result) result = await g_pFarmManager.buyPointByUid(uid, num.result)
await MessageUtils.build_message(result).send(reply_to=True) await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"更改农场名(?P<name>)",
command="我的农场",
arguments=["change-name", "{name}"],
prefix=True,
)
@diuse_farm.assign("change-name")
async def _(session: Uninfo, name: Match[str]):
if not name.available:
await MessageUtils.build_message(
"请在指令后跟需要更改的用户名"
).finish(reply_to=True)
uid = str(session.user.id)
if await isRegisteredByUid(uid) == False:
return
safeName = sanitize_username(name.result)
result = await g_pSqlManager.updateUserNameByUid(uid, safeName)
if result == True:
await MessageUtils.build_message("更新用户名成功!").send(reply_to=True)
else:
await MessageUtils.build_message("更新用户名失败!").send(reply_to=True)
+104 -20
View File
@@ -8,80 +8,164 @@
{ {
"4": "4":
{ {
"level": 2, "level": 2,
"point": 800, "point": 800,
"item": [] "item": []
}, },
"5": "5":
{ {
"level": 3, "level": 3,
"point": 1300, "point": 1300,
"item": [] "item": []
}, },
"6": "6":
{ {
"level": 5, "level": 5,
"point": 3200, "point": 3200,
"item": [] "item": []
}, },
"7": "7":
{ {
"level": 7, "level": 7,
"point": 5500, "point": 5500,
"item": [] "item": []
}, },
"8": "8":
{ {
"level": 9, "level": 9,
"point": 12000, "point": 12000,
"item": [] "item": []
}, },
"9": "9":
{ {
"level": 15, "level": 11,
"point": 19800, "point": 19800,
"item": [] "item": []
}, },
"10": "10":
{ {
"level":3, "level": 13,
"point": 3000, "point": 30000,
"item": [] "item": []
}, },
"11": "11":
{ {
"level":3, "level": 15,
"point": 3000, "point": 50000,
"item": [] "item": []
}, },
"12": "12":
{ {
"level":3, "level": 17,
"point": 3000, "point": 70000,
"item": [] "item": []
}, },
"13": "13":
{ {
"level":3, "level": 19,
"point": 3000, "point": 90000,
"item": [] "item": []
}, },
"14": "14":
{ {
"level":3, "level": 21,
"point": 3000, "point": 130000,
"item": [] "item": []
}, },
"15": "15":
{ {
"level":3, "level": 23,
"point": 3000, "point": 150000,
"item": [] "item": []
}, },
"16": "16":
{ {
"level":3, "level": 25,
"point": 3000, "point": 180000,
"item": []
},
"17":
{
"level": 27,
"point": 230000,
"item": []
},
"18":
{
"level": 29,
"point": 300000,
"item": []
},
"19":
{
"level": 31,
"point": 500000,
"item": []
},
"20":
{
"level": 33,
"point": 850000,
"item": []
},
"21":
{
"level": 36,
"point": 1100000,
"item": []
},
"22":
{
"level": 39,
"point": 1300000,
"item": []
},
"23":
{
"level": 42,
"point": 1500000,
"item": []
},
"24":
{
"level": 45,
"point": 1700000,
"item": []
},
"25":
{
"level": 48,
"point": 2000000,
"item": []
},
"26":
{
"level": 51,
"point": 2200000,
"item": []
},
"27":
{
"level": 54,
"point": 2800000,
"item": []
},
"28":
{
"level": 57,
"point": 3500000,
"item": []
},
"29":
{
"level": 60,
"point": 4300000,
"item": []
},
"30":
{
"level": 70,
"point": 5200000,
"item": [] "item": []
} }
} }
+119 -3
View File
@@ -3,10 +3,11 @@
"zhuShi": "zhuShi":
[ [
"level: 解锁等级", "level: 解锁等级",
"buy: 购买价格",
"limit: 限制等级 0:普通土地 1:红土地 2:黄土地 3:黑土地", "limit: 限制等级 0:普通土地 1:红土地 2:黄土地 3:黑土地",
"experience: 收获经验", "experience: 收获经验",
"harvest: 收获数量", "harvest: 收获数量",
"price: 单价", "price: 果实售价",
"time: 成熟时间 小时", "time: 成熟时间 小时",
"crop: 作物可以收几次", "crop: 作物可以收几次",
"again: 再次成熟时间 单位:小时", "again: 再次成熟时间 单位:小时",
@@ -19,6 +20,7 @@
"胡萝卜": "胡萝卜":
{ {
"level": 0, "level": 0,
"buy": 163,
"limit": 0, "limit": 0,
"experience": 18, "experience": 18,
"harvest": 17, "harvest": 17,
@@ -33,6 +35,7 @@
"白萝卜": "白萝卜":
{ {
"level": 0, "level": 0,
"buy": 125,
"limit": 0, "limit": 0,
"experience": 15, "experience": 15,
"harvest": 16, "harvest": 16,
@@ -47,6 +50,7 @@
"牧草": "牧草":
{ {
"level": 0, "level": 0,
"buy": 80,
"limit": 0, "limit": 0,
"experience": 10, "experience": 10,
"harvest": 25, "harvest": 25,
@@ -61,6 +65,7 @@
"大白菜": "大白菜":
{ {
"level": 1, "level": 1,
"buy": 168,
"limit": 0, "limit": 0,
"experience": 19, "experience": 19,
"harvest": 17, "harvest": 17,
@@ -75,6 +80,7 @@
"大蒜": "大蒜":
{ {
"level": 1, "level": 1,
"buy": 169,
"limit": 0, "limit": 0,
"experience": 19, "experience": 19,
"harvest": 17, "harvest": 17,
@@ -89,6 +95,7 @@
"水稻": "水稻":
{ {
"level": 2, "level": 2,
"buy": 168,
"limit": 0, "limit": 0,
"experience": 19, "experience": 19,
"harvest": 18, "harvest": 18,
@@ -96,13 +103,14 @@
"time": 14, "time": 14,
"crop": 1, "crop": 1,
"again": 0, "again": 0,
"phase": 6, "phase": 5,
"general": true, "general": false,
"sell": false "sell": false
}, },
"小麦": "小麦":
{ {
"level": 2, "level": 2,
"buy": 168,
"limit": 0, "limit": 0,
"experience": 19, "experience": 19,
"harvest": 18, "harvest": 18,
@@ -117,6 +125,7 @@
"玉米": "玉米":
{ {
"level": 3, "level": 3,
"buy": 175,
"limit": 0, "limit": 0,
"experience": 19, "experience": 19,
"harvest": 17, "harvest": 17,
@@ -131,6 +140,7 @@
"油菜": "油菜":
{ {
"level": 4, "level": 4,
"buy": 194,
"limit": 0, "limit": 0,
"experience": 29, "experience": 29,
"harvest": 23, "harvest": 23,
@@ -145,6 +155,7 @@
"生菜": "生菜":
{ {
"level": 4, "level": 4,
"buy": 195,
"limit": 0, "limit": 0,
"experience": 25, "experience": 25,
"harvest": 21, "harvest": 21,
@@ -155,6 +166,111 @@
"phase": 6, "phase": 6,
"general": true, "general": true,
"sell": false "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
} }
} }
} }
+4
View File
@@ -0,0 +1,4 @@
{
"date": "202505",
""
}
+530 -270
View File
@@ -1,6 +1,7 @@
import math import math
import os import os
import re import re
from contextlib import asynccontextmanager
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from io import StringIO from io import StringIO
from math import e from math import e
@@ -27,31 +28,41 @@ class CSqlManager:
bIsExist = os.path.exists(g_sDBFilePath) bIsExist = os.path.exists(g_sDBFilePath)
cls.m_pDB = await aiosqlite.connect(g_sDBFilePath) cls.m_pDB = await aiosqlite.connect(g_sDBFilePath)
cls.m_pDB.row_factory = aiosqlite.Row
#if bIsExist == False:
#TODO 缺少判断创建失败事件
#await cls.createDB()
await cls.checkDB() await cls.checkDB()
return True return True
@classmethod @classmethod
async def getColumns(cls, tableName): @asynccontextmanager
""" 由AI生成 async def _transaction(cls):
获取表的列信息 await cls.m_pDB.execute("BEGIN;")
"""
try: try:
cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{tableName}")') yield
columns = [row[1] for row in await cursor.fetchall()] except:
return columns await cls.m_pDB.execute("ROLLBACK;")
except aiosqlite.Error as e:
logger.error(f"获取表结构失败: {str(e)}")
raise raise
else:
await cls.m_pDB.execute("COMMIT;")
@classmethod @classmethod
async def ensure_table_exists(cls, tableName, columns) -> bool: async def getTableInfo(cls, tableName: str) -> list:
"""智能创建并分析数据库表、字段是否存在 由AI生成 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: Args:
tableName (_type_): 表名 tableName (_type_): 表名
@@ -60,67 +71,50 @@ class CSqlManager:
Returns: Returns:
_type_: _description_ _type_: _description_
""" """
try:
current_columns = await cls.getColumns(tableName)
#检查表是否存在 info = await cls.getTableInfo(tableName)
table_exists = bool(current_columns) 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:
if not table_exists: colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items())
create_sql = f''' if primaryKey:
CREATE TABLE "{tableName}" ( colsDef += f", PRIMARY KEY {primaryKey}"
{", ".join(f'"{k}" {v}' for k, v in columns.items())} await cls.m_pDB.execute(f'CREATE TABLE "{tableName}" ({colsDef});')
); return True
'''
await cls.m_pDB.execute(create_sql)
await cls.m_pDB.commit() #显式提交新建表操作
return True
#表存在时的处理 toAdd = [k for k in desired if k not in existing]
columns_to_add = [] toRemove = [k for k in existing if k not in desired]
columns_to_remove = [] typeMismatch = [k for k in desired if k in existing and existing[k] != desired[k]]
#检查需要添加的列 if toAdd and not toRemove and not typeMismatch:
for k, v in columns.items(): for col in toAdd:
if k not in current_columns: await cls.m_pDB.execute(
columns_to_add.append(f'"{k}" {v}') f'ALTER TABLE "{tableName}" ADD COLUMN "{col}" {columns[col]}'
)
return True
#检查需要移除的列 async with cls._transaction():
for col in current_columns: tmpTable = f"{tableName}_new"
if col not in columns.keys(): colsDef = ", ".join(f'"{k}" {v}' for k, v in desired.items())
columns_to_remove.append(col) if primaryKey:
colsDef += f", PRIMARY KEY {primaryKey}"
#执行修改 await cls.m_pDB.execute(f'CREATE TABLE "{tmpTable}" ({colsDef});')
if columns_to_add or columns_to_remove:
try:
#开启事务(使用connection级别的事务控制)
await cls.m_pDB.execute('BEGIN TRANSACTION')
#添加新列
for col_def in columns_to_add:
await cls.m_pDB.execute(f'ALTER TABLE "{tableName}" ADD COLUMN {col_def}')
#删除旧列
for col in columns_to_remove:
await cls.m_pDB.execute(f'ALTER TABLE "{tableName}" DROP COLUMN "{col}"')
#显式提交事务
await cls.m_pDB.commit()
return True
except Exception as e:
#回滚事务
await cls.m_pDB.rollback()
logger.error(f"表结构迁移失败: {str(e)}")
return False
except aiosqlite.Error as e:
logger.error(f"表结构迁移失败: {str(e)}")
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 return True
@classmethod @classmethod
async def checkDB(cls) -> bool: async def checkDB(cls) -> bool:
#1. 用户表
userInfo = { userInfo = {
"uid": "INTEGER PRIMARY KEY AUTOINCREMENT", "uid": "INTEGER PRIMARY KEY AUTOINCREMENT",
"name": "TEXT NOT NULL", "name": "TEXT NOT NULL",
@@ -129,24 +123,40 @@ class CSqlManager:
"soil": "INTEGER DEFAULT 3", "soil": "INTEGER DEFAULT 3",
"stealing": "TEXT DEFAULT NULL" "stealing": "TEXT DEFAULT NULL"
} }
#2. 土地表
userStorehouse = {
"uid": "INTEGER PRIMARY KEY AUTOINCREMENT",
"item": "TEXT DEFAULT ''",
"plant": "TEXT DEFAULT ''",
"seed": "TEXT DEFAULT ''"
}
userSoilInfo = { userSoilInfo = {
"uid": "INTEGER PRIMARY KEY AUTOINCREMENT", "uid": "INTEGER PRIMARY KEY AUTOINCREMENT",
**{f"soil{i}": "TEXT DEFAULT ''" for i in range(1, 31)} **{f"soil{i}": "TEXT DEFAULT ''" for i in range(1, 31)}
} }
#3. 用户作物明细表
userPlant = {
"uid": "INTEGER NOT NULL",
"plant": "TEXT NOT NULL",
"count": "INTEGER NOT NULL DEFAULT 0",
#建联合主键保证每个品种一行
"PRIMARY KEY": "(uid, plant)"
}
#4. 用户种子明细表
userSeed = {
"uid": "INTEGER NOT NULL",
"seed": "TEXT NOT NULL",
"count": "INTEGER NOT NULL DEFAULT 0",
"PRIMARY KEY": "(uid, seed)"
}
# 5. 用户道具明细表
userItem = {
"uid": "INTEGER NOT NULL",
"item": "TEXT NOT NULL",
"count": "INTEGER NOT NULL DEFAULT 0",
"PRIMARY KEY": "(uid, item)"
}
await cls.ensure_table_exists("user", userInfo) #建表(或增列)
await cls.ensureTableSchema("user", userInfo)
await cls.ensure_table_exists("storehouse", userStorehouse) await cls.ensureTableSchema("soil", userSoilInfo)
await cls.ensureTableSchema("userPlant", userPlant)
await cls.ensure_table_exists("soil", userSoilInfo) await cls.ensureTableSchema("userSeed", userSeed)
await cls.ensureTableSchema("userItem", userItem)
return True return True
@@ -160,21 +170,20 @@ class CSqlManager:
Returns: Returns:
bool: 是否执行成功 bool: 是否执行成功
""" """
if len(command) <= 0: if len(command) <= 0:
logger.warning("数据库语句长度不能!") logger.warning("数据库语句长度为空!")
return False return False
try: try:
await cls.m_pDB.execute(command) async with cls._transaction():
await cls.m_pDB.commit() await cls.m_pDB.execute(command)
return True return True
except Exception as e: except Exception as e:
logger.warning("数据库语句执行出错:" + command) logger.warning("数据库语句执行出错:" + command)
return False return False
@classmethod @classmethod
async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 100): async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 500):
"""初始化用户信息 """初始化用户信息
Args: Args:
@@ -189,11 +198,6 @@ class CSqlManager:
INSERT INTO user (uid, name, exp, point, soil, stealing) VALUES ({uid}, '{name}', {exp}, {point}, 3, '{date.today()}|5') INSERT INTO user (uid, name, exp, point, soil, stealing) VALUES ({uid}, '{name}', {exp}, {point}, 3, '{date.today()}|5')
""" """
#用户仓库
userStorehouse = f"""
INSERT INTO storehouse (uid) VALUES ({uid});
"""
#用户土地 #用户土地
userSoilInfo = f""" userSoilInfo = f"""
INSERT INTO soil (uid) VALUES ({uid}); INSERT INTO soil (uid) VALUES ({uid});
@@ -202,9 +206,6 @@ class CSqlManager:
if not await cls.executeDB(userInfo): if not await cls.executeDB(userInfo):
return False return False
if not await cls.executeDB(userStorehouse):
return False
if not await cls.executeDB(userSoilInfo): if not await cls.executeDB(userSoilInfo):
return False return False
@@ -243,6 +244,55 @@ class CSqlManager:
logger.warning(f"getUserInfoByUid查询失败: {e}") logger.warning(f"getUserInfoByUid查询失败: {e}")
return {} 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 @classmethod
async def getUserPointByUid(cls, uid: str) -> int: async def getUserPointByUid(cls, uid: str) -> int:
"""根据用户Uid获取用户农场币 """根据用户Uid获取用户农场币
@@ -445,205 +495,415 @@ class CSqlManager:
return await cls.executeDB(sql) return await cls.executeDB(sql)
@classmethod @classmethod
async def getUserSeedByUid(cls, uid: str) -> str: async def addUserSeedByUid(cls, uid: str, seed: str, count: int = 1) -> bool:
"""获取用户仓库种子信息 """根据用户uid添加种子信息
Args: Args:
info (list[dict]): 用户信息 uid (str): 用户uid
seed (str): 种子名称
count (int): 数量
Returns: Returns:
str: 仓库种子信息 bool: 是否添加成功
""" """
if len(uid) <= 0:
return ""
try: try:
async with cls.m_pDB.execute(f"SELECT seed FROM storehouse WHERE uid = {uid}") as cursor: async with cls._transaction():
async for row in cursor: #检查是否已存在该种子
return row[0] async with cls.m_pDB.execute(
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
) as cursor:
row = await cursor.fetchone()
return "" if row:
#如果种子已存在,则更新数量
newCount = row[0] + count
await cls.m_pDB.execute(
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
(newCount, uid, seed)
)
else:
#如果种子不存在,则插入新记录
newCount = count
await cls.m_pDB.execute(
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
(uid, seed, count)
)
#如果种子数量为 0,删除记录
if newCount <= 0:
await cls.m_pDB.execute(
"DELETE FROM userSeed WHERE uid = ? AND seed = ?",
(uid, seed)
)
return True
except Exception as e: except Exception as e:
logger.warning(f"getUserSeedByUid查询失败: {e}") logger.warning(f"真寻农场addUserSeedByUid 失败: {e}")
return "" return False
@classmethod @classmethod
async def getUserSeedByName(cls, uid: str, name: str) -> int: async def getUserSeedByName(cls, uid: str, seed: str) -> Optional[int]:
"""获取用户仓库种子信息 """根据种子名称获取种子数量
Args: Args:
uid (str): 用户信息 uid (str): 用户uid
name (str): 种子名称 seed (str): 种子名称
Returns: Returns:
int: 仓库种子信息 Optional[int]: 种子数量
""" """
if len(uid) <= 0:
return -1
try: try:
async with cls.m_pDB.execute(f"SELECT seed FROM storehouse WHERE uid = {uid}") as cursor: async with cls.m_pDB.execute(
async for row in cursor: "SELECT count FROM userSeed WHERE uid = ? AND seed = ?",
if row[0] == None or len(row[0]) == 0: (uid, seed)
return -1 ) as cursor:
row = await cursor.fetchone()
return row[0] if row else None
except Exception as e:
logger.warning(f"真寻农场getUserSeedByName 查询失败: {e}")
return None
plantDict: Dict[str, int] = {} @classmethod
for item in row[0].split(','): async def getUserSeedByUid(cls, uid: str) -> dict:
if '|' in item: """根据用户Uid获取仓库全部种子信息
seedName, count = item.split('|', 1)
plantDict[seedName] = int(count)
if name in plantDict: Args:
return plantDict[name] 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: else:
return -1 await cls.m_pDB.execute(
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
return -1 (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: except Exception as e:
logger.warning(f"getUserSeedByUid查询失败: {e}") logger.warning(f"真寻农场addUserItemByUid失败: {e}")
return -1
@classmethod
async def updateUserSeedByUid(cls, uid: str, seed: str) -> bool:
"""更新用户种子仓库
Args:
uid (str): 用户Uid
seed (str): 种子名称
Returns:
bool:
"""
if len(uid) <= 0:
return False return False
sql = f"UPDATE storehouse SET seed = '{seed}' WHERE uid = {uid}"
return await cls.executeDB(sql)
@classmethod
async def addUserSeedByPlant(cls, uid: str, seed: str, num: int) -> bool:
"""添加作物信息至仓库
Args:
uid (str): 用户Uid
seed (str): 种子名称
num(str): 种子数量
Returns:
bool:
"""
if len(uid) <= 0:
return False
seedsDict = {}
currentSeeds = await cls.getUserSeedByUid(uid)
if currentSeeds:
for item in currentSeeds.split(','):
if item.strip():
name, count = item.split('|')
seedsDict[name.strip()] = int(count.strip())
if seed in seedsDict:
seedsDict[seed] += num
if seedsDict[seed] <= 0:
del seedsDict[seed]
else:
if num > 0:
seedsDict[seed] = num
updatedSeeds = ','.join([f"{name}|{count}" for name, count in seedsDict.items()])
sql = f"UPDATE storehouse SET seed = '{updatedSeeds}' WHERE uid = {uid}"
return await cls.executeDB(sql)
@classmethod
async def getUserPlantByUid(cls, uid: str) -> str:
"""获取用户仓库作物信息
Args:
info (list[dict]): 用户信息
Returns:
str: 仓库作物信息
"""
if len(uid) <= 0:
return ""
try:
async with cls.m_pDB.execute(f"SELECT plant FROM storehouse WHERE uid = {uid}") as cursor:
async for row in cursor:
return row[0]
return ""
except Exception as e:
logger.warning(f"getUserPlantByUid查询失败: {e}")
return ""
@classmethod
async def updateUserPlantByUid(cls, uid: str, plant: str) -> bool:
"""更新用户作物仓库
Args:
uid (str): 用户Uid
plant (str): 作物名称
Returns:
bool:
"""
if len(uid) <= 0:
return False
sql = f"UPDATE storehouse SET plant = '{plant}' WHERE uid = {uid}"
return await cls.executeDB(sql)
@classmethod
async def addUserPlantByPlant(cls, uid: str, plant: str, num: int) -> bool:
"""添加作物信息至仓库
Args:
uid (str): 用户Uid
plant (str): 作物名称
num(str): 作物数量
Returns:
bool:
"""
if len(uid) <= 0:
return False
plantsDict = {}
currentPlants = await cls.getUserPlantByUid(uid)
if currentPlants:
for item in currentPlants.split(','):
if item.strip():
name, count = item.split('|')
plantsDict[name.strip()] = int(count.strip())
if plant in plantsDict:
plantsDict[plant] += num
if plantsDict[plant] <= 0:
del plantsDict[plant]
else:
if num > 0:
plantsDict[plant] = num
updatedPlants = ','.join([f"{name}|{count}" for name, count in plantsDict.items()])
sql = f"UPDATE storehouse SET plant = '{updatedPlants}' WHERE uid = {uid}"
return await cls.executeDB(sql)
g_pSqlManager = CSqlManager() g_pSqlManager = CSqlManager()
+102 -127
View File
@@ -51,7 +51,7 @@ class CFarmManager:
return f"充值{point}农场币成功,手续费{tax}金币,当前农场币:{number}" return f"充值{point}农场币成功,手续费{tax}金币,当前农场币:{number}"
@classmethod @classmethod
async def drawFarmByUid(cls, uid: str, name: str) -> bytes: async def drawFarmByUid(cls, uid: str) -> bytes:
"""绘制用户农场 """绘制用户农场
Args: Args:
@@ -72,7 +72,9 @@ class CFarmManager:
await grass.resize(0, soilSize[0], soilSize[1]) await grass.resize(0, soilSize[0], soilSize[1])
soilPos = g_pJsonManager.m_pSoil['soil'] soilPos = g_pJsonManager.m_pSoil['soil']
soilUnlock = await g_pSqlManager.getUserSoilByUid(uid)
userInfo = await g_pSqlManager.getUserInfoByUid(uid)
soilUnlock = int(userInfo['soil'])
x = 0 x = 0
y = 0 y = 0
@@ -128,7 +130,7 @@ class CFarmManager:
await img.paste(frame, (75, 44)) await img.paste(frame, (75, 44))
#用户名 #用户名
nameImg = await BuildImage.build_text_image(name, size = 24, font_color = (77, 35, 4)) nameImg = await BuildImage.build_text_image(userInfo['name'], size = 24, font_color = (77, 35, 4))
await img.paste(nameImg, (300, 92)) await img.paste(nameImg, (300, 92))
#经验值 #经验值
@@ -148,12 +150,10 @@ class CFarmManager:
await img.paste(levelImg, (660, 187)) await img.paste(levelImg, (660, 187))
#金币 #金币
point = await g_pSqlManager.getUserPointByUid(uid) pointImg = await BuildImage.build_text_image(str(userInfo['point']), size = 24, font_color = (253, 253, 253))
pointImg = await BuildImage.build_text_image(str(point), size = 24, font_color = (253, 253, 253))
await img.paste(pointImg, (330, 255)) await img.paste(pointImg, (330, 255))
#点券 #点券 TODO
bonds = await g_pSqlManager.getUserPointByUid(uid)
bondsImg = await BuildImage.build_text_image("0", size = 24, font_color = (253, 253, 253)) bondsImg = await BuildImage.build_text_image("0", size = 24, font_color = (253, 253, 253))
await img.paste(bondsImg, (570, 255)) await img.paste(bondsImg, (570, 255))
@@ -213,9 +213,12 @@ class CFarmManager:
currentStage = int(elapsedHour / (plantInfo['time'] / (plantInfo['phase'] - 1))) currentStage = int(elapsedHour / (plantInfo['time'] / (plantInfo['phase'] - 1)))
#TODO 缺少判断部分种子是否是通用0阶段图片
if currentStage <= 0: if currentStage <= 0:
plant = BuildImage(background = g_sResourcePath / f"plant/basic/0.png") 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) await plant.resize(0, 35, 58)
else: else:
plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo[0]}/{currentStage}.png") plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo[0]}/{currentStage}.png")
@@ -224,17 +227,9 @@ class CFarmManager:
@classmethod @classmethod
async def getUserSeedByUid(cls, uid: str) -> bytes: async def getUserSeedByUid(cls, uid: str) -> bytes:
"""获取用户种子仓库 """获取用户种子仓库"""
dataList = []
Args: columnNames = [
uid (str): 用户Uid
Returns:
bytes: 返回图片
"""
data_list = []
column_name = [
"-", "-",
"种子名称", "种子名称",
"数量", "数量",
@@ -246,59 +241,45 @@ class CFarmManager:
"是否可以上架交易行" "是否可以上架交易行"
] ]
seed = await g_pSqlManager.getUserSeedByUid(uid) # 从数据库获取结构化数据
seedRecords = await g_pSqlManager.getUserSeedByUid(uid) or {}
if seed == None: if not seedRecords:
result = await ImageTemplate.table_page( result = await ImageTemplate.table_page(
"种子仓库", "种子仓库",
"播种示例:@小真寻 播种 大白菜 [数量]", "播种示例:@小真寻 播种 大白菜 [数量]",
column_name, columnNames,
data_list, dataList,
) )
return result.pic2bytes() return result.pic2bytes()
sell = "" for seedName, count in seedRecords.items():
for item in seed.split(','): try:
if '|' in item: plantInfo = g_pJsonManager.m_pPlant['plant'][seedName]
seedName, count = item.split('|', 1) #分割一次,避免多竖线问题 iconPath = g_sResourcePath / f"plant/{seedName}/icon.png"
try: icon = (iconPath, 33, 33) if iconPath.exists() else ""
plantInfo = g_pJsonManager.m_pPlant['plant'][seedName] sellable = "可以" if plantInfo['again'] else "不可以"
icon = "" dataList.append([
icon_path = g_sResourcePath / f"plant/{seedName}/icon.png" icon,
if icon_path.exists(): seedName,
icon = (icon_path, 33, 33) count,
plantInfo['experience'],
if plantInfo['again'] == True: plantInfo['harvest'],
sell = "可以" plantInfo['time'],
else: plantInfo['crop'],
sell = "不可以" plantInfo['again'],
sellable
data_list.append( ])
[ except KeyError:
icon, continue
seedName,
count,
plantInfo['experience'],
plantInfo['harvest'],
plantInfo['time'],
plantInfo['crop'],
plantInfo['again'],
sell
]
)
except Exception as e:
continue
result = await ImageTemplate.table_page( result = await ImageTemplate.table_page(
"种子仓库", "种子仓库",
"播种示例:@小真寻 播种 大白菜 [数量]", "播种示例:@小真寻 播种 大白菜 [数量]",
column_name, columnNames,
data_list, dataList,
) )
return result.pic2bytes() return result.pic2bytes()
@classmethod @classmethod
@@ -311,55 +292,58 @@ class CFarmManager:
num (int, optional): 播种数量 num (int, optional): 播种数量
Returns: Returns:
str: str: 返回结果
""" """
try:
# 获取用户的种子数量
count = await g_pSqlManager.getUserSeedByName(uid, name)
if count is None:
count = 0 # 如果返回 None,则视为没有种子
number = 0 if count <= 0:
count = await g_pSqlManager.getUserSeedByName(uid, name) return f"没有在你的仓库发现{name}种子,快去买点吧!"
if count <= 0: # 如果播种数量超过仓库种子数量
return f"没有在你的仓库发现{name}种子,快去买点吧!" if count < num and num != -1:
return f"仓库中的{name}种子数量不足,当前剩余{count}个种子"
#如果播种超过仓库种子 # 获取用户土地数量
isMax = False soilNumber = await g_pSqlManager.getUserSoilByUid(uid)
if count < num:
isMax = True
#如果播种全部 # 如果播种数量为 -1,表示播种所有可播种的土地
isAll = False if num == -1:
if num == -1: num = count
isAll = True
soilNumber = await g_pSqlManager.getUserSoilByUid(uid) # 记录是否成功播种
successCount = 0
for i in range(1, soilNumber + 1): for i in range(1, soilNumber + 1):
if count > 0: if count > 0 and num > 0:
if isAll or num > 0:
soilName = f"soil{i}" soilName = f"soil{i}"
success, message = await g_pSqlManager.getUserSoilStatusBySoilID(uid, soilName) success, message = await g_pSqlManager.getUserSoilStatusBySoilID(uid, soilName)
if success: if success:
#更新种子数量 # 更新种子数量
num -= 1 num -= 1
count -= 1 count -= 1
#记录种子消耗数量 # 记录种子消耗数量
number -= 1 successCount += 1
#更新数据库 # 更新数据库
await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soilName, name) await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soilName, name)
str = "" # 确保用户仓库数量更新
if successCount > 0:
await g_pSqlManager.updateUserSeedByName(uid, name, count)
if isMax: # 根据播种结果给出反馈
str = f"仓库数量不够那么多,已将剩余数量全部播种!" if num == 0:
elif num > 0: return f"播种{name}成功!仓库剩余{count}个种子"
str = f"播种数量超出开垦土地数量,已将可播种土地成功播种{name}!仓库还剩下{count}个种子" else:
else: return f"播种数量超出开垦土地数量,已将可播种土地成功播种{name}!仓库剩余{count}个种子"
str = f"播种{name}成功!仓库还剩下{count}个种子"
await g_pSqlManager.addUserSeedByPlant(uid, name, number) except Exception as e:
logger.warning(f"播种操作失败: {e}")
return str return "播种失败,请稍后重试!"
@classmethod @classmethod
async def harvest(cls, uid: str) -> str: async def harvest(cls, uid: str) -> str:
@@ -414,7 +398,7 @@ class CFarmManager:
harvestRecords.append(f"收获作物:{plantId},数量为:{number},经验为:{plantInfo['experience']}") harvestRecords.append(f"收获作物:{plantId},数量为:{number},经验为:{plantInfo['experience']}")
#更新数据库操作 #更新数据库操作
await g_pSqlManager.addUserPlantByPlant(uid, plantId, number) await g_pSqlManager.addUserPlantByUid(uid, plantId, number)
await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soil_name, "", 4) await g_pSqlManager.updateUserSoilStatusByPlantName(uid, soil_name, "", 4)
if experience > 0: if experience > 0:
@@ -474,7 +458,6 @@ class CFarmManager:
Returns: Returns:
bytes: 返回图片 bytes: 返回图片
""" """
data_list = [] data_list = []
column_name = [ column_name = [
"-", "-",
@@ -487,52 +470,44 @@ class CFarmManager:
plant = await g_pSqlManager.getUserPlantByUid(uid) plant = await g_pSqlManager.getUserPlantByUid(uid)
if plant == None: if plant is None:
result = await ImageTemplate.table_page( result = await ImageTemplate.table_page(
"作物仓库", "作物仓库",
"播种示例:@小真寻 出售作物 大白菜 [数量]", "出售示例:@小真寻 出售作物 大白菜 [数量]",
column_name, column_name,
data_list, data_list,
) )
return result.pic2bytes() return result.pic2bytes()
sell = "" sell = ""
for item in plant.split(','): for name, count in plant.items():
if '|' in item: plantInfo = g_pJsonManager.m_pPlant['plant'][name]
plantName, count = item.split('|', 1) #分割一次,避免多竖线问题 icon = ""
try: icon_path = g_sResourcePath / f"plant/{name}/icon.png"
plantInfo = g_pJsonManager.m_pPlant['plant'][plantName] if icon_path.exists():
icon = (icon_path, 33, 33)
icon = "" if plantInfo['again'] == True:
icon_path = g_sResourcePath / f"plant/{plantName}/icon.png" sell = "可以"
if icon_path.exists(): else:
icon = (icon_path, 33, 33) sell = "不可以"
if plantInfo['again'] == True: number = int(count) * plantInfo['price']
sell = "可以"
else:
sell = "不可以"
number = int(count) * plantInfo['price'] data_list.append(
[
data_list.append( icon,
[ name,
icon, count,
plantName, plantInfo['price'],
count, number,
plantInfo['price'], sell
number, ]
sell )
]
)
except Exception as e:
continue
result = await ImageTemplate.table_page( result = await ImageTemplate.table_page(
"作物仓库", "作物仓库",
"播种示例:@小真寻 出售作物 大白菜 [数量]", "出售示例:@小真寻 出售作物 大白菜 [数量]",
column_name, column_name,
data_list, data_list,
) )
@@ -630,7 +605,7 @@ class CFarmManager:
logger.info(f"{randomNumber}") logger.info(f"{randomNumber}")
if randomNumber > 0: if randomNumber > 0:
await g_pSqlManager.addUserPlantByPlant(uid, plantId, randomNumber) await g_pSqlManager.addUserPlantByUid(uid, plantId, randomNumber)
harvestRecords.append(f"成功偷到作物:{plantId},数量为:{randomNumber}") harvestRecords.append(f"成功偷到作物:{plantId},数量为:{randomNumber}")
stealingStatus.append(f"{uid}-{randomNumber}") stealingStatus.append(f"{uid}-{randomNumber}")
+29 -68
View File
@@ -1,4 +1,5 @@
import math import math
from re import I
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
@@ -22,8 +23,9 @@ class CShopManager:
column_name = [ column_name = [
"-", "-",
"种子名称", "种子名称",
"解锁等级",
"种子单价", "种子单价",
"解锁等级",
"果实单价",
"收获经验", "收获经验",
"收获数量", "收获数量",
"成熟时间(小时)", "成熟时间(小时)",
@@ -53,6 +55,7 @@ class CShopManager:
[ [
icon, icon,
key, key,
plant['buy'],
plant['level'], plant['level'],
plant['price'], plant['price'],
plant['experience'], plant['experience'],
@@ -105,7 +108,7 @@ class CShopManager:
return "你的等级不够哦,努努力吧" return "你的等级不够哦,努努力吧"
point = await g_pSqlManager.getUserPointByUid(uid) point = await g_pSqlManager.getUserPointByUid(uid)
total = int(plantInfo['price']) * num total = int(plantInfo['buy']) * num
logger.debug(f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}") logger.debug(f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}")
@@ -114,7 +117,7 @@ class CShopManager:
else: else:
await g_pSqlManager.updateUserPointByUid(uid, point - total) await g_pSqlManager.updateUserPointByUid(uid, point - total)
if await g_pSqlManager.addUserSeedByPlant(uid, name, num) == False: if await g_pSqlManager.addUserSeedByUid(uid, name, num) == False:
return "购买失败,执行数据库错误!" return "购买失败,执行数据库错误!"
return f"成功购买{name},花费{total}农场币, 剩余{point - total}农场币" return f"成功购买{name},花费{total}农场币, 剩余{point - total}农场币"
@@ -129,81 +132,39 @@ class CShopManager:
Returns: Returns:
str: str:
""" """
if not isinstance(name, str) or name.strip() == "":
name = ""
plant = await g_pSqlManager.getUserPlantByUid(uid) plant = await g_pSqlManager.getUserPlantByUid(uid)
if not plant:
if plant == None:
return "你仓库没有可以出售的作物" return "你仓库没有可以出售的作物"
point = 0 point = 0
totalSold = 0 totalSold = 0
remainingItems = [] isAll = (num == -1)
isAll = False if name == "":
if num == -1: for plantName, count in plant.items():
isAll = True plantInfo = g_pJsonManager.m_pPlant['plant'][plantName]
point += plantInfo['price'] * count
items = plant.split(',') await g_pSqlManager.updateUserPlantByName(uid, plantName, 0)
if len(name) <= 0:
#出售全部
for item in items:
if '|' in item:
plant_name, count_str = item.split('|', 1)
try:
count = int(count_str)
plant_info = g_pJsonManager.m_pPlant['plant'][plant_name]
point += plant_info['price'] * count
except Exception:
continue
await g_pSqlManager.updateUserPlantByUid(uid, "") # 清空仓库
else: else:
for item in items: if name not in plant:
if '|' in item: return f"出售作物{name}出错:仓库中不存在该作物"
plantName, countStr = item.split('|', 1) available = plant[name]
try: sellAmount = available if isAll else min(available, num)
count = int(countStr) if sellAmount <= 0:
if plantName == name: return f"出售作物{name}出错:数量不足"
await g_pSqlManager.updateUserPlantByName(uid, name, available - sellAmount)
totalSold = sellAmount
if isAll: totalPoint = point if name == "" else totalSold * g_pJsonManager.m_pPlant['plant'][name]['price']
sellAmount = count currentPoint = await g_pSqlManager.getUserPointByUid(uid)
else: await g_pSqlManager.updateUserPointByUid(uid, currentPoint + totalPoint)
sellAmount = min(num, count)
totalSold += sellAmount if name == "":
remaining = count - sellAmount return f"成功出售所有作物,获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}"
if remaining > 0:
remainingItems.append(f"{plantName}|{remaining}")
if isAll == False:
num -= sellAmount
break
except (ValueError, TypeError):
continue
if num > 0 and isAll == False:
return f"出售作物{name}出错:数量不足"
#计算收益
try:
plantInfo = g_pJsonManager.m_pPlant['plant'][name]
totalPoint = plantInfo['price'] * totalSold
except KeyError:
return f"出售作物{name}出错:作物不存在"
#更新剩余作物
remainingPlant = ','.join(remainingItems) if remainingItems else ""
await g_pSqlManager.updateUserPlantByUid(uid, remainingPlant)
#更新农场币
p = await g_pSqlManager.getUserPointByUid(uid)
await g_pSqlManager.updateUserPointByUid(uid, p + totalPoint)
if name:
return f"成功出售{name},获得农场币:{totalPoint}"
else: else:
return f"成功出售所有作物,获得农场币:{totalPoint}" return f"成功出售{name},获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}"
g_pShopManager = CShopManager() g_pShopManager = CShopManager()
+15
View File
@@ -0,0 +1,15 @@
# 真寻农场更新日志
## V1.1
- 感谢[quanquan1014](https://github.com/quanquan1014)对农场名称中包含特殊字符的处理。
- 更正数据库写法,但是会导致V1.0用户的作物和种子丢失
- 完善我的农场图片资源,现在会在左上角显示经验、等级、金币等详细信息了
- 完善出售作物逻辑,现在可以空置作物名称来一键出售全部作物了,也可以选择空置数量来一键出售仓库种指定作物
- 完善播种逻辑,现在可以空置数量来一键播种指定作物了
- 新增更改农场名指令
- 改进对土地开垦条件判断
## V1.0
世界的起源。
+73 -17
View File
@@ -1,31 +1,87 @@
from unittest import result import os
import requests import httpx
from zhenxun.configs.config import Config
from zhenxun.services.log import logger
class CRequestManager: class CRequestManager:
@classmethod @classmethod
async def post(cls, url, json_data=None, form_data=None): async def download(cls, url: str, savePath: str, fileName: str) -> bool:
"""发送 POST 请求(支持 JSON/Form-Data 格式)""" """下载文件到指定路径并覆盖已存在的文件
Args:
url (str): 文件的下载链接
savePath (str): 保存文件夹路径
fileName (str): 保存后的文件名
Returns:
bool: 是否下载成功
"""
try: try:
headers = {"Content-Type": "application/json"} if json_data else None async with httpx.AsyncClient(timeout=10.0) as client:
response = requests.post( response = await client.get(url)
url, if response.status_code == 200:
json=json_data, fullPath = os.path.join(savePath, fileName)
data=form_data, os.makedirs(os.path.dirname(fullPath), exist_ok=True)
headers=headers, with open(fullPath, "wb") as f:
timeout=5 f.write(response.content)
) return True
response.raise_for_status() else:
return response.json() logger.warning(f"文件下载失败: HTTP {response.status_code} {response.text}")
except requests.exceptions.RequestException as e: return False
print(f"请求失败: {e}") except Exception as e:
logger.warning(f"下载文件异常: {e}")
return False
@classmethod
async def post(cls, endpoint: str, name: str = "", jsonData: dict = None, formData: dict = None) -> dict:
"""发送POST请求到指定接口,供其他方法统一调用
Args:
endpoint (str): 请求的接口路径
name (str, optional): 操作名称用于日志记录
jsonData (dict, optional): 以JSON格式发送的数据
formData (dict, optional): 以表单格式发送的数据
Raises:
ValueError: 当jsonData和formData都未提供时抛出
Returns:
dict: 返回请求结果的JSON数据
"""
if jsonData is None and formData is None:
raise ValueError("post请求必须提供jsonData或formData其中之一")
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
url = f"{baseUrl.rstrip('/')}/{endpoint.lstrip('/')}"
try:
async with httpx.AsyncClient(timeout=5.0) as client:
if jsonData is not None:
response = await client.post(url, json=jsonData)
else:
response = await client.post(url, data=formData)
if response.status_code == 200:
return response.json()
else:
logger.warning(f"真寻农场{name}请求失败: HTTP {response.status_code} {response.text}")
return {}
except httpx.RequestError as e:
logger.warning(f"真寻农场{name}请求异常: {e}")
return {} return {}
except Exception as e:
logger.warning(f"真寻农场{name}处理异常: {e}")
return {}
@classmethod @classmethod
async def sign(cls, uid: str) -> str: async def sign(cls, uid: str) -> str:
a = await cls.post("http://diuse.work:9099/testPost", json_data={"level":3}) a = await cls.post("http://diuse.work:9099/testPost", jsonData={"level":3})
result = "" result = ""
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB