一底座双门户多应用:企业微信私有化移动办公平台架构规划
2026/9/17 22:14:25 网站建设 项目流程

简介:这是一份面向企业信息化负责人、CIO与CTO等决策者的移动办公平台建设规划文档,针对集团多系统分散、门户形象模糊、移动端提醒不稳定等痛点,提出以「一个底座、双门户、多元办公应用」为核心的综合办公平台方案。资源为单个PDF文件,约5.57MB,便于直接查阅与内部传阅。全文围绕需求及现状分析、目标与范围、建设方案、实施保障、项目预算与成功案例六个章节展开,具体涵盖企业微信私有化底座选型、致远OA与企微双门户融合、消息与待办提醒统一、微服务架构下的开发平台与打包服务,以及OA、财务、HR、建工等子系统的集成路径,并给出预算测算、售后巡检与培训等长期运维保障设计。目前已有54人学习。管理人员可借此快速理解建设思路与价值增量,技术人员则可参考技术选型逻辑与落地实施路径。

1. 一个综合办公底座加双门户,这套移动办公平台该怎么拆

海发集团这份《移动办公平台方案规划》里最容易被读偏的一句是"打造一底座、双门户、多应用"。综合办公底座并不是企业微信本身,而是私有化部署之后叠加的开发平台、打包服务、配置与发现、镜像仓库、容器管理和运维监控;双门户指一级门户(企业微信工作台,面向集团与二级单位全员)和二级门户(致远门户,继承原有网页端成果);多应用则是订单、商旅、会议、文件、即时通信、知识这些已存在或即将接入的业务系统。它解决的不是"再做一个App",而是把11套既有系统、竹云统一账号、致远OA与致信APP的现状,收成一个消息可达、找人可达、应用可达的入口。集团信息化负责人该盯底座归属与管控边界,架构师该盯多单位隔离与集成方式,实施和运维则要先把网络分区、资源评估、消息通道这三件事落稳。

2. 企业微信私有化底座的网络分区与多单位架构规划

私有化版本和直接开个企业账号的差别,全在底座这一层:数据落在自己机房,一套集群能开出多个逻辑隔离的单位,接口开放范围更宽,同时部署、扩容、故障定位都由自己承担。方案里给底座定了五个关键词——环境依赖少、适配复杂网络、易部署易运维、满足跨网安全、面向多租户多单位。这五条落到图纸上,就是网络怎么分区、单位怎么切、资源怎么估、部署按什么顺序验收。

2.1 接入区、逻辑区、存储区的三段式落位

方案的拓扑把环境拆成 Internet 客户端、接入区、逻辑区、存储区四段,实际上就是一道从外到内的收敛过程。接入区放反向代理和负载均衡,只对外暴露 443,负责 TLS 卸载、限流和灰度切流;逻辑区跑消息、日程、会议、文档这些业务服务,互相之间走内网域名;存储区放数据库、缓存和对象存储,只接受逻辑区的连接。原则只有一条:客户端永远打不到逻辑区和存储区,任何"图省事直接放开端口"的临时方案都会变成长期负债。

区域组件暴露端口说明
接入区反向代理/负载均衡443/80仅此区域对客户端可达,双节点起步
逻辑区应用与消息服务8080/8443内网域名互调,不出公网路由
逻辑区文档与会议服务集群内端口与对象存储直连
存储区MySQL/Redis3306/6379主从或集群,仅逻辑区可访问
存储区对象存储9000微盘、微文档附件落地

提示:逻辑区与存储区建议放在不同的内网 VLAN,存储区节点不配置默认路由,避免被当成跳板。

2.2 一套集群多单位的逻辑隔离怎么切

方案里最关键的设计是"一套部署集群、创建逻辑隔离的多个单位,并互联互通"。单位对应一个机构,各单位独立管理自己的组织架构、用户账号、应用和安全策略;集团层管的是单位之间的组织关系、互访权限、应用共享和对外互联设置。这个划法决定后面所有权限模型,切错了很难回头。

切分原则是按管理主体建单位,不按业务系统建单位。一个二级单位下面的多个业态,用部门树和标签解决,不要各自开一个单位,否则通讯录会碎成十几份。

