Files
2026-06-11 12:15:29 +08:00

356 lines
12 KiB
PHP

<?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);
}
}