uniapp+node+mysql接入deepseek实现流式输出

发布于:2025-03-09 ⋅ 阅读:(17) ⋅ 点赞:(0)

node

import express from 'express';
import mysql from 'mysql2';
import cors from 'cors';
import bodyParser from 'body-parser';
import axios from 'axios';
import { WebSocketServer } from 'ws'; // 正确导入 WebSocketServer

const app = express();

// Middlewares
app.use(cors());
app.use(bodyParser.json());  // 解析JSON请求体
app.use(express.json());

// MySQL数据库配置
const db = mysql.createConnection({
  host: '127.0.0.1',
  user: 'root',
  password: '123456',
  database: 'love_strategist',
  port: 3307,
});

db.connect((err) => {
  if (err) throw err;
  console.log('✅ MySQL 连接成功');
});

// 创建 WebSocket 服务器
const wss = new WebSocketServer({ port: 8080 }); // 使用 WebSocketServer

wss.on('connection', (ws) => {
  console.log('客户端已连接');

  ws.on('message', async (message) => {
    const { message: userMessage } = JSON.parse(message);

    try {
      const response = await axios.post(
        'https://api.deepseek.com/v1/chat/completions',
        {
          model: "deepseek-chat",
          messages: [{ role: "user", content: userMessage }],
          stream: true
        },
        {
          headers: {
            'Authorization': `Bearer 你的deepseekKey`,
            'Content-Type': 'application/json'
          },
          responseType: 'stream'
        }
      );

      let buffer = ''; // 用于存储未完整的数据块

      response.data.on('data', (chunk) => {
        buffer += chunk.toString(); // 将数据块拼接到缓冲区

        // 按行分割数据
        const lines = buffer.split('\n');
        for (let i = 0; i < lines.length - 1; i++) {
          const line = lines[i].trim();
          if (line.startsWith('data: ')) {
            const jsonStr = line.replace('data: ', '');
            try {
              const data = JSON.parse(jsonStr);
              if (data.choices?.[0]?.delta?.content) {
                // 逐字发送内容
                const content = data.choices[0].delta.content;
                ws.send(JSON.stringify({ content }));
              }
            } catch (error) {
              console.error('解析JSON失败:', error);
            }
          }
        }

        // 保留未完整的数据行
        buffer = lines[lines.length - 1];
      });

      response.data.on('end', () => {
        ws.send(JSON.stringify({ content: '[DONE]' }));
      });

    } catch (error) {
      console.error('Error:', error);
      ws.send(JSON.stringify({ content: '系统错误,请稍后再试。' }));
    }
  });

  ws.on('close', () => {
    console.log('客户端已断开连接');
  });
});

// 启动 HTTP 服务器
app.listen(3000, () => {
  console.log('✅ HTTP 服务器运行在 http://localhost:3000');
});

console.log('WebSocket 服务器运行在 ws://localhost:8080');

uniapp

<template>
	<view class="container">
		<!-- 顶部导航栏 -->
		<view class="navbar">恋爱指导</view>



		<!-- 聊天对话区域 -->
		<scroll-view class="chat-box" scroll-y :style="{ height: (chatHeight - 70) + 'px' }" scroll-top="999999">
			<view v-for="(message, index) in messages" :key="index" class="chat-item">
				<view :class="message.from === 'user' ? 'user-message' : 'bot-message'">
					<text>{{ message.content }}</text>
				</view>
			</view>
		</scroll-view>

		<!-- 输入框区域 -->
		<view class="input-area">
			<input v-model="userInput" class="input-box" placeholder="请输入你的问题..." />
			<button class="send-btn" @click="sendMessage">发送</button>
		</view>
	</view>
</template>