单位编码名称层级数据归属与总部互访
HQ集团总部一级独立全部可见
SUB-A二级单位A二级独立双向通讯、应用只读分发
SUB-B二级单位B二级独立双向通讯、应用只读分发
SUB-B1三级单位B1三级归属SUB-B由SUB-B授权

单位间的应用分发是单向的:总部把应用推到二级单位工作台,二级单位不能反向推给总部;跨单位找人靠互联互通设置,不需要加好友也能直接发起会话。

2.3 资源评估:从在线并发和历史消息倒推规格

资源估算最容易犯的错是按人头算。真正吃资源的是三块:消息与待办的下发频次、微盘与微文档的附件体积和版本数、历史消息的保留策略。人头只影响通讯录表大小,几乎不占资源。部署前先在每台目标节点跑一遍环境自检,比事后返工便宜得多。

#!/bin/bash # 企微私有化底座部署前环境自检,在每台目标节点执行 set -e echo "== CPU 核心数 =="; nproc echo "== 内存(GB) =="; awk '/MemTotal/{printf "%.1f\n",$2/1024/1024}' /proc/meminfo echo "== 数据盘挂载 =="; df -hT | awk 'NR==1 || $7=="/data"' echo "== 时钟同步 =="; timedatectl | grep -E "NTP|synchronized" echo "== 关键内核参数 =="; sysctl net.ipv4.ip_forward net.core.somaxconn vm.max_map_count echo "== 端口占用 =="; for p in 443 3306 6379 9000; do ss -lntp | grep ":$p " || echo "port $p free"; done

nproc和内存决定能否单机起步、后续怎么扩;检查/data独立挂载是为了让日志和附件写满时不影响系统盘;时钟不同步会导致消息时序错乱和令牌校验反复失败;somaxconn影响高并发长连接,max_map_count影响检索类组件;端口检查是防止和新平台的代理、已有 OA 中间件撞车。

节点角色起步配置部署要点
接入代理4C8G × 2双活,会话保持按源地址
应用逻辑8C16G × 2消息、日程、文档同机起步
数据库8C32G + SSD主从,组织架构与消息索引分离库
对象存储4C8G + 大容量盘微盘、微文档附件
运维监控4C8G × 1指标与统一日志

注意:这是集团加两个二级单位量级下常见的起步组合,不是标准答案。历史消息保留期从一年改到三年,存储区容量往往是翻倍而不是加几成。

2.4 部署顺序与阶段验收点

底座施工按六步走,每一步都要有可验证的产出,不要合并验收。

  1. 环境预检与网络打通:完成 2.3 的自检脚本、域名解析、证书签发,产出环境检查报告。
  2. 底座安装与集群规划:按角色装节点,验证存储区和逻辑区连通,产出集群拓扑图。
  3. 多单位创建与互通配置:按 2.2 的表格建单位,配置互访权限,用两个不同单位的账号互发消息验证。
  4. 通讯录初始化:先从竹云/HR 导入组织结构,再做增量同步,验证部门层级和人员数量对得上。
  5. 应用接入与门户配置:一级门户工作台建应用分组,二级门户挂载致远入口。
  6. 端侧发布与联调:生成 PC 端和移动端安装包并发布,做一轮消息可达、找人可达、应用可达的端到端测试。

阶段验收里最容易走过场的是第三步,因为单机测试时单位隔离看起来"反正也没人跨单位发消息"。等二期扩到三级单位再补,权限模型基本要推倒重来。

3. 双门户接入:通讯录同步、免登与待办消息的落地方案

门户可以有两级,身份只能有一套。竹云负责账号统一,企业微信负责通讯录和即时通讯能力,中间靠同步任务和映射表把两边缝起来。这一层做不干净,后面所有"消息不达""待办点了白屏"的问题都会显得毫无规律。

3.1 从竹云账号体系到企微通讯录的同步

同步要做成幂等的可重跑任务,而不是一次性导入脚本。部门 ID 和 userid 的选型直接决定后续维护成本。

