经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
springboot+springsecurity+jwt+elementui图书管理系统Java大师-
来源:cnblogs  作者:Java大师-  时间:2023/5/29 8:49:54  对本文有异议

??图书管理系统??

一、springboot后台

1、mybatis-plus整合

1.1添加pom.xml

  1. <!--mp逆向工程 -->
  2. <dependency>
  3. <groupId>org.projectlombok</groupId>
  4. <artifactId>lombok</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.baomidou</groupId>
  8. <artifactId>mybatis-plus-boot-starter</artifactId>
  9. <version>3.4.3.1</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>com.baomidou</groupId>
  13. <artifactId>mybatis-plus-generator</artifactId>
  14. <version>3.1.0</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.freemarker</groupId>
  18. <artifactId>freemarker</artifactId>
  19. <version>2.3.31</version>
  20. </dependency>
  21. <dependency>
  22. <groupId>mysql</groupId>
  23. <artifactId>mysql-connector-java</artifactId>
  24. <version>8.0.28</version>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.apache.commons</groupId>
  28. <artifactId>commons-lang3</artifactId>
  29. <version>3.7</version>
  30. </dependency>

1.2创建CodeGenerator代码生成类

  1. package com.ds.book.mp;
  2. import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
  3. import com.baomidou.mybatisplus.core.toolkit.StringPool;
  4. import com.baomidou.mybatisplus.generator.AutoGenerator;
  5. import com.baomidou.mybatisplus.generator.InjectionConfig;
  6. import com.baomidou.mybatisplus.generator.config.*;
  7. import com.baomidou.mybatisplus.generator.config.po.TableInfo;
  8. import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
  9. import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
  10. import org.apache.commons.lang3.StringUtils;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. import java.util.Scanner;
  14. public class CodeGenerator {
  15. /**
  16. * <p>
  17. * 读取控制台内容
  18. * </p>
  19. */
  20. public static String scanner(String tip) {
  21. Scanner scanner = new Scanner(System.in);
  22. StringBuilder help = new StringBuilder();
  23. help.append("请输入" + tip + ":");
  24. System.out.println(help.toString());
  25. if (scanner.hasNext()) {
  26. String ipt = scanner.next();
  27. if (StringUtils.isNotBlank(ipt)) {
  28. return ipt;
  29. }
  30. }
  31. throw new MybatisPlusException("请输入正确的" + tip + "!");
  32. }
  33. public static void main(String[] args) {
  34. // 代码生成器
  35. AutoGenerator mpg = new AutoGenerator();
  36. // 全局配置
  37. GlobalConfig gc = new GlobalConfig();
  38. String projectPath = System.getProperty("user.dir");
  39. gc.setOutputDir(projectPath + "/src/main/java");
  40. gc.setAuthor("java大师");
  41. gc.setOpen(false);
  42. // gc.setSwagger2(true); 实体属性 Swagger2 注解
  43. mpg.setGlobalConfig(gc);
  44. // 数据源配置
  45. DataSourceConfig dsc = new DataSourceConfig();
  46. dsc.setUrl("jdbc:mysql://175.24.198.63:3306/book?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8");
  47. // dsc.setSchemaName("public");
  48. dsc.setDriverName("com.mysql.cj.jdbc.Driver");
  49. dsc.setUsername("root");
  50. dsc.setPassword("root@1234!@#");
  51. mpg.setDataSource(dsc);
  52. // 包配置
  53. PackageConfig pc = new PackageConfig();
  54. // pc.setModuleName(scanner("模块名"));
  55. pc.setParent("com.ds.book");
  56. mpg.setPackageInfo(pc);
  57. // 自定义配置
  58. InjectionConfig cfg = new InjectionConfig() {
  59. @Override
  60. public void initMap() {
  61. // to do nothing
  62. }
  63. };
  64. // 如果模板引擎是 freemarker
  65. String templatePath = "/templates/mapper.xml.ftl";
  66. // 如果模板引擎是 velocity
  67. // String templatePath = "/templates/mapper.xml.vm";
  68. // 自定义输出配置
  69. List<FileOutConfig> focList = new ArrayList<>();
  70. // 自定义配置会被优先输出
  71. focList.add(new FileOutConfig(templatePath) {
  72. @Override
  73. public String outputFile(TableInfo tableInfo) {
  74. // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
  75. return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
  76. }
  77. });
  78. /*
  79. cfg.setFileCreate(new IFileCreate() {
  80. @Override
  81. public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
  82. // 判断自定义文件夹是否需要创建
  83. checkDir("调用默认方法创建的目录,自定义目录用");
  84. if (fileType == FileType.MAPPER) {
  85. // 已经生成 mapper 文件判断存在,不想重新生成返回 false
  86. return !new File(filePath).exists();
  87. }
  88. // 允许生成模板文件
  89. return true;
  90. }
  91. });
  92. */
  93. cfg.setFileOutConfigList(focList);
  94. mpg.setCfg(cfg);
  95. // 配置模板
  96. TemplateConfig templateConfig = new TemplateConfig();
  97. // 配置自定义输出模板
  98. //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
  99. // templateConfig.setEntity("templates/entity2.java");
  100. // templateConfig.setService();
  101. // templateConfig.setController();
  102. templateConfig.setXml(null);
  103. mpg.setTemplate(templateConfig);
  104. // 策略配置
  105. StrategyConfig strategy = new StrategyConfig();
  106. strategy.setNaming(NamingStrategy.underline_to_camel);
  107. strategy.setColumnNaming(NamingStrategy.underline_to_camel);
  108. strategy.setTablePrefix("t_");
  109. // strategy.setInclude("t_user");
  110. // strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
  111. strategy.setEntityLombokModel(true);
  112. strategy.setRestControllerStyle(true);
  113. // 公共父类
  114. // strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
  115. // 写于父类中的公共字段
  116. strategy.setSuperEntityColumns("id");
  117. strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
  118. strategy.setControllerMappingHyphenStyle(true);
  119. // strategy.setTablePrefix(pc.getModuleName() + "_");
  120. mpg.setStrategy(strategy);
  121. mpg.setTemplateEngine(new FreemarkerTemplateEngine());
  122. mpg.execute();
  123. }
  124. }

1.3生成crontroller、service、mapper、entity等业务实体类

运行CodeGenerator,生成业务实体类

请输入表名,多个英文逗号分割: t_user,t_menu,t_role,t_user_role,t_role_menu

2、springsecurity-jwt整合

2.1整合springsecurity

1)

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>

2.2认证授权流程

img

认证管理

流程图解读:

1、用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到, 封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。

2、然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证 。

3、认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息, 身份信息,细节信息,但密码通常会被移除) Authentication 实例。

4、SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。 可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它 的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication。

授权管理

访问资源(即授权管理),访问url时,会通过FilterSecurityInterceptor拦截器拦截,其中会调用SecurityMetadataSource的方法来获取被拦截url所需的全部权限,再调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的投票策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则决策通过,返回访问资源,请求放行,否则跳转到403页面、自定义页面。

2.3编写自己的UserDetails和UserDetailService

2.3.1UserDetails
  1. package com.ds.book.entity;
  2. import com.baomidou.mybatisplus.annotation.TableName;
  3. import java.io.Serializable;
  4. import java.util.Collection;
  5. import lombok.Data;
  6. import lombok.EqualsAndHashCode;
  7. import lombok.experimental.Accessors;
  8. import org.springframework.security.core.GrantedAuthority;
  9. import org.springframework.security.core.userdetails.UserDetails;
  10. /**
  11. * <p>
  12. *
  13. * </p>
  14. *
  15. * @author java大师
  16. * @since 2023-03-17
  17. */
  18. @Data
  19. @EqualsAndHashCode(callSuper = false)
  20. @Accessors(chain = true)
  21. @TableName("t_user")
  22. public class User implements Serializable, UserDetails {
  23. private static final long serialVersionUID = 1L;
  24. private Integer id;
  25. /**
  26. * 登录名
  27. */
  28. private String name;
  29. /**
  30. * 用户名
  31. */
  32. private String username;
  33. /**
  34. * 密码
  35. */
  36. private String password;
  37. /**
  38. * 是否有效:1-有效;0-无效
  39. */
  40. private String status;
  41. @Override
  42. public Collection<? extends GrantedAuthority> getAuthorities() {
  43. return roles
  44. .stream()
  45. .map(role -> new SimpleGrantedAuthority(role.getRoleCode()))
  46. .collect(Collectors.toList());
  47. }
  48. @Override
  49. public boolean isAccountNonExpired() {
  50. return true;
  51. }
  52. @Override
  53. public boolean isAccountNonLocked() {
  54. return true;
  55. }
  56. @Override
  57. public boolean isCredentialsNonExpired() {
  58. return true;
  59. }
  60. @Override
  61. public boolean isEnabled() {
  62. return true;
  63. }
  64. }
2.3.2userDetailService

登录成功后,将UserDetails的roles设置到用户中

  1. package com.ds.book.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.ds.book.entity.User;
  4. import com.ds.book.mapper.UserMapper;
  5. import com.ds.book.service.IUserService;
  6. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  7. import org.apache.commons.lang3.StringUtils;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.security.core.userdetails.UserDetails;
  10. import org.springframework.security.core.userdetails.UserDetailsService;
  11. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  12. import org.springframework.stereotype.Service;
  13. /**
  14. * <p>
  15. * 服务实现类
  16. * </p>
  17. *
  18. * @author java大师
  19. * @since 2023-03-17
  20. */
  21. @Service
  22. public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserDetailsService {
  23. @Autowired
  24. private UserMapper userMapper;
  25. @Override
  26. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  27. User loginUser = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
  28. if (loginUser == null){
  29. throw new UsernameNotFoundException("用户名或密码错误");
  30. }
  31. loginUser.setRoles(userMapper.getRolesByUserId(loginUser.getId()));
  32. return loginUser;
  33. }
  34. }
2.3.2加载userDetailService

将我们自己的UserDetailService注入springsecurity

  1. package com.ds.book.config;
  2. import com.ds.book.filter.JwtTokenFilter;
  3. import com.ds.book.service.impl.UserServiceImpl;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.security.config.annotation.ObjectPostProcessor;
  8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  9. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  10. import org.springframework.security.config.annotation.web.builders.WebSecurity;
  11. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  12. import org.springframework.security.config.http.SessionCreationPolicy;
  13. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  14. import org.springframework.security.crypto.password.PasswordEncoder;
  15. import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
  16. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  17. @Configuration
  18. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  19. @Autowired
  20. private UserServiceImpl userService;
  21. @Bean
  22. public PasswordEncoder passwordEncoder(){
  23. return new BCryptPasswordEncoder();
  24. }
  25. //注入我们自己的UserDetailService
  26. @Override
  27. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  28. auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
  29. }
  30. }

问题:前后端分离项目,通常不会使用springsecurity自带的登录界面,登录界面由前端完成,后台只需要提供响应的服务即可,且目前主流不会采用session去存取用户,后端会返回响应的token,前端访问的时候,会在headers里面带入token.

2.4JwtToken

2.4.1 JWT描述

Jwt token由Header、Payload、Signature三部分组成,这三部分之间以小数点”.”连接,JWT token长这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.keH6T3x1z7mmhKL1T3r9sQdAxxdzB6siemGMr_6ZOwU

token解析后长这样: header部分,有令牌的类型(JWT)和签名算法名称(HS256): { "alg": "HS256", "typ": "JWT" } Payload部分,有效负载,这部分可以放任何你想放的数据:

{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }

Signature签名部分,由于这部分是使用header和payload部分计算的,所以还可以以此来验证payload部分有没有被篡改:

HMACSHA256(

base64UrlEncode(header) + "." +

base64UrlEncode(payload),

123456 //这里是密钥,只要够复杂,一般不会被破解

)

2.4.2 pom.xml
  1. <dependency>
  2. <groupId>io.jsonwebtoken</groupId>
  3. <artifactId>jjwt</artifactId>
  4. <version>0.9.0</version>
  5. </dependency>
2.4.3 JwtToken工具类
  1. package com.ds.book.tool;
  2. import io.jsonwebtoken.Claims;
  3. import io.jsonwebtoken.JwtBuilder;
  4. import io.jsonwebtoken.Jwts;
  5. import io.jsonwebtoken.SignatureAlgorithm;
  6. import javax.crypto.SecretKey;
  7. import javax.crypto.spec.SecretKeySpec;
  8. import java.util.Base64;
  9. import java.util.Date;
  10. import java.util.UUID;
  11. /**
  12. * JWT工具类
  13. */
  14. public class JwtUtil {
  15. //有效期为
  16. public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
  17. //设置秘钥明文
  18. public static final String JWT_KEY = "dashii";
  19. public static String getUUID(){
  20. String token = UUID.randomUUID().toString().replaceAll("-", "");
  21. return token;
  22. }
  23. /**
  24. * 生成jtw
  25. * @param subject token中要存放的数据(json格式)
  26. * @return
  27. */
  28. public static String createJWT(String subject) {
  29. JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
  30. return builder.compact();
  31. }
  32. /**
  33. * 生成jtw
  34. * @param subject token中要存放的数据(json格式)
  35. * @param ttlMillis token超时时间
  36. * @return
  37. */
  38. public static String createJWT(String subject, Long ttlMillis) {
  39. JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
  40. return builder.compact();
  41. }
  42. private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
  43. SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
  44. SecretKey secretKey = generalKey();
  45. long nowMillis = System.currentTimeMillis();
  46. Date now = new Date(nowMillis);
  47. if(ttlMillis==null){
  48. ttlMillis= JwtUtil.JWT_TTL;
  49. }
  50. long expMillis = nowMillis + ttlMillis;
  51. Date expDate = new Date(expMillis);
  52. return Jwts.builder()
  53. .setId(uuid) //唯一的ID
  54. .setSubject(subject) // 主题 可以是JSON数据
  55. .setIssuer("dashi") // 签发者
  56. .setIssuedAt(now) // 签发时间
  57. .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
  58. .setExpiration(expDate);
  59. }
  60. /**
  61. * 创建token
  62. * @param id
  63. * @param subject
  64. * @param ttlMillis
  65. * @return
  66. */
  67. public static String createJWT(String id, String subject, Long ttlMillis) {
  68. JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
  69. return builder.compact();
  70. }
  71. public static void main(String[] args) throws Exception {
  72. String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
  73. Claims claims = parseJWT(token);
  74. System.out.println(claims);
  75. }
  76. /**
  77. * 生成加密后的秘钥 secretKey
  78. * @return
  79. */
  80. public static SecretKey generalKey() {
  81. byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
  82. SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
  83. return key;
  84. }
  85. /**
  86. * 解析
  87. *
  88. * @param jwt
  89. * @return
  90. * @throws Exception
  91. */
  92. public static Claims parseJWT(String jwt) throws Exception {
  93. SecretKey secretKey = generalKey();
  94. return Jwts.parser()
  95. .setSigningKey(secretKey)
  96. .parseClaimsJws(jwt)
  97. .getBody();
  98. }
  99. }
