<!--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>
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 {
/**
* <p>
* 讀取控制檯內容
* </p>
*/
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();
}
}
執行CodeGenerator,生成業務實體類
請輸入表名,多個英文逗號分割: t_user,t_menu,t_role,t_user_role,t_role_menu
1)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
認證管理
流程圖解讀:
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頁面、自定義頁面。
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;
/**
* <p>
*
* </p>
*
* @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;
}
}
登入成功後,將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;
/**
* <p>
* 服務實現類
* </p>
*
* @author java大師
* @since 2023-03-17
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, 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;
}
}
將我們自己的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.
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 //這裡是金鑰,只要夠複雜,一般不會被破解
)
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
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();
}
}
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();
}
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";
}
}
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<FilterSecurityInterceptor> () {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(mySecurtiMetaDataSource);
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
})
.and()
.headers().cacheControl();
http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
http.cors();
}
}
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","[email protected]"))
.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)點選登入進入登入介面,點選偵錯,傳送
測試成功!
注意:前後端分離專案,退出的時候,由前端清除瀏覽器請求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;
}
}
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;
/**
* <p>
* 前端控制器
* </p>
*
* @author java大師
* @since 2023-03-09
*/
@RestController
@Api(tags = "選單管理")
public class MenuController {
@Autowired
private IMenuService menuService;
@GetMapping("/menus")
@ApiOperation("獲取選單樹")
public Result getMenus(){
List<Menu> allMenus = menuService.getMenuTree();
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);
}
}
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;
/**
* <p>
* 前端控制器
* </p>
*
* @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<User> 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);
}
}
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;
/**
* <p>
* 服務實現類
* </p>
*
* @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": "[email protected]",
"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選擇器