MFC仿QQ聊天系统:CAsyncSocket通信与多会话管理实战
简介这是一套基于C与MFC框架开发的仿QQ即时通讯系统完整源码工程面向C初学者及课程设计实践者聚焦Socket网络编程、多线程并发控制、客户端间消息互通与文件传输等核心能力训练。资源包共114个文件涵盖16个头文件h定义类接口与协议结构14个实现文件cpp承载通信逻辑与UI交互以及bmp/ico等11类图像资源和exe可执行文件完整支撑编译、调试与运行全流程压缩包大小28.67MB结构规范适合作为学期项目或毕业设计参考范例。已有1040人学习下载提供可直接构建的VS工程含sln、vcproj、rc等包含客户端界面资源、对话状态管理、文件传输缓冲机制及线程同步关键代码辅以APS资源预编译文件与PDB调试符号便于深入理解MFC消息循环与Socket异步通信机制。1. 这不是“做个QQ界面”而是用MFC在Windows原生层重建通信逻辑链很多人解压“基于C的MFC的仿QQ聊天系统.zip”后第一反应是UI控件堆得挺像双击运行却报错“找不到mfc120u.dll”或直接闪退——这恰恰暴露了对MFC本质的误读。它不是图形界面速成工具而是一套深度绑定Windows API、依赖特定VC运行时、需手动管理消息循环与资源生命周期的框架。一个能真正收发消息、维持会话状态、支持多窗口切换的“仿QQ”背后必须完成三件事Win32 Socket异步通信模型的稳定封装、CDialog/CListCtrl等控件的非阻塞消息驱动、以及GDI绘图与资源管理的精细控制。本项目面向的是已掌握C基础语法、熟悉Windows编程范式如消息映射、句柄操作、正从控制台向GUI进阶的开发者。如果你还在用cout Hello MFC测试环境建议先完成VS2013/2015中MFC静态链接配置和AfxMessageBox调用验证若已能独立实现带菜单栏的文档视图程序则可直接切入本系统的通信模块重构。2. 用CAsyncSocket构建可靠的消息收发通道绕过阻塞陷阱的3种关键处理MFC中网络通信的选型不是“用不用CAsyncSocket”而是“为什么必须用它”。CSocket虽封装更厚但默认同步阻塞模式在GUI线程中极易导致界面冻结而纯Win32WSAAsyncSelect又需手动维护窗口消息ID与回调映射易出错。CAsyncSocket通过重载OnConnect/OnReceive等虚函数将网络事件无缝接入MFC消息泵是平衡开发效率与运行稳定性的最优解。2.1 初始化Socket并绑定到主对话框窗口// 在CChatDlg类头文件中声明成员变量 private: CAsyncSocket m_clientSocket; CString m_strServerIP; UINT m_nServerPort; // 在OnInitDialog()中初始化 BOOL CChatDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 设置服务器地址实际项目应从配置文件读取 m_strServerIP _T(127.0.0.1); m_nServerPort 8080; // 创建Socket并连接 if (!m_clientSocket.Create(0, SOCK_STREAM, FD_CONNECT | FD_READ | FD_CLOSE)) { AfxMessageBox(_T(Socket创建失败)); return FALSE; } // 异步连接不阻塞UI线程 if (!m_clientSocket.Connect(m_strServerIP, m_nServerPort)) { int nError WSAGetLastError(); if (nError ! WSAEWOULDBLOCK) // 非“正在连接中”错误才报 { AfxMessageBox(_T(连接失败请检查服务器是否运行)); return FALSE; } } return TRUE; }提示Create()第二个参数必须为SOCK_STREAMTCP第三个参数是事件掩码FD_CONNECT表示连接完成通知FD_READ表示有数据可读FD_CLOSE表示对方关闭连接。漏掉任一标志都将导致对应事件无法触发。2.2 重载OnReceive实现无粘包消息解析TCP本身无消息边界原始recv()可能一次返回多个包或半个包。本系统采用“长度头内容体”协议前4字节为DWORD类型消息长度后续字节为UTF-8编码文本。void CChatDlg::OnReceive(int nErrorCode) { char szBuffer[4096] {0}; int nBytes m_clientSocket.Receive(szBuffer, sizeof(szBuffer)-1); if (nBytes 0) { // 将接收缓冲区追加到临时缓存 m_strRecvBuffer CString(szBuffer, nBytes); // 循环解析完整消息 while (m_strRecvBuffer.GetLength() 4) { DWORD dwMsgLen *(DWORD*)(LPCTSTR)m_strRecvBuffer; // 读取前4字节长度 if (m_strRecvBuffer.GetLength() 4 dwMsgLen) { // 提取完整消息体跳过长度头 CString strMsg m_strRecvBuffer.Mid(4, dwMsgLen); // UI线程安全更新PostMessage到主窗口 ::PostMessage(m_hWnd, WM_USER_RECV_MSG, (WPARAM)strMsg.AllocSysString(), 0); // 截断已处理部分 m_strRecvBuffer m_strRecvBuffer.Right(m_strRecvBuffer.GetLength() - 4 - dwMsgLen); } else { break; // 当前缓冲区不足一个完整包等待下次OnReceive } } } else if (nBytes 0) { AfxMessageBox(_T(服务器已断开连接)); m_clientSocket.Close(); } CDialogEx::OnReceive(nErrorCode); }注意CString::Mid()和Right()操作在Unicode环境下按字符计数而网络字节流是字节序列。此处dwMsgLen为UTF-8字节数strMsg构造时需确保szBuffer以\0结尾且CString能正确识别多字节字符——实际项目中应改用CT2A转换或直接操作std::vectorchar避免编码歧义。2.3 处理WM_USER_RECV_MSG自定义消息更新UI// 在CChatDlg.h中声明消息处理函数 afx_msg LRESULT OnUserRecvMsg(WPARAM wParam, LPARAM lParam); // 在CChatDlg.cpp中添加消息映射 BEGIN_MESSAGE_MAP(CChatDlg, CDialogEx) // ... 其他映射 ON_MESSAGE(WM_USER_RECV_MSG, CChatDlg::OnUserRecvMsg) END_MESSAGE_MAP() LRESULT CChatDlg::OnUserRecvMsg(WPARAM wParam, LPARAM lParam) { // wParam是BSTR指针需转换为CString BSTR bstr (BSTR)wParam; CString strMsg(bstr); SysFreeString(bstr); // 必须释放否则内存泄漏 // 安全更新列表控件假设IDC_LIST_CHAT为CListCtrl CListCtrl* pList (CListCtrl*)GetDlgItem(IDC_LIST_CHAT); if (pList) { int nItem pList-InsertItem(pList-GetItemCount(), _T(对方), 0); pList-SetItemText(nItem, 1, strMsg); pList-EnsureVisible(nItem, FALSE); } return 0; }参数名类型说明wParamWPARAMBSTR指针指向UTF-16编码的接收消息字符串lParamLPARAM未使用保留为0nItemintInsertItem返回的行索引用于后续SetItemText定位3. MFC对话框中实现多用户会话管理CListCtrl与CMap的协同设计仿QQ的核心体验在于“多窗口聊天”即每个联系人独占一个对话框实例且能独立发送/接收消息。MFC中不能简单用CDialog::DoModal()——模态对话框会阻塞主线程导致其他窗口无法响应。必须采用非模态对话框全局会话映射表的组合方案。3.1 定义会话数据结构与全局映射表// SessionData.h #pragma once #include afxwin.h struct SessionData { CString strUserID; // 联系人唯一标识如zhangsan163.com CChatSessionDlg* pDlg; // 对应的非模态对话框指针 BOOL bIsOnline; // 在线状态由心跳包维护 SessionData() : pDlg(nullptr), bIsOnline(FALSE) {} }; // 在CChatApp类中声明全局映射替代C11的std::map兼容旧版MFC CMapStringToPtr g_mapSessions;3.2 创建非模态对话框并注入会话数据// CChatSessionDlg.h class CChatSessionDlg : public CDialogEx { DECLARE_DYNAMIC(CChatSessionDlg) public: CChatSessionDlg(const CString strContactID, CWnd* pParent nullptr); virtual ~CChatSessionDlg(); protected: CString m_strContactID; // 当前会话对象ID CAsyncSocket m_socket; // 独立Socket或复用主连接此处为简化示例 virtual BOOL OnInitDialog() override; afx_msg void OnBnClickedBtnSend(); DECLARE_MESSAGE_MAP() }; // CChatSessionDlg.cpp CChatSessionDlg::CChatSessionDlg(const CString strContactID, CWnd* pParent) : CDialogEx(IDD_DIALOG_SESSION, pParent), m_strContactID(strContactID) { } BOOL CChatSessionDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 设置窗口标题为联系人昵称 SetWindowText(_T(与 ) m_strContactID _T( 的对话)); // 从全局映射获取主Socket或新建专用Socket // 实际项目中建议复用主连接通过消息头区分会话ID return TRUE; } void CChatSessionDlg::OnBnClickedBtnSend() { // 获取输入框内容假设IDC_EDIT_INPUT CString strInput; GetDlgItemText(IDC_EDIT_INPUT, strInput); if (strInput.IsEmpty()) return; // 构造带会话ID的消息包协议扩展 DWORD dwLen (DWORD)strInput.GetLength() * sizeof(TCHAR); // Unicode长度 std::vectorchar packet(4 dwLen 1); *(DWORD*)packet.data() dwLen; memcpy(packet.data() 4, (LPCSTR)CT2CA(strInput), dwLen); packet[4 dwLen] \0; // 结尾符 // 发送此处简化实际需检查m_socket是否有效 m_socket.Send(packet[0], (int)packet.size()); // 清空输入框并添加到本地聊天记录 ((CEdit*)GetDlgItem(IDC_EDIT_INPUT))-SetWindowText(_T()); CListCtrl* pList (CListCtrl*)GetDlgItem(IDC_LIST_CHAT); if (pList) { int nItem pList-InsertItem(pList-GetItemCount(), _T(我), 0); pList-SetItemText(nItem, 1, strInput); pList-EnsureVisible(nItem, FALSE); } }3.3 主窗口中动态创建与销毁会话窗口// 在CChatDlg中响应联系人双击事件假设列表控件IDC_LIST_CONTACTS void CChatDlg::OnNMDblclkListContacts(NMHDR *pNMHDR, LRESULT *pResult) { LPNMITEMACTIVATE pNMItemActivate reinterpret_castLPNMITEMACTIVATE(pNMHDR); CListCtrl* pList (CListCtrl*)GetDlgItem(IDC_LIST_CONTACTS); int nSel pList-GetSelectionMark(); if (nSel -1) return; CString strUserID pList-GetItemText(nSel, 0); // 第一列存用户ID // 检查是否已存在该会话窗口 CChatSessionDlg* pSession nullptr; if (g_mapSessions.Lookup(strUserID, (void*)pSession) pSession) { pSession-ShowWindow(SW_RESTORE); pSession-SetForegroundWindow(); *pResult 0; return; } // 创建新会话窗口 pSession new CChatSessionDlg(strUserID, this); if (pSession-Create(IDD_DIALOG_SESSION, this)) { pSession-ShowWindow(SW_SHOW); g_mapSessions.SetAt(strUserID, pSession); // 注册到全局映射 } else { AfxMessageBox(_T(创建会话窗口失败)); delete pSession; } *pResult 0; } // 在CChatDlg析构或退出时清理所有会话 CChatDlg::~CChatDlg() { POSITION pos g_mapSessions.GetStartPosition(); while (pos) { CString strKey; CChatSessionDlg* pDlg; g_mapSessions.GetNextAssoc(pos, strKey, (void*)pDlg); if (pDlg ::IsWindow(pDlg-m_hWnd)) { pDlg-DestroyWindow(); // 销毁窗口 delete pDlg; // 释放内存 } } g_mapSessions.RemoveAll(); }关键点CMapStringToPtr的SetAt()和Lookup()操作是线程安全的但DestroyWindow()必须在UI线程调用。若会话窗口在后台被用户关闭需在CChatSessionDlg::OnCancel()中主动从g_mapSessions移除自身指针避免悬空引用。4. 解决VS2013编译MFC项目必现的3类运行时错误DLL缺失、资源加载失败、GDI对象泄漏解压后的.zip项目常因开发环境差异导致运行失败。以下问题在VS2013 SP5 Windows 10 1809及以上环境中高频出现需逐项验证。4.1 “mfc120u.dll not found”错误的4种修复路径错误现象根本原因解决方案验证命令启动时报“缺少mfc120u.dll”项目配置为动态链接MFC目标机器未安装VC2013 Redistributable下载安装 vcredist_x64.exe 64位或 vcredist_x86.exe 32位dumpbin /dependents YourApp.exe | findstr mfc报“msvcr120.dll丢失”C运行时库未部署同上Redistributable包已包含dir %windir%\System32\msvcr120.dll64位或%windir%\SysWOW64\msvcr120.dll32位调试时提示“无法加载mfc120ud.dll”调试版本需Debug Redistributable安装 Microsoft Visual C 2013 Debug Runtimedepends.exe YourApp.exe查看依赖树打包后仍报错应用程序清单文件.manifest未正确嵌入在项目属性 → 配置属性 → 常规 → “使用MFC”设为“在共享DLL中使用MFC”并勾选“启用应用程序清单”mt.exe -inputresource:YourApp.exe;#2 -out:manifest.txt4.2 对话框资源加载失败的调试方法当Create()返回FALSE但无明确错误码时大概率是资源ID冲突或控件ID未定义// 在OnInitDialog()开头添加诊断代码 TRACE(_T(Loading dialog resource ID: %d\n), IDD_DIALOG_MAIN); HINSTANCE hInst AfxGetInstanceHandle(); HRSRC hRsrc FindResource(hInst, MAKEINTRESOURCE(IDD_DIALOG_MAIN), RT_DIALOG); if (!hRsrc) { TRACE(_T(Failed to find dialog resource!\n)); AfxMessageBox(_T(资源未找到请检查.rc文件是否包含IDD_DIALOG_MAIN)); return FALSE; } HGLOBAL hGlobal LoadResource(hInst, hRsrc); if (!hGlobal) { TRACE(_T(Failed to load dialog resource!\n)); return FALSE; }注意MFC资源ID必须在resource.h中明确定义且.rc文件需包含#include resource.h。常见错误是复制控件时VS未自动分配新ID导致多个控件共用同一ID如IDC_EDIT1引发GetDlgItem()返回NULL。4.3 GDI对象泄漏导致界面绘制异常MFC中频繁使用CDC::GetSafeHdc()获取设备上下文但未释放会导致GDI句柄耗尽任务管理器中“GDI对象”数持续上涨// ❌ 错误写法获取HDC后未释放 void CChatDlg::OnPaint() { CPaintDC dc(this); HDC hDC dc.GetSafeHdc(); // 获取HDC TextOut(hDC, 10, 10, _T(Hello), 5); // 直接使用 // 忘记ReleaseDC(hDC) —— 泄漏 } // ✅ 正确写法优先使用CDC封装避免裸HDC void CChatDlg::OnPaint() { CPaintDC dc(this); CRect rect; GetClientRect(rect); // 使用CDC成员函数内部自动管理HDC dc.FillSolidRect(rect, RGB(240,240,240)); dc.TextOut(10, 10, _T(Hello)); // 如需GDI对象如字体必须成对创建/删除 CFont font; font.CreateFont(16, 0, 0, 0, FW_NORMAL, FALSE, FALSE, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, _T(微软雅黑)); CFont* pOldFont dc.SelectObject(font); dc.TextOut(10, 40, _T(使用自定义字体)); dc.SelectObject(pOldFont); // 恢复原字体 // font析构时自动DeleteObject() }GDI对象类型创建方式释放方式注意事项CFontCreateFont()对象析构自动释放推荐使用CFont而非HFONTCPenCreatePen()析构自动释放避免new CPen后忘记deleteCBrushCreateSolidBrush()析构自动释放GetStockObject()返回的刷子不可DeleteHBITMAPCreateBitmap()DeleteObject()MFC中优先用CBitmap封装5. 用CImage实现联系人头像自适应缩放解决高DPI屏幕下的模糊与拉伸现代Windows系统普遍启用125%/150%缩放传统CStatic控件加载位图会出现像素化或尺寸错乱。CImage类支持PNG透明通道与高质量双线性插值缩放是MFC中处理头像的首选方案。5.1 加载PNG头像并缩放到指定矩形区域// 在CChatDlg中添加头像显示函数 void CChatDlg::DisplayAvatar(CStatic* pStatic, const CString strImagePath, int nWidth, int nHeight) { if (!pStatic || strImagePath.IsEmpty()) return; CImage image; HRESULT hr image.Load(strImagePath); if (FAILED(hr)) { // 加载失败时显示默认头像 pStatic-SetBitmap(::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP_AVATAR))); return; } // 获取静态控件客户区 CRect rect; pStatic-GetClientRect(rect); // 计算缩放比例保持宽高比 double dScaleX (double)nWidth / image.GetWidth(); double dScaleY (double)nHeight / image.GetHeight(); double dScale min(dScaleX, dScaleY); int nNewWidth (int)(image.GetWidth() * dScale); int nNewHeight (int)(image.GetHeight() * dScale); // 创建缩放后图像 CImage imageScaled; imageScaled.Create(nNewWidth, nNewHeight, image.GetBPP(), image.GetAlphaPaletteFlag()); // 双线性插值缩放比StretchBlt质量高 image.StretchBlt(imageScaled.GetDC(), 0, 0, nNewWidth, nNewHeight, SRCCOPY, HALFTONE); imageScaled.ReleaseDC(); // 转换为HBITMAP并设置到CStatic HBITMAP hBmp imageScaled.Detach(); pStatic-SetBitmap(hBmp); // 释放原始CImageDetach后不再管理hBmp image.Destroy(); }5.2 在高DPI模式下动态调整头像尺寸// 在OnInitDialog()中调用前获取DPI缩放因子 void CChatDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 获取系统DPI CDC* pDC GetDC(); int nDPI pDC-GetDeviceCaps(LOGPIXELSX); ReleaseDC(pDC); // 计算缩放系数基准DPI为96 double dScaleFactor (double)nDPI / 96.0; // 获取头像控件 CStatic* pAvatar (CStatic*)GetDlgItem(IDC_STATIC_AVATAR); if (pAvatar) { // 原始设计尺寸为64x64按DPI缩放 int nWidth (int)(64 * dScaleFactor); int nHeight (int)(64 * dScaleFactor); DisplayAvatar(pAvatar, _T(avatar.png), nWidth, nHeight); } return TRUE; }关键参数说明CImage::StretchBlt()的HALFTONE模式启用双线性插值比默认SRCCOPY更平滑CImage::Detach()返回HBITMAP并放弃内存管理权由CStatic::SetBitmap()负责后续释放LOGPIXELSX返回水平DPI值Windows 10默认为96100%、120125%、144150%。5.3 PNG透明背景的Alpha混合渲染可选增强若头像PNG含Alpha通道需启用SetLayeredWindowAttributes实现真透明// 在CChatSessionDlg::OnInitDialog()中添加 ModifyStyle(0, WS_EX_LAYERED); SetLayeredWindowAttributes(0, 0, LWA_COLORKEY); // 以黑色为透明色 // 或使用Alpha通道SetLayeredWindowAttributes(0, 255, LWA_ALPHA);但需注意WS_EX_LAYERED会禁用部分GDI绘制建议仅对头像控件单独启用而非整个对话框。本文还有配套的精品资源点击获取

