天天看點

企業微信機器人webhook消息通知

作者:Clover幸運之星

一,機器人消息通知概述

在一些服務掉線以及錯誤日志通知的時候,我們可能會使用短信,群,電話等通知,為了配合項目可以穩定的運作,需要将一些錯誤等資訊可以及時的做出通知,這個時候就需要使用到群機器人進行通知。

二,企業微信群機器人思路實作

1,在企業微信中建立一個群

2,在設定裡面添加機器人

企業微信機器人webhook消息通知

3,拿到webhook位址

三,機器人配置說明

  • 在終端某個群組添加機器人之後,建立者可以在機器人詳情頁看的該機器人特有的webhookurl。開發者可以按以下說明a向這個位址發起HTTP POST 請求,即可實作給該群組發送消息。下面舉個簡單的例子.

    假設webhook是:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa

特别特别要注意:一定要保護好機器人的webhook位址,避免洩漏!不要分享到github、部落格等可被公開查閱的地方,否則壞人就可以用你的機器人來發垃圾消息了。

以下是用curl工具往群組推送文本消息的示例(注意要将url替換成你的機器人webhook位址,content必須是utf8編碼):

curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa' \
   -H 'Content-Type: application/json' \
   -d '
   {
        "msgtype": "text",
        "text": {
            "content": "hello world"
        }
   }'           
  • 目前自定義機器人支援文本(text)、markdown(markdown)、圖檔(image)、圖文(news)四種消息類型。
  • 機器人的text/markdown類型消息支援在content中使用<@userid>擴充文法來@群成員

3.1 消息類型及資料格式

3.1.1 文本類型

{
    "msgtype": "text",
    "text": {
        "content": "廣州今日天氣:29度,大部分多雲,降雨機率:60%",
        "mentioned_list":["wangqing","@all"],
        "mentioned_mobile_list":["13800001111","@all"]
    }
}           
參數 是否必填 說明
msgtype 消息類型,此時固定為text
content 文本内容,最長不超過2048個位元組,必須是utf8編碼
mentioned_list userid的清單,提醒群中的指定成員(@某個成員),@all表示提醒所有人,如果開發者擷取不到userid,可以使用mentioned_mobile_list
mentioned_mobile_list 手機号清單,提醒手機号對應的群成員(@某個成員),@all表示提醒所有人
企業微信機器人webhook消息通知

3.1.2 markdown類型

{
    "msgtype": "markdown",
    "markdown": {
        "content": "實時新增使用者回報<font color=\"warning\">132例</font>,請相關同僚注意。\n
         >類型:<font color=\"comment\">使用者回報</font>
         >普通使用者回報:<font color=\"comment\">117例</font>
         >VIP使用者回報:<font color=\"comment\">15例</font>"
    }
}           
參數 是否必填 說明
msgtype 消息類型,此時固定為markdown
content markdown内容,最長不超過4096個位元組,必須是utf8編碼
企業微信機器人webhook消息通知

