Initial commit

parents
{
"version": "1.0.0",
"name": "Python + Node.js 全栈演示",
"tags": ["python", "flask", "nodejs", "express", "全栈", "demo"],
"description": "一个基于 Python Flask 后端和 Node.js Express 前端的全栈演示项目,提供 API 接口和前端测试页面",
"init_commands": ["make install"],
"start_commands": ["make run"],
"stop_commands": ["make stop"],
"mapping_ports": {
"APP_PORT_1": 8000,
"APP_PORT_2": 8001,
"APP_PORT_3": 8002,
"APP_PORT_4": 8003
},
"deploy": [
{
"image_name": "python-node-demo",
"app_access_entry": true,
"ports": [
{
"name": "APP_PORT_1",
"main_port": true,
"inner_access_env_key": "API_URL",
"open_access": true,
"open_access_env_key": "API_DOMAIN"
},
{
"name": "APP_PORT_2",
"main_port": false,
"inner_access_env_key": "FRONTEND_URL",
"open_access": true,
"open_access_env_key": "FRONTEND_DOMAIN"
}
],
"docker_file_dir": "./",
"deploy_resources": {
"cpu_limit": 0.5,
"memory_limit": 2,
"storage_limit": 1
}
}
]
}
\ No newline at end of file
README.md
CHANGELOG.md
LICENSE
webview-ui
assets/vscode-material-icons
*.bak
*.bak.*
node_modules/
.history/
\ No newline at end of file
{
}
\ No newline at end of file
{}
\ No newline at end of file
# 多阶段构建:Python + Node.js 全栈应用
FROM hb.eazytec-cloud.com/eazytec/eazydevelop-python-node:ubuntu22.04-python3.11-node22 AS python-base
# 设置工作目录
WORKDIR /app
# 复制 Python 后端文件
COPY backend/ /app/backend/
COPY .resource/ /app/.resource/
# 设置环境变量
ENV RESOURCE_PATH=/app/.resource/resource_deploy.json
# 安装 Python 依赖
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/ && \
pip install --no-cache-dir -r backend/requirements.txt
# Node.js 阶段
FROM hb.eazytec-cloud.com/eazytec/eazydevelop-python-node:ubuntu22.04-python3.11-node22 AS node-base
# 设置工作目录
WORKDIR /app
# 复制 Node.js 前端文件
COPY frontend/ /app/frontend/
COPY .resource/ /app/.resource/
# 设置环境变量
ENV RESOURCE_PATH=/app/.resource/resource_deploy.json
ENV NODE_ENV=production
# 安装 Node.js 依赖
RUN cd frontend && npm config set registry https://registry.npmmirror.com && \
npm install --production
# 最终阶段:合并两个服务
FROM hb.eazytec-cloud.com/eazytec/eazydevelop-python-node:ubuntu22.04-python3.11-node22
# 设置工作目录
WORKDIR /app
# 复制 Python 后端
COPY --from=python-base /app/backend/ /app/backend/
COPY --from=python-base /usr/local/lib/python3.11/site-packages/ /usr/local/lib/python3.11/site-packages/
# 复制 Node.js 前端
COPY --from=node-base /app/frontend/ /app/frontend/
COPY --from=node-base /usr/local/bin/node /usr/local/bin/node
COPY --from=node-base /usr/local/bin/npm /usr/local/bin/npm
COPY --from=node-base /usr/local/lib/node_modules/ /usr/local/lib/node_modules/
# 复制资源文件
COPY .resource/ /app/.resource/
# 设置环境变量
ENV RESOURCE_PATH=/app/.resource/resource_deploy.json
ENV PYTHONPATH=/app/backend
ENV NODE_ENV=production
# 创建启动脚本
RUN echo '#!/bin/bash\n\
# 启动 Python 后端\n\
cd /app/backend && python app.py &\n\
# 启动 Node.js 前端\n\
cd /app/frontend && node server.js &\n\
# 等待所有后台进程\n\
wait' > /app/start.sh && chmod +x /app/start.sh
# 暴露端口
EXPOSE $APP_PORT_1 $APP_PORT_2
# 启动命令
CMD ["/app/start.sh"]
# 变量定义
APP_NAME = python-node-demo
NODE = node
NPM = npm
PYTHON = python3
PIP = pip3
BACKEND_PORT = $(or $(APP_PORT_1),8000)
FRONTEND_PORT = $(or $(APP_PORT_2),8001)
RESOURCE_PATH = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/.resource/resource_dev.json
LOG_FILE_BACKEND = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/.logs/app.log
LOG_FILE_FRONTEND = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/.logs/frontend.log
PID_FILE = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/.logs/app.pid
BACKEND_PID_FILE = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/.logs/backend.pid
FRONTEND_PID_FILE = $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/.logs/frontend.pid
# 安装命令
.PHONY: install
install:
@echo "检查 Python 版本..."
@$(PYTHON) --version
@echo "检查 pip 版本..."
@$(PIP) --version
@echo "检查 Node.js 版本..."
@$(NODE) --version
@echo "检查 npm 版本..."
@$(NPM) --version
@echo "正在安装Python依赖..."
@cd backend && $(PIP) install -r requirements.txt
@echo "正在安装Node.js依赖..."
@cd frontend && $(NPM) install
@echo "✅ 依赖安装完成"
# 运行后端
.PHONY: run-backend
run-backend:
@echo "正在启动Python后端服务..."
@cd backend && BACKEND_PORT=$(BACKEND_PORT) $(PYTHON) app.py > $(LOG_FILE_BACKEND) 2>&1 & echo $$! > $(BACKEND_PID_FILE)
@echo "✅ Python后端已启动,端口: $(BACKEND_PORT)"
# 运行前端
.PHONY: run-frontend
run-frontend:
@echo "正在启动Node.js前端服务..."
@cd frontend && BACKEND_URL=http://localhost:$(BACKEND_PORT) APP_PORT_1=$(FRONTEND_PORT) $(NODE) server.js > $(LOG_FILE_FRONTEND) 2>&1 & echo $$! > $(FRONTEND_PID_FILE)
@echo "✅ Node.js前端已启动,端口: $(FRONTEND_PORT)"
# 运行所有服务
.PHONY: run
run: run-backend run-frontend
@echo "✅ 所有服务已启动"
@echo "前端地址: http://localhost:$(FRONTEND_PORT)"
@echo "后端API地址: http://localhost:$(BACKEND_PORT)"
# 停止后端
.PHONY: stop-backend
stop-backend:
@echo "正在停止 Python 后端服务..."
@if command -v lsof >/dev/null 2>&1; then \
echo "使用 lsof 命令停止后端..."; \
lsof -ti:$(BACKEND_PORT) | xargs kill -9 2>/dev/null || echo "没有找到运行在端口 $(BACKEND_PORT) 的进程"; \
elif command -v ss >/dev/null 2>&1; then \
echo "使用 ss 命令停止后端..."; \
ss -ltnp | grep ':$(BACKEND_PORT)' | awk '{print $$7}' | sed 's/.*pid=\([0-9]*\).*/\1/' | xargs kill 2>/dev/null || echo "没有找到运行在端口 $(BACKEND_PORT) 的进程"; \
elif command -v netstat >/dev/null 2>&1; then \
echo "使用 netstat 命令停止后端..."; \
netstat -tlnp 2>/dev/null | grep ':$(BACKEND_PORT)' | awk '{print $$7}' | cut -d'/' -f1 | xargs kill 2>/dev/null || echo "没有找到运行在端口 $(BACKEND_PORT) 的进程"; \
else \
echo "❌ 错误:系统中没有找到 lsof、ss 或 netstat 命令"; \
exit 1; \
fi
@echo "✅ Python后端已停止"
.PHONY: stop-frontend
stop-frontend:
@echo "正在停止 Node.js 前端服务..."
@if command -v lsof >/dev/null 2>&1; then \
echo "使用 lsof 命令停止前端..."; \
lsof -ti:$(FRONTEND_PORT) | xargs kill -9 2>/dev/null || echo "没有找到运行在端口 $(FRONTEND_PORT) 的进程"; \
elif command -v ss >/dev/null 2>&1; then \
echo "使用 ss 命令停止前端..."; \
ss -ltnp | grep ':$(FRONTEND_PORT)' | awk '{print $$7}' | sed 's/.*pid=\([0-9]*\).*/\1/' | xargs kill 2>/dev/null || echo "没有找到运行在端口 $(FRONTEND_PORT) 的进程"; \
elif command -v netstat >/dev/null 2>&1; then \
echo "使用 netstat 命令停止前端..."; \
netstat -tlnp 2>/dev/null | grep ':$(FRONTEND_PORT)' | awk '{print $$7}' | cut -d'/' -f1 | xargs kill 2>/dev/null || echo "没有找到运行在端口 $(FRONTEND_PORT) 的进程"; \
else \
echo "❌ 错误:系统中没有找到 lsof、ss 或 netstat 命令"; \
exit 1; \
fi
@echo "✅ Node.js前端已停止"
# 停止所有服务
.PHONY: stop
stop: stop-backend stop-frontend
@echo "✅ 所有服务已停止"
# 清理命令
.PHONY: clean
clean:
@echo "正在清理..."
@rm -rf frontend/node_modules
@rm -f $(LOG_FILE) $(PID_FILE) $(BACKEND_PID_FILE) $(FRONTEND_PID_FILE)
@echo "✅ 清理完成"
# 开发模式(使用nodemon)
.PHONY: dev
dev:
@echo "启动开发模式..."
@cd frontend && BACKEND_URL=http://localhost:$(BACKEND_PORT) APP_PORT_1=$(FRONTEND_PORT) npx nodemon server.js &
@cd backend && BACKEND_PORT=$(BACKEND_PORT) $(PYTHON) app.py &
@echo "✅ 开发模式已启动"
@echo "前端地址: http://localhost:$(FRONTEND_PORT)"
@echo "后端API地址: http://localhost:$(BACKEND_PORT)"
# 构建Docker镜像
.PHONY: docker-build
docker-build:
@echo "构建Docker镜像..."
@docker build -t $(APP_NAME) .
@echo "✅ Docker镜像构建完成"
# 运行Docker容器
.PHONY: docker-run
docker-run:
@echo "运行Docker容器..."
@docker run -p $(FRONTEND_PORT):3000 -p $(BACKEND_PORT):5000 $(APP_NAME)
\ No newline at end of file
from flask import Flask, jsonify, request
from flask_cors import CORS
import os
app = Flask(__name__)
CORS(app) # 允许跨域请求
@app.route('/', methods=['GET'])
def root():
"""根路径返回 helloworld"""
return "helloworld"
@app.route('/api/hello', methods=['GET'])
def hello():
"""简单的Hello World API"""
return jsonify({
'message': 'Hello from Python backend!',
'status': 'success',
'data': {
'backend': 'Python Flask',
'frontend': 'Node.js',
'timestamp': '2024-01-01T00:00:00Z'
}
})
@app.route('/api/info', methods=['POST'])
def get_info():
"""接收前端数据并返回信息的API"""
try:
data = request.get_json()
name = data.get('name', 'Anonymous')
message = data.get('message', 'No message')
return jsonify({
'message': f'Hello {name}!',
'received_message': message,
'status': 'success',
'backend_response': 'Data received successfully from Python backend'
})
except Exception as e:
return jsonify({
'message': 'Error processing request',
'status': 'error',
'error': str(e)
}), 400
if __name__ == '__main__':
port = int(os.environ.get('APP_PORT_1', 8000))
app.run(host='0.0.0.0', port=port, debug=True)
Flask==2.3.3
Flask-CORS==4.0.0
gunicorn==21.2.0
This diff is collapsed.
{
"name": "python-node-demo-frontend",
"version": "1.0.0",
"description": "Node.js前端演示应用",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"axios": "^1.6.0"
},
"devDependencies": {
"nodemon": "^3.0.1"
},
"keywords": ["nodejs", "express", "frontend", "demo"],
"author": "Demo Author",
"license": "MIT"
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python + Node.js Demo</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;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
padding: 40px;
max-width: 600px;
width: 100%;
text-align: center;
}
h1 {
color: #333;
margin-bottom: 30px;
font-size: 2.5em;
}
.api-section {
margin: 30px 0;
padding: 20px;
border: 2px solid #f0f0f0;
border-radius: 10px;
background: #fafafa;
}
.api-section h3 {
color: #555;
margin-bottom: 15px;
}
button {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
border: none;
padding: 12px 24px;
border-radius: 25px;
cursor: pointer;
font-size: 16px;
margin: 10px;
transition: transform 0.2s;
}
button:hover {
transform: translateY(-2px);
}
.form-group {
margin: 20px 0;
}
input, textarea {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 10px;
font-size: 16px;
margin: 5px 0;
}
input:focus, textarea:focus {
outline: none;
border-color: #667eea;
}
.response {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 10px;
padding: 15px;
margin: 15px 0;
text-align: left;
font-family: 'Courier New', monospace;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
}
.status {
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
.status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
</style>
</head>
<body>
<div class="container">
<h1>🐍 Python + Node.js Demo</h1>
<p>这是一个简单的全栈应用演示,后端使用Python Flask,前端使用Node.js Express</p>
<!-- GET API 测试 -->
<div class="api-section">
<h3>📡 GET API 测试</h3>
<p>点击按钮测试从Python后端获取数据</p>
<button onclick="testGetAPI()">测试 GET /api/hello</button>
<div id="getResponse" class="response" style="display: none;"></div>
</div>
<!-- POST API 测试 -->
<div class="api-section">
<h3>📤 POST API 测试</h3>
<p>填写信息并发送到Python后端</p>
<div class="form-group">
<input type="text" id="nameInput" placeholder="请输入您的姓名" value="张三">
</div>
<div class="form-group">
<textarea id="messageInput" placeholder="请输入消息" rows="3">你好,这是来自前端的消息!</textarea>
</div>
<button onclick="testPostAPI()">测试 POST /api/info</button>
<div id="postResponse" class="response" style="display: none;"></div>
</div>
<div id="status" class="status" style="display: none;"></div>
</div>
<script>
function showStatus(message, type) {
const statusDiv = document.getElementById('status');
statusDiv.textContent = message;
statusDiv.className = `status ${type}`;
statusDiv.style.display = 'block';
setTimeout(() => {
statusDiv.style.display = 'none';
}, 3000);
}
function showResponse(elementId, data) {
const responseDiv = document.getElementById(elementId);
responseDiv.textContent = JSON.stringify(data, null, 2);
responseDiv.style.display = 'block';
}
async function testGetAPI() {
try {
showStatus('正在请求GET API...', 'success');
const response = await fetch('/api/hello');
const data = await response.json();
showResponse('getResponse', data);
showStatus('GET API 请求成功!', 'success');
} catch (error) {
showResponse('getResponse', { error: error.message });
showStatus('GET API 请求失败!', 'error');
}
}
async function testPostAPI() {
try {
const name = document.getElementById('nameInput').value;
const message = document.getElementById('messageInput').value;
if (!name.trim()) {
showStatus('请输入姓名!', 'error');
return;
}
showStatus('正在发送POST请求...', 'success');
const response = await fetch('/api/info', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
message: message
})
});
const data = await response.json();
showResponse('postResponse', data);
showStatus('POST API 请求成功!', 'success');
} catch (error) {
showResponse('postResponse', { error: error.message });
showStatus('POST API 请求失败!', 'error');
}
}
// 页面加载时自动测试GET API
window.onload = function() {
testGetAPI();
};
</script>
</body>
</html>
const express = require('express');
const path = require('path');
const axios = require('axios');
const app = express();
const PORT = process.env.APP_PORT_2 || 8001;
const BACKEND_PORT = process.env.APP_PORT_1 || 8000;
const BACKEND_URL = process.env.API_URL || 'http://localhost:'+BACKEND_PORT;
// 中间件
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
// 代理API请求到Python后端
app.get('/api/hello', async (req, res) => {
try {
const response = await axios.get(`${BACKEND_URL}/api/hello`);
res.json(response.data);
} catch (error) {
res.status(500).json({
message: '无法连接到后端服务',
error: error.message
});
}
});
app.post('/api/info', async (req, res) => {
try {
const response = await axios.post(`${BACKEND_URL}/api/info`, req.body);
res.json(response.data);
} catch (error) {
res.status(500).json({
message: '无法连接到后端服务',
error: error.message
});
}
});
// 提供前端页面
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.listen(PORT, () => {
console.log(`前端服务器运行在端口 ${PORT}`);
console.log(`后端API地址: ${BACKEND_URL}`);
});
#!/bin/bash
# 启动Python后端
echo "启动Python后端服务..."
cd /app/backend
python app.py &
BACKEND_PID=$!
# 等待后端启动
sleep 3
# 启动Node.js前端
echo "启动Node.js前端服务..."
cd /app/frontend
npm start &
FRONTEND_PID=$!
# 等待进程
wait $BACKEND_PID $FRONTEND_PID
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment