项目介绍:
一个前后端分离的管理系统。前端使用vue框架,后端使用Java的springboot框架。
后台框架:SpringBoot+SpringSecurity+jwt+mybatis-plus+swagger2
环境:JDK1.8
软件: idea
前端框架:vue ,vue_cli,element
前端Vue教程:Vue之完整dome
软件: HBuilder x
项目步骤流程
首先是数据库表
sys_user,sys_role,sys_user_role
CREATE TABLE `sys_role` (
`Id` char(32) NOT NULL,
`roleName` varchar(16) NOT NULL,
`detail` varchar(255) DEFAULT NULL COMMENT '描述',
`delflag` tinyint(1) NOT NULL COMMENT '删除标识:1-删除 0-未删除',
`createTime` datetime DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='权限表';
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', 'ROLE_ADMIN', null, '0', null);
INSERT INTO `sys_role` VALUES ('2', 'ROLE_USER', null, '0', null);
--分割线----------------------------
CREATE TABLE `sys_user` (
`id` char(32) NOT NULL,
`name` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`delflag` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标识:1-删除 0-未删除',
`createTime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', 'admin', '$2a$10$dB4FzduqBNFKAhEgwEtRae8Bco/c0nXaQyFXivVxiZMoz4Kyxg9Wy', '0', null);
INSERT INTO `sys_user` VALUES ('2', 'user', '$2a$10$jcg0hO4iFFo.15MKZ0MFbuneOwbna7uGBRw0NYCyXkGHSYOSaWg9a\r\n', '0', null);
INSERT INTO `sys_user` VALUES ('3', '小明', '$2a$10$kRhBvIZXB9p7bHNuShjqUe0em.gIznIDhrbd.mFAaTmns3EknAsbK\r\n', '0', null);
INSERT INTO `sys_user` VALUES ('4', '小红', '$2a$10$kRhBvIZXB9p7bHNuShjqUe0em.gIznIDhrbd.mFAaTmns3EknAsbK\r\n', '0', null);
INSERT INTO `sys_user` VALUES ('5', '小梅', '$2a$10$kRhBvIZXB9p7bHNuShjqUe0em.gIznIDhrbd.mFAaTmns3EknAsbK\r\n', '0', null);
INSERT INTO `sys_user` VALUES ('6', '小刚', '$2a$10$kRhBvIZXB9p7bHNuShjqUe0em.gIznIDhrbd.mFAaTmns3EknAsbK\r\n', '0', null);
INSERT INTO `sys_user` VALUES ('7', '小智', '$2a$10$61K6xRiuNpB5rR/Z7syvb.upcy0nNb0nsM9Il07h5jyHUG7vnnjwK\r\n', '0', null);
--分割线----------------------------
CREATE TABLE `sys_user_role` (
`user_id` char(32) NOT NULL,
`role_id` char(32) NOT NULL,
PRIMARY KEY (`user_id`,`role_id`),
KEY `fk_role_id` (`role_id`),
CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`Id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户-角色表';
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('1', '2');
这里admin,user的账号和密码是一样的(如:账号:admin 密码:admin)。其他密码都是123。
1.idea 新建SpringBoot项目
初始结构如下
导入pom.xml依赖配置
<properties>
<java.version>1.8</java.version>
<spring.version>2.3.5.RELEASE</spring.version>
<mybatis-plus.version>3.4.1</mybatis-plus.version>
<mg.version>1.3.7</mg.version>
<mysql.version>8.0.22</mysql.version>
<swagger.version>2.5.0</swagger.version>
<lombok.version>1.16.10</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mybatis-plus自动代码生成-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>nl.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
</dependencies>
2.配置application.yml
server:
port: 8090
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/materiel?serverTimezone=Asia/Shanghai&allowMultiQueries=true&characterEncoding=utf-8
password: root
username: root
devtools:
restart:
additional-paths: src/main/java
enabled: true
thymeleaf:
cache: true
#切面启用
aop:
proxy-target-class: true
auto: true
mybatis:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
mapper-locations: classpath:/mapper/*.xml
3.使用代码自动生成工具类
3.1 依赖包和配置文件
使用mybatis-plus自动代码生成需导入包
<!--mybatis-plus自动代码生成-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 模板引擎 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
创建类MybatisPlusGenerator
package com.jykj.materiel;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;
import java.util.Scanner;
public class MybatisPlusGenerator {
/**
* <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.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 创建代码生成器对象
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(scanner("请输入你的项目路径") + "/src/main/java");
gc.setAuthor("xiaoge");
//生成之后是否打开资源管理器
gc.setOpen(false);
//重新生成时是否覆盖文件
gc.setFileOverride(false);
//%s 为占位符
//mp生成service层代码,默认接口名称第一个字母是有I
gc.setServiceName("%sService");
//设置主键生成策略 自动增长
gc.setIdType(IdType.AUTO);
//设置Date的类型 只使用 java.util.date 代替
gc.setDateType(DateType.ONLY_DATE);
//开启实体属性 Swagger2 注解
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/materiel?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
//使用mysql数据库
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("请输入模块名"));
pc.setParent("com.jykj");
pc.setController("controller");
pc.setService("service");
pc.setServiceImpl("service.impl");
pc.setMapper("mapper");
pc.setEntity("entity");
pc.setXml("dao");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
//设置哪些表需要自动生成
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
//实体类名称驼峰命名
strategy.setNaming(NamingStrategy.underline_to_camel);
//列名名称驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//使用简化getter和setter
strategy.setEntityLombokModel(true);
//设置controller的api风格 使用RestController
strategy.setRestControllerStyle(true);
//驼峰转连字符
strategy.setControllerMappingHyphenStyle(true);
//忽略表中生成实体类的前缀
//strategy.setTablePrefix("tb_");
mpg.setStrategy(strategy);
mpg.execute();
}
}
运行main方法使用
3.2 创建controller类
实现简单的与数据库交互
@Controller
public class LoginController {
@Autowired
private SysUserService sysUserService;
@GetMapping("/test")
@ResponseBody
public String test(){
HashMap<String,Object> map=new HashMap<>();
map.put("code",200);
map.put("data","成功");
List<SysUser> sysUser=sysUserService.list();
map.put("items",sysUser);
String jsonString = JSONObject.toJSONString(map);
return jsonString;
}
}
3.3 运行项目查看是否成功
4.同一公共返回类
4.1 状态码
package com.jykj.materiel.response;
/**
* @Author: NieChangan
* @Description: 返回码定义
* 规定:
* #200表示成功
* #1001~1999 区间表示参数错误
* #2001~2999 区间表示用户错误
* #3001~3999 区间表示接口异常
* #后面对什么的操作自己在这里注明就行了
*/
public enum ResultCode implements CustomizeResultCode{
/* 成功 */
SUCCESS(200, "成功"),
/* 默认失败 */
COMMON_FAIL(999, "失败"),
/* 参数错误:1000~1999 */
PARAM_NOT_VALID(1001, "参数无效"),
PARAM_IS_BLANK(1002, "参数为空"),
PARAM_TYPE_ERROR(1003, "参数类型错误"),
PARAM_NOT_COMPLETE(1004, "参数缺失"),
/* 用户错误 */
USER_NOT_LOGIN(2001, "用户未登录"),
USER_ACCOUNT_EXPIRED(2002, "账号已过期"),
USER_CREDENTIALS_ERROR(2003, "密码错误"),
USER_CREDENTIALS_EXPIRED(2004, "密码过期"),
USER_ACCOUNT_DISABLE(2005, "账号不可用"),
USER_ACCOUNT_LOCKED(2006, "账号被锁定"),
USER_ACCOUNT_NOT_EXIST(2007, "账号不存在"),
USER_ACCOUNT_ALREADY_EXIST(2008, "账号已存在"),
USER_ACCOUNT_USE_BY_OTHERS(2009, "账号下线"),
/*部门错误*/
DEPARTMENT_NOT_EXIST(3007, "部门不存在"),
DEPARTMENT_ALREADY_EXIST(3008, "部门已存在"),
/* 业务错误 */
NO_PERMISSION(3001, "没有权限"),
/*运行时异常*/
ARITHMETIC_EXCEPTION(9001,"算数异常");
private Integer code;
private String message;
ResultCode(Integer code,String message){
this.code = code;
this.message = message;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
4.2 统一返回结果
package com.jykj.materiel.response;
/**
* @author NieChangan
*/
public interface CustomizeResultCode {
/**
* 获取错误状态码
* @return 错误状态码
*/
Integer getCode();
/**
* 获取错误信息
* @return 错误信息
*/
String getMessage();
}
package com.jykj.materiel.response;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* 公共返回结果
* @author NieChangan
*/
@Data
public class Result {
@ApiModelProperty(value = "是否成功")
private Boolean success;
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private Map<String,Object> data = new HashMap<>();
/**
* 构造方法私有化,里面的方法都是静态方法
* 达到保护属性的作用
*/
private Result(){
}
/**
* 这里是使用链式编程
*/
public static Result ok(){
Result result = new Result();
result.setSuccess(true);
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(ResultCode.SUCCESS.getMessage());
return result;
}
public static Result error(){
Result result = new Result();
result.setSuccess(false);
result.setCode(ResultCode.COMMON_FAIL.getCode());
result.setMessage(ResultCode.COMMON_FAIL.getMessage());
return result;
}
/**
* 自定义返回成功与否
* @param success
* @return
*/
public Result success(Boolean success){
this.setSuccess(success);
return this;
}
public Result message(String message){
this.setMessage(message);
return this;
}
public Result code(Integer code){
this.setCode(code);
return this;
}
public Result data(String key,Object value){
this.data.put(key,value);
return this;
}
public Result data(Map<String,Object> map){
this.setData(map);
return this;
}
}
4.3 测试返回数据
修改test测试方法
@Controller
public class LoginController {
private Logger logger= LoggerFactory.getLogger(LoginController.class);
@Autowired
private SysUserService sysUserService;
@GetMapping("/test")
@ResponseBody
public Result test(){
List<SysUser> sysUser=sysUserService.list();
return Result.ok().data("user",sysUser);
}
}
5.整合SpringSecurity
Springboot+SpringSecurity入门教程 https://blog.csdn.net/yuanlaijike/article/details/80249235
5.1.配置SpringSecurity
需要依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
UserDetailsServiceImpl类:用于获取用户和分配权限。
package com.jykj.materiel.config.securityConfig;
import com.jykj.materiel.system.entity.SysRole;
import com.jykj.materiel.system.entity.SysUser;
import com.jykj.materiel.system.entity.SysUserRole;
import com.jykj.materiel.system.service.SysRoleService;
import com.jykj.materiel.system.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @Author: hjl
* @Date: 2020/11/10 0010 15:27
*/
@Service("UserDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SysUserService userService;
@Autowired
private SysRoleService roleService;
@Autowired
private SysUserService.SysUserRoleService userRoleService;
@Override
public UserDetails loadUserByUsername(String username) {
System.out.println("userName:"+ username);
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 从数据库中取出用户信息
SysUser user = userService.selectByName(username);
// 判断用户是否存在
if(user == null) {
throw new BadCredentialsException("用户名不存在");
}
// 添加权限
List<SysUserRole> userRoles = userRoleService.listByUserId(user.getId());
for (SysUserRole userRole : userRoles) {
SysRole role = roleService.selectById(userRole.getRoleId());
authorities.add(new SimpleGrantedAuthority(role.getRolename()));
}
// 返回UserDetails实现类
return new User(user.getName(), user.getPassword(), authorities);
}
}
WebSecurityConfig 类:配置拦截、跳转和比较密码
package com.jykj.materiel.config.securityConfig;
import org.springframework.beans.factory.annotation.Autowired;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author: hjl
* @Date: 2020/11/10 0010 15:31
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
/**
* @param charSequence 输入的密码
* @param s 数据库加密的密码
* @return
*/
@Override
public boolean matches(CharSequence charSequence, String s) {
//System.out.println("matches:-》charSequence: "+charSequence.toString()+" ,S: "+s);
BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
boolean f = bcryptPasswordEncoder.matches(charSequence,s);//比较密码
System.out.println("比较密码:"+f);
return f;
}
});
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 如果有允许匿名的url,填在下面
.antMatchers("/regis","/register","/test").permitAll()
.anyRequest().authenticated()
.and()
// 设置登陆页
.formLogin().loginPage("/login")
// 设置登陆成功页
.defaultSuccessUrl("/").permitAll()
// 自定义登陆用户名和密码参数,默认为username和password
// .usernameParameter("username")
// .passwordParameter("password")
.and()
// 异常处理跳转
.exceptionHandling().accessDeniedPage("/500")
.and()
.logout().permitAll();
//开启记住我功能
// http.rememberMe().rememberMeParameter("remember-me");
//以下这句就可以控制单个用户只能创建一个session,也就只能在服务器登录一次
http.sessionManagement().maximumSessions(1).expiredUrl("/login");
// 关闭CSRF跨域
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers("/css/**", "/js/**");
}
}
5.2登录注册业务
在template目录下新建 login.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆</title>
</head>
<body>
<h1>登陆1</h1>
<form method="post" action="">
<div>
用户名:<input type="text" name="username">
</div>
<div>
密码:<input type="password" name="password">
</div>
<!--<div class="checkbox">-->
<!--<label><input type="checkbox" id="rememberme" name="remember-me"/>记住我</label>-->
<!--</div>-->
<div>
<button type="submit">立即登陆</button>
</div>
<a href="/regis"> 注册</a>
</form>
</body>
</html>
在刚刚的配置类WebSecurityConfig 中,拦截跳转。
在loginController中添加登录页和登录成功页面跳转。
@GetMapping("/login")
public String showLogin() {
return "login";
}
@GetMapping("/")
public String showHome(){
String name= SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("当前登录用户:"+ name);
return "home.html";
}
由于在WebSecurityConfig中配置了登录页和成功跳转页,所以这里不用写action的值。
访问首页http://localhost:8090/会被拦截跳转到自定义的login页面。
控制台打印顺序
根据注解识别权限
在loginController中添加ROLE_ADMIN和ROLE_USER权限控制
@GetMapping("/admin")
@ResponseBody
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String printAdmin() {
return "如果你看见这句话,说明你有ROLE_ADMIN角色";
}
@GetMapping("/user")
@ResponseBody
@PreAuthorize("hasRole('ROLE_USER')")
public String printUser() {
return "如果你看见这句话,说明你有ROLE_USER角色";
}
你可以自己测试一下,注意数据库中用户是否有权限。
注册时的加密使用的是BCryptPasswordEncoder 。
@Test
void contextLoads() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encode = encoder.encode("123");
System.out.println(encode);
String charSequence="$2a$10$61K6xRiuNpB5rR/Z7syvb.upcy0nNb0nsM9Il07h5jyHUG7vnnjwK";
boolean f = bcryptPasswordEncoder.matches(charSequence,s);//比较密码
System.out.println("比较密码:"+f);
}
6.整合swagger2
6.1配置swagger2
package com.jykj.materiel.config;
import io.swagger.annotations.ApiOperation;
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.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @Author: hjl
* @Date: 2020/12/4 0004 14:02
*/
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//只扫描有注解的方法并显示
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("xx管理系统API文档")
.description("接口 http://localhost:8090/")
.termsOfServiceUrl("www.xx.cn")
// .contact(new Contact("xx科技","www.xxkj.com","xxkj@198.com"))
.version("1.0")
.build();
}
}
在对应方法上添加注解
@Controller
@Api(description = "登录相关api")
public class LoginController {
private Logger logger= LoggerFactory.getLogger(LoginController.class);
@Autowired
private SysUserService sysUserService;
@GetMapping("/test")
@ResponseBody
@ApiOperation(value="测试接口",notes = "测试查询所有用户信息")
public Result test(){
List<SysUser> sysUser=sysUserService.list();
return Result.ok().data("user",sysUser);
}
}
重新启动项目 访问: http://localhost:8090/swagger-ui.html
6.2使用knife4j美化swagger2
需要依赖包knife4j
官方地址:https://xiaoym.gitee.io/knife4j/documentation/get_start.html
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.7</version>
</dependency>
由于使用swagger2 已经配置好了apiInfo()这里就没有重新配置。
访问地址:http://localhost:8090/doc.html
7.同一异常处理
新建GlobalExceptionHandler.class类
package com.jykj.materiel.handler;
import com.jykj.materiel.response.Result;
import com.jykj.materiel.response.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Author: hjl
* @Date: 2020/12/7 0007 13:24
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
*
* 全局异常处理,捕获所有异常。
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
// e.printStackTrace();
log.error(e.getMessage());
return Result.error();
}
/**
* 自定义ArithmeticException的异常处理,捕获并返回。
* @param e
* @return
*/
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Result error(ArithmeticException e){
// e.printStackTrace();
log.error(ResultCode.ARITHMETIC_EXCEPTION.getMessage());
return Result.error()
.code(ResultCode.ARITHMETIC_EXCEPTION.getCode())
.message(ResultCode.ARITHMETIC_EXCEPTION.getMessage());
}
}
在LoginController中添加异常
@GetMapping("/test")
@ResponseBody
@ApiOperation(value="测试接口",notes = "测试查询所有用户信息")
public Result test(){
int i=1/0; //测试捕获算数异常ArithmeticException
List<SysUser> sysUser=sysUserMapper.list();
return Result.ok().data("user",sysUser);
}
8.同一日志处理
配置application.yml 添加Slf4jImpl
mybatis:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
配置logback-spring.xml
只需修改日志输出路径和sql打印位置
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 级别从高到低 OFF 、 FATAL 、 ERROR 、 WARN 、 INFO 、 DEBUG 、 TRACE 、 ALL -->
<!-- 日志输出规则 根据当前ROOT 级别,日志输出时,级别高于root默认的级别时 会输出 -->
<!-- 以下 每个配置的 filter 是过滤掉输出文件里面,会出现高级别文件,依然出现低级别的日志信息,通过filter 过滤只记录本级别的日志 -->
<!-- scan 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。 -->
<!-- scanPeriod 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 动态日志级别 -->
<jmxConfigurator/>
<!--*****************************************************************************-->
<!--自定义项 开始-->
<!--*****************************************************************************-->
<!-- 定义日志文件 输出位置 -->
<property name="log.home_dir" value="D:/dome/Materiel-logs"/>
<property name="log.app_name" value="http-demo"/>
<!-- 日志最大的历史 30天 -->
<property name="log.maxHistory" value="30"/>
<property name="log.maxSize" value="5MB"/>
<!-- 日志界别 -->
<property name="log.level" value="info"/>
<!-- 打印sql语句 需要指定dao/mapper层包的位置 -->
<property name="mapper.package" value="com.jykj.materiel.system.dao"/>
<!-- 彩色日志 -->
<!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
<!-- magenta:洋红 -->
<!-- boldMagenta:粗红-->
<!-- cyan:青色 -->
<!-- white:白色 -->
<!-- magenta:洋红 -->
<property name="CONSOLE_LOG_PATTERN"
value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
<!--*****************************************************************************-->
<!--自定义项 结束-->
<!--*****************************************************************************-->
<!-- ConsoleAppender 控制台输出日志 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
<!-- 设置日志输出格式 -->
${CONSOLE_LOG_PATTERN}
</pattern>
</encoder>
</appender>
<!-- ERROR级别日志 -->
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 RollingFileAppender -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录WARN级别的日志 -->
<!-- 果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置过滤级别 -->
<level>ERROR</level>
<!-- 用于配置符合过滤条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合过滤条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<!-- 最常用的滚动策略,它根据时间来制定滚动策略.既负责滚动也负责触发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志输出位置 可相对、和绝对路径 -->
<fileNamePattern>
${log.home_dir}/error/%d{yyyy-MM-dd}/${log.app_name}-%i.log
</fileNamePattern>
<!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动,且<maxHistory>是6,
则只保存最近6个月的文件,删除之前的旧文件。注意,删除旧文件是,那些为了归档而创建的目录也会被删除 -->
<maxHistory>${log.maxHistory}</maxHistory>
<!--日志文件最大的大小-->
<MaxFileSize>${log.maxSize}</MaxFileSize>
</rollingPolicy>
<encoder>
<pattern>
<!-- 设置日志输出格式 -->
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</encoder>
</appender>
<!-- WARN级别日志 appender -->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录WARN级别的日志 -->
<!-- 果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置过滤级别 -->
<level>WARN</level>
<!-- 用于配置符合过滤条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合过滤条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志输出位置 可相对、和绝对路径 -->
<fileNamePattern>${log.home_dir}/warn/%d{yyyy-MM-dd}/${log.app_name}-%i.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
<!--当天的日志大小 超过MaxFileSize时,压缩日志并保存-->
<MaxFileSize>${log.maxSize}</MaxFileSize>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- INFO级别日志 appender -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.home_dir}/info/%d{yyyy-MM-dd}/${log.app_name}-%i.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
<MaxFileSize>${log.maxSize}</MaxFileSize>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- DEBUG级别日志 appender -->
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.home_dir}/debug/%d{yyyy-MM-dd}/${log.app_name}-%i.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
<MaxFileSize>${log.maxSize}</MaxFileSize>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- TRACE级别日志 appender -->
<appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>TRACE</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.home_dir}/trace/%d{yyyy-MM-dd}/${log.app_name}-%i.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
<MaxFileSize>${log.maxSize}</MaxFileSize>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %logger - %msg%n</pattern>
</encoder>
</appender>
<!--设置一个向上传递的appender,所有级别的日志都会输出-->
<appender name="app" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.home_dir}/app/%d{yyyy-MM-dd}/${log.app_name}-%i.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
<MaxFileSize>${log.maxSize}</MaxFileSize>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %logger - %msg%n</pattern>
</encoder>
</appender>
<!--org.springframework.web包下的类的日志输出-->
<logger name="org.springframework.web" additivity="false" level="WARN">
<appender-ref ref="WARN"/>
</logger>
<!--dao层包下的类的日志输出-->
<logger name="${mapper.package}" additivity="false" level="DEBUG">
<appender-ref ref="app"/>
<appender-ref ref="ERROR"/>
<!--打印控制台-->
<appender-ref ref="CONSOLE"/>
</logger>
<!-- root级别 DEBUG -->
<root>
<!-- 打印debug级别日志及以上级别日志 -->
<level value="${log.level}"/>
<!-- 控制台输出 -->
<appender-ref ref="CONSOLE"/>
<!-- 不管什么包下的日志都输出文件 -->
<appender-ref ref="ERROR"/>
<appender-ref ref="INFO"/>
<appender-ref ref="WARN"/>
<appender-ref ref="DEBUG"/>
<appender-ref ref="TRACE"/>
</root>
</configuration>
9.分页查询
使用mybatis-plus的Page进行分页。
官网:https://baomidou.com/
pom.xml引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
9.1 mybatis-plus单表分页
package com.jykj.materiel.system.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jykj.materiel.response.Result;
import com.jykj.materiel.system.entity.SysUser;
import com.jykj.materiel.system.service.SysUserService;
import io.swagger.annotations.ApiOperation;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Author: hjl
* @Date: 2020/12/7 0007 15:12
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private SysUserService sysUserService;
// mybatis单表分页很简单使用page
@PostMapping("/findUserList")
@ApiOperation(value="测试分页",notes = "测试查询所有用户数据")
public Result findUserList(@Param("current")long current,@Param("size")long size) {
// 对用户进行分页,泛型中注入实体类
Page<SysUser> page=new Page<>(current,size);
Page<SysUser> userPage=sysUserService.page(page);
long total=userPage.getTotal();
// 使用Math.ceil()向上取整 如果使用的vue框架不用计算页数
//int PageCount=(int) Math.ceil((double) total/(double)size);
List<SysUser> records=userPage.getRecords();
return Result.ok().data("total",total)
//.data("PageCount",PageCount)
.data("records",records);
}
}
测试结果:
9.2 mybatis-plus多表条件查询分页
使用 mybatis-plus的IPage
首先查看IService中源码,会发现有一个page翻页查询
和之前的单表查询一样传入page,只是多了个实体对象封装操作类 queryWrapper 。
这里以用户表和权限角色表联合条件查询为例;
给其他用户赋予 ROLE_USER 角色:
INSERT INTO `materiel`.`sys_user_role` (`user_id`, `role_id`) VALUES ('2', '2');
INSERT INTO `materiel`.`sys_user_role` (`user_id`, `role_id`) VALUES ('3', '2');
INSERT INTO `materiel`.`sys_user_role` (`user_id`, `role_id`) VALUES ('4', '2');
INSERT INTO `materiel`.`sys_user_role` (`user_id`, `role_id`) VALUES ('5', '2');
INSERT INTO `materiel`.`sys_user_role` (`user_id`, `role_id`) VALUES ('6', '2');
INSERT INTO `materiel`.`sys_user_role` (`user_id`, `role_id`) VALUES ('7', '2');
sys_user 、sys_user_role 、sys_role 多表联合查询
SELECT
u.id Id,u.name name,u.`password` password,s.roleName roleName
FROM
sys_user AS u
LEFT JOIN sys_user_role AS r ON u.id = r.user_id
LEFT JOIN sys_role as s on s.Id=r.role_id
多表条件分页:
①.SysUserService中:
public interface SysUserService extends IService<SysUser> {
IPage<SysUser> UserPage(Page<SysUser> page, @Param(Constants.WRAPPER) QueryWrapper<SysUser> wrapper);
}
②.SysUserServiceImpl中:
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
@Override
public IPage<SysUser> UserPage(Page<SysUser> page, QueryWrapper<SysUser> wrapper) {
return this.baseMapper.UserPages(page,wrapper);
}
}
③.SysUserMapper中:
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
IPage<SysUser> UserPages(Page<SysUser> page, @Param(Constants.WRAPPER) QueryWrapper<SysUser> queryWrapper);
}
④.SysUserMapper.xml中:
${ew.customSqlSegment} 是传入的条件语句
如果有重复数据这里必须使用GROUP BY ,因为要查询记录条数。
<select id="UserPages" resultType="com.jykj.materiel.system.entity.SysUser">
SELECT
u.id Id,u.name name,u.`password` password,s.roleName roleName
FROM
sys_user AS u
LEFT JOIN sys_user_role AS r ON u.id = r.user_id
LEFT JOIN sys_role as s on s.Id=r.role_id
${ew.customSqlSegment}
GROUP BY Id
</select>
⑤.新建类实体类SysUserVo查询条件类:
@Data
public class SysUserVo {
private String Name;
private String roleName;
}
⑥.SysUserController中调用:
关于注解@RequestBody :https://www.cnblogs.com/zly123/p/10853049.html
@PostMapping("/finds")
@ApiOperation(value="多表条件分页",notes = "测试条件查询多表所有用户数据")
public Result finds(@RequestBody SysUserVo userVo , @RequestParam("current")long current, @RequestParam("size")long size) {
// 对用户进行分页,泛型中注入实体类
Page<SysUser> page=new Page<>(current,size);
QueryWrapper<SysUser> queryWrapper = getUserWrapper(userVo);
IPage<SysUser> userPage=sysUserService.UserPage(page,queryWrapper);
long total=userPage.getTotal();
List<SysUser> users=userPage.getRecords();
return Result.ok().data("total",total).data("users",users);
}
private QueryWrapper<SysUser> getUserWrapper(SysUserVo userVo){
QueryWrapper<SysUser> queryWrapper=new QueryWrapper<>();
if (userVo!=null){
if (!StringUtils.isEmpty(userVo.getName())){
queryWrapper.eq("name",userVo.getName());
}
if (!StringUtils.isEmpty(userVo.getRoleName())){
queryWrapper.eq("roleName",userVo.getRoleName());
}
}
return queryWrapper;
}
分页
输入条件分页查询:
10.整合SpringSecurity和Jwt
可以看这篇博客:SpringSecurity权限管理系统实战—六、SpringSecurity整合JWT
11.业务逻辑
后台github地址: