完善市场相关类;表结构升级

This commit is contained in:
milimoe 2025-07-28 20:02:09 +08:00
parent bc2f180106
commit a181fb653c
Signed by: milimoe
GPG Key ID: 9554D37E4B8991D0
13 changed files with 384 additions and 17 deletions

View File

@ -0,0 +1,17 @@
namespace Milimoe.FunGame.Core.Api.Utility
{
public static class LINQExtension
{
public static IEnumerable<T> GetPage<T>(this IEnumerable<T> list, int showPage, int pageSize)
{
return [.. list.Skip((showPage - 1) * pageSize).Take(pageSize)];
}
public static int MaxPage<T>(this IEnumerable<T> list, int pageSize)
{
if (pageSize <= 0) pageSize = 1;
int page = (int)Math.Ceiling((double)list.Count() / pageSize);
return page > 0 ? page : 1;
}
}
}

67
Entity/Trade/Market.cs Normal file
View File

@ -0,0 +1,67 @@
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
{
public class Market : BaseEntity
{
public string Description { get; set; } = "";
public DateTime? StartTime { get; set; } = null;
public DateTime? EndTime { get; set; } = null;
public DateTime? StartTimeOfDay { get; set; } = null;
public DateTime? EndTimeOfDay { get; set; } = null;
public Dictionary<long, MarketItem> MarketItems { get; } = [];
public Market(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
public void AddItem(User user, Item item, double price, int stock, string name = "")
{
if (MarketItems.Values.FirstOrDefault(m => m.Item.Id == item.Id && m.Item.Name == item.Name && m.Price == price && m.User == user.Id && m.Status == MarketItemState.Listed) is MarketItem marketItem)
{
marketItem.Stock += stock;
marketItem.Item.Price = (marketItem.Item.Price + item.Price) / marketItem.Stock;
}
else
{
long id = MarketItems.Count > 0 ? MarketItems.Keys.Max() + 1 : 1;
if (name.Trim() == "")
{
name = item.Name;
}
marketItem = new()
{
Id = id,
User = user.Id,
Username = user.Username,
Item = item,
Price = price,
Stock = stock,
Name = name
};
MarketItems.Add(id, marketItem);
}
}
public void AddItems(User user, Item[] items, double price)
{
for (int index = 0; index < items.Length; index++)
{
Item item = items[index];
AddItem(user, item, price, 1);
}
}
public override bool Equals(IBaseEntity? other)
{
return other is Market && other.GetIdName() == GetIdName();
}
}
}

View File

@ -6,13 +6,20 @@ namespace Milimoe.FunGame.Core.Entity
{ {
public class MarketItem : BaseEntity public class MarketItem : BaseEntity
{ {
public User User { get; set; } public long User { get; set; } = 0;
public string Username { get; set; } = "";
public Item Item { get; set; } public Item Item { get; set; }
public double Price { get; set; } = 0; public double Price { get; set; } = 0;
public int Stock { get; set; } = 0;
public DateTime CreateTime { get; set; } = DateTime.Now; public DateTime CreateTime { get; set; } = DateTime.Now;
public DateTime? FinishTime { get; set; } = null; public DateTime? FinishTime { get; set; } = null;
public MarketItemState Status { get; set; } = MarketItemState.Listed; public MarketItemState Status { get; set; } = MarketItemState.Listed;
public User? Buyer { get; set; } = null; public HashSet<long> Buyers { get; set; } = [];
public override string ToString()
{
return Item.Name;
}
public override bool Equals(IBaseEntity? other) public override bool Equals(IBaseEntity? other)
{ {
@ -21,7 +28,6 @@ namespace Milimoe.FunGame.Core.Entity
public MarketItem() public MarketItem()
{ {
User = Factory.GetUser();
Item = Factory.GetItem(); Item = Factory.GetItem();
} }
} }

View File

@ -60,16 +60,40 @@ namespace Milimoe.FunGame.Core.Entity
if (StartTimeOfDay.HasValue && EndTimeOfDay.HasValue) if (StartTimeOfDay.HasValue && EndTimeOfDay.HasValue)
{ {
builder.AppendLine($"每日营业时间:{StartTimeOfDay.Value.ToString(General.GeneralDateTimeFormatTimeOnly)} 至 {EndTimeOfDay.Value.ToString(General.GeneralDateTimeFormatTimeOnly)}"); builder.AppendLine($"每日营业时间:{StartTimeOfDay.Value.ToString(General.GeneralDateTimeFormatTimeOnly)} 至 {EndTimeOfDay.Value.ToString(General.GeneralDateTimeFormatTimeOnly)}");
DateTime now = DateTime.Now;
if (StartTimeOfDay.Value > now || EndTimeOfDay.Value < now) builder.AppendLine($"商店现在还未开始营业。");
} }
else else
{ {
builder.AppendLine($"[ 24H ] 全天营业"); builder.AppendLine($"[ 24H ] 全天营业");
} }
DateTime now = DateTime.Now;
TimeSpan nowTimeOfDay = now.TimeOfDay;
bool isStoreOpen = true;
bool isStoreOpenInDate = true;
if (StartTime.HasValue && StartTime.Value > now || EndTime.HasValue && EndTime.Value < now)
{
isStoreOpen = false;
isStoreOpenInDate = false;
}
if (isStoreOpen && StartTimeOfDay.HasValue && EndTimeOfDay.HasValue)
{
TimeSpan startTimeSpan = StartTimeOfDay.Value.TimeOfDay;
TimeSpan endTimeSpan = EndTimeOfDay.Value.TimeOfDay;
if (startTimeSpan <= endTimeSpan)
{
isStoreOpen = nowTimeOfDay >= startTimeSpan && nowTimeOfDay <= endTimeSpan;
}
else
{
isStoreOpen = nowTimeOfDay >= startTimeSpan || nowTimeOfDay <= endTimeSpan;
}
}
if (!isStoreOpen)
{
builder.AppendLine($"商店现在不在营业时间内。");
}
builder.AppendLine($"☆--- 商品列表 ---☆"); builder.AppendLine($"☆--- 商品列表 ---☆");
Goods[] goodsValid = [.. Goods.Values.Where(g => !g.ExpireTime.HasValue || g.ExpireTime.Value > DateTime.Now)]; Goods[] goodsValid = [.. Goods.Values.Where(g => !g.ExpireTime.HasValue || g.ExpireTime.Value > DateTime.Now)];
if (goodsValid.Length == 0) if (!isStoreOpen || goodsValid.Length == 0)
{ {
builder.AppendLine("当前没有商品可供购买,过一段时间再来吧。"); builder.AppendLine("当前没有商品可供购买,过一段时间再来吧。");
} }
@ -79,9 +103,9 @@ namespace Milimoe.FunGame.Core.Entity
{ {
builder.AppendLine(goods.ToString(user)); builder.AppendLine(goods.ToString(user));
} }
builder.AppendLine("提示:使用【商店查看+序号】查看物品详细信息,使用【商店购买+序号】购买物品(指令在 2 分钟内可用)。"); builder.AppendLine("提示:使用【商店查看+序号】查看商品详细信息,使用【商店购买+序号】购买商品(指令在 2 分钟内可用)。");
} }
if (AutoRefresh) if (isStoreOpenInDate && AutoRefresh)
{ {
builder.AppendLine($"商品将在 {NextRefreshDate.ToString(General.GeneralDateTimeFormatChinese)} 刷新。"); builder.AppendLine($"商品将在 {NextRefreshDate.ToString(General.GeneralDateTimeFormatChinese)} 刷新。");
} }

View File

@ -0,0 +1,96 @@
using System.Text.Json;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
public class MarketConverter : BaseEntityConverter<Market>
{
public override Market NewInstance()
{
return new Market("市场");
}
public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref Market result, Dictionary<string, object> convertingContext)
{
switch (propertyName)
{
case nameof(Market.Name):
result.Name = reader.GetString() ?? "";
break;
case nameof(Market.Description):
result.Description = reader.GetString() ?? "";
break;
case nameof(Market.StartTime):
string dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime time))
{
result.StartTime = time;
}
else
{
result.StartTime = DateTime.MinValue;
}
break;
case nameof(Market.EndTime):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.EndTime = time;
}
else
{
result.EndTime = DateTime.MinValue;
}
break;
case nameof(Market.StartTimeOfDay):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.StartTimeOfDay = time;
}
else
{
result.StartTimeOfDay = DateTime.MinValue;
}
break;
case nameof(Market.EndTimeOfDay):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.EndTimeOfDay = time;
}
else
{
result.EndTimeOfDay = DateTime.MinValue;
}
break;
case nameof(Market.MarketItems):
Dictionary<long, MarketItem> marketItems = NetworkUtility.JsonDeserialize<Dictionary<long, MarketItem>>(ref reader, options) ?? [];
foreach (long id in marketItems.Keys)
{
result.MarketItems[id] = marketItems[id];
}
break;
}
}
public override void Write(Utf8JsonWriter writer, Market value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteString(nameof(Market.Name), value.Name);
writer.WriteString(nameof(Market.Description), value.Description);
if (value.StartTime.HasValue) writer.WriteString(nameof(Market.StartTime), value.StartTime.Value.ToString(General.GeneralDateTimeFormat));
if (value.EndTime.HasValue) writer.WriteString(nameof(Market.EndTime), value.EndTime.Value.ToString(General.GeneralDateTimeFormat));
if (value.StartTimeOfDay.HasValue) writer.WriteString(nameof(Market.StartTimeOfDay), value.StartTimeOfDay.Value.ToString(General.GeneralDateTimeFormat));
if (value.EndTimeOfDay.HasValue) writer.WriteString(nameof(Market.EndTimeOfDay), value.EndTimeOfDay.Value.ToString(General.GeneralDateTimeFormat));
writer.WritePropertyName(nameof(Market.MarketItems));
JsonSerializer.Serialize(writer, value.MarketItems, options);
writer.WriteEndObject();
}
}
}

View File

@ -0,0 +1,93 @@
using System.Text.Json;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
public class MarketItemConverter : BaseEntityConverter<MarketItem>
{
public override MarketItem NewInstance()
{
return new MarketItem();
}
public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref MarketItem result, Dictionary<string, object> convertingContext)
{
switch (propertyName)
{
case nameof(MarketItem.Id):
result.Id = reader.GetInt64();
break;
case nameof(MarketItem.User):
result.User = reader.GetInt64();
break;
case nameof(MarketItem.Username):
result.Username = reader.GetString() ?? "";
break;
case nameof(MarketItem.Item):
result.Item = NetworkUtility.JsonDeserialize<Item>(ref reader, options) ?? Factory.GetItem();
break;
case nameof(MarketItem.Stock):
result.Stock = Convert.ToInt32(reader.GetInt64());
break;
case nameof(MarketItem.Name):
result.Name = reader.GetString() ?? "";
break;
case nameof(MarketItem.Price):
result.Price = reader.GetDouble();
break;
case nameof(MarketItem.CreateTime):
string dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime time))
{
result.CreateTime = time;
}
else
{
result.CreateTime = DateTime.MinValue;
}
break;
case nameof(MarketItem.FinishTime):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.FinishTime = time;
}
else
{
result.FinishTime = DateTime.MinValue;
}
break;
case nameof(MarketItem.Status):
result.Status = (MarketItemState)reader.GetInt32();
break;
case nameof(MarketItem.Buyers):
result.Buyers = NetworkUtility.JsonDeserialize<HashSet<long>>(ref reader, options) ?? [];
break;
}
}
public override void Write(Utf8JsonWriter writer, MarketItem value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteNumber(nameof(MarketItem.Id), value.Id);
writer.WriteNumber(nameof(MarketItem.User), value.User);
writer.WriteString(nameof(MarketItem.Username), value.Username);
writer.WritePropertyName(nameof(MarketItem.Item));
JsonSerializer.Serialize(writer, value.Item, options);
writer.WriteNumber(nameof(MarketItem.Stock), value.Stock);
writer.WriteString(nameof(MarketItem.Name), value.Name);
writer.WriteNumber(nameof(MarketItem.Price), value.Price);
writer.WriteString(nameof(MarketItem.CreateTime), value.CreateTime.ToString(General.GeneralDateTimeFormat));
if (value.FinishTime.HasValue) writer.WriteString(nameof(MarketItem.FinishTime), value.FinishTime.Value.ToString(General.GeneralDateTimeFormat));
writer.WriteNumber(nameof(MarketItem.Status), (int)value.Status);
writer.WritePropertyName(nameof(MarketItem.Buyers));
JsonSerializer.Serialize(writer, value.Buyers, options);
writer.WriteEndObject();
}
}
}

