当前位置: 首页 > news >正文

Ansible之剧本和角色

playbook剧本

YAML简介

YAML(YAML Ain't Markup Language的缩写)是一种人类可读的完整的数据序列化语言。

YAML 官网首页就很 YAML,这很有意思。 The Official YAML Web Site

通常用作软件的配置文件;

YAML基本语法规则(基于显式指示符的表示范围的Flow Style):

  • 流序列:以逗号分隔的列表形式写入,并置于方括号[]内;

  • 流映射:以逗号分隔的列表形式写入,并置于大括号{}内;

YAML支持3种基本数据类型:

  • 标量(Scalar):原子数据类型,如:字符串(String)、数字(Numbers)、布尔值(Boolean)和空值(null)等;

  • 序列(Sequence):节点列表,类似某些编程语言的数组(Array)、列表(List)等;

  • 映射(Mapping):节点到节点的映射,键值对,类似某些编程语言的哈希(Hash)、哈希映射(Hash Map)、字典(Dict)、对象(Objects)等;

与许多编程语言不同,键key可以是序列或映射。 一个YAML文件或流可以包含多个文档Documents 文档可以显式地以---开头(可选的)。在规范中称此为指令结束标记,但实际使用中更多称为文档开始标记; 文档可以显式地以...结尾(可选的)。在规范中称此为文档结束标记,如果文档没有指令,可以省略---,仅用...来分割多个文档; 如果YAML文件或流仅包含一个文档,那么--- ...可以省略

YAML支持两个指令: 版本指令:指令放在---之前行,如 %YAML 1.2 用于告知 YAML 处理器使用哪个版本处理当前文档; 标签指令:为用户提供自定义处理程序的速记标记的方法,一般很少使用

  • yaml格式:以.yaml或者.yml结尾

 root@server-21:~# cat /etc/netplan/00-installer-config.yaml# This is the network config written by 'subiquity'network:ethernets:ens33:dhcp4: nodhcp6: noaddresses: [192.168.221.21/24]gateway4: 192.168.221.2nameservers:addresses: [114.114.114.114,8.8.8.8]version: 2

yaml格式要求:

1.严格区分大小写

2.缩进统一,同一级别配置要对齐

  • 缩进不允许使用Tab制表符,只允许使用空格字符;

  • 缩进的空格数不重要,但官方推荐使用2个空格字符;

  • 冒号后面至少要有一个空格

Ansible案例

前提条件:Ubuntu配置好四台设备

 # 前提是每台机子都apt update192.168.221.120   manage01192.168.221.21   node1192.168.221.22   node2192.168.221.23   node3#(仅管理机)# ssh免密(输入yes和密码)ssh-keygenssh-copy-id root@node1ssh-copy-id root@node2ssh-copy-id root@node3#下载ansibleapt updateapt install ansible#查看版本ansible --version#创建ansiblemkdir /etc/ansiblecd /etc/ansiblevim hosts--------------------------------------------[group1]node1    # 对应 /etc/hosts 中的 192.168.221.21node2    # 对应 192.168.221.22node3    # 对应 192.168.221.23--------------------------------------------# ping节点ansible -m ping group1node3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}node1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}node2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}

案例一:playbook部署LNMP环境

 # Ubuntu部署LNMP环境vim lnmp.yaml---- name: 部署LNMP环境hosts: group1become: truetasks:- name: 安装Nginxapt:name: nginxstate: present- name: 安装MySQL服务器apt:name: mysql-serverstate: present- name: 安装PHP及相关扩展apt:name: "{{ packages }}"state: presentvars:packages:- php-fpm- php-mysql- php-cli- php-gd- php-curl- php-mbstring- name: 启动并启用Nginx服务service:name: nginxstate: startedenabled: true- name: 启动并启用MySQL服务service:name: mysqlstate: startedenabled: true- name: 启动并启用PHP-FPM服务service:name: php8.1-fpmstate: startedenabled: true# 执行剧本# 执行剧本但不真正生效,用于检测脚本是否正确ansible-playbook -C lnmp.yaml# 正常执行剧本ansible-playbook lnmp.yaml

课后练习:

1.Ansible剧本配Yum源或APT源(Ubuntu已完成,Centos未尝试)

 vim apt.yml---- name: 配置Yum/APT软件源hosts: node1become: yesgather_facts: yesvars:# 通用配置use_mirrors: truebackup_existing: true# Yum源配置yum_mirror_url: "https://mirrors.aliyun.com"centos_mirror: "{{ yum_mirror_url }}/centos"epel_mirror: "{{ yum_mirror_url }}/epel"# APT源配置apt_mirror_url: "http://mirrors.aliyun.com"ubuntu_mirror: "{{ apt_mirror_url }}/ubuntu"debian_mirror: "{{ apt_mirror_url }}/debian"​tasks:- name: 检测操作系统家族fail:msg: "不支持的操作系统: {{ ansible_distribution }}"when: >ansible_os_family != "RedHat" andansible_os_family != "Debian"​# RedHat系列配置块- name: 为RedHat系统备份现有repo文件block:- name: 备份整个repo目录command: >mv /etc/yum.repos.d /etc/yum.repos.d.backup_{{ ansible_date_time.epoch }}when: backup_existing- name: 创建新的repo目录file:path: /etc/yum.repos.dstate: directorymode: '0755'- name: 配置CentOS基础源copy:dest: /etc/yum.repos.d/CentOS-Base.repomode: '0644'content: |# CentOS-Base.repo[base]name=CentOS-$releasever - Basebaseurl={{ centos_mirror }}/$releasever/os/$basearch/gpgcheck=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}​[updates]name=CentOS-$releasever - Updatesbaseurl={{ centos_mirror }}/$releasever/updates/$basearch/gpgcheck=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}​[extras]name=CentOS-$releasever - Extrasbaseurl={{ centos_mirror }}/$releasever/extras/$basearch/gpgcheck=1gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}​[centosplus]name=CentOS-$releasever - Plusbaseurl={{ centos_mirror }}/$releasever/centosplus/$basearch/gpgcheck=1enabled=0gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-{{ ansible_distribution_major_version }}- name: 配置EPEL源copy:dest: /etc/yum.repos.d/epel.repomode: '0644'content: |[epel]name=Extra Packages for Enterprise Linux $releasever - $basearchbaseurl={{ epel_mirror }}/$releasever/$basearchenabled=1gpgcheck=0- name: 清理Yum缓存command: yum clean all- name: 更新Yum缓存command: yum makecachewhen: ansible_os_family == "RedHat"​# Debian系列配置块- name: 为Debian系统配置APT源block:- name: 备份现有sources.listcommand: >cp /etc/apt/sources.list /etc/apt/sources.list.backup_{{ ansible_date_time.epoch }}when: backup_existing- name: 配置Ubuntu源copy:dest: /etc/apt/sources.listmode: '0644'content: |# Ubuntu sources listdeb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }} main restricted universe multiversedeb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }}-security main restricted universe multiversedeb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }}-updates main restricted universe multiversedeb {{ ubuntu_mirror }}/ {{ ansible_distribution_release }}-backports main restricted universe multiversewhen: ansible_distribution == "Ubuntu"- name: 配置Debian源copy:dest: /etc/apt/sources.listmode: '0644'content: |# Debian sources listdeb {{ debian_mirror }}/ {{ ansible_distribution_release }} main contrib non-freedeb {{ debian_mirror }}/ {{ ansible_distribution_release }}-updates main contrib non-freedeb {{ debian_mirror }}/ {{ ansible_distribution_release }}-security main contrib non-freewhen: ansible_distribution == "Debian"- name: 更新APT缓存apt:update_cache: yescache_valid_time: 3600when: ansible_os_family == "Debian"​# 验证配置- name: 验证RedHat系统软件源配置command: yum repolistregister: yum_reposwhen: ansible_os_family == "RedHat"- name: 显示Yum仓库列表debug:msg: "Yum仓库配置成功,共 {{ yum_repos.stdout_lines[-1] }} 个仓库"when: ansible_os_family == "RedHat"- name: 验证Debian系统软件源配置command: apt-cache policyregister: apt_sourceswhen: ansible_os_family == "Debian"- name: 显示APT配置状态debug:msg: "APT源配置成功,缓存已更新"when: ansible_os_family == "Debian"

2.Ansible剧本修改IP地址与主机名(没出错但也没生效)

 ---- name: 配置Ubuntu系统网络和主机名hosts: node1become: truegather_facts: true​vars:# 网络配置new_ip_address: "192.168.221.100"new_netmask: "255.255.255.0"new_gateway: "192.168.221.2"new_dns_servers:- "8.8.8.8"- "8.8.4.4"network_interface: "ens33"  # 必须根据实际情况修改!# 主机名配置new_hostname: "ubuntu-server"new_domain: "local.lan"# 备份配置backup_files: true​tasks:- name: 显示当前系统信息debug:msg: |当前系统信息:主机名: {{ ansible_hostname }}IP地址: {{ ansible_default_ipv4.address }}网卡: {{ ansible_default_ipv4.interface }}所有网卡: {{ ansible_interfaces }}​- name: 检测实际网卡名称shell: ip -o link show | awk -F': ' '{print $2}' | grep -v loregister: network_interfaceschanged_when: false​- name: 显示检测到的网卡debug:msg: "系统检测到的网卡: {{ network_interfaces.stdout_lines }}"​- name: 备份原始主机名配置copy:src: /etc/hostnamedest: /etc/hostname.backup_{{ ansible_date_time.epoch }}remote_src: truebackup: "{{ backup_files }}"when: backup_files​- name: 备份原始hosts文件copy:src: /etc/hostsdest: /etc/hosts.backup_{{ ansible_date_time.epoch }}remote_src: truebackup: "{{ backup_files }}"when: backup_files​- name: 备份原始网络配置copy:src: "{{ item }}"dest: "{{ item }}.backup_{{ ansible_date_time.epoch }}"remote_src: truebackup: "{{ backup_files }}"loop: "{{ query('fileglob', '/etc/netplan/*.yaml') + query('fileglob', '/etc/netplan/*.yml') }}"ignore_errors: yeswhen: backup_files​- name: 修改主机名hostname:name: "{{ new_hostname }}"​- name: 更新hosts文件中的主机名blockinfile:path: /etc/hostsmarker: "# {mark} ANSIBLE MANAGED BLOCK - Hostname"block: |127.0.0.1 localhost127.0.1.1 {{ new_hostname }}.{{ new_domain }} {{ new_hostname }}# IPv6配置::1 localhost ip6-localhost ip6-loopbackff02::1 ip6-allnodesff02::2 ip6-allroutersstate: present​- name: 获取当前Netplan配置文件find:paths: /etc/netplanpatterns: "*.yaml,*.yml"file_type: fileregister: netplan_files​- name: 显示找到的Netplan文件debug:msg: "找到的Netplan配置文件: {{ netplan_files.files | map(attribute='path') | list }}"​- name: 禁用其他Netplan配置(避免冲突)file:path: "{{ item }}"state: absentloop: "{{ netplan_files.files | map(attribute='path') | list }}"when: item != '/etc/netplan/99-static-ip.yaml'​- name: 配置静态IP地址copy:dest: "/etc/netplan/99-static-ip.yaml"content: |network:version: 2renderer: networkdethernets:{{ network_interface }}:dhcp4: nodhcp6: noaddresses:- {{ new_ip_address }}/24routes:- to: defaultvia: {{ new_gateway }}nameservers:addresses: [{{ new_dns_servers | join(', ') }}]mode: '0644'​- name: 验证Netplan配置语法command: netplan generatechanged_when: false​- name: 应用网络配置command: netplan apply​- name: 重启网络服务systemd:name: systemd-networkdstate: restartedwhen: ansible_distribution == 'Ubuntu'​- name: 等待网络服务稳定wait_for_connection:timeout: 60​- name: 强制系统重启(确保所有更改生效)reboot:msg: "应用网络和主机名配置后重启"connect_timeout: 5reboot_timeout: 300pre_reboot_delay: 0post_reboot_delay: 30​- name: 验证新的主机名(重启后)shell: hostnameregister: hostname_checkchanged_when: false​- name: 验证新的IP地址(重启后)shell: |ip addr show {{ network_interface }} 2>/dev/null | grep "inet " | awk '{print $2}' || echo "未配置"register: ip_checkchanged_when: false​- name: 显示最终配置结果debug:msg: |🎉 系统配置完成!最终配置信息:设置的主机名: {{ new_hostname }}实际主机名: {{ hostname_check.stdout | default('未知') }}设置的IP地址: {{ new_ip_address }}实际IP地址: {{ ip_check.stdout | default('未知') }}网卡接口: {{ network_interface }}如果配置不正确,请检查:1. 网卡名称是否正确2. IP地址是否在正确的网段3. 网关地址是否正确

3.Ansible剧本实现系统优化:设置时区、时间同步、更新软件、关闭防火墙、SELinux

第一个剧本:设置时区

 ### 设置时区vim time.yml---- name: 设置时区hosts: node1become: yestasks:- name: Set timezone to Asia/Shanghaitimezone:name: Asia/Shanghai

第二个实验:时间同步

 ### 时间同步---- name: 基础时间同步配置hosts: group1become: yesgather_facts: yes​vars:ntp_servers: ["ntp.aliyun.com", "ntp1.aliyun.com"]​tasks:- name: 设置时区timezone:name: Asia/Shanghai​- name: 安装chrony (RedHat)package: name=chrony state=presentwhen: ansible_os_family == "RedHat"​- name: 安装chrony (Debian)package: name=chrony state=presentwhen: ansible_os_family == "Debian"​- name: 配置chrony服务systemd:name: "{{ 'chronyd' if ansible_os_family == 'RedHat' else 'chrony' }}"state: startedenabled: yes​- name: 显示当前时间command: dateregister: current_timechanged_when: false​- name: 显示配置结果debug:msg: "时间同步配置完成!当前时间: {{ current_time.stdout }}"

还没有写:更新软件、关闭防火墙、SELinux

4.Ansible剧本安装单机LNMP、LNMT(案例一已完成LNMP,LNMT还未排完错)

 ---- name: 部署LNMT环境hosts: node1become: truegather_facts: true​vars:# Tomcat配置tomcat_version: 9java_package: "openjdk-11-jdk"# 数据库配置 - 生产环境请务必修改这些密码!mysql_root_password: "root123"mysql_app_database: "mysql"mysql_app_user: "root"mysql_app_password: "root123"# 应用配置app_context_path: "/myapp"tomcat_manager_user: "admin"tomcat_manager_password: "admin123!"# 网络配置server_host: "localhost"​tasks:- name: 检查系统版本debug:msg: "检测到系统: {{ ansible_distribution }} {{ ansible_distribution_version }}"​- name: 更新软件包缓存apt:update_cache: yescache_valid_time: 3600​- name: 安装基础依赖apt:name: "{{ item }}"state: presentloop:- curl- wget- vim- unzip​- name: 安装Nginxapt:name: nginxstate: present​- name: 安装MySQL服务器apt:name: "{{ item }}"state: presentloop:- mysql-server- mysql-client- python3-pymysql​- name: 安装Java环境apt:name: "{{ java_package }}"state: present​- name: 设置Java环境变量lineinfile:path: /etc/environmentline: "JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64"state: present​- name: 安装Tomcatapt:name: "{{ item }}"state: presentloop:- "tomcat{{ tomcat_version }}"- "tomcat{{ tomcat_version }}-admin"- "tomcat{{ tomcat_version }}-docs"- "tomcat{{ tomcat_version }}-examples"​- name: 等待MySQL服务启动systemd:name: mysqlstate: startedregister: mysql_service_result​- name: 安全配置MySQL(使用debconf设置root密码)debconf:name: mysql-serverquestion: mysql-server/root_passwordvalue: "{{ mysql_root_password }}"vtype: password​- name: 确认MySQL root密码debconf:name: mysql-serverquestion: mysql-server/root_password_againvalue: "{{ mysql_root_password }}"vtype: password​- name: 重新配置MySQLshell: dpkg-reconfigure -f noninteractive mysql-serverwhen: mysql_service_result.changed- name: 安装PyMySQL模块pip:name: PyMySQLstate: presentbecome: yes​- name: 创建应用数据库mysql_db:name: "{{ mysql_app_database }}"state: presentlogin_user: rootlogin_password: "{{ mysql_root_password }}"ignore_errors: yes​- name: 创建应用数据库用户mysql_user:name: "{{ mysql_app_user }}"host: localhostpassword: "{{ mysql_app_password }}"priv: "{{ mysql_app_database }}.*:ALL"state: presentlogin_user: rootlogin_password: "{{ mysql_root_password }}"ignore_errors: yes​- name: 确保Tomcat webapps目录存在file:path: "/var/lib/tomcat{{ tomcat_version }}/webapps/ROOT"state: directoryowner: tomcatgroup: tomcatmode: '0755'recurse: yes​- name: 创建测试JSP页面copy:dest: "/var/lib/tomcat{{ tomcat_version }}/webapps/ROOT/index.jsp"content: |<%@ page contentType="text/html;charset=UTF-8" language="java" %><!DOCTYPE html><html><head><title>LNMT环境测试</title></head><body><h1>✅ LNMT环境部署成功!</h1><p>服务器时间: <%= new java.util.Date() %></p><p>Java版本: <%= System.getProperty("java.version") %></p><p>Tomcat版本: <%= application.getServerInfo() %></p><p>服务器: <%= request.getServerName() %>:<%= request.getServerPort() %></p><h2>数据库连接测试</h2><%try {Class.forName("com.mysql.cj.jdbc.Driver");out.println("<p style='color: green;'>✅ MySQL驱动加载成功</p>");} catch (Exception e) {out.println("<p style='color: red;'>❌ MySQL驱动加载失败: " + e.getMessage() + "</p>");}%></body></html>owner: tomcatgroup: tomcatmode: '0644'​- name: 配置Tomcat用户权限copy:dest: "/etc/tomcat{{ tomcat_version }}/tomcat-users.xml"content: |<?xml version='1.0' encoding='utf-8'?><tomcat-users xmlns="http://tomcat.apache.org/xml"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"version="1.0"><role rolename="manager-gui"/><role rolename="admin-gui"/><role rolename="manager-script"/><user username="{{ tomcat_manager_user }}"password="{{ tomcat_manager_password }}"roles="manager-gui,admin-gui,manager-script"/></tomcat-users>owner: tomcatgroup: tomcatmode: '0640'notify: 重启Tomcat服务​- name: 允许外部访问Tomcat管理界面lineinfile:path: "/etc/tomcat{{ tomcat_version }}/server.xml"regexp: '<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve"'line: '<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve"'state: presentnotify: 重启Tomcat服务​- name: 配置Nginx代理Tomcatcopy:dest: /etc/nginx/sites-available/tomcatcontent: |server {listen 80;server_name {{ server_host }};# 静态文件缓存location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {expires 1y;add_header Cache-Control "public, immutable";}# API代理location {{ app_context_path }}/ {proxy_pass http://localhost:8080{{ app_context_path }}/;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_connect_timeout 30s;proxy_read_timeout 60s;}# 根路径代理location / {proxy_pass http://localhost:8080/;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}# Tomcat管理界面location /manager/ {proxy_pass http://localhost:8080/manager/;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}}mode: '0644'notify: 重新加载Nginx配置​- name: 启用Nginx Tomcat站点file:src: /etc/nginx/sites-available/tomcatdest: /etc/nginx/sites-enabled/tomcatstate: linknotify: 重新加载Nginx配置​- name: 禁用Nginx默认站点file:path: /etc/nginx/sites-enabled/defaultstate: absentnotify: 重新加载Nginx配置​- name: 配置防火墙规则ufw:rule: allowport: "{{ item }}"proto: tcploop:- 80- 8080- 3306ignore_errors: yes​- name: 启动并启用服务systemd:name: "{{ item }}"state: startedenabled: trueloop:- nginx- mysql- "tomcat{{ tomcat_version }}"​- name: 等待服务启动完成wait_for:port: "{{ item }}"delay: 10state: startedtimeout: 120loop:- 80- 8080- 3306​- name: 验证服务状态shell: systemctl is-active {{ item }}register: service_statuschanged_when: falseloop:- nginx- mysql- "tomcat{{ tomcat_version }}"​- name: 显示部署摘要debug:msg: |🎉 LNMT环境部署完成!​服务状态:Nginx: {{ service_status.results[0].stdout }}MySQL: {{ service_status.results[1].stdout }}Tomcat: {{ service_status.results[2].stdout }}访问地址:🌐 网站: http://{{ ansible_default_ipv4.address }}:80🚀 Tomcat直接访问: http://{{ ansible_default_ipv4.address }}:8080📊 Tomcat管理: http://{{ ansible_default_ipv4.address }}:8080/manager/html安全信息:🔑 Tomcat管理员: {{ tomcat_manager_user }} / {{ tomcat_manager_password }}🔒 MySQL root密码: {{ mysql_root_password }}💾 应用数据库: {{ mysql_app_database }} (用户: {{ mysql_app_user }})​handlers:- name: 重新加载环境变量command: source /etc/environmentchanged_when: false- name: 重启Tomcat服务systemd:name: "tomcat{{ tomcat_version }}"state: restarted​- name: 重新加载Nginx配置systemd:name: nginxstate: reloaded

5.Ansible剧本安装集群:MySQL主从、Redis哨兵(未完成)


Ansible-role角色

1、核心

  • 将剧本根据功能进行拆分成不同组件;

  • 每个组件只完成一项简单任务;

  • 复杂任务通过组合多个组件来实现;

2、优缺点

  • 优点:灵活、低耦合、高复用;

  • 缺点:编写的复杂性高,对技术要求高;

http://www.dtcms.com/a/494885.html

相关文章:

  • 有什么平台做网站比较好河南省住房和城乡建设部网站首页
  • 靶场练习2
  • 把 1688 商品详情「搬进 MySQL」:Java 爬虫全链路实战(2025 版)
  • java内存性能优化工具Mat
  • React 18.x 学习计划 - 第四天:React Hooks深入
  • 地学考研专业选择学科地理、人文地理,还是GIS?不想考数学怎么选?
  • React 2025 完全指南:核心原理、实战技巧与性能优化
  • 大数据平台建站重庆网站制作团队
  • Linux CentOS 7 安装配置HAProxy完整指南:实现高可用负载均衡
  • 【小白笔记】PyTorch 和 Python 基础的这些问题
  • linux学习笔记(35)C语言连接mysql
  • 消息推送策略:如何在营销与用户体验间找到最佳平衡点
  • go资深之路笔记(九)kafka浅析
  • Java String 性能优化与内存管理:现代开发实战指南
  • 【软考备考】 NoSQL数据库有哪些,键值型、文档型、列族型、图数据库的特点与适用场景
  • 论《素数的几种筛法》
  • html静态页面怎么放在网站上原平的旅游网站怎么做的
  • 网页设计与网站建设作业公众号小程序制作步骤
  • 律师怎么做网站简单大气网站模板
  • 偏振相机在半导体制造的领域的应用
  • Uniapp微信小程序开发:EF Core 中级联删除
  • Java从入门到精通 - 集合框架(二)
  • 3proxy保姆级教程:WIN连接远端HTTPS代理
  • 大厂AI各走“开源”路
  • 室内装修效果图网站有哪些百度网盟推广是什么
  • grootN1 grootN1.5 gr00t安装方法以及使用(学习)
  • Typora(跨平台MarkDown编辑器) v1.12.2 中文绿色版
  • Unity开发抖音小游戏的震动
  • 团队作业——概要设计和数据库设计
  • 在Spring Boot开发中,HEAD、OPTIONS和 TRACE这些HTTP方法各有其特定的应用场景和实现方式