refactor(core): refactor menu

This commit is contained in:
qianmoQ 2024-11-25 20:45:06 +08:00
parent fce43fc791
commit 437036728f
16 changed files with 398 additions and 98 deletions

View File

@ -1,20 +1,12 @@
package io.edurt.datacap.server.controller.admin;
import io.edurt.datacap.common.response.CommonResponse;
import io.edurt.datacap.server.controller.BaseController;
import io.edurt.datacap.service.entity.MenuEntity;
import io.edurt.datacap.service.repository.admin.MenuRepository;
import io.edurt.datacap.service.service.MenuService;
import io.edurt.datacap.service.validation.ValidationGroup;
import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping(value = "/api/v1/menu")
public class MenuController
@ -29,13 +21,4 @@ public class MenuController
this.repository = repository;
this.service = service;
}
@RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT})
public CommonResponse<MenuEntity> saveOrUpdate(@RequestBody @Validated(ValidationGroup.Crud.Create.class) MenuEntity configure)
{
if (StringUtils.isEmpty(configure.getCode())) {
configure.setCode(UUID.randomUUID().toString());
}
return this.service.saveOrUpdate(repository, configure);
}
}

View File

@ -4,6 +4,7 @@ import io.edurt.datacap.common.response.CommonResponse;
import io.edurt.datacap.server.controller.BaseController;
import io.edurt.datacap.service.annotation.DynamicJsonView;
import io.edurt.datacap.service.entity.UserEntity;
import io.edurt.datacap.service.record.TreeRecord;
import io.edurt.datacap.service.repository.RoleRepository;
import io.edurt.datacap.service.repository.UserRepository;
import io.edurt.datacap.service.service.UserLogService;
@ -14,6 +15,8 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/user")
@ -40,7 +43,7 @@ public class UserController
{
return this.service.info(id);
}
//
// @PutMapping(value = "changePassword")
// public CommonResponse<Long> changePassword(@Validated @RequestBody UserPasswordBody configure)
// {
@ -71,11 +74,12 @@ public class UserController
// return this.service.getSugs(id);
// }
//
// @GetMapping(value = "menus")
// public CommonResponse<List<TreeRecord>> getMenus()
// {
// return this.service.getMenus();
// }
@GetMapping(value = "menus")
@DynamicJsonView
public CommonResponse<List<TreeRecord>> getMenus()
{
return this.service.getMenus();
}
//
// @PutMapping(value = "allocationRole")
// public CommonResponse<UserEntity> allocationRole(@RequestBody UserRole configure)

View File

@ -18,7 +18,6 @@ import org.springframework.web.context.request.RequestContextHolder;
@Order(1)
public class DynamicJsonViewAspect
{
@Around("@annotation(dynamicJsonView)")
public Object around(ProceedingJoinPoint point, DynamicJsonView dynamicJsonView)
throws Throwable

View File

@ -18,17 +18,16 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
public class DynamicJsonViewHttpMessageConverter
implements ResponseBodyAdvice<Object>
{
private final ObjectMapper objectMapper;
public DynamicJsonViewHttpMessageConverter(ObjectMapper objectMapper)
{
this.objectMapper = objectMapper;
log.info("DynamicJsonViewResponseBodyAdvice initialized");
log.info("DynamicJsonViewHttpMessageConverter initialized");
}
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType)
public boolean supports(@NotNull MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType)
{
return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}

View File

@ -12,8 +12,10 @@ import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@ -29,6 +31,7 @@ import java.util.UUID;
@NoArgsConstructor
@AllArgsConstructor
@MappedSuperclass
@EntityListeners(value = AuditingEntityListener.class)
@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"},
justification = "I prefer to suppress these FindBugs warnings")
public class BaseEntity
@ -48,7 +51,7 @@ public class BaseEntity
private String code;
@Column(name = "active")
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
@JsonView(value = {EntityView.AdminView.class})
private boolean active = true;
@Column(name = "create_time")

View File

@ -1,8 +1,11 @@
package io.edurt.datacap.service.entity;
import com.fasterxml.jackson.annotation.JsonView;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.edurt.datacap.common.view.EntityView;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
@ -24,6 +27,7 @@ import java.util.Set;
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "datacap_role")
@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", "EQ_OVERRIDING_EQUALS_NOT_SYMMETRIC"},
@ -32,6 +36,7 @@ public class RoleEntity
extends BaseEntity
{
@Column(name = "description", columnDefinition = "varchar(1000)")
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private String description;
@ManyToMany(fetch = FetchType.LAZY)
@ -48,11 +53,15 @@ public class RoleEntity
public boolean isDefault()
{
return this.getCode().equals("ROLE_ADMIN") || this.getCode().equals("ROLE_USER") ? true : false;
return this.getCode().equals("ROLE_ADMIN") || this.getCode().equals("ROLE_USER");
}
public String getCode()
{
if (getName() == null) {
return this.code;
}
return String.format("ROLE_%s", this.getName().toUpperCase());
}
}

