update 重写ruoyi-mqtt模块

master
管理员 4 months ago
parent 5c6a78a3bd
commit aebd46af2e

@ -17,7 +17,8 @@
"@microsoft/fetch-event-source": "2.0.1", "@microsoft/fetch-event-source": "2.0.1",
"@element-plus/icons-vue": "2.3.1", "@element-plus/icons-vue": "2.3.1",
"@vueup/vue-quill": "1.1.0", "@vueup/vue-quill": "1.1.0",
"@vueuse/core": "9.5.0", "@vueuse/components": "^12.7.0",
"@vueuse/core": "^12.7.0",
"axios": "0.27.2", "axios": "0.27.2",
"crypto-js": "4.2.0", "crypto-js": "4.2.0",
"echarts": "5.4.0", "echarts": "5.4.0",
@ -25,9 +26,11 @@
"file-saver": "2.0.5", "file-saver": "2.0.5",
"fuse.js": "6.6.2", "fuse.js": "6.6.2",
"js-cookie": "3.0.1", "js-cookie": "3.0.1",
"highlight.js": "^11.11.1",
"jsencrypt": "3.3.1", "jsencrypt": "3.3.1",
"mescroll.js": "1.4.2", "mescroll.js": "1.4.2",
"mitt": "3.0.1", "mitt": "3.0.1",
"normalize.css": "^8.0.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "2.0.22", "pinia": "2.0.22",
"pinia-plugin-persist": "1.0.0", "pinia-plugin-persist": "1.0.0",

