
先说结论这套岗位分析系统我最终用了 Spring AI 1.0.x 搭配 Redis Vector Store、Ollama 本地 Embedding 以及开放模型 Chat 能力来做 RAG 检索再用 Spring AI 的 Tool Calling 让大模型动态调用岗位统计和薪酬分析工具整套流程跑通之后从一份原始 JD 到输出结构化的岗位分析报告五分钟内能完成。整个过程里 RAG、Tool Calling、岗位分析系统这几个关键词是核心踩坑记录更是占了实践时间的大头。这篇内容适合两类人看。一类是正在做招聘平台、人才库、HR SaaS 相关业务的 Java 工程师想知道 Spring AI 这类框架能不能直接用在生产链路上另一类是刚接触 RAG 和 Tool Calling想找一个不绕弯、能落地的项目来理解这两者怎么配合的同学。我会把架构设计、核心代码、参数选择、模型调优和最终踩过的坑一起写出来。1. 项目整体设计与技术选型1.1 岗位分析系统到底要解决什么问题先把这个系统的功能边界说清楚不然容易做着做着就变成一个大杂烩 AI Demo。岗位分析系统承接的原始输入是一堆职位描述文本也就是通常说的 JD输出是结构化的分析结果包括技能要求分布、薪资区间、经验年限分布、岗位需求量趋势这些维度。我最初收到的需求是“能不能把历史岗位数据利用起来自动生成一份岗位分析报告别让 HR 一个个去翻 Excel。” 这个需求听起来不大但真实落地时涉及两条完全不同的数据链路。链路一侧是历史 JD 的非结构化文本它们沉在知识库里只能靠语义去匹配这就是 RAG 的典型场景链路另一侧是实时变化的岗位数据比如当前月份的招聘量、薪资城市系数这种数据要求实时性和准确性直接靠大模型“背出来”等于胡扯必须走工具调用。所以我在设计阶段给系统定了三个核心能力对历史 JD 做向量化入库和语义检索回答“过去一年后端工程师岗位的技能要求分布”。对实时统计类问题走 Tool Calling调用内部的岗位计数服务和薪酬分析服务拿到准确数字后再让模型组织语言。对混合问题采用“先检索、再判断、必要时候调工具”的决策模式也就是现在常说的 agentic RAG 思路。这三件事各自独立但最终要落到同一个大模型交互入口上。这个入口就是 Spring AI 的 ChatClient。1.2 为什么选 Spring AI 而不是 LangChain4j选型阶段我对比过 LangChain4j 和 Spring AI。作为长期维护 Java 后端的人我其实一开始更倾向 LangChain4j因为它的 RAG 生态组件更完整文档也更接近 LangChain 的叙事方式。但真正动手后我有两个实际顾虑。第一项目团队是纯 Spring Boot 技术栈后面要接公司内部的网关鉴权、配置中心和监控链路。LangChain4j 虽然也支持 Spring Boot 集成但它的自动配置和原生工程化能力不如 Spring AI 嵌入得深。第二Spring AI 的抽象模型更像 Spring 家族的风格一个 ChatClient 统一收口一个 VectorStore 统一向量操作一个 ToolCallbacks 做工具注册这种一致性让团队里其他人上手的成本低很多。这不代表 LangChain4j 不好它确实在功能丰富度上领先尤其是 Easy RAG 这种捷径式封装非常讨喜。但如果你的团队成员大多是 Spring 出身且后续要把模型能力无缝接入现有 Spring Boot 服务Spring AI 是更稳妥的底座。1.3 整体架构与数据流设计整个系统的架构可以拆成四层我直接用一条数据来串。第一层是数据接入层。JD 来源有手动上传的 Markdown 文件、从招聘系统同步的数据库记录以及少量网页爬取文本。接入后先做清洗去掉 HTML 标签、多余空白和无效字段再统一转成纯文本。第二层是向量化与存储层。清洗后的 JD 按固定窗口切分交给 Embedding 模型转成向量写入 Redis Vector Store同时保留原文和岗位 ID 作为元数据。第三层是智能编排层。用户的提问进来后先经过一个意图判断如果问题需要历史语义数据就走检索如果问题需要实时统计数字就走 Tool Calling如果两者混合就先用 RAG 召回一批相似岗位片段再让模型决定是否需要调用统计工具。第四层是输出层。大模型把检索到的内容、工具返回的统计结果和自身分析能力融合输出最终报告。这样设计的原因很直接把“记忆”交给 RAG把“计算和查数”交给 Tool Calling把“组织语言和逻辑推理”交给大模型各干各擅长的事。刚开始有人想直接把所有 JD 都塞进上下文让模型硬读但一次性超过十万字的文本既费 token 又容易丢细节RAG 本身就是为了解决这个问题存在的。2. 基础环境搭建与版本选型2.1 Spring AI 版本选择的第一个坑进入编码前版本这块你必须先停下来想清楚。Spring AI 从 0.8 到 1.0 的升级过程中API 变化非常大网上大量教程还停留在 0.8.x 甚至 snapshot 版本照着抄你会发现很多类已经找不到了。我所在的环境最终锁定的是 Spring AI 1.0.0 M6 之后的稳定路线并配合 Spring Boot 3.4.x这个组合经过验证是能顺利跑通 RAG 和 Tool Calling 的。如果你看到教程里出现AiClient、EmbeddingClient这种旧接口基本可以判断是旧版本内容。1.0 之后核心入口变成了ChatClientEmbeddingModel也取代了老接口。版本差异这件事我放在第五部分踩坑记录里重点说这里先给你一组没有踩坑的依赖组合。2.2 依赖引入与核心配置pom.xml 里最关键的是引入 Spring AI BOM统一管理版本号避免各种包版本不一致。下面是我这边实际跑通过的依赖集合。dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement实际业务依赖包括 Web、AI 核心、OpenAI 兼容接口、Ollama 接入和 Redis 向量存储dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-ollama/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-redis-store/artifactId /dependency我用的 Chat 模型是 OpenAI 兼容协议接入的同时 Embedding 走本地 Ollama 的 bge-m3 模型。这么做的好处是聊天的实时响应不需要本地算力而向量化这种高频且数据隐私敏感的环节完全留在本地不限速也不泄露文本内容。application.yml 的配置长这样spring: ai: openai: base-url: ${AI_GATEWAY_URL} api-key: ${AI_GATEWAY_KEY} chat: options: model: ${CHAT_MODEL:gpt-4o-mini} temperature: 0.2 ollama: base-url: http://localhost:11434 embedding: options: model: bge-m3注意 temperature 我刻意设成了 0.2。岗位分析是偏事实输出场景过高的随机性会让格式不稳定0.2 跑下来既能保留一点语感变化又不至于跑偏。2.3 Embedding 模型与向量库选型分析Embedding 模型的选择直接决定了 RAG 的上限。中文岗位文本包含大量专业词汇比如“分布式事务”“数据湖仓”“用户画像”如果 Embedding 模型中文理解能力弱检索到的相似片段就会明显偏离。我对比过几种方案OpenAI 的 text-embedding-3-small英文效果好中文效果一般如果走本地网关还有调用成本。Ollama 本地跑的 bge-m3中文支持好维度适中支持 8192 长度免费且隐私安全。bge-large-zh中文检索精度在同类里更突出但模型文件更大CPU 推理慢需要 GPU 才流畅。阿里云 DashScope 的 text-embedding-v3中文效果好在线限流低但引入了外部 API 依赖。最终我选 bge-m3 作为主力原因很朴素本地部署、免费、中文够用。bge-m3 输出维度 1024在 Redis 向量检索里的召回效果实测是达标的。向量库方面我用的 Redis Vector Store原因也很直接系统本身已经依赖 Redis 做缓存不需要额外引入 Milvus 这种重型组件。如果 JD 数据量超过百万文档级别再迁移到 Milvus 或者 Elasticsearch 也不迟。2.4 要不要上本体图谱和 GraphRAG在项目推进中期团队里有人提议引入本体和 GraphRAG把岗位之间的层级关系比如“后端工程师 — Java 工程师 — 大数据开发工程师”建成知识图谱。这个提议本身有道理但我在评估后决定当前阶段不做。原因在于数据规模和问题类型不匹配。岗位 JD 之间的关系更多是“相似”而不是“强语义关联”用户问的问题多数是“某类岗位的平均薪资”“某技能出现在哪些岗位”这些通过向量相似度检索完全可以覆盖。本体图谱的优势在于回答“哪些岗位技能继承自同一体系”这类关系链问题但目前需求没有到这个深度引入本体意味着实体识别、关系抽取、图谱存储和维护成本都上了一个台阶。这个决策我建议每个做 RAG 项目的人都思考一遍。GraphRAG 和本体 RAG 不是万灵药它们解决的是多跳推理和关系问答如果你的知识库本身就是松散文档集硬上图谱只会拖慢开发节奏。先跑纯向量 RAG等实际检索命中率上不去了再评估图谱增强这个路线最稳。3. RAG 检索链路与知识库搭建实战3.1 岗位 JD 的加载与切分处理RAG 的第一步是把文档加载进系统。岗位 JD 我在系统里统一整理成了 Markdown 文件每份文件包含岗位名称、所属部门、工作职责、任职要求、薪资范围、base 地等字段。原本也想用 PDF但 PDF 解析在 Java 生态里容易出现乱码和表格错位所以我坚持推成 Markdown。加载代码我用的是 Spring AI 的TextItemReadervar resource new ClassPathResource(jd/history/); var reader new TextItemReader(resource); var docs reader.get();但这里有个很容易被忽视的问题一份完整的 JD 可能有三四千字直接整篇向量化会让 embedding 丢失局部细节。所以我用了TokenTextSplitter做切分核心参数是 chunkSize 和 chunkOverlap。var splitter new TokenTextSplitter.Builder() .withChunkSize(350) .withChunkOverlap(80) .build(); var chunks splitter.apply(docs);chunkSize 我压到了 350而不是常见的 500理由很实际岗位分析的问题通常聚焦在某一段任职要求或者某一条职责描述过大的 chunk 会导致检索到的片段里有效信息占比低大模型在总结时容易被无关内容干扰。80 的 overlap 是为了避免切分时把“熟悉 Spring Cloud 全家桶”这种关键句截断。切分后还要给每个 chunk 补上元数据否则检索到了也找不到原始岗位。元数据我至少包含岗位 ID、岗位名称、来源文件名chunks.forEach(doc - { doc.getMetadata().put(jobId, ...); doc.getMetadata().put(jobTitle, ...); });3.2 向量化入库与索引策略向量化入库这部分Spring AI 封装得很优雅。拿到切分好的文档列表之后直接调用 VectorStore 的 add 方法就可以。var redisStore RedisVectorStore.builder(redisConnectionFactory, vectorStoreProperties) .build(); redisStore.add(chunks);一句话就完成了 embedding 计算和向量写入因为 Spring AI 会自动把 EmbeddingModel 注入到 VectorStore 里。这里我必须强调一个基础配置问题Redis Vector Store 需要 Redis Stack 支持向量搜索能力普通 Redis 实例是跑不了的。我们当时内网 Redis 版本偏老直接报语法错误最后只好单独起了一个 Redis Stack 实例。向量索引的参数也需要留意。Redis 创建向量索引时可以指定距离算法我这边用的是 COSINE 余弦距离和 bge-m3 的默认向量语义相匹配。如果换用 IP 内积距离得分分布会完全不同后续调阈值时容易一头雾水。查看索引是否建好可以走 Redis CLI 验证FT._LIST FT.INFO idx_job_vector3.3 相似度检索与命中率调优检索接口我封装在服务层查询时带上 TopK 和相似度阈值var searchRequest SearchRequest.query(userQuery) .withTopK(6) .withSimilarityThreshold(0.55) .build(); var similarDocs redisStore.similaritySearch(searchRequest);TopK 我一开始用 3发现答案覆盖度不够一些技能横向对比问题答不全。后来改成 6召回信息量刚好上下文也不会过于臃肿。相似度阈值从 0.7 一路调到 0.55这个变化反映了中文 Embedding 的一个典型问题阈值设置太高语义相近但字面差距大的片段会被过滤掉最终模型什么都回答不了。调阈值这件事不能拍脑袋我在本地准备了一组预置问题集每类问题至少 10 条逐条跑检索统计命中率。比如问“Java 后端岗位常见的中间件要求”期望召回片段里必须出现包含 Redis、RabbitMQ、Kafka 相关文字的 chunk。人工标记期望答案之后用召回片段里是否包含关键词来算 hit rate。刚开始只到 60%换用 bge-m3 后升到了 82% 左右基本够用。3.4 上下文组装与 Prompt 模板设计检索引擎返回的是一堆文档片段这些片段不能直接拼给大模型必须放进一个结构清晰的 Prompt 模板里。我用的模板长这样String promptTemplate 你是资深 HR 数据分析师。我提供若干从岗位知识库中检索到的相似岗位片段。 片段内容用 context 和 /context 包围。 context {context} /context 请基于这些片段回答用户问题{question} 要求 1. 优先使用片段中的信息不要编造岗位数据。 2. 若片段信息不足请明确回答“当前知识库中缺乏足够信息”。 3. 输出时使用编号列表便于阅读。 ;这里我踩过一个特别典型的坑最初模板里没有“信息不足时如何回应”的约束结果模型在检索结果不相关时强行脑补答案看起来说得很顺实际完全是幻觉。加了这个约束之后至少系统不再一本正经地胡说八道。上下文的长度控制也要重视。六条检索片段每条约 350 token加上问题本身总输入大概在 2500 token 以内。这个量级在当前主流模型上下文窗口里毫无压力但如果你换成更大的 chunk 和更多的召回数量就要注意模型上下文溢出的问题。解决办法是给 PromptTemplate 加 token 计数器超过阈值时优先丢弃打分低的片段而不是全部塞进去。4. Tool Calling 功能实现与编排4.1 配置需求查询工具的实现方式RAG 解决了历史知识的问题但岗位分析还需要实时数据。比如用户问“上个月前端岗位的需求量和平均薪资”这些数据根本不在知识库里必须查数据库或者调统计服务。这时就用到了 Tool Calling。Spring AI 1.0 里定义一个工具很简单核心是一个方法加一个描述注解。我封装了一个岗位需求统计工具入参是岗位名称和地区返回的是最近一段时间的需求量和薪资上下限。Component public class JobDemandTool { Tool(description 查询指定岗位和地区最近3个月的需求数量及平均薪资区间入参为岗位名称和地区) public JobMarketSummary queryJobMarket(String jobTitle, String region) { return jobMarketMapper.selectSummary(jobTitle, region); } }使用起来更简单直接把工具类的 Bean 传给 ChatClientChatClient chatClient ChatClient.builder(chatModel) .defaultTools(new JobDemandTool()) .build();Spring AI 运行时会自动把这个工具的方法结构、参数模型和描述一起传给大模型模型在回答相关问题时就会生成调用请求框架负责把请求转换成 Java 方法调用并把返回值传回给模型做后续生成。4.2 工具描述词怎么写才能让模型准确调用Tool Calling 这里最容易被低估的是描述词的质量。同一个方法描述写“工具方法”和“查询指定岗位和地区最近3个月的需求数量及平均薪资区间”模型调用的准确率天差地别。我总结出来的经验是描述里要包含触发条件、入参含义和输出粒度。比如触发条件当问题中包含具体岗位名称和地区时。入参含义jobTitle 指的是岗位类别比如后端工程师不是具体公司名称。输出粒度返回最近3个月的月度汇总。参数尽量少最多两三个。参数过多不仅让模型犹豫还会提高解析错误的概率。像岗位名称和地区这种固定维度放到参数里没问题像时间范围除非用户指定否则直接在方法内部写死成最近三个月不要在参数层面留给模型做选择。还有一个细节工具方法的返回值尽量是结构简单的对象或者纯字符串。复杂嵌套对象虽然在 Java 层面没问题但序列化成 JSON 之后模型要花更多上下文去理解偶尔还会漏字段。我这里直接把查询结果封装成简短的字符串比如“数量120薪资区间18K-25K”模型直接用这个结果生成回答省去它自己重新组织数字的风险。4.3 多工具协作场景下的调用编排实际业务里模型往往不止调用一个工具。岗位分析报告可能需要同时调岗位需求统计、技能分布统计、薪酬对比三个工具。Spring AI 的 ChatClient 支持同时注册多个工具模型可以自主决定调哪个、调几次。ChatClient chatClient ChatClient.builder(chatModel) .defaultTools(new JobDemandTool(), new JobSkillDistributionTool(), new JobSalaryCompareTool()) .build();模型在生成过程中会自行规划调用顺序。比如问“分析 Java 岗位的市场竞争格局”模型可能先调岗位需求统计确定市场热度再调技能分布工具确认常见技能要求最后调薪酬对比工具给出薪资水平然后汇总成一段分析。这里我要提醒一点如果模型一次生成里出现多个工具调用也就是并行调用一定要检查你的模型和框架是否支持。实测下来并行调用在响应速度上有明显优势但对模型的指令遵循能力要求更高。如果你的模型版本偏老反而容易漏调用。这种情况下最简单的规避方式是在 Prompt 里明确写“请一次只调用一个工具按顺序获取数据”牺牲一点速度换稳定性。4.4 RAG 与 Tool Calling 的协同策略系统开发到后期我遇到最复杂的问题是两类能力怎么协同。用户问“Java 岗位通常要求哪些技能目前市场上需求量如何”前半截是知识检索后半截是实时统计。我的处理策略分三步走。第一步先用 RAG 把相关岗位片段召回得到技能要求的候选信息。第二步把这些片段作为上下文和用户问题一起发给模型同时注册好工具。第三步模型根据上下文和问题自行判断是否需要调用工具获取实时数据。从实际效果看这种“先检索、再决策”的方式能很好地处理混合问题。有一点需要控制好当上下文里已经有检索片段时模型可能会偷懒不调工具直接基于片段编一个统计数据出来。我自己的解决办法是在 System Prompt 里加一条强约束“涉及数字、趋势、需求量等事实信息必须通过工具获取不得根据知识库片段推测。”实测调这个约束后模型调用工具的主动性明显提高。5. 踩坑记录与问题排查实录5.1 Spring AI 版本升级带来的 API 变动这个坑是我踩得最疼的必须单独讲。Spring AI 从孵化到正式版API 变化太快网上很多示例代码一跑就报NoClassDefFoundError或者方法不存在。举几个具体的例子旧版本的AiClient.call()在新版本已变成ChatClient的方式EmbeddingClient换成了EmbeddingModel向量库的 builder 方法名也有调整比如withVectorStoreProperties的参数对象形态变了。我最初按 0.8.1 snapshot 教程写代码花费大量时间在编译报错上。相对稳妥的策略是先锁定一个版本直接用 Spring AI BOM 管理按官方文档的新 API 写。除非有强需求否则别频繁升级。我最终停在 1.0.0 这一个版本上跑完整个项目功能完全够用。Spring AI 2.0 我也关注了模块拆分和响应式增强确实诱人但生产系统不能追新等社区沉淀出一个稳定版本再计划迁移。5.2 中文 Embedding 效果差导致的检索质量翻车这个问题在中文 RAG 项目里大概率会遇到。系统刚上线时我用的是通用英文 Embedding 模型中文 JD 全部被它一股脑映射到一个不理想的空间里。用户问“数据分析岗位有什么要求”召回的片段经常是后端开发的 JD语义相关性一塌糊涂。解决路径是替换成 bge-m3并且做了一轮本地效果回归。替换之后中文相似度分数整体提高了约 0.1 到 0.15原来 0.6 左右算勉强相关现在 0.55 就已经能召回到目标片段。这里也建议你维护一个属于自己的测试集别用模型自带的评测结果因为岗位文本的专业词汇分布和公开数据集差很远。bge-m3 还有一个用法值得提它可以同时输出稠密向量和稀疏向量做混合检索。稠密向量捕捉语义稀疏向量精确匹配关键词两者结合能减少“语义对但缺关键词”和“关键词对但语义偏”这两种极端情况。如果后续要继续优化 hit rate可以把稀疏检索作为第二路召回再做结果融合这个方向我目前在验证中。5.3 Tool Calling 参数解析失败与调用不稳定的坑Tool Calling 在实战里最常见的故障有两类。一是模型生成的参数类型和工具签名对不上最常见的表现是模型明明要传 int 类型的月份数结果生成的是字符串“3”二是模型在工具调用中途中断返回一段半截 JSON导致框架解析失败。针对参数类型问题我的做法是入参全部用字符串内部再转换Tool(description 传入岗位名称和地区查询需求数据) public String queryJobMarket(String jobTitle, String region) { JobsQuery query new JobsQuery(jobTitle, region); // 内部处理 }这样模型不需要纠结数字类型JSON 生成成功率大幅提高。针对半截 JSON 问题Spring AI 内部有解析失败的处理机制但更稳妥的是在 Prompt 里明确要求“只要JSON不要输出多余说明文字”。另外不要过度依赖框架的自动修复日志里一旦出现ToolExecutionException一定要把模型原始的生成内容打出来看定位是描述词问题还是模型问题。5.4 元数据丢失与结果无法溯源RAG 项目上线后业务方问得最多的就是“你给我的数据是从哪段 JD 里来的”。如果检索时只返回内容片段而不返回来源就无法满足这个溯源需求。我初期在切分时没重视 metadata 维护导致部分文档的岗位 ID 确实没带上。后面强制规定每个 chunk 必须包含 jobId、jobTitle、sourceFile 三个字段检索结果封装时直接把这几个元数据透传出去生成报告的时候再渲染成引用来源。如果用了自定义切分器也要检查切分后的 Document 对象是否保留了原始 metadata有些切分实现会生成新对象导致元数据丢失。调试方法很简单打印几个切分结果的getMetadata()看看是不是空。5.5 常见问题速查表我把项目里最有代表性的问题整理成一张速查表方便你排查时直接对照。问题典型现象主要原因解决办法版本报错编译时找不到方法或类Spring AI 版本过旧/过新锁定 1.0.x使用 BOM 管理版本中文检索差相似岗位召回不相关Embedding 模型中文能力弱替换 bge-m3配合本地测试集调阈值准确率低模型回答模糊或无关chunkSize 过大/召回数量不足缩小 chunk 到 350TopK 提到 6模型编数字回答有具体数字但无依据数据不在知识库且未调工具Prompt 强制约束必须调工具取数工具不调用模型忽略工具直接回答工具描述词不清晰描述中写明触发条件、入参含义、输出粒度参数解析失败工具执行时类型转换报错模型生成了错误 JSON 类型入参全部用 String内部再转换无来源可查分析结果无法回溯元数据丢失强制设置 jobId 等 metadata 字段Redis 报错相似度搜索语法异常普通 Redis 无向量能力改用 Redis Stack 并检查索引5.6 效果数据与性能指标项目跑通之后我做了一轮基本的指标复盘给同样在调研的人一个参考基线。检索命中率方面30 条预置岗位问题中模型输出里包含预期关键词的占 25 条hit rate 约 83%。这里说的命中率指的是“最终回答中是否出现了目标信息点”不是指答案完全正确。端到端准确率如果想继续提高下一步瓶颈主要在问题改写和多跳召回而不是向量库本身。性能上本地 bge-m3 Embedding 单条 JD约 800 token向量化耗时约 300 毫秒一批 200 条 JD 入库总耗时约一分钟对离线同步来说是完全可以接受的。在线查询时向量检索本身在几毫秒到几十毫秒量级真正的耗时大头在模型生成答案一次完整分析报告生成大概需要 10 到 20 秒。这个延迟放在异步任务里没有任何问题但如果要做同步 API 暴露给前端建议直接改异步轮询模式别让 HTTP 长连接一直挂着。成本方面本地 Embedding 把最大的费用大头切掉了在线模型只承担对话生成部分。每天 5000 次请求量级下token 费用相比全量塞上下文的方案能省下 70% 以上这也是 RAG 在实际业务里最有价值的地方之一。最后分享一个我在整个项目里体会最深的事。岗位分析系统看起来是个小应用但它把 RAG 和 Tool Calling 这两种大模型落地方式完完整整地串了一遍。RAG 负责让模型“读得懂”企业私有知识Tool Calling 负责让模型“查得到”实时数据两者缺了谁这个系统都会变成只能聊天、不能干活的玩具。你在搭建自己的系统时建议先别急着堆技术组件把问题拆成“哪些适合检索、哪些适合计算、哪些适合模型推理”再回头选型会清晰很多。