Ver Fonte

wes 多宝福袋

wesmiler há 3 anos atrás
pai
commit
8ffacd1689

+ 19 - 0
app/admin/view/box/open_box_action/index_back.html

@@ -0,0 +1,19 @@
+
+<div class="layuimini-container">
+    <div class="layuimini-main">
+        总共预约盒子个数:{$cur_info.total_num},总共盒子金额:{$cur_info.total_money}<br>
+        这一期总共预约盒子个数:{$cur_info.total_curnum},这一期总共盒子金额:{$cur_info.total_curmoney}<br>
+        <div class="layui-switch">
+            <button class="layui-btn layui-btn-normal layui-btn-sm" data-open="system.menu/setting" data-title="批量编辑" data-full="false"><i class="fa fa-plus"></i> 批量编辑</button>
+        </div>
+        <table id="currentTable" class="layui-table layui-hide"
+               lay-filter="currentTable">
+        </table>
+    </div>
+</div>
+
+<script type="text/html" id="toolbar">
+    <button class="layui-btn layui-btn-sm layuimini-btn-primary" data-treetable-refresh><i class="fa fa-refresh"></i> </button>
+    <button class="layui-btn layui-btn-normal layui-btn-sm" data-open="system.menu/setting" data-title="批量编辑" data-full="false"><i class="fa fa-plus"></i> 批量编辑</button>
+    <button class="layui-btn layui-btn-normal layui-btn-sm" data-open="system.menu/setting" data-title="批量编辑" data-full="false"><i class="fa fa-plus"></i> 批量编辑</button>
+</script>

+ 45 - 0
app/admin/view/box/open_box_action/openBox.html

@@ -0,0 +1,45 @@
+<div class="layuimini-container">
+    <form id="app-form" class="layui-form layuimini-form">
+        <input type="hidden" name="id" value="{$id}">
+        <div class="layui-form-item">
+            <label class="layui-form-label">普通</label>
+            <div class="layui-input-block">
+                <input type="text" name="box10" class="layui-input" lay-verify="required" lay-reqtext="" placeholder="请输入普通个数" value="">
+                <tip>填写普通个数比如 1。</tip>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">稀有</label>
+            <div class="layui-input-block">
+                <input type="text" name="box20" class="layui-input" lay-verify="required" lay-reqtext="" placeholder="请输入稀有个数" value="">
+                <tip>填写稀有个数比如 1。</tip>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">史诗</label>
+            <div class="layui-input-block">
+                <input type="text" name="box30" class="layui-input" lay-verify="required" lay-reqtext="" placeholder="请输入史诗个数" value="">
+                <tip>填写史诗个数比如 1。</tip>
+            </div>
+        </div>
+
+        <div class="layui-form-item">
+            <label class="layui-form-label">传说</label>
+            <div class="layui-input-block">
+                <input type="text" name="box40" class="layui-input" lay-verify="required" lay-reqtext="" placeholder="请输入传说个数" value="">
+                <tip>填写传说个数比如 1。</tip>
+            </div>
+        </div>
+
+
+
+        <div class="hr-line"></div>
+        <div class="layui-form-item text-center">
+            <button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
+            <button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
+        </div>
+
+    </form>
+</div>

+ 232 - 0
app/api/command/AutoSubmeter.php

@@ -0,0 +1,232 @@
+<?php
+declare (strict_types = 1);
+
+namespace app\api\command;
+
+use app\common\model\SubmeterModel;
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+use think\facade\Db;
+use utils\RedisCache;
+
+/**
+ * 自动分表备份 by wes 每天凌晨 0点后运行,运行两次间隔10分钟
+ * Class AutoSubmeter
+ * @package app\api\command
+ */
+class AutoSubmeter extends Command
+{
+    protected $cacheTime = 86400; // 一天
+    protected $tables = [
+        // time 备份分表周期,save_time 数据保存时间天,time_field 时间字段
+        'money_log'=>['time'=>2, 'save_time'=> 14,'time_field'=>'create_at'],
+        'box_record'=>['time'=>2, 'save_time'=> 10,'time_field'=>'create_time'],
+        'box_mid_handle'=>['time'=>2, 'save_time'=> 10,'time_field'=>'create_time'],
+        'score_log'=>['time'=>2, 'save_time'=> 10,'time_field'=>'create_at'],
+        'box_handle'=>['time'=>1, 'save_time'=> 0,'time_field'=>'create_time'],
+    ];
+
+    protected function configure()
+    {
+        $this->setName('auto_backup_submeter')
+            ->setDescription('the auto_backup_submeter command');
+    }
+
+    /**
+     * 处理分表
+     * @param Input $input
+     * @param Output $output
+     * @return int
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        if(date('H:i') > '08:00'){
+            echo json_encode(['code'=>500,'msg'=>'不在运行时间段内,请于早上0点~8点执行','date'=>date('Y-m-d H:i:s')], 256)."\n";
+            return false;
+        }
+
+        if(RedisCache::get("caches:submeter:lock")){
+            echo json_encode(['code'=>500,'msg'=>'请不要频繁执行,稍后再试~','date'=>date('Y-m-d H:i:s')], 256)."\n";
+            return false;
+        }
+
+        RedisCache::setnx("caches:submeter:lock", date('Y-m-d H:i:s'), rand(10, 20));
+        Db::startTrans();
+        try {
+            $count = 0;
+            $hasCount = 0;
+            $cacheKey = "caches:submeter:backup:";
+            foreach ($this->tables as $tableName => $item) {
+                $time = isset($item['time']) ? $item['time'] : 0; // 备份周期
+                $saveTime = isset($item['save_time']) ? $item['save_time'] : 0; // 保存时间
+                $timeField = isset($item['time_field']) ? $item['time_field'] : 0; // 时间字段
+                $time = $time ? $time : 2;
+
+                // 验证是否备份
+                $dateTime = strtotime(date('Y-m-d')) - 86400;
+                if($tableName == 'box_handle'){
+                    $index = 'all';
+                    $newTableName = $tableName . '_' . $index;
+                    $date = date('Y-m-d H:i:s', $dateTime);
+
+                    if ($this->checkCatchByTime($tableName, $index, $time)) {
+                        $hasCount++;
+                        RedisCache::set($cacheKey . "{$newTableName}_error", ['data' => $item,'name'=>$newTableName, 'error' => '未到备份时间间隔'], $this->cacheTime);
+                        continue;
+                    }
+
+                    // 复制数据
+                    $prefix = env('database.prefix','db_');
+                    $count1 = Db::name($tableName)->where('is_delete', 2)->whereNotIn('id',Db::name($newTableName)->column('id'))->count();
+                    if(!$count1){
+                        $hasCount++;
+                        RedisCache::set($cacheKey . "{$newTableName}_error", ['sql'=>Db::name($tableName)->getLastSql(),'count'=>$count1,'data' => $item,'name'=>$newTableName, 'error' => '没有数据处理'], $this->cacheTime);
+                        continue;
+                    }
+
+                    $count2 = Db::name($newTableName)->count();
+                    $sql = "insert into `{$prefix}{$newTableName}` (select * from `{$prefix}{$tableName}` where `id` not in (select `id` from `{$prefix}{$newTableName}`) and `is_delete` = 2 and `create_time` < '".date('Y-m-d')."')";
+                    $res = Db::query($sql);
+                    $count3 = Db::name($newTableName)->count();
+                    if (($count3 - $count2) < $count1) {
+                        RedisCache::set($cacheKey."{$newTableName}_error", ['count'=>$count,'sql1'=>$sql,'msg'=> '复制分表数据错误','data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
+                        sr_throw("复制[{$newTableName}]分表数据错误");
+                    }
+
+                    // 清理原表多余数据
+                    Db::name($tableName)->where(['is_delete'=>1])->where('update_time','<',$date)->delete();
+                    $res = Db::name($tableName)->whereIn('id',Db::name($newTableName)->column('id'))->update(['is_delete'=>1,'update_time'=>date('Y-m-d H:i:s')]);
+                    if(!$res){
+                        RedisCache::set($cacheKey."{$newTableName}_error", ['count'=>$count,'sql'=>$sql,'sql1'=>Db::name($tableName)->getLastSql(),'msg'=> '清理备份分表数据错误','data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
+                        sr_throw("清理[{$tableName}]表旧数据错误");
+                    }
+                }else{
+                    $index = date('Ymd', $dateTime);
+                    $newTableName = $tableName . '_' . $index;
+                    if ($this->checkCatchByTime($tableName, $index, $time)) {
+                        $hasCount++;
+                        RedisCache::set($cacheKey . "{$newTableName}_error", ['data' => $item,'name'=>$newTableName, 'error' => '未到备份时间间隔'], $this->cacheTime);
+                        continue;
+                    }
+
+                    if ($this->checkCatch($tableName, $index)) {
+                        $hasCount++;
+                        RedisCache::set($cacheKey . "{$newTableName}_error", ['data' => $item,'name'=>$newTableName, 'error' => '已经处理过'], $this->cacheTime);
+                        continue;
+                    }
+
+                    // 备份处理(1:复制表,2-清除日期间隔数据)
+                    $prefix = env('database.prefix','db_');
+                    $date = date('Y-m-d H:i:s', $dateTime);
+
+                    // 复制表结构
+                    $sql = "create table IF not exists `{$prefix}{$newTableName}` like `{$prefix}{$tableName}`";
+                    Db::query($sql);
+
+                    // 复制数据
+                    $count1 = Db::name($tableName)->count();
+                    $sql1 = "insert ignore `{$prefix}{$newTableName}` select * from `{$prefix}{$tableName}`";
+                    Db::query($sql1);
+                    if ($count1 != Db::name($newTableName)->count()) {
+                        RedisCache::set($cacheKey."{$newTableName}_error", ['sql'=>$sql,'sql1'=>$sql1,'msg'=> '复制分表数据错误','data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
+                        sr_throw("复制备份[{$newTableName}]分表数据错误");
+                    }
+
+                    // 清理原表多余数据
+                    $expiredRow = Db::name($tableName)
+                        ->where($timeField,'<', $date)
+                        ->where(function ($query) use($tableName){
+                            if($tableName == 'money_log'){
+                                $query->whereNotIn('type', [8]);
+                            }
+                        })
+                        ->count();
+
+                    $isDelete = Db::name($tableName)
+                        ->where($timeField,'<', $date)
+                        ->where(function ($query) use($tableName){
+                            if($tableName == 'money_log'){
+                                $query->whereNotIn('type', [8]);
+                            }
+                        })
+                        ->delete();
+                    if($expiredRow && !$isDelete){
+                        RedisCache::set($cacheKey."{$newTableName}_error", ['sql'=>$sql,'msg'=> '清理备份分表数据错误','data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
+                        sr_throw("清理[{$tableName}]表旧数据错误");
+                    }
+                }
+
+
+                $data = [
+                    'table_name'=> $tableName,
+                    'table_index'=> $index,
+                    'update_time'=> date('Y-m-d H:i:s'),
+                    'expired_at'=> $saveTime?date('Y-m-d H:i:s', time() + $saveTime * 24 * 3600):'',
+                    'status'=>1
+                ];
+                if(!SubmeterModel::insertGetId($data)){
+                    RedisCache::set($cacheKey."{$newTableName}_error", ['sql'=>$sql,'msg'=> '处理分表记录错误','log'=>$data,'data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
+                    sr_throw("处理[{$newTableName}]分表记录错误");
+                }
+
+                RedisCache::set($cacheKey."{$newTableName}_success", ['sql'=>$sql,'msg'=> '处理分表备份成功','log'=>$data,'data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
+                $count++;
+            }
+
+            Db::commit();
+            echo json_encode(['code'=>200,'msg'=>"运行成功:共处理{$count}个表备份,{$hasCount}个已备份过",'date'=>date('Y-m-d H:i:s')],256)."\n";
+        }catch (\Exception $exception){
+            Db::rollback();
+            RedisCache::clear("caches:submeter:lock");
+            RedisCache::set("caches:submeter:error", ['msg'=> '运行错误:'.$exception->getMessage(),'trace'=>$exception->getTrace(),'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
+            echo json_encode(['code'=>500,'msg'=>'运行错误:'.$exception->getMessage(),'date'=>date('Y-m-d H:i:s')], 256)."\n";
+        }
+
+        return true;
+    }
+
+    /**
+     * 验证是否备份分表过
+     * @param $tableName 表名
+     * @return bool|mixed
+     */
+    protected function checkCatch($tableName, $index)
+    {
+        $cacheKey = "caches:submeter:check:{$tableName}_{$index}";
+        if(RedisCache::get($cacheKey)){
+            return true;
+        }
+
+        $data = SubmeterModel::where(['table_name'=> $tableName,'table_index'=>$index,'status'=>1])->value('id');
+        if($data){
+            RedisCache::set($cacheKey, $data, rand(10, 20));
+        }
+
+        return $data;
+    }
+
+    /**
+     * 验证是否备份分表过
+     * @param $tableName 表名
+     * @return bool|mixed
+     */
+    protected function checkCatchByTime($tableName, $index, $day)
+    {
+        $cacheKey = "caches:submeter:check:time_{$tableName}_{$index}_{$day}";
+        if(RedisCache::get($cacheKey)){
+//            return true;
+        }
+
+        $date = $day == 1? date('Y-m-d') : date('Y-m-d H:i:s', time() - $day * 24 * 3600);
+        $data = SubmeterModel::where(['table_name'=> $tableName,'status'=>1])
+            ->where('update_time','>=', $date)
+            ->value('id');
+var_dump(SubmeterModel::getLastSql());
+        if($data){
+            RedisCache::set($cacheKey, $data, rand(10, 20));
+        }
+
+        return $data;
+    }
+}

+ 104 - 0
app/api/command/AutoUpdateData.php