import requests CORP_ID = "ww_hfxxxx" # 私有化环境的 corpid,与部署配置保持一致 ADDR_SECRET = "通讯录同步secret" # 通讯录管理专用 secret,不是自建应用 secret BASE = "https://qyapi.weixin.qq.com/cgi-bin" def get_token(secret): r = requests.get(f"{BASE}/gettoken", params={"corpid": CORP_ID, "corpsecret": secret}, timeout=5) data = r.json() if data.get("errcode") != 0: raise RuntimeError(f"gettoken failed: {data}") return data["access_token"] def upsert_department(token, dept): # dept: {"id":1001,"name":"二级单位A","parentid":1,"order":10} return requests.post(f"{BASE}/department/create", params={"access_token": token}, json=dept, timeout=5).json() def upsert_user(token, user): # user: {"userid":"zhangsan","name":"张三","mobile":"138...","department":[1001]} # user/update 在用户不存在时会创建,可重复执行 return requests.post(f"{BASE}/user/update", params={"access_token": token}, json=user, timeout=5).json() if __name__ == "__main__": tk = get_token(ADDR_SECRET) print(upsert_department(tk, {"id": 1001, "name": "二级单位A", "parentid": 1, "order": 10})) print(upsert_user(tk, {"userid": "zhangsan", "name": "张三", "mobile": "13800000000", "department": [1001]}))

几个参数值得单独说:ADDR_SECRET必须是通讯录管理 secret,用成应用 secret 会在写接口上报权限错误,而这个错误文案不会提示你拿错了密钥;部门 ID 建议直接沿用竹云或 HR 里的组织编码,别用自增,否则每次重导都会长出一批僵尸部门;userid固定用手机号或工号其中一种,别混用,消息推送、单点登录、待办归属全靠它定位人;人员离职走user/delete逻辑删除,物理删除后历史消息里的发送人会变成空值。

3.2 双门户打通:一级门户工作台与致远门户的单点登录

方案给了两种接入方式:走致远微协同或集团单点登录系统,或者各系统按互联清单逐个对接。前者成本低、覆盖全,适合已建成体系的系统;后者适合独立三方系统和二级单位自建系统,按需对接。

门户载体主要职责维护方
一级门户企微工作台应用分发、消息触达、通讯录集团信息化
二级门户致远网页端审批、表单、公文流转各单位

免登链路是"客户端拿 code → 后端换 userid → 映射到业务系统账号 → 换票据重定向",每一步都要能单独验证,不要等链路全通再测。

# 1) 用 code 换用户身份,code 由前端 JS-SDK 免登或 OAuth 授权拿到 curl -s "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=$TOKEN&code=$CODE" # 2) 拿企业票据,用于前端 JS-SDK 校验 curl -s "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=$TOKEN&type=agent_config" # 3) 把 userid 映射成致远登录名后换票据(示意,参数以实际系统为准) curl -s -X POST "http://oa.hf.local/seeyon/rest/token" \ -H "Content-Type: application/json" \ -d '{"loginName":"zhangsan","password":"<由集成服务托管的凭据>"}'

第一步的 code 一次性且有效期很短,缓存复用必然失败;userid 到致远登录名的映射表要落库维护,放在网关里硬编码的后果是每次组织调整都要发版;回调域名必须在应用可信域名里登记,漏一个就会出现"电脑上能免登、手机上跳登录页"这种看起来像端适配问题的故障。

3.3 待办与消息提醒:三条推送通道怎么选

移动端提醒不稳定是一期被点名的问题,根因通常不是通道不行,而是通道选错或者推送对象算错。

通道终端覆盖是否依赖应用权限适用场景
企业号消息推送PC + 移动需自建应用审批待办、系统通知
企微应用消息推送PC + 移动需应用可见范围定向到人的业务提醒
群机器人 webhookPC + 移动不需要运维告警、群内播报

待办类消息用 textcard 最合适,可点击、有单号、能带跳转地址。

