在浏览器上输入地址访问  http://localhost:8081/shop-type/list 可以看到JSON数据


nginx不能放在中文目录下,否则会报错

可以看到nginx文件有一个静态文件夹


打开nginx命令行(这里我是直接输入nginx.exe,我是习惯了,你输入start nginx.exe也可以)

访问http://localhost:8080/

一.短信登录功能实现



修改这段代码:

UserService接口类添加方法

UserServiceImpl类中实现接口并重写其方法

其方法逻辑实现步骤:

1.校验手机号
2.如果不符合,返回错误信息
3.符合,生成验证码
4.保存验证码到session
5.发送验证码
6.返回ok

我们首先看一下进行校验的工具类

package com.hmdp.utils;import cn.hutool.core.util.StrUtil;/*** @author 虎哥*/
public class RegexUtils {/*** 是否是无效手机格式* @param phone 要校验的手机号* @return true:符合,false:不符合*/public static boolean isPhoneInvalid(String phone){return mismatch(phone, RegexPatterns.PHONE_REGEX);}/*** 是否是无效邮箱格式* @param email 要校验的邮箱* @return true:符合,false:不符合*/public static boolean isEmailInvalid(String email){return mismatch(email, RegexPatterns.EMAIL_REGEX);}/*** 是否是无效验证码格式* @param code 要校验的验证码* @return true:符合,false:不符合*/public static boolean isCodeInvalid(String code){return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);}// 校验是否不符合正则格式private static boolean mismatch(String str, String regex){if (StrUtil.isBlank(str)) {return true;}return !str.matches(regex);}
}

 @Overridepublic Result sendCode(String phone, HttpSession session) {// 1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {//2.如果不符合,发送错误信息return Result.fail("手机号格式错误");}//3.如果符合,生成验证码String code = RandomUtil.randomNumbers(6);//4.保存验证码到sessionsession.setAttribute("code", code);//5.发送验证码,必须加上@Slf4j注解才有这个方法log.debug("发送短信验证码成功, 验证码: {}", code);//6.返回okreturn Result.ok();}

测试一下:

在浏览器访问 http://localhost:8080/login.html 可以看到一个短信验证页面

然后输入手机号进行验证

可以看到验证成功:

1.实现短信验证码登录功能和注册功能

首先可以看到这个实体类有三个属性(手机号,验证码,密码):

UserController实现登录功能


登录功能实现步骤:

1.校验手机号
2.校验验证码
3.不一致,报错
4.一直,根据手机号进行查询
5.判断用户是否存在
6.保存用户到session

 @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误");}Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if (cacheCode == null || cacheCode.toString().equals(code)) {return Result.fail("验证码错误");}//一致,根据用户手机号查询用户select * from tb_user where phone = ?User user = query().eq("phone", phone).one();// 判断用户是否存在if (user == null) {// 如果用户不存在,创建新用户并保存。user = createUserWithPhone(phone);}session.setAttribute("user", user);return Result.ok();}// 创建用户private User createUserWithPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));save(user);return user;}

注意

我们因为继承了ServiceImpl<UserMapper,User> ,所以可以直接调用query()方法和save()方法。

测试一下

输入验证码,登录成功。

实现登录校验拦截器功能:

实现登录拦截器思路:
1.获取session
2.获取session中的用户
3.判断用户是否存在
4.如果不存在,则进行拦截
5.存在,保存用户信息到ThreadLocal
6.放行

先修改这段代码:

然后写一个登录拦截器LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession();//2.获取session中的用户Object user = session.getAttribute("user");// 判断用户是否存在if (user == null) {// 如果用户不存在,则进行拦截response.setStatus(401);return false;}//5.存在,保存用户信息到ThreadLocalUserHolder.saveUser((User) user);//6.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}

创建配置类MvcConfig实现WebMvcConfigurer接口

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");WebMvcConfigurer.super.addInterceptors(registry);}
}

 @GetMapping("/me")public Result me(){// TODO 获取当前登录的用户并返回User user = UserHolder.getUser();return Result.ok(user);}

测试一把:

user对象的内容拷贝到UserDTO对象身上,是为了隐藏一些用户敏感信息,只显示用户不敏感的数据。

查看以下UserDTO

这里改回原始的数据


测试一下:(这样可以避免敏感信息被访问到)

2.集群的session共享问题:


用Redis来代替session

前端代码分析:

保存验证到redis中

//保存验证码到redis // set key value ex 120stringRedisTemplate.opsForValue().set("login:code:" + phone, code, 2, TimeUnit.MINUTES);

代码优化以下:

从redis获取缓存对象


 // 7.保存用户信息到redis中// 7.1.随机生成token,作为登录令牌 /这个UUID是属于 cn.hutool.core.lang.UUID;String token = UUID.randomUUID().toString(true);UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);// 7.2.将User对象转为HashMap存储Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);String tokenKey = LOGIN_USER_KEY+token;// 7.3.存储stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.s设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn Result.ok(token);


不能使用依赖注入,只能使用构造方法。因为LoginInterceptorRegister使我们自己手动new出来的。但是我们可以在WvcConfig配置类在属性StringRedisTemplate上使用依赖注入进行赋值

 @Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//HttpSession session = request.getSession();//2.获取session中的用户//Object user = session.getAttribute("user");// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {response.setStatus(401);return false;}// 2.基于TOKEN获取redis中的用户String key = RedisConstants.LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {// 4.如果用户不存在,则进行拦截response.setStatus(401);return false;}// 5.将查询到的Hash数据转换为UserId对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//6.存在,保存用户信息到ThreadLocalUserHolder.saveUser(userDTO);//7.刷新token有效期stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);//8.放行return true;}

测试一下,发现异常。

java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Stringat org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:36) ~[spring-data-redis-2.3.9.RELEASE.jar:2.3.9.RELEASE]at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:185) ~[spring-data-redis-2.3.9.RELEASE.jar:2.3.9.RELEASE]at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:147) ~[spring-data-redis-2.3.9.RELEASE.jar:2.3.9.RELEASE]at com.hmdp.service.impl.UserServiceImpl.login(UserServiceImpl.java:99) ~[classes/:na]at com.hmdp.service.impl.UserServiceImpl$$FastClassBySpringCGLIB$$9cac0aa5.invoke(<generated>) ~[classes/:na]at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.2.15.RELEASE.jar:5.2.15.RELEASE]at com.hmdp.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$9b0f4aaa.login(<generated>) ~[classes/:na]at com.hmdp.controller.UserController.login(UserController.java:56) ~[classes/:na]at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_292]at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_292]at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_292]at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_292]at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) [spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]at javax.servlet.http.HttpServlet.service(HttpServlet.java:652) [tomcat-embed-core-9.0.46.jar:4.0.FR]at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) [spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) [tomcat-embed-core-9.0.46.jar:4.0.FR]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) [tomcat-embed-websocket-9.0.46.jar:9.0.46]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) [tomcat-embed-core-9.0.46.jar:9.0.46]at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.46.jar:9.0.46]at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_292]at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_292]at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.46.jar:9.0.46]at java.lang.Thread.run(Thread.java:748) [na:1.8.0_292]

