Tio Boot DocsTio Boot Docs
Home
文档导航
  • java-db
  • api-table
  • jooq
  • mysql
  • postgresql
  • oceanbase
  • Enjoy
  • Tio Boot Admin
  • java-openai
  • ai_agent
  • knowlege_base
  • voice-agent
  • ai-search
  • ai-coding
  • ai-browser
  • 案例
Abount
AI 检索
  • Github
  • Gitee
Home
文档导航
  • java-db
  • api-table
  • jooq
  • mysql
  • postgresql
  • oceanbase
  • Enjoy
  • Tio Boot Admin
  • java-openai
  • ai_agent
  • knowlege_base
  • voice-agent
  • ai-search
  • ai-coding
  • ai-browser
  • 案例
Abount
AI 检索
  • Github
  • Gitee
  • 入门

    • 01 · 入门

      • 01_introduction
      • tio-boot:新一代高性能 Java Web 开发框架
      • tio-boot 入门示例
      • Tio-Boot 配置 : 现代化的配置方案
      • tio-boot 整合 hotswap-classloader 实现热加载
      • 自行编译 tio-boot
      • 最新版本
      • 开发规范
  • 部署与日志

    • 02 · 部署

      • 02_deployment
      • 使用 Maven Profile 实现分环境打包 tio-boot 项目
      • Maven 项目配置详解:依赖与 Profiles 配置
      • tio-boot 打包成 FatJar
      • 使用 GraalVM 构建 tio-boot Native 程序
      • 使用 Docker 部署 tio-boot
      • 部署到 Fly.io
      • 部署到 AWS Lambda
      • 到阿里云云函数
      • 使用 Deploy 工具部署
      • 使用Systemctl启动项目
      • 使用 Jenkins 部署 Tio-Boot 项目
      • 使用 Nginx 反向代理 Tio-Boot
      • 使用 Supervisor 管理 Java 应用
      • 历史部署页与替代方案
      • 胖包与瘦包的打包与部署
      • 使用 JDK 8 构建基础库与验证运行环境
    • 03 · 日志

      • 日志
      • tio-boot 整合 Logback
  • 核心开发

    • 04 · 配置

      • 04_configuration
      • 配置参数
      • 服务器监听器
      • 内置缓存系统 AbsCache
      • 使用 Redis 作为内部 Cache
      • 静态文件处理器
      • 基于域名的静态资源隔离
      • DecodeExceptionHandler
      • 开启虚拟线程(Virtual Thread)
      • 框架级错误通知
    • 05 · JSON

      • 05_json
      • Json
      • 接受 JSON 和响应 JSON
      • 响应实体类
    • 06 · Web 开发

      • Web 开发
      • 路由与参数

        • 概述
        • 添加 Controller
        • handler入门
        • Handler 的请求方法与错误响应
        • 接收请求参数
        • 接收日期参数
        • 接收数组参数
        • HttpRequest
      • 响应与文件

        • 返回字符串
        • 返回文本数据
        • 返回网页
        • 请求和响应字节
        • 文件上传
        • 文件下载
        • 返回视频文件并支持断点续传
        • HttpResponse
        • Resps
        • RespBodyVo
        • 动态 返回 CSS 实现
        • 返回图片
        • 返回 multipart
        • 使用零拷贝发送大文件
        • 分片上传
        • WebJars
      • 会话与请求控制

        • http Session
        • Cookie 读取与设置
        • 重定向和转发
        • Controller拦截器
        • 请求拦截器
        • LoggingInterceptor
        • 全局异常处理器
        • 跨域
        • 自定义 Handler 转发请求
        • 使用 HttpForwardHandler 转发所有请求
        • HTTP Basic 认证
        • Http响应加密
      • 异步与流式输出

        • 异步处理
        • Transfer-Encoding: chunked 实时音频播放
        • Server-Sent Events (SSE)
      • 工具与监控

        • 常用工具类
        • 接口访问统计
        • 接口请求和响应数据记录
        • JProtobuf
        • 测速
        • Gzip Bomb:使用压缩炸弹防御恶意爬虫
    • 07 · 参数校验

      • 07_validation
      • 数据紧校验规范
      • 参数校验
    • 08 · WebSocket 应用开发

      • 08_websocket
      • 使用 tio-boot 搭建 WebSocket 服务
      • WebSocket 聊天室项目示例
    • 09 · AOP

      • 09_aop
      • JFinal-aop
      • Aop 工具类
      • 配置
      • 独立使用 JFinal Aop
      • @AImport
      • 自定义注解拦截器
      • 原理解析
    • 10 · 国际化

      • 10_i18n
      • i18n
    • 11 · Enjoy 模板

      • 11_enjoy
      • tio-boot 整合 Enjoy 模版引擎文档
      • Tio-Boot 整合 Java-DB 与 Enjoy 模板引擎示例
      • 引擎配置
      • 表达式
      • 指令
      • 注释
      • 原样输出
      • Shared Method 扩展
      • Shared Object 扩展
      • Extension Method 扩展
      • Spring boot 整合
      • 独立使用 Enjoy
      • tio-boot enjoy 自定义指令 localeDate
      • PromptEngine
      • Enjoy 入门示例-擎渲染大模型请求体
      • Tio Boot + Enjoy:分页与 SEO 实战指南
      • TioBoot + Enjoy 生成 robots.txt 与 sitemap.xml:实战与SEO指南
      • Enjoy 使用示例
    • 12 · 定时任务

      • 12_scheduling
      • Quartz 定时任务集成指南
      • 分布式定时任务 xxl-jb
      • cron4j 使用指南
    • 13 · 测试

      • 13_testing
      • TioBootTest:环境与 AOP 初始化
      • 真实 HTTP 集成测试
      • 数据库集成测试与隔离
    • 14 · tio-utils

      • 14_tio-utils
      • tio-utils
      • EnvUtils 配置工具
      • Notification
      • Email
      • JSON
      • File
      • Base64
      • 上传和下载
      • Http
      • Telegram
      • RsaUtils
      • HttpUtils
      • ByteBufferUtils
      • 系统监控
      • 线程
      • 虚拟线程
      • 毫秒并发 ID (MCID) 生成方案
      • Base64 输入与锁等待
      • Gzip 与字节缓冲区
      • 字符串查找与随机整数
      • 日期转换与文件名解析
      • JSON 泛型转换与输出配置
      • JWT 签发与校验
      • 集合分页 PageUtils
      • URL 与流复制工具
  • 数据库与数据访问

    • 15 · java-db

      • 15_java-db
      • Db 工具类
      • java‑db
      • 操作数据库入门示例
      • SQL 模板 (SqlTemplates)
      • 数据源配置与使用
      • ActiveRecord
      • Db 工具类
      • 批量操作
      • Model
      • Model生成器
      • 注解
      • 异常处理
      • 数据库事务处理
      • Cache 缓存
      • Dialect 多数据库支持
      • 表关联操作
      • 复合主键
      • Oracle 支持
      • Enjoy SQL 模板
      • 整合 Enjoy 模板最佳实践
      • 多数据源支持
      • 独立使用 ActiveRecord
      • 调用存储过程
      • java-db 整合 Guava 的 Striped 锁优化
      • 生成 SQL
      • 通过实体类操作数据库
      • java-db 读写分离
      • Spring Boot 整合 Java-DB
      • like 查询
      • 常用操作示例
      • Druid 监控集成指南
      • SQL 统计
      • Db 与 PostgreSQL 业务实践
      • 查询资源自动管理
      • Row 展示转换与表格导出
      • 支付回执与权益事务
      • 商业库存与按量履约事务
    • 16 · api-table

      • 16_api-table
      • ApiTable 概述
      • 使用 ApiTable 连接 SQLite
      • 使用 ApiTable 连接 Mysql
      • 使用 ApiTable 连接 Postgres
      • 使用 ApiTable 连接 TDEngine
      • 使用 api-table 连接 oracle
      • 使用 api-table 连接 mysql and tdengine 多数据源
      • EasyExcel 导出
      • EasyExcel 导入
      • ApiTable 的权限与业务边界
      • ApiTable 联调与故障定位
      • ApiTable 实现增删改查
      • 数组类型
      • 单独使用 ApiTable
      • TQL(Table SQL)前端输入规范
    • 17 · MyBatis

      • 17_mybatis
      • Tio-Boot 整合 MyBatis
      • 使用配置类方式整合 MyBatis
      • 整合数据源
      • 使用 mybatis-plus 整合 tdengine
      • 整合 mybatis-plus
    • 18 · jOOQ

      • 18_jooq
      • 使用配置类方式整合 jOOQ
      • tio-boot + jOOQ 事务管理
      • 批量操作与性能优化
      • 整合agroal
      • 代码生成与类型安全
      • 基于 Record / POJO 增删改查
      • UPSERT、批量更新、返回主键与高级 SQL
      • 的多表关联查询、DTO 投影、聚合统计与视图封装
      • 的窗口函数、CTE、JSON 查询与 PostgreSQL 高级 SQL 实战
      • tio-boot + jOOQ 的审计字段、乐观锁、数据权限与企业级 Repository 设计
      • 测试策略、SQL 日志、性能诊断与生产排障
      • 多租户、读写分离与多数据源设计
      • 代码生成治理、数据库迁移与团队协作规范实战
    • 19 · PostgreSQL

      • 19_postgresql
      • PostgreSQL 安装
      • PostgreSQL 主键自增
      • PostgreSQL 日期类型
      • Postgresql 金融类型
      • PostgreSQL 数组类型
      • 索引
      • PostgreSQL 查询优化
      • 获取字段类型
      • PostgreSQL 全文检索
      • PostgreSQL 向量
      • PostgreSQL 优化向量查询
      • PostgreSQL 其他
    • 20 · MySQL

      • 20_mysql
      • 使用 Docker 运行 MySQL
      • 常见问题
    • 21 · OceanBase

      • 21_oceanbase
      • 快速体验 OceanBase 社区版
      • 快速上手 OceanBase 数据库单机部署与管理
      • 诊断集群性能
      • 优化 SQL 性能指南
      • 待定
    • 22 · Oracle

      • 22_oracle
      • Oracle
    • 23 · SQL Server

      • 23_sqlserver
      • SQL Server
    • 24 · SQLite

      • 24_sqlite
      • SQLite
    • 25 · MongoDB

      • 25_mongodb
      • tio-boot 使用 mongo-java-driver 操作 mongodb
    • 26 · Elasticsearch

      • 26_elasticsearch
      • Elasticsearch
      • JavaDB 整合 ElasticSearch
      • Elastic 工具类使用指南
      • Elastic-search 注意事项
      • ES 课程示例文档
  • 缓存与消息队列

    • 27 · Cache

      • 27_cache
      • Caffeine
      • CacheUtils 工具类
      • 使用 java-db 整合 ehcache
    • 28 · Redis

      • 28_redis
      • 使用 Docker 安装 Redis
      • 使用 java-db 整合 Redis
      • Java DB Redis 相关 Api
      • redis 使用示例
      • 和 RedisTemplate 协作
      • 使用 Jedis 连接池接入 Redis
      • hutool RedisDS
      • Redisson
      • Caffeine 与 Redis 两级缓存
      • 使用 CacheUtils 整合 caffeine 和 redis 实现的两级缓存
    • 29 · 消息队列

      • 29_mq
      • Mica-mqtt
      • EMQX
      • Disruptor
    • 30 · Kafka

      • 30_kafka
      • Kafka
      • AWS MSK
  • 认证与账号体系

    • 31 · 认证与权限

      • 31_authentication
      • FixedTokenInterceptor
      • TokenManager
      • 数据表
      • 匿名登录
      • 个人中心
      • 权限校验注解
      • Sa-Token
      • sa-token 登录注册
      • StpUtil.isLogin() 源码解析
    • 32 · 第三方登录注册

      • 32_third-party-auth
      • 邮箱登录和注册
      • 邮箱重置密码
      • 腾讯云短信登录注册
      • 腾讯云短信重置密码
      • 阿里云短信登录和注册
      • 阿里云短信重置密码
      • 微信登录与绑定手机号
      • 支付宝登录与绑定手机号
      • 微信小程序手机号快捷登录
      • Google登录
      • 阿里云邮件推送验证邮箱
  • 网络通信

    • 33 · AIO

      • 33_aio
      • ByteBuffer
      • AIO HTTP 服务器
      • 自定义和线程池和池化 ByteBuffer
      • AioHttpServer 应用示例 IP 属地查询
      • 手写 AIO Http 服务器
      • Java 21 中的虚拟线程与 AIO
    • 34 · t-io

      • 34_tio
      • 认识 t-io

        • t-io 核心优势与应用价值
        • t-io 消息处理流程
      • 快速上手

        • TioBootServer
        • 独立端口启动 TCP 服务器
        • 内置 TCP 处理器
        • 独立启动 UDPServer
        • 使用内置 UDPServer
      • 核心概念

        • TioConfig
        • ChannelContext
        • Packet
        • Tio 工具类
      • 消息与文件传输

        • 发送数据
        • HTTP 长连接与高效文件传输
        • 使用 AsynchronousSocketChannel 响应数据
      • 连接管理

        • 业务数据绑定
        • 业务数据解绑
        • 关闭连接
        • 资源共享
        • 成员排序
        • 拉黑 IP
      • 加密通信

        • SSL
        • Https建立连接过程
      • 心跳与监控

        • 监控: 心跳
        • 监控: 客户端的流量数据
        • 监控: 单条 TCP 连接的流量数据
        • 监控: 端口的流量数据
        • 单条通道统计: ChannelStat
        • 所有通道统计: GroupStat
      • 深入原理

        • tio-运行原理详解
        • DecodeRunnable
        • t-io 稳定性设计与资源管理
        • 深入解析 Tio 源码:构建高性能 Java 网络应用
        • HTTP、WebSocket 与 TCP 的缓冲区复用
    • 35 · tio-http-server

      • 35_tio-http-server
      • 使用 Tio-Http-Server 搭建简单的 HTTP 服务
      • tio-boot 添加 HttpRequestHandler
      • 在 Android 上使用 tio-boot 运行 HTTP 服务
      • tio-http-server-native
      • handler 常用操作
      • tio-http-server 与 tio-boot 的使用边界
    • 36 · tio-websocket

      • 36_tio-websocket
      • WebSocket 服务器
      • WebSocket Client
      • TCP数据转发
    • 37 · Netty

      • 37_netty
      • Netty TCP Server
      • Netty Web Socket Server
      • 使用 protoc 生成 Java 包文件
      • Netty WebSocket Server 二进制数据传输
      • Netty 组件详解
    • 38 · netty-boot

      • 38_netty-boot
      • Netty-Boot
      • 原理解析
      • 整合 Hot Reload
      • 整合 数据库
      • 整合 Redis
      • 整合 Elasticsearch
      • 整合 Dubbo
      • Listener
      • 文件上传
      • 拦截器
      • Spring Boot 整合 Netty-Boot
      • SSL 配置指南
      • ChannelInitializer
      • Reserve
  • 集成与扩展

    • 39 · 第三方集成

      • 39_integrations
      • 整合 okhttp
      • 整合 GrpahQL
      • 集成 Mailjet
      • 整合 ip2region
      • 整合 GeoLite 离线库
      • 整合 Lark 机器人指南
      • 集成 Lark Mail 实现邮件发送
      • Thymeleaf
      • Swagger
      • Clerk 验证
      • 集成datadog
    • 40 · Magic Script

      • 40_magic-script
      • tio-boot 与 magic-script 集成指南
    • 41 · Groovy

      • 41_groovy
      • tio-boot 整合 Groovy
      • 调试常用脚本
    • 42 · 爬虫

      • 42_crawling
      • jsoup
      • 爬取 z-lib.io 数据
      • 整合 WebMagic
      • WebMagic 示例:爬取学校课程数据
      • Playwright
      • Flexmark (Markdown 处理器)
      • tio-boot 整合 Playwright
      • 缓存网页数据
    • 43 · Dubbo

      • 43_dubbo
      • 概述
      • dubbo 2.6.0
      • dubbo 2.6.0 调用过程
      • dubbo 3.2.0
    • 44 · Spring

      • 44_spring
      • Spring Boot Web 整合 Tio Boot
      • spring-boot-starter-webflux 整合 tio-boot
      • tio-boot 整合 spring-boot-starter
      • Tio Boot 整合 Spring Boot Starter db
      • Tio Boot 整合 Spring Boot Starter Data Redis 指南
    • 45 · Spring Cloud

      • 45_spring-cloud
      • tio-boot spring-cloud
    • 46 · Quarkus

      • 46_quarkus
      • Quarkus(无 HTTP)整合 tio-boot(有 HTTP)
      • tio-boot + Quarkus + Hibernate ORM Panache
      • tio-boot + Quarkus + Hibernate ORM Panache + jOOQ 整合方案
    • 47 · Telegram4J

      • 47_telegram4j
      • 数据库设计
      • 基于 HTTP 协议开发 Telegram 翻译机器人
      • 基于 MTProto 协议开发 Telegram 翻译机器人
      • 过滤旧消息
      • 保存机器人消息
      • 定时推送
      • 增加命令菜单
      • 使用 telegram-Client
      • 使用自定义 StoreLayout
      • 延迟测试
      • Reactor 错误处理
      • Telegram4J 常见错误处理指南
      • 处理回调查询
      • Reactor
      • 文档翻译
      • 使用 Tio-Boot 整合 tdlight
      • tio-boot 整合 TelegramBots
      • tio-boot 整合 Telegram-Bot-Utils
      • Telegram-Bot-Utils 使用指南
    • 48 · Telegram Bots

      • 48_telegram-bots
      • TelegramBots 入门指南
      • 使用工具库 telegram-bot-base 开发翻译机器人
    • 49 · 文件存储

      • 49_file-storage
      • 文件上传数据表
      • 本地存储
      • 存储到 亚马逊 S3
      • 存储到 Cloudflare R2
      • 存储到 腾讯 COS
      • 上传文件到阿里云 OSS
    • 50 · 支付

      • 支付集成
      • 微信小程序支付:普通支付
      • 微信支付:Native 扫码支付(PC 网页扫码)
      • 支付宝:电脑网站支付接入指南
      • 支付宝:使用 page.pay 二维码前置模式实现网页内扫码支付
  • Firebase 与 Clerk

    • 51 · Firebase

      • 51_firebase
      • 整合 google firebase
      • Firebase Storage
      • Firebase Authentication
      • 使用 Firebase Admin SDK 进行匿名用户管理与自定义状态标记
      • 导出用户
      • 登录注册
      • 注册回调
    • 52 · Clerk

      • 52_clerk
      • Clerk
  • 多媒体

    • 53 · 音视频处理

      • 53_media
      • JAVE 提取视频中的声音
      • Jave 提取视频中的图片
      • 待定
    • 54 · 语音识别

      • 54_asr
      • Whisper-JNI
    • 55 · 语音合成

      • 55_tts
    • 56 · 文字识别

      • 56_ocr
    • 57 · Native Media

      • 57_native-media
      • java-native-media
      • JNI 入门示例
      • mp3 拆分
      • mp4 转 mp3
      • 使用 libmp3lame 实现高质量 MP3 编码
      • Linux 编译
      • macOS 编译
      • 从 JAR 包中加载本地库文件
      • 支持的音频和视频格式
      • 任意格式转为 mp3
      • 通用格式转换
      • 通用格式拆分
      • 视频合并
      • VideoToHLS
      • split_video_to_hls 支持其他语言
      • 持久化 HLS 会话
      • 获取视频长度
      • 保存视频的最后一帧
      • 添加水印
      • linux版本
    • 58 · 计算机视觉

      • 58_computer-vision
      • 使用 Java 运行 YOLOv8 ONNX 模型进行目标检测
      • tio-boot整合yolo
      • ONNX Runtime 推理说明
      • Paddle Structure
      • tio-boot 整合 Paddle Structure
      • tio-boot整合Paddle Structure 提取图片
      • U2Net 图片去背景原理
      • tio-boot 整合 U2Net 实现图片去背景
  • AI 开发

    • 59 · java-openai

      • 59_java-openai
      • 简介
      • 流式生成
      • 图片多模态输入
      • Google Gemini接入
      • google Vertex AI 接入
      • Perplexity API
      • WhisperClient 语音识别
      • SupadataClient 获取视频字幕
      • GiteeClient 文档解析与图片 OCR
      • DeepSeekClient 官方模型查询
      • BailianTTSClient 语音合成
    • 60 · AI Agent

      • 60_ai-agent
      • 数据库设计
      • 示例问题管理
      • 会话管理
      • 历史记录
      • 意图识别
      • 智能问答
      • 文件上传与解析文档
      • 翻译
      • 名人搜索功能实现
      • Ai studio gemini youbue 问答使用说明
      • 自建 YouTube 字幕问答系统
      • 自建 获取 youtube 字幕服务
      • 使用 OpenAI ASR 实现语音识别接口(Java 后端示例)
      • 定向搜索
      • 16
      • 17
      • 18
      • 在 tio-boot 应用中整合 ai-agent
      • 接口文档
      • 自定义 ChatAskService
      • 请求记录
      • 限流和错误处理
      • 增强检索(RAG)
      • 结构化数据检索
      • AI 问答
      • 连接代码执行器
      • 待定
      • 模型编程能力评测
      • 音频会话 SDP 示例
    • 61 · 知识库

      • Java MossKB 知识库文档
      • 学术论文
      • 数据库设计
      • 用户登录实现
      • 模型管理
      • 知识库管理
      • 文档拆分
      • 片段向量
      • 命中测试
      • 文档管理
      • 片段管理
      • 问题管理
      • 应用管理
      • 向量检索
      • 推理问答
      • 问答模块
      • 统计分析
      • 用户管理
      • VL模型文档解析方案和费用对比
      • 文档解析模型实测
      • 文档解析优化
      • 片段汇总
      • 段落分块与检索
      • 多文档解析
      • 检索性能优化
      • 对话日志
      • 创建独立用户与账号资源管理
      • api 管理
      • 独立对话页
      • 嵌入第三方web系统
      • 分段预览
      • 文档预览
      • 多轮检索
      • 上下文压缩
      • 支持自定义模型
      • 爬取网页数据
      • 隔离 Python 执行环境配置
      • 隔离 Python 执行器
      • 函数库与调用接口
      • Windows 与 Linux 部署
      • 离线运行向量模型
      • Milvus
      • 存储文件到云存储
      • 文档解析异步化
    • 62 · AI 搜索

      • 62_ai-search
      • ai-search 项目简介
      • ai-search 数据库文档
      • ai-search SearxNG 搜索引擎
      • ai-search Jina Reader API
      • ai-search Jina Search API
      • ai-search 搜索、重排与读取内容
      • ai-search PDF 文件处理
      • ai-search 推理问答
      • Google Custom Search JSON API
      • ai-search 意图识别
      • ai-search 问题重写
      • ai-search 系统 API 接口 WebSocket 版本
      • ai-search 搜索代码实现 WebSocket 版本
      • ai-search 生成建议问
      • ai-search 生成问题标题
      • ai-search 历史记录
      • Discover API
      • 翻译
      • Tavily Search API 文档
      • 对接 Tavily Search
      • 火山引擎 DeepSeek
      • 对接 火山引擎 DeepSeek
      • ai-search 搜索代码实现 SSE 版本
      • jar 包部署
      • Docker 部署
      • 爬取一个静态网站的所有数据
      • 网页数据预处理
      • 网页数据检索与问答流程整合
    • 63 · 语音 Agent

      • 63_voice-agent
      • 整合Gemini realtime模型
      • Voice Agent 前端接入接口文档
      • 整合千问realtime模型
      • 打断支持
      • 主动介入
      • eleven labs
      • 基于 tio-boot + ElevenLabs 构建实时语音 Agent(支持打断与主动介入)
    • 64 · AI Coding

      • 64_ai-coding
      • Cline 提示词
      • Cline 提示词-中文版本
    • 65 · AI Browser

      • deepseek-browser-use:从入门到源码
      • deepseek-browser-use:概念与学习路线
      • 安装、启动与健康检查
      • 第一个任务:打开页面、读取结果与关闭
      • 客户端:dsb 命令行、Python 与 PowerShell
      • 统一命令接口与人机协作
      • 浏览器、profile 与登录态
      • 窗口尺寸与页面视口
      • 接入智能体:观察、执行与验证
      • 表单与多层弹窗排障:防止重复提交
      • 调用追踪、页面留档与文件上传
      • 站点配方、技能与异步作业
      • Windows OCR:本地图片与页面文字识别
      • 配置项与运维自省
      • 命令清单
      • 源码教程:从 HTTP 请求到命令执行
      • DOM 原理:前端如何生成可交互快照
      • DOM 原理:Java 模型与跨 Frame 索引
      • 正文提取与上层结构化处理
      • 源码教程:生命周期、导航与页签
      • 源码教程:DOM、页面状态与元素读取
      • 源码教程:点击、输入、键盘与鼠标
      • 源码教程:等待条件与 JavaScript 执行
      • 源码教程:文件上传、截图、PDF 与 OCR
      • 源码教程:Cookie、存储与页面设置
      • 源码教程:网络记录、请求拦截与控制台
      • 源码教程:原生对话框、DOM 弹窗与人机协作
      • 源码教程:批量、配方、后台作业与维护
      • 源码教程:Chrome 走 CDP 与 CDP 客户端
      • 请求响应关联与线程约束
      • DeepSeek Harness 插件开发入门:从入口到第一个工具
      • 开发 dsh-plugin-deepseek-browser-use:HTTP 客户端与基础工具
      • 开发 dsh-plugin-deepseek-browser-use:会话、作业、文件与图片
      • 把 dsh-plugin-deepseek-browser-use 构建并安装到 DeepSeek Harness
      • DeepSeek Harness 原生浏览器插件:安装、配置与使用
      • DeepSeek Harness 原生浏览器插件:源码、生命周期与测试
      • 后端自动管理:安装 Java 与 Git、更新源码、编译和启动
  • 项目实战

    • 66 · java-uni-ai-server

      • 66_java-uni-ai-server
      • 语音合成系统
      • Fish.audio TTS 接口说明文档与 Java 客户端封装
      • 整合 fishaudio 到 java-uni-ai-server 项目
      • 待定
    • 67 · java-llm-proxy

      • 67_java-llm-proxy
      • 使用tio-boot搭建多模型LLM代理服务
    • 68 · java-kit-server

      • 68_java-kit-server
      • Java 执行 python 代码
      • 通过大模型执行 Python 代码
      • 执行 Python (Manim) 代码
      • 待定
      • 待定
      • 待定
      • 视频下载增加水印说明文档
    • 69 · tio-im

      • 69_tio-im
      • 通讯协议文档
      • ChatPacket.proto 文档
      • java protobuf
      • 数据表设计
      • 创建工程
      • 登录
      • 历史消息
      • 发消息
    • 70 · tio-mail-wing

      • 70_tio-mail-wing
      • tio-mail-wing简介
      • 任务1:实现POP3系统
      • 使用 getmail 验证 tio-mail-wing POP3 服务
      • 任务2:实现 SMTP 服务
      • 数据库初始化文档
      • 用户管理
      • 邮件管理
      • 任务3:实现 SMTP 服务 数据库版本
      • 任务4:实现 POP3 服务(数据库版本)
      • IMAP 协议
      • 拉取多封邮件
      • 任务5:实现 IMAP 服务(数据库版本)
      • IMAP实现讲解
      • IMAP 手动测试脚本
      • IMAP 认证机制
      • 主动推送
      • namesapce
      • CONDSTORE and QRESYNC
    • 71 · tio-mcp-server

      • 71_tio-mcp-server
      • 实现 MCP Server 开发指南
      • MCP 协议
      • /zh/71_tio-mcp-server/11.html
    • 72 · tio-log-server

      • 72_tio-log-server
      • 简介
      • 收集 docker 日志
      • 入库
    • 73 · tio-sip

      • 73_tio-sip
      • SIP Server 第一版原理说明
      • SIP Server 第一版实战
      • 一、Windows 平台测试
      • SIP Server 第二版实战
      • SIP Server 第三版实战
      • 性能优化
      • 基于 MediaProcessor 对接 Realtime 模型说明
      • 对接大语言模型
      • 支持 G722 宽带语音
      • G722编码和解码
      • 会话级采样率转换
      • 增加 9196 回声测试分机
      • 语音系统链路说明
      • 一、Gemini Realtime 的打断机制
    • 74 · tio-boot-admin

      • 74_tio-boot-admin
      • 入门指南:使用框架内置配置
      • 手动初始化数据库
      • 配置职责、生效条件与扩展边界
      • 整合数据库
      • 与前端集成
      • 文件上传
      • 网络请求
      • 单图片管理(只读模式)
      • 多图片管理
      • 布尔值管理
      • 字段联动
      • Word 管理
      • PDF 管理
      • 文章管理
      • 富文本编辑器
      • 整合 Enjoy 模版引擎
      • 历史可选方案:Token 存储与 Sa-Token
      • 业务 API 与 H5 / 小程序联调
      • 方法路由与业务鉴权
      • 整合 Redis
      • 整合 Elasticsearch
      • 后端开发规范:tio-boot、java-db 与 Kv
      • 多表实现文件数据存储
      • 受限事项与原操作闭环
      • 具名业务后台与受控命令集成
      • 固定用途查询、受控导出与删除证明
      • 登录与认证
    • 75 · 案例

      • 75_examples
      • 封装 IP 查询服务
      • tio-boot 案例 - 全局异常捕获与企业微信群通知
      • tio-boot 案例 - 文件上传和下载
      • tio-boot 案例 - 整合 ant design pro 增删改查
      • tio-boot 案例 - 流失响应
      • tio-boot 案例 - 增强检索
      • tio-boot 案例 - 整合 function call
      • tio-boot 案例 - 定时任务 监控 PostgreSQL、Redis 和 Elasticsearch
      • Tio-Boot 案例:使用 SQLite 整合到登录注册系统
      • tio-boot 案例 - 执行 shell 命令
      • /zh/75_examples/11.html
      • /zh/75_examples/12.html
      • /zh/75_examples/13.html
  • 性能、原理与源码

    • 76 · 性能测试

      • 76_performance
      • 压力测试 - tio-http-serer
      • 压力测试 - tio-boot
      • 压力测试 - tio-boot-native
      • 压力测试 - netty-boot
      • 性能测试对比
      • TechEmpower FrameworkBenchmarks
      • 压力测试 - tio-boot 12 C 32G
      • HTTP/1.1 Pipelining 性能测试报告
      • tio-boot vs Quarkus 性能对比测试报告
    • 77 · 原理

      • 77_internals
      • 生命周期
      • 请求处理流程
      • 重要的类
    • 78 · 源码解析

      • 78_source-code
      • 源码阅读入口
      • Swagger 整合到 Tio-Boot 中的指南
      • 启动与关闭生命周期
      • HTTP 请求分发与路由优先级
      • 高性能网络编程中的 ByteBuffer 分配与回收策略
      • TioBootServerHandler 源码解析
      • HTTP 请求与连接生命周期

