IDEA教程
链接: Java 单体服务开发指南.
创建基础项目
1.创建项目
一路next,选择项目存放路径后点击finish完成,等待依赖下载完成
删除没必要的文件(选中的红色)
项目结构
导入web依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
yml配置
删除 application.properties
新建 application.yml
server:
port: 8080
编写TestController测试项目
package com.zm.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("hello")
public String helloGet() {
return "get hello world!";
}
@PostMapping("hello")
public String helloPost() {
return "post hello world!";
}
}
统一参数回调
package com.zm.demo.common;
import java.io.Serializable;
public class ApiResult<T> implements Serializable {
private T data;
private Integer code;
private String msg;
/**
* 请求成功回调
*/
public static ApiResult successMsg() {
return new ApiResult().setCode(200).setMsg("ok");
}
/**
* 请求成功回调
* @param Object 对象参数
*/
public static ApiResult successMsg(Object Object) {
return new ApiResult().setCode(200).setMsg("ok").setData(Object);
}
/**
* 请求失败回调
* @param code 状态码
* @param msg 描述信息
*/
public static ApiResult errorMsg(Integer code, String msg) {
return new ApiResult().setCode(code).setMsg(msg);
}
/**
* 请求失败回调
* @param msg 描述信息
*/
public static ApiResult errorMsg(String msg) {
return new ApiResult().setCode(500).setMsg(msg);
}
public T getData() {
return data;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
public ApiResult<T> setData(T data) {
this.data = data;
return this;
}
public ApiResult<T> setCode(Integer code) {
this.code = code;
return this;
}
public ApiResult<T> setMsg(String msg) {
this.msg = msg;
return this;
}
}
整合Swagger-knife4j接口文档
pom.xml配置依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.22</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
</dependencies>
package com.zm.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(Environment environment) {
return docket("测试demo", "com.zm.demo.controller",environment);
}
private Docket docket(String groupName, String basePackage,Environment environment) {
return new Docket(DocumentationType.SWAGGER_2)
//enable配置是否启用Swagger,如果是false,在浏览器将无法访问
.enable(environment.acceptsProfiles(Profiles.of("dev", "test")))
.groupName(groupName)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage(basePackage))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("title")
.description("description")
.license("license")
.licenseUrl("licenseUrl")
.termsOfServiceUrl("termsOfServiceUrl")
.contact(new Contact("name", "url", "email"))
.version("version")
.build();
}
}
package com.zm.demo.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class HttpConverterConfig implements WebMvcConfigurer {
/**
* 配置Swagger静态资源访问
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
/**
* 使用阿里 FastJson 作为JSON MessageConverter
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
supportedMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML);
supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM);
supportedMediaTypes.add(MediaType.APPLICATION_PDF);
supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML);
supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML);
supportedMediaTypes.add(MediaType.APPLICATION_XML);
supportedMediaTypes.add(MediaType.IMAGE_GIF);
supportedMediaTypes.add(MediaType.IMAGE_JPEG);
supportedMediaTypes.add(MediaType.IMAGE_PNG);
supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM);
supportedMediaTypes.add(MediaType.TEXT_HTML);
supportedMediaTypes.add(MediaType.TEXT_MARKDOWN);
supportedMediaTypes.add(MediaType.TEXT_PLAIN);
supportedMediaTypes.add(MediaType.TEXT_XML);
converter.setSupportedMediaTypes(supportedMediaTypes);
converter.setDefaultCharset(Charset.forName("UTF-8"));
FastJsonConfig config = new FastJsonConfig();
JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
config.setSerializerFeatures(SerializerFeature.WriteDateUseDateFormat);//格式化时间
converter.setFastJsonConfig(config);
// 把FastJsonHttpMessageConverter放到Jackson的前面去
// 这个list是个ArrayList 所以我们要么就放在首位(不建议),
// 而converters.indexOf() 因为人家是new的 所以肯定是找不到此对象的位置的 所以采用遍历的方式吧
int jacksonIndex = 0;
for (int i = 0; i < converters.size(); i++) {
if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
jacksonIndex = i;
break;
}
}
// 添加在前面
converters.add(jacksonIndex, converter);
}
}
ApiResult加上Swagger注解
@ApiModelProperty(value = "响应体")
private T data;
@ApiModelProperty(value = "状态码")
private Integer code;
@ApiModelProperty(value = "描述信息")
private String msg;
多环境yml配置
application.yml
spring:
profiles:
active: dev
application-dev.yml
server:
port: 8080
application-pro.yml
server:
port: 8081
启动项目 访问 http://127.0.0.1:8080/doc.html
将application.yml中active 改为 pro 启动项目 访问 http://127.0.0.1:8080/doc.html
enable配置是否启用Swagger成功
全局异常处理
可将项目中需要统一处理的异常进行捕获,统一返回错误信息
package com.zm.demo.common;
/**
* 全局错误类枚举
* 错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号
* 错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。
* 分别是:
* A0001(用户端错误)
* B0001(系统执行出错)
* C0001(调用第三方服务出错)
*
*/
public enum ErrorCode {
/**
*系统异常
*/
SYSTEM_ERROR("B0001","系统异常"),
/**
*用户登录失效
*/
USER_LOGIN_INVALID("A0001","用户登录失效"),
;
/**
* 错误码
*/
private String errorCode;
/**
* 错误码描述
*/
private String errorMsg;
ErrorCode(String errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}
package com.zm.demo.common;
/**
* 自定义业务异常类
*/
public class BusinessRuntimeException extends RuntimeException {
private static final long serialVersionUID = 7903308178033567233L;
/**
* 错误码
*/
private String errorCode;
/**
* 错误码描述
*/
private String errorMsg;
public BusinessRuntimeException(String errorMsg) {
this.errorCode = "500";
this.errorMsg = errorMsg;
}
public BusinessRuntimeException(ErrorCode errorCode) {
this.errorCode = errorCode.getErrorCode();
this.errorMsg = errorCode.getErrorMsg();
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}
package com.zm.demo.config;
import com.zm.demo.common.ApiResult;
import com.zm.demo.common.BusinessRuntimeException;
import com.zm.demo.common.ErrorCode;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局Controller层异常处理类
* @RestControllerAdvice 和 @ControllerAdvice区别在于@RestControllerAdvice不需要加@ResponseBody
*/
@RestControllerAdvice
public class GlobalExceptionResolver {
/**
* 处理所有不可知异常
* @param e 异常
* @return json结果
*/
@ExceptionHandler(Exception.class)
public ApiResult handleException(Exception e) {
e.printStackTrace();
return ApiResult.errorMsg(ErrorCode.SYSTEM_ERROR);
}
/**
* 处理所有业务异常
* @param e 业务异常
* @return json结果
*/
@ExceptionHandler(BusinessRuntimeException.class)
public ApiResult handleOpdRuntimeException(BusinessRuntimeException e) {
return ApiResult.errorMsg(e.getErrorMsg());
}
}
ApiResult 中增加方法
/**
* 请求失败回调
* @param errorCode 错误枚举
*/
public static ApiResult errorMsg(ErrorCode errorCode) {
return new ApiResult().setCode(errorCode.getErrorCode()).setMsg(errorCode.getErrorMsg());
}
修改TestController代码,进行测试
@GetMapping("hello")
public ApiResult helloGet() {
String msg = "get hello world!";
if(true){
throw new BusinessRuntimeException("出现BusinessRuntimeException错误");
}
return ApiResult.successMsg(msg);
}
@PostMapping("hello")
public ApiResult helloPost() {
String msg = "post hello world!";
if(true){
throw new BusinessRuntimeException(ErrorCode.SYSTEM_ERROR);
}
return ApiResult.errorMsg(msg);
}
参数校验
导入依赖
<!-- hibernate校验依赖包-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>
<!-- lombok 需要下载lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
package com.zm.demo.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
@ApiModel(value = "查询订单测试参数校验")
@Data
public class SelectOrderReq {
/**
* required = true 参数为必填
*/
@ApiModelProperty(value = "订单号",required = true)
@NotBlank(message = "订单号不能为空")
private String orderNumber;
@ApiModelProperty(value = "订单类型")
@Range(min = 1,max = 3,message = "订单类型错误")
private Integer orderType;
@ApiModelProperty(value = "下单时间")
@Past(message = "查询订单时间需在当前时间之前")
private Date orderTime;
}
在GlobalExceptionResolver新增参数校验异常处理
/**
*@RequestParam上校验失败-> 抛出ConstraintViolationException
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public ApiResult error(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
return ApiResult.errorMsg(message);
}
/**
*单独使用@Valid@Validated验证路径中请求实体校验失败后抛出的异常
*/
@ExceptionHandler(BindException.class)
public ApiResult BindExceptionHandler(BindException e) {
String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
return ApiResult.errorMsg(message);
}
/**
*@RequestBody上校验失败后抛出的异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResult MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
return ApiResult.errorMsg(message);
}
package com.zm.demo.controller;
import com.zm.demo.common.ApiResult;
import com.zm.demo.common.BusinessRuntimeException;
import com.zm.demo.vo.SelectOrderReq;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@Validated
public class TestController {
@PostMapping("hello")
public ApiResult<SelectOrderReq> helloPost(@Valid @RequestBody SelectOrderReq req) {
System.out.println(req);
return ApiResult.successMsg(req);
}
}
使用Swagger进行接口测试 访问 http://127.0.0.1:8080/doc.html
更多使用: 统一的全局异常处理和参数校验.
会话认证(拦截器 和 aop 两种实现)
使用aop来实现session认证
链接: Cookie 和Session是什么?为什么要使用它们?.
导入依赖
<!-- aop依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
package com.zm.demo.aop;
import lombok.Data;
/**
* 用户session
*/
@Data
public class UserSession {
private Integer userId;
private String username;
}
package com.zm.demo.aop;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* 统一操作session
* session默认30分钟未操作则失效
* session.setMaxInactiveInterval(7200);
* 过期参数单位是秒,即在7200秒没有活动后session将失效
* 设置为-1将永不关闭。
*/
public class HttpSessionUtil {
public static final String SESSION = "UserSession";
/**
* 保存用户Session
*/
public static void setUserSession(UserSession userSession) {
getSession().setAttribute(SESSION,userSession);
}
/**
* 获取用户Session
*/
public static UserSession getUserSession() {
return (UserSession)getRequest().getSession().getAttribute(SESSION);
}
public static HttpSession getSession() {
return getRequest().getSession();
}
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes.getRequest();
}
}
package com.zm.demo.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用户session认证注解
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SessionAuthentication {
String description() default "";
}
package com.zm.demo.aop;
import com.zm.demo.common.BusinessRuntimeException;
import com.zm.demo.common.ErrorCode;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 会话认证aop
* 异常已经统一交由全局处理 这里无需处理异常
*/
@Aspect
@Component
@Order(1)
public class SessionAuthenticationAspect {
/**
*注解拦截
*/
@Pointcut("@annotation(com.zm.demo.aop.SessionAuthentication)")
public void aspect() {
}
/**
* 前置通知
*/
@Before("aspect()")
public void doBefore(JoinPoint joinPoint){
UserSession user = HttpSessionUtil.getUserSession();
if (user == null) {
throw new BusinessRuntimeException(ErrorCode.USER_LOGIN_INVALID);
}
System.out.println( user.getUsername()+ "访问接口: " + getControllerMethodDescription2(joinPoint));
}
/**
* 后置通知
* 可以随时拓展加入用户日志记录
*/
@After("aspect()")
public void doAfter(JoinPoint joinPoint){
}
/**
* 获取注解中对方法的描述信息
*/
public static String getControllerMethodDescription2(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SessionAuthentication controllerLog = method.getAnnotation(SessionAuthentication.class);
return controllerLog.description();
}
}
package com.zm.demo.controller;
import com.zm.demo.aop.HttpSessionUtil;
import com.zm.demo.aop.SessionAuthentication;
import com.zm.demo.aop.UserSession;
import com.zm.demo.common.ApiResult;
import com.zm.demo.vo.SelectOrderReq;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@Validated
public class TestController {
@GetMapping("login")
public ApiResult login() {
System.out.println("login sessionId: " + HttpSessionUtil.getSession().getId());
// 验证用户名和密码是否正确(省略)
// 存入session
UserSession userSession = new UserSession();
userSession.setUserId(1);
userSession.setUsername("小周同学~");
HttpSessionUtil.setUserSession(userSession);
return ApiResult.successMsg();
}
@GetMapping("hello")
@SessionAuthentication(description = "hello get request")
public ApiResult helloGet() {
System.out.println("hello get sessionId: " + HttpSessionUtil.getSession().getId());
return ApiResult.successMsg();
}
@PostMapping("hello")
@SessionAuthentication(description = "hello post request")
public ApiResult<SelectOrderReq> helloPost(@Valid @RequestBody SelectOrderReq req) {
System.out.println("hello post sessionId: " + HttpSessionUtil.getSession().getId());
return ApiResult.successMsg(req);
}
}
未登录的情况下访问接口提示用户登录失效
登录后访问接口,session一致
前后端分离跨域设置
链接: springboot跨域请求解决方案.
方式一:在controller上加入@CrossOrigin注解
@CrossOrigin(origins = "http://xxx.com",
maxAge = 3600,
methods = {RequestMethod.GET, RequestMethod.POST})
方式二:基于拦截器(推荐使用)
package com.zm.demo.config;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 跨域拦截器
*/
@Component
public class CorsInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setContentType("textml;charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
//Access-Control-Allow-Headers 这里为支持的请求头,如果有自定义的header字段请自己添加
response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With, userId, token");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("XDomainRequestAllowed", "1");
System.out.println("CorsInterceptor跨域拦截器");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
在实现WebMvcConfigurer接口的HttpConverterConfig类中注册拦截器
@Autowired
private CorsInterceptor corsInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将CorsInterceptor拦截器添加进来
registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
}
方式三:在实现WebMvcConfigurer接口的HttpConverterConfig类中重写addCorsMappings(不推荐使用)
@Override
public void addCorsMappings(CorsRegistry registry) {
final String ORIGINS[] = new String[] { "GET", "POST", "PUT", "DELETE" };
registry.addMapping("/**") // 所有的当前站点的请求地址,都支持跨域访问。
// 当前端配置 withCredentials=true时, 后端配置Access-Control-Allow-Origin不能为*, 必须是相应地址
// 将设置allowedOrigins替换成allowedOriginPatterns
.allowedOriginPatterns("*")
.allowCredentials(true) // 是否支持跨域用户凭证
.allowedMethods(ORIGINS) // 当前站点支持的跨域请求类型是什么
.maxAge(3600); // 超时时长设置为1小时。 时间单位是秒。
System.out.println("addCorsMappings");
}
jwt(json web token) 和 自定义token
整合spring security 实现用户认证
整合MyBatis-Plus
整合数据连接池Druid 和 Log4j2日志
链接: springboot 整合 Druid 及 Log4j2.
自定义starter
图片上传
定时任务
异步线程池
邮件发送
Http请求客户端
编程规范
关注公众号 阿里技术 下载最新java开发手册