2.4.4 JwtTokenFilter
  1. package com.ds.book.filter;
  2. import com.ds.book.entity.User;
  3. import com.ds.book.mapper.UserMapper;
  4. import com.ds.book.service.IMenuService;
  5. import com.ds.book.service.IUserService;
  6. import com.ds.book.tool.JwtUtil;
  7. import io.jsonwebtoken.Claims;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  10. import org.springframework.security.core.context.SecurityContextHolder;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.util.StringUtils;
  13. import org.springframework.web.filter.OncePerRequestFilter;
  14. import javax.servlet.FilterChain;
  15. import javax.servlet.ServletException;
  16. import javax.servlet.http.HttpServletRequest;
  17. import javax.servlet.http.HttpServletResponse;
  18. import java.io.IOException;
  19. @Component
  20. public class JwtTokenFilter extends OncePerRequestFilter {
  21. @Autowired
  22. private IUserService userService;
  23. @Autowired
  24. private UserMapper userMapper;
  25. @Override
  26. protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
  27. //1、获取token
  28. String token = httpServletRequest.getHeader("token");
  29. if (StringUtils.isEmpty(token)){
  30. filterChain.doFilter(httpServletRequest,httpServletResponse);
  31. return;
  32. }
  33. String userId;
  34. try {
  35. Claims claims = JwtUtil.parseJWT(token);
  36. userId = claims.getSubject();
  37. } catch (Exception exception) {
  38. exception.printStackTrace();
  39. throw new RuntimeException("token非法");
  40. }
  41. User user = userService.getUserById(Integer.parseInt(userId));
  42. user.setRoles(userMapper.getRolesByUserId(Integer.parseInt(userId)));
  43. UsernamePasswordAuthenticationToken authenticationToken =
  44. new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
  45. SecurityContextHolder.getContext().setAuthentication(authenticationToken);
  46. filterChain.doFilter(httpServletRequest,httpServletResponse);
  47. }
  48. }

在springsecurity中,第一个经过的过滤器是UsernamePasswordAuthenticationFilter,所以前后端分离的项目,我们自己定义的过滤器要放在这个过滤器前面,具体配置如下

  1. @Override
  2. protected void configure(HttpSecurity http) throws Exception {
  3. http.csrf().disable()
  4. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  5. .and()
  6. .authorizeRequests()
  7. .antMatchers("/login").permitAll()
  8. .anyRequest().authenticated();
  9. http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
  10. http.cors();
  11. }
2.4.5授权
2.4.5.1 开启preAuthorize进行收取(Controller路径匹配)

1)主启动类上添加EnableGlobalMethodSecurity注解

  1. @EnableGlobalMethodSecurity(prePostEnabled = true)
  2. @SpringBootApplication
  3. @MapperScan("com.ds.book.mapper")
  4. public class BookSysApplication {
  5. public static void main(String[] args) {
  6. SpringApplication.run(BookSysApplication.class,args);
  7. }
  8. }

2)Controller方法上添加@PreAuthorize注解

  1. @RestController
  2. public class HelloController {
  3. @GetMapping("/hello")
  4. @PreAuthorize("hasRole('ROLE_ADMIN')")
  5. public String hello(){
  6. return "hello";
  7. }
  8. }
2.4.5.2 增强方式授权(数据库表配置)

1)创建我们自己的FilterInvocationSecurityMetadataSource,实现getAttributes方法,获取请求url所需要的角色

  1. @Component
  2. public class MySecurtiMetaDataSource implements FilterInvocationSecurityMetadataSource {
  3. @Autowired
  4. private IMenuService menuService;
  5. AntPathMatcher antPathMatcher = new AntPathMatcher();
  6. //获取访问url需要的角色,例如:/sys/user需要ROLE_ADMIN角色,访问sys/user时获取到必须要有ROLE_ADMIN角色。返回 Collection<ConfigAttribute>
  7. @Override
  8. public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
  9. String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
  10. //获取所有的菜单及角色
  11. List<Menu> menus = menuService.getMenus();
  12. for (Menu menu : menus) {
  13. if (antPathMatcher.match(menu.getUrl(),requestURI)){
  14. String[] roles = menu.getRoles().stream().map(role -> role.getRoleCode()).toArray(String[]::new);
  15. return SecurityConfig.createList(roles);
  16. }
  17. }
  18. return null;
  19. }
  20. @Override
  21. public Collection<ConfigAttribute> getAllConfigAttributes() {
  22. return null;
  23. }
  24. @Override
  25. public boolean supports(Class<?> clazz) {
  26. return false;
  27. }
  28. }

2)创建我们自己的决策管理器AccessDecisionManager,实现decide方法,判断步骤1)中获取到的角色和我们目前登录的角色是否相同,相同则允许访问,不相同则不允许访问,

  1. @Component
  2. public class MyAccessDecisionManager implements AccessDecisionManager {
  3. //1、认证通过后,会往authentication中填充用户信息
  4. //2、拿authentication中的权限与上一步获取到的角色信息进行比对,比对成功后,允许访问
  5. @Override
  6. public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
  7. Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
  8. for (ConfigAttribute configAttribute : configAttributes) {
  9. for (GrantedAuthority authority : authorities) {
  10. if (authority.getAuthority().equals(configAttribute.getAttribute())){
  11. return;
  12. }
  13. }
  14. }
  15. throw new AccessDeniedException("权限不足,请联系管理员");
  16. }
  17. @Override
  18. public boolean supports(ConfigAttribute attribute) {
  19. return false;
  20. }
  21. @Override
  22. public boolean supports(Class<?> clazz) {
  23. return false;
  24. }
  25. }

3)在SecurityConfig中,添加后置处理器(增强器),让springsecurity使用我们自己的datametasource和decisionMananger

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. private MySecurtiMetaDataSource mySecurtiMetaDataSource;
  5. @Autowired
  6. private MyAccessDecisionManager myAccessDecisionManager;
  7. @Autowired
  8. private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
  9. @Autowired
  10. private MyAccessDeniedHandler myAccessDeniedHandler;
  11. @Autowired
  12. private UserServiceImpl userService;
  13. @Autowired
  14. private JwtTokenFilter jwtTokenFilter;
  15. @Bean
  16. public PasswordEncoder passwordEncoder(){
  17. return new BCryptPasswordEncoder();
  18. }
  19. @Override
  20. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  21. auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
  22. }
  23. @Override
  24. protected void configure(HttpSecurity http) throws Exception {
  25. http.csrf().disable()
  26. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  27. .and()
  28. .authorizeRequests()
  29. .antMatchers("/login").permitAll()
  30. .anyRequest().authenticated()
  31. //后置处理器,使用我们自己的FilterSecurityInterceptor拦截器配置
  32. .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor> () {
  33. @Override
  34. public <O extends FilterSecurityInterceptor> O postProcess(O o) {
  35. o.setSecurityMetadataSource(mySecurtiMetaDataSource);
  36. o.setAccessDecisionManager(myAccessDecisionManager);
  37. return o;
  38. }
  39. })
  40. .and()
  41. .headers().cacheControl();
  42. http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
  43. http.cors();
  44. }
  45. }
