PUGE 2 luni în urmă
comite
e573eaf834
6 a modificat fișierele cu 913 adăugiri și 0 ștergeri
  1. 140 0
      app.py
  2. BIN
      buff_prices.db
  3. 44 0
      creatDB.py
  4. 111 0
      dmarket.py
  5. 452 0
      templates/index.html
  6. 166 0
      web.py

+ 140 - 0
app.py

@@ -0,0 +1,140 @@
+import time
+
+import requests
+import sqlite3
+from datetime import datetime
+
+
+def get_buff_data(page_num):
+    """从Buff API获取商品数据"""
+    url = "https://buff.163.com/api/market/goods"
+    querystring = {"game": "csgo","page_size": 80, "page_num": page_num, "tab": "selling"}
+    headers = {
+        "accept": "application/json, text/javascript, */*; q=0.01",
+        "accept-language": "zh-CN,zh;q=0.9",
+        "cache-control": "no-cache",
+        "dnt": "1",
+        "pragma": "no-cache",
+        "priority": "u=1, i",
+        "referer": "https://buff.163.com/market/csgo",
+        "sec-fetch-dest": "empty",
+        "sec-fetch-mode": "cors",
+        "sec-fetch-site": "same-origin",
+        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
+        "x-requested-with": "XMLHttpRequest",
+        "Cookie": "NTES_YD_SESS=9Ko5g05jJPzL0nDaqrG7L0B9n7AiGVSy0Q4Rh7lAlP3Ll.DwlvG8Q0qiKkN_BGxhLcofy9IT_mEPLnlsRxJMcT2j2ZHJdIidq2YgzyU9uC._XmVDURPonklYKRXNyoZ68AzWP5XQjf7Bz1ouZKiPQ5hfZoAcTClcSb0NUg2ijt4Ce5EYAMxsLEvFAnPKKgSjKIdOtZSpSJpOcBu11Bh9Ccum0uxDMGYj9; S_INFO=1774149977|0|0&60##|18511117532; P_INFO=18511117532|1774149977|1|netease_buff|00&99|null&null&null#xiz&540100#10#0|&0||18511117532; remember_me=U1089937100|UUBCBtmtPIUdtQZz9YmlUBjQ28AfdV8m; session=1-5oyvnvmyB29IGHS6qhrIkkZEjN-Jy2mGZ6NU34Eh3UPf2017034644; csrf_token=IjY0Mzg5OTRhYmY5MTFmNGU2MzBjZDI0ZjAwZWQxYzQ1YTdmYjEyZmUi.ab9hYA.awg5FDUiey0d1h9yU_kdomsG6lo",
+        "Accept-Encoding": "gzip, deflate, br",
+        "Connection": "keep-alive"
+    }
+    
+    response = requests.get(url, headers=headers, params=querystring)
+    return response.json()
+
+def save_to_database(data):
+    """保存数据到数据库"""
+    conn = sqlite3.connect('buff_prices.db')
+    cursor = conn.cursor()
+    current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+    
+    try:
+        items = data.get('data', {}).get('items', [])
+        count = 0
+        inserted_count = 0
+        updated_count = 0
+        
+        for item in items:
+            # 获取稀有度
+            rarity = None
+            if 'goods_info' in item and 'info' in item['goods_info']:
+                tags = item['goods_info']['info'].get('tags', {})
+                if 'rarity' in tags:
+                    rarity = tags['rarity'].get('localized_name')
+            
+            # 获取类型
+            item_type = None
+            if 'goods_info' in item and 'info' in item['goods_info']:
+                tags = item['goods_info']['info'].get('tags', {})
+                if 'type' in tags:
+                    item_type = tags['type'].get('localized_name')
+            
+            # 获取图标
+            icon_url = None
+            if 'goods_info' in item:
+                icon_url = item['goods_info'].get('icon_url')
+            
+            # 插入或更新商品信息
+            cursor.execute('''
+                INSERT INTO products (buff_id, name, market_hash_name, type, rarity, icon_url, updated_at)
+                VALUES (?, ?, ?, ?, ?, ?, ?)
+                ON CONFLICT(buff_id) DO UPDATE SET
+                    name = excluded.name,
+                    market_hash_name = excluded.market_hash_name,
+                    type = excluded.type,
+                    rarity = excluded.rarity,
+                    icon_url = excluded.icon_url,
+                    updated_at = excluded.updated_at
+            ''', (
+                item['id'],
+                item['name'],
+                item['market_hash_name'],
+                item_type,
+                rarity,
+                icon_url,
+                current_time
+            ))
+            
+            # 判断是插入还是更新
+            if cursor.rowcount == 1:
+                inserted_count += 1
+            else:
+                updated_count += 1
+            
+            # 获取刚插入或更新的商品ID
+            cursor.execute('SELECT id FROM products WHERE buff_id = ?', (item['id'],))
+            product = cursor.fetchone()
+            product_id = product[0]
+            
+            # 插入或更新Buff平台价格
+            cursor.execute('''
+                INSERT INTO platform_prices (product_id, platform_code, min_price, recorded_at)
+                VALUES (?, ?, ?, ?)
+                ON CONFLICT(product_id, platform_code) DO UPDATE SET
+                    min_price = excluded.min_price,
+                    recorded_at = excluded.recorded_at
+            ''', (
+                product_id,
+                'buff',
+                item['sell_min_price'],
+                current_time
+            ))
+            
+            count += 1
+        
+        conn.commit()
+        print(f"成功保存 {count} 条商品数据")
+        print(f"其中: 新插入 {inserted_count} 条, 更新 {updated_count} 条")
+        
+    except Exception as e:
+        conn.rollback()
+        print(f"保存数据出错: {e}")
+    finally:
+        conn.close()
+
+def main():
+    ind = 0
+    while ind < 400:
+        ind = ind + 1
+        # 获取Buff数据
+        print("正在获取Buff数据,页码:" + str(ind))
+        response_data = get_buff_data(ind)
+            
+        if response_data.get('code') == 'OK':
+            # 保存到数据库
+            save_to_database(response_data)
+        else:
+            print(f"API返回错误: {response_data.get('msg')}")
+
+        time.sleep(5)
+        
+if __name__ == "__main__":
+    main()