import requests def send_task_card(token, agent_id, to_user, title, url, task_id): payload = { "touser": to_user, # userid,多个用 | 分隔,@all 慎用 "msgtype": "textcard", "agentid": agent_id, # 必须与自建应用一致 "textcard": { "title": title, "description": f"<div class=\"gray\">单号 {task_id}</div>", "url": url, # 必须是应用可信域名下的地址 "btntxt": "去处理" }, "enable_id_trans": 0, # 1 表示 touser 传的是转换后的自定义 ID "safe": 0 # 1 表示消息仅客户端内查看,不可转发 } res = requests.post("https://qyapi.weixin.qq.com/cgi-bin/message/send", params={"access_token": token}, json=payload, timeout=5).json() if res.get("errcode") != 0: raise RuntimeError(f"send failed: {res}") return res["msgid"] # 入库,用于回答"我确实没收到"这类问题

touser取不到人,八成是通讯录同步没覆盖到那个部门节点;agentid串了会直接撞上可见范围错误;url域名不对,用户点开就是白屏,而接口返回值一切正常;msgid一定要入库,它是排查投递争议的唯一凭据。

3.4 应用分发与可见范围管理

一级门户把应用分发到二级单位工作台时,可见范围按部门节点维护,不按人维护。按人维护在集团这种人员流动频率下,一个月就会积累出上百条失效白名单。对独立三方系统单独设置可见范围,避免二级单位看到不属于自己的业务系统。

4. 微服务架构下综合办公底座的服务拆分与集成

新平台基于微服务架构,但规划里同时写了"鉴于现有系统升级到新架构需要时间,新平台将兼容传统的数据集成方式"。这句话是整个技术方案里最务实的一句,它承认了现实:OA、HR、财务、建工这些系统短期内不会改造成微服务,底座必须能同时容纳两种集成方式。

4.1 门户、流程、知识、消息四类服务群怎么切

按业务能力切服务群,不按现有系统切。按系统切的结果是每个系统一套服务,底座变成一层壳子,治理价值归零。

服务群包含服务典型接口
门户服务群门户引擎、UI 服务、场景服务、标签服务工作台菜单、应用排序
流程服务群表单服务、规则引擎、流程服务待办查询、审批提交
知识服务群知识服务、问答服务、图谱服务、维基服务文档检索、微盘
消息服务群待办服务、集成服务、消息组件、互联组件消息下发、投递回执

每个服务群自治数据库,跨群调用走集成服务封装的接口,不允许服务之间直连库表。这条规矩在项目前期显得多余,等两个服务群的数据模型各自演化半年,直连库的那条 SQL 就会成为没人敢改的化石。

4.2 注册中心、配置中心与插件工厂

服务注册与配置分离,配置要能热更新,否则调一个超时参数就要重启一轮。

# 门户服务群运行配置(字段名以实际平台为准) service: name: portal-service port: 8080 registry: type: nacos addr: nacos.hf.local:8848 namespace: hf-office-prod # 按环境隔离,测试环境别注册进生产命名空间 group: portal config: >import requests OA_TODO_API = "http://oa.hf.local/seeyon/rest/todo/list" SEND_API = "https://qyapi.weixin.qq.com/cgi-bin/message/send" def pull_oa_todo(login_name, secret, last_id=0): """增量拉取待办,last_id 用于断点续拉,避免全量扫描""" r = requests.get(OA_TODO_API, params={"loginName": login_name, "token": secret, "lastId": last_id}, timeout=8) r.raise_for_status() return [{"task_id": it["id"], "title": it["subject"], "url": it["mobileUrl"], "created_at": it["createDate"]} for it in r.json().get("data", [])] def push_once(userid, agent_id, token, todo_list): for t in todo_list: payload = {"touser": userid, "msgtype": "textcard", "agentid": agent_id, "textcard": {"title": t["title"], "description": t["created_at"], "url": t["url"], "btntxt": "去处理"}} res = requests.post(SEND_API, params={"access_token": token}, json=payload, timeout=5).json() if res.get("errcode") != 0: # 失败落库,按退避策略重投,避免同一待办重复推送 print("push fail", t["task_id"], res)

三个细节:用lastId或时间水位做增量,别轮询全量,那会直接撞上接口频率限制;推送失败落失败表并按退避重试,重试键用task_id保证幂等;url必须换成移动端可达的地址,把 OA 内网地址直接塞进去是这类集成最高频的坑,测试时用 PC 浏览器看是通的,手机上必然打不开。

4.4 容器化、镜像仓库与运维监控

服务容器化解决的是发布和回滚速度,前提是健康检查探针配对了。

apiVersion: apps/v1 kind: Deployment metadata: name: portal-service spec: replicas: 2 # 至少两副本,滚动升级不掉线 selector: matchLabels: {app: portal-service} template: metadata: labels: {app: portal-service} spec: containers: - name: portal image: registry.hf.local/hf/portal-service:1.4.2 ports: [{containerPort: 8080}] readinessProbe: # 未就绪不接流量,防止启动期打挂 httpGet: {path: /actuator/health/readiness, port: 8080} initialDelaySeconds: 20 periodSeconds: 10 livenessProbe: # 与就绪探针分开,避免启动慢被反复重启 httpGet: {path: /actuator/health/liveness, port: 8080} initialDelaySeconds: 60 periodSeconds: 20 resources: requests: {cpu: "500m", memory: "1Gi"} limits: {cpu: "2", memory: "2Gi"}

镜像全部走内网 registry,tag 用不可变版本号,禁止latest;统一日志平台至少要能按trace-id把网关、服务群、集成服务三段日志串起来;运维监控平台要把消息下发成功率、令牌刷新失败率、集成服务积压量这三类指标单独建看板,它们比 CPU 使用率更早暴露问题。

5. 联调阶段的故障定位与上线前验证技巧

联调阶段最耗时间的不是功能不通,而是"用户说没收到消息"这种没有现场的问题。把定位拆成三段,速度会快很多。

5.1 消息不达的三段定位法

链路是:业务系统产生待办 → 集成服务转换 → 企微消息接口接收 → 客户端展示。四段里,接口层看 errcode,服务端看 msgid 和失败表,端侧看客户端登录状态、免打扰设置和应用可见范围。先分层,再往下钻。

errcode含义先查什么
40001凭证无效或过期secret 是否写错、是否被其他服务刷新覆盖
42001access_token 过期缓存是否做了失效重取
40013corpid 不合法私有化环境 corpid 与部署配置是否一致
60020应用无权访问该成员应用可见范围是否包含该部门节点
45009接口调用超限是否有全量轮询在刷接口

前两类基本是凭证管理问题,多实例部署时最常见的是每个副本各自刷新 token,互相把对方的 token 刷失效,解决办法是统一走一个令牌服务并做单飞刷新。第三类只在私有化环境出现,corpid 对不上时所有接口一起报错,反而好定位。

5.2 端侧适配与白名单验证

方案里提到企业微信已与各手机厂商做了适配白名单处理,这部分要在上线前逐项验证,而不是等收到"部分安卓机型收不到推送"的反馈再查。验证方法是选三类机型各一台:接入厂商推送通道的机型、原生安卓机型、iOS 机型,分别做后台杀进程、免打扰开启、锁屏三种状态下的消息到达测试,把结果记进联调报告。

PC 端和移动端安装包生成后,先在内网小范围发布,确认通讯录可见、工作台应用可见、消息可点开跳转,再全量。一级门户和二级门户要各测一遍同一份待办的跳转路径,两个门户的域名和登录态处理不同,一份通不代表另一份通。

5.3 上线前的巡检与两条关键曲线

上线前跑一遍巡检脚本,把瞬时状态固化成可对比的记录。

#!/bin/bash # 上线前巡检:令牌、消息、集成积压三条链路各取一个健康信号 TOKEN=$(curl -s "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=$CORP_ID&corpsecret=$SECRET" \ | python3 -c "import sys,json;print(json.load(sys.stdin).get('access_token',''))") [ -n "$TOKEN" ] && echo "token ok" || echo "token FAIL" curl -s -o /dev/null -w "portal health: %{http_code}\n" http://portal.hf.local/actuator/health/readiness curl -s -o /dev/null -w "oa adapter health: %{http_code}\n" http://msg.hf.local/adapter/health echo "== 集成服务失败表近 24h 条数 ==" mysql -h db.hf.local -u reader -p"$DB_PASS" -N -e \ "SELECT COUNT(*) FROM msg_push_fail WHERE created_at > NOW() - INTERVAL 1 DAY;" echo "== 待重投积压 ==" mysql -h db.hf.local -u reader -p"$DB_PASS" -N -e \ "SELECT COUNT(*) FROM msg_push_fail WHERE retry_count < 3;"

这套脚本的价值不在于当下是否通过,而在于每天跑一次、结果入库,形成基线。真正要长期盯的只有两条曲线:令牌刷新失败率和消息投递回执缺失率。前者抬头说明凭证管理或配置中心出了问题,后者抬头说明通讯录同步或可见范围有缺口。把这两条曲线接进运维监控平台,按周看趋势,比事后翻日志高效得多。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询