14W行代码量的前端页面长什么样腾讯云开发者社区

就是这样一个页面,内部逻辑复杂,优秀的重构同学做到了组件尽可能地复用,未压缩的编译后开发代码仍然有14W行,因此也不算标题党了。

我们回顾CSR(客户端渲染)的流程

要把代码在Node下跑起来,首先要编译出文件来。除了原来的CSR代码外,我们创建一个Node端的入口文件,引入CSR的React组件。

(async()=>{conststore=useStore();awaitPromise.all([store.dispatch.user.requestGetUserInfo(),store.dispatch.list.refreshRecentOpenList(),]);constinitialState=store.getState();constinitPropsDataHtml=getStateScriptTag(initialState);constbodyHtml=ReactDOMServer.renderToString();//回调函数,将结果返回的TSRCALL.tsrRenderCallback(false,bodyHtml+initPropsDataHtml);})();服务端的store,Provider,reducer,ServerIndex等都是复用的客户端的,这里的结构和以下客户端渲染的一致,只不过多了renderToString以及将结果返回的两部分。

相应的,客户端的入口文件做一点改动:

exportdefaultfunctionApp(){constinitialState=window.__initial_state__||undefined;conststore=useStore(initialState);//额外判断数据是否完整的const{getUserInfo,recentList}=isNeedToDispatchCGI(store);useEffect(()=>{Promise.race([getUserInfo&&store.dispatch.user.requestGetUserInfo(),store.dispatch.notification.requestGetNotifyNum(),]).finally(async()=>{store.dispatch.banner.requestGetUserGrowthBanner();recentList&&store.dispatch.list.requestRecentOpenList();});},[]);}主要是复用服务端注入到全局变量的数据以及CGI是否需要重发的判断。

将代码编译出来,但是先不管跑起来能否结果一致,能不报错大致跑出个DOM节点来又是另外一回事。

首先摆在我们前面的问题在于浏览器端和Node端运行环境的差异。就最基本的,window,document在Node端是没有的,相应的,它们以下的好多方法就不能使用。我们当然可以选择使用jsdom来模拟浏览器环境,以下是一个demo:

对于不支持Node环境的依赖模块来说,比如浏览器端的上报库,统一的打开窗口的库,模块动态加载库等,对首页直出是不需要的,可以选择配置alias并使用空函数代替防止调用报错或ts检查报错。

constanyFunc=(...args:any[])=>{};exportconstScriptLoader={init:anyFunc,load:anyFunc,listen:anyFunc,dispatch:anyFunc,loadRemote:anyFunc,loadModule:anyFunc,};3.3必需依赖对于必需的依赖但是又不支持Node环境的,也只能是推动兼容一下。整个过程来说只有遇到两个内部模块是不支持的,兼容工作很小。对于社区成熟的库,很多都是支持Node下环境的。

比如组件库里默认的挂载点,在默认导出里使用document.body,只要多一下判断就可以了。

举一些不支持方法的案例:

像这种在组件渲染完成后注册可见性事件的,明显在服务端是不需要的,直接屏蔽就可以了。

exportconstregisterOnceVisibilityChange=()=>{if(__SERVER__){return;}if(onVisibilityChange){removeVisibilityChange(onVisibilityChange);}};useLayoutEffect在服务端不支持,也应该屏蔽。但是需要看一下是否需要会有影响的逻辑。比如有个组件是Slide,它的功能就像是页签,在子组件挂载后,切换子组件的显示。在服务端上明显是没有DOM挂载后的回调的,因此在服务端就需要改成直接渲染要显示的子组件就可以了。

exportdefaultfunctionTransitionView({visible=false,...props}:TransitionViewProps){if(!__SERVER__){useLayoutEffect(()=>{},[visible,props.duration]);useLayoutEffect(()=>{},[_visible]);}}useMemo方法在服务端也不支持。

exportfunctionuseStore(initialState:RootStore){if(__SERVER__){returninitializeStore(initialState);}returnuseMemo(()=>initializeStore(initialState),[initialState]);}总的来说使用屏蔽的方法,加上注入的有限的全局变量,其实屏蔽的逻辑不多。对于引入jsdom来说,结果可控,工作量又小。

对于要直出一个React应用,基础组件库的支持是至关重要的。腾讯文档里使用自己开发的DUI组件库,因为之前没有SSR的需求,所以虽然代码里有一些支持Node环境的逻辑,但是还不完善。

有一些组件需要在鼠标动作或者函数式调用才渲染的,比如Tooltip,Dropdown,Menu,Modal组件等。在特定动作后才渲染子组件。在服务端上,并不会触发这些动作,就可以用空组件代替。(理想情况当然是组件里原生支持Node环境,但是有五六个组件需要支持,就先在业务里去兼容,也算给组件库提供个思路)

以Tooltip为例,这样可以支持组件同时运行在服务端和客户端,这里还补充了className,是因为发现这个组件的根节点设置的样式会影响子组件的显示,因此加上。

classStyleRegistryManage{nodeRegistry:Record={};constructor(){if(isBrowser&&!window.__dui_style_registry__){window.__dui_style_registry__={};}}//这里才是重点,在不同的端存储的地方不一样publicgetregistry(){if(isBrowser){returnwindow.__dui_style_registry__;}else{returnthis.nodeRegistry;}}publicgetlength(){returnObject.keys(this.registry).length;}publicset(key:string,bundledsBy:string[]){this.registry[key]=bundledsBy;}publicget(key:string){returnthis.registry[key];}publicadd(key:string,bundledBy:string){if(!this.registry[key]){this.registry[key]=[];}this.registry[key].push(bundledBy);}}3.6公用组件库UserAgent腾讯文档里封装了公用的判断代码运行环境的组件库UserAgent。虽然自执行的模块在架构设计上会带来混乱,因为很有可能随着调用地方的增多,你完全不知道模块在什么样的时机被以什么样的值初始化。对于SSR来说就很怕这种自执行的逻辑,因为如果模块里有不支持Node环境的代码,意味着你要么得改模块,要么不用,而不能只是屏蔽初始化。

但是这个库仍然得支持自执行,因为这个被引用得如此广泛,而且假设你要ua.isMobile这样使用,难道得每个文件内都constua=newUserAgent()吗?这个库原来读取了window.navigator.userAgent,为了里面的函数仍然能准确地判断运行环境,在vm虚拟机里通过读取HTTP头,提供了global.navigator.userAgent,在模块内兼容了这种情况。

有个场景是列表头有个筛选器,当用户筛选了后,会将筛选选项存在localStorage,刷新页面后,仍然保留筛选项。对于这个场景,在服务端直出的页面当然也是需要筛选项这个信息的,否则就会出现直出的页面已经呈现给用户后。但是我们在服务端如何知道localStorage的值呢?换个方式想,如果我们在设置localStorage的时候,同步设置localStorage和cookie,服务端从cookie取值是否就可以了。

classServerStorage{getItem(key:string){if(__SERVER__){returngetCookie(key);}returnlocalStorage.getItem(key);}setItem(key:string,value:string){if(__SERVER__){return;}localStorage.setItem(key,value);setCookie(key,value,365);}}还有个场景是基于文件夹来存储的,即用户当前处于哪个文件夹下,就存储当前文件夹下的筛选器。如果像客户端一样每个文件夹都存的话,势必会在cookie里制造很多不必要的信息。为什么说不必要?因为其实服务端只关心上一次文件夹的筛选器,而不关心其他文件夹的,因为它只需要直出上次文件夹的内容就可以了。因此这种逻辑我们就可以特殊处理,用同一个key来存储上次文件夹的信息。在切换文件夹的时候,设置当前文件夹的筛选器到cookie里。

腾讯文档列表页为了提高滚动性能,使用react-virtualized组件。而且为了支持动态高度,还使用了AutoSizer,CellMeasurer等组件。这些组件需要浏览器宽高等信息来动态计算列表项的高度。但是在服务端上,我们是无法知道浏览器的宽高的,导致渲染的列表高度是0。

虽然有项新技术ClientHints可以让服务端知道屏幕宽度,视口宽度和设备像素比(DPR),但是浏览器的支持度并不好。

即使有polyfill,用JS读取这些信息,存在cookie里。但是我们想如果用户第一次访问呢?势必会没有这些信息。再者即使是移动端宽高固定的情况,如果是旋转屏幕呢?更不用说PC端可以随意调节浏览器宽高了。因此这完全不是完美的解决方案。

如果我们将虚拟列表渲染的项单独渲染而不通过虚拟列表,用CSS自适应宽高呢?反正首屏直出的情况下是没有交互能力的,也就没有滚动加载列表的情况。甚至因为首屏不可滚动,我们在移动端还可以减少首屏列表项的数目以此来减少CGI数据。

functionVirtualListServer(props:VirtualListProps){return({props.list.map((item,index)=>(props.itemRenderer&&props.itemRenderer(props.list[index],index)))}{!props.bottomTextnull:{props.bottomText}

}
);}constVirtualList=__SERVER__VirtualListServer:VirtualListClient;3.9不可序列化对象本来这个小章节算是原CSR代码里实现的问题,但是涉及的逻辑较多,因此也只是在运用数据前来做转换。

前面说过我们会往文档里以全局变量的方式注入state,怎么注入?其实就是用JSON.stringify将state序列化成字符串,如果这时候state里包含了函数呢?那么函数就会丢失。(不过看到下一小章节你会发现serialize-javascript是有保留函数的选项的,只是我觉得state应该是纯数据,正确的做法应该是将函数从state里移除,两种方式自由取舍吧)

例如这里的pageRange,里面包含了add,getNext等方法,在数据注入到客户端后,就只剩下纯数据:

constgetDefaultList=()=>({list:[],loading:true,section:false,allObtained:false,pageRange:newPageRange({start:-listLimit,limit:listLimit}),scrollTop:0,});在客户端使用的时候,还需要将pageRange转成新的实例:

这对于加入了SSR的CSR来说会有几点问题:

两个典型的Bug(代码里写了注释,应该不用再解释了):

以兼容L5的北极星SDK来解析(cl5需要依赖环境,在我使用的基础镜像tlinux-mini上会有错误)。

PS:Axios发送HTTPS请求会报错,因此在Node端换成了Got,方便本地开发。

这里还有个点是我们应该请求哪个L5?假设有两个CGI,doclist和userInfo,我们是解析它们各自的L5,通过OIDB的协议请求吗?考虑三个方面:

好在文档还有个统一的接入层tsw,因此我们其实只需要解析接入层tsw的L5,将请求都发往它就可以了。

在SSR代发起CGI请求,不仅需要从请求取出客户端传递过来的cookie来使用,在我们的tsw服务上,还会验证csrf,因此SSR发出CGI请求后,可能tsw会更新csrf,因此还需要将CGI请求返回的set-cookie再设置回客户端。

但是实际上在我的服务里没有收到这个头部,因此仍然会报错,由于我们没法去改tsw,也很清楚地知道我们是工作在代理之后,有个解决方案:

this.app.use(async(ctx,next)=>{ctx.cookies.secure=true;awaitnext();});4.2并发和上下文隔离我们来考虑这样一种情况:

当有两个请求A和B一前一后到达Server,在经过一大串的异步逻辑之后。到达后面的那个处理逻辑的时候,它怎么知道它在处理哪个请求?方法当然是有:

因此我们需要想个办法,将A和B的请求隔离开来。

如果说要隔离请求,我们可以有cluster模块提供进程粒度的隔离,也可以通过worker_threads模块提供线程粒度的隔离。但是难道我们一个进程和一个线程同时只能处理一个请求,只有一个请求完全返回结果后才能处理下一个吗?这显然是不可能的。

但是为了下面的错误捕获问题,我确实用worker_threads+vm尝试了好几种方法,虽然最后都放弃了。并且因为使用worker_threads可以共享线程数据的优点在这个场景下并没有多大的应用场景,反而是cluster可以共享TCP端口,最后是用cluster+vm,不过这是后话了。

用简短的代码演示就是这样的:

上下文隔离,我们还可以用vm来做。(然后我们的挑战就变成了怎么把十几万行的代码放在vm里跑,为什么需要把十几万行代码都放进去?因为后面会说到被require的模块里访问global的问题,虽然后面的后面解决了这个问题)

vm的一个基本使用姿势是这样的:

使用evel和Function可以做到吗?感觉理论上像是可以的,假设我们给每个请求分配ID,使用Object.defineProperty来定义数据的存取。但是我没有试过,而是使用成熟的vm模块,好奇的读者可以试一下。

另外因为我们并没有运行外部的代码,要在vm里跑的都是业务代码,因此不关心vm的进程逃逸问题,如果有这方面担忧的可以用vm2。

我们在Node环境下访问全局变量,有两种方式:

(()=>{a=1;global.b=2;})();console.log(a);console.log(b);//1//2而在vm里,是没有global的,考察以下代码:

上下文的全局变量默认是空的,不仅global没有,还有一些函数也没有,我们来看看最终构造出的上下文是都有什么:

我们有个文件vm-global-required.js是要被require的:

//[vm-host]:2//[required-file]:1可以看到被require的模块所访问的global并不是vm定义的上下文,而是宿主环境的global。

以vm创建的代码沙箱是需要编译的,我们不可能每个请求过来都重复编译,因此可以在启动的时候就提前编译缓存:

compilerVMByFile(renderJSFile){constscriptContent=fileManage.getJSContent(renderJSFile);if(!scriptContent){return;}constscriptInstance=newvm.Script(scriptContent,{filename:renderJSFile,});returnscriptInstance;}getVMInstance(renderJSFile){if(!this.vmInstanceCache[renderJSFile]){constvmInstance=this.compilerVMByFile(renderJSFile);this.vmInstanceCache[renderJSFile]=vmInstance;}returnthis.vmInstanceCache[renderJSFile];}但是其实v8编译是不编译函数体的,好在可以设置一下:

我们的SSR和普通的后台服务最大的区别在于什么?我想是在于我们不允许返回空内容。后台的CGI服务在错误的时候,返回个错误码,有前端来以更友好的方式展示错误信息。但是SSR的服务,即使错误了,也需要返回内容给用户,否则就是白屏。因此错误的捕获显得尤为重要。

总结一下背景的话:

在node里,如果要捕获未知的错误,我们当然可以用process来捕获

如果以vm来执行代码的话,我们大可以在代码的外部包裹try...catch来捕获异常。看下面的例子,try...catch捕获到了错误,错误就没再冒泡到process。

继续改写上面的例子,将vm放在domain里执行,可以看到错误被domain捕获到了

那有什么办法吗?这里想了两个比较骚的写法。

但是这样的代码存在什么问题?

最主要的问题在于filename是编译进去的,即使生成v8代码缓存的Buffer,后面用这个Buffer来编译一个新的script实例,传递进新的filename,仍然改变不了之前的值。所以会带来代码每次都需要编译的成本。

我们可以来实践以下:

当我们想同步和异步代码都能捕获得到,那么只剩下Promise错误了。什么情况会报Promise未处理的错误呢?也就是没有写catch的情况。那么如果我们改写Promise,将每个Promise都加上一个默认的catch函数,是否能达到期望呢?

结果:在一个随机的任务ID上,成功在process上捕获到了上下文的信息。(但是Promise实现的精华在于then之后的链式调用,这在上面的代码是没有体现的。)

有没有更好的方法呢?

这样实现的效果就是:

除了发送CGI这一步需要在线上环境,在用户浏览器发起请求时由SSRServer代理请求外,空的store和以空的store渲染出React应用,是我们在编译期间就可以确定的。那么我们就可以很方便地获得一个骨架屏,而所需要做的在原来SSR的基础上只有几步:

这个方案能给我们带来什么?

在浏览器资源加载都完成后,说明达到整体可交互的状态。

consthtmlResponseCost=performance.timing.responseStart-performance.timing.requestStart;6.4.2文档大小SSR因为在文档里加了渲染后的节点和初始化数据,因此文档大小会变大。对于文档大小的变化,那么我们就会关心两个指标:文档大小和下载耗时。

计算文档大小:

实际上我们只需要实现这几大模块,以及一些额外的功能就可以了。其余的就可以让业务拓展。

我们去除一些细节和重复的,来看一下业务大概的一个配置情况:

基于以上两点,我们在想是否可以将node_modules里的模块排除开,但是一些模块又有隔离上下文需求的,就一起编译。这样可以减少重复代码的执行,加快执行速度。

但是我们将vm的运行环境抽离出单独的包tsr,那么业务的node_moduels和tsr的node_modules是隔离的,要想在tsr里require到业务的node_modules,我们需要对require的路径查找做处理。

require查找模块的路径依赖module.paths,那么我们只需要将业务node_modules的路径添加到module.paths里,就能够正确找到依赖:

答案是无效的,因为这两个文件的module对象是不一样的,我们传递到vm的全局变量里的module是vm文件里的。

同时,为了我们的React应用编译出的代码能正常requirenode_modules下的模块,我们还需要对babel做更改:

当我们后续要做ABTest或者是系统环境的分支路径隔离,就需要同时运行多个分支的代码,这如果使用云函数的话,有两个方案:

那么我们考虑使用云函数能给我们带来什么:

好吧,弹性伸缩我用STKE也可以,负载均衡有L5,STKE还可以创建负载均衡器。不说SCF创建NFS还有网络的要求,在SCF里我们仍然需要处理上下文隔离的问题,只会将问题变得更复杂。(原谅我原来先使用的STKE的,不过SCF也确实去申请平台子用户,申请权限,创建到一半了,也确实考察过)

选择了使用镜像部署的方式来提供服务,那么我们就需要有docker镜像。我们可以提供tnpm包,让业务自己启动起tsr服务。但是提供docker基础镜像,隐藏启动的细节,让业务只设置个配置路径,是更加合理而有效的方式。

可以基于Node:12,设置启动命令:

FROMnode:12COPY.//tsr/CMD["node","/tsr/scripts/cli.js"]但是node:12,或者node:12-alpine镜像在公司环境下,发起请求到接收请求都要200-300ms,原因未知,待研究。

司内环境更推荐使用tlinux-mini(tlinux镜像大),安装node,拷贝代码,并且拷贝启动脚本到/etc/kickStart.d下。(tlinux为什么不能设置CMD启动命令?因为tlinux有自己的初始化进程,进程pid=1)启动后log会输出到/etc/kickstart.d/startApp.log。

FROMcsighub.tencentyun.com/tsr/tsr:v1.0.38#编译的变量,多分支支持ARGhookBranchCOPY.//tsr-renders/#为了启动时同步代码到pvc硬盘的ENVTSR_START_SCRIPT/tsr-renders/start.js#因为代码被start.js拷贝到pvc硬盘,因此配置的路径在pvc硬盘的路径下ENVTSR_CONFIG_PATH/tsr-renders/renders-pvc/${hookBranch}/config.js7.6开发和调试当我们在本地开发的时候,可以用whistle来代理请求:

#!/bin/bashdockerpullcsighub.tencentyun.com/tsr/tsrdockerbuild-tdesktop-ssr./tsr-renderscontainer=`dockerrun-d--privileged-p80:80desktop-ssr`dockerexec-it${container}/bin/shdockercontainerstop${container}dockercontainerrm${container}有两个点需要注意:

至于一些其他方面的问题,包括:

当我们部署了SSR的服务后,没有人会这么虎将原来的Nginx服务一次性切到SSR的服务吧?我们会先在内部灰度试用,且我们要同步对比两边的数据。所以怎么接入就成了我们要考虑的问题。

腾讯文档里有个tsw服务用来转发请求,并且有个routeproxy可以设置转发规则。routeproxy由两个参数组成,ID(指定转发到机器IP的规则),FD(指定机器的开发目录路径)。

我们的SSR服务能处理的就是列表页的PC+移动端,但是其实像/desktop/目录下还有其他很多页面和资源,我们需要将这部分独立开来。

在开发阶段,我们可以自己写规则来验证:

当我们准备接入了,就需要创建一个新的L5,新的L5的机器仍然是现网的机器,将上诉规则的流量转到新的L5。这样到目前为止,对现网就没有影响。

当我们需要在现网上线SSR服务的时候,只需要将SSR的机器IP添加到L5里,并逐步调整权重,这样就能够按机器来灰度。

按图例来说就是这样的:(当然了,浏览器并不会直接和tsw交互,前面还有公司的统一接入层)

上面说到在测试环境或者未来的ABTest,我们需要同时灰度多个分支。以测试环境为例,如果我们要让SSR分支和非SSR分支同时工作,除了在一开始部署的时候将代码拷贝到不同分支的目录下,如分支为feature/test,就将代码拷贝到/tsr-renders/feature/test下。在用户访问的时候,cookie是带有特定的值来标识目前要访问开发环境下的哪个文件夹的,以很简单的代码表示:

if(/*设置了开发分支*/){if(/*待渲染的JS文件存在*/){//直出服务}else{if(/*JS文件不存在,回退到SSR分支,如果SSR分支的JS文件存在,就用直出*/){//直出服务}else{//直接输出HTML}}}(这里其实是为了上线前的验证,才会回退到SSR分支的)

前面说到我们在编译的时候会排除node_modules,那么在我们做多分支灰度的时候,node_moduels是如何处理的呢?

假设我们现在有一个分支,但是我们的某次发布是按3个批次来灰度的(实际上我们是按5个批次的):

其实我们可以通过软连接来解决这个问题:

说了这么多,是否还有什么没说到的?感觉还有几点:

罗里吧嗦说了很多,当然还有很多细节没有讲到,如果有错误的地方欢迎指正。或者有什么好方法好建议也强烈欢迎私聊交流一下。

我们是在做腾讯文档的AlloyTeam团队,腾讯文档大量招人,如果你也想来研究这么有趣的技术,或者加入开放的腾讯大家庭,无论是应聘还是内推,都欢迎联系sigmaliu@tencent.com

THE END
1.頁的部首頁的拼音頁的组词頁的意思【戌集下】【頁字部】 頁; 康熙笔画:9; 页码:页1399第01 【集韻】奚結切【韻會】【正韻】胡結切,音纈。【說文】頭也。从从儿。 又【六書故】頁卽首字,不當音纈。說文分部分切,非。 字源字形 字源演变: 甲骨文金文楷体 字形对比: https://www.chazidian.com/r_zi_zd9801/
2.页是什么意思页的拼音页怎么读汉语字典也指书籍、杂志、报纸、信件或类似物件的一张纸。如:撕下其中的一页 「页」字方言集汇: 粤语:jip6 【页】在康熙字典里的意思 《戌集下》《頁字部》 ·頁 《集韻》奚結切《韻會》《正韻》胡結切,音纈。 《說文》頭也。从从儿。 又《六書故》頁卽首字,不當音纈。說文分部分切,非。 https://www.zidianwang.cn/zidian/9875.html
3.瑞去掉王加个页什么字瑞去掉王加个页什么字 瑞去掉王加个页是颛字。颛的读音是:zhuān,形声。字从专(duān/zhuān),从页,专亦声。“专”意为“轮廓线和缓起伏的山丘”。“页”指人头。“专”与“页”联合起来表示“圆头胖脑”。释义:本义:圆头胖脑的中年贵族。引申义:体面和守法的人,忠厚善良的人。社会精英人士,拥有http://baijiahao.baidu.com/s?id=1718737189729751864&wfr=spider&for=pc
4.上下添字重写汉字这个“重”字应该如何加上下添字重写汉字,“重”字如何巧妙加字?在汉字的书写中,我们常常会遇到一些有趣的挑战,比如“重”这个字,如何通过上下添字的方式对其进行重写呢?下面,我们就来探讨一下这个问题。 一、理解“重”字的含义 上下添字重写汉字,这个“重”字应该如何加?这个标题符合了用户搜索需求,同时也带有疑问,并且总字数超过了http://m.shijingketang.com/sjjc/117693.html
5.《说文解字》第907课:“债券”的“券”为啥下面是个“刀”?(二)xuàn(又读quàn)。门窗、桥梁等建筑成弧形的部分。又叫“拱券”。 (拱券) 券的小篆写法如图: (券的小篆写法) (【说文解字】之907,部分图片源自网络)https://mp.weixin.qq.com/s?__biz=MzAwMjYzMjIwMQ==&mid=2650279110&idx=1&sn=1b21399592ff19279c7e649838e4dc34&chksm=83c2a5b8d03707600345df986c554093dd899fcd038da39a5ca8e1a44e0266d940ad75faa549&scene=27
6.KVM详解,太详细太深入了,经典kvmgraphic独占显示器支持KSM (Kenerl Same-page Merging 内核同页合并) RedHat Linux KVM 有如下两种安装方式: 4.1 在安装 RedHat Linux 时安装 KVM 选择安装类型为 Virtualizaiton Host : 可以选择具体的 KVM 客户端、平台和工具: 4.2 在已有的 RedHat Linux 中安装 KVM https://blog.csdn.net/cling60/article/details/78542445
7.凌晨1点半,学校通报:问题基本属实,存在学术不端行为凌晨1点半,学校通报:问题基本属实,存在学术不端行为 篡改实验数据、实验图片造假、论文不当署名、操纵同行评议、教材编写抄袭、打压学生……近日,华中农业大学动物营养系教授黄飞若课题组11名硕士、博士研究生,通过一份125页图文并茂的举报材料,实名举报黄飞若多个方面涉嫌学术不端的问题。https://zhuanlan.zhihu.com/p/678682935
8.精细化运营时代,小鹅通如何精细化获客?运营汪成长日记限时限量,限额100名,29.9元的价格对于用户来说不会太贵,且小鹅通的客户多为B端客户,有一定的购买能力,价格优惠且加上海报上课程亮点、老师简介(《从零开始做运营》畅销书作者张亮)会使更多用户产生购买力,如果对此有疑问,也可以先加上小助手,加上小助手后,会自动再推送出“知识变现孵化营”的亮点,再次会给用户营https://www.shangyexinzhi.com/article/7489516.html
9.家居风水180种煞及化煞方法!【化解】可在玻璃窗贴上半透明的磨砂胶纸,再把明咒葫芦两串放在窗边左右角,加上一个木葫芦,便能化解普通的反光煞;反光较弱者则不必加木葫芦,反光强者要多安放两串白玉葫芦五帝古钱便可化解或者用凸面镜或麒麟踩八卦来化解 11、枪煞 【描述】所谓“一条直路一条枪”,即是家中大门对正有一条直长的走廊、道路,http://www.360doc.com/content/12/0121/07/55540442_975073242.shtml
10.数学总复习教案14篇(3)如果一端种,另一端不种,间隔数=棵数 在首尾相接的封闭排列中,物体的个数与间隔数是相等的。类似的现象还有锯木头、爬楼梯等。 练一练:有一条长800米的公路,在公路的一侧从头到尾每隔20米栽一棵杨树,需要多少棵树苗? 学生读题后独立思考并解答,然后交流。 https://www.unjs.com/jiaoan/shuxue/20230126081225_6320437.html
11.[14102]素素出场时间表〖荷風〗08.素素救下冷剑白狐后赶往昆仑峰00分56秒~04分08秒; 素素带冷剑白狐找昆仑上人医治07分29秒~15分10秒; 素素与众人赶往狂沙坪观看决斗22分56秒~24分12秒; 宇文天两招被杀,素素与谈无欲产生了疑惑26分05秒~26分12秒; 荫尸人出面解释死的宇文天是假扮的人27分18秒~27分25秒; http://www.lovesusu.com/archiver/?tid-2799.html
12.淘宝天猫宝贝详情页如何添加视频?有哪些要求?电商现在的竞争可谓说是风云莫测的,开一个网店要想从众多竞争者中脱颖而出,这就让我们需要做的事情也就变得更多了,尤其是宝贝描述介绍这一块,这关乎着商品的转化量,很多卖家对于宝贝详情页还不能很好的排兵布阵,尤其是在这个内容营销时代,内容是非常重要的,其实要知道宝贝详情页是可以加视频的。 宝贝详情页中加https://m.taopuwang.com/article/6151
13.你所用的快捷键在这里都能找到,但是还有你没用过的Alt+Tab可以在打开的应用程序中实现窗口切换(例如在cad中不同窗口间的切换,浏览器中不同标签页间的切换等等) 4、Shift快捷键 Shift+空格 半\全角切换 Shift+F10 选中文件的右菜单 Shift+Del 永久删除 5、常规键盘快捷键 Ctrl + C 复制。 Ctrl + X 剪切。 https://www.meipian.cn/26hzx0kk
14.从绪论部分开始,至附录用阿拉伯数字连续编排,页码位于页面底端在排版文档时,为了提高可读性和清晰度,我们通常会使用分节符来对文章进行分节。分节符是一种文本格式化https://ask.zol.com.cn/x/21175964.html
15.顶岗实习系统功能说明(精选9篇)2.统一的要求详见《会计系2013届毕业生实习报告撰写要求》; 按照指导老师要求写好实习报告后,用A4纸单面打印出来,粘贴在《实习手册》的实习报告部分(第17-18页)。 七、实习单位实习鉴定表填写(第19页) 实习生务必在《实习手册》上交指导老师之前,找自己所在实习单位的负责人填写、签字并要加盖实习单位公章。 https://www.360wenmi.com/f/filefx6yylj7.html
16.面试一mob604756ee87ff的技术博客当web应用程序向要记录客户端的信息时,它也会记录客户端的IP地址或者通过域名服务器查找机器名转换为IP地址。DNS查询需要占用网络,并且包括可能从很多很远的服务器或者不起作用的服务器去获取对应的IP的过程,这样会消耗一定的时间。为了消除DNS查询对性能的影响我们可以关闭DNS查询,方式是修改server.xml文件中的enableLoohttps://blog.51cto.com/u_15127553/4519203