相关新闻

如何把一份硬件基础资料读成一节课:从选资料到动手验证的完整方法

如何把一份硬件基础资料读成一节课:从选资料到动手验证的完整方法

1. 为什么一份好资料能顶一节硬件课刚入行那几年,我总觉得硬件这东西必须得有人手把手教,看资料自学根本摸不着门道。后来踩了不少坑才慢慢意识到,真正拉开差距的往往不是谁教了你什么,而是你手里有没有一份能把基础讲透的好资料。…

2026/9/20 9:56:46 阅读更多 →
C语言指针彻底搞懂:内存地址、*p、p、**p核心原理与实战避坑

C语言指针彻底搞懂:内存地址、*p、p、**p核心原理与实战避坑

C语言这玩意儿,劝退率最高的概念绝对是“指针”,没有之一。尤其是*p、p、&p这三个符号来回切换,再加上一个**p,很多初学者直接原地爆炸。我这些年带过不少人,也看了无数帖子,发现大家卡住的根本原因不是…

2026/9/20 9:56:46 阅读更多 →
PhotoSwipe 图库自定义 Caption(图片说明)实现指南:基于 ui.registerElement 的完整方案

PhotoSwipe 图库自定义 Caption(图片说明)实现指南:基于 ui.registerElement 的完整方案

