功能变化: 后端新增登录日志功能
							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