Browse Source

增加商店的后台API

master
秦汉 3 weeks ago
parent
commit
b0ff092f26
  1. 13
      Build_God_Api/Build_God_Api/Controllers/BagController.cs
  2. 78
      Build_God_Api/Build_God_Api/Controllers/ShopController.cs
  3. 5
      Build_God_Api/Build_God_Api/DB/BagItem.cs
  4. 5
      Build_God_Api/Build_God_Api/DB/Equipment.cs
  5. 89
      Build_God_Api/Build_God_Api/DB/Shop.cs
  6. 1
      Build_God_Api/Build_God_Api/Program.cs
  7. 29
      Build_God_Api/Build_God_Api/Services/BagService.cs
  8. 353
      Build_God_Api/Build_God_Api/Services/ShopService.cs

13
Build_God_Api/Build_God_Api/Controllers/BagController.cs

@ -100,6 +100,13 @@ namespace Build_God_Api.Controllers
return await service.RemoveItemFromBag(characterBagId, itemId);
}
[HttpPost("{characterBagId}/items/destroy")]
[Authorize]
public async Task<ActionResult<bool>> DestroyBagItem(int characterBagId, [FromBody] DestroyBagItemDto dto)
{
return await service.DestroyBagItem(characterBagId, dto.ItemType, dto.ItemDbId);
}
// ============ 枚举 ============
[HttpGet("rarities")]
@ -129,4 +136,10 @@ namespace Build_God_Api.Controllers
public int ItemId { get; set; }
public int Quantity { get; set; }
}
public class DestroyBagItemDto
{
public int ItemType { get; set; }
public int ItemDbId { get; set; }
}
}

78
Build_God_Api/Build_God_Api/Controllers/ShopController.cs

