系統概述與技術選型
在數字化時代,卡密系統已成為虛擬商品交易、會員服務、軟件激活等領域的重要工具。本文將詳細介紹如何從零開始搭建一個完整的獨立卡密系統,包含完整的源代碼和實現思路。
技術棧選擇:
- 源碼及演示: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、系統部署步驟
-
環境要求
- PHP 7.4+
- MySQL 5.7+ 或 MariaDB
- Web服務器(Apache/Nginx)
- SSL證書(推薦)
-
安裝步驟
# 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 # 更新數據庫連接信息和密鑰 -
安全設置
# 修改默認管理員密碼 UPDATE admins SET password = PASSWORD('your_strong_password') WHERE username = 'admin'; # 修改配置文件權限 chmod 644 config.php chown www-data:www-data config.php
2、使用教程
-
添加商品
- 登錄後台管理系統
- 進入"商品管理"
- 點擊"添加商品"
- 填寫商品信息並保存
-
批量導入卡密
- 準備TXT格式的卡密文件(每行一個卡密)
- 在"卡密管理"中選擇對應商品
- 點擊"批量導入"
- 上傳文件並設置卡密格式
-
API對接
// 前端調用示例 const response = await fetch('https://yourdomain.com/api/card.php?action=get_products'); const products = await response.json();
3、常見問題解決
-
卡密無法顯示
- 檢查SSL配置
- 驗證加解密密鑰一致性
- 檢查數據庫字符集設置
-
性能優化建議
- 啓用OPcache
- 配置MySQL查詢緩存
- 使用CDN加速靜態資源
- 定期清理過期日誌
-
安全建議
- 定期更改管理員密碼
- 開啓防火牆限制IP訪問
- 定期備份數據庫
- 監控系統日誌
總結
本文詳細介紹了從零開始搭建一個完整的卡密髮卡系統的全過程,涵蓋了數據庫設計、前後端實現、API接口、安全防護等各個方面。通過本系統,企業和個人可以快速搭建自己的卡密髮卡平台,適用於各種虛擬商品的銷售場景。系統完全開源,可以根據實際需求進行二次開發。