效果图:

1.前言 

从零开始搭建一个项目最重要的是选择一个自己熟悉的框架,此项目使用Springboot框架来构建后端结构,使用vue来构建前端页面。数据层我们常用的是Mybatis,这里我大部分使用了Mybatis-plus简化配置,在涉及到多表联合查询的时候使用了Mybatis。登录功能使用的单点登录,使用jwt作为我们的用户身份验证。引入了SpringSecurity安全框架作为我们的权限控制和会话控制。

技术栈:

  • Springboot
  • mybatis-plus
  • spring-security
  • lombok
  • jwt

2.新建Springboot项目,注意版本

新建功能比骄简单,就不截图了。

开发工具与环境:

  • idea
  • mysql5.7
  • jdk8
  • maven3.5.0

第一步:pom依赖导入如下

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.6</version><relativePath/> </parent><groupId>com.tjise</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><!--web 依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--lombok 依赖--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--导入导出--><dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.4.0</version></dependency><!--日志信息--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency><!--下载文件--><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--mybatis-plus 依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.3.1.tmp</version></dependency><!-- swagger2 依赖 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version></dependency><!-- Swagger第三方ui依赖 --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>swagger-bootstrap-ui</artifactId><version>1.9.6</version></dependency><!--security 依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--JWT 依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency><!--验证码--><dependency><groupId>com.github.axet</groupId><artifactId>kaptcha</artifactId><version>0.0.9</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins><resources><resource><directory>src/main/java</directory><includes><include>**/*.xml</include></includes></resource></resources></build>
</project>

第二步:项目的结构

仅供参考!

第三步:去写配置文件

# 服务端口
server.port=8001
# 服务名
spring.application.name=service-employ# 环境设置:dev、test、prod
spring.profiles.active=dev# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/employ?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl#逻辑删除
#已删除
mybatis-plus.global-config.db-config.logic-delete-value=1
#未删除
mybatis-plus.global-config.db-config.logic-not-delete-value=0#开启mutipart
spring.servlet.multipart.enabled=true
#上传文件默认的大小
#设置单个文件上传的大小
spring.servlet.multipart.max-file-size=20MB
#设置总上传文件的大小
spring.servlet.multipart.max-request-size=100MB
spring.resources.static-locations=classpath:/resources/,classpath:/static/,classpath:/public/
#jwt配置
#jwt存储请求头
jwt.tokenHeader=Authorization
#jwt加密使用的密钥
jwt.secret=yed-secret
#jwt的超期限时间
jwt.expiration=604800
#jwt负载中拿到开头
jwt.tokenHead=Bearer

上边除了数据库的配置信息之外,还配置了逻辑删除的配置、jwt的配置、文件上传的配置。端口号前端为8080,所以后端我们就设置为8001.

第三步:开启mapper接口扫描,添加分页、逻辑删除的插件

package com.tjise;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.tjise.employ.mapper")
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}

第四步:创建数据库和表

主要的表有:用户表,用户角色表、菜单表、菜单权限表、菜单表和员工表

3.结果封装

因为是前后端分离项目,所以我们必须要有一个统一的结果返回封装

封装结果如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {private long code;private String message;private Object obj;/*** 成功返回结果* @param message* @return*/public static RespBean success(String message){return new RespBean(200,message,null);}/*** 成功返回结果* @param message* @param obj* @return*/public static RespBean success(String message,Object obj){return new RespBean(200,message,obj);}/*** 失败返回结果* @param message* @return*/public static RespBean error(String message){return new RespBean(500,message,null);}/*** 失败返回结果* @param message* @param obj* @return*/public static RespBean error(String message,Object obj){return new RespBean(500,message,obj);}}

4.整合SpringSecurity

首先了解一下security的原理

流程说明:

1.客户端发起一个请求,进入 Security 过滤器链。

2.当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。

3.当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。

4.当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。

Security 配置

配置类伪代码:

