SpringBoot 基础项目配置(快速启动)
项目配置
返回实体
无泛型实体
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: 雨同我
* @Description:
* @DateTime: 2022/7/7 20:53
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
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<>();
public static Result ok(){
Result r = new Result();
r.setSuccess(true);
r.setCode(ResultCode.SUCCESS);
r.setMessage("成功");
return r;
}
public static Result error(){
Result r = new Result();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage("失败");
return r;
}
public static Result error(String message){
Result r = new Result();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage(message);
return r;
}
public Result code(Integer code){
this.setCode(code);
return this; // this的意思就是谁调用就返回谁
}
public Result success(Boolean success){
this.setSuccess(success);
return this; // this的意思就是谁调用就返回谁
}
public Result message(String message){
this.setMessage(message);
return this;
}
public Result data(Map<String, Object> data){
this.data = data;
return this;
}
public Result data(String key,Object value){
this.data.put(key, value);
return this;
}
}
- 与前端绑定的码,记得看情况更改
public interface ResultCode {
public static Integer SUCCESS = 20000;
public static Integer ERROR = 20001;
}
泛型实体
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 通用返回对象
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private long code;
private String message;
private T data;
/**
* 成功返回结果
*
* @param data 获取的数据
*/
public static <T> CommonResult<T> success(T data) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
/**
* 成功返回结果
*
* @param data 获取的数据
* @param message 提示信息
*/
public static <T> CommonResult<T> success(T data, String message) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
}
/**
* 失败返回结果
* @param errorCode 错误码
*/
public static <T> CommonResult<T> failed(ResultCode errorCode) {
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
}
/**
* 失败返回结果
* @param message 提示信息
*/
public static <T> CommonResult<T> failed(String message) {
return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
}
public static <T> CommonResult<T> failed(Integer code,String message) {
return new CommonResult<T>(code, message, null);
}
/**
* 失败返回结果
*/
public static <T> CommonResult<T> failed() {
return failed(ResultCode.FAILED);
}
/**
* 参数验证失败返回结果
*/
public static <T> CommonResult<T> validateFailed() {
return failed(ResultCode.VALIDATE_FAILED);
}
/**
* 参数验证失败返回结果
* @param message 提示信息
*/
public static <T> CommonResult<T> validateFailed(String message) {
return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
}
/**
* 未登录返回结果
*/
public static <T> CommonResult<T> unauthorized(T data) {
return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
}
/**
* 未授权返回结果
*/
public static <T> CommonResult<T> forbidden(T data) {
return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* 枚举了一些常用API操作码
* Created by macro on 2019/4/19.
*/
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum ResultCode{
SUCCESS(200, "操作成功"),
FAILED(500, "操作失败"),
VALIDATE_FAILED(404, "参数检验失败"),
UNAUTHORIZED(401, "暂未登录或token已经过期"),
FORBIDDEN(403, "没有相关权限");
private long code;
private String message;
}
自定义异常
异常类
import com.lu.utils.Result;
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;
@Slf4j
@ControllerAdvice
public class GloablExceptonHandler {
//指定出现什么异常执行这个方法
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.error().message(e.getMessage());
}
@ExceptionHandler(GuliException.class)
@ResponseBody
public Result error(GuliException e){
log.info("进入了全局异常处理");
e.printStackTrace();
log.error(e.getMsg());
return Result.error().code(e.getCode()).message(e.getMsg());
}
}
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Api(tags = "自定义异常类")
public class DiyException extends RuntimeException{
@ApiModelProperty(value = "状态码")
private Integer code; //状态码
private String msg;//异常信息
}
使用方式
throw new GuliException(50000,"错误");
-
返回一个对象
参数校验自定义异常
-
BindException类属于org.springframework.validation包
-
一些其他的用法可以参考: Java BindException类代码示例 - 纯净天空 (vimsky.com)
-
项目的使用参考:
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import java.util.List;
import java.util.Set;
@RestControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
/**
* 统一处理请求参数校验(普通传参)
*
* @param e ConstraintViolationException
* @return FebsResponse
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleConstraintViolationException(ConstraintViolationException e) {
StringBuilder message = new StringBuilder();
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
for (ConstraintViolation<?> violation : violations) {
Path path = violation.getPropertyPath();
String[] pathArr = StringUtils.splitByWholeSeparatorPreserveAllTokens(path.toString(), ".");
message.append(pathArr[1]).append(violation.getMessage()).append(",");
}
message = new StringBuilder(message.substring(0, message.length() - 1));
return message.toString();
}
/**
* 统一处理请求参数校验(实体对象传参)
*
* @param e BindException
* @return FebsResponse
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String validExceptionHandler(BindException e) {
StringBuilder message = new StringBuilder();
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
for (FieldError error : fieldErrors) {
message.append(error.getField()).append(error.getDefaultMessage()).append(",");
}
message = new StringBuilder(message.substring(0, message.length() - 1));
return message.toString();
}
}
项目技术
Lombok
@Singular和@Builder
-
@Builder 是方便构建不同的构造方法重载
-
@Singular 方便操作集合
-
@Singular 注意他的命名,如果是 lists,就会转换为 list ,如果命名不规范就指定名字
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("tb_role")
@ApiModel(value="Role对象", description="")
public class Role implements Serializable {
@Singular
private List<String> lists;
@Test
public void run(){
ArrayList<String> list = new ArrayList<>();
list.add("ok");
list.add("11");
Role haha = Role.builder().list("122").list("ok").build();
System.out.println(haha);
}
MaxWell
需求:
MaxWell 监听mysql数据,绑定RabbitMQ消息队列同步es文章数据
全量备份:可以使用mysqldump直接备份整个库或者是备份其中某一个库或者一个库中的某个表。
增量备份:增量备份是针对于数据库的bin-log日志进行备份的,需要开始数据库的bin-log日志。增量备份是在全量的基础上进行操作的。增量备份主要是靠mysql记录的bin-log日志。(可以把二进制日志保存成每天的一个文件)
快速开始看官网:
Quick Start - Maxwell's Daemon (maxwells-daemon.io)
- 下面是打开二进制日志,查看,打开方式在上面官网
运行 MaxWell
- RabbitMq 输出方式
docker run --name maxwell --restart=always -d zendesk/maxwell bin/maxwell --user='数据库用户名' --password='数据库密码' --host='IP地址' --producer=rabbitmq --rabbitmq_user='MQ用户名' --rabbitmq_pass='MQ密码' --rabbitmq_host='IP地址' --rabbitmq_port='5672' --rabbitmq_exchange='maxwell_exchange' --rabbitmq_exchange_type='fanout' --rabbitmq_exchange_durable='true' --filter='exclude: *.*, include: blog.tb_article.article_title = *, include: blog.tb_article.article_content = *, include: blog.tb_article.is_delete = *, include: blog.tb_article.status = *' //运行MaxWell
docker run --name maxwell --rm -it zendesk/maxwell bin/maxwell --user='root' --password='123456789' --host='192.168.80.80' --producer=rabbitmq --rabbitmq_user='guest' --rabbitmq_pass='guest' --rabbitmq_host='192.168.80.80' --rabbitmq_port='5672' --rabbitmq_exchange='maxwell_exchange' --rabbitmq_exchange_type='fanout' --rabbitmq_exchange_durable='true' --filter='exclude: *.*, include: blog-github.tb_article.article_title = *, include: blog-github.tb_article.article_content = *, include: blog-github.tb_article.is_delete = *, include: blog-github.tb_article.status = *' --producer=stdout
//运行MaxWell
- 控制台输出方式
docker run --name maxwell --rm -it zendesk/maxwell bin/maxwell --user='root' --password='123456789' --host='192.168.80.80' --filter='exclude: *.*, include: blog-github.tb_article.article_title = *, include: blog-github.tb_article.article_content = *, include: blog-github.tb_article.is_delete = *, include: blog-github.tb_article.status = *' --producer=stdout
- 这个是控制台输出方式,更新日志
SpringSecurity权限拦截
接口方法
WebMvcConfigurer详解
其他的详解可以参考:(95条消息) SpringBoot---WebMvcConfigurer详解_zhangpower1993的博客-CSDN博客_webmvcconfigurer
官网 Api 地址:ResourceHandlerRegistry (Spring Framework 5.2.23.BUILD-SNAPSHOT API)
addInterceptors:拦截器
- addInterceptor:需要一个实现HandlerInterceptor接口的拦截器实例
- addPathPatterns:用于设置拦截器的过滤路径规则;
addPathPatterns("/**")
对所有请求都拦截 - excludePathPatterns:用于设置不需要拦截的过滤规则
- 拦截器主要用途:进行用户登录状态的拦截,日志的拦截等。
@Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/emp/toLogin","/emp/login","/js/**","/css/**","/images/**");
}
addResourceHandlers:静态资源
比如,我们想自定义静态资源映射目录的话,只需重写addResourceHandlers方法即可。
- 例如图片上传到本地
@Configuration
public class MyWebMvcConfigurerAdapter implements WebMvcConfigurer {
/**
* 配置静态访问资源
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/my/**").addResourceLocations("classpath:/my/");
}
}
- addResoureHandler:指的是对外暴露的访问路径
- addResourceLocations:指的是内部文件放置的目录
应用
- 后面有个 / 记得加,还有前面的 file:
addCorsMappings:跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
super.addCorsMappings(registry);
registry.addMapping("/**")
.allowedHeaders("*")
.allowedMethods("POST","GET")
.allowedOrigins("*");
}
自定义认证和授权
- 认证是用户登录,查询他是否注册过
- 授权是他在这里的访问权限,是 Root ,还是 User
可以参考:
https://mrbird.cc/Spring-Security-Authentication.html
CSRF
更多详解参考:浅谈CSRF - 简书 (jianshu.com)
(Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,它在 2007 年曾被列为互联网 20 大安全隐患之一,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户
的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范
,所以被认为比XSS更具危险性
。
常用的注解和方法详解
@EnableGlobalMethodSecurity
-
(jsr250Enabled = true, prePostEnabled = true, securedEnabled = true)
-
他们都是实现同一个功能
-
详解可以参考:[(96条消息) @EnableGlobalMethodSecurity注解详解_chihaihai的博客-CSDN博客](https://blog.csdn.net/chihaihai/article/details/104678864#:~:text=prePostEnabled : prePostEnabled %3D true 会解锁 %40PreAuthorize,和 %40PostAuthorize 两个注解。 从名字就可以看出%40PreAuthorize 注解会在方法执行前进行验证,而 %40PostAuthorize 注解会在方法执行后进行验证。)
prePostEnabled
-
我就讲解这个即可,其他的可以参考上面的
-
prePostEnabled=true,解锁 @PreAuthorize 和 @PostAuthorize 两个注解
-
从名字就可以看出@PreAuthorize 注解会在方法执行前进行验证,而 @PostAuthorize 注解会在方法执行后进行验证。
里面的注解详解:
包含的表达式参考: Spring Security Reference
public interface UserService {
List<User> findAllUsers();
@PostAuthorize ("returnObject.type == authentication.name")
User findById(int id);
// @PreAuthorize("hasRole('ADMIN')") 必须拥有 ROLE_ADMIN 角色。
// 如果当前主体具有指定的权限,则返回 true
@hasAnyAuthority(["admin",authority2])
void updateUser(User user);
@PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")
void deleteUser(int id);
// @PreAuthorize("principal.username.startsWith('Felordcn')") 用户名开头为 Felordcn 的用户才能访问。
// @PreAuthorize("#id.equals(principal.username)") 入参 id 必须同当前的用户名相同。
// @PreAuthorize("#id < 10") 限制只能查询 id 小于 10 的用户
}
@PreAuthorize("hasAuthority('pms:brand:read')")使用
- 鉴权
- 一直看底层,看到了下面,是不是有点眼熟
- 重写里面的方法
- 数据库字段的权限
整合 JWT 的问题
JwtAuthenticationTokenFilter 为什么不注入容器
代码位置:https://gitee.com/ha6666/java-learn/tree/master/1-springsecurity-jwt
实践问题:
推荐使用: