chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+229
View File
@@ -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,
];
}
}
+118
View File
@@ -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;
}
}
+147
View File
@@ -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();
}
}
+99
View File
@@ -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();
}
}
+85
View File
@@ -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; // 添加点赞
}
}
}
+78
View File
@@ -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;
}
}
+123
View File
@@ -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;
}
}