当前位置:   article > 正文

django登录认证_djando原生登录验证

djando原生登录验证

mixins.ListModelMixin, mixins.CreateModelMixin, GenericViewSet
mixin包括:
    CreateModelMixin       定义create方法    对应post
    ListModelMixin             有自己的方法list()可以重写,对应get方法   
    RetrieveModelMixin      定义retrieve方法    对应get方法   
    UpdateModelMixin      定义update方法    对应put patch 方法
    DestroyModelMixin      定义delete方法     对应delete
RetrieveModelMixin对于具体的商品信息进行了获取,序列化。这个在后面的商品详情页会介绍到。
UpdateModelMixin中对于部分更新还是全部更新进行了判断。
DestroyModelMixin用来连接我们的delete方法,在我们delete时有一些必要的操作,如设置返回状态204等。
GenericAPIView 继承自APIView:新增加了过滤、分页、序列化
GenericAPIView结合各种mixin可以组合成ListAPIView、RetrieveAPIView、等等,新增了get、post等方法

想用过滤、分页、序列化  等功能使用mixin,很方便
https://blog.csdn.net/summer2day/article/details/81367781
如果不在url中做转换   list  换为  get 等等  
则直接用list方法替代get方法    

搜索过滤:
可以看源码
queryset = queryset.filter(reduce(operator.and_, conditions))  源码中查询的一句代码,根据search_fields 和
输入的条件查询
搜索样式:默认?search=条件1,条件2......    如果?search=admin,1628080190@qq.com  可以模糊查询
filter_backends = [filters.SearchFilter]
search_fields = ["username", "email"]

router = DefaultRouter()
router.register(r'user_info', views.UserViewSet, basename="user")

get post请求直接对应list  create  url正常就行,/user_ifo/1/  可以对应retrieve方法   获取一条数据,list通常获取多条数据。
put  delete   要想直接对应update  destory方法,则url有变化。都对应/user_ifo/1/  形式  表示修改/删除id为1的数据。
参数可自己带也可以根据参数去更新删除对应数据,就得重写方法。源代码get_object就是获取id为1..的数据,所以最好重写一下

jwt-token认证:

token的组成:

1、头部-Header。默认是JWT,然后头部进行base64加密
2、payload 有效载荷,就是Token 的具体内容, 包含签发时间和过期时间,还有其他信息。
3、签名Signature 签名的组成是,把header、payload分别通过base64进行编码,然后拼接在一起。即对前两部分的签名,防止数据篡改。

下面代码对token进行校验的时候实际上也会对这三部分进行校验(源代码也有体现),校验都通过才会认为是有效token。

  1. import jwt
  2. from rest_framework_jwt.authentication import JSONWebTokenAuthentication
  3. from django.utils.encoding import smart_text
  4. from rest_framework import exceptions
  5. from rest_framework.authentication import get_authorization_header
  6. from rest_framework_jwt.settings import api_settings
  7. from django.utils.translation import ugettext
  8. class CustomAuthentication(JSONWebTokenAuthentication):
  9.     def get_jwt_value(self, request):
  10.         """
  11.         获取并校验前端请求头中token信息
  12.         """
  13.         auth = get_authorization_header(request).split()
  14.         auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
  15.         if not auth or smart_text(auth[0].lower()) != auth_header_prefix:
  16.             return None
  17.         if len(auth) == 1:
  18.             msg = ugettext('Invalid Authorization header. No credentials provided.')
  19.             raise exceptions.AuthenticationFailed(msg)
  20.         elif len(auth) > 2:
  21.             msg = ugettext('Invalid Authorization header. Credentials string should not contain spaces.')
  22.             raise exceptions.AuthenticationFailed(msg)
  23.         return auth[1]
  24.     def authenticate(self, request, username=None, password=None):
  25.         """
  26.         Returns a two-tuple of `User` and token if a valid signature has been
  27.         supplied using JWT-based authentication.
  28.         """
  29.         jwt_value = self.get_jwt_value(request)
  30.         if jwt_value is None:
  31.             # 没有携带token或者格式不对 返回406
  32.             raise exceptions.NotAcceptable()
  33.         try:
  34.             payload = api_settings.JWT_DECODE_HANDLER(jwt_value)
  35.         except jwt.ExpiredSignature:
  36.             # token过期 返回401
  37.             msg = ugettext('Signature has expired.')
  38.             raise exceptions.AuthenticationFailed(msg)
  39.         except jwt.DecodeError:
  40.             # token解析错误,返回400
  41.             msg = ugettext('Error decoding signature.')
  42.             raise exceptions.ParseError(msg)
  43.         except jwt.InvalidTokenError:
  44.             # 无效token,返回400
  45.             raise exceptions.ParseError()
  46.         # token认证完成再认证用户名身份
  47.         user = self.authenticate_credentials(payload)
  48.         return user, jwt_value
  1. setting.py 中配置:
  2. # AUTH_USER_MODEL = 'user.User'
  3. # 登录认证,没有就不用,有了token可以不要
  4. # AUTHENTICATION_BACKENDS = ('utils.login_auth.LoginAuthentication',)
  5. # token认证
  6. REST_FRAMEWORK = {
  7.     'DEFAULT_AUTHENTICATION_CLASSES': ['utils.authentic.CustomAuthentication'],
  8.     'DEFAULT_PERMISSION_CLASSES': ['utils.permission.MyPermission'],
  9. }
  10. # 设置Token有效时间和认证token信息的前缀,默认在api_settings也有
  11. # 这里过期时间是从登录开始传第一次token到前端算起,加密token的时候会把这个时间当做参数进行加密,通过解密来判断过期。所以每次成功请求都会生成新的token,只有两次访问时间超过JWT_EXPIRATION_DELTA才会过期。
  12. 所以这里都有一个漏洞就是在你设定的时间内就会过期前端会重新登录。所以根据业务需求可能得每次发送成功请求都要重新更新token,那这样每次的请求token都不一样。也算增加了安全性。
  13. JWT_AUTH = {
  14.     'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=500),
  15.     'JWT_AUTH_HEADER_PREFIX': 'JWT',
  16. }
  17. # redis每次请求浏览后有缓存,下次访问的时候先从redis里面取,然后再从数据库里面取。
  18. # 但是即使数据改了也不会更新,需要手动更新。所以选择合适的场景使用很重要。
  19. # 6379/1, 1是redis的数据库序号,redis默认允许16个数据库,redis不设置数据库序号默认使用的是0
  20. CACHES = {
  21. "default": {
  22. "BACKEND": "django_redis.cache.RedisCache",
  23. "LOCATION": "redis://127.0.0.1:6379",
  24. "OPTIONS": {
  25. "CLIENT_CLASS": "django_redis.client.DefaultClient",
  26. # 连接池数量,如果decode_responses不设置为True的话,使用get_redis_connection读取的数据是bytes,需要decode为utf-8
  27. "CONNECTION_POOL_KWARGS": {"max_connections": 100, "decode_responses": True},
  28. # "PASSWORD": "123456",
  29. }
  30. }
  31. }
  32. # 当关闭redis时默认会出现异常,配置这个给所有缓存配置相同的忽略行为
  33. # DJANGO_REDIS_IGNORE_EXCEPTIONS = True

传统auth模块认证

  1. from django.shortcuts import render, redirect
  2. from django.contrib import auth
  3. from django.contrib.auth.decorators import login_required
  4. def login(request):
  5. if request.method == 'POST':
  6. username = request.POST.get('username')
  7. passwd = request.POST.get('password')
  8. user = auth.authenticate(username=username, password=passwd)
  9. # 我们一般会重写authenticate方法,假如这个验证成功的话,这个user就有值,就可以进行登录
  10. # 这个依赖于session,将验证过的用户赋值给request.user属性
  11. auth.login(request, user)
  12. # 这个是把这个user封装进这个request里面,下面就可以直接进行调用了,通过request.user进行调用,进行登录验证
  13. # 就是将这个user和密码写进这个sessions里面,下次过来的时候就可以直接进行访问了,带着这个cookies进行匹配
  14. if user:
  15. # 浏览器打开网址会自动生成一个sessionId,F12可以查看到,然后服务器会保存这个sessionId和用户的登录信息
  16. # 还有过期时间保存到django_session表中。下次浏览器访问界面就是根据sessionId找到并识别用户
  17. return redirect('/index/')
  18. else:
  19. return render(request, 'login.html')
  20. # login_required就是进行登录认证,只要request.user有值request.user.is_authenticated()
  21. # 源码直接返回True,否则就是匿名用户。返回False
  22. @login_required(login_url='/login')
  23. def index(request):
  24. print('进入这个首页的页面')
  25. def logout(request):
  26. print('注销')
  27. auth.logout(request)
  28. # 这个相当于把这个request里面的user给清除掉,清除掉session_id,注销掉用户变成匿名用户
  29. # 源码中有 request.session.flush(),将session的数据都删除,并且cookies也失效。
  30. # 数据库表中对应的这条数据也会删除
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter

from user import views


router = DefaultRouter()
router.register(r'user_info', views.UserViewSet, basename="user")
router.register(r'group_info', views.GroupViewSet, basename="group")
router.register(r'permission_info', views.PermissionViewSet, basename="permission")
router.register(r'group_permission_info', views.GroupPermissionViewSet, basename="group_permission")


urlpatterns = [
    url(r'', include(router.urls)),
    url(r'login', views.UserLoginView.as_view()),
    url(r'get_group_dpt_region_info', views.CommonView.as_view()),
    url(r'get_user_all_permission', views.CommonView.as_view())
]
paginate.py 分页
​​​​​​​from rest_framework.pagination import PageNumberPagination


class MyPagination(PageNumberPagination):
    """
    page_size:(默认一页显示的条数,可在settings配置中设置或这里配置或路由中自动配置)
    page_size_query_param:通过在路由上传递数据来让每一页显示多少条数据(默认为None)(?page_size=10&page=2)
    max_page_size:最大显示多少个数据(默认为None)
    page_query_param:通过什么数据翻页(默认通过?page=页码进行翻页)
    """
    page_size = 10
    page_size_query_param = 'page_size'
    page_query_param = 'page'
  1. import logging
  2. import datetime
  3. import re
  4. from django.db import transaction
  5. from rest_framework import filters
  6. from rest_framework.mixins import ListModelMixin, CreateModelMixin, UpdateModelMixin, \
  7. DestroyModelMixin, RetrieveModelMixin
  8. from rest_framework.views import APIView
  9. from rest_framework.viewsets import GenericViewSet, ModelViewSet
  10. from rest_framework_jwt.settings import api_settings
  11. from rest_framework.response import Response
  12. from django.contrib.auth.models import Group, Permission, ContentType
  13. from django_redis import get_redis_connection
  14. from master.models import Region, Department
  15. from user.models import User
  16. from utils.paginate import MyPagination
  17. from user.serializers import UserSerializer, RegionSerializer, DepartmentSerializer, \
  18. GroupSerializer, PermissionSerializer
  19. from utils.permission import MyPermission
  20. # 统一规定返回给前端的格式,{"code": 1, "value": "", "token": ""}
  21. # code:0代表失败,1代表成功,2代表没有权限。value:代表返回给前端的值,token:代表令牌,成功请求的都携带
  22. EXPIRE_TIME = 60 # 过期时间,redis设置用户密码输入错误登录次数限制,超过限制则等待时间过后才能再次登录
  23. logger = logging.getLogger('user')
  24. class UserLoginView(APIView):
  25. # 不需要登录认证和权限认证
  26. authentication_classes = []
  27. permission_classes = []
  28. @staticmethod
  29. def post(request):
  30. username = request.data.get('username')
  31. password = request.data.get('password')
  32. # __iexact忽略大小写,__exact精确= 都类似like语句
  33. user = User.objects.filter(username__exact=username, is_active=True).first()
  34. if not user:
  35. return Response({"code": 0, "value": "username not exist", "token": ""})
  36. redis = get_redis_connection()
  37. if redis.get(username):
  38. # 锁定之后重新解锁再输入密码只有一次机会
  39. redis.setex("%s_login_times" % username, EXPIRE_TIME * 5, 3)
  40. return Response({"code": 0, "value": "Your account has been locked. Please try again later!", "token": ""})
  41. if user.check_password(password) or user.password == password:
  42. # 更新登录时间
  43. user.last_login = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  44. user.save()
  45. if redis.get(username):
  46. redis.delete(username)
  47. if redis.get("%s_login_times" % username):
  48. redis.delete("%s_login_times" % username)
  49. token = api_settings.JWT_ENCODE_HANDLER(api_settings.JWT_PAYLOAD_HANDLER(user))
  50. return Response({'code': 1, 'value': user.username, "token": token})
  51. else:
  52. if redis.get("%s_login_times" % username) and int(redis.get("%s_login_times" % username)) >= 3:
  53. redis.setex(username, EXPIRE_TIME, "true")
  54. elif redis.get("%s_login_times" % username):
  55. redis.setex("%s_login_times" % username, EXPIRE_TIME, int(redis.get("%s_login_times" % username))+1)
  56. else:
  57. redis.setex("%s_login_times" % username, EXPIRE_TIME, 1)
  58. return Response({"code": 0, "value": "password error", "token": ""})
  59. class CommonView(APIView):
  60. """
  61. 一些不固定model的请求 不需要权限认证,需要登录认证
  62. """
  63. permission_classes = []
  64. authentication_classes = []
  65. @staticmethod
  66. def get(request):
  67. """
  68. 获取group/region/department用于select框
  69. """
  70. try:
  71. regions = Region.objects.filter(active=0).order_by('name')
  72. departments = Department.objects.filter(active=0).order_by('name')
  73. groups = Group.objects.order_by('name').all()
  74. ser_region = RegionSerializer(instance=regions, many=True)
  75. ser_department = DepartmentSerializer(instance=departments, many=True)
  76. ser_group = GroupSerializer(instance=groups, many=True)
  77. data = {
  78. 'region_info': ser_region.data,
  79. 'department_info': ser_department.data,
  80. 'group_info': ser_group.data
  81. }
  82. return Response({'code': 1, 'value': data, 'token': ''})
  83. except Exception as e:
  84. logger.error(e)
  85. transaction.rollback()
  86. return Response({'code': 0, 'value': 'database error', 'token': ''})
  87. @staticmethod
  88. def post(request):
  89. try:
  90. user_group = request.user.groups.all()
  91. if user_group:
  92. permission_list = []
  93. for group in user_group:
  94. user_permission = group.permissions.all()
  95. if user_permission:
  96. for permission in user_permission:
  97. permission_list.append(permission.codename)
  98. if permission_list:
  99. return Response({'code': 1, 'value': permission_list, 'token': ''})
  100. except Exception as e:
  101. logger.error(e)
  102. transaction.rollback()
  103. return Response({'code': 0, 'value': 'database error', 'token': ''})
  104. return Response({'code': 1, 'value': '', 'token': ''})
  105. class UserViewSet(ModelViewSet):
  106. permission_classes = [MyPermission]
  107. queryset = User.objects.filter(is_active=True).order_by('username')
  108. serializer_class = UserSerializer
  109. pagination_class = MyPagination
  110. # 搜索功能,SearchFilter是搜索过滤,只针对当前查询过滤,所以不在settings.py中配置
  111. filter_backends = [filters.SearchFilter]
  112. # 搜索字段,也可以使用双下划线在Foreign Key或ManyToManyField上执行相关查找:
  113. search_fields = ["username"]
  114. def list(self, request, *args, **kwargs):
  115. """
  116. get请求,分页获取user info
  117. """
  118. try:
  119. ser_user = super().list(self, request, *args, **kwargs)
  120. if ser_user.status_code == 200:
  121. return Response({'code': 1, 'value': ser_user.data, 'token': ''})
  122. return Response({'code': 0, 'value': 'get info fail, errorCode %s' % ser_user.status_code, 'token': ''})
  123. except Exception as e:
  124. logger.error(e)
  125. transaction.rollback()
  126. return Response({'code': 0, 'value': 'database error', 'token': ''})
  127. def create(self, request, *args, **kwargs):
  128. """
  129. Add User
  130. """
  131. try:
  132. serializer = self.get_serializer(data=request.data)
  133. serializer.is_valid(raise_exception=True)
  134. serializer.validated_data['password'] = request.data.get('password')
  135. serializer.validated_data['create_by'] = request.user.username
  136. serializer.validated_data['update_by'] = request.user.username
  137. serializer.validated_data['group_id'] = request.data.getlist('groups')
  138. serializer.save()
  139. return Response({'code': 1, 'value': "Add Success", "token": ''})
  140. except Exception as e:
  141. logger.error(e)
  142. transaction.rollback()
  143. pattern = re.compile(r".*重复键违反唯一约束.+键值.*username.*=(.+).*已经存在", re.S)
  144. result = pattern.findall(str(e))
  145. if result:
  146. res = re.search(r'[\w]+', result[0])
  147. return Response({'code': 0, 'value': 'username: %s is exist' % res.group(), 'token': ''})
  148. return Response({'code': 0, 'value': 'database error', 'token': ''})
  149. def update(self, request, *args, **kwargs):
  150. """
  151. update user,根据路由中的pk找user
  152. """
  153. try:
  154. instance = self.get_object()
  155. if instance.username == request.user.username or request.user.is_superuser:
  156. serializer = self.get_serializer(instance, data=request.data)
  157. serializer.is_valid(raise_exception=True)
  158. serializer.validated_data['password'] = request.data.get('password')
  159. serializer.validated_data['update_by'] = request.user.username
  160. serializer.validated_data['group_id'] = request.data.getlist('groups')
  161. serializer.save()
  162. return Response({'code': 1, 'value': "Update Success", "token": ''})
  163. else:
  164. return Response({'code': 0, 'value': 'Insufficient Authority!', 'token': ''})
  165. except Exception as e:
  166. logger.error(e)
  167. transaction.rollback()
  168. return Response({'code': 0, 'value': 'database error', 'token': ''})
  169. def destroy(self, request, *args, **kwargs):
  170. """
  171. delete user,根据路由中的pk找user
  172. """
  173. try:
  174. user = self.get_object()
  175. if request.user.is_superuser:
  176. user.is_active = False
  177. user.update_by = user.username
  178. user.save()
  179. return Response({'code': 1, 'value': "Delete Success", "token": ""})
  180. else:
  181. return Response({'code': 0, 'value': 'Insufficient Authority!', 'token': ''})
  182. except Exception as e:
  183. logger.error(e)
  184. transaction.rollback()
  185. return Response({'code': 0, 'value': 'database error', 'token': ''})
  186. class GroupViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, GenericViewSet):
  187. permission_classes = [MyPermission]
  188. queryset = Group.objects.all()
  189. serializer_class = GroupSerializer
  190. # 根据ordering_fields中指定的字段排序,可以指定多个
  191. filter_backends = [filters.OrderingFilter]
  192. ordering_fields = ["name"]
  193. def list(self, request, *args, **kwargs):
  194. try:
  195. # 没有这句每次都去读缓存,不能有效进行增删改查。
  196. queryset = self.filter_queryset(self.get_queryset())
  197. ser_group = self.get_serializer(queryset, many=True)
  198. return Response({'code': 1, 'value': ser_group.data, 'token': ''})
  199. except Exception as e:
  200. logger.error(e)
  201. transaction.rollback()
  202. return Response({'code': 0, 'value': 'database error', 'token': ''})
  203. def create(self, request, *args, **kwargs):
  204. try:
  205. serializer = self.get_serializer(data=request.data)
  206. serializer.is_valid(raise_exception=True)
  207. serializer.save()
  208. return Response({'code': 1, 'value': "Add Success", "token": ''})
  209. except Exception as e:
  210. logger.error(e)
  211. transaction.rollback()
  212. pattern = re.compile(r".*group with this name already exists.*", re.S)
  213. if pattern.findall(str(e)):
  214. return Response({'code': 0, 'value': 'group: %s is exist' % request.data.get('name'), 'token': ''})
  215. return Response({'code': 0, 'value': 'database error', 'token': ''})
  216. def destroy(self, request, *args, **kwargs):
  217. """
  218. 先删除多对多关联关系再删除group
  219. """
  220. try:
  221. instance = self.get_object()
  222. instance.permissions.clear()
  223. instance.delete()
  224. return Response({'code': 1, 'value': "Delete Success", "token": ''})
  225. except Exception as e:
  226. logger.error(e)
  227. transaction.rollback()
  228. return Response({'code': 0, 'value': 'database error', 'token': ''})
  229. class PermissionViewSet(ModelViewSet):
  230. """
  231. 序列化采用serializers.Serializer时,如果保存和更新数据用序列化的save()时必须重写create和update方法进行保存和更新
  232. 序列化采用serializers.ModelSerializer(继承Serializer)时,它已经重写了create和update方法
  233. """
  234. permission_classes = [MyPermission]
  235. queryset = Permission.objects.all()
  236. serializer_class = PermissionSerializer
  237. pagination_class = MyPagination
  238. filter_backends = [filters.SearchFilter]
  239. search_fields = ["name", "codename"]
  240. def list(self, request, *args, **kwargs):
  241. try:
  242. ser_user = super().list(self, request, *args, **kwargs)
  243. if ser_user.status_code == 200:
  244. return Response({'code': 1, 'value': ser_user.data, 'token': ''})
  245. return Response({'code': 0, 'value': 'get info fail, errorCode %s' % ser_user.status_code, 'token': ''})
  246. except Exception as e:
  247. logger.error(e)
  248. transaction.rollback()
  249. return Response({'code': 0, 'value': 'database error', 'token': ''})
  250. def create(self, request, *args, **kwargs):
  251. try:
  252. serializer = self.get_serializer(data=request.data)
  253. serializer.is_valid(raise_exception=True)
  254. content_type_obj = ContentType.objects.filter(model='user', app_label='user').first()
  255. serializer.validated_data['content_type'] = content_type_obj
  256. serializer.save()
  257. return Response({'code': 1, 'value': "Add Success", 'token': ''})
  258. except Exception as e:
  259. logger.error(e)
  260. transaction.rollback()
  261. pattern = re.compile(r".*重复键违反唯一约束.+键值.*已经存在", re.S)
  262. if pattern.findall(str(e)):
  263. return Response({'code': 0, 'value': 'codename: %s is exist'
  264. % request.data.get('codename'), 'token': ''})
  265. return Response({'code': 0, 'value': 'database error', 'token': ''})
  266. def update(self, request, *args, **kwargs):
  267. try:
  268. instance = self.get_object()
  269. serializer = self.get_serializer(instance, data=request.data)
  270. serializer.is_valid(raise_exception=True)
  271. serializer.save()
  272. return Response({'code': 1, 'value': "Update Success", "token": ''})
  273. except Exception as e:
  274. logger.error(e)
  275. transaction.rollback()
  276. return Response({'code': 0, 'value': 'database error', 'token': ''})
  277. def destroy(self, request, *args, **kwargs):
  278. try:
  279. instance = self.get_object()
  280. instance.delete()
  281. return Response({'code': 1, 'value': "Delete Success", "token": ''})
  282. except Exception as e:
  283. logger.error(e)
  284. transaction.rollback()
  285. return Response({'code': 0, 'value': 'database error', 'token': ''})
  286. class GroupPermissionViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
  287. permission_classes = [MyPermission]
  288. queryset = Group.objects.all()
  289. serializer_class = GroupSerializer
  290. def retrieve(self, request, *args, **kwargs):
  291. """
  292. 返回当前组拥有的权限和所有权限
  293. """
  294. try:
  295. instance = self.get_object()
  296. serializer = self.get_serializer(instance)
  297. return Response({'code': 1, 'value': serializer.data, 'token': ''})
  298. except Exception as e:
  299. logger.error(e)
  300. transaction.rollback()
  301. result = re.findall(r"You do not have permission to perform this action", str(e))
  302. if result:
  303. return Response({'code': 2, 'value': 'Insufficient Authority!', 'token': ''})
  304. return Response({'code': 0, 'value': 'database error', 'token': ''})
  305. def update(self, request, *args, **kwargs):
  306. """
  307. 更新group权限,前端传入权限id
  308. """
  309. permission_id = request.data.getlist('permission_data[id]')
  310. try:
  311. instance = self.get_object()
  312. if permission_id:
  313. instance.permissions.set(permission_id)
  314. return Response({'code': 1, 'value': "Save Success", "token": ''})
  315. instance.permissions.clear()
  316. return Response({'code': 1, 'value': 'Clear Permission Success', 'token': ''})
  317. except Exception as e:
  318. logger.error(e)
  319. transaction.rollback()
  320. return Response({'code': 0, 'value': 'database error', 'token': ''})

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/71664
推荐阅读
相关标签
  

闽ICP备14008679号