fix: 组队强制本俱乐部隔离,修复组队分账冻结幂等键
组队接口一律校验 X-Club-Id=订单=帖;不走跨店互通。分账 biz_id 按队员区分。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,30 +15,45 @@ from orders.services import team_recruit as team_svc
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _uid(request):
|
||||
return getattr(request.user, 'UserUID', '') or ''
|
||||
def _require_club(request):
|
||||
club = _club(request)
|
||||
if not club:
|
||||
return None, Response({'code': 400, 'msg': '缺少俱乐部'})
|
||||
return club, None
|
||||
|
||||
|
||||
def _club(request):
|
||||
return (resolve_club_id_from_request(request) or '').strip()
|
||||
def _assert_recruit_same_club(rec, club):
|
||||
"""组队永不走跨店互通:帖/订单/当前俱乐部必须一致。"""
|
||||
if not rec:
|
||||
return Response({'code': 404, 'msg': '组队不存在'})
|
||||
if (rec.club_id or '') != club:
|
||||
return Response({'code': 403, 'msg': '仅本俱乐部可操作组队'})
|
||||
order = Order.query.filter(OrderID=rec.order_id).first()
|
||||
if not order:
|
||||
return Response({'code': 404, 'msg': '订单不存在'})
|
||||
if (order.ClubID or '') != club:
|
||||
return Response({'code': 403, 'msg': '订单不属于本俱乐部'})
|
||||
return None
|
||||
|
||||
|
||||
class ZuduiKaqiView(APIView):
|
||||
"""开启组队 POST /zudui/kaqi"""
|
||||
"""开启组队 POST /zudui/kaqi(仅本俱乐部订单,与跨店抢单无关)"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
parser_classes = [JSONParser]
|
||||
|
||||
def post(self, request):
|
||||
uid = _uid(request)
|
||||
club, err = _require_club(request)
|
||||
if err:
|
||||
return err
|
||||
oid = str(request.data.get('dingdan_id') or request.data.get('order_id') or '').strip()
|
||||
if not oid:
|
||||
return Response({'code': 400, 'msg': '缺少订单ID'})
|
||||
order = Order.query.filter(OrderID=oid).first()
|
||||
if not order:
|
||||
return Response({'code': 404, 'msg': '订单不存在'})
|
||||
club = _club(request)
|
||||
if club and (order.ClubID or '') != club:
|
||||
return Response({'code': 403, 'msg': '俱乐部不匹配'})
|
||||
if (order.ClubID or '') != club:
|
||||
return Response({'code': 403, 'msg': '仅本俱乐部订单可开启组队'})
|
||||
try:
|
||||
rec = team_svc.open_recruit(
|
||||
order=order,
|
||||
@@ -69,11 +84,18 @@ class ZuduiGaiyaoqiuView(APIView):
|
||||
|
||||
def post(self, request):
|
||||
uid = _uid(request)
|
||||
club, err = _require_club(request)
|
||||
if err:
|
||||
return err
|
||||
rid = request.data.get('recruit_id')
|
||||
try:
|
||||
rid = int(rid)
|
||||
except (TypeError, ValueError):
|
||||
return Response({'code': 400, 'msg': '缺少 recruit_id'})
|
||||
rec = TeamRecruit.query.filter(id=rid).first()
|
||||
deny = _assert_recruit_same_club(rec, club)
|
||||
if deny:
|
||||
return deny
|
||||
try:
|
||||
kw = {'recruit_id': rid, 'leader_uid': uid}
|
||||
if 'need_count' in request.data:
|
||||
@@ -101,14 +123,18 @@ class ZuduiGuantingView(APIView):
|
||||
|
||||
def post(self, request):
|
||||
uid = _uid(request)
|
||||
club, err = _require_club(request)
|
||||
if err:
|
||||
return err
|
||||
rid = request.data.get('recruit_id')
|
||||
try:
|
||||
rid = int(rid)
|
||||
except (TypeError, ValueError):
|
||||
return Response({'code': 400, 'msg': '缺少 recruit_id'})
|
||||
rec = TeamRecruit.query.filter(id=rid).first()
|
||||
if not rec:
|
||||
return Response({'code': 404, 'msg': '组队不存在'})
|
||||
deny = _assert_recruit_same_club(rec, club)
|
||||
if deny:
|
||||
return deny
|
||||
if rec.leader_uid != uid:
|
||||
return Response({'code': 403, 'msg': '仅队长可关停'})
|
||||
try:
|
||||
@@ -124,14 +150,14 @@ class ZuduiGuantingView(APIView):
|
||||
|
||||
|
||||
class ZuduiDatingView(APIView):
|
||||
"""组队大厅:仅招募中。"""
|
||||
"""组队大厅:仅本俱乐部招募中(不读跨店互通)。"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
parser_classes = [JSONParser]
|
||||
|
||||
def post(self, request):
|
||||
club = _club(request)
|
||||
if not club:
|
||||
return Response({'code': 400, 'msg': '缺少俱乐部'})
|
||||
club, err = _require_club(request)
|
||||
if err:
|
||||
return err
|
||||
page = max(1, int(request.data.get('page') or 1))
|
||||
page_size = min(50, max(1, int(request.data.get('page_size') or 10)))
|
||||
leixing = request.data.get('leixing_id')
|
||||
@@ -142,22 +168,27 @@ class ZuduiDatingView(APIView):
|
||||
status=TeamRecruit.STATUS_RECRUITING,
|
||||
).order_by('-opened_at')
|
||||
|
||||
# 可选:按订单类型过滤
|
||||
order_ids = None
|
||||
if leixing not in (None, '') or pingtai not in (None, ''):
|
||||
oq = Order.query.filter(ClubID=club, Status=2)
|
||||
if leixing not in (None, ''):
|
||||
try:
|
||||
oq = oq.filter(ProductTypeID=int(leixing))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
# 商品类型必选过滤(与前端大厅一致)
|
||||
if leixing not in (None, ''):
|
||||
try:
|
||||
lid = int(leixing)
|
||||
except (TypeError, ValueError):
|
||||
return Response({'code': 400, 'msg': '商品类型无效'})
|
||||
oq = Order.query.filter(ClubID=club, Status=2, ProductTypeID=lid)
|
||||
if pingtai not in (None, ''):
|
||||
try:
|
||||
oq = oq.filter(Platform=int(pingtai))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
order_ids = list(oq.values_list('OrderID', flat=True)[:500])
|
||||
order_ids = list(oq.values_list('OrderID', flat=True)[:800])
|
||||
qs = qs.filter(order_id__in=order_ids or ['__none__'])
|
||||
elif pingtai not in (None, ''):
|
||||
try:
|
||||
oq = Order.query.filter(ClubID=club, Status=2, Platform=int(pingtai))
|
||||
order_ids = list(oq.values_list('OrderID', flat=True)[:800])
|
||||
qs = qs.filter(order_id__in=order_ids or ['__none__'])
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
total = qs.count()
|
||||
start = (page - 1) * page_size
|
||||
@@ -187,6 +218,9 @@ class ZuduiXiangqingView(APIView):
|
||||
|
||||
def post(self, request):
|
||||
uid = _uid(request)
|
||||
club, err = _require_club(request)
|
||||
if err:
|
||||
return err
|
||||
rid = request.data.get('recruit_id')
|
||||
oid = str(request.data.get('dingdan_id') or request.data.get('order_id') or '').strip()
|
||||
rec = None
|
||||
@@ -196,9 +230,12 @@ class ZuduiXiangqingView(APIView):
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
if not rec and oid:
|
||||
rec = TeamRecruit.query.filter(order_id=oid).order_by('-id').first()
|
||||
rec = TeamRecruit.query.filter(order_id=oid, club_id=club).order_by('-id').first()
|
||||
if not rec:
|
||||
return Response({'code': 404, 'msg': '组队不存在'})
|
||||
deny = _assert_recruit_same_club(rec, club)
|
||||
if deny:
|
||||
return deny
|
||||
|
||||
is_member = TeamMember.query.filter(
|
||||
recruit_id=rec.id, user_uid=uid, join_status=TeamMember.JOIN_ACTIVE,
|
||||
@@ -218,16 +255,24 @@ class ZuduiRuiduiView(APIView):
|
||||
|
||||
def post(self, request):
|
||||
uid = _uid(request)
|
||||
club, err = _require_club(request)
|
||||
if err:
|
||||
return err
|
||||
try:
|
||||
rid = int(request.data.get('recruit_id'))
|
||||
except (TypeError, ValueError):
|
||||
return Response({'code': 400, 'msg': '缺少 recruit_id'})
|
||||
rec0 = TeamRecruit.query.filter(id=rid).first()
|
||||
deny = _assert_recruit_same_club(rec0, club)
|
||||
if deny:
|
||||
return deny
|
||||
try:
|
||||
rec = team_svc.join_recruit(
|
||||
recruit_id=rid,
|
||||
user_uid=uid,
|
||||
game_id=request.data.get('game_id') or '',
|
||||
request=request,
|
||||
require_club_id=club,
|
||||
)
|
||||
order = Order.query.filter(OrderID=rec.order_id).first()
|
||||
return Response({
|
||||
@@ -248,7 +293,9 @@ class ZuduiWodeView(APIView):
|
||||
|
||||
def post(self, request):
|
||||
uid = _uid(request)
|
||||
club = _club(request)
|
||||
club, err = _require_club(request)
|
||||
if err:
|
||||
return err
|
||||
as_leader = TeamRecruit.query.filter(
|
||||
club_id=club,
|
||||
leader_uid=uid,
|
||||
@@ -263,7 +310,7 @@ class ZuduiWodeView(APIView):
|
||||
id__in=member_rids or [0],
|
||||
club_id=club,
|
||||
status__in=[TeamRecruit.STATUS_RECRUITING, TeamRecruit.STATUS_LOCKED],
|
||||
) if member_rids and club else []
|
||||
) if member_rids else []
|
||||
as_member = list(as_member_qs) if as_member_qs else []
|
||||
as_leader_list = list(as_leader)
|
||||
oids = [r.order_id for r in as_leader_list + as_member]
|
||||
|
||||
Reference in New Issue
Block a user