1、项目概述

1.1、概述

本项目是在前面章节的基础上,进行的升级改造。增加了注册中心、网关,更加贴近于实际需求。

在进行本节之前,请先搭建前面项目:

  • 【图文详解】搭建 Spring Authorization Server + Resource + Client 完整Demo

  • 【oauth2 客户端模式】Spring Authorization Server + Resource + Client 资源服务间的相互访问

1.2、整体架构图

业务流程解析

1.2.1、总体结构

总体上分为三个部分:

  • 前端和第三方授权中心
  • 网关:把前后两部分隔离开,所有的请求访问都通过网关转发
  • 后端:所有的资源服务(微服务)、注册中心、配置中心等

1.2.2、用户访问

1、用户 访问 客户端 ,准备请求资源

2、客户端 需要用户认证/授权,并请求 授权中心 发放令牌

3、客户端 拿到令牌后,通过 网关, 去 注册中心 查找 资源服务

4、注册中心 把资源服务告之 客户端客户端 携带令牌去访问 资源服务

5、资源服务 验证令牌后,把资源数据,通过 网关,返回 客户端,再呈现给 用户

12.3、资源服务间调用

资源服务之间的访问也需要经过网关(图中略),减少服务间的耦合

a、资源服务B 希望调用 资源服务A资源服务B 通过 网关,向 授权中心 申请令牌;授权中心 验证ID/密钥(客户端模式)后,通过 网关,直接发放令牌给 资源服务B

b、资源服务B 通过 网关,向 注册中心 查找 资源服务A

c、注册中心资源服务A,再通过 网关 ,告之 资源服务B资源服务B 携带令牌访问 资源服务A

d、资源服务A 验证令牌后,把资源数据,通过 网关 ,返给 资源服务B

1.2.4、额外说明

  • 所有资源服务 都需要注册到 注册中心,访问者只需要知道 网关地址服务名称,就可以访问;不用关心资源服务的具体部署位置,不管是单机部署还是集群部署,对访问者来说都是一样的。
  • 网关 是前后端访问的桥梁,网关也可以注册到注册中心;所有的访问都应该经过网关转发,减少服务间的耦合
  • 授权中心 作为第三方存在,不应该注册到注册中心
  • 客户端 作为工程的前端,也不用注册到注册中心

1.3、搭建环境

  • Spring Security 5.6.3 (Client/Resource)
  • Spring Authorization Server 0.2.3
  • Spring Boot 2.6.7(gateway、eureka…)
  • jdk 1.8
  • mysql 5.7
  • lombok、log4j、fastjson2 …

2、结构搭建

模块 端口 说明
oauth2-server-resource-client-gateway-eureka 父工程
oauth2-client-8000 8000 项目前端(oauth2客户端)
oauth2-server-9000 9000 认证授权中心(oauth2服务端)
oauth2-resource-a-8001 8001 微服务A(oauth2资源服务器),受保护对象
oauth2-resource-b-8002 8002 微服务B(oauth2资源服务器),受保护对象
eureka-7000 7000 注册中心
gateway-9999 9999 网关

2.1、父工程

创建普通meven工程 oauth2-server-resource-client-gateway-eureka;打包格式pom,删除 src

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.tuwer</groupId><artifactId>oauth2-server-resource-client-gateway-eureka</artifactId><version>1.0-SNAPSHOT</version><packaging>pom</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><mysql-connector-java.version>8.0.29</mysql-connector-java.version><lombok.version>1.18.22</lombok.version><log4j.version>1.2.17</log4j.version><fastjson2.version>2.0.3</fastjson2.version><commons-lang.version>2.6</commons-lang.version></properties><dependencyManagement><dependencies><!--spring-cloud-dependencies--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.0</version><type>pom</type><scope>import</scope></dependency><!-- spring-boot-dependencies--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.6.7</version><type>pom</type><!--<scope>provided</scope>--><scope>import</scope></dependency><!-- Spring Security OAuth2 依赖 --><!-- 授权服务器 Spring Authorization Server--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-authorization-server</artifactId><version>0.2.3</version></dependency><!-- mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql-connector-java.version}</version></dependency><!--fastjson--><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>${fastjson2.version}</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></dependency><!--日志--><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>${log4j.version}</version></dependency><!-- StringUtils --><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>${commons-lang.version}</version></dependency></dependencies></dependencyManagement></project>