<script>
	export default {
		data() {
			return {
				userInput: '', // 用户输入的消息
				messages: [], // 存储消息记录
				chatHeight: 0, // 聊天框高度
				adviceList: [], // 存储恋爱建议
				socket: null // WebSocket 实例
			};
		},
		mounted() {
			this.adjustChatHeight(); // 调整聊天框高度
			
		},
		onShow() {
			setTimeout(() => {
				this.initWebSocket(); // 初始化 WebSocket
			}, 300)
		},
		// 页面隐藏时关闭 WebSocket
		onHide() {
			if (this.socket) {
				this.socket.close(); // 关闭 WebSocket
				console.log('WebSocket 连接已关闭');
			}
		},

		methods: {
			// 选中标签填充输入框并发送
			selectAdvice(content) {
				this.userInput = content;
				this.sendMessage();
			},

			// 动态计算聊天框高度
			adjustChatHeight() {
				const windowHeight = uni.getSystemInfoSync().windowHeight;
				this.chatHeight = windowHeight - 120; // 留出顶部和输入框的高度
			},

			// 初始化 WebSocket 连接
			initWebSocket() {
				this.socket = uni.connectSocket({
					url: 'ws://localhost:8080',
					success: () => {
						console.log('WebSocket 连接成功4');
					},
					fail: (err) => {
						console.error('WebSocket 连接失败:', err);
					}
				});

				// 监听 WebSocket 消息
				this.socket.onMessage((res) => {
					const data = JSON.parse(res.data);
					if (data.content === '[DONE]') {
						this.isStreaming = false; // 结束流式传输
					} else {
						// 逐字追加内容
						this.messages[this.messages.length - 1].content += data.content;
						this.$nextTick(() => {
							this.scrollToBottom();
						});
					}
				});

				// 监听 WebSocket 关闭
				this.socket.onClose(() => {
					console.log('WebSocket 连接已关闭');
				});
			},

			// 发送消息
			sendMessage() {
				if (this.userInput.trim() && !this.isStreaming) {
					this.addUserMessage(this.userInput); // 显示用户输入的消息
					this.isStreaming = true;

					// 通过 WebSocket 发送消息
					this.socket.send({
						data: JSON.stringify({
							message: this.userInput
						}),
						success: () => {
							console.log('消息发送成功');
							this.addBotMessage(''); // 初始化一个空消息用于逐步填充
						},
						fail: (err) => {
							console.error('消息发送失败:', err);
						}
					});

					this.userInput = ''; // 清空输入框
				}
			},

			// 添加用户消息
			addUserMessage(content) {
				this.messages.push({
					from: 'user',
					content: content
				});
			},

			// 添加机器人回复
			addBotMessage(content) {
				this.messages.push({
					from: 'bot',
					content: content
				});
			},

			// 滚动到底部
			scrollToBottom() {
				const query = uni.createSelectorQuery().in(this);
				query.select('.chat-box').boundingClientRect(res => {
					if (res) {
						uni.pageScrollTo({
							scrollTop: res.height,
							duration: 0
						});
					}
				}).exec();
			}
		}
	};
</script>

<style scoped>
	/* 页面容器 */
	.container {
		background-color: #fff;
		height: 100%;
		/* padding: 0 15px; */
		/* 加入左右边距 */
	}

	/* 顶部导航栏 */
	.navbar {
		background-color: #ff65a3;
		color: #fff;
		text-align: center;
		padding: 12px 0;
		font-size: 18px;
		font-weight: bold;
	}

	/* 聊天框 */
	.chat-box {
		padding: 10px;
		margin-bottom: 70px;
		/* 留出输入框的空间 */
		padding-left: 0;
		margin: 0 15px;
		width: auto;
		/* 去掉左边距,给消息更多空间 */
		padding-right: 0;
		/* 去掉右边距 */
	}

	/* 聊天消息 */
	.chat-item {
		margin: 10px 0;
		display: flex;
	}

	.user-message {
		text-align: right;
		background-color: #ff65a3;
		color: #fff;
		padding: 10px;
		border-radius: 10px;
		max-width: 80%;
		margin-left: auto;
	}

	.bot-message {
		text-align: left;
		background-color: #f2f2f2;
		color: #000;
		padding: 10px;
		border-radius: 10px;
		max-width: 80%;
		margin-right: auto;
	}

	/* 输入框 */
	.input-area {
		display: flex;
		position: fixed;
		bottom: 0;
		left: 0;
		width: 93%;
		padding: 10px;
		background-color: #fff;
		border-top: 1px solid #ddd;
		padding-left: 15px;
		/* 左边距 */
		padding-right: 15px;
		/* 右边距 */
	}

	.input-box {
		flex: 1;
		height: 40px;
		border-radius: 20px;
		border: 1px solid #ddd;
		padding: 0 15px;
		font-size: 16px;
	}

	.send-btn {
		background-color: #ff65a3;
		color: #fff;
		font-size: 16px;
		border: none;
		padding: 0 20px;
		border-radius: 20px;
		margin-left: 10px;
	}

	/* 恋爱指导标签 */
	.guidance-tags {
		display: flex;
		overflow-x: scroll;
		white-space: nowrap;
		padding: 10px;
		background-color: #ffe5f1;
	}

	.tag-item {
		display: inline-block;
		background-color: #ff65a3;
		color: white;
		padding: 8px 15px;
		margin-right: 10px;
		border-radius: 20px;
		font-size: 14px;
		cursor: pointer;
	}
</style>