当前位置: 首页 > news >正文

【Javaweb学习|实训总结|Week2】个人疑问记录、大模型API接入

本篇笔记主要记录第二周实训的知识总结以及个人遇到的问题及解答,用于日后复习回顾和知识巩固,希望可以帮到同样在学Javaweb的大家
在这里插入图片描述

文章目录

  • Week2
    • 一.个人疑问记录
    • 二.大模型 API 接入
      • 1)JavaScript调用API
          • 2.2 构建请求参数
          • 2.3 创建 XMLHttpRequest 对象
          • 2.4 设置请求头(Headers)
          • 2.5 处理响应结果(成功)
          • 2.6 处理网络错误
          • 2.7 发送请求
          • 完整流程图解
        • Markdown式展示输出内容
        • 流式输出内容
      • 2)多轮对话
        • kimi API 多轮对话实现
          • 步骤 1:定义全局变量存储对话历史
          • 步骤 2:修改请求参数构建逻辑
          • 步骤 3:处理响应并更新对话历史
          • 步骤 4:用户输入新问题时更新历史

Week2

一.个人疑问记录

一些学习时遇到疑问的记录

D2的三个典例

1

document.getElementById('total').textContent

部分说明
document整个网页文档
.getElementById('total')找到 id="total" 的 HTML 元素
.textContent获取或设置该元素的纯文本内容
// 1. 获取这个元素
let totalElement = document.getElementById('total');// 2. 查看它当前的文本内容
console.log(totalElement.textContent); // 输出:0// 3. 修改它的文本内容(这才是你问的重点!)
totalElement.textContent = "100";

2

 <script>// 商品对象const product = {name: "无线鼠标",price: 99.9,stock: 100,brand: "罗技",category: "电脑外设",// 对象方法:格式化价格formatPrice: function() {return `${this.price.toFixed(2)}`;},// 对象方法:检查库存状态checkStock: function() {return this.stock > 0 ? "有货" : "缺货";}};// 显示商品信息function showProductInfo() {const infoEl = document.getElementById('productInfo'); // 获取显示区域infoEl.innerHTML = `<div class="info">名称:${product.name}</div><div class="info">品牌:${product.brand}</div><div class="info">价格:${product.formatPrice()}</div><div class="info">库存:${product.stock}${product.checkStock()})</div><div class="info">分类:${product.category}</div>`;}// 更新库存function updateStock(change) {if (product.stock + change >= 0) {product.stock += change;showProductInfo();} else {alert("库存不足!");}}// 初始化显示showProductInfo();</script>

JavaScript 代码的执行有特定的规则:

执行顺序

  1. 函数定义不会立即执行
    • 当浏览器遇到 function showProductInfo() { ... }function updateStock(change) { ... }
    • 只是定义了函数,不会立即执行函数内的代码
    • 函数只有在被调用时才会执行
  2. 从上到下执行的部分
    • const product = { ... } - 定义商品对象,立即执行
    • 函数定义 - 只是注册函数,不执行函数体
    • showProductInfo(); - 这是函数调用,会立即执行

执行时机

  1. 页面加载时执行
    • 当浏览器解析 HTML 文档,遇到 <script> 标签时
    • 会立即下载并执行其中的 JavaScript 代码
    • 这个过程发生在页面渲染过程中
  2. 按照在页面中的位置顺序执行
    • 在你的例子中,<script> 位于 <body> 的底部
    • 此时页面的 HTML 结构已经基本加载完成
    • 浏览器可以找到 id="productInfo" 的元素

为什么放在底部

<script> 放在页面底部有好处:

  • 确保 HTML 元素已经创建完成
  • 避免 JavaScript 找不到页面元素的错误
  • 不会阻塞页面内容的显示

3

// 显示商品列表
function renderProducts(list) {const container = document.getElementById('productContainer');container.innerHTML = list.map(product => `<div class="product-item"><div><strong>${product.name}</strong>(${product.brand} - ${product.category})</div><div>¥${product.price.toFixed(2)}</div></div>`).join('');
}

list.map(product => ' ' )意思是把List中的每一个元素起一个别名product 通过操作product来操作list里的每一个元素

.join('') 用于将数组中的所有元素连接成一个字符串

innerHTML 只接受字符串作为值,它会将这个字符串当作 HTML 代码来解析和显示。

.join('')作用就是把div整个区块转换为字符串然后通过innerHtml输出到屏幕上,div可以转换成字符串

在 JavaScript 中,任何 HTML 标签都可以用字符串形式表示

// 按价格范围筛选
function filterByPrice(min, max) {const filtered = products.filter(p => p.price >= min && p.price < max);renderProducts(filtered);
}

pproducts 数组中每个元素的临时变量名(别名),你可以随便起名字

工作原理

filter() 方法会:

  1. 遍历 products 数组中的每个元素
  2. 将当前元素赋值给参数变量(如 p
  3. 执行箭头函数中的条件判断
  4. 如果返回 true,则将该元素加入结果数组
// 按价格排序
function sortByPrice() {// 复制数组后排序(避免修改原数组)const sorted = [...products].sort((a, b) => a.price - b.price);renderProducts(sorted);
}

比较函数 (a, b) => a.price - b.price 的返回值决定了排序顺序:

  • 返回负数a 排在 b 前面
  • 返回 0ab 位置不变
  • 返回正数a 排在 b 后面

不能直接用 a.price > b.price因为:

返回值类型不对

  • a.price > b.price 返回 truefalse
  • sort 需要数字类型的返回值

作业二

let student = {name: "张三",age: 20,major: "计算机科学",scores: {math: 90,english: 85,chinese: 95}};// 1.遍历并打印对象的一级属性(name、age、major、scores);for (let key in student) {console.log(key + ':' + student[key]);}

可以.具体的元素名称 不可以.暂时的变量 ×student.key

  1. student.key - 访问的是对象中名为 “key” 的属性
    • 这里 JavaScript 会查找 student 对象中名为 key 的属性
    • 但你的 student 对象中没有名为 key 的属性
    • 所以返回 undefined
  2. student[key] - 访问的是对象中名为 key 变量值的属性
    • 这里 key 是变量,存储着实际的属性名
    • 例如当 key = "name" 时,student[key] 相当于 student["name"]
    • 这样就能正确获取对应的属性值

input的type

type作用示例
text单行文本输入<input type="text" placeholder="姓名">
password密码输入(隐藏字符)<input type="password">
email邮箱输入(自动校验格式)<input type="email">
number只允许数字<input type="number">
checkbox多选框<input type="checkbox"> 同意条款
radio单选按钮<input type="radio" name="gender"> 男
file文件上传<input type="file">
date日期选择器<input type="date">
hidden隐藏字段(不显示)<input type="hidden" value="123">
submit提交按钮<input type="submit" value="提交">

二.大模型 API 接入

1)JavaScript调用API

1.html页面结构

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>kimi API调用演示</title><style>.container { width: 800px; margin: 20px auto; }#question { width: 600px; height: 100px; padding: 10px; margin-bottom: 10px; }#answer { width: 600px; min-height: 150px; padding: 10px; border: 1px solid #ccc; margin-top: 10px; }button { padding: 8px 20px; background: #007bff; color: white; border: none; cursor: pointer; }</style>
</head>
<body><div class="container"><h2>kimi问答</h2><input id="question" placeholder="请输入你的问题"></input><br><button onclick="callKimiAPI()">发送请求</button><div><h3>kimi回答:</h3><div id="answer"></div></div></div><script src="api.js"></script>
</body>
</html>

2.js代码

