移动端认证,短信发送等

This commit is contained in:
曹前明 2022-07-24 23:21:45 +08:00
parent dae9b5cfcc
commit 28f2daca2b
13 changed files with 276 additions and 28 deletions

View File

@ -1,3 +1,5 @@
from django.db import models
from apps.utils.models import BaseModel
# Create your models here.

View File

@ -4,3 +4,12 @@ from rest_framework import serializers
class LoginSerializer(serializers.Serializer):
username = serializers.CharField(label="用户名")
password = serializers.CharField(label="密码")
class SendCodeSerializer(serializers.Serializer):
phone = serializers.CharField(label="手机号")
class CodeLoginSerializer(serializers.Serializer):
phone = serializers.CharField(label="手机号")
code = serializers.CharField(label="验证码")

View File

@ -3,7 +3,7 @@ from django.urls import path
from rest_framework_simplejwt.views import (TokenObtainPairView,
TokenRefreshView)
from apps.auth1.views import LoginView, LogoutView, TokenBlackView
from apps.auth1.views import CodeLogin, GetTokenFromCache, LoginView, LogoutView, SendCode, TokenBlackView, WxLogin, WxmpLogin
API_BASE_URL = 'api/auth/'
urlpatterns = [
@ -11,5 +11,10 @@ urlpatterns = [
path(API_BASE_URL + 'token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path(API_BASE_URL + 'token/black/', TokenBlackView.as_view(), name='token_black'),
path(API_BASE_URL + 'login/', LoginView.as_view(), name='session_login'),
path(API_BASE_URL + 'login_wxmp/', WxmpLogin.as_view(), name='login_wxmp'),
path(API_BASE_URL + 'login_wx/', WxLogin.as_view(), name='login_wx'),
path(API_BASE_URL + 'login_sms_code/', CodeLogin.as_view(), name='login_sms_code'),
path(API_BASE_URL + 'sms_code/', SendCode.as_view(), name='sms_code_send'),
path(API_BASE_URL + 'code_cache_token/', GetTokenFromCache.as_view(), name='code_cache_token'),
path(API_BASE_URL + 'logout/', LogoutView.as_view(), name='session_logout'),
]

View File

@ -8,15 +8,30 @@ from rest_framework.generics import CreateAPIView
from rest_framework.permissions import IsAuthenticated
from apps.auth1.errors import USERNAME_OR_PASSWORD_WRONG
from rest_framework.exceptions import ParseError
import requests
import json
from django.conf import settings
from rest_framework_simplejwt.tokens import RefreshToken
from django.core.cache import cache
from apps.utils.sms import send_sms
from apps.utils.tools import rannum
from apps.auth1.serializers import LoginSerializer
from apps.auth1.serializers import LoginSerializer, SendCodeSerializer
from apps.system.models import User
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
# Create your views here.
def get_tokens_for_user(user: User):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
class TokenBlackView(APIView):
permission_classes = [IsAuthenticated]
@ -66,3 +81,109 @@ class LogoutView(APIView):
"""
logout(request)
return Response()
class WxmpLogin(CreateAPIView):
"""微信小程序自动登录
微信小程序自动登录
"""
authentication_classes = []
permission_classes = []
def post(self, request):
code = request.data['code']
info = requests.get('https://api.weixin.qq.com/sns/jscode2session?appid='+settings.WXMP_APPID+'&secret='+settings.WXMP_APPSECRET+'&js_code=' +
code+'&grant_type=authorization_code').content.decode('utf-8')
info = json.loads(info)
openid = info['openid']
session_key = info['session_key']
try:
user = User.objects.get(wxmp_openid=openid, is_active=True)
ret = get_tokens_for_user(user)
ret['wxmp_session_key'] = session_key
ret['wxmp_openid'] = openid
cache.set(code, ret, 60*5)
return Response(ret)
except Exception:
return Response({'wxmp_openid': openid, 'wxmp_session_key': session_key}, status=400)
class WxLogin(CreateAPIView):
"""微信公众号授权登录
微信公众号授权登录
"""
authentication_classes = []
permission_classes = []
def post(self, request):
code = request.data['code']
info = requests.get('https://api.weixin.qq.com/sns/jscode2session?appid='+settings.WXMP_APPID+'&secret='+settings.WXMP_APPSECRET+'&js_code=' +
code+'&grant_type=authorization_code').content.decode('utf-8')
info = json.loads(info)
openid = info['openid']
session_key = info['session_key']
try:
user = User.objects.get(wx_openid=openid, is_active=True)
ret = get_tokens_for_user(user)
ret['wx_session_key'] = session_key
ret['wx_openid'] = openid
cache.set(code, ret, 60*5)
return Response(ret)
except Exception:
return Response({'wx_openid': openid, 'wx_session_key': session_key}, status=400)
class GetTokenFromCache(CreateAPIView):
"""以code获取token
以code获取token
"""
authentication_classes = []
permission_classes = []
def post(self, request):
code = request.data['code']
ret = cache.get(code, {})
return Response(ret)
class SendCode(CreateAPIView):
authentication_classes = []
permission_classes = []
serializer_class = SendCodeSerializer
def post(self, request):
"""短信验证码发送
短信验证码发送
"""
phone = request.data['phone']
code = rannum(4)
is_ok, _ = send_sms(phone, 505, {'code': code})
cache.set(phone, code, 60*5)
if is_ok:
return Response()
raise ParseError('短信发送失败,请确认手机号')
class CodeLogin(CreateAPIView):
"""手机验证码登录
手机验证码登录
"""
authentication_classes = []
permission_classes = []
def post(self, request):
phone = request.data['phone']
code = request.data['code']
code_exist = cache.get(phone, None)
if code_exist == code:
user = User.objects.filter(phone=phone).first()
if user:
ret = get_tokens_for_user(user)
return Response(ret)
raise ParseError('账户不存在或已禁用')
raise ParseError('验证码错误')

View File

@ -90,8 +90,8 @@ def notify_event(event: Event):
event (Event): _description_
"""
# 生成通知文本
voice_msg = ''
ep = event.employee
params = {'area': event.area.name, 'employee': '', 'event': ''}
if ep:
ep_name = ep.name
ep_type = '员工'
@ -99,17 +99,17 @@ def notify_event(event: Event):
ep_type = '相关方人员'
elif ep.type == 'visitor':
ep_type = '访客'
voice_msg = '位于{}{}{},'.format(event.area.name, ep_type, ep_name)
params['employee'] = ep_type + ep_name
else:
voice_msg = '位于{}的未知人员,'.format(event.area.name)
params['employee'] = '未知人员'
for i in event.cates.all():
voice_msg = voice_msg + i.name + ','
event.voice_msg = voice_msg + '请及时处理'
params['event'] = params['event'] + i.name + ','
event.voice_msg = '位于{}{},{}请及时处理'.format(params['area'], params['employee'], params['event'])
event.save()
# 喇叭播放
Thread(target=save_voice_and_speak, args=(event,), daemon=True).start()
# 创建提醒
Thread(target=create_remind, args=(event,), daemon=True).start()
Thread(target=create_remind, args=(event, params), daemon=True).start()
def save_voice_and_speak(event: Event):
@ -135,7 +135,7 @@ def save_voice_and_speak(event: Event):
spClient.speak(event.voice, sps, v_num)
def create_remind(event: Event):
def create_remind(event: Event, params: dict):
"""
创建事件提醒并发送短信/微信
"""
@ -203,10 +203,11 @@ def create_remind(event: Event):
})
# 开始发送通知
for i in Remind.objects.filter(event=event):
if i.notify_setting.sms_enable:
if i.recipient.employee.phone: # 短信通知
Thread(target=send_sms, args=(i.recipient.employee.phone,
'1001', {'code': '5678'}), daemon=True).start()
if i.notify_setting.sms_enable and i.recipient.employee.phone:
# 发送短信通知
Thread(target=send_sms, args=(i.recipient.employee.phone,
'1003', params), daemon=True).start()
if i.notify_setting.wechat_enable:
pass
event.is_pushed = True

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.12 on 2022-07-24 05:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('system', '0003_alter_user_phone'),
]
operations = [
migrations.AddField(
model_name='user',
name='wx_headimg',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='微信头像'),
),
migrations.AddField(
model_name='user',
name='wx_nickname',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='微信昵称'),
),
migrations.AddField(
model_name='user',
name='wx_openid',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='微信公众号OpenId'),
),
migrations.AddField(
model_name='user',
name='wxmp_openid',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='微信小程序OpenId'),
),
]

