在WordPress站點中展示閱讀量等流量分析數據(超詳細實現)

這篇文章也可以在我的博客中查看

關于本文

專業的流量統計系統能夠相對真實地反應網站的訪問情況。
這些數據可以在后臺很好地進行分析統計,但有時我們希望在網站前端展示一些數據

最常見的情景就是:展示頁面的瀏覽量
這簡單的操作當然也可以通過簡單的計數器實現,但可能會造成重復統計(比如同一個用戶點擊10次)

目標

流量分析工具所提供的準確性是不可比擬的
因此這篇文章我們就來實現如何將流量分析數據搬到網站展示,做到:

  1. 同步流量分析工具數據到網站前端
    • 顯示頁面的閱讀量
  2. 不影響頁面加載
    • 用戶不會感知到同步任務進行
  3. 不頻繁訪問分析工具API
    • 減少網絡資源、API次數消耗

準備

為完成這些目標,需要一些前提準備:

  1. 配置好帶有數據訪問API的流量分析工具
    • Google AnalyticsUmami(本文將以Umami為例)
    • 這是我們的真實數據來源
  2. 配置好WordPress后臺進程(Background Process)支持
    • 如Action-Scheduler(本文將以此為例)
    • 這是我們非阻塞運行的基礎

分析問題

Analytics類

分析問題

API訪問頻率

閱讀量實時性并不強,我們無須(也不可能)每次頁面訪問都從遠程分析工具獲取數據
頻繁訪問很有可能會被禁止訪問API,(自建的相當于DDoS攻擊自己😅)
在獲取數據后,應該在短時間內緩存起來

WordPress中的跨請求緩存API是transient

處理緩存未命中

但如果緩存未命中怎么辦?是立刻訪問遠程分析工具嗎?
不可能,這樣同步執行會使頁面加載阻塞
特別是:如果你一次展示多篇文章,你需要等待它們全部完成才能加載出頁面!

因此我們必須在本地數據庫也持久化存儲閱讀量
這個冗余數據是緩存未命中時的唯一可行數據來源

在WordPress中,我們可以使用post_meta存儲它

與此同時,這也可作為數據過時的標志:
我們應該觸發更新閱讀量的后臺進程
非阻塞地將第三方分析工具的數據同步到本地上

小結

Analytics.php的是用于頁面獲取數據的接口。它的數據來源是:

  1. 內存緩存
    • 減少短期重復訪問,減少服務器壓力
  2. 本地數據庫
    • 緩存未命中時的保底數據
  3. 遠程分析工具
    • 數據更新的途徑

它的職責是:

  1. 讀寫本地數據
  2. 發出更新請求

實現

注意組織文件結構,本文將/App文件夾作為根目錄

/App/Services/Analytics/創建Analytics.php文件

編寫Analytics類,它主要包含一些靜態函數

namespace App\Services\Analytics {class Analytics{public static function getPageViews(WP_Post|int $post){}public static function setPageViews(WP_Post|int $postId, $newViews){}}
}

getPageViews

本文實現需要依賴$post->ID作為唯一標識符
如果你希望實現任何頁面的閱讀量展示,你需要:

  1. 使用url[path]md5 hash作為唯一標識符
  2. 使用自定義數據庫表存儲閱讀量:(url_md5, page_view)

需要做什么?
當訪客來訪時,需要展示閱讀量,此時:

  1. 我們需要獲取目標地址的WP_Post實例
    • 以獲取url等信息
  2. 有緩存讀緩存
  3. 無緩存讀數據庫
    1. (不阻塞執行)請求第三方流量分析API,更新記錄
    2. 馬上使用舊數據刷新緩存

前面提到了緩存過期是發出數據同步請求的標志,但我們不希望重復發起請求,
因此緩存未命中時需要馬上再次寫入緩存。

雖然數據是舊的,但不急。我們可以在數據同步時強制刷新它


大部分都好處理,異步請求比較麻煩,先賣個關子
同時我們還為閱讀量定義了緩存鍵值和在數據庫的meta鍵值:

protected static string $pageViewMetaKey = 'page_views';
protected static int $pageViewCacheTime = HOUR_IN_SECONDS;
protected static function pageViewsCacheKey(int $postId)
{return static::$pageViewMetaKey . '_' . $postId;
}public static function getPageViews(WP_Post|int $post)
{if (!($post instanceof WP_Post))$post = get_post($post);if (empty($post)) return 0;// 嘗試獲取緩存$pageViews = get_transient(Analytics::pageViewsCacheKey($post->ID));if ($pageViews !== false) return $pageViews;// 記錄更新請求// <-- ?? async call to update ?? -->// 讀取數據庫記錄,這將是最后能夠返回的值$pageViews = get_post_meta($post->ID, Analytics::$pageViewMetaKey, true) ?: 0;// 重寫緩存set_transient(Analytics::pageViewsCacheKey($post->ID), $pageViews, static::$pageViewCacheTime);return $pageViews;
}

setPageViews

這個函數用于寫入本地的數據存儲,包括緩存和數據庫
注意,它并不包含異步更新的過程,只是異步更新的結果需要借助它寫入:

public static function setPageViews(WP_Post|int $postId, $newViews)
{if ($postId instanceof WP_Post)$postId = $postId->ID;// 更新緩存set_transient(Analytics::pageViewsCacheKey($postId), $newViews, static::$pageViewCacheTime);// 寫到數據庫update_post_meta($postId, Analytics::$pageViewMetaKey, $newViews);
}

Provider

好了,該想想怎么訪問遠程API了
Analytics因為大多為固定操作,我們實現為靜態
但是更新數據來源的邏輯呢?

不同的流量分析工具會提供不同的API,因此我們也需要為它們編寫各自的處理邏輯
我們需要根據設置為Analytics注入一個恰當的數據來源實例,這里稱為Provider

先關注Analytics類中需要如何支持注入Provider

沒使用任何框架,我只能純手工注入
以下代碼是額外增加內容,需要與上文合并

class Analytics
{private static Closure|AnalyticsProvider $_provider;public static function setProvider(callable|AnalyticsProvider $provider){if (is_callable($provider))static::$_provider = Closure::fromCallable($provider);elsestatic::$_provider = $provider;}protected static function getProvider(): AnalyticsProvider{if (static::$_provider instanceof Closure)static::$_provider = (static::$_provider)();return static::$_provider;}
}

我們需要先setProvider設置使用的數據源,后續使用getProvider獲取它

因為某些provider可能會很沉重,這里支持傳入一個返回AnalyticsProviderClosure
以實現懶加載,只有需要使用它的時候才會生成

接下來再看看provider需要怎么編寫

AnalyticsProvider類

不同的provider有不同的訪問邏輯,但至少有沒有些共性?
還真有!

需要未雨綢繆的問題

Provider負責組織后臺任務,但每次請求更新都立刻組織一個后臺任務還是很恐怖的。

比如:一個頁面有100篇文章
每當Analytics::getPageViews緩存未命中時,就組織后臺任務
此時需要組織100個任務

因為php無守護進程,每個后臺任務其實需要通過寫數據庫進行任務信息持久化
因此組織100個后臺任務,意味著訪問數據庫上百次

而組織任務這個過程,是同步的、阻塞的
用戶會看著頁面轉十秒加載不出來

但說到底,有沒有必要把它視為100個任務?不能批處理一下嗎?
當然可以,而且這就是不同AnalyticsProvider的一個共性。

實現

/App/Services/Analytics/創建AnalyticsProvider.php文件

編寫Analytics

namespace App\Services\Analytics {abstract class AnalyticsProvider{}
}

pushUpdatePostViews

這是登記更新任務的邏輯
上文說了,我們不希望立刻生成后臺任務,而是記錄它:

protected array $updatesList = [];/*** 將目標加入瀏覽量更新任務隊列* @param array $args 查詢需要的參數,與具體實現有關*/
public function pushUpdatePostViews(WP_Post $post, array $args = [])
{$this->updatesList[$post->ID] = $args;
}

$args主要是請求API時的參數,比如:時間段?目標地址?國家?……
這與具體數據源的實現有關,但總之,我們需要把這些可能用到的數據存到$updatesList

