Doris-ansible自动化部署脚本

包结构

暂时无法在飞书文档外展示此内容

perl 复制代码
doris_ansible
├── conf  (配置文件路径)
│   ├── cgroup.yml                 (be单机多实例部署时cgroup配置项)
│   ├── hosts                      (ansiable主机列表)
│   ├── scale_be_vars.yml          
│   └── setup_vars.yml             (部署参数)
├── core  (playbook路径)
│   ├── scale_be_start.yml         (be批量启动playbook)
│   ├── scale_be.yml               (be部署playbook)
│   ├── scale_fe.yml               (fe部署playbook)
│   └── templates   (doris配置文件模板路径)
│       ├── be.conf.j2             (be配置文件模板)
│       ├── doris_be_supervisor_multi.ini.j2      (be_supervisor多实例配置文件模板)
│       ├── doris_be_supervisor_single.ini.j2     (be_supervisor单机部署配置文件模板)
│       ├── doris_fe_supervisor.ini.j2            (fe_supervisor配置文件模板)
│       └── fe.conf.j2              (fe配置文件模板)
└── pack             (Doris安装包及jdk安装包存放路径)
    ├── apache-doris-2.1.7-bin-x64-noavx2.tar.gz
    └── jdk-8u271-linux-x64.tar.gz

配置文件

hosts

ini 复制代码
[doris_be_scale]
10.2.2.242 ansible_port=36000
10.2.2.145 ansible_port=36000
10.2.2.252 ansible_port=36000
[doris_fe_scale]
10.2.2.245 ansible_port=36000

需要部署be的主机ip写到[doris_be_scale]下方

需要部署fe的主机ip写到[doris_be_scale]下方

ansible_port=36000是当目标主机ssh端口不是默认22时设置的,如果ssh端口为22,可以去掉该参数

setup_vars.yml

yaml 复制代码
---
################################  BE  ######################################
#be如果需要进行单机多实例部署,按照如下配置进行编写
# be_port_v:
#   - 9061
#   - 9062

# be端口设置
be_port_v:
  - 9060
  - 9061
webserver_port_v:
  - 8040
  - 8041
heartbeat_service_port_v:
  - 9050
  - 9051
brpc_port_v:
  - 8060
  - 8061
# doris_be存储目录
storage_path: 
  - /data/data1/doris
  - /data/data2/doris
# jdbc驱动包路径
jdbc_drivers_dir_v: /home/doris/apache-doris/jdbc_drivers
################################  FE  ######################################
#fe 配置
http_port_v: 8030
rpc_port_v: 9020
query_port_v: 9030
edit_log_port_v: 9010
meta_dir_v: /data/data/fe/fe_meta
LOG_DIR_v: /data/data/doris/log
lower_case_table_names_v: 0
#拓展fe节点是写master_ip:port
#例如fe_leader:10.2.2.245:9010
#如新部署,则为空
fe_leader:
##############################  公共配置  ####################################
# doris安装目录。
doris_be_home: 
  - /data/data1
  - /data/data2
doris_fe_home:
  - /data/data
#如果be需要单机多实例部署,如下
#doris_be_home:
#  - /data1
#  - /data2

#网络设置
priority_networks_v: 10.2.2.0/24
# jdk安装包路径
jdk_filepath: ../pack/jdk-8u271-linux-x64.tar.gz
# jdk安装目录
# 如果目标机器已经安装jdk,不需要重新安装,请将java_home填写成现有jdk安装路径
java_home: /usr/local/java/jdk1.8.0_271/
# doris安装包路径
doris_filepath: ../pack/apache-doris-2.1.7-bin-x64-noavx2.tar.gz

cgroup.yml

makefile 复制代码
#cgroup组
cgroup_group:
  - doris_be1
  - doris_be2
#cpu节点设置
cpus_set:
  - 0-1
  - 2-3
#内存节点设置
cpu_mems_set:
  - 0
  - 0
#最大内存限制
mem_limit:
  - 2G
  - 2G
#cgroup路径
cgroup_path : "/sys/fs/cgroup/"

根据实际情况填写,上述例子是单机2节点be安装。机器配置为4c4g

