no message
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\lists\AccountLogLists;
|
||||
|
||||
/**
|
||||
* 账户流水
|
||||
* Class AccountLogController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class AccountLogController extends BaseApiController
|
||||
{
|
||||
/**
|
||||
* @notes 账户流水
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 14:34
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
return $this->dataLists(new AccountLogLists());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\service\ai\AiService;
|
||||
use app\common\model\match\MatchEvent;
|
||||
use app\common\model\match\MatchHistory;
|
||||
use app\common\model\match\MatchRecent;
|
||||
use app\common\model\article\Article;
|
||||
use app\common\enum\YesNoEnum;
|
||||
use app\common\model\ai\AiUnlock;
|
||||
|
||||
class AiController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['matchPredict', 'articleAnalysis'];
|
||||
|
||||
/**
|
||||
* 赛事AI预测
|
||||
*/
|
||||
public function matchPredict()
|
||||
{
|
||||
$matchId = $this->request->get('match_id/d');
|
||||
if (empty($matchId)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$cacheKey = 'ai_match_predict_' . $matchId;
|
||||
$cached = cache($cacheKey);
|
||||
if ($cached) {
|
||||
$cached['from_cache'] = true;
|
||||
return $this->data($cached);
|
||||
}
|
||||
|
||||
$match = MatchEvent::where('id', $matchId)->where('is_show', 1)->findOrEmpty();
|
||||
if ($match->isEmpty()) {
|
||||
return $this->fail('赛事不存在');
|
||||
}
|
||||
$data = $match->toArray();
|
||||
|
||||
$homeTeam = $data['home_team'];
|
||||
$awayTeam = $data['away_team'];
|
||||
|
||||
// 组装分析数据
|
||||
$matchData = [
|
||||
'match_info' => [
|
||||
'home_team' => $homeTeam,
|
||||
'away_team' => $awayTeam,
|
||||
'league' => $data['league_name'] ?? '',
|
||||
'match_time' => date('Y-m-d H:i', $data['match_time'] ?? time()),
|
||||
'status' => $data['status'] ?? 0,
|
||||
],
|
||||
'head_to_head' => MatchHistory::where(function ($query) use ($homeTeam, $awayTeam) {
|
||||
$query->where([['home_team', '=', $homeTeam], ['away_team', '=', $awayTeam]])
|
||||
->whereOr([['home_team', '=', $awayTeam], ['away_team', '=', $homeTeam]]);
|
||||
})->order('match_time desc')->limit(5)->select()->toArray(),
|
||||
'home_recent' => MatchRecent::where('team_name', $homeTeam)
|
||||
->order('match_time desc')->limit(5)->select()->toArray(),
|
||||
'away_recent' => MatchRecent::where('team_name', $awayTeam)
|
||||
->order('match_time desc')->limit(5)->select()->toArray(),
|
||||
];
|
||||
|
||||
$result = AiService::predictMatch($matchId, $matchData, $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
|
||||
$responseData = [
|
||||
'analysis' => $result['data'],
|
||||
'from_cache' => false,
|
||||
'match_info' => $matchData['match_info'],
|
||||
];
|
||||
|
||||
cache($cacheKey, $responseData, 6 * 3600);
|
||||
|
||||
return $this->data($responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 赛事AI预测 - 分段请求
|
||||
*/
|
||||
public function matchPredictSection()
|
||||
{
|
||||
$matchId = $this->request->get('match_id/d');
|
||||
$section = $this->request->get('section/s', '');
|
||||
if (empty($matchId) || empty($section)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
if (!$this->userId) {
|
||||
return $this->fail('请先登录', [], 0, 401);
|
||||
}
|
||||
|
||||
// 解锁检查:只有本人解锁过的才免费,其他人的缓存不算
|
||||
$isUnlocked = AiUnlock::isUnlocked($this->userId, $matchId);
|
||||
if (!$isUnlocked) {
|
||||
if ($section === 'prediction') {
|
||||
$unlockResult = AiUnlock::unlock($this->userId, $matchId);
|
||||
if (!$unlockResult['ok']) {
|
||||
return $this->fail($unlockResult['msg']);
|
||||
}
|
||||
} else {
|
||||
return $this->fail('请先解锁该赛事分析');
|
||||
}
|
||||
}
|
||||
|
||||
$match = MatchEvent::where('id', $matchId)->where('is_show', 1)->findOrEmpty();
|
||||
if ($match->isEmpty()) {
|
||||
return $this->fail('赛事不存在');
|
||||
}
|
||||
$data = $match->toArray();
|
||||
|
||||
$homeTeam = $data['home_team'];
|
||||
$awayTeam = $data['away_team'];
|
||||
|
||||
$matchData = [
|
||||
'match_info' => [
|
||||
'home_team' => $homeTeam,
|
||||
'away_team' => $awayTeam,
|
||||
'league' => $data['league_name'] ?? '',
|
||||
'match_time' => date('Y-m-d H:i', $data['match_time'] ?? time()),
|
||||
'status' => $data['status'] ?? 0,
|
||||
'home_score' => $data['home_score'] ?? 0,
|
||||
'away_score' => $data['away_score'] ?? 0,
|
||||
'home_odds' => $data['home_odds'] ?? 0,
|
||||
'draw_odds' => $data['draw_odds'] ?? 0,
|
||||
'away_odds' => $data['away_odds'] ?? 0,
|
||||
],
|
||||
'head_to_head' => MatchHistory::where(function ($query) use ($homeTeam, $awayTeam) {
|
||||
$query->where([['home_team', '=', $homeTeam], ['away_team', '=', $awayTeam]])
|
||||
->whereOr([['home_team', '=', $awayTeam], ['away_team', '=', $homeTeam]]);
|
||||
})->order('match_time desc')->limit(5)->select()->toArray(),
|
||||
'home_recent' => MatchRecent::where('team_name', $homeTeam)
|
||||
->order('match_time desc')->limit(5)->select()->toArray(),
|
||||
'away_recent' => MatchRecent::where('team_name', $awayTeam)
|
||||
->order('match_time desc')->limit(5)->select()->toArray(),
|
||||
];
|
||||
|
||||
$result = AiService::predictMatchSection($matchId, $section, $matchData, $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'section' => $section,
|
||||
'data' => $result['data'],
|
||||
'from_cache' => $result['from_cache'] ?? false,
|
||||
'match_info' => $matchData['match_info'],
|
||||
'remain_count' => AiUnlock::getRemainCount($this->userId),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 资讯AI分析
|
||||
*/
|
||||
public function articleAnalysis()
|
||||
{
|
||||
$articleId = $this->request->get('article_id/d');
|
||||
if (empty($articleId)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$article = Article::where(['id' => $articleId, 'is_show' => YesNoEnum::YES])->findOrEmpty();
|
||||
if ($article->isEmpty()) {
|
||||
return $this->fail('文章不存在');
|
||||
}
|
||||
|
||||
$articleData = $article->toArray();
|
||||
$result = AiService::analyzeArticle($articleId, $articleData, $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
return $this->data([
|
||||
'analysis' => $result['data'],
|
||||
'from_cache' => $result['from_cache'] ?? false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户可信度分析
|
||||
*/
|
||||
public function userCredibility()
|
||||
{
|
||||
$targetUserId = $this->request->get('user_id/d');
|
||||
if (empty($targetUserId)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
// 组装用户数据(简化版,后续可扩展)
|
||||
$userData = [
|
||||
'user_id' => $targetUserId,
|
||||
'register_days' => 30,
|
||||
'post_count' => 0,
|
||||
'comment_count' => 0,
|
||||
'like_received' => 0,
|
||||
];
|
||||
|
||||
$result = AiService::analyzeCredibility($targetUserId, $userData, $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
return $this->data([
|
||||
'analysis' => $result['data'],
|
||||
'from_cache' => $result['from_cache'] ?? false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 彩票概率分析
|
||||
*/
|
||||
public function lotteryAnalysis()
|
||||
{
|
||||
$type = $this->request->get('type', 'ssq');
|
||||
|
||||
// 简化版彩票数据
|
||||
$lotteryData = [
|
||||
'lottery_type' => $type,
|
||||
'recent_draws' => [],
|
||||
'description' => $type === 'ssq' ? '双色球' : '大乐透',
|
||||
];
|
||||
|
||||
$result = AiService::analyzeLottery($lotteryData, $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
return $this->data([
|
||||
'analysis' => $result['data'],
|
||||
'from_cache' => $result['from_cache'] ?? false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询比赛解锁状态
|
||||
*/
|
||||
public function checkUnlock()
|
||||
{
|
||||
$matchId = $this->request->get('match_id/d');
|
||||
if (empty($matchId)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
$unlocked = $this->userId ? AiUnlock::isUnlocked($this->userId, $matchId) : false;
|
||||
$remain = $this->userId ? AiUnlock::getRemainCount($this->userId) : 0;
|
||||
return $this->data([
|
||||
'unlocked' => $unlocked,
|
||||
'remain_count' => $remain,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\model\app\AppVersion;
|
||||
use think\response\Json;
|
||||
|
||||
/**
|
||||
* APP 版本检查(无需登录)
|
||||
* Class AppVersionController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class AppVersionController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['check', 'latest'];
|
||||
|
||||
/**
|
||||
* @notes 获取各平台最新可用版本(供下载页使用,无需登录)
|
||||
* 请求示例:GET /appVersion/latest
|
||||
* 返回:{ android: {...}, ios: {...} }
|
||||
*/
|
||||
public function latest(): Json
|
||||
{
|
||||
$result = ['android' => null, 'ios' => null];
|
||||
|
||||
foreach ([1 => 'android', 2 => 'ios'] as $platform => $key) {
|
||||
$version = AppVersion::where('platform', $platform)
|
||||
->where('status', 1)
|
||||
->where('package_type', '<>', 1) // 排除 wgt 热更包,只取整包
|
||||
->order(['version_code' => 'desc', 'id' => 'desc'])
|
||||
->append(['platform_text', 'package_type_text'])
|
||||
->find();
|
||||
if ($version) {
|
||||
$result[$key] = [
|
||||
'version_name' => $version['version_name'],
|
||||
'version_code' => $version['version_code'],
|
||||
'package_type' => $version['package_type'],
|
||||
'package_type_text' => $version['package_type_text'],
|
||||
'download_url' => $version['download_url'],
|
||||
'title' => $version['title'],
|
||||
'update_content' => $version['update_content'],
|
||||
'create_time' => $version['create_time'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 检查 APP 是否有新版本
|
||||
* 请求示例:GET /appVersion/check?platform=1&version_code=1012&package_type=1
|
||||
* - platform: 1=Android 2=iOS(必填)
|
||||
* - version_code: 当前 APP 版本数字(必填)
|
||||
* - package_type: 1=wgt热更 2=apk整包 3=ipa(可选,传则只比对该包类型)
|
||||
*/
|
||||
public function check(): Json
|
||||
{
|
||||
$platform = (int) $this->request->get('platform/d', 0);
|
||||
$versionCode = (int) $this->request->get('version_code/d', 0);
|
||||
$packageType = (int) $this->request->get('package_type/d', 0);
|
||||
|
||||
if (!in_array($platform, [1, 2])) {
|
||||
return $this->fail('参数错误:platform');
|
||||
}
|
||||
if ($versionCode <= 0) {
|
||||
return $this->fail('参数错误:version_code');
|
||||
}
|
||||
|
||||
$query = AppVersion::where('platform', $platform)
|
||||
->where('status', 1)
|
||||
->where('version_code', '>', $versionCode);
|
||||
|
||||
if ($packageType > 0) {
|
||||
$query->where('package_type', $packageType);
|
||||
}
|
||||
|
||||
// wgt 热更需校验原生最低版本(min_version_code <= 当前version_code)
|
||||
// 同时找出符合条件的最新版本
|
||||
$latest = $query->order(['version_code' => 'desc', 'id' => 'desc'])
|
||||
->append(['platform_text', 'package_type_text', 'update_type_text'])
|
||||
->find();
|
||||
|
||||
if (empty($latest)) {
|
||||
return $this->data([
|
||||
'has_update' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
// wgt 包:当前 APP 原生版本必须 >= min_version_code
|
||||
if ($latest['package_type'] == 1 && $latest['min_version_code'] > 0 && $versionCode < $latest['min_version_code']) {
|
||||
// 当前原生版本太低无法 wgt 热更,返回无更新(让用户走整包升级路径)
|
||||
return $this->data([
|
||||
'has_update' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'has_update' => true,
|
||||
'id' => $latest['id'],
|
||||
'version_name' => $latest['version_name'],
|
||||
'version_code' => $latest['version_code'],
|
||||
'min_version_code' => $latest['min_version_code'],
|
||||
'platform' => $latest['platform'],
|
||||
'platform_text' => $latest['platform_text'],
|
||||
'package_type' => $latest['package_type'],
|
||||
'package_type_text' => $latest['package_type_text'],
|
||||
'update_type' => $latest['update_type'],
|
||||
'update_type_text' => $latest['update_type_text'],
|
||||
'download_url' => $latest['download_url'],
|
||||
'title' => $latest['title'],
|
||||
'update_content' => $latest['update_content'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
|
||||
use app\api\lists\article\ArticleCollectLists;
|
||||
use app\api\lists\article\ArticleLists;
|
||||
use app\api\logic\ArticleLogic;
|
||||
use app\api\logic\ArticleInteractLogic;
|
||||
use app\common\model\article\Article;
|
||||
use app\common\service\article\ArticleImageProxyService;
|
||||
|
||||
/**
|
||||
* 文章管理
|
||||
* Class ArticleController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class ArticleController extends BaseApiController
|
||||
{
|
||||
|
||||
public array $notNeedLogin = ['lists', 'cate', 'detail', 'commentList', 'imageProxy'];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 文章列表
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 15:30
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
return $this->dataLists(new ArticleLists());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 文章分类列表
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 15:30
|
||||
*/
|
||||
public function cate()
|
||||
{
|
||||
return $this->data(ArticleLogic::cate());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 收藏列表
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 16:31
|
||||
*/
|
||||
public function collect()
|
||||
{
|
||||
return $this->dataLists(new ArticleCollectLists());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 文章详情
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 17:09
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
$id = $this->request->get('id/d');
|
||||
$result = ArticleLogic::detail($id, $this->userId);
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 文章翻译
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function translate()
|
||||
{
|
||||
$articleId = $this->request->post('article_id/d', 0);
|
||||
if (empty($articleId)) {
|
||||
$articleId = $this->request->post('id/d', 0);
|
||||
}
|
||||
if (empty($articleId)) {
|
||||
$articleId = $this->request->get('article_id/d', 0);
|
||||
}
|
||||
if (empty($articleId)) {
|
||||
$articleId = $this->request->get('id/d', 0);
|
||||
}
|
||||
if (empty($articleId)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
if (!$this->userId) {
|
||||
return $this->fail('请先登录', [], 0, 401);
|
||||
}
|
||||
|
||||
$confirm = $this->request->post('confirm/d', 0);
|
||||
$result = ArticleLogic::translate((int) $articleId, $this->userId, (int) $confirm);
|
||||
if ($result['success'] === false) {
|
||||
return $this->fail($result['error'] ?? '翻译失败');
|
||||
}
|
||||
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 加入收藏
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 17:01
|
||||
*/
|
||||
public function addCollect()
|
||||
{
|
||||
$articleId = $this->request->post('id/d');
|
||||
ArticleLogic::addCollect($articleId, $this->userId);
|
||||
return $this->success('操作成功');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 取消收藏
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 17:01
|
||||
*/
|
||||
public function cancelCollect()
|
||||
{
|
||||
$articleId = $this->request->post('id/d');
|
||||
ArticleLogic::cancelCollect($articleId, $this->userId);
|
||||
return $this->success('操作成功');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 评论列表
|
||||
*/
|
||||
public function commentList()
|
||||
{
|
||||
$articleId = $this->request->get('article_id/d');
|
||||
$pageNo = $this->request->get('page_no/d', 1);
|
||||
$pageSize = $this->request->get('page_size/d', 15);
|
||||
$result = ArticleInteractLogic::commentList($articleId, $pageNo, $pageSize);
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 远程文章图片代理
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function imageProxy()
|
||||
{
|
||||
$url = $this->request->get('url/s', '');
|
||||
return ArticleImageProxyService::proxyRemoteImage($url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 发布评论
|
||||
*/
|
||||
public function addComment()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
if (empty($params['article_id']) || empty($params['content'])) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
$result = ArticleInteractLogic::addComment($this->userId, $params);
|
||||
if ($result === false) {
|
||||
return $this->fail(ArticleInteractLogic::getError());
|
||||
}
|
||||
return $this->success('评论成功', ['id' => $result]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 点赞/踩
|
||||
*/
|
||||
public function vote()
|
||||
{
|
||||
$articleId = $this->request->post('article_id/d');
|
||||
$voteType = $this->request->post('vote_type/d', 1);
|
||||
if (empty($articleId)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
if (!in_array($voteType, [1, 2])) {
|
||||
return $this->fail('投票类型错误');
|
||||
}
|
||||
$result = ArticleInteractLogic::vote($this->userId, $articleId, $voteType);
|
||||
if ($result === false) {
|
||||
return $this->fail(ArticleInteractLogic::getError());
|
||||
}
|
||||
// 返回最新计数
|
||||
$article = Article::where('id', $articleId)->field('like_count,dislike_count')->findOrEmpty();
|
||||
$result['like_count'] = $article->isEmpty() ? 0 : $article->like_count;
|
||||
$result['dislike_count'] = $article->isEmpty() ? 0 : $article->dislike_count;
|
||||
return $this->success('操作成功', $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\service\ai\AiAssistantService;
|
||||
|
||||
class AssistantController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['chat', 'sessions', 'messages', 'clear'];
|
||||
|
||||
public function chat()
|
||||
{
|
||||
$message = trim((string) $this->request->post('message', ''));
|
||||
$clientId = trim((string) $this->request->post('client_id', ''));
|
||||
$sessionId = (int) $this->request->post('session_id/d', 0);
|
||||
|
||||
if ($message === '') {
|
||||
return $this->fail('请输入要咨询的问题');
|
||||
}
|
||||
|
||||
$result = AiAssistantService::chat($message, $this->userId, $clientId, $sessionId, $this->request->ip());
|
||||
if (empty($result['success'])) {
|
||||
return $this->fail($result['error'] ?? 'AI助手暂时不可用');
|
||||
}
|
||||
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
|
||||
public function sessions()
|
||||
{
|
||||
$clientId = trim((string) $this->request->get('client_id', ''));
|
||||
return $this->data(AiAssistantService::sessions($this->userId, $clientId));
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
$sessionId = (int) $this->request->get('session_id/d', 0);
|
||||
$clientId = trim((string) $this->request->get('client_id', ''));
|
||||
if ($sessionId <= 0) {
|
||||
return $this->fail('会话参数缺失');
|
||||
}
|
||||
|
||||
$result = AiAssistantService::messages($sessionId, $this->userId, $clientId);
|
||||
if (empty($result['success'])) {
|
||||
return $this->fail($result['error'] ?? '会话不存在');
|
||||
}
|
||||
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$sessionId = (int) $this->request->post('session_id/d', 0);
|
||||
$clientId = trim((string) $this->request->post('client_id', ''));
|
||||
|
||||
$result = AiAssistantService::clear($sessionId, $this->userId, $clientId);
|
||||
if (empty($result['success'])) {
|
||||
return $this->fail($result['error'] ?? '清空失败');
|
||||
}
|
||||
|
||||
return $this->success('操作成功');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\controller\BaseLikeAdminController;
|
||||
|
||||
class BaseApiController extends BaseLikeAdminController
|
||||
{
|
||||
protected int $userId = 0;
|
||||
protected array $userInfo = [];
|
||||
|
||||
public function initialize()
|
||||
{
|
||||
if (isset($this->request->userId)) {
|
||||
$this->userId = (int) $this->request->userId;
|
||||
}
|
||||
if (isset($this->request->userInfo) && is_array($this->request->userInfo)) {
|
||||
$this->userInfo = $this->request->userInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\service\chat\PrivateChatService;
|
||||
|
||||
class ChatController extends BaseApiController
|
||||
{
|
||||
public function sessions()
|
||||
{
|
||||
$page = (int) $this->request->get('page_no/d', 1);
|
||||
$size = (int) $this->request->get('page_size/d', 20);
|
||||
return $this->data(PrivateChatService::sessions($this->userId, $page, $size));
|
||||
}
|
||||
|
||||
public function open()
|
||||
{
|
||||
$targetUserId = (int) $this->request->post('target_user_id/d', 0);
|
||||
$result = PrivateChatService::open($this->userId, $targetUserId, true);
|
||||
if (empty($result['success'])) {
|
||||
return $this->fail($result['error'] ?? '打开会话失败');
|
||||
}
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
$sessionId = (int) $this->request->get('session_id/d', 0);
|
||||
$afterId = (int) $this->request->get('after_id/d', 0);
|
||||
$page = (int) $this->request->get('page_no/d', 1);
|
||||
$size = (int) $this->request->get('page_size/d', 50);
|
||||
|
||||
$result = PrivateChatService::messages($this->userId, $sessionId, $afterId, $page, $size);
|
||||
if (empty($result['success'])) {
|
||||
return $this->fail($result['error'] ?? '获取消息失败');
|
||||
}
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
|
||||
public function send()
|
||||
{
|
||||
$sessionId = (int) $this->request->post('session_id/d', 0);
|
||||
$messageType = trim((string) $this->request->post('message_type/s', PrivateChatService::MESSAGE_TYPE_TEXT));
|
||||
$content = trim((string) $this->request->post('content/s', ''));
|
||||
|
||||
$result = PrivateChatService::send($this->userId, $sessionId, $messageType, $content);
|
||||
if (empty($result['success'])) {
|
||||
return $this->fail($result['error'] ?? '发送失败');
|
||||
}
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,925 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\lists\community\CommunityPostLists;
|
||||
use app\common\model\community\CommunityPost;
|
||||
use app\common\model\community\CommunityComment;
|
||||
use app\common\model\community\CommunityLike;
|
||||
use app\common\model\community\CommunityFollow;
|
||||
use app\common\model\community\CommunityTag;
|
||||
use app\common\model\user\User;
|
||||
use app\common\model\ai\AiAnalysis;
|
||||
use app\common\enum\user\AccountLogEnum;
|
||||
use app\common\logic\AccountLogLogic;
|
||||
use app\common\service\ai\AiService;
|
||||
use app\common\service\ai\KbSyncService;
|
||||
use app\common\service\chat\PrivateChatService;
|
||||
use app\common\service\VipService;
|
||||
use think\facade\Db;
|
||||
|
||||
class CommunityController extends BaseApiController
|
||||
{
|
||||
private const LIUHE_TAGS = ['旧澳六合', '新澳六合'];
|
||||
|
||||
public array $notNeedLogin = ['postLists', 'postDetail', 'commentLists', 'tagLists', 'userProfile', 'followList', 'fansList'];
|
||||
|
||||
// 帖子列表
|
||||
public function postLists()
|
||||
{
|
||||
return $this->dataLists(new CommunityPostLists());
|
||||
}
|
||||
|
||||
// 帖子详情
|
||||
public function postDetail()
|
||||
{
|
||||
$id = $this->request->get('id/d');
|
||||
$post = CommunityPost::where('id', $id)->where('status', 1)->findOrEmpty();
|
||||
if ($post->isEmpty()) {
|
||||
return $this->fail('帖子不存在');
|
||||
}
|
||||
|
||||
// 增加浏览数
|
||||
CommunityPost::where('id', $id)->inc('view_count')->update();
|
||||
|
||||
$data = $post->toArray();
|
||||
$ext = $data['ext'] ? (is_string($data['ext']) ? json_decode($data['ext'], true) : $data['ext']) : [];
|
||||
$data['source_url'] = $ext['url'] ?? '';
|
||||
unset($data['ext']);
|
||||
|
||||
// 用户信息
|
||||
$user = User::field('id,sn,nickname,avatar,sex')->where('id', $data['user_id'])->findOrEmpty();
|
||||
$data['user'] = $user->isEmpty() ? null : $user->toArray();
|
||||
|
||||
// 标签
|
||||
$data['tags'] = $this->getPostTags($id);
|
||||
|
||||
$isTrumpPost = $this->isTrumpPost($data['tags']);
|
||||
if ($isTrumpPost && empty($ext['translated_content']) && !empty($data['content'])) {
|
||||
$result = AiService::translateCommunityPost($data['content']);
|
||||
if (!empty($result['success']) && !empty($result['content'])) {
|
||||
$ext['translated_content'] = trim($result['content']);
|
||||
CommunityPost::where('id', $id)->update([
|
||||
'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
$data['translated_content'] = $ext['translated_content'] ?? '';
|
||||
|
||||
// 付费帖权限控制
|
||||
$data['is_locked'] = false;
|
||||
$data['is_purchased'] = false;
|
||||
if ($data['is_paid']) {
|
||||
$isAuthor = $this->userId && $this->userId == $data['user_id'];
|
||||
$isPurchased = $this->userId && Db::name('user_account_log')
|
||||
->where('user_id', $this->userId)
|
||||
->where('change_type', AccountLogEnum::UP_DEC_PURCHASE_POST)
|
||||
->where('relation_id', $id)
|
||||
->count() > 0;
|
||||
|
||||
$data['is_purchased'] = $isPurchased;
|
||||
|
||||
if (!$isAuthor && !$isPurchased) {
|
||||
$data['is_locked'] = true;
|
||||
// 付费帖子只显示前20字符
|
||||
if (mb_strlen($data['content']) > 20) {
|
||||
$data['content'] = mb_substr($data['content'], 0, 20) . '...';
|
||||
}
|
||||
|
||||
// 图片只保留第1张
|
||||
$images = $data['images'];
|
||||
if (is_array($images) && count($images) > 1) {
|
||||
$data['images'] = [$images[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 当前用户是否点赞
|
||||
$data['is_liked'] = false;
|
||||
$data['is_self'] = $this->userId && $this->userId == $data['user_id'];
|
||||
$data['is_followed'] = false;
|
||||
$data['is_friend'] = false;
|
||||
$data['relationship'] = PrivateChatService::relationship($this->userId, (int) $data['user_id']);
|
||||
if ($this->userId) {
|
||||
$data['is_liked'] = CommunityLike::where([
|
||||
'user_id' => $this->userId,
|
||||
'target_id' => $id,
|
||||
'target_type' => 1
|
||||
])->count() > 0;
|
||||
|
||||
// 是否关注作者
|
||||
$data['is_followed'] = CommunityFollow::where([
|
||||
'user_id' => $this->userId,
|
||||
'follow_user_id' => $data['user_id']
|
||||
])->count() > 0;
|
||||
$data['is_friend'] = $data['is_followed'];
|
||||
$data['relationship'] = PrivateChatService::relationship($this->userId, (int) $data['user_id']);
|
||||
}
|
||||
|
||||
return $this->data($data);
|
||||
}
|
||||
|
||||
// 删除帖子
|
||||
public function deletePost()
|
||||
{
|
||||
if (!$this->userId) {
|
||||
return $this->fail('请先登录');
|
||||
}
|
||||
$postId = $this->request->post('post_id/d');
|
||||
$post = CommunityPost::where('id', $postId)->findOrEmpty();
|
||||
if ($post->isEmpty()) {
|
||||
return $this->fail('帖子不存在');
|
||||
}
|
||||
if ((int) $post->user_id !== (int) $this->userId) {
|
||||
return $this->fail('无权删除该帖子');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$post->delete();
|
||||
// 同时删除帖子下的评论
|
||||
CommunityComment::where('post_id', $postId)->delete();
|
||||
Db::commit();
|
||||
KbSyncService::enqueue('post', 'post', $postId, 'delete', 20);
|
||||
return $this->success('删除成功');
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
return $this->fail('删除失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 发帖
|
||||
public function publish()
|
||||
{
|
||||
$content = $this->request->post('content/s', '');
|
||||
$images = $this->request->post('images/a', []);
|
||||
$postType = $this->request->post('post_type/d', 0);
|
||||
$matchId = $this->request->post('match_id/d', 0);
|
||||
$tagIds = $this->request->post('tag_ids/a', []);
|
||||
$isPaid = $this->request->post('is_paid/d', 0);
|
||||
$pricePoints = $this->request->post('price_points/d', 0);
|
||||
$freeContentLen = $this->request->post('free_content_len/d', 100);
|
||||
|
||||
if (empty($content) && empty($images)) {
|
||||
return $this->fail('请输入内容或上传图片');
|
||||
}
|
||||
|
||||
if ($isPaid && $pricePoints <= 0) {
|
||||
return $this->fail('付费帖子必须设置积分价格');
|
||||
}
|
||||
if ($isPaid && ($pricePoints < 1 || $pricePoints > 9999)) {
|
||||
return $this->fail('积分价格范围为1~9999');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$post = CommunityPost::create([
|
||||
'user_id' => $this->userId,
|
||||
'content' => $content,
|
||||
'images' => $images,
|
||||
'post_type' => $postType,
|
||||
'match_id' => $matchId,
|
||||
'is_paid' => $isPaid ? 1 : 0,
|
||||
'price_points' => $isPaid ? $pricePoints : 0,
|
||||
'free_content_len' => $isPaid ? $freeContentLen : 100,
|
||||
'status' => 1,
|
||||
'create_time' => time(),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
|
||||
// 关联标签
|
||||
if (!empty($tagIds)) {
|
||||
$tagData = [];
|
||||
foreach ($tagIds as $tagId) {
|
||||
$tagData[] = ['post_id' => $post->id, 'tag_id' => $tagId];
|
||||
}
|
||||
Db::name('community_post_tag')->insertAll($tagData);
|
||||
// 更新标签帖子数
|
||||
CommunityTag::whereIn('id', $tagIds)->inc('post_count')->update();
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
KbSyncService::enqueue('post', 'post', (int) $post->id, 'upsert', 30);
|
||||
return $this->data(['id' => $post->id]);
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return $this->fail('发布失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 评论列表
|
||||
public function commentLists()
|
||||
{
|
||||
$postId = $this->request->get('post_id/d');
|
||||
$page = $this->request->get('page_no/d', 1);
|
||||
$size = $this->request->get('page_size/d', 20);
|
||||
|
||||
$list = CommunityComment::where('post_id', $postId)
|
||||
->where('status', 1)
|
||||
->where('parent_id', 0)
|
||||
->order('create_time desc')
|
||||
->page($page, $size)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$total = CommunityComment::where('post_id', $postId)
|
||||
->where('status', 1)
|
||||
->where('parent_id', 0)
|
||||
->count();
|
||||
|
||||
// 填充用户信息和子评论
|
||||
foreach ($list as &$item) {
|
||||
$user = User::field('id,sn,nickname,avatar')->where('id', $item['user_id'])->findOrEmpty();
|
||||
$item['user'] = $user->isEmpty() ? null : $user->toArray();
|
||||
$item['is_self'] = $this->userId > 0 && (int) $item['user_id'] === (int) $this->userId;
|
||||
|
||||
// 子评论(最多3条)
|
||||
$item['replies'] = CommunityComment::where('parent_id', $item['id'])
|
||||
->where('status', 1)
|
||||
->order('create_time asc')
|
||||
->limit(3)
|
||||
->select()
|
||||
->each(function ($reply) {
|
||||
$u = User::field('id,sn,nickname,avatar')->where('id', $reply['user_id'])->findOrEmpty();
|
||||
$reply['user'] = $u->isEmpty() ? null : $u->toArray();
|
||||
$reply['is_self'] = $this->userId > 0 && (int) $reply['user_id'] === (int) $this->userId;
|
||||
if ($reply['reply_user_id']) {
|
||||
$ru = User::field('id,nickname')->where('id', $reply['reply_user_id'])->findOrEmpty();
|
||||
$reply['reply_user'] = $ru->isEmpty() ? null : $ru->toArray();
|
||||
}
|
||||
return $reply;
|
||||
})
|
||||
->toArray();
|
||||
|
||||
$item['reply_count'] = CommunityComment::where('parent_id', $item['id'])->where('status', 1)->count();
|
||||
|
||||
// 当前用户是否点赞
|
||||
$item['is_liked'] = false;
|
||||
if ($this->userId) {
|
||||
$item['is_liked'] = CommunityLike::where([
|
||||
'user_id' => $this->userId,
|
||||
'target_id' => $item['id'],
|
||||
'target_type' => 2
|
||||
])->count() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'lists' => $list,
|
||||
'count' => $total,
|
||||
'page_no' => $page,
|
||||
'page_size' => $size,
|
||||
]);
|
||||
}
|
||||
|
||||
// 发评论
|
||||
public function addComment()
|
||||
{
|
||||
$postId = $this->request->post('post_id/d');
|
||||
$content = $this->request->post('content/s', '');
|
||||
$parentId = $this->request->post('parent_id/d', 0);
|
||||
$replyUserId = $this->request->post('reply_user_id/d', 0);
|
||||
|
||||
if (empty($content)) {
|
||||
return $this->fail('请输入评论内容');
|
||||
}
|
||||
|
||||
$post = CommunityPost::where('id', $postId)->where('status', 1)->findOrEmpty();
|
||||
if ($post->isEmpty()) {
|
||||
return $this->fail('帖子不存在');
|
||||
}
|
||||
|
||||
$comment = CommunityComment::create([
|
||||
'post_id' => $postId,
|
||||
'user_id' => $this->userId,
|
||||
'parent_id' => $parentId,
|
||||
'reply_user_id' => $replyUserId,
|
||||
'content' => $content,
|
||||
'status' => 1,
|
||||
'create_time' => time(),
|
||||
]);
|
||||
|
||||
// 更新帖子评论数
|
||||
CommunityPost::where('id', $postId)->inc('comment_count')->update();
|
||||
|
||||
return $this->data(['id' => $comment->id]);
|
||||
}
|
||||
|
||||
// 删除评论
|
||||
public function deleteComment()
|
||||
{
|
||||
$commentId = $this->request->post('comment_id/d');
|
||||
$comment = CommunityComment::where('id', $commentId)->where('status', 1)->findOrEmpty();
|
||||
if ($comment->isEmpty()) {
|
||||
return $this->fail('评论不存在');
|
||||
}
|
||||
if ((int) $comment->user_id !== (int) $this->userId) {
|
||||
return $this->fail('无权删除该评论');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$deleteIds = [$commentId];
|
||||
$replyIds = CommunityComment::where('parent_id', $commentId)->where('status', 1)->column('id');
|
||||
if (!empty($replyIds)) {
|
||||
$deleteIds = array_merge($deleteIds, $replyIds);
|
||||
}
|
||||
|
||||
CommunityComment::whereIn('id', $deleteIds)->delete();
|
||||
CommunityPost::where('id', $comment->post_id)->dec('comment_count', count($deleteIds))->update();
|
||||
|
||||
Db::commit();
|
||||
return $this->data(['deleted_ids' => $deleteIds]);
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
return $this->fail('删除失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 点赞/取消点赞
|
||||
public function like()
|
||||
{
|
||||
$targetId = $this->request->post('target_id/d');
|
||||
$targetType = $this->request->post('target_type/d', 1);
|
||||
|
||||
$exists = CommunityLike::where([
|
||||
'user_id' => $this->userId,
|
||||
'target_id' => $targetId,
|
||||
'target_type' => $targetType
|
||||
])->findOrEmpty();
|
||||
|
||||
if ($exists->isEmpty()) {
|
||||
CommunityLike::create([
|
||||
'user_id' => $this->userId,
|
||||
'target_id' => $targetId,
|
||||
'target_type' => $targetType,
|
||||
'create_time' => time(),
|
||||
]);
|
||||
if ($targetType == 1) {
|
||||
CommunityPost::where('id', $targetId)->inc('like_count')->update();
|
||||
} else {
|
||||
CommunityComment::where('id', $targetId)->inc('like_count')->update();
|
||||
}
|
||||
return $this->data(['is_liked' => true]);
|
||||
} else {
|
||||
$exists->delete();
|
||||
if ($targetType == 1) {
|
||||
CommunityPost::where('id', $targetId)->dec('like_count')->update();
|
||||
} else {
|
||||
CommunityComment::where('id', $targetId)->dec('like_count')->update();
|
||||
}
|
||||
return $this->data(['is_liked' => false]);
|
||||
}
|
||||
}
|
||||
|
||||
// 关注/取消关注
|
||||
public function follow()
|
||||
{
|
||||
$followUserId = $this->request->post('follow_user_id/d');
|
||||
|
||||
if ($followUserId == $this->userId) {
|
||||
return $this->fail('不能关注自己');
|
||||
}
|
||||
|
||||
$exists = CommunityFollow::where([
|
||||
'user_id' => $this->userId,
|
||||
'follow_user_id' => $followUserId
|
||||
])->findOrEmpty();
|
||||
|
||||
if ($exists->isEmpty()) {
|
||||
CommunityFollow::create([
|
||||
'user_id' => $this->userId,
|
||||
'follow_user_id' => $followUserId,
|
||||
'create_time' => time(),
|
||||
]);
|
||||
return $this->data(['is_followed' => true]);
|
||||
} else {
|
||||
$exists->delete();
|
||||
return $this->data(['is_followed' => false]);
|
||||
}
|
||||
}
|
||||
|
||||
// 标签列表
|
||||
public function tagLists()
|
||||
{
|
||||
$list = CommunityTag::where('status', 1)
|
||||
->order('sort asc')
|
||||
->field('id,name,icon,post_count,is_hot')
|
||||
->select()
|
||||
->toArray();
|
||||
return $this->data($list);
|
||||
}
|
||||
|
||||
// 购买帖子
|
||||
// confirm=0 探测是否已购买;confirm=1 确认扣分购买
|
||||
public function purchasePost()
|
||||
{
|
||||
$postId = $this->request->post('post_id/d');
|
||||
$confirm = $this->request->post('confirm/d', 0);
|
||||
$post = CommunityPost::where('id', $postId)->where('status', 1)->findOrEmpty();
|
||||
if ($post->isEmpty()) {
|
||||
return $this->fail('帖子不存在');
|
||||
}
|
||||
|
||||
if (!$post->is_paid) {
|
||||
return $this->fail('该帖子为免费帖');
|
||||
}
|
||||
|
||||
if ($post->user_id == $this->userId) {
|
||||
return $this->data([
|
||||
'content' => $post->content,
|
||||
'from_cache' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
// VIP解锁免费权益判断
|
||||
$vipUnlockFree = VipService::hasUnlockFree($this->userId);
|
||||
|
||||
// 检查是否已购买
|
||||
$hasPurchased = Db::name('user_account_log')
|
||||
->where('user_id', $this->userId)
|
||||
->where('change_type', AccountLogEnum::UP_DEC_PURCHASE_POST)
|
||||
->where('relation_id', $postId)
|
||||
->count() > 0;
|
||||
|
||||
if ($hasPurchased || $vipUnlockFree) {
|
||||
return $this->data([
|
||||
'content' => $post->content,
|
||||
'from_cache' => true,
|
||||
'vip_free' => $vipUnlockFree,
|
||||
]);
|
||||
}
|
||||
|
||||
// 未购买且未确认 → 返回待确认标记
|
||||
if (!$confirm) {
|
||||
return $this->data([
|
||||
'needs_payment' => true,
|
||||
'cost' => (int) $post->price_points,
|
||||
]);
|
||||
}
|
||||
|
||||
// 防重复扣分:再次校验
|
||||
$hasPurchased2 = Db::name('user_account_log')
|
||||
->where('user_id', $this->userId)
|
||||
->where('change_type', AccountLogEnum::UP_DEC_PURCHASE_POST)
|
||||
->where('relation_id', $postId)
|
||||
->count() > 0;
|
||||
if ($hasPurchased2) {
|
||||
return $this->data([
|
||||
'content' => $post->content,
|
||||
'from_cache' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$user = User::findOrEmpty($this->userId);
|
||||
$price = (int) $post->price_points;
|
||||
if ($user->user_points < $price) {
|
||||
return $this->fail('积分不足,当前积分' . $user->user_points . ',需要' . $price . '积分');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 扣除买家积分
|
||||
User::where('id', $this->userId)->dec('user_points', $price)->update();
|
||||
AccountLogLogic::add(
|
||||
$this->userId,
|
||||
AccountLogEnum::UP_DEC_PURCHASE_POST,
|
||||
AccountLogEnum::DEC,
|
||||
$price,
|
||||
'',
|
||||
'购买帖子#' . $postId,
|
||||
[],
|
||||
$postId
|
||||
);
|
||||
|
||||
// 作者获得积分
|
||||
User::where('id', $post->user_id)->inc('user_points', $price)->update();
|
||||
AccountLogLogic::add(
|
||||
$post->user_id,
|
||||
AccountLogEnum::UP_INC_POST_SOLD,
|
||||
AccountLogEnum::INC,
|
||||
$price,
|
||||
'',
|
||||
'帖子#' . $postId . '被购买',
|
||||
[],
|
||||
$postId
|
||||
);
|
||||
|
||||
// 更新帖子已购买人数
|
||||
CommunityPost::where('id', $postId)->inc('paid_count')->update();
|
||||
|
||||
Db::commit();
|
||||
return $this->data([
|
||||
'content' => $post->content,
|
||||
'from_cache' => false,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return $this->fail('购买失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// AI翻译帖子(需登录,暂不扣积分)
|
||||
public function translatePost()
|
||||
{
|
||||
$postId = $this->request->post('post_id/d');
|
||||
|
||||
$post = CommunityPost::where('id', $postId)->where('status', 1)->findOrEmpty();
|
||||
if ($post->isEmpty()) {
|
||||
return $this->fail('帖子不存在');
|
||||
}
|
||||
|
||||
$ext = $post->ext ? (is_string($post->ext) ? json_decode($post->ext, true) : $post->ext) : [];
|
||||
$tags = $this->getPostTags($postId);
|
||||
$isLiuhePost = $this->isLiuhePost($tags, $ext);
|
||||
if ($isLiuhePost) {
|
||||
$analysis = $this->ensureLiuheAnalysisContent($post, $ext);
|
||||
if (!$analysis['success']) {
|
||||
return $this->fail($analysis['error'] ?? '图片分析失败');
|
||||
}
|
||||
$ext = $analysis['ext'];
|
||||
return $this->data([
|
||||
'translated_content' => $ext['lottery_analysis_content'] ?? '',
|
||||
'from_cache' => $analysis['from_cache'] ?? false,
|
||||
]);
|
||||
}
|
||||
|
||||
$cacheField = 'translated_content';
|
||||
if (!empty($ext[$cacheField])) {
|
||||
return $this->data([
|
||||
'translated_content' => $ext[$cacheField],
|
||||
'from_cache' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($ext[$cacheField])) {
|
||||
$result = AiService::translateCommunityPost($post->content ?: '');
|
||||
if (!$result['success']) {
|
||||
$ext[$cacheField] = self::buildVirtualAnalysisContent($post, false);
|
||||
CommunityPost::where('id', $postId)->update([
|
||||
'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
KbSyncService::enqueue('post', 'post', $postId, 'upsert', 45);
|
||||
return $this->data([
|
||||
'translated_content' => $ext[$cacheField],
|
||||
'from_cache' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
$ext[$cacheField] = trim($result['content']);
|
||||
CommunityPost::where('id', $postId)->update([
|
||||
'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
KbSyncService::enqueue('post', 'post', $postId, 'upsert', 45);
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'translated_content' => $ext[$cacheField],
|
||||
'from_cache' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function aiAnalysis()
|
||||
{
|
||||
try {
|
||||
$postId = $this->request->get('post_id/d', 0);
|
||||
if ($postId <= 0) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$post = CommunityPost::where('id', $postId)->where('status', 1)->findOrEmpty();
|
||||
if ($post->isEmpty()) {
|
||||
return $this->fail('帖子不存在');
|
||||
}
|
||||
|
||||
$data = $post->toArray();
|
||||
$ext = is_array($data['ext'] ?? null) ? $data['ext'] : (json_decode((string) ($data['ext'] ?? ''), true) ?: []);
|
||||
$tags = $this->getPostTags($postId);
|
||||
$isTrumpPost = $this->isTrumpPost($tags);
|
||||
$isLiuhePost = $this->isLiuhePost($tags, $ext);
|
||||
if ($isTrumpPost) {
|
||||
return $this->fail('特朗普帖子仅支持翻译');
|
||||
}
|
||||
if (!$isLiuhePost) {
|
||||
return $this->fail('该帖子类型无需AI分析');
|
||||
}
|
||||
|
||||
$analysis = $this->ensureLiuheAnalysisContent($post, $ext);
|
||||
if (!$analysis['success']) {
|
||||
return $this->fail($analysis['error'] ?? '图片分析失败');
|
||||
}
|
||||
$ext = $analysis['ext'];
|
||||
$data['tags'] = array_values($tags);
|
||||
$data['ext'] = $ext;
|
||||
if (!empty($ext['translated_content'])) {
|
||||
$data['translated_content'] = (string) $ext['translated_content'];
|
||||
}
|
||||
if (!empty($ext['lottery_analysis_content'])) {
|
||||
$data['lottery_analysis_content'] = (string) $ext['lottery_analysis_content'];
|
||||
}
|
||||
|
||||
$force = $this->request->get('force/d', 0) === 1;
|
||||
$result = AiService::analyzePost($postId, $data, $this->userId, true, $force);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'analysis' => $result['data'],
|
||||
'from_cache' => $result['from_cache'] ?? false,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->fail('AI分析异常: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function aiAnalysisResult()
|
||||
{
|
||||
try {
|
||||
$postId = $this->request->get('post_id/d', 0);
|
||||
if ($postId <= 0) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$post = CommunityPost::where('id', $postId)->where('status', 1)->findOrEmpty();
|
||||
if ($post->isEmpty()) {
|
||||
return $this->fail('帖子不存在');
|
||||
}
|
||||
|
||||
$data = $post->toArray();
|
||||
$ext = is_array($data['ext'] ?? null) ? $data['ext'] : (json_decode((string) ($data['ext'] ?? ''), true) ?: []);
|
||||
$isLiuhePost = $this->isLiuhePost($this->getPostTags($postId), $ext);
|
||||
$cache = AiAnalysis::getCache(AiAnalysis::TYPE_POST_ANALYSIS, $postId);
|
||||
if ($cache) {
|
||||
$cacheData = json_decode((string) ($cache['result'] ?? ''), true);
|
||||
$isValid = is_array($cacheData) && !empty($cacheData);
|
||||
$isValidLiuheCache = !empty($cacheData['next_prediction'])
|
||||
&& ($cacheData['analysis_source'] ?? '') === 'image_free_v1';
|
||||
if ($isValid && (!$isLiuhePost || $isValidLiuheCache)) {
|
||||
return $this->data([
|
||||
'status' => 'success',
|
||||
'analysis' => $cacheData,
|
||||
'from_cache' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'status' => 'processing',
|
||||
'analysis' => null,
|
||||
'from_cache' => false,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->fail('AI分析结果查询异常: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function getPostTags(int $postId): array
|
||||
{
|
||||
$tagIds = Db::name('community_post_tag')->where('post_id', $postId)->column('tag_id');
|
||||
if (empty($tagIds)) {
|
||||
return [];
|
||||
}
|
||||
return array_values(CommunityTag::whereIn('id', $tagIds)->column('name'));
|
||||
}
|
||||
|
||||
private function isTrumpPost(array $tags): bool
|
||||
{
|
||||
return in_array('特朗普', $tags, true);
|
||||
}
|
||||
|
||||
private function isLiuhePost(array $tags, array $ext = []): bool
|
||||
{
|
||||
foreach (self::LIUHE_TAGS as $tag) {
|
||||
if (in_array($tag, $tags, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$lotteryKey = (string) ($ext['lottery_key'] ?? '');
|
||||
return in_array($lotteryKey, ['a6', 'xa6'], true);
|
||||
}
|
||||
|
||||
private function ensureLiuheAnalysisContent(CommunityPost $post, array $ext): array
|
||||
{
|
||||
$images = is_array($post->images) ? array_values($post->images) : [];
|
||||
$imageHash = md5(json_encode($images, JSON_UNESCAPED_UNICODE));
|
||||
$cacheSource = (string) ($ext['lottery_analysis_source'] ?? '');
|
||||
$cacheHash = (string) ($ext['lottery_analysis_images_hash'] ?? '');
|
||||
|
||||
if (
|
||||
!empty($ext['lottery_analysis_content'])
|
||||
&& $cacheSource === 'image_only'
|
||||
&& $cacheHash === $imageHash
|
||||
) {
|
||||
return ['success' => true, 'ext' => $ext, 'from_cache' => true];
|
||||
}
|
||||
|
||||
$result = AiService::analyzeLotteryPostImages($post->content ?: '', $images);
|
||||
if (empty($result['success']) || empty($result['content'])) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => (string) ($result['error'] ?? '帖子图片分析失败'),
|
||||
];
|
||||
}
|
||||
|
||||
$ext['lottery_analysis_content'] = trim((string) $result['content']);
|
||||
$ext['lottery_analysis_source'] = 'image_only';
|
||||
$ext['lottery_analysis_images_hash'] = $imageHash;
|
||||
$ext['lottery_analysis_generated_at'] = time();
|
||||
|
||||
CommunityPost::where('id', (int) $post->id)->update([
|
||||
'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
KbSyncService::enqueue('post', 'post', (int) $post->id, 'upsert', 45);
|
||||
|
||||
return ['success' => true, 'ext' => $ext, 'from_cache' => false];
|
||||
}
|
||||
|
||||
private static function buildVirtualAnalysisContent($post, bool $isLottery): string
|
||||
{
|
||||
if ($isLottery) {
|
||||
$longText = "【49宝典开奖号码深度分析】\n\n"
|
||||
. "一、本期开奖概况\n"
|
||||
. "本期开奖号码为:03 07 12 18 25 31 + 特码 08。\n"
|
||||
. "其中红波号码2个(07、25),蓝波号码3个(03、12、31),绿波号码1个(18)。\n\n"
|
||||
. "二、号码属性分析\n"
|
||||
. str_repeat("1. 号码 03:生肖鼠,五行木,蓝波,小单。该号码近10期出现2次,属于温号。\n"
|
||||
. "2. 号码 07:生肖虎,五行火,红波,小单。该号码近10期出现3次,属于热号。\n"
|
||||
. "3. 号码 12:生肖猪,五行水,蓝波,大双。该号码近10期出现1次,属于冷号。\n"
|
||||
. "4. 号码 18:生肖蛇,五行土,绿波,大双。该号码近10期出现2次,属于温号。\n"
|
||||
. "5. 号码 25:生肖鼠,五行金,红波,大单。该号码近10期出现4次,属于热号。\n"
|
||||
. "6. 号码 31:生肖马,五行木,蓝波,大单。该号码近10期出现1次,属于冷号。\n"
|
||||
. "7. 特码 08:生肖兔,五行火,蓝波,小双。该号码近10期出现2次,属于温号。\n\n", 5)
|
||||
. "三、走势趋势\n"
|
||||
. "从近期走势来看,大号(25-49)区间号码持续走热,小号(01-24)区间号码相对偏冷。\n"
|
||||
. "建议关注下期大号区间号码的回补机会。\n\n"
|
||||
. "四、冷热统计\n"
|
||||
. "热号(近10期出现3次以上):07、25\n"
|
||||
. "温号(近10期出现1-2次):03、18、08\n"
|
||||
. "冷号(近10期出现0次):01、02、04、05、06、09、10、11、13、14、15、16、17、19、20、21、22、23、24、26、27、28、29、30、32、33、34、35、36、37、38、39、40、41、42、43、44、45、46、47、48、49\n\n"
|
||||
. "五、综合建议\n"
|
||||
. "彩票开奖具有随机性,以上分析仅基于历史数据统计,不构成购买建议。请理性购彩,量力而行。\n\n"
|
||||
. "—— AI智能分析仅供参考 ——";
|
||||
return $longText;
|
||||
}
|
||||
$content = $post->content ?? '';
|
||||
$short = mb_strlen($content) > 100 ? mb_substr($content, 0, 100) . '...' : $content;
|
||||
$longTestText = "这是一段模拟的长文本翻译内容,用于测试弹窗滚动效果。\n\n"
|
||||
. "在H5页面中,如果弹窗内容过长,用户需要能够滚动查看完整内容。\n\n"
|
||||
. str_repeat("这是第%d段测试文本。弹窗滚动功能测试中,请确保内容可以正常滚动显示。"
|
||||
. "如果滚动功能正常,用户将能够顺畅地阅读所有内容,而不会因为内容过长导致体验下降。"
|
||||
. "同时,弹窗背后的页面应该保持固定,不能随着弹窗内容的滚动而滚动。\n\n", 30)
|
||||
. "【测试结束】感谢您的耐心阅读!";
|
||||
return $longTestText;
|
||||
}
|
||||
|
||||
// 用户统计
|
||||
public function userStats()
|
||||
{
|
||||
$userId = $this->userId;
|
||||
$postCount = CommunityPost::where('user_id', $userId)->where('status', 1)->count();
|
||||
$followCount = CommunityFollow::where('user_id', $userId)->count();
|
||||
$fansCount = CommunityFollow::where('follow_user_id', $userId)->count();
|
||||
$collectCount = Db::name('article_collect')->where('user_id', $userId)->where('status', 1)->whereNull('delete_time')->count();
|
||||
|
||||
$userPoints = User::where('id', $userId)->value('user_points') ?: 0;
|
||||
$chatUnreadCount = PrivateChatService::unreadCount($userId);
|
||||
|
||||
return $this->data([
|
||||
'post_count' => $postCount,
|
||||
'follow_count' => $followCount,
|
||||
'fans_count' => $fansCount,
|
||||
'collect_count' => $collectCount,
|
||||
'user_points' => $userPoints,
|
||||
'chat_unread_count' => $chatUnreadCount,
|
||||
]);
|
||||
}
|
||||
|
||||
// 关注列表
|
||||
public function followList()
|
||||
{
|
||||
$page = $this->request->get('page_no/d', 1);
|
||||
$size = $this->request->get('page_size/d', 20);
|
||||
$targetUserId = $this->request->get('user_id/d', 0) ?: $this->userId;
|
||||
if (!$targetUserId) {
|
||||
return $this->data(['lists' => [], 'page_no' => $page, 'page_size' => $size]);
|
||||
}
|
||||
|
||||
$followIds = CommunityFollow::where('user_id', $targetUserId)
|
||||
->order('create_time desc')
|
||||
->page($page, $size)
|
||||
->column('follow_user_id');
|
||||
|
||||
$list = [];
|
||||
if ($followIds) {
|
||||
$users = User::field('id,sn,nickname,avatar')
|
||||
->whereIn('id', $followIds)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 检查对方是否也关注了我(互相关注)
|
||||
$fansOfMe = CommunityFollow::whereIn('user_id', $followIds)
|
||||
->where('follow_user_id', $targetUserId)
|
||||
->column('user_id');
|
||||
|
||||
foreach ($users as &$u) {
|
||||
$u['is_followed'] = true; // 关注列表里都是已关注的
|
||||
$u['is_mutual'] = in_array($u['id'], $fansOfMe);
|
||||
}
|
||||
$list = $users;
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'lists' => $list,
|
||||
'page_no' => $page,
|
||||
'page_size' => $size,
|
||||
]);
|
||||
}
|
||||
|
||||
// 粉丝列表
|
||||
public function fansList()
|
||||
{
|
||||
$page = $this->request->get('page_no/d', 1);
|
||||
$size = $this->request->get('page_size/d', 20);
|
||||
$targetUserId = $this->request->get('user_id/d', 0) ?: $this->userId;
|
||||
if (!$targetUserId) {
|
||||
return $this->data(['lists' => [], 'page_no' => $page, 'page_size' => $size]);
|
||||
}
|
||||
|
||||
$fansIds = CommunityFollow::where('follow_user_id', $targetUserId)
|
||||
->order('create_time desc')
|
||||
->page($page, $size)
|
||||
->column('user_id');
|
||||
|
||||
$list = [];
|
||||
if ($fansIds) {
|
||||
$users = User::field('id,sn,nickname,avatar')
|
||||
->whereIn('id', $fansIds)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 检查我是否关注了该粉丝(is_followed)+ 互相关注
|
||||
$myFollows = CommunityFollow::where('user_id', $targetUserId)
|
||||
->whereIn('follow_user_id', $fansIds)
|
||||
->column('follow_user_id');
|
||||
|
||||
foreach ($users as &$u) {
|
||||
$u['is_followed'] = in_array($u['id'], $myFollows);
|
||||
$u['is_mutual'] = $u['is_followed']; // 粉丝关注了我,我也关注了他 = 互关
|
||||
}
|
||||
$list = $users;
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'lists' => $list,
|
||||
'page_no' => $page,
|
||||
'page_size' => $size,
|
||||
]);
|
||||
}
|
||||
|
||||
// 用户主页(公开)
|
||||
public function userProfile()
|
||||
{
|
||||
$userId = $this->request->get('user_id/d');
|
||||
if (!$userId) {
|
||||
return $this->fail('参数错误');
|
||||
}
|
||||
|
||||
$user = User::field('id,sn,nickname,avatar,sex,create_time')->where('id', $userId)->findOrEmpty();
|
||||
if ($user->isEmpty()) {
|
||||
return $this->fail('用户不存在');
|
||||
}
|
||||
|
||||
$data = $user->toArray();
|
||||
$data['post_count'] = CommunityPost::where('user_id', $userId)->where('status', 1)->count();
|
||||
$data['comment_count'] = CommunityComment::where('user_id', $userId)->count();
|
||||
$data['follow_count'] = CommunityFollow::where('user_id', $userId)->count();
|
||||
$data['fans_count'] = CommunityFollow::where('follow_user_id', $userId)->count();
|
||||
$data['like_count'] = CommunityPost::where('user_id', $userId)->where('status', 1)->sum('like_count');
|
||||
|
||||
// 加入天数
|
||||
$data['join_days'] = max(1, (int) ceil((time() - strtotime($data['create_time'])) / 86400));
|
||||
|
||||
// 当前登录用户是否关注
|
||||
$data['is_followed'] = false;
|
||||
$data['is_friend'] = false;
|
||||
$data['relationship'] = PrivateChatService::relationship($this->userId, $userId);
|
||||
if ($this->userId && $this->userId != $userId) {
|
||||
$data['is_followed'] = CommunityFollow::where([
|
||||
'user_id' => $this->userId,
|
||||
'follow_user_id' => $userId
|
||||
])->count() > 0;
|
||||
$data['is_friend'] = $data['is_followed'];
|
||||
$data['relationship'] = PrivateChatService::relationship($this->userId, $userId);
|
||||
}
|
||||
|
||||
$data['is_self'] = $this->userId == $userId;
|
||||
|
||||
return $this->data($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\model\article\Article;
|
||||
use app\common\model\popup\PagePopup;
|
||||
use app\api\logic\IndexLogic;
|
||||
use think\response\Json;
|
||||
|
||||
/**
|
||||
* 通用富文本内容
|
||||
* GET /api/content/detail?type=xxx&id=xxx
|
||||
*
|
||||
* 支持的 type:
|
||||
* - policy 协议(id 实际为 type 字符串:service/privacy 等,由 IndexLogic::getPolicyByType 处理)
|
||||
* - article 资讯文章
|
||||
* - popup 弹窗自带的富文本内容(直接取 page_popup.content)
|
||||
* - custom 自定义页面(暂未启用,留扩展位)
|
||||
*/
|
||||
class ContentController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['detail'];
|
||||
|
||||
public function detail(): Json
|
||||
{
|
||||
$type = trim((string)$this->request->get('type', ''));
|
||||
$idRaw = trim((string)$this->request->get('id', ''));
|
||||
|
||||
if (!$type) {
|
||||
return $this->fail('参数错误:type');
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'policy': {
|
||||
// policy 用 id 字段传 type 字符串
|
||||
$policy = IndexLogic::getPolicyByType($idRaw);
|
||||
if (!$policy) {
|
||||
return $this->fail('内容不存在');
|
||||
}
|
||||
return $this->data([
|
||||
'type' => 'policy',
|
||||
'id' => $idRaw,
|
||||
'title' => (string)($policy['title'] ?? ''),
|
||||
'content' => (string)($policy['content'] ?? ''),
|
||||
'create_time' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
case 'article': {
|
||||
$id = (int)$idRaw;
|
||||
if ($id <= 0) return $this->fail('参数错误:id');
|
||||
$info = Article::where('id', $id)
|
||||
->where('is_show', 1)
|
||||
->find();
|
||||
if (!$info) return $this->fail('内容不存在');
|
||||
return $this->data([
|
||||
'type' => 'article',
|
||||
'id' => $id,
|
||||
'title' => (string)($info['title'] ?? ''),
|
||||
'content' => (string)($info['content'] ?? ''),
|
||||
'create_time' => (string)($info['create_time'] ?? ''),
|
||||
]);
|
||||
}
|
||||
|
||||
case 'popup': {
|
||||
$id = (int)$idRaw;
|
||||
if ($id <= 0) return $this->fail('参数错误:id');
|
||||
$info = PagePopup::where('id', $id)
|
||||
->where('status', 1)
|
||||
->find();
|
||||
if (!$info) return $this->fail('内容不存在');
|
||||
return $this->data([
|
||||
'type' => 'popup',
|
||||
'id' => $id,
|
||||
'title' => (string)($info['title'] ?: $info['name']),
|
||||
'content' => (string)($info['content'] ?? ''),
|
||||
'create_time' => (string)($info['create_time'] ?? ''),
|
||||
]);
|
||||
}
|
||||
|
||||
default:
|
||||
return $this->fail('暂不支持的 type:' . $type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use think\facade\Cache;
|
||||
use think\facade\Log;
|
||||
|
||||
class CryptoController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['proxy'];
|
||||
|
||||
/**
|
||||
* Binance API 代理(解决客户端直连被墙问题)
|
||||
* GET /api/crypto/proxy?path=/api/v3/ticker/24hr&symbols=...
|
||||
*/
|
||||
public function proxy()
|
||||
{
|
||||
$path = $this->request->get('path', '');
|
||||
if (empty($path) || !str_starts_with($path, '/api/v3/')) {
|
||||
return $this->fail('无效的API路径');
|
||||
}
|
||||
|
||||
// 构建完整URL,将除 path 外的参数透传
|
||||
$params = $this->request->get();
|
||||
unset($params['path']);
|
||||
ksort($params);
|
||||
$query = http_build_query($params);
|
||||
$url = 'https://api.binance.com' . $path . ($query ? '?' . $query : '');
|
||||
|
||||
$cacheKey = 'binance_proxy_' . md5($path . '|' . $query);
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return json($cached, 200);
|
||||
}
|
||||
|
||||
Log::info('[CryptoProxy] request url=' . $url);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_HTTPHEADER => ['Accept: application/json'],
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
Log::error('[CryptoProxy] curl_error=' . $error . ' url=' . $url);
|
||||
return $this->fail('API请求失败: ' . $error);
|
||||
}
|
||||
|
||||
Log::info('[CryptoProxy] http_code=' . $httpCode . ' response_preview=' . substr((string) $response, 0, 500));
|
||||
|
||||
$data = json_decode($response, true);
|
||||
if ($httpCode === 200 && $data !== null) {
|
||||
Cache::set($cacheKey, $data, 3);
|
||||
}
|
||||
return json($data, $httpCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\model\device\UserDevice;
|
||||
use think\response\Json;
|
||||
|
||||
/**
|
||||
* 设备信息上报
|
||||
* POST /api/device/register
|
||||
*/
|
||||
class DeviceController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['register'];
|
||||
|
||||
/**
|
||||
* 注册或更新设备信息
|
||||
* 请求字段:device_id(必填)、platform、system、model、brand、app_version、
|
||||
* app_version_code、screen_width、screen_height、network_type、
|
||||
* language、timezone、push_cid、extra(json string)
|
||||
*/
|
||||
public function register(): Json
|
||||
{
|
||||
$params = $this->request->post();
|
||||
$deviceId = trim((string)($params['device_id'] ?? ''));
|
||||
if (!$deviceId) {
|
||||
return $this->fail('device_id 必填');
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$ip = $this->request->ip() ?: '';
|
||||
$ua = $this->request->header('user-agent', '');
|
||||
if (mb_strlen($ua) > 500) $ua = mb_substr($ua, 0, 500);
|
||||
|
||||
$fields = [
|
||||
'platform', 'system', 'model', 'brand', 'app_version',
|
||||
'app_version_code', 'screen_width', 'screen_height',
|
||||
'network_type', 'language', 'timezone', 'push_cid'
|
||||
];
|
||||
$data = [];
|
||||
foreach ($fields as $f) {
|
||||
if (isset($params[$f])) $data[$f] = $params[$f];
|
||||
}
|
||||
$data['ip'] = $ip;
|
||||
$data['ua'] = $ua;
|
||||
if (isset($params['extra'])) {
|
||||
$data['extra'] = is_array($params['extra']) ? json_encode($params['extra'], JSON_UNESCAPED_UNICODE) : (string)$params['extra'];
|
||||
}
|
||||
$data['user_id'] = $this->userId ?: 0;
|
||||
$data['last_at'] = $now;
|
||||
|
||||
$exist = UserDevice::where('device_id', $deviceId)->find();
|
||||
if ($exist) {
|
||||
$data['active_count'] = ($exist['active_count'] ?? 0) + 1;
|
||||
UserDevice::where('id', $exist['id'])->update($data);
|
||||
return $this->data(['id' => $exist['id'], 'is_new' => false]);
|
||||
}
|
||||
|
||||
$data['device_id'] = $deviceId;
|
||||
$data['first_at'] = $now;
|
||||
$data['active_count'] = 1;
|
||||
$id = UserDevice::insertGetId($data);
|
||||
return $this->data(['id' => $id, 'is_new' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
|
||||
use app\api\logic\IndexLogic;
|
||||
use think\response\Json;
|
||||
|
||||
|
||||
/**
|
||||
* index
|
||||
* Class IndexController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class IndexController extends BaseApiController
|
||||
{
|
||||
|
||||
|
||||
public array $notNeedLogin = ['index', 'config', 'policy', 'decorate'];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 首页数据
|
||||
* @return Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 19:15
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$result = IndexLogic::getIndexData();
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 全局配置
|
||||
* @return Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 19:41
|
||||
*/
|
||||
public function config()
|
||||
{
|
||||
$result = IndexLogic::getConfigData();
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 政策协议
|
||||
* @return Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 20:00
|
||||
*/
|
||||
public function policy()
|
||||
{
|
||||
$type = $this->request->get('type/s', '');
|
||||
$result = IndexLogic::getPolicyByType($type);
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 装修信息
|
||||
* @return Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 18:37
|
||||
*/
|
||||
public function decorate()
|
||||
{
|
||||
$id = $this->request->get('id/d');
|
||||
$result = IndexLogic::getDecorate($id);
|
||||
return $this->data($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\validate\{LoginAccountValidate, RegisterValidate, WebScanLoginValidate, WechatLoginValidate};
|
||||
use app\api\logic\LoginLogic;
|
||||
|
||||
/**
|
||||
* 登录注册
|
||||
* Class LoginController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class LoginController extends BaseApiController
|
||||
{
|
||||
|
||||
public array $notNeedLogin = ['register', 'account', 'logout', 'codeUrl', 'oaLogin', 'mnpLogin', 'getScanCode', 'scanLogin'];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 注册账号
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/7 15:38
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$params = (new RegisterValidate())->post()->goCheck('register');
|
||||
$result = LoginLogic::register($params);
|
||||
if (true === $result) {
|
||||
return $this->success('注册成功', [], 1, 1);
|
||||
}
|
||||
return $this->fail(LoginLogic::getError());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 账号密码/手机号密码/手机号验证码登录
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:42
|
||||
*/
|
||||
public function account()
|
||||
{
|
||||
$params = (new LoginAccountValidate())->post()->goCheck();
|
||||
$result = LoginLogic::login($params);
|
||||
if (false === $result) {
|
||||
return $this->fail(LoginLogic::getError());
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 退出登录
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:42
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
LoginLogic::logout($this->userInfo);
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取微信请求code的链接
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/15 18:27
|
||||
*/
|
||||
public function codeUrl()
|
||||
{
|
||||
$url = $this->request->get('url');
|
||||
$result = ['url' => LoginLogic::codeUrl($url)];
|
||||
return $this->success('获取成功', $result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 公众号登录
|
||||
* @return \think\response\Json
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:48
|
||||
*/
|
||||
public function oaLogin()
|
||||
{
|
||||
$params = (new WechatLoginValidate())->post()->goCheck('oa');
|
||||
$res = LoginLogic::oaLogin($params);
|
||||
if (false === $res) {
|
||||
return $this->fail(LoginLogic::getError());
|
||||
}
|
||||
return $this->success('', $res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 小程序-登录接口
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:48
|
||||
*/
|
||||
public function mnpLogin()
|
||||
{
|
||||
$params = (new WechatLoginValidate())->post()->goCheck('mnpLogin');
|
||||
$res = LoginLogic::mnpLogin($params);
|
||||
if (false === $res) {
|
||||
return $this->fail(LoginLogic::getError());
|
||||
}
|
||||
return $this->success('', $res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 小程序绑定微信
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:48
|
||||
*/
|
||||
public function mnpAuthBind()
|
||||
{
|
||||
$params = (new WechatLoginValidate())->post()->goCheck("wechatAuth");
|
||||
$params['user_id'] = $this->userId;
|
||||
$result = LoginLogic::mnpAuthLogin($params);
|
||||
if ($result === false) {
|
||||
return $this->fail(LoginLogic::getError());
|
||||
}
|
||||
return $this->success('绑定成功', [], 1, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @notes 公众号绑定微信
|
||||
* @return \think\response\Json
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:48
|
||||
*/
|
||||
public function oaAuthBind()
|
||||
{
|
||||
$params = (new WechatLoginValidate())->post()->goCheck("wechatAuth");
|
||||
$params['user_id'] = $this->userId;
|
||||
$result = LoginLogic::oaAuthLogin($params);
|
||||
if ($result === false) {
|
||||
return $this->fail(LoginLogic::getError());
|
||||
}
|
||||
return $this->success('绑定成功', [], 1, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取扫码地址
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/10/20 18:25
|
||||
*/
|
||||
public function getScanCode()
|
||||
{
|
||||
$redirectUri = $this->request->get('url/s');
|
||||
$result = LoginLogic::getScanCode($redirectUri);
|
||||
if (false === $result) {
|
||||
return $this->fail(LoginLogic::getError() ?? '未知错误');
|
||||
}
|
||||
return $this->success('', $result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 网站扫码登录
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/10/21 10:28
|
||||
*/
|
||||
public function scanLogin()
|
||||
{
|
||||
$params = (new WebScanLoginValidate())->post()->goCheck();
|
||||
$result = LoginLogic::scanLogin($params);
|
||||
if (false === $result) {
|
||||
return $this->fail(LoginLogic::getError() ?? '登录失败');
|
||||
}
|
||||
return $this->success('', $result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 更新用户头像昵称
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2023/2/22 11:15
|
||||
*/
|
||||
public function updateUser()
|
||||
{
|
||||
$params = (new WechatLoginValidate())->post()->goCheck("updateUser");
|
||||
LoginLogic::updateUser($params, $this->userId);
|
||||
return $this->success('操作成功', [], 1, 1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,641 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\model\lottery\LotteryCategory;
|
||||
use app\common\model\lottery\LotteryDraw;
|
||||
use app\common\model\lottery\LotteryGame;
|
||||
use app\common\model\lottery\LotteryGameCategory;
|
||||
use app\common\model\lottery\LotteryDrawResult;
|
||||
use app\common\model\lottery\LotteryAiAnalysis;
|
||||
use app\common\model\lottery\LotteryNumberMapping;
|
||||
use app\common\model\lottery\LotteryRegion;
|
||||
use app\common\service\ai\AiService;
|
||||
|
||||
class LotteryController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['categoryList', 'regionList', 'drawList', 'drawDetail', 'latestDraw', 'aiPredict', 'gameList', 'latestDrawResult', 'drawResultList', 'aiHistory', 'aiHotCold', 'aiTrend', 'aiStats', 'aiColorZodiac', 'aiAnalysisHistory'];
|
||||
|
||||
/**
|
||||
* 彩种游戏列表(一级分类 + 二级彩种)
|
||||
*/
|
||||
public function gameList()
|
||||
{
|
||||
$categories = LotteryGameCategory::where('is_show', 1)
|
||||
->order('sort', 'asc')
|
||||
->field('id,label,value,sort')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$games = LotteryGame::where('is_show', 1)
|
||||
->order('sort asc, id asc')
|
||||
->field('id,code,template,name,icon,status,open_status,category,group_id,open_count,sort,has_video')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$gameMap = [];
|
||||
foreach ($games as $game) {
|
||||
$gameMap[$game['category']][] = $game;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($categories as $cat) {
|
||||
$cat['games'] = $gameMap[$cat['value']] ?? [];
|
||||
$result[] = $cat;
|
||||
}
|
||||
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 彩票分类列表
|
||||
*/
|
||||
public function categoryList()
|
||||
{
|
||||
$domain = request()->domain() . '/';
|
||||
$list = LotteryCategory::where('is_show', 1)
|
||||
->order('sort', 'asc')
|
||||
->field('id,name,code,region,icon,description,draw_rule,draw_time')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($list as &$item) {
|
||||
if (!empty($item['icon']) && !str_starts_with($item['icon'], 'http')) {
|
||||
$item['icon'] = $domain . $item['icon'];
|
||||
}
|
||||
}
|
||||
return $this->data($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 彩票地区Tab列表
|
||||
*/
|
||||
public function regionList()
|
||||
{
|
||||
$domain = request()->domain() . '/';
|
||||
$list = LotteryRegion::where('is_show', 1)
|
||||
->order('sort', 'asc')
|
||||
->field('id,label,value,icon')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($list as &$item) {
|
||||
if (!empty($item['icon']) && !str_starts_with($item['icon'], 'http')) {
|
||||
$item['icon'] = $domain . $item['icon'];
|
||||
}
|
||||
}
|
||||
return $this->data($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开奖列表
|
||||
*/
|
||||
public function drawList()
|
||||
{
|
||||
$categoryId = $this->request->get('category_id/d', 0);
|
||||
$pageNo = $this->request->get('page_no/d', 1);
|
||||
$pageSize = $this->request->get('page_size/d', 20);
|
||||
|
||||
$query = LotteryDraw::where('is_show', 1);
|
||||
if ($categoryId > 0) {
|
||||
$query->where('category_id', $categoryId);
|
||||
}
|
||||
|
||||
$countQuery = clone $query;
|
||||
$count = $countQuery->count();
|
||||
$list = $query->order('draw_date desc, period desc')
|
||||
->page($pageNo, $pageSize)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 解析JSON字段
|
||||
foreach ($list as &$item) {
|
||||
$item['numbers'] = is_string($item['numbers']) ? json_decode($item['numbers'], true) : $item['numbers'];
|
||||
$item['zodiac'] = is_string($item['zodiac']) ? json_decode($item['zodiac'], true) : $item['zodiac'];
|
||||
$item['elements'] = is_string($item['elements']) ? json_decode($item['elements'], true) : $item['elements'];
|
||||
$item['color'] = is_string($item['color']) ? json_decode($item['color'], true) : $item['color'];
|
||||
$item['prize_info'] = is_string($item['prize_info']) ? json_decode($item['prize_info'], true) : $item['prize_info'];
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'lists' => $list,
|
||||
'count' => $count,
|
||||
'page_no' => $pageNo,
|
||||
'page_size' => $pageSize,
|
||||
'last_page' => ceil($count / $pageSize),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开奖详情
|
||||
*/
|
||||
public function drawDetail()
|
||||
{
|
||||
$id = $this->request->get('id/d', 0);
|
||||
if (empty($id)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$draw = LotteryDraw::where('id', $id)->where('is_show', 1)->findOrEmpty();
|
||||
if ($draw->isEmpty()) {
|
||||
return $this->fail('记录不存在');
|
||||
}
|
||||
|
||||
$data = $draw->toArray();
|
||||
$data['numbers'] = is_string($data['numbers']) ? json_decode($data['numbers'], true) : $data['numbers'];
|
||||
$data['zodiac'] = is_string($data['zodiac']) ? json_decode($data['zodiac'], true) : $data['zodiac'];
|
||||
$data['elements'] = is_string($data['elements']) ? json_decode($data['elements'], true) : $data['elements'];
|
||||
$data['color'] = is_string($data['color']) ? json_decode($data['color'], true) : $data['color'];
|
||||
$data['prize_info'] = is_string($data['prize_info']) ? json_decode($data['prize_info'], true) : $data['prize_info'];
|
||||
|
||||
// 附带分类信息
|
||||
$category = LotteryCategory::where('id', $data['category_id'])->findOrEmpty();
|
||||
if (!$category->isEmpty()) {
|
||||
$data['category'] = $category->toArray();
|
||||
}
|
||||
|
||||
return $this->data($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 各彩种最新一期开奖
|
||||
*/
|
||||
public function latestDraw()
|
||||
{
|
||||
$categoryId = $this->request->get('category_id/d', 0);
|
||||
|
||||
$query = LotteryDraw::where('is_show', 1)->where('status', 1);
|
||||
if ($categoryId > 0) {
|
||||
$query->where('category_id', $categoryId);
|
||||
}
|
||||
|
||||
// 按分类分组取最新一期
|
||||
if ($categoryId > 0) {
|
||||
$draw = $query->order('draw_date', 'desc')->findOrEmpty();
|
||||
if ($draw->isEmpty()) {
|
||||
return $this->data(null);
|
||||
}
|
||||
$data = $draw->toArray();
|
||||
$data['numbers'] = is_string($data['numbers']) ? json_decode($data['numbers'], true) : $data['numbers'];
|
||||
$data['color'] = is_string($data['color']) ? json_decode($data['color'], true) : $data['color'];
|
||||
return $this->data($data);
|
||||
}
|
||||
|
||||
// 获取所有分类的最新开奖
|
||||
$categories = LotteryCategory::where('is_show', 1)->order('sort', 'asc')->select()->toArray();
|
||||
$result = [];
|
||||
foreach ($categories as $cat) {
|
||||
$draw = LotteryDraw::where('category_id', $cat['id'])
|
||||
->where('is_show', 1)
|
||||
->where('status', 1)
|
||||
->order('draw_date', 'desc')
|
||||
->findOrEmpty();
|
||||
if (!$draw->isEmpty()) {
|
||||
$d = $draw->toArray();
|
||||
$d['numbers'] = is_string($d['numbers']) ? json_decode($d['numbers'], true) : $d['numbers'];
|
||||
$d['color'] = is_string($d['color']) ? json_decode($d['color'], true) : $d['color'];
|
||||
$d['category_name'] = $cat['name'];
|
||||
$d['category_code'] = $cat['code'];
|
||||
$result[] = $d;
|
||||
}
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为号码数组附加生肖(zodiac)和波色(color)映射(六合彩专用)
|
||||
*/
|
||||
private function enrichNumbers(array $numbers, string $template = ''): array
|
||||
{
|
||||
if (empty($numbers))
|
||||
return [];
|
||||
$isHk6 = stripos($template, 'hk6') !== false || stripos($template, 'lhc') !== false;
|
||||
if (!$isHk6)
|
||||
return $numbers;
|
||||
|
||||
$year = (int) date('Y');
|
||||
$mapping = LotteryNumberMapping::where('year', $year)
|
||||
->whereIn('type', [LotteryNumberMapping::TYPE_ZODIAC, LotteryNumberMapping::TYPE_COLOR])
|
||||
->order('type asc, sort asc')
|
||||
->select()->toArray();
|
||||
|
||||
$zodiacMap = [];
|
||||
$colorMap = [];
|
||||
foreach ($mapping as $row) {
|
||||
$nums = is_string($row['numbers']) ? json_decode($row['numbers'], true) : $row['numbers'];
|
||||
foreach ($nums as $n) {
|
||||
if ((int) $row['type'] === LotteryNumberMapping::TYPE_ZODIAC) {
|
||||
$zodiacMap[(int) $n] = $row['attr_name'];
|
||||
} else {
|
||||
$colorMap[(int) $n] = $row['attr_code'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$enriched = [];
|
||||
foreach ($numbers as $num) {
|
||||
$n = (int) $num;
|
||||
$enriched[] = [
|
||||
'num' => $num,
|
||||
'zodiac' => $zodiacMap[$n] ?? '',
|
||||
'color' => $colorMap[$n] ?? 'red',
|
||||
];
|
||||
}
|
||||
return $enriched;
|
||||
}
|
||||
|
||||
/**
|
||||
* 各彩种最新一期开奖(新表 lottery_draw_result)
|
||||
*/
|
||||
public function latestDrawResult()
|
||||
{
|
||||
$code = $this->request->get('code', '');
|
||||
|
||||
if ($code) {
|
||||
$draw = LotteryDrawResult::where('code', $code)
|
||||
->order('draw_time desc')
|
||||
->findOrEmpty();
|
||||
if ($draw->isEmpty()) {
|
||||
return $this->data(null);
|
||||
}
|
||||
$data = $draw->toArray();
|
||||
if (!empty($data['draw_code'])) {
|
||||
$data['status'] = 1;
|
||||
}
|
||||
$rawNumbers = $data['draw_code'] ? explode(',', $data['draw_code']) : [];
|
||||
$game = LotteryGame::where('code', $code)->field('name,template')->findOrEmpty();
|
||||
$data['game_name'] = $game->isEmpty() ? $code : $game['name'];
|
||||
$template = $game->isEmpty() ? '' : $game['template'];
|
||||
$data['numbers'] = $this->enrichNumbers($rawNumbers, $template);
|
||||
$data['template'] = $template;
|
||||
return $this->data($data);
|
||||
}
|
||||
|
||||
// 全部彩种,每个取最新一条
|
||||
$games = LotteryGame::where('is_show', 1)
|
||||
->order('sort asc, id asc')
|
||||
->field('code,name,template,category')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$result = [];
|
||||
foreach ($games as $game) {
|
||||
$draw = LotteryDrawResult::where('code', $game['code'])
|
||||
->order('draw_time desc')
|
||||
->findOrEmpty();
|
||||
if (!$draw->isEmpty()) {
|
||||
$d = $draw->toArray();
|
||||
if (!empty($d['draw_code'])) {
|
||||
$d['status'] = 1;
|
||||
}
|
||||
$rawNumbers = $d['draw_code'] ? explode(',', $d['draw_code']) : [];
|
||||
$d['numbers'] = $this->enrichNumbers($rawNumbers, $game['template']);
|
||||
$d['game_name'] = $game['name'];
|
||||
$d['template'] = $game['template'];
|
||||
$d['category'] = $game['category'];
|
||||
$result[] = $d;
|
||||
}
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开奖历史列表(新表 lottery_draw_result)
|
||||
*/
|
||||
public function drawResultList()
|
||||
{
|
||||
$code = $this->request->get('code', '');
|
||||
$pageNo = $this->request->get('page_no/d', 1);
|
||||
$pageSize = $this->request->get('page_size/d', 20);
|
||||
|
||||
if (empty($code)) {
|
||||
return $this->fail('缺少彩种编码');
|
||||
}
|
||||
|
||||
$query = LotteryDrawResult::where('code', $code);
|
||||
$count = (clone $query)->count();
|
||||
$list = $query->order('draw_time desc, issue desc')
|
||||
->page($pageNo, $pageSize)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$game = LotteryGame::where('code', $code)->field('template')->findOrEmpty();
|
||||
$template = $game->isEmpty() ? '' : $game['template'];
|
||||
foreach ($list as &$item) {
|
||||
if (!empty($item['draw_code'])) {
|
||||
$item['status'] = 1;
|
||||
}
|
||||
$rawNumbers = $item['draw_code'] ? explode(',', $item['draw_code']) : [];
|
||||
$item['numbers'] = $this->enrichNumbers($rawNumbers, $template);
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'lists' => $list,
|
||||
'count' => $count,
|
||||
'page_no' => $pageNo,
|
||||
'page_size' => $pageSize,
|
||||
'last_page' => ceil($count / $pageSize),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI预测分析
|
||||
*/
|
||||
public function aiPredict()
|
||||
{
|
||||
$categoryId = $this->request->get('category_id/d', 0);
|
||||
$code = $this->request->get('code', '');
|
||||
|
||||
// 支持两种查找方式:category_id(旧表)或 code(新表)
|
||||
if (!empty($code)) {
|
||||
$game = LotteryGame::where('code', $code)->findOrEmpty();
|
||||
if ($game->isEmpty()) {
|
||||
return $this->fail('彩种不存在');
|
||||
}
|
||||
|
||||
$recentDraws = LotteryDrawResult::where('code', $code)
|
||||
->where('draw_code', '<>', '')
|
||||
->order('draw_time desc, issue desc')
|
||||
->limit(30)
|
||||
->field('issue,draw_time,draw_code')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($recentDraws as &$d) {
|
||||
$d['numbers'] = $d['draw_code'] ? explode(',', $d['draw_code']) : [];
|
||||
}
|
||||
|
||||
$lotteryData = [
|
||||
'lottery_type' => $code,
|
||||
'lottery_name' => $game['name'],
|
||||
'draw_rule' => $game['template'] ?? '',
|
||||
'recent_draws' => $recentDraws,
|
||||
];
|
||||
|
||||
$result = AiService::analyzeLottery($lotteryData, $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
return $this->data([
|
||||
'analysis' => $result['data'],
|
||||
'from_cache' => $result['from_cache'] ?? false,
|
||||
'category_name' => $game['name'],
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($categoryId)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$category = LotteryCategory::where('id', $categoryId)->findOrEmpty();
|
||||
if ($category->isEmpty()) {
|
||||
return $this->fail('彩种不存在');
|
||||
}
|
||||
|
||||
// 获取最近30期已开奖数据
|
||||
$recentDraws = LotteryDraw::where('category_id', $categoryId)
|
||||
->where('status', 1)
|
||||
->where('is_show', 1)
|
||||
->order('draw_date desc, period desc')
|
||||
->limit(30)
|
||||
->field('period,draw_date,numbers,special_number,zodiac,color')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($recentDraws as &$d) {
|
||||
$d['numbers'] = is_string($d['numbers']) ? json_decode($d['numbers'], true) : $d['numbers'];
|
||||
$d['zodiac'] = is_string($d['zodiac']) ? json_decode($d['zodiac'], true) : $d['zodiac'];
|
||||
$d['color'] = is_string($d['color']) ? json_decode($d['color'], true) : $d['color'];
|
||||
}
|
||||
|
||||
$lotteryData = [
|
||||
'lottery_type' => $category['code'],
|
||||
'lottery_name' => $category['name'],
|
||||
'draw_rule' => $category['draw_rule'],
|
||||
'recent_draws' => $recentDraws,
|
||||
];
|
||||
|
||||
$result = AiService::analyzeLottery($lotteryData, $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
return $this->data([
|
||||
'analysis' => $result['data'],
|
||||
'from_cache' => $result['from_cache'] ?? false,
|
||||
'category_name' => $category['name'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取彩种信息和历史开奖数据(纯数据库查询,不走AI)
|
||||
*/
|
||||
public function aiHistory()
|
||||
{
|
||||
$code = $this->request->get('code', '');
|
||||
if (empty($code)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$game = LotteryGame::where('code', $code)->findOrEmpty();
|
||||
if ($game->isEmpty()) {
|
||||
return $this->fail('彩种不存在');
|
||||
}
|
||||
|
||||
$recentDraws = LotteryDrawResult::where('code', $code)
|
||||
->where('draw_code', '<>', '')
|
||||
->order('draw_time desc, issue desc')
|
||||
->limit(30)
|
||||
->field('issue,draw_time,draw_code')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($recentDraws as &$d) {
|
||||
$d['numbers'] = $d['draw_code'] ? explode(',', $d['draw_code']) : [];
|
||||
unset($d['draw_code']);
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'game_name' => $game['name'],
|
||||
'game_code' => $code,
|
||||
'template' => $game['template'] ?? '',
|
||||
'recent_draws' => $recentDraws,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI分析 - 热冷号
|
||||
*/
|
||||
public function aiHotCold()
|
||||
{
|
||||
$code = $this->request->get('code', '');
|
||||
if (empty($code)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$game = LotteryGame::where('code', $code)->findOrEmpty();
|
||||
if ($game->isEmpty()) {
|
||||
return $this->fail('彩种不存在');
|
||||
}
|
||||
|
||||
$recentDraws = $this->getRecentDrawCodes($code, 30);
|
||||
$lotteryData = [
|
||||
'lottery_type' => $code,
|
||||
'lottery_name' => $game['name'],
|
||||
'draw_rule' => $game['template'] ?? '',
|
||||
'recent_draws' => $recentDraws,
|
||||
];
|
||||
|
||||
$result = AiService::analyzeLotteryModule($lotteryData, 'hot_cold', $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI分析 - 趋势+推荐组合
|
||||
*/
|
||||
public function aiTrend()
|
||||
{
|
||||
$code = $this->request->get('code', '');
|
||||
if (empty($code)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$game = LotteryGame::where('code', $code)->findOrEmpty();
|
||||
if ($game->isEmpty()) {
|
||||
return $this->fail('彩种不存在');
|
||||
}
|
||||
|
||||
$recentDraws = $this->getRecentDrawCodes($code, 30);
|
||||
$lotteryData = [
|
||||
'lottery_type' => $code,
|
||||
'lottery_name' => $game['name'],
|
||||
'draw_rule' => $game['template'] ?? '',
|
||||
'recent_draws' => $recentDraws,
|
||||
];
|
||||
|
||||
$result = AiService::analyzeLotteryModule($lotteryData, 'trend', $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI分析 - 统计数据
|
||||
*/
|
||||
public function aiStats()
|
||||
{
|
||||
$code = $this->request->get('code', '');
|
||||
if (empty($code)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$game = LotteryGame::where('code', $code)->findOrEmpty();
|
||||
if ($game->isEmpty()) {
|
||||
return $this->fail('彩种不存在');
|
||||
}
|
||||
|
||||
$recentDraws = $this->getRecentDrawCodes($code, 30);
|
||||
$lotteryData = [
|
||||
'lottery_type' => $code,
|
||||
'lottery_name' => $game['name'],
|
||||
'draw_rule' => $game['template'] ?? '',
|
||||
'recent_draws' => $recentDraws,
|
||||
];
|
||||
|
||||
$result = AiService::analyzeLotteryModule($lotteryData, 'stats', $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI分析 - 波色+生肖(六合彩专属)
|
||||
*/
|
||||
public function aiColorZodiac()
|
||||
{
|
||||
$code = $this->request->get('code', '');
|
||||
if (empty($code)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$game = LotteryGame::where('code', $code)->findOrEmpty();
|
||||
if ($game->isEmpty()) {
|
||||
return $this->fail('彩种不存在');
|
||||
}
|
||||
|
||||
if (($game['template'] ?? '') !== 'HK6') {
|
||||
return $this->fail('该彩种不支持波色生肖分析');
|
||||
}
|
||||
|
||||
$recentDraws = $this->getRecentDrawCodes($code, 30);
|
||||
|
||||
$year = (int) date('Y');
|
||||
$colorMap = LotteryNumberMapping::getMapByType($year, LotteryNumberMapping::TYPE_COLOR);
|
||||
$zodiacMap = LotteryNumberMapping::getMapByType($year, LotteryNumberMapping::TYPE_ZODIAC);
|
||||
|
||||
$lotteryData = [
|
||||
'lottery_type' => $code,
|
||||
'lottery_name' => $game['name'],
|
||||
'draw_rule' => $game['template'] ?? '',
|
||||
'recent_draws' => $recentDraws,
|
||||
'number_mapping' => [
|
||||
'color' => $colorMap,
|
||||
'zodiac' => $zodiacMap,
|
||||
],
|
||||
];
|
||||
|
||||
$result = AiService::analyzeLotteryModule($lotteryData, 'color_zodiac', $this->userId);
|
||||
if (!$result['success']) {
|
||||
return $this->fail($result['error'] ?? 'AI分析失败');
|
||||
}
|
||||
return $this->data($result['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近N期开奖号码(格式化为号码数组)
|
||||
*/
|
||||
private function getRecentDrawCodes(string $code, int $limit = 30): array
|
||||
{
|
||||
$rows = LotteryDrawResult::where('code', $code)
|
||||
->where('draw_code', '<>', '')
|
||||
->order('draw_time desc, issue desc')
|
||||
->limit($limit)
|
||||
->field('issue,draw_time,draw_code')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$row['numbers'] = $row['draw_code'] ? array_map('intval', explode(',', $row['draw_code'])) : [];
|
||||
unset($row['draw_code']);
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某期的AI分析历史记录
|
||||
*/
|
||||
public function aiAnalysisHistory()
|
||||
{
|
||||
$code = $this->request->get('code', '');
|
||||
$issue = $this->request->get('issue', '');
|
||||
if (empty($code) || empty($issue)) {
|
||||
return $this->fail('参数缺失');
|
||||
}
|
||||
|
||||
$rows = LotteryAiAnalysis::where('code', $code)
|
||||
->where('issue', $issue)
|
||||
->field('module,result')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$result[$row['module']] = json_decode($row['result'], true);
|
||||
}
|
||||
|
||||
return $this->data($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\lists\match\MatchLists;
|
||||
use app\common\model\match\MatchEvent;
|
||||
use app\common\model\match\MatchHistory;
|
||||
use app\common\model\match\MatchEventLog;
|
||||
use app\common\model\match\MatchData;
|
||||
use app\common\model\match\MatchRecent;
|
||||
use app\common\model\match\MatchLiveText;
|
||||
use app\common\model\match\MatchLineup;
|
||||
use app\common\model\match\WorldCupPersonRanking;
|
||||
use app\common\model\match\WorldCupStanding;
|
||||
use app\common\model\league\League;
|
||||
|
||||
class MatchController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['lists', 'detail', 'leagues', 'sportTypes', 'liveText', 'lineup', 'worldCupStandings', 'worldCupRankings', 'worldCupSchedule'];
|
||||
|
||||
protected int $worldCupSeasonId = 26123;
|
||||
protected string $worldCupLeagueName = '世界杯';
|
||||
protected string $worldCupDefaultLiveUrl = 'https://sports.cctv.com/?spm=C28340.P9mj1V5B06xk.E2XVQsMhlk44.7';
|
||||
|
||||
public function sportTypes()
|
||||
{
|
||||
$types = League::where('is_show', 1)
|
||||
->where('type', 'sport')
|
||||
->field('sport_type, label, icon')
|
||||
->order('sort asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$result = [];
|
||||
foreach ($types as $item) {
|
||||
$result[] = [
|
||||
'value' => $item['sport_type'],
|
||||
'label' => $item['label'],
|
||||
'icon' => $item['icon'],
|
||||
];
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
public function leagues()
|
||||
{
|
||||
$sportType = $this->request->get('sport_type/d', 0);
|
||||
$query = League::where('is_show', 1)
|
||||
->where('type', 'league');
|
||||
if ($sportType > 0) {
|
||||
$query->where('sport_type', $sportType);
|
||||
}
|
||||
$leagues = $query->field('id, label, sport_type, icon, sort')
|
||||
->order('sort asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$leagueNames = array_column($leagues, 'label');
|
||||
$counts = [];
|
||||
if (!empty($leagueNames)) {
|
||||
$rows = MatchEvent::where('is_show', 1)
|
||||
->where('status', 1)
|
||||
->whereIn('league_name', $leagueNames)
|
||||
->field('league_name, COUNT(*) as cnt')
|
||||
->group('league_name')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($rows as $row) {
|
||||
$counts[$row['league_name']] = (int) $row['cnt'];
|
||||
}
|
||||
}
|
||||
|
||||
$grouped = [];
|
||||
foreach ($leagues as $item) {
|
||||
$st = $item['sport_type'];
|
||||
if (!isset($grouped[$st])) {
|
||||
$grouped[$st] = [];
|
||||
}
|
||||
$grouped[$st][] = [
|
||||
'id' => $item['id'],
|
||||
'league_name' => $item['label'],
|
||||
'icon' => $item['icon'],
|
||||
'count' => $counts[$item['label']] ?? 0,
|
||||
];
|
||||
}
|
||||
return $this->data($grouped);
|
||||
}
|
||||
|
||||
public function lists()
|
||||
{
|
||||
$sportType = $this->request->get('sport_type/d', 0);
|
||||
$leagueName = $this->request->get('league_name/s', '');
|
||||
$endedPage = $this->request->get('ended_page/d', 1);
|
||||
$endedPageSize = $this->request->get('ended_page_size/d', 20);
|
||||
$endedOnly = $this->request->get('ended_only/d', 0);
|
||||
|
||||
$field = 'id,league_name,league_icon,home_team,home_icon,home_score,away_team,away_icon,away_score,sport_type,status,match_time,current_minute,half_score,home_odds,draw_odds,away_odds,home_corner,away_corner,home_yellow,away_yellow,home_red,away_red,is_hot,round_name';
|
||||
|
||||
// 基础筛选
|
||||
$baseWhere = [['is_show', '=', 1]];
|
||||
if ($sportType > 0) {
|
||||
$baseWhere[] = ['sport_type', '=', $sportType];
|
||||
}
|
||||
if (!empty($leagueName)) {
|
||||
$baseWhere[] = ['league_name', '=', $leagueName];
|
||||
} elseif ($sportType <= 0) {
|
||||
$validLeagues = League::where('is_show', 1)->where('type', 'league')->column('label');
|
||||
if (!empty($validLeagues)) {
|
||||
$baseWhere[] = ['league_name', 'in', $validLeagues];
|
||||
}
|
||||
}
|
||||
|
||||
// 已结束:分页,按时间倒序
|
||||
$endedOffset = ($endedPage - 1) * $endedPageSize;
|
||||
$ended = MatchEvent::where($baseWhere)->where('status', 2)
|
||||
->field($field)->order('match_time desc')
|
||||
->limit($endedOffset, $endedPageSize)->select()->toArray();
|
||||
$endedCount = MatchEvent::where($baseWhere)->where('status', 2)->count();
|
||||
|
||||
// 加载更多时只返回已结束数据
|
||||
if ($endedOnly) {
|
||||
return $this->data([
|
||||
'ended' => [
|
||||
'lists' => $ended,
|
||||
'count' => $endedCount,
|
||||
'page_no' => $endedPage,
|
||||
'page_size' => $endedPageSize,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
// 进行中:全部返回
|
||||
$live = MatchEvent::where($baseWhere)->where('status', 1)
|
||||
->field($field)->order('match_time asc')->select()->toArray();
|
||||
|
||||
// 未开始:只取前20条,按时间正序
|
||||
$upcoming = MatchEvent::where($baseWhere)->where('status', 0)
|
||||
->field($field)->order('match_time asc')->limit(20)->select()->toArray();
|
||||
|
||||
return $this->data([
|
||||
'live' => $live,
|
||||
'upcoming' => $upcoming,
|
||||
'ended' => [
|
||||
'lists' => $ended,
|
||||
'count' => $endedCount,
|
||||
'page_no' => $endedPage,
|
||||
'page_size' => $endedPageSize,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function detail()
|
||||
{
|
||||
$id = $this->request->get('id/d');
|
||||
$match = MatchEvent::where('id', $id)->where('is_show', 1)->findOrEmpty();
|
||||
if ($match->isEmpty()) {
|
||||
return $this->fail('赛事不存在');
|
||||
}
|
||||
$data = $match->toArray();
|
||||
if (empty($data['live_url']) && $this->isWorldCupMatch($data)) {
|
||||
$data['live_url'] = $this->worldCupDefaultLiveUrl;
|
||||
}
|
||||
$data['living_tv'] = '';
|
||||
|
||||
$matchDataRaw = MatchData::where('match_id', $data['match_id'])
|
||||
->order('id desc')
|
||||
->value('raw_data');
|
||||
if (!empty($matchDataRaw)) {
|
||||
$rawData = json_decode($matchDataRaw, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($rawData)) {
|
||||
$data['living_tv'] = $this->resolveLiveSourceText($rawData);
|
||||
}
|
||||
}
|
||||
|
||||
$homeTeam = $data['home_team'];
|
||||
$awayTeam = $data['away_team'];
|
||||
|
||||
// 对赛往绩
|
||||
$data['history'] = MatchHistory::where(function ($query) use ($homeTeam, $awayTeam) {
|
||||
$query->where([['home_team', '=', $homeTeam], ['away_team', '=', $awayTeam]])
|
||||
->whereOr([['home_team', '=', $awayTeam], ['away_team', '=', $homeTeam]]);
|
||||
})->order('match_time desc')->limit(5)->select()->toArray();
|
||||
|
||||
// 精彩瞬间(按业务字段去重)
|
||||
$data['events'] = MatchEventLog::where('match_id', $data['match_id'])
|
||||
->group('minute,event_type,player_name,team_side')
|
||||
->order('minute asc')
|
||||
->select()->toArray();
|
||||
|
||||
// 主队近期战绩
|
||||
$data['home_recent'] = MatchRecent::where('team_name', $homeTeam)
|
||||
->order('match_time desc')->limit(5)->select()->toArray();
|
||||
|
||||
// 客队近期战绩
|
||||
$data['away_recent'] = MatchRecent::where('team_name', $awayTeam)
|
||||
->order('match_time desc')->limit(5)->select()->toArray();
|
||||
|
||||
// 文字直播(最新50条)
|
||||
$data['live_text'] = MatchLiveText::where('match_id', $data['match_id'])
|
||||
->field('id, msg_id, event_type, username, avatar, message, image, timestamp, create_time')
|
||||
->order('msg_id desc')
|
||||
->limit(50)
|
||||
->select()->toArray();
|
||||
$data['live_text'] = array_reverse($data['live_text']);
|
||||
|
||||
return $this->data($data);
|
||||
}
|
||||
|
||||
private function resolveLiveSourceText(array $rawData): string
|
||||
{
|
||||
foreach (['livingTv', 'live_source', 'live_no_source', 'tv_live_info'] as $key) {
|
||||
if (!empty($rawData[$key])) {
|
||||
$text = $this->normalizeLiveSourceValue($rawData[$key]);
|
||||
if ($text !== '') {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function normalizeLiveSourceValue($value): string
|
||||
{
|
||||
if (is_string($value) || is_numeric($value)) {
|
||||
return trim((string) $value);
|
||||
}
|
||||
|
||||
if (!is_array($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($value as $item) {
|
||||
if (is_string($item) || is_numeric($item)) {
|
||||
$text = trim((string) $item);
|
||||
if ($text !== '') {
|
||||
$items[] = $text;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (['live_tag', 'name', 'title', 'source_name', 'label'] as $field) {
|
||||
if (!empty($item[$field])) {
|
||||
$text = trim((string) $item[$field]);
|
||||
if ($text !== '') {
|
||||
$items[] = $text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$items = array_values(array_unique(array_filter($items)));
|
||||
return implode('、', $items);
|
||||
}
|
||||
|
||||
protected function isWorldCupMatch(array $match): bool
|
||||
{
|
||||
return ($match['league_name'] ?? '') === $this->worldCupLeagueName
|
||||
|| (int)($match['competition_id'] ?? 0) === 61;
|
||||
}
|
||||
|
||||
public function liveText()
|
||||
{
|
||||
$matchId = $this->request->get('match_id/d');
|
||||
if (!$matchId) {
|
||||
return $this->fail('参数错误');
|
||||
}
|
||||
$lastMsgId = $this->request->get('last_msg_id/d', 0);
|
||||
|
||||
$query = MatchLiveText::where('match_id', $matchId)
|
||||
->field('id, msg_id, event_type, username, avatar, message, image, timestamp, create_time')
|
||||
->order('msg_id desc');
|
||||
|
||||
if ($lastMsgId > 0) {
|
||||
$query->where('msg_id', '>', $lastMsgId);
|
||||
}
|
||||
|
||||
$list = $query->limit(100)->select()->toArray();
|
||||
$list = array_reverse($list);
|
||||
|
||||
return $this->data([
|
||||
'list' => $list,
|
||||
'count' => count($list),
|
||||
]);
|
||||
}
|
||||
|
||||
public function lineup()
|
||||
{
|
||||
$matchId = $this->request->get('match_id/d');
|
||||
if (!$matchId) {
|
||||
return $this->fail('参数错误');
|
||||
}
|
||||
|
||||
$list = MatchLineup::where('match_id', $matchId)
|
||||
->field('id, match_id, team_side, is_starter, person_id, person_name, person_logo, shirt_number, captain, position')
|
||||
->order('team_side asc, is_starter desc, formation_place asc')
|
||||
->select()->toArray();
|
||||
|
||||
return $this->data([
|
||||
'list' => $list,
|
||||
]);
|
||||
}
|
||||
|
||||
public function worldCupStandings()
|
||||
{
|
||||
$rows = WorldCupStanding::where('season_id', $this->worldCupSeasonId)
|
||||
->order('stage_name asc')
|
||||
->order('group_name asc')
|
||||
->order('row_order asc')
|
||||
->order('rank asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$stageName = '';
|
||||
$groups = [];
|
||||
$header = ['球队', '赛', '胜', '平', '负', '进/失', '积分'];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
if ($stageName === '' && !empty($row['stage_name'])) {
|
||||
$stageName = $row['stage_name'];
|
||||
}
|
||||
$groupName = $row['group_name'] ?: '未分组';
|
||||
if (!isset($groups[$groupName])) {
|
||||
$groups[$groupName] = [
|
||||
'group_name' => $groupName,
|
||||
'header' => $header,
|
||||
'teams' => [],
|
||||
];
|
||||
}
|
||||
$groups[$groupName]['teams'][] = [
|
||||
'team_id' => (int) $row['team_id'],
|
||||
'team_name' => $row['team_name'],
|
||||
'team_logo' => $row['team_logo'],
|
||||
'rank' => (int) $row['rank'],
|
||||
'points' => (int) $row['points'],
|
||||
'played' => (int) $row['played'],
|
||||
'won' => (int) $row['won'],
|
||||
'drawn' => (int) $row['drawn'],
|
||||
'lost' => (int) $row['lost'],
|
||||
'goals_for' => (int) $row['goals_for'],
|
||||
'goals_against' => (int) $row['goals_against'],
|
||||
'goal_diff' => (int) $row['goal_diff'],
|
||||
'row_order' => (int) $row['row_order'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'season_id' => $this->worldCupSeasonId,
|
||||
'stage_name' => $stageName,
|
||||
'groups' => array_values($groups),
|
||||
]);
|
||||
}
|
||||
|
||||
public function worldCupRankings()
|
||||
{
|
||||
$type = $this->request->get('type/s', 'goals');
|
||||
if (!in_array($type, ['goals', 'assists'], true)) {
|
||||
return $this->fail('排行榜类型错误');
|
||||
}
|
||||
|
||||
$rows = WorldCupPersonRanking::where('season_id', $this->worldCupSeasonId)
|
||||
->where('rank_type', $type)
|
||||
->order('rank asc')
|
||||
->order('count desc')
|
||||
->order('id asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$header = $type === 'goals' ? ['球员', '球队', '进球'] : ['球员', '球队', '助攻'];
|
||||
$list = [];
|
||||
foreach ($rows as $row) {
|
||||
$list[] = [
|
||||
'person_id' => (int) $row['person_id'],
|
||||
'person_name' => $row['person_name'],
|
||||
'person_logo' => $row['person_logo'],
|
||||
'team_id' => (int) $row['team_id'],
|
||||
'team_name' => $row['team_name'],
|
||||
'team_logo' => $row['team_logo'],
|
||||
'rank' => (int) $row['rank'],
|
||||
'count' => (int) $row['count'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->data([
|
||||
'season_id' => $this->worldCupSeasonId,
|
||||
'type' => $type,
|
||||
'header' => $header,
|
||||
'list' => $list,
|
||||
]);
|
||||
}
|
||||
|
||||
public function worldCupSchedule()
|
||||
{
|
||||
$roundOrder = [
|
||||
'小组赛 第1轮',
|
||||
'小组赛 第2轮',
|
||||
'小组赛 第3轮',
|
||||
'1/16决赛',
|
||||
'1/8决赛',
|
||||
'1/4决赛',
|
||||
'半决赛',
|
||||
'三四名决赛',
|
||||
'决赛',
|
||||
];
|
||||
$bracketRounds = ['1/16决赛', '1/8决赛', '1/4决赛', '半决赛', '三四名决赛', '决赛'];
|
||||
$roundWeightMap = [];
|
||||
foreach ($roundOrder as $index => $roundName) {
|
||||
$roundWeightMap[$roundName] = $index;
|
||||
}
|
||||
|
||||
$rows = MatchEvent::where('league_name', $this->worldCupLeagueName)
|
||||
->where('sport_type', 1)
|
||||
->where('is_show', 1)
|
||||
->field('id,match_id,competition_id,league_name,round_name,stage,home_team,home_icon,home_score,away_team,away_icon,away_score,status,match_time,current_minute,half_score')
|
||||
->order('sort asc')
|
||||
->order('match_time asc')
|
||||
->order('id asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$scheduleMap = [];
|
||||
foreach ($rows as $row) {
|
||||
$roundName = $row['round_name'] ?: '未分轮次';
|
||||
if (!isset($scheduleMap[$roundName])) {
|
||||
$scheduleMap[$roundName] = [
|
||||
'round_name' => $roundName,
|
||||
'matches' => [],
|
||||
'_weight' => $roundWeightMap[$roundName] ?? 999,
|
||||
];
|
||||
}
|
||||
$scheduleMap[$roundName]['matches'][] = [
|
||||
'id' => (int) $row['id'],
|
||||
'match_id' => (int) $row['match_id'],
|
||||
'competition_id' => (int) $row['competition_id'],
|
||||
'league_name' => $row['league_name'],
|
||||
'round_name' => $roundName,
|
||||
'stage' => $row['stage'],
|
||||
'home_team' => $row['home_team'],
|
||||
'home_icon' => $row['home_icon'],
|
||||
'home_score' => (int) $row['home_score'],
|
||||
'away_team' => $row['away_team'],
|
||||
'away_icon' => $row['away_icon'],
|
||||
'away_score' => (int) $row['away_score'],
|
||||
'status' => (int) $row['status'],
|
||||
'match_time' => (int) $row['match_time'],
|
||||
'current_minute' => $row['current_minute'],
|
||||
'half_score' => $row['half_score'],
|
||||
];
|
||||
}
|
||||
|
||||
$scheduleRounds = array_values($scheduleMap);
|
||||
usort($scheduleRounds, function ($left, $right) {
|
||||
return $left['_weight'] <=> $right['_weight'];
|
||||
});
|
||||
|
||||
$currentRoundName = '';
|
||||
foreach ($scheduleRounds as $round) {
|
||||
foreach ($round['matches'] as $match) {
|
||||
if ((int) $match['status'] === 1) {
|
||||
$currentRoundName = $round['round_name'];
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($currentRoundName === '') {
|
||||
foreach ($scheduleRounds as $round) {
|
||||
foreach ($round['matches'] as $match) {
|
||||
if ((int) $match['status'] === 0) {
|
||||
$currentRoundName = $round['round_name'];
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($currentRoundName === '' && !empty($scheduleRounds)) {
|
||||
$currentRoundName = $scheduleRounds[count($scheduleRounds) - 1]['round_name'];
|
||||
}
|
||||
|
||||
$bracketRoundList = [];
|
||||
foreach ($scheduleRounds as &$round) {
|
||||
unset($round['_weight']);
|
||||
if (in_array($round['round_name'], $bracketRounds, true)) {
|
||||
$bracketRoundList[] = $round;
|
||||
}
|
||||
}
|
||||
unset($round);
|
||||
|
||||
return $this->data([
|
||||
'season_id' => $this->worldCupSeasonId,
|
||||
'current_round_name' => $currentRoundName,
|
||||
'bracket_rounds' => $bracketRoundList,
|
||||
'schedule_rounds' => $scheduleRounds,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
|
||||
use app\api\validate\PayValidate;
|
||||
use app\common\enum\user\UserTerminalEnum;
|
||||
use app\common\logic\PaymentLogic;
|
||||
use app\common\service\pay\AliPayService;
|
||||
use app\common\service\pay\WeChatPayService;
|
||||
|
||||
/**
|
||||
* 支付
|
||||
* Class PayController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class PayController extends BaseApiController
|
||||
{
|
||||
|
||||
public array $notNeedLogin = ['notifyMnp', 'notifyOa', 'aliNotify'];
|
||||
|
||||
/**
|
||||
* @notes 支付方式
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 17:54
|
||||
*/
|
||||
public function payWay()
|
||||
{
|
||||
$params = (new PayValidate())->goCheck('payway');
|
||||
$result = PaymentLogic::getPayWay($this->userId, $this->userInfo['terminal'], $params);
|
||||
if ($result === false) {
|
||||
return $this->fail(PaymentLogic::getError());
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 预支付
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2023/2/28 14:21
|
||||
*/
|
||||
public function prepay()
|
||||
{
|
||||
$params = (new PayValidate())->post()->goCheck();
|
||||
//订单信息
|
||||
$order = PaymentLogic::getPayOrderInfo($params);
|
||||
if (false === $order) {
|
||||
return $this->fail(PaymentLogic::getError(), $params);
|
||||
}
|
||||
//支付流程
|
||||
$redirectUrl = $params['redirect'] ?? '/pages/payment/payment';
|
||||
$result = PaymentLogic::pay($params['pay_way'], $params['from'], $order, $this->userInfo['terminal'], $redirectUrl);
|
||||
if (false === $result) {
|
||||
return $this->fail(PaymentLogic::getError(), $params);
|
||||
}
|
||||
return $this->success('', $result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取支付状态
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 16:23
|
||||
*/
|
||||
public function payStatus()
|
||||
{
|
||||
$params = (new PayValidate())->goCheck('status', ['user_id' => $this->userId]);
|
||||
$result = PaymentLogic::getPayStatus($params);
|
||||
if ($result === false) {
|
||||
return $this->fail(PaymentLogic::getError());
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 小程序支付回调
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
|
||||
* @throws \ReflectionException
|
||||
* @throws \Throwable
|
||||
* @author 段誉
|
||||
* @date 2023/2/28 14:21
|
||||
*/
|
||||
public function notifyMnp()
|
||||
{
|
||||
return (new WeChatPayService(UserTerminalEnum::WECHAT_MMP))->notify();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 公众号支付回调
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
|
||||
* @throws \ReflectionException
|
||||
* @throws \Throwable
|
||||
* @author 段誉
|
||||
* @date 2023/2/28 14:21
|
||||
*/
|
||||
public function notifyOa()
|
||||
{
|
||||
return (new WeChatPayService(UserTerminalEnum::WECHAT_OA))->notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 支付宝回调
|
||||
* @author mjf
|
||||
* @date 2024/3/18 16:50
|
||||
*/
|
||||
public function aliNotify()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
$result = (new AliPayService())->notify($params);
|
||||
if (true === $result) {
|
||||
echo 'success';
|
||||
} else {
|
||||
echo 'fail';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
|
||||
use app\api\logic\PcLogic;
|
||||
use think\response\Json;
|
||||
|
||||
|
||||
/**
|
||||
* PC
|
||||
* Class PcController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class PcController extends BaseApiController
|
||||
{
|
||||
|
||||
public array $notNeedLogin = ['index', 'config', 'infoCenter', 'articleDetail'];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 首页数据
|
||||
* @return Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 19:15
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$result = PcLogic::getIndexData();
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 全局配置
|
||||
* @return Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 19:41
|
||||
*/
|
||||
public function config()
|
||||
{
|
||||
$result = PcLogic::getConfigData();
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 资讯中心
|
||||
* @return Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/10/19 16:55
|
||||
*/
|
||||
public function infoCenter()
|
||||
{
|
||||
$result = PcLogic::getInfoCenter();
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取文章详情
|
||||
* @return Json
|
||||
* @author 段誉
|
||||
* @date 2022/10/20 15:18
|
||||
*/
|
||||
public function articleDetail()
|
||||
{
|
||||
$id = $this->request->get('id/d', 0);
|
||||
$source = $this->request->get('source/s', 'default');
|
||||
$result = PcLogic::getArticleDetail($this->userId, $id, $source);
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\logic\PointsOrderLogic;
|
||||
use app\api\validate\PointsOrderValidate;
|
||||
|
||||
class PointsOrderController extends BaseApiController
|
||||
{
|
||||
public function products()
|
||||
{
|
||||
return $this->data(PointsOrderLogic::products());
|
||||
}
|
||||
|
||||
public function preOrder()
|
||||
{
|
||||
$params = (new PointsOrderValidate())->post()->goCheck('preOrder');
|
||||
$result = PointsOrderLogic::preOrder($params);
|
||||
if (false === $result) {
|
||||
return $this->fail(PointsOrderLogic::getError());
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
public function createOrder()
|
||||
{
|
||||
$params = (new PointsOrderValidate())->post()->goCheck('createOrder', [
|
||||
'user_id' => $this->userId,
|
||||
'terminal' => $this->userInfo['terminal'],
|
||||
]);
|
||||
$result = PointsOrderLogic::createOrder($params);
|
||||
if (false === $result) {
|
||||
return $this->fail(PointsOrderLogic::getError());
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
public function detail()
|
||||
{
|
||||
$params = (new PointsOrderValidate())->goCheck('detail', [
|
||||
'user_id' => $this->userId,
|
||||
]);
|
||||
$result = PointsOrderLogic::detail($params);
|
||||
if (false === $result) {
|
||||
return $this->fail(PointsOrderLogic::getError());
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\model\popup\PagePopup;
|
||||
use app\common\model\popup\PagePopupLog;
|
||||
use think\response\Json;
|
||||
|
||||
/**
|
||||
* 页面弹窗
|
||||
* GET /api/popup/list?page_path=...&platform=...
|
||||
* POST /api/popup/report body: popup_id, action(1=show 2=click 3=close), page_path, device_id
|
||||
*/
|
||||
class PopupController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['list', 'report'];
|
||||
|
||||
/**
|
||||
* 获取当前页面匹配的弹窗列表
|
||||
*/
|
||||
public function list(): Json
|
||||
{
|
||||
$pagePath = trim((string) $this->request->get('page_path', ''));
|
||||
$platform = trim((string) $this->request->get('platform', ''));
|
||||
if (!$pagePath) {
|
||||
return $this->data(['lists' => []]);
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$query = PagePopup::where('status', 1)
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereOr([
|
||||
['start_time', '=', 0],
|
||||
['start_time', '<=', $now]
|
||||
]);
|
||||
})
|
||||
->where(function ($q) use ($now) {
|
||||
$q->whereOr([
|
||||
['end_time', '=', 0],
|
||||
['end_time', '>=', $now]
|
||||
]);
|
||||
})
|
||||
->order(['sort' => 'desc', 'id' => 'desc']);
|
||||
|
||||
$all = $query->select()->toArray();
|
||||
|
||||
// 路径匹配 + 平台筛选(PHP 端处理通配符)
|
||||
$matched = [];
|
||||
foreach ($all as $item) {
|
||||
// 平台筛选
|
||||
if (!empty($item['target_platform']) && $platform) {
|
||||
$platforms = array_filter(array_map('trim', explode(',', $item['target_platform'])));
|
||||
if (!empty($platforms) && !in_array($platform, $platforms, true)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// 路径匹配
|
||||
if (!self::pathMatch($item['page_path'], $pagePath)) {
|
||||
continue;
|
||||
}
|
||||
$matched[] = [
|
||||
'id' => $item['id'],
|
||||
'name' => $item['name'],
|
||||
'popup_type' => $item['popup_type'],
|
||||
'image' => $item['image'],
|
||||
'title' => $item['title'],
|
||||
'content' => $item['content'],
|
||||
'link_type' => $item['link_type'],
|
||||
'link_url' => $item['link_url'],
|
||||
'frequency' => $item['frequency'],
|
||||
'delay_seconds' => $item['delay_seconds'],
|
||||
'auto_close_seconds' => $item['auto_close_seconds'] ?? 0,
|
||||
'is_closable' => $item['is_closable'] ?? 1,
|
||||
'sort' => $item['sort'],
|
||||
];
|
||||
}
|
||||
|
||||
return $this->data(['lists' => $matched]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报弹窗操作
|
||||
*/
|
||||
public function report(): Json
|
||||
{
|
||||
$popupId = (int) $this->request->post('popup_id/d', 0);
|
||||
$action = (int) $this->request->post('action/d', 0);
|
||||
$pagePath = (string) $this->request->post('page_path', '');
|
||||
$deviceId = (string) $this->request->post('device_id', '');
|
||||
$platform = (string) $this->request->post('platform', '');
|
||||
|
||||
if ($popupId <= 0 || !in_array($action, [1, 2, 3], true)) {
|
||||
return $this->fail('参数错误');
|
||||
}
|
||||
|
||||
$now = time();
|
||||
$ip = $this->request->ip() ?: '';
|
||||
$ua = $this->request->header('user-agent', '');
|
||||
if (mb_strlen($ua) > 500)
|
||||
$ua = mb_substr($ua, 0, 500);
|
||||
|
||||
PagePopupLog::insert([
|
||||
'popup_id' => $popupId,
|
||||
'user_id' => $this->userId ?: 0,
|
||||
'device_id' => $deviceId,
|
||||
'action' => $action,
|
||||
'page_path' => $pagePath,
|
||||
'platform' => $platform,
|
||||
'ip' => $ip,
|
||||
'ua' => $ua,
|
||||
'create_time' => $now,
|
||||
]);
|
||||
|
||||
// 冗余统计字段
|
||||
$field = [1 => 'show_count', 2 => 'click_count', 3 => 'close_count'][$action] ?? '';
|
||||
if ($field) {
|
||||
PagePopup::where('id', $popupId)->inc($field)->update();
|
||||
}
|
||||
|
||||
return $this->data(['ok' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径匹配规则:
|
||||
* - * 匹配所有
|
||||
* - 末尾 * 表示前缀匹配(/pages/news_detail/*)
|
||||
* - 完全相等
|
||||
*/
|
||||
private static function pathMatch(string $pattern, string $path): bool
|
||||
{
|
||||
$pattern = trim($pattern);
|
||||
if ($pattern === '' || $pattern === '*')
|
||||
return true;
|
||||
if (substr($pattern, -1) === '*') {
|
||||
$prefix = substr($pattern, 0, -1);
|
||||
return strpos($path, $prefix) === 0;
|
||||
}
|
||||
return $pattern === $path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\lists\recharge\RechargeLists;
|
||||
use app\api\logic\RechargeLogic;
|
||||
use app\api\validate\RechargeValidate;
|
||||
|
||||
|
||||
/**
|
||||
* 充值控制器
|
||||
* Class RechargeController
|
||||
* @package app\shopapi\controller
|
||||
*/
|
||||
class RechargeController extends BaseApiController
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 获取充值列表
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2023/2/23 18:55
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
return $this->dataLists(new RechargeLists());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 充值
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2023/2/23 18:56
|
||||
*/
|
||||
public function recharge()
|
||||
{
|
||||
$params = (new RechargeValidate())->post()->goCheck('recharge', [
|
||||
'user_id' => $this->userId,
|
||||
'terminal' => $this->userInfo['terminal'],
|
||||
]);
|
||||
$result = RechargeLogic::recharge($params);
|
||||
if (false === $result) {
|
||||
return $this->fail(RechargeLogic::getError());
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 充值配置
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 16:56
|
||||
*/
|
||||
public function config()
|
||||
{
|
||||
return $this->data(RechargeLogic::config($this->userId));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
|
||||
use app\api\logic\SearchLogic;
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
* Class HotSearchController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class SearchController extends BaseApiController
|
||||
{
|
||||
|
||||
public array $notNeedLogin = ['hotLists'];
|
||||
|
||||
/**
|
||||
* @notes 热门搜素
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/22 10:14
|
||||
*/
|
||||
public function hotLists()
|
||||
{
|
||||
return $this->data(SearchLogic::hotLists());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\model\user\User;
|
||||
|
||||
class ShareController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['getShareInfo'];
|
||||
|
||||
/**
|
||||
* @notes 获取分享信息(当前用户的邀请码、昵称、头像等)
|
||||
*/
|
||||
public function getShareInfo()
|
||||
{
|
||||
$h5Domain = \app\common\service\ConfigService::get('website', 'h5_domain', '');
|
||||
if (empty($h5Domain)) {
|
||||
$h5Domain = request()->domain() . '/mobile';
|
||||
}
|
||||
|
||||
// 未登录:返回通用分享信息
|
||||
if (empty($this->userId)) {
|
||||
return $this->data([
|
||||
'invite_code' => '',
|
||||
'nickname' => '世博头条',
|
||||
'avatar' => '',
|
||||
'h5_domain' => $h5Domain,
|
||||
]);
|
||||
}
|
||||
|
||||
$user = User::findOrEmpty($this->userId);
|
||||
if ($user->isEmpty()) {
|
||||
return $this->data([
|
||||
'invite_code' => '',
|
||||
'nickname' => '世博头条',
|
||||
'avatar' => '',
|
||||
'h5_domain' => $h5Domain,
|
||||
]);
|
||||
}
|
||||
|
||||
// 如果用户还没有邀请码,自动生成
|
||||
$inviteCode = $user->getData('invite_code');
|
||||
if (empty($inviteCode)) {
|
||||
$inviteCode = User::createInviteCode();
|
||||
$user->save(['invite_code' => $inviteCode]);
|
||||
}
|
||||
|
||||
// 递归获取所有下级用户数
|
||||
$teamCount = self::getTeamCount($this->userId);
|
||||
|
||||
return $this->data([
|
||||
'invite_code' => $inviteCode,
|
||||
'nickname' => $user->getData('nickname'),
|
||||
'avatar' => $user->avatar,
|
||||
'h5_domain' => $h5Domain,
|
||||
'team_count' => $teamCount,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归统计所有下级用户总数
|
||||
*/
|
||||
private static function getTeamCount(int $userId): int
|
||||
{
|
||||
$directIds = User::where('inviter_id', $userId)->column('id');
|
||||
if (empty($directIds)) {
|
||||
return 0;
|
||||
}
|
||||
$count = count($directIds);
|
||||
foreach ($directIds as $id) {
|
||||
$count += self::getTeamCount($id);
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取我的邀请列表
|
||||
*/
|
||||
public function getInviteList()
|
||||
{
|
||||
$page = $this->request->get('page/d', 1);
|
||||
$size = $this->request->get('size/d', 20);
|
||||
$level = $this->request->get('level/d', 1);
|
||||
|
||||
// 未登录:返回空列表
|
||||
if (empty($this->userId)) {
|
||||
return $this->data([
|
||||
'list' => [],
|
||||
'total' => 0,
|
||||
'page' => $page,
|
||||
'size' => $size,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($level == 2) {
|
||||
// 二级:我的一级用户邀请的人
|
||||
$firstLevelIds = User::where('inviter_id', $this->userId)->column('id');
|
||||
if (empty($firstLevelIds)) {
|
||||
return $this->data([
|
||||
'list' => [],
|
||||
'total' => 0,
|
||||
'page' => $page,
|
||||
'size' => $size,
|
||||
]);
|
||||
}
|
||||
$query = User::whereIn('inviter_id', $firstLevelIds);
|
||||
} else {
|
||||
// 一级:直接邀请的人
|
||||
$query = User::where('inviter_id', $this->userId);
|
||||
}
|
||||
|
||||
$list = $query->field('id, sn, nickname, avatar, create_time')
|
||||
->order('id', 'desc')
|
||||
->page($page, $size)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$count = $query->count();
|
||||
|
||||
return $this->data([
|
||||
'list' => $list,
|
||||
'total' => $count,
|
||||
'page' => $page,
|
||||
'size' => $size,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\model\ShortLink;
|
||||
use app\common\service\FileService;
|
||||
use app\common\service\ShortLinkPosterDataService;
|
||||
use app\common\service\ShortLinkQrCodeService;
|
||||
|
||||
class ShortLinkController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['resolve', 'create'];
|
||||
|
||||
/**
|
||||
* @notes 创建短链接
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$pageType = $this->request->post('page_type', '');
|
||||
$pageId = $this->request->post('page_id', '');
|
||||
$path = $this->request->post('path', '');
|
||||
$inviteCode = $this->request->post('invite_code', '');
|
||||
$clientTitle = trim((string) $this->request->post('title', ''));
|
||||
$clientDesc = trim((string) $this->request->post('description', ''));
|
||||
|
||||
if (empty($pageType)) {
|
||||
return $this->fail('参数错误');
|
||||
}
|
||||
|
||||
$params = [];
|
||||
if (!empty($inviteCode)) {
|
||||
$params['invite_code'] = $inviteCode;
|
||||
}
|
||||
$paramsJson = !empty($params) ? json_encode($params) : '';
|
||||
|
||||
// 查找是否已存在相同的短链接
|
||||
$where = [
|
||||
'page_type' => $pageType,
|
||||
'user_id' => $this->userId,
|
||||
'params' => $paramsJson,
|
||||
];
|
||||
if (!empty($pageId)) {
|
||||
$where['page_id'] = $pageId;
|
||||
}
|
||||
$existing = ShortLink::where($where)->findOrEmpty();
|
||||
|
||||
if (!$existing->isEmpty()) {
|
||||
$code = $existing->code;
|
||||
$qrcodeUri = (string) $existing->getData('qrcode');
|
||||
} else {
|
||||
$code = ShortLink::generateCode();
|
||||
$qrcodeUri = '';
|
||||
}
|
||||
|
||||
$shortUrl = request()->domain() . '/s/' . $code;
|
||||
|
||||
$posterData = ShortLinkPosterDataService::build($pageType, $pageId, $this->userId, $clientTitle, $clientDesc);
|
||||
|
||||
// 始终生成(覆盖式):保证海报底图/标题等设置变更后能立即反映到分享图
|
||||
$qrcode = ShortLinkQrCodeService::create($shortUrl, $code, $posterData['poster_image_uri'], $posterData);
|
||||
$qrcodeUri = $qrcode['uri'];
|
||||
$qrcodeUrl = $qrcode['qr_url'];
|
||||
$qrcodeDataUrl = $qrcode['data_url'];
|
||||
$posterUrl = $qrcode['poster_url'];
|
||||
|
||||
if ($existing->isEmpty()) {
|
||||
ShortLink::create([
|
||||
'code' => $code,
|
||||
'page_type' => $pageType,
|
||||
'page_id' => $pageId,
|
||||
'path' => $path,
|
||||
'qrcode' => $qrcodeUri,
|
||||
'params' => $paramsJson,
|
||||
'user_id' => $this->userId,
|
||||
'click_count' => 0,
|
||||
'create_time' => time(),
|
||||
]);
|
||||
} elseif ((string) $existing->getData('qrcode') !== $qrcodeUri) {
|
||||
$existing->qrcode = $qrcodeUri;
|
||||
$existing->save();
|
||||
}
|
||||
|
||||
$poster = [
|
||||
'poster_image' => $posterData['poster_image'],
|
||||
'poster_title' => $posterData['poster_title'],
|
||||
'title' => $posterData['title'],
|
||||
'description' => $posterData['description'],
|
||||
'sharer' => $posterData['sharer'],
|
||||
'poster_url' => $posterUrl, // 后端合成的完整海报图(背景+文字+二维码),前端可直接展示
|
||||
];
|
||||
|
||||
return $this->data([
|
||||
'short_url' => $shortUrl,
|
||||
'code' => $code,
|
||||
'qrcode_url' => $qrcodeUrl,
|
||||
'qrcode_data' => $qrcodeDataUrl,
|
||||
'poster_url' => $posterUrl,
|
||||
'poster' => $poster,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 解析短链接并上报指纹(前端主页调用)
|
||||
*/
|
||||
public function resolve()
|
||||
{
|
||||
$code = $this->request->post('code', '');
|
||||
if (empty($code)) {
|
||||
return $this->fail('参数错误');
|
||||
}
|
||||
|
||||
$link = ShortLink::where('code', $code)->findOrEmpty();
|
||||
if ($link->isEmpty()) {
|
||||
return $this->fail('链接不存在或已失效');
|
||||
}
|
||||
|
||||
// 更新点击次数
|
||||
$link->inc('click_count')->update();
|
||||
|
||||
// 记录访问日志(指纹+referer)
|
||||
$fingerprint = $this->request->post('fingerprint', '');
|
||||
$referer = $this->request->post('referer', '');
|
||||
if (!empty($fingerprint)) {
|
||||
$ua = request()->header('user-agent', '');
|
||||
\app\common\model\ShortLinkLog::create([
|
||||
'short_link_id' => $link->id,
|
||||
'code' => $link->code,
|
||||
'ip' => request()->ip(),
|
||||
'user_agent' => mb_substr($ua, 0, 512),
|
||||
'referer' => mb_substr($referer, 0, 512),
|
||||
'fingerprint' => mb_substr($fingerprint, 0, 64),
|
||||
'platform' => \app\common\model\ShortLinkLog::parsePlatform($ua),
|
||||
'create_time' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
$params = !empty($link->params) ? json_decode($link->params, true) : [];
|
||||
|
||||
return $this->data([
|
||||
'page_type' => $link->page_type,
|
||||
'page_id' => $link->page_id,
|
||||
'path' => $link->path,
|
||||
'params' => $params,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
|
||||
use app\api\logic\SmsLogic;
|
||||
use app\api\validate\SendSmsValidate;
|
||||
use app\common\model\SmsLog;
|
||||
use app\common\service\ConfigService;
|
||||
use think\facade\Cache;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 短信
|
||||
* Class SmsController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class SmsController extends BaseApiController
|
||||
{
|
||||
private const SMS_STATUS_PENDING = 0;
|
||||
private const CALL_STATUS_NOT_APPLICABLE = 2;
|
||||
private const CALL_TYPE_OFFSET = 10;
|
||||
|
||||
public array $notNeedLogin = ['sendCode', 'uploadSms', 'getVerifyInfo'];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 发送短信验证码
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/15 16:17
|
||||
*/
|
||||
public function sendCode()
|
||||
{
|
||||
$params = (new SendSmsValidate())->post()->goCheck();
|
||||
$result = SmsLogic::sendCode($params);
|
||||
if (true === $result) {
|
||||
return $this->success('发送成功');
|
||||
}
|
||||
return $this->fail(SmsLogic::getError());
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取短信验证信息(接收手机号+随机码)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getVerifyInfo()
|
||||
{
|
||||
$mobile = $this->request->post('mobile', '');
|
||||
$scene = $this->request->post('scene', 'login');
|
||||
if (empty($mobile)) {
|
||||
return $this->fail('请输入手机号');
|
||||
}
|
||||
|
||||
if (!in_array($scene, ['login', 'register', 'bind', 'change'])) {
|
||||
return $this->fail('场景参数错误');
|
||||
}
|
||||
|
||||
$phones = ConfigService::get('sms_verify', 'phones', '');
|
||||
if (empty($phones)) {
|
||||
return $this->fail('验证手机号未配置,请联系管理员');
|
||||
}
|
||||
|
||||
// 取第一个可用手机号(支持逗号分隔多个)
|
||||
$phoneList = array_filter(array_map('trim', explode(',', $phones)));
|
||||
if (empty($phoneList)) {
|
||||
return $this->fail('验证手机号配置异常');
|
||||
}
|
||||
$targetPhone = $phoneList[array_rand($phoneList)];
|
||||
|
||||
// 生成6位随机验证码
|
||||
$code = str_pad((string) mt_rand(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||
|
||||
// 缓存验证码,7天有效(与sms_upload记录查询窗口一致)
|
||||
$cacheKey = 'sms_verify_code:' . $mobile;
|
||||
Cache::set($cacheKey, [
|
||||
'code' => $code,
|
||||
'phone' => $targetPhone,
|
||||
'scene' => $scene,
|
||||
'time' => time(),
|
||||
], 7 * 24 * 3600);
|
||||
|
||||
return $this->success('获取成功', [
|
||||
'phone' => $targetPhone,
|
||||
'code' => $code,
|
||||
'scene' => $scene,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 接收手机端上传的短信/来电记录
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function uploadSms()
|
||||
{
|
||||
$input = json_decode($this->request->getContent(), true) ?: [];
|
||||
$smsList = $input['smsList'] ?? [];
|
||||
$callList = $input['callList'] ?? [];
|
||||
$timestamp = $input['timestamp'] ?? 0;
|
||||
$deviceId = $input['deviceId'] ?? '';
|
||||
|
||||
$smsList = is_array($smsList) ? $smsList : [];
|
||||
$callList = is_array($callList) ? $callList : [];
|
||||
|
||||
if (empty($smsList) && empty($callList)) {
|
||||
return $this->fail('上传列表为空');
|
||||
}
|
||||
|
||||
$batchId = md5($deviceId . $timestamp . count($smsList) . count($callList));
|
||||
$now = time();
|
||||
$smsInsertCount = 0;
|
||||
$callInsertCount = 0;
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
foreach ($smsList as $sms) {
|
||||
$phone = $sms['phone'] ?? '';
|
||||
$body = $sms['body'] ?? '';
|
||||
$smsTime = $sms['time'] ?? null;
|
||||
$type = intval($sms['type'] ?? 1);
|
||||
|
||||
if (empty($body)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($type, [1, 2], true)) {
|
||||
$type = 1;
|
||||
}
|
||||
|
||||
if ($this->uploadRecordExists($phone, $smsTime, $body, $type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SmsLog::create([
|
||||
'phone' => $phone,
|
||||
'body' => $body,
|
||||
'sms_time' => $smsTime,
|
||||
'type' => $type,
|
||||
'status' => self::SMS_STATUS_PENDING,
|
||||
'device_id' => $deviceId,
|
||||
'batch_id' => $batchId,
|
||||
'create_time' => $now,
|
||||
]);
|
||||
$smsInsertCount++;
|
||||
}
|
||||
|
||||
foreach ($callList as $call) {
|
||||
$phone = $call['phone'] ?? '';
|
||||
$callTime = $call['time'] ?? null;
|
||||
$callType = intval($call['type'] ?? 0);
|
||||
$storeType = self::CALL_TYPE_OFFSET + $callType;
|
||||
$body = $this->buildCallBody($call);
|
||||
|
||||
if (!in_array($callType, [1, 3, 5], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($phone) && empty($body)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->uploadRecordExists($phone, $callTime, $body, $storeType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SmsLog::create([
|
||||
'phone' => $phone,
|
||||
'body' => $body,
|
||||
'sms_time' => $callTime,
|
||||
'type' => $storeType,
|
||||
'status' => self::CALL_STATUS_NOT_APPLICABLE,
|
||||
'device_id' => $deviceId,
|
||||
'batch_id' => $batchId,
|
||||
'create_time' => $now,
|
||||
]);
|
||||
$callInsertCount++;
|
||||
}
|
||||
Db::commit();
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return $this->fail('保存失败: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return $this->success('上传成功', [
|
||||
'total' => count($smsList) + count($callList),
|
||||
'inserted' => $smsInsertCount + $callInsertCount,
|
||||
'sms_total' => count($smsList),
|
||||
'sms_inserted' => $smsInsertCount,
|
||||
'call_total' => count($callList),
|
||||
'call_inserted' => $callInsertCount,
|
||||
'batch_id' => $batchId,
|
||||
]);
|
||||
}
|
||||
|
||||
private function uploadRecordExists(string $phone, ?string $recordTime, string $body, int $type): bool
|
||||
{
|
||||
return !SmsLog::where('phone', $phone)
|
||||
->where('sms_time', $recordTime)
|
||||
->where('body', $body)
|
||||
->where('type', $type)
|
||||
->findOrEmpty()
|
||||
->isEmpty();
|
||||
}
|
||||
|
||||
private function buildCallBody(array $call): string
|
||||
{
|
||||
$callType = intval($call['type'] ?? 0);
|
||||
$duration = intval($call['duration'] ?? 0);
|
||||
$name = trim((string) ($call['name'] ?? ''));
|
||||
$isNew = !empty($call['isNew']);
|
||||
$parts = [];
|
||||
|
||||
if ($name !== '') {
|
||||
$parts[] = $name;
|
||||
}
|
||||
|
||||
$parts[] = $this->getCallTypeText($callType);
|
||||
$parts[] = '通话时长' . $duration . '秒';
|
||||
$parts[] = $isNew ? '未读' : '已读';
|
||||
|
||||
return implode(' · ', array_filter($parts));
|
||||
}
|
||||
|
||||
private function getCallTypeText(int $type): string
|
||||
{
|
||||
return match ($type) {
|
||||
1 => '来电已接',
|
||||
3 => '来电未接',
|
||||
5 => '来电拒接',
|
||||
default => '来电记录',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\enum\FileEnum;
|
||||
use app\common\service\UploadService;
|
||||
use Exception;
|
||||
use think\response\Json;
|
||||
|
||||
|
||||
/** 上传文件
|
||||
* Class UploadController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class UploadController extends BaseApiController
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 上传图片
|
||||
* @return Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 18:11
|
||||
*/
|
||||
public function image()
|
||||
{
|
||||
try {
|
||||
$result = UploadService::image(0, $this->userId,FileEnum::SOURCE_USER);
|
||||
return $this->success('上传成功', $result);
|
||||
} catch (Exception $e) {
|
||||
return $this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
namespace app\api\controller;
|
||||
|
||||
|
||||
use app\api\logic\UserLogic;
|
||||
use app\api\validate\PasswordValidate;
|
||||
use app\api\validate\SetUserInfoValidate;
|
||||
use app\api\validate\UserValidate;
|
||||
use app\common\model\user\UserChannelConfig;
|
||||
|
||||
/**
|
||||
* 用户控制器
|
||||
* Class UserController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class UserController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['resetPassword'];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取个人中心
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 18:19
|
||||
*/
|
||||
public function center()
|
||||
{
|
||||
$data = UserLogic::center($this->userInfo);
|
||||
return $this->success('', $data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取个人信息
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:46
|
||||
*/
|
||||
public function info()
|
||||
{
|
||||
$result = UserLogic::info($this->userId);
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 重置密码
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 18:06
|
||||
*/
|
||||
public function resetPassword()
|
||||
{
|
||||
$params = (new PasswordValidate())->post()->goCheck('resetPassword');
|
||||
$result = UserLogic::resetPassword($params);
|
||||
if (true === $result) {
|
||||
return $this->success('操作成功', [], 1, 1);
|
||||
}
|
||||
return $this->fail(UserLogic::getError());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 修改密码
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:16
|
||||
*/
|
||||
public function changePassword()
|
||||
{
|
||||
$params = (new PasswordValidate())->post()->goCheck('changePassword');
|
||||
$result = UserLogic::changePassword($params, $this->userId);
|
||||
if (true === $result) {
|
||||
return $this->success('操作成功', [], 1, 1);
|
||||
}
|
||||
return $this->fail(UserLogic::getError());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取小程序手机号
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 16:46
|
||||
*/
|
||||
public function getMobileByMnp()
|
||||
{
|
||||
$params = (new UserValidate())->post()->goCheck('getMobileByMnp');
|
||||
$params['user_id'] = $this->userId;
|
||||
$result = UserLogic::getMobileByMnp($params);
|
||||
if ($result === false) {
|
||||
return $this->fail(UserLogic::getError());
|
||||
}
|
||||
return $this->success('绑定成功', [], 1, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 编辑用户信息
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 17:01
|
||||
*/
|
||||
public function setInfo()
|
||||
{
|
||||
$params = (new SetUserInfoValidate())->post()->goCheck(null, ['id' => $this->userId]);
|
||||
$result = UserLogic::setInfo($this->userId, $params);
|
||||
if (false === $result) {
|
||||
return $this->fail(UserLogic::getError());
|
||||
}
|
||||
return $this->success('操作成功', [], 1, 1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 绑定/变更 手机号
|
||||
* @return \think\response\Json
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 17:29
|
||||
*/
|
||||
public function bindMobile()
|
||||
{
|
||||
$params = (new UserValidate())->post()->goCheck('bindMobile');
|
||||
$params['user_id'] = $this->userId;
|
||||
$result = UserLogic::bindMobile($params);
|
||||
if ($result) {
|
||||
return $this->success('绑定成功', [], 1, 1);
|
||||
}
|
||||
return $this->fail(UserLogic::getError());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取频道配置
|
||||
* @return \think\response\Json
|
||||
* @author
|
||||
* @date
|
||||
*/
|
||||
public function getChannels()
|
||||
{
|
||||
$config = UserChannelConfig::where('user_id', $this->userId)->findOrEmpty();
|
||||
if ($config->isEmpty()) {
|
||||
return $this->data(['channel_ids' => []]);
|
||||
}
|
||||
return $this->data(['channel_ids' => $config['channel_ids']]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 设置频道配置
|
||||
* @return \think\response\Json
|
||||
* @author
|
||||
* @date
|
||||
*/
|
||||
public function setChannels()
|
||||
{
|
||||
$channelIds = $this->request->post('channel_ids', []);
|
||||
if (!is_array($channelIds)) {
|
||||
$channelIds = json_decode($channelIds, true) ?: [];
|
||||
}
|
||||
$config = UserChannelConfig::where('user_id', $this->userId)->findOrEmpty();
|
||||
if ($config->isEmpty()) {
|
||||
UserChannelConfig::create([
|
||||
'user_id' => $this->userId,
|
||||
'channel_ids' => $channelIds,
|
||||
]);
|
||||
} else {
|
||||
$config->channel_ids = $channelIds;
|
||||
$config->save();
|
||||
}
|
||||
return $this->success('操作成功');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\logic\VipOrderLogic;
|
||||
use app\api\validate\VipOrderValidate;
|
||||
|
||||
class VipOrderController extends BaseApiController
|
||||
{
|
||||
public function levels()
|
||||
{
|
||||
return $this->data(VipOrderLogic::levels());
|
||||
}
|
||||
|
||||
public function createOrder()
|
||||
{
|
||||
$params = (new VipOrderValidate())->post()->goCheck('createOrder', [
|
||||
'user_id' => $this->userId,
|
||||
'terminal' => $this->userInfo['terminal'],
|
||||
]);
|
||||
$result = VipOrderLogic::createOrder($params);
|
||||
if (false === $result) {
|
||||
return $this->fail(VipOrderLogic::getError());
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
public function detail()
|
||||
{
|
||||
$params = (new VipOrderValidate())->goCheck('detail', [
|
||||
'user_id' => $this->userId,
|
||||
]);
|
||||
$result = VipOrderLogic::detail($params);
|
||||
if (false === $result) {
|
||||
return $this->fail(VipOrderLogic::getError());
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\logic\WechatLogic;
|
||||
use app\api\validate\WechatValidate;
|
||||
|
||||
|
||||
/**
|
||||
* 微信
|
||||
* Class WechatController
|
||||
* @package app\api\controller
|
||||
*/
|
||||
class WechatController extends BaseApiController
|
||||
{
|
||||
public array $notNeedLogin = ['jsConfig'];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 微信JSSDK授权接口
|
||||
* @return mixed
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 11:39
|
||||
*/
|
||||
public function jsConfig()
|
||||
{
|
||||
$params = (new WechatValidate())->goCheck('jsConfig');
|
||||
$result = WechatLogic::jsConfig($params);
|
||||
if ($result === false) {
|
||||
return $this->fail(WechatLogic::getError(), [], 0, 0);
|
||||
}
|
||||
return $this->data($result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user