拦截器-登录检查与静态资源放行

1.编写一个拦截器实现HandlerInterceptor接口

2.拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors())

3.指定拦截规则(注意,如果是拦截所有,静态资源也会被拦截】

还是以上一节Springboot视图解析与模板引擎为例,访问除登录页面外的其它任何请求,都应该是登录之后才能访问的。我们可以使用拦截器对请求进行拦截,从而进行相应的处理。

public interface HandlerInterceptor {default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;}default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}
}

拦截器必须要实现HandlerInterceptor 接口
HandlerInterceptor 定义了3个方法,preHandle预先处理,即目标方法处理之前进行处理。目标方法处理完之后,但是还没有到达指定页面之前,我们需要给页面再放一些数据,可以用postHandle进行后置拦截。在请求处理完成之后要做的工作用afterCompletion来处理

编写一个实现HandlerInterceptor接口的拦截器:

/*
登录检查
1.配置好拦截器要拦截哪些请求
2.把这些配置放在容器中(实现WebConfigurer的addUbtercepter)
3.指定拦截规则(如果是拦截所有,静态资源也会被拦截)
* */
public class LoginIntercepter implements HandlerInterceptor {/** 目标方法执行之前* */@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//登录检查逻辑HttpSession session=request.getSession();Object loginUser =session.getAttribute("loginUser");if(loginUser!=null){//放行return true;}//拦截住,未登录,跳转到登录页
//        session.setAttribute("msg","请先登录");
//        response.sendRedirect("/");request.setAttribute("msg","请先登录");//如果是重定向取不出来错误消息request.getRequestDispatcher("/").forward(request,response);return false;}/** 目标方法执行完成之后* */@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}/** 页面渲染以后* */@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

拦截器注册到容器中 && 指定拦截规则:

@Configuration
public class AdminWebConfig implements WebMvcConfigurer{@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginIntercepter())// /**会拦截所有请求,包括静态资源.addPathPatterns("/**")             //拦截哪些.excludePathPatterns("/","/login","/css/**","/js/**","/fonts/**","/images/**");  //放行哪些}
}

注意:这里使用/的时候会拦截掉所有资源,包括静态资源,因此需要对静态资源进行放行,我们静态资源都是保存在static目录下, 而这里不能直接对/static/,因为访问静态资源的时候url不会写static。

拦截器-【源码分析】-拦截器的执行时机和原理

1.根据当前请求,找到HandlerExecutionChain(可以处理请求的handler以及handler的所有 拦截器)
2.先来顺序执行 所有拦截器的 preHandle()方法。

  1. 如果当前拦截器preHandle()返回为true。则执行下一个拦截器的preHandle()
  2. 如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的 afterCompletion();

3.如果任何一个拦截器返回false,直接跳出不执行目标方法。
4.所有拦截器都返回true,才执行目标方法。
5.倒序执行所有拦截器的postHandle()方法。
6.前面的步骤有任何异常都会直接倒序触发 afterCompletion()。
7.页面成功渲染完成以后,也会倒序触发 afterCompletion()。

public class DispatcherServlet extends FrameworkServlet {...protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;...//该方法内调用HandlerInterceptor的preHandle()if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());...//该方法内调用HandlerInterceptor的postHandle()mappedHandler.applyPostHandle(processedRequest, response, mv);}           processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {//该方法内调用HandlerInterceptor接口的afterCompletion方法triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {//该方法内调用HandlerInterceptor接口的afterCompletion方法triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {...}}private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {if (mappedHandler != null) {//该方法内调用HandlerInterceptor接口的afterCompletion方法mappedHandler.triggerAfterCompletion(request, response, ex);}throw ex;}private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {...if (mappedHandler != null) {//该方法内调用HandlerInterceptor接口的afterCompletion方法// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null);}}}
public class HandlerExecutionChain {...boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for (int i = 0; i < this.interceptorList.size(); i++) {HandlerInterceptor interceptor = this.interceptorList.get(i);//HandlerInterceptor的preHandle方法if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}return true;}void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {for (int i = this.interceptorList.size() - 1; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);//HandlerInterceptor接口的postHandle方法interceptor.postHandle(request, response, this.handler, mv);}}void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);try {//HandlerInterceptor接口的afterCompletion方法interceptor.afterCompletion(request, response, this.handler, ex);}catch (Throwable ex2) {logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);}}}} 

文件上传-单文件上传与多文件上传

使用form_layouts.html文件来实现文件上传。

在common.html中修改form_layouts的请求发送,即当在main.html中点击Form Layouts能够响应form_layouts请求。

<li class="menu-list"><a href=""><i class="fa fa-tasks"></i> <span>Forms</span></a><ul class="sub-menu-list"><li><a th:href="@{/form_layouts}"> Form Layouts</a></li><li><a href="form_advanced_components.html"> Advanced Components</a>

在FormTestController处理文件上传的请求

/*
* 文件上传测试
* */
@Slf4j
@Controller
public class FormTestController {@GetMapping("/form_layouts")public String form_layouts(){return "form/form_layouts";}//因为使用post方式提交//该函数的作用是可以自动封装上传过来的文件@PostMapping("/upload")/** @RequestParam代表从请求参数中取值* 取出表单中的文件项用@RequestPart注解,文件用MultipartFile对象来存储* */public String upload(@RequestParam("email") String email,@RequestParam("username") String username,@RequestPart("headerImg") MultipartFile headerImg,@RequestPart("photos") MultipartFile[] photos) throws IOException {log.info("上传的信息:email={},username={},headerImg={},photos={}",email,username,headerImg.getSize(),photos.length);System.out.println(email+" "+username+" "+headerImg.getSize()+" "+photos.length);if(!headerImg.isEmpty()){//保存到文件服务器,OSS文件服务器String originalFilename = headerImg.getOriginalFilename();headerImg.transferTo(new File("D:\\bootimg\\"+originalFilename));}if(photos.length>0){for(MultipartFile photo:photos){if(!photo.isEmpty()){String originalFilename = photo.getOriginalFilename();photo.transferTo(new File("D:\\bootimg\\"+originalFilename));}}}return "main";}
}

form_layouts.html文件中文件上传表单的信息

<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data"><div class="form-group"><label for="exampleInputEmail1">邮箱</label><input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email"></div><div class="form-group"><label for="exampleInputPassword1">名字</label><input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password"></div><div class="form-group"><label for="exampleInputFile">头像</label><input type="file" name="headerImg" id="exampleInputFile"><p class="help-block">Example block-level help text here.</p></div><div class="form-group"><label for="exampleInputFile">生活照</label><!--multiple多文件上传--><input type="file" name="photos" multiple><p class="help-block">Example block-level help text here.</p></div><div class="checkbox"><label><input type="checkbox"> Check me out</label></div><button type="submit" class="btn btn-primary">提交</button></form>

可以在application.properties中设置文件上传的参数

# 单个文件的最大大小
spring.servlet.multipart.max-file-size=10MB
# 整个请求的最大文件大小
spring.servlet.multipart.max-request-size=100MB

文件上传相关的配置类:

org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartProperties

文件上传源码流程

文件上传自动配置类MultipartAutoConfiguration-MultipartProperties
自动配置好了StandardServletMultipartResolver [文件.上传解析器]
原理步骤
1.请求进来使用文件上传解析器判断(isMultipart) 并封装(resolveMultipart, 返回MultipartHttpServletRequest)文件.上传请求
2.参数解析器来解析请求中的文件内容封装成MultipartFile
3.将request中文件信息封装为-个Map; MultiValueMap< String, MultipartFile>
FileCopyUtils。实现文件流的拷贝

@PostMapping("/upload")
public String upload(@RequestParam("email") String email,@RequestParam("username") String username,@RequestPart("headerImg") MultipartFile headerImg,@RequestPart("photos") MultipartFile[] photos)

文件上传相关的自动配置类MultipartAutoConfiguration有创建文件上传参数解析器StandardServletMultipartResolver。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {private final MultipartProperties multipartProperties;public MultipartAutoConfiguration(MultipartProperties multipartProperties) {this.multipartProperties = multipartProperties;}@Bean@ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })public MultipartConfigElement multipartConfigElement() {return this.multipartProperties.createMultipartConfig();}@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)@ConditionalOnMissingBean(MultipartResolver.class)public StandardServletMultipartResolver multipartResolver() {//配置好文件上传解析器StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());return multipartResolver;}}
//文件上传解析器
public class StandardServletMultipartResolver implements MultipartResolver {private boolean resolveLazily = false;public void setResolveLazily(boolean resolveLazily) {this.resolveLazily = resolveLazily;}@Overridepublic boolean isMultipart(HttpServletRequest request) {return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");}@Overridepublic MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {return new StandardMultipartHttpServletRequest(request, this.resolveLazily);}@Overridepublic void cleanupMultipart(MultipartHttpServletRequest request) {if (!(request instanceof AbstractMultipartHttpServletRequest) ||((AbstractMultipartHttpServletRequest) request).isResolved()) {// To be on the safe side: explicitly delete the parts,// but only actual file parts (for Resin compatibility)try {for (Part part : request.getParts()) {if (request.getFile(part.getName()) != null) {part.delete();}}}catch (Throwable ex) {LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);}}}}
public class DispatcherServlet extends FrameworkServlet {@Nullableprivate MultipartResolver multipartResolver;private void initMultipartResolver(ApplicationContext context) {...//这个就是配置类配置的StandardServletMultipartResolver文件上传解析器this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);...}protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;//最后finally的回收flag...try {ModelAndView mv = null;Exception dispatchException = null;try {//做预处理,如果有上传文件 就new StandardMultipartHttpServletRequest包装类processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);...// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());...// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());}....finally {...if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {...return this.multipartResolver.resolveMultipart(request);...}}protected void cleanupMultipart(HttpServletRequest request) {if (this.multipartResolver != null) {MultipartHttpServletRequest multipartRequest =WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);if (multipartRequest != null) {this.multipartResolver.cleanupMultipart(multipartRequest);}}}
}

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());跳到以下的类

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {@Overrideprotected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;...mav = invokeHandlerMethod(request, response, handlerMethod);...return mav;}@Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {//关注点invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}...invocableMethod.invokeAndHandle(webRequest, mavContainer);...return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}}}

this.argumentResolvers其中主角类RequestPartMethodArgumentResolver用来生成

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {...public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);...}@Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);...return doInvoke(args);//反射调用}@Nullableprotected Object doInvoke(Object... args) throws Exception {Method method = getBridgedMethod();ReflectionUtils.makeAccessible(method);return method.invoke(getBean(), args);...}//处理得出multipart参数,准备稍后的反射调用(@PostMapping标记的上传方法)protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {MethodParameter[] parameters = getMethodParameters();...Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}//关注点1if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {//关注点2args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {...}}return args;}}
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {//对应上面代码关注点1@Overridepublic boolean supportsParameter(MethodParameter parameter) {//标注@RequestPart的参数if (parameter.hasParameterAnnotation(RequestPart.class)) {return true;}else {if (parameter.hasParameterAnnotation(RequestParam.class)) {return false;}return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());}}//对应上面代码关注点2@Override@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);Assert.state(servletRequest != null, "No HttpServletRequest");RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());String name = getPartName(parameter, requestPart);parameter = parameter.nestedIfOptional();Object arg = null;//封装成MultipartFile类型的对象作参数Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {arg = mpArg;}...return adaptArgumentIfNecessary(arg, parameter);}
}
public final class MultipartResolutionDelegate {...@Nullablepublic static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)throws Exception {MultipartHttpServletRequest multipartRequest =WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);boolean isMultipart = (multipartRequest != null || isMultipartContent(request));if (MultipartFile.class == parameter.getNestedParameterType()) {if (!isMultipart) {return null;}if (multipartRequest == null) {multipartRequest = new StandardMultipartHttpServletRequest(request);}return multipartRequest.getFile(name);}else if (isMultipartFileCollection(parameter)) {if (!isMultipart) {return null;}if (multipartRequest == null) {multipartRequest = new StandardMultipartHttpServletRequest(request);}List<MultipartFile> files = multipartRequest.getFiles(name);return (!files.isEmpty() ? files : null);}else if (isMultipartFileArray(parameter)) {if (!isMultipart) {return null;}if (multipartRequest == null) {multipartRequest = new StandardMultipartHttpServletRequest(request);}List<MultipartFile> files = multipartRequest.getFiles(name);return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);}else if (Part.class == parameter.getNestedParameterType()) {if (!isMultipart) {return null;}return request.getPart(name);}else if (isPartCollection(parameter)) {if (!isMultipart) {return null;}List<Part> parts = resolvePartList(request, name);return (!parts.isEmpty() ? parts : null);}else if (isPartArray(parameter)) {if (!isMultipart) {return null;}List<Part> parts = resolvePartList(request, name);return (!parts.isEmpty() ? parts.toArray(new Part[0]) : null);}else {return UNRESOLVABLE;}}...}

Springboot2拦截器与文件上传相关推荐

  1. Spring Boot(5) web开发(3)拦截器、文件上传、异常处理

    Spring Boot(5) web开发(3)拦截器.文件上传.异常处理 学习视频: https://www.bilibili.com/video/BV19K4y1L7MT?p=49&spm_ ...

  2. SSM之SpringMVC 04 —— Ajax、拦截器、文件上传和下载

    系列文章 SSM之SpringMVC 01 -- SpringMVC原理及概念.Hello SpringMVC 注解版和配置版 SSM之SpringMVC 02 -- Controller和RestF ...

  3. .net mvc actionresult 返回字符串_072-SpringMVC后端控制器接收参数、处理器方法返回值类型、拦截器、文件上传下载...

    1. 注解式开发之annotation-driven解释 (1) mvc注解驱动在哪个文件中配置? 在springmvc中配置 (2) 配置mvc注解驱动使用哪个标签? 2. 注解式开发之视图解析器 ...

  4. Struts2_3_国际化处理_自定义拦截器_文件上传及下载_OGNL

    Struts2国际化处理 浏览器根据当前的语言环境自动查找对应的语言环境资源包, 使jsp显示合适的语言数据环境 Struts2实现国际化, 动作类必须继承ActionSupport 创建资源包 资源 ...

  5. SpringMVC之拦截器和文件上传下载

    过滤器与拦截器的区别:拦截器是AOP思想的具体应用. 过滤器 : servlet规范中的一部分,任何java web工程都可以使用 在url-pattern中配置了/*之后,可以对所有要访问的资源进行 ...

  6. SpringMVC学习总结(三):转发重定向、拦截器、文件上传等

    转发和重定向 当Controller中方法的返回值为字符串时,默认为视图名称.当返回值字符串以"forward:"或者"redirect:"开头,则会被认为是转 ...

  7. jfinal jboot 拦截器过滤文件上传请求 和 跨域解决方法

    public class PublicInterceptor implements Interceptor {@Overridepublic void intercept(Invocation inv ...

  8. 管理springmvc组件——前端控制器、控制器映射器和适配器、视图解析器、文件上传的、拦截器||消息转化

    管理springmvc组件 概述 在使用springmvc时要配置哪些东西 前端控制器 控制器映射器和适配器 映射器  Map<Set<String>,Object> Set& ...

  9. SpringBoot-拦截器和文件上传

    1.拦截器 1.1.HandlerInterceptor 接口 /*** 登录检查* 1.配置好拦截器要拦截哪些请求* 2.把这些配置放在容器中*/ @Slf4j public class Login ...

最新文章

  1. Django框架(二十)—— Django rest_framework-认证组件
  2. [ZJOI2008]瞭望塔
  3. 团队任务3:软件设计与开发准备
  4. Linux调度系统全景指南(下篇)
  5. 《三国志》生僻人名读法集(zz)
  6. 纯净微擎框架 V 2.5.7 稳定运营版 免验证/去授权/防拉黑/支持本地模块安装源码
  7. U盘PE安装windows7 方法
  8. slotformatcombinations_R15 38.331 无线资源控制(RRC)协议规范.pdf
  9. java 线程池的原理与实现_Java线程池原理及实现
  10. YOLO3 -- 介绍
  11. 自建私有云与公有云托管对比_云托管:利与弊
  12. 域名申请攻略(以godaddy+支付宝为例)
  13. java后台实现支付宝支付接口、支付宝订单查询接口 前端为APP
  14. 祝贺 StreamX 开源一周年
  15. 清华 计算机系 赵晟,来看看2010年清华大学自动化系录取名单本科毕业学校
  16. 光滑曲线_消防水泵-流量扬程性能曲线
  17. 海思android随笔之工厂菜单PQ acm color调用流程
  18. PHP执行Shell脚本或Bash脚本文件并返回命令输出详情
  19. Python二期学习开篇语
  20. python发短信 金山_Python爬虫基础练习(四) 金山翻译爬取(进阶篇)

热门文章

  1. 腾讯为60亿美元债券定价 为1年来亚洲最大美元债发行交易
  2. 荣耀10i高清渲染图曝光:后置三摄+高颜值渐变色机身
  3. 华为P30 Lite参数曝光 配6.15寸水滴屏
  4. 「软件测试」刚从腾讯面试出来,留下了这些面试笔记
  5. 已从事软件测试一年,感觉依然很菜,只会基础的功能测试,想进一步学习,有没有好的建议呢?
  6. 2018年高二计算机试题,2018年上学期高二信息技术期末考试试题.pdf
  7. ibdata1 mysql_ibdata1 mysql-bin
  8. crosstool-ng构建的一个错误的解决
  9. 拦截游戏窗口被移动_熊孩子骗家长人脸识别? 腾讯游戏出了个新招
  10. 【Spark】Spark SQL 物化视图技术原理与实践