优化国内构建的时间

This commit is contained in:
侯欢 2025-12-07 17:16:29 +08:00
parent 9bfefd635c
commit e0fb6381d2
8 changed files with 158 additions and 7 deletions

10
.dockerignore Normal file
View File

@ -0,0 +1,10 @@
data/
.git/
.gitignore
*.log
__pycache__/
.vscode/
.DS_Store
node_modules/
*.tmp
*.swp

View File

@ -0,0 +1,42 @@
## 问题原因
- 国内访问官方 `pypi.org`、Docker Hub 和部分 CDN 较慢,导致镜像构建阶段的依赖下载成为瓶颈。
## 加速策略
### 1) Python 依赖镜像
- 在镜像内配置 `pip` 国内源:腾讯云镜像或清华/阿里云镜像。
- 建议:`PIP_INDEX_URL=https://mirrors.cloud.tencent.com/pypi/simple`,并写入 `/etc/pip.conf`,保证所有 `pip install` 都走国内源。
### 2) Docker 镜像拉取加速
- 在服务器上配置 Docker daemon 的国内加速镜像:
- `daemon.json` 增加:`"registry-mirrors": ["https://mirror.ccs.tencentyun.com","https://hub-mirror.c.163.com","https://docker.mirrors.ustc.edu.cn"]`,重启 Docker。
### 3) 预构建并推送镜像
- 在本地/构建机上构建好镜像并推送到腾讯云 TCR容器镜像服务服务器仅需拉取镜像跳过远端构建阶段的依赖下载。
### 4) 构建层缓存优化
- 保持 `COPY requirements.txt` 在前,先 `pip install`,再 `COPY app static`,避免代码改动频繁导致依赖层重建。
- 新增 `.dockerignore`(忽略 `data/`、临时文件、`.git` 等)以减少上下文,提高构建速度。
### 5) 可选APT 源加速
- 如后续需要安装系统依赖(目前没有),切换为腾讯云/清华 APT 源:`mirrors.cloud.tencent.com` 或 `mirrors.tuna.tsinghua.edu.cn`
## 拟改动内容
1. 更新 `Dockerfile`
- 写入 `/etc/pip.conf``index-url` 指向腾讯云或清华镜像)
- 设置 `ENV PIP_INDEX_URL``PIP_DEFAULT_TIMEOUT`
- 保持依赖安装层的顺序与缓存稳定
2. 新增 `.dockerignore`:忽略不必要文件,缩短构建时间
3. 更新《部署文档.md》
- 添加 Docker daemon 镜像加速配置步骤(腾讯云镜像)
- 添加 TCR 推送/拉取流程示例
- 标注国内 pip 源配置与回退方案(多镜像选择)
4. 更新 README补充“国内部署加速”小节
## 交付后效果
- 服务器端 `docker compose up --build` 明显提速;
- 或直接 `docker compose up -d` 拉取预构建镜像,部署用时较短;
- 构建层缓存更稳定,代码改动不触发依赖重装。
## 后续可选
- 若你使用企业私有网络VPC限制外网建议固定用 TCR 方案。
- 如需离线部署,提供 `pip wheel` 预下载与本地源(后续扩展)。

View File

@ -1,5 +1,9 @@
FROM python:3.11-slim
WORKDIR /app
RUN mkdir -p /etc
RUN echo "[global]\nindex-url = https://mirrors.cloud.tencent.com/pypi/simple\n" > /etc/pip.conf
ENV PIP_INDEX_URL=https://mirrors.cloud.tencent.com/pypi/simple \
PIP_DEFAULT_TIMEOUT=120
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app app

View File