操作指引

  1. 根据需要填写hosts、setup_vars.yml、cgroup.yml

  2. 检查目标机器是否已经做过互信,ssh目标机器,如无需密码即为完成互信

  3. 将Doris安装包,jdk安装包(如果已经安装则不需要上传)上传至doris_ansible/pack目录。

  4. 执行ansiable-playbook

    1. 部署FE ansible-playbook -i doris_ansible/conf/hosts doris_ansible/core/scale_fe.yml
    2. 部署BE ansible-playbook -i doris_ansible/conf/hosts doris_ansible/core/scale_be.yml

FE扩容

markdown 复制代码
[root@localhost ~]# ansible-playbook -i doris_ansible/conf/hosts doris_ansible/core/scale_fe.yml 

PLAY [doris_fe_scale] *************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************
ok: [10.2.2.245]

TASK [create group doris(创建用户组doris)] *********************************************************************************************************************************************
ok: [10.2.2.245]

TASK [create user doris(创建用户doris)] ***********************************************************************************************************************************************
ok: [10.2.2.245]

TASK [check java(检查java是否安装)] *****************************************************************************************************************************************************
ok: [10.2.2.245]

TASK [mkdir java home_path(创建java安装目录)] *******************************************************************************************************************************************
ok: [10.2.2.245]

TASK [install java(解压缩java安装包)] ***************************************************************************************************************************************************
skipping: [10.2.2.245]

TASK [mkdir fe home_path(创建fe安装目录)] ***********************************************************************************************************************************************
ok: [10.2.2.245] => (item=/data/data)

TASK [Check if fe binary exists in each installation directory(检查fe二进制文件是否存在)] ****************************************************************************************************
ok: [10.2.2.245] => (item=/data/data)

TASK [unarchive doris(解压BE安装包)] ***************************************************************************************************************************************************
skipping: [10.2.2.245] => (item={u'failed': False, u'stat': {u'charset': u'us-ascii', u'uid': 1001, u'exists': True, u'attr_flags': u'', u'woth': False, u'device_type': 0, u'mtime': 1730879331.0, u'block_size': 4096, u'inode': 403481468, u'isgid': False, u'size': 8117, u'executable': True, u'roth': True, u'isuid': False, u'readable': True, u'isreg': True, u'version': u'18446744073313675030', u'pw_name': u'doris', u'gid': 1001, u'ischr': False, u'wusr': True, u'writeable': True, u'isdir': False, u'blocks': 16, u'xoth': True, u'rusr': True, u'nlink': 1, u'issock': False, u'rgrp': True, u'gr_name': u'doris', u'path': u'/data/data/fe/bin/start_fe.sh', u'xusr': True, u'atime': 1733987588.5360384, u'mimetype': u'text/plain', u'ctime': 1733987159.17523, u'wgrp': False, u'checksum': u'6781c8fa6c827c181b8ae0b0deae01f01a0e3490', u'dev': 2064, u'isblk': False, u'isfifo': False, u'mode': u'0755', u'xgrp': True, u'islnk': False, u'attributes': []}, u'ansible_loop_var': u'item', u'item': u'/data/data', u'invocation': {u'module_args': {u'follow': False, u'get_checksum': True, u'path': u'/data/data/fe/bin/start_fe.sh', u'checksum_algorithm': u'sha1', u'get_md5': False, u'get_mime': True, u'get_attributes': True}}, u'changed': False}) 

TASK [mkdir fe storage(创建fe存储目录)] *************************************************************************************************************************************************
ok: [10.2.2.245] => (item=/data/data/fe/fe_meta)

TASK [create fe conf(创建fe配置文件)] ***************************************************************************************************************************************************
ok: [10.2.2.245]

TASK [change fe owner(更改fe目录属主)] **************************************************************************************************************************************************
ok: [10.2.2.245]

TASK [change fe owner(更改fe目录属主)] **************************************************************************************************************************************************
ok: [10.2.2.245]

TASK [Check if supervisor is installed(检查supervisor是否安装)] *************************************************************************************************************************
ok: [10.2.2.245]

