增加访问记录功能

This commit is contained in:
2023-06-28 11:12:30 +08:00
parent a12cca37e4
commit 65e1800c6f
18 changed files with 447 additions and 98 deletions

133
.php-cs-fixer.php Normal file
View File

@@ -0,0 +1,133 @@
<?php
return (new PhpCsFixer\Config())
->setRules([
'@PSR12:risky' => true,
'array_syntax' => ['syntax' => 'short'],
'binary_operator_spaces' => [
'default' => 'single_space',
'operators' => ['=>' => 'single_space'],
],
'blank_line_after_namespace' => true,
'array_indentation'=>true,
'blank_line_after_opening_tag' => true,
'blank_line_before_statement' => [
'statements' => ['return'],
],
'braces' => true,
'cast_spaces' => true,
'class_attributes_separation' => [
'elements' => [
'method' => 'one',
'trait_import' => 'none',
],
],
'class_definition' => true,
'concat_space' => [
'spacing' => 'one',
],
'declare_equal_normalize' => true,
'elseif' => true,
'encoding' => true,
'full_opening_tag' => true,
'fully_qualified_strict_types' => true,
'function_declaration' => true,
'function_typehint_space' => true,
'heredoc_to_nowdoc' => true,
'include' => true,
'increment_style' => ['style' => 'post'],
'indentation_type' => true,
'linebreak_after_opening_tag' => true,
'line_ending' => true,
'lowercase_cast' => true,
'constant_case' => true,
'lowercase_keywords' => true,
'lowercase_static_reference' => true,
'magic_method_casing' => true,
'magic_constant_casing' => true,
'method_argument_space' => true,
'native_function_casing' => true,
'no_alias_functions' => true,
'no_extra_blank_lines' => [
'tokens' => [
'extra',
'throw',
'use',
],
],
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_closing_tag' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => [
'use' => 'echo',
],
'no_multiline_whitespace_around_double_arrow' => true,
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_after_function_name' => true,
'no_spaces_around_offset' => true,
'no_spaces_inside_parenthesis' => true,
'no_trailing_comma_in_list_call' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'no_unneeded_control_parentheses' => true,
'no_unreachable_default_argument_value' => true,
'no_useless_return' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
'normalize_index_brace' => true,
'not_operator_with_successor_space' => false,
'object_operator_without_whitespace' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'phpdoc_indent' => true,
'general_phpdoc_tag_rename' => true,
'phpdoc_inline_tag_normalizer' => true,
'phpdoc_tag_type' => true,
'phpdoc_no_access' => true,
'phpdoc_no_package' => true,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_scalar' => true,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => true,
'phpdoc_to_comment' => true,
'phpdoc_trim' => true,
'phpdoc_types' => true,
'phpdoc_var_without_name' => true,
'psr_autoloading' => true,
'self_accessor' => true,
'short_scalar_cast' => true,
'simplified_null_return' => false,
'single_blank_line_at_eof' => true,
'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => true,
'single_import_per_statement' => true,
'single_line_after_imports' => true,
'single_line_comment_style' => [
'comment_types' => ['hash'],
],
'single_quote' => true,
'space_after_semicolon' => true,
'standardize_not_equals' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline' => true,
'trim_array_spaces' => true,
'unary_operator_spaces' => true,
'visibility_required' => [
'elements' => ['method', 'property'],
],
'whitespace_after_comma_in_array' => true,
'no_unused_imports' => true,
])
// ->setIndent("\t")
->setLineEnding("\n");

View File

