-
一、概述1.1 案例介绍银发智伴(ElderlyCare AI) 是一款面向子女、老年家庭成员和应用后台管理员三类用户的智能体检报告管理应用。子女可在 PC 端上传父母的 PDF、JPG 或 PNG 体检报告,系统通过 OCR 识别、指标提取和大模型健康解读,将专业检验结果转换为通俗说明、健康建议和饮食计划;随后生成适老化 H5 分享链接与二维码,父母通过手机即可查看大字版报告并使用语音播报。后台管理员负责用户状态、角色、系统概览和审计信息管理,不参与用户报告内容的日常操作。本案例采用 FastAPI、Vue 3、SQLite、Redis、Nginx 和华为云 ECS 构建,集成华为云 OBS 与 OCR,并使用华为云码道(CodeArts)代码智能体完成需求分析、存量代码理解、任务拆解、增量开发和问题调试。应用还提供家庭成员管理、历年指标趋势、异常变化提醒、报告对比、分享密码、访问次数限制和后台治理,形成“报告上传、智能解析、健康解读、长期跟踪、适老分享、后台治理”的完整业务闭环。GitCode 源码与演示视频:https://gitcode.com/SDSXshlbz/elderlycare-ai本案例中的演示账号、家庭成员和体检指标均为合成数据,不对应任何真实个人。健康解读仅供参考,不构成医疗诊断建议。1.2 适用对象企业开发者个人开发者高校学生学习本案例前,建议具备 Python、JavaScript、Linux 常用命令和 HTTP API 的基础知识。1.3 案例时间本案例总时长预计90分钟,其中云资源准备约20分钟,项目配置与部署约40分钟,功能验证与资源清理约30分钟。1.4 案例流程[1. 准备云资源] | v [2. 配置项目与密钥] -> [3. 构建 PC/H5 前端和 FastAPI 后端] | v [5. H5适老分享] <- [4. OCR识别、指标提取与健康解读]说明:在华为云创建 ECS、VPC、安全组、EIP 和 OBS 桶,公网仅长期开放演示所需的 80 端口;准备华为云 AK/SK、OCR、OBS 和大模型配置,通过 .env 注入运行参数,不将密钥写入源码;将源码上传到 Ubuntu 22.04 ECS,执行 deploy_ecs.sh,自动安装依赖、构建两个前端并配置 Nginx、Redis 和 systemd;用户上传报告后,后端依次完成 OCR 降级识别、指标提取、风险判定、健康解读和饮食计划生成;子女在 PC 端查看趋势和对比结果,并生成带二维码、可选访问密码和语音播报的适老化 H5 分享页面。1.5 资源总览本案例使用按需计费资源,建议将完整体验控制在2小时内。不同区域和活动价格可能变化,实际费用以华为云控制台订单页为准。体验完成后请及时释放 ECS、EIP 和不再使用的 OBS 数据,避免产生多余费用。资源名称规格单价(元)弹性云服务器 ECS通用计算增强型 c7.large.2 | 2 vCPUs | 4 GiB | Ubuntu 22.04 Server | 40 GiB GPSSD以控制台实时价格为准弹性公网IP(Elastic IP,简称EIP)按流量计费 | 5Mbit/s按实际公网流量计费虚拟私有云 VPC 和安全组1个VPC、1个子网、1个安全组VPC和安全组本身免费对象存储服务 OBS标准存储,用于报告文件、二维码等对象按存储量与请求次数计费文字识别 OCR通用文字识别、通用表格识别按调用量计费或使用套餐包华为云码道(CodeArts)代码智能体通用体验版以开通页面为准阿里云百炼大模型 APIqwen-turbo、qwen-vl-ocr、qwen-vl-plus按模型调用量计费二、环境和资源准备2.1 购买华为云 ECS 弹性云服务器登录华为云控制台,依次进入 服务列表 > 计算 > 弹性云服务器 ECS,点击 购买弹性云服务器。计费模式选择 按需计费,区域选择 北京四 cn-north-4,规格选择 通用计算增强型 c7.large.2,2 vCPUs,4 GiB。镜像选择 Ubuntu Server 22.04 64bit,系统盘选择 40 GiB GPSSD。创建或选择 VPC 与子网,购买 5Mbit/s、按流量计费 的弹性公网 IP。创建安全组,长期入方向规则只放行 TCP/80。部署期间如需 SSH,可临时放行 TCP/22,并将源地址限制为管理员当前公网 IP,部署完成后立即删除 22 端口规则。点击 立即购买,确认按需资源并等待 ECS 状态变为“运行中”。记录 ECS 公网 IP,后文用 <ECS公网IP> 表示。关键节点说明:安全组只控制云侧入站流量,Nginx 仍需在实例内监听 80 端口。后端 Uvicorn 仅监听 127.0.0.1:8005,不能绕过 Nginx 直接从公网访问。2.2 准备华为云 OBS、OCR 和访问密钥进入 服务列表 > 存储 > 对象存储服务 OBS,创建私有桶,例如 elderlycare,区域需与 ECS 一致。进入 服务列表 > 人工智能 > 文字识别 OCR,开通通用文字识别和通用表格识别。点击控制台右上角用户名,进入 我的凭证 > 访问密钥,创建并妥善保存 AK/SK。在 我的凭证 > API凭证 中记录项目 ID,项目所属区域应为 cn-north-4。AK/SK 具有云资源访问权限,只能写入服务器上的 .env,不得提交到 Git、案例文档、截图或前端代码中。OBS 桶保持私有,文件由后端 SDK 访问。2.3 准备大模型与 OCR 降级服务登录阿里云百炼控制台,地址:https://dashscope.console.aliyun.com/,开通 DashScope 服务。在 API-KEY 管理 中创建 API Key,并确认账号可调用 qwen-turbo、qwen-vl-ocr 和 qwen-vl-plus。将 API Key 仅配置在 .env 中。项目首先调用华为云 OCR;若该调用失败,再依次尝试 qwen-vl-ocr 和 qwen-vl-plus,避免单一识别服务异常导致整条解析链路不可用。2.4 准备本地和云端环境本案例以 Windows 作为开发端、Ubuntu 作为运行端,建议环境如下:环境软件及版本用途Windows 10/11PowerShell 5.1+、OpenSSH Client、tar.exe源码检查、测试、打包和上传本地 PythonPython 3.10+,本案例测试环境为 Python 3.12后端测试本地 Node.jsNode.js 20、npm 10PC/H5 生产构建Ubuntu ECSUbuntu 22.04、Python 3.10、Node.js 20、Nginx、Redis演示环境运行执行以下命令复制配置模板:Copy-Item .env.example .env编辑 .env,将占位内容替换为实际配置。不要删除未使用的键,也不要在等号两侧添加多余引号:HUAWEI_CLOUD_AK=你的华为云AK HUAWEI_CLOUD_SK=你的华为云SK HUAWEI_CLOUD_REGION=cn-north-4 HUAWEI_CLOUD_PROJECT_ID=你的华为云项目ID OBS_BUCKET_NAME=elderlycare OBS_ENDPOINT=obs.cn-north-4.myhuaweicloud.com JWT_SECRET_KEY=至少32字节的随机字符串 REDIS_URL=redis://localhost:6379/0 DATABASE_URL=sqlite+aiosqlite:///./elderlycare.db MCP_SERVER_URL= LLM_API_KEY=你的千问APIKey LLM_API_BASE=https://dashscope.aliyuncs.com/compatible-mode/v1 LLM_MODEL_NAME=qwen-turbo ALIYUN_OCR_API_KEY=你的阿里云OCR_APIKey ALIYUN_OCR_MODEL=qwen-vl-ocr SHARE_BASE_URL=http://<ECS公网IP>/h5在 Windows PowerShell 中生成随机 JWT 密钥:$bytes = New-Object byte[] 48 [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes) [Convert]::ToBase64String($bytes) 三、构建银发智伴应用3.1 使用 CodeArts 代码智能体创建项目登录华为云码道(CodeArts),创建 Python + Vue 3 项目。在 CodeArts IDE 中输入以下需求描述,让代码智能体先理解目标和约束,再结合本案例存量源码进行增量开发:创建“银发智伴”体检报告管理应用。后端使用 Python FastAPI 和 SQLAlchemy Async,PC 端与 H5 端使用 Vue 3。子女在 PC 端上传父母体检报告,后端通过 OCR 提取指标,生成健康解读、饮食计划、趋势分析和报告对比;H5 使用大字号、清晰风险标签和中文语音播报。项目部署到 Ubuntu ECS,由 Nginx 统一提供 PC、H5 和 API 入口。本项目工作区保留了代码智能体生成并持续修订的规格、设计和任务文件,使用过程如下:阶段代码智能体产物或操作人工确认的关键节点需求规格化.codeartsdoer/specs/elderlycare_ai/spec.md明确子女、父母、管理员三类角色,以及“仅提供健康参考、不替代医生诊断”的职责边界存量代码分析.codeartsdoer/specs/elderlycare_ai/design.md对照现有认证、报告和分享模块,判断哪些功能复用、哪些功能增量扩展,避免大幅改变原有结构任务拆解.codeartsdoer/specs/elderlycare_ai/tasks.md将工作拆分为认证、报告、OCR、健康解读、分享、管理员、测试和部署等可验证任务代码开发根据任务逐个生成或补全 FastAPI 服务、Vue 页面和部署配置审查接口权限、异步数据库事务、上传限制、异常映射和前后端字段契约调试验证结合错误日志定位登录、复制、H5 子路径和 Ubuntu 兼容问题每次修复后执行后端测试、两个前端生产构建和 ECS 健康检查本案例使用代码智能体辅助完成的典型调试过程如下:登录错误调试:根据后端日志和认证服务调用链,发现错误凭据触发的业务异常未正确映射。修正空用户判断和异常处理器后,错误账号由 HTTP 500 变为 HTTP 401,并返回“账号或密码错误”;分享链接复制调试:公网演示环境使用 HTTP,浏览器可能禁用安全上下文中的 Clipboard API。智能体协助定位兼容性原因,并在 navigator.clipboard.writeText() 失败时使用 document.execCommand('copy') 回退;Windows 到 Ubuntu 迁移调试:检查 CRLF/LF、文件名大小写、依赖锁文件和路径分隔符;前端使用 npm ci 复现依赖,部署脚本保持 LF,源码导入路径与真实文件名大小写一致;H5 子路径调试:统一 Vite base: '/h5/'、Vue Router 的 import.meta.env.BASE_URL 与 Nginx try_files,解决分享深层路由刷新 404;验证闭环:本地运行 python -m pytest tests -q,分别执行 PC/H5 的 npm run build,部署后访问 /health,并验证登录、趋势、对比、分享和管理员接口。代码智能体用于加速分析、编码和定位问题,不能替代人工评审。代码生成后必须逐项检查权限边界、异常处理、上传限制、密钥加载、医疗免责声明和敏感信息,并通过测试及生产构建后才能部署。3.2 部署项目代码1)解决方案总体设计银发智伴采用“多端访问、统一 API、智能服务可降级、云上统一接入”的解决方案。PC 端承载报告管理、趋势对比和后台治理,H5 端专注适老化查看与语音播报;Nginx 提供统一公网入口,FastAPI 编排认证、报告、分享和智能解析流程;OBS 保存私有报告对象,SQLite 保存结构化业务数据,Redis 保存令牌黑名单和运行期状态。OCR 与大模型均设置超时、错误处理和本地降级,避免第三方服务短暂不可用时整个应用失效。用户上传报告后,请求依次经过文件校验、私有存储、OCR 识别、指标提取、风险判定、大模型健康解读和合规过滤;处理状态通过状态机管理。子女可在 PC 端查看结果、趋势和对比,也可生成带随机状态 ID、有效期、可选密码和访问次数限制的分享链接。父母通过 H5 查看大字版内容;管理员通过角色鉴权后的接口查看系统概览和管理用户。2)系统架构设计用户层:PC 子女端 / H5 父母端 / PC 管理后台 | 接入层:EIP + Nginx(/、/h5/、/api/、/health) | 应用层:FastAPI API / JWT认证 / 报告 / 分享 / 趋势 / 对比 / 管理 | 智能层:华为云OCR -> 百炼OCR -> Qwen VL / MCP大模型 / 合规工具 / 本地规则 | 数据层:SQLite业务数据 / Redis状态与令牌 / OBS私有对象 | 运维层:systemd / 健康检查 / 审计日志 / 华为云安全组架构关键点:公网只暴露 Nginx 的 80 端口,FastAPI 仅监听 127.0.0.1:8005;PC、H5 与 API 共用同一主机,减少跨域配置;智能服务通过统一服务层调用,业务 API 不直接依赖某一家模型厂商;配置和密钥全部由 .env 注入,仓库只提供不含真实值的 .env.example。核心技术难点与解决思路核心难点解决思路验证方式OCR 服务超时、配额不足或结果为空依次调用华为云 OCR、百炼 OCR 和 Qwen VL,仅在获得有效文本时结束;全失败时明确标记报告失败Mock 各级返回值,检查降级顺序和失败状态大模型输出不稳定且健康内容有合规风险传入结构化指标,以系统提示词约束输出;执行合规过滤并固定展示免责声明;调用失败时使用本地规则生成基础解读断开模型服务后仍能生成可查看的基础结果报告解析耗时,用户难以判断进度使用 OCR_PROCESSING、EXTRACTING、INTERPRETING、COMPLETED、FAILED 状态机,并通过 SSE 推送进度检查页面进度变化及异常后最终状态Windows 开发代码迁移到 Ubuntu锁定 Python/Node 依赖,使用 npm ci;Shell 脚本使用 LF;检查源码路径大小写,不使用 Windows 专属路径本地测试、双前端生产构建和 ECS 健康检查均通过H5 部署在 /h5/ 后深层路由刷新 404Vite base、Router base 和 Nginx try_files 使用一致的 /h5/ 前缀直接访问 /h5/share/<state_id> 返回 HTTP 200HTTP 演示站点复制分享链接失败优先使用 Clipboard API,失败时回退到隐藏文本框和 document.execCommand('copy')本地 HTTPS/localhost 与 ECS HTTP 环境分别点击复制医疗报告、账号和后台权限安全bcrypt 保存密码,JWT 鉴权,失败次数锁定,角色依赖保护管理员接口,OBS 私有存储并记录审计日志错误登录返回 401,普通用户访问管理员接口返回 4033)通过 GitCode 下载源码并了解项目结构本案例源码公开托管于 GitCode:https://gitcode.com/SDSXshlbz/elderlycare-ai。在 Windows PowerShell 或 Ubuntu 终端执行:git clone https://gitcode.com/SDSXshlbz/elderlycare-ai.git cd elderlycare-ai仓库不包含 .env、数据库、用户上传文件或真实医疗图片。克隆后需根据 .env.example 创建本地 .env 并填写自己的服务配置。项目结构如下:├── app/ # FastAPI后端 │ ├── api/ # 登录、报告、分享、趋势、对比等API路由 │ ├── core/ # 配置、数据库、安全、OBS、OCR、异常处理 │ ├── mcp/ # 大模型调用和本地工具降级 │ ├── models/ # SQLAlchemy ORM模型 │ ├── schemas/ # 请求和响应数据模型 │ ├── services/ # 认证、报告、解析、解读、分享等业务服务 │ └── main.py # FastAPI入口、路由和健康检查 ├── frontend/ │ ├── pc/ # 子女使用的PC端Vue 3应用 │ │ └── src/views/ # 工作台、详情、趋势、对比、家庭成员等页面 │ └── h5/ # 父母使用的适老化H5应用 │ └── src/views/SharePage.vue # 大字版健康报告和语音播报 ├── mcp_server/ # 可独立运行的模型编排与工具服务 ├── migrations/ # Alembic数据库版本记录 ├── tests/ # 后端单元测试与集成测试 ├── deploy_ecs.sh # Ubuntu ECS一键部署脚本 ├── requirements.txt # 固定版本的Python依赖 └── .env.example # 环境变量模板,不包含真实密钥 项目采用前后端分离和 API、服务、模型分层结构。浏览器请求先到 app/api/ 路由层,Pydantic app/schemas/ 完成输入输出校验,app/services/ 编排业务规则,app/models/ 通过异步 SQLAlchemy 持久化数据;OCR、OBS、安全和异常处理集中在 app/core/,模型调用与合规、营养、参考范围工具位于 app/mcp/。PC 与 H5 分别构建为静态文件,Nginx 将 / 映射到 PC,将 /h5/ 映射到 H5,将 /api/ 和 /health 反向代理到 FastAPI。4)在 Windows 本地执行构建与测试在项目根目录执行后端测试:.\.venv\Scripts\python.exe -m pytest tests -q预期结果:54 passed分别构建 PC 和 H5 生产包:Set-Location frontend\pc npm ci npm run build Set-Location ..\h5 npm ci npm run build Set-Location ..\.. npm ci 会严格按照 package-lock.json 安装依赖,避免 Windows 和 Ubuntu 使用不同依赖版本。两个 npm run build 均应成功生成各自的 dist 目录。PC 端构建时可能提示 ECharts 相关分块超过 Vite 默认的 500 KB 建议阈值,该提示不影响产物生成,后续可通过 manualChunks 进一步拆包。依赖审计警告不会阻断构建,但应单独评估,不建议直接执行可能引入破坏性升级的 npm audit fix --force。5)关键代码讲解OCR 三级降级机制(app/core/ocr_client.py)OCR 是报告解析的入口。代码先调用华为云通用文字和表格识别;如果没有识别到有效文本,再调用百炼 OCR 模型;最后使用千问 VL 多模态模型兜底。每一级仅在返回非空文本时结束链路,所有方法失败时返回空结果并由解析状态机标记失败。async def recognize_medical_report(self, image_data: bytes, file_type: str = "image") -> dict: image_b64 = base64.b64encode(image_data).decode("utf-8") result = await self._try_huawei_ocr(image_b64) if result: return result result = await self._try_aliyun_ocr(image_b64, file_type) if result: return result result = await self._try_qwen_vl(image_b64, file_type) if result: return result logger.error("All OCR methods failed (huawei -> aliyun -> qwen-vl)") return {"text": "", "confidence": 0.0} 关键释义:base64.b64encode() 将图片转为云 OCR API 可接收的 Base64 内容;降级调用相互独立,单个云服务超时或配额不足不会立即终止处理;日志只记录调用结果和错误,不输出图片、AK/SK 或 API Key;最终空文本不是伪造成功结果,业务层会将报告标记为 FAILED,便于用户重新处理。报告解析状态机和指标风险判定(app/services/parse_service.py)报告处理依次进入 OCR_PROCESSING、EXTRACTING、INTERPRETING 和 COMPLETED。前端根据状态显示处理进度;任何未捕获异常都会进入 FAILED,避免报告长期停留在处理中。await self._update_status(report, ParseStatus.OCR_PROCESSING) ocr_text = await self._run_ocr(report) if not ocr_text or not ocr_text.strip(): report.status = ParseStatus.FAILED await self.db.flush() return await self._update_status(report, ParseStatus.EXTRACTING) indicators = self._extract_indicators(ocr_text) abnormal = [i for i in indicators if i.status != IndicatorStatus.NORMAL] if abnormal: names = [i.name for i in abnormal[:5]] report.abnormal_summary = f"发现{len(abnormal)}项异常指标: {', '.join(names)}" await self._update_status(report, ParseStatus.INTERPRETING) report.status = ParseStatus.COMPLETED指标提取阶段会校验名称长度、医学单位、数值与参考范围。高于上限标记为 HIGH,低于下限标记为 LOW;偏离比例达到 1.2 或 1.5 时,风险等级依次提升为中风险或高风险。大模型健康解读与本地降级(app/mcp/mcp_client.py)后端使用 OpenAI 兼容接口调用配置的大模型,系统提示词约束输出范围,用户提示词携带结构化指标。HTTP 状态异常通过 raise_for_status() 进入异常分支;大模型不可用时,generate_interpretation() 会调用本地规则生成基础解读,保证报告仍可查看。async def _call_llm(self, system_prompt: str, user_prompt: str, temperature: float = 0.7) -> str: if not self._can_call_llm(): raise AppException(40004, "LLM API 未配置") async with httpx.AsyncClient(timeout=settings.LLM_TIMEOUT, proxy=None) as client: response = await client.post( f"{self._llm_api_base}/chat/completions", json={ "model": self._llm_model, "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ], "temperature": temperature, }, headers={"Authorization": f"Bearer {self._llm_api_key}"}, ) response.raise_for_status() data = response.json() return data.get("choices", [{}])[0].get("message", {}).get("content", "") 登录失败返回业务错误而不是服务器内部错误(app/services/auth_service.py、app/core/exceptions.py)登录时先判断用户是否存在,再校验 bcrypt 密码。账号不存在或密码错误均抛出业务码 60003,统一异常处理器将其映射为 HTTP 401。这样错误凭据不会因为空用户访问、密码哈希异常或未处理业务异常而显示“服务器内部错误”。if user is None: raise AppException(60003, "账号或密码错误") if not verify_password(password, user.password_hash): user.login_fail_count += 1 if user.login_fail_count >= settings.LOGIN_MAX_FAILURES: user.status = AccountStatus.LOCKED user.locked_until = datetime.utcnow() + timedelta( minutes=settings.LOGIN_LOCK_MINUTES ) await self.db.flush() raise AppException( 60002, f"密码错误次数过多,账号已锁定{settings.LOGIN_LOCK_MINUTES}分钟", ) await self.db.flush() raise AppException( 60003, f"账号或密码错误,还剩{settings.LOGIN_MAX_FAILURES - user.login_fail_count}次机会", ) if exc.code in (60002, 50005): status_code = 429 elif exc.code in (60003, 60004): status_code = 401 连续失败达到阈值后账号临时锁定,可以降低暴力尝试风险;成功登录后失败次数会被清零,并签发访问令牌和刷新令牌。报告分享、二维码和访问限制(app/services/share_service.py)分享状态使用随机 UUID,默认30天有效。可选4位数字访问密码使用 bcrypt 哈希保存,数据库中不存储明文;同时支持访问次数上限、失败次数锁定和主动撤销。state_id = str(uuid.uuid4()) expire_time = datetime.utcnow() + timedelta( days=settings.SHARE_DEFAULT_EXPIRE_DAYS ) password_hash = None if access_password: if not access_password.isdigit() or len(access_password) != 4: raise AppException(10001, "访问密码需为4位数字") password_hash = _bcrypt.hashpw( access_password.encode("utf-8"), _bcrypt.gensalt() ).decode("utf-8") share_url = f"{settings.SHARE_BASE_URL}/share/{state_id}" qr = qrcode.QRCode(version=1, box_size=10, border=5) qr.add_data(share_url) qr.make(fit=True) H5 适老化语音播报(frontend/h5/src/views/SharePage.vue)H5 使用浏览器原生 Web Speech API,无需额外安装播放器。播报内容由健康解读、建议、注意事项和指标组成,语速设置为 0.8,优先选择中文语音;用户再次点击按钮时立即停止。function toggleSpeech() { if (!speechSupported.value) return const synth = window.speechSynthesis if (isSpeaking.value) { synth.cancel() isSpeaking.value = false return } speechUtterance = new SpeechSynthesisUtterance(getSpeechText()) speechUtterance.lang = 'zh-CN' speechUtterance.rate = 0.8 speechUtterance.pitch = 1.0 speechUtterance.volume = 1.0 synth.speak(speechUtterance) isSpeaking.value = true } H5 子路径适配(frontend/h5/vite.config.ts、frontend/h5/src/router/index.ts)生产环境将 H5 部署在 /h5/,构建资源路径和 Vue Router 必须使用同一个基础路径,否则在 Ubuntu Nginx 下刷新分享深层路由会出现资源 404 或空白页。export default defineConfig({ base: '/h5/', plugins: [vue()], }) const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/share/:stateId', name: 'SharePage', component: () => import('../views/SharePage.vue'), }, ], }) 6)打包并上传源码不要上传 .env 等本地敏感文件,也不要上传真实 elderlycare.db、测试数据库、医疗图片、.venv、node_modules 和 dist。在 Windows PowerShell 的项目根目录执行:tar.exe -czf elderlycare-deploy.tar.gz ` --exclude='.venv' ` --exclude='*/node_modules' ` --exclude='*/dist' ` --exclude='.env' ` --exclude='elderlycare.db' ` --exclude='test.db' ` --exclude='*.jpg' ` . scp .\elderlycare-deploy.tar.gz root@<ECS公网IP>:/tmp/ scp .\.env root@<ECS公网IP>:/tmp/elderlycare.env ssh root@<ECS公网IP> 如果使用 SSH 密钥,则在三个 SSH/SCP 命令中增加 -i <私钥路径>。上传完成后,在 ECS 中执行:sudo mkdir -p /opt/elderlycare sudo tar -xzf /tmp/elderlycare-deploy.tar.gz -C /opt/elderlycare sudo install -m 600 /tmp/elderlycare.env /opt/elderlycare/.env cd /opt/elderlycare sudo chmod +x deploy_ecs.sh sudo ./deploy_ecs.sh也可以先在 Windows PowerShell 中单独上传仅保存在本地的 .env:scp .\.env root@<ECS公网IP>:/tmp/elderlycare.env ssh root@<ECS公网IP> 登录 ECS 后,直接通过 GitCode 拉取公开源码并部署:/opt/elderlycare 是 Ubuntu ECS 上的绝对安装目录,不是 GitCode 仓库中的 opt 文件夹。git clone 命令的第二个参数会把仓库内容直接检出到该目录;后续部署脚本、Nginx 和 systemd 均统一使用此路径。sudo mkdir -p /opt/elderlycare sudo chown -R "$USER:$USER" /opt/elderlycare git clone https://gitcode.com/SDSXshlbz/elderlycare-ai.git /opt/elderlycare sudo install -m 600 /tmp/elderlycare.env /opt/elderlycare/.env cd /opt/elderlycare sudo chmod +x deploy_ecs.sh sudo ./deploy_ecs.sh关键节点说明:GitCode 只传输可公开的源码和合成演示截图,.env 必须沿独立安全通道上传。后续更新可在确认服务器无未提交修改后执行 git pull --ff-only,再重新运行 deploy_ecs.sh;数据库和用户上传文件应提前备份,不能用代码仓库代替业务数据备份。7)理解并执行一键部署脚本deploy_ecs.sh 使用 set -euo pipefail,任一关键命令失败都会终止部署。脚本执行七个阶段:安装系统依赖、准备运行用户、安装 Python 依赖、构建 PC/H5、配置 Nginx、注册 systemd 服务、执行健康检查。Nginx 的关键配置如下:location /h5/ { alias /opt/elderlycare/frontend/h5/dist/; index index.html; try_files $uri $uri/ /h5/index.html; } location /api/ { proxy_pass http://127.0.0.1:8005; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_read_timeout 120s; } location / { root /opt/elderlycare/frontend/pc/dist; index index.html; try_files $uri $uri/ /index.html; } try_files 是 PC 和 H5 深层路由刷新可用的关键。后端服务使用独立用户运行,并等待网络和 Redis:[Unit] After=network-online.target redis-server.service Wants=network-online.target redis-server.service [Service] User=elderlycare Group=elderlycare WorkingDirectory=/opt/elderlycare ExecStart=/opt/elderlycare/.venv/bin/python -m uvicorn app.main:app --host 127.0.0.1 --port 8005 Restart=always RestartSec=5 脚本结束前会轮询后端健康接口。成功时输出:Backend health check passed. ======================================== Deployment completed PC: http://<ECS_PUBLIC_IP>/ H5: http://<ECS_PUBLIC_IP>/h5/ Health: http://<ECS_PUBLIC_IP>/health ========================================8)记录数据库版本并检查服务项目启动时通过 SQLAlchemy create_all() 创建当前模型表。首次部署成功后,执行 alembic stamp head 记录当前迁移基线,不重复执行已由模型创建的历史表结构:cd /opt/elderlycare source .venv/bin/activate alembic stamp head deactivate sudo nginx -t sudo systemctl is-active nginx redis-server elderlycare curl --fail http://127.0.0.1:8005/health预期结果:nginx: configuration file /etc/nginx/nginx.conf test is successful active active active {"status":"ok","version":"1.0.0"}如健康检查失败,使用以下命令定位问题:sudo journalctl -u elderlycare -n 100 --no-pager sudo tail -n 100 /var/log/nginx/error.log常见问题与处理方法:现象原因处理方法deploy_ecs.sh: /bin/bash^MWindows CRLF 换行将脚本保存为 LF 后重新上传H5 分享链接刷新后 404Vite 基础路径或 Nginx 回退错误检查 base: '/h5/' 和 /h5/index.html登录显示服务器内部错误业务异常未映射或数据库字段不完整查看 journalctl,确认 60003 返回 401,数据库版本已记录上传后一直处理中OCR、OBS 或大模型配置错误检查 .env、OBS 区域、OCR权限及后端日志CSS/模块在 Ubuntu 找不到Windows 文件名大小写不敏感统一源码导入路径与真实文件名大小写9)安全收尾部署完成后删除包含密钥的临时文件和部署压缩包,并在安全组中删除临时 22 端口规则:rm -f /tmp/elderlycare.env /tmp/elderlycare-deploy.tar.gz服务器上的 /opt/elderlycare/.env 权限应为 600,所有者应为 elderlycare:sudo stat -c '%U:%G %a %n' /opt/elderlycare/.env预期输出:elderlycare:elderlycare 600 /opt/elderlycare/.env3.3 运行效果展示本案例已部署到华为云北京四 ECS。演示环境仅使用合成数据,环境信息如下:项目演示环境信息区域北京四 cn-north-4ECS实例elderlycare-demo,实例ID 5d96458e-5a74-47e7-8859-b78c14be13a8规格与系统c7.large.2,2 vCPU / 4 GiB,Ubuntu 22.04公网IP1.92.111.234PC端URLhttp://1.92.111.234/H5端URLhttp://1.92.111.234/h5/健康检查http://1.92.111.234/health,HTTP 200GitCode源码https://gitcode.com/SDSXshlbz/elderlycare-ai普通功能测试账号邮箱:demo@elderlycare.cn密码:Demo2026!应用后台管理员测试账号邮箱:admin20260726@elderlycare.cn密码:Sc9!Vt4#Lq7@Hm2普通账号用于演示报告上传、解读、趋势、对比和分享;管理员账号登录后进入后台管理页面,用于演示系统概览、用户列表、账号状态和角色权限。演示 ECS 当前为运行状态,公网安全组不保留 SSH 入站规则,只开放作品 Web 演示所需端口。当前地址使用 HTTP,仅用于作品演示,不要录入真实个人信息、医疗报告或其他敏感数据。正式上线应配置域名、HTTPS、数据库备份、日志脱敏和监控告警。核心功能1:报告上传与年度报告管理登录后进入工作台,选择家庭成员后拖拽或点击上传 PDF、JPG、PNG 文件。页面同步展示解析状态和历史报告。演示账号已准备 2024、2025、2026 三份年度报告,用于展示长期健康变化。关键节点说明:上传请求先校验文件数量、扩展名和大小,再将报告与家庭成员关联。后端完成 OCR、指标提取和解读后,状态变为“已完成”,点击报告名称进入详情。核心功能2:智能健康解读与饮食计划报告详情将异常指标转换为通俗说明,展示风险提示、健康建议、推荐食物、忌口食物、每日食谱和指标参考范围。每份解读固定展示医疗免责声明。执行后效果:2026年度合成报告识别出空腹血糖、总胆固醇、尿酸、收缩压和 BMI 偏高,并给出饮食控制、运动、定期监测等建议;血红蛋白显示为正常。核心功能3:历次指标趋势和异常变化提醒进入 趋势分析,可以按家庭成员和指标筛选。ECharts 折线图按报告时间排序,并使用参考范围背景区间辅助判断。执行后效果:演示数据包含6个指标,每个指标有3个年度数据点。系统可识别“正常转异常”“异常恢复”“状态变化”和“数值变化”,当前演示数据生成12条异常变化记录。核心功能4:两份报告逐项对比进入 报告对比,选择较早报告 A 与较近报告 B,点击 开始对比。后端按指标名称求交集,计算 报告B数值 - 报告A数值,并按变化绝对值排序。执行后效果:2024与2026两份报告共有6项指标。尿酸由 330 umol/L 变为 455 umol/L,收缩压由 128 mmHg 变为 148 mmHg,状态从正常变为偏高;页面同时展示 BMI、空腹血糖、总胆固醇和血红蛋白的变化。核心功能5:适老化 H5 分享与语音播报在报告详情中生成分享链接或二维码,父母通过手机打开 H5。页面采用大字号、高行距、明显风险标签和大尺寸圆形播报按钮;点击播放按钮后,图标切换为暂停,再次点击立即停止。执行后效果:在 390 × 844 移动视口下页面无横向溢出,分享接口返回6项指标;语音播报可以正常开始与暂停,H5 深层路由及生产资源均返回 HTTP 200。核心功能6:家庭成员管理进入 家庭成员,可维护父母等家庭成员的关系、性别、出生日期、身高、体重、慢性病、过敏史、用药和备注。报告关联成员后,趋势与对比功能可以按成员筛选,避免多人报告混合统计。核心功能7:应用后台管理与角色权限管理员登录后可查看系统用户、报告和分享数量,按角色或账号状态筛选用户,并执行受保护的管理操作。注册页面不提供管理员角色选择,普通用户不能通过修改前端参数自行获得管理员权限;管理员接口由后端角色依赖统一保护,并具有禁止管理员自降级和最后一名管理员保护逻辑。执行后效果:管理员账号可进入 /admin 页面,普通演示账号访问同一路由时会被前端守卫拦截,直接请求管理员 API 时后端返回 HTTP 403。角色变更记录写入审计日志,便于追溯操作者、目标用户和变更时间。功能验收结果验证项实际结果正确账号登录HTTP 200,进入工作台错误账号登录HTTP 401,返回“账号或密码错误”,无服务器内部错误报告列表3份合成年度报告趋势分析6项指标,每项3个年度数据点异常变化12条变化记录报告对比6项共有指标及数值、状态变化H5分享返回健康解读、饮食计划和6项指标二维码返回 image/png管理员权限管理员可访问后台,普通用户访问管理员接口返回 HTTP 403PC/H5控制台无错误或警告Ubuntu服务Nginx、Redis、FastAPI均为 active自动化测试54 passed演示视频已上传至gitcode四、释放资源4.1 删除ECS弹性云服务器登录华为云控制台,进入 服务列表 > 计算 > 弹性云服务器 ECS。在 ECS 列表勾选本案例创建的服务器,点击 更多 > 删除。在确认对话框中勾选 释放云服务器绑定的弹性公网IP地址;如创建了额外数据盘,同时勾选删除对应数据盘。核对实例名称和公网 IP,确认无需要保留的数据后点击 是。进入 网络 > 弹性公网IP和带宽,确认 EIP 已释放;进入 对象存储服务 OBS,删除不再需要的报告、二维码和桶;进入安全组确认不存在遗留的公网 22 端口规则。删除 ECS、EIP、系统盘或 OBS 数据属于不可逆操作。释放前请先备份需要保留的源码、数据库和日志。仅关机通常仍会产生系统盘、EIP等费用,应以控制台资源状态和账单为准。五、扩展资料说明华为云 ECS:cid:link_2华为云 OBS:cid:link_4华为云 OCR:cid:link_1华为云开发者空间:cid:link_6华为云码道 CodeArts:cid:link_7银发智伴 GitCode 源码:https://gitcode.com/SDSXshlbz/elderlycare-aiFastAPI:https://fastapi.tiangolo.com/Vue 3:https://vuejs.org/Nginx:https://nginx.org/en/docs/阿里云百炼 Model Studio:https://help.aliyun.com/zh/model-studio/
-
API 接口文档自动更新:同步代码变更与文档内容在现代软件开发中,API接口文档的自动更新至关重要,它能确保文档与代码变更保持同步,减少手动维护错误,并提升开发效率。DeepSeek 或其他AI辅助工具可以用于增强这一过程,但核心依赖于自动化工具链。下面我将逐步解释如何实现API文档的自动同步,包括工具选择、集成方法和最佳实践。回答基于行业标准(如OpenAPI规范),确保真实可靠。步骤1: 理解核心需求与原理API文档自动更新的核心是:当代码库发生变更(如新增接口、修改参数)时,文档应自动重建并发布。这通常通过以下方式实现:代码即文档:在代码中嵌入结构化注释(如OpenAPI注释),工具自动提取生成文档。触发机制:当代码提交到版本控制系统(如Git)时,持续集成(CI)管道触发文档生成。同步输出:生成的文档自动部署到网站或文档平台。这种方法的好处包括:减少人工错误:文档直接从代码派生,避免不一致。提高效率:开发者只需维护代码注释,文档自动更新。支持AI辅助:DeepSeek 等工具可用于生成注释初稿或优化文档内容,但需与自动化工具集成。步骤2: 选择合适工具与配置以下是常用工具链,它们支持与DeepSeek AI集成以增强文档质量(如自动生成描述)。推荐工具基于开源标准:核心工具:Swagger/OpenAPI:行业标准,通过代码注释定义API规范。支持YAML或JSON格式。Sphinx或Doxygen:适用于Python/C++等语言,从代码注释生成文档。CI/CD工具:如GitHub Actions、Jenkins,用于自动化触发文档生成。DeepSeek 集成:DeepSeek 可作为AI辅助层:在代码注释阶段,使用DeepSeek生成或优化注释内容(如接口描述),然后由工具提取。示例流程:开发者在代码中添加OpenAPI注释。利用DeepSeek API优化注释(例如,生成更清晰的描述)。CI工具检测代码变更,运行文档生成命令。输出文档自动部署(如GitHub Pages或Swagger UI)。工具设置要点:安装依赖:例如,Python项目使用swagger-ui和openapi-core库。配置文件:在项目根目录添加swagger.yaml或Doxyfile定义文档规则。CI配置:在GitHub Actions中添加工作流,自动运行文档生成脚本。步骤3: 实现示例与代码演示以下是一个简单示例,展示如何用Python(Flask框架)实现API文档自动更新。假设使用OpenAPI和GitHub Actions,并集成DeepSeek优化注释。代码注释阶段:在API代码中添加OpenAPI格式注释。DeepSeek可用于生成初始注释(通过API调用)。from flask import Flaskapp = Flask(__name__) # 使用OpenAPI注释定义API(DeepSeek可辅助生成描述)# 路径: /users# 方法: GET# 参数: None# 响应: 用户列表@app.route('/users', methods=['GET'])def get_users(): """ get: summary: 获取所有用户信息 description: 此接口返回系统中的用户列表。 # DeepSeek优化后的描述 responses: 200: description: 成功返回用户数据 """ return {"users": ["user1", "user2"]}AI写代码python运行文档生成脚本:创建脚本(如generate_docs.py),使用工具提取注释生成文档。# generate_docs.pyfrom openapi_core import create_specfrom flask_openapi import Swagger app = Flask(__name__)swagger = Swagger(app)spec = create_spec(app) # 生成OpenAPI规范spec.to_yaml('openapi.yaml') # 输出YAML文件AI写代码python运行CI/CD配置:在GitHub仓库的`.github/workflows/docs.yml中添加Actions工作流。name: API Docs Updateon: push: branches: [main]jobs: build-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.8' - name: Install dependencies run: pip install flask flask-openapi openapi-core - name: Generate Docs run: python generate_docs.py - name: Deploy Docs uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./ # 发布到GitHub PagesAI写代码yamlDeepSeek 优化步骤(可选):在注释生成后,调用DeepSeek API优化文本(例如,使用requests库发送注释到DeepSeek端点)。脚本示例:import requestsdef optimize_with_deepseek(text): response = requests.post( "https://api.deepseek.com/optimize", json={"text": text}, headers={"Authorization": "Bearer YOUR_API_KEY"} ) return response.json()["optimized_text"]AI写代码python运行在generate_docs.py中集成此函数,自动优化注释。步骤4: 最佳实践与注意事项测试与验证:每次文档更新后,运行测试确保API兼容性(如使用Postman或Swagger Validator)。版本控制:文档与代码同仓库存储,便于回滚。性能优化:对于大型项目,增量生成文档以减少CI时间。DeepSeek 使用建议:仅用于辅助(如生成描述),核心逻辑仍需开发者控制,避免AI错误。常见问题解决:文档不更新:检查CI配置是否触发正确分支。格式错误:确保OpenAPI注释语法正确,使用工具如Swagger Editor验证。安全风险:API密钥等敏感信息勿暴露在代码中,使用环境变量。总结通过上述步骤,您可以实现API接口文档的自动更新:代码变更时,工具链自动提取注释、生成文档并部署。DeepSeek 作为AI辅助层,能提升文档质量,但核心依赖于自动化工具(如OpenAPI和CI/CD)。起始时,从简单项目试运行(如示例),再扩展到复杂系统。如果您有具体代码库或工具偏好,我可以提供更定制化的建议!————————————————版权声明:本文为CSDN博主「普通网友」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/2501_94071944/article/details/154288418
-
【案例共创】基于仓颉编程语言+DeepSeek实现模拟课堂讨论助手项目概述本项目旨在通过华为开发者空间云主机,结合仓颉编程语言和DeepSeek API,开发一个简单的模拟课堂讨论助手。用户可以通过命令行输入相关知识点或问题,应用会返回该知识点的详细解释和记忆方法。这个助手可以用诙谐幽默的方式学习各种知识。适用对象喜欢用诙谐幽默的方式学习知识的爱好者。案例流程说明:① 使用CodeArts IDE for Cangjie编辑器开发仓颉程序调用;② 领取免费 DeepSeeK Tokens;③ 仓颉对接DeepSeek接口。开发步骤1. 环境准备注册并登录华为开发者空间:访问华为开发者空间官网,注册并登录账户。创建云主机实例:在华为开发者空间中创建一个新的云主机实例,选择默认的配置。使用开箱即用的云主机实例开发,点击 打开云主机 再点击打开远程桌面。图示即可看出是开箱即用的CodeArts IDE for Cangjie编辑器2. 项目初始化创建新项目:首先我们进入到开发者空间后,双击打开CodeArts IDE for Cangjie,在CodeArts IDE for Cangjie中创建一个新的项目,命名为“demo”(随便你怎么命名)。名称和位置可以自定义,产物类型选择executable。产物类型说明: executable,可执行文件;static,静态库,是一组预先编译好的目标文件的集合;dynamic,动态库,是一种在程序运行时才被加载到内存中的库文件,多个程序共享一个动态库副本,而不是像静态库那样每个程序都包含一份完整的副本。配置项目结构:设置项目的基本目录结构,包括源代码文件、配置文件等。创建项目后,打开src目录下main.cj文件,在预置代码中增加函数和函数调用代码(没有的话可以自己新建一份,代码内容下文我有附上)。然后点击右上的运行按钮运行项目,在终端中查看输出内容。如果遇到弹窗提示运行/调试配置,点击确定继续操作。修改后的main.cj文件代码:package demo func println_add(a: Int64, b:Int64): Int64 { let number: Int64 = a+b println(number) return number } main(): Int64 { println("hello world") println_add(3,4) println("hello Cangjie") return 0 } 调试项目:点击右上调试按钮,首次会提示配置调试,点击新增配置项,选择Cangjie(cjdb)Debug——launch。调试模式说明:Launch模式,启动调试器的同时加载被调程序;Attach,针对正在运行的程序,附加到已启动的程序。然后可以在main.cj代码中设置断点,再次点击调试按钮。可以在顶部看到调试按钮,在左侧看到调试过程中的变量、监视、调用堆栈、断点信息。2.1 版本管理 可选 (链接到gitcode)点完之后会要求你填入访问密钥先登录gitcode 然后点访问令牌 新建访问令牌 一直下一步即可 会得到一串令牌 然后将令牌输入即可。然后新建一个仓库 名字你自己取当前项目代码仓是空仓库你可以通过克隆仓库开始或使用以下方式为你的项目添加文件:或为你的项目添加以下文件: 添加 README.md添加 LICENSE添加 .gitignore命令行指引你还可以按照以下说明从你的电脑中上传现有文件或项目。Git 全局设置git config --global [user.name](http://user.name/) "weixin_41024010" git config --global user.email "[weixin_41024010@noreply.gitcode.com](mailto:weixin_41024010@noreply.gitcode.com)" 创建一个新仓库git clone [https://gitcode.com/weixin_41024010/test.git](https://gitcode.com/weixin_41024010/test.git) cd test echo "# test" >> [README.md](http://readme.md/) git add [README.md](http://readme.md/) git commit -m "add README" git branch -m main git push -u origin main推送现有的文件cd existing_folder git init git remote add origin [https://gitcode.com/weixin_41024010/test.git](https://gitcode.com/weixin_41024010/test.git) git add . git commit -m "Initial commit" git branch -m main git push -u origin main推送现有的 Git 仓库cd existing_repo git remote rename origin old-origin git remote add origin [https://gitcode.com/weixin_41024010/test.git](https://gitcode.com/weixin_41024010/test.git) git push -u origin --all git push -u origin --tags � 配置好git 就可以使用了 ,但是有个地方要先设置一下:提交文件限制改大一些 ,不然如果没有配置好GIT忽略文件的话 ,会推送不上去。改动可以从这里提交 ,这样就完成了代码的版本控制3. Cangjie对接DeepSeek接口华为云提供了单模型200万免费Tokens,包含DeepSeek-R1&V3满血版,我们可以登录华为云ModelArts Studio(MaaS)控制台领取免费额度,这里我们选择DeepSeek-R1满血版来搭建我们的工程。在云主机桌面底部菜单栏,点击打开火狐浏览器。用火狐浏览器访问ModelArts Studio首页:https://www.huaweicloud.com/product/modelarts/studio.html,点击ModelArts Studio控制台跳转到登录界面,按照登录界面提示登录,即可进入ModelArts Studio控制台。你也可以在本地浏览器打开进行相关操作。领取后点击调用说明,可以获取到对应的API地址、模型名称。当然API Key也要创建下,这里创建很简单,按提示跳转过去就成,我这里就不介绍了。记录对应的API地址、模型名称、API Key留作下面步骤使用。4. Cangjie对接DeepSeek编写代码进入云主机桌面,右键选择Open Terminal Here打开终端命令窗口,克隆仓颉示例代码仓库。git clone https://gitcode.com/CaseDeveloper/Cangjie-Examples.git 使用CodeArts IDE for Cangjie打开AIChat示例项目。左上点击文件—打开项目,选择前面克隆的示例代码目录Cangjie-Examples下的AIChat打开。修改配置config.json配置文件,配置DeepSeek的API信息。配置项:model、model、base_url{ "model": "DeepSeek-V3", "api_key": "Your KEY", "base_url": "[https://maas-cn-southwest-2.modelarts-maas.com/v1/infers/8a062fd4-7367-4ab4-a936-5eeb8c4/v1/chat/completions](https://maas-cn-southwest-2.modelarts-maas.com/v1/infers/8a062fd4-7367-4ab4-a936-5eeb8fb821c4/v1/chat/completions)", "system_prompt": "模拟课堂讨论 - Simulated Classroom Discussion,I need you to help me memorize the noun explanation, after I type a noun, you will simulate 5 students in the class to generate their speeches about the noun. The discussion must be humorous, and easy to understand. The entire conversation and instructions should be provided in Chinese. The first term is: 主题是" } 打开main.cj,运行项目,我们就可跟AI助手进行聊天了。(* 注意:打开main.cj可能提示Console标红,不影响程序运行,可以忽略。亦可以删除import std.console.Console重新编写该导入语句)至此,基于仓颉编程语言+DeepSeek实现模拟课堂讨论助手开发就完成了。接下来我们在聊天项目基础上,改编成“模拟课堂讨论助手”助手,有时候英文提示词会更有效其实超级简单,我们只需要再修改 “system_prompt” 即可。修改内容如下:"system_prompt": "模拟课堂讨论 - Simulated Classroom Discussion,I need you to help me memorize the noun explanation, after I type a noun, you will simulate 5 students in the class to generate their speeches about the noun. The discussion must be humorous, and easy to understand. The entire conversation and instructions should be provided in Chinese. The first term is: 主题是" 运行程序:最后我把代码完整的给大家呈现下(chat.cj):所有代码开源在git clone [https://gitcode.com/weixin_41024010/HUAWEIDEMO.git](https://gitcode.com/weixin_41024010/HUAWEIDEMO.git) 大家可以克隆我的代码使用。使用时要根目录是CHATDEEPSEEK (就是如图所示)不然编译会有问题。具体代码如下:package openai_chat import encoding.json.stream.* import net.http.ClientBuilder import net.http.HttpHeaders import net.http.HttpRequestBuilder import net.tls.TlsClientConfig import net.tls.CertificateVerifyMode import std.collection.ArrayList import std.io.ByteArrayStream import std.time.Duration public struct FunctionCall <: JsonDeserializable<FunctionCall> & JsonSerializable { public let name: String public let arguments: String public init(name: String, arguments: String) { this.name = name this.arguments = arguments } public static func fromJson(r: JsonReader): FunctionCall { var temp_name: String = "" var temp_arguments: String = "" while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match (n) { case "name" => temp_name = r.readValue<String>() case "arguments" => temp_arguments = r.readValue<String>() case _ => r.skip() } } r.endObject() break case _ => throw Exception("can't deserialize for FunctionCall") } } return FunctionCall(temp_name, temp_arguments) } public func toJson(w: JsonWriter) { w.startObject() w.writeName("name").writeValue<String>(this.name) w.writeName("arguments").writeValue<String>(this.arguments) w.endObject() w.flush() } } public enum RoleType { User | Assistant | System | Function | NULL } public func role_type_to_str(role: RoleType): Option<String> { return match(role) { case RoleType.User => Some("user") case RoleType.Assistant => Some("assistant") case RoleType.System => Some("system") case RoleType.Function => Some("function") case RoleType.NULL => None } } public func str_to_role_type(role_option_str: Option<String>): RoleType { return match(role_option_str) { case Some(role_option) => match (role_option) { case "user" => RoleType.User case "assistant" => RoleType.Assistant case "system" => RoleType.System case "function" => RoleType.Function case x => throw Exception("unknow enum ${x} for RoleType") } case None => RoleType.NULL } } public struct Message<: JsonDeserializable<Message> & JsonSerializable { public let role: RoleType public var content: String public let function_call: Option<FunctionCall> public init(role: RoleType, content: String) { this.role = role this.content = content this.function_call = None } public init(role: RoleType, content: String, function_call: Option<FunctionCall>) { this.role = role this.content = content this.function_call = function_call } public static func fromJson(r: JsonReader): Message { var temp_role: Option<String> = None // role may null in stream chat var temp_content: String = "" var temp_functional_call: Option<FunctionCall> = None while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match (n) { case "role" => temp_role = r.readValue<Option<String>>() case "content" => temp_content = r.readValue<String>() case "function_call" => temp_functional_call = r.readValue<Option<FunctionCall>>() case _ => r.skip() } } r.endObject() break case _ => throw Exception("can't deserialize for Message") } } let role_type: RoleType = str_to_role_type(temp_role) return Message(role_type, temp_content) } public func toJson(w: JsonWriter) { w.startObject() w.writeName("role").writeValue<Option<String>>(role_type_to_str(this.role)) w.writeName("content").writeValue<String>(this.content) w.endObject() w.flush() } } public struct ChatRequest <: JsonSerializable { private let model: String private let messages: ArrayList<Message> private let max_tokens: Int64 private let temperature: Float64 private let top_p: Float64 private let n: Int32 private let stream: Bool public init( model: String, messages: ArrayList<Message>, max_tokens: Int64, temperature: Float64, top_p: Float64, n: Int32, stream: Bool ) { // construction function with messages this.model = model this.messages = messages this.max_tokens = max_tokens this.temperature = temperature this.top_p = top_p this.n = n this.stream = stream } public init( model: String, prompt: String, history: ArrayList<(String, String)>, system_prompt: String, max_tokens: Int64, temperature: Float64, top_p: Float64, n: Int32, stream: Bool ){ // construction function with prompt and system_prompt this.model = model this.messages = ArrayList<Message>([ Message(RoleType.System, system_prompt) ]) for ((use_msg, bot_msg) in history) { this.messages.append(Message(RoleType.User, use_msg)) this.messages.append(Message(RoleType.Assistant, bot_msg)) } this.messages.append(Message(RoleType.User, prompt)) this.max_tokens = max_tokens this.temperature = temperature this.top_p = top_p this.n = n this.stream = stream } public init( model: String, prompt: String, history: ArrayList<(String, String)>, system_prompt: String, stream: Bool ){ // construction function with prompt and default arguments this.model = model this.messages = ArrayList<Message>([ Message(RoleType.System, system_prompt) ]) for ((use_msg, bot_msg) in history) { this.messages.append(Message(RoleType.User, use_msg)) this.messages.append(Message(RoleType.Assistant, bot_msg)) } this.messages.append(Message(RoleType.User, prompt)) this.max_tokens = 2000 this.temperature = 0.7 this.top_p = 1.0 this.n = 1 this.stream = stream } public func toJson(w: JsonWriter) { w.startObject() w.writeName("model").writeValue<String>(this.model) w.writeName("messages").writeValue<ArrayList<Message>>(this.messages) w.writeName("max_tokens").writeValue<Int64>(this.max_tokens) w.writeName("temperature").writeValue<Float64>(this.temperature) w.writeName("top_p").writeValue<Float64>(this.top_p) w.writeName("n").writeValue<Int32>(this.n) w.writeName("stream").writeValue<Bool>(this.stream) w.endObject() w.flush() } } public struct Choice <: JsonDeserializable<Choice> & JsonSerializable { public let index: Int32 public let message: Option<Message> public let delta: Option<Message> public let finish_reason: Option<String> public let logprobs: Option<Float64> // dashscope for qwen need public init( index: Int32, message: Option<Message>, delta: Option<Message>, finish_reason: Option<String>, logprobs: Option<Float64> ) { this.index = index this.message = message this.delta = delta this.finish_reason = finish_reason this.logprobs = logprobs } public static func fromJson(r: JsonReader): Choice { var temp_index: Int32 = -1 var temp_message: Option<Message> = None var temp_delta: Option<Message> = None var temp_finish_reason: Option<String> = None var temp_logprobs: Option<Float64> = None while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match (n) { case "index" => temp_index = r.readValue<Int32>() case "message" => temp_message = r.readValue<Option<Message>>() case "delta" => temp_delta = r.readValue<Option<Message>>() case "finish_reason" => temp_finish_reason = r.readValue<Option<String>>() case "logprobs" => temp_logprobs = r.readValue<Option<Float64>>() case _ => r.skip() } } r.endObject() break case _ => throw Exception("can't deserialize for Choice") } } return Choice(temp_index, temp_message, temp_delta, temp_finish_reason, temp_logprobs) } public func toJson(w: JsonWriter) { w.startObject() w.writeName("index").writeValue<Int32>(this.index) w.writeName("message").writeValue<Option<Message>>(this.message) w.writeName("delta").writeValue<Option<Message>>(this.delta) w.writeName("finish_reason").writeValue<Option<String>>(this.finish_reason) w.writeName("logprobs").writeValue<Option<Float64>>(this.logprobs) w.endObject() w.flush() } } public struct Usage <: JsonDeserializable<Usage> & JsonSerializable { public let prompt_tokens: UInt64 public let completion_tokens: UInt64 public let total_tokens: UInt64 public init(prompt_tokens: UInt64, completion_tokens: UInt64, total_tokens: UInt64) { this.prompt_tokens = prompt_tokens this.completion_tokens = completion_tokens this.total_tokens = total_tokens } public static func fromJson(r: JsonReader): Usage { var temp_prompt_tokens: UInt64 = 0 var temp_completion_tokens: UInt64 = 0 var temp_total_tokens: UInt64 = 0 while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match (n) { case "prompt_tokens" => temp_prompt_tokens = r.readValue<UInt64>() case "completion_tokens" => temp_completion_tokens = r.readValue<UInt64>() case "total_tokens" => temp_total_tokens = r.readValue<UInt64>() case _ => r.skip() } } r.endObject() break case _ => throw Exception("can't deserialize for Usage") } } return Usage(temp_prompt_tokens, temp_completion_tokens, temp_total_tokens) } public func toJson(w: JsonWriter) { w.startObject() w.writeName("prompt_tokens").writeValue<UInt64>(this.prompt_tokens) w.writeName("completion_tokens").writeValue<UInt64>(this.completion_tokens) w.writeName("total_tokens").writeValue<UInt64>(this.total_tokens) w.endObject() w.flush() } } public struct ChatResponse <: JsonDeserializable<ChatResponse> { // some api names `id`, and some names `request_id` public let id: Option<String> public let request_id: Option<String> public let system_fingerprint: Option<String> public let model: String public let object: String public let created: UInt64 public let choices: ArrayList<Choice> public let usage: Option<Usage> public init( id: Option<String>, request_id: Option<String>, system_fingerprint: Option<String>, model: String, object: String, created: UInt64, choices: ArrayList<Choice>, usage: Option<Usage> ) { this.id = id this.request_id = request_id this.system_fingerprint = system_fingerprint this.model = model this.object = object this.created = created this.choices = choices this.usage = usage } public static func fromJson(r: JsonReader): ChatResponse { var temp_id: Option<String> = None var temp_request_id: Option<String> = None var temp_system_fingerprint: Option<String> = None var temp_model: String = "" var temp_object: String = "" var temp_created: UInt64 = 0 var temp_choices: ArrayList<Choice> = ArrayList<Choice>([]) var temp_usage: Option<Usage> = None while (let Some(v) <- r.peek()) { match(v) { case BeginObject => r.startObject() while(r.peek() != EndObject) { let n = r.readName() match (n) { case "id" => temp_id = r.readValue<Option<String>>() case "request_id" => temp_request_id = r.readValue<Option<String>>() case "system_fingerprint" => temp_system_fingerprint = r.readValue<Option<String>>() case "model" => temp_model = r.readValue<String>() case "object" => temp_object = r.readValue<String>() case "created" => temp_created = r.readValue<UInt64>() case "choices" => temp_choices = r.readValue<ArrayList<Choice>>() case "usage" => temp_usage = r.readValue<Option<Usage>>() case _ => r.skip() } } r.endObject() break case _ => throw Exception("can't deserialize for ChatResponse") } } return ChatResponse( temp_id, temp_request_id, temp_system_fingerprint, temp_model, temp_object, temp_created, temp_choices, temp_usage ) } } public func get_domain( url: String ): String { var temp_url = url if (temp_url.startsWith("https://")) { temp_url = temp_url["https://".size..] } else if (temp_url.startsWith("http://")) { temp_url = temp_url["http://".size..] } let domain: String = temp_url.split("?")[0].split("/")[0] return domain } public func build_http_client( prompt: String, env_info: EnvInfo, history: ArrayList<(String, String)>, stream!: Bool ){ // prepare input data var array_stream = ByteArrayStream() let json_writer = JsonWriter(array_stream) let chat_res = ChatRequest( env_info.model, prompt, history, env_info.system_prompt, stream ) chat_res.toJson(json_writer) let post_data: Array<UInt8> = array_stream.readToEnd() var headers: HttpHeaders = HttpHeaders() headers.add("Authorization", "Bearer ${env_info.api_key}") headers.add("Content-Type", "application/json") if (stream) { headers.add("Accept", "text/event-stream") } let request = HttpRequestBuilder() .url(env_info.base_url) .method("POST") .body(post_data) .readTimeout(Duration.second * 120) .addHeaders(headers) .build() let client = if (env_info.base_url.startsWith("https")) { var tls_client_config = TlsClientConfig() tls_client_config.verifyMode = CertificateVerifyMode.TrustAll tls_client_config.domain = get_domain(env_info.base_url) ClientBuilder() .tlsConfig(tls_client_config) .build() } else { ClientBuilder().build() } return (request, client) } public func chat( prompt: String, env_info: EnvInfo, history: ArrayList<(String, String)> ): Option<String> { let (request, client) = build_http_client( prompt, env_info, history, stream: false ) var result_message: Option<String> = None var res_text = "" try { // call api let response = client.send( request ) // read result (support max revice 100k data) let buffer = Array<Byte>(102400, item: 0) let length = response.body.read(buffer) res_text = String.fromUtf8(buffer[..length]) // println("res_text: ${res_text}") var input_stream = ByteArrayStream() input_stream.write(res_text.toArray()) // convert text to ChatResponse object let json_reader = JsonReader(input_stream) let res_object = ChatResponse.fromJson(json_reader) let choices: ArrayList<Choice> = res_object.choices if (choices.size > 0) { let message = choices[0].message.getOrThrow() // println("message: ${message.content}") result_message = Some(message.content) } else { println("can't found any response") } } catch (e: Exception) { println("ERROR: ${e.message}, reviced text is ${res_text}") } client.close() return result_message } public func stream_chat( prompt: String, env_info: EnvInfo, history: ArrayList<(String, String)> ): Option<String> { let (request, client) = build_http_client( prompt, env_info, history, stream: true ) var result_response: String = "" var temp_text2 = "" try { // call api let response = client.send( request ) // read result let buffer = Array<Byte>(10240, item: 0) var finish_reason: Option<String> = None while(finish_reason.isNone() && temp_text2 != "[DONE]") { let length = response.body.read(buffer) let res_text = String.fromUtf8(buffer[..length]) for (temp_text in res_text.split("\n")) { temp_text2 = if (temp_text.startsWith("data: ")) { temp_text["data: ".size..] } else { temp_text } if (temp_text2.size == 0) { continue } if (temp_text2 == "[DONE]") { break } // println("========================") // println("temp_text: ${temp_text2}") // println("========================") var input_stream = ByteArrayStream() input_stream.write(temp_text2.toArray()) // convert text to ChatResponse object let json_reader = JsonReader(input_stream) let res_object = ChatResponse.fromJson(json_reader) let choices: ArrayList<Choice> = res_object.choices if (choices.size > 0) { finish_reason = choices[0].finish_reason if (finish_reason.isNone()) { let delta = choices[0].delta.getOrThrow() print("${delta.content}", flush: true) result_response += delta.content } } else { println("can't found any response") } } } } catch (e: Exception) { println("ERROR: ${e.message}, reviced text is ${temp_text2}") } client.close() if (result_response.size > 0) { return Some(result_response) } else { return None } } 5. 运行和测试最后我们测试下DeepSeek,如下图:到此整个项目案例创建完成,可见是非常简单且实用的,希望能给大家带来帮助。我正在参加【案例共创】第4期 基于华为开发者空间+仓颉/DeepSeek/MCP完成应用构建开发实践 cid:link_1参考链接https://gitcode.com/weixin_41024010/HUAWEIDEMOhttps://gitcode.com/Cangjie/Cangjie-Examples
-
数据库上云有多轻松?华为云技术专家带你玩转云数据库API,智能数据底座手到擒来。 本期直播内容聚焦在华为云数据库开发者平台能力展开,重点围绕GaussDB与GaussDB(for MySQL)服务,深入浅出产品核心能力,解读开发者指南,实践一站式可视化开发者平台,助力开发者快速上手数据库服务API。直播链接:cid:link_0Q:GaussDB是如何实现故障转移和高可用的?能否详细介绍一下其故障恢复机制?A:云数据库GaussDB服务是国内首个双集群强一致方案,核心业务高可用。 双集群强一致:基于存算分离,GaussDB+鲲鹏+NOF网络+Dorado存储,全栈组合调优,集群级故障完全隔离,双AZ双活,保障RPO=0 应用无损透明倒换:故障切换快速连接、SQL操作断点继续,实现数据库HA切换时连接不断、事务自动回放,业务无感知 数据可靠:数据持久性高达99.9999999999%,保证数据安全可靠,保护业务免受故障影响 详细请参考官网:cid:link_1Q:对于没有丰富数据库开发经验的开发者来说,华为云数据库 API 的学习曲线是怎样的?有哪些资源和工具可以帮助他们快速掌握?A:开放着官网丰富的API资源和开发者资料,可以满足开发者快速学习。 参考开发者官网:https://developer.huaweicloud.com/Q:华为云数据库服务API支持哪些类型的数据库?如MySQL、PostgreSQL、MongoDB等。A:华为云官网上的已提供服务的数据库均已提供丰富的api能力。Q:华为云数据库API支持哪些编程语言,并且如何进行接口调用?A:API是基于http对外提供接口请求,任何语言都可以支持。可参考GaussDB官网如何调用API章节:https://support.huaweicloud.com/productdesc-gaussdb/gaussdb_01_057.html Q:GaussDB在处理大规模数据时的性能优化方法有哪些?A:参考官网SQL调优指南:https://support.huaweicloud.com/distributed-devg-v8-gaussdb/gaussdb-12-0246.htmlQ:华为云数据库服务API如何与云上其他服务(如云存储、云缓存)集成?A:数据库服务API由开发者进行编排和集成,可以和其他云服务集成配套。Q:在GaussDB中如何实现细粒度的访问控制和权限管理?A:云数据库GaussDB服务支持角色和策略权限管理,角色以服务为粒度。策略以API接口为粒度进行权限拆分,授权更加精细,可以精确到某个操作、资源和条件,能够满足企业对权限最小化的安全管控要求。 详细请参考官网:https://support.huaweicloud.com/api-gaussdb/gaussdb_api_190.htmlQ:GaussDB(for MySQL)支持并行查询的话,和MySQL的innodb的引擎最大的差异在哪里?A:并行查询是GaussDB(for MySQL)服务自研特性,将查询任务进行切分并分发到多个CPU核上进行计算,充分利用CPU的多核计算资源来缩短查询时间。并行查询的性能提升倍数理论上与CPU的核数正相关。 详细请参考官网:https://support.huaweicloud.com/kerneldesc-gaussdbformysql/gaussdbformysql_20_0005.htmlQ:在开发过程中,如何利用华为云数据库进行有效的性能测试和压力测试?A:可以使用业务通用的sysbench或者benchmark基准测试工具。Q:GaussDB数据库服务 API 可以支持哪些编程语言?A:数据库服务API是通用的Restful接口,允许不同软件系统之间进行交互,支持任何语言。数据库服务API为开发者提供了对应的SDK工具,支持主流的7种语言的能力。Q:如何通过华为云数据库服务API实现数据库实例的自动扩展?A:数据库服务API支持相关场景自动扩展,如:云数据库GaussDB(for MySQL)支持自动扩缩容(Autoscaling),按需实例支持根据cpu使用率自动扩大或者缩小规格,也可以支持增加或者减少只读节点,包周期实例支持根据cpu使用率自动扩大或者缩小规格。 详细请参考官网:https://www.huaweicloud.com/product/gaussdbformysql/features.html 云数据库GaussDB支持自动扩容实例磁盘,详细请参考官网:https://support.huaweicloud.com/usermanual-gaussdb/gaussdb_01_471.htmlQ:业务量提升后,GaussDB是如何扩展的A:这个需要看具体的场景,是需要扩规格还是需要扩分片。Q:华为云数据库服务API的调用费用是如何计算的?A:数据库服务按照开通资源用量收费,API调用不涉及费用。Q:GaussDB支不支持使用SQL命令修改全局参数?A:用户界面不支持。Q:发生故障以后,数据库服务的自动恢复机制是怎样的,备份恢复的操作流程是什么样的A:数据库服务提供强大的高可用能力,如部署时多副本部署,单个副本异常不影响服务正常使用等,即使数据库实例完全不可用,我们提供了丰富的恢复能力如基于时间点恢复等能力。 参考官网:https://www.huaweicloud.com/product/gaussdbformysql/features.htmlQ:如何获取华为云数据库服务API的访问权限?A:数据库服务API支持如下两种认证鉴权,可以选择其中一种进行认证鉴权。推荐使用AK/SK认证,其安全性比Token认证要高。 Token认证:通过Token认证通用请求。 AK/SK认证:通过AK(Access Key ID)/SK(Secret Access Key)加密调用请求。 详细请参考官网:https://support.huaweicloud.com/api-gaussdbformysql/gaussdbformysql_03_0001.htmlQ:GaussDB哪些监控工具可以检查数据库的健康状况?A:GaussDB云服务采集了丰富的指标并上报到了云监控服务。Q:多台弹性云服务器云主机是否可以使用同一个GaussDB数据库A:可以的。Q:华为云数据库如何支持SQL和NoSQL的混合使用?A:可以了解下HTAP数据库场景。Q:如何将现有的MySQL数据库迁移到GaussDB(for MySQL),是否有自动迁移工具?A:华为云数据复制服务(DRS)可以很方便的完成实例迁移,支持多种主流数据源,实现数据安全、稳定、实时、高速传输,提供极致交互体验。 详细请参考官网:https://www.huaweicloud.com/product/drs.htmlQ:如何使用华为云数据库服务API进行数据传输?A:数据库服务API针对数据库的管理场景提供接口服务,并不具备数据传输能力。Q:GaussDB是否兼容现有的MySQL应用?A:GaussDB的M兼容性,兼容MySQL协议。Q:华为云数据库服务API支持哪些编程语言?A:数据库服务API是通用的Restful接口,允许不同软件系统之间进行交互,支持任何语言。数据库服务API为开发者提供了对应的SDK工具,支持主流的7种语言的能力。Q:GaussDB支持自动扩展吗?如何配置?A:云数据库GaussDB服务API支持相关场景自动扩展,详细请参考官网:https://support.huaweicloud.com/usermanual-gaussdb/gaussdb_01_471.htmlQ:使用云数据库相比传统数据库,在成本上有哪些优势?A:云数据库服务相比传统的数据库,云数据库即用即买,随时退订。Q:GaussDB(for MySQL)垂直集成能力的具体实现方式是什么,它如何提升云环境下的扩展性和性能?A:包周期实例可以通过容量变更功能实现磁盘扩容可以通过规格变更到更高的CPU和内容提升性能可以通过添加只读节点的方式实现扩展性Q:在使用GaussDB时,有哪些最佳实践可以提升数据库性能?A:云数据库GaussDB服务性能调优过程需要综合考虑多方面因素,具体详情请参考GaussDB官网性能调优章节https://support.huaweicloud.com/distributed-devg-v2-gaussdb/gaussdb_v5r2c10_0221.htmlQ:华为云如何保证数据的安全性?A:华为云数据库服务采用多重防护安全方案,如:身份认证、访问控制、数据保护、监控以及恢复等,是业务数据存储和使用更加放心。Q:如何将现有的数据库迁移到华为云数据库?A:华为云数据复制服务(DRS)可以很方便的完成实例迁移,支持多种主流数据源,实现数据安全、稳定、实时、高速传输,提供极致交互体验。 详细请参考官网:https://www.huaweicloud.com/product/drs.htmlQ:华为云的GaussDB有哪些独特的优势?A:云数据库GaussDB服务主要有四个优势。1、高安全GaussDB拥有TOP级的商业数据库安全特性,如下所示,能够满足政企和金融级客户的核心安全诉求。数据动态脱敏,行级访问控制,密态计算。2、健全的工具与服务化能力GaussDB已经拥有华为云,商用服务化部署能力,同时支持DAS、DRS等生态工具。有效保障用户开发、运维、优化、监控、迁移等日常工作需要。3、全栈自研GaussDB基于鲲鹏生态,是当前国内唯一能够做到全栈自主可控的国产品牌。同时GaussDB能够基于硬件优势在底层不断进行优化,提升产品综合性能。4、开源生态GaussDB已经支持开源社区,并提供主备版版本下载。Q:GaussDB(for MySQL)支持的最大存储容量是多少,与GaussDB相比如何实现这一目标?A:云数据库GaussDB(for MySQL)服务基于华为最新一代DFV 存储,采用计算存储分离架构,支持1主15只读的高扩展性、128T的海量存储。Q:华为云数据库服务API有哪些限制?A:每一个API使用的场景约束们在API文档上有详细的说明。Q:使用华为云数据库服务API时,如何确保数据的安全性和稳定性?A:华为云api提供了两种件鉴权方式,分别为token和ak,sk的方式。在版本迭代过程中api会做到100%向前兼容。Q:GaussDB的密态计算功能是如何实现的,与其他数据库相比有何优势?A:云数据库GaussDB服务支持全密态能力,密文数据检索计算。1、数据在存储、传输、查询整个生命周期过程中均以密文形态存在。2、密钥掌握在用户自己手上,数据库管理员无法获取,加解密过程仅在客户侧完成。3、语法自动解析,应用无感知,将语法解析内置到驱动中,不需要修改原有的SQL语句、数据类型等。4、通过软硬结合可以将部分算法不进行加解密过程传递,减少硬件IO。5、通过数学算法,直接对密文进行查询,极大减少加解密时的性能损耗。详细请参考官网:cid:link_1Q:华为云数据库服务API有哪些应用场景?A:能力和控制台齐平,控制台能做的能力,api可以代替。Q:如何解决GaussDB(for MySQL)中长事务导致的锁超时问题?A:云数据库GaussDB(for MySQL)服务通过非阻塞DDL对该问题进行了相关特性的优化,详细请参考官网: https://support.huaweicloud.com/kerneldesc-gaussdbformysql/gaussdbformysql_20_0015.htmlQ:默认是一主多备的话,数据都是多副本的对吧A:是的。GaussDB(for MySQL)跨3AZ高可用部署,集群内数据3副本。Q:有没有关于数据库监控的优化A:参考官网:https://support.huaweicloud.com/usermanual-gaussdb/gaussdb_01_238.htmlQ:有类似于oracle的“完全恢复”功能吗A:数据库服务支持PITR恢复,可以恢复到具体时间点。 参考官网:https://support.huaweicloud.com/usermanual-gaussdb/gaussdb_01_646.htmlQ:在性能方面,GaussDB 和 GaussDB (for MySQL) 有哪些突出表现?与传统数据库相比有何优势?还有这两个的区别和核心能力呢?A:采用计算与存储分离,日志即数据架构,性能提升至开源MySQL的7倍。详情请看文档:https://support.huaweicloud.com/productdesc-gaussdbformysql/introduction.htmlQ:GaussDB for mysql,如果要把mysql的实例迁移过来,有专属软件吗A:通过华为云DRS(数据复制服务)可以很方便的完成实例迁移。Q:能否通过API获取GaussDB的实时负载,以及同时多少个连接正在查询修改。以及当前服务器资源使用率A:GaussDB提供了丰富的指标堆在在华为云监控服务上,可调用云监控服务进行查询。Q:GaussDB的API是否支持完整的CRUD操作,以及是否能够通过API实现数据库的自动化运维?A:数据库服务API接口支持数据库管理操作,不支持数据类CRUD操作。Q:认证有考试报名费用吗?A:需要的想要了解更多数据库相关知识,欢迎观看DTSE Tech Talk 系列技术直播
-
活动完成标准请注册华为云账号并完成实名认证,实验过程中请使用Chrome浏览器完成相关操作。华为云账号注册步骤请参考:cid:link_4实名认证操作步骤请参考:cid:link_21 通过CodeArts API设计语音合成接口1.1 进入CodeArts API官网,点击网页版体验并登录cid:link_51.2 创建项目1.2.1 登录后点击页面右上角的“创建项目”按钮1.2.2 在创建项目弹窗中输入项目名称:语音合成,并点击保存1.2.3 提示保存成功,点击API设计页签,进入到API设计页面1.3 设计接口1.3.1 点击页面上的“新建API接口”卡片1.3.2 显示新建标签页1.3.3 输入接口的URL:/tts1.3.4 输入接口名称:语音合成1.3.5 下拉滚动条到请求体区域,将请求的格式修改为:application/json1.3.6 显示请求体的数据结构模板,默认根节点类型为“object”1.3.7 点击根节点最右侧的“+”操作1.3.8 页面上在根节点下新增一条参数(可以通过滚动条左右拖动)1.3.9 将参数名param2修改为text,描述param2修改为待合成的文本1.3.10 下拉页面,看到返回响应区域1.3.11 同样点击根节点最右侧的“+”操作(可以通过滚动条拖动到最右侧),在根节点下显示一条新的参数1.3.12 将参数名param2修改为result,将描述修改为合成结果1.3.13 点击右上角的保存按钮1.3.14 保存成功,设计完成2 通过API Explorer调试语音合成接口,生成5段音频2.1 语音合成(API Explorer部分)链接:cid:link_32.1.1 生成语音格式为wav,采样率:8000,标准女音,语速0,音高0图1.1.1 wav标准女音2.1.2 生成语音格式为wav,采样率:16000,标准男声,语速0,音高0图1..12 wav标准男音2.1.3 生成语音格式为mp3,采样率:8000,标准女音,语速100,音高100图1.1.3 mp3标准女音2.1.4 生成语音格式为mp3,采样率:16000,标准男声,语速100,音高100图1.1.4 mp3标准男声2.1.5 生成语音格式为pcm,采样率:16000,标准男声,语速100,音高100图1.1.5 标准男声3 基于华为云自研CodeArts IDE,实现数据流转换为音频的功能3.1 打开CodeArts IDE ONLINE链接:cid:link_6点击免费体验云开发3.2 实现数据流转换为音频3.2.1 将实验用Demo工程上传到CodeArts IDE OnlineDemo工程下载链接:cid:link_11)打开CodeArts IDE Online后,左上角点击“文件”->“上传文件”:2)上传“CodeArtsProject.zip”,点击“终端”对工程进行解压3)左上角->文件->打开,打开解压后的工程目录3.2.2 在华为云控制台新增访问秘钥,配置AK/SK1)登录华为云控制台:https://console.huaweicloud.com/iam/?agencyId=599e2ee1492f48e9b69a0a682d435fb8®ion=cn-north-4&locale=zh-cn#/mine/accessKey选择“我的凭证”->“访问秘钥”->新增访问秘钥注:如果已经有AK/SK秘钥,不需要新增访问秘钥创建秘钥成功后点击“立即下载”,即可获得“credentials.csv”文件,里面可以查看到AK(Access Key Id)和SK(Secret Access Key)2)进入工程,src/main/resources下选择credentials.properties,讲AK/SK配置上3.2.3 将API Explorer上调试成功的流数据文件配置到APIResponse.json中在2.1语音合成章节,我们在API Explorer完成接口的调试cid:link_3最终生成“SIS_RunTts_ResponseBody_xxxxxxxxxxxxx.json”文件,打开json文件,复制响应体内容打开工程,进入src/main/resources中,选择APIResponse.json,将json文件的内容复制到APIResponse.json中。3.2.4 执行“should_return_audio_when_response_json”,生成mps格式音频文件:找到test目录下的测试类SisServiceTest.java,点击找到方法“should_return_audio_when_response_json()”,点击Run Test,最后可以看到目录下生成的音频文件。目录下生成“devkit.mps”文件右击下载,可以获得转换后的音频文件,实验结束
-
开发是用windows开发的,发版是Linux环境,Windows环境发版没有问题【主库】Next recovery time: 2024/1/15 17:50:50 (ERROR [01000] [unixODBC][Driver Manager]Can't open lib '/usr/lib64/psqlodbcw.so' : file not found)【主库】Next recovery time: 2024/1/15 17:41:38 (ERROR [01000] [unixODBC][Driver Manager]Can't open lib 'PostgreSQL Unicode' : file not found)
-
重庆工程学院HCSD活动复盘总结HCSD名称:重庆工程学院校园大使姓名:肖宏城所在学校:重庆工程学院活动背景介绍活动目的及意义为进一步加强计算机专业学生的综合实力,增强计算机专业的技术水平,提高社会竞争力,以学致用,学用结合,重庆工程学院联合多专业社团开展了“华为云HCSD码上服务”活动。邀请华为云专家到场解读行业最新动态,向同学们分享前沿技术知识,热门的实验分享,场景化的动手实践,让大家充分体验到华为云产品的技术魅力。通过宣讲,以及教师团队的协助,给未接触和接触较浅的同学和老师进行宣讲,让其了解和深入学习我们华为云的一些技术,了解我们华为云的产品以及好处,加大师生对华为云的认知度,认可度以及能通过此次活动吸取更多优秀的人才进入到我们的组织。活动预期目标活动宣传辐射人数全校计算机专业同学、活动参与人数力争200余人,通过介绍华为云的技术以及产品,吸引更多的开发者使用华为API、开发工具以及一些云服务,了解华为云带来的功能以及对于自身带来的好处。活动效果目标达成情况:宣传实际辐射人数达到300余人、活动实际参与人数120余人、体验平台人数预计80人活动现场情况:活动现场学生积极进行互动和提问,对于未来个人就业发展以及行业发展趋势有一定的自我认知,扩展了对于计算机行业的多职务的认识;现场氛围良好,工作人员细心的对学生进行引导,活跃现场气氛,积极组织现场秩序,带动现场的趣味性;华为云DTSE专家热情的回答大家的问题,给大家讲解华为云以及华为的业务范围,打破大众化认知。优缺点及改进方案活动亮点现场活动氛围良好,学生富有积极性,踊跃参加开发者认证申请,积极提出华为云存在的一些不足点,帮助华为云后续的发展。活动不足及改进方案对于宣传的方式以及途径有待改进,活动的整个申请到开展以及结束过程含有多方面沟通问题,对于活动开展的校方人员等时间安排和沟通上存在不足,校方的人员因为各种活动未到场进行一定的积极作用引导,对于一些因素仍未考虑到位。活动现场照片资源的投入及使用情况资源名称数量单价总价已发放数量结余数量荣耀体脂秤511055041数据线20011220096104定制T恤1550750150ModelArts人工智能应用开发指南30501500273帆布包1525375132
-
了不起的开发者们,华为开发者大会2023已开启!7月9日,我们携手产品专家、行业大咖,和开发者们一起开创以API为核心的数字化变革,实现API全生命周期一体化协作!API是数字化转型的基石,构建一个可靠的API全生命周期管理底座是数字化企业务实之选。API运行时决定企业生存发展的质量,APIG云原生网关为API的安全、稳定、高效、智能运行全方位保驾护航。
-
【问题来源】 百信银行 【问题简要】获取指定日期内的坐席维度监控指标接接口中的数据比如接通量,外呼量,等外呼统计数据是什么逻辑计算的【问题类别】【必填】 坐席【AICC解决方案版本】【必填】 AICC 8.12.0【期望解决时间】2023/05/23【问题现象描述】【必填】 获取指定日期内的坐席维度监控指标接接口中的数据比如接通量,外呼量,等外呼统计数据,目前是不准确的,想问下具体是什么逻辑计算的
-
华为云开发者插件(Huawei Cloud Toolkit)是围绕华为云开发者生态工具体系打造的一系列IDE插件,支持华为云API能力对接、自然语言一键转换成C++、Java等标准代码、一键部署到云端等等。一、账号准备点击链接进入产品页面,点击右上角登录/注册华为云账号,查看产品介绍、帮助文档等。cid:link_3华为云API插件是Huawei Cloud Toolkit系列插件的一员,使用API插件我们可以对接华为云丰富的API能力。API插件支持当下IDE平台(IntelliJ IDEA、VS Code)以及华为IDE CodeArts。二、IDE平台支持平台:IntelliJ系列IDE或者VS Code IDE基本操作流程:API插件安装-->华为云账号登录(请提前安装底座插件)-->API插件体验API插件安装IntelliJ IDEA安装:在IntelliJ IDEA顶部菜单栏中选择File > Settings,在Settings对话框的左侧导航栏中单击Plugins。Plugins区域单击Marketplace,在搜索栏中输入Huawei Cloud API。Search Results区域会出现Huawei Cloud API,单击Install,完成后重启IDE。VS Code IDE安装:在Visual Studio Code顶部菜单栏中选择File > Preferences > Extensions,弹出VS Code IDE的Marketplace的插件列表。在搜索栏中输入Huawei Cloud API。找到Huawei Cloud API插件,选择install,完成后重启IDE。华为云账号登录使用前:请先在IDE中安装底座插件Huawei Cloud Toolkit Platform,并完成华为云账号登录。IntelliJ IDEA登录:点击左侧菜单中的Huawei Cloud Toolkit图标,打开插件主面板,选择右上角的用户头像按钮,在弹出的页面选择华为云AK/SK登录、华为账号登录或者CSDN账号登录。若选择AK/SK登录,会进入Setting面板,填入AK、SK后点击apply进行登录操作。(AK/SK获取请点击)若选择华为账号或者CSDN账号登录,会打开浏览器进入登录页面,IDE侧会同步登录结果(华为云账号注册请点击)。VS Code IDE登录:点击左侧菜单中的Huawei Cloud Toolkit图标,打开插件主面板,选择右上角的用户头像按钮,在弹出的页面选择华为云AK/SK登录或者华为账号登录。若选择AK/SK登录,则输入之前准备好的AK、SK登录华为云。(AK/SK获取请点击)若选择华为账号登录,则会跳至浏览器在web页面登录华为云账号,IDE侧会同步登录结果(华为云账号注册请点击)。API插件体验打开插件左侧主面板:点击IDEA或者VSCode左侧工具框中的"Huawei Cloud Toolkit"工具按钮、点击插件主面板中的API主页,则可打开华为云API插件主面板 。选择任意一个云服务,查看该云服务下的提供的相关API、示例代码等,详情能力请参考API插件能力。三、华为CodeArts IDE点击进入CodeArts IDE在线免费体验(需要完成华为云账号登录操作)。2)点击左侧工具栏中的华为云API图标,则可打开华为云API插件主面板。点击查看更多。
-
文章审核未通过,请给出具体原因,如何修改?内容所属频道:博客内容标题名称:Eolink Apikit 智能 Mock 解析内容链接:https://bbs.huaweicloud.com/blogs/397063
-
文章审核未通过,请给出具体原因,如何修改?发帖正文:内容所属频道:博客内容标题名称:如何在 DevOps 中进行 API 全生命周期管理?内容链接:https://bbs.huaweicloud.com/blogs/396728
-
【问题来源】【必填】 百信 问题来源这一项如果涉及隐私,可以不填 【问题简要】【必填】 查询外呼活动重试策略,返回码是401【问题类别】【必填】 座席【AICC解决方案版本】【必填】 【AICC可选择版本:AICC 8.12.0,AICC 8.13.0等】【期望解决时间】2023/04/13【问题现象描述】【必填】 调用查询外呼活动重试策略,返回码是401
-
修改外呼互动v2https://ip:port/rest/isales/v2/openapi/campaigns/{vdnId}在接口文档 表14中 retryPolicyId 重试策略id 怎么获取
-
C++使用Vector导致判题器崩溃问题描述只需要在main.cpp中加入如下代码,即会出现判题器崩溃。#include <vector> class Crash{ std::vector<int> a; }; Crash crash;运行环境及报错使用的编译指令如下为 g++ main.cpp -o main.exe使用的执行指令为 Robot_gui.exe -f SDK/c++/main.exe -m maps\1.txt使用的编译器为 MinGW64报错信息如下[INFO]D:\Project\CodeCraft2023\Robot\Game.cpp:53|Game::Init|frame[1]|Loaded workbench : 37 ReadFileEx err. 管道已结束。 [ERR]D:\Project\CodeCraft2023\Robot\Player.cpp:214|Player::HandleIOError|frame[1]|Handle IO Error [ERR]D:\Project\CodeCraft2023\Robot\Player.cpp:72|Player::PlayerCrash|frame[1]|Player crashed. [ERR]D:\Project\CodeCraft2023\Robot\Game.cpp:285|Game::Run|frame[1]|WaitForPlayerReady err. [INFO]D:\Project\CodeCraft2023\Robot\Game.cpp:528|Game::GameOver|frame[1]|player skipped frames:0 {"status":"Runtime error.","score":0}PS:由于该问题在编译过程中不会有任何报错和警告,也未查询到可能的报错原因,只有运行判题器的时候会崩溃。因此恳请主办方帮忙调查一下原因。附录附上完整的main.cpp内容。#include <iostream> using namespace std; #include <vector> class Crash{ std::vector<int> a; }; Crash crash; bool readUntilOK() { char line[1024]; while (fgets(line, sizeof line, stdin)) { if (line[0] == 'O' && line[1] == 'K') { return true; } //do something } return false; } int main() { readUntilOK(); puts("OK"); fflush(stdout); int frameID; while (scanf("%d", &frameID) != EOF) { readUntilOK(); printf("Ó55616c3-2de7-4283-8218-d1e008d73ce2n", frameID); int lineSpeed = 3; double angleSpeed = 1.5; for(int robotId = 0; robotId < 4; robotId++){ printf("forward %d Ó55616c3-2de7-4283-8218-d1e008d73ce2n", robotId, lineSpeed); printf("rotate %d ó55616c3-2de7-4283-8218-d1e008d73ce2n", robotId, angleSpeed); } printf("OK\n", frameID); fflush(stdout); } return 0; }
上滑加载中
推荐直播
-
华为云码道Skill实战与极速交付,智能开发全链路实战2026/07/22 周三 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;姜浩-华为云HCDG核心组成员
直播深度解读华为云码道6月产品新特性,从Skill市场安装专家技能,带你零距离体验从需求,开发,审查,重构全链路闭环的开发过程。从零构建并交付一个完整项目,让您体验从代码提交到服务上线的“极速”之旅。
回顾中 -
聚开发者之力,创具身新未来2026/07/23 周四 15:00-17:00
张豪杰/程文/王军/刘新春/黄钦开 /张晓天
本次华为云具身智能开发平台CloudRobo培训面向具身智能开发者,带您全流程体验机器人本体R2C小时级接入、环境重建与轨迹生成仿真数据生产、PB级数据管理、数据评测、模型训推、强化学习和Benchmark一键评测等功能,并体验业界主流具身模型应用。
回顾中
热门标签