开发 dsh-plugin-deepseek-browser-use:会话、作业、文件与图片

上一篇已经把工具请求送到了 Java 服务。真实使用还有几个问题:两个会话会不会操作同一个任务?同一会话同时发两个命令怎么办?HTTP 已返回,但异步批次还没结束怎么办?本篇结合插件 0.2.0 的实际实现,逐步补齐这些核心能力。

1. 为什么不能只保存一个全局 taskId

假设 A 会话打开订单页面,B 会话打开搜索页面。如果两个会话共用一个任务,A 下一次点击可能落在 B 的页面上。把 task ID 交给模型填写同样不合适:它既可能填错,也可能引用另一个会话的任务。

当前实现用 Map<Owner, Entry> 保存状态,Owner 是通过工具入口验证过的活跃 Agent 对象。以下是 src/sessions.ts 完整文件;先通读整体,再按后续小节理解各个分支:

import { randomInt } from 'node:crypto';
import { BrowserClient, requireSuccess, type Envelope, type Params } from './client.js';

export interface Owner { ctx: { effect(callback: () => () => Promise<void>, label?: string): () => Promise<void> }; }
interface Entry { id: number; tail: Promise<unknown>; started: boolean; closed: boolean; abort: AbortController; cleanup: () => Promise<void>; jobs: Set<string>; activeJob?: string; batchUnknown?: boolean; }
export interface SessionOptions { browser: string; headless: boolean; }