2.2、子模块

3、注册中心

3.1、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>oauth2-server-resource-client-gateway-eureka</artifactId><groupId>com.tuwer</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>eureka-7000</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><!-- spring-cloud-starter-netflix-eureka-server --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><!-- 热部署 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>compile</scope></dependency></dependencies>
</project>

3.2、application.yml

server:port: 7000spring:application:# 注册中心name: eureka-server-7000# Eureka配置
eureka:instance:# Eureka服务端的实例名字hostname: localhostclient:# 表示是否向 Eureka 注册中心注册自己 (这个模块本身是服务器,所以不需要)register-with-eureka: false# fetch-registry 是否拉取其他的服务;如果为 false,则表示自己为注册中心或服务提供者;服务消费者的话为 truefetch-registry: false# Eureka监控页面~service-url:defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

3.3、启动类

package com.tuwer;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;/*** @author 土味儿* Date 2022/5/21* @version 1.0*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer_7000 {public static void main(String[] args) {SpringApplication.run(EurekaServer_7000.class, args);}
}

4、网关

4.1、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>oauth2-server-resource-client-gateway-eureka</artifactId><groupId>com.tuwer</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>gateway-9999</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!-- actuator完善监控信息 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!-- 热部署 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>compile</scope></dependency></dependencies>
</project>

4.2、application.yml

server:port: 9999spring:application:# 网关应用名称name: gatewey-9999# 配置 Spring Cloud 相关属性cloud:# 配置 Spring Cloud Gateway 相关属性gateway:# 配置网关发现机制discovery:# 配置处理机制locator:# ----------------# 只要请求地址符合规则:http://网关地址:端口/微服务名称/微服务请求地址,就自动映射# 把请求转发到:http://微服务提供者地址:端口/微服务名称/微服务请求地址# ----------------# 开启网关自动映射处理机制# 商业开发中,一般不设置为 true,使用默认值 false;避免不必要的自动转发规则enabled: false# 开启服务名称小写转换(默认为false)lower-case-service-id: true# 配置 Eureka
eureka:client:# 默认值:true 需要从注册中心拉取其他的服务#fetch-registry: trueservice-url:# 注册中心地址defaultZone: http://localhost:7000/eureka/instance:# 修改Eureka上的默认描述信息instance-id: gatewey-9999# 以IP地址注册到服务中心prefer-ip-address: true# 监控端口配置
management:endpoints:web:exposure:# 开启 info,health;新版本中只默认开启了 healthinclude: info,health

4.3、启动类

package com.tuwer;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;/*** @author 土味儿* Date 2022/5/21* @version 1.0*/
@SpringBootApplication
@EnableEurekaClient
public class Gateway_9999 {public static void main(String[] args) {SpringApplication.run(Gateway_9999.class, args);}
}

4.4、MyInfo.java

这一步可以省略;作用:给外部一个查看该模块基本信息的接口;

  • 需要导入 actuator 依赖
  • application.yml 中配置
  • 如果有security安全设置,需要放开端口 /actuator/info
@Component
public class MyInfo implements InfoContributor {@Overridepublic void contribute(Info.Builder builder) {HashMap<String, Object> map = new HashMap<>();// 可以从数据库获取信息map.put("ServiceName","路由网关");map.put("version","1.0-SNAPSHOT");map.put("author","tuwer");builder.withDetails(map);}
}
# 监控端口配置
management:endpoints:web:exposure:# 开启 info,health;新版本中只默认开启了 healthinclude: info,health

5、整合OAuth2三元素

5.1、复制旧项目中OAuth2三元素模块

5.2、配置资源服务到注册中心等

oauth2-resource-a-8001 为例,其它资源模块操作方法一样;

  • 授权中心不需要添加到注册中心,它作为第三方存在
  • 客户端作为工程前端,也不用添加到注册中心

5.2.1、添加依赖

<!-- 注册中心客户端 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

5.2.2、配置注册中心

# 配置 Eureka
eureka:client:# 默认值:true 需要从注册中心拉取其他的服务#fetch-registry: trueservice-url:# 注册中心地址defaultZone: http://localhost:7000/eureka/instance:# 修改Eureka上的默认描述信息instance-id: oauth2-resource-a-8001# 以IP地址注册到服务中心prefer-ip-address: true

5.2.3、启动类添加 @EnableEurekaClient

@SpringBootApplication
@EnableEurekaClient
public class Resource_a_8001 {public static void main(String[] args) {SpringApplication.run(Resource_a_8001.class, args);}
}

5.2.4、配置MyInfo.java

这一步可以省略;作用:给外部一个查看该模块基本信息的接口;

