构建Spring Boot RESTful服务+ Spring Boot执行器

总览

什么是REST?

REST(代表状态转移)是Web构建的体系结构样式,已成为用于Web应用程序的标准软件设计模式 。 代表性国家转移一词最早由REST的发起人,HTTP规范的主要作者之一Roy Fielding在其博士论文中使用 。

REST上有很多很好的参考,包括:

  • 维基百科
  • 理查森成熟度模型
  • Ryan Tomayko如何向他的妻子解释REST
  • Roy Fielding的REST API必须由超文本驱动
  • Stackoverflow SOAP与REST

本教程基于使用Spring构建Rest Services,并且本教程的开头也对REST进行了很好的概述。

什么是弹簧启动执行器?

Spring Boot Actuator是Spring Boot的子项目。 它为您的应用程序添加了几项生产级服务,而您只需花费很少的精力。

执行器的定义

致动器是负责移动或控制系统的组件。
执行器一词不限于Spring Boot; 但是,这是我们在这里的重点。

在Spring Boot应用程序中配置Actuator之后,它允许您通过调用Spring Boot Actuator公开的不同技术不可知端点(例如应用程序运行状况,Bean,记录器,映射和跟踪)来交互和监视应用程序。 在此Spring文档中列出了更多信息。

0 –带启动器的Spring Boot RESTful Web服务示例应用程序

我们将使用Spring Boot和Actuator构建一个示例RESTful Web应用程序。

该应用程序将是“用户名跟踪器”。 在此应用程序中,一个人拥有一个帐户,他们的帐户可能具有许多用户名。

查看并从 Github 下载代码

1 –项目结构

和往常一样,我们有一个正常的Maven项目结构。

2 –项目依赖性

除了典型的Spring Boot依赖关系之外,我们还为嵌入式数据库提供了HSQLDB,并为所有Actuator依赖关系提供了spring-boot-starter-actuator。

<?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.michaelcgood</groupId><artifactId>michaelcgood-springbootactuator</artifactId><version>0.0.1</version><packaging>jar</packaging><name>Spring-Boot-Actuator-Example</name><description>Michael C  Good - Spring Boot Actuator Example</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.hsqldb</groupId><artifactId>hsqldb</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

3 –运行空应用程序

尽管我们没有编写任何代码,但是我们将运行Spring Boot应用程序。

转到您的终端并按照命令进行操作。

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080
{"timestamp":1505235455245,"status":404,"error":"Not Found","message":"No message available","path":"/"}

我们尚未编写任何代码,除了默认的容器生成HTML错误响应外,Actuator还从/ error端点生成JSON响应。

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/health
{"status":"UP"}

执行器/运行状况端点将让您知道您的应用程序是否启动。

4 –模型

现在,为用户名跟踪器应用程序定义模型的字段。

  • 如前所述,一个人有一个帐户,可能有许多用户名。 所以我们用@OneToMany注释映射Set
  • 用户名模型将具有密码和用户名
  • 我们的模型将需要一个ID,并使其自动生成
  • 我们进行类构造以定义可以使用用户名和密码创建的帐户。 由于使用了这种自定义构造函数,因此我们还需要设置一个没有参数的默认构造函数。

Account.java

package com.michaelcgood.model;import java.util.HashSet;
import java.util.Set;import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;import com.fasterxml.jackson.annotation.JsonIgnore;@Entity
public class Account {public Set<Usernames> getUsernames() {return usernames;}public void setUsernames(Set<Usernames> usernames) {this.usernames = usernames;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@OneToMany(mappedBy= "account")private Set<Usernames> usernames = new HashSet<>();@Id@GeneratedValueprivate Long id;@JsonIgnorepublic String password;public String username;public Account(String name, String password) {this.username = name;this.password = password;}Account(){}}

用户名.java

