feat(system): 实现基于Session的认证与菜单管理功能

- 菜单图标字段从icon改为vueIcon,适配前端图标组件
- 登录认证方式改为Session认证,使用FormData格式提交数据
- 验证码获取方式改为直接图片URL,防止缓存问题
- 权限验证改为基于Session的用户角色判断
- axios请求配置支持withCredentials,允许携带cookie
- 部门管理新增树形结构构建方法
- 菜单管理新增路由地址、组件路径等字段
- 字典管理新增根据字典类型查询数据接口
- 系统配置新增根据配置键查询接口
- 首页控制器新增用户信息和路由信息获取接口- 菜单控制器新增菜单列表、详情、增删改查接口
- 数据库菜单表新增path、component、query、status、vue_icon、is_frame字段
dev_1.0.0
chenhao 2025-11-11 15:41:07 +08:00
parent ffe6ce25df
commit bbe760abf6
21 changed files with 707 additions and 155 deletions

View File

@ -1,21 +1,21 @@
import request from '@/utils/request'
// 登录方法
export function login(username, password, code, uuid) {
const data = {
username,
password,
code,
uuid
}
// 登录方法 - 基于 Session 认证,使用 FormData 格式
export function login(username, password, code, rememberMe) {
const formData = new FormData()
formData.append('username', username)
formData.append('password', password)
formData.append('validateCode', code)
formData.append('rememberMe', rememberMe || false)
return request({
url: '/login',
headers: {
isToken: false,
'Content-Type': 'multipart/form-data',
repeatSubmit: false
},
method: 'post',
data: data
data: formData
})
}
@ -39,22 +39,10 @@ export function getInfo() {
})
}
// 退出方法
// 退出方法 - 基于 Session 认证
export function logout() {
return request({
url: '/logout',
method: 'post'
})
}
// 获取验证码
export function getCodeImg() {
return request({
url: '/captchaImage',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}

View File

@ -3,7 +3,6 @@ import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isPathMatch } from '@/utils/validate'
import { isRelogin } from '@/utils/request'
@ -17,43 +16,37 @@ const isWhiteList = (path) => {
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else if (isWhiteList(to.path)) {
next()
} else {
if (store.getters.roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
// 基于 Session 认证:通过是否有角色信息来判断登录状态
const hasRoles = store.getters.roles && store.getters.roles.length > 0
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
if (isWhiteList(to.path)) {
// 在免登录白名单,直接进入
next()
NProgress.done()
} else {
// 没有token
if (isWhiteList(to.path)) {
// 在免登录白名单,直接进入
if (hasRoles) {
// 已有用户信息,放行
next()
} else {
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) // 否则全部重定向到登录页
NProgress.done()
// 没有用户信息,尝试获取
isRelogin.show = true
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
// 获取用户信息失败,说明未登录或 Session 已过期
store.dispatch('FedLogOut').then(() => {
Message.error(err || '未登录或登录超时,请重新登录')
next(`/login?redirect=${encodeURIComponent(to.fullPath)}`)
NProgress.done()
})
})
}
}
})

View File

@ -1,13 +1,11 @@
import router from '@/router'
import { MessageBox, } from 'element-ui'
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { isHttp, isEmpty } from "@/utils/validate"
import defAva from '@/assets/images/profile.jpg'
const user = {
state: {
token: getToken(),
id: '',
name: '',
nickName: '',
@ -17,9 +15,6 @@ const user = {
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_ID: (state, id) => {
state.id = id
},
@ -41,16 +36,14 @@ const user = {
},
actions: {
// 登录
// 登录 - 基于 Session 认证,不需要保存 token
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
const rememberMe = userInfo.rememberMe
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
setToken(res.token)
commit('SET_TOKEN', res.token)
login(username, password, code, rememberMe).then(res => {
resolve()
}).catch(error => {
reject(error)
@ -62,14 +55,16 @@ const user = {
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.user
// 后端返回的数据在 res.data 中
const data = res.data || res
const user = data.user
let avatar = user.avatar || ""
if (!isHttp(avatar)) {
avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_BASE_API + avatar
}
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', data.roles)
commit('SET_PERMISSIONS', data.permissions)
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
@ -78,13 +73,13 @@ const user = {
commit('SET_NICK_NAME', user.nickName)
commit('SET_AVATAR', avatar)
/* 初始密码提示 */
if(res.isDefaultModifyPwd) {
if(data.isDefaultModifyPwd) {
MessageBox.confirm('您的密码还是初始密码,请修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
}).catch(() => {})
}
/* 过期密码提示 */
if(!res.isDefaultModifyPwd && res.isPasswordExpired) {
if(!data.isDefaultModifyPwd && data.isPasswordExpired) {
MessageBox.confirm('您的密码已过期,请尽快修改密码!', '安全提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => {
router.push({ name: 'Profile', params: { activeTab: 'resetPwd' } })
}).catch(() => {})
@ -96,14 +91,12 @@ const user = {
})
},
// 退出系统
// 退出系统 - 基于 Session 认证
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
logout().then(() => {
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
resolve()
}).catch(error => {
reject(error)
@ -111,11 +104,11 @@ const user = {
})
},
// 前端 登出
// 前端 登出 - 清除状态
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
removeToken()
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
resolve()
})
}

View File

@ -1,7 +1,6 @@
import axios from 'axios'
import { Notification, MessageBox, Message, Loading } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from "@/utils/ruoyi"
import cache from '@/plugins/cache'
@ -17,18 +16,17 @@ const service = axios.create({
// axios中请求配置有baseURL选项表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
timeout: 10000,
// 允许携带cookieSession认证必需
withCredentials: true
})
// request拦截器
service.interceptors.request.use(config => {
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
config.headers['X-Requested-With'] = 'H5'
// Session 认证依赖 Cookie无需手动设置 token
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params)
@ -39,7 +37,7 @@ service.interceptors.request.use(config => {
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
data: (typeof config.data === 'object' && !(config.data instanceof FormData)) ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小
@ -79,7 +77,7 @@ service.interceptors.response.use(res => {
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
return res
}
if (code === 401) {
if (!isRelogin.show) {

View File

@ -62,7 +62,6 @@
</template>
<script>
import { getCodeImg } from "@/api/login"
import Cookies from "js-cookie"
import { encrypt, decrypt } from '@/utils/jsencrypt'
@ -74,10 +73,9 @@ export default {
codeUrl: "",
loginForm: {
username: "admin",
password: "admin123",
password: "admin@123",
rememberMe: false,
code: "",
uuid: ""
code: ""
},
loginRules: {
username: [
@ -110,13 +108,10 @@ export default {
},
methods: {
getCode() {
getCodeImg().then(res => {
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (this.captchaEnabled) {
this.codeUrl = "data:image/gif;base64," + res.img
this.loginForm.uuid = res.uuid
}
})
// Session 使 URL
const timestamp = new Date().getTime()
this.codeUrl = `${process.env.VUE_APP_BASE_API}/captcha/captchaImage?type=math&s=${timestamp}`
this.captchaEnabled = true
},
getCookie() {
const username = Cookies.get("username")

View File

@ -57,9 +57,9 @@
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
<el-table-column prop="icon" label="图标" align="center" width="100">
<el-table-column prop="vueIcon" label="图标" align="center" width="100">
<template slot-scope="scope">
<svg-icon :icon-class="scope.row.icon" />
<svg-icon :icon-class="scope.row.vueIcon" />
</template>
</el-table-column>
<el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
@ -131,19 +131,19 @@
</el-row>
<el-row>
<el-col :span="12" v-if="form.menuType != 'F'">
<el-form-item label="菜单图标" prop="icon">
<el-form-item label="菜单图标" prop="vueIcon">
<el-popover
placement="bottom-start"
width="460"
trigger="click"
@show="$refs['iconSelect'].reset()"
>
<IconSelect ref="iconSelect" @selected="selected" :active-icon="form.icon" />
<el-input slot="reference" v-model="form.icon" placeholder="点击选择图标" readonly>
<IconSelect ref="iconSelect" @selected="selected" :active-icon="form.vueIcon" />
<el-input slot="reference" v-model="form.vueIcon" placeholder="点击选择图标" readonly>
<svg-icon
v-if="form.icon"
v-if="form.vueIcon"
slot="prefix"
:icon-class="form.icon"
:icon-class="form.vueIcon"
style="width: 25px;"
/>
<i v-else slot="prefix" class="el-icon-search el-input__icon" />
@ -353,7 +353,7 @@ export default {
methods: {
//
selected(name) {
this.form.icon = name
this.form.vueIcon = name
},
/** 查询菜单列表 */
getList() {

View File

@ -555,4 +555,4 @@ export default {
}
}
}
</script>
</script>

View File

@ -9,7 +9,7 @@ const CompressionPlugin = require('compression-webpack-plugin')
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题
const baseUrl = 'http://localhost:8080' // 后端接口
const baseUrl = 'http://localhost:28080' // 后端接口
const port = process.env.port || process.env.npm_config_port || 80 // 端口
@ -38,6 +38,10 @@ module.exports = {
[process.env.VUE_APP_BASE_API]: {
target: baseUrl,
changeOrigin: true,
// Session 认证需要携带 Cookie
cookieDomainRewrite: {
'*': ''
},
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}

View File

@ -8,6 +8,8 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ -48,6 +50,7 @@ public class SysCaptchaController extends BaseController
response.setContentType("image/jpeg");
String type = request.getParameter("type");
type = StringUtils.isEmpty(type) ? "math" : type;
String capStr = null;
String code = null;
BufferedImage bi = null;

View File

@ -155,4 +155,11 @@ public class SysConfigController extends BaseController
{
return configService.checkConfigKeyUnique(config);
}
@GetMapping("/configKey/{key}")
@ResponseBody
public AjaxResult queryKey(@PathVariable("key") String key)
{
return success(configService.selectConfigByKey(key));
}
}

View File

@ -1,6 +1,10 @@
package com.ruoyi.web.controller.system;
import java.util.ArrayList;
import java.util.List;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysDictTypeService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@ -33,7 +37,8 @@ public class SysDictDataController extends BaseController
@Autowired
private ISysDictDataService dictDataService;
@Autowired
private ISysDictTypeService dictTypeService;
@RequiresPermissions("system:dict:view")
@GetMapping()
public String dictData()
@ -72,6 +77,22 @@ public class SysDictDataController extends BaseController
mmap.put("dictType", dictType);
return prefix + "/add";
}
/**
*
*/
@GetMapping("/type/{dictType}")
@ResponseBody
public AjaxResult add(@PathVariable("dictType") String dictType)
{
List<SysDictData> data = dictTypeService.selectDictDataByType(dictType);
if (StringUtils.isNull(data))
{
data = new ArrayList<SysDictData>();
}
return success(data);
}
/**
*

View File

@ -1,21 +1,11 @@
package com.ruoyi.web.controller.system;
import java.util.Date;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.ShiroConstants;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.utils.CookieUtils;
@ -25,6 +15,18 @@ import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.shiro.service.SysPasswordService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysMenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.stream.Collectors;
/**
*
@ -39,7 +41,6 @@ public class SysIndexController extends BaseController
@Autowired
private ISysConfigService configService;
@Autowired
private SysPasswordService passwordService;
@GetMapping("/test")
@ -178,4 +179,164 @@ public class SysIndexController extends BaseController
}
return false;
}
/**
*
*
* @return
*/
@GetMapping("/getInfo")
@ResponseBody
public AjaxResult getInfo()
{
SysUser user = getSysUser();
if (StringUtils.isNull(user))
{
return AjaxResult.error("未登录或登录超时");
}
// 角色集合
Set<String> roles = user.getRoles().stream().map(SysRole::getRoleKey).collect(Collectors.toSet());
// 权限集合
Set<String> permissions = menuService.selectPermsByUserId(user.getUserId());
Map<String, Object> data = new HashMap<>();
data.put("user", user);
data.put("roles", roles);
data.put("permissions", permissions);
data.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate()));
data.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate()));
return AjaxResult.success(data);
}
/**
*
*
* @return
*/
@GetMapping("/getRouters")
@ResponseBody
public AjaxResult getRouters()
{
SysUser user = getSysUser();
if (StringUtils.isNull(user))
{
return AjaxResult.error("未登录或登录超时");
}
List<SysMenu> menus = menuService.selectMenusByUser(user);
List<Map<String, Object>> routerList = buildRouters(menus);
return AjaxResult.success(routerList);
}
/**
* 使
*/
private List<Map<String, Object>> buildRouters(List<SysMenu> menus)
{
List<Map<String, Object>> routers = new ArrayList<>();
for (SysMenu menu : menus)
{
Map<String, Object> router = new HashMap<>();
// 基本属性
router.put("name", getRouteName(menu));
router.put("path", getRouterPath(menu));
router.put("hidden", "1".equals(menu.getVisible()));
router.put("component", getComponent(menu));
// 路由元信息
router.put("meta", buildMeta(menu));
// 子路由
List<SysMenu> cMenus = menu.getChildren();
if (cMenus != null && !cMenus.isEmpty())
{
router.put("alwaysShow", true);
router.put("redirect", "noRedirect");
router.put("children", buildRouters(cMenus));
}
routers.add(router);
}
return routers;
}
/**
*
*/
private Map<String, Object> buildMeta(SysMenu menu)
{
Map<String, Object> meta = new HashMap<>();
meta.put("title", menu.getMenuName());
meta.put("icon", menu.getVueIcon());
meta.put("noCache", "1".equals(menu.getIsRefresh()));
return meta;
}
/**
*
*/
private String getRouteName(SysMenu menu)
{
// 使用 path 字段作为路由名称,如果没有则使用 menuName
String routerName = menu.getPath();
if (StringUtils.isEmpty(routerName))
{
routerName = menu.getMenuName();
}
// 首字母大写
return StringUtils.isNotEmpty(routerName) ? StringUtils.capitalize(routerName) : "";
}
/**
*
*/
private String getRouterPath(SysMenu menu)
{
// 优先使用 path 字段,如果没有则使用 url 字段
String routerPath = menu.getPath();
if (StringUtils.isEmpty(routerPath))
{
routerPath = menu.getUrl();
}
// 一级目录需要加 /
if (menu.getParentId() == 0 && "M".equals(menu.getMenuType()))
{
if (!routerPath.startsWith("/"))
{
routerPath = "/" + routerPath;
}
}
return StringUtils.isNotEmpty(routerPath) ? routerPath : "";
}
/**
*
*/
private String getComponent(SysMenu menu)
{
// 目录使用 Layout
if ("M".equals(menu.getMenuType()))
{
if (menu.getParentId() == 0)
{
return "Layout";
}
else
{
return "ParentView";
}
}
// 菜单使用 url 字段作为组件路径
else if ("C".equals(menu.getMenuType()))
{
String component = menu.getComponent();
return StringUtils.isNotEmpty(component) ? component : menu.getPath();
}
return "Layout";
}
}

View File

@ -6,11 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
@ -52,6 +48,15 @@ public class SysMenuController extends BaseController
List<SysMenu> menuList = menuService.selectMenuList(menu, userId);
return menuList;
}
@RequiresPermissions("system:menu:list")
@GetMapping("/list")
@ResponseBody
public AjaxResult listMenu(SysMenu menu)
{
Long userId = ShiroUtils.getUserId();
List<SysMenu> menuList = menuService.selectMenuList(menu, userId);
return AjaxResult.success(menuList);
}
/**
*
@ -73,6 +78,23 @@ public class SysMenuController extends BaseController
AuthorizationUtils.clearAllCachedAuthorizationInfo();
return toAjax(menuService.deleteMenuById(menuId));
}
@Log(title = "菜单管理", businessType = BusinessType.DELETE)
@RequiresPermissions("system:menu:remove")
@DeleteMapping("/{menuId}")
@ResponseBody
public AjaxResult delete(@PathVariable("menuId") Long menuId)
{
if (menuService.selectCountMenuByParentId(menuId) > 0)
{
return AjaxResult.warn("存在子菜单,不允许删除");
}
if (menuService.selectCountRoleMenuByMenuId(menuId) > 0)
{
return AjaxResult.warn("菜单已分配,不允许删除");
}
AuthorizationUtils.clearAllCachedAuthorizationInfo();
return toAjax(menuService.deleteMenuById(menuId));
}
/**
*
@ -113,6 +135,20 @@ public class SysMenuController extends BaseController
AuthorizationUtils.clearAllCachedAuthorizationInfo();
return toAjax(menuService.insertMenu(menu));
}
@Log(title = "菜单管理", businessType = BusinessType.INSERT)
@RequiresPermissions("system:menu:add")
@PostMapping()
@ResponseBody
public AjaxResult insert(@RequestBody @Validated SysMenu menu)
{
if (!menuService.checkMenuNameUnique(menu))
{
return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
menu.setCreateBy(getLoginName());
AuthorizationUtils.clearAllCachedAuthorizationInfo();
return toAjax(menuService.insertMenu(menu));
}
/**
*
@ -124,6 +160,30 @@ public class SysMenuController extends BaseController
mmap.put("menu", menuService.selectMenuById(menuId));
return prefix + "/edit";
}
@RequiresPermissions("system:menu:edit")
@GetMapping("/{menuId}")
@ResponseBody
public AjaxResult query(@PathVariable("menuId") Long menuId)
{
return AjaxResult.success(menuService.selectMenuById(menuId));
}
@Log(title = "菜单管理", businessType = BusinessType.UPDATE)
@RequiresPermissions("system:menu:edit")
@PutMapping()
@ResponseBody
public AjaxResult edit(@RequestBody @Validated SysMenu menu)
{
if (!menuService.checkMenuNameUnique(menu))
{
return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
}
menu.setUpdateBy(getLoginName());
AuthorizationUtils.clearAllCachedAuthorizationInfo();
return toAjax(menuService.updateMenu(menu));
}
/**
*
@ -195,4 +255,17 @@ public class SysMenuController extends BaseController
mmap.put("menu", menuService.selectMenuById(menuId));
return prefix + "/tree";
}
/**
*
*
* @return
*/
@GetMapping("/getRouters")
@ResponseBody
public AjaxResult getRouters()
{
List<SysMenu> menus = menuService.selectMenusByUser(getSysUser());
return AjaxResult.success(menus);
}
}

View File

@ -363,4 +363,31 @@ public class SysUserController extends BaseController
mmap.put("dept", value==null?new SysDept():value);
return prefix + "/deptTree";
}
// ----------------------VUE页面使用---------------------------
@RequiresPermissions("system:user:list")
@GetMapping("/list")
@ResponseBody
public TableDataInfo listPage(SysUser user) {
startPage();
List<SysUser> list = userService.selectUserList(user);
return getDataTable(list);
}
@RequiresPermissions("system:user:list")
@GetMapping("/{userId}")
@ResponseBody
public AjaxResult query(@PathVariable("userId") Long userId) {
return success(userService.selectUserById(userId));
}
/**
*
*/
@RequiresPermissions("system:user:list")
@GetMapping("/deptTree")
@ResponseBody
public AjaxResult deptTree(SysDept dept) {
return success(deptService.selectDeptTreeList(dept));
}
}

View File

@ -0,0 +1,94 @@
package com.ruoyi.common.core.domain;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.utils.StringUtils;
import java.io.Serializable;
import java.util.List;
import java.util.stream.Collectors;
/**
* Treeselect
*
* @author ruoyi
*/
public class TreeSelect implements Serializable
{
private static final long serialVersionUID = 1L;
/** 节点ID */
private Long id;
/** 节点名称 */
private String label;
/** 节点禁用 */
private boolean disabled = false;
/** 子节点 */
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<TreeSelect> children;
public TreeSelect()
{
}
public TreeSelect(SysDept dept)
{
this.id = dept.getDeptId();
this.label = dept.getDeptName();
this.disabled = StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus());
this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
}
public TreeSelect(SysMenu menu)
{
this.id = menu.getMenuId();
this.label = menu.getMenuName();
this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList());
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getLabel()
{
return label;
}
public void setLabel(String label)
{
this.label = label;
}
public boolean isDisabled()
{
return disabled;
}
public void setDisabled(boolean disabled)
{
this.disabled = disabled;
}
public List<TreeSelect> getChildren()
{
return children;
}
public void setChildren(List<TreeSelect> children)
{
this.children = children;
}
}

View File

@ -9,6 +9,9 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.ruoyi.common.core.domain.BaseEntity;
import java.util.ArrayList;
import java.util.List;
/**
* sys_dept
*
@ -53,7 +56,8 @@ public class SysDept extends BaseEntity
/** 排除编号 */
private Long excludeId;
/** 子部门 */
private List<SysDept> children = new ArrayList<SysDept>();
public Long getDeptId()
{
return deptId;
@ -181,6 +185,14 @@ public class SysDept extends BaseEntity
this.excludeId = excludeId;
}
public List<SysDept> getChildren() {
return children;
}
public void setChildren(List<SysDept> children) {
this.children = children;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)

View File

@ -3,6 +3,9 @@ package com.ruoyi.common.core.domain.entity;
import java.util.List;
import java.util.ArrayList;
import javax.validation.constraints.*;
import lombok.Data;
import lombok.ToString;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.domain.BaseEntity;
@ -12,6 +15,7 @@ import com.ruoyi.common.core.domain.BaseEntity;
*
* @author ruoyi
*/
@ToString
public class SysMenu extends BaseEntity
{
private static final long serialVersionUID = 1L;
@ -52,6 +56,23 @@ public class SysMenu extends BaseEntity
/** 菜单图标 */
private String icon;
/** 路由地址 */
private String path;
/** 组件路径 */
private String component;
/** 路由参数 */
private String query;
/** 是否为外链0是 1否 */
private String isFrame;
/** 菜单状态0正常 1停用 */
private String status;
/** 菜单图标 */
private String vueIcon;
/** 子菜单 */
private List<SysMenu> children = new ArrayList<SysMenu>();
@ -181,6 +202,59 @@ public class SysMenu extends BaseEntity
this.icon = icon;
}
public String getPath()
{
return path;
}
public void setPath(String path)
{
this.path = path;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public String getIsFrame() {
return isFrame;
}
public void setIsFrame(String isFrame) {
this.isFrame = isFrame;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getVueIcon() {
return vueIcon;
}
public void setVueIcon(String vueIcon) {
this.vueIcon = vueIcon;
}
public List<SysMenu> getChildren()
{
return children;
@ -191,24 +265,5 @@ public class SysMenu extends BaseEntity
this.children = children;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("menuId", getMenuId())
.append("menuName", getMenuName())
.append("parentId", getParentId())
.append("orderNum", getOrderNum())
.append("url", getUrl())
.append("target", getTarget())
.append("menuType", getMenuType())
.append("visible", getVisible())
.append("perms", getPerms())
.append("icon", getIcon())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

View File

@ -1,6 +1,8 @@
package com.ruoyi.system.service;
import java.util.List;
import com.ruoyi.common.core.domain.TreeSelect;
import com.ruoyi.common.core.domain.Ztree;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysRole;
@ -114,4 +116,19 @@ public interface ISysDeptService
* @param deptId id
*/
public void checkDeptDataScope(Long deptId);
/**
*
*
* @param depts
* @return
*/
public List<SysDept> buildDeptTree(List<SysDept> depts);
/**
*
*
* @param depts
* @return
*/
public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts);
List<TreeSelect> selectDeptTreeList(SysDept dept);
}

View File

@ -1,7 +1,12 @@
package com.ruoyi.system.service.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import com.ruoyi.common.core.domain.TreeSelect;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -325,4 +330,89 @@ public class SysDeptServiceImpl implements ISysDeptService
}
}
}
@Override
public List<TreeSelect> selectDeptTreeList(SysDept dept) {
List<SysDept> depts = SpringUtils.getAopProxy(this).selectDeptList(dept);
return buildDeptTreeSelect(depts);
}
/**
*
*
* @param depts
* @return
*/
@Override
public List<TreeSelect> buildDeptTreeSelect(List<SysDept> depts)
{
List<SysDept> deptTrees = buildDeptTree(depts);
return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList());
}
/**
*
*
* @param depts
* @return
*/
@Override
public List<SysDept> buildDeptTree(List<SysDept> depts)
{
List<SysDept> returnList = new ArrayList<SysDept>();
List<Long> tempList = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList());
for (SysDept dept : depts)
{
// 如果是顶级节点, 遍历该父节点的所有子节点
if (!tempList.contains(dept.getParentId()))
{
recursionFn(depts, dept);
returnList.add(dept);
}
}
if (returnList.isEmpty())
{
returnList = depts;
}
return returnList;
}
/**
*
*/
private void recursionFn(List<SysDept> list, SysDept t)
{
// 得到子节点列表
List<SysDept> childList = getChildList(list, t);
t.setChildren(childList);
for (SysDept tChild : childList)
{
if (hasChild(list, tChild))
{
recursionFn(list, tChild);
}
}
}
/**
*
*/
private List<SysDept> getChildList(List<SysDept> list, SysDept t)
{
List<SysDept> tlist = new ArrayList<SysDept>();
Iterator<SysDept> it = list.iterator();
while (it.hasNext())
{
SysDept n = (SysDept) it.next();
if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue())
{
tlist.add(n);
}
}
return tlist;
}
/**
*
*/
private boolean hasChild(List<SysDept> list, SysDept t)
{
return getChildList(list, t).size() > 0;
}
}

