XX后台管理系统
Springboot + vue + dm8 的前后端分离项目,后端项目
https://spring.io
https://start.aliyun.com
1. 创建项目
初始化项目,导入坐标
web、lombok、devtools
<!-- web start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- web end -->
<!-- devtools start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- devtools end -->
<!-- lombok start -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- lombok end -->
2.整合Swagger3
2.1 导入swagger3坐标
<swagger3.version>3.0.0</swagger3.version>
<!-- swagger3 start -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger3.version}</version>
</dependency>
<!-- swagger3 end -->
2.2 编写swagger3配置类
package org.cn.common.config;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Configuration
@EnableOpenApi
@EnableWebMvc
public class Swagger3Config {
/**
* 创建API
* http:localhost:9999/swagger-ui/index.html 原生地址
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30).pathMapping("/")
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
/*.enable(enable)*/
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api,用这种方式更灵活
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
// 扫描指定包中的swagger注解
//.apis(RequestHandlerSelectors.basePackage("com.cn"))
// 扫描所有 .apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("(?!/ApiError.*).*"))
.paths(PathSelectors.any())
.build()
// 支持的通讯协议集合
.protocols(newHashSet("https", "http"))
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
/**
* 支持的通讯协议集合
*
* @param type1
* @param type2
* @return
*/
private Set<String> newHashSet(String type1, String type2) {
Set<String> set = new HashSet<>();
set.add(type1);
set.add(type2);
return set;
}
/**
* 认证的安全上下文
*/
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> securitySchemes = new ArrayList<>();
securitySchemes.add(new ApiKey("Authorization", "Authorization", "header"));
return securitySchemes;
}
/**
* 授权信息全局应用
*/
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.any()).build());
return securityContexts;
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
/**
* 添加摘要信息
* @return 返回ApiInfo对象
*/
private ApiInfo apiInfo() {
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("接口文档")
// 服务条款
.termsOfServiceUrl("NO terms of service")
// 描述
.description("权限模型管理系统-接口文档")
// 作者信息
.contact(new Contact("LM", "https://www.cnblogs.com/longronglang/", "lumin@gmail.com"))
// 版本
.version("版本号:V1.0")
//协议
.license("The Apache License")
// 协议url
.licenseUrl("https://www.apache.org/licenses/LICENSE-2.0.html")
.build();
}
}
2.3 swagger3注解测试
新增测试类、测试方法等,加上Swagger3注解进行测试
http:localhost:9999/swagger-ui/index.html
@Api(tags = "用户管理")
@ApiOperation("用户登录")
http://localhost:9999/swagger-ui/index.html
3. 代码自动生成
3.1 引入MP&DB坐标
mybatis-plus-boot-starter、mybatis-plus-generator、freemarker、dameng
注意:mybatis-plus 坐标引入的是mybatis-plus启动坐标和代码生成坐标
<dm.version>8.1.2.192</dm.version>
<mybatis-plus-boot.version>3.5.3.1</mybatis-plus-boot.version>
<mybatis-plus-generator.version>3.5.3.1</mybatis-plus-generator.version>
<!-- mybatis-plus-boot start -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot.version}</version>
</dependency>
<!-- mybatis-plus-boot end -->
<!-- mybatis-plus-generator start -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-generator.version}</version>
</dependency>
<!-- mybatis-plus-generator end -->
<!-- freemarker start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- freemarker end -->
<!-- dameng start -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>${dm.version}</version>
</dependency>
<!-- dameng end -->
3.2 代码生成工具类编写
3.2.1 代码生成工具类
package generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collections;
public class CodeGenerator {
static String url ="jdbc:dm://LOCALHOST:5236";
static String username ="SYSDBA";
static String password ="SYSDBA001";
static String moduleName ="system";
static String javaLocation ="F:\\install\\workspace\\sty\\x-dm-server\\src\\main\\java";
static String mapperLocation ="F:\\install\\workspace\\sty\\x-dm-server\\src\\main\\resources\\mapper\\";
static String tables = "x_user,x_role,x_menu,x_user_role,x_role_menu";
public static void main(String[] args) {
FastAutoGenerator.create(url,username,password)
.globalConfig(builder -> builder
.author("LuMin")
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir(javaLocation)
.commentDate("yyyy-MM-dd")
)
.packageConfig(builder -> builder
.parent("org.cn") // 设置父包名
.moduleName(moduleName) // 设置父包模块名
.entity("entity")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.pathInfo(Collections.singletonMap(OutputFile.xml, mapperLocation))
)
.strategyConfig(builder -> builder
.addInclude(tables) // 设置需要生成的表名
.addTablePrefix("x_") // 设置过滤表前缀
.entityBuilder()
.enableLombok()
)
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
public static class Test {
static Connection con = null;
static String cname = "dm.jdbc.driver.DmDriver";
static String url = "jdbc:dm://192.168.106.137:30236";
static String userid = "SYSDBA";
static String pwd = "SYSDBA001";
public static void main(String[] args) {
try {
Class.forName(cname);
con = DriverManager.getConnection(url, userid, pwd);
con.setAutoCommit(true);
System.out.println("[SUCCESS]conn database");
} catch (Exception e){
System.out.println("[FAIL]conn database:" + e.getMessage());
}
}
public void disConn(Connection con) throws SQLException {
if (con != null) {
con.close();
}
}
}
}
3.2.2 启动异常解决
# 注意生成代码之后,直接启动项目会报错,需要对项目信息进行修改
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'menuServiceImpl': Unsatisfied dependency expressed through field 'baseMapper'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.cn.system.mapper.MenuMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
# ①修改启动类扫描的包
@SpringBootApplication(scanBasePackages = {"org.cn"})
@MapperScan(value = {"org.cn.*.mapper"})
# ②在Mapper接口上增加@Mapper类注解,启动还是会报错
initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'menuServiceImpl': Unsatisfied dependency expressed through field 'baseMapper'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'menuMapper' defined in file
# ③删除创建项目默认生成的demos包,重新启动
3.3 application.yml配置
# server
server:
port: 9999
# datasource
spring:
datasource:
url: jdbc:dm://LOCALHOST:5236/XGIS
username: SYSDBA
password: SYSDBA001
driver-class-name: dm.jdbc.driver.DmDriver
jpa:
database-platform: org.hibernate.dialect.DMDialect
hibernate:
ddl-auto: update # 或者 create, create-drop, validate, none
show-sql: true # 显示执行的 SQL 语句,便于调试
properties:
hibernate:
format_sql: true # 格式化 SQL 语句输出
redis:
port: 6379
password: Admin@123
host: localhost
# mybatis-plus
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
global-config:
db-config:
logic-delete-field: deleted
logic-not-delete-value: 0
logic-delete-value: 1
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: org.cn
# logging
logging:
charset:
console: utf-8
level:
org.cn: debug
3.4 测试生成代码生成可用性
利用SpringBootJunit测试工具进行测试,在XDmServerApplicationTests类中新建testUserMapper()方法
package org.cn;
import org.cn.system.entity.User;
import org.cn.system.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class XDmServerApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
}
@Test
void testUserMapper () {
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
foreach
循环时没有打印出预期的内容(例如用户对象的某些属性),而是打印出了类似org.cn.system.entity.User@1ad1c363
这样的内容,这通常意味着你没有正确覆写User
类的toString
方法。
org.cn.system.entity.User@76d0ecd7
org.cn.system.entity.User@57c69937
org.cn.system.entity.User@1ad1c363
org.cn.system.entity.User@446b64b3
org.cn.system.entity.User@35ac9ebd
org.cn.system.entity.User@56c0a61e
一是在User实体类中增加 toString()方法
二是在实体类上添加@Data/@NoArgsConstructor/@AllArgsConstructor注解
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", status='" + status + '\'' +
", phone=" + phone +
", avatar='" + avatar + '\'' +
", deleted=" + deleted +
'}';
}
4. 统一结果返回封装
4.1 封装公共响应类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
/**
* 返回编码
*/
private Integer code;
/**
* 返回信息
*/
private String message;
/**
* 返回数据
*/
private T data;
/**
* 返回成功
* @return
* @param <T>
*/
public static <T> Result<T> success(){
return new Result<>(20000, "success",null);
}
public static <T> Result<T> success(T data){
return new Result<>(20000, "success",data);
}
public static <T> Result<T> success(T data,String message){
return new Result<>(20000, message,data);
}
public static <T> Result<T> success(String message){
return new Result<>(20000, message,null);
}
/**
* 返回失败
* @return
* @param <T>
*/
public static <T> Result<T> fail(){
return new Result<>(20001, "fail",null);
}
public static <T> Result<T> fail(Integer code){
return new Result<>(20001, "fail",null);
}
public static <T> Result<T> fail(Integer code,String message){
return new Result<>(20001, message,null);
}
public static <T> Result<T> fail(String message){
return new Result<>(20001, message,null);
}
}
4.2 测试公共响应类
修改控制器,测试公共响应类是否返回自定义数据
4.2.1 修改注解
修改UserController控制器的@Controller为@RestController
4.2.2 新增方法测试
在控制类中注入IUserService接口服务类,新增getUserList()测试方法进行测试
@Resource
private IUserService userService;
@GetMapping("/getUserList")
public Result<List<User>> getUserList() {
List<User> userList = userService.list();
return Result.success(userList, "查询成功");
}
5.实体类Lombok改造
5.1 删除注解
@Getter/@Setter是mybatis-plus-generator生成实体时自动添加的注解
@Getter
@Setter
5.2 新增注解
@Data/@NoArgsConstructor/@AllArgsConstructor是lombok中的注解
@Data
@NoArgsConstructor
@AllArgsConstructor
6.JWT登录实现
6.1 JWT 整合
6.1.1 导入Jwt坐标
spring-security-core、jjwt
<fastjson.version>2.0.1</fastjson.version>
<!-- spring-security-core start -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<!-- spring-security-core end -->
<!-- jjwt start -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- jjwt end -->
6.1.2 编写Jwt工具类
①在application.yml配置JWT令牌和有效期
# JWT 配置
jwt:
secret-key: 123456
token-validity-in-seconds: 86400000
②JwtUtil工具类
6.1.3 Jwt验证拦截器
6.1.3.1 定义Jwt拦截器
@Component
@Slf4j
public class JwtValidateInterceptor implements HandlerInterceptor {
@Resource
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// String token = request.getHeader("Authorization");
/*request.getHeader()方法与集成Swagger中ApiKey并无多大关系
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> securitySchemes = new ArrayList<>();
securitySchemes.add(new ApiKey("Authorization", "Authorization", "header"));
return securitySchemes;
}*/
String token = request.getHeader("X-Token");
log.debug(request.getRequestURI()+"需要验证"+token);
if (token != null) {
try {
jwtUtil.parseToken(token);
log.debug(request.getRequestURI()+"验证通过");
return true;
} catch (Exception e) {
throw new Exception(e);
}
}
log.debug(request.getRequestURI()+"验证失败,禁止访问");
response.setContentType("application/json;charset=utf-8");
Result<Object> fail = Result.fail(20003, "jwt验证失败,请重新登录");
response.getWriter().write(JSON.toJSONString(fail));
return false; //拦截
}
}
6.1.3.2 注册Jwt拦截器
@Configuration
public class JwtInterceptorConfig implements WebMvcConfigurer {
@Resource
private JwtValidateInterceptor jwtValidateInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration registration = registry.addInterceptor(jwtValidateInterceptor);
registration.addPathPatterns("/**")
.excludePathPatterns(
"/system/user/login",
"/system/user/info",
"/system/user/logout",
"/swagger-ui/**",
"/swagger-resources/**",
"/v3/**",
"/error",
"/webjars/**",
"/v2/**",
"/public/**",
"/static/**",
"*.html",
"doc.html",
"/favicon.ico"
);
}
}
6.1.3.3 Jwt拦截器测试
①在 XAdminServerApplicationTests 测试类中注入
@Autowired
private JwtUtil jwtUtil;
在 XAdminServerApplicationTests 测试
②测试Jwt创建
/**
* Jwt创建
*/
@Test
void testCreateJwt(){
User user = new User();
user.setUsername("zhangsan");
user.setPhone(18722820382L);
String token = jwtUtil.createToken(user);
System.out.println(token);
}
③测试Jwt解析
/**
* Jwt解析
*/
@Test
void testParseJwt(){
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJkZWRiNTRiMC1kOTllLTRhZGEtOGFjMi1iZTA2ODRlNzVhZmYiLCJzdWIiOiJ7XCJ0ZWxlcGhvbmVcIjoxODcyMjgyMDM4MixcInVzZXJuYW1lXCI6XCJ6aGFuZ3NhblwifSIsImlzcyI6InN5c3RlbSIsImlhdCI6MTcwNjExMzU3MCwiZXhwIjoxNzA2MTE1MzcwfQ.Pcj8kjcAC8FW-PER65wiYYJ1bSptOG8mViIHyeIY3EI";
Claims claims = jwtUtil.parseToken(token);
System.out.println("claims = " + claims);
}
6.2 登录逻辑
认证过程可以参考此篇博客,写得非常详细
https://blog.****.net/liuerchong/article/details/108606650
6.2.1 新增用户登录接口
在IUserService服务类中新增用户登录业务逻辑接口
/**
* 用户登录业务逻辑接口
* @param user
* @return
*/
Map<String, Object> login(User user);
6.2.2 实现用户登录接口
第一步根据用户名和密码查询用户信息;
第二步判断查询结果是否为空,为空抛出异常信息,不为空则将封装的登录信息传入JwtUtil中生成token
6.2.2.1 用户信息查询
根据前端传入User对象,LambdaQueryWrapper
// 1. 根据用户名查询用户信息
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,user.getUsername());
User loginUser = this.baseMapper.selectOne(wrapper);
6.2.2.2 用户token生成
第一步:判断用户信息是否为空
判断用户信息不为空,使用spring-security-core中PasswordEncoder进行加密并使用matches方法匹配后端查询出来的密码是否一致,这里需要注意一下,安全考虑,密码在传输过程中需要进行置空,不要放到JWT生成token中,PasswordEncoder需要配置成一个公用组件,不然会报错。PasswordEncoder组件配置如下:
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
第二步:不为空,则创建token
①定义一个字符串token,用于接收jwtUtil中生成的token值(实质是JwtBuilder中将AES加密令牌密钥和过期时间压缩的字符串);
②将封装的loginUser登录信息传入JwtUtil工具类中createToken方法中生成token;
③定义一个Map集合,将jwtUtil生成的token存储在Map集合中,返回出去
// 2. 结果不为空,则生成 token
if (loginUser != null && passwordEncoder.matches(user.getPassword(),loginUser.getPassword())) {
String key = "user" + UUID.randomUUID();
loginUser.setPassword(null);
String token = jwtUtil.createToken(loginUser);
Map<String,Object> data = new HashMap<>();
data.put("token",token);
return data;
}
第三步:JwtUtil工具生成token步骤
①在application.yml配置JWT令牌和有效期
# JWT 配置
jwt:
secret-key: 123456
token-validity-in-seconds: 86400
②创建JWT工具类,通对令牌密钥进行Base64编码,再将编码后的令牌密码进行AES加密,并设置令牌密码过期时间,过期时间等于当前时间加上application.yml中设置的过期时间,最后将加密后的令牌密钥和过期时间压缩成紧凑字符串返回出去。
/*
* @Description: JwtUtils工具类
**/
@Component
public class JwtUtil {
// 有效期
@Value("${jwt.token-validity-in-seconds}")
private Long JWT_EXPIRE;
// 令牌密钥
@Value("${jwt.secret-key}")
private String JWT_KEY;
public String createToken(Object data){
// 当前时间
Long currentTime = System.currentTimeMillis();
// 过期时间
Long expTime = currentTime+JWT_EXPIRE;
//jwt
JwtBuilder builder = Jwts.builder()
.setId(UUID.randomUUID()+"")
.setSubject(JSON.toJSONString(data))
.setIssuer("acme") // 设置JWT的发布者信息
.setIssuedAt(new Date(currentTime))
// 签名方法
.signWith(SignatureAlgorithm.HS256,encodeSecret(JWT_KEY))
.setExpiration(new Date(expTime));
return builder.compact();
}
private SecretKey encodeSecret(String key) {
byte[] encode = Base64.getEncoder().encode(key.getBytes());
SecretKeySpec aes = new SecretKeySpec(encode,0,encode.length,"AES");
return aes;
}
public Claims parseToken (String token) {
Claims body = Jwts.parser()
.setSigningKey(encodeSecret(JWT_KEY))
.parseClaimsJws(token)
.getBody();
return body;
}
public <T> T parseToken (String token,Class<T> clazz) {
Claims body = Jwts.parser()
.setSigningKey(encodeSecret(JWT_KEY))
.parseClaimsJws(token)
.getBody();
return JSON.parseObject(body.getSubject(),clazz);
}
}
6.2.3 测试用户登录接口
7. 获取当前用户信息
7.1 新增获取用户信息接口
在IUserService新增获取当前用户信息业务逻辑接口
/**
* 获取当前用户信息业务逻辑接口
* @param token
* @return
*/
Map<String, Object> getCurrentUserInfo(String token);
7.2 实现获取用户信息接口
在UserServiceImpl业务逻辑接口实现类中实现获取当前用户信息接口
@Override
public Map<String, Object> getCurrentUserInfo(String token) {
// 1. 根据 token 获取用户信息
User loginUser = null;
try {
loginUser = jwtUtil.parseToken(token,User.class);
} catch (Exception e) {
e.printStackTrace();
}
if (loginUser != null) {
Map<String, Object> data = new HashMap<>();
// 用户信息
data.put("username", loginUser.getUsername());
data.put("avatar", loginUser.getAvatar());
// 角色
// 权限列表
// 返回用户信息数据
return data;
}
return null;
}
7.3 调用获取用户信息接口
在UserController控制器新增获取用户信息方法,调用获取用户信息业务逻辑实现接口实现方法
/**
* 获取当前用户信息
* @param token
* @return
*/
@ApiOperation("当前用户信息")
@GetMapping("/info")
public Result<Map<String,Object>> getCurrentUserInfo(@RequestParam("token") String token){
// 根据 token 获取用户信息
Map<String,Object> data = userService.getCurrentUserInfo(token);
if (data != null) {
return Result.success(data);
}
return Result.fail(20003,"用户登录无效,请重新登录");
}
7.4 测试获取用户信息接口
8. 注销登录
8.1 新增注销登录接口
在IUserService新增用户登录业务逻辑接口
/**
* 注销登录
* @param token
* @return
*/
@PostMapping("logout")
public Result<?> logout(@RequestHeader("token") String token){
userService.logout(token);
return Result.success();
}
8.2 实现注销登录接口
在UserServiceImpl业务逻辑接口实现类中实现注销登录接口
@Override
public void logout(String token) {
}
8.3 调用注销登录接口
在UserController控制器新增注销登录方法,调用注销登录业务逻辑实现接口实现方法
/**
* 注销登录
* @param token
* @return
*/
@PostMapping("logout")
public Result<?> logout(@RequestHeader("token") String token){
userService.logout(token);
return Result.success();
}
9.前后端对接
解决前后端跨越问题
Access to XMLHttpRequest at 'http://localhost:9999/system/user/login' from origin
'http://localhost:9528' has been blocked by CORS policy: Response to preflight request
doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
9.1 后端跨域拦截器配置
9.1.1 在application.yml配置
# CorsUrl
corsUrl:
url: http://localhost:9528
9.1.2 跨域后端拦截器配置
package org.cn.common.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/*
* @Description: 跨域后端拦截器配置类
**/
@Configuration
public class CorsConfig {
// 允许前端服务访问后端接口
@Value("${corsUrl.url}")
private String CorsUrl;
@Bean
public CorsFilter corsFilter() {
CorsConfiguration configuration = new CorsConfiguration();
// 允许什么网址来异步访问
configuration.addAllowedOrigin(CorsUrl);
// 获取cookie
configuration.setAllowCredentials(true);
// 允许什么方法? POST、GET,此处为* 意味全部允许
configuration.addAllowedMethod("*");
// 允许所有的请求头
configuration.addAllowedHeader("*");
// 设置资源过滤器,过滤什么资源
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",configuration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
9.1.3 Jwt拦截器放行配置
package org.cn.common.config;
import org.cn.common.interceptor.JwtValidateInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class JwtInterceptorConfig implements WebMvcConfigurer {
@Resource
private JwtValidateInterceptor jwtValidateInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration registration = registry.addInterceptor(jwtValidateInterceptor);
registration.addPathPatterns("/**")
.excludePathPatterns(
"/system/user/login",
"/system/user/info",
"/system/user/logout",
"/swagger-ui/**",
"/swagger-resources/**",
"/v3/**",
"/error",
"/webjars/**",
"/v2/**",
"/public/**",
"/static/**",
"*.html",
"doc.html",
"/favicon.ico"
);
}
}
9.2 前后端联通测试
前端登录,查看页面控制台是否还报跨越的问题
10.系统管理
10.1 分页插件
实现分页查询,需要Mybatis Plus中分页插件配置成分页拦截器组件
package org.cn.common.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*
* @Description: MybatisPlusConfig分页拦截器
**/
@Configuration
public class MybatisPlusPageConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.DM));//如果配置多个插件,切记分页最后添加
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
return interceptor;
}
}
10.2 用户管理
10.2.1 用户列表
10.2.1.1 处理前端请求
①在UserController中接收前端发送 /system/user/list 请求
/**
* 分页查询
* @param username
* @param phone
* @param pageNo
* @param pageSize
* @return
*/
@ApiOperation("分页查询")
@GetMapping("/list")
public Result<Map<String,Object>> getUserList(
@RequestParam(value = "username",required = false) String username,
@RequestParam(value = "phone",required = false) String phone,
@RequestParam(value = "pageNo") Long pageNo,
@RequestParam(value = "pageSize") Long pageSize){
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.hasLength(username),User::getUsername,username);
// 疑惑:为什么请求参数中phone改成Long就查不出数据,请求参数中的类型是String才能查出数据
wrapper.eq(StringUtils.hasLength(phone),User::getPhone,phone);
wrapper.orderByDesc(User::getId);
Page<User> page = new Page<>(pageNo, pageSize);
userService.page(page,wrapper);
Map<String,Object> data = new HashMap<>();
data.put("total",page.getTotal());
data.put("rows",page.getRecords());
return Result.success(data);
}
10.2.1.2 测试接口服务
②使用Postman/Apifox/Swagger等接口测试工具,获取token,传入参数,进行获取用户列表接口服务测试
注意:特别注意,测试工具的使用中,参数传入异常,折磨了两个小时,pageNo、pageSize参数未传入
Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'pageNo' for method parameter type Long is not present]
pageNo、pageSize参数正确传入
③前端获取数据进行展示
10.2.2 新增用户
10.2.2.1 处理前端请求
①在UserController中注入PasswordEncoder并调用后端服务接口
@Resource
private PasswordEncoder passwordEncoder;
// 新增用户
@ApiOperation("添加用户")
@PostMapping
public Result<?> addUser(@RequestBody User user){
user.setPassword(passwordEncoder.encode(user.getPassword()));
userService.addUser(user);
return Result.success("添加成功");
}
10.2.1.2 新增服务接口
②在IUserService中新增用户接口
/**
* 新增用户
* @param user
*/
void addUser(User user);
10.2.1.3 实现接口服务
③在UserServiceImpl实现新增用户接口
/**
* 新增用户
* @param user
*/
@Override
@Transactional
public void addUser(User user) {
// 写入用户表
this.baseMapper.insert(user);
// 写入用户角色表
}
10.2.1.4 测试接口服务
④使用Postman/Apifox/Swagger等接口测试工具,进行添加用户接口服务测试,出现了一个异常
SQL: INSERT INTO XGIS.X_USER ( id, username, password, email, status, phone ) VALUES ( ?, ?, ?, ?, ?, ? )
### Cause: dm.jdbc.driver.DMException: 仅当指定列列表,且SET IDENTITY_INSERT为ON时,才能对自增列赋值
SET IDENTITY_INSERT XGIS.X_USER ON; // 开启自增
SET IDENTITY_INSERT XGIS.X_USER OFF; // 关闭自增
ALTER TABLE XGIS.X_USER DROP IDENTITY; // 删除自增
10.2.3 修改用户
10.2.3.1 处理前端请求
在UserController控制器获取用户ID的方法
/**
* 根据前端传入用户id查询到单条用户信息,用于回显修改弹出框
* @param id
* @return
*/
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable("id") Integer id){
User user = userService.getById(id);
return Result.success(user);
}
在UserController控制器添加修改用户方法
/**
* 修改用户
* @param user
* @return
*/
@PutMapping
public Result<?> updateUser(@RequestBody User user){
user.setPassword(null);
userService.updateById(user);
return Result.success("修改成功");
}
10.2.3.2 测试接口服务
使用Postman/Apifox/Swagger等接口测试工具,进行修改用户接口服务测试。
10.2.4 删除用户
10.2.4.1 处理前端请求
在UserController控制器添加删除用户方法
/**
* 根据用户id删除用户
* @param id
* @return
*/
@DeleteMapping("/{id}")
public Result<User> deleteUserById(@PathVariable("id") Integer id){
userService.removeById(id);
return Result.success("删除成功");
}
在application.yml中配置逻辑删除
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
global-config:
db-config:
logic-delete-field: deleted
logic-not-delete-value: 0
logic-delete-value: 1
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: org.cn
10.2.4.2 测试接口服务
使用Postman/Apifox/Swagger等接口测试工具,进行删除用户接口服务测试。
10.3 角色管理
10.2.1 角色列表
10.2.1.1 处理前端请求
单表增删改查
package org.cn.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.cn.common.utils.Result;
import org.cn.system.entity.Role;
import org.cn.system.service.IRoleService;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 角色表 前端控制器
* </p>
*
* @author LuMin
* @since 2024-11-26
*/
@Api(tags = "角色管理")
@RestController
@RequestMapping("/system/role")
public class RoleController {
@Resource
private IRoleService roleService;
/**
* 角色列表
* @param roleName
* @param roleDesc
* @param pageNo
* @param pageSize
* @return
*/
@ApiOperation("角色列表")
@GetMapping("/list")
public Result<Map<String,Object>> getRoleList(
@RequestParam(value = "roleName",required = false) String roleName,
@RequestParam(value = "roleDesc",required = false) String roleDesc,
@RequestParam(value = "pageNo") Long pageNo,
@RequestParam(value = "pageSize") Long pageSize){
LambdaQueryWrapper<Role> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(StringUtils.hasLength(roleName),Role::getRoleName,roleName);
wrapper.eq(StringUtils.hasLength(roleDesc),Role::getRoleDesc,roleDesc);
wrapper.orderByDesc(Role::getID);
Page<Role> page = new Page<>(pageNo, pageSize);
roleService.page(page,wrapper);
Map<String,Object> data = new HashMap<>();
data.put("total",page.getTotal());
data.put("rows",page.getRecords());
return Result.success(data);
}
/**
* 添加角色
* @param role
* @return
*/
@ApiOperation("添加角色")
@PostMapping("/add")
public Result<?> addRole(@RequestBody Role role){
roleService.save(role);
return Result.success("添加成功");
}
/**
* 根据页面点解获取角色id查询到单条角色信息,用于回显修改弹出框
* @param id
* @return
*/
@ApiOperation("单个角色")
@GetMapping("/{id}")
public Result<Role> getRoleById(@PathVariable("id") Integer id){
Role role = roleService.getById(id);
return Result.success(role);
}
/**
* 修改角色
* @param role
* @return
*/
@ApiOperation("修改角色")
@PutMapping("/update")
public Result<?> updateRole(@RequestBody Role role){
roleService.updateById(role);
return Result.success("修改成功");
}
/**
* 根据角色id删除角色信息
* @param id
* @return
*/
@ApiOperation("删除角色")
@DeleteMapping("/{id}")
public Result<Role> deleteRoleById(@PathVariable("id") Integer id){
roleService.removeById(id);
return Result.success("删除成功");
}
}
10.2.1.2 测试接口服务
Postman工具测试
10.4 角色菜单
10.4.1 菜单列表
10.4.1.1 处理前端请求
①在Menu实体中加上构建菜单属性
@ApiModelProperty("子菜单")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist = false)
private List<Menu> children;
@TableField(exist = false)
@ApiModelProperty("构建meta对象")
private Map<String,Object> meta;
public Map<String, Object> getMeta() {
meta = new HashMap<>();
meta.put("title",title);
meta.put("icon",icon);
return meta;
}
②在MenuController控制器种添加拦截请求
注意:将@Controller 改为@RestController,否则会报异常
异常:javax.servlet.ServletException: Could not resolve view with name 'system/menu/list' in servlet with name 'dispatcherServlet
@Resource
private IMenuService menuService;
@ApiOperation("菜单列表")
@GetMapping("/list")
public Result<List<Menu>> getMenuList () {
List<Menu> menuList = menuService.getMenuList();
return Result.success(menuList);
}
③在IMenuService服务类种构建getMenuList()接口
/**
* 菜单列表
* @return
*/
List<Menu> getMenuList();
④在MenuServiceImpl实现类中实现getMenuList()接口
@Override
public List<Menu> getMenuList() {
// 一级菜单
LambdaQueryWrapper<Menu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Menu::getParentId,0);
List<Menu> menuList = this.list(wrapper);
// 填充子菜单
setMenuChildren(menuList);
return menuList;
}
private void setMenuChildren(List<Menu> menuList) {
if (menuList != null) {
for (Menu menu : menuList) {
LambdaQueryWrapper<Menu> subWrapper = new LambdaQueryWrapper<>();
subWrapper.eq(Menu::getParentId,menu.getId());
List<Menu> subMenuList = this.list(subWrapper);
menu.setChildren(subMenuList);
// 递归
setMenuChildren(subMenuList);
}
}
}
10.4.1.2 测试接口服务
⑤在Postman中测试getMenuList()接口
10.4.2 单条角色菜单
10.4.2.1 处理前端请求
①在Role实体中加入菜单属性
@ApiModelProperty("菜单列表")
@TableField(exist = false)
private List<Integer> menuIdList;
②在RoleController控制器中改造获取单个角色的方法
/**
* 根据角色id查询某个角色所拥有的菜单权限
* @param id
* @return
*/
@ApiOperation("单条角色菜单")
@GetMapping("/{id}")
public Result<Role> getRoleById(@PathVariable("id") Integer id){
Role role = roleService.getRoleById(id);
return Result.success(role);
}
③在IRoleService服务类中构建getRoleById()接口
/**
* 根据角色id查询某个角色所拥有的菜单权限
* @param id
* @return
*/
Role getRoleById(Integer id);
④在RoleServiceImpl实现类中实现getRoleById()接口
@Override
public Role getRoleById(Integer id) {
Role role = this.baseMapper.selectById(id);
List<Integer> menuIdList = roleMenuMapper.getMenuIdListByRoleId(id);
role.setMenuIdList(menuIdList);
return role;
}
⑤在RoleMenuMapper中构建查询数据库的getMenuIdListByRoleId()接口
/**
* 根据角色菜单关联表,关联查询出某个角色拥有多少菜单权限
* @param id
* @return
*/
List<Integer> getMenuIdListByRoleId(Integer id);
⑥在RoleMenuMapper.xml构建自定义查询菜单SQL
<select id="getMenuIdListByRoleId" parameterType="Integer" resultType="Integer">
SELECT XRM.MENU_ID
FROM XGIS.X_ROLE_MENU AS XRM,
XGIS.X_MENU AS XM
WHERE XRM.MENU_ID = XM.ID
AND XM.IS_LEAF = 'Y'
AND XRM.ROLE_ID = #{ID}
</select>
10.4.2.2 测试接口服务
⑦在Postman中测试getMenuList()接口
10.4.2 添加角色菜单
10.4.2.1 处理前端请求
①在RoleController控制器中添加addRole()方法
/**
* 添加角色菜单
* @param role
* @return
*/
@ApiOperation("添加角色菜单")
@PostMapping("/add")
public Result<?> addRole(@RequestBody Role role){
roleService.addRole(role);
return Result.success("添加成功");
}
②在IRoleService服务类中添加addRole()服务接口
/**
* 添加角色菜单
* @param role
*/
void addRole(Role role);
③在IRoleServiceImpl中实现addRole()服务接口
注意:使用roleMenuMapper.insert()时,需要在RoleMenu实体中添加构造方法,否则会报错
@Override
@Transactional
public void addRole(Role role) {
// 写入角色表
this.baseMapper.insert(role);
// 写入菜单菜单关联表
if (null != role.getMenuIdList()) {
for (Integer menuId : role.getMenuIdList()){
roleMenuMapper.insert(new RoleMenu(null,role.getId(),menuId));
}
}
}
④在ReloMenu实体中添加构造方法
public RoleMenu(Object o, Integer roleId, Integer menuId) {
this.roleId = roleId;
this.menuId = menuId;
}
10.4.2.2 测试接口服务
⑦在Postman中测试addRole()接口
10.4.3 修改角色菜单
10.4.3.1 处理前端请求
①在RoleController控制器中添加updateRole()方法
/**
* 修改角色菜单:逻辑(根据角色Id把原先角色信息删除掉,再重新新增角色信息即可)
* @param role
* @return
*/
@ApiOperation("修改角色菜单")
@PutMapping("/update")
public Result<?> updateRole(@RequestBody Role role){
roleService.updateRole(role);
return Result.success("修改成功");
}
②在IRoleService服务类中添加 updateRole()服务接口
/**
* 修改角色菜单
* @param role
*/
void updateRole(Role role);
③在IRoleServiceImpl中实现addRole()服务接口
注意:使用roleMenuMapper.insert()时,需要在RoleMenu实体中添加构造方法,否则会报错
@Override
@Transactional
public void updateRole(Role role) {
// 修改角色表
this.baseMapper.updateById(role);
// 删除原有权限
LambdaQueryWrapper<RoleMenu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(RoleMenu::getRoleId,role.getId());
roleMenuMapper.delete(wrapper);
// 新增权限
if (null != role.getMenuIdList()) {
for (Integer menuId : role.getMenuIdList()){
roleMenuMapper.insert(new RoleMenu(null,role.getId(),menuId));
}
}
}
④在ReloMenu实体中添加构造方法
public RoleMenu(Object o, Integer roleId, Integer menuId) {
this.roleId = roleId;
this.menuId = menuId;
}
10.4.3.2 测试接口服务
⑦在Postman中测试updateRole()接口
10.4.4 删除角色菜单
10.4.4.1 处理前端请求
①在RoleController控制器中添加deleteRoleById()方法
/**
* 删除角色菜单
* @param roleId
* @return
*/
@DeleteMapping("/{roleId}")
public Result<Role> deleteRoleById(@PathVariable("roleId") Integer roleId){
roleService.deleteRoleById(roleId);
return Result.success("删除成功");
}
②在IRoleService服务类中添加 deleteRoleById()服务接口
/**
* 删除角色菜单
* @param roleId
*/
void deleteRoleById(Integer roleId);
③在IRoleServiceImpl中实现deleteRoleById()服务接口
@Override
@Transactional
public void deleteRoleById(Integer id) {
// 删除角色
this.baseMapper.deleteById(id);
// 删除权限
LambdaQueryWrapper<RoleMenu> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(RoleMenu::getRoleId,id);
roleMenuMapper.delete(wrapper);
}
10.4.4.2 测试接口服务
⑦在Postman中测试deleteRoleById()接口
10.5 用户角色
10.5.1 单条角色菜单
10.5.2.1 处理前端请求
①在User实体中加入角色属性
/**
* 角色列表
*/
@TableField(exist = false)
private List<Integer> roleIdList;
②在UserController控制器中改造获取单个用户的方法
/**
* 根据前端传入用户id查询到单条用户和角色信息,用于回显修改弹出框
* @param id
* @return
*/
@ApiOperation("单条用户角色")
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable("id") Integer id){
User user = userService.getUserById(id);
return Result.success(user);
}
③在IUserService服务类中构建getRoleById()接口
/**
* 获取单条用户角色
* @param id
* @return
*/
User getUserById(Integer id);
④在UserServiceImpl实现类中实现getUserById()接口
/**
* 单条用户角色
* @param id
* @return
*/
@Override
public User getUserById(Integer id) {
User user = this.baseMapper.selectById(id);
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserRole::getUserId,id);
List<UserRole> userRoleList = userRoleMapper.selectList(wrapper);
List<Integer> roleIdList = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList());
user.setRoleIdList(roleIdList);
return user;
}
10.5.2.2 测试接口服务
⑤在Postman中测试getMenuList()接口
10.5.2 添加用户角色
10.5.2.1 处理前端请求
①在UserController控制器中添加addUser()方法
/**
* 添加用户角色
* @param user
* @return
*/
@ApiOperation("添加用户角色")
@PostMapping("/add")
public Result<?> addUser(@RequestBody User user){
user.setPassword(passwordEncoder.encode(user.getPassword()));
userService.addUser(user);
return Result.success("添加成功");
}
②在IUserService服务类中添加addUser()服务接口
/**
* 添加用户角色
* @param user
*/
void addUser(User user);
③在IUsererviceImpl中实现addUser()服务接口
注意:使用userRoleMapper.insert()时,需要在UserRole实体中添加构造方法,否则会报错
/**
* 添加用户角色
* @param user
*/
@Override
@Transactional
public void addUser(User user) {
// 写入用户表
this.baseMapper.insert(user);
// 写入用户角色表
List<Integer> roleIdList = user.getRoleIdList();
if (roleIdList != null) {
for (Integer roleId : roleIdList){
userRoleMapper.insert(new UserRole(null,user.getId(),roleId));
}
}
}
④在UserRole实体中添加构造方法
public UserRole(Object o, Integer id, Integer roleId) {
this.userId = id;
this.roleId = roleId;
}
10.4.5.2 测试接口服务
⑦在Postman中测试addUser()接口
10.5.3 修改用户角色
10.5.3.1 处理前端请求
①在UserController控制器中添加updateUser()方法
/**
* 修改用户角色
* @param user
* @return
*/
@ApiOperation("修改用户角色")
@PutMapping("/update")
public Result<?> updateUser(@RequestBody User user){
user.setPassword(null);
userService.updateUser(user);
return Result.success("修改成功");
}
②在IUserService服务类中添加updateUser()服务接口
/**
* 修改用户角色
* @param user
*/
void updateUser(User user);
③在IUsererviceImpl中实现updateUser()服务接口
注意:使用userRoleMapper.insert()时,需要在UserRole实体中添加构造方法,否则会报错
/**
* 修改用户角色
* @param user
*/
@Override
@Transactional
public void updateUser(User user) {
// 更新用户表
this.baseMapper.updateById(user);
// 删除原有角色
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserRole::getUserId,user.getId());
userRoleMapper.delete(wrapper);
// 设置新的角色
List<Integer> roleIdList = user.getRoleIdList();
if (roleIdList != null) {
for (Integer roleId : roleIdList){
userRoleMapper.insert(new UserRole(null,user.getId(),roleId));
}
}
}
④在UserRole实体中添加构造方法
public UserRole(Object o, Integer id, Integer roleId) {
this.userId = id;
this.roleId = roleId;
}
10.4.3.2 测试接口服务
⑦在Postman中测试updateUser()接口
10.5.4 删除用户角色
10.5.4.1 处理前端请求
①在UserController控制器中添加deleteUserById()方法
/**
* 删除用户角色
* @param id
* @return
*/
@ApiOperation("删除用户角色")
@DeleteMapping("/{id}")
public Result<User> deleteUserById(@PathVariable("id") Integer id){
userService.deleteUserById(id);
return Result.success("删除成功");
}
②在IUserService服务类中添加deleteUserById()服务接口
/**
* 删除用户角色
* @param id
*/
void deleteUserById(Integer id);
③在IUsererviceImpl中实现deleteUserById()服务接口
/**
* 删除用户角色
* @param id
*/
@Override
public void deleteUserById(Integer id) {
// 删除用户
this.baseMapper.deleteById(id);
// 删除角色
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserRole::getUserId,id);
userRoleMapper.delete(wrapper);
}
10.4.3.2 测试接口服务
⑦在Postman中测试deleteById()接口
11 动态路由
11.1 菜单列表
11.1.1 自定义查询SQL
在 MenuMapper.xml 中菜单列表关联查询SQL
<select id="getMenuListByUserId" resultType="org.cn.system.entity.Menu">
SELECT DISTINCT XM.*
FROM XGIS.X_USER_ROLE AS XUR,
XGIS.X_ROLE_MENU AS XRM,
XGIS.X_MENU AS XM,
WHERE XM.ID = XRM.MENU_ID
AND XRM.ROLE_ID = XUR.ROLE_ID
AND XUR.USER_ID = #{userId}
AND XM.PARENT_ID = #{parentId}
</select>
11.2 构建菜单列表Mapper接口
在 MenuMapper 中构建获取菜单列表接口:getMenuListByUserId()
/**
* 根据前端传入的userId查询到所属角色信息,根据用户角色关联表获取角色ID,
* 再根据角色ID查询所属菜单,并且通过传入参数中parentId是否存在判断是否有子菜单,用于动态路由
* @param userId
* @param parentId
* @return
*/
public List<Menu> getMenuListByUserId(@Param("userId") Integer userId, @Param("parentId") Integer parentId);
11.3 构建菜单列表Service接口
在 IMenuService 中构建获取菜单列表服务接口:getMenuListByUserId()
/**
* 根据前端传入的userId查询到所属角色信息,根据用户角色关联表获取角色ID,
* 再根据角色ID查询所属菜单,并且通过传入参数中parentId是否存在判断是否有子菜单,用于动态路由
* @param userId
* @return
*/
List<Menu> getMenuListByUserId(Integer userId);
11.4 实现菜单列表Service接口
在 MenuServiceImpl 中实现获取菜单列表服务接口:getMenuListByUserId()
/**
* 根据前端传入的userId查询到所属角色信息,根据用户角色关联表获取角色ID,
* 再根据角色ID查询所属菜单,并且通过传入参数中parentId是否存在判断是否有子菜单,用于动态路由
* @param userId
* @return
*/
@Override
public List<Menu> getMenuListByUserId(Integer userId) {
// 一级菜单
List<Menu> menuList = this.baseMapper.getMenuListByUserId(userId, 0);
// 子菜单
setMenuListByUserId(userId, menuList);
return menuList;
}
private void setMenuListByUserId(Integer userId, List<Menu> menuList) {
if (menuList != null){
for (Menu menu : menuList) {
List<Menu> subMenuList = this.baseMapper.getMenuListByUserId(userId,menu.getId());
menu.setChildren(subMenuList);
// 递归
setMenuListByUserId(userId,subMenuList);
}
}
}
11.2 角色列表
11.2.1 自定义查询SQL
在 UserMapper.xml 中菜单列表关联查询SQL
<select id="getRoleNameByUserId" resultType="java.lang.String">
SELECT XR.ROLE_DESC
FROM XGIS.X_USER_ROLE AS XUR,
XGIS.X_ROLE AS XR
WHERE XUR.ROLE_ID = XR.ID
AND XUR.USER_ID = #{userId}
</select>
11.2.2 构建角色名称Mapper接口
在 UserMapper 中构建获取菜单列表接口:getRoleNameByUserId()
/**
* 根据用户ID获取角色名称
* @param id
* @return
*/
List<String> getRoleNameByUserId(Integer id);
11.3 获取所有角色信息列表
/**
* 角色列表
* @return
*/
@ApiOperation("角色列表")
@GetMapping("/all")
public Result<