当前位置: 首页 > news >正文

PostgreSQL源码分析——审计插件pgaudit

PostgreSQL审计插件pgaudit

在PostgreSQL中,提供了开源的审计插件pgaudit,但是其功能并不完善,只提供了基本的审计功能,对此,很多基于PG开发的商业数据库大多提供了丰富的审计功能。比如人大金仓,openGauss等。这里,我们不去讨论审计功能是否丰富,我们分析一下pgaudit的实现,已便我们后续开发自己的审计插件。

pgaudit的使用

安装

配置postgresql.conf,添加以下内容:

shared_preload_libraries = 'pgaudit'

创建插件:

CREATE EXTENSION pgaudit;

查看可配置参数:

postgres=# select name,setting from pg_settings where name ~ 'pgaudit';name            | setting 
----------------------------+---------pgaudit.log                | none		-- 指定会话审计日志记录哪些类型的语句pgaudit.log_catalog        | onpgaudit.log_client         | offpgaudit.log_level          | logpgaudit.log_parameter      | offpgaudit.log_relation       | offpgaudit.log_rows           | offpgaudit.log_statement      | onpgaudit.log_statement_once | offpgaudit.role               | 
(10 rows)

具体个参数的设置,可参考文档: https://github.com/pgaudit/pgaudit/blob/master/README.md

配置审计角色

postgres=# create role pgaudit with password 'pgaudit' login;
CREATE ROLE
postgres=# alter system set pgaudit.role = 'pgaudit';
ALTER SYSTEM
postgres=# select pg_reload_conf();pg_reload_conf 
----------------t
(1 row)postgres=# show pgaudit.role;pgaudit.role 
--------------pgaudit
(1 row)

实例:

设置记录write,以及ddl语句
postgres=# alter system set pgaudit.log = 'write,ddl';
ALTER SYSTEM
postgres=# select pg_reload_conf();pg_reload_conf 
----------------t
(1 row)
postgres=# insert into t1 values(3,3);
INSERT 0 1

查看日志,后面那条是审计日志:

2024-06-01 14:44:52.006 CST [27675] LOG:  statement: insert into t1 values(3,3);
2024-06-01 14:44:52.006 CST [27675] LOG:  AUDIT: SESSION,3,1,WRITE,INSERT,,,"insert into t1 values(3,3);",<not logged>

源码分析

总体上审计插件的设计思路有点类似于日志库的设计。首先是要获取审计日志信息,需要先通过配置设置审计哪些内容,比如审计DDL语句,还是DML语句,或者都进行审计。当然,这里审计规则可以设置的非常灵活,商业数据库的审计功能会更加的完善。然后还需要设置审计日志输出哪些内容,比如,语句类型,执行结果是否成功,执行影响的行数等等。配置好这些后,就是具体的获取审计日志的信息了。具体实现上就是在语句执行的过程中通过钩子函数捕获相应的信息,实现上要在不同的位置设置不同的钩子函数,比如DDL语句,则可在ProcessUtility_hook中执行自己的钩子函数,因为每个DDL语句都会走这里,具体的可以见下面的代码分析。

void ProcessUtility(PlannedStmt *pstmt,const char *queryString,bool readOnlyTree,ProcessUtilityContext context,ParamListInfo params,QueryEnvironment *queryEnv,DestReceiver *dest,QueryCompletion *qc)
{// 审计插件钩子函数 pgaudit_ProcessUtility_hookif (ProcessUtility_hook)(*ProcessUtility_hook) (pstmt, queryString, readOnlyTree,context, params, queryEnv,dest, qc);elsestandard_ProcessUtility(pstmt, queryString, readOnlyTree,context, params, queryEnv,dest, qc);
}
加载插件

总体源码并不多:当加载插件时,会执行_PG_init函数,该函数会定义GUC变量,以及安装钩子。还会执行pgaudit--1.7.sql
GUC参数:

  • pgaudit.log
  • pgaudit.log_catalog
  • pgaudit.log_client
  • pgaudit.log_level
  • pgaudit.log_parameter
  • pgaudit.log_relation
  • pgaudit.log_rows
  • pgaudit.log_statement
  • pgaudit.log_statement_once
  • pgaudit.role

