Compare commits
19 Commits
c25e96b93d
...
fae4d505de
Author | SHA1 | Date |
---|---|---|
|
fae4d505de | |
|
b5d716afbe | |
|
b66d0e02da | |
|
0b3c1f59e5 | |
|
5516fdddf9 | |
|
84a053fdb6 | |
|
44732e6352 | |
|
c23e55c819 | |
|
b01f915415 | |
|
27e3b6772f | |
|
90b6a285b3 | |
|
7652b505f7 | |
|
04e4a15136 | |
|
b799b81efc | |
|
7c49dc4b86 | |
|
84770198c8 | |
|
ae77d2905c | |
|
b4bb4f8418 | |
|
9d29b21171 |
|
@ -1,5 +1,7 @@
|
|||
# Spring Security 6 入门指南
|
||||
|
||||

|
||||
|
||||
## 基本配置
|
||||
|
||||
### 添加依赖
|
||||
|
@ -29,6 +31,63 @@ public class SecurityWebConfiguration {
|
|||
- 使用默认登录页:`.formLogin(Customizer.withDefaults())`
|
||||
- 禁用表单登录:`.formLogin(AbstractHttpConfigurer::disable)`
|
||||
|
||||
#### 配置示例
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityWebConfiguration {
|
||||
|
||||
private final DbUserDetailService dbUserDetailService;
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
http.authorizeHttpRequests(authorizeRequests ->
|
||||
// 访问路径为 /api 时需要进行认证
|
||||
authorizeRequests
|
||||
// 只认证 /api/** 下的所有接口
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
// 其余请求都放行
|
||||
.anyRequest().permitAll()
|
||||
)
|
||||
.formLogin(loginPage -> loginPage
|
||||
// 自定义登录页路径
|
||||
.loginPage("/login-page")
|
||||
// 处理登录的URL(默认就是/login)
|
||||
.loginProcessingUrl("/login")
|
||||
// 登录成功跳转
|
||||
.defaultSuccessUrl("/")
|
||||
// 登录失败跳转
|
||||
.failureUrl("/login-page?error=true")
|
||||
.permitAll()
|
||||
)
|
||||
// 使用默认的登录
|
||||
// .formLogin(Customizer.withDefaults())
|
||||
// 禁用表单登录
|
||||
// .formLogin(AbstractHttpConfigurer::disable)
|
||||
.logout(logout -> logout
|
||||
.logoutSuccessUrl("/login-page?logout=true")
|
||||
.permitAll()
|
||||
)
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.exceptionHandling(configurer -> configurer
|
||||
// 自定无权访问返回内容
|
||||
.accessDeniedHandler(new SecurityAccessDeniedHandler())
|
||||
// 自定义未授权返回内容
|
||||
.authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
|
||||
)
|
||||
|
||||
.userDetailsService(dbUserDetailService)
|
||||
;
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 认证与授权配置
|
||||
|
||||
### URL访问控制
|
||||
|
@ -340,4 +399,782 @@ public UserDetails getCurrentUserDetail() {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## URL资源认证配置
|
||||
|
||||
### 角色与权限配置
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> 1. **角色与权限的区别**:
|
||||
> - `hasRole()`会自动添加"ROLE_"前缀
|
||||
> - `hasAuthority()`直接使用指定的权限字符串
|
||||
> 2. **匹配顺序**:
|
||||
> - Spring Security会按照配置的顺序进行匹配
|
||||
> - 更具体的路径应该放在前面,通用规则(如anyRequest)放在最后
|
||||
> 3. **方法选择建议**:
|
||||
> - `hasRole()`/`hasAnyRole()`:适合基于角色的访问控制
|
||||
> - `hasAuthority()`/`hasAnyAuthority()`:适合更细粒度的权限控制
|
||||
> - `authenticated()`:只需认证通过,不检查具体角色/权限
|
||||
> - `permitAll()`:完全开放访问
|
||||
> 4. **最佳实践**:
|
||||
> - 对于REST API,通常使用`authenticated()`配合方法级权限控制
|
||||
> - 静态资源应明确配置`permitAll()`
|
||||
> - 生产环境不建议使用`anyRequest().permitAll()`
|
||||
|
||||
#### 1. 基于角色的URL访问控制
|
||||
|
||||
##### 单角色配置
|
||||
|
||||
配置`/api/**`路径下的所有接口需要`ADMIN`角色才能访问:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests(authorize -> authorize
|
||||
// 注意:会自动添加"ROLE_"前缀,实际检查的是ROLE_ADMIN
|
||||
.requestMatchers("/api/**").hasRole("ADMIN")
|
||||
)
|
||||
// 其他配置...
|
||||
;
|
||||
return http.build();
|
||||
}
|
||||
```
|
||||
|
||||
##### 多角色配置(满足任一角色即可访问)
|
||||
|
||||
```java
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests(authorize -> authorize
|
||||
// 检查是否有ADMIN或USER角色(自动添加ROLE_前缀)
|
||||
.requestMatchers("/api/**").hasAnyRole("ADMIN", "USER")
|
||||
)
|
||||
// 其他配置...
|
||||
;
|
||||
return http.build();
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 基于权限的URL访问控制
|
||||
|
||||
##### 需要所有指定权限
|
||||
|
||||
```java
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests(authorize -> authorize
|
||||
// 需要同时拥有"all"和"read"权限
|
||||
.requestMatchers("/api/**").hasAuthority("all")
|
||||
.requestMatchers("/api/**").hasAuthority("read")
|
||||
)
|
||||
// 其他配置...
|
||||
;
|
||||
return http.build();
|
||||
}
|
||||
```
|
||||
|
||||
##### 满足任一权限即可
|
||||
|
||||
```java
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests(authorize -> authorize
|
||||
// 拥有"all"或"read"任一权限即可访问
|
||||
.requestMatchers("/api/**").hasAnyAuthority("all", "read")
|
||||
)
|
||||
// 其他配置...
|
||||
;
|
||||
return http.build();
|
||||
}
|
||||
```
|
||||
|
||||
### 综合配置策略
|
||||
|
||||
#### 1. 基本配置模式
|
||||
|
||||
```java
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests(authorize -> authorize
|
||||
// 特定路径需要认证
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
// 其他请求全部放行
|
||||
.anyRequest().permitAll()
|
||||
)
|
||||
// 其他配置...
|
||||
;
|
||||
return http.build();
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 多路径匹配配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// 定义无需认证的白名单路径
|
||||
String[] permitAllUrls = {
|
||||
"/", "/doc.html/**",
|
||||
"/webjars/**", "/images/**",
|
||||
"/.well-known/**", "/favicon.ico",
|
||||
"/error/**", "/swagger-ui/**",
|
||||
"/v3/api-docs/**"
|
||||
};
|
||||
|
||||
http.authorizeHttpRequests(authorize -> authorize
|
||||
// API路径需要认证
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
// 白名单路径直接放行
|
||||
.requestMatchers(permitAllUrls).permitAll()
|
||||
// 其他请求需要登录(非匿名访问)
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
// 其他配置...
|
||||
;
|
||||
return http.build();
|
||||
}
|
||||
```
|
||||
|
||||
### 基于方法的授权
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 通过在任何 `@Configuration` 类上添加 `@EnableMethodSecurity` 注解。
|
||||
>
|
||||
> Spring Boot Starter Security 默认情况下不会激活方法级别的授权。
|
||||
|
||||
#### 提供的注解
|
||||
|
||||
1. @PreAuthorize
|
||||
2. @PostAuthorize
|
||||
3. @PreFilter
|
||||
4. @PostFilter
|
||||
|
||||
## 关于UserDetailsService的深入解析
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> 在SpringSecurity6中版本是6.3.10,如果显式的为User设置角色,在示例的Security上下文中时获取不到roles相关信息的,只能获取到authorities信息。
|
||||
>
|
||||
> 如果需要使用角色判断需要将角色的内容和权限内容一并放到authorities中。
|
||||
>
|
||||
> 在SpringSecurity6中不用显式的为角色添加`ROLE_`像这样的字符串,Security会为我们亲自加上,如果加上会有异常抛出:`ROLE_USER cannot start with ROLE_ (it is automatically added)...`
|
||||
|
||||
```java
|
||||
// 设置用户权限
|
||||
return User.builder()
|
||||
.username(userEntity.getUsername())
|
||||
.password(userEntity.getPassword())
|
||||
.roles(roles)
|
||||
// 设置用户 authorities
|
||||
.authorities(authorities)
|
||||
.build();
|
||||
```
|
||||
|
||||
如有上述需要可以尝试这样写。
|
||||
|
||||
```java
|
||||
// 设置用户角色
|
||||
String[] roles = findUserRolesByUserId(userId);
|
||||
|
||||
// 设置用户权限
|
||||
List<String> permissionsByUserId = findPermissionByUserId(userId);
|
||||
String[] permissions = permissionsByUserId.toArray(String[]::new);
|
||||
|
||||
// 也可以转成下面的形式
|
||||
// List<String> permissions = permissionsByUserId.stream()
|
||||
// .map(SimpleGrantedAuthority::new)
|
||||
// .toList();
|
||||
|
||||
String[] authorities = ArrayUtils.addAll(roles, permissions);
|
||||
|
||||
// 设置用户权限
|
||||
return User.builder()
|
||||
.username(userEntity.getUsername())
|
||||
.password(userEntity.getPassword())
|
||||
// 设置用户 authorities
|
||||
.authorities(authorities)
|
||||
.build();
|
||||
```
|
||||
|
||||
### 简单阐述
|
||||
|
||||
#### 1. UserDetailsService的核心作用
|
||||
|
||||
`UserDetailsService`是Spring Security的核心接口,负责提供用户认证数据。它只有一个核心方法:
|
||||
|
||||
```java
|
||||
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
|
||||
```
|
||||
|
||||
当用户尝试登录时,Spring Security会自动调用这个方法来获取用户详情。
|
||||
|
||||
#### 2. 为什么不需要手动校验密码?
|
||||
|
||||
在标准的表单登录流程中,Spring Security的认证流程会自动处理密码校验,这是因为:
|
||||
|
||||
1. **自动集成密码编码器**:
|
||||
Spring Security会自动使用配置的`PasswordEncoder`来比对:
|
||||
|
||||
- 用户提交的明文密码
|
||||
- 数据库中存储的加密密码(通过`UserDetails`返回)
|
||||
|
||||
2. **认证流程内部处理**:
|
||||
认证管理器(`AuthenticationManager`)会自动处理以下流程:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[用户提交凭证] --> B[调用UserDetailsService]
|
||||
B --> C[获取UserDetails]
|
||||
C --> D[PasswordEncoder比对密码]
|
||||
D --> E[认证成功/失败]
|
||||
```
|
||||
|
||||
#### 3. 完整的安全配置示例
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final DbUserDetailService dbUserDetailService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
.anyRequest().permitAll()
|
||||
)
|
||||
.formLogin(form -> form
|
||||
.loginProcessingUrl("/login")
|
||||
.permitAll()
|
||||
)
|
||||
// 即使不显式设置也会自动生效
|
||||
.userDetailsService(dbUserDetailService)
|
||||
// 必须配置PasswordEncoder
|
||||
.authenticationManager(authenticationManager(http));
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
||||
return http.getSharedObject(AuthenticationManagerBuilder.class)
|
||||
.userDetailsService(dbUserDetailService)
|
||||
.passwordEncoder(passwordEncoder)
|
||||
.and()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 关键注意事项
|
||||
|
||||
1. **必须提供PasswordEncoder**:
|
||||
如果没有配置,会出现`There is no PasswordEncoder mapped`错误
|
||||
|
||||
```java
|
||||
@Bean
|
||||
PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
```
|
||||
|
||||
2. **UserDetails实现要求**:
|
||||
你的自定义`UserDetails`实现必须包含:
|
||||
|
||||
- 正确的用户名
|
||||
- 加密后的密码
|
||||
- 账号状态信息(是否过期/锁定等)
|
||||
|
||||
3. **自动发现机制**:
|
||||
当以下条件满足时,Spring Boot会自动配置:
|
||||
|
||||
- 容器中存在唯一的`UserDetailsService`实现
|
||||
- 存在`PasswordEncoder` bean
|
||||
- 没有显式配置`AuthenticationManager`
|
||||
|
||||
#### 5. 扩展场景
|
||||
|
||||
如果需要自定义认证逻辑(如增加验证码校验),可以:
|
||||
|
||||
```java
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CustomAuthProvider implements AuthenticationProvider {
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication auth) {
|
||||
// 自定义逻辑
|
||||
UserDetails user = userDetailsService.loadUserByUsername(auth.getName());
|
||||
// 手动密码比对
|
||||
if (!passwordEncoder.matches(auth.getCredentials().toString(), user.getPassword())) {
|
||||
throw new BadCredentialsException("密码错误");
|
||||
}
|
||||
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然后在配置中注册:
|
||||
|
||||
```java
|
||||
http.authenticationProvider(customAuthProvider);
|
||||
```
|
||||
|
||||
#### 总结对比表
|
||||
|
||||
| 场景 | 需要手动处理 | 自动处理 |
|
||||
| ------------ | ---------------------------------- | ----------------------- |
|
||||
| 用户查找 | 实现`loadUserByUsername()` | ✅ |
|
||||
| 密码比对 | ❌ | 由`PasswordEncoder`处理 |
|
||||
| 账号状态检查 | 通过`UserDetails`返回的状态 | ✅ |
|
||||
| 权限加载 | 通过`UserDetails.getAuthorities()` | ✅ |
|
||||
|
||||
这样设计的好处是:开发者只需关注业务数据获取(用户信息查询),安全相关的校验逻辑由框架统一处理,既保证了安全性又减少了重复代码。
|
||||
|
||||
### 获取角色与权限
|
||||
|
||||
#### 1. 角色信息处理
|
||||
|
||||
在`UserDetailsService`实现中获取并设置用户角色:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
// 1. 查询用户基本信息
|
||||
UserEntity userEntity = userMapper.selectByUsername(username);
|
||||
if (userEntity == null) {
|
||||
throw new UsernameNotFoundException("用户不存在");
|
||||
}
|
||||
|
||||
// 2. 获取角色信息(自动添加ROLE_前缀)
|
||||
String[] roles = findUserRolesByUserId(userEntity.getId());
|
||||
|
||||
// 3. 获取权限信息
|
||||
List<String> permissions = findPermissionsByUserId(userEntity.getId());
|
||||
|
||||
return User.builder()
|
||||
.username(userEntity.getUsername())
|
||||
.password(userEntity.getPassword())
|
||||
// 角色会自动添加ROLE_前缀
|
||||
.roles(roles)
|
||||
// 直接作为权限字符串使用
|
||||
.authorities(permissions.toArray(new String[0]))
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
**关键说明**:
|
||||
|
||||
- `roles()`方法会自动为角色添加`ROLE_`前缀(如`ADMIN`会变成`ROLE_ADMIN`)
|
||||
- 角色和权限在Spring Security中是不同概念,角色本质是带有特殊前缀的权限
|
||||
|
||||
#### 2. 权限信息处理(两种方式)
|
||||
|
||||
**方式一:直接使用字符串**
|
||||
|
||||
```java
|
||||
List<String> permissions = findPermissionsByUserId(userId);
|
||||
return User.withUsername(username)
|
||||
.authorities(permissions.toArray(new String[0]))
|
||||
// ...
|
||||
.build();
|
||||
```
|
||||
|
||||
**方式二:转换为SimpleGrantedAuthority**
|
||||
|
||||
```java
|
||||
List<GrantedAuthority> authorities = permissions.stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return User.withUsername(username)
|
||||
.authorities(authorities)
|
||||
// ...
|
||||
.build();
|
||||
```
|
||||
|
||||
**权限实现类对比**:
|
||||
|
||||
| 实现类 | 适用场景 | 特点 |
|
||||
| ---------------------------- | ------------- | ---------------- |
|
||||
| `SimpleGrantedAuthority` | 普通权限/角色 | 最常用实现 |
|
||||
| `SwitchUserGrantedAuthority` | 用户切换场景 | 包含原始用户信息 |
|
||||
| `JaasGrantedAuthority` | JAAS集成 | 用于Java认证服务 |
|
||||
|
||||
#### 3. Mapper配置优化
|
||||
|
||||
**角色查询Mapper**:
|
||||
|
||||
```xml
|
||||
<select id="selectRolesByUserId" resultType="com.example.entity.Role">
|
||||
SELECT r.*
|
||||
FROM t_role r
|
||||
JOIN t_user_role ur ON r.id = ur.role_id
|
||||
WHERE ur.user_id = #{userId}
|
||||
</select>
|
||||
```
|
||||
|
||||
**权限查询Mapper**:
|
||||
|
||||
```xml
|
||||
<select id="selectPermissionsByUserId" resultType="java.lang.String">
|
||||
SELECT p.permission_code
|
||||
FROM t_permission p
|
||||
JOIN t_role_permission rp ON p.id = rp.permission_id
|
||||
JOIN t_user_role ur ON rp.role_id = ur.role_id
|
||||
WHERE ur.user_id = #{userId}
|
||||
</select>
|
||||
```
|
||||
|
||||
#### 4. 重要注意事项
|
||||
|
||||
1. **角色与权限的存储建议**:
|
||||
|
||||
- 角色建议存储为`ADMIN`、`USER`等形式
|
||||
- 权限建议存储为`user:read`、`order:delete`等具体操作
|
||||
|
||||
2. **性能优化**:
|
||||
|
||||
```java
|
||||
// 使用一次查询获取所有权限信息(避免N+1查询)
|
||||
@Select("SELECT p.permission_code FROM ... WHERE ur.user_id = #{userId}")
|
||||
List<String> selectAllUserPermissions(@Param("userId") Long userId);
|
||||
```
|
||||
|
||||
3. **Spring Security的默认行为**:
|
||||
|
||||
- 如果同时配置`roles()`和`authorities()`,后者会覆盖前者
|
||||
- 推荐统一使用`authorities()`方法处理所有授权信息
|
||||
|
||||
4. **最佳实践示例**:
|
||||
|
||||
```java
|
||||
List<GrantedAuthority> authorities = new ArrayList<>();
|
||||
// 添加角色(手动添加ROLE_前缀)
|
||||
roles.forEach(role ->
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_" + role)));
|
||||
// 添加权限
|
||||
authorities.addAll(permissions.stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.toList());
|
||||
|
||||
return User.builder()
|
||||
// ...
|
||||
.authorities(authorities)
|
||||
.build();
|
||||
```
|
||||
|
||||
#### 5. 完整流程示意图
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[登录请求] --> B[调用UserDetailsService]
|
||||
B --> C[查询用户基本信息]
|
||||
C --> D[查询用户角色]
|
||||
C --> E[查询用户权限]
|
||||
D --> F[构建UserDetails对象]
|
||||
E --> F
|
||||
F --> G[返回认证结果]
|
||||
```
|
||||
|
||||
## 注解使用
|
||||
|
||||
### 1. 基础使用说明
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> 使用权限注解时需注意:
|
||||
>
|
||||
> - `hasAuthority()` 严格区分大小写
|
||||
> - 类级别注解会被方法级别注解覆盖
|
||||
> - 默认需要启用注解支持:`@EnableMethodSecurity`
|
||||
|
||||
如果需要使用某个注解,直接在方法加上即可,当然也可以加载类上面。
|
||||
|
||||
如果类上面也加了注解,方法也加了,那么方法的会覆盖掉类上的。
|
||||
|
||||
```java
|
||||
@PreAuthorize("hasAuthority('permission:read')")
|
||||
@PostAuthorize("returnObject.data == authentication.name")
|
||||
@Operation(summary = "分页查询系统角色表", description = "分页系统角色表")
|
||||
@GetMapping("{page}/{limit}")
|
||||
public Result<PageResult<RoleVo>> getRolePage(
|
||||
@Parameter(name = "page", description = "当前页", required = true)
|
||||
@PathVariable("page") Integer page,
|
||||
@Parameter(name = "limit", description = "每页记录数", required = true)
|
||||
@PathVariable("limit") Integer limit,
|
||||
RoleDto dto) {
|
||||
Page<RoleEntity> pageParams = new Page<>(page, limit);
|
||||
PageResult<RoleVo> pageResult = roleService.getRolePage(pageParams, dto);
|
||||
return Result.success(pageResult);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 前置与后置授权对比
|
||||
|
||||
在Spring Security 6中,`@PreAuthorize`和`@PostAuthorize`确实有不同的执行时机和行为,你的理解基本正确,我来详细说明一下:
|
||||
|
||||
#### 1. @PreAuthorize
|
||||
|
||||
- **执行时机**:在方法执行**之前**进行权限检查
|
||||
- **行为**:
|
||||
- 如果当前用户没有满足注解中指定的权限条件,方法**不会被执行**,直接抛出`AccessDeniedException`
|
||||
- 这是一种"先验"的权限检查方式,可以防止无权限用户触发方法执行
|
||||
- **典型用途**:适用于方法执行前的权限验证,特别是当方法执行可能有副作用(如修改数据)时
|
||||
|
||||
```java
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public void deleteUser(Long userId) {
|
||||
// 只有ADMIN角色可以执行此方法
|
||||
// 如果不是ADMIN,代码不会执行到这里
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. @PostAuthorize
|
||||
|
||||
- **执行时机**:在方法执行**之后**进行权限检查
|
||||
- **行为**:
|
||||
- 方法**会先完整执行**,然后在返回结果前检查权限
|
||||
- 如果权限检查不通过,同样会抛出`AccessDeniedException`,但方法已经执行完毕
|
||||
- 可以基于方法的返回值进行权限判断(使用`returnObject`引用返回值)
|
||||
- **典型用途**:适用于需要根据方法返回结果决定是否允许访问的情况
|
||||
|
||||
```java
|
||||
@PostAuthorize("returnObject.owner == authentication.name")
|
||||
public Document getDocument(Long docId) {
|
||||
// 方法会先执行
|
||||
// 返回前检查文档所有者是否是当前用户
|
||||
return documentRepository.findById(docId);
|
||||
}
|
||||
```
|
||||
|
||||
**如果需要关闭**
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableMethodSecurity(prePostEnabled = false)
|
||||
class MethodSecurityConfig {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 关键区别总结
|
||||
|
||||
| 特性 | @PreAuthorize | @PostAuthorize |
|
||||
| ---------------- | -------------------- | ------------------------------ |
|
||||
| 执行时机 | 方法执行前 | 方法执行后 |
|
||||
| 方法是否会被执行 | 不满足条件时不执行 | 总是执行 |
|
||||
| 可访问的上下文 | 方法参数 | 方法参数和返回值(returnObject) |
|
||||
| 性能影响 | 更好(避免不必要执行) | 稍差(方法总会执行) |
|
||||
| 主要用途 | 防止未授权访问 | 基于返回值的访问控制 |
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> 如果使用`PostAuthorize`注解,但是服务中没有标记事务注解,那么会将整个方法全部执行,即使没有权限也不会回滚。
|
||||
>
|
||||
> **默认情况下,Spring 事务会对未捕获的 `RuntimeException` 进行回滚**,因此:
|
||||
>
|
||||
> - 如果事务仍然活跃(未提交),则会回滚。
|
||||
> - 但如果事务已经提交(例如方法执行完毕且事务已提交),则**不会回滚**。
|
||||
|
||||
1. **优先使用@PreAuthorize**:除非你需要基于返回值做判断,否则应该使用`@PreAuthorize`,因为它能更早地阻止未授权访问
|
||||
|
||||
2. **注意方法副作用**:使用`@PostAuthorize`时要特别注意,即使最终会拒绝访问,方法中的所有代码(包括数据库修改等操作)都已经执行了
|
||||
|
||||
3. **组合使用**:有时可以组合使用两者,先用`@PreAuthorize`做基本权限检查,再用`@PostAuthorize`做更精细的检查
|
||||
|
||||
4. **性能敏感场景**:对于性能敏感或可能产生副作用的方法,避免使用`@PostAuthorize`
|
||||
|
||||
**使用示例**
|
||||
|
||||
```java
|
||||
@Tag(name = "测试接口", description = "测试用的接口")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/test")
|
||||
public class TestController {
|
||||
|
||||
@PreAuthorize("hasAuthority('role::read')")
|
||||
@Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口")
|
||||
@GetMapping("role-user")
|
||||
public Result<String> roleUser() {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('USER')")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('user')")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("lower-user")
|
||||
public Result<String> lowerUser() {
|
||||
String data = "如果是大写,但是在这里是小写无法访问";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@PostAuthorize("returnObject.data == authentication.name")
|
||||
@Operation(summary = "测试使用返回参数判断权限", description = "测试使用返回参数判断权限 用户拥有 role::read 可以访问这个接口")
|
||||
@GetMapping("test-post-authorize")
|
||||
public Result<String> testPostAuthorize() {
|
||||
log.info("方法内容已经执行。。。");
|
||||
String data = "Bunny";
|
||||
return Result.success(data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 高级用法
|
||||
|
||||
#### 元注解封装
|
||||
|
||||
```java
|
||||
// 管理员权限注解
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public @interface AdminOnly {}
|
||||
|
||||
// 资源所属校验注解
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PostAuthorize("returnObject.ownerId == authentication.principal.id")
|
||||
public @interface ResourceOwner {}
|
||||
```
|
||||
|
||||
#### 模板化注解
|
||||
|
||||
```java
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasAuthority('USER') or hasAuthority(#permission)")
|
||||
public @interface UserOrPermission {
|
||||
String permission();
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
@UserOrPermission(permission = "report:read")
|
||||
public Report getReport(Long id) { ... }
|
||||
```
|
||||
|
||||
### 4. 其他注解支持
|
||||
|
||||
#### JSR-250注解
|
||||
|
||||
需显式启用:
|
||||
|
||||
```java
|
||||
@EnableMethodSecurity(jsr250Enabled = true)
|
||||
```
|
||||
|
||||
提供注解:
|
||||
|
||||
- `@RolesAllowed("ROLE")` - 等效于`@PreAuthorize("hasRole('ROLE')")`
|
||||
- `@PermitAll` - 允许所有访问
|
||||
- `@DenyAll` - 拒绝所有访问
|
||||
|
||||
#### 已废弃注解
|
||||
|
||||
- `@Secured` 在Spring Security 6中已废弃,如需使用需显式启用:
|
||||
|
||||
```java
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
```
|
||||
|
||||
### 5. 最佳实践示例
|
||||
|
||||
```java
|
||||
@Tag(name = "权限测试接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/auth-test")
|
||||
public class AuthTestController {
|
||||
|
||||
// 精确权限控制
|
||||
@AdminOnly
|
||||
@GetMapping("/admin")
|
||||
public Result<String> adminEndpoint() {
|
||||
return Result.success("Admin access");
|
||||
}
|
||||
|
||||
// 复合权限检查
|
||||
@PreAuthorize("hasAnyAuthority('DATA_READ', 'REPORT_READ')")
|
||||
@GetMapping("/reports")
|
||||
public Result<List<Report>> getReports() {
|
||||
return Result.success(reportService.getAll());
|
||||
}
|
||||
|
||||
// 返回值校验
|
||||
@ResourceOwner
|
||||
@GetMapping("/documents/{id}")
|
||||
public Document getDocument(@PathVariable Long id) {
|
||||
return docService.getById(id); // 执行后校验所有者
|
||||
}
|
||||
|
||||
// 模板注解使用
|
||||
@UserOrPermission(permission = "audit:read")
|
||||
@GetMapping("/audit-logs")
|
||||
public Result<PageResult<AuditLog>> getAuditLogs(Pageable pageable) {
|
||||
return Result.success(auditService.getLogs(pageable));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 关键注意事项
|
||||
|
||||
1. **事务边界问题**:
|
||||
|
||||
- `@PostAuthorize`注解的方法若包含写操作,需确保:
|
||||
|
||||
```java
|
||||
@Transactional
|
||||
@PostAuthorize("...")
|
||||
public void updateData() {
|
||||
// 若授权失败,已执行的操作不会回滚
|
||||
}
|
||||
```
|
||||
|
||||
2. **权限命名规范**:
|
||||
|
||||
- 角色:`ROLE_ADMIN`(自动前缀)
|
||||
- 权限:`module:action`(如`user:delete`)
|
||||
|
||||
3. **性能考虑**:
|
||||
|
||||
- 避免在`@PostAuthorize`中执行耗时操作
|
||||
- 对高频接口优先使用`@PreAuthorize`
|
||||
|
||||
4. **测试覆盖**:
|
||||
|
||||
- 必须为每个安全注解编写测试用例
|
||||
- 验证正向和反向场景
|
||||
|
||||
5. **注解组合**:
|
||||
|
||||
```java
|
||||
@AdminOnly
|
||||
@PostAuthorize("returnObject.status == 'PUBLIC'")
|
||||
public Content getContent(Long id) {
|
||||
// 需要管理员权限且只允许返回公开内容
|
||||
}
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
|
@ -46,4 +46,14 @@ public class Knife4jConfig {
|
|||
public GroupedOpenApi security() {
|
||||
return GroupedOpenApi.builder().group("security接口").pathsToMatch("/api/security/**").build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi test() {
|
||||
return GroupedOpenApi.builder().group("测试接口").pathsToMatch("/api/test/**").build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi testAdmin() {
|
||||
return GroupedOpenApi.builder().group("测试包含管理员接口").pathsToMatch("/api/test-admin/**").build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.spring.step2.config;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.spring.step2.context.BaseContext;
|
||||
import com.spring.step2.config.context.BaseContext;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.spring.step2.context;
|
||||
package com.spring.step2.config.context;
|
||||
|
||||
|
||||
public class BaseContext {
|
|
@ -1,6 +1,6 @@
|
|||
package com.spring.step2.config.web;
|
||||
|
||||
import com.spring.step2.context.BaseContext;
|
||||
import com.spring.step2.config.context.BaseContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package com.spring.step2.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@GetMapping("")
|
||||
public String indexPage() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@GetMapping("/login-page")
|
||||
public String showLoginPage() {
|
||||
return "login";
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package com.spring.step2.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.PermissionDto;
|
||||
import com.spring.step2.domain.dto.permission.PermissionDto;
|
||||
import com.spring.step2.domain.entity.PermissionEntity;
|
||||
import com.spring.step2.domain.vo.PermissionVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -27,7 +27,7 @@ import java.util.List;
|
|||
*/
|
||||
@Tag(name = "系统权限表", description = "系统权限表相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/coupon/permission")
|
||||
@RequestMapping(value = "/api/permission")
|
||||
@RequiredArgsConstructor
|
||||
public class PermissionController {
|
||||
|
||||
|
@ -46,6 +46,13 @@ public class PermissionController {
|
|||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
@Operation(summary = "所有的权限列表", description = "获取所有的权限列表")
|
||||
@GetMapping("all")
|
||||
public Result<List<PermissionVo>> getAllPermission() {
|
||||
List<PermissionVo> voList = permissionService.getAllPermission();
|
||||
return Result.success(voList);
|
||||
}
|
||||
|
||||
@Operation(summary = "添加系统权限表", description = "添加系统权限表")
|
||||
@PostMapping()
|
||||
public Result<String> addPermission(@Valid @RequestBody PermissionDto dto) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package com.spring.step2.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.RoleDto;
|
||||
import com.spring.step2.domain.dto.role.RoleDto;
|
||||
import com.spring.step2.domain.entity.RoleEntity;
|
||||
import com.spring.step2.domain.vo.RoleVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -11,6 +11,7 @@ import com.spring.step2.service.RoleService;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -27,8 +28,9 @@ import java.util.List;
|
|||
*/
|
||||
@Tag(name = "系统角色表", description = "系统角色表相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/coupon/role")
|
||||
@RequestMapping("/api/role")
|
||||
@RequiredArgsConstructor
|
||||
|
||||
public class RoleController {
|
||||
|
||||
private final RoleService roleService;
|
||||
|
@ -46,6 +48,14 @@ public class RoleController {
|
|||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@Operation(summary = "获取全部角色列表", description = "获取全部角色列表")
|
||||
@GetMapping("all")
|
||||
public Result<List<RoleVo>> getRoleList() {
|
||||
List<RoleVo> roleVoList = roleService.getRoleList();
|
||||
return Result.success(roleVoList);
|
||||
}
|
||||
|
||||
@Operation(summary = "添加系统角色表", description = "添加系统角色表")
|
||||
@PostMapping()
|
||||
public Result<String> addRole(@Valid @RequestBody RoleDto dto) {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package com.spring.step2.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.RolePermissionDto;
|
||||
import com.spring.step2.domain.dto.role.AssignRolePermissionDto;
|
||||
import com.spring.step2.domain.dto.role.RolePermissionDto;
|
||||
import com.spring.step2.domain.entity.RolePermissionEntity;
|
||||
import com.spring.step2.domain.vo.RolePermissionVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -27,7 +28,7 @@ import java.util.List;
|
|||
*/
|
||||
@Tag(name = "角色权限关联表", description = "角色权限关联表相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/coupon/role-permission")
|
||||
@RequestMapping("/api/role-permission")
|
||||
@RequiredArgsConstructor
|
||||
public class RolePermissionController {
|
||||
|
||||
|
@ -46,6 +47,13 @@ public class RolePermissionController {
|
|||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
@GetMapping("permissions")
|
||||
@Operation(summary = "根据角色id获取权限内容", description = "根据角色id获取权限内容")
|
||||
public Result<List<RolePermissionVo>> getRolePermissionById(Long permissionId) {
|
||||
List<RolePermissionVo> voList = rolePermissionService.getRolePermissionById(permissionId);
|
||||
return Result.success(voList);
|
||||
}
|
||||
|
||||
@Operation(summary = "添加角色权限关联表", description = "添加角色权限关联表")
|
||||
@PostMapping()
|
||||
public Result<String> addRolePermission(@Valid @RequestBody RolePermissionDto dto) {
|
||||
|
@ -53,6 +61,13 @@ public class RolePermissionController {
|
|||
return Result.success(ResultCodeEnum.ADD_SUCCESS);
|
||||
}
|
||||
|
||||
@Operation(summary = "为角色分配权限", description = "根据角色id分配权限")
|
||||
@PostMapping("assign-permission")
|
||||
public Result<String> assignRolePermission(@Valid @RequestBody AssignRolePermissionDto dto) {
|
||||
rolePermissionService.assignRolePermission(dto);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "更新角色权限关联表", description = "更新角色权限关联表")
|
||||
@PutMapping()
|
||||
public Result<String> updateRolePermission(@Valid @RequestBody RolePermissionDto dto) {
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.spring.step2.controller;
|
|||
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.UserDto;
|
||||
import com.spring.step2.domain.dto.user.UserDto;
|
||||
import com.spring.step2.domain.entity.UserEntity;
|
||||
import com.spring.step2.domain.vo.UserVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -28,7 +28,7 @@ import java.util.List;
|
|||
*/
|
||||
@Tag(name = "用户基本信息表", description = "用户基本信息表相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/coupon/user")
|
||||
@RequestMapping("/api/user")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
|
||||
|
@ -44,7 +44,7 @@ public class UserController {
|
|||
UserDto dto) {
|
||||
Page<UserEntity> pageParams = new Page<>(page, limit);
|
||||
PageResult<UserVo> pageResult = userService.getUserPage(pageParams, dto);
|
||||
return Result.success(pageResult);
|
||||
return Result.success(pageResult, ResultCodeEnum.LOAD_FINISHED);
|
||||
}
|
||||
|
||||
@Operation(summary = "添加用户基本信息表", description = "添加用户基本信息表")
|
||||
|
|
|
@ -2,7 +2,8 @@ package com.spring.step2.controller;
|
|||
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.UserRoleDto;
|
||||
import com.spring.step2.domain.dto.user.AssignUserRoleDto;
|
||||
import com.spring.step2.domain.dto.user.UserRoleDto;
|
||||
import com.spring.step2.domain.entity.UserRoleEntity;
|
||||
import com.spring.step2.domain.vo.UserRoleVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -28,7 +29,7 @@ import java.util.List;
|
|||
*/
|
||||
@Tag(name = "用户角色关联表", description = "用户角色关联表相关接口")
|
||||
@RestController
|
||||
@RequestMapping("/api/coupon/user-role")
|
||||
@RequestMapping("/api/user-role")
|
||||
@RequiredArgsConstructor
|
||||
public class UserRoleController {
|
||||
|
||||
|
@ -47,6 +48,13 @@ public class UserRoleController {
|
|||
return Result.success(pageResult);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据用户id获取当前用户角色列表", description = "根据用户id获取当前用户角色列表")
|
||||
@GetMapping("roles")
|
||||
public Result<List<UserRoleVo>> getRoleListByUserId(Long userId) {
|
||||
List<UserRoleVo> voList = userRoleService.getRoleListByUserId(userId);
|
||||
return Result.success(voList);
|
||||
}
|
||||
|
||||
@Operation(summary = "添加用户角色关联表", description = "添加用户角色关联表")
|
||||
@PostMapping()
|
||||
public Result<String> addUserRole(@Valid @RequestBody UserRoleDto dto) {
|
||||
|
@ -54,6 +62,13 @@ public class UserRoleController {
|
|||
return Result.success(ResultCodeEnum.ADD_SUCCESS);
|
||||
}
|
||||
|
||||
@Operation(summary = "为用户分配角色id", description = "根据用户id分配用户角色")
|
||||
@PostMapping("assign-role")
|
||||
public Result<String> assignUserRole(@Valid @RequestBody AssignUserRoleDto dto) {
|
||||
userRoleService.assignUserRole(dto);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Operation(summary = "更新用户角色关联表", description = "更新用户角色关联表")
|
||||
@PutMapping()
|
||||
public Result<String> updateUserRole(@Valid @RequestBody UserRoleDto dto) {
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package com.spring.step2.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class WebController {
|
||||
|
||||
@GetMapping("")
|
||||
public String indexPage() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@GetMapping("/login")
|
||||
public String showLoginPage() {
|
||||
return "loginPage";
|
||||
}
|
||||
|
||||
@GetMapping("/user")
|
||||
public String showUserPage() {
|
||||
return "userPage";
|
||||
}
|
||||
|
||||
@GetMapping("/role")
|
||||
public String showRolePage() {
|
||||
return "rolePage";
|
||||
}
|
||||
|
||||
@GetMapping("/permission")
|
||||
public String showPermissionPage() {
|
||||
return "permissionPage";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.spring.step2.controller.test;
|
||||
|
||||
import com.spring.step2.domain.vo.result.Result;
|
||||
import com.spring.step2.security.annotation.HasUSERAuthorize;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "测试包含 USER 接口", description = "只要包含 USER 角色都可以访问")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/test-admin")
|
||||
public class TestAdminController {
|
||||
|
||||
@HasUSERAuthorize("role:read")
|
||||
@Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口")
|
||||
@GetMapping("role-user")
|
||||
public Result<String> roleUser() {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@HasUSERAuthorize("USER")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@HasUSERAuthorize("user")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("lower-user")
|
||||
public Result<String> lowerUser() {
|
||||
String data = "如果是大写,但是在这里是小写无法访问";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package com.spring.step2.controller.test;
|
||||
|
||||
import com.spring.step2.domain.vo.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Tag(name = "测试接口", description = "测试用的接口")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/test")
|
||||
public class TestController {
|
||||
|
||||
@PreAuthorize("hasAuthority('role::read')")
|
||||
@Operation(summary = "拥有 role:read 的角色可以访问", description = "当前用户拥有 role:read 角色可以访问这个接口")
|
||||
@GetMapping("role-user")
|
||||
public Result<String> roleUser() {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('USER')")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("upper-user")
|
||||
public Result<String> upperUser() {
|
||||
String data = "是区分大小写的";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('user')")
|
||||
@Operation(summary = "拥有 USER 的角色可以访问", description = "当前用户拥有 USER 角色可以访问这个接口")
|
||||
@GetMapping("lower-user")
|
||||
public Result<String> lowerUser() {
|
||||
String data = "如果是大写,但是在这里是小写无法访问";
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@PostAuthorize("returnObject.data == authentication.name")
|
||||
@Operation(summary = "测试使用返回参数判断权限", description = "测试使用返回参数判断权限 用户拥有 role::read 可以访问这个接口")
|
||||
@GetMapping("test-post-authorize")
|
||||
public Result<String> testPostAuthorize() {
|
||||
log.info("方法内容已经执行。。。");
|
||||
String data = "Bunny";
|
||||
return Result.success(data);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.spring.step2.domain.dto;
|
||||
package com.spring.step2.domain.dto.permission;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
@ -21,6 +21,12 @@ public class PermissionDto {
|
|||
@Schema(name = "permissionCode", title = "权限编码")
|
||||
private String permissionCode;
|
||||
|
||||
@Schema(name = "url", description = "URL")
|
||||
private String url;
|
||||
|
||||
@Schema(name = "method", description = "请求方法类型")
|
||||
private String method;
|
||||
|
||||
@Schema(name = "description", title = "权限描述")
|
||||
private String description;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.spring.step2.domain.dto.role;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "AssignRolePermissionDTO对象", title = "角色分配权限DTO", description = "根据角色id分配权限")
|
||||
public class AssignRolePermissionDto {
|
||||
|
||||
@Schema(name = "roleId", title = "角色ID")
|
||||
@NotNull(message = "角色id为空")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(name = "permissionId", title = "权限ID")
|
||||
@NotEmpty(message = "权限id为空")
|
||||
private List<Long> permissionIds;
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package com.spring.step2.domain.dto;
|
||||
package com.spring.step2.domain.dto.role;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -18,13 +19,16 @@ public class RoleDto {
|
|||
@Schema(name = "id", title = "主键ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(name = "roleName", title = "角色名称")
|
||||
private String roleName;
|
||||
@Schema(name = "roleCode", title = "角色码")
|
||||
@NotBlank(message = "角色码不能为空")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(name = "description", title = "角色描述")
|
||||
@NotBlank(message = "角色描述不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(name = "remark", title = "备注信息")
|
||||
@NotBlank(message = "备注信息不能为空")
|
||||
private String remark;
|
||||
|
||||
@Schema(name = "createTime", title = "创建时间")
|
|
@ -1,4 +1,4 @@
|
|||
package com.spring.step2.domain.dto;
|
||||
package com.spring.step2.domain.dto.role;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
@ -6,8 +6,6 @@ import lombok.Builder;
|
|||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
|
@ -24,19 +22,4 @@ public class RolePermissionDto {
|
|||
@Schema(name = "permissionId", title = "权限ID")
|
||||
private Long permissionId;
|
||||
|
||||
@Schema(name = "createTime", title = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(name = "updateTime", title = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(name = "createUser", title = "创建用户ID")
|
||||
private Long createUser;
|
||||
|
||||
@Schema(name = "updateUser", title = "更新用户ID")
|
||||
private Long updateUser;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.spring.step2.domain.dto.user;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "AssignUserRoleDTO对象", title = "用户分配角色DTO", description = "根据用户id分配用户角色")
|
||||
public class AssignUserRoleDto {
|
||||
|
||||
@Schema(name = "userId", title = "用户ID")
|
||||
@NotNull(message = "用户id为空")
|
||||
private Long userId;
|
||||
|
||||
@Schema(name = "roleId", title = "角色ID")
|
||||
@NotEmpty(message = "角色ID为空")
|
||||
private List<Long> roleIds;
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package com.spring.step2.domain.dto;
|
||||
package com.spring.step2.domain.dto.user;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -13,13 +14,18 @@ import lombok.NoArgsConstructor;
|
|||
@Schema(name = "UserDTO对象", title = "用户", description = "用户的DTO对象")
|
||||
public class UserDto {
|
||||
|
||||
@Schema(name = "id", title = "主键")
|
||||
private Long id;
|
||||
|
||||
@Schema(name = "username", title = "用户名")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@Schema(name = "password", title = "密码")
|
||||
private String password;
|
||||
|
||||
@Schema(name = "email", title = "邮箱")
|
||||
@NotBlank(message = "邮箱不能为空")
|
||||
private String email;
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.spring.step2.domain.dto;
|
||||
package com.spring.step2.domain.dto.user;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
@ -6,8 +6,6 @@ import lombok.Builder;
|
|||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
|
@ -24,19 +22,4 @@ public class UserRoleDto {
|
|||
@Schema(name = "userId", title = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(name = "createTime", title = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(name = "updateTime", title = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(name = "createUser", title = "创建用户ID")
|
||||
private Long createUser;
|
||||
|
||||
@Schema(name = "updateUser", title = "更新用户ID")
|
||||
private Long updateUser;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.spring.step2.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.spring.step2.domain.entity.base.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -16,6 +18,12 @@ public class PermissionEntity extends BaseEntity {
|
|||
@Schema(name = "permissionCode", title = "权限编码")
|
||||
private String permissionCode;
|
||||
|
||||
@Schema(name = "url", description = "URL")
|
||||
private String url;
|
||||
|
||||
@Schema(name = "method", description = "请求方法类型")
|
||||
private String method;
|
||||
|
||||
@Schema(name = "description", title = "权限描述")
|
||||
private String description;
|
||||
|
||||
|
@ -23,6 +31,7 @@ public class PermissionEntity extends BaseEntity {
|
|||
private String remark;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
@TableField(exist = false)
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.spring.step2.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.spring.step2.domain.entity.base.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -13,8 +15,8 @@ import lombok.experimental.Accessors;
|
|||
@Schema(name = "Role对象", title = "系统角色表", description = "系统角色表的实体类对象")
|
||||
public class RoleEntity extends BaseEntity {
|
||||
|
||||
@Schema(name = "roleName", title = "角色名称")
|
||||
private String roleName;
|
||||
@Schema(name = "roleCode", title = "角色码")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(name = "description", title = "角色描述")
|
||||
private String description;
|
||||
|
@ -23,6 +25,7 @@ public class RoleEntity extends BaseEntity {
|
|||
private String remark;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
@TableField(exist = false)
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.spring.step2.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.spring.step2.domain.entity.base.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -20,6 +22,7 @@ public class RolePermissionEntity extends BaseEntity {
|
|||
private Long permissionId;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
@TableField(exist = false)
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package com.spring.step2.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.spring.step2.domain.entity.base.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -24,7 +25,7 @@ public class UserEntity extends BaseEntity {
|
|||
private String email;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否被删除")
|
||||
@TableLogic
|
||||
@TableField(exist = false)
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package com.spring.step2.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.spring.step2.domain.entity.base.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -20,6 +22,7 @@ public class UserRoleEntity extends BaseEntity {
|
|||
private Long userId;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
@TableField(exist = false)
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.spring.step2.domain.entity;
|
||||
package com.spring.step2.domain.entity.base;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
|
@ -1,44 +1,41 @@
|
|||
package com.spring.step2.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.spring.step2.domain.vo.base.BaseVo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(name = "PermissionVO对象", title = "系统权限表", description = "系统权限表的VO对象")
|
||||
public class PermissionVo {
|
||||
public class PermissionVo extends BaseVo {
|
||||
|
||||
@Schema(name = "id", title = "主键ID")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
|
||||
@Schema(name = "permissionCode", title = "权限编码")
|
||||
private String permissionCode;
|
||||
|
||||
@Schema(name = "url", description = "URL")
|
||||
private String url;
|
||||
|
||||
@Schema(name = "method", description = "请求方法类型")
|
||||
private String method;
|
||||
|
||||
@Schema(name = "description", title = "权限描述")
|
||||
private String description;
|
||||
|
||||
@Schema(name = "remark", title = "备注信息")
|
||||
private String remark;
|
||||
|
||||
@Schema(name = "createTime", title = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(name = "updateTime", title = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(name = "createUser", title = "创建用户ID")
|
||||
private Long createUser;
|
||||
|
||||
@Schema(name = "updateUser", title = "更新用户ID")
|
||||
private Long updateUser;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
package com.spring.step2.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.spring.step2.domain.vo.base.BaseVo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(name = "RolePermissionVO对象", title = "角色权限关联表", description = "角色权限关联表的VO对象")
|
||||
public class RolePermissionVo {
|
||||
public class RolePermissionVo extends BaseVo {
|
||||
|
||||
@Schema(name = "id", title = "主键ID")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
|
||||
@Schema(name = "roleId", title = "角色ID")
|
||||
|
@ -22,20 +28,5 @@ public class RolePermissionVo {
|
|||
@Schema(name = "permissionId", title = "权限ID")
|
||||
private Long permissionId;
|
||||
|
||||
@Schema(name = "createTime", title = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(name = "updateTime", title = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(name = "createUser", title = "创建用户ID")
|
||||
private Long createUser;
|
||||
|
||||
@Schema(name = "updateUser", title = "更新用户ID")
|
||||
private Long updateUser;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
package com.spring.step2.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.spring.step2.domain.vo.base.BaseVo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(name = "RoleVO对象", title = "系统角色表", description = "系统角色表的VO对象")
|
||||
public class RoleVo {
|
||||
public class RoleVo extends BaseVo {
|
||||
|
||||
@Schema(name = "id", title = "主键ID")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
|
||||
@Schema(name = "roleName", title = "角色名称")
|
||||
private String roleName;
|
||||
@Schema(name = "roleCode", title = "角色名称")
|
||||
private String roleCode;
|
||||
|
||||
@Schema(name = "description", title = "角色描述")
|
||||
private String description;
|
||||
|
@ -25,20 +31,5 @@ public class RoleVo {
|
|||
@Schema(name = "remark", title = "备注信息")
|
||||
private String remark;
|
||||
|
||||
@Schema(name = "createTime", title = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(name = "updateTime", title = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(name = "createUser", title = "创建用户ID")
|
||||
private Long createUser;
|
||||
|
||||
@Schema(name = "updateUser", title = "更新用户ID")
|
||||
private Long updateUser;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,41 +1,36 @@
|
|||
package com.spring.step2.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.spring.step2.domain.vo.base.BaseVo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(name = "UserRoleVO对象", title = "用户角色关联表", description = "用户角色关联表的VO对象")
|
||||
public class UserRoleVo {
|
||||
public class UserRoleVo extends BaseVo {
|
||||
|
||||
@Schema(name = "id", title = "主键")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
|
||||
@Schema(name = "roleId", title = "角色ID")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long roleId;
|
||||
|
||||
@Schema(name = "userId", title = "用户ID")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long userId;
|
||||
|
||||
@Schema(name = "createTime", title = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(name = "updateTime", title = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(name = "createUser", title = "创建用户ID")
|
||||
private Long createUser;
|
||||
|
||||
@Schema(name = "updateUser", title = "更新用户ID")
|
||||
private Long updateUser;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否删除:0-未删除,1-已删除")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,44 +1,32 @@
|
|||
package com.spring.step2.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.spring.step2.domain.vo.base.BaseVo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(name = "UserVO对象", title = "用户基本信息表", description = "用户基本信息表的VO对象")
|
||||
public class UserVo {
|
||||
public class UserVo extends BaseVo {
|
||||
|
||||
@Schema(name = "id", title = "主键")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING)
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
|
||||
@Schema(name = "username", title = "用户名")
|
||||
private String username;
|
||||
|
||||
@Schema(name = "password", title = "密码")
|
||||
private String password;
|
||||
|
||||
@Schema(name = "email", title = "邮箱")
|
||||
private String email;
|
||||
|
||||
@Schema(name = "createTime", title = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(name = "updateTime", title = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(name = "createUser", title = "创建用户ID")
|
||||
private Long createUser;
|
||||
|
||||
@Schema(name = "updateUser", title = "更新用户ID")
|
||||
private Long updateUser;
|
||||
|
||||
@Schema(name = "isDeleted", title = "是否被删除")
|
||||
private Boolean isDeleted;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package com.spring.step2.domain.vo.base;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class BaseVo {
|
||||
|
||||
@Schema(name = "createTime", title = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(name = "updateTime", title = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(name = "createUser", title = "创建用户ID")
|
||||
private Long createUser;
|
||||
|
||||
@Schema(name = "updateUser", title = "更新用户ID")
|
||||
private Long updateUser;
|
||||
|
||||
}
|
|
@ -25,6 +25,9 @@ public class PageResult<T> implements Serializable {
|
|||
@Schema(name = "pageSize", title = "每页记录数")
|
||||
private Long pageSize;
|
||||
|
||||
@Schema(name = "pages", title = "总分页数")
|
||||
private Long pages;
|
||||
|
||||
@Schema(name = "total", title = "总记录数")
|
||||
private Long total;
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ public enum ResultCodeEnum {
|
|||
ADD_SUCCESS(200, "添加成功"),
|
||||
UPDATE_SUCCESS(200, "修改成功"),
|
||||
DELETE_SUCCESS(200, "删除成功"),
|
||||
SORT_SUCCESS(200, "排序成功"),
|
||||
ASSIGN_SUCCESS(200, "排序成功"),
|
||||
SUCCESS_UPLOAD(200, "上传成功"),
|
||||
SUCCESS_LOGOUT(200, "退出成功"),
|
||||
EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"),
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
package com.spring.step2.exception;
|
||||
|
||||
|
||||
import com.spring.step2.domain.vo.result.Result;
|
||||
import com.spring.step2.domain.vo.result.ResultCodeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.sql.SQLIntegrityConstraintViolationException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局异常拦截器
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
// 自定义异常信息
|
||||
@ExceptionHandler(SecurityException.class)
|
||||
@ResponseBody
|
||||
public Result<Object> exceptionHandler(SecurityException exception) {
|
||||
Integer code = exception.getCode() != null ? exception.getCode() : 500;
|
||||
return Result.error(null, code, exception.getMessage());
|
||||
}
|
||||
|
||||
// 运行时异常信息
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseBody
|
||||
public Result<Object> exceptionHandler(RuntimeException exception) {
|
||||
String message = exception.getMessage();
|
||||
message = StringUtils.hasText(message) ? message : "服务器异常";
|
||||
exception.printStackTrace();
|
||||
|
||||
// 解析异常
|
||||
String jsonParseError = "JSON parse error (.*)";
|
||||
Matcher jsonParseErrorMatcher = Pattern.compile(jsonParseError).matcher(message);
|
||||
if (jsonParseErrorMatcher.find()) {
|
||||
return Result.error(null, 500, "JSON解析异常 " + jsonParseErrorMatcher.group(1));
|
||||
}
|
||||
|
||||
// 数据过大
|
||||
String dataTooLongError = "Data too long for column (.*?) at row 1";
|
||||
Matcher dataTooLongErrorMatcher = Pattern.compile(dataTooLongError).matcher(message);
|
||||
if (dataTooLongErrorMatcher.find()) {
|
||||
return Result.error(null, 500, dataTooLongErrorMatcher.group(1) + " 字段数据过大");
|
||||
}
|
||||
|
||||
// 主键冲突
|
||||
String primaryKeyError = "Duplicate entry '(.*?)' for key .*";
|
||||
Matcher primaryKeyErrorMatcher = Pattern.compile(primaryKeyError).matcher(message);
|
||||
if (primaryKeyErrorMatcher.find()) {
|
||||
return Result.error(null, 500, "[" + primaryKeyErrorMatcher.group(1) + "]已存在");
|
||||
}
|
||||
|
||||
// corn表达式错误
|
||||
String cronExpression = "CronExpression '(.*?)' is invalid";
|
||||
Matcher cronExpressionMatcher = Pattern.compile(cronExpression).matcher(message);
|
||||
if (cronExpressionMatcher.find()) {
|
||||
return Result.error(null, 500, "表达式 " + cronExpressionMatcher.group(1) + " 不合法");
|
||||
}
|
||||
|
||||
log.error("GlobalExceptionHandler===>运行时异常信息:{}", message);
|
||||
return Result.error(null, 500, message);
|
||||
}
|
||||
|
||||
// 表单验证字段
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Result<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
|
||||
.map(DefaultMessageSourceResolvable::getDefaultMessage)
|
||||
.distinct()
|
||||
.collect(Collectors.joining(", "));
|
||||
return Result.error(null, 201, errorMessage);
|
||||
}
|
||||
|
||||
// 特定异常处理
|
||||
@ExceptionHandler(ArithmeticException.class)
|
||||
@ResponseBody
|
||||
public Result<Object> error(ArithmeticException exception) {
|
||||
log.error("GlobalExceptionHandler===>特定异常信息:{}", exception.getMessage());
|
||||
|
||||
return Result.error(null, 500, exception.getMessage());
|
||||
}
|
||||
|
||||
// 处理SQL异常
|
||||
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
|
||||
@ResponseBody
|
||||
public Result<String> exceptionHandler(SQLIntegrityConstraintViolationException exception) {
|
||||
log.error("GlobalExceptionHandler===>处理SQL异常:{}", exception.getMessage());
|
||||
|
||||
String message = exception.getMessage();
|
||||
if (message.contains("Duplicate entry")) {
|
||||
// 错误信息
|
||||
return Result.error(ResultCodeEnum.USER_IS_EMPTY);
|
||||
} else {
|
||||
return Result.error(ResultCodeEnum.UNKNOWN_EXCEPTION);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理无权访问异常
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public Result<String> handleAccessDenied(AccessDeniedException exception) {
|
||||
return Result.error(exception.getMessage(), ResultCodeEnum.FAIL_NO_ACCESS_DENIED);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.spring.step2.exception;
|
||||
|
||||
import com.spring.step2.domain.vo.result.ResultCodeEnum;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
@Slf4j
|
||||
public class SecurityException extends RuntimeException {
|
||||
// 状态码
|
||||
Integer code;
|
||||
|
||||
// 描述信息
|
||||
String message = "服务异常";
|
||||
|
||||
// 返回结果状态
|
||||
ResultCodeEnum resultCodeEnum;
|
||||
|
||||
public SecurityException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public SecurityException(String message) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public SecurityException(ResultCodeEnum codeEnum) {
|
||||
super(codeEnum.getMessage());
|
||||
this.code = codeEnum.getCode();
|
||||
this.message = codeEnum.getMessage();
|
||||
this.resultCodeEnum = codeEnum;
|
||||
}
|
||||
|
||||
public SecurityException(String message, Exception exception) {
|
||||
super(message);
|
||||
this.message = message;
|
||||
log.error(message, exception);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ package com.spring.step2.mapper;
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.PermissionDto;
|
||||
import com.spring.step2.domain.dto.permission.PermissionDto;
|
||||
import com.spring.step2.domain.entity.PermissionEntity;
|
||||
import com.spring.step2.domain.vo.PermissionVo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
|
|
@ -4,7 +4,7 @@ package com.spring.step2.mapper;
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.RoleDto;
|
||||
import com.spring.step2.domain.dto.role.RoleDto;
|
||||
import com.spring.step2.domain.entity.RoleEntity;
|
||||
import com.spring.step2.domain.vo.RoleVo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
|
|
@ -4,12 +4,14 @@ package com.spring.step2.mapper;
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.RolePermissionDto;
|
||||
import com.spring.step2.domain.dto.role.RolePermissionDto;
|
||||
import com.spring.step2.domain.entity.RolePermissionEntity;
|
||||
import com.spring.step2.domain.vo.RolePermissionVo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 角色权限关联表 Mapper 接口
|
||||
|
@ -30,4 +32,18 @@ public interface RolePermissionMapper extends BaseMapper<RolePermissionEntity> {
|
|||
*/
|
||||
IPage<RolePermissionVo> selectListByPage(@Param("page") Page<RolePermissionEntity> pageParams, @Param("dto") RolePermissionDto dto);
|
||||
|
||||
/**
|
||||
* 根据角色id获取权限内容
|
||||
*
|
||||
* @param permissionId 权限id
|
||||
* @return 角色权限列表
|
||||
*/
|
||||
List<RolePermissionEntity> selectListByPermissionId(Long permissionId);
|
||||
|
||||
/**
|
||||
* 先删除当前已经分配的角色权限内容
|
||||
*
|
||||
* @param roleId 角色id
|
||||
*/
|
||||
void deleteByRoleId(Long roleId);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ package com.spring.step2.mapper;
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.UserDto;
|
||||
import com.spring.step2.domain.dto.user.UserDto;
|
||||
import com.spring.step2.domain.entity.PermissionEntity;
|
||||
import com.spring.step2.domain.entity.RoleEntity;
|
||||
import com.spring.step2.domain.entity.UserEntity;
|
||||
import com.spring.step2.domain.vo.UserVo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
@ -47,4 +48,12 @@ public interface UserMapper extends BaseMapper<UserEntity> {
|
|||
* @return 用户 {@link UserEntity}
|
||||
*/
|
||||
UserEntity selectByUsername(String username);
|
||||
|
||||
/**
|
||||
* 根据用户id查找该用户的角色内容
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @return 当前用户的角色信息
|
||||
*/
|
||||
List<RoleEntity> selectRolesByUserId(Long userId);
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@ package com.spring.step2.mapper;
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.spring.step2.domain.dto.UserRoleDto;
|
||||
import com.spring.step2.domain.dto.user.UserRoleDto;
|
||||
import com.spring.step2.domain.entity.UserRoleEntity;
|
||||
import com.spring.step2.domain.vo.UserRoleVo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 用户角色关联表 Mapper 接口
|
||||
|
@ -29,4 +31,19 @@ public interface UserRoleMapper extends BaseMapper<UserRoleEntity> {
|
|||
*/
|
||||
IPage<UserRoleVo> selectListByPage(@Param("page") Page<UserRoleEntity> pageParams, @Param("dto") UserRoleDto dto);
|
||||
|
||||
/**
|
||||
* 根据用户id获取当前用户角色列表
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @return 用户和角色列表
|
||||
*/
|
||||
List<UserRoleEntity> getRoleListByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 根据用户id删除用户相关分配角色
|
||||
*
|
||||
* @param userId 用户id
|
||||
*/
|
||||
void deleteByUserId(Long userId);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.spring.step2.security.annotation;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasAuthority('USER') || hasAuthority('{value}')")
|
||||
public @interface HasUSERAuthorize {
|
||||
String value();
|
||||
}
|
|
@ -2,6 +2,7 @@ package com.spring.step2.security.config;
|
|||
|
||||
import com.spring.step2.security.handler.SecurityAccessDeniedHandler;
|
||||
import com.spring.step2.security.handler.SecurityAuthenticationEntryPoint;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
|
@ -12,28 +13,20 @@ import org.springframework.security.web.SecurityFilterChain;
|
|||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@EnableMethodSecurity(jsr250Enabled = true)
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityWebConfiguration {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
String[] permitAllUrls = {
|
||||
"/", "/doc.html/**",
|
||||
"/webjars/**", "/images/**", ".well-known/**", "favicon.ico", "/error/**",
|
||||
"/swagger-ui/**", "/v3/api-docs/**"
|
||||
};
|
||||
|
||||
http.authorizeHttpRequests(authorizeRequests ->
|
||||
// 访问路径为 /api 时需要进行认证
|
||||
authorizeRequests
|
||||
.requestMatchers(permitAllUrls).permitAll()
|
||||
// .requestMatchers("/api/**").hasAnyRole("Admin", "ADMIN", "admin")
|
||||
.anyRequest().permitAll()
|
||||
// .requestMatchers("/api/security/**").permitAll()
|
||||
// .requestMatchers(HttpMethod.GET, "/api/anonymous/**").anonymous()
|
||||
// // 会自动变成 ROLE_ADMIN
|
||||
// // .requestMatchers("/api/**").hasRole("ADMIN")
|
||||
// .requestMatchers("/api/**").hasAnyAuthority("all", "read")
|
||||
// 访问路径为 /api 时需要进行认证
|
||||
authorizeRequests
|
||||
// 只认证 /api/** 下的所有接口
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
// 其余请求都放行
|
||||
.anyRequest().permitAll()
|
||||
)
|
||||
.formLogin(loginPage -> loginPage
|
||||
// 自定义登录页路径
|
||||
|
@ -55,10 +48,12 @@ public class SecurityWebConfiguration {
|
|||
.permitAll()
|
||||
)
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.exceptionHandling(configurer -> configurer
|
||||
.accessDeniedHandler(new SecurityAccessDeniedHandler())
|
||||
.authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
|
||||
)
|
||||
.exceptionHandling(exception -> {
|
||||
// 请求未授权接口
|
||||
exception.authenticationEntryPoint(new SecurityAuthenticationEntryPoint());
|
||||
// 没有权限访问
|
||||
exception.accessDeniedHandler(new SecurityAccessDeniedHandler());
|
||||
})
|
||||
;
|
||||
|
||||
return http.build();
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.spring.step2.security.handler;
|
|||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.spring.step2.domain.vo.result.Result;
|
||||
import com.spring.step2.domain.vo.result.ResultCodeEnum;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
@ -13,12 +14,15 @@ import java.io.IOException;
|
|||
|
||||
@Slf4j
|
||||
public class SecurityAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||
log.error("CustomerAccessDeniedHandler:{}", accessDeniedException.getLocalizedMessage());
|
||||
log.error("SecurityAccessDeniedHandler:{}", accessDeniedException.getLocalizedMessage());
|
||||
|
||||
Result<Object> result = Result.error(accessDeniedException.getMessage());
|
||||
// 无权访问接口
|
||||
Result<Object> result = Result.error(accessDeniedException.getMessage(), ResultCodeEnum.LOGIN_AUTH);
|
||||
|
||||
// 转成JSON格式
|
||||
Object json = JSON.toJSON(result);
|
||||
|
||||
// 返回响应
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.spring.step2.security.handler;
|
|||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.spring.step2.domain.vo.result.Result;
|
||||
import jakarta.servlet.ServletException;
|
||||
import com.spring.step2.domain.vo.result.ResultCodeEnum;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -15,11 +15,13 @@ import java.io.IOException;
|
|||
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||
log.error("CustomerAccessDeniedHandler:{}", authException.getLocalizedMessage());
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
|
||||
log.error("SecurityAuthenticationEntryPoint:{}", authException.getLocalizedMessage());
|
||||
|
||||
Result<Object> result = Result.error(authException.getMessage());
|
||||
// 未认证---未登录
|
||||
Result<Object> result = Result.error(authException.getMessage(), ResultCodeEnum.LOGIN_AUTH);
|
||||
|
||||
// 将错误的请求转成JSON
|
||||
Object json = JSON.toJSON(result);
|
||||
|
||||
// 返回响应
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
package com.spring.step2.security.service;
|
||||
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
import com.spring.step2.domain.entity.PermissionEntity;
|
||||
import com.spring.step2.domain.entity.RoleEntity;
|
||||
import com.spring.step2.domain.entity.UserEntity;
|
||||
import com.spring.step2.mapper.UserMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@DS("testJwt")
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class DbUserDetailService implements UserDetailsService {
|
||||
|
||||
|
@ -29,18 +34,48 @@ public class DbUserDetailService implements UserDetailsService {
|
|||
throw new UsernameNotFoundException("用户不存在");
|
||||
}
|
||||
|
||||
// 设置用户权限
|
||||
List<SimpleGrantedAuthority> authorities = findPermissionByUserId(userEntity.getId()).stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.toList();
|
||||
Long userId = userEntity.getId();
|
||||
|
||||
// 设置用户角色
|
||||
String[] roles = findUserRolesByUserId(userId);
|
||||
|
||||
// 设置用户权限
|
||||
List<String> permissionsByUserId = findPermissionByUserId(userId);
|
||||
String[] permissions = permissionsByUserId.toArray(String[]::new);
|
||||
|
||||
// 也可以转成下面的形式
|
||||
// List<String> permissions = permissionsByUserId.stream()
|
||||
// .map(SimpleGrantedAuthority::new)
|
||||
// .toList();
|
||||
|
||||
String[] authorities = ArrayUtils.addAll(roles, permissions);
|
||||
|
||||
// 设置用户权限
|
||||
return User.builder()
|
||||
.username(userEntity.getUsername())
|
||||
.password(userEntity.getPassword())
|
||||
// 设置用户 authorities
|
||||
.authorities(authorities)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户id查找该用户的角色内容
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @return 当前用户的角色信息
|
||||
*/
|
||||
public String[] findUserRolesByUserId(Long userId) {
|
||||
List<RoleEntity> roleList = userMapper.selectRolesByUserId(userId);
|
||||
return roleList.stream().map(RoleEntity::getRoleCode).toArray(String[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户id查找该用户的权限内容
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @return 当前用户的权限信息
|
||||
*/
|
||||
public List<String> findPermissionByUserId(Long userId) {
|
||||
List<PermissionEntity> permissionList = userMapper.selectPermissionByUserId(userId);
|
||||
return permissionList.stream().map(PermissionEntity::getPermissionCode).toList();
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.spring.step2.service;
|
|||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.spring.step2.domain.dto.PermissionDto;
|
||||
import com.spring.step2.domain.dto.permission.PermissionDto;
|
||||
import com.spring.step2.domain.entity.PermissionEntity;
|
||||
import com.spring.step2.domain.vo.PermissionVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -46,4 +46,12 @@ public interface PermissionService extends IService<PermissionEntity> {
|
|||
* @param ids 删除id列表
|
||||
*/
|
||||
void deletePermission(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获取所有的权限列表
|
||||
*
|
||||
* @return 所有权限列表
|
||||
*/
|
||||
List<PermissionVo> getAllPermission();
|
||||
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ package com.spring.step2.service;
|
|||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.spring.step2.domain.dto.RolePermissionDto;
|
||||
import com.spring.step2.domain.dto.role.AssignRolePermissionDto;
|
||||
import com.spring.step2.domain.dto.role.RolePermissionDto;
|
||||
import com.spring.step2.domain.entity.RolePermissionEntity;
|
||||
import com.spring.step2.domain.vo.RolePermissionVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -46,4 +48,19 @@ public interface RolePermissionService extends IService<RolePermissionEntity> {
|
|||
* @param ids 删除id列表
|
||||
*/
|
||||
void deleteRolePermission(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 根据角色id获取权限内容
|
||||
*
|
||||
* @param permissionId 权限id
|
||||
* @return 角色权限列表
|
||||
*/
|
||||
List<RolePermissionVo> getRolePermissionById(Long permissionId);
|
||||
|
||||
/**
|
||||
* 根据角色id分配权限
|
||||
*
|
||||
* @param dto 为角色分配权限 {@link AssignRolePermissionDto}
|
||||
*/
|
||||
void assignRolePermission(@Valid AssignRolePermissionDto dto);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.spring.step2.service;
|
|||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.spring.step2.domain.dto.RoleDto;
|
||||
import com.spring.step2.domain.dto.role.RoleDto;
|
||||
import com.spring.step2.domain.entity.RoleEntity;
|
||||
import com.spring.step2.domain.vo.RoleVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -47,4 +47,12 @@ public interface RoleService extends IService<RoleEntity> {
|
|||
* @param ids 删除id列表
|
||||
*/
|
||||
void deleteRole(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获取全部角色列表
|
||||
*
|
||||
* @return 角色列表
|
||||
*/
|
||||
List<RoleVo> getRoleList();
|
||||
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ package com.spring.step2.service;
|
|||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.spring.step2.domain.dto.UserRoleDto;
|
||||
import com.spring.step2.domain.dto.user.AssignUserRoleDto;
|
||||
import com.spring.step2.domain.dto.user.UserRoleDto;
|
||||
import com.spring.step2.domain.entity.UserRoleEntity;
|
||||
import com.spring.step2.domain.vo.UserRoleVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -46,4 +48,19 @@ public interface UserRoleService extends IService<UserRoleEntity> {
|
|||
* @param ids 删除id列表
|
||||
*/
|
||||
void deleteUserRole(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 根据用户id获取当前用户角色列表
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @return 用户和角色列表
|
||||
*/
|
||||
List<UserRoleVo> getRoleListByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 根据用户id分配用户角色
|
||||
*
|
||||
* @param dto 用户分配角色DTO {@link AssignUserRoleDto}
|
||||
*/
|
||||
void assignUserRole(@Valid AssignUserRoleDto dto);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package com.spring.step2.service;
|
|||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.spring.step2.domain.dto.UserDto;
|
||||
import com.spring.step2.domain.dto.user.UserDto;
|
||||
import com.spring.step2.domain.entity.UserEntity;
|
||||
import com.spring.step2.domain.vo.UserVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
|
|
@ -4,7 +4,7 @@ import com.baomidou.dynamic.datasource.annotation.DS;
|
|||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.spring.step2.domain.dto.PermissionDto;
|
||||
import com.spring.step2.domain.dto.permission.PermissionDto;
|
||||
import com.spring.step2.domain.entity.PermissionEntity;
|
||||
import com.spring.step2.domain.vo.PermissionVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -46,6 +46,7 @@ public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permiss
|
|||
.list(page.getRecords())
|
||||
.pageNo(page.getCurrent())
|
||||
.pageSize(page.getSize())
|
||||
.pages(page.getPages())
|
||||
.total(page.getTotal())
|
||||
.build();
|
||||
}
|
||||
|
@ -83,4 +84,18 @@ public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permiss
|
|||
public void deletePermission(List<Long> ids) {
|
||||
removeByIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的权限列表
|
||||
*
|
||||
* @return 所有权限列表
|
||||
*/
|
||||
@Override
|
||||
public List<PermissionVo> getAllPermission() {
|
||||
return list().stream().map(permissionEntity -> {
|
||||
PermissionVo permissionVo = new PermissionVo();
|
||||
BeanUtils.copyProperties(permissionEntity, permissionVo);
|
||||
return permissionVo;
|
||||
}).toList();
|
||||
}
|
||||
}
|
|
@ -4,7 +4,8 @@ import com.baomidou.dynamic.datasource.annotation.DS;
|
|||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.spring.step2.domain.dto.RolePermissionDto;
|
||||
import com.spring.step2.domain.dto.role.AssignRolePermissionDto;
|
||||
import com.spring.step2.domain.dto.role.RolePermissionDto;
|
||||
import com.spring.step2.domain.entity.RolePermissionEntity;
|
||||
import com.spring.step2.domain.vo.RolePermissionVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -46,6 +47,7 @@ public class RolePermissionServiceImpl extends ServiceImpl<RolePermissionMapper,
|
|||
.list(page.getRecords())
|
||||
.pageNo(page.getCurrent())
|
||||
.pageSize(page.getSize())
|
||||
.pages(page.getPages())
|
||||
.total(page.getTotal())
|
||||
.build();
|
||||
}
|
||||
|
@ -83,4 +85,44 @@ public class RolePermissionServiceImpl extends ServiceImpl<RolePermissionMapper,
|
|||
public void deleteRolePermission(List<Long> ids) {
|
||||
removeByIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色id获取权限内容
|
||||
*
|
||||
* @param permissionId 权限id
|
||||
* @return 角色权限列表
|
||||
*/
|
||||
@Override
|
||||
public List<RolePermissionVo> getRolePermissionById(Long permissionId) {
|
||||
List<RolePermissionEntity> list = baseMapper.selectListByPermissionId(permissionId);
|
||||
|
||||
return list.stream().map(rolePermissionEntity -> {
|
||||
RolePermissionVo rolePermissionVo = new RolePermissionVo();
|
||||
BeanUtils.copyProperties(rolePermissionEntity, rolePermissionVo);
|
||||
return rolePermissionVo;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据角色id分配权限
|
||||
*
|
||||
* @param dto 为角色分配权限 {@link AssignRolePermissionDto}
|
||||
*/
|
||||
@Override
|
||||
public void assignRolePermission(AssignRolePermissionDto dto) {
|
||||
Long roleId = dto.getRoleId();
|
||||
List<Long> permissionIds = dto.getPermissionIds();
|
||||
|
||||
// 先删除当前已经分配的角色权限内容
|
||||
baseMapper.deleteByRoleId(roleId);
|
||||
|
||||
List<RolePermissionEntity> rolePermissionEntityList = permissionIds.stream().map(permissionId -> {
|
||||
RolePermissionEntity rolePermissionEntity = new RolePermissionEntity();
|
||||
rolePermissionEntity.setRoleId(roleId);
|
||||
rolePermissionEntity.setPermissionId(permissionId);
|
||||
return rolePermissionEntity;
|
||||
}).toList();
|
||||
|
||||
saveBatch(rolePermissionEntityList);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import com.baomidou.dynamic.datasource.annotation.DS;
|
|||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.spring.step2.domain.dto.RoleDto;
|
||||
import com.spring.step2.domain.dto.role.RoleDto;
|
||||
import com.spring.step2.domain.entity.RoleEntity;
|
||||
import com.spring.step2.domain.vo.RoleVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -46,6 +46,7 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, RoleEntity> impleme
|
|||
.list(page.getRecords())
|
||||
.pageNo(page.getCurrent())
|
||||
.pageSize(page.getSize())
|
||||
.pages(page.getPages())
|
||||
.total(page.getTotal())
|
||||
.build();
|
||||
}
|
||||
|
@ -83,4 +84,20 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, RoleEntity> impleme
|
|||
public void deleteRole(List<Long> ids) {
|
||||
removeByIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全部角色列表
|
||||
*
|
||||
* @return 角色列表
|
||||
*/
|
||||
@Override
|
||||
public List<RoleVo> getRoleList() {
|
||||
return list().stream()
|
||||
.map(roleEntity -> {
|
||||
RoleVo roleVo = new RoleVo();
|
||||
BeanUtils.copyProperties(roleEntity, roleVo);
|
||||
return roleVo;
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}
|
|
@ -4,7 +4,8 @@ import com.baomidou.dynamic.datasource.annotation.DS;
|
|||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.spring.step2.domain.dto.UserRoleDto;
|
||||
import com.spring.step2.domain.dto.user.AssignUserRoleDto;
|
||||
import com.spring.step2.domain.dto.user.UserRoleDto;
|
||||
import com.spring.step2.domain.entity.UserRoleEntity;
|
||||
import com.spring.step2.domain.vo.UserRoleVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -46,6 +47,7 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRoleEnt
|
|||
.list(page.getRecords())
|
||||
.pageNo(page.getCurrent())
|
||||
.pageSize(page.getSize())
|
||||
.pages(page.getPages())
|
||||
.total(page.getTotal())
|
||||
.build();
|
||||
}
|
||||
|
@ -83,4 +85,44 @@ public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRoleEnt
|
|||
public void deleteUserRole(List<Long> ids) {
|
||||
removeByIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户id获取当前用户角色列表
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @return 用户和角色列表
|
||||
*/
|
||||
@Override
|
||||
public List<UserRoleVo> getRoleListByUserId(Long userId) {
|
||||
List<UserRoleEntity> userRoleEntityList = baseMapper.getRoleListByUserId(userId);
|
||||
return userRoleEntityList.stream().map(userRoleEntity -> {
|
||||
UserRoleVo userRoleVo = new UserRoleVo();
|
||||
BeanUtils.copyProperties(userRoleEntity, userRoleVo);
|
||||
return userRoleVo;
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户id分配用户角色
|
||||
*
|
||||
* @param dto 用户分配角色DTO {@link AssignUserRoleDto}
|
||||
*/
|
||||
@Override
|
||||
public void assignUserRole(AssignUserRoleDto dto) {
|
||||
Long userId = dto.getUserId();
|
||||
List<Long> roleIds = dto.getRoleIds();
|
||||
|
||||
// 先删除已经分配的角色
|
||||
baseMapper.deleteByUserId(userId);
|
||||
|
||||
// 为用户分配角色
|
||||
List<UserRoleEntity> entityList = roleIds.stream().map(roleId -> {
|
||||
UserRoleEntity userRoleEntity = new UserRoleEntity();
|
||||
userRoleEntity.setUserId(userId);
|
||||
userRoleEntity.setRoleId(roleId);
|
||||
return userRoleEntity;
|
||||
}).toList();
|
||||
saveBatch(entityList);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import com.baomidou.dynamic.datasource.annotation.DS;
|
|||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.spring.step2.domain.dto.UserDto;
|
||||
import com.spring.step2.domain.dto.user.UserDto;
|
||||
import com.spring.step2.domain.entity.UserEntity;
|
||||
import com.spring.step2.domain.vo.UserVo;
|
||||
import com.spring.step2.domain.vo.result.PageResult;
|
||||
|
@ -15,6 +15,7 @@ import org.springframework.beans.BeanUtils;
|
|||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -45,10 +46,12 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> impleme
|
|||
public PageResult<UserVo> getUserPage(Page<UserEntity> pageParams, UserDto dto) {
|
||||
IPage<UserVo> page = baseMapper.selectListByPage(pageParams, dto);
|
||||
|
||||
|
||||
return PageResult.<UserVo>builder()
|
||||
.list(page.getRecords())
|
||||
.pageNo(page.getCurrent())
|
||||
.pageSize(page.getSize())
|
||||
.pages(page.getPages())
|
||||
.total(page.getTotal())
|
||||
.build();
|
||||
}
|
||||
|
@ -63,8 +66,11 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> impleme
|
|||
UserEntity user = new UserEntity();
|
||||
BeanUtils.copyProperties(dto, user);
|
||||
|
||||
// 设置用户密码
|
||||
// 用户密码是否为空,为空设置默认密码
|
||||
String password = user.getPassword();
|
||||
password = StringUtils.hasText(password) ? password : "123456";
|
||||
|
||||
// 设置用户密码
|
||||
String encodePassword = passwordEncoder.encode(password);
|
||||
user.setPassword(encodePassword);
|
||||
|
||||
|
@ -80,6 +86,14 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> impleme
|
|||
public void updateUser(UserDto dto) {
|
||||
UserEntity user = new UserEntity();
|
||||
BeanUtils.copyProperties(dto, user);
|
||||
|
||||
// 设置用户密码
|
||||
String password = user.getPassword();
|
||||
if (StringUtils.hasText(password)) {
|
||||
String encodePassword = passwordEncoder.encode(password);
|
||||
user.setPassword(encodePassword);
|
||||
}
|
||||
|
||||
updateById(user);
|
||||
}
|
||||
|
||||
|
|
|
@ -44,5 +44,7 @@ mybatis-plus:
|
|||
|
||||
logging:
|
||||
level:
|
||||
com.spring: debug
|
||||
root: info
|
||||
com.spring: debug
|
||||
org.springframework.security: debug
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
<resultMap id="BaseResultMap" type="com.spring.step2.domain.entity.PermissionEntity">
|
||||
<id column="id" property="id"/>
|
||||
<id column="permission_code" property="permissionCode"/>
|
||||
<id column="url" property="url"/>
|
||||
<id column="method" property="method"/>
|
||||
<id column="description" property="description"/>
|
||||
<id column="remark" property="remark"/>
|
||||
<id column="create_time" property="createTime"/>
|
||||
|
@ -17,7 +19,7 @@
|
|||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id,permission_code,description,remark,create_time,update_time,create_user,update_user,is_deleted
|
||||
id,permission_code,url,method,description,remark,create_time,update_time,create_user,update_user,is_deleted
|
||||
</sql>
|
||||
|
||||
<!-- 分页查询系统权限表内容 -->
|
||||
|
@ -30,6 +32,12 @@
|
|||
<if test="dto.permissionCode != null and dto.permissionCode != ''">
|
||||
and permission_code like CONCAT('%',#{dto.permissionCode},'%')
|
||||
</if>
|
||||
<if test="dto.url != null and dto.url != ''">
|
||||
and url like CONCAT('%',#{dto.url},'%')
|
||||
</if>
|
||||
<if test="dto.method != null and dto.method != ''">
|
||||
and method like CONCAT('%',#{dto.method},'%')
|
||||
</if>
|
||||
<if test="dto.description != null and dto.description != ''">
|
||||
and description like CONCAT('%',#{dto.description},'%')
|
||||
</if>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="com.spring.step2.domain.entity.RoleEntity">
|
||||
<id column="id" property="id"/>
|
||||
<id column="role_name" property="roleName"/>
|
||||
<id column="role_name" property="roleCode"/>
|
||||
<id column="description" property="description"/>
|
||||
<id column="remark" property="remark"/>
|
||||
<id column="create_time" property="createTime"/>
|
||||
|
@ -30,8 +30,8 @@
|
|||
<if test="dto.id != null and dto.id != ''">
|
||||
and id like CONCAT('%',#{dto.id},'%')
|
||||
</if>
|
||||
<if test="dto.roleName != null and dto.roleName != ''">
|
||||
and role_name like CONCAT('%',#{dto.roleName},'%')
|
||||
<if test="dto.roleCode != null and dto.roleCode != ''">
|
||||
and role_code like CONCAT('%',#{dto.roleCode},'%')
|
||||
</if>
|
||||
<if test="dto.description != null and dto.description != ''">
|
||||
and description like CONCAT('%',#{dto.description},'%')
|
||||
|
|
|
@ -19,6 +19,13 @@
|
|||
id,role_id,permission_id,create_time,update_time,create_user,update_user,is_deleted
|
||||
</sql>
|
||||
|
||||
<!-- 先删除当前已经分配的角色权限内容 -->
|
||||
<delete id="deleteByRoleId">
|
||||
delete
|
||||
from t_role_permission
|
||||
where role_id = #{roleId}
|
||||
</delete>
|
||||
|
||||
<!-- 分页查询角色权限关联表内容 -->
|
||||
<select id="selectListByPage" resultType="com.spring.step2.domain.vo.RolePermissionVo">
|
||||
select
|
||||
|
@ -29,4 +36,15 @@
|
|||
</where>
|
||||
</select>
|
||||
|
||||
<!-- 根据角色id获取权限内容 -->
|
||||
<select id="selectListByPermissionId" resultType="com.spring.step2.domain.entity.RolePermissionEntity">
|
||||
select *
|
||||
from t_role_permission
|
||||
<where>
|
||||
<if test="permissionId != null">
|
||||
permission_id = #{permissionId}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
|
|
@ -51,8 +51,20 @@
|
|||
<include refid="Base_Column_List"/>
|
||||
from t_user
|
||||
<where>
|
||||
<if test="username != null and username != ;;">
|
||||
username=#{username}
|
||||
<if test="username != null and username != null">
|
||||
username = #{username}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!-- 根据用户id查找该用户的角色内容 -->
|
||||
<select id="selectRolesByUserId" resultType="com.spring.step2.domain.entity.RoleEntity">
|
||||
SELECT tr.*
|
||||
FROM t_user_role tur
|
||||
JOIN t_role tr ON tur.role_id = tr.id
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
tur.user_id = #{userId}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
|
|
@ -19,6 +19,13 @@
|
|||
id,role_id,user_id,create_time,update_time,create_user,update_user,is_deleted
|
||||
</sql>
|
||||
|
||||
<!-- 根据用户id删除用户相关分配角色 -->
|
||||
<delete id="deleteByUserId">
|
||||
delete
|
||||
from t_user_role
|
||||
where user_id = #{userId}
|
||||
</delete>
|
||||
|
||||
<!-- 分页查询用户角色关联表内容 -->
|
||||
<select id="selectListByPage" resultType="com.spring.step2.domain.vo.UserRoleVo">
|
||||
select
|
||||
|
@ -29,4 +36,16 @@
|
|||
</where>
|
||||
</select>
|
||||
|
||||
<!-- 根据用户id获取当前用户角色列表 -->
|
||||
<select id="getRoleListByUserId" resultType="com.spring.step2.domain.entity.UserRoleEntity">
|
||||
select *
|
||||
from t_user_role
|
||||
<where>
|
||||
<if test="userId != null">
|
||||
user_id = #{userId}
|
||||
</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
const {defineComponent} = Vue;
|
||||
|
||||
const HeaderNavs = defineComponent({
|
||||
name: 'HeaderNavs',
|
||||
template: `
|
||||
<ul class="nav nav-tabs justify-content-center my-1">
|
||||
<li class="nav-item" v-for="(nav,index) in navList" :key="index">
|
||||
<a class="nav-link" aria-current="page" :class="{active:activeItem(nav)}"
|
||||
:href="nav.href">{{nav.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
navList: ref([
|
||||
{href: "/login", title: "登录页面"},
|
||||
{href: "/user", title: "用户页面"},
|
||||
{href: "/role", title: "角色页面"},
|
||||
{href: "/permission", title: "权限页面"},
|
||||
])
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
activeItem(nav) {
|
||||
const pathname = window.location.pathname;
|
||||
return pathname === nav.href;
|
||||
},
|
||||
},
|
||||
})
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
// pagination 类型
|
||||
const pagination = {
|
||||
// 当前页
|
||||
pageNo: 0,
|
||||
// 分页大小
|
||||
pageSize: 0,
|
||||
// 总分页数
|
||||
pages: 0,
|
||||
}
|
||||
*/
|
||||
|
||||
const Pagination = defineComponent({
|
||||
name: "Pagination",
|
||||
template: `
|
||||
<nav aria-label="Page navigation" class="mt-3">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item" :class="{disabled:this.pagination.pageNo == 1}">
|
||||
<a @click="pageNoDecrease" class="page-link" href="javascript:">上一页</a>
|
||||
</li>
|
||||
|
||||
<li :class="{active:page===pagination.pageNo}" :key="index" class="page-item"
|
||||
v-for="(page,index) in pagination.pages">
|
||||
<a class="page-link" href="javascript:" @click="onCurrentPageClick(page)">{{page}}</a>
|
||||
</li>
|
||||
|
||||
<li class="page-item" :class="{disabled:this.pagination.pageNo >= this.pagination.pages}">
|
||||
<a @click="pageNoIncrease" class="page-link" href="javascript:">下一页</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
`,
|
||||
props: {
|
||||
pagination: {type: Object, required: true},
|
||||
// 初始化加载数据
|
||||
onSearch: {type: Function, required: true},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
/* 当前分页+1 */
|
||||
pageNoIncrease() {
|
||||
this.pagination.pageNo++;
|
||||
this.$emit("update:pagination", this.pagination);
|
||||
this.onSearch();
|
||||
},
|
||||
/* 当前分页-1 */
|
||||
pageNoDecrease() {
|
||||
this.pagination.pageNo--;
|
||||
this.$emit("update:pagination", this.pagination);
|
||||
this.onSearch();
|
||||
},
|
||||
/* 点击当前页 */
|
||||
onCurrentPageClick(page) {
|
||||
this.pagination.pageNo = page;
|
||||
this.$emit("update:pagination", this.pagination);
|
||||
this.onSearch();
|
||||
}
|
||||
}
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
// axios 配置
|
||||
const axiosInstance = axios.create({
|
||||
baseURL: 'http://localhost:8800/api',
|
||||
baseURL: 'http://localhost:8772/api',
|
||||
timeout: 16000,
|
||||
headers: {'Content-Type': 'application/json;charset=utf-8'},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/* 提高 Antd Message 的 z-index,让提示框为最上层 */
|
||||
.ant-message {
|
||||
z-index: 1100 !important;
|
||||
}
|
||||
|
||||
/* 响应式 OffCanvas 宽度 */
|
||||
.offcanvas.offcanvas-start {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.offcanvas.offcanvas-start {
|
||||
width: 75%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.offcanvas.offcanvas-start {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 添加自定义样式 */
|
||||
.database-info-card {
|
||||
border-left: 4px solid #0d6efd;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.table-info-section {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.column-list {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.column-item {
|
||||
border-left: 3px solid #6c757d;
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.column-item:hover {
|
||||
border-left-color: #0d6efd;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.badge-java {
|
||||
background-color: #5382a1;
|
||||
}
|
||||
|
||||
.badge-jdbc {
|
||||
background-color: #4479a1;
|
||||
}
|
||||
|
||||
.badge-js {
|
||||
background-color: #f7df1e;
|
||||
color: #000;
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
body {
|
||||
background-color: #f8f9fa;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.card {
|
6
spring-security/step-2/src/main/resources/static/src/lib/js/boostrap/popper.min.js
vendored
Normal file
6
spring-security/step-2/src/main/resources/static/src/lib/js/boostrap/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,112 @@
|
|||
const DialogPermission = defineComponent({
|
||||
name: "DialogPermission",
|
||||
template: `
|
||||
<div class="modal fade" id="permissionBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1"
|
||||
aria-labelledby="permissionBackdropLabel" ref="modalRef">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
||||
<!-- 头部 -->
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{isAdd?"新增权限":"修改权限"}}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="onSubmit">
|
||||
<!-- 内容 -->
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="dialogPermissionCode"><i
|
||||
class="fas fa-user-alt me-1"></i>权限码</label>
|
||||
<input autocomplete="false" class="form-control" id="dialogPermissionCode" placeholder="请输入权限码"
|
||||
type="text" v-model="form.permissionCode" required>
|
||||
<div class="form-text">在这里输入你的权限码。</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="url"><i class="fas fa-ruler me-1"></i>URL</label>
|
||||
<input autocomplete="false" class="form-control" id="url" placeholder="请输入URL" type="text"
|
||||
v-model="form.url">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="method"><i class="fas fa-pager me-1"></i>请求方法名称</label>
|
||||
<select class="form-select" aria-label="Default select example" v-model="form.method">
|
||||
<option v-for="method in requestMethod" :key="method">
|
||||
{{method}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="dialogDescription"><i class="fas fa-user-alt me-1"></i>描述</label>
|
||||
<input autocomplete="false" class="form-control" id="dialogDescription" placeholder="请输入描述"
|
||||
type="text" v-model="form.description" required>
|
||||
<div class="form-text">在这里输入你的描述。</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="dialogRemark"><i class="fas fa-user-alt me-1"></i>简述</label>
|
||||
<input autocomplete="false" class="form-control" id="dialogRemark" placeholder="请输入简述"
|
||||
type="text" v-model="form.remark" required>
|
||||
<div class="form-text">在这里输入你的简述。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="submit" class="btn btn-primary">确认</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
// 是否添加
|
||||
isAdd: {type: Boolean, default: false},
|
||||
// 权限信息
|
||||
permissionInfo: {type: Object, required: true},
|
||||
// 加载函数
|
||||
onSearch: {type: Function, required: true},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modalInstance: ref(null),
|
||||
form: ref({}),
|
||||
requestMethod: ref(["", "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "TRACE"])
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
// 是否添加表单
|
||||
const {code, message} = this.isAdd ?
|
||||
await axiosInstance.post("/permission", this.form) :
|
||||
await axiosInstance.put("/permission", this.form);
|
||||
|
||||
if (code === 200) {
|
||||
antd.message.success(message);
|
||||
// 关闭模态框
|
||||
this.modalInstance.hide();
|
||||
this.onSearch();
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
permissionInfo(val) {
|
||||
// 创建深拷贝,而不是直接赋值
|
||||
this.form = JSON.parse(JSON.stringify(val));
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化模态框实例
|
||||
const modalEl = this.$refs.modalRef;
|
||||
this.modalInstance = new bootstrap.Modal(modalEl, {
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
// 组件销毁时清理模态框实例
|
||||
if (this.modalInstance) {
|
||||
this.modalInstance.dispose();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
const AssignPermission = defineComponent({
|
||||
name: "AssignPermission",
|
||||
template: `
|
||||
<div class="offcanvas offcanvas-start" data-bs-scroll="true" id="assignPermissionOffCanvas"
|
||||
aria-labelledby="assignPermissionOffCanvasLabel" ref="assignPermissionOffCanvasRef">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="assignPermissionOffCanvasLabel">
|
||||
为角色分配权限
|
||||
<a href="JavaScript:" class="icon-link icon-link-hover text-decoration-none" @click="onSave">
|
||||
<i class="fas fa-save"></i>
|
||||
保存
|
||||
</a>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<div class="form-check" v-for="(permission,index) in permissionList" :key="permission.id">
|
||||
<input class="form-check-input" type="checkbox" v-model="permission.checked" :id="permission.permissionCode">
|
||||
<label class="form-check-label" :for="permission.permissionCode">
|
||||
{{permission.remark}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
roleInfo: {type: Object, required: true},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 所有的权限列表
|
||||
permissionList: ref([]),
|
||||
// 角色权限列表
|
||||
rolePermissionIds: ref([]),
|
||||
modalInstance: ref(null)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/* 获取权限列表 */
|
||||
async getPermissionList() {
|
||||
const {data} = await axiosInstance.get("/permission/all");
|
||||
this.permissionList = data;
|
||||
},
|
||||
|
||||
/* 保存分配角色权限 */
|
||||
async onSave() {
|
||||
// 过滤出已经被选择的权限
|
||||
const checkedPermissionList = this.permissionList
|
||||
.filter(role => role.checked)
|
||||
.map(role => role.id);
|
||||
|
||||
// 分配的数据内容
|
||||
const data = {
|
||||
roleId: this.roleInfo.id,
|
||||
permissionIds: checkedPermissionList,
|
||||
}
|
||||
|
||||
// 为角色分配权限
|
||||
const {code, message} = await axiosInstance.post("/role-permission/assign-permission", data)
|
||||
if (code === 200) {
|
||||
this.modalInstance.hide();
|
||||
this.rolePermissionIds = [];
|
||||
antd.message.success(message);
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/* 监视角色信息 */
|
||||
async roleInfo(value) {
|
||||
const {id} = value;
|
||||
// 如果没有id直接返回
|
||||
if (!id) return;
|
||||
|
||||
// 获取权限列表
|
||||
await this.getPermissionList();
|
||||
|
||||
// 获取角色拥有的权限
|
||||
const {data} = await axiosInstance.get("/role-permission/permissions", {params: {userId: id}});
|
||||
// 提取角色拥有的权限ID数组
|
||||
const rolePermissionIds = data.map(permission => permission.permissionId + "");
|
||||
|
||||
// 遍历所有权限,检查角色是否拥有该权限
|
||||
this.permissionList.forEach(role => {
|
||||
const hasRole = rolePermissionIds.includes(role.id);
|
||||
if (hasRole) role.checked = true;
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化模态框实例
|
||||
const modalEl = this.$refs.assignPermissionOffCanvasRef;
|
||||
this.modalInstance = new bootstrap.Offcanvas(modalEl);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,97 @@
|
|||
const DialogRole = defineComponent({
|
||||
name: "DialogRole",
|
||||
template: `
|
||||
<div class="modal fade" id="roleBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1"
|
||||
aria-labelledby="roleBackdropLabel" ref="modalRef">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
||||
<!-- 头部 -->
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{isAdd?"新增角色":"修改角色"}}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="onSubmit">
|
||||
<!-- 内容 -->
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="dialogRoleCode"><i class="fas fa-user-alt me-1"></i>角色码</label>
|
||||
<input autocomplete="false" class="form-control" id="dialogRoleCode" placeholder="请输入角色名"
|
||||
type="text" v-model="form.roleCode" required>
|
||||
<div class="form-text">在这里输入你的角色码。</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="dialogDescription"><i class="fas fa-user-alt me-1"></i>描述</label>
|
||||
<input autocomplete="false" class="form-control" id="dialogDescription" placeholder="请输入描述"
|
||||
type="text" v-model="form.description" required>
|
||||
<div class="form-text">在这里输入你的描述。</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="dialogRemark"><i class="fas fa-user-alt me-1"></i>简述</label>
|
||||
<input autocomplete="false" class="form-control" id="dialogRemark" placeholder="请输入简述"
|
||||
type="text" v-model="form.remark" required>
|
||||
<div class="form-text">在这里输入你的简述。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="submit" class="btn btn-primary">确认</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
// 是否添加
|
||||
isAdd: {type: Boolean, default: false},
|
||||
// 角色信息
|
||||
roleInfo: {type: Object, required: true},
|
||||
// 加载函数
|
||||
onSearch: {type: Function, required: true},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modalInstance: ref(null),
|
||||
form: ref({}),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
// 是否添加表单
|
||||
const {code, message} = this.isAdd ?
|
||||
await axiosInstance.post("/role", this.form) :
|
||||
await axiosInstance.put("/role", this.form);
|
||||
|
||||
if (code === 200) {
|
||||
antd.message.success(message);
|
||||
// 关闭模态框
|
||||
this.modalInstance.hide();
|
||||
this.onSearch();
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
roleInfo(val) {
|
||||
// 创建深拷贝,而不是直接赋值
|
||||
this.form = JSON.parse(JSON.stringify(val));
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化模态框实例
|
||||
const modalEl = this.$refs.modalRef;
|
||||
this.modalInstance = new bootstrap.Modal(modalEl, {
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
// 组件销毁时清理模态框实例
|
||||
if (this.modalInstance) {
|
||||
this.modalInstance.dispose();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
const AssignRoles = defineComponent({
|
||||
name: "AssignRoles",
|
||||
template: `
|
||||
<div class="offcanvas offcanvas-start" data-bs-scroll="true" id="assignRoleOffCanvas"
|
||||
aria-labelledby="assignRoleOffCanvasLabel" ref="assignRoleOffCanvasRef">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="assignRoleOffCanvasLabel">
|
||||
为用户分配角色
|
||||
<a href="JavaScript:" class="icon-link icon-link-hover text-decoration-none" @click="onSave">
|
||||
<i class="fas fa-save"></i>
|
||||
保存
|
||||
</a>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<div class="form-check" v-for="(role,index) in roleList" :key="role.id">
|
||||
<input class="form-check-input" type="checkbox" v-model="role.checked" :id="role.roleCode">
|
||||
<label class="form-check-label" :for="role.roleCode">
|
||||
{{role.remark}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
userinfo: {type: Object, required: true},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 所有的角色列表
|
||||
roleList: ref([]),
|
||||
// 用户角色列表
|
||||
userRoleIds: ref([]),
|
||||
modalInstance: ref(null)
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/* 获取角色列表 */
|
||||
async getRoleList() {
|
||||
const {data} = await axiosInstance.get("/role/all");
|
||||
this.roleList = data;
|
||||
},
|
||||
|
||||
/* 保存分配用户角色 */
|
||||
async onSave() {
|
||||
// 过滤出已经被选择的角色
|
||||
const checkedRoleList = this.roleList
|
||||
.filter(role => role.checked)
|
||||
.map(role => role.id);
|
||||
|
||||
// 分配的数据内容
|
||||
const data = {
|
||||
userId: this.userinfo.id,
|
||||
roleIds: checkedRoleList,
|
||||
}
|
||||
|
||||
// 为用户分配角色
|
||||
const {code, message} = await axiosInstance.post("/user-role/assign-role", data)
|
||||
if (code === 200) {
|
||||
this.modalInstance.hide();
|
||||
this.userRoleIds = [];
|
||||
antd.message.success(message);
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/* 监视用户信息 */
|
||||
async userinfo(value) {
|
||||
const {id} = value;
|
||||
// 如果没有id直接返回
|
||||
if (!id) return;
|
||||
|
||||
// 获取角色列表
|
||||
await this.getRoleList();
|
||||
|
||||
// 获取用户拥有的角色
|
||||
const {data} = await axiosInstance.get("/user-role/roles", {params: {userId: id}});
|
||||
// 提取用户拥有的角色ID数组
|
||||
const userRoleIds = data.map(role => role.roleId + "");
|
||||
|
||||
// 遍历所有角色,检查用户是否拥有该角色
|
||||
this.roleList.forEach(role => {
|
||||
const hasRole = userRoleIds.includes(role.id);
|
||||
if (hasRole) role.checked = true;
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化模态框实例
|
||||
const modalEl = this.$refs.assignRoleOffCanvasRef;
|
||||
this.modalInstance = new bootstrap.Offcanvas(modalEl);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,100 @@
|
|||
const DialogUser = defineComponent({
|
||||
name: "DialogUser",
|
||||
template: `
|
||||
<div class="modal fade" id="userBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1"
|
||||
aria-labelledby="userBackdropLabel" aria-hidden="true" ref="modalRef">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
|
||||
<!-- 头部 -->
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{isAdd?"新增用户":"修改用户"}}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="onSubmit">
|
||||
<!-- 内容 -->
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="dialogUsername"><i class="fas fa-user me-1"></i>用户名</label>
|
||||
<input autocomplete="false" class="form-control" id="dialogUsername" placeholder="请输入用户名"
|
||||
type="text" v-model="form.username" required>
|
||||
<div class="form-text">在这里输入你的用户名。</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="dialogPassword"><i class="fas fa-lock me-1"></i>密码</label>
|
||||
<input autocomplete="false" class="form-control" id="dialogPassword" placeholder="请输入密码"
|
||||
type="password" v-model="form.password">
|
||||
<div class="form-text">如果不修改或添加不填写此项。</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="dialogEmail"><i class="fas fa-envelope me-1"></i>邮箱</label>
|
||||
<input autocomplete="false" class="form-control" id="dialogEmail" placeholder="请输入邮箱"
|
||||
type="email" v-model="form.email" required>
|
||||
<div class="form-text">在这里输入你的邮箱。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="submit" class="btn btn-primary">确认</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
props: {
|
||||
// 是否添加
|
||||
isAdd: {type: Boolean, default: false},
|
||||
// 用户信息
|
||||
userinfo: {type: Object, required: true},
|
||||
// 加载函数
|
||||
onSearch: {type: Function, required: true},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modalInstance: ref(null),
|
||||
form: ref({}),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
// 是否添加表单
|
||||
const {code, message} = this.isAdd ?
|
||||
await axiosInstance.post("/user", this.form) :
|
||||
await axiosInstance.put("/user", this.form);
|
||||
|
||||
if (code === 200) {
|
||||
antd.message.success(message);
|
||||
// 关闭模态框
|
||||
this.modalInstance.hide();
|
||||
this.onSearch();
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
userinfo(val) {
|
||||
// 创建深拷贝,而不是直接赋值
|
||||
this.form = JSON.parse(JSON.stringify(val));
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// 初始化模态框实例
|
||||
const modalEl = this.$refs.modalRef;
|
||||
this.modalInstance = new bootstrap.Modal(modalEl, {
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
// 组件销毁时清理模态框实例
|
||||
if (this.modalInstance) {
|
||||
this.modalInstance.dispose();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -378,11 +378,10 @@
|
|||
<span>Spring Security 6</span>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#features" target="_blank">特性</a></li>
|
||||
<li><a href="#features">特性</a></li>
|
||||
<li><a href="#docs" target="_blank">文档</a></li>
|
||||
<li><a href="/doc.html" target="_blank">API 文档</a></li>
|
||||
<li><a href="/swagger-ui/index.html" target="_blank">Swagger UI</a></li>
|
||||
<li><a href="/login-page" target="_blank">登录</a></li>
|
||||
<li><a href="/user" target="_blank">用户管理</a></li>
|
||||
<li><a href="/login" target="_blank">登录</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" th:href="@{/webjars/bootstrap/5.1.3/css/bootstrap.min.css}">
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" th:href="@{/webjars/font-awesome/5.15.4/css/all.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/src/lib/css/style.css}">
|
||||
<link rel="stylesheet" th:href="@{/src/lib/css/tablePage.css}">
|
||||
<!-- Vue3 -->
|
||||
<script th:src="@{/src/lib/js/vue/vue.global.js}"></script>
|
||||
<!-- Bootstrap JS Bundle with Popper -->
|
||||
<script th:src="@{/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js}"></script>
|
||||
<!-- 本地引入 popper JS -->
|
||||
<script th:src="@{/src/lib/js/boostrap/popper.min.js}"></script>
|
||||
<!-- 本地引入 axios JS -->
|
||||
<script th:src="@{/src/lib/js/axios/axios.min.js}"></script>
|
||||
<!-- 引入dayjs -->
|
||||
<script th:src="@{/src/lib/js/dayjs/dayjs.min.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/customParseFormat.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/weekday.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/localeData.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/weekOfYear.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/weekYear.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/advancedFormat.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/quarterOfYear.js}"></script>
|
||||
<!-- 引入 antd JS -->
|
||||
<script th:src="@{/src/lib/js/dayjs/antd.min.js}"></script>
|
||||
|
||||
<title>权限管理页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid" id="app">
|
||||
<dialog-permission :is-add="isAdd" :on-search="onSearch"
|
||||
:permission-info="permissionInfo"></dialog-permission>
|
||||
|
||||
<header-navs></header-navs>
|
||||
<!-- 头部 -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-search me-2"></i>权限查询</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form @reset="onRest" @submit.prevent="onSearch">
|
||||
<div class="row g-3 align-items-center">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="permissionCode"><i
|
||||
class="fas fa-power-off me-1"></i>权限码</label>
|
||||
<input autocomplete="false" class="form-control" id="permissionCode" placeholder="请输入权限码"
|
||||
type="text" v-model="searchForm.permissionCode">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="url"><i
|
||||
class="fas fa-ruler me-1"></i>URL</label>
|
||||
<input autocomplete="false" class="form-control" id="url" placeholder="请输入URL"
|
||||
type="text" v-model="searchForm.url">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="method"><i
|
||||
class="fas fa-pager me-1"></i>请求方法名称</label>
|
||||
<input autocomplete="false" class="form-control" id="method" placeholder="请输入请求方法名称"
|
||||
type="text" v-model="searchForm.method">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="description"><i class="fas fa-text-width me-1"></i>描述</label>
|
||||
<input autocomplete="false" class="form-control" id="description" placeholder="请输入描述"
|
||||
type="text" v-model="searchForm.description">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="remark"><i class="fas fa-info me-1"></i>简述</label>
|
||||
<input autocomplete="false" class="form-control" id="remark" placeholder="请输入简述"
|
||||
type="text" v-model="searchForm.remark">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="search-btn-group">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<i class="fas fa-search me-1"></i>查询
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" type="reset">
|
||||
<i class="fas fa-redo me-1"></i>重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-power-off me-2"></i>权限列表</span>
|
||||
<button @click="onAdd" class="btn btn-sm btn-success"
|
||||
data-bs-target="#permissionBackdrop" data-bs-toggle="modal">
|
||||
<i class="fas fa-plus me-1"></i>新增权限
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th scope="col" width="5%">#</th>
|
||||
<th scope="col" width="10%">权限名</th>
|
||||
<th scope="col" width="10%">描述</th>
|
||||
<th scope="col" width="10%">简述</th>
|
||||
<th scope="col" width="10%">地址</th>
|
||||
<th scope="col" width="5%">方法</th>
|
||||
<th scope="col" width="15%">创建时间</th>
|
||||
<th scope="col" width="15%">更新时间</th>
|
||||
<th scope="col" width="20%">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :key="permission.id" v-for="(permission,index) in dataList">
|
||||
<th scope="row">{{index + 1}}</th>
|
||||
<td>{{permission.permissionCode}}</td>
|
||||
<td>{{permission.description}}</td>
|
||||
<td>{{permission.remark}}</td>
|
||||
<td>{{permission.url}}</td>
|
||||
<td>{{permission.method}}</td>
|
||||
<td>{{formatDate(permission.createTime)}}</td>
|
||||
<td>{{formatDate(permission.updateTime)}}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button @click="onEdit(permission)" class="btn btn-outline-primary btn-action"
|
||||
data-bs-target="#permissionBackdrop" data-bs-toggle="modal">
|
||||
<i class="fas fa-edit"></i> 修改
|
||||
</button>
|
||||
<button @click="onDeleted(permission)" class="btn btn-outline-danger btn-action">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 表格分页 -->
|
||||
<pagination :on-search="onSearch" v-model:pagination="searchForm"></pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<!-- 设置 popper 提示框 -->
|
||||
<script th:src="@{/src/config/popper-config.js}"></script>
|
||||
<!-- 加载全局axios配置 -->
|
||||
<script th:src="@{/src/config/axios-config.js}"></script>
|
||||
|
||||
<!-- 头部导航 -->
|
||||
<script th:src="@{/src/components/HeaderNavs.js}"></script>
|
||||
<!-- 分页 -->
|
||||
<script th:src="@{/src/components/Pagination.js}"></script>
|
||||
<!-- 权限表单 -->
|
||||
<script th:src="@{/src/views/permission/DialogPermission.js}"></script>
|
||||
<script>
|
||||
const {createApp, ref} = Vue;
|
||||
|
||||
const app = createApp({
|
||||
data() {
|
||||
return {
|
||||
// 查询表单
|
||||
searchForm: ref({
|
||||
permissionCode: undefined,
|
||||
url: undefined,
|
||||
method: undefined,
|
||||
description: undefined,
|
||||
remark: undefined,
|
||||
pageNo: 1,
|
||||
pageSize: 30,
|
||||
pages: 0
|
||||
}),
|
||||
// 权限信息
|
||||
permissionInfo: ref({}),
|
||||
// 弹窗标题
|
||||
isAdd: ref(false),
|
||||
// 查询权限列表
|
||||
dataList: ref([])
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
/* 格式化时间 */
|
||||
formatDate(date) {
|
||||
return dayjs(date).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
|
||||
/* 加载数据 */
|
||||
async onSearch() {
|
||||
const {pageNo, pageSize} = this.searchForm;
|
||||
// 查询数据
|
||||
const {data} = await axiosInstance.get(`/permission/${pageNo}/${pageSize}`, {params: this.searchForm})
|
||||
|
||||
// 赋值数据
|
||||
this.dataList = data.list;
|
||||
|
||||
// 设置分页内容
|
||||
this.searchForm.pageNo = data.pageNo;
|
||||
this.searchForm.pageSize = data.pageSize;
|
||||
this.searchForm.pages = data.pages;
|
||||
},
|
||||
|
||||
/* 重制表单 */
|
||||
onRest() {
|
||||
this.searchForm.permissionCode = undefined;
|
||||
this.searchForm.url = undefined;
|
||||
this.searchForm.method = undefined;
|
||||
this.searchForm.description = undefined;
|
||||
this.searchForm.remark = undefined;
|
||||
this.onSearch();
|
||||
},
|
||||
|
||||
/* 添加 */
|
||||
onAdd() {
|
||||
this.isAdd = true;
|
||||
this.permissionInfo = {};
|
||||
},
|
||||
|
||||
/* 修改 */
|
||||
onEdit(permission) {
|
||||
this.isAdd = false;
|
||||
this.permissionInfo = permission;
|
||||
},
|
||||
|
||||
/* 删除 */
|
||||
async onDeleted(permission) {
|
||||
const result = confirm("确认删除?");
|
||||
if (!result) return false;
|
||||
|
||||
// 删除权限
|
||||
const {code, message} = await axiosInstance.delete(`/permission`, {data: [permission.id]});
|
||||
if (code === 200) {
|
||||
await this.onSearch();
|
||||
antd.message.success(message);
|
||||
} else {
|
||||
antd.message.error(message);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.onSearch();
|
||||
},
|
||||
});
|
||||
|
||||
app.component('HeaderNavs', HeaderNavs)
|
||||
app.component('Pagination', Pagination)
|
||||
app.component('DialogPermission', DialogPermission)
|
||||
app.mount('#app');
|
||||
</script>
|
||||
</html>
|
|
@ -0,0 +1,246 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" th:href="@{/webjars/bootstrap/5.1.3/css/bootstrap.min.css}">
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" th:href="@{/webjars/font-awesome/5.15.4/css/all.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/src/lib/css/style.css}">
|
||||
<link rel="stylesheet" th:href="@{/src/lib/css/tablePage.css}">
|
||||
<!-- Vue3 -->
|
||||
<script th:src="@{/src/lib/js/vue/vue.global.js}"></script>
|
||||
<!-- Bootstrap JS Bundle with Popper -->
|
||||
<script th:src="@{/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js}"></script>
|
||||
<!-- 本地引入 popper JS -->
|
||||
<script th:src="@{/src/lib/js/boostrap/popper.min.js}"></script>
|
||||
<!-- 本地引入 axios JS -->
|
||||
<script th:src="@{/src/lib/js/axios/axios.min.js}"></script>
|
||||
<!-- 引入dayjs -->
|
||||
<script th:src="@{/src/lib/js/dayjs/dayjs.min.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/customParseFormat.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/weekday.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/localeData.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/weekOfYear.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/weekYear.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/advancedFormat.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/quarterOfYear.js}"></script>
|
||||
<!-- 引入 antd JS -->
|
||||
<script th:src="@{/src/lib/js/dayjs/antd.min.js}"></script>
|
||||
|
||||
<title>角色管理页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid" id="app">
|
||||
<dialog-role :is-add="isAdd" :on-search="onSearch" :role-info="roleInfo"></dialog-role>
|
||||
|
||||
<header-navs></header-navs>
|
||||
|
||||
<!-- 分配权限 -->
|
||||
<assign-permission :role-info="roleInfo"></assign-permission>
|
||||
|
||||
<!-- 头部 -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-search me-2"></i>角色查询</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form @reset="onRest" @submit.prevent="onSearch">
|
||||
<div class="row g-3 align-items-center">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="roleCode"><i class="fas fa-user-alt me-1"></i>角色名</label>
|
||||
<input autocomplete="false" class="form-control" id="roleCode" placeholder="请输入角色名"
|
||||
type="text" v-model="searchForm.roleCode">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="description"><i class="fas fa-text-width me-1"></i>描述</label>
|
||||
<input autocomplete="false" class="form-control" id="description" placeholder="请输入描述"
|
||||
type="text" v-model="searchForm.description">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="remark"><i class="fas fa-info me-1"></i>简述</label>
|
||||
<input autocomplete="false" class="form-control" id="remark" placeholder="请输入简述"
|
||||
type="text" v-model="searchForm.remark">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="search-btn-group">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<i class="fas fa-search me-1"></i>查询
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" type="reset">
|
||||
<i class="fas fa-redo me-1"></i>重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-users me-2"></i>角色列表</span>
|
||||
<button @click="onAdd" class="btn btn-sm btn-success" data-bs-target="#roleBackdrop" data-bs-toggle="modal">
|
||||
<i class="fas fa-plus me-1"></i>新增角色
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th scope="col" width="5%">#</th>
|
||||
<th scope="col" width="15%">角色名</th>
|
||||
<th scope="col" width="15%">描述</th>
|
||||
<th scope="col" width="15%">简述</th>
|
||||
<th scope="col" width="15%">创建时间</th>
|
||||
<th scope="col" width="15%">更新时间</th>
|
||||
<th scope="col" width="20%">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :key="role.id" v-for="(role,index) in dataList">
|
||||
<th scope="row">{{index + 1}}</th>
|
||||
<td>{{role.roleCode}}</td>
|
||||
<td>{{role.description}}</td>
|
||||
<td>{{role.remark}}</td>
|
||||
<td>{{formatDate(role.createTime)}}</td>
|
||||
<td>{{formatDate(role.updateTime)}}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button @click="onEdit(role)" class="btn btn-outline-primary btn-action"
|
||||
data-bs-target="#roleBackdrop" data-bs-toggle="modal">
|
||||
<i class="fas fa-edit"></i> 修改
|
||||
</button>
|
||||
<button @click="onAssignPermission(role)" class="btn btn-outline-primary btn-action"
|
||||
data-bs-target="#assignPermissionOffCanvas" data-bs-toggle="offcanvas">
|
||||
<i class="fas fa-cloud"></i> 分配权限
|
||||
</button>
|
||||
<button @click="onDeleted(role)" class="btn btn-outline-danger btn-action">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 表格分页 -->
|
||||
<pagination :on-search="onSearch" v-model:pagination="searchForm"></pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<!-- 设置 popper 提示框 -->
|
||||
<script th:src="@{/src/config/popper-config.js}"></script>
|
||||
<!-- 加载全局axios配置 -->
|
||||
<script th:src="@{/src/config/axios-config.js}"></script>
|
||||
|
||||
<!-- 头部导航 -->
|
||||
<script th:src="@{/src/components/HeaderNavs.js}"></script>
|
||||
<!-- 分页 -->
|
||||
<script th:src="@{/src/components/Pagination.js}"></script>
|
||||
<!-- 角色表单 -->
|
||||
<script th:src="@{/src/views/role/DialogRole.js}"></script>
|
||||
<!-- 角色分配权限 -->
|
||||
<script th:src="@{/src/views/role/AssignPermission.js}"></script>
|
||||
<script>
|
||||
const {createApp, ref, toRaw} = Vue;
|
||||
|
||||
const app = createApp({
|
||||
data() {
|
||||
return {
|
||||
// 查询表单
|
||||
searchForm: ref({
|
||||
roleCode: undefined,
|
||||
description: undefined,
|
||||
remark: undefined,
|
||||
pageNo: 1,
|
||||
pageSize: 30,
|
||||
pages: 0
|
||||
}),
|
||||
// 角色信息
|
||||
roleInfo: ref({}),
|
||||
// 弹窗标题
|
||||
isAdd: ref(false),
|
||||
// 查询角色列表
|
||||
dataList: ref([])
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
/* 格式化时间 */
|
||||
formatDate(date) {
|
||||
return dayjs(date).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
|
||||
/* 加载数据 */
|
||||
async onSearch() {
|
||||
const {pageNo, pageSize} = this.searchForm;
|
||||
// 查询数据
|
||||
const {data} = await axiosInstance.get(`/role/${pageNo}/${pageSize}`, {params: this.searchForm})
|
||||
|
||||
// 赋值数据
|
||||
this.dataList = data.list;
|
||||
|
||||
// 设置分页内容
|
||||
this.searchForm.pageNo = data.pageNo;
|
||||
this.searchForm.pageSize = data.pageSize;
|
||||
this.searchForm.pages = data.pages;
|
||||
},
|
||||
|
||||
/* 重制表单 */
|
||||
onRest() {
|
||||
this.searchForm.roleCode = undefined;
|
||||
this.searchForm.description = undefined;
|
||||
this.searchForm.remark = undefined;
|
||||
this.onSearch();
|
||||
},
|
||||
|
||||
/* 添加 */
|
||||
onAdd() {
|
||||
this.isAdd = true;
|
||||
this.roleInfo = {};
|
||||
},
|
||||
|
||||
/* 修改 */
|
||||
onEdit(roleInfo) {
|
||||
this.isAdd = false;
|
||||
this.roleInfo = roleInfo;
|
||||
},
|
||||
|
||||
/* 分配权限 */
|
||||
onAssignPermission(roleInfo) {
|
||||
this.roleInfo = roleInfo;
|
||||
},
|
||||
|
||||
/* 删除 */
|
||||
async onDeleted(roleInfo) {
|
||||
const result = confirm("确认删除?");
|
||||
if (!result) return false;
|
||||
|
||||
// 删除角色
|
||||
const {code, message} = await axiosInstance.delete(`/role`, {data: [roleInfo.id]});
|
||||
if (code === 200) {
|
||||
await this.onSearch();
|
||||
antd.message.success(message);
|
||||
} else {
|
||||
antd.message.error(message);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.onSearch();
|
||||
},
|
||||
});
|
||||
|
||||
app.component('HeaderNavs', HeaderNavs);
|
||||
app.component('Pagination', Pagination);
|
||||
app.component('DialogRole', DialogRole);
|
||||
app.component('AssignPermission', AssignPermission);
|
||||
app.mount('#app');
|
||||
</script>
|
||||
</html>
|
|
@ -0,0 +1,238 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" th:href="@{/webjars/bootstrap/5.1.3/css/bootstrap.min.css}">
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" th:href="@{/webjars/font-awesome/5.15.4/css/all.min.css}">
|
||||
<link rel="stylesheet" th:href="@{/src/lib/css/style.css}">
|
||||
<link rel="stylesheet" th:href="@{/src/lib/css/tablePage.css}">
|
||||
<!-- Vue3 -->
|
||||
<script th:src="@{/src/lib/js/vue/vue.global.js}"></script>
|
||||
<!-- Bootstrap JS Bundle with Popper -->
|
||||
<script th:src="@{/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js}"></script>
|
||||
<!-- 本地引入 popper JS -->
|
||||
<script th:src="@{/src/lib/js/boostrap/popper.min.js}"></script>
|
||||
<!-- 本地引入 axios JS -->
|
||||
<script th:src="@{/src/lib/js/axios/axios.min.js}"></script>
|
||||
<!-- 引入dayjs -->
|
||||
<script th:src="@{/src/lib/js/dayjs/dayjs.min.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/customParseFormat.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/weekday.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/localeData.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/weekOfYear.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/weekYear.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/advancedFormat.js}"></script>
|
||||
<script th:src="@{/src/lib/js/dayjs/quarterOfYear.js}"></script>
|
||||
<!-- 引入 antd JS -->
|
||||
<script th:src="@{/src/lib/js/dayjs/antd.min.js}"></script>
|
||||
|
||||
<title>用户管理页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid" id="app">
|
||||
<dialog-user :is-add="dialogFormFlag" :on-search="onSearch" :userinfo="userinfo"></dialog-user>
|
||||
|
||||
<!-- 头部导航 -->
|
||||
<header-navs></header-navs>
|
||||
|
||||
<!-- 分配角色抽屉 -->
|
||||
<assign-roles :userinfo="userinfo"></assign-roles>
|
||||
|
||||
<!-- 头部 -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-search me-2"></i>用户查询</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form @reset="onRest" @submit.prevent="onSearch">
|
||||
<div class="row g-3 align-items-center">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="username"><i class="fas fa-user me-1"></i>用户名</label>
|
||||
<input autocomplete="false" class="form-control" id="username" placeholder="请输入用户名"
|
||||
type="text" v-model="searchForm.username">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="email"><i class="fas fa-envelope me-1"></i>邮箱</label>
|
||||
<input autocomplete="false" class="form-control" id="email" placeholder="请输入邮箱"
|
||||
type="email" v-model="searchForm.email">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="search-btn-group">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<i class="fas fa-search me-1"></i>查询
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" type="reset">
|
||||
<i class="fas fa-redo me-1"></i>重置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span><i class="fas fa-users me-2"></i>用户列表</span>
|
||||
<button @click="onAdd" class="btn btn-sm btn-success" data-bs-target="#userBackdrop" data-bs-toggle="modal">
|
||||
<i class="fas fa-plus me-1"></i>新增用户
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th scope="col" width="5%">#</th>
|
||||
<th scope="col" width="20%">用户名</th>
|
||||
<th scope="col" width="20%">邮箱</th>
|
||||
<th scope="col" width="17%">创建时间</th>
|
||||
<th scope="col" width="17%">更新时间</th>
|
||||
<th scope="col" width="20%">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :key="user.id" v-for="(user,index) in dataList">
|
||||
<th scope="row">{{index + 1}}</th>
|
||||
<td>{{user.username}}</td>
|
||||
<td>{{user.email}}</td>
|
||||
<td>{{formatDate(user.createTime)}}</td>
|
||||
<td>{{formatDate(user.updateTime)}}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button @click="onEdit(user)" class="btn btn-outline-primary btn-action"
|
||||
data-bs-target="#userBackdrop" data-bs-toggle="modal">
|
||||
<i class="fas fa-edit"></i> 修改
|
||||
</button>
|
||||
<button @click="onAssignRoles(user)" class="btn btn-outline-primary btn-action"
|
||||
data-bs-target="#assignRoleOffCanvas" data-bs-toggle="offcanvas">
|
||||
<i class="fas fa-cloud"></i> 分配角色
|
||||
</button>
|
||||
<button @click="onDeleted(user)" class="btn btn-outline-danger btn-action">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 表格分页 -->
|
||||
<pagination :on-search="onSearch" v-model:pagination="searchForm"></pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<!-- 设置 popper 提示框 -->
|
||||
<script th:src="@{/src/config/popper-config.js}"></script>
|
||||
<!-- 加载全局axios配置 -->
|
||||
<script th:src="@{/src/config/axios-config.js}"></script>
|
||||
|
||||
<!-- 头部导航 -->
|
||||
<script th:src="@{/src/components/HeaderNavs.js}"></script>
|
||||
<!-- 分页 -->
|
||||
<script th:src="@{/src/components/Pagination.js}"></script>
|
||||
<!-- 用户表单 -->
|
||||
<script th:src="@{/src/views/user/DialogUser.js}"></script>
|
||||
<!-- 分配角色 -->
|
||||
<script th:src="@{/src/views/user/AssignRoles.js}"></script>
|
||||
<script>
|
||||
const {createApp, ref} = Vue;
|
||||
|
||||
const app = createApp({
|
||||
data() {
|
||||
return {
|
||||
// 查询表单
|
||||
searchForm: ref({
|
||||
username: undefined,
|
||||
email: undefined,
|
||||
pageNo: 1,
|
||||
pageSize: 30,
|
||||
pages: 0
|
||||
}),
|
||||
// 用户信息
|
||||
userinfo: ref({}),
|
||||
// 弹窗标题
|
||||
dialogFormFlag: ref(false),
|
||||
// 查询用户列表
|
||||
dataList: ref([])
|
||||
};
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
/* 格式化时间 */
|
||||
formatDate(date) {
|
||||
return dayjs(date).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
|
||||
/* 加载数据 */
|
||||
async onSearch() {
|
||||
const {pageNo, pageSize} = this.searchForm;
|
||||
// 查询数据
|
||||
const {data} = await axiosInstance.get(`/user/${pageNo}/${pageSize}`, {params: this.searchForm})
|
||||
|
||||
// 赋值数据
|
||||
this.dataList = data.list;
|
||||
|
||||
// 设置分页内容
|
||||
this.searchForm.pageNo = data.pageNo;
|
||||
this.searchForm.pageSize = data.pageSize;
|
||||
this.searchForm.pages = data.pages;
|
||||
},
|
||||
|
||||
/* 重制表单 */
|
||||
onRest() {
|
||||
this.searchForm.username = undefined;
|
||||
this.searchForm.email = undefined;
|
||||
this.onSearch();
|
||||
},
|
||||
|
||||
/* 添加 */
|
||||
onAdd() {
|
||||
this.dialogFormFlag = true;
|
||||
this.userinfo = {};
|
||||
},
|
||||
|
||||
/* 修改 */
|
||||
onEdit(user) {
|
||||
this.dialogFormFlag = false;
|
||||
this.userinfo = user;
|
||||
},
|
||||
|
||||
/* 为用户分配角色 */
|
||||
onAssignRoles(user) {
|
||||
this.userinfo = user;
|
||||
},
|
||||
|
||||
/* 删除 */
|
||||
async onDeleted(user) {
|
||||
const result = confirm("确认删除?");
|
||||
if (!result) return false;
|
||||
|
||||
// 删除用户
|
||||
const {code, message} = await axiosInstance.delete(`/user`, {data: [user.id]});
|
||||
if (code === 200) {
|
||||
this.onSearch();
|
||||
antd.message.success(message);
|
||||
} else {
|
||||
antd.message.error(message);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.onSearch();
|
||||
},
|
||||
});
|
||||
|
||||
app.component('HeaderNavs', HeaderNavs);
|
||||
app.component('Pagination', Pagination);
|
||||
app.component('DialogUser', DialogUser);
|
||||
app.component('AssignRoles', AssignRoles);
|
||||
app.mount('#app');
|
||||
</script>
|
||||
</html>
|
Loading…
Reference in New Issue