  • 导入依赖
<!-- actuator完善监控信息 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • MyInfo.java
@Component
public class MyInfo implements InfoContributor {@Overridepublic void contribute(Info.Builder builder) {HashMap<String, Object> map = new HashMap<>();// 可以从数据库获取信息map.put("ServiceName","资源服务器A");map.put("version","1.0-SNAPSHOT");map.put("author","tuwer");builder.withDetails(map);}
}
  • application.yml
# 监控端口配置
management:endpoints:web:exposure:# 开启 info,health;新版本中只默认开启了 healthinclude: info,health
  • 安全策略中放行端点
// 第一种方法
@Bean
SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests(requests ->// 任何请求都需要认证requests.antMatchers("/actuator/**").permitAll().anyRequest().authenticated())// ...return http.build();
}
// 第二种方法
@Bean
WebSecurityCustomizer webSecurityCustomizer() {return web -> web.ignoring().antMatchers("/actuator/health", "/actuator/info");
}

5.3、配置网关中路由规则

spring.cloud.gateway.routes 节点下配置

  • 资源服务A
        # 资源服务A# 把对 【网关/o2_resource_a/**】 的请求,转发到 【lb://oauth2-resource-a-8001】# 转发时去掉第1节 o2_resource_a# lb: 表示负载均衡 loadbalance# oauth2-resource-a-8001 是【资源服务A】在注册中心中的名称- id: oauth2-resource-a-8001uri: lb://oauth2-resource-a-8001predicates:- Path=/o2_resource_a/**- Method=GETfilters:- name: StripPrefixargs:# 过滤掉第1节parts: 1
  • 资源服务B
        # 资源服务B- id: oauth2-resource-b-8002uri: lb://oauth2-resource-b-8002predicates:- Path=/o2_resource_b/**- Method=GETfilters:- name: StripPrefixargs:# 过滤掉第1节parts: 1
  • 客户端
        # 客户端- id: oauth2-client-8000uri: http://127.0.0.1:8000predicates:- Path=/o2_client/**- Method=GETfilters:- name: StripPrefixargs:# 过滤掉第1节parts: 1
  • 认证中心
        # 认证中心- id: oauth2-server-9000uri: http://os.com:9000predicates:- Path=/o2_server/**- Method=GET,POSTfilters:- name: StripPrefixargs:# 过滤掉第1节parts: 1

5.4、旧项目部分内容修改

5.4.1、授权中心

  • 修改注册的两个客户端ID和名称;为了与旧项目区分。修改完之后,在客户端引用位置也要相应修改
//String clientId_1 = "my_client";
String clientId_1 = "my_client_2";//String clientId_2 = "micro_service";
String clientId_2 = "micro_service_2";// 客户端名称:可省略
//.clientName("my_client_name")
.clientName("my_client_name_2")    //.clientName("micro_service")
.clientName("micro_service_2")

5.4.2、客户端

  • application.yml
#client-id: my_client
client-id: my_client_2
  • ResourceController.java

    把对资源的直接调用,改为通过网关调用

// ...
// 网关地址
private String BASE_URL = "http://192.168.62.1:9999";@GetMapping("/server/a/res1")
public String getServerARes1(@RegisteredOAuth2AuthorizedClientOAuth2AuthorizedClient oAuth2AuthorizedClient) {// 网关地址/路由路径...return getServer(BASE_URL + "/o2_resource_a/res1", oAuth2AuthorizedClient);
}
// ...

