diff --git a/client-api/src/main/java/org/jiayunet/annotation/FuncPermission.java b/client-api/src/main/java/org/jiayunet/annotation/FuncPermission.java
new file mode 100644
index 0000000..7aeb865
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/annotation/FuncPermission.java
@@ -0,0 +1,22 @@
+package org.jiayunet.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 功能权限校验注解
+ *
标记在 Controller 方法上,切面会校验当前用户是否拥有该功能权限并扣减库存
+ *
+ * @author zk
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface FuncPermission {
+
+ /**
+ * 功能权限编码,对应 bg_func_permission.func_code
+ */
+ String value();
+}
diff --git a/client-api/src/main/java/org/jiayunet/aop/FuncPermissionAspect.java b/client-api/src/main/java/org/jiayunet/aop/FuncPermissionAspect.java
new file mode 100644
index 0000000..ff2468c
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/aop/FuncPermissionAspect.java
@@ -0,0 +1,46 @@
+package org.jiayunet.aop;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.jiayunet.annotation.FuncPermission;
+import org.jiayunet.server.FuncPermissionServer;
+import org.jiayunet.tool.UserSecurityTool;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 功能权限校验切面
+ * 拦截 @FuncPermission 注解,校验权限并扣减库存后放行
+ *
+ * @author zk
+ */
+@Aspect
+@Component
+@Slf4j
+public class FuncPermissionAspect {
+
+ @Autowired
+ private FuncPermissionServer funcPermissionServer;
+
+ @Around("@annotation(funcPermission)")
+ public Object check(ProceedingJoinPoint joinPoint, FuncPermission funcPermission) throws Throwable {
+
+ Long userId = UserSecurityTool.getUserId();
+ String funcCode = funcPermission.value();
+
+ log.info("功能权限校验 userId:{} funcCode:{}", userId, funcCode);
+
+ // 校验权限 + 扣减库存
+ funcPermissionServer.checkAndDeduct(userId, funcCode);
+
+ // 放行,业务异常时回退库存
+ try {
+ return joinPoint.proceed();
+ } catch (Exception e) {
+ funcPermissionServer.rollbackCount(userId, funcCode);
+ throw e;
+ }
+ }
+}
diff --git a/client-api/src/main/java/org/jiayunet/controller/RouteMenuController.java b/client-api/src/main/java/org/jiayunet/controller/RouteMenuController.java
new file mode 100644
index 0000000..2b23df6
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/controller/RouteMenuController.java
@@ -0,0 +1,33 @@
+package org.jiayunet.controller;
+
+import lombok.AllArgsConstructor;
+import org.jiayunet.pojo.vo.RouteMenuVo;
+import org.jiayunet.server.RouteMenuServer;
+import org.jiayunet.tool.UserSecurityTool;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 路由菜单控制类
+ *
+ * @author zk
+ */
+@RestController
+@RequestMapping("/route")
+@AllArgsConstructor
+public class RouteMenuController {
+
+ private RouteMenuServer routeMenuServer;
+
+ /**
+ * 获取当前用户有效路由菜单(树形结构)
+ */
+ @GetMapping("/menu")
+ public List getUserRoutes() {
+ Long userId = UserSecurityTool.getUserId();
+ return routeMenuServer.getUserRoutes(userId);
+ }
+}
diff --git a/client-api/src/main/java/org/jiayunet/pojo/vo/RouteMenuVo.java b/client-api/src/main/java/org/jiayunet/pojo/vo/RouteMenuVo.java
new file mode 100644
index 0000000..3234af4
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/pojo/vo/RouteMenuVo.java
@@ -0,0 +1,59 @@
+package org.jiayunet.pojo.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 路由菜单树形VO
+ *
+ * @author zk
+ */
+@Data
+public class RouteMenuVo {
+
+ /**
+ * 主键
+ */
+ private Long id;
+
+ /**
+ * 根节点ID
+ */
+ private Long rootId;
+
+ /**
+ * 父级路由ID
+ */
+ private Long parentId;
+
+ /**
+ * 路由名称
+ */
+ private String routeName;
+
+ /**
+ * 前端路径
+ */
+ private String routePath;
+
+ /**
+ * 前端组件路径
+ */
+ private String component;
+
+ /**
+ * 图标
+ */
+ private String icon;
+
+ /**
+ * 排序
+ */
+ private Integer sortOrder;
+
+ /**
+ * 子菜单
+ */
+ private List children;
+}
diff --git a/client-api/src/main/java/org/jiayunet/server/FuncPermissionServer.java b/client-api/src/main/java/org/jiayunet/server/FuncPermissionServer.java
new file mode 100644
index 0000000..77d5fce
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/server/FuncPermissionServer.java
@@ -0,0 +1,188 @@
+package org.jiayunet.server;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.jiayunet.exception.BusinessException;
+import org.jiayunet.exception.BusinessExpCodeEnum;
+import org.jiayunet.mapper.UserFuncPermissionStockMapper;
+import org.jiayunet.pojo.po.UserFuncPermissionStock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+
+/**
+ * 功能权限服务(校验、扣减、查询、添加库存)
+ *
+ * @author zk
+ */
+@Service
+@Slf4j
+public class FuncPermissionServer {
+
+ @Autowired
+ private UserFuncPermissionStockMapper userFuncPermissionStockMapper;
+
+ /**
+ * 校验用户功能权限并扣减库存
+ *
+ * @param userId 用户ID
+ * @param funcCode 功能权限编码
+ */
+ public void checkAndDeduct(Long userId, String funcCode) {
+ // 查询用户功能权限库存
+ UserFuncPermissionStock stock = userFuncPermissionStockMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(UserFuncPermissionStock::getUserId, userId)
+ .eq(UserFuncPermissionStock::getFuncCode, funcCode)
+ );
+
+ // 无记录,无权限
+ if (stock == null) {
+ throw new BusinessException(BusinessExpCodeEnum.PERMISSION_DENIED, "无该功能权限");
+ }
+
+ // 时间维度校验
+ if (stock.getTimeLimit() == 1 && stock.getExpireTime() != null && stock.getExpireTime().isBefore(Instant.now())) {
+ throw new BusinessException(BusinessExpCodeEnum.PERMISSION_DENIED, "功能权限已过期");
+ }
+
+ // 不限次,直接放行
+ if (stock.getCountLimit() == 0) {
+ return;
+ }
+
+ // 限次,扣减库存(乐观锁,SQL原子扣减)
+ int rows = userFuncPermissionStockMapper.update(null,
+ new LambdaUpdateWrapper()
+ .setSql("remain_count = remain_count - 1")
+ .eq(UserFuncPermissionStock::getUserId, userId)
+ .eq(UserFuncPermissionStock::getFuncCode, funcCode)
+ .gt(UserFuncPermissionStock::getRemainCount, 0)
+ );
+
+ if (rows == 0) {
+ throw new BusinessException(BusinessExpCodeEnum.PERMISSION_DENIED, "功能使用次数已用完");
+ }
+ }
+
+ /**
+ * 查询用户功能权限库存
+ *
+ * @param userId 用户ID
+ * @param funcCode 功能权限编码
+ * @return 库存记录,不存在返回null
+ */
+ public UserFuncPermissionStock query(Long userId, String funcCode) {
+ return userFuncPermissionStockMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(UserFuncPermissionStock::getUserId, userId)
+ .eq(UserFuncPermissionStock::getFuncCode, funcCode)
+ );
+ }
+
+ /**
+ * 添加或更新时间维度库存
+ *
+ * @param userId 用户ID
+ * @param funcCode 功能权限编码
+ * @param timeLimit 0=不限时 1=限时
+ * @param expireTime 过期时间,timeLimit=1时有值
+ */
+ public void addTimeStock(Long userId, String funcCode, Integer timeLimit, Instant expireTime) {
+ UserFuncPermissionStock existing = query(userId, funcCode);
+
+ if (existing == null) {
+ // 无记录,新增
+ UserFuncPermissionStock stock = new UserFuncPermissionStock();
+ stock.setUserId(userId);
+ stock.setFuncCode(funcCode);
+ stock.setTimeLimit(timeLimit);
+ stock.setExpireTime(expireTime);
+ userFuncPermissionStockMapper.insert(stock);
+ return;
+ }
+
+ // 已经不限时,不降级
+ if (existing.getTimeLimit() == 0) {
+ return;
+ }
+
+ LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper()
+ .eq(UserFuncPermissionStock::getId, existing.getId());
+
+ if (timeLimit == 0) {
+ // 升级为不限时
+ wrapper.set(UserFuncPermissionStock::getTimeLimit, 0)
+ .set(UserFuncPermissionStock::getExpireTime, null);
+ } else {
+ // 限时,取最远过期时间
+ Instant finalExpire = existing.getExpireTime() == null || expireTime.isAfter(existing.getExpireTime())
+ ? expireTime : existing.getExpireTime();
+ wrapper.set(UserFuncPermissionStock::getExpireTime, finalExpire);
+ }
+
+ userFuncPermissionStockMapper.update(null, wrapper);
+ }
+
+ /**
+ * 添加或更新次数维度库存
+ *
+ * @param userId 用户ID
+ * @param funcCode 功能权限编码
+ * @param countLimit 0=不限次 1=限次
+ * @param addCount 新增次数,countLimit=1时有值
+ */
+ public void addCountStock(Long userId, String funcCode, Integer countLimit, Integer addCount) {
+ UserFuncPermissionStock existing = query(userId, funcCode);
+
+ if (existing == null) {
+ // 无记录,新增
+ UserFuncPermissionStock stock = new UserFuncPermissionStock();
+ stock.setUserId(userId);
+ stock.setFuncCode(funcCode);
+ stock.setCountLimit(countLimit);
+ stock.setRemainCount(addCount);
+ userFuncPermissionStockMapper.insert(stock);
+ return;
+ }
+
+ // 已经不限次,不降级
+ if (existing.getCountLimit() == 0) {
+ return;
+ }
+
+ LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper()
+ .eq(UserFuncPermissionStock::getId, existing.getId());
+
+ if (countLimit == 0) {
+ // 升级为不限次
+ wrapper.set(UserFuncPermissionStock::getCountLimit, 0)
+ .set(UserFuncPermissionStock::getRemainCount, null);
+ } else {
+ // 限次,累加
+ int newCount = (existing.getRemainCount() == null ? 0 : existing.getRemainCount()) + addCount;
+ wrapper.set(UserFuncPermissionStock::getRemainCount, newCount);
+ }
+
+ userFuncPermissionStockMapper.update(null, wrapper);
+ }
+
+ /**
+ * 回退次数库存(业务异常时调用,仅限次维度有效)
+ *
+ * @param userId 用户ID
+ * @param funcCode 功能权限编码
+ */
+ public void rollbackCount(Long userId, String funcCode) {
+ userFuncPermissionStockMapper.update(null,
+ new LambdaUpdateWrapper()
+ .setSql("remain_count = remain_count + 1")
+ .eq(UserFuncPermissionStock::getUserId, userId)
+ .eq(UserFuncPermissionStock::getFuncCode, funcCode)
+ .eq(UserFuncPermissionStock::getCountLimit, 1)
+ );
+ }
+
+}
diff --git a/client-api/src/main/java/org/jiayunet/server/RouteMenuServer.java b/client-api/src/main/java/org/jiayunet/server/RouteMenuServer.java
new file mode 100644
index 0000000..3203739
--- /dev/null
+++ b/client-api/src/main/java/org/jiayunet/server/RouteMenuServer.java
@@ -0,0 +1,169 @@
+package org.jiayunet.server;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.jiayunet.mapper.RouteMenuMapper;
+import org.jiayunet.mapper.UserRouteMenuStockMapper;
+import org.jiayunet.pojo.po.RouteMenu;
+import org.jiayunet.pojo.po.UserRouteMenuStock;
+import org.jiayunet.pojo.vo.RouteMenuVo;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 路由菜单服务(查询、添加库存、获取用户菜单)
+ *
+ * @author zk
+ */
+@Service
+@Slf4j
+public class RouteMenuServer {
+
+ @Autowired
+ private UserRouteMenuStockMapper userRouteMenuStockMapper;
+
+ @Autowired
+ private RouteMenuMapper routeMenuMapper;
+
+ /**
+ * 查询用户某个路由的库存
+ *
+ * @param userId 用户ID
+ * @param routeId 路由ID
+ * @return 库存记录,不存在返回null
+ */
+ public UserRouteMenuStock query(Long userId, Long routeId) {
+ return userRouteMenuStockMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(UserRouteMenuStock::getUserId, userId)
+ .eq(UserRouteMenuStock::getRouteId, routeId)
+ );
+ }
+
+ /**
+ * 添加或更新时间维度库存
+ *
+ * @param userId 用户ID
+ * @param routeId 路由ID
+ * @param timeLimit 0=不限时 1=限时
+ * @param expireTime 过期时间,timeLimit=1时有值
+ */
+ public void addTimeStock(Long userId, Long routeId, Integer timeLimit, Instant expireTime) {
+ UserRouteMenuStock existing = query(userId, routeId);
+
+ if (existing == null) {
+ UserRouteMenuStock stock = new UserRouteMenuStock();
+ stock.setUserId(userId);
+ stock.setRouteId(routeId);
+ stock.setTimeLimit(timeLimit);
+ stock.setExpireTime(expireTime);
+ userRouteMenuStockMapper.insert(stock);
+ return;
+ }
+
+ // 已经不限时,不降级
+ if (existing.getTimeLimit() == 0) {
+ return;
+ }
+
+ LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper()
+ .eq(UserRouteMenuStock::getId, existing.getId());
+
+ if (timeLimit == 0) {
+ // 升级为不限时
+ wrapper.set(UserRouteMenuStock::getTimeLimit, 0)
+ .set(UserRouteMenuStock::getExpireTime, null);
+ } else {
+ // 限时,取最远过期时间
+ Instant finalExpire = existing.getExpireTime() == null || expireTime.isAfter(existing.getExpireTime())
+ ? expireTime : existing.getExpireTime();
+ wrapper.set(UserRouteMenuStock::getExpireTime, finalExpire);
+ }
+
+ userRouteMenuStockMapper.update(null, wrapper);
+ }
+
+ /**
+ * 获取用户有效路由菜单(树形结构,支持多级菜单)
+ *
+ * @param userId 用户ID
+ * @return 树形路由菜单
+ */
+ public List getUserRoutes(Long userId) {
+ // 查询用户有效库存(不限时 或 未过期)
+ Instant now = Instant.now();
+ List stocks = userRouteMenuStockMapper.selectList(
+ new LambdaQueryWrapper()
+ .eq(UserRouteMenuStock::getUserId, userId)
+ .and(w -> w
+ .eq(UserRouteMenuStock::getTimeLimit, 0)
+ .or()
+ .gt(UserRouteMenuStock::getExpireTime, now)
+ )
+ );
+
+ if (stocks == null || stocks.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ Set validRouteIds = stocks.stream()
+ .map(UserRouteMenuStock::getRouteId)
+ .collect(Collectors.toSet());
+
+ if (validRouteIds.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ // 一次查出所有启用的菜单
+ List allMenus = routeMenuMapper.selectList( new LambdaQueryWrapper().eq(RouteMenu::getStatus, 1));
+
+ if (allMenus.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ // 收集有权限的节点及其所有父节点ID
+ Map menuMap = allMenus.stream().collect(Collectors.toMap(RouteMenu::getId, m -> m));
+
+ Set needIds = new HashSet<>(validRouteIds);
+ for (Long routeId : validRouteIds) {
+ RouteMenu current = menuMap.get(routeId);
+ while (current != null && current.getParentId() != 0) {
+ needIds.add(current.getParentId());
+ current = menuMap.get(current.getParentId());
+ }
+ }
+
+ // 转VO
+ List voList = allMenus.stream()
+ .filter(menu -> needIds.contains(menu.getId()))
+ .map(menu -> {
+ RouteMenuVo vo = new RouteMenuVo();
+ BeanUtils.copyProperties(menu, vo);
+ return vo;
+ })
+ .collect(Collectors.toList());
+
+ // 构建树形结构
+ Map> parentMap = voList.stream()
+ .collect(Collectors.groupingBy(RouteMenuVo::getParentId));
+
+ voList.forEach(vo -> {
+ List children = parentMap.get(vo.getId());
+ if (children != null) {
+ children.sort(Comparator.comparingInt(RouteMenuVo::getSortOrder));
+ vo.setChildren(children);
+ }
+ });
+
+ return voList.stream()
+ .filter(vo -> vo.getParentId() == 0)
+ .sorted(Comparator.comparingInt(RouteMenuVo::getSortOrder))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/manager/src/main/java/org/jiayunet/mapper/FuncPermissionMapper.java b/manager/src/main/java/org/jiayunet/mapper/FuncPermissionMapper.java
new file mode 100644
index 0000000..291b41b
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/mapper/FuncPermissionMapper.java
@@ -0,0 +1,14 @@
+package org.jiayunet.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.jiayunet.pojo.po.FuncPermission;
+
+/**
+ * 功能权限Mapper
+ *
+ * @author zk
+ */
+@Mapper
+public interface FuncPermissionMapper extends CommonMapper {
+
+}
diff --git a/manager/src/main/java/org/jiayunet/mapper/RouteMenuMapper.java b/manager/src/main/java/org/jiayunet/mapper/RouteMenuMapper.java
new file mode 100644
index 0000000..cd5ff91
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/mapper/RouteMenuMapper.java
@@ -0,0 +1,14 @@
+package org.jiayunet.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.jiayunet.pojo.po.RouteMenu;
+
+/**
+ * 路由菜单Mapper
+ *
+ * @author zk
+ */
+@Mapper
+public interface RouteMenuMapper extends CommonMapper {
+
+}
diff --git a/manager/src/main/java/org/jiayunet/mapper/UserFuncPermissionStockMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserFuncPermissionStockMapper.java
new file mode 100644
index 0000000..2a5407d
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/mapper/UserFuncPermissionStockMapper.java
@@ -0,0 +1,14 @@
+package org.jiayunet.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.jiayunet.pojo.po.UserFuncPermissionStock;
+
+/**
+ * 用户功能权限库存Mapper
+ *
+ * @author zk
+ */
+@Mapper
+public interface UserFuncPermissionStockMapper extends CommonMapper {
+
+}
diff --git a/manager/src/main/java/org/jiayunet/mapper/UserRouteMenuStockMapper.java b/manager/src/main/java/org/jiayunet/mapper/UserRouteMenuStockMapper.java
new file mode 100644
index 0000000..7e51982
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/mapper/UserRouteMenuStockMapper.java
@@ -0,0 +1,14 @@
+package org.jiayunet.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.jiayunet.pojo.po.UserRouteMenuStock;
+
+/**
+ * 用户路由菜单库存Mapper
+ *
+ * @author zk
+ */
+@Mapper
+public interface UserRouteMenuStockMapper extends CommonMapper {
+
+}
diff --git a/manager/src/main/java/org/jiayunet/pojo/po/FuncPermission.java b/manager/src/main/java/org/jiayunet/pojo/po/FuncPermission.java
new file mode 100644
index 0000000..3570f56
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/pojo/po/FuncPermission.java
@@ -0,0 +1,53 @@
+package org.jiayunet.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.Instant;
+
+/**
+ * 功能权限表
+ *
+ * @author zk
+ */
+@Data
+@TableName(value = "bg_func_permission")
+public class FuncPermission {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 权限编码,最长12个字符
+ */
+ private String funcCode;
+
+ /**
+ * 功能名称
+ */
+ private String funcName;
+
+ /**
+ * 状态 1=启用 0=禁用
+ */
+ private Integer status;
+
+ /**
+ * 创建时间
+ */
+ private Instant createTime;
+
+ /**
+ * 修改时间
+ */
+ private Instant updateTime;
+
+ /**
+ * 删除标识 0正常 非0删除
+ */
+ @TableLogic(value = "0", delval = "new()")
+ private Long isDelete;
+}
diff --git a/manager/src/main/java/org/jiayunet/pojo/po/RouteMenu.java b/manager/src/main/java/org/jiayunet/pojo/po/RouteMenu.java
new file mode 100644
index 0000000..cdc7575
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/pojo/po/RouteMenu.java
@@ -0,0 +1,78 @@
+package org.jiayunet.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.Instant;
+
+/**
+ * 路由菜单表
+ *
+ * @author zk
+ */
+@Data
+@TableName(value = "bg_route_menu")
+public class RouteMenu {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 根节点ID
+ */
+ private Long rootId;
+
+ /**
+ * 父级路由ID
+ */
+ private Long parentId;
+
+ /**
+ * 路由名称
+ */
+ private String routeName;
+
+ /**
+ * 前端路径
+ */
+ private String routePath;
+
+ /**
+ * 前端组件路径
+ */
+ private String component;
+
+ /**
+ * 图标
+ */
+ private String icon;
+
+ /**
+ * 排序
+ */
+ private Integer sortOrder;
+
+ /**
+ * 状态 1=启用 0=禁用
+ */
+ private Integer status;
+
+ /**
+ * 创建时间
+ */
+ private Instant createTime;
+
+ /**
+ * 修改时间
+ */
+ private Instant updateTime;
+
+ /**
+ * 删除标识 0正常 非0删除
+ */
+ @TableLogic(value = "0", delval = "new()")
+ private Long isDelete;
+}
diff --git a/manager/src/main/java/org/jiayunet/pojo/po/UserFuncPermissionStock.java b/manager/src/main/java/org/jiayunet/pojo/po/UserFuncPermissionStock.java
new file mode 100644
index 0000000..daaa8ad
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/pojo/po/UserFuncPermissionStock.java
@@ -0,0 +1,61 @@
+package org.jiayunet.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.Instant;
+
+/**
+ * 用户功能权限库存表
+ *
+ * @author zk
+ */
+@Data
+@TableName(value = "bg_user_func_permission_stock")
+public class UserFuncPermissionStock {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 权限编码
+ */
+ private String funcCode;
+
+ /**
+ * 0=不限时 1=限时
+ */
+ private Integer timeLimit;
+
+ /**
+ * 0=不限次 1=限次
+ */
+ private Integer countLimit;
+
+ /**
+ * 过期时间,timeLimit=1时有值
+ */
+ private Instant expireTime;
+
+ /**
+ * 剩余次数,countLimit=1时有值
+ */
+ private Integer remainCount;
+
+ /**
+ * 创建时间
+ */
+ private Instant createTime;
+
+ /**
+ * 修改时间
+ */
+ private Instant updateTime;
+}
diff --git a/manager/src/main/java/org/jiayunet/pojo/po/UserRouteMenuStock.java b/manager/src/main/java/org/jiayunet/pojo/po/UserRouteMenuStock.java
new file mode 100644
index 0000000..473a129
--- /dev/null
+++ b/manager/src/main/java/org/jiayunet/pojo/po/UserRouteMenuStock.java
@@ -0,0 +1,51 @@
+package org.jiayunet.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.Instant;
+
+/**
+ * 用户路由菜单库存表
+ *
+ * @author zk
+ */
+@Data
+@TableName(value = "bg_user_route_menu_stock")
+public class UserRouteMenuStock {
+
+ @TableId(type = IdType.ASSIGN_ID)
+ private Long id;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 关联菜单ID
+ */
+ private Long routeId;
+
+ /**
+ * 0=不限时 1=限时
+ */
+ private Integer timeLimit;
+
+ /**
+ * 过期时间,timeLimit=1时有值
+ */
+ private Instant expireTime;
+
+ /**
+ * 创建时间
+ */
+ private Instant createTime;
+
+ /**
+ * 修改时间
+ */
+ private Instant updateTime;
+}