$updatesList記錄了本次請求中,所有需要請求閱讀量更新的文章和相應參數
但我們如何把它加到后臺任務?

submitTasks()

submitTasks由子類負責給出任務提交的邏輯
父類只需要給出約束

abstract public function submitTasks();

沒完,我們需要有人在最后調用這個函數,才能完成所有任務一次性提交
可以利用WordPress的shutdownhook

public function __construct()
{add_action('shutdown', [$this, 'submitTasks']);
}

因為shutdown是WordPress最后一個hook,因此不用擔心之后還會有新的任務提交請求

注意,WordPress hook的回調必須是public函數

調用

還記得Analytics::getPageViews的空缺位置嗎?
它應該調用AnalyticsProvider

public static function getPageViews(WP_Post|int $post)
{// ...// <-- ?? async call to update ?? -->static::getProvider()->pushUpdatePostViews($post);// ...
}

注意:static在上下文中就是Analytics

具體的AnalyticsProvider

主要完成兩件事:

  1. 完成任務提交邏輯
  2. 封裝處理參數

以下我以Umami為例

/App/Services/Analytics/Umami創建UmamiAnalyticsProvider.php文件
編寫UmamiAnalyticsProvider類:

namespace App\Services\Analytics\Umami {use WP_Post;use App\Services\Analytics\AnalyticsProvider;class UmamiAnalyticsProvider extends AnalyticsProvider{public function submitTasks(){if ($this->updatesList) {// <-- ?? submit this background task ?? -->}}public function pushUpdatePostViews(WP_Post $post, array $args = []){$args['path'] = parse_url(get_permalink($post))['path'];parent::pushUpdatePostViews($post, $args);}}
}
  1. Umami API獲取閱讀量必須提供頁面的path,因此我重寫pushUpdatePostViews并按id獲取了它的path
  2. submitTask先檢測了是否真有待提交任務數據,如有,提交
  • 具體提交邏輯見下文

后臺任務

萬事俱備,只欠東風
我們只剩下后臺任務需要解決了,但你先別急
這篇文章目前只到一半

本文將使用Action Scheduler作為后臺任務的驅動
但不管你是否使用它,后文的task結構都可以給你一點靈感

Action-Scheduler

Action Scheduler基本上是WordPress中支持后臺進程的唯一選擇了
它的官方例子如下:

require_once( plugin_dir_path( __FILE__ ) . '/libraries/action-scheduler/action-scheduler.php' );/*** Schedule an action with the hook 'eg_midnight_log' to run at midnight each day* so that our callback is run then.*/
function eg_schedule_midnight_log() {if ( false === as_has_scheduled_action( 'eg_midnight_log' ) ) {as_schedule_recurring_action( strtotime( 'tomorrow' ), DAY_IN_SECONDS, 'eg_midnight_log', array(), '', true );}
}
add_action( 'init', 'eg_schedule_midnight_log' );/*** A callback to run when the 'eg_midnight_log' scheduled action is run.*/
function eg_log_action_data() {error_log( 'It is just after midnight on ' . date( 'Y-m-d' ) );
}
add_action( 'eg_midnight_log', 'eg_log_action_data' );

這個例子將在每天午夜輸出一個log

但這例子其實有個坑,Action Scheduler的執行機制事實上跨越了2次php執行

  1. 第一次,制定任務
    1. 使用as_schedule_recurring_action制定任務
    2. 此時eg_midnight_loghook無效
  2. 第二次,午夜時執行任務(可能由cron或其它機制觸發)
    1. 它從數據庫中檢測到預定的任務,生成eg_midnight_loghook
    2. 執行eg_midnight_loghook的邏輯

所以坑點就在于add_action( 'eg_midnight_log', 'eg_log_action_data' );必須在執行任務時加入,在制定任務時加入是無效

而我們的目標,則是:

  1. 把2次php執行的代碼盡可能地透明化,封裝起來
  2. 使用面向對象的思想處理任務,使其模塊化

TaskManager類

TaskManager主要用于負責所有任務的提交和觸發,我的實現主要針對Action Scheduler,如果使用其它后臺任務庫,該類需要做對應修改。

在閱讀前,建議先了解Action Scheduler的基本操作

實現

/App/Services/Task創建TaskManager.php文件
編寫TaskManager類:

namespace App\Services\Task {class TaskManager{protected static array $taskList;public static function init(){}public static function registerTask($taskName){static::$taskList[] = $taskName;}public static function submitTask(string $handlerType, array $taskMeta, array $taskParams): int{}}
}

registerTask用于記錄所有需要管理的任務名,它的作用只是將名字加入$taskList列表

submitTask

用于提交“保證任務觸發時正常執行”所需的一切數據,包括:

  1. 交給誰處理(給誰處理)
  2. 執行處理的指引(怎么處理)
  3. 需要處理的數據(處理什么)

因此它需要傳入3個參數:

  1. $handlerType: 承載任務處理邏輯的類名
    • 后文會詳細介紹,它的基類是Task,包含一個handleTask方法
  2. $taskMeta: 承載任務處理的元數據
    • 比如任務時限?重試次數?
    • 反正是與任務相關,但與任務執行主體無關
  3. $taskParams: 任務執行所需的數據
    • 比如我們需要訪問api,那可能就是api參數等等

因此可以寫出這樣的代碼:

public static function submitTask(string $handlerType, array $taskMeta, array $taskParams): int
{if (!$handlerType) return 0;$args = ['handler' => $handlerType, 'meta' => $taskMeta, 'params' => $taskParams];return as_enqueue_async_action($handlerType::$taskName, $args, md5(json_encode($args)), true);
}
  1. 使用Action Scheduler提供的as_enqueue_async_action,將任務數據移交至其托管。
  2. 所有$args參數將被Action Scheduler存儲于數據庫,當執行時取出
    • 有點像序列化
  3. $taskNameTask類的靜態變量,表示任務名
    • 因為Task與任務直接關聯,因此任務名就存在它那了
  4. 防止完全重復任務
    1. 標記為唯一任務(第四個參數unique:true
    2. 計算參數的md5作為分組,用于識別重復任務

init

init需要在每次執行、所有registerTask調用結束后調用,它用于監聽后臺任務是否已觸發,如果是,則分配到相應的處理函數

public static function init()
{require_once(get_template_directory() . '/vendor/woocommerce/action-scheduler/action-scheduler.php');/*** 監聽事件觸發并轉交給handler*/foreach (static::$taskList as $taskName) {add_action($taskName, function (string $handlerType, array $meta, array $params) {$provider = new $handlerType();$provider->handleTask($meta, $params);}, 10, 3);}
}

首先需要引入Action Scheduler文件,然后對每個注冊的任務名,都使用監聽函數(這里實現為匿名函數)訂閱它的action hook

當事件觸發時,這個函數將獲得我們從TaskManager::submitTaask()中傳入的3個參數:

  1. $handlerType: 任務處理邏輯的類名
    • 用于動態生成負責處理事件的handler對象$provider = new $handlerType();
    • 調用它的Task::handleTask方法
  2. $meta: 承載任務處理的元數據
    • 將其轉交給handler
  3. $params: 任務執行所需的數據
    • 將其轉交給handler

當某個任務真正觸發時,其對應的action hook就會被觸發,然后由監聽函數轉發至真正的執行邏輯

Task類-任務處理類

Task代表了一個任務,它包括:
任務名、任務提交邏輯、任務執行邏輯

實現

/App/Services/Task創建Task.php文件
編寫Task類:

namespace App\Services\Task {use Exception;abstract class Task{public static string $taskName;/*** 提交一個該類型的任務,需要提供必要元數據和執行參數*/public static function submitTask(int $maxRetry, array $taskParams){}/*** 對應任務觸發時的執行邏輯* @param mixed $taskMeta 任務元數據* @param mixed $taskParams 任務處理數據* @throws Exception 若任務未全部完成,拋出異常*/public function handleTask(array $taskMeta, array $taskParams){// ...$this->handle($taskParams);// ...}/*** 任務邏輯主體* @param mixed $taskParams 傳入給該任務的參數* @return mixed */protected abstract function handle($taskParams);}
}

submitTask

submitTask()是對TaskManager提交函數的簡單封裝:

  1. 因為自身存儲了$taskName,因此它可以省略TaskManager的第一個參數
  2. 元數據可以明確限定
    • 比如我只需要重試次數,我就只把它當做輸入參數,然后封裝成meta

具體編寫為以下邏輯:

public static function submitTask(int $maxRetry, array $taskParams)
{$taskMeta = ['retry' => $maxRetry];TaskManager::submitTask(static::class, $taskMeta, $taskParams);
}

handleTask

前面也提到了,handleTask是最終用于處理任務的邏輯
它其實有兩個作用:

  1. 準備、善后處理
    • 接受任務元數據,先進行準備
  2. 處理任務
    • 接受任務參數,真正處理任務

在這里,“準備、善后”部分我只用作處理重試邏輯
處理任務的邏輯我把它分割到另一個handle方法,由子類實現

handleTask應在成功時返回假,失敗時返回需要任務再次執行所需的參數

public function handleTask(array $taskMeta, array $taskParams)
{$pushBacks = $this->handle($taskParams);/*** 任務失敗了,需要重新push任務:* 1. 有需要執行的東西* 2. 有retry的定義且不為0*/if (!empty($pushBacks)) {if (!empty($taskMeta['retry'])) {$taskMeta['retry'] -= 1;TaskManager::submitTask(static::class, $taskMeta, $pushBacks);throw new Exception("Retries have been scheduled for some uncompleted tasks. params are: " . var_export($pushBacks, true));} elsethrow new Exception("Some of tasks failed. params are: " . var_export($pushBacks, true));}
}

exception將由Action Scheduler處理并顯示在控制臺中

PageViewTask-具體的任務類

真正的功能類繼承自Task類,這里需要編寫訪問遠程分析工具,并返回頁面瀏覽量的邏輯
因此命名為PageViewTask

同樣地,具體的PageViewTask依靠于具體的遠程分析工具API
但在這層抽象中,我們只關注它們的共性:都需要失敗重試

實現

/App/Services/Analytics創建PageViewTask.php文件
編寫PageViewTask類:

namespace App\Services\Analytics {use App\Services\Task\Task;use Excecption;abstract class PageViewTask extends Task{public static string $taskName = 'nova_page_view_task';protected function handle($updatesList){foreach ($updatesList as $postId => $args) {try {$views = $this->getPostView($args);Analytics::setPageViews($postId, $views);// 刪掉unset($updatesList[$postId]);} catch (\Exception $e) {// 無視}}return $updatesList;}abstract protected function getPostView($args): int;}
}

首先別忘了我們需要給任務起名$taskName

php的靜態多態太爽了
C#什么時候能站起來()

handle()這段邏輯呼應了我們遠古時代實現的AnalyticsProvider::$updatesList邏輯
我們為了節省開銷,將多次閱讀量更新捆綁成一次提交
因此$updatesList包含的是一個列表的待更新文章

我們在foreach循環中分割成單個更新,再次踢皮球到getPostView交給子類處理

然后更新過程中的try ctach就有點秀了:

  • 如果沒出意外,我們把它從列表中移除,意為不再需要
  • 如果出了意外,將被catch,并跳轉到foreach下個循環

所以一頓操作后,最終執行失敗的參數會保留在$updateList
將它返回,則會觸發父類的重試邏輯,再次壓入后臺進程隊列

妙妙妙妙妙

具體的PageViewTask

每個遠程統計工具實現不同,所以這層是必須的
這里還是以Umami為例,其它的也差不多,只是需要修改訪問的參數

/App/Services/Analytics/Umami創建UmamiPageViewTask.php文件
編寫UmamiPageViewTask類:

namespace App\Services\Analytics\Umami {use Exception;use App\Services\Analytics\PageViewTask;class UmamiPageViewTask extends PageViewTask{protected function getPostView($args): int{// 獲取secret$baseUrl = of_get_option('analytics_api_domain', '');$authToken = of_get_option('analytics_api_token', '');// header$headers = array('Authorization' => "Bearer $authToken",'Content-Type' => 'application/json','Accept' => 'application/json',);// 向umami發送請求$umami_url = trailingslashit($baseUrl) . 'stats' . '?' . http_build_query(['startAt' => '0','endAt' => time() . '000','url' => $args['path'],]);$response = wp_remote_get($umami_url, ["headers" => $headers]);if (is_wp_error($response))throw new Exception($response->get_error_message());if (!empty($response['body']))$data = json_decode($response['body'], true);return \intval($data['uniques']['value']) ?? 0;}}
}

這段代碼因為比較簡單,也直接給出了
需要提醒的是:

  1. 重要數據不要硬編碼在代碼中,在WordPress中可以使用控制臺的設置功能
    • 不過這里用到的of_get_option是裝了options framework插件
  2. 大部分參數都可以自身構造而來,真正從外部接受的參數其實就只有:$args['path']
  3. 我們在$responseWP_Error時拋出異常,以示意出錯
    • 出錯的主要原因是網絡連接不佳,因此我們需要拋出錯誤,并重試
    • 返回401,404等不算出錯,有返回的情況反而沒有重試的必要
      • 因為試幾次都是一樣的
  4. 返回的處理取決于返回數據,這里是順著Umami的返回寫的

化身為神的最后一塊拼圖!

ruaaaaaaaaaaaaaaaaaaaaa

還記得嗎?之前的代碼有一段空了一塊
UmamiAnalyticsProvider提交任務時,沒有給出具體的操作代碼

因為當時還沒引入后面的一堆
但現在,我們都是懂哥了
加入這句代碼,讓這個系統運作起來:

class UmamiAnalyticsProvider extends AnalyticsProvider
{public function submitTasks(){if ($this->updatesList) {// <-- ?? submit this background task ?? -->UmamiPageViewTask::submitTask(1, $this->updatesList);}}
}

調用UmamiPageViewTask::submitTask()

  1. 參數1:重試1次
  2. 參數2:更新若干文章的必要數據

初始化

最后,我們需要初始化TaskManager,如果不初始化,沒有任務會被監聽
不管需不需要加入新任務,請確保每次php執行都會執行以下語句:

use App\Services\Analytics as Analytics;
use App\Services\Task\TaskManager;Analytics\Analytics::setProvider(new Analytics\Umami\UmamiAnalyticsProvider());
TaskManager::registerTask(Analytics\PageViewTask::$taskName);
TaskManager::init();
  1. 記得設置Provider,當然你也可以傳入Closure實現懶加載
    • e.g. fn() => new UmamiAnalyticsProvider();
  2. 記得注冊(TaskManager::registerTask所有可能執行的任務
    • 注冊開銷并不大,不要省
    • 省了任務絕對執行不了
  3. 在最后,記得調用init(),否則不會進行任何實質初始化操作

小結

花了好久,寫了這么多
包括代碼,包括文章

這過程中不止一次問自己,至于嗎?
我最終的答案是肯定的

至于把東西封裝到類里嗎?多繞啊

確實繞,甚至是俄羅斯套娃
但在理解了繞之后,帶來的是可拓展性、可維護性

當然也可以直接一步步寫下來
實不相瞞,我第一個版本就是一步步寫下去的,根本就沒有一個類

但這樣做,怎么進行拓展?
不同的代碼混在一起,怎么維護?

所以就算是花更多時間,在把這坨屎跑起來之后,都要給它框架化、規則化
消化了這坨小屎,才能避免整個程序變成大屎

框架本身增加復雜性,但它也帶來了規則性:
有了框架,就很容易借用相似的邏輯
有了框架,一切東西都井然有序

現在這個版本,你可以隨意增加更多的Task,邏輯都是一樣的
多舒服啊?

至于把問題想那么復雜嗎?

至于訪問遠程統計工具獲取精準數據嗎?
至于搞緩存嗎?
至于搞后臺進程嗎?

沒錯,要實現“顯示瀏覽量”可以很簡單
甚至不精準的統計數據,可以增加我網站的顯示訪問量(草,現在全是個位數)

但當把程序當做一種藝術,它就不能容忍湊合
精益求精,才是工匠精神

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/38940.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/38940.shtml
英文地址,請注明出處:http://en.pswp.cn/news/38940.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(el-Form)操作(不使用 ts):Element-plus 中 Form 表單組件校驗規則等的使用

Ⅰ、Element-plus 提供的 Form 表單組件與想要目標情況的對比&#xff1a; 1、Element-plus 提供 Form 表單組件情況&#xff1a; 其一、Element-plus 自提供的 Form 代碼情況為(示例的代碼)&#xff1a; // Element-plus 自提供的代碼&#xff1a; // 此時是使用了 ts 語言環…

6.3 社會工程學攻擊

數據參考&#xff1a;CISP官方 目錄 社會工程學攻擊概念社會工程學攻擊利用的人性 “弱點”典型社會工程學攻擊方式社會工程學攻擊防護 一、社會工程學攻擊概念 什么是社會工程學攻擊 也被稱為 "社交工程學" 攻擊利用人性弱點 (本能反應、貪婪、易于信任等) 進…

螢石直播以及回放的接入和銷毀

以下基于vue項目 1.安裝 npm i ezuikit-js 2、導入 main.js中 import EZUIKit from "ezuikit-js"; //導入螢石Vue.use(EZUIKit); 3、創建容器 <div class"video"><div id"video-container"></div><!-- <iframe :src…

棧存儲結構詳解

目錄 棧存儲結構詳解 進棧和出棧 棧的具體實現 棧的應用 什么是隊列&#xff08;隊列存儲結構&#xff09; 棧存儲結構詳解 同順序表和鏈表一樣&#xff0c;棧也是用來存儲邏輯關系為 "一對一" 數據的線性存儲結構&#xff0c;如圖 1 所示。 圖 1 棧存儲結構示意…

HTML5的介紹和基本框架

目錄 HTML5 HTML5介紹 HTML5的DOCTYPE聲明 HTML5基本骨架 html標簽 head標簽 body標簽 title標簽 meta標簽 在vscode中寫出第一個小框架 HTML5 HTML5介紹 HTML5是用來描述網頁的一種語言&#xff0c;被稱為超文本標記語言。用HTML5編寫的文件&#xff0c;后綴以.ht…

設備加密狗

場景描述 隨著科技的飛速發展&#xff0c;越來越多的智能設備走進生產加工車間。例如智能雕刻機、鈑金機、 榫槽機、鉆孔機、磨刀機等等。 目前市場的智能設備具有一個共同的特點&#xff0c;內置嵌入操作系統&#xff0c;如windows或者linux系統。設備制造商提供智能設備出…

20天學會rust(四)常見系統庫的使用

前面已經學習了rust的基礎知識&#xff0c;今天我們來學習rust強大的系統庫&#xff0c;從此coding事半功倍。 集合 數組&可變長數組 在 Rust 中&#xff0c;有兩種主要的數組類型&#xff1a;固定長度數組&#xff08;Fixed-size Arrays&#xff09;和可變長度數組&…

Ajax如何理解

什么是ajax ●認識前后端交互 ○就是 前端 與 后端的 一種通訊方式 ○主要使用的技術棧就是 ajax (async javascript and xml) ●ajax 特點 ○使用 ajax 技術網頁應用能夠快速的將新內容呈現在用戶界面 ○并且不需要刷新整個頁面, 也就是能夠讓頁面有 "無…

Java技術整理(5)—— Spring篇

Spring是一個全面的全面的、企業應用開發一站式的解決方案&#xff0c;貫穿表現層、業務層、持久層。但是 Spring 仍然可以和其他的框架無縫整合。 1、Spring的核心組件 &#xff08;1&#xff09;數據層&#xff1a; JDBC、ORM、OXM、JMS、Transations &#xff08;2&#x…

Flutter 中,ListView 中需要放置 ListView 需要怎么處理才高效?

問題及場景 ListView 是 Flutter 開發者第一個學習到的 Widget&#xff0c;因為它可以滑動。一切都會運行得很好&#xff0c;直到 ListView 中的 Item 本身也是一個 ListView。你可能會看到 Flutter 建議你將內部的 ListView 的ShrinkWrap 屬性設置為 True。雖然錯誤消除了&am…

連續兩年增收不增利,比亞迪電子靠新能源汽車業務再次起飛?

在凈利潤連續兩年下挫之后&#xff0c;比亞迪電子&#xff08;00285.HK&#xff09;終于迎來了好消息。 不久前比亞迪電子發布2023年中期盈利預告顯示&#xff0c;上半年凈利潤同比增加115%-146%&#xff08;2022年上半年的凈利潤顯示6.34億元&#xff09;。 這主要受益于大客…

包管理工具 nvm npm nrm yarn cnpm npx pnpm詳解

包管理工具 nvm npm yarn cnpm npx pnpm npm、cnpm、yarn、pnpm、npx、nvm的區別&#xff1a;https://blog.csdn.net/weixin_53791978/article/details/122533843 npm、cnpm、yarn、pnpm、npx、nvm的區別&#xff1a;https://blog.csdn.net/weixin_53791978/article/details/1…

【Freertos基礎入門】2個Freertos的Delay函數

文章目錄 前言一、vTaskDelay與vTaskDelayUntil二、示例代碼總結 前言 本系列基于stm32系列單片機來使用freerots 任務管理是實時操作系統&#xff08;RTOS&#xff09;的核心功能之一&#xff0c;它允許開發者以并發的方式組織和管理多個任務。FreeRTOS 是一個流行的開源RTO…

嵌入式開發中常用且雜散的命令

1、mount命令 # 掛載linux系統 mkdir /tmp/share mount -t nfs 10.77.66.88:/share/ /tmp/share -o nolock,tcp cd /tmp/share# 掛載Windows系統 mkdir /tmp/windows mount -t nfs 10.66.77.88:/c/public /tmp/windows -o nolock,tcp cd /tmp/windows# 掛載vfat格式的U盤 mkdi…

強訓第32

選擇 D B A A 發送TCP意思應該是已經建立了連接&#xff0c;會超時重傳。在未建立連接的時候&#xff0c;會放棄該鏈接 C A 80端口是http A 交換機攻擊主要有五種&#xff1a;VLAN跳躍攻擊 生成樹攻擊 MAC表洪水攻擊 ARP攻擊 VTP攻擊 B A 2^(32-26)2^(32-27)2^(32-27)128 減去…

基于Java+SpringBoot+Vue+echarts健身房管理系統設計和實現

博主介紹&#xff1a;?全網粉絲30W,csdn特邀作者、博客專家、CSDN新星計劃導師、Java領域優質創作者,博客之星、掘金/華為云/阿里云/InfoQ等平臺優質作者、專注于Java技術領域和畢業項目實戰? &#x1f345;文末獲取源碼聯系&#x1f345; &#x1f447;&#x1f3fb; 精彩專…

maven Jar包反向install到本地倉庫

maven Jar包反向install到本地倉庫 需求實現 需求 項目打包時報錯&#xff0c;缺少一個jar包。 但是在maven倉庫都找不到此jar包&#xff0c;其他人提供了這個jar包。 需要把這個jar包install到本地倉庫&#xff0c;使項目能正常打包運行。 實現 使用git bash命令執行以下腳…

16.3.4 【Linux】系統資源的觀察

free &#xff1a;觀察內存使用情況 系統當中有 2848MB 左右的實體內存&#xff0c;我的 swap 有 1GB 左右&#xff0c; 那我使用free -m 以 MBytes 來顯示時&#xff0c;就會出現上面的信息。Mem 那一行顯示的是實體內存的量&#xff0c;Swap 則是內存交換空間的量。 total 是…

C++多態

文章目錄 &#x1f435;1. 什么是多態&#x1f436;2. 構成多態的條件&#x1f429;2.1 虛函數&#x1f429;2.2 虛函數的重寫&#x1f429;2.3 final 和 override關鍵字&#x1f429;2.4 重載、重寫、重定義對比 &#x1f431;3. 虛函數表&#x1f42f;4. 多態的原理&#x1f…

神經網絡基礎-神經網絡補充概念-17-計算神經網絡的輸出

計算神經網絡的輸出通常涉及前向傳播&#xff08;Forward Propagation&#xff09;的過程&#xff0c;其中輸入數據通過網絡的層級結構&#xff0c;逐步被傳遞并變換&#xff0c;最終生成預測結果。下面我將為你展示一個簡單的神經網絡前向傳播的示例。 假設我們有一個具有以下…