当前位置:   article > 正文

Java中调用接口

Java中调用接口

一、微服务本地调用feign

Feign 是一个在 Java 平台上的声明式、模板化的 HTTP 客户端,它简化了使用 HTTP API 的过程。Feign 是 Netflix 开发的,旨在简化基于 HTTP 的 API 客户端开发。

1、特点

1.1:声明式 API 定义: 通过接口和注解的方式定义 API,使得 API 定义更加清晰和简洁。
1.2:集成 Ribbon: Feign 默认集成了 Ribbon 负载均衡器,可以轻松实现客户端的负载均衡。
1.3:集成 Hystrix: Feign 也可以集成 Hystrix,从而实现客户端的容错和断路器功能。
1.4:支持多种编码器和解码器: Feign 支持多种编码器和解码器,包括 JSON、XML 等。
1.5:支持动态 URL 和查询参数: 可以在接口方法中直接使用参数来构建 URL 和查询参数。

2、代码实例

2.1、添加maven支持
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
2.2、在服务启动类添加注解
@Slf4j//日志log
@SpringBootApplication//用于标识 Spring Boot 应用程序的注解
@EnableFeignClients//开启feign
@MapperScan("com.netrust.*.dao")//扫描包下的mapper
@EnableTransactionManagement//开启事务注解
public class BaseApplication {
    public static void main(String[] args) {
        SpringApplication.run(BaseApplication.class, args);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
2.3、编写feign类
@FeignClient(value = ServerNameConstants.SYSTEM_BASE, contextId = "room", configuration = FeignConfiguration.class)
public interface FeignApi {
    @PostMapping("/add")
    String addSysApi(@RequestBody FeignBO feignBO);
    @PutMapping("/edit")
    String editSysApi(@RequestBody FeignBO feignBO);
    @DeleteMapping("/delete")
    String deleteByIdSysApi(@RequestBody FeignDto feignDto);
    @GetMapping("/queryById")//@SpringQueryMap表示get可以用对象接收,不然就需要一个一个接收
    String queryById(@SpringQueryMap FeignDto feignDto);
    @RequestMapping(value = "file/personPhotoUpload", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    String personPhotoUpload(@RequestPart(value = "file") MultipartFile file,
                             @RequestParam(value = "moduleName") String moduleName,
                             @RequestParam(value = "functionName") String functionName,
                             @RequestParam(value = "needCompress") Boolean needCompress);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
2.4、调用服务
@Resource
private FeignApi feignApi;
/**
* 通过主键查询单条数据
*/
@GetMapping("/feign/{id}")
@ApiOperation(value = "通过主键查询单条数据")
public ResultJson queryById( FeignDto feignDto, @PathVariable("id") Long id) {
   feignDto.setId(id);
   String s = feignApi.queryById(feignDto);
   return JSONObject.parseObject(s, ResultJson.class);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
2.5、@FeignClient内部参数详解

2.5.1、value: 指定要调用的目标服务的服务名。可以是一个单独的服务名,也可以是多个服务名组成的数组。
2.5.2、url: 指定要调用的目标服务的URL地址,可以是一个完整的URL地址,也可以是占位符形式的URL地址。
2.5.3、path: 指定要调用的目标服务的基本路径,可以是一个字符串,也可以是一个占位符形式的字符串。
2.5.4、configuration: 指定要使用的Feign客户端配置类。可以自定义配置Feign客户端的行为。
2.5.5、decode404: 指定是否将404错误解码。默认情况下,Feign会将404错误解码为null值。
2.5.6、fallback: 指定一个回退类,当调用失败时,将调用回退类中的方法。回退类必须实现@FeignClient注解中的接口。
2.5.7、fallbackFactory: 指定一个回退工厂类,用于创建回退类的实例。回退工厂类必须实现FallbackFactory接口。
2.5.8、primary: 指定Feign客户端是否为首选客户端。如果设置为true,则优先使用该客户端。
2.5.9、qualifier: 指定Feign客户端的限定符。可以与@Autowired注解配合使用,用于指定要注入的Feign客户端实例。
2.5.10、name: 同value,指定要调用的目标服务的服务名。
2.5.11、contextId: 指定Feign客户端的上下文ID。用于区分不同的Feign客户端实例。
2.5.12、url: 指定要调用的目标服务的URL地址。

二、HttpClient

1、工具类

@Slf4j
public class HttpClientUtils {
    /**
     * 文件上传到第三方
     * @param url 网络地址
     * @param inputStream 文件流
     * @param headers 头文件(认证信息设置)
     * @param verification 是否需要验证
     * @return
     * @throws IOException
     */
    public static String uploadFile(String url, InputStream inputStream, Map<String, String> headers, boolean verification) throws IOException {
        HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
        HttpPost httpPost = new HttpPost(url);
        //设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
        httpPost.setConfig(requestConfig);

        if (headers != null) {
            headers.forEach((key, value) -> {
                httpPost.setHeader(key, value);
            });
        }
        //创建文件把流文件写入到字节内
        File file = File.createTempFile(UUID.randomUUID().toString(), ".jpg");
        FileOutputStream os = new FileOutputStream(file);

        int read = 0;
        byte[] buffer = new byte[1024];
        while ((read = inputStream.read(buffer, 0, 1024)) != -1) {
            os.write(buffer, 0, read);
        }
        inputStream.close();
        //发送邮件
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
        FileBody fileBody = new FileBody(file, ContentType.MULTIPART_FORM_DATA);
        builder.addPart("file", fileBody);
        httpPost.setEntity(builder.build());

        HttpResponse response = httpClient.execute(httpPost);
        String httpEntityContent = getHttpEntityContent(response);
        httpPost.abort();
        return httpEntityContent;
    }

    /**
     * 封装HTTP POST方法
     *
     * @param verification TRUE为不跳过证书检测  FALSE为跳过证书检测
     * @param (如JSON串)
     * @return
     * @throws ClientProtocolException
     * @throws IOException
     */
    public static String post(String url, String body, Map<String, String> headers, boolean verification) throws ClientProtocolException, IOException {
        HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
        HttpPost httpPost = new HttpPost(url);
        //设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
        httpPost.setConfig(requestConfig);
        httpPost.setHeader("Content-Type", "application/json");
        if (headers != null) {
            headers.forEach((key, value) -> {
                httpPost.setHeader(key, value);
            });
        }
        if (body != null) {
            StringEntity s = new StringEntity(body, StandardCharsets.UTF_8);
            s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
            //设置参数到请求对象中
            httpPost.setEntity(s);
        }

        HttpResponse response = httpClient.execute(httpPost);
        String httpEntityContent = getHttpEntityContent(response);
        httpPost.abort();
        return httpEntityContent;
    }

    public static String put(String url, String body, Map<String, String> headers, boolean verification) throws ClientProtocolException, IOException {
        HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
        HttpPut httpPut = new HttpPut(url);
        //设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
        httpPut.setConfig(requestConfig);
        httpPut.setHeader(HTTP.CONTENT_TYPE, "application/json");
        if (headers != null) {
            headers.forEach((key, value) -> {
                httpPut.setHeader(key, value);
            });
        }
        if (body != null) {
            StringEntity s = new StringEntity(body, StandardCharsets.UTF_8);
            s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
            //设置参数到请求对象中
            httpPut.setEntity(s);
        }
        HttpResponse response = httpClient.execute(httpPut);
        String httpEntityContent = getHttpEntityContent(response);
        httpPut.abort();
        return httpEntityContent;
    }

    public static String delete(String url, String body, Map<String, String> headers, boolean verification) throws ClientProtocolException, IOException {
        HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
        HttpDeleteWithBody httpDelete = new HttpDeleteWithBody(url);
        //设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
        httpDelete.setConfig(requestConfig);
        httpDelete.setHeader(HTTP.CONTENT_TYPE, "application/json");
        if (headers != null) {
            headers.forEach((key, value) -> {
                httpDelete.setHeader(key, value);
            });
        }
        if (body != null) {
            StringEntity s = new StringEntity(body, StandardCharsets.UTF_8);
            s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
            //设置参数到请求对象中
            httpDelete.setEntity(s);
        }
        HttpResponse response = httpClient.execute(httpDelete);
        String httpEntityContent = getHttpEntityContent(response);
        httpDelete.abort();
        return httpEntityContent;
    }

    /**
     * 封装HTTP GET方法
     *
     * @param
     * @param verification TRUE为不跳过证书检测  FALSE为跳过证书检测
     * @return
     * @throws ClientProtocolException
     * @throws IOException
     */
    public static String get(String url, Map<String, Object> params, Map<String, String> headers, boolean verification) throws ClientProtocolException, IOException {
        HttpClient httpClient = verification ? HttpClients.createDefault() : wrapClient();
        HttpGet httpGet = new HttpGet();
        //设置请求和传输超时时间
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build();
        httpGet.setConfig(requestConfig);
        if (headers != null) {
            headers.forEach((key, value) -> {
                httpGet.addHeader(key, value);
            });
        }
        if (params != null) {
            url = getUrlByParams(url, params);
        }
        httpGet.setURI(URI.create(url));
        HttpResponse response = httpClient.execute(httpGet);
        String httpEntityContent = getHttpEntityContent(response);
        httpGet.abort();
        return httpEntityContent;
    }

    private static String getUrlByParams(String url, Map<String, Object> params) {
        StringBuffer sb = new StringBuffer();
        params.forEach((key, value) -> {
            sb.append("&" + key + "=" + value);
        });
        if (sb.length() > 0) {
            String substring = sb.substring(1);
            url += url.contains("?") ? "&" : "?" + substring;
        }
        return url;
    }

    /**
     * 获得响应HTTP实体内容
     *
     * @param response
     * @return
     * @throws IOException
     * @throws UnsupportedEncodingException
     */
    private static String getHttpEntityContent(HttpResponse response) throws IOException, UnsupportedEncodingException {
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream is = entity.getContent();
            BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            String line = br.readLine();
            StringBuilder sb = new StringBuilder();
            while (line != null) {
                sb.append(line + "\n");
                line = br.readLine();
            }
            return sb.toString();
        }
        return null;
    }

    public static HttpClient wrapClient() {
        HttpClients.createDefault();
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager trustManager = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                }

                public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                }
            };
            ctx.init(null, new TrustManager[]{trustManager}, null);
            SSLConnectionSocketFactory ssf = new SSLConnectionSocketFactory(ctx, NoopHostnameVerifier.INSTANCE);
            return HttpClients.custom()
                    .setSSLSocketFactory(ssf)
                    .build();
        } catch (Exception e) {
            return HttpClients.createDefault();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218

2、调用解析

try {
    HashMap<String, String> map = new HashMap<>();
    map.put("app_id",appid);
    map.put("app_secret",appSecret);
    map.put("grant_type","client_credentials");

    String res = HttpClientUtils.post(host + "/oauth/token", JSON.toJSONString(map), null, false);
    JSONObject jsonObject = JSONObject.parseObject(res);
    if(jsonObject.containsKey("code") && jsonObject.getString("code").equals("0")){
        return JSONObject.parseObject(jsonObject.getString("result"),PushToken.class);
    }else{
        throw new ServiceException(res);
    }
}catch (ServiceException e){
    throw new ServiceException(e);
}catch (Exception e){
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

三、路由代理

1、工具类

@Slf4j
@Component
public class RoutingDelegateUtil {

    @Resource
    //@Qualifier(value = "remoteRestTemplate")
    private RestTemplate restTemplate;

    /**
     * 上传form表单,文件
     */
    private final static String CONTENT_TYPE_FORM = "multipart/form-data;";

    /**
     * 请求转发统一处理
     *
     * @param request  原请求对象
     * @param routeUrl 路由地址,统一前缀,重定向目标主机域名(带协议及端口)
     * @param prefix   需要去除的前缀
     * @return
     * @throws Exception
     */
    public ResponseEntity<String> redirect(HttpServletRequest request, String routeUrl, String prefix) throws Exception {
        String contentType = request.getContentType();
        log.info("getContentType={}", contentType);
        // multipart/form-data处理
        if (StringUtils.isNotEmpty(contentType) && contentType.contains(CONTENT_TYPE_FORM)) {
            return redirectFile(request, routeUrl);
        } else {
            return redirect(request, routeUrl, prefix, String.class);
        }
    }

    /**
     * 上传form表单,文件
     * <p>
     * 请求转发处理
     *
     * @param request  原请求对象
     * @param routeUrl 重定向目标主机域名(带协议及端口)
     * @return
     * @throws IOException
     */
    public ResponseEntity<String> redirectFile(HttpServletRequest request, String routeUrl) throws IOException {
        // build up the redirect URL
        String redirectUrl = createRedictUrl(request, routeUrl, "");
        log.info("redirectFile redirectUrl={}", redirectUrl);
        String method = request.getMethod();
        //设置请求头
        MultiValueMap<String, String> headers = parseRequestHeader(request);

        // 组装form参数
        MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
        StandardMultipartHttpServletRequest standardMultipartHttpServletRequest = (StandardMultipartHttpServletRequest) request;
        // 组装form参数-文件
        MultiValueMap<String, MultipartFile> multiValueMap = standardMultipartHttpServletRequest.getMultiFileMap();
        for (Map.Entry<String, List<MultipartFile>> entries : multiValueMap.entrySet()) {
            for (MultipartFile multipartFile : entries.getValue()) {
                String fileName = multipartFile.getOriginalFilename();
                log.info("redirectFile MultipartFile: fileName={}", fileName);
                File file = File.createTempFile("spw-", fileName);
                multipartFile.transferTo(file);
                FileSystemResource fileSystemResource = new FileSystemResource(file);
                form.add(entries.getKey(), fileSystemResource);
            }
        }
        // 组装form参数-一般属性
        Enumeration<String> enumeration = standardMultipartHttpServletRequest.getParameterNames();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = standardMultipartHttpServletRequest.getParameter(name);
            log.info("redirectFile enumeration: name={}, value={}", name, value);
            form.add(name, value);
        }

        // 用HttpEntity封装整个请求报文
        HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(form, headers);

        return restTemplate.exchange(redirectUrl, HttpMethod.valueOf(method), formEntity, String.class);
    }

    /**
     * 非form-data请求转发处理
     *
     * @param request  原请求对象
     * @param routeUrl 重定向目标主机域名(带协议及端口)
     * @param prefix    需要去除的前缀
     * @param clazz 结果类型
     * @return
     * @throws Exception
     */
    public <T> ResponseEntity<T> redirect(HttpServletRequest request, String routeUrl, String prefix, Class<T> clazz) throws Exception {
        // build up the redirect URL
        String redirectUrl = createRedictUrl(request, routeUrl, prefix);
        log.info("redirectUrl={}", redirectUrl);
        RequestEntity requestEntity = createRequestEntity(request, redirectUrl);
        return restTemplate.exchange(requestEntity, clazz);
    }

    /**
     * 构建重定向地址
     *
     * @param request  原请求对象
     * @param routeUrl 重定向目标主机域名(带协议及端口)
     * @param prefix   需要去除的前缀
     * @return
     */
    private String createRedictUrl(HttpServletRequest request, String routeUrl, String prefix) {
        String queryString = request.getQueryString();
        return routeUrl + request.getRequestURI().substring(1).replace(prefix, "") +
                (queryString != null ? "?" + queryString : "");
    }

    /**
     * 构建请求实体
     *
     * @param request 原请求对象
     * @param url     新目标路由URL
     * @return
     * @throws URISyntaxException
     * @throws IOException
     */
    private RequestEntity createRequestEntity(HttpServletRequest request, String url) throws URISyntaxException, IOException {
        String method = request.getMethod();
        HttpMethod httpMethod = HttpMethod.resolve(method);
        MultiValueMap<String, String> headers = parseRequestHeader(request);
        byte[] body = parseRequestBody(request);
        return new RequestEntity<>(body, headers, httpMethod, new URI(url));
    }

    /**
     * 解析请求体
     *
     * @param request
     * @return
     * @throws IOException
     */
    private byte[] parseRequestBody(HttpServletRequest request) throws IOException {
        InputStream inputStream = request.getInputStream();
        return StreamUtils.copyToByteArray(inputStream);
    }

    /**
     * 解析请求头
     *
     * @param request
     * @return
     */
    private MultiValueMap<String, String> parseRequestHeader(HttpServletRequest request) {
        HttpHeaders headers = new HttpHeaders();
        List<String> headerNames = Collections.list(request.getHeaderNames());
        for (String headerName : headerNames) {
            List<String> headerValues = Collections.list(request.getHeaders(headerName));
            for (String headerValue : headerValues) {
                headers.add(headerName, headerValue);
            }
        }
        return headers;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160

2、工具类调用

@RequestMapping("/sys-monitor/**")
    @Description("服务监控分发")
    public ResultJson transmit(HttpServletRequest request, @RequestParam(value = "ip", required = false, defaultValue = "127.0.0.1") String ip) throws Exception {
        String routeUrl = "http://" + ip + ":端口";
        ResponseEntity<ResultJson> responseEntity = restRoute.redirect(request, routeUrl, "sys-monitor", ResultJson.class);
        return responseEntity.getBody();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/569919
推荐阅读
相关标签
  

闽ICP备14008679号