/** Live owner identity, not a model-supplied task ID, controls resource access. */
export class BrowserSessions {
  private entries = new Map<Owner, Entry>();
  private disposed = false;
  constructor(readonly client: BrowserClient, readonly options: SessionOptions) {}

  private entry(owner: Owner): Entry {
    if (this.disposed) throw new Error('Browser plugin is disposed');
    const previous = this.entries.get(owner);
    if (previous) return previous;
    // 48 random bits: exact JSON/JavaScript integer, unlike server Snowflake IDs.
    const entry: Entry = { id: randomInt(1, 2 ** 48 - 1), tail: Promise.resolve(), started: false, closed: false,
      abort: new AbortController(), cleanup: async () => {}, jobs: new Set() };
    this.entries.set(owner, entry);
    entry.cleanup = owner.ctx.effect(() => async () => {
      entry.closed = true;
      entry.abort.abort(new Error('Browser session disposed'));
      await entry.tail.catch(() => {});
      if (entry.batchUnknown) throw new Error(`Task ${entry.id}: batch submission outcome unknown. Inspect service list_jobs and finish/cancel the batch before manually closing this task. Cleanup did not close potentially active work.`);
      if (entry.started) {
        const signal = AbortSignal.timeout(this.client.options.timeoutMs);
        if (entry.activeJob) {
          requireSuccess(await this.client.command(entry.id, 'cancel_job', { jobId: entry.activeJob }, signal));
          for (;;) {
            const status = requireSuccess(await this.client.command(entry.id, 'get_job', { jobId: entry.activeJob, includeResult: false }, signal));
            if ((status.data as Params)?.status !== 'running') break;
            await new Promise(resolve => setTimeout(resolve, 100));
            signal.throwIfAborted();
          }
        }
        requireSuccess(await this.client.command(entry.id, 'close', {}, signal));
      }
      this.entries.delete(owner);
    }, 'deepseek-browser-use.session');
    return entry;
  }