@ -0,0 +1,78 @@
using Build_God_Api.DB;
using Build_God_Api.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Build_God_Api.Controllers
{
[ApiController]
[Route("api/god/[controller]")]
public class ShopController(
IShopService service,
ICurrentUserService currentUserService
) : ControllerBase
{
private readonly IShopService service = service;
private readonly ICurrentUserService currentUserService = currentUserService;
// ============ 商店物品配置管理(后台) ============
[HttpGet("admin/all")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<List<ShopItem>>> GetAllShopItems()
{
return await service.GetAllShopItems();
}
[HttpGet("admin/{id}")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<ShopItem?>> GetShopItemById(int id)
{
return await service.GetShopItemById(id);
}
[HttpPost("admin")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> CreateShopItem([FromBody] ShopItem item)
{
return await service.CreateShopItem(item);
}
[HttpPut("admin/{id}")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> UpdateShopItem(int id, [FromBody] ShopItem item)
{
item.Id = id;
return await service.UpdateShopItem(item);
}
[HttpDelete("admin/{id}")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<bool>> DeleteShopItem(int id)
{
return await service.DeleteShopItem(id);
}
// ============ 玩家商店 ============
[HttpGet]
[Authorize]
public async Task<ActionResult<ShopDto>> GetCharacterShop()
{
var characterList = await service.GetCharacterShop(currentUserService.UserId);
return characterList;
}
[HttpPost("buy")]
[Authorize]
public async Task<ActionResult<bool>> BuyItem([FromBody] ShopBuyDto dto)
{
return await service.BuyItem(currentUserService.UserId, dto.ShopItemId);
}
}
public class ShopBuyDto
{
public int ShopItemId { get; set; }
}
}

5
Build_God_Api/Build_God_Api/DB/BagItem.cs

@ -27,6 +27,11 @@ namespace Build_God_Api.DB
/// 数量
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// 是否绑定(绑定后不可交易/拍卖)
/// </summary>
public bool IsBound { get; set; }
}
/// <summary>

5
Build_God_Api/Build_God_Api/DB/Equipment.cs

@ -121,6 +121,11 @@ namespace Build_God_Api.DB
/// </summary>
[SugarColumn(IsNullable = true)]
public int? SetId { get; set; }
/// <summary>
/// 是否绑定(绑定后不可交易/拍卖)
/// </summary>
public bool IsBound { get; set; }
}
/// <summary>

89
Build_God_Api/Build_God_Api/DB/Shop.cs

@ -0,0 +1,89 @@
using SqlSugar;
using System.ComponentModel;
namespace Build_God_Api.DB
{
/// <summary>
/// 商店物品配置表 - 维护可售卖的商品库
/// </summary>
public class ShopItem : BaseEntity
{
/// <summary>
/// 物品类型
/// </summary>
public BagItemType ItemType { get; set; }
/// <summary>
/// 物品ID (装备模板ID/丹药ID/垃圾ID)
/// </summary>
public int ItemId { get; set; }
/// <summary>
/// 灵石价格
/// </summary>
public int Price { get; set; }
/// <summary>
/// 每日限购次数(0表示不限购)
/// </summary>
public int DailyLimit { get; set; }
/// <summary>
/// 是否启用
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// 排序权重(越大越优先出现)
/// </summary>
public int SortOrder { get; set; }
}
/// <summary>
/// 玩家商店状态 - 记录每个玩家的商店数据
/// </summary>
public class CharacterShop : BaseEntity
{
/// <summary>
/// 角色ID
/// </summary>
public int CharacterId { get; set; }
/// <summary>
/// 上次刷新时间
/// </summary>
public DateTime LastRefreshTime { get; set; }
/// <summary>
/// 当前商品列表(JSON)- 存储当前刷出来的商品及库存
/// 格式: [{"ShopItemId":1,"Stock":5},{"ShopItemId":2,"Stock":3}]
/// </summary>
public string ItemsJson { get; set; } = "[]";
}
/// <summary>
/// 玩家商店购买记录 - 记录每日购买次数
/// </summary>
public class CharacterShopPurchaseLog : BaseEntity
{
/// <summary>
/// 角色ID
/// </summary>
public int CharacterId { get; set; }
/// <summary>
/// 商店物品配置ID
/// </summary>
public int ShopItemId { get; set; }
/// <summary>
/// 当日购买次数
/// </summary>
public int PurchasedCount { get; set; }
/// <summary>
/// 记录日期(yyyy-MM-dd)
/// </summary>
public string PurchaseDate { get; set; } = string.Empty;
}
}

1
Build_God_Api/Build_God_Api/Program.cs

@ -152,6 +152,7 @@ namespace Build_God_Api
builder.Services.AddSingleton<ICharacterAttributeCalculateService, CharacterAttributeCalculateService>();
builder.Services.AddScoped<IStatisticsService, StatisticsService>();
builder.Services.AddScoped<IBagService, BagService>();
builder.Services.AddScoped<IShopService, ShopService>();
builder.Services.AddScoped<IMissionProgressService, MissionProgressService>();
builder.Services.AddScoped<IDailyMissionService, DailyMissionService>();
builder.Services.AddHostedService<DailyMissionHostedService>();

29
Build_God_Api/Build_God_Api/Services/BagService.cs

@ -26,6 +26,9 @@ namespace Build_God_Api.Services
// 物品数量管理
Task<int> GetItemQuantity(int characterBagId, int itemType, int itemId);
Task<bool> ReduceItemQuantity(int characterBagId, int itemType, int itemId, int quantity);
// 物品销毁
Task<bool> DestroyBagItem(int characterBagId, int itemType, int itemDbId);
}
public class BagItemDto
@ -349,5 +352,31 @@ namespace Build_God_Api.Services
return true;
}
public async Task<bool> DestroyBagItem(int characterBagId, int itemType, int itemDbId)
{
if (itemType == (int)BagItemType.Equipment)
{
var equipment = await db.Queryable<EquipmentInstance>()
.FirstAsync(x => x.Id == itemDbId && x.CharacterBagId == characterBagId);
if (equipment == null)
throw new Exception("装备不存在");
await db.Deleteable(equipment).ExecuteCommandAsync();
}
else
{
var item = await db.Queryable<BagItem>()
.FirstAsync(x => x.Id == itemDbId && x.CharacterBagId == characterBagId);
if (item == null)
throw new Exception("物品不存在");
await db.Deleteable(item).ExecuteCommandAsync();
}
return true;
}
}
}

353
Build_God_Api/Build_God_Api/Services/ShopService.cs