TASK [Install epel-release(安装epel仓库)] *********************************************************************************************************************************************
ok: [10.2.2.245]

TASK [Install supervisor via yum if not present(安装supervisor)] ********************************************************************************************************************
ok: [10.2.2.245]

TASK [Create supervisor config for Doris FE(创建supervisor配置文件)] ********************************************************************************************************************
changed: [10.2.2.245]

TASK [start be] *******************************************************************************************************************************************************************
skipping: [10.2.2.245] => (item=/data/data) 

TASK [移除start_fe.sh中 '&'] *********************************************************************************************************************************************************
ok: [10.2.2.245]

TASK [修改supervisor.conf minfds 值为 1000000] ****************************************************************************************************************************************
changed: [10.2.2.245]

TASK [重启supervisord服务] ************************************************************************************************************************************************************
changed: [10.2.2.245]

TASK [Check if supervisor is running(检查supervisord是否启动)] **************************************************************************************************************************
ok: [10.2.2.245]

TASK [Reread and Update supervisor config(supervisor加载文件及更新)] *********************************************************************************************************************
changed: [10.2.2.245]

TASK [Stop Doris FE(停止doris_fe进程)] ************************************************************************************************************************************************
changed: [10.2.2.245]

TASK [Check if Doris BE is running(检查doris_fe进程是否启动)] *****************************************************************************************************************************
changed: [10.2.2.245]

TASK [Show result(输出doris_fe进程状态信息)] **********************************************************************************************************************************************
ok: [10.2.2.245] => {
    "msg": [
        "doris_fe                         STOPPED   Dec 13 11:27 AM"
    ]
}

PLAY RECAP ************************************************************************************************************************************************************************
10.2.2.245                 : ok=23   changed=6    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0   

BE扩容

python 复制代码
[root@localhost doris_ansible]# ansible-playbook -i conf/hosts core/scale_be.yml

PLAY [doris_be_scale] *************************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************
fatal: [10.2.2.145]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 10.2.2.145 port 36000: No route to host", "unreachable": true}
ok: [10.2.2.242]
ok: [10.2.2.252]

TASK [create group doris(创建用户组doris)] *********************************************************************************************************************************************
ok: [10.2.2.252]
ok: [10.2.2.242]

TASK [create user doris(创建用户doris)] ***********************************************************************************************************************************************
ok: [10.2.2.242]
ok: [10.2.2.252]

TASK [check java(检查java是否安装)] *****************************************************************************************************************************************************
ok: [10.2.2.252]
ok: [10.2.2.242]

TASK [mkdir java home_path(创建java安装目录)] *******************************************************************************************************************************************
ok: [10.2.2.252]
ok: [10.2.2.242]

TASK [install java(解压缩java安装包)] ***************************************************************************************************************************************************
skipping: [10.2.2.242]
skipping: [10.2.2.252]

TASK [mkdir be home_path(创建be安装目录)] ***********************************************************************************************************************************************
ok: [10.2.2.242] => (item=/data/data1)
ok: [10.2.2.252] => (item=/data/data1)
ok: [10.2.2.242] => (item=/data/data2)
ok: [10.2.2.252] => (item=/data/data2)

TASK [Check if BE binary exists in each installation directory(检查be二进制文件是否存在)] ****************************************************************************************************
ok: [10.2.2.242] => (item=/data/data1)
ok: [10.2.2.252] => (item=/data/data1)
ok: [10.2.2.242] => (item=/data/data2)
ok: [10.2.2.252] => (item=/data/data2)

