B站播放地址:https://www.bilibili.com/video/BV1PE411i7CV?t=51

博客地址:https://www.cnblogs.com/hellokuangshen/p/12516870.html

狂神参考雷锋阳的视频讲的,建议视频还是看雷锋阳的

1、学习路线

2、什么是SpringBoot

SpringBoot是一个Javaweb的开发框架,和SpringMVC类似,相比其他Javaweb框架,SpringBoot简化开发,约定大于配置,能迅速地开发web应用,几行代码开发一个http接口。

随着Spring不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,使Spring框架的使用变得没那么简单,人称“配置地狱”。SpringBoot正是在这样的背景下被抽象出来的开发框架,目的是为了让大家更容易地使用Spring、更容易地集成各种常用的中间件和开源框架。

SpringBoot是基于Spring开发,SpringBoot本身并不提供Spring框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于Spring框架的应用程序。也就是说,SpringBoot并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。SpringBoot****以约定大于配置的核心思想,默认帮我们进行了很多配置,多数SpringBoot应用只需要很少的Spring配置。同时,SpringBoot集成了大量常用的第三方库配置(例如RedisMongoDBRabbitMQ等),SpringBoot应用中这些第三方库几乎可以零配置的开箱即用。

SpringBoot的优点:

  • 为所有Spring开发者更快的入门;
  • 开箱即用,即提供各种默认配置来简化项目配置;
  • 内嵌式容器简化web项目;
  • 没有冗余代码生成和XML配置的要求。

3、微服务概念介绍

3.1 什么是微服务?

微服务是一种架构风格,它要求我们开发一个应用的时候,这个应用必须构建成一系列子服务的组合,可以通过http或者RPC的方式进行服务间的通信。

3.2 单体应用服务

所谓单体应用架构(all in one)是指,我们将一个应用中的所有应用服务都封装在一个应用中,比如把数据库访问、web访问等功能都放到一个war包里。

单体应用架构的优点是:易于开发和测试,也十分方便部署,当需要扩展是,只需将war包复制多份,然后放到多台服务器上,再做个负载均衡即可。

单体应用架构的缺点是:哪怕要修改一个十分微小的地方,都需要停掉整个服务,重新打包,部署这个应用的war包,各服务的部署升级等可能依赖其他服务,不够灵活。特别是对于一个大型应用,我们不可能把所有内容都放在一个应用中。

3.3 微服务架构

所谓微服务架构,就是打破之前单体应用服务的架构方式,把每个功能元素独立出来抽象成一个模块,不同的应用程序可能是不同的模块组合而成的,每个模块就称为一个微服务,微服务间可以独立地打包部署升级。

这样做的好处是:

  • 节省了调用资源;
  • 每个微服务都是一个可替换的,可独立升级的软件代码。

有关微服务的文章博客,可以参考:

4、第一个SpringBoot程序

  • 可以在官网直接下载后,导入IDEA开发(https://start.spring.io/);
  • 直接使用IDEA创建一个springboot项目(一般开发直接在IDEA中创建);

目录结构:

Main:

package com.jerry;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}

Controller:

package com.jerry.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping("hello")
@ResponseBody
public String hello()
{
return "byte dance";
}
}

使用方法:

打开浏览器,输入 http://localhost:8080/hello/hello,会返回一个网页,网页显示byte dance字符串。

5、SpringBoot自动装配原理

5.1 启动器

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

启动器:就是SpringBoot的启动场景,有多种启动器,比如spring-boot-starter-web,就会帮我们自动导入web环境所有的依赖,springboot将每一个功能场景都生成一个对应的启动器,我们需要什么样的功能和场景,只需要引入对应的starter即可。

5.2 主程序

// 标注这个类是一个springboot应用
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
// 将springboot应用启动
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
}

我们可以把 @SpringBootApplication看作是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的集合。

根据 SpringBoot 官网,这三个注解的作用分别是:

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制;
  • @ComponentScan: 扫描被@Component@Service@Controller注解修饰的 bean,注解默认会扫描该类所在的包下所有的类;
  • @Configuration:允许在 Spring 上下文中注册额外的 bean 或导入其他配置类。

