Nginx:过滤模块的实现

news2025/7/6 3:55:14

文章目录

    • 1、过滤模块的概念
    • 2、过滤模块原理
      • 2.1、过滤链表
      • 2.2、执行顺序
    • 3、过滤模块的实现
      • 3.1、编写模块结构
        • 3.1.1、模块配置结构
        • 3.1.2、模块配置命令
        • 3.1.3、模块上下文
        • 3.1.4、定义模块
      • 3.2、设置响应头
      • 3.3、设置响应体
      • 3.4、编译测试
      • 3.5、完整代码
    • 4、参考

文章参考<零声教育>的C/C++linux服务期高级架构系统教程学习: 服务器高级架构体系

nginx 模块的实现流程

  • 初始化
  • conf 文件
  • 请求处理的流程

关于模块的基础知识,建议先阅读我之前写过的 Nginx: handler 模块的实现,再来看这篇。

1、过滤模块的概念

过滤模块是一种 http 模块,一个请求可以被任意个 http 过滤模块处理,可以根据需要叠加效果或者依次处理,因此常用来处理附加功能,如图片压缩等。

http 过滤模块仅处理服务器发送给客户端的 http 响应,而不处理客户端发往服务器的 http 请求。

http 过滤模块可以选择性处理 http 头部和 http 包体,也可以两者都处理。例如 gzip 过滤模块先处理 http 头部,然后检查 http 头部里的 Content-Type 是否属于配置文件中指定的 gzip 压缩类型,接着处理 http 包体,针对每一块 buffer 缓冲区都进行 gzip 压缩,这样再交给下一个 http 过滤模块处理。

2、过滤模块原理

2.1、过滤链表

所有 http 过滤模块组成一个单链表,http 框架定义两个 static 指针,指向整个链表的第一个元素,分别指向用于处理 http 头部和 http 包体的方法。这个单向链表时围绕着每个文件(http 过滤模块)中的这两个方法来建立的,也就是说,链表中的元素实际上就是处理方法。

typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r);
typedef ngx_int_t (*ngx_http_output_body_filter_pt) (ngx_http_request_t *r, ngx_chain_t *chain);
/* 参数
 - 参数 r: 当前请求;
 - 参数 chain:要发送的 http 包体
*/

// 单链表的入口
extern ngx_http_output_header_filter_pt ngx_http_top_header_filter
extern ngx_http_output_body_filter_pt ngx_http_top_body_filter

当执行 ngx_http_send_header 发送 http 头部时,ngx_http_top_header_filter 指针遍历所有的 http 头部过滤模块,并依次执行;当执行 ngx_http_output_filter 发送 http 包体时,ngx_http_top_body_filter 指针遍历所有的 http 包体过滤模块,并依次执行。

每个 http 过滤模块初始化时,会找到链表的首元素ngx_http_top_header_filter指针和ngx_http_top_body_filter 指针,再使用静态类型的 ngx_http_next_header_filter指针和 ngx_http_next_body_filter指针将自己插入到链表的首部,其定义如下。注意两个指针必须是 static 静态变量,仅在当前文件中生效,这就允许所有的过滤模块有各自的指针。

// 定义两个静态指针,分别用于指向下一个过滤模块的 http header 和 http body
static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

在实际使用中,如果需要调用下一个 http 模块,只需要调用 ngx_http_next_header_filter(r)ngx_http_next_body_filter(r, chain)即可。

2.2、执行顺序

http 过滤模块的调用顺序由 configure 命令生成。由于每个 http 过滤模块初始化方法都会把自己头插到单链表的首部,所以调用初始化方法的顺序决定过滤模块在链表中的位置。模块的初始化顺序就是 ngx_modules.c 中的 ngx_modules 数组成员的顺序,也可以在 configure 命令执行后,make 编译命令前自行修改。初始化顺序与模块执行顺序相反(头插法)。

3、过滤模块的实现

这里要实现的一个返回给用户的页面增加前缀的过滤模块,如图:Hello World 就是过滤模块添加的前缀。

在这里插入图片描述

3.1、编写模块结构

3.1.1、模块配置结构

定义该模块的配置结构来存储配置项(配置命令)。Nginx 的配置信息分为三个作用域 main, server, location,每个模块提供的配置命令需要定义不同的模块配置结构来存储。

typedef struct {
	ngx_flag_t enable;		
} ngx_http_myfilter_conf_t;

存储 http 上下文,用于添加前缀。

typedef struct {
	ngx_int_t add_prefix;
} ngx_http_myfilter_ctx_t;

3.1.2、模块配置命令

自定义的 ngx_command_t 类型的 commands 数组

static ngx_command_t ngx_http_myfilter_module_cmds[] = {
	{
		ngx_string("add_prefix"),
		NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG,
		ngx_conf_set_flag_slot,
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_myfilter_conf_t, enable), 
		NULL,
	},
	ngx_null_command
};

使用预设的函数 ngx_conf_set_flag_slot 解析命令,作用于 NGX_HTTP_CONTENT_PHASE 阶段。

这里的宏定义 offsetof():返回一个结构体成员相对于结构体起始的字节偏移量

#define offsetof(s,m) ((size_t)&(((s*)0)->m))
offsetof(type, member-designator)

3.1.3、模块上下文

定义 ngx_http_module_t 类型的 ctx 成员,记录 http 模块的上下文。

static ngx_http_module_t ngx_http_myfilter_module_ctx = {
	NULL, 							   /* preconfiguration */
	ngx_http_myfilter_init,				/* postconfiguration */

	NULL, 								/* create main configuration */
	NULL, 								/* init main configuration */

	NULL, 								/* create server configuration */
	NULL,								/* merge server configuration */

	ngx_http_myfilter_create_loc_conf,	/* create location configuration */
	ngx_http_myfilter_merge_loc_conf, 	/* merge location configuration */
};

解析配置文件后,http 过滤模块初始化函数

// 定义两个静态指针,分别用于指向下一个过滤模块的 http header 和 http body
static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

// 初始化 http 过滤模块
static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf) {
	// 插入到 http 响应头处理方法链表的首部
	ngx_http_next_header_filter = ngx_http_top_header_filter;
	ngx_http_top_header_filter = ngx_http_myfilter_header_filter;
	
	// 插入到 http 响应体处理方法链表的首部
	ngx_http_next_body_filter = ngx_http_top_body_filter;
	ngx_http_top_body_filter = ngx_http_myfilter_body_filter;

	return NGX_OK;
}

loc 配置项回调函数1:分配存储配置项的结构体

void *ngx_http_myfilter_create_loc_conf(ngx_conf_t *cf) {
	// 创建存储配置项的结构体
	ngx_http_myfilter_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_myfilter_conf_t));
	if (conf == NULL) {
		return NULL;
	}
	// 设置当前配置项未使用
	// 若使用预设函数 ngx_conf_set_flag_slot 解析配置项参数,则必须初始化为 NGX_CONF_UNSET
	conf->enable = NGX_CONF_UNSET;

	return conf;
}

loc 配置项回调函数2:合并配置项的方法

char *ngx_http_myfilter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) {
	ngx_http_myfilter_conf_t *prev = (ngx_http_myfilter_conf_t*)parent;
	ngx_http_myfilter_conf_t *next = (ngx_http_myfilter_conf_t*)child;

	// 合并 ngx_flag_t 类型的配置项 enable
	ngx_conf_merge_value(next->enable, prev->enable, 0);

	return NGX_CONF_OK;
}

3.1.4、定义模块

定义 ngx_module_t 类型的变量定义模块本身信息,并添加配置信息,模块上下文信息。

ngx_module_t ngx_http_myfilter_module = {
	NGX_MODULE_V1,				    // 宏定义:预设值
	&ngx_http_myfilter_module_ctx,	 /* module context */
	ngx_http_myfilter_module_cmd,	 /* module directives */
	NGX_HTTP_MODULE,			    /* module type */
	NULL,						   /* init master */
	NULL,						   /* init module */
	NULL,						   /* init process */
	NULL,						   /* init thread */
	NULL,						   /* exit thread */
	NULL,						   /* exit process */
	NULL,						   /* exit master */
	NGX_MODULE_V1_PADDING			// 宏定义:预设值
};

3.2、设置响应头

// 过滤模块的功能:添加前缀
static ngx_str_t prefix = ngx_string("<h2> Hello World !!! </h2>");

