Files

465 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模块:CRB 数据模型
3CRB(Currency/Resource/Banking)货币经济系统,分两类表:
1. **MC 插件共享表**(managed=False):由 Minecraft 服务器 CME 经济插件
直接读写,Django 仅作 ORM 映射。表名为 snake_case、列名为 snake_case、
主键为 INT 自增、user_uuid 为 BINARY(16)(MC 玩家 UUID)。
Django 通过 ``db_column`` 映射,Python 属性保持 PascalCase。
2. **Django 独有表**(managed=True):游戏内企业注册、定期储值、
GVSDS 用户↔MC 玩家映射。表名与列名均为 PascalCase,由 Django 迁移管理。
所有 ORM 字段与方法遵循帕斯卡命名法(首字母大写、缩写大写、单单词小写)。
MC 插件表因外部 schema 约束,通过 ``db_column`` 保持 DB 侧 snake_case。"""
from gvsds.model_base import QModel
import datetime
from django.db import models
from gvsds.apps.core.models import UUIDField
# ============================================================================
# MC 插件共享表(managed=False)
# 由 Minecraft 服务器 CME 经济插件读写,Django 仅作 ORM 映射,不可迁移
# ============================================================================
class CmeCurrencies(QModel):
"""CME 币种表——MC 插件定义的币种
映射真实表 ``cme_currencies``(snake_case)。
PK 为 INT 自增 ``id``,与 MC 插件 schema 一致。
"""
CurrencyID = models.AutoField(primary_key=True, db_column='id', verbose_name='币种ID')
CurrencyName = models.CharField(max_length=255, unique=True, db_column='name', verbose_name='币种名称')
CurrencySymbol = models.CharField(max_length=255, db_column='symbol', verbose_name='币种符号')
Tradable = models.IntegerField(default=1, db_column='tradable', verbose_name='是否可交易')
CreatedAt = models.DateTimeField(auto_now_add=True, db_column='created_at', null=True, blank=True, verbose_name='创建时间')
class Meta:
db_table = 'cme_currencies'
app_label = 'crb'
managed = False
verbose_name = 'CME币种'
verbose_name_plural = 'CME币种'
def __str__(self):
return self.CurrencyName
class CmeBalances(QModel):
"""CME 钱包余额表——玩家货币余额
映射真实表 ``cme_balances``(snake_case)。
``UserUUID`` 存 MC 玩家 UUID(BINARY(16)),不是 GVSDS UserUUID。
GVSDS UserUUID ↔ MC PlayerUUID 通过 ``CmeWalletGVSDSMapping`` 映射。
"""
BalanceID = models.AutoField(primary_key=True, db_column='id', verbose_name='余额ID')
UserUUID = UUIDField(db_column='user_uuid', verbose_name='MC玩家UUID')
Currency = models.ForeignKey(
CmeCurrencies, on_delete=models.DO_NOTHING,
db_column='currency_id', related_name='+',
verbose_name='币种',
)
Balance = models.FloatField(default=0, db_column='balance', verbose_name='余额')
CreatedAt = models.DateTimeField(auto_now_add=True, db_column='created_at', null=True, blank=True, verbose_name='创建时间')
UpdatedAt = models.DateTimeField(auto_now=True, db_column='updated_at', null=True, blank=True, verbose_name='更新时间')
class Meta:
db_table = 'cme_balances'
app_label = 'crb'
managed = False
verbose_name = 'CME钱包余额'
verbose_name_plural = 'CME钱包余额'
unique_together = [('UserUUID', 'Currency')]
def __str__(self):
return f'{self.UserUUID}:{self.Balance}'
class CmeTransactions(QModel):
"""CME 交易流水表——所有币种转账记录
映射真实表 ``cme_transactions``(snake_case)。
``type`` 列在 Python 中用 ``TransactionType`` 属性访问(``type`` 是保留字)。
"""
TransactionID = models.AutoField(primary_key=True, db_column='id', verbose_name='交易ID')
FromUUID = UUIDField(db_column='from_uuid', null=True, blank=True, verbose_name='付款方MC玩家UUID')
ToUUID = UUIDField(db_column='to_uuid', null=True, blank=True, verbose_name='收款方MC玩家UUID')
Currency = models.ForeignKey(
CmeCurrencies, on_delete=models.DO_NOTHING,
db_column='currency_id', related_name='+',
verbose_name='币种',
)
Amount = models.FloatField(db_column='amount', verbose_name='金额')
TransactionType = models.CharField(max_length=255, db_column='type', verbose_name='交易类型')
Description = models.CharField(max_length=255, null=True, blank=True, db_column='description', verbose_name='描述')
CreatedAt = models.DateTimeField(auto_now_add=True, db_column='created_at', null=True, blank=True, verbose_name='创建时间')
class Meta:
db_table = 'cme_transactions'
app_label = 'crb'
managed = False
verbose_name = 'CME交易流水'
verbose_name_plural = 'CME交易流水'
ordering = ['-CreatedAt']
def __str__(self):
return f'{self.TransactionType}:{self.Amount}'
class CmePaymentRequests(QModel):
"""CME 付款请求表——玩家间付款请求
映射真实表 ``cme_payment_requests``(snake_case)。
"""
RequestID = models.AutoField(primary_key=True, db_column='id', verbose_name='请求ID')
FromUUID = UUIDField(db_column='from_uuid', verbose_name='付款方MC玩家UUID')
ToUUID = UUIDField(db_column='to_uuid', verbose_name='收款方MC玩家UUID')
Currency = models.ForeignKey(
CmeCurrencies, on_delete=models.DO_NOTHING,
db_column='currency_id', related_name='+',
verbose_name='币种',
)
Amount = models.FloatField(db_column='amount', verbose_name='金额')
Status = models.CharField(max_length=255, default='pending', db_column='status', verbose_name='状态')
CreatedAt = models.DateTimeField(auto_now_add=True, db_column='created_at', null=True, blank=True, verbose_name='创建时间')
class Meta:
db_table = 'cme_payment_requests'
app_label = 'crb'
managed = False
verbose_name = 'CME付款请求'
verbose_name_plural = 'CME付款请求'
ordering = ['-CreatedAt']
def __str__(self):
return f'{self.Status}:{self.Amount}'
class CmePlayerSettings(QModel):
"""CME 玩家设置表——MC 玩家语言等偏好
映射真实表 ``cme_player_settings``(snake_case)。
"""
SettingID = models.AutoField(primary_key=True, db_column='id', verbose_name='设置ID')
UserUUID = UUIDField(db_column='user_uuid', verbose_name='MC玩家UUID')
Language = models.CharField(max_length=50, default='en_us', db_column='language', verbose_name='语言')
CreatedAt = models.DateTimeField(auto_now_add=True, db_column='created_at', null=True, blank=True, verbose_name='创建时间')
UpdatedAt = models.DateTimeField(auto_now=True, db_column='updated_at', null=True, blank=True, verbose_name='更新时间')
class Meta:
db_table = 'cme_player_settings'
app_label = 'crb'
managed = False
verbose_name = 'CME玩家设置'
verbose_name_plural = 'CME玩家设置'
def __str__(self):
return f'{self.UserUUID}:{self.Language}'
class CmeExchangeRates(QModel):
"""CME 汇率表——币种间兑换汇率
映射真实表 ``cme_exchange_rates``(snake_case)。
"""
RateID = models.AutoField(primary_key=True, db_column='id', verbose_name='汇率ID')
CurrencyName = models.CharField(max_length=255, unique=True, db_column='currency_name', verbose_name='币种名称')
Rate = models.FloatField(default=1, db_column='rate', verbose_name='汇率')
CreatedAt = models.DateTimeField(auto_now_add=True, db_column='created_at', null=True, blank=True, verbose_name='创建时间')
UpdatedAt = models.DateTimeField(auto_now=True, db_column='updated_at', null=True, blank=True, verbose_name='更新时间')
class Meta:
db_table = 'cme_exchange_rates'
app_label = 'crb'
managed = False
verbose_name = 'CME汇率'
verbose_name_plural = 'CME汇率'
def __str__(self):
return f'{self.CurrencyName}:{self.Rate}'
# ============================================================================
# Django 独有表(managed=True)
# 游戏内企业注册、定期储值、GVSDS↔MC 玩家映射,由 Django 迁移管理
# ============================================================================
class CmeStoreValue(QModel):
"""CME 储值表——定期存款记录(GVSDS 业务,非 MC 插件)
玩家将游戏币存入定期,按游戏日计息,到期取现。
业务逻辑见旧 flask 的 Cash.api(计算游戏天数与利息)。
"""
StoreValueUUID = UUIDField(primary_key=True, verbose_name='储值UUID')
UserUUID = UUIDField(null=True, blank=True, verbose_name='用户UUID')
PlayerUUID = UUIDField(null=True, blank=True, verbose_name='玩家UUID')
PlayerName = models.TextField(null=True, blank=True, verbose_name='玩家名称')
StoreValueCreateDateTime = models.DateTimeField(null=True, blank=True, verbose_name='储值创建时间')
StoreValueType = models.IntegerField(null=True, blank=True, verbose_name='储值类型')
StoreValueMin = models.BigIntegerField(null=True, blank=True, verbose_name='储值最小值')
StoreValueDayInterestRate = models.SmallIntegerField(null=True, blank=True, verbose_name='日利率')
StoreValueAmount = models.BigIntegerField(null=True, blank=True, verbose_name='储值金额')
StoreValueExpirationGameDate = models.IntegerField(null=True, blank=True, verbose_name='到期游戏日期')
class Meta:
db_table = 'storevalue'
app_label = 'crb'
verbose_name = 'CME储值'
verbose_name_plural = 'CME储值'
class CmeWalletGVSDSMapping(QModel):
"""GVSDS 钱包映射表——GVSDS 用户与 MC 玩家关联
``UserUUID`` 为 GVSDS UserUUID(BINARY(16)),
``PlayerUUID`` 为 MC 玩家 UUID 字符串形式(varchar(36))。
用于 Web 端用户查询其 MC 钱包余额。
"""
MappingID = models.BigAutoField(primary_key=True, verbose_name='映射ID')
UserUUID = UUIDField(verbose_name='GVSDS用户UUID')
PlayerUUID = models.CharField(max_length=36, verbose_name='MC玩家UUID字符串')
class Meta:
db_table = 'cmewalletgvsds'
app_label = 'crb'
verbose_name = 'GVSDS钱包映射'
verbose_name_plural = 'GVSDS钱包映射'
unique_together = [('UserUUID', 'PlayerUUID')]
class Enterprise(QModel):
"""企业表——游戏内企业注册信息(GVSDS 业务,非 MC 插件)
注:旧表有拼写错误 ``EnterpriseAddres``(少 s),已修正为 ``EnterpriseAddress``。
"""
EnterpriseUUID = UUIDField(primary_key=True, verbose_name='企业UUID')
EnterpriseCreateCertificationCode = UUIDField(null=True, blank=True, verbose_name='企业认证码')
EnterpriseFatherUUID = UUIDField(null=True, blank=True, verbose_name='上级企业UUID')
EnterpriseName = models.TextField(null=True, blank=True, verbose_name='企业名称')
EnterpriseType = models.IntegerField(null=True, blank=True, verbose_name='企业类型')
EnterpriseStatus = models.IntegerField(null=True, blank=True, verbose_name='企业状态')
EnterpriseCreateTime = models.DateTimeField(null=True, blank=True, verbose_name='企业创建时间')
EnterpriseRegisteredCapital = models.FloatField(null=True, blank=True, verbose_name='注册资本')
EnterpriseCapitalGC = models.TextField(null=True, blank=True, verbose_name='资本GC')
EnterpriseCapital = models.FloatField(null=True, blank=True, verbose_name='资本')
EnterpriseTelephone = models.TextField(null=True, blank=True, verbose_name='企业电话')
EnterpriseEmail = models.TextField(null=True, blank=True, verbose_name='企业邮箱')
EnterpriseWebsite = models.TextField(null=True, blank=True, verbose_name='企业网站')
EnterpriseAddress = models.TextField(null=True, blank=True, verbose_name='企业地址')
EnterpriseDesc = models.TextField(null=True, blank=True, verbose_name='企业描述')
EnterpriseAdministrativeDivisions = models.TextField(null=True, blank=True, verbose_name='行政区划')
EnterpriseBusinessPeriod = models.BigIntegerField(null=True, blank=True, verbose_name='营业期限')
EnterpriseApprovalDate = models.DateTimeField(null=True, blank=True, verbose_name='审批日期')
EnterpriseBusinessScope = models.JSONField(null=True, blank=True, verbose_name='经营范围')
class Meta:
db_table = 'enterprise'
app_label = 'crb'
verbose_name = '企业'
verbose_name_plural = '企业'
def __str__(self):
return self.EnterpriseName or str(self.EnterpriseUUID)
class EnterpriseUser(QModel):
"""企业用户关联表——游戏内企业成员(GVSDS 业务,非 MC 插件)"""
EnterpriseSMTMUUID = UUIDField(primary_key=True, verbose_name='企业用户UUID')
EnterpriseUUID = models.ForeignKey(
Enterprise, on_delete=models.CASCADE,
to_field='EnterpriseUUID',
related_name='+', null=True, blank=True,
verbose_name='企业',
)
UserUUID = UUIDField(null=True, blank=True, verbose_name='用户UUID')
EnterpriseUserType = models.IntegerField(null=True, blank=True, verbose_name='企业用户类型')
class Meta:
db_table = 'enterpriseuser'
app_label = 'crb'
verbose_name = '企业用户'
verbose_name_plural = '企业用户'
# ============================================================================
# 商店系统(Django 独有表,managed=True)
# 淘宝/拼多多风格的游戏内商店:商品/购物车/订单/订单项
# 全部走 minecraft 库(crb app 路由),GC 虚拟币支付
# ============================================================================
# 订单状态枚举(与旧 flask 数字兼容,简化版状态机)
# 0=待付款 → 1=已付款(待发货) → 2=已发货(待收货) → 3=已完成
# 0/1 → 4=已取消
# 1/2 → 5=退款中 → 6=已退款
SHOP_ORDER_PENDING = 0
SHOP_ORDER_PAID = 1
SHOP_ORDER_SHIPPED = 2
SHOP_ORDER_COMPLETED = 3
SHOP_ORDER_CANCELLED = 4
SHOP_ORDER_REFUNDING = 5
SHOP_ORDER_REFUNDED = 6
SHOP_ORDER_STATUS_CHOICES = [
(SHOP_ORDER_PENDING, '待付款'),
(SHOP_ORDER_PAID, '已付款'),
(SHOP_ORDER_SHIPPED, '已发货'),
(SHOP_ORDER_COMPLETED, '已完成'),
(SHOP_ORDER_CANCELLED, '已取消'),
(SHOP_ORDER_REFUNDING, '退款中'),
(SHOP_ORDER_REFUNDED, '已退款'),
]
# 商品状态
SHOP_PRODUCT_OFFLINE = 0 # 下架
SHOP_PRODUCT_ONLINE = 1 # 在售
SHOP_PRODUCT_OUT_OF_STOCK = 2 # 缺货
class ShopProduct(QModel):
"""商店商品表——游戏内商品定义(GVSDS 业务,非 MC 插件)
设计说明:
- 单一规格(无 SPU/SKU 多规格),一个商品一条记录
- 价格以 GC 计价(CurrencyName='GC')
- 图片走 FileAsset 系统的 SHA1 引用,存 ShopProductImage 列为 SHA1 列表
- Stock=-1 表示无限库存
"""
ProductUUID = UUIDField(primary_key=True, verbose_name='商品UUID')
SellerUserUUID = UUIDField(verbose_name='卖家GVSDS用户UUID')
SellerPlayerName = models.CharField(max_length=64, null=True, blank=True, verbose_name='卖家玩家名')
ProductName = models.CharField(max_length=128, verbose_name='商品名称')
ProductDesc = models.TextField(null=True, blank=True, verbose_name='商品描述')
ProductPrice = models.FloatField(default=0, verbose_name='商品价格(GC)')
Stock = models.BigIntegerField(default=-1, verbose_name='库存(-1=无限)')
SoldCount = models.BigIntegerField(default=0, verbose_name='已售数量')
ProductImages = models.JSONField(default=list, blank=True, verbose_name='商品图片SHA1列表')
ProductTags = models.JSONField(default=list, blank=True, verbose_name='商品标签')
ProductCategory = models.CharField(max_length=64, null=True, blank=True, verbose_name='商品分类')
ProductStatus = models.SmallIntegerField(default=SHOP_PRODUCT_ONLINE, verbose_name='商品状态')
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'shop_product'
app_label = 'crb'
verbose_name = '商店商品'
verbose_name_plural = '商店商品'
ordering = ['-CreateTime']
def __str__(self):
return self.ProductName
class ShopCartItem(QModel):
"""购物车项——用户加入购物车的商品(GVSDS 业务)
UserUUID 为 GVSDS UserUUID(不是 MC 玩家 UUID)。
"""
CartItemUUID = UUIDField(primary_key=True, verbose_name='购物车项UUID')
UserUUID = UUIDField(verbose_name='GVSDS用户UUID')
Product = models.ForeignKey(
ShopProduct, on_delete=models.CASCADE,
to_field='ProductUUID',
related_name='+',
verbose_name='商品',
)
Quantity = models.BigIntegerField(default=1, verbose_name='数量')
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='加入时间')
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'shop_cart_item'
app_label = 'crb'
verbose_name = '购物车项'
verbose_name_plural = '购物车项'
# 同一用户同一商品唯一(避免重复加购)
unique_together = [('UserUUID', 'Product')]
class ShopOrder(QModel):
"""商店订单表——订单主表(GVSDS 业务,非 MC 插件)
状态机:
PENDING(0) → PAID(1) → SHIPPED(2) → COMPLETED(3)
PENDING(0) / PAID(1) → CANCELLED(4)
PAID(1) / SHIPPED(2) → REFUNDING(5) → REFUNDED(6)
BuyerUserUUID 为 GVSDS UserUUID,BuyerPlayerUUID 为 MC 玩家 UUID(BINARY16)。
订单号 OrderNO 格式:S + YYYYMMDDHHMMSS + 6位随机数。
"""
OrderUUID = UUIDField(primary_key=True, verbose_name='订单UUID')
OrderNO = models.CharField(max_length=64, unique=True, verbose_name='订单编号')
BuyerUserUUID = UUIDField(verbose_name='买家GVSDS用户UUID')
BuyerPlayerUUID = UUIDField(verbose_name='买家MC玩家UUID')
BuyerPlayerName = models.CharField(max_length=64, null=True, blank=True, verbose_name='买家玩家名')
TotalAmount = models.FloatField(default=0, verbose_name='订单总金额(GC)')
OrderStatus = models.SmallIntegerField(
default=SHOP_ORDER_PENDING, choices=SHOP_ORDER_STATUS_CHOICES,
verbose_name='订单状态',
)
Remark = models.CharField(max_length=512, null=True, blank=True, verbose_name='订单备注')
PayTime = models.DateTimeField(null=True, blank=True, verbose_name='付款时间')
ShipTime = models.DateTimeField(null=True, blank=True, verbose_name='发货时间')
CompleteTime = models.DateTimeField(null=True, blank=True, verbose_name='完成时间')
CancelTime = models.DateTimeField(null=True, blank=True, verbose_name='取消时间')
RefundTime = models.DateTimeField(null=True, blank=True, verbose_name='退款时间')
RefundReason = models.CharField(max_length=512, null=True, blank=True, verbose_name='退款原因')
CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'shop_order'
app_label = 'crb'
verbose_name = '商店订单'
verbose_name_plural = '商店订单'
ordering = ['-CreateTime']
def __str__(self):
return self.OrderNO
class ShopOrderItem(QModel):
"""商店订单项——订单的商品明细(含商品快照)
商品快照:下单时冻结 ProductName/Price/Image,避免商品后续修改影响历史订单。
"""
OrderItemUUID = UUIDField(primary_key=True, verbose_name='订单项UUID')
Order = models.ForeignKey(
ShopOrder, on_delete=models.CASCADE,
to_field='OrderUUID',
related_name='items',
verbose_name='订单',
)
Product = models.ForeignKey(
ShopProduct, on_delete=models.SET_NULL,
to_field='ProductUUID', null=True, blank=True,
related_name='+',
verbose_name='商品',
)
ProductSnapshot = models.JSONField(verbose_name='商品快照')
Quantity = models.BigIntegerField(default=1, verbose_name='购买数量')
UnitPrice = models.FloatField(default=0, verbose_name='单价(GC)')
Subtotal = models.FloatField(default=0, verbose_name='小计(GC)')
class Meta:
db_table = 'shop_order_item'
app_label = 'crb'
verbose_name = '商店订单项'
verbose_name_plural = '商店订单项'