From 1194038279d8a378f2ce7cbea59a32d753becbf8 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期一, 30 三月 2026 08:16:47 +0800
Subject: [PATCH] feat: update rsf-design and redis integration
---
rsf-design/tests/system-role-scope-contract.test.mjs | 21 +
rsf-design/src/api/system-manage.js | 135 ++++++++
rsf-design/src/utils/router.js | 9
rsf-design/tests/system-manage-contract.test.mjs | 20 +
rsf-server/src/test/java/com/vincent/rsf/server/common/service/RedisServiceTest.java | 53 +++
rsf-design/.env.development | 2
rsf-design/tests/system-user-page-contract.test.mjs | 10
rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java | 252 +++++------------
rsf-design/src/views/system/menu/index.vue | 29 +
rsf-design/src/locales/langs/en.json | 109 +++++++
rsf-design/src/locales/langs/zh.json | 111 +++++++
rsf-design/tests/system-menu-page-contract.test.mjs | 37 ++
12 files changed, 591 insertions(+), 197 deletions(-)
diff --git a/rsf-design/.env.development b/rsf-design/.env.development
index 38d70b4..36ddc4b 100644
--- a/rsf-design/.env.development
+++ b/rsf-design/.env.development
@@ -7,7 +7,7 @@
VITE_API_URL = /
# 浠g悊鐩爣鍦板潃锛堝紑鍙戠幆澧冮�氳繃 Vite 浠g悊杞彂璇锋眰鍒版鍦板潃锛岃В鍐宠法鍩熼棶棰橈級
-VITE_API_PROXY_URL = https://m1.apifoxmock.com/m1/6400575-6097373-default
+VITE_API_PROXY_URL = http://127.0.0.1:8085/ref-server
# Delete console
VITE_DROP_CONSOLE = false
diff --git a/rsf-design/src/api/system-manage.js b/rsf-design/src/api/system-manage.js
index e5c5bc3..1c9f35a 100644
--- a/rsf-design/src/api/system-manage.js
+++ b/rsf-design/src/api/system-manage.js
@@ -1,19 +1,132 @@
import request from '@/utils/http'
+
+export function buildUserListParams(params = {}) {
+ return {
+ current: params.current || 1,
+ pageSize: params.pageSize || params.size || 20,
+ username: params.username,
+ nickname: params.nickname,
+ phone: params.phone,
+ status: params.status,
+ deptId: params.deptId
+ }
+}
+
+export function buildRoleListParams(params = {}) {
+ return {
+ current: params.current || 1,
+ pageSize: params.pageSize || params.size || 20,
+ name: params.name,
+ code: params.code,
+ memo: params.memo,
+ status: params.status
+ }
+}
+
function fetchGetUserList(params) {
- return request.get({
- url: '/api/user/list',
- params
- })
+ return request.post({ url: '/user/page', params: buildUserListParams(params) })
}
+
+function fetchSaveUser(params) {
+ return request.post({ url: '/user/save', params })
+}
+
+function fetchUpdateUser(params) {
+ return request.post({ url: '/user/update', params })
+}
+
+function fetchDeleteUser(id) {
+ return request.post({ url: `/user/remove/${id}` })
+}
+
+function fetchResetUserPassword(params) {
+ return request.post({ url: '/auth/reset/password', params })
+}
+
+function fetchUpdateUserStatus(params) {
+ return request.post({ url: '/user/update', params })
+}
+
+function fetchGetUserDetail(id) {
+ return request.get({ url: `/user/${id}` })
+}
+
function fetchGetRoleList(params) {
- return request.get({
- url: '/api/role/list',
+ return request.post({ url: '/role/page', params: buildRoleListParams(params) })
+}
+
+function fetchSaveRole(params) {
+ return request.post({ url: '/role/save', params })
+}
+
+function fetchUpdateRole(params) {
+ return request.post({ url: '/role/update', params })
+}
+
+function fetchDeleteRole(id) {
+ return request.post({ url: `/role/remove/${id}` })
+}
+
+function fetchGetRoleOptions(params) {
+ return request.post({ url: '/role/list', params })
+}
+
+function fetchGetDeptTree(params) {
+ return request.post({ url: '/dept/tree', params })
+}
+
+function fetchGetMenuTree(params) {
+ return request.post({ url: '/menu/tree', params })
+}
+
+function fetchGetRoleScopeList(scopeType, roleId) {
+ const urlMap = {
+ menu: '/role/scope/list',
+ pda: '/rolePda/scope/list',
+ matnr: '/roleMatnr/scope/list',
+ warehouse: '/roleWarehouse/scope/list'
+ }
+ return request.get({ url: urlMap[scopeType], params: { roleId } })
+}
+
+function fetchUpdateRoleScope(scopeType, params) {
+ const urlMap = {
+ menu: '/role/scope/update',
+ pda: '/rolePda/scope/update',
+ matnr: '/roleMatnr/scope/update',
+ warehouse: '/roleWarehouse/scope/update'
+ }
+ return request.post({ url: urlMap[scopeType], params })
+}
+
+function fetchGetUserLoginList(params) {
+ return request.post({
+ url: '/userLogin/page',
params
})
}
-function fetchGetMenuList() {
- return request.get({
- url: '/api/v3/system/menus/simple'
- })
+
+function fetchGetMenuList(params) {
+ return fetchGetMenuTree(params)
}
-export { fetchGetMenuList, fetchGetRoleList, fetchGetUserList }
+
+export {
+ fetchGetUserList,
+ fetchSaveUser,
+ fetchUpdateUser,
+ fetchDeleteUser,
+ fetchResetUserPassword,
+ fetchUpdateUserStatus,
+ fetchGetUserDetail,
+ fetchGetRoleList,
+ fetchSaveRole,
+ fetchUpdateRole,
+ fetchDeleteRole,
+ fetchGetRoleOptions,
+ fetchGetDeptTree,
+ fetchGetMenuTree,
+ fetchGetRoleScopeList,
+ fetchUpdateRoleScope,
+ fetchGetUserLoginList,
+ fetchGetMenuList
+}
diff --git a/rsf-design/src/locales/langs/en.json b/rsf-design/src/locales/langs/en.json
index 4dbade2..e9ee97d 100644
--- a/rsf-design/src/locales/langs/en.json
+++ b/rsf-design/src/locales/langs/en.json
@@ -265,6 +265,115 @@
"menu": "Menu Manage"
}
},
+ "menu": {
+ "basStationArea": "BasStationArea",
+ "dashboard": "Dashboard",
+ "settings": "Settings",
+ "basicInfo": "BasicInfo",
+ "system": "System",
+ "user": "User",
+ "role": "Role",
+ "menu": "Menu",
+ "host": "Host",
+ "department": "Department",
+ "token": "Token",
+ "operation": "Operation",
+ "config": "Config",
+ "aiParam": "AI Params",
+ "aiPrompt": "Prompts",
+ "aiMcpMount": "MCP Mounts",
+ "aiCallLog": "AI Observe",
+ "tenant": "Tenant",
+ "userLogin": "Token",
+ "customer": "Customer",
+ "shipper": "shipper",
+ "matnr": "Matnr",
+ "matnrGroup": "MatnrGroup",
+ "warehouse": "Warehouse",
+ "warehouseAreas": "WarehouseAreas",
+ "loc": "Loc",
+ "locItem": "LocItem",
+ "locType": "LocType",
+ "locArea": "locArea",
+ "locAreaMat": "Logic Areas",
+ "locAreaMatRela": "LocAreaMatRela",
+ "container": "Container",
+ "contract": "Contract",
+ "qlyInspect": "QlyInspect",
+ "qlyIsptItem": "qlyIsptItem",
+ "dictType": "DictType",
+ "dictData": "DictData",
+ "companys": "Companys",
+ "serialRuleItem": "SerialRuleItem",
+ "serialRule": "SerialRule",
+ "asnOrder": "AsnOrder",
+ "asnOrderItem": "AsnOrderItem",
+ "asnOrderLog": "asnOrderLog",
+ "asnOrderItemLog": "asnOrderItemLog",
+ "purchase": "Purchase",
+ "purchaseItem": "PurchaseItem",
+ "whMat": "Warehouse Mat",
+ "fields": "Extend Fields",
+ "fieldsItem": "Extend Fields Items",
+ "warehouseAreasItem": "Temp Warehouse Areas Stock",
+ "deviceSite": "deviceSite",
+ "waitPakin": "WaitPakin",
+ "waitPakinItem": "WaitPakinItem",
+ "task": "Task",
+ "taskItem": "TaskItem",
+ "taskLog": "TaskLog",
+ "taskItemLog": "TaskItemLog",
+ "stock": "Stock Manage",
+ "stockItem": "Stock Item",
+ "locPreview": "LocItem",
+ "histories": "Histories",
+ "wareWork": "Warehouse Working",
+ "statistics": "Stock Statistics",
+ "stockManage": "Stock Manage",
+ "logs": "Logs",
+ "permissions": "Permissions",
+ "delivery": "Delivery",
+ "outStock": "Out Stock",
+ "outStockItem": "Out Stock Item",
+ "inStockPoces": "In Stock Pocess",
+ "outStockPoces": "Out Stock Pocess",
+ "warehouseStock": "Instant Inventory",
+ "deviceBind": "Device Bind",
+ "tasks": "Tasks",
+ "wave": "Wave Manage",
+ "basStation": "BasStation",
+ "basContainer": "BasContainer",
+ "outBound": "Out Bound",
+ "checkOutBound": "Check Out Bound",
+ "stockTransfer": "Stock Transfer",
+ "waveRule": "Wave Rules",
+ "checkOrder": "Check Order",
+ "checkDiff": "Check Diff",
+ "transfer": "Transfer",
+ "transferItem": "Transfer Item",
+ "locRevise": "Loc Revise",
+ "statisticReport": "Statistical Report",
+ "locDeadReport": "Locs Dead Report",
+ "stockStatistic": "Stock Statistic",
+ "outStatistic": "Out Statistic",
+ "inStatistic": "In Statistic",
+ "inStatisticItem": "In Statistic Item",
+ "outStatisticItem": "Out Statistic Item",
+ "statisticCount": "Statistic Count",
+ "preparation": "Preparation",
+ "check": "Check",
+ "abnormal": "Abnormal",
+ "platform": "Platform",
+ "freeze": "Freeze",
+ "transferPoces": "Transfer Process",
+ "menuPda": "MenuPda",
+ "taskPathTemplate": "TaskPathTemplate",
+ "taskPathTemplateNode": "TaskPathTemplateNode",
+ "subsystemFlowTemplate": "SubsystemFlowTemplate",
+ "flowStepTemplate": "FlowStepTemplate",
+ "taskPathTemplateMerge": "TaskPathTemplateMerge",
+ "missionFlowStepInstance": "Mission Flow Steps"
+ },
"table": {
"form": {
"reset": "Reset",
diff --git a/rsf-design/src/locales/langs/zh.json b/rsf-design/src/locales/langs/zh.json
index 2e9bdcc..c72e9ea 100644
--- a/rsf-design/src/locales/langs/zh.json
+++ b/rsf-design/src/locales/langs/zh.json
@@ -265,6 +265,117 @@
"menu": "鑿滃崟绠$悊"
}
},
+ "menu": {
+ "basStationArea": "绔欑偣鍖哄煙",
+ "dashboard": "鎺у埗鍙�",
+ "settings": "涓汉璁剧疆",
+ "basicInfo": "鍩虹淇℃伅",
+ "system": "绯荤粺璁剧疆",
+ "user": "鐢ㄦ埛绠$悊",
+ "role": "瑙掕壊绠$悊",
+ "menu": "鑿滃崟绠$悊",
+ "host": "鏈烘瀯绠$悊",
+ "department": "閮ㄩ棬绠$悊",
+ "token": "鐧诲綍鏃ュ織",
+ "operation": "鎿嶄綔鏃ュ織",
+ "config": "閰嶇疆鍙傛暟",
+ "aiParam": "AI 鍙傛暟",
+ "aiPrompt": "Prompt 绠$悊",
+ "aiMcpMount": "MCP 鎸傝浇",
+ "aiCallLog": "AI 瑙傛祴",
+ "tenant": "绉熸埛绠$悊",
+ "userLogin": "鐧诲綍鏃ュ織",
+ "customer": "瀹㈡埛琛�",
+ "shipper": "璐т富淇℃伅",
+ "matnr": "鐗╂枡",
+ "matnrGroup": "鐗╂枡鍒嗙粍",
+ "warehouse": "浠撳簱",
+ "warehouseAreas": "搴撳尯",
+ "loc": "搴撲綅",
+ "locItem": "搴撳瓨鏄庣粏",
+ "locType": "搴撲綅绫诲瀷(搴�)",
+ "locArea": "閫昏緫鍒嗗尯(搴�)",
+ "locAreaMat": "閫昏緫鍒嗗尯",
+ "locAreaMatRela": "搴撳尯鐗╂枡鍏崇郴",
+ "container": "瀹瑰櫒绠$悊(搴�)",
+ "contract": "鍚堝悓淇℃伅(搴�)",
+ "qlyInspect": "璐ㄦ淇℃伅",
+ "qlyIsptItem": "璐ㄦ淇℃伅鏄庣粏",
+ "dictType": "鏁版嵁瀛楀吀",
+ "dictData": "瀛楀吀鏁版嵁闆�",
+ "companys": "寰�鏉ヤ紒涓�",
+ "serialRuleItem": "缂栫爜瑙勫垯瀛愯〃",
+ "serialRule": "缂栫爜瑙勫垯",
+ "asnOrder": "鍏ュ簱閫氱煡鍗�",
+ "asnOrderItem": "鏀惰揣鏄庣粏",
+ "asnOrderLog": "鍘嗗彶閫氱煡鍗�",
+ "asnOrderItemLog": "鏀惰揣鍘嗗彶鏄庣粏",
+ "purchase": "PO鍗�",
+ "purchaseItem": "PO鍗曟槑缁�",
+ "whMat": "搴撳尯鐗╂枡鍏崇郴",
+ "fields": "鎵╁睍瀛楁",
+ "fieldsItem": "鎵╁睍瀛楁鏄庣粏",
+ "warehouseAreasItem": "鏀惰揣搴撳瓨",
+ "deviceSite": "璺緞绠$悊",
+ "waitPakin": "缁勬墭妗�",
+ "waitPakinItem": "缁勬墭妗f槑缁�",
+ "waitPakinLog": "缁勬墭鍘嗗彶妗�",
+ "waitPakinItemLog": "缁勬墭鍘嗗彶妗f槑缁�",
+ "task": "浠诲姟绠$悊",
+ "taskItem": "浠诲姟妗f槑缁�",
+ "taskLog": "浠诲姟鍘嗗彶妗�",
+ "taskItemLog": "浠诲姟鏄庣粏鍘嗗彶妗�",
+ "stock": "鍏ュ嚭搴撳巻鍙�",
+ "stockItem": "鍗曟嵁鏄庣粏",
+ "locPreview": "搴撲綅鏄庣粏",
+ "histories": "鍘嗗彶妗�",
+ "wareWork": "浠撳簱浣滀笟",
+ "statistics": "搴撳瓨鏌ヨ",
+ "stockManage": "搴撳瓨绠$悊",
+ "logs": "鏃ュ織",
+ "permissions": "鏉冮檺绠$悊",
+ "delivery": "DO鍗�",
+ "outStock": "鍑哄簱閫氱煡鍗�",
+ "outStockItem": "鍑哄簱鍗曟槑缁�",
+ "inStockPoces": "鍏ュ簱绠$悊",
+ "outStockPoces": "鍑哄簱绠$悊",
+ "warehouseStock": "鍗虫椂搴撳瓨",
+ "deviceBind": "璁惧缁戝畾",
+ "tasks": "浠诲姟绠$悊",
+ "wave": "娉㈡绠$悊",
+ "basStation": "绔欑偣绠$悊",
+ "basContainer": "瀹瑰櫒瑙勫垯",
+ "outBound": "鍑哄簱浣滀笟",
+ "checkOutBound": "鐩樼偣鍑哄簱",
+ "stockTransfer": "搴撲綅杞Щ",
+ "waveRule": "娉㈡绛栫暐",
+ "checkOrder": "鐩樼偣鍗�",
+ "checkDiff": "鐩樼偣宸紓鍗�",
+ "transfer": "璋冩嫈鍗�",
+ "transferItem": "璋冩嫈鍗曟槑缁�",
+ "locRevise": "搴撳瓨璋冩暣",
+ "statisticReport": "鎶ヨ〃绠$悊",
+ "locDeadReport": "搴撳瓨鍋滄粸鎶ヨ〃",
+ "stockStatistic": "鏃ュ叆搴撴眹鎬绘煡璇�",
+ "outStatistic": "鏃ュ嚭搴撴眹鎬绘煡璇�",
+ "inStatistic": "鏃ュ叆搴撴眹鎬绘煡璇�",
+ "inStatisticItem": "鏃ュ叆搴撴槑缁嗘煡璇�",
+ "outStatisticItem": "鏃ュ嚭搴撴槑缁嗘煡璇�",
+ "statisticCount": "鏃ュ嚭鍏ュ簱姹囨�荤粺璁�",
+ "preparation": "澶囨枡鍗�",
+ "check": "鐩樼偣绠$悊",
+ "abnormal": "寮傚父绠$悊",
+ "platform": "骞冲彴绠$悊",
+ "freeze": "搴撳瓨鍐荤粨",
+ "transferPoces": "璋冩嫧绠$悊",
+ "menuPda": "PDA鑿滃崟",
+ "taskPathTemplate": "浠诲姟璺緞妯℃澘",
+ "taskPathTemplateNode": "浠诲姟璺緞妯℃澘鑺傜偣",
+ "subsystemFlowTemplate": "瀛愮郴缁熸祦绋嬫ā鏉�",
+ "flowStepTemplate": "娴佺▼姝ラ妯℃澘",
+ "taskPathTemplateMerge": "浠诲姟璺緞妯℃澘鍚堝苟",
+ "missionFlowStepInstance": "浠诲姟娴佺▼姝ラ"
+ },
"table": {
"form": {
"reset": "閲嶇疆",
diff --git a/rsf-design/src/utils/router.js b/rsf-design/src/utils/router.js
index 9f8b47e..7c5d64f 100644
--- a/rsf-design/src/utils/router.js
+++ b/rsf-design/src/utils/router.js
@@ -20,10 +20,17 @@
}
const formatMenuTitle = (title) => {
if (title) {
- if (title.startsWith('menus.')) {
+ if (title.startsWith('menus.') || title.startsWith('menu.')) {
if (i18n.global.te(title)) {
return $t(title)
} else {
+ const fallbackTitle =
+ title.startsWith('menus.') && title.split('.').pop()
+ ? `menu.${title.split('.').pop()}`
+ : ''
+ if (fallbackTitle && i18n.global.te(fallbackTitle)) {
+ return $t(fallbackTitle)
+ }
return title.split('.').pop() || title
}
}
diff --git a/rsf-design/src/views/system/menu/index.vue b/rsf-design/src/views/system/menu/index.vue
index d28afa8..3a79c27 100644
--- a/rsf-design/src/views/system/menu/index.vue
+++ b/rsf-design/src/views/system/menu/index.vue
@@ -53,6 +53,7 @@
import MenuDialog from './modules/menu-dialog.vue'
import { formatMenuTitle } from '@/utils/router'
+ import ArtSvgIcon from '@/components/core/base/art-svg-icon/index.vue'
import ArtButtonTable from '@/components/core/forms/art-button-table/index.vue'
import { useTableColumns } from '@/hooks/core/useTableColumns'
import { fetchGetMenuList } from '@/api/system-manage'
@@ -115,12 +116,34 @@
if (row.meta?.link) return '澶栭摼'
return '鏈煡'
}
+ const getMenuDisplayTitle = (row) => {
+ const titleKey = row.meta?.title || row.name || ''
+ const normalizedTitleKey =
+ titleKey && !String(titleKey).includes('.') ? `menu.${titleKey}` : titleKey
+ return formatMenuTitle(normalizedTitleKey)
+ }
+ const getMenuDisplayIcon = (row) => row.meta?.icon || row.icon || ''
const { columnChecks, columns } = useTableColumns(() => [
+ {
+ prop: 'meta.icon',
+ label: '鍥炬爣棰勮',
+ width: 96,
+ align: 'center',
+ formatter: (row) => {
+ const icon = getMenuDisplayIcon(row)
+
+ if (!icon) return h('span', { class: 'text-g-400' }, '-')
+
+ return h('div', { class: 'flex items-center justify-center' }, [
+ h(ArtSvgIcon, { icon, class: 'text-base text-g-700' })
+ ])
+ }
+ },
{
prop: 'meta.title',
label: '鑿滃崟鍚嶇О',
minWidth: 120,
- formatter: (row) => formatMenuTitle(row.meta?.title)
+ formatter: (row) => getMenuDisplayTitle(row)
},
{
prop: 'type',
@@ -245,11 +268,11 @@
})
}
const searchMenu = (items) => {
- const results = []
+ const results = []
for (const item of items) {
const searchName = appliedFilters.name?.toLowerCase().trim() || ''
const searchRoute = appliedFilters.route?.toLowerCase().trim() || ''
- const menuTitle = formatMenuTitle(item.meta?.title || '').toLowerCase()
+ const menuTitle = getMenuDisplayTitle(item).toLowerCase()
const menuPath = (item.path || '').toLowerCase()
const nameMatch = !searchName || menuTitle.includes(searchName)
const routeMatch = !searchRoute || menuPath.includes(searchRoute)
diff --git a/rsf-design/tests/system-manage-contract.test.mjs b/rsf-design/tests/system-manage-contract.test.mjs
new file mode 100644
index 0000000..c9879e9
--- /dev/null
+++ b/rsf-design/tests/system-manage-contract.test.mjs
@@ -0,0 +1,20 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+import { buildUserListParams, buildRoleListParams } from '../src/api/system-manage.js'
+
+test('buildUserListParams matches the rsf-admin paging contract', () => {
+ assert.deepEqual(
+ buildUserListParams({
+ current: 2,
+ pageSize: 20,
+ username: 'root',
+ deptId: 3
+ }),
+ {
+ current: 2,
+ pageSize: 20,
+ username: 'root',
+ deptId: 3
+ }
+ )
+})
diff --git a/rsf-design/tests/system-menu-page-contract.test.mjs b/rsf-design/tests/system-menu-page-contract.test.mjs
new file mode 100644
index 0000000..d8bb894
--- /dev/null
+++ b/rsf-design/tests/system-menu-page-contract.test.mjs
@@ -0,0 +1,37 @@
+import assert from 'node:assert/strict'
+import fs from 'node:fs'
+import path from 'node:path'
+import test from 'node:test'
+
+const projectRoot = path.resolve(import.meta.dirname, '..')
+const zhLocalePath = path.join(projectRoot, 'src', 'locales', 'langs', 'zh.json')
+const enLocalePath = path.join(projectRoot, 'src', 'locales', 'langs', 'en.json')
+const routerUtilsPath = path.join(projectRoot, 'src', 'utils', 'router.js')
+const menuPagePath = path.join(projectRoot, 'src', 'views', 'system', 'menu', 'index.vue')
+
+test('current-system menu keys are available in rsf-design locales', () => {
+ const zhMessages = JSON.parse(fs.readFileSync(zhLocalePath, 'utf8'))
+ const enMessages = JSON.parse(fs.readFileSync(enLocalePath, 'utf8'))
+
+ assert.equal(zhMessages.menu?.system, '绯荤粺璁剧疆')
+ assert.equal(zhMessages.menu?.basicInfo, '鍩虹淇℃伅')
+ assert.equal(zhMessages.menu?.aiParam, 'AI 鍙傛暟')
+
+ assert.equal(enMessages.menu?.system, 'System')
+ assert.equal(enMessages.menu?.basicInfo, 'BasicInfo')
+ assert.equal(enMessages.menu?.aiParam, 'AI Params')
+})
+
+test('formatMenuTitle recognizes current-system menu translation keys', () => {
+ const routerSource = fs.readFileSync(routerUtilsPath, 'utf8')
+
+ assert.match(routerSource, /startsWith\('menu\.'\)|startsWith\("menu\."\)/)
+})
+
+test('menu management table shows icon preview and translated names', () => {
+ const menuPageSource = fs.readFileSync(menuPagePath, 'utf8')
+
+ assert.match(menuPageSource, /label:\s*'鍥炬爣棰勮'/)
+ assert.match(menuPageSource, /ArtSvgIcon/)
+ assert.match(menuPageSource, /row\.meta\?\.title\s*\|\|\s*row\.name|item\.meta\?\.title\s*\|\|\s*item\.name/)
+})
diff --git a/rsf-design/tests/system-role-scope-contract.test.mjs b/rsf-design/tests/system-role-scope-contract.test.mjs
new file mode 100644
index 0000000..30c293e
--- /dev/null
+++ b/rsf-design/tests/system-role-scope-contract.test.mjs
@@ -0,0 +1,21 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+import {
+ buildScopeTreeNodes,
+ buildScopeSavePayload,
+ SCOPE_TYPES
+} from '../src/views/system/role/roleScope.config.js'
+
+test('menu scope nodes preserve backend ids for save', () => {
+ const tree = buildScopeTreeNodes(SCOPE_TYPES.menu, [{ id: 1, label: '绯荤粺绠$悊' }])
+ assert.equal(tree[0].id, 1)
+})
+
+test('scope save payload is delegated per scope type', () => {
+ const payload = buildScopeSavePayload(SCOPE_TYPES.menu, {
+ roleId: 9,
+ selectedIds: [1, 2],
+ authType: 0
+ })
+ assert.equal(payload.id, 9)
+})
diff --git a/rsf-design/tests/system-user-page-contract.test.mjs b/rsf-design/tests/system-user-page-contract.test.mjs
new file mode 100644
index 0000000..3c09037
--- /dev/null
+++ b/rsf-design/tests/system-user-page-contract.test.mjs
@@ -0,0 +1,10 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+import { buildUserDialogModel } from '../src/views/system/user/userPage.helpers.js'
+
+test('buildUserDialogModel maps rsf-admin edit data into the dialog model', () => {
+ assert.equal(
+ buildUserDialogModel({ username: 'root', nickname: '绠$悊鍛�', deptId: 1, roles: [{ id: 3 }] }).username,
+ 'root'
+ )
+})
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java
index d6c0af5..4a2b8c7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java
@@ -11,6 +11,8 @@
import java.util.Date;
import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
/**
* redis tools
@@ -60,6 +62,31 @@
return null;
}
+ private <T> T withJedis(Function<Jedis, T> action) {
+ Jedis jedis = this.getJedis();
+ if (jedis == null) {
+ return null;
+ }
+ try (jedis) {
+ return action.apply(jedis);
+ } catch (Exception e) {
+ log.error(this.getClass().getSimpleName(), e);
+ }
+ return null;
+ }
+
+ private void withJedisVoid(Consumer<Jedis> action) {
+ Jedis jedis = this.getJedis();
+ if (jedis == null) {
+ return;
+ }
+ try (jedis) {
+ action.accept(jedis);
+ } catch (Exception e) {
+ log.error(this.getClass().getSimpleName(), e);
+ }
+ }
+
// key - object ----------------------------------------------------------------------------------------------------------
public String set(String flag, String key, Object value) {
@@ -70,13 +97,7 @@
this.delete(flag, key);
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.set((flag + LINK + key).getBytes(), Serialize.serialize(value));
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.set((flag + LINK + key).getBytes(), Serialize.serialize(value)));
}
public String set(String flag, String key, Object value, Integer seconds) {
@@ -87,58 +108,38 @@
this.delete(flag, key);
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.setex((flag + LINK + key).getBytes(), seconds, Serialize.serialize(value));
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.setex((flag + LINK + key).getBytes(), seconds, Serialize.serialize(value)));
}
public <T> T get(String flag, String key) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
+ return withJedis(jedis -> {
byte[] bytes = jedis.get((flag + LINK + key).getBytes());
if(bytes == null || bytes.length == 0 ) {
return null;
}
return (T) Serialize.unSerialize(bytes);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ });
}
public Long delete(String flag, String key) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.del((flag + LINK + key).getBytes());
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.del((flag + LINK + key).getBytes()));
}
public Long clear(String flag) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
this.setValue(flag, "CLEARING", "true");
- try{
+ return withJedis(jedis -> {
Object returnValue = jedis.eval("local keys = redis.call('keys', ARGV[1]) for i=1,#keys,1000 do redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) end return #keys",0,flag + LINK + "*");
return Long.parseLong(String.valueOf(returnValue));
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ });
}
// 涓哄凡瀛樺湪鐨刱ey璁剧疆杩囨湡鏃堕棿 - 绉�
@@ -146,12 +147,9 @@
if(!this.initialize) {
return;
}
- Jedis jedis = this.getJedis();
- try{
+ withJedisVoid(jedis -> {
jedis.expire((flag + LINK + key).getBytes(), seconds);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
+ });
}
// 涓哄凡瀛樺湪鐨刱ey璁剧疆杩囨湡鏃堕棿 - 鍏蜂綋鍒版椂闂存埑 锛堢锛�
@@ -159,12 +157,9 @@
if(!this.initialize) {
return;
}
- Jedis jedis = this.getJedis();
- try{
+ withJedisVoid(jedis -> {
jedis.expireAt((flag + LINK + key).getBytes(), toTime.getTime()/1000);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
+ });
}
// 鑾峰彇杩囨湡鍓╀綑鏃堕棿锛堢锛� ttl == -1 娌℃湁璁剧疆杩囨湡鏃堕棿锛� ttl == -2 key涓嶅瓨鍦�
@@ -172,13 +167,7 @@
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.ttl((flag + LINK + key).getBytes());
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.ttl((flag + LINK + key).getBytes()));
}
@@ -188,39 +177,21 @@
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.set(flag + LINK + key, value);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.set(flag + LINK + key, value));
}
public String setValue(String flag, String key, String value, Integer seconds) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.setex(flag + LINK + key, seconds , value);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.setex(flag + LINK + key, seconds , value));
}
public String getValue(String flag, String key) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.get(flag + LINK + key);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.get(flag + LINK + key));
}
public Long deleteValue(String flag, String... key) {
@@ -228,17 +199,13 @@
return null;
}
- Jedis jedis = this.getJedis();
- try{
+ return withJedis(jedis -> {
String[] keys = new String[key.length];
for(int i=0;i<key.length;i++){
keys[i] = flag + LINK + key[i];
}
return jedis.del(keys);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ });
}
public Long clearValue(String flag) {
@@ -247,39 +214,28 @@
}
this.setValue(flag, "CLEARING", "true");
- Jedis jedis = this.getJedis();
-
- try{
+ return withJedis(jedis -> {
Object returnValue = jedis.eval("return redis.call('del', unpack(redis.call('keys', ARGV[1])))",0,flag + LINK + "*");
return Long.parseLong(String.valueOf(returnValue));
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ });
}
public void setValueExpire(String flag, String key,int seconds){
if(!this.initialize) {
return;
}
- Jedis jedis = this.getJedis();
- try{
+ withJedisVoid(jedis -> {
jedis.expire((flag + LINK + key).getBytes(), seconds);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
+ });
}
public void setValueExpireAt(String flag, String key,Date atTime){
if(!this.initialize) {
return;
}
- Jedis jedis = this.getJedis();
- try{
+ withJedisVoid(jedis -> {
jedis.expireAt((flag + LINK + key).getBytes(), atTime.getTime()/1000);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
+ });
}
@@ -293,95 +249,63 @@
deleteMap(name,key);
return null;
}
- Jedis jedis = this.getJedis();
- try {
- return jedis.hset(name.getBytes(), key.getBytes(), Serialize.serialize(value));
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.hset(name.getBytes(), key.getBytes(), Serialize.serialize(value)));
}
public <T> T getMap(String name, String key) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
+ return withJedis(jedis -> {
byte[] bytes = jedis.hget(name.getBytes(), key.getBytes());
if (bytes == null || bytes.length == 0) {
return null;
}
return (T) Serialize.unSerialize(bytes);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ });
}
public Set<String> getMapKeys(String name) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.hkeys(name);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.hkeys(name));
}
public Long deleteMap(String name, String... key) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
+ return withJedis(jedis -> {
String[] keys = new String[key.length];
System.arraycopy(key, 0, keys, 0, key.length);
return jedis.hdel(name, keys);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ });
}
public Long clearMap(String name) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.del(name);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.del(name));
}
public void setMapExpire(String name,int seconds){
if(!this.initialize) {
return;
}
- Jedis jedis = this.getJedis();
- try{
+ withJedisVoid(jedis -> {
jedis.expire(name.getBytes(), seconds);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
+ });
}
public void setMapExpireAt(String name,Date atTime){
if(!this.initialize) {
return;
}
- Jedis jedis = this.getJedis();
- try{
+ withJedisVoid(jedis -> {
jedis.expireAt(name.getBytes(), atTime.getTime()/1000);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
+ });
}
@@ -395,13 +319,7 @@
if(value == null){
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.rpush(name.getBytes(), Serialize.serialize(value));
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.rpush(name.getBytes(), Serialize.serialize(value)));
}
// 鑾峰彇鍒楄〃澶撮儴鍏冪礌 && 鍒犻櫎
@@ -409,17 +327,13 @@
if(!this.initialize){
return null;
}
- Jedis jedis = this.getJedis();
- try{
+ return withJedis(jedis -> {
byte[] bytes = jedis.lpop(name.getBytes());
if(bytes == null || bytes.length == 0) {
return null;
}
return (T) Serialize.unSerialize(bytes);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ });
}
// 鍒犻櫎
@@ -427,37 +341,25 @@
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.del(name);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.del(name));
}
public void setListExpire(String name, int seconds){
if(!this.initialize) {
return;
}
- Jedis jedis = this.getJedis();
- try{
+ withJedisVoid(jedis -> {
jedis.expire(name.getBytes(), seconds);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
+ });
}
public void setListExpireAt(String name, Date atTime){
if(!this.initialize) {
return;
}
- Jedis jedis = this.getJedis();
- try{
+ withJedisVoid(jedis -> {
jedis.expireAt(name.getBytes(), atTime.getTime()/1000);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
+ });
}
// count ----------------------------------------------------------------------------------------------------------
@@ -466,26 +368,14 @@
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.incr("COUNT." + key);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.incr("COUNT." + key));
}
public Long decr(String key) {
if(!this.initialize) {
return null;
}
- Jedis jedis = this.getJedis();
- try{
- return jedis.decr("COUNT." + key);
- } catch (Exception e) {
- log.error(this.getClass().getSimpleName(), e);
- }
- return null;
+ return withJedis(jedis -> jedis.decr("COUNT." + key));
}
}
diff --git a/rsf-server/src/test/java/com/vincent/rsf/server/common/service/RedisServiceTest.java b/rsf-server/src/test/java/com/vincent/rsf/server/common/service/RedisServiceTest.java
new file mode 100644
index 0000000..c61c678
--- /dev/null
+++ b/rsf-server/src/test/java/com/vincent/rsf/server/common/service/RedisServiceTest.java
@@ -0,0 +1,53 @@
+package com.vincent.rsf.server.common.service;
+
+import com.vincent.rsf.common.utils.Serialize;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import redis.clients.jedis.Jedis;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RedisServiceTest {
+
+ @Mock
+ private Jedis jedis;
+
+ private RedisService redisService;
+
+ @BeforeEach
+ void setUp() {
+ redisService = new RedisService() {
+ @Override
+ public Jedis getJedis() {
+ return jedis;
+ }
+ };
+ redisService.initialize = true;
+ }
+
+ @Test
+ void getClosesBorrowedJedisAfterReadingValue() {
+ when(jedis.get("MENU_TREE.FULL_TREE".getBytes())).thenReturn(Serialize.serialize("cached"));
+
+ String value = redisService.get("MENU_TREE", "FULL_TREE");
+
+ assertEquals("cached", value);
+ verify(jedis).close();
+ }
+
+ @Test
+ void deleteClosesBorrowedJedisAfterDeletingKey() {
+ when(jedis.del("MENU_TREE.FULL_TREE".getBytes())).thenReturn(1L);
+
+ Long deleted = redisService.delete("MENU_TREE", "FULL_TREE");
+
+ assertEquals(1L, deleted);
+ verify(jedis).close();
+ }
+}
--
Gitblit v1.9.1