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中,提供了开源的审计插件pgaudit,但是其功能并不完善,只提供了基本的审计功能,对此,很多基于PG开发的商业数据库大多提供了丰富的审计功能。比如人大金仓,openGaus…...
ijkplayer编译 android版本
ijkplayer源码下载地址如下:https://github.com/bilibili/ijkplayer 下载代码,直接执行如下命令即可: $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 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递。 // 1. LevelContext.js 创建 context,将其从文件中导出 import { createContext } from react; export const LevelContext createContext(1);// 2. Section.j…...
【尚庭公寓SpringBoot + Vue 项目实战】用户管理(十五)
【尚庭公寓SpringBoot Vue 项目实战】用户管理(十五) 文章目录 【尚庭公寓SpringBoot Vue 项目实战】用户管理(十五)1、业务介绍2、接口实现2.1、根据条件分页查询用户列表2.2、根据ID更新用户状态 1、业务介绍 用户管理共包含两…...
laravel中如何向字段标签添加工具提示
首先,您可以使用 轻松自定义字段标签->label()。我相信您知道这一点。但您知道吗……标签输出未转义?这意味着您也可以在标签中包含 HTML。 为了尽快实现上述目标,我只是采取了一个快速而粗糙的解决方案: CRUD::field(nickna…...
高考志愿填报,选专业应该考虑哪些因素?
这是一个复杂的社会,各种影响就业的因素层出不穷,也从未断绝。对于高考生而言,高考刚结束,短暂的放松后,就必须考虑自身以后应该就读什么专业,如果不对就读专业进行评估,仔细挑选,毕…...
图书管理系统代码(Java)
1、运行演示 QQ2024528-205028-HD 详细讲解在这篇博客:JavaSE:图书管理系统-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数据库
本文适用于开发人员学习运维领域知识,主要内容为在个人理解的基础上对企业级开发中所使用的Nginx和数据库kingbase相关使用,并附上Nginx反向代理kingbase数据库的相关配置的操作方式,感谢阅读 为什么是nginx代理kingbase数据库服务端 生产环…...
没有字幕的高清爆款视频素材去哪里找?无字幕无水印素材网站分享
在寻找无水印高清视频素材的道路上感到困惑?欢迎来到今天的分享,我将为您介绍一些海外优质的素材网站,以及国内极具特色的“蛙学府”。不论您是短视频制作的新手还是老手,这些网站都将为您的创意注入新活力。 蛙学府网 蛙学府网不…...
Java23种设计模式(一)
前言 这2个月来,重新出发,从java开发需要的数据库、查询日志工具、开发工具等的安装、环境配置,再到后面的基础学习、数据库学习、扩展学习(maven、mq、设计模式、spring 系列等等),边学边记录,…...
Spring Boot集成websocket实现webrtc功能
1.什么是webrtc? WebRTC 是 Web 实时通信(Real-Time Communication)的缩写,它既是 API 也是协议。WebRTC 协议是两个 WebRTC Agent 协商双向安全实时通信的一组规则。开发人员可以通过 WebRTC API 使用 WebRTC 协议。目前 WebRTC…...
StableSwarmUI 安装教程(详细)
文章目录 背景特点安装 背景 StableSwarmUI是StabilityAI官方开源的一个文生图工作流UI,目前处于beta阶段,但主流程是可以跑通的。该UI支持接入ComfyUI、Stable Diffusion-WebUI。其工作原理就是使用ComfyUI、Stable Diffusion-WebUI或者StabilityAI官方…...
利用Unity XR交互工具包实现简易VR菜单控制——6.18山大软院项目实训
初始设置 在Unity项目中,首先需要确保安装了XR插件和XR交互工具包。这些工具包提供了对VR硬件的支持,以及一系列用于快速开发VR交互的组件和预设。 脚本概览 本示例中的menuController脚本附加在一个Unity GameObject上,这个脚本负责监听用…...
区间预测 | Matlab实现CNN-ABKDE卷积神经网络自适应带宽核密度估计多变量回归区间预测
区间预测 | Matlab实现CNN-ABKDE卷积神经网络自适应带宽核密度估计多变量回归区间预测 目录 区间预测 | Matlab实现CNN-ABKDE卷积神经网络自适应带宽核密度估计多变量回归区间预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CNN-ABKDE卷积神经网络自适应…...
【机器学习】第6章 支持向量机(SVM)
一、概念 1.支持向量机(support vector machine,SVM): (1)基于统计学理论的监督学习方法,但不属于生成式模型,而是判别式模型。 (2)支持向量机在各个领域内的…...
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 远程连接
文章目录 观前提示:本环境在 root 用户下kali 配置静态网络地址打开网络配置文件 kali 配置 ssh 远程连接 观前提示:本环境在 root 用户下 kali 配置静态网络地址 打开网络配置文件 vim /etc/network/interfaces出现一下内容 # This file describes …...
Redis常见数据类型及其常用命令详解
文章目录 一、Redis概述二、Redis常用命令1.通用命令1.1 KEYS:查看符合模板的所有 key1.2 DEL:删除一个指定的 key1.3 EXISTS:判断 key 是否存在1.4 EXPIRE:给一个 key 设置有效期,有效期到期时该 key 会被自动删除1.5…...
JMU 数科 数据库与数据仓库期末总结(4)实验设计题
E-R图 实体-关系图 E-R图的组成要素主要包括: 实体(Entity):实体代表现实世界中可相互区别的对象或事物,如顾客、订单、产品等。在图中,实体通常用矩形表示,并在矩形内标注实体的名称。 属性…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》
🧠 LangChain 中 TextSplitter 的使用详解:从基础到进阶(附代码) 一、前言 在处理大规模文本数据时,特别是在构建知识库或进行大模型训练与推理时,文本切分(Text Splitting) 是一个…...
JS红宝书笔记 - 3.3 变量
要定义变量,可以使用var操作符,后跟变量名 ES实现变量初始化,因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符,可以创建一个全局变量 如果需要定义…...
嵌入式面试常问问题
以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...
