功能变化: 后端新增登录日志功能

pull/49/head
李强 2022-04-18 01:00:24 +08:00
parent 291c78c411
commit eb412cfde5
4 changed files with 88 additions and 3 deletions

View File

@ -35,3 +35,4 @@ DATABASE_PASSWORD = "123456"
DEBUG = True # 线上环境请设置为True DEBUG = True # 线上环境请设置为True
ALLOWED_HOSTS = ["*"] ALLOWED_HOSTS = ["*"]
LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消 LOGIN_NO_CAPTCHA_AUTH = True # 登录接口 /api/token/ 是否需要验证码认证,用于测试,正式环境建议取消
ENABLE_LOGIN_ANALYSIS_LOG = True # 启动登录详细概略获取(通过调用api获取ip详细地址)

View File

@ -29,7 +29,7 @@ class Users(AbstractUser, CoreModel):
(1, "前台用户"), (1, "前台用户"),
) )
user_type = models.IntegerField(choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True, 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="关联岗位") post = models.ManyToManyField(to='Post', verbose_name='关联岗位', db_constraint=False, help_text="关联岗位")
role = models.ManyToManyField(to='Role', 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, 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): def __str__(self):
return f"{self.title}" 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',)

View File

@ -25,6 +25,7 @@ from rest_framework_simplejwt.views import TokenObtainPairView
from application import settings from application import settings
from dvadmin.system.models import Users from dvadmin.system.models import Users
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse 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.serializers import CustomModelSerializer
from dvadmin.utils.validator import CustomValidationError from dvadmin.utils.validator import CustomValidationError
@ -51,7 +52,6 @@ class CaptchaView(APIView):
return SuccessResponse(data=json_data) return SuccessResponse(data=json_data)
class LoginSerializer(TokenObtainPairSerializer): class LoginSerializer(TokenObtainPairSerializer):
""" """
登录的序列化器: 登录的序列化器:
@ -69,7 +69,7 @@ class LoginSerializer(TokenObtainPairSerializer):
} }
def validate(self, attrs): def validate(self, attrs):
captcha = self.initial_data.get('captcha',None) captcha = self.initial_data.get('captcha', None)
if settings.CAPTCHA_STATE: if settings.CAPTCHA_STATE:
if captcha is None: if captcha is None:
raise CustomValidationError("验证码不能为空") raise CustomValidationError("验证码不能为空")
@ -89,6 +89,10 @@ class LoginSerializer(TokenObtainPairSerializer):
data['name'] = self.user.name data['name'] = self.user.name
data['userId'] = self.user.id data['userId'] = self.user.id
data['avatar'] = self.user.avatar data['avatar'] = self.user.avatar
request = self.context.get('request')
request.user = self.user
# 记录登录日志
save_login_log(request=request)
return { return {
"code": 2000, "code": 2000,
"msg": "请求成功", "msg": "请求成功",

View File

@ -3,12 +3,16 @@ Request工具类
""" """
import json import json
import requests
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.urls.resolvers import ResolverMatch from django.urls.resolvers import ResolverMatch
from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework_simplejwt.authentication import JWTAuthentication
from user_agents import parse from user_agents import parse
from dvadmin.system.models import LoginLog
def get_request_user(request): def get_request_user(request):
""" """
@ -163,3 +167,50 @@ def get_verbose_name(queryset=None, view=None, model=None):
except Exception as e: except Exception as e:
pass pass
return model if model else "" 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)