专栏

一站式的自我提升与技能提升

uni-app跨平台开发注意事项

H5正常但App异常的可能性css异常:不支持的选择器 非H5端不支持*选择器;body的元素选择器请改为page,同样,div和ul和li等改为view、span和font改为text、a改为navigator、img改为image...webview浏览器兼容性vue页面在App端是被系统的webview渲染的(不是手机自带浏览器,是rom的webview),在较老的手机上,比如Android4.4、5.0或iOS8,很多css是不支持的,所以不要使用太新的css,会导致界面异常。注意这不意味着不能使用flex,Android4.4也支持flex,只是不要使用太新的css。可以找Android4.4手机或使用pc模拟器实际测试下,大多数国产Android模拟器都是4.4或5.0。小程序不存在此情况。所以如果你的H5和小程序界面正常,而App界面异常,大多是因为css兼容性。解决这类问题,可以在caniuse查询,也可以使用一个较低版本的chrome浏览器在H5端测试。Android4.4对应的webview是chrome37,如找不到老版chrome,也可以下载老版HBuilder(在HBuilderX下载页面底部有“上一代HBuilder下载”),在老HBuilder的右上角边改边看模式里是chrome37内核,可以把uni-app的H5版运行起来,将url粘贴到边改边看的浏览器中,点右键可以审查元素,排查不支持的css。原生组件层级问题 H5没有原生组件概念问题,非H5端有原生组件并引发了原生组件层级高于前端组件的概念,要遮挡video、map等原生组件,请使用cover-view组件。使用了非H5端不支持的API,比如document、xmlhttp、cookie、window、location、navigator、localstorage、websql、indexdb、webgl等对象。如果你的代码没有直接使用这些,那很可能是引入的三方库使用了这些。如果是后者,去插件市场搜索替代方案。要知道非H5端的js是运行在一个独立的js core或v8下,并不是运行在浏览器里。使用了非H5端不支持的vue语法,比如v-html指令、受小程序自定义组件限制的写法,详见不要在引用组件的地方在组件属性上直接写 style="xx",要在组件内部写样式url(//alicdn.net)等路径,改为url(https://alicdn.net),因为在App端//是file协议很多人在H5端联网时使用本地测试服务地址(localhost或127.0.0.1),这样的联网地址手机端必然无法访问,请使用手机可访问的IP进行联网H5正常但小程序异常的可能性同上小程序要求连接的网址都要配白名单小程序正常但App异常的可能性vue页面在App端是被系统的webview渲染的(不是手机自带浏览器,是rom的webview),在较老的手机上,比如Android4.4、5.0或iOS8,很多css是不支持的,所以不要使用太新的css,会导致界面异常。注意这不意味着不能使用flex,Android4.4也支持flex,只是不要使用太新的css。可以找Android4.4手机或使用pc模拟器实际测试下,大多数国产Android模拟器都是4.4或5.0。小程序不存在此情况。所以如果你的H5和小程序界面正常,而App界面异常,大多是因为css兼容性。解决这类问题,可以在caniuse查询,也可以使用一个较低版本的chrome浏览器在H5端测试。Android4.4对应的webview是chrome37,如找不到老版chrome,也可以下载老版HBuilder(在HBuilderX下载页面底部有“上一代HBuilder下载”),在老HBuilder的右上角边改边看模式里是chrome37内核,可以把uni-app的H5版运行起来,将url粘贴到边改边看的浏览器中,点右键可以审查元素,排查不支持的css。小程序或App正常,但H5异常的可能性使用了小程序原生组件,而没有使用vue标准的跨端组件。wxcomponets只有小程序和App才支持。App正常,但H5、小程序异常的可能性使用了App端特有的plus、Native.js、subNVue、原生插件等功能App使用Vue.js的注意uni-app基于Vue 2.0实现,开发者需注意Vue 1.0 -> 2.0 的使用差异data 属性必须声明为返回一个初始数据对象的函数;否则页面关闭时,数据不会自动销毁,再次打开该页面时,会显示上次数据 //正确用法,使用函数返回对象 data() { return { title: 'Hello' } } //错误写法,会导致再次打开页面时,显示上次数据 data: { title: 'Hello' } 在微信小程序端,uni-app将数据绑定功能委托给Vue,开发者需按Vue 2.0的写法实现数据绑定,不支持微信小程序的数据绑定写法,故如下写法不支持: <view id="item-{{id}}"></view> 需修改为: <view v-bind:id="'item-' + id "></view> 区别于传统web开发的注意你之前可能习惯自由的web开发,但目前各家小程序都有很多限制。 当然限制是为了在框架层更好的优化用户体验,所以小程序的体验要优于web。 并且这些限制只是写法的限制,并不影响功能。 如果你做过微信小程序开发,对这些限制应该已经很了解了。如果没有做过小程序,请仔细阅读本节。JS注意非H5端,不能使用浏览器自带对象,比如document、window、localstorage、cookie等,更不能使用jquery等依赖这些浏览器对象的框架。因为各家小程序快应用都不支持这些对象。没有这些浏览器自带对象并不影响业务开发,uni提供的api足够完成业务。uni的api在编译到web平台运行时,其实也会转为浏览器的js api。uni的api是多端可用的。在条件编译区,每个平台的专有api也可以使用,比如wx.、plus.等api可以分别在微信下和app下使用。出于降低小程序向uni-app迁移成本的考虑,wx的api在app里也可以直接运行,比如写wx.requst和uni.requst是一样的,但仍然建议仅在微信的条件编译区使用wx的api。Tag注意uni-app的tag同小程序的tag,和HTML的tag不一样,比如div要改成view,span要改成text、a要改成navigator。出于降低h5应用向uni-app迁移成本的考虑,写成div、span也可以运行在app和小程序上,因为uni-app编译器会把这些HTML标签编译为小程序标签。但仍然建议养成新习惯。Css注意虽然大部分css样式在微信小程序和app中都可以支持,但推荐使用flex布局模型,这种布局更灵活高效且支持更多平台(比如nvue、快应用只支持flex布局)单位方面,uni-app默认为rpx。这是一种可跨端的通用单位详见工程目录注意每个要显示的页面,都要放到pages目录下,新建一个页面所在的目录,然后放同名目录的vue文件,比如project/pages/lista/lista.vue,并且在pages.json里配置。这与小程序的策略相同。自定义组件,放到component目录静态资源如图片,固定放到static目录下。这是webpack、mpvue的规则数据绑定方式的注意uni-app基于Vue 2.0实现,开发者需注意Vue 1.0 -> 2.0 的使用差异,详见从 Vue 1.x 迁移每个页面支持使用原生title,首页支持使用原生底部tab,这些是要在pages.json里配置,这些并不是vue页面的一部分。当然vue里的js api也可以动态修改原生title虽然使用vue,但在app和小程序里,不是spa而是mpa位置坐标系统一为国测局坐标系gcj02,这种坐标系可以被多端支持。老版5+的百度定位和百度地图使用的是百度私有坐标系bd09ll,这种坐标系需要转换。新版uni-app里的百度地图已经默认改为gcj02。高德地图不受影响,一直是gcj02H5开发注意H5 发布到服务器注意:配置发行后的路径(发行在网站根目录可不配置),比如发行网站路径是 www.xxx.com/html5,在manifest.json文件内编辑 h5 节点,router 下增加 base 属性为 html5点击菜单 发行-> H5在当下项目下的unpackage/dist/build/h5目录找到出的资源,部署服务器(或者使用本地服务器预览)引用第三方 js 的方式:通过 npm 引入(通过条件编译,只有是 h5 平台才 import 相应的库)在manifest.json文件编辑 h5 节点的 template 属性,填写 html 模版路径,在 html 模版里面可以使用 script 的方式引入三方的 js,如下示例是加了百度统计的 html 模板部分代码,模版全部代码可参考:自定义模板... <body> <noscript> <strong>Please enable JavaScript to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?xxxxxx"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script> </body> ... 组件方面:支持mpvue组件、支持普通vue组件、不支持小程序自定义组件、不支持nvue。H5 版uni-app全支持vue语法,所以可能造成部分写法在 H5 端生效,在小程序或 App 端不生效。H5 校验了更严格的vue语法,有些写法不规范会报警,比如:data后面写对象会报警,必须写function;不能修改props的值;组件最外层template节点下不允许包含多个节点等。编译为 H5 版后生成的是单页应用(SPA)。如果遇到了白屏或网络不给力的提示,一般是跨域问题,网络请求(request、uploadFile、downloadFile等)在浏览器存在跨域限制,需服务端配合才能跨域。解决方案有2种:服务器打开跨域限制;本地浏览器安装跨域插件APP 和小程序的导航栏和tabbar均是原生控件,元素区域坐标是不包含原生导航栏和tabbar的;而 H5 里导航栏和tabbar是 div 模拟实现的,所以元素坐标会包含导航栏和tabbar的高度。为了优雅的解决多端高度定位问题,uni-app新增了2个css变量:--window-top和--window-bottom,这代表了页面的内容区域距离顶部和底部的距离。举个实例,如果你想在原生tabbar上方悬浮一个菜单,之前写bottom:0。这样的写法编译到 h5 后,这个菜单会和tabbar重叠,位于屏幕底部。而改为使用bottom:var(--window-bottom),则不管在 app 下还是在h5下,这个菜单都是悬浮在tabbar上浮的。这就避免了写条件编译代码。当然仍然也可以使用 H5 的条件编译处理界面的不同。CSS 內使用vh单位的时候注意100vh包含导航栏,使用时需要减去导航栏和tabBar高度,部分浏览器还包含浏览器操作栏高度,使用时请注意。正常支持rpx,px是真实物理像素。暂不支持通过设manifest.json的"transformPx" : true,把 px 当动态单位使用。使用罗盘、地理位置、加速计等相关接口需要使用 https 协议,本地预览(localhost)可以使用 http 协议。PC 端 Chrome 浏览器模拟器设备测试的时候,获取位置 API 需要连接谷歌服务器。组件内(页面除外)不支持onLoad、onShow等页面生命周期。为避免和内置组件冲突,自定义组件请加上前缀(但不能是 u 和 uni)。比如可使用的自定义组件名称:my-view、m-input、we-icon,例如不可使用的自定义组件名称:u-view、uni-input,如果已有项目使用了可能造成冲突的名称,请修改名称,另外微信小程序下自定义组件名称不能以 wx 开头。微信小程序开发注意微信小程序当前bug列表微信小程序更新日志支付宝小程序开发注意支付宝小程序更新日志支付宝小程序开发工具:https://docs.alipay.com/mini/ide/download目前无分包的配置,并且包体积限制在 3M。showLoading 是不透传的,也就是说 loading 显示的时候无法点击页面内容。文件名或文件夹名中不允许出现 @ 符号。网络请求返回的数据会严格按照dataType进行处理,如果不符合规范则会抛出错误,而不是按照原格式返回。canvas组件的标识是id,而不是canvas-id。目前还未进行处理,所以需要主动添加 id 属性。目前测试的结果,导航栏只有设置背景颜色为 #FFF(FFF) 时才会变成黑色文字。支付宝小程序的导航栏是支持透明渐变效果的,后面会提供相关的配置。使用伪元素做边框时,高度值不能用1rpx,需要直接用1px。不支持 ECharts。支付功能模拟不了,需要真机测试。百度小程序开发注意百度小程序更新日志百度小程序开发工具:https://smartprogram.baidu.com/docs/introduction/tool/。不支持属性选择器。不支持scoped。login / getUserInfo /支付等功能在模拟器(开发工具)上不能模拟。map组件在开发工具上预览效果不对,但是手机上是对的。getSystemInfo获取到的windowHeight在模拟器中值不正确,真机预览是正确的。v-if和v-for不可在同一标签下同时使用。页面中引入自定义组件时,渲染的结果中外层会有一个template标签,这会导致部分选择器对应的样式匹配不上。

href 2019-10-16

uni-app 初相识

uni-app简介:uni-app是什么呢?uni-app是一个使用vue.js开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台。即使不跨端,uni-app同时也是更好的小程序开发框架。功能框架:开发规范:为了实现多端兼容,综合考虑编译速度、运行性能等因素,uni-app约定了如下开发规范:页面文件遵循Vue但文件组件(SFC)规范组件标签靠近小程序规范接口能力(JS API)靠近微信小程序规范,但需将前缀wx替换为uni数据绑定及事件处理同Vue.js规范,同时补充了App及页面的生命周期为兼容多端运行,建议使用flex布局进行开发目录结构一个uni-app工程,默认包含如下目录及文件: ┌─components uni-app组件目录 │ └─comp-a.vue 可复用的a组件 ├─hybrid 存放本地网页的目录,详见 ├─platforms 存放各平台专用页面的目录,详见 ├─pages 业务页面文件存放的目录 │ ├─index │ │ └─index.vue index页面 │ └─list │ └─list.vue list页面 ├─static 存放应用引用静态资源(如图片、视频等)的目录,注意:静态资源只能存放于此 ├─wxcomponents 存放小程序组件的目录,详见 ├─main.js Vue初始化入口文件 ├─App.vue 应用配置,用来配置App全局样式以及监听 应用生命周期 ├─manifest.json 配置应用名称、appid、logo、版本等打包信息,详见 └─pages.json 配置页面路由、导航条、选项卡等页面类信息,详见 Tipsstatic目录下的js文件不会被编译,如果里面有es6的代码,不经过转换直接运行,在手机设备上会报错。css、less/scss等资源同样不要放在static目录下,建议这些公用的资源放在common目录下。HbuilderX 1.9.0+ 支持在根目录创建ext.jsonsitemap.json文件。路由uni-app路由全部交给框架统一管理,开发者需要在pages.json里配置每个路由页面的路径及页面样式,不支持Vue Router。路由跳转uni-app有两种路由跳转方式:使用navigator组件跳转、调用API跳转。页面栈运行环境判断开发环境和生产环境uni-app可通过process.env.NODE_ENV判断当前环境是开发环境还是生产环境。一般用于连接测试服务器或生产服务器的动态切换。在HBuilderX 中,点击“运行”编译出来的代码是开发环境,点击“发行”编译出来的代码是生产环境cli模式下,是通行的编译环境处理方式。if(process.env.NODE_ENV === 'development'){ console.log('开发环境') }else{ console.log('生产环境') } 如果你需要自定义更多环境,比如测试环境:假设只需要对单一平台配置,可以 package.json 中配置,在HBuilderX的运行和发行菜单里会多一个出来。如果是针对所有平台配置,可以在 vue-config.js 中配置。快捷代码块HBuilderX 中敲入代码块uEnvDev、uEnvProd可以快速生成对应development、production的运行环境判定代码。// uEnvDev if (process.env.NODE_ENV === 'development') { // TODO } // uEnvProd if (process.env.NODE_ENV === 'production') { // TODO } 判断平台平台判断有2种场景,一种是在编译期判断,一种是在运行期判断。编译期判断 编译期判断,即条件编译,不同平台在编译出包后已经是不同的代码。详见:条件编译// #ifdef H5 alert("只有h5平台才有alert方法") // #endif 如上代码只会编译到H5的发行包里,其他平台的包不会包含如上代码。运行期判断 运行期判断是指代码已经打入包中,仍然需要在运行期判断平台,此时可使用uni.getSystemInfoSync().platform判断客户端环境是 Android、iOS 还是小程序开发工具(在百度小程序开发工具、微信小程序开发工具、支付宝小程序开发工具中使用uni.getSystemInfoSync().platform返回值均为 devtools)。switch(uni.getSystemInfoSync().platform){ case 'android': console.log('运行Android上') break; case 'ios': console.log('运行iOS上') break; default: console.log('运行在开发者工具上') break; } 如有必要,也可以在条件编译里自己定义一个变量,赋不同值。在后续运行代码中动态判断环境。页面样式与布局尺寸单位uni-app支持的通用 css 单位包括 px、rpxpx 即屏幕像素rpx 即响应式px,一种根据屏幕宽度自适应的动态单位。以750宽的屏幕为基准,750rpx恰好为屏幕宽度。屏幕变宽,rpx 实际显示效果会等比放大。vue页面支持普通H5单位,但在nvue里不支持:rem 默认根字体大小为 屏幕宽度/20(微信小程序、头条小程序、App、H5)vh viewpoint height,视窗高度,1vh等于视窗高度的1%vw viewpoint width,视窗宽度,1vw等于视窗宽度的1%nvue还不支持百分比单位。App端,在 pages.json 里的 titleNView 或页面里写的 plus api 中涉及的单位,只支持 px。注意此时不支持 rpxnvue中,uni-app 模式(nvue不同编译模式介绍)可以使用 px 、rpx,表现与 vue 中一致。weex 模式目前遵循weex的单位,它的单位比较特殊:px:,以750宽的屏幕为基准动态计算的长度单位,与 vue 页面中的 rpx 理念相同。(一定要注意 weex 模式的 px,和 vue 里的 px 逻辑不一样。)wx:与设备屏幕宽度无关的长度单位,与 vue 页面中的 px 理念相同下面对rpx详细说明:设计师在提供设计图时,一般只提供一个分辨率的图。严格按设计图标注的 px 做开发,在不同宽度的手机上界面很容易变形。而且主要是宽度变形。高度一般因为有滚动条,不容易出问题。由此,引发了较强的动态宽度单位需求。微信小程序设计了 rpx 解决这个问题,uni-app在 App 端、H5 端都支持了rpx。rpx 是相对于基准宽度的单位,可以根据屏幕宽度进行自适应。uni-app规定屏幕基准宽度 750rpx。开发者可以通过设计稿基准宽度计算页面元素 rpx 值,设计稿 1px 与框架样式 1rpx 转换公式如下:设计稿 1px / 设计稿基准宽度 = 框架样式 1rpx / 750rpx换言之,页面元素宽度在uni-app中的宽度计算公式:750 * 元素在设计稿中的宽度 / 设计稿基准宽度举例说明:若设计稿宽度为 750px,元素 A 在设计稿上的宽度为 100px,那么元素 A 在uni-app里面的宽度应该设为:750 * 100 / 750,结果为:100rpx。若设计稿宽度为 640px,元素 A 在设计稿上的宽度为 100px,那么元素 A 在uni-app里面的宽度应该设为:750 * 100 / 640,结果为:117rpx。若设计稿宽度为 375px,元素 B 在设计稿上的宽度为 200px,那么元素 B 在uni-app里面的宽度应该设为:750 * 200 / 375,结果为:400rpx。Tips注意 rpx 是和宽度相关的单位,屏幕越宽,该值实际像素越大。如不想根据屏幕宽度缩放,则应该使用 px 单位。如果开发者在字体或高度中也使用了 rpx ,那么需注意这样的写法意味着随着屏幕变宽,字体会变大、高度会变大。如果你需要固定高度,则应该使用 px 。rpx不支持动态横竖屏切换计算,使用rpx建议锁定屏幕方向设计师可以用 iPhone6 作为视觉稿的标准。如果设计稿不是750px,HBuilderX提供了自动换算的工具App端,在 pages.json 里的 titleNView 或页面里写的 plus api 中涉及的单位,只支持 px,不支持 rpx。早期 uni-app 提供了 upx ,目前已经推荐统一改为 rpx 了样式导入NPM支持uni-app支持使用npm安装第三方包。此文档要求开发者们对npm有一定的了解,因此不会再去介绍npm的基本功能。如若之前未接触过npm,请翻阅NPM官方文档学习进行学习。初始化npm工程若项目之前未使用npm管理依赖(项目根目录下无package.json文件),先在项目根目录执行命令初始化npm工程:npm init -y cli项目默认已经有package.json了。HBuilderX创建的项目默认没有,需要通过初始化命令来创建。安装依赖在项目根目录执行命令安装npm包:npm install packageName --save 使用安装完即可使用npm包,js中引入npm包:import package from 'packageName' const package = require('packageName') 注意为多端兼容考虑,建议优先从uni-app插件市场获取插件。直接从 npm 下载库很容易只兼容H5端。非 H5 端不支持使用含有 dom、window 等操作的 vue 组件和 js 模块,安装的模块及其依赖的模块使用的 API 必须是 uni-app 已有的API(兼容小程序 API),比如:支持高德地图微信小程序 SDK。类似jQuery等库只能用于H5端。node_modules 目录必须在项目根目录下。不管是cli项目还是HBuilderX创建的项目。支持安装 mpvue 组件,但npm方式不支持小程序自定义组件(如 wxml格式的vant-weapp),使用小程序自定义组件请参考:小程序组件支持。关于ui库的获取,详见多端UI库

href 2019-10-16

NDK概览

NDK 说起NDK对于Android开发来说并不陌生,但是做NDK开发并不简单,需要掌握C和C++,简单来说你可以通过C和C++来实现你程序功能。比如游戏和音视频!因为最近正在学习NDK,故发文做个笔 记,简单说下学习NDK所需要知道的一些知识点。 特点: 运行效率高 代码安全性高 功能拓展性好 易于代码复用和移植JNI 定义:Java Native Interface,即 Java本地接口 作用: 使得Java 与 本地其他类型语言(如C、C++)交互 JNI是 Java 调用 Native 语言的一种特性 JNI 是属于 Java 的,与 Android 无直接关系 实际中的驱动都是C/C++开发的,通过JNI,Java可以调用C/c++实现的驱动,从而扩展Java虚拟机的能力。另外,在高效率的数学运算、游戏的实时渲染、音视频的编码和解码等方面,一般都是用C开发的为什么要有 JNI 实际使用中,Java 需要与 本地代码 进行交互 因为 Java 具备跨平台的特点,所以Java 与 本地代码交互的能力非常弱 采用JNI特性 增强 Java 与 本地代码交互的能力 public classMainActivityextends Activity { /** * 是用C/C++实现的原生方法 */ publicnativevoidcomputeFoo(); } NDK与JNI关系 JNI是实现Java调用C/C++的途径,NDK是Android中实现JNI的手段 即在Android的开发环境中,通过NDK从而实现JNI的功能 Java的优点是跨平台,和操作系统之间的调用由JVM完成,但是一些和操作系统相关的操作就无法完成,JNI的出现刚好弥补了这个缺陷,也完善了Java语言,将java扩展得更为强大常用C/C++编译器 Clang 是一个C、C++、Object-C的轻量级编译器。基于LLVM(LLVM是以C++编写而成的构架编译器的框架系统,可以说是一个用于开发编译器相关的库 gcc GNU C编译器。原本只能处理C语言,很快扩展,变得可处理C++ g++ GNU c++编译器,后缀为.c的源文件,gcc把它当作是C程序,而g++当作是C++程序;后缀为.cpp的,两者都会认为是c++程序,g++会自动链接c++标准库stl,gcc不会,gcc不会定义 __cplusplus宏,而g++会 GDB 是一个由GNU开源组织发布的、UNIX/Linux操作系统下的、基于命令行的、功能强大的程序调试工具 静态库(static libaray.a) 通常情况下,对函数库的链接是放在编译时期(compile time)完成的。所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file)。程序在运行时,与函数库再无瓜葛,因为所有需要的函数已拷贝到自己门下。所以这些函数库被成为静态库(static libaray),通常文件名为“libxxx.a”的形式Android可以使用静态库 动态链接库(dynamic link library.so) 把对一些库函数的链接载入推迟到程序运行的时期(runtime) 可以实现进程之间的资源共享 将一些程序升级变得简单 甚至可以真正坐到链接载入完全由程序员在程序代码中控制 最后送上爬坡神图

ZL 2019-09-26

搜索引擎 - solorCloud的搭建及基本使用

一 :单台solr的搭建 solr的简介:solor是基于lucene的全文搜索服务器。 2 .solr的启动与tomcat的结合 (1)在solr文件下的example目录下,将solr.war复制到tomcat下的webapps下 (2) 启动tomcat解压slor.war(将原来的solr.war解压成.jar文件) (3)将解压的solr.jar复制到solr的web-info下的lib中,将classes目录复制到web-info下 (4)将example中的solr目录复制到与tomcat同级的目录下 (5)打开tomcat的bin目录找到Catalina.bat文件 (6)设置:set "JAVA-OPTS=-Dsolr.home=solr目录位置solr-home" (7)启动tomcat重新访问一下。二:solrCloud的搭建 当一台solr能够运行的时候,开始搭建solr集群 1.将solr的配置文件交给zookeeper管理:在solr的文件夹/scripts/cloud-scripts/ 写上 ./zkcli.sh -zkhost zookeeper集群的主机名:端口号 2.修改solr.xml配置文件 在solr/solr-home 下修改配置文件: solr.xml {jetty.port.8080} 3.修改Catalina.sh: "JAVA_OPTS=-Dsolr.solr.home=....Pzhost:zookeeper集群的主机名:端口号" 4.将此配置文件发送到其他几台服务器上 scp -r ...三:solrCloud的基本使用 1.创建solr集群的对象 CluodServer cloudServer = new CluodSolrServer("集权的ip:端口号"); 2.连接索引库: cloudServer.setDefaultCollection("collection1"); 3.添加索引: SolrInputDocument solrDocument=new SolrInputDocument(); solrDocument.addField("id","1"); solrDocument.addField("title","普道科技官网"); solrDocument.addField("content","内容"); cloudServer.add(solrDocument); 4.删除索引: cloudServer.deleteById(1); 5.查询操作: SolrQuery solrQuery =new SolrQuery("*:*"); Response response = cloudServer.query(solrQuery ); (-------------------------以上是来自个人的浅尝辄止--------------------------)

上官沐雪 2019-09-09

初识 WebFlux

什么是WebFlux?webflux是Spring 5提出的一个新的开发Web的技术栈;它是一种异步非阻塞的开发模式;运行在Servlet 3.1+或Netty等容器上。webflux支持两种编程模式,一种是使用MVC的注解,另外一种是基于 Functional 函数式路由;也就是说我们现在开发Web服务多出了一个选择;和Spring mvc有什么异同?我们先看一下官方提供的对比图;第一个不同点Web flux是异步非阻塞的开发模式,而我们传统的MVC是一种同步的阻塞的开发模式。非阻塞的话意味着我们可以在一个线程里面可以处理更多的请求。而以前老的模式是一个请求对应我们容器里的一个线程,这是最大的不同点。第二个不同点是运行的环境的不同,MVC是基于ServletAPI,所以他必须运行在我们的Servlet的容器上面,而WebFlux是基于响应式流,他可以运行在Servlet 3.1之后的容器,也就是支持异步Servlet的容器。或者说运行在Netty上面。第三个不同点就是数据库这一块,Spring webflux Data Reactive Repositories里面关系型数据库都是不支持这种响应式的,仅仅支持 Mongo、Redis、等非关系型数据库。当然 还有很多区别,就不一一赘述了。为什么只能运行在Servlet 3.1+ 容器中?Servlet 3.1 规范中一个新特性是异步处理支持所谓异步处理是Servlet 线程不需一直阻塞,就是不需要到业务处理完毕再输出响应,然后结束 Servlet线程。异步处理的作用是在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,在不生成响应的情况下返回至容器。可以减少服务器资源的占用,并且提高并发处理速度。所以他必须运行在Servlet 3.1或者说Nettey 等支持异步处理的容器上;为什么目前不支持MySQL等关系型数据库?从官方提供对比图可以看出,Web mvc 支持的是 Spring data库,而 webflux 支持的是 spring data reactive库,所以这个问题的答案是,spring data的事务管理是基于 ThreadLocal传递事务的,本质是基于阻塞IO模型,不是异步的,但 Reactive 是要求异步的,不同线程里面 ThreadLocal肯定是娶不到值的。所以使用 Spring Data Reactive ,原来的 Spring 针对 Spring Data (JDBC等)的事务管理肯定不起作用了。当然这个问题也是可以解决的,感兴趣的同学可以后去研究一下。有什么缺点?代码相对来说编写复杂、不利于维护、不好扩展、社区不大,对关系型数据库支持不友好等等。通过一个案例来演示一下package com.lister.study.demo.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; import java.util.concurrent.TimeUnit; @Slf4j @RestController public class TestController { /** * 传统的Spring mvc写法 * @return */ @GetMapping("/1") private String get1() { log.info("get1 start"); String string = createStr(1); log.info("get1 end"); return string; } /** * Web Flux 写法 * @return */ @GetMapping("/2") private Mono<String> get2() { log.info("get2 start"); Mono<String> result = Mono.fromSupplier(() -> createStr(2)); log.info("get2 end"); return result; } private String createStr(int i) { log.info("createStr"); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException ignored) { } return "Some string from get" + i; } } 运行结果:Spring mvc 传统Servlet的运行结果:可以明显看出这个Servlet线程被阻塞了5秒钟再来看看我们WebFlux的运行结果:可以看出来请求线程并未被阻塞这就是为什么Flux能大大提高吞吐量的原因了再来看一组增删改查的案例Controller:@RestController @RequestMapping("/user") public class UserController { private UserService userService; public UserController(UserService userService) { this.userService = userService; } @PostMapping("/") public Mono<User> createUser(@RequestBody User user) { return userService.saveUser(user); } @PutMapping("/") public Mono<ResponseEntity<User>> updateUser(@RequestBody User user) { return userService.updateUser(user); } @DeleteMapping("/") public Mono<ResponseEntity> deleteUser(String id) { return userService.deleteUser(id); } @GetMapping("/{id}") public Mono<ResponseEntity<User>> getById(@PathVariable("id") String id) { return userService.getUserById(id).map(user -> new ResponseEntity<>(user, HttpStatus.OK)) .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @GetMapping("/name/{name}") public Flux<User> getByName(@PathVariable("name") String name) { return userService.getUserByName(name); } @GetMapping("/") public Flux<User> all() { return userService.allUser(); } } Service:public interface UserService { /** * 根据ID查询用户 * @param id * @return */ Mono<User> getUserById(String id); /** * 保存用户信息 * @param user * @return */ Mono<User> saveUser(User user); /** * 保存用户信息 * @param user * @return */ Flux<User> saveUser(Mono<User> user); /** * 更新用户 * @param user * @return */ Mono<ResponseEntity<User>> updateUser(User user); /** * 根据Id删除用户 * @param id * @return */ Mono<ResponseEntity> deleteUser(String id); /** * 根据用户名称查询用户列表 * @param name * @return */ Flux<User> getUserByName(String name); /** * 获取所有用户信息 * @return */ Flux<User> allUser(); } ServiceImpl:@Service public class UserServiceImpl implements UserService { private UserRepository userRepository; public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } /** * 根据ID查询用户 * * @param id * @return */ @Override public Mono<User> getUserById(String id) { return userRepository.findById(id); } /** * 保存用户信息 * * @param user * @return */ @Override public Mono<User> saveUser(User user) { user.setId(null); return userRepository.save(user); } /** * 保存用户信息 * * @param user * @return */ @Override public Flux<User> saveUser(Mono<User> user) { return userRepository.saveAll(user); } /** * 更新用户 * * @param user * @return */ @Override public Mono<ResponseEntity<User>> updateUser(User user) { return userRepository.findById(user.getId()) .flatMap(u -> { u.setName(user.getName()); u.setSex(user.getSex()); return userRepository.save(u); }) .map(u -> new ResponseEntity<>(u, HttpStatus.OK)) .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } /** * 根据Id删除用户 * * @param id * @return */ @Override public Mono<ResponseEntity> deleteUser(String id) { return userRepository.findById(id).flatMap(user -> userRepository.delete(user) .then(Mono.just(new ResponseEntity(HttpStatus.OK)))) .defaultIfEmpty(new ResponseEntity(HttpStatus.NOT_FOUND)); } /** * 根据用户名称查询用户列表 * * @param name * @return */ @Override public Flux<User> getUserByName(String name) { return userRepository.findByNameLike(name); } /** * 获取所有用户信息 * * @return */ @Override public Flux<User> allUser() { return userRepository.findAll(); } } Domain:@Data @Document(collection = "user") public class User { @Id private String id; private String name; private SexEnum sex; } Enum:public enum SexEnum { MAN, WOMAN,; } Repository:@Repository public interface UserRepository extends ReactiveMongoRepository<User, String> { /** * 根据名字模糊查询 * @param name * @return */ Flux<User> findByNameLike(String name); Flux<User> findByNameIsNotNull(); } 除了使用 Spring mvc 它还有 reactive stream 流的方式编程:Handler:@Component public class UserHandler { private UserService userService; public UserHandler(UserService userService) { this.userService = userService; } /** * 固定的方法格式 Mono<ServerResponse> xxx(ServerRequest request); * 根据Id查询用户 * @param request * @return */ public Mono<ServerResponse> getById(ServerRequest request) { String id = request.pathVariable("id"); return ok().contentType(APPLICATION_JSON_UTF8).body(userService.getUserById(id), User.class); } /** * 保存用户信息 * @param request * @return */ public Mono<ServerResponse> saveUser(ServerRequest request) { Mono<User> user = request.bodyToMono(User.class); return ok().contentType(APPLICATION_JSON_UTF8).body(userService.saveUser(user), User.class); } /** * 删除用户 * @param request * @return */ public Mono<ServerResponse> deleteUser(ServerRequest request) { String id = request.pathVariable("id"); return userService.deleteUser(id) .flatMap(responseEntity -> { if (responseEntity.getStatusCodeValue() == 200) { return ok().build(); } else { return notFound().build(); } }); } } 路由配置:@Configuration public class Routers { @Bean RouterFunction<ServerResponse> userRouter(UserHandler userHandler) { return RouterFunctions.nest(RequestPredicates.path("/user/router"), RouterFunctions.route(RequestPredicates.GET("/{id}"), userHandler::getById) .andRoute(RequestPredicates.POST("/") .and(RequestPredicates.accept(MediaType.APPLICATION_JSON_UTF8)), userHandler::saveUser) .andRoute(RequestPredicates.DELETE("/{id}"), userHandler::deleteUser) ); } } 这就是一个完整的 Spring flux 的增删改查的例子。