BIN
buff_prices.db


+ 44 - 0
creatDB.py

@@ -0,0 +1,44 @@
+import sqlite3
+
+def create_database():
+    conn = sqlite3.connect('buff_prices.db')
+    cursor = conn.cursor()
+    
+    # 商品主表
+    cursor.execute('''
+        CREATE TABLE IF NOT EXISTS products (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            buff_id INTEGER NOT NULL UNIQUE,
+            name VARCHAR(500) NOT NULL,
+            market_hash_name VARCHAR(255) NOT NULL UNIQUE,
+            type VARCHAR(100),
+            rarity VARCHAR(50),
+            icon_url TEXT,
+            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+            updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+        )
+    ''')
+    
+    # 平台价格表
+    cursor.execute('''
+        CREATE TABLE IF NOT EXISTS platform_prices (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            product_id INTEGER NOT NULL,
+            platform_code VARCHAR(20) NOT NULL,
+            min_price DECIMAL(10,2),
+            recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+            FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
+            UNIQUE(product_id, platform_code)
+        )
+    ''')
+    
+    # 创建索引
+    cursor.execute('CREATE INDEX IF NOT EXISTS idx_products_market_hash ON products(market_hash_name)')
+    cursor.execute('CREATE INDEX IF NOT EXISTS idx_platform_prices_product ON platform_prices(product_id)')
+    
+    conn.commit()
+    conn.close()
+    print("数据库创建成功")
+
+if __name__ == "__main__":
+    create_database()

+ 111 - 0
dmarket.py

@@ -0,0 +1,111 @@
+import time
+
+import requests
+import sqlite3
+from datetime import datetime
+
+def get_dmarket_data():
+    """从DMarket API获取商品数据"""
+    url = "https://api.dmarket.com/exchange/v1/market/items"
+    headers = {
+        "Accept": "application/json, text/plain, */*",
+        "Language": "ZH",
+        "Referer": "https://dmarket.com/",
+        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
+        "jkkat": "eafac24b",
+        "payment-session-id": "3a1b1e5b-2a62-420b-8064-444495216e98"
+    }
+    params = {
+        "side": "market",
+        "orderBy": "updated",
+        "orderDir": "desc",
+        "priceFrom": 0,
+        "priceTo": 0,
+        "gameId": "a8db",
+        "types": "dmarket",
+        "limit": 100,  # 每次获取100条,可根据需要调整
+        "currency": "USD",
+        "platform": "browser"
+    }
+    
+    response = requests.get(url, headers=headers, params=params)
+    return response.json()
+
+def save_to_database(data):
+    """保存DMarket数据到数据库"""
+    conn = sqlite3.connect('buff_prices.db')
+    cursor = conn.cursor()
+    current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+    
+    try:
+        items = data.get('objects', [])
+        count = 0
+        updated_count = 0
+        not_found_count = 0
+        
+        for item in items:
+            # 从DMarket数据中获取商品名称和磨损
+            item_name = item.get('extra', {}).get('name', '')
+            exterior = item.get('extra', {}).get('exterior', '')
+            
+            if not item_name:
+                continue
+            
+            # 构建market_hash_name格式
+            if exterior:
+                market_hash_name = f"{item_name} ({exterior.capitalize()})"
+            else:
+                market_hash_name = item_name
+            print(market_hash_name)
+            # 在products表中查找匹配的商品
+            cursor.execute('SELECT id FROM products WHERE market_hash_name LIKE ?', ('%' + market_hash_name,))
+            product = cursor.fetchone()
+            
+            if product:
+                product_id = product[0]
+                
+                # DMarket的price.USD单位是美分,转换为美元
+                price_usd = item.get('price', {}).get('USD', '')
+                if price_usd:
+                    min_price = float(price_usd) / 100
+                else:
+                    continue
+                
+                # 插入或更新DMarket平台价格
+                cursor.execute('''
+                    INSERT INTO platform_prices (product_id, platform_code, min_price, recorded_at, currency)
+                    VALUES (?, ?, ?, ?, 'USD')
+                    ON CONFLICT(product_id, platform_code) DO UPDATE SET
+                        min_price = excluded.min_price,
+                        recorded_at = excluded.recorded_at
+                ''', (product_id, 'dmarket', min_price, current_time))
+                
+                updated_count += 1
+            else:
+                not_found_count += 1
+            
+            count += 1
+        
+        conn.commit()
+        print(f"处理了 {count} 条DMarket商品数据")
+        print(f"成功更新 {updated_count} 条价格")
+        print(f"未匹配到商品 {not_found_count} 条")
+        
+    except Exception as e:
+        conn.rollback()
+        print(f"保存数据出错: {e}")
+    finally:
+        conn.close()
+
+def main():
+    while True:
+        response_data = get_dmarket_data()
+        
+        if response_data and 'objects' in response_data:
+            save_to_database(response_data)
+        else:
+            print("API返回数据异常")
+        time.sleep(5)
+
+if __name__ == "__main__":
+    main()

+ 452 - 0
templates/index.html

@@ -0,0 +1,452 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>CSGO 商品比价系统</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+        
+        body {
+            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+            background: #f5f5f5;
+            padding: 20px;
+        }
+        
+        .container {
+            max-width: 1400px;
+            margin: 0 auto;
+            background: white;
+            border-radius: 12px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+            overflow: hidden;
+        }
+        
+        .header {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            padding: 20px 30px;
+        }
+        
+        .header h1 {
+            font-size: 24px;
+            margin-bottom: 10px;
+        }
+        
+        .controls {
+            padding: 20px 30px;
+            background: #f8f9fa;
+            border-bottom: 1px solid #e9ecef;
+            display: flex;
+            gap: 15px;
+            flex-wrap: wrap;
+            align-items: flex-end;
+        }
+        
+        .control-group {
+            flex: 1;
+            min-width: 150px;
+        }
+        
+        .control-group label {
+            display: block;
+            font-size: 12px;
+            color: #6c757d;
+            margin-bottom: 5px;
+        }
+        
+        .control-group input,
+        .control-group select {
+            width: 100%;
+            padding: 8px 12px;
+            border: 1px solid #dee2e6;
+            border-radius: 6px;
+            font-size: 14px;
+        }
+        
+        .exchange-group {
+            display: flex;
+            gap: 10px;
+        }
+        
+        .exchange-group input {
+            flex: 1;
+        }
+        
+        .exchange-group button {
+            padding: 8px 15px;
+            background: #28a745;
+            color: white;
+            border: none;
+            border-radius: 6px;
+            cursor: pointer;
+        }
+        
+        .exchange-group button:hover {
+            background: #218838;
+        }
+        
+        .stats {
+            padding: 15px 30px;
+            background: #e9ecef;
+            font-size: 14px;
+            color: #495057;
+            display: flex;
+            gap: 20px;
+            flex-wrap: wrap;
+        }
+        
+        .stats span {
+            font-weight: bold;
+            color: #667eea;
+        }
+        
+        table {
+            width: 100%;
+            border-collapse: collapse;
+        }
+        
+        th, td {
+            padding: 12px 15px;
+            text-align: left;
+            border-bottom: 1px solid #e9ecef;
+        }
+        
+        th {
+            background: #f8f9fa;
+            font-weight: 600;
+            color: #495057;
+            cursor: pointer;
+            user-select: none;
+        }
+        
+        th:hover {
+            background: #e9ecef;
+        }
+        
+        th.active {
+            color: #667eea;
+        }
+        
+        tr:hover {
+            background: #f8f9fa;
+        }
+        
+        .price {
+            font-weight: 600;
+            color: #28a745;
+        }
+        
+        .price-missing {
+            color: #dc3545;
+            font-size: 12px;
+        }
+        
+        .item-name {
+            font-weight: 500;
+            max-width: 300px;
+            word-break: break-word;
+        }
+        
+        .item-type {
+            font-size: 12px;
+            color: #6c757d;
+        }
+        
+        .rarity {
+            display: inline-block;
+            padding: 2px 8px;
+            border-radius: 4px;
+            font-size: 12px;
+            background: #e9ecef;
+        }
+        
+        .pagination {
+            padding: 20px 30px;
+            display: flex;
+            justify-content: center;
+            gap: 10px;
+        }
+        
+        .pagination button {
+            padding: 8px 15px;
+            border: 1px solid #dee2e6;
+            background: white;
+            border-radius: 6px;
+            cursor: pointer;
+        }
+        
+        .pagination button:hover:not(:disabled) {
+            background: #667eea;
+            color: white;
+            border-color: #667eea;
+        }
+        
+        .pagination button:disabled {
+            opacity: 0.5;
+            cursor: not-allowed;
+        }
+        
+        .pagination .active {
+            background: #667eea;
+            color: white;
+            border-color: #667eea;
+        }
+        
+        .loading {
+            text-align: center;
+            padding: 50px;
+            color: #6c757d;
+        }
+        
+        .search-box {
+            flex: 1;
+        }
+        
+        .icon-img {
+            width: 40px;
+            height: 40px;
+            object-fit: cover;
+            border-radius: 4px;
+        }
+        
+        @media (max-width: 768px) {
+            .controls {
+                flex-direction: column;
+            }
+            
+            th, td {
+                padding: 8px 10px;
+                font-size: 12px;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <div class="header">
+            <h1>🎮 CSGO 商品比价系统</h1>
+            <p>对比 Buff、Steam、CSFloat、DMarket 平台价格</p>
+        </div>
+        
+        <div class="controls">
+            <div class="control-group search-box">
+                <label>🔍 搜索商品 (market_hash_name)</label>
+                <input type="text" id="searchInput" placeholder="输入商品名称或哈希名称...">
+            </div>
+            
+            <div class="control-group">
+                <label>📊 排序方式</label>
+                <select id="sortSelect">
+                    <option value="updated_at">最新更新</option>
+                    <option value="buff_price">Buff 价格</option>
+                    <option value="steam_price">Steam 价格</option>
+                    <option value="csfloat_price">CSFloat 价格</option>
+                    <option value="dmarket_price">DMarket 价格</option>
+                </select>
+            </div>
+            
+            <div class="control-group">
+                <label>⬆️⬇️ 排序顺序</label>
+                <select id="orderSelect">
+                    <option value="desc">降序</option>
+                    <option value="asc">升序</option>
+                </select>
+            </div>
+            
+            <div class="control-group exchange-group">
+                <label>💱 美元汇率 (USD → CNY)</label>
+                <input type="number" id="exchangeRate" step="0.01" value="7.2">
+                <button id="setRateBtn">设置</button>
+            </div>
+        </div>
+        
+        <div class="stats" id="stats">
+            <div>加载中...</div>
+        </div>
+        
+        <div style="overflow-x: auto;">
+            <table id="productTable">
+                <thead>
+                    <tr>
+                        <th>图标</th>
+                        <th>商品名称 / 类型</th>
+                        <th>稀有度</th>
+                        <th>Buff 价格 (CNY)</th>
+                        <th>Steam 价格 (CNY)</th>
+                        <th>CSFloat 价格 (CNY)</th>
+                        <th>DMarket 价格 (CNY)</th>
+                        <th>更新时间</th>
+                    </tr>
+                </thead>
+                <tbody id="tableBody">
+                    <tr><td colspan="8" class="loading">加载中...</td></tr>
+                </tbody>
+            </table>
+        </div>
+        
+        <div class="pagination" id="pagination"></div>
+    </div>
+    
+    <script>
+        let currentPage = 1;
+        let totalPages = 1;
+        let currentRate = 7.2;
+        
+        function loadProducts() {
+            const search = document.getElementById('searchInput').value;
+            const sort = document.getElementById('sortSelect').value;
+            const order = document.getElementById('orderSelect').value;
+            
+            fetch(`/api/products?page=${currentPage}&search=${encodeURIComponent(search)}&sort=${sort}&order=${order}`)
+                .then(res => res.json())
+                .then(data => {
+                    renderTable(data.products);
+                    renderPagination(data);
+                    currentRate = data.exchange_rate;
+                    document.getElementById('exchangeRate').value = currentRate;
+                })
+                .catch(err => console.error('加载失败:', err));
+        }
+        
+        function renderTable(products) {
+            const tbody = document.getElementById('tableBody');
+            
+            if (!products || products.length === 0) {
+                tbody.innerHTML = '<tr><td colspan="8" class="loading">暂无数据</td></tr>';
+                return;
+            }
+            
+            tbody.innerHTML = products.map(p => `
+                <tr>
+                    <td>${p.icon_url ? `<img src="${p.icon_url}" class="icon-img" onerror="this.style.display='none'">` : '-'}</td>
+                    <td class="item-name">
+                        ${escapeHtml(p.name)}
+                        <div class="item-type">${p.type || '未知类型'}</div>
+                        <div style="font-size: 11px; color: #999;">${escapeHtml(p.market_hash_name)}</div>
+                    </td>
+                    <td><span class="rarity">${p.rarity || '普通'}</span></td>
+                    <td class="price">${formatPrice(p.buff_price_cny)}</td>
+                    <td class="price">${formatPrice(p.steam_price_cny)}</td>
+                    <td class="price">${formatPrice(p.csfloat_price_cny)}</td>
+                    <td class="price">${formatPrice(p.dmarket_price_cny)}</td>
+                    <td style="font-size: 12px;">${formatDate(p.updated_at)}</td>
+                </tr>
+            `).join('');
+        }
+        
+        function formatPrice(price) {
+            if (!price || price === 0) return '<span class="price-missing">无数据</span>';
+            return `¥${parseFloat(price).toFixed(2)}`;
+        }
+        
+        function formatDate(dateStr) {
+            if (!dateStr) return '-';
+            const date = new Date(dateStr);
+            return date.toLocaleString('zh-CN');
+        }
+        
+        function renderPagination(data) {
+            totalPages = data.total_pages;
+            const paginationDiv = document.getElementById('pagination');
+            
+            if (totalPages <= 1) {
+                paginationDiv.innerHTML = '';
+                return;
+            }
+            
+            let html = '';
+            html += `<button ${currentPage === 1 ? 'disabled' : ''} onclick="goPage(1)">首页</button>`;
+            html += `<button ${currentPage === 1 ? 'disabled' : ''} onclick="goPage(${currentPage - 1})">上一页</button>`;
+            
+            let start = Math.max(1, currentPage - 2);
+            let end = Math.min(totalPages, currentPage + 2);
+            
+            for (let i = start; i <= end; i++) {
+                html += `<button class="${i === currentPage ? 'active' : ''}" onclick="goPage(${i})">${i}</button>`;
+            }
+            
+            html += `<button ${currentPage === totalPages ? 'disabled' : ''} onclick="goPage(${currentPage + 1})">下一页</button>`;
+            html += `<button ${currentPage === totalPages ? 'disabled' : ''} onclick="goPage(${totalPages})">末页</button>`;
+            
+            paginationDiv.innerHTML = html;
+        }
+        
+        function goPage(page) {
+            currentPage = page;
+            loadProducts();
+        }
+        
+        function loadStatistics() {
+            fetch('/api/statistics')
+                .then(res => res.json())
+                .then(data => {
+                    document.getElementById('stats').innerHTML = `
+                        <div>📦 商品总数: <span>${data.total_products}</span></div>
+                        <div>💰 Buff 有价: <span>${data.buff_count || 0}</span></div>
+                        <div>💰 Steam 有价: <span>${data.steam_count || 0}</span></div>
+                        <div>💰 CSFloat 有价: <span>${data.csfloat_count || 0}</span></div>
+                        <div>💰 DMarket 有价: <span>${data.dmarket_count || 0}</span></div>
+                    `;
+                });
+        }
+        
+        function setExchangeRate() {
+            const rate = parseFloat(document.getElementById('exchangeRate').value);
+            if (isNaN(rate) || rate <= 0) {
+                alert('请输入有效的汇率');
+                return;
+            }
+            
+            fetch('/api/set_exchange_rate', {
+                method: 'POST',
+                headers: {'Content-Type': 'application/json'},
+                body: JSON.stringify({rate: rate})
+            })
+            .then(res => res.json())
+            .then(data => {
+                if (data.success) {
+                    currentRate = data.rate;
+                    loadProducts();
+                }
+            });
+        }
+        
+        function escapeHtml(str) {
+            if (!str) return '';
+            return str.replace(/[&<>]/g, function(m) {
+                if (m === '&') return '&amp;';
+                if (m === '<') return '&lt;';
+                if (m === '>') return '&gt;';
+                return m;
+            });
+        }
+        
+        // 事件监听
+        document.getElementById('searchInput').addEventListener('input', () => {
+            currentPage = 1;
+            loadProducts();
+        });
+        document.getElementById('sortSelect').addEventListener('change', () => {
+            currentPage = 1;
+            loadProducts();
+        });
+        document.getElementById('orderSelect').addEventListener('change', () => {
+            currentPage = 1;
+            loadProducts();
+        });
+        document.getElementById('setRateBtn').addEventListener('click', setExchangeRate);
+        
+        // 初始加载
+        loadProducts();
+        loadStatistics();
+        
+        // 每5分钟刷新统计
+        setInterval(loadStatistics, 300000);
+    </script>
+</body>
+</html>

+ 166 - 0
web.py

@@ -0,0 +1,166 @@
+from flask import Flask, render_template, request, jsonify
+import sqlite3
+import json
+
+app = Flask(__name__)
+
+# 汇率缓存(前端传入后保存在内存)
+exchange_rate = 7.2  # 默认汇率
+
+def get_db():
+    """获取数据库连接"""
+    conn = sqlite3.connect('buff_prices.db')
+    conn.row_factory = sqlite3.Row
+    return conn
+
+@app.route('/')
+def index():
+    """首页"""
+    return render_template('index.html')
+
+@app.route('/api/products')
+def get_products():
+    """获取商品列表,支持排序和搜索"""
+    conn = get_db()
+    cursor = conn.cursor()
+    
+    # 获取参数
+    sort_by = request.args.get('sort', 'updated_at')
+    sort_order = request.args.get('order', 'desc')
+    search = request.args.get('search', '')
+    page = int(request.args.get('page', 1))
+    per_page = int(request.args.get('per_page', 50))
+    
+    # 构建搜索条件
+    where_clause = ""
+    params = []
+    if search:
+        where_clause = "WHERE p.market_hash_name LIKE ? OR p.name LIKE ?"
+        search_pattern = f"%{search}%"
+        params = [search_pattern, search_pattern]
+    
+    # 构建排序
+    order_clause = "ORDER BY "
+    if sort_by == 'buff_price':
+        order_clause += "buff_price"
+    elif sort_by == 'steam_price':
+        order_clause += "steam_price"
+    elif sort_by == 'csfloat_price':
+        order_clause += "csfloat_price"
+    elif sort_by == 'dmarket_price':
+        order_clause += "dmarket_price"
+    else:
+        order_clause += "p.updated_at"
+    
+    order_clause += f" {sort_order}"
+    
+    # 获取总数
+    count_query = f"SELECT COUNT(*) as total FROM products p {where_clause}"
+    cursor.execute(count_query, params)
+    total = cursor.fetchone()['total']
+    
+    # 分页查询
+    offset = (page - 1) * per_page
+    query = f'''
+        SELECT 
+            p.id,
+            p.buff_id,
+            p.name,
+            p.market_hash_name,
+            p.type,
+            p.rarity,
+            p.icon_url,
+            p.updated_at,
+            pp_buff.min_price as buff_price,
+            pp_buff.currency as buff_currency,
+            pp_steam.min_price as steam_price,
+            pp_steam.currency as steam_currency,
+            pp_csfloat.min_price as csfloat_price,
+            pp_csfloat.currency as csfloat_currency,
+            pp_dmarket.min_price as dmarket_price,
+            pp_dmarket.currency as dmarket_currency
+        FROM products p
+        LEFT JOIN platform_prices pp_buff ON p.id = pp_buff.product_id AND pp_buff.platform_code = 'buff'
+        LEFT JOIN platform_prices pp_steam ON p.id = pp_steam.product_id AND pp_steam.platform_code = 'steam'
+        LEFT JOIN platform_prices pp_csfloat ON p.id = pp_csfloat.product_id AND pp_csfloat.platform_code = 'csfloat'
+        LEFT JOIN platform_prices pp_dmarket ON p.id = pp_dmarket.product_id AND pp_dmarket.platform_code = 'dmarket'
+        {where_clause}
+        {order_clause}
+        LIMIT ? OFFSET ?
+    '''
+    
+    params.extend([per_page, offset])
+    cursor.execute(query, params)
+    products = cursor.fetchall()
+    conn.close()
+    
+    # 转换为字典列表并应用汇率
+    result = []
+    for p in products:
+        product_dict = dict(p)
+        
+        # 应用汇率换算
+        if product_dict.get('buff_currency') == 'USD' and product_dict.get('buff_price'):
+            product_dict['buff_price_cny'] = round(product_dict['buff_price'] * exchange_rate, 2)
+        else:
+            product_dict['buff_price_cny'] = product_dict.get('buff_price')
+        
+        if product_dict.get('steam_currency') == 'USD' and product_dict.get('steam_price'):
+            product_dict['steam_price_cny'] = round(product_dict['steam_price'] * exchange_rate, 2)
+        else:
+            product_dict['steam_price_cny'] = product_dict.get('steam_price')
+        
+        if product_dict.get('csfloat_currency') == 'USD' and product_dict.get('csfloat_price'):
+            product_dict['csfloat_price_cny'] = round(product_dict['csfloat_price'] * exchange_rate, 2)
+        else:
+            product_dict['csfloat_price_cny'] = product_dict.get('csfloat_price')
+        
+        if product_dict.get('dmarket_currency') == 'USD' and product_dict.get('dmarket_price'):
+            product_dict['dmarket_price_cny'] = round(product_dict['dmarket_price'] * exchange_rate, 2)
+        else:
+            product_dict['dmarket_price_cny'] = product_dict.get('dmarket_price')
+        
+        result.append(product_dict)
+    
+    return jsonify({
+        'products': result,
+        'total': total,
+        'page': page,
+        'per_page': per_page,
+        'total_pages': (total + per_page - 1) // per_page,
+        'exchange_rate': exchange_rate
+    })
+
+@app.route('/api/set_exchange_rate', methods=['POST'])
+def set_exchange_rate():
+    """设置汇率"""
+    global exchange_rate
+    data = request.get_json()
+    exchange_rate = float(data.get('rate', 7.2))
+    return jsonify({'success': True, 'rate': exchange_rate})
+
+@app.route('/api/statistics')
+def get_statistics():
+    """获取统计信息"""
+    conn = get_db()
+    cursor = conn.cursor()
+    
+    # 商品总数
+    cursor.execute('SELECT COUNT(*) as total FROM products')
+    total_products = cursor.fetchone()['total']
+    
+    # 各平台价格数量
+    stats = {'total_products': total_products}
+    for platform in ['buff', 'steam', 'csfloat', 'dmarket']:
+        cursor.execute('''
+            SELECT COUNT(DISTINCT product_id) as count 
+            FROM platform_prices 
+            WHERE platform_code = ?
+        ''', (platform,))
+        stats[f'{platform}_count'] = cursor.fetchone()['count']
+    
+    conn.close()
+    return jsonify(stats)
+
+if __name__ == '__main__':
+    app.run(debug=True, host='0.0.0.0', port=5001)