钩子函数:

  • pgaudit_ExecutorStart_hook;
  • pgaudit_ExecutorCheckPerms_hook;
  • pgaudit_ProcessUtility_hook;
  • pgaudit_object_access_hook;
  • pgaudit_ExecutorRun_hook;
  • pgaudit_ExecutorEnd_hook;

如果要实现更多的功能,可以在更多的位置加钩子函数

/* Define GUC variables and install hooks upon module load. */
void _PG_init(void)
{/* Be sure we do initialization only once */static bool inited = false;  // 确保只被加载一次if (inited)return;/* Must be loaded with shared_preload_libraries */if (!process_shared_preload_libraries_in_progress)ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),errmsg("pgaudit must be loaded via shared_preload_libraries")));// 下面是定义插件中的几个GUC参数/* Define pgaudit.log */DefineCustomStringVariable("pgaudit.log","Specifies which classes of statements will be logged by session audit ""logging. Multiple classes can be provided using a comma-separated ""list and classes can be subtracted by prefacing the class with a ""- sign.",NULL,&auditLog,"none",PGC_SUSET,GUC_LIST_INPUT | GUC_NOT_IN_SAMPLE,check_pgaudit_log,assign_pgaudit_log,NULL);// 其他参数// 安装钩子/* Install our hook functions after saving the existing pointers to preserve the chains. */next_ExecutorStart_hook = ExecutorStart_hook;ExecutorStart_hook = pgaudit_ExecutorStart_hook;// .../* Log that the extension has completed initialization */
#ifndef EXEC_BACKENDereport(LOG, (errmsg("pgaudit extension initialized")));
#elseereport(DEBUG1, (errmsg("pgaudit extension initialized")));
#endif /* EXEC_BACKEND */inited = true;
}

在加载插件时,执行pgaudit--1.7.sql,定义了2个触发器函数,2个事件触发器。函数的具体实现在pgaudit.c

-- 当DDL语句执行结束时,执行该函数
CREATE FUNCTION pgaudit_ddl_command_end()RETURNS event_triggerSECURITY DEFINERSET search_path = 'pg_catalog, pg_temp'LANGUAGE CAS 'MODULE_PATHNAME', 'pgaudit_ddl_command_end';-- ddl_command_end事件触发器
CREATE EVENT TRIGGER pgaudit_ddl_command_endON ddl_command_endEXECUTE PROCEDURE pgaudit_ddl_command_end(); -- 当DDL语句执行结束时触发执行该函数CREATE FUNCTION pgaudit_sql_drop()RETURNS event_triggerSECURITY DEFINERSET search_path = 'pg_catalog, pg_temp'LANGUAGE CAS 'MODULE_PATHNAME', 'pgaudit_sql_drop';CREATE EVENT TRIGGER pgaudit_sql_dropON sql_dropEXECUTE PROCEDURE pgaudit_sql_drop();   -- sql_drop事件触发器,删除对象操作触发
调试DDL语句的审计

我们调试一条建表语句的审计过程

调用栈:

pgaudit.so!log_audit_event(AuditEventStackItem * stackItem) (contrib\pgaudit\pgaudit.c:704)
pgaudit.so!pgaudit_ddl_command_end(FunctionCallInfo fcinfo) (contrib\pgaudit\pgaudit.c:1734)
fmgr_security_definer(FunctionCallInfo fcinfo) (src\backend\utils\fmgr\fmgr.c:732)
EventTriggerInvoke(List * fn_oid_list, EventTriggerData * trigdata) (src\backend\commands\event_trigger.c:920)
EventTriggerDDLCommandEnd(Node * parsetree) (src\backend\commands\event_trigger.c:727)
ProcessUtilitySlow(ParseState * pstate, PlannedStmt * pstmt, const char * queryString, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc) (src\backend\tcop\utility.c:1923)
standard_ProcessUtility(PlannedStmt * pstmt, const char * queryString, _Bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc) (src\backend\tcop\utility.c:1074)
pgaudit.so!pgaudit_ProcessUtility_hook(PlannedStmt * pstmt, const char * queryString, _Bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc) (contrib\pgaudit\pgaudit.c:1584)
ProcessUtility(PlannedStmt * pstmt, const char * queryString, _Bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment * queryEnv, DestReceiver * dest, QueryCompletion * qc) (src\backend\tcop\utility.c:526)
PortalRunUtility(Portal portal, PlannedStmt * pstmt, _Bool isTopLevel, _Bool setHoldSnapshot, DestReceiver * dest, QueryCompletion * qc) (src\backend\tcop\pquery.c:1158)
PortalRunMulti(Portal portal, _Bool isTopLevel, _Bool setHoldSnapshot, DestReceiver * dest, DestReceiver * altdest, QueryCompletion * qc) (src\backend\tcop\pquery.c:1315)
PortalRun(Portal portal, long count, _Bool isTopLevel, _Bool run_once, DestReceiver * dest, DestReceiver * altdest, QueryCompletion * qc) (src\backend\tcop\pquery.c:791)
exec_simple_query(const char * query_string) (src\backend\tcop\postgres.c:1250)
PostgresMain(const char * dbname, const char * username) (src\backend\tcop\postgres.c:4598)
BackendRun(Port * port) (src\backend\postmaster\postmaster.c:4514)
BackendStartup(Port * port) (src\backend\postmaster\postmaster.c:4242)
ServerLoop() (src\backend\postmaster\postmaster.c:1809)
PostmasterMain(int argc, char ** argv) (src\backend\postmaster\postmaster.c:1481)
main(int argc, char ** argv) (src\backend\main\main.c:202)

中间最重要的3个函数pgaudit_ProcessUtility_hook、捕获DDL语句,添加AuditEvent项,后续不断地补充AuditEvent中个各项信息。

static void pgaudit_ProcessUtility_hook(PlannedStmt *pstmt,const char *queryString,bool readOnlyTree,ProcessUtilityContext context,ParamListInfo params,QueryEnvironment *queryEnv,DestReceiver *dest,QueryCompletion *qc)
{AuditEventStackItem *stackItem = NULL;int64 stackId = 0;/* Don't audit substatements.  All the substatements we care about should be covered by the event triggers. */if (context <= PROCESS_UTILITY_QUERY && !IsAbortedTransactionBlockState()){/* Process top level utility statement */if (context == PROCESS_UTILITY_TOPLEVEL){/* If the stack is not empty then the only allowed entries are open* select, show, and explain cursors */if (auditEventStack != NULL){AuditEventStackItem *nextItem = auditEventStack;do{if (nextItem->auditEvent.commandTag != T_SelectStmt &&nextItem->auditEvent.commandTag != T_VariableShowStmt &&nextItem->auditEvent.commandTag != T_ExplainStmt){elog(ERROR, "pgaudit stack is not empty");}nextItem = nextItem->next;}while (nextItem != NULL);}stackItem = stack_push();stackItem->auditEvent.paramList = copyParamList(params);}elsestackItem = stack_push();stackId = stackItem->stackId;stackItem->auditEvent.logStmtLevel = GetCommandLogLevel(pstmt->utilityStmt);stackItem->auditEvent.commandTag = nodeTag(pstmt->utilityStmt);stackItem->auditEvent.command = CreateCommandTag(pstmt->utilityStmt);stackItem->auditEvent.commandText = queryString;/* If this is a DO block log it before calling the next ProcessUtility hook. */if (auditLogBitmap & LOG_FUNCTION && stackItem->auditEvent.commandTag == T_DoStmt &&!IsAbortedTransactionBlockState())log_audit_event(stackItem);/** If this is a create/alter extension command log it before calling* the next ProcessUtility hook. Otherwise, any warnings will be emitted* before the create/alter is logged and errors will prevent it from* being logged at all. */if (auditLogBitmap & LOG_DDL &&(stackItem->auditEvent.commandTag == T_CreateExtensionStmt ||stackItem->auditEvent.commandTag == T_AlterExtensionStmt) &&!IsAbortedTransactionBlockState())log_audit_event(stackItem);/** A close will free the open cursor which will also free the close* audit entry. Immediately log the close and set stackItem to NULL so* it won't be logged later.*/if (stackItem->auditEvent.commandTag == T_ClosePortalStmt){if (auditLogBitmap & LOG_MISC && !IsAbortedTransactionBlockState())log_audit_event(stackItem);stackItem = NULL;}}/* Call the standard process utility chain. */if (next_ProcessUtility_hook)(*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree, context,params, queryEnv, dest, qc);elsestandard_ProcessUtility(pstmt, queryString, readOnlyTree, context,params, queryEnv, dest, qc);/* Process the audit event if there is one.  Also check that this event* was not popped off the stack by a memory context being free'd elsewhere. */if (stackItem && !IsAbortedTransactionBlockState()){/* Make sure the item we want to log is still on the stack - if not* then something has gone wrong and an error will be raised. */stack_valid(stackId);/* Log the utility command if logging is on, the command has not* already been logged by another hook, and the transaction is not aborted. */if (auditLogBitmap != 0 && !stackItem->auditEvent.logged)log_audit_event(stackItem);}
}

