diff --git a/apps/auth1/models.py b/apps/auth1/models.py index 71a83623..bf70e4a6 100755 --- a/apps/auth1/models.py +++ b/apps/auth1/models.py @@ -1,3 +1,5 @@ from django.db import models +from apps.utils.models import BaseModel + # Create your models here. diff --git a/apps/auth1/serializers.py b/apps/auth1/serializers.py index a488f133..6c12c372 100755 --- a/apps/auth1/serializers.py +++ b/apps/auth1/serializers.py @@ -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="验证码") diff --git a/apps/auth1/urls.py b/apps/auth1/urls.py index ca3245b2..381a6742 100755 --- a/apps/auth1/urls.py +++ b/apps/auth1/urls.py @@ -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'), ] diff --git a/apps/auth1/views.py b/apps/auth1/views.py index f51efee6..d5468ddf 100755 --- a/apps/auth1/views.py +++ b/apps/auth1/views.py @@ -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('验证码错误') diff --git a/apps/ecm/service.py b/apps/ecm/service.py index 089aa62c..fc69fe32 100644 --- a/apps/ecm/service.py +++ b/apps/ecm/service.py @@ -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 diff --git a/apps/system/migrations/0004_auto_20220724_1305.py b/apps/system/migrations/0004_auto_20220724_1305.py new file mode 100644 index 00000000..44f8e6b1 --- /dev/null +++ b/apps/system/migrations/0004_auto_20220724_1305.py @@ -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'), + ), + ] diff --git a/apps/system/models.py b/apps/system/models.py index 878066da..5e30f590 100755 --- a/apps/system/models.py +++ b/apps/system/models.py @@ -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: diff --git a/apps/system/views.py b/apps/system/views.py index 74a33e7e..82316e7f 100755 --- a/apps/system/views.py +++ b/apps/system/views.py @@ -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): """文件上传 diff --git a/apps/utils/sms.py b/apps/utils/sms.py index 9851d9d1..eb7e6f6b 100644 --- a/apps/utils/sms.py +++ b/apps/utils/sms.py @@ -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) # 如果有模板参数 填写模板参数 如果无 无须填写 diff --git a/apps/vm/serializers.py b/apps/vm/serializers.py index 4dcc990c..87182fad 100644 --- a/apps/vm/serializers.py +++ b/apps/vm/serializers.py @@ -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): diff --git a/apps/vm/views.py b/apps/vm/views.py index 6b2bbd4e..7b5ccc43 100644 --- a/apps/vm/views.py +++ b/apps/vm/views.py @@ -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): diff --git a/apps/wf/tasks.py b/apps/wf/tasks.py index 5a7dc1df..b8e26c64 100644 --- a/apps/wf/tasks.py +++ b/apps/wf/tasks.py @@ -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) diff --git a/server/settings.py b/server/settings.py index 2e1c614b..1c5082c8 100755 --- a/server/settings.py +++ b/server/settings.py @@ -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