企业项目实训

编程入门 行业动态 更新时间:2024-10-26 16:22:08

企业项目<a href=https://www.elefans.com/category/jswz/34/1769369.html style=实训"/>

企业项目实训

续接上篇

git托管代码

由于我是项目经理,所以我要负责与组员沟通并且选定技术框架和构建代码的雏形,将代码构建好后通过华为云的代码托管进行更新迭代,包括前后端,后面的详细实现代码就是自己慢慢实现的代码。

前端代码托管上传:

后端代码托管上传:

 

还有就是仪表盘的一些数据:

详细实现代码–后端

由于我们使用了前后端分离技术,并且使用了springsecurity作为安全框架进行处理数据,那么就需要重写springsecurity中的很多接口并且需要jwt作为一个认证的标识,例如AuthenticationEntryPoint,BasicAuthenticationFilter,AuthenticationSuccessHandler等接口,接下来我就已经实现的这些接口来分析一下该项目中详细的使用代码。

项目配置文件application-dev.yml

server:port: 8866servlet:context-path: /
​
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver
#    url: jdbc:mysql://localhost:3306/institutions?serverTimezone=Asia/Shanghai
#    username: root
#    password: 123456url: jdbc:mysql://124.71.79.129:3306/institutions?serverTimezone=Asia/Shanghaiusername: institutionspassword: 123456
​redis: # redis配置host: 127.0.0.1 # IPport: 6379  # 端口password:  # 密码connect-timeout: 10s  # 连接超时时间lettuce: # lettuce redis客户端配置pool: # 连接池配置max-active: 8  # 连接池最大连接数(使用负值表示没有限制) 默认 8max-wait: 200s  # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1max-idle: 8 # 连接池中的最大空闲连接 默认 8min-idle: 0 # 连接池中的最小空闲连接 默认 0
​
mybatis-plus:global-config:db-config:id-type: autoconfiguration:map-underscore-to-camel-case: trueauto-mapping-behavior: fulllog-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath:mapper/*.xml
​
avatarImagesFilePath: /Users/zheng/Desktop/大三下学期/企业项目实训/permissionImages/

下列是配置文件的一些说明,该 yaml 配置文件主要包含以下配置项:

  1. server:Web 服务相关配置,设置端口号和上下文路径。

    server:port: 8866servlet:context-path: /
  2. spring.datasource:数据源相关配置,包括数据库类型、驱动类、数据库连接地址、用户名和密码。

    spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://124.71.79.129:3306/institutions?serverTimezone=Asia/Shanghaiusername: institutionspassword: 123456
  3. spring.redis:Redis 缓存相关配置,包括 Redis 的 IP 地址、端口号、连接超时时间、密码等信息。

    spring:redis:host: 127.0.0.1port: 6379password: 123456connect-timeout: 10slettuce:pool:max-active: 8max-wait: 200smax-idle: 8min-idle: 0
  4. mybatis-plus:MyBatis Plus 相关配置,包括全局配置、日志、自动映射、ID 生成策略等信息。

    mybatis-plus:global-config:db-config:id-type: autoconfiguration:map-underscore-to-camel-case: trueauto-mapping-behavior: fulllog-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath:mapper/*.xml
  5. avatarImagesFilePath:自定义的文件路径,用于存储头像图片。

    avatarImagesFilePath: /zzc/企业项目实训/permissionImages/

完善SpringSecurity配置类

package com.chun.permission.config;
​
import com.chun.permissionmon.security.*;
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.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
​
/*** spring security配置* @author zheng*/
@Configuration
//@EnableWebSecurity注解的作用是启用Spring Security的Web安全功能
@EnableWebSecurity
// @EnableGlobalMethodSecurity注解可以启用方法级安全功能,并配置使用哪种安全策略
// prePostEnabled = true是@EnableGlobalMethodSecurity注解的一个属性
// 它用于启用方法级安全中的PreAuthorize和PostAuthorize注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
​@Autowiredprivate LoginFailureHandler loginFailureHandler;@Autowiredprivate LoginSuccessHandler loginSuccessHandler;
//
//    @Autowired
//    private CaptchaFilter captchaFilter;
//@Autowiredprivate JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
//
//    @Autowired
//    private JwtAccessDeniedHandler jwtAccessDeniedHandler;
//@Autowiredprivate JwtLogoutSuccessHandler jwtLogoutSuccessHandler;
​@Autowiredprivate MyUserDetailsServiceImpl myUserDetailService;
​/*** 默认的密码加密配置,不然会报错(必须配置)* @return*/@BeanBCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}
​@BeanJwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {JwtAuthenticationFilter jwtAuthenticationFilter =new JwtAuthenticationFilter(authenticationManager());return jwtAuthenticationFilter;}
​// 请求白名单private static final String URL_WHITELIST[]={"/login","/logout","/captcha","/password","/image/**","/swagger-ui.html","/v2/**","/swagger-resources/**","/webjars/**","/**"   //放行};
​@Overrideprotected void configure(HttpSecurity http) throws Exception {// 开启跨域 以及csrf攻击,关闭http.cors() // 跨域.and().csrf().disable() // csrf攻击关闭
​// 登录配置.formLogin().successHandler(loginSuccessHandler) // 成功处理.failureHandler(loginFailureHandler) // 失败处理
​
​// 登出配置.and().logout() // 登出.logoutSuccessHandler(jwtLogoutSuccessHandler) // 登出成功的处理
​
​// 禁用session(前后端分离,所以禁用).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 改为无状态,即前后端分离
​// 配置拦截规则(放行一些请求).and().authorizeRequests().antMatchers(URL_WHITELIST).permitAll() // 放行URL_WHITELIST中的所有路径.anyRequest().authenticated() // 除了放行中的路径 其他都要进行校验
​// 异常处理配置.and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
//                .accessDeniedHandler();
​
​// 配置自定义过滤器(例如:验证码).and().addFilter(jwtAuthenticationFilter());
//                .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
//        ;}
​@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 配置好userDetailService后在此处使用即可。auth.userDetailsService(myUserDetailService);
//        super.configure(auth);}
}

