Pre Merge pull request !90 from A逗比_/dev_cheney

pull/90/MERGE
A逗比_ 2023-03-23 14:53:28 +00:00 committed by Gitee
commit 054c47236a
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
14 changed files with 92 additions and 19 deletions

1
backend/.gitignore vendored
View File

@ -92,6 +92,7 @@ ENV/
!**/migrations/__init__.py
*.pyc
conf/
conf/env.py
!conf/env.example.py
db.sqlite3
media/

View File

@ -3,6 +3,6 @@ from django.urls import path
from application.websocketConfig import MegCenter
websocket_urlpatterns = [
path('ws/<str:service_uid>/', MegCenter.as_asgi()), #consumers.DvadminWebSocket 是该路由的消费者
path('ws/', MegCenter.as_asgi()), #consumers.DvadminWebSocket 是该路由的消费者
]

View File

@ -283,7 +283,8 @@ REST_FRAMEWORK = {
),
"DEFAULT_PAGINATION_CLASS": "dvadmin.utils.pagination.CustomPagination", # 自定义分页
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
# "rest_framework_simplejwt.authentication.JWTAuthentication",
"dvadmin.utils.myJWTAuthentication.myJWTAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": [

View File

@ -58,7 +58,7 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
async def connect(self):
try:
import jwt
self.service_uid = self.scope["url_route"]["kwargs"]["service_uid"]
self.service_uid = self.scope["subprotocols"][0]
decoded_result = jwt.decode(self.service_uid, settings.SECRET_KEY, algorithms=["HS256"])
if decoded_result:
self.user_id = decoded_result.get('user_id')
@ -68,7 +68,7 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
self.chat_group_name,
self.channel_name
)
await self.accept()
await self.accept(subprotocol=self.service_uid)
# 主动推送消息
unread_count = await _get_message_unread(self.user_id)
if unread_count == 0:
@ -78,7 +78,8 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
await self.send_json(
set_message('system', 'SYSTEM', "请查看您的未读消息~",
unread=unread_count))
except InvalidSignatureError:
except InvalidSignatureError as e:
print(e.__str__())
await self.disconnect(None)
async def disconnect(self, close_code):
@ -121,6 +122,7 @@ class MessageCreateSerializer(CustomModelSerializer):
model = MessageCenter
fields = "__all__"
read_only_fields = ["id"]
def websocket_push(user_id,message):
username = "user_" + str(user_id)
@ -133,6 +135,7 @@ def websocket_push(user_id,message):
}
)
def create_message_push(title: str, content: str, target_type: int=0, target_user: list=[], target_dept=None, target_role=None,
message: dict = {'contentType': 'INFO', 'content': '测试~'}, request= Request):
if message is None:

View File

@ -47,3 +47,7 @@ ALLOWED_HOSTS = ["*"]
# daphne启动命令
#daphne application.asgi:application -b 0.0.0.0 -p 8000
# 是否开启用户登录严格模式
# 开启后同一个用户同一时间只允许在一个浏览器内登录并且注销时jwt Token 立即失效
STRICT_LOGIN = True

View File

