文章目录
前置知识
拦截器和过滤器区别:
过滤器
init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。
doFilter() :容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter。
destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("Filter 前置");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("Filter 处理中");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
System.out.println("Filter 后置");
}
}
拦截器 (Interceptor)
是链式调用
,一个应用中可以同时存在多个拦截器Interceptor, 一个请求也可以触发多个拦截器 ,而每个拦截器的调用会依据它的声明顺序依次执行。
preHandle() :这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
postHandle():只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。 有意思的是:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
afterCompletion():只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor 前置");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor 处理中");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor 后置");
}
}
将自定义好的拦截器处理类进行注册,并通过addPathPatterns、excludePathPatterns等属性设置需要拦截或需要排除的 URL。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
}
}
拦截器 or 过滤器
- SpringBoot
- SpringSecurity
- Servlet
Servlet
过滤器
Filter
- 依靠tomcat容器
- 是基于函数回调
- 请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后
- 容器启动它就存在了
SpringBoot
拦截器
设置拦截器HandlerInterceptor
package net.pro.myvue.common.securtiy.component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @author yangpan
*/
@Component
public class MyInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
String methodName = method.getName();
logger.info("====拦截到了方法:{},在该方法执行之前执行====", methodName); // 返回true才会继续执行,返回false则取消当前请求
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("执行完方法之后进执行(Controller方法调用之后),但是此时还没进行视图渲染,也就是会返回空白");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("整个请求都处理完咯,DispatcherServlet也渲染了对应的视图咯,此时我可 以做一些清理的工作了");
}
}
然后在配置拦截器WebMvcConfigurationSupport
package net.pro.myvue.common.securtiy.component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @author yangpan
*/
@Configuration
public class InterceptorConf extends WebMvcConfigurationSupport {
@Autowired
MyInterceptor myInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).addPathPatterns("/**");
}
}
OncePerRequestFilter
json的返回值
前言
以往都是SpringMVC+Mybatis+Spring+Thymeleaf的结合通过Controller的界面跳转和数据的传输,以及前端的数据表单的传输以及Ajax请求来实现数据的交互。
现在是Springboot+Vue+Mybatis来实现前后端的分离,后端只需要单一开发业务逻辑返回一个Json的数据即可,和界面的跳转一点也不沾边了,而前端也不需要完全依靠后端,只需要后端给的数据进行渲染就可以了,两者互不干涉。
以下都是为建议,并不是强制的
- 命名
- 位置
- 形式
- 代码
命名
这个最好写成XXXDTO,而且是范型的类,如ResponseDTO
,表明它是用于响应的传输数据类型
位置
src 源码目录
|-- common 各个项目的通用类库
|-- |-- domain | --ResponseDTO ?
|-- config 项目的配置信息
|-- constant 全局公共常量
|-- handler 全局处理器
|-- interceptor 全局连接器
|-- listener 全局监听器
|-- module 各个业务
|-- |--- employee 员工模块
|-- |--- role 角色模块
|-- |--- login 登录模块
|-- third 三方服务,比如redis, oss,微信sdk等等
|-- util 全局工具类
|-- Application.java
形式
代码
/**
1. 返回状态
2. 3. @author yangpan
*/
@Setter
@Getter
public class ResponseDTO<T> {
/**
* 状态码
*/
protected Integer code;
/**
* 返回消息
*/
protected String msg;
/**
* 是否成功
*/
protected Boolean success;
/**
* 返回数据
*/
protected T data;
/**
* 自定义
*
* @param code
* @param msg
* @param success
* @param data
*/
public ResponseDTO(Integer code, String msg, Boolean success, T data) {
this.code = code;
this.msg = msg;
this.success = success;
this.data = data;
}
/**
* 错误返回信息
*
* @param msg
* @param success
*/
public ResponseDTO(String msg, Boolean success) {
this.code = 0001;
this.msg = "用户错误";
this.success = success;
}
/**
* 没有数据返回成功
*/
public ResponseDTO() {
this.code = 0000;
this.msg = "操作成功!";
}
..............可自行扩展
@Override
public String toString() {
return "ResponseDTO{" +
"code=" + code +
", msg='" + msg + '\'' +
", success=" + success +
", data=" + data +
'}';
}
}
SpringSecurity
SpringSecurity入门到精通传送门
SpringBoot结合流程
密码加密放入数据库
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
====>返回的是一个加密对象
@Autowired
PasswordEncoder e;
void encoder(){
final String pwd = e.encode("所要加密密码");
final boolean b = e.matches(pwd, "还未加密前的密码");
}
根据b判断密码是否正确
SpringSecurity本质是一些拦截器的组合
前置知识
AuthenticationEntryPoint(类)
AuthenticationEntryPoint是Spring Security Web一个概念模型接口,顾名思义,他所建模的概念是:“认证入口点”。
它在用户请求处理过程中遇到认证异常时,被ExceptionTranslationFilter用于开启特定认证方案(authentication schema)的认证流程,通俗一点就是你没认证就想访问资源就会被拦截
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
StringBuffer requestURL = request.getRequestURL();
PrintWriter writer = response.getWriter();
RespBean error = RespBean.error("您暂未登录,请先登录");
error.setCode(401);
writer.write(new ObjectMapper().writeValueAsString(error));
writer.flush();
writer.close();
}
}
AccessDeniedHandler(类)
当访问接口没有权限时,自定义返回结果,可以设置SpringSecurity认证失败了要返回的结果
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
PrintWriter writer = response.getWriter();
RespBean error = RespBean.error("您的权限不足");
error.setCode(403);
writer.write(new ObjectMapper().writeValueAsString(error));
writer.flush();
writer.close();
}
}
正文
- 依赖
- 位置
- 代码
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
位置
src 源码目录
|-- common 各个项目的通用类库
|-- |-- domain | --ResponseDTO 数据转化类型
|-- |--security |-- component 安全主件
|-- SecurityConfig ?
|-- config 项目的配置信息
|-- constant 全局公共常量
|-- handler 全局处理器
|-- interceptor 全局连接器
|-- listener 全局监听器
|-- module 各个业务
|-- |--- employee 员工模块
|-- |--- role 角色模块
|-- |--- login 登录模块
|-- third 三方服务,比如redis, oss,微信sdk等等
|-- util 全局工具类
|-- Application.java
代码
1.1 SecurityConfig
1.2 CustUserDetailsImpl
SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//自定义的数据信息
@Autowired
CustUserDetailsImpl userDetails;
//这个是用于数据密码解密的,这个必须配置不然SpringSecurity会报错
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
//http安全
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.csrf()
.disable();
}
//这个是解密的
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetails).passwordEncoder(encoder());
}
//直接跳过认证
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/success",
"/session/login",
"/swagger-ui.html", //swagger放行
"/logout",
"/session/verificationCode", //放行验证码
"/css/**", ---|
"favicon.ico", ---|--->//静态资源
"/js/**", ---|
"/index.html",
"/doc.html",
"/webjars/**",
"/swagger-resources/**",
"/v2/api-docs/**",
"/ws/**");
}
}
CustUserDetailsImpl
@Component
public class CustUserDetailsImpl implements UserDetailsService{
@Autowired
LoginDao dao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
assert s != null;
final UserLoginDTO login = dao.login(s);
if (login == null || login.equals("")) {
throw new UsernameNotFoundException("用户名不存在");
}
return new User(login.getLoginName(), login.getLoginPwd(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
PostMan
当用这个修饰@RequstBody
,只能接受JSON格式
@PostMapping("/session/login")
@ApiOperation(value = "创建用户", notes = "创建用户")
public ResponseDTO<Map<String, Object>> login(@Validated @RequestBody UserLoginDTO userLoginDTO, HttpServletRequest request) {
final ResponseDTO<Map<String, Object>> login = loginService.login(userLoginDTO, request);
return login;
}
如果需要传验证码需要先得到验证码
得到的就是接下来我们要说的Base64验证码
将它粘贴到浏览器
即可得到验证码,把它算出来
然后咱就访问,记住要符合我的箭头写的
获得结果
验证码
依赖
<!--图形验证码-->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
这个是别人写好的,直接用就可以了
java代码:
CaptchaUtil
/**
* 验证码工具类
*
* @author yangpan
*/
public class CaptchaUtil {
/**
* 验证验证码
*
* @param code
* @param code2
* @return
*/
public static boolean verify(String code, String code2) {
if (code != null) {
return code.trim().toLowerCase().equals(code2);
}
return false;
}
/**
* 设置相应头
*
* @param response HttpServletResponse
*/
public static void setHeader(HttpServletResponse response) {
response.setContentType("image/gif");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
}
/**
* 清除验证码的session
*
* @param request
*/
public static void clear(HttpServletRequest request) {
request.getSession().removeAttribute(CaptchaConstant.CAPTCHA_KEY);
}
}
获取验证码服务工具
public String verificationCode(HttpServletRequest request, HttpServletResponse response) {
//设置返回的响应头
CaptchaUtil.setHeader(response);
//这个是生成验证码的api,由以上的依赖包提供,这个是算数的api
ArithmeticCaptcha captcha = new ArithmeticCaptcha(115, 40);
//设置算数的位数
captcha.setLen(2);
//验证码的答案,返回结果
String captchaResult = captcha.text();
//将其存放在Session中,以便验证验证码的时候取出验证
final HttpSession session = request.getSession();
session.setAttribute(CaptchaConstant.CAPTCHA_KEY, captchaResult);
//把验证码转化为Base64数据,直接在前端用<img src="s"/>即可展示
final String s = captcha.toBase64();
return s;
}
获取验证码
@GetMapping("/session/verificationCode")
@ApiOperation(value = "获取验证码", notes = "获取验证码")
public String verificationCode(HttpServletRequest request, HttpServletResponse response) {
final String s = loginService.verificationCode(request, response);
return s;
}
Jwt
依赖
<!--jjwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
application.yaml
中配置
############################# jwt ########################################
jwt:
# JWT存储的请求头
tokenHeader: Authorization
# JWT 加解密使用的密钥
secret: secret
# JWT的超期限时间 (60*60*24)
expiration: 20
# JWT负载拿到开头
tokenHead: Bearer
Jwt工具类JWTTokenUtil
package net.pro.myvue.common.securtiy.component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT工具类
*
* @author yangpan
*/
@Component
public class JwtTokenUtil {
/**
* 用户名的key
*/
private static final String CLAIM_KEY_USERNAME = "sub";
/**
* jwt的创建时间的key
*/
private static final String CLAIM_KEY_CREATED = "created";
/**
* 盐
*/
@Value("${jwt.secret}")
private String secret;
/**
* 失效时间
*/
@Value("${jwt.expiration}")
private Long expiration;
/**
* 根据用户信息生成token
*
* @param userDetails
* @return
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(20);
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 根据荷载生成jwt token
*
* @param claims
* @return
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从token中获取登陆用户名
*
* @param token
* @return
*/
public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getclaimsfromtoken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 从token中获取荷载
*
* @param token
* @return
*/
private Claims getclaimsfromtoken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
e.printStackTrace();
}
return claims;
}
/**
* 生成token失效时间
*
* @return
*/
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 从token中获取过期时间
* @param token
* @return
*/
private Date getExpiredDateFromToken(String token) {
Claims claims = getclaimsfromtoken(token);
return claims.getExpiration();
}
/**
* 判断token是否失效
* @param token
* @return
*/
private boolean isTokenExpired(String token) {
Date expireDate = getExpiredDateFromToken(token);
return expireDate.before(new Date());
}
/**
* 验证token是否有效
* @param token
* @param userDetails
* @return
*/
public boolean validateToken(String token,UserDetails userDetails){
String username = getUsernameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
}
jwt配置一个拦截器认证
JwtAuthorizationTokenFilter
package net.pro.myvue.common.securtiy.component;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author yangpan
*/
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Resource
JwtTokenUtil jwtTokenUtil;
@Resource
CustUserDetailsImpl custUserDetails;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader(tokenHeader);
if(null != authHeader && authHeader.startsWith(tokenHead)){
String authToken = authHeader.substring(tokenHead.length());
String username = jwtTokenUtil.getUsernameFromToken(authToken);
//token存在用户名但是未登录
if(null != username && null == SecurityContextHolder.getContext().getAuthentication()){
//执行登录
UserDetails userDetails = custUserDetails.loadUserByUsername(username);
//验证token是否有效,重新给用户对象赋值
if(jwtTokenUtil.validateToken(authToken,userDetails)){
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
}
Mysql
角色
or 权限
之前没有分清他们之间的区别,一直认为,只要其中一个就可以了为什么要配置两种~
一个角色没有权限是毫无用处的,打比方就是老师这个角色如果没有管理学生的权限,那老师这个角色不就废了嘛
,相反有权限但是没有角色理论上是可以的,你并不是老师但是有管理学生的权限,但是复用性很差,如果一旦这个权
限系统复杂起来了,等到那天你退休了,你所掌握的权限可能需要重新的分配给新的人,使得权限分配过于混乱,所以
赋予角色权限而不是直接用户个人权限可以提高复用性,降低复杂度。
总结:
用户
=> 角色
=> 权限
后台管理数据库建立
表的建立
t_employee
首先建立员工表用于登陆,然后通过t_role_employee
查询得到员工的角色
CREATE TABLE `t_employee` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`login_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登录帐号',
`login_pwd` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登录密码',
`actual_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '员工名称',
`nick_name` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '别名',
`phone` varchar(15) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '手机号码',
`id_card` varchar(18) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '身份证',
`birthday` date DEFAULT NULL COMMENT '出生日期',
`email` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱',
`department_id` int unsigned NOT NULL COMMENT '部门id',
`is_leave` int NOT NULL DEFAULT '0' COMMENT '是否离职1是',
`is_disabled` int NOT NULL DEFAULT '0' COMMENT '是否被禁用 0否1是',
`remark` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
`create_user` int unsigned NOT NULL COMMENT '创建者id',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`is_delete` int NOT NULL DEFAULT '0' COMMENT '是否删除0否 1是',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='员工表';
员工和角色之间的关系表
t_role_employee
CREATE TABLE `t_role_employee` (
`id` int NOT NULL AUTO_INCREMENT,
`role_id` int NOT NULL COMMENT '角色id',
`employee_id` int NOT NULL COMMENT '员工id',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=214 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色员工功能表';
通过sql语句
SELECT re.id FROM t_role_employee re WHERE re.employee_id = #{employee_id};
即可与员工建立关系
t_employee
=> t_role_employee
真正的角色表,用于员工的角色认证
t_role
CREATE TABLE `t_role` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`role_name` varchar(20) NOT NULL COMMENT '角色名称',
`remark` varchar(255) DEFAULT NULL COMMENT '角色描述',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8mb3 COMMENT='角色表';
角色权限表对应表
t_role_privilege
CREATE TABLE `t_role_privilege` (
`id` int NOT NULL AUTO_INCREMENT,
`role_id` int NOT NULL COMMENT '角色id',
`privilege_key` varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限key',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11121 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色权限功能表';
权限表
t_privilege
CREATE TABLE `t_privilege` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '功能权限表主键id',
`type` tinyint NOT NULL COMMENT '1.菜单 2.功能点',
`name` varchar(50) NOT NULL COMMENT '菜单名称',
`key` varchar(1000) NOT NULL COMMENT '路由name 英文关键字',
`url` text COMMENT '路由path/type=3为API接口',
`sort` int NOT NULL DEFAULT '0' COMMENT '排序',
`parent_key` varchar(1000) DEFAULT NULL COMMENT '父级key',
`update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `key` (`key`) USING BTREE,
KEY `type` (`type`) USING BTREE,
KEY `parent_key` (`parent_key`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=127 DEFAULT CHARSET=utf8mb3 COMMENT='权限功能表';
t_position
CREATE TABLE `t_position` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`position_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '岗位名称',
`remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '岗位描述',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='岗位表';
关于DTO,VO,DO,QueryParam
DTO(Data Transfer Object): 包命名为dto
可以理解为被包装的传输数据
VO(View Object) :包命名为vo
给前端所展示的数据
mybatis-plus
自动生成代码
mybatis-plus有两种方法生成,一种是基于插件的,一种是基于java代码的
基于插件
将下列的代码填在pom.xml中
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
</plugin>
当然generatorConfig.xml你可以自己定义名字和位置
然后你就会在项目的右边的maven中看见
然后在resources下配置generatorConfig.xml
generatorConfig.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 数据库驱动:选择你的本地硬盘上面的数据库驱动包-->
<classPathEntry location="C:\Users\yangpan\.m2\repository\mysql\mysql-connector-java\8.0.18\mysql-connector-java-8.0.18.jar"/>
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressDate" value="true"/>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--数据库链接URL,用户名、密码 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost/demo?serverTimezone=UTC" userId="root" password="8874520*">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- 生成模型的包名和位置-->
<javaModelGenerator targetPackage="cn.yp.demo.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- 生成映射文件的包名和位置-->
<sqlMapGenerator targetPackage="cn.yp.demo.dao2" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- 生成DAO的包名和位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="cn.yp.demo.dao2" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 要生成的表 tableName是数据库中的表名或视图名 如果要生成多个表则要多个table标签 domainObjectName是实体类名–>-->
<table tableName="student" domainObjectName="Person" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/>
</context>
</generatorConfiguration>
接下来什么都不用管了,直接生成了,什么依赖包都不用引入
缺点:只能单表的简单操作,如果复杂点可以自行在xml中配置
基于Java配置的
依赖
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
java代码
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
public class CodeGenerator {
public static void main(String[] args) {
AutoGenerator autoGenerator = new AutoGenerator();
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL);
dataSourceConfig.setDriverName("com.mysql.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("root");
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/auto_office?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai");
autoGenerator.setDataSource(dataSourceConfig);
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOpen(true); // 代码生成后打开目录
globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java");
globalConfig.setAuthor("mjdai");
// globalConfig.setIdType(IdType.ASSIGN_ID);// id 主键策略
// globalConfig.setDateType(DateType.ONLY_DATE); // 定义生成的实体类中日期类型
globalConfig.setSwagger2(true);// 开启Swaggers模式
globalConfig.setBaseResultMap(true);
globalConfig.setBaseColumnList(true);
globalConfig.setServiceName("%sService");
autoGenerator.setGlobalConfig(globalConfig);
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("org.mjdai");
packageConfig.setEntity("pojo");
packageConfig.setMapper("mapper");
packageConfig.setController("controller");
packageConfig.setService("service");
packageConfig.setServiceImpl("service.impl");
autoGenerator.setPackageInfo(packageConfig);
StrategyConfig strategyConfig = new StrategyConfig();
// strategyConfig.setInclude("t_admin"); // 生成单表写法
strategyConfig.setInclude("user","product"); // 生成多张表写法。生成所有表,不用配置
strategyConfig.setTablePrefix("t"+"_"); // 去表前缀 t,根据实际情况填写
strategyConfig.setEntityLombokModel(true);
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
autoGenerator.setStrategy(strategyConfig);
autoGenerator.execute();
}
}
点击运行即可
第二个方法和其他的方法不一样的是这个不用在mapper的xml中配置,
why?
因为
/**
* <p>
* 服务实现类
* </p>
*
* @author qhling
* @since 2021-12-06
*/
@Service
public class MsgServiceImpl extends ServiceImpl<MsgMapper, Msg> implements MsgService {
}
这个是服务自动生成的实现类,可以看见它自动的继承了ServiceImpl这个类
/**
* <p>
* 服务类
* </p>
*
* @author qhling
* @since 2021-12-06
*/
public interface MsgService extends IService<Msg> {
}
/**
* <p>
* Mapper 接口
* </p>
*
* @author qhling
* @since 2021-12-06
*/
public interface MsgMapper extends BaseMapper<Msg> {
}
这个mapper继承了BaseMapper,是实现增删改查的抽象类
最后要在启动器中扫描Mapper
数据库和javaBean的映射
1.类型对比
数据库类型 Java 类型
CHAR String
VARCHAR String
LONGVARCHAR String
NUMERIC java.math.BigDecimal
DECIMAL java.math.BigDecimal
BIT boolean
TINYINT byte
SMALLINT short
INTEGER int
BIGINT long
REAL float
FLOAT double
DOUBLE double
BINARY byte[]
VARBINARY byte[]
LONGVARBINARY byte[]
DATE java.sql.Date
TIME java.sql.Time
TIMESTAMP java.sql.Timestamp
Springboot+Mybatis
待写
SpringBoot+SpringMVC
待写
Tips
接口回调
接口回调是指:可以把使用实现了某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调.
缺省
即系统默认状态,意思与“默认”相同。缺省是一种计算机术语,指在无决策者干预情况下,对于决策或应用软件、计算机程序的系统参数的自动选择。默认选项的设计可以在用户不须决策的状况下就可以基础地使用上述的软件与程序。“缺省”最初来源于计算机英文文档中的单词"default""default”有很多意思:违约、缺省、拖欠、默认,由于当时计算机方面的翻译水平不高,于是就把这个词直译成了“缺省”,其实应该取它的引申意思“默认”。
只有沉下心,冷静下来才有可能~