20公里的徒步-真難
群里的伙伴發起了一場天目山20公里徒步的活動,想著14公里都輕松拿捏了,思考了30秒后,就借著春風帶著老婆孩子就出發了。一開始溪流清澈見底,小橋流水沒有人家;青山郁郁蔥蔥,枯藤老樹沒有烏鴉,微風習習,鳥語花香,好不愜意。大有我看青山多嫵媚,料青山見我應如是的舒坦,但是,但是沒多一會兒畫風突變了,爬過一座山還有數不盡的山,關鍵還一山更比一山高。響午溫度升高,加之水資源極度匱乏(因為沒有人家,劃重點: 徒步一定要多帶水),小寶先哭為敬了。好在睡意過去后,又堅強的跑了起來,一直用他的格言激勵自己:放棄很多簡單,堅持很難,我要堅持。大寶一直在在前面跟著大隊伍,想想也是克服了極大的困難,他一直在山頂殷切的期盼著我們,當我們出現視野里時,又高興的喊著爸爸,媽媽,小寶加油。或許大寶還是蠻優秀的,只是有時對大寶可能過于嚴厲了些。最后,大家都是笑著走過了最難的路。
走到16公里的地方,天已經黑了,已經到大路了,是不是20公里也不重要了,大家開心的找了家飯店,酣暢淋漓的吃喝了一頓,途中的跌倒與艱難全都成了豪爽的談資,第二天大家都還可以自豪的說全身酸痛不已。
流量來了-心動了
領略了天目山的秀麗風景,回歸正題。書接上文,之前搗鼓了一個小程序,沒有想到日活居然過1000了,日新增200+,活躍用戶次日留存超40%…
看著這些數據,陷入了沉思,思緒竟然來到了明朝末年(估計最近讀《明朝那些事兒》魔怔了吧),農民起義紛爭的年代,自己化身高迎祥、李自成、張獻忠,手握數萬雄兵,但不知所措…思緒一陣亂飛后得到這樣一個結論:一個小程序1000,100個小程序就是10萬(10萬日活廣告費真是不得了)- 構建小程序矩陣,構建100個程序,小程序就是雄兵,去攻城略地。
這事兒只有開頭簡單
有了目標,一口氣又注冊了5個小程序,備案,各種配置,上傳,提交審核,發布…一套動作下來,雖是幸苦,總算是5個小程序都上架了,但是心中總有點不得勁兒的感覺,又說不出是哪里出了問題。還沒等回過勁兒,發現程序有bug, 又吭哧吭哧一個個修改,上傳,提交審核,發布…這會兒明白問題在哪里了:機械重復。光明白還沒用,因為又有bug了,又是全套流程要做完。更多嚴重的問題是:這個過程又中注冊了5個新小程序…應了那句老話:萬事開頭難,開頭后更難。看著10個小程序要機械的重復發布,我沒有崩潰,也沒有去重復了,去搗鼓自動化了,解放雙手才是正確的路。雖然只是解決了代碼上傳的問題,已是一個巨大的進步。
提前在 key目錄下添加小程序代碼上傳密鑰文件格式 private.wx0d8d56e152eb16xx.key
const fileExists = require('file-exists');const del = require('del');
const child_process = require('child_process');
const ci = require('miniprogram-ci');const gulp = require('gulp');const less = require('gulp-less');
const uglify = require('gulp-uglify');
const cleanCSS = require('gulp-clean-css');
const rename = require('gulp-rename');
const gulpif = require('gulp-if');
const replace = require('gulp-replace');
const alias = require('gulp-path-alias');
const autoprefixer = require('gulp-autoprefixer');const pkg = require('./package.json');
let projectConfig = require('./project.config.json');
const buildPath = path.join(__dirname, 'dist/');const argv = require('minimist')(process.argv.slice(1));
const appId = argv["appId"];
if (appId){console.log('set appId = ',appId);projectConfig['appid'] = appId;
}const env = process.env.NODE_ENV
console.log("evn=", env)
const isPro = env === 'production';
console.log("isPro=", isPro)
const branchName = child_process.execSync('git symbolic-ref --short HEAD', {encoding: 'utf8',
});const paths = {styles: {src: ['src/**/*.less'],dest: buildPath,},images: {src: 'src/images/**/*.{png,jpg,jpeg,svg,gif}',dest: buildPath,},scripts: {src: 'src/**/*.js',dest: buildPath,},copy: {src: ['src/**','!src/**/*.less','!src/**/*.js','package.json',],dest: buildPath,},
};// 刪除構建
function clean() {return del([buildPath]);
}function log() {const data = Array.prototype.slice.call(arguments);console.log(data);
}// 任務處理函數
function styles() {return gulp.src(paths.styles.src, { base: 'src' }).pipe(alias({paths: {'@': path.resolve(__dirname, './src/'),},})).pipe(less()).pipe(autoprefixer()).pipe(gulpif(isPro, cleanCSS())).pipe(rename((path) => (path.extname = '.wxss'))).pipe(gulp.dest(paths.styles.dest));
}function scripts() {return (gulp.src(paths.scripts.src, { base: 'src' }).pipe(alias({paths: {'@': path.resolve(__dirname, './src/'), // src 目錄},}))// .pipe(babel({ presets: ['@babel/env'], 'plugins': [] })).pipe(replace('%ENV%', process.env.NODE_ENV)) // 環境變量靜態替換.pipe(replace('%VERSION%', pkg.version)).pipe(gulpif(isPro, uglify())).pipe(gulp.dest(paths.scripts.dest)));
}// 不需要處理的文件直接復制過去
function copy() {return gulp.src(paths.copy.src).pipe(gulp.dest(paths.copy.dest));
}function watchFiles() {const w1 = gulp.watch(paths.styles.src, styles).on('unlink', function (file) {log(file + ' is deleted');const filePath = file.replace(/src\\/, 'dist\\');del([filePath]);});const w2 = gulp.watch(paths.scripts.src, scripts).on('unlink', function (file) {log(file + ' is deleted');const filePath = file.replace(/src\\/, 'dist\\');del([filePath]);});const w3 = gulp.watch(paths.copy.src, copy).on('unlink', function (file) {log(file + ' is deleted');const filePath = file.replace(/src\\/, 'dist\\');del([filePath]);});return Promise.all([w1, w2, w3]);
}/*** 小程序ci相關函數*/
let project = {};const keyFile = fileExists.sync(`./key/private.${appId}.key`);
if (keyFile) {project = new ci.Project({appid: appId,type: 'miniProgram',projectPath: './dist',privateKeyPath: `./key/private.${appId}.key`,});
}
async function npmBuild() {await ci.packNpmManually({packageJsonPath: './package.json',miniprogramNpmDistDir: './src/',});
}
const envLabels = {'production': '正式環境','development': '測試環境','pre': '預發環境',
};// 機器人代號,有效范圍[1-30]
const robotMap = {'development': 1,'production': 2,'pre': 3,
}async function mpUpload() {log('mpUpload appid',appId);if (!appId) {console.log('\x1b[35m%s\x1b[0m', `
════════════════════════════════════════════════════════════════════════
?【${envLabels[env]}】小程序打包失敗,請先執行 export APPID=你的appid 命令,設置appid
════════════════════════════════════════════════════════════════════════`);return false;}projectConfig['appid'] = appId;log('projectConfig appid',projectConfig.appid);const uploadResult = await ci.upload({project,version: pkg.version,desc: `【${envLabels[env]}】${pkg.description}`,setting: {es7: true,es6: true,minifyJS: true,minifyWXML: true,minifyWXSS: true,minify: true,autoPrefixWXSS: true,},robot: robotMap[env],onProgressUpdate: console.log,});console.log('[uploadResult:]', uploadResult);console.log('\x1b[35m%s\x1b[0m', `
════════════════════════════════════════════════════════════════════════
🚀【${envLabels[env]}】小程序打包已完成,可以去發布了https://mp.weixin.qq.com/
════════════════════════════════════════════════════════════════════════`);
}async function preview() {const previewResult = await ci.preview({project,desc: `【${envLabels[env]}】${pkg.description}`, // 此備注將顯示在“小程序助手”開發版列表中qrcodeFormat: 'image',qrcodeOutputDest: './preview.jpg',setting: {es7: true,es6: true,minifyJS: true,minifyWXML: true,minifyWXSS: true,minify: true,autoPrefixWXSS: true,},robot: robotMap[env],onProgressUpdate: console.log,// pagePath: 'pages/index/index', // 預覽頁面// searchQuery: 'a=1&b=2', // 預覽參數 [注意!]這里的`&`字符在命令行中應寫成轉義字符`\&`});console.log('[previewResult:]', previewResult);console.log('\x1b[35m%s\x1b[0m', `
════════════════════════════════════════════════════════════════════════
🚀【${envLabels[env] || '測試環境'}】小程序預覽已完成,可以去小程序助手中查看了
════════════════════════════════════════════════════════════════════════`);
}
exports.watch = watchFiles;
exports.preview = preview;
// ci 自動構建npm
exports.npm = npmBuild;
exports.upload = mpUpload;exports.default = gulp.series(styles, scripts, copy, watchFiles);exports.build = gulp.series(clean, styles, scripts, copy);
再寫個python 處理批量的問題
import subprocess
# 指定的目錄
directory = '/Users/jijunjian/wealth'commandList = ['npm run deploy:pro -- --appId=wx7f4984150494f817','npm run deploy:pro -- --appId=wx1e8e9dc2e337b821','npm run deploy:pro -- --appId=wx7493b6cfe63e360e','npm run deploy:pro -- --appId=wx7c7c8a0e9e242133','npm run deploy:pro -- --appId=wxfc6898107cb428a7','npm run deploy:pro -- --appId=wx4fc01c82126749bc','npm run deploy:pro -- --appId=wx842b1d5e54ddff47'
]
index = 0;
# 要執行的shell命令
for i in commandList:# 使用subprocess.run來執行命令,cwd參數指定工作目錄result = subprocess.run(i, cwd=directory, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)# 打印命令的輸出和錯誤信息print(result.stdout) # 命令的輸出print(result.stderr) # 命令的錯誤信息index += 1
print("一共:%s" % index)
雖然沒有完全解決重復的問題,10萬日活還在遠遠的招手,小程序還得繼續注冊,但這事兒還在心里萌芽著。
日活上來了,重復還在繼續
為了解決若干小程序界面一樣的問題,可能會被下線,又風風火火的做了多個模板。維護變得愈發難了,難是發一次版本都會成為一次浩大的工程。如果更新20個程序,必須要登陸20次mp后臺,選賬號都會成為一個難點,見圖可知。
苦不堪言時,終于想起了之前參加微信生態線下交流時,一個同學提到的服務商模式,之前覺得接入成本也挺高,就放下了,現在已是非常時期,抽出一個周末開始了摸索。
撥云見日,終覓良方
注冊開放平臺,創建第三方平臺應用,綁定小程序,上傳草稿箱,設置普通模板,提交審核,上線… 2天時間終于摸索得7788了。幾乎所有操作都可以通過接口完成,比如設置域名,設置隱私,提交審核,甚至上線…有了接口就可以配置自動化了,直接使用apifox的編排能力,60分鐘搞定配置。這一套下來,直接節省了90%的工作。
管理100小程序真不難了
有了上面的一套配置,配合模板庫,對應不同的版本。發布變得非常輕松,根本不用登陸MP后臺,輕松管理100個小程序,甚至可以說多多益善。這個過程大概經歷了一個月,回頭來看,也許正是困難讓我們更強大。恰巧最近在讀老舍先生的《駱駝祥子》,連在地府都可以當個好鬼兒的祥子,卻沒能夠從苦難中強大起來,著實可惜了。最后來一張效果圖,接口自動化提交審核,發布;統一的平臺管理所有的小程序,管理100個小程序就是這么簡單。有興趣的朋友可以體驗下
官方不讓放二維碼,只能放一個鏈接了。