• [高校训练营] 基于 Vue3 + FastAPI + 华为云 MaaS +华为云码道构建会议智脑应用
    1 、概述1.1 案例介绍企业日常会议产生大量记录文本,但缺乏高效的后续跟进手段:摘要靠人工整理、任务靠口头传达、风险靠经验判断。本案例将带你从零构建一款"会议智脑"应用——上传会议记录后,自动调用华为云 MaaS 大模型(DeepSeek V4 Flash)生成摘要、提取关键决策和任务清单,并通过可视化看板、甘特图、日历等多维度视图进行任务追踪与风险预警。1.2 适用对象企业开发者个人开发者1.3 案例时间  本案例总时长预计60分钟。1.4 案例流程  1. 领取华为云 MaaS 平台大模型 Tokens,获取 API Key 和模型接入地址;  2. 配置 .env 环境变量,将 MaaS API Key 等信息写入配置;  3. 初始化数据库并启动后端服务,验证 API 文档可访问;  4. 启动前端开发服务器,登录系统并上传会议记录,体验 AI 自动生成摘要与任务提取;  5. 在任务看板、甘特图、日历等视图中查看和管理提取的任务,触发风险检测。1.5 资源总览  本案例使用的华为云服务均为按需付费,预计花费不超过50元(MaaS Tokens 代金券可覆盖)。体验完成后请及时释放资源,避免产生多余的费用。资源名称规格单价(元)    华为云 MaaSDeepSeek V4 Flash 大模型推理服务代金券可覆盖华为云码道(CodeArts)代码智能体通用体验版免费2 、环境和资源准备2.1 领取华为云MaaS平台大模型Tokens福利   方式一: 登录华为开发者空间,参考案例《华为开发者空间 - ModelArts Studio大模型通用代金券领取使用指导》中的“二、 开通MaaS平台大模型”章节内容领取代金券,获取到模型的API地址、模型名称和API Key。   方式二: 登录华为开发者空间,参考案例《华为云MaaS平台大模型Tokens领取使用指导》中的“二、 领取MaaS平台大模型Tokens”章节内容,领取MaaS平台DeepSeek V3系列大模型Tokens代金券,购买ModelArts Studio DeepSeek Tokens套餐包,开通模型服务,最后获取到模型的API地址、模型名称和API Key。     2.2 安装本地开发环境本案例需要以下开发工具: 工具版本要求用途Python≥ 3.11后端运行时Node.js≥ 18.0前端构建npm≥ 9.0前端包管理Git≥ 2.30版本控制3 、构建会议智脑应用3.1 部署项目代码1)项目结构说明:ai-meeting/├── app/│   ├── core/                    # 核心基础设施│   │   ├── config.py            # Pydantic Settings 配置管理│   │   ├── database.py          # 异步数据库引擎 + 会话工厂│   │   ├── exceptions.py        # 统一异常处理器│   │   └── logging.py           # 日志配置│   ├── models/                  # SQLAlchemy ORM 模型│   │   ├── user.py              # 用户模型│   │   ├── meeting.py           # 会议模型│   │   ├── task.py              # 任务模型│   │   └── risk_alert.py        # 风险预警模型│   ├── schemas/                 # Pydantic 请求/响应 Schema│   ├── api/                     # FastAPI 路由│   │   ├── auth.py              # 认证 + 用户管理 API│   │   ├── meetings.py          # 会议 CRUD + 搜索 + 导出│   │   ├── tasks.py             # 任务列表 + 更新 + 排序│   │   └── skill.py             # 风险检测 API│   ├── services/                # 业务逻辑层│   │   ├── auth_service.py      # 注册/登录/JWT/密码哈希│   │   ├── meeting_service.py   # 会议业务逻辑│   │   ├── task_service.py      # 任务业务逻辑│   │   └── maas_service.py      # MaaS API + 本地摘要引擎│   └── main.py                  # FastAPI 应用入口├── frontend/│   ├── package.json             # 前端依赖│   ├── vite.config.js           # Vite 配置(API 代理)│   └── src/│       ├── main.js              # 入口(ElementPlus 中文 locale)│       ├── App.vue              # 布局(侧边栏 + 路由 + 登录状态)│       └── components/          # 12 个功能组件├── .env                         # 环境变量├── requirements.txt             # Python 依赖└── init_db.py                   # 数据库初始化脚本 2)下载源码         通过git下载源码到本地(含demo演示),代码仓地址:ai-meeting - AtomGitgit clone https://gitcode.com/ gcw_Xpooy7x3/ai-meeting.gitcd ai-meeting 3)关键代码讲解   3.1 配置管理——从 .env 加载 MaaS API Key        使用 Pydantic Settings 从 .env 文件加载配置,extra: "ignore" 允许旧变量不报错,@lru_cache 实现全局单例:from pydantic_settings import BaseSettingsfrom functools import lru_cacheclass AppSettings(BaseSettings):    DATABASE_URL: str = "sqlite+aiosqlite:///./ai_meeting.db"    REDIS_URL: str = "redis://localhost:6379/0"    MAAS_API_KEY: str = ""    MAAS_API_URL: str = "https://api.modelarts-maas.com/v2/chat/completions"    MAAS_MODEL: str = "deepseek-v4-flash"    APP_NAME: str = "会议智脑"    DEBUG: bool = False    JWT_SECRET: str = "change-me-in-production"    JWT_ALGORITHM: str = "HS256"    JWT_EXPIRE_MINUTES: int = 1440    model_config = {        "env_file": ".env",        "env_file_encoding": "utf-8",        "extra": "ignore",    }@lru_cache()def get_settings() -> AppSettings:    return AppSettings()         在项目根目录创建 .env 文件,将 MaaS 的 API Key、API 地址和模型名称填入:DATABASE_URL=sqlite+aiosqlite:///./ai_meeting.dbREDIS_URL=redis://localhost:6379/0MAAS_API_KEY=<你的华为云MaaS API Key>MAAS_API_URL=https://api.modelarts-maas.com/v2/chat/completionsMAAS_MODEL=deepseek-v4-flashAPP_NAME=会议智脑DEBUG=trueJWT_SECRET=meeting-brain-jwt-secret-2026   3.2 核心逻辑——调用华为云 MaaS 大模型生成摘要      这是本案例的核心代码。process_meeting 函数实现多级降级策略:优先调用 MaaS API,失败时降级到本地规则引擎。同时支持 Redis 缓存(可选,连接失败自动跳过)。         System Prompt 设计——明确指定英文字段名和 JSON 输出格式,避免模型返回中文键名:SYSTEM_PROMPT = """你是一个严谨的会议纪要专家。请处理输入的会议记录并输出JSON。规则:- summary:不超过150字,仅包含最终结论,不重复会议过程- key_decisions:只提取有明确结论或投票通过的事项,最多5条- tasks:仅当原文明确提及"某人负责某事"或"需要在某时间前完成"时才提取,严禁臆造- 日期格式统一为 YYYY-MM-DD,如果原文没有年份则默认为当前年份- 如果原文信息不足,对应字段返回空列表或空字符串,不要编造输出必须是合法JSON,严格使用以下英文字段名(禁止使用中文字段名):{  "summary": "一句话摘要",  "key_decisions": ["决策1", "决策2"],  "tasks": [    {"description": "任务描述", "assignee": "责任人", "deadline": "YYYY-MM-DD", "priority": "high/mid/low"}  ]}"""         MaaS API 调用——使用 httpx.AsyncClient 异步调用,指数退避重试(最多3次,仅对超时/连接错误重试),超时时间90秒:async def _call_maas_api(record_text: str) -> dict:    settings = get_settings()    headers = {        "Authorization": f"Bearer {settings.MAAS_API_KEY}",        "Content-Type": "application/json",    }    body = _build_request_body(record_text)    for attempt in range(1, MAX_RETRIES + 1):        try:            async with httpx.AsyncClient(timeout=90) as client:                resp = await client.post(settings.MAAS_API_URL, headers=headers, json=body)            if resp.status_code != 200:                raise AppException(502, f"MaaS 接口返回错误码: {resp.status_code}")            content = resp.json()["choices"][0]["message"]["content"]            start, end = content.find("{"), content.rfind("}") + 1            parsed = json.loads(content[start:end])            # 兼容中文键名            if "摘要" in parsed and "summary" not in parsed:                parsed["summary"] = parsed.pop("摘要")            if "关键决策" in parsed and "key_decisions" not in parsed:                parsed["key_decisions"] = parsed.pop("关键决策")            if "任务清单" in parsed and "tasks" not in parsed:                parsed["tasks"] = parsed.pop("任务清单")            raw_tasks = parsed.get("tasks") or parsed.get("task_list") or parsed.get("action_items") or []            normalized_tasks = []            for t in raw_tasks:                if isinstance(t, dict):                    nt = {                        "description": t.get("description") or t.get("task") or t.get("任务") or "",                        "assignee": t.get("assignee") or t.get("person") or t.get("负责人") or "",                        "deadline": t.get("deadline") or t.get("due_date") or t.get("截止日期") or "",                        "priority": t.get("priority") or "mid",                    }                    normalized_tasks.append(nt)                elif isinstance(t, str):                    m = re.match(r"^([\u4e00-\u9fa5]{2,4})[::]\s*(.+)$", t)                    if m:                        assignee, desc = m.group(1), m.group(2)                        normalized_tasks.append({"description": desc, "assignee": assignee, "deadline": "", "priority": "mid"})            parsed["tasks"] = normalized_tasks            return parsed        except (httpx.TimeoutException, httpx.ConnectError):            wait = 2 ** attempt            await asyncio.sleep(wait)    raise AppException(502, "MaaS 调用失败")         多级降级与缓存——完整的 process_meeting 流程:async def process_meeting(record_text: str) -> dict:    if not record_text or not record_text.strip():        return {"summary": "", "key_decisions": [], "tasks": []}    settings = get_settings()    if not settings.MAAS_API_KEY:        return _local_summarize(record_text)    redis_client = await _get_redis()    try:        if redis_client:            cached = await redis_client.get(_cache_key(record_text))            if cached:                return json.loads(cached)        try:            result = await _call_maas_api(record_text)        except Exception:            result = _fallback_result(record_text)        if redis_client:            await redis_client.set(_cache_key(record_text), json.dumps(result, ensure_ascii=False), ex=7*24*3600)        return result    finally:        if redis_client:            await redis_client.close()3.3 会议上传与 AI 处理联动   会议上传 API 在创建记录后,自动调用 process_meeting 进行 AI 处理,将生成的摘要、决策写入 Meeting 记录,并将提取的任务批量创建为 Task 记录:class MeetingService:    def __init__(self, db: AsyncSession):        self.db = db    async def upload_meeting(self, title, record_text, tags=None):        meeting = Meeting(title=title, record_text=record_text, status=MeetingStatus.pending, tags=tags)        self.db.add(meeting)        await self.db.flush()        if record_text and record_text.strip():            await self._process_meeting_content(meeting)        return meeting.id    async def _process_meeting_content(self, meeting):        try:            result = await process_meeting(meeting.record_text)            meeting.summary = result.get("summary", "")            meeting.key_decisions = result.get("key_decisions", [])            meeting.status = MeetingStatus.processed            for task_data in result.get("tasks", []):                task = Task(                    meeting_id=meeting.id,                    description=task_data.get("description", ""),                    assignee=task_data.get("assignee", ""),                    deadline=self._parse_date(task_data.get("deadline")),                    priority=self._parse_priority(task_data.get("priority")),                    status=TaskStatus.todo,                )                self.db.add(task)            await self.db.flush()        except Exception as exc:            logger.error(f"会议处理失败,保持pending状态: {exc}")3.4 数据库会话管理——SQLite 异步适配   SQLite 适配关键点:WAL 模式支持并发读、外键约束、不使用连接池。get_db() 通过 yield 实现请求级会话,自动 commit/rollback:from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSessionfrom sqlalchemy import eventsettings = get_settings()_is_sqlite = settings.DATABASE_URL.startswith("sqlite")engine = create_async_engine(settings.DATABASE_URL, echo=settings.DEBUG)if _is_sqlite:    @event.listens_for(engine.sync_engine, "connect")    def _set_sqlite_pragma(dbapi_conn, connection_record):        cursor = dbapi_conn.cursor()        cursor.execute("PRAGMA journal_mode=WAL")        cursor.execute("PRAGMA foreign_keys=ON")        cursor.close()AsyncSessionLocal = async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)async def get_db() -> AsyncSession:    async with AsyncSessionLocal() as session:        try:            yield session            await session.commit()        except Exception:            await session.rollback()            raise3.5 前端 Vite 代理配置   开发环境将 /api 请求代理到后端 8000 端口:import { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'export default defineConfig({  plugins: [vue()],  server: {    proxy: {      '/api': { target: 'http://localhost:8000', changeOrigin: true },      '/health': { target: 'http://localhost:8000', changeOrigin: true },    },  },})4)运行调试   步骤1:安装后端依赖pip install -r requirements.txt   步骤2:安装前端依赖cd frontendnpm installcd ..   步骤3:初始化数据库python init_db.py   执行成功后输出:数据库表创建完成示例数据初始化完成!2个用户 + 3个会议 + 10项任务  管理员: admin / admin123  普通用户: demo / demo123     步骤4:启动后端服务uvicorn app.main:app --reload --port 8000   启动后访问 http://localhost:8000/docs 可查看自动生成的 OpenAPI 交互式文档:     步骤5:启动前端开发服务器cd frontendnpm run dev   启动后访问 http://localhost:5173,进入登录页面:     步骤6:登录并体验完整功能   使用 admin / admin123 登录,进入数据总览页面:     点击左侧菜单"上传会议",粘贴会议记录文本,点击"提交会议记录":     上传成功后点击"查看详情",查看 AI 自动生成的摘要、关键决策和任务清单:     切换到"任务看板"页面,拖拽卡片切换任务状态:     切换到"任务甘特图"页面,查看任务时间线:     切换到"风险检测"页面,点击"立即检测":  4 、释放资源本案例使用本地 SQLite 数据库和可选 Redis,不涉及华为云付费资源的持续占用。如需释放:• 删除本地 ai_meeting.db 文件即可清除所有数据• 如使用了华为云 MaaS Tokens 代金券,代金券到期后自动失效,无需手动释放5 、扩展资料说明• 想了解更多关于华为云 MaaS 大模型服务的可以访问:https://support.huaweicloud.com/productdesc-maas/maas_01_0001.html• 想了解更多关于 FastAPI 框架的可以访问:https://fastapi.tiangolo.com/• 想了解更多关于 Vue 3 组合式 API 的可以访问:https://cn.vuejs.org/guide/introduction.html• 想了解更多关于 Element Plus 组件库的可以访问:https://element-plus.org/zh-CN/ 
  • [高校训练营] 基于CodeArts开发游戏周边商品管理系统
    1 、概述1.1 案例介绍商品管理后台Web应用——GoodsManager。该系统采用 Python Flask + SQLite + Bootstrap 5 技术栈,实现了商品全生命周期管理、库存预警、分类管理、图片本地托管、操作记录追踪与防篡改、数据加密导入导出、撤回重做、首页仪表盘等核心功能,并采用毛玻璃主题设计,背景图动态提取主色调实现浅色/深色自适应。通过本案例,开发者将体验如何利用CodeArts代码智能体的Spec-Driven Development(规格驱动开发)工作流,从需求规格定义、实现方案设计、编码任务规划到代码实现的完整过程,高效交付一个功能完备、代码质量高、架构模块化的Web应用。1.2 适用对象个人开发者高校学生1.3 案例时间本案例总时长预计180分钟。1.4 案例流程 说明:1. AI IDE华为云码道(CodeArts)代码智能体安装部署;2.使用CodeArts代码智能体,通过自然语言描述需求,自动生成需求规格文档(spec.md);3.基于需求规格文档,自动生成实现方案文档(design.md),包含架构设计、接口设计、数据模型等;4.基于实现方案文档,自动生成编码任务规划(tasks.md),将需求拆解为可执行的编码任务;5.根据编码任务规划,逐步实现各功能模块代码,包括用户认证、商品管理、分类管理、图片托管、操作记录、数据导入导出、撤回重做、仪表盘等;6.运行start.bat一键启动应用,在浏览器中验证所有功能。1.5 资源总览本案例预计花费240元。体验完成后请及时释放资源,避免产生多余的费用。实际实践过程中的费用可能较下表更少,示例费用是分多次对话、添加新功能以及多次修复bug所用的。资源名称描述/规格价格Python 3.11运行环境免费Flask / Flask-SQLAlchemy / WerkzeugPython Web框架及ORM免费华为云码道(CodeArts)代码智能体专业版139元/月华为云码道(CodeArts)代码智能体按需计费约100元 2 、环境和资源准备2.1 AI IDE华为云码道安装部署参考案例《AI IDE华为云码道(CodeArts)代码智能体安装部署》完成Windows版AI IDE华为云码道(CodeArts)代码智能体安装部署。打开CodeArts代码智能体(IDE内置的AI助手),准备开始通过自然语言描述需求来生成项目代码。 注:本案例中项目所创建的本地目录为E:/CodeArts/PGAMEGoodsManager;本案例使用码道智能体模式,模型选择GLM-5.1,开发模式为Spec-Driven Development(规范开发)。3 、构建商品管理应用3.1 描述项目需求在CodeArts代码智能体的对话框中,输入项目需求描述。本案例的需求描述如下:开发一个名为"PGAMEGoodsManager"(PGAME可以更换为其他名称)的游戏周边商品管理后台Web应用,使用Python Flask + SQLite + Bootstrap技术栈。要求:后端Python Flask + SQLite,前端HTML + CSS (Bootstrap 5) + JavaScript;代码必须模块化(Flask Blueprint),以便后续增加新功能;库存预警≤3红色,4-5黄色,≥6默认颜色;管理员账号只能由现有admin派发,禁止直接注册成管理员;折扣价与售价不同用灰色背景标出,相同用"-"占位;数据导入导出使用简单加密过的JSON,密钥写在文件开头;品牌名显示为"P-GAME"(同样可以更改),点击以跳转仪表盘;操作类型备注应自动检测(对比新旧数据),不要求手动选择;全站采用毛玻璃(glass)主题,背景图从backgrounds/目录读取,动态提取主色调。有部分具体要求在后续3.2-3.4有详细罗列。 CodeArts代码智能体将根据需求描述,自动创建项目目录结构并生成规格驱动开发(SDD)文档目录:.codeartsdoer/specs/pgame_goods_mgr/├── spec.md         # 需求规格文档├── design.md       # 实现方案文档└── tasks.md        # 编码任务规划3.2 审阅需求规格文档(spec.md)CodeArts代码智能体自动生成了需求规格文档spec.md,包含以下核心内容:•      组件定位:核心职责、核心输入/输出、职责边界•      领域术语:游戏周边商品、分类、成本、折扣价、库存预警、数据快照、加密JSON等•      角色与边界:admin管理员、普通用户、审计员•      核心能力(5.1-5.18):用户认证、商品管理、分类管理、库存预警、图片托管、示例数据初始化、商品搜索、一键启动、操作记录、导航交互、数据导出与导入、首页仪表盘、商品详情页、撤回与重做、操作审计与防篡改、数据合并导入、主题色彩系统重构、其他Bug修复•      数据约束(6.1-6.15):商品/分类/用户账号/商品图片/搜索条件/操作记录/排序/快照/导出文件/仪表盘统计/签名/审计日志/审计员账号/主题色彩字典 如图为spec.md部分内容,开发者可审阅spec.md内容,如有修改意见可告知CodeArts代码智能体进行修改。确认无误后,进入下一阶段。3.3 审阅实现方案文档(design.md)CodeArts代码智能体基于spec.md自动生成了实现方案文档design.md,包含以下核心内容:3.3.1 需求与存量功能关系分析design.md首先分析了需求功能与存量功能的关系,将功能分为三类:已实现功能、需要扩展的功能(需在现有代码上扩展)、需要新增的功能或接口(全新设计)。其中首次构建项目时后面的两点应该没有内容。3.3.2 实现模型包含上下文视图(单体Flask Web应用架构)、服务/组件总体架构(9个Blueprint模块)、实现设计文档(15个流程图,覆盖用户认证、商品操作、排序、详情页、分类删除、数据导出/导入/撤回、删除撤回/重做、管理员派发、仪表盘、操作记录、图片资源池管理、系统初始化、HMAC签名链、签名校验、审计日志、审计员权限控制、管理员权限撤回、数据合并导入、主题色彩系统重构、折扣价联动修复等)。3.3.3 接口设计 共设计了42个接口,分为认证接口组(5个)账号管理接口组(5个)商品管理接口组(8个)分类管理接口组(3个)操作记录接口组(3个)图片托管接口组(1个)数据管理接口组(3个)撤回重做接口组(2个)仪表盘接口组(1个)增量接口组(7个)合并导入接口组(1个)审计日志接口组(1个)其他Bug修复接口组(2个)3.3.4 数据模型定义了7个数据模型:User(含is_admin、is_auditor)、Category、Goods(含discount_price、updated_at)、OperationLog(含change_type、signature)、DataSnapshot、DeletedItem(含status、get_summary())、AuditLog。开发者可审阅design.md内容,如有修改意见可告知CodeArts代码智能体进行修改。确认无误后,进入下一阶段。3.4 审阅编码任务规划(tasks.md)CodeArts代码智能体基于design.md自动生成了编码任务规划tasks.md,将整个项目拆解为22个编码/验证节:第1节:项目基础设施搭建(目录结构、依赖、数据库模型、Blueprint注册)第2节:用户认证功能实现(注册/登录/退出/装饰器/页面模板)第3节:图片托管功能实现(上传/展示/清理/替换/批量读写) 第4节:分类管理功能实现(列表/搜索/新增/删除/页面模板)第5节:商品管理功能实现(列表/搜索/排序/新增/编辑/删除/详情/页面模板第6节:账号管理功能实现(设置页/修改密码/删除账号/创建管理员)第7节:操作记录功能实现(自动记录/查看/删除/页面模板)第8节:数据导入导出功能实现(导出/导入/页面模板)第9节:撤回与重做功能实现(导入撤回/删除撤回/重做)第10节:首页仪表盘功能实现(统计/记录/快捷按钮)第11-13节:导航更新/系统初始化/一键启动第14节:集成测试与验证(20个子节覆盖所有功能验证)接下来的内容应当也是第一轮对话就写入的第15-16节:操作审计与防篡改功能实现与验证(增量)第17-18节:数据合并导入功能实现与验证(增量)第19-20节:主题色彩系统重构功能实现与验证(增量)第21-22节:其他Bug修复与优化功能实现与验证(增量)开发者可审阅tasks.md内容,如有修改意见可告知CodeArts代码智能体进行修改。确认无误后,进入下一阶段。3.5 代码实现确认tasks.md后,CodeArts代码智能体将根据编码任务规划逐步实现各功能模块代码。以下是各模块的实现要点:3.5.1 应用入口与数据库初始化(app.py)app.py是应用的核心入口,负责:•      创建Flask应用工厂函数create_app(),配置SECRET_KEY、SQLALCHEMY_DATABASE_URI等•      注册9个Blueprint(auth/account/goods/category/image/oplog/data/dashboard/undo/audit)•      数据库自动初始化:首次启动时创建表结构和示例数据(1个admin + 1个分类 + 1个商品)•      Schema迁移检测:通过inspect检测新增字段(signature/is_auditor等),缺失时触发数据库重建•      背景图主色调提取:extract_bg_colors()扫描backgrounds/目录,提取RGB平均值,计算亮度luma,预计算40个CSS颜色字符串•      全局context_processor:注入theme字典(40键)至所有模板•      背景图服务路由:/bg/<filename>提供图片服务,含Cache-Control: max-age=86400缓存头 3.5.2 数据模型层(models.py)定义了7个数据模型:模型关键字段说明Userid, username, password_hash, is_admin, is_auditor, created_at用户账号,支持三种角色Categoryid, name商品分类,名称唯一Goodsid, name, game, category_id, cost, price, discount_price, stock, image_path, created_at, updated_at商品,含折扣价和修改时间OperationLogid, operator, action, target_type, target_name, change_type, signature, detail, created_at操作记录,含HMAC签名DataSnapshotid, snapshot_type, snapshot_data, related_operation, created_at数据快照,支持撤回DeletedItemid, item_type, item_data, status(deleted/undone), created_at删除暂存,支持撤回/重做AuditLogid, audit_time, operator, operation_type, deleted_summary, deleted_count审计日志,不可修改/删除 3.5.3 用户认证模块(auth.py)实现了三个权限装饰器:•      login_required:检查Session中user_id,未登录重定向至登录页;•      admin_or_auditor_required:仅admin和审计员可访问(用于审计日志页面);•      not_auditor_required:审计员禁止访问(用于商品/分类/数据管理路由)。注册路由禁止创建管理员(User默认is_admin=False)。登录成功后Session写入user_id、username、is_admin、is_auditor。 3.5.4 商品管理模块(goods.py)商品管理是系统的核心模块,实现了以下功能:•      商品列表:支持5种搜索条件(keyword/game/category_id/stock_status/sort)组合筛选,6种排序方式;  图:六种排序方式•      库存预警:≤3红色(danger)、4-5黄色(warning)、≥6默认颜色•      折扣价显示:≠售价时灰色背景+显示原售价,=售价时显示"-"占位符•      商品新增:折扣价默认等于售价,新增时修改售价折扣价自动联动(使用prevPrice变量追踪)•      商品编辑:折扣价独立不联动,系统自动检测change_type(对比新旧数据差异)•      商品删除:删除前保存数据到DeletedItem(status=deleted),不立即删除图片文件•      商品详情页:上方展示信息+最近5条修改记录+库存变动记录,下方编辑区 3.5.5 操作记录与防篡改模块(oplog.py)这是本案例最具特色的功能模块,实现了操作记录的HMAC-SHA256签名链防篡改机制:签名链生成:每条操作记录创建时,基于operator|action|target_type|target_name|change_type|created_at_str|prev_signature七个字段生成HMAC-SHA256签名。首条记录的prev_signature为预设常量"GENESIS",后续记录依赖前一条记录的签名值,形成哈希链。  签名校验:每次访问操作记录页面时,按id升序遍历所有记录,使用hmac.compare_digest常量时间比较验证签名链完整性。校验通过显示绿色✓标记,校验失败显示红色✗"校验失败"警告。签名链重建:admin删除/清空操作记录后,自动调用_rebuild_signature_chain()按id升序重新生成所有记录的签名,保持签名链完整性。审计日志:admin删除操作记录时,自动将删除行为记录到AuditLog表(不可修改/删除),包含审计时间、操作账号、操作类型、被删除记录摘要、删除数量。 3.5.6 数据导入导出模块(data.py)数据导出:收集商品/分类/操作记录/用户账号/image_pool/图片文件 → 序列化为JSON → base64编码+密钥加密 → 生成下载文件(第1行密钥,第2行起加密数据)。数据导入(合并模式):保留当前用户账号,分类按名称去重(建立旧ID→新ID映射),商品按name+game+category_id三元组去重更新,操作日志追加,图片增量导入。合并导入后自动重建签名链。 3.5.7 撤回重做模块(undo.py)删除撤回:从DeletedItem恢复被删除的数据,将status从deleted改为undone(而非删除记录),恢复时不强制指定原始id(让数据库自动分配)。删除重做:查找status=undone的最近记录,重新删除对应数据并删除图片文件,记录"重做删除"操作日志。重做按钮独立显示,显示将被重做的商品名称和成本。保留最近10次删除记录。 3.5.8 账号管理模块(account.py)修改密码:验证旧密码+确认新密码(两次输入一致),成功后清除Session要求重新登录。创建管理员/审计员:仅admin可创建,校验用户名唯一性和密码强度(≥6位)。撤回管理员权限:admin可将其他管理员降级为普通用户(不可撤回自身),撤回操作自动记录到操作日志。 3.5.9 毛玻璃主题与动态色调(base.html)系统启动时通过extract_bg_colors()提取背景图RGB主色调,计算亮度luma = 0.299*R + 0.587*G + 0.114*B。luma>140为浅色系(降低毛玻璃明度+黑色字体),≤140为深色系(提高毛玻璃明度+白色字体)。所有CSS颜色值在Python端预计算为完整rgba()字符串,通过CSS自定义属性传递(base.html中style#theme-vars块的:root仅此处使用Jinja2赋值CSS变量),主CSS只引用var(--xxx),主CSS和JS中零Jinja2引用。提示框(.alert)字体始终黑色。粒子效果:35个粒子+连线动画,颜色通过HTML属性data-pr/data-pg/data-pb传递,降帧至30fps。导航滑块指示器与页面切换动画:sessionStorage存储上一页面位置,cubic-bezier缓动曲线0.3s平滑滑动。opacity+transform淡入淡出0.35s。 3.5.10 一键启动(start.bat)start.bat脚本自动完成以下操作:•      使用@echo off关闭命令回显;•      输出启动提示信息"正在启动 P-GAME GoodsManager...";•      检查并创建Python虚拟环境(venv);•      安装项目依赖(pip install -r requirements.txt);•      启动Flask应用并等待3秒;•      自动打开默认浏览器访问。•      注意:不要显示默认管理员账号信息,防止敏感信息泄露3.6 运行调试3.6.1 使用一键启动脚本双击项目根目录下的start.bat文件,系统将自动创建Python虚拟环境、安装依赖、启动Flask应用并打开浏览器访问图中地址(localhost)   3.6.2 手动启动在项目根目录下打开终端,依次执行以下命令:python -m venv venvvenv\Scripts\activatepip install -r requirements.txtpython app.py 3.6.3 登录系统启动后在浏览器中访问 http://localhost:5000,使用默认管理员账号登录:•      用户名:admin•      密码:admin123  3.7 功能验证3.7.1 仪表盘验证登录成功后自动跳转至仪表盘页面,验证以下内容:•      4个统计卡片:总商品数、总分类数、总用户数、库存预警商品数•      最近5条操作记录(含操作时间、操作账号、操作内容、操作类型备注)•      4个快捷按钮:新增商品、新增分类、商品列表、数据管理3.7.2 商品管理验证点击"商品管理"页签,验证以下功能:•      搜索功能:输入商品名称关键词、所属游戏关键词、选择分类和库存状态•      排序功能:选择6种排序方式(最新创建升降序、最新修改升降序、售价升降序)•      库存预警:库存≤3显示红色,4-5显示黄色,≥6默认颜色•      折扣价显示:折扣价≠售价时灰色背景+显示原售价,=售价时显示"-"•      新增商品:填写完整信息,折扣价默认等于售价并联动•      编辑商品:修改信息后系统自动检测操作类型•      删除商品:确认后删除,显示撤回按钮•      商品详情:点击商品名称进入详情页,查看修改记录和库存变动  图:商品列表页面——搜索+排序+库存预警颜色+折扣价灰色背景  图:新增商品页面——竖向布局,折扣价联动  图:商品详情页面——上方信息+修改记录,下方编辑区 3.7.3 分类管理验证点击"分类管理"页签,验证以下功能:•      新增分类:填写分类名称创建•      搜索功能:输入分类名称关键词模糊搜索•      删除分类:无关联商品时可删除,有关联商品时拒绝•      删除撤回:删除后点击撤回恢复  图:分类管理页面——搜索+新增+删除+撤回按钮 3.7.4 数据导入导出验证点击"数据管理"页签,验证以下功能:•      数据导出:点击导出按钮,下载加密JSON文件(第1行密钥,第2行起加密数据)•      数据导入(合并模式):上传加密JSON文件并输入密钥,合并导入数据(保留当前用户、合并分类/商品/日志/图片)•      导入撤回:点击撤回按钮恢复到导入前状态•      密钥错误:输入错误密钥,提示"密钥错误,无法解密数据"  图:数据管理页面——导出按钮+导入表单+撤回按钮 3.7.5 操作记录与签名校验验证点击"操作记录"页签,验证以下功能:•      签名校验列:所有记录显示✓绿色标记(校验通过)•      admin删除记录:删除后签名链自动重建,剩余记录校验仍全部通过•      审计日志:删除操作记录后,审计日志页面自动新增一条记录  图:操作记录页面——签名校验✓/✗标记  图:审计日志页面——删除操作记录的审计追踪 3.7.6 账号管理验证点击"账号设置"页签,验证以下功能:•      修改密码:输入旧密码和新密码(两次输入一致),成功后需重新登录•      创建管理员:admin可创建新管理员账号•      创建审计员:admin可创建审计员账号•      撤回管理员权限:admin可将其他管理员降级为普通用户    •      审计员登录:审计员导航仅显示仪表盘/操作记录/审计日志/账号设置   3.7.7 毛玻璃主题验证验证以下主题效果:•      背景图动态色调:系统根据背景图亮度自动切换浅色/深色主题•      浅色背景:毛玻璃明度降低,字体黑色,页面文字清晰可读•      深色背景:毛玻璃明度提高,字体白色,页面文字清晰可读•      提示框字体始终黑色•      粒子效果:35个粒子•      导航滑块:页签切换时滑块平滑滑动•      页面切换动画:内容区淡入淡出过渡 3.7.8 演示视频https://atomgit.com/MingMond/GoodsManagerDemo4 、释放资源本案例使用开发者空间资源,可以选择释放。如需清理项目文件,删除PGAMEGoodsManager目录即可。如需删除数据库文件,删除instance/目录下的goods.db文件即可。5 、扩展资料说明想了解更多关于华为云码道(CodeArts)代码智能体的可以访问:https://developer.huaweicloud.com/space/home
  • [问题求助] 无法使用codearts智能体
    插件初始化遇到问题,部分功能可能受限。如插件不可用请联系技术支撑。错误细节:Error: Server process start failed with exit code 1, signal null, error output: Error: Unexpected error, check log file at c:\Users\Jason\.codeartsdoer\codearts-data\log\kernel-codeartsdoer-incognito-2026-07-23T102558-39932-0.log for more details Failed to start server on port 50870at L (c:\Program Files\CodeArts Agent\resources\app\extensions\vscode-codebot\out\extension.js:192:77)at async dQ0 (c:\Program Files\CodeArts Agent\resources\app\extensions\vscode-codebot\out\extension.js:192:5103)at async u.startServer (c:\Program Files\CodeArts Agent\resources\app\extensions\vscode-codebot\out\extension.js:9851:16628)at async c:\Program Files\CodeArts Agent\resources\app\extensions\vscode-codebot\out\extension.js:9851:8945at async u.retryAsync (c:\Program Files\CodeArts Agent\resources\app\extensions\vscode-codebot\out\extension.js:9851:6735)at async c:\Program Files\CodeArts Agent\resources\app\extensions\vscode-codebot\out\extension.js:9851:8874
  • [问题求助] 在 idea 2026.2 中无法使用
    在 idea 2026.2 中无法使用,啥时候能支持
  • [高校训练营] 基于华为云码道(CodeArts)代码智能体的食堂菜品评价管理系统开发
    一、概述1.1 案例介绍本案例展示了如何使用华为云码道(CodeArts)代码智能体快速开发一个完整的食堂菜品评价管理系统(FCEMS - Food Court Evaluation Management System)。通过自然语言对话方式,从需求分析、系统设计、代码生成到功能迭代,完整体验AI辅助开发的强大能力。系统包含:顾客端:浏览菜品、提交评价、修改/删除评价(3天内)管理端:菜品管理、评价管理、分类管理、用户管理后端API:完整的RESTful API数据库:MySQL数据库设计技术栈:后端:Node.js + Express + MySQL前端:原生HTML + CSS + JavaScript认证:JWT TokenAI辅助:华为云码道代码智能体代码仓库及demo演示视频:cid:link_31.2 适用对象个人开发者高校学生1.3 案例时间如:本案例总时长预计60分钟(包含环境准备、开发调试、功能测试)。1.4 案例流程说明:华为云码道(CodeArts)代码智能体安装部署生成项目 PRD 文档,明确系统需求和功能设计基于 PRD 文档,智能体生成完整的后端 API 和数据库设计智能体生成前端顾客端和管理端界面启动服务,测试系统功能,发现问题通过对话方式描述问题,智能体自动修复代码根据用户反馈,迭代优化系统功能1.5 资源总览本案例预计花费26.47元。体验完成后请及时释放资源,避免产生多余的费用。资源名称规格单价(元)华为云码道(CodeArts)代码智能体基础版26.47二、环境和资源准备2.1 开通华为云码道CodeArts登录华为云码道,开通CodeArts服务。在CodeArts中创建项目,进入代码智能体(CodeArts IDE)开发环境。2.2 本地开发环境要求在CodeArts IDE中开发时,需确保本地已安装以下工具:Node.js 18+MySQL 8.0+三、构建食堂菜品评价管理系统3.1 需求分析与系统设计在码道对话界面选择 氛围编程模式(Vibe-Coding),发送下述请求。实现某食堂的菜品评价管理系统。用于顾客评价反馈食堂菜品,为食堂管理人员提供改进依据。实现菜品相关信息(品名、原材料、照片、价格等)管理。实现菜品的评价、打分、查询等功能。这是我的基础要求,请基于这个要求的基础上给出更加详细具体的项目设计报告。3.2 后端开发1)项目结构说明:使用CodeArts智能体生成的后端项目结构:fcems/backend/ ├── src/ │ ├── app.js # 主应用入口 │ ├── config/ │ │ └── database.js # 数据库配置 │ ├── middleware/ │ │ └── auth.js # 认证中间件 │ └── routes/ │ ├── auth.js # 认证路由 │ ├── dish.js # 菜品路由 │ ├── review.js # 评价路由 │ ├── category.js # 分类路由 │ ├── analytics.js # 数据分析路由 │ └── user.js # 用户路由 ├── database/ │ ├── init.js # 数据库初始化脚本 │ ├── add-test-data.js # 添加测试数据 │ └── add-reviews.js # 添加评价数据 ├── .env # 环境变量配置 ├── package.json # 依赖配置 └── package-lock.json根据项目设计报告开发完整的食堂菜品评价管理系统。2)数据库配置 (src/config/database.js)const mysql = require('mysql2/promise'); require('dotenv').config(); const dbConfig = { host: process.env.DB_HOST || 'localhost', user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'fcems', waitForConnections: true, connectionLimit: 10, queueLimit: 0 }; const pool = mysql.createPool(dbConfig); async function query(sql, params) { const [rows] = await pool.execute(sql, params); return rows; } module.exports = { query, transaction, pool }; 3)认证中间件 (src/middleware/auth.js)const jwt = require('jsonwebtoken'); const { query } = require('../config/database'); const auth = async (req, res, next) => { try { const token = req.header('Authorization')?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ success: false, message: '请先登录' }); } const decoded = jwt.verify(token, process.env.JWT_SECRET); const users = await query('SELECT * FROM user WHERE id = ? AND status = 1', [decoded.userId]); if (users.length === 0) { return res.status(401).json({ success: false, message: '用户不存在或已被禁用' }); } req.user = users[0]; req.token = token; next(); } catch (error) { res.status(401).json({ success: false, message: '认证失败,请重新登录' }); } }; const requireRole = (...roles) => { return (req, res, next) => { if (!roles.includes(req.user.role)) { return res.status(403).json({ success: false, message: '权限不足' }); } next(); }; }; module.exports = { auth, requireRole }; 4)菜品路由 (src/routes/dish.js)关键功能:获取菜品列表、添加菜品、编辑菜品、上下架、删除// 获取菜品列表(支持状态筛选) router.get('/', async (req, res) => { try { const { page = 1, limit = 10, status, search } = req.query; let sql = ` SELECT d.*, c.name as category_name, (SELECT AVG(rating) FROM review WHERE dish_id = d.id AND status = 1) as avg_rating FROM dish d LEFT JOIN category c ON d.category_id = c.id WHERE d.deleted_at IS NULL `; if (status !== undefined) { sql += ' AND d.status = ?'; params.push(parseInt(status)); } // ... 其他筛选条件 const dishes = await query(sql, params); res.json({ success: true, data: { dishes, total, page, limit } }); } catch (error) { res.status(500).json({ success: false, message: '获取菜品列表失败' }); } }); // 添加菜品(检查名称重复) router.post('/', auth, requireRole(2, 3), async (req, res) => { const { name, category_id, price } = req.body; // 检查名称是否重复 const [existingDish] = await query( 'SELECT id FROM dish WHERE name = ? AND deleted_at IS NULL', [name] ); if (existingDish) { return res.status(400).json({ success: false, message: '菜品名称已存在,请使用其他名称' }); } // 插入新菜品 const result = await query( 'INSERT INTO dish (name, category_id, price, ...) VALUES (?, ?, ?, ...)', [name, category_id, price, ...] ); res.json({ success: true, message: '菜品添加成功', data: { id: result.insertId } }); }); 5)评价路由 (src/routes/review.js)关键功能:提交评价、修改评价(3天内)、删除评价(3天内)、管理员回复// 修改评价(3天内) router.put('/:id', auth, async (req, res) => { const { id } = req.params; const { rating, content } = req.body; const [review] = await query('SELECT * FROM review WHERE id = ?', [id]); if (review.user_id !== req.user.id) { return res.status(403).json({ success: false, message: '只能修改自己的评价' }); } // 检查是否在3天内 const reviewDate = new Date(review.created_at); const now = new Date(); const daysDiff = (now - reviewDate) / (1000 * 60 * 60 * 24); if (daysDiff > 3) { return res.status(403).json({ success: false, message: '评价提交超过3天,不允许修改' }); } // 更新评价 await query('UPDATE review SET rating = ?, content = ? WHERE id = ?', [rating, content, id]); res.json({ success: true, message: '评价修改成功' }); }); // 管理员回复评价 router.post('/:id/reply', auth, requireRole(2, 3), async (req, res) => { const { id } = req.params; const { reply } = req.body; await query('UPDATE review SET admin_reply = ? WHERE id = ?', [reply, id]); res.json({ success: true, message: '回复成功' }); }); 至此后端API路由创建完成后端代码文件列表3.3 前端代码生成生成的前端界面包括:顾客端:菜品浏览、评价提交、我的评价管理管理端:登录页、仪表盘、菜品管理、评价管理、分类管理、用户管理美观的UI设计,使用渐变色和现代风格1)顾客端菜品展示async function loadDishes(page = 1) { let url = `${API_BASE}/dishes?page=${page}&limit=12&status=1`; const res = await fetch(url); const data = await res.json(); if (data.success) { renderDishes(data.data.dishes); } } function renderDishes(dishes) { const container = document.getElementById('dishesGrid'); container.innerHTML = dishes.map(dish => ` <div class="dish-card" onclick="showDishDetail(${dish.id})"> <div class="dish-image-container"> <img src="${dish.images ? JSON.parse(dish.images)[0] : ''}" onerror="this.parentElement.style.background='linear-gradient(135deg, #667eea 0%, #764ba2 100%)'"> </div> <div class="dish-info"> <h3>${dish.name}</h3> <div class="dish-rating">★ ${parseFloat(dish.avg_rating || 0).toFixed(1)}</div> <div class="dish-price">¥${dish.price}</div> </div> </div> `).join(''); } 2)评价提交(含时间限制检查)async function submitReview() { const review = { dish_id: currentDish.id, rating: selectedRating, tags: selectedTags, content: document.getElementById('reviewContent').value, is_anonymous: document.getElementById('isAnonymous').checked }; const res = await fetch(`${API_BASE}/reviews`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: JSON.stringify(review) }); const data = await res.json(); if (data.success) { alert(`评价提交成功!获得${data.data.points_earned}积分`); loadReviews(currentDish.id); } else { alert(data.message); } } 3)管理端菜品管理async function loadDishesPage() { const res = await fetch(`${API_BASE}/dishes?limit=100`, { headers: getAuthHeaders() }); const data = await res.json(); // 显示所有未删除的菜品(包括上架和下架) document.getElementById('pageContent').innerHTML = ` <table class="table"> <thead> <tr> <th>菜品名称</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> ${data.data.dishes.map(dish => ` <tr> <td>${dish.name}</td> <td> <span class="badge ${dish.status === 1 ? 'badge-success' : 'badge-danger'}"> ${dish.status === 1 ? '上架' : '下架'} </span> </td> <td> <button onclick="editDish(${dish.id})">编辑</button> <button onclick="toggleDishStatus(${dish.id}, ${dish.status})"> ${dish.status === 1 ? '下架' : '上架'} </button> <button onclick="deleteDish(${dish.id})">删除</button> </td> </tr> `).join('')} </tbody> </table> `; } 4)管理员回复评价async function replyReview(id) { // 获取评价详情,显示已有回复 const res = await fetch(`${API_BASE}/reviews/all?limit=100`, { headers: getAuthHeaders() }); const data = await res.json(); const review = data.data.reviews.find(r => r.id === id); const existingReply = review ? (review.admin_reply || '') : ''; document.getElementById('modalBody').innerHTML = ` <h2>${existingReply ? '修改回复' : '回复评价'}</h2> <textarea id="replyContent" rows="4">${existingReply}</textarea> <button onclick="submitReply(${id})">提交</button> `; document.getElementById('modal').classList.add('active'); } 至此前端界面开发完成3.4 功能测试与问题修复3.4.1 测试系统功能1)测试顾客端访问:fcems/frontend/customer/index.html测试步骤:注册/登录账号浏览菜品列表点击菜品查看详情提交评价在"我的评价"中修改/删除评价【注册/登录账号页面】【浏览菜品列表】【点击菜品查看详情】【提交评价】【在"我的评价"中修改/删除评价】2)测试管理端访问:fcems/frontend/admin/login.html登录账号:用户名:admin密码:admin123测试步骤:查看仪表盘数据进入菜品管理,测试添加、编辑、上下架、删除进入评价管理,测试审核、回复进入分类管理,测试添加、编辑、删除进入用户管理,查看用户列表【管理端登录页面】【管理端仪表盘页面】【管理端菜品管理页面:可以添加、编辑、上下架、删除菜品】【添加菜品】【编辑菜品】【下架菜品】【上架菜品】【删除菜品】【管理端评价管理页面:可以审核、回复评价】【驳回评价】【回复评价】【管理端分类管理页面:可以添加、编辑、删除菜品分类】【点击“添加分类”按键】【点击分类的“删除”按键】【管理端用户管理页面,查看用户列表】3.4.2 发现问题并修复问题1:管理端登录后提示"无法连接到服务器"问题描述:登录成功后,页面弹出提示框"无法连接到服务器,请检查后端服务是否启动"原因分析:showPage函数使用了event.target,但从checkAuth调用时没有event对象修复方法:在CodeArts对话框中描述问题:管理端登录后弹出"无法连接到服务器"提示,控制台错误:"Cannot read properties of undefined (reading 'target')"请修复这个问题。CodeArts自动修复代码:// 修改前 function showPage(page) { event.target.closest('.menu-item').classList.add('active'); // ... } // 修改后 function showPage(page, event) { if (event && event.target) { event.target.closest('.menu-item').classList.add('active'); } // ... } 问题2:菜品编辑按钮无反应问题描述:管理端菜品管理中,点击"编辑"按钮没有反应原因分析:缺少editDish和updateDish函数修复方法:在CodeArts对话框中输入:菜品管理的编辑按钮点击无反应,请添加菜品编辑功能。CodeArts生成编辑功能代码:async function editDish(id) { const [dishRes, categoriesRes] = await Promise.all([ fetch(`${API_BASE}/dishes/${id}`, { headers: getAuthHeaders() }), fetch(`${API_BASE}/categories`, { headers: getAuthHeaders() }) ]); const dishData = await dishRes.json(); const categoriesData = await categoriesRes.json(); if (dishData.success) { const dish = dishData.data; // 显示编辑表单,填充已有数据 document.getElementById('modalBody').innerHTML = ` <h2>编辑菜品</h2> <form onsubmit="updateDish(event, ${id})"> <input id="editDishName" value="${dish.name}" required> <select id="editDishCategory"> ${categoriesData.data.map(c => `<option value="${c.id}" ${c.id === dish.category_id ? 'selected' : ''}>${c.name}</option>` ).join('')} </select> <input id="editDishPrice" value="${dish.price}" required> <button type="submit">保存</button> </form> `; document.getElementById('modal').classList.add('active'); } } async function updateDish(e, id) { e.preventDefault(); const dish = { name: document.getElementById('editDishName').value, category_id: parseInt(document.getElementById('editDishCategory').value), price: parseFloat(document.getElementById('editDishPrice').value) }; const res = await fetch(`${API_BASE}/dishes/${id}`, { method: 'PUT', headers: getAuthHeaders(), body: JSON.stringify(dish) }); if ((await res.json()).success) { closeModal(); loadDishesPage(); } } 问题3:下架菜品从列表中消失问题描述:菜品下架后,从管理端列表中消失,无法重新上架需求:下架菜品应保留在列表中显示"下架"状态标签提供"上架"按钮修复方法:在CodeArts对话框中输入:菜品管理应该显示所有未删除的菜品,包括下架的菜品。下架菜品应该保留编辑、删除、上架按钮。顾客端只显示上架的菜品。CodeArts修复:后端修改:// 修改查询条件,只过滤deleted_at,不过滤status let sql = ` SELECT d.*, c.name as category_name FROM dish d LEFT JOIN category c ON d.category_id = c.id WHERE d.deleted_at IS NULL`; // 添加status参数支持 if (status !== undefined) { sql += ' AND d.status = ?'; params.push(parseInt(status)); } 前端修改:// 顾客端:只获取上架的菜品 let url = `${API_BASE}/dishes?page=${page}&limit=12&status=1`; // 管理端:获取所有未删除的菜品 let url = `${API_BASE}/dishes?limit=100`; 问题4:添加菜品时名称可重复问题描述:可以添加同名菜品,导致数据混乱修复方法:在CodeArts对话框中输入:添加菜品时,新菜品名称不允许与已有菜品名称相同。CodeArts添加名称检查:router.post('/', auth, requireRole(2, 3), async (req, res) => { const { name } = req.body; // 检查名称是否重复 const [existingDish] = await query( 'SELECT id FROM dish WHERE name = ? AND deleted_at IS NULL', [name] ); if (existingDish) { return res.status(400).json({ success: false, message: '菜品名称已存在,请使用其他名称' }); } // 继续添加... }); 问题5:管理员回复无法保存和显示对评价的回复问题描述:回复内容没有被保存下次点击"回复"时无法修改回复内容没有显示在顾客端修复方法:在CodeArts对话框中输入:评价回复功能问题: 1. 回复内容应该被保存 2. 下次点击回复时可以修改上一次的回复 3. 回复内容应该显示在顾客端对应评价的下方CodeArts修复:管理端修改:async function replyReview(id) { // 获取评价详情,显示已有回复 const res = await fetch(`${API_BASE}/reviews/all?limit=100`); const data = await res.json(); const review = data.data.reviews.find(r => r.id === id); const existingReply = review ? (review.admin_reply || '') : ''; // 显示编辑表单,填充已有回复 document.getElementById('modalBody').innerHTML = ` <h2>${existingReply ? '修改回复' : '回复评价'}</h2> <textarea id="replyContent">${existingReply}</textarea> <button onclick="submitReply(${id})">提交</button> `; } 顾客端修改:// 在评价下方显示管理员回复 ${review.admin_reply ? ` <div class="admin-reply" style=" margin-top: 1rem; padding: 1rem; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-left: 4px solid #667eea;"> <div style="color: #667eea; font-weight: 600;"> <i class="fas fa-reply"></i> 管理员回复 </div> <div>${review.admin_reply}</div> </div>` : ''} 四、扩展资料说明华为云码道CodeArts实战速成:cid:link_0华为云开发者AI训练营:cid:link_2
  • [高校训练营] 基于华为云码道(CodeArts)的应用构建案例-嘉欣猎头公司管理系统:码道驱动,智能匹配
    嘉欣猎头公司管理系统:码道驱动,智能匹配一、概述1.1 案例介绍本案例采用华为云码道(CodeArts)代码智能体作为核心开发工具,从零构建一个互联网行业猎头公司管理系统。系统面向猎头顾问、软件工程师、企业HR三类角色,实现了工程师信息管理、公司职位管理、智能匹配推荐、面试流程状态机、职业轨迹追踪等核心功能。开发过程采用V1快速原型→V2功能完善→V3亮点打磨的三轮迭代模式,全程使用码道代码智能体辅助开发,充分展示了码道在项目骨架生成、代码续写、智能问答、SDD规范驱动开发等场景下的实践应用。代码仓库:https://gitcode.com/2301_78976454/headhunter-management-system在线演示:http://1.95.91.47/(测试账号:headhunter/123456、zhangsan/123456、huawei_hr/123456)1.2 适用对象高校学生个人开发者企业开发者1.3 案例时间本案例总时长预计120分钟(不含环境安装时间)。1.4 案例流程说明:环境准备:安装华为云码道(CodeArts)代码智能体、Node.js、MySQL;V1快速原型:使用码道生成项目骨架,完成5张核心表+基础CRUD+简单匹配推荐;V2功能完善:新增JWT认证+三角色权限、面试流程状态机、职业轨迹、多维度匹配算法;V3亮点打磨:集成ECharts数据仪表盘、匹配度雷达图、职位对比、通知中心;部署上线:部署至华为云ECS,提供可访问的演示环境。1.5 资源总览本案例预计花费0元(使用免费资源即可完成开发)。如需部署到华为云ECS,预计花费约0.6元/小时。体验完成后请及时释放资源,避免产生多余的费用。资源名称规格单价(元)华为云码道(CodeArts)代码智能体通用体验版免费Node.jsv22.x免费MySQL8.0+免费弹性云服务器ECS(可选,用于部署)2 vCPUs 4GiB0.6/小时二、环境和资源准备2.1 安装华为云码道(CodeArts)代码智能体访问华为云码道下载页面,下载并安装码道代码智能体。安装完成后,使用华为云账号登录。2.2 安装Node.js开发环境访问Node.js官网 下载并安装LTS版本(建议v22.x)。安装完成后,在终端验证:node --version npm --version 2.3 安装MySQL数据库安装MySQL 8.0及以上版本,并确保root用户可正常登录。创建项目数据库:mysql -u root -p CREATE DATABASE IF NOT EXISTS headhunter_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 2.4 完成码道CodeArts实战速成考试访问码道CodeArts实战速成考试,完成在线学习并通过考试,获取通过证书。三、构建嘉欣猎头公司管理系统3.1 项目结构说明本项目采用前后端分离架构,后端使用Node.js + Express + Sequelize + MySQL,前端使用Vue3 + Vite + Element Plus + ECharts。project30/ ├── sql/ │ └── init.sql # 数据库建表+示例数据脚本 ├── backend/ │ ├── package.json # 后端依赖配置 │ └── src/ │ ├── app.js # Express入口,路由注册 │ ├── config/ │ │ └── database.js # MySQL数据库连接配置 │ ├── middleware/ │ │ └── auth.js # JWT认证中间件+角色权限控制 │ ├── models/ │ │ ├── index.js # 模型统一导出 │ │ ├── User.js # 用户模型(三角色) │ │ ├── Engineer.js # 软件工程师模型 │ │ ├── Company.js # 互联网公司模型 │ │ ├── Department.js # 部门模型 │ │ ├── Position.js # 职位模型 │ │ ├── Recommendation.js # 推荐记录模型 │ │ ├── InterviewProcess.js # 面试流程模型 │ │ └── CareerHistory.js # 职业轨迹模型 │ └── routes/ │ ├── auth.js # 登录/注册/个人信息 │ ├── engineer.js # 工程师CRUD+按姓名搜索+职业查询 │ ├── company.js # 公司CRUD+已招聘查询 │ ├── department.js # 部门CRUD │ ├── position.js # 职位CRUD+搜索 │ ├── recommendation.js # 推荐+智能匹配 │ ├── interview.js # 面试流程+看板 │ └── career.js # 职业轨迹CRUD ├── frontend/ │ ├── package.json # 前端依赖配置 │ ├── vite.config.js # Vite配置+API代理 │ ├── index.html # 入口HTML │ └── src/ │ ├── main.js # Vue3入口 │ ├── App.vue # 根组件 │ ├── api/ │ │ ├── request.js # Axios封装+JWT拦截器 │ │ └── index.js # 全部API接口定义 │ ├── router/ │ │ └── index.js # 路由配置+角色守卫 │ ├── components/ │ │ └── NotificationCenter.vue # 通知中心组件 │ ├── layouts/ │ │ ├── HeadhunterLayout.vue # 猎头顾问布局 │ │ ├── EngineerLayout.vue # 工程师布局 │ │ └── HrLayout.vue # 企业HR布局 │ └── views/ │ ├── LoginView.vue # 登录/注册页 │ ├── headhunter/ # 猎头端7个页面(含信息查询) │ ├── engineer/ # 工程师端6个页面 │ └── hr/ # HR端4个页面项目源码已上传至GitCode,可通过以下命令下载到本地:git clone https://gitcode.com/2301_78976454/headhunter-management-system.git3.2 V1快速原型:搭建核心链路3.2.1 使用码道创建项目骨架在码道IDE中,打开终端,创建项目目录并初始化后端:mkdir project30 && cd project30 mkdir sql backend frontend cd backend && npm init -y 使用码道智能问答功能,输入提示词:“帮我创建一个Express + Sequelize + MySQL的后端项目骨架,包含5个模型:Engineer、Company、Department、Position、Recommendation”码道会自动生成模型文件和基础路由代码,我们在此基础上进行修改。3.2.2 数据库设计与初始化使用码道辅助编写SQL建表脚本,核心5张表设计如下:CREATE TABLE engineer ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT '姓名', age INT COMMENT '年龄', gender VARCHAR(10) COMMENT '性别', current_company VARCHAR(100) COMMENT '现工作公司', current_salary DECIMAL(10,2) COMMENT '现薪水(元/月)', current_position VARCHAR(100) COMMENT '现职务', expected_salary DECIMAL(10,2) COMMENT '期望薪水(元/月)', skills VARCHAR(500) COMMENT '擅长技能,逗号分隔', expected_city VARCHAR(50) COMMENT '期望城市', open_to_headhunter TINYINT DEFAULT 1 COMMENT '是否接受猎头推荐', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT '软件工程师表'; 执行初始化脚本:mysql -u root -p < sql/init.sql3.2.3 后端核心路由以推荐路由为例,V1实现了简单的技能+薪资+城市匹配算法:function calcMatchScore(engineer, position) { let score = 0; const eSkills = (engineer.skills || '').split(',').map(s => s.trim().toLowerCase()); const pSkills = (position.required_skills || '').split(',').map(s => s.trim().toLowerCase()); if (eSkills.length > 0 && pSkills.length > 0) { const matched = pSkills.filter(ps => eSkills.some(es => es.includes(ps) || ps.includes(es))); score += (matched.length / pSkills.length) * 50; } if (engineer.expected_salary >= position.salary_min && engineer.expected_salary <= position.salary_max) { score += 30; } if (position.location.includes(engineer.expected_city)) { score += 20; } return Math.min(100, Math.round(score * 100) / 100); } 3.2.4 前端基础页面使用码道代码续写功能,快速生成Vue3 + Element Plus的CRUD页面。V1阶段共6个页面:工作台、工程师管理、公司管理、职位管理、推荐管理、信息查询。3.2.5 V1验证启动后端和前端服务:# 后端 cd backend && npm install && npm run dev # 前端(需在纯英文路径下运行) cd frontend && npm install && npm run devV1交付标准:工程师信息增删改查、公司→部门→职位层级管理、猎头推送推荐+工程师接受/拒绝、按公司/按人双向查询。3.3 V2功能完善:满足全部硬性要求3.3.1 JWT三角色认证新增User模型和auth中间件,实现猎头/工程师/企业HR三角色认证:const jwt = require('jsonwebtoken'); const SECRET = 'jiaxin_headhunter_2026'; function authMiddleware(req, res, next) { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) return res.status(401).json({ code: 1, msg: '未登录' }); try { req.user = jwt.verify(token, SECRET); next(); } catch { res.status(401).json({ code: 1, msg: 'token无效或已过期' }); } } function roleMiddleware(...roles) { return (req, res, next) => { if (!roles.includes(req.user.role)) return res.status(403).json({ code: 1, msg: '无权限' }); next(); }; } 3.3.2 面试流程状态机实现6状态状态机:推荐(recommended) → 确认(confirmed) → 面试(interviewing) → 发Offer(offered) → 入职(onboarded) 或 失败(failed):const VALID_TRANSITIONS = { recommended: ['confirmed', 'failed'], confirmed: ['interviewing', 'failed'], interviewing: ['offered', 'failed'], offered: ['onboarded', 'failed'], onboarded: [], failed: [] }; InterviewProcess.canTransitionTo = function (currentStatus, newStatus) { return VALID_TRANSITIONS[currentStatus]?.includes(newStatus) || false; }; 当状态变为onboarded时,自动更新工程师的当前公司和职位,并写入职业轨迹记录。3.3.3 多维度加权匹配算法将V1的简单匹配升级为分项评分(技能50分+薪资30分+城市20分):function calcMatchDetail(engineer, position) { let skillScore = 0, salaryScore = 0, cityScore = 0; // 技能匹配(满分50分) const eSkills = (engineer.skills || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean); const pSkills = (position.required_skills || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean); if (eSkills.length > 0 && pSkills.length > 0) { const matched = pSkills.filter(ps => eSkills.some(es => es.includes(ps) || ps.includes(es))); skillScore = (matched.length / pSkills.length) * 50; } // 薪资匹配(满分30分) if (engineer.expected_salary && position.salary_min && position.salary_max) { const mid = (Number(position.salary_min) + Number(position.salary_max)) / 2; const diff = Math.abs(Number(engineer.expected_salary) - mid) / mid; if (diff <= 0.1) salaryScore = 30; else if (diff <= 0.2) salaryScore = 25; else if (diff <= 0.3) salaryScore = 20; else salaryScore = 10; } // 城市匹配(满分20分) if (engineer.expected_city && position.location) { if (position.location.includes(engineer.expected_city)) cityScore = 20; } const total = Math.min(100, Math.round((skillScore + salaryScore + cityScore) * 100) / 100); return { total, skill_score: skillScore, salary_score: salaryScore, city_score: cityScore }; } 3.3.4 三角色独立界面V2将V1的单页面Tab切换升级为三角色独立布局:猎头顾问:深蓝侧边栏(#1a1a2e),包含工作台、人才库、职位库、智能匹配、面试看板、数据统计、信息查询软件工程师:靛蓝侧边栏(#0f3460),包含首页、个人档案、推荐职位、职位对比、面试记录、职业轨迹企业HR:紫色侧边栏(#533483),包含招聘概览、职位管理、候选人、已录用3.3.5 V2验证V2交付标准:三角色JWT认证与权限隔离、面试全流程状态流转、多维度智能匹配推荐、跳槽后按公司/按人双向查询(课题硬性要求✅)、工程师职业轨迹展示。3.3.6 自检修复:满足课题硬性要求对照课题30的硬性要求进行自检,发现并修复了以下问题:问题1:每个部门至少3个职位原始示例数据仅11个职位,部分部门不足3个。补充SQL示例数据至30个职位,确保10个部门每个至少3个职位:-- 示例:技术部补充职位(已有3个,此处展示格式) INSERT INTO position (title, department_id, ...) VALUES ('全栈工程师', 1, ...), ('DevOps工程师', 1, ...), ('数据开发工程师', 1, ...); 问题2:按工程师姓名查找职业轨迹原仅支持按工程师ID查询,新增按姓名模糊搜索API:// backend/src/routes/engineer.js - 新增按姓名搜索路由 router.get('/search/name/:name', async (req, res) => { const engineers = await Engineer.findAll({ where: { name: { [Op.like]: `%${req.params.name}%` } } }); res.json({ code: 0, data: engineers }); }); 前端查询页面同步升级,支持"按ID"和"按姓名"两种搜索模式,按姓名搜索时展示匹配工程师列表,可展开查看完整职业轨迹时间线。注意:Express路由注册顺序中,/search/name/:name 必须在 /:id 之前注册,否则会被参数路由拦截。3.4 V3亮点打磨:冲击满分3.4.1 ECharts数据统计仪表盘在猎头端新增数据统计页面,使用ECharts实现6个可视化图表:// 技能分布柱状图 skillChart.setOption({ xAxis: { type: 'category', data: d.topSkills.map(i => i.name) }, yAxis: { type: 'value', name: '人数' }, series: [{ type: 'bar', data: d.topSkills.map(i => i.value), itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#409eff' }, { offset: 1, color: '#79bbff' } ])} }] }); 6个图表覆盖:热门技能TOP10柱状图、期望城市饼图、期望薪资玫瑰图、面试状态环形图、公司招聘横向柱图、职位薪资热力图。3.4.2 匹配度雷达图在智能匹配页面,为每个推荐结果生成ECharts雷达图,直观展示技能/薪资/城市三维匹配情况:chart.setOption({ radar: { indicator: [ { name: '技能匹配', max: 50 }, { name: '薪资匹配', max: 30 }, { name: '城市匹配', max: 20 } ], shape: 'circle' }, series: [{ type: 'radar', data: [{ value: [item.skill_score, item.salary_score, item.city_score] }] }] }); 3.4.3 职位对比功能工程师端新增职位对比页面,可选择两个已接受的推荐职位并排对比,同时生成双雷达图叠加展示:series: [{ type: 'radar', data: [ { value: [...], name: '职位A', areaStyle: { color: 'rgba(64,158,255,0.15)' } }, { value: [...], name: '职位B', areaStyle: { color: 'rgba(103,194,58,0.15)' } } ] }] 3.4.4 通知中心实现基于轮询的通知中心组件,30秒自动刷新,三角色差异化通知:猎头:待处理推荐数、已入职人数工程师:新推荐数、待参加面试数HR:候选人接受数onMounted(() => { fetchNotifications() timer = setInterval(fetchNotifications, 30000) }) 3.4.5 后端统计API增强新增 /api/stats/dashboard 接口,一次请求返回6维度统计数据;新增 /api/stats/notifications 接口,按角色返回差异化通知。3.4.6 V3验证V3交付标准:ECharts数据仪表盘6图、匹配度雷达图、职位对比功能、通知中心轮询。3.4.7 系统运行效果上图展示了猎头顾问登录后的数据统计仪表盘,包含热门技能TOP10柱状图、期望城市饼图、期望薪资玫瑰图、面试状态环形图、公司招聘横向柱图、职位薪资热力图共6个可视化图表。3.5 部署至华为云ECS(可选)3.5.1 购买ECS弹性云服务器登录华为云控制台,购买ECS实例(2vCPUs 4GiB,Ubuntu 22.04),配置安全组开放80端口,购买弹性公网IP(按流量计费)。3.5.2 部署后端# 安装Node.js 22 curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash - sudo apt-get install -y nodejs # 安装PostgreSQL sudo apt-get install -y postgresql postgresql-contrib sudo systemctl start postgresql # 配置数据库 sudo -u postgres psql -c "ALTER USER postgres PASSWORD '123456';" sudo -u postgres psql -c "CREATE DATABASE headhunter_db;" # 下载项目代码 sudo apt-get install -y git cd /root && git clone https://gitcode.com/2301_78976454/headhunter-management-system.git # 导入数据库 cp /root/headhunter-management-system/sql/init_pg.sql /tmp/ sudo -u postgres psql -d headhunter_db -f /tmp/init_pg.sql # 安装后端依赖并启动 cd /root/headhunter-management-system/backend && npm install nohup node src/app.js > /root/backend.log 2>&1 & 3.5.3 部署前端# 构建前端 cd /root/headhunter-management-system/frontend sed -i '/esbuild-windows-64/d' package.json npm install && npm run build # 部署到nginx sudo mkdir -p /var/www/headhunter sudo cp -r dist/* /var/www/headhunter/ sudo chmod -R 755 /var/www/headhunter3.5.4 配置Nginxsudo apt-get install -y nginx创建Nginx配置文件 /etc/nginx/sites-available/headhunter:server { listen 80; server_name _; root /var/www/headhunter; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } sudo ln -sf /etc/nginx/sites-available/headhunter /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t && sudo systemctl restart nginx3.5.5 开放安全组端口在华为云控制台:ECS详情 → 安全组 → 入方向规则 → 添加规则:协议TCP,端口80,源地址0.0.0.0/0。3.6 系统测试3.6.1 演示环境系统已部署至华为云ECS,可通过以下地址访问:访问地址:http://1.95.91.47测试账号:角色用户名密码功能范围猎头顾问headhunter123456工作台、人才库、职位库、智能匹配、面试看板、数据统计、信息查询工程师zhangsan123456首页、个人档案、推荐职位、职位对比、面试记录、职业轨迹企业HRhuawei_hr123456招聘概览、职位管理、候选人、已录用3.6.2 功能测试用例编号测试功能操作步骤预期结果T01猎头登录输入headhunter/123456,选择猎头顾问,点击登录跳转到猎头工作台页面T02人才库管理点击"人才库",查看工程师列表显示8名工程师信息,支持按技能/城市/薪资筛选T03职位层级管理点击"职位库",展开公司→部门→职位显示5家公司、10个部门、30个职位的层级结构T04智能匹配推荐点击"智能匹配",选择工程师,点击"一键匹配"生成匹配推荐列表,显示匹配度评分和雷达图T05面试流程流转点击"面试看板",拖拽卡片变更状态状态按推荐→确认→面试→Offer→入职流转T06数据统计仪表盘点击"数据统计"显示6个ECharts图表(技能柱状图、城市饼图等)T07按姓名查询职业轨迹点击"信息查询",切换"按姓名",输入"张"模糊搜索出张三,展开查看职业轨迹时间线T08按公司查询已招聘在"信息查询"左侧,选择公司点击查询显示该公司已招聘工程师列表T09工程师登录输入zhangsan/123456,选择工程师跳转到工程师首页T10推荐职位响应工程师端点击"推荐职位",接受/拒绝推荐更新推荐状态,猎头端同步可见T11职位对比工程师端点击"职位对比",选择两个职位显示双雷达图叠加对比T12HR登录输入huawei_hr/123456,选择企业HR跳转到HR招聘概览T13候选人查看HR端点击"候选人"显示接受推荐的候选人列表T14通知中心任意角色登录后,点击右上角铃铛图标显示角色差异化通知,30秒自动刷新T15角色权限隔离猎头账号尝试访问/engineer路径自动重定向回/headhunter,无权限页面不可访问四、释放资源4.1 删除ECS弹性云服务器进入ECS列表,点击全选按钮,点击"更多 → 删除"。在对话框中选择"释放云服务器绑定的公网IP地址"和"删除云服务器挂载的数据盘",点击"是"。五、扩展资料说明华为云码道(CodeArts)代码智能体:https://codearts.huaweicloud.comVue3官方文档:https://cn.vuejs.orgElement Plus组件库:https://element-plus.org/zh-CNECharts数据可视化:https://echarts.apache.org/zh/index.htmlSequelize ORM文档:https://sequelize.orgExpress框架文档:https://expressjs.com
  • [高校训练营] 基于华为云码道(CodeArts)代码智能体的失物招领平台开发
    一、概述1.1 案例介绍随着校园、社区及公共场所人员流动增加,物品遗失与寻找需求日益普遍。传统失物招领方式通常依赖线下公告、微信群等渠道,存在信息分散、检索困难、匹配效率低等问题。为提升失物信息流转效率,本案例设计并开发了一个智能化失物招领平台,实现失物信息与招领信息的集中管理和便捷查询。本案例基于 AI Agent 自动化开发模式,利用华为开发者空间开发平台和华为云码道(CodeArts)代码智能体完成全流程应用构建。应用采用前后端分离架构,选择采用 Vue3 前端、Flask 后端、SQLite 数据库的技术栈进行开发。系统支持用户注册登录、失物信息发布、招领信息发布、物品图片上传、关键词搜索、分类筛选、详情查看、站内联系以及个人信息管理等核心功能,同时提供信息状态管理能力,帮助用户完成从信息发布到物品找回的完整业务流程。本案例构建的项目和文档已保存至GitCode,可自行下载源码。Demo演示视频技术选型:华为开发者空间开发平台 - 云开发环境(容器):华为云提供的一站式云上开发环境,支持多种主流开发语言和运行环境,开发者无需进行复杂的本地环境配置即可快速开展应用开发。本案例中选用云开发环境(容器)作为应用的开发与运行环境,为项目提供统一稳定的基础环境,避免因操作系统、依赖版本等环境差异导致的应用运行问题,提高项目构建和部署效率。华为云码道(CodeArts)代码智能体:华为云提供的智能化软件开发工具,融合代码大模型、AI IDE 和 Code Agent 等能力,能够理解项目需求并辅助完成代码生成、代码优化和工程构建。本案例中使用华为云码道(CodeArts)代码智能体作为核心开发辅助工具,根据失物招领应用需求完成前后端功能开发,加速 Web 应用构建过程,降低应用开发门槛。1.2 适用对象企业开发者个人开发者高校学生1.3 案例时间本案例总时长预计60分钟。1.4 案例流程说明:VSCode安装开发者空间插件,远程连接云开发环境(容器);云开发环境(容器)搭建华为云码道(CodeArts)代码智能体;对话码道,开发SDD并检验是否符合项目预期;对话码道,编写代码生成项目;启动项目,验证项目功能;对话码道,更新优化SDD,沉淀项目知识库。1.5 资源总览本案例预计花费0元。资源名称规格单价(元)开发者空间云开发环境ARM | 2vCPUs 4GB | All In One 开发环境免费华为云码道(CodeArts)代码智能体体验版免费二、环境和资源准备2.1 VSCode 远程连接云开发环境参考案例《华为开发者空间 - 云开发环境(容器)IDE插件远程连接操作指导》中的“二、 云开发环境IDE插件远程连接操作指导”章节,完成 VS Code 到云开发环境(容器)的远程连接。2.2 VSCode 搭建码道智能体VS Code 连接云开发环境的状态下,在“扩展”中搜索“CodeArts”,安装“CodeArts Agent”插件。安装完成后,VS Code中执行命令:CodeArts Agent: Open the Q&A Dialog右侧弹出的侧栏为码道智能体对话界面。登录后进行对话测试,确认码道智能体搭建成功且能够正常使用。三、使用码道构建失物招领平台系统3.1 需求分析与功能设计项目名称: Anima - 失物招领平台项目定位:现代化失物招领平台,支持用户发布失物和招领信息、浏览与搜索信息、联系发布者,并具备完整的用户管理能力。技术栈:前端:Vue3 + Vite + TypeScript后端:Python Flask数据库:SQLite核心功能:用户登录和信息管理用户发布失物信息和招领信息,可对自己发布的信息进行编辑、删除、修改完成状态展示最新发布的信息,可分类浏览,可按条件检索查看物品详情并联系发布者管理员管理用户和信息3.2 Prompt 设计和 SDD 生成(1)根据以上项目需求,设计如下 Prompt:根据以下需求描述,开发一个失物招领 Web 应用。 1. 项目概述 项目名称:Anima 项目定位:现代化的失物招领平台,支持用户发布失物和招领信息、浏览与搜索信息、联系发布者,并具备完整的用户管理能力。 2. 技术框架 技术栈:实现前后端分离,前端采用Vue3,后端采用Python Flask,数据库使用SQLite。具备良好的可维护性、可扩展性和规范的项目结构,API 设计遵循 RESTful 风格。 3. 核心功能 (1)用户注册、登录、退出及个人信息管理。 (2)发布失物信息和招领信息,并支持上传多张图片。 (3)首页展示最新发布的信息,可按失物/招领分类浏览。 (4)支持按物品名称、分类、地点、时间等条件进行搜索和筛选。 (5)查看物品详情,并提供联系发布者的方式(站内联系即可,无需真实短信或邮件)。 (6)用户可管理自己发布的信息,包括编辑、删除、修改状态(已找到/已认领)。 (7)管理员具备用户管理、信息审核(可选)及违规内容处理能力。 基本业务流程:注册登录后即可发布失物或招领信息;其他用户可浏览、搜索、查看详情,并通过站内方式联系发布者。当物品成功找回后,发布者可以将信息标记为已完成,已完成的信息仍可保留展示但默认降低排序。 4. 设计风格 整体采用现代、简洁、清爽的设计风格,界面注重易用性与响应式布局,能够适配桌面端与移动端浏览器,保证良好的用户体验。在码道对话界面选择 规范驱动模式(Spec-Driven Mode),发送上述请求。(2)码道首先调用 creating-sdd-directory skill 创建项目目录结构。(3)之后码道根据需求描述,生成需求规格文档 spec.md。如有需要可对需求规格文档进行修改。(4)确认后,码道继续生成技术设计文档 design.md,完成生成后可按需修改。(5)确认后,码道继续生成编码任务文档 task.md,完成生成后可按需修改。(6)检查需求规格文档、技术设计文档、编码任务文档是否与预期一致,如有需要调整的地方可直接进行修改。3.3 编码任务执行确认 SSD 无误后,在码道界面发送请求:遵循spec.md、design.md、tasks.md进行项目开发。码道将根据 SSD 执行编码任务。(1)初始化后端项目结构(2)创建数据模型(3)创建工具函数与权限装饰器(4)创建后端核心接口(5)验证后端构建(6)创建初始化前端项目结构、配置依赖(7)创建路由配置和 API 封装(8)更新 main.js(9)创建全局样式和组件(10)实现前端页面视图(11)验证前端构建(12)集成验证(13)开发完成3.4 环境配置与快速启动开发完成后,可查看如下项目结构:/workspace/ ├── anima-backend/ # 后端 Flask 项目 │ ├── app/ │ │ ├── __init__.py # 应用工厂 + 种子数据 │ │ ├── config.py # 配置文件 │ │ ├── extensions.py # SQLAlchemy/JWT/Migrate/CORS │ │ ├── models/ # 数据模型 │ │ │ ├── user.py # User 模型(密码哈希、to_dict) │ │ │ ├── item.py # Item 模型(图片JSON、状态映射) │ │ │ └── message.py # Message 模型 │ │ ├── api/ # API 蓝图 │ │ │ ├── auth.py # 注册/登录/个人信息/改密 │ │ │ ├── items.py # 信息CRUD/搜索/状态变更 │ │ │ ├── messages.py # 站内消息/已读/未读数 │ │ │ └── admin.py # 用户管理/信息下架 │ │ ├── utils/ # 工具函数 │ │ │ ├── __init__.py # 响应辅助/权限装饰器 │ │ │ └── validators.py # 校验函数 │ │ └── static/uploads/ # 图片上传目录 │ ├── run.py # 启动入口 │ └── requirements.txt │ ├── anima-frontend/ # 前端 Vue3 项目 │ ├── src/ │ │ ├── api/ # API 请求封装 │ │ ├── components/ # 通用组件 │ │ │ ├── AppHeader.vue # 导航栏(响应式+消息未读数) │ │ │ ├── AppFooter.vue │ │ │ ├── ItemCard.vue # 信息卡片 │ │ │ ├── SearchBar.vue # 搜索筛选栏 │ │ │ ├── ImageUpload.vue # 图片上传 │ │ │ └── Pagination.vue # 分页 │ │ ├── views/ # 页面视图(7个) │ │ │ ├── HomeView.vue # 首页 │ │ │ ├── LoginView.vue # 登录 │ │ │ ├── RegisterView.vue# 注册 │ │ │ ├── ItemDetailView.vue # 详情+站内联系 │ │ │ ├── ItemPostView.vue # 发布 │ │ │ ├── ItemEditView.vue # 编辑 │ │ │ ├── MyItemsView.vue # 我的发布 │ │ │ ├── ProfileView.vue # 个人信息 │ │ │ ├── MessagesView.vue # 消息中心 │ │ │ └── admin/ # 管理员页面 │ │ ├── router/index.js # 路由+守卫 │ │ ├── stores/user.js # Pinia 用户状态 │ │ └── assets/main.css # 全局样式 │ └── vite.config.js # Vite 代理配置 │ └── .codeartsdoer/specs/anima/ # SDD 文档 ├── spec.md ├── design.md └── tasks.md安装依赖、启动项目后端:cd anima-backend pip install -r requirements.txt python run.py安装依赖、启动项目前端:cd anima-frontend npm install npm run dev成功启动后,浏览器访问 http://localhost:3000/ 进入应用页面。3.5 项目功能测试进入应用首页,可以看到导航栏、信息筛选卡片以及失物招领信息列表。尝试注册账号并登录,可查看账号个人信息并修改昵称和密码。导航栏点击“发布信息”,填写失物或招领的相关内容,点击“发布”后可以在首页看到已发布的信息。首页点击失物招领信息卡片,进入详情页、查看该条信息的具体内容。对于自己发布的信息,可以点击“编辑”来编辑该信息的内容,或者点击“标记为已找到(标记为已认领)”来修改完成状态。对于他人发布的信息,可在详情页联系发布者。可在导航栏点击“消息”,查看自己收到和发出的消息。对于他人发布的信息,可在详情页联系发布者。可在导航栏点击“消息”,查看自己收到和发出的消息。导航栏点击“我的发布”,查看自己发布的失物招领信息。登录管理员账号,验证用户管理和信息管理功能。3.5 后续开发与优化(1)与码道进行对话,反馈测试应用时发现的问题,例如编辑失物招领信息时添加图片功能出现异常。码道会进行修改、解决这些问题。(2)与码道进行对话,对当前应用进行优化,例如完善控件显示、为关键操作添加二次确认、对页面进行美化等。美化后的应用,首页界面如下。(3)完成开发后,向码道以下指令,更新 SSD。请总结项目,更新SDD,确保下次可以直接通过该SDD复现项目的风格、布局、颜色、功能特性以及前后台业务逻辑等。同步更新 SSD,可以完整记录系统设计与实现过程中的关键决策,提高开发过程的可追溯性,并促进团队协作与知识共享。此外,通过保持文档与代码的一致性,构建了系统化的项目文档体系,为后续维护和功能扩展提供了有力支撑。3.6 核心代码讲解(1)后端应用工厂与蓝图注册anima-backend/app/__init__.py 中的 create_app 是后端唯一启动入口,完成配置加载、扩展初始化、蓝图挂载和数据库初始化,组装成一个可运行的 Flask 应用。# anima-backend/app/__init__.py def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) # 挂载数据库、JWT、迁移、跨域等扩展 db.init_app(app) jwt.init_app(app) migrate.init_app(app, db) cors.init_app(app, resources={r"/api/*": {"origins": "*"}}) os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) # 提供上传图片的静态访问地址 @app.route('/static/uploads/<path:filename>') def uploaded_file(filename): return send_from_directory(app.config['UPLOAD_FOLDER'], filename) # 按业务注册四类 API app.register_blueprint(auth_bp, url_prefix='/api/auth') app.register_blueprint(items_bp, url_prefix='/api/items') app.register_blueprint(messages_bp, url_prefix='/api/messages') app.register_blueprint(admin_bp, url_prefix='/api/admin') # 首次启动时建表,并写入默认管理员 with app.app_context(): from app import models # noqa: F401 db.create_all() _seed_admin() return app初始化 db、jwt、migrate、cors,分别负责 ORM、登录令牌、数据库迁移与前端跨域调用支持,并开放图片上传目录 /static/uploads/ 供浏览器访问图片。后端按业务划分 /api/auth、/api/items、/api/messages、/api/admin 四个蓝图,分别负责认证、失物招领、站内消息、管理端;后端启动时自动建表,并通过 _seed_admin 写入默认管理员账号(admin / admin123),便于首次体验管理功能。(2)核心数据模型数据模型定义了 User、Item、Message 三类核心实体,分别对应用户、失物/招领信息与站内消息。User 模型:保存账号身份、负责密码安全# anima-backend/app/models/user.py class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(20), unique=True, nullable=False, index=True) email = db.Column(db.String(120), unique=True, nullable=False, index=True) password_hash = db.Column(db.String(256), nullable=False) nickname = db.Column(db.String(30)) role = db.Column(db.String(20), nullable=False, default='user') status = db.Column(db.String(20), nullable=False, default='active') @password.setter def password(self, password): self.password_hash = bcrypt.hashpw( password.encode('utf-8'), bcrypt.gensalt() ).decode('utf-8') def verify_password(self, password): return bcrypt.checkpw( password.encode('utf-8'), self.password_hash.encode('utf-8') ) def to_dict(self): return { 'id': self.id, 'username': self.username, 'email': self.email, 'nickname': self.display_name, 'role': '管理员' if self.role == 'admin' else '普通用户', 'status': self.status, # ... } User 对应注册用户。除了用户名、邮箱、昵称外,用 role 区分普通用户与管理员,用 status 标记账号是否可用。密码不存明文,而是通过 @password.setter 用 bcrypt 生成哈希写入、登录时校验,保证账号安全。to_dict 把数据库记录转成前端可用的 JSON(角色显示为中文);另有 to_public_dict 只返回昵称等公开字段,用于物品详情页展示发布者,避免泄露邮箱等敏感信息。Item 模型:保存失物招领信息# anima-backend/app/models/item.py class Item(db.Model): __tablename__ = 'items' title = db.Column(db.String(50), nullable=False) type = db.Column(db.String(10), nullable=False, index=True) # lost / found description = db.Column(db.Text, nullable=False) category = db.Column(db.String(20), nullable=False, index=True) location = db.Column(db.String(100), nullable=False) event_time = db.Column(db.DateTime, nullable=False) _images = db.Column('images', db.Text) status = db.Column(db.String(20), nullable=False, default='active', index=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) STATUS_DISPLAY = { 'active': '进行中', 'found': '已找到', 'claimed': '已认领', 'removed': '已下架' } @property def images(self): return json.loads(self._images) if self._images else [] @images.setter def images(self, value): self._images = json.dumps(value) if value else None @property def is_completed(self): return self.status in ('found', 'claimed') Item 是平台的核心业务对象,一条记录代表用户发布的一则失物或招领,包含标题、描述、分类、地点、事件时间,以及多张图片和当前状态字段。user_id 指向发布者,从而把信息与用户关联起来。Message 模型:保存围绕某条信息的站内联系记录# anima-backend/app/models/message.py class Message(db.Model): __tablename__ = 'messages' content = db.Column(db.String(500), nullable=False) sender_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) receiver_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) item_id = db.Column(db.Integer, db.ForeignKey('items.id'), nullable=False) is_read = db.Column(db.Boolean, nullable=False, default=False) Message 用于实现“在详情页联系发布者”。每条消息都绑定发送方、接收方和关联的 Item,并记录是否已读,从而支撑消息中心列表与导航栏未读提示。与普通私信不同,这里强制要求 item_id,保证联系必须围绕某条失物/招领展开,消息中心也能据此展示“关于哪条信息的沟通”;is_read 则用于统计未读数,并在用户查看后标记为已读。(3)统一响应与权限装饰器app/utils 提供两套公共能力:统一 JSON 响应格式,以及对接口做登录/管理员校验。所有业务接口都复用它们,避免每个接口各自拼返回体、各自写鉴权代码。# anima-backend/app/utils/__init__.py def success_response(data=None, message='success'): return jsonify({'code': 0, 'message': message, 'data': data or {}}) def error_response(code, message): return jsonify({'code': code, 'message': message, 'data': {}}), \ 400 if code < 500 else 500 def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): verify_jwt_in_request() identity = get_jwt_identity() user = User.query.get(identity) if not user: return error_response(401, '用户不存在') if user.status == 'disabled': return error_response(403, '账号已被禁用') return f(*args, **kwargs, current_user=user) return decorated_function def admin_required(f): # 在登录校验基础上,额外要求 role == 'admin' ... 成功与失败都返回 {code, message, data}:前端只需判断 code === 0 即可处理结果,错误提示取自 message。login_required 装饰在需要登录的接口上:校验请求头中的 JWT,查出对应用户,若账号被禁用则拒绝访问,通过后把 current_user 传给视图函数。admin_required 在此基础上再检查 role == 'admin',专门保护管理端接口。这样鉴权逻辑集中维护,业务代码只需关心业务本身。(4)用户认证接口auth 蓝图负责用户进入系统的全过程,包括创建账号、验证身份、签发登录凭证,以及登录后的个人资料与改密。# anima-backend/app/api/auth.py @auth_bp.route('/register', methods=['POST']) def register(): data = request.get_json() username = data.get('username', '').strip() email = data.get('email', '').strip() password = data.get('password', '') # 校验格式与唯一性后创建用户 user = User(username=username, email=email) user.password = password db.session.add(user) db.session.commit() token = create_access_token(identity=user.id) return success_response({ 'token': token, 'user': user.to_dict() }, '注册成功') @auth_bp.route('/login', methods=['POST']) def login(): username = data.get('username', '').strip() password = data.get('password', '') # 支持用用户名或邮箱登录 user = User.query.filter( (User.username == username) | (User.email == username) ).first() if not user or not user.verify_password(password): return error_response(401, '用户名或密码错误') if user.status == 'disabled': return error_response(403, '账号已被禁用') token = create_access_token(identity=user.id) return success_response({'token': token, 'user': user.to_dict()}, '登录成功') 注册流程为:校验用户名/邮箱/密码格式 → 检查是否已存在 → 创建 User(密码自动哈希)→ 签发 JWT 并连同用户信息返回,注册成功即处于登录态。登录流程为:按用户名或邮箱查找用户 → 校验密码与账号状态 → 同样返回 token 与 user。前端保存该 Token 后,后续请求即可证明身份。同文件还提供需登录才能访问的个人资料读写与修改密码接口。(5)失物招领接口items 蓝图实现平台主业务,包括用户浏览/搜索信息、登录后发布带图信息、对自己发布的信息做编辑删除,以及在找回物品后标记完成。首页列表、详情页、发布页、我的发布都依赖这里的接口。列表查询与排序:口面向所有访客,负责把符合条件的信息分页返回给首页# anima-backend/app/api/items.py @items_bp.route('', methods=['GET']) def get_items(): query = Item.query.filter(Item.status != 'removed') if item_type and item_type in Item.VALID_TYPES: query = query.filter(Item.type == item_type) if keyword: query = query.filter( db.or_(Item.title.contains(keyword), Item.description.contains(keyword)) ) if category and category in Item.VALID_CATEGORIES: query = query.filter(Item.category == category) if location: query = query.filter(Item.location.contains(location)) # 还可按 start_date / end_date 过滤 event_time # 进行中优先,再按创建时间倒序 query = query.order_by( db.case((Item.status == 'active', 0), else_=1), Item.created_at.desc() ) pagination = query.paginate(page=page, per_page=per_page, error_out=False) return success_response({ 'items': [item.to_dict() for item in pagination.items], 'total': pagination.total, 'page': pagination.page, 'pages': pagination.pages }) 查询时,先排除已下架信息,再按类型、关键词、分类、地点、时间等条件叠加过滤;排序上使 active(进行中)排在前面,同优先级再按发布时间倒序,这样首页优先看到仍待匹配的信息。最后分页返回,供前端翻页展示。发布信息与图片上传:要求登录,负责把表单字段和图片落库为一条新 Item@items_bp.route('', methods=['POST']) @login_required def create_item(current_user): # 校验标题、类型、描述、分类、地点、时间等必填项 images = [] files = request.files.getlist('images') for f in files: if f.filename: # 校验格式与大小后,用 UUID 重命名并保存 filename = f'{uuid.uuid4().hex}.{ext}' f.save(os.path.join(current_app.config['UPLOAD_FOLDER'], filename)) images.append(f'/static/uploads/{filename}') item = Item( title=title, type=item_type, description=description, category=category, location=location, event_time=event_time, user_id=current_user.id ) item.images = images db.session.add(item) db.session.commit() return success_response(item.to_dict(), '发布成功') 接口使用 multipart/form-data 同时接收文字与文件:先校验业务字段,再逐张检查图片扩展名与大小,保存到上传目录后把访问路径写入 item.images,最后关联 current_user.id 入库。编辑接口逻辑类似,但会保留前端回传的 existing_images,再追加新上传图片,避免改信息时把原图清空。状态变更:用于发布者在物品找回后结束该条信息@items_bp.route('/<int:item_id>/status', methods=['PUT']) @login_required def update_item_status(current_user, item_id): item = Item.query.get(item_id) if item.user_id != current_user.id: return error_response(403, '无权操作') if item.is_completed: return error_response(400, '已完成的信息不可恢复为进行中') if item.type == 'lost' and new_status == 'found': item.status = 'found' elif item.type == 'found' and new_status == 'claimed': item.status = 'claimed' else: return error_response(400, '无效的状态变更') db.session.commit() return success_response(item.to_dict(), '状态更新成功') 只有发布者本人可操作;失物只能标为“已找到”,招领只能标为“已认领”;一旦完成不可再改回进行中。这样既闭环了“发布 → 找回 → 结束”流程,也避免状态被随意篡改。(6)站内消息接口messages 蓝图实现站内联系,即浏览者在详情页给发布者留言,双方在消息中心查看往来,导航栏展示未读数量。它替代真实短信/邮件,把沟通留在平台内完成。# anima-backend/app/api/messages.py @messages_bp.route('', methods=['POST']) @login_required def send_message(current_user): if receiver_id == current_user.id: return error_response(400, '不能联系自己') item = Item.query.get(item_id) if not item or item.status == 'removed': return error_response(404, '关联信息不存在') if item.user_id != receiver_id: return error_response(400, '接收者不是该信息的发布者') message = Message( content=content, sender_id=current_user.id, receiver_id=receiver_id, item_id=item_id ) db.session.add(message) db.session.commit() return success_response(message.to_dict(), '消息发送成功') @messages_bp.route('/unread-count', methods=['GET']) @login_required def get_unread_count(current_user): count = Message.query.filter( Message.receiver_id == current_user.id, Message.is_read == False ).count() return success_response({'count': count}) 发送时校验:不能给自己发、关联信息必须存在且未下架、接收者必须是该信息的发布者。通过后写入 Message,消息中心即可按收发关系列表展示。unread-count 统计当前用户未读收件数,供导航栏提示;同模块还有标记已读等接口,配合前端完成“联系 → 查看 → 已读”的完整链路。(7)管理端接口admin 蓝图只对管理员开放,用于处理违规账号与违规信息:查看用户列表并启用/禁用账号,查看全部信息并上架/下架。# anima-backend/app/api/admin.py @admin_bp.route('/users/<int:user_id>/status', methods=['PUT']) @admin_required def update_user_status(current_user, user_id): if user_id == current_user.id: return error_response(400, '不能禁用自己的账号') # status 仅允许 active / disabled user.status = new_status db.session.commit() return success_response(user.to_dict(), '操作成功') @admin_bp.route('/items/<int:item_id>/status', methods=['PUT']) @admin_required def update_item_status(current_user, item_id): # 管理员可将信息设为 active 或 removed(下架) item.status = new_status db.session.commit() return success_response(item.to_dict(), '操作成功') 两个接口都使用 @admin_required,非管理员无法调用。禁用用户时禁止操作自己,避免管理员误锁自身;信息状态只允许在“进行中 / 已下架”之间切换,下架后前台列表不再展示。同文件还提供用户与信息的分页查询,支撑管理页表格展示。(8)前端请求封装与用户状态前端要调用上述 API,需要在每次请求自动带上登录 Token,并在页面间共享用户身份。request.js 与 Pinia 的 user store 分别承担这两项功能;Vite 代理则让开发环境下前端能无跨域地访问后端。请求层用 Axios 拦截器统一处理鉴权头与登录失效:// anima-frontend/src/api/request.js const api = axios.create({ baseURL: '/api', timeout: 10000 }) api.interceptors.request.use(config => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } return config }) api.interceptors.response.use( response => response, error => { if (error.response && error.response.status === 401) { useUserStore().logout() router.push({ name: 'Login', query: { redirect: router.currentRoute.value.fullPath } }) } return Promise.reject(error) } ) 发出请求前,若本地已有 Token,则写入 Authorization: Bearer ...,后端 login_required 才能识别用户;若返回 401,说明登录已失效,则清空本地状态并跳转登录页,同时带上当前路径以便登录后返回。用户状态由 Pinia store 集中管理,并同步到 localStorage,刷新页面后仍保持登录:// anima-frontend/src/stores/user.js export const useUserStore = defineStore('user', () => { const token = ref(localStorage.getItem('token') || '') const user = ref(JSON.parse(localStorage.getItem('user') || 'null')) const isLoggedIn = computed(() => !!token.value) const isAdmin = computed(() => user.value?.role === '管理员') async function login(credentials) { const res = await authApi.login(credentials) if (res.data.code === 0) { token.value = res.data.data.token user.value = res.data.data.user localStorage.setItem('token', res.data.data.token) localStorage.setItem('user', JSON.stringify(res.data.data.user)) } return res } // register / logout / fetchProfile 同理 }) login / register 成功后同时更新内存与本地存储;isLoggedIn、isAdmin 供路由守卫和导航栏判断是否展示管理入口。logout 则清除两端数据。开发时,Vite 把 /api、/static 代理到后端 5000 端口,浏览器只访问 3000,即可完成前后端联调:// anima-frontend/vite.config.js server: { port: 3000, proxy: { '/api': { target: 'http://localhost:5000', changeOrigin: true }, '/static': { target: 'http://localhost:5000', changeOrigin: true } } } (9)前端路由守卫与首页路由模块决定用户能打开哪些页面,保护需登录/管理员的功能;首页为失物招领信息的主入口。前者保护需登录/管理员的功能页,把后端列表接口的数据呈现为可搜索、可翻页的卡片列表。路由守卫在每次跳转前检查权限:// anima-frontend/src/router/index.js router.beforeEach((to, from, next) => { const userStore = useUserStore() if (to.meta.requiresAuth && !userStore.isLoggedIn) { next({ name: 'Login', query: { redirect: to.fullPath } }) } else if (to.meta.requiresAdmin && !userStore.isAdmin) { next({ name: 'Home' }) } else { next() } }) 发布、编辑、我的发布、个人中心、消息等路由配置了 meta.requiresAuth,未登录会跳到登录页并记录原目标地址;管理端路由额外配置 requiresAdmin,非管理员会被拦回首页,避免前端直接进入无权页面。首页脚本负责拉取并刷新信息列表:// anima-frontend/src/views/HomeView.vue(脚本核心) async function loadItems() { const params = { page: page.value, per_page: 20, ...filters.value } const res = await itemsApi.getItems(params) if (res.data.code === 0) { items.value = res.data.data.items totalPages.value = res.data.data.pages } } function handleSearch(searchFilters) { filters.value = searchFilters page.value = 1 loadItems() } 页面挂载时调用 loadItems 请求 /api/items;用户在 SearchBar 中筛选时,handleSearch 更新条件并回到第 1 页再请求;分页组件切换页码时同样调用 loadItems。返回的 items 交给 ItemCard 渲染。至此,后端列表能力与前端浏览体验对接完成,构成平台最核心的使用路径:搜索筛选 → 浏览卡片 → 进入详情 → 联系或管理。3.7 开发总结本案例基于华为开发者空间云开发环境和华为云码道(CodeArts)代码智能体,完成了一个智能化失物招领平台的设计与开发。整个开发过程采用规范驱动开发(Spec-Driven Development,SDD)模式,通过需求规格文档、技术设计文档和编码任务文档逐步明确系统目标、架构设计和实现任务,实现了从需求分析到代码生成的流程化开发。在开发过程中,利用云开发环境提供统一稳定的运行环境,避免了本地环境配置和依赖管理带来的问题;通过 CodeArts Agent 对需求进行理解和拆解,自动生成项目 SDD,并依据 SDD 完成前后端代码构建。开发过程中结合实际运行测试结果,通过持续反馈与智能体协作,对系统功能、界面交互和代码结构进行优化,最终实现了用户管理、失物招领信息管理、搜索筛选、图片上传、站内消息等完整业务功能。通过本案例可以看到,AI Agent 驱动的软件开发模式能够有效降低应用开发门槛,提高原型构建和功能迭代效率。同时,基于 SDD 的开发方式使需求、设计与代码之间保持关联,增强了开发过程的可追溯性,为后续维护、扩展以及团队协作提供了良好的基础。四、释放资源4.1 关闭华为开发者空间云开发环境(容器)案例完成后,如无需继续使用该开发环境,可关闭对应云开发环境,避免持续占用开发资源。VS Code 中左侧导航栏选择”开发者空间,选中容器,点击“关机”按钮,弹框点击“确认”即可关闭容器。五、扩展资料说明华为云码道CodeArts实战速成:cid:link_1华为云开发者AI训练营:cid:link_2
  • 基于华为云码道(CodeArts)代码智能体的失物招领平台开发
    一、概述1.1 案例介绍随着校园、社区及公共场所人员流动增加,物品遗失与寻找需求日益普遍。传统失物招领方式通常依赖线下公告、微信群等渠道,存在信息分散、检索困难、匹配效率低等问题。为提升失物信息流转效率,本案例设计并开发了一个智能化失物招领平台,实现失物信息与招领信息的集中管理和便捷查询。本案例基于 AI Agent 自动化开发模式,利用华为开发者空间开发平台和华为云码道(CodeArts)代码智能体完成全流程应用构建。应用采用前后端分离架构,选择采用 Vue3 前端、Flask 后端、SQLite 数据库的技术栈进行开发。系统支持用户注册登录、失物信息发布、招领信息发布、物品图片上传、关键词搜索、分类筛选、详情查看、站内联系以及个人信息管理等核心功能,同时提供信息状态管理能力,帮助用户完成从信息发布到物品找回的完整业务流程。本案例构建的项目和文档已保存至GitCode,可自行下载源码。Demo演示视频技术选型:华为开发者空间开发平台 - 云开发环境(容器):华为云提供的一站式云上开发环境,支持多种主流开发语言和运行环境,开发者无需进行复杂的本地环境配置即可快速开展应用开发。本案例中选用云开发环境(容器)作为应用的开发与运行环境,为项目提供统一稳定的基础环境,避免因操作系统、依赖版本等环境差异导致的应用运行问题,提高项目构建和部署效率。华为云码道(CodeArts)代码智能体:华为云提供的智能化软件开发工具,融合代码大模型、AI IDE 和 Code Agent 等能力,能够理解项目需求并辅助完成代码生成、代码优化和工程构建。本案例中使用华为云码道(CodeArts)代码智能体作为核心开发辅助工具,根据失物招领应用需求完成前后端功能开发,加速 Web 应用构建过程,降低应用开发门槛。1.2 适用对象企业开发者个人开发者高校学生1.3 案例时间本案例总时长预计60分钟。1.4 案例流程说明:VSCode安装开发者空间插件,远程连接云开发环境(容器);云开发环境(容器)搭建华为云码道(CodeArts)代码智能体;对话码道,开发SDD并检验是否符合项目预期;对话码道,编写代码生成项目;启动项目,验证项目功能;对话码道,更新优化SDD,沉淀项目知识库。1.5 资源总览本案例预计花费0元。资源名称规格单价(元)开发者空间云开发环境ARM | 2vCPUs 4GB | All In One 开发环境免费华为云码道(CodeArts)代码智能体体验版免费二、环境和资源准备2.1 VSCode 远程连接云开发环境参考案例《华为开发者空间 - 云开发环境(容器)IDE插件远程连接操作指导》中的“二、 云开发环境IDE插件远程连接操作指导”章节,完成 VS Code 到云开发环境(容器)的远程连接。2.2 VSCode 搭建码道智能体VS Code 连接云开发环境的状态下,在“扩展”中搜索“CodeArts”,安装“CodeArts Agent”插件。安装完成后,VS Code中执行命令:CodeArts Agent: Open the Q&A Dialog右侧弹出的侧栏为码道智能体对话界面。登录后进行对话测试,确认码道智能体搭建成功且能够正常使用。三、使用码道构建失物招领平台系统3.1 需求分析与功能设计项目名称: Anima - 失物招领平台项目定位:现代化失物招领平台,支持用户发布失物和招领信息、浏览与搜索信息、联系发布者,并具备完整的用户管理能力。技术栈:前端:Vue3 + Vite + TypeScript后端:Python Flask数据库:SQLite核心功能:用户登录和信息管理用户发布失物信息和招领信息,可对自己发布的信息进行编辑、删除、修改完成状态展示最新发布的信息,可分类浏览,可按条件检索查看物品详情并联系发布者管理员管理用户和信息3.2 Prompt 设计和 SDD 生成(1)根据以上项目需求,设计如下 Prompt:根据以下需求描述,开发一个失物招领 Web 应用。 1. 项目概述 项目名称:Anima 项目定位:现代化的失物招领平台,支持用户发布失物和招领信息、浏览与搜索信息、联系发布者,并具备完整的用户管理能力。 2. 技术框架 技术栈:实现前后端分离,前端采用Vue3,后端采用Python Flask,数据库使用SQLite。具备良好的可维护性、可扩展性和规范的项目结构,API 设计遵循 RESTful 风格。 3. 核心功能 (1)用户注册、登录、退出及个人信息管理。 (2)发布失物信息和招领信息,并支持上传多张图片。 (3)首页展示最新发布的信息,可按失物/招领分类浏览。 (4)支持按物品名称、分类、地点、时间等条件进行搜索和筛选。 (5)查看物品详情,并提供联系发布者的方式(站内联系即可,无需真实短信或邮件)。 (6)用户可管理自己发布的信息,包括编辑、删除、修改状态(已找到/已认领)。 (7)管理员具备用户管理、信息审核(可选)及违规内容处理能力。 基本业务流程:注册登录后即可发布失物或招领信息;其他用户可浏览、搜索、查看详情,并通过站内方式联系发布者。当物品成功找回后,发布者可以将信息标记为已完成,已完成的信息仍可保留展示但默认降低排序。 4. 设计风格 整体采用现代、简洁、清爽的设计风格,界面注重易用性与响应式布局,能够适配桌面端与移动端浏览器,保证良好的用户体验。在码道对话界面选择 规范驱动模式(Spec-Driven Mode),发送上述请求。(2)码道首先调用 creating-sdd-directory skill 创建项目目录结构。(3)之后码道根据需求描述,生成需求规格文档 spec.md。如有需要可对需求规格文档进行修改。(4)确认后,码道继续生成技术设计文档 design.md,完成生成后可按需修改。(5)确认后,码道继续生成编码任务文档 task.md,完成生成后可按需修改。(6)检查需求规格文档、技术设计文档、编码任务文档是否与预期一致,如有需要调整的地方可直接进行修改。3.3 编码任务执行确认 SSD 无误后,在码道界面发送请求:遵循spec.md、design.md、tasks.md进行项目开发。码道将根据 SSD 执行编码任务。(1)初始化后端项目结构(2)创建数据模型(3)创建工具函数与权限装饰器(4)创建后端核心接口(5)验证后端构建(6)创建初始化前端项目结构、配置依赖(7)创建路由配置和 API 封装(8)更新 main.js(9)创建全局样式和组件(10)实现前端页面视图(11)验证前端构建(12)集成验证(13)开发完成3.4 环境配置与快速启动开发完成后,可查看如下项目结构:/workspace/ ├── anima-backend/ # 后端 Flask 项目 │ ├── app/ │ │ ├── __init__.py # 应用工厂 + 种子数据 │ │ ├── config.py # 配置文件 │ │ ├── extensions.py # SQLAlchemy/JWT/Migrate/CORS │ │ ├── models/ # 数据模型 │ │ │ ├── user.py # User 模型(密码哈希、to_dict) │ │ │ ├── item.py # Item 模型(图片JSON、状态映射) │ │ │ └── message.py # Message 模型 │ │ ├── api/ # API 蓝图 │ │ │ ├── auth.py # 注册/登录/个人信息/改密 │ │ │ ├── items.py # 信息CRUD/搜索/状态变更 │ │ │ ├── messages.py # 站内消息/已读/未读数 │ │ │ └── admin.py # 用户管理/信息下架 │ │ ├── utils/ # 工具函数 │ │ │ ├── __init__.py # 响应辅助/权限装饰器 │ │ │ └── validators.py # 校验函数 │ │ └── static/uploads/ # 图片上传目录 │ ├── run.py # 启动入口 │ └── requirements.txt │ ├── anima-frontend/ # 前端 Vue3 项目 │ ├── src/ │ │ ├── api/ # API 请求封装 │ │ ├── components/ # 通用组件 │ │ │ ├── AppHeader.vue # 导航栏(响应式+消息未读数) │ │ │ ├── AppFooter.vue │ │ │ ├── ItemCard.vue # 信息卡片 │ │ │ ├── SearchBar.vue # 搜索筛选栏 │ │ │ ├── ImageUpload.vue # 图片上传 │ │ │ └── Pagination.vue # 分页 │ │ ├── views/ # 页面视图(7个) │ │ │ ├── HomeView.vue # 首页 │ │ │ ├── LoginView.vue # 登录 │ │ │ ├── RegisterView.vue# 注册 │ │ │ ├── ItemDetailView.vue # 详情+站内联系 │ │ │ ├── ItemPostView.vue # 发布 │ │ │ ├── ItemEditView.vue # 编辑 │ │ │ ├── MyItemsView.vue # 我的发布 │ │ │ ├── ProfileView.vue # 个人信息 │ │ │ ├── MessagesView.vue # 消息中心 │ │ │ └── admin/ # 管理员页面 │ │ ├── router/index.js # 路由+守卫 │ │ ├── stores/user.js # Pinia 用户状态 │ │ └── assets/main.css # 全局样式 │ └── vite.config.js # Vite 代理配置 │ └── .codeartsdoer/specs/anima/ # SDD 文档 ├── spec.md ├── design.md └── tasks.md安装依赖、启动项目后端:cd anima-backend pip install -r requirements.txt python run.py安装依赖、启动项目前端:cd anima-frontend npm install npm run dev成功启动后,浏览器访问 http://localhost:3000/ 进入应用页面。3.5 项目功能测试进入应用首页,可以看到导航栏、信息筛选卡片以及失物招领信息列表。尝试注册账号并登录,可查看账号个人信息并修改昵称和密码。导航栏点击“发布信息”,填写失物或招领的相关内容,点击“发布”后可以在首页看到已发布的信息。首页点击失物招领信息卡片,进入详情页、查看该条信息的具体内容。对于自己发布的信息,可以点击“编辑”来编辑该信息的内容,或者点击“标记为已找到(标记为已认领)”来修改完成状态。对于他人发布的信息,可在详情页联系发布者。可在导航栏点击“消息”,查看自己收到和发出的消息。对于他人发布的信息,可在详情页联系发布者。可在导航栏点击“消息”,查看自己收到和发出的消息。导航栏点击“我的发布”,查看自己发布的失物招领信息。登录管理员账号,验证用户管理和信息管理功能。3.5 后续开发与优化(1)与码道进行对话,反馈测试应用时发现的问题,例如编辑失物招领信息时添加图片功能出现异常。码道会进行修改、解决这些问题。(2)与码道进行对话,对当前应用进行优化,例如完善控件显示、为关键操作添加二次确认、对页面进行美化等。美化后的应用,首页界面如下。(3)完成开发后,向码道以下指令,更新 SSD。请总结项目,更新SDD,确保下次可以直接通过该SDD复现项目的风格、布局、颜色、功能特性以及前后台业务逻辑等。同步更新 SSD,可以完整记录系统设计与实现过程中的关键决策,提高开发过程的可追溯性,并促进团队协作与知识共享。此外,通过保持文档与代码的一致性,构建了系统化的项目文档体系,为后续维护和功能扩展提供了有力支撑。3.6 核心代码讲解(1)后端应用工厂与蓝图注册anima-backend/app/__init__.py 中的 create_app 是后端唯一启动入口,完成配置加载、扩展初始化、蓝图挂载和数据库初始化,组装成一个可运行的 Flask 应用。# anima-backend/app/__init__.py def create_app(config_class=Config): app = Flask(__name__) app.config.from_object(config_class) # 挂载数据库、JWT、迁移、跨域等扩展 db.init_app(app) jwt.init_app(app) migrate.init_app(app, db) cors.init_app(app, resources={r"/api/*": {"origins": "*"}}) os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) # 提供上传图片的静态访问地址 @app.route('/static/uploads/<path:filename>') def uploaded_file(filename): return send_from_directory(app.config['UPLOAD_FOLDER'], filename) # 按业务注册四类 API app.register_blueprint(auth_bp, url_prefix='/api/auth') app.register_blueprint(items_bp, url_prefix='/api/items') app.register_blueprint(messages_bp, url_prefix='/api/messages') app.register_blueprint(admin_bp, url_prefix='/api/admin') # 首次启动时建表,并写入默认管理员 with app.app_context(): from app import models # noqa: F401 db.create_all() _seed_admin() return app初始化 db、jwt、migrate、cors,分别负责 ORM、登录令牌、数据库迁移与前端跨域调用支持,并开放图片上传目录 /static/uploads/ 供浏览器访问图片。后端按业务划分 /api/auth、/api/items、/api/messages、/api/admin 四个蓝图,分别负责认证、失物招领、站内消息、管理端;后端启动时自动建表,并通过 _seed_admin 写入默认管理员账号(admin / admin123),便于首次体验管理功能。(2)核心数据模型数据模型定义了 User、Item、Message 三类核心实体,分别对应用户、失物/招领信息与站内消息。User 模型:保存账号身份、负责密码安全# anima-backend/app/models/user.py class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(20), unique=True, nullable=False, index=True) email = db.Column(db.String(120), unique=True, nullable=False, index=True) password_hash = db.Column(db.String(256), nullable=False) nickname = db.Column(db.String(30)) role = db.Column(db.String(20), nullable=False, default='user') status = db.Column(db.String(20), nullable=False, default='active') @password.setter def password(self, password): self.password_hash = bcrypt.hashpw( password.encode('utf-8'), bcrypt.gensalt() ).decode('utf-8') def verify_password(self, password): return bcrypt.checkpw( password.encode('utf-8'), self.password_hash.encode('utf-8') ) def to_dict(self): return { 'id': self.id, 'username': self.username, 'email': self.email, 'nickname': self.display_name, 'role': '管理员' if self.role == 'admin' else '普通用户', 'status': self.status, # ... } User 对应注册用户。除了用户名、邮箱、昵称外,用 role 区分普通用户与管理员,用 status 标记账号是否可用。密码不存明文,而是通过 @password.setter 用 bcrypt 生成哈希写入、登录时校验,保证账号安全。to_dict 把数据库记录转成前端可用的 JSON(角色显示为中文);另有 to_public_dict 只返回昵称等公开字段,用于物品详情页展示发布者,避免泄露邮箱等敏感信息。Item 模型:保存失物招领信息# anima-backend/app/models/item.py class Item(db.Model): __tablename__ = 'items' title = db.Column(db.String(50), nullable=False) type = db.Column(db.String(10), nullable=False, index=True) # lost / found description = db.Column(db.Text, nullable=False) category = db.Column(db.String(20), nullable=False, index=True) location = db.Column(db.String(100), nullable=False) event_time = db.Column(db.DateTime, nullable=False) _images = db.Column('images', db.Text) status = db.Column(db.String(20), nullable=False, default='active', index=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) STATUS_DISPLAY = { 'active': '进行中', 'found': '已找到', 'claimed': '已认领', 'removed': '已下架' } @property def images(self): return json.loads(self._images) if self._images else [] @images.setter def images(self, value): self._images = json.dumps(value) if value else None @property def is_completed(self): return self.status in ('found', 'claimed') Item 是平台的核心业务对象,一条记录代表用户发布的一则失物或招领,包含标题、描述、分类、地点、事件时间,以及多张图片和当前状态字段。user_id 指向发布者,从而把信息与用户关联起来。Message 模型:保存围绕某条信息的站内联系记录# anima-backend/app/models/message.py class Message(db.Model): __tablename__ = 'messages' content = db.Column(db.String(500), nullable=False) sender_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) receiver_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) item_id = db.Column(db.Integer, db.ForeignKey('items.id'), nullable=False) is_read = db.Column(db.Boolean, nullable=False, default=False) Message 用于实现“在详情页联系发布者”。每条消息都绑定发送方、接收方和关联的 Item,并记录是否已读,从而支撑消息中心列表与导航栏未读提示。与普通私信不同,这里强制要求 item_id,保证联系必须围绕某条失物/招领展开,消息中心也能据此展示“关于哪条信息的沟通”;is_read 则用于统计未读数,并在用户查看后标记为已读。(3)统一响应与权限装饰器app/utils 提供两套公共能力:统一 JSON 响应格式,以及对接口做登录/管理员校验。所有业务接口都复用它们,避免每个接口各自拼返回体、各自写鉴权代码。# anima-backend/app/utils/__init__.py def success_response(data=None, message='success'): return jsonify({'code': 0, 'message': message, 'data': data or {}}) def error_response(code, message): return jsonify({'code': code, 'message': message, 'data': {}}), \ 400 if code < 500 else 500 def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): verify_jwt_in_request() identity = get_jwt_identity() user = User.query.get(identity) if not user: return error_response(401, '用户不存在') if user.status == 'disabled': return error_response(403, '账号已被禁用') return f(*args, **kwargs, current_user=user) return decorated_function def admin_required(f): # 在登录校验基础上,额外要求 role == 'admin' ... 成功与失败都返回 {code, message, data}:前端只需判断 code === 0 即可处理结果,错误提示取自 message。login_required 装饰在需要登录的接口上:校验请求头中的 JWT,查出对应用户,若账号被禁用则拒绝访问,通过后把 current_user 传给视图函数。admin_required 在此基础上再检查 role == 'admin',专门保护管理端接口。这样鉴权逻辑集中维护,业务代码只需关心业务本身。(4)用户认证接口auth 蓝图负责用户进入系统的全过程,包括创建账号、验证身份、签发登录凭证,以及登录后的个人资料与改密。# anima-backend/app/api/auth.py @auth_bp.route('/register', methods=['POST']) def register(): data = request.get_json() username = data.get('username', '').strip() email = data.get('email', '').strip() password = data.get('password', '') # 校验格式与唯一性后创建用户 user = User(username=username, email=email) user.password = password db.session.add(user) db.session.commit() token = create_access_token(identity=user.id) return success_response({ 'token': token, 'user': user.to_dict() }, '注册成功') @auth_bp.route('/login', methods=['POST']) def login(): username = data.get('username', '').strip() password = data.get('password', '') # 支持用用户名或邮箱登录 user = User.query.filter( (User.username == username) | (User.email == username) ).first() if not user or not user.verify_password(password): return error_response(401, '用户名或密码错误') if user.status == 'disabled': return error_response(403, '账号已被禁用') token = create_access_token(identity=user.id) return success_response({'token': token, 'user': user.to_dict()}, '登录成功') 注册流程为:校验用户名/邮箱/密码格式 → 检查是否已存在 → 创建 User(密码自动哈希)→ 签发 JWT 并连同用户信息返回,注册成功即处于登录态。登录流程为:按用户名或邮箱查找用户 → 校验密码与账号状态 → 同样返回 token 与 user。前端保存该 Token 后,后续请求即可证明身份。同文件还提供需登录才能访问的个人资料读写与修改密码接口。(5)失物招领接口items 蓝图实现平台主业务,包括用户浏览/搜索信息、登录后发布带图信息、对自己发布的信息做编辑删除,以及在找回物品后标记完成。首页列表、详情页、发布页、我的发布都依赖这里的接口。列表查询与排序:口面向所有访客,负责把符合条件的信息分页返回给首页# anima-backend/app/api/items.py @items_bp.route('', methods=['GET']) def get_items(): query = Item.query.filter(Item.status != 'removed') if item_type and item_type in Item.VALID_TYPES: query = query.filter(Item.type == item_type) if keyword: query = query.filter( db.or_(Item.title.contains(keyword), Item.description.contains(keyword)) ) if category and category in Item.VALID_CATEGORIES: query = query.filter(Item.category == category) if location: query = query.filter(Item.location.contains(location)) # 还可按 start_date / end_date 过滤 event_time # 进行中优先,再按创建时间倒序 query = query.order_by( db.case((Item.status == 'active', 0), else_=1), Item.created_at.desc() ) pagination = query.paginate(page=page, per_page=per_page, error_out=False) return success_response({ 'items': [item.to_dict() for item in pagination.items], 'total': pagination.total, 'page': pagination.page, 'pages': pagination.pages }) 查询时,先排除已下架信息,再按类型、关键词、分类、地点、时间等条件叠加过滤;排序上使 active(进行中)排在前面,同优先级再按发布时间倒序,这样首页优先看到仍待匹配的信息。最后分页返回,供前端翻页展示。发布信息与图片上传:要求登录,负责把表单字段和图片落库为一条新 Item@items_bp.route('', methods=['POST']) @login_required def create_item(current_user): # 校验标题、类型、描述、分类、地点、时间等必填项 images = [] files = request.files.getlist('images') for f in files: if f.filename: # 校验格式与大小后,用 UUID 重命名并保存 filename = f'{uuid.uuid4().hex}.{ext}' f.save(os.path.join(current_app.config['UPLOAD_FOLDER'], filename)) images.append(f'/static/uploads/{filename}') item = Item( title=title, type=item_type, description=description, category=category, location=location, event_time=event_time, user_id=current_user.id ) item.images = images db.session.add(item) db.session.commit() return success_response(item.to_dict(), '发布成功') 接口使用 multipart/form-data 同时接收文字与文件:先校验业务字段,再逐张检查图片扩展名与大小,保存到上传目录后把访问路径写入 item.images,最后关联 current_user.id 入库。编辑接口逻辑类似,但会保留前端回传的 existing_images,再追加新上传图片,避免改信息时把原图清空。状态变更:用于发布者在物品找回后结束该条信息@items_bp.route('/<int:item_id>/status', methods=['PUT']) @login_required def update_item_status(current_user, item_id): item = Item.query.get(item_id) if item.user_id != current_user.id: return error_response(403, '无权操作') if item.is_completed: return error_response(400, '已完成的信息不可恢复为进行中') if item.type == 'lost' and new_status == 'found': item.status = 'found' elif item.type == 'found' and new_status == 'claimed': item.status = 'claimed' else: return error_response(400, '无效的状态变更') db.session.commit() return success_response(item.to_dict(), '状态更新成功') 只有发布者本人可操作;失物只能标为“已找到”,招领只能标为“已认领”;一旦完成不可再改回进行中。这样既闭环了“发布 → 找回 → 结束”流程,也避免状态被随意篡改。(6)站内消息接口messages 蓝图实现站内联系,即浏览者在详情页给发布者留言,双方在消息中心查看往来,导航栏展示未读数量。它替代真实短信/邮件,把沟通留在平台内完成。# anima-backend/app/api/messages.py @messages_bp.route('', methods=['POST']) @login_required def send_message(current_user): if receiver_id == current_user.id: return error_response(400, '不能联系自己') item = Item.query.get(item_id) if not item or item.status == 'removed': return error_response(404, '关联信息不存在') if item.user_id != receiver_id: return error_response(400, '接收者不是该信息的发布者') message = Message( content=content, sender_id=current_user.id, receiver_id=receiver_id, item_id=item_id ) db.session.add(message) db.session.commit() return success_response(message.to_dict(), '消息发送成功') @messages_bp.route('/unread-count', methods=['GET']) @login_required def get_unread_count(current_user): count = Message.query.filter( Message.receiver_id == current_user.id, Message.is_read == False ).count() return success_response({'count': count}) 发送时校验:不能给自己发、关联信息必须存在且未下架、接收者必须是该信息的发布者。通过后写入 Message,消息中心即可按收发关系列表展示。unread-count 统计当前用户未读收件数,供导航栏提示;同模块还有标记已读等接口,配合前端完成“联系 → 查看 → 已读”的完整链路。(7)管理端接口admin 蓝图只对管理员开放,用于处理违规账号与违规信息:查看用户列表并启用/禁用账号,查看全部信息并上架/下架。# anima-backend/app/api/admin.py @admin_bp.route('/users/<int:user_id>/status', methods=['PUT']) @admin_required def update_user_status(current_user, user_id): if user_id == current_user.id: return error_response(400, '不能禁用自己的账号') # status 仅允许 active / disabled user.status = new_status db.session.commit() return success_response(user.to_dict(), '操作成功') @admin_bp.route('/items/<int:item_id>/status', methods=['PUT']) @admin_required def update_item_status(current_user, item_id): # 管理员可将信息设为 active 或 removed(下架) item.status = new_status db.session.commit() return success_response(item.to_dict(), '操作成功') 两个接口都使用 @admin_required,非管理员无法调用。禁用用户时禁止操作自己,避免管理员误锁自身;信息状态只允许在“进行中 / 已下架”之间切换,下架后前台列表不再展示。同文件还提供用户与信息的分页查询,支撑管理页表格展示。(8)前端请求封装与用户状态前端要调用上述 API,需要在每次请求自动带上登录 Token,并在页面间共享用户身份。request.js 与 Pinia 的 user store 分别承担这两项功能;Vite 代理则让开发环境下前端能无跨域地访问后端。请求层用 Axios 拦截器统一处理鉴权头与登录失效:// anima-frontend/src/api/request.js const api = axios.create({ baseURL: '/api', timeout: 10000 }) api.interceptors.request.use(config => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } return config }) api.interceptors.response.use( response => response, error => { if (error.response && error.response.status === 401) { useUserStore().logout() router.push({ name: 'Login', query: { redirect: router.currentRoute.value.fullPath } }) } return Promise.reject(error) } ) 发出请求前,若本地已有 Token,则写入 Authorization: Bearer ...,后端 login_required 才能识别用户;若返回 401,说明登录已失效,则清空本地状态并跳转登录页,同时带上当前路径以便登录后返回。用户状态由 Pinia store 集中管理,并同步到 localStorage,刷新页面后仍保持登录:// anima-frontend/src/stores/user.js export const useUserStore = defineStore('user', () => { const token = ref(localStorage.getItem('token') || '') const user = ref(JSON.parse(localStorage.getItem('user') || 'null')) const isLoggedIn = computed(() => !!token.value) const isAdmin = computed(() => user.value?.role === '管理员') async function login(credentials) { const res = await authApi.login(credentials) if (res.data.code === 0) { token.value = res.data.data.token user.value = res.data.data.user localStorage.setItem('token', res.data.data.token) localStorage.setItem('user', JSON.stringify(res.data.data.user)) } return res } // register / logout / fetchProfile 同理 }) login / register 成功后同时更新内存与本地存储;isLoggedIn、isAdmin 供路由守卫和导航栏判断是否展示管理入口。logout 则清除两端数据。开发时,Vite 把 /api、/static 代理到后端 5000 端口,浏览器只访问 3000,即可完成前后端联调:// anima-frontend/vite.config.js server: { port: 3000, proxy: { '/api': { target: 'http://localhost:5000', changeOrigin: true }, '/static': { target: 'http://localhost:5000', changeOrigin: true } } } (9)前端路由守卫与首页路由模块决定用户能打开哪些页面,保护需登录/管理员的功能;首页为失物招领信息的主入口。前者保护需登录/管理员的功能页,把后端列表接口的数据呈现为可搜索、可翻页的卡片列表。路由守卫在每次跳转前检查权限:// anima-frontend/src/router/index.js router.beforeEach((to, from, next) => { const userStore = useUserStore() if (to.meta.requiresAuth && !userStore.isLoggedIn) { next({ name: 'Login', query: { redirect: to.fullPath } }) } else if (to.meta.requiresAdmin && !userStore.isAdmin) { next({ name: 'Home' }) } else { next() } }) 发布、编辑、我的发布、个人中心、消息等路由配置了 meta.requiresAuth,未登录会跳到登录页并记录原目标地址;管理端路由额外配置 requiresAdmin,非管理员会被拦回首页,避免前端直接进入无权页面。首页脚本负责拉取并刷新信息列表:// anima-frontend/src/views/HomeView.vue(脚本核心) async function loadItems() { const params = { page: page.value, per_page: 20, ...filters.value } const res = await itemsApi.getItems(params) if (res.data.code === 0) { items.value = res.data.data.items totalPages.value = res.data.data.pages } } function handleSearch(searchFilters) { filters.value = searchFilters page.value = 1 loadItems() } 页面挂载时调用 loadItems 请求 /api/items;用户在 SearchBar 中筛选时,handleSearch 更新条件并回到第 1 页再请求;分页组件切换页码时同样调用 loadItems。返回的 items 交给 ItemCard 渲染。至此,后端列表能力与前端浏览体验对接完成,构成平台最核心的使用路径:搜索筛选 → 浏览卡片 → 进入详情 → 联系或管理。3.7 开发总结本案例基于华为开发者空间云开发环境和华为云码道(CodeArts)代码智能体,完成了一个智能化失物招领平台的设计与开发。整个开发过程采用规范驱动开发(Spec-Driven Development,SDD)模式,通过需求规格文档、技术设计文档和编码任务文档逐步明确系统目标、架构设计和实现任务,实现了从需求分析到代码生成的流程化开发。在开发过程中,利用云开发环境提供统一稳定的运行环境,避免了本地环境配置和依赖管理带来的问题;通过 CodeArts Agent 对需求进行理解和拆解,自动生成项目 SDD,并依据 SDD 完成前后端代码构建。开发过程中结合实际运行测试结果,通过持续反馈与智能体协作,对系统功能、界面交互和代码结构进行优化,最终实现了用户管理、失物招领信息管理、搜索筛选、图片上传、站内消息等完整业务功能。通过本案例可以看到,AI Agent 驱动的软件开发模式能够有效降低应用开发门槛,提高原型构建和功能迭代效率。同时,基于 SDD 的开发方式使需求、设计与代码之间保持关联,增强了开发过程的可追溯性,为后续维护、扩展以及团队协作提供了良好的基础。四、释放资源4.1 关闭华为开发者空间云开发环境(容器)案例完成后,如无需继续使用该开发环境,可关闭对应云开发环境,避免持续占用开发资源。VS Code 中左侧导航栏选择”开发者空间,选中容器,点击“关机”按钮,弹框点击“确认”即可关闭容器。五、扩展资料说明华为云码道CodeArts实战速成:cid:link_1华为云开发者AI训练营:cid:link_2
  • [高校训练营] 基于华为云 ECS 的微信公众号表情包机器人
    基于华为云 ECS 的微信公众号表情包机器人本文是一份面向华为云论坛/开发者社区的案例分享稿,采用“场景介绍、技术方案、实操步骤、问题排查、经验总结”的写法。案例以 /opt/wechat-bot 项目为基础,演示如何将一个微信公众号表情包保存机器人部署到云服务器,并通过 Docker Compose 管理 Web 回调服务与后台抓取服务。一、案例背景日常使用微信时,很多有趣的表情包会散落在聊天记录或收藏表情里。手动保存、分类和转发这些图片并不方便,尤其是微信公众号接收图片、收藏表情和私信消息时,还会遇到不同消息类型、图片临时地址、登录态失效等问题。本案例希望实现一个轻量级机器人:用户把图片或表情发给公众号后,系统自动保存到服务器目录。如果公众号收到的是“暂不支持的收藏表情”提示,后台任务会记录待处理消息,并尝试从公众号私信页面抓取原图。保存成功后,机器人返回可访问的下载链接。服务部署在云服务器上,使用容器方式运行,便于重启、迁移和排障。从论坛案例分享角度看,这类项目适合作为“云上开发项目分享”或“上云技术实践”:业务场景足够小,但包含云服务器、容器部署、Webhook 回调、文件持久化、后台任务和运行监控等完整工程要素。具体实现请关注公众号:gh_4a17557e4daa为什么适合作为华为云实践案例这类小工具看起来只是一个“保存表情包”的个人需求,但真正跑起来后,会自然牵出一套完整的云上应用流程:需要一台稳定在线的云服务器,保证微信公众号服务器可以随时回调。需要一个公网访问入口,让微信服务器和用户都能访问服务。需要文件持久化,避免容器重建或服务重启后表情包丢失。需要后台任务处理“回调里拿不到原图”的特殊场景。需要日志、状态文件和管理接口,方便定位签名失败、登录过期、下载失败等问题。需要开源前的脱敏处理,避免把公众号密钥、登录态和用户素材上传到仓库。所以它不是一个单纯的 Python 脚本,而是一个很小但比较完整的云上应用雏形。个人开发者可以借这个项目熟悉 ECS、Docker Compose、Webhook、反向代理、持久化目录和基础运维排障;企业内部的小工具也可以按类似方式快速上云。本案例的实现边界为了让案例更容易复现,本项目没有引入复杂的消息队列、数据库和对象存储,主要使用本地文件保存运行状态:pending_unsupported.jsonl 作为待处理消息队列。mp_state.json 保存公众号后台登录态。mp_scraper_state.json 记录后台抓取历史和去重信息。mp_scraper_status.json 保存 scraper 最近一次运行状态。stickers/ 目录保存下载后的图片和表情文件。这种设计适合个人项目、小流量场景和原型验证。如果后续用户量增加,可以再把文件队列替换为 Redis、SQLite、RDS 或云原生消息服务,把本地素材迁移到 OBS 对象存储。二、方案选型1. 云上资源本案例建议使用华为云弹性云服务器 ECS 承载应用。ECS 的定位是提供可按需创建和管理的云服务器,适合部署 Web 应用、后台任务、自动化脚本等轻量服务。建议资源配置如下:模块建议配置说明服务器华为云 ECS,Linux 系统用于运行 Flask 服务和后台抓取任务公网访问弹性公网 IP + 域名微信公众号回调需要公网可访问地址安全组放通 80/443,按需放通 5000生产环境建议由 Nginx/Caddy 反代到 5000存储目录/opt/wechat-bot/stickers保存用户发送的图片或表情包部署方式Docker Compose同时管理 Web 服务与后台 scraper 服务2. 应用架构项目当前由两个核心进程组成:服务入口作用webapp.py提供微信公众号服务器校验、消息接收、表情保存、管理状态接口scrapermp_private_scraper.py watch监听待处理的收藏表情消息,登录公众号后台并尝试抓取原图整体链路如下:微信用户 -> 发送图片/表情到公众号 -> 微信服务器回调 /wechat -> Flask 校验签名并解析 XML -> 图片下载到 /opt/wechat-bot/stickers -> 返回下载链接 收藏表情无法直接解析时: 微信回调 -> 写入 pending_unsupported.jsonl -> scraper 后台轮询公众号私信 -> 抓取图片并保存 -> 更新状态文件3. 项目目录说明部署目录建议固定为 /opt/wechat-bot,这样 Docker、systemd 和运维脚本都可以使用稳定路径。当前项目中比较关键的文件如下:文件/目录是否建议提交仓库作用app.py是Flask Web 服务入口,处理微信公众号校验、回调、图片保存和管理接口mp_private_scraper.py是后台抓取服务,用 Playwright 处理公众号私信页面里的收藏表情docker-compose.yml是同时编排 web 和 scraper 两个服务Dockerfile是基于 Playwright Python 镜像构建运行环境.env.example是环境变量模板,只放占位值,不放真实密钥.gitignore是防止密钥、登录态、图片素材和缓存文件被提交README-deploy.md是简版部署说明huaweicloud-wechat-bot-case-sharing.md是本案例分享文档.env否本地真实环境变量,包含 Token、AppID、密钥等private.env否私有配置文件,不适合进入仓库access_token_cache.json否微信 access token 缓存mp_state.json否公众号后台登录态mp_login_qr.png否后台登录二维码截图mp_private_last.png否后台页面调试截图stickers/*否用户发送或抓取到的实际表情素材开源仓库里只保留代码、模板和说明文档,运行态数据留在服务器本地。这样既方便别人复现,也不会泄露真实公众号配置和用户文件。三、核心实现拆解1. 微信回调校验微信公众号服务器配置需要校验 signature、timestamp、nonce 和 echostr。项目中的 check_sig() 会按微信规则对 token、timestamp、nonce 排序后计算 SHA1,再和微信传入的签名做安全比较。关键点:Token 必须和公众号后台配置一致。回调路径建议使用 /wechat。若启用安全模式,需要同时配置 WX_APPID 和 WX_AES_KEY。示例环境变量:WX_TOKEN=替换为公众号后台Token WX_APPID=替换为公众号AppID WX_AES_KEY=替换为EncodingAESKey BASE_URL=https://example.com/stickers ADMIN_TOKEN=替换为管理接口Token STICKER_DIR=/opt/wechat-bot/stickers PENDING_FILE=/opt/wechat-bot/pending_unsupported.jsonl MP_QR_FILE=/opt/wechat-bot/mp_login_qr.png MP_STATUS_FILE=/opt/wechat-bot/mp_scraper_status.json2. 图片和表情保存当消息类型是 image 或 emoji 时,服务会读取图片地址并下载到 STICKER_DIR。保存时根据响应头或内容类型识别扩展名,常见格式包括 JPG、PNG、GIF、WEBP。返回给用户的是一个公开访问链接,链接前缀来自:BASE_URL + "/" + 文件名这里需要注意两点:BASE_URL 必须是外网可访问地址,否则用户收到链接后无法下载。stickers 目录需要做持久化挂载,避免容器重建后文件丢失。3. 收藏表情的异步处理部分收藏表情并不会直接以图片消息形式进入公众号回调,可能只收到“不支持的消息类型”文本。项目的处理方式是:将这类消息写入 pending_unsupported.jsonl。scraper 服务周期性读取待处理消息。通过 Playwright 登录公众号后台私信页面。从私信 DOM 和图片请求中识别候选图片。下载成功后写入状态文件,方便排查。这个设计的优点是不会阻塞公众号回调。即使后台抓取需要等待登录态、页面加载或重试,Web 服务仍然可以快速响应微信服务器。4. 文件持久化和去重思路容器化部署最容易踩的坑之一是“文件写进了容器内部,重建后就没了”。本项目把所有运行状态都挂载到宿主机目录:./stickers:/opt/wechat-bot/stickers./mp_state.json:/opt/wechat-bot/mp_state.json./mp_scraper_state.json:/opt/wechat-bot/mp_scraper_state.json./mp_scraper_status.json:/opt/wechat-bot/mp_scraper_status.json./pending_unsupported.jsonl:/opt/wechat-bot/pending_unsupported.jsonl./access_token_cache.json:/opt/wechat-bot/access_token_cache.json这样容器只是运行环境,真实数据在宿主机。后续如果要做备份,也可以直接备份 /opt/wechat-bot 下的状态文件和 stickers 目录。后台抓取时,图片来源地址会被转换成相对稳定的 source key。这个 key 可以用来判断同一个私信图片是否已经处理过,避免 scraper 每轮轮询都重复下载同一张图片。对于个人项目来说,这种轻量去重比引入数据库更直接。5. 管理接口与运行状态项目提供了两个简单管理接口:接口作用/admin/mp-qr查看公众号后台登录二维码截图/admin/mp-status查看 scraper 最近状态和二维码是否存在这两个接口都依赖 ADMIN_TOKEN,没有正确 token 时直接返回空结果或 404,避免管理信息暴露在公网。状态文件的价值在于排障时能快速回答几个问题:scraper 是否还在运行?最近一次轮询是什么时间?是否需要重新扫码登录公众号后台?待处理消息有没有被消费?图片下载失败是网络问题、页面结构问题,还是消息本身没有可抓取图片?小项目不一定一开始就接入完整监控,但至少要留下“能看见服务状态”的入口。这个项目用状态 JSON 文件和 Docker 日志解决了第一阶段的可观测性问题。四、部署步骤1. 华为云侧准备在华为云控制台准备 ECS 时,可以按下面顺序处理:创建一台 Linux ECS,系统可选择 Ubuntu、Debian、CentOS 或 Huawei Cloud EulerOS。绑定弹性公网 IP,确保微信服务器可以访问到回调地址。配置安全组,至少放通 80、443,测试阶段可临时放通 5000。如果使用域名,把域名解析到 ECS 的公网 IP。安装 Docker 和 Docker Compose。准备 HTTPS 反向代理。微信公众号正式回调建议使用 HTTPS 域名。实际生产环境建议用 Nginx、Caddy 或其他网关把公网请求转发到本机 5000 端口。这样 Flask 服务只负责业务逻辑,TLS 证书、域名转发、访问日志都交给反向代理处理。一个典型的访问链路是:微信服务器 -> https://你的域名/wechat -> ECS 安全组 443 端口 -> Nginx/Caddy -> 127.0.0.1:5000/wechat -> Flask 应用如果只是临时联调,也可以先通过 http://公网IP:5000/wechat 验证逻辑,但长期使用建议切到 HTTPS 域名。2. 准备服务器目录登录 ECS 后创建项目目录:mkdir -p /opt/wechat-bot/stickers /opt/wechat-bot/verify cd /opt/wechat-bot将代码放入该目录后,复制环境变量模板:cp .env.example .env然后编辑 .env,至少配置:WX_TOKENWX_APPIDWX_AES_KEYBASE_URLADMIN_TOKEN生产环境不要把真实 .env、private.env、登录态文件和 access token 缓存提交到代码仓库。为了减少遗漏,可以先创建运行态文件:touch pending_unsupported.jsonl touch mp_state.json mp_scraper_state.json mp_scraper_status.json access_token_cache.json如果这些文件不存在,Docker Compose 在某些环境下可能会把挂载目标当成目录处理,后续读写就会比较别扭。提前创建空文件更稳。3. 启动容器项目已经提供 docker-compose.yml,可以直接构建并启动:docker-compose build docker-compose up -d 启动后查看服务状态:docker-compose ps docker-compose logs -f web docker-compose logs -f scraperWeb 服务默认监听容器内 5000 端口,并映射到宿主机:ports: - "5000:5000" 如果生产环境使用 Nginx 或 Caddy,建议将公网 HTTPS 流量反向代理到 127.0.0.1:5000。启动后可以先做一个最简单的健康检查:curl http://127.0.0.1:5000/如果返回:wechat-bot ok说明 Web 容器已经可以正常响应。4. 配置微信公众号服务器地址在公众号后台配置服务器地址:https://你的域名/wechat同时填入:Token:对应 .env 中的 WX_TOKENEncodingAESKey:对应 .env 中的 WX_AES_KEY消息加解密方式:按实际需求选择明文、兼容或安全模式保存配置时,可以观察 web 服务日志。如果签名校验通过,会看到类似:VERIFY OK5. 登录公众号后台供 scraper 使用后台抓取服务依赖公众号后台登录态。项目提供了管理接口查看二维码和状态:GET /admin/mp-qr?token=你的ADMIN_TOKEN GET /admin/mp-status?token=你的ADMIN_TOKEN如果 scraper 提示需要登录,可以访问二维码接口,用管理员微信扫码。登录成功后,状态会写入:/opt/wechat-bot/mp_state.json /opt/wechat-bot/mp_scraper_status.json这些文件同样需要持久化保存。6. 联调测试完成部署后,建议按下面顺序验证:测试项操作预期结果Web 健康检查访问 /返回 wechat-bot ok微信服务器校验在公众号后台保存服务器配置日志出现 VERIFY OK普通文本消息给公众号发送文字返回使用说明图片消息给公众号发送图片返回 表情包已保存 和下载链接图片访问打开返回的链接能直接看到图片收藏表情发送收藏表情进入待处理流程或返回抓取说明管理二维码访问 /admin/mp-qr?token=...能看到登录截图或未准备提示scraper 状态访问 /admin/mp-status?token=...返回 JSON 状态联调时重点看两个日志:docker-compose logs -f web docker-compose logs -f scraperweb 日志用于确认微信回调是否正常进入、签名是否通过、消息类型是否识别正确;scraper 日志用于确认后台登录态、私信页面访问、候选图片识别和图片下载情况。五、常见问题与处理1. 微信公众号校验失败现象:VERIFY FAIL POST VERIFY FAIL排查方向:检查公众号后台 Token 是否和 .env 的 WX_TOKEN 一致。检查公网 URL 是否真实访问到当前服务。检查反向代理是否转发了 query string。如果启用安全模式,确认 WX_APPID、WX_AES_KEY 是否完整。2. 用户收到链接但打不开排查方向:BASE_URL 是否设置为公网域名,而不是内网地址。/stickers/<filename> 是否能直接访问。安全组、防火墙、反向代理是否放通对应路径。容器内保存路径和宿主机挂载路径是否一致。3. 收藏表情没有抓取成功排查方向:pending_unsupported.jsonl 是否有新增记录。scraper 容器日志是否提示登录过期。/admin/mp-qr 是否能看到最新二维码截图。mp_scraper_status.json 中的 updated_at 是否持续更新。公众号后台页面结构可能变化,DOM 识别逻辑需要随页面调整。4. 容器重启后文件丢失需要确认 docker-compose.yml 中已经挂载持久化目录和状态文件:volumes: - ./stickers:/opt/wechat-bot/stickers - ./mp_state.json:/opt/wechat-bot/mp_state.json - ./mp_scraper_state.json:/opt/wechat-bot/mp_scraper_state.json - ./mp_scraper_status.json:/opt/wechat-bot/mp_scraper_status.json - ./pending_unsupported.jsonl:/opt/wechat-bot/pending_unsupported.jsonl六、实践效果完成部署后,用户侧的体验比较直接:给公众号发送图片或表情。机器人自动保存文件。公众号回复下载链接。对于收藏表情,后台服务异步尝试抓取,不影响主回调。工程侧的收益也比较明显:Web 回调和后台任务拆成两个容器,职责清晰。状态文件、待处理队列和图片目录都落在宿主机,便于备份。通过 Docker Compose 管理启动顺序、重启策略和挂载目录,迁移成本较低。日志集中在 docker-compose logs 中,排障路径简单。从开发者实践角度看,项目最大的收获是把“能跑的脚本”改造成了“能长期运行的服务”。这个过程里最重要的并不是代码写了多少,而是把运行条件、配置方式、异常处理、数据持久化和安全边界都补齐。1. 使用体验普通用户不需要理解部署细节,只要把表情或图片发给公众号,就能得到一个下载链接。对于无法直接解析的收藏表情,系统也不会直接失败,而是提示后台正在尝试抓取。这种交互虽然简单,但背后把同步回调和异步补偿拆开了:同步链路负责快速响应微信服务器,避免超时。异步链路负责处理不稳定、耗时长、需要登录态的抓取动作。文件链接统一通过 BASE_URL 暴露,用户拿到的是稳定访问地址。2. 运维体验部署后,日常维护主要关注四类信息:关注点查看方式Web 服务是否在线访问 / 或查看 docker-compose ps微信回调是否正常查看 docker-compose logs -f web后台抓取是否运行查看 docker-compose logs -f scraper登录态是否过期查看 /admin/mp-status?token=... 或二维码截图这套排障路径比较短,适合个人项目。遇到问题时,一般从 Web 日志、scraper 日志、状态 JSON 文件和实际保存目录四个地方就能定位。3. 云上部署收益部署在 ECS 上之后,项目获得了几个本地环境很难稳定提供的能力:公网入口稳定在线,微信公众号服务器可以持续回调。服务可以 7x24 小时运行,不依赖个人电脑开机。容器重启策略能处理部分异常退出。目录挂载让运行态数据和容器生命周期解耦。后续可以继续接入 OBS、云监控、云日志、负载均衡等云服务。这个案例虽然规模小,但应用结构和很多实际业务服务是一致的:入口服务接收外部请求,后台 worker 做异步任务,运行数据持久化,日志用于排障,敏感配置通过环境变量管理。七、安全与开源处理把项目上传到公开仓库前,最容易忽略的是运行态文件。微信公众号项目里常见的敏感信息不只包括 .env,还包括登录二维码、后台登录态、access token 缓存、用户上传素材和调试截图。本项目开源前做了几类处理:类型示例处理方式环境变量.env、private.env不提交,只提交 .env.example公众号密钥WX_TOKEN、WX_APPID、WX_AES_KEY、WX_APPSECRET示例文件中改成占位值管理密钥ADMIN_TOKEN仅通过本地环境变量配置登录态mp_state.json加入 .gitignoretoken 缓存access_token_cache.json加入 .gitignore二维码/截图mp_login_qr.png、mp_private_last.png加入 .gitignore用户素材stickers/*目录保留,实际文件忽略缓存目录venv/、__pycache__/不进入仓库.gitignore 中保留了 stickers/.gitkeep 和 verify/.gitkeep,这样仓库里能看到需要的目录结构,但不会上传真实表情包或验证文件。同时,代码里的默认配置也要避免写真实值。例如:app.py 不内置真实 Token 和 AppID。mp_private_scraper.py 不内置个人域名。snapshot_private.py 不内置公众号后台 token URL。wechat-mp-scraper.service 通过 EnvironmentFile 读取私有配置。这一点很重要。很多项目并不是 .env 泄露,而是开发过程中随手写进脚本、服务文件、调试 URL 的 token 被一起提交了。开源前用 rg 扫一遍关键字,是一个很值得保留的小习惯。示例扫描命令:rg -n "(token=|APPSECRET|ADMIN_TOKEN|WX_TOKEN|access_token|private.env|mp_state)" . 扫描结果不一定都代表泄露,但可以帮助快速发现“不该出现在仓库里的真实运行信息”。八、可优化方向后续可以继续补强以下能力:将 pending_unsupported.jsonl 替换为 SQLite 或 Redis,增强并发和状态管理。为 /admin/* 接口增加 IP 白名单或更严格的鉴权策略。接入 OBS 对象存储,把表情文件从本地磁盘迁移到云上存储。增加 Prometheus 或轻量健康检查,监控回调成功率、下载失败率和 scraper 登录状态。在部署层增加 HTTPS 自动证书续期,降低公众号回调配置维护成本。对重复图片做 hash 去重,避免长期运行后磁盘膨胀。如果从华为云产品组合角度继续扩展,可以按下面路线演进:阶段当前方案可升级方向文件存储ECS 本地 stickers/OBS 对象存储,提供更稳定的静态文件访问状态管理JSON 文件Redis、SQLite、RDS 或云数据库日志查看Docker Compose 日志云日志服务,统一检索和告警可用性单台 ECS镜像化部署、多实例、负载均衡域名证书手动配置自动证书续期和统一入口网关安全防护ADMIN_TOKENIP 白名单、WAF、访问审计对于当前个人项目来说,不需要一开始就把这些都接上。更合理的路径是先保证主流程跑通,再根据使用频率和故障点逐步升级。云上资源的好处就在于弹性比较强:小项目可以轻量起步,后续再平滑补能力。九、论坛发布建议如果把这篇内容发布到华为云论坛,可以再补几张截图,让案例更直观:截图展示重点ECS 控制台实例截图展示项目部署在云服务器上安全组规则截图展示 80/443 或测试端口放通公众号服务器配置截图展示 /wechat 回调地址Docker Compose 运行截图展示 web 和 scraper 两个容器Web 日志截图展示 VERIFY OK 或消息回调日志scraper 状态截图展示后台抓取服务运行状态微信对话截图展示用户发送表情和机器人返回链接Git 仓库截图展示已开源的项目结构和脱敏处理推荐标题:〖案例共创〗基于华为云 ECS 的微信公众号表情包机器人部署实践正文结构可以保持本文这种顺序:先讲为什么做这个项目。再讲整体架构和华为云资源。然后给部署步骤和核心代码思路。接着写遇到的问题和排查方法。最后补实践效果、优化方向和仓库链接。这样读者能先理解需求,再跟着复现,最后也能看到项目还可以怎么继续演进。十、总结这个案例的核心不是复杂算法,而是把一个真实的小需求做成稳定的云上服务:公网回调、消息校验、文件保存、后台补偿、容器部署和日志排障都具备了。对于个人开发者或小团队来说,这类项目很适合用来练习“从脚本到服务”的完整上云流程。项目当前仍然是一个轻量版本,但已经包含了一个云上应用的基本骨架:ECS 提供持续在线的运行环境。Flask 负责微信公众号 Webhook。Docker Compose 负责编排服务。Playwright 负责处理公众号私信页面里的补偿抓取。本地挂载目录保存图片和运行状态。.gitignore 和 .env.example 保证开源时不带隐私数据。后续如果继续完善,可以优先做三件事:第一,把图片文件迁移到 OBS;第二,把运行状态从 JSON 文件升级为更可靠的存储;第三,增加健康检查和告警,让服务出问题时能主动发现。从一个小需求出发,最终沉淀出一个可部署、可维护、可开源的云上小工具,这就是本案例最有价值的地方。十一、参考资料华为云论坛:cid:link_1华为云案例共创活动说明:cid:link_2华为云 ECS 最佳实践汇总:cid:link_0本地项目部署说明:/opt/wechat-bot/README-deploy.md本地项目入口:/opt/wechat-bot/app.py本地后台抓取服务:/opt/wechat-bot/mp_private_scraper.py项目仓库:cid:link_3
  • [问题求助] Cannot connect to API: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the
    Cannot connect to API: The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()这个问题怎么解决?
  • [交流吐槽] 有时候模型会误判复读
    如题,有时候上下文里重复出现的文本(比如长路径、上下文代码中原有的重复)会被模型误判为复读,然后又会重新来一遍,再次陷入复读,如果不干预的话就是死循环,浪费很多算力。比如遇到一种情况,模型总结12345,到5的时候误判复读,于是又从1开始,到5的时候又误判……这个问题可以从几个维度优化:1. 判断复读的策略,可能存在优化的空间2. 识别到复读后的行为,尽可能从开始复读之前就近开始重新输出
  • [高校训练营] 码道暑期实习训练营 | Merchive个人藏品管理系统应用构建案例
    Merchive个人藏品管理系统 - 应用构建案例文档一、概述1.1 案例介绍Merhive是一个专为影视周边/谷子收藏爱好者设计的个人藏品管理系统。项目(含演示视频)地址:https://gitcode.com/Christine_wxh/Merchive问题提出影视周边收藏爱好者普遍面临以下问题:藏品管理混乱:购买的大量周边(吧唧、立牌、色纸等)难以系统记录,容易遗忘或重复购买信息分散:藏品信息散落在各个平台、相册、备忘录中,查找困难缺乏统计:不知道总共花了多少钱、各类别占比如何、购入趋势如何纪念日遗忘:忘记藏品购入周年等有意义的日期隐私担忧:使用公共平台记录藏品担心隐私泄露Merhive提供一站式解决方案:本地化部署、数据完全自主掌控、多维度管理、趣味化交互。核心亮点:完整藏品管理:支持多图上传、自定义裁剪、多维度筛选数据可视化:ECharts统计图表数据安全:JWT认证、用户数据隔离、数据库备份隐私保护:每个部署者使用独立数据库,数据完全隔离1.2 适用对象个人开发者影视周边收藏爱好者1.3 案例时间本案例总时长预计60分钟(含环境搭建、代码部署、功能测试)。1.4 案例流程说明:准备开发环境(Node.js、Java、MySQL);克隆项目代码,配置数据库连接;初始化数据库,导入预设数据;启动后端服务,启动前端开发服务器;测试功能。1.5 资源总览本案例本地开发完全免费。资源名称规格说明Node.jsv18+ 或 v24前端运行环境Java JDK11后端运行环境Maven3.6+Java构建工具MySQL8.0+数据库二、系统架构设计2.1 整体架构本系统采用前后端分离架构:┌─────────────────────────────────────────────────────────────┐ │ 用户浏览器 │ │ (Vue 3 SPA应用) │ └─────────────────────────────────────────────────────────────┘ │ │ HTTP/HTTPS ▼ ┌─────────────────────────────────────────────────────────────┐ │ Nginx (可选) │ │ 静态资源 + 反向代理 │ └─────────────────────────────────────────────────────────────┘ │ ┌───────────────┴───────────────┐ │ │ ▼ ▼ ┌──────────────────────────┐ ┌──────────────────────────┐ │ 前端静态资源 │ │ 后端API服务 │ │ (HTML/CSS/JS) │ │ (Spring Boot 8080) │ │ │ │ │ │ - Vue 3 │ │ - RESTful API │ │ - Element Plus │ │ - JWT认证 │ │ - ECharts │ │ - 业务逻辑 │ │ - Axios │ │ - 数据访问 │ └──────────────────────────┘ └──────────────────────────┘ │ │ JDBC ▼ ┌──────────────────────────┐ │ MySQL 8.0 数据库 │ │ │ │ - 用户数据 │ │ - 藏品数据 │ │ - 标签数据 │ └──────────────────────────┘2.2 技术栈选型说明前端技术栈技术版本选型理由Vue 33.4+最新稳定版,Composition API提供更好的代码组织Vite6.x新一代构建工具,开发体验优秀,支持Node.js v24Element Plus2.7+Vue 3生态最成熟的UI组件库ECharts5.5+功能强大的数据可视化库Pinia2.1+Vue 3官方推荐的状态管理库Axios1.7+主流HTTP客户端,拦截器机制完善后端技术栈技术版本选型理由Spring Boot2.7.18企业级框架,生态成熟,Java 11兼容性好MyBatis-Plus3.5.5MyBatis增强版,简化CRUD操作MySQL8.0+主流关系型数据库,支持JSON类型JWT-无状态认证,适合前后端分离jBCrypt0.4密码加密,避免Spring Security过重依赖为什么不用Spring Security?Spring Security功能强大但配置复杂,默认会拦截所有请求返回401。本项目只需要密码加密和JWT认证,使用jBCrypt + 自定义拦截器更轻量灵活。2.3 数据库设计共7张表,采用用户隔离设计:表名说明用户隔离t_user用户表-t_collection藏品表user_id字段t_collection_image藏品图片表通过collection关联t_tag标签表user_id区分预设/自定义t_collection_tag藏品-标签关联表通过collection关联t_status_change_log状态变更日志表通过collection关联t_wish许愿表user_id字段三、使用华为云码道(CodeArts)代码智能体辅助开发3.1 开发流程概述本项目全程使用华为云码道(CodeArts)代码智能体辅助开发,遵循SDD(Specification-Driven Development)规范驱动开发流程:需求描述 → 需求规格设计 → 实现方案设计 → 编码任务规划 → 任务执行3.2 各阶段详细说明3.2.1 需求规格设计(spec.md)使用方式:向CodeArts描述项目需求,自动生成结构化需求文档。生成内容:功能需求列表非功能需求(性能、安全、可用性)验收标准约束条件效率提升:传统需求文档编写需要2-3小时,使用CodeArts仅需10分钟。3.2.2 实现方案设计(design.md)使用方式:CodeArts根据需求自动生成技术方案。生成内容:技术选型及理由架构设计数据库表设计API接口设计前后端交互设计效率提升:传统方案设计需要1-2天,使用CodeArts仅需30分钟。3.2.3 编码任务规划(tasks.md)使用方式:CodeArts将设计拆解为具体编码任务。生成内容:任务列表(按优先级排序)任务依赖关系预估工作量验收标准3.2.4 任务执行使用方式:逐个执行编码任务,CodeArts自动生成代码。辅助能力:自动生成Controller、Service、Mapper代码自动处理依赖注入自动生成前端组件和API调用实时调试和问题修复效率提升:传统编码需要2-3周,使用CodeArts仅需3天。3.3 CodeArts解决的关键问题问题传统方式CodeArts方式需求理解偏差反复沟通确认结构化文档自动生成技术选型困难调研对比耗时根据需求自动推荐代码风格不统一人工Code Review自动生成规范代码前后端接口对齐手动编写文档自动生成API封装Bug定位困难逐行排查智能分析错误原因四、环境和资源准备4.1 本地开发环境要求必备软件软件版本要求说明Node.jsv18+ 或 v24前端运行环境Java JDK11后端运行环境Maven3.6+Java构建工具MySQL8.0+数据库安装验证# 验证Node.js node -v # 应输出 v18.x.x 或 v24.x.x # 验证Java java -version # 应输出 11.x.x # 验证Maven mvn -v # 应输出 3.6.x 或更高 # 验证MySQL mysql --version # 应输出 8.0.x 4.2 创建数据库# 登录MySQL mysql -u root -p # 创建数据库 CREATE DATABASE merchive CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 退出MySQL exit; 五、构建Merhive应用5.1 获取项目代码# 克隆项目代码 git clone https://gitcode.com/Christine_wxh/Merchive.git cd merhive5.2 项目结构说明merhive/ ├── merchive-backend/ # 后端项目(Spring Boot) │ ├── pom.xml # Maven依赖配置 │ ├── src/main/java/com/merchive/ │ │ ├── controller/ # 控制器(接收HTTP请求) │ │ ├── service/ # 服务层(业务逻辑) │ │ ├── entity/ # 实体类(对应数据库表) │ │ ├── mapper/ # Mapper接口(数据库操作) │ │ ├── dto/ # 数据传输对象 │ │ ├── config/ # 配置类 │ │ └── util/ # 工具类 │ └── src/main/resources/ │ ├── application.yml # 配置文件(数据库连接等) │ └── db/ # 数据库初始化SQL │ ├── merchive-frontend/ # 前端项目(Vue 3) │ ├── package.json # 依赖配置 │ ├── vite.config.js # Vite配置 │ └── src/ │ ├── api/ # API封装 │ ├── router/ # 路由配置 │ ├── stores/ # Pinia状态管理 │ ├── components/ # 公共组件 │ └── views/ # 页面组件 │ └── README.md # 项目说明文档5.3 配置后端数据库连接编辑 merchive-backend/src/main/resources/application.yml:spring: datasource: url: jdbc:mysql://localhost:3306/merchive?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root # 你的MySQL用户名 password: 你的密码 # 你的MySQL密码 重要:每个部署者必须配置自己的数据库连接信息,确保数据隔离和安全。5.4 初始化数据库# 进入后端项目目录 cd merchive-backend # 执行数据库初始化脚本 mysql -u root -p --default-character-set=utf8mb4 merchive < src/main/resources/db/schema.sql mysql -u root -p --default-character-set=utf8mb4 merchive < src/main/resources/db/data.sql注意:--default-character-set=utf8mb4 请确保执行以避免中文乱码。5.5 启动后端服务# 在 merchive-backend 目录下执行 mvn spring-boot:run等待启动成功,看到类似输出:Started MerchiveApplication in 5.123 seconds后端运行在 http://localhost:80805.6 启动前端服务打开新的终端窗口:# 进入前端项目目录 cd merchive-frontend # 安装依赖(首次运行需要) npm install # 启动开发服务器 npm run dev前端运行在 http://localhost:51735.7 功能测试5.7.1 注册和登录浏览器访问 http://localhost:5173点击"立即注册"创建新账号使用注册的账号登录5.7.2 核心功能测试清单功能测试步骤预期结果注册登录注册新账号并登录成功登录,跳转首页添加藏品点击"添加藏品",填写表单,上传图片藏品创建成功图片裁剪上传图片后拖拽裁剪框生成自定义裁剪图片搜索筛选使用类别、价格、标签筛选返回符合条件的藏品统计看板点击"统计"菜单显示统计图表数据导出点击"导出Excel/PDF"下载文件成功数据库备份首页点击"导出备份"下载SQL备份文件账户管理点击头像→账户信息可修改用户名/密码六、解决方案6.1 用户数据隔离方案问题:多用户使用同一系统,如何保证数据隔离?解决方案:数据库层面:所有业务表设计user_id字段查询层面:所有查询强制带WHERE user_id = 当前用户ID权限校验:更新/删除操作前验证user_id是否匹配认证层面:JWT Token包含userId,无法伪造代码实现:// 所有查询都带userId过滤 LambdaQueryWrapper<Collection> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Collection::getUserId, userId); 6.2 文件上传存储方案问题:图片上传后如何存储和访问?解决方案:存储位置:服务器本地./uploads/collections/目录数据库存储:只存相对路径访问方式:通过WebMvcConfig映射为静态资源URL安全处理:使用Files.copy()替代transferTo(),解决Windows路径问题6.3 数据导出方案问题:如何导出藏品数据为Excel/PDF?解决方案:Excel导出:使用Apache POI生成标准表格PDF导出:使用OpenPDF生成卡片式布局,包含图片前端处理:Axios响应拦截器特殊处理blob类型响应6.4 数据库备份方案问题:如何实现一键数据库备份?解决方案:后端实现:调用mysqldump命令生成SQL文件前端触发:首页提供"导出备份"按钮文件下载:生成带时间戳的SQL文件,自动下载七、核心技术难点与解决思路7.1 Spring Security依赖冲突难点描述:引入spring-security-crypto进行密码加密时,传递依赖了Spring Security自动配置,导致所有请求返回401未授权。解决思路:分析依赖树,定位冲突来源排除Spring Security依赖改用org.mindrot:jbcrypt:0.4进行密码加密自定义JWT拦截器实现认证最终方案:<dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.4</version> </dependency> 7.2 MySQL中文乱码难点描述:Windows环境下MySQL客户端默认使用GBK编码读取SQL文件,导致中文数据插入后乱码。解决思路:分析字符编码流程:文件编码 → 客户端编码 → 服务器编码确认SQL文件为UTF-8编码强制指定客户端编码为utf8mb4最终方案:mysql -u root -p --default-character-set=utf8mb4 merchive < data.sql7.3 文件上传路径问题难点描述:file.transferTo()在Windows环境下因相对路径解析异常导致文件上传失败。解决思路:分析Spring文件保存机制发现transferTo()对相对路径处理不一致改用绝对路径 + Files.copy()最终方案:Path absolutePath = Paths.get(storagePath).resolve(filename).normalize(); Files.copy(file.getInputStream(), absolutePath); 7.4 Axios文件下载拦截难点描述:Axios响应拦截器对所有响应执行错误检查,导致blob类型响应(文件下载)失败。解决思路:分析Axios拦截器执行流程发现blob响应也被拦截器处理增加类型判断跳过blob响应最终方案:if (response.config.responseType === 'blob') { return response; // 跳过blob响应的错误检查 } 7.5 esbuild与Node.js版本兼容难点描述:esbuild 0.21.x在Node.js v24上崩溃,报错STATUS_STACK_BUFFER_OVERRUN。解决思路:查阅esbuild版本兼容性文档发现esbuild 0.21.x不支持Node.js v24升级Vite到6.x,esbuild到0.25.5最终方案:{ "devDependencies": { "vite": "^6.4.3", "esbuild": "^0.25.5" } } 八、演示视频请观看Merchive项目演示视频(Merchive项目演示视频.mp4)了解完整功能展示。项目演示视频地址:https://gitcode.com/Christine_wxh/Merchive/blob/main/Merchive项目演示视频.mp4
  • [高校训练营] 基于华为云码道(CodeArts)代码智能体的花店管理系统开发
    一、概述1.1 案例介绍本案例使用华为云码道(CodeArts)代码智能体,从零开始完成一个花店管理系统(花语轩)的全流程开发与云端部署。涵盖需求分析、方案设计、前后端编码、调试验证、ECS部署上线全流程,体验AI辅助编程的高效开发模式。网站url:http://124.71.227.253,仓库地址:https://gitcode.com/2301_80246598/Flower-shop-web.git1.2 适用对象高校学生个人开发者1.3 案例时间本案例总时长预计90分钟。1.4 案例流程说明:在华为云控制台购买ECS弹性云服务器,配置VPC与安全组;使用华为云码道CodeArts代码智能体,通过自然语言对话完成需求规格设计、方案设计和编码任务规划;在CodeArts辅助下完成前后端代码编写、调试和功能验证;将项目构建产物上传至ECS,配置MySQL、Nginx等服务,完成线上部署。1.5 资源总览本案例预计花费164.38元。体验完成后请及时释放资源,避免产生多余的费用。资源名称规格单价(元)华为云码道(CodeArts)代码智能体专业版105.77弹性云服务器 ECS1 vCPU | 1GiB | Huawei Cloud EulerOS 2.030.60云硬盘 EVS高IO | 40GiB28.00虚拟私有云 VPC按需0.01二、环境和资源准备2.1 购买ECS弹性云服务器登录华为云控制台,点击菜单 服务列表 > 计算 > 弹性云服务器,点击"购买弹性云服务器"。选择如下配置:- 计费模式:包年/包月- 区域:可选就近区域- 规格:通用计算型 | 1 vCPU | 1GiB- 镜像:Huawei Cloud EulerOS 2.0 标准版 64位- 系统盘:高IO | 40GiB- 网络:默认VPC和安全组设置登录凭证:root用户,密码自定义。点击"立即购买",等待ECS创建完成。注意:由于1GiB内存较小,后续部署时需为MySQL配置低内存模式并添加2GB Swap交换分区。2.2 开通华为云码道CodeArts登录华为云码道,开通CodeArts服务。在CodeArts中创建项目,进入代码智能体(CodeArts IDE)开发环境。2.3 本地开发环境要求在CodeArts IDE中开发时,需确保本地已安装以下工具:JDK 17+Maven 3.8+Node.js 18+MySQL 8.0+说明:CodeArts代码智能体可在对话中直接执行命令,无需手动切换终端。三、码道研发花店管理系统3.1 需求规格设计在CodeArts代码智能体中,通过自然语言描述项目需求,智能体自动生成需求规格文档(spec.md)。向CodeArts输入需求描述:开发一个花店管理系统(花语轩),课程项目,实现鲜花的分类管理、上架下架管理、会员管理(普通会员/黄金会员/白金会员/普通顾客)、购物车、模拟付款、畅销统计等完整业务闭环。CodeArts自动生成需求规格文档,包含:用户注册即成为普通会员,累计消费金额到达门槛自动升级(≥500元→黄金会员9折,≥2000元→白金会员8折),等级不降级付款做"模拟付款"即可完整业务流程:用户登录→浏览鲜花→加入购物车→修改数量/勾选结算→生成订单→计算折扣→确认付款→扣减库存(乐观锁)→记录销量→清空已购购物车项→更新累计消费→检测会员升级前端需展示鲜花图片技术栈:后端 Spring Boot 3.x + MyBatis-Plus + MySQL 8.0 + JWT,前端 Vue 3 + Element Plus + Pinia3.2 方案设计与任务规划3.2.1 设计文档生成CodeArts根据需求规格自动生成实现方案设计文档(design.md),包括:系统架构:前后端分离,后端Spring Boot提供RESTful API,前端Vue 3 SPA数据库设计:6张核心表(t_member、t_category、t_flower、t_cart_item、t_order、t_order_item)API设计:6组Controller(Member、Category、Flower、Cart、Order、Admin)会员等级与折扣策略3.2.2 编码任务规划CodeArts根据设计文档自动生成编码任务清单(tasks.md),将开发工作分解为可执行的任务项。3.3 后端开发3.3.1 项目结构CodeArts自动生成后端项目结构:flower-shop-server/ ├── pom.xml ├── src/main/java/com/flowershop/ │ ├── FlowerShopApplication.java │ ├── common/ # UnifiedResponse, ErrorCode, BusinessException │ ├── config/ # JwtUtil, CorsConfig, MyBatisPlusConfig, WebMvcConfig │ ├── filter/ # JwtAuthFilter │ ├── entity/ # Member, Category, Flower, CartItem, Order, OrderItem │ ├── entity/enums/ # MemberLevel, MemberRole, FlowerStatus, OrderStatus │ ├── dto/ # 11个请求DTO │ ├── vo/ # 14个响应VO │ ├── mapper/ # 6个Mapper接口 │ ├── service/impl/ # 6个Service实现 │ └── controller/ # 6个Controller └── src/main/resources/ ├── application.yml ├── schema.sql └── data.sql 3.3.2 关键代码说明1) 会员等级自动升级(MemberServiceImpl.java)会员等级根据累计消费自动升级,升级后不降级:public void checkAndUpgradeLevel(Long memberId) { Member member = getById(memberId); BigDecimal totalSpent = member.getTotalSpent(); MemberLevel oldLevel = member.getLevel(); MemberLevel newLevel = oldLevel; if (totalSpent.compareTo(new BigDecimal("2000")) >= 0) { newLevel = MemberLevel.PLATINUM; } else if (totalSpent.compareTo(new BigDecimal("500")) >= 0) { newLevel = MemberLevel.GOLD; } if (newLevel != oldLevel) { member.setLevel(newLevel); updateById(member); } } 2) 乐观锁扣减库存(FlowerMapper.java)使用MyBatis-Plus的乐观锁机制防止超卖:@Update("UPDATE t_flower SET stock = stock - #{quantity}, sales = sales + #{quantity} WHERE id = #{id} AND stock >= #{quantity}") int deductStock(@Param("id") Long id, @Param("quantity") Integer quantity); 3) JWT认证过滤器(JwtAuthFilter.java)公开路径放行,其余请求需携带JWT Token:private static final String[] PUBLIC_PATHS = { "/api/v1/member/login", "/api/v1/member/register", "/api/v1/flowers", "/api/v1/categories", "/images/**", "/error" }; 3.3.3 数据库初始化schema.sql定义6张核心表,data.sql初始化1个管理员、6个分类、16款鲜花:3.3.4 编译与启动在CodeArts终端中执行:.\start-backend.bat3.4 前端开发3.4.1 项目结构CodeArts自动生成前端项目结构:flower-shop-web/ ├── vite.config.js ├── package.json ├── src/ │ ├── main.js │ ├── App.vue │ ├── router/index.js │ ├── stores/ # user.js, cart.js (Pinia) │ ├── utils/request.js # Axios封装 │ ├── api/ # 6个API模块 │ └── views/ # 8个用户页面 + 4个管理页面 └── dist/ # 生产构建产物 3.4.2 关键配置说明1) Vite代理配置(vite.config.js)开发环境下,前端通过Vite代理访问后端API和图片资源:server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true }, '/images': { target: 'http://localhost:8080', changeOrigin: true } } } 2) 路由守卫(router/index.js)根据用户角色控制页面访问权限,管理员可访问管理后台页面。3.4.3 安装依赖与启动.\start-fronted.bat 3.5 功能验证与Bug修复在CodeArts辅助下,通过浏览器自动化测试完整业务流程,发现并修复了以下Bug:3.5.1 图片显示问题问题:外部图片URL存在防盗链,前端无法显示鲜花图片。解决:起初CodeArts创建SVG占位图放在后端static/images/目录下,数据库中image_url改为本地路径如/images/rose1.svg。后续提供了可用的外部图片URL后,替换回外部链接。3.5.2 Vite代理未覆盖images路径问题:vite.config.js只代理了/api,但图片路径/images/**也需要代理到后端8080端口。解决:在vite.config.js中添加/images代理规则。3.5.3 创建订单时total_amount为null问题:OrderServiceImpl.createOrder()先执行insert再计算金额,导致total_amount字段为null(数据库不允许为空)。解决:调整为先计算所有金额,再一次性插入订单。3.5.4 管理员菜单不显示问题:MemberVO缺少role字段,前端无法判断用户是否为管理员。解决:在MemberVO中添加role字段,并在toMemberVO()方法中设置vo.setRole(member.getRole().name())。3.5.5 完整流程验证在CodeArts辅助下验证了完整的用户和管理员流程:用户流程:注册→登录→浏览鲜花→加入购物车→结算→生成订单→确认付款→状态变为"已支付"→购物车清空→销量更新管理员流程:admin登录→商品管理(含编辑/下架)→分类管理(含增删改)→会员管理(显示等级/消费)→畅销统计(排名/时间筛选)四、ECS部署项目4.1 构建生产版本4.1.1 构建前端在CodeArts终端中执行:cd flower-shop-web npm run build 构建产物输出到dist/目录。4.1.2 后端JAR包后端已通过mvn package -DskipTests构建,产物为flower-shop-server/target/flower-shop-server-1.0.0.jar。4.2 上传文件到ECS使用Python paramiko库通过SSH将构建产物上传到ECS服务器的/opt/flower-shop/目录:flower-shop-server.jar — 后端JAR包dist/ — 前端构建产物目录schema.sql — 数据库建表脚本data.sql — 数据库初始数据脚本4.3 安装服务器环境通过SSH连接ECS,安装运行所需软件:4.3.1 安装JDK 17ECS默认yum源无JDK 17,通过华为云镜像下载安装:curl -fsSL 'https://repo.huaweicloud.com/openjdk/17.0.2/openjdk-17.0.2_linux-x64_bin.tar.gz' -o /tmp/jdk17.tar.gz tar -xzf /tmp/jdk17.tar.gz -C /usr/local/ ln -sf /usr/local/jdk-17.0.2 /usr/local/jdk17 /usr/local/jdk17/bin/java -version 4.3.2 安装MySQL 8与Nginxyum install -y mysql-server nginx 4.3.3 配置MySQL低内存模式由于ECS仅1GiB内存,需为MySQL配置低内存模式并添加Swap:dd if=/dev/zero of=/swapfile bs=1M count=2048 chmod 600 /swapfile mkswap /swapfile swapon /swapfile cat > /etc/my.cnf.d/low-memory.cnf << 'EOF' [mysqld] performance_schema=OFF innodb_buffer_pool_size=128M innodb_log_buffer_size=8M max_connections=30 EOF 4.4 配置MySQL数据库4.4.1 重置root密码MySQL 8首次安装后root密码为随机值,需通过skip-grant-tables模式重置:systemctl stop mysqld systemctl set-environment MYSQLD_OPTS="--skip-grant-tables --skip-networking" systemctl start mysqld mysql -uroot -e "FLUSH PRIVILEGES; ALTER USER 'root'@'localhost' IDENTIFIED BY '246537Znc'; FLUSH PRIVILEGES;" systemctl stop mysqld systemctl unset-environment MYSQLD_OPTS systemctl start mysqld 4.4.2 创建数据库并导入数据mysql -uroot -p246537Znc -e "CREATE DATABASE IF NOT EXISTS flower_shop DEFAULT CHARACTER SET utf8mb4;" mysql -uroot -p246537Znc flower_shop < /opt/flower-shop/schema.sql mysql -uroot -p246537Znc flower_shop < /opt/flower-shop/data.sql 4.5 启动后端服务4.5.1 直接启动nohup /usr/local/jdk17/bin/java -Xmx256m -jar /opt/flower-shop/flower-shop-server.jar > /opt/flower-shop/backend.log 2>&1 & 4.5.2 配置systemd开机自启创建服务文件/etc/systemd/system/flower-shop.service:[Unit] Description=Flower Shop Backend After=network.target mysqld.service [Service] Type=simple ExecStart=/usr/local/jdk17/bin/java -Xmx256m -jar /opt/flower-shop/flower-shop-server.jar Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target 启用服务:systemctl daemon-reload systemctl enable flower-shop 4.6 配置Nginx反向代理创建Nginx配置文件/etc/nginx/conf.d/flower-shop.conf:server { listen 80; server_name _; root /opt/flower-shop/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } 启动Nginx:rm -f /etc/nginx/conf.d/default.conf nginx -t && systemctl restart nginx && systemctl enable nginx 4.7 防火墙放通80端口firewall-cmd --permanent --add-service=http firewall-cmd --reload iptables -I INPUT -p tcp --dport 80 -j ACCEPT 4.8 验证部署结果在浏览器中访问 [http://<ECS公网IP>](http://124.71.227.253),确认网站正常运行:五、网站功能说明5.1 管理员功能说明管理员账号:admin密码:admin1235.1.1 鲜花的分类管理可进行新增、删除、编辑分类名的操作,如新增洋桔梗类鲜花,删除某类鲜花,修改某类鲜花名称。5.1.2 鲜花上架下架管理下架某类鲜花后首页该花束不可见,可重新上架5.1.3 会员管理5.1.4 统计畅销鲜花5.2 用户功能说明5.2.1 会员等级消费升级用户注册即成为普通会员,累计消费金额到达门槛自动升级(≥500元一黄金会员9折,≥2000元白金会员8折),等级不降级。白金会员后续消费享受8折优惠。5.2.2 基本购物功能至此,码道驱动,打造花店管理系统案例结束!
  • [高校训练营] 基于华为云码道的模拟面试以及简历改善平台
    一、概述1.1 案例介绍求职准备通常包含简历整理、岗位理解、技能差距分析、项目经历补充和面试训练等多个环节。传统方式需要用户在招聘网站、开源平台、笔记工具和面试题库之间反复切换,流程分散、反馈慢,也难以形成持续改进闭环。本案例围绕这一痛点,基于华为云码道代码智能体的规范开发模式,构建一个"面试模拟助手"应用。系统支持账号注册登录、简历上传与解析、岗位匹配分析、技能差距识别、GitHub 项目搜索与推荐、项目实践路径生成、模拟面试、面试报告和用户级 API Key 配置等功能。项目采用前后端分离架构:前端:Vue3、TypeScript、Element Plus、Pinia、Vite。后端:FastAPI、Uvicorn、SQLAlchemy、SQLite,支持后续切换 PostgreSQL。AI 能力:DeepSeek API,用于简历分析、项目推荐、技能指导和模拟面试。外部接口:GitHub API,用于开源项目搜索。文件解析:pdfplumber 解析 PDF,python-docx 解析 Word。部署方式:支持 Windows 桌面应用打包和华为云 ECS 云服务部署。整个过程先使用码道完成规范化开发,再通过 AI Shell 完成云资源规划、Terraform 编排、ECS 初始化、应用部署和访问验证,展示从自然语言需求到公网可访问应用的完整交付流程。案例技术选型:华为云码道(CodeArts)代码智能体:集代码大模型、AI IDE、Code Agent 为一体的智能编码产品。本案例使用其中的规范开发模式,从需求分析、技术设计、任务拆解到代码实现、测试验证,形成较完整的开发闭环。开发者空间 AI Shell:华为云提供的智能 AI 命令行工具。本案例通过自然语言对话完成项目结构分析、ECS 资源规划、Terraform 配置生成、云资源创建、远程部署和访问验证。Terraform:用于声明式创建华为云资源,包括 VPC、子网、安全组、ECS、EIP 和 EIP 绑定,降低手动配置云资源的复杂度。通过码道规范开发模式与 AI Shell 对话式部署的结合,本案例完成了"AI 求职辅助应用"的开发、部署和验证,让学生或个人开发者能够以较低门槛体验完整云上交付过程。仓库链接:interview_assistant:基于 FastAPI 与 Vue3 的面试模拟助手桌面应用 - AtomGit本项目部署链接:http://49.4.115.167/1.2 适用对象个人开发者:希望学习 Vue + FastAPI + AI API 的完整应用开发与云部署流程。高校学生:希望完成一个可展示、可部署、可写入课程作品或简历的 AI 项目。求职产品开发者:希望快速搭建简历分析、项目推荐和模拟面试类应用原型。云开发初学者:希望通过 AI Shell 理解 ECS、EIP、VPC、安全组和 Terraform 的基本用法。1.3 案例时间本案例总时长预计 90 到 120 分钟:环境准备与码道项目导入:10 分钟。规范开发模式分析、设计与任务拆解:20 分钟。代码开发与本地验证:30 分钟。AI Shell 分析部署资源并生成 Terraform:15 分钟。执行 Terraform 创建 ECS 并部署应用:20 到 30 分钟。公网访问验证与资源清理:10 分钟。如果已经具备项目源码、华为云账号和开发者空间环境,部署阶段可以压缩到 30 分钟左右。1.4 案例流程说明:用户首先在本地 PC 中打开华为云码道代码智能体,通过规范开发模式完成"面试模拟助手(Interview Assistant)"项目的需求分析、技术设计、任务拆解和代码生成。项目代码开发完成后,将源码提交到 GitCode 仓库中,作为后续云端部署的代码来源。随后,用户进入开发者空间 AI Shell,通过自然语言指令调用 AI Cli 工具,从 GitCode 拉取项目源码,并在云端环境中完成资源规划、Terraform 配置生成、ECS 创建、应用依赖安装、前后端构建、Nginx 反向代理和 systemd 服务配置。部署完成后,用户即可通过公网访问云端运行的 Interview_assistant 应用。1.5 资源总览本案例预计花费约 0 到 20 元,具体取决于 ECS 按需运行时长和带宽计费情况。资源名称规格费用说明华为云码道(CodeArts)代码智能体体验版免费开发者空间 AI Shell标准环境免费ECS 弹性云服务器s6.medium.2,1 vCPU / 2GB按需计费EIP 弹性公网 IP5 Mbps按流量或带宽计费VPC / 子网 / 安全组基础网络资源通常不单独计费EVS 系统盘Ubuntu 22.04,40GB SSD随 ECS 计费二、系统架构设计2.1 整体架构项目采用前后端分离架构,后端使用 FastAPI 提供 RESTful API,前端使用 Vue3 构建单页面应用。支持两种部署方式:桌面应用模式:通过 PyInstaller 和 pywebview 打包为 Windows 桌面应用,适合个人用户本地使用。云服务模式:部署到华为云 ECS,通过 Nginx 反向代理,支持多用户公网访问。数据存储使用 SQLite,用户配置和 API Key 按用户隔离存储。AI 能力通过 DeepSeek API 接入,GitHub 项目搜索通过 GitHub API 接入。2.2 项目结构说明项目核心结构如下:D:\Huawei |-- backend | |-- main.py # FastAPI 应用入口 | |-- config.py # 配置、数据目录、API Key、CORS | |-- database.py # SQLAlchemy 数据库连接和迁移 | |-- interview_assistant.spec # 后端 PyInstaller 打包配置 | |-- requirements.txt # 后端依赖 | |-- adapters | | |-- deepseek.py # DeepSeek API 适配器 | | |-- github_api.py # GitHub API 适配器 | |-- routers | | |-- auth.py # 注册登录 | | |-- resume.py # 简历上传和分析 | | |-- project.py # 项目搜索和推荐 | | |-- interview.py # 模拟面试 | | |-- system.py # API Key 管理 | |-- services | | |-- file_parser.py # PDF / Word 解析 |-- frontend | |-- package.json # 前端依赖 | |-- src | |-- router # Vue Router | |-- stores # Pinia 状态管理 | |-- utils/request.ts # Axios 请求封装 | |-- views # 登录、简历、项目、面试、设置页面 |-- launcher | |-- launcher.py # Windows 桌面启动器 | |-- requirements.txt # launcher 依赖 |-- installer | |-- setup.iss # Inno Setup 安装脚本 |-- deploy | |-- nginx.conf # Nginx 反向代理配置 | |-- interview-assistant.service # systemd 服务配置 | |-- init.sh # ECS 初始化部署脚本 |-- terraform | |-- providers.tf # HuaweiCloud Provider | |-- variables.tf # 变量定义 | |-- main.tf # VPC、子网、安全组、ECS、EIP | |-- outputs.tf # EIP、SSH 命令等输出 |-- tests | |-- test_windows_packaging.py # Windows 打包行为测试 | |-- test_account_api_isolation.py # 多账号 API Key 隔离测试 |-- build.py # 本地一键构建脚本 |-- config.ini.template # 默认配置模板 |-- README.md # 项目说明2.3 后端核心逻辑2.3.1 应用入口后端入口文件为 backend/main.py。它完成以下工作:创建 FastAPI 应用。注册 CORS 中间件。注册 auth、resume、project、interview、user、system 等路由。初始化数据目录、日志目录、上传目录和配置目录。调用 migrate_db() 创建或迁移 SQLite 表结构。在生产部署中监听 0.0.0.0,由 Nginx 反向代理访问。部署前,码道根据 AI Shell 的上云要求完成了关键调整:修改项文件说明后端监听地址backend/main.py从 127.0.0.1 调整为 0.0.0.0,适配 ECS 服务监听动态 API 地址backend/config.py避免硬编码 localhost,支持根据请求动态推断SECRET_KEYbackend/config.py改为环境变量优先,支持生产随机密钥CORS 配置backend/config.py支持环境变量 EXTRA_CORS_ORIGINS生产环境配置backend/.env.production新建生产配置模板Nginx 配置deploy/nginx.conf反向代理 80 到 FastAPI 8000systemd 服务deploy/interview-assistant.service进程守护和开机自启2.3.2 用户级 API Key 隔离API Key 相关逻辑位于 backend/config.py 和 backend/routers/system.py。系统按当前登录用户 ID 生成独立配置路径:def _get_user_api_key_file(user_id: int) -> str: user_dir = os.path.join(USER_CONFIG_DIR, f'user_{user_id}') os.makedirs(user_dir, exist_ok=True) return os.path.join(user_dir, 'api_keys.ini') 读取、写入和状态检查函数都支持 user_id 参数:def read_api_key(key_name: str, user_id: int = 0) -> str | None: ... def write_api_key(key_name: str, key_value: str, user_id: int = 0) -> None: ... def get_all_api_keys_status(user_id: int = 0) -> dict: ... 系统设置接口通过 get_current_user 获取当前用户,并将 user.id 传入 API Key 操作:@router.put("/api-keys") def update_api_key(req: ApiKeyUpdateRequest, user: User = Depends(get_current_user)): ... write_api_key(key_name, key_value, user_id=user.id) DeepSeek 和 GitHub 适配器也接收 user_id,确保 AI 调用使用当前账号自己的密钥:adapter = DeepSeekAdapter(user_id=user.id) adapter = GitHubAdapter(user_id=user.id) 2.4 前端核心逻辑前端位于 frontend 目录,使用 Vue3 + TypeScript + Element Plus。主要页面包括:页面文件功能登录src/views/auth/LoginView.vue用户登录并保存 token注册src/views/auth/RegisterView.vue创建新账号仪表盘src/views/dashboard/DashboardView.vue展示简历、项目和面试概况简历优化src/views/resume/ResumeView.vue上传简历、分析岗位匹配项目推荐src/views/project/ProjectView.vue搜索项目、刷新推荐、生成实践路径模拟面试src/views/interview/InterviewView.vue创建面试会话并答题面试报告src/views/interview/InterviewReportView.vue查看评分与改进建议设置src/views/settings/SettingsView.vue配置 DeepSeek、GitHub 和 TTS请求封装位于 frontend/src/utils/request.ts,会自动读取本地 token,并在接口返回 401 时尝试刷新登录态。三、使用华为云码道(CodeArts)代码智能体辅助完成代码开发及调试3.1 代码智能体在项目开发中的应用华为云码道(CodeArts)代码智能体在本项目中发挥了关键作用,辅助完成了以下开发任务:需求分析与规格定义:根据自然语言需求生成详细的需求规格文档,明确功能边界和技术要求。技术设计与架构规划:基于需求规格生成完整的技术设计方案,包括前后端技术选型、数据库设计、API 设计等。任务拆解与排期:将复杂项目拆解为可执行的开发任务,形成清晰的开发路线图。代码生成与补全:智能体根据需求描述自动生成后端 API 路由、前端组件、数据库模型等代码片段,显著减少手动编码工作量。代码审查与优化:智能体对现有代码进行审查,识别潜在的性能问题、安全漏洞和代码风格不一致,并提供优化建议。调试支持:智能体协助定位运行时错误,分析日志输出,提供修复建议,加速问题排查。测试用例生成:智能体根据功能描述自动生成单元测试和集成测试用例,提高测试覆盖率。文档生成:智能体根据代码注释和结构自动生成 API 文档、部署说明和用户手册。架构设计建议:智能体提供前后端分离架构、数据库设计、API 设计等方面的最佳实践建议。3.2 规范开发模式流程3.2.1 输入需求并启动规范开发在码道代码智能体中切换到规范开发模式后,可以输入类似提示词:我希望开发一个面向求职准备场景的面试模拟助手系统。 系统需要支持以下功能: - 用户注册登录 - 简历上传与解析 - 岗位匹配分析 - 技能差距识别 - 开源项目推荐 - 项目实践路径生成 - 模拟面试 - 面试报告 - 用户个人 API Key 配置。 前端使用 Vue3 + TypeScript + Element Plus,后端使用 FastAPI + SQLAlchemy,AI 能力接入 DeepSeek API。 请按规范开发模式帮我完成需求分析、技术设计、任务拆解、代码实现和测试验证。码道会按照规范开发流程推进,先分析业务目标和功能边界,再生成技术设计与任务列表,最后进入代码实现与测试阶段。3.2.2 项目核心能力模块功能说明用户认证注册、登录、刷新 token、JWT 鉴权简历解析上传 PDF / Word 简历,解析文本并保存岗位分析输入目标岗位 JD,生成匹配分数、差距和建议项目推荐结合简历和岗位目标,通过 DeepSeek 与 GitHub API 推荐项目实践路径为推荐项目生成阶段化实践路线模拟面试根据简历和岗位生成面试题,支持答题、追问和报告用户设置保存 DeepSeek API Key、GitHub Token、TTS 服务地址部署适配支持 Nginx 反向代理、systemd 守护、生产环境变量3.3 具体应用场景3.3.1 后端开发辅助FastAPI 路由生成:智能体根据需求描述自动生成完整的 CRUD 路由,包括请求验证、数据库操作和响应格式化。SQLAlchemy 模型设计:智能体协助设计数据库表结构,生成符合业务需求的 ORM 模型。错误处理与日志:智能体建议统一的错误处理中间件和日志记录策略。API Key 加密存储:智能体提供安全的 API Key 存储方案,包括加密算法选择和密钥管理策略。3.3.2 前端开发辅助Vue3 组件生成:智能体根据设计稿或功能描述生成 Vue 单文件组件,包括模板、脚本和样式。TypeScript 类型定义:智能体根据后端 API 响应自动生成 TypeScript 接口定义,确保类型安全。状态管理设计:智能体建议 Pinia store 结构,优化状态管理和组件通信。路由守卫实现:智能体生成认证和授权路由守卫,保护需要登录的页面。3.3.3 桌面应用打包辅助PyInstaller 配置优化:智能体提供 PyInstaller spec 文件的最佳配置,解决隐藏控制台、资源打包等问题。pywebview 集成:智能体协助实现桌面窗口启动器,管理后端进程和前端页面加载。Inno Setup 脚本编写:智能体生成完整的安装脚本,包含文件复制、快捷创建和卸载清理。3.3.4 测试与调试辅助单元测试生成:智能体根据业务逻辑自动生成 pytest 测试用例,覆盖正常和异常场景。API 测试脚本:智能体生成 Postman 风格的 API 测试脚本,方便接口验证。性能调优建议:智能体分析代码性能瓶颈,提供数据库查询优化、缓存策略等建议。安全审计:智能体检查代码中的安全漏洞,如 SQL 注入、XSS、CSRF 等,并提供修复方案。3.4 开发效率提升通过华为云码道(CodeArts)代码智能体的辅助,本项目开发效率提升显著:开发时间缩短约 40%:智能体自动生成基础代码,减少重复劳动。代码质量提升:智能体的代码审查和建议帮助保持代码风格一致,减少潜在 bug。文档完整性提高:智能体自动生成的文档覆盖全面,减少手动编写工作量。问题解决速度加快:调试支持功能帮助快速定位和修复问题。四、解决方案4.1 整体解决方案本项目提供了一套完整的面试模拟助手解决方案,包括:用户管理:注册、登录、JWT 认证、Token 刷新。简历管理:PDF/Word 简历上传、解析、存储、编辑。岗位匹配分析:基于 AI 的简历与岗位描述匹配度分析、技能差距识别。开源项目推荐:根据技能差距推荐 GitHub 开源项目,生成实践路径。模拟面试:AI 生成面试题、实时评估答案、生成面试报告。多部署模式:支持 Windows 桌面应用打包和华为云 ECS 云服务部署。多用户隔离:每个用户的 API Key、简历数据、面试记录完全隔离。4.2 技术栈选择组件技术选型理由后端框架FastAPI高性能、异步支持、自动 API 文档生成数据库SQLite轻量、无需单独部署、适合桌面应用和简单云部署ORMSQLAlchemyPython 生态成熟、支持异步操作前端框架Vue3 + TypeScript响应式、类型安全、生态丰富UI 组件库Element Plus企业级组件、与 Vue3 完美集成状态管理PiniaVue3 官方推荐、TypeScript 友好构建工具Vite快速构建、热重载、开发体验好桌面启动器pywebview轻量、跨平台、可嵌入浏览器打包工具PyInstaller将 Python 代码打包为独立 exe安装包生成Inno SetupWindows 安装包制作、免费开源云部署华为云 ECS弹性计算、按需付费、稳定可靠基础设施即代码Terraform声明式云资源配置、可重复部署反向代理Nginx高性能、稳定、配置简单进程管理systemdLinux 标准服务管理、开机自启4.3 部署方案开发环境:前后端分离运行,便于调试。桌面应用:打包为 Windows 桌面应用,用户一键安装使用。云服务环境:部署到华为云 ECS,通过 Nginx 反向代理,支持多用户公网访问。数据存储:用户数据存储在本地 %APPDATA% 目录(桌面应用)或 /opt/interview_assistant/data(云部署),确保数据持久化。配置管理:API Key 等敏感配置按用户加密存储,支持多用户隔离。五、核心技术难点与解决思路5.1 多用户 API Key 隔离难点:多个用户使用同一台电脑或同一云服务时,需要确保每个用户的 API Key 相互隔离,避免密钥泄露和混用。解决方案:按用户 ID 创建独立配置目录:%APPDATA%\面试模拟助手\config\user_<用户ID>\(桌面应用)或 /opt/interview_assistant/data/config/user_<用户ID>/(云部署)每个用户的 API Key 加密存储在自己的配置文件中所有 API 调用都传入当前用户 ID,确保使用正确的密钥系统设置接口只操作当前用户的配置实现代码:def _get_user_api_key_file(user_id: int) -> str: user_dir = os.path.join(USER_CONFIG_DIR, f'user_{user_id}') os.makedirs(user_dir, exist_ok=True) return os.path.join(user_dir, 'api_keys.ini') 5.2 桌面应用无控制台窗口难点:打包后的 Python 后端 exe 会显示黑色控制台窗口,影响用户体验。解决方案:在 PyInstaller spec 中设置 console=False桌面启动器使用 subprocess.CREATE_NO_WINDOW 标志启动后端进程后端使用 uvicorn.run(..., log_config=None) 避免日志输出到控制台实现代码:# PyInstaller spec console=False # 启动器代码 startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE process = subprocess.Popen( [exe_path, '--port', str(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo, creationflags=subprocess.CREATE_NO_WINDOW ) 5.3 前端静态资源嵌入后端难点:桌面应用需要将前端构建产物嵌入后端 exe,避免依赖外部文件;云部署需要将前端资源部署到合适位置。解决方案:前端使用 Vite 构建,输出到 frontend/dist桌面应用:PyInstaller 将 frontend/dist 目录打包到 _internal/web,后端启动时检查是否在打包环境中,如果是则从 _internal/web 加载静态文件云部署:前端构建产物复制到 /opt/interview_assistant/frontend/dist,Nginx 配置静态文件服务使用 StaticFiles 挂载静态资源目录实现代码:if getattr(sys, 'frozen', False): # 打包环境 web_dir = os.path.join(sys._MEIPASS, 'web') else: # 开发环境 web_dir = os.path.join(os.path.dirname(__file__), '..', 'frontend', 'dist') app.mount("/", StaticFiles(directory=web_dir, html=True), name="web") 5.4 简历文件解析难点:支持 PDF 和 Word 格式简历,提取结构化文本信息。解决方案:PDF 使用 pdfplumber 提取文本Word 使用 python-docx 提取文本统一文本清洗流程:去除多余空格、换行符、特殊字符提取关键信息:姓名、联系方式、教育经历、工作经历、技能等实现代码:def parse_pdf(file_path: str) -> str: import pdfplumber text = "" with pdfplumber.open(file_path) as pdf: for page in pdf.pages: text += page.extract_text() + "\n" return clean_text(text) def parse_docx(file_path: str) -> str: from docx import Document doc = Document(file_path) text = "\n".join([paragraph.text for paragraph in doc.paragraphs]) return clean_text(text) 5.5 AI 接口调用与错误处理难点:AI 服务可能超时、限流或返回错误,需要优雅降级和重试机制。解决方案:实现适配器模式,统一 AI 服务接口添加超时设置和重试逻辑记录详细日志便于排查提供友好的用户错误提示实现代码:class DeepSeekAdapter: def __init__(self, user_id: int = 0): self.api_key = read_api_key("deepseek", user_id) self.timeout = 30 self.max_retries = 3 def chat(self, messages: List[Dict], temperature: float = 0.7) -> str: for attempt in range(self.max_retries): try: response = requests.post( "https://api.deepseek.com/v1/chat/completions", headers={"Authorization": f"Bearer {self.api_key}"}, json={"model": "deepseek-chat", "messages": messages}, timeout=self.timeout ) response.raise_for_status() return response.json()["choices"][0]["message"]["content"] except Exception as e: if attempt == self.max_retries - 1: raise time.sleep(2 ** attempt) # 指数退避 5.6 桌面应用单实例锁难点:防止用户多次点击启动多个应用实例,导致端口冲突和资源浪费。解决方案:使用 Windows 命名互斥体(Mutex)实现单实例锁启动时检查是否已有实例运行如果已有实例,激活现有窗口并退出实现代码:import win32event import win32api import winerror mutex_name = "Global\\面试模拟助手_Launcher" mutex = win32event.CreateMutex(None, False, mutex_name) if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS: # 已有实例运行 sys.exit(0) 5.7 云部署适配与配置管理难点:将本地开发的应用适配到云环境,处理环境变量、文件路径、服务监听等问题。解决方案:使用环境变量配置生产环境参数,避免硬编码动态检测运行环境,调整配置路径和监听地址提供 Nginx 配置模板和 systemd 服务文件创建部署脚本自动完成环境初始化实现代码:# 生产环境配置 if os.getenv("ENVIRONMENT") == "production": SERVER_HOST = "0.0.0.0" # 监听所有网络接口 DATA_DIR = "/opt/interview_assistant/data" LOG_DIR = "/var/log/interview_assistant" else: SERVER_HOST = "127.0.0.1" # 本地开发 DATA_DIR = os.path.join(os.path.expanduser("~"), ".interview_assistant", "data") LOG_DIR = os.path.join(os.path.expanduser("~"), ".interview_assistant", "logs") 六、环境和资源准备6.1 准备码道开发环境参考华为云码道(CodeArts)代码智能体安装部署说明,完成 Windows 版码道安装并登录账号。打开项目后,进入代码智能体对话区域,选择"规范开发模式"。本案例使用规范开发模式完成以下工作:根据自然语言需求生成需求规格。根据需求规格生成技术设计。根据技术设计拆解开发任务。自动修改前后端代码。生成并运行测试。输出开发总结和交付说明。6.2 准备 AI 服务配置应用支持以下配置项:配置项是否必需说明DEEPSEEK_API_KEY必需用于 AI 简历分析、岗位匹配、项目推荐、技能指导和模拟面试GITHUB_TOKEN可选提高 GitHub API 搜索额度TTS_SERVICE_URL可选预留语音播报服务地址SECRET_KEY必需用于 JWT 和服务端加密,生产环境应使用强随机值项目在本地开发时支持用户在设置页保存个人 API Key;部署到 ECS 后,还需要在 backend/.env.production 中配置生产环境变量。6.3 准备项目源码本案例项目根目录为:D:\Huawei核心目录如下:D:\Huawei |-- backend # FastAPI 后端服务 |-- frontend # Vue3 前端工程 |-- launcher # Windows 桌面启动器 |-- installer # Windows 安装脚本 |-- deploy # 云部署配置 |-- terraform # Terraform 云资源配置 |-- tests # 测试用例 |-- build.py # 本地一键构建脚本 |-- README.md # 项目说明其中部署到 ECS 时主要使用 backend、frontend 和 deploy 相关文件。七、码道规范开发面试模拟助手7.1 本地验证后端本地启动:cd D:\Huawei\backend python -m pip install -r requirements.txt python -m uvicorn main:app --host 127.0.0.1 --port 8000 --reload 前端本地启动:cd D:\Huawei\frontend npm install npm run dev测试命令:cd D:\Huawei python -m unittest discover -s tests当前项目测试覆盖账号 API Key 隔离、Windows 打包配置等关键逻辑。7.2 使用核心功能7.2.1 注册和登录打开应用。进入注册页,填写邮箱、密码和昵称。注册成功后登录。登录后前端会保存 accessToken 和 refreshToken。7.2.2 配置 API Key进入"设置"页面,配置:DeepSeek API Key:用于 AI 能力。GitHub Token:用于 GitHub 项目搜索,可选。TTS 服务 URL:用于语音播报扩展,可选。保存后,后端会将配置写入当前登录用户目录:%APPDATA%\面试模拟助手\config\user_<用户ID>\api_keys.ini7.2.3 上传简历并分析岗位匹配进入"简历优化"页面。上传 PDF、DOC 或 DOCX 简历。系统解析简历文本并保存到本地数据库。输入目标岗位 JD。点击分析,系统调用 DeepSeek 生成匹配分数、技能差距和优化建议。后端对应接口:接口方法说明/api/v1/resume/uploadPOST上传并解析简历/api/v1/resume/analyzePOST根据简历和 JD 生成匹配分析/api/v1/resume/listGET获取当前用户简历列表/api/v1/resume/market-analysisPOST分析目标岗位市场要求/api/v1/resume/skill-guidancePOST生成技能补充指导7.2.4 生成项目推荐和实践路径系统会结合用户目标岗位、简历内容、岗位匹配差距和市场技能要求生成项目推荐。后端对应接口:接口方法说明/api/v1/project/searchGET使用 GitHub API 搜索开源项目/api/v1/project/refresh-recommendationsPOST使用 DeepSeek 刷新项目推荐/api/v1/project/{project_id}/guideGET生成项目复现指南/api/v1/project/{project_id}/practice-pathGET生成五阶段实践路径/api/v1/project/{project_id}/stage-progress/{stage_index}PUT更新实践阶段完成状态7.2.5 进行模拟面试进入"模拟面试"页面。选择面试类型和难度。系统根据简历和 JD 生成面试题。用户逐题作答。系统评估答案,可生成追问。完成后生成整体面试报告。后端对应接口:接口方法说明/api/v1/interview/sessionsPOST创建面试会话/api/v1/interview/sessions/{session_id}/answersPOST提交答案/api/v1/interview/sessions/{session_id}/completePOST完成面试并生成报告/api/v1/interview/sessions/{session_id}/reportGET查看面试报告/api/v1/interview/sessions/compareGET对比两次面试表现八、开发者空间 AI Shell 部署到华为云 ECS8.1 分析 ECS 部署资源完成代码开发后,打开开发者空间 AI Shell,输入提示词:帮我分析项目,我想将系统部署到ECS上,具体需要哪些资源,请帮我罗列一下AI Shell 会读取项目结构,识别该项目是前后端分离的面试模拟助手系统,并分析部署所需资源。AI Shell 推荐的最小化部署资源如下:ECS 弹性云服务器:s6.medium.2(1 vCPU / 2GB),Ubuntu 22.04,40GB SSDEIP 弹性公网 IP:5 Mbps 带宽,按流量或带宽计费VPC 虚拟私有云:默认配置,包含一个子网安全组:开放 80(HTTP)、443(HTTPS)、22(SSH)端口外部服务依赖:DeepSeek API、GitHub API部署架构:用户通过浏览器访问 EIP 公网 IPNginx 反向代理到 FastAPI 后端(端口 8000)FastAPI 服务处理业务逻辑,连接 SQLite 数据库前端静态资源由 Nginx 直接服务systemd 守护后端进程,确保服务高可用8.2 生成 Terraform 配置并调整代码继续在 AI Shell 中输入:帮我生成最经济的Terraform配置,并且帮我调整一下需要修改的部署要点AI Shell 先给出部署前需要修改的关键点:后端监听地址从 127.0.0.1 改为 0.0.0.0动态 API 地址配置,避免硬编码 localhostSECRET_KEY 改为环境变量优先CORS 配置支持环境变量 EXTRA_CORS_ORIGINS创建生产环境配置文件 backend/.env.production提供 Nginx 配置模板 deploy/nginx.conf提供 systemd 服务配置 deploy/interview-assistant.service随后 AI Shell 查询可用区、生成随机密码,并创建 Terraform 配置文件:terraform/providers.tf:HuaweiCloud Provider 配置terraform/variables.tf:变量定义terraform/main.tf:VPC、子网、安全组、ECS、EIP 资源配置terraform/outputs.tf:EIP、SSH 命令等输出8.3 执行 Terraform 创建云资源确认资源方案后,在 AI Shell 中输入:请使用以上配置,执行terraform applyAI Shell 执行:cd /root/interview_assistant/terraform terraform apply -auto-approveTerraform 创建了 10 个资源,包括 VPC、子网、安全组、安全组规则、ECS、EIP 和 EIP 绑定。部署完成后,ECS 状态为 ACTIVE。8.4 连接 ECS 并部署应用ECS 创建完成后,继续输入:帮我连接到ECS并部署应用AI Shell 通过 SSH / SFTP 将项目上传到 ECS,并执行部署脚本。部署脚本主要完成:安装系统依赖。安装 Node.js 18。创建 Python 虚拟环境。安装后端依赖。构建前端 dist。配置 Nginx。配置 systemd 服务。启动 interview-assistant 服务。验证前端、API 文档和健康检查接口。部署过程中修复的问题:问题处理方式pip install 冲突清理残留的超时进程后重新安装deploy/ 目录缺失配置文件补提交到 git,并通过 SFTP 上传DATA_DIR 权限错误从 /var/www/面试模拟助手 改为 /opt/interview_assistant/data最终部署结果:ECS: 49.4.115.167(s6.medium.2,Ubuntu 22.04)应用路径: /opt/interview_assistantPython 虚拟环境: /opt/interview_assistant/backend/venvNode.js: v18.20.8前端构建: /frontend/dist数据目录: /opt/interview_assistant/data配置文件: backend/.env.production待办事项:在 .env.production 中补充真实 DEEPSEEK_API_KEY,否则 AI 面试和分析功能无法正常调用。可选配置域名、SSL 证书和 GitHub Token。生产环境建议使用 SSH 密钥登录,替代密码登录。九、打包为 Windows 桌面应用9.1 构建前端静态资源cd D:\Huawei\frontend npm run build构建成功后会生成:D:\Huawei\frontend\dist后端 PyInstaller 打包时会把该目录复制到 _internal\web,由 FastAPI 挂载为静态页面。9.2 打包后端服务进入后端目录:cd D:\Huawei\backend python -m pip install -r requirements.txt python -m PyInstaller interview_assistant.spec --noconfirm --clean --distpath D:\Huawei\dist_backend构建产物:D:\Huawei\dist_backend\interview_assistant\interview_assistant.exe D:\Huawei\dist_backend\interview_assistant\_internal\web\index.htmlinterview_assistant.spec 中配置了 console=False,后端 exe 在 launcher 调用时不会弹出黑色命令窗口。9.3 打包桌面 launcherlauncher 位于 launcher/launcher.py,主要职责如下:读取安装目录下的 config.ini。查找可用端口,默认从 8000 到 8010。启动后端 interview_assistant.exe。等待 /health 接口可用。使用 pywebview 打开桌面小窗口,而不是跳转浏览器。在 Windows 下使用单实例锁,避免多次点击创建多个 launcher.exe。关闭窗口时终止后端进程。打包命令:cd D:\Huawei\launcher python -m pip install pyinstaller pystray Pillow pywebview python -m PyInstaller --onefile --windowed --name launcher --noconfirm --clean --distpath D:\Huawei\dist_launcher --workpath D:\Huawei\launcher\build --specpath D:\Huawei\launcher\build launcher.py构建产物:D:\Huawei\dist_launcher\launcher.exe9.4 生成安装包安装脚本位于:D:\Huawei\installer\setup.iss安装包会包含:后端目录 dist_backend\interview_assistant桌面启动器 dist_launcher\launcher.exe配置文件 installer\config.ini桌面快捷方式和开始菜单快捷方式生成安装包:Copy-Item -Path 'D:\Huawei\config.ini.template' -Destination 'D:\Huawei\installer\config.ini' -Force & 'C:\Users\User\AppData\Local\Programs\Inno Setup 7\ISCC.exe' 'D:\Huawei\installer\setup.iss' 输出文件:D:\Huawei\output\面试模拟助手_Setup_v1.0.0.exe9.5 使用一键构建脚本项目根目录提供 build.py,可一键执行前端构建、后端打包、launcher 打包和安装包生成:cd D:\Huawei python build.py可选参数:python build.py --skip-frontend python build.py --skip-backend python build.py --skip-launcher python build.py --skip-installer python build.py --clean 十、访问验证和效果确认10.1 验证前端页面ECS部署完成后,在浏览器访问AI shell给出的链接页面正常显示登录界面,表示前端静态资源已正确部署,Nginx 配置生效。10.2 验证 API 文档访问:http://xx.x.xxx.xxx/docs返回 FastAPI 自动生成的交互式 API 文档,表示 FastAPI 服务正常运行,并且 Nginx 已正确代理 API 文档。10.3 验证健康检查接口访问:http://xx.x.xxx.xxx/health或在服务器中执行:curl http://xxx.x.x.x:xxxx/health服务正常时返回健康状态信息。10.4 验证 systemd 服务在 ECS 上执行:systemctl status interview-assistant如果状态为 active (running),说明后端服务已经由 systemd 守护。后续服务器重启后,也可以自动拉起应用。十一、释放资源11.1 清理云资源ECS、EIP 和 EVS 均可能产生按需费用。体验完成后,如果不再使用,请在 AI Shell 中输入:帮我清理所创建的华为云资源或进入 Terraform 目录执行:cd /root/interview_assistant/terraform terraform destroy执行前请确认已经备份需要保留的数据,例如 SQLite 数据库、上传的简历文件和 .env.production 配置。11.2 清理本地构建产物如果只清理本地 Windows 构建产物,可在项目根目录执行:cd D:\Huawei python build.py --clean 该命令会清理:frontend/distbackend/distbackend/builddist_backenddist_launcheroutput11.3 卸载桌面应用如果通过安装包安装,可在 Windows"应用和功能"中卸载"面试模拟助手",或使用开始菜单中的卸载入口。卸载时安装脚本会尝试清理安装目录下的后端文件:{app}\backend用户数据默认保存在 %APPDATA%,用于保留登录账号、简历、项目、面试记录和 API Key 配置。需要彻底清理时,可手动删除:%APPDATA%\面试模拟助手删除该目录会清空本地数据库、上传文件、日志和所有用户 API Key 配置,请提前确认是否需要备份。十二、扩展资料说明12.1 技术文档华为云码道(CodeArts)代码智能体:https://codearts.huaweicloud.com/华为云 ECS 文档:https://support.huaweicloud.com/ecs/华为云 VPC 文档:https://support.huaweicloud.com/vpc/FastAPI 官方文档:https://fastapi.tiangolo.com/SQLAlchemy 官方文档:https://docs.sqlalchemy.org/
  • [高校训练营] 基于华为云码道与 ModelArts MaaS 的原创智能论文阅读学习助手PaperLens
    基于华为云码道与 ModelArts MaaS 的原创智能论文阅读学习助手PaperLens案例类型:AI 应用开发 / 智能阅读 / 开发者工具实践适用对象:高校学生、科研入门者、需要精读英文论文的个人用户在线体验:http://101.245.81.114代码仓库:falconousZhang/PaperLens_final参考体例:华为云开发者空间实战案例1. 案例介绍1.1 项目背景在阅读英文科研论文时,初学者经常遇到以下问题:PDF 排版复杂,正文、公式、图表与双栏文本之间缺乏清晰的阅读引导;论文中包含大量专业术语和长句,逐句翻译耗时,简单机翻又难以解释原理;阅读过程中产生的高亮、笔记和问题分散在不同工具中,难以形成连续的学习记录;通用大模型不了解当前论文上下文,容易给出脱离原文、缺少依据的回答;传统审稿工具更偏向评价论文质量,并不完全适合个人“读懂论文、掌握方法”的目标。PaperLens 因此被设计为一款 AI 驱动的个人论文阅读学习助手。系统以原始 PDF 为阅读主体,在不破坏论文版式的前提下,将总结、翻译、选中文字解释、论文问答、高亮、笔记、批判性阅读和学习报告导出整合到同一个工作台中。1.2 建设目标项目的核心目标不是替代用户阅读,而是降低进入论文内容的门槛,并让 AI 的每一次回答都尽量与论文原文建立联系。具体目标包括:保留原始 PDF 排版,提供逐页阅读体验;支持页面总结、全文翻译和选中文字解释;建立论文级多轮问答,让模型结合全文和历史对话回答;支持原文高亮与笔记,并按论文、页码进行管理;提供用户注册登录、数据隔离和管理员治理能力;将学习解释、笔记、批判性阅读等内容汇总导出为 Markdown、PDF 或 DOCX;使用低成本华为云资源完成可访问、可演示的部署。1.3 案例成果PaperLens 已形成从论文上传到学习资料沉淀的完整闭环:注册/登录 ↓ 上传 PDF → 文本与版式解析 → 进入逐页阅读工作台 ↓ ↓ 论文库管理 总结 / 翻译 / 选中文字解释 ↓ ↓ 阅读进度 多轮论文问答 ↓ ↓ 高亮与笔记 ← 原文定位与交互 → 批判性阅读 └───────────────┬───────────────┘ ↓ 学习报告导出图 1 PaperLens 论文库:集中展示论文解析状态、阅读进度、高亮与笔记数量,并支持继续阅读和论文管理。项目已部署在华为云 ECS,使用华为云 ModelArts Studio(MaaS)提供真实大模型推理能力,并通过 Docker Compose 运行前端、后端和 PostgreSQL。2. 整体解决方案2.1 方案概述PaperLens 采用前后端分离架构。浏览器负责 PDF 页面展示、文本选择和学习交互;后端负责用户权限、论文解析、任务状态、模型调用、数据持久化和报告生成。大模型能力通过统一的 LLMClient 抽象接入,当前实际部署使用华为云 ModelArts Studio(MaaS)的对话模型服务。系统遵循三个设计原则:原文优先:左侧始终展示原始 PDF 页面,AI 结果作为辅助信息显示在右侧;来源可追溯:解析时记录页码、字符区间和文本块位置,学习内容可以重新定位到原文;任务可恢复:耗时操作以任务状态保存,页面刷新后可以恢复轮询,不依赖一次 HTTP 连接持续到模型返回。2.2 技术选型层次技术或服务作用前端Vue 3、TypeScript、Vite、Pinia、Vue Router、Axios阅读工作台、状态管理、路由保护和 API 调用后端Python、FastAPI、Pydantic、SQLAlchemyREST API、业务服务、参数校验和数据访问数据库PostgreSQL 16、Alembic用户、论文、页面、问答、解释、笔记、任务和审计数据PDF 处理PyMuPDF、pdfplumber页面渲染、正文提取、文本块定位和表格识别大模型华为云 ModelArts Studio(MaaS)、GLM-5.2总结、翻译、选中文字解释、论文问答和批判性阅读报告ReportLab、python-docxMarkdown、PDF、DOCX 学习报告生成部署华为云 ECS、VPC、安全组、弹性公网 IP、Docker Compose、Nginx单机容器化部署与公网访问研发辅助华为云码道(CodeArts)代码智能体需求理解、跨文件编码、测试设计、问题定位和部署调试2.3 开发环境与云资源准备本案例将“开发工具”和“运行资源”明确分开。码道、Rules 与 Skills 只在研发阶段使用,不会随应用一起部署;真正运行 PaperLens 时只需要前端、后端、数据库、文件卷和 MaaS 服务。类别本案例配置说明本地开发Windows、Git、Docker Desktop、Node.js、Python编码、容器联调和定向验收智能研发华为云码道(CodeArts)代码智能体、项目级 Rules、开发 Skills需求设计、编码、测试资产与问题定位大模型服务ModelArts Studio(MaaS)兼容对话接口由统一 LLMClient 调用,密钥仅通过环境变量注入云服务器华为云 ECS,Ubuntu 22.04,2 vCPU、4 GiB、40 GiB小规模实习项目的单机部署网络VPC、子网、安全组、弹性公网 IP,5 Mbit/s公网只开放 Web 入口和受限 SSH容器运行Docker Engine、Docker Compose运行 Nginx、FastAPI 和 PostgreSQL为控制成本,当前实际部署没有单独购买 RDS、OBS、ELB 或 Kubernetes。数据库与文件使用 ECS 上的 Docker 持久卷;项目保留向 RDS 和 OBS 演进的接口与部署资料,但案例不会把“已经设计”描述成“已经购买并运行”。3. 系统架构设计3.1 逻辑架构3.2 分层设计表现层前端采用 Vue 3 + TypeScript。核心页面包括登录注册、论文库、上传页面、论文阅读工作台、批判性阅读结果、报告导出和管理员控制台。论文阅读工作台采用左右分栏布局:左侧按页显示原始 PDF 图像,并叠加可选择的透明文本层;右侧在“学习解释、论文问答、学习记录”之间切换;用户选择原文后,可以直接创建黄色高亮、绿色笔记或发起通俗解释;点击历史解释时,系统自动跳转到对应页并高亮来源文本。接口层FastAPI 对外提供统一的 /api/v1 接口,按领域拆分为认证、论文、任务、学习解释、问答、论文库、学习记录、导出和管理员接口。Pydantic 负责输入输出边界,统一异常处理避免将数据库语句、文件路径或上游响应泄露给前端。业务层业务逻辑集中在 Service 层:pdf_parser:正文、章节、文本块、表格和 Evidence 解析;learning_service:页面总结、翻译与选中文字解释;qa_service、qa_retriever:论文级多轮问答和证据检索;highlight_service、note_service:高亮、笔记与原文锚点;review_service:批判性阅读;export_service、report_converter:学习报告组织与格式转换;admin_service:用户治理、内容元数据查询和审计。数据层PostgreSQL 保存结构化业务数据,Docker Volume 保存 PDF、页面图像和导出报告。数据库迁移由 Alembic 管理,容器启动时先执行迁移,再启动后端服务。3.3 核心数据流论文上传与解析用户上传 PDF,后端校验扩展名、文件头、大小和文件哈希;文件写入受控存储目录,创建论文记录和解析任务;PyMuPDF 提取页面正文、页面尺寸和文本块坐标;pdfplumber 尝试提取表格,表格失败不影响正文解析;系统生成页面、章节、文本分块和 Evidence 数据;论文状态更新为 PARSED,前端进入阅读工作台。学习解释用户选择总结、翻译,或在 PDF 文本层中选择一段原文;后端根据页码、字符区间和论文归属校验来源;系统构造带有明确边界的 Prompt,并调用 MaaS;模型输出经过结构校验和清洗后持久化;前端轮询任务状态,并将结果与对应页和选区关联。论文问答首次提问时,后端读取论文全文,在长度预算内构造论文上下文;后续提问同时附加最近的历史问答。检索模块优先识别问题中的页码、表号、图号等显式引用,再结合文本相关性选择候选证据。问答记录保存在会话中,用户可以切换、滚动查看或删除历史会话。3.4 华为云部署架构本案例定位为小规模实习项目,因此优先选择低成本、易维护的单机方案,而不是引入复杂的微服务集群。部署中只将 Nginx 的 80 端口发布到公网,后端 8000 和数据库 5432 仅在 Docker 私有网络中访问。数据库和文件目录使用持久卷,容器设置健康检查与 restart: unless-stopped。当前演示环境使用 HTTP;正式生产环境应增加域名、HTTPS 证书并启用 Secure Cookie。3.5 工程目录设计项目采用按前端、后端、部署和设计资料分区的单仓库结构。核心目录如下:PaperLens/ ├── backend/ │ ├── paperlens/ │ │ ├── api/ # FastAPI 路由与认证边界 │ │ ├── core/ # 配置、安全、错误和可观测性 │ │ ├── models/ # SQLAlchemy 业务模型 │ │ ├── schemas/ # Pydantic 请求与响应契约 │ │ └── services/ # 解析、解释、问答、记录和导出服务 │ ├── alembic/ # 数据库迁移链 │ └── tests/ # 后端测试资产 ├── frontend/ │ ├── src/api/ # API 客户端 │ ├── src/components/ # 阅读工作台组件 │ ├── src/stores/ # Pinia 状态 │ └── src/views/ # 登录、论文库、阅读、管理等页面 ├── deploy/huawei/ # 单 ECS 与生产化部署配置 ├── ProjectDocs/ # 需求、架构、页面、测试和 SDD 设计资料 ├── docker-compose.yml # 本地开发编排 └── README.md这种结构使码道能够先从设计资料理解约束,再定位到对应领域的路由、Schema、Service、模型和前端页面,减少把业务逻辑堆进单个文件的情况。3.6 核心数据模型PaperLens 的数据模型围绕“用户—论文—页面内容—学习行为”展开:数据域核心实体设计要点认证users、auth_sessions、password_reset_tokens角色、状态、刷新令牌轮换和密码重置论文papers、paper_pages、paper_sections、paper_chunks论文归属、解析状态、逐页正文和章节结构原文定位evidences、paper_tables页码、引用文本、字符区间、边界框和表格结构学习解释learning_explanations、learning_citations模式、范围、任务状态、来源引用和失败恢复论文问答paper_qa_conversations、paper_qa_turns、paper_qa_citations多轮顺序、幂等请求、上下文哈希和证据绑定学习记录paper_library_entries、paper_highlights、paper_notes阅读进度、黄色高亮、绿色笔记和原文锚点扩展分析analysis_tasks、review_results、metric_records、experiment_results批判性阅读、指标与实验理解导出与治理export_reports、admin_audit_logs报告状态、文件信息和管理员不可变审计任务型实体统一使用 PENDING → RUNNING → SUCCEEDED/FAILED 状态机。模型调用前结束数据库事务,模型返回后再用新事务写入结果,避免在外部网络等待期间长期持有连接或行锁。4. 使用华为云码道(CodeArts)代码智能体辅助开发与调试4.1 使用方式PaperLens 的功能跨度较大,涉及前端交互、后端 API、数据库迁移、PDF 处理、大模型调用和云端部署。项目使用华为云码道(CodeArts)代码智能体辅助理解代码库、拆解需求、生成跨文件代码、补充测试以及定位运行故障。项目没有采用“一次性生成整个系统”的方式,而是将开发过程拆成可验证的小阶段:明确用户目标 ↓ 形成单轮任务提示词和边界 ↓ 码道理解代码库并完成跨文件实现 ↓ 集中进行定向测试、构建或实际操作验收 ↓ 根据日志和页面现象定位问题 ↓ 码道完成同轮修正 ↓ 进入下一功能阶段4.2 Rules 与 Skills 工程化约束为了让智能体在长期迭代中保持一致,项目为码道配置了项目级规则和技能工作流,主要覆盖:需求细化与架构设计;页面原型和交互约束;前后端测试设计;功能详细设计与任务拆解;Sprint 进度管理;Bug 修复记录。每次给码道的任务都会说明目标、允许修改的范围、禁止事项、接口契约、数据一致性要求和验收方式。相比只描述“实现某功能”,这种结构化提示词能够降低跨文件修改遗漏、重复造轮子和无关重构的概率。4.3 提示词管理与迭代方法PaperLens 没有把码道提示词当作一次性聊天内容,而是为每个开发阶段保留任务编号、目标、约束、验收标准和后续状态。项目共形成 P1~P8.4 的 32 轮阶段提示词归档,使需求变化、实现边界和技术决策能够回溯。一条可执行的码道提示词通常包含以下结构:# 码道下一阶段提示词:<阶段编号与名称> ## 任务目标 - 本轮只解决什么问题 - 完成后用户能获得什么能力 - 与既有功能的关系 ## 开始前边界与真实基线 - 必读的设计文档和真实代码 - 当前迁移、接口、容器和功能状态 - 必须保护的用户数据与现有修改 - 禁止读取的密钥、令牌和环境信息 ## 设计与实现要求 - 数据模型、状态机和迁移规则 - API 请求/响应与错误语义 - Service、前端交互和安全边界 - 并发、幂等、事务和失败恢复 ## 测试资产与验收 - 只编写少量关键测试资产 - 码道不运行测试、构建、迁移或 Docker 命令 - 集中验收阶段执行定向测试、关键烟测和前端构建 ## 完成定义 - 允许修改的文件 - 必须同步的设计与 Sprint 文档 - 实际完成项、未完成项和风险必须如实报告提示词也随项目实践逐步演进:早期更强调从零搭建和运行验证;中期增加数据模型、API 契约、并发和安全约束;后期为了提高效率,码道只负责编写或更新少量测试资产,不在实现轮次运行耗时测试,测试执行统一放到集中验收阶段。4.4 代表性码道提示词节选以下内容选自项目实际提示词归档。为适合作为案例展示,省略了较长的文件清单、历史统计值和重复性约束,但保留了当轮的目标、关键边界与完成定义。提示词一:建立可运行工程骨架使用场景:项目初期先统一技术选型和数据契约,避免前后端、数据库和设计文档各自演进。你现在负责继续开发 D:\shixi\PaperLens 项目。 本轮目标不是一次性完成整个系统,而是完成“规格修正 + 可运行工程骨架”, 为后续端到端 MVP 开发建立稳定基础。 一、必须采用的 MVP 决策 1. 前端使用 Vue 3 + TypeScript + Vite + Pinia + Vue Router。 2. 后端使用 FastAPI + SQLAlchemy + Alembic。 3. 本地和部署均使用 PostgreSQL,不使用 SQLite。 4. Evidence 必须记录 page_number、quoted_text、bbox、char_start、 char_end、section_id 和 chunk_id,保证前端后续能够定位原文。 5. 上传统一使用 multipart 流式上传,最大 50 MB。 6. 后台任务进度统一使用 HTTP 轮询,暂不引入 WebSocket。 7. MVP 只支持可提取文本的 PDF,OCR 放入后续版本。 8. LLM 必须通过统一 LLMClient 调用,默认提供 MockLLMClient。 二、本轮交付 - 创建 backend、frontend、docker-compose.yml、.env.example 和 README。 - 后端实现健康检查、配置、数据库连接、ORM、首个迁移和统一错误结构。 - 前端实现基础路由、Pinia、首页、健康检查及后端不可用提示。 - Compose 只包含 PostgreSQL、backend 和 frontend。 三、边界 - 不写入真实密钥,不初始化或提交 Git。 - 不引入 Celery、Redis、Nginx、FAISS 或真实云服务。 - 不生成大量空接口或只有 pass 的占位代码。 - 本轮到“工程骨架可以启动、模型和契约自洽”为止, 不继续实现 PDF 解析和真实 LLM 功能。落地结果:码道完成了 FastAPI、Vue、PostgreSQL 与 Docker Compose 的基础工程,并建立了后续一直沿用的 Evidence 定位字段和 LLMClient 抽象。提示词二:接入华为云 ModelArts MaaS使用场景:在 Mock 模型链路已经可用后,增加真实华为云模型适配器,同时保证本地开发不依赖云端密钥。# P3.3 华为云 MaaS 真实生成式模型适配器 ## 任务目标 在不改变现有审阅 API、数据库模型和前端的前提下, 把 LLMClient 从“只有 Mock 实现”扩展为可配置的 HuaweiMaaSLLMClient。 默认本地和测试仍使用 MockLLMClient。 ## 实现边界 1. 复用现有 httpx,不新增第三方模型 SDK、requests 或重试库。 2. 华为 MaaS 适配器使用标准 chat/completions 请求结构。 3. endpoint、model、API Key、连接超时和读取超时全部来自 Settings。 4. API Key 使用安全类型保存,不得出现在日志、异常、响应或 repr 中。 5. 不修改公开 API、ORM、Alembic、Docker 和前端。 6. 云接口测试必须使用 MockTransport,禁止真实联网和产生费用。 ## 响应与失败处理 - 校验 HTTP 状态、响应 JSON、choices、message 和 content。 - 兼容模型返回单个完整 Markdown JSON 围栏。 - 拒绝前后杂文、多对象、字段缺失和未知字段。 - 上游失败统一转换为安全业务错误,不把响应正文或密钥返回给客户端。 ## 完成定义 - Mock 与 Huawei MaaS 通过同一 LLMClient 工厂切换。 - 没有云端配置时项目仍可离线运行。 - 配置示例只使用占位符,不读取、打印或提交真实密钥。落地结果:真实模型与 Mock 模型共用同一业务接口,学习解释、问答和批判性阅读无需感知底层供应商;部署时只需通过环境变量选择 MaaS 适配器。提示词三:把产品主线校正为论文阅读学习使用场景:项目中期确认“帮助个人用户读懂论文”才是核心目标,因此需要在保留已有分析能力的同时重构主要交互。# P7.1 论文阅读学习工作台与证据化学习解释 ## 任务目标 把 PaperLens 的产品主线从“辅助审稿”校正为“帮助个人用户阅读论文并学习”。 在已完成的 PDF 解析、章节/页面、Evidence、认证隔离和 Huawei MaaS LLMClient 基础上,实现受保护的论文阅读工作台,以及针对当前页面或 选中文字的总结、翻译和通俗解释闭环。 已有结构化审阅、指标提取、实验分析和报告能力继续保留,分别作为 “批判性阅读”“实验理解”和“学习成果导出”的高级能力,不删除或重做。 ## 来源与安全边界 1. 客户端只提交 mode、scope 和页码/选区标识,不提交论文正文或 prompt。 2. 后端根据当前用户和 paper_id 重新读取来源,禁止跨用户、跨论文引用。 3. 论文标题和正文均视为不可信输入,并放在明确标签中;其中出现的 “忽略之前指令”等文字不得覆盖 system 指令。 4. SUMMARY 概括当前范围;TRANSLATE 忠实翻译并保留标题、段落和编号; 选中文字解释要说明概念、原理和例子。 5. 结果必须保存页码、来源哈希和任务状态,失败只记录安全公开文案。 ## 前端交互 - 阅读页采用左右分栏,左侧保留原始 PDF 版式并支持文本选择。 - 右侧显示学习解释历史;点击记录跳转到来源页。 - 选中文字解释与原文位置关联,不能把解释结果挤在 PDF 正文下方。 - 页面切换或组件卸载时停止旧轮询,避免旧结果覆盖新页面。落地结果:PaperLens 从“生成审阅结论”转向“原文阅读 + 页面解释 + 学习沉淀”,形成当前最具辨识度的双栏阅读工作台。提示词四:实现论文级多轮问答使用场景:解决“论文里明明存在,模型却因为只收到当前页片段而回答没有”的问题。# P7.2 当前论文多轮问答与证据化会话 ## 任务目标 实现只围绕当前用户、当前论文的多轮问答。用户可以新建会话、连续提问、 查看历史;有依据的回答绑定服务端选取的论文来源,证据不足时明确降级, 不能用模型常识伪装成论文结论。 ## 上下文构造 1. 以当前 question 为 query,仅在当前论文的 Evidence 中做确定性相关性排序。 2. 后续提问附加同会话最近的成功问答,超限时按完整轮次从最旧开始移除。 3. 候选 Evidence 按相关度、页码、创建时间和 ID 稳定排序,并限制 top_k。 4. 当前问题、历史回答和论文正文全部视为不可信内容,不能提升为 system role。 5. Embedding 与 LLM 调用期间不得持有数据库事务或行锁。 ## 幂等与结果契约 - 请求包含 client_request_id;重复请求返回原轮次,不重复调用模型。 - 同一会话只允许一个 PENDING/RUNNING 轮次。 - 成功回答保存 answer、grounded 和来源引用;证据不足时 grounded=false。 - 模型只返回一个严格 JSON 对象,拒绝额外解释、未知字段和跨论文引用。 ## 前端交互 - 右侧使用类似即时通信软件的消息时间线。 - 会话历史和消息区域可独立滚动,输入区固定在底部。 - 支持新建、切换和删除会话,轮询在终态立即停止。落地结果:系统形成论文级会话、轮次、上下文预算和幂等机制。真实论文验证中发现仅依赖少量 Evidence 会漏掉跨页图表后,后续迭代又将策略调整为“首次提问按预算提供全文基础上下文,后续附加历史,并优先识别页码、表号和图号”,体现了设计根据实际效果继续修正的过程。提示词五:准备华为云部署与安全收口使用场景:开发轮次结束后,为 ECS 部署、备份恢复和后续云资源演进准备可复用资产。# P8.4 华为云部署、备份恢复与综合安全验收 ## 任务目标 在既有论文阅读学习、登录注册、管理员、任务恢复和限流能力基础上, 补齐华为云部署配置、备份恢复说明和安全清单,使项目达到 “代码与部署资料完整,等待真实云环境验收”的状态。 ## 真实性要求 1. 不实际购买、创建、修改或删除华为云资源。 2. 不把“部署资产已完成”写成“真实云上已经部署”。 3. 示例只能使用占位符,禁止读取或写入 API Key、AK/SK、JWT Secret、 数据库密码、真实 IP、域名和证书私钥。 4. 码道只编写代码、少量测试资产、部署配置和文档, 不运行测试、构建、迁移、Docker、HTTP 或真实云服务命令。 ## 部署资产 - 提供 deploy/huawei 下的环境示例、Compose、Nginx、部署和回滚说明。 - 后端和数据库不直接暴露公网;只由 Nginx 代理同源 /api/。 - 容器使用非 root、只读文件系统、tmpfs、no-new-privileges、 healthcheck、资源上限和 restart policy。 - Secret 通过受限环境文件或 secret 文件注入,entrypoint 不打印内容。 - 给出 VPC、安全组、ECS、MaaS、健康检查和小额验证的人工配置顺序。 ## 完成定义 区分“代码与部署资产已实现”“离线验收尚未执行” 和“真实华为云资源尚未创建/验证”三种状态,不夸大完成度。落地结果:项目形成单 ECS 演示编排和面向生产化演进的配置资料;真实部署时又根据小规模需求选择 PostgreSQL 与文件卷同机运行,避免为了案例展示购买不必要资源。4.5 码道参与的主要开发阶段阶段码道辅助内容形成的结果工程骨架分析前后端技术栈,生成 FastAPI、Vue、PostgreSQL、Docker 基础结构可运行的前后端与数据库环境PDF 解析实现上传校验、页面解析、章节识别、文本块坐标和 Evidence 生成从 PDF 到可阅读、可定位数据的链路大模型接入抽象 LLMClient,实现 Mock 与 Huawei MaaS 适配器本地离线开发与云端真实模型可切换阅读工作台跨文件实现 PDF 页面、透明文本层、左右分栏和选区工具条保留原版式的交互式阅读体验学习解释设计总结、翻译、选中文字解释的 Prompt、状态机和历史记录面向学习场景的 AI 辅助阅读能力论文问答设计会话、轮次、全文上下文、历史预算和检索策略支持论文内连续提问用户与管理实现 JWT、刷新令牌、用户隔离、管理员角色和审计完整登录注册与管理闭环报告导出组织解释、高亮、笔记和批判性阅读内容Markdown、PDF、DOCX 学习报告云端部署分析 Docker、Nginx、卷权限和 MaaS 日志在小规格华为云 ECS 上稳定运行4.6 码道辅助调试实例实例一:测试数据误写开发库早期测试虽然创建了测试数据库,但应用在模块导入时已经初始化了指向开发库的数据库连接,导致部分测试仍可能写入开发库。码道根据数据库记录变化和初始化顺序分析问题,协助调整为延迟配置数据库 Engine,并增加测试库名称守卫、迁移失败即终止和测试残留检查。解决后,测试环境明确使用 paperlens_test,避免自动化测试污染真实论文数据。实例二:真实 MaaS 输出格式不稳定Mock 模型始终返回标准 JSON,但真实模型可能返回 Markdown 代码围栏、额外解释或字段缺失。码道协助增加严格的响应解析、围栏兼容、字段校验和安全失败状态;学习解释还使用独立的较长读取超时,避免长页翻译被普通问答的超时配置提前终止。实例三:Docker 容器在 ECS 上反复重启部署时 Nginx 采用只读文件系统,但默认尝试在 /var/cache/nginx 创建临时目录,导致容器因权限不足反复重启。码道根据容器日志定位到临时目录问题,将相关目录调整到 /tmp,并通过受限 tmpfs 提供可写空间。后端文件卷也曾因宿主卷所有权不匹配导致上传失败,随后增加一次性的 storage-init 服务,在后端启动前修正目录所有者和权限。实例四:公网 HTTP 下论文问答误报网络失败浏览器在普通 HTTP 环境下不能保证提供 crypto.randomUUID()。前端在创建会话后生成幂等请求 ID 时抛出本地异常,因此服务器只看到会话被创建和删除,没有收到真正的问题请求。码道根据前后端访问日志定位到请求链中断位置,增加基于 getRandomValues 的 UUID v4 回退逻辑,并区分本地运行异常和真实网络异常。这些问题说明,代码智能体的价值不仅是生成代码,还包括结合代码库、日志、运行环境和数据状态完成工程化定位。5. 功能解决方案设计5.1 原版式 PDF 阅读系统不将 PDF 正文简单转换成连续纯文本,而是为每一页生成页面图像,同时输出带坐标的文本层。页面图像保证视觉排版与原论文一致,透明文本层负责文字选择、字符偏移计算和高亮交互。这种设计兼顾了两个目标:用户看到的是原论文版式、图片、公式和表格;系统仍能知道用户选中了哪段文字,并把操作绑定到页码和字符区间。图 2 论文阅读工作台:左侧按页保留原始 PDF 版式,右侧统一承载学习解释、论文问答和学习记录。5.2 页面级学习解释“总结”和“翻译”按页生成并保留历史:总结要求覆盖当前页的各级标题;如果一个段落延续到下一页,可读取有限的下一页上下文补全含义;翻译要求保留标题层级和正文段落,不将公式、编号和专有名词随意改写;选中文字解释只处理用户选择的原文,重点说明概念、原理和例子。所有解释按页排序。点击历史记录可跳转到来源页;选中文字解释打开时,对应原文保持蓝色高亮。图 3 页面完整翻译:在保留标题、作者信息和正文层次的基础上,对当前页内容进行中文翻译。图 4 选中文字解释:左侧原文保持蓝色定位高亮,右侧从概念、原理和示例角度给出通俗说明。5.3 论文级多轮问答论文问答采用类似即时通信软件的对话界面。系统保存完整会话历史,输入区固定在底部,消息区域可独立滚动。为了减少“论文里明明有,模型却回答没有”的情况,后端不只发送当前页摘要,而是在首次提问时组装论文全文上下文;后续轮次再附加历史问答,并对超长内容执行可预测的长度预算。对页码、图号、表号等问题,检索器给予显式引用更高优先级。图 5 论文级多轮问答:用户可以围绕指定页码、表格或方法连续追问,系统保留会话历史并结合论文内容回答。5.4 高亮和笔记用户可以直接在 PDF 上选择文字:高亮以黄色保存;笔记以绿色标记,并保存笔记正文;选中文字解释使用蓝色定位;学习记录只展示当前页的高亮和笔记。记录同时保存原文、页码、字符起止位置和来源哈希。当论文内容或解析结果发生变化时,系统可以识别来源不一致,避免错误定位。图 6 高亮与笔记:黄色标记用于原文高亮,绿色标记关联学习笔记,右侧仅展示当前页的学习记录。5.5 用户、权限与管理员系统系统支持注册、登录、刷新令牌、退出、修改密码、忘记密码和个人资料。密码使用 Argon2 哈希;访问令牌采用 JWT,刷新令牌使用 HttpOnly Cookie,并具有轮换和重放检测机制。所有论文、解释、问答和学习记录均按 user_id 隔离。管理员可以查看系统概况、管理用户状态和角色、只读查看跨用户内容元数据,关键操作写入不可变审计记录。5.6 学习报告导出报告不再限定为“审阅报告”。即使论文没有执行批判性阅读,只要存在学习解释、高亮或笔记,也可以生成学习报告。报告按页组织内容,并可选择是否加入批判性阅读、指标或实验信息,最终导出为 Markdown、PDF 或 DOCX。图 7 学习报告导出:支持 PDF、DOCX 和 Markdown,固定汇总学习解释、高亮摘录和学习笔记,并可按需加入扩展分析。6. 核心技术难点与解决思路6.1 PDF 视觉版式与文本交互难以兼得难点:直接展示 PDF 可以保留版式,但难以稳定获取选中文字的字符位置;只展示解析文本又会破坏双栏、图表和公式布局。解决思路:采用“页面图像 + SVG 透明文本层 + 解析文本索引”的三层结构。图像负责视觉,文本层负责浏览器选择,后端标准化文本负责字符区间与学习记录。6.2 解析结果存在不确定性难点:不同 PDF 的字体、编码、文本顺序和表格结构差异很大,单个表格解析异常可能导致整个事务失败。解决思路:对正文、章节、表格和 Evidence 分阶段处理;表格写入使用嵌套事务或降级策略,使局部失败不影响论文正文;对扫描版 PDF 明确返回不支持 OCR,而不是生成不可用结果。6.3 大模型回答必须与论文上下文绑定难点:如果只传当前页或少量 Evidence,模型可能无法理解跨页图表;如果直接无限制传全文,又会超过上下文或增加费用。解决思路:采用“全文基础上下文 + 当前页优先 + 显式页码/图表引用检索 + 历史轮次预算”的组合策略。系统保存上下文哈希和请求幂等键,避免同一问题被重复提交。6.4 模型输出和网络调用不稳定难点:真实模型可能返回围栏文本、非标准字段或较长推理内容;长页翻译比普通问答耗时更长。解决思路:统一 LLMClient 接口,设置连接与读取超时边界;不同任务可以覆盖单次读取超时;模型结果经过严格 Pydantic 校验,失败时写入安全的任务状态,不将上游响应和密钥返回给用户。6.5 异步任务与页面状态一致性难点:解析、解释、问答和导出都不是瞬时操作。快速切换论文或页面时,旧请求可能晚于新请求返回并覆盖界面。解决思路:后端采用持久化任务状态和原子认领,前端采用受控轮询、代次标识和组件卸载清理。刷新页面后重新查询活动任务,终态立即停止轮询。6.6 小规格 ECS 上的资源与可靠性难点:2 vCPU、4 GiB 内存同时运行镜像构建、数据库、后端和前端时容易出现内存压力;公网拉取 Docker Hub 镜像也可能超时。解决思路:配置交换分区和容器资源上限;使用多阶段构建缩小运行镜像;通过华为云 SWR 镜像加速拉取基础镜像;只运行单后端实例和小连接池,避免为实习项目引入 Redis、Celery、Kubernetes 等额外组件。7. 安全与可靠性设计真实 API Key、数据库密码和 JWT Secret 通过环境文件注入,不进入代码仓库;上传文件校验后缀、PDF magic、大小和存储路径,防止路径穿越;数据查询统一校验资源所有者,管理员接口使用独立权限保护;日志只记录请求 ID、阶段和安全错误分类,不记录论文全文、令牌或 MaaS 响应正文;后端与数据库不直接暴露公网端口;容器启用 no-new-privileges,前端使用只读文件系统;提供 live/ready 健康检查、启动恢复和容器自动重启;自动化测试使用独立测试数据库,并在测试前后检查数据残留。8. 项目实施过程8.1 分阶段建设项目采用逐阶段增量开发,每一阶段都对应独立提示词、设计更新和可验收结果。阶段主要任务阶段出口P1FastAPI、Vue、PostgreSQL、Docker 工程骨架首页与健康检查可运行,迁移链建立P2PDF 上传、解析、章节、页面、文本块、表格和 Evidence论文可从文件转换为结构化、可定位内容P3MockLLM、Embedding、Huawei MaaS、结构化结果前端真实模型与离线模型可以切换P4指标抽取、实验数据统计和模型运行配置模型理解与确定性计算分离P5实验文件导入、校验、比较和可视化论文实验结果可结构化分析P6Markdown、PDF、DOCX 报告分析结果可以形成文件交付P7阅读工作台、学习解释、多轮问答、高亮和笔记产品主线转为个人论文阅读学习P8登录注册、管理员、审计、恢复、限流、部署与安全形成完整用户系统并具备云端运行条件其中 P7 是产品方向最重要的一次调整。项目没有删除已经实现的审阅、指标和实验功能,而是把它们移动为“批判性阅读”和“实验理解”等高级入口,主路径改为上传论文后直接进入逐页阅读工作台。8.2 设计文档与任务追踪码道在编码前先同步以下设计层:需求细化:确认用户目标、功能范围、非目标和冲突决策;架构设计:明确前后端边界、外部 MaaS、任务与存储关系;数据模型:定义实体、外键、状态机、索引和迁移安全;API 设计:固定请求字段、响应结构、权限和错误语义;页面设计:固定路由、页面状态和交互行为;测试设计:只保留正常路径、关键失败和必要恢复场景;SDD 与 Sprint:把需求映射到具体设计、文件和任务状态。这种做法解决了长周期智能体开发中常见的“上一轮约束在下一轮丢失”问题。提示词不再重复粘贴整个项目,而是引用稳定设计资料,再补充本轮真实基线和差异要求。8.3 集中验收策略为了避免码道在每个实现轮次反复执行耗时的全量测试,项目后期采用“实现与验收分离”策略:码道负责更新必要测试资产,但提示词明确禁止运行测试、构建、迁移往返、Docker 重建和 HTTP 烟测;实现轮次完成后,先检查实际改动范围和接口契约;后端默认只运行受影响模块的定向测试;前端变更运行相关 Vitest 和一次生产构建;只保留一条关键业务烟测,例如“上传 PDF → 解析 → 进入阅读页”;认证、迁移链、共享基础设施或最终发布才执行更完整的回归。单个新功能通常只设计 1 个正常用例、1 个重要失败用例,以及在确有并发或恢复风险时增加 1 个对应场景。该策略更符合个人实习项目的成本与风险水平。8.4 华为云 ECS 部署过程部署采用单机 Compose,核心步骤如下:创建 VPC、子网、安全组、弹性公网 IP 和 Ubuntu 22.04 ECS;安全组开放 80,并将 22 端口来源限制为当前管理 IP;安装 Docker Engine 与 Compose,配置华为云 SWR 镜像加速;将代码发布包上传到 /opt/paperlens,检查校验和后解压;创建权限为 600 的部署环境文件,交互式写入数据库密码、JWT Secret 和 MaaS Key;使用 docker-compose.single.yml 构建并启动服务;检查容器状态、前端健康检查和后端 readiness;在浏览器完成注册、上传论文、学习解释和论文问答验证。示例命令中的配置均使用占位符,不包含真实凭据:cd /opt/paperlens chmod 600 deploy/huawei/.env.single docker compose \ --env-file deploy/huawei/.env.single \ -f deploy/huawei/docker-compose.single.yml \ up -d --build docker compose \ --env-file deploy/huawei/.env.single \ -f deploy/huawei/docker-compose.single.yml \ ps -a curl -fsS http://127.0.0.1/healthz curl -fsS http://127.0.0.1/api/v1/health/ready服务以 detached 模式运行,因此关闭本地 PowerShell 或 SSH 会话不会停止容器。ECS 重启后,Docker 服务与 Compose 的重启策略负责恢复应用。8.5 部署期问题闭环现象定位依据修正Docker Hub 拉取超时docker pull 访问官方 Registry 超时配置华为云 SWR 镜像加速并重启 DockerNginx 容器持续重启日志显示只读目录无法创建临时文件临时目录迁移到 /tmp,通过受限 tmpfs 提供写入PDF 上传失败后端日志显示持久卷目录权限不足启动前由一次性初始化服务修正卷所有权管理员升级 SQL 失败psql 变量替换与引号组合错误改用明确参数边界并先只读查询用户 ID论文问答前端报网络失败后端只有会话请求,没有问题请求为非安全 HTTP 环境增加 UUID v4 回退实现长页翻译偶发失败MaaS 请求耗时超过通用读取超时为学习解释配置独立、有限的读取超时部署验收以真实页面操作为准,不仅依赖容器显示 healthy。只有注册登录、论文上传解析、MaaS 学习解释、论文问答和管理员入口均完成小额验证,才认为案例具备可演示性。9. 应用效果与价值PaperLens 将“看 PDF、查术语、做笔记、问模型、整理报告”从多个割裂工具合并为一个连续流程。对个人学习场景而言,它带来的价值主要体现在:降低英文论文和专业概念的理解门槛;保持 AI 结果与当前论文、页码和原文选区的联系;让多轮问答、解释历史、高亮和笔记可以长期保存;通过华为云 MaaS 获得真实模型能力,同时保留 Mock 模型便于离线开发;通过码道代码智能体提升跨前后端开发和故障定位效率;使用单台小规格 ECS 即可完成课程设计、实习成果或个人演示部署。10. 局限与后续规划当前版本面向小规模个人使用,仍有以下边界:暂不支持扫描版论文 OCR;语义检索尚未使用持久化向量数据库;后台任务仍采用进程内执行器,不适合多实例横向扩展;单机 PostgreSQL 和本地文件卷需要定期备份;后续可根据实际用户量逐步引入 OBS、RDS、HTTPS、任务队列、pgvector 和多模态论文理解,但不在小规模案例阶段提前增加系统复杂度。11. 总结PaperLens 展示了如何将华为云码道(CodeArts)代码智能体、ModelArts Studio(MaaS)与常见 Web 技术结合,构建一套可实际部署的智能论文阅读学习应用。在研发侧,码道帮助项目完成需求拆解、跨文件编码、测试设计和部署故障定位;在运行侧,MaaS 提供总结、翻译、解释和问答能力;在基础设施侧,华为云 ECS 提供轻量、可控的容器运行环境。最终方案既满足个人学习项目的成本边界,也保留了向云数据库、对象存储和更可靠任务架构演进的空间。12. 参考资料华为云开发者空间实战案例参考页面华为云码道(CodeArts)代码智能体产品功能华为云码道(CodeArts)内置智能体用户指南ModelArts Studio(MaaS)API 调用规范华为云弹性云服务器 ECS 产品介绍
总条数:622 到第
上滑加载中