调整websocket订阅消息处理流程
添加消息处理器(一般用于处理非图形状态变化消息)
This commit is contained in:
parent
6626ad5114
commit
7e4eaed0cf
@ -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": {
|
||||
|
42
src/examples/app/api/ApiCommon.ts
Normal file
42
src/examples/app/api/ApiCommon.ts
Normal 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;
|
||||
}
|
77
src/examples/app/api/UserApi.ts
Normal file
77
src/examples/app/api/UserApi.ts
Normal 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);
|
||||
}
|
@ -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() {
|
||||
|
@ -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)
|
||||
|
@ -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
93
src/pages/UserLogin.vue
Normal 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>
|
97
src/pages/UserRegister.vue
Normal file
97
src/pages/UserRegister.vue
Normal 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>
|
@ -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
|
||||
{
|
||||
|
10
yarn.lock
10
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user