- 分享一个小游戏开发神器-Annie2x,附上案例下载后可直接预览
在这里给大家分享一个小游戏开发神器-Annie2x. Annie2x是一款Flash插件,直接通过Flash来开发小游戏,简单逆天。 如果你会Flash或者以前用Flash做页游,做动画,那Annie2x基本上就是0难度了,直接上手。 如果你在Flash使用过CreateJS开发过H5,那基本上Annie2x基本上就是0难度了,直接上手。 并且Annie2x比CreateJS好用太多了。 如何使用呢,去Annie2x官网下载插件(http://annie2x.com)。然后在他的论坛有一个专门讲安装的教程 http://ask.annie2x.com/article/5。 照着来就行了。 不说了,直接上图: [图片] [图片] [图片] [图片] 具体制作可以去官方论坛,或者官方的QQ里了解,那里有更多学习资源。 上手非常快,之前用laya egret 还有cocos做一个项目,要做动画效果的时候真的是好麻烦。 有了Annie2x,像做swf一样开发小游戏,真的是太爽了。 上面这个游戏我只用了半天时间就把以前的Flash游戏转换成小游戏了。这里放出源码,大家直接下载后解压用微信开发者工具打开就能预览效果了,见证下这个神奇的开发工具吧。 源码地址:https://pan.baidu.com/s/1Igcyc7pAKijoezfCfm3I7A 密码:9x1j
2018-08-08 - [开盖即食]基于canvas的“刮刮乐”刮奖组件
[图片] 工作中有时候会遇到一些关于“抽奖”的需求,这次以“刮刮乐项目”举例,分享一个实战抽奖功能。 本人对之前网上流传的一些H5刮刮乐JS插件版本进行了一些改造,使其能适用于实际项目,并且支持小程序canvas 2D的新API,这里顺便提下2D API和实际H5 canvas中JS写法非常类似,只有少数不同。 [图片] 1、方法介绍: 1.1 刮刮乐JS组件 [代码]class Scratch { constructor(page, opts) { opts = opts || {}; this.page = page; this.canvasId = opts.canvasId || 'canvas'; this.width = opts.width || 300; this.height = opts.height || 300; this.bgImg = opts.bgImg || ''; //覆盖的图片 this.maskColor = opts.maskColor || '#edce94'; this.size = opts.size || 15, //this.r = this.size * 2; this.r = this.size; this.area = this.r * this.r; this.showPercent = opts.showPercent || 0.2; //刮开多少比例显示全部 this.rpx = wx.getSystemInfoSync().windowWidth / 750; //设备缩放比例 this.scale = opts.scale || 0.5; this.totalArea = this.width * this.height; this.startCallBack = opts.startCallBack || false; //第一次刮时触发刮奖效果 this.overCallBack = opts.overCallBack || false; //刮奖完触发 this.init(); } init() { let self = this; this.show = false; this.clearPoints = []; const query = wx.createSelectorQuery(); //console.log(this.canvasId); query.select(this.canvasId) .fields({ node: true, size: true }) .exec((res) => { //console.log(res); this.canvas = res[0].node; this.ctx = this.canvas.getContext('2d') this.canvas.width = res[0].width; this.canvas.height = res[0].height; //const dpr = wx.getSystemInfoSync().pixelRatio; //this.canvas.width = res[0].width * dpr; //this.canvas.height = res[0].height * dpr; self.drawMask(); self.bindTouch(); }) } async drawMask() { let self = this; if (self.bgImg) { //判断是否是网络图片 let imgObj = self.canvas.createImage(); if (self.bgImg.indexOf("http") > -1) { await wx.getImageInfo({ src: self.bgImg, //服务器返回的图片地址 success: function (res) { imgObj.src = res.path; //res.path是网络图片的本地地址 }, fail: function (res) { //失败回调 console.log(res); } }); } else { imgObj.src = self.bgImg; //res.path是网络图片的本地地址 } imgObj.onload = function (res) { self.ctx.drawImage(imgObj, 0, 0, self.width * self.rpx, self.height * self.rpx); //方法不执行 } imgObj.onerror = function (res) { console.log('onload失败') //实际执行了此方法 } } else { this.ctx.fillStyle = this.maskColor; this.ctx.fillRect(0, 0, self.width * self.rpx, self.height * self.rpx); } //this.ctx.draw(); } bindTouch() { this.page.touchStart = (e) => { this.eraser(e, true); } this.page.touchMove = (e) => { this.eraser(e, false); } this.page.touchEnd = (e) => { if (this.show) { //this.page.clearCanvas(); if (this.overCallBack) this.overCallBack(); this.ctx.clearRect(0, 0, this.width * this.rpx, this.height * this.rpx); //this.ctx.draw(); } } } eraser(e, bool) { let len = this.clearPoints.length; let count = 0; let x = e.touches[0].x, y = e.touches[0].y; let x1 = x - this.size; let y1 = y - this.size; if (bool) { this.clearPoints.push({ x1: x1, y1: y1, x2: x1 + this.r, y2: y1 + this.r }) } for (let item of this.clearPoints) { if (item.x1 > x || item.y1 > y || item.x2 < x || item.y2 < y) { count++; } else { break; } } if (len === count) { this.clearPoints.push({ x1: x1, y1: y1, x2: x1 + this.r, y2: y1 + this.r }); } //添加计算已清除的面积,达到标准值后,设置刮卡区域刮干净 let clearNum = parseFloat(this.r * this.r * len) / parseFloat(this.scale * this.totalArea); if (!this.show) { this.page.setData({ clearNum: parseFloat(this.r * this.r * len) / parseFloat(this.scale * this.totalArea) }) }; if (this.startCallBack) this.startCallBack(); //console.log(clearNum) if (clearNum > this.showPercent) { //if (len && this.r * this.r * len > this.scale * this.totalArea) { this.show = true; } this.clearArcFun(x, y, this.r, this.ctx); } clearArcFun(x, y, r, ctx) { let stepClear = 1; clearArc(x, y, r); function clearArc(x, y, radius) { let calcWidth = radius - stepClear; let calcHeight = Math.sqrt(radius * radius - calcWidth * calcWidth); let posX = x - calcWidth; let posY = y - calcHeight; let widthX = 2 * calcWidth; let heightY = 2 * calcHeight; if (stepClear <= radius) { ctx.clearRect(posX, posY, widthX, heightY); stepClear += 1; clearArc(x, y, radius); } } } } export default Scratch [代码] 1.2 JS 调用方法 [代码]new Scratch(self, { canvasId: '#coverCanvas', //对应的canvasId width: 600, height: 300, //maskColor:"", //封面颜色 showPercent: 0.3, //刮开多少比例显示全部,比如0.3为 30%面积 bgImg: "./cover.jpg", //封面图片 overCallBack: () => { //刮奖刮完回调函数 }, startCallBack: () => { //当用户触摸canvas板的时候触发回调 } }) [代码] 实际中还支持其他很多的配置项,比如缩放比例,刮开比例,放置区域等等,大家可以根据实际需求设置。 1.3 实际页面中的JS调用方法: [代码]//引入刮刮乐部分 import Scratch from './scratch.js'; const app = getApp() Page({ data: { firstTouch: 0, isOver: 0, }, onLoad() { let self = this; new Scratch(self, { canvasId: '#coverCanvas', width: 600, height: 300, //maskColor:"", //封面颜色 bgImg: "./cover.jpg", //封面图片 overCallBack: () => { this.setData({ isOver: "结束啦" }) //this.clearCanvas(); }, startCallBack: () => { this.setData({ firstTouch: "开始刮啦" }) //this.postScratchSubmit(); } }) }, //刮卡已刮干净 clearCanvas() { let self = this; console.log("over"); }, }) [代码] 1.4 HTML/CSS [代码]<-- html --> <view class="wrap"> <canvas class="cover_canvas" type="2d" disable-scroll="false" id='coverCanvas' bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd"></canvas> <image class="img" src="reward.jpg" mode="widthFix" /> </view> /* css */ .wrap { width: 600rpx; height: 300rpx; margin: 100rpx auto; border: 1px solid #000; position: relative; } .cover_canvas { width: 600rpx; height: 300rpx; z-index: 9; } .wrap .img { position: absolute; left: 0; top: 0; z-index: 1; width: 600rpx; height: 300rpx; } [代码] 这里注意 type=“2d” 写法,这里使用的是新的2D canvas。 2、注意事项 canvas一些效果不支持真机调试,直接预览就行了 如果刮奖结果是通过第一次触碰canvas触发的,这里的请求需要写一个同步方法 刮刮乐JS的配置会优先判断bgImg这个属性,再判断maskColor 需要反复刮奖,可以反复new 它。 3、代码片段 地址: https://developers.weixin.qq.com/s/RxiaHam574or 建议将IDE工具升级到 1.03.24以上,避免一些BUG [图片] 觉得有用,请点个赞,这是我继续分享的动力~
2021-02-18 - 扩展小程序页面compute属性思路
本来之前技术栈一直是Vue,后来做小程序了,各种不习惯,尤其是少了计算属性和监听器,wxml里面又不能直接放函数,又没有Vue那种过滤器的写法。也许有人说直接uni-app就好了,但是平时做跨端的很少,所以我偏不用uni-app,我就要自己写一个。以下内容纯靠个人臆想,搞出来的compute属性虽然能用,但仅供参考。 出于偶然发现了小程序能够自己重写生命周期钩子,听说很多的一些统计sdk也都是利用这样的机制做事件打点统计啥的,于是这么好玩的东西,我决定参一脚试一下好不好玩。compute属性的扩展就是在重写的生命周期里面做的,实现思路步骤大体如下两步: 在重写小程序onLoad生命周期里面判断一下用户是否有定义compute属性,如果有的话深拷贝一个data出来给这个data循环遍历绑定一下get用来做compute的依赖收集,然后用setData绑定到data上,compute属性里面的一般是个函数返回一个经过计算处理的值,这个函数里面一旦调用了data上的属性时就会触发前面设置的get函数进行依赖收集。依赖收集完成后,还需要重写一下setData,监听一下用户在用setData改数据时都改了哪些数据,然后触发compute的更新。OK,大功告成,一个勉强能跑的小轮子就出来了,其实小程序里面很多api都能重写或者使用defineProperty来监听,可以用来解决日常很多的开发问题,像啥页面栈溢出问题,随手加个watch监听器,加个store全局数据管理啥的估计都不是啥大问题。 最后附上代码和用法,仅提供一个思路,cv的话请慎重。 // app.js require('./utils/compute.js') // page.js Page({ data: { a: 0, k: [0, 0, 0, 1], c: { a: 9 } }, compute: { b(data) { return data.c.a + data.a + data.k[0] } }, onLoad() { // 下面几种写法都能监听到并更新 this.setData({ a: 8, 'c.a': 10, [`k[${0}]`]: 999 }, () => { console.log(this.data.b) }) }, }) // compute.js const oldPage = Page Page = function (app) { let oldOnLoad = app.onLoad; app.onLoad = function (options) { let o_setData = this.setData let dep = {} // 重写setData this.setData = function (o, fn) { o_setData.call(this, o, function () { if (app.compute) { Object.keys(dep).map(c => { Object.keys(o).map(i => { if (dep[c].includes(i) || dep[c].includes(i.split('.')[0]) || dep[c].includes(i.split('[')[0])) { o_setData.call(this, { [c]: app.compute[c].call(this, this.data) }) } }) }) } fn && fn.call(this) }) } // 收集一下依赖 if (app.compute) { let _data = JSON.parse(JSON.stringify(this.data)) let bufferDep = [] Object.keys(_data).map(key => { let val = _data[key] Object.defineProperty(_data, key, { get: function () { bufferDep.push(key) return val } }) }) Object.keys(app.compute).map(key => { bufferDep = [] this.setData({ [key]: app.compute[key].call(this, _data) }, function () { dep[key] = bufferDep }) }) } oldOnLoad.call(this, options) } return oldPage(app) }
2021-02-01 - 微信小程序自定义tarbra的坑,动态适配iphoneX iphone11 带安全区域的手机
微信小程序虽然开放了自定义的tabbar 因为他用的是fixed定位布局 导致每个tabbar页都要去动态计算padding-bottom 或者bottom值,之前尝试过 wx.getSystemInfo({ success: function(res) { console.log(res) if (res.model.search('iPhone X') != -1) { that.globalData.isIphoneX = true } }, }) 在app.js中判断是不是iphone X ok这个时候是完美适配的 但是有一天测试同学拿着iphone 11 pro max找我 说页面的padding-bottom值会盖住,在我的排查中发现res.model.search('iPhone X') != -1 这句代码拿到的结果为-1 我之前是这么处理的 我判断机型为iphonex的时候 tabbar 页面的padding-bottom为100rpx+64rpx 但是iphone 11pro 系列手机在这个判断中无效 经过排查并反复改 终于拿到了完美适配的方案!!!!我们只需要在外层的view padding-bottom: calc(100rpx + env(safe-area-inset-bottom))就好了 有需要的同学点个关注吧!!! 对了 再次说明下 custom-tab-bar.wxss 中.tar-bar里的height我自己改成了100rpx 微信官方的是50px
2020-03-19 - 小程序的视频内容流自动播放
小程序的视频内容流自动播放 啊啊啊,又解决一个问题 0、起因 这个需求产生的起因,是在做内容流(包含文本,图片,视频)的时候,需要如果流里面有视频,则滚动到一定位置时自动播放视频,类似朋友圈、微博等等的自动播放效果。 [图片] 1、第一版尝试 第一版的思路是: 收集当前所有内容流相对于页面头部的高度,做成一个Array 滚动过程中,监听页面滚动事件,当达到某个高度要求,则播放对应的索引视频 这个操作缺点太多了,捡几个主要的说 缺点: 内容流是一个个的组件,获取距离顶部高度不方便,也不太准。并且组件内需要通过事件传播到列表页,在列表页进行高度Array整理、事件监听、切换索引等等(如果有几种列表页,就要写几遍,很麻烦) 监听滚动事件本身就消耗性能,做了节流也不是那么优秀 2、第二版尝试 突然,就发现了[代码]wx.createIntersectionObserver[代码]这个属性,它的作用是:返回[代码]intersectionObserver[代码]对象,用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见(创建一个目标元素,根据目标元素和视窗的相交距离来判断当前页面滚动的情况。通常这个方案也用于页面图片的懒加载)。参考https://developers.weixin.qq.com/miniprogram/dev/api/wxml/IntersectionObserver.html 怎么解释呢,就是可以理解为,做一个监听,如果当前被监听的元素,进入了你规定的视界或者离开你规定的视界,就触发。 那么,怎么做到监听呢,参考如下代码: [代码]/** 监控视频是否需要播放 */ let {screenWidth, screenHeight} = this.extData.systemInfo //获取屏幕高度 let topBottomPadding = (screenHeight - 80)/2 //取屏幕中间80的高度作为播放触发区域,然后计算上下视窗的高度 topBottomPadding // 80这个高度可以根据UI样式调整,我这边基本两个视频间隔高度在100左右,超过了两个视频之间的间隔,就会冲突,两个视频会同时播放,不建议过大 const videoObserve = wx.createIntersectionObserver() videoObserve.relativeToViewport({bottom: -topBottomPadding, top: -topBottomPadding}) .observe(`#emotion${this.data.randomId}`, (res) => { let {intersectionRatio} = res if(intersectionRatio === 0) { //离开视界,因为视窗占比为0,停止播放 this.setData({ playstart: false }) }else{ //进入视界,开始播放 this.setData({ playstart: true }) } }) [代码] 其中,[代码]observe[代码] 是对应你需要监听的视频(也就是滚动进入视窗的元素) 那么,为什么选择[代码]relativeToViewport[代码]呢,是因为我们需要对它进入某一个视窗进行监听,而不是对进入整个屏幕视窗监听(因为可能整个视窗里会有多个视频)。 以上,就是整个逻辑思路。 最开始用的[代码]relativeTo[代码]监听视频进入某个元素(如[代码].view-port[代码]),但是后来发现每个页面都要写这个元素,太麻烦,并且容易遮盖操作区域 [代码]// 太麻烦,后来舍弃了这个方案 <view class="view-port" style="height: 100rpx; position: fixed; z-index: 1;width: 100%;letf:0;top:50%;transform: translateY(-50%);"></view> [代码]
2019-12-01 - 微信小程序CI流程搭建教学(2) -- jenkins一键发布
教学仓库: https://github.com/jinxuanzheng01/blog-xcx-ci-demo/tree/master,可以clone下来跟着教程一起尝试 CI流程搭建教学三部曲: 实现云构建如何接入CI(jenkins版)搭建一个版本发布管理后台 + 企业微信机器人群通知背景相信长期在做小程序的同学都有过以下经历: 只有固定的几台机器能发版,换台机器总担心出现点问题 (心力成本)多个小程序发版,切换多个仓库,甚至要用开发者工具去打开不同的项目(体力成本)频繁发版 + 发版流程琐碎 + 可能存在的多个小程序,一天的时间被机械劳动的事耗掉半天(时间成本)本文主要来分享一下我这边的一套解决,实现一键发布,统一管理,在读之前最好先看下上一篇文章,不然可能会有些阻碍 关于jenkins:https://www.jenkins.io/zh/ 案例 [图片] 这个是目前我们已经搭建好的一套服务,可以看到整个流程都做了那些事? 拉取代码询问当前需要变更的版本号构建(gulp, webpack,npm)存储当前版本镜像到静态资源服务器版本信息插入数据库更改后的版本文件提交到git仓库 其中红色为必须项,黑色看情况添加,如果你想也可以加入你想要添加的任何环节,下图是我们的版本发布管理后台的界面,存储的mysql的版本信息大概是这个样子: [图片] 关于管理后台第三章再讲,本文先着重描述怎么接入jenkins Let’go首先你需要一台云服务器用来搭建jenkins环境,推荐2核4g,不过1核2g也能跑 安装javajenkins依赖于java环境,所以先安装java yum install -y java-1.8.0-openjdk.x86_64 安装jenkins包官方教程: https://www.jenkins.io/zh/doc/book/installing/ 仓库地址: http://pkg.jenkins-ci.org/redhat-stable/ sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key yum install jenkins # 运行 service jenkins start 运行成功后访服务器端口号8080 [图片] 如果想修改端口号 vim /etc/sysconfig/jenkins, 找到JENKINS_PORT,修改后执行 [代码]service jenkins restart[代码] 即可 [图片] 关于插件安装个人比较懒,点推荐快速过,后面有用到的一些插件慢慢会介绍 [图片] 插件装完,中间需要创建管理员账号,一路确定最终安装成功 [图片] 创建流水线pipeline首先创建一个任务, 这里选择流水线(有兴趣可以了解下其他项), 点击确定 [图片] 点击流水线找到编辑script的地方 [图片] [图片] 这里可以选择在服务器上编写,还是从项目代码中读取jenkinsfile文件,我这里为了方便调试直接在jenkins机器上编写了 pipeline script流水线语法官方教程: https://www.jenkins.io/zh/doc/book/pipeline/,里面讲的比较详细,可以先按下面的代码运行,遇到问题再查教程 流水线结构写流水线之前,先想好我们整个发布流程需要做什么? 以当前例的话,大致分为5个环节: 拉取代码 询问需要更改的版本号安装npm包执行发布提交修改的版本号信息到仓库 每一个环节都是一个stage,先把stage列好,接下来我们一个一个补充 pipeline { agent any stages { // 拉取git代码 stage('git pull') { steps { } } // 询问当前版本信息 stage('inquirer version') { steps { } } // 构建 stage('build') { steps { } } // 推送版本信息到git仓库 stage('push version2git') { steps { } } } } 流水线语法 很多插件的语法使用还是比较复杂和琐碎的,这里提供了一个自动生成pipieline语法的功能 [图片] 以git为例,会自动生成相应的语法,避免很多查文档的时间 [图片] 拉取git代码根据上文所述,我们需要生成流水线脚本,但是想要让仓库和jenkins建立ssh链接,需要配置git仓库的公钥和jenkins的私钥凭据 配置git仓库公钥jenkins拉取git仓库代码,为了方便需要配下ssh秘钥 # 生成秘钥(没有要求一路回车即可) ssh-keygen -t rsa -C "616347058@qq.com" # 查看生成的ssh文件 cd ~/.ssh && ls # 一般会有id_rsa id_rsa.pub两个文件, 一个私钥一个公钥 点击个人头像点击setting找到ssh,粘贴 [代码]id_rsa.pub[代码] 里的内容到输入框,点击add添加完成 [图片] [图片] 配置jenkins私钥除了在git上配置公钥外,还需要在jenkins上配置下凭据,点击添加 [图片] 选择类型为ssh username,其他input的可以自己定一下的,这里需要一下,关于private项填的是私钥,需要复制[代码]id_rsa[代码]的内容,填写完成后点击添加,并在 Credentials 一栏选择刚才添加的证书 [图片] 当前页面没有显示红字即为成功 [图片] pipeline code// 拉取git代码 stage('git pull') { steps { git branch: 'test', credentialsId: '1', url: 'git@github.com:jinxuanzheng01/blog-xcx-ci-demo.git' } } 添加到指定位置后,点击保存,尝试构建一次查看是否成功,点左下角#5可以查看当前构建进程 [图片] 点击console output可以查看当前log,workspaces可以查看当前工作空间 [图片] 可以看到当前工作空间为仓库代码 [图片] 询问当前版本信息jenkins是不支持交互式命令的,所以需要换一种实现,查了下文档jenkins是有api支持的,方法名为 [代码]input[代码],不过在进行调用之前需要获取之前的版本信息,即读取version.config.json这个文件,整体代码如下: // 询问当前版本信息 stage('inquirer version') { steps { script { // 读取版本信息 def versionJson = readJSON file: './version.config.json', text: '' // 设置问题描述 def userInput = input( id: 'versionInput', message: '请设置版本信息', parameters: [ [defaultValue: versionJson.version, description: '设置版本号', name: 'VERSION', $class: 'TextParameterDefinition'], [defaultValue: 'jenkins CI is upload trial version as: ' + new Date().format('yyyy-MM-dd HH:mm:ss'), description: '设置版本描述(please use english)', name: 'VERSIONDESC', $class: 'TextParameterDefinition'] ]) // 设置全局变量 env.VERSION = userInput.VERSION; env.VERSIONDESC = userInput.VERSIONDESC; // 重写本地版本文件(为后续进行版本提交做准备) writeJSON file: './version.config.json', json: [version: env.VERSION, versionDesc: env.VERSIONDESC], pretty: 4; } } } 运行下, 会发现会提示等待,鼠标点击后弹出模态框,可以看到里面有从本地文件里提取出来的上个版本的信息,手动修改下即可 [图片] 注: readJSON和 writeJSON方法依赖 Pipeline Utility Steps 这个插件,没有的话会报错 执行构建这里实际都是构建流程,直接统一用一个“build stage” 即可, 这里步骤很简单,可以想一下从git clone下来一个项目应该干什么,这里实际就是在做这样一件事情 // 构建 stage('build') { steps { sh "npm install" sh "npm run build" } } 不过这里依赖node环境,需要先处理下,不然会报npm not found 安装nodejs环境这里的运行环境和本机没有关系,类似于一个沙盒,所以如果要安装node环境需要安装Config File Provider Plugin和NodeJS Plugin这两个插件 配置全局工具安装完成后添加全局工具,找到"系统管理 -> 全局工具配置 -> NodeJS" ,点击nodejs安装,默认项即可,点击保存,重启jenkins服务,回到服务器,输入命令 [代码]service jenkins restart[代码] [图片] pipeline code // 构建 stage('build') { steps { nodejs('NodeJS 14.3.0') { sh "npm install" sh "npm run build" } } } 上传成功这时候如果没问题的话代码应该已经发布到体验版了,这里如果报ip错误记得去微信公众后台添加一下上传的ip白名单 [图片] [图片] 提交新的版本号信息到仓库pipeline code基本就是将之前手动输入的命令写到脚本里 // 推送版本信息到git仓库 stage('push version2git') { steps { sh "git config --local user.name ${GIT_USER_NAME} && git config --local user.email ${GIT_USER_EMAIL}" sh "git add version.config.json" sh "git commit -m 'docs: 更改版本号为${VERSION}'" sh "git push origin ${BRANCH_NAME}" } } 关于上面代码中的"${}",其实是预设的环境变量,我们可以把一些固定的东西提出来,避免硬编码,例如: pipeline { agent any // 环境变量 environment { GIT_USER_NAME = 'jenkinsCI' GIT_USER_EMAIL = 'test@163.com' GIT_ADDRESS = 'git@github.com:jinxuanzheng01/blog-xcx-ci-demo.git' BRANCH_NAME = 'master' } } 更改jenkins账号权限直接进行push的话,git会报权限不足,这里倒不是因为ssh秘钥有问题,而是jenkins在执行的时候使用的不是linux机器的root权限,而是一个jenkins的账号 [图片] 所以最快速的做法是将jenkins运行环境修改为root权限 # 打开配置文件 vim /etc/sysconfig/jenkins # 修改jenkins_user为root, 默认为jenkins $JENKINS_USER="root" # 修改相关文件夹权限组 chown -R root:root /var/lib/jenkins chown -R root:root /var/cache/jenkins chown -R root:root /var/log/jenkins # 重启jenkins service jenkins restart 运行pipeline重新构建,jenkins无报错,查看git仓库,执行成功 [图片] Running success到此为止,整个pipeline已经work,你可以很轻松的尝试一键发布小程序 [图片] 尤其是有多个小程序项目的时候,可以单独建个分组,方便集中管理,再也不需要手动切入不同的仓库,甚至使用小程序开发者工具去打开各个小程序,大概如下 [图片] 项目集中管理不香么? Tips: jenkins流水线代码已经放到仓库里了,地址 https://github.com/jinxuanzheng01/blog-xcx-ci-demo/blob/master/Jenkinsfile 额外补充权限管理jenkins有很多的功能插件,比如权限管理,这个还是个比较刚需的功能,可以自行Google下并不难 发送http请求在pipeline中有时候需要去请求一些我们的其他服务,可以使用HTTP Request Plugin这个插件,文档地址:https://plugins.jenkins.io/http_request/里面有一些demo npm切换私有仓库方法一: npm可以直接用npm_token登录 方法二:项目内设置.npmrc,填写私有仓库 + 用户信息 jenkins使用docker报权限不足[图片]修改jenkins账号权限为root权限即可,上文有写,也可以直接修改docker.sock这个文件权限,如 [代码]chmod 777 /var/run/docker.sock[代码] 手动安装jenkins插件使用jenkins的插件管理安装插件很慢,有的时候还会失败,这里提供一个手动安装的方法 jenkins插件库: http://updates.jenkins-ci.org/download/plugins/config-file-provider/ 搜索需要的插件下载(一般是*.hpi格式的),打开jenkins "系统管理 -> 插件管理 -> 高级",选择下载的文件上传即可 [图片]
2020-05-28 - 小程序页面(Page)扩展,为所有页面添加公共的生命周期、事件处理等函数
背景 在小程序的原生开发中,页面中经常会用到一些公共方法,例如在页面onLoad中验证权限、所有页面都需要onShareAppMessage设置分享等 假设我们在编码时每个页面都写一遍,显然不是一个高级程序员会干的事情,太Low了。如果我们定义一个公共文件,导出这些公共方法,每个页面都引入,然后再生命周期或者事件处理函数中调用,虽然看起来很方便,但不够优雅,达不到我们最终的目的(偷懒)。 下面给大家介绍一种相对比较优雅的实现方式,扩展Page来实现以上的操作。 Page(页面) 需要传入的是一个 [代码]object[代码] 类型的参数,那么我们重载一个 [代码]Page[代码] 函数,将这个 [代码]object[代码] 参数拦截改掉就可以了,下面直接上代码。 实现 1、在根目录新建一个 [代码]page-extend.js[代码] 文件,公共的逻辑都写在这里面 [代码]/** * * Page扩展函数 * * @param {*} Page 原生Page */ const pageExtend = Page => { return object => { // 导出原生Page传入的object参数中的生命周期函数 // 由于命名冲突,所以将onLoad生命周期函数命名成了onLoaded const { onLoaded } = object // 公共的onLoad生命周期函数 object.onLoad = function (options) { // 在onLoad中执行的代码 ... // 执行onLoaded生命周期函数 if (typeof onLoaded === 'function') { onLoaded.call(this, options) } } // 公共的onShareAppMessage事件处理函数 object.onShareAppMessage = () => { return { title: '分享标题', imageUrl: '分享封面' } } return Page(object) } } // 获取原生Page const originalPage = Page // 定义一个新的Page,将原生Page传入Page扩展函数 Page = pageExtend(originalPage) [代码] 2、在 [代码]app.js[代码] 中引入 [代码]page-extend.js[代码] 文件 [代码]require('./page-extend') App({ // 其他代码 ... }) [代码] 代码片段 https://developers.weixin.qq.com/s/Cyx8iGmV7Ldp 本文内容及评论未经允许,禁止任何形式的转载与复制(代码可在程序中使用)
2019-12-24 - 小程序模板消息能力调整通知
小程序模板消息能力在帮助小程序实现服务闭环的同时,也存在一些问题,如: 1. 部分开发者在用户无预期或未进行服务的情况下发送与用户无关的消息,对用户产生了骚扰; 2. 模板消息需在用户访问小程序后的 7 天内下发,不能满足部分业务的时间要求。 为提升小程序模板消息能力的使用体验,我们对模板消息的下发条件进行了调整,由用户自主订阅所需消息。 一次性订阅消息 一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。 [图片] (一次性订阅示例) 长期性订阅消息 一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。 目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。 调整计划 小程序订阅消息接口上线后,原先的模板消息接口将停止使用,详情如下: 1. 开发者可登录小程序管理后台开启订阅消息功能,接口开发可参考文档:《小程序订阅消息》 2. 开发者使用订阅消息能力时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《小程序订阅消息接口运营规范》 3. 原有的小程序模板消息接口将于 2020 年 1 月 10 日下线,届时将无法使用此接口发送模板消息,请各位开发者注意及时调整接口。 微信团队 2019.10.12
2019-10-13