@@ -0,0 +1,104 @@
+<?php
+declare (strict_types=1);
+
+namespace app\api\command;
+
+use app\common\model\BoxModel;
+use app\common\model\MoneyLogModel;
+use app\common\model\UserModel;
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+use think\facade\Db;
+use utils\RedisCache;
+
+/**
+ * 自动更新和清除平台基础数据和用户统计 by wes 每天凌晨 2点运行
+ * Class AutoUpdateData
+ * @package app\api\command
+ */
+class AutoUpdateData extends Command
+{
+    protected function configure()
+    {
+        // 每天刷新用户一些用户信息
+        $this->setName('auto_update_data')
+            ->setDescription('the auto_update_data command');
+    }
+
+    /**
+     * 处理
+     * @param Input $input
+     * @param Output $output
+     * @return int
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        $cacheKey = "caches:clearData:".date('Ymd');
+        if(RedisCache::get($cacheKey)){
+            echo json_encode(['code'=>500,'msg'=>'已经运行过,请不要重复运行','date'=>date('Y-m-d H:i:s')], 256)."\n";
+            return false;
+        }
+
+        RedisCache::setnx($cacheKey, date('Y-m-d H:i:s'), 86400);
+        Db::startTrans();
+        try {
+            $this->updateUserData();
+            Db::commit();
+            echo json_encode(['code'=>200,'msg'=>'初始化更新数据成功','date'=>date('Y-m-d H:i:s')], 256)."\n";
+        } catch (\Exception $e) {
+            Db::rollback();
+            RedisCache::clear($cacheKey);
+            echo json_encode(['code'=>500,'msg'=>$e->getMessage(),'date'=>date('Y-m-d H:i:s')], 256)."\n";
+        }
+
+        return true;
+    }
+
+    /**
+     * 处理数据
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function updateUserData()
+    {
+        // 更新用户所有昨日今日数据
+        $list = MoneyLogModel::where('type', 6)
+            ->whereDay('create_at', 'yesterday')
+            ->field(Db::raw('uid,sum(money) as total'))
+            ->group('uid')
+            ->select();
+        $list = $list? $list->toArray() :[];
+        foreach ($list as $key => $val) {
+            UserModel::where('id', $val['uid'])->save(['yesterday_money' => $val['total']]);
+        }
+
+        // 盒子数
+        UserModel::where('today_box', '>', 0)->save(['today_box' => 0]);
+        UserModel::where('today_team_box', '>', 0)->save(['today_team_box' => 0]);
+
+        // 开启下一期 福袋预约
+        $info = BoxModel::where('status', 1)->order('id desc')->find();
+        if ($info) {
+            // 更新旧期数,且只留7天数据
+            BoxModel::where('status', 2)
+                ->where('create_time','<',date('Y-m-d H:i:s', time() - 7*86400))
+                ->delete();;
+            BoxModel::where('status', 1)->save(['status' => 2]);
+            $count = $info['buy_most'] * (1 + env('boxsetting.ONCEDAY_ADD_SCALE') / 100);
+            BoxModel::insert([
+                'buy_most' => intval($count / 10) * 10,
+                'once_buy' => $info['once_buy'],
+                'qi_count' => $info['qi_count'] + 1,
+                'time_set' => $info['time_set'],
+                'box_img' => $info['box_img'],
+                'box_title' => $info['box_title'],
+                'create_time' => sr_getcurtime(time()),
+                'appoint_day' => sr_getcurtime(time(), 'Y-m-d')
+            ]);
+        }
+
+    }
+
+}

+ 45 - 0
app/api/command/AutoUpdateUserLevel.php

@@ -0,0 +1,45 @@
+<?php
+declare (strict_types = 1);
+
+namespace app\api\command;
+
+use app\common\model\SubmeterModel;
+use app\common\service\UserService;
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+use think\facade\Db;
+use utils\RedisCache;
+
+/**
+ * 用户自动升级 by wes 每间隔10分钟运行一次
+ * Class AutoUpdateUserLevel
+ * @package app\api\command
+ */
+class AutoUpdateUserLevel extends Command
+{
+    protected $cacheTime = 86400; // 一天
+
+    protected function configure()
+    {
+        $this->setName('auto_update_user_level')
+            ->setDescription('the auto_update_user_level command');
+    }
+
+    /**
+     * 处理用户自动升级
+     * @param Input $input
+     * @param Output $output
+     * @return int
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        $result = UserService::make()->updateLevel();
+        if(is_array($result)){
+            echo json_encode($result, 256)."\n";
+        }else{
+            echo json_encode(['code'=>500,'msg'=> $result?$result:'处理失败'], 256)."\n";
+        }
+        return true;
+    }
+}

+ 169 - 0
app/api/command/SettleBoxReturn.php

@@ -0,0 +1,169 @@
+<?php
+declare (strict_types = 1);
+
+namespace app\api\command;
+
+use app\common\model\BoxMidHandleModel;
+use app\common\model\MoneyLogModel;
+use app\common\model\ScoreLogModel;
+use app\common\model\SubmeterModel;
+use app\common\model\UserModel;
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+use think\facade\Db;
+use utils\RedisCache;
+
+/**
+ * 空袋结算退回 by wes 每天晚上21点后运行1次
+ * Class SettleBoxReturn
+ * @package app\api\command
+ */
+class SettleBoxReturn extends Command
+{
+    protected $cacheTime = 86400; // 一天
+
+    protected function configure()
+    {
+        $this->setName('settle_box_return')
+            ->setDescription('the settle_box_return command');
+    }
+
+    /**
+     * 处理分表
+     * @param Input $input
+     * @param Output $output
+     * @return int
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        $cacheKey = "caches:boxReturn:".date('YmdHi');
+        if(RedisCache::get($cacheKey.'_lock')){
+            echo json_encode(['code'=>'error','msg'=>'请不要频繁提交,正在结算中稍后再试~','date'=>date('Y-m-d H:i:s')])."\n";
+            return false;
+        }
+
+        RedisCache::setnx($cacheKey.'_lock', date('Y-m-d H:i:s'), rand(5, 10));
+        Db::startTrans();
+        try {
+            $startTime = microtime(true);
+            if($result = $this->settleBoxReturn()){
+                $endTime = microtime(true);
+                $runTime = round(($endTime-$startTime)/1000, 3).'秒';
+                $result['time'] = $runTime;
+                echo json_encode($result, 256)."\n";
+                Db::commit();
+            }
+        } catch (\Exception $e) {
+            Db::rollback();
+            $log = ['msg'=> $e->getMessage(),'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')];
+            RedisCache::set($cacheKey."_fail", $log, 5 * 3600);
+            echo json_encode(['code'=>'error','msg'=>$e->getMessage(),'date'=>date('Y-m-d H:i:s')], 256)."\n";
+        }
+
+        return true;
+    }
+
+    /**
+     * 结算空袋退回
+     */
+    public function settleBoxReturn()
+    {
+        // 空袋盒子
+        $startTime = microtime(true);
+        $date = date('Y-m-d H:i:s');
+        $boxList = BoxMidHandleModel::where('box_settle_time','<=', $date)
+            ->where(['goods_id'=>0,'status'=>1,'is_delete'=>2])
+            ->field('id,h_sn,uid,rid,pay_type')
+            ->order('uid')
+            ->limit(2000)
+            ->select();
+        $boxList = $boxList? $boxList->toArray() : [];
+        if(empty($boxList)){
+            return ['code'=>500,'msg'=>'暂无数据处理','date'=>$date];
+        }
+
+        $scoreCount = $moneyCount = 0;
+        $moneyLogs = $scoreLogs = $catchIds = [];
+        $cacheKey = "caches:boxReturn:".date('YmdHi').'_';
+        foreach ($boxList as $key=> $item){
+            $boxMid = isset($item['id'])? $item['id'] : 0;
+//            $rid = isset($item['rid'])? $item['rid'] : 0;
+            $uid = isset($item['uid'])? $item['uid'] : 0;
+            $payType = isset($item['pay_type'])? $item['pay_type'] : 0;
+            $boxMoney = env('boxsetting.ONE_BOX_PRICE', 288);
+
+            // 退回积分或余额
+            $userInfo = UserModel::where('id', $uid)->field('score,money')->find();
+            $userMoney = isset($userInfo['money'])? floatval($userInfo['money']) : 0;
+            $userScore = isset($userInfo['score'])? intval($userInfo['score']) : 0;
+            if($payType == 1){
+                if(!UserModel::where('id', $uid)->inc('score', intval($boxMoney))->update()){
+                    RedisCache::set($cacheKey."{$uid}:{$boxMid}_error", ['msg'=>"退回用户[{$uid}]积分失败",'box'=> $item,'user'=>$userInfo,'date'=>$date], 7200);
+                    sr_throw("退回用户[{$uid}]积分失败");
+                }
+
+                $log = [
+                    'uid'=>$uid,
+                    'type'=> 2,
+                    'score'=> $boxMoney,
+                    'create_at'=> sr_getcurtime(time()),
+                    'state'=> 1,
+                    'before_score'=> $userScore,
+                    'after_score'=> intval($userScore + $boxMoney),
+                    'from_id'=> $boxMid,
+                    'uid2'=> 0,
+                    'remark'=> "空袋退回"
+                ];
+                $scoreLogs[] = $log;
+                RedisCache::set($cacheKey."score_{$uid}:{$boxMid}_catch", ['msg'=>"退回用户[{$uid}]积分处理",'log'=>$log,'box'=> $item,'user'=>$userInfo,'date'=>$date], 7200);
+                $scoreCount++;
+            }
+            // 退回余额
+            else if($payType == 2){
+                if(!UserModel::where('id', $uid)->inc('money', floatval($boxMoney))->update()){
+                    RedisCache::set($cacheKey."{$uid}:{$boxMid}_error", ['msg'=>"退回用户[{$uid}]余额失败",'box'=> $item,'user'=>$userInfo,'date'=>$date], 7200);
+                    sr_throw("退回用户[{$uid}]余额失败");
+                }
+                $log = [
+                    'uid' => $uid,
+                    'type' => 3,
+                    'money' => $boxMoney,
+                    'create_at' => sr_getcurtime(time()),
+                    'state' => 1,
+                    'before_money' => $userMoney,
+                    'after_money' => floatval($userMoney + $boxMoney),
+                    'from_id' => $boxMid,
+                    'uid2' => 0,
+                    'remark' => "空袋退回"
+                ];
+                $moneyLogs[] = $log;
+                RedisCache::set($cacheKey."money_{$uid}:{$boxMid}_catch", ['msg'=>"退回用户[{$uid}]余额处理",'log'=>$log,'box'=> $item,'user'=>$userInfo,'date'=>$date], 7200);
+                $moneyCount++;
+            }
+
+            $catchIds[] = $boxMid;
+        }
+
+        if($catchIds && !BoxMidHandleModel::whereIn('id', $catchIds)->update(['status'=> 2,'updated_time'=> $date])){
+            sr_throw('处理盒子状态更新错误');
+        }
+
+        if(empty($scoreLogs) && empty($moneyLogs)){
+            sr_throw('没有成功退回的盒子');
+        }
+
+        if($scoreLogs && !ScoreLogModel::insertAll(array_values($scoreLogs))){
+            sr_throw('退还积分处理失败');
+        }
+
+        if($moneyLogs && !MoneyLogModel::insertAll(array_values($moneyLogs))){
+            sr_throw('退还余额处理失败');
+        }
+
+        $endTime = microtime(true);
+        $runTime = round(($endTime-$startTime)/1000, 3).'秒';
+        RedisCache::set($cacheKey."_result", ['total'=> count($boxList),'score'=>count($scoreLogs),'money'=>count($moneyLogs),'time'=> $runTime], 3600);
+        return ['code'=>200, 'msg'=>"成功退还".count($boxList)."个盒子,余额{$moneyCount}个,积分{$scoreCount}个",'time'=>$runTime,'date'=> $date];
+    }
+}

+ 277 - 0
app/api/command/SettleTeamAward.php

@@ -0,0 +1,277 @@
+<?php
+declare (strict_types = 1);
+
+namespace app\api\command;
+
+use app\api\controller\h5\User;
+use app\common\model\BoxHandleModel;
+use app\common\model\MoneyLogModel;
+use app\common\model\SubmeterModel;
+use app\common\model\UserModel;
+use app\common\service\LevelSettingService;
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+use think\facade\Db;
+use utils\RedisCache;
+
+/**
+ * 结算团队收益 by wes 每天晚上21点后运行1次
+ * Class SettleTeamAward
+ * @package app\api\command
+ */
+class SettleTeamAward extends Command
+{
+    protected $cacheTime = 86400; // 一天
+
+    protected function configure()
+    {
+        $this->setName('settle_team_award')
+            ->setDescription('the settle_team_award command');
+    }
+
+    /**
+     * 处理分表
+     * @param Input $input
+     * @param Output $output
+     * @return int
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        $cacheKey = "caches:settleTeamAward:".date('Ymd').":";
+        if(RedisCache::get($cacheKey.'lock')){
+            echo json_encode(['code'=>'error','msg'=>'请不要频繁提交,正在结算中稍后再试~','date'=>date('Y-m-d H:i:s')],256)."\n";
+            return false;
+        }
+
+        if(date('H:i') < '21:00'){
+            echo json_encode(['code'=>'error','msg'=>'未到结算时间,结算时间为每日21:00后~','date'=>date('Y-m-d H:i:s')],256)."\n";
+//            return false;
+        }
+
+        RedisCache::setnx($cacheKey.'lock', date('Y-m-d H:i:s'), rand(30, 60));
+        Db::startTrans();
+        try {
+
+            if($result = $this->settleTeamAward()){
+                echo json_encode($result, 256)."\n";
+                Db::commit();
+            }
+        } catch (\Exception $e) {
+            Db::rollback();
+            $log = ['msg'=> $e->getMessage(),'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')];
+            RedisCache::set($cacheKey."fail", $log, 5 * 3600);
+            RedisCache::clear($cacheKey.'lock');
+            echo json_encode(['code'=>'error','msg'=>$e->getMessage(),'date'=>date('Y-m-d H:i:s')], 256)."\n";
+        }
+
+        return true;
+    }
+
+    /**
+     * 团队奖金结算
+     * @return array
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function settleTeamAward()
+    {
+        // 获取开出的福袋盒子
+        $boxList = BoxHandleModel::alias('a')
+            ->leftJoin('user u','u.id=a.uid')
+            ->where('a.is_team_handle', '0')
+            ->where('a.open_time', '<=', date('Y-m-d H:i:s'))
+            ->field('a.id,a.uid,a.rid,u.pid,u.level,u.path,u.has_fd,u.money')
+            ->order('a.id desc')
+            ->select();
+        $boxList = $boxList? $boxList->toArray() : [];
+        if(empty($boxList)){
+            sr_throw('暂无团队奖金可结算');
+        }
+
+        // 获取等级配置
+        $levelConfig = LevelSettingService::make()->getConfigData(0,1);
+        if(empty($levelConfig)){
+            sr_throw('请先配置等级参数');
+        }
+
+        $date = date('Y-m-d H:i:s');
+        $catchIds = $moneyLogs = []; // 处理的数据
+        $awardCounts = [];
+        $cacheKey = "caches:settleTeamAward:".date('Ymd').":";
+        foreach ($boxList as $k => $val){
+            $boxId = isset($val['id'])? $val['id'] : 0;
+            $uid = isset($val['uid'])? $val['uid'] : 0;
+            $pid = isset($val['pid'])? $val['pid'] : 0;
+            $path = isset($val['path'])? $val['path'] : '';
+            if($pid<=0 || empty($path)){
+                $catchIds[] = $boxId;
+                continue;
+            }
+
+            /* TODO 验证上级是否存在,并且计算直推奖金*/
+            $pInfo = UserModel::where(['id'=> $pid])
+                ->field('id,money,level,pid,path,has_fd,total_appoint_count,store_expire_time')
+                ->find();
+            $pHasFd = isset($pInfo['has_fd'])? intval($pInfo['has_fd']) : 0;
+            $pLevel = isset($pInfo['level'])? intval($pInfo['level']) : 0;
+            $appointCount = isset($pInfo['total_appoint_count'])? intval($pInfo['total_appoint_count']) : 0;
+            $storeExpireTime = isset($pInfo['store_expire_time']) && $pInfo['store_expire_time']? strtotime($pInfo['store_expire_time']) : 0;
+            if($pInfo && $pHasFd){
+                // 计算上级V0以上,当前等级的直推奖金:v1-1元,v2-2元,v3-3元,v4-1元(没分完得)
+                $ztAward = isset($levelConfig[$pLevel]['zt_money'])? floatval($levelConfig[$pLevel]['zt_money']) : 0;
+                if($pLevel>0 && $ztAward>0){
+                    $pMoney = isset($pInfo['money'])? $pInfo['money'] : 0;
+                    // 今日直推奖金
+                    $todayZtMoney = ($appointCount>=0 && $storeExpireTime>=time())? $ztAward : 0;
+                    // 直推奖流水
+                    $log = [
+                        'uid'=> $pid,
+                        'type'=> 5,
+                        'money'=> $ztAward,
+                        'create_at'=> sr_getcurtime(time()),
+                        'state'=> 1,
+                        'before_money'=> $pMoney,
+                        'after_money'=> floatval($pMoney + $ztAward),
+                        'from_id'=> $boxId,
+                        'uid2'=> $uid,
+                        'free_type'=> 0,
+                        'remark'=> '直推1个福袋奖金'.($todayZtMoney>0? ',有今日奖金':'')
+                    ];
+                    $moneyLogs[] = $log;
+                    if(!UserModel::where('id', $pid)->inc('money', $ztAward)->inc('today_money', $todayZtMoney)->update()){
+                        $logData = ['ztAward'=>$ztAward,'data'=> $val,'pInfo'=> $pInfo,'log'=>$log,'msg'=>"直推用户[{$pid}]结算直推奖错误",'date'=> $date];
+                        RedisCache::set($cacheKey."box_{$uid}_{$boxId}:{$pid}_{$pLevel}_error", $logData, 3*3600);
+                        sr_throw("直推用户[{$pid}]结算直推奖错误");
+                    }
+
+                    // 累计当前用户获得的奖金
+                    $awardCounts[$pid] = isset($awardCounts[$pid])? round($awardCounts[$pid]+$ztAward, 2) : $ztAward;
+                }
+            }
+
+            /* TODO 计算团队奖金 */
+            $maxLevel = 0;
+            $awardTotal = 0;
+            $teamMoneys = [];
+            $teamAwardEnd = $sameLevelAwardEnd = 0;  // 是否给完奖金
+            $pIds = explode(',', $path);
+            $pIds = array_reverse($pIds);
+            $ids = implode(',', $pIds);
+            $order = 'field(id,' . $ids . ')';
+            $teamList = UserModel::whereIn('id', $pIds)
+                ->field('id,money,level,has_fd,total_appoint_count,store_expire_time')
+                ->order(Db::raw($order))
+                ->select();
+            $teamList = $teamList? $teamList->toArray() : [];
+            if(!empty($teamList)){
+                foreach($teamList as $key => $item){
+                    // 计算团队奖金
+                    $tId = isset($item['id'])? $item['id'] : 0;
+                    $tLevel = isset($item['level'])? $item['level'] : 0;
+                    $tLastId = isset($teamList[$key-1]['id'])? intval($teamList[$key-1]['id']) : 0;
+                    $tLastLevel = isset($teamList[$key-1]['level'])? intval($teamList[$key-1]['level']) : 0; // 前一个人的等级
+                    $tMoney = isset($item['money'])? $item['money'] : 0;
+                    $tHasFd = isset($item['has_fd'])? $item['has_fd'] : 0;
+                    $tAppointCount = isset($item['total_appoint_count'])? intval($item['total_appoint_count']) : 0;
+                    $tStoreExpireTime = isset($item['store_expire_time']) && !empty($item['store_expire_time'])? strtotime($item['store_expire_time']) : 0;
+
+                    $levelData = isset($levelConfig[$tLevel])? $levelConfig[$tLevel] : [];
+                    if($key == 0){
+                        // 等级大于V0
+                        if($tLevel > 0){
+                            $teamAward = isset($levelData['dynamic_scale'])? round($levelData['dynamic_scale'], 2) : 0;
+                            if($teamAward>0){
+                                $teamMoneys[$tId] = $teamAward;
+                                $awardTotal += $teamAward;
+                                $maxLevel = $tLevel;
+
+                                // V4以及以上等级直接分完后结束,不再往下分
+                                if($tLevel >= 4){
+                                    $teamAwardEnd = true;
+                                }
+                            }
+                        }
+                    }
+                    // 如果未分完,且等级比上一级和当前分到到用户等级都高,则再分剩余奖金
+                    else if(!$teamAwardEnd && $tLevel > $tLastLevel && $tLevel > $maxLevel){
+                        $teamAward = isset($levelData['dynamic_scale'])? floatval($levelData['dynamic_scale']) : 0;
+                        $teamAward = max(0, $teamAward - $awardTotal);
+                        if($teamAward>0){
+                            $teamMoneys[$tId] = $teamAward;
+                            $awardTotal += $teamAward;
+                        }
+                    }
+
+                    /* TODO 计算平级奖金:未分完(金额小于0.1)情况下一直分 */
+                    $sameTeamAward = 0;
+                    if($key>0 && !$sameLevelAwardEnd){
+                        if($tLevel == $tLastLevel){
+                            $sameAward = isset($teamMoneys[$tLastId])? floatval($teamMoneys[$tLastId]) : 0;
+                            $sameTeamAward = round($sameAward * 0.1, 2);
+                            $sameTeamAward = $sameTeamAward>0.1? $sameTeamAward : 0;
+                            if($sameTeamAward>0){
+                                $teamMoneys[$tId] = $sameTeamAward;
+                            }
+                        }
+                    }
+
+                    /* TODO 当前用户最终是否有团队奖金,有且预约过福袋则入账 */
+                    $endTeamAward = isset($teamMoneys[$tId])? floatval($teamMoneys[$tId]) : 0;
+                    if($endTeamAward>0 && $tAppointCount>0){
+                        // 今日团队奖金
+                        $tTodayAward = ($appointCount>=10 && $tStoreExpireTime >= time())? $endTeamAward : 0;
+
+                        // 流水
+                        $log = [
+                            'uid'=> $tId,
+                            'type'=> 7,
+                            'money'=> $endTeamAward,
+                            'create_at'=> sr_getcurtime(time()),
+                            'state'=> 1,
+                            'before_money'=> $tMoney,
+                            'after_money'=> floatval($tMoney + $endTeamAward),
+                            'from_id'=> $boxId,
+                            'uid2'=> $sameTeamAward? $tLastId : 0,
+                            'free_type'=> 0,
+                            'remark'=> ($sameTeamAward? '1个福袋平级奖':'1个福袋团队奖').($tTodayAward>0? ',有今日奖金':'')
+                        ];
+                        $moneyLogs[] = $log;
+                        if(!UserModel::where('id', $tId)->inc('money', $endTeamAward)->inc('today_money', $tTodayAward)->update()){
+                            $logData = ['endAward'=>$endTeamAward,'todayAward'=>$tTodayAward,'data'=> $val,'tInfo'=> $item,'log'=>$log,'msg'=>"用户[{$tId}]结算团队奖错误",'date'=> $date];
+                            RedisCache::set($cacheKey."box_{$uid}_{$boxId}:{$pid}_{$pLevel}_error", $logData, 3*3600);
+                            sr_throw("用户[{$tId}]结算团队奖错误");
+                        }
+
+                        $awardCounts[$tId] = isset($awardCounts[$tId])? round($awardCounts[$tId]+$endTeamAward, 2) : $endTeamAward;
+                    }
+
+                }
+            }
+
+            $catchIds[] = $boxId;
+            $logData = ['teamAwards'=>$teamMoneys,'data'=> $val,'teamAward'=> $awardTotal,'msg'=>"盒子[{$boxId}]团队奖金处理完成",'date'=> $date];
+            RedisCache::set($cacheKey."box_{$uid}_{$boxId}:catch", $logData, 3*3600);
+        }
+
+        if(empty($catchIds)){
+            sr_throw('结算失败,没有结算成功数据');
+        }
+
+        // 更新处理记录状态
+        if(!BoxHandleModel::whereIn('id', $catchIds)->save(['is_team_handle' => 1,'update_time'=> $date])){
+            sr_throw('更新处理数据状态失败');
+        }
+
+        // 写入奖金流水
+        if($moneyLogs && !MoneyLogModel::insertAll($moneyLogs)){
+            sr_throw('处理奖金流水错误');
+        }
+
+        $logData = ['count'=> count($catchIds),'data'=> $val,'awards'=> $awardCounts,'msg'=>"团队奖金结算成功",'date'=> $date];
+        RedisCache::set($cacheKey."result", $logData, 3*3600);
+        return ['code'=>200, 'msg'=> "团队奖金结算成功,累计结算记录".count($catchIds).",结算人数".count($awardCounts)."人,总奖金".array_sum($awardCounts),'date'=> $date];
+    }
+}