TASK [unarchive doris(解压BE安装包)] ***************************************************************************************************************************************************
skipping: [10.2.2.242] => (item={u'failed': False, u'stat': {u'charset': u'us-ascii', u'uid': 1001, u'exists': True, u'attr_flags': u'', u'woth': False, u'device_type': 0, u'mtime': 1730879332.0, u'block_size': 4096, u'inode': 134382560, u'isgid': False, u'size': 12018, u'executable': True, u'roth': True, u'isuid': False, u'readable': True, u'isreg': True, u'version': u'1492749182', u'pw_name': u'doris', u'gid': 1001, u'ischr': False, u'wusr': True, u'writeable': True, u'isdir': False, u'blocks': 24, u'xoth': True, u'rusr': True, u'nlink': 1, u'issock': False, u'rgrp': True, u'gr_name': u'doris', u'path': u'/data/data1/be/bin/start_be.sh', u'xusr': True, u'atime': 1734000561.657362, u'mimetype': u'text/plain', u'ctime': 1734000524.7656057, u'wgrp': False, u'checksum': u'87b8a550cc760b5dbbae9fcc961437db69005bd9', u'dev': 2064, u'isblk': False, u'isfifo': False, u'mode': u'0755', u'xgrp': True, u'islnk': False, u'attributes': []}, u'ansible_loop_var': u'item', u'item': u'/data/data1', u'invocation': {u'module_args': {u'follow': False, u'get_checksum': True, u'path': u'/data/data1/be/bin/start_be.sh', u'checksum_algorithm': u'sha1', u'get_md5': False, u'get_mime': True, u'get_attributes': True}}, u'changed': False}) 
skipping: [10.2.2.242] => (item={u'failed': False, u'stat': {u'charset': u'us-ascii', u'uid': 1001, u'exists': True, u'attr_flags': u'', u'woth': False, u'device_type': 0, u'mtime': 1730879332.0, u'block_size': 4096, u'inode': 134269890, u'isgid': False, u'size': 12018, u'executable': True, u'roth': True, u'isuid': False, u'readable': True, u'isreg': True, u'version': u'18446744072778930415', u'pw_name': u'doris', u'gid': 1001, u'ischr': False, u'wusr': True, u'writeable': True, u'isdir': False, u'blocks': 24, u'xoth': True, u'rusr': True, u'nlink': 1, u'issock': False, u'rgrp': True, u'gr_name': u'doris', u'path': u'/data/data2/be/bin/start_be.sh', u'xusr': True, u'atime': 1734000561.5733602, u'mimetype': u'text/plain', u'ctime': 1734000525.8206272, u'wgrp': False, u'checksum': u'87b8a550cc760b5dbbae9fcc961437db69005bd9', u'dev': 2064, u'isblk': False, u'isfifo': False, u'mode': u'0755', u'xgrp': True, u'islnk': False, u'attributes': []}, u'ansible_loop_var': u'item', u'item': u'/data/data2', u'invocation': {u'module_args': {u'follow': False, u'get_checksum': True, u'path': u'/data/data2/be/bin/start_be.sh', u'checksum_algorithm': u'sha1', u'get_md5': False, u'get_mime': True, u'get_attributes': True}}, u'changed': False}) 
skipping: [10.2.2.252] => (item={u'failed': False, u'stat': {u'charset': u'us-ascii', u'uid': 1001, u'exists': True, u'attr_flags': u'', u'woth': False, u'device_type': 0, u'mtime': 1730879332.0, u'block_size': 4096, u'inode': 402653314, u'isgid': False, u'size': 12018, u'executable': True, u'roth': True, u'isuid': False, u'readable': True, u'isreg': True, u'version': u'18446744072516289342', u'pw_name': u'doris', u'gid': 1001, u'ischr': False, u'wusr': True, u'writeable': True, u'isdir': False, u'blocks': 24, u'xoth': True, u'rusr': True, u'nlink': 1, u'issock': False, u'rgrp': True, u'gr_name': u'doris', u'path': u'/data/data1/be/bin/start_be.sh', u'xusr': True, u'atime': 1734054049.606421, u'mimetype': u'text/plain', u'ctime': 1734000524.4029808, u'wgrp': False, u'checksum': u'87b8a550cc760b5dbbae9fcc961437db69005bd9', u'dev': 2064, u'isblk': False, u'isfifo': False, u'mode': u'0755', u'xgrp': True, u'islnk': False, u'attributes': []}, u'ansible_loop_var': u'item', u'item': u'/data/data1', u'invocation': {u'module_args': {u'follow': False, u'get_checksum': True, u'path': u'/data/data1/be/bin/start_be.sh', u'checksum_algorithm': u'sha1', u'get_md5': False, u'get_mime': True, u'get_attributes': True}}, u'changed': False}) 
skipping: [10.2.2.252] => (item={u'failed': False, u'stat': {u'charset': u'us-ascii', u'uid': 1001, u'exists': True, u'attr_flags': u'', u'woth': False, u'device_type': 0, u'mtime': 1730879332.0, u'block_size': 4096, u'inode': 402784073, u'isgid': False, u'size': 12018, u'executable': True, u'roth': True, u'isuid': False, u'readable': True, u'isreg': True, u'version': u'18446744072650006313', u'pw_name': u'doris', u'gid': 1001, u'ischr': False, u'wusr': True, u'writeable': True, u'isdir': False, u'blocks': 24, u'xoth': True, u'rusr': True, u'nlink': 1, u'issock': False, u'rgrp': True, u'gr_name': u'doris', u'path': u'/data/data2/be/bin/start_be.sh', u'xusr': True, u'atime': 1734054049.4594178, u'mimetype': u'text/plain', u'ctime': 1734000525.3360002, u'wgrp': False, u'checksum': u'87b8a550cc760b5dbbae9fcc961437db69005bd9', u'dev': 2064, u'isblk': False, u'isfifo': False, u'mode': u'0755', u'xgrp': True, u'islnk': False, u'attributes': []}, u'ansible_loop_var': u'item', u'item': u'/data/data2', u'invocation': {u'module_args': {u'follow': False, u'get_checksum': True, u'path': u'/data/data2/be/bin/start_be.sh', u'checksum_algorithm': u'sha1', u'get_md5': False, u'get_mime': True, u'get_attributes': True}}, u'changed': False}) 

