feat: 默认端口5070、部署文档与外网HTTPS及强制刷新

- 默认监听端口改为 5070(app.py、.env.example、PM2 配置)
- README/部署说明:仓库地址、/opt/LocalNav、root 运行、Nginx+HTTPS 外网接入
- iframe 工具栏在「刷新」旁增加「强制刷新」(跳过缓存,等同 Ctrl+F5)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-05-30 11:24:25 +08:00
parent ea1c4bcf12
commit a8cf3422e4
7 changed files with 206 additions and 44 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
# 仅 python app.py 直连启动时生效 # 仅 python app.py 直连启动时生效
# NAV_HOST=0.0.0.0 # NAV_HOST=0.0.0.0
# NAV_PORT=5000 # NAV_PORT=5070
# 调试:1 开启,勿在生产长期开启 # 调试:1 开启,勿在生产长期开启
# NAV_DEBUG=0 # NAV_DEBUG=0
+6
View File
@@ -2,8 +2,14 @@
Flask + SQLite 的局域网导航聚合:左侧分组与服务列表,右侧 iframe 内嵌打开 `http://内网IP:端口`,统一入口管理宝塔、面板、本地服务等。 Flask + SQLite 的局域网导航聚合:左侧分组与服务列表,右侧 iframe 内嵌打开 `http://内网IP:端口`,统一入口管理宝塔、面板、本地服务等。
**代码仓库:** https://git.bz121.com/dekun/LocalNav.git
**完整中文说明与部署文档:** [部署与使用说明.md](./部署与使用说明.md) **完整中文说明与部署文档:** [部署与使用说明.md](./部署与使用说明.md)
**默认端口:** `5070`(可通过环境变量 `NAV_PORT` 修改)
配置:将 `.env.example` 复制为 `.env` 并填写变量(与 `app.py` 同目录);详见说明文档「环境变量与 `.env`」一节。 配置:将 `.env.example` 复制为 `.env` 并填写变量(与 `app.py` 同目录);详见说明文档「环境变量与 `.env`」一节。
进程守护(可选):`pm2 start ecosystem.config.cjs`(需已安装 PM2),详见 [部署与使用说明.md](./部署与使用说明.md) 中「9.7 使用 PM2」。 进程守护(可选):`pm2 start ecosystem.config.cjs`(需已安装 PM2),详见 [部署与使用说明.md](./部署与使用说明.md) 中「9.7 使用 PM2」。
**Ubuntu 生产部署(推荐):** 克隆到 `/opt/LocalNav`,以 **root** 用户运行 systemd 服务,默认监听 `5070`;外网访问请配置 Nginx/Caddy 反向代理与 HTTPS,详见部署文档「九、部署指南」与「9.8 外网 HTTPS 访问」。
+1 -1
View File
@@ -390,5 +390,5 @@ app = create_app()
if __name__ == "__main__": if __name__ == "__main__":
host = os.environ.get("NAV_HOST", "0.0.0.0") host = os.environ.get("NAV_HOST", "0.0.0.0")
port = int(os.environ.get("NAV_PORT", "5000")) port = int(os.environ.get("NAV_PORT", "5070"))
app.run(host=host, port=port, debug=os.environ.get("NAV_DEBUG") == "1") app.run(host=host, port=port, debug=os.environ.get("NAV_DEBUG") == "1")
+1 -1
View File
@@ -47,7 +47,7 @@ module.exports = {
// 如需在 PM2 层覆盖环境变量,可取消注释并修改(一般使用根目录 .env 即可) // 如需在 PM2 层覆盖环境变量,可取消注释并修改(一般使用根目录 .env 即可)
// env: { // env: {
// NAV_HOST: '0.0.0.0', // NAV_HOST: '0.0.0.0',
// NAV_PORT: '5000', // NAV_PORT: '5070',
// NAV_DEBUG: '0', // NAV_DEBUG: '0',
// }, // },
}, },
+7
View File
@@ -357,6 +357,13 @@ a:hover {
white-space: nowrap; white-space: nowrap;
} }
.frame-toolbar-actions {
display: flex;
align-items: center;
gap: 0.45rem;
flex-shrink: 0;
}
.btn-toolbar-refresh { .btn-toolbar-refresh {
flex-shrink: 0; flex-shrink: 0;
padding: 0.38rem 0.85rem; padding: 0.38rem 0.85rem;
+48 -7
View File
@@ -98,9 +98,19 @@
</button> </button>
<span class="frame-title" id="current-service-name"></span> <span class="frame-title" id="current-service-name"></span>
</div> </div>
<button type="button" class="btn btn-secondary btn-toolbar-refresh" id="frame-refresh"> <div class="frame-toolbar-actions">
刷新 <button type="button" class="btn btn-secondary btn-toolbar-refresh" id="frame-refresh">
</button> 刷新
</button>
<button
type="button"
class="btn btn-secondary btn-toolbar-refresh"
id="frame-force-refresh"
title="强制刷新(等同 Ctrl+F5,跳过缓存)"
>
强制刷新
</button>
</div>
</div> </div>
<div class="frame-wrap"> <div class="frame-wrap">
<iframe id="svc-frame" title="内嵌服务" hidden></iframe> <iframe id="svc-frame" title="内嵌服务" hidden></iframe>
@@ -149,6 +159,7 @@
var links = document.querySelectorAll(".nav-link[data-url]"); var links = document.querySelectorAll(".nav-link[data-url]");
var cards = document.querySelectorAll(".service-card[data-url]"); var cards = document.querySelectorAll(".service-card[data-url]");
var btnRefresh = document.getElementById("frame-refresh"); var btnRefresh = document.getElementById("frame-refresh");
var btnForceRefresh = document.getElementById("frame-force-refresh");
var btnBack = document.getElementById("frame-back-overview"); var btnBack = document.getElementById("frame-back-overview");
var currentBaseUrl = ""; var currentBaseUrl = "";
@@ -179,9 +190,7 @@
setActive(nav); setActive(nav);
} }
function reloadUrl() { function buildCacheBustUrl(u, hard) {
var u = currentBaseUrl;
if (!u) return;
var sep = u.indexOf("?") >= 0 ? "&" : "?"; var sep = u.indexOf("?") >= 0 ? "&" : "?";
var hash = ""; var hash = "";
var base = u; var base = u;
@@ -190,7 +199,35 @@
base = u.slice(0, hashPos); base = u.slice(0, hashPos);
hash = u.slice(hashPos); hash = u.slice(hashPos);
} }
frame.src = base + sep + "_navts=" + Date.now() + hash; var ts = Date.now();
if (hard) {
return (
base +
sep +
"_navts=" +
ts +
"&_navnocache=" +
Math.random().toString(36).slice(2) +
hash
);
}
return base + sep + "_navts=" + ts + hash;
}
function reloadUrl() {
var u = currentBaseUrl;
if (!u) return;
frame.src = buildCacheBustUrl(u, false);
}
function forceReloadUrl() {
var u = currentBaseUrl;
if (!u) return;
frame.src = "about:blank";
frame.onload = function () {
frame.onload = null;
frame.src = buildCacheBustUrl(u, true);
};
} }
function showDashboard() { function showDashboard() {
@@ -223,6 +260,10 @@
reloadUrl(); reloadUrl();
}); });
btnForceRefresh.addEventListener("click", function () {
forceReloadUrl();
});
btnBack.addEventListener("click", function () { btnBack.addEventListener("click", function () {
showDashboard(); showDashboard();
}); });
+142 -34
View File
@@ -1,6 +1,8 @@
# 本地导航站 · 部署与使用说明 # 本地导航站 · 部署与使用说明
本文档为 **中文说明****部署操作** 合一版本,适用于在局域网Ubuntu / Windows 等)上自建使用,不涉及公网穿透、云服务器 Nginx 或 frp 本文档为 **中文说明****部署操作** 合一版本,适用于在局域网**外网 HTTPS** 场景下自建使用
**代码仓库:** https://git.bz121.com/dekun/LocalNav.git
--- ---
@@ -87,7 +89,7 @@ cp .env.example .env
| `NAV_SECRET_KEY` | Flask 会话、CSRF 等加密签名密钥。**生产或长期运行务必设置固定值**,否则重启后会话失效且安全性下降。 | 未设置时每次进程启动随机生成 | | `NAV_SECRET_KEY` | Flask 会话、CSRF 等加密签名密钥。**生产或长期运行务必设置固定值**,否则重启后会话失效且安全性下降。 | 未设置时每次进程启动随机生成 |
| `NAV_DATABASE_URL` | SQLAlchemy 数据库连接串 | `sqlite:///nav_local.db` | | `NAV_DATABASE_URL` | SQLAlchemy 数据库连接串 | `sqlite:///nav_local.db` |
| `NAV_HOST` | `python app.py` 时监听地址 | `0.0.0.0` | | `NAV_HOST` | `python app.py` 时监听地址 | `0.0.0.0` |
| `NAV_PORT` | `python app.py` 时监听端口 | `5000` | | `NAV_PORT` | `python app.py` 时监听端口 | `5070` |
| `NAV_DEBUG` | 是否开启调试模式(**勿**在内网多人共用服务器上长期开启) | 未设置或不为 `1` 则为关闭;设为 `1` 开启 | | `NAV_DEBUG` | 是否开启调试模式(**勿**在内网多人共用服务器上长期开启) | 未设置或不为 `1` 则为关闭;设为 `1` 开启 |
**生成密钥示例(Linux / macOS):** **生成密钥示例(Linux / macOS):**
@@ -108,7 +110,14 @@ $env:NAV_SECRET_KEY = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count
### 6.1 获取代码 ### 6.1 获取代码
将项目目录放到目标机器上(拷贝、压缩包解压、或 Git 克隆均可)。下文以项目根目录为当前工作目录。 **Git 克隆(推荐):**
```bash
git clone https://git.bz121.com/dekun/LocalNav.git
cd LocalNav
```
也可将项目目录放到目标机器上(拷贝、压缩包解压等)。下文以项目根目录为当前工作目录。
### 6.2 创建虚拟环境并安装依赖 ### 6.2 创建虚拟环境并安装依赖
@@ -153,13 +162,13 @@ export NAV_SECRET_KEY="$(openssl rand -hex 32)" # Linux;长期部署也可
python app.py python app.py
``` ```
默认监听 **`http://0.0.0.0:5000`**。在同一局域网内的其他设备浏览器访问: 默认监听 **`http://0.0.0.0:5070`**。在同一局域网内的其他设备浏览器访问:
```text ```text
http://<本机局域网IP>:5000 http://<本机局域网IP>:5070
``` ```
例如:`http://192.168.1.100:5000` 例如:`http://192.168.1.100:5070`
**注意**:内置的 `app.run()` 为 Flask 开发用服务器,**并发能力与健壮性有限**,适合个人使用或低并发内网场景。需要长期开机、略高并发时,建议使用下文 **Gunicorn** 方式。 **注意**:内置的 `app.run()` 为 Flask 开发用服务器,**并发能力与健壮性有限**,适合个人使用或低并发内网场景。需要长期开机、略高并发时,建议使用下文 **Gunicorn** 方式。
@@ -179,7 +188,7 @@ http://<本机局域网IP>:5000
- 使用 [DB Browser for SQLite](https://sqlitebrowser.org/) 等工具打开 `nav_local.db`,删除 `users` 表中对应用户后,临时改代码跑一次初始化(不推荐反复操作); - 使用 [DB Browser for SQLite](https://sqlitebrowser.org/) 等工具打开 `nav_local.db`,删除 `users` 表中对应用户后,临时改代码跑一次初始化(不推荐反复操作);
- 或自行增加「修改密码」路由(二次开发)。 - 或自行增加「修改密码」路由(二次开发)。
- **不要将**带默认口令的数据库文件提交到公开 Git 仓库。 - **不要将**带默认口令的数据库文件提交到公开 Git 仓库。
- 本应用设计为 **内网**,请勿直接暴露到公网。 - 本应用设计为 **内网聚合入口**;若需外网访问,请按 **9.8** 配置 HTTPS 与反向代理,勿将 `5070` 端口裸奔到公网。
--- ---
@@ -193,6 +202,7 @@ http://<本机局域网IP>:5000
- **左侧**:按分组展示服务名称;点击后在 **右侧 iframe** 打开对应地址。 - **左侧**:按分组展示服务名称;点击后在 **右侧 iframe** 打开对应地址。
- **顶部**:可进入「分组管理」「服务管理」或退出登录。 - **顶部**:可进入「分组管理」「服务管理」或退出登录。
- **内嵌页工具栏**:「刷新」为普通刷新(追加时间戳参数);「强制刷新」等同 **Ctrl+F5**,先清空 iframe 再带随机参数重新加载,尽量跳过浏览器缓存(跨域 iframe 时效果取决于目标站点策略)。
### 8.3 分组管理(`/admin/groups` ### 8.3 分组管理(`/admin/groups`
@@ -218,31 +228,40 @@ http://<本机局域网IP>:5000
## 九、部署指南(以 Ubuntu 为例) ## 九、部署指南(以 Ubuntu 为例)
以下假设:系统为 **Ubuntu 20.04/22.04/24.04** 等,项目路径为 `/opt/nav-site`,监听端口 **5000**;可根据实际路径与端口修改。 以下假设:系统为 **Ubuntu 20.04/22.04/24.04** 等,项目路径为 **`/opt/LocalNav`**,监听端口 **5070**,进程以 **root** 用户运行;可根据实际域名与端口修改。
### 9.1 系统准备 ### 9.1 系统准备
```bash ```bash
sudo apt update sudo apt update
sudo apt install -y python3 python3-venv python3-pip sudo apt install -y python3 python3-venv python3-pip git
``` ```
(若已安装 Python 3venv,可跳过。) (若已安装 Python 3venv 与 git,可跳过。)
### 9.2 放置项目并安装依赖 ### 9.2 克隆项目并安装依赖
```bash ```bash
sudo mkdir -p /opt/nav-site sudo mkdir -p /opt
# 将项目文件同步到 /opt/nav-site,保证 app.py、requirements.txt 等在根目录 cd /opt
sudo git clone https://git.bz121.com/dekun/LocalNav.git
cd /opt/nav-site cd /opt/LocalNav
sudo python3 -m venv .venv python3 -m venv .venv
sudo chown -R $USER:$USER /opt/nav-site
source .venv/bin/activate source .venv/bin/activate
pip install -U pip pip install -U pip
pip install -r requirements.txt -i https://pypi.org/simple pip install -r requirements.txt -i https://pypi.org/simple
``` ```
后续更新代码:
```bash
cd /opt/LocalNav
git pull
source .venv/bin/activate
pip install -r requirements.txt -i https://pypi.org/simple
sudo systemctl restart nav-site
```
### 9.3 配置密钥(必做) ### 9.3 配置密钥(必做)
```bash ```bash
@@ -263,7 +282,7 @@ sudo chmod 600 /etc/nav-site/secret_key
sudo nano /etc/systemd/system/nav-site.service sudo nano /etc/systemd/system/nav-site.service
``` ```
示例内容: 示例内容**root 运行**
```ini ```ini
[Unit] [Unit]
@@ -272,11 +291,11 @@ After=network.target
[Service] [Service]
Type=simple Type=simple
User=你的Linux用户名 User=root
Group=你的Linux用户组 Group=root
WorkingDirectory=/opt/nav-site WorkingDirectory=/opt/LocalNav
EnvironmentFile=-/etc/nav-site/env EnvironmentFile=-/etc/nav-site/env
ExecStart=/opt/nav-site/.venv/bin/python /opt/nav-site/app.py ExecStart=/opt/LocalNav/.venv/bin/python /opt/LocalNav/app.py
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec=5
@@ -295,7 +314,7 @@ sudo nano /etc/nav-site/env
```ini ```ini
NAV_SECRET_KEY=粘贴openssl_rand_hex32的输出 NAV_SECRET_KEY=粘贴openssl_rand_hex32的输出
NAV_HOST=0.0.0.0 NAV_HOST=0.0.0.0
NAV_PORT=5000 NAV_PORT=5070
``` ```
启用并启动: 启用并启动:
@@ -316,14 +335,14 @@ journalctl -u nav-site -f
### 9.5 防火墙(若启用了 ufw ### 9.5 防火墙(若启用了 ufw
```bash ```bash
sudo ufw allow 5000/tcp sudo ufw allow 5070/tcp
sudo ufw reload sudo ufw reload
``` ```
仅内网使用时,也可限制来源网段(示例,请按实际修改): 仅内网使用时,也可限制来源网段(示例,请按实际修改):
```bash ```bash
sudo ufw allow from 192.168.0.0/16 to any port 5000 proto tcp sudo ufw allow from 192.168.0.0/16 to any port 5070 proto tcp
``` ```
### 9.6 可选:使用 Gunicorn 提高稳定性 ### 9.6 可选:使用 Gunicorn 提高稳定性
@@ -331,14 +350,14 @@ sudo ufw allow from 192.168.0.0/16 to any port 5000 proto tcp
安装: 安装:
```bash ```bash
source /opt/nav-site/.venv/bin/activate source /opt/LocalNav/.venv/bin/activate
pip install gunicorn pip install gunicorn
``` ```
`WorkingDirectory` 仍为项目根目录,**保证 `nav_local.db` 路径与工作目录一致**。示例 `ExecStart` `WorkingDirectory` 仍为项目根目录,**保证 `nav_local.db` 路径与工作目录一致**。示例 `ExecStart`
```ini ```ini
ExecStart=/opt/nav-site/.venv/bin/gunicorn -w 2 -b 0.0.0.0:5000 app:app ExecStart=/opt/LocalNav/.venv/bin/gunicorn -w 2 -b 0.0.0.0:5070 app:app
``` ```
说明:`-w 2` 为 worker 数量,可按机器 CPU 调整;`app:app` 表示 `app.py` 中的全局变量 `app` 说明:`-w 2` 为 worker 数量,可按机器 CPU 调整;`app:app` 表示 `app.py` 中的全局变量 `app`
@@ -356,17 +375,17 @@ sudo npm install -g pm2
**2. 准备虚拟环境与依赖(项目根目录)** **2. 准备虚拟环境与依赖(项目根目录)**
```bash ```bash
cd /opt/nav-site # 或你的项目路径 cd /opt/LocalNav # 项目路径
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate source .venv/bin/activate
pip install -r requirements.txt -i https://pypi.org/simple pip install -r requirements.txt -i https://pypi.org/simple
cp -n .env.example .env && nano .env # 至少配置 NAV_SECRET_KEY cp -n .env.example .env && nano .env # 至少配置 NAV_SECRET_KEY、NAV_PORT=5070
``` ```
**3. 启动 / 查看 / 维护** **3. 启动 / 查看 / 维护**
```bash ```bash
cd /opt/nav-site cd /opt/LocalNav
pm2 start ecosystem.config.cjs pm2 start ecosystem.config.cjs
pm2 status pm2 status
pm2 logs nav-site pm2 logs nav-site
@@ -393,6 +412,93 @@ pm2 startup
**说明**:若 `interpreter` 指向的 `.venv/bin/python` 不存在,PM2 会启动失败;请确认虚拟环境路径与 `ecosystem.config.cjs` 中一致。Windows 下脚本会自动使用 `.venv\Scripts\python.exe` **说明**:若 `interpreter` 指向的 `.venv/bin/python` 不存在,PM2 会启动失败;请确认虚拟环境路径与 `ecosystem.config.cjs` 中一致。Windows 下脚本会自动使用 `.venv\Scripts\python.exe`
### 9.8 外网 HTTPS 访问(Nginx 反向代理)
若需从 **公网或外网** 通过 **HTTPS** 访问本导航站(浏览器地址栏为 `https://`),建议在 Ubuntu 上用 **Nginx** 终止 TLS,反代到本机 `127.0.0.1:5070`。Flask 仍监听内网端口,不直接暴露 5070 到公网。
**1. 安装 Nginx 与 CertbotLet's Encrypt 免费证书,需已解析到本机的域名)**
```bash
sudo apt install -y nginx certbot python3-certbot-nginx
```
**2. 调整应用环境变量**
编辑 `/etc/nav-site/env`(或项目根目录 `.env`),增加:
```ini
NAV_TRUST_PROXY=1
NAV_SESSION_COOKIE_SECURE=1
NAV_CSRF_TRUSTED_ORIGINS=https://你的域名
```
保存后重启:`sudo systemctl restart nav-site`
**3. Nginx 站点配置**
`nav.example.com` 替换为你的域名:
```bash
sudo nano /etc/nginx/sites-available/localnav
```
```nginx
server {
listen 80;
server_name nav.example.com;
location / {
proxy_pass http://127.0.0.1:5070;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_read_timeout 300s;
}
}
```
启用并重载:
```bash
sudo ln -sf /etc/nginx/sites-available/localnav /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
**4. 申请 HTTPS 证书**
```bash
sudo certbot --nginx -d nav.example.com
```
按提示完成验证后,Certbot 会自动配置 `listen 443 ssl` 与证书路径。证书续期一般已由系统定时任务处理,可手动验证:
```bash
sudo certbot renew --dry-run
```
**5. 防火墙**
```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# 若仅通过 Nginx 对外,可不放行 5070 到公网
sudo ufw reload
```
**6. 访问**
外网浏览器打开:`https://nav.example.com`
**说明:**
- 内嵌 iframe 中的目标服务仍为你在「服务管理」里填写的 `http://内网IP:端口`;外网用户能否打开取决于目标服务是否可从其网络访问,与本导航站 HTTPS 无关。
- 若无公网域名,仅有公网 IP,可改用 **自签证书****Caddy**`caddy reverse-proxy --from :443 --to 127.0.0.1:5070`),但浏览器会提示证书不受信任,需自行导入信任。
- 公网暴露登录入口存在安全风险,请务必使用强密码,并考虑 IP 白名单、VPN 或 Nginx `allow/deny` 等额外访问控制。
--- ---
## 十、数据与备份 ## 十、数据与备份
@@ -429,14 +535,14 @@ pm2 startup
**Q:为什么右侧 iframe 是白的?** **Q:为什么右侧 iframe 是白的?**
常见原因:目标站禁止被嵌入;目标服务宕机或地址/端口填错;浏览器混合内容策略(本应用为 HTTP 打开链接,若目标强制 HTTPS 且策略严格,可能异常)。可在新标签直接打开同一 URL 对比排查。 常见原因:目标站禁止被嵌入;目标服务宕机或地址/端口填错;浏览器混合内容策略(本应用为 HTTP 打开链接,若目标强制 HTTPS 且策略严格,可能异常)。可在新标签直接打开同一 URL 对比排查。
**Q:端口想改成 8080** **Q:端口想改成其他值**
设置环境变量 `NAV_PORT=8080` 后重启进程;防火墙放行对应端口 设置环境变量 `NAV_PORT=8080`(示例)后重启进程;防火墙与 Nginx `proxy_pass` 端口需一并修改。默认端口为 **5070**
**Q:忘记密码怎么办?** **Q:忘记密码怎么办?**
若有服务器文件权限,可用 SQLite 工具修改 `users` 表,或删除用户行后通过代码逻辑重新种子用户(需具备运维或开发能力)。 若有服务器文件权限,可用 SQLite 工具修改 `users` 表,或删除用户行后通过代码逻辑重新种子用户(需具备运维或开发能力)。
**Q:能否放到公网** **Q:能否从外网访问**
本应用为内网场景设计,未内置 HTTPS、限流、审计等生产级能力。**不建议**直接暴露公网;若必须上公网,请自行叠加反向代理、TLS、访问控制与监控 可以。推荐在 Ubuntu 上用 **Nginx + HTTPS** 反代到 `127.0.0.1:5070`,并配置 `NAV_TRUST_PROXY=1` 等变量,详见 **9.8 外网 HTTPS 访问**。请务必使用强密码并做好访问控制。
--- ---
@@ -448,3 +554,5 @@ pm2 startup
--- ---
**文档结束。** 若你后续增加「HTTPS 链接」「新窗口打开」「修改密码」等功能,建议在本文档对应章节补充说明并保持与代码一致。 **文档结束。** 若你后续增加「HTTPS 链接」「新窗口打开」「修改密码」等功能,建议在本文档对应章节补充说明并保持与代码一致。
**仓库地址:** https://git.bz121.com/dekun/LocalNav.git