+ 104 - 0
app/api/command/SettleUserTodayAward.php

@@ -0,0 +1,104 @@
+<?php
+declare (strict_types = 1);
+
+namespace app\api\command;
+
+use app\common\model\MoneyLogModel;
+use app\common\model\UserModel;
+use think\console\Command;
+use think\console\Input;
+use think\console\Output;
+use think\facade\Db;
+use utils\RedisCache;
+
+/**
+ * 结算今日奖金 by wes 每天晚上21点后运行1次
+ * Class SettleUserTodayAward
+ * @package app\api\command
+ */
+class SettleUserTodayAward extends Command
+{
+    protected function configure()
+    {
+        // 获取用户今日奖金
+        $this->setName('settle_user_today_award')
+            ->setDescription('the settle_user_today_award command');
+    }
+
+    /**
+     * 今日奖金结算
+     * @param Input $input
+     * @param Output $output
+     * @return bool
+     */
+    protected function execute(Input $input, Output $output)
+    {
+        $cacheKey = "caches:settleTodayAward:".date('Ymd');
+        if(RedisCache::get($cacheKey.'_lock')){
+            echo json_encode(['code'=>500,'msg'=>'今日奖金已经结算过,请不要重复运行','date'=>date('Y-m-d H:i:s')], 256)."\n";
+            return false;
+        }
+
+        RedisCache::setnx($cacheKey.'_lock', date('Y-m-d H:i:s'), 86400);
+        Db::startTrans();
+        try {
+            $list = UserModel::where('today_money', '>', 0)->field('id,money,today_money')->select();
+            $list = $list? $list->toArray() : [];
+            if(empty($list)){
+                sr_throw('暂无今日奖金可结算');
+            }
+
+            $totalMoney = 0;
+            foreach ($list as $key=>$val){
+                // 结算奖金到账
+                $uid = isset($val['id'])? intval($val['id']) : 0;
+                $userMoney = isset($val['money'])? floatval($val['money']) : 0;
+                $todayMoney = isset($val['today_money'])? floatval($val['today_money']) : 0;
+                $totalMoney += $todayMoney;
+                if($uid && !UserModel::where('id',$val['id'])->inc('money', $todayMoney)->inc('todayaward_money', $todayMoney)->update()){
+                    sr_throw("结算用户[{$uid}]今日奖金失败");
+                }
+
+                $data = [
+                    'uid'=>$uid,
+                    'type'=> 6,
+                    'money'=> $todayMoney,
+                    'create_at'=>sr_getcurtime(time()),
+                    'state'=> 1,
+                    'before_money'=> $userMoney,
+                    'after_money'=> floatval($userMoney + $todayMoney),
+                    'from_id'=> 0,
+                    'uid2'=> 0,
+                    'free_type'=> 0,
+                    'remark'=> '今日奖金结算'
+                ];
+                if(!MoneyLogModel::insertGetId($data)){
+                    sr_throw("用户[{$uid}]今日奖金结算处理失败");
+                }
+            }
+
+            // 初始化今日奖金
+            if(!UserModel::where('today_money', '>', 0)->update(['today_money'=>0,'update_time'=>date('Y-m-d H:i:s')])){
+                sr_throw("今日奖金清除错误");
+            }
+
+            // 初始化今日预约
+            if(!UserModel::where('total_appoint_count', '>', 0)->update(['total_appoint_count'=>0,'update_time'=>date('Y-m-d H:i:s')])){
+                sr_throw("今日预约盒子清除错误");
+            }
+
+            Db::commit();
+            $msg = "今日奖金结算成功,累计用户".count($list)."人,累计金额{$totalMoney}元";
+            RedisCache::set($cacheKey.'_success',['msg'=> $msg,'date'=>date('Y-m-d H:i:s')], 7200);
+            echo json_encode(['code'=>500,'msg'=> $msg,'date'=>date('Y-m-d H:i:s')], 256)."\n";
+        }catch (\Exception $e){
+            Db::rollback();
+            RedisCache::clear($cacheKey.'_lock');
+            RedisCache::set($cacheKey.'_fail',['msg'=> $e->getMessage(),'trace'=>$e->getTrace()], 7200);
+            echo json_encode(['code'=>500,'msg'=>$e->getMessage(),'date'=>date('Y-m-d H:i:s')], 256)."\n";
+        }
+
+        return true;
+    }
+
+}

Diff do ficheiro suprimidas por serem muito extensas
+ 1737 - 0
app/api/controller/v1/Pay.php


+ 258 - 0
app/api/services/BasePayServices.php