  run(owner: Owner, method: string, params: Params, signal: AbortSignal): Promise<Envelope> {
    signal.throwIfAborted();
    const entry = this.entry(owner);
    const combined = AbortSignal.any([signal, entry.abort.signal]);
    const operation = entry.tail.then(async (): Promise<Envelope> => {
      combined.throwIfAborted();
      if (entry.closed) throw new Error('Browser session is closed');
      if (entry.batchUnknown) throw new Error(`Task ${entry.id}: batch submission outcome unknown; inspect service list_jobs before manually recovering. This Session will not submit more browser actions.`);
      if (method === 'get_job' || method === 'cancel_job') {
        if (typeof params.jobId !== 'string' || !entry.jobs.has(params.jobId)) throw new Error('Job does not belong to this Session');
      } else if (entry.activeJob) {
        throw new Error(`Browser batch ${entry.activeJob} is pending. Use dsb_job until it finishes before issuing other commands.`);
      }
      if (method === 'close' && !entry.started) return { ok: true, data: { id: entry.id, closed: true } };
      if (!entry.started) {
        // Remember ownership before HTTP, so timeout during start is still cleaned up.
        entry.started = true;
        const started = await this.client.command(entry.id, 'start', { ...this.options }, combined);
        if (!started.ok) entry.started = false;
        requireSuccess(started);
        if (method === 'start') return started;
      } else if (method === 'start') {
        return { ok: true, data: { id: entry.id, reused: true } };
      }
      let result: Envelope;
      try { result = await this.client.command(entry.id, method, params, combined); }
      catch (error) {
        if (method === 'commands') entry.batchUnknown = true;
        throw error;
      }
      const data = result.data as Params | undefined;
      if (method === 'commands' && result.ok) {
        if (typeof data?.jobId !== 'string') {
          entry.batchUnknown = true;
          throw new Error(`Task ${entry.id}: async batch response missing jobId; inspect service before resubmitting`);
        }
        entry.jobs.add(data.jobId);
        entry.activeJob = data.jobId;
      }
      if (method === 'get_job' && result.ok && data && ['done', 'failed', 'cancelled'].includes(String(data.status)) && params.jobId === entry.activeJob) {
        entry.activeJob = undefined;
      }
      if (method === 'close' && result.ok) entry.started = false;
      return result;
    });
    entry.tail = operation.catch(() => {});
    return operation;
  }