View File

@ -134,6 +134,12 @@ class User(AbstractUser, CommonBModel):
depts = models.ManyToManyField(Dept, through='system.userpost')
roles = models.ManyToManyField(Role, verbose_name='关联角色')
# 关联账号
wx_openid = models.CharField('微信公众号OpenId', max_length=100, null=True, blank=True)
wx_nickname = models.CharField('微信昵称', max_length=100, null=True, blank=True)
wx_headimg = models.CharField('微信头像', max_length=100, null=True, blank=True)
wxmp_openid = models.CharField('微信小程序OpenId', max_length=100, null=True, blank=True)
objects = SoftDeletableUserManager()
class Meta:

View File

@ -462,10 +462,53 @@ class UserViewSet(CustomModelViewSet):
'perms': perms,
'belong_dept_name': user.belong_dept.name if user.belong_dept else '',
'post_name': user.post.name if user.post else '',
'is_superuser': user.is_superuser
'is_superuser': user.is_superuser,
'wxmp_openid': user.wxmp_openid,
'wx_openid': user.wx_openid
}
return Response(data)
@action(methods=['post'], detail=False, permission_classes=[IsAuthenticated])
def bind_wxmp(self, request, pk=None):
"""
绑定微信小程序
绑定微信小程序
"""
openid = request.data['openid']
user = request.user
if user.wxmp_openid != openid:
User.objects.filter(wxmp_openid=openid).update(wxmp_openid=None)
user.wxmp_openid = openid
user.save()
return Response({'wxmp_openid': openid})
@action(methods=['post'], detail=False, permission_classes=[IsAuthenticated])
def unbind_wxmp(self, request, pk=None):
"""
解绑微信小程序
解绑微信小程序
"""
user = request.user
user.wxmp_openid = None
user.save()
return Response()
@action(methods=['post'], detail=False, permission_classes=[IsAuthenticated])
def bind_wx(self, request, pk=None):
"""绑定微信公众号
绑定微信公众号, 用于发送通知
"""
openid = request.data['openid']
user = request.user
if user.wx_openid != openid:
User.objects.filter(wx_openid=openid).update(wx_openid=None)
user.wx_openid = openid
user.save()
return Response({'wx_openid': openid})
class FileViewSet(CustomCreateModelMixin, RetrieveModelMixin, ListModelMixin, CustomGenericViewSet):
"""文件上传

View File

@ -26,7 +26,7 @@ def send_sms(phone: str, template_code: str, template_param: dict):
# 手机号码
request.add_query_param('PhoneNumbers', phone)
# 签名名称
request.add_query_param('SignName', "国家建材网")
request.add_query_param('SignName', "曲阳金隅EHS")
# 模板CODE
request.add_query_param('TemplateCode', template_code)
# 如果有模板参数 填写模板参数 如果无 无须填写

View File

@ -1,3 +1,4 @@
from requests import request
from apps.hrm.models import Employee
from apps.utils.constants import EXCLUDE_FIELDS
from apps.utils.serializers import CustomModelSerializer
@ -80,7 +81,9 @@ class VpeopleSerializer(CustomModelSerializer):
class VisitorRegisterSerializer(serializers.Serializer):
name = serializers.CharField(label="姓名")
username = serializers.CharField(label='用户名', min_length=6)
username = serializers.CharField(label="账户名", required=False)
wxmp_openid = serializers.CharField(label='微信小程序ID', required=False)
wx_openid = serializers.CharField(label='微信公众号ID', required=False)
class VisitDetailSerializer(CustomModelSerializer):

View File

@ -75,16 +75,21 @@ class VisitorViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Custo
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
user = User.objects.get_queryset(all=True).filter(username=vdata['username']).first()
if user:
raise ParseError('账号已存在或禁用不可注册')
else:
password = make_password('0000')
user = User.objects.create(name=vdata['name'],
username=vdata['username'],
type='visitor',
password=password)
return Response({'user': user.id}, status=201)
if vdata.get('username', None): # 如果是用户名注册
user = User.objects.get_queryset(all=True).filter(username=vdata['username']).first()
if user:
raise ParseError('账号已存在或禁用不可注册')
else:
password = make_password('0000')
user = User.objects.create(name=vdata['name'],
username=vdata['username'],
type='visitor',
password=password)
return Response({'user': user.id}, status=201)
elif vdata.get('wxmp_openid', None): # 如果是微信小程序注册
pass
elif vdata.get('wx_openid', None): # 如果是微信公众号注册
pass
class VpeopleViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, DestroyModelMixin, CustomGenericViewSet):

View File

@ -3,18 +3,33 @@ from __future__ import absolute_import, unicode_literals
import importlib
import logging
import traceback
from apps.system.models import User
from apps.utils.sms import send_sms
from apps.utils.task import CustomTask
from celery import shared_task
from apps.wf.models import State, Ticket, TicketFlow, Transition
myLogger = logging.getLogger('log')
@shared_task(base=CustomTask)
def send_ticket_notice(ticket_id):
"""
发送通知
"""
pass
ticket = Ticket.objects.filter(id=ticket_id).first()
params = {'workflow': ticket.workflow.name, 'state': ticket.state.name}
if ticket:
if ticket.participant_type == 1:
# 发送短信通知
pt = User.objects.filter(id=ticket.participant).first()
if pt and pt.phone:
send_sms(pt.phone, 1002, params)
elif ticket.participant_type == 2:
pts = User.objects.filter(id__in=ticket.participant).first()
for i in pts:
if i.phone:
send_sms(i.phone, 1002, params)
@shared_task(base=CustomTask)

View File

@ -394,3 +394,8 @@ BD_SP_SECRET = conf.BD_SP_SECRET
# 算法层
AI_IP = conf.AI_IP
# 微信有关
WXMP_APPID = conf.WXMP_APPID
WXMP_APPSECRET = conf.WXMP_APPSECRET