12月每日收获

135 阅读14分钟

12.8

  • RuntimeException异常不需要捕获,也不需要在方法头中抛出。运行时异常包括数学运算异常(除0),空指针异常,数组越界异常等,这些异常不需要捕获的原因是调用方本身无法处理或恢复的程序错误,这些程序错误通常是无法在运行过程中处理的,必须改正程序代码。其他非运行时错误比如文件未找到异常,可以捕获后让用户重新输入一个正确的文件路径,这种就是可以处理的。
  • @RequestParam:这个注解用于两种情况,第一种是获取get请求的参数,get请求的参数是放在请求头的,使用@RequestParam获取到get请求头的值;第二种是获取其它类型的请求(POST,DELETE)的**Content-Typeapplication/x-www-form-urlencoded** 请求体数据,这个类型一般使用表单提交,数据在请求体中使用&拼接,获取数据可以使用该注解。
  • @RequestBody:这个注解可以获取请求体的数据,一般用于处理非 Content-Type: application/x-www-form-urlencoded编码格式的数据,比如:application/jsonapplication/xml等类型的数据。就application/json类型的数据而言,使用注解@RequestBody可以将body里面所有的json数据传到后端,后端再进行解析。
  • @PathVariable:获取URL变量的注解,URL是@RequestMapping(value="/user/{username}"),就可以使用@PathVariable(value="username") String username来获取到数据。

12.11

  • mybatis的update语句返回值有几种。1表示要更新数据能在数据库中找到,不管更新之后数据变没变;0表示数据在数据库中没找到,无法更新;负数表示执行出错。

12.18

  • 转发和重定向的区别:转发是指服务器接收到客户端的请求后,将请求转发给另一个资源(通常是服务器内的另一个程序),客户端并不知道这个过程,因为它只知道与最初请求的服务器通信。重定向是指服务器接收到客户端的请求后,返回一个状态码和新的URL给客户端,要求客户端重新发送请求到这个新的URL。客户端接收到重定向响应后,会再次向新的URL发起请求,浏览器的url地址会变化。转发客户端只会发送一个请求,重定向客户端会发送两个请求并且第二次请求会丢失第一次请求的参数。转发使用的场景是服务器内部资源的交互和处理。当一个资源需要另一个资源处理请求时,服务器可以使用转发来将请求传递给其他程序或组件,以便它们可以处理请求并生成响应,一般不能跨域只能用于访问服务器内部资源,浏览器的url地址不会变化。重定向使用场景是用于引导用户到其他位置。例如,在网站重构时,旧的URL可能会重定向到新的URL,以确保用户能够找到新的资源。另一个例子是在网站上进行身份验证后,用户可能会被重定向到他们最初请求的页面,跨域重定向。image.png
  • 单点登录:SSO 是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。需要在客户端保存一个token,请求服务器时带上这个token去做校验用户的登录状态,这个token可以存储在cookie中也可以存储在localstorage中。注意跨域的问题,cookie有个域范围,url请求在范围之内的才会携带上对应的cookie。跨域cookie的解决可以利用重定向,在sso服务器返回请求时设置sso的请求域的cooike并且携带token重定向到业务url,业务收到后在设置业务请求域的cooike,这样就设置了跨域的cookie为同样的值。SSO 单点登录详解
  • CSRF攻击:CSRF(Cross-Site Request Forgery)跨站请求伪造攻击。使用token可以防止CSRF攻击,因为token需要前端手动加到http请求中,而cookie是自动携带的。
    • 1、用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
    • 2、在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
    • 3、用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
    • 4、网站B接收到用户请求后,开始攻击,发出一个请求要求访问第三方站点A;比如转账请求www.mybank.com/Transfer?ba…
    • 5、浏览器根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。 image.png
  • localstorage和cookie的区别
    • 存储容量:Cookie每个特定的域下,浏览器通常允许存储的 cookie 数量为 20 个,每个 cookie 的大小限制在 4KB 左右。localStorage通常允许存储 5MB 左右的数据。
    • 生命周期:Cookie可以设置失效时间,如果不设置失效时间,则默认为会话级别的 cookie,关闭浏览器后会被删除。localStorage除非被清除,否则数据永远不会过期。
    • 与服务器通信:Cookie每次请求一个新页面时,cookie 会被发送到服务器。localStorage数据只会保留在客户端,不会被发送到服务器。
  • 跨域跨站:浏览器有同源策略,如果两个URL的协议,域名,端口完全一致才是同源。有一个不一致就是跨域。只要两个 URL 的 eTLD+1 相同即是同站,不需要考虑协议和端口。eTLD+1: 有效顶级域名+二级域名,如 taobao.com,baidu.com,sugarat.top

