1 程序基本结构

提示词:给出Windows下使用Python开发的电商订单分布微服务系统:订单服务、支付服务、库存服务,NACOS服务注册发现,MongoDB数据存储,统一网关服务并托管兼作页面web服务器,HTML/CSS/JS页面设计[相互关联的订单、支付、库存]。

2 系统架构设计概览

3 核心微服务开发

3.1 库存服务

inventorySvc.py完整代码

from flask import Flask, jsonify, request
from pymongo import MongoClient
from bson.objectid import ObjectId
import threading
import time
import requests

app = Flask(__name__)

# MongoDB连接配置
client = MongoClient('mongodb://localhost:27017/')
db = client['ecommerce_db']
products_collection = db['products']

# Nacos配置
NACOS_SERVER = "http://localhost:8848"
SERVICE_IP = "127.0.0.1"
SERVICE_PORT = 5002
SERVICE_NAME = "inventorySvc"


def register_to_nacos():
    """向Nacos注册服务"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance"
    params = {
        "serviceName": SERVICE_NAME,
        "ip": SERVICE_IP,
        "port": SERVICE_PORT,
        "weight": 1.0,
        "enable": True,
        "healthy": True,
        "metadata": '{"preserved.register.source":"PYTHON"}'
    }
    try:
        response = requests.post(url, params=params)
        if response.status_code == 200:
            print(f"服务 {SERVICE_NAME} 注册成功")
        else:
            print(f"注册失败: {response.text}")
    except Exception as e:
        print(f"连接Nacos失败: {e}")


def send_heartbeat():
    """发送心跳维持注册状态"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance/beat"
    params = {
        "serviceName": SERVICE_NAME,
        "ip": SERVICE_IP,
        "port": SERVICE_PORT,
        "beat": f'{{"ip":"{SERVICE_IP}","port":{SERVICE_PORT},"serviceName":"{SERVICE_NAME}"}}'
    }
    try:
        requests.put(url, params=params)
    except:
        pass


def start_heartbeat_thread():
    """启动心跳线程"""

    def heartbeat_loop():
        while True:
            send_heartbeat()
            time.sleep(5)

    threading.Thread(target=heartbeat_loop, daemon=True).start()


def product_serializer(product):
    """将MongoDB文档序列化为JSON友好的字典"""
    return {
        "id": str(product['_id']),
        "name": product.get('name'),
        "price": product.get('price'),
        "stock": product.get('stock'),
        "specs": product.get('specs', {})
    }


@app.route('/products', methods=['GET'])
def get_products():
    """获取商品列表接口,支持模糊查询"""
    try:
        query_name = request.args.get('name')
        query = {}
        if query_name:
            query['name'] = {"$regex": query_name, "$options": "i"}

        products_cursor = products_collection.find(query)
        products_list = [product_serializer(product) for product in products_cursor]

        return jsonify({
            "code": 200,
            "message": "查询成功",
            "data": products_list
        }), 200
    except Exception as e:
        return jsonify({"code": 500, "message": f"服务器错误: {str(e)}"}), 500


@app.route('/inventory/check', methods=['POST'])
def check_and_reduce_stock():
    """检查并扣减库存(供订单服务调用)"""
    data = request.json
    product_id = data.get('product_id')
    quantity = data.get('quantity')

    if not product_id or quantity is None:
        return jsonify({"error": "参数缺失"}), 400

    try:
        product = products_collection.find_one({"_id": ObjectId(product_id)})

        if product and product.get('stock', 0) >= quantity:
            new_stock = product['stock'] - quantity
            products_collection.update_one(
                {"_id": ObjectId(product_id)},
                {"$set": {"stock": new_stock}}
            )
            return jsonify({
                "available": True,
                "price": product.get('price', 0)
            }), 200

        return jsonify({"available": False, "message": "库存不足"}), 200
    except Exception as e:
        return jsonify({"error": str(e)}), 500


@app.route('/inventory/restore', methods=['POST'])
def restore_stock():
    """恢复库存(用于订单取消或支付失败时的回滚)"""
    data = request.json
    product_id = data.get('product_id')
    quantity = data.get('quantity')

    if not product_id or quantity is None:
        return jsonify({"error": "参数缺失"}), 400

    try:
        products_collection.update_one(
            {"_id": ObjectId(product_id)},
            {"$inc": {"stock": quantity}}
        )
        return jsonify({"message": "库存恢复成功"}), 200
    except Exception as e:
        return jsonify({"error": str(e)}), 500