5.3 自动装配原理

总结:springboot所有自动配置都是在启动的时候扫描并加载,spring.factories所有的自动配置都在这里面,但是不一定生效,要判断条件是否成立,只有导入了对应的starter,就有对应的启动器了,有了启动器,我们自动装配就会生效,配置就会成功。

TMD还没讲怎么用呢就开始讲底层是不是撒比???

6、主启动类如何运行(后续补充)

7、Yaml

7.1 Springboot配置文件

Springboot的配置文件一般位于resource目录下,配置文件的作用:修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们配置好了。

Springboot使用一个全局的配置文件,配置文件名称是固定的

  • application.properties

  • 语法结构:key=value

  • 不推荐使用

  • application.yml

  • 语法结构:key:空格 value

7.2 Yaml语法

7.2.1 Yaml概述

Yaml全称:“Yet Another Markup Language”,仍然是一种标记语言。

以前的配置文件,大多数使用xml来配置,比如一个简单的端口配置,对比一下xml和yaml配置:

<server>
<port>8080</port>
</server>
server:
port: 8080

7.2.2 yaml基本语法

  • yaml大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许用tab,只允许用空格
  • '#'表示注释,没有多行注释

yaml支持普通的k-v、数组/列表、映射(map)和自定义类的实例。

(1)普通的k-v

name: zhangjian

(2)数组/列表

意思是数组和列表都是一样的写法,有两种格式:不写成一行和写成一行。

不写成一行:

  hobbies:
- code
- music

写成一行:

hobbies: [code, music]

注意:code和music逗号后面的空格可有可不有。

(3)映射(map)

数据结构就是Java里的map结构,同样有两种格式:不写成一行和写成一行。

不写成一行:

  scoreMap:
- Math: 100
- English: 100

写成一行:

scoreMap: {Math: 100, English: 100}

注意:key和value中间的:后面一定要有空格,不同的k-v对,即逗号后面的空格可有可不有。

(4)自定义类的实例

Bean如下:

public class Dog {
private String name;
private int age;
}

Bean对应的yml写法如下:

  dog:
name: miumiu
age: 1

Student的成员属性有Dog的实例,yml写法如下:

student:
name: zhangjian
age: 17
isMarry: false
birthday: 1993/05/16
scoreMap: {Math: 100, English: 100}
hobbies: [code,music] dog:
name: miumiu
age: 1

7.2.3 占位符

${}

person:
name: qinjiang${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: ${person.hello:other}_旺财
age: 1

8、给属性赋值的几种方式

8.1 通过Yaml配置文件赋值

application.yml配置文件中将类的属性值配好,在要配置属性的类中,通过注解@ConfigurationProperties(prefix = "XXX")将yml配置文件中的配置项和实体类相关联,@ConfigurationProperties作用:将配置文件中配置的每一个属性的值,映射到这个类中,告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定,举例如下:

Student类:

package com.jerry.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import java.util.Date;
import java.util.List;
import java.util.Map; @Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "student")
public class Student {
private String name;
private Integer age;
private Date birthday;
private Boolean isMarry;
private Map<String, Integer> scoreMap;
private List<String> hobbies;
private Dog dog;
}

注意Student类被注解@ConfigurationProperties(prefix = "student")修饰。

Dog类:

package com.jerry.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component; @Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Dog {
private String name;
private int age;
}

yml配置文件:

student:
name: zhangjian
age: 17
isMarry: false
birthday: 1993/05/16
scoreMap: {Math: 100, English: 100}
hobbies: [code,music] dog:
name: miumiu
age: 1

Student类被注解@ConfigurationProperties(prefix = "student")修饰,其中prefix="student",这个student对应的就是yml配置文件里的student。

测试类:

package com.jerry;

import com.jerry.pojo.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest
class Springboot01HelloworldApplicationTests {
@Autowired
private Student student; @Test
void contextLoads() {
System.out.println(student);
}
}

