当前位置:

首页 > Java服务RestTemplate与HttpClient如何使用

Java服务RestTemplate与HttpClient如何使用

概述常见的远程调用方式有以下2种:RPC:RemoteProduceCall远程过程调用,类似的还有RMI(remotemethodinvoke)。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表。Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合

概述

常见的远程调用方式有以下2种:

  • RPC: Remote Produce Call远程过程调用,类似的还有RMI(remote method invoke)。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表。

  • Http: http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。 现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。现在热门的Rest风格,就可以通过http协议来实现。

如果项目全部采用 Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。

如果项目的技术栈多样化,主要采用了Spring和SpringBoot框架,那么SpringCloud搭建微服务是不二之选,使用Http方式来实现服务间调用。

java开发中,使用http连接,访问第三方网络接口,通常使用的连接工具为RestTemplate、HttpClient和OKHttp。

RestTemplate

概述及依赖

HttpClient和OKHttp两种连接工具,使用起来比较复杂,如果使用spring框架,可以使用restTemplate来进行http连接请求。

restTemplate默认的连接方式是java中的HttpConnection,可以使用ClientHttpRequestFactory指定不同的HTTP连接方式。

依赖


    org.springframework
    spring-web
    5.2.2.RELEASE


    org.apache.httpcomponents
    httpclient
    4.5.7

配置类

基础配置

@Configuration
public class RestTemplateConfig {
   @Bean
   public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
       return new RestTemplate(factory);
   }
   @Bean
   public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
       SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
       factory.setReadTimeout(150 * 1000); // ms
       factory.setConnectTimeout(150 * 1000); // ms
       return factory;
   }
}

进阶配置

import org.apache.http.client.HttpClient;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
    /**
     * http连接管理器
     */
    @Bean
    public HttpClientConnectionManager poolingHttpClientConnectionManager() {
        /*// 注册http和https请求
        Registry registry = RegistryBuilder.create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);*/
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        // 最大连接数
        poolingHttpClientConnectionManager.setMaxTotal(500);
        // 同路由并发数(每个主机的并发)
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100);
        return poolingHttpClientConnectionManager;
    }
    /**
     * HttpClient
     * @param poolingHttpClientConnectionManager
     */
    @Bean
    public HttpClient httpClient(HttpClientConnectionManager poolingHttpClientConnectionManager) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // 设置http连接管理器
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
        /*// 设置重试次数,默认是3次,没有开启
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true));*/
        // 设置默认请求头
        /*List
 headers = new ArrayList<>();         headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));         headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));         headers.add(new BasicHeader("Accept-Language", "zh-CN"));         headers.add(new BasicHeader("Connection", "Keep-Alive"));         headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));         httpClientBuilder.setDefaultHeaders(headers);*/         return httpClientBuilder.build();     }     /**      * 请求连接池配置      * @param httpClient      */     @Bean     public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {         HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();         // httpClient创建器         clientHttpRequestFactory.setHttpClient(httpClient);         // 连接超时时间/毫秒(连接上服务器(握手成功)的时间,超出抛出connect timeout)         clientHttpRequestFactory.setConnectTimeout(5 * 1000);         // 数据读取超时时间(socketTimeout)/毫秒(服务器返回数据(response)的时间,超过抛出read timeout)         clientHttpRequestFactory.setReadTimeout(10 * 1000);         // 从连接池获取请求连接的超时时间,不宜过长,必须设置/毫秒(超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool)         clientHttpRequestFactory.setConnectionRequestTimeout(10 * 1000);         return clientHttpRequestFactory;     }     /**      * rest模板      */     @Bean     public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {         // 配置请求工厂         return new RestTemplate(clientHttpRequestFactory);     } }

使用

实体类

@Data
@Builder
@NoArgsConstrutor
@AllArgsConstrutor
public class BaseResponse implements Serializable {
    private static final long serialVersionUID  = 1L;
    private String responseCode;
    private String responseMessage;
    private List responseData;
}
@Data
@Builder
@NoArgsConstrutor
@AllArgsConstrutor
public class TempUser implements Serializable {
    private static final long serialVersionUID  = 1L;
    private String userName;   
    private Integer age;
}

GET请求

普通访问

BaseResponse result = restTemplate.getForObject(
    "http://localhost:8080/cs-admin/rest/getUser?userName=张三&age=18", BaseResponse.class);

返回HTTP状态

ResponseEntity responseEntity = restTemplate.getForEntity(
    "http://localhost:8080/cs-admin/rest/getUser?userName=张三&age=18", TempUser.class);
// 获取状态对象
HttpStatus httpStatus = responseEntity.getStatusCode();
// 获取状态码
int statusCodeValue = responseEntity.getStatusCodeValue();
// 获取headers
HttpHeaders httpHeaders = responseEntity.getHeaders();
// 获取body
BaseResponse result = responseEntity.getBody();

映射请求参数

Map paramMap = new HashMap<>();
paramMap.put("userName", "张三");
paramMap.put("age", 18);
BaseResponse result = restTemplate.getForObject(
    "http://localhost:8080/cs-admin/rest/getUser?userName={userName}&age={age}", 
    BaseResponse.class, paramMap);

POST请求

普通访问接口

TempUser param = new TempUser();
param.setUserName("张三");
param.setAge(18);
BaseResponse result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/getPostUser", 
                                             param, BaseResponse.class);