前端UI库/组件 【免费下载链接】PhotoSwipe JavaScript image gallery for mobile and desktop, modular, framework independent 项目地址: https://gitcode.com/gh_mirrors/ph/PhotoSwipe 点击查看 免费下载 PhotoSwipe 默认不内置 caption(图片说明文…

2026/9/20 9:55:45 阅读更多 →

最新新闻

sccache 一篇搞定:用编译器缓存把编译等待砍掉一半

sccache 一篇搞定:用编译器缓存把编译等待砍掉一半

sccache 一篇搞定:用编译器缓存把编译等待砍掉一半 【免费下载链接】sccache Sccache is a ccache-like tool. It is used as a compiler wrapper and avoids compilation when possible. Sccache has the capability to utilize caching in remote storage environ…

2026/9/20 10:47:33 阅读更多 →
VS Code + Git 入门指南:从安装到日常提交与分支操作

VS Code + Git 入门指南:从安装到日常提交与分支操作

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 10:47:33 阅读更多 →
Bili.UWP 安装与上手全解:Windows 11 上原生 B站UWP客户端快速评测

Bili.UWP 安装与上手全解:Windows 11 上原生 B站UWP客户端快速评测

Bili.UWP 安装与上手全解:Windows 11 上原生 B站UWP客户端快速评测 【免费下载链接】Bili.Uwp 适用于新系统UI的哔哩 项目地址: https://gitcode.com/GitHub_Trending/bi/Bili.Uwp 如果你受够了浏览器标签页里那个越来越臃肿的网页版 B 站,Bili.U…

