当前位置: 首页 > news >正文

docker项目打包演示项目(数字排序服务)

本文章的主要目的是为了熟练将自己的本地项目通过docker打包成镜像,客户通过运行docker打包好的镜像可以直接访问写好的代码,不需要自己反复去配置环境,省去一系列复杂的操作,本篇文章主要包括四个部分,第一后端代码的封装通过Fastapi技术,第二是前端代码,第三是解决跨域问题,第四是Dockerfile文件的编写。

docker打包整体效果运行

项目整体实现逻辑图

一. 后端API代码封装

1. 项目结构概览

数字排序服务后端
├── FastAPI应用实例
├── CORS跨域配置
├── 静态文件服务
├── 路由处理
│   ├── 根路径 (返回前端页面)
│   ├── /frontend (前端页面访问)
│   ├── /frontend-en (英文前端页面)
│   └── /sort (核心排序API)
└── 数据模型定义

2. 请求处理流程图

客户端请求 → CORS中间件 → 路由匹配 → 数据验证 → 业务处理 → 返回响应↓
前端页面请求 → 静态文件服务 → 返回HTML↓
API排序请求 → Pydantic验证 → 排序算法 → 返回JSON

3.代码模块详细解析

(1)导入与初始化模块
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional, Union
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, HTMLResponse
import osapp = FastAPI(title="数字排序服务")​​功能说明​​:
导入FastAPI框架核心组件
使用Pydantic进行数据验证
支持类型提示(List, Optional, Union)
配置CORS跨域访问
提供静态文件服务能力
(2)CORS跨域配置模块
app.add_middleware(CORSMiddleware,allow_origins=["*"],  # 允许所有来源allow_credentials=True,allow_methods=["*"],  # 允许所有HTTP方法allow_headers=["*"],  # 允许所有HTTP头
)配置详解​​:
allow_origins=["*"]: 开发环境配置,生产环境应限制具体域名
支持前后端分离部署模式
允许浏览器跨域请求API接口
(3)静态文件服务模块
app.mount("/static", StaticFiles(directory="."), name="static")​​服务逻辑​​:
将当前目录挂载为静态文件服务
通过/static路径访问静态资源
支持HTML、CSS、JS等前端资源访问
(4)前端页面路由模块
@app.get("/", response_class=HTMLResponse)
async def read_index():try:with open("前端测试页面.html", "r", encoding="utf-8") as f:html_content = f.read()return HTMLResponse(content=html_content)except FileNotFoundError:# 备用英文页面try:with open("frontend.html", "r", encoding="utf-8") as f:html_content = f.read()return HTMLResponse(content=html_content)except FileNotFoundError:return HTMLResponse(content="<h1>前端页面未找到</h1>")路由逻辑​​:
优先加载中文前端页面
提供备用英文页面支持
异常处理确保服务稳定性
支持直接通过根路径访问前端
(5)数据模型定义模块
class SortRequest(BaseModel):numbers: List[Union[int, float]]  # 支持整数和浮点数ascending: Optional[bool] = True  # 排序方式,默认升序remove_duplicates: Optional[bool] = False  # 是否去重,默认不去重模型特性​​:
​​类型安全​​: 使用Union支持多种数字类型
​​默认值​​: 提供合理的默认配置
​​可选参数​​: 客户端可选择性提供参数
​​自动验证​​: Pydantic自动验证输入数据格式
(6)核心排序算法整体流程
@app.post("/sort", summary="数字排序接口")
async def sort_numbers(request: SortRequest):# 1. 去重处理(可选)if request.remove_duplicates:unique_numbers = []seen = set()for num in request.numbers:key = (num, type(num))  # 区分整数和浮点数if key not in seen:seen.add(key)unique_numbers.append(num)numbers_to_sort = unique_numberselse:numbers_to_sort = request.numbers# 2. 排序处理sorted_numbers = sorted(numbers_to_sort, reverse=not request.ascending)# 3. 返回结果return {"sorted_numbers": sorted_numbers,"original_count": len(request.numbers),"result_count": len(sorted_numbers),"order": "升序" if request.ascending else "降序","removed_duplicates": request.remove_duplicates}排序逻辑​​:
使用Python内置sorted()函数
时间复杂度: O(n log n)
稳定性: 保持相等元素的相对顺序
支持混合类型排序(int和float)
(7)启动配置
if __name__ == "__main__":import uvicornuvicorn.run(app, host="0.0.0.0", port=8021)
(8)完整代码如下
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional, Union
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, HTMLResponse
import osapp = FastAPI(title="数字排序服务")# 添加CORS中间件,允许跨域请求
app.add_middleware(CORSMiddleware,allow_origins=["*"],  # 允许所有来源,生产环境应该限制具体域名allow_credentials=True,allow_methods=["*"],  # 允许所有HTTP方法allow_headers=["*"],  # 允许所有HTTP头
)# 挂载静态文件目录(前端页面)
app.mount("/static", StaticFiles(directory="."), name="static")# 根路径返回前端页面
@app.get("/", response_class=HTMLResponse)
async def read_index():# 直接返回前端页面的HTML内容try:with open("前端测试页面.html", "r", encoding="utf-8") as f:html_content = f.read()return HTMLResponse(content=html_content)except FileNotFoundError:# 如果中文文件不存在,尝试英文文件try:with open("frontend.html", "r", encoding="utf-8") as f:html_content = f.read()return HTMLResponse(content=html_content)except FileNotFoundError:return HTMLResponse(content="<h1>前端页面未找到</h1><p>请检查前端HTML文件是否存在</p>")# 提供前端页面的直接访问路径
@app.get("/frontend", response_class=HTMLResponse)
async def read_frontend():return await read_index()# 提供英文前端页面的访问路径
@app.get("/frontend-en", response_class=HTMLResponse)
async def read_frontend_en():try:with open("frontend.html", "r", encoding="utf-8") as f:html_content = f.read()return HTMLResponse(content=html_content)except FileNotFoundError:return HTMLResponse(content="<h1>英文前端页面未找到</h1>")class SortRequest(BaseModel):numbers: List[Union[int, float]]  # 支持整数和浮点数ascending: Optional[bool] = True  # 排序方式,默认升序remove_duplicates: Optional[bool] = False  # 是否去重,默认不去重@app.post("/sort", summary="数字排序接口")
async def sort_numbers(request: SortRequest):"""对输入的数字列表进行排序,保留原始数据类型,并可选择去重- **numbers**: 需要排序的数字列表 (例如: [3, 1.5, 4])- **ascending**:- True: 升序排列 (默认)- False: 降序排列- **remove_duplicates**:- True: 去除重复元素- False: 保留所有元素 (默认)"""# 如果需要去重,则先转换为集合去重if request.remove_duplicates:# 使用集合去重,但保持原始数据类型unique_numbers = []seen = set()for num in request.numbers:# 使用元组 (值, 类型) 来区分整数和浮点数key = (num, type(num))if key not in seen:seen.add(key)unique_numbers.append(num)numbers_to_sort = unique_numberselse:numbers_to_sort = request.numbers# 执行排序sorted_numbers = sorted(numbers_to_sort, reverse=not request.ascending)# 返回结果return {"sorted_numbers": sorted_numbers,"original_count": len(request.numbers),"result_count": len(sorted_numbers),"order": "升序" if request.ascending else "降序","removed_duplicates": request.remove_duplicates}if __name__ == "__main__":import uvicornuvicorn.run(app, host="0.0.0.0", port=8021)