// 1. 配置基础信息(替换为自己的AK/SK,注意SK不要泄露)
const API_KEY = "个人的API_KEY";
const API_URL = "https://api.moonshot.cn/v1/chat/completions";
// 2. 定义API调用函数
function callKimiAPI() {// 2.1 获取用户输入的问题const question = document.getElementById("question").value.trim();if (!question) {alert("请输入问题!");return;}// 2.2 构建请求参数(按kimi API文档要求)const requestData = {model: "kimi-k2-0711-preview", // 选择kimi对话模型messages: [{ role: "user", content: question } // 对话历史,仅包含用户问题],temperature: 0.7 // 可选参数,控制响应随机性(0.7为适中值)};// 2.3 创建XMLHttpRequest对象(AJAX核心)const xhr = new XMLHttpRequest();xhr.open("POST", API_URL, true); // 第三个参数为true,表示异步请求// 2.4 设置请求头(关键:指定内容类型、携带API密钥)xhr.setRequestHeader("Content-Type", "application/json");xhr.setRequestHeader("Authorization", `Bearer ${"你自己的AK"}`); // 部分接口用此格式传递AK,// 2.5 处理响应结果xhr.onload = function() {if (xhr.status >= 200 && xhr.status < 300) {// 响应成功:解析JSON数据,展示答案const response = JSON.parse(xhr.responseText);const answer = response.choices[0].message.content;document.getElementById("answer").innerText = answer;} else {// 响应失败:展示错误信息const error = JSON.parse(xhr.responseText);document.getElementById("answer").innerText = `调用失败:${error.error.message}`;}};// 2.6 处理网络错误xhr.onerror = function() {document.getElementById("answer").innerText = "网络错误,请检查网络连接!";};// 2.7 发送请求(将JSON对象转为字符串)xhr.send(JSON.stringify(requestData));
}
2.2 构建请求参数
const requestData = {model: "kimi-k2-0711-preview",messages: [{ role: "user", content: question }],temperature: 0.7
};

说明

  • requestData:这是要发送给 Kimi API 的数据对象。
  • model:指定使用的模型版本(这里是 kimi-k2-0711-preview)。
  • messages:对话历史,是一个数组,每条消息有:
    • role: 角色,可以是 "user"(用户)、"assistant"(AI)、"system"(系统提示)
    • content: 实际内容(比如用户的问题)
  • temperature: 0.7:控制 AI 回答的“随机性”或“创造力”。
    • 越接近 0 → 越保守、确定(适合数学、事实问答)
    • 越接近 1 → 越有创意、多样(可能出错)
    • 0.7 是一个适中值,平衡准确性和自然度。

✅ 这部分定义了你要问什么、用哪个模型、以及生成风格。


2.3 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();
xhr.open("POST", API_URL, true);

说明

  • XMLHttpRequest(简称 XHR)是浏览器提供的原生对象,用于发送 HTTP 请求(即 AJAX)。
  • xhr.open(method, url, async)
    • "POST":请求方法,因为我们要发送数据到服务器。
    • API_URL:Kimi API 的地址,例如 "https://api.moonshot.cn/v1/chat/completions"(你需要自己定义这个变量)。
    • true:表示这是一个异步请求,不会阻塞页面。

✅ 这一步准备好了“信封”,等待填写内容并寄出。


2.4 设置请求头(Headers)
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("Authorization", `Bearer ${"API_KEY"}`);

说明

请求头告诉服务器:

  • 你发的是什么类型的数据
  • 你是谁(权限验证)
请求头作用
"Content-Type": "application/json"告诉服务器:我发的是 JSON 格式的数据
"Authorization": "Bearer your-api-key"提供你的 API 密钥(Access Key),用于身份认证

注意:

  • Bearer 是一种标准认证方式,格式为:Bearer <your-api-key>

✅ 没有正确的 Authorization,API 会拒绝你的请求(401 错误)。


2.5 处理响应结果(成功)
xhr.onload = function() {if (xhr.status >= 200 && xhr.status < 300) {const response = JSON.parse(xhr.responseText);const answer = response.choices[0].message.content;document.getElementById("answer").innerText = answer;} else {const error = JSON.parse(xhr.responseText);document.getElementById("answer").innerText = `调用失败:${error.error.message}`;}
};

说明

  • xhr.onload:当服务器返回响应后,执行这个函数。
  • xhr.status:HTTP 状态码
    • 200-299:成功(如 200 OK)
    • 其他:失败(如 401 未授权,500 服务器错误)

成功时:

  1. JSON.parse(xhr.responseText):把服务器返回的 JSON 字符串转成 JS 对象。

  2. response.choices[0].message.content:Kimi API 返回的结果结构通常是:

    {"choices": [{"message": {"role": "assistant","content": "这里是AI的回答"}}]
    }
    
  3. document.getElementById("answer").innerText = answer:把 AI 的回答显示在页面上,比如 <div id="answer"></div>

失败时:

  • 解析错误信息,并显示在页面上,例如:“调用失败:Invalid API Key”