@@ -0,0 +1,258 @@
+<?php
+/**
+ * 支付公用类
+ */
+
+namespace app\api\services;
+
+
+use app\common\model\PaymentModel;
+use app\common\model\ScoreLogModel;
+use app\common\model\ShopGoodsModel;
+use app\common\model\ShopOrderGoodsModel as OrderGoods;
+use app\common\model\ShopOrderModel;
+use app\common\model\ShopOrderModel as Order;
+use app\common\model\UserModel;
+use app\common\model\UserMoneyModel;
+use app\common\model\UserShouxufeilogModel;
+use app\common\model\VipOrderModel;
+use app\common\service\PaymentService;
+use app\common\service\ShopOrderService;
+use app\common\service\UserService;
+use jobs\VipOrderJob;
+use services\CacheServices;
+use think\Exception;
+use think\facade\Db;
+use think\facade\Env;
+use utils\RedisCache;
+
+abstract class BasePayServices
+{
+
+    /**
+     * 支付状态
+     * @var bool
+     */
+    protected $pay_status = false;
+
+    /**
+     * 支付回调信息
+     * @var string
+     */
+    protected $notify_info = '';
+
+    /**
+     * 支付通道 1 支付宝 2 微信
+     * @var int
+     */
+    protected $pay_way = 1;
+
+    /**
+     * 费用配置
+     * @param int $uid
+     * @param array $data
+     * @param int $type
+     * @return array
+     */
+    public function _payConf(int $uid, array $data, int $type = 1)
+    {
+
+        $body = $total_amount = $remarks = '';
+        switch ($data['order_type']) {
+            case 1: // 充值
+                $body = '充值';
+                $total_amount = $data['money'];
+                break;
+            case 2: // 实名认证
+                $total_amount = Db::name('user_data')->where('uid', $uid)->value('rz_money');
+                $body = '实名认证费用';
+                break;
+            case 3: // 开通会员
+                $vip_money = Env::get('app.VIP_MONEY', 38);
+                if (!$vip_money > 0) {
+                    throw new Exception('会员配置不存在');
+                }
+                $body = '开通会员';
+                $total_amount = $vip_money;
+                $remarks = $data['order_id'];
+                break;
+            case 4: //购买商品
+                $order_sn = $data['order_id'];
+                $checkingOrder = Order::checkingOrder($order_sn, 0, $uid);
+                if (!$checkingOrder) {
+                    throw new Exception('校验订单失败');
+                }
+                if ($checkingOrder['flag'] !== 200) {
+                    throw new Exception($checkingOrder['msg']);
+                }
+                $body = '购买商品';
+                $total_amount = $checkingOrder['orderPayment'];
+                $remarks = $order_sn;
+                //更改订单支付状态为线下支付待审核状态
+                if ($this->data['channel'] == 4 && $this->data['order_type'] == 4) {
+                    Order::where('order_sn', 'in', $order_sn)->save(['pay_type' => 4, 'status' => 4]);
+                }
+                break;
+        }
+
+        if (!$body || !$total_amount || $total_amount <= 0)
+            throw new Exception('配置错误');
+
+        return [$body, $type == 1 ? $total_amount : bcmul($total_amount, 100, 2), $data['order_type'], $remarks, $data['trade_type'], $data['channel'], isset($data['voucher_img']) ? $data['voucher_img'] : ''];
+    }
+
+    /**
+     * 保存订单信息
+     * @param array $param
+     * @param int $pay_way
+     */
+    public function setPaymentOrder(array $param, int $pay_way = 1)
+    {
+        $insert = [
+            'total_fee' => $param['total_amount'],
+            'trade_type' => $param['trade_type'],
+            'body' => $param['body'],
+            'state' => 7,
+            'out_trade_no' => $param['out_trade_no'],
+            'pay_way' => $param['pay_way'],
+            'remarks' => $param['remarks'],
+            'order_type' => $param['order_type'],
+            'uid' => $param['uid'],
+            'voucher_img' => $param['voucher_img'],
+            'out_trade_no1' => (isset($param['out_trade_no1']) ? $param['out_trade_no1'] : (isset($param[''])))
+        ];
+        // 信息
+        $payWayCode = PaymentService::make()->getPayCode($pay_way);
+        RedisCache::set("caches:payment:{$payWayCode}:otn_{$param['out_trade_no']}:{$param['uid']}_{$param['remarks']}_payment", $insert, 7200);
+        Db::name('payment')->insert($insert);
+    }
+
+    /**
+     * 处理订单回调
+     * @param string $payType
+     */
+    public function afterPay($payType = 2)
+    {
+        $date = date('Y-m-d H:i:s');
+        $outTradeNo = isset($this->notify_info['out_trade_no']) ? $this->notify_info['out_trade_no'] : '';
+        $payCode = PaymentService::make()->getPayCode($payType);
+        $cacheKey = "caches:payNotify:{$payCode}:otn_{$outTradeNo}:";
+        RedisCache::set($cacheKey.'catch', ['notify' => $this->notify_info, 'date' => $date], 7200);
+        if (!$this->pay_status || empty($outTradeNo)) {
+            sr_throw('回调参数错误,处理失败');
+        }
+
+        $payInfo = PaymentService::make()->getCacheInfo($outTradeNo, 7, '', false);
+        $payId = isset($payInfo['id']) ? $payInfo['id'] : 0;
+        $payUid = isset($payInfo['uid']) ? $payInfo['uid'] : 0;
+        $orderSn = isset($payInfo['remarks']) ? $payInfo['remarks'] : '';
+        $orderType = isset($payInfo['order_type']) ? intval($payInfo['order_type']) : 0;
+        if (empty($payInfo) || $payId <= 0 || $payUid <= 0) {
+            $logData = ['notify' => $this->notify_info, 'payInfo' => $payInfo, 'error' => '支付信息不存在,或参数错误', 'date' => $date];
+            RedisCache::set($cacheKey . "error_{$orderSn}", $logData, 7200);
+            sr_throw('支付信息不存在,或参数错误');
+        }
+
+        // 更新支付状态
+        Db::startTrans();
+        if (!PaymentModel::where('id', $payId)->update(['state' => 6, 'pay_at' => date('Y-m-d H:i:s')])) {
+            Db::rollback();
+            $logData = ['notify' => $this->notify_info, 'payInfo' => $payInfo, 'error' => '更新支付状态失败', 'date' => $date];
+            RedisCache::set($cacheKey . "error_{$orderSn}", $logData, 7200);
+            sr_throw('更新支付状态失败');
+        }
+
+        // 订单业务处理
+        switch ($orderType) {
+            case 1: // 充值
+                break;
+            case 3:
+                // 充值会员
+                break;
+            case 4: //购买商品
+                $orderInfo = ShopOrderService::make()->getInfoBySn($orderSn, $payUid, '', false);
+                $orderId = isset($orderInfo['order_id']) ? $orderInfo['order_id'] : 0;
+                $orderStatus = isset($orderInfo['status']) ? $orderInfo['status'] : 0;
+                if (empty($orderInfo) || $orderId <= 0) {
+                    Db::rollback();
+                    $logData = ['notify' => $this->notify_info, 'orderInfo' => $orderInfo, 'payInfo' => $payInfo, 'error' => "单号{$orderSn}商城订单不存在", 'date' => $date];
+                    RedisCache::set($cacheKey . "error_{$orderSn}", $logData, 7200);
+                    sr_throw("单号{$orderSn}商城订单不存在");
+                }
+
+                // 验证订单状态
+                if ($orderStatus != 0) {
+                    Db::rollback();
+                    $logData = ['notify' => $this->notify_info, 'orderInfo' => $orderInfo, 'payInfo' => $payInfo, 'error' => "单号{$orderSn}商城订单已处理", 'date' => $date];
+                    RedisCache::set($cacheKey . "error_{$orderSn}", $logData, 7200);
+                    sr_throw("单号{$orderSn}商城订单已处理");
+                }
+
+                // 更新订单状态
+                if (!ShopOrderModel::where('order_id', $orderId)->update(['pay_type' => $payType, 'status' => 1, 'updated_time' => $date])) {
+                    Db::rollback();
+                    $logData = ['notify' => $this->notify_info, 'orderInfo' => $orderInfo, 'payInfo' => $payInfo, 'error' => "单号{$orderSn}商城订单已处理", 'date' => $date];
+                    RedisCache::set($cacheKey . "error_{$orderSn}", $logData, 7200);
+                    sr_throw("单号{$orderSn}商城订单已处理");
+                }
+
+
+                // 增加订单商品销量
+                $updateSale = ShopOrderModel::alias('a')
+                    ->leftJoin('shop_order_goods og', 'og.order_id=a.order_id')
+                    ->leftJoin('shop_goods g', 'g.goods_id=og.goods_id')
+                    ->where(['a.order_id' => $orderId, 'a.user_id' => $payUid])
+                    ->update([
+                        'g.sales_volume' => Db::raw('g.sales_volume + og.num'),
+                        'g.real_sales_volume' => Db::raw('g.real_sales_volume + og.num'),
+                    ]);
+                if (!$updateSale) {
+                    Db::rollback();
+                    $logData = ['notify' => $this->notify_info, 'orderInfo' => $orderInfo, 'payInfo' => $payInfo, 'error' => "单号{$orderSn}商城订单更新商品销量失败", 'date' => $date];
+                    RedisCache::set($cacheKey . "error_{$orderSn}", $logData, 7200);
+                    sr_throw("单号{$orderSn}商城订单更新商品销量失败");
+                }
+
+                // 送积分/红包积分
+                $userInfo = UserService::make()->getCacheInfo($payUid, 'id,mobile,score,money', false);
+                $rebateScore = isset($orderInfo['rebate_score']) ? floatval($orderInfo['rebate_score']) : 0;
+                if ($rebateScore > 0 && $userInfo) {
+                    // 更新用户账户积分
+                    if (!UserModel::where('id', $payUid)->inc('score', $rebateScore)->update()) {
+                        Db::rollback();
+                        $logData = ['notify' => $this->notify_info, 'orderInfo' => $orderInfo, 'user' => $userInfo, 'payInfo' => $payInfo, 'error' => "更新用户赠送积分失败", 'date' => $date];
+                        RedisCache::set($cacheKey . "error_{$orderSn}", $logData, 7200);
+                        sr_throw("更新用户赠送积分失败");
+                    }
+
+                    // 处理积分流水明细
+                    $userScore = isset($userInfo['score']) ? floatval($userInfo['score']) : 0;
+                    $data = [
+                        'uid' => $payUid,
+                        'type' => 5,
+                        'score' => $rebateScore,
+                        'create_at' => sr_getcurtime(time()),
+                        'state' => 12,
+                        'before_score' => $userScore,
+                        'after_score' => max(0, $userScore + $rebateScore),
+                        'from_id' => $payId,
+                        'uid2' => 0
+                    ];
+                    if (!ScoreLogModel::insertGetId($data)) {
+                        Db::rollback();
+                        $logData = ['notify' => $this->notify_info, 'orderInfo' => $orderInfo, 'user' => $userInfo, 'score' => $data, 'payInfo' => $payInfo, 'error' => "赠送积分处理失败", 'date' => $date];
+                        RedisCache::set($cacheKey . "error_{$orderSn}", $logData, 7200);
+                        sr_throw("赠送积分处理失败");
+                    }
+
+                }
+
+                $logData = ['notify' => $this->notify_info, 'orderInfo' => $orderInfo, 'user' => $userInfo, 'score' => $data, 'payInfo' => $payInfo, 'error' => "赠送积分处理失败", 'date' => $date];
+                RedisCache::set($cacheKey."success_{$orderSn}", $logData, 7200);
+                break;
+        }
+
+        Db::commit();
+        return 'success';
+    }
+}

Diff do ficheiro suprimidas por serem muito extensas
+ 2141 - 0
app/api/services/ThirdPayServices.php


+ 23 - 0
app/common/model/BoxHandleAllModel.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace app\common\model;
+
+use app\common\model\TimeModel;
+use think\facade\Db;
+use think\Model;
+
+class BoxHandleAllModel extends Model
+{
+
+    protected $name = "box_handle_all";
+
+
+
+
+
+
+
+
+
+
+}

+ 16 - 0
app/common/model/SubmeterModel.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace app\common\model;
+
+use think\Model;
+
+/**
+ * 分表记录
+ * Class SubmeterModel
+ * @package app\common\model
+ */
+class SubmeterModel extends Model
+{
+
+    protected $name = 'submeter';
+}

+ 16 - 0
app/common/model/WithdrawAccountModel.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace app\common\model;
+
+use think\Model;
+
+/**
+ * 提现账号
+ * Class WithdrawAccountModel
+ * @package app\common\model
+ */
+class WithdrawAccountModel extends Model
+{
+
+    protected $name = 'withdraw_accoiuntinfo';
+}

+ 379 - 0
app/common/service/PaymentService.php

