404
+Page not found
+diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..2967384 --- /dev/null +++ b/404.html @@ -0,0 +1,330 @@ + + +
+ + + + + + + +Page not found
+my name is lian
docker run -t -d -p 0.0.0.0:9980:9980 -e "aliasgroup1=http://192.168.0.44:8000" -e "extra_params=--o:ssl.enable=false" --name code -e "username=lian" -e "password=lian" --restart always collabora/code
+
sudo apt-get update
+
+sudo apt-get install \
+ ca-certificates \
+ curl \
+ gnupg \
+ lsb-release
+
+curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
+
+echo \
+ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
+ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
+
+sudo apt-get update
+sudo apt-get install docker-ce docker-ce-cli containerd.io
+
+# login to docker hub
+docker login
+
+{
+ "registry-mirrors": ["https://0nth4654.mirror.aliyuncs.com"],
+ "insecure-registries": ["test.seafile.top"]
+}
+
+将本地的 8123 端口映射到 Docker 容器的 8000 端口。
+docker run -it -p 127.0.0.1:8123:8000 ubuntu:latest /bin/bash
+
+docker run -d --name hancom-office -p 8888:80 --privileged=true centos:7 /usr/sbin/init
+
+-t
: 在新容器内指定一个伪终端或终端。-i
: 允许你对容器内的标准输入 (STDIN) 进行交互。-P
: 是容器内部端口 随机映射 到主机的高端口。-p
: 是容器内部端口 绑定 到指定的主机端口。# This will connect to the particular container
+docker exec -it <container-id> /bin/bash
+
+docker exec -it hancom-office /bin/bash
+
+可用 docker ps
或者 docker port {container_id}
命令查看
docker ps -a
+CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
+fab859fcf1ad ubuntu:latest "/bin/bash" 4 seconds ago Up 3 seconds 0.0.0.0:8123->8000/tcp quirky_hopper
+
+只显示某几列信息
+docker ps -a --format "{{.ID}}\t{{.Names}}\t{{.Image}}"
+
+docker port fab859fcf1ad
+8000/tcp -> 0.0.0.0:8123
+
+docker logs -f {container_id}
+
++++
-f
: 像使用tail -f
一样来输出容器内部的标准输出。
docker top {container_id}
+
+docker commit -m="update" -a="lian" container-name imwhatiam/ubuntu:v2
+
++++
-m
: 提交的描述信息 +-a
: 指定镜像作者 +runoob/ubuntu:v2
: 指定要创建的目标镜像名
# push local image to docker hub, must login first.
+docker push imwhatiam/ubuntu-seafile:v1
+
+docker stop $(docker ps -a -q)
+
+docker rm $(docker ps -a -q -f status=exited)
+
+# 将主机 /www/runoob 目录拷贝到容器 96f7f14e99ab 的 /www 目录下。
+docker cp /www/runoob 96f7f14e99ab:/www/
+
+# 将主机 /www/runoob 目录拷贝到容器 96f7f14e99ab 中,目录重命名为 www。
+docker cp /www/runoob 96f7f14e99ab:/www
+
+# 将容器 96f7f14e99ab 的 /www 目录拷贝到主机的 /tmp 目录中。
+docker cp 96f7f14e99ab:/www /tmp/
+
+docker cp foo.txt mycontainer:/foo.txt
+docker cp mycontainer:/foo.txt foo.txt
+
+# restart it in the background
+docker start `docker ps -q -l`
+
+# This will start all container which are in exited state.
+docker start $(docker ps -a -q --filter "status=exited")
+
+# reattach the terminal & stdin
+docker attach `docker ps -q -l`
+
+# 将 id 为 a404c6c174a2 的 **容器** 按日期保存为tar文件。
+docker export -o mysql-`date +%Y%m%d`.tar a404c6c174a2
+
+# 将 **镜像** runoob/ubuntu:v3 生成 my_ubuntu_v3.tar
+docker save -o my_ubuntu_v3.tar runoob/ubuntu:v3
+
+# 从镜像归档文件my_ubuntu_v3.tar创建镜像,命名为runoob/ubuntu:v4
+docker import my_ubuntu_v3.tar runoob/ubuntu:v3
+
+docker search httpd
+
+# 查看 docker 底层信息
+docker inspect
+
+# 获取某个具体信息
+docker inspect -f '{{.NetworkSettings.IPAddress}}' ubuntu
+
+查看 docker 占用
+docker system df
+
+清理
+docker system prune
+
+删除 docker container 内的 /var/ 目录下的日志,并重启 docker container 。
+系统版本为 Ubuntu 14.04, 升级 docker 后,版本不一致导致的,解决方法:
+apt remove docker-ce docker-ce-cli
+apt install docker-ce=18.06.1~ce~3-0~ubuntu
+
+FROM ubuntu:latest
+
+ARG DEBIAN_FRONTEND=noninteractive
+
+# https://developer.aliyun.com/mirror/ubuntu
+# RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak
+# COPY ubuntu-source.list /etc/apt/sources.list
+
+RUN mkdir /root/.pip
+COPY pip.conf /root/.pip/
+COPY tmux.conf /root/.tmux.conf
+
+RUN apt-get -q update && \
+ apt-get -qy upgrade
+
+RUN apt-get install -qy --no-install-recommends pkg-config \
+ python3 python3-dev python3-pip python3-setuptools \
+ curl less vim wget git net-tools tmux tzdata
+
+# RUN rm -rf /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+RUN ln -s /usr/bin/python3 /usr/bin/python
+
+RUN pip install wheel && \
+ pip install --upgrade django ipython pip
+
+RUN git config --global user.name "lian" && \
+ git config --global user.email "imwhatiam123@gmail.com" && \
+ git config --global core.editor "vim"
+
+# Clean up APT when done.
+RUN apt-get -qy autoremove && \
+ apt-get clean
+
python /root/scripts/start.py >> /var/log/start.log 2>&1
+
+lsb_release -a
+uname -a
+cat /etc/issue
+
+cat /etc/centos-release
+
+[root@node3 ~]# last
+
+root pts/0 43.224.44.74 Wed Nov 16 09:43 still logged in
+root pts/2 114.249.209.248 Tue Nov 1 21:40 - 23:54 (02:13)
+root pts/1 114.249.209.248 Tue Nov 1 20:41 - 23:57 (03:16)
+root pts/0 114.249.209.248 Tue Nov 1 20:35 - 23:57 (03:21)
+root pts/0 114.249.209.248 Tue Nov 1 19:42 - 20:35 (00:52)
+root pts/0 54.179.196.89 Tue Nov 1 14:55 - 19:07 (04:11)
+root pts/0 123.117.78.247 Tue Nov 1 14:52 - 14:53 (00:00)
+root pts/0 123.117.78.247 Tue Nov 1 14:13 - 14:14 (00:01)
+reboot system boot 3.10.0-1160.15.2 Tue Nov 1 22:12 - 10:04 (14+11:51)
+root pts/0 114.249.233.212 Tue Feb 23 10:02 - crash (616+12:09)
+root pts/1 114.249.233.212 Tue Feb 23 09:42 - 18:17 (08:35)
+root pts/0 43.224.44.74 Tue Feb 23 09:41 - 09:42 (00:00)
+
+[root@node3 ~]# grep 154.91.227.231 /var/log/secure
+
+Nov 16 08:41:24 node3 sshd[143795]: Accepted password for root from 154.91.227.231 port 55592 ssh2
+
+在 ~/.bash_profile
或者 ~/.bashrc
中增加以下命令
# some more ls aliases
+alias ll='ls -alF'
+alias e='exit'
+alias c='clear'
+
+Ctrl+k, 用于删除从光标处开始到结尾处的所有字符
+Ctrl+u, 用于删除从光标开始到行首的所有字符。一般在密码或命令输入错误时常用
+Ctrl+w, 剪切光标所在处之前的一个词 (以空格、标点等为分隔符)
+ctrl + 方向键左键, 光标移动到前一个单词开头
+ctrl + 方向键右键, 光标移动到后一个单词结尾
+
+tcpdump -i any port 8082 -w output.pcap
+
+The .pfx file, which is in a PKCS#12 format, contains the SSL certificate (public keys) and the corresponding private keys. Sometimes, you might have to import the certificate and private keys separately in an unencrypted plain text format to use it on another system. This topic provides instructions on how to convert the .pfx file to .crt and .key files.
+Run the following command to extract the private key:
+openssl pkcs12 -in [yourfile.pfx] -nocerts -out [drlive.key]
+
+You will be prompted to type the import password. Type the password that you used to protect your keypair when you created the .pfx file.
+You will be prompted again to provide a new password to protect the .key file that you are creating. Store the password to your key file in a secure place to avoid misuse.
+Run the following command to extract the certificate:
+openssl pkcs12 -in [yourfile.pfx] -clcerts -nokeys -out [drlive.crt]
+
+Run the following command to decrypt the private key:
+openssl rsa -in [drlive.key] -out [drlive-decrypted.key]
+
+Type the password that you created to protect the private key file in the previous step.
+The .crt file and the decrypted and encrypted .key files are available in the path, where you started OpenSSL.
+There might be instances where you might have to convert the .pfx file into .pem format. Run the following command to convert it into PEM format.
+openssl rsa -in [keyfile-encrypted.key] -outform PEM -out [keyfile-encrypted-pem.key]
+
+cat << EOF > tmp-file
+deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
+deb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
+deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
+deb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
+deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
+deb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
+deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse
+deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse
+deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
+deb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
+EOF
+
+命令 | +含义 | +
---|---|
-a | +标示已修改的变量,以供输出至环境变量。 | +
-b | +使被中止的后台程序立刻回报执行状态。 | +
-C | +转向所产生的文件无法覆盖已存在的文件。 | +
-d | +Shell预设会用杂凑表记忆使用过的指令,以加速指令的执行。使用-d参数可取消。 | +
-e | +若指令传回值不等于0,则立即退出shell。 | +
-f | +取消使用通配符。 | +
-h | +自动记录函数的所在位置。 | +
-H | +Shell 可利用"!"加<指令编号>的方式来执行history中记录的指令。 | +
-k | +指令所给的参数都会被视为此指令的环境变量。 | +
-l | +记录for循环的变量名称。 | +
-m | +使用监视模式。 | +
-n | +只读取指令,而不实际执行。 | +
-p | +启动优先顺序模式。 | +
-P | +启动-P参数后,执行指令时,会以实际的文件或目录来取代符号连接。 | +
-t | +执行完随后的指令,即退出shell。 | +
-u | +当执行时使用到未定义过的变量,则显示错误信息。 | +
-v | +显示shell所读取的输入值。 | +
-x | +执行指令后,会先显示该指令及所下的参数。 | +
+<参数> | +取消某个set曾启动的参数。 | +
du /var/www -h --max-depth=1 | sort -rn
+du -sh directory
+
+# The `-s` is size, the `-h` is human readable.
+ls -sh filename
+
+find /var/lib/docker/overlay2/ -type f -size +100M -print0 | xargs -0 du -h | sort -nr
+
+df -hl
+
+Filesystem Size Used Avail Capacity Mounted on
+/dev/sda8 103G 9.5G 89G 10% /home
+
+HD硬盘接口的第一个硬盘(a),第二个分区(8),容量是103G,用了9.5G,可用是89,因此利用率是10%, 被挂载到(/home)
+标准输入(stdin
): 默认为键盘输入
标准输出(stdout
): 默认为屏幕输出,表示为 1
标准错误输出(stderr
): 默认也是输出到屏幕,表示为 2
将输出重定向到 ls_result 文件中
+ls > ls_result
+
+追加到 ls_result 文件中
+ls -l >> ls_result
+
+只有标准输出被存入 all_result 文件中
+find /home -name lost* > all_result
+
+表示将标准错误输出重定向
+find /home -name lost* 2> err_result
+
+不输出错信息
+find /home -name lost* 2> /dev/null
+
+标准错误输出和标准输入一样都被存入到文件中
+find /home -name lost_ > all_result 2>& 1
+or
+find /home -name lost_ >& all_result
+
++++
+- +
>
就是输出(标准输出和标准错误输出)重定向的代表符号;- 连续两个
+>
符号,即>>
则表示不清除原来的而追加输出;
chmod \[--help] \[--version] mode file
+chmod u+x test.sh
+
+Args
+-c
: 若该档案权限确实已经更改,才显示其更改动作-f
: 若该档案权限无法被更改也不要显示错误讯息-v
: 显示权限变更的详细资料-R
: 对目前目录下的所有档案与子目录进行相同的权限变更(即以递回的方式逐个变更)–help
: 显示辅助说明–version
: 显示版本权限范围
+u
:目录或者文件的当前的用户g
:目录或者文件的当前的群组o
:除了目录或者文件的当前用户或群组之外的用户或者群组a
:所有的用户及群组权限操作
++
表示增加权限-
表示取消权限=
表示唯一设定权限权限代号:
+r
:读权限,用数字4表示w
:写权限,用数字2表示x
:执行权限,用数字1表示所有者有读和写的权限,组用户只有读的权限
+sudo chmod 644 ×××
+
+只有所有者有读和写以及执行的权限
+sudo chmod 700 ×××
+
+每个人都有读和写的权限
+sudo chmod 666 ×××
+
+每个人都有读和写以及执行的权限
+sudo chmod 777 ×××
+
+其中:
+×××
指文件名(也可以是文件夹名,不过要在 chmod
后加 -ld
)。rwx
属性则 4+2+1=7rw-
属性则 4+2=6;r-x
属性则 4+1=7。apt install net-tools
+
+netstat -tlnp | grep 8000
+
+lsof -i:8000
+
+# for MAC
+lsof -iTCP -sTCP:LISTEN -n -P
+
+netstat
+\-a (all)显示所有选项,默认不显示LISTEN相关
+\-t (tcp)仅显示tcp相关选项
+\-u (udp)仅显示udp相关选项
+\-n 拒绝显示别名,能显示数字的全部转化成数字。
+\-l 仅列出有在 Listen (监听) 的服務状态
+\-p 显示建立相关链接的程序名
+\-r 显示路由信息,路由表
+\-e 显示扩展信息,例如uid等
+\-s 按各个协议进行统计
+\-c 每隔一个固定时间,执行该netstat命令。
+
+# 1. Entry: Minute when the process will be started [0-60]
+# 2. Entry: Hour when the process will be started [0-23]
+# 3. Entry: Day of the month when the process will be started [1-28/29/30/31]
+# 4. Entry: Month of the year when the process will be started [1-12]
+# 5. Entry: Weekday when the process will be started [0-6] [0 is Sunday]
+#
+# all x min = */x
+
+* * * * * command to be executed
+┬ ┬ ┬ ┬ ┬
+│ │ │ │ │
+│ │ │ │ │
+│ │ │ │ └───── day of week (0 - 6) (0 is Sunday, or use names)
+│ │ │ └────────── month (1 - 12)
+│ │ └─────────────── day of month (1 - 31)
+│ └──────────────────── hour (0 - 23)
+└───────────────────────── min (0 - 59)
+
+To open crontab
+crontab -e
+
+To list crontab content
+crontab -l
+
+To remove all your cron jobs
+crontab -r
+
+Run mycommand at 5:09am on January 1st plus every Monday in January
+09 05 1 1 1 mycommand
+
+Run mycommand at 05 and 35 past the hours of 2:00am and 8:00am on the 1st through the 28th of every January and July.
+05,35 02,08 1-28 1,7 * mycommand
+
+Run mycommand every 5 minutes
+*/5 * * * * mycommand
+
+show the first n lines of the file, n=10 default
+head -n ~/solar.html
+
+show the last n lines of the file, n=10 default
+tail -n ~/solar.html
+
+show the changes of the file ontime
+tail -f ~/solar.html
+
+filter out lines includes HEAD
tail -f /opt/seafile/logs/*.log /var/log/nginx/*.log | grep -v HEAD
+
+grep -r search-string .
+
+find \<指定目录> \<指定条件> \<指定动作>
+recursively delete all files of a specific extension in the current dir
+find . -type f -name "*.bak"
+
+seafile-data# find . -type f | wc -l
+1721980
+seafile-data# find . -size +10M -type f | wc -l
+2169
+seafile-data# find . -size +1M -type f | wc -l
+33583
+
+tree -P '*.py|*.html' -L 2
+
+sed -i 's/old-string/new-string/g' /path/to/file.txt
+
+For CentOS
+sudo iptables -I INPUT 1 -p tcp --dport 8082 -j ACCEPT
+sudo iptables -I INPUT 1 -p tcp --dport 8000 -j ACCEPT
+
+-c
:-c
或 --create
建立新的备份文件。-x
:-x
或 --extract
或 --get
从备份文件中还原文件。将打包文件解压。-z
:-z
或 --gzip
或 --ungzip
通过 gzip
指令处理备份文件。将打包文件压缩。 -v
:-v
或--verbose
显示指令执行过程。-f
:-f <备份文件>
或 --file=<备份文件>
指定备份文件。打包目录下的workspace(不压缩)
+tar -cvf workspace.tar workspace
+
+打包并压缩目录下的worksapce
+tar -czvf workspace.tar.gz.$(date +%Y-%m-%d) workspace
+
+tar -xzvf workspace.tar.gz
+
+chmod 0600 /root/.ssh/id_rsa
+
+export LC_ALL=en_US.UTF-8
+
说到进程,恐怕面试中最常见的问题就是线程和进程的关系了,那么先说一下答案:在 Linux 系统中,进程和线程几乎没有区别。
+Linux 中的进程就是一个数据结构,看明白就可以理解文件描述符、重定向、管道命令的底层工作原理,最后我们从操作系统的角度看看为什么说线程和进程基本没有区别。
+首先,抽象地来说,我们的计算机就是这个东西:
+ +这个大的矩形表示计算机的内存空间,其中的小矩形代表进程,左下角的圆形表示磁盘,右下角的图形表示一些输入输出设备,比如鼠标键盘显示器等等。另外,注意到内存空间被划分为了两块,上半部分表示用户空间,下半部分表示内核空间。
+用户空间装着用户进程需要使用的资源,比如你在程序代码里开一个数组,这个数组肯定存在用户空间;内核空间存放内核进程需要加载的系统资源,这一些资源一般是不允许用户访问的。但是注意有的用户进程会共享一些内核空间的资源,比如一些动态链接库等等。
+我们用 C 语言写一个 hello 程序,编译后得到一个可执行文件,在命令行运行就可以打印出一句 hello world,然后程序退出。在操作系统层面,就是新建了一个进程,这个进程将我们编译出来的可执行文件读入内存空间,然后执行,最后退出。
+你编译好的那个可执行程序只是一个文件,不是进程,可执行文件必须要载入内存,包装成一个进程才能真正跑起来。进程是要依靠操作系统创建的,每个进程都有它的固有属性,比如进程号(PID)、进程状态、打开的文件等等,进程创建好之后,读入你的程序,你的程序才被系统执行。
+那么,操作系统是如何创建进程的呢?对于操作系统,进程就是一个数据结构,我们直接来看 Linux 的源码:
+struct task_struct {
+ // 进程状态
+ long state;
+ // 虚拟内存结构体
+ struct mm_struct *mm;
+ // 进程号
+ pid_t pid;
+ // 指向父进程的指针
+ struct task_struct __rcu *parent;
+ // 子进程列表
+ struct list_head children;
+ // 存放文件系统信息的指针
+ struct fs_struct *fs;
+ // 一个数组,包含该进程打开的文件指针
+ struct files_struct *files;
+};
+
+task_struct
就是 Linux 内核对于一个进程的描述,也可以称为「进程描述符」。源码比较复杂,我这里就截取了一小部分比较常见的。
其中比较有意思的是mm
指针和files
指针。mm
指向的是进程的虚拟内存,也就是载入资源和可执行文件的地方;files
指针指向一个数组,这个数组里装着所有该进程打开的文件的指针。
先说files
,它是一个文件指针数组。一般来说,一个进程会从files[0]
读取输入,将输出写入files[1]
,将错误信息写入files[2]
。
举个例子,以我们的角度 C 语言的printf
函数是向命令行打印字符,但是从进程的角度来看,就是向files[1]
写入数据;同理,scanf
函数就是进程试图从files[0]
这个文件中读取数据。
每个进程被创建时,files
的前三位被填入默认值,分别指向标准输入流、标准输出流、标准错误流。我们常说的「文件描述符」就是指这个文件指针数组的索引,所以程序的文件描述符默认情况下 0 是输入,1 是输出,2 是错误。
我们可以重新画一幅图:
+ +对于一般的计算机,输入流是键盘,输出流是显示器,错误流也是显示器,所以现在这个进程和内核连了三根线。因为硬件都是由内核管理的,我们的进程需要通过「系统调用」让内核进程访问硬件资源。
+PS:不要忘了,Linux 中一切都被抽象成文件,设备也是文件,可以进行读和写。
+如果我们写的程序需要其他资源,比如打开一个文件进行读写,这也很简单,进行系统调用,让内核把文件打开,这个文件就会被放到files
的第 4 个位置:
明白了这个原理,输入重定向就很好理解了,程序想读取数据的时候就会去files[0]
读取,所以我们只要把files[0]
指向一个文件,那么程序就会从这个文件中读取数据,而不是从键盘:
$ command < file.txt
+
+
+同理,输出重定向就是把files[1]
指向一个文件,那么程序的输出就不会写入到显示器,而是写入到这个文件中:
$ command > file.txt
+
+
+错误重定向也是一样的,就不再赘述。
+管道符其实也是异曲同工,把一个进程的输出流和另一个进程的输入流接起一条「管道」,数据就在其中传递,不得不说这种设计思想真的很优美:
+$ cmd1 | cmd2 | cmd3
+
+
+到这里,你可能也看出「Linux 中一切皆文件」设计思路的高明了,不管是设备、另一个进程、socket 套接字还是真正的文件,全部都可以读写,统一装进一个简单的files
数组,进程通过简单的文件描述符访问相应资源,具体细节交于操作系统,有效解耦,优美高效。
首先要明确的是,多进程和多线程都是并发,都可以提高处理器的利用效率,所以现在的关键是,多线程和多进程有啥区别。
+为什么说 Linux 中线程和进程基本没有区别呢,因为从 Linux 内核的角度来看,并没有把线程和进程区别对待。
+我们知道系统调用fork()
可以新建一个子进程,函数pthread()
可以新建一个线程。但无论线程还是进程,都是用task_struct
结构表示的,唯一的区别就是共享的数据区域不同。
换句话说,线程看起来跟进程没有区别,只是线程的某些数据区域和其父进程是共享的,而子进程是拷贝副本,而不是共享。就比如说,mm
结构和files
结构在线程中都是共享的,我画两张图你就明白了:
所以说,我们的多线程程序要利用锁机制,避免多个线程同时往同一区域写入数据,否则可能造成数据错乱。
+那么你可能问,既然进程和线程差不多,而且多进程数据不共享,即不存在数据错乱的问题,为什么多线程的使用比多进程普遍得多呢?
+因为现实中数据共享的并发更普遍呀,比如十个人同时从一个账户取十元,我们希望的是这个共享账户的余额正确减少一百元,而不是希望每人获得一个账户的拷贝,每个拷贝账户减少十元。
+当然,必须要说明的是,只有 Linux 系统将线程看做共享数据的进程,不对其做特殊看待,其他的很多操作系统是对线程和进程区别对待的,线程有其特有的数据结构,我个人认为不如 Linux 的这种设计简洁,增加了系统的复杂度。
+在 Linux 中新建线程和进程的效率都是很高的,对于新建进程时内存区域拷贝的问题,Linux 采用了 copy-on-write 的策略优化,也就是并不真正复制父进程的内存空间,而是等到需要写操作时才去复制。所以 Linux 中新建进程和新建线程都是很迅速的。
那么对于 Linux 命令行,本文不是介绍某些命令的用法,而是说明一些简单却特别容易让人迷惑的细节问题。
+1、标准输入和命令参数的区别。
+2、在后台运行命令在退出终端后也全部退出了。
+3、单引号和双引号表示字符串的区别。
+4、有的命令和sudo
一起用就 command not found。
这个问题一定是最容易让人迷惑的,具体来说,就是搞不清什么时候用管道符|
和文件重定向>
,<
,什么时候用变量$
。
比如说,我现在有个自动连接宽带的 shell 脚本connect.sh
,存在我的家目录:
$ where connect.sh
+/home/fdl/bin/connect.sh
+
+如果我想删除这个脚本,而且想少敲几次键盘,应该怎么操作呢?我曾经这样尝试过:
+$ where connect.sh | rm
+
+实际上,这样操作是错误的,正确的做法应该是这样的:
+$ rm $(where connect.sh)
+
+前者试图将where
的结果连接到rm
的标准输入,后者试图将结果作为命令行参数传入。
标准输入就是编程语言中诸如scanf
或者readline
这种命令;而参数是指程序的main
函数传入的args
字符数组。
前文「Linux文件描述符」说过,管道符和重定向符是将数据作为程序的标准输入,而$(cmd)
是读取cmd
命令输出的数据作为参数。
用刚才的例子说,rm
命令源代码中肯定不接受标准输入,而是接收命令行参数,删除相应的文件。作为对比,cat
命令是既接受标准输入,又接受命令行参数:
$ cat filename
+...file text...
+
+$ cat < filename
+...file text...
+
+$ echo 'hello world' | cat
+hello world
+
+如果命令能够让终端阻塞,说明该命令接收标准输入,反之就是不接受,比如你只运行cat
命令不加任何参数,终端就会阻塞,等待你输入字符串并回显相同的字符串。
比如说你远程登录到服务器上,运行一个 Django web 程序:
+$ python manager.py runserver 0.0.0.0
+Listening on 0.0.0.0:8080...
+
+现在你可以通过服务器的 IP 地址测试 Django 服务,但是终端此时就阻塞了,你输入什么都不响应,除非输入 Ctrl-C 或者 Ctrl-/ 终止 python 进程。
+可以在命令之后加一个&
符号,这样命令行不会阻塞,可以响应你后续输入的命令,但是如果你退出服务器的登录,就不能访问该网页了。
如果你想在退出服务器之后仍然能够访问 web 服务,应该这样写命令 (cmd &)
:
$ (python manager.py runserver 0.0.0.0 &)
+Listening on 0.0.0.0:8080...
+
+$ logout
+
+底层原理是这样的:
+每一个命令行终端都是一个 shell 进程,你在这个终端里执行的程序实际上都是这个 shell 进程分出来的子进程。正常情况下,shell 进程会阻塞,等待子进程退出才重新接收你输入的新的命令。加上&
号,只是让 shell 进程不再阻塞,可以继续响应你的新命令。但是无论如何,你如果关掉了这个 shell 命令行端口,依附于它的所有子进程都会退出。
而(cmd &)
这样运行命令,则是将cmd
命令挂到一个systemd
系统守护进程名下,认systemd
做爸爸,这样当你退出当前终端时,对于刚才的cmd
命令就完全没有影响了。
类似的,还有一种后台运行常用的做法是这样:
+$ nohup some_cmd &
+
+nohup
命令也是类似的原理,不过通过我的测试,还是(cmd &)
这种形式更加稳定。
不同的 shell 行为会有细微区别,但有一点是确定的,对于$
,(
,)
这几个符号,单引号包围的字符串不会做任何转义,双引号包围的字符串会转义。
shell 的行为可以测试,使用set -x
命令,会开启 shell 的命令回显,你可以通过回显观察 shell 到底在执行什么命令:
可见 echo $(cmd)
和 echo "$(cmd)"
,结果差不多,但是仍然有区别。注意观察,双引号转义完成的结果会自动增加单引号,而前者不会。
也就是说,如果 $
读取出的参数字符串包含空格,应该用双引号括起来,否则就会出错。
有时候我们普通用户可以用的命令,用 sudo
加权限之后却报错 command not found:
$ connect.sh
+network-manager: Permission denied
+
+$ sudo connect.sh
+sudo: command not found
+
+原因在于,connect.sh
这个脚本仅存在于该用户的环境变量中:
$ where connect.sh
+/home/fdl/bin/connect.sh
+
+当使用 sudo
时,系统会使用 /etc/sudoers
这个文件中规定的该用户的权限和环境变量,而这个脚本在 /etc/sudoers
环境变量目录中当然是找不到的。
解决方法是使用脚本文件的路径,而不是仅仅通过脚本名称:
+$ sudo /home/fdl/bin/connect.sh
+
+alias ll='ls -alF'
+alias e='exit'
+alias c='clear'
+
+# Git branch in prompt.
+parse_git_branch() {
+ git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
+}
+export PS1="\[\033[34m\]\t\[\033[00m\] \W\[\033[32m\]\$(parse_git_branch)\[\033[00m\] $ "
+
+# # brew install python@3.10
+#
+# # If you need to have python@3.10 first in your PATH, run:
+#
+# export PATH="/usr/local/opt/curl/bin:$PATH"
+# export PATH="/usr/local/opt/python@3.10/bin:$PATH"
+# export PATH="/usr/local/opt/python@3.10/Frameworks/Python.framework/Versions/3.10/bin:$PATH"
+#
+# #For compilers to find python@3.10 you may need to set:
+# export LDFLAGS="-L/usr/local/opt/python@3.10/lib"
+#
+# # For pkg-config to find python@3.10 you may need to set:
+# export PKG_CONFIG_PATH="/usr/local/opt/python@3.10/lib/pkgconfig"
+#
+# export LDFLAGS="-L/usr/local/opt/curl/lib"
+# export CPPFLAGS="-I/usr/local/opt/curl/include"
+# export PKG_CONFIG_PATH="/usr/local/opt/curl/lib/pkgconfig"
+
+[[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] && . "/usr/local/etc/profile.d/bash_completion.sh"
+
+source /Users/lian/python3.10-venv/bin/activate
+source ~/.git-completion.bash
+
需要设置collate(校对) 。 collate规则:
+*_bin
: 表示的是binary case sensitive collation,也就是说是区分大小写的
+*_cs
: case sensitive collation,区分大小写
+*_ci
: case insensitive collation,不区分大小写
sudo apt-get update
+sudo apt-get install mariadb-server mariadb-client
+sudo mysql_secure_installation
+sudo mysql -uroot
+
+vi /etc/mysql/mariadb.conf.d/50-server.cnf
[mysqld]
+
+lower_case_table_names=1
+
+service mysql restart
+
+mysql -u root -p
+
+show databases;
+show tables;
+
+CREATE DATABASE liantest CHARACTER SET utf8;
+
+use mysql;
+
+mysqladmin -u root -p drop mytestdb;
+drop database mytestdb;
+
+mysqldump -u user -p password -d seahub > seahub.sql
+
+mysqldump -u user -p password seahub > seahub.sql
+
+mysqldump -u user -p password seahub profile_profile api2_token > table.sql
+
+show create table table_name
+
+or
+describe dbname.table_name
+
+CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
+GRANT ALL PRIVILEGES ON *.* TO 'newuser'@'localhost';
+
+FLUSH PRIVILEGES;
+
+use mysql;
+update user set password=PASSWORD('password') where User='user';
+flush privileges;
+quit
+
+drop user 'root'@'114.249.235.35';
+
+GRANT ALL ON *.* TO root@'192.168.255.221' IDENTIFIED BY 'root';
+
+or
+GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'password' WITH GRANT OPTION;
+
+then
+FLUSH PRIVILEGES;
+
+select TABLE_NAME, concat(truncate(data_length/1024/1024,2), ' MB') as data_size,
+concat(truncate(index_length/1024/1024,2), ' MB') as index_size
+from information_schema.tables where TABLE_SCHEMA = 'seahub-demo'
+group by TABLE_NAME
+order by data_length desc;
+
+select count(distinct username) from UserActivityStat where timestamp>='2022-01-01 00:00:00' and timestamp<="2022-12-31 23:59:59";
+
+select count(distinct user) from api2_token where created>='2022-01-01 00:00:00' and created<="2022-12-31 23:59:59";
+
+select count(distinct user) from api2_tokenv2 where created_at>='2022-01-01 00:00:00' and created_at<="2022-12-31 23:59:59";
+
+select count(distinct username) from base_userlastlogin where last_login>='2022-01-01 00:00:00' and last_login<="2022-12-31 23:59:59";
+
+select count(distinct op_user) from Activity where timestamp>='2022-01-01 00:00:00' and timestamp<="2022-12-31 23:59:59";
+
Can't connect to MySQL server on '172.17.30.163' (115)
+cd /etc/mysql/mariadb.conf.d/50-server.cnf
+vi mariadb.conf.d/50-server.cnf
+bind-address = 0.0.0.0
+sudo systemctl restart mysql
+
+Host 'iZ2ze77tnovxl4jeegy0i4Z' is not allowed to connect to this MariaDB server
+mysql
+
+MariaDB [(none)]> use mysql
+Reading table information for completion of table and column names
+You can turn off this feature to get a quicker startup with -A
+
+Database changed
+MariaDB [mysql]> select Host, User,Password from user;
++-----------+------+-------------------------------------------+
+| Host | User | Password |
++-----------+------+-------------------------------------------+
+| localhost | root | *B207A659E3668E1E01AAFEB3E47E073B98EAD62B |
++-----------+------+-------------------------------------------+
+1 row in set (0.000 sec)
+
+MariaDB [mysql]> update user set Host='%' where User='root';
+Query OK, 1 row affected (0.000 sec)
+Rows matched: 1 Changed: 1 Warnings: 0
+
+MariaDB [mysql]> flush privileges;
+Query OK, 0 rows affected (0.000 sec)
+
+MariaDB [mysql]> exit
+Bye
+
+Access denied for user 'root'@'localhost'
+https://stackoverflow.com/questions/39281594/error-1698-28000-access-denied-for-user-rootlocalhost
+Some systems like Ubuntu, mysql is using by default the UNIX auth_socket plugin.
+Basically means that: db_users using it, will be "auth" by the system user credentias. You can see if your root user is set up like this by doing the following:
+$ sudo mysql -u root # I had to use "sudo" since is new installation
+
+mysql> USE mysql;
+mysql> SELECT User, Host, plugin FROM mysql.user;
+
++------------------+-----------------------+
+| User | plugin |
++------------------+-----------------------+
+| root | auth_socket |
+| mysql.sys | mysql_native_password |
+| debian-sys-maint | mysql_native_password |
++------------------+-----------------------+
+
+As you can see in the query, the root user is using the auth_socket plugin
+There are 2 ways to solve this:
+You can set the root user to use the mysql_native_password plugin +You can create a new db_user with you system_user (recommended) +Option 1:
+$ sudo mysql -u root # I had to use "sudo" since is new installation
+
+mysql> USE mysql;
+mysql> UPDATE user SET plugin='mysql_native_password' WHERE User='root';
+mysql> FLUSH PRIVILEGES;
+mysql> exit;
+
+$ sudo service mysql restart
+
+Option 2: (replace YOUR_SYSTEM_USER with the username you have)
+$ sudo mysql -u root # I had to use "sudo" since is new installation
+
+mysql> USE mysql;
+mysql> CREATE USER 'YOUR_SYSTEM_USER'@'localhost' IDENTIFIED BY 'YOUR_PASSWD';
+mysql> GRANT ALL PRIVILEGES ON *.* TO 'YOUR_SYSTEM_USER'@'localhost';
+mysql> UPDATE user SET plugin='auth_socket' WHERE User='YOUR_SYSTEM_USER';
+mysql> FLUSH PRIVILEGES;
+mysql> exit;
+
+$ sudo service mysql restart
+
+Remember that if you use option #2 you'll have to connect to mysql as your system username (mysql -u YOUR_SYSTEM_USER)
+Note: On some systems (e.g., Debian stretch) 'auth_socket' plugin is called 'unix_socket', so the corresponding SQL command should be: UPDATE user SET plugin='unix_socket' WHERE User='YOUR_SYSTEM_USER';
+Update: from @andy's comment seems that mysql 8.x.x updated/replaced the auth_socket for caching_sha2_password I don't have a system setup with mysql 8.x.x to test this, however the steps above should help you to understand the issue. Here's the reply:
+One change as of MySQL 8.0.4 is that the new default authentication plugin is 'caching_sha2_password'. The new 'YOUR_SYSTEM_USER' will have this auth plugin and you can login from the bash shell now with "mysql -u YOUR_SYSTEM_USER -p" and provide the password for this user on the prompt. No need for the "UPDATE user SET plugin" step. For the 8.0.4 default auth plugin update see, https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password/
+mysql
+
+MariaDB [(none)]> use mysql
+Reading table information for completion of table and column names
+You can turn off this feature to get a quicker startup with -A
+
+Database changed
+MariaDB [mysql]> UPDATE user SET plugin='mysql_native_password' WHERE User='root';
+Query OK, 1 row affected (0.000 sec)
+Rows matched: 1 Changed: 1 Warnings: 0
+
+MariaDB [mysql]> FLUSH PRIVILEGES;
+Query OK, 0 rows affected (0.000 sec)
+
+MariaDB [mysql]> exit
+Bye
+
+Specified key was too long; max key length is 767 bytes
+执行语句报错
+MySQL [ccnet_db]> CREATE TABLE IF NOT EXISTS lian3EmailUser (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, email VARCHAR(255), passwd VARCHAR(256), is_staff BOOL NOT NULL, is_active BOOL NOT NULL, ctime BIGINT, reference_id VARCHAR(255),UNIQUE INDEX (email), UNIQUE INDEX (reference_id))ENGINE=INNODB;
+
+ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
+
+但是同样语句,后面加了 CHARSET=utf8
即可执行成功
MySQL [ccnet_db]> CREATE TABLE IF NOT EXISTS lian3EmailUser (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, email VARCHAR(255), passwd VARCHAR(256), is_staff BOOL NOT NULL, is_active BOOL NOT NULL, ctime BIGINT, reference_id VARCHAR(255),UNIQUE INDEX (email), UNIQUE INDEX (reference_id))ENGINE=INNODB CHARSET=utf8;
+
+Query OK, 0 rows affected (0.00 sec)
+
+检查发现,数据库字符集用的是 utf8mb4
MySQL [(none)]> show create database ccnet_db;
++----------+----------------------------------------------------------------------+
+| Database | Create Database |
++----------+----------------------------------------------------------------------+
+| ccnet_db | CREATE DATABASE `ccnet_db` /*!40100 DEFAULT CHARACTER SET utf8mb4 */ |
++----------+----------------------------------------------------------------------+
+1 row in set (0.00 sec)
+
+改为 utf8
后即可
ALTER DATABASE ccnet_db DEFAULT CHARACTER SET utf8;
+
+系统变量 innodb_large_prefix
开启了,则对于使用 DYNAMIC
或 COMPRESSED
行格式的 InnoDB
表,索引键前缀限制为3072字节。如果禁用 innodb_large_prefix
,不管是什么表,索引键前缀限制为767字节。
上述的bug很明显是索引超出了限制的长度767(我司生产上 innodb_large_prefix
禁用了):
我发现报错的那张表建立了一个 varchar
类型的索引, varchar(255)
,觉得没什么问题,其实不然,上述的767是字节,而 varchar
类型是字符,同时我发现我使用的字符集为(utf8mb4
),这个指每个字符最大的字节数为4,所以很明显 4*255 > 767
。
所以就报上述错了 (Specified key was too long; max key length is 767 bytes)
。
解决方法:
+改变 varchar
的字符数,我改成了64就可以了。 varchar(64)
或者启用 innodb_large_prefix
,那么限制值会增加到3072
Access denied for user 'seafile'@'localhost' to database 'ifile'
+mysql -u root -p
+
+grant all privileges on *.* to 'seafile'@'localhost' identified by 'IeKi8aht';
+
+flush privileges;
+
+BLOB/TEXT Column Used in Key Specification Without a Key Length
+http://stackoverflow.com/questions/1827063/mysql-error-key-specification-without-a-key-length
+Can't create table (errno: 150 "Foreign key constraint is incorrectly formed")
+建立外键的字段必须和引用表的字段一模一样的类型。 +https://upliu.net/foreign-key-constraint-is-incorrectly-formed.html
+阿里巴巴定制的数据库中,创建表需要使用字符集CHARSET=utf8mb4
。
CREATE TABLE `tags_fileuuidmap` (
+ ...
+ `uuid` char(32) NOT NULL COMMENT 'uuid',
+ ...
+) ENGINE=InnoDB AUTO_INCREMENT=396 DEFAULT CHARSET=utf8mb4 COMMENT='tags_fileuuidmap'
+;
+
+Seahub 的数据库中,创建表时使用的字符集为CHARSET=utf8
。
CREATE TABLE `related_files_relatedfiles` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `o_uuid_id` char(32) NOT NULL,
+ `r_uuid_id` char(32) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `related_files_relate_o_uuid_id_aaa8e613_fk_tags_file` (`o_uuid_id`),
+ KEY `related_files_relate_r_uuid_id_031751df_fk_tags_file` (`r_uuid_id`),
+ CONSTRAINT `related_files_relate_o_uuid_id_aaa8e613_fk_tags_file` FOREIGN KEY (`o_uuid_id`) REFERENCES `tags_fileuuidmap` (`uuid`),
+ CONSTRAINT `related_files_relate_r_uuid_id_031751df_fk_tags_file` FOREIGN KEY (`r_uuid_id`) REFERENCES `tags_fileuuidmap` (`uuid`)
+) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8
+
+所以直接使用 Seahub 的创建语句,给阿里巴巴创建新表时,会报错:
+ERROR 1005 (HY000): Can't create table `ali_seahub`.`related_files_relatedfiles` (errno: 150 "Foreign key constraint is incorrectly formed")
+
+解决方法
+根据 MySQL 的 外键文档
+++Corresponding columns in the foreign key and the referenced key must have similar data types. The size and sign of integer types must be the same. The length of string types need not be the same. For nonbinary (character) string columns, the character set and collation must be the same.
+
将 Seahub 建表语句中的 CHARSET 改为 utf8mb4,经测试可创建新表成功。
+该错误一般出现原因如下:
+1、外键的引用类型不一样,如主键是int外键是char
+2、找不到主表中引用的列
+3、主键和外键的字符编码不一致,也可能存储引擎不一样
+CREATE TABLE t_employee(
+ emp_id INT(3) PRIMARY KEY,
+ emp_no INT(3) UNIQUE NOT NULL,
+ emp_name VARCHAR(10) NOT NULL,
+ emp_age tinyint(4) NOT NULL DEFAULT 25 CHECK (emp_age BETWEEN 20 AND 60),
+ sex VARCHAR(1) CHECK (sex in ('男','女')),
+ job VARCHAR(20),
+ sal INT(10),
+ -- inline写法
+ -- REFERENCES 主表(主表字段)
+ -- dept_no int REFERENCES t_dept(dept_no)
+ -- outline写法
+ dept_no int NOT NULL ,
+ FOREIGN KEY(dept_no) REFERENCES t_dept(dept_no) ON DELETE SET NULL
+);
+
+格式为 dept_no int NOT NULL
, 但是外键却为 FOREIGN KEY(dept_no) REFERENCES t_dept(dept_no) ON DELETE SET NULL
,删除格式的 NOT NULL
即可
类型 | +字节 | +最小值~最大值(带符号的/无符号的) | +
---|---|---|
TINYINT | +1 | +-128~127 / 0~255 | +
SMALLINT | +2 | +-32768~32767 / 0~65535 | +
MEDIUMINT | +3 | +-8388608~8388607 / 0~16777215 | +
INT | +4 | +-2147483648~2147483647 / 0~4294967295 | +
BIGINT | +8 | +-9223372036854775808~9223372036854775807 / 0~18446744073709551615 | +
INT 类型,占用 4 个字节,每字节 8 个比特,即总共占用 32 个比特,所以:
+无符号型,最大值为 4294967295(即4byte=32bit,最大值即是32个1组成)
+有符号型,最大值为 2147483647。
+所以达到最大值后,INSERT 语句还会使用 2147483647,导致报错。
+建表语句 id int(11) unsigned NOT NULL AUTO_INCREMENT,
中的 11
,只表示可显示的位数,与实际存储长度无关。
show variables like 'character%';
+set names utf8mb4;
+
密钥协商的步骤
+网站方面首先要花一笔银子,在某个 CA 那里购买一个数字证书。
+该证书通常会对应几个文件:其中一个文件包含公钥,还有一个文件包含私钥。网站方面必须在 Web 服务器上部署这两个文件。
+所谓的“公钥”,顾名思义就是可以公开的 key;而所谓的“私钥”就是私密的 key。
+其实前面已经说过了,这里再唠叨一下: “非对称加密算法”从数学上确保了——即使你知道某个公钥,也很难(不是不可能,是很难)根据此公钥推导出对应的私钥。
+这是“一次性”的准备工作。
+当浏览器访问该网站,Web 服务器首先把包含公钥的证书发送给浏览器。
+浏览器验证网站发过来的证书。如果发现其中有诈,浏览器会提示“CA 证书安全警告”。
+由于有了这一步,就大大降低了(注意:是“大大降低”,而不是“彻底消除”)前面提到的“中间人攻击”的风险。
+为啥浏览器能发现 CA 证书是否有诈? +因为正经的 CA 证书,都是来自某个权威的 CA。如果某个 CA 足够权威,那么主流的操作系统(或浏览器)会内置该 CA 的“根证书”。 (比如 Windows 中就内置了几十个权威 CA 的根证书)
+因此,浏览器就可以利用系统内置的根证书,来判断网站发过来的 CA 证书是不是某个 CA 颁发的。 (关于“根证书”和“证书信任链”的概念,请参见之前的教程《数字证书及CA的扫盲介绍》)
+如果网站发过来的 CA 证书没有问题,那么浏览器就从该 CA 证书中提取出“公钥”。
+然后浏览器随机生成一个“对称加密的密钥”(以下称为 k)。用 CA 证书的公钥加密 k,得到密文 k'
+浏览器把 k' 发送给网站。
+网站收到浏览器发过来的 k',用服务器上的私钥进行解密,得到 k。
+至此,浏览器和网站都拥有 k,“密钥交换”大功告成啦。
deploy onlyoffice server
+please see here
+please see here
+please see here
+# for onlyoffice
+map $http_x_forwarded_proto $the_scheme {
+ default $http_x_forwarded_proto;
+ "" $scheme;
+}
+
+map $http_x_forwarded_host $the_host {
+ default $http_x_forwarded_host;
+ "" $host;
+}
+
+map $http_upgrade $proxy_connection {
+ default upgrade;
+ "" close;
+}
+
+......
+
+ location / {
+ # IMPORTANT ! - Trailing slash !
+ proxy_pass http://127.0.0.1:8123/;
+
+ proxy_http_version 1.1;
+ client_max_body_size 0; # Limit Document size to 100MB
+ proxy_read_timeout 3600s;
+ proxy_connect_timeout 3600s;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $proxy_connection;
+
+ # IMPORTANT ! - Subfolder and NO trailing slash !
+ proxy_set_header X-Forwarded-Host $the_host;
+
+ proxy_set_header X-Forwarded-Proto $the_scheme;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+
+
+sudo docker run -i -t -d -p 8123:80 -e JWT_ENABLED=true -e JWT_SECRET=lian-secret-string --name oods-enable-jwt --restart always onlyoffice/documentserver:6.2
+
+30 2 * * 1 certbot renew >> /var/log/le-renew.log
+35 2 * * 1 service nginx restart
+
+>>> import base64
+>>> data = '{"u": "test"}'
+>>> code = base64.b64encode(data.encode('utf-8'))
+>>> code
+'eyJ1IjogInRlc3QifQ=='
+
+Note the trailing ==
to make len a multiple of 4. This decodes properly
>>> len(code)
+20
+>>> base64.b64decode(code)
+'{"u": "test"}'
+>>> base64.b64decode(code) == data
+True
+
+without the == padding (this is how many things are encoded for e.g. access tokens)
+>>> base64.b64decode(code[0:18]) == data
+...
+TypeError: Incorrect padding
+
+However, you can add back the padding
+>>> base64.b64decode(code + b"==") == data
+True
+
+Or add an arbitrary amount of padding (it will ignore extraneous padding)
+>>> base64.b64decode(code + b"========") == data
+True
+
+or
+>>> base64.b64decode(code + b'=' * (-len(code) % 4)) == data
+True
+
+This last property of python's base64 decoding ensures that the following code
+adding 3 padding =
will never succumb to the TypeError and will always produce the same result.
>>> base64.b64decode(code + b"===") == data
+True
+
+It's clumsy but effective method to deal with strings from different implementations of base64 encoders
Django Rest Framework by default will make APIView csrf excempt for ApiView.
+>>> from django.contrib.sessions.models import Session
+>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
+>>> s.expire_date
+datetime.datetime(2005, 8, 20, 13, 35, 12)
+>>> s.session_data
+'KGRwMQpTJ19hdXRoX3VzZXJfaWQnCnAyCkkxCnMuMTExY2ZjODI2Yj...'
+>>> s.get_decoded()
+{'user_id': 42}
+
+手动触发数据库事务的 commit 提交
+from django.db import transaction
+from seahub.utils import gen_token
+
+token = gen_token(30) + gen_token(30)
+transaction.set_autocommit(False)
+try:
+ t = ClientSSOToken(token=token)
+ t.save()
+ transaction.commit()
+except Exception as e:
+ logger.error(e)
+ transaction.rollback()
+finally:
+ transaction.set_autocommit(True)
+
+USE_TZ = True
数据库中记录 UTC 时间,Django 从数据库中取出时间后,会生成 tzinfo
为 UTC
的 datetime
对象。
USE_TZ = True
数据库中记录的是根据 TIME_ZONE
得到的本地时间,Django 从数据库中取出时间后,会生成 tzinfo
为 None
的 datetime
对象。
比如,在 北京时间 2021-04-10 14点左右 创建的数据,在数据中存的时间为:
+数据库中存的时间 | +配置 | +说明 | +
---|---|---|
2021-04-10 13:53:40 | +TIME_ZONE = 'Asia/Shanghai' |
+存的是上海当地时间 | +
2021-04-10 05:57:18 | +USE_TZ = True and TIME_ZONE = 'Asia/Shanghai' |
+忽略 TIME_ZONE 配置,存的是 UTC 时间 |
+
2021-04-10 01:05:29 | +TIME_ZONE = America/Chicago |
+存的是芝加哥当地时间 | +
对比两个 datetime
对象时,如果一个有 tzinfo
、一个没有 tzinfo
则会报以上错误,解决方法:
将 datetime
对象统一 make_naive
(make_aware
也可以)后,再对比。
from django.utils.timezone import make_naive, is_aware
+
+# before make_naive
+# 2021-04-09 05:32:30+00:00
+# tzinfo: UTC
+
+# after make_naive
+# 2021-04-09 13:32:30
+# tzinfo: None
+
+if is_aware(last_login_time):
+ last_login_time = make_naive(last_login_time)
+
+curl -XPUT "http://localhost:9200/movies/movie/1" -d'
+{
+ "title": "The Godfather",
+ "director": "Francis Ford Coppola",
+ "year": 1972,
+ "genres": ["Crime", "Drama"]
+}' -H 'Content-Type: application/json'
+
+curl -XPUT "http://localhost:9200/movies/movie/2" -d'
+{
+ "title": "Lawrence of Arabia",
+ "director": "David Lean",
+ "year": 1962,
+ "genres": ["Adventure", "Biography", "Drama"]
+}' -H 'Content-Type: application/json'
+
+curl -XPUT "http://localhost:9200/movies/movie/3" -d'
+{
+ "title": "To Kill a Mockingbird",
+ "director": "Robert Mulligan",
+ "year": 1962,
+ "genres": ["Crime", "Drama", "Mystery"]
+}' -H 'Content-Type: application/json'
+
+curl -XPUT "http://localhost:9200/movies/movie/4" -d'
+{
+ "title": "Apocalypse Now",
+ "director": "Francis Ford Coppola",
+ "year": 1979,
+ "genres": ["Drama", "War"]
+}' -H 'Content-Type: application/json'
+
+curl -XPUT "http://localhost:9200/movies/movie/5" -d'
+{
+ "title": "Kill Bill: Vol. 1",
+ "director": "Quentin Tarantino",
+ "year": 2003,
+ "genres": ["Action", "Crime", "Thriller"]
+}' -H 'Content-Type: application/json'
+
+curl -XPUT "http://localhost:9200/movies/movie/6" -d'
+{
+ "title": "The Assassination of Jesse James by the Coward Robert Ford",
+ "director": "Andrew Dominik",
+ "year": 2007,
+ "genres": ["Biography", "Crime", "Drama"]
+}' -H 'Content-Type: application/json'
+
+curl -XPOST "http://localhost:9200/_search" -d'
+{
+ "query": {
+ "query_string": {
+ "query": "ford",
+ "fields": ["title"]
+ }
+ }
+}' -H 'Content-Type: application/json'
+
+{
+ "took": 561,
+ "timed_out": false,
+ "_shards": {
+ "total": 4,
+ "successful": 4,
+ "skipped": 0,
+ "failed": 0
+ },
+ "hits": {
+ "total": {
+ "value": 2,
+ "relation": "eq"
+ },
+ "max_score": 1.0467482,
+ "hits": [
+ {
+ "_index": "movies",
+ "_type": "movie",
+ "_id": "3",
+ "_score": 1.0467482,
+ "_source": {
+ "title": "To Kill a Mockingbird",
+ "director": "Robert Mulligan",
+ "year": 1962,
+ "genres": [
+ "Crime",
+ "Drama",
+ "Mystery"
+ ]
+ }
+ },
+ {
+ "_index": "movies",
+ "_type": "movie",
+ "_id": "5",
+ "_score": 1.0467482,
+ "_source": {
+ "title": "Kill Bill: Vol. 1",
+ "director": "Quentin Tarantino",
+ "year": 2003,
+ "genres": [
+ "Action",
+ "Crime",
+ "Thriller"
+ ]
+ }
+ }
+ ]
+ }
+}
+
缩写 | +全称 | +
---|---|
LDAP | +Light Directory Access Portocol | +
DN | +Distinguished Name | +
dc | +Domain Component | +
ou | +Organization Unit | +
cn | +Common Name | +
uid | +User ID | +
cn=username,ou=people,dc=test,dc=com
+
+是一个 DN,代表一条记录,代表一位在 test.com 公司 people 部门的用户 username。
+apt install python3-ldap
+
+import ldap
+from pprint import pprint
+
+ldapconn = ldap.initialize('ldap://ldap.forumsys.com:389')
+ldapconn.simple_bind_s('cn=read-only-admin,dc=example,dc=com', 'password')
+
+base_dn = 'dc=example,dc=com'
+
+print("\nsearch_filter = 'ou=scientists'")
+search_filter = 'ou=scientists'
+result = ldapconn.search_s(base_dn, ldap.SCOPE_SUBTREE, search_filter, None)
+pprint(result)
+
+print("\nsearch_filter = 'uid=tesla'")
+search_filter = 'uid=tesla'
+result = ldapconn.search_s(base_dn, ldap.SCOPE_SUBTREE, search_filter, None)
+pprint(result)
+
+search_filter = 'ou=scientists'
+[('ou=scientists,dc=example,dc=com',
+ {'cn': [b'Scientists'],
+ 'objectClass': [b'groupOfUniqueNames', b'top'],
+ 'ou': [b'scientists'],
+ 'uniqueMember': [b'uid=einstein,dc=example,dc=com',
+ b'uid=galieleo,dc=example,dc=com',
+ b'uid=tesla,dc=example,dc=com',
+ b'uid=newton,dc=example,dc=com',
+ b'uid=training,dc=example,dc=com',
+ b'uid=jmacy,dc=example,dc=com']})]
+
+search_filter = 'uid=tesla'
+[('uid=tesla,dc=example,dc=com',
+ {'cn': [b'Nikola Tesla'],
+ 'gidNumber': [b'99999'],
+ 'homeDirectory': [b'home'],
+ 'mail': [b'tesla@ldap.forumsys.com'],
+ 'objectClass': [b'inetOrgPerson',
+ b'organizationalPerson',
+ b'person',
+ b'top',
+ b'posixAccount'],
+ 'sn': [b'Tesla'],
+ 'uid': [b'tesla'],
+ 'uidNumber': [b'88888']})]
+
+LDAP Server Connection Info:
+Server: www.zflexldap.com
+Port: 389
+Bind DN: cn=ro_admin,ou=sysadmins,dc=zflexsoftware,dc=com
+Bind Password: zflexpass
+
+Other Users IDs and their passwords are:
+uid=guest1,ou=users,ou=guests,dc=zflexsoftware,dc=com
+guest1password
+uid=guest2,ou=users,ou=guests,dc=zflexsoftware,dc=com
+guest2password
+uid=guest3,ou=users,ou=guests,dc=zflexsoftware,dc=com
+guest3password
+
+
+Here are the credentials for an Online LDAP Test Server that you can use for testing your applications that require LDAP-based authentication. Our goal is to eliminate the need for you to download, install and configure an LDAP sever for testing. If all you need is to test connectivity and authentication against a few identities, you have come to the right place.
+LDAP Server Information (read-only access):
+Server: ldap.forumsys.com
+Port: 389
+Bind DN: cn=read-only-admin,dc=example,dc=com
+Bind Password: password
+
+All user passwords are password
.
You may also bind to individual Users (uid) or the two Groups (ou) that include:
+ou=mathematicians,dc=example,dc=com
+
+riemann
+gauss
+euler
+euclid
+
+ou=scientists,dc=example,dc=com
+
+einstein
+newton
+galieleo
+tesla
+
+
+日志级别等级 CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET
+#!/usr/bin/env python
+# coding:utf-8
+
+import logging
+
+logging.basicConfig(
+ level=logging.DEBUG,
+ format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
+ datefmt='%a, %d %b %Y %H:%M:%S',
+ filename='test.log',
+ filemode='w'
+)
+
+logging.debug('debug message')
+logging.info('info message')
+logging.warning('warning message')
+logging.error('error message')
+logging.critical('critical message')
+
+%(name)s Logger的名字
+%(levelno)s 数字形式的日志级别
+%(levelname)s 文本形式的日志级别
+%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
+%(filename)s 调用日志输出函数的模块的文件名
+%(module)s 调用日志输出函数的模块名
+%(funcName)s 调用日志输出函数的函数名
+%(lineno)d 调用日志输出函数的语句所在的代码行
+%(created)f 当前时间,用UNIX标准的表示时间的浮点数表示
+%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
+%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
+%(thread)d 线程ID。可能没有
+%(threadName)s 线程名。可能没有
+%(process)d 进程ID。可能没有
+%(message)s用户输出的消息
+
+代码文件中,直接可以 get 到自定义的 onlyoffice logger,使用自定义的 onlyoffice logger 来处理。
+logger = logging.getLogger('onlyoffice')
+
+代码文件中(seahub/api2/endpoints/upload_links.py
),get logger 时,获取到的是 __name__
(seahub.api2.endpoints.upload_links
),按层级关系依次向上寻找 logger。
logger = logging.getLogger(__name__)
+logger.error('in upload link')
+
+如果自定义或者层级关系均未找到 logger,则使用 root(或 ''
)定义的 logger。
settins.py
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+
+ 'formatters': {
+ 'upload_link_format': {
+ 'format': '%(lineno)s %(funcName)s %(message)s',
+
+ },
+ 'standard': {
+ 'format': '%(asctime)s [%(levelname)s] %(name)s:%(lineno)s %(funcName)s %(message)s',
+
+ }
+ },
+ 'filters': {
+ 'require_debug_false': {
+ '()': 'django.utils.log.RequireDebugFalse'
+ },
+ 'require_debug_true': {
+ '()': 'django.utils.log.RequireDebugTrue'
+ },
+ },
+ 'handlers': {
+ 'upload_link_handler': {
+ 'level': 'DEBUG',
+ 'class': 'logging.StreamHandler',
+ 'formatter': 'upload_link_format',
+ },
+ 'console': {
+ 'level': 'DEBUG',
+ 'filters': ['require_debug_true'],
+ 'class': 'logging.StreamHandler',
+ 'formatter': 'standard',
+ },
+ 'default': {
+ 'level': 'INFO',
+ 'class': 'logging.handlers.RotatingFileHandler',
+ 'filename': os.path.join(LOG_DIR, 'seahub.log'),
+ 'maxBytes': 1024*1024*100, # 100 MB
+ 'backupCount': 5,
+ 'formatter': 'standard',
+ },
+ 'onlyoffice_handler': {
+ 'level': 'INFO',
+ 'class': 'logging.handlers.RotatingFileHandler',
+ 'filename': os.path.join(LOG_DIR, 'onlyoffice.log'),
+ 'maxBytes': 1024*1024*100, # 100 MB
+ 'backupCount': 5,
+ 'formatter': 'standard',
+ },
+ },
+ 'loggers': {
+ '': { # 默认 logger
+ 'handlers': ['default'],
+ 'level': 'INFO',
+ 'propagate': True
+ },
+ 'seahub.api2.endpoints': { # 按层级关系依次向上寻找 logger
+ 'handlers': ['upload_link_handler', ],
+ 'level': 'DEBUG',
+ 'propagate': False
+ },
+ 'onlyoffice': { # 自定义的 onlyoffice logger
+ 'handlers': ['onlyoffice_handler', ],
+ 'level': 'INFO',
+ 'propagate': False
+ },
+ 'django.db.backends': {
+ 'handlers': ['console'],
+ 'level': "DEBUG",
+ 'propagate': False,
+ },
+ }
+}
+
from PIL import Image
+
+filename = r'screencapture-joinquant-research-2020-11-19-15_36_46.jpg'
+img = Image.open(filename)
+size = img.size
+
+height = [0, 2800, 4300, 10300, 16300, 22300]
+for i in range(len(height)):
+
+ if i+1 >= len(height):
+ break
+
+ sub_img = img.crop((0, height[i], size[0], height[i+1]))
+ sub_img.save('tmp_{0}.png'.format(i))
+
pip install mysqlclient
+ubuntu 20.04 +python 3.8.0
+Python.h: No such file or directory
+sudo apt-get install python3-dev +sudo apt install libpython3.8-dev
In [1]: import re
+
+In [2]: line = 'CREATE TABLE IF NOT EXISTS `api2_tokenv2` (`key` varchar(40) NOT NULL PRIMARY KEY...'
+
+In [3]: m = re.search('CREATE TABLE(?: IF NOT EXISTS)? [`"]?(\w+)[`"]?(\s*\(.*)', line)
+
+In [4]: m.groups()
+Out[4]: ('api2_tokenv2', ' (`key` varchar(40) NOT NULL PRIMARY KEY...')
+
+\1
\2
分别代表什么了呢?其实代表的就是group(1)
和group(2)
,可以引用已经匹配出来的字符串。
In [1]: import re
+
+In [2]: line = "created_at datetime NOT NULL DEFAULT `1970-01-01 00:00:00`,"
+
+In [3]: line = re.sub(r"default `([^`]*)`", r"default '\1'", line, 0, re.IGNORECASE)
+
+In [4]: line
+Out[4]: "created_at datetime NOT NULL default '1970-01-01 00:00:00',"
+
+
+from dropbox api
+https://www.dropbox.com/developers/documentation/http/documentation#files-delete_batch
+请求
+curl -X POST https://api.dropboxapi.com/2/files/delete_batch
+
+参数
+{
+ "entries": [
+ {
+ "path": "/Homework/math/Prime_Numbers.txt"
+ }
+ ]
+}
+
+请求
+curl -X POST https://api.dropboxapi.com/2/files/move_batch
+
+
+参数
+{
+ "entries": [
+ {
+ "from_path": "/Homework/math",
+ "to_path": "/Homework/algebra"
+ }
+ ],
+ "allow_shared_folder": false,
+ "autorename": false
+}
+
+已知一只股票过去 200 天的价格
+[Price1, Price2, Price3, ..., Price100, Price101, ..., Price200]
+
+
+求最近 100 天中(Price100 ~ Price200)都有哪几天创了百日新高。
+算第 100 天的价格 Price100 是否创了百日新高。
+[Price1, Price2, Price3, ..., Price99]
中,股价最高是哪天,假设为 PriceX。算第 101 天的价格 Price101 是否创了百日新高。
+[Price2, Price3, Price4, ..., Price100]
,因为根据上次(第 100 天)的计算,已知是 PriceX 了。以此类推计算到第 200 天。
+计算第 Y 天的价格 PriceY 是否创了百日新高时,只要 PriceX 在相对 PriceY 之前的 99 天中,即可直接对比 PriceY 和 PriceX。否则需要再次计算相对 PriceY 之前 99 天的价格中,股价最高是哪天。
saml,Security Assertion Markup Language,安全断言标记语言。
+SP,Service Provider 服务提供方。
+IdP,Identity Provider 身份认证方。
+通过浏览器 GET 或者 POST 请求来转发请求、交互信息。
+IAM,Identity and Access Management.
+参数 | +含义 | +
---|---|
entityID | +IdP 唯一标识。 建议使用域名形式,确保全局唯一。取值中要包含“https://”。 示例:https://www.idp.com | +
两个 |
+是一份包含公钥的证书,该证书用于验证签名。为了确保安全性,建议使用长度大于等于2048位的公钥。SP通过IDP元数据文件中的签名证书来确认认证过程中断言消息的可信性、完整性。 | +
<md:SingleLogoutService中Location的值 | +会话注销功能。示例:https://www.idp.com/saml/logout | +
<md:SingleSignOnService中Location的值 | +IDP处理SAML请求认证的地址,用以接收处理SAMLRequest,并生成SAMLResponse,示例:https://www.idp.com/saml/login | +
未登录时,浏览器会将页面重定向到 IdP,
+HTTP Redirect Binding
+https://samltest.id/idp/profile/SAML2/Redirect/SSO?SAMLRequest=fVNdj5swEHy%2FX4F4Twy0lyArSZUm%2FYiUJijQPvSl2rOXniWwqb3c5f59bZLr5aRrQAixnp3dmV1mDtqm48ue7vUB%2F%2FTo6CaKjm2jHR%2BO5nFvNTfglOMaWnScBC%2BX37Y8Gye8s4aMME38Kul6DjiHlpTRIWmznsf73aft%2Fstm96uWAlLI6izL8xon%2FkGAuywFuJ2K6URmaZokE6hD4g%2B0znPMY08ZvqOosOZBSbQ7X3Eel0VEXsxQw7keN9oRaPL4JEtHye0oS6sk5%2B9ynr3%2FGVBrj1YaaCC9J%2BocZyyoCTRjJZmSHfN6a9UgC2IydkCpLApiZbkPFMXZjY9KS6V%2FX7fh7gRy%2FGtVFaNiX1aBYvlszspo17doS7QPSuD3w%2FbUlW8qzabjxN8pz5N8yj6AcPFisGAW%2BuWDXrt4G90igQQCNmOX4Jf0jgcDN%2BvCNEo8DfFwfTa2Bfq%2FpNQXCBElR%2FUA5b12HQpVK5TxP5pl05jHlUUgPyOyPcYRe1X8vIQoh5X0LhAeKVqZtgOrXJgNHkHQWfCL6Ev4qvE7dsB6cXUNBRcB58OFfz0aK8P8%2FDhRVhZ888bS2aQ3yU9dsyttL26ejy%2F%2Fr8Vf&RelayState=http%3A%2F%2F127.0.0.1%3A8087%2F
Query String Parameters:
+SAMLRequest
。
RelayState
,an URL parameter that we use to say to our Identity Provider where he should send the response back。
下面 python3-saml-demo-django 代码示例中,配置 saml/advanced_settings.json
中的 "authnRequestsSigned": True,
,则会使用 SigAlg
和 Signature
参数。
GET https://samltest.id/idp/profile/SAML2/Redirect/SSO?SAMLRequest=fVNNj9owEL3vr4hyBychfFlARaEfSBQikt1DL5WxJ11LiZ3ak13239cObJeVtiSHKOM3b%2Ba9Gc8sq6uGLlt8VAf404LFuyA41ZWytDuah61RVDMrLVWsBkuR03z5Y0uTfkQbo1FzXYXvkm7nMGvBoNTKJ23W83C%2F%2B7Ldf9vsfqWTMZsM4hFPh8ekTOPRMR0Mk%2Bl0cBSpGIppGsNUJCPmEx%2FAWMcxDx2l%2Fw%2BCzOgnKcDsXMV5mGcBOjFdDWtb2CiLTKHDR0nci4a9eFrEEY0GNI1%2BetTaoaVi2JE%2BIjaWEuLVeJq%2BFESKhji9payAeDEJOYCQBjiSPN97iuzixmephFS%2Fb9twPIMs%2FV4UWS%2Fb54WnWL6as9LKtjWYHMyT5HB%2F2J67ck3FybgfuTemk2gyJp8Yt%2BGis2Dm%2B6WdXrP4GF0DMsGQkRm5Br%2BlN9QbuFlnupL8pYv756s2NcP%2FS4pdAR%2BRold2UNoq2wCXpQQR%2FqNZVpV%2BXhlg6GaEpoUwIO%2BKX5YQRLeSzgWEEwYrXTfMSOtnAyfG8SL4TfQ1fFW5HTtAubi5hpxyj3PhzH2etRF%2Bfm6cIArDXPPa4MWkD8nPXZMbbS%2FuXo%2Bv79fiLw%3D%3D&RelayState=http%3A%2F%2F127.0.0.1%3A8087%2F&Signature=DKJ1wQO7FTxrt7mGh6ytws%2B8KvMaM5AdT1Ls3sBM5JSPZOMsCdvCwMWAOkGU8EE1LGtkFrD6mpjI%2B%2FXeejQ03e6LdjrtGCdVp0ht2tunEpsPv2Ia31%2FvSGs39jCBRmTkPOnl21Of8T%2BOnCfmjxs4qhpFX25KX0TxfhK%2BMP9ZXa1XKAcrdiLXC%2B0jNYQth75eCG%2BrvgW53inFfNy6diNdPqM0AjhCVqhSFezuBuw9BJciVXi9T8occUiWFDCOFn8ThjrJOB0eslft2%2Bx5GrKnjr8RO1so7WnSz1N5FIe5Uw0713iXD16BsM9xVHLHbA01E0SIy%2Bz40J3C4XZyhB43Vw%3D%3D&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256
SigAlg
,可选,签名算法(比如:http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
)。
Signature
,可选,签名值。SP 发起请求前,会将请求中的 SAMLRequest={SAMLRequest}&RelayState={RelayState}&SigAlg={SigAlg}
使用 SP 私钥签名(签名算法为 SigAlg
字段指定的算法),之后再做一次 Base64 编码作为签名值。
如果有参数签名的话,IdP 收到请求后通过 SP 公钥(SP Metadata.xml文件中 <ds:X509Certificate></ds:X509Certificate>
标签内的值)来验证签名。签名验证通过,则表明是合法 SP 发送的请求可以进行后续操作,否则请求非法。
HTTP POST Binding
+POST https://win-mi64c2jsv9s.lian.local/adfs/ls/
Form Data:
+SAMLRequest
。
RelayState
。
显示 Idp 登录页面,用户登录成功后,IdP 发送 POST 请求(就是一个 HTML form 表单和一段立即提交该表单的 JS 代码)给 SP(AssertionConsumerService)并带上以下参数:
+POST https://demo.seafile.top/saml2/acs/
Form Data:
+SAMLResponse
。
RelayState
,samlRequest中相同参数值。
SP 收到 SAMLResponse
后,解析出用户信息,进行后续操作。
https://github.com/imwhatiam/python3-saml-demo-django
+idp 使用 https://samltest.id/ 提供的服务。也可对接 onelogin 提供的 idp 服务,具体参考:https://developers.onelogin.com/saml/python 。
+sp 使用 https://github.com/onelogin/python3-saml
+运行命令(也可下载 Dockerfile 和 settings.json 到同一目录后自行 docker build -t imwhatiam/python3-saml-demo-django:v1 .
):
docker run -it -p 8000:8000 --name test-saml2 imwhatiam/python3-saml-demo-django:v1 bash
+
+进入到容器后,再运行:
+python3 manage.py runserver 0.0.0.0:8000
+
+然后浏览器中访问 http://127.0.0.1:8000/
+点击 Login,之后进入到 https://samltest.id/ 的登录界面,按提示输入用户名密码后,即可跳转回本地,并显示已登录用户的信息。
+我已经预先设置好,如 sp 访问地址变了,需要重新上传:
+我已预先配置好:
+sp 部分使用 onelogin 默认配置 https://github.com/onelogin/python3-saml/blob/master/demo-django/saml/settings.json#L4 ,但注意需要改为自己的域名或IP。
+sp 使用自签名证书:openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout sp.key
idp 部分参考 https://samltest.id/download/#SAMLtest%E2%80%99s_IdP
The quick brown fox jumped over the lazy dogs.
+Click here for some secure content.
+This is some secure content. You should not be able to see it until you have entered your username + and password.
+测试用户名:casuser
+密码:Mellon
pip3 install python-cas
+
+seahub_settings.py
+ENABLE_CAS = True
+CAS_SERVER_URL = 'https://casserver.herokuapp.com/cas/'
+CAS_LOGOUT_COMPLETELY = True
+
+apt-get install libapache2-mod-auth-cas
+
+vi /etc/apache2/mods-enabled/auth_cas.conf
+
+CASCookiePath /var/cache/apache2/mod_auth_cas/
+CASLoginURL https://casserver.herokuapp.com/cas/login
+CASValidateURL https://casserver.herokuapp.com/cas/serviceValidate
+
+vi /etc/apache2/sites-enabled/000-default.conf
+
+<VirtualHost *:80>
+ Alias /media /home/user/haiwen/seafile-server-latest/seahub/media
+
+ RewriteEngine On
+
+ <Location /media>
+ Require all granted
+ </Location>
+
+ <Location /sso>
+ AuthType CAS
+ Require valid-user
+ CASAuthNHeader remote-user
+ </Location>
+
+ # seafile fileserver
+ ProxyPass /seafhttp http://127.0.0.1:8082
+ ProxyPassReverse /seafhttp http://127.0.0.1:8082
+ RewriteRule ^/seafhttp - [QSA,L]
+
+ # seahub
+ SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
+ ProxyPreserveHost On
+ ProxyPass / http://127.0.0.1:8000/
+ ProxyPassReverse / http://127.0.0.1:8000/
+</VirtualHost>
+
+vi /opt/seafile/conf/seahub_settings.py
+
+ENABLE_REMOTE_USER_AUTHENTICATION = True
+REMOTE_USER_DOMAIN = 'your.seafile-domain.com'
+
+yum install mod_auth_cas
+
+vi /etc/httpd/conf.d/auth_cas.conf
+
+其他同上
+\n New to GitHub?\n Create an account.\n
\nLoading preview…
\");try{const s=await ho(e,t.value,r,a,c);n.innerHTML=s||\"Nothing to preview
\"}catch(l){n.innerHTML=\"Error rendering preview
\"}}i(\".js-preview-tab\",function(e){let t,n,s,o,i;e.addEventListener(\"mouseenter\",()=>{!async function(){if(!t){t=d(e,\".js-previewable-comment-form\"),n=u(t,\".js-comment-field\",HTMLTextAreaElement);const r=t.querySelector(\".js-original-line\"),a=t.querySelector(\".js-path\"),c=t.querySelector(\".js-line-number\");s=r instanceof HTMLInputElement?r.value:null,o=a instanceof HTMLInputElement?a.value:null,i=c instanceof HTMLInputElement?c.value:null}try{await ho(t,n.value,s,o,i)}catch(r){}}()})}),N(\"keydown\",\".js-comment-field\",function(e){const t=e.target;if(h(t instanceof HTMLTextAreaElement,\"app/assets/modules/github/behaviors/commenting/preview.js:236\"),(e.ctrlKey||e.metaKey)&&\"P\"===e.key){const n=d(t,\".js-previewable-comment-form\");n.classList.contains(\"write-selected\")&&(t.blur(),n.dispatchEvent(new CustomEvent(\"preview:render\",{bubbles:!0,cancelable:!1})),e.preventDefault(),e.stopImmediatePropagation())}});const vo=/^(\\+1|-1|:\\+1?|:-1?)$/,jo=e=>{let t=!1;for(const n of e.split(\"\\n\")){const e=n.trim();if(e&&!e.startsWith(\">\")){if(t&&!1===vo.test(e))return!1;!t&&vo.test(e)&&(t=!0)}}return t};function yo(e){const t=e.target;h(t instanceof HTMLTextAreaElement,\"app/assets/modules/github/behaviors/commenting/reaction-suggestion.js:43\");const n=t.value,s=d(t,\".js-reaction-suggestion\");if(s)if(jo(n)){s.classList.remove(\"hide-reaction-suggestion\"),s.classList.add(\"reaction-suggestion\");const e=g(s,\"data-reaction-markup\");s.setAttribute(\"data-reaction-suggestion-message\",e)}else Lo(s)}function Lo(e){e.classList.remove(\"reaction-suggestion\"),e.classList.add(\"hide-reaction-suggestion\"),e.removeAttribute(\"data-reaction-suggestion-message\")}l(\"focusout\",\"#new_comment_field\",function(e){const t=e.currentTarget;Lo(d(t,\".js-reaction-suggestion\"))}),l(\"focusin\",\"#new_comment_field\",function(e){yo(e)}),N(\"keyup\",\"#new_comment_field\",function(e){yo(e)}),i(\"details.select-menu details-menu include-fragment\",function(e){const t=e.closest(\"details\");t&&(e.addEventListener(\"loadstart\",function(){t.classList.add(\"is-loading\"),t.classList.remove(\"has-error\")}),e.addEventListener(\"error\",function(){t.classList.add(\"has-error\")}),e.addEventListener(\"loadend\",function(){t.classList.remove(\"is-loading\");const e=t.querySelector(\".js-filterable-field\");e&&L(e,\"filterable:change\")}))}),i(\"details details-menu .js-filterable-field\",{constructor:HTMLInputElement,add(e){const t=d(e,\"details\");t.addEventListener(\"toggle\",function(){t.hasAttribute(\"open\")||(e.value=\"\",L(e,\"filterable:change\"))})}}),i(\"details details-menu remote-input input\",{constructor:HTMLInputElement,add(e){const t=d(e,\"details\");t.addEventListener(\"toggle\",function(){t.hasAttribute(\"open\")||(e.value=\"\")})}}),i(\"form details-menu\",e=>{const t=d(e,\"form\");t.addEventListener(\"reset\",()=>{setTimeout(()=>(function(e){const t=e.querySelectorAll(\"details-menu [role=menuitemradio] input[type=radio]:checked\");for(const n of t)L(n,\"change\")})(t),0)})}),l(\"details-menu-selected\",\"[data-menu-input]\",e=>{const t=g(e.target,\"data-menu-input\"),n=document.getElementById(t);(n instanceof HTMLInputElement||n instanceof HTMLTextAreaElement)&&(h(e instanceof CustomEvent,\"app/assets/modules/github/behaviors/details-menu.js:77\"),h(e.detail.relatedTarget instanceof HTMLButtonElement,\"app/assets/modules/github/behaviors/details-menu.js:78\"),n.value=e.detail.relatedTarget.value)},{capture:!0}),i(\"details-menu remote-input\",{constructor:B,initialize(e){const t=document.getElementById(e.getAttribute(\"aria-owns\")||\"\");if(!t)return;let n;e.addEventListener(\"load\",()=>{n=document.activeElement&&t.contains(document.activeElement)&&document.activeElement.id?document.activeElement.id:null}),e.addEventListener(\"loadend\",()=>{if(n){const s=t.querySelector(`#${n}`)||t.querySelector('[role^=\"menu\"]');s?s.focus():e.input&&e.input.focus()}})}}),l(\"details-menu-selected\",\"details-menu[data-menu-max-options]\",e=>{const t=+g(e.currentTarget,\"data-menu-max-options\")===e.currentTarget.querySelectorAll('[role=\"menuitemcheckbox\"][aria-checked=\"true\"]').length;u(e.currentTarget,\"[data-menu-max-options-warning]\").hidden=!t;for(const n of p(e.currentTarget,'[role=\"menuitemcheckbox\"] input',HTMLInputElement))n.disabled=t&&!n.checked},{capture:!0});const wo=new WeakMap,Eo=[\"input[type=submit][data-disable-with]\",\"button[data-disable-with]\"].join(\", \");function To(e,t){e instanceof HTMLInputElement?e.value=t:e.innerHTML=t}function ko(e){for(const t of e.querySelectorAll(Eo)){const n=wo.get(t);null!=n&&(h(t instanceof HTMLInputElement||t instanceof HTMLButtonElement,\"app/assets/modules/github/behaviors/disable-with.js:57\"),To(t,n),t.hasAttribute(\"data-disable-invalid\")&&!e.checkValidity()||(t.disabled=!1),wo.delete(t))}}l(\"submit\",\"form\",function(e){for(const n of e.currentTarget.querySelectorAll(Eo)){h(n instanceof HTMLInputElement||n instanceof HTMLButtonElement,\"app/assets/modules/github/behaviors/disable-with.js:41\"),wo.set(n,(t=n)instanceof HTMLInputElement?t.value||\"Submit\":t.innerHTML||\"\");const e=n.getAttribute(\"data-disable-with\");e&&To(n,e),n.disabled=!0}var t},{capture:!0}),l(\"deprecatedAjaxComplete\",\"form\",function({currentTarget:e,target:t}){h(e instanceof HTMLFormElement,\"app/assets/modules/github/behaviors/disable-with.js:71\"),e===t&&ko(e)}),P(ko),l(\"menu:activate\",\".js-select-menu\",function(e){e.currentTarget.classList.add(\"is-dirty\")}),l(\"menu:deactivate\",\".js-select-menu\",function(e){e.currentTarget.classList.remove(\"is-dirty\")});const Mo={OS:\"Meta\",Win:\"Meta\",Windows:\"Meta\",Scroll:\"ScrollLock\",SpaceBar:\" \",Left:\"ArrowLeft\",Right:\"ArrowRight\",Down:\"ArrowDown\",Up:\"ArrowUp\",Del:\"Delete\",Esc:\"Escape\"};const Ao=Object.getOwnPropertyDescriptor(KeyboardEvent.prototype,\"key\");if(Ao){let e=e=>Ao.get.apply(e);/Macintosh.*Safari/.test(navigator.userAgent)&&(e=(e=>{return function(e,t){return t&&/^[a-z]$/.test(e)?e.toUpperCase():e}(Ao.get.apply(e),e.shiftKey)})),Object.defineProperty(KeyboardEvent.prototype,\"key\",{enumerable:!0,configurable:!0,get(){return t=e(this),Mo[t]||t;var t}})}const xo=new WeakMap;function So(e){return xo.has(e)}function Ho(e,t,n){const s=n.limit,o=u(e,\"template\",HTMLTemplateElement),i={};for(const u of p(e,\"input[type=hidden]\",HTMLInputElement))i[`${u.name}${u.value}`]=u;let r=o.nextElementSibling;for(;r;){const e=r;r=e.nextElementSibling,e.classList.contains(\"selected\")||e.classList.contains(\"select-menu-divider\")?e.classList.add(\"d-none\"):e.remove()}const a=xo.get(e);h(null!=a,\"app/assets/modules/github/substring-memory-filter-list.js:69\");let c=0,l=document.createDocumentFragment();const d=e.querySelector(\".js-divider-suggestions\"),m=e.querySelector(\".js-divider-rest\");function f(e){const n=!(null!=s&&c>=s)&&function(e){return`${e.login} ${e.name}`.toLowerCase().trim()}(e).indexOf(t)>=0;if(n||e.selected){const t=function(e,t,n){if(null!=e.element)return e.element;const s=t.content.cloneNode(!0),o=u(s,\"input[type=checkbox]\",HTMLInputElement);e.type&&(o.name=`reviewer_${e.type}_ids[]`);o.value=e.id;const i=`${o.name}${e.id}`;let r=e.selected;n[i]&&(r=!0,n[i].remove(),delete n[i]);const a=u(s,\".js-navigation-item\");r&&(a.classList.add(\"selected\"),o.checked=!0);e.disabled&&a.classList.add(\"disabled\");const c=s.querySelector(\".js-username\");c&&(c.textContent=e.login);const l=s.querySelector(\".js-description\");l&&(l.textContent=e.name);const d=s.querySelector(\".js-extended-description\");d&&(e.description?d.textContent=e.description:d.remove());return u(s,\".js-avatar\",HTMLImageElement).src=e.avatar,e.element=a,e.element}(e,o,i);t.classList.toggle(\"d-none\",!n),n&&c++,l.appendChild(t)}}if(d&&a.suggestions){for(const e of a.suggestions)f(e);l.childNodes.length&&(d.after(l),d.classList.toggle(\"d-none\",0===c),l=document.createDocumentFragment())}const g=c;for(const u of a.users)f(u);return e.append(l),m&&m.classList.toggle(\"d-none\",c===g||0===g),c}function Co(e,t){let n=_o(e,t);if(n&&-1===t.indexOf(\"/\")){n+=_o(e.substring(e.lastIndexOf(\"/\")+1),t)}return n}function qo(e){const t=e.toLowerCase().split(\"\");let n=\"\";for(let s=0;s