带HEAD访问接口

// 请求头信息
HttpHeaders headers = new HttpHeaders();
//headers.setContentType(MediaType.valueOf("application/json;charset=UTF-8"));
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//headers.add("headParam1", "headParamValue");
// 请求体内容
TempUser param = new TempUser();
param.setUserName("张三");
param.setAge(18);
// 组装请求信息
HttpEntity httpEntity=new HttpEntity<>(param, headers);
BaseResponse result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/getPostUser", 
                                             httpEntity, BaseResponse.class);

无请求体的访问:仅method为post,传参方式仍然为get的param方式

Map paramMap = new HashMap<>();
paramMap.put("userName", "张三");
paramMap.put("age", 18);
BaseResponse result = restTemplate.postForObject(
    "http://localhost:8080/cs-admin/rest/getPostUserNoBody?userName={userName}&age={age}", 
    null, BaseResponse.class, paramMap);
System.out.println(result);

上传文件

后台接口代码:

@RequestMapping("uploadFile")
public TempUser uploadFile(HttpServletRequest request, TempUser form) {
    MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
    //获取文件信息
    MultipartFile multipartFile = multipartHttpServletRequest.getFile("file");
    TempUser tempUser = new TempUser();
    if (multipartFile != null) {
        tempUser.setUserName(form.getUserName() + " " + multipartFile.getOriginalFilename());
    }
    if(form!=null){
        tempUser.setAge(form.getAge());
    }
    return tempUser;
}

访问方式:

// 文件
FileSystemResource file=new FileSystemResource("D:\\Elasticsearch权威指南(中文版).pdf");
// 设置请求内容
MultiValueMap param=new LinkedMultiValueMap<>();
param.add("file", file);
// 其他参数
param.add("userName", "张三");
param.add("age", 18);
// 组装请求信息
HttpEntity> httpEntity=new HttpEntity<>(param);
// 发送请求
TempUser result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/uploadFile", 
                                             httpEntity, TempUser.class);

HttpClient

概述

HttpClient 通过连接池创建连接:

  • 管理连接的基本单位是Route(路由),每个路由上都会维护一定数量的HTTP连接

  • 每次调用后必须执行 releaseConnection

  • 路由可以理解为客户端机器到目标机器的一条线路

  • 如果不给 httpclient配置指定的连接管理器,httpclient会自动使用PoolingHttpClientConnectionManager作为连接管理器。

  • PoolingHttpClientConnectionManager默认的maxConnPerRoute和maxConnTotal分别是是2和20。也就是对于每个服务器最多只会维护2个连接,看起来有点少。所以,在日常使用时我们尽量使用自己配置的连接管理器比较好。

连接池:

  • 连接池技术作为创建和管理连接的缓冲池技术。

  • 连接池管理的对象是长连接

  • 有长连接的优势

**长连接:**是指客户端与服务器端一旦建立连接以后,可以进行多次数据传输而不需重新建立连接,

优势:

  • 省去了每次数据传输连接建立的时间开销

  • 资源的访问控制

**短连接:**每次数据传输都需要客户端和服务器端建立一次连接

使用

使用HttpClient发送请求、接收响应很简单,一般需要如下几步即可:

  • 创建HttpClient对象

  • 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。

  • 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HetpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数,参数则必须用NameValuePair[]数组存储

  • 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse

  • 调用HttpResponse的getAllHeaders()getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponsegetEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容

  • 释放连接。无论执行方法是否成功,都必须释放连接

依赖:


    org.apache.httpcomponents
    httpclient
    4.5.2

    
    org.apache.httpcomponents
    httpclient-cache
    4.5.2

    
    org.apache.httpcomponents
    httpmime
    4.5.2

