Compare commits

..
Author SHA1 Message Date
leefer 2f8a76da3f Merge pull request 'docs: add project handover handbook' (#1) from agent/agent/1f4a1630f24c into main
Reviewed-on: http://192.168.200.36:3200/leefer/tongxunruanjian/pulls/1
2026-08-23 16:55:28 +08:00
编码工程师andmultica-agent 6cd369f548 docs: add project handover handbook
Co-authored-by: multica-agent <github@multica.ai>
2026-08-23 14:28:07 +08:00
编码工程师andmultica-agent fa8584a73f chore(scripts): 修正 fix-livekit-ports.sh 回退指令为从备份恢复
Co-authored-by: multica-agent <github@multica.ai>
2026-08-18 00:32:09 +08:00
编码工程师andmultica-agent 5cc2685f03 fix(livekit): 统一信令/媒体端口为 17880/17881/17882,修复接听后双方网络错误
根因:LiveKit 的 config/livekit.yaml 媒体端口(7881/7882)与宿主实际映射(17881/17882)不一致,
信令走 17880 能通,但媒体被宣告到 7881/7882 连不上。

- config/livekit.yaml: port/tcp_port/udp_port -> 17880/17881/17882
- docker-compose.yaml: 容器侧端口与宿主侧一致(1788x),不再重映射
- .env.example: 默认端口改为 1788x 并注明三处需一致
- scripts/selftest.sh: LK_PORT 默认值改为 17880
- scripts/fix-livekit-ports.sh: 一键备份+对齐 .env+仅重建 livekit 的修复脚本
- README: 更新端口表、客户端接入参数与排障说明

Co-authored-by: multica-agent <github@multica.ai>
2026-08-18 00:31:08 +08:00
13 changed files with 208 additions and 18 deletions
+6 -4
View File
@@ -65,10 +65,12 @@ LOG_LEVEL=3
LIVEKIT_API_KEY=openimLKkey
# secret 必须 ≥32 字符,否则 LiveKit 启动报 secret is too short
LIVEKIT_API_SECRET=lk-secret-9f2c7a41d5e8b063f1a4c795e2d8b3a6
# 三个都是宿主机映射端口;7880/7881/7882 被占用时改成 17880/17881/17882 之类即可
LIVEKIT_PORT=7880
LIVEKIT_RTC_TCP_PORT=7881
LIVEKIT_RTC_UDP_PORT=7882
# 三个端口必须与 config/livekit.yaml 的 port/tcp_port/udp_port 完全一致,
# 且与 docker-compose.yaml 的“宿主侧=容器侧”保持一致(默认 17880/17881/17882)。
# 改端口时三个文件要一起改,否则语音通话会出现“能接听但双方网络错误”。
LIVEKIT_PORT=17880
LIVEKIT_RTC_TCP_PORT=17881
LIVEKIT_RTC_UDP_PORT=17882
# ===== 公司账号服务(工号+密码登录,自研)=====
# 宿主端口;管理页 http://<SERVER_IP>:10010/
+6 -6
View File
@@ -33,9 +33,9 @@
| 10002 | TCP/HTTP | OpenIM REST API |
| 10005 | TCP/HTTP | MinIO(图片/语音/文件下载) |
| 10010 | TCP/HTTP | 公司账号服务(员工登录接口 + 管理页) |
| 7880 | TCP/WS | LiveKit 信令(`.env``LIVEKIT_PORT` 可改) |
| 7882 | UDP | LiveKit 语音媒体(`LIVEKIT_RTC_UDP_PORT` 可改) |
| 7881 | TCP | LiveKit 媒体备用通道(`LIVEKIT_RTC_TCP_PORT` 可改) |
| 17880 | TCP/WS | LiveKit 信令(`.env``LIVEKIT_PORT` 可改,但必须与 `config/livekit.yaml` 一致 |
| 17882 | UDP | LiveKit 语音媒体(`LIVEKIT_RTC_UDP_PORT` 可改,同上 |
| 17881 | TCP | LiveKit 媒体备用通道(`LIVEKIT_RTC_TCP_PORT` 可改,同上 |
| 10004 | TCP/HTTP | MinIO 控制台(运维用,可不开放) |
| 12379/12380 | TCP | Etcd(仅容器间用,建议不对外) |
@@ -79,7 +79,7 @@ docker compose up -d # 再启动
- OpenIM API: `http://<服务器IP>:10002`
- OpenIM WebSocket: `ws://<服务器IP>:10001`
- LiveKit: `ws://<服务器IP>:7880`7880 为默认信令端口,可用 `.env``LIVEKIT_PORT` 改),API Key/Secret 见 `.env``LIVEKIT_API_KEY` / `LIVEKIT_API_SECRET`
- LiveKit: `ws://<服务器IP>:17880`17880 为默认信令端口;改端口时必须 `.env``LIVEKIT_PORT``config/livekit.yaml``port``docker-compose.yaml` 的容器侧端口三者一起改),API Key/Secret 见 `.env``LIVEKIT_API_KEY` / `LIVEKIT_API_SECRET`
- 测试账号:`test001` / `test002`(由自测脚本注册);语音通话时两名用户以各自 identity 进同一房间即可
## 常见问题
@@ -87,6 +87,6 @@ docker compose up -d # 再启动
- **文件/语音能发但打不开**`MINIO_EXTERNAL_ADDRESS` 没写成客户端能访问的 IP。改 `.env``docker compose up -d` 重建 openim-server。
- **启动报缺 `ETCD_USERNAME` / `KAFKA_USERNAME` 等警告**:未启用对应组件认证,官方说明可忽略。
- **openim-server 一直 unhealthy**`docker exec -it openim-server mage check` 看哪项依赖没通;首次启动需等 30-60 秒。
- **livekit 启动报 `bind ... 7880/7881: address already in use`**:服务器上这些端口被别的程序占了(`ss -tlnp | grep 788` 查占用者)。不要动别人的服务,把 `.env` `LIVEKIT_PORT` / `LIVEKIT_RTC_TCP_PORT` / `LIVEKIT_RTC_UDP_PORT` 改成空闲端口(如 17880/17881/17882),再 `docker compose up -d --force-recreate livekit`之后客户端和自测脚本都用新端口(自测脚本会自动读 `.env`
- **livekit 启动报 `bind ... 7880/7881: address already in use`**:服务器上这些端口被别的程序占了(`ss -tlnp | grep 788` 查占用者)。不要动别人的服务,把端口统一改成空闲端口(如 17880/17881/17882)——注意**必须三处一起改**:`.env` `LIVEKIT_PORT` / `LIVEKIT_RTC_TCP_PORT` / `LIVEKIT_RTC_UDP_PORT``config/livekit.yaml``port` / `tcp_port` / `udp_port``docker-compose.yaml` 里 livekit 的容器侧端口(保持“宿主侧=容器侧”),然后 `docker compose up -d --force-recreate livekit`只改其中一两处会导致“能邀请、能接听,但接听后双方网络错误”
- **livekit 日志报 `secret is too short`**`LIVEKIT_API_SECRET` 必须 ≥32 字符,换个长密钥后重建 livekit 容器。
- **语音通话连不上**:确认服务器 UDP 7882 放行(内网防火墙/安全组);UDP 不通时会走 TCP 7881 兜底。
- **语音通话连不上(接听后双方网络错误)**:先确认 `.env` / `config/livekit.yaml` / `docker-compose.yaml` 三处端口一致;再确认服务器防火墙放通 `LIVEKIT_PORT`(TCP)、`LIVEKIT_RTC_TCP_PORT`(TCP)、`LIVEKIT_RTC_UDP_PORT`(UDP)(默认 17880/17881/17882)。UDP 不通时会走 TCP 兜底。
+10 -3
View File
@@ -1,10 +1,17 @@
# LiveKit 服务端配置(内网部署)
# API Key/Secret 通过环境变量 LIVEKIT_KEYS 注入(见 docker-compose.yaml
# 服务器对内 IP 通过启动参数 --node-ip 注入(deploy.sh 写入 .env 的 SERVER_IP
port: 7880
#
# 重要:这里的 port / tcp_port / udp_port 必须与 docker-compose.yaml 里
# 容器侧端口、以及 .env 的 LIVEKIT_PORT / LIVEKIT_RTC_TCP_PORT /
# LIVEKIT_RTC_UDP_PORT 三者完全一致(默认 17880/17881/17882)。
# LiveKit 用 --node-ip + use_external_ip:false 向客户端宣告媒体地址时,
# 只会宣告这里的端口,不会感知 Docker 宿主侧的重映射;三者不一致会导致
# “能邀请、能接听,但接听后双方网络错误”(信令通、媒体不通)。
port: 17880
rtc:
tcp_port: 7881
udp_port: 7882
tcp_port: 17881
udp_port: 17882
# 内网部署:不做公网 IP 探测,candidate 直接使用 --node-ip
use_external_ip: false
room:
+6 -4
View File
@@ -370,7 +370,9 @@ services:
# LiveKit:语音/视频通话的 SFUApache 2.0,自部署。
# 客户端通过 ws://<SERVER_IP>:<LIVEKIT_PORT> 连接,凭 LIVEKIT_API_KEY/SECRET 签发的 token 进房。
# 三个宿主端口都可在 .env 调整(7880/7881/7882 被其他服务占用时改映射即可,容器内端口不变)。
# 三个端口必须与 config/livekit.yaml 里容器监听的端口完全一致(默认 17880/17881/17882),
# 即“宿主侧 = 容器侧”,不要在宿主侧做重映射;LiveKit 向客户端宣告媒体地址时只认
# config/livekit.yaml 里的端口,重映射会导致信令通、媒体不通。
livekit:
image: ${LIVEKIT_IMAGE}
container_name: livekit
@@ -379,9 +381,9 @@ services:
environment:
LIVEKIT_KEYS: "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}"
ports:
- "${LIVEKIT_PORT:-7880}:7880" # HTTP / WebSocket 信令
- "${LIVEKIT_RTC_TCP_PORT:-7881}:7881" # RTC over TCPUDP 不通时的备用通道)
- "${LIVEKIT_RTC_UDP_PORT:-7882}:7882/udp" # RTC 媒体(UDP 复用端口)
- "${LIVEKIT_PORT:-17880}:17880" # HTTP / WebSocket 信令
- "${LIVEKIT_RTC_TCP_PORT:-17881}:17881" # RTC over TCPUDP 不通时的备用通道)
- "${LIVEKIT_RTC_UDP_PORT:-17882}:17882/udp" # RTC 媒体(UDP 复用端口)
volumes:
- ./config/livekit.yaml:/etc/livekit.yaml
networks:
+26
View File
@@ -0,0 +1,26 @@
# 畅联项目手册
这是项目的交接首页。新接手的人先按下面顺序阅读,不要只凭旧文档或聊天记录判断项目状态。
1. 先读 [项目需求](项目需求.md):弄清楚项目要做什么、不能碰什么。
2. 再读 [最新进度](最新进度.md):确认代码已经做到哪里、哪些结论已经被验证。
3. 最后读 [任务清单](任务清单.md):从“正在做”和“还没安排”里选择下一件事。
## 仓库里有什么
- `mobile/`:手机端,使用 Flutter,含 Android 和 iPhone 的工程文件。
- `pc-client/`:电脑端,使用 Electron,Windows 是当前优先支持的平台。
- `account-service/`:公司账号服务,负责工号密码登录、管理员导入和管理名单。
- `docker-compose.yaml``config/``scripts/`:OpenIM、语音服务和内网环境的配置及维护脚本。
- `docs/`:本手册和以前留下的验收、回归、使用说明。
## 更新规矩
以后每完成、新增、取消或发现一个任务,负责这件事的智能体必须在同一次提交中顺手更新本目录:
- 改需求或边界时,更新 `项目需求.md`
- 做完功能、修复问题、完成构建或验证时,更新 `最新进度.md`
- 任务状态变化时,更新 `任务清单.md` 的三栏,并写明依据。
- 旧文档不能删除;如果它和当前事实不一致,在旧文档开头加“已过时,仅留档备查”提示,再在本目录写新版。
手册只记录已经确认的事实。拿不准的内容要写成“待确认”,不要当成已完成。
+2
View File
@@ -1,3 +1,5 @@
> ⚠️ 本文档已过时,仅留档备查,请勿删除。当前请先看 `docs/README.md`、`docs/项目需求.md`、`docs/最新进度.md` 和 `docs/任务清单.md`。
# 畅联使用说明(一页纸)
## 这是什么
+2
View File
@@ -1,3 +1,5 @@
> ⚠️ 本文档已过时,仅留档备查,请勿删除。当前请先看 `docs/README.md`、`docs/项目需求.md`、`docs/最新进度.md` 和 `docs/任务清单.md`。
# 第4步 · 试用前部署与验收清单(B-61)
> 本文件记录"50 人试用"前必须在服务器上手动完成的事项、验收清单与手机正式签名流程。
+2
View File
@@ -1,3 +1,5 @@
> ⚠️ 本文档已过时,仅留档备查,请勿删除。当前请先看 `docs/README.md`、`docs/项目需求.md`、`docs/最新进度.md` 和 `docs/任务清单.md`。
# 阶段 4 跨端回归记录(2026-08-16
> 基线:`main` @ `a007e8f`(含 B-52 三项修复:发消息根因 `d6ef6eb`、同事页/五态 `b50ee24`+`c73e19c`、PC 通讯录入口 `032882d`,以及本轮版本号提升 `7eab7d5`、AUTH_HOST 联调开关 `a007e8f`)。
+26
View File
@@ -0,0 +1,26 @@
# 任务清单
更新时间:2026-08-23。任务状态以当前仓库和项目约束为准;没有证据的事项不写成“已完成”。
## 正在做
目前没有已确认正在施工的业务代码任务。
本次已完成仓库交接手册整理,后续所有任务都应按 `docs/README.md` 的更新规矩同步维护本清单。
## 已做完
- 建立手机端 Flutter 工程和 Windows 优先的 Electron 电脑端工程。
- 接入公司工号加密码登录,以及管理员导入、管理员工账号的账号服务。
- 完成聊天、通讯录、同事申请、语音消息、文件/图片消息和一对一语音通话的相关代码接入。
- 完成一轮手机与电脑的跨端回归记录;其中已通过的项目见旧档 `phase4-regression-2026-08-16.md`
- 将 LiveKit 的信令/媒体端口统一为 `17880``17881``17882`,并提供可重复执行的修复脚本。
- 建立本目录四份交接手册,并为三份过时旧说明加上明确的留档提示。
## 还没安排
- 组织真实手机和 Windows 电脑的回归:重点确认文件传输和一对一语音通话。
- 明确手机正式签名的负责人和安全保管方式;不能把签名密钥提交进仓库。
- 获得负责人同意后,再导入真实员工名单并验证账号管理流程。
- 等负责人对三个公网问题拍板后,才安排公网 IP/域名、备案、反向代理和 HTTPS 的具体施工;当前禁止擅自开始。
- 公网方案获批后,重新评估电脑端 Electron、登录失败限制、令牌本地保存和跨域设置的安全风险。
+31
View File
@@ -0,0 +1,31 @@
# 最新进度
更新时间:2026-08-23。以下内容依据当前仓库代码和最新提交 `fa8584a` 整理。
## 现在做到哪里
项目已经具备可继续测试的手机端、Windows 电脑端、账号服务和内网部署配置:
- 手机端在 `mobile/`,使用 OpenIM Flutter SDK `3.8.3+hotfix.12`,版本 `1.0.6+7`
- 电脑端在 `pc-client/`,使用 Electron,显示版本 `v1.0.2`
- 员工用工号和密码通过 `account-service/` 登录;管理员导入和管理账号的页面、接口已在仓库中。
- 手机和电脑端已接入聊天、通讯录、同事申请、语音消息、文件/图片消息和一对一语音通话相关代码。
- 语音服务 LiveKit 的信令和媒体端口已统一为 `17880``17881``17882`;仓库提供 `scripts/fix-livekit-ports.sh` 用于服务器已部署环境的端口修复。
## 已有验证记录
- `docs/phase4-regression-2026-08-16.md` 记录过手机与电脑之间的文字、同事申请、图片、语音消息等跨端回归结果。
- 该记录中的文件传输和一对一语音通话,曾因模拟器环境无法闭环,仍需要用真实设备再次确认。
- 旧记录使用的是更早的提交、安装包版本和端口状态;因此已保留为档案并在开头标明过时,不能当作当前验收结论。
## 当前停在哪
公网接入施工暂停,等待负责人对三个公网相关问题拍板后再恢复。现阶段没有把内网服务发布到公网的授权。
这意味着:代码可继续维护和内网验证,但公网 IP/域名、备案、反向代理和 HTTPS 等工作不能擅自启动。
## 已知风险和待验证事实
- 真机上的文件传输和一对一语音通话仍需实际设备验证,不能只根据模拟器结果宣称完成。
- 手机正式签名、真实员工名单导入和试用前的管理员安全设置,都需要负责人明确确认并由有权限的人执行。
- 电脑端使用较旧的 Electron,且历史记录提到内网场景的安全取舍;若未来获准公网接入,必须先重新评估 HTTPS、浏览器安全设置、登录限流和跨域策略。
+31
View File
@@ -0,0 +1,31 @@
# 项目需求
## 项目是什么
“畅联”是集团内部使用的通讯 App,也就是给员工聊天、找同事和语音通话的工具。项目以开源 OpenIM 为基础改造,保留 OpenIM 原有标识,不换品牌、不另起一套产品标识。
## 要解决什么
员工需要在公司内部使用同一个账号,在手机和电脑上沟通,不依赖个人社交软件。管理员可以把员工名单一次导入,员工用工号和密码登录。
## 必须具备的功能
1. 聊天:能收发工作消息;当前代码还包含图片、文件和语音消息的相关能力。
2. 通讯录:能查找同事、添加同事和管理联系人。
3. 一对一语音通话:通过 LiveKit(负责实时语音连接的服务)建立通话。
4. 账号登录:员工用“工号 + 密码”登录,不能用个人注册替代。
5. 管理员导入:管理员能导入员工名单并管理账号。
## 支持范围
- 手机端:Android 和 iPhone 工程都在仓库中;当前手机端版本号为 `1.0.6+7`
- 电脑端:Windows 优先;当前电脑端显示版本为 `v1.0.2`
- 服务端:以 Docker Compose(把一组服务一起启动的工具)编排 OpenIM、账号服务和语音服务。
## 当前边界
- 只做集团内部通讯,不改造成公开社交产品。
- 保留 OpenIM 的现有标识,禁止替换品牌。
- 本项目当前只记录和维护内网环境;公网接入尚未获准施工。
- 不把员工名单、密码、管理员口令、密钥或服务器私密配置写入仓库和文档。
- 本仓库的日常任务如无明确授权,不做发布、部署或改动生产环境。
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# 修复语音通话“门号不一致”(信令通、媒体不通)问题。
# 根因:LiveKit 的 config/livekit.yaml 里媒体端口(7881/7882)与宿主实际映射(17881/17882)不一致,
# 导致 LiveKit 向客户端宣告的媒体地址连不上。
# 修复:统一 .env / config/livekit.yaml / docker-compose.yaml 三处端口为 17880/17881/17882
# 只重启 livekit 容器。
#
# 用法(在服务器仓库目录里执行,且已 git pull 到含本修复的版本):
# ./scripts/fix-livekit-ports.sh
#
# 该脚本幂等,可重复执行;会先备份再改动,失败可回退。
set -euo pipefail
cd "$(dirname "$0")/.."
TS="$(date +%Y%m%d-%H%M%S)"
BK="backup-livekit-ports-$TS"
mkdir -p "$BK"
echo "==> 备份当前配置到 $BK/"
for f in .env config/livekit.yaml docker-compose.yaml; do
[ -f "$f" ] && cp -p "$f" "$BK/$(basename "$f")"
done
[ -f "$BK/.env" ] || echo "(未找到 .env,跳过备份)"
# 1. 校验仓库文件已含修复(config/livekit.yaml 端口应为 17880/17881/17882
grep -q '^port: 17880' config/livekit.yaml || { echo "✗ config/livekit.yaml 未包含 port: 17880,请先 git pull 更新仓库后再执行本脚本"; exit 1; }
grep -q 'tcp_port: 17881' config/livekit.yaml || { echo "✗ config/livekit.yaml 未包含 tcp_port: 17881"; exit 1; }
grep -q 'udp_port: 17882' config/livekit.yaml || { echo "✗ config/livekit.yaml 未包含 udp_port: 17882"; exit 1; }
# 2. 对齐 .env 三个端口
if [ ! -f .env ]; then
cp .env.example .env
echo "==> 未找到 .env,已从 .env.example 生成"
fi
sed -i 's|^LIVEKIT_PORT=.*|LIVEKIT_PORT=17880|' .env
sed -i 's|^LIVEKIT_RTC_TCP_PORT=.*|LIVEKIT_RTC_TCP_PORT=17881|' .env
sed -i 's|^LIVEKIT_RTC_UDP_PORT=.*|LIVEKIT_RTC_UDP_PORT=17882|' .env
# 若这三行原本不存在,则追加
grep -q '^LIVEKIT_PORT=' .env || echo 'LIVEKIT_PORT=17880' >> .env
grep -q '^LIVEKIT_RTC_TCP_PORT=' .env || echo 'LIVEKIT_RTC_TCP_PORT=17881' >> .env
grep -q '^LIVEKIT_RTC_UDP_PORT=' .env || echo 'LIVEKIT_RTC_UDP_PORT=17882' >> .env
echo "==> .env 三个端口已设为 17880/17881/17882"
# 3. 只重建 livekit 容器(不动其它服务)
docker compose up -d --force-recreate livekit
# 4. 校验
echo "==> 等待 livekit 就绪..."
sleep 3
docker compose ps livekit
echo
echo "==> 请确认防火墙放通 17880/TCP、17881/TCP、17882/UDP(默认已在本机监听):"
ss -tlnp 2>/dev/null | grep -E ':(17880|17881)' || true
ss -ulnp 2>/dev/null | grep ':17882' || true
echo
echo "==> 完成。回退方法(用刚才的备份恢复旧配置):"
echo " cp $BK/livekit.yaml config/livekit.yaml 2>/dev/null"
echo " cp $BK/docker-compose.yaml docker-compose.yaml 2>/dev/null"
echo " cp $BK/.env .env 2>/dev/null"
echo " docker compose up -d --force-recreate livekit"
+1 -1
View File
@@ -18,7 +18,7 @@ MINIO_SK="$(env_get MINIO_SECRET_ACCESS_KEY)"; MINIO_SK="${MINIO_SK:-openIM123}"
MINIO_PORT="$(env_get MINIO_PORT)"; MINIO_PORT="${MINIO_PORT:-10005}"
LK_KEY="$(env_get LIVEKIT_API_KEY)"
LK_SECRET="$(env_get LIVEKIT_API_SECRET)"
LK_PORT="$(env_get LIVEKIT_PORT)"; LK_PORT="${LK_PORT:-7880}"
LK_PORT="$(env_get LIVEKIT_PORT)"; LK_PORT="${LK_PORT:-17880}"
LK_HTTP="http://${IP}:${LK_PORT}"
LK_WS="ws://${IP}:${LK_PORT}"
LK_CLI_IMAGE="livekit/livekit-cli:v2.18.2"