├── views ├── index.jade ├── error.jade └── layout.jade ├── public └── stylesheets │ └── style.css ├── config.js ├── routes ├── users.js ├── index.js └── appApi.js ├── package.json ├── db ├── model │ └── version.js └── mongoose ├── app.js ├── .gitignore ├── bin └── www ├── README.md └── controllers └── appApi.js /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | -------------------------------------------------------------------------------- /views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content 8 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | zipPath:'/Users/tangsicheng/myCode/learn/weex/home-project/home-zip-source/home-app-eros', 3 | cliPath:'/Users/tangsicheng/myCode/my_github_code/private/home-project/home-app-eros/dist/js' 4 | } -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | router.get('/a', function(req, res, next) { 9 | res.send('respond with a a'); 10 | }); 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | var fs = require('fs'); 4 | var config = require('../config') 5 | var path = require('path') 6 | var mime = require('mime'); 7 | 8 | /* GET home page. */ 9 | router.get('/', function(req, res, next) { 10 | res.render('index', { title: 'Express' }); 11 | }); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /routes/appApi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tangsicheng on 2018/1/30. 3 | */ 4 | var express = require('express'); 5 | var router = express.Router(); 6 | var appApi = require('../controllers/appApi'); 7 | // 8 | /* GET users listing. */ 9 | router.get('/downloadIncrementZip/:zipName', appApi.downloadIncrementZip); 10 | 11 | router.post('/add',appApi.add); 12 | 13 | router.get('/check', appApi.check); 14 | 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "home-node-express", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.17.1", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.6.3", 12 | "express": "~4.15.2", 13 | "fs": "0.0.1-security", 14 | "jade": "~1.11.0", 15 | "mime": "^1.4.0", 16 | "moment": "^2.19.1", 17 | "js-md5": "^0.6.1", 18 | "mongoose": "^4.2.9", 19 | "morgan": "~1.8.1", 20 | "path": "^0.12.7", 21 | "serve-favicon": "~2.4.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /db/model/version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tangsicheng on 2018/1/30. 3 | */ 4 | var mongoose = require("../mongoose"), 5 | moment = require("moment") 6 | var modelSchema = new mongoose.Schema({ 7 | appName: { type: String, require: true }, 8 | jsPath: { type: String, require: true }, 9 | iOS: { type: String, require: true }, 10 | android: { type: String, require: true }, 11 | jsVersion: { type: String, require: true }, 12 | timestamp: {type: Number, require: true }, 13 | createTime: {type: String, default: moment().format('YYYY-MM-DD h:m:s')} 14 | }) 15 | 16 | modelSchema.methods.findbyAppName = (appName, callback) => { 17 | this.model('app').find({ appName }, callback) 18 | } 19 | 20 | var version = mongoose.model("version", modelSchema) 21 | module.exports = version -------------------------------------------------------------------------------- /db/mongoose: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tangsicheng on 2017/12/28. 3 | */ 4 | /** 5 | * https://www.cnblogs.com/zhongweiv/p/mongoose.html 6 | */ 7 | var mongoose = require("mongoose"); 8 | var DB_URL = 'mongodb://localhost/home' 9 | /** 10 | * 连接 11 | */ 12 | mongoose.connect(DB_URL); 13 | /** 14 | * 更多监听事件,可以查看http://mongoosejs.com/docs/api.html#connection_Connection 15 | */ 16 | /** 17 | * 连接成功 18 | */ 19 | mongoose.connection.on('connected',function () { 20 | console.log('Mongoose connection open to' + DB_URL); 21 | }) 22 | /** 23 | * 连接异常 24 | */ 25 | mongoose.connection.on('error',function (err) { 26 | console.log('Mongoose connection error :'+err); 27 | }) 28 | /** 29 | * 连接断开 30 | */ 31 | mongoose.connection.on('disconnected',function () { 32 | console.log('Mongoose connection disconnected'); 33 | }) 34 | 35 | module.exports = mongoose; -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var index = require('./routes/index'); 9 | var users = require('./routes/users'); 10 | var appApi = require('./routes/appApi'); 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'jade'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', index); 26 | app.use('/users', users); 27 | app.use('/app', appApi); 28 | // app.use('/test', require('./routes/test')); 29 | 30 | // catch 404 and forward to error handler 31 | app.use(function(req, res, next) { 32 | var err = new Error('Not Found'); 33 | err.status = 404; 34 | next(err); 35 | }); 36 | 37 | // error handler 38 | app.use(function(err, req, res, next) { 39 | // set locals, only providing error in development 40 | res.locals.message = err.message; 41 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 42 | 43 | // render the error page 44 | res.status(err.status || 500); 45 | res.render('error'); 46 | }); 47 | 48 | module.exports = app; 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Xcode template 3 | # Xcode 4 | # 5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xccheckout 25 | *.xcscmblueprint 26 | ### Example user template template 27 | ### Example user template 28 | 29 | # IntelliJ project files 30 | .idea 31 | *.iml 32 | out 33 | gen### Node template 34 | # Logs 35 | logs 36 | *.log 37 | npm-debug.log* 38 | yarn-debug.log* 39 | yarn-error.log* 40 | 41 | # Runtime data 42 | pids 43 | *.pid 44 | *.seed 45 | *.pid.lock 46 | 47 | # Directory for instrumented libs generated by jscoverage/JSCover 48 | lib-cov 49 | 50 | # Coverage directory used by tools like istanbul 51 | coverage 52 | 53 | # nyc test coverage 54 | .nyc_output 55 | 56 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 57 | .grunt 58 | 59 | # Bower dependency directory (https://bower.io/) 60 | bower_components 61 | 62 | # node-waf configuration 63 | .lock-wscript 64 | 65 | # Compiled binary addons (http://nodejs.org/api/addons.html) 66 | build/Release 67 | 68 | # Dependency directories 69 | node_modules/ 70 | jspm_packages/ 71 | 72 | # Typescript v1 declaration files 73 | typings/ 74 | 75 | # Optional npm cache directory 76 | .npm 77 | 78 | # Optional eslint cache 79 | .eslintcache 80 | 81 | # Optional REPL history 82 | .node_repl_history 83 | 84 | # Output of 'npm pack' 85 | *.tgz 86 | 87 | # Yarn Integrity file 88 | .yarn-integrity 89 | 90 | # dotenv environment variables file 91 | .env 92 | 93 | 94 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('home-node-express:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3001'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eros热更新服务端(express+mongodb) 2 | 3 | 说明: 4 | ==================================== 5 | 本demo 是针对[eros-template](https://github.com/bmfe/eros-template)(一个针对weex进行二次封装的非常优秀的框架)做的一个check版本热更新的服务端,基于[eros-publish](https://github.com/bmfe/eros-publish)做了一些小改动和添加了一些小功能。 6 | 7 | 如果对你有用的话,请右上角点个star 8 | 9 | 10 | 使用: 11 | ==================================== 12 | 13 | 下载 14 | ---------------------------------------- 15 | 使用git从[eros-node-server](https://github.com/shawn-tangsc/eros-node-server)主页下载项目 16 | 17 | ``` bash 18 | git clone https://github.com/shawn-tangsc/eros-node-server 19 | ``` 20 | 前期准备 21 | ---------------------------------------- 22 | + 下列是mac下环境执行的语法,需要先安装[homebrew](https://brew.sh/),linux下同理用相应的包管理工具下载。 23 | 24 | ``` 25 | brew install bsdiff 26 | brew install bspatch 27 | ``` 28 | 29 | 30 | 初始化 31 | ---------------------------------------- 32 | 执行: 33 | 34 | ``` bash 35 | cd eros-node-server 36 | npm install 37 | ``` 38 | 39 | 40 | 修改配置文件 41 | ---------------------------------------- 42 | 1.在本项目中修改db/mongoose 下的mongodb的ip和port 为本地local path 43 | 44 | ``` bash 45 | var mongoose = require("mongoose"); 46 | var DB_URL = 'mongodb://localhost/home' 47 | ``` 48 | 49 | 2.修改根目录中的config.js 中的zipPath! 50 | 51 | + 下面这个路径配置的是你差分包和全量包里面的路径,可以自己去查一下 52 | 53 | ``` bash 54 | module.exports = { 55 | zipPath: 56 | } 57 | ``` 58 | 59 | 60 | + 下面去修改你eros-template 里面的-----》eros.dev.js 61 | 62 | ``` 63 | 'diff': { 64 | 'pwd': <你希望全量包放的位置,这里需要注意,他会在你指定的目录下多新建一个目录>, 65 | 'proxy': `<本项目启动后的url或者ip>:3001/app/downloadIncrementZip` 66 | }, 67 | ``` 68 | 69 | + 下面去修改你eros-template 里面的-----》eros.native.js 70 | 71 | ``` 72 | 'url': { 73 | ... 74 | 'bundleUpdate': `<本项目启动后的url或者ip>:3001/app/check`, 75 | ... 76 | }, 77 | ``` 78 | 79 | 生成差分包和全量包 80 | --------- 81 | 82 | + 在你的eros-template 目录根据实际情况执行,eros会将你的包放到你指定的目录下面 83 | 84 | ``` 85 | eros build //生成全量包 86 | eros build -d //生成差分包 87 | ``` 88 | + 这里要注意:并不会去保存差分包信息到服务器上! 89 | 90 | 每次有新版本后,你在执行 91 | ``` 92 | eros build -d 93 | ``` 94 | 的时候系统会自动去你配置的diff.pwd所指定的路径下,根据当前最新的包去生成和目标目录下的所有全量包之间的差分包,所以你需要在本项目根目录中config.js中配置 95 | 96 | ``` 97 | module.exports = { 98 | ... 99 | cliPath:'/Users/tangsicheng/myCode/my_github_code/private/home-project/home-app-eros/dist/js' 100 | } 101 | ``` 102 | 去告诉本服务器,如果客户端请求的是差分包,应该去哪里找。 103 | 104 | 105 | 启动服务器 106 | ---------------------------------------- 107 | 108 | + 在本项目的目录下,执行下列命令,这会给你在本地启动一个端口为3001的服务器 109 | 110 | ``` 111 | npm run start 112 | ``` 113 | 114 | + 启动mongodb的本地服务 115 | 116 | ``` 117 | mongod 118 | ``` 119 | 120 | 将eros全量包信息保存到服务器 121 | ---------------- 122 | + 执行下列命令,可以让你将eros-template里面之前生成的全量包的信息(/dist/version.json)保存的服务器上面, 123 | 124 | ``` 125 | eros build -s http://localhost:3001/app/add 126 | ``` 127 | 128 | 129 | 测试 130 | ---------------------------------------- 131 | 132 | + 然后打开你eros项目中platform对应的移动端项目就可以直接在模拟器或真机上测试热更新了。。 133 | 134 | --- 135 |
©2017 Shawn TangSiCheng
136 | 137 | -------------------------------------------------------------------------------- /controllers/appApi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tangsicheng on 2018/1/30. 3 | */ 4 | var config = require('../config'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var mime = require('mime'); 8 | var Version = require('../db/model/version'); 9 | var md5 = require('js-md5') 10 | 11 | const format = ({resCode = 0, msg = 'success', data = {}}) => { 12 | return { 13 | resCode, 14 | msg, 15 | data 16 | } 17 | } 18 | 19 | const requestZip = ({res, apps, appName, platform, version, jsVersion, isDiff, next}) => { 20 | getNewestInfo({ appName, platform, version}).then(newests => { 21 | if (!newests || !newests.length) { 22 | console.log('error'); 23 | var err = new Error('Not Found'); 24 | err.status = 500; 25 | next(err); 26 | } 27 | if(isDiff == 0 || isDiff === 'false' || isDiff === false) { 28 | console.log('请求全量包'); 29 | // 请求全量包 30 | res.send(format({ 31 | msg: "请求全量包成功", 32 | data: { 33 | diff: false, 34 | path: `${newests[0].jsPath}/${newests[0].jsVersion}.zip` 35 | } 36 | })) 37 | return 38 | } 39 | // 请求差分包 40 | if(!apps.length){ 41 | // 不存在jsVersion 当前包信息可能被篡改 直接返回最新版本全量包 42 | console.log('不存在jsVersion 当前包信息可能被篡改 直接返回最新版本全量包') 43 | res.send(format({ 44 | msg: "jsVersion 不存在", 45 | data: { 46 | diff: false, 47 | path: `${newests[0].jsPath}/${newests[0].jsVersion}.zip` 48 | } 49 | })) 50 | }else { 51 | if(newests[0].jsVersion === jsVersion) { 52 | // 存在 jsVersion 并且是最新 53 | res.send(format({ 54 | resCode:4000, 55 | msg: "当前版本已是最新,不需要更新" 56 | })) 57 | } else { 58 | // 存在 jsVersion 但不是最新 59 | console.log('存在 jsVersion 但不是最新') 60 | res.send(format({ 61 | msg: "当前版本需要更新", 62 | data: { 63 | diff: true, 64 | jsVersion: newests[0].jsVersion, 65 | path: `${newests[0].jsPath}/${md5(jsVersion + newests[0].jsVersion)}.zip` 66 | } 67 | })) 68 | } 69 | } 70 | }) 71 | } 72 | /** 73 | * 通过app的名字和设备对应的版本号,返回相应的数据 74 | * 75 | * */ 76 | const getNewestInfo = ({appName, platform, version}) => { 77 | let params = { 78 | appName 79 | } 80 | params[platform] = version; 81 | return Version.find(params).sort({ timestamp : 'desc'}); 82 | 83 | 84 | } 85 | 86 | const APPAPI = {}; 87 | APPAPI.downloadIncrementZip = (req, res, next) => { 88 | 89 | var zipName = req.param('zipName'); 90 | //TODO 这里应该做一个非空判断,如果为空,应该去error 91 | let file = config.zipPath+'/'+zipName; 92 | let diffFile = config.cliPath + '/' +zipName; 93 | console.log(file); 94 | fs.exists(file,(isExist)=>{ 95 | "use strict"; 96 | if(isExist){ 97 | var filename = path.basename(file); 98 | var mimetype = mime.lookup(file); //匹配文件格式 99 | 100 | res.setHeader('Content-disposition', 'attachment; filename=' + filename); 101 | res.setHeader('Content-type', mimetype); 102 | var filestream = fs.createReadStream(file) 103 | 104 | filestream.on('data', function(chunk) { 105 | res.write(chunk); 106 | }); 107 | filestream.on('end', function() { 108 | res.end(); 109 | }); 110 | } else { 111 | fs.exists(diffFile,(isExist)=>{ 112 | if(isExist){ 113 | var filename = path.basename(diffFile); 114 | var mimetype = mime.lookup(diffFile); //匹配文件格式 115 | 116 | res.setHeader('Content-disposition', 'attachment; filename=' + filename); 117 | res.setHeader('Content-type', mimetype); 118 | var filestream = fs.createReadStream(diffFile) 119 | 120 | filestream.on('data', function(chunk) { 121 | res.write(chunk); 122 | }); 123 | filestream.on('end', function() { 124 | res.end(); 125 | }); 126 | }else { 127 | console.log('error'); 128 | var err = new Error('Not Found'); 129 | err.status = 500; 130 | return next(err); 131 | } 132 | }) 133 | } 134 | }) 135 | }; 136 | 137 | APPAPI.add = (req, res, next) => { 138 | console.log(req.body); 139 | let version = new Version(req.body) 140 | 141 | version.save(() => { 142 | res.send(format({ 143 | data: 'success' 144 | })) 145 | }) 146 | }; 147 | 148 | APPAPI.check = (req, res, next) => { 149 | let { appName, jsVersion, isDiff = true } = req.query, 150 | platform = !!req.query.iOS ? 'iOS': 'android', 151 | version = req.query[platform], 152 | checkParams = { 153 | appName, 154 | } 155 | console.log('app>>>>>>>'+appName); 156 | if(jsVersion) checkParams['jsVersion'] = jsVersion 157 | checkParams[platform] = version 158 | Version.find(checkParams, (err, apps) =>{ 159 | if (err) { return next(err) } 160 | requestZip({ 161 | res, apps, appName, platform, version, jsVersion, isDiff, next 162 | }) 163 | }) 164 | }; 165 | 166 | module.exports = APPAPI; --------------------------------------------------------------------------------