chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\blog\model;
|
||||
|
||||
use think\facade\Cache;
|
||||
use ywxapp\model\BaseModel;
|
||||
use ywxapp\model\MemberUser;
|
||||
|
||||
class BlogArticle extends BaseModel
|
||||
{
|
||||
// 设置数据表名
|
||||
protected $name = 'blog_article';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
|
||||
// 定义允许写入的字段
|
||||
protected $allowField = [
|
||||
'title', 'content', 'summary', 'cover_image',
|
||||
'uid', 'cid', 'status', 'audit_status', 'is_top',
|
||||
'view_count', 'comment_count', 'like_count', 'favorite_count', 'share_count',
|
||||
];
|
||||
|
||||
// 设置字段类型
|
||||
protected $type = [
|
||||
'id' => 'int',
|
||||
'uid' => 'int',
|
||||
'cid' => 'int',
|
||||
'status' => 'int',
|
||||
'audit_status' => 'int',
|
||||
'is_top' => 'int',
|
||||
'view_count' => 'int',
|
||||
'comment_count' => 'int',
|
||||
'like_count' => 'int',
|
||||
'favorite_count' => 'int',
|
||||
'share_count' => 'int',
|
||||
'create_at' => 'int',
|
||||
'update_at' => 'int',
|
||||
];
|
||||
|
||||
// 内容修改器(过滤危险标签)
|
||||
|
||||
public function setContentAttr($value)
|
||||
{
|
||||
return htmlspecialchars_decode($value);
|
||||
}
|
||||
|
||||
// 摘要写入修改器:提交时若留空,自动从正文截取并持久化
|
||||
|
||||
public function setSummaryAttr($value, $data)
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
if ($value !== '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$content = $data['content'] ?? '';
|
||||
$content = is_string($content) ? strip_tags($content) : '';
|
||||
$content = trim($content);
|
||||
|
||||
return $content !== '' ? mb_substr($content, 0, 150) : '';
|
||||
}
|
||||
|
||||
// 获取文章摘要(自动生成,兼容遗留未存摘要的文章)
|
||||
|
||||
public function getSummaryAttr($value, $data)
|
||||
{
|
||||
if ($value !== '' && $value !== null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$content = is_string($data['content'] ?? '') ? (string) $data['content'] : '';
|
||||
$content = html_entity_decode(strip_tags($content), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$content = trim(preg_replace('/\s+/', ' ', $content));
|
||||
|
||||
// 正文为 JSON 或无可读文本时返回空,由模板显示「暂无描述」,避免首页展示 JSON 片段
|
||||
if ($content === '' || $this->isJsonContent($content)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return mb_substr($content, 0, 150);
|
||||
}
|
||||
|
||||
// 判断文本是否像 JSON(自动摘要时跳过,避免卡片展示 JSON 片段)
|
||||
|
||||
protected function isJsonContent($text)
|
||||
{
|
||||
$text = ltrim($text);
|
||||
if ($text === '' || ($text[0] !== '{' && $text[0] !== '[')) {
|
||||
return false;
|
||||
}
|
||||
json_decode($text);
|
||||
return json_last_error() === JSON_ERROR_NONE;
|
||||
}
|
||||
|
||||
// 格式化创建时间(兼容 int / 数字字符串 / 日期字符串 / DateTime)
|
||||
|
||||
public function getCreateTextAttr($value, $data)
|
||||
{
|
||||
$t = $data['create_at'] ?? $value;
|
||||
if ($t instanceof \DateTime) {
|
||||
return $t->format('Y-m-d H:i');
|
||||
}
|
||||
if (is_numeric($t)) {
|
||||
return date('Y-m-d H:i', (int) $t);
|
||||
}
|
||||
$ts = strtotime((string) $t);
|
||||
return $ts !== false ? date('Y-m-d H:i', $ts) : (string) $t;
|
||||
}
|
||||
|
||||
// 定义关联 - 作者(博客作者即主系统会员,统一使用 wxapp_user 表,主键 uid)
|
||||
|
||||
public function author()
|
||||
{
|
||||
return $this->belongsTo(MemberUser::class, 'uid', 'uid');
|
||||
}
|
||||
|
||||
// 兼容别名:部分视图/调用使用 user 关系
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(MemberUser::class, 'uid', 'uid');
|
||||
}
|
||||
|
||||
// 定义关联 - 分类
|
||||
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(BlogCategory::class, 'cid');
|
||||
}
|
||||
|
||||
// 定义关联 - 标签(关联表 wxapp_blog_article_tag,列 aid/tid)
|
||||
|
||||
public function tags()
|
||||
{
|
||||
return $this->belongsToMany(BlogTag::class, 'blog_article_tag', 'tid', 'aid');
|
||||
}
|
||||
|
||||
// 定义关联 - 评论
|
||||
|
||||
public function comments()
|
||||
{
|
||||
return $this->hasMany(BlogComment::class, 'aid');
|
||||
}
|
||||
|
||||
// 定义关联 - 点赞
|
||||
|
||||
public function likes()
|
||||
{
|
||||
return $this->hasMany(BlogLike::class, 'aid');
|
||||
}
|
||||
|
||||
// 定义关联 - 收藏
|
||||
|
||||
public function favorites()
|
||||
{
|
||||
return $this->hasMany(BlogFavorite::class, 'aid');
|
||||
}
|
||||
|
||||
// 增加浏览次数
|
||||
|
||||
public function incrementViewCount()
|
||||
{
|
||||
$this->view_count++;
|
||||
$this->save();
|
||||
|
||||
// 清除相关缓存
|
||||
Cache::delete('article_' . $this->id);
|
||||
}
|
||||
|
||||
// 检查文章是否置顶
|
||||
|
||||
public function isTop()
|
||||
{
|
||||
return $this->is_top === 1;
|
||||
}
|
||||
|
||||
// 检查文章是否发布
|
||||
|
||||
public function isPublished()
|
||||
{
|
||||
return $this->status === 1;
|
||||
}
|
||||
|
||||
// 获取相关文章(同分类)
|
||||
|
||||
public function getRelatedArticles($limit = 5)
|
||||
{
|
||||
return self::where('cid', $this->cid)
|
||||
->where('id', '<>', $this->id)
|
||||
->where('status', 1)
|
||||
->order('view_count', 'desc')
|
||||
->limit($limit)
|
||||
->select();
|
||||
}
|
||||
|
||||
// 获取上一篇/下一篇文章
|
||||
|
||||
public function getPrevNextArticles()
|
||||
{
|
||||
$prev = self::where('id', '<', $this->id)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
|
||||
$next = self::where('id', '>', $this->id)
|
||||
->where('status', 1)
|
||||
->order('id', 'asc')
|
||||
->find();
|
||||
|
||||
return [
|
||||
'prev' => $prev,
|
||||
'next' => $next,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\blog\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
|
||||
class BlogCategory extends BaseModel
|
||||
{
|
||||
|
||||
// 设置数据表名
|
||||
protected $name = 'blog_category';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
|
||||
// 定义允许写入的字段
|
||||
protected $allowField = [
|
||||
'uid', 'pid', 'name', 'description', 'sort', 'status'
|
||||
];
|
||||
|
||||
// 设置字段类型
|
||||
protected $type = [
|
||||
'uid' => 'int',
|
||||
'pid' => 'int',
|
||||
'sort' => 'int',
|
||||
'status' => 'int',
|
||||
'create_at' => 'int',
|
||||
'update_at' => 'int'
|
||||
];
|
||||
|
||||
// 定义关联 - 文章
|
||||
|
||||
public function articles()
|
||||
{
|
||||
return $this->hasMany(BlogArticle::class, 'cid');
|
||||
}
|
||||
|
||||
// 定义关联 - 父级分类
|
||||
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(self::class, 'pid');
|
||||
}
|
||||
|
||||
// 定义关联 - 子分类
|
||||
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(self::class, 'pid');
|
||||
}
|
||||
|
||||
// 获取文章数量
|
||||
|
||||
public function getArticleCount()
|
||||
{
|
||||
return $this->articles()->where('status', 1)->count();
|
||||
}
|
||||
|
||||
// 检查分类是否可用
|
||||
|
||||
public function isActive()
|
||||
{
|
||||
return $this->status === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某会员「可用」的分类:自己创建的(uid=$uid) + 全局共享的(uid=0)
|
||||
* 用于会员中心写文章时的分类下拉。
|
||||
*/
|
||||
public static function getAvailable(int $uid)
|
||||
{
|
||||
return self::where('status', 1)
|
||||
->where(function ($query) use ($uid) {
|
||||
$query->where('uid', 0)->whereOr('uid', $uid);
|
||||
})
|
||||
->order('sort', 'asc')
|
||||
->select();
|
||||
}
|
||||
|
||||
// 获取分类树形结构
|
||||
|
||||
public static function getCategoryTree()
|
||||
{
|
||||
$categories = self::order('sort', 'asc')->select()->toArray();
|
||||
return self::buildTree($categories);
|
||||
}
|
||||
|
||||
// 递归构建树形结构
|
||||
|
||||
private static function buildTree($items, $parentId = 0)
|
||||
{
|
||||
$tree = [];
|
||||
foreach ($items as $item) {
|
||||
if (($item['pid'] ?? 0) == $parentId) {
|
||||
$children = self::buildTree($items, $item['id']);
|
||||
if ($children) {
|
||||
$item['children'] = $children;
|
||||
}
|
||||
$tree[] = $item;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\blog\model;
|
||||
|
||||
use think\facade\Db;
|
||||
use ywxapp\model\BaseModel;
|
||||
use ywxapp\model\MemberUser;
|
||||
|
||||
|
||||
class BlogComment extends BaseModel
|
||||
{
|
||||
// 设置数据表名
|
||||
protected $name = 'blog_comment';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳(int 型 Unix 时间戳,匹配 create_at/update_at int 列)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
|
||||
// 定义允许写入的字段
|
||||
protected $allowField = [
|
||||
'content', 'uid', 'aid', 'pid',
|
||||
'status', 'like_count', 'ip', 'user_agent',
|
||||
];
|
||||
|
||||
// 设置字段类型
|
||||
protected $type = [
|
||||
'id' => 'int',
|
||||
'uid' => 'int',
|
||||
'aid' => 'int',
|
||||
'pid' => 'int',
|
||||
'status' => 'int',
|
||||
'like_count' => 'int',
|
||||
'create_at' => 'int',
|
||||
'update_at' => 'int',
|
||||
];
|
||||
|
||||
// 内容修改器(过滤敏感词)
|
||||
|
||||
public function setContentAttr($value)
|
||||
{
|
||||
$sensitiveWords = ['垃圾', '广告', '违法', '政治'];
|
||||
return str_replace($sensitiveWords, '***', $value);
|
||||
}
|
||||
|
||||
// 定义关联 - 用户(博客用户即主系统会员,统一使用 wxapp_user 表,主键 uid)
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(MemberUser::class, 'uid', 'uid');
|
||||
}
|
||||
|
||||
// 定义关联 - 文章
|
||||
|
||||
public function article()
|
||||
{
|
||||
return $this->belongsTo(BlogArticle::class, 'aid');
|
||||
}
|
||||
|
||||
// 定义关联 - 子评论
|
||||
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(BlogComment::class, 'pid');
|
||||
}
|
||||
|
||||
// 定义关联 - 父评论
|
||||
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(BlogComment::class, 'pid');
|
||||
}
|
||||
|
||||
// 检查评论是否通过审核
|
||||
|
||||
public function isApproved()
|
||||
{
|
||||
return $this->status === 1;
|
||||
}
|
||||
|
||||
// 格式化创建时间(兼容 int / 数字字符串 / 日期字符串 / DateTime)
|
||||
|
||||
public function getCreateTextAttr($value, $data)
|
||||
{
|
||||
$t = $data['create_at'] ?? $value;
|
||||
if ($t instanceof \DateTime) {
|
||||
return $t->format('Y-m-d H:i');
|
||||
}
|
||||
if (is_numeric($t)) {
|
||||
return date('Y-m-d H:i', (int) $t);
|
||||
}
|
||||
$ts = strtotime((string) $t);
|
||||
return $ts !== false ? date('Y-m-d H:i', $ts) : (string) $t;
|
||||
}
|
||||
|
||||
// 获取评论树
|
||||
|
||||
public static function getCommentTree($articleId, $parentId = 0, $depth = 0)
|
||||
{
|
||||
$comments = self::with('member')
|
||||
->where('aid', $articleId)
|
||||
->where('pid', $parentId)
|
||||
->where('status', 1)
|
||||
->order('create_at', 'asc')
|
||||
->select();
|
||||
|
||||
$tree = [];
|
||||
foreach ($comments as $comment) {
|
||||
$comment->depth = $depth;
|
||||
$comment->children = self::getCommentTree($articleId, $comment->id, $depth + 1);
|
||||
$tree[] = $comment;
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
// 删除评论及其子评论
|
||||
|
||||
public function deleteWithChildren()
|
||||
{
|
||||
// 删除子评论
|
||||
$children = $this->children()->select();
|
||||
foreach ($children as $child) {
|
||||
$child->deleteWithChildren();
|
||||
}
|
||||
|
||||
// 删除当前评论
|
||||
$this->delete();
|
||||
|
||||
// 更新文章评论数
|
||||
Db::name('blog_article')
|
||||
->where('id', $this->aid)
|
||||
->dec('comment_count')
|
||||
->update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\blog\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
use ywxapp\model\MemberUser;
|
||||
|
||||
|
||||
class BlogFavorite extends BaseModel
|
||||
{
|
||||
// 设置数据表名
|
||||
protected $name = 'blog_favorite';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳(int 型 Unix 时间戳,匹配 create_at int 列)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = false;
|
||||
|
||||
// 定义允许写入的字段
|
||||
protected $allowField = [
|
||||
'uid', 'aid'
|
||||
];
|
||||
|
||||
// 设置字段类型
|
||||
protected $type = [
|
||||
'id' => 'int',
|
||||
'uid' => 'int',
|
||||
'aid' => 'int',
|
||||
'create_at' => 'int'
|
||||
];
|
||||
|
||||
// 定义关联 - 用户(博客用户即主系统会员,统一使用 wxapp_user,主键 uid)
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(MemberUser::class, 'uid', 'uid');
|
||||
}
|
||||
|
||||
// 定义关联 - 文章
|
||||
|
||||
public function article()
|
||||
{
|
||||
return $this->belongsTo(BlogArticle::class, 'aid');
|
||||
}
|
||||
|
||||
// 检查用户是否已收藏
|
||||
|
||||
public static function hasFavorited($userId, $articleId)
|
||||
{
|
||||
return self::where('uid', $userId)
|
||||
->where('aid', $articleId)
|
||||
->count() > 0;
|
||||
}
|
||||
|
||||
// 切换收藏状态
|
||||
|
||||
public static function toggle($userId, $articleId)
|
||||
{
|
||||
$favorite = self::where('uid', $userId)
|
||||
->where('aid', $articleId)
|
||||
->find();
|
||||
|
||||
if ($favorite) {
|
||||
$favorite->delete();
|
||||
return false; // 取消收藏
|
||||
} else {
|
||||
$favorite = new self();
|
||||
$favorite->uid = $userId;
|
||||
$favorite->aid = $articleId;
|
||||
$favorite->save();
|
||||
return true; // 添加收藏
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户收藏列表
|
||||
|
||||
public static function getUserFavorites($userId, $page = 1, $pageSize = 10)
|
||||
{
|
||||
return self::with(['article' =>
|
||||
function($query) {
|
||||
$query->with('author,category')->where('status', 1);
|
||||
}])
|
||||
->where('uid', $userId)
|
||||
->order('create_at', 'desc')
|
||||
->page($page, $pageSize)
|
||||
->select();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\blog\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
use ywxapp\model\MemberUser;
|
||||
|
||||
|
||||
class BlogLike extends BaseModel
|
||||
{
|
||||
// 设置数据表名
|
||||
protected $name = 'blog_like';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳(int 型 Unix 时间戳,匹配 create_at int 列)
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = false;
|
||||
|
||||
// 定义允许写入的字段
|
||||
protected $allowField = [
|
||||
'uid', 'aid'
|
||||
];
|
||||
|
||||
// 设置字段类型
|
||||
protected $type = [
|
||||
'id' => 'int',
|
||||
'uid' => 'int',
|
||||
'aid' => 'int',
|
||||
'create_at' => 'int'
|
||||
];
|
||||
|
||||
// 定义关联 - 用户(博客用户即主系统会员,统一使用 wxapp_user,主键 uid)
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(MemberUser::class, 'uid', 'uid');
|
||||
}
|
||||
|
||||
// 定义关联 - 文章
|
||||
|
||||
public function article()
|
||||
{
|
||||
return $this->belongsTo(BlogArticle::class, 'aid');
|
||||
}
|
||||
|
||||
// 检查用户是否已点赞
|
||||
|
||||
public static function hasLiked($userId, $articleId)
|
||||
{
|
||||
return self::where('uid', $userId)
|
||||
->where('aid', $articleId)
|
||||
->count() > 0;
|
||||
}
|
||||
|
||||
// 切换点赞状态
|
||||
|
||||
public static function toggle($userId, $articleId)
|
||||
{
|
||||
$like = self::where('uid', $userId)
|
||||
->where('aid', $articleId)
|
||||
->find();
|
||||
|
||||
if ($like) {
|
||||
$like->delete();
|
||||
return false; // 取消点赞
|
||||
} else {
|
||||
$like = new self();
|
||||
$like->uid = $userId;
|
||||
$like->aid = $articleId;
|
||||
$like->save();
|
||||
return true; // 添加点赞
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\blog\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
|
||||
class BlogTag extends BaseModel
|
||||
{
|
||||
// 设置数据表名
|
||||
protected $name = 'blog_tag';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = 'update_at';
|
||||
|
||||
// 定义允许写入的字段
|
||||
protected $allowField = [
|
||||
'name', 'description'
|
||||
];
|
||||
|
||||
// 设置字段类型
|
||||
protected $type = [
|
||||
'id' => 'int',
|
||||
'create_at' => 'int',
|
||||
'update_at' => 'int'
|
||||
];
|
||||
|
||||
// 定义关联 - 文章
|
||||
|
||||
public function articles()
|
||||
{
|
||||
return $this->belongsToMany(BlogArticle::class, 'blog_article_tag', 'aid', 'tid');
|
||||
}
|
||||
|
||||
// 获取文章数量
|
||||
|
||||
public function getArticleCount()
|
||||
{
|
||||
return $this->articles()->where('status', 1)->count();
|
||||
}
|
||||
|
||||
// 获取热门标签
|
||||
|
||||
public static function getHotTags($limit = 20)
|
||||
{
|
||||
return self::withCount('articles')
|
||||
->having('articles_count > 0')
|
||||
->order('articles_count', 'desc')
|
||||
->limit($limit)
|
||||
->select();
|
||||
}
|
||||
|
||||
// 根据名称查找或创建标签
|
||||
|
||||
public static function findOrCreate($name)
|
||||
{
|
||||
$tag = self::where('name', $name)->find();
|
||||
if (!$tag) {
|
||||
$tag = new self();
|
||||
$tag->name = $name;
|
||||
$tag->save();
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\blog\model;
|
||||
|
||||
use ywxapp\model\BaseModel;
|
||||
|
||||
|
||||
class BlogVisit extends BaseModel
|
||||
{
|
||||
// 设置数据表名
|
||||
protected $name = 'blog_visit';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
protected $createTime = 'create_at';
|
||||
protected $updateTime = false;
|
||||
|
||||
// 定义允许写入的字段
|
||||
protected $allowField = [
|
||||
'ip', 'url', 'referer', 'user_agent'
|
||||
];
|
||||
|
||||
// 设置字段类型
|
||||
protected $type = [
|
||||
'create_at' => 'int'
|
||||
];
|
||||
|
||||
// 获取客户端IP地址
|
||||
|
||||
public static function getClientIp()
|
||||
{
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
|
||||
$ip = trim($ips[0]);
|
||||
}
|
||||
return $ip;
|
||||
}
|
||||
|
||||
// 记录访问
|
||||
|
||||
public static function record($url, $referer = '', $userAgent = '')
|
||||
{
|
||||
$visit = new self();
|
||||
$visit->ip = self::getClientIp();
|
||||
$visit->url = $url;
|
||||
$visit->referer = $referer ?: request()->server('HTTP_REFERER', '');
|
||||
$visit->user_agent = $userAgent ?: request()->server('HTTP_USER_AGENT', '');
|
||||
$visit->save();
|
||||
}
|
||||
|
||||
// 获取访问统计
|
||||
|
||||
public static function getStats($days = 7)
|
||||
{
|
||||
$startTime = time() - ($days * 86400);
|
||||
|
||||
// 总访问量
|
||||
$total = self::where('create_at', '>=', $startTime)->count();
|
||||
|
||||
// 独立访客
|
||||
$uniqueVisitors = self::where('create_at', '>=', $startTime)
|
||||
->distinct(true)
|
||||
->field('ip')
|
||||
->count();
|
||||
|
||||
// 热门页面
|
||||
$hotPages = self::where('create_at', '>=', $startTime)
|
||||
->field('url, COUNT(*) as count')
|
||||
->group('url')
|
||||
->order('count', 'desc')
|
||||
->limit(10)
|
||||
->select();
|
||||
|
||||
// 访问趋势
|
||||
$trend = [];
|
||||
for ($i = 0; $i < $days; $i++) {
|
||||
$date = date('Y-m-d', time() - ($i * 86400));
|
||||
$count = self::whereDay('create_at', $date)->count();
|
||||
$trend[$date] = $count;
|
||||
}
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'unique_visitors' => $uniqueVisitors,
|
||||
'hot_pages' => $hotPages,
|
||||
'trend' => $trend
|
||||
];
|
||||
}
|
||||
|
||||
// 获取真实IP(考虑代理)
|
||||
|
||||
public static function getRealIp()
|
||||
{
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
|
||||
if (isset($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP)) {
|
||||
$ip = $_SERVER['HTTP_CLIENT_IP'];
|
||||
} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
|
||||
foreach ($ips as $candidate) {
|
||||
$candidate = trim($candidate);
|
||||
if (filter_var($candidate, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||
$ip = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user