Nail nail custom robot simple to use

Preface:

Years ago there are nailing robot to company’s demand, use after found that really is very simple, have to exclamation ali cow force, this article summarizes the experience of personal use nailing robot, at the same time introduce themselves on the basis of constructing a utility class to facilitate subsequent direct “box”, hope to inspire the reader.

Purpose:

  1. A simple description of the nail custom robot use, note is a custom robot
  2. I designed a tool class for nailing robot. (key)
  3. Summarize some personal use of the nail robot pit, at the same time to provide solutions to the reader reference can solve the problem

Stapling document:

The use of robots is still very simple, direct reference to the documentation can be built, if you know this part can directly jump to write tool class part for the subsequent reading of the article.

Developers.dingtalk.com/document/ap…

Since the official document of the nail is updated frequently, the connection here may fail in the future

How do you create a robot

The document is more detailed, we can carry out actual combat according to the content of the document. Here, robots created by the Novice experience group are used for experiments. The following covers creating a custom robot and testing how the robot works.

Create a custom robot

Click a robot at random, right click the menu, “more robots” will appear, enter the interface

Click “More Robots”

Select the custom robot for use:

There are many other robots, but if you are interested, check out The Documentation for more information

Select Add from the following screen:

To get to the next screen, fill in the following as instructed:

  • The name of robot: oneself take a proper name, oneself like line
  • Add to the group: A key step, which means the specific group to which your robot should be added for use. This means that only people in the group can receive notifications.

Here is a description of the security Settings:

  • Custom keywords:The key configuration, here custom keywords can be set according to their own preferences. But once set to send requestsYou must carry keywords, the request takes effect. Otherwise, an error code is returned31000And the corresponding error message.
  • Checkmark: Checkmark is suggested to better protect the interface in the request, and note that the content is copied after the signature is added
  • IP address (segment) : This parameter is not checked because it has not been tested. In the formal production environment, IP address restriction is recommended to ensure safety

It is recommended to save the preceding and the keywords, but you can also view them from the Settings after the build is complete:

Signature: SECf075e3890b7d79ca645e51b42644fc57c2402577d5a955bce51cb980cec0a3b6

Key words: New person

So far, we have successfully created a custom robot of nails. The whole process is very simple. Remember to save the corresponding information here:

https://oapi.dingtalk.com/robot/send?access_token=381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050
Copy the code

The above is the personal configuration. This robot has been deleted at the time of Posting, so readers can experiment by themselves.

Test whether the robot can be used normally

Through the above steps, we have built a basic robot for our use. Before going to the next step, we need to verify whether the nail robot can be used normally. Here is a simple and quick verification method for different platforms.

Windows authentication mode:

$curl = $curl = $curl = $curl = $curl = $curl = $curl = $curl = $curl = $curl

As shown below, select Git Bash Here to open the command-line interface

CURL request: CURL request: CURL request: CURL request

curl 'https://oapi.dingtalk.com/robot/send?access_token=381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050&time stamp=1613211530113&secret=SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc' \ -H 'Content-Type: Application/json \ '- d' {" msgtype ", "text", "text" : {" content ":" new content test "}} 'Copy the code

As expected, the verification failed in this case according to the official documentation. Why? There are several reasons:

  • Signed ciphertext: We set the signed ciphertext, so the request parameters need to add the corresponding signature ciphertext, that is, the signature step is selected in the step of adding.

  • Timestamps: The request needs to pass a time stamp, but we do not include a time stamp in the request parameters, and the time stamp must be within one hour of the system time, even if the request parameters are correct after this time will not pass

timestamp = 1613212103494 sign = MO79EJ58O9lmuQJo1dB1KGMhkZI%2BM5KkyD0NYuNe8%2B8%3D

Note that we added two parameters to the URL:

curl 'https://oapi.dingtalk.com/robot/send?access_token=381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050&time stamp=1613212722591&sign=SsKKlkvwM%2F4tsCPE6YoGls8vgkQqWJGHYpvWbW7hTGM%3D' \ -H 'Content-Type: Application/json \ '- d' {" msgtype ", "text", "text" : {" content ":" new why you so cow force "}} 'Copy the code

About this part of the content, we have summarized into the “problem summary” part, if you still feel confused, you can refer to it.

The request for content fails with the keyword set by the user:

zhaoxudong@LAPTOP-MEUFMP1M MINGW64 /d/Users/zhaoxudong/Desktop
$ curl 'https://oapi.dingtalk.com/robot/send?access_token=381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050&time stamp=1613212722591&sign=SsKKlkvwM%2F4tsCPE6YoGls8vgkQqWJGHYpvWbW7hTGM%3D' \
>    -H 'Content-Type: application/json' \
>    -d '{" msgtype ", "text", "text" : {" content ":" new why you so cow force "}}'
  %Total % Received % Xferd Average Speed Time Time Time Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   178  100   115  100    63    991    543 --:--:-- --:--:-- --:--:--  1534
{"errcode":310000,"errmsg":"keywords not in content, more: [https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq]"}

Copy the code

After troubleshooting, we found that the Windows system uses gb2312 code by default, so we need to switch the system code at this time. In order to prove that there is a problem with the system code, we first verify the code:

Open the CMD window of Window, enter the CHCP command to enter the specific page, and you can see the following 936. Baidu found that it was GB2312, which was transcoded in the process of sending the request, resulting in garbled characters.

C:\Users\zhaoxudong> CHCP activity code page: 936Copy the code

Solution is more simple, change the rectification system coding can, about setting method: blog.csdn.net/robinhunan/…

Episode: After setting up, the editor could not be compiled due to coding problems. After verification, it was found that the problem was that classes could not be found due to the garbled code of the folder. Therefore, it is suggested to place Java projects in a directory full of English. So more recommended Linux way, can save a lot of trouble

Linux Authentication mode:

Linux is a simple CURL request, and the probability of failure is relatively small. According to the window content, three parameters are required to successfully request a CURL request.

curl 'https://oapi.dingtalk.com/robot/send?access_token=381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050&time stamp=1613212722591&sign=SsKKlkvwM%2F4tsCPE6YoGls8vgkQqWJGHYpvWbW7hTGM%3D' \ -H 'Content-Type: Application/json \ '- d' {" msgtype ", "text", "text" : {" content ":" new why you so cow force "}} 'Copy the code

If errorCode returns 0, the request is successful:

{"errcode":0,"errmsg":"ok"}
Copy the code

After the request is successful, we can see the corresponding result:

Note that the pin robot cannot request too often. It is recommended to limit QPS requests per minute

Write utility classes

As can be seen from the previous section, the construction of the whole nail robot is very simple. However, it is not particularly convenient to use. I had a small need to use the nail to make an early warning. In order to be able to use the nail directly out of the box, I built a tool class by myself

My personal level is limited, and there is still a lot of room for improvement in the tool class. However, FOR me, THERE is no bottleneck in using the tool class.

The code address of the utility class

Here is the personal gadget class integrated into the personal small project, want to refer to the direct download, the following article code is also from the project inside.

Please check:com.zxd.interview.dingrobotThis package

Specific code address: gitee.com/lazyTimes/i…

Ideas for building utility classes

The required components of the whole request process are divided into the following parts:

Build the basic request environment: the required parameters such as the request address, request signature, or keyword, are required otherwise the request will not run properly, so we propose it as the environment.

Build request parameters: Since The studs support a lot of MSgType, that is, text type, I refer to the SDK to build a request parameter class. In order to facilitate expansion, I designed an interface for subsequent extension and compatibility.

Send requests using JAVA code: With minimal dependencies, use the most common HttpClient to send requests that mimic JAVA. But on this basis to do a little bit of packaging, convenient subsequent expansion

  1. HttpClient encapsulates request parameters into a configuration object for management
  2. Request method encapsulation, which is wrapped with an object, or can be wrapped directly with Springorg.springframework.web.bind.annotation.RequestMethodOr just use enumerations to build constants.
  3. Build the nail request utility class: Finally, we integrate all the above steps to build a core request tool class, through the environment parameters to build the request URL and some Header Settings, and build different request methods to send the request, call the HttpClient tool class to send the request, and after sending the result object and a series of operations are completed by this tool class. Is the core class of this tool class.
  4. Msg: This object contains the entity object corresponding to all JSON parameter formats supported by the request, and the corresponding object is constructed according to the parameter format. Personally, all the internal classes are encapsulated into an object, which is convenient for the client to understand and call.

Return request result: contains error code, error message, and other parameters, etc., can also be modified to return a string, the client decides how to handle

Return result after request: encapsulate the above error code or error message as a simple object to return, also can return string results if you do not like.

Unit testing

Before we introduce the formal results, here is a screenshot of the results, including all the types in the stapling document, including the main types currently supported by the stapling document:

Here is the code for the unit test, which tests various request types and calls the toolkit to send the request:

Note that the following request text contains the keyword set in the previous request example. The request cannot succeed without the keyword

import com.alibaba.fastjson.JSON;
import org.apache.commons.codec.binary.Base64;
import org.junit.jupiter.api.Test;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/ * * *@author zxd
 * @versionV1.0.0 *@Package : com.zxd.interview.dingrobot
 * @Description: Nail robot test class *@Create on : 2021/2/7 11:06
 **/
public class DingRobotUtilsTest {

    /** Results of running the following five unit tests */ 
    @Test
    public void testAll(a) {
        testText();
        testLink();
        testMarkdown();
        testActionCard();
        testFeedCard();
    }

    /** * Builds the current system timestamp */
    @Test
    public void generateSystemCurrentTime(a) throws Exception {
        long currentTimeMillis = System.currentTimeMillis();
        String secret = "SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc";
        String sign = generateSign(currentTimeMillis, secret);
        System.out.println("timestamp = " + currentTimeMillis);
        System.out.println("sign = " + sign);
    }

    /** * Test link type request */
    @Test
    public void testLink(a) {
        DingRobotRequest.Builder builder = new DingRobotRequest.Builder();
        DingRobotRequest build = builder.secret("SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc")
                .url("https://oapi.dingtalk.com/robot/send")
                .accessToken("381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050")
                .msg(generateLink()).build();
        try {
            DingRobotResponseMsg dingRobotResponseMsg = DingRobotUtils.notifyRobot(build);
            System.err.println(JSON.toJSONString(dingRobotResponseMsg));
        } catch(Exception e) { e.printStackTrace(); }}/** * Tests the text type */
    @Test
    public void testText(a) {
        DingRobotRequest.Builder builder = new DingRobotRequest.Builder();
        DingRobotRequest build = builder.secret("SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc")
                .url("https://oapi.dingtalk.com/robot/send")
                .accessToken("381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050")
                .msg(generateText()).build();
        try {
            DingRobotResponseMsg dingRobotResponseMsg = DingRobotUtils.notifyRobot(build);
            System.err.println(JSON.toJSONString(dingRobotResponseMsg));
        } catch(Exception e) { e.printStackTrace(); }}/** Tests the markdown type */
    @Test
    public void testMarkdown(a) {
        DingRobotRequest.Builder builder = new DingRobotRequest.Builder();
        DingRobotRequest build = builder.secret("SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc")
                .url("https://oapi.dingtalk.com/robot/send")
                .accessToken("381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050")
                .msg(generateMarkdown()).build();
        try {
            DingRobotResponseMsg dingRobotResponseMsg = DingRobotUtils.notifyRobot(build);
            System.err.println(JSON.toJSONString(dingRobotResponseMsg));
        } catch(Exception e) { e.printStackTrace(); }}/** Test ActionCard type */
    @Test
    public void testActionCard(a) {
        DingRobotRequest.Builder builder = new DingRobotRequest.Builder();
        DingRobotRequest build = builder.secret("SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc")
                .url("https://oapi.dingtalk.com/robot/send")
                .accessToken("381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050")
                .msg(generateActionCard()).build();
        try {
            DingRobotResponseMsg dingRobotResponseMsg = DingRobotUtils.notifyRobot(build);
            System.err.println(JSON.toJSONString(dingRobotResponseMsg));
        } catch(Exception e) { e.printStackTrace(); }}/** Tests the FeedCard type */
    @Test
    public void testFeedCard(a) {
        DingRobotRequest.Builder builder = new DingRobotRequest.Builder();
        DingRobotRequest build = builder.secret("SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc")
                .url("https://oapi.dingtalk.com/robot/send")
                .accessToken("381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050")
                .msg(generateFeed()).build();
        try {
            DingRobotResponseMsg dingRobotResponseMsg = DingRobotUtils.notifyRobot(build);
            System.err.println(JSON.toJSONString(dingRobotResponseMsg));
        } catch(Exception e) { e.printStackTrace(); }}private DingRobotRequestBody generateFeed(a) {
        List<DingRobotRequestBody.FeedCard.FeedItem> list = new ArrayList<>();
        DingRobotRequestBody dingRobotRequestBody = new DingRobotRequestBody();
        DingRobotRequestBody.FeedCard feedCard = new DingRobotRequestBody.FeedCard();
        DingRobotRequestBody.FeedCard.FeedItem feedItem = new DingRobotRequestBody.FeedCard.FeedItem();
        feedItem.setMessageURL("https://www.dingtalk.com/");
        feedItem.setTitle("The train of the new generation moves forward.");
        feedItem.setPicURL("https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png");
        list.add(feedItem);
        feedCard.setLinks(list);
        dingRobotRequestBody.setFeedCard(feedCard);
        dingRobotRequestBody.setMsgType("feedCard");
        return dingRobotRequestBody;
    }

    private DingRobotRequestBody generateActionCard(a) {
        DingRobotRequestBody dingRobotRequestBody = new DingRobotRequestBody();
        DingRobotRequestBody.ActionCard actionCard = new DingRobotRequestBody.ActionCard();
        actionCard.setBtnOrientation("0");
        actionCard.setSingleTitle("Read the full article");
        actionCard.setSingleURL("https://www.dingtalk.com/");
        actionCard.setText("The couple! [screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png) \n" +
                ### The Apple Cafe Steve Jobs wanted to build 20 years ago \n +
                "The design of Apple Store is changing from a full sense of technology to life, and the trend of life can actually be traced back to Apple's plan to build a cafe 20 years ago.");
        actionCard.setTitle("Steve Jobs wanted to build an Apple Cafe 20 years ago, and it was the precursor to the Apple Store.");
        dingRobotRequestBody.setMsgType("actionCard");
        dingRobotRequestBody.setActionCard(actionCard);
        return dingRobotRequestBody;
    }

