[Enhance] API for redeem

- Add API endpoints for redeem system
- Continue to fix the bug mentioned in 930faf508d
This commit is contained in:
Lost-MSth
2023-02-28 18:28:09 +08:00
parent 930faf508d
commit a84ec560dd
15 changed files with 283 additions and 50 deletions

View File

@@ -1,6 +1,7 @@
from flask import Blueprint
from . import (users, songs, token, system, items, purchases, presents)
from . import (users, songs, token, system, items,
purchases, presents, redeems)
bp = Blueprint('api', __name__, url_prefix='/api/v1')
bp.register_blueprint(users.bp)
@@ -10,3 +11,4 @@ bp.register_blueprint(system.bp)
bp.register_blueprint(items.bp)
bp.register_blueprint(purchases.bp)
bp.register_blueprint(presents.bp)
bp.register_blueprint(redeems.bp)

View File

@@ -105,7 +105,7 @@ def presents_present_items_patch(data, user, present_id: str):
p = Present(c)
p.present_id = present_id
p.select_items()
p.delete_items([ItemFactory.from_dict(x, c=c)
p.remove_items([ItemFactory.from_dict(x, c=c)
for x in data.get('remove', [])])
p.add_items([ItemFactory.from_dict(x, c=c)
for x in data.get('create', [])])

View File

@@ -119,7 +119,7 @@ def purchases_purchase_items_patch(data, user, purchase_name: str):
p = Purchase(c)
p.purchase_name = purchase_name
p.select_items()
p.delete_items([ItemFactory.from_dict(x, c=c)
p.remove_items([ItemFactory.from_dict(x, c=c)
for x in data.get('remove', [])])
p.add_items([ItemFactory.from_dict(x, c=c)
for x in data.get('create', [])])

View File

@@ -0,0 +1,119 @@
from flask import Blueprint, request
from core.error import DataExist, InputError, NoData
from core.item import ItemFactory
from core.redeem import Redeem
from core.sql import Connect, Query, Sql
from .api_auth import api_try, request_json_handle, role_required
from .api_code import success_return
from .constant import Constant
bp = Blueprint('redeems', __name__, url_prefix='/redeems')
@bp.route('', methods=['GET'])
@role_required(request, ['select'])
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
@api_try
def redeems_get(data, user):
'''查询全redeem信息'''
with Connect() as c:
query = Query(['code', 'type'], ['code'], ['code']).from_dict(data)
x = Sql(c).select('redeem', query=query)
r = [Redeem().from_list(i) for i in x]
if not r:
raise NoData(api_error_code=-2)
return success_return([x.to_dict(has_items=False) for x in r])
@bp.route('', methods=['POST'])
@role_required(request, ['insert'])
@request_json_handle(request, required_keys=['code', 'type'], optional_keys=['items'])
@api_try
def redeems_post(data, user):
'''添加redeem注意可以有items不存在的item会自动创建'''
with Connect() as c:
r = Redeem(c).from_dict(data)
if r.select_exists():
raise DataExist(
f'redeem `{r.code}` already exists')
r.insert_all()
return success_return(r.to_dict(has_items='items' in data))
@bp.route('/<string:code>', methods=['GET'])
@role_required(request, ['select'])
@api_try
def redeems_redeem_get(user, code: str):
'''查询单个redeem信息'''
with Connect() as c:
r = Redeem(c).select(code)
r.select_items()
return success_return(r.to_dict())
@bp.route('/<string:code>', methods=['DELETE'])
@role_required(request, ['delete'])
@api_try
def redeems_redeem_delete(user, code: str):
'''删除redeem会连带删除redeem_item'''
with Connect() as c:
Redeem(c).select(code).delete_all()
return success_return()
@bp.route('/<string:code>', methods=['PUT'])
@role_required(request, ['change'])
@request_json_handle(request, optional_keys=['type'], must_change=True)
@api_try
def redeems_redeem_put(data, user, code: str):
'''更新redeem信息注意不能有items'''
with Connect() as c:
r = Redeem(c).select(code)
if 'type' in data:
r.redeem_type = int(data['type'])
r.update()
return success_return(r.to_dict(has_items=False))
@bp.route('/<string:code>/items', methods=['GET'])
@role_required(request, ['select'])
@api_try
def redeems_redeem_items_get(user, code: str):
'''查询redeem的items'''
with Connect() as c:
r = Redeem(c)
r.code = code
r.select_items()
return success_return([x.to_dict(has_is_available=True) for x in r.items])
@bp.route('/<string:code>/items', methods=['PATCH'])
@role_required(request, ['change'])
@request_json_handle(request, is_batch=True)
@api_try
def redeems_redeem_items_patch(data, user, code: str):
'''增删改单个redeem的items'''
with Connect() as c:
r = Redeem(c)
r.code = code
r.select_items()
r.remove_items([ItemFactory.from_dict(x, c=c)
for x in data.get('remove', [])])
r.add_items([ItemFactory.from_dict(x, c=c)
for x in data.get('create', [])])
updates = data.get('update', [])
for x in updates:
if 'amount' not in x:
raise InputError('`amount` is required in `update`')
if not isinstance(x['amount'], int) or x['amount'] <= 0:
raise InputError(
'`amount` must be a positive integer', api_error_code=-101)
r.update_items([ItemFactory.from_dict(x, c=c) for x in updates])
return success_return([x.to_dict(has_is_available=True) for x in r.items])

View File

@@ -111,7 +111,9 @@ class CharacterValue:
class Character:
database_table_name = None
def __init__(self) -> None:
def __init__(self, c=None) -> None:
self.c = c
self.character_id = None
self.name = None
self.char_type = None
@@ -142,6 +144,12 @@ class Character:
# 应该是只有对立这样
return self.character_id == 1
def to_dict(self) -> dict:
pass
def from_list(self, l: list) -> 'Character':
pass
class UserCharacter(Character):
'''

View File

@@ -42,9 +42,9 @@ class Item:
(self.item_id, self.item_type))
return bool(self.c.fetchone()[0])
def insert(self):
self.c.execute('''insert into item values(?,?,?)''',
(self.item_id, self.item_type, self.is_available))
def insert(self, ignore: bool = False):
sql = '''insert into item values(?,?,?)''' if not ignore else '''insert or ignore into item values(?,?,?)'''
self.c.execute(sql, (self.item_id, self.item_type, self.is_available))
def delete(self):
self.c.execute('''delete from item where item_id=? and type=?''',

View File

@@ -116,8 +116,8 @@ class RemoteMultiPlayer:
self.data_recv = received.split('|')
if self.data_recv[0] != '0':
raise ArcError('Link Play error.',
int(self.data_recv[0]), status=400)
code = int(self.data_recv[0])
raise ArcError(f'Link Play error code: {code}', code, status=400)
def create_room(self, user: 'Player' = None) -> None:
'''创建房间'''

View File

@@ -31,9 +31,8 @@ class Present:
self.present_id = d['present_id']
self.expire_ts = int(d['expire_ts'])
self.description = d.get('description', '')
self.items = []
for i in d.get('items', []):
self.items.append(ItemFactory.from_dict(i))
self.items = [ItemFactory.from_dict(
i, c=self.c) for i in d.get('items', [])]
return self
def from_list(self, l: list) -> 'Present':
@@ -110,7 +109,7 @@ class Present:
self.c.execute('''update present set expire_ts=?, description=? where present_id=?''',
(self.expire_ts, self.description, self.present_id))
def delete_items(self, items: list) -> None:
def remove_items(self, items: list) -> None:
'''删除present_item表中的物品'''
for i in items:
if i not in self.items:

View File

@@ -225,7 +225,7 @@ class Purchase:
(self.purchase_name, i.item_id, i.item_type, i.amount) for i in items])
self.items.extend(items)
def delete_items(self, items: list) -> None:
def remove_items(self, items: list) -> None:
'''删除purchase_item表'''
for i in items:
if i not in self.items:

View File

@@ -1,4 +1,4 @@
from .error import NoData, RedeemUnavailable
from .error import DataExist, NoData, RedeemUnavailable
from .item import ItemFactory
@@ -11,29 +11,117 @@ class Redeem:
self.items: list = []
self.fragment: int = None
def select(self, code: str = None) -> None:
def to_dict(self, has_items: bool = True) -> dict:
r = {
'code': self.code,
'type': self.redeem_type
}
if has_items:
r['items'] = [x.to_dict() for x in self.items]
return r
def from_dict(self, d: dict) -> 'Redeem':
self.code = str(d['code'])
self.redeem_type = int(d.get('type') or d.get('redeem_type', 0))
self.items = [ItemFactory.from_dict(
i, c=self.c) for i in d.get('items', [])]
return self
def from_list(self, l: list) -> 'Redeem':
self.code = l[0]
self.redeem_type = l[1]
return self
def select_exists(self) -> bool:
self.c.execute(
'''select exists(select * from redeem where code=?)''', (self.code,))
return bool(self.c.fetchone()[0])
def select(self, code: str = None) -> 'Redeem':
if code:
self.code = code
self.c.execute('''select * from redeem where code=:a''',
{'a': self.code})
x = self.c.fetchone()
if x is None:
raise NoData('The redeem `%s` does not exist.' % self.code, 504)
raise NoData(f'The redeem `{self.code}` does not exist.', 504)
self.redeem_type = x[1]
return self
def select_items(self) -> None:
self.c.execute('''select * from redeem_item where code=:a''',
{'a': self.code})
x = self.c.fetchall()
if not x:
raise NoData(
'The redeem `%s` does not have any items.' % self.code)
self.items = [ItemFactory.from_dict({
'item_id': i[1],
'type': i[2],
'amount': i[3] if i[3] else 1
}, self.c) for i in x]
}, self.c) for i in self.c.fetchall()]
def insert(self) -> None:
self.c.execute('''insert into redeem values(?,?)''',
(self.code, self.redeem_type))
def insert_items(self) -> None:
for i in self.items:
i.insert(ignore=True)
self.c.execute('''insert into redeem_item values(?,?,?,?)''', (
self.code, i.item_id, i.item_type, i.amount))
def insert_all(self) -> None:
self.insert()
self.insert_items()
def delete(self) -> None:
self.c.execute('''delete from redeem where code=?''', (self.code,))
def delete_redeem_item(self) -> None:
self.c.execute(
'''delete from redeem_item where code=?''', (self.code,))
def delete_all(self) -> None:
self.delete_redeem_item()
self.delete()
def update(self) -> None:
self.c.execute('''update redeem set type=? where code=?''',
(self.redeem_type, self.code))
def remove_items(self, items: list) -> None:
'''删除redeem_item表中的物品'''
for i in items:
if i not in self.items:
raise NoData(
f'No such item `{i.item_type}`: `{i.item_id}` in redeem `{self.code}`', api_error_code=-124)
self.c.executemany('''delete from redeem_item where code=? and item_id=? and type=?''', [
(self.code, i.item_id, i.item_type) for i in items])
for i in items:
self.items.remove(i)
def add_items(self, items: list) -> None:
'''添加物品到redeem_item表'''
for i in items:
if not i.select_exists():
raise NoData(
f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121)
if i in self.items:
raise DataExist(
f'Item `{i.item_type}`: `{i.item_id}` already exists in redeem `{self.code}`', api_error_code=-123)
self.c.executemany('''insert into redeem_item values(?,?,?,?)''', [
(self.code, i.item_id, i.item_type, i.amount) for i in items])
self.items.extend(items)
def update_items(self, items: list) -> None:
'''更新redeem_item表中的物品'''
for i in items:
if i not in self.items:
raise NoData(
f'No such item `{i.item_type}`: `{i.item_id}` in redeem `{self.code}`', api_error_code=-124)
self.c.executemany('''update redeem_item set amount=? where code=? and item_id=? and type=?''', [
(i.amount, self.code, i.item_id, i.item_type) for i in items])
for i in items:
self.items[self.items.index(i)].amount = i.amount
class UserRedeem(Redeem):

View File

@@ -15,7 +15,7 @@ class Config:
--------------------------------------------------
'''
IS_DEBUG = False
DEBUG = False
TIME_LIMIT = 3600000

View File

@@ -30,9 +30,9 @@ class UDP_handler(socketserver.BaseRequestHandler):
logging.error(e)
return None
if Config.IS_DEBUG:
logging.info(
f'UDP-From-{self.client_address[0]}-{binascii.b2a_hex(plaintext)}')
# if Config.DEBUG:
# logging.info(
# f'UDP-From-{self.client_address[0]}-{binascii.b2a_hex(plaintext)}')
commands = CommandParser(
user['room'], user['player_index']).get_commands(plaintext)
@@ -46,9 +46,9 @@ class UDP_handler(socketserver.BaseRequestHandler):
for i in commands:
iv, ciphertext, tag = encrypt(user['key'], i, b'')
if Config.IS_DEBUG:
logging.info(
f'UDP-To-{self.client_address[0]}-{binascii.b2a_hex(i)}')
# if Config.DEBUG:
# logging.info(
# f'UDP-To-{self.client_address[0]}-{binascii.b2a_hex(i)}')
server.sendto(token + iv + tag[:12] +
ciphertext, self.client_address)
@@ -59,15 +59,18 @@ class TCP_handler(socketserver.StreamRequestHandler):
self.data = self.rfile.readline().strip()
message = self.data.decode('utf-8')
# print(message)
if Config.DEBUG:
logging.info(f'TCP-From-{self.client_address[0]}-{message}')
data = message.split('|')
if data[0] != Config.AUTHENTICATION:
self.wfile.write(b'No authentication')
logging.warning('TCP-%s-No authentication' %
self.client_address[0])
logging.warning(f'TCP-{self.client_address[0]}-No authentication')
return None
self.wfile.write(TCPRouter(data[1:]).handle().encode('utf-8'))
r = TCPRouter(data[1:]).handle()
if Config.DEBUG:
logging.info(f'TCP-To-{self.client_address[0]}-{r}')
self.wfile.write(r.encode('utf-8'))
def link_play(ip: str = Config.HOST, udp_port: int = Config.UDP_PORT, tcp_port: int = Config.TCP_PORT):

View File

@@ -94,7 +94,7 @@ class TCPRouter:
self.data = data # data: list[str] = [command, ...]
def debug(self):
if Config.IS_DEBUG:
if Config.DEBUG:
return eval(self.data[1])
return 'ok'
@@ -186,9 +186,13 @@ class TCPRouter:
return '1202'
room: Room = Store.room_code_dict[room_code]
if room.player_num == 4:
player_num = room.player_num
if player_num == 4:
# 满人
return '1201'
elif player_num == 0:
# 房间不存在
return '1202'
elif room.state != 2:
# 无法加入
return '1205'

View File

@@ -1,3 +1,5 @@
from time import time
from .config import Config
@@ -79,8 +81,27 @@ class Room:
@property
def player_num(self) -> int:
self.check_player_online()
return sum(i.player_id != 0 for i in self.players)
def check_player_online(self, now: int = None):
# 检测玩家是否被自动踢出房间 / 离线判断
now = round(time() * 1000000) if now is None else now
flag = False
player_index_list = []
for i, x in enumerate(self.players):
if x.player_id == 0 or x.last_timestamp == 0:
continue
if now - x.last_timestamp >= Config.PLAYER_TIMEOUT:
self.delete_player(i)
flag = True
player_index_list.append(i)
elif x.online == 1 and now - x.last_timestamp >= Config.PLAYER_PRE_TIMEOUT:
x.online = 0
player_index_list.append(i)
return flag, player_index_list
def get_players_info(self):
# 获取所有玩家信息
re = b''

View File

@@ -142,22 +142,11 @@ class CommandParser:
re.append(self.s.command_0c())
player.last_timestamp = self.s.timestamp
flag_13 = False
# 离线判断
for i in range(4):
if i != self.player_index:
t = self.room.players[i]
if t.player_id != 0:
if t.last_timestamp != 0:
if t.online == 1 and self.s.timestamp - t.last_timestamp >= Config.PLAYER_PRE_TIMEOUT:
t.online = 0
self.room.command_queue.append(
self.s.command_12(i))
elif t.online == 0 and self.s.timestamp - t.last_timestamp >= Config.PLAYER_TIMEOUT:
self.room.delete_player(i)
self.room.command_queue.append(
self.s.command_12(i))
flag_13 = True
flag_13, player_index_list = self.room.check_player_online(
self.s.timestamp)
for i in player_index_list:
self.room.command_queue.append(self.s.command_12(i))
flag_11 = False
flag_12 = False