目前支援的markdown文法是如下的子集:

  1. 标題 (支援1至6級标題,注意#與文字中間要有空格)
# 标題一
## 标題二
### 标題三
#### 标題四
##### 标題五
###### 标題六           
  1. 加粗
**bold**           
  1. 連結
[這是一個連結](http://work.weixin.qq.com/api/doc)           
  1. 行内代碼段(暫不支援跨行)
`code`           
  1. 引用
> 引用文字           
  1. 字型顔色(隻支援3種内置顔色)
<font color="info">綠色</font>
<font color="comment">灰色</font>
<font color="warning">橙紅色</font>           

圖檔類型

{
    "msgtype": "image",
    "image": {
        "base64": "DATA",
        "md5": "MD5"
    }
}           
參數 是否必填 說明
msgtype 消息類型,此時固定為image
base64 圖檔内容的base64編碼
md5 圖檔内容(base64編碼前)的md5值

注:圖檔(base64編碼前)最大不能超過2M,支援JPG,PNG格式

企業微信機器人webhook消息通知

3.1.3圖文類型

{
    "msgtype": "news",
    "news": {
       "articles" : [
           {
               "title" : "中秋節禮品領取",
               "description" : "今年中秋節公司有豪禮相送",
               "url" : "www.qq.com",
               "picurl" : "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png"
           }
        ]
    }
}           
參數 是否必填 說明
msgtype 消息類型,此時固定為news
articles 圖文消息,一個圖文消息支援1到8條圖文
title 标題,不超過128個位元組,超過會自動截斷
description 描述,不超過512個位元組,超過會自動截斷
url 點選後跳轉的連結。
picurl 圖文消息的圖檔連結,支援JPG、PNG格式,較好的效果為大圖 1068*455,小圖150*150。
企業微信機器人webhook消息通知

3.1.4檔案類型

{
    "msgtype": "file",
    "file": {
         "media_id": "3a8asd892asd8asd"
    }
}           
參數 是否必填 說明
msgtype 消息類型,此時固定為file
media_id 檔案id,通過下文的檔案上傳接口擷取
企業微信機器人webhook消息通知

3.1.5 模版卡片類型

文本通知模版卡片

企業微信機器人webhook消息通知
{
    "msgtype":"template_card",
    "template_card":{
        "card_type":"text_notice",
        "source":{
            "icon_url":"https://wework.qpic.cn/wwpic/252813_jOfDHtcISzuodLa_1629280209/0",
            "desc":"企業微信",
            "desc_color":0
        },
        "main_title":{
            "title":"歡迎使用企業微信",
            "desc":"您的好友正在邀請您加入企業微信"
        },
        "emphasis_content":{
            "title":"100",
            "desc":"資料含義"
        },
        "quote_area":{
            "type":1,
            "url":"https://work.weixin.qq.com/?from=openApi",
            "appid":"APPID",
            "pagepath":"PAGEPATH",
            "title":"引用文本标題",
            "quote_text":"Jack:企業微信真的很好用~\nBalian:超級好的一款軟體!"
        },
        "sub_title_text":"下載下傳企業微信還能搶紅包!",
        "horizontal_content_list":[
            {
                "keyname":"邀請人",
                "value":"張三"
            },
            {
                "keyname":"企微官網",
                "value":"點選通路",
                "type":1,
                "url":"https://work.weixin.qq.com/?from=openApi"
            },
            {
                "keyname":"企微下載下傳",
                "value":"企業微信.apk",
                "type":2,
                "media_id":"MEDIAID"
            }
        ],
        "jump_list":[
            {
                "type":1,
                "url":"https://work.weixin.qq.com/?from=openApi",
                "title":"企業微信官網"
            },
            {
                "type":2,
                "appid":"APPID",
                "pagepath":"PAGEPATH",
                "title":"跳轉小程式"
            }
        ],
        "card_action":{
            "type":1,
            "url":"https://work.weixin.qq.com/?from=openApi",
            "appid":"APPID",
            "pagepath":"PAGEPATH"
        }
    }
}           

請求參數

參數 類型 必須 說明
msgtype String 消息類型,此時的消息類型固定為template_card
template_card Object 具體的模版卡片參數

template_card的參數說明

參數 類型 必須 說明
card_type String 模版卡片的模版類型,文本通知模版卡片的類型為text_notice
source Object 卡片來源樣式資訊,不需要來源樣式可不填寫
source.icon_url String 來源圖檔的url
source.desc String 來源圖檔的描述,建議不超過13個字
source.desc_color Int 來源文字的顔色,目前支援:0(預設) 灰色,1 黑色,2 紅色,3 綠色
main_title Object 模版卡片的主要内容,包括一級标題和标題輔助資訊
main_title.title String 一級标題,建議不超過26個字。模版卡片主要内容的一級标題main_title.title和二級普通文本sub_title_text必須有一項填寫
main_title.desc String 标題輔助資訊,建議不超過30個字
emphasis_content Object 關鍵資料樣式
emphasis_content.title String 關鍵資料樣式的資料内容,建議不超過10個字
emphasis_content.desc String 關鍵資料樣式的資料描述内容,建議不超過15個字
quote_area Object 引用文獻樣式,建議不與關鍵資料共用
quote_area.type Int 引用文獻樣式區域點選事件,0或不填代表沒有點選事件,1 代表跳轉url,2 代表跳轉小程式
quote_area.url String 點選跳轉的url,quote_area.type是1時必填
quote_area.appid String 點選跳轉的小程式的appid,quote_area.type是2時必填
quote_area.pagepath String 點選跳轉的小程式的pagepath,quote_area.type是2時選填
quote_area.title String 引用文獻樣式的标題
quote_area.quote_text String 引用文獻樣式的引用文案
sub_title_text String 二級普通文本,建議不超過112個字。模版卡片主要内容的一級标題main_title.title和二級普通文本sub_title_text必須有一項填寫
horizontal_content_list Object[] 二級标題+文本清單,該字段可為空數組,但有資料的話需确認對應字段是否必填,清單長度不超過6
horizontal_content_list.type Int 連結類型,0或不填代表是普通文本,1 代表跳轉url,2 代表下載下傳附件,3 代表@員工
horizontal_content_list.keyname String 二級标題,建議不超過5個字
horizontal_content_list.value String 二級文本,如果horizontal_content_list.type是2,該字段代表檔案名稱(要包含檔案類型),建議不超過26個字
horizontal_content_list.url String 連結跳轉的url,horizontal_content_list.type是1時必填
horizontal_content_list.media_id String

附件的media_id

,horizontal_content_list.type是2時必填

horizontal_content_list.userid String 被@的成員的userid,horizontal_content_list.type是3時必填
jump_list Object[] 跳轉指引樣式的清單,該字段可為空數組,但有資料的話需确認對應字段是否必填,清單長度不超過3
jump_list.type Int 跳轉連結類型,0或不填代表不是連結,1 代表跳轉url,2 代表跳轉小程式
jump_list.title String 跳轉連結樣式的文案内容,建議不超過13個字
jump_list.url String 跳轉連結的url,jump_list.type是1時必填
jump_list.appid String 跳轉連結的小程式的appid,jump_list.type是2時必填
jump_list.pagepath String 跳轉連結的小程式的pagepath,jump_list.type是2時選填
card_action Object 整體卡片的點選跳轉事件,text_notice模版卡片中該字段為必填項
card_action.type Int 卡片跳轉類型,1 代表跳轉url,2 代表打開小程式。text_notice模版卡片中該字段取值範圍為[1,2]
card_action.url String 跳轉事件的url,card_action.type是1時必填
card_action.appid String 跳轉事件的小程式的appid,card_action.type是2時必填
card_action.pagepath String 跳轉事件的小程式的pagepath,card_action.type是2時選填

圖文展示模版卡片

企業微信機器人webhook消息通知
{
    "msgtype":"template_card",
    "template_card":{
        "card_type":"news_notice",
        "source":{
            "icon_url":"https://wework.qpic.cn/wwpic/252813_jOfDHtcISzuodLa_1629280209/0",
            "desc":"企業微信",
            "desc_color":0
        },
        "main_title":{
            "title":"歡迎使用企業微信",
            "desc":"您的好友正在邀請您加入企業微信"
        },
        "card_image":{
            "url":"https://wework.qpic.cn/wwpic/354393_4zpkKXd7SrGMvfg_1629280616/0",
            "aspect_ratio":2.25
        },
        "image_text_area":{
            "type":1,
            "url":"https://work.weixin.qq.com",
            "title":"歡迎使用企業微信",
            "desc":"您的好友正在邀請您加入企業微信",
            "image_url":"https://wework.qpic.cn/wwpic/354393_4zpkKXd7SrGMvfg_1629280616/0"
        },
        "quote_area":{
            "type":1,
            "url":"https://work.weixin.qq.com/?from=openApi",
            "appid":"APPID",
            "pagepath":"PAGEPATH",
            "title":"引用文本标題",
            "quote_text":"Jack:企業微信真的很好用~\nBalian:超級好的一款軟體!"
        },
        "vertical_content_list":[
            {
                "title":"驚喜紅包等你來拿",
                "desc":"下載下傳企業微信還能搶紅包!"
            }
        ],
        "horizontal_content_list":[
            {
                "keyname":"邀請人",
                "value":"張三"
            },
            {
                "keyname":"企微官網",
                "value":"點選通路",
                "type":1,
                "url":"https://work.weixin.qq.com/?from=openApi"
            },
            {
                "keyname":"企微下載下傳",
                "value":"企業微信.apk",
                "type":2,
                "media_id":"MEDIAID"
            }
        ],
        "jump_list":[
            {
                "type":1,
                "url":"https://work.weixin.qq.com/?from=openApi",
                "title":"企業微信官網"
            },
            {
                "type":2,
                "appid":"APPID",
                "pagepath":"PAGEPATH",
                "title":"跳轉小程式"
            }
        ],
        "card_action":{
            "type":1,
            "url":"https://work.weixin.qq.com/?from=openApi",
            "appid":"APPID",
            "pagepath":"PAGEPATH"
        }
    }
}           

請求參數

參數 類型 必須 說明
msgtype String 模版卡片的消息類型為template_card
template_card Object 具體的模版卡片參數

template_card的參數說明

參數 類型 必須 說明
card_type String 模版卡片的模版類型,圖文展示模版卡片的類型為news_notice
source Object 卡片來源樣式資訊,不需要來源樣式可不填寫
source.icon_url String 來源圖檔的url
source.desc String 來源圖檔的描述,建議不超過13個字
source.desc_color Int 來源文字的顔色,目前支援:0(預設) 灰色,1 黑色,2 紅色,3 綠色
main_title Object 模版卡片的主要内容,包括一級标題和标題輔助資訊
main_title.title String 一級标題,建議不超過26個字
main_title.desc String 标題輔助資訊,建議不超過30個字
card_image Object 圖檔樣式
card_image.url String 圖檔的url
card_image.aspect_ratio Float 圖檔的寬高比,寬高比要小于2.25,大于1.3,不填該參數預設1.3
image_text_area Object 左圖右文樣式
image_text_area.type Int 左圖右文樣式區域點選事件,0或不填代表沒有點選事件,1 代表跳轉url,2 代表跳轉小程式
image_text_area.url String 點選跳轉的url,image_text_area.type是1時必填
image_text_area.appid String 點選跳轉的小程式的appid,必須是與目前應用關聯的小程式,image_text_area.type是2時必填
image_text_area.pagepath String 點選跳轉的小程式的pagepath,image_text_area.type是2時選填
image_text_area.title String 左圖右文樣式的标題
image_text_area.desc String 左圖右文樣式的描述
image_text_area.image_url String 左圖右文樣式的圖檔url
quote_area Object 引用文獻樣式,建議不與關鍵資料共用
quote_area.type Int 引用文獻樣式區域點選事件,0或不填代表沒有點選事件,1 代表跳轉url,2 代表跳轉小程式
quote_area.url String 點選跳轉的url,quote_area.type是1時必填
quote_area.appid String 點選跳轉的小程式的appid,quote_area.type是2時必填
quote_area.pagepath String 點選跳轉的小程式的pagepath,quote_area.type是2時選填
quote_area.title String 引用文獻樣式的标題
quote_area.quote_text String 引用文獻樣式的引用文案
vertical_content_list Object[] 卡片二級垂直内容,該字段可為空數組,但有資料的話需确認對應字段是否必填,清單長度不超過4
vertical_content_list.title String 卡片二級标題,建議不超過26個字
vertical_content_list.desc String 二級普通文本,建議不超過112個字
horizontal_content_list Object[] 二級标題+文本清單,該字段可為空數組,但有資料的話需确認對應字段是否必填,清單長度不超過6
horizontal_content_list.type Int 模版卡片的二級标題資訊内容支援的類型,1是url,2是檔案附件
horizontal_content_list.keyname String 二級标題,建議不超過5個字
horizontal_content_list.value String 二級文本,如果horizontal_content_list.type是2,該字段代表檔案名稱(要包含檔案類型),建議不超過26個字
horizontal_content_list.url String 連結跳轉的url,horizontal_content_list.type是1時必填
horizontal_content_list.media_id String

附件的media_id

,horizontal_content_list.type是2時必填

jump_list Object[] 跳轉指引樣式的清單,該字段可為空數組,但有資料的話需确認對應字段是否必填,清單長度不超過3
jump_list.type Int 跳轉連結類型,0或不填代表不是連結,1 代表跳轉url,2 代表跳轉小程式
jump_list.title String 跳轉連結樣式的文案内容,建議不超過13個字
jump_list.url String 跳轉連結的url,jump_list.type是1時必填
jump_list.appid String 跳轉連結的小程式的appid,jump_list.type是2時必填
jump_list.pagepath String 跳轉連結的小程式的pagepath,jump_list.type是2時選填
card_action Object 整體卡片的點選跳轉事件,news_notice模版卡片中該字段為必填項
card_action.type Int 卡片跳轉類型,1 代表跳轉url,2 代表打開小程式。news_notice模版卡片中該字段取值範圍為[1,2]
card_action.url String 跳轉事件的url,card_action.type是1時必填
card_action.appid String 跳轉事件的小程式的appid,card_action.type是2時必填
card_action.pagepath String 跳轉事件的小程式的pagepath,card_action.type是2時選填

3.2 消息發送頻率限制

每個機器人發送的消息不能超過20條/分鐘。

3.3 檔案上傳接口

素材上傳得到media_id,該media_id僅三天内有效

media_id隻能是對應上傳檔案的機器人可以使用

請求方式:POST(HTTPS)

請求位址:https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=KEY&type=TYPE

使用multipart/form-data POST上傳檔案, 檔案辨別名為”media”

參數說明:

參數 必須 說明
key 調用接口憑證, 機器人webhookurl中的key參數
type 固定傳file

POST的請求包中,form-data中媒體檔案辨別,應包含有 filename、filelength、content-type等資訊

filename辨別檔案展示的名稱。比如,使用該media_id發消息時,展示的檔案名由該字段控制

請求示例:

POST https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa&type=file HTTP/1.1
Content-Type: multipart/form-data; boundary=-------------------------acebdf13572468
Content-Length: 220

---------------------------acebdf13572468
Content-Disposition: form-data; name="media";filename="wework.txt"; filelength=6
Content-Type: application/octet-stream

mytext
---------------------------acebdf13572468--           

傳回資料:

{
   "errcode": 0,
   "errmsg": "ok",
   "type": "file",
   "media_id": "1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0",
   "created_at": "1380000000"
}           

參數說明:

參數 說明
type 媒體檔案類型,分别有圖檔(image)、語音(voice)、視訊(video),普通檔案(file)
media_id 媒體檔案上傳後擷取的唯一辨別,3天内有效
created_at 媒體檔案上傳時間戳

上傳的檔案限制:

  • 要求檔案大小在5B~20M之間

四,JAVA代碼實作

MessageSenderAutoConfiguration

@Slf4j
@Configuration
@AutoConfigureOrder(value = Ordered.HIGHEST_PRECEDENCE)
public class MessageSenderAutoConfiguration {


    @Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public MessageSenderProperties getProperties() {
        return new MessageSenderProperties();
    }


    @Bean
    public WebhookMessageService getMessageSenderService() {
        MessageSenderProperties properties = getProperties();
        if (ObjectUtils.isEmpty(properties.getWechat_webhooks())) {
            log.error("加載webhook—api預設配置失敗");
        }
        return new WebhookMessageServiceImpl(properties);
    }
}
           

MessageSenderProperties

@Data
@ConfigurationProperties(prefix = "spring.message")
public class MessageSenderProperties {

    /**
     * message開關
     */
    private boolean enable = true;

    /**
     * wechat-webhookList
     */
    private List<String> wechat_webhooks;


}           

Article

@Data
@Accessors(chain = true)
public class Article {

    private String title;

    private String description;

    private String url;

    private String picUrl;

}           

WorkWebhookMessage

@Data
@Accessors(chain = true)
public class WorkWebhookMessage {

    private String webhook;

    private String msgtype;

    private Text text;

    private Markdown markdown;

    private Image image;

    private News news;


    @Data
    @Accessors(chain = true)
    public static class Text {
        private String content;
        private List<String> mentioned_list;
    }

    @Data
    @Accessors(chain = true)
    public static class Markdown {
        private String content;
    }

    @Data
    @Accessors(chain = true)
    public static class Image {
        private String base64;
        private String md5;
    }

    @Data
    @Accessors(chain = true)
    public static class News {
        private List<Article> articles;
    }


    /**
     * @type 發送網絡圖檔或者本地圖檔都可以
     * @desc
     */
    public static WorkWebhookMessage buildImageMessage(String imagePath) {
        File file;
        try {
            WorkWebhookMessage message = new WorkWebhookMessage();
            message.setMsgtype("image");
            WorkWebhookMessage.Image image = new WorkWebhookMessage.Image();
            if (imagePath.startsWith("http")) {
                file = Fileutils.downloadFile(imagePath, "image", IDUtils.genRandom("image-", 15));
                image.setBase64(ImageToBase64.ImageToBase64(file));
                image.setMd5(MD5Utils.getFileMD5String(file));
                message.setImage(image);
                Fileutils.keepTop(file, 1);
            } else {
                file = new File(imagePath);
                image.setBase64(ImageToBase64.ImageToBase64(file));
                image.setMd5(MD5Utils.getFileMD5String(file));
                message.setImage(image);
            }
            return message;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * @type 建構markdown消息
     * @desc
     */
    public static WorkWebhookMessage buildMarkDownMessage(MarkdownBuffer content) {
        WorkWebhookMessage message = new WorkWebhookMessage();
        message.setMsgtype("markdown");
        WorkWebhookMessage.Markdown markdown = new WorkWebhookMessage.Markdown();
        markdown.setContent(content.toString());
        message.setMarkdown(markdown);
        return message;
    }


    /**
     * @type 批量建構圖文卡片連結消息
     * @desc
     */
    public static WorkWebhookMessage buildNewsMessage(List<Article> articles) {
        WorkWebhookMessage message = new WorkWebhookMessage();
        message.setMsgtype("news");
        WorkWebhookMessage.News news = new WorkWebhookMessage.News();
        news.setArticles(articles);
        message.setNews(news);
        return message;
    }

    /**
     * @type 建構圖文卡片連結消息
     * @desc
     */
    public static WorkWebhookMessage buildNewsMessage(Article article) {
        WorkWebhookMessage message = new WorkWebhookMessage();
        message.setMsgtype("news");
        WorkWebhookMessage.News news = new WorkWebhookMessage.News();
        List<Article> list = new ArrayList();
        list.add(article);
        news.setArticles(list);
        message.setNews(news);
        return message;
    }


    /**
     * @type 建構普通文本消息
     * @desc
     */
    public static WorkWebhookMessage buildText(String content) {
        return buildText(content, false);
    }

    /**
     * @type 建構普通文本消息(@ALL 指定webhookapi)
     * @desc
     */
    public static WorkWebhookMessage buildText(String content, boolean atAll) {
        WorkWebhookMessage message = new WorkWebhookMessage();
        message.setMsgtype("text");
        WorkWebhookMessage.Text text = new WorkWebhookMessage.Text();
        text.setContent(content);
        List<String> mentioned_list = text.getMentioned_list();
        if (atAll) {
            if (mentioned_list == null) {
                mentioned_list = new ArrayList<>();
            }
            mentioned_list.add("@all");
            text.setMentioned_list(mentioned_list);
        }
        message.setText(text);
        return message;
    }

}
           

WebhookMessageService

public interface WebhookMessageService {
    /**
     * @type 發送企業微信消息--系統配置發送者
     * @desc
     */
    boolean send(WorkWebhookMessage workWebhookMessage);

    /**
     * @type 發送企業微信消息--自定義發送者
     * @desc
     */
    boolean send(WorkWebhookMessage workWebhookMessage, String webhook);
}           

WebhookMessageServiceImpl

@Slf4j
public class WebhookMessageServiceImpl implements WebhookMessageService {
    private List<String> wechat_webhooks;


    public WebhookMessageServiceImpl(MessageSenderProperties messagesenderProperties) {
        this.wechat_webhooks = messagesenderProperties.getWechat_webhooks();
        if (wechat_webhooks == null || wechat_webhooks.size() == 0) {
            log.error("沒有配置webhook");
        }
    }

    @Override
    public boolean send(WorkWebhookMessage weWorkWebhookMessage) {
        return send(weWorkWebhookMessage, wechat_webhooks.get(0));
    }

    @Override
    public boolean send(WorkWebhookMessage weWorkWebhookMessage, String webhook) {
        if (StringUtils.isEmpty(webhook)) {
            return true;
        }
        Map<String, String> headerParams = new HashMap<>();
        headerParams.put("Content-Type", "application/json");
        headerParams.put("charset", "utf-8");
        String responseStr = HttpClientUtil.doPostJson(webhook, headerParams, JSON.toJSONString(weWorkWebhookMessage));
        if (JSON.parseObject(responseStr).getString("errmsg").equals("ok")) {
            log.warn("webHookSendMessage failed:{}",responseStr);
            return true;
        } else {
            return false;
        }
    }
}
           

Base64Utils

public class Base64Utils {

    public static Key DEFAULT_KEY = null;

    public static final String DEFAULT_SECRET_KEY1 = "?:P)(OL><KI*&UJMNHY^%TGBVFR$#EDCXSW@!QAZ";
    public static final String DEFAULT_SECRET_KEY2 = "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/";
    public static final String DEFAULT_SECRET_KEY3 = "!QAZ@WSX#EDC$RFV%TGB^YHN&UJM*IK<(OL>)P:?";
    public static final String DEFAULT_SECRET_KEY4 = "1qaz@WSX3edc$RFV5tgb^YHN7ujm*IK<9ol.)P:?";
    public static final String DEFAULT_SECRET_KEY5 = "!QAZ2wsx#EDC4rfv%TGB6yhn&UJM8ik,(OL>0p;/";
    public static final String DEFAULT_SECRET_KEY6 = "1qaz2wsx3edc4rfv5tgb^YHN&UJM*IK<(OL>)P:?";
    public static final String DEFAULT_SECRET_KEY = DEFAULT_SECRET_KEY1;

    public static final String DES = "DES";

    public static final Base32 base32 = new Base32();

    static {
        DEFAULT_KEY = obtainKey(DEFAULT_SECRET_KEY);
    }

    /**
     * 獲得key
     **/
    public static Key obtainKey(String key) {
        if (key == null) {
            return DEFAULT_KEY;
        }
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(DES);
            //替換開始
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(key.getBytes());
            keyGenerator.init(secureRandom);
            return keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 加密<br>
     * String明文輸入,String密文輸出
     */
    public static String encode(String str) {
        return encode64(null, str);
    }

    /**
     * 加密<br>
     * String明文輸入,String密文輸出
     */
    public static String encode64(String key, String str) {
        return Base64.encodeBase64URLSafeString(obtainEncode(key, str.getBytes()));
    }

    /**
     * 加密<br>
     * String明文輸入,String密文輸出
     */
    public static String encode32(String key, String str) {
        return base32.encodeAsString(obtainEncode(key, str.getBytes())).replaceAll("=", "");
    }

    /**
     * 加密<br>
     * String明文輸入,String密文輸出
     */
    public static String encode16(String key, String str) {
        return Hex.encodeHexString(obtainEncode(key, str.getBytes()));
    }

    /**
     * 解密<br>
     * 以String密文輸入,String明文輸出
     */
    public static String decode(String str) {
        return decode64(null, str);
    }

    /**
     * 解密<br>
     * 以String密文輸入,String明文輸出
     */
    public static String decode64(String key, String str) {
        return new String(obtainDecode(key, Base64.decodeBase64(str)));
    }

    /**
     * 解密<br>
     * 以String密文輸入,String明文輸出
     */
    public static String decode32(String key, String str) {
        return new String(obtainDecode(key, base32.decode(str)));
    }

    /**
     * 解密<br>
     * 以String密文輸入,String明文輸出
     */
    public static String decode16(String key, String str) {
        try {
            return new String(obtainDecode(key, Hex.decodeHex(str.toCharArray())));
        } catch (DecoderException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加密<br>
     * 以byte[]明文輸入,byte[]密文輸出
     */
    private static byte[] obtainEncode(String key, byte[] str) {
        byte[] byteFina = null;
        Cipher cipher;
        try {
            Key key1 = obtainKey(key);
            cipher = Cipher.getInstance(DES);
            cipher.init(Cipher.ENCRYPT_MODE, key1);
            byteFina = cipher.doFinal(str);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            cipher = null;
        }
        return byteFina;
    }

    /**
     * 解密<br>
     * 以byte[]密文輸入,以byte[]明文輸出
     */
    private static byte[] obtainDecode(String key, byte[] str) {
        Cipher cipher;
        byte[] byteFina = null;
        try {
            Key key1 = obtainKey(key);
            cipher = Cipher.getInstance(DES);
            cipher.init(Cipher.DECRYPT_MODE, key1);
            byteFina = cipher.doFinal(str);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            cipher = null;
        }
        return byteFina;
    }
}
           

Fileutils

/**
 * Java原生的API可用于發送HTTP請求,即java.net.URL、java.net.URLConnection,這些API很好用、很常用,
 * 但不夠簡便;
 * <p>
 * 1.通過統一資源定位器(java.net.URL)擷取連接配接器(java.net.URLConnection) 2.設定請求的參數 3.發送請求
 * 4.以輸入流的形式擷取傳回内容 5.關閉輸入流
 */
public class Fileutils {


    private static Logger logger = Logger.getLogger(Fileutils.class.getName());

    /**
     * 下載下傳網絡檔案
     */
    public static File downloadFile(String urlPath, String folderName, String fileName) throws Exception {

        File folder = new File(folderName);
        if (!folder.exists()) {
            folder.mkdir();
        }
        File file = new File(folder + File.separator + fileName);
        BufferedInputStream bin = null;
        OutputStream out = null;
        try {
            // 統一資源
            URL url = new URL(urlPath);
            // 連接配接類的父類,抽象類
            URLConnection urlConnection = url.openConnection();
            // http的連接配接類
            HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
            // 設定請求的方法,預設是GET
            httpURLConnection.setRequestMethod("GET");
            // 設定字元編碼
            httpURLConnection.setRequestProperty("Charset", "UTF-8");
            // 打開到此 URL 引用的資源的通信連結(如果尚未建立這樣的連接配接)。
            httpURLConnection.connect();
            // 檔案大小
            int fileLength = httpURLConnection.getContentLength();
            // 檔案名
            String filePathUrl = httpURLConnection.getURL().getFile();
            String fileFullName = filePathUrl.substring(filePathUrl.lastIndexOf(File.separatorChar) + 1);
            fileFullName = fileFullName.substring(fileFullName.lastIndexOf("/") + 1);
            url.openConnection();
            bin = new BufferedInputStream(httpURLConnection.getInputStream());
            out = new FileOutputStream(file);
            int size = 0;
            int len = 0;
            byte[] buf = new byte[1024];
            while ((size = bin.read(buf)) != -1) {
                len += size;
                out.write(buf, 0, size);
            }
            return file;
        } catch (Exception e) {
            throw new RuntimeException(e.toString() + "檔案下載下傳異常,請檢查下載下傳連結$" + urlPath);
        } finally {
            if (bin != null) {
                bin.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }

    /**
     * 方法描述:保持最大的檔案數量
     * 傳回類型:
     * 修改内容:(若修改了請注明修改人,修改時間,修改内容)
     */
    public static void keepTop(File file, int max) {
        File[] files = file.getParentFile().listFiles();
        if (files.length > max) {
            List<File> list = new ArrayList<>();
            for (int i = 0; i < files.length; i++) {
                list.add(files[i]);
            }
            AtomicInteger removeCounter = new AtomicInteger(files.length - max);
            list.stream().sorted(Comparator.comparingLong(File::lastModified)).forEach(ele -> {
                if (removeCounter.get() > 0) {
                    ele.delete();
                }
                removeCounter.getAndDecrement();
            });
        }
    }
}
           

HttpClientUtil

@SuppressWarnings("all")
public class HttpClientUtil {

    public static String doGet(String url, Map<String, String> param, String username, String password) {

        // 建立Httpclient對象
        CloseableHttpClient httpclient = HttpClients.createDefault();

        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            // 建立uri
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();

            // 建立http GET請求
            HttpGet httpGet = new HttpGet(uri);
            httpGet.addHeader("Authorization", "Basic " + Base64.getUrlEncoder().encodeToString((username + ":" + password).getBytes()));

            // 執行請求
            response = httpclient.execute(httpGet);
            // 判斷傳回狀态是否為200
            if (response.getStatusLine().getStatusCode() == 200) {
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8"); // UTF-8
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultString;
    }

    public static String doGet(String url, String username, String password) {
        return doGet(url, null, username, password);
    }

    public static String doPost(String url, Map<String, String> param) {
        return doPost(url, param, null);
    }

    private final static CloseableHttpClient getBasicHttpClient(String username, String password) {
        // 建立HttpClientBuilder
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // 設定BasicAuth
        CredentialsProvider provider = new BasicCredentialsProvider();
        // Create the authentication scope
        AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM);
        // Create credential pair,在此處填寫使用者名和密碼
        UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
        // Inject the credentials
        provider.setCredentials(scope, credentials);
        // Set the default credentials provider
        httpClientBuilder.setDefaultCredentialsProvider(provider);
        // HttpClient
        CloseableHttpClient closeableHttpClient = httpClientBuilder.build();
        return closeableHttpClient;
    }

    public static String doPost(String url, Map<String, String> param, Map<String, String> header) {
        // 建立Httpclient對象
        CloseableHttpClient httpClient = getBasicHttpClient("huanglei", "111111");
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 建立Http Post請求
            HttpPost httpPost = new HttpPost(url);
            // 建立header頭資訊
            if (header != null) {
                for (Map.Entry<String, String> entry : header.entrySet()) {
                    httpPost.addHeader(entry.getKey(), entry.getValue());
                }
            }
            // 建立參數清單
            if (param != null) {
                List<NameValuePair> paramList = new ArrayList<>();
                for (String key : param.keySet()) {
                    paramList.add(new BasicNameValuePair(key, param.get(key)));
                }
                // 模拟表單
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, "utf-8");
                httpPost.setEntity(entity);
            }
            // 執行http請求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        return resultString;
    }

    public static String doPost(String url) {
        return doPost(url, null);
    }

    public static String doPostJson(String url, String json) {
        // 建立Httpclient對象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 建立Http Post請求
            HttpPost httpPost = new HttpPost(url);
            // 添加header
            httpPost = (HttpPost) addHeaders(httpPost, null);
            // 建立請求内容
            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            // 執行http請求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        return resultString;
    }

    public static String doPostJson(String url, Map<String, String> headers, String json) {
        // 建立Httpclient對象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 建立Http Post請求
            HttpPost httpPost = new HttpPost(url);
            // 添加header

            httpPost = (HttpPost) addHeaders(httpPost, headers);
            // 建立請求内容
            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            // 執行http請求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        return resultString;
    }

    private static HttpRequestBase addHeaders(HttpRequestBase httpRequest, Map<String, String> headers) {
        // 設定請求頭資訊,鑒權
//        httpRequest.setHeader("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0");
        httpRequest.addHeader("Authorization", "1231321321313132132");
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            httpRequest.addHeader(entry.getKey(), entry.getValue());
        }
        // 設定配置請求參數
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000)// 連接配接主機服務逾時時間
                .setConnectionRequestTimeout(35000)// 請求逾時時間
                .setSocketTimeout(60000)// 資料讀取逾時時間
                .build();
        // 為httpGet執行個體設定配置
        httpRequest.setConfig(requestConfig);
        return httpRequest;
    }


    public static void main(String[] args) {


    }

}           

IDUtils

public class IDUtils {

    /**
     * 圖檔名生成
     */
    public static String genImageName() {
        //取目前時間的長整形值包含毫秒
        long millis = System.currentTimeMillis();
        //long millis = System.nanoTime();
        //加上三位随機數
        Random random = new Random();
        int end3 = random.nextInt(999);
        //如果不足三位前面補0
        String str = millis + String.format("%03d", end3);

        return str;
    }

    /**
     * 商品id生成
     */
    public static String genRandom() {
        //取目前時間的長整形值包含毫秒
        long millis = System.currentTimeMillis();
        //long millis = System.nanoTime();
        //加上兩位随機數
        Random random = new Random();
        int end2 = random.nextInt(99);
        //如果不足兩位前面補0
        String str = millis + String.format("%02d", end2);
        long id = new Long(str);
        String timeId = Long.toString(id);
        return timeId;
    }

    /**
     * 生成UUID工具類
     */
    public static String getUUID() {
        String uuid = UUID.randomUUID().toString().replace("-", "");
        return uuid;
    }


    //    生成時間+随機數的id
    public static String genRandom(String pre, Integer needLength) {
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmm");
        String part2 = format.format(new Date());
        int originLength = pre.length() + part2.length();
        if (needLength == null || needLength <= originLength) {
            needLength = pre.length() + part2.length();
        }
        StringBuffer part3 = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < needLength - originLength; i++) {
            int rand = random.nextInt(10);
            part3.append(rand);
        }
        return pre + "" + part2 + "" + part3;
    }


    //    生成20位随機id
    public static Long genRandomId() {
        long timeMillis = System.currentTimeMillis();
        Random random = new Random();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < 6; i++) {
            buffer.append(random.nextInt(10));
        }
        String id = timeMillis + buffer.toString();
        return Long.valueOf(id);
    }

    //    生成時間+随機數的id
    public static String genRandom(String pre, int needLength) {
        if (needLength <= pre.length()) {
            needLength = pre.length();
        }
        StringBuffer part3 = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < needLength - pre.length(); i++) {
            int rand = random.nextInt(10);
            part3.append(rand);
        }
        return pre + "" + part3;
    }

    public static void main(String[] args) {

    }

    public static String genSMSCode() {
        String value = "";
        Random random = new Random();
        int gen = random.nextInt(2);
        String charOrNum = gen % 2 == 0 ? "char" : "num";
        if ("char".equals(charOrNum)) {
            //字元
            int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
            int ascii = random.nextInt(26);
            value += (char) (ascii + temp);
        } else if ("num".equals(charOrNum)) {
            //是數字
            value += String.valueOf(random.nextInt(10));
        }
        return value;
    }

    public static String genSMSCode_num() {
        String value = "";
        Random random = new Random();
        while (true) {
            value += String.valueOf(random.nextInt(10));
            if (value.length() == 6) {
                break;
            }
        }
        return value;

    }

    public static String genOssKey(String originalFilename) {
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        return getUUID() + suffix;
    }

}
           

ImageToBase64

public class ImageToBase64 {

    private static String strNetImageToBase64;


    /**
     * 網絡圖檔轉換Base64的方法
     *
     * @param netImagePath
     */
    public static String NetImageToBase64(String netImagePath) {
        final ByteArrayOutputStream data = new ByteArrayOutputStream();
        try {
            // 建立URL
            URL url = new URL(netImagePath);
            final byte[] by = new byte[1024];
            // 建立連結
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        InputStream is = conn.getInputStream();
                        // 将内容讀取記憶體中
                        int len = -1;
                        while ((len = is.read(by)) != -1) {
                            data.write(by, 0, len);
                        }
                        // 對位元組數組Base64編碼
                        Base64.Encoder encoder = Base64.getEncoder();
                        strNetImageToBase64 = encoder.encodeToString(data.toByteArray());
                        // 關閉流
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

        } catch (IOException e) {
            e.printStackTrace();
        }
        return strNetImageToBase64;
    }

    /**
     * 本地圖檔轉換Base64的方法
     *
     * @param imgPath
     */

    public static String ImageToBase64(String imgPath) {
        return ImageToBase64(new File(imgPath));
    }

    /**
     * 本地圖檔轉換Base64的方法
     *
     */
    public static String ImageToBase64(File imageFile) {
        byte[] data = null;
        // 讀取圖檔位元組數組
        try {
            InputStream in = new FileInputStream(imageFile);
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 對位元組數組Base64編碼
        Base64.Encoder encoder = Base64.getEncoder();
        // 傳回Base64編碼過的位元組數組字元串
        return encoder.encodeToString(Objects.requireNonNull(data));
    }


}
           

MarkdownBuffer

public class MarkdownBuffer {

    private static final String NEXT_LINE = "\n";
    private StringBuffer buffer = new StringBuffer();

    public MarkdownBuffer h6(String text) {
        buffer.append("###### " + text);
        return this;
    }

    public MarkdownBuffer h5(String text) {
        buffer.append("##### " + text);
        return this;
    }

    public MarkdownBuffer h4(String text) {
        buffer.append("#### " + text);
        return this;
    }

    public MarkdownBuffer h3(String text) {
        buffer.append("### " + text);
        return this;
    }

    public MarkdownBuffer h2(String text) {
        buffer.append("## " + text);
        return this;
    }

    public MarkdownBuffer h1(String h1Text) {
        buffer.append("# " + h1Text);
        return this;
    }

    public MarkdownBuffer code(String code) {
        buffer.append("`" + code + "`");
        return this;
    }

    public MarkdownBuffer link(String link, String url) {
        buffer.append("[" + link + "](" + url + ")");
        return this;
    }

    public MarkdownBuffer text(String text) {
        buffer.append(text);
        return this;
    }

    public MarkdownBuffer quote(String text) {
        buffer.append("> " + text);
        return this;
    }

    public MarkdownBuffer orange(String orangeText) {
        buffer.append("<font color=\"warning\">" + orangeText + "</font>");
        return this;
    }

    public MarkdownBuffer green(String greenText) {
        buffer.append("<font color=\"info\">" + greenText + "</font>");
        return this;
    }

    public MarkdownBuffer gray(String grayText) {
        buffer.append("<font color=\"comment\">" + grayText + "</font>");
        return this;
    }

    public MarkdownBuffer bold(String boldText) {
        buffer.append("**" + boldText + "**");
        return this;
    }

    public MarkdownBuffer nextLine() {
        buffer.append(NEXT_LINE);
        return this;
    }

    public MarkdownBuffer quoteEnd() {
        buffer.append(NEXT_LINE).append(NEXT_LINE);
        return this;
    }

    @Override
    public String toString() {
        return this.buffer.toString();
    }


}

           

MD5Utils

public class MD5Utils {
    /**
     * 預設的密碼字元串組合,用來将位元組轉換成 16 進制表示的字元,apache校驗下載下傳的檔案的正确性用的就是預設的這個組合
     */
    protected static char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    protected static MessageDigest messagedigest = null;
    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsaex) {
            System.err.println(MD5Utils.class.getName() + "初始化失敗,MessageDigest不支援MD5Util。");
            nsaex.printStackTrace();
        }
    }

    /**
     * 生成字元串的md5校驗值
     *
     * @param s
     * @return
     */
    public static String getMD5String(String s) {
        return getMD5String(s.getBytes());
    }

    /**
     * 判斷字元串的md5校驗碼是否與一個已知的md5碼相比對
     *
     * @param password
     *            要校驗的字元串
     * @param md5PwdStr
     *            已知的md5校驗碼
     * @return
     */
    public static boolean checkPassword(String password, String md5PwdStr) {
        String s = getMD5String(password);
        return s.equals(md5PwdStr);
    }

    /**
     * 生成檔案的md5校驗值
     *
     * @param file
     * @return
     * @throws IOException
     */
    public static String getFileMD5String(File file) throws IOException {
        InputStream fis;
        fis = new FileInputStream(file);
        byte[] buffer = new byte[1024];
        int numRead = 0;
        while ((numRead = fis.read(buffer)) > 0) {
            messagedigest.update(buffer, 0, numRead);
        }
        fis.close();
        return bufferToHex(messagedigest.digest());
    }

    /**
     * JDK1.4中不支援以MappedByteBuffer類型為參數update方法,并且網上有讨論要慎用MappedByteBuffer,
     * 原因是當使用 FileChannel.map 方法時,MappedByteBuffer 已經在系統内占用了一個句柄, 而使用
     * FileChannel.close 方法是無法釋放這個句柄的,且FileChannel有沒有提供類似 unmap 的方法,
     * 是以會出現無法删除檔案的情況。
     *
     * 不推薦使用
     *
     * @param file
     * @return
     * @throws IOException
     */
    public static String getFileMD5String_old(File file) throws IOException {
        FileInputStream in = new FileInputStream(file);
        FileChannel ch = in.getChannel();
        MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
        messagedigest.update(byteBuffer);
        in.close();
        return bufferToHex(messagedigest.digest());
    }

    public static String getMD5String(byte[] bytes) {
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        char c0 = hexDigits[(bt & 0xf0) >> 4];// 取位元組中高 4 位的數字轉換, >>>
        // 為邏輯右移,将符号位一起右移,此處未發現兩種符号有何不同
        char c1 = hexDigits[bt & 0xf];// 取位元組中低 4 位的數字轉換
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

    public static void main(String[] args) throws IOException {
        long begin = System.currentTimeMillis();

        File file = new File("D:/BaiduNetdiskDownload/01_SpringBoot全套視訊教程2018年3月份錄制2.0.x版本/01-SpringBoot簡介.avi");
        if(!file.exists()){
            System.out.println("不存在");
        }
        String md5 = getFileMD5String(file);

        String md5_a = getMD5String("a");

        long end = System.currentTimeMillis();
        System.out.println("md5:" + md5 + " time:" + ((end - begin) / 1000) + "s");
        System.out.println(file.getPath());
    }

    public static String getNetFileMD5String(String imagePath) {
        try {
            // 建立URL
            URL url = new URL(imagePath);
            final byte[] by = new byte[1024];
            // 建立連結
            final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            InputStream is = conn.getInputStream();
            File temp = File.createTempFile("temp", ".img");
            FileOutputStream outputStream = new FileOutputStream(temp);
            int len = -1;
            while ((len = is.read(by)) != -1) {
                outputStream.write(by, 0, len);
            }
            // 關閉流
            is.close();
            outputStream.close();
            String md5String = getFileMD5String(temp);
            temp.delete();
            return md5String;

        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
           

TextBuffer

public class TextBuffer {

    private static final String NEXT_LINE = "\n";

    private StringBuffer buffer = new StringBuffer();

    public TextBuffer nextLine() {
        buffer.append( NEXT_LINE);
        return this;
    }

    public TextBuffer append(String text) {
        buffer.append(text);
        return this;
    }

    @Override
    public String toString() {
        return this.buffer.toString();
    }
}
           

繼續閱讀