功能变化: 后端新增登录日志功能
							parent
							
								
									291c78c411
								
							
						
					
					
						commit
						eb412cfde5
					
				|  | @ -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详细地址) | ||||
|  |  | |||
|  | @ -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',) | ||||
|  |  | |||
|  | @ -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": "请求成功", | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 李强
						李强