From 838b0a18e752c5b2b2e19da889dafbee69417437 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 13 Aug 2026 01:00:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(gerp/mail):=20=E6=94=AF=E6=8C=81=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=B3=A8=E5=86=8C=E5=A4=9A=E4=B8=AA=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E9=82=AE=E7=AE=B1=EF=BC=8C=E4=B8=8A=E9=99=90=205=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MailApply 上限由 1 改 5,超出提示"邮箱数量已达上限" - MyMailbox 返回 MailboxList 数组(按创建时间倒序),替代单 Mailbox - ChangePassword/Login 支持可选 MailboxName 指定目标邮箱,缺省取第一个 --- gvsds/apps/gerp/views/mail.py | 50 ++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/gvsds/apps/gerp/views/mail.py b/gvsds/apps/gerp/views/mail.py index 780b71f5..eadd5cbf 100644 --- a/gvsds/apps/gerp/views/mail.py +++ b/gvsds/apps/gerp/views/mail.py @@ -41,6 +41,8 @@ _MAIL_TOKEN_TTL = 24 * 3600 # token 有效期 24 小时 _MAIL_TOKEN_STORE = {} # token -> {"email": str, "password": str, "exp": float} _MAIL_TOKEN_LOCK = threading.Lock() +_MAILBOX_LIMIT = 5 # 每个用户最多申请的企业邮箱数 + def _PurgeMailTokens(): """清理已过期 token。""" @@ -218,9 +220,9 @@ class MailApplyView(ELTAuthenticatedAPIView): if not user_bytes: return Response({'code': 1, 'msg': '无法识别当前用户'}, status=401) - # 每个用户最多一个邮箱 - if UserMailbox.objects.filter(UserUUID=request.user).exists(): - return Response({'code': 1, 'msg': '您已拥有邮箱,一个用户只能申请一个'}, status=400) + # 每个用户最多申请 _MAILBOX_LIMIT 个邮箱 + if UserMailbox.objects.filter(UserUUID=request.user).count() >= _MAILBOX_LIMIT: + return Response({'code': 1, 'msg': f'邮箱数量已达上限(最多 {_MAILBOX_LIMIT} 个)'}, status=400) mailbox_name = (request.data.get('MailboxName') or '').strip() password = request.data.get('Password') or '' @@ -291,9 +293,11 @@ class MailChangePasswordView(ELTAuthenticatedAPIView): permission_classes = [IsAuthenticated] def post(self, request): - mailbox = UserMailbox.objects.filter(UserUUID=request.user, Status=1).first() + mailbox_name = (request.data.get('MailboxName') or '').strip() + queryset = UserMailbox.objects.filter(UserUUID=request.user, Status=1) + mailbox = queryset.filter(MailboxName=mailbox_name).first() if mailbox_name else queryset.first() if mailbox is None: - return Response({'code': 1, 'msg': '您尚未申请邮箱'}, status=404) + return Response({'code': 1, 'msg': '未找到该邮箱' if mailbox_name else '您尚未申请邮箱'}, status=404) # 安全:改密必须先验证当前邮箱密码,防止已登录会话被劫持后直接改密 old_password = request.data.get('OldPassword') or '' if not old_password: @@ -315,27 +319,23 @@ class MailChangePasswordView(ELTAuthenticatedAPIView): class MailMyMailboxView(ELTAuthenticatedAPIView): - """我的邮箱信息 + JMAP 连接配置(供浏览器直连)。""" + """我的邮箱列表 + JMAP 连接配置(供浏览器直连)。""" permission_classes = [IsAuthenticated] def get(self, request): - mailbox = UserMailbox.objects.filter(UserUUID=request.user).first() session = StalwartMailManager.SessionConfig() - if mailbox is None: - return Response({'code': 0, 'data': {'Mailbox': None, 'Session': session}}) - return Response({ - 'code': 0, - 'data': { - 'Mailbox': { - 'MailboxName': mailbox.MailboxName, - 'QuotaMB': mailbox.QuotaMB, - 'Status': mailbox.Status, - 'CreateTime': mailbox.CreateTime.isoformat() if mailbox.CreateTime else None, - }, - 'Session': session, - }, - }) + rows = list(UserMailbox.objects.filter(UserUUID=request.user).order_by('-CreateTime')) + mailbox_list = [ + { + 'MailboxName': m.MailboxName, + 'QuotaMB': m.QuotaMB, + 'Status': m.Status, + 'CreateTime': m.CreateTime.isoformat() if m.CreateTime else None, + } + for m in rows + ] + return Response({'code': 0, 'data': {'MailboxList': mailbox_list, 'Session': session}}) class MailAdminListView(ELTAuthenticatedAPIView): @@ -403,16 +403,18 @@ class MailLoginView(ELTAuthenticatedAPIView): 密码仅在本次请求明文传输一次;后端校验通过后把凭据存入进程内存, 返回 token,后续 JMAP 调用只携带 token(见 MailJmapView)。 - 请求体:Password + 请求体:Password, MailboxName(可选,多邮箱时指定登录哪个;缺省取第一个) 响应:{code: 0, data: {Token, MailboxName}} """ permission_classes = [IsAuthenticated] def post(self, request): - mailbox = UserMailbox.objects.filter(UserUUID=request.user, Status=1).first() + mailbox_name = (request.data.get('MailboxName') or '').strip() + queryset = UserMailbox.objects.filter(UserUUID=request.user, Status=1) + mailbox = queryset.filter(MailboxName=mailbox_name).first() if mailbox_name else queryset.first() if mailbox is None: - return Response({'code': 1, 'msg': '您尚未申请邮箱'}, status=404) + return Response({'code': 1, 'msg': '未找到该邮箱' if mailbox_name else '您尚未申请邮箱'}, status=404) password = request.data.get('Password') or '' if not password: return Response({'code': 1, 'msg': '请输入邮箱密码'}, status=400)