  async dispose(): Promise<void> {
    this.disposed = true;
    const results = await Promise.allSettled([...this.entries.values()].map(entry => entry.cleanup()));
    const errors = results.filter(r => r.status === 'rejected').map(r => r.reason);
    if (errors.length) throw new AggregateError(errors, 'Browser session cleanup failed');
  }
}

这份文件可以在原工程中编译;它依赖上一篇介绍的 client.ts。Owner 接口只声明会话管理需要的 ctx.effect,不把整个 Harness Agent 类型耦合到 HTTP 会话管理器里,便于独立测试。

2. 先读懂 Entry 中的字段

字段含义缺少它会怎样
id发送到 Java 的任务 ID无法把后续命令关联到自己的页面
tail当前队列尾部的 Promise同会话并发动作可能乱序
started任务是否已启动或可能已启动重复 start,或漏掉需要清理的任务
closedAgent 作用域是否已关闭排队调用可能在释放之后继续操作
abort本会话的取消控制器释放作用域时无法通知等待中的请求
cleanup注册到作用域的清理函数插件卸载时无法统一释放资源
jobs本会话创建过的 job ID 集合可能查询或取消别人的作业
activeJob尚未观察到终态的作业普通命令可能插入仍运行的批次
batchUnknown批次提交结果无法确认响应丢失后可能重复提交整个批次