@ -38,6 +38,11 @@
- 宝塔/Nginx可选反代到 `http://127.0.0.1:57777` 并启用 HTTPS
- 详见《部署文档.md》
## 国内部署加速(腾讯云)
- Docker 加速:在服务器 `/etc/docker/daemon.json` 配置 `registry-mirrors`(参见《部署文档.md》
- Python 依赖:镜像已默认使用腾讯云 PyPI 源 `https://mirrors.cloud.tencent.com/pypi/simple`
- 预构建:在本地构建并推送到腾讯云 TCR服务器直接拉取镜像避免远端构建时慢下载
## 前端扫码
- 优先使用浏览器原生 `BarcodeDetector` 实时识别
- 非安全上下文或不支持时,自动切换为“拍照识别”方式

View File

@ -16,6 +16,12 @@ let videoEl = null;
let mediaStream = null;
const scannerMessage = qs('#scannerMessage');
const captureInput = qs('#captureInput');
const toggleTorchBtn = qs('#toggleTorch');
const zoomInput = qs('#zoom');
const zoomWrap = qs('#zoomWrap');
let detector = null;
let videoTrack = null;
let loopReq = null;
// 无分页模式
let currentTab = 'search';
@ -93,8 +99,10 @@ async function openScanner(){
return;
}
if('BarcodeDetector' in window){
const formats = ['ean_13','ean_8','code_128','code_39','upc_a','upc_e'];
const detector = new window.BarcodeDetector({ formats });
const supported = await window.BarcodeDetector.getSupportedFormats();
const wanted = ['ean_13','ean_8','code_128','code_39','upc_a','upc_e'];
const useFormats = wanted.filter(f=>supported.includes(f));
detector = new window.BarcodeDetector({ formats: useFormats.length?useFormats:supported });
mediaStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
videoEl = document.createElement('video');
videoEl.autoplay = true;
@ -105,9 +113,35 @@ async function openScanner(){
host.appendChild(videoEl);
await videoEl.play();
scannerMessage.classList.add('hidden');
const loop = async()=>{
try{
videoTrack = mediaStream.getVideoTracks()[0];
const caps = videoTrack.getCapabilities ? videoTrack.getCapabilities() : {};
if(caps.torch){ toggleTorchBtn.classList.remove('hidden'); }
else { toggleTorchBtn.classList.add('hidden'); }
if(caps.zoom){
zoomWrap.classList.remove('hidden');
const min = caps.zoom.min ?? 1;
const max = caps.zoom.max ?? 5;
zoomInput.min = String(min);
zoomInput.max = String(max);
zoomInput.value = String(min);
} else {
zoomWrap.classList.add('hidden');
}
}catch(_){ }
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const tick = async()=>{
try{
const codes = await detector.detect(videoEl);
const w = videoEl.videoWidth || 640;
const h = videoEl.videoHeight || 480;
const size = Math.min(w, h);
const sx = (w - size)/2;
const sy = (h - size)/2;
canvas.width = size;
canvas.height = size;
ctx.drawImage(videoEl, sx, sy, size, size, 0, 0, size, size);
const codes = await detector.detect(canvas);
if(codes && codes.length){
const text = codes[0].rawValue;
input.value = text;
@ -117,9 +151,9 @@ async function openScanner(){
return;
}
}catch(_){ }
if(!scannerModal.classList.contains('hidden')) requestAnimationFrame(loop);
if(!scannerModal.classList.contains('hidden')){ loopReq = requestAnimationFrame(tick); }
};
requestAnimationFrame(loop);
loopReq = requestAnimationFrame(tick);
} else {
scannerMessage.textContent = '浏览器不支持实时扫码,已切换为拍照识别方式';
captureInput.click();
@ -135,6 +169,7 @@ function closeScannerFn(){
try{ if(mediaStream){ mediaStream.getTracks().forEach(t=>t.stop()); mediaStream=null; } }catch(_){ }
try{ const host = document.getElementById('scanner'); if(host){ host.innerHTML=''; } }catch(_){ }
try{ if(scannerMessage){ scannerMessage.textContent=''; scannerMessage.classList.add('hidden'); } }catch(_){ }
try{ if(loopReq){ cancelAnimationFrame(loopReq); loopReq=null; } }catch(_){ }
scannerModal.classList.add('hidden');
}
@ -167,6 +202,23 @@ captureInput.addEventListener('change', async ()=>{
}
});
toggleTorchBtn.addEventListener('click', async ()=>{
try{
if(!videoTrack) return;
const settings = videoTrack.getSettings ? videoTrack.getSettings() : {};
const torchOn = settings.torch === true;
await videoTrack.applyConstraints({ advanced: [{ torch: !torchOn }] });
}catch(_){ }
});
zoomInput.addEventListener('input', async ()=>{
try{
if(!videoTrack) return;
const z = Number(zoomInput.value);
await videoTrack.applyConstraints({ advanced: [{ zoom: z }] });
}catch(_){ }
});
scanBtn.addEventListener('click', openScanner);
closeScanner.addEventListener('click', closeScannerFn);

View File

@ -43,7 +43,14 @@
<div id="scannerModal" class="modal hidden">
<div class="modal-content">
<div id="scannerMessage" class="scanner-message hidden"></div>
<div id="scanner"></div>
<div id="scanner" class="scanner-area"></div>
<div class="scanner-controls">
<button id="toggleTorch" class="hidden">开灯</button>
<label id="zoomWrap" class="hidden">
缩放
<input id="zoom" type="range" min="1" max="5" step="0.1" value="1" />
</label>
</div>
<button id="closeScanner">关闭</button>
</div>
</div>

View File

@ -35,6 +35,9 @@ mark{background:#ffec99}
.modal{position:fixed;inset:0;background:rgba(0,0,0,0.4);display:flex;align-items:center;justify-content:center}
.modal.hidden{display:none}
.modal-content{background:#fff;border-radius:10px;padding:12px;width:92%;max-width:480px}
.scanner-area{position:relative}
.scanner-controls{display:flex;gap:10px;align-items:center;justify-content:flex-end;margin:8px 0}
.scanner-overlay{position:absolute;inset:0;border:2px dashed rgba(25,118,210,0.6);border-radius:8px;pointer-events:none}
@media (max-width:600px){
.container{padding:12px}
.tab{padding:8px 12px}

View File

@ -65,5 +65,33 @@ docker compose up --build -d
- 宿主仅开放 80/443`57777` 可通过回环访问并由 Nginx 反代对外
- 上传导入大小可在 Nginx/面板调整;建议限制文件类型为 Excel`.xlsx/.xls`
## 国内部署加速(腾讯云)
- Docker Hub 拉取加速:
- 编辑 `/etc/docker/daemon.json`
```json
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com",
"https://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"
]
}
```
- 重启 Docker`systemctl restart docker`
- Python 依赖加速:镜像内已默认配置 `pip` 使用腾讯云源 `https://mirrors.cloud.tencent.com/pypi/simple`
- 预构建镜像:
- 在本地构建并推送到腾讯云 TCR容器镜像服务
```bash
docker build -t ccr.ccs.tencentyun.com/<namespace>/<repo>:v1 .
docker login ccr.ccs.tencentyun.com
docker push ccr.ccs.tencentyun.com/<namespace>/<repo>:v1
```
- 服务器直接拉取镜像并运行,跳过远端构建:
```bash
docker pull ccr.ccs.tencentyun.com/<namespace>/<repo>:v1
docker compose up -d
```
---
如需将 Compose 改为读取 `.env`(例如 `PORT`、令牌等),我可以补充 `.env.example``docker-compose.yml``env_file` 配置。当前方案保持简洁,端口固定为 57777。