2026/9/20 10:47:33 阅读更多 →
浏览器10分钟跑通Manim:极坐标与三维XYZ动画实战

浏览器10分钟跑通Manim:极坐标与三维XYZ动画实战

先给结论:想 10 分钟在浏览器里跑通第一个 Manim 动画,这件事现在真的不难,你不需要先装 Python、不用折腾 Conda、也不用跟 FFmpeg 和 LaTeX 较劲。只要电脑上有一个不算太老的 Chrome、Edge 或 Firefox,打开一个网页&#xff0c…

2026/9/20 10:47:33 阅读更多 →
Ubuntu输入法配置全指南:从Fcitx5到Rime定制

Ubuntu输入法配置全指南:从Fcitx5到Rime定制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/20 10:47:33 阅读更多 →
短剧出海本地化怎么做?AI配音翻译工具实测与选型指南

短剧出海本地化怎么做?AI配音翻译工具实测与选型指南

1. 短剧出海本地化的核心挑战与方案选型1.1 本地化不是“翻译字幕”这么简单做短剧出海的朋友应该都有体会:片子剪完,真正的战争才刚刚开始。国内观众能接受的台词、笑点、情绪节奏,放到海外市场很可能完全“接不住地气”。本地化&#xff08…

2026/9/20 10:46:32 阅读更多 →

日新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/20 0:00:46 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/20 0:00:46 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/20 0:00:46 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/20 0:00:46 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/20 0:00:46 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/19 23:01:36 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/19 17:50:38 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →