博客 / 詳情

返回

企業/個人髮卡網源碼:零基礎快速搭建獨立卡密系統

系統概述與技術選型

在數字化時代,卡密系統已成為虛擬商品交易、會員服務、軟件激活等領域的重要工具。本文將詳細介紹如何從零開始搭建一個完整的獨立卡密系統,包含完整的源代碼和實現思路。

技術棧選擇:

  • 源碼及演示:fakaysw.top
  • 前端:HTML5 + CSS3 + JavaScript + Bootstrap 5
  • 後端:PHP 7.4+ (兼顧兼容性和性能)
  • 數據庫:MySQL 5.7+ 或 MariaDB
  • 安全機制:PDO預處理、密碼哈希、令牌驗證

數據庫設計

首先設計系統數據庫結構,這是整個系統的基礎:

-- 創建數據庫
CREATE DATABASE IF NOT EXISTS `card_system` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `card_system`;

-- 管理員表
CREATE TABLE `admins` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(255) NOT NULL,
  `email` varchar(100) DEFAULT NULL,
  `last_login` datetime DEFAULT NULL,
  `created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 商品分類表
CREATE TABLE `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `description` text,
  `sort_order` int(11) DEFAULT 0,
  `status` tinyint(1) DEFAULT 1,
  `created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 商品表
CREATE TABLE `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `category_id` int(11) DEFAULT NULL,
  `name` varchar(200) NOT NULL,
  `description` text,
  `price` decimal(10,2) NOT NULL,
  `stock` int(11) NOT NULL DEFAULT 0,
  `auto_delivery` tinyint(1) DEFAULT 1,
  `status` tinyint(1) DEFAULT 1,
  `created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `category_id` (`category_id`),
  CONSTRAINT `products_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 卡密表
CREATE TABLE `cards` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) NOT NULL,
  `card_number` varchar(100) NOT NULL,
  `card_password` varchar(100) DEFAULT NULL,
  `secret_key` varchar(32) DEFAULT NULL,
  `status` tinyint(1) DEFAULT 0 COMMENT '0:未售,1:已售,2:已使用',
  `sold_at` datetime DEFAULT NULL,
  `used_at` datetime DEFAULT NULL,
  `created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `card_number` (`card_number`),
  KEY `product_id` (`product_id`),
  KEY `status` (`status`),
  CONSTRAINT `cards_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 訂單表
CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_no` varchar(50) NOT NULL,
  `product_id` int(11) NOT NULL,
  `product_name` varchar(200) NOT NULL,
  `price` decimal(10,2) NOT NULL,
  `contact` varchar(100) NOT NULL,
  `card_id` int(11) DEFAULT NULL,
  `card_info` text,
  `status` tinyint(1) DEFAULT 0 COMMENT '0:待處理,1:已完成,2:已取消',
  `ip_address` varchar(45) DEFAULT NULL,
  `paid_at` datetime DEFAULT NULL,
  `created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `order_no` (`order_no`),
  KEY `product_id` (`product_id`),
  KEY `card_id` (`card_id`),
  KEY `contact` (`contact`),
  CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`),
  CONSTRAINT `orders_ibfk_2` FOREIGN KEY (`card_id`) REFERENCES `cards` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 系統配置表
CREATE TABLE `settings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `setting_key` varchar(50) NOT NULL,
  `setting_value` text,
  `description` varchar(200) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `setting_key` (`setting_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入默認管理員 (密碼: admin123,實際使用時應更改)
INSERT INTO `admins` (`username`, `password`) VALUES 
('admin', '$2y$10$YourHashedPasswordHere');

-- 插入默認配置
INSERT INTO `settings` (`setting_key`, `setting_value`, `description`) VALUES
('site_name', '我的髮卡網', '網站名稱'),
('site_url', 'http://localhost', '網站地址'),
('currency', '¥', '貨幣符號'),
('contact_email', 'admin@example.com', '聯繫郵箱');

核心配置文件

創建系統配置文件,確保安全性和可維護性:

<?php
// config.php - 核心配置文件

// 錯誤報告設置
error_reporting(E_ALL);
ini_set('display_errors', 0); // 生產環境設為0
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/logs/error.log');

// 時區設置
date_default_timezone_set('Asia/Shanghai');

// 數據庫配置
define('DB_HOST', 'localhost');
define('DB_NAME', 'card_system');
define('DB_USER', 'your_username');
define('DB_PASS', 'your_password');
define('DB_CHARSET', 'utf8mb4');

// 安全配置
define('SALT_KEY', 'your_secret_salt_key_here_change_me');
define('TOKEN_EXPIRE', 3600); // token過期時間1小時
define('MAX_LOGIN_ATTEMPTS', 5); // 最大登錄嘗試次數
define('LOCKOUT_TIME', 900); // 鎖定時間15分鐘

// 路徑配置
define('BASE_URL', 'http://yourdomain.com');
define('ADMIN_PATH', '/admin');
define('UPLOAD_PATH', __DIR__ . '/uploads/');

// 自動加載函數
spl_autoload_register(function ($class) {
    $file = __DIR__ . '/classes/' . str_replace('\\', '/', $class) . '.php';
    if (file_exists($file)) {
        require_once $file;
    }
});

// 啓動Session
session_start();

// CSRF保護
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// 數據庫連接類
class Database {
    private static $instance = null;
    private $connection;
    
    private function __construct() {
        try {
            $dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
            $options = [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES => false,
            ];
            $this->connection = new PDO($dsn, DB_USER, DB_PASS, $options);
        } catch (PDOException $e) {
            error_log("Database connection failed: " . $e->getMessage());
            die("系統維護中,請稍後再試。");
        }
    }
    
    public static function getInstance() {
        if (self::$instance == null) {
            self::$instance = new Database();
        }
        return self::$instance->connection;
    }
}

// 通用函數
function sanitize_input($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
    return $data;
}

function generate_order_no() {
    return date('YmdHis') . str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
}

function encrypt_card($data, $key) {
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $encrypted = openssl_encrypt($data, 'aes-256-cbc', $key, 0, $iv);
    return base64_encode($encrypted . '::' . $iv);
}

function decrypt_card($data, $key) {
    list($encrypted_data, $iv) = explode('::', base64_decode($data), 2);
    return openssl_decrypt($encrypted_data, 'aes-256-cbc', $key, 0, $iv);
}
?>

後台管理系統

1、管理員登錄系統

<?php
// admin/login.php
require_once '../config.php';

// 如果已登錄,跳轉到後台首頁
if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true) {
    header('Location: index.php');
    exit;
}

$error = '';
$login_attempts = $_SESSION['login_attempts'] ?? 0;
$lockout_time = $_SESSION['lockout_time'] ?? 0;

// 檢查是否被鎖定
if ($login_attempts >= MAX_LOGIN_ATTEMPTS && (time() - $lockout_time) < LOCKOUT_TIME) {
    $remaining = LOCKOUT_TIME - (time() - $lockout_time);
    $error = "登錄嘗試過多,請等待 " . ceil($remaining / 60) . " 分鐘後再試。";
} elseif ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['csrf_token']) && $_POST['csrf_token'] === $_SESSION['csrf_token']) {
    $username = sanitize_input($_POST['username']);
    $password = $_POST['password'];
    
    try {
        $db = Database::getInstance();
        $stmt = $db->prepare("SELECT * FROM admins WHERE username = ? LIMIT 1");
        $stmt->execute([$username]);
        $admin = $stmt->fetch();
        
        if ($admin && password_verify($password, $admin['password'])) {
            // 登錄成功
            $_SESSION['admin_logged_in'] = true;
            $_SESSION['admin_id'] = $admin['id'];
            $_SESSION['admin_username'] = $admin['username'];
            $_SESSION['login_attempts'] = 0;
            $_SESSION['lockout_time'] = 0;
            
            // 更新最後登錄時間
            $update_stmt = $db->prepare("UPDATE admins SET last_login = NOW() WHERE id = ?");
            $update_stmt->execute([$admin['id']]);
            
            // 記錄登錄日誌
            $log_stmt = $db->prepare("INSERT INTO login_logs (admin_id, ip_address, user_agent, success) VALUES (?, ?, ?, 1)");
            $log_stmt->execute([$admin['id'], $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT']]);
            
            header('Location: index.php');
            exit;
        } else {
            // 登錄失敗
            $login_attempts = ($_SESSION['login_attempts'] ?? 0) + 1;
            $_SESSION['login_attempts'] = $login_attempts;
            
            if ($login_attempts >= MAX_LOGIN_ATTEMPTS) {
                $_SESSION['lockout_time'] = time();
            }
            
            $error = "用户名或密碼錯誤!";
            
            // 記錄失敗日誌
            if ($admin) {
                $log_stmt = $db->prepare("INSERT INTO login_logs (admin_id, ip_address, user_agent, success) VALUES (?, ?, ?, 0)");
                $log_stmt->execute([$admin['id'], $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT']]);
            }
        }
    } catch (PDOException $e) {
        error_log("Login error: " . $e->getMessage());
        $error = "系統錯誤,請稍後再試。";
    }
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>管理員登錄 - 髮卡系統</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        body { background: #f5f5f5; }
        .login-container { max-width: 400px; margin: 100px auto; padding: 20px; background: white; border-radius: 10px; box-shadow: 0 0 20px rgba(0,0,0,0.1); }
    </style>
</head>
<body>
    <div class="container">
        <div class="login-container">
            <h2 class="text-center mb-4">髮卡系統管理後台</h2>
            <?php if ($error): ?>
                <div class="alert alert-danger"><?php echo $error; ?></div>
            <?php endif; ?>
            <form method="POST" action="">
                <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
                <div class="mb-3">
                    <label for="username" class="form-label">用户名</label>
                    <input type="text" class="form-control" id="username" name="username" required>
                </div>
                <div class="mb-3">
                    <label for="password" class="form-label">密碼</label>
                    <input type="password" class="form-control" id="password" name="password" required>
                </div>
                <button type="submit" class="btn btn-primary w-100">登錄</button>
            </form>
        </div>
    </div>
</body>
</html>

2、後台主界面

<?php
// admin/index.php
require_once '../config.php';

// 檢查登錄狀態
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
    header('Location: login.php');
    exit;
}

// 獲取統計信息
try {
    $db = Database::getInstance();
    
    // 今日統計
    $today_start = date('Y-m-d 00:00:00');
    $today_end = date('Y-m-d 23:59:59');
    
    $stats = [
        'total_products' => $db->query("SELECT COUNT(*) FROM products WHERE status = 1")->fetchColumn(),
        'total_cards' => $db->query("SELECT COUNT(*) FROM cards")->fetchColumn(),
        'available_cards' => $db->query("SELECT COUNT(*) FROM cards WHERE status = 0")->fetchColumn(),
        'sold_cards' => $db->query("SELECT COUNT(*) FROM cards WHERE status = 1")->fetchColumn(),
        'today_orders' => $db->query("SELECT COUNT(*) FROM orders WHERE created_at BETWEEN '{$today_start}' AND '{$today_end}'")->fetchColumn(),
        'today_income' => $db->query("SELECT SUM(price) FROM orders WHERE status = 1 AND created_at BETWEEN '{$today_start}' AND '{$today_end}'")->fetchColumn() ?: 0,
    ];
    
    // 最近訂單
    $recent_orders = $db->query("
        SELECT o.*, p.name as product_name 
        FROM orders o 
        LEFT JOIN products p ON o.product_id = p.id 
        ORDER BY o.created_at DESC 
        LIMIT 10
    ")->fetchAll();
    
} catch (PDOException $e) {
    die("數據庫查詢失敗: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>控制面板 - 髮卡系統</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
    <style>
        .sidebar { min-height: 100vh; }
        .stat-card { transition: transform 0.3s; }
        .stat-card:hover { transform: translateY(-5px); }
    </style>
</head>
<body>
    <div class="container-fluid">
        <div class="row">
            <!-- 側邊欄 -->
            <nav class="col-md-2 d-md-block bg-dark sidebar min-vh-100">
                <div class="position-sticky pt-3">
                    <h4 class="text-white px-3">髮卡系統</h4>
                    <ul class="nav flex-column">
                        <li class="nav-item">
                            <a class="nav-link active text-white" href="index.php">
                                <i class="bi bi-speedometer2"></i> 控制面板
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-white" href="products.php">
                                <i class="bi bi-box"></i> 商品管理
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-white" href="cards.php">
                                <i class="bi bi-credit-card"></i> 卡密管理
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-white" href="orders.php">
                                <i class="bi bi-receipt"></i> 訂單管理
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-white" href="settings.php">
                                <i class="bi bi-gear"></i> 系統設置
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-danger" href="logout.php">
                                <i class="bi bi-box-arrow-right"></i> 退出登錄
                            </a>
                        </li>
                    </ul>
                </div>
            </nav>
            
            <!-- 主內容區 -->
            <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 py-4">
                <h2>控制面板</h2>
                <div class="row my-4">
                    <!-- 統計卡片 -->
                    <div class="col-md-3 mb-3">
                        <div class="card stat-card bg-primary text-white">
                            <div class="card-body">
                                <h5 class="card-title">上架商品</h5>
                                <h2 class="card-text"><?php echo $stats['total_products']; ?></h2>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-3 mb-3">
                        <div class="card stat-card bg-success text-white">
                            <div class="card-body">
                                <h5 class="card-title">總卡密數</h5>
                                <h2 class="card-text"><?php echo $stats['total_cards']; ?></h2>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-3 mb-3">
                        <div class="card stat-card bg-info text-white">
                            <div class="card-body">
                                <h5 class="card-title">可售卡密</h5>
                                <h2 class="card-text"><?php echo $stats['available_cards']; ?></h2>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-3 mb-3">
                        <div class="card stat-card bg-warning text-white">
                            <div class="card-body">
                                <h5 class="card-title">今日訂單</h5>
                                <h2 class="card-text"><?php echo $stats['today_orders']; ?></h2>
                            </div>
                        </div>
                    </div>
                </div>
                
                <!-- 最近訂單 -->
                <div class="card">
                    <div class="card-header">
                        <h5 class="mb-0">最近訂單</h5>
                    </div>
                    <div class="card-body">
                        <div class="table-responsive">
                            <table class="table table-hover">
                                <thead>
                                    <tr>
                                        <th>訂單號</th>
                                        <th>商品名稱</th>
                                        <th>價格</th>
                                        <th>聯繫方式</th>
                                        <th>狀態</th>
                                        <th>下單時間</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <?php foreach ($recent_orders as $order): ?>
                                    <tr>
                                        <td><?php echo $order['order_no']; ?></td>
                                        <td><?php echo htmlspecialchars($order['product_name']); ?></td>
                                        <td>¥<?php echo $order['price']; ?></td>
                                        <td><?php echo htmlspecialchars($order['contact']); ?></td>
                                        <td>
                                            <?php 
                                            $status_labels = ['待處理', '已完成', '已取消'];
                                            $status_classes = ['warning', 'success', 'secondary'];
                                            echo '<span class="badge bg-' . $status_classes[$order['status']] . '">' . $status_labels[$order['status']] . '</span>';
                                            ?>
                                        </td>
                                        <td><?php echo $order['created_at']; ?></td>
                                    </tr>
                                    <?php endforeach; ?>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </main>
        </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

卡密API接口

<?php
// api/card.php
require_once '../config.php';

header('Content-Type: application/json; charset=utf-8');

// 允許跨域請求
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: Content-Type');

$response = ['code' => 0, 'message' => '', 'data' => null];

try {
    $db = Database::getInstance();
    
    $action = $_GET['action'] ?? '';
    
    switch ($action) {
        case 'get_products':
            // 獲取商品列表
            $stmt = $db->query("
                SELECT p.*, c.name as category_name 
                FROM products p 
                LEFT JOIN categories c ON p.category_id = c.id 
                WHERE p.status = 1 
                ORDER BY p.id DESC
            ");
            $products = $stmt->fetchAll();
            
            $response['code'] = 1;
            $response['data'] = $products;
            break;
            
        case 'create_order':
            // 創建訂單
            if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
                throw new Exception('非法請求方法');
            }
            
            $product_id = intval($_POST['product_id'] ?? 0);
            $contact = sanitize_input($_POST['contact'] ?? '');
            
            if ($product_id <= 0 || empty($contact)) {
                throw new Exception('參數不完整');
            }
            
            // 驗證商品
            $stmt = $db->prepare("SELECT * FROM products WHERE id = ? AND status = 1");
            $stmt->execute([$product_id]);
            $product = $stmt->fetch();
            
            if (!$product) {
                throw new Exception('商品不存在或已下架');
            }
            
            if ($product['stock'] <= 0 && $product['auto_delivery'] == 0) {
                throw new Exception('商品庫存不足');
            }
            
            // 開始事務
            $db->beginTransaction();
            
            try {
                // 創建訂單
                $order_no = generate_order_no();
                $stmt = $db->prepare("
                    INSERT INTO orders (order_no, product_id, product_name, price, contact, ip_address) 
                    VALUES (?, ?, ?, ?, ?, ?)
                ");
                $stmt->execute([
                    $order_no,
                    $product_id,
                    $product['name'],
                    $product['price'],
                    $contact,
                    $_SERVER['REMOTE_ADDR']
                ]);
                
                $order_id = $db->lastInsertId();
                $card_info = null;
                
                // 如果自動發貨,分配卡密
                if ($product['auto_delivery'] == 1) {
                    // 查找可用卡密
                    $card_stmt = $db->prepare("
                        SELECT * FROM cards 
                        WHERE product_id = ? AND status = 0 
                        LIMIT 1 FOR UPDATE
                    ");
                    $card_stmt->execute([$product_id]);
                    $card = $card_stmt->fetch();
                    
                    if ($card) {
                        // 更新卡密狀態
                        $update_card = $db->prepare("
                            UPDATE cards SET status = 1, sold_at = NOW() 
                            WHERE id = ?
                        ");
                        $update_card->execute([$card['id']]);
                        
                        // 解密卡密
                        $card_info = [
                            'card_number' => $card['card_number'],
                            'card_password' => $card['card_password'] ? decrypt_card($card['card_password'], SALT_KEY . $card['secret_key']) : null
                        ];
                        
                        // 更新訂單
                        $update_order = $db->prepare("
                            UPDATE orders SET 
                            card_id = ?, 
                            card_info = ?, 
                            status = 1, 
                            paid_at = NOW() 
                            WHERE id = ?
                        ");
                        $update_order->execute([
                            $card['id'],
                            json_encode($card_info, JSON_UNESCAPED_UNICODE),
                            $order_id
                        ]);
                        
                        // 更新庫存
                        if ($product['stock'] > 0) {
                            $update_stock = $db->prepare("UPDATE products SET stock = stock - 1 WHERE id = ?");
                            $update_stock->execute([$product_id]);
                        }
                    } else {
                        // 無卡密,標記為待處理
                        $card_info = null;
                    }
                }
                
                $db->commit();
                
                $response['code'] = 1;
                $response['data'] = [
                    'order_no' => $order_no,
                    'card_info' => $card_info,
                    'status' => $card_info ? 1 : 0
                ];
                $response['message'] = $card_info ? '訂單創建成功,卡密已自動發放' : '訂單創建成功,請等待人工處理';
                
            } catch (Exception $e) {
                $db->rollBack();
                throw $e;
            }
            break;
            
        case 'check_order':
            // 查詢訂單
            $order_no = sanitize_input($_GET['order_no'] ?? '');
            $contact = sanitize_input($_GET['contact'] ?? '');
            
            if (empty($order_no) || empty($contact)) {
                throw new Exception('參數不完整');
            }
            
            $stmt = $db->prepare("
                SELECT o.*, p.name as product_name 
                FROM orders o 
                LEFT JOIN products p ON o.product_id = p.id 
                WHERE o.order_no = ? AND o.contact = ?
            ");
            $stmt->execute([$order_no, $contact]);
            $order = $stmt->fetch();
            
            if (!$order) {
                throw new Exception('訂單不存在');
            }
            
            $response['code'] = 1;
            $response['data'] = $order;
            break;
            
        default:
            throw new Exception('未知操作');
    }
    
} catch (Exception $e) {
    $response['code'] = 0;
    $response['message'] = $e->getMessage();
}

echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>

前端髮卡頁面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>卡密購買系統</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
    <style>
        .product-card { 
            transition: all 0.3s; 
            cursor: pointer;
        }
        .product-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 20px rgba(0,0,0,0.1);
        }
        .modal-backdrop { background-color: rgba(0,0,0,0.7); }
    </style>
</head>
<body>
    <!-- 導航欄 -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="#">
                <i class="bi bi-credit-card"></i> 卡密髮卡系統
            </a>
        </div>
    </nav>

    <!-- 商品列表 -->
    <div class="container mt-4">
        <h2 class="mb-4">商品列表</h2>
        <div class="row" id="product-list">
            <!-- 商品將通過JavaScript動態加載 -->
        </div>
    </div>

    <!-- 購買模態框 -->
    <div class="modal fade" id="buyModal" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">購買商品</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <form id="orderForm">
                        <input type="hidden" id="product_id" name="product_id">
                        <div class="mb-3">
                            <label for="product_name" class="form-label">商品名稱</label>
                            <input type="text" class="form-control" id="product_name" readonly>
                        </div>
                        <div class="mb-3">
                            <label for="product_price" class="form-label">商品價格</label>
                            <input type="text" class="form-control" id="product_price" readonly>
                        </div>
                        <div class="mb-3">
                            <label for="contact" class="form-label">聯繫方式 <small class="text-muted">(用於接收卡密)</small></label>
                            <input type="text" class="form-control" id="contact" name="contact" 
                                   placeholder="請輸入QQ號、郵箱或手機號" required>
                        </div>
                        <div class="alert alert-info">
                            <i class="bi bi-info-circle"></i> 提交後請保存訂單號,用於查詢訂單狀態
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
                    <button type="button" class="btn btn-primary" onclick="submitOrder()">提交訂單</button>
                </div>
            </div>
        </div>
    </div>

    <!-- 訂單結果模態框 -->
    <div class="modal fade" id="resultModal" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">訂單詳情</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <div id="orderResult"></div>
                </div>
            </div>
        </div>
    </div>

    <!-- 查詢訂單 -->
    <div class="container mt-5">
        <div class="card">
            <div class="card-header">
                <h5 class="mb-0"><i class="bi bi-search"></i> 訂單查詢</h5>
            </div>
            <div class="card-body">
                <div class="row g-3">
                    <div class="col-md-5">
                        <input type="text" class="form-control" id="search_order_no" placeholder="請輸入訂單號">
                    </div>
                    <div class="col-md-5">
                        <input type="text" class="form-control" id="search_contact" placeholder="請輸入聯繫方式">
                    </div>
                    <div class="col-md-2">
                        <button class="btn btn-primary w-100" onclick="checkOrder()">查詢</button>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <footer class="mt-5 py-4 bg-dark text-white text-center">
        <div class="container">
            <p class="mb-0">© 2023 卡密髮卡系統. All rights reserved.</p>
        </div>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <script>
        // 加載商品列表
        document.addEventListener('DOMContentLoaded', function() {
            loadProducts();
        });

        // 加載商品
        async function loadProducts() {
            try {
                const response = await fetch('api/card.php?action=get_products');
                const data = await response.json();
                
                if (data.code === 1) {
                    const container = document.getElementById('product-list');
                    container.innerHTML = '';
                    
                    data.data.forEach(product => {
                        const productHtml = `
                            <div class="col-md-4 mb-4">
                                <div class="card product-card h-100" onclick="showBuyModal(${product.id}, '${product.name}', ${product.price})">
                                    <div class="card-body">
                                        <h5 class="card-title">${product.name}</h5>
                                        <p class="card-text text-muted">${product.description || '暫無描述'}</p>
                                        <div class="d-flex justify-content-between align-items-center">
                                            <span class="h4 text-primary">¥${product.price}</span>
                                            <span class="badge ${product.stock > 0 ? 'bg-success' : 'bg-danger'}">
                                                ${product.stock > 0 ? '有貨' : '缺貨'}
                                            </span>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        `;
                        container.innerHTML += productHtml;
                    });
                }
            } catch (error) {
                console.error('加載商品失敗:', error);
            }
        }

        // 顯示購買模態框
        function showBuyModal(productId, productName, productPrice) {
            document.getElementById('product_id').value = productId;
            document.getElementById('product_name').value = productName;
            document.getElementById('product_price').value = '¥' + productPrice;
            document.getElementById('contact').value = '';
            
            const modal = new bootstrap.Modal(document.getElementById('buyModal'));
            modal.show();
        }

        // 提交訂單
        async function submitOrder() {
            const productId = document.getElementById('product_id').value;
            const contact = document.getElementById('contact').value;
            
            if (!contact) {
                alert('請輸入聯繫方式');
                return;
            }
            
            const formData = new FormData();
            formData.append('product_id', productId);
            formData.append('contact', contact);
            
            try {
                const response = await fetch('api/card.php?action=create_order', {
                    method: 'POST',
                    body: formData
                });
                
                const data = await response.json();
                showOrderResult(data);
                
            } catch (error) {
                console.error('提交訂單失敗:', error);
                alert('提交失敗,請稍後重試');
            }
        }

        // 顯示訂單結果
        function showOrderResult(data) {
            const resultDiv = document.getElementById('orderResult');
            
            if (data.code === 1) {
                let html = `
                    <div class="alert alert-success">
                        <h5><i class="bi bi-check-circle"></i> ${data.message}</h5>
                        <p><strong>訂單號:</strong>${data.data.order_no}</p>
                        <p><strong>聯繫方式:</strong>${document.getElementById('contact').value}</p>
                `;
                
                if (data.data.card_info) {
                    html += `
                        <hr>
                        <h6>卡密信息:</h6>
                        <p><strong>卡號:</strong><code>${data.data.card_info.card_number}</code></p>
                    `;
                    if (data.data.card_info.card_password) {
                        html += `<p><strong>密碼:</strong><code>${data.data.card_info.card_password}</code></p>`;
                    }
                    html += '<div class="alert alert-warning mt-3"><i class="bi bi-exclamation-triangle"></i> 請立即保存卡密信息,頁面關閉後將無法查看!</div>';
                } else {
                    html += '<p class="text-warning"><i class="bi bi-clock"></i> 訂單處理中,請稍後查詢</p>';
                }
                
                html += '</div>';
                resultDiv.innerHTML = html;
                
                // 關閉購買模態框
                const buyModal = bootstrap.Modal.getInstance(document.getElementById('buyModal'));
                buyModal.hide();
                
            } else {
                resultDiv.innerHTML = `
                    <div class="alert alert-danger">
                        <h5><i class="bi bi-x-circle"></i> 下單失敗</h5>
                        <p>${data.message}</p>
                    </div>
                `;
            }
            
            const resultModal = new bootstrap.Modal(document.getElementById('resultModal'));
            resultModal.show();
            
            // 重新加載商品列表
            loadProducts();
        }

        // 查詢訂單
        async function checkOrder() {
            const orderNo = document.getElementById('search_order_no').value;
            const contact = document.getElementById('search_contact').value;
            
            if (!orderNo || !contact) {
                alert('請輸入訂單號和聯繫方式');
                return;
            }
            
            try {
                const response = await fetch(`api/card.php?action=check_order&order_no=${orderNo}&contact=${contact}`);
                const data = await response.json();
                
                if (data.code === 1) {
                    const order = data.data;
                    const statusText = ['待處理', '已完成', '已取消'][order.status];
                    const statusClass = ['warning', 'success', 'secondary'][order.status];
                    
                    let cardInfo = '';
                    if (order.card_info) {
                        const card = JSON.parse(order.card_info);
                        cardInfo = `
                            <p><strong>卡號:</strong><code>${card.card_number}</code></p>
                            ${card.card_password ? `<p><strong>密碼:</strong><code>${card.card_password}</code></p>` : ''}
                        `;
                    }
                    
                    const resultHtml = `
                        <div class="card">
                            <div class="card-body">
                                <h5>訂單查詢結果</h5>
                                <p><strong>訂單號:</strong>${order.order_no}</p>
                                <p><strong>商品:</strong>${order.product_name}</p>
                                <p><strong>價格:</strong>¥${order.price}</p>
                                <p><strong>狀態:</strong><span class="badge bg-${statusClass}">${statusText}</span></p>
                                <p><strong>下單時間:</strong>${order.created_at}</p>
                                ${cardInfo}
                            </div>
                        </div>
                    `;
                    
                    document.getElementById('orderResult').innerHTML = resultHtml;
                } else {
                    document.getElementById('orderResult').innerHTML = `
                        <div class="alert alert-danger">${data.message}</div>
                    `;
                }
                
                const resultModal = new bootstrap.Modal(document.getElementById('resultModal'));
                resultModal.show();
                
            } catch (error) {
                console.error('查詢失敗:', error);
                alert('查詢失敗,請稍後重試');
            }
        }
    </script>
</body>
</html>

安全增強與優化建議

1、安全加固措施

<?php
// security.php - 安全增強功能

// 1. IP頻率限制
class RateLimiter {
    private static $instance = null;
    private $redis;
    
    private function __construct() {
        // 使用Redis進行頻率限制
        $this->redis = new Redis();
        $this->redis->connect('127.0.0.1', 6379);
    }
    
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function check($key, $limit, $window) {
        $now = microtime(true);
        $window_start = $now - $window;
        
        // 移除過期記錄
        $this->redis->zRemRangeByScore($key, 0, $window_start);
        
        // 獲取當前請求數
        $current = $this->redis->zCard($key);
        
        if ($current < $limit) {
            $this->redis->zAdd($key, $now, $now);
            $this->redis->expire($key, $window);
            return true;
        }
        
        return false;
    }
}

// 2. 輸入驗證類
class InputValidator {
    public static function validateEmail($email) {
        return filter_var($email, FILTER_VALIDATE_EMAIL);
    }
    
    public static function validateURL($url) {
        return filter_var($url, FILTER_VALIDATE_URL);
    }
    
    public static function sanitizeString($string, $max_length = 255) {
        $string = strip_tags($string);
        $string = htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
        return mb_substr($string, 0, $max_length);
    }
    
    public static function validatePrice($price) {
        return preg_match('/^\d+(\.\d{1,2})?$/', $price) && $price > 0;
    }
}

// 3. 卡密生成器
class CardGenerator {
    public static function generate($type = 'mixed', $length = 16) {
        $chars = '';
        
        switch ($type) {
            case 'numeric':
                $chars = '0123456789';
                break;
            case 'alpha':
                $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
                break;
            case 'alphanum':
                $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
                break;
            default:
                $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
        }
        
        $result = '';
        $char_length = strlen($chars);
        
        for ($i = 0; $i < $length; $i++) {
            $result .= $chars[random_int(0, $char_length - 1)];
        }
        
        return $result;
    }
    
    public static function generateBatch($count, $product_id, $type = 'mixed', $length = 16) {
        $cards = [];
        $db = Database::getInstance();
        
        $db->beginTransaction();
        try {
            for ($i = 0; $i < $count; $i++) {
                do {
                    $card_number = self::generate($type, $length);
                    $stmt = $db->prepare("SELECT id FROM cards WHERE card_number = ?");
                    $stmt->execute([$card_number]);
                } while ($stmt->fetch());
                
                $card_password = self::generate('alphanum', 8);
                $secret_key = bin2hex(random_bytes(16));
                $encrypted_password = encrypt_card($card_password, SALT_KEY . $secret_key);
                
                $stmt = $db->prepare("
                    INSERT INTO cards (product_id, card_number, card_password, secret_key) 
                    VALUES (?, ?, ?, ?)
                ");
                $stmt->execute([$product_id, $card_number, $encrypted_password, $secret_key]);
                
                $cards[] = [
                    'card_number' => $card_number,
                    'card_password' => $card_password
                ];
            }
            
            $db->commit();
            return $cards;
            
        } catch (Exception $e) {
            $db->rollBack();
            throw $e;
        }
    }
}

// 4. 日誌記錄
class Logger {
    public static function log($level, $message, $context = []) {
        $log_entry = sprintf(
            "[%s] %s: %s %s\n",
            date('Y-m-d H:i:s'),
            strtoupper($level),
            $message,
            !empty($context) ? json_encode($context, JSON_UNESCAPED_UNICODE) : ''
        );
        
        $log_file = __DIR__ . '/logs/system-' . date('Y-m-d') . '.log';
        file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX);
    }
}
?>

2、性能優化配置

# nginx配置文件示例
server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/card_system;
    index index.php index.html;
    
    # 啓用gzip壓縮
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
    
    # 安全頭部
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    
    # 靜態文件緩存
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
    
    # PHP處理
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        
        # 防止文件上傳攻擊
        fastcgi_param PHP_VALUE "upload_max_filesize=10M \n post_max_size=10M";
        
        # 隱藏PHP版本
        fastcgi_hide_header X-Powered-By;
    }
    
    # 禁止訪問敏感文件
    location ~ /\.(env|git|svn) {
        deny all;
    }
    
    location ~ /(config|logs|vendor) {
        deny all;
    }
}

部署與使用指南

1、系統部署步驟

  1. 環境要求

    • PHP 7.4+
    • MySQL 5.7+ 或 MariaDB
    • Web服務器(Apache/Nginx)
    • SSL證書(推薦)
  2. 安裝步驟

    # 1. 上傳文件到服務器
    scp -r card_system/* user@yourserver:/var/www/html/
    
    # 2. 設置目錄權限
    chmod -R 755 /var/www/html/
    chmod -R 777 /var/www/html/uploads/
    chmod -R 777 /var/www/html/logs/
    
    # 3. 創建數據庫
    mysql -u root -p
    CREATE DATABASE card_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    
    # 4. 導入數據庫結構
    mysql -u root -p card_system < database.sql
    
    # 5. 修改配置文件
    vi /var/www/html/config.php
    # 更新數據庫連接信息和密鑰
  3. 安全設置

    # 修改默認管理員密碼
    UPDATE admins SET password = PASSWORD('your_strong_password') WHERE username = 'admin';
    
    # 修改配置文件權限
    chmod 644 config.php
    chown www-data:www-data config.php

2、使用教程

  1. 添加商品

    • 登錄後台管理系統
    • 進入"商品管理"
    • 點擊"添加商品"
    • 填寫商品信息並保存
  2. 批量導入卡密

    • 準備TXT格式的卡密文件(每行一個卡密)
    • 在"卡密管理"中選擇對應商品
    • 點擊"批量導入"
    • 上傳文件並設置卡密格式
  3. API對接

    // 前端調用示例
    const response = await fetch('https://yourdomain.com/api/card.php?action=get_products');
    const products = await response.json();

3、常見問題解決

  1. 卡密無法顯示

    • 檢查SSL配置
    • 驗證加解密密鑰一致性
    • 檢查數據庫字符集設置
  2. 性能優化建議

    • 啓用OPcache
    • 配置MySQL查詢緩存
    • 使用CDN加速靜態資源
    • 定期清理過期日誌
  3. 安全建議

    • 定期更改管理員密碼
    • 開啓防火牆限制IP訪問
    • 定期備份數據庫
    • 監控系統日誌

總結

本文詳細介紹了從零開始搭建一個完整的卡密髮卡系統的全過程,涵蓋了數據庫設計、前後端實現、API接口、安全防護等各個方面。通過本系統,企業和個人可以快速搭建自己的卡密髮卡平台,適用於各種虛擬商品的銷售場景。系統完全開源,可以根據實際需求進行二次開發。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.