2.4.6异常处理

1)前端渲染工具类

  1. public class WebUtils
  2. {
  3. /**
  4. * 将字符串渲染到客户端
  5. *
  6. * @param response 渲染对象
  7. * @param string 待渲染的字符串
  8. * @return null
  9. */
  10. public static String renderString(HttpServletResponse response, String string) {
  11. try
  12. {
  13. response.setStatus(200);
  14. response.setContentType("application/json");
  15. response.setCharacterEncoding("utf-8");
  16. response.getWriter().print(string);
  17. }
  18. catch (IOException e)
  19. {
  20. e.printStackTrace();
  21. }
  22. return null;
  23. }
  24. }

2)未登录异常处理,实现commence方法

  1. @Component
  2. public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
  3. @Override
  4. public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
  5. Result result = new Result(401,"未登录,请先登录",null);
  6. String json = JSON.toJSONString(result);
  7. WebUtils.renderString(httpServletResponse,json);
  8. }
  9. }

3)授权失败异常处理,实现Handle方法

  1. @Component
  2. public class MyAccessDeniedHandler implements AccessDeniedHandler {
  3. @Override
  4. public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
  5. Result result = new Result(403,"权限不足请联系管理员",null);
  6. String s = JSON.toJSONString(result);
  7. WebUtils.renderString(httpServletResponse,s);
  8. }
  9. }

3、整合swagger2

1)添加pom.xml依赖

  1. <dependency>
  2. <groupId>io.springfox</groupId>
  3. <artifactId>springfox-swagger2</artifactId>
  4. <version>2.7.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>io.springfox</groupId>
  8. <artifactId>springfox-swagger-ui</artifactId>
  9. <version>2.7.0</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>com.github.xiaoymin</groupId>
  13. <artifactId>knife4j-spring-boot-starter</artifactId>
  14. <version>2.0.7</version>
  15. </dependency>

2)创建swagger配置文件

  1. package com.ds.book.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import springfox.documentation.builders.ApiInfoBuilder;
  5. import springfox.documentation.builders.PathSelectors;
  6. import springfox.documentation.builders.RequestHandlerSelectors;
  7. import springfox.documentation.service.*;
  8. import springfox.documentation.spi.DocumentationType;
  9. import springfox.documentation.spi.service.contexts.SecurityContext;
  10. import springfox.documentation.spring.web.plugins.Docket;
  11. import springfox.documentation.swagger2.annotations.EnableSwagger2;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. @Configuration
  15. @EnableSwagger2
  16. public class Swagger2Config {
  17. @Bean
  18. public Docket createRestApi() {
  19. return new Docket(DocumentationType.SWAGGER_2)
  20. .pathMapping("/")
  21. .apiInfo(apiInfo())
  22. .select()
  23. //swagger要扫描的包路径
  24. .apis(RequestHandlerSelectors.basePackage("com.ds.book.controller"))
  25. .paths(PathSelectors.any())
  26. .build()
  27. .securityContexts(securityContexts())
  28. .securitySchemes(securitySchemes());
  29. }
  30. private ApiInfo apiInfo() {
  31. return new ApiInfoBuilder().title("图书管理系统接口文档")
  32. //作者、路径和邮箱
  33. .contact(new Contact("java大师","http://localhost:8080/doc.html","fry000@qq.com"))
  34. .version("1.0").description("图书管理接口文档").build();
  35. }
  36. private List<SecurityContext> securityContexts() {
  37. //设置需要登录认证的路径
  38. List<SecurityContext> result = new ArrayList<>();
  39. result.add(getContextByPath("/.*"));
  40. return result;
  41. }
  42. //通过pathRegex获取SecurityContext对象
  43. private SecurityContext getContextByPath(String pathRegex) {
  44. return SecurityContext.builder()
  45. .securityReferences(defaultAuth())
  46. .forPaths(PathSelectors.regex(pathRegex))
  47. .build();
  48. }
  49. //默认为全局的SecurityReference对象
  50. private List<SecurityReference> defaultAuth() {
  51. List<SecurityReference> result = new ArrayList<>();
  52. AuthorizationScope authorizationScope = new AuthorizationScope("global",
  53. "accessEverything");
  54. AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
  55. authorizationScopes[0] = authorizationScope;
  56. result.add(new SecurityReference("Authorization", authorizationScopes));
  57. return result;
  58. }
  59. private List<ApiKey> securitySchemes() {
  60. //设置请求头信息
  61. List<ApiKey> result = new ArrayList<>();
  62. //设置header中的token
  63. ApiKey apiKey = new ApiKey("token", "token", "header");
  64. result.add(apiKey);
  65. return result;
  66. }
  67. }

3)修改SecurityConfig配置类,允许访问swagger的地址

  1. //主要的配置文件,antMatchers匹配的路径,全部忽略,不进行JwtToken的认证
  2. @Override
  3. public void configure(WebSecurity web) throws Exception {
  4. web.ignoring().antMatchers(
  5. "/login",
  6. "/logout",
  7. "/css/**",
  8. "/js/**",
  9. "/index.html",
  10. "favicon.ico",
  11. "/doc.html",
  12. "/webjars/**",
  13. "/swagger-resources/**",
  14. "/v2/api-docs/**"
  15. );
  16. }

4)编写LoginController接口,通过@Api和@ApiOperation注解使用swagger

  1. package com.ds.book.controller;
  2. import com.ds.book.entity.Result;
  3. import com.ds.book.entity.User;
  4. import com.ds.book.service.IUserService;
  5. import io.swagger.annotations.Api;
  6. import io.swagger.annotations.ApiOperation;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.security.access.SecurityConfig;
  9. import org.springframework.security.access.prepost.PreAuthorize;
  10. import org.springframework.web.bind.annotation.GetMapping;
  11. import org.springframework.web.bind.annotation.PostMapping;
  12. import org.springframework.web.bind.annotation.RequestBody;
  13. import org.springframework.web.bind.annotation.RestController;
  14. @RestController
  15. @Api(tags = "登录")
  16. public class LoginController {
  17. @Autowired
  18. private IUserService userService;
  19. @ApiOperation("登录")
  20. @PostMapping("/login")
  21. public Result login(@RequestBody User user){
  22. return userService.login(user);
  23. }
  24. }

5)输入地址 http://localhost:8080/doc.html,进入swagger

6)点击登录进入登录接口,点击调试,发送

测试成功!

4、业务接口

4.1 登录接口