java工具类

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
 * HttpClient 工具类
 */
@Slf4j
public class HttpClientUtil {
    public static final String APPLICATION_JSON_VALUE = "application/json";
    private static final Logger logger = log;
    private static final Integer CONN_TIME_OUT = 3000;// 超时时间豪秒
    private static final Integer SOCKET_TIME_OUT = 10000;
    /** 每个路由的最大请求数,默认2 */
    private static final Integer DEFAULT_MAX_PER_ROUTE = 40;
    /** 最大连接数,默认20 */
    private static final Integer MAX_TOTAL = 400;
    private static HttpClient httpClient;
    static {
        // 请求配置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(CONN_TIME_OUT)
                .setConnectionRequestTimeout(CONN_TIME_OUT)
                .setSocketTimeout(SOCKET_TIME_OUT)
                .build();
        // 管理 http连接池
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);
        cm.setMaxTotal(MAX_TOTAL);
        httpClient = HttpClients.custom()
            .setConnectionManager(cm)
            .setDefaultRequestConfig(requestConfig)
            .build();
    }
    /**
     * Get请求
     */
    public static String requestGet(String url, Map paramsMap) throws Exception {
        logger.info("GET request  url:{} params:{}", url, paramsMap);
        Long start = System.currentTimeMillis();
        List params = initParams(paramsMap);
        // Get请求
        HttpGet httpGet = new HttpGet(url);
        try {
            // 设置参数
            String str = EntityUtils.toString(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
            String uriStr = StringUtils.isEmpty(str) ? 
                httpGet.getURI().toString() : httpGet.getURI().toString() + "?" + str;
            httpGet.setURI(new URI(uriStr));
            // 发送请求
            HttpResponse response = httpClient.execute(httpGet);
            logger.info("GET request  url:{} response:{} time:{}",
                    url, response, System.currentTimeMillis() - start);
            // 获取返回数据
            return getSuccessRetFromResp(response, url, JSON.toJSONString(paramsMap));
        } finally {
            // 必须释放连接,不然连接用完后会阻塞
            httpGet.releaseConnection();
        }
    }
    /**
     * Post请求,Map格式数据
     */
    public static String requestPost(String url, Map paramsMap) throws Exception {
        logger.info("POST request  url:{} params:{}", url, paramsMap);
        Long start = System.currentTimeMillis();
        List params = initParams(paramsMap);
        HttpPost httpPost = new HttpPost(url);
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(params, Consts.UTF_8));
            HttpResponse response = httpClient.execute(httpPost);
            logger.info("POST request  url:{} response:{}  time:{}",
                    url, response, System.currentTimeMillis() - start);
            String retStr = getSuccessRetFromResp(response, url, JSON.toJSONString(paramsMap));
            return retStr;
        } finally {
            httpPost.releaseConnection();
        }
    }
    /**
     * Post请求,json格式数据
     *
     */
    public static String requestPostJsonStr(String url, String json) throws Exception {
        logger.info("POST request  url:{} params:{}", url, json);
        long start = System.currentTimeMillis();
        HttpPost httpPost = new HttpPost(url);
        try {
            StringEntity entity = new StringEntity(json, Consts.UTF_8);
            entity.setContentType(APPLICATION_JSON_VALUE);
            httpPost.setEntity(entity);
            HttpResponse response = httpClient.execute(httpPost);
            logger.info("POST request  url:{} response:{}  time:{}", 
                        url, response, System.currentTimeMillis() - start);
            return getSuccessRetFromResp(response, url, json);
        } finally {
            // 资源释放
            httpPost.releaseConnection();
        }
    }
    /**
     * post Object格式数据
     */
    public static String requestPostJson(String url, Object obj) throws Exception {
        String params = JSON.toJSONString(obj);
        return requestPostJsonStr(url, params);
    }
    private static String getSuccessRetFromResp(HttpResponse response, String url, String params) throws Exception {
        String retStr = "";
        // 检验状态码,如果成功接收数据
        int code = response.getStatusLine().getStatusCode();
        if (code == 200) {
            retStr = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
        } else {
            throw new RuntimeException(String.format("Http request error:%s, url:%s, params:%s", response, url, params));
        }
        logger.info("Http request retStr:{}. url:{}", retStr, url);
        return retStr;
    }
    private static List initParams(Map paramsMap) {
        List params = new ArrayList();
        if (paramsMap == null)
            return params;
        for (Map.Entry entry : paramsMap.entrySet()) {
            params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        return params;
    }
}
本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
相关文章 更多
谷歌浏览器Mac版入口
谷歌浏览器Mac版入口