    private DingRobotRequestBody generateMarkdown(a) {
        DingRobotRequestBody dingRobotRequestBody = new DingRobotRequestBody();
        DingRobotRequestBody.MarkDown markDown = new DingRobotRequestBody.MarkDown();
        dingRobotRequestBody.setMsgType("markdown");
        markDown.setTitle("Hangzhou Weather");
        markDown.setText("Rookie Test Title \n" +
                "# level 1 title \n" +
                "## secondary title \n" +
                "### 3 title \n" +
                "#### Grade 4 Title \n" +
                "##### grade 5 title \n" +
                "###### LEVEL 6 Title \n" +
                "\n" +
                "Reference \ n" +
                "> A man who stands for nothing will fall for anything.\n" +
                "\n" +
                "Text in bold, italic \n" +
                "**bold**\n" +
                "*italic*\n" +
                "\n" +
                "Link \ n" +
                "[this is a link](http://name.com)\n" +
                "\n" +
                "Pictures \ n" +
                ! "" [](http://name.com/pic.jpg)\n" +
                "\n" +
                "Unordered list \n" +
                "- item1\n" +
                "- item2\n" +
                "\n" +
                "Ordered list \n" +
                "1. item1\n" +
                "2. item2");
        dingRobotRequestBody.setMarkDown(markDown);
        return dingRobotRequestBody;
    }

    private DingRobotRequestBody generateText(a) {
        DingRobotRequestBody dingRobotRequestBody = new DingRobotRequestBody();
        DingRobotRequestBody.Text text = new DingRobotRequestBody.Text();
        text.setContent("Why is the new guy so badass?");
        DingRobotRequestBody.At at = getnerateAt();
        dingRobotRequestBody.setMsgType("text");
        dingRobotRequestBody.setAt(at);
        dingRobotRequestBody.setText(text);
        return dingRobotRequestBody;
    }

    private DingRobotRequestBody generateLink(a) {
        DingRobotRequestBody dingRobotRequestBody = new DingRobotRequestBody();
        DingRobotRequestBody.Link link = new DingRobotRequestBody.Link();
        link.setMessageUrl("https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srci d=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nett ype=WIFI");
        link.setPicUrl("");
        link.setTitle("The train of the ages moves forward.");
        link.setText("New guy: This upcoming release, founder XX calls it Mangrove. Before that, when faced with a major upgrade, product managers used a code name that suited the situation. This time, why mangrove?");
        DingRobotRequestBody.At at = getnerateAt();
        dingRobotRequestBody.setMsgType("link");
        dingRobotRequestBody.setAt(at);
        dingRobotRequestBody.setLink(link);
        return dingRobotRequestBody;
    }

    /** * Build at request **@return* /
    private DingRobotRequestBody.At getnerateAt(a) {
        DingRobotRequestBody.At at = new DingRobotRequestBody.At();
        at.setAtAll(true);
        at.setAtMobiles(Arrays.asList("xxxxx"."123456789"));
        return at;
    }

    /** * Build signature method **@paramTimestamp Indicates the time *@paramSecret secret key *@return
     * @throws Exception
     */
    private String generateSign(Long timestamp, String secret) throws Exception {
        String stringToSign = timestamp + "\n" + secret;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        return URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8"); }}Copy the code

Build utility class:

The following illustrates my basic design for the above unit tests. We build a nailing tool class to support use.

Class structure introduction:

Maven depends on:

Before the specific code preparation, the need to introduce the corresponding dependency, personal uphold the principle of minimum dependence, the use of tripartite JAR package only for some test kit and Httpclient request kit and the most familiar fastjson kit.

<! -- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>

<! -- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>
<! -- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
Copy the code

Class structure:

Class structure contains the previous design ideas described in the case, including request class, tool class, parameter encapsulation and request object structure encapsulation.

+ DingRobotRequest. Java nailing request object + DingRobotRequestAble. Java request interface, Allow sending nailing request interface + DingRobotRequestBody. Java interface allows sending nailing request specific implementation class, more important, docking document object of nailing + DingRobotRequestMsg. Java abandoned objects, But still reserve s + DingRobotResponseMsg. Java + DingRobotUtils request returns object. The Java nailing request tools, Java httpClient request utility class + httpConfig. Java request parameter construction class + httpmethods. Java request method classCopy the code

Build the basic request environment

To build the basic request environment, we use objects to encapsulate all the environment parameters, and use build mode to build a builder, using build to build the environment parameters we need, which is used as follows:

  • Build the request URL
  • Build the request accessToken
  • Build request MSG, important, you can send different messages by building corresponding requests
DingRobotRequest build = builder.secret("SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc")
                .url("https://oapi.dingtalk.com/robot/send")
                .accessToken("381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050")
                .msg(generateActionCard()).build();
Copy the code

The specific source code is as follows, which contains a few simple necessary parameters, and a builder. Note that for the constructor to be private, only the constructor is allowed to be initialized externally:

/ * * *@author zxd
 * @versionV1.0.0 *@Package : com.dcc.common.field
 * @Description: Nail robot request entity class *@Create on : 2021/2/5 15:40
 **/
public class DingRobotRequest {

    /** * request URL */
    private String url;

    /** * token */
    private String accessToken;

    /** * key */
    private String secret;

    /** * request MSG */
    private DingRobotRequestBody msg;

    private DingRobotRequest(a){}private DingRobotRequest(Builder builder) {
        this.url = builder.url;
        this.accessToken = builder.accessToken;
        this.secret = builder.secret;
        this.msg = builder.msg;
    }

    public static class Builder {

        private String url;
        private String accessToken;
        private String secret;
        private DingRobotRequestBody msg;

        public DingRobotRequest.Builder url(String url){
            this.url = url;
            return this;
        }
        public DingRobotRequest.Builder accessToken(String accessToken){
            this.accessToken = accessToken;
            return this;
        }
        public DingRobotRequest.Builder secret(String secret){
            this.secret = secret;
            return this;
        }
        public DingRobotRequest.Builder msg(DingRobotRequestBody msg){
            this.msg = msg;
            return this;
        }

        public DingRobotRequest build(a){
            return new DingRobotRequest(this); }}public String getUrl(a) {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getAccessToken(a) {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public String getSecret(a) {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public DingRobotRequestBody getMsg(a) {
        return msg;
    }

    public void setMsg(DingRobotRequestBody msg) {
        this.msg = msg;
    }

    @Override
    public String toString(a) {
        return "DingRobotRequest{" +
                "url='" + url + '\' ' +
                ", accessToken='" + accessToken + '\' ' +
                ", secret='" + secret + '\' ' +
                ", msg='" + msg + '\' ' +
                '} '; }}Copy the code

Build request parameters

Here is an example of building request parameters. We can construct different requests using chain calls:

 /** * The default configuration of the pin robot **@paramDingRobotRequest Nailing robot requests objects *@paramDingRobotRequestMsg Nails the robot request entity *@return* /
    private static HttpConfig buildDefaultHttpConfig(DingRobotRequest dingRobotRequest, DingRobotRequestAble dingRobotRequestMsg) {
        return HttpConfig.custom().headers(defaultBasicHeader())
                .url(dingRobotRequest.getUrl())
                .encoding("UTF-8")
                .method(HttpMethods.POST)
                .json(JSON.toJSONString(dingRobotRequestMsg));
    }
Copy the code

We define the request encoding, request header, request method parameters, request context parameters, and so on.

/** * Request configuration class ** /
public class HttpConfig {

    private HttpConfig(a) {}// Pass in a specific type of argument
    public static final String ENTITY_STRING = "$ENTITY_STRING$";
    public static final String ENTITY_MULTIPART = "$ENTITY_MULTIPART$";

    /** * get instance **@return* /
    public static HttpConfig custom(a) {
        return new HttpConfig();
    }

    /** * HttpClient object */
    private HttpClient client;

    /** * Header Header information */
    private Header[] headers;

    /** * whether to return headers for response */
    private boolean isReturnRespHeaders;

    /**
     * 请求方法
     */
    private HttpMethods method = HttpMethods.GET;

    /** * Request method name */
    private String methodName;

    /**
     * 用于cookie操作
     */
    private HttpContext context;

    /**
     * 传递参数
     */
    private Map<String, Object> map;

    /** * Use json as the input parameter */
    private String json;

    /** * input/output encoding */
    private String encoding = Charset.defaultCharset().displayName();

    /** * Input code */
    private String inenc;

    /** * output code */
    private String outenc;

    /** * Solve the problem of strean being closed */
    private static final ThreadLocal<OutputStream> outs = new ThreadLocal<OutputStream>();

    /** ** The url is overwritten */
    private static final ThreadLocal<String> urls = new ThreadLocal<String>();

    /** * HttpClient object */
    public HttpConfig client(HttpClient client) {
        this.client = client;
        return this;
    }

    /** * resource url */
    public HttpConfig url(String url) {
        urls.set(url);
        return this;
    }

    /** * Header Header information */
    public HttpConfig headers(Header[] headers) {
        this.headers = headers;
        return this;
    }

    /** * Header Information (whether to return headers in response) */
    public HttpConfig headers(Header[] headers, boolean isReturnRespHeaders) {
        this.headers = headers;
        this.isReturnRespHeaders = isReturnRespHeaders;
        return this;
    }

    /**
     * 请求方法
     */
    public HttpConfig method(HttpMethods method) {
        this.method = method;
        return this;
    }

    /**
     * 请求方法
     */
    public HttpConfig methodName(String methodName) {
        this.methodName = methodName;
        return this;
    }

    /** * Related to cookie operation */
    public HttpConfig context(HttpContext context) {
        this.context = context;
        return this;
    }

    /**
     * 传递参数
     */
    public HttpConfig map(Map<String, Object> map) {
        synchronized (getClass()) {
            if (this.map == null || map == null) {
                this.map = map;
            } else {
                this.map.putAll(map); ; }}return this;
    }

    /** * Takes a json string as an argument */
    public HttpConfig json(String json) {
        this.json = json;
        map = new HashMap<String, Object>();
        map.put(ENTITY_STRING, json);
        return this;
    }

    /** ** ** is used when uploading files
    public HttpConfig files(String[] filePaths) {
        return files(filePaths, "file");
    }

    /** ** * is used when uploading files@paramFilePaths File path */
    public HttpConfig files(String[] filePaths, String inputName) {
        return files(filePaths, inputName, false);
    }

    /** ** * is used when uploading files@paramFilePaths Path of the file to be uploaded *@paramInputName is the name value of the File input tag. The default value is File *@param forceRemoveContentTypeChraset
     * @return* /
    public HttpConfig files(String[] filePaths, String inputName, boolean forceRemoveContentTypeChraset) {
        synchronized (getClass()) {
            if (this.map == null) {
                this.map = new HashMap<String, Object>();
            }
        }
        map.put(ENTITY_MULTIPART, filePaths);
        map.put(ENTITY_MULTIPART + ".name", inputName);
        map.put(ENTITY_MULTIPART + ".rmCharset", forceRemoveContentTypeChraset);
        return this;
    }

    /** * input/output encoding */
    public HttpConfig encoding(String encoding) {
        // Set the input and output
        inenc(encoding);
        outenc(encoding);
        this.encoding = encoding;
        return this;
    }

    /** * Input code */
    public HttpConfig inenc(String inenc) {
        this.inenc = inenc;
        return this;
    }

    /** * output code */
    public HttpConfig outenc(String outenc) {
        this.outenc = outenc;
        return this;
    }

    /** * output stream object */
    public HttpConfig out(OutputStream out) {
        outs.set(out);
        return this;
    }

    public HttpClient client(a) {
        return client;
    }

    public Header[] headers() {
        return headers;
    }

    public boolean isReturnRespHeaders(a) {
        return isReturnRespHeaders;
    }

    public String url(a) {
        return urls.get();
    }

    public HttpMethods method(a) {
        return method;
    }

    public String methodName(a) {
        return methodName;
    }

    public HttpContext context(a) {
        return context;
    }

    public Map<String, Object> map(a) {
        return map;
    }

    public String json(a) {
        return json;
    }

    public String encoding(a) {
        return encoding;
    }

    public String inenc(a) {
        return inenc == null ? encoding : inenc;
    }

    public String outenc(a) {
        return outenc == null ? encoding : outenc;
    }

    public OutputStream out(a) {
        returnouts.get(); }}Copy the code

Send the request using JAVA code

Build a basic utility class from the Httpclient request toolkit:

This class is a highly coupled class that is difficult to reuse and extend, and is not very well designed.

/** ** HttpClient request tool encapsulation class */
public class HttpClientUtil {

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

		// Create an Httpclient object
		CloseableHttpClient httpclient = HttpClients.createDefault();

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

			// Create an HTTP GET request
			HttpGet httpGet = new HttpGet(uri);

			// Execute the request
			response = httpclient.execute(httpGet);
			// Check whether the return status is 200
			if (response.getStatusLine().getStatusCode() == 200) {
				resultString = EntityUtils.toString(response.getEntity(), "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) {
		return doGet(url, null);
	}

	public static String doPost(String url, Map<String, String> param) {
		// Create an Httpclient object
		CloseableHttpClient httpClient = HttpClients.createDefault();
		CloseableHttpResponse response = null;
		String resultString = "";
		try {
			// Create an Http Post request
			HttpPost httpPost = new HttpPost(url);
			// Create a parameter list
			if(param ! =null) {
				List<NameValuePair> paramList = new ArrayList<>();
				for (String key : param.keySet()) {
					paramList.add(new BasicNameValuePair(key, param.get(key)));
				}
				// Simulate the form
				UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
				httpPost.setEntity(entity);
			}
			// Execute the HTTP request
			response = httpClient.execute(httpPost);
			resultString = EntityUtils.toString(response.getEntity(), "utf-8");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				response.close();
			} catch(IOException e) { e.printStackTrace(); }}return resultString;
	}

	public static String doPost(String url) {
		return doPost(url, null);
	}
	
	public static String doPostJson(String url, String json) {
		// Create an Httpclient object
		CloseableHttpClient httpClient = HttpClients.createDefault();
		CloseableHttpResponse response = null;
		String resultString = "";
		try {
			// Create an Http Post request
			HttpPost httpPost = new HttpPost(url);
			// Create the requested content
			StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
			httpPost.setEntity(entity);
			// Execute the HTTP request
			response = httpClient.execute(httpPost);
			resultString = EntityUtils.toString(response.getEntity(), "utf-8");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(response ! =null){ response.close(); }}catch(IOException e) { e.printStackTrace(); }}return resultString;
	}

	/** * request send * according to request Config@param httpConfig
	 * @return* /
	public static String send(HttpConfig httpConfig) {
		returndoPostJson(httpConfig.url(), httpConfig.json()); }}Copy the code

Then according to the results of the request to design a nail robot return object, return object design is relatively simple.

/ * * *@author zxd
 * @versionV1.0.0 *@Package : com.dcc.common.field
 * @Description: The pin robot returns object *@Create on : 2021/2/5 18:26
 **/
public class DingRobotResponseMsg {

    /** * error code */
    private String errcode;

    /** * error message */
    private String errmsg;
    /** * more links */
    private String more;

    public DingRobotResponseMsg(String errcode, String errmsg, String more) {
        this.errcode = errcode;
        this.errmsg = errmsg;
        this.more = more;
    }

    public DingRobotResponseMsg(a) {}public String getErrcode(a) {
        return errcode;
    }

    public String getErrmsg(a) {
        return errmsg;
    }

    public String getMore(a) {
        return more;
    }

    public void setErrcode(String errcode) {
        this.errcode = errcode;
    }

    public void setErrmsg(String errmsg) {
        this.errmsg = errmsg;
    }

    public void setMore(String more) {
        this.more = more; }}Copy the code

Finally, and most importantly, from the pinned document, we will build a request object class of all types that are currently supported by the pinned document. Internal uses a large number of internal classes, the client needs to understand certain details before specific calls. Below is a brief description of the basic usage structure of content classes.

  • At The internal class of an object
  • Text Text type
  • Link Request Link type
  • MarkDown MarkDown type
  • ActionCard Indicates the overall jump type
  • FeedCard Share card type
/ * * *@author zxd
 * @versionV1.0.0 *@Package : com.dcc.common.field
 * @DescriptionRequest cases: {" msgType ": "text","text": {"content": "custom specific content"}} *@link {https://developers.dingtalk.com/document/app/custom-robot-access}
 *
 * @Create on : 2021/2/5 11:55
 **/
public class DingRobotRequestBody implements DingRobotRequestAble {

    /** * object content */
    private At at;

    /** * type */
    private String msgtype;

    /** * Text type */
    private Text text;

    /** * Connection type */
    private Link link;

    /** * Markdown type */
    private MarkDown markdown;

    /** * Jump to ActionCard type */
    private ActionCard actionCard;

    /** * FeedCard type */
    private FeedCard feedCard;


    /** * FeedCard type ** msgType String Yes This message type is fixed FeedCard. * Title String is a single piece of information text. * messageURL String is clicking on a single message to jump to the link. * picURL String is the URL of the image following a single message. * /
    public static class FeedCard{

        private List<FeedItem> links;

        /** * indicates the FeedCard subtype */
        public static class FeedItem{

            private String title;

            private String messageURL;

            private String picURL;

            public String getTitle(a) {
                return title;
            }

            public void setTitle(String title) {
                this.title = title;
            }

            public String getMessageURL(a) {
                return messageURL;
            }

            public void setMessageURL(String messageURL) {
                this.messageURL = messageURL;
            }

            public String getPicURL(a) {
                return picURL;
            }

            public void setPicURL(String picURL) {
                this.picURL = picURL; }}public List<FeedItem> getLinks(a) {
            return links;
        }

        public void setLinks(List<FeedItem> links) {
            this.links = links; }}ActionCard type * msgType String Indicates the message type, which is fixed as ActionCard. * Title String is the content displayed in the first screen session. * Text String is a message in Markdown format. * singleTitle String is the title of a single button. * * Note that BTNS is invalid after setting this item and singleURL. * * singleURL String is the URL triggered by clicking the singleTitle button. * btnOrientation String No 0: buttons are aligned vertically 1: buttons are aligned horizontally */
    public static class ActionCard{

        private String title;

        private String text;

        private String btnOrientation;

        private String singleTitle;

        private String singleURL;

        public String getTitle(a) {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getText(a) {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public String getBtnOrientation(a) {
            return btnOrientation;
        }

        public void setBtnOrientation(String btnOrientation) {
            this.btnOrientation = btnOrientation;
        }

        public String getSingleTitle(a) {
            return singleTitle;
        }

        public void setSingleTitle(String singleTitle) {
            this.singleTitle = singleTitle;
        }

        public String getSingleURL(a) {
            return singleURL;
        }

        public void setSingleURL(String singleURL) {
            this.singleURL = singleURL; }}/** ** class */
    public static class At{

        /** * whether to notify all */
        private boolean atAll;

        /** * need @ phone number array */
        private List<String> atMobiles;

        public boolean isAtAll(a) {
            return atAll;
        }

        public void setAtAll(boolean atAll) {
            this.atAll = atAll;
        }

        public List<String> getAtMobiles(a) {
            return atMobiles;
        }

        public void setAtMobiles(List<String> atMobiles) {
            this.atMobiles = atMobiles; }}/** ** Syntax format for sending markDown * msgType String Indicates the message type, which is fixed as markdown. * Title String is the content displayed in the first screen session. * Text String is a message in Markdown format. * atMobiles Array Specifies the user's mobile phone number. Note that the @ person's mobile phone number is included in the text. * isAtAll Boolean No Yes @all. * /
    public static class MarkDown{

        private String title;

        private String text;

        public String getTitle(a) {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getText(a) {
            return text;
        }

        public void setText(String text) {
            this.text = text; }}/** * msgType String is the message type, which is fixed to link. The title String is the message title. The text String is the message content. If it's too long it's only partially shown. MessageUrl String is the URL to click the message to jump to. PicUrl String No Image URL. * /
    public static class Link{

        private String text;

        private String messageUrl;

        private String picUrl;

        private String title;

        public String getText(a) {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public String getMessageUrl(a) {
            return messageUrl;
        }

        public void setMessageUrl(String messageUrl) {
            this.messageUrl = messageUrl;
        }

        public String getPicUrl(a) {
            return picUrl;
        }

        public void setPicUrl(String picUrl) {
            this.picUrl = picUrl;
        }

        public String getTitle(a) {
            return title;
        }

        public void setTitle(String title) {
            this.title = title; }}/** * Pin request: plain text */
    public static class Text{

        /** * text request content */
        private String content;

        public String getContent(a) {
            return content;
        }

        public void setContent(String content) {
            this.content = content; }}@Override
    public void setMsgType(String msgtype) {
        this.msgtype = msgtype;
    }

    @Override
    public void setText(Text text) {
        this.text = text;
    }

    @Override
    public void setLink(Link link) {
        this.link = link;
    }

    @Override
    public void setMarkDown(MarkDown markDown) {
        this.markdown = markDown;
    }

    @Override
    public void setActionCard(ActionCard actionCard) {
        this.actionCard = actionCard;
    }

    @Override
    public void setFeedCard(FeedCard feedCard) {
        this.feedCard = feedCard;
    }

    public At getAt(a) {
        return at;
    }

    public void setAt(At at) {
        this.at = at;
    }

    public String getMsgtype(a) {
        return msgtype;
    }

    public Text getText(a) {
        return text;
    }

    public Link getLink(a) {
        return link;
    }

    public MarkDown getMarkdown(a) {
        return markdown;
    }

    public ActionCard getActionCard(a) {
        return actionCard;
    }

    public FeedCard getFeedCard(a) {
        returnfeedCard; }}Copy the code

Episode: While generating the specific spike corresponding request object, we build a corresponding interface

/ * * *@author zxd
 * @versionV1.0.0 *@Package : com.zxd.interview.dingrobot
 * @Description: allows the interface to send pin requests *@Create on : 2021/2/7 11:45
 **/
public interface DingRobotRequestAble {

    /** * All subclasses need to integrate this interface *@return* /
    void setMsgType(String msgType);

    /** * Plain text type *@param text
     */
    void setText(DingRobotRequestBody.Text text);

    /** * link type *@param link
     */
    void setLink(DingRobotRequestBody.Link link);

    /** * Markdown type *@param markDown
     */
    void setMarkDown(DingRobotRequestBody.MarkDown markDown);

    /** * Jump to ActionCard type *@param actionCard
     */
    void setActionCard(DingRobotRequestBody.ActionCard actionCard);

    /** * FeedCard type *@param feedCard
     */
    void setFeedCard(DingRobotRequestBody.FeedCard feedCard);

}
Copy the code

Build the nail request utility class

After introduce the auxiliary object all above, we set out to build the core of nailing request tools, certain request tool class contains the basic steps, provide foreign request method, the caller can, according to the request object to construct the corresponding parameters from the code below you can see the core method is notifyRobot this method, This method is very simple, and the internal logic is divided into the following steps:

  • Build the request environment parameters
  • Build the URL of the request and the corresponding carrying parameters
  • Build the specific request parameters
  • Converts the JSON string returned by the request
/ * * *@author zxd
 * @versionV1.0.0 *@Package : com.dcc.common.utils
 * @Description: Nail robot tools *@Create on : 2021/2/4 00:11
 **/
public class DingRobotUtils {

    private static final Logger LOGGER = LoggerFactory.getLogger(DingRobotUtils.class);

    public static DingRobotResponseMsg notifyRobot(DingRobotRequest dingRobotRequest, long currentTimeMillis) throws Exception {
        Map<String, Object> param = buildParam(dingRobotRequest, currentTimeMillis);
        String s = buildParamUrl(param);
        // The pinned request parameters need to be concatenated to the URL link
        dingRobotRequest.setUrl(String.format("%s? %s", dingRobotRequest.getUrl(), s));
        HttpConfig httpConfig = buildDefaultHttpConfig(dingRobotRequest, dingRobotRequest.getMsg());
        return parseResponse(notifyRobot(httpConfig));
    }

    /** * is converted to the corresponding object **@paramNotifyRobot converts JSON *@return* /
    private static DingRobotResponseMsg parseResponse(String notifyRobot) {
        try {
            return JSON.parseObject(notifyRobot, DingRobotResponseMsg.class);
        } catch (Exception e) {
            LOGGER.error("Type conversion failed because :{}", e.getMessage());
            throwe; }}/** * notifications are made according to the custom timestamp **@paramDingRobotRequest Nailing robot request *@throws Exception
     */
    public static DingRobotResponseMsg notifyRobot(DingRobotRequest dingRobotRequest) throws Exception {
        long currentTimeMillis = System.currentTimeMillis();
        return notifyRobot(dingRobotRequest, currentTimeMillis);
    }


    /** * Build request environment parameters **@paramDingRobotRequest request request *@paramCurrentTimeMillis Current timestamp *@return
     * @throws Exception
     */
    private static Map<String, Object> buildParam(DingRobotRequest dingRobotRequest, long currentTimeMillis) throws Exception {
        Map<String, Object> param = new HashMap<>(3);
        param.put("access_token", dingRobotRequest.getAccessToken());
        param.put("timestamp", currentTimeMillis);
        param.put("sign", generateSign(currentTimeMillis, dingRobotRequest.getSecret()));
        return param;
    }

    /** * The default configuration of the pin robot **@paramDingRobotRequest Nailing robot requests objects *@paramDingRobotRequestMsg Nails the robot request entity *@return* /
    private static HttpConfig buildDefaultHttpConfig(DingRobotRequest dingRobotRequest, DingRobotRequestAble dingRobotRequestMsg) {
        return HttpConfig.custom().headers(defaultBasicHeader())
                .url(dingRobotRequest.getUrl())
                .encoding("UTF-8")
                .method(HttpMethods.POST)
                .json(JSON.toJSONString(dingRobotRequestMsg));
    }

    /** * Default headers configuration **@return* /
    private static Header[] defaultBasicHeader() {
        Header[] headers = new Header[1];
        headers[0] = new BasicHeader("Content-Type"."application/json");
        return headers;
    }

    private static String notifyRobot(HttpConfig httpConfig) throws Exception {
        String send = "";
        try {
            send = HttpClientUtil.send(httpConfig);
        } catch (Exception e) {
            LOGGER.error("HTTPClient failed to send request because :{}", e.getMessage());
            throw e;
        }
        return send;
    }

    /** * Generates a signature based on the timestamp and key **@paramTimestamp Indicates the time *@paramSecret secret key *@return
     * @throws Exception
     */
    private static String generateSign(Long timestamp, String secret) throws Exception {
        String stringToSign = timestamp + "\n" + secret;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        return URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
    }

    /** * Build the URL argument **@paramParam requests the MAP parameter *@return* /
    private static String buildParamUrl(Map<String, Object> param) {
        if (null == param || param.size() == 0) {
            return "";
        }
        StringBuilder stringBuilder = new StringBuilder();
        param.forEach((key, value) -> {
            stringBuilder.append(key).append("=").append(value);
            stringBuilder.append("&");
        });
        stringBuilder.deleteCharAt(stringBuilder.length() - 1);
        returnstringBuilder.toString(); }}Copy the code

Here is how the utility class is used. Simply pass in the environment parameters and the required request MSG to send the request directly and return the corresponding result.

 DingRobotRequest.Builder builder = new DingRobotRequest.Builder();
DingRobotRequest build = builder.secret("SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc")
    .url("https://oapi.dingtalk.com/robot/send")
    .accessToken("381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050")
    .msg(generateActionCard()).build();
try {
    DingRobotResponseMsg dingRobotResponseMsg = DingRobotUtils.notifyRobot(build);
    System.err.println(JSON.toJSONString(dingRobotResponseMsg));
} catch (Exception e) {
    e.printStackTrace();
}
Copy the code

At this point, a tool class build is complete, and the whole process is very simple. This tool code is also the result of constant small changes. Personal code level is limited, if you have any comments welcome comments.

Summary of problems:

Here is a summary of some of the points where individuals spend a lot of time using nails.

Joke: In fact, I feel that The error code of Dingding robot is not particularly intuitive. Here are some small pits that I stepped on.

About the checker test robot appears31000The problem of

If add the time for the signature of the robot is need to add the corresponding sign and the timestamp parameter can test is successful, personal card here for a while to understand the intention of the designers, although is easy to understand, but for the first time use is not very friendly people, at the same time inside the document description of this block is less obviously, Here are some personal tips:

First, we need to generate a signature based on the requested timestamp and secret key

./** * Builds the current system timestamp */
@Test
public void generateSystemCurrentTime(a) throws Exception {
    long l = System.currentTimeMillis();
    String secret = "SEC2e67120c5e4affa1177ac25fe8dc77ba1c5b49284a9dc7e1888770bc3b76b1fc";
    String sign = generateSign(l, secret);
    System.out.println("timestamp = "+ l);
    System.out.println("sign = " + sign);
}

private String generateSign(Long timestamp, String secret) throws Exception {
    String stringToSign = timestamp + "\n" + secret;
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
    byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
    return URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8"); }...Copy the code

After generating the signature, we need to put the timestamp and signature in the request URL parameter to pass the test:

https://oapi.dingtalk.com/robot/send?access_token=381c2f405e0f906fd556b27cea9f66864120860b5d8b117bb046e10b6599b050&times tamp=1613212722591&sign=SsKKlkvwM%2F4tsCPE6YoGls8vgkQqWJGHYpvWbW7hTGM%3DCopy the code

Tip: note that checkmarks have been added to Settings

At the end

This article mainly for the record of personal use of nails some experience, as well as the preparation of a tool kit to facilitate the need of the future can be directly used.

The use of the nail robot is over, and now the utility class has been applied to the normal sending request notification of the company’s projects. HttpClient request tool class refactoring, but currently I am still referring to and learning the design record, found that can be split objects or many. Contains request methods, request headers, request encoding, and other forms of transformation.

Finally, personal recently from the code concise way * * * * in learning a lot of useful programming skills and writing code details, recommend readers to take a look at this book, to write a good code and good comments or want to learn to improve their code is good, the subsequent will write a individual notes, can focus on a wave of interest.