@ -0,0 +1,353 @@
using Build_God_Api.DB;
using Build_God_Api.Dto;
using SqlSugar;
using System.Text.Json;
namespace Build_God_Api.Services
{
public interface IShopService
{
// 商店物品配置管理(后台)
Task<List<ShopItem>> GetAllShopItems();
Task<ShopItem?> GetShopItemById(int id);
Task<bool> CreateShopItem(ShopItem item);
Task<bool> UpdateShopItem(ShopItem item);
Task<bool> DeleteShopItem(int id);
// 玩家商店
Task<ShopDto> GetCharacterShop(int characterId);
Task<bool> BuyItem(int characterId, int shopItemId);
}
public class ShopItemDisplayDto
{
public int ShopItemId { get; set; }
public int ItemType { get; set; }
public int ItemId { get; set; }
public string? ItemName { get; set; }
public int Price { get; set; }
public int DailyLimit { get; set; }
public int PurchasedToday { get; set; }
public int RemainingStock { get; set; }
public string? Icon { get; set; }
public int? ItemRarity { get; set; }
}
public class ShopDto
{
public int CharacterId { get; set; }
public DateTime LastRefreshTime { get; set; }
public List<ShopItemDisplayDto> Items { get; set; } = new();
}
public class ShopCurrentItem
{
public int ShopItemId { get; set; }
public int Stock { get; set; }
}
public class ShopService(ISqlSugarClient db, IBagService bagService) : IShopService
{
private readonly ISqlSugarClient db = db;
private readonly IBagService bagService = bagService;
private const int RefreshIntervalHours = 4;
private const int ShopItemCount = 6;
// ============ 商店物品配置管理 ============
public async Task<List<ShopItem>> GetAllShopItems()
{
return await db.Queryable<ShopItem>()
.OrderBy(x => x.SortOrder, OrderByType.Desc)
.ToListAsync();
}
public async Task<ShopItem?> GetShopItemById(int id)
{
return await db.Queryable<ShopItem>().FirstAsync(x => x.Id == id);
}
public async Task<bool> CreateShopItem(ShopItem item)
{
await db.Insertable(item).ExecuteCommandAsync();
return true;
}
public async Task<bool> UpdateShopItem(ShopItem item)
{
var existing = await GetShopItemById(item.Id);
if (existing == null)
throw new Exception("商店物品不存在");
existing.ItemType = item.ItemType;
existing.ItemId = item.ItemId;
existing.Price = item.Price;
existing.DailyLimit = item.DailyLimit;
existing.IsActive = item.IsActive;
existing.SortOrder = item.SortOrder;
await db.Updateable(existing).ExecuteCommandAsync();
return true;
}
public async Task<bool> DeleteShopItem(int id)
{
await db.Deleteable<ShopItem>().Where(x => x.Id == id).ExecuteCommandAsync();
return true;
}
// ============ 玩家商店 ============
public async Task<ShopDto> GetCharacterShop(int characterId)
{
var characterShop = await db.Queryable<CharacterShop>()
.FirstAsync(x => x.CharacterId == characterId);
var now = DateTime.Now;
var needRefresh = characterShop == null ||
(now - characterShop.LastRefreshTime).TotalHours >= RefreshIntervalHours;
if (needRefresh)
{
characterShop = await RefreshShop(characterId);
}
return await BuildShopDto(characterId, characterShop);
}
public async Task<bool> BuyItem(int characterId, int shopItemId)
{
var character = await db.Queryable<Character>().FirstAsync(x => x.Id == characterId)
?? throw new Exception("角色不存在");
var shopItem = await GetShopItemById(shopItemId)
?? throw new Exception("商店物品不存在");
if (!shopItem.IsActive)
throw new Exception("该物品已下架");
// 检查灵石是否足够
if (character.Money < shopItem.Price)
throw new Exception("灵石不足");
var characterShop = await db.Queryable<CharacterShop>()
.FirstAsync(x => x.CharacterId == characterId)
?? throw new Exception("玩家商店数据不存在");
// 检查库存
var currentItems = JsonSerializer.Deserialize<List<ShopCurrentItem>>(characterShop.ItemsJson) ?? new();
var currentItem = currentItems.FirstOrDefault(x => x.ShopItemId == shopItemId);
if (currentItem == null || currentItem.Stock <= 0)
throw new Exception("该物品已售罄");
// 检查限购
var purchaseLog = shopItem.DailyLimit > 0 ? await db.Queryable<CharacterShopPurchaseLog>()
.FirstAsync(x => x.CharacterId == characterId
&& x.ShopItemId == shopItemId
&& x.PurchaseDate == DateTime.Now.ToString("yyyy-MM-dd")) : null;
if (shopItem.DailyLimit > 0)
{
var purchasedCount = purchaseLog?.PurchasedCount ?? 0;
if (purchasedCount >= shopItem.DailyLimit)
throw new Exception("已达到每日购买上限");
}
// 扣除灵石
character.Money -= shopItem.Price;
await db.Updateable(character).ExecuteCommandAsync();
// 添加物品到背包
var characterBag = await db.Queryable<CharacterBag>()
.FirstAsync(x => x.CharacterId == characterId)
?? throw new Exception("角色背包不存在");
// 装备特殊处理:创建装备实例
if (shopItem.ItemType == BagItemType.Equipment)
{
await CreateEquipmentInstance(characterBag.Id, shopItem.ItemId);
}
else
{
await bagService.AddItemToBag(characterBag.Id, (int)shopItem.ItemType, shopItem.ItemId, 1);
}
// 更新库存
currentItem.Stock--;
characterShop.ItemsJson = JsonSerializer.Serialize(currentItems);
await db.Updateable(characterShop).ExecuteCommandAsync();
// 记录购买次数
var today2 = DateTime.Now.ToString("yyyy-MM-dd");
if (purchaseLog == null)
{
purchaseLog = new CharacterShopPurchaseLog
{
CharacterId = characterId,
ShopItemId = shopItemId,
PurchasedCount = 1,
PurchaseDate = today2
};
await db.Insertable(purchaseLog).ExecuteCommandAsync();
}
else
{
purchaseLog.PurchasedCount++;
await db.Updateable(purchaseLog).ExecuteCommandAsync();
}
return true;
}
// ============ 私有方法 ============
private async Task<CharacterShop> RefreshShop(int characterId)
{
var now = DateTime.Now;
// 获取所有启用的商品,按权重排序后随机抽取
var allItems = await db.Queryable<ShopItem>()
.Where(x => x.IsActive)
.OrderBy(x => x.SortOrder, OrderByType.Desc)
.ToListAsync();
if (!allItems.Any())
{
var newCharacterShop = new CharacterShop
{
CharacterId = characterId,
LastRefreshTime = now,
ItemsJson = "[]"
};
await db.Insertable(newCharacterShop).ExecuteCommandAsync();
return newCharacterShop;
}
// 随机抽取商品
var selectedItems = new List<ShopCurrentItem>();
var random = new Random();
var count = Math.Min(ShopItemCount, allItems.Count);
var shuffled = allItems.OrderBy(_ => random.Next()).Take(count).ToList();
foreach (var item in shuffled)
{
selectedItems.Add(new ShopCurrentItem
{
ShopItemId = item.Id,
Stock = 99 // 库存充足
});
}
var characterShop = await db.Queryable<CharacterShop>()
.FirstAsync(x => x.CharacterId == characterId);
if (characterShop == null)
{
characterShop = new CharacterShop
{
CharacterId = characterId,
LastRefreshTime = now,
ItemsJson = JsonSerializer.Serialize(selectedItems)
};
await db.Insertable(characterShop).ExecuteCommandAsync();
}
else
{
characterShop.LastRefreshTime = now;
characterShop.ItemsJson = JsonSerializer.Serialize(selectedItems);
await db.Updateable(characterShop).ExecuteCommandAsync();
}
return characterShop;
}
private async Task<ShopDto> BuildShopDto(int characterId, CharacterShop characterShop)
{
var currentItems = JsonSerializer.Deserialize<List<ShopCurrentItem>>(characterShop.ItemsJson) ?? new();
var today = DateTime.Now.ToString("yyyy-MM-dd");
var result = new ShopDto
{
CharacterId = characterId,
LastRefreshTime = characterShop.LastRefreshTime,
Items = new List<ShopItemDisplayDto>()
};
foreach (var current in currentItems)
{
var shopItem = await GetShopItemById(current.ShopItemId);
if (shopItem == null || !shopItem.IsActive)
continue;
var display = new ShopItemDisplayDto
{
ShopItemId = shopItem.Id,
ItemType = (int)shopItem.ItemType,
ItemId = shopItem.ItemId,
Price = shopItem.Price,
DailyLimit = shopItem.DailyLimit,
RemainingStock = current.Stock
};
// 获取物品详情
if (shopItem.ItemType == BagItemType.Equipment)
{
var equip = await db.Queryable<EquipmentTemplate>().FirstAsync(x => x.Id == shopItem.ItemId);
display.ItemName = equip?.Name;
display.Icon = equip?.Icon;
display.ItemRarity = equip != null ? (int)equip.Rarity : null;
}
else if (shopItem.ItemType == BagItemType.Pill)
{
var pill = await db.Queryable<Pill>().FirstAsync(x => x.Id == shopItem.ItemId);
display.ItemName = pill?.Name;
display.Icon = pill?.Icon;
}
else if (shopItem.ItemType == BagItemType.Scrap)
{
var scrap = await db.Queryable<Scrap>().FirstAsync(x => x.Id == shopItem.ItemId);
display.ItemName = scrap?.Name;
display.Icon = scrap?.Icon;
}
// 获取今日购买次数
if (shopItem.DailyLimit > 0)
{
var purchaseLog = await db.Queryable<CharacterShopPurchaseLog>()
.FirstAsync(x => x.CharacterId == characterId
&& x.ShopItemId == shopItem.Id
&& x.PurchaseDate == today);
display.PurchasedToday = purchaseLog?.PurchasedCount ?? 0;
}
result.Items.Add(display);
}
return result;
}
private async Task CreateEquipmentInstance(int characterBagId, int equipmentTemplateId)
{
var template = await db.Queryable<EquipmentTemplate>()
.FirstAsync(x => x.Id == equipmentTemplateId)
?? throw new Exception("装备模板不存在");
var instance = new EquipmentInstance
{
CharacterBagId = characterBagId,
EquipmentTemplateId = equipmentTemplateId,
Name = template.Name,
Type = template.Type,
Rarity = template.Rarity,
Attributes = "[]",
EnhanceLevel = 0,
EnhanceBonusPercent = 0,
RequirdLevelId = template.RequirdLevelId,
SetId = template.SetId,
IsBound = true
};
await db.Insertable(instance).ExecuteCommandAsync();
}
}
}
Loading…
Cancel
Save