注意@Autowired自动将student属性注入到Springboot01HelloworldApplicationTests类中。

8.2 @PropertySource加载指定的配置文件

不推荐

8.3 @Value配置(推荐,公司基本都用这个)

还需搭配@Autowired@Component注解使用。

8.4 小结

  • 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
  • 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!

9、JSR303校验

见SpringBoot常用注解 6、参数校验部分:https://www.yuque.com/docs/share/5e853752-a1ba-4d24-ad6f-6c7ec5147dbc?#

10、多环境配置及配置文件位置

实际开发过程中可能有多种环境,比如研发环境、类生产环境、现网环境等,每一类环境对应的配置文件都不一样,比如不同的环境对接不同的数据源,因此就存在这种场景:需要灵活地切换不同的配置文件供Spring加载。

SpringBoot提供两种方式进行多环境配置:

  • properties
  • yml

10.1 properties多环境配置

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本,例如:

  • application-test.properties,代表测试环境配置
  • application-dev.properties,代表开发环境配置

当上述配置文件同时存在于resource目录下时,它****默认使用application.properties主配置文件

当我们需要切换成test环境或者dev环境的配置时,只需要在application.properties主配置文件中加入:

spring.profiles.active=测试级别(dev or test)

可通过在不同的配置文件中配置不同的端口号验证。

10.2 yml多环境配置

和properties配置文件中一样,但是使用yml去实现多环境配置不需要创建多个配置文件,更加方便了 !

server:
port: 8081
#选择要激活哪个环境块
spring:
profiles:
active: test ---
server:
port: 8083
spring:
profiles: dev #配置环境的名称 ---
server:
port: 8084
spring:
profiles: myEnv #配置环境的名称

如果yml和properties同时对dev或者test环境编写了配置文件 , 默认会使用dev或者test环境的properties配置文件。

11、SpringBoot进行web开发

用SpringBoot进行web开发要解决的问题:

  • 静态资源的导入
  • 首页
  • 模板引擎(Thymeleaf)
  • 装配SpringMVC
  • 增删改查
  • 拦截器
  • 国际化(实现中英文切换)

12、静态资源导入

12.1 web静态资源和动态资源

静态资源和动态资源的概念

  • 静态资源:一般客户端发送请求到web服务器,web服务器从内存在取到相应的文件,返回给客户端,客户端解析并渲染显示出来。
  • 动态资源:一般客户端请求的动态资源,先将请求交于web容器,web容器连接数据库,数据库处理数据之后,将内容交给web服务器,web服务器返回给客户端解析渲染处理。

静态资源和动态资源的区别

  • 静态资源一般都是设计好的html页面,而动态资源依靠设计好的程序来实现按照需求的动态响应;
  • 静态资源的交互性差,动态资源可以根据需求自由实现;
  • 在服务器的运行状态不同,静态资源不需要与数据库参于程序处理,动态资源可能需要多个数据库的参与运算。

12.2 静态资源导入的几种方式

12.2.1 webjars + jQuery

没啥实际意义。

12.2.2 静态资源映射规则

将静态资源放在以下目录,可以被SpringBoot识别到:

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

其中classpath为resource目录:

我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件,一般遵循以下规则:

  • static存放静态资源;
  • public存放公共的资源;
  • resource存放上传的图片等资源。

优先级:resource > static > public。

12.3 自定义资源路径

我们也可以自己指定特定目录来存放静态资源,在application.properties中配置:

spring.resources.static-locations=classpath:/coding/,classpath:/kuang/

其中classpath为上面截图中的resource目录,/coding和/kuang是我们自定义存放静态资源的目录,一旦自己定义了静态文件目录的路径,原来的自动配置就都会失效了,不推荐自己再自定义一个资源路径。

13、首页定制

首页就是我们访问一个网站首先出现的页面,比如百度,我们将首页对应的index.html(文件名必须是这个,springboot源码写死的)文件放在上面讲的静态资源目录下,比如public或者static目录下,如下:

重新运行springboot,打开浏览器,如图:

我们可以精心编写首页对应的html文件,浏览器渲染index.html后,会为我们呈现绚丽的网站首页画面!

