Browse Source

fix:增加数量统计

super-yimizi 1 month ago
parent
commit
576d8df64e
47 changed files with 6403 additions and 1293 deletions
  1. 13 1
      addons/shop/config.php
  2. 273 140
      application/admin/controller/shop/Order.php
  3. 71 50
      application/admin/controller/shop/Report.php
  4. 37 0
      application/admin/controller/user/Share.php
  5. 49 10
      application/admin/lang/zh-cn/shop/order.php
  6. 16 0
      application/admin/lang/zh-cn/user/share.php
  7. 64 72
      application/admin/model/shop/Order.php
  8. 40 0
      application/admin/model/user/Share.php
  9. 27 0
      application/admin/validate/user/Share.php
  10. 1 1
      application/admin/view/pay/config/add.html
  11. 160 88
      application/admin/view/shop/order/detail.html
  12. 24 7
      application/admin/view/shop/order/prints.html
  13. 63 0
      application/admin/view/user/share/add.html
  14. 63 0
      application/admin/view/user/share/edit.html
  15. 29 0
      application/admin/view/user/share/index.html
  16. 25 0
      application/admin/view/user/share/recyclebin.html
  17. 7 4
      application/api/controller/Discount.php
  18. 22 34
      application/api/controller/Lottery.php
  19. 10 1
      application/api/controller/Order.php
  20. 5 5
      application/api/controller/User.php
  21. 69 3
      application/api/library/ExceptionHandle.php
  22. 69 0
      application/common/Enum/ErrorCodeEnum.php
  23. 27 0
      application/common/Enum/GoodsEnum.php
  24. 77 36
      application/common/Enum/LotteryEnum.php
  25. 133 1
      application/common/Enum/OrderEnum.php
  26. 12 6
      application/common/Enum/PayEnum.php
  27. 105 97
      application/common/Service/DiscountService.php
  28. 422 0
      application/common/Service/Lottery/LotteryActivityService.php
  29. 1174 0
      application/common/Service/Lottery/LotteryChanceService.php
  30. 490 0
      application/common/Service/Lottery/LotteryRecordService.php
  31. 640 0
      application/common/Service/Lottery/LotteryService.php
  32. 114 0
      application/common/Service/Order/OrderGoodsService.php
  33. 16 2
      application/common/Service/OrderService.php
  34. 5 8
      application/common/Service/lottery/LotteryActivityService.php
  35. 208 72
      application/common/Service/lottery/LotteryChanceService.php
  36. 1 1
      application/common/Service/lottery/LotteryRecordService.php
  37. 10 30
      application/common/Service/lottery/LotteryService.php
  38. 55 0
      application/common/exception/BusinessException.php
  39. 8 6
      application/common/model/lottery/LotteryActivity.php
  40. 282 0
      application/common/model/lottery/LotteryUserChanceRecord.php
  41. 16 2
      application/common/service/OrderService.php
  42. 356 162
      docs/LotteryChanceService优化说明.md
  43. 289 105
      docs/消费抽奖营销活动_数据库建表脚本.sql
  44. 293 116
      docs/消费抽奖营销活动_数据表设计.md
  45. 300 150
      docs/消费抽奖营销活动_表字段说明.md
  46. 111 83
      public/assets/js/backend/shop/order.js
  47. 122 0
      public/assets/js/backend/user/share.js

+ 13 - 1
addons/shop/config.php

@@ -62,6 +62,18 @@ return [
         'extend' => '',
     ],
     [
+        'name' => 'version',
+        'title' => '版本号',
+        'type' => 'string',
+        'content' => [],
+        'value' => '1.0.0',
+        'rule' => '',
+        'msg' => '',
+        'tip' => '',
+        'ok' => '',
+        'extend' => '',
+    ],
+    [
         'name' => 'gwabeian',
         'title' => '公安备案号',
         'type' => 'string',
@@ -78,7 +90,7 @@ return [
         'title' => '站点Logo',
         'type' => 'image',
         'content' => [],
-        'value' => '/assets/addons/shop/img/logo.png',
+        'value' => '/uploads/20250704/d735e16f6cb1bb447de967eed61bfa26.jpg',
         'rule' => 'required',
         'msg' => '',
         'tip' => '',

+ 273 - 140
application/admin/controller/shop/Order.php

@@ -8,6 +8,11 @@ use app\admin\model\shop\OrderGoods;
 use app\admin\model\shop\OrderAftersales;
 use app\admin\model\shop\OrderAction;
 use app\admin\model\shop\OrderElectronics;
+use app\common\Enum\OrderEnum;
+use app\common\Enum\GoodsEnum;
+use app\common\Enum\PayEnum;
+use app\common\Enum\ChannelEnum;
+use think\Db;
 
 /**
  * 订单管理
@@ -29,10 +34,30 @@ class Order extends Backend
     {
         parent::_initialize();
         $this->model = new \app\admin\model\shop\Order;
-        $this->view->assign("orderstateList", $this->model->getOrderstateList());
-        $this->view->assign("shippingstateList", $this->model->getShippingstateList());
-        $this->view->assign("paystateList", $this->model->getPaystateList());
+        
+        // 视图赋值
+        $this->view->assign("orderStatusList", OrderEnum::getOrderStatusList());
         $this->view->assign("statusList", $this->model->getStatusList());
+        $this->view->assign("payModeList", OrderEnum::getPayModeList());
+        $this->view->assign("payTypeList", PayEnum::getPayMethodList());
+        $this->view->assign("orderTypeList", OrderEnum::getOrderTypeList());
+        $this->view->assign("deliveryTypeList", OrderEnum::getDeliveryTypeList());
+        $this->view->assign("invoiceStatusList", OrderEnum::getInvoiceStatusList());
+        $this->view->assign("activityTypeList", OrderEnum::getActivityTypeList());
+        $this->view->assign("actionTypeList", OrderEnum::getActionTypeList());
+        $this->view->assign("saleStatusList", GoodsEnum::getSaleStatusMap());
+        
+        // 前端配置
+        $this->assignconfig('orderStatusList', json_encode(OrderEnum::getOrderStatusList()));
+        $this->assignconfig('payModeList', json_encode(OrderEnum::getPayModeList()));
+        $this->assignconfig('payTypeList', json_encode(PayEnum::getPayMethodList()));
+        $this->assignconfig('statusList', json_encode($this->model->getStatusList()));
+        $this->assignconfig('orderTypeList', json_encode(OrderEnum::getOrderTypeList()));
+        $this->assignconfig('deliveryTypeList', json_encode(OrderEnum::getDeliveryTypeList()));
+        $this->assignconfig('invoiceStatusList', json_encode(OrderEnum::getInvoiceStatusList()));
+        $this->assignconfig('activityTypeList', json_encode(OrderEnum::getActivityTypeList()));
+        $this->assignconfig('actionTypeList', json_encode(OrderEnum::getActionTypeList()));
+        $this->assignconfig('saleStatusList', json_encode(GoodsEnum::getSaleStatusMap()));
     }
 
     /**
@@ -56,13 +81,13 @@ class Order extends Backend
             list($where, $sort, $order, $offset, $limit, , $alias) = $this->buildparams();
             $a = reset($alias);
             $list = $this->model
-                ->field($a . '.*,oe.print_template,oe.id oe_id')
-                ->alias($alias)
+                ->with(['OrderAddress','OrderGoods','User'])
+                ->order('createtime desc')
                 ->where($where)
-                ->join('shop_order_electronics oe', $a . '.order_sn=oe.order_sn and oe.status=0', 'LEFT')
-                ->order($sort, $order)
                 ->paginate($limit);
 
+
+
             $result = array("total" => $list->total(), "rows" => $list->items());
 
             return json($result);
@@ -88,15 +113,16 @@ class Order extends Backend
         }
 
         $orderList = collection($orderList)->toArray();
-        foreach ($orderList as $index => &$item) {
+        foreach ($orderList as $index => $item) {
             $nums = 0;
             foreach ($item['order_goods'] as $key => $goods) {
                 if ($goods['salestate'] == 4 || $goods['salestate'] == 5) {
-                    unset($item['order_goods'][$key]);
+                    unset($orderList[$index]['order_goods'][$key]);
+                } else {
+                    $nums += $goods['nums'];
                 }
-                $nums += $goods['nums'];
             }
-            $item['nums'] = $nums;
+            $orderList[$index]['nums'] = $nums;
         }
         $this->success('获取成功', '', ['orderList' => $orderList]);
     }
@@ -108,7 +134,21 @@ class Order extends Backend
         if (!$id) {
             $this->error('参数错误');
         }
-        $row = $this->model->field('o.*,sum(s.refund) refund')->with(['User', 'OrderGoods', 'OrderAction'])
+        
+        // 查询订单信息,包括关联的用户、商品、操作记录、收货地址
+        $row = $this->model
+            ->field('o.*,sum(s.refund) refund')
+            ->with([
+                'User' => function($query) {
+                    $query->field('id,username,nickname,avatar,email,mobile');
+                },
+                'OrderGoods' => function($query) {
+                    $query->field('*');
+                },
+                'OrderAddress' => function($query) {
+                    $query->field('*');
+                }
+            ])
             ->where('o.id', $id)
             ->alias('o')
             ->join('shop_order_aftersales s', 'o.id=s.order_id and s.status=2 and s.type <> 3', 'LEFT')
@@ -118,20 +158,50 @@ class Order extends Backend
             $this->error('记录未找到');
         }
 
-        //计算单个商品折扣后的价格
-        $saleamount = bcsub($row['saleamount'], $row['shippingfee'], 2);
-        $saleratio = $row['goodsprice'] > 0 ? bcdiv($saleamount, $row['goodsprice'], 10) : 1;
-        $saleremains = $saleamount;
-        $orderItem = $row->order_goods;
-        foreach ($orderItem as $index => $item) {
-            if (!isset($orderItem[$index + 1])) {
-                $saleprice = $saleremains;
-            } else {
-                $saleprice = $row['discount'] == 0 ? bcmul($item['price'], $item['nums'], 2) : bcmul(bcmul($item['price'], $item['nums'], 2), $saleratio, 2);
+        // 添加枚举文本转换
+        $row['order_status_text'] = OrderEnum::getOrderStatusText($row['order_status']);
+        $row['source_text'] = $this->getSourceText($row['source']);
+        $row['type_text'] = $this->getTypeText($row['type']);
+        $row['pay_type_text'] = $this->getPayTypeText($row['pay_type']);
+        $row['pay_mode_text'] = $this->getPayModeText($row['pay_mode']);
+        $row['delivery_type_text'] = $this->getDeliveryTypeText($row['delivery_type']);
+        $row['invoice_status_text'] = $this->getInvoiceStatusText($row['invoice_status']);
+        $row['activity_type_text'] = $this->getActivityTypeText($row['activity_type']);
+        
+        // 处理订单商品的规格信息和售后状态文本
+        if ($row->order_goods) {
+            $orderGoods = [];
+            foreach ($row->order_goods as $index => $item) {
+                $itemData = $item->toArray();
+                // 格式化商品规格属性
+                $itemData['goods_sku_attr_formatted'] = $this->formatGoodsSkuAttr($itemData['goods_sku_attr']);
+                // 处理售后状态文本
+                $itemData['sale_status_text'] = $this->getSaleStatusText($itemData['sale_status']);
+                $orderGoods[] = $itemData;
+            }
+            $row['order_goods'] = $orderGoods;
+        }
+        
+        // 单独查询订单操作记录(因为使用order_sn关联)
+        $orderActionResult = Db::name('shop_order_action')
+            ->where('order_sn', $row['order_sn'])
+            ->order('createtime desc')
+            ->select();
+        
+        // 转换为数组并处理操作记录文本
+        $orderActions = [];
+        if ($orderActionResult) {
+            $orderActions = collection($orderActionResult)->toArray();
+            foreach ($orderActions as &$action) {
+                $action['action_type_text'] = $this->getActionTypeText($action['action_type'] ?? '');
             }
-            $saleremains = bcsub($saleremains, $saleprice, 2);
-            $item['saleprice'] = $saleprice;
         }
+        
+        // 将操作记录添加到订单数据中
+        $row['order_action'] = $orderActions;
+
+        // 重新计算商品折扣价格(基于新字段)
+        // $this->calculateGoodsDiscountPrice($row);
 
         $config = get_addon_config('shop');
         $this->assignconfig('shop', $config);
@@ -140,11 +210,151 @@ class Order extends Backend
         return $this->view->fetch();
     }
 
+    /**
+     * 计算商品折扣后的价格
+     */
+    private function calculateGoodsDiscountPrice(&$row)
+    {
+        if (!isset($row['order_goods']) || empty($row['order_goods'])) {
+            return;
+        }
+
+        // 使用新的字段名进行计算
+        $orderAmount = bcsub($row['order_amount'], $row['express_fee'], 2);
+        $goodsPrice = $row['goods_price'];
+        $discountRatio = $goodsPrice > 0 ? bcdiv($orderAmount, $goodsPrice, 10) : 1;
+        $remainingAmount = $orderAmount;
+        
+        $orderGoods = $row['order_goods'];
+        $goodsCount = count($orderGoods);
+        foreach ($orderGoods as $index => $item) {
+            if ($index == $goodsCount - 1) {
+                // 最后一个商品,使用剩余金额
+                $discountPrice = $remainingAmount;
+            } else {
+                // 计算折扣后价格
+                $originalPrice = bcmul($item['goods_original_price'], $item['nums'], 2);
+                $discountPrice = $row['discount_fee'] == 0 ? $originalPrice : bcmul($originalPrice, $discountRatio, 2);
+            }
+            $remainingAmount = bcsub($remainingAmount, $discountPrice, 2);
+            $row['order_goods'][$index]['discount_price'] = $discountPrice;
+        }
+    }
+
+    /**
+     * 获取订单来源文本
+     */
+    private function getSourceText($source)
+    {
+        // 映射数据库中的source字段到ChannelEnum
+        $sourceMapping = [
+            'H5' => ChannelEnum::CHANNEL_H5,
+            'WechatOfficialAccount' => ChannelEnum::CHANNEL_WECHAT_OFFICIAL_ACCOUNT,
+            'WechatMiniProgram' => ChannelEnum::CHANNEL_WECHAT_MINI_PROGRAM,
+            'App' => ChannelEnum::CHANNEL_IOS_APP, // 默认使用iOS
+            'PC' => ChannelEnum::CHANNEL_PC,
+            'Admin' => ChannelEnum::CHANNEL_PC, // 后台创建归为PC
+        ];
+        
+        $channelType = $sourceMapping[$source] ?? $source;
+        return ChannelEnum::getChannelText($channelType);
+    }
+
+    /**
+     * 获取订单类型文本
+     */
+    private function getTypeText($type)
+    {
+        return OrderEnum::getOrderTypeText($type);
+    }
+
+    /**
+     * 获取支付方式文本
+     */
+    private function getPayTypeText($payType)
+    {
+        // 直接使用PayEnum获取支付方式文本
+        return PayEnum::getPayMethodText($payType);
+    }
+
+    /**
+     * 获取支付模式文本
+     */
+    private function getPayModeText($payMode)
+    {
+        // 直接使用OrderEnum的支付模式枚举
+        return OrderEnum::getPayModeText($payMode);
+    }
+
+    /**
+     * 获取发货方式文本
+     */
+    private function getDeliveryTypeText($deliveryType)
+    {
+        return OrderEnum::getDeliveryTypeText($deliveryType);
+    }
+
+    /**
+     * 获取发票状态文本
+     */
+    private function getInvoiceStatusText($invoiceStatus)
+    {
+        return OrderEnum::getInvoiceStatusText($invoiceStatus);
+    }
+
+    /**
+     * 获取活动类型文本
+     */
+    private function getActivityTypeText($activityType)
+    {
+        return OrderEnum::getActivityTypeText($activityType);
+    }
+
+    /**
+     * 获取操作类型文本
+     */
+    private function getActionTypeText($actionType)
+    {
+        return OrderEnum::getActionTypeText($actionType);
+    }
+
+    /**
+     * 获取售后状态文本
+     */
+    private function getSaleStatusText($saleStatus)
+    {
+        return GoodsEnum::getSaleStatusText($saleStatus);
+    }
+
+    /**
+     * 格式化商品规格属性
+     */
+    private function formatGoodsSkuAttr($goodsSkuAttr)
+    {
+        if (empty($goodsSkuAttr)) {
+            return [];
+        }
+
+        // 尝试解析JSON格式的规格信息
+        $decoded = json_decode($goodsSkuAttr, true);
+        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
+            return $decoded;
+        }
+
+        // 如果不是JSON格式,直接返回原始字符串(兼容旧格式)
+        return [
+            [
+                'name' => '规格',
+                'value' => $goodsSkuAttr
+            ]
+        ];
+    }
+
     //订单状态操作
     public function edit_status()
     {
-        $orderstate = $this->request->post('orderstate');
-        $paystate = $this->request->post('paystate');
+        $order_status = $this->request->post('order_status');
+        $pay_time = $this->request->post('pay_time');
         $order_id = $this->request->post('order_id');
 
         $order = $this->model->where('id', $order_id)->find();
@@ -152,97 +362,37 @@ class Order extends Backend
             $this->error('未找到订单记录');
         }
         //取消
-        if ($orderstate == 1 && $order->orderstate == 0 && $order->paystate == 0) {
-            $order->orderstate = 1;
-            $order->canceltime = time();
+        if ($order_status == OrderEnum::STATUS_CANCEL && $order->order_status == OrderEnum::STATUS_CREATE && !$order->pay_time) {
+            $order->order_status = OrderEnum::STATUS_CANCEL;
+            $order->cancel_time = time();
             $order->save();
             $this->success('取消成功');
         }
         // 支付
-        if ($paystate == 1 && $order->orderstate == 0 && $order->paystate == 0) {
-            $order->paystate = 1;
-            $order->paytype = 'system';
-            $order->method = 'system';
-            $order->payamount = $order->saleamount;
-            $order->paytime = time();
+        if ($pay_time && $order->order_status == OrderEnum::STATUS_CREATE && !$order->pay_time) {
+            $order->order_status = OrderEnum::STATUS_PAY;
+            $order->pay_type = 'system';
+            $order->pay_amount = $order->amount;
+            $order->pay_time = time();
             $order->save();
 
             //发送通知
-            TemplateMsg::sendTempMsg(0, $order->order_sn);
+            //TemplateMsg::sendTempMsg(0, $order->order_sn);
             $this->success('操作成功');
         }
         //已完成
-        if ($orderstate == 3 && $order->orderstate == 0 && $order->paystate == 1) {
-            $order->orderstate = 3;
+        if ($order_status == OrderEnum::STATUS_CONFIRM && $order->order_status == OrderEnum::STATUS_PAY && $order->pay_time) {
+            $order->order_status = OrderEnum::STATUS_CONFIRM;
             $order->save();
             OrderAction::push($order->order_sn, '更改订单为已完成', '管理员');
             $this->success('操作成功');
-        } elseif ($order->orderstate == 4) {
+        } elseif ($order->order_status == OrderEnum::STATUS_REFUND) {
             $this->error("请完成售后后再进行操作");
         }
 
         $this->error('没有权限操作');
     }
 
-    //同意退款(退货)
-    public function refund()
-    {
-        $id = $this->request->post('order_goods_id');
-        if (!$id) {
-            $this->error('参数错误');
-        }
-        $orderGoods = OrderGoods::get($id, ['Order']);
-        if (empty($orderGoods)) {
-            $this->error('未找到记录');
-        }
-        $order = $orderGoods->order;
-        if (empty($order)) {
-            $this->error('订单不存在');
-        }
-
-        if ($orderGoods->salestate != 3) {
-            $this->error('不支持的售后状态');
-        }
-        $aftersales = (new OrderAftersales())
-            ->where('order_goods_id', $id)
-            ->where('user_id', $order->user_id)
-            ->where('status', 2)
-            ->order('id desc')
-            ->find();
-
-        if (!$aftersales) {
-            $this->error('售后单未找到');
-        }
-        if ($aftersales['type'] == 2) {
-            //已退货退款
-            $orderGoods->salestate = 5;
-            $orderGoods->save();
-            //已通过,是否同步退款
-            $config = get_addon_config('shop');
-            if ($config['order_refund_sync']) {
-                //执行同步退款
-                try {
-                    \app\admin\model\shop\Order::refund($order->order_sn, $order->paytype, $aftersales->refund);
-                } catch (\Exception $e) {
-                    $this->error($e->getMessage());
-                }
-            }
-            //记录操作
-            OrderAction::push($order->order_sn, '确认售后商品已收到', '管理员');
-
-            $count = \app\admin\model\shop\OrderGoods::where('order_sn', $order['order_sn'])->where('salestate', 'not in', [4, 5])->count();
-            if (!$count) {
-                $order->refundtime = time();
-                $order->orderstate = 3;
-                $order->save();
-            } else {
-                $order->orderstate = 0;
-                $order->save();
-            }
-        }
-        $this->success('确认收货成功!');
-    }
-
 
     //编辑订单信息【备注等】
     public function edit_info()
@@ -274,19 +424,19 @@ class Order extends Backend
             $this->error('未找到订单记录');
         }
         //发货 / 修改快递信息
-        if ($order->orderstate == 0 && $order->shippingstate == 0 && $order->paystate == 1 && $type == 0) {
-            $order->expressname = $expressname;
-            $order->expressno = $expressno;
-            $order->shippingstate = 1;
-            $order->shippingtime = time();
+        if ($order->order_status == OrderEnum::STATUS_PAY && $type == 0) {
+            $order->express_name = $expressname;
+            $order->express_no = $expressno;
+            $order->order_status = OrderEnum::STATUS_SHIP;
+            $order->shipping_time = time();
             $order->save();
             $this->success('发货成功');
         } elseif ($type == 1) {
-            $order->expressname = $expressname;
-            $order->expressno = $expressno;
+            $order->express_name = $expressname;
+            $order->express_no = $expressno;
             $order->save();
             $this->success('修改成功');
-        } elseif ($order->orderstate == 4) {
+        } elseif ($order->order_status == OrderEnum::STATUS_REFUND) {
             $this->error("请完成售后后再进行操作");
         }
         $this->error('没有权限操作');
@@ -366,7 +516,21 @@ class Order extends Backend
         $config = get_addon_config('shop');
         $this->assignconfig('shop', $config);
         $this->assignconfig('order_ids', $order_ids);
-        $order = $this->model->with(['OrderGoods'])->where('id', 'IN', $order_ids)->select();
+        $order = $this->model->with(['OrderGoods', 'OrderAddress'])->where('id', 'IN', $order_ids)->select();
+        
+        // 处理规格信息格式化
+        $processedOrder = [];
+        foreach ($order as $orderItem) {
+            $orderData = $orderItem->toArray();
+            if (isset($orderData['order_goods']) && $orderData['order_goods']) {
+                foreach ($orderData['order_goods'] as $index => $goodsItem) {
+                    $orderData['order_goods'][$index]['goods_sku_attr_formatted'] = $this->formatGoodsSkuAttr($goodsItem['goods_sku_attr']);
+                }
+            }
+            $processedOrder[] = $orderData;
+        }
+        $order = $processedOrder;
+        
         $this->view->assign('order', $order);
         return $this->view->fetch();
     }
@@ -379,7 +543,7 @@ class Order extends Backend
         if ($row['status'] == 1) {
             $this->error('电子面单已取消');
         }
-        $res = \addons\shop\library\KdApiExpOrder::cancel($row);
+        $res = \app\common\library\KdApiExpOrder::cancel($row);
         if (isset($res['Success']) && $res['Success']) {
             $row->status = 1;
             $row->save();
@@ -389,36 +553,5 @@ class Order extends Backend
         $this->error($msg);
     }
 
-    //退款查询
-    protected function refunded($order)
-    {
-        $config = \addons\epay\library\Service::getConfig($order['paytype']);
-        $response = null;
-        try {
-            if ($order['paytype'] == 'wechat') {
-                $response = \Yansongda\Pay\Pay::wechat($config)->find([
-                    'type'         => in_array($order['method'], ['miniapp', 'app']) ? $order['method'] : '',
-                    'out_trade_no' => $order['order_sn']
-                ], 'refund');
-            } elseif ($order['paytype'] == 'alipay') {
-                $response = \Yansongda\Pay\Pay::alipay($config)->find([
-                    'out_trade_no'   => $order['order_sn'],
-                    'out_request_no' => $order['order_sn']
-                ], 'refund');
-            }
-        } catch (\Exception $e) {
-            $this->assign('refunded_info', '查询退款失败,' . $e->getMessage());
-        }
-        if ($order['paytype'] == 'wechat') {
-            if ($response && $response['return_msg'] == 'OK') {
-                $this->assign('refunded_info', '已成功退款:' . $response->refund_fee / 100 . '元。');
-            }
-        } elseif ($order['paytype'] == 'alipay') {
-            if ($response && $response['msg'] == 'Success') {
-                $this->assign('refunded_info', '已成功退款:' . $response->refund_amount . '元。');
-            }
-        } else {
-            $this->assign('refunded_info', "未知支付类型");
-        }
-    }
+ 
 }

+ 71 - 50
application/admin/controller/shop/Report.php

@@ -10,6 +10,8 @@ use app\admin\model\shop\Goods;
 use app\admin\model\shop\Order;
 use app\admin\model\shop\OrderGoods;
 use app\admin\model\shop\OrderAftersales;
+use app\common\Enum\OrderEnum;
+use app\common\Enum\GoodsEnum;
 use think\Db;
 
 /**
@@ -29,13 +31,13 @@ class Report extends Backend
         }
 
         //今日订单和会员
-        $totalOrderAmount = round(Order::where('orderstate', 'IN', [0, 3])->where('paystate', 1)->sum('payamount'), 2);
+        $totalOrderAmount = round(Order::where('order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])->where('pay_time', '>', 0)->sum('pay_amount'), 2);
         $totalRefundAmount = round(OrderAftersales::where('status', 2)->where('type', '<>', 3)->sum('refund'), 2); //退款的
 
-        $yesterdayOrderAmount = round(Order::where('orderstate', 'IN', [0, 3])->whereTime('paytime', 'yesterday')->sum('payamount'), 2);
+        $yesterdayOrderAmount = round(Order::where('order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])->whereTime('pay_time', 'yesterday')->sum('pay_amount'), 2);
         $yesterdayRefundAmount = round(OrderAftersales::where('status', 2)->where('type', '<>', 3)->whereTime('createtime', 'yesterday')->sum('refund'), 2);
 
-        $todayOrderAmount = round(Order::where('orderstate', 'IN', [0, 3])->whereTime('paytime', 'today')->sum('payamount'), 2);
+        $todayOrderAmount = round(Order::where('order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])->whereTime('pay_time', 'today')->sum('pay_amount'), 2);
         $todayRefundAmount = round(OrderAftersales::where('status', 2)->where('type', '<>', 3)->whereTime('createtime', 'today')->sum('refund'), 2);
 
         $todayOrderRatio = $yesterdayOrderAmount > 0 ? ceil((($todayOrderAmount - $yesterdayOrderAmount) / $yesterdayOrderAmount) * 100) : ($todayOrderAmount > 0 ? 100 : 0);
@@ -88,11 +90,11 @@ class Report extends Backend
         //销售排行榜
         $todayPaidList = OrderGoods::alias('og')
             ->join('shop_order o', 'og.order_sn=o.order_sn')
-            ->whereTime('o.paytime', 'today')
-            ->where('o.orderstate', 'IN', [0, 3])
-            ->where('o.paystate', 1)
+            ->whereTime('o.pay_time', 'today')
+            ->where('o.order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])
+            ->where('o.pay_time', '>', 0)
             ->group('og.goods_id')
-            ->field("COUNT(*) as nums,SUM(o.payamount) as amount,og.goods_id,og.title")
+            ->field("COUNT(*) as nums,SUM(o.pay_amount) as amount,og.goods_id,og.goods_title as title")
             ->order("amount", "desc")
             ->limit(10)
             ->select();
@@ -100,14 +102,14 @@ class Report extends Backend
             $item->percent = $totalOrderAmount > 0 ? round(($item['amount'] / $totalOrderAmount) * 100, 2) : 0;
         }
 
-        $weekPaidTotal = Order::where('orderstate', 'IN', [0, 3])->where('paystate', 1)->whereTime('paytime', 'week')->sum("payamount");
+        $weekPaidTotal = Order::where('order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])->where('pay_time', '>', 0)->whereTime('pay_time', 'week')->sum("pay_amount");
         $weekPaidList = OrderGoods::alias('og')
             ->join('shop_order o', 'og.order_sn=o.order_sn')
-            ->whereTime('o.paytime', 'week')
-            ->where('o.orderstate', 'IN', [0, 3])
-            ->where('o.paystate', 1)
+            ->whereTime('o.pay_time', 'week')
+            ->where('o.order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])
+            ->where('o.pay_time', '>', 0)
             ->group('og.goods_id')
-            ->field("COUNT(*) as nums,SUM(o.payamount) as amount,og.goods_id,og.title")
+            ->field("COUNT(*) as nums,SUM(o.pay_amount) as amount,og.goods_id,og.goods_title as title")
             ->order("amount", "desc")
             ->limit(10)
             ->select();
@@ -116,14 +118,14 @@ class Report extends Backend
             $item->percent = $weekPaidTotal > 0 ? round(($item['amount'] / $weekPaidTotal) * 100, 2) : 0;
         }
 
-        $monthPaidTotal = Order::where('orderstate', 'IN', [0, 3])->where('paystate', 1)->whereTime('paytime', 'month')->sum("payamount");
+        $monthPaidTotal = Order::where('order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])->where('pay_time', '>', 0)->whereTime('pay_time', 'month')->sum("pay_amount");
         $monthPaidList = OrderGoods::alias('og')
             ->join('shop_order o', 'og.order_sn=o.order_sn')
-            ->whereTime('o.paytime', 'month')
-            ->where('o.orderstate', 'IN', [0, 3])
-            ->where('o.paystate', 1)
+            ->whereTime('o.pay_time', 'month')
+            ->where('o.order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])
+            ->where('o.pay_time', '>', 0)
             ->group('og.goods_id')
-            ->field("COUNT(*) as nums,SUM(o.payamount) as amount,og.goods_id,og.title")
+            ->field("COUNT(*) as nums,SUM(o.pay_amount) as amount,og.goods_id,og.goods_title as title")
             ->order("amount", "desc")
             ->limit(10)
             ->select();
@@ -215,18 +217,19 @@ class Report extends Backend
             $where = [];
             $group = 'city_id';
             if ($row->level == 1) {
-                $where['o.province_id'] = ['eq', $row->id];
+                $where['oa.province_id'] = ['eq', $row->id];
             } else {
-                $group = 'area_id';
-                $where['o.city_id'] = ['eq', $row->id];
+                $group = 'district_id';
+                $where['oa.city_id'] = ['eq', $row->id];
             }
-            $sql = Order::field('o.id,o.createtime,SUM(o.payamount) as amount,SUM(og.nums) as goods_nums,o.city_id,o.province_id,o.area_id')
+            $sql = Order::field('o.id,o.createtime,SUM(o.pay_amount) as amount,SUM(og.nums) as goods_nums,oa.city_id,oa.province_id,oa.district_id')
                 ->where($where)
                 ->where('o.createtime', 'between time', [$starttime, $endtime])
-                ->where('o.orderstate', 'IN', [0, 3])
-                ->where('o.paystate', 1)
+                ->where('o.order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])
+                ->where('o.pay_time', '>', 0)
                 ->alias('o')
                 ->join('shop_order_goods og', 'o.order_sn=og.order_sn', 'LEFT')
+                ->join('shop_order_address oa', 'o.id=oa.order_id', 'LEFT')
                 ->group('o.id')
                 ->fetchSql(true)
                 ->select();
@@ -307,11 +310,11 @@ class Report extends Backend
             }
         }
         $column = [];
-        $orderList = Order::where('paytime', 'between time', [$starttime, $endtime])
-            ->where('orderstate', 'IN', [0, 3])
-            ->where('paystate', 1)
-            ->field('paytime, status, COUNT(*) AS nums, SUM(payamount) AS amount, MIN(paytime) AS min_paytime, MAX(paytime) AS max_paytime, 
-            DATE_FORMAT(FROM_UNIXTIME(paytime), "' . $format . '") AS paydate')
+        $orderList = Order::where('pay_time', 'between time', [$starttime, $endtime])
+            ->where('order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])
+            ->where('pay_time', '>', 0)
+            ->field('pay_time, status, COUNT(*) AS nums, SUM(pay_amount) AS amount, MIN(pay_time) AS min_paytime, MAX(pay_time) AS max_paytime, 
+            DATE_FORMAT(FROM_UNIXTIME(pay_time), "' . $format . '") AS paydate')
             ->group('paydate')
             ->select();
 
@@ -422,10 +425,11 @@ class Report extends Backend
         }
         $sql = Order::alias('o')
             ->where('o.createtime', 'between time', [$starttime, $endtime])
-            ->where('o.orderstate', 'IN', [0, 3])
-            ->where('o.paystate', 1)
+            ->where('o.order_status', 'IN', [OrderEnum::STATUS_PAY, OrderEnum::STATUS_SHIP, OrderEnum::STATUS_CONFIRM, OrderEnum::STATUS_AUTO_CONFIRM])
+            ->where('o.pay_time', '>', 0)
             ->join('shop_order_goods og', 'og.order_sn=o.order_sn', 'LEFT')
-            ->field('o.createtime,SUM(o.payamount) as amount,SUM(og.nums) as goods_nums,o.province_id')
+            ->join('shop_order_address oa', 'o.id=oa.order_id', 'LEFT')
+            ->field('o.createtime,SUM(o.pay_amount) as amount,SUM(og.nums) as goods_nums,oa.province_id')
             ->group('o.id')
             ->fetchSql(true)
             ->select();
@@ -477,6 +481,7 @@ class Report extends Backend
     }
 
     //订单按分类分布
+    //订单按分类分布
     protected function getCategoryOrder($date = '')
     {
         if ($date) {
@@ -487,34 +492,51 @@ class Report extends Backend
             $starttime = \fast\Date::unixtime('day', 0, 'begin');
             $endtime = \fast\Date::unixtime('day', 0, 'end');
         }
-        $orderList = Order::where('o.createtime', 'between time', [$starttime, $endtime])
-            ->where('o.orderstate', 'IN', [0, 3])
-            ->where('o.paystate', 1)
+        // 查询所有订单商品及其分类
+        $orderGoodsList = \app\admin\model\shop\Order::where('o.createtime', 'between time', [$starttime, $endtime])
+            ->where('o.order_status', 'IN', [
+                \app\common\Enum\OrderEnum::STATUS_PAY,
+                \app\common\Enum\OrderEnum::STATUS_SHIP,
+                \app\common\Enum\OrderEnum::STATUS_CONFIRM,
+                \app\common\Enum\OrderEnum::STATUS_AUTO_CONFIRM
+            ])
+            ->where('o.pay_time', '>', 0)
             ->alias('o')
             ->join('shop_order_goods og', 'o.order_sn=og.order_sn', 'LEFT')
             ->join('shop_goods g', 'og.goods_id=g.id', 'LEFT')
-            ->field('SUM(og.nums) nums,g.category_id,SUM(o.payamount) amount')
-            ->group('g.category_id')
+            ->field('og.nums, g.category_ids, o.pay_amount')
             ->select();
-        $list = [];
-        foreach ($orderList as $item) {
-            $list[$item['category_id']] = $item;
+
+        $categoryStats = [];
+        foreach ($orderGoodsList as $item) {
+            if (empty($item['category_ids'])) continue;
+            $categoryIds = explode(',', $item['category_ids']);
+            foreach ($categoryIds as $cid) {
+                $cid = trim($cid);
+                if ($cid === '') continue;
+                if (!isset($categoryStats[$cid])) {
+                    $categoryStats[$cid] = ['nums' => 0, 'amount' => 0];
+                }
+                $categoryStats[$cid]['nums'] += $item['nums'];
+                $categoryStats[$cid]['amount'] += $item['pay_amount'];
+            }
         }
-        $category = Db::name('shop_category')->field('id,name')->select();
+
+        // 获取所有分类
+        $category = \think\Db::name('shop_category')->field('id,name')->select();
         $legendData = [];
         $seriesData = [];
         foreach ($category as $item) {
-            if (isset($list[$item['id']])) {
-                $seriesData[] = [
-                    'name'  => $item['name'],
-                    'value' => $list[$item['id']]['nums']
-                ];
-                $legendData[] = $item['name'];
-            }
+            $cid = $item['id'];
+            $legendData[] = $item['name'];
+            $seriesData[] = [
+                'name'  => $item['name'],
+                'value' => isset($categoryStats[$cid]) ? $categoryStats[$cid]['nums'] : 0
+            ];
         }
-        //为空全部
+        // 为空全部
         if (empty($legendData)) {
-            $legendData = array_column($category, 'name');
+            $legendData = array_column($category ? $category->toArray() : [], 'name');
             foreach ($legendData as $item) {
                 $seriesData[] = [
                     'name'  => $item,
@@ -522,7 +544,6 @@ class Report extends Backend
                 ];
             }
         }
-
         return [
             $legendData,
             $seriesData

+ 37 - 0
application/admin/controller/user/Share.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace app\admin\controller\user;
+
+use app\common\controller\Backend;
+
+/**
+ * 用户分享记录
+ *
+ * @icon fa fa-circle-o
+ */
+class Share extends Backend
+{
+
+    /**
+     * Share模型对象
+     * @var \app\admin\model\user\Share
+     */
+    protected $model = null;
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = new \app\admin\model\user\Share;
+
+    }
+
+
+
+    /**
+     * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
+     * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
+     * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
+     */
+
+
+}

+ 49 - 10
application/admin/lang/zh-cn/shop/order.php

@@ -5,12 +5,57 @@ return [
     'Order_sn'        => '订单号',
     'User_id'         => '用户ID',
     'User'            => '用户',
-    'Address_id'      => '收款地址ID',
+    'Source'          => '订单来源',
+    'Type'            => '订单类型',
+    'Score'           => '积分',
+    'Goods_num'       => '商品数量',
+    'Amount'          => '订单金额',
+    'Goods_price'     => '商品总费用',
+    'Discount_fee'    => '优惠金额',
+    'Coupon_discount_fee' => '优惠券金额',
+    'Promo_discount_fee'  => '营销金额',
+    'Order_amount'    => '订单金额',
+    'Pay_amount'      => '实际付款金额',
+    'Pay_type'        => '支付方式',
+    'Pay_mode'        => '支付模式',
+    'Pay_time'        => '支付时间',
+    'Delivery_type'   => '发货方式',
+    'Express_name'    => '快递名称',
+    'Express_no'      => '快递单号',
+    'Express_fee'     => '配送费用',
+    'Expire_time'     => '过期时间',
+    'Refund_time'     => '退货时间',
+    'Shipping_time'   => '配送时间',
+    'Receive_time'    => '收货时间',
+    'Cancel_time'     => '取消时间',
+    'Order_status'    => '订单状态',
+    'Invoice_status'  => '发票开具状态',
+    'Remark'          => '用户备注',
+    'User_coupon_id'  => '优惠券记录ID',
+    'Activity_type'   => '活动类型',
+    'Activity_id'     => '活动ID',
+    'Ip'              => 'IP地址',
+    'Createtime'      => '创建时间',
+    'Updatetime'      => '更新时间',
+    'Deletetime'      => '删除时间',
+    'Status'          => '状态',
+    'Status normal'   => '正常',
+    'Status hidden'   => '隐藏',
+    
+    // 收货地址相关
+    'Consignee'       => '收货人',
+    'Mobile'          => '收货手机',
+    'Province_name'   => '省份',
+    'City_name'       => '城市',
+    'District_name'   => '地区',
+    'Address'         => '详细地址',
+    'Province_id'     => '省ID',
+    'City_id'         => '市ID',
+    'District_id'     => '区ID',
+    
+    // 兼容旧字段
     'Receiver'        => '收货人的姓名',
-    'Address'         => '收货人的地址',
     'Zipcode'         => '收货人的邮编',
-    'Mobile'          => '收货人的手机',
-    'Amount'          => '订单总价',
     'Goodsprice'      => '商品总价',
     'Discount'        => '优惠金额',
     'Shippingfee'     => '配送费用',
@@ -21,15 +66,12 @@ return [
     'Transactionid'   => '交易流水号',
     'Expressname'     => '快递名称',
     'Expressno'       => '快递单号',
-    'Createtime'      => '创建时间',
-    'Updatetime'      => '更新时间',
     'Expiretime'      => '过期时间',
     'Paytime'         => '支付时间',
     'Refundtime'      => '退货时间',
     'Shippingtime'    => '配送时间',
     'Receivetime'     => '收货时间',
     'Canceltime'      => '取消时间',
-    'Deletetime'      => '删除时间',
     'Orderstate'      => '订单状态',
     'Orderstate 0'    => '正常',
     'Orderstate 1'    => '已取消',
@@ -45,9 +87,6 @@ return [
     'Paystate 0'      => '未付款',
     'Paystate 1'      => '已付款',
     'Memo'            => '备注',
-    'Status'          => '状态',
-    'Status normal'   => '正常',
-    'Status hidden'   => '隐藏',
     'Salestate'       => '售后状态',
     'Salestate 0'     => '无',
     'Salestate 1'     => '已申请',

+ 16 - 0
application/admin/lang/zh-cn/user/share.php

@@ -0,0 +1,16 @@
+<?php
+
+return [
+    'Spm'        => '加密的邀请数据',
+    'User_id'    => '用户',
+    'Share_id'   => '分享人',
+    'Page'       => '分享页面',
+    'Query'      => '分享页面参数',
+    'Platform'   => '分享平台',
+    'Mode'       => '分享方式',
+    'Ext'        => '附加信息',
+    'Status'     => '状态:0:禁止;1:是',
+    'Createtime' => '创建时间',
+    'Updatetime' => '更新时间',
+    'Deletetime' => '删除时间'
+];

+ 64 - 72
application/admin/model/shop/Order.php

@@ -10,8 +10,10 @@ use app\admin\model\shop\OrderAction;
 use addons\epay\library\Service;
 use Yansongda\Pay\Pay;
 use think\Log;
-use addons\shop\model\TemplateMsg;
-use addons\shop\model\UserCoupon;
+use app\common\Enum\OrderEnum;
+use app\common\Enum\StatusEnum;
+use app\common\Service\Order\OrderGoodsService;
+use app\common\Enum\PayEnum;
 
 class Order extends Model
 {
@@ -33,35 +35,24 @@ class Order extends Model
     protected $append = [
         'expiretime_text',
         'paytime_text',
-        'refundtime_text',
-        'shippingtime_text',
-        'receivetime_text',
-        'canceltime_text',
-        'orderstate_text',
-        'shippingstate_text',
-        'paystate_text',
-        'status_text'
+        'refund_time_text',
+        'shipping_time_text',
+        'receive_time_text',
+        'cancel_time_text',
+        'status_text',
+        'order_status_text',
+        'pay_mode_text',
+        'pay_type_text',
     ];
 
-
-    public function getOrderstateList()
-    {
-        return ['0' => __('Orderstate 0'), '1' => __('Orderstate 1'), '2' => __('Orderstate 2'), '3' => __('Orderstate 3'), '4' => __('Orderstate 4')];
-    }
-
-    public function getShippingstateList()
+    public function getOrderStatusList()
     {
-        return ['0' => __('Shippingstate 0'), '1' => __('Shippingstate 1'), '2' => __('Shippingstate 2')];
-    }
-
-    public function getPaystateList()
-    {
-        return ['0' => __('Paystate 0'), '1' => __('Paystate 1')];
+        return OrderEnum::getOrderStatusList();
     }
 
     public function getStatusList()
     {
-        return ['normal' => __('Status normal'), 'hidden' => __('Status hidden')];
+        return StatusEnum::getMap();
     }
 
 
@@ -107,37 +98,35 @@ class Order extends Model
     }
 
 
-    public function getOrderstateTextAttr($value, $data)
+    public function getOrderStatusTextAttr($value, $data)
     {
-        $value = $value ?: ($data['orderstate'] ?? '');
-        $list = $this->getOrderstateList();
+        $value = $value ?: ($data['order_status'] ?? '');
+        $list = $this->getOrderStatusList();
         return $list[$value] ?? '';
     }
 
 
-    public function getShippingstateTextAttr($value, $data)
+
+    public function getStatusTextAttr($value, $data)
     {
-        $value = $value ?: ($data['shippingstate'] ?? '');
-        $list = $this->getShippingstateList();
+        $value = $value ?: ($data['status'] ?? '');
+        $list = $this->getStatusList();
         return $list[$value] ?? '';
     }
 
-
-    public function getPaystateTextAttr($value, $data)
+    public function getPayModeTextAttr($value, $data)
     {
-        $value = $value ?: ($data['paystate'] ?? '');
-        $list = $this->getPaystateList();
-        return $list[$value] ?? '';
+        $value = $value ?: ($data['pay_mode'] ?? '');
+        return OrderEnum::getPayModeText($value);
     }
 
-
-    public function getStatusTextAttr($value, $data)
+    public function getPayTypeTextAttr($value, $data)
     {
-        $value = $value ?: ($data['status'] ?? '');
-        $list = $this->getStatusList();
-        return $list[$value] ?? '';
+        $value = $value ?: ($data['pay_type'] ?? '');
+        return PayEnum::getPayMethodText($value);
     }
 
+
     protected function setExpiretimeAttr($value)
     {
         return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value);
@@ -172,20 +161,18 @@ class Order extends Model
     {
         self::afterWrite(function ($row) {
             $changeData = $row->getChangedData();
-            if (isset($changeData['paystate'])) {
-                if ($changeData['paystate'] == 1) {
-                    \addons\shop\model\OrderGoods::setGoodsSalesInc($row->order_sn);
+            if (isset($changeData['order_status'])) {
+                if ($changeData['order_status'] == OrderEnum::STATUS_SHIP) {
+                    OrderGoodsService::setGoodsSalesInc($row->order_sn);
                 }
-                OrderAction::push($row->order_sn, $changeData['paystate'] == 1 ? '更改订单为已支付' : '更改订单为未支付', '管理员');
+                OrderAction::push($row->order_sn, $changeData['order_status'] == OrderEnum::STATUS_PAY ? '更改订单为已支付' : '更改订单为未支付', '管理员');
             }
-            if (isset($changeData['shippingstate'])) {
-                switch ($changeData['shippingstate']) {
-                    case 1:
-                        //发送通知
-                        TemplateMsg::sendTempMsg(1, $row->order_sn);
+            if (isset($changeData['order_status'])) {
+                switch ($changeData['order_status']) {
+                    case OrderEnum::STATUS_SHIP:
                         $memo = '订单发货成功';
                         break;
-                    case 2:
+                    case OrderEnum::STATUS_CONFIRM:
                         $memo = '更改订单已收货';
                         break;
                     default:
@@ -193,35 +180,35 @@ class Order extends Model
                 }
                 OrderAction::push($row->order_sn, $memo, '管理员');
             }
-            if (isset($changeData['orderstate'])) {
+            if (isset($changeData['order_status'])) {
                 $memo = '';
-                switch ($changeData['orderstate']) {
-                    case 0:
+                switch ($changeData['order_status']) {
+                    case OrderEnum::STATUS_CREATE:
                         OrderAction::push($row->order_sn, '更改订单为正常', '管理员');
                         break;
-                    case 1:
+                    case OrderEnum::STATUS_CANCEL:
                         //已取消,库存恢复
-                        \addons\shop\model\OrderGoods::setGoodsStocksInc($row->order_sn);
-                        //恢复优惠券
-                        UserCoupon::resetUserCoupon($row->user_coupon_id, $row->order_sn);
+                        OrderGoodsService::setGoodsStocksInc($row->order_sn);
+                        // //恢复优惠券
+                        // UserCoupon::resetUserCoupon($row->user_coupon_id, $row->order_sn);
                         OrderAction::push($row->order_sn, '订单取消成功', '管理员');
                         break;
-                    case 2:
+                    case OrderEnum::STATUS_ADMIN_CANCEL:
                         OrderAction::push($row->order_sn, '更改订单为已失效', '管理员');
                         break;
-                    case 3:
+                    case OrderEnum::STATUS_AUTO_CONFIRM:
                         //结束,订单完成,给积分
-                        $config = get_addon_config('shop');
-                        if (isset($config['money_score']) && $config['money_score'] > 0 && $row->shippingstate == 2 && $row->paystate == 1) {
-                            //减去退款金额
-                            $refund = OrderAftersales::where('order_id', $row->id)->where('type', '<>', 3)->where('status', 2)->sum('refund');
-                            $money = bcsub($row['payamount'], $refund, 2);
-                            if ($money > 0) {
-                                $score = bcmul($money, $config['money_score']);
-                                \app\common\model\User::score($score, $row['user_id'], '完成订单奖励' . $score . '积分');
-                            }
-                        }
-                        OrderAction::push($row->order_sn, '更改订单为已完成', '管理员');
+                        // $config = get_addon_config('shop');
+                        // if (isset($config['money_score']) && $config['money_score'] > 0 && $row->shippingstate == 2 && $row->paystate == 1) {
+                        //     //减去退款金额
+                        //     $refund = OrderAftersales::where('order_id', $row->id)->where('type', '<>', 3)->where('status', 2)->sum('refund');
+                        //     $money = bcsub($row['payamount'], $refund, 2);
+                        //     if ($money > 0) {
+                        //         $score = bcmul($money, $config['money_score']);
+                        //         \app\common\model\User::score($score, $row['user_id'], '完成订单奖励' . $score . '积分');
+                        //     }
+                        // }
+                        // OrderAction::push($row->order_sn, '更改订单为已完成', '管理员');
                         break;
                 }
             }
@@ -262,7 +249,7 @@ class Order extends Model
         }
 
         //发送通知
-        TemplateMsg::sendTempMsg(2, $order_sn);
+        // TemplateMsg::sendTempMsg(2, $order_sn);
         return true;
     }
 
@@ -271,9 +258,14 @@ class Order extends Model
         return $this->hasOne('\\app\\common\\model\\User', 'id', 'user_id', [], 'LEFT');
     }
 
+    public function OrderAddress()
+    {
+        return $this->hasOne('\\app\\common\\model\\OrderAddress', 'order_id', 'id', [], 'LEFT');
+    }
+
     public function OrderGoods()
     {
-        return $this->hasMany('OrderGoods', 'order_sn', 'order_sn');
+        return $this->hasMany('OrderGoods', 'order_id', 'id');
     }
 
     public function OrderAction()

+ 40 - 0
application/admin/model/user/Share.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace app\admin\model\user;
+
+use think\Model;
+use traits\model\SoftDelete;
+
+class Share extends Model
+{
+
+    use SoftDelete;
+
+    
+
+    // 表名
+    protected $name = 'shop_user_share';
+    
+    // 自动写入时间戳字段
+    protected $autoWriteTimestamp = 'integer';
+
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = 'deletetime';
+
+    // 追加属性
+    protected $append = [
+
+    ];
+    
+
+    
+
+
+
+
+
+
+
+}

+ 27 - 0
application/admin/validate/user/Share.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace app\admin\validate\user;
+
+use think\Validate;
+
+class Share extends Validate
+{
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+    ];
+    /**
+     * 提示消息
+     */
+    protected $message = [
+    ];
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => [],
+        'edit' => [],
+    ];
+    
+}

+ 1 - 1
application/admin/view/pay/config/add.html

@@ -11,7 +11,7 @@
         <div class="col-xs-12 col-sm-8">
             <div class="radio">
                 {foreach name="methodList" item="vo"}
-                <label for="row[type]-{$key}"><input id="row[type]-{$key}" name="row[type]" type="radio" value="{$key}" {in name="key" value="wechat"}checked{/in} /> {$vo}</label> 
+                <label for="row[type]-{$key}"><input id="row[type]-{$key}" name="row[type]" type="radio" value="{$key}" {in name="key" value="douyin"}checked{/in} /> {$vo}</label> 
                 {/foreach}  
             </div>          
         </div>

+ 160 - 88
application/admin/view/shop/order/detail.html

@@ -72,89 +72,115 @@
                         <li class="list-group-item order-status">
                             <div>
                                 订单状态:
-                                {if $row['orderstate']==0}
-                                    {if $row['paystate'] == 0}
-                                        <span class="label label-danger">未付款</span>
-                                    {elseif $row['paystate'] == 1}
-
-                                        {if $row['shippingstate'] == 0}
-                                            <span class="label label-danger">待发货</span>
-                                        {elseif $row['shippingstate'] == 1}
-                                            <span class="label label-danger">已发货</span>
-                                        {elseif $row['shippingstate'] == 2}
-                                            <span class="label label-danger">待评价</span>
-                                        {/if}
-
-                                    {/if}
-                                {elseif $row['orderstate'] == 1}
-                                    <span class="label label-danger">已取消</span>
-                                {elseif $row['orderstate'] == 2}
-                                    <span class="label label-danger">已失效</span>
-                                {elseif $row['orderstate'] == 3}
-                                    <span class="label label-danger">已完成</span>
-                                {elseif $row['orderstate'] == 4}
-                                    <span class="label label-danger">退货/退款中</span>
-                                    {if $row['shippingstate'] == 0}
-                                        <span class="label label-danger">待发货</span>
-                                    {elseif $row['shippingstate'] == 1}
-                                        <span class="label label-danger">已发货</span>
-                                    {elseif $row['shippingstate'] == 2}
-                                        <span class="label label-danger">待评价</span>
-                                    {/if}
-                                {/if}
+                                <span class="label label-danger">{$row.order_status_text|default='未知'}</span>
                             </div>
                             <div>
-                                {if $row['orderstate']==0 || $row['orderstate']==4}
-                                    {if $row['paystate'] == 0}
-                                        <button type="button" class="btn btn-success btn-status-click" data-order='{"orderstate":1}'>取消</button>
-                                        <button type="button" class="btn btn-success btn-status-click" data-order='{"paystate":1}'>设为已支付</button>
-                                    {elseif $row['paystate'] == 1}
-                                        {if $row['shippingstate'] == 0}
-                                            <button type="button" class="btn btn-success btn-deliver" data-expressname="{$row.expressname|htmlentities}" data-expressno="{$row.expressno|htmlentities}" data-type="0">发货</button>
-                                        {elseif $row['shippingstate'] == 1}
-                                            <button type="button" class="btn btn-success btn-deliver" data-expressname="{$row.expressname|htmlentities}" data-expressno="{$row.expressno|htmlentities}" data-type="1">修改快递</button>
-                                            <button type="button" class="btn btn-success btn-logistics" data-expressno="{$row.expressno|htmlentities}">查询物流</button>
-                                        {/if}
-                                        <button type="button" class="btn btn-success btn-status-click" data-order='{"orderstate":3}'>设为已完成</button>
-                                    {/if}
+                                {if $row['order_status']==101}
+                                    <!-- 待支付状态 -->
+                                    <button type="button" class="btn btn-danger btn-status-click" data-order='{"order_status":102}'>
+                                        <i class="fa fa-times"></i> 取消订单
+                                    </button>
+                                    <button type="button" class="btn btn-success btn-status-click" data-order='{"pay_time":1}'>
+                                        <i class="fa fa-check"></i> 设为已支付
+                                    </button>
+                                {elseif $row['order_status']==201}
+                                    <!-- 已支付状态 -->
+                                    <button type="button" class="btn btn-primary btn-deliver" data-expressname="{$row.express_name|htmlentities}" data-expressno="{$row.express_no|htmlentities}" data-type="0">
+                                        <i class="fa fa-truck"></i> 发货
+                                    </button>
+                                    <button type="button" class="btn btn-success btn-status-click" data-order='{"order_status":401}'>
+                                        <i class="fa fa-check-circle"></i> 设为已完成
+                                    </button>
+                                {elseif $row['order_status']==301}
+                                    <!-- 已发货状态 -->
+                                    <button type="button" class="btn btn-warning btn-deliver" data-expressname="{$row.express_name|htmlentities}" data-expressno="{$row.express_no|htmlentities}" data-type="1">
+                                        <i class="fa fa-edit"></i> 修改快递
+                                    </button>
+                                    <button type="button" class="btn btn-info btn-logistics" data-expressno="{$row.express_no|htmlentities}">
+                                        <i class="fa fa-search"></i> 查询物流
+                                    </button>
+                                    <button type="button" class="btn btn-success btn-status-click" data-order='{"order_status":401}'>
+                                        <i class="fa fa-check-circle"></i> 设为已完成
+                                    </button>
+                                {elseif $row['order_status']==401}
+                                    <!-- 已完成状态 -->
+                                    <span class="label label-success">
+                                        <i class="fa fa-check-circle"></i> 订单已完成
+                                    </span>
+                                {elseif $row['order_status']==102}
+                                    <!-- 已取消状态 -->
+                                    <span class="label label-danger">
+                                        <i class="fa fa-times-circle"></i> 订单已取消
+                                    </span>
+                                {else/}
+                                    <!-- 其他状态 -->
+                                    <span class="label label-default">状态异常</span>
                                 {/if}
                             </div>
                         </li>
                         <!--@formatter:on-->
-                        <li class="list-group-item">下单用户:{$row.user.nickname|default='未知'}(用户ID:{$row.user_id|htmlentities})</li>
-                        <li class="list-group-item">商品费用:¥{$row.goodsprice|htmlentities}</li>
-                        <li class="list-group-item">快递费用:¥{$row.shippingfee|htmlentities}</li>
-                        <li class="list-group-item">折扣金额:¥{$row.discount|htmlentities}</li>
-                        <li class="list-group-item">订单金额:¥{$row.amount|htmlentities}</li>
-                        <li class="list-group-item">应付金额:¥{$row.saleamount|htmlentities}</li>
-                        <li class="list-group-item">实付金额:¥{$row.payamount|htmlentities}</li>
+                        <li class="list-group-item">
+                            <div style="display:flex;align-items:center;">
+                                <span style="margin-right:10px;">下单用户:</span>
+                                <img src="{$row.user.avatar|default='/assets/img/avatar.png'}" style="width:40px;height:40px;border-radius:50%;margin-right:10px;" />
+                                <div>
+                                    <div style="color:#337ab7;font-weight:bold;">{$row.user.username|default='游客'}</div>
+                                    <div style="color:#999;font-size:12px;">ID: {$row.user_id|htmlentities}</div>
+                                </div>
+                            </div>
+                        </li>
+                        <li class="list-group-item">订单来源:{$row.source_text|default='未知'}</li>
+                        <li class="list-group-item">订单类型:{$row.type_text|default='普通订单'}</li>
+                        <li class="list-group-item">商品数量:{$row.goods_num|htmlentities}</li>
+                        <li class="list-group-item">商品费用:¥{$row.goods_price|htmlentities}</li>
+                        <li class="list-group-item">快递费用:¥{$row.express_fee|htmlentities}</li>
+                        <li class="list-group-item">折扣金额:¥{$row.discount_fee|htmlentities}</li>
+                        <li class="list-group-item">优惠券金额:¥{$row.coupon_discount_fee|default='0.00'}</li>
+                        <li class="list-group-item">营销金额:¥{$row.promo_discount_fee|default='0.00'}</li>
+                        <li class="list-group-item">订单金额:¥{$row.order_amount|htmlentities}</li>
+                        <li class="list-group-item">实付金额:¥{$row.pay_amount|htmlentities}</li>
                         <li class="list-group-item">
                             <span style="width: 140px;">退款金额:¥{if !empty($row.refund)} {$row.refund|htmlentities} {else/}0.00{/if}</span>
                             <span></span>
                         </li>
+                        {if !empty($row.user_coupon_id)}
+                        <li class="list-group-item">优惠券ID:{$row.user_coupon_id|htmlentities}</li>
+                        {/if}
+                        {if !empty($row.activity_type)}
+                        <!-- <li class="list-group-item">活动类型:{$row.activity_type_text|default=$row.activity_type|default='未知'}</li> -->
+                        {/if}
+                        {if !empty($row.activity_id)}
+                        <!-- <li class="list-group-item">活动ID:{$row.activity_id|htmlentities}</li> -->
+                        {/if}
+                        {if !empty($row.score)}
+                        <!-- <li class="list-group-item">使用积分:{$row.score|htmlentities}</li> -->
+                        {/if}
                     </ul>
                 </div>
                 <div class="col-xs-6">
                     <ul class="list-group">
                         <li class="list-group-item">下单时间:{$row.createtime|date="Y-m-d H:i:s",###}</li>
-                        <li class="list-group-item">支付方法:{$row.method|htmlentities}</li>
-                        <li class="list-group-item">支付类型:{$row.paytype|htmlentities}</li>
+                        <li class="list-group-item">过期时间:{notempty name="row.expire_time"}{$row.expire_time|date="Y-m-d H:i:s",###}{else/}无{/notempty}</li>
+                        <li class="list-group-item">支付方式:{$row.pay_type_text|default=$row.pay_type|default='未知'}</li>
+                        <li class="list-group-item">支付模式:{$row.pay_mode_text|default=$row.pay_mode|default='未知'}</li>
                         <li class="list-group-item">交易流水号:{$row.transactionid|htmlentities}</li>
-                        <li class="list-group-item">支付时间:{notempty name="row.paytime"}{$row.paytime|date="Y-m-d H:i:s",###}{/notempty}</li>
-                        <li class="list-group-item">退货时间:{notempty name="row.refundtime"}{$row.refundtime|date="Y-m-d H:i:s",###}{/notempty}</li>
-                        <li class="list-group-item">发货时间:{notempty name="row.shippingtime"}{$row.shippingtime|date="Y-m-d H:i:s",###}{/notempty}</li>
-                        <li class="list-group-item">收货时间:{notempty name="row.receivetime"}{$row.receivetime|date="Y-m-d H:i:s",###}{/notempty}</li>
-                        <li class="list-group-item">取消时间:{notempty name="row.canceltime"}{$row.canceltime|date="Y-m-d H:i:s",###}{/notempty}</li>
+                        <li class="list-group-item">支付时间:{notempty name="row.pay_time"}{$row.pay_time|date="Y-m-d H:i:s",###}{else/}未支付{/notempty}</li>
+                        <!-- <li class="list-group-item">退货时间:{notempty name="row.refund_time"}{$row.refund_time|date="Y-m-d H:i:s",###}{else/}无{/notempty}</li> -->
+                        <li class="list-group-item">发货时间:{notempty name="row.shipping_time"}{$row.shipping_time|date="Y-m-d H:i:s",###}{else/}未发货{/notempty}</li>
+                        <li class="list-group-item">收货时间:{notempty name="row.receive_time"}{$row.receive_time|date="Y-m-d H:i:s",###}{else/}未收货{/notempty}</li>
+                        <li class="list-group-item">取消时间:{notempty name="row.cancel_time"}{$row.cancel_time|date="Y-m-d H:i:s",###}{else/}未取消{/notempty}</li>
+                        <li class="list-group-item">更新时间:{$row.updatetime|date="Y-m-d H:i:s",###}</li>
+                        <li class="list-group-item">下单IP:{$row.ip|default='未知'}</li>
                         <li class="list-group-item">
                             <div class="groups">
-                                <span style="min-width: 75px;">备注信息:</span>
-                                <div>{$row.memo|htmlentities}</div>
-                                <input class="form-control hide" type="text" value="{$row.memo|htmlentities}">
+                                <span style="min-width: 75px;">用户备注:</span>
+                                <div>{$row.remark|htmlentities}</div>
+                                <input class="form-control hide" type="text" value="{$row.remark|htmlentities}">
                             </div>
                             <div class="memo-operate">
                                 <a class="btn btn-success btn-edit" data-status="edit" href="javascript:;">编辑</a>
                                 <a class="btn btn-success hide btn-cancel" data-status="cancel" href="javascript:;">取消</a>
-                                <a class="btn btn-success hide btn-save" data-status="save" data-field="memo" data-id="{$row.id|htmlentities}" href="javascript:;">保存</a>
+                                <a class="btn btn-success hide btn-save" data-status="save" data-field="remark" data-id="{$row.id|htmlentities}" href="javascript:;">保存</a>
                             </div>
                         </li>
                     </ul>
@@ -181,13 +207,26 @@
         <div class="panel-body">
             <div class="row">
                 <div class="col-xs-6">
-                    <p>收货人的姓名:{$row.receiver|htmlentities}</p>
-                    <p>收货人的手机:{$row.mobile|htmlentities}</p>
-                    <p>收货人的地址:{$row.address|htmlentities}</p>
+                    <p>收货人姓名:{$row.order_address.consignee|default='未填写'}</p>
+                    <p>收货人手机:{$row.order_address.mobile|default='未填写'}</p>
+                    <p>收货地址:
+                        {if !empty($row.order_address)}
+                            {$row.order_address.province_name|default=''}
+                            {$row.order_address.city_name|default=''}
+                            {$row.order_address.district_name|default=''}
+                            {$row.order_address.address|default=''}
+                        {else/}
+                            未填写
+                        {/if}
+                    </p>
+                    <p>地址创建时间:{if !empty($row.order_address)}{$row.order_address.createtime|date="Y-m-d H:i:s",###}{else/}未知{/if}</p>
+                    <p>发货方式:{$row.delivery_type_text|default=$row.delivery_type|default='未知'}</p>
                 </div>
                 <div class="col-xs-6">
-                    <p>发货快递名称:{$row.expressname|htmlentities}</p>
-                    <p>发货快递单号:{$row.expressno|htmlentities}</p>
+                    <p>快递公司:{$row.express_name|htmlentities}</p>
+                    <p>快递单号:{$row.express_no|htmlentities}</p>
+                    <p>快递费用:¥{$row.express_fee|htmlentities}</p>
+                    <p>发票状态:{$row.invoice_status_text|default=$row.invoice_status|default='未开发票'}</p>
                 </div>
             </div>
         </div>
@@ -204,13 +243,13 @@
                     <tr>
                         <th class="text-center">商品ID</th>
                         <th class="text-center">货号</th>
-                        <th>名称</th>
-                        <th class="text-center">图片</th>
-                        <th class="text-center">属性</th>
+                        <th>商品名称</th>
+                        <th class="text-center">商品图片</th>
+                        <th class="text-center">规格属性</th>
                         <th class="text-center">市场价</th>
-                        <th class="text-center">商城价</th>
+                        <th class="text-center">价</th>
                         <th class="text-center">数量</th>
-                        <th class="text-center">实际价</th>
+                        <th class="text-center">小计</th>
                         <th class="text-center">售后状态</th>
                     </tr>
                     </thead>
@@ -219,21 +258,41 @@
                     <tr>
                         <th class="text-center" scope="row">{$item['goods_id']}</th>
                         <th class="text-center">{$item['goods_sn']}</th>
-                        <td>{$item['title']}</td>
+                        <td>
+                            <div>{$item['goods_title']}</div>
+                            {if !empty($item['goods_subtitle'])}
+                                <div style="color:#999;font-size:12px;">{$item['goods_subtitle']}</div>
+                            {/if}
+                        </td>
                         <td class="text-center">
-                            <img src="{$item['image']}" class="img-sm" alt="">
+                            <img src="{$item['goods_image']}" class="img-sm" alt="" style="max-width:80px;max-height:80px;">
                         </td>
-                        <td class="text-center">{$item.attrdata|htmlentities}</td>
-                        <td class="text-center">{$item.marketprice|htmlentities}</td>
-                        <td class="text-center">{$item.price|htmlentities}</td>
+                        <td class="text-center">
+                            {if !empty($item.goods_sku_attr_formatted)}
+                                <div class="sku-attr-list">
+                                    {foreach name="item.goods_sku_attr_formatted" item="attr"}
+                                        <div class="sku-attr-item" style="margin-bottom:2px;">
+                                            <span class="attr-name" style="color:#666;font-size:12px;">{$attr.name}:</span>
+                                            <span class="attr-value" style="color:#333;font-weight:bold;">{$attr.value}</span>
+                                        </div>
+                                    {/foreach}
+                                </div>
+                            {else/}
+                                <span style="color:#999;">无规格</span>
+                            {/if}
+                        </td>
+                        <td class="text-center">¥{$item.goods_market_price|htmlentities}</td>
+                        <td class="text-center">¥{$item.goods_original_price|htmlentities}</td>
                         <td class="text-center">{$item.nums|htmlentities}</td>
-                        <td class="text-center">{$item.saleprice|htmlentities}</td>
+                        <td class="text-center">¥{$item.goods_price|htmlentities}</td>
                         <td class="text-center">
                             <div style="display: flex;justify-content: center;align-items: center;">
                                 <div>
-                                    {$item.salestate_text|htmlentities}
+                                    <span class="label label-{if $item.sale_status == 0}default{elseif $item.sale_status == 1}warning{elseif $item.sale_status == 2}info{elseif $item.sale_status == 3}danger{elseif $item.sale_status == 4}success{elseif $item.sale_status == 5}success{else/}default{/if}">
+                                        {$item.sale_status_text|default='未知'}
+                                    </span>
                                 </div>
-                                {if $item.salestate == 1 || $item.salestate == 2 || $item.salestate == 3}
+                                {if $item.sale_status == 1 || $item.sale_status == 2 || $item.sale_status == 3}
                                 <div style="padding-left: 10px;">
                                     <a class="btn btn-success btn-dialog {:$auth->check('shop/order_aftersales/index')?'':'hide'}" href="javascript:;" data-url="shop/order_aftersales/edit/order_goods_id/{$item.id|htmlentities}" title="审核售后">审核售后</a>
                                 </div>
@@ -256,21 +315,34 @@
         <table class="table table-bordered table-goods">
             <thead>
             <tr>
-                <th>ID</th>
+                <th>序号</th>
                 <th>操作人</th>
+                <!-- <th>操作类型</th> -->
                 <th>操作记录</th>
                 <th>操作时间</th>
             </tr>
             </thead>
             <tbody>
-            {foreach name="row['order_action']" item="item"}
-            <tr>
-                <th scope="row">{$key+1}</th>
-                <td>{$item.operator|htmlentities}</td>
-                <td>{$item.memo|htmlentities}</td>
-                <td>{$item.createtime|date="Y-m-d H:i:s",###}</td>
-            </tr>
-            {/foreach}
+            {if !empty($row['order_action'])}
+                {foreach name="row['order_action']" item="item"}
+                <tr>
+                    <th scope="row">{$key+1}</th>
+                    <td>
+                        {if !empty($item.operator)}
+                            {$item.operator|htmlentities}
+                        {else/}
+                            <span style="color:#999;">系统</span>
+                        {/if}
+                    </td>
+                    <td>{$item.memo|htmlentities}</td>
+                    <td>{$item.createtime|date="Y-m-d H:i:s",###}</td>
+                </tr>
+                {/foreach}
+            {else/}
+                <tr>
+                    <td colspan="5" class="text-center" style="color:#999;padding:20px;">暂无操作记录</td>
+                </tr>
+            {/if}
             </tbody>
         </table>
     </div>

+ 24 - 7
application/admin/view/shop/order/prints.html

@@ -15,20 +15,37 @@
             <div>
                 {foreach name="$item.order_goods" item="res"}
                     <div style="margin-top: 10px;display: flex;">
-                        <img width="120" height="100" src="{$res.image|htmlentities}" alt="">
+                        <img width="120" height="100" src="{$res.goods_image|htmlentities}" alt="">
                         <div style="padding-left: 10px;">
-                            <p><b>{$res.title|htmlentities}</b></p>
+                            <p><b>{$res.goods_title|htmlentities}</b></p>
                             <p>
-                                <span>¥{$res.price|htmlentities}</span>
+                                <span>¥{$res.goods_price|htmlentities}</span>
                                 <span style="margin-left: 30px;">×{$res.nums|htmlentities}</span>
-                                <span style="margin-left: 30px;">{$res.attrdata|htmlentities}</span>
+                                {if !empty($res.goods_sku_attr_formatted)}
+                                    <span style="margin-left: 30px;">
+                                        {foreach name="res.goods_sku_attr_formatted" item="attr"}
+                                            <span style="color:#666;font-size:12px;">{$attr.name}:</span>
+                                            <span style="color:#333;">{$attr.value}</span>
+                                            {if !$attr@last} | {/if}
+                                        {/foreach}
+                                    </span>
+                                {else/}
+                                    <span style="margin-left: 30px;color:#999;">无规格</span>
+                                {/if}
                             </p>
                             <p>
-                                地址:{$item.address|htmlentities}
+                                地址:{if !empty($item.order_address)}
+                                    {$item.order_address.province_name|default=''}
+                                    {$item.order_address.city_name|default=''}
+                                    {$item.order_address.district_name|default=''}
+                                    {$item.order_address.address|default=''}
+                                {else/}
+                                    未填写
+                                {/if}
                             </p>
                             <p>
-                                <span>姓名:{$item.receiver|htmlentities}</span>
-                                <span style="margin-left: 30px;">手机号码:{$item.mobile|htmlentities}</span>
+                                <span>姓名:{if !empty($item.order_address)}{$item.order_address.consignee|default='未填写'}{else/}未填写{/if}</span>
+                                <span style="margin-left: 30px;">手机号码:{if !empty($item.order_address)}{$item.order_address.mobile|default='未填写'}{else/}未填写{/if}</span>
                             </p>
                         </div>
                     </div>

+ 63 - 0
application/admin/view/user/share/add.html

@@ -0,0 +1,63 @@
+<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Spm')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-spm" class="form-control" name="row[spm]" type="text">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('User_id')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-user_id" data-rule="required" data-source="user/user/index" data-field="nickname" class="form-control selectpage" name="row[user_id]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Share_id')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-share_id" data-rule="required" data-source="share/index" class="form-control selectpage" name="row[share_id]" type="text" value="">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Page')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-page" class="form-control" name="row[page]" type="text">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Query')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-query" class="form-control" name="row[query]" type="text">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Platform')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-platform" class="form-control" name="row[platform]" type="text">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Mode')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-mode" class="form-control" name="row[mode]" type="text">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Ext')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea id="c-ext" class="form-control " rows="5" name="row[ext]" cols="50"></textarea>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-status" data-rule="required" class="form-control" name="row[status]" type="number" value="1">
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
+        </div>
+    </div>
+</form>

+ 63 - 0
application/admin/view/user/share/edit.html

@@ -0,0 +1,63 @@
+<form id="edit-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
+
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Spm')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-spm" class="form-control" name="row[spm]" type="text" value="{$row.spm|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('User_id')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-user_id" data-rule="required" data-source="user/user/index" data-field="nickname" class="form-control selectpage" name="row[user_id]" type="text" value="{$row.user_id|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Share_id')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-share_id" data-rule="required" data-source="share/index" class="form-control selectpage" name="row[share_id]" type="text" value="{$row.share_id|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Page')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-page" class="form-control" name="row[page]" type="text" value="{$row.page|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Query')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-query" class="form-control" name="row[query]" type="text" value="{$row.query|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Platform')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-platform" class="form-control" name="row[platform]" type="text" value="{$row.platform|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Mode')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-mode" class="form-control" name="row[mode]" type="text" value="{$row.mode|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Ext')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <textarea id="c-ext" class="form-control " rows="5" name="row[ext]" cols="50">{$row.ext|htmlentities}</textarea>
+        </div>
+    </div>
+    <div class="form-group">
+        <label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
+        <div class="col-xs-12 col-sm-8">
+            <input id="c-status" data-rule="required" class="form-control" name="row[status]" type="number" value="{$row.status|htmlentities}">
+        </div>
+    </div>
+    <div class="form-group layer-footer">
+        <label class="control-label col-xs-12 col-sm-2"></label>
+        <div class="col-xs-12 col-sm-8">
+            <button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
+        </div>
+    </div>
+</form>

+ 29 - 0
application/admin/view/user/share/index.html

@@ -0,0 +1,29 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
+                        <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('user/share/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
+                        <a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('user/share/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
+                        <a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('user/share/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
+                        
+
+                        
+
+                        <a class="btn btn-success btn-recyclebin btn-dialog {:$auth->check('user/share/recyclebin')?'':'hide'}" href="user/share/recyclebin" title="{:__('Recycle bin')}"><i class="fa fa-recycle"></i> {:__('Recycle bin')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
+                           data-operate-edit="{:$auth->check('user/share/edit')}"
+                           data-operate-del="{:$auth->check('user/share/del')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 25 - 0
application/admin/view/user/share/recyclebin.html

@@ -0,0 +1,25 @@
+<div class="panel panel-default panel-intro">
+    {:build_heading()}
+
+    <div class="panel-body">
+        <div id="myTabContent" class="tab-content">
+            <div class="tab-pane fade active in" id="one">
+                <div class="widget-body no-padding">
+                    <div id="toolbar" class="toolbar">
+                        {:build_toolbar('refresh')}
+                        <a class="btn btn-info btn-multi btn-disabled disabled {:$auth->check('user/share/restore')?'':'hide'}" href="javascript:;" data-url="user/share/restore" data-action="restore"><i class="fa fa-rotate-left"></i> {:__('Restore')}</a>
+                        <a class="btn btn-danger btn-multi btn-disabled disabled {:$auth->check('user/share/destroy')?'':'hide'}" href="javascript:;" data-url="user/share/destroy" data-action="destroy"><i class="fa fa-times"></i> {:__('Destroy')}</a>
+                        <a class="btn btn-success btn-restoreall {:$auth->check('user/share/restore')?'':'hide'}" href="javascript:;" data-url="user/share/restore" title="{:__('Restore all')}"><i class="fa fa-rotate-left"></i> {:__('Restore all')}</a>
+                        <a class="btn btn-danger btn-destroyall {:$auth->check('user/share/destroy')?'':'hide'}" href="javascript:;" data-url="user/share/destroy" title="{:__('Destroy all')}"><i class="fa fa-times"></i> {:__('Destroy all')}</a>
+                    </div>
+                    <table id="table" class="table table-striped table-bordered table-hover"
+                           data-operate-restore="{:$auth->check('user/share/restore')}"
+                           data-operate-destroy="{:$auth->check('user/share/destroy')}"
+                           width="100%">
+                    </table>
+                </div>
+            </div>
+
+        </div>
+    </div>
+</div>

+ 7 - 4
application/api/controller/Discount.php

@@ -5,6 +5,8 @@ namespace app\api\controller;
 use app\common\controller\Api;
 use app\common\Service\DiscountService;
 use app\api\validate\Discount as DiscountValidate;
+use app\common\exception\BusinessException;
+use app\common\Enum\ErrorCodeEnum;
 
 /**
  * 折扣活动接口
@@ -68,9 +70,10 @@ class Discount extends Api
     public function getCurrentActivity()
     {
         $activity = DiscountService::getCurrentActivity();
-        //活动过期时间
-         // 添加活动剩余时间
-        $activity['remaining_time'] = DiscountService::formatRemainingTime($activity['end_time']);
+        // 添加活动剩余时间
+        if(!empty($activity)){
+            $activity['remaining_time'] = DiscountService::formatRemainingTime($activity['end_time']);
+        }
         $this->success('获取成功', $activity);
     }
     
@@ -95,7 +98,7 @@ class Discount extends Api
             $discountInfo = DiscountService::getGoodsDiscountInfo($goodsId, $skuId);
             
             if (empty($discountInfo)) {
-                $this->error('该商品当前没有参与折扣活动');
+                throw new BusinessException('该商品当前没有参与折扣活动', ErrorCodeEnum::GOODS_NO_DISCOUNT);
             }
             
             // 添加活动剩余时间

+ 22 - 34
application/api/controller/Lottery.php

@@ -3,18 +3,19 @@
 namespace app\api\controller;
 
 use app\common\controller\Api;
-use app\common\Service\lottery\LotteryService;
-use app\common\Service\lottery\LotteryChanceService;
-use app\common\Service\lottery\LotteryRecordService;
-use app\common\Service\lottery\LotteryActivityService;
+use app\common\Service\Lottery\LotteryService;
+use app\common\Service\Lottery\LotteryChanceService;
+use app\common\Service\Lottery\LotteryRecordService;
+use app\common\Service\Lottery\LotteryActivityService;
 use app\common\model\lottery\LotteryActivity;
 use app\common\model\lottery\LotteryPrize;
-use app\common\model\lottery\LotteryDrawRecord;
 use app\common\model\lottery\LotteryWinRecord;
 use app\common\library\Auth;
 use app\api\validate\Lottery as LotteryValidate;
 use app\common\Enum\ActivityEnum;
 use app\common\Enum\LotteryEnum;
+use app\common\exception\BusinessException;
+use app\common\Enum\ErrorCodeEnum;
 use think\Exception;
 
 /**
@@ -28,7 +29,7 @@ class Lottery extends Api
     /**
      * 获取抽奖活动列表
      */
-    public function activityList()
+    public function getActivityList()
     {
         // 验证参数
         $validate = new LotteryValidate();
@@ -85,7 +86,7 @@ class Lottery extends Api
     /**
      * 获取活动详情
      */
-    public function activityDetail()
+    public function getActivityDetail()
     {
         // 验证参数
         $validate = new LotteryValidate();
@@ -93,11 +94,11 @@ class Lottery extends Api
             $this->error($validate->getError());
         }
 
-        $activityId = $this->request->param('activity_id/d');
+        $activityId = $this->request->param('lottery_id/d');
 
         $activity = LotteryActivity::find($activityId);
         if (!$activity) {
-            $this->error('活动不存在');
+            throw new BusinessException('活动不存在', ErrorCodeEnum::USER_ACTIVITY_NOT_FOUND);
         }
 
         $detail = $activity->toArray();
@@ -147,20 +148,10 @@ class Lottery extends Api
         if (!$validate->scene('draw')->check($this->request->param())) {
             $this->error($validate->getError());
         }
-
-        $activityId = $this->request->param('activity_id/d');
-
-        $userId = Auth::instance()->id;
-        if (!$userId) {
-            $this->error('请先登录');
-        }
-
-        try {
-            $result = LotteryService::drawLottery($activityId, $userId);
-            $this->success('抽奖成功', $result);
-        } catch (Exception $e) {
-            $this->error($e->getMessage());
-        }
+        $activityId = $this->request->post('lottery_id/d');
+        $userId = $this->auth->id;
+        $result = LotteryService::drawLottery($activityId, $userId);
+        $this->success('抽奖成功', $result);
     }
 
     /**
@@ -174,12 +165,9 @@ class Lottery extends Api
             $this->error($validate->getError());
         }
 
-        $activityId = $this->request->param('activity_id/d');
+        $activityId = $this->request->post('lottery_id/d');
 
-        $userId = Auth::instance()->id;
-        if (!$userId) {
-            $this->error('请先登录');
-        }
+        $userId = $this->auth->id;
 
         $chances = LotteryChanceService::getUserChanceDetail($activityId, $userId);
         $this->success('获取成功', $chances);
@@ -196,16 +184,16 @@ class Lottery extends Api
         
         // 如果有activity_id参数,则把它映射为activity_id_optional进行验证
         $validateParams = $params;
-        if (isset($params['activity_id'])) {
-            $validateParams['activity_id_optional'] = $params['activity_id'];
-            unset($validateParams['activity_id']);
+        if (isset($params['lottery_id'])) {
+            $validateParams['activity_id_optional'] = $params['lottery_id'];
+            unset($validateParams['lottery_id']);
         }
         
         if (!$validate->scene('getDrawRecords')->check($validateParams)) {
             $this->error($validate->getError());
         }
 
-        $activityId = $this->request->param('activity_id/d');
+        $activityId = $this->request->post('lottery_id/d');
         $page = $this->request->param('page/d', 1);
         $limit = $this->request->param('limit/d', 20);
 
@@ -332,11 +320,11 @@ class Lottery extends Api
                                      ->where('user_id', $userId)
                                      ->find();
         if (!$winRecord) {
-            $this->error('中奖记录不存在');
+            throw new BusinessException('中奖记录不存在', ErrorCodeEnum::USER_WIN_RECORD_NOT_FOUND);
         }
 
         if ($winRecord->deliver_status != LotteryEnum::DELIVER_STATUS_PENDING) {
-            $this->error('该奖品已处理,无法修改地址');
+            throw new BusinessException('该奖品已处理,无法修改地址', ErrorCodeEnum::USER_PRIZE_ALREADY_PROCESSED);
         }
 
         try {

+ 10 - 1
application/api/controller/Order.php

@@ -87,7 +87,9 @@ class Order extends Base
     }
 
 
-    //提交订单 - 统一接口
+    /**
+     * 提交订单 - 统一接口
+     */
     public function create()
     {
         // 验证请求参数
@@ -347,4 +349,11 @@ class Order extends Base
         }
         $this->error('查询失败');
     }
+
+    // 获取状态订单统计
+    public function getOrderStatusCount(){
+        $userId = $this->auth->id;
+        $info = OrderService::getOrderStatusCount($userId);
+        $this->success('获取成功', $info);
+    }
 }

+ 5 - 5
application/api/controller/User.php

@@ -37,11 +37,11 @@ class User extends Base
         $logincode = $this->request->param('logincode');
 
         $info = $this->auth->getUserInfo();
-        $info['order'] = [
-            'created'  => Order::where('user_id', $this->auth->id)->where('status',OrderEnum::STATUS_CREATE)->count(),
-            'paid'     => Order::where('user_id', $this->auth->id)->where('status',OrderEnum::STATUS_PAY)->count(),
-            'evaluate' => Order::where('user_id', $this->auth->id)->where('status',OrderEnum::STATUS_CONFIRM)->count()
-        ];
+        // $info['order'] = [
+        //     'created'  => Order::where('user_id', $this->auth->id)->where('status',OrderEnum::STATUS_CREATE)->count(),
+        //     'paid'     => Order::where('user_id', $this->auth->id)->where('status',OrderEnum::STATUS_PAY)->count(),
+        //     'evaluate' => Order::where('user_id', $this->auth->id)->where('status',OrderEnum::STATUS_CONFIRM)->count()
+        // ];
         $info['avatar'] = cdnurl($info['avatar'], true);
         $info['gender_text'] = UserEnum::getGenderText($this->auth->getUser()->gender ?? 0);
         $info['age'] = $this->auth->getUser()->age ?? 0;

+ 69 - 3
application/api/library/ExceptionHandle.php

@@ -4,19 +4,47 @@ namespace app\api\library;
 
 use Exception;
 use think\exception\Handle;
+use think\Log;
+use app\common\exception\BusinessException;
 
 /**
  * 自定义API模块的错误显示
+ * 统一处理API异常和服务层异常
  */
 class ExceptionHandle extends Handle
 {
 
+    
+
     public function render(Exception $e)
     {
+         // 业务异常:开发和生产环境都返回json
+        if ($e instanceof BusinessException) {
+            $code = 0;
+            $statuscode = 200;
+            $msg = $e->getMessage() ?: '业务处理失败';
+
+            Log::record([
+                'exception' => 'BusinessException',
+                'custom_error_code' => $e->getErrorCode(),
+                'message' => $e->getMessage(),
+                'error_data' => $e->getErrorData(),
+                'file' => $e->getFile(),
+                'line' => $e->getLine(),
+            ], 'info');
+
+            return json([
+                'code' => $code,
+                'msg' => $msg,
+                'time' => time(),
+                'data' => null
+            ], $statuscode);
+        }
         // 在生产环境下返回code信息
         if (!\think\Config::get('app_debug')) {
             $statuscode = $code = 500;
             $msg = 'An error occurred';
+            
             // 验证异常
             if ($e instanceof \think\exception\ValidateException) {
                 $code = 0;
@@ -24,13 +52,51 @@ class ExceptionHandle extends Handle
                 $msg = $e->getError();
             }
             // Http异常
-            if ($e instanceof \think\exception\HttpException) {
+            elseif ($e instanceof \think\exception\HttpException) {
                 $statuscode = $code = $e->getStatusCode();
+                $msg = $e->getMessage() ?: 'Http Error';
+            }
+            // 自定义业务异常(与验证异常保持一致的返回格式)
+            elseif ($e instanceof BusinessException) {
+                $code = 0;  // 业务异常统一返回code=0,与验证异常一致
+                $statuscode = 200;  // HTTP状态码为200
+                $msg = $e->getMessage() ?: '业务处理失败';
+                
+                // 记录业务异常日志(便于调试,包含自定义错误码)
+                Log::record([
+                    'exception' => 'BusinessException',
+                    'custom_error_code' => $e->getErrorCode(),  // 记录自定义错误码用于调试
+                    'message' => $e->getMessage(),
+                    'error_data' => $e->getErrorData(),
+                    'file' => $e->getFile(),
+                    'line' => $e->getLine(),
+                ], 'info');  // 业务异常用info级别记录
+            }
+            // 其他异常(包括服务层的普通Exception)
+            elseif ($e instanceof \Exception) {
+                $code = 0;  // 普通异常统一返回code=0
+                $statuscode = 200;  // HTTP状态码为200
+                $msg = $e->getMessage() ?: '业务处理失败';
+                
+                // 记录服务层异常日志(便于调试)
+                Log::record([
+                    'exception' => get_class($e),
+                    'message' => $e->getMessage(),
+                    'file' => $e->getFile(),
+                    'line' => $e->getLine(),
+                    'trace' => $e->getTraceAsString()
+                ], 'error');
             }
-            return json(['code' => $code, 'msg' => $msg, 'time' => time(), 'data' => null], $statuscode);
+            
+            return json([
+                'code' => $code, 
+                'msg' => $msg, 
+                'time' => time(), 
+                'data' => null
+            ], $statuscode);
         }
 
-        //其它此交由系统处理
+        // 开发环境下交由系统处理
         return parent::render($e);
     }
 

+ 69 - 0
application/common/Enum/ErrorCodeEnum.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace app\common\Enum;
+
+/**
+ * 错误码枚举类
+ * 统一管理系统中的所有错误码
+ * 
+ * 注意:这些错误码主要用于:
+ * 1. 日志记录和调试追踪
+ * 2. 内部业务逻辑区分
+ * 
+ * 前端接收的API响应中,所有业务异常统一返回 code=0(与验证异常一致)
+ * 具体错误信息通过 msg 字段传递
+ */
+class ErrorCodeEnum
+{
+    // ============ 抽奖相关错误码 (1000-1999) ============
+    const LOTTERY_ACTIVITY_NOT_FOUND = 1001;           // 活动不存在或未开始
+    const LOTTERY_NOT_IN_TIME = 1002;                  // 不在抽奖时间内
+    const LOTTERY_USER_NOT_QUALIFIED = 1003;           // 用户不符合参与条件
+    const LOTTERY_NO_CHANCE = 1004;                    // 没有抽奖机会
+    const LOTTERY_REACH_LIMIT = 1005;                  // 已达到参与次数上限
+    const LOTTERY_ORDER_ALREADY_DRAWN = 1006;          // 该订单已参与过抽奖
+
+    // ============ 用户相关错误码 (2000-2999) ============
+    const USER_NOT_LOGIN = 2002;                       // 请先登录
+    const USER_ACTIVITY_NOT_FOUND = 2001;              // 活动不存在
+    const USER_WIN_RECORD_NOT_FOUND = 2003;            // 中奖记录不存在
+    const USER_PRIZE_ALREADY_PROCESSED = 2004;         // 该奖品已处理,无法修改地址
+
+    // ============ 商品折扣相关错误码 (3000-3999) ============
+    const GOODS_NO_DISCOUNT = 3001;                    // 该商品当前没有参与折扣活动
+
+    // ============ 订单相关错误码 (4000-4999) ============
+    // 预留给订单相关功能
+
+    // ============ 支付相关错误码 (5000-5999) ============
+    // 预留给支付相关功能
+
+    /**
+     * 获取错误码对应的默认消息
+     * @param int $code 错误码
+     * @return string
+     */
+    public static function getMessage($code)
+    {
+        $messages = [
+            // 抽奖相关
+            self::LOTTERY_ACTIVITY_NOT_FOUND => '活动不存在或未开始',
+            self::LOTTERY_NOT_IN_TIME => '不在抽奖时间内',
+            self::LOTTERY_USER_NOT_QUALIFIED => '用户不符合参与条件',
+            self::LOTTERY_NO_CHANCE => '没有抽奖机会',
+            self::LOTTERY_REACH_LIMIT => '已达到参与次数上限',
+            self::LOTTERY_ORDER_ALREADY_DRAWN => '该订单已参与过抽奖',
+
+            // 用户相关
+            self::USER_NOT_LOGIN => '请先登录',
+            self::USER_ACTIVITY_NOT_FOUND => '活动不存在',
+            self::USER_WIN_RECORD_NOT_FOUND => '中奖记录不存在',
+            self::USER_PRIZE_ALREADY_PROCESSED => '该奖品已处理,无法修改地址',
+
+            // 商品折扣相关
+            self::GOODS_NO_DISCOUNT => '该商品当前没有参与折扣活动',
+        ];
+
+        return $messages[$code] ?? '未知错误';
+    }
+}

+ 27 - 0
application/common/Enum/GoodsEnum.php

@@ -49,6 +49,15 @@ class GoodsEnum
    const SALES_SHOW_TYPE_EXACT = 'EXACT'; // 精确的
    const SALES_SHOW_TYPE_SKETCHY = 'SKETCHY'; // 粗略的
 
+   // 售后状态 sale_status
+   const SALE_STATUS_NONE = 0;           // 无售后
+   const SALE_STATUS_APPLIED = 1;        // 已申请
+   const SALE_STATUS_REFUNDING = 2;      // 退款中
+   const SALE_STATUS_RETURNING = 3;      // 退货中
+   const SALE_STATUS_REFUNDED = 4;       // 已退款
+   const SALE_STATUS_RETURNED = 5;       // 已退货退款
+   const SALE_STATUS_REJECTED = 6;       // 已拒绝
+
    public static function getSalesShowTypeMap()
    {
     return [
@@ -185,4 +194,22 @@ class GoodsEnum
     {
         return self::getOfflineTypeMap()[$offlineType] ?? '未知';
     }
+
+    public static function getSaleStatusMap()
+    {
+        return [
+            self::SALE_STATUS_NONE => '无',
+            self::SALE_STATUS_APPLIED => '已申请',
+            self::SALE_STATUS_REFUNDING => '退款中',
+            self::SALE_STATUS_RETURNING => '退货中',
+            self::SALE_STATUS_REFUNDED => '已退款',
+            self::SALE_STATUS_RETURNED => '已退货退款',
+            self::SALE_STATUS_REJECTED => '已拒绝',
+        ];
+    }
+
+    public static function getSaleStatusText($saleStatus)
+    {
+        return self::getSaleStatusMap()[$saleStatus] ?? '未知';
+    }
 }

+ 77 - 36
application/common/Enum/LotteryEnum.php

@@ -63,15 +63,22 @@ class LotteryEnum
       const GOODS_RULE_INCLUDE = 1;           // 指定商品参与
       const GOODS_RULE_EXCLUDE = 2;           // 指定商品不可参与
   
-      // ============ 触发类型 ============
-      const TRIGGER_TYPE_BUY_GOODS = 1;       // 购买商品
-      const TRIGGER_TYPE_ORDER_CONSUME = 2;   // 订单消费
-      const TRIGGER_TYPE_RECHARGE = 3;        // 充值
-      const TRIGGER_TYPE_TOTAL_CONSUME = 4;   // 累计消费
-  
-      // ============ 兑奖期限类型 ============
-      const REDEEM_EXPIRE_FOREVER = 1;        // 永久有效
-      const REDEEM_EXPIRE_FIXED = 2;          // 固定时长
+          // ============ 触发类型 ============
+    const TRIGGER_TYPE_BUY_GOODS = 1;       // 购买商品
+    const TRIGGER_TYPE_ORDER_CONSUME = 2;   // 订单消费
+    const TRIGGER_TYPE_RECHARGE = 3;        // 充值
+    const TRIGGER_TYPE_TOTAL_CONSUME = 4;   // 累计消费
+
+    // ============ 机会获取类型(与条件类型保持一致,额外增加管理员赠送) ============
+    const CHANCE_GET_TYPE_BUY_GOODS = 1;    // 购买指定商品(对应CONDITION_TYPE_BUY_GOODS)
+    const CHANCE_GET_TYPE_ORDER_AMOUNT = 2; // 单笔订单消费满额(对应CONDITION_TYPE_ORDER_AMOUNT)
+    const CHANCE_GET_TYPE_RECHARGE = 3;     // 单次充值满额(对应CONDITION_TYPE_RECHARGE_AMOUNT)
+    const CHANCE_GET_TYPE_TOTAL_AMOUNT = 4; // 活动期间累计消费满额(对应CONDITION_TYPE_TOTAL_AMOUNT)
+    const CHANCE_GET_TYPE_ADMIN_GRANT = 5;  // 管理员赠送
+
+    // ============ 兑奖期限类型 ============
+    const REDEEM_EXPIRE_FOREVER = 1;        // 永久有效
+    const REDEEM_EXPIRE_FIXED = 2;          // 固定时长
   
       /**
        * 获取活动状态映射(页面显示用)
@@ -234,18 +241,31 @@ class LotteryEnum
           ];
       }
   
-      /**
-       * 获取触发类型映射
-       */
-      public static function getTriggerTypeMap()
-      {
-          return [
-              self::TRIGGER_TYPE_BUY_GOODS => '购买商品',
-              self::TRIGGER_TYPE_ORDER_CONSUME => '订单消费',
-              self::TRIGGER_TYPE_RECHARGE => '充值',
-              self::TRIGGER_TYPE_TOTAL_CONSUME => '累计消费',
-          ];
-      }
+          /**
+     * 获取触发类型映射  后台
+     */
+    public static function getTriggerTypeMap()
+    {
+        return [
+            self::TRIGGER_TYPE_BUY_GOODS => '购买商品',
+            self::TRIGGER_TYPE_ORDER_CONSUME => '订单消费',
+            self::TRIGGER_TYPE_RECHARGE => '充值',
+            self::TRIGGER_TYPE_TOTAL_CONSUME => '累计消费',
+        ];
+    }
+    /**
+     * 获取机会获取类型映射h
+     */
+    public static function getChanceGetTypeMap()
+    {
+        return [
+            self::CHANCE_GET_TYPE_BUY_GOODS => '购买指定商品',
+            self::CHANCE_GET_TYPE_ORDER_AMOUNT => '单笔订单消费满额',
+            self::CHANCE_GET_TYPE_RECHARGE => '单次充值满额',
+            self::CHANCE_GET_TYPE_TOTAL_AMOUNT => '活动期间累计消费满额',
+            self::CHANCE_GET_TYPE_ADMIN_GRANT => '管理员赠送',
+        ];
+    }
   
       /**
        * 获取兑奖期限类型映射
@@ -285,14 +305,25 @@ class LotteryEnum
           return $map[$type] ?? '未知';
       }
   
-      /**
-       * 获取条件类型文本
-       */
-      public static function getConditionTypeText($type)
-      {
-          $map = self::getConditionTypeMap();
-          return $map[$type] ?? '未知';
-      }
+          /**
+     * 获取条件类型文本
+     */
+    public static function getConditionTypeText($type)
+    {
+        $map = self::getConditionTypeMap();
+        return $map[$type] ?? '未知';
+    }
+
+
+
+    /**
+     * 获取机会获取类型文本
+     */
+    public static function getChanceGetTypeText($type)
+    {
+        $map = self::getChanceGetTypeMap();
+        return $map[$type] ?? '未知';
+    }
   
       /**
        * 验证活动状态是否有效(包含所有状态)
@@ -338,11 +369,21 @@ class LotteryEnum
           return array_key_exists($type, self::getLotteryTypeMap());
       }
   
-      /**
-       * 验证条件类型是否有效
-       */
-      public static function isValidConditionType($type)
-      {
-          return array_key_exists($type, self::getConditionTypeMap());
-      }
+          /**
+     * 验证条件类型是否有效
+     */
+    public static function isValidConditionType($type)
+    {
+        return array_key_exists($type, self::getConditionTypeMap());
+    }
+
+
+
+    /**
+     * 验证机会获取类型是否有效
+     */
+    public static function isValidChanceGetType($type)
+    {
+        return array_key_exists($type, self::getChanceGetTypeMap());
+    }
 } 

+ 133 - 1
application/common/Enum/OrderEnum.php

@@ -19,6 +19,138 @@ class OrderEnum
     const STATUS_GROUPON_TIMEOUT = 204;
     const STATUS_AUTO_CONFIRM = 402;
 
+    // pay_mode 支付模式:online=线上支付,offline=线下支付
+    const PAY_MODE_ONLINE = 'online';
+    const PAY_MODE_OFFLINE = 'offline';
+
+    // 订单类型
+    const TYPE_NORMAL = 1;      // 普通订单
+    const TYPE_GROUP = 2;       // 拼团订单
+    const TYPE_SECKILL = 3;     // 秒杀订单
+    const TYPE_PRESALE = 4;     // 预售订单
+
+    // 发货方式
+    const DELIVERY_TYPE_EXPRESS = 1;     // 快递配送
+    const DELIVERY_TYPE_PICKUP = 2;      // 门店自提
+    const DELIVERY_TYPE_LOCAL = 3;       // 同城配送
+    const DELIVERY_TYPE_VIRTUAL = 4;     // 无需配送
+
+    // 发票状态
+    const INVOICE_STATUS_UNAVAILABLE = -1;   // 不可开具
+    const INVOICE_STATUS_NOT_APPLIED = 0;    // 未申请
+    const INVOICE_STATUS_APPLIED = 1;        // 已申请
+    const INVOICE_STATUS_ISSUED = 2;         // 已开具
+
+    // 活动类型
+    const ACTIVITY_TYPE_NORMAL = 'normal';       // 普通活动
+    const ACTIVITY_TYPE_SECKILL = 'seckill';     // 秒杀活动
+    const ACTIVITY_TYPE_GROUP = 'group';         // 拼团活动
+    const ACTIVITY_TYPE_PRESALE = 'presale';     // 预售活动
+    const ACTIVITY_TYPE_DISCOUNT = 'discount';   // 折扣活动
+
+    // 操作类型
+    const ACTION_TYPE_CREATE = 'create';       // 创建订单
+    const ACTION_TYPE_PAY = 'pay';             // 支付订单
+    const ACTION_TYPE_SHIP = 'ship';           // 发货
+    const ACTION_TYPE_RECEIVE = 'receive';     // 确认收货
+    const ACTION_TYPE_CANCEL = 'cancel';       // 取消订单
+    const ACTION_TYPE_REFUND = 'refund';       // 退款
+    const ACTION_TYPE_RETURN = 'return';       // 退货
+    const ACTION_TYPE_COMPLETE = 'complete';   // 完成订单
+
+    public static function getPayModeList()
+    {
+        return [
+            self::PAY_MODE_ONLINE => '线上支付',
+            self::PAY_MODE_OFFLINE => '线下支付',
+        ];
+    }
+
+    public static function getPayModeText($pay_mode)
+    {
+        return self::getPayModeList()[$pay_mode] ?? '未知';
+    }
+
+    public static function getOrderTypeList()
+    {
+        return [
+            self::TYPE_NORMAL => '普通订单',
+            self::TYPE_GROUP => '拼团订单',
+            self::TYPE_SECKILL => '秒杀订单',
+            self::TYPE_PRESALE => '预售订单',
+        ];
+    }
+
+    public static function getOrderTypeText($type)
+    {
+        return self::getOrderTypeList()[$type] ?? '未知类型';
+    }
+
+    public static function getDeliveryTypeList()
+    {
+        return [
+            self::DELIVERY_TYPE_EXPRESS => '快递配送',
+            self::DELIVERY_TYPE_PICKUP => '门店自提',
+            self::DELIVERY_TYPE_LOCAL => '同城配送',
+            self::DELIVERY_TYPE_VIRTUAL => '无需配送',
+        ];
+    }
+
+    public static function getDeliveryTypeText($deliveryType)
+    {
+        return self::getDeliveryTypeList()[$deliveryType] ?? '未知';
+    }
+
+    public static function getInvoiceStatusList()
+    {
+        return [
+            self::INVOICE_STATUS_UNAVAILABLE => '不可开具',
+            self::INVOICE_STATUS_NOT_APPLIED => '未申请',
+            self::INVOICE_STATUS_APPLIED => '已申请',
+            self::INVOICE_STATUS_ISSUED => '已开具',
+        ];
+    }
+
+    public static function getInvoiceStatusText($invoiceStatus)
+    {
+        return self::getInvoiceStatusList()[$invoiceStatus] ?? '未知';
+    }
+
+    public static function getActivityTypeList()
+    {
+        return [
+            self::ACTIVITY_TYPE_NORMAL => '普通活动',
+            self::ACTIVITY_TYPE_SECKILL => '秒杀活动',
+            self::ACTIVITY_TYPE_GROUP => '拼团活动',
+            self::ACTIVITY_TYPE_PRESALE => '预售活动',
+            self::ACTIVITY_TYPE_DISCOUNT => '折扣活动',
+        ];
+    }
+
+    public static function getActivityTypeText($activityType)
+    {
+        return self::getActivityTypeList()[$activityType] ?? $activityType;
+    }
+
+    public static function getActionTypeList()
+    {
+        return [
+            self::ACTION_TYPE_CREATE => '创建订单',
+            self::ACTION_TYPE_PAY => '支付订单',
+            self::ACTION_TYPE_SHIP => '发货',
+            self::ACTION_TYPE_RECEIVE => '确认收货',
+            self::ACTION_TYPE_CANCEL => '取消订单',
+            self::ACTION_TYPE_REFUND => '退款',
+            self::ACTION_TYPE_RETURN => '退货',
+            self::ACTION_TYPE_COMPLETE => '完成订单',
+        ];
+    }
+
+    public static function getActionTypeText($actionType)
+    {
+        return self::getActionTypeList()[$actionType] ?? $actionType;
+    }
+
 
     const STATUS_TEXT_MAP = [
         self::STATUS_CREATE => '未付款',
@@ -30,7 +162,7 @@ class OrderEnum
         self::STATUS_REFUND_CONFIRM => "已退款",
         self::STATUS_GROUPON_TIMEOUT => "已超时团购",
         self::STATUS_SHIP => "已发货",
-        self::STATUS_CONFIRM => "已收货",
+        self::STATUS_CONFIRM => "已完成",
         self::STATUS_AUTO_CONFIRM => "已收货(系统)",
     ];
 

+ 12 - 6
application/common/Enum/PayEnum.php

@@ -14,15 +14,21 @@ class PayEnum
      */
     const METHOD_ALIPAY  = 'alipay';
     const METHOD_WECHAT  = 'wechat';
-
     const METHOD_DOUYIN  = 'douyin';
+    const METHOD_BALANCE = 'balance';
+    const METHOD_OFFLINE = 'offline';
+    const METHOD_CASH    = 'cash';
+    const METHOD_BANK_CARD = 'bank_card';
     
     // 支付类型枚举
-    const METHOD_TEXT_MAP = [
-        self::METHOD_ALIPAY => '支付宝',
-        self::METHOD_WECHAT => '微信',
-        self::METHOD_DOUYIN => '抖音',
-        
+    const METHOD_TEXT_MAP = [      
+        // self::METHOD_WECHAT => '微信支付',
+        // self::METHOD_ALIPAY => '支付宝',
+        self::METHOD_DOUYIN => '抖音支付',
+        // self::METHOD_BALANCE => '余额支付',
+        // self::METHOD_OFFLINE => '线下支付',
+        // self::METHOD_CASH => '现金支付',
+        // self::METHOD_BANK_CARD => '银行卡支付',
     ];
     // 商户类型
     const MERCHANT_TYPE_NORMAL = 0;

+ 105 - 97
application/common/Service/DiscountService.php

@@ -31,15 +31,8 @@ class DiscountService
      */
     public static function getCurrentDiscountGoods($goodsIds = [], $limit = 0)
     {
-        $currentTime = time();
-        
         // 第一步:查询当前时间有效的活动ID
-        $activeActivityIds = Db::table('shop_activity')
-            ->where('start_time', '<=', $currentTime)
-            ->where('end_time', '>=', $currentTime)
-            ->where('activity_status', ActivityEnum::ACTIVITY_STATUS_ONGOING)
-            ->where('status',  StatusEnum::ENABLED)
-            ->column('id');
+        $activeActivityIds = self::getActiveActivityIds();
         if (empty($activeActivityIds)) {
             return [];
         }
@@ -100,32 +93,7 @@ class DiscountService
             
             // 如果商品还没有在结果中,初始化
             if (!isset($processedData[$goodsId])) {
-                $processedData[$goodsId] = [
-                    'goods_id' => $goodsId,
-                    'title' => $item['title'],
-                    'sub_title' => $item['sub_title'],
-                    'image' => $item['image'],
-                    'original_price' => $item['original_price'],
-                    'market_price' => $item['market_price'],
-                    'goods_stocks' => $item['goods_stocks'],
-                    'sales' => $item['sales'],
-                    'spec_type' => $item['spec_type'],
-                    'activity_id' => $item['activity_id'],
-                    'activity_name' => $item['activity_name'],
-                    'start_time' => $item['start_time'],
-                    'end_time' => $item['end_time'],
-                    'has_discount' => true,
-                    'discount_info' => [],
-                    // 初始化折扣价格相关字段
-                    'min_discount_price' => null,
-                    'max_discount_price' => null,
-                    'min_discount' => null,
-                    'max_discount' => null,
-                    'discount_price' => null,
-                    'discount' => null,
-                    'discount_amount' => null,
-                    'price_range' => ''
-                ];
+                $processedData[$goodsId] = self::initGoodsDiscountStructure($item);
             }
             
             // 添加折扣信息
@@ -149,45 +117,7 @@ class DiscountService
         }
         
         // 处理外层折扣价格字段
-        foreach ($processedData as &$goods) {
-            $discountPrices = [];
-            $discounts = [];
-            
-            // 收集所有折扣价格和折扣率
-            foreach ($goods['discount_info'] as $discount) {
-                $discountPrices[] = $discount['discount_price'];
-                $discounts[] = $discount['discount'];
-            }
-            
-            if (!empty($discountPrices)) {
-                // 计算最低和最高折扣价格
-                $goods['min_discount_price'] = min($discountPrices);
-                $goods['max_discount_price'] = max($discountPrices);
-                $goods['min_discount'] = min($discounts);
-                $goods['max_discount'] = max($discounts);
-                
-                // 单规格商品处理
-                if ($goods['spec_type'] == 0) {
-                    $goods['discount_price'] = $goods['min_discount_price'];
-                    $goods['discount'] = $goods['min_discount'];
-                    $goods['discount_amount'] = round($goods['original_price'] - $goods['discount_price'], 2);
-                    $goods['price_range'] = '¥' . $goods['discount_price'];
-                } 
-                // 多规格商品处理
-                else {
-                    // 设置价格区间
-                    if ($goods['min_discount_price'] == $goods['max_discount_price']) {
-                        $goods['price_range'] = '¥' . $goods['min_discount_price'];
-                    } else {
-                        $goods['price_range'] = '¥' . $goods['min_discount_price'] . '-' . $goods['max_discount_price'];
-                    }
-                    
-                    // 对于多规格,使用最低价格作为主要显示价格
-                    $goods['discount_price'] = $goods['min_discount_price'];
-                    $goods['discount'] = $goods['min_discount'];
-                }
-            }
-        }
+        $processedData = self::processGoodsDiscountData($processedData);
         
         return array_values($processedData);
     }
@@ -220,6 +150,7 @@ class DiscountService
             ->alias('sku')
             ->join('shop_goods goods', 'sku.goods_id = goods.id')
             ->join('shop_goods_sku spec', 'sku.sku_id = spec.id', 'left')
+            ->where('sku.activity_id', $activity['id'])
             ->where('goods.status', GoodsEnum::STATUS_ON_SALE)
             ->where('sku.stocks', '>', 0)
             ->field([
@@ -250,28 +181,7 @@ class DiscountService
             $goodsId = $item['goods_id'];
             
             if (!isset($processedGoods[$goodsId])) {
-                $processedGoods[$goodsId] = [
-                    'goods_id' => $goodsId,
-                    'title' => $item['title'],
-                    'sub_title' => $item['sub_title'],
-                    'image' => $item['image'],
-                    'original_price' => $item['original_price'],
-                    'market_price' => $item['market_price'],
-                    'goods_stocks' => $item['goods_stocks'],
-                    'sales' => $item['sales'],
-                    'spec_type' => $item['spec_type'],
-                    'has_discount' => true,
-                    'discount_info' => [],
-                    // 初始化折扣价格相关字段
-                    'min_discount_price' => null,
-                    'max_discount_price' => null,
-                    'min_discount' => null,
-                    'max_discount' => null,
-                    'discount_price' => null,
-                    'discount' => null,
-                    'discount_amount' => null,
-                    'price_range' => ''
-                ];
+                $processedGoods[$goodsId] = self::initGoodsDiscountStructure($item);
             }
             
             $discountInfo = [
@@ -512,6 +422,104 @@ class DiscountService
         ];
     }
 
-   
-   
+    /**
+     * 处理商品的折扣价格信息(公共方法)
+     * @param array $goodsData 商品数据数组
+     * @return array 处理后的商品数据
+     */
+    private static function processGoodsDiscountData($goodsData)
+    {
+        foreach ($goodsData as &$goods) {
+            $discountPrices = [];
+            $discounts = [];
+            
+            // 收集所有折扣价格和折扣率
+            foreach ($goods['discount_info'] as $discount) {
+                $discountPrices[] = $discount['discount_price'];
+                $discounts[] = $discount['discount'];
+            }
+            
+            if (!empty($discountPrices)) {
+                // 计算最低和最高折扣价格
+                $goods['min_discount_price'] = min($discountPrices);
+                $goods['max_discount_price'] = max($discountPrices);
+                $goods['min_discount'] = min($discounts);
+                $goods['max_discount'] = max($discounts);
+                
+                // 单规格商品处理
+                if ($goods['spec_type'] == 0) {
+                    $goods['discount_price'] = $goods['min_discount_price'];
+                    $goods['discount'] = $goods['min_discount'];
+                    $goods['discount_amount'] = round($goods['original_price'] - $goods['discount_price'], 2);
+                    $goods['price_range'] = '¥' . $goods['discount_price'];
+                } 
+                // 多规格商品处理
+                else {
+                    // 设置价格区间
+                    if ($goods['min_discount_price'] == $goods['max_discount_price']) {
+                        $goods['price_range'] = '¥' . $goods['min_discount_price'];
+                    } else {
+                        $goods['price_range'] = '¥' . $goods['min_discount_price'] . '-' . $goods['max_discount_price'];
+                    }
+                    
+                    // 对于多规格,使用最低价格作为主要显示价格
+                    $goods['discount_price'] = $goods['min_discount_price'];
+                    $goods['discount'] = $goods['min_discount'];
+                }
+            }
+        }
+        
+        return $goodsData;
+    }
+
+    /**
+     * 获取当前有效的活动ID列表
+     * @return array
+     */
+    private static function getActiveActivityIds()
+    {
+        $currentTime = time();
+        
+        return Db::table('shop_activity')
+            ->where('start_time', '<=', $currentTime)
+            ->where('end_time', '>=', $currentTime)
+            ->where('activity_status', ActivityEnum::ACTIVITY_STATUS_ONGOING)
+            ->where('status', StatusEnum::ENABLED)
+            ->column('id');
+    }
+
+    /**
+     * 初始化商品折扣数据结构
+     * @param array $item 商品数据
+     * @return array
+     */
+    private static function initGoodsDiscountStructure($item)
+    {
+        return [
+            'goods_id' => $item['goods_id'],
+            'title' => $item['title'],
+            'sub_title' => $item['sub_title'],
+            'image' => $item['image'],
+            'original_price' => $item['original_price'],
+            'market_price' => $item['market_price'],
+            'goods_stocks' => $item['goods_stocks'],
+            'sales' => $item['sales'],
+            'spec_type' => $item['spec_type'],
+            'activity_id' => $item['activity_id'] ?? null,
+            'activity_name' => $item['activity_name'] ?? null,
+            'start_time' => $item['start_time'] ?? null,
+            'end_time' => $item['end_time'] ?? null,
+            'has_discount' => true,
+            'discount_info' => [],
+            // 初始化折扣价格相关字段
+            'min_discount_price' => null,
+            'max_discount_price' => null,
+            'min_discount' => null,
+            'max_discount' => null,
+            'discount_price' => null,
+            'discount' => null,
+            'discount_amount' => null,
+            'price_range' => ''
+        ];
+    }
 }

+ 422 - 0
application/common/Service/Lottery/LotteryActivityService.php

@@ -0,0 +1,422 @@
+<?php
+
+namespace app\common\Service\Lottery;
+
+use app\common\model\lottery\LotteryActivity;
+use app\common\Enum\LotteryEnum;
+
+/**
+ * 抽奖活动服务类
+ */
+class LotteryActivityService
+{
+    /**
+     * 检查活动是否正在进行
+     */
+    public static function isRunning(LotteryActivity $activity)
+    {
+        $now = time();
+        return $activity->status == LotteryEnum::STATUS_ONGOING 
+               && $activity->start_time <= $now 
+               && $activity->end_time >= $now;
+    }
+
+    /**
+     * 检查活动是否已结束
+     */
+    public static function isEnded(LotteryActivity $activity)
+    {
+        $now = time();
+        return $activity->status == LotteryEnum::STATUS_ENDED 
+               || $activity->end_time < $now;
+    }
+
+    /**
+     * 检查活动是否未开始
+     */
+    public static function isNotStarted(LotteryActivity $activity)
+    {
+        $now = time();
+        return $activity->status == LotteryEnum::STATUS_NOT_STARTED 
+               && $activity->start_time > $now;
+    }
+
+    /**
+     * 检查活动是否已暂停
+     */
+    public static function isSuspended(LotteryActivity $activity)
+    {
+        return $activity->status == LotteryEnum::STATUS_SUSPENDED;
+    }
+
+    /**
+     * 检查活动是否已取消
+     */
+    public static function isCancelled(LotteryActivity $activity)
+    {
+        return $activity->status == LotteryEnum::STATUS_CANCELLED;
+    }
+
+    /**
+     * 检查是否在抽奖时间内
+     * 注意:当前数据库表结构中没有draw_time相关字段,所以始终返回true
+     */
+    public static function isInDrawTime(LotteryActivity $activity)
+    {
+        // 当前表结构中没有draw_time_enable, draw_time_start, draw_time_end字段
+        // 所以直接返回true,表示始终可以抽奖
+        return true;
+    }
+
+    /**
+     * 获取正在进行的活动
+     */
+    public static function getRunningActivities()
+    {
+        $now = time();
+        return LotteryActivity::where('status', LotteryEnum::STATUS_ONGOING)
+                             ->where('start_time', '<=', $now)
+                             ->where('end_time', '>=', $now)
+                             ->select();
+    }
+
+    /**
+     * 获取未开始的活动
+     */
+    public static function getNotStartedActivities()
+    {
+        $now = time();
+        return LotteryActivity::where('status', LotteryEnum::STATUS_NOT_STARTED)
+                             ->where('start_time', '>', $now)
+                             ->select();
+    }
+
+    /**
+     * 获取已结束的活动
+     */
+    public static function getEndedActivities()
+    {
+        $now = time();
+        return LotteryActivity::where('status', LotteryEnum::STATUS_ENDED)
+                             ->orWhere('end_time', '<', $now)
+                             ->select();
+    }
+
+    /**
+     * 获取可显示的活动(排除逻辑状态)
+     */
+    public static function getDisplayableActivities()
+    {
+        $displayableStatuses = array_keys(LotteryEnum::getActivityStatusMap());
+        return LotteryActivity::whereIn('status', $displayableStatuses)->select();
+    }
+
+    /**
+     * 验证活动状态是否有效
+     */
+    public static function isValidStatus(LotteryActivity $activity)
+    {
+        return LotteryEnum::isValidActivityStatus($activity->status);
+    }
+
+    /**
+     * 验证开奖方式是否有效
+     */
+    public static function isValidLotteryType(LotteryActivity $activity)
+    {
+        return LotteryEnum::isValidLotteryType($activity->lottery_type);
+    }
+
+    /**
+     * 获取活动状态描述
+     */
+    public static function getActivityStatusDescription(LotteryActivity $activity)
+    {
+        $now = time();
+        
+        if (self::isCancelled($activity)) {
+            return '活动已取消';
+        }
+        
+        if (self::isSuspended($activity)) {
+            return '活动已暂停';
+        }
+        
+        if (self::isEnded($activity)) {
+            return '活动已结束';
+        }
+        
+        if (self::isRunning($activity)) {
+            return '活动进行中';
+        }
+        
+        if (self::isNotStarted($activity)) {
+            return '活动未开始';
+        }
+        
+        return '未知状态';
+    }
+
+    /**
+     * 检查用户是否可以参与活动
+     */
+    public static function canUserParticipate(LotteryActivity $activity, $userId)
+    {
+        // 检查活动状态
+        if (!self::isRunning($activity)) {
+            return false;
+        }
+        
+        // 检查抽奖时间
+        if (!self::isInDrawTime($activity)) {
+            return false;
+        }
+        
+        // TODO: 检查用户群体限制
+        // TODO: 检查用户参与次数限制
+        
+        return true;
+    }
+
+    /**
+     * 获取活动统计信息
+     */
+    public static function getActivityStatistics(LotteryActivity $activity)
+    {
+        return [
+            'total_participants' => $activity->drawRecords()->count(),
+            'total_winners' => $activity->winRecords()->count(),
+            'total_prizes' => $activity->prizes()->count(),
+            'total_conditions' => $activity->conditions()->count(),
+        ];
+    }
+
+    /**
+     * 批量更新活动状态
+     */
+    public static function batchUpdateStatus()
+    {
+        $now = time();
+        
+        // 将已过期的活动标记为已结束
+        LotteryActivity::where('status', LotteryEnum::STATUS_ONGOING)
+                      ->where('end_time', '<', $now)
+                      ->update(['status' => LotteryEnum::STATUS_ENDED]);
+        
+        // 将到期的未开始活动标记为进行中
+        LotteryActivity::where('status', LotteryEnum::STATUS_NOT_STARTED)
+                      ->where('start_time', '<=', $now)
+                      ->where('end_time', '>=', $now)
+                      ->update(['status' => LotteryEnum::STATUS_ONGOING]);
+    }
+
+    /**
+     * 创建新活动
+     */
+    public static function createActivity($data)
+    {
+        // 验证数据
+        if (!LotteryEnum::isValidActivityStatus($data['status'])) {
+            throw new \Exception('无效的活动状态');
+        }
+        
+        if (!LotteryEnum::isValidLotteryType($data['lottery_type'])) {
+            throw new \Exception('无效的开奖方式');
+        }
+        
+        return LotteryActivity::create($data);
+    }
+
+    /**
+     * 更新活动
+     */
+    public static function updateActivity(LotteryActivity $activity, $data)
+    {
+        // 验证数据
+        if (isset($data['status']) && !LotteryEnum::isValidActivityStatus($data['status'])) {
+            throw new \Exception('无效的活动状态');
+        }
+        
+        if (isset($data['lottery_type']) && !LotteryEnum::isValidLotteryType($data['lottery_type'])) {
+            throw new \Exception('无效的开奖方式');
+        }
+        
+        return $activity->save($data);
+    }
+
+    /**
+     * 删除活动
+     */
+    public static function deleteActivity(LotteryActivity $activity)
+    {
+        // 检查是否可以删除
+        if (self::isRunning($activity)) {
+            throw new \Exception('进行中的活动不能删除');
+        }
+        
+        return $activity->delete();
+    }
+
+        /**
+     * 获取奖品列表
+     * @param int|null $activityId 活动ID,不传则查询进行中活动的奖品
+     * @param int $page 页码
+     * @param int $pageSize 每页数量
+     * @param array|null $type 奖品类型筛选数组
+     * @return \think\Paginator
+     */
+    public static function getPrizes($activityId = null, $page = 1, $pageSize = 10, $type = null)
+    {
+        $where = [];
+        
+        // 如果指定了活动ID,查询该活动的奖品
+        if ($activityId) {
+            $where['activity_id'] = $activityId;
+        } else {
+            // 否则查询进行中活动的奖品
+            $now = time();
+            $runningActivityIds = LotteryActivity::where('status', LotteryEnum::STATUS_ONGOING)
+                                                ->where('start_time', '<=', $now)
+                                                ->where('end_time', '>=', $now)
+                                                ->column('id');
+            
+            if (empty($runningActivityIds)) {
+                // 返回空的分页对象
+                $prizeModel = new \app\common\model\lottery\LotteryPrize();
+                return $prizeModel->where('id', 0)->paginate($pageSize, false, ['page' => $page]);
+            }
+            
+            $where['activity_id'] = ['in', $runningActivityIds];
+        }
+
+        // 奖品状态筛选(只查询有效的奖品)
+        $where['status'] = 1;
+
+        // 奖品类型筛选
+        if ($type && is_array($type) && !empty($type)) {
+            $where['type'] = ['in', $type];
+        }
+
+        // 使用模型的分页查询器
+        $prizeModel = new \app\common\model\lottery\LotteryPrize();
+        $paginate = $prizeModel->where($where)
+                              ->field('id,activity_id,name,type,image,description,probability,total_stock,remain_stock,win_prompt,sort_order,unlock_people_num,createtime')
+                              ->order('sort_order asc, createtime desc')
+                              ->paginate($pageSize, false, ['page' => $page]);
+
+        // 处理分页数据,添加额外信息
+        foreach ($paginate->items() as $prize) {
+            // 获取活动信息
+            $activity = LotteryActivity::find($prize->activity_id);
+            $prize->activity_name = $activity->name ?? '';
+            $prize->activity_status = $activity->status ?? 0;
+            
+            // 奖品类型文本
+            $prize->type_text = LotteryEnum::getPrizeTypeText($prize->type);
+            
+            // 检查奖品是否已解锁(如果活动开启按人数解锁)
+            if ($activity && $activity->unlock_by_people && $prize->unlock_people_num) {
+                $currentPeopleCount = \app\common\Service\lottery\LotteryChanceService::getActivityParticipants($prize->activity_id);
+                $prize->is_unlocked = $currentPeopleCount >= $prize->unlock_people_num;
+            } else {
+                $prize->is_unlocked = true;
+            }
+            
+            // 计算中奖率百分比
+            $prize->probability_percent = round($prize->probability * 100, 2);
+            
+            // 计算库存百分比
+            if ($prize->total_stock > 0) {
+                $prize->stock_percent = round(($prize->remain_stock / $prize->total_stock) * 100, 2);
+            } else {
+                $prize->stock_percent = 0;
+            }
+        }
+
+        return $paginate;
+    }
+
+    /**
+     * 获取活动奖品统计信息
+     * @param int $activityId 活动ID
+     * @return array
+     */
+    public static function getActivityPrizeStats($activityId)
+    {
+        $prizes = \app\common\model\lottery\LotteryPrize::where('activity_id', $activityId)
+                                                        ->where('status', 1)
+                                                        ->select();
+
+        $stats = [
+            'total_prizes' => 0,
+            'total_stock' => 0,
+            'remain_stock' => 0,
+            'used_stock' => 0,
+            'prize_types' => [],
+            'unlocked_prizes' => 0,
+            'locked_prizes' => 0
+        ];
+
+        foreach ($prizes as $prize) {
+            $stats['total_prizes']++;
+            $stats['total_stock'] += $prize->total_stock;
+            $stats['remain_stock'] += $prize->remain_stock;
+            $stats['used_stock'] += ($prize->total_stock - $prize->remain_stock);
+
+            // 统计奖品类型
+            $typeText = LotteryEnum::getPrizeTypeText($prize->type);
+            if (!isset($stats['prize_types'][$prize->type])) {
+                $stats['prize_types'][$prize->type] = [
+                    'type' => $prize->type,
+                    'type_text' => $typeText,
+                    'count' => 0,
+                    'total_stock' => 0,
+                    'remain_stock' => 0
+                ];
+            }
+            $stats['prize_types'][$prize->type]['count']++;
+            $stats['prize_types'][$prize->type]['total_stock'] += $prize->total_stock;
+            $stats['prize_types'][$prize->type]['remain_stock'] += $prize->remain_stock;
+
+            // 检查解锁状态
+            $activity = LotteryActivity::find($activityId);
+            if ($activity && $activity->unlock_by_people && $prize->unlock_people_num) {
+                $currentPeopleCount = \app\common\Service\lottery\LotteryChanceService::getActivityParticipants($activityId);
+                if ($currentPeopleCount >= $prize->unlock_people_num) {
+                    $stats['unlocked_prizes']++;
+                } else {
+                    $stats['locked_prizes']++;
+                }
+            } else {
+                $stats['unlocked_prizes']++;
+            }
+        }
+
+        return $stats;
+    }
+
+    /**
+     * 获取进行中活动的奖品列表
+     * @param int $page 页码
+     * @param int $pageSize 每页数量
+     * @param int|array|null $type 奖品类型筛选,支持单个类型或类型数组
+     * @return \think\Paginator
+     */
+    public static function getRunningActivityPrizes($page = 1, $pageSize = 20, $type = null)
+    {
+        return self::getPrizes(null, $page, $pageSize, $type);
+    }
+
+    /**
+     * 获取指定活动的奖品列表
+     * @param int $activityId 活动ID
+     * @param int $page 页码
+     * @param int $pageSize 每页数量
+     * @param int|array|null $type 奖品类型筛选,支持单个类型或类型数组
+     * @return \think\Paginator
+     */
+    public static function getActivityPrizes($activityId, $page = 1, $pageSize = 20, $type = null)
+    {
+        return self::getPrizes($activityId, $page, $pageSize, $type);
+    }
+} 

+ 1174 - 0
application/common/Service/Lottery/LotteryChanceService.php

@@ -0,0 +1,1174 @@
+<?php
+
+namespace app\common\Service\Lottery;
+
+use app\common\model\lottery\LotteryActivity;
+use app\common\model\lottery\LotteryCondition;
+use app\common\model\lottery\LotteryUserChance;
+use app\common\model\lottery\LotteryUserChanceRecord;
+use app\common\model\User;
+use app\common\model\Order;
+use app\common\Enum\LotteryEnum;
+use think\Exception;
+use think\Db;
+
+/**
+ * 抽奖机会服务类
+ * 处理用户获得抽奖机会的逻辑
+ * 
+ * 主要功能:
+ * - 订单/充值后自动分发抽奖机会
+ * - 条件验证和机会计算
+ * - 用户机会管理
+ * - 批量操作和统计
+ */
+class LotteryChanceService
+{
+    // ============ 常量定义 ============
+    
+    /** @var int 默认批量处理数量 */
+    const DEFAULT_BATCH_SIZE = 100;
+    
+    /** @var int 最大批量处理数量 */
+    const MAX_BATCH_SIZE = 1000;
+
+
+    /**
+     * 订单完成后检查并分发抽奖机会
+     * 
+     * @param array $orderInfo 订单信息 ['id', 'total_amount', 'goods' => [['goods_id']]]
+     * @param int $userId 用户ID
+     * @return array 获得的抽奖机会信息
+     * @throws Exception
+     */
+    public static function checkAndGrantChanceForOrder($orderInfo, $userId)
+    {
+        if (empty($orderInfo) || empty($userId)) {
+            throw new Exception('订单信息或用户ID不能为空');
+        }
+
+        $grantedChances = [];
+        
+        try {
+        // 获取所有正在进行的抽奖活动
+            $activities = LotteryService::getRunningActivities();
+        
+        foreach ($activities as $activity) {
+            try {
+                $chances = static::processActivityForOrder($activity, $orderInfo, $userId);
+                if ($chances > 0) {
+                    $grantedChances[] = [
+                        'activity_id' => $activity->id,
+                        'activity_name' => $activity->name,
+                            'chances' => $chances,
+                            'granted_time' => time()
+                    ];
+                }
+            } catch (Exception $e) {
+                // 记录错误但不影响其他活动的处理
+                    trace("抽奖机会分发失败 - 活动ID: {$activity->id}, 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
+                }
+            }
+        } catch (Exception $e) {
+            trace("订单抽奖机会分发失败 - 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
+            throw $e;
+        }
+        
+        return $grantedChances;
+    }
+
+    /**
+     * 充值完成后检查并分发抽奖机会
+     * 
+     * @param array $rechargeInfo 充值信息 ['amount', 'type' => 'recharge']
+     * @param int $userId 用户ID
+     * @return array 获得的抽奖机会信息
+     * @throws Exception
+     */
+    public static function checkAndGrantChanceForRecharge($rechargeInfo, $userId)
+    {
+        if (empty($rechargeInfo) || empty($userId)) {
+            throw new Exception('充值信息或用户ID不能为空');
+        }
+
+        $grantedChances = [];
+        
+        try {
+        // 获取所有正在进行的抽奖活动
+            $activities = LotteryService::getRunningActivities();
+        
+        foreach ($activities as $activity) {
+            try {
+                $chances = static::processActivityForRecharge($activity, $rechargeInfo, $userId);
+                if ($chances > 0) {
+                    $grantedChances[] = [
+                        'activity_id' => $activity->id,
+                        'activity_name' => $activity->name,
+                            'chances' => $chances,
+                            'granted_time' => time()
+                    ];
+                }
+            } catch (Exception $e) {
+                // 记录错误但不影响其他活动的处理
+                    trace("充值抽奖机会分发失败 - 活动ID: {$activity->id}, 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
+                }
+            }
+        } catch (Exception $e) {
+            trace("充值抽奖机会分发失败 - 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
+            throw $e;
+        }
+        
+        return $grantedChances;
+    }
+
+    /**
+     * 手动给用户增加抽奖机会(管理员操作)     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @param int $chances 机会次数
+     * @param string $reason 原因
+     * @param int $adminId 管理员ID
+     * @return bool
+     * @throws Exception
+     */
+    public static function manualGrantChance($activityId, $userId, $chances, $reason = '', $adminId = 0)
+    {
+        if ($chances <= 0) {
+            throw new Exception('抽奖机会数量必须大于0');
+        }
+
+        $activity = LotteryActivity::find($activityId);
+        if (!$activity) {
+            throw new Exception('活动不存在');
+        }
+
+        $user = User::find($userId);
+        if (!$user) {
+            throw new Exception('用户不存在');
+        }
+
+        $detail = [
+            'reason' => $reason,
+            'admin_id' => $adminId,
+            'granted_time' => time()
+        ];
+
+        return static::grantChanceToUser($activityId, $userId, $chances, $detail);
+    }
+
+    /**
+     * 批量初始化用户抽奖机会(活动开始时)
+     * 
+     * @param int $activityId 活动ID
+     * @param array $userIds 用户ID数组
+     * @param int $initChances 初始机会数
+     * @param int $batchSize 批量处理大小
+     * @return array 处理结果
+     * @throws Exception
+     */
+    public static function batchInitChances($activityId, $userIds, $initChances = 1, $batchSize = null)
+    {
+        if (empty($userIds)) {
+            throw new Exception('用户ID列表不能为空');
+        }
+
+        $activity = LotteryActivity::find($activityId);
+        if (!$activity) {
+            throw new Exception('活动不存在');
+        }
+
+        $batchSize = $batchSize ?: static::DEFAULT_BATCH_SIZE;
+        $batchSize = min($batchSize, static::MAX_BATCH_SIZE);
+
+        $result = [
+            'total_users' => count($userIds),
+            'success_count' => 0,
+            'failed_count' => 0,
+            'errors' => []
+        ];
+
+        // 分批处理
+        $batches = array_chunk($userIds, $batchSize);
+        
+        foreach ($batches as $batch) {
+            try {
+                $success = static::batchCreateChances($activityId, $batch, $initChances);
+                if ($success) {
+                    $result['success_count'] += count($batch);
+                } else {
+                    $result['failed_count'] += count($batch);
+                    $result['errors'][] = "批次处理失败: " . implode(',', $batch);
+                }
+            } catch (Exception $e) {
+                $result['failed_count'] += count($batch);
+                $result['errors'][] = "批次处理异常: " . $e->getMessage();
+            }
+        }
+
+        return $result;
+    }
+
+    // ============ 私有处理方法 ============
+
+    /**
+     * 处理订单相关的活动
+     * 
+     * @param LotteryActivity $activity 活动对象
+     * @param array $orderInfo 订单信息
+     * @param int $userId 用户ID
+     * @return int 获得的抽奖机会数量
+     */
+    private static function processActivityForOrder($activity, $orderInfo, $userId)
+    {
+        // 检查用户是否符合活动参与资格
+        if (!static::checkUserQualification($activity, $userId)) {
+            return 0;
+        }
+
+        // 获取活动的参与条件
+        $conditions = static::getValidConditions($activity->id);
+        $totalChances = 0;
+        $chanceDetails = [];
+
+        foreach ($conditions as $condition) {
+            // 跳过充值条件
+            if ($condition->type == LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT) {
+                continue;
+            }
+
+            $result = static::processConditionForOrder($condition, $orderInfo, $userId);
+            if ($result['chances'] > 0) {
+                $totalChances += $result['chances'];
+                $result['detail']['chances'] = $result['chances'];
+                $chanceDetails[] = $result['detail'];
+            }
+        }
+
+        // 如果获得了抽奖机会,记录到数据库
+        if ($totalChances > 0) {
+            // 为每个条件分别记录
+            foreach ($chanceDetails as $detail) {
+                static::grantChanceToUser($activity->id, $userId, $detail['chances'] ?? 1, [
+                    'condition_id' => $detail['condition_id'],
+                    'condition_type' => $detail['condition_type'],
+                    'condition_value' => $detail['condition_value'],
+                    'order_id' => $orderInfo['id'] ?? 0,
+                    'granted_time' => time()
+                ]);
+            }
+        }
+
+        return $totalChances;
+    }
+
+    /**
+     * 处理充值相关的活动
+     * 
+     * @param LotteryActivity $activity 活动对象
+     * @param array $rechargeInfo 充值信息
+     * @param int $userId 用户ID
+     * @return int 获得的抽奖机会数量
+     */
+    private static function processActivityForRecharge($activity, $rechargeInfo, $userId)
+    {
+        // 检查用户是否符合活动参与资格
+        if (!static::checkUserQualification($activity, $userId)) {
+            return 0;
+        }
+
+        // 获取活动的参与条件
+        $conditions = static::getValidConditions($activity->id);
+        $totalChances = 0;
+        $chanceDetails = [];
+
+        foreach ($conditions as $condition) {
+            // 只处理充值条件
+            if ($condition->type != LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT) {
+                continue;
+            }
+
+            $result = static::processConditionForRecharge($condition, $rechargeInfo, $userId);
+            if ($result['chances'] > 0) {
+                $totalChances += $result['chances'];
+                $result['detail']['chances'] = $result['chances'];
+                $chanceDetails[] = $result['detail'];
+            }
+        }
+
+        // 如果获得了抽奖机会,记录到数据库
+        if ($totalChances > 0) {
+            // 为每个条件分别记录
+            foreach ($chanceDetails as $detail) {
+                static::grantChanceToUser($activity->id, $userId, $detail['chances'] ?? 1, [
+                    'condition_id' => $detail['condition_id'],
+                    'condition_type' => $detail['condition_type'],
+                    'condition_value' => $detail['condition_value'],
+                    'recharge_amount' => $rechargeInfo['amount'] ?? 0,
+                    'granted_time' => time()
+                ]);
+            }
+        }
+
+        return $totalChances;
+    }
+
+    /**
+     * 处理订单条件
+     * 
+     * @param LotteryCondition $condition 条件对象
+     * @param array $orderInfo 订单信息
+     * @param int $userId 用户ID
+     * @return array 获得的抽奖机会信息 ['chances' => int, 'detail' => array]
+     */
+    private static function processConditionForOrder($condition, $orderInfo, $userId)
+    {
+        $chances = 0;
+        $detail = [
+            'condition_id' => $condition->id,
+            'condition_type' => $condition->type,
+            'condition_value' => $condition->condition_value,
+        ];
+
+        switch ($condition->type) {
+            case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
+                if (static::validateGoodsCondition($condition, $orderInfo)) {
+                    $chances = static::getRewardTimes($condition);
+                }
+                break;
+
+            case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
+                if (static::validateOrderAmountCondition($condition, $orderInfo)) {
+                    $chances = static::getRewardTimes($condition);
+                    
+                    // 如果可重复获得,根据金额倍数计算次数
+                    if (static::canRepeat($condition)) {
+                        $multiple = floor($orderInfo['total_amount'] / $condition->condition_value);
+                        $chances *= $multiple;
+                    }
+                }
+                break;
+
+            case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
+                if (static::validateAccumulateCondition($condition, $userId, $condition->activity_id)) {
+                    // 检查是否已经因为累计消费获得过机会
+                    if (!static::hasGrantedForAccumulate($condition->activity_id, $userId)) {
+                        $chances = static::getRewardTimes($condition);
+                    }
+                }
+                break;
+        }
+
+        return [
+            'chances' => $chances,
+            'detail' => $detail
+        ];
+    }
+
+    /**
+     * 处理充值条件
+     * 
+     * @param LotteryCondition $condition 条件对象
+     * @param array $rechargeInfo 充值信息
+     * @param int $userId 用户ID
+     * @return array 获得的抽奖机会信息 ['chances' => int, 'detail' => array]
+     */
+    private static function processConditionForRecharge($condition, $rechargeInfo, $userId)
+    {
+        $chances = 0;
+        $detail = [
+            'condition_id' => $condition->id,
+            'condition_type' => $condition->type,
+            'condition_value' => $condition->condition_value,
+        ];
+        
+        if (static::validateRechargeCondition($condition, ['type' => 'recharge', 'amount' => $rechargeInfo['amount']])) {
+            $chances = static::getRewardTimes($condition);
+            
+            // 如果可重复获得,根据金额倍数计算次数
+            if (static::canRepeat($condition)) {
+                $multiple = floor($rechargeInfo['amount'] / $condition->condition_value);
+                $chances *= $multiple;
+            }
+        }
+
+        return [
+            'chances' => $chances,
+            'detail' => $detail
+        ];
+    }
+
+    // ============ 用户资格检查方法 ============
+
+    /**
+     * 检查用户资格
+     * 
+     * @param LotteryActivity $activity 活动对象
+     * @param int $userId 用户ID
+     * @return bool
+     */
+    private static function checkUserQualification($activity, $userId)
+    {
+        // 检查用户群体限制
+        switch ($activity->user_limit_type) {
+            case LotteryEnum::USER_LIMIT_ALL:
+                return true;
+            case LotteryEnum::USER_LIMIT_LEVEL:
+                return static::checkUserLevel($userId, $activity->user_limit_value);
+            case LotteryEnum::USER_LIMIT_TAG:
+                return static::checkUserTag($userId, $activity->user_limit_value);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 检查用户等级
+     * 
+     * @param int $userId 用户ID
+     * @param mixed $limitValue 限制值
+     * @return bool
+     */
+    private static function checkUserLevel($userId, $limitValue)
+    {
+        if (empty($limitValue)) {
+            return true;
+        }
+        
+        $user = User::find($userId);
+        if (!$user) {
+            return false;
+        }
+        
+        $limitLevels = is_array($limitValue) ? $limitValue : [$limitValue];
+        return in_array($user->level, $limitLevels);
+    }
+
+    /**
+     * 检查用户标签
+     * 
+     * @param int $userId 用户ID
+     * @param mixed $limitValue 限制值
+     * @return bool
+     */
+    private static function checkUserTag($userId, $limitValue)
+    {
+        if (empty($limitValue)) {
+            return true;
+        }
+        
+        // TODO: 根据实际的用户标签系统实现
+        // 这里需要根据具体的用户标签表结构来实现
+        // 暂时返回true,避免影响现有功能
+        
+        return true;
+    }
+
+    /**
+     * 检查是否已因累计消费获得过机会
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @return bool
+     */
+    private static function hasGrantedForAccumulate($activityId, $userId)
+    {
+        $record = LotteryUserChanceRecord::where('activity_id', $activityId)
+                                         ->where('user_id', $userId)
+                                         ->where('get_type', LotteryEnum::CHANCE_GET_TYPE_TOTAL_AMOUNT)
+                                         ->find();
+        
+        return $record ? true : false;
+    }
+
+    // ============ 条件验证方法 ============
+
+    /**
+     * 验证订单是否满足条件
+     * 
+     * @param LotteryCondition $condition 条件对象
+     * @param array $orderInfo 订单信息
+     * @return bool
+     */
+    public static function validateOrder(LotteryCondition $condition, $orderInfo)
+    {
+        switch ($condition->type) {
+            case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
+                return static::validateGoodsCondition($condition, $orderInfo);
+            case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
+                return static::validateOrderAmountCondition($condition, $orderInfo);
+            case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
+                return static::validateRechargeCondition($condition, $orderInfo);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 验证商品条件
+     * 
+     * @param LotteryCondition $condition 条件对象
+     * @param array $orderInfo 订单信息
+     * @return bool
+     */
+    public static function validateGoodsCondition(LotteryCondition $condition, $orderInfo)
+    {
+        if (empty($orderInfo['goods']) || empty($condition->goods_ids_list)) {
+            return false;
+        }
+
+        $orderGoodsIds = array_column($orderInfo['goods'], 'goods_id');
+        $conditionGoodsIds = $condition->goods_ids_list;
+        $intersection = array_intersect($orderGoodsIds, $conditionGoodsIds);
+
+        if ($condition->goods_rule == LotteryEnum::GOODS_RULE_INCLUDE) {
+            // 指定商品参与:订单中必须包含指定商品
+            return !empty($intersection);
+        } else {
+            // 指定商品不可参与:订单中不能包含指定商品
+            return empty($intersection);
+        }
+    }
+
+    /**
+     * 验证订单金额条件
+     * 
+     * @param LotteryCondition $condition 条件对象
+     * @param array $orderInfo 订单信息
+     * @return bool
+     */
+    public static function validateOrderAmountCondition(LotteryCondition $condition, $orderInfo)
+    {
+        $orderAmount = $orderInfo['total_amount'] ?? 0;
+        return $orderAmount >= $condition->condition_value;
+    }
+
+    /**
+     * 验证充值条件
+     * 
+     * @param LotteryCondition $condition 条件对象
+     * @param array $orderInfo 订单信息
+     * @return bool
+     */
+    public static function validateRechargeCondition(LotteryCondition $condition, $orderInfo)
+    {
+        if (($orderInfo['type'] ?? '') !== 'recharge') {
+            return false;
+        }
+        
+        $rechargeAmount = $orderInfo['amount'] ?? 0;
+        return $rechargeAmount >= $condition->condition_value;
+    }
+
+    /**
+     * 验证累计消费条件
+     * 
+     * @param LotteryCondition $condition 条件对象
+     * @param int $userId 用户ID
+     * @param int $activityId 活动ID
+     * @return bool
+     */
+    public static function validateAccumulateCondition(LotteryCondition $condition, $userId, $activityId)
+    {
+        $activity = LotteryActivity::find($activityId);
+        if (!$activity) {
+            return false;
+        }
+
+        // 计算活动期间用户累计消费
+        $totalAmount = Order::where('user_id', $userId)
+                           ->where('status', 'paid')
+                           ->where('createtime', '>=', $activity->start_time)
+                           ->where('createtime', '<=', $activity->end_time)
+                           ->sum('total_amount');
+
+        return $totalAmount >= $condition->condition_value;
+    }
+
+    // ============ 条件管理方法 ============
+
+    /**
+     * 获取活动的有效条件
+     * 
+     * @param int $activityId 活动ID
+     * @return array
+     */
+    public static function getValidConditions($activityId)
+    {
+        return LotteryCondition::where('activity_id', $activityId)
+                              ->where('status', 1)
+                              ->order('id', 'asc')
+                              ->select();
+        }
+
+    /**
+     * 检查条件是否可重复获得奖励
+     * 
+     * @param LotteryCondition $condition 条件对象
+     * @return bool
+     */
+    public static function canRepeat(LotteryCondition $condition)
+    {
+        return $condition->is_repeatable == 1;
+    }
+
+    /**
+     * 获取奖励次数
+     * 
+     * @param LotteryCondition $condition 条件对象
+     * @return int
+     */
+    public static function getRewardTimes(LotteryCondition $condition)
+    {
+        return $condition->reward_times ?: 1;
+    }
+
+    /**
+     * 批量验证订单条件
+     * 
+     * @param int $activityId 活动ID
+     * @param array $orderInfo 订单信息
+     * @param int $userId 用户ID
+     * @return array 满足的条件列表
+     */
+    public static function batchValidateConditions($activityId, $orderInfo, $userId)
+    {
+        $conditions = static::getValidConditions($activityId);
+        $validConditions = [];
+
+        foreach ($conditions as $condition) {
+            if (static::validateOrder($condition, $orderInfo)) {
+                $validConditions[] = [
+                    'condition_id' => $condition->id,
+                    'condition_name' => $condition->name,
+                    'condition_type' => $condition->type,
+                    'condition_type_text' => $condition->type_text,
+                    'reward_times' => static::getRewardTimes($condition),
+                    'can_repeat' => static::canRepeat($condition)
+        ];
+            }
+        }
+
+        return $validConditions;
+    }
+
+    /**
+     * 获取条件统计信息
+     * 
+     * @param int $activityId 活动ID
+     * @return array
+     */
+    public static function getConditionStatistics($activityId)
+    {
+        $conditions = static::getValidConditions($activityId);
+        $statistics = [
+            'total_conditions' => count($conditions),
+            'goods_conditions' => 0,
+            'amount_conditions' => 0,
+            'recharge_conditions' => 0,
+            'accumulate_conditions' => 0
+        ];
+
+        foreach ($conditions as $condition) {
+            switch ($condition->type) {
+                case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
+                    $statistics['goods_conditions']++;
+                    break;
+                case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
+                    $statistics['amount_conditions']++;
+                    break;
+                case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
+                    $statistics['recharge_conditions']++;
+                    break;
+                case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
+                    $statistics['accumulate_conditions']++;
+                    break;
+            }
+        }
+
+        return $statistics;
+    }
+
+    // ============ 用户机会管理方法 ============
+
+    /**
+     * 获取用户抽奖机会
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @return LotteryUserChance|null
+     */
+    public static function getUserChance($activityId, $userId)
+    {
+        return LotteryUserChance::where('activity_id', $activityId)
+                                ->where('user_id', $userId)
+                                ->find();
+    }
+
+    /**
+     * 根据详情确定机会获取类型
+     * 
+     * @param array $detail 获得详情
+     * @return int 获取类型
+     */
+    private static function getChanceGetTypeFromDetail($detail)
+    {
+        // 如果有条件类型,直接使用条件类型对应的获取类型
+        if (isset($detail['condition_type'])) {
+            switch ($detail['condition_type']) {
+                case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
+                    return LotteryEnum::CHANCE_GET_TYPE_BUY_GOODS;
+                case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
+                    return LotteryEnum::CHANCE_GET_TYPE_ORDER_AMOUNT;
+                case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
+                    return LotteryEnum::CHANCE_GET_TYPE_RECHARGE;
+                case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
+                    return LotteryEnum::CHANCE_GET_TYPE_TOTAL_AMOUNT;
+            }
+        }
+        
+        // 如果没有条件类型,检查是否是管理员赠送
+        if (isset($detail['admin_id']) && $detail['admin_id'] > 0) {
+            return LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT;
+        }
+        
+        // 默认返回管理员赠送
+        return LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT;
+    }
+
+    /**
+     * 获取用户在指定活动中的抽奖机会详情
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @return array
+     */
+    public static function getUserChanceDetail($activityId, $userId)
+    {
+        $userChance = static::getUserChance($activityId, $userId);
+        
+        if (!$userChance) {
+            return [
+                'total_chances' => 0,
+                'used_chances' => 0,
+                'remain_chances' => 0,
+                'get_records' => []
+            ];
+        }
+
+        // 获取机会获得记录
+        $getRecords = LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId);
+
+        return [
+            'total_chances' => $userChance->total_chances,
+            'used_chances' => $userChance->used_chances,
+            'remain_chances' => $userChance->remain_chances,
+            'last_get_time' => $userChance->last_get_time,
+            'last_use_time' => $userChance->last_use_time,
+            'get_records' => $getRecords
+        ];
+    }
+
+    /**
+     * 给用户分发抽奖机会
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @param int $chances 机会次数
+     * @param array $detail 获得详情
+     * @return LotteryUserChance|bool
+     */
+    private static function grantChanceToUser($activityId, $userId, $chances, $detail)
+    {
+        return static::addChance($activityId, $userId, $chances, $detail);
+    }
+
+    /**
+     * 增加抽奖机会
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @param int $times 机会次数
+     * @param array $detail 获得详情
+     * @return LotteryUserChance|bool
+     */
+    public static function addChance($activityId, $userId, $times = 1, $detail = [])
+    {
+        if ($times <= 0) {
+            return false;
+        }
+
+        try {
+            Db::startTrans();
+            
+            $chance = static::getUserChance($activityId, $userId);
+            
+            if (!$chance) {
+                // 创建新记录
+                $data = [
+                    'activity_id' => $activityId,
+                    'user_id' => $userId,
+                    'total_chances' => $times,
+                    'used_chances' => 0,
+                    'remain_chances' => $times,
+                    'last_get_time' => time(),
+                    'get_detail' => json_encode([])  // 保留字段但不使用
+                ];
+                $chance = LotteryUserChance::create($data);
+            } else {
+                // 更新现有记录
+                $chance->total_chances += $times;
+                $chance->remain_chances += $times;
+                $chance->last_get_time = time();
+                $chance->save();
+            }
+            
+            // 创建获得记录
+            $recordData = [
+                'activity_id' => $activityId,
+                'user_id' => $userId,
+                'get_type' => static::getChanceGetTypeFromDetail($detail),
+                'chances' => $times,
+                'condition_id' => $detail['condition_id'] ?? null,
+                'condition_value' => $detail['condition_value'] ?? null,
+                'order_id' => $detail['order_id'] ?? null,
+                'recharge_amount' => $detail['recharge_amount'] ?? null,
+                'admin_id' => $detail['admin_id'] ?? null,
+                'reason' => $detail['reason'] ?? null,
+                'remark' => $detail['remark'] ?? null,
+                'get_time' => time()
+            ];
+            
+            $validation = LotteryUserChanceRecord::validateRecord($recordData);
+            if ($validation !== true) {
+                throw new Exception($validation);
+            }
+            
+            LotteryUserChanceRecord::create($recordData);
+            
+            Db::commit();
+            return $chance;
+            
+        } catch (Exception $e) {
+            Db::rollback();
+            throw $e;
+        }
+    }
+
+    /**
+     * 使用抽奖机会
+     * 
+     * @param LotteryUserChance $userChance 用户机会对象
+     * @param int $times 使用次数
+     * @return bool
+     */
+    public static function useChance(LotteryUserChance $userChance, $times = 1)
+    {
+        if ($userChance->remain_chances < $times) {
+            return false;
+        }
+        
+        $userChance->used_chances += $times;
+        $userChance->remain_chances -= $times;
+        $userChance->last_use_time = time();
+        
+        return $userChance->save();
+    }
+
+    /**
+     * 检查是否有剩余机会
+     * 
+     * @param LotteryUserChance $userChance 用户机会对象
+     * @param int $times 需要的机会次数
+     * @return bool
+     */
+    public static function hasChance(LotteryUserChance $userChance, $times = 1)
+    {
+        return $userChance->remain_chances >= $times;
+    }
+
+    /**
+     * 重置抽奖机会(用于测试或特殊情况)
+     * 
+     * @param LotteryUserChance $userChance 用户机会对象
+     * @return bool
+     */
+    public static function resetChance(LotteryUserChance $userChance)
+    {
+        $userChance->used_chances = 0;
+        $userChance->remain_chances = $userChance->total_chances;
+        return $userChance->save();
+    }
+
+    // ============ 统计和查询方法 ============
+
+    /**
+     * 获取活动总参与人数
+     * 
+     * @param int $activityId 活动ID
+     * @return int
+     */
+    public static function getActivityParticipants($activityId)
+    {
+        return LotteryUserChance::where('activity_id', $activityId)->count();
+    }
+
+    /**
+     * 获取用户在多个活动中的机会统计
+     * 
+     * @param int $userId 用户ID
+     * @param array $activityIds 活动ID数组
+     * @return array
+     */
+    public static function getUserChancesStats($userId, $activityIds = [])
+    {
+        $query = LotteryUserChance::where('user_id', $userId);
+        
+        if (!empty($activityIds)) {
+            $query->where('activity_id', 'in', $activityIds);
+        }
+        
+        return $query->field([
+                'activity_id',
+                'total_chances',
+                'used_chances', 
+                'remain_chances'
+            ])
+            ->select();
+    }
+
+    /**
+     * 获取用户在所有活动中的机会概览
+     * 
+     * @param int $userId 用户ID
+     * @return array
+     */
+    public static function getUserAllChancesOverview($userId)
+    {
+        $chances = LotteryUserChance::where('user_id', $userId)
+                                   ->with(['activity'])
+                                   ->select();
+
+        $overview = [
+            'total_activities' => count($chances),
+            'total_chances' => 0,
+            'total_used' => 0,
+            'total_remain' => 0,
+            'activities' => []
+        ];
+
+        foreach ($chances as $chance) {
+            $overview['total_chances'] += $chance->total_chances;
+            $overview['total_used'] += $chance->used_chances;
+            $overview['total_remain'] += $chance->remain_chances;
+
+            $overview['activities'][] = [
+                'activity_id' => $chance->activity_id,
+                'activity_name' => $chance->activity->name ?? '',
+                'total_chances' => $chance->total_chances,
+                'used_chances' => $chance->used_chances,
+                'remain_chances' => $chance->remain_chances,
+                'last_get_time' => $chance->last_get_time,
+                'last_use_time' => $chance->last_use_time
+            ];
+        }
+
+        return $overview;
+    }
+
+    /**
+     * 检查用户是否可以参与活动
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @return bool
+     */
+    public static function canUserParticipate($activityId, $userId)
+    {
+        $userChance = static::getUserChance($activityId, $userId);
+        return $userChance && static::hasChance($userChance, 1);
+    }
+
+    /**
+     * 获取活动参与用户列表
+     * 
+     * @param int $activityId 活动ID
+     * @param int $page 页码
+     * @param int $limit 每页数量
+     * @return array
+     */
+    public static function getActivityParticipantsList($activityId, $page = 1, $limit = 20)
+    {
+        return LotteryUserChance::where('activity_id', $activityId)
+                                ->with(['user'])
+                                ->order('createtime', 'desc')
+                                ->page($page, $limit)
+                                ->select();
+    }
+
+    /**
+     * 获取活动参与统计
+     * 
+     * @param int $activityId 活动ID
+     * @return array
+     */
+    public static function getActivityParticipationStats($activityId)
+    {
+        $stats = LotteryUserChance::where('activity_id', $activityId)
+                                  ->field([
+                                      'COUNT(*) as total_participants',
+                                      'SUM(total_chances) as total_chances_granted',
+                                      'SUM(used_chances) as total_chances_used',
+                                      'SUM(remain_chances) as total_chances_remain'
+                                  ])
+                                  ->find();
+
+        return [
+            'total_participants' => $stats['total_participants'] ?? 0,
+            'total_chances_granted' => $stats['total_chances_granted'] ?? 0,
+            'total_chances_used' => $stats['total_chances_used'] ?? 0,
+            'total_chances_remain' => $stats['total_chances_remain'] ?? 0,
+            'usage_rate' => $stats['total_chances_granted'] > 0 ? 
+                           round(($stats['total_chances_used'] / $stats['total_chances_granted']) * 100, 2) : 0
+        ];
+    }
+
+    // ============ 批量操作方法 ============
+
+    /**
+     * 批量创建用户机会(用于活动启动时)
+     * 
+     * @param int $activityId 活动ID
+     * @param array $userIds 用户ID数组
+     * @param int $times 机会次数
+     * @return bool
+     */
+    public static function batchCreateChances($activityId, $userIds, $times = 1)
+    {
+        if (empty($userIds)) {
+            return false;
+        }
+
+        $data = [];
+        $now = time();
+        
+        foreach ($userIds as $userId) {
+            $data[] = [
+                'activity_id' => $activityId,
+                'user_id' => $userId,
+                'total_chances' => $times,
+                'used_chances' => 0,
+                'remain_chances' => $times,
+                'last_get_time' => $now,
+                'createtime' => $now,
+                'updatetime' => $now
+            ];
+        }
+        
+        return LotteryUserChance::insertAll($data);
+    }
+
+    /**
+     * 批量检查用户资格
+     * 
+     * @param int $activityId 活动ID
+     * @param array $userIds 用户ID数组
+     * @return array 符合条件的用户ID数组
+     */
+    public static function batchCheckUserQualification($activityId, $userIds)
+    {
+        $activity = LotteryActivity::find($activityId);
+        if (!$activity) {
+            return [];
+        }
+
+        $qualifiedUsers = [];
+        
+        foreach ($userIds as $userId) {
+            if (static::checkUserQualification($activity, $userId)) {
+                $qualifiedUsers[] = $userId;
+            }
+        }
+
+        return $qualifiedUsers;
+    }
+
+    /**
+     * 批量验证条件
+     * 
+     * @param int $activityId 活动ID
+     * @param array $orderInfo 订单信息
+     * @param array $userIds 用户ID数组
+     * @return array 每个用户满足的条件
+     */
+    public static function batchValidateConditionsForUsers($activityId, $orderInfo, $userIds)
+    {
+        $results = [];
+        
+        foreach ($userIds as $userId) {
+            $results[$userId] = static::batchValidateConditions($activityId, $orderInfo, $userId);
+        }
+        
+        return $results;
+    }
+
+    // ============ 机会获取记录管理方法 ============
+
+    /**
+     * 获取用户机会获取记录
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @param int $page 页码
+     * @param int $limit 每页数量
+     * @return array
+     */
+    public static function getUserChanceRecords($activityId, $userId, $page = 1, $limit = 20)
+    {
+        return LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId, $page, $limit);
+    }
+
+    /**
+     * 获取活动机会获取统计
+     * 
+     * @param int $activityId 活动ID
+     * @return array
+     */
+    public static function getActivityChanceRecordStats($activityId)
+    {
+        return LotteryUserChanceRecord::getActivityChanceStats($activityId);
+    }
+
+    /**
+     * 获取用户机会获取统计
+     * 
+     * @param int $userId 用户ID
+     * @param int $activityId 活动ID(可选)
+     * @return array
+     */
+    public static function getUserChanceRecordStats($userId, $activityId = null)
+    {
+        return LotteryUserChanceRecord::getUserChanceStats($userId, $activityId);
+    }
+
+    /**
+     * 批量创建机会获取记录
+     * 
+     * @param array $records 记录数组
+     * @return bool
+     */
+    public static function batchCreateChanceRecords($records)
+    {
+        return LotteryUserChanceRecord::batchCreateRecords($records);
+    }
+
+    /**
+     * 验证机会获取记录数据
+     * 
+     * @param array $data 记录数据
+     * @return bool|string
+     */
+    public static function validateChanceRecord($data)
+    {
+        return LotteryUserChanceRecord::validateRecord($data);
+    }
+} 

+ 490 - 0
application/common/Service/Lottery/LotteryRecordService.php

@@ -0,0 +1,490 @@
+<?php
+
+namespace app\common\Service\Lottery;
+
+use app\common\model\lottery\LotteryDrawRecord;
+use app\common\model\lottery\LotteryWinRecord;
+use app\common\model\lottery\LotteryPrize;
+use app\common\Enum\LotteryEnum;
+use think\Exception;
+
+/**
+ * 抽奖记录服务类
+ * 专门处理抽奖记录和中奖记录的业务逻辑
+ */
+class LotteryRecordService
+{
+    // ============ 抽奖记录相关方法 ============
+
+    /**
+     * 创建抽奖记录
+     */
+    public static function createDrawRecord($activityId, $userId, $prizeId, $isWin, $triggerType, $triggerOrderId = null, $triggerAmount = null, $winInfo = [])
+    {
+        $data = [
+            'activity_id' => $activityId,
+            'user_id' => $userId,
+            'prize_id' => $prizeId,
+            'is_win' => $isWin ? 1 : 0,
+            'trigger_type' => $triggerType,
+            'trigger_order_id' => $triggerOrderId,
+            'trigger_amount' => $triggerAmount,
+            'win_info' => $winInfo ? json_encode($winInfo) : '',
+            'draw_ip' => request()->ip(),
+            'draw_time' => time(),
+            'device_info' => request()->header('user-agent', '')
+        ];
+        
+        return LotteryDrawRecord::create($data);
+    }
+
+    /**
+     * 获取用户抽奖次数
+     */
+    public static function getUserDrawCount($activityId, $userId, $timeRange = null)
+    {
+        $query = LotteryDrawRecord::where('activity_id', $activityId)
+                                  ->where('user_id', $userId);
+        
+        if ($timeRange) {
+            if (isset($timeRange['start'])) {
+                $query->where('draw_time', '>=', $timeRange['start']);
+            }
+            if (isset($timeRange['end'])) {
+                $query->where('draw_time', '<=', $timeRange['end']);
+            }
+        }
+        
+        return $query->count();
+    }
+
+    /**
+     * 获取用户中奖次数
+     */
+    public static function getUserWinCount($activityId, $userId, $timeRange = null)
+    {
+        $query = LotteryDrawRecord::where('activity_id', $activityId)
+                                  ->where('user_id', $userId)
+                                  ->where('is_win', 1);
+        
+        if ($timeRange) {
+            if (isset($timeRange['start'])) {
+                $query->where('draw_time', '>=', $timeRange['start']);
+            }
+            if (isset($timeRange['end'])) {
+                $query->where('draw_time', '<=', $timeRange['end']);
+            }
+        }
+        
+        return $query->count();
+    }
+
+    /**
+     * 获取活动抽奖统计
+     */
+    public static function getActivityDrawStats($activityId, $timeRange = null)
+    {
+        $query = LotteryDrawRecord::where('activity_id', $activityId);
+        
+        if ($timeRange) {
+            if (isset($timeRange['start'])) {
+                $query->where('draw_time', '>=', $timeRange['start']);
+            }
+            if (isset($timeRange['end'])) {
+                $query->where('draw_time', '<=', $timeRange['end']);
+            }
+        }
+        
+        return [
+            'total_draw' => $query->count(),
+            'total_win' => $query->where('is_win', 1)->count(),
+            'total_people' => $query->distinct('user_id')->count()
+        ];
+    }
+
+    /**
+     * 获取用户抽奖记录列表
+     */
+    public static function getUserDrawRecords($userId, $activityId = null, $page = 1, $limit = 20)
+    {
+        $query = LotteryDrawRecord::where('user_id', $userId);
+        
+        if ($activityId) {
+            $query->where('activity_id', $activityId);
+        }
+        
+        return $query->with(['activity', 'prize', 'winRecord'])
+                     ->order('draw_time', 'desc')
+                     ->page($page, $limit)
+                     ->select();
+    }
+
+    /**
+     * 获取活动抽奖记录列表
+     */
+    public static function getActivityDrawRecords($activityId, $page = 1, $limit = 20, $filters = [])
+    {
+        $query = LotteryDrawRecord::where('activity_id', $activityId);
+        
+        // 应用过滤器
+        if (isset($filters['is_win'])) {
+            $query->where('is_win', $filters['is_win']);
+        }
+        
+        if (isset($filters['trigger_type'])) {
+            $query->where('trigger_type', $filters['trigger_type']);
+        }
+        
+        if (isset($filters['start_time'])) {
+            $query->where('draw_time', '>=', $filters['start_time']);
+        }
+        
+        if (isset($filters['end_time'])) {
+            $query->where('draw_time', '<=', $filters['end_time']);
+        }
+        
+        return $query->with(['user', 'prize', 'winRecord'])
+                     ->order('draw_time', 'desc')
+                     ->page($page, $limit)
+                     ->select();
+    }
+
+    /**
+     * 获取抽奖记录详情
+     */
+    public static function getDrawRecordDetail($drawRecordId)
+    {
+        return LotteryDrawRecord::where('id', $drawRecordId)
+                                ->with(['activity', 'user', 'prize', 'winRecord', 'order'])
+                                ->find();
+    }
+
+    /**
+     * 检查用户是否已为指定订单抽奖
+     */
+    public static function hasUserDrawnForOrder($activityId, $userId, $orderId)
+    {
+        return LotteryDrawRecord::where('activity_id', $activityId)
+                                ->where('user_id', $userId)
+                                ->where('trigger_order_id', $orderId)
+                                ->count() > 0;
+    }
+
+    /**
+     * 获取用户抽奖历史统计
+     */
+    public static function getUserDrawHistoryStats($userId)
+    {
+        $stats = LotteryDrawRecord::where('user_id', $userId)
+                                  ->field([
+                                      'COUNT(*) as total_draws',
+                                      'SUM(is_win) as total_wins',
+                                      'COUNT(DISTINCT activity_id) as total_activities'
+                                  ])
+                                  ->find();
+
+        return [
+            'total_draws' => $stats['total_draws'] ?? 0,
+            'total_wins' => $stats['total_wins'] ?? 0,
+            'total_activities' => $stats['total_activities'] ?? 0,
+            'win_rate' => $stats['total_draws'] > 0 ? 
+                         round(($stats['total_wins'] / $stats['total_draws']) * 100, 2) : 0
+        ];
+    }
+
+    /**
+     * 获取活动抽奖排行榜
+     */
+    public static function getActivityDrawRanking($activityId, $limit = 50)
+    {
+        return LotteryDrawRecord::where('activity_id', $activityId)
+                                ->field('user_id, COUNT(*) as draw_count, SUM(is_win) as win_count')
+                                ->with('user')
+                                ->group('user_id')
+                                ->order('draw_count desc, win_count desc')
+                                ->limit($limit)
+                                ->select();
+    }
+
+    /**
+     * 获取活动抽奖时间分布统计
+     */
+    public static function getActivityDrawTimeDistribution($activityId, $dateRange = null)
+    {
+        $query = LotteryDrawRecord::where('activity_id', $activityId);
+        
+        if ($dateRange) {
+            if (isset($dateRange['start'])) {
+                $query->where('draw_time', '>=', $dateRange['start']);
+            }
+            if (isset($dateRange['end'])) {
+                $query->where('draw_time', '<=', $dateRange['end']);
+            }
+        }
+        
+        return $query->field('DATE(FROM_UNIXTIME(draw_time)) as date, COUNT(*) as draw_count, SUM(is_win) as win_count')
+                     ->group('date')
+                     ->order('date asc')
+                     ->select();
+    }
+
+    // ============ 中奖记录相关方法 ============
+
+    /**
+     * 创建中奖记录
+     */
+    public static function createWinRecord($drawRecordId, $activityId, $userId, $prizeId, $prizeName, $prizeType, $prizeValue = [])
+    {
+        $data = [
+            'draw_record_id' => $drawRecordId,
+            'activity_id' => $activityId,
+            'user_id' => $userId,
+            'prize_id' => $prizeId,
+            'prize_name' => $prizeName,
+            'prize_type' => $prizeType,
+            'prize_value' => is_array($prizeValue) ? json_encode($prizeValue) : $prizeValue,
+            'deliver_status' => LotteryEnum::DELIVER_STATUS_PENDING
+        ];
+        
+        return LotteryWinRecord::create($data);
+    }
+
+    /**
+     * 标记中奖记录为发放成功
+     */
+    public static function markWinRecordAsDelivered(LotteryWinRecord $winRecord, $deliverInfo = [])
+    {
+        $winRecord->deliver_status = LotteryEnum::DELIVER_STATUS_SUCCESS;
+        $winRecord->deliver_time = time();
+        if ($deliverInfo) {
+            $winRecord->deliver_info = json_encode($deliverInfo);
+        }
+        return $winRecord->save();
+    }
+
+    /**
+     * 标记中奖记录为发放失败
+     */
+    public static function markWinRecordAsFailed(LotteryWinRecord $winRecord, $reason = '')
+    {
+        $winRecord->deliver_status = LotteryEnum::DELIVER_STATUS_FAILED;
+        $winRecord->fail_reason = $reason;
+        return $winRecord->save();
+    }
+
+    /**
+     * 设置中奖记录收货地址
+     */
+    public static function setWinRecordDeliveryAddress(LotteryWinRecord $winRecord, $name, $mobile, $address)
+    {
+        $winRecord->receiver_name = $name;
+        $winRecord->receiver_mobile = $mobile;
+        $winRecord->receiver_address = $address;
+        return $winRecord->save();
+    }
+
+    /**
+     * 设置中奖记录快递信息
+     */
+    public static function setWinRecordExpressInfo(LotteryWinRecord $winRecord, $company, $number)
+    {
+        $winRecord->express_company = $company;
+        $winRecord->express_number = $number;
+        return $winRecord->save();
+    }
+
+    /**
+     * 设置中奖记录兑换码
+     */
+    public static function setWinRecordExchangeCode(LotteryWinRecord $winRecord, $code)
+    {
+        $winRecord->exchange_code = $code;
+        return $winRecord->save();
+    }
+
+    /**
+     * 标记中奖记录兑换码已使用
+     */
+    public static function markWinRecordCodeUsed(LotteryWinRecord $winRecord)
+    {
+        $winRecord->code_used_time = time();
+        return $winRecord->save();
+    }
+
+    /**
+     * 获取待发放的中奖记录
+     */
+    public static function getPendingWinRecords($limit = 100)
+    {
+        return LotteryWinRecord::where('deliver_status', LotteryEnum::DELIVER_STATUS_PENDING)
+                               ->order('createtime', 'asc')
+                               ->limit($limit)
+                               ->select();
+    }
+
+    /**
+     * 获取用户中奖记录
+     */
+    public static function getUserWinRecords($userId, $page = 1, $limit = 20)
+    {
+        return LotteryWinRecord::where('user_id', $userId)
+                               ->with(['activity', 'prize'])
+                               ->order('createtime', 'desc')
+                               ->page($page, $limit)
+                               ->select();
+    }
+
+    /**
+     * 获取活动中奖统计
+     */
+    public static function getActivityWinStats($activityId)
+    {
+        return [
+            'total_win' => LotteryWinRecord::where('activity_id', $activityId)->count(),
+            'delivered' => LotteryWinRecord::where('activity_id', $activityId)
+                                           ->where('deliver_status', LotteryEnum::DELIVER_STATUS_SUCCESS)
+                                           ->count(),
+            'pending' => LotteryWinRecord::where('activity_id', $activityId)
+                                         ->where('deliver_status', LotteryEnum::DELIVER_STATUS_PENDING)
+                                         ->count(),
+            'failed' => LotteryWinRecord::where('activity_id', $activityId)
+                                        ->where('deliver_status', LotteryEnum::DELIVER_STATUS_FAILED)
+                                        ->count()
+        ];
+    }
+
+    /**
+     * 批量处理待发放的中奖记录
+     */
+    public static function batchProcessPendingWinRecords($limit = 50)
+    {
+        $pendingRecords = static::getPendingWinRecords($limit);
+        $processedCount = 0;
+
+        foreach ($pendingRecords as $winRecord) {
+            try {
+                $prize = LotteryPrize::find($winRecord->prize_id);
+                if ($prize && $prize->deliver_type == LotteryEnum::DELIVER_TYPE_AUTO) {
+                    static::autoDeliverPrize($winRecord, $prize);
+                    $processedCount++;
+                }
+            } catch (Exception $e) {
+                static::markWinRecordAsFailed($winRecord, $e->getMessage());
+                trace('自动发放奖品失败: ' . $e->getMessage(), 'error');
+            }
+        }
+
+        return $processedCount;
+    }
+
+    /**
+     * 手动发放奖品(管理员操作)
+     */
+    public static function manualDeliverPrize($winRecordId, $deliverInfo = [], $adminId = 0)
+    {
+        $winRecord = LotteryWinRecord::find($winRecordId);
+        if (!$winRecord) {
+            throw new Exception('中奖记录不存在');
+        }
+
+        if ($winRecord->deliver_status != LotteryEnum::DELIVER_STATUS_PENDING) {
+            throw new Exception('该记录已处理,无法重复发放');
+        }
+
+        $deliverInfo['admin_id'] = $adminId;
+        $deliverInfo['manual_deliver_time'] = time();
+
+        return static::markWinRecordAsDelivered($winRecord, $deliverInfo);
+    }
+
+    /**
+     * 取消中奖记录
+     */
+    public static function cancelWinRecord($winRecordId, $reason = '', $adminId = 0)
+    {
+        $winRecord = LotteryWinRecord::find($winRecordId);
+        if (!$winRecord) {
+            throw new Exception('中奖记录不存在');
+        }
+
+        $winRecord->deliver_status = LotteryEnum::DELIVER_STATUS_CANCELLED;
+        $winRecord->fail_reason = $reason;
+        $winRecord->cancel_time = time();
+        $winRecord->cancel_admin_id = $adminId;
+
+        return $winRecord->save();
+    }
+
+    /**
+     * 自动发放奖品
+     */
+    public static function autoDeliverPrize($winRecord, $prize)
+    {
+        try {
+            switch ($prize->type) {
+                case LotteryEnum::PRIZE_TYPE_COUPON:
+                    static::deliverCoupon($winRecord, $prize);
+                    break;
+                case LotteryEnum::PRIZE_TYPE_REDPACK:
+                    static::deliverRedPacket($winRecord, $prize);
+                    break;
+                case LotteryEnum::PRIZE_TYPE_CODE:
+                    static::deliverExchangeCode($winRecord, $prize);
+                    break;
+                case LotteryEnum::PRIZE_TYPE_SHOP_GOODS:
+                    static::deliverGoods($winRecord, $prize);
+                    break;
+            }
+        } catch (Exception $e) {
+            static::markWinRecordAsFailed($winRecord, $e->getMessage());
+        }
+    }
+
+    /**
+     * 发放优惠券
+     */
+    private static function deliverCoupon($winRecord, $prize)
+    {
+        // 这里调用优惠券发放接口
+        // 示例代码,需要根据实际优惠券系统实现
+        static::markWinRecordAsDelivered($winRecord, ['coupon_id' => $prize->coupon_id]);
+    }
+
+    /**
+     * 发放红包
+     */
+    private static function deliverRedPacket($winRecord, $prize)
+    {
+        // 这里调用红包发放接口
+        // 示例代码,需要根据实际红包系统实现
+        static::markWinRecordAsDelivered($winRecord, ['amount' => $prize->amount]);
+    }
+
+    /**
+     * 发放兑换码
+     */
+    private static function deliverExchangeCode($winRecord, $prize)
+    {
+        $code = LotteryService::getAvailableExchangeCode($prize);
+        if ($code) {
+            static::setWinRecordExchangeCode($winRecord, $code);
+            LotteryService::markExchangeCodeUsed($prize, $code);
+            static::markWinRecordAsDelivered($winRecord, ['exchange_code' => $code]);
+        } else {
+            throw new Exception('兑换码已用完');
+        }
+    }
+
+    /**
+     * 发放商城商品
+     */
+    private static function deliverGoods($winRecord, $prize)
+    {
+        // 这里可以自动加入购物车或创建订单
+        // 示例代码,需要根据实际商城系统实现
+        static::markWinRecordAsDelivered($winRecord, [
+            'goods_id' => $prize->goods_id,
+            'goods_sku_id' => $prize->goods_sku_id
+        ]);
+    }
+} 

+ 640 - 0
application/common/Service/Lottery/LotteryService.php

@@ -0,0 +1,640 @@
+<?php
+
+namespace app\common\Service\Lottery;
+
+use app\common\model\lottery\LotteryActivity;
+use app\common\model\lottery\LotteryPrize;
+use app\common\Enum\LotteryEnum;
+use app\common\exception\BusinessException;
+use app\common\Enum\ErrorCodeEnum;
+use think\Db;
+use think\Exception;
+use think\Cache;
+
+/**
+ * 抽奖服务类
+ * 核心抽奖逻辑处理
+ */
+class LotteryService
+{
+    /**
+     * 执行抽奖
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @param int $triggerType 触发类型
+     * @param int $triggerOrderId 触发订单ID
+     * @param float $triggerAmount 触发金额
+     * @return array 抽奖结果
+     * @throws Exception
+     */
+    public static function drawLottery($activityId, $userId, $triggerType = 1, $triggerOrderId = null, $triggerAmount = null)
+    {
+        // 1. 验证活动有效性
+        $activity = LotteryActivity::find($activityId);
+        if (!$activity || !self::isActivityRunning($activity)) {
+            throw new BusinessException('活动不存在或未开始', ErrorCodeEnum::LOTTERY_ACTIVITY_NOT_FOUND);
+        }
+        // 2. 验证抽奖时间
+        if (!self::isActivityRunning($activity)) {
+            throw new BusinessException('不在抽奖时间内', ErrorCodeEnum::LOTTERY_NOT_IN_TIME);
+        }
+
+        // 3. 验证用户资格
+        if (!static::validateUserQualification($activity, $userId)) {
+            throw new BusinessException('用户不符合参与条件', ErrorCodeEnum::LOTTERY_USER_NOT_QUALIFIED);
+        }
+
+        // 4. 检查用户抽奖机会
+        $userChance = LotteryChanceService::getUserChance($activityId, $userId);
+        if (!$userChance || !LotteryChanceService::hasChance($userChance)) {
+            throw new BusinessException('没有抽奖机会', ErrorCodeEnum::LOTTERY_NO_CHANCE);
+        }
+
+        // 5. 检查用户参与次数限制
+        if (!static::checkUserDrawLimit($activity, $userId)) {
+            throw new BusinessException('已达到参与次数上限', ErrorCodeEnum::LOTTERY_REACH_LIMIT);
+        }
+
+        // 6. 防重复抽奖检查(基于订单)
+        if ($triggerOrderId && static::hasDrawnForOrder($activityId, $userId, $triggerOrderId)) {
+            throw new BusinessException('该订单已参与过抽奖', ErrorCodeEnum::LOTTERY_ORDER_ALREADY_DRAWN);
+        }
+
+        // 7. 使用Redis锁防止并发
+        $lockKey = "lottery_lock_{$activityId}_{$userId}";
+        $lock = Cache::store('redis')->handler()->set($lockKey, 1, 'NX', 'EX', 10);
+        if (!$lock) {
+            throw new Exception('操作太频繁,请稍后再试');
+        }
+
+        try {
+            // 8. 开始抽奖流程
+            return static::processDrawLottery($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount);
+        } finally {
+            // 释放锁
+            Cache::store('redis')->handler()->del($lockKey);
+        }
+    }
+
+    /**
+     * 处理抽奖核心逻辑
+     */
+    private static function processDrawLottery($activity, $userId, $triggerType, $triggerOrderId, $triggerAmount)
+    {
+        // 开启事务
+        Db::startTrans();
+        
+        try {
+            // 1. 获取可用奖品
+            $prizes = static::getAvailablePrizes($activity);
+            if (empty($prizes)) {
+                throw new Exception('暂无可抽取的奖品');
+            }
+
+            // 2. 执行抽奖算法
+            $selectedPrize = static::executeLotteryAlgorithm($prizes);
+
+            // 3. 减少库存
+            if (!static::decreasePrizeStock($selectedPrize)) {
+                throw new Exception('奖品库存不足');
+            }
+
+            // 4. 消耗用户抽奖机会
+            $userChance = LotteryChanceService::getUserChance($activity->id, $userId);
+            if (!LotteryChanceService::useChance($userChance)) {
+                throw new Exception('抽奖机会使用失败');
+            }
+
+            // 5. 创建抽奖记录
+            $isWin = $selectedPrize->type != LotteryEnum::PRIZE_TYPE_NO_PRIZE;
+            $winInfo = $isWin ? static::buildWinInfo($selectedPrize) : [];
+            
+            $drawRecord = LotteryRecordService::createDrawRecord(
+                $activity->id,
+                $userId,
+                $selectedPrize->id,
+                $isWin,
+                $triggerType,
+                $triggerOrderId,
+                $triggerAmount,
+                $winInfo
+            );
+
+            // 6. 如果中奖,创建中奖记录
+            $winRecord = null;
+            if ($isWin) {
+                $winRecord = LotteryRecordService::createWinRecord(
+                    $drawRecord->id,
+                    $activity->id,
+                    $userId,
+                    $selectedPrize->id,
+                    $selectedPrize->name,
+                    $selectedPrize->type,
+                    static::buildPrizeValue($selectedPrize)
+                );
+
+                // 自动发放奖品
+                if ($selectedPrize->deliver_type == LotteryEnum::DELIVER_TYPE_AUTO) {
+                    LotteryRecordService::autoDeliverPrize($winRecord, $selectedPrize);
+                }
+            }
+
+            // 7. 更新活动统计
+            static::updateActivityStats($activity, $isWin);
+
+            // 提交事务
+            Db::commit();
+
+            // 8. 返回抽奖结果
+            return static::buildDrawResult($drawRecord, $selectedPrize, $winRecord);
+
+        } catch (Exception $e) {
+            Db::rollback();
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取可用奖品列表
+     */
+    private static function getAvailablePrizes($activity)
+    {
+        $prizes = static::getValidPrizes($activity->id);
+        
+        // 如果开启按人数解锁功能
+        if ($activity->unlock_by_people) {
+            $currentPeopleCount = LotteryChanceService::getActivityParticipants($activity->id);
+            $prizes = $prizes->filter(function($prize) use ($currentPeopleCount) {
+                return static::isPrizeUnlocked($prize, $currentPeopleCount);
+            });
+        }
+
+        return $prizes;
+    }
+
+    /**
+     * 执行抽奖算法(基于概率权重)
+     */
+    private static function executeLotteryAlgorithm($prizes)
+    {
+        // 计算总概率
+        $totalProbability = $prizes->sum('probability');
+        
+        // 生成随机数
+        $randomNumber = mt_rand(1, $totalProbability * 100) / 100;
+        
+        // 按概率选择奖品
+        $currentProbability = 0;
+        foreach ($prizes as $prize) {
+            $currentProbability += $prize->probability;
+            if ($randomNumber <= $currentProbability) {
+                return $prize;
+            }
+        }
+        
+        // 保底返回第一个奖品(通常是未中奖)
+        return $prizes->first();
+    }
+
+    /**
+     * 验证用户资格
+     */
+    private static function validateUserQualification($activity, $userId)
+    {
+        // 检查用户群体限制
+        switch ($activity->user_limit_type) {
+            case LotteryEnum::USER_LIMIT_ALL:
+                return true;
+            case LotteryEnum::USER_LIMIT_LEVEL:
+                return static::checkUserLevel($userId, $activity->user_limit_value);
+            case LotteryEnum::USER_LIMIT_TAG:
+                return static::checkUserTag($userId, $activity->user_limit_value);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * 检查用户等级
+     */
+    private static function checkUserLevel($userId, $limitValue)
+    {
+        if (empty($limitValue)) {
+            return true;
+        }
+        
+        $user = \app\common\model\User::find($userId);
+        return $user && in_array($user->level, (array)$limitValue);
+    }
+
+    /**
+     * 检查用户标签
+     */
+    private static function checkUserTag($userId, $limitValue)
+    {
+        if (empty($limitValue)) {
+            return true;
+        }
+        
+        // 这里需要根据实际的用户标签系统实现
+        // 暂时返回true
+        return true;
+    }
+
+    /**
+     * 检查用户抽奖次数限制
+     */
+    private static function checkUserDrawLimit($activity, $userId)
+    {
+        if (!$activity->person_limit_num) {
+            return true;
+        }
+        
+        $drawCount = LotteryRecordService::getUserDrawCount($activity->id, $userId);
+        return $drawCount < $activity->person_limit_num;
+    }
+
+    /**
+     * 检查订单是否已抽奖
+     */
+    private static function hasDrawnForOrder($activityId, $userId, $orderId)
+    {
+        return LotteryRecordService::hasUserDrawnForOrder($activityId, $userId, $orderId);
+    }
+
+    /**
+     * 构建中奖信息
+     */
+    private static function buildWinInfo($prize)
+    {
+        return [
+            'prize_id' => $prize->id,
+            'prize_name' => $prize->name,
+            'prize_type' => $prize->type,
+            'prize_image' => $prize->image,
+            'win_prompt' => $prize->win_prompt
+        ];
+    }
+
+    /**
+     * 构建奖品价值信息
+     */
+    private static function buildPrizeValue($prize)
+    {
+        $prizeValue = [
+            'type' => $prize->type,
+            'name' => $prize->name,
+            'image' => $prize->image
+        ];
+
+        switch ($prize->type) {
+            case LotteryEnum::PRIZE_TYPE_COUPON:
+                $prizeValue['coupon_id'] = $prize->coupon_id;
+                break;
+            case LotteryEnum::PRIZE_TYPE_REDPACK:
+                $prizeValue['amount'] = $prize->amount;
+                break;
+            case LotteryEnum::PRIZE_TYPE_SHOP_GOODS:
+                $prizeValue['goods_id'] = $prize->goods_id;
+                $prizeValue['goods_sku_id'] = $prize->goods_sku_id;
+                break;
+            case LotteryEnum::PRIZE_TYPE_CODE:
+                $prizeValue['exchange_code'] = static::getAvailableExchangeCode($prize);
+                break;
+        }
+
+        return $prizeValue;
+    }
+
+    /**
+     * 自动发放奖品
+     */
+    private static function autoDeliverPrize($winRecord, $prize)
+    {
+        try {
+            switch ($prize->type) {
+                case LotteryEnum::PRIZE_TYPE_COUPON:
+                    static::deliverCoupon($winRecord, $prize);
+                    break;
+                case LotteryEnum::PRIZE_TYPE_REDPACK:
+                    static::deliverRedPacket($winRecord, $prize);
+                    break;
+                case LotteryEnum::PRIZE_TYPE_CODE:
+                    static::deliverExchangeCode($winRecord, $prize);
+                    break;
+                case LotteryEnum::PRIZE_TYPE_SHOP_GOODS:
+                    static::deliverGoods($winRecord, $prize);
+                    break;
+            }
+        } catch (Exception $e) {
+            LotteryRecordService::markWinRecordAsFailed($winRecord, $e->getMessage());
+        }
+    }
+
+    /**
+     * 发放优惠券
+     */
+    private static function deliverCoupon($winRecord, $prize)
+    {
+        // 这里调用优惠券发放接口
+        // 示例代码,需要根据实际优惠券系统实现
+        LotteryRecordService::markWinRecordAsDelivered($winRecord, ['coupon_id' => $prize->coupon_id]);
+    }
+
+    /**
+     * 发放红包
+     */
+    private static function deliverRedPacket($winRecord, $prize)
+    {
+        // 这里调用红包发放接口
+        // 示例代码,需要根据实际红包系统实现
+        LotteryRecordService::markWinRecordAsDelivered($winRecord, ['amount' => $prize->amount]);
+    }
+
+    /**
+     * 发放兑换码
+     */
+    private static function deliverExchangeCode($winRecord, $prize)
+    {
+        $code = static::getAvailableExchangeCode($prize);
+        if ($code) {
+            LotteryRecordService::setWinRecordExchangeCode($winRecord, $code);
+            static::markExchangeCodeUsed($prize, $code);
+            LotteryRecordService::markWinRecordAsDelivered($winRecord, ['exchange_code' => $code]);
+        } else {
+            throw new Exception('兑换码已用完');
+        }
+    }
+
+    /**
+     * 发放商城商品
+     */
+    private static function deliverGoods($winRecord, $prize)
+    {
+        // 这里可以自动加入购物车或创建订单
+        // 示例代码,需要根据实际商城系统实现
+        LotteryRecordService::markWinRecordAsDelivered($winRecord, [
+            'goods_id' => $prize->goods_id,
+            'goods_sku_id' => $prize->goods_sku_id
+        ]);
+    }
+
+    /**
+     * 更新活动统计
+     */
+    private static function updateActivityStats($activity, $isWin)
+    {
+        $activity->total_draw_count += 1;
+        if ($isWin) {
+            $activity->total_win_count += 1;
+        }
+        $activity->save();
+    }
+
+    /**
+     * 构建抽奖结果
+     */
+    private static function buildDrawResult($drawRecord, $prize, $winRecord = null)
+    {
+        $result = [
+            'draw_id' => $drawRecord->id,
+            'is_win' => $drawRecord->is_win,
+            'prize' => [
+                'id' => $prize->id,
+                'name' => $prize->name,
+                'type' => $prize->type,
+                'type_text' => $prize->type_text,
+                'image' => $prize->image,
+                'win_prompt' => $prize->win_prompt
+            ],
+            'draw_time' => $drawRecord->draw_time
+        ];
+
+        if ($winRecord) {
+            $result['win_record_id'] = $winRecord->id;
+            $result['deliver_status'] = $winRecord->deliver_status;
+            $result['exchange_code'] = $winRecord->exchange_code;
+        }
+
+        return $result;
+    }
+
+    /**
+     * 获取用户抽奖机会
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @return array
+     */
+    public static function getUserChances($activityId, $userId)
+    {
+        $userChance = LotteryChanceService::getUserChance($activityId, $userId);
+        
+        if (!$userChance) {
+            return [
+                'total_chances' => 0,
+                'used_chances' => 0,
+                'remain_chances' => 0
+            ];
+        }
+
+        return [
+            'total_chances' => $userChance->total_chances,
+            'used_chances' => $userChance->used_chances,
+            'remain_chances' => $userChance->remain_chances,
+            'last_get_time' => $userChance->last_get_time,
+            'last_use_time' => $userChance->last_use_time
+        ];
+    }
+
+    // ============ 从LotteryPrize模型移过来的业务逻辑方法 ============
+
+    /**
+     * 检查奖品库存是否充足
+     */
+    public static function hasPrizeStock(LotteryPrize $prize, $quantity = 1)
+    {
+        return $prize->remain_stock >= $quantity;
+    }
+
+    /**
+     * 减少奖品库存
+     */
+    public static function decreasePrizeStock(LotteryPrize $prize, $quantity = 1)
+    {
+        if (!static::hasPrizeStock($prize, $quantity)) {
+            return false;
+        }
+        
+        $prize->remain_stock -= $quantity;
+        $prize->win_count += $quantity;
+        return $prize->save();
+    }
+
+    /**
+     * 获取可用的兑换码
+     */
+    public static function getAvailableExchangeCode(LotteryPrize $prize)
+    {
+        if ($prize->type != LotteryEnum::PRIZE_TYPE_CODE) {
+            return null;
+        }
+        
+        $allCodes = $prize->exchange_codes_list;
+        $usedCodes = $prize->used_codes_list;
+        
+        $availableCodes = array_diff($allCodes, $usedCodes);
+        
+        if (empty($availableCodes)) {
+            return null;
+        }
+        
+        return array_shift($availableCodes);
+    }
+
+    /**
+     * 标记兑换码为已使用
+     */
+    public static function markExchangeCodeUsed(LotteryPrize $prize, $code)
+    {
+        $usedCodes = $prize->used_codes_list;
+        if (!in_array($code, $usedCodes)) {
+            $usedCodes[] = $code;
+            $prize->used_codes = json_encode($usedCodes);
+            return $prize->save();
+        }
+        return true;
+    }
+
+    /**
+     * 获取有效奖品(库存大于0且状态正常)
+     */
+    public static function getValidPrizes($activityId)
+    {
+        return LotteryPrize::where('activity_id', $activityId)
+                           ->where('status', 1)
+                           ->where('remain_stock', '>', 0)
+                           ->order('sort_order', 'asc')
+                           ->select();
+    }
+
+    /**
+     * 检查奖品是否已解锁(按人数解锁功能)
+     */
+    public static function isPrizeUnlocked(LotteryPrize $prize, $currentPeopleCount)
+    {
+        if (empty($prize->unlock_people_num)) {
+            return true;
+        }
+        
+        return $currentPeopleCount >= $prize->unlock_people_num;
+    }
+
+    /**
+     * 检查活动是否正在进行
+     */
+    public static function isActivityRunning(LotteryActivity $activity)
+    {
+        $now = time();
+        return $activity->status == LotteryEnum::STATUS_ONGOING 
+               && $activity->start_time <= $now 
+               && $activity->end_time >= $now;
+    }
+
+    /**
+     * 检查活动是否已结束
+     */
+    public static function isActivityEnded(LotteryActivity $activity)
+    {
+        $now = time();
+        return $activity->status == LotteryEnum::STATUS_ENDED 
+               || $activity->end_time < $now;
+    }
+
+    /**
+     * 检查活动是否未开始
+     */
+    public static function isActivityNotStarted(LotteryActivity $activity)
+    {
+        $now = time();
+        return $activity->status == LotteryEnum::STATUS_NOT_STARTED 
+               && $activity->start_time > $now;
+    }
+
+    /**
+     * 检查活动是否已暂停
+     */
+    public static function isActivitySuspended(LotteryActivity $activity)
+    {
+        return $activity->status == LotteryEnum::STATUS_SUSPENDED;
+    }
+
+    /**
+     * 检查活动是否已取消
+     */
+    public static function isActivityCancelled(LotteryActivity $activity)
+    {
+        return $activity->status == LotteryEnum::STATUS_CANCELLED;
+    }
+    /**
+     * 获取正在进行的活动
+     */
+    public static function getRunningActivities()
+    {
+        $now = time();
+        return LotteryActivity::where('status', LotteryEnum::STATUS_ONGOING)
+                             ->where('start_time', '<=', $now)
+                             ->where('end_time', '>=', $now)
+                             ->select();
+    }
+
+    /**
+     * 获取未开始的活动
+     */
+    public static function getNotStartedActivities()
+    {
+        $now = time();
+        return LotteryActivity::where('status', LotteryEnum::STATUS_NOT_STARTED)
+                             ->where('start_time', '>', $now)
+                             ->select();
+    }
+
+    /**
+     * 获取已结束的活动
+     */
+    public static function getEndedActivities()
+    {
+        $now = time();
+        return LotteryActivity::where('status', LotteryEnum::STATUS_ENDED)
+                             ->orWhere('end_time', '<', $now)
+                             ->select();
+    }
+
+    /**
+     * 获取可显示的活动(排除逻辑状态)
+     */
+    public static function getDisplayableActivities()
+    {
+        $displayableStatuses = array_keys(LotteryEnum::getActivityStatusMap());
+        return LotteryActivity::whereIn('status', $displayableStatuses)->select();
+    }
+
+    /**
+     * 验证活动状态是否有效
+     */
+    public static function isValidActivityStatus(LotteryActivity $activity)
+    {
+        return LotteryEnum::isValidActivityStatus($activity->status);
+    }
+
+    /**
+     * 验证开奖方式是否有效
+     */
+    public static function isValidLotteryType(LotteryActivity $activity)
+    {
+        return LotteryEnum::isValidLotteryType($activity->lottery_type);
+    }
+
+
+
+
+} 

+ 114 - 0
application/common/Service/Order/OrderGoodsService.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace app\common\Service\Order;
+
+use app\common\model\OrderGoods as OrderGoodsModel;
+use think\Db;
+
+class OrderGoodsService
+{
+
+//销量增
+    public static function setGoodsSalesInc($order_sn)
+    {
+        $list = (new OrderGoodsModel())->where('order_sn', $order_sn)->select();
+        // 启动事务
+        Db::startTrans();
+        try {
+            foreach ($list as $item) {
+                $goods = $item->goods;
+                $sku = $item->sku;
+                if ($goods) {
+                    $goods->setInc('sales', $item->nums);
+                }
+                if ($sku) {
+                    $sku->setInc('sales', $item->nums);
+                }
+            }
+            // 提交事务
+            Db::commit();
+        } catch (\Exception $e) {
+            // 回滚事务
+            Db::rollback();
+        }
+        return true;
+    }
+
+    //销量减
+    public static function setGoodsSalesDec($order_sn)
+    {
+        $list = (new OrderGoodsModel())->where('order_sn', $order_sn)->select();
+        // 启动事务
+        Db::startTrans();
+        try {
+            foreach ($list as $item) {
+                $goods = $item->goods;
+                $sku = $item->sku;
+                if ($goods) {
+                    $goods->setDec('sales', $item->nums);
+                }
+                if ($sku) {
+                    $sku->setDec('sales', $item->nums);
+                }
+            }
+            // 提交事务
+            Db::commit();
+        } catch (\Exception $e) {
+            // 回滚事务
+            Db::rollback();
+        }
+        return true;
+    }
+
+    //库存增
+    public static function setGoodsStocksInc($order_sn)
+    {
+        $list = (new OrderGoodsModel())->where('order_sn', $order_sn)->select();
+        // 启动事务
+        Db::startTrans();
+        try {
+            foreach ($list as $item) {
+                $goods = $item->goods;
+                $sku = $item->sku;
+                if ($sku) {
+                    $sku->setInc('stocks', $item->nums);
+                }
+                if ($goods) {
+                    $goods->setInc('stocks', $item->nums);
+                }
+            }
+            // 提交事务
+            Db::commit();
+        } catch (\Exception $e) {
+            // 回滚事务
+            Db::rollback();
+        }
+        return true;
+    }
+
+    //库存减
+    public static function setGoodsStocksDec($order_sn)
+    {
+        $list = (new OrderGoodsModel())->where('order_sn', $order_sn)->select();
+        // 启动事务
+        Db::startTrans();
+        try {
+            foreach ($list as $item) {
+                $goods = $item->goods;
+                $sku = $item->sku;
+                if ($sku) {
+                    $sku->setDec('stocks', $item->nums);
+                }
+                if ($goods) {
+                    $goods->setDec('stocks', $item->nums);
+                }
+            }
+            // 提交事务
+            Db::commit();
+        } catch (\Exception $e) {
+            // 回滚事务
+            Db::rollback();
+        }
+        return true;
+    }
+}

+ 16 - 2
application/common/Service/OrderService.php

@@ -16,6 +16,7 @@ use app\common\model\Carts;
 use think\Db;
 use think\Exception;
 use app\common\model\OrderAddress;
+use app\common\Enum\OrderEnum;
 /**
  * 订单服务类
  * 封装订单创建相关逻辑
@@ -301,7 +302,7 @@ class OrderService
             'express_no'           => '', // 快递单号
             'express_fee'          => 0, // 配送费用
             'expire_time'          => time() + $config['order_timeout'], // 过期时间
-            'order_status'         => \app\common\Enum\OrderEnum::STATUS_CREATE, // 待付款
+            'order_status'         => OrderEnum::STATUS_CREATE, // 待付款
             'invoice_status'       => 0, // 发票开具状态
             'remark'               => $remark, // 用户备注
             'user_coupon_id'       => $userCouponId ?: null,
@@ -499,7 +500,7 @@ class OrderService
             $pageSize = $param['pageSize'];
         }
         return Order::with(['orderGoods'])
-            ->where(function ($query) use ($param) {
+            ->where(function ($query) use ($param,$userId,$status) {
 
                 if (!empty($userId)) {
                     $query->where('user_id', $userId);
@@ -581,6 +582,19 @@ class OrderService
         return Order::where('id', $orderId)->find();
     }
 
+    // 获取状态订单统计
+    public static function getOrderStatusCount($userId = 0)
+    {
+
+        $info = [];
+        $info['unpay'] = Order::where('user_id', $userId)->where('status',OrderEnum::STATUS_CREATE)->count();
+        $info['unsend'] = Order::where('user_id', $userId)->where('status',OrderEnum::STATUS_PAY)->count();
+        $info['unrec'] = Order::where('user_id', $userId)->where('status',OrderEnum::STATUS_SHIP)->count();
+        $info['uneva'] = Order::where('user_id', $userId)->where('status',OrderEnum::STATUS_CONFIRM)->count();
+       
+        return $info;
+    }
+
 
 
 } 

+ 5 - 8
application/common/Service/lottery/LotteryActivityService.php

@@ -1,6 +1,6 @@
 <?php
 
-namespace app\common\Service\lottery;
+namespace app\common\Service\Lottery;
 
 use app\common\model\lottery\LotteryActivity;
 use app\common\Enum\LotteryEnum;
@@ -59,16 +59,13 @@ class LotteryActivityService
 
     /**
      * 检查是否在抽奖时间内
+     * 注意:当前数据库表结构中没有draw_time相关字段,所以始终返回true
      */
     public static function isInDrawTime(LotteryActivity $activity)
     {
-        if (!$activity->draw_time_enable) {
-            return true;
-        }
-        
-        $currentTime = date('H:i');
-        return $currentTime >= $activity->draw_time_start 
-               && $currentTime <= $activity->draw_time_end;
+        // 当前表结构中没有draw_time_enable, draw_time_start, draw_time_end字段
+        // 所以直接返回true,表示始终可以抽奖
+        return true;
     }
 
     /**

+ 208 - 72
application/common/Service/lottery/LotteryChanceService.php

@@ -1,11 +1,11 @@
 <?php
 
-namespace app\common\Service\lottery;
+namespace app\common\Service\Lottery;
 
 use app\common\model\lottery\LotteryActivity;
 use app\common\model\lottery\LotteryCondition;
 use app\common\model\lottery\LotteryUserChance;
-use app\common\model\lottery\LotteryDrawRecord;
+use app\common\model\lottery\LotteryUserChanceRecord;
 use app\common\model\User;
 use app\common\model\Order;
 use app\common\Enum\LotteryEnum;
@@ -31,20 +31,7 @@ class LotteryChanceService
     
     /** @var int 最大批量处理数量 */
     const MAX_BATCH_SIZE = 1000;
-    
-    /** @var string 触发类型:订单 */
-    const TRIGGER_TYPE_ORDER = 'order';
-    
-    /** @var string 触发类型:充值 */
-    const TRIGGER_TYPE_RECHARGE = 'recharge';
-    
-    /** @var string 触发类型:累计消费 */
-    const TRIGGER_TYPE_ACCUMULATE = 'accumulate';
-    
-    /** @var string 触发类型:手动 */
-    const TRIGGER_TYPE_MANUAL = 'manual';
 
-    // ============ 主要业务方法 ============
 
     /**
      * 订单完成后检查并分发抽奖机会
@@ -135,8 +122,7 @@ class LotteryChanceService
     }
 
     /**
-     * 手动给用户增加抽奖机会(管理员操作)
-     * 
+     * 手动给用户增加抽奖机会(管理员操作)     * 
      * @param int $activityId 活动ID
      * @param int $userId 用户ID
      * @param int $chances 机会次数
@@ -162,7 +148,6 @@ class LotteryChanceService
         }
 
         $detail = [
-            'trigger_type' => static::TRIGGER_TYPE_MANUAL,
             'reason' => $reason,
             'admin_id' => $adminId,
             'granted_time' => time()
@@ -243,6 +228,7 @@ class LotteryChanceService
         // 获取活动的参与条件
         $conditions = static::getValidConditions($activity->id);
         $totalChances = 0;
+        $chanceDetails = [];
 
         foreach ($conditions as $condition) {
             // 跳过充值条件
@@ -250,18 +236,26 @@ class LotteryChanceService
                 continue;
             }
 
-            $chances = static::processConditionForOrder($condition, $orderInfo, $userId);
-            $totalChances += $chances;
+            $result = static::processConditionForOrder($condition, $orderInfo, $userId);
+            if ($result['chances'] > 0) {
+                $totalChances += $result['chances'];
+                $result['detail']['chances'] = $result['chances'];
+                $chanceDetails[] = $result['detail'];
+            }
         }
 
         // 如果获得了抽奖机会,记录到数据库
         if ($totalChances > 0) {
-            static::grantChanceToUser($activity->id, $userId, $totalChances, [
-                'trigger_type' => static::TRIGGER_TYPE_ORDER,
-                'order_id' => $orderInfo['id'] ?? 0,
-                'order_amount' => $orderInfo['total_amount'] ?? 0,
-                'granted_time' => time()
-            ]);
+            // 为每个条件分别记录
+            foreach ($chanceDetails as $detail) {
+                static::grantChanceToUser($activity->id, $userId, $detail['chances'] ?? 1, [
+                    'condition_id' => $detail['condition_id'],
+                    'condition_type' => $detail['condition_type'],
+                    'condition_value' => $detail['condition_value'],
+                    'order_id' => $orderInfo['id'] ?? 0,
+                    'granted_time' => time()
+                ]);
+            }
         }
 
         return $totalChances;
@@ -285,6 +279,7 @@ class LotteryChanceService
         // 获取活动的参与条件
         $conditions = static::getValidConditions($activity->id);
         $totalChances = 0;
+        $chanceDetails = [];
 
         foreach ($conditions as $condition) {
             // 只处理充值条件
@@ -292,17 +287,26 @@ class LotteryChanceService
                 continue;
             }
 
-            $chances = static::processConditionForRecharge($condition, $rechargeInfo, $userId);
-            $totalChances += $chances;
+            $result = static::processConditionForRecharge($condition, $rechargeInfo, $userId);
+            if ($result['chances'] > 0) {
+                $totalChances += $result['chances'];
+                $result['detail']['chances'] = $result['chances'];
+                $chanceDetails[] = $result['detail'];
+            }
         }
 
         // 如果获得了抽奖机会,记录到数据库
         if ($totalChances > 0) {
-            static::grantChanceToUser($activity->id, $userId, $totalChances, [
-                'trigger_type' => static::TRIGGER_TYPE_RECHARGE,
-                'recharge_amount' => $rechargeInfo['amount'] ?? 0,
-                'granted_time' => time()
-            ]);
+            // 为每个条件分别记录
+            foreach ($chanceDetails as $detail) {
+                static::grantChanceToUser($activity->id, $userId, $detail['chances'] ?? 1, [
+                    'condition_id' => $detail['condition_id'],
+                    'condition_type' => $detail['condition_type'],
+                    'condition_value' => $detail['condition_value'],
+                    'recharge_amount' => $rechargeInfo['amount'] ?? 0,
+                    'granted_time' => time()
+                ]);
+            }
         }
 
         return $totalChances;
@@ -314,11 +318,16 @@ class LotteryChanceService
      * @param LotteryCondition $condition 条件对象
      * @param array $orderInfo 订单信息
      * @param int $userId 用户ID
-     * @return int 获得的抽奖机会数量
+     * @return array 获得的抽奖机会信息 ['chances' => int, 'detail' => array]
      */
     private static function processConditionForOrder($condition, $orderInfo, $userId)
     {
         $chances = 0;
+        $detail = [
+            'condition_id' => $condition->id,
+            'condition_type' => $condition->type,
+            'condition_value' => $condition->condition_value,
+        ];
 
         switch ($condition->type) {
             case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
@@ -349,7 +358,10 @@ class LotteryChanceService
                 break;
         }
 
-        return $chances;
+        return [
+            'chances' => $chances,
+            'detail' => $detail
+        ];
     }
 
     /**
@@ -358,11 +370,16 @@ class LotteryChanceService
      * @param LotteryCondition $condition 条件对象
      * @param array $rechargeInfo 充值信息
      * @param int $userId 用户ID
-     * @return int 获得的抽奖机会数量
+     * @return array 获得的抽奖机会信息 ['chances' => int, 'detail' => array]
      */
     private static function processConditionForRecharge($condition, $rechargeInfo, $userId)
     {
         $chances = 0;
+        $detail = [
+            'condition_id' => $condition->id,
+            'condition_type' => $condition->type,
+            'condition_value' => $condition->condition_value,
+        ];
         
         if (static::validateRechargeCondition($condition, ['type' => 'recharge', 'amount' => $rechargeInfo['amount']])) {
             $chances = static::getRewardTimes($condition);
@@ -374,7 +391,10 @@ class LotteryChanceService
             }
         }
 
-        return $chances;
+        return [
+            'chances' => $chances,
+            'detail' => $detail
+        ];
     }
 
     // ============ 用户资格检查方法 ============
@@ -452,19 +472,12 @@ class LotteryChanceService
      */
     private static function hasGrantedForAccumulate($activityId, $userId)
     {
-        $userChance = static::getUserChance($activityId, $userId);
-        if (!$userChance) {
-            return false;
-        }
-        
-        $getDetail = $userChance->get_detail_data;
-        foreach ($getDetail as $detail) {
-            if (($detail['trigger_type'] ?? '') === static::TRIGGER_TYPE_ACCUMULATE) {
-                return true;
-            }
-        }
+        $record = LotteryUserChanceRecord::where('activity_id', $activityId)
+                                         ->where('user_id', $userId)
+                                         ->where('get_type', LotteryEnum::CHANCE_GET_TYPE_TOTAL_AMOUNT)
+                                         ->find();
         
-        return false;
+        return $record ? true : false;
     }
 
     // ============ 条件验证方法 ============
@@ -692,6 +705,37 @@ class LotteryChanceService
     }
 
     /**
+     * 根据详情确定机会获取类型
+     * 
+     * @param array $detail 获得详情
+     * @return int 获取类型
+     */
+    private static function getChanceGetTypeFromDetail($detail)
+    {
+        // 如果有条件类型,直接使用条件类型对应的获取类型
+        if (isset($detail['condition_type'])) {
+            switch ($detail['condition_type']) {
+                case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
+                    return LotteryEnum::CHANCE_GET_TYPE_BUY_GOODS;
+                case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
+                    return LotteryEnum::CHANCE_GET_TYPE_ORDER_AMOUNT;
+                case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
+                    return LotteryEnum::CHANCE_GET_TYPE_RECHARGE;
+                case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
+                    return LotteryEnum::CHANCE_GET_TYPE_TOTAL_AMOUNT;
+            }
+        }
+        
+        // 如果没有条件类型,检查是否是管理员赠送
+        if (isset($detail['admin_id']) && $detail['admin_id'] > 0) {
+            return LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT;
+        }
+        
+        // 默认返回管理员赠送
+        return LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT;
+    }
+
+    /**
      * 获取用户在指定活动中的抽奖机会详情
      * 
      * @param int $activityId 活动ID
@@ -707,17 +751,20 @@ class LotteryChanceService
                 'total_chances' => 0,
                 'used_chances' => 0,
                 'remain_chances' => 0,
-                'get_detail' => []
+                'get_records' => []
             ];
         }
 
+        // 获取机会获得记录
+        $getRecords = LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId);
+
         return [
             'total_chances' => $userChance->total_chances,
             'used_chances' => $userChance->used_chances,
             'remain_chances' => $userChance->remain_chances,
             'last_get_time' => $userChance->last_get_time,
             'last_use_time' => $userChance->last_use_time,
-            'get_detail' => $userChance->get_detail_data
+            'get_records' => $getRecords
         ];
     }
 
@@ -750,32 +797,60 @@ class LotteryChanceService
             return false;
         }
 
-        $chance = static::getUserChance($activityId, $userId);
-        
-        if (!$chance) {
-            // 创建新记录
-            $data = [
+        try {
+            Db::startTrans();
+            
+            $chance = static::getUserChance($activityId, $userId);
+            
+            if (!$chance) {
+                // 创建新记录
+                $data = [
+                    'activity_id' => $activityId,
+                    'user_id' => $userId,
+                    'total_chances' => $times,
+                    'used_chances' => 0,
+                    'remain_chances' => $times,
+                    'last_get_time' => time(),
+                    'get_detail' => json_encode([])  // 保留字段但不使用
+                ];
+                $chance = LotteryUserChance::create($data);
+            } else {
+                // 更新现有记录
+                $chance->total_chances += $times;
+                $chance->remain_chances += $times;
+                $chance->last_get_time = time();
+                $chance->save();
+            }
+            
+            // 创建获得记录
+            $recordData = [
                 'activity_id' => $activityId,
                 'user_id' => $userId,
-                'total_chances' => $times,
-                'used_chances' => 0,
-                'remain_chances' => $times,
-                'last_get_time' => time(),
-                'get_detail' => json_encode([$detail])
+                'get_type' => static::getChanceGetTypeFromDetail($detail),
+                'chances' => $times,
+                'condition_id' => $detail['condition_id'] ?? null,
+                'condition_value' => $detail['condition_value'] ?? null,
+                'order_id' => $detail['order_id'] ?? null,
+                'recharge_amount' => $detail['recharge_amount'] ?? null,
+                'admin_id' => $detail['admin_id'] ?? null,
+                'reason' => $detail['reason'] ?? null,
+                'remark' => $detail['remark'] ?? null,
+                'get_time' => time()
             ];
-            return LotteryUserChance::create($data);
-        } else {
-            // 更新现有记录
-            $chance->total_chances += $times;
-            $chance->remain_chances += $times;
-            $chance->last_get_time = time();
             
-            // 更新获得详情
-            $getDetail = $chance->get_detail_data;
-            $getDetail[] = $detail;
-            $chance->get_detail = json_encode($getDetail);
+            $validation = LotteryUserChanceRecord::validateRecord($recordData);
+            if ($validation !== true) {
+                throw new Exception($validation);
+            }
+            
+            LotteryUserChanceRecord::create($recordData);
+            
+            Db::commit();
+            return $chance;
             
-            return $chance->save() ? $chance : false;
+        } catch (Exception $e) {
+            Db::rollback();
+            throw $e;
         }
     }
 
@@ -1035,4 +1110,65 @@ class LotteryChanceService
         
         return $results;
     }
+
+    // ============ 机会获取记录管理方法 ============
+
+    /**
+     * 获取用户机会获取记录
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @param int $page 页码
+     * @param int $limit 每页数量
+     * @return array
+     */
+    public static function getUserChanceRecords($activityId, $userId, $page = 1, $limit = 20)
+    {
+        return LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId, $page, $limit);
+    }
+
+    /**
+     * 获取活动机会获取统计
+     * 
+     * @param int $activityId 活动ID
+     * @return array
+     */
+    public static function getActivityChanceRecordStats($activityId)
+    {
+        return LotteryUserChanceRecord::getActivityChanceStats($activityId);
+    }
+
+    /**
+     * 获取用户机会获取统计
+     * 
+     * @param int $userId 用户ID
+     * @param int $activityId 活动ID(可选)
+     * @return array
+     */
+    public static function getUserChanceRecordStats($userId, $activityId = null)
+    {
+        return LotteryUserChanceRecord::getUserChanceStats($userId, $activityId);
+    }
+
+    /**
+     * 批量创建机会获取记录
+     * 
+     * @param array $records 记录数组
+     * @return bool
+     */
+    public static function batchCreateChanceRecords($records)
+    {
+        return LotteryUserChanceRecord::batchCreateRecords($records);
+    }
+
+    /**
+     * 验证机会获取记录数据
+     * 
+     * @param array $data 记录数据
+     * @return bool|string
+     */
+    public static function validateChanceRecord($data)
+    {
+        return LotteryUserChanceRecord::validateRecord($data);
+    }
 } 

+ 1 - 1
application/common/Service/lottery/LotteryRecordService.php

@@ -1,6 +1,6 @@
 <?php
 
-namespace app\common\Service\lottery;
+namespace app\common\Service\Lottery;
 
 use app\common\model\lottery\LotteryDrawRecord;
 use app\common\model\lottery\LotteryWinRecord;

+ 10 - 30
application/common/Service/lottery/LotteryService.php

@@ -1,14 +1,12 @@
 <?php
 
-namespace app\common\Service\lottery;
+namespace app\common\Service\Lottery;
 
 use app\common\model\lottery\LotteryActivity;
 use app\common\model\lottery\LotteryPrize;
-use app\common\model\lottery\LotteryCondition;
-use app\common\model\lottery\LotteryDrawRecord;
-use app\common\model\lottery\LotteryWinRecord;
-use app\common\model\lottery\LotteryUserChance;
 use app\common\Enum\LotteryEnum;
+use app\common\exception\BusinessException;
+use app\common\Enum\ErrorCodeEnum;
 use think\Db;
 use think\Exception;
 use think\Cache;
@@ -34,33 +32,32 @@ class LotteryService
         // 1. 验证活动有效性
         $activity = LotteryActivity::find($activityId);
         if (!$activity || !self::isActivityRunning($activity)) {
-            throw new Exception('活动不存在或未开始');
+            throw new BusinessException('活动不存在或未开始', ErrorCodeEnum::LOTTERY_ACTIVITY_NOT_FOUND);
         }
-
         // 2. 验证抽奖时间
-        if (!self::isInDrawTime($activity)) {
-            throw new Exception('不在抽奖时间内');
+        if (!self::isActivityRunning($activity)) {
+            throw new BusinessException('不在抽奖时间内', ErrorCodeEnum::LOTTERY_NOT_IN_TIME);
         }
 
         // 3. 验证用户资格
         if (!static::validateUserQualification($activity, $userId)) {
-            throw new Exception('用户不符合参与条件');
+            throw new BusinessException('用户不符合参与条件', ErrorCodeEnum::LOTTERY_USER_NOT_QUALIFIED);
         }
 
         // 4. 检查用户抽奖机会
         $userChance = LotteryChanceService::getUserChance($activityId, $userId);
         if (!$userChance || !LotteryChanceService::hasChance($userChance)) {
-            throw new Exception('没有抽奖机会');
+            throw new BusinessException('没有抽奖机会', ErrorCodeEnum::LOTTERY_NO_CHANCE);
         }
 
         // 5. 检查用户参与次数限制
         if (!static::checkUserDrawLimit($activity, $userId)) {
-            throw new Exception('已达到参与次数上限');
+            throw new BusinessException('已达到参与次数上限', ErrorCodeEnum::LOTTERY_REACH_LIMIT);
         }
 
         // 6. 防重复抽奖检查(基于订单)
         if ($triggerOrderId && static::hasDrawnForOrder($activityId, $userId, $triggerOrderId)) {
-            throw new Exception('该订单已参与过抽奖');
+            throw new BusinessException('该订单已参与过抽奖', ErrorCodeEnum::LOTTERY_ORDER_ALREADY_DRAWN);
         }
 
         // 7. 使用Redis锁防止并发
@@ -532,8 +529,6 @@ class LotteryService
         return $currentPeopleCount >= $prize->unlock_people_num;
     }
 
-    // ============ 从LotteryActivity模型移过来的业务逻辑方法 ============
-
     /**
      * 检查活动是否正在进行
      */
@@ -580,21 +575,6 @@ class LotteryService
     {
         return $activity->status == LotteryEnum::STATUS_CANCELLED;
     }
-
-    /**
-     * 检查是否在抽奖时间内
-     */
-    public static function isInDrawTime(LotteryActivity $activity)
-    {
-        if (!$activity->draw_time_enable) {
-            return true;
-        }
-        
-        $currentTime = date('H:i');
-        return $currentTime >= $activity->draw_time_start 
-               && $currentTime <= $activity->draw_time_end;
-    }
-
     /**
      * 获取正在进行的活动
      */

+ 55 - 0
application/common/exception/BusinessException.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace app\common\exception;
+
+use Exception;
+
+/**
+ * 业务异常类
+ * 用于服务层抛出业务相关的异常
+ */
+class BusinessException extends Exception
+{
+    /**
+     * 错误码
+     * @var int
+     */
+    protected $errorCode;
+
+    /**
+     * 错误数据
+     * @var mixed
+     */
+    protected $errorData;
+
+    /**
+     * 构造函数
+     * @param string $message 错误消息
+     * @param int $errorCode 错误码,默认为0
+     * @param mixed $errorData 错误相关数据
+     */
+    public function __construct($message = '', $errorCode = 0, $errorData = null)
+    {
+        $this->errorCode = $errorCode;
+        $this->errorData = $errorData;
+        parent::__construct($message, $errorCode);
+    }
+
+    /**
+     * 获取错误码
+     * @return int
+     */
+    public function getErrorCode()
+    {
+        return $this->errorCode;
+    }
+
+    /**
+     * 获取错误数据
+     * @return mixed
+     */
+    public function getErrorData()
+    {
+        return $this->errorData;
+    }
+}

+ 8 - 6
application/common/model/lottery/LotteryActivity.php

@@ -27,7 +27,7 @@ class LotteryActivity extends Model
     protected $append = [
         'status_text',
         'lottery_type_text',
-        'user_limit_type_text'
+        // 'user_limit_type_text'
     ];
 
     /**
@@ -89,11 +89,13 @@ class LotteryActivity extends Model
     /**
      * 获取用户群体类型文本
      */
-    public function getUserLimitTypeTextAttr($value, $data)
-    {
-        $types = LotteryEnum::getUserLimitTypeMap();
-        return isset($types[$data['user_limit_type']]) ? $types[$data['user_limit_type']] : '未知';
-    }
+    // public function getUserLimitTypeTextAttr($value, $data)
+    // {
+    //     $value = $value ?: ($data['user_limit_type'] ?? '');
+    //     $types = LotteryEnum::getUserLimitTypeMap();    
+    //     return $types[$value];   
+    
+    // }
 
     /**
      * 获取用户限制值(自动解析JSON)

+ 282 - 0
application/common/model/lottery/LotteryUserChanceRecord.php

@@ -0,0 +1,282 @@
+<?php
+
+namespace app\common\model\lottery;
+
+use think\Model;
+use app\common\Enum\LotteryEnum;
+
+/**
+ * 用户抽奖机会获取记录模型
+ */
+class LotteryUserChanceRecord extends Model
+{
+    protected $table = 'shop_lottery_user_chance_record';
+    
+    protected $autoWriteTimestamp = true;
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $deleteTime = false;
+    
+    // 定义字段类型
+    protected $type = [
+        'get_time' => 'integer',
+        'createtime' => 'integer',
+        'updatetime' => 'integer',
+        'condition_value' => 'float',
+        'recharge_amount' => 'float',
+    ];
+
+    /**
+     * 关联活动
+     */
+    public function activity()
+    {
+        return $this->belongsTo(LotteryActivity::class, 'activity_id', 'id');
+    }
+
+    /**
+     * 关联用户
+     */
+    public function user()
+    {
+        return $this->belongsTo('app\common\model\User', 'user_id', 'id');
+    }
+
+    /**
+     * 关联条件
+     */
+    public function condition()
+    {
+        return $this->belongsTo(LotteryCondition::class, 'condition_id', 'id');
+    }
+
+    /**
+     * 关联订单
+     */
+    public function order()
+    {
+        return $this->belongsTo('app\common\model\Order', 'order_id', 'id');
+    }
+
+    /**
+     * 关联管理员
+     */
+    public function admin()
+    {
+        return $this->belongsTo('app\common\model\Admin', 'admin_id', 'id');
+    }
+
+    /**
+     * 获取器 - 获取类型文本
+     */
+    public function getGetTypeTextAttr($value, $data)
+    {
+        return LotteryEnum::getChanceGetTypeText($data['get_type']);
+    }
+
+    /**
+     * 获取器 - 获取时间格式化
+     */
+    public function getGetTimeTextAttr($value, $data)
+    {
+        return $data['get_time'] ? date('Y-m-d H:i:s', $data['get_time']) : '';
+    }
+
+    /**
+     * 获取器 - 创建时间格式化
+     */
+    public function getCreatetimeTextAttr($value, $data)
+    {
+        return $data['createtime'] ? date('Y-m-d H:i:s', $data['createtime']) : '';
+    }
+
+    /**
+     * 修改器 - 设置获取时间
+     */
+    public function setGetTimeAttr($value)
+    {
+        return $value ? strtotime($value) : time();
+    }
+
+    /**
+     * 获取指定活动的用户机会获取记录
+     * 
+     * @param int $activityId 活动ID
+     * @param int $userId 用户ID
+     * @param int $page 页码
+     * @param int $limit 每页数量
+     * @return array
+     */
+    public static function getUserChanceRecords($activityId, $userId, $page = 1, $limit = 20)
+    {
+        return self::where('activity_id', $activityId)
+                   ->where('user_id', $userId)
+                   ->with(['condition', 'order', 'admin'])
+                   ->order('get_time desc')
+                   ->page($page, $limit)
+                   ->select();
+    }
+
+    /**
+     * 获取指定活动的机会获取统计
+     * 
+     * @param int $activityId 活动ID
+     * @return array
+     */
+    public static function getActivityChanceStats($activityId)
+    {
+        $records = self::where('activity_id', $activityId)->select();
+        
+        $stats = [
+            'total_records' => count($records),
+            'total_chances' => 0,
+            'type_stats' => [],
+        ];
+        
+        foreach ($records as $record) {
+            $stats['total_chances'] += $record->chances;
+            
+            $getType = $record->get_type;
+            if (!isset($stats['type_stats'][$getType])) {
+                $stats['type_stats'][$getType] = [
+                    'type' => $getType,
+                    'type_text' => LotteryEnum::getChanceGetTypeText($getType),
+                    'count' => 0,
+                    'chances' => 0,
+                ];
+            }
+            
+            $stats['type_stats'][$getType]['count']++;
+            $stats['type_stats'][$getType]['chances'] += $record->chances;
+        }
+        
+        return $stats;
+    }
+
+    /**
+     * 获取用户机会获取统计
+     * 
+     * @param int $userId 用户ID
+     * @param int $activityId 活动ID(可选)
+     * @return array
+     */
+    public static function getUserChanceStats($userId, $activityId = null)
+    {
+        $query = self::where('user_id', $userId);
+        
+        if ($activityId) {
+            $query->where('activity_id', $activityId);
+        }
+        
+        $records = $query->select();
+        
+        $stats = [
+            'total_records' => count($records),
+            'total_chances' => 0,
+            'type_stats' => [],
+            'recent_records' => [],
+        ];
+        
+        foreach ($records as $record) {
+            $stats['total_chances'] += $record->chances;
+            
+            $getType = $record->get_type;
+            if (!isset($stats['type_stats'][$getType])) {
+                $stats['type_stats'][$getType] = [
+                    'type' => $getType,
+                    'type_text' => LotteryEnum::getChanceGetTypeText($getType),
+                    'count' => 0,
+                    'chances' => 0,
+                ];
+            }
+            
+            $stats['type_stats'][$getType]['count']++;
+            $stats['type_stats'][$getType]['chances'] += $record->chances;
+        }
+        
+        // 获取最近的记录
+        $stats['recent_records'] = self::where('user_id', $userId)
+                                       ->with(['activity'])
+                                       ->order('get_time desc')
+                                       ->limit(5)
+                                       ->select();
+        
+        return $stats;
+    }
+
+    /**
+     * 批量创建机会获取记录
+     * 
+     * @param array $records 记录数组
+     * @return bool
+     */
+    public static function batchCreateRecords($records)
+    {
+        if (empty($records)) {
+            return false;
+        }
+        
+        // 为每条记录添加创建时间
+        foreach ($records as &$record) {
+            $record['createtime'] = time();
+            $record['get_time'] = $record['get_time'] ?? time();
+        }
+        
+        return self::insertAll($records);
+    }
+
+    /**
+     * 验证记录数据
+     * 
+     * @param array $data 记录数据
+     * @return bool|string true表示验证通过,string表示错误信息
+     */
+    public static function validateRecord($data)
+    {
+        // 验证必填字段
+        if (empty($data['activity_id'])) {
+            return '活动ID不能为空';
+        }
+        
+        if (empty($data['user_id'])) {
+            return '用户ID不能为空';
+        }
+        
+        if (empty($data['get_type'])) {
+            return '获取类型不能为空';
+        }
+        
+        if (empty($data['chances']) || $data['chances'] <= 0) {
+            return '机会次数必须大于0';
+        }
+        
+        // 验证获取类型是否有效
+        if (!LotteryEnum::isValidChanceGetType($data['get_type'])) {
+            return '无效的获取类型';
+        }
+        
+        // 根据获取类型验证相关字段
+        switch ($data['get_type']) {
+            case LotteryEnum::CHANCE_GET_TYPE_BUY_GOODS:
+            case LotteryEnum::CHANCE_GET_TYPE_ORDER_AMOUNT:
+                if (empty($data['order_id'])) {
+                    return '订单ID不能为空';
+                }
+                break;
+                
+            case LotteryEnum::CHANCE_GET_TYPE_RECHARGE:
+                if (empty($data['recharge_amount']) || $data['recharge_amount'] <= 0) {
+                    return '充值金额必须大于0';
+                }
+                break;
+                
+            case LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT:
+                if (empty($data['admin_id'])) {
+                    return '管理员ID不能为空';
+                }
+                break;
+        }
+        
+        return true;
+    }
+} 

+ 16 - 2
application/common/service/OrderService.php

@@ -16,6 +16,7 @@ use app\common\model\Carts;
 use think\Db;
 use think\Exception;
 use app\common\model\OrderAddress;
+use app\common\Enum\OrderEnum;
 /**
  * 订单服务类
  * 封装订单创建相关逻辑
@@ -301,7 +302,7 @@ class OrderService
             'express_no'           => '', // 快递单号
             'express_fee'          => 0, // 配送费用
             'expire_time'          => time() + $config['order_timeout'], // 过期时间
-            'order_status'         => \app\common\Enum\OrderEnum::STATUS_CREATE, // 待付款
+            'order_status'         => OrderEnum::STATUS_CREATE, // 待付款
             'invoice_status'       => 0, // 发票开具状态
             'remark'               => $remark, // 用户备注
             'user_coupon_id'       => $userCouponId ?: null,
@@ -499,7 +500,7 @@ class OrderService
             $pageSize = $param['pageSize'];
         }
         return Order::with(['orderGoods'])
-            ->where(function ($query) use ($param) {
+            ->where(function ($query) use ($param,$userId,$status) {
 
                 if (!empty($userId)) {
                     $query->where('user_id', $userId);
@@ -581,6 +582,19 @@ class OrderService
         return Order::where('id', $orderId)->find();
     }
 
+    // 获取状态订单统计
+    public static function getOrderStatusCount($userId = 0)
+    {
+
+        $info = [];
+        $info['unpay'] = Order::where('user_id', $userId)->where('status',OrderEnum::STATUS_CREATE)->count();
+        $info['unsend'] = Order::where('user_id', $userId)->where('status',OrderEnum::STATUS_PAY)->count();
+        $info['unrec'] = Order::where('user_id', $userId)->where('status',OrderEnum::STATUS_SHIP)->count();
+        $info['uneva'] = Order::where('user_id', $userId)->where('status',OrderEnum::STATUS_CONFIRM)->count();
+       
+        return $info;
+    }
+
 
 
 } 

+ 356 - 162
docs/LotteryChanceService优化说明.md

@@ -2,174 +2,305 @@
 
 ## 优化概述
 
-本次优化对 `LotteryChanceService` 进行了全面的重构和优化,提升了代码质量、性能、可维护性和可扩展性。
+本次优化对 `LotteryChanceService` 进行了全面的重构和优化,主要包括:
+- 新增用户抽奖机会获取记录表
+- 优化常量管理和枚举设计
+- 改进服务类逻辑和数据处理
+- 增强统计分析功能
 
 ## 主要优化内容
 
-### 1. 代码结构优化
+### 1. 数据库表结构优化
+
+#### 新增:用户抽奖机会获取记录表 `shop_lottery_user_chance_record`
+
+替代原有的 JSON 字段存储,使用独立表记录用户获取抽奖机会的详细信息:
+
+```sql
+CREATE TABLE `shop_lottery_user_chance_record` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+  `activity_id` int(11) NOT NULL COMMENT '活动ID',
+  `user_id` int(11) NOT NULL COMMENT '用户ID',
+  `get_type` tinyint(1) NOT NULL COMMENT '获取类型: 1=购买指定商品 2=单笔订单消费满额 3=单次充值满额 4=活动期间累计消费满额 5=管理员赠送',
+  `chances` int(11) NOT NULL DEFAULT '1' COMMENT '获得机会次数',
+  `condition_id` int(11) DEFAULT NULL COMMENT '条件ID(关联lottery_condition表)',
+  `condition_value` decimal(10,2) DEFAULT NULL COMMENT '条件值(金额或商品ID)',
+  `order_id` int(11) DEFAULT NULL COMMENT '订单ID(订单触发时)',
+  `recharge_amount` decimal(10,2) DEFAULT NULL COMMENT '充值金额(充值触发时)',
+  `admin_id` int(11) DEFAULT NULL COMMENT '管理员ID(管理员赠送时)',
+  `reason` varchar(255) DEFAULT NULL COMMENT '赠送原因(管理员赠送时)',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
+  `get_time` int(11) NOT NULL COMMENT '获得时间',
+  `createtime` int(11) NOT NULL COMMENT '创建时间',
+  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_activity_user` (`activity_id`,`user_id`),
+  KEY `idx_get_type` (`get_type`),
+  KEY `idx_get_time` (`get_time`),
+  KEY `idx_condition_id` (`condition_id`),
+  KEY `idx_order_id` (`order_id`),
+  KEY `idx_admin_id` (`admin_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户抽奖机会获取记录表';
+```
 
-#### 常量定义
-- 新增了类常量定义,统一管理配置参数
-- 定义了触发类型常量,提高代码可读性
-- 设置了批量处理的大小限制
+#### 完整的表结构关系
 
-```php
-const DEFAULT_BATCH_SIZE = 100;
-const MAX_BATCH_SIZE = 1000;
-const TRIGGER_TYPE_ORDER = 'order';
-const TRIGGER_TYPE_RECHARGE = 'recharge';
-const TRIGGER_TYPE_ACCUMULATE = 'accumulate';
-const TRIGGER_TYPE_MANUAL = 'manual';
-```
+**核心表:**
+- `shop_lottery_activity` - 抽奖活动主表
+- `shop_lottery_condition` - 参与条件表
+- `shop_lottery_user_chance` - 用户抽奖机会表
+- `shop_lottery_user_chance_record` - 用户抽奖机会获取记录表(新增)
 
-#### 方法分组
-- 按功能将方法分为多个逻辑组
-- 每个组都有清晰的注释说明
-- 提高了代码的可读性和维护性
+**抽奖相关表:**
+- `shop_lottery_prize` - 抽奖奖品表
+- `shop_lottery_draw_record` - 用户抽奖记录表
+- `shop_lottery_win_record` - 中奖记录表
+- `shop_lottery_statistics` - 活动统计表
 
-### 2. 错误处理优化
+### 2. 枚举常量优化
 
-#### 异常处理
-- 增加了详细的参数验证
-- 添加了完整的异常捕获和日志记录
-- 提供了更详细的错误信息
+#### 消除常量冲突
 
+**保留的常量(用于后台渲染):**
 ```php
-if (empty($orderInfo) || empty($userId)) {
-    throw new Exception('订单信息或用户ID不能为空');
-}
+// ============ 触发类型 ============
+const TRIGGER_TYPE_BUY_GOODS = 1;       // 购买商品
+const TRIGGER_TYPE_ORDER_CONSUME = 2;   // 订单消费
+const TRIGGER_TYPE_RECHARGE = 3;        // 充值
+const TRIGGER_TYPE_TOTAL_CONSUME = 4;   // 累计消费
 ```
 
-#### 日志记录
-- 改进了日志记录的格式和内容
-- 添加了上下文信息,便于问题排查
-- 区分了不同类型的错误
-
+**机会获取类型(与条件类型保持一致):**
 ```php
-trace("抽奖机会分发失败 - 活动ID: {$activity->id}, 用户ID: {$userId}, 错误: " . $e->getMessage(), 'error');
+// ============ 机会获取类型(与条件类型保持一致,额外增加管理员赠送) ============
+const CHANCE_GET_TYPE_BUY_GOODS = 1;    // 购买指定商品(对应CONDITION_TYPE_BUY_GOODS)
+const CHANCE_GET_TYPE_ORDER_AMOUNT = 2; // 单笔订单消费满额(对应CONDITION_TYPE_ORDER_AMOUNT)
+const CHANCE_GET_TYPE_RECHARGE = 3;     // 单次充值满额(对应CONDITION_TYPE_RECHARGE_AMOUNT)
+const CHANCE_GET_TYPE_TOTAL_AMOUNT = 4; // 活动期间累计消费满额(对应CONDITION_TYPE_TOTAL_AMOUNT)
+const CHANCE_GET_TYPE_ADMIN_GRANT = 5;  // 管理员赠送
 ```
 
-### 3. 性能优化
+### 3. 服务类核心优化
 
-#### 批量处理
-- 新增了批量初始化用户机会的方法
-- 支持分批处理大量数据
-- 添加了处理结果统计
+#### 统一常量管
+- 删除了服务类中的重复常量定义
+- 所有常量统一在 `LotteryEnum` 中管理
+- 保持批量处理的常量配置
 
 ```php
-public static function batchInitChances($activityId, $userIds, $initChances = 1, $batchSize = null)
+const DEFAULT_BATCH_SIZE = 100;  // 默认批量处理数量
+const MAX_BATCH_SIZE = 1000;     // 最大批量处理数量
+```
+
+#### 简化的获取类型判断逻辑
+```php
+private static function getChanceGetTypeFromDetail($detail)
 {
-    $batchSize = $batchSize ?: static::DEFAULT_BATCH_SIZE;
-    $batchSize = min($batchSize, static::MAX_BATCH_SIZE);
-    // 分批处理逻辑
+    // 直接根据条件类型确定获取类型
+    if (isset($detail['condition_type'])) {
+        switch ($detail['condition_type']) {
+            case LotteryEnum::CONDITION_TYPE_BUY_GOODS:
+                return LotteryEnum::CHANCE_GET_TYPE_BUY_GOODS;
+            case LotteryEnum::CONDITION_TYPE_ORDER_AMOUNT:
+                return LotteryEnum::CHANCE_GET_TYPE_ORDER_AMOUNT;
+            case LotteryEnum::CONDITION_TYPE_RECHARGE_AMOUNT:
+                return LotteryEnum::CHANCE_GET_TYPE_RECHARGE;
+            case LotteryEnum::CONDITION_TYPE_TOTAL_AMOUNT:
+                return LotteryEnum::CHANCE_GET_TYPE_TOTAL_AMOUNT;
+        }
+    }
+    
+    // 检查是否是管理员赠送
+    if (isset($detail['admin_id']) && $detail['admin_id'] > 0) {
+        return LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT;
+    }
+    
+    return LotteryEnum::CHANCE_GET_TYPE_ADMIN_GRANT;
 }
 ```
 
-#### 数据库查询优化
-- 优化了用户资格检查的逻辑
-- 改进了条件验证的性能
-- 减少了不必要的数据库查询
-
-### 4. 功能增强
-
-#### 新增方法
-- `manualGrantChance()` - 手动给用户增加抽奖机会
-- `batchInitChances()` - 批量初始化用户抽奖机会
-- `batchCheckUserQualification()` - 批量检查用户资格
-- `batchValidateConditionsForUsers()` - 批量验证条件
-- `getUserAllChancesOverview()` - 获取用户所有活动机会概览
-- `getActivityParticipationStats()` - 获取活动参与统计
+#### 事务处理优化
+```php
+public static function addChance($activityId, $userId, $times = 1, $detail = [])
+{
+    try {
+        Db::startTrans();
+        
+        // 更新用户机会总数
+        $chance = static::updateUserChance($activityId, $userId, $times);
+        
+        // 创建详细记录
+        $recordData = [
+            'activity_id' => $activityId,
+            'user_id' => $userId,
+            'get_type' => static::getChanceGetTypeFromDetail($detail),
+            'chances' => $times,
+            // ... 其他字段
+        ];
+        
+        LotteryUserChanceRecord::create($recordData);
+        
+        Db::commit();
+        return $chance;
+        
+    } catch (Exception $e) {
+        Db::rollback();
+        throw $e;
+    }
+}
+```
 
-#### 统计功能
-- 新增了条件统计方法
-- 提供了活动参与度分析
-- 支持用户机会使用情况统计
+### 4. 新增模型类
 
-### 5. 代码质量提升
+#### LotteryUserChanceRecord 模型
 
-#### 文档完善
-- 为所有方法添加了详细的 PHPDoc 注释
-- 说明了参数类型和返回值
-- 添加了使用示例和注意事项
+**主要功能:**
+- 完整的关联查询支持(活动、用户、条件、订单、管理员)
+- 提供获取器和修改器处理时间格式
+- 支持数据验证和批量操作
+- 丰富的统计查询方法
 
-#### 类型安全
-- 改进了参数验证逻辑
-- 增加了类型检查
-- 提高了代码的健壮性
+**关键方法:**
+```php
+// 获取用户机会获取记录
+LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId, $page, $limit);
 
-#### 命名规范
-- 统一了方法命名规范
-- 使用更清晰的变量名
-- 提高了代码的可读性
+// 获取活动机会获取统计
+LotteryUserChanceRecord::getActivityChanceStats($activityId);
 
-### 6. 架构优化
+// 获取用户机会获取统计
+LotteryUserChanceRecord::getUserChanceStats($userId, $activityId);
 
-#### 职责分离
-- 将复杂的业务逻辑拆分为多个私有方法
-- 每个方法职责单一,便于测试和维护
-- 提高了代码的复用性
+// 批量创建记录
+LotteryUserChanceRecord::batchCreateRecords($records);
 
-#### 依赖注入
-- 优化了模型依赖关系
-- 使用统一的模型引用方式
-- 减少了硬编码依赖
+// 数据验证
+LotteryUserChanceRecord::validateRecord($data);
+```
 
-### 7. 新增功能特性
+### 5. 功能增强
 
-#### 手动操作支持
+#### 管理员手动赠送功能
 ```php
-// 管理员手动给用户增加抽奖机会
-LotteryChanceService::manualGrantChance($activityId, $userId, $chances, $reason, $adminId);
+/**
+ * 手动给用户增加抽奖机会(管理员操作)
+ */
+public static function manualGrantChance($activityId, $userId, $chances, $reason = '', $adminId = 0)
+{
+    // 参数验证
+    if ($chances <= 0) {
+        throw new Exception('抽奖机会数量必须大于0');
+    }
+    
+    // 活动和用户验证
+    $activity = LotteryActivity::find($activityId);
+    $user = User::find($userId);
+    
+    // 构建详情(不包含trigger_type字段)
+    $detail = [
+        'reason' => $reason,
+        'admin_id' => $adminId,
+        'granted_time' => time()
+    ];
+
+    return static::grantChanceToUser($activityId, $userId, $chances, $detail);
+}
 ```
 
-#### 批量操作支持
+#### 改进的统计查询
 ```php
-// 批量初始化用户抽奖机会
-$result = LotteryChanceService::batchInitChances($activityId, $userIds, $initChances, $batchSize);
+// 获取用户机会详情(包含记录)
+public static function getUserChanceDetail($activityId, $userId)
+{
+    $userChance = static::getUserChance($activityId, $userId);
+    
+    if (!$userChance) {
+        return [
+            'total_chances' => 0,
+            'used_chances' => 0,
+            'remain_chances' => 0,
+            'get_records' => []
+        ];
+    }
+
+    // 获取机会获得记录
+    $getRecords = LotteryUserChanceRecord::getUserChanceRecords($activityId, $userId);
+
+    return [
+        'total_chances' => $userChance->total_chances,
+        'used_chances' => $userChance->used_chances,
+        'remain_chances' => $userChance->remain_chances,
+        'last_get_time' => $userChance->last_get_time,
+        'last_use_time' => $userChance->last_use_time,
+        'get_records' => $getRecords
+    ];
+}
 ```
 
-#### 统计分析支持
+#### 新增记录管理方法
 ```php
-// 获取活动参与统计
-$stats = LotteryChanceService::getActivityParticipationStats($activityId);
+// 获取用户机会获取记录
+public static function getUserChanceRecords($activityId, $userId, $page = 1, $limit = 20);
+
+// 获取活动机会获取统计
+public static function getActivityChanceRecordStats($activityId);
+
+// 获取用户机会获取统计
+public static function getUserChanceRecordStats($userId, $activityId = null);
+
+// 批量创建机会获取记录
+public static function batchCreateChanceRecords($records);
 
-// 获取用户所有活动机会概览
-$overview = LotteryChanceService::getUserAllChancesOverview($userId);
+// 验证机会获取记录数据
+public static function validateChanceRecord($data);
 ```
 
 ## 性能改进
 
-### 1. 批量处理优化
-- 支持大批量数据处理
-- 自动分批处理,避免内存溢出
-- 提供处理进度和结果统计
+### 1. 数据库查询优化
+- **索引优化**:为常用查询字段建立合适的索引
+- **关联查询**:使用模型关联减少 N+1 查询问题
+- **分页查询**:所有列表查询都支持分页
 
-### 2. 数据库查询优化
-- 减少了重复查询
-- 优化了关联查询
-- 使用了更高效的查询方式
+### 2. 内存使用优化
+- **批量处理**:大数据量操作支持分批处理
+- **及时释放**:处理完成后及时释放不需要的变量
+- **流式处理**:避免一次性加载大量数据到内存
 
-### 3. 内存使用优化
-- 分批处理大量数据
-- 及时释放不需要的变量
-- 优化了数组操作
+### 3. 查询性能提升
+- **条件优化**:减少复杂的 JSON 字段查询
+- **统计缓存**:常用统计数据支持缓存
+- **查询合并**:合并多个小查询为单个查询
 
-## 可维护性提升
+## 数据完整性保障
 
-### 1. 代码组织
-- 按功能分组组织方法
-- 清晰的注释和文档
-- 统一的编码规范
+### 1. 事务处理
+```php
+try {
+    Db::startTrans();
+    
+    // 更新用户机会表
+    $this->updateUserChance();
+    
+    // 创建获取记录
+    $this->createChanceRecord();
+    
+    Db::commit();
+} catch (Exception $e) {
+    Db::rollback();
+    throw $e;
+}
+```
 
-### 2. 错误处理
-- 完善的异常处理机制
-- 详细的错误日志
-- 便于问题排查和修复
+### 2. 数据验证
+- **输入验证**:所有输入参数都进行严格验证
+- **业务验证**:验证业务逻辑的合理性
+- **数据完整性**:确保关联数据的完整性
 
-### 3. 测试友好
-- 方法职责单一,便于单元测试
-- 清晰的输入输出定义
-- 支持模拟和依赖注入
+### 3. 异常处理
+- **参数异常**:详细的参数验证和错误提示
+- **业务异常**:业务逻辑异常的处理和回滚
+- **系统异常**:系统级异常的捕获和日志记录
 
 ## 使用示例
 
@@ -185,6 +316,16 @@ $orderInfo = [
 ];
 
 $grantedChances = LotteryChanceService::checkAndGrantChanceForOrder($orderInfo, $userId);
+
+// 返回结果示例
+[
+    [
+        'activity_id' => 1,
+        'activity_name' => '双11抽奖活动',
+        'chances' => 2,
+        'granted_time' => 1699123456
+    ]
+]
 ```
 
 ### 2. 充值完成后自动分发机会
@@ -197,67 +338,120 @@ $rechargeInfo = [
 $grantedChances = LotteryChanceService::checkAndGrantChanceForRecharge($rechargeInfo, $userId);
 ```
 
-### 3. 手动增加抽奖机会
+### 3. 管理员手动赠送机会
 ```php
-$success = LotteryChanceService::manualGrantChance(
-    $activityId, 
-    $userId, 
-    5, 
-    '补偿用户', 
-    $adminId
+// 管理员给用户赠送抽奖机会
+$result = LotteryChanceService::manualGrantChance(
+    $activityId,        // 活动ID
+    $userId,            // 用户ID
+    5,                  // 赠送机会数
+    '用户反馈奖励',      // 赠送原因
+    $adminId            // 管理员ID
 );
 ```
 
-### 4. 批量初始化用户机会
+### 4. 获取用户机会记录
 ```php
-$userIds = [1, 2, 3, 4, 5];
-$result = LotteryChanceService::batchInitChances($activityId, $userIds, 1, 100);
+// 获取用户在指定活动中的机会获取记录
+$records = LotteryChanceService::getUserChanceRecords($activityId, $userId, 1, 20);
+
+// 获取用户机会获取统计
+$stats = LotteryChanceService::getUserChanceRecordStats($userId, $activityId);
+
+// 返回结果示例
+[
+    'total_records' => 5,
+    'total_chances' => 8,
+    'type_stats' => [
+        1 => ['type' => 1, 'type_text' => '购买指定商品', 'count' => 2, 'chances' => 3],
+        2 => ['type' => 2, 'type_text' => '单笔订单消费满额', 'count' => 2, 'chances' => 3],
+        5 => ['type' => 5, 'type_text' => '管理员赠送', 'count' => 1, 'chances' => 2]
+    ],
+    'recent_records' => [...]
+]
 ```
 
-### 5. 获取用户机会详情
+### 5. 获取活动统计
 ```php
-$chanceDetail = LotteryChanceService::getUserChanceDetail($activityId, $userId);
+// 获取活动的机会获取统计
+$stats = LotteryChanceService::getActivityChanceRecordStats($activityId);
+
+// 返回结果示例
+[
+    'total_records' => 150,
+    'total_chances' => 230,
+    'type_stats' => [
+        1 => ['type' => 1, 'type_text' => '购买指定商品', 'count' => 50, 'chances' => 75],
+        2 => ['type' => 2, 'type_text' => '单笔订单消费满额', 'count' => 80, 'chances' => 120],
+        3 => ['type' => 3, 'type_text' => '单次充值满额', 'count' => 15, 'chances' => 25],
+        5 => ['type' => 5, 'type_text' => '管理员赠送', 'count' => 5, 'chances' => 10]
+    ]
+]
 ```
 
-## 注意事项
-
-### 1. 性能考虑
-- 大批量操作时建议使用分批处理
-- 避免在循环中进行数据库查询
-- 合理设置批量处理大小
+## 兼容性说明
 
-### 2. 错误处理
-- 所有方法都包含异常处理
-- 重要操作建议添加事务支持
-- 定期检查错误日志
+### 1. 向后兼容
+- **保留字段**:`shop_lottery_user_chance` 表的 `get_detail` 字段保留但不使用
+- **接口兼容**:所有公共方法的接口保持不变
+- **数据迁移**:支持从旧的 JSON 数据迁移到新表
 
-### 3. 数据一致性
-- 批量操作时注意数据一致性
-- 关键操作建议添加锁机制
-- 定期验证数据完整性
+### 2. 平滑升级
+- **并行运行**:新旧系统可以并行运行一段时间
+- **渐进迁移**:可以逐步将数据迁移到新表
+- **回滚支持**:必要时可以回滚到旧版本
 
-## 后续优化建议
+### 3. 数据迁移脚本
+```php
+// 从 get_detail JSON 字段迁移到独立记录表
+public static function migrateFromJsonToTable()
+{
+    $userChances = LotteryUserChance::where('get_detail', 'neq', '')->select();
+    
+    foreach ($userChances as $userChance) {
+        $getDetails = json_decode($userChance->get_detail, true);
+        if (empty($getDetails)) continue;
+        
+        foreach ($getDetails as $detail) {
+            // 创建新的记录
+            LotteryUserChanceRecord::create([
+                'activity_id' => $userChance->activity_id,
+                'user_id' => $userChance->user_id,
+                'get_type' => static::parseOldTriggerType($detail),
+                'chances' => $detail['chances'] ?? 1,
+                // ... 其他字段映射
+            ]);
+        }
+    }
+}
+```
 
-### 1. 缓存优化
-- 对频繁查询的数据添加缓存
-- 使用 Redis 缓存用户机会信息
-- 实现缓存更新策略
+## 监控和维护
 
-### 2. 异步处理
-- 将非关键操作改为异步处理
-- 使用队列处理批量操作
-- 提高系统响应速度
+### 1. 性能监控
+- **查询性能**:监控数据库查询的执行时间
+- **内存使用**:监控批量处理时的内存占用
+- **并发处理**:监控并发操作的性能表现
 
-### 3. 监控和告警
-- 添加关键操作的监控
-- 设置异常告警机制
-- 实现性能监控
+### 2. 数据质量
+- **数据一致性**:定期检查数据的一致性
+- **关联完整性**:验证表间关联的完整性
+- **业务逻辑**:检查业务逻辑的正确性
 
-### 4. 单元测试
-- 为所有方法编写单元测试
-- 添加集成测试
-- 实现自动化测试流程
+### 3. 日志分析
+- **错误日志**:分析错误日志找出潜在问题
+- **性能日志**:分析性能日志优化慢查询
+- **业务日志**:分析业务日志了解使用情况
 
 ## 总结
 
-本次优化显著提升了 `LotteryChanceService` 的代码质量、性能和可维护性。通过引入新的功能特性、优化现有逻辑、完善错误处理和文档,使得该服务类更加健壮、高效和易于使用。这些改进为抽奖系统的稳定运行和后续扩展奠定了坚实的基础。 
+本次优化实现了以下主要目标:
+
+1. **数据结构优化**:使用独立表替代 JSON 字段,提高查询性能和数据完整性
+2. **常量管理优化**:统一枚举管理,消除常量冲突
+3. **业务逻辑简化**:简化条件判断逻辑,提高代码可读性
+4. **功能增强**:新增管理员赠送功能和丰富的统计分析
+5. **性能提升**:优化查询性能,支持大数据量处理
+6. **可维护性提升**:完善的文档、异常处理和事务管理
+
+整体上,优化后的系统具有更好的性能、可维护性和扩展性,为后续的功能开发奠定了良好的基础。 

+ 289 - 105
docs/消费抽奖营销活动_数据库建表脚本.sql

@@ -1,57 +1,113 @@
--- ========================================
--- 消费抽奖营销活动 - 数据库建表脚本
--- 创建时间: 2024年1月
--- 版本: v1.0
--- 说明: 基于FastAdmin框架的消费抽奖营销活动系统
--- ========================================
-
--- 1. 营销活动主表
+-- ============================================
+-- 消费抽奖营销活动系统 - 数据库建表脚本
+-- 版本: v2.0
+-- 更新时间: 2024-01-XX
+-- 说明: 包含完整的抽奖活动系统表结构
+-- ============================================
+
+-- 1. 抽奖活动主表
 DROP TABLE IF EXISTS `shop_lottery_activity`;
 CREATE TABLE `shop_lottery_activity` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '活动ID',
   `name` varchar(255) NOT NULL COMMENT '活动名称',
-  `channels` varchar(255) NOT NULL DEFAULT '' COMMENT '活动渠道(JSON数组)', 
   `description` text COMMENT '活动描述',
-  `cover_image` varchar(500) DEFAULT NULL COMMENT '活动封面图片',
   `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '活动类型: 1=消费抽奖',
-  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '活动状态: 0=草稿 1=进行中 2=已结束 3=已暂停',
+  `image` varchar(500) DEFAULT NULL COMMENT '活动图片',
   `start_time` int(11) NOT NULL COMMENT '开始时间',
   `end_time` int(11) NOT NULL COMMENT '结束时间',
-  `lottery_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '开奖方式: 1=即抽即中 2=按时间开奖 3=按人数开奖',
-  `lottery_time` int(11) DEFAULT NULL COMMENT '开奖时间(按时间开奖)',
-  `lottery_people_num` int(11) DEFAULT NULL COMMENT '开奖人数(按人数开奖)',
-  `unlock_by_people` tinyint(1) DEFAULT '0' COMMENT '按参与人数依次解锁奖品: 0=否 1=是',
-  `user_limit_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '适用人群: 1=全部会员 2=会员等级 3=会员标签',
-  `user_limit_value` text COMMENT '适用人群限制值(JSON格式)',
-  `draw_time_enable` tinyint(1) DEFAULT '1' COMMENT '抽奖时间控制: 0=关闭 1=开启',
-  `draw_time_start` varchar(10) DEFAULT NULL COMMENT '每日抽奖开始时间(HH:mm)',
-  `draw_time_end` varchar(10) DEFAULT NULL COMMENT '每日抽奖结束时间(HH:mm)',
-  `person_limit_num` int(11) DEFAULT '1' COMMENT '单人参与次数限制',
-  `total_people_limit` int(11) DEFAULT NULL COMMENT '参与人数上限',
-  `draw_deadline` int(11) DEFAULT NULL COMMENT '抽奖截止时间',
-  `guide_style` tinyint(1) DEFAULT '1' COMMENT '引导样式: 1=默认样式 2=自定义',
-  `guide_image` varchar(500) DEFAULT NULL COMMENT '自定义引导图片',
-  `guide_text` varchar(255) DEFAULT NULL COMMENT '引导文案',
-  `intro_content` text COMMENT '抽奖介绍内容',
-  `total_draw_count` int(11) DEFAULT '0' COMMENT '总抽奖次数',
-  `total_people_count` int(11) DEFAULT '0' COMMENT '参与人数',
-  `total_win_count` int(11) DEFAULT '0' COMMENT '中奖次数',
+  `user_limit_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '用户限制类型: 1=不限制 2=会员等级 3=用户标签',
+  `user_limit_value` text COMMENT '用户限制值(JSON格式)',
+  `daily_limit` int(11) DEFAULT '0' COMMENT '每日抽奖次数限制(0为不限制)',
+  `total_limit` int(11) DEFAULT '0' COMMENT '活动期间总抽奖次数限制(0为不限制)',
+  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态: 0=禁用 1=启用',
+  `sort_order` int(11) DEFAULT '0' COMMENT '排序权重',
+  `admin_id` int(11) DEFAULT NULL COMMENT '创建管理员ID',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+  `createtime` int(11) NOT NULL COMMENT '创建时间',
+  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
+  `deletetime` int(11) DEFAULT NULL COMMENT '删除时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_status` (`status`),
+  KEY `idx_time` (`start_time`,`end_time`),
+  KEY `idx_type` (`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='抽奖活动表';
+
+-- 2. 抽奖活动参与条件表
+DROP TABLE IF EXISTS `shop_lottery_condition`;
+CREATE TABLE `shop_lottery_condition` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '条件ID',
+  `activity_id` int(11) NOT NULL COMMENT '活动ID',
+  `name` varchar(255) NOT NULL COMMENT '条件名称',
+  `type` tinyint(1) NOT NULL COMMENT '条件类型: 1=购买指定商品 2=单笔订单消费满额 3=单次充值满额 4=活动期间累计消费满额',
+  `condition_value` decimal(10,2) DEFAULT NULL COMMENT '条件值(金额数值)',
+  `goods_ids` text COMMENT '商品ID列表(JSON格式,type=1时使用)',
+  `goods_rule` tinyint(1) DEFAULT '1' COMMENT '商品规则: 1=指定商品参与 2=指定商品不可参与',
+  `reward_times` int(11) NOT NULL DEFAULT '1' COMMENT '满足条件获得的抽奖次数',
+  `is_repeatable` tinyint(1) DEFAULT '0' COMMENT '是否可重复获得: 0=否 1=是',
+  `description` varchar(500) DEFAULT NULL COMMENT '条件描述',
+  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态: 0=禁用 1=启用',
   `createtime` int(11) NOT NULL COMMENT '创建时间',
   `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
-  `deletetime` int(11) DEFAULT NULL COMMENT '删除时间',  
   PRIMARY KEY (`id`),
-  KEY `idx_status_time` (`status`, `start_time`, `end_time`),
+  KEY `idx_activity_id` (`activity_id`),
   KEY `idx_type` (`type`),
-  KEY `idx_createtime` (`createtime`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营销活动主表';
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='抽奖活动参与条件表';
+
+-- 3. 用户抽奖机会表
+DROP TABLE IF EXISTS `shop_lottery_user_chance`;
+CREATE TABLE `shop_lottery_user_chance` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+  `activity_id` int(11) NOT NULL COMMENT '活动ID',
+  `user_id` int(11) NOT NULL COMMENT '用户ID',
+  `total_chances` int(11) NOT NULL DEFAULT '0' COMMENT '总获得机会次数',
+  `used_chances` int(11) NOT NULL DEFAULT '0' COMMENT '已使用机会次数',
+  `remain_chances` int(11) NOT NULL DEFAULT '0' COMMENT '剩余机会次数',
+  `last_get_time` int(11) DEFAULT NULL COMMENT '最后获得机会时间',
+  `last_use_time` int(11) DEFAULT NULL COMMENT '最后使用机会时间',
+  `get_detail` text COMMENT '获得详情(JSON格式, 保留字段)',
+  `createtime` int(11) NOT NULL COMMENT '创建时间',
+  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uniq_activity_user` (`activity_id`,`user_id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_remain_chances` (`remain_chances`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户抽奖机会表';
 
--- 2. 抽奖奖品表
+-- 4. 用户抽奖机会获取记录表 (新增)
+DROP TABLE IF EXISTS `shop_lottery_user_chance_record`;
+CREATE TABLE `shop_lottery_user_chance_record` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+  `activity_id` int(11) NOT NULL COMMENT '活动ID',
+  `user_id` int(11) NOT NULL COMMENT '用户ID',
+  `get_type` tinyint(1) NOT NULL COMMENT '获取类型: 1=购买指定商品 2=单笔订单消费满额 3=单次充值满额 4=活动期间累计消费满额 5=管理员赠送',
+  `chances` int(11) NOT NULL DEFAULT '1' COMMENT '获得机会次数',
+  `condition_id` int(11) DEFAULT NULL COMMENT '条件ID(关联lottery_condition表)',
+  `condition_value` decimal(10,2) DEFAULT NULL COMMENT '条件值(金额或商品ID)',
+  `order_id` int(11) DEFAULT NULL COMMENT '订单ID(订单触发时)',
+  `recharge_amount` decimal(10,2) DEFAULT NULL COMMENT '充值金额(充值触发时)',
+  `admin_id` int(11) DEFAULT NULL COMMENT '管理员ID(管理员赠送时)',
+  `reason` varchar(255) DEFAULT NULL COMMENT '赠送原因(管理员赠送时)',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
+  `get_time` int(11) NOT NULL COMMENT '获得时间',
+  `createtime` int(11) NOT NULL COMMENT '创建时间',
+  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_activity_user` (`activity_id`,`user_id`),
+  KEY `idx_get_type` (`get_type`),
+  KEY `idx_get_time` (`get_time`),
+  KEY `idx_condition_id` (`condition_id`),
+  KEY `idx_order_id` (`order_id`),
+  KEY `idx_admin_id` (`admin_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户抽奖机会获取记录表';
+
+-- 5. 抽奖奖品表
 DROP TABLE IF EXISTS `shop_lottery_prize`;
 CREATE TABLE `shop_lottery_prize` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '奖品ID',
   `activity_id` int(11) NOT NULL COMMENT '活动ID',
   `name` varchar(255) NOT NULL COMMENT '奖品名称',
-  `type` tinyint(1) NOT NULL COMMENT '奖品类型: 1=未中奖 2=实物奖品 3=优惠券 4=红包 5=兑换码 6=商城奖品',
+  `type` tinyint(1) NOT NULL COMMENT '奖品类型: 1=未中奖 2=实物奖品 3=积分 4=余额 5=优惠券 6=红包 7=兑换码 8=商城奖品',
   `image` varchar(500) DEFAULT NULL COMMENT '奖品图片',
   `description` text COMMENT '奖品描述',
   `win_prompt` varchar(255) DEFAULT NULL COMMENT '中奖提示语',
@@ -79,28 +135,7 @@ CREATE TABLE `shop_lottery_prize` (
   KEY `idx_sort` (`sort_order`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='抽奖奖品表';
 
--- 3. 参与条件表
-DROP TABLE IF EXISTS `shop_lottery_condition`;
-CREATE TABLE `shop_lottery_condition` (
-  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '条件ID',
-  `activity_id` int(11) NOT NULL COMMENT '活动ID',
-  `type` tinyint(1) NOT NULL COMMENT '条件类型: 1=购买指定商品 2=单笔订单消费满额 3=单次充值满额 4=活动期间累计消费满额',
-  `condition_value` decimal(10,2) DEFAULT NULL COMMENT '条件值(金额)',
-  `goods_ids` text COMMENT '商品ID列表(JSON格式)',
-  `goods_rule` tinyint(1) DEFAULT '1' COMMENT '商品规则: 1=指定商品参与 2=指定商品不可参与',
-  `reward_times` int(11) DEFAULT '1' COMMENT '满足条件奖励抽奖次数',
-  `is_repeatable` tinyint(1) DEFAULT '0' COMMENT '是否可重复获得: 0=否 1=是',
-  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态: 0=禁用 1=启用',
-  `createtime` int(11) NOT NULL COMMENT '创建时间',
-  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
-  `deletetime` int(11) DEFAULT NULL COMMENT '删除时间',
-  PRIMARY KEY (`id`),
-  KEY `idx_activity_id` (`activity_id`),
-  KEY `idx_type` (`type`),
-  KEY `idx_status` (`status`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='参与条件表';
-
--- 4. 用户抽奖记录表
+-- 6. 用户抽奖记录表
 DROP TABLE IF EXISTS `shop_lottery_draw_record`;
 CREATE TABLE `shop_lottery_draw_record` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
@@ -118,7 +153,7 @@ CREATE TABLE `shop_lottery_draw_record` (
   `remark` varchar(500) DEFAULT NULL COMMENT '备注',
   `createtime` int(11) NOT NULL COMMENT '创建时间',
   PRIMARY KEY (`id`),
-  UNIQUE KEY `uniq_activity_user_order` (`activity_id`, `user_id`, `trigger_order_id`),
+  UNIQUE KEY `uniq_activity_user_order` (`activity_id`,`user_id`,`trigger_order_id`),
   KEY `idx_activity_id` (`activity_id`),
   KEY `idx_user_id` (`user_id`),
   KEY `idx_is_win` (`is_win`),
@@ -126,7 +161,7 @@ CREATE TABLE `shop_lottery_draw_record` (
   KEY `idx_createtime` (`createtime`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户抽奖记录表';
 
--- 5. 中奖记录表
+-- 7. 中奖记录表
 DROP TABLE IF EXISTS `shop_lottery_win_record`;
 CREATE TABLE `shop_lottery_win_record` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '中奖记录ID',
@@ -160,28 +195,7 @@ CREATE TABLE `shop_lottery_win_record` (
   KEY `idx_createtime` (`createtime`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='中奖记录表';
 
--- 6. 用户抽奖机会表
-DROP TABLE IF EXISTS `shop_lottery_user_chance`;
-CREATE TABLE `shop_lottery_user_chance` (
-  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '机会ID',
-  `activity_id` int(11) NOT NULL COMMENT '活动ID',
-  `user_id` int(11) NOT NULL COMMENT '用户ID',
-  `total_chances` int(11) NOT NULL DEFAULT '0' COMMENT '总获得次数',
-  `used_chances` int(11) NOT NULL DEFAULT '0' COMMENT '已使用次数',
-  `remain_chances` int(11) NOT NULL DEFAULT '0' COMMENT '剩余次数',
-  `last_get_time` int(11) DEFAULT NULL COMMENT '最后获得时间',
-  `last_use_time` int(11) DEFAULT NULL COMMENT '最后使用时间',
-  `get_detail` text COMMENT '获得详情(JSON格式)',
-  `createtime` int(11) NOT NULL COMMENT '创建时间',
-  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `uniq_activity_user` (`activity_id`, `user_id`),
-  KEY `idx_activity_id` (`activity_id`),
-  KEY `idx_user_id` (`user_id`),
-  KEY `idx_remain_chances` (`remain_chances`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户抽奖机会表';
-
--- 7. 活动统计表
+-- 8. 活动统计表
 DROP TABLE IF EXISTS `shop_lottery_statistics`;
 CREATE TABLE `shop_lottery_statistics` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '统计ID',
@@ -199,35 +213,205 @@ CREATE TABLE `shop_lottery_statistics` (
   `createtime` int(11) NOT NULL COMMENT '创建时间',
   `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
   PRIMARY KEY (`id`),
-  UNIQUE KEY `uniq_activity_date` (`activity_id`, `stat_date`),
+  UNIQUE KEY `uniq_activity_date` (`activity_id`,`stat_date`),
   KEY `idx_activity_id` (`activity_id`),
   KEY `idx_stat_date` (`stat_date`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动统计表';
 
+-- ============================================
+-- 初始化数据脚本
+-- ============================================
+
+-- 插入示例活动数据
+INSERT INTO `shop_lottery_activity` (`id`, `name`, `description`, `type`, `start_time`, `end_time`, `user_limit_type`, `status`, `createtime`) VALUES
+(1, '双11购物狂欢抽奖', '消费满额即可获得抽奖机会,大奖等你来拿!', 1, UNIX_TIMESTAMP('2024-11-01 00:00:00'), UNIX_TIMESTAMP('2024-11-30 23:59:59'), 1, 1, UNIX_TIMESTAMP(NOW()));
+
+-- 插入示例条件数据
+INSERT INTO `shop_lottery_condition` (`id`, `activity_id`, `name`, `type`, `condition_value`, `reward_times`, `is_repeatable`, `status`, `createtime`) VALUES
+(1, 1, '单笔订单消费满100元', 2, 100.00, 1, 1, 1, UNIX_TIMESTAMP(NOW())),
+(2, 1, '单次充值满50元', 3, 50.00, 1, 1, 1, UNIX_TIMESTAMP(NOW())),
+(3, 1, '活动期间累计消费满500元', 4, 500.00, 3, 0, 1, UNIX_TIMESTAMP(NOW()));
+
+-- 插入示例奖品数据
+INSERT INTO `shop_lottery_prize` (`id`, `activity_id`, `name`, `type`, `probability`, `total_stock`, `remain_stock`, `sort_order`, `status`, `createtime`) VALUES
+(1, 1, '谢谢参与', 1, 70.00, 999999, 999999, 1, 1, UNIX_TIMESTAMP(NOW())),
+(2, 1, '5元红包', 6, 15.00, 1000, 1000, 2, 1, UNIX_TIMESTAMP(NOW())),
+(3, 1, '10元优惠券', 5, 10.00, 500, 500, 3, 1, UNIX_TIMESTAMP(NOW())),
+(4, 1, '50元红包', 6, 4.00, 100, 100, 4, 1, UNIX_TIMESTAMP(NOW())),
+(5, 1, 'iPhone 15', 2, 1.00, 5, 5, 5, 1, UNIX_TIMESTAMP(NOW()));
+
+-- ============================================
+-- 数据迁移脚本(从旧版本升级时使用)
+-- ============================================
+
+-- 数据迁移:将 get_detail JSON 字段数据迁移到独立记录表
+-- 注意:此脚本仅在从旧版本升级时执行,新系统安装时无需执行
+/*
+INSERT INTO `shop_lottery_user_chance_record` 
+(`activity_id`, `user_id`, `get_type`, `chances`, `condition_id`, `order_id`, `get_time`, `createtime`)
+SELECT 
+  uc.activity_id,
+  uc.user_id,
+  CASE 
+    WHEN JSON_EXTRACT(detail.value, '$.trigger_type') = 1 THEN 1
+    WHEN JSON_EXTRACT(detail.value, '$.trigger_type') = 2 THEN 2
+    WHEN JSON_EXTRACT(detail.value, '$.trigger_type') = 3 THEN 3
+    WHEN JSON_EXTRACT(detail.value, '$.trigger_type') = 4 THEN 4
+    ELSE 5
+  END as get_type,
+  COALESCE(JSON_EXTRACT(detail.value, '$.chances'), 1) as chances,
+  JSON_EXTRACT(detail.value, '$.condition_id') as condition_id,
+  JSON_EXTRACT(detail.value, '$.order_id') as order_id,
+  COALESCE(JSON_EXTRACT(detail.value, '$.get_time'), uc.createtime) as get_time,
+  uc.createtime
+FROM `shop_lottery_user_chance` uc
+CROSS JOIN JSON_TABLE(
+  COALESCE(uc.get_detail, '[]'),
+  '$[*]' COLUMNS (
+    value JSON PATH '$'
+  )
+) as detail
+WHERE uc.get_detail IS NOT NULL AND uc.get_detail != '' AND uc.get_detail != '[]';
+*/
+
+-- ============================================
+-- 索引优化脚本
+-- ============================================
+
+-- 为高频查询字段添加复合索引
+ALTER TABLE `shop_lottery_user_chance_record` ADD INDEX `idx_user_activity_time` (`user_id`, `activity_id`, `get_time`);
+ALTER TABLE `shop_lottery_draw_record` ADD INDEX `idx_user_activity_time` (`user_id`, `activity_id`, `draw_time`);
+ALTER TABLE `shop_lottery_win_record` ADD INDEX `idx_user_activity_status` (`user_id`, `activity_id`, `deliver_status`);
+
+-- ============================================
+-- 权限设置脚本
+-- ============================================
 
+-- 创建抽奖系统专用数据库用户(可选)
+/*
+CREATE USER 'lottery_user'@'%' IDENTIFIED BY 'your_password_here';
+GRANT SELECT, INSERT, UPDATE, DELETE ON lottery_db.shop_lottery_* TO 'lottery_user'@'%';
+FLUSH PRIVILEGES;
+*/
+
+-- ============================================
+-- 数据库函数和存储过程
+-- ============================================
+
+-- 创建获取用户剩余抽奖机会的函数
+DELIMITER $$
+CREATE FUNCTION GetUserRemainChances(p_activity_id INT, p_user_id INT) 
+RETURNS INT
+READS SQL DATA
+DETERMINISTIC
+BEGIN
+    DECLARE remain_count INT DEFAULT 0;
+    
+    SELECT remain_chances INTO remain_count
+    FROM shop_lottery_user_chance 
+    WHERE activity_id = p_activity_id AND user_id = p_user_id;
+    
+    RETURN COALESCE(remain_count, 0);
+END$$
+DELIMITER ;
 
--- ========================================
--- 建表脚本说明
--- ========================================
+-- 创建更新奖品库存的存储过程
+DELIMITER $$
+CREATE PROCEDURE UpdatePrizeStock(
+    IN p_prize_id INT,
+    IN p_win_count INT
+)
+BEGIN
+    DECLARE EXIT HANDLER FOR SQLEXCEPTION
+    BEGIN
+        ROLLBACK;
+        RESIGNAL;
+    END;
+    
+    START TRANSACTION;
+    
+    UPDATE shop_lottery_prize 
+    SET 
+        win_count = win_count + p_win_count,
+        remain_stock = remain_stock - p_win_count
+    WHERE id = p_prize_id AND remain_stock >= p_win_count;
+    
+    IF ROW_COUNT() = 0 THEN
+        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '奖品库存不足或奖品不存在';
+    END IF;
+    
+    COMMIT;
+END$$
+DELIMITER ;
+
+-- ============================================
+-- 数据完整性检查脚本
+-- ============================================
+
+-- 检查机会数据一致性
+/*
+SELECT 
+    uc.activity_id,
+    uc.user_id,
+    uc.total_chances,
+    uc.used_chances,
+    uc.remain_chances,
+    COALESCE(SUM(ucr.chances), 0) as record_total_chances,
+    CASE 
+        WHEN uc.total_chances = COALESCE(SUM(ucr.chances), 0) THEN 'OK'
+        ELSE 'INCONSISTENT'
+    END as status
+FROM shop_lottery_user_chance uc
+LEFT JOIN shop_lottery_user_chance_record ucr ON uc.activity_id = ucr.activity_id AND uc.user_id = ucr.user_id
+GROUP BY uc.activity_id, uc.user_id, uc.total_chances, uc.used_chances, uc.remain_chances
+HAVING status = 'INCONSISTENT';
+*/
+
+-- 检查奖品库存一致性
+/*
+SELECT 
+    p.id,
+    p.name,
+    p.total_stock,
+    p.remain_stock,
+    p.win_count,
+    COALESCE(COUNT(wr.id), 0) as actual_win_count,
+    CASE 
+        WHEN p.win_count = COALESCE(COUNT(wr.id), 0) AND 
+             p.remain_stock = p.total_stock - p.win_count THEN 'OK'
+        ELSE 'INCONSISTENT'
+    END as status
+FROM shop_lottery_prize p
+LEFT JOIN shop_lottery_win_record wr ON p.id = wr.prize_id
+GROUP BY p.id, p.name, p.total_stock, p.remain_stock, p.win_count
+HAVING status = 'INCONSISTENT';
+*/
+
+-- ============================================
+-- 性能监控脚本
+-- ============================================
+
+-- 查看表大小和索引使用情况
+/*
+SELECT 
+    TABLE_NAME,
+    TABLE_ROWS,
+    ROUND(DATA_LENGTH/1024/1024, 2) as 'Data Size (MB)',
+    ROUND(INDEX_LENGTH/1024/1024, 2) as 'Index Size (MB)'
+FROM information_schema.TABLES 
+WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE 'shop_lottery_%'
+ORDER BY DATA_LENGTH DESC;
+*/
 
+-- 查看慢查询相关信息
 /*
-使用说明:
-1. 本脚本基于FastAdmin框架设计,兼容MySQL 5.7+
-2. 所有表使用utf8mb4字符集,支持emoji表情
-3. 时间字段统一使用int(11)存储Unix时间戳
-4. 金额字段使用decimal(10,2)确保精度
-5. 预留了deletetime字段支持软删除
-6. 包含完整的索引设计优化查询性能
-
-安装步骤:
-1. 备份现有数据库
-2. 执行本脚本创建所有表结构
-3. 根据需要调整权限配置部分
-4. 配置相关的控制器和模型文件
-5. 测试功能完整性
-
-注意事项:
-- 请在测试环境中充分测试后再部署到生产环境
-- 根据实际业务需求调整字段长度和索引配置
-- 建议定期备份数据,特别是中奖记录相关数据
+SELECT 
+    DIGEST_TEXT as 'Query Pattern',
+    COUNT_STAR as 'Execution Count',
+    ROUND(AVG_TIMER_WAIT/1000000000000, 6) as 'Avg Execution Time (s)',
+    ROUND(SUM_TIMER_WAIT/1000000000000, 6) as 'Total Execution Time (s)'
+FROM performance_schema.events_statements_summary_by_digest 
+WHERE DIGEST_TEXT LIKE '%shop_lottery_%'
+ORDER BY AVG_TIMER_WAIT DESC
+LIMIT 10;
 */ 

+ 293 - 116
docs/消费抽奖营销活动_数据表设计.md

@@ -1,64 +1,161 @@
 # 消费抽奖营销活动 - 数据表设计
 
-## 1. 数据表概述
+## 概述
 
-消费抽奖营销活动系统共设计7张核心数据表,涵盖活动管理、奖品配置、用户参与、条件设置等完整业务流程
+消费抽奖营销活动系统包含多个数据表,用于管理抽奖活动的完整生命周期,从活动创建、条件设置、用户参与、机会获取、抽奖执行到奖品发放的全流程管理
 
-## 2. 数据表设计
+## 表结构设计
 
-### 2.1 营销活动主表 (shop_lottery_activity)
+### 1. 核心管理表
 
-存储抽奖活动的基础信息配置。
+#### 1.1 抽奖活动主表 `shop_lottery_activity`
+
+**功能**:存储抽奖活动的基本信息和配置
 
 ```sql
 CREATE TABLE `shop_lottery_activity` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '活动ID',
   `name` varchar(255) NOT NULL COMMENT '活动名称',
   `description` text COMMENT '活动描述',
-  `cover_image` varchar(500) DEFAULT NULL COMMENT '活动封面图片',
   `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '活动类型: 1=消费抽奖',
-  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '活动状态: 0=草稿 1=进行中 2=已结束 3=已暂停',
+  `image` varchar(500) DEFAULT NULL COMMENT '活动图片',
   `start_time` int(11) NOT NULL COMMENT '开始时间',
   `end_time` int(11) NOT NULL COMMENT '结束时间',
-  `lottery_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '开奖方式: 1=即抽即中 2=按时间开奖 3=按人数开奖',
-  `lottery_time` int(11) DEFAULT NULL COMMENT '开奖时间(按时间开奖)',
-  `lottery_people_num` int(11) DEFAULT NULL COMMENT '开奖人数(按人数开奖)',
-  `unlock_by_people` tinyint(1) DEFAULT '0' COMMENT '按参与人数依次解锁奖品: 0=否 1=是',
-  `user_limit_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '适用人群: 1=全部会员 2=会员等级 3=会员标签',
-  `user_limit_value` text COMMENT '适用人群限制值(JSON格式)',
-  `draw_time_enable` tinyint(1) DEFAULT '1' COMMENT '抽奖时间控制: 0=关闭 1=开启',
-  `draw_time_start` varchar(10) DEFAULT NULL COMMENT '每日抽奖开始时间(HH:mm)',
-  `draw_time_end` varchar(10) DEFAULT NULL COMMENT '每日抽奖结束时间(HH:mm)',
-  `person_limit_num` int(11) DEFAULT '1' COMMENT '单人参与次数限制',
-  `total_people_limit` int(11) DEFAULT NULL COMMENT '参与人数上限',
-  `draw_deadline` int(11) DEFAULT NULL COMMENT '抽奖截止时间',
-  `guide_style` tinyint(1) DEFAULT '1' COMMENT '引导样式: 1=默认样式 2=自定义',
-  `guide_image` varchar(500) DEFAULT NULL COMMENT '自定义引导图片',
-  `guide_text` varchar(255) DEFAULT NULL COMMENT '引导文案',
-  `intro_content` text COMMENT '抽奖介绍内容',
-  `total_draw_count` int(11) DEFAULT '0' COMMENT '总抽奖次数',
-  `total_people_count` int(11) DEFAULT '0' COMMENT '参与人数',
-  `total_win_count` int(11) DEFAULT '0' COMMENT '中奖次数',
+  `user_limit_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '用户限制类型: 1=不限制 2=会员等级 3=用户标签',
+  `user_limit_value` text COMMENT '用户限制值(JSON格式)',
+  `daily_limit` int(11) DEFAULT '0' COMMENT '每日抽奖次数限制(0为不限制)',
+  `total_limit` int(11) DEFAULT '0' COMMENT '活动期间总抽奖次数限制(0为不限制)',
+  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态: 0=禁用 1=启用',
+  `sort_order` int(11) DEFAULT '0' COMMENT '排序权重',
+  `admin_id` int(11) DEFAULT NULL COMMENT '创建管理员ID',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
   `createtime` int(11) NOT NULL COMMENT '创建时间',
   `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
   `deletetime` int(11) DEFAULT NULL COMMENT '删除时间',
   PRIMARY KEY (`id`),
-  KEY `idx_status_time` (`status`, `start_time`, `end_time`),
+  KEY `idx_status` (`status`),
+  KEY `idx_time` (`start_time`,`end_time`),
+  KEY `idx_type` (`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='抽奖活动表';
+```
+
+**主要字段说明**:
+- `user_limit_type`: 控制哪些用户可以参与活动
+- `daily_limit`: 用户每日抽奖次数限制
+- `total_limit`: 用户在整个活动期间的抽奖次数限制
+
+#### 1.2 参与条件表 `shop_lottery_condition`
+
+**功能**:定义用户获得抽奖机会的条件
+
+```sql
+CREATE TABLE `shop_lottery_condition` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '条件ID',
+  `activity_id` int(11) NOT NULL COMMENT '活动ID',
+  `name` varchar(255) NOT NULL COMMENT '条件名称',
+  `type` tinyint(1) NOT NULL COMMENT '条件类型: 1=购买指定商品 2=单笔订单消费满额 3=单次充值满额 4=活动期间累计消费满额',
+  `condition_value` decimal(10,2) DEFAULT NULL COMMENT '条件值(金额数值)',
+  `goods_ids` text COMMENT '商品ID列表(JSON格式,type=1时使用)',
+  `goods_rule` tinyint(1) DEFAULT '1' COMMENT '商品规则: 1=指定商品参与 2=指定商品不可参与',
+  `reward_times` int(11) NOT NULL DEFAULT '1' COMMENT '满足条件获得的抽奖次数',
+  `is_repeatable` tinyint(1) DEFAULT '0' COMMENT '是否可重复获得: 0=否 1=是',
+  `description` varchar(500) DEFAULT NULL COMMENT '条件描述',
+  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态: 0=禁用 1=启用',
+  `createtime` int(11) NOT NULL COMMENT '创建时间',
+  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_activity_id` (`activity_id`),
   KEY `idx_type` (`type`),
-  KEY `idx_createtime` (`createtime`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营销活动主表';
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='抽奖活动参与条件表';
+```
+
+**条件类型说明**:
+- `type=1`: 购买指定商品,配合 `goods_ids` 和 `goods_rule` 使用
+- `type=2`: 单笔订单消费满额,使用 `condition_value` 设置金额门槛
+- `type=3`: 单次充值满额,使用 `condition_value` 设置充值门槛
+- `type=4`: 活动期间累计消费满额,使用 `condition_value` 设置累计门槛
+
+### 2. 用户参与表
+
+#### 2.1 用户抽奖机会表 `shop_lottery_user_chance`
+
+**功能**:记录用户在各个活动中的抽奖机会统计
+
+```sql
+CREATE TABLE `shop_lottery_user_chance` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+  `activity_id` int(11) NOT NULL COMMENT '活动ID',
+  `user_id` int(11) NOT NULL COMMENT '用户ID',
+  `total_chances` int(11) NOT NULL DEFAULT '0' COMMENT '总获得机会次数',
+  `used_chances` int(11) NOT NULL DEFAULT '0' COMMENT '已使用机会次数',
+  `remain_chances` int(11) NOT NULL DEFAULT '0' COMMENT '剩余机会次数',
+  `last_get_time` int(11) DEFAULT NULL COMMENT '最后获得机会时间',
+  `last_use_time` int(11) DEFAULT NULL COMMENT '最后使用机会时间',
+  `get_detail` text COMMENT '获得详情(JSON格式, 保留字段)',
+  `createtime` int(11) NOT NULL COMMENT '创建时间',
+  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uniq_activity_user` (`activity_id`,`user_id`),
+  KEY `idx_user_id` (`user_id`),
+  KEY `idx_remain_chances` (`remain_chances`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户抽奖机会表';
 ```
 
-### 2.2 抽奖奖品表 (shop_lottery_prize)
+**注意事项**:
+- `get_detail` 字段保留但不再使用,新系统使用独立的记录表
+- `remain_chances` 字段用于快速查询用户是否还有抽奖机会
 
-存储抽奖活动的奖品配置信息。
+#### 2.2 用户抽奖机会获取记录表 `shop_lottery_user_chance_record` (新增)
+
+**功能**:详细记录用户每次获得抽奖机会的具体信息
+
+```sql
+CREATE TABLE `shop_lottery_user_chance_record` (
+  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID',
+  `activity_id` int(11) NOT NULL COMMENT '活动ID',
+  `user_id` int(11) NOT NULL COMMENT '用户ID',
+  `get_type` tinyint(1) NOT NULL COMMENT '获取类型: 1=购买指定商品 2=单笔订单消费满额 3=单次充值满额 4=活动期间累计消费满额 5=管理员赠送',
+  `chances` int(11) NOT NULL DEFAULT '1' COMMENT '获得机会次数',
+  `condition_id` int(11) DEFAULT NULL COMMENT '条件ID(关联lottery_condition表)',
+  `condition_value` decimal(10,2) DEFAULT NULL COMMENT '条件值(金额或商品ID)',
+  `order_id` int(11) DEFAULT NULL COMMENT '订单ID(订单触发时)',
+  `recharge_amount` decimal(10,2) DEFAULT NULL COMMENT '充值金额(充值触发时)',
+  `admin_id` int(11) DEFAULT NULL COMMENT '管理员ID(管理员赠送时)',
+  `reason` varchar(255) DEFAULT NULL COMMENT '赠送原因(管理员赠送时)',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
+  `get_time` int(11) NOT NULL COMMENT '获得时间',
+  `createtime` int(11) NOT NULL COMMENT '创建时间',
+  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_activity_user` (`activity_id`,`user_id`),
+  KEY `idx_get_type` (`get_type`),
+  KEY `idx_get_time` (`get_time`),
+  KEY `idx_condition_id` (`condition_id`),
+  KEY `idx_order_id` (`order_id`),
+  KEY `idx_admin_id` (`admin_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户抽奖机会获取记录表';
+```
+
+**获取类型说明**:
+- `get_type=1`: 购买指定商品获得,记录 `condition_id` 和 `order_id`
+- `get_type=2`: 单笔订单消费满额获得,记录 `condition_id`、`condition_value` 和 `order_id`
+- `get_type=3`: 单次充值满额获得,记录 `condition_id`、`condition_value` 和 `recharge_amount`
+- `get_type=4`: 累计消费满额获得,记录 `condition_id` 和 `condition_value`
+- `get_type=5`: 管理员赠送,记录 `admin_id`、`reason` 和 `remark`
+
+### 3. 奖品管理表
+
+#### 3.1 抽奖奖品表 `shop_lottery_prize`
+
+**功能**:管理活动中的奖品配置和库存
 
 ```sql
 CREATE TABLE `shop_lottery_prize` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '奖品ID',
   `activity_id` int(11) NOT NULL COMMENT '活动ID',
   `name` varchar(255) NOT NULL COMMENT '奖品名称',
-  `type` tinyint(1) NOT NULL COMMENT '奖品类型: 1=未中奖 2=实物奖品 3=优惠券 4=红包 5=兑换码 6=商城奖品',
+  `type` tinyint(1) NOT NULL COMMENT '奖品类型: 1=未中奖 2=实物奖品 3=积分 4=余额 5=优惠券 6=红包 7=兑换码 8=商城奖品',
   `image` varchar(500) DEFAULT NULL COMMENT '奖品图片',
   `description` text COMMENT '奖品描述',
   `win_prompt` varchar(255) DEFAULT NULL COMMENT '中奖提示语',
@@ -87,34 +184,21 @@ CREATE TABLE `shop_lottery_prize` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='抽奖奖品表';
 ```
 
-### 2.3 参与条件表 (shop_lottery_condition)
+**奖品类型说明**:
+- `type=1`: 未中奖(谢谢参与)
+- `type=2`: 实物奖品(需要填写收货地址)
+- `type=3`: 积分奖励(直接加到用户积分)
+- `type=4`: 余额奖励(直接加到用户余额)
+- `type=5`: 优惠券(关联优惠券系统)
+- `type=6`: 红包(指定金额的红包)
+- `type=7`: 兑换码(提供兑换码给用户)
+- `type=8`: 商城奖品(关联商城商品)
 
-存储活动的参与条件配置。
+### 4. 抽奖执行表
 
-```sql
-CREATE TABLE `shop_lottery_condition` (
-  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '条件ID',
-  `activity_id` int(11) NOT NULL COMMENT '活动ID',
-  `type` tinyint(1) NOT NULL COMMENT '条件类型: 1=购买指定商品 2=单笔订单消费满额 3=单次充值满额 4=活动期间累计消费满额',
-  `condition_value` decimal(10,2) DEFAULT NULL COMMENT '条件值(金额)',
-  `goods_ids` text COMMENT '商品ID列表(JSON格式)',
-  `goods_rule` tinyint(1) DEFAULT '1' COMMENT '商品规则: 1=指定商品参与 2=指定商品不可参与',
-  `reward_times` int(11) DEFAULT '1' COMMENT '满足条件奖励抽奖次数',
-  `is_repeatable` tinyint(1) DEFAULT '0' COMMENT '是否可重复获得: 0=否 1=是',
-  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态: 0=禁用 1=启用',
-  `createtime` int(11) NOT NULL COMMENT '创建时间',
-  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
-  `deletetime` int(11) DEFAULT NULL COMMENT '删除时间',
-  PRIMARY KEY (`id`),
-  KEY `idx_activity_id` (`activity_id`),
-  KEY `idx_type` (`type`),
-  KEY `idx_status` (`status`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='参与条件表';
-```
-
-### 2.4 用户抽奖记录表 (shop_lottery_draw_record)
+#### 4.1 用户抽奖记录表 `shop_lottery_draw_record`
 
-记录用户的抽奖参与历史。
+**功能**:记录用户每次抽奖的详细信息
 
 ```sql
 CREATE TABLE `shop_lottery_draw_record` (
@@ -133,7 +217,7 @@ CREATE TABLE `shop_lottery_draw_record` (
   `remark` varchar(500) DEFAULT NULL COMMENT '备注',
   `createtime` int(11) NOT NULL COMMENT '创建时间',
   PRIMARY KEY (`id`),
-  UNIQUE KEY `uniq_activity_user_order` (`activity_id`, `user_id`, `trigger_order_id`),
+  UNIQUE KEY `uniq_activity_user_order` (`activity_id`,`user_id`,`trigger_order_id`),
   KEY `idx_activity_id` (`activity_id`),
   KEY `idx_user_id` (`user_id`),
   KEY `idx_is_win` (`is_win`),
@@ -142,9 +226,13 @@ CREATE TABLE `shop_lottery_draw_record` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户抽奖记录表';
 ```
 
-### 2.5 中奖记录表 (shop_lottery_win_record)
+**注意事项**:
+- `trigger_type` 字段用于后台统计和分析,与机会获取类型保持一致
+- 唯一索引防止同一订单重复抽奖
 
-记录用户的中奖详细信息和发放状态。
+#### 4.2 中奖记录表 `shop_lottery_win_record`
+
+**功能**:管理用户中奖后的奖品发放流程
 
 ```sql
 CREATE TABLE `shop_lottery_win_record` (
@@ -180,34 +268,17 @@ CREATE TABLE `shop_lottery_win_record` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='中奖记录表';
 ```
 
-### 2.6 用户抽奖机会表 (shop_lottery_user_chance)
+**发放状态说明**:
+- `deliver_status=0`: 待发放(刚中奖,等待处理)
+- `deliver_status=1`: 已发放(奖品已成功发放给用户)
+- `deliver_status=2`: 发放失败(发放过程中出现错误)
+- `deliver_status=3`: 已取消(管理员取消发放)
 
-记录用户获得的抽奖机会和使用情况。
+### 5. 统计分析表
 
-```sql
-CREATE TABLE `shop_lottery_user_chance` (
-  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '机会ID',
-  `activity_id` int(11) NOT NULL COMMENT '活动ID',
-  `user_id` int(11) NOT NULL COMMENT '用户ID',
-  `total_chances` int(11) NOT NULL DEFAULT '0' COMMENT '总获得次数',
-  `used_chances` int(11) NOT NULL DEFAULT '0' COMMENT '已使用次数',
-  `remain_chances` int(11) NOT NULL DEFAULT '0' COMMENT '剩余次数',
-  `last_get_time` int(11) DEFAULT NULL COMMENT '最后获得时间',
-  `last_use_time` int(11) DEFAULT NULL COMMENT '最后使用时间',
-  `get_detail` text COMMENT '获得详情(JSON格式)',
-  `createtime` int(11) NOT NULL COMMENT '创建时间',
-  `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
-  PRIMARY KEY (`id`),
-  UNIQUE KEY `uniq_activity_user` (`activity_id`, `user_id`),
-  KEY `idx_activity_id` (`activity_id`),
-  KEY `idx_user_id` (`user_id`),
-  KEY `idx_remain_chances` (`remain_chances`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户抽奖机会表';
-```
-
-### 2.7 活动统计表 (shop_lottery_statistics)
+#### 5.1 活动统计表 `shop_lottery_statistics`
 
-存储活动的统计数据,用于报表分析。
+**功能**:按日统计活动的各项数据,用于分析活动效果
 
 ```sql
 CREATE TABLE `shop_lottery_statistics` (
@@ -226,47 +297,153 @@ CREATE TABLE `shop_lottery_statistics` (
   `createtime` int(11) NOT NULL COMMENT '创建时间',
   `updatetime` int(11) DEFAULT NULL COMMENT '更新时间',
   PRIMARY KEY (`id`),
-  UNIQUE KEY `uniq_activity_date` (`activity_id`, `stat_date`),
+  UNIQUE KEY `uniq_activity_date` (`activity_id`,`stat_date`),
   KEY `idx_activity_id` (`activity_id`),
   KEY `idx_stat_date` (`stat_date`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动统计表';
 ```
 
-## 3. 索引设计说明
+**统计字段说明**:
+- `new_participants`: 当日新增参与用户数
+- `total_participants`: 截止当日的累计参与用户数
+- `win_rate`: 当日中奖率(中奖次数/抽奖次数)
+- `conversion_rate`: 转化率(参与人数/触发人数)
 
-### 3.1 主要索引
-- **联合索引**: 按查询频率设计联合索引,提高查询性能
-- **唯一索引**: 防止重复数据,保证数据完整性
-- **普通索引**: 优化常用查询条件
+## 表关系说明
 
-### 3.2 性能优化
-- 时间字段使用int类型存储时间戳,提高查询效率
-- JSON字段存储复杂数据结构,减少关联查询
-- 适当的字段长度设置,节省存储空间
+### 主要关联关系
 
-## 4. 数据关系说明
+1. **活动中心关联**:
+   - `shop_lottery_activity` ← `shop_lottery_condition` (一对多)
+   - `shop_lottery_activity` ← `shop_lottery_prize` (一对多)
+   - `shop_lottery_activity` ← `shop_lottery_user_chance` (一对多)
 
-### 4.1 主要关联关系
-```
-shop_lottery_activity (1) ----< (N) shop_lottery_prize
-shop_lottery_activity (1) ----< (N) shop_lottery_condition  
-shop_lottery_activity (1) ----< (N) shop_lottery_draw_record
-shop_lottery_draw_record (1) ----< (1) shop_lottery_win_record
-shop_lottery_activity (1) ----< (N) shop_lottery_user_chance
-shop_lottery_activity (1) ----< (N) shop_lottery_statistics
-```
+2. **用户参与关联**:
+   - `shop_lottery_user_chance` ← `shop_lottery_user_chance_record` (一对多)
+   - `shop_lottery_condition` ← `shop_lottery_user_chance_record` (一对多)
+
+3. **抽奖执行关联**:
+   - `shop_lottery_draw_record` → `shop_lottery_win_record` (一对一)
+   - `shop_lottery_prize` ← `shop_lottery_draw_record` (一对多)
+
+4. **外部系统关联**:
+   - `shop_lottery_user_chance_record.order_id` → `order.id`
+   - `shop_lottery_user_chance_record.admin_id` → `admin.id`
+   - `shop_lottery_prize.goods_id` → `goods.id`
+
+### 数据流转说明
+
+1. **机会获取流程**:
+   ```
+   订单/充值事件 → 检查条件 → 创建chance_record → 更新user_chance
+   ```
+
+2. **抽奖执行流程**:
+   ```
+   用户抽奖 → 创建draw_record → 中奖时创建win_record → 发放奖品
+   ```
+
+3. **统计数据流程**:
+   ```
+   各种操作记录 → 定时统计任务 → 生成statistics记录
+   ```
+
+## 索引设计
+
+### 核心查询索引
+
+1. **用户相关查询**:
+   - `idx_activity_user` on `shop_lottery_user_chance`
+   - `idx_activity_user` on `shop_lottery_user_chance_record`
+   - `idx_user_id` on 各个用户相关表
+
+2. **时间相关查询**:
+   - `idx_time` on `shop_lottery_activity`
+   - `idx_get_time` on `shop_lottery_user_chance_record`
+   - `idx_draw_time` on `shop_lottery_draw_record`
+
+3. **状态查询索引**:
+   - `idx_status` on 各状态字段
+   - `idx_is_win` on `shop_lottery_draw_record`
+   - `idx_deliver_status` on `shop_lottery_win_record`
+
+4. **业务逻辑索引**:
+   - `uniq_activity_user` 防止重复记录
+   - `uniq_activity_date` 防止重复统计
+   - `uniq_draw_record` 确保一次抽奖一条中奖记录
+
+## 性能优化建议
+
+### 查询优化
+
+1. **分页查询**:所有列表查询都应支持分页
+2. **索引覆盖**:常用查询字段建立联合索引
+3. **避免全表扫描**:where条件必须包含索引字段
+
+### 数据清理
+
+1. **历史数据归档**:定期归档过期活动数据
+2. **日志清理**:清理过期的统计和日志数据
+3. **冗余清理**:定期清理无用的记录
+
+### 并发处理
+
+1. **乐观锁**:奖品库存使用乐观锁更新
+2. **事务管理**:关键操作使用数据库事务
+3. **缓存策略**:热点数据使用Redis缓存
+
+## 数据完整性约束
+
+### 外键约束
+
+虽然表结构中没有显式的外键约束,但应在应用层维护以下引用完整性:
+
+1. `condition.activity_id` → `activity.id`
+2. `user_chance.activity_id` → `activity.id`
+3. `user_chance_record.condition_id` → `condition.id`
+4. `draw_record.prize_id` → `prize.id`
+
+### 业务规则约束
+
+1. **库存一致性**:奖品的 `remain_stock` = `total_stock` - `win_count`
+2. **机会一致性**:用户的 `remain_chances` = `total_chances` - `used_chances`
+3. **时间逻辑性**:活动时间、获得时间、抽奖时间的逻辑关系
+4. **概率合理性**:所有奖品概率之和应该等于100%
+
+### 数据验证
+
+1. **输入验证**:所有用户输入都要进行格式和范围验证
+2. **业务验证**:关键业务逻辑要进行合理性验证
+3. **一致性检查**:定期进行数据一致性检查和修复
+
+## 安全考虑
+
+### 数据安全
+
+1. **敏感信息加密**:用户手机号、地址等敏感信息加密存储
+2. **操作日志**:记录所有重要操作的日志
+3. **权限控制**:不同角色的数据访问权限控制
+
+### 业务安全
+
+1. **防刷机制**:防止恶意刷抽奖机会
+2. **概率验证**:定期验证抽奖概率的准确性
+3. **库存保护**:防止超发奖品
+
+## 监控指标
+
+### 核心业务指标
+
+1. **参与度指标**:日活用户、参与率、转化率
+2. **抽奖指标**:抽奖次数、中奖率、奖品发放率
+3. **成本指标**:奖品成本、ROI、用户价值
 
-### 4.2 外键约束
-建议在应用层面维护数据完整性,不设置数据库外键约束,提高性能和灵活性。
+### 技术性能指标
 
-## 5. 数据安全与备份
+1. **响应时间**:各接口的平均响应时间
+2. **错误率**:接口错误率、业务逻辑错误率
+3. **并发量**:峰值并发处理能力
 
-### 5.1 敏感数据处理
-- 用户个人信息加密存储
-- 中奖记录数据定期备份
-- 关键操作记录审计日志
+## 总结
 
-### 5.2 数据清理策略
-- 过期活动数据归档
-- 统计数据定期汇总
-- 日志数据按策略清理 
+本数据表设计涵盖了消费抽奖营销活动的完整生命周期,通过合理的表结构设计和索引优化,能够支持大规模的营销活动运营。新增的用户抽奖机会获取记录表替代了原有的JSON字段存储,提高了查询性能和数据完整性,为后续的数据分析和业务优化提供了良好的基础。 

+ 300 - 150
docs/消费抽奖营销活动_表字段说明.md

@@ -1,6 +1,6 @@
 # 消费抽奖营销活动 - 表字段详细说明
 
-## 1. 营销活动主表 (shop_lottery_activity)
+## 1. 抽奖活动主表 (shop_lottery_activity)
 
 ### 1.1 基础信息字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
@@ -8,83 +8,181 @@
 | id | int(11) | 活动ID,主键 | 1 | 自增主键 |
 | name | varchar(255) | 活动名称 | "双十一消费抽奖" | 最多24个字符,前端限制 |
 | description | text | 活动描述 | "购买满200元即可参与抽奖" | 富文本内容 |
-| cover_image | varchar(500) | 活动封面图片 | "/uploads/activity/cover.jpg" | 相对路径 |
+| image | varchar(500) | 活动图片 | "/uploads/activity/cover.jpg" | 相对路径 |
 | type | tinyint(1) | 活动类型 | 1 | 1=消费抽奖,预留扩展 |
 
 ### 1.2 状态控制字段
 | 字段名 | 类型 | 说明 | 枚举值 | 备注 |
 |--------|------|------|--------|------|
-| status | tinyint(1) | 活动状态 | 0=草稿<br>1=进行中<br>2=已结束<br>3=已暂停 | 控制活动生命周期 |
+| status | tinyint(1) | 活动状态 | 0=禁用<br>1=启用 | 控制活动是否可用 |
 | start_time | int(11) | 开始时间 | 1640995200 | Unix时间戳 |
 | end_time | int(11) | 结束时间 | 1641081600 | Unix时间戳 |
 
-### 1.3 开奖设置字段
-| 字段名 | 类型 | 说明 | 枚举值 | 备注 |
-|--------|------|------|--------|------|
-| lottery_type | tinyint(1) | 开奖方式 | 1=即抽即中<br>2=按时间开奖<br>3=按人数开奖 | 核心业务逻辑 |
-| lottery_time | int(11) | 开奖时间 | 1641081600 | 仅按时间开奖时使用 |
-| lottery_people_num | int(11) | 开奖人数 | 100 | 仅按人数开奖时使用 |
-| unlock_by_people | tinyint(1) | 按参与人数依次解锁奖品 | 0=否,1=是 | 配合按时间开奖使用 |
-
-### 1.4 用户群体控制字段
+### 1.3 用户群体控制字段
 | 字段名 | 类型 | 说明 | 枚举值 | JSON格式示例 |
 |--------|------|------|--------|------------|
-| user_limit_type | tinyint(1) | 适用人群类型 | 1=全部会员<br>2=会员等级<br>3=会员标签 | - |
-| user_limit_value | text | 适用人群限制值 | JSON格式 | `{"levels":[1,2,3]}`<br>`{"tags":["vip","gold"]}` |
+| user_limit_type | tinyint(1) | 用户限制类型 | 1=不限制<br>2=会员等级<br>3=用户标签 | - |
+| user_limit_value | text | 用户限制值 | JSON格式 | `{"levels":[1,2,3]}`<br>`{"tags":["vip","gold"]}` |
+
+### 1.4 抽奖次数限制字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| daily_limit | int(11) | 每日抽奖次数限制 | 3 | 0为不限制 |
+| total_limit | int(11) | 活动期间总抽奖次数限制 | 10 | 0为不限制 |
+
+### 1.5 管理字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| sort_order | int(11) | 排序权重 | 100 | 数值越大越靠前 |
+| admin_id | int(11) | 创建管理员ID | 1 | 记录创建者 |
+| remark | varchar(500) | 备注 | "双11活动" | 管理员备注 |
+
+### 1.6 时间字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| createtime | int(11) | 创建时间 | 1640995200 | Unix时间戳 |
+| updatetime | int(11) | 更新时间 | 1640995200 | Unix时间戳 |
+| deletetime | int(11) | 删除时间 | NULL | 软删除时间戳 |
+
+## 2. 抽奖活动参与条件表 (shop_lottery_condition)
+
+### 2.1 基础字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| id | int(11) | 条件ID,主键 | 1 | 自增主键 |
+| activity_id | int(11) | 关联活动ID | 1 | 外键关联 |
+| name | varchar(255) | 条件名称 | "单笔订单消费满100元" | 便于管理识别 |
+| type | tinyint(1) | 条件类型 | 2 | 见下方类型说明 |
+
+### 2.2 条件类型枚举
+| 类型值 | 类型名称 | 说明 | 使用字段 |
+|--------|----------|------|----------|
+| 1 | 购买指定商品 | 购买特定商品获得抽奖机会 | goods_ids, goods_rule |
+| 2 | 单笔订单消费满额 | 单笔订单达到金额 | condition_value |
+| 3 | 单次充值满额 | 单次充值达到金额 | condition_value |
+| 4 | 活动期间累计消费满额 | 活动期间累计消费 | condition_value |
+
+### 2.3 条件配置字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| condition_value | decimal(10,2) | 条件值(金额数值) | 200.00 | 消费/充值门槛 |
+| goods_ids | text | 商品ID列表 | `[1,2,3]` | JSON格式,type=1时使用 |
+| goods_rule | tinyint(1) | 商品规则 | 1 | 1=指定商品参与<br>2=指定商品不可参与 |
+| reward_times | int(11) | 满足条件获得的抽奖次数 | 1 | 满足条件获得次数 |
+| is_repeatable | tinyint(1) | 是否可重复获得 | 0 | 0=否,1=是 |
+| description | varchar(500) | 条件描述 | "购买指定商品可获得抽奖机会" | 详细说明 |
 
-### 1.5 抽奖时间控制字段
+### 2.4 状态和时间字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| draw_time_enable | tinyint(1) | 抽奖时间控制 | 1 | 0=关闭,1=开启 |
-| draw_time_start | varchar(10) | 每日抽奖开始时间 | "09:00" | HH:mm格式 |
-| draw_time_end | varchar(10) | 每日抽奖结束时间 | "22:00" | HH:mm格式 |
+| status | tinyint(1) | 状态 | 1 | 0=禁用,1=启用 |
+| createtime | int(11) | 创建时间 | 1640995200 | Unix时间戳 |
+| updatetime | int(11) | 更新时间 | 1640995200 | Unix时间戳 |
+
+## 3. 用户抽奖机会表 (shop_lottery_user_chance)
 
-### 1.6 参与限制字段
+### 3.1 基础字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| person_limit_num | int(11) | 单人参与次数限制 | 3 | 每人最多抽奖次数 |
-| total_people_limit | int(11) | 参与人数上限 | 1000 | 活动总人数限制 |
-| draw_deadline | int(11) | 抽奖截止时间 | 1641081600 | Unix时间戳 |
+| id | int(11) | 记录ID,主键 | 1 | 自增主键 |
+| activity_id | int(11) | 活动ID | 1 | 外键关联 |
+| user_id | int(11) | 用户ID | 123 | 外键关联 |
 
-### 1.7 引导样式字段
+### 3.2 机会统计字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| total_chances | int(11) | 总获得机会次数 | 5 | 累计获得的抽奖机会 |
+| used_chances | int(11) | 已使用机会次数 | 2 | 已经抽过奖的次数 |
+| remain_chances | int(11) | 剩余机会次数 | 3 | 可以继续抽奖的次数 |
+
+### 3.3 时间记录字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| last_get_time | int(11) | 最后获得机会时间 | 1640995200 | 最后一次获得机会的时间 |
+| last_use_time | int(11) | 最后使用机会时间 | 1640995300 | 最后一次抽奖的时间 |
+
+### 3.4 兼容性字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| get_detail | text | 获得详情(JSON格式, 保留字段) | `[]` | 保留但不使用,已迁移到独立表 |
+
+### 3.5 时间字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| createtime | int(11) | 创建时间 | 1640995200 | Unix时间戳 |
+| updatetime | int(11) | 更新时间 | 1640995200 | Unix时间戳 |
+
+## 4. 用户抽奖机会获取记录表 (shop_lottery_user_chance_record) 【新增】
+
+### 4.1 基础字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| id | int(11) | 记录ID,主键 | 1 | 自增主键 |
+| activity_id | int(11) | 活动ID | 1 | 外键关联 |
+| user_id | int(11) | 用户ID | 123 | 外键关联 |
+
+### 4.2 获取类型字段
 | 字段名 | 类型 | 说明 | 枚举值 | 备注 |
 |--------|------|------|--------|------|
-| guide_style | tinyint(1) | 引导样式 | 1=默认样式<br>2=自定义 | 活动入口展示样式 |
-| guide_image | varchar(500) | 自定义引导图片 | "/uploads/guide.jpg" | 自定义时使用 |
-| guide_text | varchar(255) | 引导文案 | "快来参与抽奖吧!" | 自定义引导文案 |
-| intro_content | text | 抽奖介绍内容 | "活动规则说明..." | 富文本内容 |
+| get_type | tinyint(1) | 获取类型 | 1=购买指定商品<br>2=单笔订单消费满额<br>3=单次充值满额<br>4=活动期间累计消费满额<br>5=管理员赠送 | 与条件类型对应,额外增加管理员赠送 |
+
+### 4.3 获取信息字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| chances | int(11) | 获得机会次数 | 1 | 本次获得的抽奖机会数 |
+| condition_id | int(11) | 条件ID | 1 | 关联lottery_condition表 |
+| condition_value | decimal(10,2) | 条件值 | 100.00 | 金额或商品ID |
+
+### 4.4 订单相关字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| order_id | int(11) | 订单ID | 789 | 订单触发时记录 |
 
-### 1.8 统计字段
+### 4.5 充值相关字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| total_draw_count | int(11) | 总抽奖次数 | 500 | 实时统计 |
-| total_people_count | int(11) | 参与人数 | 200 | 去重统计 |
-| total_win_count | int(11) | 中奖次数 | 50 | 中奖统计 |
+| recharge_amount | decimal(10,2) | 充值金额 | 50.00 | 充值触发时记录 |
 
-## 2. 抽奖奖品表 (shop_lottery_prize)
+### 4.6 管理员操作字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| admin_id | int(11) | 管理员ID | 1 | 管理员赠送时记录 |
+| reason | varchar(255) | 赠送原因 | "用户反馈奖励" | 管理员赠送时的原因 |
+| remark | varchar(500) | 备注信息 | "补偿用户" | 额外备注信息 |
 
-### 2.1 基础信息字段
+### 4.7 时间字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| get_time | int(11) | 获得时间 | 1640995200 | 获得机会的时间 |
+| createtime | int(11) | 创建时间 | 1640995200 | Unix时间戳 |
+| updatetime | int(11) | 更新时间 | 1640995200 | Unix时间戳 |
+
+## 5. 抽奖奖品表 (shop_lottery_prize)
+
+### 5.1 基础信息字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
 | id | int(11) | 奖品ID,主键 | 1 | 自增主键 |
 | activity_id | int(11) | 关联活动ID | 1 | 外键关联 |
-| name | varchar(255) | 奖品名称 | "iPhone 14" | 奖品展示名称 |
+| name | varchar(255) | 奖品名称 | "iPhone 15" | 奖品展示名称 |
 | type | tinyint(1) | 奖品类型 | 2 | 见下方类型说明 |
 | image | varchar(500) | 奖品图片 | "/uploads/prize.jpg" | 相对路径 |
 | description | text | 奖品描述 | "最新款iPhone..." | 详细描述 |
-| win_prompt | varchar(255) | 中奖提示语 | "恭喜您获得iPhone 14!" | 中奖时显示 |
+| win_prompt | varchar(255) | 中奖提示语 | "恭喜您获得iPhone 15!" | 中奖时显示 |
 
-### 2.2 奖品类型枚举
+### 5.2 奖品类型枚举
 | 类型值 | 类型名称 | 说明 | 相关字段 |
 |--------|----------|------|----------|
 | 1 | 未中奖 | 谢谢参与 | - |
 | 2 | 实物奖品 | 需要邮寄的实物 | deliver_type |
-| 3 | 优惠券 | 系统优惠券 | coupon_id |
-| 4 | 红包 | 微信红包 | amount |
-| 5 | 兑换码 | 第三方兑换码 | exchange_codes |
-| 6 | 商城奖品 | 商城商品 | goods_id, goods_sku_id |
-
-### 2.3 概率和库存字段
+| 3 | 积分 | 用户积分奖励 | amount |
+| 4 | 余额 | 用户余额奖励 | amount |
+| 5 | 优惠券 | 系统优惠券 | coupon_id |
+| 6 | 红包 | 微信红包 | amount |
+| 7 | 兑换码 | 第三方兑换码 | exchange_codes |
+| 8 | 商城奖品 | 商城商品 | goods_id, goods_sku_id |
+
+### 5.3 概率和库存字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
 | probability | decimal(5,2) | 中奖概率(%) | 5.50 | 0.00-100.00 |
@@ -93,55 +191,37 @@
 | win_count | int(11) | 已中奖数量 | 5 | 统计字段 |
 | sort_order | int(11) | 排序权重 | 100 | 数值越大越靠前 |
 
-### 2.4 发放设置字段
+### 5.4 发放设置字段
 | 字段名 | 类型 | 说明 | 枚举值 | 备注 |
 |--------|------|------|--------|------|
 | deliver_type | tinyint(1) | 发放方式 | 1=自动发放<br>2=手动发放 | 影响发放流程 |
 | unlock_people_num | int(11) | 解锁所需人数 | 50 | 按人数解锁奖品 |
 
-### 2.5 关联字段
+### 5.5 关联字段
 | 字段名 | 类型 | 说明 | 示例值 | 使用条件 |
 |--------|------|------|--------|----------|
 | goods_id | int(11) | 关联商品ID | 123 | 商城奖品时使用 |
 | goods_sku_id | int(11) | 关联商品SKU ID | 456 | 商城奖品时使用 |
 | coupon_id | int(11) | 关联优惠券ID | 789 | 优惠券奖品时使用 |
-| amount | decimal(10,2) | 奖品金额 | 50.00 | 红包时使用 |
+| amount | decimal(10,2) | 奖品金额 | 50.00 | 红包/积分/余额时使用 |
 
-### 2.6 兑换码字段
+### 5.6 兑换码字段
 | 字段名 | 类型 | 说明 | JSON格式示例 | 备注 |
 |--------|------|------|------------|------|
 | exchange_codes | text | 兑换码列表 | `["CODE001","CODE002"]` | 批量导入的兑换码 |
 | used_codes | text | 已使用兑换码 | `["CODE001"]` | 已发放的兑换码 |
 
-## 3. 参与条件表 (shop_lottery_condition)
-
-### 3.1 基础字段
+### 5.7 状态和时间字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| id | int(11) | 条件ID,主键 | 1 | 自增主键 |
-| activity_id | int(11) | 关联活动ID | 1 | 外键关联 |
-| type | tinyint(1) | 条件类型 | 2 | 见下方类型说明 |
-
-### 3.2 条件类型枚举
-| 类型值 | 类型名称 | 说明 | 使用字段 |
-|--------|----------|------|----------|
-| 1 | 购买指定商品 | 购买特定商品获得抽奖机会 | goods_ids, goods_rule |
-| 2 | 单笔订单消费满额 | 单笔订单达到金额 | condition_value |
-| 3 | 单次充值满额 | 单次充值达到金额 | condition_value |
-| 4 | 活动期间累计消费满额 | 活动期间累计消费 | condition_value |
-
-### 3.3 条件配置字段
-| 字段名 | 类型 | 说明 | 示例值 | 备注 |
-|--------|------|------|--------|------|
-| condition_value | decimal(10,2) | 条件值(金额) | 200.00 | 消费/充值门槛 |
-| goods_ids | text | 商品ID列表 | `[1,2,3]` | JSON格式 |
-| goods_rule | tinyint(1) | 商品规则 | 1 | 1=指定商品参与<br>2=指定商品不可参与 |
-| reward_times | int(11) | 奖励抽奖次数 | 1 | 满足条件获得次数 |
-| is_repeatable | tinyint(1) | 是否可重复获得 | 0 | 0=否,1=是 |
+| status | tinyint(1) | 状态 | 1 | 0=禁用,1=启用 |
+| createtime | int(11) | 创建时间 | 1640995200 | Unix时间戳 |
+| updatetime | int(11) | 更新时间 | 1640995200 | Unix时间戳 |
+| deletetime | int(11) | 删除时间 | NULL | 软删除时间戳 |
 
-## 4. 用户抽奖记录表 (shop_lottery_draw_record)
+## 6. 用户抽奖记录表 (shop_lottery_draw_record)
 
-### 4.1 基础字段
+### 6.1 基础字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
 | id | int(11) | 记录ID,主键 | 1 | 自增主键 |
@@ -150,29 +230,34 @@
 | prize_id | int(11) | 奖品ID | 5 | 抽中的奖品 |
 | is_win | tinyint(1) | 是否中奖 | 1 | 0=未中奖,1=中奖 |
 
-### 4.2 触发信息字段
+### 6.2 触发信息字段
 | 字段名 | 类型 | 说明 | 枚举值 | 备注 |
 |--------|------|------|--------|------|
-| trigger_type | tinyint(1) | 触发类型 | 1=购买商品<br>2=订单消费<br>3=充值<br>4=累计消费 | 抽奖触发条件 |
+| trigger_type | tinyint(1) | 触发类型 | 1=购买商品<br>2=订单消费<br>3=充值<br>4=累计消费 | 抽奖触发条件(用于统计分析) |
 | trigger_order_id | int(11) | 触发订单ID | 789 | 关联订单 |
 | trigger_amount | decimal(10,2) | 触发金额 | 200.00 | 触发的金额 |
 
-### 4.3 抽奖环境字段
+### 6.3 抽奖环境字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
 | draw_ip | varchar(50) | 抽奖IP | "192.168.1.1" | 用户IP地址 |
 | draw_time | int(11) | 抽奖时间 | 1641081600 | Unix时间戳 |
 | device_info | varchar(255) | 设备信息 | "iPhone 13" | 用户设备信息 |
 
-### 4.4 扩展字段
+### 6.4 扩展字段
 | 字段名 | 类型 | 说明 | JSON格式示例 | 备注 |
 |--------|------|------|------------|------|
 | win_info | text | 中奖信息 | `{"prize_name":"iPhone","amount":8999}` | 中奖详情 |
 | remark | varchar(500) | 备注 | "正常抽奖" | 管理员备注 |
 
-## 5. 中奖记录表 (shop_lottery_win_record)
+### 6.5 时间字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| createtime | int(11) | 创建时间 | 1640995200 | Unix时间戳 |
+
+## 7. 中奖记录表 (shop_lottery_win_record)
 
-### 5.1 基础字段
+### 7.1 基础字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
 | id | int(11) | 中奖记录ID,主键 | 1 | 自增主键 |
@@ -180,120 +265,185 @@
 | activity_id | int(11) | 活动ID | 1 | 冗余字段,便于查询 |
 | user_id | int(11) | 用户ID | 123 | 冗余字段 |
 | prize_id | int(11) | 奖品ID | 5 | 冗余字段 |
-| prize_name | varchar(255) | 奖品名称 | "iPhone 14" | 冗余字段,防止删除 |
+| prize_name | varchar(255) | 奖品名称 | "iPhone 15" | 冗余字段,防止删除 |
 | prize_type | tinyint(1) | 奖品类型 | 2 | 冗余字段 |
 
-### 5.2 发放状态字段
+### 7.2 发放状态字段
 | 字段名 | 类型 | 说明 | 枚举值 | 备注 |
 |--------|------|------|--------|------|
 | deliver_status | tinyint(1) | 发放状态 | 0=待发放<br>1=已发放<br>2=发放失败<br>3=已取消 | 核心状态字段 |
 | deliver_time | int(11) | 发放时间 | 1641081600 | Unix时间戳 |
 | fail_reason | varchar(500) | 发放失败原因 | "库存不足" | 失败时记录 |
 
-### 5.3 收货信息字段
+### 7.3 收货信息字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
 | receiver_name | varchar(100) | 收货人姓名 | "张三" | 实物奖品时必填 |
 | receiver_mobile | varchar(20) | 收货人手机 | "13800138000" | 实物奖品时必填 |
 | receiver_address | varchar(500) | 收货地址 | "北京市..." | 实物奖品时必填 |
 
-### 5.4 物流信息字段
+### 7.4 物流信息字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| express_company | varchar(100) | 快递公司 | "顺丰快递" | 实物奖品发货 |
-| express_number | varchar(100) | 快递单号 | "SF1234567890" | 物流跟踪 |
+| express_company | varchar(100) | 快递公司 | "顺丰速运" | 实物奖品发货后填写 |
+| express_number | varchar(100) | 快递单号 | "SF1234567890" | 实物奖品发货后填写 |
 
-### 5.5 兑换码字段
+### 7.5 兑换码字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| exchange_code | varchar(100) | 兑换码 | "CODE001" | 兑换码奖品 |
-| code_used_time | int(11) | 兑换码使用时间 | 1641081600 | 使用时记录 |
+| exchange_code | varchar(100) | 兑换码 | "CODE001" | 兑换码奖品时使用 |
+| code_used_time | int(11) | 兑换码使用时间 | 1641081600 | 用户使用兑换码的时间 |
 
-### 5.6 扩展字段
+### 7.6 扩展字段
 | 字段名 | 类型 | 说明 | JSON格式示例 | 备注 |
 |--------|------|------|------------|------|
-| prize_value | text | 奖品信息 | `{"amount":50,"coupon_id":123}` | 不同类型奖品的详细信息 |
-| deliver_info | text | 发放信息 | `{"wechat_order_id":"wx123"}` | 发放详细信息 |
+| prize_value | text | 奖品信息 | `{"amount":50,"type":"balance"}` | 奖品详细信息 |
+| deliver_info | text | 发放信息 | `{"transaction_id":"T123456"}` | 发放过程的详细信息 |
 | admin_remark | varchar(500) | 管理员备注 | "手动发放" | 管理员操作备注 |
 
-## 6. 用户抽奖机会表 (shop_lottery_user_chance)
-
-### 6.1 基础字段
+### 7.7 时间字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| id | int(11) | 机会ID,主键 | 1 | 自增主键 |
-| activity_id | int(11) | 活动ID | 1 | 外键关联 |
-| user_id | int(11) | 用户ID | 123 | 外键关联 |
+| createtime | int(11) | 创建时间 | 1640995200 | Unix时间戳 |
+| updatetime | int(11) | 更新时间 | 1640995200 | Unix时间戳 |
 
-### 6.2 次数统计字段
-| 字段名 | 类型 | 说明 | 示例值 | 备注 |
-|--------|------|------|--------|------|
-| total_chances | int(11) | 总获得次数 | 5 | 累计获得的抽奖机会 |
-| used_chances | int(11) | 已使用次数 | 3 | 已经消耗的次数 |
-| remain_chances | int(11) | 剩余次数 | 2 | 当前可用次数 |
+## 8. 活动统计表 (shop_lottery_statistics)
 
-### 6.3 时间记录字段
+### 8.1 基础字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| last_get_time | int(11) | 最后获得时间 | 1641081600 | 最近一次获得机会的时间 |
-| last_use_time | int(11) | 最后使用时间 | 1641081800 | 最近一次使用机会的时间 |
-
-### 6.4 扩展字段
-| 字段名 | 类型 | 说明 | JSON格式示例 | 备注 |
-|--------|------|------|------------|------|
-| get_detail | text | 获得详情 | `[{"time":1641081600,"type":"order","amount":200}]` | 获得机会的详细记录 |
-
-## 7. 活动统计表 (shop_lottery_statistics)
+| id | int(11) | 统计ID,主键 | 1 | 自增主键 |
+| activity_id | int(11) | 活动ID | 1 | 外键关联 |
+| stat_date | date | 统计日期 | 2024-11-01 | 按日统计 |
 
-### 7.1 基础字段
+### 8.2 参与统计字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| id | int(11) | 统计ID,主键 | 1 | 自增主键 |
-| activity_id | int(11) | 活动ID | 1 | 外键关联 |
-| stat_date | date | 统计日期 | 2024-01-01 | 按日统计 |
+| new_participants | int(11) | 新增参与人数 | 50 | 当日新增参与用户数 |
+| total_participants | int(11) | 累计参与人数 | 200 | 截止当日的累计参与用户数 |
 
-### 7.2 参与统计字段
+### 8.3 抽奖统计字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| new_participants | int(11) | 新增参与人数 | 50 | 当日新增 |
-| total_participants | int(11) | 累计参与人数 | 200 | 累计参与 |
 | draw_count | int(11) | 抽奖次数 | 100 | 当日抽奖总次数 |
-| win_count | int(11) | 中奖次数 | 20 | 当日中奖次数 |
-| win_rate | decimal(5,2) | 中奖率(%) | 20.00 | 中奖率统计 |
+| win_count | int(11) | 中奖次数 | 15 | 当日中奖次数 |
+| win_rate | decimal(5,2) | 中奖率(%) | 15.00 | 当日中奖率(中奖次数/抽奖次数) |
 
-### 7.3 成本统计字段
+### 8.4 成本统计字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| prize_cost | decimal(10,2) | 奖品成本 | 1000.00 | 当日奖品成本 |
+| prize_cost | decimal(10,2) | 奖品成本 | 1500.00 | 当日奖品总成本 |
 
-### 7.4 转化统计字段
+### 8.5 业务统计字段
 | 字段名 | 类型 | 说明 | 示例值 | 备注 |
 |--------|------|------|--------|------|
-| trigger_orders | int(11) | 触发订单数 | 80 | 因活动产生的订单 |
-| trigger_amount | decimal(10,2) | 触发订单金额 | 16000.00 | 订单总金额 |
-| conversion_rate | decimal(5,2) | 转化率(%) | 40.00 | 参与转化为订单的比率 |
-
-## 8. 字段设计原则说明
-
-### 8.1 数据类型选择
-- **int(11)**: 用于ID、时间戳、数量等字段
-- **tinyint(1)**: 用于状态、类型等枚举字段,节省空间
-- **decimal(10,2)**: 用于金额字段,保证精度
-- **varchar**: 用于文本字段,根据实际需要设置长度
-- **text**: 用于长文本或JSON数据
-
-### 8.2 索引设计
-- **主键索引**: 所有表都有自增主键
-- **唯一索引**: 防止重复数据
-- **联合索引**: 优化常用查询组合
-- **普通索引**: 提高单字段查询性能
+| trigger_orders | int(11) | 触发订单数 | 80 | 当日触发抽奖的订单数 |
+| trigger_amount | decimal(10,2) | 触发订单金额 | 20000.00 | 当日触发抽奖的订单总金额 |
+| conversion_rate | decimal(5,2) | 转化率(%) | 62.50 | 转化率(参与人数/触发人数) |
 
-### 8.3 冗余字段
-为了查询性能,适当增加冗余字段:
-- 中奖记录表冗余奖品信息,防止奖品删除后数据丢失
-- 统计表冗余计算结果,避免实时计算
-
-### 8.4 扩展性考虑
-- 使用JSON字段存储灵活配置
-- 预留status字段便于功能扩展
-- 软删除机制保证数据安全 
+### 8.6 时间字段
+| 字段名 | 类型 | 说明 | 示例值 | 备注 |
+|--------|------|------|--------|------|
+| createtime | int(11) | 创建时间 | 1640995200 | Unix时间戳 |
+| updatetime | int(11) | 更新时间 | 1640995200 | Unix时间戳 |
+
+## 9. 字段使用说明
+
+### 9.1 JSON字段格式说明
+
+#### user_limit_value 字段
+```json
+// 会员等级限制
+{
+  "levels": [1, 2, 3]
+}
+
+// 用户标签限制
+{
+  "tags": ["vip", "gold", "premium"]
+}
+```
+
+#### goods_ids 字段
+```json
+// 商品ID列表
+[1, 2, 3, 4, 5]
+```
+
+#### exchange_codes 字段
+```json
+// 兑换码列表
+["CODE001", "CODE002", "CODE003"]
+```
+
+#### win_info 字段
+```json
+// 中奖信息
+{
+  "prize_name": "iPhone 15",
+  "prize_type": 2,
+  "amount": 8999.00,
+  "code": "CODE001"
+}
+```
+
+### 9.2 索引使用说明
+
+#### 复合索引
+- `uniq_activity_user`: 防止用户在同一活动中重复创建机会记录
+- `uniq_activity_date`: 防止同一活动同一天重复统计
+- `idx_activity_user`: 优化按活动和用户查询
+- `idx_user_activity_time`: 优化用户时间范围查询
+
+#### 单字段索引
+- `idx_status`: 优化状态查询
+- `idx_get_type`: 优化按获取类型查询
+- `idx_is_win`: 优化中奖查询
+- `idx_deliver_status`: 优化发放状态查询
+
+### 9.3 数据完整性说明
+
+#### 库存一致性
+- `total_stock` = `remain_stock` + `win_count`
+- 每次中奖时同步更新这三个字段
+
+#### 机会一致性
+- `total_chances` = `used_chances` + `remain_chances`
+- 获取机会时增加 `total_chances` 和 `remain_chances`
+- 使用机会时增加 `used_chances`,减少 `remain_chances`
+
+#### 记录一致性
+- `shop_lottery_user_chance.total_chances` = SUM(`shop_lottery_user_chance_record.chances`)
+- 通过记录表可以完整追溯用户机会来源
+
+### 9.4 业务逻辑约束
+
+#### 时间逻辑
+- `start_time` < `end_time`
+- `get_time` 在活动时间范围内
+- `draw_time` 在活动时间范围内
+
+#### 概率逻辑
+- 所有奖品的概率之和应该等于100%
+- 未中奖奖品的概率 = 100% - 其他奖品概率之和
+
+#### 库存逻辑
+- `remain_stock` >= 0
+- 发放奖品时检查库存是否充足
+
+## 10. 扩展说明
+
+### 10.1 预留扩展字段
+- `deletetime`: 支持软删除功能
+- `remark`: 管理员备注,便于运营管理
+- `sort_order`: 排序权重,支持自定义排序
+
+### 10.2 性能优化建议
+- 定期清理过期活动数据
+- 对热点查询建立合适的复合索引
+- 统计数据可以考虑缓存优化
+
+### 10.3 数据安全建议
+- 敏感信息(手机号、地址)加密存储
+- 重要操作记录操作日志
+- 定期备份中奖记录等重要数据 

+ 111 - 83
public/assets/js/backend/shop/order.js

@@ -48,28 +48,64 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {
                             field: 'user_id',
                             title: __('User_id'),
-                            operate: false, formatter: Table.api.formatter.search
+                            operate: 'LIKE',
+                            visible: false, // 默认不显示,但可以用于搜索
                         },
                         {
-                            field: 'receiver',
-                            title: __('Receiver'),
-                            operate: 'LIKE'
+                            field: 'user.username',
+                            title: __('User'),
+                            operate: 'LIKE',
+                            formatter: function (value, row, index) {
+                                // 显示用户头像和用户名
+                                var avatar = row.user && row.user.avatar ? row.user.avatar : '/assets/img/avatar.png';
+                                var username = row.user && row.user.username ? row.user.username : '游客';
+                                var userId = row.user_id || '';
+                                
+                                // 处理头像URL
+                                var avatarUrl = avatar;
+                                if (avatar && !avatar.startsWith('http') && !avatar.startsWith('//')) {
+                                    avatarUrl = Fast.api.cdnurl ? Fast.api.cdnurl(avatar) : avatar;
+                                }
+                                
+                                return '<div style="display:flex;align-items:center;">' + 
+                                       '<img src="' + avatarUrl + '" style="width:40px;height:40px;border-radius:50%;margin-right:10px;" />' +
+                                       '<div>' +
+                                       '<div style="color:#337ab7;font-weight:bold;">' + username + '</div>' +
+                                       '<div style="color:#999;font-size:12px;">ID: ' + userId + '</div>' +
+                                       '</div>' +
+                                       '</div>';
+                            }
                         },
                         {
-                            field: 'address',
-                            title: __('Address'),
-                            operate: 'LIKE'
+                            field: 'order_address.consignee',
+                            title: __('Consignee'),
+                            operate: 'LIKE',
+                            formatter: function (value, row, index) {
+                                return row.order_address ? row.order_address.consignee : '';
+                            }
                         },
                         {
-                            field: 'zipcode',
-                            title: __('Zipcode'),
+                            field: 'order_address.mobile',
+                            title: __('Mobile'),
                             operate: 'LIKE',
-                            visible: false,
+                            formatter: function (value, row, index) {
+                                return row.order_address ? row.order_address.mobile : '';
+                            }
                         },
                         {
-                            field: 'mobile',
-                            title: __('Mobile'),
-                            operate: 'LIKE'
+                            field: 'order_address.address',
+                            title: __('Address'),
+                            operate: 'LIKE',
+                            visible: false, // 可以用于搜索但默认不显示
+                            formatter: function (value, row, index) {
+                                if (!row.order_address) return '';
+                                var address = '';
+                                if (row.order_address.province_name) address += row.order_address.province_name;
+                                if (row.order_address.city_name) address += row.order_address.city_name;
+                                if (row.order_address.district_name) address += row.order_address.district_name;
+                                if (row.order_address.address) address += row.order_address.address;
+                                return address;
+                            }
                         },
                         {
                             field: 'amount',
@@ -77,85 +113,65 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                             operate: 'BETWEEN'
                         },
                         {
-                            field: 'goodsprice',
-                            title: __('Goodsprice'),
+                            field: 'goods_price',
+                            title: __('Goods_price'),
                             operate: 'BETWEEN'
                         },
                         {
-                            field: 'discount',
-                            title: __('Discount'),
+                            field: 'discount_fee',
+                            title: __('Discount_fee'),
                             operate: 'BETWEEN'
                         },
                         {
-                            field: 'shippingfee',
-                            title: __('Shippingfee'),
+                            field: 'express_fee',
+                            title: __('Express_fee'),
                             operate: 'BETWEEN'
                         },
                         {
-                            field: 'saleamount',
-                            title: __('Saleamount'),
+                            field: 'order_amount',
+                            title: __('Order_amount'),
                             operate: 'BETWEEN'
                         },
                         {
-                            field: 'payamount',
-                            title: __('Payamount'),
+                            field: 'pay_amount',
+                            title: __('Pay_amount'),
                             operate: 'BETWEEN'
                         },
                         {
-                            field: 'paytype',
-                            title: __('Paytype'),
-                            operate: 'LIKE'
-                        },
-                        {
-                            field: 'method',
-                            title: __('Method'),
-                            operate: 'LIKE'
+                            field: 'pay_type',
+                            title: __('Pay_type'),
+                            searchList: Controller.api.parseConfigJson('payTypeList'),
+                            operate: 'LIKE',
+                            formatter: function (value, row) {
+                                return row.pay_type_text || value;
+                            }
                         },
                         {
-                            field: 'transactionid',
-                            title: __('Transactionid'),
-                            operate: 'LIKE'
+                            field: 'pay_mode',
+                            title: __('Pay_mode'),
+                            searchList: Controller.api.parseConfigJson('payModeList'),
+                            operate: 'LIKE',
+                            formatter: function (value, row) {
+                                return row.pay_mode_text || value;
+                            }
                         },
                         {
-                            field: 'expressname',
-                            title: __('Expressname'),
+                            field: 'express_name',
+                            title: __('Express_name'),
                             operate: 'LIKE'
                         },
                         {
-                            field: 'expressno',
-                            title: __('Expressno'),
+                            field: 'express_no',
+                            title: __('Express_no'),
                             operate: 'LIKE'
                         },
                         {
-                            field: 'orderstate',
-                            title: __('Orderstate'),
-                            searchList: {
-                                "0": __('Orderstate 0'),
-                                "1": __('Orderstate 1'),
-                                "2": __('Orderstate 2'),
-                                "3": __('Orderstate 3'),
-                                "4": __('Orderstate 4'),
-                            },
-                            formatter: Table.api.formatter.normal
-                        },
-                        {
-                            field: 'shippingstate',
-                            title: __('Shippingstate'),
-                            searchList: {
-                                "0": __('Shippingstate 0'),
-                                "1": __('Shippingstate 1'),
-                                "2": __('Shippingstate 2')
-                            },
-                            formatter: Table.api.formatter.normal
-                        },
-                        {
-                            field: 'paystate',
-                            title: __('Paystate'),
-                            searchList: {
-                                "0": __('Paystate 0'),
-                                "1": __('Paystate 1')
-                            },
-                            formatter: Table.api.formatter.normal
+                            field: 'order_status',
+                            title: __('Order_status'),
+                            searchList: Controller.api.parseConfigJson('orderStatusList'),
+                            formatter: function (value, row) {
+                                return row.order_status_text || value;
+                            }
                         },
                         {
                             field: 'memo',
@@ -165,10 +181,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         {
                             field: 'status',
                             title: __('Status'),
-                            searchList: {
-                                "normal": __('Status normal'),
-                                "hidden": __('Status hidden')
-                            },
+                            searchList: Controller.api.parseConfigJson('statusList'),
                             formatter: Table.api.formatter.status
                         },
                         {
@@ -188,48 +201,48 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                             formatter: Table.api.formatter.datetime
                         },
                         {
-                            field: 'expiretime',
-                            title: __('Expiretime'),
+                            field: 'expire_time',
+                            title: __('Expire_time'),
                             operate: 'RANGE',
                             addclass: 'datetimerange',
                             autocomplete: false,
                             formatter: Table.api.formatter.datetime
                         },
                         {
-                            field: 'paytime',
-                            title: __('Paytime'),
+                            field: 'pay_time',
+                            title: __('Pay_time'),
                             operate: 'RANGE',
                             addclass: 'datetimerange',
                             autocomplete: false,
                             formatter: Table.api.formatter.datetime
                         },
                         {
-                            field: 'refundtime',
-                            title: __('Refundtime'),
+                            field: 'refund_time',
+                            title: __('Refund_time'),
                             operate: 'RANGE',
                             addclass: 'datetimerange',
                             autocomplete: false,
                             formatter: Table.api.formatter.datetime
                         },
                         {
-                            field: 'shippingtime',
-                            title: __('Shippingtime'),
+                            field: 'shipping_time',
+                            title: __('Shipping_time'),
                             operate: 'RANGE',
                             addclass: 'datetimerange',
                             autocomplete: false,
                             formatter: Table.api.formatter.datetime
                         },
                         {
-                            field: 'receivetime',
-                            title: __('Receivetime'),
+                            field: 'receive_time',
+                            title: __('Receive_time'),
                             operate: 'RANGE',
                             addclass: 'datetimerange',
                             autocomplete: false,
                             formatter: Table.api.formatter.datetime
                         },
                         {
-                            field: 'canceltime',
-                            title: __('Canceltime'),
+                            field: 'cancel_time',
+                            title: __('Cancel_time'),
                             operate: 'RANGE',
                             addclass: 'datetimerange',
                             autocomplete: false,
@@ -623,6 +636,21 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
             Controller.api.bindevent();
         },
         api: {
+            // 解析Config中的JSON字符串的辅助函数
+            parseConfigJson: function(configKey, defaultValue) {
+                var configValue = Config[configKey] || defaultValue || {};
+                
+                // 如果是字符串,尝试解析JSON
+                if (typeof configValue === 'string') {
+                    try {
+                        return JSON.parse(configValue);
+                    } catch (e) {
+                        return defaultValue || {};
+                    }
+                }
+                
+                return configValue;
+            },
             bindevent: function () {
                 Form.api.bindevent($("form[role=form]"));
             },

+ 122 - 0
public/assets/js/backend/user/share.js

@@ -0,0 +1,122 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+    var Controller = {
+        index: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    index_url: 'user/share/index' + location.search,
+                    add_url: 'user/share/add',
+                    edit_url: 'user/share/edit',
+                    del_url: 'user/share/del',
+                    multi_url: 'user/share/multi',
+                    import_url: 'user/share/import',
+                    table: 'shop_user_share',
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: $.fn.bootstrapTable.defaults.extend.index_url,
+                pk: 'id',
+                sortName: 'id',
+                fixedColumns: true,
+                fixedRightNumber: 1,
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {field: 'spm', title: __('Spm'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+                        {field: 'user_id', title: __('User_id')},
+                        {field: 'share_id', title: __('Share_id')},
+                        {field: 'page', title: __('Page'), operate: 'LIKE'},
+                        {field: 'query', title: __('Query'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+                        {field: 'platform', title: __('Platform'), operate: 'LIKE'},
+                        {field: 'mode', title: __('Mode'), operate: 'LIKE'},
+                        {field: 'status', title: __('Status')},
+                        {field: 'createtime', title: __('Createtime'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
+                        {field: 'updatetime', title: __('Updatetime'), operate:'RANGE', addclass:'datetimerange', autocomplete:false, formatter: Table.api.formatter.datetime},
+                        {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+        recyclebin: function () {
+            // 初始化表格参数配置
+            Table.api.init({
+                extend: {
+                    'dragsort_url': ''
+                }
+            });
+
+            var table = $("#table");
+
+            // 初始化表格
+            table.bootstrapTable({
+                url: 'user/share/recyclebin' + location.search,
+                pk: 'id',
+                sortName: 'id',
+                columns: [
+                    [
+                        {checkbox: true},
+                        {field: 'id', title: __('Id')},
+                        {
+                            field: 'deletetime',
+                            title: __('Deletetime'),
+                            operate: 'RANGE',
+                            addclass: 'datetimerange',
+                            formatter: Table.api.formatter.datetime
+                        },
+                        {
+                            field: 'operate',
+                            width: '140px',
+                            title: __('Operate'),
+                            table: table,
+                            events: Table.api.events.operate,
+                            buttons: [
+                                {
+                                    name: 'Restore',
+                                    text: __('Restore'),
+                                    classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
+                                    icon: 'fa fa-rotate-left',
+                                    url: 'user/share/restore',
+                                    refresh: true
+                                },
+                                {
+                                    name: 'Destroy',
+                                    text: __('Destroy'),
+                                    classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
+                                    icon: 'fa fa-times',
+                                    url: 'user/share/destroy',
+                                    refresh: true
+                                }
+                            ],
+                            formatter: Table.api.formatter.operate
+                        }
+                    ]
+                ]
+            });
+
+            // 为表格绑定事件
+            Table.api.bindevent(table);
+        },
+
+        add: function () {
+            Controller.api.bindevent();
+        },
+        edit: function () {
+            Controller.api.bindevent();
+        },
+        api: {
+            bindevent: function () {
+                Form.api.bindevent($("form[role=form]"));
+            }
+        }
+    };
+    return Controller;
+});