  • 由于一个帐户可以使用多个用户名,因此情况也相反:一个帐户可以使用多个用户名。 因此,我们使用@ManyToOne注释映射Account
  • 要跟踪用户名,我们需要:URL和用户名
  • 我们再次定义一个自动生成的ID
  • 我们定义了一个自定义类构造函数,该构造函数需要account,url和username参数。 再一次,我们需要定义一个默认的构造函数方法,以避免引发错误。
package com.michaelcgood.model;import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;import com.fasterxml.jackson.annotation.JsonIgnore;@Entity
public class Usernames {@JsonIgnore@ManyToOneprivate Account account;public Account getAccount() {return account;}public void setAccount(Account account) {this.account = account;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@Id@GeneratedValueprivate Long id;public String url;public String username;Usernames(){}public Usernames(Account account, String url, String username){this.url=url;this.account=account;this.username=username;}}

5 –储存库

我们为两个模型创建一个存储库,并使用派生查询创建搜索功能。

AccountRepository.java

package com.michaelcgood.dao;import java.util.Optional;import org.springframework.data.jpa.repository.JpaRepository;import com.michaelcgood.model.Account;public interface AccountRepository extends JpaRepository<Account,Long> {Optional<Account> findByUsername(String username);
}

用户名Repository.java

package com.michaelcgood.dao;import java.util.Collection;import org.springframework.data.jpa.repository.JpaRepository;import com.michaelcgood.model.Usernames;public interface UsernamesRepository extends JpaRepository<Usernames,Long> {Collection<Usernames> findByAccountUsername(String username);}

6 –控制器

在控制器中,我们定义将用于RESTful Web服务的所有映射。