谷歌浏览器Mac版官方安装指南 谷歌浏览器Mac版官方安装入口是https://www.google.com/chrome/,需macOS 12+系统、500MB空间,下载.dmg后拖入应用程序安装,支持多设备同步、性能优化与隐私保护功能。 苹果电脑Chrome的安装入口究竟在哪里?这个问题最近可是

Chrome浏览器JS脚本不运行怎么办
Chrome浏览器JS脚本不运行怎么办

Chrome中JavaScript未执行需依次检查:一、移除站点级禁用并添加允许域名;二、开启全局JavaScript开关;三、禁用干扰扩展;四、在开发者工具中启用JavaScript;五、重置内容设置为默认。 有时在Chrome里打开网页,会发现交互按钮点了没反应,数据加载不出来,页面仿佛“静止”

IE浏览器怀旧版在线网址
IE浏览器怀旧版在线网址

IE浏览器怀旧版在线网址:一次精准的技术时光回溯 最近,不少老用户和怀旧爱好者在反复搜索一个问题:那个经典的Internet Explorer,如今还能在哪里原汁原味地体验到?答案指向一个特定的地址:https://ie.microsoft.com/legacy/。 这个网站远不止是一个简单的“皮肤

火狐浏览器有哪些设置功能
火狐浏览器有哪些设置功能

火狐浏览器五大核心设置功能:解锁高效、安全与个性化体验 火狐浏览器功能强大,但如果不仔细挖掘,很多能大幅提升效率和安全性的设置可能就“藏着掖着”了。这就好比拥有一台高性能设备,却只用了基础模式。那么,如何把它调整到最顺手、最安全的状态?接下来,我们就聚焦于当前版本(截至2025年末)最关键的五大设置

chrome搜索免验证入口
chrome搜索免验证入口

Chrome官方免验证入口为https://www.google.cn/chrome/,提供全平台安装包、免登录即用、本地化安全机制及引擎级性能优化。 到底该去哪里找正版、免费且无需繁琐验证的Chrome浏览器入口?这个问题困扰了不少网友。今天,我们就来直通核心,为大家详细拆解Chrome引擎的官方

java heap space 选型思路:使用场景与区别整理
java heap space 选型思路:使用场景与区别整理

Java堆是JVM存储对象的核心内存区域,配置需结合场景:单体应用适中设置;大数据处理需大堆并关注GC停顿;微服务强调快速启动;高并发需精细划分堆区域。关键参数-Xms和-Xmx建议等值以稳定性能。垃圾回收器选择影响效率,如G1适用于大堆,ZGC可实现低停顿。内存错误时需监控堆状态。

java heap space 使用中遇到的问题怎么解决
java heap space 使用中遇到的问题怎么解决

Java堆内存溢出错误通常因内存泄漏、数据处理需求过大或JVM参数配置不当引起。排查时可借助jmap、堆转储及MAT等工具定位问题。解决方案包括调整JVM内存参数(如-Xmx)、修复代码中的内存泄漏、优化大数据处理逻辑,并建立持续监控与预防机制,以保障应用稳定运行。

java xml 选型思路:使用场景与区别整理
java xml 选型思路:使用场景与区别整理

XML在Java开发中用于配置、数据交换等场景。解析方式主要有DOM、SAX、StAX及第三方库。DOM适合操作小文件,SAX/StAX适合处理大文件流,JAXB用于对象与XML映射。选型需结合数据大小、内存、性能及团队熟悉度,现代框架常封装底层解析。

using namespace 使用中遇到的问题怎么解决
using namespace 使用中遇到的问题怎么解决

命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

c语言函数递归 实操经验总结:这些技巧很实用
c语言函数递归 实操经验总结:这些技巧很实用

理解递归的基本原理在C语言中,递归是一种函数调用自身的编程技术。要掌握它,首先需要理解其核心思想:将一个复杂的大问题,分解为一个或几个与原问题相似但规模更小的子问题,直到子问题足够简单,可以直接求解。这个过程通常包含两个关键部分:递归出口和递归体。递归出口定义了问题何时不再继续分解,即最简单、可直接

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件
macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

Mac软件 更多
灵活计算器
灵活计算器
macOS/iOS/Android

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师
macOS

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

WINDOWS 更多
Windows 10
Windows 10
Windows

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘
Windows/macOS/iOS/Android

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。