Web虚拟卡销售店铺实现方案

Web虚拟卡销售店铺实现方案

一、项目概述

1.1 项目背景

随着数字经济的快速发展,虚拟卡(如礼品卡、会员卡、游戏点卡等)的市场需求呈现爆发式增长。本项目旨在构建一个完整的Web虚拟卡销售平台,包含前端销售系统、后端管理系统和移动端H5支付功能,采用PHP作为后端技术栈,ThinkPHP作为开发框架,并集成微信支付、支付宝等主流支付方式。

1.2 系统架构

系统采用前后端分离架构:

  • 前端:Vue.js + Element UI(管理端)+ Vant(移动端)
  • 后端:ThinkPHP + MySQL
  • 数据库:MySQL
  • 缓存:Redis
  • 支付:微信支付H5 API + 支付宝支付API

二、技术选型与环境搭建

2.1 后端技术栈

ThinkPHP框架:作为国内使用率极高的MVC架构PHP框架,提供了丰富的内置功能,如数据库抽象层、路由机制、模板引擎、验证器、日志系统等,极大提升了开发效率。

主要依赖

{
  "require": {
    "php": ">=7.4",
    "topthink/framework": "^6.0",
    "topthink/think-migration": "^3.0",
    "topthink/think-captcha": "^3.0",
    "topthink/think-jwt": "^1.0",
    "alibabacloud/sdk": "^2.0",
    "overtrue/wechat": "^6.0"
  }
}

2.2 前端技术栈

用户端

  • Vue.js 3.x
  • Vant 4.x(移动端UI组件库)
  • Axios(HTTP客户端)
  • Vue Router(路由管理)
  • Vuex(状态管理)

管理端

  • Vue.js 3.x
  • Element Plus(PC端UI组件库)
  • ECharts(数据可视化)

2.3 开发环境配置

服务器环境

  • Nginx 1.22.1
  • MySQL 5.7+
  • PHP 7.4+
  • Redis 6.0+

开发工具

  • PHPStorm / VS Code
  • Git版本控制
  • Composer包管理
  • Docker(可选)

三、数据库设计

3.1 数据库ER图

系统主要实体包括:用户(User)、虚拟卡产品(CardProduct)、卡密库存(CardSecret)、订单(Order)、支付记录(Payment)、管理员(Admin)。

3.2 数据表设计

用户表

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(255) NOT NULL COMMENT '密码',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-正常',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`),
  UNIQUE KEY `uk_email` (`email`),
  UNIQUE KEY `uk_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

虚拟卡产品表

CREATE TABLE `card_product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `product_name` varchar(100) NOT NULL COMMENT '产品名称',
  `product_type` varchar(50) NOT NULL COMMENT '产品类型',
  `face_value` decimal(10,2) NOT NULL COMMENT '面值',
  `selling_price` decimal(10,2) NOT NULL COMMENT '售价',
  `description` text COMMENT '产品描述',
  `cover_image` varchar(255) DEFAULT NULL COMMENT '封面图',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-下架,1-上架',
  `sort` int(11) DEFAULT '0' COMMENT '排序',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='虚拟卡产品表';

卡密库存表