@@ -77,7 +77,7 @@ function get_source_link($url)
{ {
if (empty($url)) { if (empty($url)) {
$url = '/static/images/avatar.jpeg'; $url = '/static/images/avatar.png';
} }
if (strpos($url, '/') === 0) { if (strpos($url, '/') === 0) {
return $url; return $url;
@@ -303,3 +303,25 @@ if (!function_exists('ua_htmlentities')) {
return htmlentities($string); return htmlentities($string);
} }
} }
function show_time_ago($timestamp) {
$current_time = new DateTime();
$target_time = new DateTime("@$timestamp");
$interval = $current_time->diff($target_time);
if ($interval->y > 0) {
$result = $interval->format('%y 年前');
} elseif ($interval->m > 0) {
$result = $interval->format('%m 个月前');
} elseif ($interval->d > 0) {
$result = $interval->format('%d 天前');
} elseif ($interval->h > 0) {
$result = $interval->format('%h 小时前');
} elseif ($interval->i > 0) {
$result = $interval->format('%i 分钟前');
} else {
$result = $interval->format('%s 秒前');
}
return $result;
}

View File

@@ -5,14 +5,16 @@ namespace app\index\controller;
use app\model\Category; use app\model\Category;
use app\model\Nav; use app\model\Nav;
use app\model\Post; use app\model\Post;
use app\model\PostVisit;
use think\facade\Cache; use think\facade\Cache;
use think\facade\Session; use think\facade\Session;
use think\facade\View; use think\facade\View;
use think\helper\Str;
use UserHub\Client; use UserHub\Client;
class Common extends BaseController class Common extends BaseController
{ {
protected $userinfo = [];
public function initialize() public function initialize()
{ {
parent::initialize(); parent::initialize();
@@ -42,6 +44,21 @@ class Common extends BaseController
$top_posts = Post::where('is_top', 1)->limit(8)->where('type', 3)->cacheAlways('top_post')->select(); $top_posts = Post::where('is_top', 1)->limit(8)->where('type', 3)->cacheAlways('top_post')->select();
View::assign('top_posts', $top_posts); View::assign('top_posts', $top_posts);
$list_site_last_visit = PostVisit::order('id desc')
->group('ip,uid')
->limit(6)
->cache(60)
->select();
View::assign('list_site_last_visit', $list_site_last_visit);
$total_hits = Post::cache(60)->sum('hits');
View::assign('total_hits', $total_hits);
$total_week_hits = PostVisit::cache(60)->where('create_time', '>', time() - 86400 * 7)->count();
$total_day_hits = PostVisit::cache(60)->where('create_time', '>', time() - 86400)->count();
View::assign('total_week_hits', $total_week_hits);
View::assign('total_day_hits', $total_day_hits);
$this->userHubLogin(); $this->userHubLogin();
} }
@@ -66,11 +83,11 @@ class Common extends BaseController
// 获取用户信息 // 获取用户信息
$user_info = $user_hub_client->getUserinfoByCode($code); $user_info = $user_hub_client->getUserinfoByCode($code);
Session::set('user_uid', $user_info['uid']); Session::set('user_uid', $user_info['uid']);
} }
} else { } else {
$user_info = self::getUserInfo($user_uid); $user_info = self::getUserInfo($user_uid);
} }
$this->userinfo = $user_info;
View::assign('user_info', $user_info); View::assign('user_info', $user_info);
} }

View File

@@ -8,18 +8,16 @@ use app\model\PostCategory;
use think\facade\Cache; use think\facade\Cache;
use think\facade\Session; use think\facade\Session;
use think\facade\View; use think\facade\View;
use think\Request;
class Index extends Common class Index extends Common
{ {
/** /**
* 显示资源列表 * 显示资源列表.
* *
* @return \think\Response * @return \think\Response
*/ */
public function index() public function index()
{ {
$page_cache_key = md5($this->request->url()); $page_cache_key = md5($this->request->url());
$content = Cache::get($page_cache_key); $content = Cache::get($page_cache_key);
@@ -53,14 +51,12 @@ class Index extends Common
$model_post = Post::hasWhere('categorys', $categorys_where)->where('status', 1)->order('id desc'); $model_post = Post::hasWhere('categorys', $categorys_where)->where('status', 1)->order('id desc');
} else { } else {
$model_sub_category = Category::find($sub_category_id); $model_sub_category = Category::find($sub_category_id);
$page_title .= "-{$model_sub_category->title}"; $page_title .= "-{$model_sub_category->title}";
$model_post = Post::hasWhere('categorys', ['category_id' => $sub_category_id])->where('status', 1)->order('id desc'); $model_post = Post::hasWhere('categorys', ['category_id' => $sub_category_id])->where('status', 1)->order('id desc');
} }
} else { } else {
$model_post = Post::where('status', 1)->order('id desc'); $model_post = Post::where('status', 1)->order('id desc');
} }
@@ -84,9 +80,9 @@ class Index extends Common
'category_id' => $this->request->param('category_id'), 'category_id' => $this->request->param('category_id'),
'sub_category_id' => $this->request->param('sub_category_id'), 'sub_category_id' => $this->request->param('sub_category_id'),
'page' => $page, 'page' => $page,
'keywords' => $keywords 'keywords' => $keywords,
], ],
'list_rows' => 10 'list_rows' => 10,
]); ]);
View::assign('current_category', $current_category); View::assign('current_category', $current_category);
@@ -102,13 +98,12 @@ class Index extends Common
return $content; return $content;
} }
public function logout() public function logout()
{ {
Session::clear(); Session::clear();
$back_url = $this->request->param('back_url', '/'); $back_url = $this->request->param('back_url', '/');
return $this->success('退出成功', $back_url); return $this->success('退出成功', $back_url);
} }
} }

