调整websocket订阅消息处理流程

添加消息处理器(一般用于处理非图形状态变化消息)
This commit is contained in:
walker 2023-06-30 15:50:30 +08:00
parent 6626ad5114
commit 7e4eaed0cf
10 changed files with 410 additions and 14 deletions

View File

@ -17,6 +17,7 @@
"@pixi/graphics-extras": "^7.2.4",
"@quasar/extras": "^1.0.0",
"@stomp/stompjs": "^7.0.0",
"alova": "^2.7.1",
"axios": "^1.4.0",
"google-protobuf": "^3.21.2",
"js-base64": "^3.7.5",
@ -39,6 +40,7 @@
"eslint-plugin-vue": "^9.0.0",
"prettier": "^2.5.1",
"protoc-gen-ts": "^0.8.6",
"ts-md5": "^1.3.1",
"typescript": "^4.5.4"
},
"engines": {

View File

@ -0,0 +1,42 @@
export class PageQueryDto {
current: number;
size: number;
orders?: OrderItemDto[];
constructor(current: number, size: number, orders?: OrderItemDto[]) {
this.current = current;
this.size = size;
this.orders = orders;
}
}
export class OrderItemDto {
column: string;
asc: boolean;
constructor(column: string, asc: boolean) {
this.column = column;
this.asc = asc;
}
static asc(column: string): OrderItemDto {
return new OrderItemDto(column, true);
}
static desc(column: string): OrderItemDto {
return new OrderItemDto(column, false);
}
}
export interface PageDto<T = unknown> {
records: T[];
/**
*
*/
total: number;
/**
*
*/
current: number;
/**
*
*/
size: number;
}

View File

@ -0,0 +1,77 @@
import { api } from 'src/boot/axios';
import { PageDto, PageQueryDto } from './ApiCommon';
import { Md5 } from 'ts-md5';
const UserUriBase = '/api/user';
interface RegisterInfo {
name: string;
mobile: string;
password: string;
}
interface User {
id: string;
name: string;
mobile: string;
password: string;
registerTime: string;
}
const PasswordSult = '4a6d74126bfd06d69406fcccb7e7d5d9'; // 密码加盐
function encryptPassword(password: string): string {
const md5 = new Md5();
return md5.appendStr(`${password}${PasswordSult}`).end() as string;
}
/**
*
* @param info
* @returns
*/
export async function register(info: RegisterInfo): Promise<User> {
const response = await api.post(`${UserUriBase}/register`, {
...info,
password: encryptPassword(info.password),
});
return response.data as User;
}
interface LoginInfo {
account: string;
password: string;
}
/**
*
* @param loginInfo
* @returns
*/
export async function login(loginInfo: LoginInfo): Promise<string> {
const info = { ...loginInfo, password: encryptPassword(loginInfo.password) };
const response = await api.post(`${UserUriBase}/login`, info);
return response.data;
}
export class PagingQueryParams extends PageQueryDto {
name?: string;
roleId?: number;
}
/**
*
* @param params
* @returns
*/
export async function pageQuery(
params: PagingQueryParams
): Promise<PageDto<User>> {
const response = await api.get(`${UserUriBase}/paging`, {
params: params,
});
return response.data;
}
export function distributeRole(query: { userId: number; roleIds: number[] }) {
return api.post('/api/role/distribute', query);
}

View File

@ -1,7 +1,7 @@
function getHost(): string {
// return '192.168.3.7:9081';
// return '192.168.3.47:9081';
return '192.168.3.7:9081';
return '192.168.3.233:9081';
// return '192.168.3.7:9081';
}
export function getHttpBase() {

View File

@ -152,12 +152,32 @@ export class StompCli {
}
// 状态订阅消息转换器
export type MessageConverter = (message: Uint8Array) => GraphicState[];
export type GraphicStateMessageConvert = (
message: Uint8Array
) => GraphicState[];
// 订阅消息处理器
export type SubscriptionMessageHandle = (message: Uint8Array) => void;
// 图形app状态订阅
export interface AppStateSubscription {
/**
*
*/
destination: string;
messageConverter: MessageConverter;
subscription?: StompSubscription; // 订阅成功对象,用于取消订阅
/**
*
*/
messageConverter?: GraphicStateMessageConvert;
/**
*
*/
messageHandle?: SubscriptionMessageHandle;
/**
*
* 使
*/
subscription?: StompSubscription;
}
/**
@ -180,8 +200,16 @@ export class AppWsMsgBroker {
sub.subscription = StompCli.trySubscribe(
sub.destination,
(message: Message) => {
const graphicStates = sub.messageConverter(message.binaryBody);
this.app.handleGraphicStates(graphicStates);
if (sub.messageConverter) {
const graphicStates = sub.messageConverter(message.binaryBody);
this.app.handleGraphicStates(graphicStates);
} else if (sub.messageHandle) {
sub.messageHandle(message.binaryBody);
} else {
console.error(
`订阅destination:${sub.destination}没有消息处理器或图形状态消息转换器`
);
}
}
);
// console.log('代理订阅结果', sub.subscription)

View File

@ -11,6 +11,19 @@
Title
</q-toolbar-title>
<div class="q-gutter-sm row items-center no-wrap">
<q-btn
round
dense
flat
color="white"
:icon="$q.fullscreen.isActive ? 'fullscreen_exit' : 'fullscreen'"
@click="toggleFullscreen"
v-if="$q.screen.gt.sm"
>
</q-btn>
</div>
<q-btn dense flat round icon="menu" @click="toggleRightDrawer" />
</q-toolbar>
<q-resize-observer @resize="onHeaderResize" />
@ -100,21 +113,26 @@
<draw-properties></draw-properties>
</q-drawer>
<q-page-container>
<q-page-container id="app-page">
<div id="draw-app-container"></div>
</q-page-container>
</q-layout>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import DrawProperties from 'src/components/draw-app/DrawProperties.vue';
import { getDrawApp, loadDrawDatas } from 'src/examples/app';
import { getJwtToken } from 'src/examples/app/configs/TokenManage';
import { getWebsocketUrl } from 'src/examples/app/configs/UrlManage';
import { AppStateSubscription } from 'src/jlgraphic';
import { useDrawStore } from 'src/stores/draw-store';
import { onMounted, onUnmounted, ref } from 'vue';
import { Ref, onMounted, onUnmounted, ref } from 'vue';
import { useRouter } from 'vue-router';
const $q = useQuasar();
const drawStore = useDrawStore();
const router = useRouter();
const leftDrawerOpen = ref(false);
const rightDrawerOpen = ref(false);
@ -129,6 +147,14 @@ function toggleRightDrawer() {
const link = ref('outbox');
function toggleFullscreen(e: unknown): void {
// const dom = document.getElementById('app-page');
// if (dom) {
// $q.fullscreen.toggle(dom);
// }
$q.fullscreen.toggle();
}
onMounted(() => {
console.log('绘制应用layout mounted');
const dom = document.getElementById('draw-app-container');
@ -141,13 +167,21 @@ onMounted(() => {
onResize();
drawApp.enableWsStomp({
wsUrl: getWebsocketUrl(),
token: '',
token: getJwtToken() as string,
onAuthenticationFailed: () => {
$q.dialog({
title: '认证失败',
message: '认证失败或登录超时,请重新登录',
persistent: true,
}).onOk(() => {
router.push({ name: 'login' });
});
},
});
drawApp.subscribe({
destination: '/queue/line/2',
messageConverter: (msg) => {
console.log(msg);
return [];
destination: '/queue/line/3/device',
messageHandle: (msg) => {
console.log('设备消息处理', msg);
},
});
}

93
src/pages/UserLogin.vue Normal file
View File

@ -0,0 +1,93 @@
<template>
<q-layout>
<q-page-container>
<q-page class="flex bg-image flex-center">
<q-card
v-bind:style="$q.screen.lt.sm ? { width: '80%' } : { width: '30%' }"
style="min-width: 350px"
>
<q-card-section>
<q-avatar size="100px" class="absolute-center shadow-10">
<img src="icons/favicon-96x96.png" />
</q-avatar>
</q-card-section>
<q-card-section>
<div class="text-center q-pt-lg">
<div class="col text-h6 ellipsis">登录</div>
</div>
</q-card-section>
<q-card-section>
<q-form @submit="doLogin" class="q-gutter-md">
<q-input
filled
v-model="loginInfo.account"
label="账号"
lazy-rules
type="tel"
mask="###########"
:rules="[(val) => val.length == 11 || '请输入正确手机格式!']"
/>
<q-input
type="password"
filled
v-model="loginInfo.password"
label="密码"
lazy-rules
/>
<div class="text-right">
<q-btn flat label="注册" to="/register" color="primary" />
</div>
<div class="text-center">
<q-btn label="登录" type="submit" color="primary" />
</div>
</q-form>
</q-card-section>
</q-card>
</q-page>
</q-page-container>
</q-layout>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import { ApiError } from 'src/boot/axios';
import { login } from 'src/examples/app/api/UserApi';
import {
clearJwtToken,
saveJwtToken,
} from 'src/examples/app/configs/TokenManage';
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
const $q = useQuasar();
const router = useRouter();
const loginInfo = reactive({
account: '',
password: '',
});
async function doLogin() {
try {
clearJwtToken();
const token = await login(loginInfo);
saveJwtToken(token);
router.push({ name: 'home' });
} catch (err) {
const apiErr = err as ApiError;
$q.notify({
type: 'negative',
message: apiErr.title,
});
}
}
</script>
<style>
.bg-image {
background-image: linear-gradient(135deg, #5481fd 0%, #0e02b1 80%);
}
</style>

View File

@ -0,0 +1,97 @@
<template>
<q-layout>
<q-page-container>
<q-page class="flex bg-image flex-center">
<q-card
v-bind:style="$q.screen.lt.sm ? { width: '80%' } : { width: '30%' }"
style="min-width: 350px"
>
<q-card-section>
<q-avatar size="100px" class="absolute-center shadow-10">
<img src="icons/favicon-96x96.png" />
</q-avatar>
</q-card-section>
<q-card-section>
<div class="text-center q-pt-lg">
<div class="col text-h6 ellipsis">注册</div>
</div>
</q-card-section>
<q-card-section>
<q-form @submit="doRegister" class="q-gutter-md">
<q-input
filled
v-model="loginInfo.name"
label="姓名"
lazy-rules
/>
<q-input
filled
v-model="loginInfo.mobile"
label="手机号"
lazy-rules
type="tel"
mask="###########"
:rules="[(val) => val.length == 11 || '请输入正确手机格式!']"
/>
<q-input
type="password"
filled
v-model="loginInfo.password"
label="密码"
lazy-rules
/>
<div class="text-center q-gutter-md">
<q-btn label="注册" type="submit" color="primary" />
<q-btn label="取消" to="/login" color="secondary" />
</div>
</q-form>
</q-card-section>
</q-card>
</q-page>
</q-page-container>
</q-layout>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import { register } from 'src/examples/app/api/UserApi';
import { ApiError } from 'src/boot/axios';
import { reactive } from 'vue';
import { useRouter } from 'vue-router';
const $q = useQuasar();
const router = useRouter();
const loginInfo = reactive({
name: '',
mobile: '',
password: '',
});
async function doRegister() {
console.log(loginInfo);
try {
await register(loginInfo);
$q.notify({
type: 'positive',
message: '注册成功',
});
router.push({ name: 'login' });
} catch (err) {
const apiErr = err as ApiError;
$q.notify({
type: 'negative',
message: apiErr.title,
});
}
}
</script>
<style>
.bg-image {
background-image: linear-gradient(135deg, #5481fd 0%, #0e02b1 80%);
}
</style>

View File

@ -3,9 +3,22 @@ import { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: () => import('layouts/DrawLayout.vue'),
},
{
path: '/login',
name: 'login',
component: () => import('pages/UserLogin.vue'),
},
{
path: '/register',
name: 'register',
component: () => import('pages/UserRegister.vue'),
},
// Always leave this as last one,
// but you can also remove it
{

View File

@ -734,6 +734,11 @@ ajv@^8.0.1:
require-from-string "^2.0.2"
uri-js "^4.2.2"
alova@^2.7.1:
version "2.7.1"
resolved "https://registry.npmmirror.com/alova/-/alova-2.7.1.tgz#b91b7c80137a44b57792badda9e7ab09acf93f55"
integrity sha512-/+lbPt+u/c4rBx4fq83795Xuh8Ohj5ZB4tJ7pxL2BxLgRlLNCT9/I1yGrgJEz/jXTqIZHMI2EeBAIb/B0u76tw==
ansi-escapes@^4.2.1:
version "4.3.2"
resolved "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
@ -3156,6 +3161,11 @@ toidentifier@1.0.1:
resolved "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
ts-md5@^1.3.1:
version "1.3.1"
resolved "https://registry.npmmirror.com/ts-md5/-/ts-md5-1.3.1.tgz#f5b860c0d5241dd9bb4e909dd73991166403f511"
integrity sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==
tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"