TASK [mkdir be storage(创建be存储目录)] *************************************************************************************************************************************************
ok: [10.2.2.242] => (item=/data/data1/doris)
ok: [10.2.2.252] => (item=/data/data1/doris)
ok: [10.2.2.242] => (item=/data/data2/doris)
ok: [10.2.2.252] => (item=/data/data2/doris)

TASK [create be conf(创建be配置文件)] ***************************************************************************************************************************************************
ok: [10.2.2.242] => (item=0)
ok: [10.2.2.252] => (item=0)
ok: [10.2.2.242] => (item=1)
ok: [10.2.2.252] => (item=1)

TASK [change be owner(更改be目录属主)] **************************************************************************************************************************************************
ok: [10.2.2.252] => (item=/data/data1)
ok: [10.2.2.242] => (item=/data/data1)
ok: [10.2.2.252] => (item=/data/data2)
ok: [10.2.2.242] => (item=/data/data2)

TASK [start be] *******************************************************************************************************************************************************************
skipping: [10.2.2.242] => (item=/data/data1) 
skipping: [10.2.2.242] => (item=/data/data2) 
skipping: [10.2.2.252] => (item=/data/data1) 
skipping: [10.2.2.252] => (item=/data/data2) 

TASK [install cgroup(安装libcgroup-tools)] ******************************************************************************************************************************************
ok: [10.2.2.252]
ok: [10.2.2.242]

TASK [create cgroup group(创建cgroup组)] *********************************************************************************************************************************************
changed: [10.2.2.252] => (item=doris_be1)
changed: [10.2.2.242] => (item=doris_be1)
changed: [10.2.2.242] => (item=doris_be2)
changed: [10.2.2.252] => (item=doris_be2)

TASK [modify cpuset.cpus(修改cpuset.cpus内容)] ****************************************************************************************************************************************
changed: [10.2.2.252] => (item=0)
changed: [10.2.2.242] => (item=0)
changed: [10.2.2.252] => (item=1)
changed: [10.2.2.242] => (item=1)

TASK [modify cpuset.mems(修改cpuset.mems内容)] ****************************************************************************************************************************************
changed: [10.2.2.242] => (item=0)
changed: [10.2.2.252] => (item=0)
changed: [10.2.2.252] => (item=1)
changed: [10.2.2.242] => (item=1)

TASK [modify memory.limit_in_bytes(修改memory.limit_in_bytes内容)] ********************************************************************************************************************
changed: [10.2.2.252] => (item=0)
changed: [10.2.2.242] => (item=0)
changed: [10.2.2.242] => (item=1)
changed: [10.2.2.252] => (item=1)

