diff --git a/app/admin/controller/Statistics.php b/app/admin/controller/Statistics.php index 35c18a0..cf084ec 100755 --- a/app/admin/controller/Statistics.php +++ b/app/admin/controller/Statistics.php @@ -43,22 +43,39 @@ class Statistics extends Base if ($type) { $where[] = ['type', '=', $type]; } + return View::fetch("Statistics/profit"); + } +/** + * 盒子利润统计数据接口(用于前端分离模式) + * @param Request $request + * @return \think\response\Json + */ + public function profitData(Request $request) + { + $goodId = trim(input('get.goodId')); + $title = trim(input('get.title')); + $status = trim(input('get.status')); + $type = trim(input('get.type')); + $addtime = trim(input('get.addtime')); + $page = intval(input('page', 1)); + $limit = intval(input('limit', 20)); - // 解析时间范围 - $startTime = 0; - $endTime = time(); - if ($addtime) { - $times = explode(' - ', $addtime); - $startTime = strtotime($times[0]); - $endTime = strtotime($times[1]); + // 构建查询条件 + $where = [['delete_time', '=', null]]; + if ($goodId) { + $where[] = ['id', '=', $goodId]; + } + if ($title) { + $where[] = ['title', 'like', '%' . $title . '%']; + } + if ($status) { + $where[] = ['status', '=', $status]; + } + if ($type) { + $where[] = ['type', '=', $type]; } - // 获取测试用户ID - $testUsers = User::where('istest', '>', 0)->column('id'); - $testUserIds = empty($testUsers) ? [0] : $testUsers; - $testUserIdsStr = implode(',', $testUserIds); - - // 构建SQL查询 + // 构建SQL查询,只返回基本信息 $query = Db::name('goods')->alias('goods') ->field([ 'goods.id', @@ -67,54 +84,70 @@ class Statistics extends Base 'goods.price', 'goods.stock', 'goods.status', - 'goods.type', - '(SELECT COALESCE(sum(use_money), 0) + COALESCE(sum(price), 0) FROM `order` WHERE status=1 AND (price>0 OR use_money>0) AND pay_time > ' . $startTime . ' and pay_time < ' . $endTime . ' AND goods_id=goods.id AND status=1 AND user_id NOT IN (' . $testUserIdsStr . ')) AS use_money', - '(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS sc_money', - '(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND LENGTH(recovery_num)>0 AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS re_money', - '(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND LENGTH(send_num)>0 AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS fh_money', - '(SELECT count(1) FROM `order_list` WHERE goods_id=goods.id AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ' and parent_goods_list_id=0 ) AS cj_count' + 'goods.type' ]) ->where($where) ->order(['goods.id' => 'desc']); - // 获取列表数据 - $list = $query->select()->toArray(); + // 获取总数量 + $total = $query->count(); + + // 分页 + $list = $query->page($page, $limit)->select()->toArray(); - // 计算总金额 - $totalIncome = 0; - $totalCost = 0; - $totalProfit = 0; - $totalReMoney = 0; - $totalFhMoney = 0; - - foreach ($list as &$item) { - // 计算单个盒子的利润和利润率 - $item['profit'] = $item['use_money'] - $item['sc_money']; - $item['profit_rate'] = $item['use_money'] > 0 ? round(($item['profit'] / $item['use_money']) * 100, 2) : 0; - $item['is_negative'] = $item['profit'] < 0; - - // 计算总金额 - $totalIncome += $item['use_money']; - $totalCost += $item['sc_money']; - $totalReMoney += $item['re_money']; - $totalFhMoney += $item['fh_money']; + // 获取盒子类型名称 + $typesList = $this->getGoodsTypes(); + $typesMap = []; + foreach ($typesList as $item) { + $typesMap[$item['value']] = $item['fl_name']; } - $totalProfit = $totalIncome - $totalCost; + // 初始化统计数据为0 + foreach ($list as &$item) { + // 添加类型名称 + $item['type_name'] = isset($typesMap[$item['type']]) ? $typesMap[$item['type']] : '未知类型'; + + // 初始化统计字段为0,后续会异步加载 + $item['use_money'] = 0; + $item['sc_money'] = 0; + $item['re_money'] = 0; + $item['fh_money'] = 0; + $item['cj_count'] = 0; + $item['profit'] = 0; + $item['profit_rate'] = 0; + $item['is_negative'] = false; + $item['loaded'] = false; // 标记是否已加载统计数据 + } - // 传递数据给视图 - View::assign([ - 'list' => $list, - 'totalIncome' => $totalIncome, - 'totalCost' => $totalCost, - 'totalProfit' => $totalProfit, - 'totalReMoney' => $totalReMoney, - 'totalFhMoney' => $totalFhMoney, + // 返回JSON数据 + return json([ + 'code' => 0, + 'msg' => '获取数据成功', + 'count' => $total, + 'data' => $list, + 'summary' => [ + 'totalIncome' => 0, + 'totalCost' => 0, + 'totalProfit' => 0, + 'totalReMoney' => 0, + 'totalFhMoney' => 0, + ] ]); - - return View::fetch("Statistics/profit"); } + /** + * 获取盒子类型列表(用于API调用) + */ + private function getGoodsTypes() + { + $types = Db::name('goods_type') + ->field('value, fl_name, remark') + ->where('is_fenlei', 1) + ->order('sort_order asc') + ->select() + ->toArray(); + return $types; + } /** * 解析时间范围 */ @@ -616,4 +649,63 @@ class Statistics extends Base return View::fetch('Statistics/productsOverview'); } + /** + * 获取单个盒子的统计数据 + * @param Request $request + * @return \think\response\Json + */ + public function getBoxStatistics(Request $request) + { + $goodId = intval(input('get.goods_id', 0)); + $addtime = trim(input('get.addtime', '')); + + if ($goodId <= 0) { + return json(['code' => 1, 'msg' => '参数错误']); + } + + // 解析时间范围 + $startTime = 0; + $endTime = time(); + if ($addtime) { + $times = explode(' - ', $addtime); + $startTime = strtotime($times[0]); + $endTime = strtotime($times[1]); + } + + // 获取测试用户ID + $testUsers = User::where('istest', '>', 0)->column('id'); + $testUserIds = empty($testUsers) ? [0] : $testUsers; + $testUserIdsStr = implode(',', $testUserIds); + + // 查询单个盒子的统计数据 + $data = Db::name('goods')->alias('goods') + ->field([ + 'goods.id', + '(SELECT COALESCE(sum(use_money), 0) + COALESCE(sum(price), 0) FROM `order` WHERE status=1 AND (price>0 OR use_money>0) AND pay_time > ' . $startTime . ' and pay_time < ' . $endTime . ' AND goods_id=goods.id AND status=1 AND user_id NOT IN (' . $testUserIdsStr . ')) AS use_money', + '(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS sc_money', + '(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND LENGTH(recovery_num)>0 AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS re_money', + '(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND LENGTH(send_num)>0 AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS fh_money', + '(SELECT count(1) FROM `order_list` WHERE goods_id=goods.id AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ' and parent_goods_list_id=0 ) AS cj_count' + ]) + ->where('goods.id', '=', $goodId) + ->find(); + + if (!$data) { + return json(['code' => 1, 'msg' => '盒子不存在']); + } + + // 处理数值,确保为浮点型 + $data['use_money'] = floatval($data['use_money']); + $data['sc_money'] = floatval($data['sc_money']); + $data['re_money'] = floatval($data['re_money']); + $data['fh_money'] = floatval($data['fh_money']); + + // 计算利润和利润率 + $data['profit'] = $data['use_money'] - $data['sc_money']; + $data['profit_rate'] = $data['use_money'] > 0 ? round(($data['profit'] / $data['use_money']) * 100, 2) : 0; + $data['is_negative'] = $data['profit'] < 0; + + return json(['code' => 0, 'msg' => '获取成功', 'data' => $data]); + } + } diff --git a/app/admin/route/app.php b/app/admin/route/app.php index bdc14d9..46d2aa6 100755 --- a/app/admin/route/app.php +++ b/app/admin/route/app.php @@ -415,4 +415,15 @@ Route::get('sign_config_coupons', 'SignConfig/getCoupons'); #UserRank.php用户排行榜 #============================ Route::rule('user_rank', 'UserRank/index', 'GET'); -Route::rule('user_rank_data', 'UserRank/getRankData', 'GET'); \ No newline at end of file +Route::rule('user_rank_data', 'UserRank/getRankData', 'GET'); + +#============================ +#Statistics.php统计管理 +#============================ +Route::rule('statistics_profit', 'Statistics/profit', 'GET'); +Route::rule('statistics/profitData', 'Statistics/profitData', 'GET'); +Route::rule('statistics_order', 'Statistics/orderList', 'GET'); +Route::rule('statistics_orderList', 'Statistics/goodsList', 'GET'); +Route::rule('statistics_exchangeList', 'Statistics/exchangeList', 'GET'); +Route::rule('statistics_shipmentList', 'Statistics/shipmentList', 'GET'); +Route::rule('statistics_productsOverview', 'Statistics/productsOverview', 'GET'); \ No newline at end of file diff --git a/app/admin/view/Statistics/profit.html b/app/admin/view/Statistics/profit.html index 727f827..eb286ae 100755 --- a/app/admin/view/Statistics/profit.html +++ b/app/admin/view/Statistics/profit.html @@ -4,27 +4,25 @@
-
+
- +
- +
- - - - + + +
@@ -37,13 +35,12 @@
- +
-
@@ -52,12 +49,12 @@
-
+
总收入
- ¥ {$totalIncome|default="0"|round=2} + ¥ 0.00
@@ -65,15 +62,15 @@
总出货价值
- ¥ {$totalCost|default="0"|round=2} + ¥ 0.00
总利润
-
- ¥ {$totalProfit|default="0"|round=2} +
+ ¥ 0.00
@@ -81,88 +78,33 @@
总兑换/发货价值
- ¥ {$totalReMoney|default="0"|round=2} / - ¥ {$totalFhMoney|default="0"|round=2} + ¥ 0.00 / + ¥ 0.00
- - - - - - - - - - - - - - - - - - - {volist name="list" id="vo"} - - - - - - - - - - - - - - - {/volist} - {if condition="empty($list)"} - - - - {/if} - -
盒子ID盒子名称盒子类型/状态盒子单价抽奖次数收入出货价值兑换价值发货价值利润利润率操作
{$vo.id}{$vo.title} - - / - {if condition="$vo.status eq 1"} - 上架 - {elseif condition="$vo.status eq 2"} - 下架 - {elseif condition="$vo.status eq 3"} - 售罄 - {/if} - ¥ {$vo.price}{$vo.cj_count} - ¥ {$vo.use_money|default="0"|round=2} - - ¥ {$vo.sc_money|default="0"|round=2} - - ¥ {$vo.re_money|default="0"|round=2} - - ¥ {$vo.fh_money|default="0"|round=2} - - = 0"}layui-bg-green{else}layui-bg-red{/if}"> - ¥ {$vo.profit|default="0"|round=2} - - - = 0"}layui-bg-green{else}layui-bg-red{/if}"> - {$vo.profit_rate|default="0"|round=2}% - - - - -
暂时没有数据!
+
+ + + + + + + +
@@ -173,6 +115,7 @@ var layer = layui.layer; var laydate = layui.laydate; var form = layui.form; + var table = layui.table; // 日期时间范围 laydate.render({ @@ -180,7 +123,7 @@ type: 'datetime', range: true }); - + // 加载盒子类型数据 function loadGoodsTypes() { $.ajax({ @@ -192,102 +135,294 @@ var html = ''; $.each(res.data, function (index, item) { - if(item.value == '{$Request.get.type}'){ - html += ''; - } else { - html += ''; - } + html += ''; }); $('#goodsType').html(html); form.render('select'); - - // 更新表格中的类型显示 - $('button[data-type]').each(function() { - var type = $(this).data('type'); - var typeInfo = res.data.find(function(item) { - return item.value == type; - }); - if (typeInfo) { - $(this).text(typeInfo.fl_name); - } else { - $(this).text('未知类型'); - } - }); } } }); } + // 全局变量,存储汇总数据 + var summaryData = { + totalIncome: 0, + totalCost: 0, + totalProfit: 0, + totalReMoney: 0, + totalFhMoney: 0 + }; + + // 全局变量,用于控制异步加载 + var loadingQueue = []; + var isLoading = false; + var maxConcurrentRequests = 3; // 最大并发请求数 + + // 初始化表格 + var profitTable = table.render({ + elem: '#profitTable', + url: '{:url("/admin/statistics/profitData")}', + method: 'get', + page: true, + limit: 20, + where: getSearchParams(), + parseData: function(res) { + if (res.code === 0) { + // 清空汇总数据 + summaryData = { + totalIncome: 0, + totalCost: 0, + totalProfit: 0, + totalReMoney: 0, + totalFhMoney: 0 + }; + + // 更新统计摘要数据为0 + updateStatisticsSummary(summaryData); + + return { + "code": 0, + "msg": res.msg, + "count": res.count, + "data": res.data + }; + } + return { + "code": res.code, + "msg": res.msg, + "count": 0, + "data": [] + }; + }, + cols: [[ + {field: 'id', title: '盒子ID', width: 80, sort: true, fixed: 'left'}, + {field: 'title', title: '盒子名称', width: 180}, + {templet: function(d) { + var statusHtml = ''; + if(d.status == 1) { + statusHtml = '上架'; + } else if(d.status == 2) { + statusHtml = '下架'; + } else if(d.status == 3) { + statusHtml = '售罄'; + } + return '
/ ' + statusHtml + '
'; + }, title: '盒子类型/状态', width: 160}, + {field: 'price', title: '盒子单价', width: 100, templet: '
¥ {{d.price}}
'}, + {field: 'cj_count', title: '抽奖次数', width: 100, templet: function(d) { + if (!d.loaded) { + return '
'; + } + return '
' + d.cj_count + '
'; + }}, + {field: 'use_money', title: '收入', width: 110, templet: function(d) { + if (!d.loaded) { + return '
'; + } + return '
¥ ' + d.use_money.toFixed(2) + '
'; + }}, + {field: 'sc_money', title: '出货价值', width: 110, templet: function(d) { + if (!d.loaded) { + return '
'; + } + return '
¥ ' + d.sc_money.toFixed(2) + '
'; + }}, + {field: 're_money', title: '兑换价值', width: 110, templet: function(d) { + if (!d.loaded) { + return '
'; + } + return '
¥ ' + d.re_money.toFixed(2) + '
'; + }}, + {field: 'fh_money', title: '发货价值', width: 110, templet: function(d) { + if (!d.loaded) { + return '
'; + } + return '
¥ ' + d.fh_money.toFixed(2) + '
'; + }}, + {field: 'profit', title: '利润', width: 110, templet: function(d) { + if (!d.loaded) { + return '
'; + } + var colorClass = d.profit >= 0 ? 'layui-bg-green' : 'layui-bg-red'; + return '
¥ ' + d.profit.toFixed(2) + '
'; + }}, + {field: 'profit_rate', title: '利润率', width: 100, templet: function(d) { + if (!d.loaded) { + return '
'; + } + var colorClass = d.profit_rate >= 0 ? 'layui-bg-green' : 'layui-bg-red'; + return '
' + d.profit_rate.toFixed(2) + '%
'; + }}, + {title: '操作', width: 110, toolbar: '#operationTpl', fixed: 'right'} + ]], + done: function(res) { + // 清空加载队列 + loadingQueue = []; + isLoading = false; + + // 将所有数据项添加到加载队列中 + var tableData = table.cache.profitTable || []; + for (var i = 0; i < tableData.length; i++) { + if (!tableData[i].loaded) { + loadingQueue.push(tableData[i]); + } + } + + // 开始加载统计数据 + processLoadingQueue(); + } + }); + + // 处理加载队列 + function processLoadingQueue() { + if (isLoading || loadingQueue.length === 0) return; + + isLoading = true; + var requests = 0; + var activeRequests = 0; + + // 获取当前需要处理的项目 + while (requests < maxConcurrentRequests && loadingQueue.length > 0) { + var item = loadingQueue.shift(); + requests++; + activeRequests++; + + loadBoxStatistics(item, function() { + activeRequests--; + if (activeRequests === 0) { + isLoading = false; + processLoadingQueue(); + } + }); + } + } + + // 加载单个盒子的统计数据 + function loadBoxStatistics(item, callback) { + var params = getSearchParams(); + + $.ajax({ + url: '{:url("/admin/statistics/getBoxStatistics")}', + type: 'GET', + data: { + goods_id: item.id, + addtime: params.addtime + }, + success: function(res) { + if (res.code === 0) { + var data = res.data; + + // 更新表格中的数据 + var tableData = table.cache.profitTable; + for (var i = 0; i < tableData.length; i++) { + if (tableData[i].id === item.id) { + tableData[i].use_money = data.use_money; + tableData[i].sc_money = data.sc_money; + tableData[i].re_money = data.re_money; + tableData[i].fh_money = data.fh_money; + tableData[i].cj_count = data.cj_count; + tableData[i].profit = data.profit; + tableData[i].profit_rate = data.profit_rate; + tableData[i].is_negative = data.is_negative; + tableData[i].loaded = true; + + // 更新汇总数据 + summaryData.totalIncome += data.use_money; + summaryData.totalCost += data.sc_money; + summaryData.totalReMoney += data.re_money; + summaryData.totalFhMoney += data.fh_money; + summaryData.totalProfit = summaryData.totalIncome - summaryData.totalCost; + + // 更新统计摘要 + updateStatisticsSummary(summaryData); + + // 设置背景色 + if (data.is_negative) { + $('tr[data-index="' + i + '"]').css('background-color', '#ffebee'); + } + + break; + } + } + + // 重新渲染表格 + table.render({ + elem: '#profitTable', + data: tableData, + page: false, + cols: profitTable.config.cols + }); + } + + if (callback) callback(); + }, + error: function() { + if (callback) callback(); + } + }); + } + + // 更新统计摘要 + function updateStatisticsSummary(summary) { + if (!summary) return; + + $('#totalIncome').text(summary.totalIncome.toFixed(2)); + $('#totalCost').text(summary.totalCost.toFixed(2)); + $('#totalProfit').text(summary.totalProfit.toFixed(2)); + $('#totalReMoney').text(summary.totalReMoney.toFixed(2)); + $('#totalFhMoney').text(summary.totalFhMoney.toFixed(2)); + + // 设置利润颜色 + var profitElem = $('#totalProfit'); + if (summary.totalProfit >= 0) { + profitElem.parent().css('color', '#5FB878'); + } else { + profitElem.parent().css('color', '#FF5722'); + } + } + + // 获取搜索参数 + function getSearchParams() { + return { + goodId: $('#goodId').val(), + title: $('#title').val(), + status: $('#status').val(), + type: $('#goodsType').val(), + addtime: $('#addtime').val() + }; + } + + // 搜索按钮点击事件 + $('#searchBtn').on('click', function() { + profitTable.reload({ + where: getSearchParams(), + page: { + curr: 1 + } + }); + }); + + // 监听表格工具条事件 + table.on('tool(profitTable)', function(obj) { + var data = obj.data; + if (obj.event === 'viewProductsOverview') { + // 查看出货概览 + layer.open({ + type: 2, + title: '出货概览', + shadeClose: false, + shade: 0.3, + area: ['90%', '90%'], + content: '{:url("/admin/statistics_productsOverview")}?goods_id=' + data.id + }); + } + }); + // 页面加载完成后执行 $(function() { loadGoodsTypes(); }); }); - - // 查看支付订单列表 - function viewOrders(goodsId) { - var url = "{:url('/admin/statistics_order')}?goods_id=" + goodsId; - layer.open({ - type: 2, - title: '支付订单列表', - shadeClose: false, - shade: 0.3, - area: ['90%', '90%'], - content: url - }); - } - - // 查看出货列表 - function viewOrderLists(goodsId) { - var url = "{:url('/admin/statistics_orderList')}?goods_id=" + goodsId; - layer.open({ - type: 2, - title: '出货列表', - shadeClose: false, - shade: 0.3, - area: ['90%', '90%'], - content: url - }); - } - - // 查看兑换列表 - function viewExchangeList(goodsId) { - var url = "{:url('/admin/statistics_exchangeList')}?goods_id=" + goodsId; - layer.open({ - type: 2, - title: '兑换列表', - shadeClose: false, - shade: 0.3, - area: ['90%', '90%'], - content: url - }); - } - - // 查看发货列表 - function viewShipmentList(goodsId) { - var url = "{:url('/admin/statistics_shipmentList')}?goods_id=" + goodsId; - layer.open({ - type: 2, - title: '发货列表', - shadeClose: false, - shade: 0.3, - area: ['90%', '90%'], - content: url - }); - } - - // 查看出货概览 - function viewProductsOverview(goodsId) { - var url = "{:url('/admin/statistics_productsOverview')}?goods_id=" + goodsId; - layer.open({ - type: 2, - title: '出货概览', - shadeClose: false, - shade: 0.3, - area: ['90%', '90%'], - content: url - }); - } \ No newline at end of file diff --git a/app/api/controller/Config.php b/app/api/controller/Config.php index b58f4b5..e1e4c96 100755 --- a/app/api/controller/Config.php +++ b/app/api/controller/Config.php @@ -26,7 +26,7 @@ class Config extends Base return $this->renderSuccess('获取成功', [ 'good_type' => $goodsTypeList, 'app_setting' => $app_setting, - 'version' => '100' + 'version' => '101' ]); } diff --git a/app/api/controller/Login.php b/app/api/controller/Login.php index 0907fec..6d80885 100755 --- a/app/api/controller/Login.php +++ b/app/api/controller/Login.php @@ -183,12 +183,18 @@ class Login extends Base */ public function login() { + // 初始化日志收集变量 + $logMessages = []; try { $code = request()->param("code", ''); if (empty($code)) { + $logMessages[] = '用户未获取到code:' . $code; + Log::error(end($logMessages)); return $this->renderError('请求参数错误'); } + + $logMessages[] = '用户开始登录: ' . $code; $click_id = request()->header('clickid'); $wxServer = new \app\common\server\Wx($this->app); $user_base = $wxServer->getOpenid($code); @@ -241,52 +247,75 @@ class Login extends Base } else { } - $res[] = UserAccount::where(['user_id' => $user['id']])->update([ - 'account_token' => $account_token, - 'token_num' => $token_num, - 'token_time' => $time, - 'last_login_time' => $time, - 'last_login_ip' => ip2long($last_login_ip1), - 'last_login_ip1' => $last_login_ip1, - 'ip_adcode' => $ip_adcode, - 'ip_province' => $ip_province, - 'ip_city' => $ip_city, - ]); + + // 检查UserAccount是否存在 + $userAccount = UserAccount::where(['user_id' => $user['id']])->find(); + if ($userAccount) { + // 存在则更新 + $res[] = UserAccount::where(['user_id' => $user['id']])->update([ + 'account_token' => $account_token, + 'token_num' => $token_num, + 'token_time' => $time, + 'last_login_time' => $time, + 'last_login_ip' => ip2long($last_login_ip1), + 'last_login_ip1' => $last_login_ip1, + 'ip_adcode' => $ip_adcode, + 'ip_province' => $ip_province, + 'ip_city' => $ip_city, + ]); + } else { + // 不存在则新增 + $res[] = UserAccount::insert([ + 'user_id' => $user['id'], + 'account_token' => $account_token, + 'token_num' => $token_num, + 'token_time' => $time, + 'last_login_time' => $time, + 'last_login_ip' => ip2long($last_login_ip1), + 'last_login_ip1' => $last_login_ip1, + 'ip_adcode' => $ip_adcode, + 'ip_province' => $ip_province, + 'ip_city' => $ip_city, + ]); + } // 记录用户登录日志(每天只记录一次) UserLoginLog::recordLogin( $user['id'], 'wechat', $last_login_ip1, - $ip_province . $ip_city + ''//$ip_province . $ip_city ); + $logMessages[] = '用户登录成功: ' . $code . ' 用户ID: ' . $user['id'] . '用户手机号' . $user['mobile']; + // 输出收集的所有日志 + Log::info(implode("==》", $logMessages)); + return $this->renderSuccess("登录成功", $account_token); } else { $nickname = request()->param('nickname', ''); $headimg = request()->param('headimg', ''); - if (!$nickname) { - return $this->renderError('请求参数错误!'); - } + // if (!$nickname) { + // return $this->renderError('请求参数错误!'); + // } $pid = 0; $pid_pid = request()->param('pid', 0); - if ($pid_pid > 0) { - log::info("获取推荐人id" . $pid_pid); - } + $randx = rand(1000, 9999); - if ($nickname == "微信用户") { - $nickname = $nickname . $randx; - } + $nickname = "微信用户" . $randx; + $logMessages[] = $nickname; + $randx = rand(10000, 99999); $identicon = new \Identicon\Identicon(); $imageData = $identicon->getImageData($openid . $nickname); $uploadResult = $this->uploader->uploadFile($imageData, "storage/users/icon/default/" . $randx . ".png"); $headimg = $uploadResult['full_url']; if ($pid_pid) { + $logMessages[] = "尝试获取推荐人ID: " . $pid_pid; $pid_info = User::where('id', '=', $pid_pid)->value("id"); if ($pid_info) { - log::info("获取推荐人id" . $pid_info); + $logMessages[] = "获取推荐人ID成功: " . $pid_info; $pid = $pid_info; } } @@ -319,12 +348,10 @@ class Login extends Base $redis = (new RedisHelper())->getRedis(); $lockKey = 'user:beta_reward:' . $user_id; if ($redis->set($lockKey, 1, ['nx', 'ex' => 60])) { - try { - $res[] = User::changeMoney($user_id, 50000, 8, '内测免费送'); - } finally { - // 释放锁 - $redis->del($lockKey); - } + $res[] = User::changeMoney($user_id, 50000, 8, '内测免费送'); + $logMessages[] = '赠送钻石: 50000'; + // 释放锁 + $redis->del($lockKey); } } } @@ -380,20 +407,32 @@ class Login extends Base $user_id, 'wechat:v1.0.0', $last_login_ip1, - $ip_province . $ip_city + ''//$ip_province . $ip_city ); + $logMessages[] = '==》用户注册成功: ' . $code . ' 用户ID: ' . $user_id; + // 输出收集的所有日志 + Log::info(implode("==>", $logMessages)); + Db::commit(); return $this->renderSuccess("登录成功", $account_token); } else { Db::rollback(); + + $logMessages[] = '==》用户注册失败: ' . $code . ' 用户ID: ' . $user_id; + // 输出收集的所有日志 + Log::info(implode("==>", $logMessages)); + return $this->renderError("登录失败"); } } } catch (\Exception $e) { - Log::error('错误信息' . $e->getMessage()); - Log::error('错误行数' . $e->getLine()); + $logMessages[] = '登录失败->错误信息: ' . $e->getMessage(); + $logMessages[] = '登录失败->错误行数: ' . $e->getLine(); + // 输出收集的错误日志 + Log::error(implode("==>", $logMessages)); + return $this->renderError("登录失败"); } } @@ -403,13 +442,24 @@ class Login extends Base */ public function h5login() { + // 获取请求参数 + $code = request()->param("code", ''); + if (empty($code)) { + return $this->renderError('请求参数错误'); + } + $click_id = request()->header('clickid'); + + // 使用Redis全局锁防止并发请求 + $redis = (new RedisHelper())->getRedis(); + $lockKey = 'global:h5login:lock:' . md5($code . $click_id); + + // 尝试获取锁,设置过期时间为30秒 + if (!$redis->set($lockKey, 1, ['nx', 'ex' => 30])) { + // 如果获取锁失败,表示有并发请求正在处理 + return $this->renderError('登录请求处理中,请稍后再试'); + } try { - $code = request()->param("code", ''); - if (empty($code)) { - return $this->renderError('请求参数错误'); - } - $click_id = request()->header('clickid'); $wxServer = new \app\common\server\WechatOfficialAccount($this->app); $user_base = $wxServer->getAccessToken($code); // $user_base_info = $wxServer->getUserInfo($user_base); @@ -460,17 +510,37 @@ class Login extends Base } - $res[] = UserAccount::where(['user_id' => $user['id']])->update([ - 'account_token' => $account_token, - 'token_num' => $token_num, - 'token_time' => $time, - 'last_login_time' => $time, - 'last_login_ip' => ip2long($last_login_ip1), - 'last_login_ip1' => $last_login_ip1, - 'ip_adcode' => $ip_adcode, - 'ip_province' => $ip_province, - 'ip_city' => $ip_city, - ]); + + // 检查UserAccount是否存在 + $userAccount = UserAccount::where(['user_id' => $user['id']])->find(); + if ($userAccount) { + // 存在则更新 + $res[] = UserAccount::where(['user_id' => $user['id']])->update([ + 'account_token' => $account_token, + 'token_num' => $token_num, + 'token_time' => $time, + 'last_login_time' => $time, + 'last_login_ip' => ip2long($last_login_ip1), + 'last_login_ip1' => $last_login_ip1, + 'ip_adcode' => $ip_adcode, + 'ip_province' => $ip_province, + 'ip_city' => $ip_city, + ]); + } else { + // 不存在则新增 + $res[] = UserAccount::insert([ + 'user_id' => $user['id'], + 'account_token' => $account_token, + 'token_num' => $token_num, + 'token_time' => $time, + 'last_login_time' => $time, + 'last_login_ip' => ip2long($last_login_ip1), + 'last_login_ip1' => $last_login_ip1, + 'ip_adcode' => $ip_adcode, + 'ip_province' => $ip_province, + 'ip_city' => $ip_city, + ]); + } // 记录用户登录日志(每天只记录一次) UserLoginLog::recordLogin( @@ -515,6 +585,13 @@ class Login extends Base 'click_id' => $click_id, 'uid' => '', ]); + + // 生成用户uid + $uid = $this->generateUid($user_id); + if ($uid) { + User::where('id', $user_id)->update(['uid' => $uid]); + } + $time = time(); #token字符串 $token_num = getRandStr(10); @@ -581,6 +658,9 @@ class Login extends Base Log::error('错误信息' . $e->getMessage()); Log::error('错误行数' . $e->getLine()); return $this->renderError('非法请求'); + } finally { + // 确保释放锁 + $redis->del($lockKey); } } @@ -613,62 +693,89 @@ class Login extends Base public function login_bind_mobile() { $user = $this->getUser(); - $user_id = $user['id']; - $code = request()->param("code", ''); - $wxServer = new \app\common\server\Wx($this->app); - $mobile = $wxServer->getMobile($code); - // return $this->renderError($mobile,[$mobile,$code]); - Db::startTrans(); - $res = []; - // $res[] = User::where(['id' => $user['id']])->update([ - // 'mobile' => $mobile, - // 'update_time' => time(), - // ]); - $data = []; - $user_mobile = User::where(['mobile' => $mobile])->find(); - if ($user_mobile) { - $old_user_account = UserAccount::where(['user_id' => $user_id])->find(); - #修改openid - $res[] = User::where(['id' => $user_mobile['id']]) - ->update([ - 'openid' => $user['openid'], - // 'nickname' => $user['nickname'], - // 'headimg' => $user['headimg'], - ]); - $time = time(); - #token字符串 - $token_num = getRandStr(10); - #加密token - $account_token = user_md5($user_mobile['id'] . $token_num . $time); - #修改token - $res[] = UserAccount::where(['user_id' => $user_mobile['id']])->update([ - 'account_token' => $account_token, - 'token_num' => $token_num, - 'token_time' => $time, - 'last_login_time' => $old_user_account['last_login_time'], - 'last_login_ip' => $old_user_account['last_login_ip'], - ]); - #修改 - $res[] = User::where(['id' => $user['id']])->delete(); - $res[] = UserAccount::where(['user_id' => $user_id])->delete(); - $data['token'] = $account_token; - // $res[] = UserAccount::where(['user_id' => $user['id']])->update([ - // 'token_time' => time(), - // ]); - } else { - $res[] = User::where(['id' => $user['id']])->update([ - 'mobile' => $mobile, - 'update_time' => time(), - ]); - } - if (resCheck($res)) { - Db::commit(); - return $this->renderSuccess("绑定成功2", $data); - } else { - Db::rollback(); - return $this->renderSuccess("绑定成功3"); + if (empty($user)) { + return $this->renderError('用户不存在'); } + $user_id = $user['id']; + $code = request()->param("code", ''); + if (empty($code)) { + return $this->renderError('参数错误,缺少code'); + } + + // 使用Redis全局锁防止并发绑定请求 + $redis = (new RedisHelper())->getRedis(); + $lockKey = 'global:bind_mobile:lock:' . $user_id; + + // 尝试获取锁,设置过期时间为20秒 + if (!$redis->set($lockKey, 1, ['nx', 'ex' => 20])) { + // 如果获取锁失败,表示有并发请求正在处理 + return $this->renderError('绑定请求处理中,请稍后再试'); + } + + try { + $wxServer = new \app\common\server\Wx($this->app); + $mobile = $wxServer->getMobile($code); + // return $this->renderError($mobile,[$mobile,$code]); + Db::startTrans(); + $res = []; + // $res[] = User::where(['id' => $user['id']])->update([ + // 'mobile' => $mobile, + // 'update_time' => time(), + // ]); + $data = []; + $user_mobile = User::where(['mobile' => $mobile])->find(); + if ($user_mobile) { + $old_user_account = UserAccount::where(['user_id' => $user_id])->find(); + #修改openid + $res[] = User::where(['id' => $user_mobile['id']]) + ->update([ + 'openid' => $user['openid'], + // 'nickname' => $user['nickname'], + // 'headimg' => $user['headimg'], + ]); + $time = time(); + #token字符串 + $token_num = getRandStr(10); + #加密token + $account_token = user_md5($user_mobile['id'] . $token_num . $time); + #修改token + $res[] = UserAccount::where(['user_id' => $user_mobile['id']])->update([ + 'account_token' => $account_token, + 'token_num' => $token_num, + 'token_time' => $time, + 'last_login_time' => $old_user_account['last_login_time'], + 'last_login_ip' => $old_user_account['last_login_ip'], + ]); + #修改 + $res[] = User::where(['id' => $user['id']])->delete(); + $res[] = UserAccount::where(['user_id' => $user_id])->delete(); + $data['token'] = $account_token; + // $res[] = UserAccount::where(['user_id' => $user['id']])->update([ + // 'token_time' => time(), + // ]); + } else { + $res[] = User::where(['id' => $user['id']])->update([ + 'mobile' => $mobile, + 'update_time' => time(), + ]); + } + if (resCheck($res)) { + Db::commit(); + return $this->renderSuccess("绑定成功", $data); + } else { + Db::rollback(); + return $this->renderError("绑定失败"); + } + } catch (\Exception $e) { + Db::rollback(); + Log::error('绑定手机号错误: ' . $e->getMessage()); + Log::error('错误行数: ' . $e->getLine()); + return $this->renderError('绑定失败: ' . $e->getMessage()); + } finally { + // 确保释放锁 + $redis->del($lockKey); + } } @@ -678,22 +785,49 @@ class Login extends Base public function login_bind_mobile_h5() { $user = $this->getUser(); - $user_id = $user['id']; - $mobile = request()->param("mobile", ''); - Db::startTrans(); - $res = []; - $res[] = User::where(['id' => $user_id])->update([ - 'mobile' => $mobile, - 'update_time' => time(), - ]); - if (resCheck($res)) { - Db::commit(); - return $this->renderSuccess("绑定成功2"); - } else { - Db::rollback(); - return $this->renderSuccess("绑定成功3"); + if (empty($user)) { + return $this->renderError('用户不存在'); } + $user_id = $user['id']; + $mobile = request()->param("mobile", ''); + if (empty($mobile)) { + return $this->renderError('请输入手机号'); + } + + // 使用Redis全局锁防止并发绑定请求 + $redis = (new RedisHelper())->getRedis(); + $lockKey = 'global:bind_mobile_h5:lock:' . $user_id; + + // 尝试获取锁,设置过期时间为10秒 + if (!$redis->set($lockKey, 1, ['nx', 'ex' => 10])) { + // 如果获取锁失败,表示有并发请求正在处理 + return $this->renderError('绑定请求处理中,请稍后再试'); + } + + try { + Db::startTrans(); + $res = []; + $res[] = User::where(['id' => $user_id])->update([ + 'mobile' => $mobile, + 'update_time' => time(), + ]); + if (resCheck($res)) { + Db::commit(); + return $this->renderSuccess("绑定成功"); + } else { + Db::rollback(); + return $this->renderError("绑定失败"); + } + } catch (\Exception $e) { + Db::rollback(); + Log::error('绑定手机号H5错误: ' . $e->getMessage()); + Log::error('错误行数: ' . $e->getLine()); + return $this->renderError('绑定失败: ' . $e->getMessage()); + } finally { + // 确保释放锁 + $redis->del($lockKey); + } } /** @@ -718,77 +852,86 @@ class Login extends Base public function recordLogin() { try { - // $user_id = $this->getUserid1(); - // //去redis中查询一下,如果存在今日的数据,则不在往下执行 - // $redis = (new RedisHelper())->getRedis(); - // $today = date('Y-m-d'); - // $redis_key = "login_record:" . $today . ":" . $user_id; - // $redis_data = $redis->get($redis_key); - // if ($redis_data) { - // return $this->renderSuccess('登录记录成功'); - // } + // 获取用户信息 $user = $this->getUser(); if (empty($user)) { return $this->renderError('用户不存在'); } - // 获取设备信息 - $device = request()->param('device', ''); // 设备类型 - $device_info = request()->param('device_info', ''); // 设备详细信息 - - // 获取IP和地理位置信息 - $ip = $this->getRealIp(); - $location = ''; - $ip_province = ''; - $ip_city = ''; - $ip_adcode = ''; - - $result = $this->ip_location($ip); - if ($result) { - $ip_province = $result['province']; - $ip_city = $result['city']; - $ip_adcode = $result['adcode']; - $location = $ip_province . $ip_city; - } $user_id = $user['id']; - $isTest = \app\common\helper\ConfigHelper::getSystemTestKey("enable_test"); - if ($isTest == "1") { - // 使用Redis锁防止重复获取 - $redis = (new RedisHelper())->getRedis(); - $lockKey = 'user:beta_reward:' . $user_id; - if ($redis->set($lockKey, 1, ['nx', 'ex' => 60])) { - $userCount = ProfitMoney::where('user_id', $user_id) - ->where('type', 8) - ->where('content', '=', '内测免费送') - ->count(); - if ($userCount == 0) { + // 使用Redis全局锁防止并发请求 + $redis = (new RedisHelper())->getRedis(); + $lockKey = 'global:record_login:lock:' . $user_id; + + // 尝试获取锁,设置过期时间为10秒 + if (!$redis->set($lockKey, 1, ['nx', 'ex' => 10])) { + // 如果获取锁失败,直接返回成功,避免用户等待 + return $this->renderSuccess('登录成功', [ + 'uid' => $user['uid'] ?: $user['id'], + 'nickname' => $user['nickname'], + 'headimg' => imageUrl($user['headimg']) + ]); + } + + try { + // 获取设备信息 + $device = request()->param('device', ''); // 设备类型 + $device_info = request()->param('device_info', ''); // 设备详细信息 + + // 获取IP和地理位置信息 + $ip = $this->getRealIp(); + $location = ''; + $ip_province = ''; + $ip_city = ''; + $ip_adcode = ''; + + $result = $this->ip_location($ip); + if ($result) { + $ip_province = $result['province']; + $ip_city = $result['city']; + $ip_adcode = $result['adcode']; + $location = $ip_province . $ip_city; + } + + $isTest = \app\common\helper\ConfigHelper::getSystemTestKey("enable_test"); + if ($isTest == "1") { + // 使用全局 Redis 锁防止重复获取内测奖励 + $bonusLockKey = 'global:bonus:lock:' . $user_id; + if ($redis->set($bonusLockKey, 1, ['nx', 'ex' => 60])) { try { - $res[] = User::changeMoney($user_id, 50000, 8, '内测免费送'); + $userCount = ProfitMoney::where('user_id', $user_id) + ->where('type', 8) + ->where('content', '=', '内测免费送') + ->count(); + if ($userCount == 0) { + $res[] = User::changeMoney($user_id, 50000, 8, '内测免费送'); + } } finally { - // 释放锁 - $redis->del($lockKey); + // 释放内测奖励锁 + $redis->del($bonusLockKey); } } } + + // 记录登录日志 + UserLoginLog::recordLogin( + $user['id'], + $device, + $ip, + $location + ); + + return $this->renderSuccess('登录成功', [ + 'uid' => $user['uid'] ?: $user['id'], + 'nickname' => $user['nickname'], + 'headimg' => imageUrl($user['headimg']) + ]); + } finally { + // 确保释放锁 + $redis->del($lockKey); } - // 记录登录日志 - UserLoginLog::recordLogin( - $user['id'], - $device, - $ip, - $location - ); - // //将数据写入redis,过期时间为当天剩余的时间 - // $redis->set($redis_key, json_encode($user), 86400 - time() % 86400); - return $this->renderSuccess('登录成功', [ - 'uid' => $user['uid'] ?: $user['id'], - 'nickname' => $user['nickname'], - 'headimg' => imageUrl($user['headimg']) - ]); - - } catch (\Exception $e) { Log::error('记录登录错误: ' . $e->getMessage()); Log::error('错误行数: ' . $e->getLine());