使用Spring Boot和Vue进行有益的开发
“我喜歡編寫身份驗證和授權代碼。” ?從來沒有Java開發人員。 厭倦了一次又一次地建立相同的登錄屏幕? 嘗試使用Okta API進行托管身份驗證,授權和多因素身份驗證。
Vue是一個Web框架,由于它的精簡和刻薄,最近引起了很多關注。 它的基準框架成本約為4萬,被稱為簡約Web框架。 隨著最近對Web性能的關注以及移動優先,移動快速的關注,Vue變得越來越流行也就不足為奇了。 如果您花時間學習AngularJS,很可能會在Vue.js中找到老朋友。
Spring Boot是Java生態系統中我最喜歡的框架之一。 是的,我有偏見。 自2004年以來,我就一直是Spring Framework的愛好者。能夠使用Spring MVC編寫Java Webapp真是太好了,但是大多數人都使用XML進行配置。 盡管Spring支持JavaConfig,但直到Spring Boot(在2014年)才真正起步。 如今,您再也看不到Spring教程,該教程向您展示了如何使用XML進行配置。 做得好,Spring Boot團隊!
我之所以寫本教程,是因為我是Vue的忠實擁護者。 如果您了解我,就會知道我是一個Web框架愛好者。 也就是說,我是Web框架的忠實擁護者。 就像NBA球迷有一些喜歡的球員一樣,我也有一些喜歡的框架。 Vue最近成為其中之一,我想向您展示原因。
在本文中,我將向您展示如何使用Spring Data JPA和Hibernate構建Spring Boot API。 然后,我將向您展示如何創建Vue PWA并對其進行自定義以顯示API中的數據。 然后,您將添加一些動畫gif,一些認證,并祝您玩得開心!
使用Spring Boot構建REST API
要開始使用Spring Boot,請導航至start.spring.io并選擇版本2.1.1+。 在“搜索依賴項”字段中,選擇以下內容:
- H2 :內存數據庫
- Lombok(Lombok) :因為沒有人喜歡生成(甚至更糟糕的是編寫!)getter和setter。
- JPA :Java的標準ORM
- 其余存儲庫 :允許您將JPA存儲庫公開為REST端點
- Web :具有Jackson(用于JSON),Hibernate Validator和嵌入式Tomcat的Spring MVC
如果您更喜歡命令行,請安裝HTTPie并運行以下命令以下載demo.zip 。
http https://start.spring.io/starter.zip dependencies==h2,lombok,data-jpa,data-rest,web \packageName==com.okta.developer.demo -d創建一個名為spring-boot-vue-example 。 將demo.zip的內容demo.zip到其server目錄中。
mkdir spring-boot-vue-example unzip demo.zip -d spring-boot-vue-example/server在您喜歡的IDE中打開“服務器”項目,然后運行DemoApplication或使用./mvnw spring-boot:run從命令行啟動它。
創建一個com.okta.developer.demo.beer程序包和其中的Beer.java文件。 此類將是保存您的數據的實體。
package com.okta.developer.demo.beer;import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull;import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id;@Data @NoArgsConstructor @Entity class Beer {public Beer(String name) {this.name = name;}@Id@GeneratedValueprivate Long id;@NonNullprivate String name; }添加一個利用Spring Data在此實體上執行CRUD的BeerRepository類。
package com.okta.developer.demo.beer;import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.rest.core.annotation.RepositoryRestResource;@RepositoryRestResource interface BeerRepository extends JpaRepository<Beer, Long> { }添加@RepositoryRestResource注釋BeerRepository暴露了其所有的CRUD操作的REST端點。
添加使用此存儲庫的BeerCommandLineRunner并創建一組默認數據。
package com.okta.developer.demo.beer;import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component;import java.util.stream.Stream;@Component public class BeerCommandLineRunner implements CommandLineRunner {private final BeerRepository repository;public BeerCommandLineRunner(BeerRepository repository) {this.repository = repository;}@Overridepublic void run(String... strings) throws Exception {// Top beers from https://www.beeradvocate.com/lists/us, November 2018Stream.of("Kentucky Brunch Brand Stout", "Marshmallow Handjee", "Barrel-Aged Abraxas","Hunahpu's Imperial Stout", "King Julius", "Heady Topper","Budweiser", "Coors Light", "PBR").forEach(name ->repository.save(new Beer(name)));repository.findAll().forEach(System.out::println);} }重新啟動您的應用程序,您應該會在終端上看到印刷的啤酒列表。
添加一個BeerController類來創建一個端點,該端點過濾出的啤酒數量少于大啤酒。
package com.okta.developer.demo.beer;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;import java.util.Collection; import java.util.stream.Collectors;@RestController public class BeerController {private BeerRepository repository;public BeerController(BeerRepository repository) {this.repository = repository;}@GetMapping("/good-beers")public CollectiongoodBeers() {return repository.findAll().stream().filter(this::isGreat).collect(Collectors.toList());}private boolean isGreat(Beer beer) {return !beer.getName().equals("Budweiser") &&!beer.getName().equals("Coors Light") &&!beer.getName().equals("PBR");} }重新構建您的應用程序并導航到http://localhost:8080/good-beers 。 您應該在瀏覽器中看到優質啤酒的列表。
使用HTTPie時,您也應該在終端窗口中看到相同的結果。
http :8080/good-beers使用Vue CLI創建項目
這些天來,創建API似乎很容易,這在很大程度上要歸功于Spring Boot。 在本節中,我希望向您展示使用Vue創建UI也非常簡單。 我還將向您展示如何使用TypeScript開發Vue應用。 如果您按照以下步驟操作,則將創建一個新的Vue應用,從API獲取啤酒名稱和圖像,并創建組件以顯示其數據。
要創建Vue項目,請確保已安裝Node.js和Vue CLI 3 。 創建本教程時,我使用了Node 11.3.0。
npm install -g @vue/cli@3.2.1在終端窗口中,使用cd進入spring-boot-vue-example目錄的根目錄并運行以下命令。 該命令將創建一個新的Vue應用程序并提示您選擇。
vue create client當提示您選擇禮物時,選擇手動選擇功能 。
檢查TypeScript , PWA和Router功能。 選擇其余問題的默認值(按Enter )。
在終端窗口中,cd進入client目錄,然后在您喜歡的編輯器中打開package.json 。 添加與serve腳本相同的start腳本。
"scripts": {"start": "vue-cli-service serve","serve": "vue-cli-service serve","build": "vue-cli-service build","lint": "vue-cli-service lint" },現在,您可以使用npm start Vue應用npm start 。 您的Spring Boot應用程序仍應在端口8080上運行,這將導致您的Vue應用程序使用端口8081。我希望您在本教程中始終在8081上運行您的Vue應用程序。 為確保它始終在此端口上運行,請創建一個client/vue.config.js文件,并向其中添加以下JavaScript。
module.exports = {devServer: {port: 8081} };在瀏覽器中打開http://localhost:8081 ,您應該會看到類似下面的頁面。
在Vue中創建良好的Beers UI
到目前為止,您已經創建了一個好的啤酒API和一個Vue客戶端,但是尚未創建UI來顯示API中的啤酒列表。 為此,請打開client/src/views/Home.vue并添加一個created()方法。
import axios from 'axios'; ...private async created() {const response = await axios.get('/good-beers');this.beers = await response.data; }Vue的組件生命周期將調用created()方法。
John Papa的帶有TypeScript的Vue.js對弄清楚如何將TypeScript與Vue一起使用提供了很大的幫助。 Vue的TypeScript文檔也很有幫助。
您需要安裝axios才能編譯此代碼。
npm i axios您會看到這會將響應數據放入本地beers變量中。 要正確定義此變量,請創建一個Beer接口并將Home類的beers變量初始化為一個空數組。
export interface Beer {id: number;name: string;giphyUrl: string; }@Component({components: {HelloWorld,}, }) export default class Home extends Vue {public beers: Beer[] = [];private async created() {const response = await axios.get('/good-beers');this.beers = await response.data;} }敏銳的眼睛會注意到,這會在與Vue應用程序相同的端口上向/good-beers發出請求(因為它是相對URL)。 為此,您需要修改client/vue.config.js以使其具有將此URL發送到Spring Boot應用程序的代理。
module.exports = {devServer: {port: 8081,proxy: {"/good-beers": {target: "http://localhost:8080",secure: false}}} };修改client/src/views/Home.vue的模板,以顯示API中的優質啤酒列表。
<template><div class="home"><img alt="Vue logo" src="../assets/logo.png"><h1>Beer List</h1><div v-for="beer in beers">{{ beer.name }}</div></div> </template>使用npm start重新啟動Vue應用,并在http://localhost:8081上刷新您的應用。 您應該從Spring Boot API中看到啤酒列表。
創建一個BeerList組件
為了使此應用程序易于維護,請將啤酒清單邏輯和渲染移至其自己的BeerList組件。 創建client/src/components/BeerList.vue并用Home.vue的代碼填充它。 刪除Vue徽標,自定義模板的主類名稱,然后刪除HelloWorld組件。 完成后,它應該如下所示。
<template><div class="beer-list"><h1>Beer List</h1><div v-for="beer in beers">{{ beer.name }}</div></div> </template><script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import axios from 'axios';export interface Beer {id: number;name: string;giphyUrl: string; }@Component export default class BeerList extends Vue {public beers: Beer[] = [];private async created() {const response = await axios.get('/good-beers');this.beers = await response.data;} } </script>然后更改client/src/views/Home.vue ,使其僅包含徽標和對<BeerList/>的引用。
<template><div class="home"><img alt="Vue logo" src="../assets/logo.png"><BeerList/></div> </template><script lang="ts"> import { Component, Vue } from 'vue-property-decorator'; import BeerList from '@/components/BeerList.vue';@Component({components: {BeerList,}, }) export default class Home extends Vue {} </script>創建一個GiphyImage組件
為了使外觀看起來更好一點,添加GIPHY組件以根據啤酒的名稱獲取圖像。 創建client/src/components/GiphyImage.vue并將以下代碼放入其中。
<template><img :src=giphyUrl v-bind:alt=name height="200"/> </template><script lang="ts"> import { Component, Prop, Vue } from 'vue-property-decorator'; import axios from 'axios';@Component export default class GiphyImage extends Vue {@Prop() private name!: string;private giphyUrl: string = '';private async created() {const giphyApi = '//api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&limit=1&q=';const response = await axios.get(giphyApi + this.name);const data = await response.data.data;if (data.length) {this.giphyUrl = data[0].images.original.url;} else {this.giphyUrl = '//media.giphy.com/media/YaOxRsmrv9IeA/giphy.gif';}} } </script><!-- The "scoped" attribute limits CSS to this component only --> <style scoped> img {margin: 10px 0 0; } </style>更改BeerList.vue以在其模板中使用<GiphyImage/>組件:
<div v-for="beer in beers">{{ beer.name }}<br/><GiphyImage :name="beer.name"/> </div>并將其添加到<script>塊中的components列表中:
import GiphyImage from '@/components/GiphyImage.vue';@Component({components: {GiphyImage}, }) export default class BeerList extends Vue { ... }在同一文件中,在底部添加<style>部分,然后使用CSS Grid布局將啤酒按行組織。
<style scoped> .grid {display: grid;grid-template-columns: repeat(3, 1fr);grid-gap: 10px;grid-auto-rows: minmax(100px, auto); } </style>您需要將div環繞在啤酒清單模板上,以使其生效。
<div class="grid"><div v-for="beer in beers">{{ beer.name }}<br/><GiphyImage :name="beer.name"/></div> </div>進行這些更改后,您的用戶界面應類似于以下啤酒名稱和匹配圖像列表。
您剛剛創建了一個與Spring Boot API對話的Vue應用。 恭喜你! 🎉
添加PWA支持
Vue CLI開箱即用地支持漸進式Web應用程序(PWA)。 創建Vue應用程序時,您選擇了PWA作為功能。
PWA功能僅在生產中啟用,因為在開發中緩存資產可能是一個真正的難題。 在client目錄中運行npm run build來創建一個可以投入生產的版本。 然后使用serve創建一個Web服務器并顯示您的應用程序。
npm i -g serve serve -s dist -p 8081您應該能夠打開瀏覽器,并在http://localhost:8081看到您的應用程序。 第一次嘗試時,我發現加載頁面沒有呈現任何啤酒名稱,并且所有圖像均相同。 這是因為客戶端嘗試向/good-beers發出請求,并且在生產模式下未配置任何代理。
要解決此問題,您需要在客戶端中更改URL并將Spring Boot配置為允許從http://localhost:8081進行跨域訪問。
修改client/src/components/BeerList.vue以使用Spring Boot API的完整URL。
private async created() {const response = await axios.get('http://localhost:8080/good-beers');this.beers = await response.data; }如果進行這些更改后,您在用戶界面中看不到任何更改,那是因為您的瀏覽器已緩存您的應用程序。 使用隱身窗口或清除緩存(在Chrome中: 開發者工具 > 應用程序 > 清除存儲 > 清除網站數據 )可解決此問題。
為Spring Boot配置CORS
在服務器項目中,打開src/main/java/…?/demo/beer/BeerController.java并添加一個@CrossOrigin批注以啟用來自客戶端的跨域資源共享(CORS)( http://localhost:8081 ) 。
import org.springframework.web.bind.annotation.CrossOrigin; ...@GetMapping("/good-beers")@CrossOrigin(origins = "http://localhost:8081")public Collection<Beer> goodBeers() {進行這些更改后,重建Vue應用以進行生產,刷新瀏覽器,一切都應按預期呈現。
使用Lighthouse查看您的PWA分數
我在Chrome中進行了Lighthouse審核,發現此應用目前得分為81/100。 該報告最突出的抱怨是我沒有使用HTTPS。 為了查看該應用使用HTTPS時的評分,我將其部署到Pivotal Cloud Foundry和Heroku 。 我很興奮地發現它在兩個平臺上都得分很高。
得分為96的原因是因為The viewport size is 939px, whereas the window size is 412px. 我不確定是什么引起了這個問題,也許是CSS Grid布局?
要查看我用來部署所有內容的腳本,請參閱heroku.sh隨附的GitHub存儲庫中的heroku.sh和cloudfoundry.sh 。
您將需要在運行部署腳本之前初始化Git。 運行rm -rf client/.git ,然后運行git commit -a "Add project" 。
使用Okta添加身份驗證
您可能會想,“這很酷,很容易看出人們為什么挖Vue。” 嘗試過后,您可能還會挖掘其他工具:使用Okta進行身份驗證! 為什么選擇Okta? 因為您可以免費獲得1,000個每月活躍用戶 ! 值得一試,尤其是當您看到使用Okta將auth添加到Spring Boot和Vue如此容易時。
Okta Spring啟動啟動器
為了保護您的API,可以使用Okta的Spring Boot Starter 。 要集成此啟動器,請將以下依賴項添加到server/pom.xml :
<dependency><groupId>com.okta.spring</groupId><artifactId>okta-spring-boot-starter</artifactId><version>0.6.1</version> </dependency> <dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>2.1.1.RELEASE</version> </dependency>現在,您需要配置服務器以使用Okta進行身份驗證。 為此,您需要在Okta中創建OIDC應用。
在Okta中創建OIDC應用
登錄到您的1563開發者帳戶(或者注冊 ,如果你沒有一個帳戶)并導航到應用程序 > 添加應用程序 。 單擊“ 單頁應用程序” ,再單擊“ 下一步” ,然后為該應用程序命名。 將localhost:8080所有實例更改為localhost:8081 ,然后單擊完成 。
將客戶端ID復制到您的server/src/main/resources/application.properties文件中。 在其中時,添加與您的Okta域匹配的okta.oauth2.issuer屬性。 例如:
okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default okta.oauth2.client-id={yourClientId}將{yourOktaDomain}替換為您的組織機構網址,您可以在開發人員控制臺的儀表板上找到它。 確保在值中不包括-admin !
更新server/src/main/java/…?/demo/DemoApplication.java以將其啟用為資源服務器。
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;@EnableResourceServer @SpringBootApplication進行了這些更改之后,您應該能夠重新啟動服務器,并在嘗試導航到http://localhost:8080時看到訪問被拒絕。
Okta的Vue支持
Okta的Vue SDK可讓您將OIDC集成到Vue應用程序中。 您可以在npmjs.com上找到有關Okta的Vue SDK的更多信息。 要安裝,請在client目錄中運行以下命令:
npm i @okta/okta-vue@1.0.7 npm i -D @types/okta__okta-vue
Okta的Vue SDK的類型可能會包含在將來的版本中。 我創建了一個添加請求的拉取請求 。
打開client/src/router.ts并添加您的Okta配置。 該router.ts下面還包含了一個路徑BeerList ,這是需要進行身份驗證的回調,以及導航后衛需要認證/beer-list路徑。 用這個替換您的,然后更新yourClientDomain和yourClientId以匹配您的設置。 確保刪除{}因為它們只是占位符。
import Vue from 'vue'; import Router from 'vue-router'; import Home from './views/Home.vue'; import OktaVuePlugin from '@okta/okta-vue'; import BeerList from '@/components/BeerList.vue';Vue.use(Router); Vue.use(OktaVuePlugin, {issuer: 'https://{yourOktaDomain}/oauth2/default',client_id: '{yourClientId}',redirect_uri: window.location.origin + '/implicit/callback',scope: 'openid profile email', });const router = new Router({mode: 'history',base: process.env.BASE_URL,routes: [{path: '/',name: 'home',component: Home,},{path: '/about',name: 'about',// route level code-splitting// this generates a separate chunk (about.[hash].js) for this route// which is lazy-loaded when the route is visited.component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),},{path: '/beer-list',name: 'beer-list',component: BeerList,meta: {requiresAuth: true,},},{ path: '/implicit/callback', component: OktaVuePlugin.handleCallback() },], });router.beforeEach(Vue.prototype.$auth.authRedirectGuard());export default router;由于您具有BeerList的路由,因此請從client/src/views/Home.vue刪除它。
<template><div class="home"><img alt="Vue logo" src="../assets/logo.png"></div> </template><script lang="ts"> import { Component, Vue } from 'vue-property-decorator';@Component export default class Home extends Vue {} </script>在client/src/App.vue鏈接添加到>BeerList client/src/App.vue 。 您還需要添加代碼來檢測用戶是否已登錄。 替換<template>部分,并將下面的<script>添加到您的App.vue 。
<template><div id="app"><div id="nav"><router-link to="/">Home</router-link> |<router-link to="/about">About</router-link><template v-if="authenticated"> |<router-link to="/beer-list">Good Beers</router-link></template></div><button v-if="authenticated" v-on:click="logout">Logout</button><button v-else v-on:click="$auth.loginRedirect()">Login</button><router-view/></div> </template><script lang="ts"> import { Component, Vue, Watch } from 'vue-property-decorator';@Component export default class App extends Vue {public authenticated: boolean = false;private created() {this.isAuthenticated();}@Watch('$route')private async isAuthenticated() {this.authenticated = await this.$auth.isAuthenticated();}private async logout() {await this.$auth.logout();await this.isAuthenticated();// Navigate back to homethis.$router.push({path: '/'});} } </script>重新啟動Vue應用程序,您應該看到一個登錄按鈕。
單擊它,您將被重定向到Okta。 輸入您用來注冊Okta的憑據,您將被重定向回該應用程序。 您應該看到一個注銷按鈕和一個鏈接,以查看一些優質啤酒。
如果單擊“ Good Beers”鏈接,您將看到組件的標題,但沒有數據。 如果您查看JavaScript控制臺,則會看到CORS錯誤。
發生此錯誤是因為Spring的@CrossOrigin在Spring Security中不能很好地發揮作用。 要解決此問題,請在DemoApplication.java的主體中添加一個simpleCorsFilter bean。
package com.okta.developer.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter;import java.util.Collections;@EnableResourceServer @SpringBootApplication public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}@Beanpublic FilterRegistrationBean<CorsFilter> simpleCorsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration config = new CorsConfiguration();config.setAllowCredentials(true);config.setAllowedOrigins(Collections.singletonList("http://localhost:8081"));config.setAllowedMethods(Collections.singletonList("*"));config.setAllowedHeaders(Collections.singletonList("*"));source.registerCorsConfiguration("/**", config);FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));bean.setOrder(Ordered.HIGHEST_PRECEDENCE);return bean;} }進行此更改后,重新啟動服務器。 要使其在客戶端上全部client/src/components/BeerList.vue ,請修改client/src/components/BeerList.vue的created()方法以設置授權標頭。
private async created() {const response = await axios.get('http://localhost:8080/good-beers',{headers: {Authorization: `Bearer ${await this.$auth.getAccessToken()}`,},},);this.beers = await response.data; }現在,您應該能夠以經過身份驗證的用戶身份查看優質啤酒清單。
如果可行,那就太好了! 👍
了解有關Spring Boot和Vue的更多信息
本教程向您展示了如何構建使用諸如Spring Boot和Vue之類的現代框架的應用程序。 您學習了如何使用Okta的Vue SDK添加OIDC身份驗證和保護路由。 如果您想觀看本教程的視頻,我將其作為截屏視頻發布到YouTube上 。
如果您想了解有關Vue現象的更多信息,我推薦了幾篇文章。 首先,我認為這很不錯,它不是由公司(例如Angular + Google和React + Facebook)贊助的,這主要是由社區推動的。 挑戰Google和Facebook的Solo JavaScript開發人員是《連線》雜志的一篇文章,解釋了為什么這樣做如此驚人。
關于JavaScript框架的性能,JavaScript框架的基準成本是Anku Sethi的一篇有趣的博客文章。 我喜歡他寫這本書的動力:
上周,我對僅在頁面上包含React會產生多少性能影響感到好奇。 因此,我在廉價的Android手機上運行了一些數字,并對此進行了撰寫。
要了解有關Vue,Spring Boot或Okta的更多信息,請查看以下資源:
- 使用Spring Boot和Vue.js構建一個簡單的CRUD應用
- 使用Vue.js和Node構建基本的CRUD應用
- 使用Go和Vue構建單頁應用
- Spring Boot 2.1:出色的OIDC,OAuth 2.0和反應式API支持
您可以在GitHub上找到與本文相關的源代碼。 主要示例(無身份驗證)在master分支中,而Okta集成在okta分支中。 要簽出本地計算機上的Okta分支,請運行以下命令。
git clone -b okta https://github.com/oktadeveloper/spring-boot-vue-example.git如果您發現任何問題,請在下面添加評論,我們將盡力為您提供幫助。 如果您喜歡本教程,則應該在Twitter上關注我的團隊 。 我們還有一個YouTube頻道 ,我們在其中發布屏幕錄像。
該教程有Angular和React版本。
“我喜歡編寫身份驗證和授權代碼。” ?從來沒有Java開發人員。 厭倦了一次又一次地建立相同的登錄屏幕? 嘗試使用Okta API進行托管身份驗證,授權和多因素身份驗證。
使用Spring Boot和Vue進行Bootiful開發最初于2018年12月3日發布在Okta開發人員博客上。
翻譯自: https://www.javacodegeeks.com/2019/01/bootiful-development-spring-boot-vue.html
總結
以上是生活随笔為你收集整理的使用Spring Boot和Vue进行有益的开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux的head命令(linux的h
- 下一篇: ddos防御公司(防ddos攻击公司)