注意:前后端分离项目,退出的时候,由前端清除浏览器请求header中的token和sessionStorage或者LocalStorage,后端只要返回一个退出成功的消息。

  1. package com.ds.book.controller;
  2. import com.ds.book.entity.Result;
  3. import com.ds.book.entity.User;
  4. import com.ds.book.service.IUserService;
  5. import io.swagger.annotations.Api;
  6. import io.swagger.annotations.ApiOperation;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.security.access.SecurityConfig;
  9. import org.springframework.security.access.prepost.PreAuthorize;
  10. import org.springframework.security.core.userdetails.UserDetails;
  11. import org.springframework.security.core.userdetails.UserDetailsService;
  12. import org.springframework.web.bind.annotation.GetMapping;
  13. import org.springframework.web.bind.annotation.PostMapping;
  14. import org.springframework.web.bind.annotation.RequestBody;
  15. import org.springframework.web.bind.annotation.RestController;
  16. import java.security.Principal;
  17. @RestController
  18. @Api(tags = "登录")
  19. public class LoginController {
  20. @Autowired
  21. private IUserService userService;
  22. @Autowired
  23. private UserDetailsService userDetailsService;
  24. @ApiOperation("登录")
  25. @PostMapping("/login")
  26. public Result login(@RequestBody User user){
  27. return userService.login(user);
  28. }
  29. @ApiOperation("退出")
  30. @PostMapping("/logout")
  31. public Result logout(){
  32. return Result.success("退出成功");
  33. }
  34. @ApiOperation("获取当前登录用户信息")
  35. @GetMapping("/user/info")
  36. public User user(Principal principal){
  37. if (principal == null){
  38. return null;
  39. }
  40. String username = principal.getName();
  41. User user = (User)userDetailsService.loadUserByUsername(username);
  42. user.setPassword(null);
  43. return user;
  44. }
  45. }

4.2菜单接口

  1. package com.ds.book.controller;
  2. import com.ds.book.entity.Menu;
  3. import com.ds.book.entity.Result;
  4. import com.ds.book.service.IMenuService;
  5. import io.swagger.annotations.Api;
  6. import io.swagger.annotations.ApiOperation;
  7. import io.swagger.models.auth.In;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.security.access.prepost.PreAuthorize;
  10. import org.springframework.web.bind.annotation.*;
  11. import java.util.List;
  12. /**
  13. * <p>
  14. * 前端控制器
  15. * </p>
  16. *
  17. * @author java大师
  18. * @since 2023-03-09
  19. */
  20. @RestController
  21. @Api(tags = "菜单管理")
  22. public class MenuController {
  23. @Autowired
  24. private IMenuService menuService;
  25. @GetMapping("/menus")
  26. @ApiOperation("获取菜单树")
  27. public Result getMenus(){
  28. List<Menu> allMenus = menuService.getMenuTree();
  29. return Result.success("查询成功",allMenus);
  30. }
  31. @PostMapping("/menu/add")
  32. @ApiOperation("添加菜单")
  33. public Result addMenu(@RequestBody Menu menu){
  34. return menuService.addMenu(menu);
  35. }
  36. @PostMapping("/menu/update")
  37. @ApiOperation("修改菜单")
  38. public Result updateMenu(@RequestBody Menu menu){
  39. return menuService.updateMenu(menu);
  40. }
  41. @PostMapping("/menu/delete/{id}")
  42. @ApiOperation("删除菜单")
  43. public Result deleteMenu(@PathVariable Integer id){
  44. return menuService.deleteMenu(id);
  45. }
  46. }

4.3用户接口

  1. package com.ds.book.controller;
  2. import com.ds.book.entity.Result;
  3. import com.ds.book.entity.User;
  4. import com.ds.book.service.IUserService;
  5. import io.swagger.annotations.Api;
  6. import io.swagger.annotations.ApiOperation;
  7. import io.swagger.models.auth.In;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.security.crypto.password.PasswordEncoder;
  10. import org.springframework.web.bind.annotation.*;
  11. import javax.jws.soap.SOAPBinding;
  12. import java.util.List;
  13. /**
  14. * <p>
  15. * 前端控制器
  16. * </p>
  17. *
  18. * @author java大师
  19. * @since 2023-03-09
  20. */
  21. @RestController
  22. @Api(tags = "用户管理")
  23. public class UserController {
  24. @Autowired
  25. private IUserService userService;
  26. @Autowired
  27. private PasswordEncoder passwordEncoder;
  28. @GetMapping("/users")
  29. @ApiOperation("查询用户列表")
  30. public Result getUsers(){
  31. List<User> list = userService.getUsers();
  32. if (list != null){
  33. return Result.success("查询成功",list);
  34. }
  35. return Result.error("查询失败");
  36. }
  37. @PostMapping("/user/add")
  38. @ApiOperation("添加用户")
  39. public Result addUser(@RequestBody User user){
  40. user.setPassword(passwordEncoder.encode("123456"));
  41. return userService.addUser(user);
  42. }
  43. @PostMapping("/user/update")
  44. @ApiOperation("修改用户")
  45. public Result updateUser(@RequestBody User user){
  46. return userService.updateUser(user);
  47. }
  48. @PostMapping("/user/chooseRole/{userId}/{roleId}")
  49. @ApiOperation("选择角色")
  50. public Result chooseRole(@PathVariable Integer userId,@PathVariable Integer roleId){
  51. return userService.chooseRole(userId,roleId);
  52. }
  53. @PostMapping("/user/delete/{id}")
  54. @ApiOperation("删除用户")
  55. public Result deleteUser(@PathVariable Integer id){
  56. return userService.deleteUser(id);
  57. }
  58. }

4.4角色接口

  1. package com.ds.book.service.impl;
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  3. import com.ds.book.entity.Menu;
  4. import com.ds.book.entity.Result;
  5. import com.ds.book.entity.Role;
  6. import com.ds.book.entity.RoleMenu;
  7. import com.ds.book.mapper.RoleMapper;
  8. import com.ds.book.mapper.RoleMenuMapper;
  9. import com.ds.book.service.IRoleService;
  10. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.stereotype.Service;
  13. import java.util.ArrayList;
  14. import java.util.List;
  15. /**
  16. * <p>
  17. * 服务实现类
  18. * </p>
  19. *
  20. * @author java大师
  21. * @since 2023-03-09
  22. */
  23. @Service
  24. public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IRoleService {
  25. @Autowired
  26. private RoleMapper roleMapper;
  27. @Autowired
  28. private RoleMenuMapper roleMenuMapper;
  29. private List<Menu> buildMenuTree(List<Menu> menus, Integer parentId) {
  30. List<Menu> treeMenus = new ArrayList<>();
  31. for (Menu menu : menus) {
  32. if (parentId==0 ? menu.getParentId()==0 : parentId.equals(menu.getParentId())) {
  33. List<Menu> children = buildMenuTree(menus, menu.getId());
  34. if (!children.isEmpty()) {
  35. menu.setChildren(children);
  36. }
  37. treeMenus.add(menu);
  38. }
  39. }
  40. return treeMenus;
  41. }
  42. @Override
  43. public List getRoles() {
  44. List<Role> roles = roleMapper.getRoles();
  45. for (Role role : roles) {
  46. role.setMenus(buildMenuTree(role.getMenus(),0));
  47. }
  48. return roles;
  49. }
  50. @Override
  51. public Result chooseMenus(Integer roleId, Integer[] menuIds) {
  52. try {
  53. roleMenuMapper.delete(new QueryWrapper<RoleMenu>().eq("role_id",roleId));
  54. for (Integer menuId : menuIds) {
  55. RoleMenu roleMenu = new RoleMenu();
  56. roleMenu.setRoleId(roleId);
  57. roleMenu.setMenuId(menuId);
  58. roleMenuMapper.insert(roleMenu);
  59. }
  60. return Result.success("添加成功");
  61. } catch (Exception exception) {
  62. return Result.error("添加失败");
  63. }
  64. }
  65. }

二、springboot前端

1、vue-cli创建vue-book前端项目

  1. vue create vue-book

选择Vue2,运行完毕,出现以下画面

  1. 执行绿色的命令,出现下列界面代表脚手架创建项目成功

2、整合elementui

  1. //命令行安装
  2. npm i element-ui -S
  3. //main.js使用element-ui
  4. import Vue from 'vue';
  5. import ElementUI from 'element-ui';
  6. import 'element-ui/lib/theme-chalk/index.css';
  7. import App from './App.vue';
  8. Vue.use(ElementUI);
  9. new Vue({
  10. el: '#app',
  11. render: h => h(App)
  12. });

3、安装vue-router

2.1安装依赖

  1. npm install vue-router@3

2.2创建路由文件

  1. import Vue from 'vue'
  2. import VueRouter from "vue-router";
  3. Vue.use(VueRouter)
  4. //配置localhost:8080/跳转为登录页
  5. const routes =[
  6. {
  7. path:'/',
  8. name:'Login',
  9. component:() => import('@/pages/Login.vue')
  10. }
  11. ]
  12. export default new VueRouter({
  13. routes
  14. })

4、整合json-server

4.1安装json-server

  1. npm install -g json-server

4.2创建mock文件夹,新建db.json

  1. {
  2. "posts": [
  3. {
  4. "id": 1,
  5. "title": "json-server",
  6. "author": "typicode"
  7. }
  8. ],
  9. "users": [
  10. {
  11. "id": 1,
  12. "username": "admin",
  13. "password": "123"
  14. }
  15. ],
  16. "login":
  17. {
  18. "code": 200,
  19. "message":"返回成功",
  20. "data": {
  21. "id": "1237361915165020161",
  22. "username": "admin",
  23. "phone": "111111111111",
  24. "nickName": "javads",
  25. "realName": "javads",
  26. "sex": 1,
  27. "deptId": "1237322421447561216",
  28. "deptName": "测试部门",
  29. "status": 1,
  30. "email": "xxxx@qq.com",
  31. "token":"ASDSADASDSW121DDSA",
  32. "menus": [
  33. {
  34. "id": "1236916745927790564",
  35. "title": "系统管理",
  36. "icon": "el-icon-star-off",
  37. "path": "/sys",
  38. "name": "Sys",
  39. "children": [
  40. {
  41. "id": "1236916745927790578",
  42. "title": "角色管理",
  43. "icon": "el-icon-s-promotion",
  44. "path": "/sys/roles",
  45. "name": "Roles",
  46. "children": []
  47. },
  48. {
  49. "id": "1236916745927790560",
  50. "title": "菜单管理",
  51. "icon": "el-icon-s-tools",
  52. "path": "/sys/menus",
  53. "name": "Menus",
  54. "children": []
  55. },
  56. {
  57. "id": "1236916745927790575",
  58. "title": "用户管理",
  59. "icon": "el-icon-s-custom",
  60. "path": "/sys/users",
  61. "name": "User",
  62. "children": []
  63. }
  64. ],
  65. "spread": true,
  66. "checked": false
  67. },
  68. {
  69. "id": "1236916745927790569",
  70. "title": "账号管理",
  71. "icon": "el-icon-s-data",
  72. "path": "/account",
  73. "name": "Account",
  74. "children": []
  75. }
  76. ],
  77. "permissions": [
  78. "sys:log:delete",
  79. "sys:user:add",
  80. "sys:role:update",
  81. "sys:dept:list"
  82. ]
  83. }
  84. },
  85. "comments": [
  86. {
  87. "id": 1,
  88. "body": "some comment",
  89. "postId": 1
  90. }
  91. ],
  92. "profile": {
  93. "name": "typicode"
  94. }
  95. }

4.3修改vue.config.js,json-server的默认端口为3000,将代理服务器的的端口改成3000

  1. const { defineConfig } = require('@vue/cli-service')
  2. module.exports = defineConfig({
  3. transpileDependencies: true,
  4. lintOnSave:false,
  5. devServer:{
  6. proxy:{
  7. '/api':{
  8. target:'http://localhost:3000',
  9. pathRewrite:{'^/api':''},
  10. ws:true, //不写为true,websocket
  11. changeOrigin:true //不写为true
  12. }
  13. }
  14. }
  15. })

4.4修改package.json,在scripts添加以下代码

  1. "mock": "json-server src/mock/db.json --port 3000 --middlewares src/mock/middlewares.js"

4.5 运行json-server,出现以下界面代表运行成功

  1. json-server.cmd --watch db.jso

5、整合axios

5.1配置axios请求拦截器,新建utils文件夹,新建api.js,输入以下内容

  1. import router from '../router'
  2. import axios from 'axios'
  3. import {Message} from 'element-ui'
  4. import {Loading} from 'element-ui'
  5. axios.defaults.baseURL = '/api'
  6. //添加遮罩层代码
  7. let loading;
  8. let loadingNum = 0;
  9. //弹出遮罩层
  10. function showLoading(){
  11. if (loadingNum ===0){
  12. loading = Loading.service({
  13. lock:true,
  14. text:'加载中,请稍后...',
  15. background:'rgba(255,255,255,0.5)'
  16. })
  17. }
  18. loadingNum++;
  19. }
  20. //关闭遮罩层
  21. function hiddenLoading(){
  22. loadingNum--;
  23. if (loadingNum <=0){
  24. loading.close();
  25. }
  26. }
  27. /**
  28. * 添加响应拦截器,在浏览器每次发请求之前,token放入http消息头当中
  29. */
  30. axios.interceptors.request.use(config =>{
  31. showLoading();
  32. if(window.sessionStorage.getItem('token')){
  33. config.headers.Authorization =window.sessionStorage.getItem('token')
  34. }
  35. console.log(config)
  36. return config
  37. },error => {
  38. console.log(error)
  39. })
  40. /**
  41. * 添加响应拦截器
  42. */
  43. axios.interceptors.response.use(success => {
  44. hiddenLoading();
  45. if (success.status && success.status == 200){
  46. if (success.data.code == 500 || success.data.code == 401 || success.data.code == 403) {
  47. Message.error({
  48. offset:200,
  49. message:success.data.message
  50. })
  51. router.replace("/")
  52. }
  53. if (success.data.message){
  54. Message.success({
  55. offset:200,
  56. message:success.data.message
  57. })
  58. }
  59. }
  60. return success.data
  61. },error => {
  62. hiddenLoading();
  63. if (error.response.code == 504 || error.response.code == 404) {
  64. Message.error({
  65. message: '服务器跑路了'
  66. });
  67. } else if (error.response.status == 403) {
  68. Message.error({
  69. message: '权限不足,请联系管理员'
  70. });
  71. } else if (error.response.code == 401) {
  72. Message.error({
  73. message: '尚未登录,请先登录'
  74. })
  75. router.replace('/');
  76. } else {
  77. if (error.response.data.message) {
  78. Message.error({
  79. message: error.response.data.message
  80. });
  81. } else {
  82. Message.error({
  83. message: '未知错误'
  84. });
  85. }
  86. }
  87. return;
  88. })
  89. export default axios