randomInt(1, 2 ** 48 - 1) 在 48 位范围内选择正整数,可被 JavaScript number 精确表示,也可传入 Java long。这里避免直接使用可能超过 JavaScript 安全整数范围的服务端 Snowflake ID;随机值不是绝对无碰撞保证,也不是认证凭据。

任务归属隔离不等于登录态隔离:多个任务仍可能使用同一个浏览器 profile 的 Cookie。需要不同账号互不干扰时,应使用独立服务和 profile。

3. 顺序队列为什么写成两个 Promise

关注 run 中这三行的关系:

const operation = entry.tail.then(async (): Promise<Envelope> => {
  // 在这里检查取消、启动任务、发送本次命令。
});
entry.tail = operation.catch(() => {});
return operation;

这是结构示意,注释位置在完整文件中有实际实现,不是可直接替换的方法。

新的 operation 接在旧 tail 后面,因此同一 Owner 的两个请求按加入队列的顺序执行。返回给调用者的是原始 operation,调用者仍能看到失败。保存给下一项的 tail 则通过 catch 恢复为可继续等待的 Promise,避免一次失败让后续所有调用都跳过执行。

排队前检查一次取消,进入队列后还要检查一次。假设第二个点击在等待第一个请求时被取消,只在函数入口检查就会漏掉这个变化,最终仍把取消的点击发给浏览器。