CREATE TABLE `card_secret` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `product_id` bigint(20) NOT NULL COMMENT '产品ID',
  `card_no` varchar(100) NOT NULL COMMENT '卡号',
  `card_password` varchar(100) NOT NULL COMMENT '卡密',
  `status` tinyint(1) DEFAULT '0' COMMENT '状态:0-未售出,1-已售出',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_card_no` (`card_no`),
  KEY `idx_product_id` (`product_id`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='卡密库存表';

订单表

CREATE TABLE `order` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `order_no` varchar(50) NOT NULL COMMENT '订单号',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `product_id` bigint(20) NOT NULL COMMENT '产品ID',
  `product_name` varchar(100) NOT NULL COMMENT '产品名称',
  `face_value` decimal(10,2) NOT NULL COMMENT '面值',
  `selling_price` decimal(10,2) NOT NULL COMMENT '售价',
  `quantity` int(11) NOT NULL DEFAULT '1' COMMENT '购买数量',
  `total_amount` decimal(10,2) NOT NULL COMMENT '总金额',
  `payment_method` varchar(20) DEFAULT NULL COMMENT '支付方式',
  `payment_status` tinyint(1) DEFAULT '0' COMMENT '支付状态:0-未支付,1-已支付',
  `order_status` tinyint(1) DEFAULT '0' COMMENT '订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消',
  `card_no` varchar(100) DEFAULT NULL COMMENT '卡号',
  `card_password` varchar(100) DEFAULT NULL COMMENT '卡密',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_product_id` (`product_id`),
  KEY `idx_payment_status` (`payment_status`),
  KEY `idx_order_status` (`order_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

支付记录表

CREATE TABLE `payment` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `order_no` varchar(50) NOT NULL COMMENT '订单号',
  `payment_no` varchar(50) NOT NULL COMMENT '支付流水号',
  `payment_method` varchar(20) NOT NULL COMMENT '支付方式',
  `payment_amount` decimal(10,2) NOT NULL COMMENT '支付金额',
  `payment_status` tinyint(1) NOT NULL COMMENT '支付状态:0-未支付,1-已支付',
  `payment_time` datetime DEFAULT NULL COMMENT '支付时间',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_payment_no` (`payment_no`),
  KEY `idx_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付记录表';

系统日志表

CREATE TABLE `sys_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户ID',
  `username` varchar(50) DEFAULT NULL COMMENT '用户名',
  `operation` varchar(50) DEFAULT NULL COMMENT '用户操作',
  `method` varchar(200) DEFAULT NULL COMMENT '请求方法',
  `params` text COMMENT '请求参数',
  `time` bigint(20) DEFAULT NULL COMMENT '执行时长(毫秒)',
  `ip` varchar(64) DEFAULT NULL COMMENT 'IP地址',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表';

四、后端实现

4.1 ThinkPHP项目结构

src/
├── app/                    # 应用目录
│   ├── controller/         # 控制器
│   │   ├── api/           # API接口控制器
│   │   │   ├── AuthController.php
│   │   │   ├── CardController.php
│   │   │   ├── OrderController.php
│   │   │   └── PaymentController.php
│   │   └── admin/         # 管理端控制器
│   │       ├── AdminAuthController.php
│   │       ├── AdminCardController.php
│   │       └── AdminOrderController.php
│   ├── model/             # 模型
│   │   ├── User.php
│   │   ├── CardProduct.php
│   │   ├── CardSecret.php
│   │   ├── Order.php
│   │   └── Payment.php
│   ├── service/           # 服务层
│   │   ├── AuthService.php
│   │   ├── CardService.php
│   │   ├── OrderService.php
│   │   └── PaymentService.php
│   ├── validate/          # 验证器
│   │   ├── UserValidate.php
│   │   ├── CardValidate.php
│   │   └── OrderValidate.php
│   └── common.php         # 公共函数
├── config/                 # 配置文件
│   ├── app.php
│   ├── database.php
│   ├── cache.php
│   ├── jwt.php
│   └── wechat.php
├── route/                  # 路由定义
│   └── app.php
├── public/                 # 入口文件
│   └── index.php
└── vendor/                 # 依赖包

4.2 核心功能实现

4.2.1 用户认证与授权

JWT认证实现

<?php
namespace app\service;

use think\facade\Config;
use think\facade\Cache;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class AuthService
{
    /**
     * 生成JWT Token
     * @param array $userInfo 用户信息
     * @return string
     */
    public static function generateToken($userInfo)
    {
        $config = Config::get('jwt');
        $payload = [
            'iss' => $config['iss'],
            'iat' => time(),
            'exp' => time() + $config['expire'],
            'user_id' => $userInfo['id'],
            'username' => $userInfo['username']
        ];
        
        return JWT::encode($payload, $config['key'], $config['alg']);
    }

    /**
     * 验证JWT Token
     * @param string $token
     * @return array|false
     */
    public static function verifyToken($token)
    {
        try {
            $config = Config::get('jwt');
            $decoded = JWT::decode($token, new Key($config['key'], $config['alg']));
            return (array)$decoded;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * 用户登录
     * @param string $username
     * @param string $password
     * @return array|false
     */
    public static function login($username, $password)
    {
        $user = User::where('username', $username)
            ->where('status', 1)
            ->find();
        
        if (!$user || !password_verify($password, $user->password)) {
            return false;
        }
        
        $token = self::generateToken($user->toArray());
        
        // 记录登录日志
        LogService::addLoginLog($user->id, $user->username);
        
        return [
            'token' => $token,
            'user_info' => [
                'id' => $user->id,
                'username' => $user->username,
                'email' => $user->email,
                'phone' => $user->phone,
                'avatar' => $user->avatar
            ]
        ];
    }
}
?>

4.2.2 虚拟卡管理

卡密生成算法

<?php
namespace app\service;

use think\facade\Db;
use think\facade\Cache;

class CardService
{
    /**
     * 生成卡密
     * @param int $productId 产品ID
     * @param int $quantity 生成数量
     * @param string $prefix 卡号前缀
     * @return bool
     */
    public static function generateCardSecret($productId, $quantity, $prefix = '')
    {
        Db::startTrans();
        try {
            for ($i = 0; $i < $quantity; $i++) {
                $cardNo = self::generateCardNo($prefix);
                $cardPassword = self::generateCardPassword();
                
                $data = [
                    'product_id' => $productId,
                    'card_no' => $cardNo,
                    'card_password' => $cardPassword,
                    'status' => 0,
                    'create_time' => date('Y-m-d H:i:s')
                ];
                
                Db::name('card_secret')->insert($data);
            }
            
            Db::commit();
            return true;
        } catch (\Exception $e) {
            Db::rollback();
            return false;
        }
    }

    /**
     * 生成卡号
     * @param string $prefix
     * @return string
     */
    private static function generateCardNo($prefix = '')
    {
        $prefix = $prefix ?: date('Ymd');
        $random = mt_rand(100000, 999999);
        return $prefix . $random;
    }

    /**
     * 生成卡密
     * @return string
     */
    private static function generateCardPassword()
    {
        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $password = '';
        for ($i = 0; $i < 12; $i++) {
            $password .= $chars[mt_rand(0, strlen($chars) - 1)];
        }
        return $password;
    }

    /**
     * 获取可用卡密
     * @param int $productId
     * @return array|false
     */
    public static function getAvailableCard($productId)
    {
        $card = Db::name('card_secret')
            ->where('product_id', $productId)
            ->where('status', 0)
            ->order('id ASC')
            ->find();
        
        if (!$card) {
            return false;
        }
        
        // 更新卡密状态为已售出
        Db::name('card_secret')
            ->where('id', $card['id'])
            ->update(['status' => 1, 'update_time' => date('Y-m-d H:i:s')]);
        
        return $card;
    }
}
?>

4.2.3 订单服务

订单创建与处理

<?php
namespace app\service;

use think\facade\Db;
use think\facade\Cache;
use app\model\Order;

class OrderService
{
    /**
     * 创建订单
     * @param int $userId
     * @param int $productId
     * @param int $quantity
     * @return array|false
     */
    public static function createOrder($userId, $productId, $quantity = 1)
    {
        // 获取产品信息
        $product = Db::name('card_product')
            ->where('id', $productId)
            ->where('status', 1)
            ->find();
        
        if (!$product) {
            return false;
        }
        
        // 检查库存
        $availableStock = self::getAvailableStock($productId);
        if ($availableStock < $quantity) {
            return false;
        }
        
        Db::startTrans();
        try {
            // 生成订单号
            $orderNo = self::generateOrderNo();
            
            // 计算总金额
            $totalAmount = bcmul($product['selling_price'], $quantity, 2);
            
            // 创建订单
            $orderData = [
                'order_no' => $orderNo,
                'user_id' => $userId,
                'product_id' => $productId,
                'product_name' => $product['product_name'],
                'face_value' => $product['face_value'],
                'selling_price' => $product['selling_price'],
                'quantity' => $quantity,
                'total_amount' => $totalAmount,
                'payment_status' => 0,
                'order_status' => 0,
                'create_time' => date('Y-m-d H:i:s')
            ];
            
            $orderId = Db::name('order')->insertGetId($orderData);
            
            Db::commit();
            
            return [
                'order_id' => $orderId,
                'order_no' => $orderNo,
                'total_amount' => $totalAmount,
                'product_name' => $product['product_name']
            ];
        } catch (\Exception $e) {
            Db::rollback();
            return false;
        }
    }

    /**
     * 生成订单号
     * @return string
     */
    private static function generateOrderNo()
    {
        return date('YmdHis') . mt_rand(1000, 9999);
    }

    /**
     * 获取可用库存
     * @param int $productId
     * @return int
     */
    public static function getAvailableStock($productId)
    {
        return Db::name('card_secret')
            ->where('product_id', $productId)
            ->where('status', 0)
            ->count();
    }

    /**
     * 支付成功回调
     * @param string $orderNo
     * @param array $paymentData
     * @return bool
     */
    public static function paymentSuccess($orderNo, $paymentData)
    {
        Db::startTrans();
        try {
            // 更新订单状态
            $order = Db::name('order')
                ->where('order_no', $orderNo)
                ->where('payment_status', 0)
                ->lock(true)
                ->find();
            
            if (!$order) {
                return false;
            }
            
            // 获取卡密
            $card = CardService::getAvailableCard($order['product_id']);
            if (!$card) {
                return false;
            }
            
            // 更新订单信息
            $updateData = [
                'payment_status' => 1,
                'order_status' => 2, // 已发货
                'payment_method' => $paymentData['payment_method'],
                'card_no' => $card['card_no'],
                'card_password' => $card['card_password'],
                'update_time' => date('Y-m-d H:i:s')
            ];
            
            Db::name('order')
                ->where('order_no', $orderNo)
                ->update($updateData);
            
            // 添加支付记录
            $paymentData = [
                'order_no' => $orderNo,
                'payment_no' => $paymentData['payment_no'],
                'payment_method' => $paymentData['payment_method'],
                'payment_amount' => $order['total_amount'],
                'payment_status' => 1,
                'payment_time' => date('Y-m-d H:i:s')
            ];
            
            Db::name('payment')->insert($paymentData);
            
            Db::commit();
            
            // 发送卡密邮件或短信
            self::sendCardInfo($order['user_id'], $card['card_no'], $card['card_password']);
            
            return true;
        } catch (\Exception $e) {
            Db::rollback();
            return false;
        }
    }

    /**
     * 发送卡密信息
     * @param int $userId
     * @param string $cardNo
     * @param string $cardPassword
     * @return bool
     */
    private static function sendCardInfo($userId, $cardNo, $cardPassword)
    {
        $user = Db::name('user')->where('id', $userId)->find();
        if (!$user) {
            return false;
        }
        
        // 发送邮件
        $emailService = new EmailService();
        $subject = '您的虚拟卡购买成功';
        $content = "尊敬的{$user['username']},您购买的虚拟卡已发货:\n卡号:{$cardNo}\n密码:{$cardPassword}\n请妥善保管,请勿泄露给他人。";
        
        return $emailService->send($user['email'], $subject, $content);
    }
}
?>

4.2.4 微信支付集成

微信支付配置

<?php
namespace app\service;

use think\facade\Config;
use EasyWeChat\Factory;

class WeChatPayService
{
    /**
     * 获取微信支付实例
     * @return \EasyWeChat\Payment\Application
     */
    public static function getPayment()
    {
        $config = Config::get('wechat.payment');
        return Factory::payment($config);
    }

    /**
     * 创建支付订单
     * @param string $orderNo
     * @param float $amount
     * @param string $body
     * @param string $openid
     * @return array|false
     */
    public static function createOrder($orderNo, $amount, $body, $openid = null)
    {
        try {
            $payment = self::getPayment();
            
            $data = [
                'body' => $body,
                'out_trade_no' => $orderNo,
                'total_fee' => intval($amount * 100), // 单位:分
                'notify_url' => url('/api/payment/wechat/notify', [], true, true),
                'trade_type' => $openid ? 'JSAPI' : 'NATIVE',
            ];
            
            if ($openid) {
                $data['openid'] = $openid;
            }
            
            $result = $payment->order->unify($data);
            
            if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') {
                return $result;
            }
            
            return false;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * 处理支付回调
     * @return array|false
     */
    public static function handleNotify()
    {
        try {
            $payment = self::getPayment();
            $response = $payment->handlePaidNotify(function($message, $fail){
                // 验证订单
                $order = Db::name('order')
                    ->where('order_no', $message['out_trade_no'])
                    ->find();
                
                if (!$order) {
                    return $fail('订单不存在');
                }
                
                if ($order['payment_status'] == 1) {
                    return true;
                }
                
                // 验证金额
                $totalFee = intval($order['total_amount'] * 100);
                if ($message['total_fee'] != $totalFee) {
                    return $fail('金额不一致');
                }
                
                // 更新订单状态
                $paymentData = [
                    'payment_no' => $message['transaction_id'],
                    'payment_method' => 'wechat',
                    'payment_status' => 1,
                    'payment_time' => date('Y-m-d H:i:s')
                ];
                
                $result = OrderService::paymentSuccess($order['order_no'], $paymentData);
                
                return $result ? true : $fail('订单处理失败');
            });
            
            return $response;
        } catch (\Exception $e) {
            return false;
        }
    }
}
?>

五、前端实现

5.1 用户端前端实现

5.1.1 项目结构

src/
├── api/                    # API接口
│   ├── auth.js            # 认证相关接口
│   ├── card.js            # 虚拟卡相关接口
│   ├── order.js           # 订单相关接口
│   └── payment.js         # 支付相关接口
├── components/            # 公共组件
│   ├── Header.vue         # 头部组件
│   ├── Footer.vue         # 底部组件
│   ├── Loading.vue        # 加载组件
│   └── CardItem.vue       # 卡产品项组件
├── pages/                  # 页面组件
│   ├── Home.vue           # 首页
│   ├── Login.vue          # 登录页
│   ├── Register.vue       # 注册页
│   ├── ProductList.vue    # 产品列表页
│   ├── ProductDetail.vue  # 产品详情页
│   ├── Order.vue          # 订单页
│   ├── Payment.vue        # 支付页
│   ├── OrderList.vue      # 订单列表页
│   └── OrderDetail.vue    # 订单详情页
├── router/                # 路由配置
│   └── index.js
├── store/                 # Vuex状态管理
│   ├── index.js
│   └── modules/
│       ├── auth.js        # 认证模块
│       ├── card.js        # 虚拟卡模块
│       └── order.js       # 订单模块
├── utils/                 # 工具函数
│   ├── request.js         # 请求封装
│   ├── auth.js            # 认证工具
│   └── common.js          # 公共工具
└── App.vue                # 根组件

5.1.2 核心页面实现

首页(Home.vue)

<template>
  <div class="home">
    <!-- 轮播图 -->
    <van-swipe class="banner-swipe" :autoplay="3000">
      <van-swipe-item v-for="item in banners" :key="item.id">
        <img :src="item.image" class="banner-image" />
      </van-swipe-item>
    </van-swipe>

    <!-- 产品分类 -->
    <div class="category-section">
      <div class="section-title">热门分类</div>
      <div class="category-list">
        <div v-for="category in categories" :key="category.id" class="category-item">
          <img :src="category.icon" class="category-icon" />
          <div class="category-name">{{ category.name }}</div>
        </div>
      </div>
    </div>

    <!-- 推荐产品 -->
    <div class="product-section">
      <div class="section-title">推荐产品</div>
      <div class="product-list">
        <CardItem
          v-for="product in products"
          :key="product.id"
          :product="product"
          @click="goToDetail(product.id)"
        />
      </div>
    </div>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { getBanners, getCategories, getProducts } from '@/api/card'
import CardItem from '@/components/CardItem.vue'

export default {
  name: 'Home',
  components: {
    CardItem
  },
  setup() {
    const router = useRouter()
    const banners = ref([])
    const categories = ref([])
    const products = ref([])

    const loadData = async () => {
      try {
        const [bannerRes, categoryRes, productRes] = await Promise.all([
          getBanners(),
          getCategories(),
          getProducts({ page: 1, limit: 8 })
        ])
        
        banners.value = bannerRes.data
        categories.value = categoryRes.data
        products.value = productRes.data.list
      } catch (error) {
        console.error('加载数据失败:', error)
      }
    }

    const goToDetail = (productId) => {
      router.push(`/product/${productId}`)
    }

    onMounted(() => {
      loadData()
    })

    return {
      banners,
      categories,
      products,
      goToDetail
    }
  }
}
</script>

产品详情页(ProductDetail.vue)

<template>
  <div class="product-detail">
    <!-- 产品图片 -->
    <div class="product-image">
      <img :src="product.cover_image" />
    </div>

    <!-- 产品信息 -->
    <div class="product-info">
      <div class="product-name">{{ product.product_name }}</div>
      <div class="product-price">
        <span class="current-price">¥{{ product.selling_price }}</span>
        <span class="face-value">面值:¥{{ product.face_value }}</span>
      </div>
      <div class="product-description">{{ product.description }}</div>
    </div>

    <!-- 购买按钮 -->
    <div class="buy-action">
      <van-button type="primary" block @click="handleBuy">立即购买</van-button>
    </div>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getProductDetail } from '@/api/card'
import { createOrder } from '@/api/order'
import { showToast } from 'vant'

export default {
  name: 'ProductDetail',
  setup() {
    const route = useRoute()
    const router = useRouter()
    const product = ref({})
    const loading = ref(false)

    const loadProductDetail = async () => {
      try {
        const res = await getProductDetail(route.params.id)
        product.value = res.data
      } catch (error) {
        console.error('加载产品详情失败:', error)
      }
    }

    const handleBuy = async () => {
      if (loading.value) return
      
      loading.value = true
      try {
        const res = await createOrder(product.value.id, 1)
        if (res.code === 0) {
          router.push(`/payment/${res.data.order_no}`)
        } else {
          showToast(res.msg || '创建订单失败')
        }
      } catch (error) {
        console.error('创建订单失败:', error)
        showToast('创建订单失败')
      } finally {
        loading.value = false
      }
    }

    onMounted(() => {
      loadProductDetail()
    })

    return {
      product,
      handleBuy
    }
  }
}
</script>

5.2 管理端前端实现

5.2.1 项目结构

src/
├── api/                    # API接口
│   ├── admin/
│   │   ├── auth.js        # 管理端认证接口
│   │   ├── card.js        # 管理端虚拟卡接口
│   │   ├── order.js       # 管理端订单接口
│   │   └── user.js        # 管理端用户接口
│   └── index.js
├── components/            # 公共组件
│   ├── Layout.vue         # 布局组件
│   ├── Sidebar.vue        # 侧边栏
│   ├── Header.vue         # 头部
│   └── Table.vue          # 表格组件
├── views/                 # 页面组件
│   ├── Login.vue          # 登录页
│   ├── Dashboard.vue      # 仪表盘
│   ├── CardList.vue       # 产品列表
│   ├── CardAdd.vue        # 添加产品
│   ├── CardEdit.vue       # 编辑产品
│   ├── CardSecret.vue     # 卡密管理
│   ├── OrderList.vue      # 订单列表
│   ├── OrderDetail.vue    # 订单详情
│   ├── UserList.vue      # 用户列表
│   └── System.vue         # 系统设置
├── router/                # 路由配置
│   └── index.js
├── store/                 # Vuex状态管理
│   ├── index.js
│   └── modules/
│       ├── auth.js        # 认证模块
│       ├── card.js        # 虚拟卡模块
│       └── order.js       # 订单模块
├── utils/                 # 工具函数
│   ├── request.js         # 请求封装
│   ├── auth.js            # 认证工具
│   └── common.js          # 公共工具
└── App.vue                # 根组件

5.2.2 核心页面实现

产品管理页(CardList.vue)

<template>
  <div class="card-list">
    <div class="header">
      <el-button type="primary" @click="handleAdd">添加产品</el-button>
      <el-button @click="handleImport">批量导入卡密</el-button>
    </div>

    <el-table :data="tableData" style="width: 100%">
      <el-table-column prop="id" label="ID" width="60" />
      <el-table-column prop="product_name" label="产品名称" />
      <el-table-column prop="product_type" label="产品类型" />
      <el-table-column prop="face_value" label="面值" />
      <el-table-column prop="selling_price" label="售价" />
      <el-table-column prop="stock" label="库存" />
      <el-table-column prop="status" label="状态">
        <template #default="scope">
          <el-tag :type="scope.row.status ? 'success' : 'danger'">
            {{ scope.row.status ? '上架' : '下架' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="create_time" label="创建时间" />
      <el-table-column label="操作" width="200">
        <template #default="scope">
          <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
          <el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <div class="pagination">
      <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :page-sizes="[10, 20, 50, 100]"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { getCardList, deleteCard } from '@/api/admin/card'
import { ElMessage, ElMessageBox } from 'element-plus'

export default {
  name: 'CardList',
  setup() {
    const router = useRouter()
    const tableData = ref([])
    const currentPage = ref(1)
    const pageSize = ref(10)
    const total = ref(0)

    const loadData = async () => {
      try {
        const res = await getCardList({
          page: currentPage.value,
          limit: pageSize.value
        })
        tableData.value = res.data.list
        total.value = res.data.total
      } catch (error) {
        console.error('加载数据失败:', error)
      }
    }

    const handleAdd = () => {
      router.push('/admin/card/add')
    }

    const handleEdit = (row) => {
      router.push(`/admin/card/edit/${row.id}`)
    }

    const handleDelete = async (row) => {
      try {
        await ElMessageBox.confirm('确定要删除该产品吗?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
        
        await deleteCard(row.id)
        ElMessage.success('删除成功')
        loadData()
      } catch (error) {
        console.error('删除失败:', error)
      }
    }

    const handleSizeChange = (val) => {
      pageSize.value = val
      loadData()
    }

    const handleCurrentChange = (val) => {
      currentPage.value = val
      loadData()
    }

    onMounted(() => {
      loadData()
    })

    return {
      tableData,
      currentPage,
      pageSize,
      total,
      handleAdd,
      handleEdit,
      handleDelete,
      handleSizeChange,
      handleCurrentChange
    }
  }
}
</script>

六、微信H5支付集成

6.1 微信支付配置

微信支付配置文件(config/wechat.php)

<?php
return [
    'payment' => [
        'app_id' => env('WECHAT_APP_ID', ''),
        'mch_id' => env('WECHAT_MCH_ID', ''),
        'key' => env('WECHAT_KEY', ''),
        'cert_path' => env('WECHAT_CERT_PATH', ''),
        'key_path' => env('WECHAT_KEY_PATH', ''),
        'notify_url' => env('WECHAT_NOTIFY_URL', ''),
        'sandbox' => env('WECHAT_SANDBOX', false),
    ],
];

6.2 支付流程实现

支付控制器(PaymentController.php)

<?php
namespace app\controller\api;

use think\facade\Db;
use think\facade\Config;
use app\BaseController;
use app\service\WeChatPayService;
use app\service\OrderService;

class PaymentController extends BaseController
{
    /**
     * 创建支付订单
     * @return \think\Response
     */
    public function create()
    {
        $orderNo = $this->request->param('order_no');
        $openid = $this->request->param('openid');
        
        // 获取订单信息
        $order = Db::name('order')
            ->where('order_no', $orderNo)
            ->where('payment_status', 0)
            ->find();
        
        if (!$order) {
            return json(['code' => 1, 'msg' => '订单不存在或已支付']);
        }
        
        // 创建微信支付订单
        $result = WeChatPayService::createOrder(
            $orderNo,
            $order['total_amount'],
            $order['product_name'],
            $openid
        );
        
        if (!$result) {
            return json(['code' => 1, 'msg' => '创建支付订单失败']);
        }
        
        return json(['code' => 0, 'data' => $result]);
    }

    /**
     * 微信支付回调
     * @return mixed
     */
    public function wechatNotify()
    {
        return WeChatPayService::handleNotify();
    }

    /**
     * 查询支付状态
     * @return \think\Response
     */
    public function query()
    {
        $orderNo = $this->request->param('order_no');
        
        $order = Db::name('order')
            ->where('order_no', $orderNo)
            ->find();
        
        if (!$order) {
            return json(['code' => 1, 'msg' => '订单不存在']);
        }
        
        return json([
            'code' => 0,
            'data' => [
                'payment_status' => $order['payment_status'],
                'order_status' => $order['order_status'],
                'card_no' => $order['card_no'],
                'card_password' => $order['card_password']
            ]
        ]);
    }
}
?>

七、系统安全与性能优化

7.1 安全防护措施

数据加密

  • 用户密码采用bcrypt加密存储
  • 卡密信息采用AES-256加密存储
  • 敏感数据传输使用HTTPS加密

防SQL注入

  • 使用ThinkPHP的预处理语句
  • 对用户输入进行严格过滤和验证

防XSS攻击

  • 对用户输入进行HTML转义
  • 使用Content Security Policy(CSP)

防CSRF攻击

  • 使用Token验证机制
  • 验证请求来源

7.2 性能优化

缓存策略

  • 使用Redis缓存热点数据(如产品信息、用户信息)
  • 设置合理的缓存过期时间

数据库优化

  • 建立合适的索引
  • 分库分表(大流量场景)
  • 读写分离

静态资源优化

  • CDN加速静态资源
  • 图片懒加载
  • 资源压缩

八、部署与运维

8.1 环境部署

服务器环境要求

  • Linux服务器(推荐CentOS 7+)
  • Nginx 1.18+
  • PHP 7.4+
  • MySQL 5.7+
  • Redis 6.0+

一键安装脚本

#!/bin/bash

# 安装Nginx
yum install -y nginx

# 安装PHP
yum install -y php php-fpm php-mysql php-redis php-gd php-mbstring php-xml

# 安装MySQL
yum install -y mysql-server

# 安装Redis
yum install -y redis

# 配置防火墙
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

# 启动服务
systemctl start nginx
systemctl start php-fpm
systemctl start mysqld
systemctl start redis
systemctl enable nginx
systemctl enable php-fpm
systemctl enable mysqld
systemctl enable redis

8.2 监控与告警

系统监控

  • 使用Prometheus监控服务器性能
  • 使用Grafana展示监控数据
  • 设置告警规则

日志管理

  • 使用ELK(Elasticsearch + Logstash + Kibana)收集和分析日志
  • 设置日志轮转策略

8.3 备份与恢复

数据库备份

#!/bin/bash

# 数据库备份脚本
DATE=$(date +%Y%m%d%H%M%S)
BACKUP_DIR="/data/backup/mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="password"
DATABASE="virtual_card"

mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD $DATABASE > $BACKUP_DIR/$DATABASE-$DATE.sql

# 保留最近7天的备份
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
版权声明:本文为JienDa博主的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
若内容若侵犯到您的权益,请发送邮件至:platform_service@jienda.com我们将第一时间处理!
所有资源仅限于参考和学习,版权归JienDa作者所有,更多请访问JienDa首页。

给TA赞助
共{{data.count}}人
人已赞助
后端

【Java 开发日记】我们来说一说 Redis 主从复制的原理及作用

2025-12-19 21:34:54

后端

Spring Web MVC从入门到实战

2025-12-19 21:50:46

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索