@ -13,12 +13,13 @@ STATUS_CHOICES = (
)
class Users(CoreModel,AbstractUser):
class Users(CoreModel, AbstractUser):
username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name="用户账号", help_text="用户账号")
email = models.EmailField(max_length=255, verbose_name="邮箱", null=True, blank=True, help_text="邮箱")
mobile = models.CharField(max_length=255, verbose_name="电话", null=True, blank=True, help_text="电话")
avatar = models.CharField(max_length=255, verbose_name="头像", null=True, blank=True, help_text="头像")
name = models.CharField(max_length=40, verbose_name="姓名", help_text="姓名")
login_flag = models.CharField(max_length=36, verbose_name="凭证失效flag", null=True, blank=True, help_text="凭证及时失效标志")
GENDER_CHOICES = (
(0, "未知"),
(1, ""),

View File

@ -1,5 +1,6 @@
import base64
import hashlib
import uuid
from datetime import datetime, timedelta
from captcha.views import CaptchaStore, captcha_image
@ -57,11 +58,13 @@ class LoginSerializer(TokenObtainPairSerializer):
captcha = serializers.CharField(
max_length=6, required=False, allow_null=True, allow_blank=True
)
class Meta:
model = Users
fields = "__all__"
read_only_fields = ["id"]
class LoginView(TokenObtainPairView):
"""
登录接口
@ -125,6 +128,14 @@ class LoginView(TokenObtainPairView):
if role:
result['role_info'] = role.values('id', 'name', 'key')
refresh = LoginSerializer.get_token(user)
if settings.STRICT_LOGIN:
login_flag = uuid.uuid4().__str__()
refresh['login_flag'] = login_flag
user.login_flag = login_flag
try:
user.save()
except Exception as e:
return ErrorResponse("登录失败!系统发生致命错误!")
result["refresh"] = str(refresh)
result["access"] = str(refresh.access_token)
# 记录登录日志
@ -165,6 +176,13 @@ class LoginTokenView(TokenObtainPairView):
class LogoutView(APIView):
def post(self, request):
if settings.STRICT_LOGIN:
user = request.user
try:
user.login_flag = "logout"
user.save()
except Exception as e:
pass
return DetailResponse(msg="注销成功")

View File

@ -100,7 +100,7 @@ def websocket_push(user_id, message):
主动推送消息
"""
username = "user_"+str(user_id)
print(103,message)
# print(103,message)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
username,

View File

@ -0,0 +1,38 @@
from django.conf import settings
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import InvalidToken
class myJWTAuthentication(JWTAuthentication):
"""
重写校验
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def authenticate(self, request):
header = self.get_header(request)
if header is None:
return None
raw_token = self.get_raw_token(header)
if raw_token is None:
return None
validated_token = self.get_validated_token(raw_token)
user = self.get_user(validated_token)
user_login_flag = user.login_flag
if settings.STRICT_LOGIN and validated_token['login_flag'] != user_login_flag:
if user_login_flag == "logout":
raise InvalidToken({
"detail": "Token has invalided",
"messages": "token已失效",
})
else:
raise InvalidToken({
"detail": "The user has logged in elsewhere, please confirm the account security!",
"code": "User logs in elsewhere",
"messages": "用户已在其他地方登录,请确认账户安全!",
})
else:
return user, validated_token

View File

@ -21,16 +21,23 @@ export function getErrorMessage (msg) {
util.cookies.remove('token')
util.cookies.remove('uuid')
router.push({ path: '/login' })
router.go(0)
// router.go(0)
return '登录超时,请重新登录!'
}
if (msg.code === 'user_not_found') {
util.cookies.remove('token')
util.cookies.remove('uuid')
router.push({ path: '/login' })
router.go(0)
// router.go(0)
return '用户无效,请重新登录!'
}
if (msg.code === 'User logs in elsewhere') {
util.cookies.remove('token')
util.cookies.remove('uuid')
router.push({ path: '/login' })
// router.go(0)
return '用户已在其他地方登录,请确认账户安全!'
}
return Object.values(msg)
}
if (Object.prototype.toString.call(msg).slice(8, -1) === 'Array') {
@ -82,7 +89,6 @@ function createService () {
util.cookies.remove('token')
util.cookies.remove('uuid')
util.cookies.remove('refresh')
router.push({ path: '/login' })
errorCreate(`${getErrorMessage(dataAxios.msg)}`)
break
case 404:

View File

@ -4,8 +4,9 @@ import store from '@/store'
function initWebSocket (e) {
const token = util.cookies.get('token')
if (token) {
const wsUri = util.wsBaseURL() + 'ws/' + token + '/'
this.socket = new WebSocket(wsUri)// 这里面的this都指向vue
const wsUri = util.wsBaseURL() + 'ws/'
// const wsUri = util.wsBaseURL() + 'ws/' + token + '/'
this.socket = new WebSocket(wsUri, [token])// 这里面的this都指向vue
this.socket.onerror = webSocketOnError
this.socket.onmessage = webSocketOnMessage
this.socket.onclose = closeWebsocket

View File

@ -213,7 +213,7 @@ export default {
},
//
onDrag (e, item) {
const { key, width, height } = item
const { key } = item
const parentRect = this.$refs.widgets.getBoundingClientRect()
let mouseInGrid = false
if (((mouseXY.x > parentRect.left) && (mouseXY.x < parentRect.right)) && ((mouseXY.y > parentRect.top) && (mouseXY.y < parentRect.bottom))) {
@ -238,15 +238,15 @@ export default {
}
const el = this.$refs.gridlayout.$children[index]
el.dragging = { top: mouseXY.y - parentRect.top, left: mouseXY.x - parentRect.left }
const new_pos = el.calcXY(mouseXY.y - parentRect.top, mouseXY.x - parentRect.left)
const newPos = el.calcXY(mouseXY.y - parentRect.top, mouseXY.x - parentRect.left)
if (mouseInGrid === true) {
this.$refs.gridlayout.dragEvent('dragstart', this.getLayoutElementNumber(key), new_pos.x, new_pos.y, 1, 1)
this.$refs.gridlayout.dragEvent('dragstart', this.getLayoutElementNumber(key), newPos.x, newPos.y, 1, 1)
DragPos.i = String(index)
DragPos.x = this.layout[index].x
DragPos.y = this.layout[index].y
}
if (mouseInGrid === false) {
this.$refs.gridlayout.dragEvent('dragend', this.getLayoutElementNumber(key), new_pos.x, new_pos.y, 1, 1)
this.$refs.gridlayout.dragEvent('dragend', this.getLayoutElementNumber(key), newPos.x, newPos.y, 1, 1)
this.layout = this.layout.filter(obj => obj.i !== this.getLayoutElementNumber(key))
}
}

View File

@ -8,7 +8,7 @@ export const crudOptions = (vm) => {
// rowKey: true, // 必须设置true or false
rowId: 'id',
height: '100%', // 表格高度100%, 使用toolbar必须设置
highlightCurrentRow: false,
highlightCurrentRow: false
},
rowHandle: {
fixed: 'right',

View File

@ -9,8 +9,8 @@ export const crudOptions = (vm) => {
},
options: {
height: '100%',
// tableType: 'vxe-table',
//rowKey: true,
// tableType: 'vxe-table',
// rowKey: true,
rowId: 'id'
},
selectionRow: {