Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added batch query user information by bk_username #2034

Merged
35 changes: 35 additions & 0 deletions src/bk-user/bkuser/apis/apigw/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# TencentBlueKing is pleased to support the open source community by making
# 蓝鲸智云 - 用户管理 (bk-user) available.
# Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
# Licensed under the MIT License (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at
#
# http://opensource.org/licenses/MIT
#
# Unless required by applicable law or agreed to in writing, software distributed under
# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific language governing permissions and
# limitations under the License.
#
# We undertake not to change the open source license (MIT license) applicable
# to the current version of the project delivered to anyone in the future.
from django.conf import settings
from rest_framework import serializers

from bkuser.common.serializers import StringArrayField


class TenantUserListInputSLZ(serializers.Serializer):
bk_usernames = StringArrayField(
help_text="蓝鲸用户唯一标识,多个使用逗号分隔",
max_items=settings.BATCH_QUERY_USER_INFO_BY_BK_USERNAME_LIMIT,
)
rolin999 marked this conversation as resolved.
Show resolved Hide resolved


class TenantUserListOutputSLZ(serializers.Serializer):
bk_username = serializers.CharField(help_text="蓝鲸用户唯一标识", source="id")
tenant_id = serializers.CharField(help_text="租户 ID")
phone = serializers.CharField(help_text="手机号", source="data_source_user.phone")
phone_country_code = serializers.CharField(help_text="手机国际区号", source="data_source_user.phone_country_code")
email = serializers.CharField(help_text="邮箱", source="data_source_user.email")
1 change: 1 addition & 0 deletions src/bk-user/bkuser/apis/apigw/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
path(
"tenant-users/<str:tenant_user_id>/", views.TenantUserRetrieveApi.as_view(), name="apigw.tenant_user.retrieve"
),
path("tenant-users/", views.TenantUserListApi.as_view(), name="apigw.tenant_user.list"),
rolin999 marked this conversation as resolved.
Show resolved Hide resolved
]
36 changes: 36 additions & 0 deletions src/bk-user/bkuser/apis/apigw/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#
# We undertake not to change the open source license (MIT license) applicable
# to the current version of the project delivered to anyone in the future.
from django.db.models import QuerySet
from rest_framework import generics
from rest_framework.response import Response

Expand All @@ -22,6 +23,7 @@

from .authentications import InnerBearerTokenAuthentication
from .permissions import IsInnerBearerTokenAuthenticated
from .serializers import TenantUserListInputSLZ, TenantUserListOutputSLZ


class TenantUserRetrieveApi(generics.RetrieveAPIView):
Expand All @@ -43,3 +45,37 @@ def get(self, request, *args, **kwargs):
raise error_codes.OBJECT_NOT_FOUND.f(f"user({tenant_user_id}) not found", replace=True)

return Response({"tenant_id": tenant_user.tenant_id})


class TenantUserListApi(generics.ListAPIView):
"""
根据 bk_username 批量查询用户信息
"""

authentication_classes = [InnerBearerTokenAuthentication]
permission_classes = [IsInnerBearerTokenAuthenticated]
rolin999 marked this conversation as resolved.
Show resolved Hide resolved

pagination_class = None

serializer_class = TenantUserListOutputSLZ

def get_queryset(self) -> QuerySet[TenantUser]:
slz = TenantUserListInputSLZ(data=self.request.query_params)
slz.is_valid(raise_exception=True)
data = slz.validated_data

# [only] 用于减少查询字段,仅查询必要字段
rolin999 marked this conversation as resolved.
Show resolved Hide resolved
return (
TenantUser.objects.filter(id__in=data["bk_usernames"])
rolin999 marked this conversation as resolved.
Show resolved Hide resolved
.select_related("data_source_user")
.only(
"id",
"tenant_id",
"data_source_user__phone_country_code",
"data_source_user__phone",
"data_source_user__email",
rolin999 marked this conversation as resolved.
Show resolved Hide resolved
)
)
rolin999 marked this conversation as resolved.
Show resolved Hide resolved

