关注公号:java大师,回复“图书”,获取源码
1.1添加pom.xml
- <!--mp逆向工程 -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.4.3.1</version>
- </dependency>
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-generator</artifactId>
- <version>3.1.0</version>
- </dependency>
- <dependency>
- <groupId>org.freemarker</groupId>
- <artifactId>freemarker</artifactId>
- <version>2.3.31</version>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>8.0.28</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.7</version>
- </dependency>
1.2创建CodeGenerator代码生成类
- package com.ds.book.mp;
-
- import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
- import com.baomidou.mybatisplus.core.toolkit.StringPool;
- import com.baomidou.mybatisplus.generator.AutoGenerator;
- import com.baomidou.mybatisplus.generator.InjectionConfig;
- import com.baomidou.mybatisplus.generator.config.*;
- import com.baomidou.mybatisplus.generator.config.po.TableInfo;
- import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
- import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
- import org.apache.commons.lang3.StringUtils;
-
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Scanner;
-
- public class CodeGenerator {
-
- /**
- *
- * 读取控制台内容
- *
- */
- public static String scanner(String tip) {
- Scanner scanner = new Scanner(System.in);
- StringBuilder help = new StringBuilder();
- help.append("请输入" + tip + ":");
- System.out.println(help.toString());
- if (scanner.hasNext()) {
- String ipt = scanner.next();
- if (StringUtils.isNotBlank(ipt)) {
- return ipt;
- }
- }
- throw new MybatisPlusException("请输入正确的" + tip + "!");
- }
-
- public static void main(String[] args) {
- // 代码生成器
- AutoGenerator mpg = new AutoGenerator();
-
- // 全局配置
- GlobalConfig gc = new GlobalConfig();
- String projectPath = System.getProperty("user.dir");
- gc.setOutputDir(projectPath + "/src/main/java");
- gc.setAuthor("java大师");
- gc.setOpen(false);
- // gc.setSwagger2(true); 实体属性 Swagger2 注解
- mpg.setGlobalConfig(gc);
-
- // 数据源配置
- DataSourceConfig dsc = new DataSourceConfig();
- dsc.setUrl("jdbc:mysql://175.24.198.63:3306/book?useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8");
- // dsc.setSchemaName("public");
- dsc.setDriverName("com.mysql.cj.jdbc.Driver");
- dsc.setUsername("root");
- dsc.setPassword("root@1234!@#");
- mpg.setDataSource(dsc);
-
- // 包配置
- PackageConfig pc = new PackageConfig();
- // pc.setModuleName(scanner("模块名"));
- pc.setParent("com.ds.book");
- mpg.setPackageInfo(pc);
-
- // 自定义配置
- InjectionConfig cfg = new InjectionConfig() {
- @Override
- public void initMap() {
- // to do nothing
- }
- };
-
- // 如果模板引擎是 freemarker
- String templatePath = "/templates/mapper.xml.ftl";
- // 如果模板引擎是 velocity
- // String templatePath = "/templates/mapper.xml.vm";
-
- // 自定义输出配置
- List<FileOutConfig> focList = new ArrayList<>();
- // 自定义配置会被优先输出
- focList.add(new FileOutConfig(templatePath) {
- @Override
- public String outputFile(TableInfo tableInfo) {
- // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
- return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
- }
- });
- /*
- cfg.setFileCreate(new IFileCreate() {
- @Override
- public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
- // 判断自定义文件夹是否需要创建
- checkDir("调用默认方法创建的目录,自定义目录用");
- if (fileType == FileType.MAPPER) {
- // 已经生成 mapper 文件判断存在,不想重新生成返回 false
- return !new File(filePath).exists();
- }
- // 允许生成模板文件
- return true;
- }
- });
- */
- cfg.setFileOutConfigList(focList);
- mpg.setCfg(cfg);
-
- // 配置模板
- TemplateConfig templateConfig = new TemplateConfig();
-
- // 配置自定义输出模板
- //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
- // templateConfig.setEntity("templates/entity2.java");
- // templateConfig.setService();
- // templateConfig.setController();
-
- templateConfig.setXml(null);
- mpg.setTemplate(templateConfig);
-
- // 策略配置
- StrategyConfig strategy = new StrategyConfig();
- strategy.setNaming(NamingStrategy.underline_to_camel);
- strategy.setColumnNaming(NamingStrategy.underline_to_camel);
- strategy.setTablePrefix("t_");
- // strategy.setInclude("t_user");
- // strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
- strategy.setEntityLombokModel(true);
- strategy.setRestControllerStyle(true);
- // 公共父类
- // strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
- // 写于父类中的公共字段
- strategy.setSuperEntityColumns("id");
- strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
- strategy.setControllerMappingHyphenStyle(true);
- // strategy.setTablePrefix(pc.getModuleName() + "_");
- mpg.setStrategy(strategy);
- mpg.setTemplateEngine(new FreemarkerTemplateEngine());
- mpg.execute();
- }
-
- }
1.3生成crontroller、service、mapper、entity等业务实体类
运行CodeGenerator,生成业务实体类
请输入表名,多个英文逗号分割: t_user,t_menu,t_role,t_user_role,t_role_menu
2.1整合springsecurity
1)
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
2.2认证授权流程
认证管理
流程图解读:
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
- package com.ds.book.entity;
-
- import com.baomidou.mybatisplus.annotation.TableName;
- import java.io.Serializable;
- import java.util.Collection;
-
- import lombok.Data;
- import lombok.EqualsAndHashCode;
- import lombok.experimental.Accessors;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.userdetails.UserDetails;
-
- /**
- *
- *
- *
- *
- * @author java大师
- * @since 2023-03-17
- */
- @Data
- @EqualsAndHashCode(callSuper = false)
- @Accessors(chain = true)
- @TableName("t_user")
- public class User implements Serializable, UserDetails {
-
- private static final long serialVersionUID = 1L;
-
- private Integer id;
-
- /**
- * 登录名
- */
- private String name;
-
- /**
- * 用户名
- */
- private String username;
-
- /**
- * 密码
- */
- private String password;
-
- /**
- * 是否有效:1-有效;0-无效
- */
- private String status;
-
-
- @Override
- public Collection extends GrantedAuthority> getAuthorities() {
- return roles
- .stream()
- .map(role -> new SimpleGrantedAuthority(role.getRoleCode()))
- .collect(Collectors.toList());
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
- }
2.3.2userDetailService
登录成功后,将UserDetails的roles设置到用户中
- package com.ds.book.service.impl;
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.ds.book.entity.User;
- import com.ds.book.mapper.UserMapper;
- import com.ds.book.service.IUserService;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import org.springframework.stereotype.Service;
-
- /**
- *
- * 服务实现类
- *
- *
- * @author java大师
- * @since 2023-03-17
- */
- @Service
- public class UserServiceImpl extends ServiceImpl
, User> implements IUserService, UserDetailsService { -
- @Autowired
- private UserMapper userMapper;
-
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- User loginUser = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
- if (loginUser == null){
- throw new UsernameNotFoundException("用户名或密码错误");
- }
- loginUser.setRoles(userMapper.getRolesByUserId(loginUser.getId()));
- return loginUser;
- }
- }
2.3.2加载userDetailService
将我们自己的UserDetailService注入springsecurity
- package com.ds.book.config;
-
- import com.ds.book.filter.JwtTokenFilter;
- import com.ds.book.service.impl.UserServiceImpl;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.security.config.annotation.ObjectPostProcessor;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.builders.WebSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.config.http.SessionCreationPolicy;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-
- @Configuration
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
-
- @Autowired
- private UserServiceImpl userService;
-
- @Bean
- public PasswordEncoder passwordEncoder(){
- return new BCryptPasswordEncoder();
- }
-
- //注入我们自己的UserDetailService
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
- }
- }
问题:前后端分离项目,通常不会使用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
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.0</version>
- </dependency>
2.4.3 JwtToken工具类
- package com.ds.book.tool;
-
-
- import io.jsonwebtoken.Claims;
- import io.jsonwebtoken.JwtBuilder;
- import io.jsonwebtoken.Jwts;
- import io.jsonwebtoken.SignatureAlgorithm;
-
- import javax.crypto.SecretKey;
- import javax.crypto.spec.SecretKeySpec;
- import java.util.Base64;
- import java.util.Date;
- import java.util.UUID;
-
- /**
- * JWT工具类
- */
- public class JwtUtil {
-
- //有效期为
- public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时
- //设置秘钥明文
- public static final String JWT_KEY = "dashii";
-
- public static String getUUID(){
- String token = UUID.randomUUID().toString().replaceAll("-", "");
- return token;
- }
-
- /**
- * 生成jtw
- * @param subject token中要存放的数据(json格式)
- * @return
- */
- public static String createJWT(String subject) {
- JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
- return builder.compact();
- }
-
- /**
- * 生成jtw
- * @param subject token中要存放的数据(json格式)
- * @param ttlMillis token超时时间
- * @return
- */
- public static String createJWT(String subject, Long ttlMillis) {
- JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
- return builder.compact();
- }
-
- private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
- SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
- SecretKey secretKey = generalKey();
- long nowMillis = System.currentTimeMillis();
- Date now = new Date(nowMillis);
- if(ttlMillis==null){
- ttlMillis= JwtUtil.JWT_TTL;
- }
- long expMillis = nowMillis + ttlMillis;
- Date expDate = new Date(expMillis);
- return Jwts.builder()
- .setId(uuid) //唯一的ID
- .setSubject(subject) // 主题 可以是JSON数据
- .setIssuer("dashi") // 签发者
- .setIssuedAt(now) // 签发时间
- .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
- .setExpiration(expDate);
- }
-
- /**
- * 创建token
- * @param id
- * @param subject
- * @param ttlMillis
- * @return
- */
- public static String createJWT(String id, String subject, Long ttlMillis) {
- JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
- return builder.compact();
- }
-
- public static void main(String[] args) throws Exception {
- String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
- Claims claims = parseJWT(token);
- System.out.println(claims);
- }
-
- /**
- * 生成加密后的秘钥 secretKey
- * @return
- */
- public static SecretKey generalKey() {
- byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
- SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
- return key;
- }
-
- /**
- * 解析
- *
- * @param jwt
- * @return
- * @throws Exception
- */
- public static Claims parseJWT(String jwt) throws Exception {
- SecretKey secretKey = generalKey();
- return Jwts.parser()
- .setSigningKey(secretKey)
- .parseClaimsJws(jwt)
- .getBody();
- }
- }
2.4.4 JwtTokenFilter
- package com.ds.book.filter;
-
- import com.ds.book.entity.User;
- import com.ds.book.mapper.UserMapper;
- import com.ds.book.service.IMenuService;
- import com.ds.book.service.IUserService;
- import com.ds.book.tool.JwtUtil;
- import io.jsonwebtoken.Claims;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.stereotype.Component;
- import org.springframework.util.StringUtils;
- import org.springframework.web.filter.OncePerRequestFilter;
-
- import javax.servlet.FilterChain;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
-
- @Component
- public class JwtTokenFilter extends OncePerRequestFilter {
-
- @Autowired
- private IUserService userService;
- @Autowired
- private UserMapper userMapper;
-
- @Override
- protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
- //1、获取token
- String token = httpServletRequest.getHeader("token");
- if (StringUtils.isEmpty(token)){
- filterChain.doFilter(httpServletRequest,httpServletResponse);
- return;
- }
- String userId;
- try {
- Claims claims = JwtUtil.parseJWT(token);
- userId = claims.getSubject();
- } catch (Exception exception) {
- exception.printStackTrace();
- throw new RuntimeException("token非法");
- }
- User user = userService.getUserById(Integer.parseInt(userId));
- user.setRoles(userMapper.getRolesByUserId(Integer.parseInt(userId)));
- UsernamePasswordAuthenticationToken authenticationToken =
- new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities());
- SecurityContextHolder.getContext().setAuthentication(authenticationToken);
- filterChain.doFilter(httpServletRequest,httpServletResponse);
- }
- }
在springsecurity中,第一个经过的过滤器是UsernamePasswordAuthenticationFilter,所以前后端分离的项目,我们自己定义的过滤器要放在这个过滤器前面,具体配置如下
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.csrf().disable()
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- .authorizeRequests()
- .antMatchers("/login").permitAll()
- .anyRequest().authenticated();
- http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
- http.cors();
- }
2.4.5授权
2.4.5.1 开启preAuthorize进行收取(Controller路径匹配)
1)主启动类上添加EnableGlobalMethodSecurity注解
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- @SpringBootApplication
- @MapperScan("com.ds.book.mapper")
- public class BookSysApplication {
- public static void main(String[] args) {
- SpringApplication.run(BookSysApplication.class,args);
- }
- }
2)Controller方法上添加@PreAuthorize注解
- @RestController
- public class HelloController {
-
- @GetMapping("/hello")
- @PreAuthorize("hasRole('ROLE_ADMIN')")
- public String hello(){
- return "hello";
- }
- }
2.4.5.2 增强方式授权(数据库表配置)
1)创建我们自己的FilterInvocationSecurityMetadataSource,实现getAttributes方法,获取请求url所需要的角色
- @Component
- public class MySecurtiMetaDataSource implements FilterInvocationSecurityMetadataSource {
-
- @Autowired
- private IMenuService menuService;
- AntPathMatcher antPathMatcher = new AntPathMatcher();
-
- //获取访问url需要的角色,例如:/sys/user需要ROLE_ADMIN角色,访问sys/user时获取到必须要有ROLE_ADMIN角色。返回 Collection<ConfigAttribute>
- @Override
- public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
- String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
- //获取所有的菜单及角色
- List<Menu> menus = menuService.getMenus();
- for (Menu menu : menus) {
- if (antPathMatcher.match(menu.getUrl(),requestURI)){
- String[] roles = menu.getRoles().stream().map(role -> role.getRoleCode()).toArray(String[]::new);
- return SecurityConfig.createList(roles);
- }
- }
- return null;
- }
-
- @Override
- public Collection<ConfigAttribute> getAllConfigAttributes() {
- return null;
- }
-
- @Override
- public boolean supports(Class<?> clazz) {
- return false;
- }
- }
2)创建我们自己的决策管理器AccessDecisionManager,实现decide方法,判断步骤1)中获取到的角色和我们目前登录的角色是否相同,相同则允许访问,不相同则不允许访问,
- @Component
- public class MyAccessDecisionManager implements AccessDecisionManager {
-
- //1、认证通过后,会往authentication中填充用户信息
- //2、拿authentication中的权限与上一步获取到的角色信息进行比对,比对成功后,允许访问
- @Override
- public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
- Collection extends GrantedAuthority> authorities = authentication.getAuthorities();
- for (ConfigAttribute configAttribute : configAttributes) {
- for (GrantedAuthority authority : authorities) {
- if (authority.getAuthority().equals(configAttribute.getAttribute())){
- return;
- }
- }
- }
- throw new AccessDeniedException("权限不足,请联系管理员");
- }
-
- @Override
- public boolean supports(ConfigAttribute attribute) {
- return false;
- }
-
- @Override
- public boolean supports(Class> clazz) {
- return false;
- }
- }
3)在SecurityConfig中,添加后置处理器(增强器),让springsecurity使用我们自己的datametasource和decisionMananger
- @Configuration
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
- @Autowired
- private MySecurtiMetaDataSource mySecurtiMetaDataSource;
- @Autowired
- private MyAccessDecisionManager myAccessDecisionManager;
- @Autowired
- private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
- @Autowired
- private MyAccessDeniedHandler myAccessDeniedHandler;
-
- @Autowired
- private UserServiceImpl userService;
-
- @Autowired
- private JwtTokenFilter jwtTokenFilter;
-
- @Bean
- public PasswordEncoder passwordEncoder(){
- return new BCryptPasswordEncoder();
- }
-
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception {
- auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
- }
-
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.csrf().disable()
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- .authorizeRequests()
- .antMatchers("/login").permitAll()
- .anyRequest().authenticated()
- //后置处理器,使用我们自己的FilterSecurityInterceptor拦截器配置
- .withObjectPostProcessor(new ObjectPostProcessor
() { - @Override
- public
O postProcess(O o) { - o.setSecurityMetadataSource(mySecurtiMetaDataSource);
- o.setAccessDecisionManager(myAccessDecisionManager);
- return o;
- }
- })
- .and()
- .headers().cacheControl();
- http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
- http.cors();
- }
- }
2.4.6异常处理
1)前端渲染工具类
- public class WebUtils
- {
- /**
- * 将字符串渲染到客户端
- *
- * @param response 渲染对象
- * @param string 待渲染的字符串
- * @return null
- */
- public static String renderString(HttpServletResponse response, String string) {
- try
- {
- response.setStatus(200);
- response.setContentType("application/json");
- response.setCharacterEncoding("utf-8");
- response.getWriter().print(string);
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- return null;
- }
- }
2)未登录异常处理,实现commence方法
- @Component
- public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
- @Override
- public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
- Result result = new Result(401,"未登录,请先登录",null);
- String json = JSON.toJSONString(result);
- WebUtils.renderString(httpServletResponse,json);
-
- }
- }
3)授权失败异常处理,实现Handle方法
- @Component
- public class MyAccessDeniedHandler implements AccessDeniedHandler {
- @Override
- public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
- Result result = new Result(403,"权限不足请联系管理员",null);
- String s = JSON.toJSONString(result);
- WebUtils.renderString(httpServletResponse,s);
- }
- }
1)添加pom.xml依赖
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger2</artifactId>
- <version>2.7.0</version>
- </dependency>
- <dependency>
- <groupId>io.springfox</groupId>
- <artifactId>springfox-swagger-ui</artifactId>
- <version>2.7.0</version>
- </dependency>
- <dependency>
- <groupId>com.github.xiaoymin</groupId>
- <artifactId>knife4j-spring-boot-starter</artifactId>
- <version>2.0.7</version>
- </dependency>
2)创建swagger配置文件
- package com.ds.book.config;
-
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import springfox.documentation.builders.ApiInfoBuilder;
- import springfox.documentation.builders.PathSelectors;
- import springfox.documentation.builders.RequestHandlerSelectors;
- import springfox.documentation.service.*;
- import springfox.documentation.spi.DocumentationType;
- import springfox.documentation.spi.service.contexts.SecurityContext;
- import springfox.documentation.spring.web.plugins.Docket;
- import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
- import java.util.ArrayList;
- import java.util.List;
-
- @Configuration
- @EnableSwagger2
- public class Swagger2Config {
- @Bean
- public Docket createRestApi() {
- return new Docket(DocumentationType.SWAGGER_2)
- .pathMapping("/")
- .apiInfo(apiInfo())
- .select()
- //swagger要扫描的包路径
- .apis(RequestHandlerSelectors.basePackage("com.ds.book.controller"))
- .paths(PathSelectors.any())
- .build()
- .securityContexts(securityContexts())
- .securitySchemes(securitySchemes());
- }
-
- private ApiInfo apiInfo() {
- return new ApiInfoBuilder().title("图书管理系统接口文档")
- //作者、路径和邮箱
- .contact(new Contact("java大师","http://localhost:8080/doc.html","fry000@qq.com"))
- .version("1.0").description("图书管理接口文档").build();
- }
-
- private List<SecurityContext> securityContexts() {
- //设置需要登录认证的路径
- List<SecurityContext> result = new ArrayList<>();
- result.add(getContextByPath("/.*"));
- return result;
- }
-
- //通过pathRegex获取SecurityContext对象
- private SecurityContext getContextByPath(String pathRegex) {
- return SecurityContext.builder()
- .securityReferences(defaultAuth())
- .forPaths(PathSelectors.regex(pathRegex))
- .build();
- }
-
- //默认为全局的SecurityReference对象
- private List<SecurityReference> defaultAuth() {
- List<SecurityReference> result = new ArrayList<>();
- AuthorizationScope authorizationScope = new AuthorizationScope("global",
- "accessEverything");
- AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
- authorizationScopes[0] = authorizationScope;
- result.add(new SecurityReference("Authorization", authorizationScopes));
- return result;
- }
-
- private List<ApiKey> securitySchemes() {
- //设置请求头信息
- List<ApiKey> result = new ArrayList<>();
- //设置header中的token
- ApiKey apiKey = new ApiKey("token", "token", "header");
- result.add(apiKey);
- return result;
- }
- }
3)修改SecurityConfig配置类,允许访问swagger的地址
- //主要的配置文件,antMatchers匹配的路径,全部忽略,不进行JwtToken的认证
- @Override
- public void configure(WebSecurity web) throws Exception {
- web.ignoring().antMatchers(
- "/login",
- "/logout",
- "/css/**",
- "/js/**",
- "/index.html",
- "favicon.ico",
- "/doc.html",
- "/webjars/**",
- "/swagger-resources/**",
- "/v2/api-docs/**"
- );
- }
4)编写LoginController接口,通过@Api和@ApiOperation注解使用swagger
- package com.ds.book.controller;
-
- import com.ds.book.entity.Result;
- import com.ds.book.entity.User;
- import com.ds.book.service.IUserService;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.access.SecurityConfig;
- import org.springframework.security.access.prepost.PreAuthorize;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- @Api(tags = "登录")
- public class LoginController {
-
- @Autowired
- private IUserService userService;
-
- @ApiOperation("登录")
- @PostMapping("/login")
- public Result login(@RequestBody User user){
- return userService.login(user);
- }
- }
5)输入地址 http://localhost:8080/doc.html,进入swagger
6)点击登录进入登录接口,点击调试,发送
测试成功!
4.1 登录接口
注意:前后端分离项目,退出的时候,由前端清除浏览器请求header中的token和sessionStorage或者LocalStorage,后端只要返回一个退出成功的消息。
- package com.ds.book.controller;
-
- import com.ds.book.entity.Result;
- import com.ds.book.entity.User;
- import com.ds.book.service.IUserService;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.access.SecurityConfig;
- import org.springframework.security.access.prepost.PreAuthorize;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RestController;
-
- import java.security.Principal;
-
- @RestController
- @Api(tags = "登录")
- public class LoginController {
-
- @Autowired
- private IUserService userService;
- @Autowired
- private UserDetailsService userDetailsService;
-
- @ApiOperation("登录")
- @PostMapping("/login")
- public Result login(@RequestBody User user){
- return userService.login(user);
- }
-
- @ApiOperation("退出")
- @PostMapping("/logout")
- public Result logout(){
- return Result.success("退出成功");
- }
-
- @ApiOperation("获取当前登录用户信息")
- @GetMapping("/user/info")
- public User user(Principal principal){
- if (principal == null){
- return null;
- }
- String username = principal.getName();
- User user = (User)userDetailsService.loadUserByUsername(username);
- user.setPassword(null);
- return user;
- }
-
- }
4.2菜单接口
- package com.ds.book.controller;
-
-
- import com.ds.book.entity.Menu;
- import com.ds.book.entity.Result;
- import com.ds.book.service.IMenuService;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import io.swagger.models.auth.In;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.access.prepost.PreAuthorize;
- import org.springframework.web.bind.annotation.*;
-
- import java.util.List;
-
- /**
- *
- * 前端控制器
- *
- *
- * @author java大师
- * @since 2023-03-09
- */
- @RestController
- @Api(tags = "菜单管理")
- public class MenuController {
-
- @Autowired
- private IMenuService menuService;
-
- @GetMapping("/menus")
- @ApiOperation("获取菜单树")
- public Result getMenus(){
- List
- return Result.success("查询成功",allMenus);
- }
-
- @PostMapping("/menu/add")
- @ApiOperation("添加菜单")
- public Result addMenu(@RequestBody Menu menu){
- return menuService.addMenu(menu);
- }
-
- @PostMapping("/menu/update")
- @ApiOperation("修改菜单")
- public Result updateMenu(@RequestBody Menu menu){
- return menuService.updateMenu(menu);
- }
-
- @PostMapping("/menu/delete/{id}")
- @ApiOperation("删除菜单")
- public Result deleteMenu(@PathVariable Integer id){
- return menuService.deleteMenu(id);
- }
- }
4.3用户接口
- package com.ds.book.controller;
-
-
- import com.ds.book.entity.Result;
- import com.ds.book.entity.User;
- import com.ds.book.service.IUserService;
- import io.swagger.annotations.Api;
- import io.swagger.annotations.ApiOperation;
- import io.swagger.models.auth.In;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.crypto.password.PasswordEncoder;
- import org.springframework.web.bind.annotation.*;
-
- import javax.jws.soap.SOAPBinding;
- import java.util.List;
-
- /**
- *
- * 前端控制器
- *
- *
- * @author java大师
- * @since 2023-03-09
- */
- @RestController
- @Api(tags = "用户管理")
- public class UserController {
- @Autowired
- private IUserService userService;
- @Autowired
- private PasswordEncoder passwordEncoder;
-
- @GetMapping("/users")
- @ApiOperation("查询用户列表")
- public Result getUsers(){
- List
list = userService.getUsers(); - if (list != null){
- return Result.success("查询成功",list);
- }
- return Result.error("查询失败");
- }
-
- @PostMapping("/user/add")
- @ApiOperation("添加用户")
- public Result addUser(@RequestBody User user){
- user.setPassword(passwordEncoder.encode("123456"));
- return userService.addUser(user);
- }
-
- @PostMapping("/user/update")
- @ApiOperation("修改用户")
- public Result updateUser(@RequestBody User user){
- return userService.updateUser(user);
- }
-
- @PostMapping("/user/chooseRole/{userId}/{roleId}")
- @ApiOperation("选择角色")
- public Result chooseRole(@PathVariable Integer userId,@PathVariable Integer roleId){
- return userService.chooseRole(userId,roleId);
- }
-
- @PostMapping("/user/delete/{id}")
- @ApiOperation("删除用户")
- public Result deleteUser(@PathVariable Integer id){
- return userService.deleteUser(id);
- }
- }
4.4角色接口
- package com.ds.book.service.impl;
-
- import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
- import com.ds.book.entity.Menu;
- import com.ds.book.entity.Result;
- import com.ds.book.entity.Role;
- import com.ds.book.entity.RoleMenu;
- import com.ds.book.mapper.RoleMapper;
- import com.ds.book.mapper.RoleMenuMapper;
- import com.ds.book.service.IRoleService;
- import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
-
- import java.util.ArrayList;
- import java.util.List;
-
- /**
- *
- * 服务实现类
- *
- *
- * @author java大师
- * @since 2023-03-09
- */
- @Service
- public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IRoleService {
- @Autowired
- private RoleMapper roleMapper;
- @Autowired
- private RoleMenuMapper roleMenuMapper;
-
- private List<Menu> buildMenuTree(List<Menu> menus, Integer parentId) {
- List<Menu> treeMenus = new ArrayList<>();
- for (Menu menu : menus) {
- if (parentId==0 ? menu.getParentId()==0 : parentId.equals(menu.getParentId())) {
- List<Menu> children = buildMenuTree(menus, menu.getId());
- if (!children.isEmpty()) {
- menu.setChildren(children);
- }
- treeMenus.add(menu);
- }
- }
- return treeMenus;
- }
-
- @Override
- public List getRoles() {
- List<Role> roles = roleMapper.getRoles();
- for (Role role : roles) {
- role.setMenus(buildMenuTree(role.getMenus(),0));
- }
- return roles;
- }
-
- @Override
- public Result chooseMenus(Integer roleId, Integer[] menuIds) {
- try {
- roleMenuMapper.delete(new QueryWrapper<RoleMenu>().eq("role_id",roleId));
- for (Integer menuId : menuIds) {
- RoleMenu roleMenu = new RoleMenu();
- roleMenu.setRoleId(roleId);
- roleMenu.setMenuId(menuId);
- roleMenuMapper.insert(roleMenu);
- }
- return Result.success("添加成功");
- } catch (Exception exception) {
- return Result.error("添加失败");
- }
- }
- }
vue create vue-book
选择Vue2,运行完毕,出现以下画面
执行绿色的命令,出现下列界面代表脚手架创建项目成功
- //命令行安装
- npm i element-ui -S
-
- //main.js使用element-ui
- import Vue from 'vue';
- import ElementUI from 'element-ui';
- import 'element-ui/lib/theme-chalk/index.css';
- import App from './App.vue';
-
- Vue.use(ElementUI);
-
- new Vue({
- el: '#app',
- render: h => h(App)
- });
2.1安装依赖
npm install vue-router@3
2.2创建路由文件
- import Vue from 'vue'
- import VueRouter from "vue-router";
-
- Vue.use(VueRouter)
-
- //配置localhost:8080/跳转为登录页
- const routes =[
- {
- path:'/',
- name:'Login',
- component:() => import('@/pages/Login.vue')
- }
- ]
-
- export default new VueRouter({
- routes
- })
4.1安装json-server
npm install -g json-server
4.2创建mock文件夹,新建db.json
- {
- "posts": [
- {
- "id": 1,
- "title": "json-server",
- "author": "typicode"
- }
- ],
- "users": [
- {
- "id": 1,
- "username": "admin",
- "password": "123"
- }
- ],
- "login":
- {
- "code": 200,
- "message":"返回成功",
- "data": {
- "id": "1237361915165020161",
- "username": "admin",
- "phone": "111111111111",
- "nickName": "javads",
- "realName": "javads",
- "sex": 1,
- "deptId": "1237322421447561216",
- "deptName": "测试部门",
- "status": 1,
- "email": "xxxx@qq.com",
- "token":"ASDSADASDSW121DDSA",
- "menus": [
- {
- "id": "1236916745927790564",
- "title": "系统管理",
- "icon": "el-icon-star-off",
- "path": "/sys",
- "name": "Sys",
- "children": [
- {
- "id": "1236916745927790578",
- "title": "角色管理",
- "icon": "el-icon-s-promotion",
- "path": "/sys/roles",
- "name": "Roles",
- "children": []
- },
- {
- "id": "1236916745927790560",
- "title": "菜单管理",
- "icon": "el-icon-s-tools",
- "path": "/sys/menus",
- "name": "Menus",
- "children": []
- },
- {
- "id": "1236916745927790575",
- "title": "用户管理",
- "icon": "el-icon-s-custom",
- "path": "/sys/users",
- "name": "User",
- "children": []
- }
- ],
- "spread": true,
- "checked": false
- },
- {
- "id": "1236916745927790569",
- "title": "账号管理",
- "icon": "el-icon-s-data",
- "path": "/account",
- "name": "Account",
- "children": []
- }
- ],
- "permissions": [
- "sys:log:delete",
- "sys:user:add",
- "sys:role:update",
- "sys:dept:list"
- ]
- }
- },
- "comments": [
- {
- "id": 1,
- "body": "some comment",
- "postId": 1
- }
- ],
- "profile": {
- "name": "typicode"
- }
- }
4.3修改vue.config.js,json-server的默认端口为3000,将代理服务器的的端口改成3000
- const { defineConfig } = require('@vue/cli-service')
- module.exports = defineConfig({
- transpileDependencies: true,
- lintOnSave:false,
- devServer:{
- proxy:{
- '/api':{
- target:'http://localhost:3000',
- pathRewrite:{'^/api':''},
- ws:true, //不写为true,websocket
- changeOrigin:true //不写为true
- }
- }
- }
- })
4.4修改package.json,在scripts添加以下代码
"mock": "json-server src/mock/db.json --port 3000 --middlewares src/mock/middlewares.js"
4.5 运行json-server,出现以下界面代表运行成功
json-server.cmd --watch db.jso
5.1配置axios请求拦截器,新建utils文件夹,新建api.js,输入以下内容
- import router from '../router'
- import axios from 'axios'
- import {Message} from 'element-ui'
- import {Loading} from 'element-ui'
-
-
- axios.defaults.baseURL = '/api'
-
- //添加遮罩层代码
- let loading;
- let loadingNum = 0;
-
- //弹出遮罩层
- function showLoading(){
- if (loadingNum ===0){
- loading = Loading.service({
- lock:true,
- text:'加载中,请稍后...',
- background:'rgba(255,255,255,0.5)'
- })
- }
- loadingNum++;
- }
-
- //关闭遮罩层
- function hiddenLoading(){
- loadingNum--;
- if (loadingNum <=0){
- loading.close();
- }
-
- }
-
- /**
- * 添加响应拦截器,在浏览器每次发请求之前,token放入http消息头当中
- */
- axios.interceptors.request.use(config =>{
- showLoading();
- if(window.sessionStorage.getItem('token')){
- config.headers.Authorization =window.sessionStorage.getItem('token')
- }
- console.log(config)
- return config
- },error => {
- console.log(error)
- })
-
- /**
- * 添加响应拦截器
- */
- axios.interceptors.response.use(success => {
- hiddenLoading();
- if (success.status && success.status == 200){
- if (success.data.code == 500 || success.data.code == 401 || success.data.code == 403) {
- Message.error({
- offset:200,
- message:success.data.message
- })
- router.replace("/")
- }
- if (success.data.message){
- Message.success({
- offset:200,
- message:success.data.message
- })
- }
- }
- return success.data
- },error => {
- hiddenLoading();
- if (error.response.code == 504 || error.response.code == 404) {
- Message.error({
- message: '服务器跑路了'
- });
- } else if (error.response.status == 403) {
- Message.error({
- message: '权限不足,请联系管理员'
- });
- } else if (error.response.code == 401) {
- Message.error({
- message: '尚未登录,请先登录'
- })
- router.replace('/');
- } else {
- if (error.response.data.message) {
- Message.error({
- message: error.response.data.message
- });
- } else {
- Message.error({
- message: '未知错误'
- });
- }
- }
- return;
- })
-
- export default axios
5.2创建请求接口,新建http.js
- import axios from './api'
-
- export const login = (param) =>{
- return axios.get(`/posts`, {param})
- }
-
-
- export const getUser = () =>{
- return axios.get(`/users`, {})
- }
6.1登录界面
- <template>
- <div class="login-container">
- <el-form ref="form" :model="form" label-width="100px" class="login-form">
- <h1 style="margin-bottom: 20px;text-align: center">欢迎登录</h1>
- <el-form-item label="用户名">
- <el-input v-model="form.username"></el-input>
- </el-form-item>
- <el-form-item label="密码">
- <el-input type="password" v-model="form.password"></el-input>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="onSubmit">登录</el-button>
- <el-button>取消</el-button>
- </el-form-item>
- </el-form>
- </div>
- </template>
- <script>
- import {initRoutes} from "@/utils/routesUtil";
- import {login,getUser} from "@/utils/http";
- export default {
- name:'Login',
- data() {
- return {
- form: {
- username: '',
- password: '',
- }
- }
- },
- methods: {
- onSubmit() {
- login(this.form).then(res=>{
- if(res){
- //浏览器中存储token,以后每次调用后端接口,浏览器都会带入这个token
- window.sessionStorage.setItem("token",res.data.token)
- //初始化路由数据
- let myRoutes = initRoutes(res.data.menus)
- //将路由进行替换并添加到router中
- this.$router.options.routes = [myRoutes]
- this.$router.addRoute(myRoutes)
- this.$router.replace("/home")
- }else{
- return false
- }
- })
- },
- }
- }
- </script>
- <style scoped>
-
- .login-form {
- border: 1px #DCDFE6 solid;
- border-radius: 4px;
- padding: 40px;
- margin: 110px 400px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
- width: 400px;
- }
-
- .login-container {
- /*background: url(../assets/image/login2.jpg) no-repeat;*/
- height: 100%;
- width: 100%;
- overflow: hidden;
- background-size: cover;
- }
- </style>
6.2处理后台请求返回工具类
- export const initTmpRoutes = (menus) => {
- let tmpRoutes = []
- menus.forEach(menu => {
- let {id,title,icon,path,name,children} = menu
- if(children instanceof Array){
- children = initTmpRoutes(children)
- }
- let tmpRoute = {
- path:path,
- meta:{icon:icon,title:title},
- name:name,
- children:children,
- component:children.length?{render(c){return c('router-view')}}:()=>import(`@/pages${path}/${name}.vue`)
- }
- console.log('tmpRoute',tmpRoute.path)
- tmpRoutes.push(tmpRoute)
- })
- return tmpRoutes
- }
-
- export const initRoutes = (menus)=>{
- const homeRoute = {
- path:'/home',
- name:'Home',
- meta:{title:'首页',icon: 'el-icon-star-off'},
- component:() => import('@/pages/Home.vue'),
- }
- homeRoute.children = initTmpRoutes(menus);
- console.log('homeRoute',homeRoute)
- return homeRoute;
- }
6.3首页、导航页和主页
home.vue
- <template>
- <div class="box">
- <el-container style="height: 100%;" direction="vertial">
- <el-aside width="200px">
- <Nav/>
- </el-aside>
- <el-container>
- <el-header class="homeHeader">
- <el-dropdown class="userInfo" @command="handlecommand">
- <span class="el-dropdown-link">
- </span>
- <el-dropdown-menu slot="dropdown">
- <el-dropdown-item command="userInfo">个人中心</el-dropdown-item>
- <el-dropdown-item command="setting">设置</el-dropdown-item>
- <el-dropdown-item command="logout">退出</el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </el-header>
- <el-main>
- <Main/>
- </el-main>
- <el-footer>底部</el-footer>
- </el-container>
- </el-container>
- </div>
- </template>
-
- <script>
- import Nav from "@/components/Nav";
- import Main from "@/components/Main";
- import RecursiveMenu from "@/components/RecursiveMenu";
- export default{
- data(){
- return {
- user:JSON.parse(window.sessionStorage.getItem('user'))
- }
- },
- components:{
- Nav,
- RecursiveMenu,
- Main
- },
- methods:{
- handlecommand(command){
- if(command=='logout'){
- this.$confirm('确定退出?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }).then(()=>{
- logout();
- window.sessionStorage.removeItem('user');
- window.sessionStorage.removeItem('token');
- this.$store.commit('initRoutes',[]);
- this.$router.replace('/');
- }).catch(()=>{
-
- })
- }
- }
- },
- }
-
- </script>
-
- <style>
- #app,
- html,
- body,
- .box,
- .el-container{
- padding: 0px;
- margin: 0px;
- height: 100%;
- }
- .el-header,
- .el-footer {
- background-color: #B3C0D1;
- color: #333;
- text-align: right;
- line-height: 60px;
- }
-
- .el-aside {
- background-color: #545C64;
- color: #333;
- text-align: center;
- line-height: 300px;
- }
-
- .el-main {
- background-color: #E9EEF3;
- color: #333;
- display: flex;
- flex-direction: column;
- }
-
- body>.el-container {
- margin-bottom: 40px;
- }
-
- .homeHeader .userInfo{
- cursor: pointer;
- }
-
- .el-dropdown-link img{
- width: 36px;
- height: 36px;
- border-radius: 18px;
- }
- </style>
Nav.vue
- <template>
- <el-menu router>
- <template v-for="item in routes">
- <el-submenu v-if="item.children.length" :index="item.path">
- <template slot="title">{{ item.meta.title }}</template>
- <recursive-menu :menu="item.children"></recursive-menu>
- </el-submenu>
- <el-menu-item v-else :index="item.path">{{ item.meta.title }}</el-menu-item>
- </template>
- </el-menu>
- </template>
-
- <script>
- import RecursiveMenu from "@/components/RecursiveMenu";
- export default {
- name: 'Nav',
- components:{
- RecursiveMenu
- },
- computed:{
- routes(){
- console.log('Nav routes:',this.$router.options.routes.length)
- // return this.$router.options.routes[1].children;
- return this.$router.options.routes;
- }
- }
- }
- </script>
RecursiveMenu.vue
- <template>
- <div>
- <el-menu router>
- <template v-for="item in menu">
- <el-submenu v-if="item.children.length" :index="item.path">
- <template slot="title">{{ item.meta.title }}</template>
- <recursive-menu :menu="item.children"></recursive-menu>
- </el-submenu>
- <el-menu-item v-else :index="item.path">{{ item.meta.title }}</el-menu-item>
- </template>
- </el-menu>
- </div>
-
- </template>
-
- <script>
- export default {
- name: 'RecursiveMenu',
- props: {
- menu: {
- type: Array,
- required: true
- },
- },
- components: {
- RecursiveMenu: () => import('./RecursiveMenu.vue')
- }
- }
- </script>
可以看到左边的菜单和路由已经展示在浏览器中
注意:这里有一个坑,页面刷新以后,路由中的数据就会丢失,系统菜单会不显示
原因:页面刷新后,页面会重新实例化路由数据,因为是动态路由,所以页面刷新后会将router置为router/index.js配置的原始路由数据,所以匹配路由地址的时候会报错。
解决方法
思路:因为目前login接口返回的时候,直接将菜单数据传回前端,所以我们需要将菜单缓存起来,因为每次页面刷新vuex数据都会重置,所以不适合存储在vuex中,可以将菜单数据存储在sessionStorage中,页面刷新在实例化vue的created生命周期函数之前初始化路由即可
步骤
1)安装vuex
npm install vuex@3
2)修改登录页Login.vue
- <template>
- <div class="login-container">
- <el-form ref="form" :model="form" label-width="100px" class="login-form">
- <h1 style="margin-bottom: 20px;text-align: center">欢迎登录</h1>
- <el-form-item label="用户名">
- <el-input v-model="form.username"></el-input>
- </el-form-item>
- <el-form-item label="密码">
- <el-input type="password" v-model="form.password"></el-input>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="onSubmit">登录</el-button>
- <el-button>取消</el-button>
- </el-form-item>
- </el-form>
- </div>
- </template>
- <script>
- import {initRoutes} from "@/utils/routesUtil";
- import {login,getUser} from "@/utils/http";
- export default {
- name:'Login',
- data() {
- return {
- form: {
- username: '',
- password: '',
- }
- }
- },
- methods: {
- onSubmit() {
- login(this.form).then(res=>{
- if(res){
- //将token和menus保存在vuex中
- this.$store.dispatch("UPDATETOKEN",res.data.token);
- this.$store.dispatch("UPDATEUSERDATA",res.data.menus)
- //登录的时候,初始化菜单放在vuex中,不在登录页进行处理
- this.$store.commit('INITROUTES',res.data.menus)
- // 以下代码为注释
- // let myRoutes = initRoutes(res.data.menus)
- // this.$router.options.routes = [myRoutes]
- // this.$router.addRoute(myRoutes)
- this.$router.replace("/home")
- }else{
- return false
- }
- })
- },
- }
- }
- </script>
- <style scoped>
-
- .login-form {
- border: 1px #DCDFE6 solid;
- border-radius: 4px;
- padding: 40px;
- margin: 110px 400px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
- width: 400px;
- }
-
- .login-container {
- /*background: url(../assets/image/login2.jpg) no-repeat;*/
- height: 100%;
- width: 100%;
- overflow: hidden;
- background-size: cover;
- }
- </style>
3)创建store文件夹,创建index.js
- import Vuex from 'vuex'
- import Vue from "vue";
- import {initRoutes} from "@/utils/routesUtil";
- import Router from "@/router";
- Vue.use(Vuex)
-
- const state = {
- token:window.sessionStorage.getItem('token')||'',
- userData:window.sessionStorage.getItem('userData')||{},
- routes:{}
- }
- const mutations = {
- SETTOKEN(state,token){
- window.sessionStorage.setItem('token',token)
- state.token = token
- },
- SETUSERDATA(state,userData){
- window.sessionStorage.setItem('userData',JSON.stringify(userData))
- state.userData = userData
- },
- INITROUTES(state,menus){
- let myRoutes = initRoutes(menus)
- Router.options.routes = [myRoutes]
- Router.addRoute(myRoutes);
- state.routes = myRoutes
- }
- }
-
- const actions = {
- UPDATETOKEN(context,value){
- context.commit('SETTOKEN',value)
- },
- UPDATEUSERDATA(context,value){
- context.commit('SETUSERDATA',value)
- }
- }
-
-
- const getters = {
- userinfo(state){
- return state.userData
- },
- menus(state){
- return state.userData.menus
- },
- routes(state){
- return state.routes.filter(item => {
- return item.name==='Home'
- })[0].children
- }
- }
-
- export default new Vuex.Store({
- state,
- mutations,
- actions,
- getters
- })
4)main.js修改
- import Vue from 'vue'
- import App from './App.vue'
- import ElementUI from 'element-ui'
- import router from './router'
- import 'element-ui/lib/theme-chalk/index.css'
- import store from "@/store"
-
- Vue.config.productionTip = false
- Vue.use(ElementUI)
-
- //生成路由,由于没有获取菜单接口,所以直接从sessionStorage中直接去userData数据,进行路由的初始化
- const init = async ()=>{
- if (sessionStorage.getItem('token')){
- if (store.state.routes){
- await store.commit('INITROUTES',JSON.parse(sessionStorage.getItem('userData')))
- }
- }
- }
-
- //此处await不可缺少,需要等待路由数据先生成,才能进行vue实例的创建,否则会报错
- async function call(){
- await init();
- new Vue({
- render: h => h(App),
- router,
- store
- }).$mount('#app')
- }
- call()
5)如果未登录,则跳转到login页处理,main.js添加如下内容
- //路由导航守卫,每次路由地址改变前出发
- router.beforeEach((to,from,next)=>{
- if (sessionStorage.getItem('token')) {
- next();
- } else {
- //如果是登录页面路径,就直接next()
- if (to.path === '/login') {
- next();
- } else {
- if(to.path === '/home'){
- next();
- }
- next('/login');
- }
- }
- })
安装e-icon-picker选择器