feat: 新增 2H 运营台独立登录接口,不改旧 kefu-login

This commit is contained in:
XingQue committed 2026-08-09 16:37:48 +08:00
1 parent 9b21bb0860
commit 545b235379
3 files changed
+256 -1

No files matched your search

+186
View File
@@ -0,0 +1,186 @@
"""
2H 运营台专用登录(与旧 /jituan/auth/kefu-login 完全独立)。
- 不修改、不调用旧登录 View
- 校验通过后强制要求可访问目标 club_id(默认 eh)
- 旧 kefu 后台继续只用 kefu-login,互不影响
"""
from __future__ import annotations
import logging
from django.utils import timezone
from rest_framework_simplejwt.tokens import RefreshToken
from jituan.constants import DATA_SCOPE_SINGLE, SUPER_ADMIN_PHONES
from jituan.services.admin_context import (
build_admin_club_context,
get_allowed_club_ids,
is_hardcoded_super_phone,
is_system_super_admin,
)
from jituan.services.kefu_menu import build_menu_access_payload
from users.business_models import User
logger = logging.getLogger(__name__)
EH_CONSOLE_CLUB_ID = 'eh'
def _authenticate_kefu_user(phone: str, password: str, erjimima: str):
"""
返回 (user, kefu, error_code, error_msg)
error_code: None 表示成功;否则为业务码。
"""
phone = (phone or '').strip()
password = password or ''
erjimima = erjimima or ''
if not phone or not password:
return None, None, 1, '请填写手机号和密码'
user_mains = list(
User.query.filter(Phone=phone, KefuProfile__isnull=False).select_related('KefuProfile')
)
if not user_mains:
for admin_user in User.query.filter(Phone=phone):
if (
admin_user.IsSuperuser
or admin_user.UserType == 'admin'
or admin_user.Phone in SUPER_ADMIN_PHONES
):
user_mains = [admin_user]
break
if not user_mains:
return None, None, 1, '用户不存在'
target_user = None
target_kefu = None
password_matched = False
for user in user_mains:
if not user.CheckPassword(password):
continue
password_matched = True
try:
kefu_probe = user.KefuProfile
except Exception:
kefu_probe = None
if kefu_probe is not None and int(getattr(kefu_probe, 'zhuangtai', 1) or 0) != 1:
if not is_hardcoded_super_phone(user):
if target_user is None:
target_user = user
target_kefu = kefu_probe
continue
is_admin = (
user.IsSuperuser
or user.UserType == 'admin'
or user.Phone in SUPER_ADMIN_PHONES
)
if is_admin:
return user, kefu_probe, None, None
kefu = kefu_probe
if kefu is None:
continue
stored_erji = kefu.erjimima or ''
if stored_erji.startswith(('$2b$', '$2a$')) and len(stored_erji) == 60:
import bcrypt as _bcrypt
if not _bcrypt.checkpw(erjimima.encode('utf-8'), stored_erji.encode('utf-8')):
continue
elif stored_erji != erjimima:
continue
if kefu.zhuangtai != 1:
if target_user is None:
target_user = user
target_kefu = kefu
continue
return user, kefu, None, None
if target_user and target_kefu and int(getattr(target_kefu, 'zhuangtai', 1) or 0) != 1:
return None, None, 4, '账号已被封禁'
if password_matched:
return None, None, 1, '二级密码错误'
return None, None, 1, '密码错误'
def user_can_access_console_club(user, club_id: str) -> bool:
if is_system_super_admin(user):
return True
return club_id in get_allowed_club_ids(user)
def login_eh_console(request, phone: str, password: str, erjimima: str, club_id: str = EH_CONSOLE_CLUB_ID):
"""
2H 运营台登录。
返回 (http_status, body_dict)
"""
target_club = (club_id or EH_CONSOLE_CLUB_ID).strip() or EH_CONSOLE_CLUB_ID
user, kefu, err_code, err_msg = _authenticate_kefu_user(phone, password, erjimima)
if err_code is not None:
http = 403 if err_code == 4 else 400
return http, {'code': err_code, 'msg': err_msg, 'data': None}
if not user_can_access_console_club(user, target_club):
allowed = sorted(get_allowed_club_ids(user))
logger.warning(
'eh-console 拒登 phone=%s want=%s allowed=%s',
phone, target_club, allowed,
)
return 403, {
'code': 403,
'msg': f'无权进入俱乐部「{target_club}」运营台,请使用已在该俱乐部配置的账号',
'data': {
'allowed': False,
'club_id': target_club,
'allowed_club_ids': allowed,
},
}
user.IP = _request_ip(request)
user.UserLastLoginDate = timezone.now()
user.save(update_fields=['IP', 'UserLastLoginDate'])
token = str(RefreshToken.for_user(user).access_token)
nicheng = kefu.nicheng if kefu else (user.UserName or user.Phone)
club_context = build_admin_club_context(user)
# 菜单按目标单店 eh 计算(不按集团 all)
request.user = user
request.club_id = target_club
request.club_scope = DATA_SCOPE_SINGLE
menu_access, _ = build_menu_access_payload(request, phone)
return 200, {
'code': 0,
'msg': '登录成功',
'data': {
'token': token,
'nicheng': nicheng,
'yonghuid': user.UserUID,
'console': 'eh',
'fixed_club_id': target_club,
'club_context': club_context,
'menu_access': menu_access if menu_access else {
'visible_page_ids': [],
'visible_paths': [],
'permission_codes': [],
'role_names': [],
'yonghuid': user.UserUID or '',
'can_switch_club': False,
'is_group_admin': False,
'menu_ready': False,
},
},
}
def _request_ip(request):
xff = request.META.get('HTTP_X_FORWARDED_FOR')
if xff:
return xff.split(',')[0].strip()
return request.META.get('REMOTE_ADDR') or ''
+4
View File
@@ -50,6 +50,8 @@ from jituan.views import (
ClubHuiyuanStatsView,
ClubKefuLoginView,
ClubKefuMenuAccessView,
EhConsoleLoginView,
ClubConsoleGateView,
ClubPermDebugView,
ClubListView,
ClubDefaultInviteView,
@@ -131,8 +133,10 @@ urlpatterns = [
path('auth/wechat-oauth-login', ClubWechatOAuthLoginView.as_view(), name='jituan_wechat_oauth_login'),
path('auth/refresh-wx-bind', ClubWxBindRefreshView.as_view(), name='jituan_refresh_wx_bind'),
path('auth/kefu-login', ClubKefuLoginView.as_view(), name='jituan_kefu_login'),
path('auth/eh-console-login', EhConsoleLoginView.as_view(), name='jituan_eh_console_login'),
path('auth/me-context', ClubMeContextView.as_view(), name='jituan_me_context'),
path('auth/menu-access', ClubKefuMenuAccessView.as_view(), name='jituan_menu_access'),
path('auth/console-gate', ClubConsoleGateView.as_view(), name='jituan_console_gate'),
path('auth/perm-debug', ClubPermDebugView.as_view(), name='jituan_perm_debug'),
path('auth/dashou-register', ClubDashouRegisterView.as_view(), name='jituan_dashou_register'),
path('houtai/club-manage', ClubManageView.as_view(), name='jituan_club_manage'),
+66 -1
View File
@@ -12,7 +12,13 @@ from rest_framework_simplejwt.tokens import RefreshToken
from jituan.constants import SUPER_ADMIN_PHONES, ADMIN_ROLE_LABELS, CLUB_ID_DEFAULT, DATA_SCOPE_ALL, DATA_SCOPE_SINGLE
from jituan.models import Club, AdminAssignment
from jituan.services.admin_context import build_admin_club_context, is_kefu_backend_account, can_manage_admin_assignments
from jituan.services.admin_context import (
build_admin_club_context,
is_kefu_backend_account,
can_manage_admin_assignments,
get_allowed_club_ids,
is_system_super_admin,
)
from jituan.services.kefu_menu import build_menu_access_payload
from jituan.services.wechat_login import (
login_or_register_by_club,
@@ -345,6 +351,65 @@ class ClubKefuMenuAccessView(APIView):
return Response({'code': 0, 'msg': 'ok', 'data': payload})
class EhConsoleLoginView(APIView):
"""
2H 运营台专用登录(全新接口,与旧后台无关)。
POST /jituan/auth/eh-console-login
body: { phone, password, erjimima }
注意:绝不改动 /jituan/auth/kefu-login;旧 kefu 后台继续用旧登录。
"""
throttle_classes = [AnonRateThrottle]
permission_classes = [AllowAny]
def post(self, request):
from jituan.services.eh_console_login import EH_CONSOLE_CLUB_ID, login_eh_console
phone = (request.data.get('phone') or '').strip()
password = request.data.get('password') or ''
erjimima = request.data.get('erjimima') or ''
http_status, body = login_eh_console(
request, phone, password, erjimima, club_id=EH_CONSOLE_CLUB_ID,
)
return Response(body, status=http_status)
class ClubConsoleGateView(APIView):
"""
GET /jituan/auth/console-gate?club_id=eh
2H 运营台会话复查门禁(新接口)。不参与旧后台登录。
"""
permission_classes = [IsAuthenticated]
def get(self, request):
from jituan.services.eh_console_login import user_can_access_console_club
user = request.user
if not is_kefu_backend_account(user):
return Response({'code': 403, 'msg': '非后台账号', 'data': None}, status=403)
target = (request.query_params.get('club_id') or '').strip() or 'eh'
allowed = sorted(get_allowed_club_ids(user))
ok = bool(user_can_access_console_club(user, target))
payload = {
'allowed': ok,
'club_id': target,
'allowed_club_ids': allowed,
'phone': getattr(user, 'Phone', '') or '',
'is_super': bool(is_system_super_admin(user)),
}
if not ok:
return Response(
{
'code': 403,
'msg': f'无权进入俱乐部「{target}」运营台,请使用已在该俱乐部配置的账号',
'data': payload,
},
status=status.HTTP_403_FORBIDDEN,
)
return Response({'code': 0, 'msg': 'ok', 'data': payload})
class ClubPermDebugView(APIView):
"""GET /jituan/auth/perm-debug — 当前俱乐部权限解析明细。"""