二. 前端代码

1.整体架构与可视化逻辑

数字排序服务前端
├── HTML结构
│   ├── 头部信息 (meta, title, style)
│   └── 主体内容
│       ├── 容器布局
│       ├── 头部区域 (标题和描述)
│       ├── 服务器信息显示
│       ├── 输入区域
│       ├── 按钮区域
│       ├── 加载动画
│       ├── 错误提示
│       └── 结果展示区域
├── CSS样式系统
│   ├── 响应式布局
│   ├── 渐变背景和阴影
│   ├── 动画效果
│   └── 组件样式
└── JavaScript功能模块├── 服务器连接检测├── 输入验证和解析├── API请求处理├── 结果展示逻辑└── 错误处理机制

2.用户交互流程图

页面加载 → 显示服务器信息 → 用户输入数据 → 点击排序按钮↓
验证输入格式 → 发送API请求 → 显示加载动画 → 接收后端响应↓
成功: 显示结果区域 ←─→ 失败: 显示错误提示↓
用户可清除结果或重新排序

3.代码详细解析

(1)文档类型和基础设置

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>数字排序服务 - 前端测试页面</title>功能说明​​:
使用HTML5文档类型
设置中文语言环境
响应式视口配置,支持移动设备
明确的页面标题
(2)主体结构布局
<body><div class="container"><div class="header">...</div><div class="content"><div class="server-info">...</div><div class="input-section">...</div><div class="button-group">...</div><div class="loading" id="loading">...</div><div class="error-message" id="errorMessage">...</div><div class="result-section" id="resultSection">...</div></div></div>布局逻辑​​:
​​容器模式​​: 使用container包装所有内容
​​层次分明​​: 清晰的区域划分
​​ID标识​​: 为动态内容区域设置唯一ID
​​语义化结构​​: 合理的HTML标签使用
(3)CSS样式重置和基础样式
* {margin: 0;padding: 0;box-sizing: border-box;
}设计理念​​:
统一所有元素的盒模型
消除浏览器默认间距
为自定义布局奠定基础
(4)响应式布局系统
body {min-height: 100vh;padding: 20px;
}.container {max-width: 800px;margin: 0 auto;
}响应式特性​​:
​​视口高度适配​​: min-height: 100vh
​​安全边距​​: 主体周围20px内边距
​​最大宽度限制​​: 800px适合大多数屏幕
​​居中布局​​: margin: 0 auto
(5)渐变和视觉效果
body {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}.header {background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}视觉设计​​:
​​渐变背景​​: 135度角渐变,营造科技感
​​色彩搭配​​: 蓝色系渐变,专业且美观
​​层次感​​: 容器阴影增强立体感
(6)动画效果系统
@keyframes fadeIn {from { opacity: 0; transform: translateY(20px); }to { opacity: 1; transform: translateY(0); }
}@keyframes shake {0%, 100% { transform: translateX(0); }25% { transform: translateX(-5px); }75% { transform: translateX(5px); }
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}动画设计​​:
​​淡入效果​​: 用于结果展示
​​抖动效果​​: 用于错误提示
​​旋转效果​​: 用于加载动画
​​流畅过渡​​: 所有交互都有过渡效果
(7)JavaScript功能模块详细解析,配置和初始化模块
// 使用固定的后端地址
const API_BASE_URL = 'http://192.168.16.137:8021';// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {updateServerInfo();// 添加键盘事件支持document.getElementById('numbers').addEventListener('keypress', function(e) {if (e.key === 'Enter') {sortNumbers();}});
});初始化逻辑​​:
​​硬编码后端地址​​: 直接指定API服务器
​​自动执行​​: 页面加载完成后自动检测服务器状态
​​键盘支持​​: 回车键触发排序功能
​​控制台日志​​: 便于调试和监控
(8)服务器信息管理模块
function updateServerInfo() {document.getElementById('frontendUrl').textContent = window.location.href;document.getElementById('backendUrl').textContent = API_BASE_URL;testBackendConnection();
}async function testBackendConnection() {try {const response = await fetch(`${API_BASE_URL}/docs`, { method: 'GET' });if (response.ok) {document.getElementById('connectionStatus').innerHTML = '<span style="color: green;">✅ 后端服务连接正常</span>';} else {document.getElementById('connectionStatus').innerHTML = '<span style="color: orange;">⚠️ 后端服务可能未启动</span>';}} catch (error) {document.getElementById('connectionStatus').innerHTML = '<span style="color: red;">❌❌ 后端服务连接失败</span>';}
}连接检测机制​​:
​​实时状态显示​​: 三种状态(正常/警告/失败)
​​友好图标​​: 使用emoji增强可读性
​​颜色编码​​: 绿色=正常,橙色=警告,红色=失败
​​自动检测​​: 页面加载时自动测试连接
(9)输入验证和解析模块
function parseNumbers(input) {if (!input.trim()) {throw new Error('请输入数字列表');}const numbers = input.split(',').map(item => {const trimmed = item.trim();if (!trimmed) return null;const num = parseFloat(trimmed);if (isNaN(num)) {throw new Error(`"${trimmed}" 不是有效的数字`);}return num;}).filter(num => num !== null);if (numbers.length === 0) {throw new Error('没有找到有效的数字');}return numbers;
}​​验证算法​​:
​​空值检查​​: 确保输入不为空
​​分割处理​​: 按逗号分割输入字符串
​​类型转换​​: 将字符串转换为数字
​​错误处理​​: 详细的错误信息提示
​​过滤处理​​: 移除空值和无效数据
(10)API处理模块
async function sortNumbers() {try {hideError();showLoading();// 获取输入值const numbersInput = document.getElementById('numbers').value;const order = document.getElementById('order').value === 'true';const removeDuplicates = document.getElementById('removeDuplicates').checked;// 解析数字const numbers = parseNumbers(numbersInput);// 准备请求数据const requestData = {numbers: numbers,ascending: order,remove_duplicates: removeDuplicates};// 发送请求到后端const response = await fetch(`${API_BASE_URL}/sort`, {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify(requestData)});if (!response.ok) {const errorText = await response.text();throw new Error(`服务器错误: ${response.status} - ${errorText}`);}const result = await response.json();showResults({...result,original_numbers: numbers.join(', ')});} catch (error) {showError(`错误: ${error.message}`);console.error('排序错误:', error);} finally {hideLoading();}
}请求流程​​:
​​错误清理​​: 开始前清除之前的错误信息
​​加载状态​​: 显示加载动画
​​数据收集​​: 获取所有用户输入
​​请求构建​​: 构造符合后端API的请求格式
​​错误处理​​: 详细的HTTP错误处理
​​结果展示​​: 统一的结果格式化显示
(11)界面状态管理模块
// 显示/隐藏错误信息
function showError(message) {const errorDiv = document.getElementById('errorMessage');errorDiv.textContent = message;errorDiv.classList.add('show');setTimeout(() => {errorDiv.classList.remove('show');}, 5000);
}function hideError() {document.getElementById('errorMessage').classList.remove('show');
}// 显示/隐藏加载动画
function showLoading() {document.getElementById('loading').classList.add('show');
}function hideLoading() {document.getElementById('loading').classList.remove('show');
}// 显示结果
function showResults(data) {const resultSection = document.getElementById('resultSection');document.getElementById('originalNumbers').textContent = data.original_numbers || '无数据';document.getElementById('sortedNumbers').textContent = Array.isArray(data.sorted_numbers) ? data.sorted_numbers.join(', ') : '无数据';document.getElementById('sortOrder').textContent = data.order || '未知';document.getElementById('originalCount').textContent = data.original_count || 0;document.getElementById('resultCount').textContent = data.result_count || 0;document.getElementById('duplicatesRemoved').textContent = data.removed_duplicates ? '是' : '否';resultSection.classList.add('show');
}// 清除结果
function clearResults() {document.getElementById('resultSection').classList.remove('show');hideError();
}状态管理​​:
​​CSS类控制​​: 通过添加/移除show类控制显示
​​自动隐藏​​: 错误信息5秒后自动消失
​​数据绑定​​: 将API响应数据绑定到对应DOM元素
​​完整性检查​​: 处理可能的空数据情况
(12)前端完整代码如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>数字排序服务 - 前端测试页面</title><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);min-height: 100vh;padding: 20px;}.container {max-width: 800px;margin: 0 auto;background: white;border-radius: 15px;box-shadow: 0 20px 40px rgba(0,0,0,0.1);overflow: hidden;}.header {background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);color: white;padding: 30px;text-align: center;}.header h1 {font-size: 2.5em;margin-bottom: 10px;}.header p {font-size: 1.1em;opacity: 0.9;}.content {padding: 30px;}.input-section {margin-bottom: 30px;}.input-group {margin-bottom: 20px;}label {display: block;margin-bottom: 8px;font-weight: 600;color: #333;}input[type="text"], select {width: 100%;padding: 12px;border: 2px solid #e1e5e9;border-radius: 8px;font-size: 16px;transition: border-color 0.3s;}input[type="text"]:focus, select:focus {outline: none;border-color: #4facfe;}.checkbox-group {display: flex;align-items: center;gap: 10px;margin-bottom: 15px;}.checkbox-group input[type="checkbox"] {width: 18px;height: 18px;}.button-group {display: flex;gap: 15px;margin-top: 30px;}button {flex: 1;padding: 15px;border: none;border-radius: 8px;font-size: 16px;font-weight: 600;cursor: pointer;transition: all 0.3s;}.btn-primary {background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);color: white;}.btn-secondary {background: #f8f9fa;color: #666;border: 2px solid #e1e5e9;}button:hover {transform: translateY(-2px);box-shadow: 0 10px 20px rgba(0,0,0,0.2);}.result-section {margin-top: 30px;padding: 20px;background: #f8f9fa;border-radius: 8px;display: none;}.result-section.show {display: block;animation: fadeIn 0.5s;}.result-item {margin-bottom: 15px;padding: 15px;background: white;border-radius: 8px;border-left: 4px solid #4facfe;}.result-label {font-weight: 600;color: #333;margin-bottom: 5px;}.result-value {color: #666;word-break: break-all;}.error-message {background: #ffe6e6;color: #d63031;padding: 15px;border-radius: 8px;margin-top: 20px;border-left: 4px solid #d63031;display: none;}.error-message.show {display: block;animation: shake 0.5s;}@keyframes fadeIn {from { opacity: 0; transform: translateY(20px); }to { opacity: 1; transform: translateY(0); }}@keyframes shake {0%, 100% { transform: translateX(0); }25% { transform: translateX(-5px); }75% { transform: translateX(5px); }}.loading {text-align: center;padding: 20px;display: none;}.loading.show {display: block;}.spinner {border: 4px solid #f3f3f3;border-top: 4px solid #4facfe;border-radius: 50%;width: 40px;height: 40px;animation: spin 1s linear infinite;margin: 0 auto 10px;}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }}</style>
</head>
<body><div class="container"><div class="header"><h1>🔢 数字排序服务</h1><p>测试后端排序接口的前端页面</p></div><div class="content"><div class="input-section"><div class="input-group"><label for="numbers">输入数字列表(用逗号分隔):</label><input type="text" id="numbers" placeholder="例如:3, 1.5, 4, 2, 3.7, 1" value="3, 1.5, 4, 2, 3.7, 1"></div><div class="input-group"><label for="order">排序方式:</label><select id="order"><option value="true">升序排列</option><option value="false">降序排列</option></select></div><div class="checkbox-group"><input type="checkbox" id="removeDuplicates"><label for="removeDuplicates">去除重复元素</label></div></div><div class="button-group"><button class="btn-primary" onclick="sortNumbers()">🚀 开始排序</button><button class="btn-secondary" onclick="clearResults()">🗑️ 清除结果</button></div><div class="loading" id="loading"><div class="spinner"></div><p>正在排序中,请稍候...</p></div><div class="error-message" id="errorMessage"></div><div class="result-section" id="resultSection"><h3>📊 排序结果</h3><div class="result-item"><div class="result-label">原始数字列表:</div><div class="result-value" id="originalNumbers"></div></div><div class="result-item"><div class="result-label">排序后数字列表:</div><div class="result-value" id="sortedNumbers"></div></div><div class="result-item"><div class="result-label">排序方式:</div><div class="result-value" id="sortOrder"></div></div><div class="result-item"><div class="result-label">原始元素数量:</div><div class="result-value" id="originalCount"></div></div><div class="result-item"><div class="result-label">结果元素数量:</div><div class="result-value" id="resultCount"></div></div><div class="result-item"><div class="result-label">是否去重:</div><div class="result-value" id="duplicatesRemoved"></div></div></div></div></div><script>// 动态获取后端API地址function getBackendUrl() {const protocol = window.location.protocol;const hostname = window.location.hostname;const port = window.location.port;// 在Docker容器中,前端和后端都在同一个容器内运行// 使用当前页面的主机和端口if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0.0.0.0') {// 使用当前页面的地址return `${protocol}//${hostname}:${port || 8021}`;}// 其他情况使用当前主机地址return `${protocol}//${hostname}:${port || 8021}`;}const API_BASE_URL = getBackendUrl();// 显示错误信息function showError(message) {const errorDiv = document.getElementById('errorMessage');errorDiv.textContent = message;errorDiv.classList.add('show');setTimeout(() => {errorDiv.classList.remove('show');}, 5000);}// 隐藏错误信息function hideError() {document.getElementById('errorMessage').classList.remove('show');}// 显示加载动画function showLoading() {document.getElementById('loading').classList.add('show');}// 隐藏加载动画function hideLoading() {document.getElementById('loading').classList.remove('show');}// 显示结果function showResults(data) {const resultSection = document.getElementById('resultSection');document.getElementById('originalNumbers').textContent = data.original_numbers || '无数据';document.getElementById('sortedNumbers').textContent = Array.isArray(data.sorted_numbers) ? data.sorted_numbers.join(', ') : '无数据';document.getElementById('sortOrder').textContent = data.order || '未知';document.getElementById('originalCount').textContent = data.original_count || 0;document.getElementById('resultCount').textContent = data.result_count || 0;document.getElementById('duplicatesRemoved').textContent = data.removed_duplicates ? '是' : '否';resultSection.classList.add('show');}// 清除结果function clearResults() {document.getElementById('resultSection').classList.remove('show');hideError();}// 解析输入的数字function parseNumbers(input) {if (!input.trim()) {throw new Error('请输入数字列表');}const numbers = input.split(',').map(item => {const trimmed = item.trim();if (!trimmed) return null;// 尝试解析为数字const num = parseFloat(trimmed);if (isNaN(num)) {throw new Error(`"${trimmed}" 不是有效的数字`);}return num;}).filter(num => num !== null);if (numbers.length === 0) {throw new Error('没有找到有效的数字');}return numbers;}// 排序数字的主函数async function sortNumbers() {try {hideError();showLoading();// 获取输入值const numbersInput = document.getElementById('numbers').value;const order = document.getElementById('order').value === 'true';const removeDuplicates = document.getElementById('removeDuplicates').checked;// 解析数字const numbers = parseNumbers(numbersInput);// 准备请求数据const requestData = {numbers: numbers,ascending: order,remove_duplicates: removeDuplicates};// 发送请求到后端const response = await fetch(`${API_BASE_URL}/sort`, {method: 'POST',headers: {'Content-Type': 'application/json',},body: JSON.stringify(requestData)});if (!response.ok) {const errorText = await response.text();throw new Error(`服务器错误: ${response.status} - ${errorText}`);}const result = await response.json();// 显示结果showResults({...result,original_numbers: numbers.join(', ')});} catch (error) {showError(`错误: ${error.message}`);console.error('排序错误:', error);} finally {hideLoading();}}// 页面加载完成后设置默认值document.addEventListener('DOMContentLoaded', function() {// 添加键盘事件支持document.getElementById('numbers').addEventListener('keypress', function(e) {if (e.key === 'Enter') {sortNumbers();}});console.log('数字排序服务前端测试页面已加载完成');console.log('后端API地址:', API_BASE_URL);});</script>
</body>
</html>


三. 跨域问题解决

1.CORS请求处理器类

class CORSHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):"""自定义HTTP请求处理器,添加CORS支持"""def end_headers(self):"""添加CORS头信息"""self.send_header('Access-Control-Allow-Origin', '*')self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')self.send_header('Access-Control-Allow-Headers', 'Content-Type')super().end_headers()def do_OPTIONS(self):"""处理OPTIONS预检请求"""self.send_response(200)self.end_headers()功能说明:- 跨域支持 : 允许前端页面(8000端口)访问后端API(8021端口)
- 预检处理 : 处理浏览器发送的OPTIONS预检请求
- 方法支持 : 支持GET、POST、OPTIONS三种HTTP方法