def init_db_data():
    """初始化示例数据"""
    if products_collection.count_documents({}) == 0:
        sample_products = [
            {"name": "华为Mate60", "price": 6999.00, "stock": 100,
             "specs": {"color": "黑色", "ram": "12GB", "storage": "256GB"}},
            {"name": "iPhone 15 Pro", "price": 8999.00, "stock": 80,
             "specs": {"color": "白色", "ram": "8GB", "storage": "256GB"}},
            {"name": "小米14 Ultra", "price": 5999.00, "stock": 120,
             "specs": {"color": "绿色", "ram": "16GB", "storage": "512GB"}},
            {"name": "vivo X100 Pro", "price": 4999.00, "stock": 60,
             "specs": {"color": "蓝色", "ram": "12GB", "storage": "256GB"}},
            {"name": "OPPO Find X7", "price": 3999.00, "stock": 90,
             "specs": {"color": "灰色", "ram": "12GB", "storage": "256GB"}}
        ]
        products_collection.insert_many(sample_products)
        print("数据库初始化完成,已插入示例商品。")


if __name__ == '__main__':
    init_db_data()
    register_to_nacos()
    start_heartbeat_thread()
    app.run(port=SERVICE_PORT, debug=True)

3.2 订单服务

orderSvc.py

from flask import Flask, jsonify, request
from pymongo import MongoClient
from bson.objectid import ObjectId
import requests
import threading
import time
from datetime import datetime

app = Flask(__name__)

# MongoDB连接配置
client = MongoClient('mongodb://localhost:27017/')
db = client['ecommerce_db']
orders_collection = db['orders']

# Nacos配置
NACOS_SERVER = "http://localhost:8848"
SERVICE_IP = "127.0.0.1"
SERVICE_PORT = 5001
SERVICE_NAME = "orderSvc"

# 服务地址映射(生产环境应从Nacos动态获取)
SERVICE_MAP = {
    "inventorySvc": "http://localhost:5002",
    "paymentSvc": "http://localhost:5003"
}


def register_to_nacos():
    """向Nacos注册服务"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance"
    params = {
        "serviceName": SERVICE_NAME,
        "ip": SERVICE_IP,
        "port": SERVICE_PORT,
        "weight": 1.0,
        "enable": True,
        "healthy": True
    }
    try:
        response = requests.post(url, params=params)
        if response.status_code == 200:
            print(f"服务 {SERVICE_NAME} 注册成功")
    except Exception as e:
        print(f"连接Nacos失败: {e}")


def send_heartbeat():
    """发送心跳"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance/beat"
    params = {
        "serviceName": SERVICE_NAME,
        "ip": SERVICE_IP,
        "port": SERVICE_PORT,
        "beat": f'{{"ip":"{SERVICE_IP}","port":{SERVICE_PORT},"serviceName":"{SERVICE_NAME}"}}'
    }
    try:
        requests.put(url, params=params)
    except:
        pass


def start_heartbeat_thread():
    """启动心跳线程"""

    def heartbeat_loop():
        while True:
            send_heartbeat()
            time.sleep(5)

    threading.Thread(target=heartbeat_loop, daemon=True).start()


@app.route('/orders', methods=['POST'])
def create_order():
    """创建订单"""
    data = request.json
    user_id = data.get('user_id')
    product_id = data.get('product_id')
    quantity = data.get('quantity', 1)

    if not all([user_id, product_id]):
        return jsonify({"error": "参数缺失"}), 400

    try:
        # 1. 调用库存服务检查库存
        inventory_url = f"{SERVICE_MAP['inventorySvc']}/inventory/check"
        inv_response = requests.post(inventory_url, json={
            "product_id": product_id,
            "quantity": quantity
        }, timeout=5)

        if inv_response.status_code != 200:
            return jsonify({"error": "库存服务不可用"}), 503

        inv_data = inv_response.json()
        if not inv_data.get('available'):
            return jsonify({"error": "库存不足"}), 400

        # 2. 创建订单
        order_doc = {
            "user_id": user_id,
            "product_id": product_id,
            "quantity": quantity,
            "status": "CREATED",
            "total_price": inv_data.get('price', 0) * quantity,
            "created_at": datetime.now(),
            "updated_at": datetime.now()
        }

        result = orders_collection.insert_one(order_doc)
        order_id = str(result.inserted_id)

        return jsonify({
            "message": "订单创建成功",
            "order_id": order_id,
            "total_price": order_doc['total_price']
        }), 201

    except requests.exceptions.Timeout:
        return jsonify({"error": "库存服务超时"}), 504
    except Exception as e:
        return jsonify({"error": f"创建订单失败: {str(e)}"}), 500


@app.route('/orders/<order_id>', methods=['GET'])
def get_order(order_id):
    """查询订单详情"""
    try:
        order = orders_collection.find_one({"_id": ObjectId(order_id)})
        if order:
            order['_id'] = str(order['_id'])
            order['created_at'] = order['created_at'].isoformat()
            order['updated_at'] = order['updated_at'].isoformat()
            return jsonify({"code": 200, "data": order}), 200
        return jsonify({"error": "订单不存在"}), 404
    except Exception as e:
        return jsonify({"error": str(e)}), 500


