这篇文章主要为大家展示了“微信小程序中如何实现圆形菜单”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“微信小程序中如何实现圆形菜单”这篇文章吧。建行APP首页有个圆形菜单.仿了个玩具出来.功能介绍: 1.一个圆形背景.六个item菜单.中间是微信用户的头像; 2.触摸滚动.速度较小时,随手指滚动,手指抬起,滚动停止;速度较大时,随手指滚动,手指抬起,还会自动滚动一段时间; 上一张真机截图: 上代码: 1.index.jsvar app = getApp() Page({ data: { userInfo: {}, menuList: {},//菜单集合 animationData: {}, startPoint: {},//触摸开始 dotPoint: {},//圆点坐标 startAngle: 0,//开始角度 tempAngle: 0,//移动角度 downTime: 0,//按下时间 upTime: 0,//抬起时间 // isRunning: false,//正在滚动 }, onLoad: function () { var that = this //调用应用实例的方法获取全局数据 app.getUserInfo(function (userInfo) { //更新数据 that.setData({ userInfo: userInfo, }) }) wx.getSystemInfo({ success: function (res) { var windowWidth = res.windowWidth * 0.5; that.setData({ //圆点坐标,x为屏幕一半,y为半径与margin-top之和,px //后面获取的触摸坐标是px,所以这里直接用px. dotPoint: { clientX: windowWidth, clientY: 250 } }) } }) }, onReady: function (e) { var that = this; app.menuConfig = { menu: [ { 'index': 0, 'menu': '我的账户', 'src': '../images/account.png' }, { 'index': 1, 'menu': '信用卡', 'src': '../images/card.png' }, { 'index': 2, 'menu': '投资理财', 'src': '../images/investment.png' }, { 'index': 3, 'menu': '现金贷款', 'src': '../images/loan.png' }, { 'index': 4, 'menu': '特色服务', 'src': '../images/service.png' }, { 'index': 5, 'menu': '转账汇款', 'src': '../images/transfer.png' } ] } // 绘制转盘 var menuConfig = app.menuConfig.menu, len = menuConfig.length, menuList = [], degNum = 360 / len // 文字旋转 turn 值 for (var i = 0; i < len; i++) { menuList.push({ deg: i * degNum, menu: menuConfig[i].menu, src: menuConfig[i].src }); console.log("menu:" + menuConfig[i].menu) } that.setData({ menuList: menuList }); }, // 菜单拖动的三个方法 buttonStart: function (e) { this.setData({ startPoint: e.touches[0] }) var x = this.data.startPoint.clientX - this.data.dotPoint.clientX; var y = this.data.startPoint.clientY - this.data.dotPoint.clientY; var startAngle = Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI; this.setData({ startAngle: startAngle }) }, buttonMove: function (e) { //获取滑动时的时间 var downTime = Date.now(); this.setData({ downTime: downTime }) var that = this; var endPoint = e.touches[e.touches.length - 1] //根据触摸位置计算角度 var x = endPoint.clientX - this.data.dotPoint.clientX; var y = endPoint.clientY - this.data.dotPoint.clientY; var moveAngle = Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI var quadrant = 1; if (x >= 0) { quadrant = y >= 0 ? 4 : 1; } else { quadrant = y >= 0 ? 3 : 2; } var tempAngle = 0; // 如果是一、四象限,则直接end角度-start角度,角度值都是正值 if (quadrant == 1 || quadrant == 4) { tempAngle += moveAngle - this.data.startAngle; } else // 二、三象限,色角度值是负值 { tempAngle += this.data.startAngle - moveAngle; } var menuConfig = app.menuConfig.menu; var menuList = []; for (var i = 0; i < this.data.menuList.length; i++) { menuList.push({ deg: this.data.menuList[i].deg + tempAngle, menu: menuConfig[i].menu, src: menuConfig[i].src }); } this.setData({ menuList: menuList }) //重置开始角度 this.setData({ startPoint: e.touches[e.touches.length - 1] }) var endX = this.data.startPoint.clientX - this.data.dotPoint.clientX; var endY = this.data.startPoint.clientY - this.data.dotPoint.clientY; var startAngle = Math.asin(endY / Math.hypot(endX, endY)) * 180 / Math.PI; this.setData({ startAngle: startAngle, tempAngle: tempAngle }) }, buttonEnd: function (e) { // 计算,每秒移动的角度 var that = this; var upTime = Date.now(); var angleSpeed = this.data.tempAngle * 1000 / (upTime - this.data.downTime); if (Math.abs(angleSpeed) < 100) { //速度小于100时,停止滚动 return } else { //速度大于100时,自动滚动 if (angleSpeed > 0) { if (angleSpeed > 500) angleSpeed = 500 var animationRun = wx.createAnimation({ duration: 2000, //ease-out结束时减速 timingFunction: 'ease-out' }) that.animationRun = animationRun animationRun.rotate(angleSpeed).step() that.setData({ animationData: animationRun.export(), }) } else { if (angleSpeed < -500) angleSpeed = -500 angleSpeed = Math.abs(angleSpeed); var animationRun = wx.createAnimation({ duration: 2000, // ease-out结束时减速 timingFunction: 'ease-out' }) that.animationRun = animationRun animationRun.rotate(-angleSpeed).step() that.setData({ animationData: animationRun.export(), }) } } } })2.index.wxml<view class="circle-out"> <view class="circle-in"> <image class="userinfo-avatar" src="{{userInfo.avatarUrl}}"></image> <view class="menu-list" catchtouchmove="buttonMove" catchtouchstart="buttonStart" catchtouchend="buttonEnd"> <view class="menu-item" wx:for="{{menuList}}" wx:key="unique" animation="{{animationData}}"> <view class="menu-circle-item" style="-webkit-transform: rotate({{item.deg}}deg);" data-menu="{{item.menu}}"> <image class="image-style" src="{{item.src}}"></image> </view> <view class="menu-circle-text-item" style="-webkit-transform: rotate({{item.deg}}deg);"> <text class="text-style">{{item.menu}}</text> </view> </view> </view> </view></view>3.index.wxsspage { background-image: url('http://ac-ejx0nsfy.clouddn.com/ac767407f474e1c3970a.jpg'); background-attachment: fixed; background-repeat: no-repeat; background-size: cover; }.circle-out { margin: 75px auto; position: relative; width: 350px; height: 350px; border-radius: 50%; background-color: #415cab; }.userinfo-avatar { width: 70px; height: 70px; border-radius: 50%; position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; }/**子控件的透明度等于父控件透明度*子控件透明度,父控件的opacity设置后, 所以子控件opacity设置为1依然无效,必须分离开 */.circle-in { position: absolute; width: 330px; height: 330px; border-radius: 50%; top: 0; bottom: 0; left: 0; right: 0; margin: auto; background-color: #fff; }/**菜单*/.menu-list { position: absolute; left: 0; top: 0; width: inherit; height: inherit; }.menu-item { position: absolute; left: 0; top: 0; width: 100%; height: 100%; font-weight: 500; }.menu-circle-item { -webkit-transform-origin: 50% 150px; transform-origin: 50% 150px; margin: 0 auto; margin-top: 15px; position: relative; height: 50px; width: 50px; background-color: #77c2fc; text-align: center; border-radius: 50%; }.image-style { height: 25px; width: 25px; color: #f00; margin: 12.5px auto; }.text-style { margin: 5px auto; font-size: 15px; }/***/.menu-circle-text-item { -webkit-transform-origin: 50% 100px; transform-origin: 50% 100px; margin: 0 auto; position: relative; height: 25px; width: auto; text-align: center; }js注释补充: 获取手指抬起时的角速度 1.获取角度.借图说话. Math.sqrt( x * x + y * y )是斜边长,乘以 sin a 就是 y 的长度; 获取a的角度:Math.asin(y / Math.hypot(x, y) ; [ hypot是x * x + y * y ] 2.根据角度差计算角速度 var angleSpeed = this.data.tempAngle * 1000 / (upTime - this.data.downTime);3.当角速度小于100的时候触摸滑动停止,不自动滚动;大于100时,自动滚动.我这里用动画,有个问题:很难把握动画持续时间和速度的关系.总感觉不够流畅.我表示不能忍. 4.分象限的问题.看看代码就知道了.主要是根据up时的触摸点相对于圆点的X轴差值来计算.大于0就是一四象限.小于0就是二三象限.以上是“微信小程序中如何实现圆形菜单”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注辰讯云资讯频道!...
这篇文章主要介绍php中正则表达式的定界符是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!正则表达式的定界符:正则表达式就是用来声明正则表达式边界的符号,正则表达式是字符串类型。所以在定义正则表达式时先定义字符串类型;正则表达式常用的边界符"/ ,实际上字母和非数字字符以及“V之外的字符都可以做正则表达式的边界符使用。注意:一个完整的正则表达式准确的说有两个边界符。第一个是字符串类型的引号边界符第二个是正则表达式的边界符例如:$parrten = '';具体我们以代码演示为例:<?php/******正则表达式的定界符*****/$pattern = '/ /'; $str = '';//进行匹配preg_match( $pattern, $str);?>代码解析:首先我们先定义一个变量$pattern,对于我们在代码中写的字符串如果不和函数一起使用,那他就是一个普通的字符串,因此我们需要进行匹配,我们所需要的匹配函数是(preg_match),定义完之后,我们还需要给定两个参数,我们需要和str进行匹配,然后我们进行运行,如果preg_match进行报错,那么就说明我们刚刚定义的不能当定界符;我们运行结果发现,没有任何报错(运行结果如下),也就是说,我们刚刚定义的是可以当做正则表达式的定界符的;运行结果:在比如说,我们把刚刚定义的$pattern = '/ /'转换为返斜线\\,是不是还会运行成功,我们在运行结果会发现,会报错,也就是说,我们用反斜线不能当正则表达式的定界符;因为它本身就是有意义的,他是我们常说的转义字符,(分隔符不能是字母数字或反斜杠)<?php /******正则表达式的定界符*****/ //$pattern = '/ /';$pattern = '\ \';$str = ''; //进行匹配 preg_match( $pattern, $str); ?>运行结果:以上是“php中正则表达式的定界符是什么”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注辰讯云资讯频道!...
这篇文章将为大家详细讲解有关微信小程序中底部导航栏目的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。微信小程序 底部导航栏目开发我们先来看个效果图这里,我们添加了三个导航图标,因为我们有三个页面,微信小程序最多能加5个。那他们是怎么出现怎么着色的呢?两步就搞定!1. 图标准备阿里图标库 http://www.iconfont.cn/collections/show/29我们进入该网站,鼠标滑到一个喜欢的图标上面 点击下方的 下载按钮在弹出框中 选择了 俩个不同颜色的 图标 选择64px大小即可,我选择的是png 然后下载下来 起上别名将上述起好名字的图标 保存到 小程序 项目目录中 新创建的 images 文件夹中,准备工作就做好了2. 更改配置文件我们找到项目根目录中的配置文件 app.json 加入如下配置信息"tabBar": { "color": "#a9b7b7", "selectedColor": "#11cd6e", "borderStyle":"white", "list": [{ "selectedIconPath": "images/111.png", "iconPath": "images/11.png", "pagePath": "pages/index/index", "text": "首页" }, { "selectedIconPath": "images/221.png", "iconPath": "images/22.png", "pagePath": "pages/logs/logs", "text": "日志" }, { "selectedIconPath": "images/331.png", "iconPath": "images/33.png", "pagePath": "pages/test/test", "text": "开心测试" }] },解释一下 对应的属性信息tabBar 指底部的 导航配置属性color 未选择时 底部导航文字的颜色selectedColor 选择时 底部导航文字的颜色borderStyle 底部导航边框的样色(注意 这里如果没有写入样式 会导致 导航框上边框会出现默认的浅灰色线条)list 导航配置数组selectedIconPath 选中时 图标路径iconPath 未选择时 图标路径pagePath 页面访问地址text 导航图标下方文字关于“微信小程序中底部导航栏目的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。...
小编给大家分享一下IIS中实现备份和还原的方法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!备份 IIS 配置若要备份 IIS 配置,请按照下列步骤操作: 1. 在本地计算机上的 IIS 管理单元中,单击 Internet 信息服务下面的计算机图标。 2. 单击操作并选择备份/还原配置。 3. 单击创建备份,选择备份文件的名称,然后单击确定。备注:默认备份位置是 %SystemRoot%\system32\inetsrv\MetaBack 文件夹。如果要将备份文件保存到其他位置,可以将文件从该默认位置复制到另一个位置。在默认位置保存一个副本以便方便地恢复。请注意,在默认情况下,C:\Winnt 是 Microsoft Windows 2000 中的 %SystemRoot% 文件夹。 4. 单击关闭。备注: 此备份方法提供只还原 IIS 设置(而不是内容文件)的方法。如果重新安装操作系统,则此备份方法无效。无法使用备份文件在正运行 Windows 2000 的其他计算机上还原 IIS 配置。 back to the top 还原 IIS 配置根据您是否删除和重新安装了 IIS,还原配置的步骤也将有所不同。备注:如果您要还原 IIS 配置并且只有元数据库文件的较旧的副本存在或者不存在元数据库文件的任何副本,请参见下面的 Microsoft 知识库文章: 234429 How to Manually Restore the Metabase When No Backup Exists(在没有任何备份存在时如何手动还原元数据库) 不进行 IIS 重新安装就还原 IIS 配置若要在尚未删除和重新安装 IIS 的情况下还原 IIS 配置,请按照下列步骤操作: 1. 在本地计算机上的 IIS 管理单元中,单击 Internet 信息服务下面的计算机图标。 2. 单击操作并选择备份/还原配置。 3. 在配置备份名对话框中,选择一个备份文件并单击还原。在系统询问您是否要还原配置设置时,单击是。 back to the top 在 IIS 重新安装后还原 IIS 配置若要在已删除和重新安装 IIS 后还原 IIS 配置,请按照下列步骤操作:1. 在本地计算机上的 IIS 管理单元中,单击 Internet 信息服务下面的计算机图标。 2. 单击操作并选择备份/还原配置。 3. 在配置备份名对话框中,选择已创建的备份文件,然后单击还原。尽管错误消息指示还原已失败,仍还原了一部分的备份配置。 4. 在命令提示符下,键入以下命令: cscript.exeX:\InetPub\AdminScripts\Adsutil.vbs enum w3svc其中,X 是安装 IIS 的驱动器的驱动器号。从列出的设置中,找到 WamUserName 和关联的 WAMUserPass 值。备注: 若要使用 Adsutil.vbs 实用程序,必须安装 Windows Script Host。 5. 单击开始,指向设置,单击控制面板,双击管理工具,然后双击计算机管理。单击本地用户管理器并单击用户。 6. 双击 IWAM_computername 用户帐户。键入您从前一步骤检索的 WAMUserPass 值,然后单击确定。 7. 在配置备份名对话框中,选择已创建的备份文件,然后单击还原。这可以完全还原您的配置。备注: 如果您更改进程外应用程序的标识,则这些应用程序回复为以前的 IWAM 用户名。 以上是“IIS中实现备份和还原的方法”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注辰讯云资讯频道!...
免备案云服务器是什么?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。现在不用备案的云服务器,有中国港澳台、美国及海外其他国家地区的主机。不过,国内服务器(除港澳台外)都是需要经过备案,才能解析域名,用于搭建网站使用。使用免备案云服务器,无需经历备案的过程,节省时间,利于企业快速上线。免备案云服务器,比较适合外贸网站用户。国内网站在海外访问时,会有局限性。而海外云主机访问没有限制,且避免了南北互通问题,也就是所谓电信、联通等的差异。用户可以自由访问,无需担心不同网络间访问受限。美国云服务器美国云服务中心拥有高级的安保措施,数据安全性和服务稳定性是世界一流标准。ip资源丰富,技术发展成熟,且价格也相对便宜。在全球的访问速度都比较快,即买即用,高速连接,低延迟,集群化管理,分布式存储设计,深受全球站长信赖。香港云服务器美国云服务器虽然好,但香港地区具有地域优势,速度快,对于国内大陆用户来说,线路铺设距离短,故障率低,排障工作也容易进行。同时它也采用了国际带宽,更主要的是也可以享受海外云服务器同等待遇,无需备案,即开即用。香港与世界其他国家的互联网是畅通的,没有防火墙的限制,同时香港拥有互联网在亚洲的几大专线接口之一。所以访问的速度是很快的,这也是很多外贸公司以及客户主要是国外的网站选择香港云服务器的原因。对于国内人士来讲,香港云服务器更加符合使用习惯,速度快,可免除语音障碍。沟通交流起来也比较方便,有什么不清楚的直接联系即可。看完上述内容,你们掌握免备案云服务器是什么的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注辰讯云资讯频道,感谢各位的阅读!...
这篇文章主要介绍了微信小程序中用户数据解密的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。微信小程序 用户数据解密官方指引图:引导图一步一步操作1、获取codeonLoad: function (options) { // 页面初始化 options为页面跳转所带来的参数 let that = this wx.login({ success: function (res) { // success let code = res.code that.setData({ code: code }) wx.getUserInfo({ success: function (res) { // success that.setData({ userInfo: res.userInfo }) that.setData({ iv: res.iv }) that.setData({ encryptedData: res.encryptedData }) that.get3rdSession() } }) } }) }2、发送code到第三方服务器,获取3rd_sessionget3rdSession:function(){ let that = this wx.request({ url: 'https://localhost:8443/get3rdSession', data: { code: this.data.code }, method: 'GET', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT // header: {}, // 设置请求的 header success: function (res) { // success var sessionId = res.data.session; that.setData({ sessionId: sessionId }) wx.setStorageSync('sessionId', sessionId) that.decodeUserInfo() } }) }3、在第三方服务器上发送appid、appsecret、code到微信服务器换取session_key和openid这里使用JFinal搭建的服务器Redis配置public void configPlugin(Plugins me) { //用于缓存userinfo模块的redis服务 RedisPlugin userInfoRedis = new RedisPlugin("userInfo","localhost"); me.add(userInfoRedis); }获取第三方sessionpublic void get3rdSession() { //获取名为userInfo的Redis Cache对象 Cache userInfoCache = Redis.use("userInfo"); String sessionId = ""; JSONObject json = new JSONObject(); String code = getPara("code"); String url = "https://api.weixin.qq.com/sns/jscode2session?appid=wx7560b8008e2c445d&secret=f1af3312b7038513fd17dd9cbc3b357c&js_code=" + code + "&grant_type=authorization_code"; //执行命令生成3rd_session String session = ExecLinuxCMDUtil.instance.exec("cat /dev/urandom |od -x | tr -d ' '| head -n 1").toString(); json.put("session", session); //创建默认的httpClient实例 CloseableHttpClient httpClient = getHttpClient(); try { //用get方法发送http请求 HttpGet get = new HttpGet(url); System.out.println("执行get请求:...." + get.getURI()); CloseableHttpResponse httpResponse = null; //发送get请求 httpResponse = httpClient.execute(get); try { //response实体 HttpEntity entity = httpResponse.getEntity(); if (null != entity) { String result = EntityUtils.toString(entity); System.out.println(result); JSONObject resultJson = JSONObject.fromObject(result); String session_key = resultJson.getString("session_key"); String openid = resultJson.getString("openid"); //session存储 userInfoCache.set(session,session_key+","+openid); } } finally { httpResponse.close(); } } catch (Exception e) { e.printStackTrace(); } finally { try { closeHttpClient(httpClient); } catch (IOException e) { e.printStackTrace(); } } renderJson(json); }private CloseableHttpClient getHttpClient() { return HttpClients.createDefault(); }private void closeHttpClient(CloseableHttpClient client) throws IOException { if (client != null) { client.close(); } }ExecLinuxCMDUtil.Javaimport java.io.InputStreamReader;import java.io.LineNumberReader;/** * java在linux环境下执行linux命令,然后返回命令返回值。 * Created by LJaer on 16/12/22. */public class ExecLinuxCMDUtil { public static final ExecLinuxCMDUtil instance = new ExecLinuxCMDUtil(); public static Object exec(String cmd) { try { String[] cmdA = { "/bin/sh", "-c", cmd }; Process process = Runtime.getRuntime().exec(cmdA); LineNumberReader br = new LineNumberReader(new InputStreamReader( process.getInputStream())); StringBuffer sb = new StringBuffer(); String line; while ((line = br.readLine()) != null) { System.out.println(line); sb.append(line).append("\n"); } return sb.toString(); } catch (Exception e) { e.printStackTrace(); } return null; } }4、解密用户数据decodeUserInfo:function(){ let that = this wx.request({ url: 'https://localhost:8443/decodeUserInfo', data: { encryptedData: that.data.encryptedData, iv: that.data.iv, session: wx.getStorageSync('sessionId') }, method: 'GET', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT // header: {}, // 设置请求的 header success: function (res) { // success console.log(res) } }) }console输出结果:后端解密代码/** * 解密用户敏感数据 */public void decodeUserInfo(){ String encryptedData = getPara("encryptedData"); String iv = getPara("iv"); String session = getPara("session"); //从缓存中获取session_key //获取名称为userInfo的Redis Cache对象 Cache userInfoRedis = Redis.use("userInfo"); Object wxSessionObj = userInfoRedis.get(session); if(null==wxSessionObj){ renderNull(); } String wxSessionStr = (String)wxSessionObj; String session_key = wxSessionStr.split(",")[0]; try { byte[] resultByte = AESUtil.instance.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(session_key), Base64.decodeBase64(iv)); if(null != resultByte && resultByte.length > 0){ String userInfo = new String(resultByte, "UTF-8"); System.out.println(userInfo); JSONObject json = JSONObject.fromObject(userInfo); //将字符串{“id”:1} renderJson(json); } } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }AESUtil.javaimport org.bouncycastle.jce.provider.BouncyCastleProvider;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.security.*;public class AESUtil { public static final AESUtil instance = new AESUtil(); public static boolean initialized = false; /** * AES解密 * @param content 密文 * @return * @throws InvalidAlgorithmParameterException * @throws NoSuchProviderException */ public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException { initialize(); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); Key sKeySpec = new SecretKeySpec(keyByte, "AES"); cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化 byte[] result = cipher.doFinal(content); return result; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static void initialize(){ if (initialized) return; Security.addProvider(new BouncyCastleProvider()); initialized = true; } //生成iv public static AlgorithmParameters generateIV(byte[] iv) throws Exception{ AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); params.init(new IvParameterSpec(iv)); return params; } }感谢你能够认真阅读完这篇文章,希望小编分享的“微信小程序中用户数据解密的示例分析”这篇文章对大家有帮助,同时也希望大家多多支持辰讯云,关注辰讯云资讯频道,更多相关知识等着你来学习!...
小编给大家分享一下微信小程序中如何动态显示项目倒计时效果,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1、一般我们说的显示秒杀都是指的单条数据,循环我没做。效果:2、wxml代码:<p><block wx:if="{{total_micro_second<=0}}">剩余时间:已经截止</block><block wx:if="{{clock!='已经截止'}}">剩余时间:{{clock}}</block></p>3、.js文件代码:function countdown(that) { var EndTime = that.data.end_time || []; var NowTime = new Date().getTime(); var total_micro_second = EndTime - NowTime || []; console.log('剩余时间:' + total_micro_second); // 渲染倒计时时钟 that.setData({ clock: dateformat(total_micro_second) }); if (total_micro_second <= 0) { that.setData({ clock: "已经截止" }); //return; } setTimeout(function () { total_micro_second -= 1000; countdown(that); } , 1000) } // 时间格式化输出,如11:03 25:19 每1s都会调用一次 function dateformat(micro_second) { // 总秒数 var second = Math.floor(micro_second / 1000); // 天数 var day = Math.floor(second/3600/24); // 小时 var hr = Math.floor(second/3600%24); // 分钟 var min = Math.floor(second/60%60); // 秒 var sec = Math.floor(second%60); return day + "天" + hr + "小时" + min + "分钟" + sec+"秒"; } Page({ /** * 页面的初始数据 */ data: { id:'', result:[], end_time:'', clock:'' },/** * 生命周期函数--监听页面加载 */ onLoad: function (options) { var that = this; wx.request({ url: 'https://m.******.com/index.php/Home/Xiaoxxf/activity_detail?a_id='+options.id,//不含富文本html data: {}, method: 'GET', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT header: { 'Content-Type': 'application/json' }, success: function (res) { that.setData({ common: res.data, //一维数组,全部数据 end_time: res.data.end_time //项目截止时间,时间戳,单位毫秒 }) console.log(that.data.common); console.log('结束时间:' + that.data.end_time); }, fail: function (res) { }, complete: function (res) { }, }), //调用上面定义的递归函数,一秒一刷新时间 countdown(that); },以上是“微信小程序中如何动态显示项目倒计时效果”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注辰讯云资讯频道!...
这篇文章给大家分享的是有关微信小程序中如何实现数量加减功能的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。我们在购买宝贝的时候,购物的数量,经常是我们需要使用的,如下所示:在宝贝详情页里:在购物车里:现在就为大家介绍这个小组件,在小程序中,该如何去写下图为本项目的图:wxml:<!-- 主容器 --> <view class="stepper"> <!-- 减号 --> <text class="{{minusStatus}}" bindtap="bindMinus">-</text> <!-- 数值 --> <input type="number" bindchange="bindManual" value="{{num}}" /> <!-- 加号 --> <text class="normal" bindtap="bindPlus">+</text> </view>wxss:/*全局样式*/ page { padding: 20px 0; } /*主容器*/ .stepper { width: 80px; height: 26px; /*给主容器设一个边框*/ border: 1px solid #ccc; border-radius: 3px; margin:0 auto; } /*加号和减号*/ .stepper text { width: 19px; line-height: 26px; text-align: center; float: left; } /*数值*/ .stepper input { width: 40px; height: 26px; float: left; margin: 0 auto; text-align: center; font-size: 12px; /*给中间的input设置左右边框即可*/ border-left: 1px solid #ccc; border-right: 1px solid #ccc; } /*普通样式*/ .stepper .normal{ color: black; } /*禁用样式*/ .stepper .disabled{ color: #ccc; }js:Page({ data: { // input默认是1 num: 1, // 使用data数据对象设置样式名 minusStatus: 'disabled' }, /* 点击减号 */ bindMinus: function() { var num = this.data.num; // 如果大于1时,才可以减 if (num > 1) { num --; } // 只有大于一件的时候,才能normal状态,否则disable状态 var minusStatus = num <= 1 ? 'disabled' : 'normal'; // 将数值与状态写回 this.setData({ num: num, minusStatus: minusStatus }); }, /* 点击加号 */ bindPlus: function() { var num = this.data.num; // 不作过多考虑自增1 num ++; // 只有大于一件的时候,才能normal状态,否则disable状态 var minusStatus = num < 1 ? 'disabled' : 'normal'; // 将数值与状态写回 this.setData({ num: num, minusStatus: minusStatus }); }, /* 输入框事件 */ bindManual: function(e) { var num = e.detail.value; // 将数值与状态写回 this.setData({ num: num }); } })运行结果:感谢各位的阅读!关于“微信小程序中如何实现数量加减功能”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!...
这篇文章给大家分享的是有关微信小程序中自定义模态弹窗的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。首先看看官方提供的模态弹窗,供大家参考,具体内容如下api如下:示例:这样的模态弹窗,充其量只能做个alert,提示一下信息。但是并不能使用它来处理复杂性的弹窗业务,因此写了Michael从新自定义了一个,采用了仿原生的样式写法wxml:<!--button--> <view class="btn" bindtap="powerDrawer" data-statu="open">button</view> <!--mask--> <view class="drawer_screen" bindtap="powerDrawer" data-statu="close" wx:if="{{showModalStatus}}"></view> <!--content--> <!--使用animation属性指定需要执行的动画--> <view animation="{{animationData}}" class="drawer_box" wx:if="{{showModalStatus}}"> <!--drawer content--> <view class="drawer_title">弹窗标题</view> <view class="drawer_content"> <view class="top grid"> <label class="title col-0">标题</label> <input class="input_base input_h40 col-1" name="rName" value="可自行定义内容"></input> </view> <view class="top grid"> <label class="title col-0">标题</label> <input class="input_base input_h40 col-1" name="mobile" value="110"></input> </view> <view class="top grid"> <label class="title col-0">标题</label> <input class="input_base input_h40 col-1" name="phone" value="拒绝伸手党"></input> </view> <view class="top grid"> <label class="title col-0">标题</label> <input class="input_base input_h40 col-1" name="Email" value="仅供学习使用"></input> </view> <view class="top bottom grid"> <label class="title col-0">备注</label> <input class="input_base input_h40 col-1" name="bz"></input> </view> </view> <view class="btn_ok" bindtap="powerDrawer" data-statu="close">确定</view> </view>wxss:/*button*/ .btn { width: 80%; padding: 20rpx 0; border-radius: 10rpx; text-align: center; margin: 40rpx 10%; background: #000; color: #fff; } /*mask*/ .drawer_screen { width: 100%; height: 100%; position: fixed; top: 0; left: 0; z-index: 1000; background: #000; opacity: 0.5; overflow: hidden; } /*content*/ .drawer_box { width: 650rpx; overflow: hidden; position: fixed; top: 50%; left: 0; z-index: 1001; background: #FAFAFA; margin: -150px 50rpx 0 50rpx; border-radius: 3px; } .drawer_title{ padding:15px; font: 20px "microsoft yahei"; text-align: center; } .drawer_content { height: 210px; overflow-y: scroll; /*超出父盒子高度可滚动*/ } .btn_ok{ padding: 10px; font: 20px "microsoft yahei"; text-align: center; border-top: 1px solid #E8E8EA; color: #3CC51F; } .top{ padding-top:8px; } .bottom { padding-bottom:8px; } .title { height: 30px; line-height: 30px; width: 160rpx; text-align: center; display: inline-block; font: 300 28rpx/30px "microsoft yahei"; } .input_base { border: 2rpx solid #ccc; padding-left: 10rpx; margin-right: 50rpx; } .input_h40{ height: 30px; line-height: 30px; } .input_h70{ height: 60px; } .input_view{ font: 12px "microsoft yahei"; background: #fff; color:#000; line-height: 30px; } input { font: 12px "microsoft yahei"; background: #fff; color:#000 ; } radio{ margin-right: 20px; } .grid { display: -webkit-box; display: box; } .col-0 {-webkit-box-flex:0;box-flex:0;} .col-1 {-webkit-box-flex:1;box-flex:1;} .fl { float: left;} .fr { float: right;}js:Page({ data: { showModalStatus: false }, powerDrawer: function (e) { var currentStatu = e.currentTarget.dataset.statu; this.util(currentStatu) }, util: function(currentStatu){ /* 动画部分 */ // 第1步:创建动画实例 var animation = wx.createAnimation({ duration: 200, //动画时长 timingFunction: "linear", //线性 delay: 0 //0则不延迟 }); // 第2步:这个动画实例赋给当前的动画实例 this.animation = animation; // 第3步:执行第一组动画 animation.opacity(0).rotateX(-100).step(); // 第4步:导出动画对象赋给数据对象储存 this.setData({ animationData: animation.export() }) // 第5步:设置定时器到指定时候后,执行第二组动画 setTimeout(function () { // 执行第二组动画 animation.opacity(1).rotateX(0).step(); // 给数据对象储存的第一组动画,更替为执行完第二组动画的动画对象 this.setData({ animationData: animation }) //关闭 if (currentStatu == "close") { this.setData( { showModalStatus: false } ); } }.bind(this), 200) // 显示 if (currentStatu == "open") { this.setData( { showModalStatus: true } ); } } })运行:感谢各位的阅读!关于“微信小程序中自定义模态弹窗的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!...
小编给大家分享一下怎么在Kubernetes上运行高可用的WordPress和MySQL,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!在Kubernetes上运行高可用的WordPress和MySQLWordPress是用于编辑和发布Web内容的主流平台。在本教程中,我将逐步介绍如何使用Kubernetes来构建高可用性(HA)WordPress部署。WordPress由两个主要组件组成:WordPress PHP服务器和用于存储用户信息、帖子和网站数据的数据库。我们需要让整个应用程序中这两个组件在高可用的同时都具备容错能力。在硬件和地址发生变化的时候,运行高可用服务可能会很困难:非常难维护。借助Kubernetes以及其强大的网络组件,我们可以部署高可用的WordPress站点和MySQL数据库,而无需(几乎无需)输入单个IP地址。在本教程中,我将向你展示如何在Kubernetes中创建存储类、服务、配置映射和集合,如何运行高可用MySQL,以及如何将高可用WordPress集群挂载到数据库服务上。如果你还没有Kubernetes集群,你可以在Amazon、Google或者Azure上轻松找到并且启动它们,或者在任意的服务器上使用Rancher Kubernetes Engine (RKE)架构概述现在我来简要介绍一下我们将要使用的技术及其功能:WordPress应用程序文件的存储:具有GCE持久性磁盘备份的NFS存储数据库集群:带有用于奇偶校验的xtrabackup的MySQL应用程序级别:挂载到NFS存储的WordPress DockerHub映像负载均衡和网络:基于Kubernetes的负载均衡器和服务网络该体系架构如下所示:在K8s中创建存储类、服务和配置映射在Kubernetes中,状态集提供了一种定义pod初始化顺序的方法。我们将使用一个有状态的MySQL集合,因为它能确保我们的数据节点有足够的时间在启动时复制先前pods中的记录。我们配置这个状态集的方式可以让MySQL主机在其他附属机器之前先启动,因此当我们扩展时,可以直接从主机将克隆发送到附属机器上。首先,我们需要创建一个持久卷存储类和配置映射,以根据需要应用主从配置。我们使用持久卷,避免数据库中的数据受限于集群中任何特定的pods。这种方式可以避免数据库在MySQL主机pod丢失的情况下丢失数据,当主机pod丢失时,它可以重新连接到带xtrabackup的附属机器,并将数据从附属机器拷贝到主机中。MySQL的复制负责主机-附属的复制,而xtrabackup负责附属-主机的复制。要动态分配持久卷,我们使用GCE持久磁盘创建存储类。不过,Kubernetes提供了各种持久性卷的存储方案:# storage-class.yamlkind: StorageClassapiVersion: storage.k8s.io/v1metadata: name: slowprovisioner: kubernetes.io/gce-pdparameters: type: pd-standard zone: us-central1-a创建类,并且使用指令:$ kubectl create -f storage-class.yaml部署它。接下来,我们将创建configmap,它指定了一些在MySQL配置文件中设置的变量。这些不同的配置由pod本身选择有关,但它们也为我们提供了一种便捷的方式来管理潜在的配置变量。创建名为mysql-configmap.yaml的YAML文件来处理配置,如下:# mysql-configmap.yamlapiVersion: v1kind: ConfigMapmetadata: name: mysql labels: app: mysqldata: master.cnf: | # Apply this config only on the master. [mysqld] log-bin skip-host-cache skip-name-resolve slave.cnf: | # Apply this config only on slaves. [mysqld] skip-host-cache skip-name-resolve创建configmap并使用指令:$ kubectl create -f mysql-configmap.yaml来部署它。接下来我们要设置服务以便MySQL pods可以互相通信,并且我们的WordPress pod可以使用mysql-services.yaml与MySQL通信。这也为MySQL服务启动了服务负载均衡器。# mysql-services.yaml# Headless service for stable DNS entries of StatefulSet members.apiVersion: v1kind: Servicemetadata: name: mysql labels: app: mysqlspec: ports: - name: mysql port: 3306 clusterIP: None selector: app: mysql通过此服务声明,我们就为实现一个多写入、多读取的MySQL实例集群奠定了基础。这种配置是必要的,每个WordPress实例都可能写入数据库,所以每个节点都必须准备好读写。执行命令 $ kubectl create -f mysql-services.yaml来创建上述的服务。到这为止,我们创建了卷声明存储类,它将持久磁盘交给所有请求它们的容器,我们配置了configmap,在MySQL配置文件中设置了一些变量,并且我们配置了一个网络层服务,负责对MySQL服务器请求的负载均衡。上面说的这些只是准备有状态集的框架, MySQL服务器实际在哪里运行,我们接下来将继续探讨。配置有状态集的MySQL本节中,我们将编写一个YAML配置文件应用于使用了状态集的MySQL实例。我们先定义我们的状态集:1, 创建三个pods并将它们注册到MySQL服务上。2, 按照下列模版定义每个pod:♢ 为主机MySQL服务器创建初始化容器,命名为init-mysql.♢ 给这个容器使用mysql:5.7镜像♢ 运行一个bash脚本来启动xtrabackup♢ 为配置文件和configmap挂载两个新卷3, 为主机MySQL服务器创建初始化容器,命名为clone-mysql.♢ 为该容器使用Google Cloud Registry的xtrabackup:1.0镜像♢ 运行bash脚本来克隆上一个同级的现有xtrabackups♢ 为数据和配置文件挂在两个新卷♢ 该容器有效地托管克隆的数据,便于新的附属容器可以获取它4, 为附属MySQL服务器创建基本容器♢ 创建一个MySQL附属容器,配置它连接到MySQL主机♢ 创建附属xtrabackup容器,配置它连接到xtrabackup主机5, 创建一个卷声明模板来描述每个卷,每个卷是一个10GB的持久磁盘下面的配置文件定义了MySQL集群的主节点和附属节点的行为,提供了运行附属客户端的bash配置,并确保在克隆之前主节点能够正常运行。附属节点和主节点分别获得他们自己的10GB卷,这是他们在我们之前定义的持久卷存储类中请求的。apiVersion: apps/v1beta1kind: StatefulSetmetadata: name: mysqlspec: selector: matchLabels: app: mysql serviceName: mysql replicas: 3 template: metadata: labels: app: mysql spec: initContainers: - name: init-mysql image: mysql:5.7 command: - bash - "-c" - | set -ex # Generate mysql server-id from pod ordinal index. [[ `hostname` =~ -([0-9]+)$ ]] || exit 1 ordinal=${BASH_REMATCH[1]} echo [mysqld] > /mnt/conf.d/server-id.cnf # Add an offset to avoid reserved server-id=0 value. echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf # Copy appropriate conf.d files from config-map to emptyDir. if [[ $ordinal -eq 0 ]]; then cp /mnt/config-map/master.cnf /mnt/conf.d/ else cp /mnt/config-map/slave.cnf /mnt/conf.d/ fi volumeMounts: - name: conf mountPath: /mnt/conf.d - name: config-map mountPath: /mnt/config-map - name: clone-mysql image: gcr.io/google-samples/xtrabackup:1.0 command: - bash - "-c" - | set -ex # Skip the clone if data already exists. [[ -d /var/lib/mysql/mysql ]] && exit 0 # Skip the clone on master (ordinal index 0). [[ `hostname` =~ -([0-9]+)$ ]] || exit 1 ordinal=${BASH_REMATCH[1]} [[ $ordinal -eq 0 ]] && exit 0 # Clone data from previous peer. ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql # Prepare the backup. xtrabackup --prepare --target-dir=/var/lib/mysql volumeMounts: - name: data mountPath: /var/lib/mysql subPath: mysql - name: conf mountPath: /etc/mysql/conf.d containers: - name: mysql image: mysql:5.7 env: - name: MYSQL_ALLOW_EMPTY_PASSWORD value: "1" ports: - name: mysql containerPort: 3306 volumeMounts: - name: data mountPath: /var/lib/mysql subPath: mysql - name: conf mountPath: /etc/mysql/conf.d resources: requests: cpu: 500m memory: 1Gi livenessProbe: exec: command: ["mysqladmin", "ping"] initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 readinessProbe: exec: # Check we can execute queries over TCP (skip-networking is off). command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"] initialDelaySeconds: 5 periodSeconds: 2 timeoutSeconds: 1 - name: xtrabackup image: gcr.io/google-samples/xtrabackup:1.0 ports: - name: xtrabackup containerPort: 3307 command: - bash - "-c" - | set -ex cd /var/lib/mysql # Determine binlog position of cloned data, if any. if [[ -f xtrabackup_slave_info ]]; then # XtraBackup already generated a partial "CHANGE MASTER TO" query # because we're cloning from an existing slave. mv xtrabackup_slave_info change_master_to.sql.in # Ignore xtrabackup_binlog_info in this case (it's useless). rm -f xtrabackup_binlog_info elif [[ -f xtrabackup_binlog_info ]]; then # We're cloning directly from master. Parse binlog position. [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1 rm xtrabackup_binlog_info echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\ MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in fi # Check if we need to complete a clone by starting replication. if [[ -f change_master_to.sql.in ]]; then echo "Waiting for mysqld to be ready (accepting connections)" until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done echo "Initializing replication from clone position" # In case of container restart, attempt this at-most-once. mv change_master_to.sql.in change_master_to.sql.orig mysql -h 127.0.0.1 <<EOF $(<change_master_to.sql.orig), MASTER_HOST='mysql-0.mysql', MASTER_USER='root', MASTER_PASSWORD='', MASTER_CONNECT_RETRY=10; START SLAVE; EOF fi # Start a server to send backups when requested by peers. exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \ "xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root" volumeMounts: - name: data mountPath: /var/lib/mysql subPath: mysql - name: conf mountPath: /etc/mysql/conf.d resources: requests: cpu: 100m memory: 100Mi volumes: - name: conf emptyDir: {} - name: config-map configMap: name: mysql volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi将该文件存为mysql-statefulset.yaml,输入kubectl="" create="" -f="" mysql-statefulset.yaml并让kubernetes部署你的数据库。现在当你调用$="" kubectl="" get="" pods,你应该看到3个pods启动或者准备好,其中每个pod上都有两个容器。主节点pod表示为mysql-0,而附属的pods为mysql-1和mysql-2.让pods执行几分钟来确保xtrabackup服务在pod之间正确同步,然后进行wordpress的部署。您可以检查单个容器的日志来确认没有错误消息抛出。 查看日志的命令为$="" logs="" <container_name="">主节点xtrabackup容器应显示来自附属的两个连接,并且日志中不应该出现任何错误。部署高可用的WordPress整个过程的最后一步是将我们的WordPress pods部署到集群上。为此我们希望为WordPress的服务和部署进行定义。为了让WordPress实现高可用,我们希望每个容器运行时都是完全可替换的,这意味着我们可以终止一个,启动另一个而不需要对数据或服务可用性进行修改。我们也希望能够容忍至少一个容器的失误,有一个冗余的容器负责处理slack。WordPress将重要的站点相关数据存储在应用程序目录/var/www/html中。对于要为同一站点提供服务的两个WordPress实例,该文件夹必须包含相同的数据。当运行高可用WordPress时,我们需要在实例之间共享/var/www/html文件夹,因此我们定义一个NGS服务作为这些卷的挂载点。下面是设置NFS服务的配置,我提供了纯英文的版本:使用指令$ kubectl create -f nfs.yaml部署NFS服务。现在,我们需要运行$ kubectl describe services nfs-server获得IP地址,这在后面会用到。注意:将来,我们可以使用服务名称讲这些绑定在一起,但现在你需要对IP地址进行硬编码。# wordpress.yamlapiVersion: v1kind: Servicemetadata: name: wordpress labels: app: wordpressspec: ports: - port: 80 selector: app: wordpress tier: frontend type: LoadBalancer---apiVersion: v1kind: PersistentVolumemetadata: name: nfsspec: capacity: storage: 20G accessModes: - ReadWriteMany nfs: # FIXME: use the right IP server: <ip of="" the="" nfs="" service=""> path: "/"---apiVersion: v1kind: PersistentVolumeClaimmetadata: name: nfsspec: accessModes: - ReadWriteMany storageClassName: "" resources: requests: storage: 20G---apiVersion: apps/v1beta1 # for versions before 1.8.0 use apps/v1beta1kind: Deploymentmetadata: name: wordpress labels: app: wordpressspec: selector: matchLabels: app: wordpress tier: frontend strategy: type: Recreate template: metadata: labels: app: wordpress tier: frontend spec: containers: - image: wordpress:4.9-apache name: wordpress env: - name: WORDPRESS_DB_HOST value: mysql - name: WORDPRESS_DB_PASSWORD value: "" ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: nfs我们现在创建了一个持久卷声明,和我们之前创建的NFS服务建立映射,然后将卷附加到WordPress pod上,即/var/www/html根目录,这也是WordPress安装的地方。这里保留了集群中WordPress pods的所有安装和环境。有了这些配置,我们就可以对任何WordPress节点进行启动和拆除,而数据能够留下来。因为NFS服务需要不断使用物理卷,该卷将保留下来,并且不会被回收或错误分配。使用指令$ kubectl create -f wordpress.yaml部署WordPress实例。默认部署只会运行一个WordPress实例,可以使用指令$ kubectl scale --replicas=<number of="" replicas="">deployment/wordpress扩展WordPress实例数量。要获得WordPress服务负载均衡器的地址,你需要输入$ kubectl get services wordpress并从结果中获取EXTERNAL-IP字段来导航到WordPress。弹性测试OK,现在我们已经部署好了服务,那我们来拆除一下它们,看看我们的高可用架构如何处理这些混乱。在这种部署方式中,唯一剩下的单点故障就是NFS服务(原因总结在文末结论中)。你应该能够测试其他任何的服务来了解应用程序是如何响应的。现在我已经启动了WordPress服务的三个副本,以及MySQL服务中的一个主两个附属节点。首先,我们先kill掉其他而只留下一个WordPress节点,来看看应用如何响应:$ kubectl scale --replicas=1 deployment/wordpress现在我们应该看到WordPress部署的pod数量有所下降。$ kubectl get pods应该能看到WordPress pods的运行变成了1/1。点击WordPress服务IP,我们将看到与之前一样的站点和数据库。如果要扩展复原,可以使用$ kubectl scale --replicas=3 deployment/wordpress再一次,我们可以看到数据包留在了三个实例中。下面测试MySQL的状态集,我们使用指令缩小备份的数量:$ kubectl scale statefulsets mysql --replicas=1我们会看到两个附属从该实例中丢失,如果主节点在此时丢失,它所保存的数据将保存在GCE持久磁盘上。不过就必须手动从磁盘恢复数据。如果所有三个MySQL节点都关闭了,当新节点出现时就无法复制。但是,如果一个主节点发生故障,一个新的主节点就会自动启动,并且通过xtrabackup重新配置来自附属节点的数据。因此,在运行生产数据库时,我不建议以小于3的复制系数来运行。在结论段中,我们会谈谈针对有状态数据有什么更好的解决方案,因为Kubernetes并非真正是为状态设计的。结论和建议到现在为止,你已经完成了在Kubernetes构建并部署高可用WordPress和MySQL的安装!不过尽管取得了这样的效果,你的研究之旅可能还远没有结束。可能你还没注意到,我们的安装仍然存在着单点故障:NFS服务器在WordPress pods之间共享/var/www/html目录。这项服务代表了单点故障,因为如果它没有运行,在使用它的pods上html目录就会丢失。教程中我们为服务器选择了非常稳定的镜像,可以在生产环境中使用,但对于真正的生产部署,你可以考虑使用GlusterFS对WordPress实例共享的目录开启多读多写。这个过程涉及在Kubernetes上运行分布式存储集群,实际上这不是Kubernetes构建的,因此尽管它运行良好,但不是长期部署的理想选择。对于数据库,我个人建议使用托管的关系数据库服务来托管MySQL实例,因为无论是Google的CloudSQL还是AWS的RDS,它们都以更合理的价格提供高可用和冗余处理,并且不需担心数据的完整性。Kuberntes并不是围绕有状态的应用程序设计的,任何建立在其中的状态更多都是事后考虑。目前有大量的解决方案可以在选择数据库服务时提供所需的保证。也就是说,上面介绍的是一种理想的流程,由Kubernetes教程、web中找到的例子创建一个有关联的现实的Kubernetes例子,并且包含了Kubernetes 1.8.x中所有的新特性。以上是“怎么在Kubernetes上运行高可用的WordPress和MySQL”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注辰讯云资讯频道!...