Commit ea4c9e498164f992582634471ddb47a056aad0c1

Authored by YunaiV
1 parent dba723b8

1. 修改 MenuDO 的字段,menuId 改成 id,menuName 改成 name

2. 添加 Menu 的创建、修改、删除接口
Showing 29 changed files with 503 additions and 237 deletions
ruoyi-ui/src/api/system/menu.js
... ... @@ -10,9 +10,9 @@ export function listMenu(query) {
10 10 }
11 11  
12 12 // 查询菜单详细
13   -export function getMenu(menuId) {
  13 +export function getMenu(id) {
14 14 return request({
15   - url: '/system/menu/' + menuId,
  15 + url: '/system/menu/get?id=' + id,
16 16 method: 'get'
17 17 })
18 18 }
... ... @@ -36,7 +36,7 @@ export function roleMenuTreeselect(roleId) {
36 36 // 新增菜单
37 37 export function addMenu(data) {
38 38 return request({
39   - url: '/system/menu',
  39 + url: '/system/menu/create',
40 40 method: 'post',
41 41 data: data
42 42 })
... ... @@ -45,16 +45,16 @@ export function addMenu(data) {
45 45 // 修改菜单
46 46 export function updateMenu(data) {
47 47 return request({
48   - url: '/system/menu',
49   - method: 'put',
  48 + url: '/system/menu/update',
  49 + method: 'post',
50 50 data: data
51 51 })
52 52 }
53 53  
54 54 // 删除菜单
55   -export function delMenu(menuId) {
  55 +export function delMenu(id) {
56 56 return request({
57   - url: '/system/menu/' + menuId,
58   - method: 'delete'
  57 + url: '/system/menu/delete?id=' + id,
  58 + method: 'post'
59 59 })
60   -}
61 60 \ No newline at end of file
  61 +}
... ...
ruoyi-ui/src/store/modules/dict.js
... ... @@ -28,8 +28,8 @@ const actions = {
28 28 }
29 29 // 处理 dictValue 层级
30 30 dictDataMap[dictData.dictType].push({
31   - dictValue: dictData.dictValue,
32   - dictLabel: dictData.dictLabel
  31 + value: dictData.value,
  32 + label: dictData.label
33 33 })
34 34 })
35 35 // 存储到 Store 中
... ...
ruoyi-ui/src/store/modules/permission.js
... ... @@ -44,7 +44,7 @@ function filterAsyncRouter(asyncRouterMap, isRewrite = false) {
44 44 // 将 ruoyi 后端原有耦合前端的逻辑,迁移到此处
45 45 // 处理 meta 属性
46 46 route.meta = {
47   - title: route.menuName,
  47 + title: route.name,
48 48 icon: route.icon
49 49 }
50 50 // 处理 component 属性
... ...
ruoyi-ui/src/utils/constants.js 0 → 100644
  1 +/**
  2 + * Created by 芋道源码
  3 + *
  4 + * 枚举类
  5 + */
  6 +
  7 +/**
  8 + * 全局通用状态枚举
  9 + */
  10 +export const SysCommonStatusEnum = {
  11 + ENABLE: 0, // 开启
  12 + DISABLE: 1 // 禁用
  13 +}
  14 +
  15 +/**
  16 + * 菜单的类型枚举
  17 + */
  18 +export const SysMenuTypeEnum = {
  19 + DIR : 1, // 目录
  20 + MENU: 2, // 菜单
  21 + BUTTON: 3 // 按钮
  22 +}
  23 +
  24 +/**
  25 + * 角色的类型枚举
  26 + */
  27 +export const RoleTypeEnum = {
  28 + SYSTEM: 1, // 内置角色
  29 + CUSTOM: 2 // 自定义角色
  30 +}
... ...
ruoyi-ui/src/utils/dict.js
... ... @@ -6,7 +6,8 @@
6 6 import store from '@/store'
7 7  
8 8 export const DICT_TYPE = {
9   - SYS_COMMON_STATUS: 'sys_common_status'
  9 + SYS_COMMON_STATUS: 'sys_common_status',
  10 + SYS_MENU_TYPE: 'menu_type'
10 11 }
11 12  
12 13 /**
... ... @@ -28,8 +29,8 @@ export function getDictDataLabel(dictType, value) {
28 29 // 获取 value 对应的展示名
29 30 value = value + '' // 强制转换成字符串,因为 DictData 小类数值,是字符串
30 31 for (const dictData of dictDatas) {
31   - if (dictData.dictValue === value) {
32   - return dictData.dictLabel
  32 + if (dictData.value === value) {
  33 + return dictData.label
33 34 }
34 35 }
35 36 return ''
... ...
ruoyi-ui/src/views/system/menu/index.vue
1 1 <template>
2 2 <div class="app-container">
3 3 <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
4   - <el-form-item label="菜单名称" prop="menuName">
  4 + <el-form-item label="菜单名称" prop="name">
5 5 <el-input
6   - v-model="queryParams.menuName"
  6 + v-model="queryParams.name"
7 7 placeholder="请输入菜单名称"
8 8 clearable
9 9 size="small"
... ... @@ -42,10 +42,10 @@
42 42 <el-table
43 43 v-loading="loading"
44 44 :data="menuList"
45   - row-key="menuId"
  45 + row-key="id"
46 46 :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
47 47 >
48   - <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
  48 + <el-table-column prop="name" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
49 49 <el-table-column prop="icon" label="图标" align="center" width="100">
50 50 <template slot-scope="scope">
51 51 <svg-icon :icon-class="scope.row.icon" />
... ... @@ -102,16 +102,18 @@
102 102 </el-form-item>
103 103 </el-col>
104 104 <el-col :span="24">
105   - <el-form-item label="菜单类型" prop="menuType">
106   - <el-radio-group v-model="form.menuType">
107   - <el-radio label="1">目录</el-radio>
108   - <el-radio label="2">菜单</el-radio>
109   - <el-radio label="3">按钮</el-radio>
  105 + <el-form-item label="菜单类型" prop="type">
  106 + <el-radio-group v-model="form.type">
  107 + <el-radio
  108 + v-for="dict in menuTypeDictDatas"
  109 + :key="dict.value"
  110 + :label="dict.value"
  111 + >{{dict.label}}</el-radio>
110 112 </el-radio-group>
111 113 </el-form-item>
112 114 </el-col>
113 115 <el-col :span="24">
114   - <el-form-item v-if="form.menuType != '3'" label="菜单图标">
  116 + <el-form-item v-if="form.type != '3'" label="菜单图标">
115 117 <el-popover
116 118 placement="bottom-start"
117 119 width="460"
... ... @@ -133,65 +135,38 @@
133 135 </el-form-item>
134 136 </el-col>
135 137 <el-col :span="12">
136   - <el-form-item label="菜单名称" prop="menuName">
137   - <el-input v-model="form.menuName" placeholder="请输入菜单名称" />
  138 + <el-form-item label="菜单名称" prop="name">
  139 + <el-input v-model="form.name" placeholder="请输入菜单名称" />
138 140 </el-form-item>
139 141 </el-col>
140 142 <el-col :span="12">
141   - <el-form-item label="显示排序" prop="orderNum">
142   - <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
143   - </el-form-item>
144   - </el-col>
145   - <el-col :span="12">
146   - <el-form-item v-if="form.menuType != '3'" label="是否外链">
147   - <el-radio-group v-model="form.isFrame">
148   - <el-radio label="0">是</el-radio>
149   - <el-radio label="1">否</el-radio>
150   - </el-radio-group>
  143 + <el-form-item label="显示排序" prop="sort">
  144 + <el-input-number v-model="form.sort" controls-position="right" :min="0" />
151 145 </el-form-item>
152 146 </el-col>
153 147 <el-col :span="12">
154   - <el-form-item v-if="form.menuType != '3'" label="路由地址" prop="path">
  148 + <el-form-item v-if="form.type != '3'" label="路由地址" prop="path">
155 149 <el-input v-model="form.path" placeholder="请输入路由地址" />
156 150 </el-form-item>
157 151 </el-col>
158   - <el-col :span="12" v-if="form.menuType == '2'">
  152 + <el-col :span="12" v-if="form.type == '2'">
159 153 <el-form-item label="组件路径" prop="component">
160 154 <el-input v-model="form.component" placeholder="请输入组件路径" />
161 155 </el-form-item>
162 156 </el-col>
163 157 <el-col :span="12">
164   - <el-form-item v-if="form.menuType != '1'" label="权限标识">
  158 + <el-form-item v-if="form.type != '1'" label="权限标识">
165 159 <el-input v-model="form.perms" placeholder="请权限标识" maxlength="50" />
166 160 </el-form-item>
167 161 </el-col>
168 162 <el-col :span="12">
169   - <el-form-item v-if="form.menuType != '3'" label="显示状态">
170   - <el-radio-group v-model="form.visible">
171   - <el-radio
172   - v-for="dict in visibleOptions"
173   - :key="dict.dictValue"
174   - :label="dict.dictValue"
175   - >{{dict.dictLabel}}</el-radio>
176   - </el-radio-group>
177   - </el-form-item>
178   - </el-col>
179   - <el-col :span="12">
180   - <el-form-item v-if="form.menuType != '3'" label="菜单状态">
  163 + <el-form-item v-if="form.type != '3'" label="菜单状态">
181 164 <el-radio-group v-model="form.status">
182 165 <el-radio
183   - v-for="dict in statusOptions"
184   - :key="dict.dictValue"
185   - :label="dict.dictValue"
186   - >{{dict.dictLabel}}</el-radio>
187   - </el-radio-group>
188   - </el-form-item>
189   - </el-col>
190   - <el-col :span="12">
191   - <el-form-item v-if="form.menuType == '2'" label="是否缓存">
192   - <el-radio-group v-model="form.isCache">
193   - <el-radio label="0">缓存</el-radio>
194   - <el-radio label="1">不缓存</el-radio>
  166 + v-for="dict in statusDictDatas"
  167 + :key="dict.value"
  168 + :label="dict.value"
  169 + >{{dict.label}}</el-radio>
195 170 </el-radio-group>
196 171 </el-form-item>
197 172 </el-col>
... ... @@ -211,6 +186,7 @@ import Treeselect from &quot;@riophae/vue-treeselect&quot;;
211 186 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
212 187 import IconSelect from "@/components/IconSelect";
213 188  
  189 +import { SysMenuTypeEnum, SysCommonStatusEnum } from '@/utils/constants'
214 190 import { getDictDataLabel, getDictDatas, DICT_TYPE } from '@/utils/dict'
215 191  
216 192 export default {
... ... @@ -230,29 +206,34 @@ export default {
230 206 title: "",
231 207 // 是否显示弹出层
232 208 open: false,
233   - // 显示状态数据字典
234   - visibleOptions: [],
235 209 // 菜单状态数据字典
236 210 statusOptions: [],
237 211 // 查询参数
238 212 queryParams: {
239   - menuName: undefined,
  213 + name: undefined,
240 214 visible: undefined
241 215 },
242 216 // 表单参数
243 217 form: {},
244 218 // 表单校验
245 219 rules: {
246   - menuName: [
  220 + name: [
247 221 { required: true, message: "菜单名称不能为空", trigger: "blur" }
248 222 ],
249   - orderNum: [
  223 + sort: [
250 224 { required: true, message: "菜单顺序不能为空", trigger: "blur" }
251 225 ],
252 226 path: [
253 227 { required: true, message: "路由地址不能为空", trigger: "blur" }
254 228 ]
255   - }
  229 + },
  230 +
  231 + // 枚举
  232 + MenuTypeEnum: SysMenuTypeEnum,
  233 + CommonStatusEnum: SysCommonStatusEnum,
  234 + // 数据字典
  235 + menuTypeDictDatas: getDictDatas(DICT_TYPE.SYS_MENU_TYPE),
  236 + statusDictDatas: getDictDatas(DICT_TYPE.SYS_COMMON_STATUS)
256 237 };
257 238 },
258 239 created() {
... ... @@ -267,7 +248,7 @@ export default {
267 248 getList() {
268 249 this.loading = true;
269 250 listMenu(this.queryParams).then(response => {
270   - this.menuList = this.handleTree(response.data, "menuId");
  251 + this.menuList = this.handleTree(response.data, "id");
271 252 this.loading = false;
272 253 });
273 254 },
... ... @@ -277,8 +258,8 @@ export default {
277 258 delete node.children;
278 259 }
279 260 return {
280   - id: node.menuId,
281   - label: node.menuName,
  261 + id: node.id,
  262 + label: node.name,
282 263 children: node.children
283 264 };
284 265 },
... ... @@ -286,8 +267,8 @@ export default {
286 267 getTreeselect() {
287 268 listMenu().then(response => {
288 269 this.menuOptions = [];
289   - const menu = { menuId: 0, menuName: '主类目', children: [] };
290   - menu.children = this.handleTree(response.data, "menuId");
  270 + const menu = { id: 0, name: '主类目', children: [] };
  271 + menu.children = this.handleTree(response.data, "id");
291 272 this.menuOptions.push(menu);
292 273 });
293 274 },
... ... @@ -303,15 +284,12 @@ export default {
303 284 // 表单重置
304 285 reset() {
305 286 this.form = {
306   - menuId: undefined,
  287 + id: undefined,
307 288 parentId: 0,
308   - menuName: undefined,
  289 + name: undefined,
309 290 icon: undefined,
310   - menuType: "1",
311   - orderNum: undefined,
312   - isFrame: "1",
313   - isCache: "0",
314   - visible: "0",
  291 + type: "1",
  292 + sort: undefined,
315 293 status: "0"
316 294 };
317 295 this.resetForm("form");
... ... @@ -329,8 +307,8 @@ export default {
329 307 handleAdd(row) {
330 308 this.reset();
331 309 this.getTreeselect();
332   - if (row != null && row.menuId) {
333   - this.form.parentId = row.menuId;
  310 + if (row != null && row.id) {
  311 + this.form.parentId = row.id;
334 312 } else {
335 313 this.form.parentId = 0;
336 314 }
... ... @@ -341,7 +319,7 @@ export default {
341 319 handleUpdate(row) {
342 320 this.reset();
343 321 this.getTreeselect();
344   - getMenu(row.menuId).then(response => {
  322 + getMenu(row.id).then(response => {
345 323 this.form = response.data;
346 324 this.open = true;
347 325 this.title = "修改菜单";
... ... @@ -351,7 +329,24 @@ export default {
351 329 submitForm: function() {
352 330 this.$refs["form"].validate(valid => {
353 331 if (valid) {
354   - if (this.form.menuId != undefined) {
  332 + // 若权限类型为菜单时,进行 route 的校验,避免后续拼接出来的路由无法跳转
  333 + if (this.form.type === ResourceTypeEnum.MENU) {
  334 + // 如果是外链,则不进行校验
  335 + const route = this.resourceForm.route
  336 + if (route.indexOf('http://') === -1 || route.indexOf('https://') === -1) {
  337 + // 父权限为根节点,route 必须以 / 开头
  338 + if (this.resourceForm.pid === 0 && route.charAt(0) !== '/') {
  339 + this.messageSuccess('前端必须以 / 开头')
  340 + return
  341 + } else if (this.resourceForm.pid !== 0 && route.charAt(0) === '/') {
  342 + this.messageSuccess('前端不能以 / 开头')
  343 + return
  344 + }
  345 + }
  346 + }
  347 +
  348 + // 提交
  349 + if (this.form.id !== undefined) {
355 350 updateMenu(this.form).then(response => {
356 351 this.msgSuccess("修改成功");
357 352 this.open = false;
... ... @@ -369,12 +364,12 @@ export default {
369 364 },
370 365 /** 删除按钮操作 */
371 366 handleDelete(row) {
372   - this.$confirm('是否确认删除名称为"' + row.menuName + '"的数据项?', "警告", {
  367 + this.$confirm('是否确认删除名称为"' + row.name + '"的数据项?', "警告", {
373 368 confirmButtonText: "确定",
374 369 cancelButtonText: "取消",
375 370 type: "warning"
376 371 }).then(function() {
377   - return delMenu(row.menuId);
  372 + return delMenu(row.id);
378 373 }).then(() => {
379 374 this.getList();
380 375 this.msgSuccess("删除成功");
... ...
ruoyi-ui/src/views/system/role/index.vue
... ... @@ -437,7 +437,7 @@ export default {
437 437 roleKey: undefined,
438 438 roleSort: 0,
439 439 status: "0",
440   - menuIds: [],
  440 + ids: [],
441 441 deptIds: [],
442 442 menuCheckStrictly: true,
443 443 deptCheckStrictly: true,
... ... @@ -535,14 +535,14 @@ export default {
535 535 this.$refs["form"].validate(valid => {
536 536 if (valid) {
537 537 if (this.form.roleId != undefined) {
538   - this.form.menuIds = this.getMenuAllCheckedKeys();
  538 + this.form.ids = this.getMenuAllCheckedKeys();
539 539 updateRole(this.form).then(response => {
540 540 this.msgSuccess("修改成功");
541 541 this.open = false;
542 542 this.getList();
543 543 });
544 544 } else {
545   - this.form.menuIds = this.getMenuAllCheckedKeys();
  545 + this.form.ids = this.getMenuAllCheckedKeys();
546 546 addRole(this.form).then(response => {
547 547 this.msgSuccess("新增成功");
548 548 this.open = false;
... ... @@ -592,4 +592,4 @@ export default {
592 592 }
593 593 }
594 594 };
595   -</script>
596 595 \ No newline at end of file
  596 +</script>
... ...
ruoyi-ui/src/views/tool/gen/editTable.vue
... ... @@ -167,7 +167,7 @@ export default {
167 167 });
168 168 /** 查询菜单下拉列表 */
169 169 getMenuTreeselect().then(response => {
170   - this.menus = this.handleTree(response.data, "menuId");
  170 + this.menus = this.handleTree(response.data, "id");
171 171 });
172 172 }
173 173 },
... ...
ruoyi-ui/src/views/tool/gen/genInfoForm.vue
... ... @@ -219,8 +219,8 @@ export default {
219 219 delete node.children;
220 220 }
221 221 return {
222   - id: node.menuId,
223   - label: node.menuName,
  222 + id: node.id,
  223 + label: node.name,
224 224 children: node.children
225 225 };
226 226 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/vo/SysAuthMenuRespVO.java
1 1 package cn.iocoder.dashboard.modules.system.controller.auth.vo;
2 2  
3 3 import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
4 5 import lombok.AllArgsConstructor;
5 6 import lombok.Builder;
6 7 import lombok.Data;
... ... @@ -15,32 +16,22 @@ import java.util.List;
15 16 @Builder
16 17 public class SysAuthMenuRespVO {
17 18  
18   - /**
19   - * 菜单编号
20   - */
21   - private Long menuId;
22   - /**
23   - * 父菜单编号
24   - */
  19 + @ApiModelProperty(value = "菜单名称", required = true, example = "芋道")
  20 + private Long id;
  21 +
  22 + @ApiModelProperty(value = "父菜单 ID", required = true, example = "1024")
25 23 private Long parentId;
26 24  
27   - /**
28   - * 菜单名称
29   - */
30   - private String menuName;
  25 + @ApiModelProperty(value = "菜单名称", required = true, example = "芋道")
  26 + private String name;
31 27  
32   - /**
33   - * 路由地址
34   - */
  28 + @ApiModelProperty(value = "路由地址", example = "post", notes = "仅菜单类型为菜单或者目录时,才需要传")
35 29 private String path;
36   - /**
37   - * 组件地址
38   - */
  30 +
  31 + @ApiModelProperty(value = "组件路径", example = "system/post/index", notes = "仅菜单类型为菜单时,才需要传")
39 32 private String component;
40 33  
41   - /**
42   - * 菜单图标
43   - */
  34 + @ApiModelProperty(value = "菜单图标", example = "/menu/list", notes = "仅菜单类型为菜单或者目录时,才需要传")
44 35 private String icon;
45 36  
46 37 /**
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/dict/vo/SysDataDictSimpleVO.java
... ... @@ -12,9 +12,9 @@ public class SysDataDictSimpleVO {
12 12 private String dictType;
13 13  
14 14 @ApiModelProperty(value = "字典键值", required = true, example = "1")
15   - private String dictValue;
  15 + private String value;
16 16  
17 17 @ApiModelProperty(value = "字典标签", required = true, example = "男")
18   - private String dictLabel;
  18 + private String label;
19 19  
20 20 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/permission/SysMenuController.java
1 1 package cn.iocoder.dashboard.modules.system.controller.permission;
2 2  
3 3 import cn.iocoder.dashboard.common.pojo.CommonResult;
  4 +import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuCreateReqVO;
4 5 import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuListReqVO;
5 6 import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuRespVO;
  7 +import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuUpdateReqVO;
  8 +import cn.iocoder.dashboard.modules.system.convert.permission.SysMenuConvert;
  9 +import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
6 10 import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService;
7 11 import io.swagger.annotations.Api;
8   -import org.springframework.web.bind.annotation.GetMapping;
9   -import org.springframework.web.bind.annotation.RequestMapping;
10   -import org.springframework.web.bind.annotation.RestController;
  12 +import io.swagger.annotations.ApiOperation;
  13 +import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.*;
11 15  
12 16 import javax.annotation.Resource;
13 17 import java.util.List;
... ... @@ -22,24 +26,21 @@ public class SysMenuController {
22 26 @Resource
23 27 private SysMenuService menuService;
24 28  
25   - /**
26   - * 获取菜单列表
27   - */
  29 + @ApiOperation("获取菜单列表")
28 30 // @PreAuthorize("@ss.hasPermi('system:menu:list')")
29 31 @GetMapping("/list")
30 32 public CommonResult<List<SysMenuRespVO>> list(SysMenuListReqVO reqVO) {
31 33 return success(menuService.listMenus(reqVO));
32 34 }
33   -//
34   -// /**
35   -// * 根据菜单编号获取详细信息
36   -// */
  35 +
  36 + @ApiOperation("获取菜单信息")
  37 + @GetMapping("/get")
37 38 // @PreAuthorize("@ss.hasPermi('system:menu:query')")
38   -// @GetMapping(value = "/{menuId}")
39   -// public AjaxResult getInfo(@PathVariable Long menuId) {
40   -// return AjaxResult.success(menuService.selectMenuById(menuId));
41   -// }
42   -//
  39 + public CommonResult<SysMenuRespVO> getMenu(Long id) {
  40 + SysMenuDO menu = menuService.getMenu(id);
  41 + return success(SysMenuConvert.INSTANCE.convert(menu));
  42 + }
  43 +
43 44 // /**
44 45 // * 获取菜单下拉树列表
45 46 // */
... ... @@ -63,57 +64,31 @@ public class SysMenuController {
63 64 // ajax.put("menus", menuService.buildMenuTreeSelect(menus));
64 65 // return ajax;
65 66 // }
66   -//
67   -// /**
68   -// * 新增菜单
69   -// */
  67 +
  68 + @ApiOperation("新增菜单")
70 69 // @PreAuthorize("@ss.hasPermi('system:menu:add')")
71 70 // @Log(title = "菜单管理", businessType = BusinessType.INSERT)
72   -// @PostMapping
73   -// public AjaxResult add(@Validated @RequestBody SysMenu menu) {
74   -// if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) {
75   -// return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
76   -// } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame())
77   -// && !StringUtils.startsWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS)) {
78   -// return AjaxResult.error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
79   -// }
80   -// menu.setCreateBy(SecurityUtils.getUsername());
81   -// return toAjax(menuService.insertMenu(menu));
82   -// }
83   -//
84   -// /**
85   -// * 修改菜单
86   -// */
  71 + @PostMapping("/create")
  72 + public CommonResult<Long> createMenu(@Validated @RequestBody SysMenuCreateReqVO reqVO) {
  73 + Long menuId = menuService.createMenu(reqVO);
  74 + return success(menuId);
  75 + }
  76 +
  77 + @ApiOperation("修改菜单")
87 78 // @PreAuthorize("@ss.hasPermi('system:menu:edit')")
88 79 // @Log(title = "菜单管理", businessType = BusinessType.UPDATE)
89   -// @PutMapping
90   -// public AjaxResult edit(@Validated @RequestBody SysMenu menu) {
91   -// if (UserConstants.NOT_UNIQUE.equals(menuService.checkMenuNameUnique(menu))) {
92   -// return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在");
93   -// } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame())
94   -// && !StringUtils.startsWithAny(menu.getPath(), Constants.HTTP, Constants.HTTPS)) {
95   -// return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头");
96   -// } else if (menu.getMenuId().equals(menu.getParentId())) {
97   -// return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
98   -// }
99   -// menu.setUpdateBy(SecurityUtils.getUsername());
100   -// return toAjax(menuService.updateMenu(menu));
101   -// }
102   -//
103   -// /**
104   -// * 删除菜单
105   -// */
106   -// @PreAuthorize("@ss.hasPermi('system:menu:remove')")
  80 + @PostMapping("/update")
  81 + public CommonResult<Boolean> updateMenu(@Validated @RequestBody SysMenuUpdateReqVO reqVO) {
  82 + menuService.updateMenu(reqVO);
  83 + return success(true);
  84 + }
  85 +
  86 + @ApiOperation("删除菜单")
107 87 // @Log(title = "菜单管理", businessType = BusinessType.DELETE)
108 88 // @DeleteMapping("/{menuId}")
109   -// public AjaxResult remove(@PathVariable("menuId") Long menuId) {
110   -// if (menuService.hasChildByMenuId(menuId)) {
111   -// return AjaxResult.error("存在子菜单,不允许删除");
112   -// }
113   -// if (menuService.checkMenuExistRole(menuId)) {
114   -// return AjaxResult.error("菜单已分配,不允许删除");
115   -// }
116   -// return toAjax(menuService.deleteMenuById(menuId));
117   -// }
  89 + public CommonResult<Boolean> deleteMenu(@RequestParam("id") Long id) {
  90 + menuService.deleteMenu(id);
  91 + return success(true);
  92 + }
118 93  
119 94 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/permission/vo/SysMenuBaseVO.java
... ... @@ -17,15 +17,15 @@ public class SysMenuBaseVO {
17 17 @ApiModelProperty(value = "菜单名称", required = true, example = "芋道")
18 18 @NotBlank(message = "菜单名称不能为空")
19 19 @Size(max = 50, message = "菜单名称长度不能超过50个字符")
20   - private String menuName;
  20 + private String name;
21 21  
22 22 @ApiModelProperty(value = "权限标识", example = "sys:menu:add", notes = "仅菜单类型为按钮时,才需要传递")
23 23 @Size(max = 100)
24 24 private String permission;
25 25  
26   - @ApiModelProperty(value = "类型", required = true, example = "1", notes = "参见 MenuTypeEnum 枚举类")
  26 + @ApiModelProperty(value = "类型", required = true, example = "1", notes = "参见 SysMenuTypeEnum 枚举类")
27 27 @NotBlank(message = "菜单类型不能为空")
28   - private Integer menuType;
  28 + private Integer type;
29 29  
30 30 @ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024")
31 31 @NotBlank(message = "显示顺序不能为空")
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/permission/vo/SysMenuListReqVO.java
... ... @@ -14,7 +14,7 @@ public class SysMenuListReqVO {
14 14 @ApiModelProperty(value = "菜单名称", example = "芋道", notes = "模糊匹配")
15 15 private String menuName;
16 16  
17   - @ApiModelProperty(value = "展示状态", example = "1", notes = "参见 CommonStatusEnum 枚举类")
  17 + @ApiModelProperty(value = "展示状态", example = "1", notes = "参见 SysCommonStatusEnum 枚举类")
18 18 private Integer status;
19 19  
20 20 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/permission/vo/SysMenuRespVO.java
... ... @@ -17,9 +17,9 @@ import java.util.Date;
17 17 public class SysMenuRespVO extends SysMenuBaseVO {
18 18  
19 19 @ApiModelProperty(value = "菜单编号", required = true, example = "1024")
20   - private Integer menuId;
  20 + private Integer id;
21 21  
22   - @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举类")
  22 + @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 SysCommonStatusEnum 枚举类")
23 23 private Integer status;
24 24  
25 25 @ApiModelProperty(value = "创建时间", required = true, example = "时间戳格式")
... ...
src/main/java/cn/iocoder/dashboard/modules/system/controller/permission/vo/SysMenuUpdateReqVO.java
... ... @@ -13,6 +13,6 @@ public class SysMenuUpdateReqVO extends SysMenuBaseVO {
13 13  
14 14 @ApiModelProperty(value = "菜单编号", required = true, example = "1024")
15 15 @NotNull(message = "菜单编号不能为空")
16   - private Integer menuId;
  16 + private Long id;
17 17  
18 18 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/convert/auth/SysAuthConvert.java
1 1 package cn.iocoder.dashboard.modules.system.convert.auth;
2 2  
3 3 import cn.iocoder.dashboard.framework.security.core.LoginUser;
4   -import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthPermissionInfoRespVO;
5 4 import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthMenuRespVO;
  5 +import cn.iocoder.dashboard.modules.system.controller.auth.vo.SysAuthPermissionInfoRespVO;
6 6 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
7 7 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO;
8 8 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
  9 +import cn.iocoder.dashboard.modules.system.enums.permission.MenuIdEnum;
9 10 import cn.iocoder.dashboard.util.collection.CollectionUtils;
10 11 import org.mapstruct.Mapper;
11 12 import org.mapstruct.Mapping;
12 13 import org.mapstruct.factory.Mappers;
  14 +import org.slf4j.LoggerFactory;
13 15  
14   -import java.util.List;
  16 +import java.util.*;
15 17  
16 18 @Mapper
17 19 public interface SysAuthConvert {
... ... @@ -31,4 +33,36 @@ public interface SysAuthConvert {
31 33  
32 34 SysAuthMenuRespVO convertTreeNode(SysMenuDO menu);
33 35  
  36 + /**
  37 + * 将菜单列表,构建成菜单树
  38 + *
  39 + * @param menuList 菜单列表
  40 + * @return 菜单树
  41 + */
  42 + default List<SysAuthMenuRespVO> buildMenuTree(List<SysMenuDO> menuList) {
  43 + // 排序,保证菜单的有序性
  44 + menuList.sort(Comparator.comparing(SysMenuDO::getSort));
  45 + // 构建菜单树
  46 + // 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
  47 + Map<Long, SysAuthMenuRespVO> treeNodeMap = new LinkedHashMap<>();
  48 + menuList.forEach(menu -> treeNodeMap.put(menu.getId(), SysAuthConvert.INSTANCE.convertTreeNode(menu)));
  49 + // 处理父子关系
  50 + treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(MenuIdEnum.ROOT.getId())).forEach((childNode) -> {
  51 + // 获得父节点
  52 + SysAuthMenuRespVO parentNode = treeNodeMap.get(childNode.getParentId());
  53 + if (parentNode == null) {
  54 + LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
  55 + childNode.getId(), childNode.getParentId());
  56 + return;
  57 + }
  58 + // 将自己添加到父节点中
  59 + if (parentNode.getChildren() == null) {
  60 + parentNode.setChildren(new ArrayList<>());
  61 + }
  62 + parentNode.getChildren().add(childNode);
  63 + });
  64 + // 获得到所有的根节点
  65 + return CollectionUtils.filterList(treeNodeMap.values(), node -> MenuIdEnum.ROOT.getId().equals(node.getParentId()));
  66 + }
  67 +
34 68 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/convert/permission/SysMenuConvert.java
1 1 package cn.iocoder.dashboard.modules.system.convert.permission;
2 2  
  3 +import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuCreateReqVO;
3 4 import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuRespVO;
  5 +import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuUpdateReqVO;
4 6 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
5 7 import org.mapstruct.Mapper;
6 8 import org.mapstruct.factory.Mappers;
... ... @@ -14,4 +16,10 @@ public interface SysMenuConvert {
14 16  
15 17 List<SysMenuRespVO> convertList(List<SysMenuDO> list);
16 18  
  19 + SysMenuDO convert(SysMenuCreateReqVO bean);
  20 +
  21 + SysMenuDO convert(SysMenuUpdateReqVO bean);
  22 +
  23 + SysMenuRespVO convert(SysMenuDO bean);
  24 +
17 25 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/permission/SysMenuMapper.java
1 1 package cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission;
2 2  
3 3 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
  4 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
4 5 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
5 6 import org.apache.ibatis.annotations.Mapper;
6 7  
7 8 @Mapper
8 9 public interface SysMenuMapper extends BaseMapper<SysMenuDO> {
  10 +
  11 + default SysMenuDO selectByParentIdAndName(Long parentId, String name) {
  12 + return selectOne(new QueryWrapper<SysMenuDO>().eq("parent_id", parentId)
  13 + .eq("name", name));
  14 + }
  15 +
  16 + default Integer selectCountByParentId(Long parentId) {
  17 + return selectCount(new QueryWrapper<SysMenuDO>().eq("parent_id", parentId));
  18 + }
  19 +
9 20 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/dict/SysDictDataDO.java
... ... @@ -26,30 +26,30 @@ public class SysDictDataDO extends BaseDO {
26 26 */
27 27 @TableId
28 28 @Excel(name = "字典编码", cellType = Excel.ColumnType.NUMERIC)
29   - private Long dictCode;
  29 + private Long id;
30 30 /**
31 31 * 字典排序
32 32 */
33 33 @Excel(name = "字典排序", cellType = Excel.ColumnType.NUMERIC)
34   - private Integer dictSort;
  34 + private Integer sort;
35 35 /**
36 36 * 字典标签
37 37 */
38 38 @Excel(name = "字典标签")
39 39 @NotBlank(message = "字典标签不能为空")
40 40 @Size(max = 100, message = "字典标签长度不能超过100个字符")
41   - private String dictLabel;
  41 + private String label;
42 42 /**
43   - * 字典键值
  43 + * 字典值
44 44 */
45 45 @Excel(name = "字典键值")
46 46 @NotBlank(message = "字典键值不能为空")
47 47 @Size(max = 100, message = "字典键值长度不能超过100个字符")
48   - private String dictValue;
  48 + private String value;
49 49 /**
50 50 * 字典类型
51 51 *
52   - * 外键 {@link SysDictDataDO#getDictType()}
  52 + * 冗余 {@link SysDictDataDO#getDictType()}
53 53 */
54 54 @Excel(name = "字典类型")
55 55 @NotBlank(message = "字典类型不能为空")
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/dict/SysDictTypeDO.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dict;
3 3 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.dashboard.framework.excel.Excel;
5 5 import cn.iocoder.dashboard.framework.mybatis.core.BaseDO;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
6 7 import com.baomidou.mybatisplus.annotation.TableId;
7 8 import com.baomidou.mybatisplus.annotation.TableName;
8 9 import lombok.Data;
... ... @@ -26,24 +27,22 @@ public class SysDictTypeDO extends BaseDO {
26 27 */
27 28 @TableId
28 29 @Excel(name = "字典主键", cellType = Excel.ColumnType.NUMERIC)
29   - private Long dictId;
30   -
  30 + private Long id;
31 31 /**
32 32 * 字典名称
33 33 */
34 34 @Excel(name = "字典名称")
35 35 @NotBlank(message = "字典名称不能为空")
36 36 @Size(max = 100, message = "字典类型名称长度不能超过100个字符")
37   - private String dictName;
38   -
  37 + private String name;
39 38 /**
40 39 * 字典类型
41 40 */
  41 + @TableField("dict_type")
42 42 @Excel(name = "字典类型")
43 43 @NotBlank(message = "字典类型不能为空")
44 44 @Size(max = 100, message = "字典类型类型长度不能超过100个字符")
45   - private String dictType;
46   -
  45 + private String type;
47 46 /**
48 47 * 状态
49 48 *
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dataobject/permission/SysMenuDO.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission;
3 3 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
4 4 import cn.iocoder.dashboard.framework.mybatis.core.BaseDO;
5 5 import cn.iocoder.dashboard.modules.system.enums.permission.MenuTypeEnum;
  6 +import com.baomidou.mybatisplus.annotation.TableField;
6 7 import com.baomidou.mybatisplus.annotation.TableId;
7 8 import com.baomidou.mybatisplus.annotation.TableName;
8 9 import lombok.Data;
... ... @@ -22,11 +23,11 @@ public class SysMenuDO extends BaseDO {
22 23 * 菜单ID
23 24 */
24 25 @TableId
25   - private Long menuId;
  26 + private Long id;
26 27 /**
27 28 * 菜单名称
28 29 */
29   - private String menuName;
  30 + private String name;
30 31 /**
31 32 * 权限标识
32 33 *
... ... @@ -43,7 +44,8 @@ public class SysMenuDO extends BaseDO {
43 44 *
44 45 * 枚举 {@link MenuTypeEnum}
45 46 */
46   - private Integer menuType;
  47 + @TableField("menu_type")
  48 + private Integer type;
47 49 /**
48 50 * 显示顺序
49 51 */
... ...
src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java
... ... @@ -18,4 +18,12 @@ public interface SysErrorCodeConstants {
18 18 ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期");
19 19 ErrorCode TOKEN_PARSE_FAIL = new ErrorCode(1002001001, "Token 解析失败");
20 20  
  21 + // ========== 菜单模块 1002002000 ==========
  22 + ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1002002000, "已经存在该名字的菜单");
  23 + ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1002002001, "父菜单不存在");
  24 + ErrorCode MENU_PARENT_ERROR = new ErrorCode(1002002002, "不能设置自己为父菜单");
  25 + ErrorCode MENU_NOT_EXISTS = new ErrorCode(1002002003, "菜单不存在");
  26 + ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1002002004, "存在子菜单,无法删除");
  27 + ErrorCode MENU_PARENT_NOT_MENU = new ErrorCode(1002002005, "父菜单的类型必须是菜单");
  28 +
21 29 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysAuthServiceImpl.java
... ... @@ -12,7 +12,6 @@ import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMe
12 12 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO;
13 13 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO;
14 14 import cn.iocoder.dashboard.modules.system.dal.redis.dao.auth.SysLoginUserRedisDAO;
15   -import cn.iocoder.dashboard.modules.system.enums.permission.MenuIdEnum;
16 15 import cn.iocoder.dashboard.modules.system.enums.permission.MenuTypeEnum;
17 16 import cn.iocoder.dashboard.modules.system.enums.user.UserStatus;
18 17 import cn.iocoder.dashboard.modules.system.service.auth.SysAuthService;
... ... @@ -38,7 +37,10 @@ import org.springframework.stereotype.Service;
38 37 import org.springframework.util.Assert;
39 38  
40 39 import javax.annotation.Resource;
41   -import java.util.*;
  40 +import java.util.Date;
  41 +import java.util.HashSet;
  42 +import java.util.List;
  43 +import java.util.Set;
42 44  
43 45 import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
44 46 import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
... ... @@ -243,36 +245,12 @@ public class SysAuthServiceImpl implements SysAuthService {
243 245  
244 246 @Override
245 247 public List<SysAuthMenuRespVO> listMenus(Long userId, Set<Long> roleIds) {
  248 + // 获得用户拥有的菜单列表
246 249 List<SysMenuDO> menuList = permissionService.listRoleMenusFromCache(roleIds,
247   - SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()),
248   - SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus()));
  250 + SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
  251 + SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
249 252 // 转换成 Tree 结构返回
250   - return buildRouterTree(menuList);
251   - }
252   -
253   - private static List<SysAuthMenuRespVO> buildRouterTree(List<SysMenuDO> menuList) {
254   - // 排序,保证菜单的有序性
255   - menuList.sort(Comparator.comparing(SysMenuDO::getSort));
256   - // 构建菜单树
257   - // 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
258   - Map<Long, SysAuthMenuRespVO> treeNodeMap = new LinkedHashMap<>();
259   - menuList.forEach(menu -> treeNodeMap.put(menu.getMenuId(), SysAuthConvert.INSTANCE.convertTreeNode(menu)));
260   - // 处理父子关系
261   - treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(MenuIdEnum.ROOT.getId())).forEach((childNode) -> {
262   - // 获得父节点
263   - SysAuthMenuRespVO parentNode = treeNodeMap.get(childNode.getParentId());
264   - if (parentNode == null) {
265   - log.error("[buildRouterTree][resource({}) 找不到父资源({})]", childNode.getMenuId(), childNode.getParentId());
266   - return;
267   - }
268   - // 将自己添加到父节点中
269   - if (parentNode.getChildren() == null) {
270   - parentNode.setChildren(new ArrayList<>());
271   - }
272   - parentNode.getChildren().add(childNode);
273   - });
274   - // 获得到所有的根节点
275   - return CollectionUtils.filterList(treeNodeMap.values(), node -> MenuIdEnum.ROOT.getId().equals(node.getParentId()));
  253 + return SysAuthConvert.INSTANCE.buildMenuTree(menuList);
276 254 }
277 255  
278 256 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictDataServiceImpl.java
... ... @@ -19,7 +19,7 @@ public class SysDictDataServiceImpl implements SysDictDataService {
19 19  
20 20 private static final Comparator<SysDictDataDO> COMPARATOR_TYPE_AND_SORT = Comparator
21 21 .comparing(SysDictDataDO::getDictType)
22   - .thenComparingInt(SysDictDataDO::getDictSort);
  22 + .thenComparingInt(SysDictDataDO::getSort);
23 23  
24 24 @Resource
25 25 private SysDictDataMapper dictDataMapper;
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuService.java
1 1 package cn.iocoder.dashboard.modules.system.service.permission;
2 2  
  3 +import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuCreateReqVO;
3 4 import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuListReqVO;
4 5 import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuRespVO;
  6 +import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuUpdateReqVO;
5 7 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
6 8  
7 9 import java.util.Collection;
... ... @@ -9,6 +11,8 @@ import java.util.List;
9 11  
10 12 /**
11 13 * 菜单 Service 接口
  14 + *
  15 + * @author 芋道源码
12 16 */
13 17 public interface SysMenuService {
14 18  
... ... @@ -49,4 +53,34 @@ public interface SysMenuService {
49 53 List<SysMenuDO> listMenusFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
50 54 Collection<Integer> menusStatuses);
51 55  
  56 + /*
  57 + * 创建菜单
  58 + *
  59 + * @param reqVO 菜单信息
  60 + * @return 创建出来的菜单编号
  61 + */
  62 + Long createMenu(SysMenuCreateReqVO reqVO);
  63 +
  64 + /**
  65 + * 更新菜单
  66 + *
  67 + * @param reqVO 菜单信息
  68 + */
  69 + void updateMenu(SysMenuUpdateReqVO reqVO);
  70 +
  71 + /**
  72 + * 删除菜单
  73 + *
  74 + * @param id 菜单编号
  75 + */
  76 + void deleteMenu(Long id);
  77 +
  78 + /**
  79 + * 获得菜单
  80 + *
  81 + * @param id 菜单编号
  82 + * @return 菜单
  83 + */
  84 + SysMenuDO getMenu(Long id);
  85 +
52 86 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysPermissionService.java
... ... @@ -48,4 +48,11 @@ public interface SysPermissionService {
48 48 */
49 49 Long getDeptRoleId(Long deptId);
50 50  
  51 + /**
  52 + * 删除授予给角色的菜单们
  53 + *
  54 + * @param menuId 菜单编号
  55 + */
  56 + void deleteRolesMenuByMenuId(Long menuId);
  57 +
51 58 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java
1 1 package cn.iocoder.dashboard.modules.system.service.permission.impl;
2 2  
  3 +import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
  4 +import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuCreateReqVO;
3 5 import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuListReqVO;
4 6 import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuRespVO;
  7 +import cn.iocoder.dashboard.modules.system.controller.permission.vo.SysMenuUpdateReqVO;
5 8 import cn.iocoder.dashboard.modules.system.convert.permission.SysMenuConvert;
6 9 import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysMenuMapper;
7 10 import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO;
  11 +import cn.iocoder.dashboard.modules.system.enums.permission.MenuIdEnum;
  12 +import cn.iocoder.dashboard.modules.system.enums.permission.MenuTypeEnum;
8 13 import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService;
  14 +import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
9 15 import cn.iocoder.dashboard.util.collection.CollectionUtils;
10 16 import com.google.common.collect.ImmutableMap;
11 17 import com.google.common.collect.ImmutableMultimap;
... ... @@ -21,8 +27,12 @@ import java.util.List;
21 27 import java.util.Map;
22 28 import java.util.stream.Collectors;
23 29  
  30 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
  31 +
24 32 /**
25 33 * 菜单 Service 实现
  34 + *
  35 + * @author 芋道源码
26 36 */
27 37 @Service
28 38 @Slf4j
... ... @@ -46,6 +56,8 @@ public class SysMenuServiceImpl implements SysMenuService {
46 56  
47 57 @Resource
48 58 private SysMenuMapper menuMapper;
  59 + @Resource
  60 + private SysPermissionService permissionService;
49 61  
50 62 /**
51 63 * 初始化 {@link #menuCache} 和 {@link #permMenuCache} 缓存
... ... @@ -57,7 +69,7 @@ public class SysMenuServiceImpl implements SysMenuService {
57 69 ImmutableMap.Builder<Long, SysMenuDO> menuCacheBuilder = ImmutableMap.builder();
58 70 ImmutableMultimap.Builder<String, SysMenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
59 71 menuList.forEach(menuDO -> {
60   - menuCacheBuilder.put(menuDO.getMenuId(), menuDO);
  72 + menuCacheBuilder.put(menuDO.getId(), menuDO);
61 73 permMenuCacheBuilder.put(menuDO.getPermission(), menuDO);
62 74 });
63 75 menuCache = menuCacheBuilder.build();
... ... @@ -79,7 +91,7 @@ public class SysMenuServiceImpl implements SysMenuService {
79 91 return Collections.emptyList();
80 92 }
81 93 // 创建新数组,避免缓存被修改
82   - return menuCache.values().stream().filter(menu -> menuTypes.contains(menu.getMenuType())
  94 + return menuCache.values().stream().filter(menu -> menuTypes.contains(menu.getType())
83 95 && menusStatuses.contains(menu.getStatus()))
84 96 .collect(Collectors.toList());
85 97 }
... ... @@ -91,10 +103,186 @@ public class SysMenuServiceImpl implements SysMenuService {
91 103 if (CollectionUtils.isAnyEmpty(menuIds, menuTypes, menusStatuses)) {
92 104 return Collections.emptyList();
93 105 }
94   - return menuCache.values().stream().filter(menu -> menuIds.contains(menu.getMenuId())
95   - && menuTypes.contains(menu.getMenuType())
  106 + return menuCache.values().stream().filter(menu -> menuIds.contains(menu.getId())
  107 + && menuTypes.contains(menu.getType())
96 108 && menusStatuses.contains(menu.getStatus()))
97 109 .collect(Collectors.toList());
98 110 }
99 111  
  112 + @Override
  113 + public Long createMenu(SysMenuCreateReqVO reqVO) {
  114 + // 校验父菜单存在
  115 + checkParentResource(reqVO.getParentId(), null);
  116 + // 校验菜单(自己)
  117 + checkResource(reqVO.getParentId(), reqVO.getName(), null);
  118 + // 插入数据库
  119 + SysMenuDO menu = SysMenuConvert.INSTANCE.convert(reqVO);
  120 + initMenuProperty(menu);
  121 + menuMapper.insert(menu);
  122 + // 返回
  123 + return menu.getId();
  124 + }
  125 +
  126 + @Override
  127 + public void updateMenu(SysMenuUpdateReqVO reqVO) {
  128 + // 校验更新的菜单是否存在
  129 + if (menuMapper.selectById(reqVO.getId()) == null) {
  130 + throw ServiceExceptionUtil.exception(MENU_NOT_EXISTS);
  131 + }
  132 + // 校验父菜单存在
  133 + checkParentResource(reqVO.getParentId(), reqVO.getId());
  134 + // 校验菜单(自己)
  135 + checkResource(reqVO.getParentId(), reqVO.getName(), reqVO.getId());
  136 + // 更新到数据库
  137 + SysMenuDO updateObject = SysMenuConvert.INSTANCE.convert(reqVO);
  138 + initMenuProperty(updateObject);
  139 + menuMapper.updateById(updateObject);
  140 + }
  141 +
  142 + /**
  143 + * 删除菜单
  144 + *
  145 + * @param menuId 菜单编号
  146 + */
  147 + public void deleteMenu(Long menuId) {
  148 + // 校验更新的菜单是否存在
  149 + if (menuMapper.selectById(menuId) == null) {
  150 + throw ServiceExceptionUtil.exception(MENU_NOT_EXISTS);
  151 + }
  152 + // 校验是否还有子菜单
  153 + if (menuMapper.selectCountByParentId(menuId) > 0) {
  154 + throw ServiceExceptionUtil.exception(MENU_EXISTS_CHILDREN);
  155 + }
  156 + // 校验删除的菜单是否存在
  157 + if (menuMapper.selectById(menuId) == null) {
  158 + throw ServiceExceptionUtil.exception(MENU_NOT_EXISTS);
  159 + }
  160 + // 标记删除
  161 + menuMapper.deleteById(menuId);
  162 + // 删除授予给角色的权限
  163 + permissionService.deleteRolesMenuByMenuId(menuId);
  164 + }
  165 +
  166 + @Override
  167 + public SysMenuDO getMenu(Long id) {
  168 + return menuMapper.selectById(id);
  169 + }
  170 +
  171 +// /**
  172 +// * 获得菜单列表
  173 +// *
  174 +// * @param menuIds 菜单编号列表
  175 +// * @return 菜单列表
  176 +// */
  177 +// public List<ResourceBO> listResources(List<Integer> menuIds) {
  178 +// List<ResourceDO> menuDOs = menuMapper.selectBatchIds(menuIds);
  179 +// return ResourceConvert.INSTANCE.convertList(menuDOs);
  180 +// }
  181 +//
  182 +// /**
  183 +// * 获得菜单全列表
  184 +// *
  185 +// * @return 菜单全列表
  186 +// */
  187 +// public List<ResourceBO> listResources() {
  188 +// List<ResourceDO> menuDOs = menuMapper.selectList(null);
  189 +// return ResourceConvert.INSTANCE.convertList(menuDOs);
  190 +// }
  191 +//
  192 +// /**
  193 +// * 获得指定类型的菜单列表
  194 +// *
  195 +// * @param type 菜单类型,允许空
  196 +// * @return 菜单列表
  197 +// */
  198 +// public List<ResourceBO> listResourcesByType(Integer type) {
  199 +// List<ResourceDO> menuDOs = menuMapper.selectListByType(type);
  200 +// return ResourceConvert.INSTANCE.convertList(menuDOs);
  201 +// }
  202 +//
  203 +// /**
  204 +// * 获得角色拥有的菜单列表
  205 +// *
  206 +// * @param roleIds 角色编号
  207 +// * @param type 菜单类型,允许空
  208 +// * @return 菜单列表
  209 +// */
  210 +// public List<ResourceBO> listRoleResourcesByType(Collection<Integer> roleIds, Integer type) {
  211 +// List<RoleResourceDO> roleResourceDOs = roleResourceMapper.selectListByRoleIds(roleIds);
  212 +// if (CollectionUtils.isEmpty(roleResourceDOs)) {
  213 +// return Collections.emptyList();
  214 +// }
  215 +// List<ResourceDO> menuDOs = menuMapper.selectListByIdsAndType(
  216 +// CollectionUtils.convertSet(roleResourceDOs, RoleResourceDO::getResourceId), type);
  217 +// return ResourceConvert.INSTANCE.convertList(menuDOs);
  218 +// }
  219 +//
  220 + /**
  221 + * 校验父菜单是否合法
  222 + *
  223 + * 1. 不能设置自己为父菜单
  224 + * 2. 父菜单不存在
  225 + * 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型
  226 + *
  227 + * @param parentId 父菜单编号
  228 + * @param childId 当前菜单编号
  229 + */
  230 + private void checkParentResource(Long parentId, Long childId) {
  231 + if (parentId == null || MenuIdEnum.ROOT.getId().equals(parentId)) {
  232 + return;
  233 + }
  234 + // 不能设置自己为父菜单
  235 + if (parentId.equals(childId)) {
  236 + throw ServiceExceptionUtil.exception(MENU_PARENT_ERROR);
  237 + }
  238 + SysMenuDO menu = menuMapper.selectById(parentId);
  239 + // 父菜单不存在
  240 + if (menu == null) {
  241 + throw ServiceExceptionUtil.exception(MENU_PARENT_NOT_EXISTS);
  242 + }
  243 + // 父菜单必须是目录类型
  244 + if (!MenuTypeEnum.DIR.getType().equals(menu.getType())) {
  245 + throw ServiceExceptionUtil.exception(MENU_PARENT_NOT_MENU);
  246 + }
  247 + }
  248 +
  249 + /**
  250 + * 校验菜单是否合法
  251 + *
  252 + * 1. 校验相同父菜单编号下,是否存在相同的菜单名
  253 + *
  254 + * @param name 菜单名字
  255 + * @param parentId 父菜单编号
  256 + * @param id 菜单编号
  257 + */
  258 + private void checkResource(Long parentId, String name, Long id) {
  259 + SysMenuDO menu = menuMapper.selectByParentIdAndName(parentId, name);
  260 + if (menu == null) {
  261 + return;
  262 + }
  263 + // 如果 menuId 为空,说明不用比较是否为相同 menuId 的菜单
  264 + if (id == null) {
  265 + throw ServiceExceptionUtil.exception(MENU_NAME_DUPLICATE);
  266 + }
  267 + if (!menu.getId().equals(id)) {
  268 + throw ServiceExceptionUtil.exception(MENU_NAME_DUPLICATE);
  269 + }
  270 + }
  271 +
  272 + /**
  273 + * 初始化菜单的通用属性。
  274 + *
  275 + * 例如说,只有目录或者菜单类型的菜单,才设置 icon
  276 + *
  277 + * @param menu 菜单
  278 + */
  279 + private void initMenuProperty(SysMenuDO menu) {
  280 + // 菜单为按钮类型时,无需 component、icon、path 属性,进行置空
  281 + if (MenuTypeEnum.BUTTON.getType().equals(menu.getType())) {
  282 + menu.setComponent("");
  283 + menu.setIcon("");
  284 + menu.setPath("");
  285 + }
  286 + }
  287 +
100 288 }
... ...
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysPermissionServiceImpl.java
... ... @@ -107,4 +107,9 @@ public class SysPermissionServiceImpl implements SysPermissionService {
107 107 return roleDept != null ? roleDept.getRoleId() : null;
108 108 }
109 109  
  110 + @Override
  111 + public void deleteRolesMenuByMenuId(Long menuId) {
  112 + // TODO 实现我
  113 + }
  114 +
110 115 }
... ...