@app.route('/orders/user/<user_id>', methods=['GET'])
def get_user_orders(user_id):
    """查询用户的所有订单"""
    try:
        orders = list(orders_collection.find({"user_id": user_id}))
        for order in orders:
            order['_id'] = str(order['_id'])
            order['created_at'] = order['created_at'].isoformat()
            order['updated_at'] = order['updated_at'].isoformat()
        return jsonify({"code": 200, "data": orders}), 200
    except Exception as e:
        return jsonify({"error": str(e)}), 500


@app.route('/orders/<order_id>/status', methods=['PUT'])
def update_order_status(order_id):
    """更新订单状态"""
    data = request.json
    new_status = data.get('status')

    valid_statuses = ['CREATED', 'PAID', 'SHIPPED', 'DELIVERED', 'CANCELLED']
    if new_status not in valid_statuses:
        return jsonify({"error": "无效的状态"}), 400

    try:
        result = orders_collection.update_one(
            {"_id": ObjectId(order_id)},
            {"$set": {"status": new_status, "updated_at": datetime.now()}}
        )

        if result.modified_count > 0:
            return jsonify({"message": "状态更新成功"}), 200
        return jsonify({"error": "订单不存在"}), 404
    except Exception as e:
        return jsonify({"error": str(e)}), 500


if __name__ == '__main__':
    register_to_nacos()
    start_heartbeat_thread()
    app.run(port=SERVICE_PORT, debug=True)

3.3 支付服务

paymentSvc.py

from flask import Flask, jsonify, request
from pymongo import MongoClient
from bson.objectid import ObjectId
import requests
import threading
import time
from datetime import datetime
import uuid

app = Flask(__name__)

# MongoDB连接配置
client = MongoClient('mongodb://localhost:27017/')
db = client['ecommerce_db']
payments_collection = db['payments']

# Nacos配置
NACOS_SERVER = "http://localhost:8848"
SERVICE_IP = "127.0.0.1"
SERVICE_PORT = 5003
SERVICE_NAME = "paymentSvc"


def register_to_nacos():
    """向Nacos注册服务"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance"
    params = {
        "serviceName": SERVICE_NAME,
        "ip": SERVICE_IP,
        "port": SERVICE_PORT,
        "weight": 1.0,
        "enable": True,
        "healthy": True
    }
    try:
        response = requests.post(url, params=params)
        if response.status_code == 200:
            print(f"服务 {SERVICE_NAME} 注册成功")
    except Exception as e:
        print(f"连接Nacos失败: {e}")


def send_heartbeat():
    """发送心跳"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance/beat"
    params = {
        "serviceName": SERVICE_NAME,
        "ip": SERVICE_IP,
        "port": SERVICE_PORT,
        "beat": f'{{"ip":"{SERVICE_IP}","port":{SERVICE_PORT},"serviceName":"{SERVICE_NAME}"}}'
    }
    try:
        requests.put(url, params=params)
    except:
        pass


def start_heartbeat_thread():
    """启动心跳线程"""

    def heartbeat_loop():
        while True:
            send_heartbeat()
            time.sleep(5)

    threading.Thread(target=heartbeat_loop, daemon=True).start()


@app.route('/pay', methods=['POST'])
def process_payment():
    """处理支付"""
    data = request.json
    order_id = data.get('order_id')
    amount = data.get('amount')
    payment_method = data.get('payment_method', 'credit_card')

    if not all([order_id, amount]):
        return jsonify({"error": "参数缺失"}), 400

    try:
        # 模拟支付处理
        transaction_id = f"TXN{datetime.now().strftime('%Y%m%d%H%M%S')}{uuid.uuid4().hex[:6].upper()}"

        payment_doc = {
            "order_id": order_id,
            "amount": amount,
            "payment_method": payment_method,
            "status": "SUCCESS",
            "transaction_id": transaction_id,
            "created_at": datetime.now()
        }

        result = payments_collection.insert_one(payment_doc)

        # 支付成功后,通知订单服务更新状态
        try:
            order_service_url = "http://localhost:5001"
            requests.put(
                f"{order_service_url}/orders/{order_id}/status",
                json={"status": "PAID"},
                timeout=3
            )
        except:
            print(f"通知订单服务失败,订单ID: {order_id}")

        return jsonify({
            "message": "支付成功",
            "status": "SUCCESS",
            "transaction_id": transaction_id,
            "payment_id": str(result.inserted_id)
        }), 200

    except Exception as e:
        return jsonify({"error": f"支付处理失败: {str(e)}"}), 500


@app.route('/payments/<order_id>', methods=['GET'])
def get_payment(order_id):
    """查询支付记录"""
    try:
        payment = payments_collection.find_one({"order_id": order_id})
        if payment:
            payment['_id'] = str(payment['_id'])
            payment['created_at'] = payment['created_at'].isoformat()
            return jsonify({"code": 200, "data": payment}), 200
        return jsonify({"error": "支付记录不存在"}), 404
    except Exception as e:
        return jsonify({"error": str(e)}), 500