解决办法:

 Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));

测试一下:成功运行,登录验证成功

在这里我们要改善一些问题,只要用户一直操作,token就不会消失。

删掉LoginInterceptor原来的代码

测试一下:数据库有缓存数据

后来测试发现验证码错误,原因时我少打了一个!号,应该是判断缓存数据与输入的用户数据是否相等,如果不相等,则返回验证码错误。,

二.商户查询缓存

1.添加商品缓存

添加Redis缓存:

ShopController

public interface IShopService extends IService<Shop> {Result queryById(Long id);
}

实现思路:
1.从redis查询商铺缓存
2.判断是否存在
3.存在,直接返回
4.不存在,根据id查询数据
5.不存在,返回数据库
6.存在,写入redis
7.返回

 @Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryById(Long id) {String key = CACHE_SHOP_KEY + id;// 1.从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}//4.不存在,根据id查询数据库Shop shop = getById(id);//5.不存在,返回错误if (shop == null) {return Result.fail("店铺不存在");}//6.存在写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));return Result.ok(shop);}

缓存练习题分析:


先删除缓存,再操作数据库的情况:

先删除缓存,后更新数据库
该方案也会出问题,此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)

请求A进行写操作,删除缓存
请求B查询发现缓存不存在
请求B去数据库查询得到旧值
请求B将旧值写入缓存
请求A将新值写入数据库
上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

正常的情况下:

异常情况下:(删缓存很快,但更新数据库很慢,在多线程并发时,会存在线程安全问题)

当数据从数据库写入缓存时

另外一个线程对数据库进行了更新操作。数据库和缓存产生数据不一致问题。

先操作数据库,再删除缓存的情况下:

在正常情况下:


异常情况下:

先操作数据库,再删除缓存这种方案更好。

  @Override@Transactionalpublic Result update(Shop shop) {Long id = shop.getId();if (id == null) {return Result.fail("店铺id不能为空");}//1.更新数据库updateById(shop);//2.删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + id);return null;}

2.缓存穿透

在redis中缓存空值

 stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);

为了解决缓存穿透引发的数据不一致问题:

我们需要提前检查redis中key对应的值是否是空值,如果是空值,则返回错误信息。注意这里的null是存在的,只不过是一个空值,它与什么数据都没有有着本质的·区别。

// 判断命中的是否是是空值if (shopJson != null) {// 返回一个错误信息return Result.fail("店铺信息不存在!");}

缓存雪崩:

缓存击穿:


怎样解决缓存击穿的问题:

第一种:互斥锁

第二种(逻辑过期):

