diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 32e678eb..00000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -*.js linguist-language=go -*.css linguist-language=go -*.html linguist-language=go \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7d045a54..8af60b58 100755 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ dist .idea *.swp .bashrc -debug \ No newline at end of file +debug +wemall \ No newline at end of file diff --git a/configuration.json b/configuration.json index 669dc163..2b490c33 100644 --- a/configuration.json +++ b/configuration.json @@ -28,17 +28,18 @@ "go": { "Debug" : true, /*是否是debug模式*/ "UploadImgDir" : "/Users/liushen/dev/uploads/img", /*图片上传的目录*/ - "ImgPath" : "/img", /*上传后的图片请求地址前缀*/ - "Port" : 8012, /*go监听的端口*/ - "MaxOrder" : 10000, /*最大的排序号*/ - "MinOrder" : 0, /*最小的排序号*/ - "PageSize" : 20, /*默认每页的条数*/ - "MaxPageSize" : 100, /*每页最大的条数*/ - "MinPageSize" : 20, /*每页最小的条数*/ - "MaxNameLen" : 100, /*最大的名称长度*/ - "MaxRemarkLen" : 500, /*最大的备注长度*/ - "MaxContentLen" : 10000, /*最大的内容长度*/ - "MaxProductCateCount" : 6 /*产品最多的分类个数*/ + "ImgPath" : "/img", /*上传后的图片请求地址前缀*/ + "Port" : 8012, /*go监听的端口*/ + "SessionID" : "iris.sid", /*后台设置的session id*/ + "MaxOrder" : 10000, /*最大的排序号*/ + "MinOrder" : 0, /*最小的排序号*/ + "PageSize" : 20, /*默认每页的条数*/ + "MaxPageSize" : 100, /*每页最大的条数*/ + "MinPageSize" : 20, /*每页最小的条数*/ + "MaxNameLen" : 100, /*最大的名称长度*/ + "MaxRemarkLen" : 500, /*最大的备注长度*/ + "MaxContentLen" : 10000, /*最大的内容长度*/ + "MaxProductCateCount" : 6 /*产品最多的分类个数*/ }, "software": { "name" : "wemall微商城", /*软件名称*/ diff --git a/configuration.prod.json b/configuration.prod.json new file mode 100644 index 00000000..829c0350 --- /dev/null +++ b/configuration.prod.json @@ -0,0 +1,56 @@ +{ + "webPoweredBy": "wemall", /*前端node.js加的X-Powered-By*/ + "apiPoweredBy": "wemall api", /*后台go加的X-Powered-By*/ + "database": { + "Dialect" : "mysql", + "Database" : "wemall", + "User" : "root", + "Password" : "test1234", + "Charset" : "utf8", + "SQLLog" : true, /*是否输出SQL*/ + "URL" : "" /*数据库连接地址*/ + }, + "nodejs": { + "page": { + "title" : "wemall-微商城", /*网站标题*/ + "sitePath" : "", /*网站前缀,适用于子应用的场景*/ + "jsPath" : "/javascripts", /*前端JS请求地址前缀*/ + "imagePath" : "/images", /*前端图片请求地址前缀*/ + "cssPath" : "/styles", /*前端css请求地址前缀*/ + "ueditorURL" : "/ueditor/" /*前端ueditor请求地址前缀*/ + }, + "env" : "production", /*模式(开发,测试,产品)*/ + "useProxy" : false, /*node.js发请求是否使用代理*/ + "proxyUri" : "http://127.0.0.1:8881", /*代理地址及端口*/ + "port" : 8010, /*前端node.js监听的端口*/ + "staticPort" : 8011 /*前端静态文件服务器监听的端口(本地开发时使用)*/ + }, + "go": { + "Debug" : true, /*是否是debug模式*/ + "UploadImgDir" : "/Users/liushen/dev/uploads/img", /*图片上传的目录*/ + "ImgPath" : "/img", /*上传后的图片请求地址前缀*/ + "Port" : 8012, /*go监听的端口*/ + "SessionID" : "iris.sid", /*后台设置的session id*/ + "MaxOrder" : 10000, /*最大的排序号*/ + "MinOrder" : 0, /*最小的排序号*/ + "PageSize" : 20, /*默认每页的条数*/ + "MaxPageSize" : 100, /*每页最大的条数*/ + "MinPageSize" : 20, /*每页最小的条数*/ + "MaxNameLen" : 100, /*最大的名称长度*/ + "MaxRemarkLen" : 500, /*最大的备注长度*/ + "MaxContentLen" : 10000, /*最大的内容长度*/ + "MaxProductCateCount" : 6 /*产品最多的分类个数*/ + }, + "software": { + "name" : "wemall微商城", /*软件名称*/ + "version" : "1.0.0", /*软件版本*/ + "officialURL" : "https://www.shen100.com" /*官网地址*/ + }, + "api": { + "Prefix" : "/api", /*api服务请求前缀*/ + "URL" : "http://127.0.0.1:8012/api" /*api服务请求地址(给node.js调用)*/ + }, + "docs": { + "github": "https://github.com/shen100/wemall" /*wemall项目github地址*/ + } +} \ No newline at end of file diff --git "a/docs/api/\345\220\216\345\217\260\347\256\241\347\220\206/\347\263\273\347\273\237\346\246\202\345\206\265.md" "b/docs/api/\345\220\216\345\217\260\347\256\241\347\220\206/\347\263\273\347\273\237\346\246\202\345\206\265.md" deleted file mode 100755 index 9fa49963..00000000 --- "a/docs/api/\345\220\216\345\217\260\347\256\241\347\220\206/\347\263\273\347\273\237\346\246\202\345\206\265.md" +++ /dev/null @@ -1,175 +0,0 @@ -## /admin/overview/user/30d -返回近30天,每天注册的用户数 - -### HTTP请求方法 -GET - -### 是否需要登录 -是 - -### 返回结果 - -``` -{ - "msg": "success", - "errNo": 0, - "data": { - "users": [ - { - "count": 1, - "date": { - "year": 2017, - "month": 1, - "date": 11 - } - }, - { - "count": 3, - "date": { - "year": 2017, - "month": 1, - "date": 12 - } - } - ] - } -} -``` - -### 返回字段说明 -| 返回值字段 | 字段类型 | 字段说明 | -|:--------|---------:|:-------:| -| count | int | 当天注册的新用户数 | -| date | object | 日期 | - - -## /admin/overview/user/sale/30d -返回近30天,每天有消费形为的用户数 - -### HTTP请求方法 -GET - -### 是否需要登录 -是 - -### 返回结果 - -``` -{ - "msg": "success", - "errNo": 0, - "data": { - "users": [ - { - "count": 1, - "date": { - "year": 2017, - "month": 1, - "date": 11 - } - }, - { - "count": 2, - "date": { - "year": 2017, - "month": 1, - "date": 12 - } - } - ] - } -} -``` - -### 返回字段说明 -| 返回值字段 | 字段类型 | 字段说明 | -|:--------|---------:|:-------:| -| count | int | 当天有消费形为的用户数 | -| date | object | 日期 | - - -## /admin/overview/order/30d -返回近30天,每天的订单数 - -### HTTP请求方法 -GET - -### 是否需要登录 -是 - -### 返回结果 - -``` -{ - "msg": "success", - "errNo": 0, - "data": { - "orders": [ - { - "count": 1, - "date": { - "year": 2017, - "month": 1, - "date": 10 - } - }, - { - "count": 2, - "date": { - "year": 2017, - "month": 1, - "date": 11 - } - } - ] - } -} -``` - -| 返回值字段 | 字段类型 | 字段说明 | -|:--------|---------:|:-------:| -| count | int | 当天的订单数 | -| date | object | 日期 | - - -## /admin/overview/sale/30d -返回近30天,每天的销售额 - -### HTTP请求方法 -GET - -### 是否需要登录 -是 - -### 返回结果 -``` -{ - "msg": "success", - "errNo": 0, - "data": { - "sales": [ - { - "amount": 22, - "date": { - "year": 2017, - "month": 1, - "date": 10 - } - }, - { - "amount": 78.9, - "date": { - "year": 2017, - "month": 1, - "date": 11 - } - } - ] - } -} -``` - -| 返回值字段 | 字段类型 | 字段说明 | -|:--------|---------:|:-------:| -| amount | number | 当天的销售额 | -| date | object | 日期 | \ No newline at end of file diff --git "a/docs/nginx\351\205\215\347\275\256.md" "b/docs/nginx\351\205\215\347\275\256.md" new file mode 100644 index 00000000..88c9e0ce --- /dev/null +++ "b/docs/nginx\351\205\215\347\275\256.md" @@ -0,0 +1,13 @@ +# nginx配置 + +## 开启gzip压缩 + +``` +gzip on; +gzip_min_length 10k; +gzip_buffers 4 16k; +gzip_comp_level 2; +gzip_types application/x-javascript text/javascript application/javascript text/plain text/css application/json application/xml image/jpeg image/gif image/png; +gzip_vary off; +gzip_disable "MSIE [1-6]\."; +``` \ No newline at end of file diff --git a/go/config/config.go b/go/config/config.go index 14117d91..0e1cb354 100644 --- a/go/config/config.go +++ b/go/config/config.go @@ -56,7 +56,7 @@ type serverConfig struct { ImgPath string UploadImgDir string Port int - StaticPort int + SessionID string MaxOrder int MinOrder int PageSize int diff --git a/go/controller/user/user.go b/go/controller/user/user.go index b4931dcc..ff6ca3c2 100644 --- a/go/controller/user/user.go +++ b/go/controller/user/user.go @@ -1,9 +1,13 @@ package user import ( + "fmt" "time" "gopkg.in/kataras/iris.v6" + "github.com/jinzhu/gorm" + "wemall/go/config" "wemall/go/model" + "wemall/go/utils" ) // YesterdayRegisterUser 昨日注册的用户数 @@ -77,4 +81,71 @@ func Analyze(ctx *iris.Context) { "msg" : "success", "data" : data, }) +} + +// Login 用户登录 +func Login(ctx *iris.Context) { + var user model.User + session := ctx.Session() + + session.Get("user") + + if err := ctx.ReadJSON(&user); err != nil { + fmt.Println(err.Error()); + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "参数无效", + "data" : iris.Map{}, + }) + return + } + + hash, pwdErr := utils.HashPassword(user.Password) + + if pwdErr != nil { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.LoginError, + "msg" : "用户名或密码错误", + "data" : iris.Map{}, + }) + } + + db, connErr := gorm.Open(config.DBConfig.Dialect, config.DBConfig.URL) + if connErr != nil { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + + defer db.Close() + var queryUser model.User + + err := db.Model(&queryUser).Where("password = ?", user.Email).Error + + if err != nil { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + + if utils.CheckPasswordHash(queryUser.Password, hash) { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.SUCCESS, + "msg" : "success", + "data" : iris.Map{}, + }) + } else { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.LoginError, + "msg" : "用户名或密码错误", + "data" : iris.Map{}, + }) + return + } } \ No newline at end of file diff --git a/go/model/errorcode.go b/go/model/errorcode.go index fa1b0aab..d6ed785f 100644 --- a/go/model/errorcode.go +++ b/go/model/errorcode.go @@ -1,16 +1,18 @@ package model type errorCode struct { - SUCCESS int - ERROR int - NotFound int + SUCCESS int + ERROR int + NotFound int + LoginError int } // ErrorCode 错误码 var ErrorCode = errorCode{ - SUCCESS : 0, - ERROR : 1, - NotFound : 404, + SUCCESS : 0, + ERROR : 1, + NotFound : 404, + LoginError : 1000, //用户名或密码错误 } diff --git a/go/model/order.go b/go/model/order.go index ab922f36..0a95007e 100644 --- a/go/model/order.go +++ b/go/model/order.go @@ -136,7 +136,7 @@ type OrderPerDay []struct { // Latest30Day 近30天,每天的订单数 func (orders OrderPerDay) Latest30Day() (OrderPerDay) { - now := time.Now() + now := time.Now() year := now.Year() month := now.Month() date := now.Day() diff --git a/go/model/product.go b/go/model/product.go index b092943e..d164d423 100644 --- a/go/model/product.go +++ b/go/model/product.go @@ -5,9 +5,9 @@ import "time" // Product 商品 type Product struct { ID uint `gorm:"primary_key" json:"id"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - DeletedAt *time.Time `sql:"index" json:"deletedAt"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt *time.Time `sql:"index" json:"deletedAt"` Name string `json:"name"` BrowseCount int `json:"browseCount"` BuyCount int `json:"buyCount"` diff --git a/go/model/user.go b/go/model/user.go index a03c7bf5..e8dd78e8 100644 --- a/go/model/user.go +++ b/go/model/user.go @@ -10,20 +10,21 @@ import ( // User 用户 type User struct { ID uint `gorm:"primary_key" json:"id"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - DeletedAt *time.Time `sql:"index" json:"deletedAt"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt *time.Time `sql:"index" json:"deletedAt"` ContactID string `json:"contactId"` //默认地址 OpenID string `json:"openId"` Nickname string `json:"nickname"` Username string `json:"username"` + Email string `json:"email"` Phone string `json:"phone"` Password string `json:"password"` Token string `json:"token"` Sex bool `json:"sex"` Subscribe bool `json:"subscribe"` - Status int `json:"status"` - Lastip string `json:"lastip"` + Status int `json:"status"` + Lastip string `json:"lastip"` } // YesterdayRegisterUser 昨日注册的用户数 diff --git a/go/utils/utils.go b/go/utils/utils.go index b1a4912c..a2f58529 100644 --- a/go/utils/utils.go +++ b/go/utils/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "errors" + "golang.org/x/crypto/bcrypt" ) func setField(obj interface{}, name string, value interface{}) error { @@ -45,30 +46,31 @@ func SetStructByJSON(obj interface{}, mapData map[string]interface{}) error { // StrToIntMonth 字符串月份转整数月份 func StrToIntMonth(month string) int { - fmt.Println(month) var data = map[string]int{ - "May": 4, + "January" : 0, + "February" : 1, + "March" : 2, + "April" : 3, + "May" : 4, + "June" : 5, + "July" : 6, + "August" : 7, + "September" : 8, + "October" : 9, + "November" : 10, + "December" : 11, }; return data[month]; } - func SubString(str string, begin, length int) (substr string) { - // 将字符串的转换成[]rune - rs := []rune(str) - len := len(rs) - - // 简单的越界判断 - if begin < 0 { - begin = 0 - } - if begin >= len { - begin = len - } - end := begin + length - if end > len { - end = len - } - - // 返回子串 - return string(rs[begin:end]) -} \ No newline at end of file +// HashPassword 将密码加密 +func HashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) + return string(bytes), err +} + +// CheckPasswordHash 验证密码 +func CheckPasswordHash(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 6ad2f1ba..00000000 --- a/gulpfile.js +++ /dev/null @@ -1,275 +0,0 @@ -/** - * [gulp description] - * @type {[type]} - */ -var gulp = require("gulp"); -var gutil = require("gulp-util"); -var del = require("del"); -var rename = require('gulp-rename'); -var less = require('gulp-less'); -var autoprefixer = require('gulp-autoprefixer'); -var cached = require('gulp-cached'); -var remember = require('gulp-remember'); - -var webpack = require("webpack"); -var WebpackDevServer = require("webpack-dev-server"); -var webpackConfig = require("./webpack.config.js"); - -var connect = require('gulp-connect'); -var rest = require('connect-rest'); -var mocks = require('./mocks'); - -/** - * ---------------------------------------------------- - * source configuration - * ---------------------------------------------------- - */ - -var src = { - html: "src/html/*.html", // html 文件 - vendor: ["vendor/**/*", "bower_components/**/*"], // vendor 目录和 bower_components - style: "src/style/*/index.less", // style 目录下所有 xx/index.less - assets: "assets/**/*" // 图片等应用资源 -}; - -var dist = { - root: "dist/", - html: "dist/", - style: "dist/style", - vendor: "dist/vendor", - assets: "dist/assets" -}; - -var bin = { - root: "bin/", - html: "bin/", - style: "bin/style", - vendor: "bin/vendor", - assets: "bin/assets" -}; - -/** - * ---------------------------------------------------- - * tasks - * ---------------------------------------------------- - */ - -/** - * clean build dir - */ -function clean(done) { - del.sync(dist.root); - done(); -} - -/** - * [cleanBin description] - * @return {[type]} [description] - */ -function cleanBin(done) { - del.sync(bin.root); - done(); -} - -/** - * [copyVendor description] - * @return {[type]} [description] - */ -function copyVendor() { - return gulp.src(src.vendor) - .pipe(gulp.dest(dist.vendor)); -} - -/** - * [copyAssets description] - * @return {[type]} [description] - */ -function copyAssets() { - return gulp.src(src.assets) - .pipe(gulp.dest(dist.assets)); -} - -/** - * [copyDist description] - * @return {[type]} [description] - */ -function copyDist() { - return gulp.src(dist.root + '**/*') - .pipe(gulp.dest(bin.root)); -} - -/** - * [html description] - * @return {[type]} [description] - */ -function html() { - return gulp.src(src.html) - .pipe(gulp.dest(dist.html)) -} - -/** - * [style description] - * @param {Function} done [description] - * @return {[type]} [description] - */ -function style() { - return gulp.src(src.style) - .pipe(cached('style')) - .pipe(less()) - .on('error', handleError) - .pipe(autoprefixer({ - browsers: ['last 3 version'] - })) - .pipe(gulp.dest(dist.style)) -} - -exports.style = style; - -/** - * [webpackProduction description] - * @param {Function} done [description] - * @return {[type]} [description] - */ -function webpackProduction(done) { - var config = Object.create(webpackConfig); - config.plugins = config.plugins.concat( - new webpack.DefinePlugin({ - "process.env": { - "NODE_ENV": "production" - } - }), - new webpack.optimize.DedupePlugin(), - new webpack.optimize.UglifyJsPlugin() - ); - - webpack(config, function(err, stats) { - if(err) throw new gutil.PluginError("webpack:build", err); - gutil.log("[webpack:production]", stats.toString({ - colors: true - })); - done(); - }); -} - - -/** - * [webpackDevelopment description] - * @param {Function} done [description] - * @return {[type]} [description] - */ -var devConfig, devCompiler; - -devConfig = Object.create(webpackConfig); -devConfig.devtool = "sourcemap"; -devConfig.debug = true; -devCompiler = webpack(devConfig); - -function webpackDevelopment(done) { - devCompiler.run(function(err, stats) { - if (err) { - throw new gutil.PluginError("webpack:build-dev", err); - return; - } - gutil.log("[webpack:build-dev]", stats.toString({ - colors: true - })); - done(); - }); -} - -/** - * webpack develop server - */ -// devConfig.plugins = devConfig.plugins || [] -// devConfig.plugins.push(new webpack.HotModuleReplacementPlugin()) -// function webpackDevelopmentServer(done) { -// new WebpackDevServer(devCompiler, { -// contentBase: dist.root, -// lazy: false, -// hot: true -// }).listen(8080, 'localhost', function (err) { -// if (err) throw new gutil.PluginError('webpack-dev-server', err) -// gutil.log('[webpack-dev-server]', 'http://localhost:8080/') -// reload(); -// done(); -// }); -// } - -/** - * [connectServer description] - * @return {[type]} [description] - */ -function connectServer(done) { - connect.server({ - root: dist.root, - port: 8080, - livereload: true, - middleware: function(connect, opt) { - return [rest.rester({ - context: "/" - })] - } - }); - mocks(rest); - done(); -} - -/** - * [watch description] - * @return {[type]} [description] - */ -function watch() { - gulp.watch(src.html, html); - gulp.watch("src/**/*.js", webpackDevelopment); - gulp.watch("src/**/*.less", style); - gulp.watch("dist/**/*").on('change', function(file) { - gulp.src('dist/') - .pipe(connect.reload()); - }); -} - -/** - * default task - */ -gulp.task("default", gulp.series( - clean, - gulp.parallel(copyAssets, copyVendor, html, style, webpackDevelopment), - connectServer, - watch -)); - -/** - * production build task - */ -gulp.task("build", gulp.series( - clean, - gulp.parallel(copyAssets, copyVendor, html, style, webpackProduction), - cleanBin, - copyDist, - function(done) { - console.log('build success'); - done(); - } -)); - -/** - * [handleError description] - * @param {[type]} err [description] - * @return {[type]} [description] - */ -function handleError(err) { - if (err.message) { - console.log(err.message) - } else { - console.log(err) - } - this.emit('end') -} - -/** - * [reload description] - * @return {[type]} [description] - */ -function reload() { - connect.reload(); -} \ No newline at end of file diff --git a/main.go b/main.go index c3e61b8a..61644205 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( _ "github.com/jinzhu/gorm/dialects/mysql" "gopkg.in/kataras/iris.v6" "gopkg.in/kataras/iris.v6/adaptors/httprouter" + "gopkg.in/kataras/iris.v6/adaptors/sessions" "strconv" "wemall/go/config" "wemall/go/model" @@ -25,8 +26,13 @@ func main() { if config.ServerConfig.Debug { app.Adapt(iris.DevLogger()) } + app.Adapt(httprouter.New()) + app.Adapt(sessions.New(sessions.Config{ + Cookie: config.ServerConfig.SessionID, + })) + apiPrefix := config.APIConfig.Prefix router := app.Party(apiPrefix) @@ -85,3 +91,5 @@ func main() { app.Listen(":" + strconv.Itoa(config.ServerConfig.Port)) } + + diff --git a/nginx/www.shen100.com.conf b/nginx/www.shen100.com.conf new file mode 100644 index 00000000..6d1f911d --- /dev/null +++ b/nginx/www.shen100.com.conf @@ -0,0 +1,41 @@ +upstream wemallNodejs { + server 127.0.0.1:8010; +} + +upstream wemallApp { + server 127.0.0.1:8012; +} + +server { + listen 80; + server_name www.shen100.com; + + #if ($host != 'www.imofa.net' ) { + # rewrite ^/(.*)$ http://www.imofa.net/$1 permanent; + #} + + access_log /usr/local/nginx/logs/wemall.access.log; + error_log /usr/local/nginx/logs/wemall.error.log; + + #root /root/nowamagic_venv/nowamagic_pj; + + location / { + proxy_pass http://wemallNodejs; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /api { + proxy_pass http://wemallApp; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location ~ .*\.(gif|jpg|jpeg|bmp|png|ico|txt|js|css|eot|ttf|svg|woff|apk|jar|zip)$ + { + root /data/web/static; + expires 365d; + } +} diff --git a/nodejs/gulpfile.js b/nodejs/gulpfile.js new file mode 100644 index 00000000..8d61edad --- /dev/null +++ b/nodejs/gulpfile.js @@ -0,0 +1,131 @@ +var gulp = require('gulp'); +var gulpUtil = require('gulp-util'); +var minifycss = require('gulp-minify-css'); +var clean = require('gulp-clean'); +var rename = require('gulp-rename'); +var rev = require('gulp-rev'); +var revCollector = require('gulp-rev-collector'); +var webpack = require('webpack'); +var webpkConfig = require('./webpack.config.js'); + +function getTimestamp() { + var date = new Date(); + var year = date.getFullYear(); + var month = date.getMonth() + 1; + var d = date.getDate(); + month = month < 10 ? '0' + month : month; + d = d < 10 ? '0' + d : d; + var hour = date.getHours(); + hour = hour < 10 ? '0' + hour : hour; + var minute = date.getMinutes(); + minute = minute < 10 ? '0' + minute : minute; + return '' + year + month + d + hour + minute; +} + +var timestamp = getTimestamp(); +var distPath = 'dist/'; + +//nodejs项目发布目录 +var nodejsDistPath = distPath + 'nodejs/'; + +//nodejs项目发布目录下的timestamp目录 +var nodejsTimestampPath = nodejsDistPath + timestamp + '/'; + +//静态资源发布目录 +var staticDistPath = distPath + 'static/'; + +gulp.task('clean', function() { + return gulp.src(distPath) + .pipe(clean()); +}); + +//运行webpack +gulp.task('webpack', ['clean'], function(callback) { + var compiler = webpack(Object.create(webpkConfig)); + compiler.run(function(err, stats) { + if (err) { + throw new gulpUtil.PluginError('webpack', err); + } + gulpUtil.log('webpack', stats.toString({ + colors: true + })); + callback(); + }); +}); + +//webpack没有处理javascripts/libs目录下的js +gulp.task('jslibs', ['clean'], function() { + return gulp.src(['static/javascripts/libs/**/*.js']) + .pipe(gulp.dest(staticDistPath + 'javascripts/libs')) +}); + +gulp.task('copy-css', ['clean'], function() { + return gulp.src(['static/styles/**/*.css']) + .pipe(minifycss()) //压缩 + .pipe(rev()) //文件名加hash + .pipe(gulp.dest(staticDistPath + 'styles')) + .pipe(rev.manifest()) + .pipe(gulp.dest(staticDistPath + 'styles'));//生成rev-manifest.json +}); + +gulp.task('copy-server', ['clean'], function() { + return gulp.src(['server/**/*']) + .pipe(gulp.dest(nodejsTimestampPath + 'server')) +}); + +//修改HTML中js文件名,css文件名 +gulp.task('rev', ['copy-server', 'webpack', 'jslibs', 'copy-css'], function() { + return gulp.src([ + staticDistPath + '**/*.json', + nodejsTimestampPath + 'server/views/**/*.hbs' + ]) + .pipe(revCollector()) + .pipe(gulp.dest(nodejsTimestampPath + 'server/views')); +}); + +//依赖webpack任务,运行webpack任务后,也会生成图片 +gulp.task('copy-images', ['webpack'], function() { + return gulp.src([ + 'static/images/**/*' + ]) + .pipe(gulp.dest(staticDistPath + 'images')); +}); + +//依赖webpack任务,运行webpack任务后,也会生成字体文件 +gulp.task('copy-fonts', ['webpack'], function() { + return gulp.src([ + 'static/fonts/**/*' + ]) + .pipe(gulp.dest(staticDistPath + 'fonts')); +}); + +gulp.task('copy-app.js', ['clean'], function() { + return gulp.src(['app.js']) + .pipe(gulp.dest(nodejsTimestampPath)); +}); + +gulp.task('copy-package.json', ['clean'], function() { + return gulp.src(['package.json']) + .pipe(gulp.dest(nodejsDistPath)); +}); + +gulp.task('copy-configuration.json', ['clean'], function() { + return gulp.src(['../configuration.prod.json']) + .pipe(rename('configuration.json')) + .pipe(gulp.dest(nodejsDistPath)); +}); + +//产品模式 +gulp.task('default', [ + 'rev', + 'copy-fonts', + 'copy-images', + 'copy-app.js', + 'copy-package.json', + 'copy-configuration.json' +]); + + + + + diff --git a/nodejs/package.json b/nodejs/package.json index f309c93a..54abdc11 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -15,6 +15,7 @@ "express": "4.15.2", "hbs": "4.0.1", "morgan": "1.8.1", + "request": "2.75.0", "serve-favicon": "2.4.2" }, "devDependencies": { @@ -34,6 +35,7 @@ "gulp-rename": "1.2.2", "gulp-util": "3.0.8", "isomorphic-fetch": "2.2.1", + "progress-bar-webpack-plugin": "1.9.0", "react": "15.4.2", "react-dom": "15.4.2", "react-redux": "5.0.3", @@ -46,6 +48,7 @@ "ua-parser-js": "0.7.12", "webpack": "2.3.2", "webpack-dev-middleware": "1.10.1", - "webpack-hot-middleware": "2.17.1" + "webpack-hot-middleware": "2.17.1", + "webpack-manifest-plugin": "1.1.0" } } diff --git a/nodejs/server/utils/index.js b/nodejs/server/utils/index.js index 31407b28..d3aa6f91 100644 --- a/nodejs/server/utils/index.js +++ b/nodejs/server/utils/index.js @@ -7,7 +7,7 @@ var EnvUtil = { if (config.env === 'development') { packages = require('../../package'); } else { - packages = require('../../../package'); + packages = require('../../../../package'); } Object.keys(packages.dependencies).forEach(function(p) { try { diff --git a/nodejs/server/views/admin/index.hbs b/nodejs/server/views/admin/index.hbs index c0d3e9fc..a73ae78f 100644 --- a/nodejs/server/views/admin/index.hbs +++ b/nodejs/server/views/admin/index.hbs @@ -13,7 +13,7 @@ var softwareConfig = {{{json softwareConfig}}}; var globalData = {{{json data}}}; - + \ No newline at end of file diff --git a/nodejs/staticServ.js b/nodejs/staticServ.js index fe473be1..3ebe5c72 100755 --- a/nodejs/staticServ.js +++ b/nodejs/staticServ.js @@ -9,7 +9,7 @@ var bodyParser = require('body-parser'); var webpack = require('webpack'); var webpackDevMiddleware = require('webpack-dev-middleware'); var webpackHotMiddleware = require('webpack-hot-middleware'); -var webpackConfig = require('./webpack.config'); +var webpackConfig = require('./webpack.config.dev'); var config = require('./server/config'); var compiler = webpack(webpackConfig); @@ -48,9 +48,11 @@ app.use(function(req, res, next) { }); app.use(function(err, req, res, next) { - res.locals.error = err; res.status(err.status || 500); - res.render('error'); + res.render('error', { + message : err.message, + error : config.env === 'development' ? err : {} + }); }); server.listen(config.staticPort, function() { diff --git a/nodejs/webpack.config.prod.js b/nodejs/webpack.config.dev.js similarity index 73% rename from nodejs/webpack.config.prod.js rename to nodejs/webpack.config.dev.js index a51ba2ae..2baa2834 100755 --- a/nodejs/webpack.config.prod.js +++ b/nodejs/webpack.config.dev.js @@ -3,9 +3,7 @@ var path = require('path'); var webpack = require('webpack'); var BellOnBundlerErrorPlugin = require('bell-on-bundler-error-plugin'); -var appConfig = require('./appconfig.json'); var hotMiddleware = 'webpack-hot-middleware/client?reload=true'; -var jsPath = appConfig.page.JSPath; function getEntryMap() { var entryArr = [ @@ -13,13 +11,15 @@ function getEntryMap() { ]; var entryMap = {}; entryArr.forEach(function(key) { - entryMap[key] = ['babel-polyfill', './client/javascripts/' + key + '.js', hotMiddleware]; + entryMap[key] = ['babel-polyfill', './static/javascripts/' + key + '.js', hotMiddleware]; }); + entryMap['vendor'] = [ 'react', 'react-dom', 'react-redux', - 'redux' + 'redux', + 'echarts' ]; return entryMap; } @@ -27,10 +27,10 @@ function getEntryMap() { var config = { entry: getEntryMap(), output: { - publicPath : appConfig.page.SitePath + '/', - filename : '.' + jsPath + '/[name].js', - path : path.resolve(__dirname, './dist/app/client'), - chunkFilename : '.' + jsPath + '/[name].js', + publicPath : '/', + filename : './javascripts/[name].js', + path : path.resolve(__dirname, '../dist/nodejs/static'), + chunkFilename : './javascripts/[name].js' }, module: { loaders: [ @@ -45,14 +45,14 @@ var config = { } ] }, - devtool: 'inline-source-map', + devtool: 'cheap-module-eval-source-map', resolve: { extensions: ['.js', '.jsx', '.json'] }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', - filename: '.' + jsPath + '/vendor.bundle.js' + filename: './javascripts/vendor.js' }), new BellOnBundlerErrorPlugin(), new webpack.optimize.OccurrenceOrderPlugin(), diff --git a/nodejs/webpack.config.js b/nodejs/webpack.config.js index 7f708e4d..fc34cafd 100755 --- a/nodejs/webpack.config.js +++ b/nodejs/webpack.config.js @@ -1,65 +1,59 @@ 'use strict'; -var path = require('path'); -var webpack = require('webpack'); -var BellOnBundlerErrorPlugin = require('bell-on-bundler-error-plugin'); -var config = require('./server/config'); -var hotMiddleware = 'webpack-hot-middleware/client?reload=true'; -var jsPath = config.page.jsPath; +var path = require('path'); +var webpack = require('webpack'); +var ProgressBarPlugin = require('progress-bar-webpack-plugin'); +var ManifestPlugin = require('webpack-manifest-plugin'); +var devWebpack = require('./webpack.config.dev'); -function getEntryMap() { - var entryArr = [ - 'admin/app' - ]; - var entryMap = {}; - entryArr.forEach(function(key) { - entryMap[key] = ['babel-polyfill', './static/javascripts/' + key + '.js', hotMiddleware]; - }); +var entry = {}; - entryMap['vendor'] = [ - 'react', - 'react-dom', - 'react-redux', - 'redux', - 'echarts' - ]; - return entryMap; +for (var key in devWebpack.entry) { + if (devWebpack.entry.hasOwnProperty(key)) { + if (key == 'vendor') { + entry[key] = devWebpack.entry[key]; + } else { + entry[key] = devWebpack.entry[key].slice(0, -1) + } + } } var config = { - entry: getEntryMap(), + entry: entry, output: { - publicPath : '/', - filename : './javascripts/[name].js', - path : path.resolve(__dirname, './dist/app/client'), - chunkFilename : './javascripts/[name].js' + publicPath : '/javascripts/', + filename : '[name]-[hash:10].js', + path : path.resolve(__dirname, './dist/static/javascripts'), + chunkFilename : '[name]-[chunkHash:10].js' }, module: { - loaders: [ - { - test: /\.css$/, - loaders: ['style-loader', 'css-loader'] - }, - { - test: /\.js$/, - exclude: /(node_modules|bower_components)/, - loader: 'babel-loader?+babelrc,+cacheDirectory,presets[]=es2015,presets[]=stage-0,presets[]=react' - } - ] + loaders: devWebpack.module.loaders }, - devtool: 'cheap-module-eval-source-map', + devtool: 'inline-source-map', resolve: { extensions: ['.js', '.jsx', '.json'] }, plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', - filename: './javascripts/vendor.bundle.js' + filename: 'vendor-[hash:10].js' }), - new BellOnBundlerErrorPlugin(), new webpack.optimize.OccurrenceOrderPlugin(), - new webpack.HotModuleReplacementPlugin(), - new webpack.NoEmitOnErrorsPlugin() + new ProgressBarPlugin(), + new webpack.NoEmitOnErrorsPlugin(), + new ManifestPlugin({ + fileName: 'rev-manifest.json' + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false + } + }), + new webpack.DefinePlugin({ + 'process.env': { + 'NODE_ENV': '"production"' + } + }) ] };