View File

@ -62,6 +62,16 @@ namespace Milimoe.FunGame.Core.Library.Constant
_ => "已过期" _ => "已过期"
}; };
} }
public static string GetMarketItemStatus(MarketItemState status)
{
return status switch
{
MarketItemState.Delisted => "已下架",
MarketItemState.Purchased => "已售罄",
_ => "已上架"
};
}
} }
/// <summary> /// <summary>

View File

@ -10,10 +10,11 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
public const string Column_ItemGuid = "ItemGuid"; public const string Column_ItemGuid = "ItemGuid";
public const string Column_UserId = "UserId"; public const string Column_UserId = "UserId";
public const string Column_Price = "Price"; public const string Column_Price = "Price";
public const string Column_Stock = "Stock";
public const string Column_CreateTime = "CreateTime"; public const string Column_CreateTime = "CreateTime";
public const string Column_FinishTime = "FinishTime"; public const string Column_FinishTime = "FinishTime";
public const string Column_Status = "Status"; public const string Column_Status = "Status";
public const string Column_Buyer = "Buyer"; public const string Column_Buyers = "Buyers";
public const string Select_MarketItems = $"{Command_Select} {Command_All} {Command_From} {TableName}"; public const string Select_MarketItems = $"{Command_Select} {Command_All} {Command_From} {TableName}";
@ -35,14 +36,15 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
return $"{Select_MarketItems} {Command_Where} {Column_Status} = @Status"; return $"{Select_MarketItems} {Command_Where} {Column_Status} = @Status";
} }
public static string Insert_MarketItem(SQLHelper SQLHelper, Guid ItemGuid, long UserId, double Price, MarketItemState state = MarketItemState.Listed) public static string Insert_MarketItem(SQLHelper SQLHelper, Guid ItemGuid, long UserId, double Price, double Stock, MarketItemState state = MarketItemState.Listed)
{ {
SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString(); SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString();
SQLHelper.Parameters["@UserId"] = UserId; SQLHelper.Parameters["@UserId"] = UserId;
SQLHelper.Parameters["@Price"] = Price; SQLHelper.Parameters["@Price"] = Price;
SQLHelper.Parameters["@Stock"] = Stock;
SQLHelper.Parameters["@Status"] = (int)state; SQLHelper.Parameters["@Status"] = (int)state;
return $"{Command_Insert} {Command_Into} {TableName} ({Column_ItemGuid}, {Column_UserId}, {Column_Price}, {Column_Status}) {Command_Values} (@ItemId, @UserId, @Price, @Status)"; return $"{Command_Insert} {Command_Into} {TableName} ({Column_ItemGuid}, {Column_UserId}, {Column_Price}, {Column_Stock}, {Column_Status}) {Command_Values} (@ItemId, @UserId, @Price, @Stock, @Status)";
} }
public static string Update_MarketItemPrice(SQLHelper SQLHelper, Guid ItemGuid, double Price) public static string Update_MarketItemPrice(SQLHelper SQLHelper, Guid ItemGuid, double Price)
@ -51,6 +53,13 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
SQLHelper.Parameters["@Price"] = Price; SQLHelper.Parameters["@Price"] = Price;
return $"{Command_Update} {TableName} {Command_Set} {Column_Price} = @Price {Command_Where} {Column_ItemGuid} = @ItemGuid"; return $"{Command_Update} {TableName} {Command_Set} {Column_Price} = @Price {Command_Where} {Column_ItemGuid} = @ItemGuid";
} }
public static string Update_MarketItemStock(SQLHelper SQLHelper, Guid ItemGuid, double Stock)
{
SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString();
SQLHelper.Parameters["@Stock"] = Stock;
return $"{Command_Update} {TableName} {Command_Set} {Column_Stock} = @Stock {Command_Where} {Column_ItemGuid} = @ItemGuid";
}
public static string Update_MarketItemState(SQLHelper SQLHelper, Guid ItemGuid, MarketItemState state) public static string Update_MarketItemState(SQLHelper SQLHelper, Guid ItemGuid, MarketItemState state)
{ {
@ -59,12 +68,12 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
return $"{Command_Update} {TableName} {Command_Set} {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid"; return $"{Command_Update} {TableName} {Command_Set} {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid";
} }
public static string Update_Buy(SQLHelper SQLHelper, Guid ItemGuid, long Buyer) public static string Update_Buy(SQLHelper SQLHelper, Guid ItemGuid, string Buyers)
{ {
SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString(); SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString();
SQLHelper.Parameters["@Buyer"] = Buyer; SQLHelper.Parameters["@Buyers"] = Buyers;
SQLHelper.Parameters["@Status"] = (int)MarketItemState.Purchased; SQLHelper.Parameters["@Status"] = (int)MarketItemState.Purchased;
return $"{Command_Update} {TableName} {Command_Set} {Column_Buyer} = @Buyer, {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid"; return $"{Command_Update} {TableName} {Command_Set} {Column_Buyers} = @Buyers, {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid";
} }
public static string Update_MarketItemFinishTime(SQLHelper SQLHelper, Guid ItemGuid, DateTime FinishTime) public static string Update_MarketItemFinishTime(SQLHelper SQLHelper, Guid ItemGuid, DateTime FinishTime)

View File

@ -0,0 +1 @@
ALTER TABLE your_table_name CHANGE COLUMN Buyer Buyers VARCHAR(255) NOT NULL DEFAULT '';

View File

@ -0,0 +1,43 @@
PRAGMA foreign_keys = OFF;
BEGIN TRANSACTION;
ALTER TABLE MarketItems RENAME TO _MarketItems_old;
CREATE TABLE MarketItems (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ItemGuid TEXT NOT NULL DEFAULT '',
UserId INTEGER NOT NULL DEFAULT 0,
Price REAL NOT NULL DEFAULT 0,
CreateTime DATETIME NOT NULL DEFAULT (DATETIME('now')),
FinishTime DATETIME DEFAULT NULL,
Status INTEGER NOT NULL DEFAULT 0,
Buyers TEXT NOT NULL DEFAULT ''
);
INSERT INTO MarketItems (
Id,
ItemGuid,
UserId,
Price,
CreateTime,
FinishTime,
Status,
Buyers
)
SELECT
Id,
ItemGuid,
UserId,
Price,
CreateTime,
FinishTime,
Status,
CAST(Buyer AS TEXT)
FROM _MarketItems_old;
DROP TABLE _MarketItems_old;
COMMIT;
PRAGMA foreign_keys = ON;

View File