@Overridepublic void configure(WebSecurity web) throws Exception {//放行静态资源web.ignoring().antMatchers("/login","/logout","/css/**","/js/**","favicon.ico","/index.html","/doc.html","/webjars/**","/swagger-resources/**","/v2/api-docs/**","/captcha","/ws/**","/employ/upload","/employ/export","/resources/**");}@Overrideprotected void configure(HttpSecurity http) throws Exception {//使用jwt,不需要用csrfhttp.csrf().disable()//基于token不需要用session.sessionManagement().and().authorizeRequests().antMatchers("/众安保险.pdf").permitAll().anyRequest().authenticated()//动态权限控制.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setAccessDecisionManager(customUrlDecisionManager);object.setSecurityMetadataSource(customFilter);return object;}}).and()//允许跨域访问.cors().and()//禁用缓存.headers().frameOptions().disable().cacheControl();//添加jwt 登录授权过滤器http.addFilterBefore(jwtAutjencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);//添加自定义未授权和未登录的结果返回http.exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler).authenticationEntryPoint(restAuthorizationEntryPoint);}@Override@Beanpublic UserDetailsService userDetailsService() {return username -> {Admin admin = adminService.getAdminByUserName(username);if (null != admin) {admin.setRoles(adminService.getRoles(admin.getId()));return admin;}return null;};}

配置类简介:

  • configure(AuthenticationManagerBuilder auth)
protected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());}