14、Thymeleaf模板引擎

14.1 什么是web的模板引擎

模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,是用于前端脚本简化字符串拼接的。模板引擎提供一个模板,后端传来的数据源(json或者字符串格式)通过这个模板引擎的处理生成一个html文本,浏览器再通过渲染这个html文本给用户呈现最终的网站页面,流程如下:

ThymeleafSpringBoot推荐的模板引擎,此外FreeMarker也是使用较多的模板引擎。

SpringBoot使用Thymeleaf模板引擎,需要引入Thymeleafmaven依赖:

<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

此外,还需要在html文本里引入Thymeleaf的命名空间:

<html lang="en" xmlns:th="http://www.thymeleaf.org">

14.2 Thymeleaf语法

15、SpringMVC自动配置

X、分布式 Dubbo + Zookeeper + SpringBoot

X.1 分布式理论

分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。分布式系统是由一组通过网络进行通信,为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器、处理更多的数据。

分布式系统是建立在网络之上的软件系统。

首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(比如加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本事就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题...

可以参考Dubbo官网的背景介绍:http://dubbo.apache.org/zh-cn/docs/user/preface/background.html

以下摘自上面的连接(感觉很浓缩很高度,但是凭我现在的开发经验还不能领会...):

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

Todo:上面四种模式需要网上查资料弄清楚,最好每种模式对应一个结构图,需要总结!

X.2 RPC框架

参考:https://www.yuque.com/zhangjian-mbxkb/uxqnxx/ag2a4f

X.2.1 什么是RPC?

RPC(Remote Procedure Call)是远程过程调用,是一种进程间通信的方式,RPC是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显示编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

举个例子,两台服务器A、B,一个应用a部署在服务器A上,另一个应用b部署在服务器B上,应用a想要调用服务器B上的应用b提供的方法,由于应用a和应用b不在一个内存空间,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是因为无法在一个进程内,甚至一台计算内通过本地调用的方式完成的需求,比如不同系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地函数一样去调远程函数。

推荐阅读文章:https://www.jianshu.com/p/2accc2840a1b

X.2.2 RPC基本原理

Todo:补图!需要花时间至少知道个大概流程。

RPC两大核心模块:通讯、序列化。

X.2.3 http和RPC的区别和联系

既然有了http,为什么还需要RPC实现服务间的通信呢?

分布式-springboot基础入门的更多相关文章

  1. springBoot 基础入门

    来处:是spring项目中的一个子项目 优点  (被称为搭建项目的脚手架)         减少一切xml配置,做到开箱即用,快速上手,专注于业务而非配置     从创建项目上: -- 快速创建独立运 ...

  2. SpringBoot基础入门

    1.SpringBoot核心相关内容 1.1入口类 SpringBoot通常有一个入口类*Application,内部有一个main方法,是启动SpringBoot的入口.使用@SpringBootA ...

  3. SpringBoot基础篇-SpringBoot快速入门

    SpringBoot基础 学习目标: 能够理解Spring的优缺点 能够理解SpringBoot的特点 能够理解SpringBoot的核心功能 能够搭建SpringBoot的环境 能够完成applic ...

  4. SpringBoot之基础入门-专题一

    SpringBoot之基础入门-专题一 一.Spring介绍 1.1.SpringBoot简介 在初次学习Spring整合各个第三方框架构建项目的时候,往往会有一大堆的XML文件的配置,众多的dtd或 ...

  5. Spring Boot 2.x零基础入门到高级实战教程

    一.零基础快速入门SpringBoot2.0 1.SpringBoot2.x课程全套介绍和高手系列知识点 简介:介绍SpringBoot2.x课程大纲章节 java基础,jdk环境,maven基础 2 ...

  6. [转]小D课堂 - 零基础入门SpringBoot2.X到实战_汇总

    原文地址:https://www.cnblogs.com/wangjunwei/p/11392825.html 第1节零基础快速入门SpringBoot2.0 小D课堂 - 零基础入门SpringBo ...

  7. 小D课堂 - 零基础入门SpringBoot2.X到实战_汇总

    第1节零基础快速入门SpringBoot2.0 小D课堂 - 零基础入门SpringBoot2.X到实战_第1节零基础快速入门SpringBoot2.0_1.SpringBoot2.x课程介绍和高手系 ...

  8. rabbitmq(一)-基础入门

    原文地址:https://www.jianshu.com/p/e186a7fce8cc 在学东西之前,我们先有一个方法论,知道如何学习.学习一个东西一般都遵循以下几个环节: xxx是什么,诞生的原因, ...

  9. 学习SpringBoot,整合全网各种优秀资源,SpringBoot基础,中间件,优质项目,博客资源等,仅供个人学习SpringBoot使用

    学习SpringBoot,整合全网各种优秀资源,SpringBoot基础,中间件,优质项目,博客资源等,仅供个人学习SpringBoot使用 一.SpringBoot系列教程 二.SpringBoot ...

随机推荐

  1. 翻译 | 30个 Python3 的最佳实践,技巧和窍门

    1.使用 Python3 如果你关注 Python 的话,应该会知道 Python 2 已经于今年(2020 年)1 月 1 日正式弃用了.这份教程的很多例子都是只支持 Python 3 的,如果你还 ...

  2. HarmonyOS(LiteOs_m) 官方例程移植到STM32初体验

    HarmonyOS(LiteOs_m) 官方例程移植到STM32初体验 硬件平台 基于正点原子战舰V3开发板 MCU:STM32F103ZET6 片上SRAM大小:64KBytes 片上FLASH大小 ...

  3. WEB安全讨论-表单登录是先验证验证码还是密码

    表单登录是先验证验证码还是密码? 肯定是验证码呀!!!这是毋庸置疑的.但是发现有人会验证密码,感觉先验证密码和先验证验证码是一个概念是一样的.但是其实是完全不一样的.下面我们来一起详细的剖析一下: 消 ...

  4. 项目API接口鉴权流程总结

    权益需求对接中,公司跟第三方公司合作,有时我们可能作为甲方,提供接口给对方,有时我们也作为乙方,调对方接口,这就需要API使用签名方法(Sign)对接口进行鉴权.每一次请求都需要在请求中包含签名信息, ...

  5. 敏捷史话(三):笃定前行的勇者——Ken Schwaber

    很多人之所以平凡,并不在于能力的缺失,而是因为缺乏迈出一步的勇气.只有少部分的人可以带着勇气和坚持,走向不凡.Ken Schwaber 就是这样的人,他带着他的勇气和坚持在敏捷的道路上不断前行,以实现 ...

  6. LeetCode703 流中第k大的元素

    前言: 我们已经介绍了二叉搜索树的相关特性,以及如何在二叉搜索树中实现一些基本操作,比如搜索.插入和删除.熟悉了这些基本概念之后,相信你已经能够成功运用它们来解决二叉搜索树问题. 二叉搜索树的有优点是 ...

  7. 【Shell】使用awk sed获取一行内容的两个值

    突然有需求需要一个脚本,同时获取到每一行数据的两个值,下面做了一个例子模板,仅供记录参考 cat test.txt  id=1,name=zclinux1 id=2,name=zclinux2 id= ...

  8. kubernets之secret资源

    一  对于一些保密度比较高的文件,k8s又是如何存储的呢? 针对那些保密度比较高的配置文件,例如证书以及一些认证配置不能直接存储在configmap中,而是需要存储在另外一种资源中,需要对存储在里面的 ...

  9. PAT练习num4-D进制的A+B

    输入两个非负 10 进制整数 A 和 B (≤),输出 A+B 的 D (1)进制数. 输入格式: 输入在一行中依次给出 3 个整数 A.B 和 D. 输出格式: 输出 A+B 的 D 进制数. 输入 ...

  10. CSS实现迷你键盘

    最近做了一个迷你键盘的dome,这里分享给大家 dome下载地址(点击下载) 代码如下: <!DOCTYPE html> <html lang="en" > ...