zhou zhou
19 小时以前 1194038279d8a378f2ce7cbea59a32d753becbf8
feat: update rsf-design and redis integration
5个文件已添加
7个文件已修改
786 ■■■■ 已修改文件
rsf-design/.env.development 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/api/system-manage.js 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/locales/langs/en.json 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/locales/langs/zh.json 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/utils/router.js 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/system/menu/index.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/tests/system-manage-contract.test.mjs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/tests/system-menu-page-contract.test.mjs 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/tests/system-role-scope-contract.test.mjs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/tests/system-user-page-contract.test.mjs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/service/RedisService.java 252 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/test/java/com/vincent/rsf/server/common/service/RedisServiceTest.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/.env.development
@@ -7,7 +7,7 @@
VITE_API_URL = /
# 代理目标地址(开发环境通过 Vite 代理转发请求到此地址,解决跨域问题)
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
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
}
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",
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": "组托档明细",
    "waitPakinLog": "组托历史档",
    "waitPakinItemLog": "组托历史档明细",
    "task": "任务管理",
    "taskItem": "任务档明细",
    "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": "重置",
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
      }
    }
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',
@@ -249,7 +272,7 @@
    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)
rsf-design/tests/system-manage-contract.test.mjs
New file
@@ -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
    }
  )
})
rsf-design/tests/system-menu-page-contract.test.mjs
New file
@@ -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/)
})
rsf-design/tests/system-role-scope-contract.test.mjs
New file
@@ -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)
})
rsf-design/tests/system-user-page-contract.test.mjs
New file
@@ -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'
  )
})
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;
        });
    }
    // 为已存在的key设置过期时间 - 秒
@@ -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);
        }
        });
    }
    // 为已存在的key设置过期时间 - 具体到时间戳 (秒)
@@ -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));
    }
}
rsf-server/src/test/java/com/vincent/rsf/server/common/service/RedisServiceTest.java
New file
@@ -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();
    }
}