View File

@ -1,7 +1,9 @@
package io.edurt.datacap.service.record;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonView;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.edurt.datacap.common.view.EntityView;
import io.edurt.datacap.service.entity.MenuEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
@ -20,18 +22,43 @@ import java.util.List;
@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"})
public class TreeRecord
{
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
List<TreeRecord> children;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private Long id;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private String title;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private String url;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private Integer sorted;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private String code;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private String i18nKey;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private String icon;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private boolean isNew;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private Boolean checked = false;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private Boolean selected = false;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private String description;
@JsonView(value = {EntityView.UserView.class, EntityView.AdminView.class})
private Long parent;
public static TreeRecord of(MenuEntity entity, boolean checked, boolean selected, List<TreeRecord> children)

View File

@ -2,7 +2,9 @@ package io.edurt.datacap.service.security;
import com.fasterxml.jackson.annotation.JsonIgnore;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.edurt.datacap.service.entity.RoleEntity;
import io.edurt.datacap.service.entity.UserEntity;
import org.apache.commons.compress.utils.Sets;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@ -12,6 +14,7 @@ import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"})
@ -39,7 +42,7 @@ public class UserDetailsService
public static UserDetailsService build(UserEntity user)
{
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(String.valueOf(role.getId())))
.map(role -> new SimpleGrantedAuthority(role.getCode()))
.collect(Collectors.toList());
String avatar = null;
if (user.getAvatarConfigure() != null) {
@ -115,6 +118,15 @@ public class UserDetailsService
UserDetailsService loginPrincipalUserInfo = (UserDetailsService) principal;
userInfo.setUsername(loginPrincipalUserInfo.getUsername());
userInfo.setId(loginPrincipalUserInfo.getId());
Set<RoleEntity> roles = Sets.newHashSet();
loginPrincipalUserInfo.getAuthorities()
.forEach(v -> {
roles.add(RoleEntity.builder()
.code(v.getAuthority())
.build());
});
userInfo.setRoles(roles);
}
}
return userInfo;

View File

@ -1,6 +1,7 @@
<template>
<ShadcnDrawer v-model="visible" :title="title" width="40%">
<ShadcnSpin v-if="loading" fixed/>
<ShadcnForm v-model="formState" v-if="formState" @on-submit="onSubmit">
<ShadcnRow class="space-x-2">
<ShadcnCol span="6">

View File

@ -0,0 +1,84 @@
---
title: 修改菜单 (管理员)
---
请求地址:`/api/v1/menu`
请求方式:`PUT`
## Body
=== "示例"
```json
{
"code": "10ae626195704b96b353d00aeb6ab1e5",
"name": "测试菜单",
"description": "",
"url": "/test",
"sorted": "01",
"type": "VIEW",
"active": true,
"i18nKey": "common.home",
"icon": "",
"new": false
}
```
=== "参数"
|参数|类型| 描述 |
|---|---|---------------------|
|`code`|String| 菜单code |
|`name`|String| 菜单名称 |
|`description`|String| 菜单描述 |
|`url`|String| 菜单路径 |
|`sorted`|String| 菜单排序 |
|`type`|String| 菜单类型 |
|`active`|Boolean| 是否激活 |
|`i18nKey`|String| 国际化key |
|`icon`|String| 图标 |
|`new`|Boolean| 是否为新菜单 |
## Response
=== "示例"
```json
{
"name": "测试菜单",
"code": "10ae626195704b96b353d00aeb6ab1e5",
"createTime": null,
"updateTime": "2024-11-25 20:32:53",
"description": "",
"url": "/test",
"group": null,
"sorted": 1,
"type": "VIEW",
"parent": 0,
"i18nKey": "common.home",
"icon": "",
"redirect": 0,
"isNew": false,
"new": false
}
```
=== "参数"
|参数|类型| 描述 |
|---|---|---------------------|
|`name`|String| 菜单名称 |
|`code`|String| 菜单code |
|`createTime`|String| 创建时间 |
|`updateTime`|String| 更新时间 |
|`description`|String| 菜单描述 |
|`url`|String| 菜单路径 |
|`group`|String| 菜单分组 |
|`sorted`|Number| 菜单排序 |
|`type`|String| 菜单类型 |
|`parent`|Number| 父菜单 |
|`i18nKey`|String| 国际化key |
|`icon`|String| 图标 |
|`redirect`|Number| 跳转地址 |
|`isNew`|Boolean| 是否为新菜单 |