@app.route('/refund', methods=['POST'])
def process_refund():
    """处理退款"""
    data = request.json
    order_id = data.get('order_id')

    if not order_id:
        return jsonify({"error": "参数缺失"}), 400

    try:
        payment = payments_collection.find_one({"order_id": order_id})
        if not payment:
            return jsonify({"error": "支付记录不存在"}), 404

        if payment['status'] != 'SUCCESS':
            return jsonify({"error": "该订单未支付成功"}), 400

        # 模拟退款处理
        payments_collection.update_one(
            {"order_id": order_id},
            {"$set": {"status": "REFUNDED", "refund_time": datetime.now()}}
        )

        # 通知订单服务更新状态
        try:
            order_service_url = "http://localhost:5001"
            requests.put(
                f"{order_service_url}/orders/{order_id}/status",
                json={"status": "CANCELLED"},
                timeout=3
            )
        except:
            print(f"通知订单服务失败,订单ID: {order_id}")

        return jsonify({"message": "退款成功"}), 200

    except Exception as e:
        return jsonify({"error": f"退款处理失败: {str(e)}"}), 500


if __name__ == '__main__':
    register_to_nacos()
    start_heartbeat_thread()
    app.run(port=SERVICE_PORT, debug=True)

4 Nacos服务注册与发现集成

registerNacosAll.py

import requests
import threading
import time

NACOS_SERVER = "http://localhost:8848"
SERVICE_IP = "127.0.0.1"

# 服务配置列表
SERVICES = [
    {"name": "orderSvc", "port": 5001},
    {"name": "inventorySvc", "port": 5002},
    {"name": "paymentSvc", "port": 5003},
    {"name": "gateway", "port": 8080}
]


def register_service(service_name, service_port):
    """注册单个服务到Nacos"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance"
    params = {
        "serviceName": service_name,
        "ip": SERVICE_IP,
        "port": service_port,
        "weight": 1.0,
        "enable": True,
        "healthy": True,
        "metadata": '{"preserved.register.source":"PYTHON"}'
    }
    try:
        response = requests.post(url, params=params)
        if response.status_code == 200:
            print(f" 服务 {service_name} (端口:{service_port}) 注册成功")
        else:
            print(f" 服务 {service_name} 注册失败: {response.text}")
    except Exception as e:
        print(f" 连接Nacos失败: {e}")


def send_heartbeat(service_name, service_port):
    """发送心跳"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance/beat"
    params = {
        "serviceName": service_name,
        "ip": SERVICE_IP,
        "port": service_port,
        "beat": f'{{"ip":"{SERVICE_IP}","port":{service_port},"serviceName":"{service_name}"}}'
    }
    try:
        requests.put(url, params=params)
    except:
        pass


def heartbeat_loop():
    """心跳循环"""
    while True:
        for service in SERVICES:
            send_heartbeat(service["name"], service["port"])
        time.sleep(5)


if __name__ == '__main__':
    print("�� 开始注册所有服务到Nacos...")
    print(f"�� Nacos服务器地址: {NACOS_SERVER}")
    print(f"�� 本机IP: {SERVICE_IP}")
    print("-" * 50)

    # 注册所有服务
    for service in SERVICES:
        register_service(service["name"], service["port"])

    print("-" * 50)
    print(" 所有服务注册完成,开始发送心跳...")

    # 启动心跳线程
    threading.Thread(target=heartbeat_loop, daemon=True).start()

    # 保持主线程运行
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("\n�� 程序退出")

5 统一网关与前端页面设计

5.1 网关服务实现

gateway.py

from flask import Flask, request, jsonify, send_from_directory
import requests
import threading
import time

app = Flask(__name__, static_folder='static')

# 服务路由映射表(生产环境应从Nacos动态获取)
SERVICE_MAP = {
    "orderSvc": "http://localhost:5001",
    "paymentSvc": "http://localhost:5003",
    "inventorySvc": "http://localhost:5002"
}

# Nacos配置
NACOS_SERVER = "http://localhost:8848"
SERVICE_IP = "127.0.0.1"
SERVICE_PORT = 8080
SERVICE_NAME = "gateway"


def register_to_nacos():
    """向Nacos注册网关服务"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance"
    params = {
        "serviceName": SERVICE_NAME,
        "ip": SERVICE_IP,
        "port": SERVICE_PORT,
        "weight": 1.0,
        "enable": True,
        "healthy": True
    }
    try:
        response = requests.post(url, params=params)
        if response.status_code == 200:
            print(f"网关服务 {SERVICE_NAME} 注册成功")
    except Exception as e:
        print(f"连接Nacos失败: {e}")


def send_heartbeat():
    """发送心跳"""
    url = f"{NACOS_SERVER}/nacos/v1/ns/instance/beat"
    params = {
        "serviceName": SERVICE_NAME,
        "ip": SERVICE_IP,
        "port": SERVICE_PORT,
        "beat": f'{{"ip":"{SERVICE_IP}","port":{SERVICE_PORT},"serviceName":"{SERVICE_NAME}"}}'
    }
    try:
        requests.put(url, params=params)
    except:
        pass


def start_heartbeat_thread():
    """启动心跳线程"""

    def heartbeat_loop():
        while True:
            send_heartbeat()
            time.sleep(5)

    threading.Thread(target=heartbeat_loop, daemon=True).start()


@app.route('/api/<service_name>/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def proxy(service_name, path):
    """API路由转发"""
    target_url = SERVICE_MAP.get(service_name)
    if not target_url:
        return jsonify({"error": "Service not found"}), 404

    url = f"{target_url}/{path}"

    try:
        # 转发请求到目标服务
        resp = requests.request(
            method=request.method,
            url=url,
            headers={k: v for k, v in request.headers if k != 'Host'},
            data=request.get_data(),
            params=request.args,
            timeout=10
        )

        # 返回响应
        excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
        headers = [(name, value) for name, value in resp.raw.headers.items()
                   if name.lower() not in excluded_headers]

        return (resp.content, resp.status_code, headers)

    except requests.exceptions.Timeout:
        return jsonify({"error": "服务超时"}), 504
    except requests.exceptions.ConnectionError:
        return jsonify({"error": "服务不可用"}), 503
    except Exception as e:
        return jsonify({"error": f"代理请求失败: {str(e)}"}), 500


# 托管前端页面
@app.route('/')
def index():
    return send_from_directory('static', 'index.html')


@app.route('/<path:filename>')
def static_files(filename):
    return send_from_directory('static', filename)


if __name__ == '__main__':
    register_to_nacos()
    start_heartbeat_thread()
    app.run(port=SERVICE_PORT, debug=True)

5.2 前端页面设计

static/index.html

<!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: 1400px;
            margin: 0 auto;
        }

        header {
            background: white;
            border-radius: 15px;
            padding: 30px;
            margin-bottom: 30px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.1);
            text-align: center;
        }

        header h1 {
            color: #333;
            font-size: 2.5em;
            margin-bottom: 10px;
        }

        header p {
            color: #666;
            font-size: 1.1em;
        }

        .dashboard {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 30px;
            margin-bottom: 30px;
        }

        .card {
            background: white;
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.1);
            transition: transform 0.3s ease;
        }

        .card:hover {
            transform: translateY(-5px);
        }

        .card h2 {
            color: #333;
            margin-bottom: 20px;
            padding-bottom: 15px;
            border-bottom: 2px solid #f0f0f0;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .card h2 .icon {
            font-size: 1.5em;
        }

        .form-group {
            margin-bottom: 15px;
        }

        .form-group label {
            display: block;
            margin-bottom: 5px;
            color: #555;
            font-weight: 500;
        }

        .form-group input,
        .form-group select {
            width: 100%;
            padding: 10px 15px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 14px;
            transition: border-color 0.3s ease;
        }

        .form-group input:focus,
        .form-group select:focus {
            outline: none;
            border-color: #667eea;
        }

        .btn {
            padding: 10px 25px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            margin-right: 10px;
            margin-bottom: 10px;
        }

        .btn-primary {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
        }

        .btn-success {
            background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
            color: white;
        }

        .btn-success:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 20px rgba(17, 153, 142, 0.4);
        }

        .btn-danger {
            background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%);
            color: white;
        }

        .btn-danger:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 20px rgba(235, 51, 73, 0.4);
        }

        .btn-info {
            background: linear-gradient(135deg, #2193b0 0%, #6dd5ed 100%);
            color: white;
        }

        .btn-info:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 20px rgba(33, 147, 176, 0.4);
        }

        .result-area {
            margin-top: 20px;
            padding: 15px;
            background: #f8f9fa;
            border-radius: 8px;
            border: 1px solid #e0e0e0;
            max-height: 300px;
            overflow-y: auto;
        }

        .result-area pre {
            white-space: pre-wrap;
            word-wrap: break-word;
            font-family: 'Courier New', monospace;
            font-size: 13px;
            color: #333;
        }

        .status-badge {
            display: inline-block;
            padding: 3px 10px;
            border-radius: 12px;
            font-size: 12px;
            font-weight: 600;
        }

        .status-created { background: #fff3cd; color: #856404; }
        .status-paid { background: #d4edda; color: #155724; }
        .status-shipped { background: #cce5ff; color: #004085; }
        .status-delivered { background: #d1ecf1; color: #0c5460; }
        .status-cancelled { background: #f8d7da; color: #721c24; }

        .full-width {
            grid-column: 1 / -1;
        }

        .stats-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 15px;
            margin-bottom: 20px;
        }

        .stat-item {
            text-align: center;
            padding: 15px;
            background: #f8f9fa;
            border-radius: 10px;
        }

        .stat-item .number {
            font-size: 2em;
            font-weight: 700;
            color: #667eea;
        }

        .stat-item .label {
            color: #666;
            margin-top: 5px;
        }

        @media (max-width: 768px) {
            .dashboard {
                grid-template-columns: 1fr;
            }

            .stats-grid {
                grid-template-columns: 1fr;
            }

            header h1 {
                font-size: 1.8em;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>�� 电商微服务系统</h1>
            <p>基于Python + Flask + MongoDB + Nacos 构建的分布式电商平台</p>
        </header>

        <div class="stats-grid">
            <div class="stat-item">
                <div class="number" id="productCount">0</div>
                <div class="label">商品总数</div>
            </div>
            <div class="stat-item">
                <div class="number" id="orderCount">0</div>
                <div class="label">订单总数</div>
            </div>
            <div class="stat-item">
                <div class="number" id="paymentCount">0</div>
                <div class="label">支付成功数</div>
            </div>
        </div>

        <div class="dashboard">
            <!-- 商品管理 -->
            <div class="card">
                <h2><span class="icon">��</span> 商品管理</h2>
                <div class="form-group">
                    <label>商品名称</label>
                    <input type="text" id="productName" placeholder="输入商品名称搜索">
                </div>
                <div>
                    <button class="btn btn-primary" onclick="searchProducts()">�� 搜索商品</button>
                    <button class="btn btn-info" onclick="loadAllProducts()">�� 加载全部</button>
                </div>
                <div class="result-area" id="productResult">
                    <pre>点击按钮加载商品数据...</pre>
                </div>
            </div>

            <!-- 订单管理 -->
            <div class="card">
                <h2><span class="icon">��</span> 订单管理</h2>
                <div class="form-group">
                    <label>用户ID</label>
                    <input type="text" id="userId" value="user_001" placeholder="输入用户ID">
                </div>
                <div class="form-group">
                    <label>商品ID</label>
                    <input type="text" id="orderProductId" placeholder="输入商品ID">
                </div>
                <div class="form-group">
                    <label>数量</label>
                    <input type="number" id="orderQuantity" value="1" min="1">
                </div>
                <div>
                    <button class="btn btn-success" onclick="createOrder()">�� 创建订单</button>
                    <button class="btn btn-info" onclick="getUserOrders()">�� 我的订单</button>
                </div>
                <div class="result-area" id="orderResult">
                    <pre>填写信息后创建订单...</pre>
                </div>
            </div>

            <!-- 支付管理 -->
            <div class="card">
                <h2><span class="icon">��</span> 支付管理</h2>
                <div class="form-group">
                    <label>订单ID</label>
                    <input type="text" id="paymentOrderId" placeholder="输入订单ID">
                </div>
                <div class="form-group">
                    <label>支付金额</label>
                    <input type="number" id="paymentAmount" step="0.01" placeholder="输入支付金额">
                </div>
                <div class="form-group">
                    <label>支付方式</label>
                    <select id="paymentMethod">
                        <option value="credit_card">信用卡</option>
                        <option value="alipay">支付宝</option>
                        <option value="wechat">微信支付</option>
                        <option value="bank_transfer">银行转账</option>
                    </select>
                </div>
                <div>
                    <button class="btn btn-success" onclick="processPayment()">�� 支付</button>
                    <button class="btn btn-danger" onclick="processRefund()">️ 退款</button>
                    <button class="btn btn-info" onclick="checkPayment()">�� 查询支付</button>
                </div>
                <div class="result-area" id="paymentResult">
                    <pre>填写信息后进行支付操作...</pre>
                </div>
            </div>

            <!-- 库存管理 -->
            <div class="card">
                <h2><span class="icon">��</span> 库存管理</h2>
                <div class="form-group">
                    <label>商品ID</label>
                    <input type="text" id="stockProductId" placeholder="输入商品ID">
                </div>
                <div class="form-group">
                    <label>扣减数量</label>
                    <input type="number" id="stockQuantity" value="1" min="1">
                </div>
                <div>
                    <button class="btn btn-primary" onclick="checkStock()"> 检查库存</button>
                    <button class="btn btn-danger" onclick="reduceStock()">�� 扣减库存</button>
                    <button class="btn btn-success" onclick="restoreStock()">�� 恢复库存</button>
                </div>
                <div class="result-area" id="stockResult">
                    <pre>输入商品ID进行库存操作...</pre>
                </div>
            </div>
        </div>
    </div>

    <script>
        const API_BASE = '/api';

        // 显示结果
        function showResult(elementId, data) {
            const element = document.getElementById(elementId);
            element.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
        }

        // 显示错误
        function showError(elementId, error) {
            const element = document.getElementById(elementId);
            element.innerHTML = `<pre style="color: red;">错误: ${error}</pre>`;
        }

        // 加载所有商品
        function loadAllProducts() {
            fetch(`${API_BASE}/inventorySvc/products`)
                .then(response => response.json())
                .then(data => {
                    showResult('productResult', data);
                    if (data.data) {
                        document.getElementById('productCount').textContent = data.data.length;
                    }
                })
                .catch(error => showError('productResult', error));
        }

        // 搜索商品
        function searchProducts() {
            const name = document.getElementById('productName').value;
            const url = name ?
                `${API_BASE}/inventorySvc/products?name=${encodeURIComponent(name)}` :
                `${API_BASE}/inventorySvc/products`;

            fetch(url)
                .then(response => response.json())
                .then(data => showResult('productResult', data))
                .catch(error => showError('productResult', error));
        }

        // 创建订单
        function createOrder() {
            const userId = document.getElementById('userId').value;
            const productId = document.getElementById('orderProductId').value;
            const quantity = parseInt(document.getElementById('orderQuantity').value);

            if (!userId || !productId) {
                showError('orderResult', '请填写用户ID和商品ID');
                return;
            }

            fetch(`${API_BASE}/orderSvc/orders`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    user_id: userId,
                    product_id: productId,
                    quantity: quantity
                })
            })
            .then(response => response.json())
            .then(data => {
                showResult('orderResult', data);
                if (data.order_id) {
                    document.getElementById('paymentOrderId').value = data.order_id;
                    document.getElementById('paymentAmount').value = data.total_price;
                }
            })
            .catch(error => showError('orderResult', error));
        }

        // 获取用户订单
        function getUserOrders() {
            const userId = document.getElementById('userId').value;
            if (!userId) {
                showError('orderResult', '请填写用户ID');
                return;
            }

            fetch(`${API_BASE}/orderSvc/orders/user/${userId}`)
                .then(response => response.json())
                .then(data => {
                    showResult('orderResult', data);
                    if (data.data) {
                        document.getElementById('orderCount').textContent = data.data.length;
                    }
                })
                .catch(error => showError('orderResult', error));
        }

        // 处理支付
        function processPayment() {
            const orderId = document.getElementById('paymentOrderId').value;
            const amount = parseFloat(document.getElementById('paymentAmount').value);
            const paymentMethod = document.getElementById('paymentMethod').value;

            if (!orderId || !amount) {
                showError('paymentResult', '请填写订单ID和支付金额');
                return;
            }

            fetch(`${API_BASE}/paymentSvc/pay`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    order_id: orderId,
                    amount: amount,
                    payment_method: paymentMethod
                })
            })
            .then(response => response.json())
            .then(data => {
                showResult('paymentResult', data);
                if (data.status === 'SUCCESS') {
                    document.getElementById('paymentCount').textContent =
                        parseInt(document.getElementById('paymentCount').textContent) + 1;
                }
            })
            .catch(error => showError('paymentResult', error));
        }

        // 处理退款
        function processRefund() {
            const orderId = document.getElementById('paymentOrderId').value;
            if (!orderId) {
                showError('paymentResult', '请填写订单ID');
                return;
            }

            fetch(`${API_BASE}/paymentSvc/refund`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({order_id: orderId})
            })
            .then(response => response.json())
            .then(data => showResult('paymentResult', data))
            .catch(error => showError('paymentResult', error));
        }

        // 查询支付
        function checkPayment() {
            const orderId = document.getElementById('paymentOrderId').value;
            if (!orderId) {
                showError('paymentResult', '请填写订单ID');
                return;
            }

            fetch(`${API_BASE}/paymentSvc/payments/${orderId}`)
                .then(response => response.json())
                .then(data => showResult('paymentResult', data))
                .catch(error => showError('paymentResult', error));
        }

        // 检查库存
        function checkStock() {
            const productId = document.getElementById('stockProductId').value;
            if (!productId) {
                showError('stockResult', '请填写商品ID');
                return;
            }

            fetch(`${API_BASE}/inventorySvc/inventory/check`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    product_id: productId,
                    quantity: 0
                })
            })
            .then(response => response.json())
            .then(data => showResult('stockResult', data))
            .catch(error => showError('stockResult', error));
        }

        // 扣减库存
        function reduceStock() {
            const productId = document.getElementById('stockProductId').value;
            const quantity = parseInt(document.getElementById('stockQuantity').value);

            if (!productId) {
                showError('stockResult', '请填写商品ID');
                return;
            }

            fetch(`${API_BASE}/inventorySvc/inventory/check`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    product_id: productId,
                    quantity: quantity
                })
            })
            .then(response => response.json())
            .then(data => showResult('stockResult', data))
            .catch(error => showError('stockResult', error));
        }

        // 恢复库存
        function restoreStock() {
            const productId = document.getElementById('stockProductId').value;
            const quantity = parseInt(document.getElementById('stockQuantity').value);

            if (!productId) {
                showError('stockResult', '请填写商品ID');
                return;
            }

            fetch(`${API_BASE}/inventorySvc/inventory/restore`, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    product_id: productId,
                    quantity: quantity
                })
            })
            .then(response => response.json())
            .then(data => showResult('stockResult', data))
            .catch(error => showError('stockResult', error));
        }

        // 页面加载时自动加载商品
        window.onload = function() {
            loadAllProducts();
        };
    </script>
</body>
</html>

JS交互static/script.js

const API_GATEWAY = 'http://localhost:8080/api'
async function loadProducts() {    // 加载商品列表
    const response = await fetch(`${API_GATEWAY}/inventorySvc/products`)
    const products = await response.json(); const listDiv = document.getElementById('product-list')
    listDiv.innerHTML = products.data.map(p =>
        `<div class="product">
            <span>${p.name} - 库存: ${p.stock}</span>
            <button onclick="document.getElementById('product-id').value='${p.id}'">选择</button>
        </div>`
    ).join('')
}
async function createOrder() {     // 创建订单
    const userId = document.getElementById('user-id').value
    const productId = document.getElementById('product-id').value
    const response = await fetch(`${API_GATEWAY}/orderSvc/orders`, {
        method: 'POST', headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({user_id: userId, product_id: productId, quantity: 1})
    })
    const result = await response.json(); alert(result.message || result.error)
async function payOrder() {        // 支付订单
    const orderId = document.getElementById('order-id').value
    // 简化:假设支付金额固定
    const response = await fetch(`${API_GATEWAY}/paymentSvc/pay`, {
        method: 'POST', headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({order_id: orderId, amount: 100}) })
    const result = await response.json()
    alert(result.message)
}
window.onload = loadProducts      // 页面加载时初始化

6 部署运行测试

6.1 提前准备

安装python,mongodb,navicat管理器,JDK1.8以上版本,安装并运行nacos

6.2 核心依赖库清单

requirements.txt,更新后的库清单:

annotated-doc==0.0.4

annotated-types==0.7.0

anyio==4.13.0

blinker==1.9.0

certifi==2026.5.20

charset-normalizer==3.4.7

click==8.4.1

colorama==0.4.6

dnspython==2.8.0

fastapi==0.136.3

Flask==3.1.3

h11==0.16.0

idna==3.18

itsdangerous==2.2.0

Jinja2==3.1.6

MarkupSafe==3.0.3

pydantic==2.13.4

pydantic_core==2.46.4

pymongo==4.17.0

python-multipart==0.0.30

python-nacos==0.1.1

requests==2.34.2

starlette==1.2.1

typing-inspection==0.4.2

typing_extensions==4.15.0

urllib3==2.7.0

uvicorn==0.48.0

Werkzeug==3.1.8

6.3 虚拟环境操作指南

6.3.1 创建虚拟环境

Python 3.3+ 内置了 venv 模块,推荐在项目根目录下执行以下命令创建名为 venv 的虚拟环境:python -m venv venv。该命令会在当前目录下创建一个包含独立Python解释器和pip工具的文件夹。

6.3.2 激活虚拟环境

创建完成后,必须激活环境才能在其中安装依赖。在Windows命令提示符(CMD)或PowerShell中,执行:

在CMD中,venv\Scripts\activate.bat

在PowerShell中 (可能需要先执行 Set-ExecutionPolicy RemoteSigned),venv\Scripts\Activate.ps1。

激活成功后,终端提示符前会出现 (venv) 前缀,表明已进入隔离环境。

6.3.3 安装依赖

在激活的虚拟环境中,使用pip安装 requirements.txt 中列出的依赖:

pip install -r requirements.txt

此操作会将所有依赖安装到 venv\Lib\site-packages 目录下,与系统全局环境完全隔离。

6.3.4 依赖管理与导出

在开发过程中,如果新增了依赖库,应及时更新 requirements.txt 文件,以便团队协作和部署:pip freeze > requirements.txt。该命令会将当前环境所有包及其精确版本号写入文件,确保环境可复现。

6.4 部署运行与调式

6.4.1 本地开发调试

在开发阶段,可以直接使用Flask内置服务器进行调试。

调试模式:在代码中设置 app.run(debug=True),开启调试模式后,代码修改会自动重载,便于快速迭代。

运行服务:确保虚拟环境已激活,运行服务5个py文件

python orderSvc.py

python invertorySvc.py

python paymentSvc.py

python gateway.py

python registerNacosAll.py

6.4.2 进入页面

localhost:8080查看页面

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