2.6 处理网络错误
xhr.onerror = function() {document.getElementById("answer").innerText = "网络错误,请检查网络连接!";
};
  • xhr.onerror:当发生网络问题(如断网、DNS 错误、CORS 跨域)时触发。
  • 它不会进入 onload,而是直接走 onerror
  • 提示用户“网络错误”,提升用户体验。

2.7 发送请求

为啥send放最后

先准备好接收和处理响应的机制,再发送请求

xhr.send(JSON.stringify(requestData));
  • JSON.stringify(requestData):把 JS 对象转成 JSON 字符串(因为 HTTP 只能传输文本)。
  • xhr.send(...):真正把请求发出去!

一旦调用 send(),浏览器就会向 Kimi 服务器发送请求。


完整流程图解
用户输入问题↓
JavaScript 构建 requestData(JSON对象)↓
创建 xhr 对象,配置 POST 请求↓
设置请求头(Content-Type + Authorization)↓
xhr.send(JSON.stringify(requestData)) → 发送到 Kimi 服务器↓
服务器处理,返回 AI 回答(JSON)↓
xhr.onload 触发 → 解析 response → 显示 answer↓
如果失败 → xhr.onerror 或 status 错误处理

附 XMLHttpRequest 就像一个“智能快递员”

快递场景对应 XHR 操作
你要寄快递你创建一个 XMLHttpRequest 对象
填写收件人地址(API URL)调用 xhr.open("POST", "https://api.moonshot.cn/...")
打包物品(请求数据)准备 requestData(如问题、模型参数)
贴标签(请求头)xhr.setRequestHeader(...)(如内容类型、密钥)
派出快递员xhr.send() 发送请求
快递员带回包裹(响应)xhr.onload 接收服务器返回的数据
快递出问题(网络断了)xhr.onerror 处理网络错误
Markdown式展示输出内容

在接收响应后对内容进行处理,主要是添加 Markdown 渲染功能

  1. 首先需要引入一个 Markdown 渲染库(如 marked.js)
  2. 修改响应处理部分,将文本内容通过渲染库转换为 HTML
  3. 使用innerHTML而非innerText插入到页面中
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>kimi API调用演示(支持Markdown)</title><style>.container { width: 800px; margin: 20px auto; }#question { width: 600px; height: 100px; padding: 10px; margin-bottom: 10px; }#answer { width: 600px; min-height: 150px; padding: 10px; border: 1px solid #ccc; margin-top: 10px;/* 添加Markdown内容的基础样式 */line-height: 1.6;}button { padding: 8px 20px; background: #007bff; color: white; border: none; cursor: pointer; }/* Markdown样式增强 */#answer h1 { font-size: 1.8em; margin: 1em 0; }#answer h2 { font-size: 1.5em; margin: 0.8em 0; }#answer p { margin: 0.5em 0; }#answer pre { background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; }#answer code { background: #f0f0f0; padding: 2px 4px; border-radius: 2px; }#answer ul, #answer ol { margin: 0.5em 0 0.5em 2em; }</style><!-- 引入marked.js用于Markdown渲染 --><script src="https://cdn.bootcdn.net/ajax/libs/marked/15.0.3/marked.min.js"></script>
</head>
<body><div class="container"><h2>kimi智能问答(支持Markdown输出)</h2><input id="question" placeholder="请输入你的问题(如:用Markdown格式介绍kimi大模型的特点)"></input><br><button onclick="callKimiAPI()">发送请求</button><div><h3>kimi响应:</h3><div id="answer"></div></div></div><script src="api.js"></script>
</body>
</html>
// 1. 配置基础信息
const API_KEY = "你自己的AK";
const API_URL = "https://api.moonshot.cn/v1/chat/completions";
// 2. 定义API调用函数
function callKimiAPI() {// 2.1 获取用户输入的问题const question = document.getElementById("question").value.trim();if (!question) {alert("请输入问题!");return;}// 2.2 构建请求参数(按kimi API文档要求)const requestData = {model: "kimi-k2-0711-preview", // 选择kimi对话模型messages: [{ role: "system", content: "请用Markdown格式回答我的问题,包括必要的标题、列表、代码块等格式" },{ role: "user", content: question } // 对话历史,包含系统提示和用户问题],temperature: 0.7 // 可选参数,控制响应随机性(0.7为适中值)};// 2.3 创建XMLHttpRequest对象(AJAX核心)const xhr = new XMLHttpRequest();xhr.open("POST", API_URL, true); // 第三个参数为true,表示异步请求// 2.4 设置请求头(关键:指定内容类型、携带API密钥)xhr.setRequestHeader("Content-Type", "application/json");xhr.setRequestHeader("Authorization", `Bearer ${"你自己的AK"}`);// 2.5 处理响应结果xhr.onload = function() {if (xhr.status >= 200 && xhr.status < 300) {// 响应成功:解析JSON数据,展示答案const response = JSON.parse(xhr.responseText);const markdownContent = response.choices[0].message.content;// 将Markdown内容转换为HTMLconst htmlContent = marked.parse(markdownContent);// 使用innerHTML插入渲染后的HTMLdocument.getElementById("answer").innerHTML = htmlContent;} else {// 响应失败:展示错误信息try {const error = JSON.parse(xhr.responseText);document.getElementById("answer").innerText = `调用失败:${error.error.message}`;} catch (e) {document.getElementById("answer").innerText = `调用失败,状态码:${xhr.status}`;}}};// 2.6 处理网络错误xhr.onerror = function() {document.getElementById("answer").innerText = "网络错误,请检查网络连接!";};// 2.7 发送请求(将JSON对象转为字符串)xhr.send(JSON.stringify(requestData));
}
  1. 引入 Markdown 渲染库:通过 CDN 引入了 marked.js,这是一个轻量的 Markdown 解析库
  2. 添加系统提示:在 messages 数组中增加了一个 system 角色的消息,明确告诉 kimi 需要返回 Markdown 格式的内容
  3. 处理 Markdown 渲染:
    • 使用marked.parse()将 kimi 返回的 Markdown 文本转换为 HTML
    • 使用innerHTML替代innerText将渲染后的 HTML 插入到页面中
  4. 添加基础样式:为常见的 Markdown 元素(标题、列表、代码块等)添加了基础样式,使展示效果更好