// 处理请求中的 http 头部
static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r) {	
	ngx_http_myfilter_ctx_t *ctx;
	ngx_http_myfilter_conf_t *conf;

	// 若返回的响应码不是200,直接交由下一个过滤模块处理响应码非200的情况
	if (r->headers_out.status != NGX_HTTP_OK) {
		return ngx_http_next_header_filter(r);
	}

	// 获取 http 上下文
	ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
	// 若该请求的上下文已经存在,说明该函数已经被调用,直接交由下一个过滤模块处理
	if (ctx) {
		return ngx_http_next_header_filter(r);
	}

	// 获取配置命令
	conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);
	// 若该配置项的enable成员为0,未开启,直接交由下一个过滤模块处理
	if (conf->enable == 0) {
		return ngx_http_next_header_filter(r);
	}

	// 构造 http 上下文
	ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));
	if (ctx == NULL) {
		return NGX_ERROR;
	}

	// 不添加前缀
	ctx->add_prefix = 0;

	// 将构造的上下文设置到当前请求中
	ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module);
	// 自定义过滤模块值处理 Content-Type 是 "text/plain" 类型的 http 响应
	if (r->headers_out.content_type.len >= sizeof("text/plain") -1
		&& ngx_strncasecmp(r->headers_out.content_type.data, (u_char*)"text/plain", sizeof("text/plain") - 1) == 0)
	{
		// 设置为1,表示在 http 响应体中添加前缀
		ctx->add_prefix = 1;
		
		// 添加前缀 prefix 后,http 响应体增加长度
		if (r->headers_out.content_length_n > 0) {
			r->headers_out.content_length_n += prefix.len;
		}
	}

	// 交由下一个过滤模块继续处理
	return ngx_http_next_header_filter(r);
}

3.3、设置响应体

static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *chain) {
	ngx_http_myfilter_ctx_t *ctx;
	ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);

	// 若获取不到上下文,或者上下文结构体重的 add_prefix 为0或者2,不添加前缀
	// 交由下一个过滤模块处理
	if (ctx == NULL || ctx->add_prefix != 1) {
		return ngx_http_next_body_filter(r, chain);
	}

	// 设置http响应体中该前缀已添加
	ctx->add_prefix = 2;
	
	// 从内存池中分配内存,用于存储字符串前缀
	ngx_buf_t *b = ngx_create_temp_buf(r->pool, prefix.len);
	// 将 ngx_buf_t 中的指针正确地指向 prefix 字符串
	b->start = b->pos = prefix.data;
	b->last = b->pos + prefix.len;

	// 从内存池中生成 ngx_chain_t 链表,将更分配的 ngx_chain_t 设置到 buf 成员中
	// 并将它添加到原先待发送的 http 响应体前面
	ngx_chain_t *c1 = ngx_alloc_chain_link(r->pool);
	c1->buf = b;
	c1->next = chain;

	// 调用下一个模块的 http body 处理方法,传入新生成的链表 c1
	return ngx_http_next_body_filter(r, c1);
}

3.4、编译测试

编写配置文件

在对应的模块目录中添加配置文件 config,配置文件中需要定义以下三个变量

# 1、模块名称,仅在 configure 文件执行时使用
ngx_addon_name=ngx_http_myfilter_module 
# 2、添加新增模块
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_myfilter_module" 
# 3、添加新增模块的源代码,多个源代码间用空格符连接
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_myfilter_module.c" 

编译测试

进入 nginx 源码目录,执行 configure 脚本,添加模块所在路径

./configure --add-module=PATH 
# 例:
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_realip_module --with-http_v2_module --with-openssl=../openssl-1.1.1g --add-module=/root/code/ # 这里是我的模块路径

configure 脚本执行完毕后,Nginx 会生成 objs/Makefile 和 objs/ngx_modules.c 两个文件,这里也可以查看到自定义的模块已添加。当然,也可以直接修改这两个文件添加自定义模块。

编译,编译过程中显示自定义模块已添加。

make 
make install

进入到 nginx 安装目录,在 ./conf/nginx.conf 的 location 块中添加自定义的 add_prefix on命令。

启动 nginx

/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
/usr/local/nginx/sbin/nginx -s reload

访问 nginx,可以看到自定义过滤模块的执行效果

3.5、完整代码

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

// 配置项
typedef struct {
	ngx_flag_t enable;		
} ngx_http_myfilter_conf_t;

// http 上下文
typedef struct {
	ngx_int_t add_prefix;
} ngx_http_myfilter_ctx_t;

// 模块声明
ngx_module_t ngx_http_myfilter_module;

// 定义两个静态指针,分别用于指向下一个过滤模块的 http header 和 http body
static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

// 过滤模块的功能:添加前缀
static ngx_str_t prefix = ngx_string("<h2> Hello World !!! </h2>");