JWT认证失败处理类

下列代码是一个 JWT 认证失败处理类,实现了 Spring Security 的 AuthenticationEntryPoint 接口。当用户无法通过 JWT 认证时,将会调用 commence() 方法来返回错误信息。

具体来说,该类在整个系统中的作用是:在用户进行请求时,如果由于认证失败(如未提供令牌、令牌过期、令牌无效等)而导致无法访问受保护的资源时,会自动调用该类中的 commence() 方法,在响应中返回认证失败的状态码和消息。其中,认证失败的状态码为 401(HttpServletResponse.SC_UNAUTHORIZED),消息内容为“认证失败,请登录”。

具体实现中,首先设置了响应头的编码格式为 UTF-8,然后使用 JSONUtil 工具类将错误信息封装成 JSON 格式并输出到客户端。这样,当用户请求被拦截时,就会向客户端返回认证失败的信息,提醒用户重新进行身份验证或者登录操作。

package com.chun.permissionmon.security;
​
import cn.hutool.json.JSONUtil;
import com.chun.permission.utils.R;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
​
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
​
/*** @Author: zzc* @Date: 2023/4/19-11:16* @Description: jwt认证失败处理**/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {// 返回成功信息,根据用户名生成token返回给前端// 设置请求头编码格式 ContentTypehttpServletResponse.setContentType("application/json;charset=UTF-8");// 异步返回数据ServletOutputStream outputStream = httpServletResponse.getOutputStream();
​// 返回错误信息,因为有中文 所以最好设置一下编码格式为UTF-8outputStream.write(JSONUtil.toJsonStr(R.error(HttpServletResponse.SC_UNAUTHORIZED, "认证失败,请登录")).getBytes("UTF-8"));outputStream.flush();outputStream.close();}
}

自定义JWT认证过滤流

下列代码是一个自定义的 JWT 认证过滤器,用于拦截客户端请求并进行身份验证。该过滤器继承自 Spring Security 的 BasicAuthenticationFilter 类,并重写了其中的 doFilterInternal() 方法。

具体来说,该类在整个系统中的作用是:当用户进行请求时,会自动调用该类中的 doFilterInternal() 方法,在其中对请求进行拦截和处理。首先,获取请求头中的 JWT 令牌,然后检查请求地址是否在白名单之内。如果令牌为空或者请求地址在白名单中,则直接放行;否则,使用 JwtUtils 工具类对令牌进行解析,并根据解析出来的用户名查询数据库中的用户信息。随后,将用户信息封装为 UsernamePasswordAuthenticationToken 对象,并将其设置为当前安全上下文的认证对象,最后将请求传递给过滤器链的下一个过滤器。

需要注意的是,该类中还包括一个请求白名单数组 URL_WHITELIST[],用于存储不需要进行身份验证的请求地址。默认情况下,该数组包括登录、登出、验证码等几个请求地址。如果请求地址在白名单之内,则不会进行身份验证,直接放行。

该自定义 JWT 认证过滤器是整个系统中用于保护受限资源的一道安全屏障,用于对客户端请求进行认证和授权。

package com.chun.permissionmon.security;
​
import com.chun.permissionmon.constant.JwtConstant;
import com.chun.permission.entity.SysUser;
import com.chun.permission.service.SysUserService;
import com.chun.permission.utils.CheckResult;
import com.chun.permission.utils.JwtUtils;
import com.chun.permission.utils.StringUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
​
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
​
/*** @Author: zzc* @Date: 2023/4/18-22:55* @Description: 自定义jwt认证过滤流**/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {@Autowiredprivate SysUserService sysUserService;
​@Autowiredprivate MyUserDetailsServiceImpl myUserDetailsService;
​// 请求白名单private static final String URL_WHITELIST[] = {"/login","/logout","/captcha","/password","/image/**",};
​public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}
​@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {// 先获取tokenString token = request.getHeader("token");// 再获取请求地址
//        System.out.println("请求url:" + request.getRequestURI());// 如果token是空的 后者 url在白名单里面的则放行if(StringUtil.isEmpty(token) || new ArrayList<String>(Arrays.asList(URL_WHITELIST)).contains(request.getRequestURI())){chain.doFilter(request, response);return ;}CheckResult checkResult = JwtUtils.validateJWT(token);if(!checkResult.isSuccess()){switch(checkResult.getErrCode()){case JwtConstant.JWT_ERRCODE_NULL : throw new JwtException("Token不存在");case JwtConstant.JWT_ERRCODE_FAIL : throw new JwtException("Token验证不通过");case JwtConstant.JWT_ERRCODE_EXPIRE : throw new JwtException("Token过期");
​}}Claims claims = JwtUtils.parseJWT(token);String username = claims.getSubject(); // 获取用户名
​SysUser sysUser = sysUserService.getByUsername(username);
​UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(username,null, myUserDetailsService.getUserAuthority(sysUser.getId()));
​SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);chain.doFilter(request, response);}
}

下列代码是在自定义JWT认证过滤流使用的一个工具类,它是一个用于对 JWT 进行加密和解密的工具类,包含了一些常用的 JWT 操作方法。该类中提供了创建 JWT、生成 JWT Token、验证 JWT 以及解析 JWT 的方法。