TASK [modify cpuset folder owner(修改cpuset文件夹属主)] **********************************************************************************************************************************
changed: [10.2.2.242] => (item=doris_be1)
changed: [10.2.2.252] => (item=doris_be1)
changed: [10.2.2.252] => (item=doris_be2)
changed: [10.2.2.242] => (item=doris_be2)

TASK [modify memory folder group(修改memory文件夹属主)] **********************************************************************************************************************************
changed: [10.2.2.242] => (item=doris_be1)
changed: [10.2.2.252] => (item=doris_be1)
changed: [10.2.2.242] => (item=doris_be2)
changed: [10.2.2.252] => (item=doris_be2)

TASK [Check if supervisor is installed(检查supervisor是否安装)] *************************************************************************************************************************
ok: [10.2.2.242]
ok: [10.2.2.252]

TASK [Install epel-release(安装epel仓库)] *********************************************************************************************************************************************
ok: [10.2.2.252]
ok: [10.2.2.242]

TASK [Install supervisor via yum if not present(安装supervisor)] ********************************************************************************************************************
ok: [10.2.2.242]
ok: [10.2.2.252]

TASK [Create supervisor config for Doris BE(创建supervisor配置文件)] ********************************************************************************************************************
ok: [10.2.2.242] => (item=0)
ok: [10.2.2.252] => (item=0)
ok: [10.2.2.242] => (item=1)
ok: [10.2.2.252] => (item=1)

TASK [Create supervisor config for Doris BE(创建supervisor配置文件)] ********************************************************************************************************************
skipping: [10.2.2.242]
skipping: [10.2.2.252]

TASK [设置 vm.max_map_count =2000000 ----/etc/sysctl.conf] **************************************************************************************************************************
ok: [10.2.2.252]
ok: [10.2.2.242]

TASK [生效内核配置] *********************************************************************************************************************************************************************
changed: [10.2.2.242]
changed: [10.2.2.252]

TASK [设置文件描述符 soft nofile limit] **************************************************************************************************************************************************
ok: [10.2.2.242]
ok: [10.2.2.252]

TASK [设置文件描述符 hard nofile limit] **************************************************************************************************************************************************
ok: [10.2.2.242]
ok: [10.2.2.252]

TASK [修改supervisor.conf minfds 值为 1000000] ****************************************************************************************************************************************
ok: [10.2.2.242]
ok: [10.2.2.252]

TASK [重启supervisord服务] ************************************************************************************************************************************************************
changed: [10.2.2.252]
changed: [10.2.2.242]

TASK [Check if supervisor is running(检查supervisord是否启动)] **************************************************************************************************************************
ok: [10.2.2.252]
ok: [10.2.2.242]

TASK [Start supervisor if not running(启动supervisor)] ******************************************************************************************************************************
skipping: [10.2.2.242]
skipping: [10.2.2.252]

TASK [Reread and Update supervisor config(supervisor加载文件及更新)] *********************************************************************************************************************
changed: [10.2.2.242]
changed: [10.2.2.252]

TASK [Check if Doris BE is running(检查doris_be进程是否启动)] *****************************************************************************************************************************
changed: [10.2.2.252]
changed: [10.2.2.242]

TASK [Show result(输出doris_be进程状态信息)] **********************************************************************************************************************************************
ok: [10.2.2.242] => {
    "msg": [
        "doris_be1                        STARTING  ", 
        "doris_be2                        STARTING  "
    ]
}
ok: [10.2.2.252] => {
    "msg": [
        "doris_be1                        RUNNING   pid 32153, uptime 0:00:42", 
        "doris_be2                        RUNNING   pid 32152, uptime 0:00:42"
    ]
}

PLAY RECAP ************************************************************************************************************************************************************************
10.2.2.145                 : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0   
10.2.2.242                 : ok=31   changed=10   unreachable=0    failed=0    skipped=5    rescued=0    ignored=0   
10.2.2.252                 : ok=31   changed=10   unreachable=0    failed=0    skipped=5    rescued=0    ignored=0   
相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax