打造AI智能”成语接龙“游戏

目录

一、项目背景与技术选型

[1. 需求分析](#1. 需求分析)

[2. 技术栈选择](#2. 技术栈选择)

二、系统架构与核心模块设计

[1. 后端核心模块:游戏逻辑类(IdiomGame)](#1. 后端核心模块:游戏逻辑类(IdiomGame))

(1)初始成语生成(generate_initial_idiom)

(2)游戏状态管理

[(3)API 接口封装](#(3)API 接口封装)

[2. AI 交互层:容错与响应处理](#2. AI 交互层:容错与响应处理)

[3. 前端交互层:用户体验优化](#3. 前端交互层:用户体验优化)

(1)状态可视化

(2)输入校验

(3)历史记录展示

(4)完整代码

三、关键技术难点与解决方案

[1. AI 响应异步处理问题](#1. AI 响应异步处理问题)

[2. 跨域请求问题](#2. 跨域请求问题)

[3. 敏感配置管理](#3. 敏感配置管理)

[4. 用户体验与容错平衡](#4. 用户体验与容错平衡)

四、系统优化与扩展方向

[1. 现有优化点](#1. 现有优化点)

[2. 扩展方向](#2. 扩展方向)

(1)功能扩展

(2)技术优化

五、项目总结与思考

[1. 技术价值](#1. 技术价值)

[2. 产品思考](#2. 产品思考)

[3. 开发感悟](#3. 开发感悟)

六、快速上手指南

[1. 环境准备](#1. 环境准备)

[2. 启动服务](#2. 启动服务)

[3. 访问游戏](#3. 访问游戏)

结语


成语接龙作为中国传统文化的经典游戏,既考验词汇量,又锻炼思维敏捷度。当传统游戏遇上 AI 技术,会碰撞出怎样的火花?本文将详细拆解一个基于 Flask+Coze AI 打造的智能成语接龙游戏的实现过程,从技术架构、核心逻辑到用户体验优化,带你深入了解如何将 AI 能力落地到实际应用中。

一、项目背景与技术选型

1. 需求分析

我们希望打造一个兼具趣味性和智能化的成语接龙游戏,核心需求包括:

  • 自动生成初始接龙成语,支持游戏重置
  • 验证用户输入的成语有效性,实现 AI 自动接龙
  • 记录游戏历史,提供友好的交互界面
  • 具备容错机制,在 AI 服务异常时保证游戏可用

2. 技术栈选择

技术 / 工具 用途 选型理由
Flask 后端 Web 框架 轻量级、易上手,适合快速开发小型 API 服务
Coze AI 智能成语生成 字节跳动旗下 AI 平台,支持多轮对话,响应速度快,中文处理能力优秀
Flask-CORS 跨域支持 解决前端页面与后端 API 的跨域请求问题
HTML/CSS/JS 前端界面 原生技术栈,无需额外依赖,适配性强
dotenv 环境变量管理 安全管理敏感配置(如 API Token),便于环境切换

二、系统架构与核心模块设计

整个系统分为后端服务层AI 交互层前端交互层三个核心部分,架构如下:

复制代码
前端页面(HTML/JS) → 后端API(Flask) → Coze AI SDK → 成语生成/验证
    ↑                      ↓                    ↓
游戏状态展示 ← 游戏逻辑处理 ← AI响应解析与容错 ← 成语结果返回

1. 后端核心模块:游戏逻辑类(IdiomGame)

游戏的核心逻辑封装在IdiomGame类中,主要承担三个核心职责:

(1)初始成语生成(generate_initial_idiom)
  • AI 调用流程:构造用户指令消息,调用 Coze Chat API 生成四字成语
  • 容错机制:设置 30 秒超时时间,若 AI 响应超时 / 返回无效结果,自动切换到默认成语列表
  • 数据清洗:过滤非中文字符,确保返回结果为标准四字成语
python 复制代码
def generate_initial_idiom(self):
    try:
        # 构造AI请求消息
        messages = [Message(role="user", content="生成一个四字成语作为开头,仅返回成语本身")]
        chat = self.coze.chat.create(bot_id=self.bot_id, user_id=self.user_id, additional_messages=messages)
        
        # 等待AI响应(超时控制)
        timeout = 0
        while chat.status == ChatStatus.IN_PROGRESS and timeout < 30:
            chat = self.coze.chat.retrieve(conversation_id=chat.conversation_id, chat_id=chat.id)
            timeout += 1
            time.sleep(1)
        
        # 解析并清洗AI响应
        initial_idiom = msg.content.strip()
        initial_idiom = "".join(filter(lambda x: '\u4e00' <= x <= '\u9fff', initial_idiom))
        if initial_idiom and len(initial_idiom) == 4:
            return initial_idiom
        raise Exception("AI生成的初始成语无效")
    except Exception as e:
        # 降级策略:使用默认成语列表
        return random.choice(COMMON_IDIOMS)
(2)游戏状态管理
  • reset_game:重置游戏,生成新初始成语并清空历史记录
  • add_to_history:记录用户与 AI 的接龙记录,限制最多保存 20 条
  • get_sdk_response:处理用户输入,调用 AI 完成接龙,返回标准化响应
(3)API 接口封装

后端暴露三个核心 API 接口,实现前后端交互:

接口路径 请求方法 功能
/api/init GET 初始化游戏,返回当前成语和历史记录
/api/play POST 提交用户成语,返回 AI 接龙结果
/api/restart POST 重置游戏,生成新初始成语

2. AI 交互层:容错与响应处理

AI 交互是整个系统的关键环节,我们做了多层容错设计:

  1. 超时保护:所有 AI 请求设置 30 秒超时,避免服务挂起
  2. 响应验证:检查返回结果是否为 4 个中文字符,无效则触发降级
  3. 异常捕获:捕获网络错误、API 调用错误等,统一返回错误信息
  4. 历史兜底:即使 AI 服务完全不可用,默认成语列表仍能保证游戏基础功能

3. 前端交互层:用户体验优化

前端页面聚焦于流畅的交互体验清晰的状态反馈,核心设计点包括:

(1)状态可视化
  • 加载状态:初始成语获取时显示 "加载中..."
  • 操作反馈:提交按钮显示 "提交中...",禁用重复点击
  • 结果提示:不同类型的消息使用不同样式(成功 - 绿色、错误 - 红色、信息 - 蓝色)
(2)输入校验

前端提前做输入验证,减少无效请求:

javascript 复制代码
if (!/^[\u4e00-\u9fa5]+$/.test(userInput)) {
    showMessage('请输入中文成语', 'error');
    return;
}
if (userInput.length !== 4) {
    showMessage('请输入四字成语', 'error');
    return;
}
(3)历史记录展示

按时间倒序展示用户与 AI 的接龙记录,清晰呈现游戏进程:

html 复制代码
<li>
    <span class="user">你: 一心一意</span>
    <span class="ai">AI: 意气风发</span>
    <span class="time">14:25:30</span>
</li>
(4)完整代码

后端:

python 复制代码
# 导入必要的库和模块
import os  # 用于操作系统环境变量
from flask import Flask, request, jsonify, send_file  # Flask web框架相关功能
from cozepy import Coze, TokenAuth, ChatStatus, COZE_CN_BASE_URL, Message  # Coze AI服务SDK
from dotenv import load_dotenv  # 用于加载环境变量文件
import uuid  # 用于生成唯一的用户ID
import time  # 用于时间相关操作

# 加载.env文件中的环境变量
load_dotenv()

# 创建Flask应用实例
app = Flask(__name__)

# 用于存储会话状态的字典,key为客户端IP或自定义标识,value为会话信息
user_sessions = {}


# 定义Coze服务类,用于封装与Coze AI服务的交互
class CozeService:
    def __init__(self):
        # 从环境变量获取Coze API令牌,如果没有则返回None
        self.api_token = os.getenv("COZE_API_TOKEN")
        # 从环境变量获取机器人ID,如果没有则使用默认值
        self.bot_id = os.getenv("BOT_ID", "7552823978826694671")
        # 初始化Coze客户端,使用令牌认证和中国区基础URL
        self.coze = Coze(
            auth=TokenAuth(token=self.api_token),
            base_url=COZE_CN_BASE_URL
        )

    def get_sdk_response(self, user_message, user_identifier):
        """获取智能体响应,基于用户标识保持会话"""
        try:
            # 检查是否已有该用户的会话
            if user_identifier in user_sessions:
                session_data = user_sessions[user_identifier]
                conversation_id = session_data["conversation_id"]
                user_id = session_data["user_id"]

                # 构建用户消息
                messages = [
                    Message(
                        role="user",  # 消息角色为用户
                        content=user_message,  # 消息内容
                        content_type="text",  # 内容类型为文本
                        type="question"  # 消息类型为问题
                    )
                ]

                # 在现有会话中继续聊天
                chat = self.coze.chat.create(
                    bot_id=self.bot_id,  # 指定机器人ID
                    user_id=user_id,  # 使用相同的用户ID
                    conversation_id=conversation_id,  # 使用相同的会话ID
                    additional_messages=messages,  # 添加用户消息
                    auto_save_history=True  # 自动保存聊天历史
                )
            else:
                # 创建新的会话
                # 生成唯一的用户ID
                user_id = str(uuid.uuid4())

                # 构建用户消息
                messages = [
                    Message(
                        role="user",  # 消息角色为用户
                        content=user_message,  # 消息内容
                        content_type="text",  # 内容类型为文本
                        type="question"  # 消息类型为问题
                    )
                ]

                # 创建新的聊天会话
                chat = self.coze.chat.create(
                    bot_id=self.bot_id,  # 指定机器人ID
                    user_id=user_id,  # 指定用户ID
                    additional_messages=messages,  # 添加用户消息
                    auto_save_history=True  # 自动保存聊天历史
                )

                # 保存会话信息到user_sessions字典中
                user_sessions[user_identifier] = {
                    "conversation_id": chat.conversation_id,  # Coze的会话ID
                    "user_id": user_id,  # 用户ID
                    "chat_id": chat.id,  # 聊天ID
                    "created_at": time.time(),  # 会话创建时间戳
                    "last_activity": time.time()  # 最后活动时间
                }

            # 更新最后活动时间
            user_sessions[user_identifier]["last_activity"] = time.time()
            # 更新chat_id(每次新的聊天都会生成新的chat_id)
            user_sessions[user_identifier]["chat_id"] = chat.id

            # 轮询等待聊天完成,当聊天状态为"进行中"时继续查询
            while chat.status == ChatStatus.IN_PROGRESS:
                chat = self.coze.chat.retrieve(
                    conversation_id=chat.conversation_id,  # 会话ID
                    chat_id=chat.id  # 聊天ID
                )

            # 如果聊天状态为"已完成",获取聊天消息
            if chat.status == ChatStatus.COMPLETED:
                messages = self.coze.chat.messages.list(
                    conversation_id=chat.conversation_id,  # 会话ID
                    chat_id=chat.id  # 聊天ID
                )
                # 遍历消息,找到助手的回复
                for msg in messages:
                    if msg.role == "assistant":
                        return {
                            "status": "success",
                            "content": msg.content
                        }
            # 如果对话未完成,返回失败状态
            return {"status": "failed", "content": "对话未完成"}

        except Exception as e:
            # 如果发生异常,返回错误状态和异常信息
            return {"status": "error", "content": str(e)}


# 创建CozeService实例
coze_service = CozeService()


@app.route('/')
def index():
    # 在返回页面之前清理过期会话
    cleanup_expired_sessions()

    # 返回index.html文件
    return send_file('index.html')


# 定义聊天路由,处理POST请求
@app.route('/chat', methods=['POST'])
def chat():
    # 从请求的JSON数据中获取用户消息
    data = request.json
    user_message = data.get('message')

    # 如果消息为空,返回错误
    if not user_message:
        return jsonify({"status": "error", "content": "消息不能为空"})

    # 使用客户端IP作为用户标识(也可以使用cookie或其他方式)
    user_identifier = request.remote_addr

    # 获取AI响应,基于用户标识保持会话
    response = coze_service.get_sdk_response(user_message, user_identifier)
    # 将响应转换为JSON格式返回
    return jsonify(response)


# 清理过期会话的辅助函数
def cleanup_expired_sessions():
    expired_sessions = []

    for user_identifier, session_data in user_sessions.items():
            expired_sessions.append(user_identifier)

    # 删除过期会话
    for user_identifier in expired_sessions:
        del user_sessions[user_identifier]


# 如果是直接运行此脚本,启动Flask应用
if __name__ == '__main__':
    # 启动应用,开启调试模式,端口为5000
    app.run(debug=True, port=5000)

前端:

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>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Arial', sans-serif;
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            color: #333;
            line-height: 1.6;
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 900px;
            margin: 0 auto;
            background-color: white;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            overflow: hidden;
        }

        header {
            background: linear-gradient(90deg, #3498db, #2c3e50);
            color: white;
            padding: 30px 20px;
            text-align: center;
        }

        header h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
            text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
        }

        header p {
            font-size: 1.2rem;
            opacity: 0.9;
        }

        .chat-container {
            display: flex;
            flex-direction: column;
            height: 500px;
        }

        .chat-messages {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
            background-color: #f9f9f9;
        }

        .message {
            margin-bottom: 15px;
            padding: 12px 18px;
            border-radius: 18px;
            max-width: 80%;
            animation: fadeIn 0.3s ease;
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .user-message {
            background-color: #3498db;
            color: white;
            margin-left: auto;
            border-bottom-right-radius: 5px;
        }

        .bot-message {
            background-color: #ecf0f1;
            color: #333;
            margin-right: auto;
            border-bottom-left-radius: 5px;
        }

        .input-area {
            display: flex;
            padding: 15px;
            border-top: 1px solid #eee;
            background-color: white;
        }

        #user-input {
            flex: 1;
            padding: 12px 18px;
            border: 1px solid #ddd;
            border-radius: 25px;
            outline: none;
            font-size: 1rem;
            transition: border-color 0.3s;
        }

        #user-input:focus {
            border-color: #3498db;
            box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
        }

        #send-button {
            margin-left: 10px;
            padding: 12px 25px;
            background-color: #3498db;
            color: white;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-size: 1rem;
            transition: background-color 0.3s, transform 0.2s;
        }

        #send-button:hover {
            background-color: #2980b9;
            transform: scale(1.05);
        }

        #send-button:active {
            transform: scale(0.98);
        }

        .typing-indicator {
            display: none;
            padding: 10px 15px;
            background-color: #ecf0f1;
            border-radius: 18px;
            margin-bottom: 15px;
            width: fit-content;
            border-bottom-left-radius: 5px;
        }

        .typing-dots {
            display: flex;
            align-items: center;
            height: 20px;
        }

        .typing-dot {
            width: 8px;
            height: 8px;
            background-color: #7f8c8d;
            border-radius: 50%;
            margin: 0 3px;
            animation: typingAnimation 1.4s infinite ease-in-out;
        }

        .typing-dot:nth-child(1) { animation-delay: 0s; }
        .typing-dot:nth-child(2) { animation-delay: 0.2s; }
        .typing-dot:nth-child(3) { animation-delay: 0.4s; }

        @keyframes typingAnimation {
            0%, 60%, 100% { transform: translateY(0); }
            30% { transform: translateY(-5px); }
        }

        @media (max-width: 600px) {
            header h1 {
                font-size: 2rem;
            }

            header p {
                font-size: 1rem;
            }

            .message {
                max-width: 90%;
            }

            #send-button {
                padding: 12px 20px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>我的未来我做主</h1>
            <p>与AI助手一起规划您的未来</p>
        </header>

        <div class="chat-container">
            <div id="chat-messages" class="chat-messages">
                <div class="message bot-message">
                    <p>您好!我是您的未来规划助手。请问您想了解什么关于未来规划的问题?</p>
                </div>
            </div>

            <div class="typing-indicator" id="typing-indicator">
                <div class="typing-dots">
                    <div class="typing-dot"></div>
                    <div class="typing-dot"></div>
                    <div class="typing-dot"></div>
                </div>
            </div>

            <div class="input-area">
                <input type="text" id="user-input" placeholder="输入您的问题...">
                <button id="send-button">发送</button>
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const chatMessages = document.getElementById('chat-messages');
            const userInput = document.getElementById('user-input');
            const sendButton = document.getElementById('send-button');
            const typingIndicator = document.getElementById('typing-indicator');

            // 添加用户消息到聊天界面
            function addUserMessage(message) {
                const messageElement = document.createElement('div');
                messageElement.classList.add('message', 'user-message');
                messageElement.innerHTML = `<p>${message}</p>`;
                chatMessages.appendChild(messageElement);
                scrollToBottom();
            }

            // 添加AI消息到聊天界面
            function addBotMessage(message) {
                const messageElement = document.createElement('div');
                messageElement.classList.add('message', 'bot-message');
                messageElement.innerHTML = `<p>${message}</p>`;
                chatMessages.appendChild(messageElement);
                scrollToBottom();
            }

            // 显示正在输入指示器
            function showTypingIndicator() {
                typingIndicator.style.display = 'block';
                scrollToBottom();
            }

            // 隐藏正在输入指示器
            function hideTypingIndicator() {
                typingIndicator.style.display = 'none';
            }

            // 滚动到底部
            function scrollToBottom() {
                chatMessages.scrollTop = chatMessages.scrollHeight;
            }

            // 发送消息到后端
            async function sendMessage() {
                const message = userInput.value.trim();
                if (!message) return;

                // 添加用户消息到界面
                addUserMessage(message);
                userInput.value = '';

                // 显示正在输入指示器
                showTypingIndicator();

                try {
                    // 发送请求到后端
                    const response = await fetch('/chat', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({ message: message })
                    });

                    const data = await response.json();

                    // 隐藏正在输入指示器
                    hideTypingIndicator();

                    if (data.status === 'success') {
                        addBotMessage(data.content);
                    } else {
                        addBotMessage('抱歉,我遇到了一些问题,请稍后再试。');
                        console.error('API Error:', data.content);
                    }
                } catch (error) {
                    // 隐藏正在输入指示器
                    hideTypingIndicator();
                    addBotMessage('网络错误,请检查您的连接。');
                    console.error('Fetch Error:', error);
                }
            }

            // 发送按钮点击事件
            sendButton.addEventListener('click', sendMessage);

            // 输入框回车事件
            userInput.addEventListener('keypress', function(e) {
                if (e.key === 'Enter') {
                    sendMessage();
                }
            });

            // 页面加载后自动聚焦到输入框
            userInput.focus();
        });
    </script>
</body>
</html>

三、关键技术难点与解决方案

1. AI 响应异步处理问题

Coze Chat API 采用异步响应模式,直接调用后无法立即获取结果。解决方案:

  • 轮询机制:调用chat.create后,循环调用chat.retrieve获取最新状态
  • 超时控制:设置最大轮询次数,避免无限等待
  • 状态判断:通过ChatStatus枚举值判断 AI 处理状态(IN_PROGRESS/COMPLETED)

2. 跨域请求问题

前端页面与后端 API 部署在同一域名但不同端口,导致跨域请求被浏览器拦截。解决方案:

  • 引入 Flask-CORS 扩展,全局启用 CORS:CORS(app)
  • 生产环境可配置指定域名白名单,提升安全性

3. 敏感配置管理

API Token、Bot ID 等敏感信息直接写死代码存在安全风险。解决方案:

  • 使用.env文件存储环境变量:COZE_API_TOKEN=xxx
  • 通过dotenv加载配置:load_dotenv()
  • 代码中通过os.environ.get()获取,避免硬编码

4. 用户体验与容错平衡

AI 响应存在延迟,若直接等待会导致用户体验下降。解决方案:

  • 前端禁用操作按钮并显示加载状态,明确告知用户处理中
  • 后端设置合理的超时时间(30 秒),兼顾响应速度和成功率
  • AI 服务异常时自动切换到本地成语列表,保证游戏不中断

四、系统优化与扩展方向

1. 现有优化点

  • 性能优化:历史记录限制最多 20 条,减少数据传输量
  • 资源复用 :全局唯一的IdiomGame实例,避免重复初始化 AI 客户端
  • 错误处理:全局异常处理器,统一返回 JSON 格式错误信息

2. 扩展方向

(1)功能扩展
  • 成语验证:增加成语合法性校验(接入成语词典 API)
  • 难度分级:根据用户水平调整 AI 接龙难度(如生僻成语 / 常用成语)
  • 计分系统:记录接龙成功次数,增加游戏竞技性
  • 多语言支持:适配繁体中文,面向海外用户
(2)技术优化
  • 缓存机制:缓存 AI 生成的成语列表,减少 API 调用次数
  • 异步处理:使用 Flask-Async 扩展,将 AI 请求改为异步任务,提升并发能力
  • 前端框架重构:使用 Vue/React 重构前端,提升代码可维护性
  • 部署优化:使用 Docker 容器化部署,支持多环境一键部署

五、项目总结与思考

1. 技术价值

这个小项目展示了 AI 技术与传统应用结合的可能性:

  • 轻量化 AI 集成:无需复杂的模型部署,通过 API 即可快速接入 AI 能力
  • 容错设计的重要性:任何依赖外部服务的应用,都需要做好降级策略
  • 前后端分离的简化实现:原生技术栈也能打造流畅的交互体验

2. 产品思考

从产品角度,这个游戏的核心价值在于 "轻量化" 和 "趣味性":

  • 无需下载安装,网页端直接体验
  • AI 接龙替代人工,随时随地可玩
  • 传统文化与现代技术结合,兼具教育意义和娱乐性

3. 开发感悟

  • 小项目也需要注重架构设计:模块化封装让代码更易维护
  • 容错机制是生产级应用的必备:用户不会关心技术细节,只在意是否可用
  • 快速迭代与验证:先实现核心功能,再逐步优化体验和扩展功能

六、快速上手指南

1. 环境准备

bash 复制代码
# 安装依赖
pip install flask flask-cors cozepy python-dotenv

# 创建.env文件
COZE_API_TOKEN=你的Coze API Token
BOT_ID=你的Coze Bot ID
USER_ID=自定义用户ID

2. 启动服务

bash 复制代码
python app.py

3. 访问游戏

打开浏览器,访问http://localhost:5000即可开始游戏。

结语

这个智能成语接龙游戏看似简单,却涵盖了前后端交互、AI 集成、容错设计、用户体验优化等多个开发维度。在实际开发中,我们不需要一开始就追求完美,而是先实现核心功能,再通过持续优化提升系统的稳定性和用户体验。

AI 技术的普及让小型应用也能具备智能能力,关键在于找到合适的应用场景,并用简洁的技术方案解决核心问题。希望本文的拆解能为你带来启发,也欢迎你基于这个项目继续扩展,打造更有趣的 AI 应用。

相关推荐
2301_790300963 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python
蚁巡信息巡查系统3 小时前
网站信息发布再巡查机制怎么建立?
大数据·人工智能·数据挖掘·内容运营
AI浩3 小时前
C-RADIOv4(技术报告)
人工智能·目标检测
打小就很皮...3 小时前
《在 React/Vue 项目中引入 Supademo 实现交互式新手指引》
前端·supademo·新手指引
Purple Coder3 小时前
AI赋予超导材料预测论文初稿
人工智能
C澒3 小时前
系统初始化成功率下降排查实践
前端·安全·运维开发
Data_Journal3 小时前
Scrapy vs. Crawlee —— 哪个更好?!
运维·人工智能·爬虫·媒体·社媒营销
云边云科技_云网融合4 小时前
AIoT智能物联网平台:架构解析与边缘应用新图景
大数据·网络·人工智能·安全
摘星编程4 小时前
React Native + OpenHarmony:自定义useFormik表单处理
javascript·react native·react.js
VCR__4 小时前
python第三次作业
开发语言·python