AuthenticationManager 的建造器,配置 AuthenticationManagerBuilder 会让Security 自动构建一个 AuthenticationManager;如果你想要使用该功能你就得配置一个UserDetailService和PasswordEncord。UserDetailService用于在认证器中根据用户传过来的用户查找一个用户,PasswordEncord用于密码的加密与对比,我们存储密码的时候用PasswordEncord.encord()加密存储,在认证器里会调用PasswordEncord.matchs()方法进行密码的比对。

  • configure(WebSecurity web)
 public void configure(WebSecurity web) throws Exception {//放行静态资源web.ignoring().antMatchers("/login","/logout","/css/**","/js/**","favicon.ico","/index.html","/doc.html","/webjars/**","/swagger-resources/**","/v2/api-docs/**","/captcha","/ws/**","/employ/upload","/employ/export","/resources/**");}

这个配置方法用于配置静态资源的处理方式,可以使用Ant匹配规则

  • configure(HttpSecurity http)
protected void configure(HttpSecurity http) throws Exception {//使用jwt,不需要用csrfhttp.csrf().disable()//基于token不需要用session.sessionManagement().and().authorizeRequests().antMatchers("/众安保险.pdf").permitAll().anyRequest().authenticated()//动态权限控制.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setAccessDecisionManager(customUrlDecisionManager);object.setSecurityMetadataSource(customFilter);return object;}}).and()//允许跨域访问.cors().and()//禁用缓存.headers().frameOptions().disable().cacheControl();//添加jwt 登录授权过滤器http.addFilterBefore(jwtAutjencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);//添加自定义未授权和未登录的结果返回http.exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler).authenticationEntryPoint(restAuthorizationEntryPoint);}

这个配置方法是最关键的方法,也是最复杂的方法。

用户认证

首先我们来解决用户认证的问题,用户认证分为首次登录和二次认证。

  • 首次登录认证:用户名、密码、验证码完成登录。
  • 二次token认证:请求头携带jwt进行身份认证。

生成验证码

首先我们先生成验证码,我们先配置一下图片验证码的生成规则

@Configuration
public class CaptchaConfig {@Beanpublic DefaultKaptcha defaultKaptcha(){//验证码生成器DefaultKaptcha defaultKaptcha=new DefaultKaptcha();//配置Properties properties = new Properties();//是否有边框properties.setProperty("kaptcha.border", "yes");//设置边框颜色properties.setProperty("kaptcha.border.color", "105,179,90");//边框粗细度,默认为1// properties.setProperty("kaptcha.border.thickness","1");//验证码properties.setProperty("kaptcha.session.key","code");//验证码文本字符颜色 默认为黑色properties.setProperty("kaptcha.textproducer.font.color", "blue");//设置字体样式properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");//字体大小,默认40properties.setProperty("kaptcha.textproducer.font.size", "30");//验证码文本字符内容范围 默认为abced2345678gfynmnpwx// properties.setProperty("kaptcha.textproducer.char.string", "");//字符长度,默认为5properties.setProperty("kaptcha.textproducer.char.length", "4");//字符间距 默认为2properties.setProperty("kaptcha.textproducer.char.space", "4");//验证码图片宽度 默认为200properties.setProperty("kaptcha.image.width", "100");//验证码图片高度 默认为40properties.setProperty("kaptcha.image.height", "40");Config config = new Config(properties);defaultKaptcha.setConfig(config);return defaultKaptcha;}

上边定义了图片验证码的长宽颜色等

然后我们通过控制器提供生成验证码的方法。

@RestController
public class CaptchaController {@Autowiredprivate DefaultKaptcha defaultKaptcha;@GetMapping(value = "/captcha",produces = "image/jpeg")public void captcha(HttpServletRequest request, HttpServletResponse response) {// 定义response输出类型为image/jpeg类型response.setDateHeader("Expires", 0);// Set standard HTTP/1.1 no-cache headers.response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");// Set IE extended HTTP/1.1 no-cache headers (use addHeader).response.addHeader("Cache-Control", "post-check=0, pre-check=0");// Set standard HTTP/1.0 no-cache header.response.setHeader("Pragma", "no-cache");// return a jpegresponse.setContentType("image/jpeg");//-------------------生成验证码 begin --------------------------//获取验证码文本内容String text = defaultKaptcha.createText();System.out.println("验证码内容:" + text);//将验证码文本内容放入sessionrequest.getServletContext().setAttribute("captcha", text);//根据文本验证码内容创建图形验证码BufferedImage image = defaultKaptcha.createImage(text);ServletOutputStream outputStream = null;try {outputStream = response.getOutputStream();//输出流输出图片,格式为jpgImageIO.write(image, "jpg", outputStream);outputStream.flush();} catch (IOException e) {e.printStackTrace();} finally {if (null != outputStream) {try {outputStream.close();} catch (IOException e) {e.printStackTrace();}}}}

因为是前后端分离的项目,我们禁用了session,所以我们将验证码放在了ServletContext中,然后以二进制流的形式将图片验证码输出。

验证码校验

  //校验验证码String captcha = (String) request.getServletContext().getAttribute("captcha");if (StringUtils.isEmpty(code)||!captcha.equalsIgnoreCase(code)){return RespBean.error("验证码输入错误,请重新输入!");}

登录成功之后,更新security登录用户对象,生成token,然后将token作为请求头返回回去,名称就叫作Authorization。我们需要在配置文件中配置一些jwt的一些信息:

#jwt存储请求头
jwt.tokenHeader=Authorization
#jwt加密使用的密钥
jwt.secret=yed-secret
#jwt的超期限时间
jwt.expiration=604800
#jwt负载中拿到开头
jwt.tokenHead=Bearer

我们去swagger里边进行测试

上边我们可以看到,我们已经登录成功,然后我们的token也可以看到。

身份认证-1:

登录成功之后前端就可以获取到token的信息,前端中我们是保存在了sessionStorage中,然后每次axios请求之前,我们都会添加上我们的请求头信息。这样携带请求头就可以正常访问我们的接口了。

身份认证-2:

我们的用户必须是存储在数据库里边,密码也是经过加密的,所以我们先来解决这个问题。这里我们使用了Security内置的BCryPasswordEncoder,里边就有生成和匹配密码是否正确的方法,也就是加密和验证的策略。因此我们需要在SecurityConfig中进行配置

@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}

因为我们登录过程中系统不是从我们数据库中获取数据的,因此我们需要重新定义这个查用户数据的过程,重写UserDetailsService接口

 @Override@Beanpublic UserDetailsService userDetailsService() {return username -> {Admin admin = adminService.getAdminByUserName(username);if (null != admin) {admin.setRoles(adminService.getRoles(admin.getId()));return admin;}return null;};}

解决授权

然后就是关于权限部分,也是security的重要功能,当用户认证成功之后,我们就知道是谁在访问系统接口,就是这个用户有没有权限去访问这个接口,要解决这个问题的话我们需要知道用户有哪些权限,哪些角色,这样security才能为我们做出权限的判断。

之前我们定义过几张表,用户、角色、菜单、以及一些关联表,一般情况下当权限粒度比较细的时候,我们通过判断用户有没有此菜单的操作权限,而不是通过角色判断。而用户和菜单不直接做关联的,是通过用户拥有哪些角色,角色拥有哪些菜单这样来获取的。

问题:我们在哪里赋予用户的权限

用户登录的时候

 @Beanpublic UserDetailsService userDetailsService() {return username -> {Admin admin = adminService.getAdminByUserName(username);if (null != admin) {admin.setRoles(adminService.getRoles(admin.getId()));return admin;}return null;};}

我们再来整体梳理一下授权、验证权限的流程:

用户登录的时候识别到用户,并获取用户的权限信息

Security通过FilterSecurityInterceptor匹配url和权限是否匹配

有权限则可以访问接口,当没有权限的时候返回异常

 */
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {@Overridepublic void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {for (ConfigAttribute configAttribute : configAttributes) {//当前url所需角色String needRole = configAttribute.getAttribute();//判断角色是否登录即可访问的角色,此角色在CustomFilter中设置if ("ROLE_LOGIN".equals(needRole)){//判断是否登录if (authentication instanceof AnonymousAuthenticationToken){throw new AccessDeniedException("尚未登录,请登录!");}else {return;}}//判断用户角色是否为url所需角色Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();for (GrantedAuthority authority : authorities) {if (authority.getAuthority().equals(needRole)){return;}}}throw new AccessDeniedException("权限不足,请联系管理员!");}

员工管理的接口的开发:

我们首先来开发员工管理的接口,主要功能就是实现对员工的增删改查,数据的导入和导出,分页显示和逻辑删除。

首先看一下员工的实体类:

public class User {@Excel(name="编号")private Integer id;  //员工的编号@Excel(name="姓名")private String name; //员工姓名@Excel(name="性别")private String sex; //员工性别@Excel(name="籍贯")private String provices;//员工籍贯@Excel(name="年龄")private Integer age; //员工年龄@Excel(name="薪水")private double salary; //员工薪水@Excel(name="地址")private String address;//员工的地址@Excel(name="院校")private String school;//毕业院校@TableLogicprivate Integer isdelete=0;//逻辑删除}

接着在controller层编写对员工的分页显示的接口:

 @PostMapping("pageCodition/{current}/{size}")public RespPage pageEmployCodition(@PathVariable long current,@PathVariable long size,@RequestBody(required = false) Employ employ) {//创建page对象,开启分页Page<User> userPage = new Page<>(current, size);//构建条件QueryWrapper<User> wrapper = new QueryWrapper<>();String name = employ.getName();Integer id = employ.getId();String provices = employ.getProvices();//判断条件是否为空if (!StringUtils.isEmpty(name)) {wrapper.like("name", name);}if (!StringUtils.isEmpty(id)) {wrapper.eq("id", id);}if (!StringUtils.isEmpty(provices)) {wrapper.like("provices", provices);}//调用方法实现分页//调用方法的时候,底层封装,把分页所有数据封装到page里边employService.page(userPage, wrapper);long total = userPage.getTotal();//总记录数List<User> records = userPage.getRecords();//数据List集合return RespPage.ok().data("total", total).data("rows", records);}

要实现分页查询,首先得传两个参数,当前页和每页大小(current、size)

分页显示的步骤:

首先开启分页,传入当前页和每页大小。

 Page<User> userPage = new Page<>(current, size);

接着创建条件构造器,

QueryWrapper<User> wrapper = new QueryWrapper<>();

最后调用分页查询的方法。调用方法的时候底层封装,将所有分页的数据封装到page里边。

  employService.page(userPage, wrapper);

添加员工的接口:

 @PostMapping("add")public boolean adduser(@RequestBody User user) {boolean save = employService.save(user);if (save != false) {return true;} else {return false;}}

添加员工的接口就比较简单了,首先用post请求,方法中传入实体类对象,调用mybatisplus自带的添加方法.save(),返回值是boolean类型的。

逻辑删除员工的接口:

在实现逻辑删除之前,我们首先看一下逻辑删除和物理删除,它们有什么区别呢?

物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据。

逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录。

在我们日常开发中,为了保留数据,经常会使用逻辑删除的方式进行数据删除,下面我们就来看看逻辑删除怎么实现的吧

1.数据库的修改,添加isdelete字段:

2.实体类的修改,添加对应的字段,并添加@TableLogic注解.

public class User {@TableLogicprivate Integer isdelete=0;//逻辑删除}

3.添加全局配置:

#逻辑删除
#已删除
mybatis-plus.global-config.db-config.logic-delete-value=1
#未删除
mybatis-plus.global-config.db-config.logic-not-delete-value=0

0表示未删除,1表示已删除。

4.controller实现逻辑删除的方法:

 //3.逻辑删除员工@DeleteMapping("{id}")public boolean removeEmpoy(@PathVariable Integer id) {boolean b = employService.removeById(id);return b;}

根据员工的id删除员工,调用service层的remove方法进行删除。

批量删除员工的接口:

所谓批量删除就是能同时删除多个,调用mybatisplus自带的批量删除的方法

@DeleteMapping("/ids")public boolean removeEmpployids(Integer[] ids) {boolean b = employService.removeByIds(Arrays.asList(ids));return b;}

因为是删除多个,所以这里我们定义一个数组,然后调用removeByIds方法,需要注意的是,方法里边需要一个数,所以需要将数组转换成数。

修改员工信息的接口:

@PostMapping("update")public Boolean update(@RequestBody User user) {boolean b = employService.updateById(user);if (b != false) {return true;} else {return false;}}

使用post请求,直接调用mybatisplus自带的修改方法.updateById()通过用户的id修改用户的信息,返回值是一个boolean类型。

导入员工的数据:

在实现导入导出数据之前我们需要添加相关的依赖:

<dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>4.4.0</version></dependency>

这里我们用的是easy-poi;

添加完依赖之后需要对要导出的字段添加对应的注解:

public class User {@Excel(name="编号")private Integer id;  //员工的编号@Excel(name="姓名")private String name; //员工姓名@Excel(name="性别")private String sex; //员工性别@Excel(name="籍贯",width = 15)private String provices;//员工籍贯@Excel(name="年龄")private Integer age; //员工年龄@Excel(name="薪水",width = 15)private double salary; //员工薪水@Excel(name="地址",width = 15)private String address;//员工的地址@Excel(name="院校",width = 20)private String school;//毕业院校
}

controller层实现导入的接口:

 @PostMapping("/import")public RespPage importEmploy(MultipartFile file) {//准备导入的参数ImportParams params = new ImportParams();//去掉标题行params.setTitleRows(1);try {List<User> list = ExcelImportUtil.importExcel(file.getInputStream(), User.class, params);if (employService.saveBatch(list)) ;{return RespPage.ok();}} catch (Exception e) {e.printStackTrace();}return RespPage.error();}

首先准备导入的参数

 ImportParams params = new ImportParams();

去掉标题行,因为标题不能导入

  params.setTitleRows(1);

以流的形式导入:

 List<User> list = ExcelImportUtil.importExcel(file.getInputStream(), User.class, params);

导入数据成功之后更新数据:

  if (employService.saveBatch(list)) ;{return RespPage.ok();}} catch (Exception e) {e.printStackTrace();}return RespPage.error();}

导出数据:

    //7.导出员工信息@GetMapping(value = "/export",produces = "application/octet-stream")public void exportEmploy(HttpServletResponse response) {//获取所有员工的数据List<User> list = employService.list(null);//准备导出参数ExportParams params = new ExportParams("员工表", "员工表", ExcelType.HSSF);Workbook workbook = ExcelExportUtil.exportExcel(params, User.class, list);ServletOutputStream out = null;try {//流形式response.setHeader("content-type","application/octet-stream");//防止中文乱码response.setHeader("content-disposition","attachment;filename="+ URLEncoder.encode("员工表.xls","UTF-8"));out = response.getOutputStream();workbook.write(out);} catch (IOException e) {e.printStackTrace();}finally {if (null!=out){try {out.close();} catch (IOException e) {e.printStackTrace();}}}}

首先获取到所有员工的数据,然后准备导出参数,调用导出的方法。

文件的上传和下载:

文件上传的方法

  public int Upload(@RequestParam("file") MultipartFile file,File file1) {if(!file.isEmpty()) {// 获取文件名称,包含后缀String fileName = file.getOriginalFilename();//随机生成uuid作为文件的idString id=UUID.randomUUID().toString();// 存放在这个路径下:该路径是该工程目录下的static文件下// 放在static下的原因是,存放的是静态文件资源,即通过浏览器输入本地服务器地址,加文件 名时是可以访问到的String path = "D:\\java项目 \\svnbucket\\workspace\\employ\\springboootemploy\\src\\main\\resources\\static\\";try {// 该方法是对文件写入的封装,在util类中,导入该包即可使用FileUtils.uploadFile(file.getBytes(), path, fileName);} catch (Exception e) {e.printStackTrace();}// 接着创建对应的实体类,将以下路径进行添加,然后通过数据库操作方法写入file1.setFilepath("http://localhost:8001/"+fileName);file1.setFilename(fileName);fileMapper.insert(file1);}return 0;}

文件上传的接口

 @PostMapping("/upload")public RespPage Upload(@RequestParam("file") MultipartFile file, com.tjise.employ.entity.File file1) {fileService.Upload(file, file1);String filename = file1.getFilename();String filepath = file1.getFilepath();Long id = file1.getId();return RespPage.ok().data("id", id).data("filename", filename).data("filepath", filepath);}

post请求,调用上传文件的接口

文件下载:

    @GetMapping("/download/{filename}")@ResponseBodypublic RespPage download(HttpServletResponse response, @PathVariable("filename") String filename) {//下载后的文件名String fileName = "文件下载.pdf";//使用流的形式下载文件try {//加载文件File file = new File("D:\\java项目\\workspace\\springboot02\\src\\main\\resources\\static\\"+filename);InputStream is = new BufferedInputStream(new FileInputStream(file));byte[] buffer = new byte[is.available()];is.read(buffer);is.close();// 清空responseresponse.reset();// 设置response的Headerresponse.addHeader("Content-Disposition", "attachment" + new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));response.addHeader("Content-Length", "" + file.length());OutputStream toClient = new BufferedOutputStream(response.getOutputStream());response.setContentType("application/octet-stream");toClient.write(buffer);toClient.flush();toClient.close();return RespPage.ok();} catch (Exception e) {e.printStackTrace();return RespPage.ok();}}

以二进制流的形式下载文件

员工管理接口的所有功能都写完了,下边我们实现一下权限管理模块。

菜单接口的开发

我们先来开发菜单接口,因为它不需要通过其他的表来获取信息的。比如用户表需要关联角色表,角色表需要关联菜单表,因此菜单表的增删改是比较简单的。

获取菜单导航和权限的链接是这样的/menu,然后我们的菜单导航的json数据应该是这样的:

注意导航中有个children,也就是子菜单,是个树形结构

所以在打代码的时候一定要注意这个关系的关联,我们的Menu实体类中有一个parentId,但是没有children,因此我们可以在Menu中添加一个children

com.tjise.employ.Menu

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("menu")
@Accessors(chain = true)
public class Menu {private Integer id;private String url;private String path;private String component;private String name;private String iconCls;private Boolean requireAuth;private Integer parentId;@TableField(exist = false)private List<Menu> children;@TableField(exist = false)private List<Role> roles;@TableField(exist = false)private List<Menu> children;
}

ok,我们开始编码:

public class MenuController {@Autowiredprivate MenuService menuService;//根据用户的id查询菜单列表@GetMapping("/menu")public List<Menu> getMenusByAdminId(){return menuService.getMenusByAdminId();}
}

根据用户的id查询菜单的信息

角色接口开发:

角色接口主要实现的功能就是对角色的增删改查和对角色分配菜单

角色的增加

//添加角色
@PostMapping("/add")
public RespBean addRole(@RequestBody Role role){if (!role.getName().startsWith("ROLE_")){role.setName("ROLE_"+role.getName());}if (roleService.save(role)){return RespBean.success("添加成功!");}return RespBean.error("添加失败!");
}

添加角色需要注意的是,角色的统一编码,开头是以ROLE_开头的

角色的删除

//删除角色
@DeleteMapping("/delete/{rid}")
public RespBean deleteRole(@PathVariable Integer rid){if(roleService.removeById(rid)){return RespBean.success("删除成功!");}return RespBean.error("删除失败!");
}

根据角色的id删除角色

角色信息的修改

//修改角色信息@PostMapping("/update")public Boolean update(@RequestBody Role role) {boolean b = roleService.updateById(role);if (b != false) {return true;} else {return false;}}

根据角色的名称查询角色

        //根据角色的名称查询角色的信息@PostMapping("/findByname")public RespBean findByName(@RequestBody Role role){Page<Role> page=new Page<>();//构建条件QueryWrapper<Role> wrapper = new QueryWrapper<>();String zname = role.getZname();//判断条件是否为空if (!StringUtils.isEmpty(zname)) {wrapper.like("zname", zname);}Page<Role> page1 = roleService.page(page, wrapper);return RespBean.success("查询成功!",page1);}

根据角色的id获取菜单的id

    //根据角色id获取菜单的id@GetMapping("/mid/{rid}")public List<Integer> getMidByRid(@PathVariable Integer rid){List<MenuRole> rid1 = menuRoleService.list(new QueryWrapper<MenuRole>().eq("rid", rid));List<Integer> collect = rid1.stream().map(MenuRole::getMid).collect(Collectors.toList());return collect;}

给角色分配权限

mid方法

获取角色信息,因为我们不仅仅是在编辑角色的时候会用到这个方法,在回显关联菜单的时候也要被调用,因此我们需要把角色关联的所有菜单的id全部查询出来,也就是分配权限的功能。对应到前端,点击分配权限会弹出所有的菜单列表,然后根据角色已经关联的id回显勾选上已经关联过的,效果如下:

然后点击保存的时候,我们需要将角色的id和所有已经勾选上的菜单的id的数组一起传过来,如下:

//更新角色的菜单@PutMapping("/")public RespBean updateMenuRole(Integer rid,Integer[] mids){return menuRoleService.updateMenuRole(rid,mids);}

Service层

public interface MenuRoleService extends IService<MenuRole> {/*** 更新角色菜单*/RespBean updateMenuRole(Integer rid, Integer[] mids);
}

ServiceImpl层

public class MenuRoleServiceImpl extends ServiceImpl<MenuRoleMapper, MenuRole> implements MenuRoleService {@Autowiredprivate MenuRoleMapper menuRoleMapper;/*** 更新角色菜单*/@Override@Transactionalpublic RespBean updateMenuRole(Integer rid, Integer[] mids) {menuRoleMapper.delete(new QueryWrapper<MenuRole>().eq("rid", rid));if (null == mids || 0 == mids.length) {return RespBean.success("更新成功!");}Integer result = menuRoleMapper.insertRecord(rid, mids);if (result==mids.length){return RespBean.success("更新成功!");}return RespBean.error("更新失败!");}
}

首先执行删除的方法,删除角色对应的菜单,然后判断是否有对应的菜单,如果没有则执行插入的方法。

ok,角色管理到这儿就已经结束了。

用户接口的开发:

用户管理里边有个用户关联角色的操作和角色关联菜单的操作差不多,其他的增删改查的操作也都一样,多了一个重置密码的操作。

UserController

public class UserController {@Autowiredprivate AdminService adminService;@Autowiredprivate RoleService roleService;@Autowiredprivate HttpServletRequest req;@Autowiredprivate BCryptPasswordEncoder passwordEncoder;@Autowiredprivate AdminRoleService adminRoleService;//获取所有的角色@GetMapping("/roles")public List<Role> getAllRoles(){return roleService.list();}//根据角色id获取菜单的id@GetMapping("/rid/{adminid}")public List<Integer> getMidByRid(@PathVariable Integer adminid){List<AdminRole> rid1 = adminRoleService.list(new QueryWrapper<AdminRole>().eq("adminid", adminid));List<Integer> collect = rid1.stream().map(AdminRole::getRid).collect(Collectors.toList());return collect;}//根据用户名分页显示用户的信息@GetMapping("/list")public RespBean list(String username){Page<Admin> pageData = adminService.page(getPage(), new QueryWrapper<Admin>().like(StrUtil.isNotBlank(username), "username", username));pageData.getRecords().forEach(u -> {u.setRoles(roleService.listRoleByUserId(Long.valueOf(u.getId())));});return RespBean.success("",pageData);}//添加用户@PostMapping("/add")public RespBean addUser(@RequestBody Admin admin){//初始化密码String password=passwordEncoder.encode(Rsultcode.DEFAULT_PASSWORD);admin.setPassword(password);if (adminService.save(admin)){return RespBean.success("添加成功!");}return RespBean.error("添加失败!");}//删除用户@DeleteMapping("/delete/{id}")public RespBean deleteRole(@PathVariable Integer id){if(adminService.removeById(id)){return RespBean.success("删除成功!");}return RespBean.error("删除失败!");}//修改用户的信息@PostMapping("/update")public Boolean update(@RequestBody Admin admin) {boolean b = adminService.updateById(admin);if (b != false) {return true;} else {return false;}}//给用户分配角色@PutMapping("/")public RespBean rolePerm( Integer adminid,Integer[] rolesIds){return adminRoleService.updateAdminRole(adminid,rolesIds);}//重置密码@PostMapping("/repass/{adminid}")public RespBean repass(@PathVariable Long adminid){Admin admin=adminService.getById(adminid);admin.setPassword(passwordEncoder.encode(Rsultcode.DEFAULT_PASSWORD));adminService.updateById(admin);return RespBean.success("");}

最后的效果如下:

基于Springboot+vue前后端分离的项目--后端笔记相关推荐

  1. 基于SpringBoot+Vue前后端分离的在线教育平台项目

    基于SpringBoot+Vue前后端分离的在线教育平台项目 赠给有缘人,希望能帮助到你!也请不要吝惜你的大拇指,你的Star.点赞将是对我最大的鼓励与支持! 开源传送门: 后台:Gitee | Gi ...

  2. 基于springboot+vue前后端分离的婚礼服装租赁系统

    1 简介 今天向大家介绍一个帮助往届学生完成的毕业设计项目,基于springboot+vue前后端分离的婚礼服装租赁系统. 计算机毕业生设计,课程设计需要帮助的可以找我 源码获取------> ...

  3. 基于springboot vue前后端分离的图书借阅管理系统源码

    请观看视频: 基于springboot vue前后端分离的图书借阅管理系统源码 <project xmlns="http://maven.apache.org/POM/4.0.0&qu ...

  4. 基于springboot+vue前后端分离的学生在线考试管理系统

    一.基于springboot+vue前后端分离的学生在线考试管理系统 本系统通过教师用户创建班级编写试卷信息然后发布到班级.学生用户进入班级,在线作答,考试结果数据通过网络回收,系统自动进行判分,生成 ...

  5. 视频教程-Springboot+Vue前后的分离整合项目实战-Java

    Springboot+Vue前后的分离整合项目实战 10多年互联网一线实战经验,现就职于大型知名互联网企业,架构师, 有丰富实战经验和企业面试经验:曾就职于某上市培训机构数年,独特的培训思路,培训体系 ...

  6. Jeecg-Boot 2.0.0 版本发布,基于Springboot+Vue 前后端分离快速开发平台

    Jeecg-Boot 2.0.0 版本发布,前后端分离快速开发平台 Jeecg-Boot项目简介 源码下载 升级日志 Issues解决 v1.1升级到v2.0不兼容地方 系统截图 Jeecg-Boot ...

  7. 酒店管理|基于Springboot+Vue前后端分离实现酒店管理系统

    作者主页:编程指南针 作者简介:Java领域优质创作者.CSDN博客专家 .掘金特邀作者.多年架构师设计经验.腾讯课堂常驻讲师 主要内容:Java项目.毕业设计.简历模板.学习资料.面试题库.技术互助 ...

  8. 《SpringBoot+vue全栈开发实战项目》笔记

    前言 Spring 作为一个轻量级的容器,在JavaEE开发中得到了广泛的应用,但是Spring 的配置繁琐臃肿,在和各种第三方框架进行整合时代码量都非常大,并且整合的代码大多是重复的,为了使开发者能 ...

  9. 光学元件工艺管理系统源码:基于springboot+Vue前后端分离项目

最新文章

  1. JavaScript深入理解对象方法——Object.entries()
  2. 使用 Navicat Lite 连接 Oracle 数据库
  3. 初创互联网公司简明创业指南 - YC新掌门Sam Altman
  4. python列拼接dataframe_如何将两个dataframe中的两列合并为新dataframe(pandas)的一列?...
  5. mapinfo图层导入奥维_(通信技能分享)怎样把谷歌地球上画的路线图导入到测试软件中!...
  6. android 获取编译日期,flutter学习笔记(2)android编译,以及如何加快首次编译时间。...
  7. 谷歌技术三宝之MapReduce(转)
  8. 团队作业4——第一次项目冲刺(Alpha版本)2017.4.23
  9. 2021-2025年中国超声波管道监测系统行业市场供需与战略研究报告
  10. 内核提速开机linux,Linux启动全线提速法
  11. Python 异常(Exception)
  12. 3分钟tips:泛函中,什么是开映像定理?
  13. R语言之高级数据分析「聚类分析」
  14. 111、锐捷交换机如何配置?一步步详解,交换机配置再也不难了
  15. 诺基亚 PC Internet 访问 N70为例
  16. BWA处理WES文件
  17. 代理模式,明星经纪人--Java
  18. RabbitMq集成SpirngBoot
  19. Windows11之Dev-C++超详细下载安装与使用教程
  20. Games101 笔记 Lecture 7-9 Shading (Illumination, Shading)

热门文章

  1. Java中的Map及其使用
  2. SpringCloud重试机制配置
  3. PHP:PhpSpreadsheet实现Excel的读取和写入
  4. u盘装puppy linux,将PuppyLinux安装到U盘
  5. python:读取Excel文件
  6. CTF题库实验吧女神 (猫流大大发现一个女神,你能告诉我女神的名字么(名字即是flag))
  7. 2022年数据中心产业发展将呈现三大新趋势
  8. 函数式接口Stream类
  9. 二类电商运营怎么选品 二类电商怎么运营?
  10. python自动输入账号密码并识别验证码登录