AIGC小程序項目

一、文生文功能

(1)前端部分

使用 Pinia 狀態管理庫創建的聊天機器人消息存儲模塊,它實現了文生文(文本生成文本)的核心邏輯。

1.Pinia狀態管理

這個模塊管理兩個主要狀態:

  • messages:存儲所有聊天歷史記錄,包括用戶消息和 AI 回復
  • receiveText:臨時存儲 AI 流式返回的文本內容

主要包含兩個核心方法:

  • startSending:處理用戶消息發送邏輯
  • handleText:處理 AI 模型返回的流式響應

messages?數組中的每個元素都是一個對象,具有以下結構:

2.消息流轉過程

  1. 用戶發送消息

    • 用戶輸入文本后,startSending?方法會創建一個用戶消息對象并添加到?messages?數組
    • 同時預添加一個空的 AI 回復對象,初始狀態為?"start"

? ? ? ? ? ? ? ? ??

  1. AI 流式響應

    • 每次接收到新的文本片段時,handleText?方法會更新 AI 回復對象
    • finish_reason?會從?"start"?變為?"respond",表示正在生成回復
    • content?字段會逐步追加新的文本片段

? ? ? ? ? ? ? ? ?

?這里receiveText一點一點追加大模型返回的文本片段,然后賦值給大模型消息對象的content字段

  1. 回復完成

    • 當 AI 完成回復時,finish_reason?會被設置為最終狀態(如?"stop"
    • 如果有網絡搜索結果,會添加到?web_search?字段
    • 最后將最新的兩條消息(用戶 + AI)保存到服務器

? ? ? ? ? ? ? ??

  • 在流式響應過程中,每個數據塊(token)會依次返回,此時?finish_reason?字段通常是?null?或?undefined
  • 當回復完成時,最后一個數據塊會包含?finish_reason?字段,指示回復是如何結束的

? ? ?

以下是完整的message數組示例:

[{"role": "user","content": "推薦幾部科幻電影"},{"role": "assistant","content": "以下是幾部值得一看的科幻電影:1.《星際穿越》2.《盜夢空間》3.《2001太空漫游》","finish_reason": "stop","web_search": [{"title": "豆瓣科幻電影Top10","url": "https://movie.douban.com/chart","snippet": "豆瓣評分最高的科幻電影排行榜..."}]},{"role": "user","content": "《星際穿越》的導演是誰?"},{"role": "assistant","content": "《星際穿越》是由克里斯托弗·諾蘭執導的。","finish_reason": "stop","web_search": []}
]

?3.HTTP 請求封裝中的流式數據處理

  • 使用onChunkReceived監聽流式數據
  • 將二進制數據轉換為字符串并處理編碼
  • 實現緩沖區機制,按行解析 SSE (Server-Sent Events) 格式數據
  • 過濾有效數據塊并傳遞給chatbotMessage存儲模塊處理
requestTask.onChunkReceived(response=>{// 將ArrayBuffer轉換為字符串let arrayBuffer = response.dataconst arrayBufferss = new Uint8Array(arrayBuffer)let string = ''for(let i = 0; i < arrayBufferss.length; i++){string += String.fromCharCode(arrayBufferss[i])}// 處理編碼并追加到緩沖區buffer += decodeURIComponent(escape(string))// 按行解析數據while(buffer.includes('\n')){const index = buffer.indexOf('\n')const chunk = buffer.slice(0,index)buffer = buffer.slice(index + 1)// 處理SSE格式數據if(chunk.startsWith('data: ') && !chunk.includes('[DONE]')){const jsonData = JSON.parse(chunk.replace('data: ',''))chatbotMessage().handleText(jsonData)}}
})

大模型返回的SSE數據格式

4.實時UI渲染

<view class="zhipu-message" v-if="item.role === 'assistant'"><towxml :nodes="appContext.$towxml(item.content,'markdown')"></towxml><!-- 加載動畫 --><loadingVue v-if="item.finish_reason == 'start'"></loadingVue><!-- 網絡搜索結果 -->
</view>
  • item.content?是當前 AI 回復的內容
  • towxml?組件將 Markdown 格式的文本渲染為富文本
  • 每當item.content更新時,towxml會重新渲染,顯示最新內容

(2)后端部分

async createCompletions(ctx) {const { messages } = ctx.request.body;await Validate.isarrayCheck(messages, "缺少對話信息", "messages");
  • 從請求體中獲取對話歷史messages
  • 使用Validate.isarrayCheck驗證messages是否為數組
  • 如果驗證失敗,會拋出錯誤并返回相應的錯誤信息

const data = await ai.createCompletions({model: "glm-4-0520",messages,stream: true,tools: [{type: "web_search",web_search: {enable: true,search_result: true,},},],
});
  • 調用 AI 模型的createCompletions方法生成回復
  • 指定使用glm-4-0520模型
  • 傳遞完整的對話歷史messages
  • 設置stream: true啟用流式響應
  • 啟用網絡搜索工具,允許模型在生成回答時參考實時網絡信息

ctx.status = 200;
for await (const chunk of data) {console.log(chunk.toString());ctx.res.write(chunk);
}
  • 設置 HTTP 狀態碼為 200(成功)
  • 使用for await...of遍歷異步可迭代對象data
    • 每次迭代獲取模型生成的一個數據塊(可能是一個單詞、一個句子片段等)
  • 通過ctx.res.write(chunk)將數據塊實時寫入 HTTP 響應
    • 這些數據會立即傳輸到前端,而不需要等待整個回復完成

與前端的配合

前端代碼(之前分析過的)會這樣處理這個流式響應:

  1. 接收二進制數據塊并轉換為文本
  2. 按行解析 SSE 格式的數據
  3. 提取 JSON 對象并更新聊天界面
  4. 隨著新數據的到來,文本會逐字顯示在界面上

(3)SSE通信

? ? ? ? ?不同類型的大模型應用,對網絡通信的需求不盡相同,但幾乎都離不開以下需求。

? ? ? ? ?具體就是:

  • 1)實時對話:用戶與模型進行連續交互,模型需要即時響應。例如通義千問,HIgress 官網的答疑機器人,都是需要依據客戶問題,即時做出響應;
  • 2)流式輸出:大模型生成內容時,逐字或逐句返回結果,而不是一次性返回。但是釘釘、微信等應用,兩個人相互對話時,采用的就不是流式輸出了,文字等內容都是一次性返回的;
  • 3)長時任務處理:大模型可能需要較長時間處理復雜任務,同時需要向客戶端反饋進度,尤其是處理長文本、以及圖片、視頻等多模態內容;這是因為依賴大模型計算的響應,要比依賴人為寫入的業務邏輯的響應,消耗的資源多的多,這也是為什么大模型的計算要依靠 GPU,而非 CPU,CPU 在并行計算和大規模矩陣計算上遠不如 GPU;
  • 4)多輪交互:用戶與模型之間需要多次往返交互,保持上下文。這是大模型應用保障用戶體驗的必備能力。

