跳至主要內容

RestTemplate

chanchaw大约 5 分钟javaspring

概述

微信后台使用自己制作的 httpUtils 发送模板消息在 windows 服务器上会有中文乱码的问题,使用 spring 自带的 RestTemplate 可以解决

案例

依赖

springboot 项目的依赖项 spring-boot-starter-web 中自带,还有 Jackson

代码

在项目根目录中创建目录 config 作为所有配置类的根目录,并创建下面配置类

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);
        factory.setConnectTimeout(15000);
        return factory;
    }
}

使用案例

@SpringBootTest
class DemoApplicationTests {

    @Resource
    private RestTemplate restTemplate;

    @Test
    void getTest() {
        String str = restTemplate.getForObject("http://localhost:8888/get?name=zs", String.class);
        System.out.println(str);
    }

    @Test
    void getRestTest() {
        String name = "ls";
        String str = restTemplate.getForObject("http://localhost:8888/getName/" + name, String.class);
        System.out.println(str);
    }

    @Test
    void postTest() {
        LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.set("name", "zs");
        String str = restTemplate.postForObject("http://localhost:8888/post", map, String.class);
        System.out.println(str);
    }

    @Test
    void postBodyTest() {
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());
        HashMap<String, Object> map = new HashMap<>();
        map.put("name", "zs");
        map.put("age", 23);
        String stu = JSON.toJSONString(map);
        HttpEntity<String> formEntity = new HttpEntity<String>(stu, headers);
        String str = restTemplate.postForObject("http://localhost:8888/postBody", formEntity, String.class);
        System.out.println(str);
    }

    @Test
    void putTest() {
        restTemplate.put("http://localhost:8888/put?name=zs", null);
    }

    @Test
    void deleteTest() {
        restTemplate.delete("http://localhost:8888/delete?name=zs");
    }
    
    // 返回带有泛型的类型,如下代码,请求后返回的类型是:JuheRes<DayInfo>
    // 初次使用在 showabe 项目中 HolidayUtils # getDayInfo 方法中
    void generalType(){
        ResponseEntity<JuheRes<DayInfo>> response = restTemplate.exchange(
                url, HttpMethod.GET, null,
                new ParameterizedTypeReference<JuheRes<DayInfo>>() {}
        );
        JuheRes<DayInfo> res = response.getBody();
    }
    
}

自己试验的代码

@Resource
private RestTemplate restTemplate;

public String rtPost(String url, String params) throws JsonProcessingException {
    HttpHeaders headers = new HttpHeaders();
    MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
    headers.setContentType(type);
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "zs");
    map.put("age", 23);

    ObjectMapper mapper = new ObjectMapper();
    ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
    String stu = ow.writeValueAsString(map);

    HttpEntity<String> formEntity = new HttpEntity<String>(params, headers);
    String str = restTemplate.postForObject(url, formEntity, String.class);
    return str;
}

初次应用在样品管理微信后台项目 https://gitee.com/chanchaw/wechatbetradeopen in new window 的方法 HttpUtils # rtPost

封装工具

根据上面介绍配置 RestTemplate 后做了如下封装工具

package com.xdf.wxpad.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.Map;

/**
 * @author chanchaw
 * @create 2024-09-02 8:58
 */
@Component
public class RestTemplateUtils {
    private static final ObjectMapper mapper = new ObjectMapper();
    @Resource
    private RestTemplate restTemplate;

    public <T> T postJson(String url, Map<String, Object> param, Class<T> responseType) throws JsonProcessingException {
        HttpHeaders headers = new HttpHeaders();
        MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
        headers.setContentType(type);
        headers.add("Accept", MediaType.APPLICATION_JSON.toString());

        ObjectMapper mapper = new ObjectMapper();
        ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
        String paramJsonString = ow.writeValueAsString(param);

        HttpEntity<String> formEntity = new HttpEntity<>(paramJsonString, headers);
        String responseJsonString = restTemplate.postForObject(url, formEntity, String.class);

        return mapper.readValue(responseJsonString, responseType);
    }
    
    /**
     * post方法发送 formData 数据的请求,使用案例:
     *  MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
     *  map.put("appid", new ArrayList<>(Arrays.asList(DevMaterial.VISITOR_MINI.APPID)));
     *  map.put("secret", new ArrayList<>(Arrays.asList(DevMaterial.VISITOR_MINI.APPSECRET)));
     *  map.put("js_code", new ArrayList<>(Arrays.asList(code)));
     *  map.put("grant_type", new ArrayList<>(Arrays.asList("authorization_code")));
     *  MiniGetUserProfileRes res = restTemplateUtils.postForm("https://xxx", map,MiniGetUserProfileRes.class);
     * 初次使用在项目 https://gitee.com/chanchaw/visitor 中的 WechatUtils.java # getUserProfile 中
     * @param url
     * @param param
     * @param responseType
     * @return
     * @param <T>
     * @throws JsonProcessingException
     */
    public <T> T postForm(String url, MultiValueMap<String, Object> param, Class<T> responseType) throws JsonProcessingException 	{
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, Object>> reqParam = new HttpEntity<>(param, headers);
        ResponseEntity<String> res = restTemplate.postForEntity(url, reqParam, String.class);
        String bodyString = res.getBody();
        return mapper.readValue(bodyString, responseType);
    }
}

调用方法

@Autowired
private RestTemplateUtils restTemplateUtils;

@PostMapping("/postJsonFieldInfo")
public JsonResult postJsonFieldInfo() throws JsonProcessingException {
    PostJson postJson = new PostJson();
    postJson.setUrl("https://染厂云域名/zknoteb/dbManager/fieldInfoList");
    postJson.setRetClass(ZkRes.class);
    HashMap<String, Object> map = new HashMap<>();
    HashMap<String, Object> dbConfig = new HashMap<>();
    dbConfig.put("dbType","mysql");
    dbConfig.put("ip","49.234.149.119");
    dbConfig.put("port","16033");
    dbConfig.put("userName","root");
    dbConfig.put("passWord","!XDFserver20211001@");
    dbConfig.put("dbName","zhike");
    map.put("dbConfig",dbConfig);
    map.put("dbName","zhike");
    map.put("tableName","z_thing");
    ZkRes res = restTemplateUtils.postJson(postJson.getUrl(), map, ZkRes.class);
    return JsonResult.ok(res);
}

错误与提示

响应实体对象反序列化失败

import Exc.NotAllowedErrorException;
import Exc.RunTimeErrorException;
import account.entity.*;
import account.entity.smartwarehouse.ResponseSendYFH;
import account.entity.smartwarehouse.SmartWarehouseYFH;
import account.entity.smartwarehouse.SmartWarehouseYFHPallet;
import account.enums.Response;
import account.service.GConfigOnevarService;
import account.service.PalletSWService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@RequestMapping(value = "/sendYFH2SmartWarehouse", method = RequestMethod.POST)
public ResponseSendYFH sendYFH2SmartWarehouse(@RequestBody SmartWarehouseYFH record) throws Exception {
    GConfigOnevar configOnevar = configOnevarService.findById("WEB应用_推送预发货单给立库系统");
    if(configOnevar == null) throw new RunTimeErrorException("请在配置文件中设置【WEB应用_推送预发货单给立库系统】对应的URL");
    String url = Optional.ofNullable(configOnevar.getbValue()).orElse("");
    if(url.length() == 0) throw new RunTimeErrorException("请在配置文件中设置【WEB应用_推送预发货单给立库系统】对应的URL");

    if(record == null) throw new RunTimeErrorException("空数据无法发送给立库系统!");
    List<SmartWarehouseYFHPallet> data = record.getData();
    if(data == null || data.size() == 0) throw new RunTimeErrorException("空数据无法发送给立库系统!");

    HttpHeaders headers = new HttpHeaders();
    MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
    headers.setContentType(type);
    headers.add("Accept", MediaType.APPLICATION_JSON.toString());

    ObjectMapper mapper = new ObjectMapper();
    ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
    String jsonData = "";
    try {
        jsonData = ow.writeValueAsString(record);
    } catch (JsonProcessingException e) {
        logger.info("预发货单推送到立库系统时出现异常,推送的参数是:" + record.toString() + ",响应的异常信息是:" + e.getMessage());
        throw new RuntimeException(e);
    }

    HttpEntity<String> formEntity = new HttpEntity(jsonData, headers);
    logger.info("推送预发货单给立库系统,url:" + url + ",推送的参数是:" + record.toString());
    
    // 在白玉兰对接立库系统中,由于对方响应来的结果是 JSON 字符串,而不是 JSON 序列化后的结果,导致下面通过指定 ResponseSendYFH.class
    // 进行反序列化失败,特别为该类制作了 “自定义反序列化器”,看本方法下面的类
    ResponseEntity<ResponseSendYFH> res = restTemplate.postForEntity(url, formEntity, ResponseSendYFH.class);
    ResponseSendYFH responseBody = null;
    if (res.getStatusCode().is2xxSuccessful()) {
        logger.info("推送预发货单给立库系统收到的响应结果:" + res.toString());
        responseBody = res.getBody();
        logger.info("推送预发货单给立库系统收到的响应数据:" + responseBody.toString());
        if (responseBody != null) {
            // 处理成功的响应
            System.out.println("Response: " + responseBody);
        } else {
            System.out.println("Response body is null");
            logger.info("推送预发货单给立库系统收到的响应数据:NULL");
        }
    } else {
        logger.info("推送预发货单给立库系统收到失败响应码:: " + res.getStatusCode());
    }
    logger.info("推送预发货单给立库系统封装收到的响应结果即将返回给前端,数据是:" + responseBody.toString());
    return responseBody;
}

自定义反序列化器

package account.config;

import account.entity.smartwarehouse.ResponseSendYFH;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class ResponseSendYFHDeserializer extends JsonDeserializer<ResponseSendYFH> {
    @Override
    public ResponseSendYFH deserialize(JsonParser jsonParser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        System.out.println(node);

        // 由于对方响应来的是字符串,所以需要再次通过 readTree 读取为 JsonNode
        // 没有下面 if 判断直接 node.get("success") 得到的是空
        // 如果node是一个字符串而不是对象,我们需要先解析字符串
        if (node.isTextual()) {
            // 再次解析字符串内容
            ObjectMapper mapper = new ObjectMapper();
            node = mapper.readTree(node.asText());
        }

        JsonNode success1 = node.get("success");
        System.out.println(success1);
        boolean success = success1.asBoolean();
        System.out.println(success);

        JsonNode message1 = node.get("message");
        System.out.println(message1);
        String message = message1.asText();
        System.out.println(message);

        // 根据你的需求自定义反序列化逻辑
        return new ResponseSendYFH(success, message);
    }
}