5.2创建请求接口,新建http.js

  1. import axios from './api'
  2. export const login = (param) =>{
  3. return axios.get(`/posts`, {param})
  4. }
  5. export const getUser = () =>{
  6. return axios.get(`/users`, {})
  7. }

6、业务功能

6.1登录界面

  1. <template>
  2. <div class="login-container">
  3. <el-form ref="form" :model="form" label-width="100px" class="login-form">
  4. <h1 style="margin-bottom: 20px;text-align: center">欢迎登录</h1>
  5. <el-form-item label="用户名">
  6. <el-input v-model="form.username"></el-input>
  7. </el-form-item>
  8. <el-form-item label="密码">
  9. <el-input type="password" v-model="form.password"></el-input>
  10. </el-form-item>
  11. <el-form-item>
  12. <el-button type="primary" @click="onSubmit">登录</el-button>
  13. <el-button>取消</el-button>
  14. </el-form-item>
  15. </el-form>
  16. </div>
  17. </template>
  18. <script>
  19. import {initRoutes} from "@/utils/routesUtil";
  20. import {login,getUser} from "@/utils/http";
  21. export default {
  22. name:'Login',
  23. data() {
  24. return {
  25. form: {
  26. username: '',
  27. password: '',
  28. }
  29. }
  30. },
  31. methods: {
  32. onSubmit() {
  33. login(this.form).then(res=>{
  34. if(res){
  35. //浏览器中存储token,以后每次调用后端接口,浏览器都会带入这个token
  36. window.sessionStorage.setItem("token",res.data.token)
  37. //初始化路由数据
  38. let myRoutes = initRoutes(res.data.menus)
  39. //将路由进行替换并添加到router中
  40. this.$router.options.routes = [myRoutes]
  41. this.$router.addRoute(myRoutes)
  42. this.$router.replace("/home")
  43. }else{
  44. return false
  45. }
  46. })
  47. },
  48. }
  49. }
  50. </script>
  51. <style scoped>
  52. .login-form {
  53. border: 1px #DCDFE6 solid;
  54. border-radius: 4px;
  55. padding: 40px;
  56. margin: 110px 400px;
  57. box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
  58. width: 400px;
  59. }
  60. .login-container {
  61. /*background: url(../assets/image/login2.jpg) no-repeat;*/
  62. height: 100%;
  63. width: 100%;
  64. overflow: hidden;
  65. background-size: cover;
  66. }
  67. </style>

6.2处理后台请求返回工具类

  1. export const initTmpRoutes = (menus) => {
  2. let tmpRoutes = []
  3. menus.forEach(menu => {
  4. let {id,title,icon,path,name,children} = menu
  5. if(children instanceof Array){
  6. children = initTmpRoutes(children)
  7. }
  8. let tmpRoute = {
  9. path:path,
  10. meta:{icon:icon,title:title},
  11. name:name,
  12. children:children,
  13. component:children.length?{render(c){return c('router-view')}}:()=>import(`@/pages${path}/${name}.vue`)
  14. }
  15. console.log('tmpRoute',tmpRoute.path)
  16. tmpRoutes.push(tmpRoute)
  17. })
  18. return tmpRoutes
  19. }
  20. export const initRoutes = (menus)=>{
  21. const homeRoute = {
  22. path:'/home',
  23. name:'Home',
  24. meta:{title:'首页',icon: 'el-icon-star-off'},
  25. component:() => import('@/pages/Home.vue'),
  26. }
  27. homeRoute.children = initTmpRoutes(menus);
  28. console.log('homeRoute',homeRoute)
  29. return homeRoute;
  30. }

6.3首页、导航页和主页

home.vue

  1. <template>
  2. <div class="box">
  3. <el-container style="height: 100%;" direction="vertial">
  4. <el-aside width="200px">
  5. <Nav/>
  6. </el-aside>
  7. <el-container>
  8. <el-header class="homeHeader">
  9. <el-dropdown class="userInfo" @command="handlecommand">
  10. <span class="el-dropdown-link">
  11. </span>
  12. <el-dropdown-menu slot="dropdown">
  13. <el-dropdown-item command="userInfo">个人中心</el-dropdown-item>
  14. <el-dropdown-item command="setting">设置</el-dropdown-item>
  15. <el-dropdown-item command="logout">退出</el-dropdown-item>
  16. </el-dropdown-menu>
  17. </el-dropdown>
  18. </el-header>
  19. <el-main>
  20. <Main/>
  21. </el-main>
  22. <el-footer>底部</el-footer>
  23. </el-container>
  24. </el-container>
  25. </div>
  26. </template>
  27. <script>
  28. import Nav from "@/components/Nav";
  29. import Main from "@/components/Main";
  30. import RecursiveMenu from "@/components/RecursiveMenu";
  31. export default{
  32. data(){
  33. return {
  34. user:JSON.parse(window.sessionStorage.getItem('user'))
  35. }
  36. },
  37. components:{
  38. Nav,
  39. RecursiveMenu,
  40. Main
  41. },
  42. methods:{
  43. handlecommand(command){
  44. if(command=='logout'){
  45. this.$confirm('确定退出?', '提示', {
  46. confirmButtonText: '确定',
  47. cancelButtonText: '取消',
  48. type: 'warning'
  49. }).then(()=>{
  50. logout();
  51. window.sessionStorage.removeItem('user');
  52. window.sessionStorage.removeItem('token');
  53. this.$store.commit('initRoutes',[]);
  54. this.$router.replace('/');
  55. }).catch(()=>{
  56. })
  57. }
  58. }
  59. },
  60. }
  61. </script>
  62. <style>
  63. #app,
  64. html,
  65. body,
  66. .box,
  67. .el-container{
  68. padding: 0px;
  69. margin: 0px;
  70. height: 100%;
  71. }
  72. .el-header,
  73. .el-footer {
  74. background-color: #B3C0D1;
  75. color: #333;
  76. text-align: right;
  77. line-height: 60px;
  78. }
  79. .el-aside {
  80. background-color: #545C64;
  81. color: #333;
  82. text-align: center;
  83. line-height: 300px;
  84. }
  85. .el-main {
  86. background-color: #E9EEF3;
  87. color: #333;
  88. display: flex;
  89. flex-direction: column;
  90. }
  91. body>.el-container {
  92. margin-bottom: 40px;
  93. }
  94. .homeHeader .userInfo{
  95. cursor: pointer;
  96. }
  97. .el-dropdown-link img{
  98. width: 36px;
  99. height: 36px;
  100. border-radius: 18px;
  101. }
  102. </style>

Nav.vue

  1. <template>
  2. <el-menu router>
  3. <template v-for="item in routes">
  4. <el-submenu v-if="item.children.length" :index="item.path">
  5. <template slot="title">{{ item.meta.title }}</template>
  6. <recursive-menu :menu="item.children"></recursive-menu>
  7. </el-submenu>
  8. <el-menu-item v-else :index="item.path">{{ item.meta.title }}</el-menu-item>
  9. </template>
  10. </el-menu>
  11. </template>
  12. <script>
  13. import RecursiveMenu from "@/components/RecursiveMenu";
  14. export default {
  15. name: 'Nav',
  16. components:{
  17. RecursiveMenu
  18. },
  19. computed:{
  20. routes(){
  21. console.log('Nav routes:',this.$router.options.routes.length)
  22. // return this.$router.options.routes[1].children;
  23. return this.$router.options.routes;
  24. }
  25. }
  26. }
  27. </script>