View File

@@ -5,17 +5,16 @@ declare(strict_types=1);
namespace app\index\controller; namespace app\index\controller;
use app\model\Post as ModelPost; use app\model\Post as ModelPost;
use app\model\PostCategory; use app\model\PostVisit;
use think\facade\Cache; use think\facade\Cache;
use think\facade\Db;
use think\facade\View; use think\facade\View;
use think\model\Relation; use think\model\Relation;
use think\Request;
class Post extends Common class Post extends Common
{ {
/** /**
* 显示指定的资源 * 显示指定的资源.
* *
* @param int $id * @param int $id
* @return \think\Response * @return \think\Response
@@ -29,11 +28,10 @@ class Post extends Common
$model_post = Cache::get($cache_key); $model_post = Cache::get($cache_key);
if (empty($model_post)) { if (empty($model_post)) {
$model_post = ModelPost::with([ $model_post = ModelPost::with([
'comments' => function (Relation $query) { 'comments' => function (Relation $query) {
$query->order('id asc'); $query->order('id asc');
} },
])->where('uid', $uid)->find(); ])->where('uid', $uid)->find();
if (empty($model_post)) { if (empty($model_post)) {
@@ -43,17 +41,32 @@ class Post extends Common
Cache::set($cache_key, $model_post, 600); Cache::set($cache_key, $model_post, 600);
} }
$model_post->hits = Db::raw('hits + 1');
$model_post->hits = $model_post->hits + 1;
$model_post->save(); $model_post->save();
$this->recordVisit($model_post->id);
$list_last_visit = PostVisit::where('post_id', $model_post->id)
->order('id desc')
->group('ip,uid')
->limit(12)
->cache(60)
->select();
View::assign('post', $model_post); View::assign('post', $model_post);
View::assign('list_last_visit', $list_last_visit);
return View::fetch(); return View::fetch();
} }
public function recordVisit($post_id)
{
$model_visit = new PostVisit();
$model_visit->uid = $this->userinfo['uid'] ?? '';
$model_visit->post_id = $post_id;
$model_visit->avatar = $this->userinfo['avatar'] ?? '';
$model_visit->nickname = $this->userinfo['nickname'] ?? '';
$model_visit->ip = $this->request->ip();
$model_visit->save();
}
} }

View File

@@ -15,7 +15,7 @@ class Admin extends Model
{ {
if(empty($value)){ if(empty($value)){
return '/static/images/avatar.jpeg'; return '/static/images/avatar.png';
} }
return \get_source_link($value); return \get_source_link($value);

View File

@@ -23,6 +23,9 @@ class Post extends Base
use SoftDelete; use SoftDelete;
// //
public const CACHE_KEY_HITS = 'cache_hits_';
public static $autoClearCache = [ public static $autoClearCache = [
[ [
'name' => 'top_post' 'name' => 'top_post'
@@ -184,7 +187,7 @@ class Post extends Base
public function getPosterAttr($value) public function getPosterAttr($value)
{ {
if (empty($value)) { if (empty($value)) {
$value = '/static/images/avatar.jpeg'; $value = '/static/images/avatar.png';
} }
return get_source_link($value); return get_source_link($value);
@@ -233,4 +236,31 @@ class Post extends Base
} }
return $end_content; return $end_content;
} }
public function getHitsTitleAttr()
{
$cache_key = static::CACHE_KEY_HITS.$this->getAttr('id');
$value = Cache::get($cache_key);
if(!is_null($value)) {
return $value;
}
$value = $this->getData('hits');
Cache::set($cache_key, $value, 600);
return $value;
}
public function setHitsAttr($value)
{
$cache_key = static::CACHE_KEY_HITS.$this->getAttr('id');
$this->getAttr('hits_title');
Cache::inc($cache_key);
return $value;
}
} }

44
app/model/PostVisit.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
declare (strict_types=1);
namespace app\model;
use think\Model;
/**
* @mixin \think\Model
*/
class PostVisit extends Model
{
//
public function getAvatarSrcAttr()
{
$value = $this->getAttr('avatar');
if(empty($value)) {
$value = '/static/images/avatar.png';
}
return $value;
}
public function getNicknameTitleAttr()
{
$value = $this->getAttr('nickname');
if(empty($value)) {
$value = 'IP用户:'.$this->getAttr('ip');
}
return $value;
}
public function getCreateTimeTitleAttr()
{
$value = $this->getData('create_time');
return show_time_ago($value);
}
}

View File

@@ -19,7 +19,7 @@ class User extends Model
public function getAvatarAttr($value) public function getAvatarAttr($value)
{ {
if(empty($value)){ if(empty($value)){
return '/static/images/avatar.jpeg'; return '/static/images/avatar.png';
} }
return \get_source_link($value); return \get_source_link($value);

View File

@@ -0,0 +1,42 @@
<?php
use app\common\ColumnFormat;
use think\migration\Migrator;
class CreateTablePostVisit extends Migrator
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$table = $this->table('post_visit');
$table->addColumn(ColumnFormat::timestamp('create_time'));
$table->addColumn(ColumnFormat::timestamp('update_time'));
$table->addColumn(ColumnFormat::timestamp('delete_time'));
$table->addColumn(ColumnFormat::integer('post_id'));
$table->addColumn(ColumnFormat::stringUrl('ip'));
$table->addColumn(ColumnFormat::stringNormal('nickname'));
$table->addColumn(ColumnFormat::stringNormal('uid'));
$table->addColumn(ColumnFormat::stringUrl('avatar'));
$table->create();
}
}

View File

@@ -19,7 +19,7 @@ class InitAdmin extends Seeder
$account['account'] = 'admin'; $account['account'] = 'admin';
$account['salt'] = Str::random(6); $account['salt'] = Str::random(6);
$account['password'] = md5('123456'.$account['salt']); $account['password'] = md5('123456'.$account['salt']);
$account['avatar'] = '/static/images/avatar.jpeg'; $account['avatar'] = '/static/images/avatar.png';
$model_admin = Admin::where('account',$account['account'])->find(); $model_admin = Admin::where('account',$account['account'])->find();

View File

@@ -0,0 +1,4 @@
.ul-avatar-sidebar-list {
display: flex;
flex-wrap: wrap;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,7 +1,11 @@
<link rel="stylesheet" href="/static/css/reset.css"> <link rel="stylesheet" href="/static/css/reset.css">
<link rel="stylesheet" href="//layui.ulthon.com/cdn/layui-ul.css">
<link rel="stylesheet" href="/static/css/pagination.css"> <link rel="stylesheet" href="/static/css/pagination.css">
<link rel="stylesheet" href="/static/lib/layui/css/layui.css"> <link rel="stylesheet" href="/static/lib/layui/css/layui.css">
<link rel="stylesheet" href="/static/css/index.articles.css"> <link rel="stylesheet" href="/static/css/index.articles.css">
<link rel="stylesheet" href="/static/css/index.css">
<script src="/static/lib/jquery/jquery-3.4.1.min.js"></script> <script src="/static/lib/jquery/jquery-3.4.1.min.js"></script>
<script src="/static/lib/jquery/jquery.cookie.js"></script> <script src="/static/lib/jquery/jquery.cookie.js"></script>
<script src="/static/lib/layui/layui.js"></script> <script src="/static/lib/layui/layui.js"></script>

View File

@@ -74,6 +74,36 @@
</div> </div>
</div> </div>
</div> </div>
<div class="session border">
<div class="header">
<span>最近文章浏览</span>
<span style="font-size: 12px;">
{empty name='Request.session.user_uid'}
<a href="{$login_url|default=''}">点击登录</a>
{else /}
<a href="{:get_system_config('user_hub_host')}" target="_blank">{$user_info.nickname|default=$user_info.account}</a>
<a href="{:url('index/Logout',['back_url'=>$post.read_url])}">退出</a>
{/empty}
</span>
</div>
<div class="body">
<div class="ul-avatar-sidebar-list" style="margin-top: 15px;">
{volist name='list_site_last_visit' id='vo'}
<div class="ul-avatar-sidebar-list-item" style="width: 100%;">
<div class="ul-avatar-sidebar-list-item-img" style="background-image: url({$vo.avatar_src});"></div>
<div class="ul-avatar-sidebar-list-item-info">
<div class="ul-avatar-sidebar-list-item-title">{$vo.nickname_title}</div>
<div class="ul-avatar-sidebar-list-item-intro" style="color: #000;">{$vo.create_time_title}</div>
</div>
</div>
{/volist}
</div>
<div>累计文章浏览次数:{$total_hits}</div>
<div>一周文章浏览次数:{$total_week_hits}</div>
<div>今日文章浏览次数:{$total_day_hits}</div>
</div>
</div>
<div class="session"> <div class="session">
<div class="info"> <div class="info">

View File

@@ -95,13 +95,10 @@
<div class="plus-info"> <div class="plus-info">
<span style="margin-right: 15px;">[ {$post->publish_time_text} ]</span> <span style="margin-right: 15px;">[ {$post->publish_time_text} ]</span>
<span>{$post.hits}访问</span> <span>{$post.hits_title}访问</span>
<span>{$post.comment_count}评论</span> <span>{$post.comment_count}评论</span>
</div> </div>
</div> </div>
</a> </a>
{/switch} {/switch}
{/volist} {/volist}

View File

@@ -138,6 +138,26 @@
</div> </div>
<div class="post-container">
<fieldset class="layui-elem-field layui-field-title">
<legend>最近浏览</legend>
<div class="layui-field-box">
<div class="ul-avatar-sidebar-list">
{volist name='list_last_visit' id='vo'}
<div class="ul-avatar-sidebar-list-item" style="width: 25%;">
<div class="ul-avatar-sidebar-list-item-img" style="background-image: url({$vo.avatar_src});"></div>
<div class="ul-avatar-sidebar-list-item-info">
<div class="ul-avatar-sidebar-list-item-title">{$vo.nickname_title}</div>
<div class="ul-avatar-sidebar-list-item-intro">{$vo.create_time_title}</div>
</div>
</div>
{/volist}
</div>
<div class="layui-word-aux" >累计浏览次数:{$post->hits_title}</div>
</div>
</fieldset>
</div>
<div class="post-container"> <div class="post-container">
<fieldset class="layui-elem-field layui-field-title"> <fieldset class="layui-elem-field layui-field-title">
@@ -175,8 +195,6 @@
<a href="{:get_system_config('user_hub_host')}" target="_blank" class="layui-word-aux">{$user_info.nickname|default=$user_info.account}</a> <a href="{:get_system_config('user_hub_host')}" target="_blank" class="layui-word-aux">{$user_info.nickname|default=$user_info.account}</a>
<a href="{:url('index/Logout',['back_url'=>$post.read_url])}" class="layui-word-aux">退出</a> <a href="{:url('index/Logout',['back_url'=>$post.read_url])}" class="layui-word-aux">退出</a>
{/empty} {/empty}
</div> </div>
</form> </form>
</div> </div>