记录了审计信息后,需要将其进行输出,在商业数据库中,可以存储在表中或者文件中,通过表等进行查看,在pgaudit插件中,近输出到日志文件中。

static void log_audit_event(AuditEventStackItem *stackItem)
{/* By default, put everything in the MISC class. */int class = LOG_MISC;const char *className = CLASS_MISC;MemoryContext contextOld;StringInfoData auditStr;/** Skip logging script statements if an extension is currently being created* or altered. PostgreSQL reports the statement text for each statement in* the script as the entire script text, which can blow up the logs. The* create/alter statement will still be logged.** Since a superuser is responsible for determining which extensions are* available, and in most cases installing them, it should not be necessary* to log each statement in the script.*/if (creating_extension)return;/* If this event has already been logged don't log it again */if (stackItem->auditEvent.logged)return;// .../** Create the audit substring** The type-of-audit-log and statement/substatement ID are handled below,* this string is everything else.*/initStringInfo(&auditStr);append_valid_csv(&auditStr, GetCommandTagName(stackItem->auditEvent.command));appendStringInfoCharMacro(&auditStr, ',');append_valid_csv(&auditStr, stackItem->auditEvent.objectType);appendStringInfoCharMacro(&auditStr, ',');append_valid_csv(&auditStr, stackItem->auditEvent.objectName);// 其他信息/* Log rows affected */if (auditLogRows)appendStringInfo(&auditStr, "," INT64_FORMAT,stackItem->auditEvent.rows);/** Log the audit entry.  Note: use of INT64_FORMAT here is bad for* translatability, but we currently haven't got translation support in* pgaudit anyway. */ereport(auditLogClient ? auditLogLevel : LOG_SERVER_ONLY,(errmsg("AUDIT: %s," INT64_FORMAT "," INT64_FORMAT ",%s,%s",stackItem->auditEvent.granted ?AUDIT_TYPE_OBJECT : AUDIT_TYPE_SESSION,stackItem->auditEvent.statementId,stackItem->auditEvent.substatementId,className,auditStr.data),errhidestmt(true),errhidecontext(true)));stackItem->auditEvent.logged = true;MemoryContextSwitchTo(contextOld);
}
调试DML语句的审计

查询语句的审计调用栈如下:

pgaudit.so!log_audit_event(AuditEventStackItem * stackItem) (contrib\pgaudit\pgaudit.c:654)
pgaudit.so!log_select_dml(Oid auditOid, List * rangeTabls) (contrib\pgaudit\pgaudit.c:1211)
pgaudit.so!pgaudit_ExecutorCheckPerms_hook(List * rangeTabls, _Bool abort) (contrib\pgaudit\pgaudit.c:1413)
ExecCheckRTPerms(List * rangeTable, _Bool ereport_on_violation) (src\backend\executor\execMain.c:591)
InitPlan(QueryDesc * queryDesc, int eflags) (src\backend\executor\execMain.c:820)
standard_ExecutorStart(QueryDesc * queryDesc, int eflags) (src\backend\executor\execMain.c:265)
pgaudit.so!pgaudit_ExecutorStart_hook(QueryDesc * queryDesc, int eflags) (contrib\pgaudit\pgaudit.c:1351)
ExecutorStart(QueryDesc * queryDesc, int eflags) (src\backend\executor\execMain.c:142)
PortalStart(Portal portal, ParamListInfo params, int eflags, Snapshot snapshot) (src\backend\tcop\pquery.c:517)
exec_simple_query(const char * query_string) (src\backend\tcop\postgres.c:1211)
PostgresMain(const char * dbname, const char * username) (src\backend\tcop\postgres.c:4598)
BackendRun(Port * port) (src\backend\postmaster\postmaster.c:4514)
BackendStartup(Port * port) (src\backend\postmaster\postmaster.c:4242)
ServerLoop() (src\backend\postmaster\postmaster.c:1809)
PostmasterMain(int argc, char ** argv) (src\backend\postmaster\postmaster.c:1481)
main(int argc, char ** argv) (src\backend\main\main.c:202)
钩子函数

具体实现审计时,涉及到下面这些钩子函数:

static ExecutorCheckPerms_hook_type next_ExecutorCheckPerms_hook = NULL;
static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
static object_access_hook_type next_object_access_hook = NULL;
static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
/* The following hook functions are required to get rows */
static ExecutorRun_hook_type next_ExecutorRun_hook = NULL;
static ExecutorEnd_hook_type next_ExecutorEnd_hook = NULL;

/** Hook ExecutorStart to get the query text and basic command type for queries* that do not contain a table and so can't be idenitified accurately in* ExecutorCheckPerms.*/
static void
pgaudit_ExecutorStart_hook(QueryDesc *queryDesc, int eflags)
{AuditEventStackItem *stackItem = NULL;if (!internalStatement){/* Push the audit even onto the stack */stackItem = stack_push();/* Initialize command using queryDesc->operation */switch (queryDesc->operation){case CMD_SELECT:stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;stackItem->auditEvent.commandTag = T_SelectStmt;stackItem->auditEvent.command = CMDTAG_SELECT;break;case CMD_INSERT:stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;stackItem->auditEvent.commandTag = T_InsertStmt;stackItem->auditEvent.command = CMDTAG_INSERT;break;case CMD_UPDATE:stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;stackItem->auditEvent.commandTag = T_UpdateStmt;stackItem->auditEvent.command = CMDTAG_UPDATE;break;case CMD_DELETE:stackItem->auditEvent.logStmtLevel = LOGSTMT_MOD;stackItem->auditEvent.commandTag = T_DeleteStmt;stackItem->auditEvent.command = CMDTAG_DELETE;break;default:stackItem->auditEvent.logStmtLevel = LOGSTMT_ALL;stackItem->auditEvent.commandTag = T_Invalid;stackItem->auditEvent.command = CMDTAG_UNKNOWN;break;}/* Initialize the audit event */stackItem->auditEvent.commandText = queryDesc->sourceText;stackItem->auditEvent.paramList = copyParamList(queryDesc->params);}/* Call the previous hook or standard function */if (next_ExecutorStart_hook)next_ExecutorStart_hook(queryDesc, eflags);elsestandard_ExecutorStart(queryDesc, eflags);/** Move the stack memory context to the query memory context.  This needs* to be done here because the query context does not exist before the* call to standard_ExecutorStart() but the stack item is required by* pgaudit_ExecutorCheckPerms_hook() which is called during* standard_ExecutorStart().*/if (stackItem){MemoryContextSetParent(stackItem->contextAudit,queryDesc->estate->es_query_cxt);/* Set query context for tracking rows processed */if (auditLogRows)stackItem->auditEvent.queryContext = queryDesc->estate->es_query_cxt;}
}/** Hook ExecutorCheckPerms to do session and object auditing for DML.*/
static bool pgaudit_ExecutorCheckPerms_hook(List *rangeTabls, bool abort)
{Oid auditOid;/* Get the audit oid if the role exists */auditOid = get_role_oid(auditRole, true);/* Log DML if the audit role is valid or session logging is enabled */if ((auditOid != InvalidOid || auditLogBitmap != 0) &&!IsAbortedTransactionBlockState()){/* If auditLogRows is on, wait for rows processed to be set */if (auditLogRows && auditEventStack != NULL){/* Check if the top item is SELECT/INSERT for CREATE TABLE AS */if (auditEventStack->auditEvent.commandTag == T_SelectStmt &&auditEventStack->next != NULL &&auditEventStack->next->auditEvent.command == CMDTAG_CREATE_TABLE_AS &&auditEventStack->auditEvent.rangeTabls != NULL){/** First, log the INSERT event for CREATE TABLE AS here.* The SELECT event for CREATE TABLE AS will be logged* in pgaudit_ExecutorEnd_hook() later to get rows.*/log_select_dml(auditOid, rangeTabls);}else{/* Save auditOid and rangeTabls to call log_select_dml()* in pgaudit_ExecutorEnd_hook() later. */auditEventStack->auditEvent.auditOid = auditOid;auditEventStack->auditEvent.rangeTabls = rangeTabls;}}elselog_select_dml(auditOid, rangeTabls);}/* Call the next hook function */if (next_ExecutorCheckPerms_hook &&!(*next_ExecutorCheckPerms_hook) (rangeTabls, abort))return false;return true;
}/* Hook ExecutorRun to get rows processed by the current statement.*/
static void pgaudit_ExecutorRun_hook(QueryDesc *queryDesc, ScanDirection direction, uint64 count, bool execute_once)
{AuditEventStackItem *stackItem = NULL;/* Call the previous hook or standard function */if (next_ExecutorRun_hook)next_ExecutorRun_hook(queryDesc, direction, count, execute_once);elsestandard_ExecutorRun(queryDesc, direction, count, execute_once);if (auditLogRows && !internalStatement){/* Find an item from the stack by the query memory context */stackItem = stack_find_context(queryDesc->estate->es_query_cxt);/* Accumulate the number of rows processed */if (stackItem != NULL)stackItem->auditEvent.rows += queryDesc->estate->es_processed;}
}/** Hook ExecutorEnd to get rows processed by the current statement.*/
static void pgaudit_ExecutorEnd_hook(QueryDesc *queryDesc)
{AuditEventStackItem *stackItem = NULL;AuditEventStackItem *auditEventStackFull = NULL;if (auditLogRows && !internalStatement){/* Find an item from the stack by the query memory context */stackItem = stack_find_context(queryDesc->estate->es_query_cxt);if (stackItem != NULL && stackItem->auditEvent.rangeTabls != NULL){/* Reset auditEventStack to use in log_select_dml() */auditEventStackFull = auditEventStack;auditEventStack = stackItem;/* Log SELECT/DML audit entry */log_select_dml(stackItem->auditEvent.auditOid,stackItem->auditEvent.rangeTabls);/* Switch back to the previous auditEventStack */auditEventStack = auditEventStackFull;}}/* Call the previous hook or standard function */if (next_ExecutorEnd_hook)next_ExecutorEnd_hook(queryDesc);elsestandard_ExecutorEnd(queryDesc);
}/** Hook object_access_hook to provide fully-qualified object names for function* calls.*/
static void pgaudit_object_access_hook(ObjectAccessType access,Oid classId,Oid objectId,int subId,void *arg)
{if (auditLogBitmap & LOG_FUNCTION && access == OAT_FUNCTION_EXECUTE &&auditEventStack && !IsAbortedTransactionBlockState())log_function_execute(objectId);if (next_object_access_hook)(*next_object_access_hook) (access, classId, objectId, subId, arg);
}

相关文章:

PostgreSQL源码分析——审计插件pgaudit

PostgreSQL审计插件pgaudit 在PostgreSQL中&#xff0c;提供了开源的审计插件pgaudit&#xff0c;但是其功能并不完善&#xff0c;只提供了基本的审计功能&#xff0c;对此&#xff0c;很多基于PG开发的商业数据库大多提供了丰富的审计功能。比如人大金仓&#xff0c;openGaus…...

ijkplayer编译 android版本

ijkplayer源码下载地址如下&#xff1a;https://github.com/bilibili/ijkplayer 下载代码&#xff0c;直接执行如下命令即可&#xff1a; $cd /data/project/ijkplayer/ $git clone https://github.com/bilibili/ijkplayer.git $git checkout -B latest k0.8.8 1 环境安装 …...

面向对象的进阶---static

1.static 静态变量 package com.itheima.a01staticdemo01;public class Student {private String name;private int age;public static String teacherName;public Student() {}public Student(String name, int age) {this.name name;this.age age;}/*** 获取* return n…...

React useContext

useContext 允许父组件向其下层无论多深的任何组件提供信息&#xff0c;而无需通过 props 显式传递。 // 1. LevelContext.js 创建 context&#xff0c;将其从文件中导出 import { createContext } from react; export const LevelContext createContext(1);// 2. Section.j…...

【尚庭公寓SpringBoot + Vue 项目实战】用户管理(十五)

【尚庭公寓SpringBoot Vue 项目实战】用户管理&#xff08;十五&#xff09; 文章目录 【尚庭公寓SpringBoot Vue 项目实战】用户管理&#xff08;十五&#xff09;1、业务介绍2、接口实现2.1、根据条件分页查询用户列表2.2、根据ID更新用户状态 1、业务介绍 用户管理共包含两…...

laravel中如何向字段标签添加工具提示

首先&#xff0c;您可以使用 轻松自定义字段标签->label()。我相信您知道这一点。但您知道吗……标签输出未转义&#xff1f;这意味着您也可以在标签中包含 HTML。 为了尽快实现上述目标&#xff0c;我只是采取了一个快速而粗糙的解决方案&#xff1a; CRUD::field(nickna…...

高考志愿填报,选专业应该考虑哪些因素?

这是一个复杂的社会&#xff0c;各种影响就业的因素层出不穷&#xff0c;也从未断绝。对于高考生而言&#xff0c;高考刚结束&#xff0c;短暂的放松后&#xff0c;就必须考虑自身以后应该就读什么专业&#xff0c;如果不对就读专业进行评估&#xff0c;仔细挑选&#xff0c;毕…...

图书管理系统代码(Java)

1、运行演示 QQ2024528-205028-HD 详细讲解在这篇博客&#xff1a;JavaSE&#xff1a;图书管理系统-CSDN博客 2、所建的包 3、Java代码 3.1 book包 3.1.1 Book类代码 package book;/*** Created with IntelliJ IDEA.* Description:* User: dings* Date: 2024-05-13* Time:…...

Nginx反向代理Kingbase数据库

本文适用于开发人员学习运维领域知识&#xff0c;主要内容为在个人理解的基础上对企业级开发中所使用的Nginx和数据库kingbase相关使用&#xff0c;并附上Nginx反向代理kingbase数据库的相关配置的操作方式&#xff0c;感谢阅读 为什么是nginx代理kingbase数据库服务端 生产环…...

没有字幕的高清爆款视频素材去哪里找?无字幕无水印素材网站分享

在寻找无水印高清视频素材的道路上感到困惑&#xff1f;欢迎来到今天的分享&#xff0c;我将为您介绍一些海外优质的素材网站&#xff0c;以及国内极具特色的“蛙学府”。不论您是短视频制作的新手还是老手&#xff0c;这些网站都将为您的创意注入新活力。 蛙学府网 蛙学府网不…...

Java23种设计模式(一)

前言 这2个月来&#xff0c;重新出发&#xff0c;从java开发需要的数据库、查询日志工具、开发工具等的安装、环境配置&#xff0c;再到后面的基础学习、数据库学习、扩展学习&#xff08;maven、mq、设计模式、spring 系列等等&#xff09;&#xff0c;边学边记录&#xff0c…...

Spring Boot集成websocket实现webrtc功能

1.什么是webrtc&#xff1f; WebRTC 是 Web 实时通信&#xff08;Real-Time Communication&#xff09;的缩写&#xff0c;它既是 API 也是协议。WebRTC 协议是两个 WebRTC Agent 协商双向安全实时通信的一组规则。开发人员可以通过 WebRTC API 使用 WebRTC 协议。目前 WebRTC…...

StableSwarmUI 安装教程(详细)

文章目录 背景特点安装 背景 StableSwarmUI是StabilityAI官方开源的一个文生图工作流UI&#xff0c;目前处于beta阶段&#xff0c;但主流程是可以跑通的。该UI支持接入ComfyUI、Stable Diffusion-WebUI。其工作原理就是使用ComfyUI、Stable Diffusion-WebUI或者StabilityAI官方…...

利用Unity XR交互工具包实现简易VR菜单控制——6.18山大软院项目实训

初始设置 在Unity项目中&#xff0c;首先需要确保安装了XR插件和XR交互工具包。这些工具包提供了对VR硬件的支持&#xff0c;以及一系列用于快速开发VR交互的组件和预设。 脚本概览 本示例中的menuController脚本附加在一个Unity GameObject上&#xff0c;这个脚本负责监听用…...

区间预测 | Matlab实现CNN-ABKDE卷积神经网络自适应带宽核密度估计多变量回归区间预测

区间预测 | Matlab实现CNN-ABKDE卷积神经网络自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现CNN-ABKDE卷积神经网络自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CNN-ABKDE卷积神经网络自适应…...

【机器学习】第6章 支持向量机(SVM)

一、概念 1.支持向量机&#xff08;support vector machine&#xff0c;SVM&#xff09;&#xff1a; &#xff08;1&#xff09;基于统计学理论的监督学习方法&#xff0c;但不属于生成式模型&#xff0c;而是判别式模型。 &#xff08;2&#xff09;支持向量机在各个领域内的…...

hive笔记

文章目录 1. 如何增加列2. 如何查看表的具体列的数据类型3. 如何drop一个表 1. 如何增加列 alter table your_table_name add columns (your_column_name varchar(255));2. 如何查看表的具体列的数据类型 DESCRIBE your_table_name3. 如何drop一个表 drop table your_table_…...

kali - 配置静态网络地址 + ssh 远程连接

文章目录 观前提示&#xff1a;本环境在 root 用户下kali 配置静态网络地址打开网络配置文件 kali 配置 ssh 远程连接 观前提示&#xff1a;本环境在 root 用户下 kali 配置静态网络地址 打开网络配置文件 vim /etc/network/interfaces出现一下内容 # This file describes …...

Redis常见数据类型及其常用命令详解

文章目录 一、Redis概述二、Redis常用命令1.通用命令1.1 KEYS&#xff1a;查看符合模板的所有 key1.2 DEL&#xff1a;删除一个指定的 key1.3 EXISTS&#xff1a;判断 key 是否存在1.4 EXPIRE&#xff1a;给一个 key 设置有效期&#xff0c;有效期到期时该 key 会被自动删除1.5…...

JMU 数科 数据库与数据仓库期末总结(4)实验设计题

E-R图 实体-关系图 E-R图的组成要素主要包括&#xff1a; 实体&#xff08;Entity&#xff09;&#xff1a;实体代表现实世界中可相互区别的对象或事物&#xff0c;如顾客、订单、产品等。在图中&#xff0c;实体通常用矩形表示&#xff0c;并在矩形内标注实体的名称。 属性…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

Web中间件--tomcat学习

Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机&#xff0c;它可以执行Java字节码。Java虚拟机是Java平台的一部分&#xff0c;Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建

【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理

在城市的某个角落&#xff0c;一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延&#xff0c;滚滚浓烟弥漫开来&#xff0c;周围群众的生命财产安全受到严重威胁。就在这千钧一发之际&#xff0c;消防救援队伍迅速行动&#xff0c;而豪越科技消防一体化安全管控平台构建的消防“…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中&#xff0c;如何在保障应用高可用的同时有效地管理资源&#xff0c;一直是运维人员和开发者关注的重点。随着微服务架构的普及&#xff0c;集群内各个服务的负载波动日趋明显&#xff0c;传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...