流式输出内容

流失输出即边生成边展示,需要利用 kimi API 支持的流式响应功能,并通过监听progress事件实时处理增量数据。

  1. 核心参数设置
    在请求参数中添加了stream: true,告诉 kimi API 需要启用流式响应模式,此时 API 会分多次返回生成的内容,而不是等待全部生成完成后一次性返回。
  2. 分块数据处理
    流式响应采用 SSE(Server-Sent Events)格式,每个数据块以data:开头,以\n\n结尾。我们通过progress事件监听这些分块数据,实时解析并处理。
  3. 增量渲染逻辑:
    • 使用streamContent变量累积所有分块的内容
    • 每次收到新的分块数据,就将其追加到streamContent
    • 立即使用marked.parse()将累积的内容转换为 HTML 并更新到页面
    • 当收到[DONE]标记时,说明流式输出完成

2)多轮对话

大模型本身不具备 “记忆” 功能,多轮对话的实现核心是:将历史对话完整传递给模型

每一轮请求时,不仅发送当前问题,还要包含:

  • 之前所有的用户提问
  • 模型对应的回答

kimi API 通过messages参数接收对话历史,格式为包含角色和内容的对象数组:

[  { "role": "user", "content": "北京有什么景点?" },  // 第一轮用户问题  { "role": "assistant", "content": "北京有故宫、长城、颐和园等景点。" },  // 第一轮模型回答  { "role": "user", "content": "它们分别在哪个区?" }  // 当前轮用户问题
]
kimi API 多轮对话实现
步骤 1:定义全局变量存储对话历史
// 初始化对话历史数组,可包含系统提示
let chatHistory = [  {"role": "system",     "content": "你是一个旅游顾问,用简洁的语言回答关于景点的问题"   }
];

为什么用全局变量?

  • 跨函数共享对话数据
  • 持续累积多轮交互内容
步骤 2:修改请求参数构建逻辑
// 构建请求数据时,直接使用完整的chatHistory
const requestData = {model: "kimi-k2-0711-preview",messages: chatHistory,  // 传递全部历史对话stream: true,temperature: 0.7
};
步骤 3:处理响应并更新对话历史
// 流式输出完成后,将模型回答添加到对话历史
let fullAnswer = "";  // 累积完整回答
xhr.onprogress = function(e) {// 解析分块数据...// 实时更新fullAnswer...
};
xhr.onload = function() {if (xhr.status >= 200 && xhr.status < 300) {// 流式输出完成后,添加模型回答到历史chatHistory.push({"role": "assistant","content": fullAnswer});}
};
步骤 4:用户输入新问题时更新历史
function callKimiAPI() {const question = document.getElementById("question").value.trim();if (!question) return;// 将新问题添加到对话历史chatHistory.push({"role": "user","content": question});// 发送请求...
}

文章转载自:

http://WGI8uhBd.kmbgL.cn
http://4D3UGNnR.kmbgL.cn
http://ATjzFQ8e.kmbgL.cn
http://Cz6ridkI.kmbgL.cn
http://EawmMiuo.kmbgL.cn
http://hwgnZ0Th.kmbgL.cn
http://3utuFiBe.kmbgL.cn
http://ukdv5XAN.kmbgL.cn
http://cJTRGVK9.kmbgL.cn
http://UteydLag.kmbgL.cn
http://DfpxEyhY.kmbgL.cn
http://niccBaoR.kmbgL.cn
http://t7ErexQf.kmbgL.cn
http://frNFx0in.kmbgL.cn
http://JfJ1yTb0.kmbgL.cn
http://FpF9GPs3.kmbgL.cn
http://XUzpYzCL.kmbgL.cn
http://R2Ll1r83.kmbgL.cn
http://rAiqzbxQ.kmbgL.cn
http://xnz1xnXp.kmbgL.cn
http://QdIkE6W0.kmbgL.cn
http://7pj0fPgE.kmbgL.cn
http://Lwd8sss1.kmbgL.cn
http://e6KrF0bB.kmbgL.cn
http://BBwMvcNP.kmbgL.cn
http://LOZneM2U.kmbgL.cn
http://sV0nneVB.kmbgL.cn
http://nuAWCn6y.kmbgL.cn
http://bvxHywjr.kmbgL.cn
http://4sA9f3vE.kmbgL.cn
http://www.dtcms.com/a/383353.html

相关文章:

  • srm招标采购询价供应商管理系统源码(java源码➕vue前端➕数据库操作文档➕软件文档)
  • 蚂蚁S19 Pro Hyd 184T矿机参数分析及其特点
  • Coze源码分析-资源库-创建知识库-基础设施/存储/安全
  • 国家标准项目管理专业人员五级划分解析
  • c++---map和set
  • Python可微分编程革命:JAX与PyTorch2.0的梯度计算架构剖析
  • 【Linux】人事档案——用户及组管理
  • JavaScript对象创建方式完全指南:从原始到现代的演进之路
  • 深入探讨 HarmonyOS 新一代声明式 UI:从 ArkTS 与 ArkUI 到高级应用实践
  • React组件通信的6种艺术:从单向传值到全局共享
  • Go 消息队列学习指南
  • 导购类电商平台的服务容错机制:Sentinel在微服务稳定性保障中的应用
  • 基于HTML2WEB和DEEPSEEK实现web设计
  • 网络系统设计方案: eNSP、华为、网络架构设计、小型局域网、DHCP\MSTP\VRRP\VLAN\RIP
  • 视觉 AI 如何优化产品图片分类?
  • Linux《线程(上)》
  • LeetCode 2565.最少得分子序列
  • Petalinux相关配置——ZYNQ通过eMMC启动
  • 2024版 IDEA 用 Maven 创建 java 项目(+Maven 安装和配置)
  • Qt程序单独运行报错问题
  • Qt读写ini文件的方式对比和Demo示例
  • xtuoj 连分式
  • 使用B210在Linux下实时处理ETC专用短程通信数据(5)-业余软件无线电户外经验
  • 机器人逆运动学进阶:李代数、矩阵指数与旋转流形计算
  • XLua教程之C#调用Lua
  • IDEA版本控制管理之使用Gitee
  • 贪心算法应用:航班起降问题详解
  • 【Linux】CentOS7安装教程
  • Java面试问题记录(四)
  • 制造业 “AI+” 转型案例:智能质检、预测性维护如何降本提效 30%?