作者归档:Rick

阿里云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

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入门指南

通过使用【位置服务】测试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

几种情况的说明:

  1. 无主题的不聚合

  2. 转发的虽然有引用,但是不聚合,从会话中脱离出来。因为主题发生了变化。“回复:你好”的会话主题是“你好”,而“转发:你好”的会话主题是“转发:你好”

具体步骤:

数据准备

  1. 拉取所有的收件箱邮件

  2. 按条件拉取发件箱,找跟收件箱的关系

    前提,必须 收件箱有的【会话主题】

    • 收件箱邮件引用
    • 引用收件箱
    • 收件箱有相同的邮件引用 & 收件时间 < 发件时间
    • 收件箱有相同的自定义引用 & 邮件引用不存在 & 收件时间 < 发件时间

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. 所有邮件按照发件顺序倒序排

数据聚合

遍历所有邮件

1. 有主题吗?没有主题自己单独一个会话。否则往下继续执行2

  1. 邮件引用,有没有引用其他邮件

    • 有引用,看有没有以【Reference ID+会话主题】为key的空间,如果有在尾部加入,没有自己开辟
    • 没有引用,继续3
  2. 邮件被引用

    • 有没有【Message_ID + 会话主题】的空间,有直接加入

– 没有继续详细执行4

  1. 自定义引用
    • 看有没有以【自定义引用+会话主题】为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);
            }

        });

Docker下搭建FastDFS

1. 创建虚拟机,安装docker,docker-compose环境

2.创建目录和文件

$ mkdir -p /usr/local/docker/fastdfs
$ cd /usr/local/docker/fastdfs
$ vim docker-compose.yml
...

最终目录结构
image

2.1 docker-compose.yml

version: '3'

services:
 tracker:
  container_name: tracker
  image: "morunchang/fastdfs"
  network_mode: host
  entrypoint: sh tracker.sh
 storage0:
  container_name: storage0
  depends_on:
    - tracker
  build: ./storage
  network_mode: host
  environment:
   GROUP_NAME: storagegroup
   TRACKER_IP: 192.168.1.144:22122
  volumes:
    - /var/local/docker/fastdfs/storage0:/data/fast_data

2.2 storage/Dockerfile

Dockerfile

FROM morunchang/fastdfs
COPY nginx.conf /data/nginx/conf/nginx.conf
ENTRYPOINT sh storage.sh

2.2 storage/nginx.cnf

#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        location /storagegroup/M00 {
           proxy_next_upstream http_502 http_504 error timeout invalid_header;
             proxy_cache http-cache;
             proxy_cache_valid  200 304 12h;
             proxy_cache_key $uri$is_args$args;
             proxy_pass http://fdfs_group1;
             expires 30d;
         }

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }

}

启动

docker-compose up -d

客户端测试


参考网站
* https://blog.csdn.net/alinyua/article/details/82464496
* https://blog.csdn.net/lizhihaooo/article/details/79261388
* http://blog.csdn.net/ityouknow/article/details/79078175

注意如果拒绝访问,那么有可能是tracker的ip地址配置有误,或者防火墙需要关闭,关闭防火墙的参考https://www.cnblogs.com/moxiaoan/p/5683743.html

**外网部署,要开启23000端口

https://www.cnblogs.com/nbf-156cwl/articles/FastDFS.html

像outlook一样,做一个重复的周期性事件

项目有个需求,做个类似outlook的周期性会议的功能,如下图所示:

outlook会议周期性图片

  • 方案一:

    由于使用的是java平台,刚开始想通过成熟的调度框架quartz来实现,但是无法通过一个trigger实现一个复杂任务的调度。如:每2个月的第四周的周末,次行10次后结束

    quartz提供来四种计划:SimpleScheduleBuilder CronScheduleBuilder DailyTimeIntervalScheduleBuilder CalendarIntervalScheduleBuilder没种计划都有各自特定的场景。cronExpress也不是万能的,比如表示【每2个月的第四周的周末】是无法通过表达式:0 0 0 ?*/2 SAT#4,SUN#4,对于周来讲这样的写法会抛出异常,不运行逗号隔开多个

    Support for specifying multiple "nth" days is not implemented.
    
  • 方案二

    网上找了好久的方案,通过这篇文章日历设计之重复事件规则设计,知道了有个icalendar的协议RFC2445协议 (更加详细的中文解释在https://www.jianshu.com/p/8f8572292c58)。

    于是,我需要找基于java平台的支持RFC2445协议,找到了2个。

    1. lib-recur

    github地址:https://github.com/dmfs/lib-recur  \
    验证地址:http://recurrence-expansion-service.appspot.com/
    验证代码:

      RecurrenceRule rule = new RecurrenceRule("FREQ=MONTHLY;INTERVAL=1;BYDAY=2SA,2SU;COUNT=10");
    
        DateTime start = DateTime.parse("20180528T063302Z");//new DateTime(2018, Calendar.MAY /* 0-based month numbers! */,28, 14, 33, 02);
    
        RecurrenceRuleIterator it = rule.iterator(start);
    
        //        it.skip(0);
    
        int maxInstances = 20; // limit instances for rules that recur forever
    
        while (it.hasNext() && (!rule.isInfinite() || maxInstances-- > 0))
        {
            DateTime nextInstance = it.nextDateTime();
            System.out.println(nextInstance.toString());
            // do something with nextInstance
        }
    

    2. ical4j

    github地址:https://github.com/ical4j/ical4j

    代码演示:

     @Test
    public void testIcal4j() throws ParseException, IOException {
        DtStart dtstart = new DtStart("20180528T063302Z");
        DtStart endDate = new DtStart("20200925T063302Z");
    
        RRule rule = new RRule("FREQ=MONTHLY;INTERVAL=1;BYDAY=2SA,2SU;COUNT=10");
    
        VEvent sessionEvent = new VEvent(dtstart.getDate(), "hahahhehe");
    
        sessionEvent.getProperties().add(rule);
    //        sessionEvent.getProperties().add(dtstart);
        sessionEvent.getProperties().add(new Uid("2322332323323323"));
        sessionEvent.getProperties().add(new Location("南京堵路"));
    
        // 提醒,提前10分钟
        VAlarm valarm = new VAlarm(new Dur(0, 0, -10, 0));
        valarm.getProperties().add(new Summary("Event Alarm"));
        valarm.getProperties().add(Action.DISPLAY);
        valarm.getProperties().add(new Description("Progress Meeting at 9:30am"));
        sessionEvent.getAlarms().add(valarm);
    
    
        net.fortuna.ical4j.model.Calendar calendar = new net.fortuna.ical4j.model.Calendar();
        calendar.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN"));
        calendar.getProperties().add(Version.VERSION_2_0);
        calendar.getProperties().add(CalScale.GREGORIAN);
        calendar.getComponents().add(sessionEvent);
        calendar.validate();
    
    
    //        FileOutputStream fout = new FileOutputStream("/Users/rick/jkxyx205/log/2.ics");
    //        CalendarOutputter outputter = new CalendarOutputter();
    //        outputter.output(calendar, fout);
    
    //        PeriodList periodList = sessionEvent.getConsumedTime(dtstart.getDate(), endDate.getDate());
    //
    //        for(Period period : periodList) {
    //            System.out.println(period.get);
    //        }
    
    
        DateList list = rule.getRecur().getDates(dtstart.getDate(), dtstart.getDate(), endDate.getDate(), Value.DATE_TIME, 10);
    
        for(Date date : list) {
            System.out.println(date.toLocaleString());
        }
    }
    
    

    可以通过该库输出标准格式文件.ics,可参考文章ical4j 实现ICS文件的生成和解析

    为了提高性能,需要在客户端处理相关协议,基于javascript平台的支持RFC2445协议前端框架rrule

    github地址:https://github.com/jakubroztocil/rrule \
    验证地址:http://jakubroztocil.github.io/rrule/

更完整的rrule验证:https://www.textmagic.com/free-tools/rrule-generator

技术难点

    1. 用户在打开应用时, 当页面定位在某个月(周, 日)视图上时, 怎么样从这个用户中所有的周期事件中快速计算出所有的当前视图事件? 不可能每次请求一次, 全部事件计算一次.
    1. 日历的主要功能是提醒. 怎么样准时, 无误的把每分钟要提醒的短信, 邮件发出去, 这又是个量变到质变的难点, 如果当前数据库有几千万的周期事件?