简介
这个功能写成了一个springboot项目,部署在taptap的云引擎上。
taptap云引擎官方文档
使用了官方提供的命令行工具,创建项目和部署到云引擎。命令行文档
他生成的项目实际是springboot的改版,添加了一些他们独有的功能。比如云函数等。
实际开发与平时一致(他们提供的功能其实基本没用到,或许以后会用到。
另外,要吐槽的一个点就是:
项目生成默认配置使用的是Java11,但是部署到云引擎时,报错。
换成Java8后正常运行。
版本问题,影响不大。新版任你发,我用Java 8。
可复用模块
从请求中获取ip地址
Remote Address:
Remote Address代表HTTP请求的远程地址,即请求的源地址。http协议在三次握手时时用的就是这个Remote Address地址,发送响应报文时也是使用的这个Remote Address地址。
所以,Remote Address地址是不能伪造的,否则请求者会收不到响应报文。
但是,**http请求经过代理服务器转发时,用户真实ip会丢失。**所以有了X-Forwarded-For
获取ip的方式。
X-Forwarded-For:
为了避免真实ip的丢失,代理服务器会增加叫X-Forwarded-For的头信息。将客户端ip记录到其中,以保证服务器可以获取到客户端真实ip。
X-Forwarded-For是一个拓展头。虽然HTTP/1.1(RFC 2616)协议并没有对它的定义,但它已经成为事实上的标准(都在用
X-Forwarded-For请求头格式:X-Forwarded-For: client, proxy1, proxy2
第一个便是请求的原始ip,后面则是代理服务器的ip。
由于请求头可以伪造,所以不要相信请求头中携带的ip信息。
直接对外提供服务的 Web 应用,在进行与安全有关的操作时,只能通过 Remote Address 获取 IP,不能相信任何请求头;
使用 Nginx 等 Web Server 进行反向代理的 Web 应用,在配置正确的前提下,要用 X-Forwarded-For 最后一节 或 X-Real-IP 来获取 IP(因为 Remote Address 得到的是 Nginx 所在服务器的内网 IP);同时还应该禁止 Web 应用直接对外提供服务;
在与安全无关的场景,例如通过 IP 显示所在地天气,可以从 X-Forwarded-For 靠前的位置获取 IP,但是需要校验 IP 格式合法性;
参考文章:
关于X-Forwarded-For的介绍
HTTP 请求头中的 X-Forwarded-For
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
public static String getIP(HttpServletRequest request) { String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); if ("127.0.0.1".equalsIgnoreCase(ip) || "0:0:0:0:0:0:0:1".equalsIgnoreCase(ip)) { InetAddress iNet = null; try { iNet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } if (iNet != null) ip = iNet.getHostAddress(); } } if (ip != null && ip.length() > 15) { if (ip.indexOf(",") > 0) { ip = ip.substring(0, ip.indexOf(",")); } } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; }
|
密码的md5加密
MD5,全称 消息摘要算法第五版(Message Digest Algorithm 5)
不多介绍,详见MD5百度百科
关于加密算法的改进:
- 加盐
即在原来的明文中加入一组随机串,再通过加密算法加密,将密文存入数据库。
- 加次数
即多加密几次,增加破解难度。不过会消耗更多计算资源。
jdk自带api:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public static String md5(String password){ String hashedPwd = null; try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(password.getBytes());
hashedPwd = new BigInteger(1, md.digest()).toString(16); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return hashedPwd; }
|
spring的DigestUtils工具类
1 2 3 4
| public static String md5(String password) { return DigestUtils.md5DigestAsHex((password).getBytes()); }
|
发送mail邮件
使用JavaMail发送邮件
依赖:
1 2 3 4 5 6
| <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version> </dependency>
|
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| private static final String mailHost = null;
private static final String mailTransportProtocol = "smtp";
private static final String mailSmtpAuth = "true";
private static final String fromEmail = null;
private static final String password = null;
public static void sendMail(String toEmail){ String sendContent = "test mail"; Properties prop = new Properties(); prop.setProperty("mail.host", mailHost); prop.setProperty("mail.transport.protocol", mailTransportProtocol); prop.setProperty("mail.smtp.auth", mailSmtpAuth); Session session = Session.getInstance(prop); Transport ts; try { ts = session.getTransport(); ts.connect(mailHost, fromEmail, password); MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(fromEmail)); message.setRecipient(Message.RecipientType.TO, new InternetAddress(toEmail)); message.setSubject("标题"); message.setContent(sendContent, "text/html;charset=UTF-8"); ts.sendMessage(message, message.getAllRecipients()); ts.close(); } catch (MessagingException e) { e.printStackTrace(); } }
|
参考文章:
使用JavaMail创建邮件和发送邮件
使用springboot集成的mail模块
依赖:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
|
配置:
1 2 3 4
| spring.mail.protocol= spring.mail.host= spring.mail.username= spring.mail.password=
|
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Resource private JavaMailSender javaMailSender; @Value("${fromEmail}") private String fromEmail;
public static void sendMail(String toEmail){ String sendContent = "test mail"; try { MimeMessageHelper messageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(), true); messageHelper.setFrom(fromEmail); messageHelper.setTo(toEmail); messageHelper.setSubject("标题"); messageHelper.setText(sendContent, true); javaMailSender.send(messageHelper.getMimeMessage()); } catch (MessagingException e) { e.printStackTrace(); } }
|
jdbc数据库连接池(鸽了)
因为建立数据库连接与关闭数据库连接是非常耗时的事情,如果每次查询都建立连接、关闭连接会产生很大的性能开销。
所以有了连接池的出现来解决这一问题。
即在程序启动时,初始化连接池(连接数据库,创建多个连接)。在需要使用时从连接池中获取连接,使用结束放回连接池。
以减少性能开销。
代码:
附DataSource的产出背景:
DataSource,一个被严重低估的接口
bug及解决方案
session变化
现象
ajax请求及其余请求在前几次请求时,session会发送变化。导致存在session中的数据获取不到。
原因
通过HttpServletRequest获取session对象时,使用 request.getSession()
方法。
getSession方法会检测当前是否有session存在,默认不存在会创建一个新的session,存在则返回。
ajax请求跨域请求默认不携带cookie信息。即获取不到session
解决方案(未解决)
调用getSession方法时传入参数false或true
例如:request.getSession(false);
为true时,先查看请求时是否有sessionID。如果没有,则创建一个新的session对象。如果有则根据sessionID查找对应的session对象,找到了就返回该session对象,没找到就创建新的session对象。
为false时,先查看请求中是否有sessionID,没有则返回null。有则根据sessionID查找对应的session对象,找到了就返回该session对象,没找到就创建新的session对象。
默认为true
建议:
往session中写入参数时使用 request.getSession();
从session中读取参数时使用 request.getSession(false);
附session其他操作:
设置值:session.setAttribute(String name,Object obj);
读取值:session.getAttribute(String name);
删除session:session.invalidate();
让ajax请求携带参数
添加属性:xhr.withCredentials=true
附js原生实现ajax请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| var Ajax = { get: function (url, callback) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, false); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if (xhr.status == 200 || xhr.status == 304) { console.log(xhr.responseText); callback(xhr.responseText); } } } xhr.send(); },
post: function (url, data, callback) { var xhr = new XMLHttpRequest(); xhr.open('POST', url, false); xhr.withCredentials=true xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200 || xhr.status === 304) { callback(xhr.responseText); } } } xhr.send(data); } }
|
说明:
这个bug其实并未解决,因为部署到tap云引擎时,是一个springboot项目。所有的请求应该都是同源的,不会出现跨域的情况。
而session变化原因,就是getSession会创建新的session对象。
将getSession传入false,同时改完ajax属性后,这个bug依旧会出现。
在部署到生产环境后,依旧有用户偶尔会出现了session为null的情况。
2022-08-12 暂未解决。
总结
第一次使用平台提供的自动化的部署和管理功能。
有部署状态(预备环境和生产环境)、请求统计、日志、及环境变量各种设置等。
taptap云服务还是很成熟的。相比自己在腾讯云服务器上使用要方便很多,不管是部署还是监控。
官方文档也相对很齐全,参看文档来使用是完全可以的。