@ -17,9 +17,12 @@ importers:
'@vueup/vue-quill': '@vueup/vue-quill':
specifier: 1.1.0 specifier: 1.1.0
version: 1.1.0(vue@3.5.13) version: 1.1.0(vue@3.5.13)
'@vueuse/components':
specifier: ^12.7.0
version: 12.8.2
'@vueuse/core': '@vueuse/core':
specifier: 9.5.0 specifier: ^12.7.0
version: 9.5.0(vue@3.5.13) version: 12.8.2
axios: axios:
specifier: 0.27.2 specifier: 0.27.2
version: 0.27.2 version: 0.27.2
@ -38,6 +41,9 @@ importers:
fuse.js: fuse.js:
specifier: 6.6.2 specifier: 6.6.2
version: 6.6.2 version: 6.6.2
highlight.js:
specifier: ^11.11.1
version: 11.11.1
js-cookie: js-cookie:
specifier: 3.0.1 specifier: 3.0.1
version: 3.0.1 version: 3.0.1
@ -50,6 +56,9 @@ importers:
mitt: mitt:
specifier: 3.0.1 specifier: 3.0.1
version: 3.0.1 version: 3.0.1
normalize.css:
specifier: ^8.0.1
version: 8.0.1
nprogress: nprogress:
specifier: 0.2.0 specifier: 0.2.0
version: 0.2.0 version: 0.2.0
@ -86,7 +95,7 @@ importers:
version: 1.56.1 version: 1.56.1
unplugin-auto-import: unplugin-auto-import:
specifier: 0.11.4 specifier: 0.11.4
version: 0.11.4(@vueuse/core@9.5.0(vue@3.5.13))(rollup@4.30.0)(webpack-sources@3.2.3) version: 0.11.4(@vueuse/core@12.8.2)(rollup@4.30.0)(webpack-sources@3.2.3)
vite: vite:
specifier: 5.4.11 specifier: 5.4.11
version: 5.4.11(@types/node@22.7.5)(sass@1.56.1) version: 5.4.11(@types/node@22.7.5)(sass@1.56.1)
@ -339,61 +348,51 @@ packages:
resolution: {integrity: sha512-bsPGGzfiHXMhQGuFGpmo2PyTwcrh2otL6ycSZAFTESviUoBOuxF7iBbAL5IJXc/69peXl5rAtbewBFeASZ9O0g==} resolution: {integrity: sha512-bsPGGzfiHXMhQGuFGpmo2PyTwcrh2otL6ycSZAFTESviUoBOuxF7iBbAL5IJXc/69peXl5rAtbewBFeASZ9O0g==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.30.0': '@rollup/rollup-linux-arm-musleabihf@4.30.0':
resolution: {integrity: sha512-kvyIECEhs2DrrdfQf++maCWJIQ974EI4txlz1nNSBaCdtf7i5Xf1AQCEJWOC5rEBisdaMFFnOWNLYt7KpFqy5A==} resolution: {integrity: sha512-kvyIECEhs2DrrdfQf++maCWJIQ974EI4txlz1nNSBaCdtf7i5Xf1AQCEJWOC5rEBisdaMFFnOWNLYt7KpFqy5A==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.30.0': '@rollup/rollup-linux-arm64-gnu@4.30.0':
resolution: {integrity: sha512-CFE7zDNrokaotXu+shwIrmWrFxllg79vciH4E/zeK7NitVuWEaXRzS0mFfFvyhZfn8WfVOG/1E9u8/DFEgK7WQ==} resolution: {integrity: sha512-CFE7zDNrokaotXu+shwIrmWrFxllg79vciH4E/zeK7NitVuWEaXRzS0mFfFvyhZfn8WfVOG/1E9u8/DFEgK7WQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.30.0': '@rollup/rollup-linux-arm64-musl@4.30.0':
resolution: {integrity: sha512-MctNTBlvMcIBP0t8lV/NXiUwFg9oK5F79CxLU+a3xgrdJjfBLVIEHSAjQ9+ipofN2GKaMLnFFXLltg1HEEPaGQ==} resolution: {integrity: sha512-MctNTBlvMcIBP0t8lV/NXiUwFg9oK5F79CxLU+a3xgrdJjfBLVIEHSAjQ9+ipofN2GKaMLnFFXLltg1HEEPaGQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.30.0': '@rollup/rollup-linux-loongarch64-gnu@4.30.0':
resolution: {integrity: sha512-fBpoYwLEPivL3q368+gwn4qnYnr7GVwM6NnMo8rJ4wb0p/Y5lg88vQRRP077gf+tc25akuqd+1Sxbn9meODhwA==} resolution: {integrity: sha512-fBpoYwLEPivL3q368+gwn4qnYnr7GVwM6NnMo8rJ4wb0p/Y5lg88vQRRP077gf+tc25akuqd+1Sxbn9meODhwA==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-powerpc64le-gnu@4.30.0': '@rollup/rollup-linux-powerpc64le-gnu@4.30.0':
resolution: {integrity: sha512-1hiHPV6dUaqIMXrIjN+vgJqtfkLpqHS1Xsg0oUfUVD98xGp1wX89PIXgDF2DWra1nxAd8dfE0Dk59MyeKaBVAw==} resolution: {integrity: sha512-1hiHPV6dUaqIMXrIjN+vgJqtfkLpqHS1Xsg0oUfUVD98xGp1wX89PIXgDF2DWra1nxAd8dfE0Dk59MyeKaBVAw==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.30.0': '@rollup/rollup-linux-riscv64-gnu@4.30.0':
resolution: {integrity: sha512-U0xcC80SMpEbvvLw92emHrNjlS3OXjAM0aVzlWfar6PR0ODWCTQtKeeB+tlAPGfZQXicv1SpWwRz9Hyzq3Jx3g==} resolution: {integrity: sha512-U0xcC80SMpEbvvLw92emHrNjlS3OXjAM0aVzlWfar6PR0ODWCTQtKeeB+tlAPGfZQXicv1SpWwRz9Hyzq3Jx3g==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.30.0': '@rollup/rollup-linux-s390x-gnu@4.30.0':
resolution: {integrity: sha512-VU/P/IODrNPasgZDLIFJmMiLGez+BN11DQWfTVlViJVabyF3JaeaJkP6teI8760f18BMGCQOW9gOmuzFaI1pUw==} resolution: {integrity: sha512-VU/P/IODrNPasgZDLIFJmMiLGez+BN11DQWfTVlViJVabyF3JaeaJkP6teI8760f18BMGCQOW9gOmuzFaI1pUw==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.30.0': '@rollup/rollup-linux-x64-gnu@4.30.0':
resolution: {integrity: sha512-laQVRvdbKmjXuFA3ZiZj7+U24FcmoPlXEi2OyLfbpY2MW1oxLt9Au8q9eHd0x6Pw/Kw4oe9gwVXWwIf2PVqblg==} resolution: {integrity: sha512-laQVRvdbKmjXuFA3ZiZj7+U24FcmoPlXEi2OyLfbpY2MW1oxLt9Au8q9eHd0x6Pw/Kw4oe9gwVXWwIf2PVqblg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.30.0': '@rollup/rollup-linux-x64-musl@4.30.0':
resolution: {integrity: sha512-3wzKzduS7jzxqcOvy/ocU/gMR3/QrHEFLge5CD7Si9fyHuoXcidyYZ6jyx8OPYmCcGm3uKTUl+9jUSAY74Ln5A==} resolution: {integrity: sha512-3wzKzduS7jzxqcOvy/ocU/gMR3/QrHEFLge5CD7Si9fyHuoXcidyYZ6jyx8OPYmCcGm3uKTUl+9jUSAY74Ln5A==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.30.0': '@rollup/rollup-win32-arm64-msvc@4.30.0':
resolution: {integrity: sha512-jROwnI1+wPyuv696rAFHp5+6RFhXGGwgmgSfzE8e4xfit6oLRg7GyMArVUoM3ChS045OwWr9aTnU+2c1UdBMyw==} resolution: {integrity: sha512-jROwnI1+wPyuv696rAFHp5+6RFhXGGwgmgSfzE8e4xfit6oLRg7GyMArVUoM3ChS045OwWr9aTnU+2c1UdBMyw==}
@ -435,6 +434,9 @@ packages:
'@types/web-bluetooth@0.0.16': '@types/web-bluetooth@0.0.16':
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
'@types/web-bluetooth@0.0.21':
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
'@vitejs/plugin-vue@4.6.2': '@vitejs/plugin-vue@4.6.2':
resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==} resolution: {integrity: sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
@ -497,12 +499,24 @@ packages:
peerDependencies: peerDependencies:
vue: ^3.2.41 vue: ^3.2.41
'@vueuse/components@12.8.2':
resolution: {integrity: sha512-Nj27u1KsDWzoTthlChzVndJ9g0sW5APCXO3EJkSxlG11nN/ANTUlPPeoJOFvtbdDRnvsMJalboJyE0rRyg7yNg==}
'@vueuse/core@12.8.2':
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
'@vueuse/core@9.5.0': '@vueuse/core@9.5.0':
resolution: {integrity: sha512-6GsWBsJHEb3sYw15mbLrcbslAVY45pkzjJYTKYKCXv88z7srAF0VEW0q+oXKsl58tCbqooplInahXFg8Yo1m4w==} resolution: {integrity: sha512-6GsWBsJHEb3sYw15mbLrcbslAVY45pkzjJYTKYKCXv88z7srAF0VEW0q+oXKsl58tCbqooplInahXFg8Yo1m4w==}
'@vueuse/metadata@12.8.2':
resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}
'@vueuse/metadata@9.5.0': '@vueuse/metadata@9.5.0':
resolution: {integrity: sha512-4M1AyPZmIv41pym+K5+4wup3bKuYebbH8w8BROY1hmT7rIwcyS4tEL+UsGz0Hiu1FCOxcoBrwtAizc0YmBJjyQ==} resolution: {integrity: sha512-4M1AyPZmIv41pym+K5+4wup3bKuYebbH8w8BROY1hmT7rIwcyS4tEL+UsGz0Hiu1FCOxcoBrwtAizc0YmBJjyQ==}
'@vueuse/shared@12.8.2':
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
'@vueuse/shared@9.5.0': '@vueuse/shared@9.5.0':
resolution: {integrity: sha512-HnnCWU1Vg9CVWRCcI8ohDKDRB2Sc4bTgT1XAIaoLSfVHHn+TKbrox6pd3klCSw4UDxkhDfOk8cAdcK+Z5KleCA==} resolution: {integrity: sha512-HnnCWU1Vg9CVWRCcI8ohDKDRB2Sc4bTgT1XAIaoLSfVHHn+TKbrox6pd3klCSw4UDxkhDfOk8cAdcK+Z5KleCA==}
@ -1011,6 +1025,10 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true hasBin: true
highlight.js@11.11.1:
resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
engines: {node: '>=12.0.0'}
htmlparser2@3.10.1: htmlparser2@3.10.1:
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
@ -1306,6 +1324,9 @@ packages:
normalize-wheel-es@1.2.0: normalize-wheel-es@1.2.0:
resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
normalize.css@8.0.1:
resolution: {integrity: sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==}
nprogress@0.2.0: nprogress@0.2.0:
resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==}
@ -2050,6 +2071,8 @@ snapshots:
'@types/web-bluetooth@0.0.16': {} '@types/web-bluetooth@0.0.16': {}
'@types/web-bluetooth@0.0.21': {}
'@vitejs/plugin-vue@4.6.2(vite@5.4.11(@types/node@22.7.5)(sass@1.56.1))(vue@3.5.13)': '@vitejs/plugin-vue@4.6.2(vite@5.4.11(@types/node@22.7.5)(sass@1.56.1))(vue@3.5.13)':
dependencies: dependencies:
vite: 5.4.11(@types/node@22.7.5)(sass@1.56.1) vite: 5.4.11(@types/node@22.7.5)(sass@1.56.1)
@ -2157,6 +2180,23 @@ snapshots:
quill-delta: 4.2.2 quill-delta: 4.2.2
vue: 3.5.13 vue: 3.5.13
'@vueuse/components@12.8.2':
dependencies:
'@vueuse/core': 12.8.2
'@vueuse/shared': 12.8.2
vue: 3.5.13
transitivePeerDependencies:
- typescript
'@vueuse/core@12.8.2':
dependencies:
'@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 12.8.2
'@vueuse/shared': 12.8.2
vue: 3.5.13
transitivePeerDependencies:
- typescript
'@vueuse/core@9.5.0(vue@3.5.13)': '@vueuse/core@9.5.0(vue@3.5.13)':
dependencies: dependencies:
'@types/web-bluetooth': 0.0.16 '@types/web-bluetooth': 0.0.16
@ -2167,8 +2207,16 @@ snapshots:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
'@vueuse/metadata@12.8.2': {}
'@vueuse/metadata@9.5.0': {} '@vueuse/metadata@9.5.0': {}
'@vueuse/shared@12.8.2':
dependencies:
vue: 3.5.13
transitivePeerDependencies:
- typescript
'@vueuse/shared@9.5.0(vue@3.5.13)': '@vueuse/shared@9.5.0(vue@3.5.13)':
dependencies: dependencies:
vue-demi: 0.14.10(vue@3.5.13) vue-demi: 0.14.10(vue@3.5.13)
@ -2802,6 +2850,8 @@ snapshots:
he@1.2.0: {} he@1.2.0: {}
highlight.js@11.11.1: {}
htmlparser2@3.10.1: htmlparser2@3.10.1:
dependencies: dependencies:
domelementtype: 1.3.1 domelementtype: 1.3.1
@ -3094,6 +3144,8 @@ snapshots:
normalize-wheel-es@1.2.0: {} normalize-wheel-es@1.2.0: {}
normalize.css@8.0.1: {}
nprogress@0.2.0: {} nprogress@0.2.0: {}
nth-check@2.1.1: nth-check@2.1.1:
@ -3597,7 +3649,7 @@ snapshots:
universalify@2.0.1: {} universalify@2.0.1: {}
unplugin-auto-import@0.11.4(@vueuse/core@9.5.0(vue@3.5.13))(rollup@4.30.0)(webpack-sources@3.2.3): unplugin-auto-import@0.11.4(@vueuse/core@12.8.2)(rollup@4.30.0)(webpack-sources@3.2.3):
dependencies: dependencies:
'@antfu/utils': 0.6.3 '@antfu/utils': 0.6.3
'@rollup/pluginutils': 5.1.2(rollup@4.30.0) '@rollup/pluginutils': 5.1.2(rollup@4.30.0)
@ -3606,7 +3658,7 @@ snapshots:
unimport: 0.7.1(rollup@4.30.0)(webpack-sources@3.2.3) unimport: 0.7.1(rollup@4.30.0)(webpack-sources@3.2.3)
unplugin: 0.10.2 unplugin: 0.10.2
optionalDependencies: optionalDependencies:
'@vueuse/core': 9.5.0(vue@3.5.13) '@vueuse/core': 12.8.2
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
- webpack-sources - webpack-sources

@ -6,7 +6,7 @@ import ElementPlus from 'element-plus'
// import locale from 'element-plus/lib/locale/lang/zh-cn' // 中文语言 // import locale from 'element-plus/lib/locale/lang/zh-cn' // 中文语言
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import 'normalize.css' // CSS重置样式
import '@/assets/styles/index.scss' // global css import '@/assets/styles/index.scss' // global css
import App from './App' import App from './App'
import { store, useSettingsStore } from './store' import { store, useSettingsStore } from './store'

@ -0,0 +1,34 @@
# Dependencies
node_modules/
# Logs
logs/
*.log
# Environment files
.env*
# IDE files
.vscode/
.idea/
# OS generated files
.DS_Store
Thumbs.db
# Build outputs
dist/
build/
# PM2 logs
.pm2/
# Docker
.dockerignore
# pnpm
pnpm-lock.yaml
# Temporary files
*.tmp
*.temp

@ -0,0 +1,35 @@
# Use the official Node.js image as the base image
FROM node:22.16.0 AS builder
# Set the working directory inside the container
WORKDIR /app
# Copy package.json and package-lock.json (if available) to the working directory
COPY package*.json ./
# Install the application dependencies
RUN npm install
# Copy the rest of the application code to the working directory
COPY . .
# Production stage
FROM node:22.16.0-alpine AS production
# Set the working directory inside the container
WORKDIR /app
# Copy dependencies from builder stage
COPY --from=builder /app/node_modules ./node_modules
# Copy application code from builder stage
COPY --from=builder /app/. .
# Install pm2 globally
RUN npm install -g pm2
# Expose the port that the application listens on
EXPOSE 3000
# Define the command to run the application in production mode with pm2.json config
CMD ["pm2", "start", "pm2.json", "--no-daemon"]

@ -0,0 +1,3 @@
# 内部授权服务器
为内部第三方提供授权restful-api,如:emqx,zlmediakit

@ -0,0 +1,27 @@
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const username = process.env.AUTH_USERNAME || 'admin';
const password = process.env.AUTH_PASSWORD || '3.1415926'
const authUrl = process.env.AUTH_URL || 'http://127.0.0.1:8080'
// Middleware to parse JSON bodies
app.use(express.json());
app.use(express.urlencoded({extended: true}));
// RESTful routes
app.get('/', (req, res) => {
res.json({message: 'Welcome to the auth-inner-server API'});
});
app.post('/emqx-login', (req, res) => {
if (req.body.username === username && req.body.password === password) {
res.json({is_superuser: true, result: 'allow'});
} else {
res.json({is_superuser: false, result: 'deny'});
}
})
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});

@ -0,0 +1,24 @@
{
"name": "auth-inner-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"prod": "pm2 start index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.21.2",
"pm2": "^6.0.8"
},
"devDependencies": {
"nodemon": "^3.1.10"
},
"engines": {
"node": "22.16.0"
}
}

@ -0,0 +1,25 @@
{
"apps": [
{
"name": "auth-inner-server",
"script": "index.js",
"watch": [
"app"
],
"ignore_watch": [
"app/public"
],
"log_date_format": "YYYY-MM-DD HH:mm Z",
"error_file": "./logs/pm2-err.log",
"out_file": "./logs/pm2-out.log",
"merge_logs": true,
"exec_mode": "fork",
"max_memory_restart": "200M",
"autorestart": true,
"env": {
"NODE_ENV": "prd"
},
"instances": 1
}
]
}

@ -370,6 +370,7 @@
<module>ruoyi-common</module> <module>ruoyi-common</module>
<module>ruoyi-demo</module> <module>ruoyi-demo</module>
<module>ruoyi-sms</module> <module>ruoyi-sms</module>
<module>ruoyi-mqtt</module>
</modules> </modules>
<packaging>pom</packaging> <packaging>pom</packaging>
<build> <build>

@ -73,6 +73,15 @@
<artifactId>ruoyi-system-file</artifactId> <artifactId>ruoyi-system-file</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-mqtt</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-cache</artifactId>
</dependency>
<!-- demo模块 --> <!-- demo模块 -->
<dependency> <dependency>

@ -91,7 +91,7 @@ public class SysLoginController {
Map<String,Object> ret = MapUtil.newHashMap(); Map<String,Object> ret = MapUtil.newHashMap();
ret.put("is_superuser",false); ret.put("is_superuser",false);
ret.put("result","deny"); ret.put("result","deny");
if(loginBody.getUsername().equals("energy2") && loginBody.getPassword().equals("energy21415926")){ if(loginBody.getUsername().equals(config.getName()) && loginBody.getPassword().equals(config.getName()+"1415926")){
ret.put("is_superuser",true); ret.put("is_superuser",true);
ret.put("result","allow"); ret.put("result","allow");
return ret; return ret;

@ -3,7 +3,7 @@ package com.ruoyi.common.event;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
/** /**
* energy2 * base2024
*/ */
public class QaEvent extends ApplicationEvent { public class QaEvent extends ApplicationEvent {

@ -0,0 +1,332 @@
在物联网IoT和分布式系统中MQTTMessage Queuing Telemetry Transport是一种轻量级、基于发布/订阅模式的消息传输协议特别适合带宽有限、网络不稳定的场景。Spring Boot作为主流的Java开发框架通过集成MQTT客户端库如Eclipse Paho可以快速实现MQTT通信功能。
### 一、MQTT核心概念
在详解Spring Boot集成MQTT前需先了解几个核心概念
- **Broker**MQTT服务器如Eclipse Mosquitto、EMQX负责接收客户端发送的消息并转发给订阅者。
- **客户端Client**分为发布者Publisher和订阅者Subscriber同一客户端可同时扮演两种角色。
- **主题Topic**:消息的分类标识(如`device/temp`),支持层级结构和通配符(`+`匹配单级、`#`匹配多级)。
- **QoSQuality of Service**消息传输质量等级分3级
- QoS 0最多一次消息可能丢失不确认
- QoS 1至少一次确保消息到达可能重复
- QoS 2刚好一次确保消息唯一到达最可靠但开销大
- **遗嘱消息Last Will and Testament**客户端异常断开时Broker自动向指定主题发送的消息。
### 二、Spring Boot集成MQTT的核心依赖
Spring Boot本身不直接提供MQTT Starter通常通过集成**Eclipse Paho MQTT客户端**实现。需在`pom.xml`中添加依赖:
```xml
<!-- MQTT客户端核心依赖 -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
<!-- 可选Spring Integration MQTT更符合Spring编程模型 -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
<version>5.5.15</version>
</dependency>
```
### 三、基础实现基于Paho客户端的MQTT通信
#### 1. 配置MQTT连接参数
在`application.yml`中配置Broker地址、客户端ID等信息
```yaml
mqtt:
broker-url: tcp://localhost:1883 # MQTT Broker地址TCP协议
client-id: springboot-mqtt-client # 客户端唯一ID避免重复可加随机数
username: admin # 可选Broker认证用户名
password: 123456 # 可选Broker认证密码
keep-alive: 60 # 心跳间隔(秒)
default-topic: device/data # 默认主题
qos: 1 # 默认QoS等级
```
#### 2. 配置MQTT客户端工厂
通过`@Configuration`创建MQTT客户端工厂和连接选项
```java
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MqttConfig {
@Value("${mqtt.broker-url}")
private String brokerUrl;
@Value("${mqtt.client-id}")
private String clientId;
@Value("${mqtt.username}")
private String username;
@Value("${mqtt.password}")
private String password;
@Value("${mqtt.keep-alive}")
private int keepAlive;
// 配置连接选项
@Bean
public MqttConnectOptions mqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{brokerUrl}); // 支持多个Broker地址
options.setUserName(username);
options.setPassword(password.toCharArray());
options.setKeepAliveInterval(keepAlive);
options.setCleanSession(false); // 不清除会话(重连后保留订阅关系)
// 配置遗嘱消息(可选)
options.setWill("device/offline", "客户端断开连接".getBytes(), 1, false);
return options;
}
// 创建MQTT客户端实例异步客户端推荐使用
@Bean
public MqttAsyncClient mqttAsyncClient() throws Exception {
// MemoryPersistence消息临时存储在内存可选FilePersistence持久化到文件
MqttAsyncClient client = new MqttAsyncClient(brokerUrl, clientId, new MemoryPersistence());
// 连接Broker
client.connect(mqttConnectOptions()).waitForCompletion();
return client;
}
}
```
#### 3. 实现消息发送Publisher
创建消息发送服务,封装发送逻辑:
```java
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class MqttPublisher {
@Autowired
private MqttAsyncClient mqttClient;
@Value("${mqtt.default-topic}")
private String defaultTopic;
@Value("${mqtt.qos}")
private int defaultQos;
/**
* 发送消息到默认主题
*/
public void send(String payload) throws Exception {
send(defaultTopic, payload, defaultQos, false);
}
/**
* 发送消息到指定主题
* @param topic 主题
* @param payload 消息内容
* @param qos QoS等级
* @param retained 是否保留消息Broker存储最后一条保留消息新订阅者会立即收到
*/
public void send(String topic, String payload, int qos, boolean retained) throws Exception {
MqttMessage message = new MqttMessage(payload.getBytes());
message.setQos(qos);
message.setRetained(retained);
// 异步发送waitForCompletion()等待发送完成
mqttClient.publish(topic, message).waitForCompletion();
}
}
```
#### 4. 实现消息接收Subscriber
通过`MqttCallback`接口监听消息和连接状态:
```java
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@Service
public class MqttSubscriber {
@Autowired
private MqttAsyncClient mqttClient;
@Value("${mqtt.default-topic}")
private String defaultTopic;
@Value("${mqtt.qos}")
private int defaultQos;
// 初始化时订阅主题
@PostConstruct
public void subscribe() throws Exception {
// 订阅默认主题指定QoS和消息监听器
mqttClient.subscribe(defaultTopic, defaultQos, new IMqttMessageListener() {
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
String payload = new String(message.getPayload());
System.out.println("收到消息:主题=" + topic + ",内容=" + payload);
// 处理消息逻辑(如存入数据库、触发业务操作等)
}
}).waitForCompletion();
}
}
```
### 四、高级用法基于Spring Integration MQTT
Spring Integration提供了更符合Spring风格的MQTT集成方式通过消息通道MessageChannel和注解简化开发。
#### 1. 配置Integration MQTT
```java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@Configuration
public class MqttIntegrationConfig {
@Value("${mqtt.broker-url}")
private String brokerUrl;
@Value("${mqtt.client-id}")
private String clientId;
@Value("${mqtt.username}")
private String username;
@Value("${mqtt.password}")
private String password;
// 客户端工厂
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{brokerUrl});
options.setUserName(username);
options.setPassword(password.toCharArray());
factory.setConnectionOptions(options);
return factory;
}
// 接收消息的通道
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
// 入站适配器(接收消息)
@Bean
public MessageProducer inbound() {
// 订阅主题device/data客户端ID加后缀避免与发送端冲突
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
clientId + "-inbound", mqttClientFactory(), "device/data");
adapter.setConverter(new DefaultPahoMessageConverter()); // 消息转换器
adapter.setQos(1); // QoS等级
adapter.setOutputChannel(mqttInputChannel()); // 绑定到接收通道
return adapter;
}
// 处理接收的消息(使用@ServiceActivator
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler handler() {
return message -> {
String topic = (String) message.getHeaders().get("mqtt_receivedTopic");
String payload = message.getPayload().toString();
System.out.println("Integration接收消息主题=" + topic + ",内容=" + payload);
};
}
// 发送消息的通道
@Bean
public MessageChannel mqttOutputChannel() {
return new DirectChannel();
}
// 出站适配器(发送消息)
@Bean
@ServiceActivator(inputChannel = "mqttOutputChannel")
public MessageHandler outbound() {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler(
clientId + "-outbound", mqttClientFactory());
handler.setAsync(true); // 异步发送
handler.setDefaultTopic("device/control"); // 默认发送主题
handler.setDefaultQos(1); // 默认QoS
return handler;
}
}
```
#### 2. 使用Integration发送消息
通过`MessageChannel`发送消息:
```java
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class IntegrationMqttPublisher {
// 注入发送通道
@Resource(name = "mqttOutputChannel")
private MessageChannel mqttOutputChannel;
public void send(String payload) {
// 发送到默认主题
mqttOutputChannel.send(MessageBuilder.withPayload(payload).build());
}
public void sendToTopic(String topic, String payload) {
// 发送到指定主题通过header指定
mqttOutputChannel.send(MessageBuilder.withPayload(payload)
.setHeader("mqtt_topic", topic)
.build());
}
}
```
### 五、注意事项
1. **客户端ID唯一性**同一Broker下客户端ID不可重复否则会导致连接被强制断开可通过`clientId + 随机数`避免)。
2. **QoS选择**根据业务可靠性要求选择QoS物联网场景常用QoS 1平衡可靠性和性能
3. **断线重连**Paho客户端默认支持重连可通过`MqttConnectOptions.setAutomaticReconnect(true)`增强重连逻辑。
4. **消息持久化**:若需避免消息丢失,可使用`FilePersistence`替代`MemoryPersistence`,并设置`cleanSession=false`。
5. **主题设计**:合理规划主题层级(如`device/{设备ID}/temp`),便于管理和订阅。
通过以上方式Spring Boot可快速集成MQTT实现消息的发布与订阅适用于物联网设备通信、分布式系统通知等场景。根据业务复杂度可选择基础Paho客户端或Spring Integration简化开发。

@ -0,0 +1,35 @@
# 网络设置
networks:
base2024-network:
name: base2024-network
driver: bridge
ipam:
config:
- subnet: 192.168.222.0/24
gateway: 192.168.222.1
services:
# mqtt服务
mqtt:
container_name: mqtt
restart: always
image: registry.cn-hangzhou.aliyuncs.com/awl/emqx:5.6.1
ports:
- "1883:1883"
- "8083:8083"
# - "8084:8084"
# - "8883:8883"
- "18083:18083"
volumes:
- ./mqtt/configs/:/opt/emqx/data/configs/:rw
- ./mqtt/log/:/opt/emqx/log/:rw
environment:
TZ: Asia/Shanghai
privileged: true
networks:
- base2024-network

@ -0,0 +1,51 @@
authentication = [
{
backend = http
body {
password = "${password}"
username = "${username}"
}
connect_timeout = 15s
enable_pipelining = 100
headers {content-type = "application/json"}
mechanism = password_based
method = post
pool_size = 8
request_timeout = 5s
ssl {enable = false, verify = verify_peer}
url = "http://server1:8080/emqx-login"
}
]
mqtt {
await_rel_timeout = 300s
exclusive_subscription = false
idle_timeout = 15s
ignore_loop_deliver = false
keepalive_multiplier = 1.5
max_awaiting_rel = 100
max_clientid_len = 65535
max_inflight = 32
max_mqueue_len = 1000
max_packet_size = 256MB
max_qos_allowed = 2
max_subscriptions = infinity
max_topic_alias = 65535
max_topic_levels = 128
message_expiry_interval = infinity
mqueue_default_priority = lowest
mqueue_priorities = disabled
mqueue_store_qos0 = true
peer_cert_as_clientid = disabled
peer_cert_as_username = disabled
response_information = ""
retain_available = true
retry_interval = 30s
server_keepalive = disabled
session_expiry_interval = 2h
shared_subscription = true
shared_subscription_strategy = round_robin
strict_mode = false
upgrade_qos = false
use_username_as_clientid = false
wildcard_subscription = true
}

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ruoyi-vue-plus</artifactId>
<groupId>com.ruoyi</groupId>
<version>4.6.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-mqtt</artifactId>
<description>
mqtt模块
</description>
<dependencies>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-cache</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,17 @@
package com.ruoyi.mqtt;
import com.ruoyi.mqtt.config.MqttConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* ruoyi使
*/
@Import(MqttConfig.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MqttEnabled {
}

@ -0,0 +1,57 @@
package com.ruoyi.mqtt;
import com.ruoyi.mqtt.event.*;
/**
*
*/
public interface MqttEventHandler {
/**
*
*
* @param event
* @param item MQTT
* @return
*/
default boolean next(MqttEvent event, MqttItem item) {
if (event instanceof MqttConnectionExceptionEvent) {
return next((MqttConnectionExceptionEvent) event, item);
} else if (event instanceof MqttConnectionLostEvent) {
return next((MqttConnectionLostEvent) event, item);
} else if (event instanceof MqttConnectionSuccessEvent) {
return next((MqttConnectionSuccessEvent) event, item);
} else if (event instanceof MqttMessageDeliveryEvent) {
return next((MqttMessageDeliveryEvent) event, item);
} else if (event instanceof MqttMessageEvent) {
return next((MqttMessageEvent) event, item);
} else if (event instanceof MqttReconnectionEvent) {
return next((MqttReconnectionEvent) event, item);
}
return false;
}
default boolean next(MqttConnectionExceptionEvent event, MqttItem item) {
return true;
}
default boolean next(MqttConnectionLostEvent event, MqttItem item) {
return true;
}
default boolean next(MqttConnectionSuccessEvent event, MqttItem item) {
return true;
}
default boolean next(MqttMessageDeliveryEvent event, MqttItem item) {
return true;
}
default boolean next(MqttMessageEvent event, MqttItem item) {
return true;
}
default boolean next(MqttReconnectionEvent event, MqttItem item) {
return true;
}
}

@ -0,0 +1,111 @@
package com.ruoyi.mqtt;
import static com.ruoyi.mqtt.config.MqttProperties.DEFAULT;
import com.ruoyi.mqtt.config.MqttProperties.Config;
import com.ruoyi.mqtt.event.MqttEvent;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import java.util.function.Consumer;
/**
* MQTT
* <p>
* MQTT
* </p>
*/
public interface MqttFactory {
/**
* MQTT
*
* @param configName
* @return MQTT
*/
MqttItem get(String configName);
/**
* MQTT
*
* @return MQTT
*/
default MqttItem get() {
return get(DEFAULT);
}
/**
* MQTT使
*
* @param configName
* @param config MQTT
* @param handlers
*/
void put(String configName, Config config, MqttEventHandler... handlers);
/**
* MQTT
*
* @param configName
* @return
*/
boolean contains(String configName);
/**
* MQTT
*
* @param configName
*/
void remove(String configName);
/**
* MQTT
*
* @param configName
* @param topic
* @param payload ()
* @param qos QoS 0 QoS 1 QoS 2
* @param retained
* @throws MqttException MQTT
*/
IMqttDeliveryToken send(String configName, String topic, byte[] payload, int qos, boolean retained) throws MqttException;
/**
* MQTT
*
* @param configName
* @param sendName
* @param payload ()
* @param params {0},{1}...
* @throws MqttException MQTT
*/
IMqttDeliveryToken send(String configName, String sendName, byte[] payload, String... params) throws MqttException;
/**
* MQTT
*
* @param sendName
* @param payload ()
* @param params {0},{1}...
* @throws MqttException MQTT
*/
default IMqttDeliveryToken send(String sendName, byte[] payload, String... params) throws MqttException {
return send(DEFAULT, sendName, payload, params);
}
/**
* MQTT
*
* @param topic
* @param payload ()
* @param qos QoS 0 QoS 1 QoS 2
* @param retained
* @throws MqttException MQTT
*/
default IMqttDeliveryToken send(String topic, byte[] payload, int qos, boolean retained) throws MqttException {
return send(DEFAULT, topic, payload, qos, retained);
}
}

@ -0,0 +1,237 @@
package com.ruoyi.mqtt;
import com.ruoyi.mqtt.config.MqttProperties;
import com.ruoyi.mqtt.config.MqttProperties.Config;
import com.ruoyi.mqtt.event.*;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* MQTT
* <p>
* MQTT
*
* </p>
*/
@Slf4j
public class MqttItem {
private final static int CHECK_GAP = 10;
/**
* MQTT
*/
@Getter
private final Config config;
/**
* ID
*/
@Getter
private final String clientId;
/**
*
*/
@Getter
private final String configName;
@Getter
private final List<MqttEventHandler> messageHandlers = new CopyOnWriteArrayList<>();
private final Consumer<MqttEvent> consumer = (e) -> {
for (MqttEventHandler messageHandler : messageHandlers) {
if (!messageHandler.next(e, MqttItem.this)) {
return;
}
}
};
private IMqttAsyncClient client;
private final ScheduledExecutorService executorService;
private ScheduledFuture<?> future;
private final AtomicBoolean connecting = new AtomicBoolean(false);
/**
*
*
* @param configName
* @param config MQTT
* @param executorService
*/
public MqttItem(String configName, Config config, ScheduledExecutorService executorService) {
this.configName = configName;
this.config = config;
this.executorService = executorService;
this.clientId = config.getClientId() + "_" + Long.toString(System.currentTimeMillis() - 1735660800000L + new Random().nextInt(1024), 36);
connect();
schedule();
}
/**
* MQTT
*
* @return MQTT
* @throws RuntimeException
* @throws NullPointerException
*/
public IMqttAsyncClient getClient() {
if (connecting.get()) {
throw new RuntimeException("mqtt客户端连接中:" + configName);
}
if (client == null) {
throw new NullPointerException("mqtt客户端未创建:" + configName);
}
if (!client.isConnected()) {
throw new RuntimeException("mqtt客户端未连接:" + configName);
}
return client;
}
/**
*
*/
private void schedule() {
future = executorService.scheduleWithFixedDelay(() -> {
if (client != null && client.isConnected()) {
return;
}
consumer.accept(new MqttReconnectionEvent(configName));
connect();
}, CHECK_GAP, CHECK_GAP, TimeUnit.SECONDS);
}
/**
* MQTT
*/
public void connect() {
if (connecting.get()) {
return;
}
connecting.set(true);
if (client != null) {
try {
client.close();
} catch (Exception ignored) {
}
}
client = null;
try {
MqttConnectOptions options = mqttConnectOptions(config);
client = new MqttAsyncClient(config.getUrl(), clientId, new MemoryPersistence());
client.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
consumer.accept(new MqttConnectionLostEvent(configName, cause));
connecting.set(false);
connect();
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
if(log.isDebugEnabled()){
log.debug("mqtt message: {}={}",topic,new String(message.getPayload(), StandardCharsets.UTF_8));
}
consumer.accept(new MqttMessageEvent(configName, topic, message));
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
consumer.accept(new MqttMessageDeliveryEvent(configName, token));
}
});
client.connect(options).waitForCompletion(5000);
if (config.getSubscribes() != null && !config.getSubscribes().isEmpty()) {
client.subscribe(
config.getSubscribes().stream().map(MqttProperties.Topic::getTopic).toArray(String[]::new),
config.getSubscribes().stream().mapToInt(MqttProperties.Topic::getQos).toArray()
);
}
consumer.accept(new MqttConnectionSuccessEvent(configName));
} catch (Exception e) {
consumer.accept(new MqttConnectionExceptionEvent(configName, e));
} finally {
connecting.set(false);
}
}
/**
* MQTT
*/
public void destroy() {
if (future != null) {
future.cancel(true);
future = null;
}
if (client != null) {
try {
client.close();
} catch (Exception ignored) {
}
client = null;
}
}
/**
* MQTT
*
* @param properties MQTT
* @return MQTT
*/
public MqttConnectOptions mqttConnectOptions(MqttProperties.Config properties) {
// 连接设置
MqttConnectOptions options = new MqttConnectOptions();
// 是否清空session设置false表示服务器会保留客户端的连接记录订阅主题qos,客户端重连之后能获取到服务器在客户端断开连接期间推送的消息
// 设置为true表示每次连接服务器都是以新的身份
options.setCleanSession(properties.getCleanSession());
// options.setCleanSession(true);
// 设置连接用户名
options.setUserName(properties.getUsername());
// 设置连接密码
options.setPassword(properties.getPassword().toCharArray());
// 设置超时时间,单位为秒
options.setConnectionTimeout(properties.getConnectionTimeout());
// 设置心跳时间 单位为秒,表示服务器每隔 1.5*20秒的时间向客户端发送心跳判断客户端是否在线
options.setKeepAliveInterval(properties.getKeepAliveInterval());
// 设置遗嘱消息的话题,若客户端和服务器之间的连接意外断开,服务器将发布客户端的遗嘱信息
options.setWill(properties.getWillTopic(), properties.getWillMessage().getBytes(), properties.getWillQos(), false);
// 设置重连
// options.setAutomaticReconnect(properties.getAutomaticReconnect());
options.setAutomaticReconnect(false);
// options.setMaxReconnectDelay(properties.getMaxReconnectDelay());
options.setCustomWebSocketHeaders(properties.getCustomWebSocketHeaders());
options.setSSLProperties(properties.getSslProperties());
options.setMqttVersion(properties.getMqttVersion().getVersion());
options.setMaxInflight(properties.getMaxInflight());
options.setServerURIs(properties.getUrls().toArray(new String[0]));
return options;
}
}

@ -0,0 +1,178 @@
package com.ruoyi.mqtt;
import com.ruoyi.mqtt.config.MqttConfig;
import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import java.nio.charset.StandardCharsets;
/**
* MQTT便MQTT
* <p>
* {@link MqttFactory}MQTT
* (QoS)
* </p>
*/
public class MqttUtil {
private MqttUtil() {
}
/**
* MQTT
*
* @return MQTT
*/
public static MqttFactory getFactory() {
return MqttConfig.getMqttFactory();
}
/**
* MQTT
*
* @return MQTT
*/
public static MqttItem getItem() {
return getFactory().get();
}
/**
* MQTT
*
* @param configName
* @return MQTT
*/
public static MqttItem getItem(String configName) {
return getFactory().get(configName);
}
/**
* MQTT
*
* @return MQTT
*/
public static IMqttAsyncClient getClient() {
return getItem().getClient();
}
/**
* MQTT
*
* @param configName
* @return MQTT
*/
public static IMqttAsyncClient getClient(String configName) {
return getItem(configName).getClient();
}
/**
* MQTT
*
* @param configName
* @param sendName
* @param payload ()
* @param params ,{0},{1}...
* @throws MqttException MQTT
*/
public static IMqttDeliveryToken send(String configName, String sendName, byte[] payload, String... params)throws MqttException {
return getFactory().send(configName, sendName, payload, params);
}
/**
* MQTT
*
* @param configName
* @param sendName
* @param payload ()
* @param params ,{0},{1}...
* @throws MqttException MQTT
*/
public static IMqttDeliveryToken send(String configName, String sendName, String payload, String... params)throws MqttException {
return getFactory().send(configName, sendName, payload.getBytes(StandardCharsets.UTF_8), params);
}
/**
* MQTT(使)
*
* @param sendName
* @param payload ()
* @param params ,{0},{1}...
* @throws MqttException MQTT
*/
public static IMqttDeliveryToken send(String sendName, byte[] payload, String... params)throws MqttException {
return getFactory().send(sendName, payload, params);
}
/**
* MQTT(使)
*
* @param sendName
* @param payload ()
* @param params ,{0},{1}...
* @throws MqttException MQTT
*/
public static IMqttDeliveryToken send(String sendName, String payload, String... params)throws MqttException {
return getFactory().send(sendName, payload.getBytes(StandardCharsets.UTF_8), params);
}
/**
* MQTT
*
* @param configName
* @param topic
* @param payload ()
* @param qos QoS 0 QoS 1 QoS 2
* @param retained
* @throws MqttException MQTT
*/
public static IMqttDeliveryToken send(String configName, String topic, byte[] payload, int qos, boolean retained) throws MqttException{
return getFactory().send(configName, topic, payload, qos, retained);
}
/**
* MQTT
*
* @param configName
* @param topic
* @param payload ()
* @param qos QoS 0 QoS 1 QoS 2
* @param retained
* @throws MqttException MQTT
*/
public static IMqttDeliveryToken send(String configName, String topic, String payload, int qos, boolean retained) throws MqttException{
return getFactory().send(configName, topic, payload.getBytes(StandardCharsets.UTF_8), qos, retained);
}
/**
* MQTT(使)
*
* @param topic
* @param payload ()
* @param qos QoS 0 QoS 1 QoS 2
* @param retained
* @throws MqttException MQTT
*/
public static IMqttDeliveryToken send(String topic, byte[] payload, int qos, boolean retained) throws MqttException{
return getFactory().send(topic, payload, qos, retained);
}
/**
* MQTT(使)
*
* @param topic
* @param payload ()
* @param qos QoS 0 QoS 1 QoS 2
* @param retained
* @throws MqttException MQTT
*/
public static IMqttDeliveryToken send(String topic, String payload, int qos, boolean retained) throws MqttException{
return getFactory().send(topic, payload.getBytes(StandardCharsets.UTF_8), qos, retained);
}
}

@ -0,0 +1,37 @@
package com.ruoyi.mqtt;
/**
* MQTT
* <p>
* MQTT3.13.1.1
* </p>
*/
public enum MqttVersion {
/** 默认MQTT版本 */
MQTT_VERSION_DEFAULT(0),
/** MQTT 3.1版本 */
MQTT_VERSION_3_1(3),
/** MQTT 3.1.1版本 */
MQTT_VERSION_3_1_1(4);
private int version;
/**
*
*
* @param version
*/
private MqttVersion(int version) {
this.version = version;
}
/**
*
*
* @return
*/
public int getVersion() {
return version;
}
}

@ -0,0 +1,181 @@
package com.ruoyi.mqtt.config;
import com.ruoyi.mqtt.MqttEventHandler;
import com.ruoyi.mqtt.MqttFactory;
import com.ruoyi.mqtt.MqttItem;
import com.ruoyi.mqtt.MqttUtil;
import com.ruoyi.mqtt.event.MqttEvent;
import com.ruoyi.mqtt.event.MqttSendEvent;
import com.ruoyi.mqtt.event.MqttSendTopicEvent;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@Configuration
@EnableConfigurationProperties(MqttProperties.class)
@Slf4j
@RequiredArgsConstructor
public class MqttConfig implements MqttFactory, MqttEventHandler, PriorityOrdered {
private final MqttProperties properties;
private final ApplicationContext act;
@Getter
private static MqttFactory mqttFactory;
private ScheduledExecutorService executorService;
private final Map<String, MqttItem> clients = new ConcurrentHashMap<>();
@PostConstruct
public void init() throws Exception {
if (!properties.getEnabled()) {
log.info("mqtt模块未激活");
return;
}
log.info("mqtt模块启动中...");
try {
executorService = act.getBeansOfType(ScheduledExecutorService.class).values().stream().findFirst().get();
} catch (Exception e) {
executorService = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors());
}
properties.getConfigs().forEach((a, b) -> {
if (!b.getEnabled()) {
return;
}
put(a, b, this);
});
mqttFactory = this;
}
@Override
public boolean next(MqttEvent event, MqttItem item) {
log.debug("mqtt event: {} = {}", event.getConfigName(), event.getClass().getName());
act.publishEvent(event);
return true;
}
@PreDestroy
public void destroy() {
clients.forEach((a, b) -> {
try {
b.destroy();
} catch (Exception e) {
log.error("mqtt客户端关闭失败:" + a, e);
}
});
try {
executorService.shutdown();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public MqttItem get(String configName) {
return clients.get(configName);
}
public void put(String configName, MqttProperties.Config config, MqttEventHandler... handlers) {
if (clients.containsKey(configName)) {
throw new RuntimeException("配置项已经存在");
}
MqttItem mqttItem = new MqttItem(configName, config, executorService);
if (handlers != null && handlers.length > 0) {
Collections.addAll(mqttItem.getMessageHandlers(), handlers);
}
clients.put(configName, mqttItem);
log.debug("mqtt配置项添加:" + configName);
}
public boolean contains(String configName) {
return clients.containsKey(configName);
}
public void remove(String configName) {
if (contains(configName)) {
get(configName).destroy();
clients.remove(configName);
}
}
public IMqttDeliveryToken send(String configName, String topic, byte[] payload, int qos, boolean retained) throws MqttException {
MqttItem item = get(configName);
if (item == null) {
throw new RuntimeException("mqtt客户端未找到:" + configName);
}
log.debug("mqtt发送成功:configName={},topic={}", configName, topic);
return item.getClient().publish(topic, payload, qos, retained);
}
public IMqttDeliveryToken send(String configName, String sendName, byte[] payload, String... params) throws MqttException {
MqttItem item = get(configName);
if (item == null) {
throw new RuntimeException("mqtt客户端未找到:" + configName);
}
MqttProperties.Topic topic = item.getConfig().getSends().get(sendName);
if (topic == null) {
throw new RuntimeException("mqtt配置的主题未找到:" + configName + " = " + sendName);
}
String topicTempalte = topic.getTopic();
if (params != null) {
for (int i = 0; i < params.length; i++) {
topicTempalte = topicTempalte.replace("{" + i + "}", params[i]);
}
}
log.debug("mqtt发送成功:configName={},sendName={},topic={}", configName, sendName, topicTempalte);
return item.getClient().publish(topicTempalte, payload, topic.getQos(), topic.getRetained());
}
@EventListener
public void listener(MqttSendEvent event) throws MqttException {
send(event.getConfigName(), event.getSendName(), event.getPayload(), event.getParams());
}
@EventListener
public void listener(MqttSendTopicEvent event) throws MqttException {
send(event.getConfigName(), event.getTopic(), event.getPayload(), event.getQos(), event.isRetained());
}
// @Scheduled(cron = "*/5 * * * * ?")
// public void test() {
// String s = Long.toString(System.currentTimeMillis(), 36);
// try {
//// act.publishEvent(new MqttSendEvent("test",s.getBytes(StandardCharsets.UTF_8),s));
// MqttUtil.send("test", s, new String[]{s});
// log.info("test success:{}", s);
// } catch (Exception e) {
// log.info("test error:" + s, e);
// }
// }
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
}
}

@ -0,0 +1,194 @@
package com.ruoyi.mqtt.config;
import com.ruoyi.mqtt.MqttVersion;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.*;
/**
* MQTT
* <p>
* MQTTMQTT
* </p>
*/
@Data
@ConfigurationProperties(value = MqttProperties.PREFIX, ignoreInvalidFields = true, ignoreUnknownFields = true)
public class MqttProperties {
public final static String DEFAULT = "default";
/**
*
*/
public static final String PREFIX = "spring.mqtt";
/**
* MQTT
* : false
*/
private Boolean enabled = false;
/**
*
* : "default"
*/
private String defaultConfig = DEFAULT;
/**
* MQTTkeyvalue
*/
private Map<String, Config> configs = new HashMap<>();
/**
* MQTT
* <p>
* MQTT(QoS)
* </p>
*/
@Data
public static class Topic {
/**
* 使 "+" "#"
* 使:{0},{1},{2},使params
*/
private String topic;
/**
* (QoS)
* : 0
*/
private Integer qos = 0;
/**
* ,
* : false
*/
private Boolean retained = false;
}
/**
* MQTT
* <p>
* MQTT
* </p>
*/
@Data
public static class Config {
/**
* MQTT
* : true
*/
private Boolean enabled = true;
/**
* ()
* : 36
*/
private String clientId = Long.toString(System.currentTimeMillis(), 36);
/**
* URL
* : tcp://127.0.0.1:1883
*/
private String url = "tcp://127.0.0.1:1883";
/**
* URL()
*/
private List<String> urls = new ArrayList<>();
/**
* sessionfalseqos,
* true
* : true
*/
private Boolean cleanSession = true;
/**
*
* : admin
*/
private String username = "admin";
/**
*
* : 123456
*/
private String password = "123456";
/**
* ,
* : 5
*/
private Integer connectionTimeout = 5;
/**
* 60线
* : 60
*/
private Integer keepAliveInterval = 60;
/**
*
* : will/topic
*/
private String willTopic = "will/topic";
/**
*
* : offline
*/
private String willMessage = "offline";
/**
* (QoS)
* : 0
*/
private Integer willQos = 0;
/**
*
*/
private List<Topic> subscribes = new ArrayList<>();
/**
*
* : true ()
*/
// private Boolean automaticReconnect = true;
// private Integer maxReconnectDelay = Integer.MAX_VALUE;
/**
* WebSocket
*/
private Properties customWebSocketHeaders = new Properties();
/**
* SSL
*/
private Properties sslProperties = new Properties();
/**
* keyvalue
*/
private Map<String, Topic> sends = new HashMap<>();
/**
* MQTT
* : MQTT_VERSION_DEFAULT
*/
private MqttVersion mqttVersion = MqttVersion.MQTT_VERSION_DEFAULT;
/**
*
* : 10
*/
private Integer maxInflight = 10;
}
}

@ -0,0 +1,21 @@
package com.ruoyi.mqtt.event;
import lombok.Getter;
/**
* MQTT
*/
public final class MqttConnectionExceptionEvent extends MqttEvent{
@Getter
private Throwable cause;
public MqttConnectionExceptionEvent(Throwable cause) {
this.cause = cause;
}
public MqttConnectionExceptionEvent(String configName, Throwable cause) {
super(configName);
this.cause = cause;
}
}

@ -0,0 +1,21 @@
package com.ruoyi.mqtt.event;
import lombok.Getter;
/**
* MQTT
*/
public final class MqttConnectionLostEvent extends MqttEvent{
@Getter
private Throwable cause;
public MqttConnectionLostEvent(Throwable cause) {
this.cause = cause;
}
public MqttConnectionLostEvent(String configName,Throwable cause) {
super(configName);
this.cause = cause;
}
}

@ -0,0 +1,14 @@
package com.ruoyi.mqtt.event;
/**
* MQTT
*/
public final class MqttConnectionSuccessEvent extends MqttEvent{
public MqttConnectionSuccessEvent() {
}
public MqttConnectionSuccessEvent(String configName) {
super(configName);
}
}

@ -0,0 +1,26 @@
package com.ruoyi.mqtt.event;
import com.ruoyi.mqtt.config.MqttProperties;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
/**
* Mqtt
*/
public abstract class MqttEvent extends ApplicationEvent {
@Getter
private String configName = MqttProperties.DEFAULT;
public MqttEvent() {
this(MqttProperties.DEFAULT);
}
public MqttEvent(String configName) {
super(configName);
}
@Override
public String getSource() {
return (String)super.getSource();
}
}

@ -0,0 +1,24 @@
package com.ruoyi.mqtt.event;
import lombok.Getter;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttMessage;
/**
*
*/
public final class MqttMessageDeliveryEvent extends MqttEvent {
@Getter
private IMqttDeliveryToken token;
public MqttMessageDeliveryEvent(IMqttDeliveryToken token) {
this.token = token;
}
public MqttMessageDeliveryEvent(String configName, IMqttDeliveryToken token) {
super(configName);
this.token = token;
}
}

@ -0,0 +1,27 @@
package com.ruoyi.mqtt.event;
import lombok.Getter;
import org.eclipse.paho.client.mqttv3.MqttMessage;
/**
*
*/
public final class MqttMessageEvent extends MqttEvent {
@Getter
private String topic;
@Getter
private MqttMessage message;
public MqttMessageEvent(String topic, MqttMessage message) {
this.topic = topic;
this.message = message;
}
public MqttMessageEvent(String configName, String topic, MqttMessage message) {
super(configName);
this.topic = topic;
this.message = message;
}
}

@ -0,0 +1,15 @@
package com.ruoyi.mqtt.event;
/**
* MQTT
*/
public final class MqttReconnectionEvent extends MqttEvent{
public MqttReconnectionEvent() {
}
public MqttReconnectionEvent(String configName) {
super(configName);
}
}

@ -0,0 +1,53 @@
package com.ruoyi.mqtt.event;
import com.ruoyi.mqtt.config.MqttProperties;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import java.util.List;
/**
*
*/
public class MqttSendEvent extends ApplicationEvent {
@Getter
private String configName;
@Getter
private String sendName;
@Getter
private byte[] payload;
@Getter
private String[] params;
public MqttSendEvent(String configName, String sendName, byte[] payload, String... params) {
super(configName);
this.configName = configName;
this.sendName = sendName;
this.payload = payload;
this.params = params;
}
public MqttSendEvent(String configName, String sendName, byte[] payload, List<String> params) {
this(configName, sendName, payload, params.toArray(new String[0]));
}
public MqttSendEvent(String sendName, byte[] payload, List<String> params) {
this(MqttProperties.DEFAULT, sendName, payload, params.toArray(new String[0]));
}
public MqttSendEvent(String sendName, byte[] payload, String... params) {
this(MqttProperties.DEFAULT, sendName, payload, params);
}
@Override
public String getSource() {
return (String) super.getSource();
}
}

@ -0,0 +1,49 @@
package com.ruoyi.mqtt.event;
import com.ruoyi.mqtt.config.MqttProperties;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
import java.util.List;
/**
*
*/
public class MqttSendTopicEvent extends ApplicationEvent {
@Getter
private String configName;
@Getter
private String topic;
@Getter
private byte[] payload;
@Getter
private int qos;
@Getter
private boolean retained;
public MqttSendTopicEvent(String configName, String topic, byte[] payload, int qos, boolean retained) {
super(configName);
this.configName = configName;
this.topic = topic;
this.payload = payload;
this.qos = qos;
this.retained = retained;
}
public MqttSendTopicEvent(String topic, byte[] payload, int qos, boolean retained) {
this(MqttProperties.DEFAULT, topic, payload, qos, retained);
}
@Override
public String getSource() {
return (String) super.getSource();
}
}

@ -0,0 +1,39 @@
package com.ruoyi.mqtt.rmi;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* MQTT RMI
*
* MQTT RMI
*/
public interface Const {
String REQUEST_TOPICE = "/request";
String RESPONSE_TOPICE = "/response";
/**
*
*
* @param e
* @return enull"null"
*/
public static String getStackTraceAsString(Throwable e) {
// 处理null情况
if (e == null) {
return null;
}
// 使用StringWriter和PrintWriter来捕获栈跟踪信息
try (
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
) {
e.printStackTrace(pw);
return sw.toString();
} catch (IOException ex) {
return null;
}
}
}

@ -0,0 +1,26 @@
package com.ruoyi.mqtt.rmi;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
/**
* MQTT RMI
*
* MQTT RMI
*/
public class MqttRmiRequest {
private String requestId;
private String name;
private String method;
private List<JsonNode> args;
}

@ -0,0 +1,10 @@
package com.ruoyi.mqtt.rmi;
import com.fasterxml.jackson.databind.ObjectMapper;
public interface MqttRmiRequestSender {
ObjectMapper getMapper();
MqttRmiResponse request(MqttRmiRequest request, long timeout);
}

@ -0,0 +1,29 @@
package com.ruoyi.mqtt.rmi;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
/**
* MQTT RMI
*
* MQTT RMI
*/
public class MqttRmiResponse {
private String requestId;
private String name;
private String method;
private Boolean ok;
private JsonNode body;
private String error;
}

@ -0,0 +1,23 @@
package com.ruoyi.mqtt.rmi;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.mqtt.rmi.impl.MqttRmiConsumerImpl;
public class MqttRmiUtil {
private MqttRmiUtil() {
}
public static MqttRmiResponse request(MqttRmiRequest request, long timeout) {
return MqttRmiConsumerImpl.getSender().request(request, timeout);
}
public static MqttRmiResponse request(MqttRmiRequest request) {
return MqttRmiConsumerImpl.getSender().request(request, 0);
}
public static ObjectMapper getMapper() {
return MqttRmiConsumerImpl.getSender().getMapper();
}
}

@ -0,0 +1,17 @@
package com.ruoyi.mqtt.rmi.annotation;
import com.ruoyi.mqtt.rmi.config.MqttRmiConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* ruoyi使
*/
@Import(MqttRmiConfig.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MqttRmiEnabled {
}

@ -0,0 +1,32 @@
package com.ruoyi.mqtt.rmi.annotation;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Component
/**
* MQTT RMI
*
* MQTT RMI
*/
public @interface MqttRmiMethod {
/**
*
*
* @return
*/
String value() default "";
/**
* 0 使
* @return
*/
long timeout() default 0;
}

@ -0,0 +1,23 @@
package com.ruoyi.mqtt.rmi.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
/**
* MQTT RMI
*
* MQTT RMI
*/
public @interface MqttRmiProvider {
/**
*
* @return
*/
String value() default "";
}

@ -0,0 +1,16 @@
package com.ruoyi.mqtt.rmi.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/**
* MQTT RMI
*
* 便 MQTT RMI
*/
public @interface MqttRmiScan {
String[] value() default {};
}

@ -0,0 +1,23 @@
package com.ruoyi.mqtt.rmi.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
/**
* MQTT RMI
*
* MQTT RMI
*/
public @interface MqttRmiService {
/**
*
* @return
*/
String value() default "";
}

@ -0,0 +1,57 @@
package com.ruoyi.mqtt.rmi.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.mqtt.MqttFactory;
import com.ruoyi.mqtt.rmi.impl.MqttRmiConsumerBeanDefinitionRegistryPostProcessor;
import com.ruoyi.mqtt.rmi.impl.MqttRmiConsumerImpl;
import com.ruoyi.mqtt.rmi.impl.MqttRmiProviderImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
@Configuration
@EnableConfigurationProperties(MqttRmiProperties.class)
/**
* MQTT RMI
*
* MQTT RMI Bean
*/
@Slf4j
@RequiredArgsConstructor
public class MqttRmiConfig implements PriorityOrdered {
MqttRmiProperties properties;
@Bean
// @ConditionalOnProperty(prefix = MqttRmiProperties.PREFIX,name = "provider",havingValue = "true",matchIfMissing = false)
public MqttRmiProviderImpl mqttRmiProviderImpl(MqttRmiProperties properties, MqttFactory factory, ObjectMapper mapper) {
return new MqttRmiProviderImpl(properties, factory, mapper);
}
@Bean
// @ConditionalOnProperty(prefix = MqttRmiProperties.PREFIX,name = "consumer",havingValue = "true",matchIfMissing = false)
public MqttRmiConsumerImpl mqttRmiConsumer(MqttRmiProperties properties, MqttFactory factory, ObjectMapper mapper, ApplicationContext act) {
return new MqttRmiConsumerImpl(properties, factory, mapper, act);
}
@Bean
@ConditionalOnProperty(prefix = MqttRmiProperties.PREFIX,name = "consumer",havingValue = "true",matchIfMissing = false)
public MqttRmiConsumerBeanDefinitionRegistryPostProcessor mqttRmiConsumerBeanDefinitionRegistryPostProcessor() {
return new MqttRmiConsumerBeanDefinitionRegistryPostProcessor();
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
}
}

@ -0,0 +1,74 @@
package com.ruoyi.mqtt.rmi.config;
import com.ruoyi.mqtt.config.MqttProperties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* MQTT
* <p>
* MQTTMQTT
* </p>
*/
@Data
@ConfigurationProperties(value = MqttRmiProperties.PREFIX, ignoreInvalidFields = true, ignoreUnknownFields = true)
/**
* MQTT RMI
*
* MQTT RMI
*/
public class MqttRmiProperties {
/**
*
*/
public static final String PREFIX = "spring.mqtt.rmi";
/**
* MQTT
* : false
*/
private Boolean enabled = false;
/**
* ,, : false
*/
private Boolean provider = false;
/**
* ,, : false
*/
private Boolean consumer = false;
/**
*
* : "default"
*/
private String configName = MqttProperties.DEFAULT;
/**
* rmi,
* : /request
* : /response
*/
private MqttProperties.Topic topic;
/**
* ,
*/
private String topicRequestPrefix="";
/**
* , : true
*/
private Boolean notFindMethodSendError = true;
/**
* (),: 500
*/
private Long timeout = 500L;
}

@ -0,0 +1,20 @@
package com.ruoyi.mqtt.rmi.exception;
import lombok.Getter;
/**
* MQTT RMI
*
* MQTT RMI
*/
public class MqttRmiErrorException extends MqttRmiException {
public MqttRmiErrorException(String requestId) {
super(requestId);
}
public MqttRmiErrorException(String requestId, String message) {
super(requestId, message);
}
}

@ -0,0 +1,25 @@
package com.ruoyi.mqtt.rmi.exception;
import lombok.Getter;
/**
* MQTT RMI
*
* MQTT RMI ID
*/
public class MqttRmiException extends RuntimeException {
@Getter
private String requestId;
public MqttRmiException(String requestId) {
super();
this.requestId = requestId;
}
public MqttRmiException(String requestId,String message) {
super(message);
this.requestId = requestId;
}
}

@ -0,0 +1,17 @@
package com.ruoyi.mqtt.rmi.exception;
/**
* MQTT RMI
*
* MQTT RMI
*/
public class MqttRmiTimeoutException extends MqttRmiException {
public MqttRmiTimeoutException(String requestId) {
super(requestId);
}
public MqttRmiTimeoutException(String requestId, String message) {
super(requestId, message);
}
}

@ -0,0 +1,67 @@
package com.ruoyi.mqtt.rmi.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ClassUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.mqtt.rmi.MqttRmiRequestSender;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiScan;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
/**
* MQTT RMI Bean
*
* @MqttRmiScan @MqttRmiService Bean
*/
public class MqttRmiConsumerBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
Map<String, Object> scanBean = configurableListableBeanFactory.getBeansWithAnnotation(MqttRmiScan.class);
List<String> basePackages = new ArrayList<>();
for (Object obj : scanBean.values()) {
// log.info("{}={}", obj.getClass(), obj.getClass().isAnnotationPresent(MqttRmiScan.class));
MqttRmiScan mqttRmiScan = obj.getClass().getAnnotation(MqttRmiScan.class);
String[] temp = new String[]{obj.getClass().getPackage().getName()};
if (mqttRmiScan.value().length > 0) {
temp = mqttRmiScan.value();
}
Collections.addAll(basePackages, temp);
}
log.info("basePackages:{}", basePackages);
Set<Class<?>> set = new HashSet<>();
for (String basePackage : basePackages) {
set.addAll(ClassUtil.scanPackageByAnnotation(basePackage, MqttRmiService.class).stream().filter(Class::isInterface).collect(Collectors.toSet()));
}
if (CollUtil.isEmpty(set)) {
return;
}
log.info("mqttRmiConsumerProxy:{}", set);
Object bean = new MqttRmiConsumerProxy(set.toArray(new Class[0])).createProxy();
configurableListableBeanFactory.autowireBean(bean);
configurableListableBeanFactory.registerSingleton("mqttRmiConsumerProxy", bean);
}
}

@ -0,0 +1,158 @@
package com.ruoyi.mqtt.rmi.impl;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.mqtt.MqttFactory;
import com.ruoyi.mqtt.MqttItem;
import com.ruoyi.mqtt.rmi.MqttRmiRequest;
import com.ruoyi.mqtt.rmi.MqttRmiRequestSender;
import com.ruoyi.mqtt.rmi.MqttRmiResponse;
import com.ruoyi.mqtt.rmi.config.MqttRmiProperties;
import com.ruoyi.mqtt.rmi.exception.MqttRmiErrorException;
import com.ruoyi.mqtt.rmi.exception.MqttRmiTimeoutException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import static com.ruoyi.mqtt.rmi.Const.*;
@RequiredArgsConstructor
@Slf4j
/**
* MQTT RMI
*
* MQTT RMI MQTT
*/
public class MqttRmiConsumerImpl implements IMqttMessageListener, MqttRmiRequestSender, PriorityOrdered {
private final MqttRmiProperties properties;
private final MqttFactory factory;
private final ObjectMapper mapper;
private final ApplicationContext act;
private MqttItem mqttItem;
private TimedCache<String, CompletableFuture<MqttRmiResponse>> requestCache;
@Getter
private static MqttRmiRequestSender sender;
@PostConstruct
public void init() throws Exception {
sender = this;
if (!properties.getConsumer() || !properties.getEnabled()) {
log.info("MqttRmiConsumer未激活");
return;
}
log.info("MqttRmiConsumer已激活");
mqttItem = factory.get(properties.getConfigName());
mqttItem.getClient().subscribe(properties.getTopic().getTopic() + RESPONSE_TOPICE, properties.getTopic().getQos(), this).waitForCompletion();
requestCache = CacheUtil.newTimedCache(properties.getTimeout());
// requestCache = new TimedCache<>(properties.getTimeout());
requestCache.setListener((a, b) -> {
if (!b.isDone()) {
b.complete(null);
}
});
requestCache.schedulePrune(properties.getTimeout() / 10);
}
@Override
public ObjectMapper getMapper() {
return mapper;
}
public MqttRmiResponse request(MqttRmiRequest request, long timeout) {
if (StrUtil.isBlank(request.getRequestId())) {
request.setRequestId(IdUtil.nanoId());
}
CompletableFuture<MqttRmiResponse> future = new CompletableFuture<>();
if (timeout > 0) {
requestCache.put(request.getRequestId(), future, timeout);
} else {
requestCache.put(request.getRequestId(), future);
}
send(request);
MqttRmiResponse ret = future.join();
if (ObjUtil.isNull(ret)) {
// return MqttRmiResponse.builder()
// .requestId(request.getRequestId())
// .name(request.getRequestId())
// .method(request.getRequestId())
// .ok(false)
// .error("timeout")
// .build();
throw new MqttRmiTimeoutException(request.getRequestId());
}
requestCache.remove(request.getRequestId());
if (!ret.getOk()) {
throw new MqttRmiErrorException(ret.getRequestId(), ret.getError());
}
return ret;
}
public void send(MqttRmiRequest request) {
try {
mqttItem.getClient().publish(
properties.getTopicRequestPrefix() + properties.getTopic().getTopic() + REQUEST_TOPICE,
mapper.writeValueAsString(request).getBytes(StandardCharsets.UTF_8),
properties.getTopic().getQos(),
properties.getTopic().getRetained());
} catch (Exception ex) {
log.warn("MqttRmiRequest send error", ex);
}
}
public void messageArrived(String topic, MqttMessage message) throws Exception {
String json = new String(message.getPayload(), StandardCharsets.UTF_8);
MqttRmiResponse response = null;
try {
response = mapper.readValue(json, MqttRmiResponse.class);
} catch (JsonProcessingException e) {
log.debug("MqttRmiConsumer json parse error: {}",
json);
return;
}
String requestId = response.getRequestId();
if (StrUtil.isBlank(requestId) || !requestCache.containsKey(requestId)) {
log.debug("MqttRmiConsumer ignore requestId: {} = {}",
requestId, json);
return;
}
try {
requestCache.get(requestId).complete(response);
} catch (Exception e) {
log.debug("MqttRmiConsumer complete error requestId: {} ",
requestId);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
}
}

@ -0,0 +1,93 @@
package com.ruoyi.mqtt.rmi.impl;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.mqtt.rmi.MqttRmiRequest;
import com.ruoyi.mqtt.rmi.MqttRmiRequestSender;
import com.ruoyi.mqtt.rmi.MqttRmiResponse;
import com.ruoyi.mqtt.rmi.MqttRmiUtil;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiMethod;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
@RequiredArgsConstructor
@Slf4j
/**
* MQTT RMI
*
* MQTT RMI
*/
public class MqttRmiConsumerProxy implements InvocationHandler {
// 目标接口的Class对象
private final Class<?>[] targetInterfaces;
/**
*
*/
@SuppressWarnings("unchecked")
public <T> T createProxy() {
return (T) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
targetInterfaces,
this
);
}
/**
*
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法调用前的逻辑
Class<?> clazz = method.getDeclaringClass();
String name = clazz.getSimpleName();
if (clazz.isAnnotationPresent(MqttRmiService.class)) {
MqttRmiService mqttRmiService = clazz.getAnnotation(MqttRmiService.class);
if (StrUtil.isNotBlank(mqttRmiService.value())) {
name = mqttRmiService.value();
}
}
String methodName = method.getName();
long timeout = 0;
if (method.isAnnotationPresent(MqttRmiMethod.class)) {
MqttRmiMethod mqttRmiMethod = method.getAnnotation(MqttRmiMethod.class);
if (StrUtil.isNotBlank(mqttRmiMethod.value())) {
methodName = mqttRmiMethod.value();
}
timeout = mqttRmiMethod.timeout();
}
List<JsonNode> list = new ArrayList<>();
if (ArrayUtil.isNotEmpty(args)) {
for (Object obj : args) {
list.add(MqttRmiUtil.getMapper().valueToTree(obj));
}
}
log.debug("MqttRmiConsumerProxy remote invoke: name={},method={},timeout={},args={}", name, methodName, timeout, list);
MqttRmiRequest request = MqttRmiRequest.builder()
.name(name)
.method(methodName)
.args(list)
.build();
MqttRmiResponse response = MqttRmiUtil.request(request, timeout);
Class<?> retType = method.getReturnType();
if (retType == void.class) {
return null;
}
return MqttRmiUtil.getMapper().convertValue(response.getBody(),retType);
}
}

@ -0,0 +1,86 @@
package com.ruoyi.mqtt.rmi.impl;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiMethod;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiProvider;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Getter
@Configuration
@RequiredArgsConstructor
/**
* MQTT RMI Bean
*
* @MqttRmiProvider Bean
* 便 MQTT
*/
public class MqttRmiProviderBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
public final static String SP = "\t";
private final static Map<String, Method> methodMap = new HashMap<>();
private final static Map<String, Object> beanMap = new HashMap<>();
public final static Collection<Method> getMethods() {
return methodMap.values();
}
public final static Method getMethod(String name, String method) {
return methodMap.get(name + SP + method);
}
public final static Collection<Object> getBeans() {
return beanMap.values();
}
public final static Object getBean(String name) {
return beanMap.get(name);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass().isAnnotationPresent(MqttRmiProvider.class)) {
String name = bean.getClass().getSimpleName();
MqttRmiProvider mqttRmiProvider = bean.getClass().getAnnotation(MqttRmiProvider.class);
if (!mqttRmiProvider.value().isEmpty()) {
name = mqttRmiProvider.value();
}
beanMap.put(name,bean);
for (Method m : bean.getClass().getMethods()) {
if (!m.isAnnotationPresent(MqttRmiMethod.class)) {
continue;
}
String methodName = m.getName();
MqttRmiMethod mqttRmiMethod = m.getAnnotation(MqttRmiMethod.class);
if (!mqttRmiMethod.value().isEmpty()) {
methodName = mqttRmiMethod.value();
}
methodMap.put(name + SP + methodName, m);
log.debug("MqttRmiProvider: name={},method={} > {}", name, methodName,m.toString());
}
}
return bean;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
}
}

@ -0,0 +1,137 @@
package com.ruoyi.mqtt.rmi.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.mqtt.MqttFactory;
import com.ruoyi.mqtt.MqttItem;
import com.ruoyi.mqtt.rmi.MqttRmiRequest;
import com.ruoyi.mqtt.rmi.MqttRmiResponse;
import com.ruoyi.mqtt.rmi.config.MqttRmiProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import static com.ruoyi.mqtt.rmi.Const.*;
import static com.ruoyi.mqtt.rmi.impl.MqttRmiProviderBeanPostProcessor.*;
@RequiredArgsConstructor
@Slf4j
/**
* MQTT RMI
*
* MQTT
*/
public class MqttRmiProviderImpl implements PriorityOrdered, IMqttMessageListener {
private final MqttRmiProperties properties;
private final MqttFactory factory;
private final ObjectMapper mapper;
private MqttItem mqttItem;
@PostConstruct
public void afterPropertiesSet() throws Exception {
if (!properties.getProvider() || !properties.getEnabled()) {
log.info("MqttRmiProvider未激活");
return;
}
log.info("MqttRmiProvider已激活");
mqttItem = factory.get(properties.getConfigName());
mqttItem.getClient().subscribe(properties.getTopic().getTopic() + REQUEST_TOPICE, properties.getTopic().getQos(), this).waitForCompletion();
}
private void send(MqttRmiResponse response) {
try {
mqttItem.getClient().publish(
properties.getTopic().getTopic() + RESPONSE_TOPICE,
mapper.writeValueAsString(response).getBytes(StandardCharsets.UTF_8),
properties.getTopic().getQos(),
properties.getTopic().getRetained());
} catch (Exception ex) {
log.warn("MqttRmiReponse send error", ex);
}
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
// if (!event.getTopic().equals(properties.getTopic().getTopic() + REQUEST_TOPICE)) {
// return true;
// }
// log.debug("size:{}",mqttItem.getMessageHandlers().size());
String json = new String(message.getPayload(), StandardCharsets.UTF_8);
log.debug("MqttRmiProvider json={}", json);
MqttRmiRequest request = null;
try {
request = mapper.readValue(json, MqttRmiRequest.class);
} catch (JsonProcessingException e) {
log.warn("MqttRmiRequest JSON Error", e);
}
MqttRmiResponse response = new MqttRmiResponse();
response.setOk(false);
response.setName(request.getName());
response.setRequestId(request.getRequestId());
response.setMethod(request.getMethod());
Method method = getMethod(request.getName(), request.getMethod());
//如果没有对应的方法不做处理
if (method == null) {
if (properties.getNotFindMethodSendError()) {
response.setError("not find method");
send(response);
return;
} else {
log.warn("MqttRmiProvider not find method");
return;
}
}
Class<?>[] pts = method.getParameterTypes();
Object[] args = new Object[pts.length];
for (int i = 0; i < pts.length; i++) {
try {
args[i] = mapper.convertValue(request.getArgs().get(i), pts[i]);
} catch (Exception e) {
response.setError("args error");
// response.setStackTrace(getStackTraceAsString(e));
log.warn("MqttRmiProvider args error", e);
send(response);
return;
}
}
Object ret = null;
try {
ret = method.invoke(getBean(request.getName()), args);
response.setOk(true);
if (ret != null) {
response.setBody(mapper.valueToTree(ret));
}
send(response);
} catch (Exception e) {
response.setError(e.getCause().getMessage());
// response.setStackTrace(getStackTraceAsString(e.getCause()));
log.warn("MqttRmiProvider invoke error: " + e.getCause().getMessage(), e.getCause());
send(response);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 最高优先级
}
}

@ -0,0 +1,14 @@
package com.ruoyi.mqtt.rmi.test;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiScan;
import org.springframework.stereotype.Component;
//@Component
@MqttRmiScan
/**
* MQTT RMI
*
* MQTT RMI
*/
public class MqttRmiScanEnabler {
}

@ -0,0 +1,14 @@
package com.ruoyi.mqtt.rmi.test;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiScan;
import org.springframework.stereotype.Component;
//@Component
@MqttRmiScan({"com.ruoyi.service.xxxxxx"})
/**
* MQTT RMI 1
*
* MQTT RMI
*/
public class MqttRmiScanEnabler1 {
}

@ -0,0 +1,38 @@
package com.ruoyi.mqtt.rmi.test;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiMethod;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
//@Component
@MqttRmiProvider
@Slf4j
/**
* MQTT RMI
*
* MQTT RMI
*/
public class MqttRmiTestProvider {
@MqttRmiMethod
public void test0() {
log.debug("test0");
}
@MqttRmiMethod
public void test1(String name) {
log.debug("test1:{}",name);
}
@MqttRmiMethod("test22")
public String test2(String name) {
log.debug("test2:{}",name);
return "test2+"+name;
}
@MqttRmiMethod
public String test3(String name) {
throw new RuntimeException(name);
}
}

@ -0,0 +1,21 @@
package com.ruoyi.mqtt.rmi.test;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiMethod;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiScan;
import com.ruoyi.mqtt.rmi.annotation.MqttRmiService;
//@MqttRmiService("MqttRmiTestProvider")
/**
* MQTT RMI
*
* MQTT RMI
*/
public interface MqttRmiTestService {
void test0();
void test1(String name);
@MqttRmiMethod(value="test22",timeout = 500)
String test2(String name);
String test3(String name);
}

@ -0,0 +1,64 @@
package com.ruoyi.mqtt.rmi.test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.mqtt.rmi.MqttRmiRequestSender;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
//@Component
@RequiredArgsConstructor
@Slf4j
public class MqttRmiTestServiceTest implements ApplicationRunner {
private final MqttRmiTestService service;
private final MqttRmiRequestSender sender;
private final ObjectMapper mapper;
// public MqttRmiTestServiceTest() {
// log.info("MqttRmiTestServiceTest create");
// }
public void test() {
String s = Long.toString(System.currentTimeMillis(), 36);
try {
service.test0();
log.info("service.test0 success");
} catch (Exception e) {
log.info("service.test0 error",e);
}
try {
service.test1(s);
log.info("service.test1(s) success");
} catch (Exception e) {
log.info("service.test1(s) error",e);
}
try {
String ret =service.test2(s);
log.info("service.test2(s) success: "+ret);
} catch (Exception e) {
log.info("service.test2(s) error",e);
}
try {
String ret =service.test3(s);
log.info("service.test3(s) success: "+ret);
} catch (Exception e) {
log.info("service.test3(s) error",e);
}
}
@Override
public void run(ApplicationArguments args) throws Exception {
test();
}
}

@ -0,0 +1,2 @@
com.ruoyi.mqtt.config.MqttConfig
com.ruoyi.mqtt.rmi.config.MqttRmiConfig

@ -0,0 +1,45 @@
--- # mqtt配置
spring:
mqtt:
enabled: true # 是否启用MQTT功能, 默认值: false
default-config: default # 默认配置名称, 默认值: "default"
configs:
default:
enabled: true # 是否启用该MQTT客户端配置, 默认值: true
client-id: ${ruoyi.name}-client # 客户端编号(唯一), 默认值: 基于系统时间生成的36进制字符串
url: tcp://192.168.3.222:1883 # 服务器URL, 默认值: tcp://127.0.0.1:1883
urls: # 服务器集群URL列表(可选)
- tcp://192.168.3.222:1883
username: ${ruoyi.name} # 用户名, 默认值: admin
password: ${ruoyi.name}1415926 # 密码, 默认值: 123456
clean-session: true # 是否清空session, 默认值: true
connection-timeout: 5 # 连接超时时间(秒), 默认值: 5
keep-alive-interval: 60 # 心跳时间(秒), 默认值: 60
will-topic: will/topic # 遗嘱主题, 默认值: will/topic
will-message: offline # 遗嘱消息, 默认值: offline
will-qos: 0 # 遗嘱消息的服务质量(QoS), 默认值: 0
custom-web-socket-headers: {} # 自定义WebSocket头部信息
ssl-properties: {} # SSL属性配置
mqtt-version: MQTT_VERSION_DEFAULT # MQTT协议版本, 默认值: MQTT_VERSION_DEFAULT
max-inflight: 10 # 最大未确认消息数量, 默认值: 10
sends: # 发送主题映射 发送主题可以使用如:{0},{1},{2},发送时使用params数组替换
test:
topic: ${ruoyi.name}/test/{0}
qos: 0 # 服务质量(QoS), 默认值: 0
retained: false # 是否保留消息, 默认值: false
subscribes: # 订阅主题列表,订阅主题可以使用 单级通配符 "+" 和多级通配符 "#"
- topic: ${ruoyi.name}/#
qos: 0 # 服务质量(QoS), 默认值: 0
rmi:
enabled: false # 是否启用MQTT的远程方法调用功能, 默认值: false
provider: true # 是否是提供者,负责接口的实现, 默认值: false
consumer: true # 是否是消费者,负责接口的定义, 默认值: false
config-name: default # 默认配置名称, 默认值: "default"
topic: # 用于rmi的发送和订阅主题,不能含变量和通配符
topic: ${ruoyi.name}/rmi # 主题名称
qos: 0 # 服务质量(QoS), 默认值: 0
retained: false # 是否保留消息, 默认值: false
topic-request-prefix: "" # 发送请求时,主题添加的前缀
not-find-method-send-error: true # 没有发现方法是否发送错误, 默认值: true
timeout: 500 # 远程方法调用默认超时(毫秒), 默认值: 500

@ -6,10 +6,7 @@ import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil; import cn.hutool.http.HttpUtil;
import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel; import lombok.*;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@ -21,6 +18,7 @@ import java.io.File;
import java.io.InputStream; import java.io.InputStream;
@EqualsAndHashCode(callSuper = true)
@Configuration @Configuration
@ConfigurationProperties(prefix = "ruoyi.file") @ConfigurationProperties(prefix = "ruoyi.file")
@Data @Data

Loading…
Cancel
Save