@@ -0,0 +1,379 @@
+<?php
+
+namespace app\common\service;
+
+use app\common\model\PaymentModel;
+use app\common\model\ScoreLogModel;
+use app\common\model\ServicesOrderModel;
+use app\common\model\ShopGoodsModel;
+use app\common\model\ShopOrderGoodsModel as OrderGoods;
+use app\common\model\ShopOrderModel;
+use app\common\model\ThirdpayBackModel;
+use app\common\model\UserModel;
+use think\facade\Db;
+use utils\RedisCache;
+
+/**
+ * 支付服务 by wes
+ * Class PaymentService
+ * @package app\common\service
+ */
+class PaymentService
+{
+    protected static $instance = null;
+    protected $cacheTime = 7200;
+    protected $payWayArr = [
+        1=>'wxpay',
+        2=>'alipay',
+        3=>'balancePay',
+        14=>'huifuPay',
+        15=>'sqzpay',
+        16=>'sqzAliPay',
+        17=>'sqzWxPay',
+        18=>'sqzYljk',
+        19=>'usdtPay',
+        20=>'yswkPay',
+        22=>'dkpay',
+        56=>'ysftyPay'
+    ];
+    protected $model = null;
+
+    public function __construct()
+    {
+        $this->model = new PaymentModel();
+    }
+
+    /**
+     * 静态化入口
+     * @return static|null
+     */
+    public static function make()
+    {
+        if(!self::$instance){
+            self::$instance = new static();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * 获取支付方式标识
+     * @param $payType 支付类型
+     * @return string
+     */
+    public function getPayCode($payType)
+    {
+        return isset($this->payWayArr[$payType])? $this->payWayArr[$payType] : 'defpay';
+    }
+
+    /**
+     * 按状态获取支付单数量
+     * @param $uid
+     * @param $orderType
+     * @param $state
+     * @param int $time 时间/小时,默认2小时
+     * @return array|mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function getPaymentCountByState($uid, $orderSn='', $orderType, $state, $time = 2)
+    {
+        $cacheKey = "caches:paymentCall:u{$uid}_ot{$orderType}_s{$state}_{$time}_{$orderSn}";
+        $data = RedisCache::get($cacheKey);
+        if($data){
+            return $data;
+        }
+        $where = [];
+        if($orderType){
+            $where['order_type'] = $orderType;
+        }
+
+        if($state){
+            $where['state'] = $state;
+        }
+
+        $data = $this->model->where($where)->where(function ($query) use($time,$orderSn){
+            if($time>0){
+                $query->where('creat_at','>=', time() - $time * 3600);
+            }
+            if($orderSn){
+                $query->where('remarks',$orderSn);
+            }
+        })->count('id');
+        if($data){
+            RedisCache::set($cacheKey, $data, rand(3,5));
+        }
+
+        return $data;
+    }
+
+    /**
+     * 验证订单是否已支付
+     * @param $uid
+     * @param $orderSn
+     * @return array|mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function checkPaymentState($uid, $orderSn)
+    {
+        $cacheKey = "caches:paymentState:u{$uid}_sn{$orderSn}";
+        $data = RedisCache::get($cacheKey);
+        if($data){
+            return $data;
+        }
+        $data = $this->model->where(['state'=> 6])->where(function ($query) use($orderSn){
+            if($orderSn){
+                $query->where('remarks', $orderSn);
+            }
+        })->value('id');
+        if($data){
+            RedisCache::set($cacheKey, $data, rand(2,3));
+        }
+
+        return $data;
+    }
+
+    /**
+     * 获取支付信息(有缓存)
+     * @param $outTradeNo
+     * @param int $state
+     * @param string $field
+     * @param false $cache
+     * @return array|mixed
+     */
+    public function getCacheInfo($outTradeNo, $state=7, $field='', $cache = false)
+    {
+        $cacheKey = "caches:payment:info:otn{$outTradeNo}_{$state}".($field? '_'.md5($field):'');
+        $data = RedisCache::get($cacheKey);
+        if($data && $cache){
+            return $data;
+        }
+        $where = ['out_trade_no'=> $outTradeNo];
+        if($state){
+            $where['state'] = $state;
+        }
+        $field = $field? $field : 'id,out_trade_no,uid,total_fee,state,trade_type,out_trade_no1,hy_token_id,syl_sureorderid,hy_bill_no,is_retreat,pay_way,order_type,sid,remarks,trade_no';
+        $data = $this->model->where($where)
+            ->field($field)
+            ->order('creat_at desc')
+            ->findOrEmpty();
+        if($data && $cache){
+            RedisCache::set($cacheKey, $data, rand(5,10));
+        }
+
+        return $data;
+    }
+
+
+    /**
+     * 支付回调处理
+     * @param $outTradeNo
+     * @param $payMoney
+     * @param $payType
+     * @param $content
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function payBack($outTradeNo, $payMoney, $payType, $content)
+    {
+        $nowTime = date('Y-m-d H:i:s');
+        $params = $content? json_decode($content, true):[];
+        $payCode = PaymentService::make()->getPayCode($payType);
+        $cacheKey = "caches:payNotify:{$payCode}:otn_{$outTradeNo}:";
+        RedisCache::set($cacheKey.'catch', ['params'=>$params,'date'=> $nowTime], $this->cacheTime);
+
+        // 验证支付请求信息
+        $payInfo = PaymentService::make()->getCacheInfo($outTradeNo, 0);
+        $payId = isset($payInfo['id'])? $payInfo['id']:0;
+        $payUid = isset($payInfo['uid'])? $payInfo['uid']:0;
+        $payState = isset($payInfo['state'])? intval($payInfo['state']):0;
+        $totalFee = isset($payInfo['total_fee'])? floatval($payInfo['total_fee']):0;
+        $orderSn = isset($payInfo['remarks'])? trim($payInfo['remarks']):'';
+        $orderType = isset($payInfo['order_type'])? intval($payInfo['order_type']) : 0;
+        if (!$payInfo || $payUid<=0 || empty($orderSn) || $payState!= 7) {
+            $error = empty($payInfo) || !$payId || empty($orderSn)? "单号{$orderSn}支付信息不存在或参数错误":"单号{$orderSn}已经回调支付";
+            $logData = ['params'=>$params,'payInfo'=>$payInfo,'msg'=> $error,'date'=> $nowTime];
+            RedisCache::set($cacheKey.'error', $logData, $this->cacheTime);
+            sr_throw($error);
+        }
+
+        // 验证支付金额
+        if($payMoney < $totalFee || $totalFee<=0){
+            $logData = ['params'=>$params,'payInfo'=>$payInfo,'msg'=> "单号{$orderSn}支付金额错误",'date'=> $nowTime];
+            RedisCache::set($cacheKey.'error', $logData, $this->cacheTime);
+            sr_throw("单号{$orderSn}支付金额错误");
+        }
+
+        // 更新支付状态
+        if(!PaymentModel::where('id', $payId)->update(['state' => 6, 'pay_at' => $nowTime])){
+            $logData = ['params'=>$params,'payInfo'=>$payInfo,'msg'=> "单号{$orderSn}更新支付状态失败",'date'=> $nowTime];
+            RedisCache::set($cacheKey.'_error', $logData, $this->cacheTime);
+            sr_throw("单号{$orderSn}更新支付状态失败");
+        }
+
+        // 第三方回调数据
+        $data = [
+            'out_order_no' => $outTradeNo,
+            'content' => $content,
+            'create_time' => sr_getcurtime(time()),
+            'type' => $payType,
+            'uid' => $payUid,
+            'money' => $payMoney
+        ];
+        if(!ThirdpayBackModel::insertGetid($data)){
+            $logData = ['params'=>$params,'payInfo'=>$payInfo,'third'=>$data,'msg'=> "单号{$orderSn}处理第三方回调数据错误",'date'=> $nowTime];
+            RedisCache::set($cacheKey.'_error', $logData, $this->cacheTime);
+            sr_throw("单号{$orderSn}处理第三方回调数据错误");
+        }
+
+        /* TODO 服务商订单处理 */
+        if ($orderType == 6) {
+            // 获取验证订单信息
+            $orderInfo = ServiceOrderService::make()->getInfoBySn($orderSn, $payUid);
+            $orderId = isset($orderInfo['order_id'])? $orderInfo['order_id'] : 0;
+            $orderMoney = isset($orderInfo['payment'])? floatval($orderInfo['payment']) : 0;
+            $orderStatus = isset($orderInfo['status'])? intval($orderInfo['status']) : 0;
+            if(empty($orderInfo) || $orderId<=0 || $orderMoney<=0){
+                $logData = ['params'=>$params,'payInfo'=>$payInfo,'order'=>$orderInfo,'msg'=> "服务商订单{$orderSn}不存在或参数错误",'date'=> $nowTime];
+                RedisCache::set($cacheKey.'_error', $logData, $this->cacheTime);
+                sr_throw("服务商订单{$orderSn}不存在或参数错误");
+            }
+
+            // 验证订单状态
+            if($orderStatus != 1){
+                $logData = ['params'=>$params,'payInfo'=>$payInfo,'order'=>$orderInfo,'msg'=> "服务商订单{$orderSn}订单已处理",'date'=> $nowTime];
+                RedisCache::set($cacheKey.'_error', $logData, $this->cacheTime);
+                sr_throw("服务商订单{$orderSn}订单已处理");
+            }
+
+            // 更新订单状态
+            if(!ServicesOrderModel::where('order_id', $orderId)->update(['status' => 2, 'pay_type' => $payType, 'updated_time' => $nowTime])){
+                $logData = ['params'=>$params,'payInfo'=>$payInfo,'order'=>$orderInfo,'msg'=> "服务商订单{$orderSn}更新状态错误",'date'=> $nowTime];
+                RedisCache::set($cacheKey.'_error', $logData, $this->cacheTime);
+                sr_throw("服务商订单{$orderSn}更新状态错误");
+            }
+
+            // 更新用户服务商有效期
+            $date = sr_getcurtime(time(), 'Y-m-d');
+            $expireDay = date('Y-m-d', strtotime("$date +1 month"));
+            if(!UserModel::where('id', $payUid)->update(['store_type' => 1,'store_expire_time' => $expireDay,'update_time'=>time()])){
+                $logData = ['params'=>$params,'payInfo'=>$payInfo,'order'=>$orderInfo,'storeExpire'=>$expireDay,'msg'=> "服务商订单{$orderSn}更新用户服务商有效期错误",'date'=> $nowTime];
+                RedisCache::set($cacheKey.'_error', $logData, $this->cacheTime);
+                sr_throw("服务商订单{$orderSn}更新用户服务商有效期错误");
+            }
+
+            $logData = ['params'=>$params,'payInfo'=>$payInfo,'order'=>$orderInfo,'storeExpire'=>$expireDay,'msg'=> "服务商订单{$orderSn}支付回调处理成功",'date'=> $nowTime];
+            RedisCache::set($cacheKey."success_{$orderSn}", $logData, $this->cacheTime);
+
+        }
+
+        /** TODO 商城订单处理 **/
+        elseif ($orderType == 4) {
+            // 获取验证订单信息
+            $orderInfo = ShopOrderService::make()->getInfoBySn($orderSn, $payUid);
+            $orderId = isset($orderInfo['order_id'])? $orderInfo['order_id'] : 0;
+            $orderMoney = isset($orderInfo['payment'])? floatval($orderInfo['payment']) : 0;
+            $orderStatus = isset($orderInfo['status'])? intval($orderInfo['status']) : -1;
+            if(empty($orderInfo) || $orderId<=0 || $orderMoney<=0){
+                $logData = ['params'=>$params,'payInfo'=>$payInfo,'order'=>$orderInfo,'msg'=> "商城订单{$orderSn}不存在或参数错误",'date'=> $nowTime];
+                RedisCache::set($cacheKey."error_{$orderSn}", $logData, $this->cacheTime);
+                sr_throw("商城订单{$orderSn}不存在或参数错误");
+            }
+
+            // 验证订单状态
+            if($orderStatus != 0){
+                $logData = ['params'=>$params,'payInfo'=>$payInfo,'order'=>$orderInfo,'msg'=> "商城订单{$orderSn}订单已处理",'date'=> $nowTime];
+                RedisCache::set($cacheKey."error_{$orderSn}", $logData, $this->cacheTime);
+                sr_throw("商城订单{$orderSn}订单已处理");
+            }
+
+            // 更新订单状态
+            if(!ShopOrderModel::where('order_id', $orderId)->update(['status' => 1, 'pay_type' => $payType, 'updated_time' => $nowTime])){
+                $logData = ['params'=>$params,'payInfo'=>$payInfo,'order'=>$orderInfo,'msg'=> "服务商订单{$orderSn}更新状态错误",'date'=> $nowTime];
+                RedisCache::set($cacheKey.'_error', $logData, $this->cacheTime);
+                sr_throw("服务商订单{$orderSn}更新状态错误");
+            }
+
+            // 增加订单商品销量
+            $updateSale = ShopOrderModel::alias('a')
+                ->leftJoin('shop_order_goods og', 'og.order_id=a.order_id')
+                ->leftJoin('shop_goods g', 'g.goods_id=og.goods_id')
+                ->where(['a.order_id' => $orderId, 'a.user_id' => $payUid])
+                ->update([
+                    'g.sales_volume' => Db::raw('g.sales_volume + og.num'),
+                    'g.real_sales_volume' => Db::raw('g.real_sales_volume + og.num'),
+                ]);
+            if (!$updateSale) {
+                Db::rollback();
+                $logData = ['params' => $params, 'orderInfo' => $orderInfo, 'payInfo' => $payInfo, 'error' => "商城订单{$orderSn}更新商品销量失败", 'date' => $date];
+                RedisCache::set($cacheKey . "error_{$orderSn}", $logData, 7200);
+                sr_throw("商城订单{$orderSn}更新商品销量失败");
+            }
+
+            // 更新用户交易额(消费)
+            $userInfo = UserService::make()->getCacheInfo($payUid,'id,mobile,score,money,path', false);
+            $upperPath = isset($userInfo['path'])? $userInfo['path'] : '';
+            if($userInfo && !UserModel::where('id', $payUid)->inc('total_income', $totalFee)->inc('total_team_income',$totalFee)->update()){
+                $logData = ['params'=>$params,'payInfo'=>$payInfo,'user'=>$userInfo,'order'=>$orderInfo,'msg'=> "商城订单{$orderSn}更新用户交易数据错误",'date'=> $nowTime];
+                RedisCache::set($cacheKey."error_{$orderSn}", $logData, $this->cacheTime);
+                sr_throw("商城订单{$orderSn}更新用户交易数据错误");
+            }
+
+            // 更新团队交易额数据
+            if($upperPath && !UserModel::whereIn('id', explode(',', $upperPath))->inc('total_team_income',$totalFee)->update()){
+                $logData = ['params'=>$params,'payInfo'=>$payInfo,'user'=>$userInfo,'order'=>$orderInfo,'msg'=> "商城订单{$orderSn}更新用户上级交易数据错误",'date'=> $nowTime];
+                RedisCache::set($cacheKey."error_{$orderSn}", $logData, $this->cacheTime);
+                sr_throw("商城订单{$orderSn}更新用户上级交易数据错误");
+            }
+
+
+            // 赠送积分处理
+            $rebateScore = isset($orderInfo['rebate_score']) ? floatval($orderInfo['rebate_score']) : 0;
+            if ($rebateScore > 0 && $userInfo) {
+                // 更新用户账户积分
+                if (!UserModel::where('id', $payUid)->inc('score', $rebateScore)->update()) {
+                    $logData = ['params'=>$params,'payInfo'=>$payInfo,'user'=>$userInfo,'order'=>$orderInfo,'msg'=> "商城订单{$orderSn}赠送积分处理错误",'date'=> $nowTime];
+                    RedisCache::set($cacheKey."error_{$orderSn}", $logData, $this->cacheTime);
+                    sr_throw("商城订单{$orderSn}赠送积分处理错误");
+                }
+
+                // 处理积分流水明细
+                $userScore = isset($userInfo['score']) ? floatval($userInfo['score']) : 0;
+                $data = [
+                    'uid' => $payUid,
+                    'type' => 3,
+                    'score' => $rebateScore,
+                    'create_at' => sr_getcurtime(time()),
+                    'state' => 12,
+                    'before_score' => $userScore,
+                    'after_score' => floatval($userScore + $rebateScore),
+                    'from_id' => $payId,
+                    'uid2' => 0
+                ];
+                if (!ScoreLogModel::insertGetId($data)) {
+                    $logData = ['params'=>$params,'payInfo'=>$payInfo,'user'=>$userInfo,'order'=>$orderInfo,'msg'=> "商城订单{$orderSn}赠送积分明细处理错误",'date'=> $nowTime];
+                    RedisCache::set($cacheKey."error_{$orderSn}", $logData, $this->cacheTime);
+                    sr_throw("商城订单{$orderSn}赠送积分明细处理错误");
+                }
+
+            }
+
+            $logData = ['params'=>$params,'payInfo'=>$payInfo,'user'=>$userInfo,'order'=>$orderInfo,'msg'=> "商城订单{$orderSn}回调处理成功",'date'=> $nowTime];
+            RedisCache::set($cacheKey."success_{$orderSn}", $logData, $this->cacheTime);
+        }
+
+
+        return true;
+    }
+
+
+
+
+
+
+}

+ 786 - 0
app/common/service/ShopOrderService.php

@@ -0,0 +1,786 @@
+<?php
+
+namespace app\common\service;
+
+use app\api\services\ExpressServices;
+use app\common\model\ShopCartModel;
+use app\common\model\ShopGoods;
+use app\common\model\ShopGoodsModel;
+use app\common\model\ShopGoodsSpec;
+use app\common\model\ShopOrder;
+use app\common\model\ShopOrderGoodsModel;
+use app\common\model\ShopOrderShippingModel;
+use app\common\model\UserAddressModel;
+use jobs\ShopOrderJob;
+use think\Exception;
+use think\facade\Db;
+use utils\Queue;
+use utils\RedisCache;
+
+/**
+ * 商城订单服务 by wes
+ * Class ShopOrderService
+ * @package app\common\service
+ */
+class ShopOrderService
+{
+    protected static $instance = null;
+    protected $model = null;
+
+    public function __construct()
+    {
+        $this->model = new ShopOrder();
+    }
+
+    /**
+     * 静态化入口
+     * @return static|null
+     */
+    public static function make()
+    {
+        if (!self::$instance) {
+            self::$instance = new static();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * 商城下单
+     * @param $uid 用户ID
+     * @param $buyGoods 购买的商品数组
+     * @param $payType 支付方式
+     * @param $request 请求数据
+     * @param int $cartIds 购物车ID数组
+     * @param int $cls 赠送月卡功能开关
+     * @return array
+     * @throws Exception
+     */
+    public function createOrder($uid, $buyGoods, $payType, $request, $cartIds = 0, $cls = 0)
+    {
+        // 获取收货地址信息
+        $orderType = 1;   //  订单类型 1商城订单 2积分订单 3.兑换券 4.锁定积分兑换 5.福袋发货订单
+        $addressId = $request->post('address', '');
+        $remark = $request->post('order_remark', '');
+        if (!$addressId || !$userAddress = UserAddressModel::getAddressIdDetails((int)$addressId)) {
+            throw new Exception('获取收货地址失败');
+        }
+
+        $addressUid = isset($userAddress['uid']) ? $userAddress['uid'] : 0;
+        if ($addressUid != $uid) {
+            throw new Exception('收货地址信息异常');
+        }
+
+        if (empty($buyGoods)) {
+            throw new Exception('商品参数错误');
+        }
+
+        // 未支付订单数量验证
+        $count = ShopOrderService::make()->checkBuyCountByTime($uid, 1, 0);
+        $noPayLimit = env('common.ORDER_NO_PAY_LIMIT', 5);
+        if ($count >= $noPayLimit) {
+            throw new Exception("2小时内未支付的订单不能超过{$noPayLimit}个,下单失败");
+        }
+
+        // 订单商品数据处理
+        $i = 0;
+        $nowTime = time();
+        $orderId = $this->model->max('order_id') + 1;
+        $orderDatas = $orderGoods = $orders = $shippingList = [];
+        $expireTime = time() + intval(env('common.ORDER_EXPIRES_TIME'));
+        Db::startTrans();
+        try {
+            foreach ($buyGoods as $k => $v) {
+                $goodsSn = isset($v['goods_sn']) ? trim($v['goods_sn']) : 0;
+                $specId = isset($v['spec_id']) ? trim($v['spec_id']) : 0;
+                $num = isset($v['num']) ? intval($v['num']) : 0;
+                if ($num < 1) {
+                    throw new Exception('请先选择商品数量哦');
+                }
+
+                // 商品信息验证
+                $goodsInfo = ShopGoodsService::make()->getCacheInfo($v['goods_sn']);
+                if (!$goodsInfo) {
+                    throw new Exception('商品参数错误或已下架,请返回刷新列表');
+                }
+                if ($goodsInfo['goods_type'] == 2) {
+                    throw new Exception('商福袋商品不能购买');
+                }
+
+                // 商品限购验证
+                if ($goodsInfo['restrictions_num'] > 0) {
+                    $count = ShopOrderGoodsService::make()->getCountByUserAndGoods($uid, $goodsSn);
+
+                    if ($count > $goodsInfo['restrictions_num']) {
+                        throw new Exception('超过限购');
+                    }
+                    if ($v['num'] + $count > $goodsInfo['restrictions_num']) {
+                        throw new Exception('超过限购数量,还可购买:' . ($goodsInfo['restrictions_num'] - $count) . '件');
+                    }
+                }
+
+                // 商品库存验证
+                if ($goodsSn && $specId && $num > 0) {
+                    $stock = ShopGoodsSpecService::make()->getStock($goodsSn, $specId);
+                    if ($num > $stock) {
+                        throw new Exception('下单失败,所选商品含有库存不足的商品:SN-' . $goodsSn);
+                    }
+                }
+
+                // 是否赠送月卡
+                $giveVip = 0;
+                $goodsGiveVip = isset($goodsInfo['give_vip']) ? $goodsInfo['give_vip'] : 0;
+                if ($cls == 1 && $goodsGiveVip) {
+                    $giveVip = $goodsGiveVip;
+                }
+
+                // 同商品订单数据
+                if (!isset($orderDatas[$goodsSn]) || empty($orderDatas[$goodsSn])) {
+                    $orderSn = createdOrderSn();
+                    $orders[] = $orderSn;
+                    $orderId = $orderId + $i;
+                    $orderDatas[$goodsSn] = [
+                        'order_id' => $orderId,
+                        'order_sn' => $orderSn,
+                        'total_price' => 0,
+                        'payment' => 0,
+                        'user_id' => $uid,
+                        'status' => 0, // 待付款
+                        'ship_postfee' => 0,
+                        'rebate_score' => 0, //总返利积分
+                        'created_time' => date('Y-m-d H:i:s', $nowTime + $i),
+                        'updated_time' => date('Y-m-d H:i:s', $nowTime + $i),
+                        'give_vip' => $giveVip,
+                        'order_remark' => $remark,
+                        'pay_type' => $payType,
+                        'order_type' => $orderType,
+                        'coupon_number' => '',
+                        'expires_time' => sr_getcurtime($expireTime + $i),
+                        'supplier_name' => isset($goodsInfo['supplier_name']) ? $goodsInfo['supplier_name'] : '',
+                    ];
+
+                    // 构建订单配送数据
+                    $shippingList[$orderSn] = [
+                        'order_id' => $orderId,
+                        'sp_name' => $userAddress['name'],
+                        'sp_mobile' => $userAddress['mobile'],
+                        'sp_province' => $userAddress['sp_province'],
+                        'sp_city' => $userAddress['sp_city'],
+                        'sp_county' => $userAddress['sp_county'],
+                        'sp_remark' => $userAddress['remark'],
+                        'sp_mergename' => $userAddress['mergename'],
+                        'created_time' => date('Y-m-d H:i:s', $nowTime + $i),
+                        'updated_time' => date('Y-m-d H:i:s', $nowTime + $i),
+                    ];
+
+                    $i++;
+                }
+
+
+                // 订单商品规格数据
+                $goodsSpec = ShopGoodsSpecService::make()->getDataByGoods($goodsInfo['goods_id'], $specId);
+                $goodsSpecId = isset($goodsSpec['goods_spec_id']) ? $goodsSpec['goods_spec_id'] : 0;
+                $weight = isset($goodsSpec['weight']) ? $goodsSpec['weight'] : 0;
+                $price = isset($goodsSpec['price']) ? $goodsSpec['price'] : 0;
+
+                // 同一个商品数据合计
+                $totalFee = bcmul($num, $price, 3);
+                $totalWeight = bcmul($num, $weight, 3);
+                $orderDatas[$goodsSn]['total_weight'] = isset($orderDatas[$goodsSn]['total_weight']) ? bcadd($orderDatas[$goodsSn]['total_weight'], $totalWeight, 3) : $totalWeight;
+                $orderDatas[$goodsSn]['total_price'] = isset($orderDatas[$goodsSn]['total_price']) ? bcadd($orderDatas[$goodsSn]['total_price'], $totalFee, 3) : $totalFee;
+                $orderDatas[$goodsSn]['num'] = isset($orderDatas[$goodsSn]['num']) ? intval($orderDatas[$goodsSn]['num'] + $num) : $num;
+
+                // 返还积分/金额合计
+                $rebateScore = isset($goodsInfo['rebate_score']) ? bcmul($num, $goodsInfo['rebate_score'], 3) : 0;
+                $rebateMoney = isset($goodsInfo['rebate_money']) ? bcmul($num, $goodsInfo['rebate_money'], 3) : 0;
+                $orderDatas[$goodsSn]['rebate_score'] = isset($orderDatas[$goodsSn]['rebate_score']) ? bcadd($orderDatas[$goodsSn]['rebate_score'], $rebateScore, 3) : $rebateScore;
+                $orderDatas[$goodsSn]['rebate_money'] = isset($orderDatas[$goodsSn]['rebate_money']) ? bcadd($orderDatas[$goodsSn]['rebate_money'], $rebateScore, 3) : $rebateMoney;
+                $orderDatas[$goodsSn]['rebate_money'] = 0;  // 临时覆盖值
+
+                // 邮费计算合计
+                $postFee = 0;
+                $costPostFee = true;
+                if ($costPostFee) {
+                    $city = isset($userAddress['city']) ? $userAddress['city'] : '';
+                    $postData = [
+                        'goods_sn' => $goodsInfo['goods_sn'],
+                        'num' => $orderDatas[$goodsSn]['num'],
+                        'total_price' => $orderDatas[$goodsSn]['total_price'],
+                        'total_weight' => $orderDatas[$goodsSn]['total_weight'],
+                    ];
+                    $postTemplateId = isset($goodsInfo['post_template_id']) ? $goodsInfo['post_template_id'] : 0;
+                    $postFee = ShopOrderService::make()->postFee($city, $postData, $postTemplateId);  // 邮费
+                }
+
+                $orderDatas[$goodsSn]['ship_postfee'] = $postFee;
+                $orderDatas[$goodsSn]['payment'] = bcadd($orderDatas[$goodsSn]['total_price'], $postFee, 3);
+
+                // 订单商品
+                $orderGoods[] = [
+                    'order_id' => $orderDatas[$goodsSn]['order_id'],
+                    'goods_id' => $goodsInfo['goods_id'],
+                    'goods_name' => $goodsInfo['goods_name'],
+                    'goods_category' => $goodsInfo['category'],
+                    'goods_img' => isset($goodsSpec['picture']) && $goodsSpec['picture'] ? $goodsSpec['picture'] : $goodsInfo['goods_img'],
+                    'num' => $num,
+                    'price' => $price,
+                    'total_fee' => $totalFee,
+                    'goods_spec_id' => $goodsSpecId,
+                    'spec_ids' => isset($goodsSpec['spec_ids']) ? $goodsSpec['spec_ids'] : '',
+                    'spec_text' => isset($goodsSpec['spec_text']) ? $goodsSpec['spec_text'] : '',
+                    'rebate_score' => isset($goodsSpec['rebate_score']) ? $goodsSpec['rebate_score'] : 0,
+                    'total_rebate_score' => $orderDatas[$goodsSn]['rebate_score'],
+                    'uid' => $uid
+                ];
+
+                // 扣库存
+                if (!ShopGoodsSpec::where(['goods_spec_id' => $goodsSpecId])->dec('stock', $num)->update()) {
+                    throw new Exception('商品库存处理失败');
+                }
+
+                // 扣除商品总库存
+                if (!ShopGoods::where(['goods_id' => $goodsInfo['goods_id']])->dec('inventory', $num)->update()) {
+                    throw new Exception('商品库存处理失败');
+                }
+
+                // 增加销量
+                if (in_array($orderType, [3, 4, 5])) {
+                    if (!ShopGoodsModel::where(['goods_id' => $goodsInfo['goods_id']])
+                        ->inc('sales_volume', $num)
+                        ->inc('real_sales_volume', $num)
+                        ->update()) {
+                        throw new Exception('商品销量处理失败');
+                    }
+                }
+
+                // 清除商品相关实时数据缓存
+                RedisCache::keyDel('caches:goods:list_*');
+                RedisCache::keyDel('caches:goodsSpec:list_*');
+
+            }
+
+            if (empty($orders) || empty($orderGoods)) {
+                throw new Exception('订单数据错误,下单失败');
+            }
+
+            // 批量写入订单数据
+            $data = array_values($orderDatas);
+            if (!$this->model->insertAll($data)) {
+                Db::rollback();
+                throw new Exception('下单失败');
+            }
+
+            // 批量写入订单商品数据
+            $data = array_values($orderGoods);
+            if (!ShopOrderGoodsModel::insertAll($data)) {
+                Db::rollback();
+                throw new Exception('下单失败,订单商品处理错误');
+            }
+
+            // 写入配送单数据
+            $data = array_values($shippingList);
+            if (!ShopOrderShippingModel::insertAll($data)) {
+                Db::rollback();
+                throw new Exception('下单失败,订单配送数据处理错误');
+            }
+
+            // 超时过期订单队列任务处理
+            if (in_array($orderType, [1, 3]) && $orders) {
+                $ex_time = intval(env('common.ORDER_EXPIRES_TIME', 60 * 60));
+                Queue::instance()->log('订单过期执行成功')
+                    ->job(ShopOrderJob::class)
+                    ->do('ShopOrderExpired')
+                    ->secs($ex_time)
+                    ->push([$orders[0]]);
+            }
+
+            // 验证检查购物车数据
+            if ($cartIds && is_array($cartIds)) {
+                ShopCartModel::where('id', 'in', $cartIds)->where(['user_id' => $uid])->delete();
+            }
+
+            // 清除单一购物车数据
+            $cartId = $request->post('cart_id', 0);
+            if ($cartId) {
+                ShopCartModel::where('id', $cartId)->delete();
+            }
+
+            $cacheKey = "caches:orders:u_{$uid}_{$orderType}:create_{$payType}_" . date('YmdHis') . rand(10, 99);
+            RedisCache::set($cacheKey, ['uid' => $uid, 'orderDatas' => $orderDatas, 'shipping' => $shippingList, 'orderGoods' => $orderGoods, 'post' => $request->post()], 7200);
+
+            Db::commit();
+            return $orders;
+        } catch (\Exception $e) {
+            throw new Exception($e->getMessage());
+        }
+    }
+
+    /**
+     * 下单数据
+     * @param $buyGoods 购买商品
+     * @param $request
+     * @return array
+     * @throws Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function buyDetail($buyGoods, $request)
+    {
+        $addressId = $request->post('address_id', 0);
+        if (!$address = UserAddressModel::getAddressIdDetails($addressId, $request->uid, 1)) {
+            throw new Exception('订单地址错误');
+        }
+
+        $details = $goodsSnArr = [];
+        $paymentTotal = 0;  // 总订单金额
+        $totalRebateScore = 0;  // 总返还积分
+        $totalNum = 0;  // 商品总数量
+        $curXzmoney = SystemConfigService::make()->getConfigByName('xz_cur_money', '', 'xzconfig');
+        foreach ($buyGoods as $k => $v) {
+            $goodsSn = isset($v['goods_sn']) ? $v['goods_sn'] : '';
+            $specId = isset($v['spec_id']) ? $v['spec_id'] : 0;
+            $num = isset($v['num']) ? intval($v['num']) : 0;
+            $totalNum += $num;
+            if ($num <= 0) {
+                throw new Exception('下单失败,请先选择商品数量');
+            }
+
+            // 商品信息
+            if (!$goodsSn || !$goodsInfo = ShopGoodsService::make()->getCacheInfo($goodsSn)) {
+                throw new Exception('下单失败,商品参数错误');
+            }
+
+            // 订单商品规格数据
+            $goodsSnArr[] = $goodsSn;
+            $goodsSpec = ShopGoodsSpecService::make()->getDataByGoods($goodsInfo['goods_id'], $specId);
+            $weight = isset($goodsSpec['weight']) ? $goodsSpec['weight'] : 0;
+            $price = isset($goodsSpec['price']) ? $goodsSpec['price'] : 0;
+            if (empty($goodsSpec)) {
+                throw new Exception('下单失败,存在无效商品');
+            }
+
+            // 商品数据
+            $postFee = '0.00';
+            $rebateScore = bcmul($num, $goodsSpec['rebate_score'], 2);
+            $totalFee = bcmul($num, $price, 2);
+            $totalWeight = bcmul($num, $weight, 3);
+
+            // 组装订单商品数据
+            $details[$goodsSn]['total_rebate_score'] = isset($details[$goodsSn]['total_rebate_score']) ? bcadd($details[$goodsSn]['total_rebate_score'], $rebateScore, 3) : $rebateScore; //总返利积分
+            $details[$goodsSn]['total_num'] = isset($details[$goodsSn]['total_num']) ? bcadd($details[$goodsSn]['total_num'], $num, 3) : $num; //总数量
+            $details[$goodsSn]['total_price'] = isset($details[$goodsSn]['total_price']) ? bcadd($details[$goodsSn]['total_price'], $totalFee, 3) : $totalFee; // 总金额
+            $details[$goodsSn]['total_weight'] = isset($details[$goodsSn]['total_weight']) ? bcadd($details[$goodsSn]['total_weight'], $totalWeight, 3) : $totalWeight; // 总重量
+            $details[$goodsSn]['post_fee'] = $postFee;
+            $details[$goodsSn]['payment'] = bcadd($details[$goodsSn]['total_price'], $details[$goodsSn]['post_fee'], 2);
+            $details[$goodsSn]['supplier_name'] = $goodsInfo['supplier_name'];
+
+            $goodsName = isset($goodsInfo['goods_name']) ? $goodsInfo['goods_name'] : '';
+            $goodsName = $goodsName ? str_replace(' ', '', $goodsName) : '';
+            $goodsName = mb_strlen($goodsName, 'utf-8') > 20 ? mb_substr($goodsName, 0, 19, 'utf-8') : $goodsName;
+            $goodsType = isset($goodsInfo['goods_type']) ? $goodsInfo['goods_type'] : 0;
+            if ($goodsType == 5) {
+                $price = getXzPirceWithPrice($curXzmoney, $price);
+                $totalFee = getXzPirceWithPrice($curXzmoney, $totalFee);
+            }
+            $goods = [
+                'goods_sn' => $goodsInfo['goods_sn'],
+                'goods_id' => $goodsInfo['goods_id'],
+                'goods_type' => $goodsType,
+                'goods_name' => $goodsName,
+                'picture' => $goodsSpec['picture'] ?: $goodsInfo['goods_img'],
+                'num' => $num, //数量
+                'price' => $price,//单价
+                'weight' => $goodsSpec['weight'], //重量
+                'total_fee' => $totalFee, //总价
+                'rebate_score' => $goodsSpec['rebate_score'],//单个返利积分
+                'total_items_rebate_score' => $rebateScore,//总返利积分
+                'spec_ids' => $goodsSpec['spec_ids'],
+                'spec_text' => $goodsSpec['spec_text'],
+                'post_template_id' => $goodsInfo['post_template_id'], //是否包邮,否就是运费模板ID
+                'stock' => $goodsSpec['stock'],
+                'supplier_name' => $goodsInfo['supplier_name']
+            ];
+
+            // 参数合计
+            $totalNum += $num;
+            $paymentTotal += bcadd($totalFee, $postFee, 3);
+            $totalRebateScore = bcadd($totalRebateScore, $rebateScore, 3);
+            $details[$goodsSn]['goods'][] = $goods;
+
+        }
+
+        $total = [
+            'count_payment' => sprintf("%.2f", $paymentTotal),
+            'count_rebate_score' => $totalRebateScore,
+            'count_num' => $totalNum,
+        ];
+
+        return ['address' => $address, 'buyDetail' => array_values($details), 'total' => $total];
+    }
+
+    /**
+     * 取消订单
+     */
+    public function cancelOrder($data, $userId)
+    {
+        $nowTime = date('Y-m-d H:i:s', time());
+        $data = $this->model->where(['order_sn' => $data['order_sn'], 'user_id' => $userId])->field('order_id,status,order_sn')->findOrEmpty();
+        if (empty($data)) {
+            throw new Exception('参数错误');
+        }
+        if ($data['status'] != 0) {
+            throw new Exception('订单状态错误');
+        }
+
+        Db::startTrans();
+        if (!$this->model->where(['order_sn' => $data['order_sn']])->save(['status' => 3, 'updated_time' => $nowTime])) {
+            Db::rollback();
+            throw new Exception('取消失败');
+        }
+
+        // 恢复库存
+        $updateStock = $this->model->alias('a')->where(['a.order_sn' => $data['order_sn'], 'a.user_id' => $userId])
+            ->leftJoin('shop_order_goods og', 'og.order_id=a.order_id')
+            ->leftJoin('shop_goods_spec gs', 'gs.goods_spec_id=og.goods_spec_id')
+            ->leftJoin('shop_goods g', 'g.goods_id=og.goods_id')
+            ->update([
+                'g.inventory'=> Db::raw('g.inventory + og.num'),
+                'gs.stock'=> Db::raw('gs.stock + og.num'),
+            ]);
+        if (!$updateStock) {
+            Db::rollback();
+            throw new Exception('取消失败,库存处理错误');
+        }
+
+        Db::commit();
+        return true;
+    }
+
+    /**
+     * 确定订单
+     */
+    public function receiveOrder($data, $userId)
+    {
+        $nowTime = date('Y-m-d H:i:s', time());
+        $data = $this->model->where(['order_sn' => $data['order_sn'], 'user_id' => $userId])->findOrEmpty()->toArray();
+        if (empty($data)) {
+            throw new Exception('参数错误');
+        }
+        if ($data['status'] != 2) {
+            throw new Exception('订单状态错误');
+        }
+
+        return $this->model->where(['order_sn' => $data['order_sn']])->save(['status' => 4, 'updated_time' => $nowTime]);
+    }
+
+    /**
+     * 计算运费
+     * @param $address 用户地址
+     * @param $data 商品数据
+     * @param $express_template_id 模板ID
+     * 获取运费
+     */
+    public function postFee($address, $data, $express_template_id)
+    {
+        //获取运费模板
+        $expressDelivery = ExpressDeliveryService::make()->getDataByTemplate($express_template_id);
+        //获取运费模板列表
+        $expressDeliveryItems = ExpressShippingMethodService::make()->getListByTemplate($express_template_id);
+
+        $price_method_field = '';
+        switch ($expressDelivery['price_method']) {
+            case 1:
+                $price_method_field = $data['total_price']; //按金额
+                break;
+            case 2:
+                $price_method_field = $data['num'];  //按件数
+                break;
+            case 3:
+                $price_method_field = $data['total_weight']; //按重量
+                break;
+            default:
+                break;
+        }
+
+        if ($expressDelivery['price_method'] == 1) {
+
+            //计算默认运费 【配送区域、免运费金额、达标时运费、未达标时运费;】
+            if ($price_method_field < $expressDelivery['default_free_price']) {
+                $default_postfee = $expressDelivery['default_fastandard_price'];
+            } else {
+                //达标时运费
+                $default_postfee = $expressDelivery['default_price'];
+            }
+
+            if (empty($expressDeliveryItems)) {
+                return $default_postfee;
+            }
+
+            //按指定地区计算邮费
+            foreach ($expressDeliveryItems as $k => $node) {
+                if (in_array($address, explode(',', $node['address_items_ids']))) {
+                    //未达标运费
+                    if ($price_method_field < $node['free_price']) {
+                        return $node['fastandard_price'];
+                    } else {
+                        //达标时运费
+                        return $node['price'];
+                    }
+                } else {
+                    return $default_postfee;
+                }
+            }
+        } else {
+            //按件数 重量计费 【配送区域、首件、达标时运费、续件、未达标时运费】
+            //计算默认运费
+            if ($price_method_field <= $expressDelivery['default_pieces']) {
+                $default_postfee = $expressDelivery['default_price'];
+            } else {
+                //达到续件条件时的运费
+                if ($expressDelivery['add_pieces']) {
+                    //配置过续件费用
+                    //续件数 =  (下单件数 - 首件) /续件数
+                    //总运费 =  首件运费 + (续件数 * 续件费用)
+                    $add_pieces = bcdiv(($price_method_field - $expressDelivery['default_pieces']), $expressDelivery['add_pieces'], 2);
+                    $default_postfee = bcadd($expressDelivery['default_price'], bcmul($add_pieces, $expressDelivery['add_price'], 2), 2);
+                } else {
+                    //未配置过续件,按首件价格
+                    $default_postfee = $expressDelivery['default_price'];
+                }
+            }
+            if (empty($expressDeliveryItems)) {
+                return $default_postfee;
+            }
+            //按指定地区计算邮费
+            foreach ($expressDeliveryItems as $k => $node) {
+                if (in_array($address, explode(',', $node['address_items_ids']))) {
+                    //未达到续件条件时的运费
+                    if ($price_method_field <= $node['pieces']) {
+                        return $node['price'];
+                    } else {
+                        //达到续件条件时的运费
+                        if ($node['add_pieces']) {
+                            //配置过续件费用
+                            //续件数 =  (下单件数 - 首件) /续件数
+                            //总运费 =  首件运费 + (续件数 * 续件费用)
+                            $add_pieces = bcdiv(($price_method_field - $node['pieces']), $node['add_pieces'], 2);
+                            return bcadd($node['price'], bcmul($add_pieces, $node['add_price'], 2), 2);
+                        } else {
+                            //未配置过续件,按首件价格
+                            return $node['price'];
+                        }
+                    }
+                } else {
+                    return $default_postfee;
+                }
+            }
+        }
+    }
+
+    /**
+     * 验证商品库存
+     * @param $data
+     * @return bool
+     */
+    public function checkStock($data)
+    {
+        foreach ($data as $key => $value) {
+            $num = array_sum(array_column($value, 'num'));
+            $goodsSn = isset($value[0]['goods_sn']) ? $value[0]['goods_sn'] : 0;
+            $specId = isset($value[0]['spec_id']) ? $value[0]['spec_id'] : 0;
+            if ($goodsSn && $specId && $num > 0) {
+                $stock = ShopGoodsSpecService::make()->getStock($goodsSn, $specId);
+                if ($num > $stock) {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+
+        }
+
+        return true;
+    }
+
+    /**
+     * 2小时内未付款订单数
+     * @param $uid
+     * @param int $orderType
+     * @param int $status
+     * @return array|int|mixed
+     * @throws \think\db\exception\DbException
+     */
+    public function checkBuyCountByTime($uid, $orderType = 1, $status = 0)
+    {
+        $cacheKey = "caches:orders:unpayCount:{$uid}_{$orderType}_{$status}";
+        $data = RedisCache::get($cacheKey);
+        if ($data) {
+            return $data;
+        }
+
+        $data = $this->model->where(['user_id' => $uid, 'order_type' => $orderType, 'status' => $status])
+            ->whereTime('created_time', '-2 hours')
+            ->count('order_id');
+        if ($data) {
+            RedisCache::set($cacheKey, $data, rand(3, 5));
+        }
+
+        return $data;
+    }
+
+    /**
+     * 根据单号获取订单信息(有缓存)
+     * @param $orderSn 订单号
+     * @param int $uid 所属用户ID
+     * @param int $status 状态
+     * @param string $field 返沪字段
+     * @param bool $cache 是否缓存,默认是
+     * @return array|mixed
+     */
+    public function getInfoBySn($orderSn, $uid=0, $status=0, $field='', $cache=false)
+    {
+        $cacheKey = "caches:orders:info:sn{$orderSn}_{$uid}".($field? '_'.md5($field):'');
+        $data = RedisCache::get($cacheKey);
+        if($data && $cache){
+            return $data;
+        }
+        $where = ['order_sn'=> $orderSn];
+        if($uid>0){
+            $where['user_id'] = $uid;
+        }
+
+        if($status>0){
+            $where['status'] = $status;
+        }
+
+        $field = $field? $field : 'order_id,order_sn,user_id,payment,total_price,status,pay_type,rebate_score,rebate_money,num,order_type,give_vip';
+        $data = $this->model->where($where)->field($field)->findOrEmpty();
+        $data = $data? $data->toArray() : [];
+        if($data && $cache){
+            RedisCache::set($cacheKey, $data, rand(5,10));
+        }
+
+        return $data;
+    }
+
+    /**
+     * 根据单号获取订单信息(有缓存)
+     * @param $orderSn 订单号
+     * @param int $uid 所属用户ID
+     * @param string $field 返沪字段
+     * @param bool $cache 是否缓存,默认是
+     * @return array|mixed
+     */
+    public function getInfoById($orderId, $uid=0, $field='', $cache=true)
+    {
+        $cacheKey = "caches:orders:info:id{$orderId}_{$uid}".($field? '_'.md5($field):'');
+        $data = RedisCache::get($cacheKey);
+        if($data && $cache){
+            return $data;
+        }
+        $where = ['order_id'=> $orderId,'user_id'=> 0,'status'=>1];
+        if($uid>0){
+            $where['user_id'] = $uid;
+        }else{
+            unset($where['user_id']);
+        }
+
+        $field = $field? $field : 'order_id,order_sn,user_id,payment,total_price,status,pay_type,rebate_score,rebate_money,num,order_type,give_vip,coupon_number,hg_status,hg_enable';
+        $data = $this->model->where($where)->field($field)->findOrEmpty();
+        $data = $data? $data->toArray() : [];
+        if($data && $cache){
+            RedisCache::set($cacheKey, $data, rand(5,10));
+        }
+
+        return $data;
+    }
+
+    /**
+     * 订单列表
+     */
+    public function getList ($params, $pageSize, $field = '')
+    {
+        $page = request()->post('page', 1);
+        $userId = isset($params['user_id'])? $params['user_id'] : 0;
+        $cacheKey = "caches:orders:list:{$userId}_{$page}_{$pageSize}".($field? '_'.md5($field):'');
+        $data = RedisCache::get($cacheKey);
+        if($data){
+            return $data;
+        }
+
+
+        $where['user_id'] = $userId;
+        $status = isset($params['status'])? $params['status'] : '-1';
+        if($status>=0){
+            $where['status'] = $status;
+        }
+        $orderType = isset($params['order_type'])? $params['order_type'] : 0;
+        if($orderType){
+            $where['order_type'] = $orderType;
+        }
+        $field = $field? $field:'order_id,status,order_sn,order_type,coupon_number,created_time,order_remark,payment,ship_postfee,rebate_score,ship_name,ship_code,ship_number,expires_time,rebate_lock_score';
+        $data = $this->model->where($where)
+            ->whereNotIn('status', [3])
+            ->relation(['withOrderGoods'])
+            ->field($field)
+            ->order('order_id desc')
+            ->paginate($pageSize)
+            ->each(function($v, $k){
+                foreach ($v['withOrderGoods'] as $key=>$val){
+                    $goods_sn = ShopGoods::where('goods_id', $val['goods_id'])->value('goods_sn');
+                    $v['withOrderGoods'][$key]['goods_sn'] = $goods_sn;
+                }
+
+                $v['withOrderShipping'] = ShopOrderShippingService::make()->getInfo($v['order_id']);
+                $v['withOrderExpress'] = ExpressServices::instance()->orderId($v['order_id'])
+                    ->code($v['ship_code'])
+                    ->number($v['ship_number'])
+                    ->find();
+
+                if (intval($v['status']) == 2){
+                    $v['express_url'] =  "https://m.kuaidi100.com/result.jsp?nu=".$v['ship_number'];
+                }else{
+                    $v['express_url'] = "";
+                }
+
+                if ($v['status'] == 1){
+                    if ($v['order_type'] == 1){
+                        $v['can_hg'] = 1;
+                    }
+                }else{
+                    $v['can_hg'] = 2;
+                }
+
+                switch ($v['status']){
+                    case 0:
+                        $v['status_title'] = '待付款';
+                        break;
+                    case 1:
+                        $v['status_title'] = '待发货';
+                        break;
+                    case 2:
+                        $v['status_title'] = '已发货';
+                        break;
+                    case 3:
+                        $v['status_title'] = '已过期';
+                        break;
+                    case 4:
+                        $v['status_title'] = '已完成';
+                        break;
+                    case 5:
+                        $v['status_title'] = '已完成';
+                        break;
+                    case 6:
+                        $v['status_title'] = '已完成';
+                        break;
+                }
+            });
+
+        $data = $data? $data->toArray() : [];
+        if($data['data']){
+            RedisCache::set($cacheKey, $data['data'], rand(2,3));
+        }
+        return $data['data'];
+    }
+}

+ 63 - 0
app/common/service/ShopOrderShippingService.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace app\common\service;
+
+use app\common\model\ShopOrderShippingModel;
+use app\common\model\YsBankListModel;
+use utils\RedisCache;
+
+/**
+ * 订单配送信息 by wes
+ * Class ShopOrderShippingService
+ * @package app\common\service
+ */
+class ShopOrderShippingService
+{
+    protected static $instance = null;
+    protected $model = null;
+
+    public function __construct()
+    {
+        $this->model = new ShopOrderShippingModel();
+    }
+
+    /**
+     * 静态化入口
+     * @return static|null
+     */
+    public static function make()
+    {
+        if (!self::$instance) {
+            self::$instance = new static();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * 根据单号获取订单信息(有缓存)
+     * @param $orderId 订单号
+     * @param string $field 返沪字段
+     * @param bool $cache 是否缓存,默认是
+     * @return array|mixed
+     */
+    public function getInfo($orderId, $field='', $cache=true)
+    {
+        $cacheKey = "caches:orders:shipping:info_{$orderId}".($field? '_'.md5($field):'');
+        $data = RedisCache::get($cacheKey);
+        if($data && $cache){
+            return $data;
+        }
+        $where = ['order_id'=> $orderId];
+        $field = $field? $field : 'sp_id,order_id,sp_name,sp_mobile,sp_province,sp_city,sp_county,sp_remark,sp_mergename';
+        $data = $this->model->where($where)->field($field)->findOrEmpty();
+        $data = $data? $data->toArray() : [];
+        if($data && $cache){
+            RedisCache::set($cacheKey, $data, rand(10,20));
+        }
+
+        return $data;
+    }
+
+
+}

+ 249 - 0
app/common/service/UserService.php

@@ -0,0 +1,249 @@
+<?php
+
+namespace app\common\service;
+
+use app\common\model\MoneyLogModel;
+use app\common\model\UserModel;
+use app\common\model\UserUnmoneyModel;
+use utils\RedisCache;
+
+/**
+ * 用户服务 by wes
+ * Class UserService
+ * @package app\common\service
+ */
+class UserService
+{
+    protected static $instance = null;
+    protected $model = null;
+
+    public function __construct()
+    {
+        $this->model = new UserModel();
+    }
+
+    /**
+     * 静态化入口
+     * @return static|null
+     */
+    public static function make()
+    {
+        if(!self::$instance){
+            self::$instance = new static();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * 用户缓存信息
+     * @param int $uid
+     * @return array|mixed
+     */
+    public function getCacheInfo($uid, $field='', $cache=false)
+    {
+        $cacheKey = "caches:users:info:u{$uid}".($field?'_'.md5($field):'');
+        $data = RedisCache::get($cacheKey);
+        if($data && $cache){
+            return $data;
+        }
+        $where = ['id'=> $uid];
+        $field = $field? $field : 'id,user_name,mobile,level,code,pid,money,score,profit_money,level_type,status,is_auth,is_reward,user_type,store_type,has_fd';
+        $data = $this->model->where($where)->field($field)->findOrEmpty();
+        $data = $data? $data->toArray():[];
+        if($data && $cache){
+            RedisCache::set($cacheKey, $data, rand(3,5));
+        }
+
+        return $data;
+    }
+
+
+    /**
+     * 结算利润到余额
+     * @param $uid
+     * @throws Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function switchProfitToMoney($uid)
+    {
+        $userInfo = UserModel::where('id', $uid)->field('id,mobile,money,profit_money,status')->find();
+        $profitMoney = isset($userInfo['profit_money'])? intval($userInfo['profit_money']): 0;
+        $userMoney = isset($userInfo['money'])? floatval($userInfo['money']): 0;
+        $userStatus = isset($userInfo['status'])? $userInfo['status'] : 0;
+        if(empty($userInfo) || $userStatus != 1){
+            sr_throw('用户参数错误或已被冻结');
+        }
+
+        if ($profitMoney < 100){
+            sr_throw('利润小于100,结算失败');
+        }else{
+            $profitMoney = $profitMoney -  $profitMoney % 100;
+            $updateAccount = UserModel::where('id', $uid)
+                ->inc('money',$profitMoney)
+                ->inc('total_profit_money', $profitMoney)
+                ->dec('profit_money', $profitMoney)
+                ->update();
+            if(!$updateAccount){
+                sr_throw('利润结算失败');
+            }
+
+            $data = [
+                'uid'=>$uid,
+                'type'=> 8,
+                'money'=> $profitMoney,
+                'status'=> 1,
+                'state'=> 2,
+                'before_money'=> $userMoney,
+                'after_money'=> floatval($userMoney+$profitMoney),
+                'remark'=> '利润结算到余额',
+                'create_time'=>date('Y-m-d H:i:s')
+            ];
+            if(!UserUnmoneyModel::insertGetId($data)){
+                sr_throw('利润结算处理失败');
+            }
+        }
+    }
+
+    /**
+     * 用户升级
+     * @return array|string
+     */
+    public function updateLevel()
+    {
+        $cacheKey = "caches:users:updateLevel:".date('YmdHi');
+        if(RedisCache::get($cacheKey.'_lock')){
+            return '请不要频繁提交处理';
+        }
+        Db::startTrans();
+        try {
+            RedisCache::setnx($cacheKey . 'lock', date('Y-m-d H:i:s'), rand(5, 10));
+            $list = $this->model->where('has_update_level', '>', 0)
+                ->field('id,level,pid,path')
+                ->select();
+            $list = $list? $list->toArray() : [];
+            if (empty($list)) {
+                return '暂无用户需要升级';
+            }
+
+            // 等级配置
+            $levelConfig = LevelSettingService::make()->getConfigData(0, 1);
+            if (empty($levelConfig)) {
+                return '请先配置等级升级参数';
+            }
+
+            // 处理升级
+            $noCatchUids = []; // 未升级的用户
+            $levelIds = []; // 升级了的用户
+            foreach ($list as $item) {
+                $uid = isset($item['id']) ? $item['id'] : 0;
+                $path = isset($item['path']) ? $item['path'] : 0;
+                $userLevel = isset($item['level']) ? intval($item['level']) : 0;
+
+                // 判断团队人数获取可升等级
+                $updateLevel = 0;
+                $teamCount = $this->getTeamCount($uid, $path);  // 团队人数
+                foreach ($levelConfig as $val) {
+                    $needTeamCount = isset($val['team_num']) ? $val['team_num'] : 0;
+                    $level = isset($val['level']) ? $val['level'] : 0;
+                    // 如果团队人数满足当前等级要求(排除)
+                    if ($level > 0 && $needTeamCount && $teamCount >= $needTeamCount) {
+                        $updateLevel = $level;
+                    }
+                }
+
+                // 如果当前用户需要升级
+                $ztCount = 0;
+                if ($updateLevel > $userLevel) {
+                    // 所需直推等级
+                    $ztLevel = isset($levelConfig[$updateLevel]['zt_level']) ? intval($levelConfig[$updateLevel]['zt_level']) : 0;
+                    $ztCount = $this->getZtCount($uid, $ztLevel);
+
+                    // 可升级的等级所需直推人数
+                    $needZtCount = isset($levelConfig[$updateLevel]['zt_num']) ? intval($levelConfig[$updateLevel]['zt_num']) : 0;
+                    if ($needZtCount <= $ztCount) {
+                        // 当前用户升级,且去掉可升级状态
+                        $this->model->where('id', $uid)->update(['level' => $updateLevel, 'has_update_level' => 0, 'update_time' => date('Y-m-d H:i:s')]);
+                        $levelIds[] = $uid;
+                    }else{
+                        $noCatchUids[] = $uid;
+                    }
+                }else{
+                    $noCatchUids[] = $uid;
+                }
+
+                // 用户升级后,处理上层用户升级(设置为可升级状态)
+                if ($path && $updateLevel>$userLevel) {
+                    $this->model->whereIn('id', explode(',', $path))->update(['has_update_level' => 1, 'update_time' => date('Y-m-d H:i:s')]);
+                }
+
+                // 处理缓存
+                RedisCache::set("caches:users:updateLevel:user_{$uid}:".date('YmdHi'), ['msg'=>'升级处理成功','data'=> $val,'team'=>$teamCount,'zt'=>$ztCount,'updateLevel'=> $updateLevel,'config'=>$levelConfig,'date'=>date('Y-m-d H:i:s')], 3600);
+            }
+
+            // 如果存在未升级用户清除升级状态
+            if($noCatchUids){
+                $this->model->whereIn('id', $noCatchUids)->update(['has_update_level' => 0, 'update_time' => date('Y-m-d H:i:s')]);
+            }
+
+            Db::commit();
+            RedisCache::set($cacheKey.'_success', ['msg'=>'升级处理成功','total'=> count($list),'level'=>count($levelIds),'date'=>date('Y-m-d H:i:s')], 7200);
+            return ['code'=>200, 'msg'=>"处理升级成功,累计处理用户".count($list)."个,升级用户".count($levelIds).'个'];
+        } catch (\Exception $exception){
+            Db::rollback();
+            RedisCache::set($cacheKey.'_fail', ['msg'=> $exception->getMessage(),'trace'=> $exception->getTrace(),'date'=>date('Y-m-d H:i:s')], 7200);
+            return ['code'=>500, 'msg'=>"处理升级失败:".$exception->getMessage()];
+        }
+    }
+
+    /**
+     * 统计有效直推人数
+     * @param $uid
+     * @param int $level 等级
+     * @return array|int|mixed
+     * @throws \think\db\exception\DbException
+     */
+    public function getZtCount($uid, $level = 0)
+    {
+        $cacheKey = "caches:users:counts:zt_num_{$uid}_{$level}";
+        $data = RedisCache::get($cacheKey);
+        if($data){
+            return $data;
+        }
+        $where = ['pid'=> $uid,'has_fd'=>1];
+        if($level>0){
+            $where['level'] = $level;
+        }
+        $data = $this->model->where($where)->count('id');
+        if($data){
+            RedisCache::set($cacheKey, $data, rand(10,20));
+        }
+        return $data;
+    }
+
+    /**
+     * 统计有效团队人数
+     * @param $uid 用户ID
+     * @param $path
+     * @return array|int|mixed
+     * @throws \think\db\exception\DbException
+     */
+    public function getTeamCount($uid, $path)
+    {
+        $cacheKey = "caches:users:counts:team_num_{$uid}";
+        $data = RedisCache::get($cacheKey);
+        if($data){
+            return $data;
+        }
+
+        $data = $this->model->where('path','like',"%{$path}")
+            ->where('has_fd',1)
+            ->count('id');
+        if($data){
+            RedisCache::set($cacheKey, $data, rand(10,20));
+        }
+        return $data;
+    }
+}

+ 87 - 0
app/common/service/UserUnmoneyService.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace app\common\service;
+
+use app\common\model\UserUnmoneyModel;
+use app\common\model\YsBankListModel;
+use utils\RedisCache;
+
+/**
+ * 用户利润 by wes
+ * Class UserUnmoneyService
+ * @package app\common\service
+ */
+class UserUnmoneyService
+{
+    protected static $instance = null;
+    protected $model = null;
+
+    public function __construct()
+    {
+        $this->model = new UserUnmoneyModel();
+    }
+
+    /**
+     * 静态化入口
+     * @return static|null
+     */
+    public static function make()
+    {
+        if (!self::$instance) {
+            self::$instance = new static();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * 获取列表
+     * @param $params 查询参数
+     * @param int $pageSize
+     * @param string $field
+     * @param string $cache
+     * @return array|mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function getList($params, $pageSize = 10, $field = '', $cache=true)
+    {
+        $page = request()->post('page', 1);
+        $cacheKey = "caches:temp:profitLogs:{$page}_{$pageSize}_" . md5(json_encode($params));
+        $data = RedisCache::get($cacheKey);
+        if ($data && $cache) {
+            return $data;
+        }
+
+        $typeArr = config('type.profit');
+        $where = [];
+        $type = isset($params['type'])? intval($params['type']) : 0;
+        if($type){
+            $where['type'] = $type;
+        }
+
+        $uid = isset($params['uid'])? intval($params['uid']) : 0;
+        if($uid){
+            $where['uid'] = $uid;
+        }
+
+        $field = $field ? $field : 'id,uid,money,status,state,type,create_time as create_at,remark';
+        $data = $this->model->where($where)->where(function ($query) use ($params) {
+                    $time = isset($params['time']) ? trim($params['time']) : '';
+                })
+                ->field($field)
+                ->withAttr('type', function ($value, $data) use ($typeArr) {
+                    return isset($typeArr[$value]) ? $typeArr[$value] : '未知类型';
+                })
+                ->order('id desc')
+                ->page($page, $pageSize)->select();
+        $data = $data? $data->toArray() : [];
+        if($data && $cache){
+            RedisCache::set($cacheKey, $data, rand(3,5));
+        }
+
+        return $data;
+    }
+
+}

+ 71 - 0
app/common/service/WithdrawAccountService.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace app\common\service;
+
+use app\common\model\WithdrawAccountModel;
+use app\common\model\YsBankListModel;
+use utils\RedisCache;
+
+/**
+ * 提现账号 by wes
+ * Class WithdrawAccountService
+ * @package app\common\service
+ */
+class WithdrawAccountService
+{
+    protected static $instance = null;
+    protected $model = null;
+
+    public function __construct()
+    {
+        $this->model = new WithdrawAccountModel();
+    }
+
+    /**
+     * 静态化入口
+     * @return static|null
+     */
+    public static function make()
+    {
+        if (!self::$instance) {
+            self::$instance = new static();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * 获取银行卡列表
+     * @param $uid 查询参数
+     * @param string $field
+     * @param string $cache
+     * @return array|mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function getListByUser($uid, $type=0, $field = '', $cache=true)
+    {
+        $cacheKey = "caches:temp:withdrawAccount:{$uid}_{$type}" . md5($field);
+        $data = RedisCache::get($cacheKey);
+        if ($data && $cache) {
+            return $data;
+        }
+
+        $where = ['uid'=> $uid];
+        if($type){
+            $where['type'] = $type;
+        }
+        $field = $field ? $field : 'id,name,number,bank_subname,create_time';
+        $data = $this->model->where($where)->where(['is_del' => 2])
+                ->field($field)
+                ->select();
+        $data = $data? $data->toArray() : [];
+        if($data && $cache){
+            RedisCache::set($cacheKey, $data, 5,10);
+        }
+
+        return $data;
+    }
+
+}