- 前言:在特定的场景下,需要实现用户的活跃状态检测,如(在线,今日在线,近一周,近两周…),在接到这个需求之后,讨论过很多方案,其中最多提到的就是按照session的失效时间来做,但是,在后来的讨论中,发现在项目中,session的失效时间一般不能设置太短比如两小时,这样是不符合实际场景的,我们项目要求失效时间为24小时,因此如果想要实现动态的检测用户活跃状态就不能满足了。
最后在问过群友后,万能的群友给去了一个很好的解决方案:
本次的解决方案也是依托这个思想来进行,方案如下: - [1] 数据库部分:在User表中新增用户在线状态字段和在线状态更新时间字段,并且写一个获取用户状态的方法(因为此需求的难点在与当天在线和当前在线的检测,而该方式是实现除当前在线之外的状态的检测)。
# 新增字段
class PersonalInfo(models.Model):
id = models.OneToOneField(User, primary_key=True,verbose_name='选择用户', on_delete=models.CASCADE)
online_status = models.IntegerField(null=True, blank=True, verbose_name='在线状态', choices=ONLINE_STATUS)
online_time = models.DateTimeField(null=True, blank=True, verbose_name='在线状态更新时间')
ONLINE_STATUS = (
(0, "当前在线"),
(1, "今日在线"),
(2, "近一周"),
(3, "近两周"),
(4, "近一个月"),
(5, "近两个月"),
(6, "近三个月"),
(7, "近六个月"),
(8, "六个月以前"),
)
# 新增方法
def active_time(self):
# 计算活跃时间
try:
# 因为在Django自带的User表中有最后登录时间(last_login)这个字段,因此,可以通过计算当前时间与最后登录时间的差来计算用户的在线状态(除当前在线)
at = self.id.last_login # 获取最后登录时间
ds = datetime.datetime.now() - at # 获取时间差
year = int(time.strftime('%Y', time.localtime())) # 获取当前年份
month = int(time.strftime('%m', time.localtime())) # 获取当前月份
days = calendar.monthrange(year, month)[1] # 计算当月的天数
days_1 = calendar.monthrange(year - 1 if month == 1 else year, 12 if month == 1 else month - 1)[1] # 1个月前
days_2 = calendar.monthrange(year - 1 if month <= 2 else year, 12 if month <= 2 else month - 1)[1] # 2个月前
days_3 = calendar.monthrange(year - 1 if month <= 3 else year, 12 if month <= 3 else month - 1)[1] # 3个月前
days_4 = calendar.monthrange(year - 1 if month <= 4 else year, 12 if month <= 4 else month - 1)[1] # 4个月前
days_5 = calendar.monthrange(year - 1 if month <= 5 else year, 12 if month <= 5 else month - 1)[1] # 5个月前
if ds.days <= 0:
return 1 # 当天
elif ds.days <= 7:
return 2 # 一周
elif ds.days <= 14:
return 3 # 两周
elif ds.days <= days:
return 4 # 近一个月
elif ds.days <= (days + days_1):
return 5 # 近两个月
elif ds.days <= (days + days_1 + days_2):
return 6 # 近三个月
elif ds.days <= (days + days_1 + days_2 + days_3 + days_4 + days_5):
return 7 # 近六个月
else:
return 8 # 六个月以上
except Exception as e:
raise e
- [2] 用户本身的状态获取,采取固定时间Say Hi模式,而这个定时任务由前端来发起,如果在服务器端设置定时任务,权衡利弊后发现实现起来很耗资源。
class OnlineStatus(APIView):
def get(self, request):
# 当前用户的心跳检测(定时由前端做)
'''
校验session(封装过的):
session_dict = session_exist(request)
if session_dict["code"] is 0:
return JsonResponse(session_dict, safe=False, json_dumps_params={'ensure_ascii': False})
'''
session_key = request.META.get("HTTP_AUTHORIZATION")
session = Session.objects.get(session_key=session_key)
uid = session.get_decoded().get('_auth_user_id')
back_dir = dict(code=200, msg="", data=dict())
try:
user = User.objects.get(id=uid)
p1 = PersonalInfo.objects.filter(id=user.id).exists()
now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
# 这里判断是为了PersonInfo表数据初始化
if p1:
# 设置用户在线为当前在线
pi = PersonalInfo.objects.get(id=user.id)
pi.online_status = 0
pi.online_time = now
pi.save()
else:
# 创建数据,设置用户在线为当前在线,因为这是在心跳检测,所以直接设置
PersonalInfo.objects.create(id=user, online_status=0, online_time=now)
except Exception as e:
back_dir['msg'] = str(e)
return Response(back_dir)
- [3] 用户获取好友活跃状态,为了防止用户心跳检测之后时间超出没有再次检测,从而没有改变在线状态,因此需要先调用刚刚自定义active_time方法,检测用户是否是当前在线状态,(如果是则要判断用户距离上次心跳检测时间是否超过预期时间,如果是则****修改为当天在线,如果否则直接返回当前在线状态)如果当前状态为非当天在线直接,修改用户状态,返回检测结果。
# 反序列化器
class OnlineStatusSerializer(serializers.Serializer):
user_id = serializers.IntegerField(required=True, help_text='用户id')
def post(self, request):
"""
检测其它用户的活跃状态
ONLINE_STATUS = (
(0, "当前在线"),
(1, "今日在线"),
(2, "近一周"),
(3, "近两周"),
(4, "近一个月"),
(5, "近两个月"),
(6, "近三个月"),
(7, "近六个月"),
(8, "六个月以前"),
)
"""
session_dict = session_exist(request)
'''
if session_dict["code"] is 0:
return JsonResponse(session_dict, safe=False, json_dumps_params={'ensure_ascii': False})
'''
data = request.data
back_dir = dict(code=200, msg="", data=dict())
# drf参数校验
src = OnlineStatusSerializer(data=data)
bool = src.is_valid()
now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
if bool:
try:
p1 = PersonalInfo.objects.filter(id=data['user_id']).first()
# PersonInfo可能未同步,因此检测一下
if p1:
# 检测一下用户现在应该是是状态
sta = p1.active_time()
# 如果是当天在线,则再判断是否是当前在线
if sta <= 1:
ds = datetime.datetime.now() - p1.id.last_login
# 如果心跳检测时间超过规定检测时间则说明用户已经下线
if ds.seconds >= ONLINE_STATUS_CHECK_TIME * 60:
p1.online_status = 1
p1.online_time = now
p1.save()
else:
p1.online_status = 0
# 这里不改变时间,应该这里应该就是0 ,之所以修改完全是为了加一层保证
p1.save()
# 如果超过一天在线则不管是否是当前在线
else:
# 可能心跳检测完后没有再此按照时间判断是说明状态,因此要改变
p1.online_status = sta
p1.online_time = now
p1.save()
else:
u1 = User.objects.filter(id=data['user_id']).first()
p1 = PersonalInfo.objects.create(id=u1)
p1.online_status = p1.active_time()
p1.save()
back_dir['data'] = p1.online_status
except Exception as e:
back_dir['msg'] = str(e)
else:
back_dir['msg'] = bool.errors
return Response(back_dir)
END!
【文章编写不易,如需转发请联系作者!】