@ -95,7 +95,7 @@ CREATE TABLE `MarketItems` (
`CreateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `CreateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`FinishTime` datetime DEFAULT NULL, `FinishTime` datetime DEFAULT NULL,
`Status` int(10) NOT NULL DEFAULT '0', `Status` int(10) NOT NULL DEFAULT '0',
`Buyer` bigint(20) NOT NULL DEFAULT '0', `Buyers` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`Id`) PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -92,7 +92,7 @@ CREATE TABLE MarketItems (
CreateTime DATETIME NOT NULL DEFAULT (DATETIME('now')), CreateTime DATETIME NOT NULL DEFAULT (DATETIME('now')),
FinishTime DATETIME DEFAULT NULL, FinishTime DATETIME DEFAULT NULL,
Status INTEGER NOT NULL DEFAULT 0, Status INTEGER NOT NULL DEFAULT 0,
Buyer INTEGER NOT NULL DEFAULT 0 Buyers TEXT NOT NULL DEFAULT ''
); );
-- ---------------------------- -- ----------------------------

View File

@ -22,7 +22,8 @@ namespace Milimoe.FunGame.Core.Service
Converters = { new DateTimeConverter(), new DataTableConverter(), new DataSetConverter(), new UserConverter(), new RoomConverter(), Converters = { new DateTimeConverter(), new DataTableConverter(), new DataSetConverter(), new UserConverter(), new RoomConverter(),
new CharacterConverter(), new MagicResistanceConverter(), new EquipSlotConverter(), new SkillConverter(), new EffectConverter(), new ItemConverter(), new CharacterConverter(), new MagicResistanceConverter(), new EquipSlotConverter(), new SkillConverter(), new EffectConverter(), new ItemConverter(),
new InventoryConverter(), new NormalAttackConverter(), new ClubConverter(), new GoodsConverter(), new StoreConverter(), new InventoryConverter(), new NormalAttackConverter(), new ClubConverter(), new GoodsConverter(), new StoreConverter(),
new NovelOptionConverter(), new NovelNodeConverter(), new ShieldConverter(), new RoundRecordConverter(), new ActivityConverter(), new QuestConverter() new NovelOptionConverter(), new NovelNodeConverter(), new ShieldConverter(), new RoundRecordConverter(), new ActivityConverter(), new QuestConverter(),
new MarketConverter(), new MarketItemConverter()
} }
}; };