  • 我们用@RestController而不是@Controller注释控制器。 如javadoc中所述,它是“一种方便注释,其本身通过@Controller和@ResponseBody进行了注释。”
  • 我们声明UsernamesRepository和AccountRepository的变量,并使其成为最终变量,因为我们只希望将值分配一次。 我们通过UsernamesRestController类构造函数将它们注释为@Autowired。
  • {userId}和{usernamesId}是路径变量 。 这意味着这些值在URL中提供。 这将在我们的演示中显示。
  • Controller方法返回POJO(普通的旧Java对象) 。 Spring Boot自动连接HttpMessageConverter以将这些通用对象转换为JSON。

用户名RestController.java

package com.michaelcgood.controller;import java.net.URI;
import java.util.Collection;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;import com.michaelcgood.dao.AccountRepository;
import com.michaelcgood.dao.UsernamesRepository;
import com.michaelcgood.model.Usernames;@RestController
@RequestMapping("/{userId}/usernames")
public class UsernamesRestController {private final UsernamesRepository usernamesRepository;private final AccountRepository accountRepository;@AutowiredUsernamesRestController(UsernamesRepository usernamesRepository, AccountRepository accountRepository){this.usernamesRepository = usernamesRepository;this.accountRepository = accountRepository;}@GetMappingCollection<Usernames> readUsernames (@PathVariable String userId){this.validateUser(userId);return this.usernamesRepository.findByAccountUsername(userId);}@PostMappingResponseEntity<?> add(@PathVariable String userId,@RequestBody Usernames input){this.validateUser(userId);return this.accountRepository.findByUsername(userId).map(account -> {Usernames result = usernamesRepository.save(new Usernames(account,input.url,input.username));URI url = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(result.getId()).toUri();return ResponseEntity.created(url).build();	}).orElse(ResponseEntity.noContent().build());}@GetMapping(value="{usernamesId}")Usernames readUsername(@PathVariable String userId, @PathVariable Long usernameId){this.validateUser(userId);return this.usernamesRepository.findOne(usernameId);}private void validateUser(String userId){this.accountRepository.findByUsername(userId).orElseThrow(() -> new UserNotFoundException(userId));}}

UserNotFoundException.java

在这里,我们定义了在Controller类中用来解释找不到用户的自定义异常。

package com.michaelcgood.controller;import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {/*** */private static final long serialVersionUID = 7537022054146700535L;public UserNotFoundException(String userId){super("Sorry, we could not find user '" + userId +"'.");
}}

7 – @SpringBootApplication

我们使用CommandLineRunner创建帐户并插入用户名。 每个帐户都有两个用户名。

package com.michaelcgood;import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;import com.michaelcgood.dao.AccountRepository;
import com.michaelcgood.dao.UsernamesRepository;
import com.michaelcgood.model.Account;
import com.michaelcgood.model.Usernames;import java.util.Arrays;@SpringBootApplication
public class SpringBootActuatorExampleApplication {public static void main(String[] args) {SpringApplication.run(SpringBootActuatorExampleApplication.class, args);}@BeanCommandLineRunner init(AccountRepository accountRepository,UsernamesRepository usernamesRepository) {return (evt) -> Arrays.asList("ricksanchez,mortysmith,bethsmith,jerrysmith,summersmith,birdperson,squanchy,picklerick".split(",")).forEach(a -> {Account account = accountRepository.save(new Account(a,"password"));usernamesRepository.save(new Usernames(account,"http://example.com/login", a +"1"));usernamesRepository.save(new Usernames(account,"http://example2.com/login", "the_"+a));});}
}

8 –配置

在Spring文档中有说明 :

默认情况下,所有敏感的HTTP端点都是安全的,因此只有具有ACTUATOR角色的用户才能访问它们。 使用标准的HttpServletRequest.isUserInRole方法可以增强安全性。

我们尚未设置任何安全性和用户角色,因为这只是一个示例。 因此,为了便于演示,我将禁用安全性要求。 否则,我们将立即收到一个“未经授权”的错误,如下图所示。

{"timestamp":1505321635068,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource.","path":"/beans"}

application.properties

将此添加到application.properties以禁用身份验证。

management.security.enabled=false

9 –演示

要从服务器检索响应,您可以在浏览器中访问URL或使用curl。 对于我的演示,我正在使用curl。

REST查询存储库中的数据

查询属于帐户jerrysmith的用户名。

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/jerrysmith/usernames
[{"id":7,"url":"http://example.com/login","username":"jerrysmith1"},{"id":8,"url":"http://example2.com/login","username":"the_jerrysmith"}]

查询属于帐户picklerick的用户名

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/picklerick/usernames
[{"id":15,"url":"http://example.com/login","username":"picklerick1"},{"id":16,"url":"http://example2.com/login","username":"the_picklerick"}]

执行器查询

该查询的响应非常长,因此被截断了。

豆子

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/beans
[{"context":"application","parent":null,"beans":[{"bean":"springBootActuatorExampleApplication","aliases":[],"scope":"singleton","type":"com.michaelcgood.SpringBootActuatorExampleApplication$$EnhancerBySpringCGLIB$$509f4984","resource":"null","dependencies":[]},{"bean":"org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory","aliases":[],"scope":"singleton","type":"org.springframework.core.type.classreading.CachingMetadataReaderFactory","resource":"null","dependencies":[]},{"bean":"usernamesRestController","aliases":[],"scope":"singleton","type":"com.michaelcgood.controller.UsernamesRestController","resource":"file [/Users/mike/javaSTS/Spring-Boot-Actuator-Example/target/classes/com/michaelcgood/controller/UsernamesRestController.class]","dependencies":["usernamesRepository","accountRepository"]},{"bean":"init","aliases":[],"scope":"singleton","type":"com.michaelcgood.SpringBootActuatorExampleApplication$$Lambda$11/889398176","resource":"com.michaelcgood.SpringBootActuatorExampleApplication",
[...]

指标

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/metrics
{"mem":350557,"mem.free":208275,"processors":4,"instance.uptime":213550,"uptime":240641,"systemload.average":1.6552734375,"heap.committed":277504,"heap.init":131072,"heap.used":69228,"heap":1864192,"nonheap.committed":74624,"nonheap.init":2496,"nonheap.used":73062,"nonheap":0,"threads.peak":27,"threads.daemon":23,"threads.totalStarted":30,"threads":25,"classes":9791,"classes.loaded":9791,"classes.unloaded":0,"gc.ps_scavenge.count":11,"gc.ps_scavenge.time":139,"gc.ps_marksweep.count":2,"gc.ps_marksweep.time":148,"httpsessions.max":-1,"httpsessions.active":0,"datasource.primary.active":0,"datasource.primary.usage":0.0,"gauge.response.beans":14.0,"gauge.response.info":13.0,"counter.status.200.beans":2,"counter.status.200.info":1}

9 –结论

恭喜,您已经创建了一个可以用Actuator监视的RESTful Web服务。 REST实际上是不同客户端进行通信的最有机的方式,因为它由于HTTP而起作用。

源代码在 Github上

翻译自: https://www.javacodegeeks.com/2017/10/building-spring-boot-restful-service-spring-boot-actuator.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/349256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

tf调不到keras怎么 回事_格力变频空调快速维修方法及技巧 空调压缩机不到一分钟就停,怎么回事?...

格力变频空调快速维修方法及技巧一、 室内部分&#xff1a; 1、F1开路&#xff1a;制冷时不启动或启动一下就停机&#xff1b;制热正常&#xff0c;且一直高频运转。 2、F2开路&#xff1a;工作6—10分钟就停机&#xff0c;显示E2停止外机。 3、F2短路&#xff1…

php自动释放mysql连接,php怎么关闭mysql连接

php怎么关闭mysql连接2021-03-17 07:45:43php中可使用mysqli_close()函数来关闭mysql连接&#xff0c;语法格式“mysqli_close(connection);”。mysqli_close()函数可关闭先前打开的数据库连接&#xff0c;如果成功返回TRUE&#xff0c;反之则返回FALSE。本教程操作环境&#x…

Java 8 –按值对HashMap进行升序和降序排序

在上一篇文章中&#xff0c;我向您展示了如何通过键对Java 8中的Map进行排序 &#xff0c;今天&#xff0c;我将教您如何使用Java 8功能&#xff08;例如&#xff0c;lambda表达式&#xff0c;方法引用&#xff0c;流和新方法&#xff09; 按值对Map进行排序。添加到java.util.…

scrcpy投屏_scrcpy 使用教程:将安卓设备投屏到 PC 端

阿拉平平读完需要6分钟速读仅需 2 分钟scrcpy 是一款开源的安卓设备投屏工具&#xff0c;通过 USB 或 Wi-Fi 与设备连接后就可以在 PC 端操作安卓设备&#xff0c;无需 root 权限且支持多平台运行。本文将演示如何使用 scrcpy 进行投屏操作。1. 下载安装到 Releases 下载最新的…

打砖块小游戏php程序,利用原生js实现html5打砖块小游戏(代码示例)

本篇文章给大家通过代码示例介绍一下利用原生js实现html5打砖块小游戏的方法。有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对大家有所帮助。前言PS&#xff1a;本次项目中使用了大量 es6 语法&#xff0c;故对于 es6 语法不太熟悉的小伙伴最好能先了…

si9000阻抗匹配计算_如何在设计之初计算出两层PCB板差分线的阻抗,线宽,间距...

最近在设计一款两层板PCB。板上一些高速信号线&#xff0c;分别是MIMP接口的差分线和USB2.0的差分线。既然是高速线&#xff0c;那么就需要设计成阻抗匹配走线。MIMP差分线需要做100ohm匹配&#xff0c;USB线需要做90ohm匹配。差分线阻抗的计算主要跟线宽&#xff0c;间距&…

jax-ws cxf_Apache CXF – JAX-WS –简单教程

jax-ws cxf许多Java开发人员都认为Web Service实现的任务艰巨-好吧&#xff0c;没有人能真正责怪他们&#xff0c;尤其是在企业应用程序开发的多年中&#xff0c;这给开发和设计带来了很多复杂性。 对于某些人来说&#xff0c;了解它是构建完整的企业应用程序的下一步-Web服务-…

写屏障是什么_面试官为什么问内存模型总离不开final关键字,该如何应对?

Java 语言的每个关键字都设计的很巧妙&#xff0c;金雕玉琢&#xff0c;只有深度钻研其中&#xff0c;才知其中懊悔&#xff0c;本文带领大家一起深入理解 Java 内存模型之 final。加我微信好友的不要着急&#xff0c;手机没电了&#xff0c;等我借个充电器之后&#xff0c;再一…

非静态方法可以访问Java中的静态变量/方法吗?

“非静态方法可以访问静态变量或调用静态方法”是Java中有关静态修饰符的常见问题之一&#xff0c;答案是&#xff0c; 是的 &#xff0c;非静态方法可以访问静态变量或调用静态方法。 Java中的方法。 这没有问题&#xff0c;因为有静态成员&#xff0c;即静态变量和静态方法都…

which oracle linux,(总结)Linux下Oracle11gR2的ORA-00845错误解决方法

PS&#xff1a;前些时间一台演示环境的Oracle 11g for Linux不知什么原因&#xff0c;启动不起来&#xff0c;报错ORA-00845。搜索了下&#xff0c;这个问题是由于设置SGA的大小超过了操作系统/dev/shm的大小。当时解决了没空写总结&#xff0c;今天有点空&#xff0c;总结分享…

oracle存储过程深入,深入了解oracle存储过程的优缺点

定义&#xff1a;存储过程(Stored Procedure )是一组为了完成特定功能的SQL 语句集&#xff0c;经编译后存储在数据库中。用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象&#xff0c;任何一个设计良好的数据库应用程…

如何在Java 8中使用LocalDateTime格式化/解析日期-示例教程

Java项目中的常见任务之一是将日期格式化或解析为String&#xff0c;反之亦然。 解析日期表示您有一个表示日期的字符串&#xff0c;例如“ 2017-08-3”&#xff0c;并且要将其转换为表示Java中日期的对象&#xff0c;例如Java 8之前版本中的java.util.Date以及LocalDate或Loca…

如何获取当前刀具号_数控刀具的选用原则,如何使用数控刀具?一文全面介绍数控刀具...

数控刀具选用概述学习数控相关知识&#xff0c;最基础的是认识和了解刀具的材料以及选用原则&#xff0c;我们应当了解数控刀具的种类及特点、如何正确选择和使用数控加工刀具&#xff1b;学会根据被加工材料来合理选择数控刀具的材料和切削参数。选用原则&#xff1a;数控车床…

Java命令行界面(第27部分):cli-parser

CLI Parser最初托管在Google Code上&#xff0c;现在已存档在Google Code上 &#xff0c;现在可以在GitHub上使用 。 归档的Google Code项目页面将CLI解析器描述为“使用非常简单&#xff0c;非常小的依赖项”&#xff0c;它使用注释“使非常简洁的主要方法不需要知道如何解析带…

使用2.26内核的linux,介绍linux 2.6.9-42内核升级到linux 2.6.26-42的方法

介绍linux 2.6.9-42内核升级到linux 2.6.26-42的方法来源&#xff1a;互联网作者&#xff1a;佚名时间&#xff1a;2013-04-10 13:32这篇升级Linux内容的文章&#xff0c;是基于Red Hat的Linux版本&#xff0c;从linux 2.6.9-42内核升级到linux 2.6.26-42的方法&#xff0c;对于…

Java命令行界面(第1部分):Apache Commons CLI

尽管我通常使用Groovy编写要从命令行运行的JVM托管脚本&#xff0c;但是有时候我需要解析Java应用程序中的命令行参数&#xff0c;并且有很多库可供Java开发人员用来解析命令行参数。 在本文中&#xff0c;我将介绍这些Java命令行解析库中最著名的一种&#xff1a; Apache Comm…

丙烯怎么做成流体丙烯_韧性好强度高的聚丙烯复合材料怎么做?让人工智能来帮忙...

01背景介绍聚丙烯(PP)是一种应用广泛的通用塑料&#xff0c;价格便宜、力学性能好、热稳定性高&#xff0c;在机械、汽车、电子电器、建筑、纺织、包装和食品工业等领域应用广泛。聚丙烯韧性和冲击强度不高&#xff0c;限制了它的应用。加入热塑性弹性体(TPE)&#xff0c;如苯乙…

vivado安装_Vivado下载与安装指南

Vivado下载与安装指南目前&#xff0c;vivado已推出2019.1版本&#xff0c;实验室所安装的为2018.3版本&#xff0c;由于软件向下兼容的特性&#xff0c;建议安装2018版本&#xff0c;若安装2019版本&#xff0c;请自带笔记本&#xff0c;安装过程与之前没有差别&#xff0c;这…

嵌入式基于linux电机控制器,基于嵌入式Linux的移动机器人控制系统

使用select机制监控是否语音识别结果&#xff0c;在超出等待时间后&#xff0c;会退出等待并重新初始化语音模块LD3320&#xff0c;释放公共资源&#xff0c;这样也使得系统能够及时响应LD3320的MP3播放功能&#xff0c;避免了在长时间没有语音识别结果时&#xff0c;系统进入卡…

windows server 驱动精灵_还在用Windows文件共享?我来教你一键摆脱Windows海量小文件使用和备份的噩梦...

每当我问到客户&#xff0c;“你用什么存储产品作为文件共享&#xff1f;”经常听到的一个答案(自豪滴)是&#xff0c;“文件共享需要存储么&#xff1f;我们用Windows就可以做到。”Windows就是个百宝箱&#xff0c;什么都能往里装&#xff0c;就像你家冰箱一样。众所周知&…