• [高校训练营] 基于 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/ 
  • [行业前沿] 深入理解 HTTP 缓存机制
     在现代 Web 开发中,性能优化是提升用户体验和降低服务器成本的核心环节。而在众多优化手段中,HTTP 缓存因其简单高效、效果显著,成为前端与后端开发者必须掌握的基础能力。合理的缓存策略不仅能大幅减少网络请求、加快页面加载速度,还能有效减轻源服务器负载。本文将系统讲解 HTTP 缓存的工作原理、核心头部字段、验证机制及实际应用策略,帮助开发者构建高性能的 Web 应用。一、HTTP 缓存的基本原理HTTP 缓存是指浏览器或其他中间代理(如 CDN)将服务器返回的响应内容临时存储,在后续相同请求中直接使用本地副本,而无需再次向源服务器发起完整请求。这种机制基于“内容在一段时间内不会频繁变化”的假设,适用于静态资源(如图片、CSS、JS)以及部分动态内容。HTTP 缓存分为两类:强缓存(Strong Cache):直接使用本地缓存,不与服务器通信。协商缓存(Revalidation Cache):向服务器确认缓存是否仍有效,若有效则返回 304 Not Modified,避免重复传输内容。整个流程由 HTTP 响应头中的特定字段控制,客户端根据这些指令决定如何处理缓存。二、强缓存:通过过期时间控制强缓存的核心在于设置资源的有效期,一旦在有效期内,浏览器将直接使用本地缓存,不会发送请求到服务器(地址栏回车或新标签页打开除外)。1. Cache-ControlCache-Control 是 HTTP/1.1 引入的标准头部,优先级高于旧的 Expires 字段。常用指令包括:max-age=<seconds>:指定资源在多少秒内有效Cache-Control: max-age=3600表示该资源在 1 小时内可被直接使用。public / private:public 表示响应可被任何缓存(包括 CDN、代理)存储private 表示仅用户浏览器可缓存,中间代理不得缓存(适用于含用户私有信息的内容)no-cache:不使用强缓存,但可使用协商缓存(每次需验证)no-store:禁止任何形式的缓存,敏感数据(如银行页面)应使用此指令2. ExpiresExpires 是 HTTP/1.0 的遗留字段,指定一个绝对过期时间(GMT 格式):Expires: Wed, 21 Nov 2025 10:00:00 GMT由于依赖客户端时间,易受时钟偏差影响,现代开发中应优先使用 Cache-Control: max-age。三、协商缓存:验证缓存有效性当强缓存过期后,浏览器会向服务器发起请求,但会携带缓存验证信息。服务器根据这些信息判断资源是否已更新,若未更新则返回 304 状态码,告知客户端继续使用本地缓存。协商缓存依赖两组头部字段:1. Last-Modified 与 If-Modified-Since服务器在首次响应中返回 Last-Modified,表示资源最后修改时间:Last-Modified: Mon, 18 Nov 2025 08:30:00 GMT后续请求中,浏览器自动添加 If-Modified-Since:If-Modified-Since: Mon, 18 Nov 2025 08:30:00 GMT服务器比较该时间与当前资源修改时间:若未修改,返回 304 Not Modified(无响应体)若已修改,返回 200 OK 及新内容局限性:若资源在 1 秒内多次修改,Last-Modified 无法精确识别变化。2. ETag 与 If-None-Match为解决上述问题,HTTP/1.1 引入了 ETag(Entity Tag),它是资源的唯一标识符(通常为内容哈希值):首次响应:ETag: "a1b2c3d4e5"后续请求:If-None-Match: "a1b2c3d4e5"服务器比对 ETag:相同 → 返回 304不同 → 返回 200 和新内容ETag 精度更高,但计算哈希可能带来 CPU 开销。对于静态文件,Web 服务器(如 Nginx)通常基于文件 inode 和修改时间生成弱 ETag(以 W/ 开头),兼顾效率与准确性。四、实际应用中的缓存策略1. 静态资源:长期强缓存 + 文件指纹对于 CSS、JS、图片等静态资源,推荐采用“永久缓存 + 内容哈希”的策略:构建时为文件名添加哈希值:app.a1b2c3.js设置 Cache-Control: max-age=31536000(1 年)当内容变更时,文件名随之改变,浏览器自动请求新资源此方案确保用户始终获取最新版本,同时最大化利用缓存。2. HTML 页面:短时效或协商缓存HTML 通常作为入口文件,内容可能频繁变化,不宜长期缓存。建议:设置 Cache-Control: no-cache,强制每次验证或使用较短的 max-age(如 60 秒)配合 must-revalidate3. API 接口:按需配置公共数据(如城市列表):可设置较长缓存时间用户私有数据:使用 private + 短 max-age 或 no-cache敏感操作(如支付结果):使用 no-store五、常见问题与注意事项强制刷新行为用户按下 Ctrl+F5(Windows)或 Cmd+Shift+R(Mac)时,浏览器会忽略缓存并发送带 Cache-Control: no-cache 的请求。CDN 缓存与源站缓存分离CDN 通常遵循自身的缓存规则,可能覆盖源站的 Cache-Control。需在 CDN 控制台单独配置缓存策略。Service Worker 的影响若使用 PWA 技术,Service Worker 可完全接管请求,此时 HTTP 缓存可能被绕过,需在 SW 脚本中显式处理缓存逻辑。避免缓存污染对包含用户身份信息的 URL(如 /api/profile?user=123),确保不同用户的响应不会被错误共享,可通过设置 private 或使用认证头隔离。六、结语HTTP 缓存机制是 Web 性能优化的基石之一。通过合理配置 Cache-Control、ETag 等头部字段,开发者可以在不改动业务逻辑的前提下,显著提升页面加载速度和系统吞吐量。然而,缓存也是一把双刃剑——配置不当可能导致用户看到过期内容或资源无法更新。因此,理解其工作原理,并结合项目特点制定清晰的缓存策略,是每个 Web 开发者必备的能力。在实践中,建议结合浏览器开发者工具的 Network 面板,实时观察缓存命中情况,持续优化缓存配置。
  • [技术干货] 应用层协议——HTTP-转载
    HTTP协议虽然我们说,应用层协议是我们程序猿自己定的.但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用.HTTP(超文本传输协议)就是其 中之一。在互联网世界中,HTTP(Hyper Text Transfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如HTML文档)。 HTTP协议是客户端与服务器之间通信的基础。客户端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP协议是一个无连接、无状态的协 议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。认识URL平时我们俗称的"网址"其实就是说的URL urlencode和urldecode像/?:等这样的字符,已经被url当做特殊意义理解了.因此这些字符不能随意出现.比如,某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义. 转义的规则如下:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位 做一位,前面加上%,编码成%XY格式。如下图搜索C++中"+" 被转义成了"%2B" urldecode 就是urlencode的逆过程;在线编码工具:http://tool.chinaz.com/tools/urlencode.aspx选中其中的URL编码/解码模式,在输入C++后点击编码就能得到编码后的结果。 同理C%2B%2B点击解码就会得到C++HTTP协议请求与响应格式HTTP请求HTTP请求协议格式如下: HTTP请求由以下四部分组成:请求行:[请求方法]+[url]+[http版本]请求报头:请求的属性,这些属性都是以key: value的形式按行陈列的。空行:遇到空行表示请求报头结束。请求正文:请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度。其中,前面三部分是一般是HTTP协议自带的,是由HTTP协议自行设置的,而请求正文一般是用户的相关信息或数据,如果用户在请求时没有信息要上传给服务器,此时请求正文就为空字符串。代码如下class HttpRequest{public:    HttpRequest()    {    }    std::string Serialize()    {        return std::string();    }    void ParseReqLine(std::string &reqline)    {        // GET / HTTP/1.1        std::stringstream ss(reqline);        ss >> _method >> _uri >> _version;    }    bool Deserialize(std::string &reqstr)    {        // 1. 提取请求行        std::string reqline;        bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);        LOG(LogLevel::DEBUG) << reqline;         // 2. 对请求行进行反序列化        ParseReqLine(reqline);        if (_uri == "/")            _uri = webroot + _uri + homepage; // ./wwwroot/index.html        else            _uri = webroot + _uri; // ./wwwroot/a/b/c.html         LOG(LogLevel::DEBUG) << "_method: " << _method;        LOG(LogLevel::DEBUG) << "_uri: " << _uri;        LOG(LogLevel::DEBUG) << "_version: " << _version;         // ./wwwroot/XXX.YYY        return true;    }    std::string Uri() { return _uri; }    ~HttpRequest()    {    } private:    std::string _method;  // 请求方法    std::string _uri;     // URI    std::string _version; // HTTP版本     std::unordered_map<std::string, std::string> _headers; // 请求报头    std::string _blankline;                                // 空行    std::string _text;                                     // 请求正文};AI写代码cpp运行HTTP响应HTTP响应由以下四部分组成:状态行:[http版本]+[状态码]+[状态码描述]响应报头:响应的属性,这些属性都是以key: value的形式按行陈列的。空行:遇到空行表示响应报头结束。响应正文:响应正文允许为空字符串,如果响应正文存在,则响应报头中会有一个Content-Length属性来标识响应正文的长度。比如服务器返回了一个html页面,那么这个html页面的内容就是在响应正文当中的。 代码如下:class HttpResponse{public:    HttpResponse() : _blankline(glinespace), _version("HTTP/1.0")    {    }    // 实现: 成熟的http,应答做序列化,不要依赖任何第三方库!    std::string Serialize()    {        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace;        std::string resp_header;        for (auto &header : _headers)        {            std::string line = header.first + glinesep + header.second + glinespace;            resp_header += line;        }         return status_line + resp_header + _blankline + _text;    }     void SetTargetFile(const std::string &target)    {        _targetfile = target;    }     void SetCode(int code)    {        _code = code;        switch (_code)        {        case 200:            _desc = "OK";            break;        case 404:            _desc = "Not Found";            break;        default:            break;        }    }     bool MakeResponse()    {        bool res = Util::ReadFileContent(_targetfile, &_text); // 浏览器请求的资源,一定会存在吗?出错呢?        if (!res)        {            SetCode(404);            _targetfile=webroot+page_404;            Util::ReadFileContent(_targetfile, &_text);        }        SetCode(200);        return true;    }    bool Deserialize(std::string &reqstr)    {        return true;    }    ~HttpResponse() {} public:    std::string _version;    int _code;         // 404    std::string _desc; // "Not Found"     std::unordered_map<std::string, std::string> _headers;    std::string _blankline;    std::string _text;     // 其他属性    std::string _targetfile;};AI写代码cpp运行Http类class Http{public:    Http(uint16_t port) : tsvrp(std::make_unique<TcpServer>(port))    {    }    void HandlerHttpRquest(std::shared_ptr<Socket> &sock, InetAddr &client) {    // 1. 接收HTTP请求数据    std::string httpreqstr;    int n = sock->Recv(&httpreqstr); // 读取请求数据     if (n > 0) {        // 2. 解析HTTP请求        HttpRequest req;        req.Deserialize(httpreqstr);         // 3. 生成HTTP响应        HttpResponse resp;        resp.SetTargetFile(req.Uri()); // 设置目标文件路径        resp.MakeResponse();           // 读取文件内容或返回404         // 4. 发送响应        std::string response_str = resp.Serialize();        sock->Send(response_str);    }}    void Start()    {        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)                     { this->HandlerHttpRquest(sock, client); });    }    ~Http()    {    } private:    std::unique_ptr<TcpServer> tsvrp;};AI写代码cpp运行服务器代码#include "Http.hpp" //http portint main(int argc, char *argv[]){    if(argc != 2)    {        std::cout << "Usage: " << argv[0] << " port" << std::endl;        exit(USAGE_ERR);    }    uint16_t port = std::stoi(argv[1]);     std::unique_ptr<Http> httpsvr = std::make_unique<Http>(port);    httpsvr->Start();    return 0;}AI写代码cpp运行http协议底层代码还是TCP socket,我们在服务器创建Http类的智能指针,并启动服务器在Start()函数中使用lambda表达式捕获类中的HandlerHttpRquest()在HandlerHttpRquest()中1. 接收HTTP请求数据当你在浏览器中输入网址(如 http://106.54.48.112:8080/index.html)并访问时,浏览器会通过 TCP 连接向你的服务器发送 HTTP 请求数据。你的服务器通过 sock->Recv()读取的正是浏览器发送的原始 HTTP 请求数据。2. 解析HTTP请求在HTTP请求类中实现的反序列化读取_method , _uri , _version,主要是获得uri这样就可以访问服务器上wwwroot目录下的资源。3. 生成HTTP响应在获得HTTP请求后,要告诉浏览器资源的路径即设置路径,然后完成响应。在 MakeResponse方法中调用Util::ReadFileContent(_targetfile, &_text)的目的是读取客户端请求的目标文件内容(106.54.48.112:8080/index.html),并将其存储到 HTTP 响应的 _text 成员变量中,以便后续通过 Serialize()方法将文件内容作为 HTTP 响应正文返回给浏览器4. 发送响应即把序列化后的字符串发给浏览器注:在请求与响应中要严格按照图片中的结构完成序列化,否则浏览器与服务器无法建立连接// 工具类 class Util{public:    static bool ReadFileContent(const std::string &filename, std::string *out)    {        // version1        std::ifstream in(filename);        if (!in.is_open())        {            return false;        }        std::string line;        while (std::getline(in, line))        {            *out += line;        }        in.close();        return true;    }    static bool ReadOneLine(std::string &bigstr, std::string *out, const std::string &sep /*\r\n*/)    {        auto pos = bigstr.find(sep);        if (pos == std::string::npos)            return false;         *out = bigstr.substr(0, pos);        bigstr.erase(0, pos + sep.size());        return true;    }    static int FileSize(const std::string &filename)    {        std::ifstream in(filename, std::ios::binary);        if (!in.is_open())            return -1;        in.seekg(0, in.end);        int filesize = in.tellg();        in.seekg(0, in.beg);        in.close();        return filesize;    }};AI写代码cpp运行index.html以ai帮我们完成网址首页为例<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>Default Home Page</title>    <style>        body {            font-family: Arial, sans-serif;            margin: 0;            padding: 0;            background-color: #f4f4f4;            color: #333;        }        header {            background-color: #007bff;            color: #fff;            padding: 10px 20px;            text-align: center;        }        nav {            background-color: #343a40;            padding: 10px 0;        }        nav a {            color: #fff;            text-decoration: none;            padding: 10px 20px;            display: inline-block;        }        nav a:hover {            background-color: #5a6268;        }        .container {            padding: 20px;        }        .welcome {            text-align: center;            margin-bottom: 20px;        }        .welcome h1 {            margin: 0;        }        .content {            background-color: #fff;            padding: 20px;            border-radius: 8px;            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);        }        footer {            background-color: #343a40;            color: #fff;            text-align: center;            padding: 10px 0;            position: fixed;            width: 100%;            bottom: 0;        }    </style></head><body>    <header>        <h1>Welcome to Our Website</h1>    </header>    <nav>        <a href="#">Home</a>        <a href="Login.html">Login</a> <!-- 跳转到登录页面 -->        <a href="Register.html">Register</a> <!-- 跳转到注册页面 -->        <a href="#">About</a>        <a href="#">Contact</a>    </nav>    <div class="container">        <div class="welcome">            <h1>Welcome to Our Default Home Page</h1>            <p>This is a simple default home page template.</p>        </div>        <div class="content">            <h2>Introduction</h2>            <p>This is a basic HTML template for a default home page. It includes a header, navigation bar, a welcome section, and a content area. You can customize this template to suit your needs.</p>        </div>    </div>    <footer>        <p>&copy; 2025 Your Company Name. All rights reserved.</p>    </footer></body></html>AI写代码cpp运行代码效果服务器日志的消息 浏览器输入服务器ip+端口号 网址默认首页 HTTP的方法HTTP常见的方法如下:方法说明支持的HTTP协议版本​GET​获取资源1.0、1.1​POST​传输实体主体1.0、1.1​PUT​传输文件1.0、1.1​HEAD​获得报文首部1.0、1.1​DELETE​删除文件1.0、1.1​OPTIONS​询问支持的方法1.1​TRACE​追踪路径1.1​CONNECT​要求用隧道协议连接代理1.1​LINK​建立和资源之间的联系1.0​UNLINE​断开连接关系1.0其中最常用的就是GET方法和POST方法。GET方法和POST方法GET方法一般用于获取某种资源信息,而POST方法一般用于将数据上传给服务器。但实际我们上传数据时也有可能使用GET方法,比如百度提交数据时实际使用的就是GET方法。GET方法和POST方法都可以带参:GET方法是通过url传参的。POST方法是通过正文传参的。从GET方法和POST方法的传参形式可以看出,POST方法能传递更多的参数,因为url的长度是有限制的,POST方法通过正文传参就可以携带更多的数据。此外,使用POST方法传参更加私密,因为POST方法不会将你的参数回显到url当中,此时也就不会被别人轻易看到。不能说POST方法比GET方法更安全,因为POST方法和GET方法实际都不安全,要做到安全只能通过加密来完成HTTP的状态码HTTP的状态码如下:类别    分类    原因短语1XX    Informational(信息性状态码)    接收的请求正在处理2XX    Success(成功状态码)    请求正常处理完毕3XX    Redirection(重定向状态码)    需要进行附加操作以完成请求4XX    Client Error(客户端错误状态码)    服务器无法处理请求5XX    Server Error(服务器错误状态码)    服务器处理请求出错最常见的状态码,比如200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect),504(Bad Gateway)。Redirection(重定向状态码)重定向就是通过各种方法将各种网络请求重新定个方向转到其它位置,此时这个服务器相当于提供了一个引路的服务。重定向又可分为临时重定向和永久重定向,其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向。临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。而如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。临时重定向演示进行临时重定向时需要用到Location字段,Location字段是HTTP报头当中的一个属性信息,该字段表明了你所要重定向到的目标网站。我们这里要演示临时重定向,可以将HTTP响应当中的状态码改为302,然后跟上对应的状态码描述,此外,还需要在HTTP响应报头当中添加Location字段,这个Location后面跟的就是你需要重定向到的网页,比如我们这里将其设置为qq的首页。代码如下class HttpResponse{public:    HttpResponse() : _blankline(glinespace), _version("HTTP/1.0")    {    }        void SetCode(int code)    {        _code = code;        switch (_code)        {        case 200:            _desc = "OK";            break;        case 404:            _desc = "Not Found";            break;        case 302:            _desc = "See Other";            break;        default:            break;        }    }    void SetHeader(const std::string &key, const std::string &value)    {        auto iter = _headers.find(key);        if (iter != _headers.end())            return;        _headers.insert(std::make_pair(key, value));    }     bool MakeResponse()    {        if (_targetfile == "./wwwroot/redir_test")        {            SetCode(302);            SetHeader("Location", "https://www.qq.com/");            return true;        }        bool res = Util::ReadFileContent(_targetfile, &_text); // 浏览器请求的资源,一定会存在吗?出错呢?        if (!res)        {            SetCode(404);            _targetfile = webroot + page_404;            Util::ReadFileContent(_targetfile, &_text);        }         SetCode(200);        return true;    }    ~HttpResponse() {} public:    std::string _version;    int _code;         // 404    std::string _desc; // "Not Found"     std::unordered_map<std::string, std::string> _headers;    std::string _blankline;    std::string _text;     // 其他属性    std::string _targetfile;};在浏览器输入106.54.48.112:8080/redir_test就会自动跳转到qq的首页 上述操作中是浏览器的底层根据Location自己定位网址的。HTTP常见的HeaderHTTP常见的Header如下:Content-Type:数据类型(text/html等)。Content-Length:正文的长度。Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上。User-Agent:声明用户的操作系统和浏览器的版本信息。Referer:当前页面是哪个页面跳转过来的。Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能。————————————————                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。                        原文链接:https://blog.csdn.net/jingfeng514/article/details/151315040
  • [技术干货] HTTPS对比HTTP协议
    HTTPS 的优点HTTPS能够取代HTTP,主要是因为HTTPS在HTTP的基础上增加了安全性,提供了更加安全的数据传输方式。以下是HTTPS相较于HTTP的主要优势:数据安全性:HTTPS使用加密算法(如SSL/TLS)对传输的数据进行加密,保护数据在传输过程中不被窃取和篡改。这一特点使得HTTPS在数据安全性上大大优于HTTP,因为HTTP的数据传输是明文的,容易被黑客截获和篡改。身份认证:HTTPS通过证书验证服务器身份,确保用户与正规服务器建立连接,防止恶意伪造网站。这种身份认证机制有效避免了钓鱼网站和假冒网站的出现,增加了网络使用的安全性。完整性保护:HTTPS使用数字签名来检测数据是否被篡改,保证数据的完整性。即使数据在传输过程中被截获,攻击者也无法篡改数据而不被检测到。更高的搜索引擎排名:搜索引擎更倾向于显示使用HTTPS的网站,因为HTTPS提供更安全和可信的用户体验。这有助于提升网站的可见性和信誉。关于发送同样的网页数据量,HTTPS相较于HTTP会增加一定的数据量,因为HTTPS需要传输额外的加密和验证信息。然而,这种增加通常很小,一般不会对网络性能造成显著影响。具体的增加量取决于使用的加密算法和密钥长度等因素,但通常不会超过原始数据量的几个百分点。HTTPS证书不可信任可能的原因至于有些网址会提示证书不可信任,这通常是由以下几个原因造成的:证书已过期:网站的SSL/TLS证书有一个有效期限,如果证书已过期,浏览器将不再信任该证书。证书颁发机构不受信任:浏览器内置了一份受信任的证书颁发机构列表,如果网站的证书是由一个不在此列表中的机构颁发的,浏览器将不信任该证书。证书链不完整:SSL/TLS证书通常包含一个证书链,用于证明证书的有效性。如果证书链中的某个环节缺失或损坏,浏览器将无法验证证书的有效性。域名不匹配:如果证书中的域名与实际访问的域名不匹配,浏览器将认为证书无效。在解决证书不可信任的问题时,网站管理员应该检查证书的有效期、颁发机构、证书链和域名等信息,确保所有信息都正确无误。同时,用户也可以尝试更新浏览器或操作系统,以获取最新的受信任证书颁发机构列表。
  • 云速建站加SSL证书升级HTTPS访问
    一、Gworg申请SSL证书,国内网站一般用DV单域名。淘宝申请地址:cid:link_1图文教程:https://www.gworg.com/ssl/1384.html提交域名,付款后,很快就会收到解析说明二、登录华为云——云解析(如果域名在其它地方注册的,请进入对应的提供商地方管理域名)三、添加记录集页面快速访问地址(红色区域改成自己的域名就可以进入面板)https://console.huaweicloud.com/dns/?region=cn-north-4#/dns/publicRset?zoneid=8aace3b97c27b313017c91699c3f16ea&zonename=gworg.com.&zoneStatus=ACTIVE解析特别注意有特殊符号,值:"c31a75070bbd54c8a44593b9914c4a27fa0d891a63f4aea799ca07f709d4db8e"大约等待1-15分钟就可以拿到SSL证书,进入邮箱下载证书。四、配置云速建站1、邮件证书解压后选择:Nginx文件夹2、选择:后台管理选3、择绑定域名4、添加SSL证书注意:续费或更新SSL证书,可以点击删除证书或者点击更新证书后配置。5、选择对应的文件,右击打开方式将代码全部复制到对应的框内。1_gworg.com_bundle.crt(证书内容,包括中间证书)2_gworg.com.key(key内容)6、代码复制到对应框内,点击:确定。如果存在其它域名也相同操作。7、最后点击:确定生效时间:华为云云速建站配置完毕后,至少等待30分钟后生效,访问者需要清空浏览器记录访问。
  • [问题求助] 在MQTT协议设备OTA升级实践中设备侧如何通过Https请求下载升级包,我用PC MQTTfx可以下载,但是用MCU一直不行
    PC根据文档可以下载根据文档这里格式不知道有没有错,下面第一个红色框是请求红色是发送的,绿色是接收,不明白400错误那里怎么解决,我用的ESP8266透传,已经接上了https 443接口
  • [技术干货] 短链接的背后故事:为互联网用户带来的便捷与安全【转】
    一、短链接的起源短链接是一种将长URL转换为短、简洁的网址的技术。它的起源可以追溯到互联网发展的早期,当时长URL的使用给用户带来了繁琐和不便。为了解决这个问题,短链接技术应运而生。短链接 | 一个覆盖广泛主题工具的高效在线平台(amd794.com)cid:link_0二、短链接解决了什么问题短链接的主要目的是解决长URL带来的繁琐和不便。长URL不仅难以记忆和分享,而且在某些场景下可能会被截断或破坏。短链接通过将长URL转换为短、简洁的网址,使得用户可以更方便地分享和访问链接。三、短链接对现在的影响和作用链接分享:短链接在社交媒体和移动设备上的广泛应用,使得用户可以更轻松地分享链接。短链接不仅节省了字符空间,还可以提高链接的可读性和美观性。链接跟踪:短链接技术还可以用于链接跟踪和分析。通过在短链接中添加跟踪参数,可以追踪链接的点击量、来源和转化率等数据,为营销和分析提供有价值的信息。链接管理:短链接可以简化链接管理的流程。通过使用短链接服务,用户可以轻松地创建、编辑和管理大量的链接,提高链接管理的效率和便利性。链接安全:短链接技术可以提高链接的安全性。短链接服务通常会提供链接验证和防止恶意链接的功能,保护用户免受恶意网站和钓鱼攻击的威胁。四、短链接的应用领域社交媒体:短链接在社交媒体平台上被广泛应用,方便用户分享和点击链接,同时提供链接跟踪和分析的功能。移动应用:短链接在移动应用中用于分享和打开链接,可以提高用户体验和链接的可用性。营销活动:短链接在营销活动中起到重要作用,可以追踪链接的点击量和转化率,帮助营销人员评估活动效果。链接管理工具:短链接服务可以用于链接管理工具,帮助用户创建、编辑和管理大量的链接。五、总结短链接技术的诞生解决了长URL带来的繁琐和不便,对互联网用户的链接分享和访问带来了便利。短链接在社交媒体、移动应用、营销活动和链接管理等方面发挥着重要作用。同时,短链接技术还提供了链接跟踪和分析的功能,帮助用户评估链接的效果和营销活动的效果。随着互联网的发展,短链接技术仍然在不断创新和发展,为用户提供更便捷、安全和高效的链接管理和分享体验。转载自https://www.cnblogs.com/Amd794/p/18034385
  • [问题求助] 关于报表系统与监控系统接口数据不一致问题
    【问题来源】黑龙江农信社【问题简要】在客户云系统的报表模块里,查看的数据与手册中大屏监控接口返回数据不一致【问题类别】座席监控【AICC解决方案版本】AICC 版本:AICC 8.0.71【问题现象描述】如:通过手册中 获取座席维度的历史监控指标接口:rest/cmsapp/v1/openapi/hisindex/agent查询接通率 与 客户云系统中的接通率有差异​​
  • [问题求助] 关于坐席监控接口鉴权authorization在java代码中请求响应401问题
    【问题来源】黑龙江农信社【问题简要】使用 AICC 22.200.0 产品文档 (OP独立部署) 01.chm 中的java 鉴权示例代码无法进行鉴权,response 响应码 401【问题类别】座席监控【AICC解决方案版本】AICC 版本:AICC 8.0.71【问题现象描述】根据文档中 鉴权的示例代码生成了 authorization 串在执行示例代码的 doPost 方法后,返回401 鉴权失败但将 authorization 粘贴到 postman 执行 就可以返回 200 成功示例代码 原封粘贴出来,并未做逻辑变动java版本是 1.8_381
  • [问题求助] 关于坐席监控接口鉴权authorization在java代码中请求响应401问题
    根据文档中 鉴权的示例代码生成了 authorization 串在执行示例代码的 doPost 方法后,返回401 鉴权失败但复制粘贴到 postman 执行 就可以返回 200 成功java版本是 1.8_381这是什么情况呢???
  • [问题求助] CMS小文件过多df -i /ctibill占用超80%
    【问题来源】       浙江省公安厅【问题类别】       CMS failbak目录小文件过多【AICC解决方案版本】       22.200【问题现象描述】     df -i| grep -v tmpfs | grep -v shm | grep -v udev     发现CMS的 /ctibill/outpush/-1/dcsjob/failbak 如下小文件过多,3天会生成50多万条,df -i占用接近80%,请问这个怎么产生的?怎么关闭?          Prm_xxxx.txt          oplist_xxxx.txt          AgentSkilInfor_xxxx.txt【日志或错误截图】
  • [问题求助] gateway实时质检的插入功能
    【问题来源】 浙江110【问题简要】 实时质检里质检席插入某个工号后,不取消插入就会一直插入所有话务,是否有只插入单通话务的设计,不对下通话务生效。目前就是全程插入的设计么。监听是否也是全程监听。【问题类别】 CTI平台提供的cc-gateway接口【AICC解决方案版本】 AICC 22.200 ,浙江110版本4月9日整体升级过
  • [问题求助] ccgateway的PhoneBusy触发频繁
    【问题来源】 浙江110【问题简要】目前存在未操作话机和gateway平台接口时,话机也未被重复登录情况下,收到PhoneBusy后被自动置忙,部分话机比较频繁,需要降低误报频率【问题类别】 CTI平台提供的cc-gateway接口【AICC解决方案版本】 AICC 22.200 ,浙江110版本4月9日整体升级过
  • [问题求助] 第三发对接CCGW。错误消息:未能为 SSL/TLS 安全通道建立信任关系。
    【问题来源】东莞消防【问题摘要】第三方业务侧开发Restful对接方式,签入CCGW时提示未能为 SSL/TLS 安全通道建立信任关系【问题类别】AICC 8.15.1 组网方案咨询【AICC解决方案版本】AICC 8.15.1【期望解决时间】今天【问题现象描述】
  • [问题求助] 如何动态设置工号对应的话务员姓名
    【问题来源】 浙江110【问题简要】gateway的查询签入技能队列座席详细信息会返回坐席名称name字段,目前返回都是空,如何通过gateway接口或其他方式设置坐席的该字段;目前签入接口没有设置姓名的字段,只有一个疑似serviceworkid的业务工号字段,但设置了不知道哪个接口能查询。这边需要设置后能查询到话务员姓名展示,请帮忙解答下。【问题类别】 CTI平台提供的cc-gateway接口【AICC解决方案版本】 AICC 22.200 ,浙江110版本4月9日整体升级过