def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
3 changes: 3 additions & 0 deletions src/bk-user/bkuser/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,3 +685,6 @@ def _build_file_handler(log_path: Path, filename: str, format: str) -> Dict:

# 限制 bk_username 批量查询 display_name 的数量上限,避免性能问题
BATCH_QUERY_USER_DISPLAY_NAME_BY_BK_USERNAME_LIMIT = env.int("BATCH_QUERY_USER_DISPLAY_NAME_BY_BK_USERNAME_LIMIT", 100)

# 限制 bk_username 批量查询用户信息的数量上限,避免性能问题
BATCH_QUERY_USER_INFO_BY_BK_USERNAME_LIMIT = env.int("BATCH_QUERY_USER_INFO_BY_BK_USERNAME_LIMIT", 100)
rolin999 marked this conversation as resolved.
Show resolved Hide resolved
40 changes: 39 additions & 1 deletion src/bk-user/tests/apis/apigw/test_tenant_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
# We undertake not to change the open source license (MIT license) applicable
# to the current version of the project delivered to anyone in the future.
import pytest
from django.conf import settings
from django.urls import reverse
from rest_framework import status

pytestmark = pytest.mark.django_db


class TestTenantUserRetrieve:
class TestTenantUserRetrieveApi:
def test_retrieve_tenant_user(self, apigw_api_client, default_tenant_user_data, default_tenant):
resp = apigw_api_client.get(reverse("apigw.tenant_user.retrieve", kwargs={"tenant_user_id": "zhangsan"}))
assert resp.status_code == status.HTTP_200_OK
Expand All @@ -32,3 +33,40 @@ def test_retrieve_tenant_user_not_found(self, apigw_api_client, default_tenant_u
reverse("apigw.tenant_user.retrieve", kwargs={"tenant_user_id": "zhangsan_not_found"})
)
assert resp.status_code == status.HTTP_404_NOT_FOUND


class TestTenantUserListApi:
def test_list_tenant_user(self, apigw_api_client, default_tenant_user_data, default_tenant):
resp = apigw_api_client.get(reverse("apigw.tenant_user.list"), data={"bk_usernames": "zhangsan,lisi"})

assert resp.status_code == status.HTTP_200_OK
assert len(resp.data) == 2
assert {t["bk_username"] for t in resp.data} == {"zhangsan", "lisi"}
assert {t["tenant_id"] for t in resp.data} == {default_tenant.id, default_tenant.id}
assert {t["phone"] for t in resp.data} == {"13512345671", "13512345672"}
assert {t["email"] for t in resp.data} == {"[email protected]", "[email protected]"}
assert {t["phone_country_code"] for t in resp.data} == {"86"}

def test_with_invalid_bk_usernames(self, apigw_api_client, default_tenant_user_data, default_tenant):
resp = apigw_api_client.get(reverse("apigw.tenant_user.list"), data={"bk_usernames": "zhangsan,not_exist"})

assert resp.status_code == status.HTTP_200_OK
assert len(resp.data) == 1
assert resp.data[0]["bk_username"] == "zhangsan"
assert resp.data[0]["tenant_id"] == default_tenant.id
assert resp.data[0]["phone"] == "13512345671"
assert resp.data[0]["email"] == "[email protected]"
assert resp.data[0]["phone_country_code"] == "86"

def test_with_no_bk_usernames(self, apigw_api_client):
resp = apigw_api_client.get(reverse("apigw.tenant_user.list"), data={"bk_usernames": ""})
assert resp.status_code == status.HTTP_400_BAD_REQUEST

def test_with_invalid_length(self, apigw_api_client):
resp = apigw_api_client.get(
reverse("apigw.tenant_user.list"),
data={
"bk_usernames": ",".join(map(str, range(1, settings.BATCH_QUERY_USER_INFO_BY_BK_USERNAME_LIMIT + 2)))
},
)
assert resp.status_code == status.HTTP_400_BAD_REQUEST
Loading