RecursiveMenu.vue

  1. <template>
  2. <div>
  3. <el-menu router>
  4. <template v-for="item in menu">
  5. <el-submenu v-if="item.children.length" :index="item.path">
  6. <template slot="title">{{ item.meta.title }}</template>
  7. <recursive-menu :menu="item.children"></recursive-menu>
  8. </el-submenu>
  9. <el-menu-item v-else :index="item.path">{{ item.meta.title }}</el-menu-item>
  10. </template>
  11. </el-menu>
  12. </div>
  13. </template>
  14. <script>
  15. export default {
  16. name: 'RecursiveMenu',
  17. props: {
  18. menu: {
  19. type: Array,
  20. required: true
  21. },
  22. },
  23. components: {
  24. RecursiveMenu: () => import('./RecursiveMenu.vue')
  25. }
  26. }
  27. </script>

可以看到左边的菜单和路由已经展示在浏览器中

注意:这里有一个坑,页面刷新以后,路由中的数据就会丢失,系统菜单会不显示

原因:页面刷新后,页面会重新实例化路由数据,因为是动态路由,所以页面刷新后会将router置为router/index.js配置的原始路由数据,所以匹配路由地址的时候会报错。

解决方法

思路:因为目前login接口返回的时候,直接将菜单数据传回前端,所以我们需要将菜单缓存起来,因为每次页面刷新vuex数据都会重置,所以不适合存储在vuex中,可以将菜单数据存储在sessionStorage中,页面刷新在实例化vue的created生命周期函数之前初始化路由即可

步骤

1)安装vuex

  1. npm install vuex@3

2)修改登录页Login.vue

  1. <template>
  2. <div class="login-container">
  3. <el-form ref="form" :model="form" label-width="100px" class="login-form">
  4. <h1 style="margin-bottom: 20px;text-align: center">欢迎登录</h1>
  5. <el-form-item label="用户名">
  6. <el-input v-model="form.username"></el-input>
  7. </el-form-item>
  8. <el-form-item label="密码">
  9. <el-input type="password" v-model="form.password"></el-input>
  10. </el-form-item>
  11. <el-form-item>
  12. <el-button type="primary" @click="onSubmit">登录</el-button>
  13. <el-button>取消</el-button>
  14. </el-form-item>
  15. </el-form>
  16. </div>
  17. </template>
  18. <script>
  19. import {initRoutes} from "@/utils/routesUtil";
  20. import {login,getUser} from "@/utils/http";
  21. export default {
  22. name:'Login',
  23. data() {
  24. return {
  25. form: {
  26. username: '',
  27. password: '',
  28. }
  29. }
  30. },
  31. methods: {
  32. onSubmit() {
  33. login(this.form).then(res=>{
  34. if(res){
  35. //将token和menus保存在vuex中
  36. this.$store.dispatch("UPDATETOKEN",res.data.token);
  37. this.$store.dispatch("UPDATEUSERDATA",res.data.menus)
  38. //登录的时候,初始化菜单放在vuex中,不在登录页进行处理
  39. this.$store.commit('INITROUTES',res.data.menus)
  40. // 以下代码为注释
  41. // let myRoutes = initRoutes(res.data.menus)
  42. // this.$router.options.routes = [myRoutes]
  43. // this.$router.addRoute(myRoutes)
  44. this.$router.replace("/home")
  45. }else{
  46. return false
  47. }
  48. })
  49. },
  50. }
  51. }
  52. </script>
  53. <style scoped>
  54. .login-form {
  55. border: 1px #DCDFE6 solid;
  56. border-radius: 4px;
  57. padding: 40px;
  58. margin: 110px 400px;
  59. box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
  60. width: 400px;
  61. }
  62. .login-container {
  63. /*background: url(../assets/image/login2.jpg) no-repeat;*/
  64. height: 100%;
  65. width: 100%;
  66. overflow: hidden;
  67. background-size: cover;
  68. }
  69. </style>

3)创建store文件夹,创建index.js

  1. import Vuex from 'vuex'
  2. import Vue from "vue";
  3. import {initRoutes} from "@/utils/routesUtil";
  4. import Router from "@/router";
  5. Vue.use(Vuex)
  6. const state = {
  7. token:window.sessionStorage.getItem('token')||'',
  8. userData:window.sessionStorage.getItem('userData')||{},
  9. routes:{}
  10. }
  11. const mutations = {
  12. SETTOKEN(state,token){
  13. window.sessionStorage.setItem('token',token)
  14. state.token = token
  15. },
  16. SETUSERDATA(state,userData){
  17. window.sessionStorage.setItem('userData',JSON.stringify(userData))
  18. state.userData = userData
  19. },
  20. INITROUTES(state,menus){
  21. let myRoutes = initRoutes(menus)
  22. Router.options.routes = [myRoutes]
  23. Router.addRoute(myRoutes);
  24. state.routes = myRoutes
  25. }
  26. }
  27. const actions = {
  28. UPDATETOKEN(context,value){
  29. context.commit('SETTOKEN',value)
  30. },
  31. UPDATEUSERDATA(context,value){
  32. context.commit('SETUSERDATA',value)
  33. }
  34. }
  35. const getters = {
  36. userinfo(state){
  37. return state.userData
  38. },
  39. menus(state){
  40. return state.userData.menus
  41. },
  42. routes(state){
  43. return state.routes.filter(item => {
  44. return item.name==='Home'
  45. })[0].children
  46. }
  47. }
  48. export default new Vuex.Store({
  49. state,
  50. mutations,
  51. actions,
  52. getters
  53. })

4)main.js修改

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. import ElementUI from 'element-ui'
  4. import router from './router'
  5. import 'element-ui/lib/theme-chalk/index.css'
  6. import store from "@/store"
  7. Vue.config.productionTip = false
  8. Vue.use(ElementUI)
  9. //生成路由,由于没有获取菜单接口,所以直接从sessionStorage中直接去userData数据,进行路由的初始化
  10. const init = async ()=>{
  11. if (sessionStorage.getItem('token')){
  12. if (store.state.routes){
  13. await store.commit('INITROUTES',JSON.parse(sessionStorage.getItem('userData')))
  14. }
  15. }
  16. }
  17. //此处await不可缺少,需要等待路由数据先生成,才能进行vue实例的创建,否则会报错
  18. async function call(){
  19. await init();
  20. new Vue({
  21. render: h => h(App),
  22. router,
  23. store
  24. }).$mount('#app')
  25. }
  26. call()

5)如果未登录,则跳转到login页处理,main.js添加如下内容

  1. //路由导航守卫,每次路由地址改变前出发
  2. router.beforeEach((to,from,next)=>{
  3. if (sessionStorage.getItem('token')) {
  4. next();
  5. } else {
  6. //如果是登录页面路径,就直接next()
  7. if (to.path === '/login') {
  8. next();
  9. } else {
  10. if(to.path === '/home'){
  11. next();
  12. }
  13. next('/login');
  14. }
  15. }
  16. })

安装e-icon-picker选择器

原文链接:https://www.cnblogs.com/dalaba/p/17437953.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号