4. 懒启动与 close 的两种含义

首次调用 dsb_state 或 dsb_navigate 时,started 为 false,管理器先发送 start,成功后再发送目标命令。后续 dsb_start 返回 reused:true,不会重复创建任务。

为什么在 start 请求之前先设置 started = true?因为 HTTP 超时不代表服务端没有创建任务。提前记下“可能已启动”,作用域清理时才能尝试关闭它;若收到明确的 ok:false,则恢复为 false。

显式调用 dsb_close 只把 started 改回 false,保留活跃 Agent 的 Entry,所以下次可以重新 start。注意:若还存在未观察到终态的批次,dsb_close 会被直接拒绝(activeJob 检查在 close 分支之前),需要先用 dsb_job 观察到终态。Agent 作用域释放时才设置 closed = true,并取消信号、等待队列和清理任务。这两个动作语义不同。

5. 通用命令为什么还需要允许列表

把模型传来的任意 method 转给服务很方便,但会绕过任务生命周期和异步作业管理。当前工具层先检查准确命令名,再排除必须走专用工具或不公开的操作:

const forbidden = new Set(['start', 'close', 'shutdown', 'cleanup', 'get_config', 'list_tasks', 'list_jobs', 'get_job', 'cancel_job', 'run_recipe', 'commands']);
export function validateCommand(method: string): void {
  if (!commandNames.includes(method) || forbidden.has(method)) {
    throw new Error(`Command ${method} is not an exposed task command. Use dedicated lifecycle/job tools; service-wide commands and recipes are not exposed.`);
  }
}

commandNames 来自 src/commands.ts,是 Java CommandTable.java 的名称快照。它不是每次联网动态发现的服务能力。新增服务命令时要更新快照,并运行现有一致性测试。

这里禁止通用入口直接调用 start/close/get_job/cancel_job,是为了统一管理会话;禁止 commands/run_recipe,是为了避免嵌套动作绕过逐项验证;禁止 shutdown/cleanup 等全局操作,是为了避免影响其他会话。这些限制不是页面 JavaScript 的安全沙箱。

6. 异步批次:队列结束不等于浏览器工作结束

以下是 src/tools.ts 中通用命令、批次和作业工具的实际注册代码,依赖已有 add、command 和 object:

  add('command', 'Execute an exact supported task-scoped command. Use dsb_methods and the skill for names/parameters. No lifecycle, global management, nested batches or recipes.',
    z.strictObject({ method: z.string().min(1), params: object.optional() }), (a, e) => { validateCommand(a.method); return command(e, a.method, a.params ?? {}); });
  add('batch', 'Submit sequential task commands as a background job. Poll dsb_job before issuing other actions. Do not batch indices whose validity depends on earlier page changes.', z.strictObject({
    commands: z.array(z.strictObject({ method: z.string(), params: object.optional() })).min(1).max(200),
    stopOnError: z.boolean().optional(),
  }), (a, e) => {
    for (const step of a.commands) validateCommand(step.method);
    return command(e, 'commands', { commands: a.commands.map(step => ({ [step.method]: step.params ?? {} })), async: true, stopOnError: a.stopOnError ?? true });
  });
  add('job', 'Read or request cooperative cancellation of a job created by this Session. Cancellation does not undo an already delivered action.',
    z.strictObject({ jobId: z.string().min(1), cancel: z.boolean().optional() }), (a, e) => command(e, a.cancel ? 'cancel_job' : 'get_job', { jobId: a.jobId, ...(a.cancel ? {} : { includeResult: true }) }));

模型更容易填写这种结构:

{
  "commands": [
    { "method": "get_title" },
    { "method": "get_url" }
  ],
  "stopOnError": true
}

服务需要的 params 则是:

{
  "commands": [{ "get_title": {} }, { "get_url": {} }],
  "async": true,
  "stopOnError": true
}

map(step => ({ [step.method]: step.params ?? {} })) 完成两种格式的转换;方括号表示用变量值作为属性名。批次中的每一项先经过 validateCommand,所以不能把被禁止的命令藏进数组。

服务返回 job ID 后,这次 HTTP 就结束了,但批次可能仍在执行。BrowserSessions 因此保存 activeJob,暂时拒绝其他普通动作,只允许查询或取消本会话拥有的作业。查询到 done/failed/cancelled 才放行。

还有一条同样会卡死的路径要记住:作业结果只保存在服务端内存里(只保留最近 50 个,服务重启即丢)。作业查不到时 get_job 返回 ok:false,而实现只在 ok:true 时才清除 activeJob,于是这个会话的普通浏览器动作会被一直拒绝,错误文案却还在提示继续用 dsb_job。遇到“没有这个任务”时不要反复轮询,按 batchUnknown 的方式收尾(交给运维核查服务端作业,然后新建 Harness 会话)。

观察到的情况会话处理使用者下一步
提交成功,有 job ID保存归属并锁住普通动作调用 dsb_job
查询仍在运行保留 activeJob稍后继续查询
查询到终态清除 activeJob检查内部结果,再继续操作
取消请求成功尚不把它当作执行终态继续查询确认停止
提交响应丢失或成功响应没有 job ID设置 batchUnknown由运维核查服务端作业,不能盲目重放

作业查询外层 ok:true 只说明“查到了”,执行是否成功还要看 data.status 和内部 data.ok。页面索引会随着前一步操作变化,因此不要把依赖新索引的多次点击盲目塞进同一个批次。

7. 资源释放为什么不能直接 shutdown

完整 entry 方法中注册的清理逻辑依次做:标记关闭、发出取消、等待本会话队列、处理未结束的作业、关闭自己的 task、删除 Map 条目。

有活跃作业时先 cancel_job,再查询到不再 running,才 close。取消是协作式的,不会撤销已点击的按钮。清理等待受 timeoutMs 限制,不能永远阻塞卸载。

batchUnknown 时直接报告清理失败,保留 task ID,避免关闭仍可能运行的作业。运维需要在 Java 服务端 list_jobs 中匹配 browserId,等待或取消作业后关闭任务,然后使用新的 Harness 会话。插件通用工具本身不暴露全局 list_jobs。

插件级 dispose() 使用 Promise.allSettled,一个会话清理失败不会阻止尝试清理其他会话,最后用 AggregateError 汇总失败。整个路径不调用共享服务的 shutdown。

8. 上传文件:传字节而不是把本机路径交给远程服务

这是 src/tools.ts 中的上传工具片段:

  add('upload', 'Upload a file from the Harness host to a file input. localPath is on the Harness host; bytes are transferred over HTTP even if browser service is remote.',
    z.strictObject({ ...target, localPath: z.string().min(1) }), async (a, e) => {
      checkTarget(a);
      const info = await stat(a.localPath);
      if (!info.isFile() || info.size > options.maxUploadBytes) throw new Error('Upload must be a regular file within maxUploadBytes');
      const bytes = await readFile(a.localPath, { signal: e.signal });
      if (bytes.length > options.maxUploadBytes) throw new Error('Upload exceeds maxUploadBytes');
      const { localPath, ...locator } = a;
      return command(e, 'upload_file', { ...locator, filename: basename(localPath), contentBase64: bytes.toString('base64') });
    });

用到的 Node import 是:

import { readFile, stat } from 'node:fs/promises';
import { basename } from 'node:path';

localPath 属于 Harness 主机,Java 服务不一定在同一台机器,所以请求发送 filename/contentBase64。stat 先检查普通文件及大小,读完再检查实际字节数,捕获检查与读取之间文件变大的情况;当前实现仍是内存读取,不是严格有界的流式上传。base64 还会增加约三分之一的传输体积。

const { localPath, ...locator } = a 从参数中分离出本机路径,避免把它误当作页面定位参数传给服务。index/selector 的二选一检查继续复用基础工具逻辑。

9. 截图:图片路径和模型看到图片是两件事

这是截图工具的实际代码:

  add('screenshot', 'Capture the page or a selector. Set view=true to attach image for a verified vision-capable model; otherwise return a saved screenshot URL.',
    z.strictObject({ fullPage: z.boolean().optional(), selector: z.string().optional(), view: z.boolean().optional() }), async (a, e) => {
      const attachments = ctx.get('attachments');
      if (a.view) {
        if (!attachments) throw new Error('view=true requires the Harness attachment service');
        const route = e.agent?.session.requestHeader()?.config;
        const provider = route?.provider ?? e.agent?.options.provider;
        const model = route?.model ?? e.agent?.options.model;
        const llm = ctx.get('llm');
        if (!provider || !model || !llm) throw new Error('Cannot verify the active model image capability; use view=false');
        const info = await llm.resolveModelInfo(provider, model, e.signal);
        if (!info.inputModalities?.includes('image')) throw new Error('Current model does not declare image input; use view=false and DOM text tools');
      }
      const { view, ...params } = a;
      const result = await command(e, 'screenshot', { ...params, inline: view ?? false });
      if (!result.ok || !view) return result;
      const data = result.data as Params;
      if (typeof data?.base64 !== 'string') throw new Error('Screenshot response missing base64');
      e.signal.throwIfAborted();
      const attachment = await attachments!.saveImage({ data: Buffer.from(data.base64, 'base64'), mediaType: 'image/png', name: 'browser-screenshot.png' });
      const { base64: _bytes, ...metadata } = data;
      return { ...result, data: metadata, attachment: attachment as unknown as Params };
    });

默认 view:false 只返回截图元信息、路径或 URL,减少图片传输。view:true 时流程是:

  1. 读取当前调用路由的 provider/model,缺失时使用 Agent 配置;确认宿主附件服务和 LLM 服务可用。
  2. 通过模型信息检查 inputModalities 是否含 image,不凭模型名字猜测视觉能力。
  3. 请求 screenshot(inline:true),拿到 base64 后解码为字节。
  4. 用 attachments.saveImage 保存为宿主附件,移除规范结果里的 base64。
  5. 由基础篇的 output.render 生成 image 内容块。

如果直接把 base64 放进文本,模型不会因此得到正常的图片输入,还会占用大量上下文。没有图片能力时,可以继续使用默认截图留档和 DOM 文本工具。

10. 用测试证明这些设计有效

工程的 test/plugin.test.mjs 已包含以下测试,运行 npm test 可验证:

测试情形要证明的行为
同一 Owner 并发 execute_js实际 HTTP 不重叠,且只 start 一次
两个不同 Owner使用不同的安全整数 task ID
排队点击被取消没发出点击,下一条请求仍正常执行
批次运行时再点击拒绝插入动作,观察终态后恢复
另一个 Owner 查询 job拒绝访问非本会话作业
批次响应被截断冻结会话,不重发,不假装清理成功
上传中文文件传的是实际文件字节,文件名保留
截图展示检查模型能力,保存附件,不把 base64 留在结果中

这些测试大多使用假 HTTP 服务,验证的是协议和状态管理。真实浏览器、目标 Harness 的激活、Web UI 图片显示是不同层次的验证,不能互相代替。

上一篇:HTTP 客户端与基础工具。下一篇:构建并安装到 DeepSeek Harness。

Edit this page
Last Updated: 10/6/26, 2:38 AM
Contributors: litongjava
Prev
开发 dsh-plugin-deepseek-browser-use:HTTP 客户端与基础工具
Next
把 dsh-plugin-deepseek-browser-use 构建并安装到 DeepSeek Harness