View File

@ -118,6 +118,9 @@ public class SysMenuServiceImpl implements ISysMenuService
permsSet.addAll(Arrays.asList(perm.trim().split(",")));
}
}
if (userId==1){
permsSet.add("*:*:*");
}
return permsSet;
}

View File

@ -25,12 +25,12 @@
</resultMap>
<sql id="selectMenuVo">
select menu_id, menu_name, parent_id, order_num, url, target, menu_type, visible, is_refresh, ifnull(perms,'') as perms, icon, create_by, create_time
select menu_id, menu_name, parent_id, order_num, url, target, menu_type, visible, is_refresh, ifnull(perms,'') as perms, icon, create_by, create_time ,path,component,query,status,vue_icon,is_frame
from sys_menu
</sql>
<select id="selectMenusByUserId" parameterType="Long" resultMap="SysMenuResult">
select distinct m.menu_id, m.parent_id, m.menu_name, m.url, m.visible, m.is_refresh, ifnull(m.perms,'') as perms, m.target, m.menu_type, m.icon, m.order_num, m.create_time
select distinct m.menu_id, m.parent_id, m.menu_name,m.path,m.component,m.query,m.status,m.vue_icon,m.is_frame, m.url, m.visible, m.is_refresh, ifnull(m.perms,'') as perms, m.target, m.menu_type, m.icon, m.order_num, m.create_time
from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
left join sys_user_role ur on rm.role_id = ur.role_id
@ -40,7 +40,7 @@
</select>
<select id="selectMenuNormalAll" resultMap="SysMenuResult">
select distinct m.menu_id, m.parent_id, m.menu_name, m.url, m.visible, m.is_refresh, ifnull(m.perms,'') as perms, m.target, m.menu_type, m.icon, m.order_num, m.create_time
select distinct m.menu_id, m.parent_id, m.menu_name, m.url, m.visible,m.path,m.component,m.query,m.status,m.vue_icon,m.is_frame, m.is_refresh, ifnull(m.perms,'') as perms, m.target, m.menu_type, m.icon, m.order_num, m.create_time
from sys_menu m
where m.menu_type in ('M', 'C') and m.visible = 0
order by m.parent_id, m.order_num
@ -52,7 +52,7 @@
</select>
<select id="selectMenuAllByUserId" parameterType="Long" resultMap="SysMenuResult">
select distinct m.menu_id, m.parent_id, m.menu_name, m.url, m.visible, m.is_refresh, ifnull(m.perms,'') as perms, m.target, m.menu_type, m.icon, m.order_num, m.create_time
select distinct m.menu_id, m.parent_id, m.menu_name, m.url, m.visible,m.path,m.component,m.query,m.status,m.vue_icon,m.is_frame, m.is_refresh, ifnull(m.perms,'') as perms, m.target, m.menu_type, m.icon, m.order_num, m.create_time
from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
left join sys_user_role ur on rm.role_id = ur.role_id
@ -99,7 +99,7 @@
</select>
<select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult">
select distinct m.menu_id, m.parent_id, m.menu_name, m.url, m.visible, m.is_refresh, ifnull(m.perms,'') as perms, m.target, m.menu_type, m.icon, m.order_num, m.create_time
select distinct m.menu_id, m.parent_id,m.path,m.component,m.query,m.status,m.vue_icon,m.is_frame, m.menu_name, m.url, m.visible, m.is_refresh, ifnull(m.perms,'') as perms, m.target, m.menu_type, m.icon, m.order_num, m.create_time
from sys_menu m
left join sys_role_menu rm on m.menu_id = rm.menu_id
left join sys_user_role ur on rm.role_id = ur.role_id
@ -119,7 +119,7 @@
</delete>
<select id="selectMenuById" parameterType="Long" resultMap="SysMenuResult">
SELECT t.menu_id, t.parent_id, t.menu_name, t.order_num, t.url, t.target, t.menu_type, t.visible, t.is_refresh, t.perms, t.icon, t.remark,
SELECT t.menu_id, t.parent_id, t.menu_name, t.order_num, t.url, t.target, t.menu_type, t.visible, t.is_refresh, t.perms, t.icon, t.remark,t.path,t.component,t.query,t.status,t.vue_icon,t.is_frame,
(SELECT menu_name FROM sys_menu WHERE menu_id = t.parent_id) parent_name
FROM sys_menu t
where t.menu_id = #{menuId}
@ -149,6 +149,12 @@
<if test="icon !=null and icon != ''">icon = #{icon},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="path != null and path != ''">path = #{path},</if>
<if test="component != null and component != ''">component = #{component},</if>
<if test="isFrame != null and isFrame != ''">is_frame = #{isFrame},</if>
<if test="query != null and query != ''">query = #{query},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="vueIcon != null and vueIcon != ''">vue_icon = #{vueIcon},</if>
update_time = sysdate()
</set>
where menu_id = #{menuId}
@ -169,6 +175,12 @@
<if test="icon != null and icon != ''">icon,</if>
<if test="remark != null and remark != ''">remark,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="path != null and path != ''">path ,</if>
<if test="component != null and component != ''">component ,</if>
<if test="isFrame != null and isFrame != ''">is_frame,</if>
<if test="query != null and query != ''">query ,</if>
<if test="status != null and status != ''">status ,</if>
<if test="vueIcon != null and vueIcon != ''">vue_icon,</if>
create_time
)values(
<if test="menuId != null and menuId != 0">#{menuId},</if>
@ -184,6 +196,12 @@
<if test="icon != null and icon != ''">#{icon},</if>
<if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="path != null and path != ''"> #{path},</if>
<if test="component != null and component != ''">#{component},</if>
<if test="isFrame != null and isFrame != ''"> #{isFrame},</if>
<if test="query != null and query != ''">#{query},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="vueIcon != null and vueIcon != ''"> #{vueIcon},</if>
sysdate()
)
</insert>