? ? ? ?這些場景對實時性和雙向通信有較高要求,沿用 Web 類應用的主流通信協議 - HTTPS,將會? ?存在很多問題。

? ? ?以下是主要的問題:

  • 1)僅支持單向通信,即請求-響應模型,必須是客戶端發起時,服務端才能做出響應,無法進行雙向通信,導致無法支持流式輸出,無法處理長時任務;
  • 2)客戶端每次發出請求都需要重新建立連接,延遲增加,導致無法支持實時對話;
  • 3)HTTPS 是一種無狀態的通信協議,每次請求都是獨立的,服務端不會保存客戶端的狀態,即便客戶端可以在每次請求時重復發送上下文信息,但會帶來額外的網絡開銷,導致無法高效的支持多輪交互場景。

? ? ? 雖然 HTTPS 已經發展到 HTTPS/2 和 HTTPS/3,在性能上了有了提升,但是面對大模型應用這類對實時性要求較高的場景,依舊不夠原生,并未成為這類場景下的主流通信協議。

二、實時語音功能

(1)前端部分

1. 初始化與準備工作

  • 引入必要的模塊和變量:在input-box.vue文件中,引入了阿里云相關的請求接口aliTokenaliyunUrlappKey,以及自定義的語音識別類SpeechTranscription

import {aliToken,aliyunUrl,appKey} from '@/api/request.js'
import {SpeechTranscription} from '@/voice/st.js'
  • 獲取 Token:在主頁面加載時,調用aliToken接口獲取阿里云語音識別所需的 Token。

  • 實例化語音識別對象:使用獲取到的 Token、URL 和 AppKey 實例化SpeechTranscription對象,并存儲在launckVoice變量中。

onLoad(async()=>{const token = await aliToken()const st = new SpeechTranscription({url:aliyunUrl,token:token.data,appkey:appKey})launckVoice.value = st
})

2. 開始語音錄制與識別

  • 長按開始說話:用戶長按 “按住 說話” 按鈕,觸發longpress方法。

  • 檢查當前是否有正在進行的對話,如果有則返回。

  • 顯示語音錄制區域。

  • 調用launckVoice.value.start方法開始語音識別,并傳入默認的開始參數。

  • 調用recorderManager.start方法開始錄音。

const longpress = async()=>{if(inProgress().queryValue())return falseshowAudio.value = trueawait launckVoice.value.start(launckVoice.value.defaultStartParams())recorderManager.start({duration:100000,sampleRate:16000,numberOfChannels:1,format:'PCM',frameSize:4})
}
  • 實時輸出錄音:使用recorderManager.onFrameRecorded監聽錄音的每一幀數據,并將其發送給阿里云語音識別服務。

recorderManager.onFrameRecorded(res=>{launckVoice.value.sendAudio(res.frameBuffer)
})

3. 處理語音識別結果

  • 監聽語音識別事件:在SpeechTranscription(實例化的阿里云語音對象)對象上監聽多個事件,包括開始、中間結果、句子結束、關閉和錯誤。

// 實時語音識別開始。
st.on("started",()=>{console.log('實時語音識別開始');
})
// 實時語音識別中間結果。
st.on("changed",msg=>{console.log('實時語音識別中間結果');console.log(msg);const res = JSON.parse(msg)const queryIndex = storageArr.value.findIndex(item=>item.index === res.payload.index)if(queryIndex >= 0){storageArr.value[queryIndex].result = res.payload.result}else{storageArr.value.push(res.payload)}
})
// 提示句子結束。
st.on("end",msg=>{console.log('提示句子結束');console.log(msg);const res = JSON.parse(msg)const queryIndex = storageArr.value.findIndex(item=>item.index === res.payload.index)if(queryIndex >= 0){storageArr.value[queryIndex].result = res.payload.result}else{storageArr.value.push(res.payload)}
})
// 連接關閉。
st.on("closed",()=>{console.log('連接關閉');
})
// 錯誤。
st.on("failed",(err)=>{console.log('阿里云語音識別錯誤');console.log(err);uni.showToast({icon:"none",title:'錄音出現錯誤'})
})

4. 結束語音錄制與識別

  • 手指放開停止錄音:用戶放開手指,觸發touchend方法。

const touchend = ()=>{showAudio.value = falserecorderManager.stop()
}
  • 隱藏語音錄制區域。

  • 調用recorderManager.stop方法停止錄音。

  • 處理錄音結束事件:使用recorderManager.onStop監聽錄音結束事件,強制關閉阿里云語音識別監聽,并將識別結果拼接成字符串存儲在inputContent中。

recorderManager.onStop(res=>{console.log('錄音結束了');console.log(res);showAudio.value = false// 強制關閉阿里云語音識別監聽launckVoice.value.shutdown()// 錄制結束取出文字發送大模型if(storageArr.value.length > 0){storageArr.value.forEach(item=>{inputContent.value += item.result})}
})

5. 實際數據流轉示例

1.?changed?事件(中間結果)

阿里云返回的json數據格式

{"payload": {"index": 1,       // 當前句子的唯一索引(同一輪錄音中的句子編號)"result": "你好,", // 中間識別結果(可能后續會補充)"status": "partial" // 標識為中間結果}
}
st.on("changed", msg => {const res = JSON.parse(msg); // 解析JSON數據const { index, result } = res.payload; // 提取索引和文本// 查找是否已存在相同index的結果塊const queryIndex = storageArr.value.findIndex(item => item.index === index);if (queryIndex >= 0) {// **存在已記錄的塊**:更新該塊的文本(中間結果可能逐次補充)storageArr.value[queryIndex].result = result;} else {// **不存在記錄**:新增一個結果塊(處理可能的亂序返回)storageArr.value.push({ index, result });}
});
changed: { index: 1, result: "今天" }
changed: { index: 1, result: "今天天氣" }
changed: { index: 1, result: "今天天氣怎么樣" }
end: { index: 1, result: "今天天氣怎么樣" }  // 第一句結束changed: { index: 2, result: "明天" }
changed: { index: 2, result: "明天有什么" }
changed: { index: 2, result: "明天有什么安排" }
end: { index: 2, result: "明天有什么安排" }  // 第二句結束

這段數組邏輯是這樣的:先查找數組中有沒有存在的和阿里云返回結果的index相同的index,有的話說明是同一個片段句子,覆蓋就行,沒有的話說明是新句子,重新push一個對象進數組

(例如返回來的index為1,數組已經存在index為1的對象,則覆蓋;如果返回來的index是2,數組不存在index為2的對象,則新增一個index=2的對象去存儲)

關鍵點:

  1. 增量更新:每次返回的中間結果會覆蓋前一次的結果。
  2. 按索引管理:通過?index?區分不同的句子(若用戶連續說多句話)。
  3. 實時展示:可用于實現 “邊說邊顯示” 的效果(如語音輸入法的逐字顯示)。
2.end?事件(最終結果)

當檢測到語音停頓(用戶停止說話),阿里云返回完整的最終識別結果。

st.on("end", msg => {const res = JSON.parse(msg);const queryIndex = storageArr.value.findIndex(item => item.index === res.payload.index);if (queryIndex >= 0) {// 更新已有結果塊(將中間結果替換為最終結果)storageArr.value[queryIndex].result = res.payload.result;} else {// 添加新結果塊(理論上不會觸發,因為end事件前必有changed事件)storageArr.value.push(res.payload);}
});

關鍵點

  1. 最終確認end?事件的結果比?changed?事件更準確(經過模型后處理優化)。
  2. 句子邊界:一個?end?事件表示一個完整句子的結束。
  3. 結果固化:最終結果不會再被覆蓋,可直接用于后續處理。

總的來說,on事件用于實時更新識別出來的文本數據,end事件用來處理最后識別結果,糾正一些on事件中的諧音錯誤

用戶按下按鈕
↓
recorderManager.start() 開始錄音
↓
每采集一幀音頻數據↓recorderManager.onFrameRecorded() 觸發↓launckVoice.value.sendAudio(res.frameBuffer) 發送數據到阿里云↓阿里云處理數據并返回識別結果↓st.on("changed") 或 st.on("end") 觸發↓將識別結果存入 storageArr
↓
用戶松開按鈕
↓
recorderManager.onStop() 觸發↓launckVoice.value.shutdown() 關閉連接↓拼接 storageArr 中的所有結果↓sendIng() 將文本發送給大模型

(2)后端部分

class VoiceController {async aliToken(ctx) {// 檢查Redis緩存中是否已有Tokenconst alitoken = await ctx.redis.get("aliToken")if (alitoken) {ctx.send(alitoken)return false}// 調用阿里云API生成新Tokenconst result = await client.request('CreateToken')console.log(result)// 處理返回結果并緩存if (result.Token && result.Token.Id) {// 計算Token過期時間const expires_in = result.Token.ExpireTime - dayjs().unix()// 緩存到Redis并設置過期時間await ctx.redis.set('aliToken', result.Token.Id, 'EX', expires_in)ctx.send(result.Token.Id)} else {ctx.send(null, 500, "獲取阿里云token失敗", result)}}
}
  • 緩存優先策略:首先檢查 Redis 中是否有緩存的 Token,如果有則直接返回
  • Token 生成:調用CreateToken接口生成新 Token
  • 緩存處理:將 Token 存入 Redis 并設置與阿里云一致的過期時間,避免頻繁調用 API

三、用戶登錄

(1)前端部分

1. 獲取登陸碼

		uni.login({success:async(res)=>{await userData().isNotLoggedIn(userInfo.nickname,fileurl,res.code)loading.value = false}})

? ?使用?uni.login?方法獲取用戶的登錄碼?code

關于code的一些說明:

當用戶退出登錄后再次登錄,后端服務器使用新的?code?向微信服務器換取?openid?時,獲取到的仍然是之前的?openid

  • 避免敏感信息泄露code?是臨時且一次性有效的,它不是用戶的敏感信息(如?openid?、用戶的真實身份信息等)。在小程序前端向微信服務器請求登錄時,微信服務器會返回一個?code?,小程序前端再把這個?code?發送給開發者的后端服務器。后端服務器拿著這個?code?去微信服務器換取用戶的?openid?等敏感信息。如此一來,用戶的敏感信息就不會直接在前端暴露,減少了信息泄露的風險。
  • 防止惡意攻擊:由于?code?有有效期,而且只能使用一次,這就增加了攻擊者利用?code?進行惡意操作的難度。就算攻擊者截獲了?code?,在其過期之后也就無法再使用了。

2. 調用后端wxLogin接口

		// 未登錄獲取用戶信息async isNotLoggedIn(nickName, avatar, code){// 請求接口const result = await wxLogin({nickName, avatar, code})// console.log(result);// 存儲本地緩存uni.setStorageSync('userInfo',result.data)this.userInfo = result.data// 請求聊天列表const chatListData = await userChatList()this.chatList = chatListData.datathis.isLogin = true}

調用后端接口,將用戶昵稱、頭像、獲取到的code傳給后端,后端返回結果,將其保存在本地緩存,其中后端返回的結果如下:

{"data": {"token": "生成的 JWT 令牌","nickName": "用戶昵稱","avatar": "用戶頭像地址"},"msg": "SUCCESS","error": null,"serviceCode": 200
}

(2)后端部分

class UserController {//用戶登錄async wxLogin(ctx) {const { nickName, avatar, code } = ctx.request.bodyawait Validate.nullCheck(nickName, '請輸入昵稱', 'name')await Validate.nullCheck(avatar, '請上傳頭像', 'avatar')await Validate.nullCheck(code, '缺少code', 'avatar')//獲取openidconst openid = await new UserService().getOpenid(code)//查詢數據庫是否已存在用戶信息// console.log(nickName, avatar, openid)const userInfo = await User.findOne({ where: { openid } })if (!userInfo) {await User.create({ nickName, avatar, openid })}ctx.send({ token: generateToken(openid), nickName, avatar })}
}

將code換為openid的service部分:

const { appid, secret, code2session } = require("@/config/default").weixin
const qs = require("querystring")
const axios = require("axios")
class UserService {// 獲取openidasync getOpenid(code) {const query = qs.stringify({appid,secret,js_code: code,grant_type: "authorization_code"})const res = await axios.get(`${code2session}?${query}`)console.log(res)if (res.data.errcode) {throw { msg: "獲取code出錯", code: 400, error: res.data }} else {return res.data.openid}}
}
  • 用途:實現微信登錄流程中的「code 換取 openid」環節,這是微信小程序 / 公眾號登錄的核心步驟。
  • 技術棧
    • 使用?axios?發起 HTTP 請求,調用微信官方接口。
    • 通過?querystring?處理 URL 查詢參數。
    • 配置信息(如?appidsecret)從項目配置文件中讀取。

?微信登錄流程關聯

這段代碼是微信登錄流程中的關鍵環節,整體流程如下:

  1. 前端獲取?code
    前端調用微信登錄接口(如小程序的?wx.login),獲取臨時登錄憑證?code,并傳遞給后端。
  2. 后端換取?openid
    后端通過?codeappidsecret?向微信服務器發起請求,驗證?code?的有效性并獲取?openid(用戶在微信體系內的唯一標識)。
  3. 業務邏輯處理
    后端使用?openid?進行用戶注冊 / 登錄(如查詢數據庫是否存在該用戶),并生成自定義令牌(如 JWT)返回給前端。

四、用戶鑒權

生成 JWT Token

wxLogin方法中,用戶登錄時會獲取openid,然后后端調用generateToken方法生成 JWT Token,并將其返回給前端。

存儲 Token

前端收到 Token 后,將其存儲到本地緩存中。在后續的請求中,前端需要在請求頭中攜帶這個 Token。

驗證 JWT Token

在每個需要鑒權的接口中,會對請求頭中的 Token 進行驗證。

const basicAuth = require("basic-auth");
var jwt = require("jsonwebtoken");
const { secretkey } = require("./default").userToken;const authority = async (ctx, next) => {const token = basicAuth(ctx.req);if (!token || !token.name) {throw { msg: "沒有登陸,沒有訪問權限", code: 401 };}try {var authcode = jwt.verify(token.name, secretkey); //解密token為openid} catch (error) {if (error.name == "TokenExpiredError") {throw { msg: "登錄過期,重新登陸", code: 401 };}throw { msg: "沒有訪問權限", code: 401 };}ctx.auth = {uid: authcode.uid,};await next();
};module.exports = authority;

authority中間件中,首先從請求頭中獲取 Token,然后使用jwt.verify方法對 Token 進行驗證。如果驗證通過,將openid存儲在ctx.auth中,并繼續執行后續的中間件或路由處理函數;如果驗證失敗,拋出相應的錯誤信息。

應用鑒權中間件

在路由中,為需要鑒權的接口應用authority中間件。

?jwt.verify?是如何驗證 Token 的有效性的?

1. 生成 Token

首先,假設我們要為一個用戶生成一個 Token,該用戶的?openid?為?"user123"。在?AIGC - backend/config/jwt.js?中,生成 Token 的代碼如下:

const jwt = require('jsonwebtoken');
const { secretkey, expiresIn } = require('./default').userToken;// 生成token
function generateToken(uid) {return jwt.sign({ uid }, secretkey, { expiresIn });  //uid是openid,密鑰是自己定義的
}// 示例:生成一個openid為user123的token
const openid = "user123";
const token = generateToken(openid);
console.log("生成的Token:", token);

假設?secretkey?為?"mysecretkey"expiresIn?為?"1h"(表示 Token 在 1 小時后過期),生成的 Token 可能如下(實際生成的 Token 會有所不同):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJ1c2Vy123IiwiaWF0IjoxNjk4NjM2MDAwLCJleHAiOjE2OTg2Mzk2MDB9.abcdef1234567890

這個 Token 由三部分組成,用點號?.?分隔:

  • 頭部(Header)eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9,它通常包含了令牌的類型(JWT)和使用的簽名算法(這里是 HMAC SHA256,即?HS256)。
  • 負載(Payload)eyJ1aWQiOiJ1c2Vy123IiwiaWF0IjoxNjk4NjM2MDAwLCJleHAiOjE2OTg2Mzk2MDB9,包含了我們存儲的用戶?openiduid)、簽發時間(iat)和過期時間(exp)。
  • 簽名(Signature)abcdef1234567890,用于驗證消息在傳輸過程中沒有被更改。

2. 驗證 Token

當用戶發起請求時,服務器會從請求頭中獲取 Token,并使用?jwt.verify?方法進行驗證。在?AIGC - backend/config/auth.js?中,驗證 Token 的代碼如下:

const basicAuth = require("basic-auth");
var jwt = require("jsonwebtoken");
const { secretkey } = require("./default").userToken;const authority = async (ctx, next) => {const token = basicAuth(ctx.req);if (!token || !token.name) {throw { msg: "沒有登陸,沒有訪問權限", code: 401 };}try {var authcode = jwt.verify(token.name, secretkey); //解密token為openid} catch (error) {if (error.name == "TokenExpiredError") {throw { msg: "登錄過期,重新登陸", code: 401 };}throw { msg: "沒有訪問權限", code: 401 };}ctx.auth = {uid: authcode.uid,};await next();
};module.exports = authority;
  • 獲取 Token:假設客戶端在請求頭中攜帶了上面生成的 Token,服務器通過?basicAuth(ctx.req)?獲取到該 Token。
  • 簽名驗證jwt.verify?方法會根據 Token 的頭部指定的簽名算法(HS256),使用相同的密鑰(mysecretkey)重新計算簽名。具體步驟如下:
    1. 將頭部和負載部分用點號?.?連接起來,得到一個字符串。
    2. 使用?HS256?算法和?mysecretkey?對這個字符串進行簽名。
    3. 將計算得到的簽名與 Token 中的簽名部分進行比對。如果兩者一致,說明 Token 沒有被篡改。
  • 過期時間驗證jwt.verify?方法會檢查當前時間是否超過了 Token 的過期時間。假設當前時間是?1698638000,而 Token 的過期時間是?1698639600,當前時間小于過期時間,說明 Token 未過期。
  • 返回結果:如果簽名驗證和過期時間驗證都通過,jwt.verify?方法會返回 Token 中包含的信息,即:
{"uid": "user123","iat": 1698636000,"exp": 1698639600
}

服務器把?openid?存儲在?ctx.auth?中,在后續存儲和獲取聊天對應用戶聊天記錄起到關鍵作用

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/81192.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/81192.shtml
英文地址,請注明出處:http://en.pswp.cn/web/81192.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Axios中POST、PUT、PATCH用法區別

在 Axios 中&#xff0c;POST、PUT 和 PATCH 是用于發送 HTTP 請求的三種不同方法&#xff0c;它們的核心區別源自 HTTP 協議的設計語義。以下是它們的用法和區別&#xff1a; 1. POST 語義&#xff1a;用于創建新資源。 特點&#xff1a; 非冪等&#xff08;多次調用可能產生…

[爬蟲知識] Cookie與Session

相關實戰案例&#xff1a;[爬蟲實戰] 爬取小說標題與對應內容 相關爬蟲專欄&#xff1a;JS逆向爬蟲實戰 爬蟲知識點合集 爬蟲實戰案例 一、引入場景 在http協議中&#xff0c;瀏覽器是無狀態&#xff08;即無記憶&#xff09;的&#xff0c;對于請求與響應的產生數據&#…

怎樣改變中斷優先級?

在STM32中改變中斷優先級可以通過STM32CubeMX配置和代碼中設置兩種方式來實現。以下以STM32F1系列為例進行說明: 使用STM32CubeMX配置 打開工程:在STM32CubeMX中打開你的工程。進入NVIC配置:在Pinout & Configuration選項卡中,點擊NVIC進入中斷向量控制器配置界面。選…

科學計算中的深度學習模型精解:CNN、U-Net 和 Diffusion Models

關鍵要點 模型概述:卷積神經網絡(CNN)、U-Net 和 Diffusion Models 是深度學習中的核心模型,廣泛應用于科學計算任務,如偏微分方程(PDE)求解、圖像分割和數據生成。科學計算應用:CNN 可用于高效求解 PDEs,U-Net 擅長醫學圖像分割和材料分析,Diffusion Models 在生成合…

解決Docker無法拉取鏡像問題:Windows系統配置鏡像加速全指南

問題背景 在使用 Docker 時&#xff0c;你是否遇到過以下報錯&#xff1f; Unable to find image ‘mysql:latest’ locally docker: Error response from daemon: Get “https://registry-1.docker.io/v2/”: dial tcp 128.242.250.155:443: i/o timeout. 這類問題通常是由于…

Spring AI 使用教程

Spring AI 使用教程&#xff08;2025年5月24日更新&#xff09; 一、環境搭建與項目初始化 創建Spring Boot項目 使用IDEA或Spring Initializr創建項目&#xff0c;選擇JDK 17或更高版本&#xff08;推薦21&#xff09;。勾選依賴項&#xff1a;Spring Web、Lombok&#xff0c;…

iOS 直播特殊禮物特效實現方案(Swift實現,超詳細!)

特殊禮物特效是提升直播互動體驗的關鍵功能&#xff0c;下面我將詳細介紹如何在iOS應用中實現各種高級禮物特效。 基礎特效類型 1.1 全屏動畫特效 class FullScreenAnimationView: UIView {static func show(with gift: GiftModel, in view: UIView) {let effectView FullS…

分布式事務之Seata

概述 Seata有四種模式 AT模式&#xff1a;無侵入式的分布式事務解決方案&#xff0c;適合不希望對業務進行改造的場景&#xff0c;但由于需要添加全局事務鎖&#xff0c;對影響高并發系統的性能。該模式主要關注多DB訪問的數據一致性&#xff0c;也包括多服務下的多DB數據訪問…

信息收集與搜索引擎

6.1 常見的搜索引擎&#xff08;一、二&#xff09; 6.1.1 通用搜索引擎 Google/Bing&#xff1a; 用途&#xff1a;基礎信息收集&#xff08;域名、子域名、敏感文件&#xff09;。 高級語法&#xff1a; site:target.com&#xff1a;限定搜索目標域名。 filetype:pdf&am…

【Java項目測試報告】:在線聊天平臺(Online-Chat)

被測試項目已部署&#xff1a;登錄頁面http://123.249.78.82:8080/login.html 一、項目背景 1.1 測試目標 驗證系統功能完整性&#xff0c;確保用戶管理、消息傳輸、好友管理等核心模塊符合需求。 1.2 項目技術棧 后端&#xff1a;Spring Boot/Spring MVC/WebSocket 數據…

RAGFlow與Dify的深度刨析

目錄 一、RAGFlow 框架 二、Dify 框架 三、兩者集成 四、深度對比 1. 核心定位對比 2. 核心功能對比 3. 技術架構對比 4. 部署與成本 5. 適用場景推薦 總結 一、RAGFlow 框架 RAGFlow 是一個專注于深度文檔理解和檢索增強生成&#xff08;RAG&#xff09;技術的框架…

CQF預備知識:一、微積分 -- 1.2.2 函數f(x)的類型詳解

文中內容僅限技術學習與代碼實踐參考&#xff0c;市場存在不確定性&#xff0c;技術分析需謹慎驗證&#xff0c;不構成任何投資建議。 &#x1f4d6; 數學入門全解 本系列教程為CQF(國際量化金融分析師證書)認證所需的數學預備知識&#xff0c;涵蓋所有需要了解的數學基礎知識…

嵌入式工程師常用軟件

1、 Git Git 是公司常用的版本管理工具&#xff0c;人人都要會。在線的 git 教程可以參考菜鳥教程&#xff1a; https://www.runoob.com/git/git-tutorial.html 電子書教程請在搜索欄搜索&#xff1a; git Git 教程很多&#xff0c;常用的命令如下&#xff0c;這些命令可…

TReport組件指南總結

1. TReport 組件簡介 TReport 是一個用于生成和打印報表的組件,通常用于連接數據集(如 TDataSet)并設計復雜的報表布局。它支持動態數據綁定、多頁報表、分組統計、圖表插入等功能。 2. 安裝與配置 安裝:如果使用的是第三方報表工具(如 Rave Reports),需在 Delphi 中通…

spark任務的提交流程

目錄 spark任務的提交流程1. 資源申請與初始化2. 任務劃分與調度3. 任務執行4. 資源釋放與結果處理附:關鍵組件協作示意圖擴展說明SparkContext介紹 spark任務的提交流程 用戶創建一個 Spark Context;Spark Context 去找 Cluster Manager 申請資源同時說明需要多少 CPU 和內…

【C++】C++異步編程四劍客:future、async、promise和packaged_task詳解

C異步編程四劍客&#xff1a;future、async、promise和packaged_task詳解 1. 引言 1.1 異步編程的重要性 在現代C編程中&#xff0c;異步操作是提高程序性能和響應能力的關鍵技術。它允許程序在等待耗時操作&#xff08;如I/O、網絡請求或復雜計算&#xff09;完成時繼續執行…

2021-10-28 C++判斷完全平方數

緣由判斷一個整數是否為完全平方數-編程語言-CSDN問答 整數用平方法小數用5分法逼近。 int 判斷平方數(int n) {//緣由https://ask.csdn.net/questions/7546950?spm1005.2025.3001.5141int a 1;while (a < n / a)if (a*a < n)a;else if (a*a n)return 1;elsereturn 0…

解決weman框架redis報錯:Class “llluminatelRedis\RedisManager“ not found

解決weman框架redis報錯&#xff1a;Class "llluminatelRedis\RedisManager" not found 報錯解決方案 報錯 解決方案 按照手冊執行 composer require psr/container ^1.1.1 illuminate/redis illuminate/events 安裝redis組件 然后restart重啟就行了 php webman s…

Windows 11 電源計劃進階——通過異類策略優化大小核CPU調度

一、為什么需要手動控制大小核調度&#xff1f; 1.1 Intel 12/13/14代酷睿與Win11的適配現狀 Intel 12代酷睿首次引入混合架構設計&#xff08;P-Core性能核 E-Core能效核&#xff09;&#xff0c;Windows 11雖然原生支持線程調度器&#xff08;Thread Director&#xff09;…

文件系統·linux

目錄 磁盤簡介 Ext文件系統 塊 分區 分組 inode 再談inode 路徑解析 路徑緩存 再再看inode 掛載 小知識 磁盤簡介 磁盤&#xff1a;一個機械設備&#xff0c;用于儲存數據。 未被打開的文件都是存在磁盤上的&#xff0c;被打開的加載到內存中。 扇區&#xff1a;是…