1.老规矩,先讲故事
这天我的微信小程序想实现接入一个AI页面实现用户AI智能问答功能,但发现网络上大家都比较吝啬,要么要开会员,要么不分享。导致我走了好多弯路,最后也明白了一些道理,掌握了一些新的知识。
2.AI问答功能的实现
这部分直接上代码吧(主要两个文件,小程序前端页面js文件与服务端云函数index.js文件)
//页面js文件
const app = getApp()
Page({
data: {
chatList: [],
inputValue: '',
loading: false,
scrollToView: '',
loadingText: '发送中...'
},
onLoad() {
// 初始化聊天列表
this.setData({
chatList: [{
role: 'assistant',
content: '你好!我是徒步小智,有什么我可以帮你的吗?'
}]
})
},
onInput(e) {
this.setData({
inputValue: e.detail.value
})
},
async sendMessage() {
if (!this.data.inputValue.trim() || this.data.loading) return
const userMessage = this.data.inputValue.trim()
this.setData({
chatList: [...this.data.chatList, {
role: 'user',
content: userMessage
}],
inputValue: '',
loading: true,
loadingText: '正在思考中...'
})
this.scrollToBottom()
try {
const maxRetries = 2
let retryCount = 0
let success = false
while (retryCount <= maxRetries && !success) {
try {
//wx.cloud.callFunction调用该云函数
const response = await wx.cloud.callFunction({
name: 'aiChat',
data: {
message: userMessage
}
})
if (response.result && response.result.data && response.result.data.result) {
this.setData({
chatList: [...this.data.chatList, {
role: 'assistant',
content: response.result.data.result
}],
loading: false,
loadingText: '发送中...'
})
success = true
} else {
throw new Error('无效的响应数据')
}
} catch (error) {
retryCount++
if (retryCount <= maxRetries) {
this.setData({
loadingText: `重试第${retryCount}次...`
})
await new Promise(resolve => setTimeout(resolve, 1000))
} else {
throw error
}
}
}
this.scrollToBottom()
} catch (error) {
console.error('AI API调用失败:', error)
this.setData({
chatList: [...this.data.chatList, {
role: 'assistant',
content: '抱歉,我现在有点忙,请稍后再试。'
}],
loading: false,
loadingText: '发送中...'
})
wx.showToast({
title: '请求超时,请稍后重试',
icon: 'none',
duration: 2000
})
}
},
scrollToBottom() {
const length = this.data.chatList.length
if (length > 0) {
this.setData({
scrollToView: `msg-${length - 1}`
})
}
}
})
//云函数/cloudfunctions/aiChat/index.js文件
const cloud = require('wx-server-sdk')
const axios = require('axios')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
//定义一个云函数入口,使用async声明异步函数
exports.main = async (event, context) => {
//从事件对象中解构出用户输入的message参数
const { message } = event
try {
//axios({ method: 'POST', ... })使用axios发起POST请求
const response = await axios({
method: 'POST',
url: 'https://qianfan.baidubce.com/v2/chat/completions',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer bce-v3/xxxxxx/xxxxxx'
},
data: {
model: "ernie-3.5-8k",
messages: [
{
role: "user",
content: message
}
]
}
})
// 从响应数据中提取AI助手的回复内容
const aiContent = response.data.choices[0].message.content
return {
code: 200,
data: {
result: aiContent
}
}
} catch (error) {
console.error('API调用错误:', error)
return {
code: 500,
error: error.message
}
}
}
3.需要注意的问题
1.跨域问题
先说一下,我使用的是百度智能云千帆大模型服务与开发平台ModelBuilder。这是他的官方api调用示例
curl -X POST 'https://qianfan.baidubce.com/v2/chat/completions' -H 'Content-Type: application/json' -H 'Authorization: Bearer bce-v3/ALTAK-*****************/2d7*****************' -d '{
"model": "ernie-3.5-8k",
"messages": [
{
"role": "user",
"content": "你好"
}
]
}'
我在postman上面进行测试,发现上面是完全可行的,但当我页面js文件使用 wx.request()无法实现AI API的调用。这让我十分不解,我也去配置了不检验域名,设置了域名白名单。还是无法解决,后来将调用API放进云函数使用axios网络请求库解决实现了调用。
真相大白
经过我的不蟹探究,最后发现是跨域问题。
一、跨域问题的本质
-
浏览器安全策略:
-
跨域限制(CORS)是浏览器强制的安全机制
-
只存在于浏览器环境(包括小程序)
-
服务端之间的通信没有跨域概念
-
-
错误表现示例:
// 前端直接调用会出现的错误 Access to XMLHttpRequest at 'https://qianfan.baidubce.com/...' from origin 'https://your-domain.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource
二、两种场景对比
场景1:前端直接调用(失败)
sequenceDiagram participant 浏览器/小程序 as 前端 participant 百度API as 服务端 前端->>服务端: wx.request(带Authorization头) 服务端-->>前端: 响应(无CORS头) 浏览器拦截响应 ❌
关键问题:
-
百度API响应头没有设置
Access-Control-Allow-Origin: *
-
浏览器/小程序引擎拒绝解析响应
场景2:云函数中转(成功)
sequenceDiagram participant 浏览器/小程序 as 前端 participant 云函数 as 服务端 participant 百度API as 服务端 前端->>云函数: wx.cloud.callFunction(安全) 云函数->>百度API: axios请求(服务端到服务端)✅ 百度API-->>云函数: 原始响应(无CORS限制) 云函数-->>前端: 返回清洗后的数据
成功关键:
-
前端与云函数同源(或云函数已配置CORS)
-
云函数到百度API是服务端间通信,不受浏览器策略限制
三、云函数的优势详解
-
网络层面:
-
云函数部署在腾讯云/阿里云等服务器
-
默认拥有公网访问能力
-
无需处理证书、代理等复杂配置
-
-
协议层面:
-
服务端之间使用标准HTTP/HTTPS协议
-
无需处理CORS、OPTIONS预检请求
-
-
安全层面:
-
前端永远看不到API密钥
-
可在云函数做请求限流、内容过滤
-
避免直接暴露API端点
-
在我的例子中,前端wx.request链接https://qianfan.baidubce.com服务端会发生跨域问题使无法正确返回,如果放云函数里使用axios方法就是服务端-服务端,就不会有不会有跨域问题
四、为什么小程序用wx.request失败?
根本原因链:
小程序未配置域名白名单 → 百度API未设置CORS头 → 小程序引擎拦截响应 → 开发者工具Network显示请求被blocked
具体限制:
-
小程序要求所有请求域名必须加入
request合法域名
列表 -
即使你配置了
qianfan.baidubce.com
,百度服务器也不会返回CORS头 -
双重限制下必然失败
五、最终结论
通过云函数中转的本质,是把原本需要浏览器处理的跨域请求,转换为服务端之间的自由通信。这种方案:
-
✅ 彻底规避跨域问题
-
✅ 提升安全性
-
✅ 符合企业级应用规范
这是所有主流应用(如ChatGPT、文心一言官方应用)采用的架构设计,也是唯一符合安全规范的实现方式。
2.超时问题
在我的跨域问题解决后又发生了一个问题:
{"errorCode":-1,"errorMessage":"Invoking task timed out after 3 seconds","statusCode":433}
于是首先我在\miniprogram\cloudfunctions\aiChat\config.json中设置了
{
"permissions": {
"openapi": []
},
"timeout": 60,
"triggers": [],
"memorySize": 256
}
但发现不起作用,于是,我又在云开发里设置超时时间改为了33s,成功解决了该问题(AI思考时间一般较长,默认3s肯定无法正确返回数据)