2.本地IP获取函数

def get_local_ip():"""获取本地IP地址"""try:s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.connect(("8.8.8.8", 80))ip = s.getsockname()[0]s.close()return ipexcept:return "127.0.0.1"技术原理:- UDP连接技巧 : 通过连接Google DNS服务器获取本地出口IP
- 异常处理 : 网络异常时返回本地回环地址
- 实际用途 : 提供局域网访问地址

3. 服务器启动函数

def start_server(port=8000):local_ip = get_local_ip()with socketserver.TCPServer(("", port), CORSHTTPRequestHandler) as httpd:# 信息显示print("=" * 60)print("🚀 前端测试服务器已启动!")print(f"📱 本地访问地址: http://localhost:{port}/前端测试页面.html")print(f"🌐 局域网访问地址: http://{local_ip}:{port}/前端测试页面.html")print("💡 提示:确保后端服务已在8021端口运行")# 自动打开浏览器def open_browser():time.sleep(2)webbrowser.open(f"http://localhost:{port}/前端测试页面.html")threading.Thread(target=open_browser, daemon=True).start()# 启动服务器httpd.serve_forever()

4.完整代码如下

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
前端测试页面服务器
为前端测试页面.html提供HTTP服务,解决跨域问题
"""import http.server
import socketserver
import webbrowser
import socket
import threading
import time
from urllib.parse import urlparseclass CORSHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):"""自定义HTTP请求处理器,添加CORS支持"""def end_headers(self):"""添加CORS头信息"""self.send_header('Access-Control-Allow-Origin', '*')self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')self.send_header('Access-Control-Allow-Headers', 'Content-Type')super().end_headers()def do_OPTIONS(self):"""处理OPTIONS预检请求"""self.send_response(200)self.end_headers()def get_local_ip():"""获取本地IP地址"""try:# 创建一个临时socket连接来获取本地IPs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.connect(("8.8.8.8", 80))ip = s.getsockname()[0]s.close()return ipexcept:return "127.0.0.1"def start_server(port=8000):"""启动HTTP服务器"""local_ip = get_local_ip()with socketserver.TCPServer(("", port), CORSHTTPRequestHandler) as httpd:print("=" * 60)print("🚀 前端测试服务器已启动!")print("=" * 60)print(f"📱 本地访问地址: http://localhost:{port}/前端测试页面.html")print(f"🌐 局域网访问地址: http://{local_ip}:{port}/前端测试页面.html")print("=" * 60)print("💡 提示:确保后端服务已在8021端口运行")print("💡 提示:后端API地址: http://localhost:8021")print("=" * 60)# 延迟2秒后自动打开浏览器def open_browser():time.sleep(2)webbrowser.open(f"http://localhost:{port}/前端测试页面.html")threading.Thread(target=open_browser, daemon=True).start()try:httpd.serve_forever()except KeyboardInterrupt:print("\n👋 服务器已停止")if __name__ == "__main__":start_server()


四. Dockerfile编写

1.Docker构建流程详细步骤

### 阶段1: 基础环境构建
1.拉取基础镜像 : python:3.9-slim
2.创建工作目录 : /app
3.设置环境变量 : 优化Python运行环境### 阶段2: 系统准备
4.更新包管理器 : apt-get update
4.安装系统依赖 : 基础系统工具
4.清理缓存 : 减少镜像大小### 阶段3: Python环境配置
7.复制依赖文件 : requirements.txt
7.升级pip : 确保包管理器最新
7.安装依赖 : FastAPI, uvicorn, pydantic### 阶段4: 应用部署
10.复制应用代码 : 所有项目文件
10.设置文件权限 : 更改文件所有者
10.切换用户 : 使用非root用户### 阶段5: 服务启动
13.暴露端口 : 8021用于外部访问
13.设置启动命令 : 运行后端服务

2.完整Dockerfile文件如下

# 使用Python官方镜像作为基础镜像
FROM python:3.9-slim# 设置工作目录
WORKDIR /app# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1# 安装系统依赖
RUN apt-get update && apt-get install -y \&& rm -rf /var/lib/apt/lists/*# 复制依赖文件
COPY requirements.txt .# 安装Python依赖
RUN pip install --no-cache-dir --upgrade pip && \pip install --no-cache-dir -r requirements.txt# 复制所有应用代码(包括前端页面)
COPY . .# 创建非root用户运行应用(安全最佳实践)
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser# 暴露端口
EXPOSE 8021# 启动命令(直接运行后端服务)
CMD ["python", "后端排序接口封装.py"]

3. 找到有放置Dockerfile文件的位置上,点击Shift + 鼠标右键 找到 Power shell 窗口运行指令来构建创建镜像,指令如下:docker build -t your_image_name .   *****切记后面的. 不能丢

# 停止并删除现有容器
docker stop number-sorter-container
docker rm number-sorter-container# 重新构建镜像(包含修复后的前端页面)
docker build -t number-sorter .# 重新运行容器
docker run -d -p 8021:8021 --name number-sorter-container number-sorter

4.打包好的镜像会出现在 docker中,我们也可以找到我们创建好的镜像名称

5.最后运行成功的效果如下

最后总结一句话,如果我们想停掉正在运行的容器的时候,我们执行docker stop number-sorter-container之后还得删除容器再执行docker rm number-sorter-container,这里解释一下为什么会这样做,因为在Docker的容器命名机制中,每个容器必须有唯一的名称,Docker不允许使用相同的名称创建新容器,即使容器已停止,名称仍然被占用,当前两步都执行完之后后面才可以执行docker run -d -p 8021:8021 --name number-sorter-container number-sorter 服务才会重启。

# 第一次创建 - 成功
docker run --name number-sorter-container ...  # ✅ 成功# 容器停止后立即重新创建 - 失败
docker stop number-sorter-container
docker run --name number-sorter-container ...  # ❌ 失败:名称冲突# 必须删除后才能重新使用名称
docker stop number-sorter-container
docker rm number-sorter-container              # ✅ 删除容器
docker run --name number-sorter-container ...  # ✅ 成功:名称已释放

6.容器状态与名称占用的关系

http://www.dtcms.com/a/483158.html

相关文章:

  • 诸城网站建设诸城wordpress 删除缓存
  • 自动化三维测量实现精密轴承全尺寸在线测量-中科米堆CASAIM
  • glitch做网站帝国cms做笑话网站
  • 什么网站可以做动图泰州营销型网站
  • OWL与VUE3 的高级组件通信全解析
  • 外贸人常用的网站建设免费网站制作
  • 用 Python + Vue3 打造超炫酷音乐播放器:网易云歌单爬取 + Three.js 波形可视化
  • GRS 认证:再生产品的 “绿色通行证”—— 知识深度解析
  • 常平众展做网站在新西兰做兼职的网站
  • 解决拓扑排序
  • Component template requires a root element, rather than just错误
  • 网站选择空间建筑工程类招聘网站
  • 开源的故障诊断大模型(FDLM):从多模态时序到可解释智能维护
  • 【编号219】中国钢铁工业年鉴(2000-2024)
  • 企业营销策划pptseo服务公司
  • LeetCode 算法题【中等】189. 轮转数组
  • 极验验证 wordpress郑州seo方案
  • 深圳网站设计公司排行网站建设市场调研框架
  • 前端环境搭建,保姆式教学
  • 长春 行业网站品牌建设情况汇报
  • 网站推广方案模板免费网络游戏大全
  • 从混合部署到高可用:在内网环境下搭建 GitLab-Jenkins-OpenResty的完整实战复盘20251014
  • 园林公司网站建设费用自适应wordpress模板
  • 使用 fcntl 系统函数在 Linux 下改变文件属性
  • Docker 容器访问宿主机 Ollama 服务配置教程
  • 可以做彩页的网站ps做图哪个网站好
  • 使用Spring Boot构建数据访问层
  • 小白测评做网站免费网页制作有哪些
  • 潍坊专业建站wordpress视频分享
  • Vue 的响应式更新时机 + 异步录音事件回调造成状态“延后更新”或异步竞态问题(race condition)