From eb412cfde53ff612845bf3e500805f6339a978c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BC=BA?= <1206709430@qq.com> Date: Mon, 18 Apr 2022 01:00:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=8F=98=E5=8C=96:=20?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E6=96=B0=E5=A2=9E=E7=99=BB=E5=BD=95=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/conf/env.example.py | 1 + backend/dvadmin/system/models.py | 31 +++++++++++++++- backend/dvadmin/system/views/login.py | 8 +++-- backend/dvadmin/utils/request_util.py | 51 +++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/backend/conf/env.example.py b/backend/conf/env.example.py index 4b1b16d..9c8def9 100644 --- a/backend/conf/env.example.py +++ b/backend/conf/env.example.py @@ -35,3 +35,4 @@ DATABASE_PASSWORD = "123456" DEBUG = True # 线上环境请设置为True ALLOWED_HOSTS = ["*"] LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消 +ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址) diff --git a/backend/dvadmin/system/models.py b/backend/dvadmin/system/models.py index 27dea49..68ea579 100644 --- a/backend/dvadmin/system/models.py +++ b/backend/dvadmin/system/models.py @@ -29,7 +29,7 @@ class Users(AbstractUser, CoreModel): (1, "前台用户"), ) user_type = models.IntegerField(choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True, - help_text="用户类型") + help_text="用户类型") post = models.ManyToManyField(to='Post', verbose_name='关联岗位', db_constraint=False, help_text="关联岗位") role = models.ManyToManyField(to='Role', verbose_name='关联角色', db_constraint=False, help_text="关联角色") dept = models.ForeignKey(to='Dept', verbose_name='所属部门', on_delete=models.PROTECT, db_constraint=False, null=True, @@ -306,3 +306,32 @@ class SystemConfig(CoreModel): def __str__(self): return f"{self.title}" + + +class LoginLog(CoreModel): + LOGIN_TYPE_CHOICES = ( + (1, '普通登录'), + ) + username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录ip") + ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip") + agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息") + browser = models.CharField(max_length=200, verbose_name="浏览器名", null=True, blank=True, help_text="浏览器名") + os = models.CharField(max_length=200, verbose_name="操作系统", null=True, blank=True, help_text="操作系统") + continent = models.CharField(max_length=50, verbose_name="州", null=True, blank=True, help_text="州") + country = models.CharField(max_length=50, verbose_name="国家", null=True, blank=True, help_text="国家") + province = models.CharField(max_length=50, verbose_name="省份", null=True, blank=True, help_text="省份") + city = models.CharField(max_length=50, verbose_name="城市", null=True, blank=True, help_text="城市") + district = models.CharField(max_length=50, verbose_name="县区", null=True, blank=True, help_text="县区") + isp = models.CharField(max_length=50, verbose_name="运营商", null=True, blank=True, help_text="运营商") + area_code = models.CharField(max_length=50, verbose_name="区域代码", null=True, blank=True, help_text="区域代码") + country_english = models.CharField(max_length=50, verbose_name="英文全称", null=True, blank=True, help_text="英文全称") + country_code = models.CharField(max_length=50, verbose_name="简称", null=True, blank=True, help_text="简称") + longitude = models.CharField(max_length=50, verbose_name="经度", null=True, blank=True, help_text="经度") + latitude = models.CharField(max_length=50, verbose_name="纬度", null=True, blank=True, help_text="纬度") + login_type = models.IntegerField(default=1, choices=LOGIN_TYPE_CHOICES, verbose_name="登录类型", help_text="登录类型") + + class Meta: + db_table = table_prefix + 'system_login_log' + verbose_name = '登录日志' + verbose_name_plural = verbose_name + ordering = ('-create_datetime',) diff --git a/backend/dvadmin/system/views/login.py b/backend/dvadmin/system/views/login.py index 3a85d10..90bf163 100644 --- a/backend/dvadmin/system/views/login.py +++ b/backend/dvadmin/system/views/login.py @@ -25,6 +25,7 @@ from rest_framework_simplejwt.views import TokenObtainPairView from application import settings from dvadmin.system.models import Users from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse +from dvadmin.utils.request_util import save_login_log from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.validator import CustomValidationError @@ -51,7 +52,6 @@ class CaptchaView(APIView): return SuccessResponse(data=json_data) - class LoginSerializer(TokenObtainPairSerializer): """ 登录的序列化器: @@ -69,7 +69,7 @@ class LoginSerializer(TokenObtainPairSerializer): } def validate(self, attrs): - captcha = self.initial_data.get('captcha',None) + captcha = self.initial_data.get('captcha', None) if settings.CAPTCHA_STATE: if captcha is None: raise CustomValidationError("验证码不能为空") @@ -89,6 +89,10 @@ class LoginSerializer(TokenObtainPairSerializer): data['name'] = self.user.name data['userId'] = self.user.id data['avatar'] = self.user.avatar + request = self.context.get('request') + request.user = self.user + # 记录登录日志 + save_login_log(request=request) return { "code": 2000, "msg": "请求成功", diff --git a/backend/dvadmin/utils/request_util.py b/backend/dvadmin/utils/request_util.py index 87fa3ff..14414fb 100644 --- a/backend/dvadmin/utils/request_util.py +++ b/backend/dvadmin/utils/request_util.py @@ -3,12 +3,16 @@ Request工具类 """ import json +import requests +from django.conf import settings from django.contrib.auth.models import AbstractBaseUser from django.contrib.auth.models import AnonymousUser from django.urls.resolvers import ResolverMatch from rest_framework_simplejwt.authentication import JWTAuthentication from user_agents import parse +from dvadmin.system.models import LoginLog + def get_request_user(request): """ @@ -163,3 +167,50 @@ def get_verbose_name(queryset=None, view=None, model=None): except Exception as e: pass return model if model else "" + + +def get_ip_analysis(ip): + """ + 获取ip详细概略 + :param ip: ip地址 + :return: + """ + data = { + "continent": "", + "country": "", + "province": "", + "city": "", + "district": "", + "isp": "", + "area_code": "", + "country_english": "", + "country_code": "", + "longitude": "", + "latitude": "" + } + if ip != 'unknown' and ip: + if getattr(settings, 'ENABLE_LOGIN_ANALYSIS_LOG', True): + res = requests.post(url='https://ip.django-vue-admin.com/ip/analysis', data=json.dumps({"ip": ip})) + if res.status_code == 200: + res_data = res.json() + if res_data.get('code') == 0: + data = res_data.get('data') + return data + return data + + +def save_login_log(request): + """ + 保存登录日志 + :return: + """ + ip = get_request_ip(request=request) + analysis_data = get_ip_analysis(ip) + analysis_data['username'] = request.user.username + analysis_data['ip'] = ip + analysis_data['agent'] = str(parse(request.META['HTTP_USER_AGENT'])) + analysis_data['browser'] = get_browser(request) + analysis_data['os'] = get_os(request) + analysis_data['creator_id'] = request.user.id + analysis_data['dept_belong_id'] = getattr(request.user, 'dept_id', '') + LoginLog.objects.create(**analysis_data)