// 处理请求中的 http 头部
static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r) {	
	ngx_http_myfilter_ctx_t *ctx;
	ngx_http_myfilter_conf_t *conf;

	// 若返回的响应码不是200,直接交由下一个过滤模块处理响应码非200的情况
	if (r->headers_out.status != NGX_HTTP_OK) {
		return ngx_http_next_header_filter(r);
	}

	// 获取 http 上下文
	ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);
	// 若该请求的上下文已经存在,说明该函数已经被调用,直接交由下一个过滤模块处理
	if (ctx) {
		return ngx_http_next_header_filter(r);
	}

	// 获取配置项
	conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);
	// 若该配置项的 enable 成员为0,配置文件没有配置 add_prefix 配置项,直接交由下一个过滤模块处理
	if (conf->enable == 0) {
		return ngx_http_next_header_filter(r);
	}

	// 构造 http 上下文
	ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));
	if (ctx == NULL) {
		return NGX_ERROR;
	}

	// 不添加前缀
	ctx->add_prefix = 0;

	// 将构造的上下文设置到当前请求中
	ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module);
	// 自定义过滤模块值处理 Content-Type 是 "text/plain" 类型的 http 响应
	if (r->headers_out.content_type.len >= sizeof("text/plain") -1
		&& ngx_strncasecmp(r->headers_out.content_type.data, (u_char*)"text/plain", sizeof("text/plain") - 1) == 0)
	{
		// 设置为1,表示在 http 响应体中添加前缀
		ctx->add_prefix = 1;
		
		// 添加前缀 prefix 后,http 响应体增加长度
		if (r->headers_out.content_length_n > 0) {
			r->headers_out.content_length_n += prefix.len;
		}
	}

	// 交由下一个过滤模块继续处理
	return ngx_http_next_header_filter(r);
}

// 处理请求中的 http 包体
static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *chain) {
	ngx_http_myfilter_ctx_t *ctx;
	ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);

	// 若获取不到上下文,或者上下文结构体重的 add_prefix 为0或者2,不添加前缀
	// 交由下一个过滤模块处理
	if (ctx == NULL || ctx->add_prefix != 1) {
		return ngx_http_next_body_filter(r, chain);
	}

	// 设置http响应体中该前缀已添加
	ctx->add_prefix = 2;
	
	// 从内存池中分配内存,用于存储字符串前缀
	ngx_buf_t *b = ngx_create_temp_buf(r->pool, prefix.len);
	// 将 ngx_buf_t 中的指针正确地指向 prefix 字符串
	b->start = b->pos = prefix.data;
	b->last = b->pos + prefix.len;

	// 从内存池中生成 ngx_chain_t 链表,将更分配的 ngx_chain_t 设置到 buf 成员中
	// 并将它添加到原先待发送的 http 响应体前面
	ngx_chain_t *c1 = ngx_alloc_chain_link(r->pool);
	c1->buf = b;
	c1->next = chain;

	// 调用下一个模块的 http body 处理方法,传入新生成的链表 c1
	return ngx_http_next_body_filter(r, c1);
}

// 初始化 http 过滤模块
static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf) {
	// 插入到 http 响应头处理方法链表的首部
	ngx_http_next_header_filter = ngx_http_top_header_filter;
	ngx_http_top_header_filter = ngx_http_myfilter_header_filter;
	
	// 插入到 http 响应体处理方法链表的首部
	ngx_http_next_body_filter = ngx_http_top_body_filter;
	ngx_http_top_body_filter = ngx_http_myfilter_body_filter;

	return NGX_OK;
}

// loc 配置项回调函数1:分配存储配置项的结构体
static void *ngx_http_myfilter_create_loc_conf(ngx_conf_t *cf) {
	// 创建存储配置项的结构体
	ngx_http_myfilter_conf_t *conf = ngx_palloc(cf->pool, sizeof(ngx_http_myfilter_conf_t));
	if (conf == NULL) {
		return NULL;
	}
	// 设置当前配置项未使用
	// 若使用预设函数 ngx_conf_set_flag_slot 解析配置项参数,则必须初始化为 NGX_CONF_UNSET
	conf->enable = NGX_CONF_UNSET;

	return conf;
}

// loc 配置项回调函数2:合并配置项的方法
static char *ngx_http_myfilter_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) {
	ngx_http_myfilter_conf_t *prev = (ngx_http_myfilter_conf_t*)parent;
	ngx_http_myfilter_conf_t *next = (ngx_http_myfilter_conf_t*)child;

	// 合并 ngx_flag_t 类型的配置项 enable
	ngx_conf_merge_value(next->enable, prev->enable, 0);

	return NGX_CONF_OK;
}