具体来说,该类在整个系统中的作用是:在用户进行身份认证时,会使用该工具类对身份信息进行加密并生成 JWT Token,将其作为请求头发送给服务端;在服务端对请求进行处理时,会使用该工具类对 JWT Token 进行验证和解析,从中提取出用户身份信息,并检查其是否合法。具体实现中,该工具类使用了 io.jsonwebtoken 包中的相关类和方法,包含了签发 JWT、生成加密 Key、解析 JWT 等常用操作。其中,在签发 JWT 时,可以设置 JWT 中的 ID、用户名(Subject)、过期时间等参数,并使用 AES256 加密算法对其进行加密。在验证 JWT 时,则需要进行异常捕获,判断 JWT 是否过期或者签名是否正确,并将其解码为 Claims 对象返回。在解析 JWT 时,则需要提供相应的密钥 SecretKey 来进行解密。

该 JWT 工具类是整个系统中用于进行身份验证和授权的核心工具,通过对用户身份进行加密和解密,保证了用户身份信息的安全性和可靠性。

下面是JwtUtils工具类的详细代码:

package com.chun.permission.utils;
​
import com.chun.permissionmon.constant.JwtConstant;
import com.chun.permission.utils.CheckResult;
import io.jsonwebtoken.*;
import org.bouncycastle.util.encoders.Base64;
​
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
​
/*** jwt加密和解密的工具类*/
public class JwtUtils {
​/*** 签发JWT* @param id* @param subject 可以是JSON数据 尽可能少* @param ttlMillis* @return*/public static String createJWT(String id, String subject, long ttlMillis) {SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);// 生成加密的keySecretKey secretKey = generalKey();JwtBuilder builder = Jwts.builder().setId(id).setSubject(subject)   // 主题 ==> 相当于用户名.setIssuer("Zheng")     // 签发者.setIssuedAt(now)      // 签发时间.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙if (ttlMillis >= 0) {long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);builder.setExpiration(expDate); // 过期时间}return builderpact();}
​/*** 生成jwt token* @param username* @return*/public static String getJwtToken(String username){return createJWT(username,username,60*60*1000);}
​/*** 验证JWT* @param jwtStr* @return*/public static CheckResult validateJWT(String jwtStr) {CheckResult checkResult = new CheckResult();Claims claims = null;try {claims = parseJWT(jwtStr);checkResult.setSuccess(true);checkResult.setClaims(claims);} catch (ExpiredJwtException e) {checkResult.setErrCode(JwtConstant.JWT_ERRCODE_EXPIRE);checkResult.setSuccess(false);} catch (SignatureException e) {checkResult.setErrCode(JwtConstant.JWT_ERRCODE_FAIL);checkResult.setSuccess(false);} catch (Exception e) {checkResult.setErrCode(JwtConstant.JWT_ERRCODE_FAIL);checkResult.setSuccess(false);}return checkResult;}
​/*** 生成加密Key* @return*/public static SecretKey generalKey() {byte[] encodedKey = Base64.decode(JwtConstant.JWT_SECERT);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}
​
​/*** 解析JWT字符串* @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}
​public static void main(String[] args) throws InterruptedException {//小明失效 10sString sc = createJWT("1","admin", 60 * 60 * 1000);System.out.println(sc);System.out.println(validateJWT(sc).getErrCode());System.out.println(validateJWT(sc).getClaims().getId());System.out.println(validateJWT(sc).getClaims().getSubject());Thread.sleep(3000);System.out.println("===========================================");System.out.println(validateJWT(sc).getClaims());Claims claims = validateJWT(sc).getClaims();String sc2 = createJWT(claims.getId(),claims.getSubject(), JwtConstant.JWT_TTL);System.out.println(sc2);}
}

SpringSecurity登录成功处理器

下列代码是一个用于处理 Spring Security 登录成功后的操作处理器。在用户登录成功后,该处理器会获取当前用户信息、生成 JWT Token、更新用户最后登录记录以及获取用户权限菜单等操作,并将这些信息封装在响应体中返回给客户端。

具体来说,该类在整个系统中的作用是:在用户登录成功之后,Spring Security 将调用该类中的 onAuthenticationSuccess() 方法,在其中进行一些必要的操作并返回响应数据。首先,获取到当前用户的用户名,并使用 JwtUtils 工具类生成 JWT Token;随后,获取当前用户信息,并通过 SysUserService 更新用户的最后登录时间。接着,根据当前用户的角色信息,查询其对应的权限菜单,并通过 sysMenuService.buildTreeMenu() 方法将其转化为菜单树结构。最终,将以上信息封装在响应体中,并将其以 JSON 格式返回给客户端。

该登录成功处理器是整个系统中用于返回用户信息和权限菜单的核心组件,通过将这些信息发送给客户端,实现了用户登录成功后相关操作的统一处理。

package com.chun.permissionmon.security;
​
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.chun.permission.entity.SysMenu;
import com.chun.permission.entity.SysRole;
import com.chun.permission.entity.SysUser;
import com.chun.permission.service.SysMenuService;
import com.chun.permission.service.SysRoleService;
import com.chun.permission.service.SysUserService;
import com.chun.permission.utils.JwtUtils;
import com.chun.permission.utils.R;
import com.chun.permission.utils.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
​
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
​
/*** @Author: zzc* @Date: 2023/4/18-21:07* @Description: SpringSecurity登录成功处理器**/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {@Autowiredprivate SysUserService sysUserService;@Autowiredprivate SysRoleService sysRoleService;@Autowiredprivate SysMenuService sysMenuService;@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {// 返回成功信息,根据用户名生成token返回给前端// 设置请求头编码格式 ContentTypehttpServletResponse.setContentType("application/json;charset=UTF-8");// 异步返回数据ServletOutputStream outputStream = httpServletResponse.getOutputStream();// 数据先写死
//        String username = "user";// 现在可以通过authentication.getName获取String username = authentication.getName();// 获取当前用户SysUser currentUser = sysUserService.getByUsername(username);
​// 更新用户最后登录记录sysUserService.update(new UpdateWrapper<SysUser>().set("login_date",new Date()).eq("username",username));
​// 获取用户的权限菜单// 根据用户id获取所有角色信息List<SysRole> roleList = sysRoleService.list(new QueryWrapper<SysRole>().inSql("id", "SELECT role_id FROM sys_user_role WHERE user_id =" + currentUser.getId()));// 获取用户对应的角色信息并且加入到currentUse中currentUser.setRoles(roleList.stream().map(SysRole::getName).collect(Collectors. joining(",")));
​// 遍历所有的角色,获取所有菜单权限,而且不重复Set<SysMenu> menuSet = new HashSet<>();for (SysRole sysRole : roleList) {List<SysMenu> sysMenuList = sysMenuService.list(new QueryWrapper<SysMenu>().inSql("id", "SELECT menu_id FROM sys_role_menu WHERE role_id =" + sysRole.getId()));for(SysMenu sysMenu : sysMenuList){menuSet.add(sysMenu);}}List<SysMenu> sysMenuList = new ArrayList<>(menuSet);
​// 排序sysMenuList.sort(Comparatorparing(SysMenu::getOrderNum));// 转菜单树List<SysMenu> menuList = sysMenuService.buildTreeMenu(sysMenuList);
//        System.out.println(menuList);
​// 发送数据,将String数据转换为json格式并且发送,校验:authorizationoutputStream.write(JSONUtil.toJsonStr(R.ok("登录成功").put("authorization", token).put("current", currentUser).put("menuList", menuList)).getBytes("UTF-8"));outputStream.flush();outputStream.close();}
}

SpringSecurity登录失败处理器

下列代码是一个用于处理 Spring Security 登录失败后的操作处理器。在用户登录失败后,该处理器会获取错误信息,并将其封装为响应体返回给客户端。

具体来说,该类在整个系统中的作用是:在用户登录失败之后,Spring Security 将调用该类中的 onAuthenticationFailure() 方法,在其中获取异常信息,并将其封装为响应体返回给客户端。如果异常类型是 BadCredentialsException(用户名或密码错误),则将错误信息设置为“用户名或者密码错误”;否则,直接将异常信息返回。

该登录失败处理器是整个系统中用于返回错误信息的核心组件,通过将错误信息发送给客户端,实现了用户登录失败后相关操作的统一处理,提高了系统的安全性和可靠性。

package com.chun.permissionmon.security;
​
import cn.hutool.json.JSONUtil;
import com.chun.permission.utils.JwtUtils;
import com.chun.permission.utils.R;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
​
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
​
/*** @Author: zzc* @Date: 2023/4/18-21:16* @Description: SpringSecurity登录失败处理器**/
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {// 返回成功信息,根据用户名生成token返回给前端// 设置请求头编码格式 ContentTypehttpServletResponse.setContentType("application/json;charset=UTF-8");// 异步返回数据ServletOutputStream outputStream = httpServletResponse.getOutputStream();// 返回报错信息String message = e.getMessage();// 严谨一些,判断错误类型if(e instanceof BadCredentialsException){message = "用户名或者密码错误";}// 返回错误信息,因为有中文 所以最好设置一下编码格式为UTF-8outputStream.write(JSONUtil.toJsonStr(R.error(message)).getBytes("UTF-8"));outputStream.flush();outputStream.close();}
}

用户logout登出自定义处理

下列代码是一个用于处理 Spring Security 登出操作的处理器。在用户完成登出操作后,该处理器会生成相应的响应信息,并将其封装在响应体中返回给客户端。

具体来说,该类在整个系统中的作用是:在用户完成登出操作之后,Spring Security 将调用该类中的 onLogoutSuccess() 方法,在其中创建成功响应体并将其发送给客户端。该处理器只需要返回“登出成功”信息即可。

该登出成功处理器是整个系统中用于返回登出成功信息的核心组件,通过将登出信息发送给客户端,实现了用户登出操作的统一处理,提高了系统的安全性和可靠性。

package com.chun.permissionmon.security;
​
import cn.hutool.json.JSONUtil;
import com.chun.permission.utils.R;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
​
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
​
/*** @Author: zzc* @Date: 2023/4/19-11:26* @Description: logout登出自定义处理**/
@Component
public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {// 返回成功信息,根据用户名生成token返回给前端// 设置请求头编码格式 ContentTypehttpServletResponse.setContentType("application/json;charset=UTF-8");// 异步返回数据ServletOutputStream outputStream = httpServletResponse.getOutputStream();// 返回登出信息,因为有中文 所以最好设置一下编码格式为UTF-8outputStream.write(JSONUtil.toJsonStr(R.ok("登出成功!")).getBytes("UTF-8"));outputStream.flush();outputStream.close();}
}

用户详情服务实现类

该代码是一个用于处理 Spring Security 用户认证和授权的服务类。在用户进行认证和授权时,该类会查询数据库获取用户信息、角色信息和菜单权限信息,并将其封装为 UserDetails 对象返回给 Spring Security。

具体来说,该类在整个系统中的作用是:在用户进行认证和授权时,Spring Security 将调用该类中的 loadUserByUsername() 方法,在其中查询并获取到当前用户信息及其对应的权限信息,随后将这些信息封装为 UserDetails 对象返回给 Spring Security。该方法先根据用户名从数据库中查询到相应的用户信息,并判断该用户是否存在或账号是否被封禁,然后根据用户 ID 从数据库中获取该用户的权限信息,并使用 AuthorityUtils 工具类将其转换成 List<GrantedAuthority> 类型的对象。最终,将用户名、密码和权限信息作为参数创建一个 User 对象并返回。

该服务类提供了用户认证和授权相关的核心功能,是整个系统中必不可少的组件。

package com.chun.permissionmon.security;
​
import com.chun.permission.entity.SysUser;
import com.chun.permissionmon.exception.UserCountLockException;
import com.chun.permission.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
​
import java.util.List;
​
/*** @Author: zzc* @Date: 2023/4/18-22:26* @Description: 是SpringSecurity提供的 我们只需要这样子写即可**/
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;
​// 查询用户的方法(查询数据库)@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser sysUser = sysUserService.getByUsername(username);if(sysUser == null){// 等于空说明用户不存在throw new UsernameNotFoundException("用户名或者密码错误!");} else if("1".equals(sysUser.getStatus())){throw new UserCountLockException("该用户账号被封禁,具体请联系管理员");}// import org.springframework.security.core.userdetails.User;return new User(sysUser.getUsername(), sysUser.getPassword(), getUserAuthority(sysUser.getId()));}
​/*** 获取用户权限信息 包括角色 菜单权限信息* @param userId* @return*/public List<GrantedAuthority> getUserAuthority(Long userId){// 格式ROLE_admin,ROLE_common,system:user:resetPwd,system:role:delete,system:user:list,system:menu:query,system:menu:list,system:menu:add,system:user:delete,system:role:list,system:role:menu,system:user:edit,system:user:query,system:role:edit,system:user:add,system:user:role,system:menu:delete,system:role:add,system:role:query,system:menu:editString authority = sysUserService.getUserAuthorityInfo(userId);return AuthorityUtilsmaSeparatedStringToAuthorityList(authority);}
}

详细实现代码-前端

因为前端有80%的代码都是本人书写的,在此技术博客我就只写我认为的在编写代码的过程中遇到的难题,下列顺序不分先后。

日期格式化

export function formatDate(val) {var date = new Date(Number(val)); //时间戳为10位需*1000,时间戳为13位的话不需乘1000var Y = date.getFullYear() + "-";var M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "-";var D = date.getDate() + " ";var h = date.getHours() + ":";var m = date.getMinutes() + ":";var s = (date.getSeconds() < 10 ? "0" + (date.getSeconds()) : date.getSeconds());return Y + M + D + h + m + s;
}

上述formatDate.js定义了一个 JavaScript 函数 formatDate,用于将时间戳转换为指定格式的日期字符串。主要作用如下:

  1. 通过 new Date() 方法将时间戳转换为 Date 对象。

    var date = new Date(Number(val));
  2. 获取年份、月份、日期、小时数、分钟数和秒数,并进行格式化。

    var Y = date.getFullYear() + "-";
    var M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "-";
    var D = date.getDate() + " ";
    var h = date.getHours() + ":";
    var m = date.getMinutes() + ":";
    var s = (date.getSeconds() < 10 ? "0" + (date.getSeconds()) : date.getSeconds());
  3. 将格式化后的年份、月份、日期、小时数、分钟数和秒数拼接成完整的日期字符串,并返回。

    return Y + M + D + h + m + s;

在整个系统中,该函数可以被其他 JavaScript 模块引用,用于格式化时间戳并显示日期。例如在前端页面中,后台返回的时间戳需要使用该函数进行格式化,以便用户能够直观地看到日期和时间,并提高用户体验。

私密数据加密解密

import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
// 密钥对生成 
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +'7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +'UP8iWi1Qw0Y='
// 加密
export function encrypt(txt) {const encryptor = new JSEncrypt()encryptor.setPublicKey(publicKey) // 设置公钥return encryptor.encrypt(txt) // 对数据进行加密
}
// 解密
export function decrypt(txt) {const encryptor = new JSEncrypt()encryptor.setPrivateKey(privateKey) // 设置私钥return encryptor.decrypt(txt) // 对数据进行解密
}

该代码主要是使用 jsencrypt 库提供的加密和解密方法,用于在前后端之间进行数据传递时对敏感数据进行加密和解密。主要作用如下:

  1. 声明了公钥和私钥变量。

    const publicKey = '...';
    ​
    const privateKey = '...';
  2. 导出了加密和解密函数。

    export function encrypt(txt) {// 创建 JSEncrypt 实例const encryptor = new JSEncrypt()// 设置公钥encryptor.setPublicKey(publicKey)// 对数据进行加密return encryptor.encrypt(txt)
    }
    ​
    export function decrypt(txt) {// 创建 JSEncrypt 实例const encryptor = new JSEncrypt()// 设置私钥encryptor.setPrivateKey(privateKey)// 对数据进行解密return encryptor.decrypt(txt)
    }
  3. encrypt 函数中,创建了 JSEncrypt 实例,并调用 setPublicKey 方法设置公钥,最后使用 encrypt 方法对数据进行加密,并返回加密后的结果。

  4. decrypt 函数中,创建了 JSEncrypt 实例,并调用 setPrivateKey 方法设置私钥,最后使用 decrypt 方法对数据进行解密,并返回解密后的结果。

在整个系统中,该代码可以被其他 JavaScript 模块引入并使用,用于在前后端之间进行安全的数据传输。通过将敏感数据进行加密,可以有效防止数据在传输过程中被窃取或篡改,提高系统的安全性和可靠性。

axios封装交互

// 引入axios
import axios from 'axios';
import store from '@/store'
​
// let baseUrl="http://localhost:8866/";
let baseUrl="http://124.71.79.129:8866/";
​
// 创建axios实例
const httpService = axios.create({// url前缀-'http:xxx.xxx'// baseURL: process.env.BASE_API, // 需自定义baseURL: baseUrl,// 请求超时时间timeout: 3000 // 需自定义
});
​
// 添加请求和响应拦截器
// 添加请求拦截器
httpService.interceptors.request.use(function (config) {// 在发送请求之前做些什么//config.headers.token=window.sessionStorage.getItem('token');config.headers.token = store.getters.GET_TOKENreturn config;
}, function (error) {// 对请求错误做些什么return Promise.reject(error);
});
​
// 添加响应拦截器
httpService.interceptors.response.use(function (response) {// 对响应数据做点什么...return response;
}, function (error) {// 对响应错误做点什么return Promise.reject(error);
});
/*网络请求部分*/
/**  get请求*  url:请求地址*  params:参数* */
export function get(url, params = {}) {return new Promise((resolve, reject) => {httpService({url: url,method: 'get',params: params}).then(response => {resolve(response);}).catch(error => {reject(error);});});
}
/**  post请求*  url:请求地址*  params:参数* */
export function post(url, params = {}) {return new Promise((resolve, reject) => {httpService({url: url,method: 'post',data: params}).then(response => {console.log(response)resolve(response);}).catch(error => {console.log(error)reject(error);});});
}
/**  文件上传*  url:请求地址*  params:参数* */
export function fileUpload(url, params = {}) {return new Promise((resolve, reject) => {httpService({url: url,method: 'post',data: params,headers: { 'Content-Type': 'multipart/form-data' }}).then(response => {resolve(response);}).catch(error => {reject(error);});});
}
export function getServerUrl(){return baseUrl;
}
export default {get,post,fileUpload,getServerUrl
}

该代码主要是封装了一个基于 axios 的 httpService,用于向后端发送 HTTP 请求并处理响应结果。主要作用如下:

  1. 引入了 axios 库和 store。

    import axios from 'axios';
    import store from '@/store'
  2. 定义了服务器地址。

    let baseUrl="http://124.71.79.129:8866/";
  3. 创建了 axios 实例,并设置了请求超时时间和请求和响应拦截器。

    const httpService = axios.create({baseURL: baseUrl,timeout: 3000
    });
    ​
    httpService.interceptors.request.use(function (config) {config.headers.token = store.getters.GET_TOKENreturn config;
    }, function (error) {return Promise.reject(error);
    });
    ​
    httpService.interceptors.response.use(function (response) {return response;
    }, function (error) {return Promise.reject(error);
    });
  4. 导出了 getpostfileUpload 方法和 getServerUrl 方法,用于向后端发送 GET、POST 和文件上传请求,并获取服务器地址。

    export function get(url, params = {}) {return new Promise((resolve, reject) => {httpService({url: url,method: 'get',params: params}).then(response => {resolve(response);}).catch(error => {reject(error);});});
    }
    ​
    export function post(url, params = {}) {return new Promise((resolve, reject) => {httpService({url: url,method: 'post',data: params}).then(response => {resolve(response);}).catch(error => {reject(error);});});
    }
    ​
    export function fileUpload(url, params = {}) {return new Promise((resolve, reject) => {httpService({url: url,method: 'post',data: params,headers: { 'Content-Type': 'multipart/form-data' }}).then(response => {resolve(response);}).catch(error => {reject(error);});});
    }
    ​
    export function getServerUrl(){return baseUrl;
    }

在整个系统中,该代码可以被其他 JavaScript 模块引入并使用,用于向后端发送 HTTP 请求,并获取响应结果。通过封装 httpService,可以提高代码的复用性和可维护性,并降低开发难度。此外,在请求拦截器和响应拦截器中可以进行 token 鉴权,从而保证系统的安全性和正确性。

vuex状态管理

import { createStore } from 'vuex'
import router from '@/router'
​
export default createStore({state: {hasRoutes: false,editableTabsValue: '/index',editableTabs: [{title: '首页',name: '/index'}]},getters: {GET_TOKEN: (state) => {return sessionStorage.getItem("token");},// 将JSON格式转换成对象的形式GET_MENULIST:(state) => {return JSON.parse(sessionStorage.getItem("menuList"));},GET_USERINFO:(state) => {return JSON.parse(sessionStorage.getItem("userInfo"));}},mutations: {SET_TOKEN:(state ,token) => {sessionStorage.setItem("token", token);},// 将对象转换成JSON格式SET_MENULIST:(state,menuList) => {sessionStorage.setItem("menuList", JSON.stringify(menuList))},SET_USERINFO:(state,userInfo) => {sessionStorage.setItem("userInfo", JSON.stringify(userInfo))},SET_ROUTES_STATE:(state,hasRoutes) => {state.hasRoutes = hasRoutes},ADD_TABS:(state,tab) => {if(state.editableTabs.findIndex(e => e.name === tab.path) === -1){state.editableTabs.push({title: tab.name,name: tab.path})}state.editableTabsValue = tab.path},RESET_TABS:(state) => {state.editableTabsValue = '/index',state.editableTabs = [{title: '首页',name: '/index'}]},},actions: {// 安全退出logout(){window.sessionStorage.clear();router.replace("/login")}},modules: {}
})

该代码主要是使用 Vuex 库管理前端的状态,并提供了一些 getters 和 mutations 方法用于获取和修改状态。主要作用如下:

  1. 引入了 Vuex 库和路由。

    import { createStore } from 'vuex'
    import router from '@/router'
  2. 创建了一个 Vuex Store 实例,并定义了初始状态。

    export default createStore({state: {hasRoutes: false,editableTabsValue: '/index',editableTabs: [{title: '首页',name: '/index'}]},...
    })
  3. 在 getters 中定义了 GET_TOKENGET_MENULISTGET_USERINFO 三个方法,用于获取存储在 sessionStorage 中的 token、menuList 和 userInfo。

    getters: {GET_TOKEN: (state) => {return sessionStorage.getItem("token");},GET_MENULIST:(state) => {return JSON.parse(sessionStorage.getItem("menuList"));},GET_USERINFO:(state) => {return JSON.parse(sessionStorage.getItem("userInfo"));}
    },
  4. 在 mutations 中定义了 SET_TOKENSET_MENULISTSET_USERINFOSET_ROUTES_STATEADD_TABSRESET_TABS 六个方法,用于修改存储在 sessionStorage 中的 token、menuList、userInfo、hasRoutes、editableTabsValue 和 editableTabs。

    mutations: {SET_TOKEN:(state ,token) => {sessionStorage.setItem("token", token);},SET_MENULIST:(state,menuList) => {sessionStorage.setItem("menuList", JSON.stringify(menuList))},SET_USERINFO:(state,userInfo) => {sessionStorage.setItem("userInfo", JSON.stringify(userInfo))},SET_ROUTES_STATE:(state,hasRoutes) => {state.hasRoutes = hasRoutes},ADD_TABS:(state,tab) => {if(state.editableTabs.findIndex(e => e.name === tab.path) === -1){state.editableTabs.push({title: tab.name,name: tab.path})}state.editableTabsValue = tab.path},RESET_TABS:(state) => {state.editableTabsValue = '/index',state.editableTabs = [{title: '首页',name: '/index'}]},
    },
  5. 在 actions 中定义了 logout 方法,用于安全退出系统,并清空 sessionStorage 中的数据。

    actions: {logout(){window.sessionStorage.clear();router.replace("/login")}
    },

在整个系统中,该代码可以被其他 JavaScript 模块引入并使用,用于管理前端的状态和提供一些可重用的方法。通过使用 Vuex,可以方便地管理前端的状态,从而提高了代码的可维护性和可扩展性。此外,在 getters 和 mutations 中对存储在 sessionStorage 中的数据进行操作,可以保证系统的安全性和正确性。

vue-router动态跳转

// 路由守卫实现
import store from "@/store";
import router from "@/router/index";
​
router.beforeEach((to, from, next)=>{let token = store.getters.GET_TOKEN// 只判断一次,获取hasRouteslet hasRoutes = store.state.hasRoutes// 获取menuListlet menuList = store.getters.GET_MENULIST;
​const whiteList=['/login'] // 白名单if(token){if(!hasRoutes){bindRoute(menuList);storemit("SET_ROUTES_STATE",true)}next();}else{if(whiteList.includes(to.path)){next(); // 放行}else{next("/login") // 跳转登录页面}}
})
​
// 动态绑定路由
const bindRoute = (menuList) => {let newRoutes = router.options.routes; // 获取路由中的routes对象menuList.forEach((menu) => {if(menu.children){menu.children.forEach((m) =>{// 菜单转成路由let route = menuToRoute(m,menu.name); // route是封装好的一个路由对象if(route){newRoutes[0].children.push(route); // 添加到路由管理}})}})// 重新添加到路由管理中newRoutes.forEach((route) => {router.addRoute(route);})
}
​
// 菜单转对象成路由对象
const menuToRoute = (menu,parentName) => {// 数据库中有无componentif (!menuponent) {return null}else{let route = {name: menu.name, // 菜单名称path: menu.path, // 浏览器路由路径meta:{parentName: parentName}}// 路由到哪个组件,使用拼接实现routeponent = () => import('@/views/' + menuponent +'.vue')return route}
}
​
const menuList = store.getters.GET_MENULIST;
const token = store.getters.GET_TOKEN;
​
if(token){bindRoute(menuList); // 动态绑定路由
}

该代码的主要作用是实现了一个路由守卫并提供了动态绑定路由的方法。具体作用如下:

  1. 引入了 store 和 router。

    import store from "@/store";
    import router from "@/router/index";
  2. beforeEach 钩子函数中,判断用户是否登录,并执行相应的操作。如果用户已经登录,则判断是否已经加载过路由,如果没有则执行动态绑定路由的方法。如果用户未登录,则判断是否在白名单中,如果是则放行,否则跳转到登录页面。

    router.beforeEach((to, from, next)=>{let token = store.getters.GET_TOKENlet hasRoutes = store.state.hasRouteslet menuList = store.getters.GET_MENULIST;
    ​const whiteList=['/login']if(token){if(!hasRoutes){bindRoute(menuList);storemit("SET_ROUTES_STATE",true)}next();}else{if(whiteList.includes(to.path)){next();}else{next("/login")}}
    })
  3. 定义了 bindRoute 方法,用于将从后台获取到的菜单列表动态绑定到路由中。

    const bindRoute = (menuList) => {let newRoutes = router.options.routes;menuList.forEach((menu) => {if(menu.children){menu.children.forEach((m) =>{let route = menuToRoute(m,menu.name);if(route){newRoutes[0].children.push(route);}})}})newRoutes.forEach((route) => {router.addRoute(route);})
    }
  4. 定义了 menuToRoute 方法,用于将菜单转化为路由对象。

    const menuToRoute = (menu,parentName) => {if (!menuponent) {return null}else{let route = {name: menu.name,path: menu.path,meta:{parentName: parentName}}routeponent = () => import('@/views/' + menuponent +'.vue')return route}
    }

通过使用路由守卫可以对用户登录状态进行判断,从而保证系统的安全性和正确性。同时,在动态绑定路由方法中可以将从后台获取到的菜单列表转化为路由对象,并将其添加到路由管理中,从而实现了动态生成路由的功能。此外,由于是在前端处理路由,因此可以提高应用程序的性能和响应速度,从而提高用户体验。

svg图标引用

const webpack = require('webpack');
const path = require('path')
function resolve(dir) {return path.join(__dirname, dir)
}
​
module.exports = {lintOnSave: false,chainWebpack(config) {// 设置 svg-sprite-loader// config 为 webpack 配置对象// config.module 表示创建一个具名规则,以后用来修改规则config.module// 规则.rule('svg')// 忽略.exclude.add(resolve('src/icons'))// 结束.end()// config.module 表示创建一个具名规则,以后用来修改规则config.module// 规则.rule('icons')// 正则,解析 .svg 格式文件.test(/\.svg$/)// 解析的文件.include.add(resolve('src/icons'))// 结束.end()// 新增了一个解析的loader.use('svg-sprite-loader')// 具体的loader.loader('svg-sprite-loader')// loader 的配置.options({symbolId: 'icon-[name]'})// 结束.end()config.plugin('ignore').use(new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn$/))config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/icons')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({symbolId: 'icon-[name]'}).end()}
}

以上代码主要作用是配置了一个 svg-sprite-loader,用于将项目中的 SVG 图标打包成雪碧图,并提供给其他组件使用。

具体作用如下:

  1. 引入了 webpackpath 库,以及一个 resolve 方法,用于获取绝对路径。

    const webpack = require('webpack');
    ​
    const path = require('path')
    function resolve(dir) {return path.join(__dirname, dir)
    }
  2. 设置了 lintOnSave: false 选项,用于关闭 ESLint 的 lintOnSave 检查功能。

    module.exports = {lintOnSave: false,...
    }
  3. chainWebpack 方法中设置了一个 svg-sprite-loader 规则,用于将项目中的 SVG 图标打包成雪碧图,并提供给其他组件使用。

    chainWebpack(config) {config.module.rule('svg').exclude.add(resolve('src/icons')).end()config.module.rule('icons').test(/\.svg$/).include.add(resolve('src/icons')).end().use('svg-sprite-loader').loader('svg-sprite-loader').options({symbolId: 'icon-[name]'}).end()
    }

    具体来说,该规则做了以下几件事情:

    • 排除 src/icons 目录下的 SVG 文件,因为这些文件需要单独处理。

    • 解析 src/icons 目录下的 SVG 文件。

    • 使用 svg-sprite-loader 将 SVG 图标打包成雪碧图,其中 symbolId 选项用于指定 SVG 图标的名称。

  4. 新增了一个插件 ignore,用于忽略 moment 库中一些语言文件,从而减小打包后的体积。

    config.plugin('ignore').use(new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn$/))

在整个系统中,该代码主要用于配置 svg-sprite-loader 规则,从而将项目中的 SVG 图标打包成雪碧图。通过将所有的 SVG 图标打包成雪碧图,并提供给其他组件使用,可以方便地实现图标的复用和替换,从而提高了代码的可维护性和可扩展性。此外,由于加入了一个插件 ignore,从而可以减小打包后的体积,从而提高了应用程序的性能和响应速度。

下面是如何进行使用:

import SvgIcon from '@/components/SvgIcon'
​
const svgRequired = require.context('./svg', false, /\.svg$/)
svgRequired.keys().forEach((item) => svgRequired(item))
​
// 将svg-icon作为全局组件
export default (app) => {appponent('svg-icon', SvgIcon)
}

该代码主要作用是注册一个全局的 SVG 图标组件。具体作用如下:

  1. 引入了 SvgIcon 组件,并使用 require.context 方法获取 svg 目录下所有以 .svg 结尾的文件。

    import SvgIcon from '@/components/SvgIcon'
    ​
    const svgRequired = require.context('./svg', false, /\.svg$/)
    svgRequired.keys().forEach((item) => svgRequired(item))
  2. 定义了一个函数,通过 appponent 方法将 SvgIcon 组件注册为全局组件。

    export default (app) => {appponent('svg-icon', SvgIcon)
    }

它主要用于将 SvgIcon 组件注册为全局组件,从而可以在应用程序的任何地方使用该组件。通过引入所有 svg 目录下的图标,并将其注册为全局组件,可以使得项目中的任何一个模块都可以使用该组件,从而提高了代码的可重用性和可维护性。此外,由于本代码只是将 SvgIcon 组件注册为全局组件,因此并不会影响整个系统的性能和响应速度。

总结

我们项目是一个使用 Spring Boot 框架开发的培训机构教务管理系统,提供了用户管理、角色管理、菜单管理、权限管理等功能。

以下是对该项目结构的简要分析:

项目结构

该项目采用 Maven 构建工具进行管理,项目结构清晰明了,主要包括了 controller、service、dao、entity 等几个模块。

技术选型

该项目使用了 Spring Boot、Spring Security、MyBatis 等框架和技术,能够快速搭建一个安全可靠、易于维护的 Web 应用程序。

安全性

该项目使用了 Spring Security 框架进行安全控制,提供了较为完善的用户认证、授权、加密、防护等功能,保证了应用程序的安全性和可靠性。

数据库管理

该项目使用了 MySQL 数据库,采用 MyBatis 框架进行数据持久化操作,能够方便地进行数据库管理和开发。

代码质量

该项目的代码风格规范,结构清晰,注释完整,易于维护和扩展,体现了良好的编程习惯和团队协作意识。

功能特点

该项目实现了用户管理、角色管理、菜单管理、权限管理等功能,能够满足培训机构教务管理的基本需求。同时,也提供了较为完善的系统日志、异常处理等功能,方便开发和运维人员进行问题排查和解决。

总体来说,我们的项目使用了 Spring Boot 框架和多种技术和工具,实现了比较完整的培训机构教务管理系统,具有良好的代码质量和安全性保障,适合于企业内部或外部的培训机构使用。如果需要进一步定制和扩展,可以在此基础上进行二次开发和优化。

更多推荐

企业项目实训

本文发布于:2024-03-09 01:19:14,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1723204.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:实训   项目   企业

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!