先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注go)
正文
5、处理创建请求(3-4节内容)
(1)@RequestBody映射请求体到java方法的参数上。
当使用post方法发送请求体数据时,Controller中接收方法的参数上要加@RequestBody来映射请求体到参数上(参数一般是实体类的对象)。
(2)日期类型参数的处理。
前后端使用时间戳来传递时间。
(3)@Valid注解验证请求参数的合法性,BindingResult收集校验结果。
@Valid加在方法的参数上,用来校验前端传来的参数与后台的数据要求是否一致。
BindingResult作为方法的参数,用来收集校验的结果。
6、开发用户信息的修改和删除服务(3-5节内容)
(1)常用的校验注解使用
@NotNull 值不能为空
@Null 值必须为空
@Pattern(regex=) 字符串必须匹配正则表达式
@Email 字符串必须是Email地址
@Size 集合中元素数量必须在min和max之间
等等
(2)自定义校验的提示消息
在加注解时,给message字段设置自定义的校验提示消息,而不是使用默认的提示校验提示信息。例如:@NotBlank(message = “密码不能为空”)
package security.demo.dto;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import security.demo.validator.MyConstraint;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Past;
import java.util.Date;
//@Data
public class User {
/**
- id
*/
@ApiModelProperty(“用户id”)
private String id;
/**
- 用户名不能为空
*/
@MyConstraint(message = “用户名不能为空”)
@ApiModelProperty(“用户名”)
private String username;
/**
- 密码不能为空
*/
@NotBlank(message = “密码不能为空”)
@ApiModelProperty(“用户密码”)
private String password;
/**
- 必须是一个过去的时间
*/
@Past(message = “生日必须是一个过去的时间”)
@ApiModelProperty(“用户生日”)
private Date birthDay;
/**
- 声明简单视图接口
*/
public interface UserSimpleView{};
/**
- 声明详情视图接口
- 因为UserDetailView extends UserSimpleView
- 所以UserDetailView会显示UserSimpleView中显示的所有字段
*/
public interface UserDetailView extends UserSimpleView{};
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public User(String id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public User(String id, String username, String password, Date birthDay) {
this.id = id;
this.username = username;
this.password = password;
this.birthDay = birthDay;
}
/**
- 设定id只在简单视图显示
- @return
*/
@JsonView(UserSimpleView.class)
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
/**
- 设定用户名只在简单视图显示
- @return
*/
@JsonView(UserSimpleView.class)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
/**
- 设定密码只在详情视图显示
- @return
*/
@JsonView(UserDetailView.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/**
- 设定生日只在简单视图显示
- @return
*/
@JsonView(UserSimpleView.class)
public Date getBirthDay() {
return birthDay;
}
public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}
@Override
public String toString() {
return “User{” +
“id='” + id + ‘’’ +
“, username='” + username + ‘’’ +
“, password='” + password + ‘’’ +
“, birthDay=” + birthDay +
‘}’;
}
}
(3)自定义校验注解
①、新建自定义的校验注解
package security.demo.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)//表明这个注解具有校验作用,使用的校验逻辑由validatedBy指定的校验类来执行
public @interface MyConstraint {
/**
- 校验需要的提示信息
- @return
*/
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
②、新建自定义的校验逻辑类
package security.demo.validator;
import org.springframework.beans.factory.annotation.Autowired;
import security.demo.service.HelloService;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
- 自定义的校验类需要实现ConstraintValidator接口
- MyConstraint:是需要验证的注解
- String:需要验证的字段类型,这样该注解就只能用在String类型的参数上,如果设置成Object,则任何类型都能使用该注解
*/
public class MyConstraintValidator implements ConstraintValidator<MyConstraint,Object> {
@Autowired
private HelloService helloService;
/**
- 校验类初始化时执行
- @param constraintAnnotation
*/
@Override
public void initialize(MyConstraint constraintAnnotation) {
System.out.println(“my custom validator init”);
}
/**
- @param o 需要校验的值
- @param constraintValidatorContext
- @return
*/
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
helloService.greeting(“tom”);
System.out.println(“需要校验的值:”+o);
// return true;
return false;
}
}
③、将自定义的校验注解加到需要校验的字段上。
/**
- 用户名不能为空
*/
@MyConstraint(message = “用户名不能为空”)
private String username;
7、Restful API错误处理机制(3-6节内容)
(1)SpringBoot默认的错误处理机制
①: 浏览器端发出的请求,springboot如果处理时有错误,会由BasicErrorController的errorHtml()方法来处理,然后返回页面;
②: 非浏览器发出的请求(如PostMan等),springboot如果处理时有错误,会由BasicErrorController的error()方法来处理,然后返回JSON格式的错误信息;
(2)自定义异常处理
①: 浏览器端发出的请求,出现异常时返回的页面是在**src/main/resources/templates/error/**404.html配置,状态码是多少的就配置以该状态码为名称的页面,并在页面中设置返回的信息。
②: 非浏览器发出的请求(如PostMan等),springboot如果处理时有异常的情况处理方法:
步骤一:创建一个异常处理类
package security.demo.exception;
import java.io.Serializable;
/**
- 自定义的处理用户不存在的异常处理
*/
public class UserNotExistException extends RuntimeException implements Serializable {
private static final long serialVersionUID = -4253671095865255861L;
private String id;
public UserNotExistException(String id) {
super(“User Not Exist!”);
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
步骤二、创建一个异常的拦截处理器
package security.demo.web.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import security.demo.exception.UserNotExistException;
import java.util.HashMap;
import java.util.Map;
/**
- ControllerAdvice:处理Controller抛出的异常
*/
@ControllerAdvice
public class ControllerExceptionHandler {
/**
- 处理抛出来的UserNotExistException类型的异常
- @param exception
- @return
*/
@ExceptionHandler(UserNotExistException.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String,Object> handlerUserNotExistException(UserNotExistException exception){
Map<String,Object> result = new HashMap<>();
result.put(“id”,exception.getId());
result.put(“message”,exception.getMessage());
return result;
}
}
只要springboot方法里抛出UserNotExistException类型的异常就会由ControllerExceptionHandler配置的**handlerUserNotExistException()**方法来处理。
8、Restful API的拦截(以拦截记录时间为例)(3-7、8节内容)
(1)过滤器(Filter)
优点: filter里面是能够获取到(HttpServletRequest request)和响应(HttpServletResponse response),从request中也能获取到传入的参数信息;
缺点:无法知道是哪一个Controller类中的哪个方法被执行。
Filter随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。
1.启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;
2.每一次请求时都只调用方法doFilter()进行处理;
3.停止服务器时调用destroy()方法,销毁实例。
方式一:实现Filter类(有@Component注解)
package security.demo.web.filter;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
import java.util.Date;
/**
- @Component 这个注解的目的是将TimeFilter交给容器来管理。
- 时间过滤器
*/
@Component
public class TimeFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(“时间过滤器初始化!”);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println(“时间过滤器开始执行过滤!”);
// long startTime = new Date().getTime();
long startTime = System.currentTimeMillis();
//执行下一个过滤器
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("TimeFilter的耗时: "+(System.currentTimeMillis() - startTime));
System.out.println(“时间过滤器结束执行过滤!”);
}
@Override
public void destroy() {
System.out.println(“销毁时间过滤器!”);
}
}
方式二:实现Filter((无**@Component注解**)
①先定义一个没有@Component注解的过滤器
package security.demo.web.filter;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
/**
- 没有@Component注解的过滤器
*/
public class OtherTimeFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println(“OtherTimeFilter初始化!”);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println(“OtherTimeFilter开始执行过滤!”);
long startTime = System.currentTimeMillis();
//执行下一个过滤器
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("OtherTimeFilter的耗时: "+(System.currentTimeMillis() - startTime));
System.out.println(“OtherTimeFilter结束执行过滤!”);
}
@Override
public void destroy() {
System.out.println(“销毁OtherTimeFilter!”);
}
}
②使用Filter配置类来管理没有@Component注解的过滤器
package security.demo.web.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import security.demo.web.filter.OtherTimeFilter;
/**
- Filter:无法获取处理请求的方法信息
- springboot中filter的配置和顺序执行:https://www.cnblogs.com/douJiangYouTiao888/p/9183095.html
- 配置类
*/
@Configuration
public class WebFilterConfig {
/**
- FilterRegistrationBean是用来注册Filter的类
- @return
*/
@Bean
public FilterRegistrationBean otherTimeFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//将自定义的没有@Component注解的过滤器注册进来
registrationBean.setFilter(new OtherTimeFilter());
//设置过滤顺序
registrationBean.setOrder(2);
//设置过滤器的名字
registrationBean.setName(“OtherTimeFilter”);
//设置需要过滤的地址
registrationBean.addUrlPatterns(“/*”);
return registrationBean;
}
}
方式一和方式二的区别:
方式二没有使用@Component注解,而是使用配置类来注册过滤器,这样的好处正如在过滤器注册时的设置一样清晰:
①:可以设置过滤器在过滤器链的位置(顺序);
②:可以设置过滤器在过滤器链中的名称;
③:可以设置过滤器拦截的规则;
而使用@Component注解的过滤器则无法更好的实现上述细节。
(2)拦截器(Interceptor)
优点: 可获请求(HttpServletRequest request)和响应(HttpServletResponse response)对象,也可获取方法所在类的类名及方法名信息**。**
MethodParameter[] methodParameters = ((HandlerMethod)handler).getMethodParameters();
for (MethodParameter methodParameter : methodParameters) {
String parameterName = methodParameter.getParameterName();
// 只能获取参数的名称,不能获取到参数的值
//System.out.println("parameterName: " + parameterName);
}
缺点:从handler对象中只能获取参数的名称,不能获取到参数的值,所以无法获取处理请求的方法里的形参信息,(从request中可以间接能获取到传入的参数信息)。
它比filter的执行优先级低。
步骤一:需要先实现HandlerInterceptor
package security.demo.web.interceptor;
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;
/**
- Interceptor:可获取处理请求的方法信息
*/
@Component
public class TimeInterceptor implements HandlerInterceptor {
/**
- 处理请求的方法执行前运行此方法
- @param request
- @param response
- @param handler 处理请求的方法
- @return
- @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(“preHandle方法执行”);
System.out.println("处理请求的方法的类名: "+((HandlerMethod)handler).getBean().getClass().getName());
System.out.println("处理请求的方法名: "+((HandlerMethod)handler).getMethod().getName());
request.setAttribute(“startTime”,System.currentTimeMillis());
return true;
}
/**
- 处理请求的方法执行后运行此方法
- @param request
- @param response
- @param handler
- @param modelAndView
- @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(“postHandle方法执行”);
Long startTime = (Long)request.getAttribute(“startTime”);
System.out.println("Interceptor执行耗时: "+ (System.currentTimeMillis() - startTime));
}
/**
- 处理请求的方法执行完毕后运行此方法
- @param request
- @param response
- @param handler
- @param exception
- @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
System.out.println(“afterCompletion方法执行”);
Long startTime = (Long)request.getAttribute(“startTime”);
System.out.println("Interceptor执行耗时: "+ (System.currentTimeMillis() - startTime));
System.out.println(“exception is :”+exception);
}
}
步骤二:新建一个Web的配置类继承WebMvcConfigurationSupport
package security.demo.web.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import security.demo.web.filter.OtherTimeFilter;
import security.demo.web.interceptor.TimeInterceptor;
/**
- Filter:无法获取处理请求的方法信息
- springboot中filter的配置和顺序执行:https://www.cnblogs.com/douJiangYouTiao888/p/9183095.html
- 配置类:配置过滤器和拦截器,如果同时配置了拦截器和过滤器,二者都会起作用
*/
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Autowired
private TimeInterceptor timeInterceptor;
/**
- FilterRegistrationBean是用来注册Filter的类
- @return
*/
@Bean
public FilterRegistrationBean otherTimeFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//将自定义的没有@Component注解的过滤器注册进来
registrationBean.setFilter(new OtherTimeFilter());
//设置过滤顺序
registrationBean.setOrder(2);
//设置过滤器的名字
registrationBean.setName(“OtherTimeFilter”);
//设置需要过滤的地址
registrationBean.addUrlPatterns(“/*”);
return registrationBean;
}
/**
- 注册拦截器
- @param registry
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
}
}
(3)切片(Aspect)(3-8节内容) (最常使用)
优点: 可以获取方法信息及方法里的参数信息。
缺点: 无法直接获取方法的请求及响应对象。
(通过下面代码可以间接获取方法的请求及响应对象)
ServletRequestAttributes attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attribute.getRequest();
HttpServletResponse response = attribute.getResponse();
常使用在日志,事务,请求参数安全验证等
package security.demo.web.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TimeAspect {
/**
- (* security.demo.web.controller.UserController.*(…)):
- 第一个星号表示任何类型的返回值,第二个星号是UserController类里任何的方法
- (…):表示方法里的任何参数
- 整个的意思是作用在UserController的任何方法上,且不论方法的参数是什么,并且不管返回的是什么类型的方法上(即UserController的所有方法上)
- ProceedingJoinPoint:ProceedingJoinPoint的对象包含了被拦截的方法的所有信息
/
@Around("execution( security.demo.web.controller.UserController.*(…))")
public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(“time aspect start!”);
//获取被拦截的方法
Object proceed = pjp.proceed();
//获取方法的参数的数组
Object[] args = pjp.getArgs();
for(Object arg : args){
System.out.println("参数是: "+arg);
}
System.out.println(“time aspect end!”);
return proceed;
}
}
三者在程序中的执行顺序:
①Filter ②Interceptor (③ControllerAdvice) ④Aspect
执行后的返回顺序:
controller -> aspect -> controllerAdvice -> Interceptor -> Filter
9、文件的上传下载
下载功能需要加入IO的jar包
commons-io commons-io 2.5代码:
package security.demo.web.controller;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import security.demo.dto.FileInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;
@RestController
@RequestMapping(“/file”)
public class FileController {
/**
- 文件上传
- @param file
- @return
*/
@PostMapping
public FileInfo update(MultipartFile file){
//上传时参数的名字
System.out.println(file.getName());
//原始的文件名
String fileOriginalName = file.getOriginalFilename();
//文件的后缀名
String fileSuffixName = fileOriginalName.substring(fileOriginalName.lastIndexOf(“.”));
//文件的大小
System.out.println(file.getSize());
String folder = “自己指定一个路径”;
File uploadPath = new File(folder);
if(!uploadPath.exists()){
uploadPath.mkdirs();
}
File saveFilePath = new File(folder, UUID.randomUUID().toString()+fileSuffixName);
try {
file.transferTo(saveFilePath);
} catch (IOException e) {
e.printStackTrace();
}
FileInfo fileInfo = new FileInfo(saveFilePath.getAbsolutePath());
return fileInfo;
}
/**
- 文件下载,因为是写在响应response中的,所以是无返回值的
- @param request
- @param response
- @param id
*/
@GetMapping(“/{id}”)
public void download(HttpServletRequest request, HttpServletResponse response, @PathVariable(“id”) String id) throws IOException {
//输入流
InputStream inputStream = new FileInputStream(new File(“文件存储路径”,“文件的名字(包含后缀)”));
//输出流
OutputStream outputStream = response.getOutputStream();
//设置响应中的内容类型为下载类型
response.setContentType(“application/x-download”);
//设置下载时文件的名字
response.addHeader(“Content-Disposition”,“attachment;filename=(设定的文件名(包含后缀名))”);
IOUtils.copy(inputStream,outputStream);
outputStream.flush();
//关闭流
inputStream.close();
outputStream.close();
}
}
10、异步处理REST服务
(1)使用Runnable异步处理Rest服务
在主线程中使用Callable来发起一个副线程来执行耗时的逻辑。(需要注意的是副线程必须要由主线程发起调用,即主副线程之间必须要由关联)
package security.demo.web.async;
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
nse response, @PathVariable(“id”) String id) throws IOException {
//输入流
InputStream inputStream = new FileInputStream(new File(“文件存储路径”,“文件的名字(包含后缀)”));
//输出流
OutputStream outputStream = response.getOutputStream();
//设置响应中的内容类型为下载类型
response.setContentType(“application/x-download”);
//设置下载时文件的名字
response.addHeader(“Content-Disposition”,“attachment;filename=(设定的文件名(包含后缀名))”);
IOUtils.copy(inputStream,outputStream);
outputStream.flush();
//关闭流
inputStream.close();
outputStream.close();
}
}
10、异步处理REST服务
(1)使用Runnable异步处理Rest服务
在主线程中使用Callable来发起一个副线程来执行耗时的逻辑。(需要注意的是副线程必须要由主线程发起调用,即主副线程之间必须要由关联)
package security.demo.web.async;
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-wMs5zuBt-1713162396009)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!