// 定义命令数组
static ngx_command_t ngx_http_myfilter_module_cmds[] = {
	{
		ngx_string("add_prefix"),
		NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG,
		ngx_conf_set_flag_slot,
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_myfilter_conf_t, enable), // 宏定义:返回一个结构体成员相对于结构体起始的偏移量
		NULL,
	},
	ngx_null_command
};

// 定义 http 模块。记录 http 模块的上下文信息
static ngx_http_module_t ngx_http_myfilter_module_ctx = {
	NULL, 								/* preconfiguration */
	ngx_http_myfilter_init,				/* postconfiguration */

	NULL, 								/* create main configuration */
	NULL, 								/* init main configuration */

        NULL, 								/* create server configuration */
	NULL,								/* merge server configuration */

	ngx_http_myfilter_create_loc_conf,	/* create location configuration */
	ngx_http_myfilter_merge_loc_conf, 	/* merge location configuration */
};

// 定义模块。http 过滤模块
ngx_module_t ngx_http_myfilter_module = {
	NGX_MODULE_V1,
	&ngx_http_myfilter_module_ctx,
	ngx_http_myfilter_module_cmds,
	NGX_HTTP_MODULE,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NGX_MODULE_V1_PADDING
};

4、参考

  • 陶辉. 深入理解Nginx:模块开发与架构解析[M]. 北京:机械工业出版社,2016.
  • 聂松松等. Nginx底层设计与源码分析[M]. 北京:机械工业出版社,2021.
  • Nginx 入门指南

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/29070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

牛客网语法篇练习分支控制(二)

1.牛牛的通勤路上有两种选择&#xff0c;要么走路&#xff0c;要么打车&#xff0c;牛牛走路的速度是 1m/s 。打车的速度的 10m/s &#xff0c;但是打车需要等出租车 10 s&#xff0c;请你计算牛牛想尽快到公司应该选择打车还是走路。 a int(input()) if a < a / 10 10:p…

单商户商城系统功能拆解35—分销应用—分销概览

单商户商城系统&#xff0c;也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法&#xff0c;例如拼团&#xff0c;秒杀&#xff0c;砍价&#xff0c;包邮…

热烈祝贺|盏百年生物科技有限公司受邀参加2022世界滋补产业生态大会

自2017年“盏百年”品牌创立以来&#xff0c;公司致力于以鲜炖燕窝为导向&#xff0c;以燕窝全产业链建设为核心&#xff0c;打造中国燕窝文化专营品牌。 5年来&#xff0c;盏百年凭借实体体验服务店连锁经营&#xff0c;打造一对一私人滋补管家这一创新模式&#xff0c;树立了…

什么是分布式锁?他解决了什么样的问题?

相信对于朋友们来说&#xff0c;锁这个东西已经非常熟悉了&#xff0c;在说分布式锁之前&#xff0c;我们来聊聊单体应用时候的本地锁&#xff0c;这个锁很多小伙伴都会用 ✔本地锁 我们在开发单体应用的时候&#xff0c;为了保证多个线程并发访问公共资源的时候&#xff0c;…

Apache DolphinScheduler新一代分布式工作流任务调度平台实战

总体架构 MasterServer&#xff1a;MasterServer采用分布式无中心设计理念&#xff0c;MasterServer主要负责 DAG 任务切分、任务提交监控&#xff0c;并同时监听其它MasterServer和WorkerServer的健康状态。 MasterServer服务启动时向Zookeeper注册临时节点&#xff0c;通过监…

Java集合框架【二容器[LinkedList容器类、Set接口]】

文章目录一 LinkedList容器类1.1 LinkedList的使用(List接口)1.2 Linked的使用(非List标准)1.4 LinkedList源码分析二 Set接口2.1 Set接口特点2.2 HashSet容器类2.2.1 Hash算法原理2.2.2 HashSet的例子2.2.3 HashSet存储特征分析2.3 TreeSet容器类2.4 通过元素自身实现比较规则…

[附源码]java毕业设计学校失物招领系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【尚硅谷】IDEA2022快速上手开发利器

【尚硅谷】IDEA2022快速上手开发利器 【尚硅谷】IDEA2022快速上手开发利器一、详细设置1.1 如何打开详细配置界面1.2 系统设置1.3 设置整体主题1.4 设置编辑器主题样式1.5 显示行号与方法分隔符1.6 代码智能提示功能1.7 自动导包配置1.8 设置项目文件编码&#xff08;一定要改&…

uniapp小程序实现圆环效果

