AI智能助手项目开发:实现本地部署网页渲染和AI对话

目录

项目背景

环境依赖

 FastAPI初始化

API获取

数据库设计

users表

 chat_sessions表

chat_sessions_log表

FastAPI接口

登录请求接口

注册请求接口

AI聊天请求接口

界面设计参考

html界面设计代码

密码加密功能

主要的前端功能(js)[采用axios请求]

登录功能的axios的请求方法

 注册功能的axios的请求方法

index.js的axios的请求方法

对ai聊天内容解码

 将AI内容插入页面的的function方法

AI聊天的axios请求

拓展的js功能

验证用户登录状态

对聊天界面的限制,登录之后才能使用AI对话。

AI聊天内容加载交互

历史对话加载和渲染

功能拓展和后续开发方向

总结


项目背景

随着人工智能技术的快速发展,智能对话助手已成为提升用户体验和工作效率的重要工具。本项目旨在 开发一个功能完善、界面友好的 AI 智能助手系统,参考豆包 AI 助手的设计理念,为用户提供智能对话、 文档处理、知识问答等服务

环境依赖

python:3.8+

运行编程软件:pycharm

拥有openai,fastapi,uvc

pip install openai
pip install FastAPI

 FastAPI初始化


from fastapi import FastAPI
import uvicorn

app = FastAPI()


//该段代码作用于跨域请求
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 允许所有来源;你可以改为 ["http://localhost:5500"] 指定具体域名
    allow_credentials=True,
    allow_methods=["*"],  # 允许所有方法(GET, POST, OPTIONS等)
    allow_headers=["*"],  # 允许所有头部
)



if __name__ == '__main__':
    uvicorn.run(app, host="127.0.0.1", port=8000)

API获取

篇幅有限,可以点击下面链接进行详细参考获取

如何获取AI模型及本地部署-优快云博客

数据库设计

users表

  CREATE TABLE IF NOT EXISTS users 
        (id INTEGER PRIMARY KEY AUTOINCREMENT,
        phonenumber TEXT NOT NULL UNIQUE,
        username TEXT NOT NULL,
        password TEXT NOT NULL,
        created_time DATETIME DEFAULT CURRENT_TIMESTAMP
        )

 这段代码用于创建一个名为 users 的数据库表,功能如下:
id:自增主键,唯一标识每个用户
phonenumber:不能为空,且唯一(唯一手机号)
username:用户名,不能为空
password:密码,不能为空
created_time:创建时间,默认为当前时间
该表用于存储用户信息,常用于用户注册或登录系统中。

 chat_sessions表

CREATE TABLE IF NOT EXISTS chat_sessions 
        (
        id INTEGER AUTO_INCREMENT PRIMARY KEY,
        user_id INTEGER NOT NULL,
        session_id TEXT NOT NULL,
        role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
        content TEXT NOT NULL,
        FOREIGN KEY (user_id) REFERENCES users (id)
        )

用于创建一个名为 chat_sessions 的数据表,其功能是存储用户的聊天会话记录。具体结构如下:
id:主键,自动递增。
user_id:用户 ID,不可为空,外键关联 users 表的 id。
session_id:会话 ID,文本类型,不可为空。
role:角色,只能是 'user'、'assistant' 或 'system'。
content:聊天内容,不可为空。

chat_sessions_log表

CREATE TABLE chat_sessions_log (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        user_id INTEGER NOT NULL,
        session_id TEXT NOT NULL,
        session_data TEXT NOT NULL,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP   
    )

 这段代码用于创建一个名为 chat_sessions_log 的数据库表,包含以下字段:
id:自增主键
user_id:用户ID,整数且非空
session_id:会话ID,文本且非空
session_data:会话数据,文本且非空
created_at:创建时间,默认值为当前时间戳
功能:记录用户聊天会话信息,用于渲染到历史对话栏。

表结构表现

 

 

 

FastAPI接口

关于用户方面,需要完成登录,注册的post请求方法。

关于AI聊天方面,需要完成聊天请求的post请求和获取历史对话的get请求方法。


登录请求接口

@app.post("/login")
cursor.execute("SELECT * FROM users WHERE phonenumber=? AND password=?", (phonenumber, has_password))
        user = cursor.fetchone()
        if user:
            # 返回用户完整信息(包括 id)
            return {
                "message": "登录成功",
                "user": {
                    "id": user[0],
                    "username": user[2],
                    "phonenumber": user[1]
                }
            }
        else:
            return {"message": "用户名或密码错误"}

 这段代码实现用户登录验证功能:
        1.使用手机号和哈希后的密码在数据库中查询用户;
        2.如果查到用户记录,返回登录成功及用户信息(ID、用户名、手机号);
        3.如果未查到记录,返回“用户名或密码错误”。

注册请求接口

@app.post("/register")
async def register(request: Request):
    body = await request.json()
    print("收到的请求体:", body)

    # 手动提取字段
    username = body.get('username')
    phonenumber = body.get('phonenumber')
    password = body.get('password')

    if not all([username, phonenumber, password]):
        return {"message": "缺少必要字段"}

    # 新增:手机号格式判断
    if not re.match(r"^1\d{10}$", phonenumber):
        return {"message": "手机号格式不正确,必须为11位且以1开头"}

    has_password = hash_password(password)

    conn = sqlite3.connect("data.db")
    cursor = conn.cursor()

    try:
        cursor.execute("SELECT * FROM users WHERE phonenumber=?", (phonenumber,))
        if cursor.fetchone():
            return {"message": "手机号已被注册"}

        cursor.execute(
            "INSERT INTO users (phonenumber, username, password) VALUES (?, ?, ?)",
            (phonenumber, username, has_password)
        )
        conn.commit()
        return {"message": "注册成功"}
    except Exception as e:
        print(f"注册失败: {e}")
        return {"message": "注册失败", "error": str(e)}
    finally:
        conn.close()

该函数实现用户注册功能,流程如下:
        1.接收请求并解析 JSON 数据;
        2.提取用户名、手机号、密码字段,校验是否完整;
        3.使用正则校验手机号格式;
        4.对密码进行 SHA256 哈希处理;
        5.连接数据库,检查手机号是否已注册;
        6.若未注册,则将用户信息插入数据库;
        7.异常处理并返回相应结果,确保连接最终关闭。

AI聊天请求接口

@app.post("/chat")
conn = sqlite3.connect("data.db")
        cursor = conn.cursor()

        # 解析 JSON 字符串
        messages = json.loads(session_data)

        # 示例调用模型(可以跳过)
        client = OpenAI(
            api_key="XXX-XXXXX",
            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
        )
        completion = client.chat.completions.create(model="qwen-plus", messages=messages)
        assistant_output = completion.choices[0].message.content

        # 插入完整对话(作为 JSON 字符串) + 最新回复
        # 插入完整对话到 chat_sessions_log 表中
        cursor.execute(
            "INSERT INTO chat_sessions_log (user_id, session_id, session_data) VALUES (?, ?, ?)",
            (user_id, session_id, session_data)
        )

        # 插入 AI 回复
        cursor.execute(
            "INSERT INTO chat_sessions (user_id, session_id, role, content) VALUES (?, ?, ?, ?)",
            (user_id, session_id, "assistant", assistant_output)
        )

        conn.commit()
        return {"session_id": session_id, "response": assistant_output}

这段代码的主要功能是处理用户的聊天请求,具体包括以下步骤:
1.连接数据库:使用 sqlite3 连接本地数据库 data.db。
2.解析对话数据:将 session_data 从 JSON 字符串解析为 Python 对象 messages。
3.调用 AI 模型:通过 OpenAI 兼容接口调用 qwen-plus 模型生成 AI 回复。
4.记录完整对话日志:将原始的 session_data 插入到 chat_sessions_log 表中。
5.保存 AI 回复内容:将 AI 的回复作为角色 "assistant" 插入到 chat_sessions 表中。
3.提交事务并返回结果:提交数据库更改,并返回会话 ID 和 AI 的回复。
简而言之:接收用户输入 → 调用模型生成回复 → 记录对话日志和回复内容 → 返回结果。

界面设计参考

 

 

html界面设计代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录/注册界面</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: #f5f5f5;
            margin: 0;
        }

        .auth-container {
            width: 320px;
            padding: 30px;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
            position: relative;
            overflow: hidden;
            min-height: 400px;
        }

        .auth-title {
            text-align: center;
            margin-bottom: 30px;
            color: #333;
            font-size: 24px;
        }

        .input-group {
            margin-bottom: 25px;
        }

        .input-field {
            width: 100%;
            padding: 10px 0;
            border: none;
            border-bottom: 1px solid #ddd;
            outline: none;
            font-size: 16px;
            transition: border-color 0.3s;
        }

        .input-field:focus {
            border-bottom-color: #4285f4;
        }

        .button-group {
            display: flex;
            justify-content: space-between;
            margin-top: 30px;
        }

        .btn {
            width: 48%;
            padding: 12px 0;
            border: none;
            border-radius: 4px;
            font-size: 16px;
            cursor: pointer;
            transition: all 0.3s;
        }

        .btn-login {
            background-color: #4285f4;
            color: white;
        }

        .btn-login:hover {
            background-color: #3367d6;
        }

        .btn-register {
            background-color: #f1f1f1;
            color: #555;
        }

        .btn-register:hover {
            background-color: #e0e0e0;
        }

        .btn-back {
            background-color: #f1f1f1;
            color: #555;
        }

        .btn-back:hover {
            background-color: #e0e0e0;
        }

        .btn-submit {
            background-color: #34a853;
            color: white;
        }

        .btn-submit:hover {
            background-color: #2d8e47;
        }

        .form-container {
            transition: all 0.3s ease;
            position: absolute;
            width: calc(100% - 60px);
        }

        #login-form {
            opacity: 1;
            transform: translateX(0);
        }

        #register-form {
            opacity: 0;
            transform: translateX(100%);
        }

        .show-register #login-form {
            opacity: 0;
            transform: translateX(-100%);
        }

        .show-register #register-form {
            opacity: 1;
            transform: translateX(0);
        }
    </style>
</head>
<body>
    <div class="auth-container" id="auth-container">
        <div class="form-container" id="login-form">
            <h1 class="auth-title">用户登录</h1>

            <div class="input-group">
                <input type="text" class="input-field" placeholder="用户名" id="username" required>
            </div>

            <div class="input-group">
                <input type="tel" class="input-field" placeholder="手机号" id="phonenumber" required>
            </div>

            <div class="input-group">
                <input type="password" class="input-field" placeholder="密码" id="user_password" required>
            </div>

            <div class="button-group">
                <button type="submit" class="btn btn-login" id="login-btn">登录</button>
                <button type="button" class="btn btn-register" id="show-register">注册</button>
            </div>
        </div>

        <div class="form-container" id="register-form">
            <h1 class="auth-title">用户注册</h1>

            <div class="input-group">
                <input type="text" class="input-field" placeholder="注册用户名" id="user_name" required >
            </div>

            <div class="input-group">
                <input type="tel" class="input-field" placeholder="注册手机号" id="phone_number" required >
            </div>

            <div class="input-group">
                <input type="password" class="input-field" placeholder="密码" id="password" required >
            </div>

            <div class="input-group">
                <input type="password" class="input-field" placeholder="确认密码" id="res_password" required>
                <!-- 在手机号输入框下方加一个提示 -->
                <div id="phone-error" style="color: red; font-size: 12px; height: 12px;"></div>
            </div>

            <div class="button-group">
                <button type="button" class="btn btn-back" id="show-login">返回登录</button>
                <button type="submit" class="btn btn-submit" id="register-btn">注册</button>
            </div>
        </div>
    </div>

    <script>
    const authContainer = document.getElementById('auth-container');
    const showRegisterBtn = document.getElementById('show-register');
    const showLoginBtn = document.getElementById('show-login');

    // 切换到注册界面的函数
    function showRegisterForm() {
        authContainer.classList.add('show-register');
    }

    // 切换到登录界面的函数
    function showLoginForm() {
        authContainer.classList.remove('show-register');
    }

    // 页面加载时判断 hash 值
    window.addEventListener('DOMContentLoaded', () => {
        if (window.location.hash === '#register') {
            showRegisterForm(); // 显示注册界面
        } else {
            showLoginForm(); // 默认显示登录界面
        }
    });

    // 点击按钮切换界面
    showRegisterBtn.addEventListener('click', showRegisterForm);
    showLoginBtn.addEventListener('click', showLoginForm);
    </script>

    <script src="js/axios.min.js"></script>
    <script src="js/register.js"></script>
    <script src="js/login.js"></script>
</body>
</html>

 

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>智能对话系统</title>
    <link rel="stylesheet" href="css/index.css">
</head>
<body>
    <!-- 左侧栏 -->
    <div class="left-panel">
        <!-- 左侧上部 -->
        <div class="panel-section top-section">
            <div class="header">
                <h1 class="page-title">智能AI助手</h1>
                <button class="new-chat-btn">新建对话</button>
            </div>
            <select class="agent-select">
                <option>智能体选择</option>
                <option>智能体1</option>
                <option>智能体2</option>
                <option>智能体3</option>
            </select>
        </div>

        <!-- 左侧中部:历史对话区域 -->
        <div class="history-section panel-section">
            <!-- 历史对话内容将动态添加 -->
        </div>

        <!-- 左侧下部:登录注册 -->
        <div class="panel-section auth-section" id="auth-container">
    <!-- 登录前 -->
    <div id="login-register-buttons">
        <button class="auth-btn login-btn" id="login_btn">登录</button>
        <button class="auth-btn register-btn" id="register_btn">注册</button>
    </div>

    <!-- 登录后 -->
    <div id="user-info" style="display: none;">
        <div class="avatar-circle">👤</div>
        <div class="user-details">
            <p id="user-username">用户名</p>
            <p id="user-phone">手机号</p>
        </div>
        <button class="auth-btn register-btn" id="logout_btn">退出登录</button>
    </div>
</div>

    </div>

    <!-- 右侧栏 -->
    <div class="right-panel">
        <!-- 右侧上部:固定文字内容 -->
        <div class="info-section">
            <h2 class="info-title">智能AI助手</h2>
            <p>我是智能AI助手,专门帮你解答各种问题。</p>
        </div>
        <!-- 右侧中部:对话渲染区域 -->
        <div class="chat-section">
            <!-- 对话内容将动态渲染 -->
        </div>

        <!-- 右侧下部:输入区域 -->
        <div class="input-section">
            <!-- 问题输入框 -->
            <textarea class="message-input" placeholder="请输入您的问题..." id="user_input"></textarea>

            <!-- 其他模式和提交按钮 -->
            <div class="controls">
                <div class="mode-buttons">
                    <button class="mode-btn active">模式1</button>
                    <button class="mode-btn">模式2</button>
                    <button class="mode-btn">模式3</button>
                </div>
                <div class="action-buttons">
                    <!-- 新增:联网搜索按钮 -->
                    <button class="search-btn" id="web-search-btn">🌐 联网搜索</button>
                    <!-- 文件上传按钮 -->
                    <label for="file-upload" class="upload-btn">
                        <i>📎</i>
                        <input type="file" id="file-upload" class="file-upload" multiple />
                    </label>
                    <!-- 提交按钮 -->
                    <button class="submit-btn" id="submit-btn">提交</button>
                </div>
            </div>
        </div>
    </div>
<script src="js/index.js"></script>
<script src="js/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</body>
</html>

密码加密功能

def hash_password(password):
    """
    对密码进行哈希处理,返回哈希后的密码
    """
    # 使用 SHA256 加密算法对密码进行哈希处理
    hashed_password = hashlib.sha256(password.encode()).hexdigest()
    return hashed_password

 注:为了防止用户信息泄露,使用哈希加密,进行登录判断不需要进行解密,只需要登录输入框进行哈希加密判断对比数据库内存储的哈希值进行判断。

主要的前端功能(js)[采用axios请求]

登录功能的axios的请求方法

axios({
        method: 'post',
        url: 'http://127.0.0.1:8000/login',
        data: {
            username: username.value,
            phonenumber: phone.value,
            password: password.value
        }
    }).then(function (res) {
        alert(res.data.message);
        if (res.data.message === "登录成功") {
            // 保存用户信息到 localStorage
            const userData = res.data.user;
            localStorage.setItem("user", JSON.stringify(userData));
            // 跳转回首页
            window.location.href = "index.html";
        }
    }).catch(function (err) {
        let errorMessage = "登录失败,请稍后再试";
        if (err.response && err.response.data && err.response.data.message) {
            errorMessage = err.response.data.message;
        }
        alert(errorMessage);
        console.error(err);
    });

 注册功能的axios的请求方法

axios({
        method: 'post',
        url: 'http://127.0.0.1:8000/register',
        data: {
            username: username.value,
            phonenumber: phonenumber.value,
            password: password.value
        }
    }).then(function (res) {
        alert(res.data.message);
        if (res.data.message === "注册成功") {
            window.location.href = "denglu.html";
        }
    }).catch(function (err) {
        let errorMessage = "注册失败,请稍后再试";
        if (err.response && err.response.data && err.response.data.message) {
            errorMessage = err.response.data.message;
        }
        alert(errorMessage);
        console.error(err);
    });

index.js的axios的请求方法

对ai聊天内容解码

首先我们获取的AI输出的内容含有##,###等一些其他未知符号的内容,它可能是一些格式,我们需要对这些内容进行解读,解码操作。

将内容中的HTML实体解码后,将## 标题转为<h2>标签,换行转为<br>,--行转为<hr>。


function formatContentSimple(content) {
        content = decodeHTMLEntities(content);
        return content
            .replace(/##\s*(.+)$/gim, '<h2>$1</h2>')
            .replace(/\n/g, '<br>')
            .replace(/^--$/gim, '<hr>');
    }

 将AI内容插入页面的的function方法

function decodeHTMLEntities(text) {
        const textArea = document.createElement("textarea");
        textArea.innerHTML = text;
        return textArea.value;
    }

AI聊天的axios请求

axios({
            method: 'post',
            url: 'http://127.0.0.1:8000/chat',
            data: {
                user_id: user_id,
                session_id: sessionId,
                session_data: JSON.stringify(currentSession)
            }
        }).then(function (res) {
            stopTypingAnimation();
            const decoded = decodeHTMLEntities(res.data.response);
            const formatted = formatContentSimple(decoded);
            loadingMessage.innerHTML = `<strong>助手:</strong>${formatted}`;
        }).catch(function (error) {
            stopTypingAnimation();
            console.error('请求失败:', error);
            loadingMessage.innerHTML = `<strong>助手:</strong>抱歉,AI 回答失败,请稍后再试。`;
        });
    });

拓展的js功能

验证用户登录状态

function getCurrentUser() {
        const userStr = localStorage.getItem("user");
        if (!userStr) return null;
        try {
            return JSON.parse(userStr);
        } catch (e) {
            console.error("解析用户信息失败", e);
            return null;
        }
    }

const currentUser = getCurrentUser();
    let userId = null;

    if (currentUser) {
        userId = currentUser.id;

        axios.get(`http://127.0.0.1:8000/user/${userId}`)
            .then(res => {
                if (res.data.exists) {
                    usernameDisplay.textContent = res.data.user.username;
                    phoneDisplay.textContent = res.data.user.phonenumber;
                    loginRegisterButtons.style.display = "none";
                    userInfoContainer.style.display = "block";

                    axios.get(`http://127.0.0.1:8000/history/${userId}`)
                        .then(res => {
                            const history = res.data.history;
                            renderHistory(history);
                             renderHistoryToSidebar();
                        })
                        .catch(err => {
                            console.error("获取历史记录失败", err);
                        });
                } else {
                    localStorage.removeItem("user");
                    window.location.reload();
                }
            })
            .catch(err => {
                console.error("验证用户失败:", err);
                localStorage.removeItem("user");
                window.location.reload();
            });
    }

对聊天界面的限制,登录之后才能使用AI对话。

注:和问题输入框提交问题绑定在一起

在市面上你可以见到的大部分AI聊天都是要登录之后才能进行使用,这是为了防止恶意注入数据等一些问题。同时也是为了不同的用户进行个性化的定制提升用户的使用体验。

const currentUser = getCurrentUser();
        if (!currentUser) {
            showError("请先登录");
            return;
        }

AI聊天内容加载交互

我们做一个AI聊天都是要有交互体验的,在不采用流式输出的情况下,这种交互要体验出来,因为AI要把所有内容获取到才能整体插入到页面中,你可能等个十几秒才能看见内容。这样子别人以为你的AI聊天是坏的,所以需要采用有交互体验的方式。

function startTypingAnimation(element) {
        clearInterval(typingAnimation);
        let dotCount = 0;
        typingAnimation = setInterval(() => {
            element.textContent = "加载中" + ".".repeat(dotCount % 4);
            dotCount++;
        }, 500);
    }

历史对话加载和渲染

newChatBtn.addEventListener("click", function () {
        const messages = Array.from(chatSection.children).map(msg => ({
            role: msg.classList.contains('user') ? 'user' : 'assistant',
            content: msg.innerHTML
        }));

        if (messages.length > 0 && firstQuestion) {
            const sessionKey = `chat_session_${firstQuestion}`;
            localStorage.setItem(getUserKey(sessionKey), JSON.stringify(messages));

            let historyList = JSON.parse(localStorage.getItem(getUserKey("chat_history_list"))) || [];

            if (!historyList.includes(sessionKey)) {
                historyList.unshift(sessionKey);
                localStorage.setItem(getUserKey("chat_history_list"), JSON.stringify(historyList));
            }
        }

        chatSection.innerHTML = "";
        currentSession = [];
        user_input.value = "";
        firstQuestion = "";

        renderHistoryToSidebar();
    });

    function loadChatSession(sessionKey) {
        const messages = JSON.parse(localStorage.getItem(getUserKey(sessionKey)));
        if (messages) {
            chatSection.innerHTML = "";
            currentSession = [];
            messages.forEach(msg => {
                appendMessage(msg.role, msg.content);
            });
        }
        firstQuestion = "";
    }

    function renderHistoryToSidebar() {
        const historySection = document.querySelector(".history-section");
        const historyList = JSON.parse(localStorage.getItem(getUserKey("chat_history_list"))) || [];

        historySection.innerHTML = "";

        historyList.forEach(sessionKey => {
            const title = sessionKey.replace("chat_session_", "");

            const div = document.createElement("div");
            div.className = "session-box";
            div.textContent = title;

            div.addEventListener("click", () => {
                loadChatSession(sessionKey);
            });

            historySection.appendChild(div);
        });
    }

这段 JavaScript 代码实现了以下功能:
保存聊天记录:点击 newChatBtn 时,将当前聊天内容保存到 localStorage,并以 firstQuestion 作为标识。
更新聊天历史列表:将新的聊天会话键(session key)加入历史列表并更新本地存储。
清空当前聊天界面:清空聊天区域、用户输入框和当前会话数据。
渲染历史记录到侧边栏:从 localStorage 中读取历史记录,动态生成可点击的会话项。
加载历史会话:点击历史记录时调用 loadChatSession,恢复对应聊天内容到界面。

功能拓展和后续开发方向

1.实现流式输出

2.实现联网功能

3.实现模式切换,如:智能问答模式,写作模式,AI图片生成模式等等

4.实现语音输入功能

5.添加QQ,微信登录注册功能,邮箱登录功能等等的登录注册方式

6.添加对历史对话的管理

7.实现对用户的账号密码管理,如忘记密码,注销账号等。

8.实现对智能体切换

9.实现文件上传和对文件解码操作输出文件的功能操作

10.优化界面,实现响应式交互等等。

还有很多功能方向可以进行优化。

总结

该项目目前处于初步开发阶段,属于个人完成,界面需要优化,等一些功能较少,不过基本上能实现交互对话的方式,和历史对话的存储及读取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值