no message
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?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
|
||||
// +----------------------------------------------------------------------
|
||||
return [
|
||||
'middleware' => [
|
||||
app\api\http\middleware\InitMiddleware::class, // 初始化
|
||||
app\api\http\middleware\LoginMiddleware::class, // 登录验证
|
||||
],
|
||||
];
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?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
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types=1);
|
||||
|
||||
namespace app\api\http\middleware;
|
||||
|
||||
|
||||
use app\common\exception\ControllerExtendException;
|
||||
use app\api\controller\BaseApiController;
|
||||
use think\exception\ClassNotFoundException;
|
||||
use think\exception\HttpException;
|
||||
|
||||
|
||||
class InitMiddleware
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 初始化
|
||||
* @param $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
* @throws ControllerExtendException
|
||||
* @author 段誉
|
||||
* @date 2022/9/6 18:17
|
||||
*/
|
||||
public function handle($request, \Closure $next)
|
||||
{
|
||||
//获取控制器
|
||||
try {
|
||||
$controller = str_replace('.', '\\', $request->controller());
|
||||
$controller = '\\app\\api\\controller\\' . $controller . 'Controller';
|
||||
$controllerClass = invoke($controller);
|
||||
if (($controllerClass instanceof BaseApiController) === false) {
|
||||
throw new ControllerExtendException($controller, '404');
|
||||
}
|
||||
} catch (ClassNotFoundException $e) {
|
||||
throw new HttpException(404, 'controller not exists:' . $e->getClass());
|
||||
}
|
||||
//创建控制器对象
|
||||
$request->controllerObject = invoke($controller);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?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
|
||||
// +----------------------------------------------------------------------
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\api\http\middleware;
|
||||
|
||||
|
||||
use app\common\cache\UserTokenCache;
|
||||
use app\common\service\JsonService;
|
||||
use app\api\service\UserTokenService;
|
||||
use think\facade\Config;
|
||||
|
||||
class LoginMiddleware
|
||||
{
|
||||
/**
|
||||
* @notes 登录验证
|
||||
* @param $request
|
||||
* @param \Closure $next
|
||||
* @return mixed|\think\response\Json
|
||||
* @author 令狐冲
|
||||
* @date 2021/7/1 17:33
|
||||
*/
|
||||
public function handle($request, \Closure $next)
|
||||
{
|
||||
$token = $request->header('token');
|
||||
if ($token === 'null' || $token === 'undefined' || $token === '') {
|
||||
$token = null;
|
||||
}
|
||||
//判断接口是否免登录
|
||||
$isNotNeedLogin = $request->controllerObject->isNotNeedLogin();
|
||||
// GET请求视为读操作,无token时以游客身份放行
|
||||
$isReadRequest = $request->isGet();
|
||||
|
||||
//不直接判断$isNotNeedLogin结果,使不需要登录的接口通过,为了兼容某些接口可以登录或不登录访问
|
||||
if (empty($token) && !$isNotNeedLogin) {
|
||||
if ($isReadRequest) {
|
||||
// GET请求无token,以游客身份放行
|
||||
$request->userInfo = [];
|
||||
$request->userId = 0;
|
||||
return $next($request);
|
||||
}
|
||||
//写操作没有token需要登录才能访问, 指定show为0,前端不弹出此报错
|
||||
return JsonService::fail('请求参数缺token', [], 0, 0);
|
||||
}
|
||||
|
||||
$userInfo = (new UserTokenCache())->getUserInfo($token);
|
||||
|
||||
if (empty($userInfo) && !$isNotNeedLogin) {
|
||||
if ($isReadRequest) {
|
||||
// GET请求token无效,以游客身份放行
|
||||
$request->userInfo = [];
|
||||
$request->userId = 0;
|
||||
return $next($request);
|
||||
}
|
||||
//写操作token过期无效需要登录才能访问
|
||||
return JsonService::fail('登录超时,请重新登录', [], -1, 0);
|
||||
}
|
||||
|
||||
//token临近过期,自动续期
|
||||
if ($userInfo) {
|
||||
//获取临近过期自动续期时长
|
||||
$beExpireDuration = Config::get('project.user_token.be_expire_duration');
|
||||
//token续期
|
||||
if (time() > ($userInfo['expire_time'] - $beExpireDuration)) {
|
||||
$result = UserTokenService::overtimeToken($token);
|
||||
//续期失败(数据表被删除导致)
|
||||
if (empty($result)) {
|
||||
if ($isReadRequest) {
|
||||
$request->userInfo = [];
|
||||
$request->userId = 0;
|
||||
return $next($request);
|
||||
}
|
||||
return JsonService::fail('登录过期', [], -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//给request赋值,用于控制器
|
||||
$request->userInfo = $userInfo;
|
||||
$request->userId = $userInfo['user_id'] ?? 0;
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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\lists;
|
||||
|
||||
use app\common\enum\user\AccountLogEnum;
|
||||
use app\common\model\user\UserAccountLog;
|
||||
|
||||
|
||||
/**
|
||||
* 账户流水列表
|
||||
* Class AccountLogLists
|
||||
* @package app\shopapi\lists
|
||||
*/
|
||||
class AccountLogLists extends BaseApiDataLists
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 14:43
|
||||
*/
|
||||
public function queryWhere()
|
||||
{
|
||||
// 指定用户
|
||||
$where[] = ['user_id', '=', $this->userId];
|
||||
|
||||
// 用户余额明细
|
||||
if (isset($this->params['type']) && $this->params['type'] == 'um') {
|
||||
$where[] = ['change_type', 'in', AccountLogEnum::getUserMoneyChangeType()];
|
||||
}
|
||||
|
||||
// 用户积分明细
|
||||
if (isset($this->params['type']) && $this->params['type'] == 'up') {
|
||||
$where[] = ['change_type', 'in', AccountLogEnum::getUserPointsChangeType()];
|
||||
}
|
||||
|
||||
// 变动类型
|
||||
if (!empty($this->params['action'])) {
|
||||
$where[] = ['action', '=', $this->params['action']];
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 14:43
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$field = 'change_type,change_amount,action,create_time,remark';
|
||||
$lists = UserAccountLog::field($field)
|
||||
->where($this->queryWhere())
|
||||
->order('id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$item['type_desc'] = AccountLogEnum::getChangeTypeDesc($item['change_type']);
|
||||
$symbol = $item['action'] == AccountLogEnum::DEC ? '-' : '+';
|
||||
$item['change_amount_desc'] = $symbol . $item['change_amount'];
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 14:44
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return UserAccountLog::where($this->queryWhere())->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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\lists;
|
||||
|
||||
use app\common\lists\BaseDataLists;
|
||||
|
||||
abstract class BaseApiDataLists extends BaseDataLists
|
||||
{
|
||||
protected array $userInfo = [];
|
||||
protected int $userId = 0;
|
||||
|
||||
public string $export;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
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;
|
||||
}
|
||||
$this->export = $this->request->get('export', '');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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\lists\article;
|
||||
|
||||
use app\api\lists\BaseApiDataLists;
|
||||
use app\common\enum\YesNoEnum;
|
||||
use app\common\model\article\Article;
|
||||
|
||||
/**
|
||||
* 文章收藏列表
|
||||
* Class ArticleCollectLists
|
||||
* @package app\api\lists\article
|
||||
*/
|
||||
class ArticleCollectLists extends BaseApiDataLists
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 获取收藏列表
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 16:29
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$field = "c.id,c.article_id,a.title,a.image,a.desc,a.is_show,
|
||||
a.click_virtual, a.click_actual,a.create_time, c.create_time as collect_time";
|
||||
|
||||
$lists = (new Article())->alias('a')
|
||||
->join('article_collect c', 'c.article_id = a.id')
|
||||
->field($field)
|
||||
->where([
|
||||
'c.user_id' => $this->userId,
|
||||
'c.status' => YesNoEnum::YES,
|
||||
'a.is_show' => YesNoEnum::YES,
|
||||
])
|
||||
->order(['sort' => 'desc', 'c.id' => 'desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->append(['click'])
|
||||
->hidden(['click_virtual', 'click_actual'])
|
||||
->select()->toArray();
|
||||
|
||||
foreach ($lists as &$item) {
|
||||
$item['collect_time'] = date('Y-m-d H:i', $item['collect_time']);
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取收藏数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 16:29
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return (new Article())->alias('a')
|
||||
->join('article_collect c', 'c.article_id = a.id')
|
||||
->where([
|
||||
'c.user_id' => $this->userId,
|
||||
'c.status' => YesNoEnum::YES,
|
||||
'a.is_show' => YesNoEnum::YES,
|
||||
])
|
||||
->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?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\lists\article;
|
||||
|
||||
use app\api\lists\BaseApiDataLists;
|
||||
use app\api\logic\ArticleLogic;
|
||||
use app\common\enum\YesNoEnum;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\article\Article;
|
||||
use app\common\model\article\ArticleCollect;
|
||||
use app\common\service\article\ArticleImageProxyService;
|
||||
use app\common\model\user\UserChannelConfig;
|
||||
|
||||
|
||||
/**
|
||||
* 文章列表
|
||||
* Class ArticleLists
|
||||
* @package app\api\lists\article
|
||||
*/
|
||||
class ArticleLists extends BaseApiDataLists implements ListsSearchInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 搜索条件
|
||||
* @return \string[][]
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 18:54
|
||||
*/
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['cid']
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 自定查询条件
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2022/10/25 16:53
|
||||
*/
|
||||
public function queryWhere()
|
||||
{
|
||||
$where[] = ['is_show', '=', 1];
|
||||
if (!empty($this->params['keyword'])) {
|
||||
$where[] = ['title', 'like', '%' . $this->params['keyword'] . '%'];
|
||||
}
|
||||
if (isset($this->params['is_recommend']) && $this->params['is_recommend'] !== '') {
|
||||
$where[] = ['is_recommend', '=', $this->params['is_recommend']];
|
||||
}
|
||||
if (!empty($this->params['is_mychannel'])) {
|
||||
if ($this->userId) {
|
||||
$config = UserChannelConfig::where('user_id', $this->userId)->findOrEmpty();
|
||||
$channelIds = [];
|
||||
if (!$config->isEmpty()) {
|
||||
$raw = $config['channel_ids'];
|
||||
if (is_string($raw)) {
|
||||
$channelIds = json_decode($raw, true) ?: [];
|
||||
} elseif (is_array($raw)) {
|
||||
$channelIds = $raw;
|
||||
}
|
||||
}
|
||||
if (!empty($channelIds)) {
|
||||
$where[] = ['cid', 'in', $channelIds];
|
||||
} else {
|
||||
$where[] = ['id', '=', 0];
|
||||
}
|
||||
} else {
|
||||
$where[] = ['id', '=', 0];
|
||||
}
|
||||
}
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取文章列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 18:55
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$orderRaw = 'published_at desc, id desc';
|
||||
$sortType = $this->params['sort'] ?? 'default';
|
||||
$cid = (int) ($this->params['cid'] ?? 0);
|
||||
// 最新排序
|
||||
if ($sortType == 'new') {
|
||||
$orderRaw = 'published_at desc, id desc';
|
||||
}
|
||||
// 最热排序
|
||||
if ($sortType == 'hot') {
|
||||
$orderRaw = 'click_actual + click_virtual desc, id desc';
|
||||
}
|
||||
|
||||
$field = 'id,cid,title,desc,content,author,image,is_video,duration,video_url,click_virtual,click_actual,create_time,ext';
|
||||
$result = Article::field($field)
|
||||
->withCount(['comments' => 'comment_count'])
|
||||
->where($this->queryWhere())
|
||||
->where($this->searchWhere)
|
||||
->orderRaw($orderRaw)
|
||||
->append(['click'])
|
||||
->hidden(['click_virtual', 'click_actual'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()->toArray();
|
||||
|
||||
$articleIds = array_column($result, 'id');
|
||||
|
||||
$collectIds = ArticleCollect::where(['user_id' => $this->userId, 'status' => YesNoEnum::YES])
|
||||
->whereIn('article_id', $articleIds)
|
||||
->column('article_id');
|
||||
|
||||
foreach ($result as &$item) {
|
||||
$item['collect'] = in_array($item['id'], $collectIds);
|
||||
if (!empty($item['content'])) {
|
||||
$item['content'] = mb_substr(strip_tags($item['content']), 0, 100);
|
||||
}
|
||||
$item['translated_content'] = $cid === 22 ? ArticleLogic::getCachedTranslatedContent($item) : '';
|
||||
$item = ArticleImageProxyService::rewriteArticlePayload($item);
|
||||
unset($item['ext']);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取文章数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 18:55
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return Article::where($this->searchWhere)
|
||||
->where($this->queryWhere())
|
||||
->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\lists\community;
|
||||
|
||||
use app\api\lists\BaseApiDataLists;
|
||||
use app\common\model\community\CommunityPost;
|
||||
use app\common\model\community\CommunityFollow;
|
||||
use app\common\model\community\CommunityTag;
|
||||
use app\common\model\user\User;
|
||||
use app\common\enum\user\AccountLogEnum;
|
||||
use app\common\service\ai\AiService;
|
||||
use app\common\service\VipService;
|
||||
use think\facade\Cache;
|
||||
use think\facade\Db;
|
||||
use app\common\model\community\CommunityLike;
|
||||
|
||||
class CommunityPostLists extends BaseApiDataLists
|
||||
{
|
||||
private const LIUHE_TAGS = ['旧澳六合', '新澳六合'];
|
||||
|
||||
private function buildQuery()
|
||||
{
|
||||
$query = CommunityPost::where($this->queryWhere());
|
||||
$trumpTagId = (int) CommunityTag::where('name', '特朗普')->value('id');
|
||||
if ($trumpTagId > 0) {
|
||||
$query->whereRaw(
|
||||
"NOT (id IN (SELECT post_id FROM la_community_post_tag WHERE tag_id = {$trumpTagId})" .
|
||||
" AND TRIM(IFNULL(content, '')) = ''" .
|
||||
" AND IFNULL(images, '[]') <> '[]')"
|
||||
);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
private function queryWhere(): array
|
||||
{
|
||||
$where = [['status', '=', 1]];
|
||||
|
||||
if (!empty($this->params['post_type'])) {
|
||||
$where[] = ['post_type', '=', $this->params['post_type']];
|
||||
}
|
||||
if (!empty($this->params['user_id'])) {
|
||||
$where[] = ['user_id', '=', $this->params['user_id']];
|
||||
}
|
||||
if (!empty($this->params['follow']) && $this->userId) {
|
||||
$followUserIds = CommunityFollow::where('user_id', $this->userId)
|
||||
->column('follow_user_id');
|
||||
if ($followUserIds) {
|
||||
$where[] = ['user_id', 'in', $followUserIds];
|
||||
} else {
|
||||
$where[] = ['id', '=', 0];
|
||||
}
|
||||
}
|
||||
if (!empty($this->params['tag_id'])) {
|
||||
$postIds = Db::name('community_post_tag')
|
||||
->where('tag_id', $this->params['tag_id'])
|
||||
->column('post_id');
|
||||
if ($postIds) {
|
||||
$where[] = ['id', 'in', $postIds];
|
||||
} else {
|
||||
$where[] = ['id', '=', 0];
|
||||
}
|
||||
}
|
||||
return $where;
|
||||
}
|
||||
|
||||
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 getLiuheAnalysisContent(array $ext): string
|
||||
{
|
||||
if (($ext['lottery_analysis_source'] ?? '') !== 'image_only') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) ($ext['lottery_analysis_content'] ?? '');
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$list = $this->buildQuery()
|
||||
->field('id,origin_id,user_id,content,images,ext,post_type,match_id,is_paid,price_points,paid_count,view_count,like_count,comment_count,share_count,is_top,is_hot,is_recommend,create_time')
|
||||
->orderRaw('is_top DESC, is_hot DESC, create_time DESC')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 批量预加载:用户、标签、翻译状态
|
||||
$postIds = array_column($list, 'id');
|
||||
$userIds = array_unique(array_column($list, 'user_id'));
|
||||
|
||||
// 批量查询用户 → id 为 key
|
||||
$userMap = [];
|
||||
if ($userIds) {
|
||||
$users = User::field('id,sn,nickname,avatar,sex')->whereIn('id', $userIds)->select()->toArray();
|
||||
foreach ($users as $u) {
|
||||
$userMap[$u['id']] = $u;
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查询帖子标签 → post_id => [tag_name, ...]
|
||||
$tagMap = [];
|
||||
if ($postIds) {
|
||||
$postTags = Db::name('community_post_tag')->whereIn('post_id', $postIds)->select()->toArray();
|
||||
$tagIdsByPost = [];
|
||||
foreach ($postTags as $pt) {
|
||||
$tagIdsByPost[$pt['post_id']][] = $pt['tag_id'];
|
||||
}
|
||||
$allTagIds = array_unique(array_column($postTags, 'tag_id'));
|
||||
$tagNames = $allTagIds ? CommunityTag::whereIn('id', $allTagIds)->column('name', 'id') : [];
|
||||
foreach ($tagIdsByPost as $pid => $tids) {
|
||||
$tagMap[$pid] = array_values(array_intersect_key($tagNames, array_flip($tids)));
|
||||
}
|
||||
}
|
||||
|
||||
// 已付费翻译的帖子ID集合(带缓存)
|
||||
$translatedPostIds = [];
|
||||
if ($this->userId) {
|
||||
$translatedPostIds = self::getUserTranslatedPostIds($this->userId);
|
||||
}
|
||||
|
||||
// 批量查询已购买的付费帖子ID
|
||||
$purchasedPostIds = [];
|
||||
if ($this->userId && $postIds) {
|
||||
$purchasedPostIds = Db::name('user_account_log')
|
||||
->where('user_id', $this->userId)
|
||||
->where('change_type', AccountLogEnum::UP_DEC_PURCHASE_POST)
|
||||
->whereIn('relation_id', $postIds)
|
||||
->column('relation_id');
|
||||
}
|
||||
|
||||
// VIP权益:解锁帖子免费、翻译免费
|
||||
$vipUnlockFree = $this->userId ? VipService::hasUnlockFree($this->userId) : false;
|
||||
$vipTranslateFree = $this->userId ? VipService::hasTranslateFree($this->userId) : false;
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$ext = $item['ext'] ?? '';
|
||||
$ext = $ext ? (is_string($ext) ? json_decode($ext, true) : $ext) : [];
|
||||
|
||||
// 判断是否已解锁(自己发的 或 已购买 或 VIP解锁免费)
|
||||
$item['is_unlocked'] = 0;
|
||||
if ($item['is_paid']) {
|
||||
if ($item['user_id'] == $this->userId || in_array($item['id'], $purchasedPostIds) || $vipUnlockFree) {
|
||||
$item['is_unlocked'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 付费帖子:未解锁 → 截断内容并隐藏全文
|
||||
if ($item['is_paid'] && !$item['is_unlocked']) {
|
||||
$item['content_short'] = mb_substr($item['content'], 0, 20) . '...';
|
||||
$item['content'] = $item['content_short'];
|
||||
} elseif (mb_strlen($item['content']) > 120) {
|
||||
$item['content_short'] = mb_substr($item['content'], 0, 120) . '...';
|
||||
} else {
|
||||
$item['content_short'] = $item['content'];
|
||||
}
|
||||
|
||||
$item['user'] = $userMap[$item['user_id']] ?? null;
|
||||
$item['tags'] = $tagMap[$item['id']] ?? [];
|
||||
$item['source_url'] = $ext['url'] ?? '';
|
||||
$item['is_liked'] = false;
|
||||
if ($this->userId) {
|
||||
$item['is_liked'] = CommunityLike::where([
|
||||
'user_id' => $this->userId,
|
||||
'target_id' => $item['id'],
|
||||
'target_type' => 1
|
||||
])->count() > 0;
|
||||
}
|
||||
|
||||
$item['translated_content'] = '';
|
||||
$isTrumpPost = in_array('特朗普', $item['tags']);
|
||||
$isLiuhePost = $this->isLiuhePost($item['tags'], $ext);
|
||||
if ($isTrumpPost) {
|
||||
if (empty($ext['translated_content']) && !empty($item['content'])) {
|
||||
$result = AiService::translateCommunityPost($item['content']);
|
||||
if (!empty($result['success']) && !empty($result['content'])) {
|
||||
$ext['translated_content'] = trim($result['content']);
|
||||
CommunityPost::where('id', $item['id'])->update([
|
||||
'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
$item['translated_content'] = $ext['translated_content'] ?? '';
|
||||
} elseif ((!empty($item['origin_id']) || $isLiuhePost) && (in_array($item['id'], $translatedPostIds) || $vipTranslateFree)) {
|
||||
$item['translated_content'] = $isLiuhePost
|
||||
? $this->getLiuheAnalysisContent($ext)
|
||||
: (string) ($ext['translated_content'] ?? '');
|
||||
}
|
||||
unset($item['ext']);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return $this->buildQuery()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户已付费翻译的帖子ID集合(优先缓存)
|
||||
*/
|
||||
public static function getUserTranslatedPostIds(int $userId): array
|
||||
{
|
||||
$cacheKey = 'translate_paid:' . $userId;
|
||||
$postIds = Cache::get($cacheKey);
|
||||
if ($postIds !== null) {
|
||||
return $postIds;
|
||||
}
|
||||
|
||||
$remarks = Db::name('user_account_log')
|
||||
->where('user_id', $userId)
|
||||
->where('change_type', AccountLogEnum::UP_DEC_TRANSLATE_POST)
|
||||
->column('remark');
|
||||
|
||||
$postIds = [];
|
||||
foreach ($remarks as $remark) {
|
||||
if (preg_match('/(?:翻译|分析)帖子#(\d+)/', $remark, $m)) {
|
||||
$postIds[] = (int) $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
Cache::set($cacheKey, $postIds, 3600);
|
||||
return $postIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译扣分后刷新缓存:追加新帖子ID
|
||||
*/
|
||||
public static function refreshTranslateCache(int $userId, int $postId): void
|
||||
{
|
||||
$cacheKey = 'translate_paid:' . $userId;
|
||||
$postIds = Cache::get($cacheKey);
|
||||
if (is_array($postIds)) {
|
||||
if (!in_array($postId, $postIds)) {
|
||||
$postIds[] = $postId;
|
||||
Cache::set($cacheKey, $postIds, 3600);
|
||||
}
|
||||
} else {
|
||||
Cache::delete($cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\lists\match;
|
||||
|
||||
use app\api\lists\BaseApiDataLists;
|
||||
use app\common\model\match\MatchEvent;
|
||||
use app\common\model\league\League;
|
||||
|
||||
class MatchLists extends BaseApiDataLists
|
||||
{
|
||||
private function queryWhere(): array
|
||||
{
|
||||
$where = [['is_show', '=', 1]];
|
||||
if (!empty($this->params['sport_type'])) {
|
||||
$where[] = ['sport_type', '=', $this->params['sport_type']];
|
||||
} else {
|
||||
$validSportTypes = League::where('is_show', 1)
|
||||
->where('type', 'sport')
|
||||
->where('sport_type', '>', 0)
|
||||
->column('sport_type');
|
||||
if (!empty($validSportTypes)) {
|
||||
$where[] = ['sport_type', 'in', $validSportTypes];
|
||||
}
|
||||
}
|
||||
if (!empty($this->params['league_name'])) {
|
||||
$where[] = ['league_name', '=', $this->params['league_name']];
|
||||
} elseif (empty($this->params['sport_type'])) {
|
||||
$validLeagues = League::where('is_show', 1)
|
||||
->where('type', 'league')
|
||||
->column('label');
|
||||
if (!empty($validLeagues)) {
|
||||
$where[] = ['league_name', 'in', $validLeagues];
|
||||
}
|
||||
}
|
||||
if (isset($this->params['status']) && $this->params['status'] !== '') {
|
||||
$where[] = ['status', '=', $this->params['status']];
|
||||
}
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
return MatchEvent::where($this->queryWhere())
|
||||
->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')
|
||||
->orderRaw('FIELD(status, 1, 0, 3, 2), CASE WHEN status = 2 THEN -match_time ELSE match_time END ASC')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return MatchEvent::where($this->queryWhere())->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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\lists\recharge;
|
||||
|
||||
use app\api\lists\BaseApiDataLists;
|
||||
use app\common\enum\PayEnum;
|
||||
use app\common\model\recharge\RechargeOrder;
|
||||
|
||||
|
||||
/**
|
||||
* 充值记录列表
|
||||
* Class RechargeLists
|
||||
* @package app\api\lists\recharge
|
||||
*/
|
||||
class RechargeLists extends BaseApiDataLists
|
||||
{
|
||||
/**
|
||||
* @notes 获取列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2023/2/23 18:43
|
||||
*/
|
||||
public function lists(): array
|
||||
{
|
||||
$lists = RechargeOrder::field('order_amount,create_time')
|
||||
->where([
|
||||
'user_id' => $this->userId,
|
||||
'pay_status' => PayEnum::ISPAID
|
||||
])
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach($lists as &$item) {
|
||||
$item['tips'] = '充值' . format_amount($item['order_amount']) . '元';
|
||||
}
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取数量
|
||||
* @return int
|
||||
* @author 段誉
|
||||
* @date 2023/2/23 18:43
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return RechargeOrder::where([
|
||||
'user_id' => $this->userId,
|
||||
'pay_status' => PayEnum::ISPAID
|
||||
])
|
||||
->count();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\logic;
|
||||
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\article\Article;
|
||||
use app\common\model\article\ArticleComment;
|
||||
use app\common\model\article\ArticleVote;
|
||||
use app\common\model\user\User;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 文章互动逻辑(评论/点赞/踩)
|
||||
* Class ArticleInteractLogic
|
||||
* @package app\api\logic
|
||||
*/
|
||||
class ArticleInteractLogic extends BaseLogic
|
||||
{
|
||||
/**
|
||||
* @notes 评论列表
|
||||
*/
|
||||
public static function commentList($articleId, $pageNo = 1, $pageSize = 15)
|
||||
{
|
||||
$where = [
|
||||
'article_id' => $articleId,
|
||||
'parent_id' => 0,
|
||||
'is_show' => 1,
|
||||
];
|
||||
|
||||
$count = ArticleComment::where($where)->count();
|
||||
|
||||
$list = ArticleComment::where($where)
|
||||
->order('id', 'desc')
|
||||
->page($pageNo, $pageSize)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$userIds = array_column($list, 'user_id');
|
||||
|
||||
// 取子评论的user_id
|
||||
$commentIds = array_column($list, 'id');
|
||||
$replies = [];
|
||||
if (!empty($commentIds)) {
|
||||
$repliesRaw = ArticleComment::where('parent_id', 'in', $commentIds)
|
||||
->where('is_show', 1)
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
$replyUserIds = array_column($repliesRaw, 'user_id');
|
||||
$replyTargetUserIds = array_column($repliesRaw, 'reply_user_id');
|
||||
$userIds = array_merge($userIds, $replyUserIds, $replyTargetUserIds);
|
||||
|
||||
foreach ($repliesRaw as $r) {
|
||||
$replies[$r['parent_id']][] = $r;
|
||||
}
|
||||
}
|
||||
|
||||
// 批量获取用户信息
|
||||
$userIds = array_unique(array_filter($userIds));
|
||||
$users = [];
|
||||
if (!empty($userIds)) {
|
||||
$usersRaw = User::whereIn('id', $userIds)
|
||||
->field('id,nickname,avatar')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($usersRaw as $u) {
|
||||
$users[$u['id']] = $u;
|
||||
}
|
||||
}
|
||||
|
||||
$defaultUser = ['nickname' => '匿名用户', 'avatar' => ''];
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$u = $users[$item['user_id']] ?? $defaultUser;
|
||||
$item['nickname'] = $u['nickname'];
|
||||
$item['avatar'] = $u['avatar'];
|
||||
$item['reply_list'] = [];
|
||||
|
||||
if (isset($replies[$item['id']])) {
|
||||
foreach ($replies[$item['id']] as $reply) {
|
||||
$ru = $users[$reply['user_id']] ?? $defaultUser;
|
||||
$reply['nickname'] = $ru['nickname'];
|
||||
$reply['avatar'] = $ru['avatar'];
|
||||
$reply['reply_nickname'] = '';
|
||||
if ($reply['reply_user_id'] > 0) {
|
||||
$tu = $users[$reply['reply_user_id']] ?? $defaultUser;
|
||||
$reply['reply_nickname'] = $tu['nickname'];
|
||||
}
|
||||
$item['reply_list'][] = $reply;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return [
|
||||
'count' => $count,
|
||||
'lists' => $list,
|
||||
'page_no' => $pageNo,
|
||||
'page_size' => $pageSize,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 发布评论
|
||||
*/
|
||||
public static function addComment($userId, $params)
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
$comment = ArticleComment::create([
|
||||
'article_id' => $params['article_id'],
|
||||
'user_id' => $userId,
|
||||
'parent_id' => $params['parent_id'] ?? 0,
|
||||
'reply_user_id' => $params['reply_user_id'] ?? 0,
|
||||
'content' => $params['content'],
|
||||
]);
|
||||
|
||||
Article::where('id', $params['article_id'])
|
||||
->inc('comment_count')
|
||||
->update();
|
||||
|
||||
Db::commit();
|
||||
return $comment->id;
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 点赞/踩
|
||||
*/
|
||||
public static function vote($userId, $articleId, $voteType)
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
$existing = ArticleVote::where([
|
||||
'article_id' => $articleId,
|
||||
'user_id' => $userId,
|
||||
])->findOrEmpty();
|
||||
|
||||
if (!$existing->isEmpty()) {
|
||||
if ($existing->vote_type == $voteType) {
|
||||
// 取消
|
||||
$field = $voteType == ArticleVote::VOTE_LIKE ? 'like_count' : 'dislike_count';
|
||||
$existing->delete();
|
||||
Article::where('id', $articleId)->where($field, '>', 0)->dec($field)->update();
|
||||
Db::commit();
|
||||
return ['action' => 'cancel', 'vote_type' => $voteType];
|
||||
} else {
|
||||
// 切换: 旧的减,新的加
|
||||
$oldField = $existing->vote_type == ArticleVote::VOTE_LIKE ? 'like_count' : 'dislike_count';
|
||||
$newField = $voteType == ArticleVote::VOTE_LIKE ? 'like_count' : 'dislike_count';
|
||||
$existing->vote_type = $voteType;
|
||||
$existing->save();
|
||||
Article::where('id', $articleId)->where($oldField, '>', 0)->dec($oldField)->update();
|
||||
Article::where('id', $articleId)->inc($newField)->update();
|
||||
Db::commit();
|
||||
return ['action' => 'switch', 'vote_type' => $voteType];
|
||||
}
|
||||
} else {
|
||||
// 新增
|
||||
ArticleVote::create([
|
||||
'article_id' => $articleId,
|
||||
'user_id' => $userId,
|
||||
'vote_type' => $voteType,
|
||||
]);
|
||||
$field = $voteType == ArticleVote::VOTE_LIKE ? 'like_count' : 'dislike_count';
|
||||
Article::where('id', $articleId)->inc($field)->update();
|
||||
Db::commit();
|
||||
return ['action' => 'add', 'vote_type' => $voteType];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取用户对文章的投票状态
|
||||
*/
|
||||
public static function getVoteStatus($userId, $articleId)
|
||||
{
|
||||
if (empty($userId)) {
|
||||
return 0;
|
||||
}
|
||||
$vote = ArticleVote::where([
|
||||
'article_id' => $articleId,
|
||||
'user_id' => $userId,
|
||||
])->findOrEmpty();
|
||||
|
||||
return $vote->isEmpty() ? 0 : $vote->vote_type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
<?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\logic;
|
||||
|
||||
use app\common\enum\user\AccountLogEnum;
|
||||
use app\common\enum\YesNoEnum;
|
||||
use app\common\logic\AccountLogLogic;
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\article\Article;
|
||||
use app\common\model\article\ArticleCate;
|
||||
use app\common\model\article\ArticleCollect;
|
||||
use app\api\logic\ArticleInteractLogic;
|
||||
use app\common\service\article\ArticleImageProxyService;
|
||||
use app\common\service\ai\AiService;
|
||||
use app\common\service\ai\KbSyncService;
|
||||
use app\common\model\user\User;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
* 文章逻辑
|
||||
* Class ArticleLogic
|
||||
* @package app\api\logic
|
||||
*/
|
||||
class ArticleLogic extends BaseLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 文章详情
|
||||
* @param $articleId
|
||||
* @param $userId
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 17:09
|
||||
*/
|
||||
public static function detail($articleId, $userId)
|
||||
{
|
||||
$article = Article::getArticleDetailArr($articleId);
|
||||
if (empty($article)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ext = self::decodeExt($article['ext'] ?? []);
|
||||
$translatedContent = self::getTranslatedContentForUser($articleId, $userId, $ext);
|
||||
if ($translatedContent === '' && (int) ($article['cid'] ?? 0) === 22) {
|
||||
$translatedContent = self::autoTranslateArticle($article);
|
||||
}
|
||||
if ($translatedContent !== '') {
|
||||
$article['translated_content'] = $translatedContent;
|
||||
} elseif (!empty($ext['translated_content'])) {
|
||||
$article['translated_content'] = (string) $ext['translated_content'];
|
||||
}
|
||||
$article['collect'] = ArticleCollect::isCollectArticle($userId, $articleId);
|
||||
$article['vote_status'] = ArticleInteractLogic::getVoteStatus($userId, $articleId);
|
||||
$article = ArticleImageProxyService::rewriteArticlePayload($article);
|
||||
|
||||
return $article;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 加入收藏
|
||||
* @param $userId
|
||||
* @param $articleId
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 16:52
|
||||
*/
|
||||
public static function addCollect($articleId, $userId)
|
||||
{
|
||||
$where = ['user_id' => $userId, 'article_id' => $articleId];
|
||||
$collect = ArticleCollect::where($where)->findOrEmpty();
|
||||
if ($collect->isEmpty()) {
|
||||
ArticleCollect::create([
|
||||
'user_id' => $userId,
|
||||
'article_id' => $articleId,
|
||||
'status' => YesNoEnum::YES
|
||||
]);
|
||||
} else {
|
||||
ArticleCollect::update([
|
||||
'id' => $collect['id'],
|
||||
'status' => YesNoEnum::YES
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 取消收藏
|
||||
* @param $articleId
|
||||
* @param $userId
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 16:59
|
||||
*/
|
||||
public static function cancelCollect($articleId, $userId)
|
||||
{
|
||||
ArticleCollect::update(['status' => YesNoEnum::NO], [
|
||||
'user_id' => $userId,
|
||||
'article_id' => $articleId,
|
||||
'status' => YesNoEnum::YES
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 文章分类
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/23 14:11
|
||||
*/
|
||||
public static function cate()
|
||||
{
|
||||
return ArticleCate::field('id,name')
|
||||
->where('is_show', '=', 1)
|
||||
->order(['sort' => 'desc', 'id' => 'desc'])
|
||||
->select()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 文章翻译
|
||||
* @param int $articleId
|
||||
* @return array
|
||||
*/
|
||||
public static function translate(int $articleId, int $userId = 0, int $confirm = 0): array
|
||||
{
|
||||
$article = Article::where(['id' => $articleId, 'is_show' => YesNoEnum::YES])->findOrEmpty();
|
||||
if ($article->isEmpty()) {
|
||||
return ['success' => false, 'error' => '文章不存在'];
|
||||
}
|
||||
|
||||
$articleData = $article->toArray();
|
||||
$ext = self::decodeExt($articleData['ext'] ?? []);
|
||||
$translatedContent = self::getTranslatedContentForUser($articleId, $userId, $ext);
|
||||
$translatePoints = 5;
|
||||
$remark = 'AI翻译资讯#' . $articleId;
|
||||
|
||||
$hasPaid = false;
|
||||
if ($userId) {
|
||||
$hasPaid = Db::name('user_account_log')
|
||||
->where('user_id', $userId)
|
||||
->where('change_type', AccountLogEnum::UP_DEC_TRANSLATE_POST)
|
||||
->where('remark', $remark)
|
||||
->count() > 0;
|
||||
}
|
||||
|
||||
if ($hasPaid && $translatedContent !== '') {
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'translated_content' => $translatedContent,
|
||||
'from_cache' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($hasPaid) {
|
||||
$confirm = 1;
|
||||
}
|
||||
|
||||
if (!$confirm) {
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'needs_payment' => true,
|
||||
'cost' => $translatePoints,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if (!$userId) {
|
||||
return ['success' => false, 'error' => '请先登录'];
|
||||
}
|
||||
|
||||
if (!$hasPaid) {
|
||||
$user = User::findOrEmpty($userId);
|
||||
if ($user->isEmpty()) {
|
||||
return ['success' => false, 'error' => '用户不存在'];
|
||||
}
|
||||
if (($user->user_points ?? 0) < $translatePoints) {
|
||||
return ['success' => false, 'error' => '积分不足,当前积分' . ($user->user_points ?? 0) . ',翻译需要' . $translatePoints . '积分'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($translatedContent !== '') {
|
||||
if (!$hasPaid) {
|
||||
User::where('id', $userId)->dec('user_points', $translatePoints)->update();
|
||||
AccountLogLogic::add(
|
||||
$userId,
|
||||
AccountLogEnum::UP_DEC_TRANSLATE_POST,
|
||||
AccountLogEnum::DEC,
|
||||
$translatePoints,
|
||||
'',
|
||||
$remark
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'translated_content' => $translatedContent,
|
||||
'from_cache' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$title = trim((string) ($articleData['title'] ?? ''));
|
||||
$abstract = trim((string) ($articleData['abstract'] ?? $articleData['desc'] ?? ''));
|
||||
$content = self::normalizeContent((string) ($articleData['content'] ?? ''));
|
||||
|
||||
$result = AiService::translateArticle($title, $abstract, $content);
|
||||
if (!$result['success']) {
|
||||
return ['success' => false, 'error' => $result['error'] ?? '翻译失败'];
|
||||
}
|
||||
|
||||
$translatedContent = trim((string) ($result['content'] ?? ''));
|
||||
if ($translatedContent === '') {
|
||||
return ['success' => false, 'error' => '翻译结果为空'];
|
||||
}
|
||||
|
||||
$ext['translated_content'] = $translatedContent;
|
||||
Article::where('id', $articleId)->update([
|
||||
'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
KbSyncService::enqueue('article', 'article', $articleId, 'upsert', 45);
|
||||
|
||||
if (!$hasPaid) {
|
||||
User::where('id', $userId)->dec('user_points', $translatePoints)->update();
|
||||
AccountLogLogic::add(
|
||||
$userId,
|
||||
AccountLogEnum::UP_DEC_TRANSLATE_POST,
|
||||
AccountLogEnum::DEC,
|
||||
$translatePoints,
|
||||
'',
|
||||
$remark
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'translated_content' => $translatedContent,
|
||||
'from_cache' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 自动翻译并落库文章译文缓存
|
||||
* @param array $articleData
|
||||
* @return string
|
||||
*/
|
||||
public static function autoTranslateArticle(array $articleData): string
|
||||
{
|
||||
$articleId = (int) ($articleData['id'] ?? 0);
|
||||
if ($articleId <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ext = self::decodeExt($articleData['ext'] ?? []);
|
||||
if (!empty($ext['translated_content'])) {
|
||||
return (string) $ext['translated_content'];
|
||||
}
|
||||
|
||||
$title = trim((string) ($articleData['title'] ?? ''));
|
||||
$abstract = trim((string) ($articleData['abstract'] ?? $articleData['desc'] ?? ''));
|
||||
$content = self::normalizeContent((string) ($articleData['content'] ?? ''));
|
||||
if ($title === '' && $abstract === '' && $content === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$result = AiService::translateArticle($title, $abstract, $content);
|
||||
if (empty($result['success'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$translatedContent = trim((string) ($result['content'] ?? ''));
|
||||
if ($translatedContent === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ext['translated_content'] = $translatedContent;
|
||||
Article::where('id', $articleId)->update([
|
||||
'ext' => json_encode($ext, JSON_UNESCAPED_UNICODE),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
KbSyncService::enqueue('article', 'article', $articleId, 'upsert', 45);
|
||||
|
||||
return $translatedContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 仅读取已缓存的文章翻译内容
|
||||
* @param array $articleData
|
||||
* @return string
|
||||
*/
|
||||
public static function getCachedTranslatedContent(array $articleData): string
|
||||
{
|
||||
$ext = self::decodeExt($articleData['ext'] ?? []);
|
||||
return !empty($ext['translated_content']) ? (string) $ext['translated_content'] : '';
|
||||
}
|
||||
|
||||
protected static function getTranslatedContentForUser(int $articleId, int $userId, array $ext): string
|
||||
{
|
||||
if (empty($ext['translated_content']) || !$userId) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$hasPaid = Db::name('user_account_log')
|
||||
->where('user_id', $userId)
|
||||
->where('change_type', AccountLogEnum::UP_DEC_TRANSLATE_POST)
|
||||
->where('remark', 'AI翻译资讯#' . $articleId)
|
||||
->count() > 0;
|
||||
|
||||
return $hasPaid ? (string) $ext['translated_content'] : '';
|
||||
}
|
||||
|
||||
protected static function decodeExt($ext): array
|
||||
{
|
||||
if (is_array($ext)) {
|
||||
return $ext;
|
||||
}
|
||||
if (is_string($ext) && $ext !== '') {
|
||||
$data = json_decode($ext, true);
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected static function normalizeContent(string $content): string
|
||||
{
|
||||
if ($content === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = str_ireplace(['<br>', '<br/>', '<br />'], "\n", $content);
|
||||
$content = str_ireplace(['</p>', '</div>', '</li>', '</h1>', '</h2>', '</h3>'], "\n", $content);
|
||||
$content = strip_tags($content);
|
||||
$content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$content = preg_replace('/\r\n|\r/u', "\n", $content);
|
||||
$content = preg_replace('/\n{3,}/u', "\n\n", $content);
|
||||
|
||||
return trim($content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?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\logic;
|
||||
|
||||
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\article\Article;
|
||||
use app\common\model\decorate\DecoratePage;
|
||||
use app\common\model\decorate\DecorateTabbar;
|
||||
use app\common\service\ConfigService;
|
||||
use app\common\service\FileService;
|
||||
|
||||
|
||||
/**
|
||||
* index
|
||||
* Class IndexLogic
|
||||
* @package app\api\logic
|
||||
*/
|
||||
class IndexLogic extends BaseLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 首页数据
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 19:15
|
||||
*/
|
||||
public static function getIndexData()
|
||||
{
|
||||
// 装修配置
|
||||
$decoratePage = DecoratePage::findOrEmpty(1);
|
||||
|
||||
// 首页文章
|
||||
$field = [
|
||||
'id', 'title', 'desc', 'abstract', 'image',
|
||||
'author', 'click_actual', 'click_virtual', 'create_time'
|
||||
];
|
||||
|
||||
$article = Article::field($field)
|
||||
->where(['is_show' => 1])
|
||||
->order(['id' => 'desc'])
|
||||
->limit(20)->append(['click'])
|
||||
->hidden(['click_actual', 'click_virtual'])
|
||||
->select()->toArray();
|
||||
|
||||
return [
|
||||
'page' => $decoratePage,
|
||||
'article' => $article
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取政策协议
|
||||
* @param string $type
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 20:00
|
||||
*/
|
||||
public static function getPolicyByType(string $type)
|
||||
{
|
||||
return [
|
||||
'title' => ConfigService::get('agreement', $type . '_title', ''),
|
||||
'content' => ConfigService::get('agreement', $type . '_content', ''),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 装修信息
|
||||
* @param $id
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 18:37
|
||||
*/
|
||||
public static function getDecorate($id)
|
||||
{
|
||||
return DecoratePage::field(['type', 'name', 'data', 'meta'])
|
||||
->findOrEmpty($id)->toArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取配置
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 19:38
|
||||
*/
|
||||
public static function getConfigData()
|
||||
{
|
||||
// 底部导航
|
||||
$tabbar = DecorateTabbar::getTabbarLists();
|
||||
// 导航颜色
|
||||
$style = ConfigService::get('tabbar', 'style', config('project.decorate.tabbar_style'));
|
||||
// 登录配置
|
||||
$loginConfig = [
|
||||
// 登录方式
|
||||
'login_way' => ConfigService::get('login', 'login_way', config('project.login.login_way')),
|
||||
// 注册强制绑定手机
|
||||
'coerce_mobile' => ConfigService::get('login', 'coerce_mobile', config('project.login.coerce_mobile')),
|
||||
// 政策协议
|
||||
'login_agreement' => ConfigService::get('login', 'login_agreement', config('project.login.login_agreement')),
|
||||
// 第三方登录 开关
|
||||
'third_auth' => ConfigService::get('login', 'third_auth', config('project.login.third_auth')),
|
||||
// 微信授权登录
|
||||
'wechat_auth' => ConfigService::get('login', 'wechat_auth', config('project.login.wechat_auth')),
|
||||
// qq授权登录
|
||||
'qq_auth' => ConfigService::get('login', 'qq_auth', config('project.login.qq_auth')),
|
||||
];
|
||||
// 网址信息
|
||||
$website = [
|
||||
'h5_favicon' => FileService::getFileUrl(ConfigService::get('website', 'h5_favicon')),
|
||||
'shop_name' => ConfigService::get('website', 'shop_name'),
|
||||
'shop_logo' => FileService::getFileUrl(ConfigService::get('website', 'shop_logo')),
|
||||
];
|
||||
// H5配置
|
||||
$webPage = [
|
||||
// 渠道状态 0-关闭 1-开启
|
||||
'status' => ConfigService::get('web_page', 'status', 1),
|
||||
// 关闭后渠道后访问页面 0-空页面 1-自定义链接
|
||||
'page_status' => ConfigService::get('web_page', 'page_status', 0),
|
||||
// 自定义链接
|
||||
'page_url' => ConfigService::get('web_page', 'page_url', ''),
|
||||
'url' => request()->domain() . '/mobile'
|
||||
];
|
||||
|
||||
// 备案信息
|
||||
$copyright = ConfigService::get('copyright', 'config', []);
|
||||
|
||||
return [
|
||||
'domain' => FileService::getFileUrl(),
|
||||
'style' => $style,
|
||||
'tabbar' => $tabbar,
|
||||
'login' => $loginConfig,
|
||||
'website' => $website,
|
||||
'webPage' => $webPage,
|
||||
'version'=> config('project.version'),
|
||||
'copyright' => $copyright,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
<?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\logic;
|
||||
|
||||
use app\common\cache\WebScanLoginCache;
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\api\service\{UserTokenService, WechatUserService};
|
||||
use app\common\enum\{LoginEnum, user\UserTerminalEnum, YesNoEnum};
|
||||
use app\common\service\{
|
||||
ConfigService,
|
||||
FileService,
|
||||
wechat\WeChatConfigService,
|
||||
wechat\WeChatMnpService,
|
||||
wechat\WeChatOaService,
|
||||
wechat\WeChatRequestService
|
||||
};
|
||||
use app\common\model\user\{User, UserAuth};
|
||||
use think\facade\{Db, Config};
|
||||
|
||||
/**
|
||||
* 登录逻辑
|
||||
* Class LoginLogic
|
||||
* @package app\api\logic
|
||||
*/
|
||||
class LoginLogic extends BaseLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 账号密码注册
|
||||
* @param array $params
|
||||
* @return bool
|
||||
* @author 段誉
|
||||
* @date 2022/9/7 15:37
|
||||
*/
|
||||
public static function register(array $params)
|
||||
{
|
||||
try {
|
||||
$userSn = User::createUserSn();
|
||||
$passwordSalt = Config::get('project.unique_identification');
|
||||
$password = create_password($params['password'], $passwordSalt);
|
||||
$avatar = ConfigService::get('default_image', 'user_avatar');
|
||||
$inviteCode = User::createInviteCode();
|
||||
|
||||
$inviterId = 0;
|
||||
if (!empty($params['inviter_code'])) {
|
||||
$inviter = User::where('invite_code', $params['inviter_code'])->find();
|
||||
if ($inviter) {
|
||||
$inviterId = $inviter->id;
|
||||
}
|
||||
}
|
||||
|
||||
User::create([
|
||||
'sn' => $userSn,
|
||||
'avatar' => $avatar,
|
||||
'nickname' => '用户' . $userSn,
|
||||
'account' => $params['account'],
|
||||
'password' => $password,
|
||||
'channel' => $params['channel'],
|
||||
'invite_code' => $inviteCode,
|
||||
'inviter_id' => $inviterId,
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 账号/手机号登录,手机号验证码
|
||||
* @param $params
|
||||
* @return array|false
|
||||
* @author 段誉
|
||||
* @date 2022/9/6 19:26
|
||||
*/
|
||||
public static function login($params)
|
||||
{
|
||||
try {
|
||||
// 账号/手机号 密码登录
|
||||
$where = ['account|mobile' => $params['account']];
|
||||
if ($params['scene'] == LoginEnum::MOBILE_CAPTCHA) {
|
||||
//手机验证码登录
|
||||
$where = ['mobile' => $params['account']];
|
||||
}
|
||||
|
||||
// 验证码登录:只要上传表里存在该手机号记录即可视为通过
|
||||
$verifyScene = '';
|
||||
$smsMatched = false;
|
||||
if ($params['scene'] == LoginEnum::MOBILE_CAPTCHA) {
|
||||
$verifyInfo = \think\facade\Cache::get('sms_verify_info:' . $params['account']);
|
||||
if ($verifyInfo && is_array($verifyInfo)) {
|
||||
$verifyScene = $verifyInfo['scene'] ?? 'login';
|
||||
}
|
||||
|
||||
$userMobile = $params['account'];
|
||||
$record = \app\common\model\SmsLog::where(function ($query) use ($userMobile) {
|
||||
$query->where('phone', $userMobile)
|
||||
->whereOr('phone', '+86' . $userMobile)
|
||||
->whereOr('phone', '86' . $userMobile);
|
||||
})
|
||||
->order('sms_time', 'desc')
|
||||
->findOrEmpty();
|
||||
$smsMatched = !$record->isEmpty();
|
||||
if ($smsMatched && (int)$record->status === 0) {
|
||||
$record->status = 1;
|
||||
$record->save();
|
||||
}
|
||||
}
|
||||
|
||||
$user = User::where($where)->findOrEmpty();
|
||||
if ($user->isEmpty()) {
|
||||
// 验证码登录场景:用户不存在则自动注册,无论验证码是否匹配都允许注册
|
||||
if ($params['scene'] == LoginEnum::MOBILE_CAPTCHA) {
|
||||
// is_bind_mobile: sms_upload匹配=1,缓存码匹配但sms_upload不匹配=0,都不匹配=0
|
||||
$isPhoneReal = $smsMatched ? 1 : 0;
|
||||
$userSn = User::createUserSn();
|
||||
$avatar = ConfigService::get('default_image', 'user_avatar');
|
||||
$inviteCode = User::createInviteCode();
|
||||
$inviterId = 0;
|
||||
if (!empty($params['inviter_code'])) {
|
||||
$inviter = User::where('invite_code', $params['inviter_code'])->find();
|
||||
if ($inviter) {
|
||||
$inviterId = $inviter->id;
|
||||
}
|
||||
}
|
||||
$user = User::create([
|
||||
'sn' => $userSn,
|
||||
'avatar' => $avatar,
|
||||
'nickname' => '用户' . $userSn,
|
||||
'account' => $params['account'],
|
||||
'mobile' => $params['account'],
|
||||
'channel' => $params['terminal'],
|
||||
'invite_code' => $inviteCode,
|
||||
'inviter_id' => $inviterId,
|
||||
'is_bind_mobile' => $isPhoneReal,
|
||||
]);
|
||||
} else {
|
||||
throw new \Exception('用户不存在');
|
||||
}
|
||||
} else {
|
||||
// 已有账号:验证码登录必须通过 sms_upload 验证
|
||||
if ($params['scene'] == LoginEnum::MOBILE_CAPTCHA && !$smsMatched) {
|
||||
throw new \Exception('验证码错误,请使用密码登录');
|
||||
}
|
||||
if ($params['scene'] == LoginEnum::MOBILE_CAPTCHA && $smsMatched) {
|
||||
$user->is_bind_mobile = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证码验证成功才清除缓存
|
||||
if ($params['scene'] == LoginEnum::MOBILE_CAPTCHA && $smsMatched) {
|
||||
\think\facade\Cache::delete('sms_verify_code:' . $params['account']);
|
||||
\think\facade\Cache::delete('sms_verify_info:' . $params['account']);
|
||||
}
|
||||
|
||||
//更新登录信息
|
||||
$user->login_time = time();
|
||||
$user->login_ip = request()->ip();
|
||||
$user->save();
|
||||
|
||||
//设置token
|
||||
$userInfo = UserTokenService::setToken($user->id, $params['terminal']);
|
||||
|
||||
//返回登录信息
|
||||
$avatar = $user->avatar ?: Config::get('project.default_image.user_avatar');
|
||||
$avatar = FileService::getFileUrl($avatar);
|
||||
|
||||
return [
|
||||
'nickname' => $userInfo['nickname'],
|
||||
'sn' => $userInfo['sn'],
|
||||
'mobile' => $userInfo['mobile'],
|
||||
'avatar' => $avatar,
|
||||
'token' => $userInfo['token'],
|
||||
'is_real_phone' => $user->is_bind_mobile ?? 0,
|
||||
'verify_scene' => $verifyScene,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 退出登录
|
||||
* @param $userInfo
|
||||
* @return bool
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 17:56
|
||||
*/
|
||||
public static function logout($userInfo)
|
||||
{
|
||||
//token不存在,不注销
|
||||
if (!isset($userInfo['token'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//设置token过期
|
||||
return UserTokenService::expireToken($userInfo['token']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取微信请求code的链接
|
||||
* @param string $url
|
||||
* @return string
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:47
|
||||
*/
|
||||
public static function codeUrl(string $url)
|
||||
{
|
||||
return (new WeChatOaService())->getCodeUrl($url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 公众号登录
|
||||
* @param array $params
|
||||
* @return array|false
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:47
|
||||
*/
|
||||
public static function oaLogin(array $params)
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
//通过code获取微信 openid
|
||||
$response = (new WeChatOaService())->getOaResByCode($params['code']);
|
||||
$userServer = new WechatUserService($response, UserTerminalEnum::WECHAT_OA);
|
||||
$userInfo = $userServer->getResopnseByUserInfo()->authUserLogin()->getUserInfo();
|
||||
|
||||
// 更新登录信息
|
||||
self::updateLoginInfo($userInfo['id']);
|
||||
|
||||
Db::commit();
|
||||
return $userInfo;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 小程序-静默登录
|
||||
* @param array $params
|
||||
* @return array|false
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:47
|
||||
*/
|
||||
public static function silentLogin(array $params)
|
||||
{
|
||||
try {
|
||||
//通过code获取微信 openid
|
||||
$response = (new WeChatMnpService())->getMnpResByCode($params['code']);
|
||||
$userServer = new WechatUserService($response, UserTerminalEnum::WECHAT_MMP);
|
||||
$userInfo = $userServer->getResopnseByUserInfo('silent')->getUserInfo();
|
||||
|
||||
if (!empty($userInfo)) {
|
||||
// 更新登录信息
|
||||
self::updateLoginInfo($userInfo['id']);
|
||||
}
|
||||
|
||||
return $userInfo;
|
||||
} catch (\Exception $e) {
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 小程序-授权登录
|
||||
* @param array $params
|
||||
* @return array|false
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:47
|
||||
*/
|
||||
public static function mnpLogin(array $params)
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
//通过code获取微信 openid
|
||||
$response = (new WeChatMnpService())->getMnpResByCode($params['code']);
|
||||
$userServer = new WechatUserService($response, UserTerminalEnum::WECHAT_MMP);
|
||||
$userInfo = $userServer->getResopnseByUserInfo()->authUserLogin()->getUserInfo();
|
||||
|
||||
// 更新登录信息
|
||||
self::updateLoginInfo($userInfo['id']);
|
||||
|
||||
Db::commit();
|
||||
return $userInfo;
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 更新登录信息
|
||||
* @param $userId
|
||||
* @throws \Exception
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:46
|
||||
*/
|
||||
public static function updateLoginInfo($userId)
|
||||
{
|
||||
$user = User::findOrEmpty($userId);
|
||||
if ($user->isEmpty()) {
|
||||
throw new \Exception('用户不存在');
|
||||
}
|
||||
|
||||
$time = time();
|
||||
$user->login_time = $time;
|
||||
$user->login_ip = request()->ip();
|
||||
$user->update_time = $time;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 小程序端绑定微信
|
||||
* @param array $params
|
||||
* @return bool
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:46
|
||||
*/
|
||||
public static function mnpAuthLogin(array $params)
|
||||
{
|
||||
try {
|
||||
//通过code获取微信openid
|
||||
$response = (new WeChatMnpService())->getMnpResByCode($params['code']);
|
||||
$response['user_id'] = $params['user_id'];
|
||||
$response['terminal'] = UserTerminalEnum::WECHAT_MMP;
|
||||
|
||||
return self::createAuth($response);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 公众号端绑定微信
|
||||
* @param array $params
|
||||
* @return bool
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:43
|
||||
*/
|
||||
public static function oaAuthLogin(array $params)
|
||||
{
|
||||
try {
|
||||
//通过code获取微信openid
|
||||
$response = (new WeChatOaService())->getOaResByCode($params['code']);
|
||||
$response['user_id'] = $params['user_id'];
|
||||
$response['terminal'] = UserTerminalEnum::WECHAT_OA;
|
||||
|
||||
return self::createAuth($response);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 生成授权记录
|
||||
* @param $response
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:43
|
||||
*/
|
||||
public static function createAuth($response)
|
||||
{
|
||||
//先检查openid是否有记录
|
||||
$isAuth = UserAuth::where('openid', '=', $response['openid'])->findOrEmpty();
|
||||
if (!$isAuth->isEmpty()) {
|
||||
throw new \Exception('该微信已被绑定');
|
||||
}
|
||||
|
||||
if (isset($response['unionid']) && !empty($response['unionid'])) {
|
||||
//在用unionid找记录,防止生成两个账号,同个unionid的问题
|
||||
$userAuth = UserAuth::where(['unionid' => $response['unionid']])
|
||||
->findOrEmpty();
|
||||
if (!$userAuth->isEmpty() && $userAuth->user_id != $response['user_id']) {
|
||||
throw new \Exception('该微信已被绑定');
|
||||
}
|
||||
}
|
||||
|
||||
//如果没有授权,直接生成一条微信授权记录
|
||||
UserAuth::create([
|
||||
'user_id' => $response['user_id'],
|
||||
'openid' => $response['openid'],
|
||||
'unionid' => $response['unionid'] ?? '',
|
||||
'terminal' => $response['terminal'],
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取扫码登录地址
|
||||
* @return array|false
|
||||
* @author 段誉
|
||||
* @date 2022/10/20 18:23
|
||||
*/
|
||||
public static function getScanCode($redirectUri)
|
||||
{
|
||||
try {
|
||||
$config = WeChatConfigService::getOpConfig();
|
||||
$appId = $config['app_id'];
|
||||
$redirectUri = UrlEncode($redirectUri);
|
||||
|
||||
// 设置有效时间标记状态, 超时扫码不可登录
|
||||
$state = MD5(time() . rand(10000, 99999));
|
||||
(new WebScanLoginCache())->setScanLoginState($state);
|
||||
|
||||
// 扫码地址
|
||||
$url = WeChatRequestService::getScanCodeUrl($appId, $redirectUri, $state);
|
||||
return ['url' => $url];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 网站扫码登录
|
||||
* @param $params
|
||||
* @return array|false
|
||||
* @author 段誉
|
||||
* @date 2022/10/21 10:28
|
||||
*/
|
||||
public static function scanLogin($params)
|
||||
{
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 通过code 获取 access_token,openid,unionid等信息
|
||||
$userAuth = WeChatRequestService::getUserAuthByCode($params['code']);
|
||||
|
||||
if (empty($userAuth['openid']) || empty($userAuth['access_token'])) {
|
||||
throw new \Exception('获取用户授权信息失败');
|
||||
}
|
||||
|
||||
// 获取微信用户信息
|
||||
$response = WeChatRequestService::getUserInfoByAuth($userAuth['access_token'], $userAuth['openid']);
|
||||
|
||||
// 生成用户或更新用户信息
|
||||
$userServer = new WechatUserService($response, UserTerminalEnum::PC);
|
||||
$userInfo = $userServer->getResopnseByUserInfo()->authUserLogin()->getUserInfo();
|
||||
|
||||
// 更新登录信息
|
||||
self::updateLoginInfo($userInfo['id']);
|
||||
|
||||
Db::commit();
|
||||
return $userInfo;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 更新用户信息
|
||||
* @param $params
|
||||
* @param $userId
|
||||
* @return User
|
||||
* @author 段誉
|
||||
* @date 2023/2/22 11:19
|
||||
*/
|
||||
public static function updateUser($params, $userId)
|
||||
{
|
||||
return User::where(['id' => $userId])->update([
|
||||
'nickname' => $params['nickname'],
|
||||
'avatar' => FileService::setFileUrl($params['avatar']),
|
||||
'is_new_user' => YesNoEnum::NO
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<?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\logic;
|
||||
|
||||
|
||||
use app\common\enum\YesNoEnum;
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\article\Article;
|
||||
use app\common\model\article\ArticleCate;
|
||||
use app\common\model\article\ArticleCollect;
|
||||
use app\common\model\article\ArticleComment;
|
||||
use app\common\model\decorate\DecoratePage;
|
||||
use app\common\service\ConfigService;
|
||||
use app\common\service\FileService;
|
||||
|
||||
|
||||
/**
|
||||
* index
|
||||
* Class IndexLogic
|
||||
* @package app\api\logic
|
||||
*/
|
||||
class PcLogic extends BaseLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 首页数据
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 19:15
|
||||
*/
|
||||
public static function getIndexData()
|
||||
{
|
||||
// 装修配置
|
||||
$decoratePage = DecoratePage::findOrEmpty(4);
|
||||
// 最新资讯
|
||||
$newArticle = self::getLimitArticle('new', 7);
|
||||
// 全部资讯
|
||||
$allArticle = self::getLimitArticle('all', 5);
|
||||
// 热门资讯
|
||||
$hotArticle = self::getLimitArticle('hot', 8);
|
||||
|
||||
return [
|
||||
'page' => $decoratePage,
|
||||
'all' => $allArticle,
|
||||
'new' => $newArticle,
|
||||
'hot' => $hotArticle
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取文章
|
||||
* @param string $sortType
|
||||
* @param int $limit
|
||||
* @return mixed
|
||||
* @author 段誉
|
||||
* @date 2022/10/19 9:53
|
||||
*/
|
||||
public static function getLimitArticle(string $sortType, int $limit = 0, int $cate = 0, int $excludeId = 0)
|
||||
{
|
||||
// 查询字段
|
||||
$field = [
|
||||
'id',
|
||||
'cid',
|
||||
'title',
|
||||
'desc',
|
||||
'abstract',
|
||||
'image',
|
||||
'author',
|
||||
'click_actual',
|
||||
'click_virtual',
|
||||
'create_time'
|
||||
];
|
||||
|
||||
// 排序条件
|
||||
$orderRaw = 'published_at desc, id desc';
|
||||
if ($sortType == 'new') {
|
||||
$orderRaw = 'published_at desc, id desc';
|
||||
}
|
||||
if ($sortType == 'hot') {
|
||||
$orderRaw = 'click_actual + click_virtual desc, id desc';
|
||||
}
|
||||
|
||||
// 查询条件
|
||||
$where[] = ['is_show', '=', YesNoEnum::YES];
|
||||
if (!empty($cate)) {
|
||||
$where[] = ['cid', '=', $cate];
|
||||
}
|
||||
if (!empty($excludeId)) {
|
||||
$where[] = ['id', '<>', $excludeId];
|
||||
}
|
||||
|
||||
$article = Article::field($field)
|
||||
->withCount(['comments' => 'comment_count'])
|
||||
->where($where)
|
||||
->append(['click'])
|
||||
->orderRaw($orderRaw)
|
||||
->hidden(['click_actual', 'click_virtual']);
|
||||
|
||||
if ($limit) {
|
||||
$article->limit($limit);
|
||||
}
|
||||
|
||||
return $article->select()->toArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取配置
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 19:38
|
||||
*/
|
||||
public static function getConfigData()
|
||||
{
|
||||
// 登录配置
|
||||
$loginConfig = [
|
||||
// 登录方式
|
||||
'login_way' => ConfigService::get('login', 'login_way', config('project.login.login_way')),
|
||||
// 注册强制绑定手机
|
||||
'coerce_mobile' => ConfigService::get('login', 'coerce_mobile', config('project.login.coerce_mobile')),
|
||||
// 政策协议
|
||||
'login_agreement' => ConfigService::get('login', 'login_agreement', config('project.login.login_agreement')),
|
||||
// 第三方登录 开关
|
||||
'third_auth' => ConfigService::get('login', 'third_auth', config('project.login.third_auth')),
|
||||
// 微信授权登录
|
||||
'wechat_auth' => ConfigService::get('login', 'wechat_auth', config('project.login.wechat_auth')),
|
||||
// qq授权登录
|
||||
'qq_auth' => ConfigService::get('login', 'qq_auth', config('project.login.qq_auth')),
|
||||
];
|
||||
|
||||
// 网站信息
|
||||
$website = [
|
||||
'shop_name' => ConfigService::get('website', 'shop_name'),
|
||||
'shop_logo' => FileService::getFileUrl(ConfigService::get('website', 'shop_logo')),
|
||||
'pc_logo' => FileService::getFileUrl(ConfigService::get('website', 'pc_logo')),
|
||||
'pc_title' => ConfigService::get('website', 'pc_title'),
|
||||
'pc_ico' => FileService::getFileUrl(ConfigService::get('website', 'pc_ico')),
|
||||
'pc_desc' => ConfigService::get('website', 'pc_desc'),
|
||||
'pc_keywords' => ConfigService::get('website', 'pc_keywords'),
|
||||
];
|
||||
|
||||
// 站点统计
|
||||
$siteStatistics = [
|
||||
'clarity_code' => ConfigService::get('siteStatistics', 'clarity_code'),
|
||||
];
|
||||
|
||||
// 备案信息
|
||||
$copyright = ConfigService::get('copyright', 'config', []);
|
||||
|
||||
// 公众号二维码
|
||||
$oaQrCode = ConfigService::get('oa_setting', 'qr_code', '');
|
||||
$oaQrCode = empty($oaQrCode) ? $oaQrCode : FileService::getFileUrl($oaQrCode);
|
||||
// 小程序二维码
|
||||
$mnpQrCode = ConfigService::get('mnp_setting', 'qr_code', '');
|
||||
$mnpQrCode = empty($mnpQrCode) ? $mnpQrCode : FileService::getFileUrl($mnpQrCode);
|
||||
|
||||
return [
|
||||
'domain' => FileService::getFileUrl(),
|
||||
'login' => $loginConfig,
|
||||
'website' => $website,
|
||||
'siteStatistics' => $siteStatistics,
|
||||
'version' => config('project.version'),
|
||||
'copyright' => $copyright,
|
||||
'admin_url' => request()->domain() . '/admin',
|
||||
'qrcode' => [
|
||||
'oa' => $oaQrCode,
|
||||
'mnp' => $mnpQrCode,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 资讯中心
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/10/19 16:55
|
||||
*/
|
||||
public static function getInfoCenter()
|
||||
{
|
||||
$data = ArticleCate::field(['id', 'name'])
|
||||
->with([
|
||||
'article' => function ($query) {
|
||||
$query->hidden(['content', 'click_virtual', 'click_actual'])
|
||||
->orderRaw('published_at desc, id desc')
|
||||
->append(['click'])
|
||||
->limit(10);
|
||||
}
|
||||
])
|
||||
->where(['is_show' => YesNoEnum::YES])
|
||||
->order(['sort' => 'desc', 'id' => 'desc'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 收集所有文章ID,批量统计评论数
|
||||
$allIds = [];
|
||||
foreach ($data as $cate) {
|
||||
foreach ($cate['article'] as $article) {
|
||||
$allIds[] = $article['id'];
|
||||
}
|
||||
}
|
||||
if ($allIds) {
|
||||
$commentCounts = ArticleComment::where('delete_time', null)
|
||||
->whereIn('article_id', $allIds)
|
||||
->group('article_id')
|
||||
->column('COUNT(*)', 'article_id');
|
||||
foreach ($data as &$cate) {
|
||||
foreach ($cate['article'] as &$article) {
|
||||
$article['comment_count'] = $commentCounts[$article['id']] ?? 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取文章详情
|
||||
* @param $userId
|
||||
* @param $articleId
|
||||
* @param string $source
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2022/10/20 15:18
|
||||
*/
|
||||
public static function getArticleDetail($userId, $articleId, $source = 'default')
|
||||
{
|
||||
// 文章详情
|
||||
$detail = Article::getArticleDetailArr($articleId);
|
||||
|
||||
// 根据来源列表查找对应列表
|
||||
$nowIndex = 0;
|
||||
$lists = self::getLimitArticle($source, 0, $detail['cid']);
|
||||
foreach ($lists as $key => $item) {
|
||||
if ($item['id'] == $articleId) {
|
||||
$nowIndex = $key;
|
||||
}
|
||||
}
|
||||
// 上一篇
|
||||
$detail['last'] = $lists[$nowIndex - 1] ?? [];
|
||||
// 下一篇
|
||||
$detail['next'] = $lists[$nowIndex + 1] ?? [];
|
||||
|
||||
// 最新资讯
|
||||
$detail['new'] = self::getLimitArticle('new', 8, $detail['cid'], $detail['id']);
|
||||
// 关注状态
|
||||
$detail['collect'] = ArticleCollect::isCollectArticle($userId, $articleId);
|
||||
// 分类名
|
||||
$detail['cate_name'] = ArticleCate::where('id', $detail['cid'])->value('name');
|
||||
|
||||
return $detail;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\logic;
|
||||
|
||||
use app\common\enum\PayEnum;
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\points\PointsOrder;
|
||||
use app\common\model\points\PointsProduct;
|
||||
|
||||
class PointsOrderLogic extends BaseLogic
|
||||
{
|
||||
public static function products(): array
|
||||
{
|
||||
return PointsProduct::where('status', 1)
|
||||
->order('sort', 'desc')
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
private static function makeSign(int $productId, int $points, string $amount): string
|
||||
{
|
||||
$secret = env('APP_KEY', 'likeadmin');
|
||||
return md5("pid={$productId}&pts={$points}&amt={$amount}&key={$secret}");
|
||||
}
|
||||
|
||||
public static function preOrder(array $params)
|
||||
{
|
||||
try {
|
||||
$product = PointsProduct::where(['id' => $params['product_id'], 'status' => 1])->findOrEmpty();
|
||||
if ($product->isEmpty()) {
|
||||
throw new \Exception('积分套餐不存在或已下架');
|
||||
}
|
||||
$amount = (string) $product['amount'];
|
||||
return [
|
||||
'product_id' => (int) $product['id'],
|
||||
'name' => $product['name'],
|
||||
'points' => (int) $product['points'],
|
||||
'amount' => $amount,
|
||||
'sign' => self::makeSign((int) $product['id'], (int) $product['points'], $amount),
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function createOrder(array $params)
|
||||
{
|
||||
try {
|
||||
$product = PointsProduct::where(['id' => $params['product_id'], 'status' => 1])->findOrEmpty();
|
||||
if ($product->isEmpty()) {
|
||||
throw new \Exception('积分套餐不存在或已下架');
|
||||
}
|
||||
$order = PointsOrder::create([
|
||||
'sn' => generate_sn(PointsOrder::class, 'sn'),
|
||||
'user_id' => $params['user_id'],
|
||||
'product_id' => $product['id'],
|
||||
'product_name' => $product['name'],
|
||||
'points' => $product['points'],
|
||||
'order_amount' => $product['amount'],
|
||||
'pay_status' => PayEnum::UNPAID,
|
||||
'order_terminal' => $params['terminal'],
|
||||
]);
|
||||
return [
|
||||
'order_id' => (int) $order['id'],
|
||||
'from' => 'points_order'
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function detail(array $params)
|
||||
{
|
||||
try {
|
||||
$order = PointsOrder::where(['id' => $params['order_id'], 'user_id' => $params['user_id']])
|
||||
->append(['pay_status_text', 'pay_way_text'])
|
||||
->findOrEmpty();
|
||||
if ($order->isEmpty()) {
|
||||
throw new \Exception('订单不存在');
|
||||
}
|
||||
$data = $order->toArray();
|
||||
$data['pay_time'] = empty($data['pay_time']) ? '' : date('Y-m-d H:i:s', $data['pay_time']);
|
||||
return $data;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?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\logic;
|
||||
|
||||
use app\common\enum\PayEnum;
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\recharge\RechargeOrder;
|
||||
use app\common\model\user\User;
|
||||
use app\common\service\ConfigService;
|
||||
|
||||
|
||||
/**
|
||||
* 充值逻辑层
|
||||
* Class RechargeLogic
|
||||
* @package app\shopapi\logic
|
||||
*/
|
||||
class RechargeLogic extends BaseLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 充值
|
||||
* @param array $params
|
||||
* @return array|false
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 10:43
|
||||
*/
|
||||
public static function recharge(array $params)
|
||||
{
|
||||
try {
|
||||
$data = [
|
||||
'sn' => generate_sn(RechargeOrder::class, 'sn'),
|
||||
'order_terminal' => $params['terminal'],
|
||||
'user_id' => $params['user_id'],
|
||||
'pay_status' => PayEnum::UNPAID,
|
||||
'order_amount' => $params['money'],
|
||||
];
|
||||
$order = RechargeOrder::create($data);
|
||||
|
||||
return [
|
||||
'order_id' => (int)$order['id'],
|
||||
'from' => 'recharge'
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 充值配置
|
||||
* @param $userId
|
||||
* @return array
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 16:56
|
||||
*/
|
||||
public static function config($userId)
|
||||
{
|
||||
$userMoney = User::where(['id' => $userId])->value('user_money');
|
||||
$minAmount = ConfigService::get('recharge', 'min_amount', 0);
|
||||
$status = ConfigService::get('recharge', 'status', 0);
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'min_amount' => $minAmount,
|
||||
'user_money' => $userMoney,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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\logic;
|
||||
|
||||
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\HotSearch;
|
||||
use app\common\service\ConfigService;
|
||||
|
||||
/**
|
||||
* 搜索逻辑
|
||||
* Class SearchLogic
|
||||
* @package app\api\logic
|
||||
*/
|
||||
class SearchLogic extends BaseLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 热搜列表
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/23 14:34
|
||||
*/
|
||||
public static function hotLists()
|
||||
{
|
||||
$data = HotSearch::field(['name', 'sort'])
|
||||
->order(['sort' => 'desc', 'id' => 'desc'])
|
||||
->select()->toArray();
|
||||
|
||||
return [
|
||||
// 功能状态 0-关闭 1-开启
|
||||
'status' => ConfigService::get('hot_search', 'status', 0),
|
||||
// 热门搜索数据
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?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\logic;
|
||||
|
||||
use app\common\enum\notice\NoticeEnum;
|
||||
use app\common\logic\BaseLogic;
|
||||
|
||||
|
||||
/**
|
||||
* 短信逻辑
|
||||
* Class SmsLogic
|
||||
* @package app\api\logic
|
||||
*/
|
||||
class SmsLogic extends BaseLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 发送验证码
|
||||
* @param $params
|
||||
* @return false|mixed
|
||||
* @author 段誉
|
||||
* @date 2022/9/15 16:17
|
||||
*/
|
||||
public static function sendCode($params)
|
||||
{
|
||||
try {
|
||||
$scene = NoticeEnum::getSceneByTag($params['scene']);
|
||||
if (empty($scene)) {
|
||||
throw new \Exception('场景值异常');
|
||||
}
|
||||
|
||||
$result = event('Notice', [
|
||||
'scene_id' => $scene,
|
||||
'params' => [
|
||||
'mobile' => $params['mobile'],
|
||||
'code' => mt_rand(1000, 9999),
|
||||
]
|
||||
]);
|
||||
|
||||
return $result[0];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin蹇€熷紑鍙戝墠鍚庣鍒嗙绠$悊鍚庡彴锛圥HP鐗堬級
|
||||
// +----------------------------------------------------------------------
|
||||
// | 娆㈣繋闃呰瀛︿範绯荤粺绋嬪簭浠g爜锛屽缓璁弽棣堟槸鎴戜滑鍓嶈繘鐨勫姩鍔?
|
||||
// | 寮€婧愮増鏈彲鑷敱鍟嗙敤锛屽彲鍘婚櫎鐣岄潰鐗堟潈logo
|
||||
// | gitee涓嬭浇锛歨ttps://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github涓嬭浇锛歨ttps://github.com/likeshop-github/likeadmin
|
||||
// | 璁块棶瀹樼綉锛歨ttps://www.likeadmin.cn
|
||||
// | likeadmin鍥㈤槦 鐗堟潈鎵€鏈?鎷ユ湁鏈€缁堣В閲婃潈
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\api\logic;
|
||||
|
||||
|
||||
use app\common\{
|
||||
enum\notice\NoticeEnum,
|
||||
enum\user\UserTerminalEnum,
|
||||
enum\YesNoEnum,
|
||||
logic\BaseLogic,
|
||||
model\SmsLog,
|
||||
model\user\User,
|
||||
model\user\UserAuth,
|
||||
service\FileService,
|
||||
service\sms\SmsDriver,
|
||||
service\wechat\WeChatMnpService
|
||||
};
|
||||
use think\facade\Config;
|
||||
|
||||
/**
|
||||
* 浼氬憳閫昏緫灞?
|
||||
* Class UserLogic
|
||||
* @package app\shopapi\logic
|
||||
*/
|
||||
class UserLogic extends BaseLogic
|
||||
{
|
||||
|
||||
private static function findLatestUploadRecord(string $mobile)
|
||||
{
|
||||
return SmsLog::where(function ($query) use ($mobile) {
|
||||
$query->where('phone', $mobile)
|
||||
->whereOr('phone', '+86' . $mobile)
|
||||
->whereOr('phone', '86' . $mobile);
|
||||
})
|
||||
->order('sms_time', 'desc')
|
||||
->findOrEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 涓汉涓績
|
||||
* @param array $userInfo
|
||||
* @return array
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 娈佃獕
|
||||
* @date 2022/9/16 18:04
|
||||
*/
|
||||
public static function center(array $userInfo): array
|
||||
{
|
||||
if (empty($userInfo['user_id'])) {
|
||||
return [];
|
||||
}
|
||||
$user = User::where(['id' => $userInfo['user_id']])
|
||||
->field('id,sn,sex,account,nickname,real_name,avatar,mobile,create_time,is_new_user,user_money,password,is_vip,vip_level,vip_expire_time,ai_free_count,ai_free_date,is_bind_mobile')
|
||||
->findOrEmpty();
|
||||
if (!$user->isEmpty() && !empty($user->mobile) && ((int) $user->is_bind_mobile !== 1 || (int) $user->is_new_user !== 1)) {
|
||||
$uploadRecord = self::findLatestUploadRecord((string) $user->mobile);
|
||||
if (!$uploadRecord->isEmpty()) {
|
||||
$user->is_bind_mobile = 1;
|
||||
$user->is_new_user = 1;
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($userInfo['terminal'], [UserTerminalEnum::WECHAT_MMP, UserTerminalEnum::WECHAT_OA])) {
|
||||
$auth = UserAuth::where(['user_id' => $userInfo['user_id'], 'terminal' => $userInfo['terminal']])->find();
|
||||
$user['is_auth'] = $auth ? YesNoEnum::YES : YesNoEnum::NO;
|
||||
}
|
||||
|
||||
$user['has_password'] = !empty($user['password']);
|
||||
$vipInfo = $user->getVipInfo();
|
||||
$user->hidden(['password', 'is_vip', 'vip_level', 'vip_expire_time', 'ai_free_count', 'ai_free_date']);
|
||||
$result = $user->toArray();
|
||||
$result['vip_info'] = $vipInfo;
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 涓汉淇℃伅
|
||||
* @param $userId
|
||||
* @return array
|
||||
* @author 娈佃獕
|
||||
* @date 2022/9/20 19:45
|
||||
*/
|
||||
public static function info(int $userId)
|
||||
{
|
||||
$user = User::where(['id' => $userId])
|
||||
->field('id,sn,sex,account,password,nickname,real_name,avatar,mobile,create_time,user_money,is_vip,vip_level,vip_expire_time,ai_free_count,ai_free_date')
|
||||
->findOrEmpty();
|
||||
$user['has_password'] = !empty($user['password']);
|
||||
$user['has_auth'] = self::hasWechatAuth($userId);
|
||||
$user['version'] = config('project.version');
|
||||
$vipInfo = $user->getVipInfo();
|
||||
$user->hidden(['password', 'is_vip', 'vip_level', 'vip_expire_time', 'ai_free_count', 'ai_free_date']);
|
||||
$result = $user->toArray();
|
||||
$result['vip_info'] = $vipInfo;
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 璁剧疆鐢ㄦ埛淇℃伅
|
||||
* @param int $userId
|
||||
* @param array $params
|
||||
* @return User|false
|
||||
* @author 娈佃獕
|
||||
* @date 2022/9/21 16:53
|
||||
*/
|
||||
public static function setInfo(int $userId, array $params)
|
||||
{
|
||||
try {
|
||||
if ($params['field'] == "avatar") {
|
||||
$params['value'] = FileService::setFileUrl($params['value']);
|
||||
}
|
||||
|
||||
return User::update(
|
||||
[
|
||||
'id' => $userId,
|
||||
$params['field'] => $params['value']
|
||||
]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 鏄惁鏈夊井淇℃巿鏉冧俊鎭?
|
||||
* @param $userId
|
||||
* @return bool
|
||||
* @author 娈佃獕
|
||||
* @date 2022/9/20 19:36
|
||||
*/
|
||||
public static function hasWechatAuth(int $userId)
|
||||
{
|
||||
//鏄惁鏈夊井淇℃巿鏉冪櫥褰?
|
||||
$terminal = [UserTerminalEnum::WECHAT_MMP, UserTerminalEnum::WECHAT_OA, UserTerminalEnum::PC];
|
||||
$auth = UserAuth::where(['user_id' => $userId])
|
||||
->whereIn('terminal', $terminal)
|
||||
->findOrEmpty();
|
||||
return !$auth->isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 閲嶇疆鐧诲綍瀵嗙爜
|
||||
* @param $params
|
||||
* @return bool
|
||||
* @author 娈佃獕
|
||||
* @date 2022/9/16 18:06
|
||||
*/
|
||||
public static function resetPassword(array $params)
|
||||
{
|
||||
try {
|
||||
// 鏍¢獙楠岃瘉鐮?
|
||||
$smsDriver = new SmsDriver();
|
||||
if (!$smsDriver->verify($params['mobile'], $params['code'], NoticeEnum::FIND_LOGIN_PASSWORD_CAPTCHA)) {
|
||||
throw new \Exception('verification code error');
|
||||
}
|
||||
|
||||
// 閲嶇疆瀵嗙爜
|
||||
$passwordSalt = Config::get('project.unique_identification');
|
||||
$password = create_password($params['password'], $passwordSalt);
|
||||
|
||||
// 鏇存柊
|
||||
User::where('mobile', $params['mobile'])->update([
|
||||
'password' => $password
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 淇瀵嗙爜
|
||||
* @param $params
|
||||
* @param $userId
|
||||
* @return bool
|
||||
* @author 娈佃獕
|
||||
* @date 2022/9/20 19:13
|
||||
*/
|
||||
public static function changePassword(array $params, int $userId)
|
||||
{
|
||||
try {
|
||||
$user = User::findOrEmpty($userId);
|
||||
if ($user->isEmpty()) {
|
||||
throw new \Exception('user not found');
|
||||
}
|
||||
|
||||
// 瀵嗙爜鐩?
|
||||
$passwordSalt = Config::get('project.unique_identification');
|
||||
|
||||
if (!empty($user['password'])) {
|
||||
if (empty($params['old_password'])) {
|
||||
throw new \Exception('璇峰~鍐欐棫瀵嗙爜');
|
||||
}
|
||||
$oldPassword = create_password($params['old_password'], $passwordSalt);
|
||||
if ($oldPassword != $user['password']) {
|
||||
throw new \Exception('鍘熷瘑鐮佷笉姝g‘');
|
||||
}
|
||||
}
|
||||
|
||||
// 淇濆瓨瀵嗙爜
|
||||
$password = create_password($params['password'], $passwordSalt);
|
||||
$user->password = $password;
|
||||
$user->save();
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 鑾峰彇灏忕▼搴忔墜鏈哄彿
|
||||
* @param array $params
|
||||
* @return bool
|
||||
* @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
|
||||
* @author 娈佃獕
|
||||
* @date 2023/2/27 11:49
|
||||
*/
|
||||
public static function getMobileByMnp(array $params)
|
||||
{
|
||||
try {
|
||||
$response = (new WeChatMnpService())->getUserPhoneNumber($params['code']);
|
||||
$phoneNumber = $response['phone_info']['purePhoneNumber'] ?? '';
|
||||
if (empty($phoneNumber)) {
|
||||
throw new \Exception('鑾峰彇鎵嬫満鍙风爜澶辫触');
|
||||
}
|
||||
|
||||
$user = User::where([
|
||||
['mobile', '=', $phoneNumber],
|
||||
['id', '<>', $params['user_id']]
|
||||
])->findOrEmpty();
|
||||
|
||||
if (!$user->isEmpty()) {
|
||||
throw new \Exception('mobile number already bound to another account');
|
||||
}
|
||||
|
||||
// 缁戝畾鎵嬫満鍙?
|
||||
User::update([
|
||||
'id' => $params['user_id'],
|
||||
'mobile' => $phoneNumber
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 缁戝畾鎵嬫満鍙?
|
||||
* @param $params
|
||||
* @return bool
|
||||
* @author 娈佃獕
|
||||
* @date 2022/9/21 17:28
|
||||
*/
|
||||
public static function bindMobile(array $params)
|
||||
{
|
||||
try {
|
||||
// 妫€鏌ユ墜鏈哄彿鏄惁宸茶鍏朵粬鐢ㄦ埛鍗犵敤
|
||||
$existUser = User::where('mobile', $params['mobile'])
|
||||
->where('id', '<>', $params['user_id'])
|
||||
->findOrEmpty();
|
||||
if (!$existUser->isEmpty()) {
|
||||
throw new \Exception('璇ユ墜鏈哄彿宸茶鍏朵粬鐢ㄦ埛浣跨敤');
|
||||
}
|
||||
|
||||
// 鏍¢獙鐭俊锛氫粠缂撳瓨鏍¢獙楠岃瘉鐮侊紝鐒跺悗鍙涓婁紶琛ㄥ瓨鍦ㄨ鎵嬫満鍙疯褰曞嵆瑙嗕负閫氳繃
|
||||
$cacheKey = 'sms_verify_code:' . $params['mobile'];
|
||||
$cached = \think\facade\Cache::get($cacheKey);
|
||||
if (!$cached || $cached['code'] != $params['code']) {
|
||||
throw new \Exception('verification code error');
|
||||
}
|
||||
|
||||
// sms_upload.phone 鏄彂閫佹柟锛堢敤鎴锋墜鏈哄彿锛夛紝鐢ㄧ敤鎴锋墜鏈哄彿鏌ヨ
|
||||
$userMobile = $params['mobile'];
|
||||
$record = \app\common\model\SmsLog::where(function ($query) use ($userMobile) {
|
||||
$query->where('phone', $userMobile)
|
||||
->whereOr('phone', '+86' . $userMobile)
|
||||
->whereOr('phone', '86' . $userMobile);
|
||||
})
|
||||
->order('sms_time', 'desc')
|
||||
->findOrEmpty();
|
||||
|
||||
if ($record->isEmpty()) {
|
||||
throw new \Exception('鐭俊鍔╂墜璁板綍涓湭鎵惧埌璇ユ墜鏈哄彿');
|
||||
}
|
||||
|
||||
if ((int) $record->status === 0) {
|
||||
$record->status = 1;
|
||||
$record->save();
|
||||
}
|
||||
\think\facade\Cache::delete($cacheKey);
|
||||
|
||||
User::update([
|
||||
'id' => $params['user_id'],
|
||||
'mobile' => $params['mobile'],
|
||||
'is_bind_mobile' => 1
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\logic;
|
||||
|
||||
use app\common\enum\PayEnum;
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\user\User;
|
||||
use app\common\model\vip\VipLevel;
|
||||
use app\common\model\vip\VipOrder;
|
||||
|
||||
class VipOrderLogic extends BaseLogic
|
||||
{
|
||||
public static function levels(): array
|
||||
{
|
||||
return VipLevel::where('status', 1)
|
||||
->order('sort', 'desc')
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public static function createOrder(array $params)
|
||||
{
|
||||
try {
|
||||
$level = VipLevel::where(['id' => $params['level_id'], 'status' => 1])->findOrEmpty();
|
||||
if ($level->isEmpty()) {
|
||||
throw new \Exception('VIP套餐不存在或已下架');
|
||||
}
|
||||
|
||||
// 有效期内不能重复购买同一等级
|
||||
$user = User::findOrEmpty($params['user_id']);
|
||||
if ($user->is_vip == 1 && $user->vip_expire_time > time() && $user->vip_level == $level['level']) {
|
||||
throw new \Exception('您当前已是' . $level['name'] . ',有效期内无需重复购买');
|
||||
}
|
||||
|
||||
$order = VipOrder::create([
|
||||
'sn' => generate_sn(VipOrder::class, 'sn'),
|
||||
'user_id' => $params['user_id'],
|
||||
'level_id' => $level['id'],
|
||||
'level_name' => $level['name'],
|
||||
'duration' => $level['duration'],
|
||||
'order_amount' => $level['price'],
|
||||
'pay_status' => PayEnum::UNPAID,
|
||||
'order_terminal' => $params['terminal'],
|
||||
]);
|
||||
return [
|
||||
'order_id' => (int) $order['id'],
|
||||
'from' => 'vip_order'
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function detail(array $params)
|
||||
{
|
||||
try {
|
||||
$order = VipOrder::where(['id' => $params['order_id'], 'user_id' => $params['user_id']])
|
||||
->append(['pay_status_text', 'pay_way_text'])
|
||||
->findOrEmpty();
|
||||
if ($order->isEmpty()) {
|
||||
throw new \Exception('订单不存在');
|
||||
}
|
||||
$data = $order->toArray();
|
||||
$data['pay_time'] = empty($data['pay_time']) ? '' : date('Y-m-d H:i:s', $data['pay_time']);
|
||||
return $data;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?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\logic;
|
||||
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\service\wechat\WeChatOaService;
|
||||
use EasyWeChat\Kernel\Exceptions\Exception;
|
||||
|
||||
/**
|
||||
* 微信
|
||||
* Class WechatLogic
|
||||
* @package app\api\logic
|
||||
*/
|
||||
class WechatLogic extends BaseLogic
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 微信JSSDK授权接口
|
||||
* @param $params
|
||||
* @return false|mixed[]
|
||||
* @throws \Psr\SimpleCache\InvalidArgumentException
|
||||
* @throws \Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface
|
||||
* @throws \Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface
|
||||
* @throws \Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface
|
||||
* @throws \Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface
|
||||
* @throws \Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 11:49
|
||||
*/
|
||||
public static function jsConfig($params)
|
||||
{
|
||||
try {
|
||||
$url = urldecode($params['url']);
|
||||
return (new WeChatOaService())->getJsConfig($url, [
|
||||
'onMenuShareTimeline',
|
||||
'onMenuShareAppMessage',
|
||||
'onMenuShareQQ',
|
||||
'onMenuShareWeibo',
|
||||
'onMenuShareQZone',
|
||||
'openLocation',
|
||||
'getLocation',
|
||||
'chooseWXPay',
|
||||
'updateAppMessageShareData',
|
||||
'updateTimelineShareData',
|
||||
'openAddress',
|
||||
'scanQRCode'
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
self::setError('获取jssdk失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?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\service;
|
||||
|
||||
use app\common\cache\UserTokenCache;
|
||||
use app\common\model\user\UserSession;
|
||||
use think\facade\Config;
|
||||
|
||||
class UserTokenService
|
||||
{
|
||||
|
||||
/**
|
||||
* @notes 设置或更新用户token
|
||||
* @param $userId
|
||||
* @param $terminal
|
||||
* @return array|false|mixed
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:10
|
||||
*/
|
||||
public static function setToken($userId, $terminal)
|
||||
{
|
||||
$time = time();
|
||||
$userSession = UserSession::where([['user_id', '=', $userId], ['terminal', '=', $terminal]])->find();
|
||||
|
||||
//获取token延长过期的时间
|
||||
$expireTime = $time + Config::get('project.user_token.expire_duration');
|
||||
$userTokenCache = new UserTokenCache();
|
||||
|
||||
//token处理
|
||||
if ($userSession) {
|
||||
//清空缓存
|
||||
$userTokenCache->deleteUserInfo($userSession->token);
|
||||
//重新获取token
|
||||
$userSession->token = create_token($userId);
|
||||
$userSession->expire_time = $expireTime;
|
||||
$userSession->update_time = $time;
|
||||
$userSession->save();
|
||||
} else {
|
||||
//找不到在该终端的token记录,创建token记录
|
||||
$userSession = UserSession::create([
|
||||
'user_id' => $userId,
|
||||
'terminal' => $terminal,
|
||||
'token' => create_token($userId),
|
||||
'expire_time' => $expireTime
|
||||
]);
|
||||
}
|
||||
|
||||
return $userTokenCache->setUserInfo($userSession->token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 延长token过期时间
|
||||
* @param $token
|
||||
* @return array|false|mixed
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:10
|
||||
*/
|
||||
public static function overtimeToken($token)
|
||||
{
|
||||
$time = time();
|
||||
$userSession = UserSession::where('token', '=', $token)->find();
|
||||
if ($userSession->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
//延长token过期时间
|
||||
$userSession->expire_time = $time + Config::get('project.user_token.expire_duration');
|
||||
$userSession->update_time = $time;
|
||||
$userSession->save();
|
||||
|
||||
return (new UserTokenCache())->setUserInfo($userSession->token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 设置token为过期
|
||||
* @param $token
|
||||
* @return bool
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:10
|
||||
*/
|
||||
public static function expireToken($token)
|
||||
{
|
||||
$userSession = UserSession::where('token', '=', $token)
|
||||
->find();
|
||||
if (empty($userSession)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$time = time();
|
||||
$userSession->expire_time = $time;
|
||||
$userSession->update_time = $time;
|
||||
$userSession->save();
|
||||
|
||||
return (new UserTokenCache())->deleteUserInfo($token);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?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\service;
|
||||
|
||||
|
||||
use app\common\enum\YesNoEnum;
|
||||
use app\common\model\user\{User, UserAuth};
|
||||
use app\common\enum\user\UserTerminalEnum;
|
||||
use app\common\service\{ConfigService, storage\Driver as StorageDriver};
|
||||
use think\Exception;
|
||||
|
||||
|
||||
/**
|
||||
* 用户功能类(主要微信登录后创建和更新用户)
|
||||
* Class WechatUserService
|
||||
* @package app\api\service
|
||||
*/
|
||||
class WechatUserService
|
||||
{
|
||||
|
||||
protected int $terminal = UserTerminalEnum::WECHAT_MMP;
|
||||
protected array $response = [];
|
||||
protected ?string $code = null;
|
||||
protected ?string $openid = null;
|
||||
protected ?string $unionid = null;
|
||||
protected ?string $nickname = null;
|
||||
protected ?string $headimgurl = null;
|
||||
protected User $user;
|
||||
|
||||
|
||||
public function __construct(array $response, int $terminal)
|
||||
{
|
||||
$this->terminal = $terminal;
|
||||
$this->setParams($response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 设置微信返回的用户信息
|
||||
* @param $response
|
||||
* @author cjhao
|
||||
* @date 2021/8/2 11:49
|
||||
*/
|
||||
private function setParams($response): void
|
||||
{
|
||||
$this->response = $response;
|
||||
$this->openid = $response['openid'];
|
||||
$this->unionid = $response['unionid'] ?? '';
|
||||
$this->nickname = $response['nickname'] ?? '';
|
||||
$this->headimgurl = $response['headimgurl'] ?? '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 根据opendid或unionid获取系统用户信息
|
||||
* @return $this
|
||||
* @author 段誉
|
||||
* @date 2022/9/23 16:09
|
||||
*/
|
||||
public function getResopnseByUserInfo(): self
|
||||
{
|
||||
$openid = $this->openid;
|
||||
$unionid = $this->unionid;
|
||||
|
||||
$user = User::alias('u')
|
||||
->field('u.id,u.sn,u.mobile,u.nickname,u.avatar,u.mobile,u.is_disable,u.is_new_user')
|
||||
->join('user_auth au', 'au.user_id = u.id')
|
||||
->where(function ($query) use ($openid, $unionid) {
|
||||
$query->whereOr(['au.openid' => $openid]);
|
||||
if (isset($unionid) && $unionid) {
|
||||
$query->whereOr(['au.unionid' => $unionid]);
|
||||
}
|
||||
})
|
||||
->findOrEmpty();
|
||||
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取用户信息
|
||||
* @param bool $isCheck 是否验证账号是否可用
|
||||
* @return array
|
||||
* @throws Exception
|
||||
* @author cjhao
|
||||
* @date 2021/8/3 11:42
|
||||
*/
|
||||
public function getUserInfo($isCheck = true): array
|
||||
{
|
||||
if (!$this->user->isEmpty() && $isCheck) {
|
||||
$this->checkAccount();
|
||||
}
|
||||
if (!$this->user->isEmpty()) {
|
||||
$this->getToken();
|
||||
}
|
||||
return $this->user->toArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 校验账号
|
||||
* @throws Exception
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:14
|
||||
*/
|
||||
private function checkAccount()
|
||||
{
|
||||
if ($this->user->is_disable) {
|
||||
throw new Exception('您的账号异常,请联系客服。');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 创建用户
|
||||
* @throws Exception
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:06
|
||||
*/
|
||||
private function createUser(): void
|
||||
{
|
||||
//设置头像
|
||||
if (empty($this->headimgurl)) {
|
||||
// 默认头像
|
||||
$defaultAvatar = config('project.default_image.user_avatar');
|
||||
$avatar = ConfigService::get('default_image', 'user_avatar', $defaultAvatar);
|
||||
} else {
|
||||
// 微信获取到的头像信息
|
||||
$avatar = $this->getAvatarByWechat();
|
||||
}
|
||||
|
||||
$userSn = User::createUserSn();
|
||||
$this->user->sn = $userSn;
|
||||
$this->user->account = 'u' . $userSn;
|
||||
$this->user->nickname = "用户" . $userSn;
|
||||
$this->user->avatar = $avatar;
|
||||
$this->user->channel = $this->terminal;
|
||||
$this->user->is_new_user = YesNoEnum::YES;
|
||||
|
||||
if ($this->terminal != UserTerminalEnum::WECHAT_MMP && !empty($this->nickname)) {
|
||||
$this->user->nickname = $this->nickname;
|
||||
}
|
||||
|
||||
$this->user->save();
|
||||
|
||||
UserAuth::create([
|
||||
'user_id' => $this->user->id,
|
||||
'openid' => $this->openid,
|
||||
'unionid' => $this->unionid,
|
||||
'terminal' => $this->terminal,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 更新用户信息
|
||||
* @throws Exception
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:06
|
||||
* @remark 该端没授权信息,重新写入一条该端的授权信息
|
||||
*/
|
||||
private function updateUser(): void
|
||||
{
|
||||
// 无头像需要更新头像
|
||||
if (empty($this->user->avatar)) {
|
||||
$this->user->avatar = $this->getAvatarByWechat();
|
||||
$this->user->save();
|
||||
}
|
||||
|
||||
$userAuth = UserAuth::where(['user_id' => $this->user->id, 'openid' => $this->openid])
|
||||
->findOrEmpty();
|
||||
|
||||
// 无该端授权信息,新增一条
|
||||
if ($userAuth->isEmpty()) {
|
||||
$userAuth->user_id = $this->user->id;
|
||||
$userAuth->openid = $this->openid;
|
||||
$userAuth->unionid = $this->unionid;
|
||||
$userAuth->terminal = $this->terminal;
|
||||
$userAuth->save();
|
||||
} else {
|
||||
if (empty($userAuth['unionid']) && !empty($this->unionid)) {
|
||||
$userAuth->unionid = $this->unionid;
|
||||
$userAuth->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取token
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\DbException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @author cjhao
|
||||
* @date 2021/8/2 16:45
|
||||
*/
|
||||
private function getToken(): void
|
||||
{
|
||||
$user = UserTokenService::setToken($this->user->id, $this->terminal);
|
||||
$this->user->token = $user['token'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 用户授权登录,
|
||||
* 如果用户不存在,创建用户;用户存在,更新用户信息,并检查该端信息是否需要写入
|
||||
* @return WechatUserService
|
||||
* @throws Exception
|
||||
* @author cjhao
|
||||
* @date 2021/8/2 16:35
|
||||
*/
|
||||
public function authUserLogin(): self
|
||||
{
|
||||
if ($this->user->isEmpty()) {
|
||||
$this->createUser();
|
||||
} else {
|
||||
$this->updateUser();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 处理从微信获取到的头像信息
|
||||
* @return string
|
||||
* @throws Exception
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 9:50
|
||||
*/
|
||||
public function getAvatarByWechat(): string
|
||||
{
|
||||
// 存储引擎
|
||||
$config = [
|
||||
'default' => ConfigService::get('storage', 'default', 'local'),
|
||||
'engine' => ConfigService::get('storage')
|
||||
];
|
||||
|
||||
$fileName = md5($this->openid . time()) . '.jpeg';
|
||||
|
||||
if ($config['default'] == 'local') {
|
||||
// 本地存储
|
||||
$avatar = download_file($this->headimgurl, 'uploads/user/avatar/', $fileName);
|
||||
} else {
|
||||
// 第三方存储
|
||||
$avatar = 'uploads/user/avatar/' . $fileName;
|
||||
$StorageDriver = new StorageDriver($config);
|
||||
if (!$StorageDriver->fetch($this->headimgurl, $avatar)) {
|
||||
throw new Exception('头像保存失败:' . $StorageDriver->getError());
|
||||
}
|
||||
}
|
||||
return $avatar;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
<?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\validate;
|
||||
|
||||
use app\common\cache\UserAccountSafeCache;
|
||||
use app\common\enum\LoginEnum;
|
||||
use app\common\enum\notice\NoticeEnum;
|
||||
use app\common\enum\user\UserTerminalEnum;
|
||||
use app\common\enum\YesNoEnum;
|
||||
use app\common\model\SmsLog;
|
||||
use app\common\service\ConfigService;
|
||||
use app\common\service\sms\SmsDriver;
|
||||
use app\common\validate\BaseValidate;
|
||||
use app\common\model\user\User;
|
||||
use think\facade\Config;
|
||||
|
||||
/**
|
||||
* 账号密码登录校验
|
||||
* Class LoginValidate
|
||||
* @package app\api\validate
|
||||
*/
|
||||
class LoginAccountValidate extends BaseValidate
|
||||
{
|
||||
|
||||
protected $rule = [
|
||||
'terminal' => 'require|in:' . UserTerminalEnum::WECHAT_MMP . ',' . UserTerminalEnum::WECHAT_OA . ','
|
||||
. UserTerminalEnum::H5 . ',' . UserTerminalEnum::PC . ',' . UserTerminalEnum::IOS .
|
||||
',' . UserTerminalEnum::ANDROID,
|
||||
'scene' => 'require|in:' . LoginEnum::ACCOUNT_PASSWORD . ',' . LoginEnum::MOBILE_CAPTCHA . '|checkConfig',
|
||||
'account' => 'require',
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
'terminal.require' => '终端参数缺失',
|
||||
'terminal.in' => '终端参数状态值不正确',
|
||||
'scene.require' => '场景不能为空',
|
||||
'scene.in' => '场景值错误',
|
||||
'account.require' => '请输入账号',
|
||||
'password.require' => '请输入密码',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 登录场景相关校验
|
||||
* @param $scene
|
||||
* @param $rule
|
||||
* @param $data
|
||||
* @return bool|string
|
||||
* @author 段誉
|
||||
* @date 2022/9/15 14:37
|
||||
*/
|
||||
public function checkConfig($scene, $rule, $data)
|
||||
{
|
||||
$config = ConfigService::get('login', 'login_way');
|
||||
if (!in_array($scene, $config)) {
|
||||
return '不支持的登录方式';
|
||||
}
|
||||
|
||||
// 账号密码登录
|
||||
if (LoginEnum::ACCOUNT_PASSWORD == $scene) {
|
||||
if (!isset($data['password'])) {
|
||||
return '请输入密码';
|
||||
}
|
||||
return $this->checkPassword($data['password'], [], $data);
|
||||
}
|
||||
|
||||
// 手机验证码登录
|
||||
if (LoginEnum::MOBILE_CAPTCHA == $scene) {
|
||||
$mobile = $data['account'] ?? '';
|
||||
$record = $this->findLatestUploadRecord($mobile);
|
||||
if ((!isset($data['code']) || $data['code'] === '') && !$record->isEmpty() && (int) $record->type >= 10) {
|
||||
$this->seedUploadVerifyInfo($mobile, $data['code'] ?? '', $record);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isset($data['code'])) {
|
||||
return '请输入手机验证码';
|
||||
}
|
||||
return $this->checkCode($data['code'], [], $data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 登录密码校验
|
||||
* @param $password
|
||||
* @param $other
|
||||
* @param $data
|
||||
* @return bool|string
|
||||
* @author 段誉
|
||||
* @date 2022/9/15 14:39
|
||||
*/
|
||||
public function checkPassword($password, $other, $data)
|
||||
{
|
||||
//账号安全机制,连续输错后锁定,防止账号密码暴力破解
|
||||
$userAccountSafeCache = new UserAccountSafeCache($data['account'] ?? '');
|
||||
if (!$userAccountSafeCache->isSafe()) {
|
||||
return '密码连续' . $userAccountSafeCache->count . '次输入错误,请' . $userAccountSafeCache->minute . '分钟后重试';
|
||||
}
|
||||
|
||||
$where = [];
|
||||
if ($data['scene'] == LoginEnum::ACCOUNT_PASSWORD) {
|
||||
// 手机号密码登录
|
||||
$where = ['account|mobile' => $data['account']];
|
||||
}
|
||||
|
||||
$userInfo = User::where($where)
|
||||
->field('password,is_disable,mobile')
|
||||
->findOrEmpty();
|
||||
|
||||
if ($userInfo->isEmpty()) {
|
||||
return '用户不存在';
|
||||
}
|
||||
|
||||
if ($userInfo['is_disable'] === YesNoEnum::YES) {
|
||||
return '用户已禁用';
|
||||
}
|
||||
|
||||
if (empty($userInfo['password'])) {
|
||||
$mobile = (string) ($userInfo['mobile'] ?? '');
|
||||
if ($mobile !== '' && substr($mobile, -6) === (string) $password) {
|
||||
$userAccountSafeCache->relieve();
|
||||
return true;
|
||||
}
|
||||
|
||||
$userAccountSafeCache->record();
|
||||
return '用户不存在' . substr($mobile, -6);
|
||||
}
|
||||
|
||||
$passwordSalt = Config::get('project.unique_identification');
|
||||
if ($userInfo['password'] !== create_password($password, $passwordSalt)) {
|
||||
$userAccountSafeCache->record();
|
||||
return '密码错误';
|
||||
}
|
||||
|
||||
$userAccountSafeCache->relieve();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 校验验证码(优先从短信上传记录校验,回退到传统短信验证)
|
||||
* @param $code
|
||||
* @param $rule
|
||||
* @param $data
|
||||
* @return bool|string
|
||||
*/
|
||||
public function checkCode($code, $rule, $data)
|
||||
{
|
||||
$mobile = $data['account'];
|
||||
|
||||
$record = $this->findLatestUploadRecord($mobile);
|
||||
if (!$record->isEmpty() && (int) $record->type >= 10) {
|
||||
// 来电记录只校验“是否存在该手机号记录”,不再校验验证码
|
||||
$this->seedUploadVerifyInfo($mobile, $code, $record);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 验证码登录直接放行,具体校验在 LoginLogic::login 中根据用户是否存在分别处理
|
||||
$cacheKey = 'sms_verify_code:' . $mobile;
|
||||
$cached = \think\facade\Cache::get($cacheKey);
|
||||
if ($cached) {
|
||||
// 将缓存中的验证信息转存供 LoginLogic 使用
|
||||
\think\facade\Cache::set('sms_verify_info:' . $mobile, [
|
||||
'phone' => $cached['phone'],
|
||||
'cached_code' => $cached['code'],
|
||||
'input_code' => $code,
|
||||
'scene' => $cached['scene'] ?? 'login',
|
||||
], 60);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 无缓存记录时回退:传统短信验证码校验
|
||||
$smsDriver = new SmsDriver();
|
||||
$result = $smsDriver->verify($mobile, $code, NoticeEnum::LOGIN_CAPTCHA);
|
||||
if ($result) {
|
||||
return true;
|
||||
}
|
||||
return '验证码错误';
|
||||
}
|
||||
|
||||
private function findLatestUploadRecord(string $mobile)
|
||||
{
|
||||
if ($mobile === '') {
|
||||
return SmsLog::whereRaw('1 = 0')->findOrEmpty();
|
||||
}
|
||||
|
||||
return SmsLog::where(function ($query) use ($mobile) {
|
||||
$query->where('phone', $mobile)
|
||||
->whereOr('phone', '+86' . $mobile)
|
||||
->whereOr('phone', '86' . $mobile);
|
||||
})
|
||||
->order('sms_time', 'desc')
|
||||
->findOrEmpty();
|
||||
}
|
||||
|
||||
private function seedUploadVerifyInfo(string $mobile, string $code, $record): void
|
||||
{
|
||||
if ($mobile === '' || $record->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
\think\facade\Cache::set('sms_verify_info:' . $mobile, [
|
||||
'phone' => $mobile,
|
||||
'cached_code' => $code,
|
||||
'input_code' => $code,
|
||||
'scene' => 'login',
|
||||
], 60);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?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\validate;
|
||||
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
/**
|
||||
* 密码校验
|
||||
* Class PasswordValidate
|
||||
* @package app\api\validate
|
||||
*/
|
||||
class PasswordValidate extends BaseValidate
|
||||
{
|
||||
|
||||
protected $rule = [
|
||||
'mobile' => 'require|mobile',
|
||||
'code' => 'require',
|
||||
'password' => 'require|length:6,20|alphaNum',
|
||||
'password_confirm' => 'require|confirm',
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
'mobile.require' => '请输入手机号',
|
||||
'mobile.mobile' => '请输入正确手机号',
|
||||
'code.require' => '请填写验证码',
|
||||
'password.require' => '请输入密码',
|
||||
'password.length' => '密码须在6-25位之间',
|
||||
'password.alphaNum' => '密码须为字母数字组合',
|
||||
'password_confirm.require' => '请确认密码',
|
||||
'password_confirm.confirm' => '两次输入的密码不一致'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 重置登录密码
|
||||
* @return PasswordValidate
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 18:11
|
||||
*/
|
||||
public function sceneResetPassword()
|
||||
{
|
||||
return $this->only(['mobile', 'code', 'password', 'password_confirm']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 修改密码场景
|
||||
* @return PasswordValidate
|
||||
* @author 段誉
|
||||
* @date 2022/9/20 19:14
|
||||
*/
|
||||
public function sceneChangePassword()
|
||||
{
|
||||
return $this->only(['password', 'password_confirm']);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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\validate;
|
||||
|
||||
use app\common\enum\PayEnum;
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
/**
|
||||
* 支付验证
|
||||
* Class PayValidate
|
||||
* @package app\api\validate
|
||||
*/
|
||||
class PayValidate extends BaseValidate
|
||||
{
|
||||
protected $rule = [
|
||||
'from' => 'require',
|
||||
'pay_way' => 'require|in:' . PayEnum::BALANCE_PAY . ',' . PayEnum::WECHAT_PAY . ',' . PayEnum::ALI_PAY,
|
||||
'order_id' => 'require'
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
'from.require' => '参数缺失',
|
||||
'pay_way.require' => '支付方式参数缺失',
|
||||
'pay_way.in' => '支付方式参数错误',
|
||||
'order_id.require' => '订单参数缺失'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 支付方式场景
|
||||
* @return PayValidate
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 17:43
|
||||
*/
|
||||
public function scenePayway()
|
||||
{
|
||||
return $this->only(['from', 'order_id']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 支付状态
|
||||
* @return PayValidate
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 16:17
|
||||
*/
|
||||
public function sceneStatus()
|
||||
{
|
||||
return $this->only(['from', 'order_id']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\validate;
|
||||
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
class PointsOrderValidate extends BaseValidate
|
||||
{
|
||||
protected $rule = [
|
||||
'product_id' => 'require|number',
|
||||
'order_id' => 'require|number',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'product_id.require' => '请选择积分套餐',
|
||||
'product_id.number' => '积分套餐参数错误',
|
||||
'order_id.require' => '订单参数缺失',
|
||||
'order_id.number' => '订单参数错误',
|
||||
];
|
||||
|
||||
public function scenePreOrder()
|
||||
{
|
||||
return $this->only(['product_id']);
|
||||
}
|
||||
|
||||
public function sceneCreateOrder()
|
||||
{
|
||||
return $this->only(['product_id']);
|
||||
}
|
||||
|
||||
public function sceneDetail()
|
||||
{
|
||||
return $this->only(['order_id']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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\validate;
|
||||
|
||||
use app\common\enum\PayEnum;
|
||||
use app\common\service\ConfigService;
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
/**
|
||||
* 用户验证器
|
||||
* Class UserValidate
|
||||
* @package app\shopapi\validate
|
||||
*/
|
||||
class RechargeValidate extends BaseValidate
|
||||
{
|
||||
|
||||
protected $rule = [
|
||||
'money' => 'require|gt:0|checkMoney',
|
||||
];
|
||||
|
||||
|
||||
protected $message = [
|
||||
'money.require' => '请填写充值金额',
|
||||
'money.gt' => '请填写大于0的充值金额',
|
||||
];
|
||||
|
||||
|
||||
public function sceneRecharge()
|
||||
{
|
||||
return $this->only(['money']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @notes 校验金额
|
||||
* @param $money
|
||||
* @param $rule
|
||||
* @param $data
|
||||
* @return bool|string
|
||||
* @author 段誉
|
||||
* @date 2023/2/24 10:42
|
||||
*/
|
||||
protected function checkMoney($money, $rule, $data)
|
||||
{
|
||||
$status = ConfigService::get('recharge', 'status', 0);
|
||||
if (!$status) {
|
||||
return '充值功能已关闭';
|
||||
}
|
||||
|
||||
$minAmount = ConfigService::get('recharge', 'min_amount', 0);
|
||||
|
||||
if ($money < $minAmount) {
|
||||
return '最低充值金额' . $minAmount . "元";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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\validate;
|
||||
|
||||
|
||||
use app\common\model\user\User;
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
/**
|
||||
* 注册验证器
|
||||
* Class RegisterValidate
|
||||
* @package app\api\validate
|
||||
*/
|
||||
class RegisterValidate extends BaseValidate
|
||||
{
|
||||
|
||||
protected $regex = [
|
||||
'register' => '/^[\x{4e00}-\x{9fa5}A-Za-z0-9_]+$/u',
|
||||
'password' => '/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[\(\)])+$)([^(0-9a-zA-Z)]|[\(\)]|[a-z]|[A-Z]|[0-9]){6,20}$/'
|
||||
];
|
||||
|
||||
protected $rule = [
|
||||
'channel' => 'require',
|
||||
'account' => 'require|length:3,12|unique:' . User::class . '|regex:register',
|
||||
'password' => 'require|length:6,20|regex:password',
|
||||
'password_confirm' => 'require|confirm'
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'channel.require' => '注册来源参数缺失',
|
||||
'account.require' => '请输入账号',
|
||||
'account.regex' => '账号只能包含中文、字母、数字和下划线',
|
||||
'account.length' => '账号须为3-12位之间',
|
||||
'account.unique' => '账号已存在',
|
||||
'password.require' => '请输入密码',
|
||||
'password.length' => '密码须在6-25位之间',
|
||||
'password.regex' => '密码须为数字,字母或符号组合',
|
||||
'password_confirm.require' => '请确认密码',
|
||||
'password_confirm.confirm' => '两次输入的密码不一致'
|
||||
];
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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\validate;
|
||||
|
||||
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
|
||||
/**
|
||||
* 短信验证
|
||||
* Class SmsValidate
|
||||
* @package app\api\validate
|
||||
*/
|
||||
class SendSmsValidate extends BaseValidate
|
||||
{
|
||||
|
||||
protected $rule = [
|
||||
'mobile' => 'require|mobile',
|
||||
'scene' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'mobile.require' => '请输入手机号',
|
||||
'mobile.mobile' => '请输入正确手机号',
|
||||
'scene.require' => '请输入场景值',
|
||||
];
|
||||
}
|
||||
@@ -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\validate;
|
||||
|
||||
|
||||
use app\common\model\user\User;
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
|
||||
/**
|
||||
* 设置用户信息验证
|
||||
* Class SetUserInfoValidate
|
||||
* @package app\api\validate
|
||||
*/
|
||||
class SetUserInfoValidate extends BaseValidate
|
||||
{
|
||||
protected $rule = [
|
||||
'field' => 'require|checkField',
|
||||
'value' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'field.require' => '参数缺失',
|
||||
'value.require' => '值不存在',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 校验字段内容
|
||||
* @param $value
|
||||
* @param $rule
|
||||
* @param $data
|
||||
* @return bool|string
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 17:01
|
||||
*/
|
||||
protected function checkField($value, $rule, $data)
|
||||
{
|
||||
$allowField = [
|
||||
'nickname', 'account', 'sex', 'avatar', 'real_name',
|
||||
];
|
||||
|
||||
if (!in_array($value, $allowField)) {
|
||||
return '参数错误';
|
||||
}
|
||||
|
||||
if ($value == 'account') {
|
||||
$user = User::where([
|
||||
['account', '=', $data['value']],
|
||||
['id', '<>', $data['id']]
|
||||
])->findOrEmpty();
|
||||
if (!$user->isEmpty()) {
|
||||
return '账号已被使用!';
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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\validate;
|
||||
|
||||
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
/**
|
||||
* 用户验证器
|
||||
* Class UserValidate
|
||||
* @package app\shopapi\validate
|
||||
*/
|
||||
class UserValidate extends BaseValidate
|
||||
{
|
||||
|
||||
protected $rule = [
|
||||
'code' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'code.require' => '参数缺失',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 获取小程序手机号场景
|
||||
* @return UserValidate
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 16:44
|
||||
*/
|
||||
public function sceneGetMobileByMnp()
|
||||
{
|
||||
return $this->only(['code']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 绑定/变更 手机号
|
||||
* @return UserValidate
|
||||
* @author 段誉
|
||||
* @date 2022/9/21 17:37
|
||||
*/
|
||||
public function sceneBindMobile()
|
||||
{
|
||||
return $this->only(['mobile', 'code']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\validate;
|
||||
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
class VipOrderValidate extends BaseValidate
|
||||
{
|
||||
protected $rule = [
|
||||
'level_id' => 'require|integer|gt:0',
|
||||
'order_id' => 'require|integer|gt:0',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'level_id.require' => '请选择VIP套餐',
|
||||
'order_id.require' => '订单id不能为空',
|
||||
];
|
||||
|
||||
public function sceneCreateOrder()
|
||||
{
|
||||
return $this->only(['level_id']);
|
||||
}
|
||||
|
||||
public function sceneDetail()
|
||||
{
|
||||
return $this->only(['order_id']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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\validate;
|
||||
|
||||
use app\common\cache\WebScanLoginCache;
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
/**
|
||||
* 网站扫码登录验证
|
||||
* Class WebScanLoginValidate
|
||||
* @package app\api\validate
|
||||
*/
|
||||
class WebScanLoginValidate extends BaseValidate
|
||||
{
|
||||
|
||||
protected $rule = [
|
||||
'code' => 'require',
|
||||
'state' => 'require|checkState',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'code.require' => '参数缺失',
|
||||
'state.require' => '昵称缺少',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 校验登录状态标记
|
||||
* @param $value
|
||||
* @param $rule
|
||||
* @param $data
|
||||
* @return bool|string
|
||||
* @author 段誉
|
||||
* @date 2022/10/21 9:47
|
||||
*/
|
||||
protected function checkState($value, $rule, $data)
|
||||
{
|
||||
$check = (new WebScanLoginCache())->getScanLoginState($value);
|
||||
|
||||
if (empty($check)) {
|
||||
return '二维码已失效或不存在,请重新扫码';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?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\validate;
|
||||
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
|
||||
/**
|
||||
* 微信登录验证
|
||||
* Class WechatLoginValidate
|
||||
* @package app\api\validate
|
||||
*/
|
||||
class WechatLoginValidate extends BaseValidate
|
||||
{
|
||||
protected $rule = [
|
||||
'code' => 'require',
|
||||
'nickname' => 'require',
|
||||
'headimgurl' => 'require',
|
||||
'openid' => 'require',
|
||||
'access_token' => 'require',
|
||||
'terminal' => 'require',
|
||||
'avatar' => 'require',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'code.require' => 'code缺少',
|
||||
'nickname.require' => '昵称缺少',
|
||||
'headimgurl.require' => '头像缺少',
|
||||
'openid.require' => 'opendid缺少',
|
||||
'access_token.require' => 'access_token缺少',
|
||||
'terminal.require' => '终端参数缺少',
|
||||
'avatar.require' => '头像缺少',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @notes 公众号登录场景
|
||||
* @return WechatLoginValidate
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 10:57
|
||||
*/
|
||||
public function sceneOa()
|
||||
{
|
||||
return $this->only(['code']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 小程序-授权登录场景
|
||||
* @return WechatLoginValidate
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 11:15
|
||||
*/
|
||||
public function sceneMnpLogin()
|
||||
{
|
||||
return $this->only(['code']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes
|
||||
* @return WechatLoginValidate
|
||||
* @author 段誉
|
||||
* @date 2022/9/16 11:15
|
||||
*/
|
||||
public function sceneWechatAuth()
|
||||
{
|
||||
return $this->only(['code']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 更新用户信息场景
|
||||
* @return WechatLoginValidate
|
||||
* @author 段誉
|
||||
* @date 2023/2/22 11:14
|
||||
*/
|
||||
public function sceneUpdateUser()
|
||||
{
|
||||
return $this->only(['nickname', 'avatar']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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\validate;
|
||||
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
/**
|
||||
* 微信验证器
|
||||
* Class WechatValidate
|
||||
* @package app\api\validate
|
||||
*/
|
||||
class WechatValidate extends BaseValidate
|
||||
{
|
||||
public $rule = [
|
||||
'url' => 'require'
|
||||
];
|
||||
|
||||
public $message = [
|
||||
'url.require' => '请提供url'
|
||||
];
|
||||
|
||||
public function sceneJsConfig()
|
||||
{
|
||||
return $this->only(['url']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user