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