Redis实战篇(视频学习来自黑马程序员)相关推荐

  1. [学习笔记]黑马程序员Spark全套视频教程,4天spark3.2快速入门到精通,基于Python语言的spark教程

    文章目录 视频资料: 思维导图 一.Spark基础入门(环境搭建.入门概念) 第二章:Spark环境搭建-Local 2.1 课程服务器环境 2.2 Local模式基本原理 2.3 安装包下载 2.4 ...

  2. [学习笔记]黑马程序员-Hadoop入门视频教程

    文章目录 参考资料 第一章:大数据导论与Linux基础(p1-p17) 1.1 大数据导论 1.1.1 企业数据分析方向 1.1.2 数据分析基本流程步骤 明确分析的目的和思路 数据收集 数据处理 数 ...

  3. 梦想在三十岁起航!__来自黑马程序员69期安卓班的学员

        梦想在三十岁起航! 多久没动笔了?恐怕我也记不起来了,在生活不如意的时候也曾想写点什么,可是却又全然没有思路,曾经的文思泉涌,早已在长时间浑浑噩噩的工作生活中干涸了,而今宛如仲永一般,泯然众人 ...

  4. [学习笔记]黑马程序员python教程

    文章目录 思维导图 Python基础知识图谱 面向对象 SQL入门和实战 Python高阶技巧 第一阶段 第九章:Python异常.模块与包 1.9.1异常的捕获 1.9.1.1 为什么要捕获异常 1 ...

  5. SSM 框架学习(黑马程序员)

    (Spring+SpringMVC+MyBatis) SSM框架教程 黑马程序员最全SSM框架教程|Spring+SpringMVC+MyBatis全套教程 01. Spring 简介 1.1 Spr ...

  6. 【转】2023年Java学习路线图-黑马程序员

    PS:注意收藏,此套路线图会不定期更新! Java学习路线图(2023版,视频已更新) 入门: Java SE基础 → Java Web(含数据库+H5+js+vue) 中级: Maven → Git ...

  7. Node.js学习笔记 [黑马程序员]——day34

    文章目录 初识 Express 简介 Express 的基本使用 托管静态资源 nodemon Express 路由 路由的概念 :dog:什么是路由 :dog:Express 中的路由 :dog: ...

  8. Node.js学习笔记 [黑马程序员]——day2

    文章目录 模块化的基本概念 模块化规范 Node.js 中模块的分类 Node.js 中模块的分类 加载模块 Node.js 中的模块作用域 向外共享模块作用域中的成员 `module` 对象 `mo ...

  9. C++入门学习(黑马程序员课程讲义)——第一阶段

    1 C++初识 1.1 编写C++程序步骤 四个:创建项目.创建文件.编写代码.运行程序 1.2 注释 单行注释://描述信息 (通常放在一行代码的上方,或者一条语句的末尾) 多行注释:/描述信息/ ...

  10. JavaSE学习(黑马程序员徐磊老师)day01

    1.Java快速入门 01.Java的概述(常识,没有什么含金量,但是要记住) Java是美国 sun 公司(Stanford University Network)在1995年推出的一门计算机高级编 ...

最新文章

  1. python 3读取文件-python3的txt文件读写
  2. 获取Docker中容器的信息
  3. 2017-10-9(Volley使用范例源码分析)
  4. codewars??? Is my friend cheating?
  5. x86已安装该产品 剑灵vcredist_MySQL Server v5.7正式版(附安装和配置数据库教程)
  6. TypeScript 学习一 参数,函数,析构表达式
  7. Pycharm上Django的使用 Day8
  8. JZOJ 3461. 【NOIP2013模拟联考5】小麦亩产一千八(kela)
  9. 用几个最简单的例子带你入门 Python 爬虫
  10. 二叉树的前中后层遍历
  11. OpenCV精进之路(零):core组件——Mat和IplImage访问像素的方法总结
  12. 【图像检索】基于matlab GUI KNN图像检索【含Matlab源码 267期】
  13. 库克谈人工智能:增长飞快 兼具颠覆性和创造性
  14. C/C++面试/笔试题2022
  15. hadoop搭建伪分布式集群(centos7+hadoop-3.1.1)
  16. poi 使用模板导出数据
  17. Linux文件管理及用户命令
  18. 【解决】阿拉伯语等右向左排版文字CSS解决方案
  19. DCM和PLL和MMCM的差别
  20. 程序员年薪20万、30万、40万都是如何生活的?

热门文章

  1. 设计师学python还是processing_人人都能学会的processing创意编程能实现什么?
  2. SWF是什么文件,SWF文件用什么软件可以打开
  3. 伪静态化不正常,电脑打不开贴子,手机可以
  4. 单片机编程软件很简单(24),keil单片机编程软件仿真、调试技巧+常见错误
  5. 前端 地图增加边框线_echarts map地图设置外边框或者阴影
  6. Java设计模式之适配器模式详解
  7. AVL Cruise 2020安装教程
  8. Apache SOLR and Carrot2集成
  9. html5 微信 飞机 源码,[HTML5]微信飞机大战
  10. QT出现“d:\Program Files (x86)\SogouInput\Components\”问题初步想法