分类 软件 下的文章

今天第一次用IDEA写跑Tomcat的Web项目。等了老半天项目都没部署上去,直接访问地址也是404,然后习惯性地点击终止服务器,打算重新启动一次。

然后遇到了这个问题:

Application Server was not connected before run configuration stop, reason:
Unable to ping server at localhost:1099

然后查看配置,发现这个1099指的是一个什么JMX端口,似乎是用来远程控制Tomcat用的。 然后网上试了好多办法都没用,甚至JB自家论坛的解决方案都是无效的。。

最后猜测可能是Tomcat自己本身完全就没有开启JMX功能(因为搜JMX好多都是教怎么启用的),然后倒腾了一下,发现照这个思路可以解决问题。

具体方法如下: Edit Configurations.. - Tomcat - <服务器名> - Startup/Connection,选择使用的配置方案(一般都是run吧),在Environment Variables中添加环境变量CATALINA_OPTS,值设置为

-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false

之后再使用该配置文件启动服务器,问题解决。

最近遇到的场景,有两张图片(cv2的BGR格式)和一个蒙版(只有0,1两种值),需要按照蒙版的值将两张图象合并。

一开始百度咕果上到处查,最接近的大概是bitwise_and函数,但是其起作用的主要是0这个像素值,显然只能实现抠图的功能,但是不能合并。可以看看介个

不废话,看代码:

import numpy as np

def mergeImgs(img1, img2, mask):
    '''
    Replacing places in img1 with img2 where mask = 1.
    '''
    img2 = img2 * mask[:,:,np.newaxis]
    mask = mask * (-1) + 1
    mask = mask.astype('uint8')
    img1 = img1 * mask[:,:,np.newaxis]
    img1 = img1 + img2
    return img1

原理是使用矩阵的乘法运算,把两张图片相反区域的、mask中值为0的区域,像素颜色设置为0,然后再把两张图矩阵相加。由于mask区域是直接用的取反处理,所以不会出现任意一个错误像素点(完美拼图)。

想着想着发现这个+*操作分别对应了OpenCV里的bitwise_andbitwise_or操作,估计最多就是效率上的差别吧= =

总而言之,OpenCV并没有提供一个一步到位的多图区域拼接函数,但是只要思路对了,自己几行代码也能搞定。

  1. 实际上SpringBoot是可以结合JSP使用的,不过需要加上providedRumtime("org.apache.tomcat.embed:tomcat-embed-jasper")作为JSP解析器。然而使用了JSP特性页面可能会出现编译错误,这个时候SpringBoot显示出来的是404。
  2. Thymeleaf的直接取值表达式是[[${val}]]
  3. 验证登录的时候,最好使用@{/login}获取Spring Security的登录地址,不然登录似乎会失败。
  4. Thymeleaf需要从application.properties一类配置文件中读取数据的时候,传统方法是先使用@Value{key.name}注入到对应类型的变量中,然后再用MVC的ModeladdAttribute()方法设置后传给对应的模板。
  5. 退出登录也要POST方法。
  6. 登陆/退出(默认URLlogin/logout)成功后自动跳转的方法名分别为defaultSuccessUrllogoutSuccessUrl。其中defaultSuccessUrl有第二个选填参数表示是否永远跳转至该页面。
  7. 别想着明文密码存储了,Spring Security 5删除了PlaintextPasswordEncoder,现在基本上都是用BCrypt,到网上随便找一个BCrypt加密工具来生成测试账户的密码吧。(或者用AuthenticationManagerBuilder.withUser直接在代码里也行,这里可以用明文)
  8. 可以通过重写UserDetailsUserDetailsService来重定向登陆所用的用户信息类到自己想要的方式。比如写一个连接数据库的Entity,加上jdbcAuthentication()(说实话,这个真的有必要吗),就可以实现从数据库中获取登录信息。进行鉴权处理的是Authentication类,所需要的UserDetails信息是约定俗成usernamepassword两个属性,其中password是存在数据库中的、使用约定(可以设置)的加密方式加密后的串。(不记得从哪看来的了,说是spring的原则是约定高于配置) 参考:https://www.baeldung.com/spring-security-authentication-with-a-database 以及附带的github代码
  9. 如果在post表单里用了th:object的话,在对应的Controller方法中要用@ModelAttribute(modelName)获得。
  10. Kotlin的foreach用的是大括号,可以使用it获取当前次循环正使用的元素。
  11. 若是对RequestMapping方法中,有javax.validation验证限制的对象使用@Valid注解的话,可以按照预期检查出错误来,但是由于validation本身发生在进入方法体之前,方法本身并不会被执行。需要使用ExceptionHandler来捕获异常,若是对数据传输对象Data Transfer Object, DTO使用@Valid的话,会产生org.springframework.validation.BindException(噗,stackoverflow上有个老哥写成了java.net.BindException
  12. Kotlin会给方法自动生成getter和setter,但是仅仅是作用和Java中的一样,并不能通过getXXX()方法来访问属性= =
  13. Kotlin通过反射调用方法有点麻烦,可以绕道Java,通过::class.java.getMethod().invoke()实现等效的功能。
  14. Thymeleaf在使用@{}生成链接地址的时候,可以通过特殊的方式生成含变量的路径,格式形如@{/path/{key}(key=${val})}会生成/path/${val}形式的地址。
  15. URL作为变量要用@PathVariable而不是@PathParam。以及为了防止万一,最好传入封装类型而不是基本类型。
  16. 可以使用th:checked属性设置输入复选框的初值。

Ура!是HTTP/2!

几个月之前就想给站点加上HTTPS了,我这么个无名小站都被强制上过一次广告(看自己网站的时候发现的= =)。万一变成人家的支付宝红包/淘宝活动/并夕夕推广重灾区了怎么办。直到今天才下定决心来解决这件事情,唉,拖延症病入膏肓啊。

初次接触到HTTP/2这个技术是某一次在看某v开头(咳咳)的魔法工具的时候,见到了Akamai公司做的一个示例。至于Akamai是个啥公司呢,接触过烂橘子的朋友肯定知道,这是一家CDN提供商,提供的服务主要是网络资源的托管一类的。(举个类似的例子,七牛云?)再加上Wappalyzer里面那个蓝色的小闪电看起来好炫!于是就自己动手,丰衣足食了。

其实整个过程中步骤十分简单,大致可以描述如下:

  1. 下载用于申请免费SSL证书的certbot-auto脚本;
  2. certbot-auto -d <domain-name>用于给指定域名申请SSL证书,如果用的是nginx,而且配置文件不是放在/etc/nginx.conf的环境下的话(LNMP说的就是你),可以选择在certbot-auto后添加certonly参数来表示只需要该脚本来申请证书,配置什么的由自己来;
  3. ssl on语句告诉nginx该server需要启用SSL协议;
  4. 如果用的是certbot-auto certonly的话,申请成功后会把证书的地址告诉用户,将该地址添加到nginx对应server的配置中即可。ssl_certificatessl_certificate_key对应的分别是证书的公钥和私钥,使用certbot-auto生成后两者的名字对应为fullchain.pemprivkey.pem,记得不要搞错了;
  5. listen参数的端口从80改为443,再在后面添加ssl http2参数,用于启用HTTP/2;
  6. 新建一个server,监听相同的地址的80端口,然后加一句:
    return 301 https://$server_name$request_uri;

    以使得访问该网站的用户都强制使用HTTPS。当然,如果浏览器支持HTTP/2的话,会自动启用该协议的;

  7. 可以添加这么两行:
    ssl_stapling on;
    ssl_stapling_verify on;

    来缩短SSL证书的查询时间,提高用户体验,具体信息请直接搜ssl stapling。

然后整个人就卡在了第二步,说是什么tk域名的CAA记录查不到,超时,卡了一上午,中午都差点头疼的午睡没睡着。。后来发现是云主机默认的DNS服务器有问题,加了一个Google的8888服务器问题马上解决,我tm。。。

最后顺带提一句,之前在网上看到过HTTP/2本身其实是可以不用SSL协议的,但是网络安全是大势所趋,所以基本上上了HTTP/2的都会用SSL,甚至两者变成了必然关系,很少有见到裸HTTP/2的配置教程了。

在初步接触Java Servlet之后,会发现一个HTTPServlet有这么两个附属对象:ServletConfigServletContext,两者长得很像,又有着不少相同的方法(getAttribute(), getInitParameter(), getServletName()/getServletNames()),在网上搜了一下,发现有讲述两者之间的区别的博客,但是我是在理解之后才看懂的。。

在此简述一下使用Tomcat运行Web服务的三大主要层次,我依次将其命名为Server, Application和Servlet。其中Server指的是Tomcat本身,可以理解为一台服务器上一般只会跑一个Tomcat,所有需要借助Tomcat实现的Web服务都会依赖于这一个Tomcat实例。其次就是Application了,这就是上面所说,需要借助Tomcat实现的服务。一个Web服务就是一个Web项目,内部可以有复杂的Java逻辑代码,可以包括与数据库交互,等等。一个Servlet指的是提供单一Web功能的程序单位。

Application举例说明的话就是登陆系统,从登陆到连接后台,查看数据,修改用户信息、密码等,算是一个完整的后台管理系统服务,仅靠单一的URL是很难(但是是可以)实现整个功能的,起码作为一个便于维护和扩展的项目来说,都会需要多个URL来实现不同的功能,因为这样至少光靠URL地址就能有效地组织整合不同的功能了。这是Application。而Servlet实现的是原子操作,比如说后台管理系统中的登陆,或是获取用户信息,或是修改密码这种单一而清晰的任务。

然后就可以说明了:ServletConfig是Servlet层面的,每个Servlet都有自己独立的ServletConfig;而ServletContext是Application层面的,同一服务/项目下的所有Servlet可以通过ServletContext共享数据。那为什么ServletContext不是Server层面呢?ServletContext中可以存放应用的重要数据,出于安全考虑,一是防止数据窃取,二是防止篡改,不论是无意还是有意。

再仔细看看ServletContext的方法的话,会发现有一个方法名为getServletNames(),返回的是一个Enumeration,也就是说具有多个Servlet名称。相对地,ServletConfig的方法名为getServletName(),返回一个String。这下就更加清晰了,显然一个Servlet只应该有一个名字,那么ServletContext肯定就是Servlet层面之上的了。

此外有一个小坑:声明Application级别的初始变量是在web-app建立context-param子节点,使用ServletContextgetInitParameter()方法获取;Servlet级别的初始变量则需要在web-app节点下先新建servlet子节点,再在servlet子节点下新建init-param节点定义,使用ServletConfiggetInitParameter()方法获取。其中两个param的定义结构相同,使用param-name子节点定义变量名,param-value子节点定义变量值,而显然这个值只能定义为String类型。

例如:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">

  ...

  <context-param>
    <param-name>contextInitParam</param-name>
    <param-value>someValue</param-value>
  </context-param>

  <servlet>
    <servlet-name>demoServlet</servlet-name>
    <servlet-class>tk.esperz.demoServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
      <param-name>servletInitParam</param-name>
      <param-value>otherServletsCannotSeeMe</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
     <servlet-name>demoServlet</servlet-name>
     <url-pattern>/demo.do</url-pattern>
  </servlet-mapping>
</web-app>

其中为该Web项目定义了一个名为contextInitParam的上下文初始化变量,值为someValue,所有该项目中的Servlet都可以通过ServletContext.getInitParameter("contextInitParam")获取;为名为demoServlet的Servlet定义了一个名为servletInitParam的初始化变量,值为otherServletsCannotSeeMe,仅在该Servlet内可以通过ServletConfig.getInitParameter("servletInitParam")获取。凡是用错了方法的都会返回null