文章目录调用组件uniapp小程序利用 canvas2d实现根据指定时间动态画圆环效果调用 <view class"dubbing-control" :style"{width:recordWidth,height:recordWidth}"><dubbing-button v-if"show" :width.sync"recordWidth" :s…

e智团队实验室项目-第四周-YOLOv论文的对比实验中遇到的问题

贾小云*&#xff0c;赵雅玲 *, 张钊* , 李锦玉*&#xff0c;迟梦瑶*&#xff0c;赵尉*&#xff0c;潘玉*&#xff0c;刘立赛&#xff0c;祝大双&#xff0c;李月&#xff0c;曹海艳&#xff0c; (淮北师范大学计算机科学与技术学院&#xff0c;淮北师范大学经济与管理学院&…

2022年度国家级科技企业孵化器开始申报

科技部火炬中心关于开展2022年度国家级科技企业孵化器申报工作的通知各省、自治区、直辖市及计划单列市科技厅&#xff08;委、局&#xff09;&#xff0c;新疆生产建设兵团科技局&#xff1a; 为贯彻落实党的二十大精神&#xff0c;加快实施创新驱动发展战略&#xff0c;加快实…

MySQL操作

目录 1.对库操作 1.1 创建数据库 1.1.1 查看有哪些数据库 1.1.2 指定数据库的字符集 1.1.3 查重创建数据库 1.1.4 查看警告信息 1.1.5 小知识:SQL语句中的分号 1.1.6 小知识:设置默认字符集 1.1.7 小知识:语句中的大小写 1.2 使用/选中数据库 1.3 删除数据库(慎重操作…

PHP视频网站用wamp、phpstudy运行定制开发mysql数据库BS模式

一、源码特点 PHP视频网站是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库系统主要采用B/S模式开发,开发环境为PHP APACHE&#xff0c;数据库为mysql5.0&#xff0c;使 用php语言开发 PHP视频网站用wamp、phpstu…

21. [Python GUI] PyQt5中的模型与视图框架-抽象模型基类QAbstractItemModel与自定义模型

PyQt5中的抽象模型基类QAbstractItemModel与自定义模型 一、关于QAbstractItemModel类 QAbstractItemModel类继承自QObject&#xff0c; 该类是Qt所有模型类的基类&#xff0c;用于管理模型/视图结构中的数据。Qt的所有模型都需要子类化该类。注意&#xff0c;该类是抽象类&am…

数字孪生应用方向展示

昨晚&#xff0c;2022年卡塔尔世界杯正式打响&#xff01;伴随开幕式的进行&#xff0c;由中国铁建城建的卡塔尔世界杯主场馆卢赛尔体育场惊艳全球。事实上&#xff0c;在数字孪生技术的加持下&#xff0c;体育场馆建设也是重点技术应用方向之一&#xff0c;今天就为大家重点展…

java读取文件

先看项目截图 public class FileTest {public static void main(String[]args) throws IOException {String path Objects.requireNonNull(FileTest.class.getClassLoader().getResource("")).getPath();System.out.println(path);System.out.println("****…

微信“史诗级”更新,小而美终于回来啦~

最近微信安卓版又有了更新&#xff0c;版本号也来到了8.0.30。 此次更新又被业界称之为“史诗级”更新&#xff0c;主要原因是新版本微信安装包体积缩小了10M。 没错&#xff0c;你没有看错微信的安装包真的缩小了&#xff0c;而且整整缩小了10M&#xff01; 天呐&#xff0…

MyBatis从入门到精通真没那么难!跟着我带你深入实践Mybatis技术原理与实战!

什么是Mybatis mybatis 是一个优秀的基于java的持久层框架&#xff0c;它内部封装了jdbc&#xff0c;使开发者只需要关注sql语句本身&#xff0c;而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 mybatis通过xml或注解的方式将要执行的各种 statemen…

跨平台应用开发进阶(四十五)uni-app集成企微客服实战

文章目录一、前言二、功能实现2.1 环境准备2.2 代码层面2.3 拓展工具三、拓展阅读一、前言 应用运营过程中&#xff0c;考虑接入企业微信客服功能&#xff0c;大致看了下官方接入文档&#xff0c;并不困难&#xff0c;引入代码量也不大。按照手册来操作即可。 二、功能实现 …

Go Module的基本使用

go module是类似于java中的maven,是包的管理工具&#xff0c;在没有这个go module之前&#xff0c;都是配置本地的GOPATH&#xff0c;创建的每个项目也都必须创建在这个GOPATH的src目录下&#xff0c;且项目的go文件不能重名 go module是在go1.1.1版本推出的 开启go module 在…