vuessr是什么,vuessr框架
本文主要介绍了对VueSSR的一些理解和详细配置,具有很好的参考价值。希望对大家有帮助。如有错误或不足之处,请不吝赐教。
目录
概念流程图编译图index.html相关配置目录结构变化,待安装的依赖启动命令,末尾异步处理示例。如果是静态页面,比如官网中的SSR处理可以直接使用prerender-spa-plugin插件进行预渲染。请参考我之前的博客:vue单页是通过prerender-spa-plugin插件进行SEO优化的。
以下是基于vue-CLI @ 2.x的工程相关结构转换,最后给出一个异步请求的例子,供参考。
概念
流程图
这是具体流程图,如何实现,后续配置会详细说明。
编译图解
结合上面的流程图来理解编译的流程图,因为服务器端渲染只是一个可以等待异步数据的预渲染,最终用户交互还是需要客户端录入生成的js来控制。这就是为什么需要两个条目文件,但是它们有相同的条目(app.js)。
串联服务器和客户端的集线器被存储。服务器将预渲染页面的存储数据放入全局窗口,客户端同步数据完成异步数据预渲染。
相关配置
了解了大致的步骤和流程,再了解VueSSR的配置就不会那么突兀了。
注意:如果路线懒加载,会出现vue模板中的样式。取而代之的是,它将使用js来呈现,浏览器将会有一个没有样式的空白期到最终页面。因为SSR渲染没有SPA的首屏渲染问题,加载不偷懒也没问题。
目录结构
index.html 的改动
需要加入!vue-SSR-插座占位符
!声明文档类型
超文本标记语言
头
meta charset=utf-8
meta http-equiv= X-UA-Compatible content= IE=edge,chrome=1
meta name= renderer content= WebKit
meta name= viewport content= width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no
link rel= icon href= type= image/x-icon /
标题{ { title } }/标题
/头
身体
div id=应用程序
!-vue-SSR-插座-
/div
/body
/html
需要安装的依赖
需要添加一些相关的依赖项。这里有一个总结:
npm安装内存-fs choki Dar vue-server-renderer @ 2 . 5 . 17 LRU-cache serve-favicon压缩路由-cache vuex-router-sync - save
注意:vue-server-renderer应该与项目的vue版本一致。
server.js
开发模式调用setup-dev-server中的热更新插件并实时编译。
const fs=require( fs );//读取文件
const path=require( path );
const express=require( express );
const app=express();
const LRU=require( LRU-cache );//封装缓存的get set方法
/*
*处理favicon.ico文件:函数:
* 1.扔掉这些不必要的日志。
* 2.缓存图标,以防止从原因磁盘重复读取。
* 3.提供基于图标的ETag属性,而不是通过文件信息更新缓存。
* 4.使用最兼容的内容类型来处理请求。
*/
const favicon=require( serve-favicon );
const compression=require( compression );//压缩
const micro cache=require( route-cache );//请求缓存
const resolve=(file)=path . resolve(_ _ dirname,file);//返回绝对路径
const { createBundleRenderer }=require( vue-server-renderer );
const is prod=process . ENV . node _ ENV=== production ;
const useMicroCache=process . env . micro _ CACHE!== false
常量服务器信息=
` express/$ { require( express/package )。版本} `
` vue-server-renderer/$ { require( vue-server-renderer/package )。版本} `;
让渲染器;
让readyPromise
//生成渲染器函数
函数createRenderer(捆绑包,选项){
return createBundleRenderer(bundle,Object.assign(options,{
缓存:新LRU({
max: 1000,
maxAge: 1000 * 60 * 15
}),
basedir: resolve(。/dist ),
runInNewContext: false
}));
}
函数render(req,res) {
const s=date . now();
res.setHeader(Content-type , text/html );
res.setHeader(Server ,服务器信息);
const handleError=err={
if (err.url) {
资源重定向(err.url)
} else if(err.code===404) {
大会第404号决议.发送("404 未找到页面")
}否则{
//呈现错误页面或重定向
决议现状(500).发送( 500 内部服务器错误)
console . error(` 1渲染期间出错:${req.url} `)
控制台。错误(错误堆栈)
}
};
常量上下文={
标题:" ssr "标题,
url: req.url
};
渲染器。rendertostring(context,(err,html)={
控制台。日志(错误);
如果(错误){
返回处理错误(err);
}
RES . send(html);
如果(!isProd) {
console . log(` o整个请求:$ {日期。现在()-s } ms `);
}
})
}
const templatePath=resolve( ./index。html’);
if (isProd) {
const模板=fs。读取文件同步(模板路径,“utf-8”);
const bundle=require( ./dist/vue-SSR-server-bundle。JSON’);
const clientManifest=require( ./dist/vue-SSR-客户端-清单。JSON’);
renderer=createRenderer(bundle,{
模板,
客户端清单
})
}否则{
readyPromise=require( ./build/setup-dev-server )(
app,
模板路径,
(套装,选项)={
renderer=createRenderer(捆绑包,选项)
}
)
}
const serve=(路径,缓存)=express.static(解析(路径),{
maxAge: cache isProd?1000 * 60 * 60 * 24 * 30 : 0
});
//静态文件压缩,支持压缩和紧缩方式(原来这步是服务器做的),阈值:0,0kb以上的都压缩,即所有文件都压缩,
//可通过过滤器过滤
//TODO
app.use(压缩({ threshold:0 });
app . use(favicon(’./favicon。ico’));
app.use(/dist),serve(./dist ,true));
app.use(/static),serve( ./static ,true));
app.use(/service-worker.js),serve(./dist/service-worker.js ,true));
//TODO
app。使用(微缓存。缓存秒数(1,req=useMicroCache req。原网址));
app.get(* ,isProd?render : (req,res)={
现成的承诺。然后(()=render(req,RES));
});
//监听
app.listen(8082,function () {
console.log(成功监听.8082);
});
entry-server.js
在路由分解之前,做数据预渲染
从""导入{ createApp } ./app
const is dev=process。环境。node _ ENV!==生产过程。环境。node _ ENV!==坐;
//此导出的函数将由“联邦储备银行”调用。
//这是我们执行数据预取以确定
//应用程序在实际呈现之前的状态。
//因为数据提取是异步的,所以此函数应该
//返回解析到应用实例的承诺。
导出默认上下文={
返回新承诺((解决,拒绝)={
const s=isDev日期。now();
const { app,路由器,商店}=创建app();
const { url }=上下文;
const {完整路径}=路由器。解析(URL).路线;
if (fullPath!==url) {
返回拒绝({ url:完整路径})
}
//设置路由器的位置
路由器.推送(网址);
//等到路由器解决了可能的异步挂钩
router.onReady(()={
常量匹配组件=路由器。getmatched组件()
//没有匹配的路由
如果(!matchedComponents.length) {
退货拒绝({代码:404 })
}
//在路由匹配的组件上调用fetchData挂钩。
//预取挂钩调度存储操作并返回承诺,
//当操作完成且存储状态为
//已更新。
保证。所有(匹配的组件。map(({异步数据})=异步数据异步数据({
店,
路由:路由器.当前路由器
}))).然后(()={
isDev console . log(` o数据预取:$ { date . now()-s }毫秒`)
//在所有预取挂钩被解决之后,我们的存储现在是
//填充了呈现应用程序所需的状态。
//公开呈现上下文的状态,并让请求处理程序
//在超文本标记语言响应中内联状态。这允许客户端
//存储以获取服务器端状态,而不必复制
//客户端上的初始数据提取。
内容。州=商店。状态;
解决(应用程序)
}).捕捉(拒绝)
},拒绝)
})
}
entery-client.js
窗户。INITIAL_STATE就是服务端存在超文本标记语言中的商店数据,客户端做一次同步,router.onReady在第一次不会触发,只有路由器接管页面之后才会触发,在解决前中手动进行数据请求(否则异步数据中的请求不会触发)
从""导入{createApp} ./app ;
const {app,路由器,商店}=创建app();
//用服务器初始化的状态填充存储。
//状态是在苏维埃社会主义共和国期间确定的,并内联在页面标记中。
如果(窗口__INITIAL_STATE__) {
store.replaceState(窗口.初始状态__)
}
router.onReady(()={
//添加用于处理异步数据的路由器挂钩。
//在解析初始路由后执行,这样我们就不会重复提取
//我们已经拥有的数据。使用router.beforeResolve()以便所有
//异步组件被解析。
router.beforeResolve(收件人,发件人,下一个)={
常数匹配=路由器。getmatched组件(到);
const prev匹配=路由器。getmatched组件(来自);
让diffed=false
//只需匹配和上一个路由不同的路由,共用的路由因为已经渲染过了,不需要再处理
常量激活=匹配。过滤器((c,i)={
return diffed (diffed=(prev matched[I]!==c))
});
常量异步数据挂钩=激活。map(c=c .异步数据).过滤器(_=_);
如果(!asyncDataHooks.length) {
console.log(没有客户端异步);
返回下一个()
}
//bar.start()
console.log(客户端异步开始);
保证。all(asyncdatahooks。map(hook=hook({ store,route: to }).然后(()={
//bar.finish()
console.log(客户端异步完成);
下一个()
}).接住(下一个)
});
//以激活模式挂载,不会改变浏览器已经渲染的内容
应用程序.$ mount( # app );
});
//服务人员
if( https:===位置。协议导航器。服务人员){
领航员。服务人员。注册(/service-worker。js’)
}
app.js
从“Vue”导入Vue
从导入应用程序. 1/app。vue ;
从""导入{createStore} ./store ;
从导入{创建路由器} ./router ;
从" vuex-路由器-同步"导入{ sync };
导出函数createApp() {
const store=createStore();
const路由器=create router();
//将路由器与状态管理存储同步。
//这将注册" store.state.route "
同步(商店、路由器);
//创建应用程序实例。
//这里我们将路由器、存储和固态继电器上下文注入到所有子组件中,
//使它们在任何地方都可用。"路由器"和"这个"。$store `
const app=new Vue({
路由器,
店,
render: h=h(App)
});
//暴露app,路由器,商店。
//注意,我们没有在这里安装应用程序,因为引导会
//不同取决于我们是在浏览器中还是在服务器上。
返回{应用程序、路由器、商店}
}
router.js 和 store.js
防止数据污染,每次都要创造新的实例
注意:路由如果用懒加载会出现某视频剪辑软件模板里面的样式没办法抽离到钢性铸铁中,而是用射流研究…渲染,浏览器会出现一个没有样式到最终页面的空白期。由于苏维埃社会主义共和国渲染做了没有矿泉首屏渲染问题,所以不用懒加载也没事。
//store.js
从“vue”导入某视频剪辑软件
从" vuex "导入状态管理
Vue.use(Vuex)
导出函数createStore () {
退回新Vuex .商店({
状态:{
令牌,
},
突变:{},
动作:{}
})
}
//router.js
从“vue”导入某视频剪辑软件
从“vue路由器"导入路由器
Vue.use(路由器)
从" @/视图/表格"导入表;
从" @/视图/第1页"导入第1页;
出口常量constantRouterMap=[
{路径:"/",组件:表,隐藏:真},
{ path:"/page1 ",组件:第1页,隐藏:true }
];
导出函数createRouter() {
退回新路由器({
模式:"历史",
fallback: false,//设置浏览器不支持history.pushState时,不回退
linkActiveClass:打开活动,
scrollBehavior: ()=({ y: 0 }),
路线:constantRouterMap
})
}
setup-dev-server.js
const fs=require( fs );
const path=require( path );
//文件处理工具
const MFS=require( memory-fs );
const web pack=require( web pack );
//对fs。手表的包装,优化fs,看原来的功能
const choki Dar=require( choki Dar );
const clientConfig=require( ./web pack。客户。config’);
const serverConfig=require( ./web pack。服务器。config’);
常量读取文件=(文件系统,文件)={
尝试{
返回fs。读取文件同步(路径。加入(客户端配置。输出。路径,文件), utf-8 );
} catch (e) {
}
};
模块.导出=函数setupDevServer(app,templatePath,cb) {
let包
让模板;
让客户端清单
让准备好;
const ready Promise=new Promise(r=ready=r);
//1.生成新的渲染器函数;2 .渲染器。render tostring();
常量更新=()={
如果(捆绑客户端清单){
//执行server.js中的提出函数,但是是异步的
ready();
cb(管束,{
模板,
客户端清单
})
}
};
模板=fs。读取文件同步(模板路径,“utf-8”);
//模板改了之后刷新待办事项
chokidar.watch(templatePath).on(change ,()={
模板=fs。读取文件同步(模板路径,“utf-8”);
控制台。log("索引。超文本标记语言模板已更新");
update();
});
客户端配置。入口。app=[ web pack-hot-middleware/client ,客户端配置。入口。app];
clientConfig.plugins.push(
新网络包HotModuleReplacementPlugin(),
新网络包NoEmitOnErrorsPlugin()
);
const客户端编译器=web pack(客户端配置);
const dev middleware=require( web pack-dev-middleware )(客户端编译器,{
公共路径:客户端配置。输出。公共道路
无信息:没错
});
app.use(开发中间件);
客户端编译器。插件( done ,stats={
统计=统计。to JSON();
统计数据。错误。foreach(err=console。log(err));
统计数据。警告。foreach(err=console。log(err));
if (stats.errors.length)返回;
客户端清单=JSON。解析(readFile(
文件系统,
vue-ssr-client-manifest.json
));
update();
});
app。use(require( web pack-hot-middleware )(客户端编译器,{心跳:5000 });
const server compiler=web pack(服务器配置);
const MFS=new MFS();
服务器编译器。输出文件系统=MFS
//监听计算机网络服务器文件修改待办事项
serverCompiler.watch({},(err,stats)={
if(err)throw err;
统计=统计。to JSON();
if (stats.errors.length)返回;
//读取vue-SSR-web pack-插件生成的包
bundle=JSON.parse(readFile(mfs, vue-SSR-server-bundle。JSON’);
update();
});
返回准备承诺
};
webpack.base.config.js
vue-loader.conf就是某视频剪辑软件脚手架自动生成的文件,就不再贴出了
const path=require( path );
var utils=require( ./utils’);
var config=require(./config );
var vueLoaderConfig=require( ./vue-loader。conf’);
var extract text plugin=require( extract-text-web pack-plugin );
var OptimizeCSSPlugin=require( optimize-CSS-assets-web pack-plugin );
var friendly error splugin=require( friendly-errors-web pack-plugin );
var CopyWebpackPlugin=require( copy-web pack-plugin )
函数解析(目录){
返回path.join(__dirname,.,目录)
}
const是prod=process。环境。node _ ENV=== production
模块。导出={
开发工具:isProd
?错误的
: #廉价模块源映射,
输出:{
path: path.resolve(__dirname,./dist ),
公共路径:"/dist/",
文件名:isProd?utils . assets path( js/[name]).【chunk hash 】. js ):utils。资产路径([名称])。js’),
chunkFilename: isProd?utils . assets path( js/[id]).【chunk hash 】. js ):utils . assets path([id].js’)
},
解决:{
扩展名:[。js , .vue , .json],
别名:{
vue$: vue/dist/vue.esm.js ,
@ :解决(客户端),
src: path.resolve(__dirname,./client’),
assets: path.resolve(__dirname,./客户/资产),
“components”:路径。resolve(_ _ dirname,./客户端/组件),
views: path.resolve(__dirname,./客户端/视图),
api: path.resolve(__dirname,./客户端/API’),
utils: path.resolve(__dirname,./client/utils ),
router: path.resolve(__dirname,./客户端/路由器),
vendor: path.resolve(__dirname,./客户/供应商),
static: path.resolve(__dirname,./static’),
}
},
外部:{
jQuery:“jQuery”
},
模块:{
规则:[
//{
//测试:/\。(jsvue)$/,
//loader: eslint-loader ,
//enforce:"pre ",
//包含:[resolve(src ),resolve(test)],
//选项:{
//formatter:require( eslint-friendly-formatter )
//}
//},
{
测试:/\。vue$/,
加载器:“vue-loader”,
选项:vueLoaderConfig
},
{
测试:/\。js$/,
装载机:巴别塔装载机?缓存目录,
包含:[解析(客户端)、解析(测试)]
},
{
测试:/\。(pngjpe?ggifsvg)(\?*)?$/,
加载程序:"网址加载程序",
查询:{
限额:10000,
名称:utils . assets path( img/[name]).[哈希:7】。[分机])
}
},
{
测试:/\。(woff2?eotttfotf)(\?*)?$/,
加载程序:"网址加载程序",
查询:{
限额:10000,
名称:utils.assetsPath(fonts/[name]).[分机])
}
},
.utils。样式加载器({ source map:config。戴夫。CSS源图})
]
},
插件:isProd
?[
//新建web包。优化。moduleconcatenationplugin(),
新的ExtractTextPlugin({
文件名:通用 css
}),
//压缩提取的CSS .我们使用这个插件是为了
//可以对来自不同组件的重复半铸钢钢性铸铁(Cast Semi-Steel)进行重复数据删除。
新的OptimizeCSSPlugin(),
//复制自定义静态资产
新建CopyWebpackPlugin([
{
from: path.resolve(__dirname,./static’),
收件人:配置。建造。资产子目录,
忽略:[。*]
}
]),
]
: [
新FriendlyErrorsPlugin(),
//复制自定义静态资产
新建CopyWebpackPlugin([
{
from: path.resolve(__dirname,./static’),
收件人:配置。建造。资产子目录,
忽略:[。*]
}
]),
]
};
webpack.server.config.js
const web pack=require( web pack );
const merge=require( web pack-merge );
const config=require(./config );
const baseConfig=require( ./web pack。基地。config’);
//const node externals=require( web pack-node-externals )
const VueSSRServerPlugin=require( vue-server-renderer/server-plugin );
设env,NODE _ ENV=进程。环境。NODE _ ENV
if (NODE_ENV===development) {
env=配置。戴夫。env
} else if(NODE _ ENV=== production ){
env=配置。建造。prodenv
}否则{
env=配置。建造。sitenv
}
模块。导出=合并(基本配置,{
目标:"节点",
开发工具:“# source-map”,
条目:。/client/entry-server.js ,
输出:{
文件名:" server-bundle.js ",
库目标:" commonjs2 "
},
插件:[
新网络包。定义插件({
process.env: env,
process.env.VUE_ENV :"服务器","
}),
新VueSSRServerPlugin()
]
});
webpack.client.config.js
const web pack=require( web pack );
const merge=require( web pack-merge );
const config=require(./config );
const utils=require( ./utils’);
const base=require( ./web pack。基地。config’);
const VueSSRClientPlugin=require( vue-server-renderer/client-plugin));
//TODO
//const SWPrecachePlugin=require( SW-precache-web pack-plugin );
设env,NODE _ ENV=进程。环境。NODE _ ENV
if (NODE_ENV===development) {
env=配置。戴夫。env
} else if(NODE _ ENV=== production ){
env=配置。建造。prodenv
}否则{
env=配置。建造。sitenv
}
module.exports=merge(base,{
条目:{
应用程序:。/client/entry-client.js
},
插件:
NODE_ENV!==deve
lopment ? [
new webpack.DefinePlugin({
process.env: env,
process.env.VUE_ENV: "client"
}),
new webpack.optimize.UglifyJsPlugin({
compress: {warnings: false}
}),
// extract vendor chunks for better caching
new webpack.optimize.CommonsChunkPlugin({
name: vendor,
minChunks: function (module) {
// a module is extracted into the vendor chunk if...
return (
// its inside node_modules
/node_modules/.test(module.context) &&
// and not a CSS file (due to extract-text-webpack-plugin limitation)
!/\.css$/.test(module.request)
)
}
}),
// extract webpack runtime & manifest to avoid vendor chunk hash changing
// on every build.
new webpack.optimize.CommonsChunkPlugin({
name: manifest
}),
new VueSSRClientPlugin(),
] : [
new webpack.DefinePlugin({
process.env: env,
process.env.VUE_ENV: "client"
}),
new VueSSRClientPlugin(),
],
});
启动命令
开发模式:npm run dev生产模式:npm run build & npm run start "scripts": {
"dev": "cross-env NODE_ENV=development supervisor server/app.js",
"start": "cross-env NODE_ENV=production node server/app.js",
"build": "npm run build:client && npm run build:server",
"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js",
"build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js"
},
异步处理例子
store.js
//store.js
import {TableRequest} from @/api/Table;
export default {
state: {
userName:
},
mutations: {
GETUSERNAME(state, data) {
state.userName = data;
}
},
actions: {
GetUserName({commit}) {
return TableRequest().then(res => {
commit(GETUSERNAME, res.data.content);
})
}
}
}
// api.js 请求可以用node做一个
export function GetUserName (user, password) {
const data = {
user,
password
}
return fetch({
url: /apis/getUserName,
data
})
}
vue页面
<template>
<div>
{{$store.state.userName}}
</div>
</template>
<script>
export default {
name: APP,
asyncData({store, route}) {
return store.dispatch(fetchItem, route.params.id);
},
}
</script>
server.js添加请求处理
const fs = require(fs); //读取文件
const path = require(path);
const express = require(express);
const app= express();
const LRU = require(lru-cache); //封装缓存的get set方法
/*
* 处理favicon.ico文件:作用:
* 1. 去除这些多余无用的日志
* 2. 将icon缓存在内存中,防止从因盘中重复读取
* 3. 提供了基于icon 的 ETag 属性,而不是通过文件信息来更新缓存
* 4. 使用最兼容的Content-Type处理请求
*/
const favicon = require(serve-favicon);
const compression = require(compression); //压缩
const microcache = require(route-cache); //请求缓存
const resolve = (file) => path.resolve(__dirname, file); //返回绝对路径
const {createBundleRenderer} = require(vue-server-renderer);
const isProd = process.env.NODE_ENV === production;
const useMicroCache = process.env.MICRO_CACHE !== false;
const serverInfo =
`express/${require(express/package).version}` +
`vue-server-renderer/${require(vue-server-renderer/package).version}`;
let renderer;
let readyPromise;
//生成renderer函数
function createRenderer(bundle, options) {
return createBundleRenderer(bundle, Object.assign(options, {
cache: new LRU({
max: 1000,
maxAge: 1000 * 60 * 15
}),
basedir: resolve(./dist),
runInNewContext: false
}));
}
function render(req, res) {
const s = Date.now();
res.setHeader(Content-type, text/html);
res.setHeader(Server, serverInfo);
const handleError = err => {
if (err.url) {
res.redirect(err.url)
} else if(err.code === 404) {
res.status(404).send(404 Page Not Found)
} else {
// Render Error Page or Redirect
res.status(500).send(500 Internal Server Error)
console.error(`error during render : ${req.url}`)
console.error(err.stack)
}
};
const context = {
title: ssr标题,
url: req.url
};
renderer.renderToString(context, (err, html) => {
console.log(err);
if (err) {
return handleError(err);
}
res.send(html);
if (!isProd) {
console.log(`whole request: ${Date.now() - s}ms`);
}
})
}
const templatePath = resolve(./index.html);
if (isProd) {
const template = fs.readFileSync(templatePath, utf-8);
const bundle = require(./dist/vue-ssr-server-bundle.json);
const clientManifest = require(./dist/vue-ssr-client-manifest.json);
renderer = createRenderer(bundle, {
template,
clientManifest
})
} else {
readyPromise = require(./build/setup-dev-server)(
app,
templatePath,
(bundle, options) => {
renderer = createRenderer(bundle, options)
}
)
}
const serve = (path, cache) => express.static(resolve(path), {
maxAge: cache && isProd ? 1000 * 60 * 60 * 24 * 30 : 0
});
/**
* 开始
* 测试数据请求的配置,可删
**/
app.get(/getName, function (req, res, next) {
res.json({
code: 200,
content: 我是userName,
msg: 请求成功
})
});
/**
* 结束
* 测试数据请求的配置,可删
**/
//静态文件压缩,支持gzip和deflate方式(原来这步是nginx做的),threshold: 0, 0kb以上的都压缩,即所有文件都压缩,
//可通过filter过滤
//TODO
app.use(compression({threshold: 0}));
app.use(favicon(./favicon.ico));
app.use(/dist, serve(./dist, true));
app.use(/static, serve(./static, true));
app.use(/service-worker.js, serve(./dist/service-worker.js, true));
//TODO
app.use(microcache.cacheSeconds(1, req => useMicroCache && req.originalUrl));
app.get(*, isProd ? render : (req, res) => {
readyPromise.then(() => render(req, res));
});
// 监听
app.listen(8082, function () {
console.log(success listen...8082);
});
结尾
以上就是结合vue-cli改造的一个ssr框架,流程和原理理解了之后可自行改造相关配置。希望能给大家一个参考,也希望大家多多支持我们。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。