12.25

  • hyperloglog原理:Redis HyperLogLog 是用来做基数统计的一种数据结构,基数统计就是为了计算在一批数据中,它的不重复元素有多少个。HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,在标准误差0.81%的前提下,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。hyperloglog有三个比较重要的操作,第一个是 add 操作也就是添加指定元素到 HyperLogLog 中,第二个是 count 操作返回给定 HyperLogLog 的基数估算值,第三个是 merge 操作将多个 HyperLogLog 合并为一个 HyperLogLog。在统计文章的 UV 时,每次访问把用户的ip加到 HyperLogLog 中,返回给前端的 UV 就是 HyperLogLog count 的返回值。定时任务刷到数据库中。还可以统计更加细粒度的 UV ,比如统计每一天的 UV ,每小时的 UV ,把 key 设置成与文章 id 和时间有关的即可,在需要统计更粗粒度的 UV 时,可以采用 merge 操作合并每天的 UV 得到每个月的 UV。(如果 HyperLogLog 需要设置过期时间,把登录的ip用消息队列存到数据库中,当需要统计UV但是对应的HyperLogLog过期被淘汰时,可以从数据库把所有ip读出存入HyperLogLog再进行统计)
  • 缓存击穿的处理方式:使用互斥锁避免请求瞬间全部打到数据库查询上,造成数据库的压力瞬间增大。互斥锁的请求方式有两种:
    • redis实现的分布式锁/ReentrantLock:这两种锁都可以尝试只获取一次锁,返回成功或者失败。具体实现上会先从缓存中查询数据,如果有则返回,没有就尝试加锁一次,如果失败就休眠一段时间再次调用该方法查询数据(从头开始),成功就查数据库,把数据加到缓存中,返回查询的数据。这种实现只有一个请求会获取到锁。
    • synchronized:线程会一直阻塞到获取锁为止。具体实现是先从缓存中查询数据,如果有则返回,没有就使用synchronized加锁,该关键字拿不到锁时会一直阻塞线程,直到锁被释放该线程拿到锁才继续执行,得到锁后需要先判断能否从缓存中查询数据,因为有可能之前已经有请求获取到锁查数据库加缓存。如果有则返回,没有就查数据库,把数据加到缓存中,返回查询的数据。这种实现需要做双重检查。
  • 布隆过滤器:HyperLogLog没有判断一个元素是否包含的方法,只可以先获取数量,添加元素后获取数量,对比这两个数量是否一致判断这个元素是否已经被添加,但不是一个原子操作,需要自己加锁,实现较为复杂。布隆过滤器可以使用BF.ADD的返回值判断,为1表示新添加, 0表示已经被添加过,也可以使用BF.EXISTS来判断,布隆过滤器比hyperloglog内存占用大点。
  • 认证:"Authorization: Bearer" 是一种在 HTTP 请求头部中用于传递访问令牌(Access Token)的常见格式。它用于在客户端和服务器之间进行身份验证和授权操作,格式为:
    Authorization: Bearer your_access_token
    
    它指示后面的令牌是访问令牌,服务器可以读取并使用该令牌进行身份验证和授权操作,Bearer 常见于 OAuth 和 JWT 授权。
  • es和mysql各自优势:es适合文本搜索和分析,关键词查询等;mysql适合结构化数据存储和事务的处理。
  • kafka + elasticsearch 实现文章发布:用户发布文章后需要把文章信息添加到 mysql 和 es 中去,如果很多用户同时发布文章,可能插入数据库操作需要一定的时间。本项目在用户点击文章发布后,把文章信息添加到一个消息队列中去,直接给用户返回发布成功的界面,插入数据库操作变成异步。消息队列监听器收到消息后把文章数据插入 mysql 和 es 中。
  • elasticsearch实现文章搜索:设置分页和要查询的索引,设置must并添加match,match的查询词会被分词,在设置要高亮的字段,也就是文章标题和文章摘要,在高亮字段内容前后设置颜色标签<span style='color:red'>,发送请求得到结果后解析。返回结果的条件查询和高亮查询是在两个字段中,还没有组合在一起,我们需要把高亮字段替换原字段内容。先以Map<String, Object>获取到原来的结果,再解析出对应的高亮字段(高亮的配置如下:fragment_size这个参数表示高亮字段内容长度,去除html样式标签,统计字数,默认为100;number_of_fragments:可以返回的最大片段数。如果片段数设置为0,则不返回任何片段。而是突出显示并返回整个字段内容,默认为5;no_match_size:如果没有要突出显示的匹配片段,则要从字段开头返回的文本量,默认为0即不返回任何内容。如果字段的内容过多,可以设置这些参数着重显示某些语句,本项目是标题和摘要,不超过100字符按照默认即可),把原来的字段替换成高亮字段即可,即使用sourceAsMap.put(字段,内容)。

12.26

9.4 AOP打印日志以及设置缓存
这两个功能的实现都是先自定义一个注解,然后使用AOP技术以自定义注解作为切点,在方法前后加上特定的代码实现功能,在需要该功能的地方加上自定义注解即可。访问修饰符必须为public,不写默认为pubic,关键字为@interface,注解名称为自定义注解的名称,引用该注解就是@注解名称;接着声明注解类型元素,访问修饰符必须为public,不写默认为public,该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一维数组,注解类型元素允许我们设置默认值,如果不设置则在后续使用注解时候,必须填写对应的属性值。可以使用元注解对自定义注解进行进一步的设置,所谓元注解就是专门修饰注解的注解,有4个标准的元注解类型。

  • @Target:用于描述注解的使用范围,该注解可以使用在什么地方,比如应用于类,应用于属性,应用于方法,应用于方法的形参等。
  • @Retention:表明该注解的生命周期,取值包含三种。
    • SOURCE:源码级别保留,编译后即丢弃。一般用于做一些检查性的操作,比如 @Override 检查该方法是否为重写方法。
    • CLASS:编译级别保留,编译后的class文件中存在,在jvm运行时丢弃,这是默认值。一般用于要在编译时进行一些预处理操作的场景。
    • RUNTIME:运行级别保留,由 JVM 加载,包含在class文件中,在运行时可以被获取到。如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解。
  • @Documented:指明修饰的注解,可以被例如javadoc此类的工具文档化。
  • @Inherited:用于标注一个父类的注解是否可以被子类继承,如果一个注解需要被其子类所继承,则在声明时直接使用@Inherited注解即可。如果没有写此注解,则无法被子类继承。注意实现接口不能继承只能是父子关系。

使用AOP打印方法日志,包括调用的方法名称,方法参数,调用时间,模块名称和具体操作。首先自定义注解LogAnnotation,设置该注解的使用范围是方法,由于AOP以该注解作为切点,需要在运行时可以获取到,设置该注解的生命周期为RUNTIME,声明注解类型元素为module和operator,也就是模块和具体操作,在需要打印日志的方法上加上对应的注解设置元素值即可@LogAnnotation,使用起来非常方便,减少了代码的冗余。声明一个该注解的切入点,@Around配置该切入点的环绕通知并传入参数ProceedingJoinPoint,输出调用的方法名称,方法参数以及调用时间。

使用AOP设置缓存,首先自定义注解,配置和日志一样,声明注解类型元素为过期时间和操作名称,这个操作名称写成格式化的字符串使用大括号 {} 作为占位符,把方法的参数使用String的format方法填入作为key,该方法查询的结果作为value存入缓存,在执行方法的时候先到缓存池中查询,如果有直接返回,如果没有则继续查询并将结果存入缓存,在需要缓存的方法上加上对应的注解即可@Cache,使用起来非常方便,减少了代码的冗余。主要是对文章的详细内容做了缓存,如果文章做了更新就删除对应的缓存。考虑缓存击穿问题,使用redis锁作为互斥锁减小数据库的压力,在注解类型元素多传入一个锁名称,获取不到缓存时去循环并休眠获取互斥锁,获取到后判断缓存是否有,没有查数据库加缓存返回数据。