  • 本地模拟客户
    /*** 虚拟一个本地用户** @return UserDetailsService*/@BeanUserDetailsService userDetailsService() {return username -> User.withUsername("local_admin").password("123456").roles("TEST","ABC")//.authorities("ROLE_ADMIN", "ROLE_USER").build();}
  • 本地权限提升
@Controller
public class IndexController {@AutowiredUserDetailsService userDetailsService;/*** 权限提升* 第三方用户进入本系统后,绑定本地用户,获取本地用户的角色和权限* @param model* @return*/@GetMapping("/")public String user(Model model) {// 从安全上下文中获取登录信息,返回给modelMap<String, Object> map = new HashMap<>(5);Authentication auth = SecurityContextHolder.getContext().getAuthentication();String username = auth.getName();map.put("当前用户", username);map.put("原来权限", auth.getAuthorities());//Set<GrantedAuthority> authorities = new ArraySet<>(auth.getAuthorities());Set<GrantedAuthority> authorities = new HashSet<>(auth.getAuthorities());// 根据三方用户查绑定的本地用户String localUser = getLocalUser(username);UserDetails userDetails = userDetailsService.loadUserByUsername(localUser);map.put("本地用户", localUser);// 本地用户权限//List<GrantedAuthority> authorities1 = new ArrayList<>(userDetails.getAuthorities());Set<GrantedAuthority> authorities1 = new HashSet<>(userDetails.getAuthorities());map.put("本地用户权限", authorities1);// 把本地用户权限加入原来权限集中authorities.addAll(authorities1);map.put("新的权限", authorities);// 生成新的认证信息Authentication newAuth = new OAuth2AuthenticationToken((OAuth2User) auth.getPrincipal(),authorities,"myClient");// 重置认证信息SecurityContextHolder.getContext().setAuthentication(newAuth);model.addAttribute("user", map);return "index";}/*** 模拟通过第三方用户,得到本地用户* @param remoteUsername* @return*/private String getLocalUser(String remoteUsername){String u = "";// 模拟通过三方用户查本地用户if(StringUtils.isNotEmpty(remoteUsername)){u = "local_admin";}return u;}@GetMapping("/out")public void logout(HttpServletRequest request,HttpServletResponse response) {// ...}
}

5.4.3、资源服务器

本项目中需要 资源服务B 调用 资源服务A;只需要修改 资源服务B

  • ResourceController.java

    申请令牌时,认证中心地址可以通过网关转发,地址为:BASE_URL + '/o2_server/...';客户端模式申请令牌时,不需要认证/授权/授权码等步骤,没有 redirect_url 转发路径

// ...
// 网关地址
private String BASE_URL = "http://192.168.62.1:9999";@GetMapping("/res1")
public String getRes1(HttpServletRequest request) {//return getServer("http://127.0.0.1:8001/res2", request);return getServer(BASE_URL + "/o2_resource_a/res2", request);//return JSON.toJSONString(new Result(200, "服务B -> 资源1"));
}private String getServer(String url,HttpServletRequest request) {// ...// 对id及密钥加密//byte[] userpass = Base64.encodeBase64(("micro_service:123456").getBytes());byte[] userpass = Base64.encodeBase64(("micro_service_2:123456").getBytes());// ...try {// 发起申请令牌请求responseEntity1 = restTemplate.exchange(BASE_URL + "/o2_server/oauth2/token?grant_type=client_credentials", HttpMethod.POST, httpEntity1, String.class);} catch (RestClientException e) {//System.out.println("令牌申请失败");}// ...
}

6、测式

权限测试

客户端中需要加入本地用户,第三方用户绑定本地用户,再把本地用户角色/权限赋于给第三方用户,实现客户端的角色管理。

详细设计及说明见:OAuth2在分布式微服务架构下基于角色的权限设计(RBAC)

  • 资源服务器放开客户端的访问权限,只要是合法的客户端请求都通过

  • 客户端根据角色/权限控制访问;修改客户端中安全策略;重启客户端测试

  • 添加403页面

Git仓库:https://gitee.com/tuwer/oauth2

【图文详解】搭建 Spring Authorization oauth2-server-resource-client-gateway-eureka 完整Demo相关推荐

  1. php强类型 vscode,VSCode + WSL 2 + Ruby环境搭建图文详解

    vscode配置ruby开发环境 vscode近年来发展迅速,几乎在3年之间就抢占了原来vim.sublime text的很多份额,犹记得在2015-2016年的时候,ruby推荐的开发环境基本上都是 ...

  2. php mac 开发环境搭建_Mac搭建php的开发环境(图文详解)

    搭建php的开发环境(图文详解) 这篇文章主要介绍了Mac下搭建php开发环境教程,Mac OS X内置了Apache 和 PHP,这样使用起来非常方便.本文以Mac OS X 10.6.3为例,需要 ...

  3. 大数据学习系列之七 ----- Hadoop+Spark+Zookeeper+HBase+Hive集群搭建 图文详解

    引言 在之前的大数据学习系列中,搭建了Hadoop+Spark+HBase+Hive 环境以及一些测试.其实要说的话,我开始学习大数据的时候,搭建的就是集群,并不是单机模式和伪分布式.至于为什么先写单 ...

  4. 基于CentOS6.5下snort+barnyard2+base的入侵检测系统的搭建(图文详解)(博主推荐)...

    为什么,要写这篇论文? 是因为,目前科研的我,正值研三,致力于网络安全.大数据.机器学习研究领域! 论文方向的需要,同时不局限于真实物理环境机器实验室的攻防环境.也不局限于真实物理机器环境实验室的大数 ...

  5. x264代码剖析(一):图文详解x264在Windows平台上的搭建

    x264代码剖析(一):图文详解x264在Windows平台上的搭建 X264源码下载地址:http://ftp.videolan.org/pub/videolan/x264/ 平台:win7 PC. ...

  6. 图文详解如何搭建Windows的Android C++开发环境

    原地址:http://www.apkbus.com/android-18595-1-1.html //================================================= ...

  7. ftp linux包,图文详解Ubuntu搭建Ftp服务器的方法(包成功)

    一.今天下午由于课程的要求不得已做了Ubuntu搭建Ftp服务器的实验,但是实验指导书还是N年前的技术,网上搜了一大把,都是模模糊糊的! 在百般困难中终于试验成功,特把经验分给大家 希望大家少走弯路! ...

  8. pycharm+python3.7+pyqt配置_Python3+Pycharm+PyQt5环境搭建步骤图文详解

    搭建环境: 操作系统:Win10 64bit Python版本:3.7 Pycharm:社区免费版 一.Python3.7安装 下载到安装包后打开,如果想安装到默认路径(C盘)的话一直点下一步就可以了 ...

  9. linux rpm安装zabbix,CentOS 7上安装Zabbix Server 3.0 图文详解

    CentOS 7上安装Zabbix Server 3.0 图文详解 1.查看系统信息. cat /etc/RedHat-release CentOS Linux release 7.0.1406 (C ...

最新文章

  1. Java解决递归栈溢出_方法递归调用中java栈溢出的问题 及 解答 | 学步园
  2. SQL SERVER 判断是否存在并删除某个数据库、表、视图、触发器、储存过程、函数
  3. 通知:前blog文章全丢了..
  4. 两条曲线所围成的面积_三个视频搞定:求曲边梯形面积的思想、微积分基本定理及其几何意义、微积分理论的可视化解读、...
  5. 【转】Web实现音频、视频通信
  6. mysql文件_mysql 的各种文件详细说明
  7. 关于面向对象和面向过程本质的区别(个人感悟)
  8. Leetcode46. Permutations全排列
  9. 导入jasperreports出现Cannot resolve com.lowagie:itext:2.1.7.js6异常、生成PDF中文不显示中文解决方法、使用命令安装jar包
  10. Windows批处理添加注释
  11. .NET6东西--可写的JSON DOM API
  12. chm文件打开出现已取消该网页的导航
  13. abaqus中六面体单元对比四面体
  14. 没有网服务器怎么打开网页,苹果手机没有浏览器怎么打开网页
  15. 软件:股票小助手/盯盘小助手!
  16. java 免登录_最简单、稳定的免密登录
  17. CTU 2017 B - Pond Cascade 二分
  18. java 接口,接口的特性,接口实现多态,面向接口编程
  19. 实验 详解K8S多节点部署群集
  20. Prometheus Operator 部署

热门文章

  1. AQR:趋势跟踪——有效的风险对冲策略
  2. 年终盘点丨2017人工智能十大关键词,收购、政策、场景创新上榜
  3. 广东省高考数学能用计算机吗,广东高考志愿填报:计算机类专业注意事项
  4. 毕业答辩的流程是什么
  5. mysql表白代码_GitHub - Rianley/wechatAlliance: 微信小程序--校园小情书后台源码,好玩的表白墙,告白墙。...
  6. Spring属性注入DI
  7. 火车售票系统(基于UML的软件分析与设计模型建模实验)
  8. 如何查看mysql的版本情况?
  9. 计算机专业常用英语单词
  10. Win日志批量清理器