/* Welcome to Compass.
* In this file you should write your main styles. (or centralize your imports)
* Import this file using the following HTML or equivalent:
* <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" /> */
@import "variable"; //导入变量文件
// 需要安装插件 gem install compass-normalize
// config.rb配置 require 'compass-normalize'
@import "normalize" ;
//@import "compass/reset";
@import "compass/layout";
@include sticky-footer(54px);
/**
`commonProperty`先声明再使用
*/
@mixin commonProperty($w: 50%) {
background: #fff;
padding: 5px;
width: $w;
float: left;
}
.btn { // 编译「显示」到css中
margin: 0;
}
%btn-hidden { // 编译「不显示」到css中
text-align: center;
}
//----
.btn-primary {
@extend .btn;
@extend %btn-hidden;
@include commonProperty();
padding: 9px;
}
html, body {
color: $color;
font: { // 属性嵌套
family: '黑体';
size: 16px;
}
@at-root { // 保留层次关系,编译后到顶层
.header {
position: relative;
}
}
}
p {
@include commonProperty(100%);
background-image: image-url("a.png"); //属性中,直接调用函数
}
@debug append-url("hello.png");
/**
`makecText`先声明再使用
*/
@function makeText($selector) {
@return $selector;
}
#{ makeText(".hello-world") } { //如果在选择器材中使用,#{functionName}
color: red;
font-size: 20px;
}
Apache + Let’s Encrypt配置HTTPS
申请通配符证书
# 下载 Certbot 客户端
$ wget https://dl.eff.org/certbot-auto
# 设为可执行权限
$ chmod a+x certbot-auto
# 移动的bin目录下
mv certbot-auto /usr/bin
部署apache
$ sudo certbot-auto --apache
根据命令行提示进行操作
- 校验证书信息
openssl x509 -in /etc/letsencrypt/live/xhope.top/cert.pem -noout -text
- 证书和密钥保存在下列目录
tree /etc/letsencrypt/live/xhope.top/
修改配置文件
- 查看apache配置文件路径
$ httpd -V
- 编辑
/conf.d/ssl.conf,使用证书。
LoadModule ssl_module modules/mod_ssl.so
Listen 443
<VirtualHost *:443>
ServerName www.xhope.top
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/xhope.top/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/xhope.top/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/xhope.top/chain.pem
</VirtualHost>
- 强制跳转到https,在网站的根目录下创建
.htaccess文件
$ cd /var/www/html
$ vi .htaccess
编辑.htaccess
RewriteEngine On
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R,L]
- 检查证书状态
https://www.ssllabs.com/ssltest/analyze.html?d=xhope.top
- 其他工具检查
使用 crontab 让 lets encrypt 自动续期
Let’s Encrypt 证书只有 90 天的有效期,一旦我们忘记了就会失效了。所以建议使用 crontab 进行自动续期,让证书一直有效。
$ crontab -e
在编辑模式下添加
0 3 1 * * /usr/bin/certbot-auto renew 2>>/var/log/cert-renew.log >>/var/log/cert-renew.log
用 crontab -l 命令查看一下是否存在刚才添加的定时命令。如果存在的话,那么每月 1 日的凌晨 3 点就会执行一次所有域名的续期操作。不过有请求次数的限制所以别太频繁了。
$ crontab -l
https://www.vpsss.net/1328.html
参考网站:
- https://www.booleanworld.com/adding-https-website-lets-encrypt/
- https://www.jianshu.com/p/df6d13187578
- https://zhuanlan.zhihu.com/p/26309980
- https://www.hi-linux.com/posts/6968.html
- https://httpd.apache.org/docs/2.4/ssl/ssl_howto.html
- https://linuxhostsupport.com/blog/how-to-install-lets-encrypt-on-centos-7-with-apache/
- https://certbot.eff.org/lets-encrypt/centosrhel7-nginx
- https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-centos-7
- http://yhz61010.iteye.com/blog/2365312
- https://towait.com/blog/secure-apache-with-lets-encrypt-on-centos-7/
- https://stringblog.com/%E9%80%9A%E8%BF%87certbot%E9%85%8D%E7%BD%AElets-encrypt%E7%9A%84ssl%EF%BC%88apache%EF%BC%89/
- https://zhuanlan.zhihu.com/p/35155749
阿里云API使用
阿里云获取Access Key,在控制台 > 头像hover > accesskeys
API:域名查看是否可以注册
文档地址:域名Check接口
手动实现签名
手动实现比较麻烦,但是可以了解签名的组成过程。
package com.yodean.component.site;
import org.springframework.util.Base64Utils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* All rights Reserved, Designed By www.xhope.top
*
* @version V1.0
* @Description: (用一句话描述该文件做什么)
* @author: Rick.Xu
* @date: 11/19/18 18:49
* @Copyright: 2018 www.yodean.com. All rights reserved.
*/
public class Test {
private static final String ENCODING = "UTF-8";
private static final String ALGORITHM = "HmacSHA1";
private static String percentEncode(String value) throws UnsupportedEncodingException {
return value != null ? URLEncoder.encode(value, ENCODING).replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
}
private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private static String formatIso8601Date(Date date) {
SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT);
df.setTimeZone(new SimpleTimeZone(0, "GMT"));
return df.format(date);
}
public static void main(String[] args) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
Map<String, String> parameters = new HashMap();
// 输入请求参数
parameters.put("Action", "CheckDomain");//变量
parameters.put("DomainName", "google.com");
parameters.put("FeeCommand", "create");
parameters.put("FeeCurrency", "CNY");
parameters.put("FeePeriod", "1");
// 公共参数
parameters.put("Version", "2018-01-29"); //变量
parameters.put("AccessKeyId", "test");
parameters.put("Timestamp", formatIso8601Date(new Date()));
parameters.put("SignatureMethod", "HMAC-SHA1");
parameters.put("SignatureVersion", "1.0");
parameters.put("SignatureNonce", UUID.randomUUID().toString());
parameters.put("Format", "JSON");
StringBuilder url = new StringBuilder();
url.append("http://domain.aliyuncs.com/?"); //变量
parameters.forEach((k, v) -> {
url.append(k).append("=").append(v).append("&");
});
// 获取签名字符串
String stringToSign = getStringToSign(parameters);
System.out.println(stringToSign);
System.out.println();
// 签名
String signature = signature(stringToSign);
System.out.println(signature);
System.out.println();
// 添加签名到原始url
url.append("Signature=" + signature);
System.out.println(url.toString());
}
private static String signature(String stringToSign) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
String key = "testsecret&";
Mac mac = Mac.getInstance(ALGORITHM);
mac.init(new SecretKeySpec(key.getBytes(ENCODING), ALGORITHM));
byte[] signData = mac.doFinal(stringToSign.getBytes(ENCODING));
String signature = new String(Base64Utils.encodeToString(signData));
return percentEncode(signature);
}
private static String getStringToSign(Map<String, String> parameters) throws UnsupportedEncodingException {
final String HTTP_METHOD = "GET";
// 排序请求参数
String[] sortedKeys = parameters.keySet().toArray(new String[]{});
Arrays.sort(sortedKeys);
final String SEPARATOR = "&";
// 构造 stringToSign 字符串
StringBuilder stringToSign = new StringBuilder();
stringToSign.append(HTTP_METHOD).append(SEPARATOR);
stringToSign.append(percentEncode("/")).append(SEPARATOR);
StringBuilder canonicalizedQueryString = new StringBuilder();
for(String key : sortedKeys) {
// 这里注意编码 key 和 value
canonicalizedQueryString.append("&")
.append(percentEncode(key)).append("=")
.append(percentEncode(parameters.get(key)));
}
// 这里注意编码 canonicalizedQueryString
stringToSign.append(percentEncode(
canonicalizedQueryString.toString().substring(1)));
return stringToSign.toString();
}
}
SDK
百度云API使用
Access Key
域名服务API仅对申请的用户开放
,使用前请先提交工单申请。提交工单需要提供AK(Access Key),查看方式如下:管理控制平台>鼠标悬浮到右上角,您的用户名的地方不要点击,下面有一个”安全认证”>点击安全认证,里面有AK。
一般半天就能审核下来
使用API前的准备
自己搞签名太繁琐,可以用提供的SDK。下载地址,也可通过maven引用
pom.xml
<dependency>
<groupId>com.baidubce</groupId>
<artifactId>bce-java-sdk</artifactId>
<version>0.10.42</version>
</dependency>
通过使用【位置服务】测试SDK正确性。帮助文档
main.java
BceClientConfiguration config = new BceClientConfiguration();
config.setCredentials(new DefaultBceCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY));
// 设置网络协议
config.setProtocol(Protocol.HTTPS);
// 设置允许打开的最大连接数
config.setMaxConnections(10);
// 设置建立连接的超时时间
config.setConnectionTimeoutInMillis(5000);
DuMapClient client = new DuMapClient(config);
PlaceSearchByRegionParam param = PlaceSearchByRegionParam.builder()
.query("ATM机") // 检索关键字
.tag("银行") // 检索分类偏好
.region("北京") // 检索行政区划区域
.output("json") // 输出格式为json字符串或者xml字符串
.build();
String response = client.placeQuery(appId, param); // <your-app-id> 为用户的appId,即前端应用列表中的应用id
System.out.println(response); //JSON字符串
位置服务需要提供your-app-id,这个可以在控制台设置,链接
调用域名API
域名服务并没有自己的xxxClient,需要模仿DuMap服务,写一个。
DomainResponse.java
public class DomainResponse extends AbstractBceResponse {
private String payload;
public DomainResponse() {
}
public String getPayload() {
return this.payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
}
DomainResponseHandler.java
public class DomainResponseHandler implements HttpResponseHandler {
@Override
public boolean handle(BceHttpResponse httpResponse, AbstractBceResponse response) throws Exception {
InputStream content = httpResponse.getContent();
DomainResponse domainResponse = (DomainResponse)response;
if (content != null) {
domainResponse.setPayload(new String(ByteStreams.toByteArray(content)));
content.close();
}
return true;
}
}
DomainClient.java
public class DomainClient extends AbstractBceClient {
private static HttpResponseHandler[] dumapHandlers = new HttpResponseHandler[]{new BceMetadataResponseHandler(), new BceErrorResponseHandler(), new DomainResponseHandler()};
public DomainClient(BceClientConfiguration config){
super(config, dumapHandlers);
}
public String query() throws URISyntaxException {
String s = "http://bcd.baidubce.com";
URI uri = HttpUtils.appendUri(new URI(s), "/v1/domain/price");
InternalRequest request = new InternalRequest(HttpMethodName.GET, uri);
request.addParameter("domain", "51fzlh.com");
DomainResponse response = this.invokeHttpClient(request, DomainResponse.class);
return response.getPayload();
}
}
Main.java
BceClientConfiguration config = new BceClientConfiguration();
config.setCredentials(new DefaultBceCredentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY));
DomainClient client = new DomainClient(config);
System.out.println(client.query());
Foxmail邮件会话模式实现逻辑
邮件信息的组成部分
- folder: 位置信息
收件箱发件箱 - Message_ID: 邮件的ID
- subject: 主题
- sessionSubject 会话主题。去除主题的前缀,如
回复Re答复自动回复自动答复 -
References:
当回复(全部回复)/转发一个邮件的时候,当前邮件回在头信息中添加References,参照Message_ID。多轮回复,邮件有多个References.Root引用在第一个位置 -
Default References(subject + 所有参与者)为组合生产一个定义的
Default References,这个always有。
会话的聚合逻辑
聚合的原则:
将相同【会话主题】和具有【引用关系】邮件组成一个会话模式
* 引用关系包含邮件References和Default Reference
几种情况的说明:
-
无主题的不聚合
-
转发的虽然有引用,但是不聚合,从会话中脱离出来。因为主题发生了变化。“回复:你好”的会话主题是“你好”,而“转发:你好”的会话主题是“转发:你好”
具体步骤:
数据准备
-
拉取所有的
收件箱邮件 -
按条件拉取
发件箱,找跟收件箱的关系前提,必须
收件箱有的【会话主题】- 被
收件箱邮件引用 - 引用
收件箱 - 和
收件箱有相同的邮件引用 & 收件时间 < 发件时间 - 和
收件箱有相同的自定义引用 & 邮件引用不存在 & 收件时间 < 发件时间
- 被
Query.sql
SELECT
SUBJECT,
NAME,
common_subject,
message_id,
session_id session_id,
reference,
send_time
FROM
t_mail t
WHERE
EXISTS (
SELECT
1
FROM
t_mail t2
WHERE
t. NAME = 'INBOX' -- 【INBOX】所有邮件
OR (
t.common_subject = t2.common_subject
and (
(
t.message_id = t2.reference -- 被INBOX引用的【发件箱】邮件
AND t2.`name` = 'INBOX'
)
OR
(
t.session_id = t2.session_id -- INBOX时间之后,【发件箱】能组队的
AND t2.`name` = 'INBOX'
AND t.send_time > t2.send_time
And t2.reference is null
)
or (
t.reference = t2.message_id
AND t2.`name` = 'INBOX'
)
OR (
t.reference = t2.reference -- INBOX时间之后,【发件箱】是引用的
AND t2.`name` = 'INBOX'
AND t.send_time > t2.send_time
)
)
)
)
ORDER BY
send_time DESC;
- 所有邮件按照发件顺序倒序排
数据聚合
遍历所有邮件
1. 有主题吗?没有主题自己单独一个会话。否则往下继续执行2
-
邮件引用,有没有引用其他邮件
- 有引用,看有没有以【Reference ID+会话主题】为key的空间,如果有在尾部加入,没有自己开辟
- 没有引用,继续3
-
邮件被引用
- 有没有【Message_ID + 会话主题】的空间,有直接加入
– 没有继续详细执行4
- 自定义引用
- 看有没有以【自定义引用+会话主题】为key的空间,如果有在尾部加入,没有自己开辟
foreach.java
list.forEach(mailEntity -> {
if (StringUtils.isBlank(mailEntity.getCommonSubject())) { //无主题独占会话
sessionMap.put(String.valueOf(IDUtils.genItemId()), Sets.newLinkedHashSet(mailEntity));
return;
}
String reference = mailEntity.getReference();
// 引用方
if (reference != null) {
Set<MailEntity> line = sessionMap.get(reference + ":" + mailEntity.getCommonSubject());
if (line == null) {
line = Sets.newLinkedHashSet();
sessionMap.put(reference + ":" + mailEntity.getCommonSubject(), line);
}
line.add(mailEntity);
return;
}
// 被引用方
String messageId = mailEntity.getMessageId();
Set<MailEntity> line = sessionMap.get(messageId + ":" + mailEntity.getCommonSubject());
if (line != null) {
line.add(mailEntity);
return;
}
// 自建引用
String sessionId = mailEntity.getSessionId();
line = sessionMap.get(sessionId + ":" + mailEntity.getCommonSubject());
if (line != null) {
line.add(mailEntity);
return;
} else {
line = Sets.newLinkedHashSet();
sessionMap.put(sessionId + ":" + mailEntity.getCommonSubject(), line);
line.add(mailEntity);
}
});