李斯特 2019-09-06

Flutter 路由与导航

路由管理路由(Route)在移动开发中通常指页面(Page),在Android中通常指一个Activity。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是Android还是iOS,导航管理都会维护一个路由栈,路由入栈(push)操作对应打开一个新页面,路由出栈(pop)操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: MainRoute(), ); } } class MainRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("主页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //导航到新路由 Navigator.push(context, MaterialPageRoute(builder: (context) { return SecondRoute(); })); }, child: Text("进入第二页"), ) ], ), ); } } class SecondRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("第二页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //路由pop弹出 Navigator.pop(context); }, child: Text("返回"), ) ], ), ); } } 命名路由命名路由(Named Route)即给路由起一个名字,然后可以通过路由名字直接打开新的路由。这为路由管理带来了一种直观、简单的方式。但是在早期的Flutter版本是不支持传参的,所以不要别网上的一些资料误导,看源码可以发现 Navigator.pushNamed()方法可以传一个arguments,一看到这个,让我想起Android里面fragment接收参数也是通过 arguments,于是找资料查看如何接收这个参数,发现有两种方式class CompileIncomePage extends StatefulWidget { @override _CompileIncomeState createState() => _CompileIncomeState(); } class _CompileIncomeState extends State<CompileIncomePage> { @override Widget build(BuildContext context) { var arguments = ModalRoute.of(context).settings.arguments; //通过这个来获取前一个页面传过来的参数(这里举例的是有状态的widget,亲测在无状态widget也是可以通过这个) return Container(); } } 来自Flutter官网的例子:https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments ,主要这种方式感觉比较麻烦所以我在项目中使用的是上面那种方式 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( // Provide a function to handle named routes. Use this function to // identify the named route being pushed, and create the correct // Screen. onGenerateRoute: (settings) { // If you push the PassArguments route if (settings.name == PassArgumentsScreen.routeName) { // Cast the arguments to the correct type: ScreenArguments. final ScreenArguments args = settings.arguments; // Then, extract the required data from the arguments and // pass the data to the correct screen. return MaterialPageRoute( builder: (context) { return PassArgumentsScreen( title: args.title, message: args.message, ); }, ); } }, title: 'Navigation with Arguments', home: HomeScreen(), ); } } class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home Screen'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ // A button that navigates to a named route that. The named route // extracts the arguments by itself. RaisedButton( child: Text("Navigate to screen that extracts arguments"), onPressed: () { // When the user taps the button, navigate to the specific route // and provide the arguments as part of the RouteSettings. Navigator.push( context, MaterialPageRoute( builder: (context) => ExtractArgumentsScreen(), // Pass the arguments as part of the RouteSettings. The // ExtractArgumentScreen reads the arguments from these // settings. settings: RouteSettings( arguments: ScreenArguments( 'Extract Arguments Screen', 'This message is extracted in the build method.', ), ), ), ); }, ), // A button that navigates to a named route. For this route, extract // the arguments in the onGenerateRoute function and pass them // to the screen. RaisedButton( child: Text("Navigate to a named that accepts arguments"), onPressed: () { // When the user taps the button, navigate to a named route // and provide the arguments as an optional parameter. Navigator.pushNamed( context, PassArgumentsScreen.routeName, arguments: ScreenArguments( 'Accept Arguments Screen', 'This message is extracted in the onGenerateRoute function.', ), ); }, ), ], ), ), ); } } // A Widget that extracts the necessary arguments from the ModalRoute. class ExtractArgumentsScreen extends StatelessWidget { static const routeName = '/extractArguments'; @override Widget build(BuildContext context) { // Extract the arguments from the current ModalRoute settings and cast // them as ScreenArguments. final ScreenArguments args = ModalRoute.of(context).settings.arguments; return Scaffold( appBar: AppBar( title: Text(args.title), ), body: Center( child: Text(args.message), ), ); } } // A Widget that accepts the necessary arguments via the constructor. class PassArgumentsScreen extends StatelessWidget { static const routeName = '/passArguments'; final String title; final String message; // This Widget accepts the arguments as constructor parameters. It does not // extract the arguments from the ModalRoute. // // The arguments are extracted by the onGenerateRoute function provided to the // MaterialApp widget. const PassArgumentsScreen({ Key key, @required this.title, @required this.message, }) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Text(message), ), ); } } // You can pass any object to the arguments parameter. In this example, // create a class that contains both a customizable title and message. class ScreenArguments { final String title; final String message; ScreenArguments(this.title, this.message); } 自定义路由切换动画Material库中提供了MaterialPageRoute,它在Android上会上下滑动切换。如果想自定义路由切换动画,可以使用PageRouteBuilder。import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: MainRoute(), ); } } class MainRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("主页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () async { //导航到新路由 var result = await Navigator.push( context, PageRouteBuilder( ///动画时间 transitionDuration: Duration(milliseconds: 500), pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { ///平移 return SlideTransition( ///Tween:在补间动画中,定义开始点结束点 position: new Tween<Offset>( begin: const Offset(1.0, 0.0), end: const Offset(0.0, 0.0), ).animate(animation), child: SecondRoute(), ); }, ), ); debugPrint("返回:$result"); }, child: Text("进入第二页"), ) ], ), ); } } class SecondRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("第二页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //路由pop弹出 Navigator.pop(context, "结束"); }, child: Text("返回"), ) ], ), ); } } 同时我们也可以对动画进行组合import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: MainRoute(), ); } } class MainRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("主页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () async { //导航到新路由 var result = await Navigator.push( context, PageRouteBuilder( ///动画时间 transitionDuration: Duration(milliseconds: 500), pageBuilder: (BuildContext context, Animation animation, Animation secondaryAnimation) { ///透明渐变与旋转 return new FadeTransition( opacity: animation, child: new RotationTransition( turns: new Tween<double>(begin: 0.5, end: 1.0) .animate(animation), child: SecondRoute(), ), ); },), ); debugPrint("返回:$result"); }, child: Text("进入第二页"), ) ], ), ); } } class SecondRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("第二页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //路由pop弹出 Navigator.pop(context, "结束"); }, child: Text("返回"), ) ], ), ); } } 注意点import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Flutter Demo', home: Scaffold( appBar: AppBar( title: Text("主页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { ///Navigator.push内部其实就是 Navigator.of(context).push Navigator.of(context).push(MaterialPageRoute(builder: (_){ return new SecondRoute(); })); }, child: Text("进入第二页"), ) ], ), ), ); } } class SecondRoute extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("第二页"), ), body: Column( children: <Widget>[ Text("第一个页面"), RaisedButton( onPressed: () { //路由pop弹出 Navigator.pop(context); }, child: Text("返回"), ) ], ), ); } } 问题关键点在于Navigator operation requested with a context that does not include a Navigator.(导航操作请求使用了不包含Navigator的上下文context)​Navigator实际上也是一个Widget,这个异常出现在Navigator.of(context)路由器的获取上,而这句代码会从当前的context的父级一层层向上去查找一个Navigator,我们当前传递的context就是MyApp,它的父级是root——UI根节点。Navigator这个widget的并不是由root创建的,因此在root下一级的上下文中无法获得Navigator。所以问题就在于,Navigator需要通过MaterialApp或者它孩子的上下文。

ZL 2019-09-06
写文章
最热榜单