View File

@ -8,14 +8,44 @@ title: 菜单列表 (管理员)
!!! note
该接口用于获取菜单列表,需要传递分页数据 `page``size` 作为参数,分页数据默认为 `1``size` 默认为 `12`。详细参考 [分页实体](../common/page.md)
该接口用于获取菜单列表,需要传递分页数据 `page``size` 作为参数,分页数据默认为 `1``size` 默认为 `10`。详细参考 [分页实体](../common/page.md)
## Response
=== "示例"
```json
[
{
"id": 1,
"title": "全局 - 首页",
"url": "/home",
"sorted": 1,
"code": "HOME",
"i18nKey": "common.home",
"icon": "Home",
"isNew": false,
"checked": true,
"selected": true,
"parent": 0,
"new": false
}
]
```
=== "参数"
| 参数名 | 类型 | 描述 |
| --- |----| --- |
| `id` | 数值 | ID |
| `title` | 字符串 | 标题 |
| `url` | 字符串 | URL |
| `sorted` | 数值 | 排序 |
| `code` | 字符串 | 代码 |
| `i18nKey` | 字符串 | 国际化 |
| `icon` | 字符串 | 图标 |
| `isNew` | 布尔 | 是否新建 |
| `checked` | 布尔 | 是否选中 |
| `selected` | 布尔 | 是否选中 |
| `parent` | 数值 | 父菜单 |
| `new` | 布尔 | 是否新建|

View File

@ -0,0 +1,82 @@
---
title: 保存菜单 (管理员)
---
请求地址:`/api/v1/menu`
请求方式:`POST`
## Body
=== "示例"
```json
{
"name": "测试菜单",
"description": "",
"url": "/test",
"sorted": "01",
"type": "VIEW",
"active": true,
"i18nKey": "common.home",
"icon": "",
"new": false
}
```
=== "参数"
|参数|类型| 描述 |
|---|---|---------------------|
|`name`|String| 菜单名称 |
|`description`|String| 菜单描述 |
|`url`|String| 菜单路径 |
|`sorted`|String| 菜单排序 |
|`type`|String| 菜单类型 |
|`active`|Boolean| 是否激活 |
|`i18nKey`|String| 国际化key |
|`icon`|String| 图标 |
|`new`|Boolean| 是否为新菜单 |
## Response
=== "示例"
```json
{
"name": "测试菜单",
"code": "10ae626195704b96b353d00aeb6ab1e5",
"createTime": null,
"updateTime": "2024-11-25 20:32:53",
"description": "",
"url": "/test",
"group": null,
"sorted": 1,
"type": "VIEW",
"parent": 0,
"i18nKey": "common.home",
"icon": "",
"redirect": 0,
"isNew": false,
"new": false
}
```
=== "参数"
|参数|类型| 描述 |
|---|---|---------------------|
|`name`|String| 菜单名称 |
|`code`|String| 菜单code |
|`createTime`|String| 创建时间 |
|`updateTime`|String| 更新时间 |
|`description`|String| 菜单描述 |
|`url`|String| 菜单路径 |
|`group`|String| 菜单分组 |
|`sorted`|Number| 菜单排序 |
|`type`|String| 菜单类型 |
|`parent`|Number| 父菜单 |
|`i18nKey`|String| 国际化key |
|`icon`|String| 图标 |
|`redirect`|Number| 跳转地址 |
|`isNew`|Boolean| 是否为新菜单 |

View File

@ -0,0 +1,47 @@
---
title: 用户信息
---
请求地址: `/api/v1/user`
请求方式: `GET`
## Response
=== "示例"
```json
{
"id": 1,
"name": null,
"code": null,
"active": true,
"createTime": "2023-07-04 21:47:24",
"updateTime": "2024-11-03 19:38:47",
"username": "admin",
"chatConfigure": "{\"type\":\"ChatGPT\",\"host\":null,\"token\":null,\"timeout\":0,\"contentCount\":5}",
"system": false,
"editorConfigure": {},
"avatarConfigure": {
"path": ""
},
"roles": []
}
```
=== "参数"
| 参数名 | 类型 | 描述 | 依赖 |
| --- |----| --- |---------|
| `id` | 数值 | 用户ID | `ADMIN` |
|`name` | 字符串 | 用户名 | |
|`code` | 字符串 | 用户编码 | |
|`active` | 布尔值 | 是否激活 | `ADMIN` |
|`createTime` | 字符串 | 创建时间 | |
|`updateTime` | 字符串 | 更新时间 | |
|`username` | 字符串 | 用户名 | |
|`chatConfigure` | 字符串 | 聊天配置 | |
|`system` | 布尔值 | 是否为系统用户 | `ADMIN` |
|`editorConfigure` | 字符串 | 编辑器配置 | |
|`avatarConfigure` | 字符串 | 头像配置 | |
|`roles` | 数组 | 用户角色 | |

View File

@ -15,7 +15,6 @@ title: 用户列表 (管理员)
=== "示例"
```json
[
{
"id": 1,
"name": null,
@ -24,59 +23,29 @@ title: 用户列表 (管理员)
"createTime": "2023-07-04 21:47:24",
"updateTime": "2024-11-03 19:38:47",
"username": "admin",
"password": "$2a$10$FyWYvR61FHzT1szZtV69j.APDCrAqRcqMO.CiUYOSRiXmvugDsALu",
"chatConfigure": "{\"type\":\"ChatGPT\",\"host\":null,\"token\":null,\"timeout\":0,\"contentCount\":5}",
"system": false,
"editorConfigure": {
"fontSize": 18,
"theme": "tomorrow"
},
"editorConfigure": {},
"avatarConfigure": {
"path": ""
},
"roles": [
{
"code": "ROLE_ADMIN",
"description": "这是管理员路由,可以管理站点所有功能"
"roles": []
}
],
"sources": [
{
"id": 2,
"name": "MySQL",
"code": "9dbb343af37445b4891e40e2b8dafe84",
"active": true,
"createTime": "2024-07-07 19:41:39",
"updateTime": "2024-11-24 10:18:51",
"description": null,
"type": "MySQL",
"protocol": "JDBC",
"host": "127.0.0.1",
"port": 3306,
"username": "root",
"password": "12345678",
"catalog": null,
"database": "datacap",
"ssl": false,
"usedConfig": false,
"version": "8.3.0",
"available": true,
"message": null,
"configures": {
"useOldAliasMetadataBehavior": true
},
"schema": null,
"pipelines": null,
"home": null,
"user": {
"id": 1,
"username": "admin"
},
"public": null
}
]
}
]
```
=== "参数"
| 参数名 | 类型 | 描述 | 依赖 |
| --- |----| --- |---------|
| `id` | 数值 | 用户ID | `ADMIN` |
|`name` | 字符串 | 用户名 | |
|`code` | 字符串 | 用户编码 | |
|`active` | 布尔值 | 是否激活 | `ADMIN` |
|`createTime` | 字符串 | 创建时间 | |
|`updateTime` | 字符串 | 更新时间 | |
|`username` | 字符串 | 用户名 | |
|`chatConfigure` | 字符串 | 聊天配置 | |
|`system` | 布尔值 | 是否为系统用户 | `ADMIN` |
|`editorConfigure` | 字符串 | 编辑器配置 | |
|`avatarConfigure` | 字符串 | 头像配置 | |
|`roles` | 数组 | 用户角色 | |

View File

@ -0,0 +1,47 @@
---
title: 用户菜单
---
请求地址: `/api/v1/user/menus`
请求方式: `GET`
## Response
=== "示例"
```json
[
{
"id": 1,
"title": "全局 - 首页",
"url": "/home",
"sorted": 1,
"code": "HOME",
"i18nKey": "common.home",
"icon": "Home",
"isNew": false,
"checked": true,
"selected": true,
"parent": 0,
"new": false
}
]
```
=== "参数"
| 参数名 | 类型 | 描述 |
| --- |----| --- |
| `id` | 数值 | ID |
| `title` | 字符串 | 标题 |
| `url` | 字符串 | URL |
| `sorted` | 数值 | 排序 |
| `code` | 字符串 | 代码 |
| `i18nKey` | 字符串 | 国际化 |
| `icon` | 字符串 | 图标 |
| `isNew` | 布尔 | 是否新建 |
| `checked` | 布尔 | 是否选中 |
| `selected` | 布尔 | 是否选中 |
| `parent` | 数值 | 父菜单 |
| `new` | 布尔 | 是否新建|

View File

@ -291,6 +291,8 @@ nav:
- ApiUser:
- api/user/register.md
- api/user/login.md
- api/user/info.md
- api/user/menu.md
- api/user/list.md
- ApiPlugin:
- api/plugin/plugin.md
@ -304,5 +306,7 @@ nav:
- api/dashboard/delete.md
- ApiMenu:
- api/menu/list.md
- api/menu/save.md
- api/menu/edit.md
- useCases.md
- partners.md