-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
182 lines (182 loc) · 84.9 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Docker 是个啥?]]></title>
<url>%2F2017%2F09%2F20%2Fdocker-guide-1%2F</url>
<content type="text"><![CDATA[第一次听到 Docker 这个东西已经不记得是什么时候了。大概也就是两年前左右,那时对这些新兴技术也是觉得云里雾里。加上平时也没空去了解,所以也不知道是什么东西。直到去年第一次使用它时才慢慢明白是做什么用,相信有很多同学也和我一样,对 Docker 的概念十分模糊。这里就记录一下 Docker 的相关知识吧。 容器 容器(container)到底是什么东西?我们再学习 Java Web 开发的时候肯定有听过 Servlet 容器,在生活中我们也有各种容器,锅碗瓢盆杯子都是容器。那他们有什么共同点?就是用来装东西的,只是各自装载的东西不同。Servlet 容器用来装载 Servlet 程序,而 Docker 容器,则是用来装载各种各样的程序。我们可以理解为容器是一个虚拟机,里面可以安装各种各样的程序。每个容器之间是互相隔离的,它们之间可以通过网络进行通信。 注意:Docker 并非是虚拟机,这里将它比作虚拟机只是为了方便理解。相对于虚拟机,Docker 省去了 Guest OS、虚拟化硬件等等步骤。启动速度在几十毫秒内。而虚拟机则需要数分钟。他们的应用场景并不相同,虚拟机往往应用在需要完全隔离环境的场景下。而 Docker 则是隔离应用。 镜像 Docker 镜像类似我们以往认知中的系统镜像。系统镜像封装了整个系统的所有文件,安装程序等等。Docker 镜像则是一个固化的环境,一般而言这个环境大多数是一个经过简化的 Linux 系统。而容器则是基于镜像运行而来的。这就像上面虚拟机的例子,我们运行一个虚拟机也需要对应的系统。我们还可以在容器中进行一些操作后再固化成一个新的镜像。这样我们可以针对不同程序来制作镜像。然后通过镜像在所有基于 Docker 环境的机器上批量运行N个容器。这也减少了我们在部署程序时由环境因素而引起的客观问题。 安装 了解了以上概念之后我们可以知道,操作 Docker 最基本的就是操作 Docker 容器、Docker 镜像 这两个东西。接下来我们就要开始使用它了,安装步骤可以通过官网了解到。 使用 接下来就是怎么使用它了,我们知道 Docker 有容器和镜像,这里初略列出它们各自的一些操作,所有的 Docker 命令都带有前缀 docker: 容器 运行容器 run 镜像名称 命令 基于某个镜像运行后得到一个容器 停止容器 stop 容器id 停止某个容器 删除容器 rm 容器id 删除某个容器 在容器中执行命令 exec 命令 容器id 在容器中执行命令 查看容器运行状态 ps 镜像 获取镜像 pull 镜像名称 提交镜像 commit 容器id 镜像名称 删除镜像 rmi 镜像名称 容器操作运行容器 这些都是最基本的操作,那么我们现在就开始来运行一个容器吧。这个容器基于 ubuntu 制作而成。运行docker run ubuntu /bin/echo "Hello World" : 格式:docker run 参数 (容器id/容器名称/镜像名称) 命令 这里我们运行了一个基于 ubuntu 镜像的容器,并且运行后执行了 /bin/echo "Hello World" 命令。这里需要说明的是,如果我们以一个本地不存在的镜像来运行容器时,docker 会到官方仓库搜索并 pull 这个镜像。看起来好像就是运行了一个程序一样,其实它是个系统。只是我们让他运行了一个命令后就自动结束了。 运行交互式容器 交互式容器是什么?上面是最普通的运行一个容器的命令,但是我们说了这个容器其实是个系统。那我们怎么去操作这个系统?这时就需要我们再运行时加上-it命令,以便我们能够对这个容器进行更多操作。运行 docker run -it ubuntu /bin/bash : 看,是不是跟普通的 linux 系统一样? 基本的 shell 命令都没问题,最后我们退出这个容器。现在是不是能理解为什么要把它比喻为虚拟机了吧?这个时候同学们又要问了,要是我运行的时候忘记加 -it 了怎么办?这个时候只需要重新运行这个容器就可以了 docker run -it 容器id /bin/bash 查看容器 ps 命令是用来查看容器状态的。运行docker ps 命令得到如下结果: 没有任何正在运行的容器,因为我们上面运行的容器都在执行完命令后自动结束了,所以这里看不到。我们需要加上 -a 参数查看所有容器,运行 docker ps -a : 格式:docker ps 参数 有两条记录,CONTAINET ID 代表容器的唯一id,如果觉得 ID不好记,我们可以为容器设置别名。在运行容器时加上 --name 容器名称 参数,例如: docker run --name ubuntu 为什么容器执行完命令就会结束? 因为系统中没后台驻留的程序,所以命令执行完容器就自动结束了。如果容器中运行 Tomcat、Nginx 等等会驻留在后台的程序,那么容器就会一直处于运行中的状态。直到你手动关闭它。 停止容器 明白了上面运行容器的操作后,相信这个命令对你而言就是小事一桩。我们先运行一个交互式容器 docker run -it --name ubuntu ubuntu /bin/bash 。然后把它放在一边,打开一个新的终端,运行 docker ps 就能看到正在运行中的容器了。 现在我们看到容器id和容器名称了,运行 docker stop (容器id/容器名称) 就可以停止它了。 这个时候 docker ps 看不到该容器,但是 docker ps -a 还是可以看到。如果删除了容器就再也看不到了。 删除容器 运行 docker rm (容器id/容器名称) : 镜像操作 这里稍微讲解镜像的拉取,提交,删除操作。除这些外一些高级操作现在暂时不会涉及。 拉取镜像 上面说到在运行容器时,如果对应的镜像没有下载,则会自动先把镜像下载下来然后运行容器。有时候你想自己预先下载镜像,这时候就需要用到 pull 命令。在 Docker Hub 可以搜索到现在官方仓库已有的镜像。例如我们搜索 java 的镜像: 点击第一个进去,查看tag 能看到很多不同的镜像。 这时我们可以选择一个然后 pull 到本地: 删除镜像 当需要删除镜像时只需要执行 docker rmi 镜像名称 即可。需要注意的是,如果该镜像被某个容器使用了,则需要删除容器后才能删除镜像。 提交容器 从 Docker Hub 可以看到,有各种镜像。它们都是怎么做的?结合上面的知识,我们可以在 Ubuntu 容器中安装 Java,安装 Tomcat。最后把它 commit 成一个新镜像,最后我们就得到一个可以运行 Java Web 应用的镜像啦。运行一个交互式容器,在其中安装需要的软件,最后执行 docker commit (容器id/容器名称) 镜像名称 就可以把容器固化为一个镜像啦。然后我们只要以新镜像为基础运行容器就可以了。]]></content>
<categories>
<category>后端</category>
</categories>
<tags>
<tag>Docker</tag>
<tag>后端</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Webpack2 学习记录 — 第三章·开发/生产配置优化]]></title>
<url>%2F2017%2F06%2F14%2Fwebpack-guide-3%2F</url>
<content type="text"><![CDATA[上一章我们完成了 Webpack HMR 的配置,这也是在前端工程化开发后的标配。相信这些新的东西能让你体会到一些不一样的感觉。这一章我们主要把上一章的配置进行一些优化,并且添加一些打包压缩之类的配置。我们在第一章完成了基本的配置,能够打包输出静态文件。在第二章完成了 HMR 的配置,但是我们会发现在第二章里并不会输出静态文件。所以这里引出一个概念,在前端工程化的项目里,开发与生产并不一定会用同一套配置。开发时我们需要自带服务器、需要 HMR ,但是在生产环境并不需要这些,我们只需要一些静态文件,往 nginx 一丢就可以了。接下来主要就会讲这一部分! 配置优化 项目需要同时具备 dev 和 build 两套配置,所以我们抽离其中一些公用的配置,减少的样板代码的重复出现。仔细思考,我们会发现其中 loaders 部分是通用的,部分 plugins 也是通用的。所以我们可以在 build 目录建立一个 base 配置,内容与项目根目录下的 webpack-config.js 项目。然后去除一些不通用的配置: 123456789101112131415161718192021222324252627282930313233343536373839// webpack.base.jsmodule.exports = { entry: ['./src/main.js'], output: { path: '/Users/aixiaoai/nodejs/webpack-demo/dist', publicPath: '/', filename: 'bundle.js' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(png|jpe?g|git|svg)(\?.*)?$/, use: { loader: 'url-loader', options: { // 如果超过大小限制,则不进行编码,输出到/img目录 limit: 10000, name: '/Users/aixiaoai/nodejs/webpack-demo/dist/img/[name].[hash:7].[ext]' } } }, { test: /\.html$/, use: { loader: 'html-loader', options: { // 压缩 html 代码 minimize: true } } } ] }} 上面配置存在一些问题,例如存在一些绝对路径、多入口文件配置问题,我们可以使用 NodeJS 的 path 模块来获取正确的路径,并且对多入口文件进行修改: 1234567891011121314151617181920212223242526272829303132333435// webpack.base.conf.jsvar path = require('path')function resolve (dir) { return path.join(__dirname, '../', dir)}module.exports = { entry: { app: './src/main.js' }, output: { path: resolve('dist'), publicPath: '/', filename: '[name].js' }, module: { rules: [ // 省略相关代码 { test: /\.(png|jpe?g|git|svg)(\?.*)?$/, use: { loader: 'url-loader', options: { // 如果超过大小限制,则不进行编码,输出到/img目录 limit: 10000, name: resolve('dist/img/[name].[hash:7].[ext]') } } } // 省略相关代码 ] }} __dirname:NodeJs Api ,获取当前目录 path.join(__dirname, ‘../‘, dir):拼接目录 但这样也不太好,因为开发与生产的构建过程我们最好通过一些环境变量来进行干预,例如一些路径我们可以通过环境不同而输出到不同路径。这里我们新建一个 config 目录,用来存放环境配置: 12345// prod.env.js 生产环境变量module.exports = { NODE_ENV: '"production"'} 12345678// dev.env.js 开发环境变量var merge = require('webpack-merge')var prodEnv = require('./prod.env')module.exports = merge(prodEnv, { NODE_ENV: '"development"'}) webpack-merge 模块用来合并对象,这里 dev.env.js 覆盖了 prod.env.js 的值 1234567891011121314151617181920212223242526// index.js 环境配置文件var path = require('path')module.exports = { build: { env: require('./prod.env'), index: path.resolve(__dirname, '../dist/index.html'), assetsRoot: path.resolve(__dirname, '../dist'), assetsSubDirectory: 'static', assetsPublicPath: '/', productionSourceMap: true, productionGzip: false, productionGzipExtensions: ['js', 'css'], bundleAnalyzerReport: process.env.npm_config_report }, dev: { env: require('./dev.env'), port: 8080, autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: {}, cssSourceMap: false }} 接下来我们来改造 webpack.base.conf.js: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374// webpack.base.conf.jsvar path = require('path')var config = require('../config')function resolve (dir) { return path.join(__dirname, '../', dir)}module.exports = { entry: { app: './src/main.js' }, output: { path: config.build.assetsRoot, filename: '[name].js', publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath }, resolve: { extensions: ['.js', '.json'], alias: { '@': resolve('src') } }, module: { rules: [ { test: /\.js$/, loader: 'eslint-loader', enforce: 'pre', include: [resolve('src'), resolve('test')], options: { formatter: require('eslint-friendly-formatter') } }, { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test')] }, { test: /\.(png|jpe?g|git|svg)(\?.*)?$/, use: { loader: 'url-loader', options: { // 如果超过大小限制,则不进行编码,输出到/img目录 limit: 10000, name: resolve('dist/img/[name].[hash:7].[ext]') } } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: resolve('dist/fonts/[name].[hash:7].[ext]') } }, { test: /\.html$/, use: { loader: 'html-loader', options: { // 压缩 html 代码 minimize: true } } } ] }} 这里添加了 Babel 、字体相关配置。并且将 css-loader 去除,因为 css 相关 loader 比较多。需要追加一些 sass、less 等等。封装成一个工具函数比较好 Babel:用来将 ES6 编译成 ES5 的工具 ESLint 需要添加一下配置文件 1234// .eslintignorebuild/*.jsconfig/*.js 1234567891011121314151617181920212223242526// .eslintrc.jsmodule.exports = { root: true, parser: 'babel-eslint', parserOptions: { sourceType: 'module' }, env: { browser: true, }, extends: 'standard', // required to lint *.vue files plugins: [ 'html' ], // add your custom rules here 'rules': { // allow paren-less arrow functions 'arrow-parens': 0, // allow async-await 'generator-star-spacing': 0, // allow debugger during development 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 }} 新建一个 utils.js: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566// utils.jsvar path = require('path')var config = require('../config')// 导出 css 的相关 pluginvar ExtractTextPlugin = require('extract-text-webpack-plugin')exports.assetsPath = function (_path) { var assetsSubDirectory = process.env.NODE_ENV === 'production' ? config.build.assetsSubDirectory : config.dev.assetsPublicPath return path.posix.join(assetsSubDirectory, _path)}exports.cssLoaders = function (options) { options = options || {} var cssLoader = { loader: 'css-loader', options: { minimize: process.env.NODE_ENV === 'production', sourceMap: options.sourceMap } } function generateLoaders (loader, loaderOptions) { var loaders = [cssLoader] if (loader) { loaders.push({ loader: loader + '-loader', options: Object.assign({}, loaderOptions, { sourceMap: options.sourceMap }) }) } if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, fallback: 'style-loader' }) } else { return ['style-loader'].concat(loaders) } } return { css: generateLoaders(), less: generateLoaders('less'), sass: generateLoaders('sass', { indentedSyntax: true}), scss: generateLoaders('sass') }}exports.styleLoaders = function (options) { var output = [] var loaders = exports.cssLoaders(options) for (var extension in loaders) { var loader = loaders[extension] output.push({ test: new RegExp('\\.' + extension + '$'), use: loader }) } return output} 继续改造 webpack.base.conf.js : 123456789101112131415161718192021222324252627282930313233343536// webpack.base.conf.jsvar path = require('path')var utils = require('./utils')var config = require('../config')// 省略相关代码module.exports = { // 省略相关代码 module: { rules: [ // 省略相关代码 { test: /\.(png|jpe?g|git|svg)(\?.*)?$/, use: { loader: 'url-loader', options: { // 如果超过大小限制,则不进行编码,输出到/img目录 limit: 10000, name: utils.assetsPath('/img/[name].[hash:7].[ext]') } } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } } // 省略相关代码 ] }} 至此完成了 webpack.base.conf.js 的改造,通过封装工具函数、独立路径配置来达成高可用配置的效果。接下来完成 dev 环境的配置,我们新建一个 webpack.dev.conf.js 文件: 12345678910111213141516171819202122232425262728293031323334353637// webpack.dev.conf.jsvar utils = require('./utils')var webpack = require('webpack')var config = require('../config')var merge = require('webpack-merge')var baseWebpackConfig = require('./webpack.base.conf')var HtmlWebpackPlugin = require('html-webpack-plugin')// webpack 编译错误格式化为比较友好的格式的插件var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')// 添加 webpack-hot-middleware 客户端Object.keys(baseWebpackConfig.entry).forEach(function (name) { baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])})// 合并 base 配置module.exports = merge(baseWebpackConfig, { module: { // 添加 css loaders rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) }, devtool: '#cheap-module-eval-source-map', plugins: [ new webpack.DefinePlugin({ 'process.env': config.dev.env }), new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(), new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', inject: true }), new FriendlyErrorsPlugin() ]}) 接下来修改 dev-server.js : 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283// dev-server.jsvar config = require('../config')if (!process.env.NODE_ENV) { process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)}// 用户自动打开浏览器var opn = require('opn')var path = require('path')var express = require('express')var webpack = require('webpack')// 用户设置代理请求var proxyMiddleware = require('http-proxy-middleware')var webpackConfig = require('./webpack.dev.conf')// 端口号var port = process.env.PORT || config.dev.port// 是否自动打开浏览器var autoOpenBrowser = !!config.dev.autoOpenBrowser// 请求转发表var proxyTable = config.dev.proxyTablevar app = express()var compiler = webpack(webpackConfig)var devMiddleware = require('webpack-dev-middleware')(compiler, { publicPath: webpackConfig.output.publicPath, // 格式化错误提示 quiet: true})var hotMiddleware = require('webpack-hot-middleware')(compiler, { log: () => {}})compiler.plugin('compilation', function (compilation) { compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { hotMiddleware.publish({ action: 'reload' }) cb() })})Object.keys(proxyTable).forEach(function (context) { var options = proxyTable[context] if (typeof options === 'string') { options = { target: options } } app.use(proxyMiddleware(options.filter || context, options))})app.use(require('connect-history-api-fallback')())app.use(devMiddleware)app.use(hotMiddleware)var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)app.use(staticPath, express.static('./static'))var uri = 'http://localhost:' + portvar _resolvevar readyPromise = new Promise(resolve => { _resolve = resolve})console.log('> 启动开发服务器...')devMiddleware.waitUntilValid(() => { console.log('> 监听端口' + uri + '\n') if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { opn(uri) } _resolve()})var server = app.listen(port)module.exports = { ready: readyPromise, close: () => { server.close() }} 现在完成了 dev 环境的配置,执行 yarn run dev 来测试一下吧! 很完美!不是吗!有了上面的基础,接下来编写 build 配置就稍微省些力气了,新建 webpack.prod.conf.js : 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103// webpack.prod.conf.jsvar path = require('path')var utils = require('./utils')var webpack = require('webpack')var config = require('../config')var merge = require('webpack-merge')var baseWebpackConfig = require('./webpack.base.conf')var CopyWebpackPlugin = require('copy-webpack-plugin')var HtmlWebpackPlugin = require('html-webpack-plugin')var ExtractTextPlugin = require('extract-text-webpack-plugin')var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')var env = config.build.envvar webpackConfig = merge(baseWebpackConfig, { module: { rules: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) }, devtool: config.build.productionSourceMap ? "#source-map" : false, output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].[chunkhash].js'), chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }, plugins: [ new webpack.DefinePlugin({ 'process.env': env }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false }, sourceMap: true }), new ExtractTextPlugin({ filename: utils.assetsPath('css/[name].[contenthash].css') }), new OptimizeCSSPlugin({ cssProcessorOptions: { safe: true } }), new HtmlWebpackPlugin({ filename: config.build.index, template: 'index.html', inject: true, chunksSortMode: 'dependency' }), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks: function (module, count) { return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['vendor'] }), // copy custom static assets new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), to: config.build.assetsSubDirectory, ignore: ['.*'] } ]) ]})if (config.build.productionGzip) { var CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ asset: '[path].gz[query]', algorithm: 'gzip', test: new RegExp( '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' ), threshold: 10240, minRatio: 0.8 }) )}if (config.build.bundleAnalyzerReport) { var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin())}module.exports = webpackConfig 创建 build.js 文件: 12345678910111213141516171819202122232425262728293031// build.jsprocess.env.NODE_ENV = 'production'var ora = require('ora')var rm = require('rimraf')var path = require('path')var chalk = require('chalk')var webpack = require('webpack')var config = require('../config')var webpackConfig = require('./webpack.prod.conf')var spinner = ora('正在构建生产环境')spinner.start()rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { if (err) throw err webpack(webpackConfig, function (err, stats) { spinner.stop() if (err) throw err process.stdout.write(stats.toString({ colors: true, modules: false, children: false, chunks: false, chunkModules: false }) + '\n\n') console.log(chalk.cyan(' 构建完成.\n')) })}) 安装相关的依赖: 1yarn add babel-core babel-eslint babel-loader babel-preset-env connect-history-api-fallback copy-webpack-plugin eslint eslint-config-standard eslint-friendly-formatter eslint-loader eslint-plugin-html eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard extract-text-webpack-plugin friendly-errors-webpack-plugin http-proxy-middleware less less-loader node-sass opn optimize-css-assets-webpack-plugin ora sass-loader webpack-merge 执行 yarn run build 试一下吧! 大功告成!源代码]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
<tag>前端</tag>
<tag>Webpack</tag>
<tag>NodeJS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Webpack2 学习记录 — 第二章·模块热替换/兼容配置]]></title>
<url>%2F2017%2F06%2F13%2Fwebpack-guide-2%2F</url>
<content type="text"><![CDATA[前言 上一章我们完成了 Webpack 的基础配置。对项目常用的资源都进行了模块化,这一章主要说明在基于 Webpack 的项目上实现模块热替换 (HMR)。模块热替换表现在开发上,就是当我们修改某段 html 或者 js 时。浏览器会自动完成代码的替换切不需要刷新浏览器。别看这个事情很小,一旦习惯了这种设定,就很难回到传统的开发方式了。它对开发效率上的提升是肥肠有必要的,所以我们今天就在基于 Webpack 的项目上来实现这一特性。Webpack 里实现 HMR 有两种方式: webpack-dev-server 自带服务器、配置简便。 webpack-dev-middleware 这是一个中间件,必须搭配 express 等其他服务器来使用。配置较为复杂,需要有 express 等 Node 服务器使用经验。 这两个我们都会讲到 HMR: Hot Module Replacement webpack-dev-server 基于上一章的源代码进行开发,我们为其添加 webpack-dev-server 依赖 yarn add webpack-dev-server ,并且在 package.json 文件中为 scripts 属性添加一条脚本。 123456789// package.json{ // 省略代码 "scripts": { "dev": "webpack-dev-server --open" }, // 省略代码} 最后执行 yarn run dev ,正常情况下会自动打开浏览器并且访问项目。仔细观察,当我们改动 html 或 js 的时候会触发 Webpack 的构建过程,并且浏览器自动刷新了。这个阶段我们叫做实时重载,如果没有太多要求,这个时候也就已经够用了。但如果项目是一个 SPA 应用,那就需要更进一步的实现 HMR ,实现重新加载模块而不刷新页面。因为对于 SPA 应用来说 HMR 能更好的发挥它的作用。将 dev 脚本更改为 "dev": "webpack-dev-server --open --inline -- hot" 启用热替换功能。更改 common.css 中 box 的背景色,保存后查看浏览器状态。会发现页面在不刷新的情况下更新了 div.box 的背景色。观察控制台的内容,提示已经更新了模块。 需要注意的是 index.html 并不在依赖索引中,尽管会触发 webpack 的构建过程,但 webpack-dev-server hmr 并不会重载 index.html 的内容。 源代码 webpack-dev-middleware 同样使用上一章源代码进行开发。相对于 webpack-dev-server 来说,因为需要另外配置服务器,所以webpack-dev-middleware 的使用相对复杂一点。 这里需要有点 express 基础 添加 wepack-dev-middleware 与 express依赖 yarn add wepack-dev-middleware express 。新建 build/dev-server.js 文件: 1234567891011121314151617// dev-server.jsvar express = require("express");var webpackDevMiddleware = require("webpack-dev-middleware");var webpack = require("webpack");var webpackConfig = require("../webpack.config");var app = express();var compiler = webpack(webpackConfig);app.use(webpackDevMiddleware(compiler, { publicPath: "/" // 大部分情况下和 `output.publicPath`相同}));app.listen(8080, function () { console.log("Listening on port 8080!");}); 以上代码的作用是启动 webpack ,并且配置 webpack-dev-middleware 中间件,最后启动 express。这仅仅是创建了一个基于 webpack 的开发服务器,实现 HMR 我们需要再添加一个依赖 webpack-hot-middleware yarn add webpack-hot-middleware 。在 webpack.config.js 中修改一下内容: 12345678910111213// webpack.config.js// 省略相关代码var webpack = require("webpack")module.exports = { entry: ['./build/dev-client.js', './src/main.js'], // 省略相关代码 plugins: [ // 省略相关代码 new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ]} 创建 build/dev-client.js 文件: 123456789// dev-client.jsrequire('eventsource-polyfill')var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')hotClient.subscribe(function (event) { if (event.action === 'reload') { window.location.reload() }}) 安装 eventsource-polyfill 依赖以兼容不支持事件源的浏览器 yarn add eventsource-polyfill ,添加完以上内容后,修改 dev-server.js 文件: 12345678910111213141516171819// dev-server.js// 省略相关代码var webpackHotMiddleware = require("webpack-hot-middleware")var compiler = webpack(webpackConfig);var hotMiddleware = webpackHotMiddleware(compiler);// 解决 index.html 修改后不刷新问题compiler.plugin('compilation', function (compilation) { compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { hotMiddleware.publish({ action: 'reload' }) cb() })})app.use(hotMiddleware);// 省略相关代码 更改 package.json 的 dev 脚本 为 node build/dev-server.js ,运行 yarn run dev 启动服务。 测试效果,实现了 HMR ,并解决 webpack-dev-server 下 index.html 修改不刷新的问题。 源代码 第三方模块兼容 大多数情况下,我们还会依赖一些对全局变量有要求的第三方库。例如各类 Jquery 插件,依赖于全局变量 $ 或 jquery 。对于这种情况,可以使用 ProvidePlugin 插件。参考一下情况: 1234567891011121314// webpack.config.jsvar webpack = require("webpack")module.exports = { // 省略相关代码 plugins: [ // 省略相关代码 new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }) ]} main.js 中使用 jquery : 12345// main.jsrequire('./css/common.css')$("#app").addClass("box") 测试后一切正常! 兼容第三方模块还有其他方式,通过 imports-loader 、 exports-loader 都是解决第三方依赖的方式,可以参考官网相关资料。 源代码]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
<tag>前端</tag>
<tag>Webpack</tag>
<tag>NodeJS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Webpack2 学习记录 — 第一章·最小可用配置]]></title>
<url>%2F2017%2F05%2F29%2Fwebpack-guide-1%2F</url>
<content type="text"><![CDATA[在大前端的路上,总有那么些坑需要去踩。 相比于 14 年初刚出来工作,那时对前端的感觉远没有现在敏感,也不会想到现在的前端会发生天翻地覆的变化。细数自己对前端领域的技能,不懂的还有很多。NodeJs 也是后来粗略看了些,略微了解。前端自动化方面则一直没有时间去学习,例如 Gulp 、Grunt 都一窍不通。而前端模块化方面,之前略微了解 RequireJS ,Webpack 也曾手动写过配置。如今 Webpack 已升级到 2.0 + ,感觉是一个系统的学习并记录下过程的好时机。基于这个原因,就有了这个系列。— Webpack2 学习记录 基础 对于初次接触 Webpack 甚至 NodeJS 的同学,学习起来可能会有些困难。对于这种情况,建议还是先学习一些 NodeJS 的基础知识再学习 Webpack 会好些。Webpack 是一个模块化打包工具,它能将 JavaScript 、 CSS 、 JSX 、 图片等等文件进行模块化加载且打包。我们不在这里赘述模块化工程的重要性,因为这是越来越壮大的前端开发工作所不可避免的部分。Webpack 所主张的是 一切皆模块 的理解,我们就来看看它是怎么使用的吧! Hello Webpack!12345678// lib/utils.jsexport function show () { console.log('Hello Webpack!')}// main.jsvar utils = require('./lib/utils')utils.show() 以往,我们在 HTML 里对 JS 代码的程序依赖只能通过 script 标签的加载顺序控制。这样的坏处是程序的依赖关系不可见,代码里没有显式的表名程序需要的依赖。导致工程维护困难,这也正是前端模块化工程出现的原因之一。在上面的程序里,我们可以看到在 main.js 依赖与 utils.js 模块,优势是显而易见的。但我们现在要怎么使用它呢?接下来就是 Webpack 发挥作用的时候了。 12345678// webpack.config.jsmodule.exports = { entry: './src/main.js', output: { path: '/Users/aixiaoai/nodejs/webpack-demo/dist', filename: 'bundle.js' }} 执行 webpack 就会开始进行构建。打包成功后,项目下的 dist 目录会多出 bundle.js 文件。这就是打包后的程序,我们可以试着运行看看,执行 node dist/bundle.js 12webpack-demo$ node dist/bundle.jsHello Webpack! 项目被成功打包了,我们可以把该 JS 文件引入 HTML 了,是不是很酷!接下来我们讲解一下以上是如何实现的。 讲解 请尽量将项目作为 NodeJS 项目运行,因为在此基础上我们需要使用很多依赖于 NPM 包管理器的工具。相关命令 npm init 或 yarn init 安装 webpack npm install webpack --save-dev 或 yarn add webpack webpack 默认会寻找项目下命名为 webpack.config.js 的文件作为配置。所以我们新建一个名为 webpack.config.js 的文件,内容与上面的示例相同 这里有一个需要注意的地方,output.path 的值需要是绝对路径 运行 webpack 源代码 进阶 在上面的例子中我们讲到使用 Webpack 来打包一个 JavaScript 文件。有同学肯定会问,不是说 Webpack 的理念是一切皆模块吗?那其他文件怎么办?这里我们需要讲到 Webpack 的 rules ,在 Webpack 1.x 时代叫做 loaders。 模块化加载 CSS Webpack 里针对不同文件转化为模块的做法,是通过相对应的模块加载器实现的。例如 ES6 可以用 babel-loader 来加载、图片文件可以用 url-loader 来加载、CSS 可以用 style-loader|css-loader 加载。拿 CSS 文件来举例: 安装 style-loader和css-loader npm install style-loader css-loader --save-dev 或 yarn add style-loader css-loader 123456/* css/common.css */.box { background-color: #666666; width: 400px; height: 400px;} 123456789101112131415161718192021// main.jsrequire('./css/common.css')document.getElementById('app').className = 'box'// webpack.config.jsmodule.exports = { entry: './src/main.js', output: { path: '/Users/aixiaoai/nodejs/webpack-demo/dist', filename: 'bundle.js' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] } ] }} 1234567891011121314<!-- index.html --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title></head><body> <div id="app"></div></body><script src="./dist/bundle.js"></script></html> 执行 webpack,如果没有报错即是打包成功。我们来看看效果: 成功应用了 CSS ,很酷吧! 模块化加载图片等资源文件对于图片也是如此,我们加入 url-loader 试试。安装 url-loader npm install url-loader --save-dev 或 yarn add url-loader 12345678910111213141516171819202122232425// webpack.config.js// 省略相关代码module.exports = { // 省略相关代码 module: { rules: { // css-loader 省略相关代码 { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, use: { loader: 'url-loader', options: { // 如果超过大小限制,则不进行编码,输出到/img目录 limit: 10000, name: '/img/[name].[hash:7].[ext]' } } } } }}// main.js// 省略相关代码document.getElementById('img').src = require('./img/logo.svg') 1234567891011121314<!-- index.html --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title></head><body> <div id="app"><img src="" alt="" id="img"></div></body><script src="./dist/bundle.js"></script></html> 执行 webpack命令,查看效果: 666 !你们说,到底 6 不 6 !但是!有同学会说了,不想在 JS 中加载图片,想直接在 img 标签加在 src 属性里可以吗?当然可以! 基于 HTML 加载图片 我们发现需要在 JS 中加载图片,这在开发上难免会有些不便。这里需要使用 html-loader 来解决这个问题,安装npm install html-loader --save-dev 或 yarn add html-loader,加载器的配置也是老生常谈了: 1234567891011121314151617181920212223// webpack.config.jsmodule.exports = { // 省略相关代码 module: { rules: { // 省略相关代码 { test: /\.html$/, use: { loader: 'html-loader', options: { minimize: true } } } } }}// main.jsrequire('./css/common.css')document.getElementById('app').className = 'box' 1234567891011121314<!-- index.html --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Webpack-Demo</title></head><body> <div id="app"><img src="./src/img/logo.svg" alt="" id="img"></div></body><script src="./dist/bundle.js"></script></html> 效果与上一个例子是相同的,但我们可以在 img 标签的 src 属性指定图片了! Plugins(插件) 基于上面的示例,我们发现需要手动在 index.html 中引入 bundle.js 。这里要用到 Webpack Plugins 中的 html-webpack-plugin 插件,我们来对上面的项目进行一些改造。安装插件npm install html-webpack-plugin --save-dev 或 yarn add html-webpack-plugin ,然后我们开始配置插件: 12345678910111213141516// webpack.config.jsvar HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = { // 省略相关代码 plugins: [ new HtmlWebpackPlguin({ // 输出文件名 filename: 'index.html', // 模板文件名 template: 'index.html', // script 标签置于 body 底部 inject: true }) ]} 12345678910111213<!-- index.html --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Webpack-Demo</title></head><body> <div id="app"><img src="./src/img/logo.svg" alt="" id="img"></div></body></html> 运行 webpack 开始构建,插件会将项目根目录下的 index.html 作为模板编译到 dist 目录下。 123dist/|_bundle.js|_index.html 访问构建后的 index.html 查看效果。 大成功! 源代码]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
<tag>前端</tag>
<tag>Webpack</tag>
<tag>NodeJS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[我的游戏历程 - 青春]]></title>
<url>%2F2017%2F05%2F24%2Fmy-game-experience-2%2F</url>
<content type="text"><![CDATA[小学五六年级时,我第一次进入网吧、第一次有了 QQ 。第一次去网吧时,托同学帮我注册了 QQ,还给了我一个《梦幻西游》账号。谁也没想到,这次经历导致我几年后辍学。那时的我正值青春年少,玩的最多的就是 — 网游。 《梦幻西游》在我的青春的游戏史占了很大的一部分。我很清楚的记得刚玩时,到 10 级就需要充点卡。但还在小学时的我,没有钱去买点卡。所以玩到 9 级就只能在建邺城(新手村)遛弯、打乌龟、海毛虫,运气好的还能遇到泡泡(珍稀神兽)。新手村待久了总会觉得无趣,因为新手任务做完了,地图逛完了,没有其他的探索要素。所以总会忍不住点下升级按钮,走出建业城门,来到江南野外则又是一番新天地。由于下线前可以不用充点卡,所以还是能玩到 15 级左右。拜了师门,学了技能。这时网吧时间也要到了,一个新手号也就这样结束了,有段时间是这样循环往复过来的。有一次有同学送给了我一个 40+ 级的大唐官府剑侠客,那时我第一次接触到较为高级的角色。玩这个号时,经常去抓鬼,只是没有武器没有装备,打起来异常辛苦。这个号玩了一阵后由于没有合适的路子搞装备,我也就弃了。还有次是帮同学练号,中途我还把他号给盗了,最后东窗事发弄得非常尴尬。还有次我离家出走,去一个同玩《梦幻西游》的网友那里留宿。这些都是玩这游戏时经历的故事。 其次玩的很多的是《跑跑卡丁车》。这个不得不说的是同班同学也有好几个玩的,我喜欢玩赛车游戏,喜欢风驰电掣,也是那时的秋名山老司机。森林发卡赛道我可以用板车跑赢其他人的高级车,那时真真是意气风发。有段时间甚至想过以后是不是能参加职业赛事,当然这只是一个不切实际的想法。那点技巧拿到高手里来说还是过于低级。在这之前,我还是一个贫民玩家。但有一次过年,我开始有了压岁钱,搬到深圳后第一次有自己的压岁钱,父母给的。有 50 块,我犹豫了一阵然后买了跑跑卡丁车里的车辆。我记得很清楚,那是棉花糖系列 — 棉花糖PRO 从此摇身一变为人民币贫民玩家。怀揣着激动无比的心情,在网吧加了 2 个小时的钟,尽情体验那风驰电挚带来的激情。 小学五年级时,班里来了个转学生,在那之前我一直与一个女孩同桌。她是我的邻居,也是我偷偷喜欢的人。在这个转学生来了之后,班里开始调整位置。从此他们变成了同桌,我调到了他们的后面。有一次周末,我与转学生和其他几个男同学,相约去网吧玩游戏。这是我第一次玩这个游戏 — 《冒险岛》。 相对来说《冒险岛》并不是我很深入去玩的一款游戏,我记得它升级很慢。从彩虹岛的新手教程升到10级,然后坐船去金银岛。这个过程还算可以接受,但后面的升级道路会越来越困难。升级所需经验经常是成倍增长的。之后的很长一段时间我并没有继续接触这个游戏,再次接触时是战神这个职业上线的时候,伴随出现的是利用战神的反盾技能出现的外挂。这是我第一次把一个角色升到 100 级以上,用出各种炫酷的高级技能时总会有一种心满意足的成就感,尽管是用开外挂升级得来的。从《冒险岛》后会引申到另一款游戏《冲锋岛》,由世纪天成代理。当时宣传时说是《冒险岛》精神续作,开发团队大部分是原冒险岛的开发团队组成。这在当时还是很吸引眼球的,特别是《冒险岛》的老粉丝们。在这个游戏里还认识了刘力菱同学,也是到现在也要好的朋友。旧时的游戏经历也差不多要结束了。我还记得那时最后玩的一款游戏是《海洋骑士团》,盛大代理,游戏内容是各种可爱的卡通人物骑在乌龟、海豚上在水里竞速的游戏。也是在这个时候认识了我们美丽、可爱的郑波澜同学。而我也在这时结束了辍学生涯,前往广州上学,步入新时代。]]></content>
<categories>
<category>杂谈</category>
</categories>
<tags>
<tag>家用机</tag>
<tag>掌机</tag>
<tag>游戏</tag>
</tags>
</entry>
<entry>
<title><![CDATA[我的游戏历程 - 初见]]></title>
<url>%2F2017%2F05%2F09%2Fmy-game-experience-1%2F</url>
<content type="text"><![CDATA[最近总想写些什么,像是无数话语梗在心头,好不难受!想来想去,发现游戏在我的人生中占有很大一部分位置。那就来讲讲我玩游戏的经历吧。 我记得,那时我还小。家住在揭阳市惠来县隆江镇,我们家跟着叔叔伯伯一样,做鱼副产品营生,还有个小厂子。那时候厂子就我爸和我妈在做,奶奶偶尔也会在厂子里看着。相对于各位叔叔伯伯的厂来说,我们家的厂子很小,亏了那时效益不错,我们家也过得还好。 我爸每过一段时间就会去送货,大多是送到深圳等地。回来时总会给我们带各种好吃好玩的。有时候会带好吃好玩的回来,记得第一次喝益力多,也是那时我爸带回来的,印象甚是深刻。我第一次玩到的游戏机 — 红白机,也就是我爸在某次送货回家时带回了一个盒子。那时我也不知道它叫什么,只知道我爸插到电视上后可以玩游戏。这就是我第一次接触到家用机,直到十几年后 — 我十五岁,我才知道它的名字叫 — 红白机(FC)。 那时候我们家流行玩的是《坦克大战》,也是我爸唯一会玩的游戏。后来喜欢玩《F1 赛车》、《雪人兄弟》、《松鼠大战》、《魂斗罗》。但好景不长,也不知道那台 FC 是不是正版,而且玩的时候由于天生大力 — 手柄按键容易坏。由于这个原因,手柄坏了后也没地方换修。结果就是这台 FC 慢慢的淡出了视线,不知在何时消失的无影无踪。后面再玩到时,只能是去亲戚家蹭小霸王了。所以现在还是蛮想念那台 FC 的。]]></content>
<categories>
<category>杂谈</category>
</categories>
<tags>
<tag>家用机</tag>
<tag>掌机</tag>
<tag>游戏</tag>
</tags>
</entry>
<entry>
<title><![CDATA[我眼中的锤子手机 - 锤子科技 2017 春季新品发布会]]></title>
<url>%2F2017%2F05%2F08%2Fsmartisan-1%2F</url>
<content type="text"><![CDATA[说起为什么知道老罗这个人。许多人都能追溯到老罗语录时期,十几岁便听着老罗语录长大。在那时,我还是个懵懵懂懂的小学生。在家看着各种国产、日产动画片,难以想象在今日会对这样一个胖子发出什么感想。 我不是一个从小听着老罗语录长大的人。 我已经忘记第一次知道老罗时是在什么时候,依稀记得是在 12 年左右。那时还是个学生,当时对小米、王自如还存在好感。在他们之间微博的交流中知道了这个人,并且在某个慵懒的午后,不知在什么地方看了《一个理想主义者的创业故事 2》,之后便一发不可收拾。我抽时间看完了老罗的其他几场演讲,包括在吉林大学的那场《我的奋斗》。至于原因,可能就是因为比相声好看? 通过干干净净地赚钱让人相信干干净净地赚钱是可能的;通过实现理想让人相信实现理想是可能的;通过改变世界让人相信改变世界是可能的……即使是在中国。 打动我的其实是老罗与他的培训机构-老罗英语培训创业中的各种经历,让我知道我们还可以干干净净挣钱,认认真真做事。那时,我知道我们的价值观是相同的。我们相信善良、相信所有美好的事物,即使现在身处黑暗。 我是一个科技爱好者,我喜欢游戏,SONY 的 PLAYSTATION 3、4、Vita,Nintendo 的 DS、3DS、WII、NS,MOTOROLA 的 MOTO360,APPLE 的 IPAD,IPHONE,MAC。科技产品总是让我内心充满激动,准确的讲,美好的事物总是让人激动的。在《一个理想主义者的创业故事 3》中,老罗公布了他做手机的计划,现在的我已经想不起当时是何种反应。那时小米崛起,一个英语老师转行做手机似乎并不是什么不可实现的事情?那时我肯定是充满期盼的。 一年后,锤子 ROM 问世。那场发布会无疑是失败的,失控的三小时即使是躺在床上看完也是无比疲惫。不得不说,那时我有些失望。尽管在 ROM 上有些功能确实是有亮点,但我对它桌面的设计并不是特别认同。尽管依旧期待,但散去了不少热情。 永远年轻,永远热泪盈眶。— 《The Dharm Bums》 又一年,我已从学生转变为一个上班族。我记得那年那晚,锤子科技 2014 产品发布会。刚下班我就急着回家,边吃饭边用着 iphone 5 看直播。它来了,带着骄傲。Smartisan T1 售价 3000 元起,我承认它的骄傲,只是我买不起。那时也震惊于很多人服了全款或部分货款等出货,却过了一个多月都拿不到。等到产能稳定时,T1 从 3000 + 降价到 2400 + 。那时我在顺电试用了 T1 ,在那之前我对这台手机的所有担忧全部消失的无影无踪。用那时的话讲就是好用的不像安卓手机,然后我用 2800 RMB 买了 4G 32G 版的黑色,现在想想觉得很可惜。因为我觉得 Smartisan T1 白色版真是绝美! 一年后,我在长沙。在失望中购买了 Smartisan T2 ,失望的原因是因为配置。经过一年的服役,不算旗舰的 T1 已经显得有些力不从心。我急切的希望有一台性能强劲的手机,也希望从工业设计、软件系统都能像 Smartisan 做的那样好。我买 T2 唯一的理由只是因为它系统基于 Android 5.0.1,还有就是它美。 16年,Smartisan M系列面世。相对于外观的妥协,配置诚意满满,系统功能更新让人惊艳。One step、Big Bang 都是让人为之惊叹的优点,只是这并不够,外观中规中矩。对于之前用过 T1、T2,审美口味早已被养刁了的我总是在想,为什么锤科总是没有一部完美的手机呢? 现在,锤子科技新品发布会即将到来。依旧让我激动,我希望它能成功。因为我相信美好的事物必将能成功。 如果一开始你没能成功,拿个更大的锤子]]></content>
<categories>
<category>杂谈</category>
</categories>
</entry>
<entry>
<title><![CDATA[SSM 整合教程,献给新人们的礼物 - 第二章]]></title>
<url>%2F2017%2F04%2F19%2Fssm-c2%2F</url>
<content type="text"><![CDATA[前情回顾 在第一章里,我们完成了一个基于 Spring + Spring MVC 的 Web 应用程序。但在我们现实中,一个具有一定业务能力的 Web 系统往往都需要进行数据的持久化。接下来,我们会在第一章的基础上,整合 ORM 框架 — Mybatis 。 ORM: Object Relational Mapping 对象关系映射 添加 Mybatis 依赖1234567891011121314151617181920212223242526272829303132333435363738394041424344454647<!-- Mybatis --><dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.4</version></dependency><dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version></dependency><!-- 数据库连接池 --><dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version></dependency><!-- Spring ORM --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.3.7.RELEASE</version></dependency><!-- Spring 事务 --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.7.RELEASE</version></dependency><!-- Spring AOP --><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.10</version></dependency><!-- Mysql 驱动 --><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version></dependency> 创建数据库配置文件在资源文件夹 resoueces 下创建 datasource.properties: 12345678910111213141516# c3p0 property# 数据库驱动jdbc.driverClassName=com.mysql.jdbc.Driver# 数据库链接jdbc.url=jdbc:mysql://localhost:3306/easyms?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC# 用户名jdbc.username=root# 密码jdbc.password=000000# 最大连接数conn.maxPoolSize=40conn.maxStatements=80# 最小连接数conn.minPoolSize=5conn.initialPoolSize=5conn.maxIdleTime=60 配置数据源在 spring 配置文件 ssm-context.xml 里配置数据源,首先需要导入数据库配置文件 datasource.properties : 12<!--解析资源文件 --><context:property-placeholder location="classpath:database.properties"/> 配置数据源,在 ssm-context.xml 添加如下内容: 123456789101112<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="maxIdleTime" value="${conn.maxIdleTime}" /> <property name="maxStatements" value="${conn.maxStatements}" /> <property name="initialPoolSize" value="${conn.initialPoolSize}" /> <property name="maxPoolSize" value="${conn.maxPoolSize}" /> <property name="minPoolSize" value="${conn.minPoolSize}" /></bean> 注意:dataSouce 的创建必须在 解析资源文件之后,因为初始化数据源的各种信息是通过资源文件读取出来的。 配置 SqlSessionFactory在 ssm-context.xml 添加如下内容: 12345678910111213<!-- MyBatis sqlSessionFactory配置 --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml" /> <property name="typeAliasesPackage" value="ml.jjandxa.mapper.model" /></bean><!-- 自动扫描 Mapper --><bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name="basePackage" value="ml.jjandxa.mapper.mapper" /></bean><bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /></bean> 注:mapperLocations 为 Mapper Xml 文件所在路径,这里为 resources/mybatis/mapper 文件夹为例。typeAliasesPackage 为 Model 类所在路径。 配置 AOP 事务管理在 ssm-context.xml 添加如下内容: 123456789101112131415161718<!-- Spring事务管理器 --><bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /></bean><aop:config> <aop:pointcut id="serviceMethmods" expression="execution(* ml.jjandxa.service.*.*(..))" /> <aop:advisor pointcut-ref="serviceMethmods" advice-ref="txAdvice" /></aop:config><!-- 事务的传播特性 --><tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="*" read-only="true" /> </tx:attributes></tx:advice> 注:pointcut 里的 expression 需更改为相应的包名,它的作用就是配置 aop 时拦截哪些类与方法 如果提示 apo 、 tx 标签找不到,需要在根节点的属性里导入标签的命名空间 123456789<beans xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocatio="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-apo.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"></beans> 到此为止,框架整合的部分结束,接下来便是如何使用的部分了。 编写业务代码对 Mybatis 来说,编写业务时需要几部分: Mapper 接口 Mapper XML Model 其中 Mapper 接口与 Mepper XML 是相对应的,对于每个表都需要以上三部分。回头看看 Mybatis 的配置中,sqlSessionFactory 与 mapperScannerConfigurer 配置了几个关键属性: sqlSessionFactory 的 mapperLocations 属性,这个属性配置 Mapper XML 的路径。 sqlSessionFactory 的 typeAliasesPackage 属性,这个属性配置 Model 的路径。 mapperScannerConfigurer 的 basePackage 属性,这个属性配置了 Mapper 接口的路径。 基于以上配置,我们先在 ml.jjandxa.mapper.model 下编写 Model: 12345678910111213141516171819202122232425262728293031323334353637package ml.jjandxa.mapper.model;/** * Created by aixiaoai on 2017/4/23. */public class User { private Integer id; private String name; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; }} 接着我们需要在 ml.jjandxa.mapper.mapper 包下编写 Mapper 接口 : 12345678910111213141516package ml.jjandxa.mapper.mapper;import ml.jjandxa.mapper.model.User;import org.springframework.stereotype.Repository;import java.util.List;/** * Created by aixiaoai on 2017/4/23. */@Repositorypublic interface UserMapper { // 查询所有用户 List<User> selectAll();} 最后,便是需要在 resources/mybatis/mapper 下编写 Mapper XML 文件: 123456789101112131415<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace="ml.jjandxa.mapper.mapper.UserMapper"> <resultMap id="BaseResultMapper" type="ml.jjandxa.mapper.model.User" > <id column="ID" property="id" /> <result column="NAME" property="name" /> <result column="PASSWORD" property="password" /> </resultMap> <select id="selectAll" resultMap="BaseResultMapper" > SELECT * FROM USER </select></mapper> 注:以上三步,Mapper 接口、Model 的编写都较为简单。重点在于 Mapper XML 的编写。 Mapper XML 根节点的 namespace 即是 Mapper 接口的全限定包名,这就是为什么说 Mapper 接口与 Mapper XML 是相对应的。 Mapper XML 中的 select 、 update 、 insert 、 delete 节点对应 Mapper 接口中的一个方法。 至此,可以发现 Mapper XML 中的 namespace 加上节点id 等于 Mapper 接口中的方法 ml.jjandxa.mapper.mapper.UserMapper.selectAll() 注2:Mapper 接口需要加上 @Repository 注解 最后我们需要编写 Service 层的代码: 1234567891011121314151617181920212223package ml.jjandxa.service;import ml.jjandxa.mapper.mapper.UserMapper;import ml.jjandxa.mapper.model.User;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;/** * Created by aixiaoai on 2017/4/23. */@Servicepublic class UserService { @Autowired private UserMapper userMapper; //查询所有 public List<User> selectAll() { return userMapper.selectAll(); }} 注:同样,注意要加上 @Service 注解 最后一部分,在 Controller 中注入 UserService,这里选择在第一章中最后测试用的 HelloSpring 来测试: 12345678910111213141516171819202122232425262728293031323334353637package ml.jjandxa.controller;import ml.jjandxa.mapper.model.User;import ml.jjandxa.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;import java.util.List;import java.util.Map;/** * Created by aixiaoai on 2017/3/24. */@Controllerpublic class HelloSpring { @Autowired private UserService userService; @ResponseBody @RequestMapping("/hello") public Object hello() { // 查询所有记录 List<User> list = userService.selectAll(); Map<String, Object> result = new HashMap<>(); result.put("data", list); return result; }} 注: 记得往表里添加数据!! 来测试一下吧,启动服务。观察控制台输出,若没有异常,便说明整合并无出现明显错误。访问 http://localhost:8080/hellossm/hello 成功查询出数据!大成功!!! 附录源代码 结语SSM 整合教程到这里就结束了。这些东西很简单,但我想作为新手们入门的一个敲门砖很合适。我们不仅需要知道如何整合框架,也需要明白这些框架如何使用。后续可能会出 Spring MVC 入门使用教程,Mybatis 入门使用教程。当然,最后还是会告诉大家。以上这么繁杂的配置过程,其实都可以用 Spring Boot 来快速解决。但在那之前,希望新手们能够多花时间学习、实践、总结。 ^_^]]></content>
<categories>
<category>后端</category>
</categories>
<tags>
<tag>后端</tag>
<tag>Java</tag>
<tag>Spring MVC</tag>
<tag>Spring</tag>
<tag>Mybatis</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Github Pages & Hexo 博客搭建教程]]></title>
<url>%2F2017%2F03%2F24%2Fgithub-pages-deploy-guide%2F</url>
<content type="text"><![CDATA[为什么要写博客? 之所以以这个标题开头,正是因为这篇文章主要讲的便是基于 Github Pages 服务与 Hexo 静态博客框架搭建博客的教程。博客是什么?在现在这个信息爆炸的时代,我们每天接收到的信息多不胜数。在这海量的信息里有大部分是无用的,我认为在吸收这些信息的同时,能够表达出自身想法、整合并输出优秀的内容正是我们所欠缺的。所以便在这里献上这篇文章,拥有一个博客也是你表达自身想法的第一步。权当抛砖引玉了 ^_^ 环境要求 Github 账户 Git 客户端 NodeJS 文本编辑器 使用 Github Pages 服务 创建一个以自己 Github 用户名加上 github.io 的仓库 例:jjandxa 是我的 Github 用户名,所以需要创建一个名为 jjandxa.github.io 的仓库 测试 Github Pages 服务是否正常 往该仓库推送一个 index.html 页面,并访问 用户名.github.io 域名 这里我以一个测试账户来测试 安装 Hexo 安装 NodeJS ,这一步比较简单,这里就不再赘述了。 这里有一个需要注意的点, Windows 下安装 NodeJS 时需要勾选 Add Path 。如果忘记勾选了,就需要自己配置环境变量。 NPM 是 NodeJS 的包管理器,但是 NPM 官方仓库的速度很慢。建议安装淘宝的 CNPM ,在命令行输入: 1npm install cnpm -g 使用 CNPM 安装 Hexo 1cnpm install hexo -g 使用 hexo 创建一个博客 1hexo init blog 创建博客时可能需要等很久,这是因为在安装 hexo 需要的依赖。当看到提示 Install Dependencies 时就可以 Ctrl + C 取消安装了 安装依赖 12cd blogcnpm install 启动服务 1hexo s 访问 Hexo ,打开浏览器访问 http://localhost:4000 部署博客到 Github Pages 安装 hexo git 部署依赖 1npm install hexo-deployer-git --save 修改博客配置文件 _config.yml 123deploy: type: git repo: <repository url> 该配置默认会部署到 master 分支 执行部署命令 1hexo d 完成后即可直接以你的仓库名称为域名来访问你的博客啦! 其他关于如何配置博客的页面标题、作者、描述、菜单等等请浏览 Hexo 的官方网站。 关于如何配置主题请在 Hexo 官网的主题列表挑选一款主题,然后在查看该主题的文档来配置。这里以 Next 主题为例,请访问 Next 的官方网站查看文档。]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
<tag>Hexo</tag>
<tag>Github Pages</tag>
</tags>
</entry>
<entry>
<title><![CDATA[SSM 整合教程,献给新人们的礼物 - 第一章]]></title>
<url>%2F2017%2F03%2F24%2Fssm-c1%2F</url>
<content type="text"><![CDATA[前言 大半年没有更新过博客,最近在水神的鼓动下又弄起了 Hexo ,想想还是写篇博客抒发下。 初入 Java Web 的童鞋第一个门槛就是创建一个 Web 应用程序,尽管可以使用 Servlet 编写,但那也显得太过“原始“了。为了优化应用架构、提高效率、代码可控,所以我们需要用到各种框架来辅助我们构建应用程序。目前我主要使用的便是 Spring MVC、Spring、Mybatis—简称 SSM 。 接下来会使用 Maven 来管理依赖与构建项目,不懂的童鞋也可以等我后面专门讲解 Maven 的文章。 项目使用 JetBrains 家族的 Intellij Idea 开发。 创建 Maven Web 项目 首先我们需要创建一个基于 Maven 的 Web 项目,点击 Create New Project 。 选择 Maven ,并且选择生成 Web 原型项目-勾选 Create from archetype ,选择 maven -archetype-webapp 原型,并填写项目信息。 提示,选择 archetype 时可能觉得有点乱,这时输入 webapp 即可过滤选择项,然后通过上下箭头选择 Maven 相关: GroupId: 一般为你自己的独立域名 ArtifactId: 项目名称 Version: 版本号 以上信息是 Maven 项目里的必要条件,描述了该项目的所有者、名称、版本号,以作为其他项目添加依赖时的依据。 点击 Finsh 完成项目的创建。 最好开启 Maven 的自动导入喔 Maven pom.xml 设置 指定源代码、单元测试相关的路径。 12345678910111213141516171819202122<build> <!-- 打包后的文件名 --> <finalName>hello-ssm</finalName> <!-- 项目目录 --> <!-- 源代码目录 --> <sourceDirectory>src/main/java</sourceDirectory> <!-- 源代码资源目录 --> <resources> <resource> <directory>src/main/resources</directory> </resource> </resources> <!-- 单元测试代码目录 --> <testOutputDirectory>src/test/java</testOutputDirectory> <testResources> <testResource> <directory>src/test/java</directory> </testResource> </testResources></build> 部署项目。 项目正常启动。 Spring & Spring MVC整合 首先需要添加依赖,spring-webmvc 已经包含了 spring-context 、 spring-core 等等其他核心依赖,所以不用再显式添加了。 123456789101112131415161718192021222324252627282930313233343536373839<!-- servlet-api --><dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version></dependency><!-- spring context --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.7.RELEASE</version></dependency><!-- jackson --><dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.8.7</version></dependency><dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.7</version></dependency><dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.8.7</version></dependency><!-- fileupload --><dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.2</version></dependency> 注:依赖需要添加到 dependencies 节点中 添加完依赖,需要对 web.xml 进行配置,整合 spring & spring mvc 时需要注意以下几点: Spring Context 配置 在 web.xml 添加如下内容: 123456789<!-- spring 监听与参数 --><listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:ssm-context.xml</param-value></context-param> 该配置指定了 ContextLoaderListener 监听器,该监听器会在容器启动时读取 contextConfigLocation 参数以加载 Spring 配置文件。 注:若需要加载多个配置文件,在 contextConfigLocation 参数里以 , 分隔配置文件即可。 例:classpath:ssm-context.xml, classpath:ssm-context2.xml 编码过滤器 在 web.xml 添加如下内容: 123456789101112131415161718<!-- 字符编码过滤器 --><filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter><!-- 映射所有请求到该过滤器 --><filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping> 注:该过滤器的作用等同于 12request.setCharacterEncoding(“编码”);response.setCharacterEncoding(“编码”); 该过滤器接收两个参数: encoding 、 forceEncoding 。encoding 为设置 request 的编码,forceEncoding 为设置 response 的编码与 request 的编码一致。 Spring MVC 配置 在 web.xml 添加如下内容: 123456789101112131415<!-- Spring MVC --><servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:ssm-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup></servlet><servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>/</url-pattern></servlet-mapping> 该配置指定了 Spring MVC 的 Controller 分发器,并且设置了读取 MVC 配置的 xml 配置文件,然后将该 Servlet 映射为 / 路径。 注:经常有人会把 / 与 /* 弄混 /:代表匹配 / 路径下的所有 url ,不包括有后缀的路径,如:/user/addUser 或 /user/deleteUser。 /*:代表匹配所有路径,包括有后缀名的,如:/user/addUser 和 /user/userManaged.jsp。 如配置为 /* 的话需要单独设置 MVC 的资源目录,以忽略 DispatcherServlet 的拦截,如果不配置的话会导致访问 JSP 报 404 错误。 在 resource 目录下创建 ssm-context.xml 、 ssm-mvc.xml 文件。 注:Idea 支持直接创建 Spring 配置文件,右键 resource 目录,New - XML Configuration - Spring Config 即可。 在 ssm-mvc.xml 添加如下内容: 1234567891011121314<!-- 扫描 Spring 组件 --><context:component-scan base-package="ml.jjandxa" /><!-- 视图解析器 --><bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver" > <!-- JSTL 解析器 --> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <!-- 视图前缀 --> <property name="prefix" value="/page" /> <!-- 视图后缀 --> <property name="suffix" value=".jsp" /></bean> ssm-context.xml 添加以下内容: 12<!-- 开启注解 --><context:annotation-config/> 以上配置设置了 Spring 扫描 ml.jjandxa 包下的所有组件,并且开始注解配置。最后设置了 Srping MVC 的视图解析器。 继续在 ssm-mvc.xml 添加如下内容: 123456789101112131415161718192021222324<!-- 对@ResponBody注解进行支持 --><bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" /><bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" > <property name="messageConverters"> <list> <ref bean="mappingJackson2HttpMessageConverter" /> </list> </property></bean><bean id="mappingJackson2HttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>text/json;charset=UTF-8</value> </list> </property></bean><!-- 支持上传文件 --><bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8"/></bean> 以上配置为开启 @ResponseBody 注解的 Json 输出,并且开启了上传文件的支持。 你好,Spring MVC 以上就是整合 Spring MVC 的详尽步骤,启动项目检查是否能够正常启动!如果正常启动,代表上述配置并没有出什么错误,编写一个 Controller 验证 Spring MVC 是否正常工作。编写如下控制器: 12345678910111213141516171819202122232425package ml.jjandxa.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.util.HashMap;import java.util.Map;/** * Created by aixiaoai on 2017/3/24. */@Controllerpublic class HelloSpring { @ResponseBody @RequestMapping("/hello") public Object hello() { Map<String, Object> result = new HashMap<>(); result.put("data", "Hello World!"); return result; }} 测试请求: 成功啦!^_^ 附录源代码]]></content>
<categories>
<category>后端</category>
</categories>
<tags>
<tag>后端</tag>
<tag>Java</tag>
<tag>Spring MVC</tag>
<tag>Spring</tag>
<tag>Mybatis</tag>
</tags>
</entry>
<entry>
<title><![CDATA[部署Docker Gitlab]]></title>
<url>%2F2016%2F07%2F14%2Fdeploy-docker-gitlab%2F</url>
<content type="text"><![CDATA[Gitlab 由于公司现在 Git 私有服务使用的是 Gitblit ,虽然只有我们一个项目组在用 Git ,但使用下来问题不断,基本有以下几点: 团队成员对 Git 的使用不熟悉,目前只有我一个人长期使用 GitHub 、 Coding 等服务。 缺少 Git 工作流程的规范化。 由第二点引起的缺少代码评审机制 对于第一点,引入 SourceTree 可以解决大部分问题,在操作上使用图形界面相对于 Bash 上敲命令容易的多。 对于第二点,起先打算引入 Git Flow 工作流,然而相对来说还是较为复杂,成员不一定能够理解其中奥妙。 对于第三点,如果使用 Git Flow 工作流的话实现不了强制性代码评审,直到后来看到了 Gitlab 。 基于以上几点,我在 Ubuntu 16.04 安装了 Gitlab ,具体安装过程我就不详述了,有兴趣的请点以下链接前往官网: 点我去官网 使用过程中遇到了一个问题,在进入管理员页面时经常会出现 502 错误。详细排查之后才发现是内存不足的原因。 试用过后认为有些优点是比较值得说的: 界面美观 Gitlab Flow类似于 Github Flow 的工作流,就问你怕没?master 分支作为保护分支,只有项目拥有者可以直接操作,成员只能在 master 基础上创建功能分支,开发完成后,提交 Merge Request ,项目拥有者同意后才能合并代码。 强制性代码评审由上一条带来的好处就是, Mereg Request 时,项目拥有者可以和其他成员一起评审代码,对代码进行评论,在此期间,成员可以继续提交代码, Merge Request 会自动跟踪最新代码的情况。 Gitlab CI 集成暂时没用到,就不深入讨论了 具体而言, Gitlab 足够强大,然而问题来了,公司现在代码服务器在内网,系统为 Windows Server 2008 ,而 Gitlab 只支持 Linux ,无奈只能等新服务器再使用 Gitlab 。 你以为这样就结束了? Docker并没有,我在 Ubuntu 16.04 上安装了 Gitlab 后,想起对服务器来说,系统上装了很多服务,维护时总是在解决各种服务的冲突,另外服务不支持快速部署,对移植不友好等等。由此我想到了 Docker ,在恶补了一两天,熟悉 Docker 基本使用后,便着手在 Docker 上部署 Gitlab ,由于在 Docker 上已经有成熟的 Gitlab 镜像,我们直接拿来用就可以了。Docker-Gitlab 在官网可以看到,部署方式特别简单,以下指出几点关键的地方: docker-compose作为一个将容器部署从复杂变为简单的工具,我一开始是不知道的,从 Docker 官网我们可以看到以下这两条安装命令: 12$ curl -L https://github.com/docker/compose/releases/download/1.6.2/run.sh > /usr/local/bin/docker-compose$ chmod +x /usr/local/bin/docker-compose 比较坑的是,国内运行这条命令来安装的速度,慢如乌龟。我一天都没下好,所以建议大家使用 python pip 来安装,运行如下命令: 12345//安装 pipsudo apt-get install python-pip//安装 docker-composepip install docker-compose 安装了 docker-compose 后,就需要部署 Docker-Gitlab 了: 123456789//下载脚本wget https://raw.githubusercontent.com/sameersbn/docker-gitlab/master/docker-compose.yml//第一次运行,来编译,创建容器docker-compose up//后面可以运行一下命令启动或停止容器docker-compose startdocker-compose stop 最后,说一下如何备份 Docker-Gitlab 的数据,运行以下命令: 12345678910111213141516sudo docker run --name gitlab -it --rm \--link [你的postgresql容器名称]:postgresql --link \ [你的redis容器名称]:redisio \--net [docker-compose 初次运行时创建的网络名] \-e 'DB_ADAPTER=postgresql' \-e 'DB_HOST=postgresql' \-e 'DB_PORT=5432' \-e 'DB_USER=gitlab' \-e 'DB_PASS=password' \-e 'DB_NAME=gitlabhq_production' \-e 'REDIS_HOST=redis' \-e 'REDIS_PORT=6379' \--publish 10022:22 --publish 10080:80 \--env 'GITLAB_PORT=10080' --env 'GITLAB_SSH_PORT=10022' \--env 'GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alpha-numeric-string' \--volume /srv/docker/gitlab/gitlab:/home/git/data \sameersbn/gitlab:8.9.6 app:rake gitlab:backup:create 正常情况下你会看下以下画面: 备份文件放在 /srv/docker/gitlab/gitlab/backups 目录下,更换服务器时,只需要将备份文件放到新服务器恢复,一切就完成啦 End]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
<tag>Gitlab</tag>
<tag>Docker</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RequireJS 填坑]]></title>
<url>%2F2016%2F05%2F09%2FRequireJS%2F</url>
<content type="text"><![CDATA[什么是 RequireJS — 自己 Google 去。 Start点我去官网 刚认识用 RequireJS 之前,我还不知道前端还能这样模块化编程,尽管我是个渣,但当我第一次用上之后就回不去以前了,模块化管理你的 JS 是很有必要的,有利于前端工程的规范化管理,页面也不用写很多 link 标签和 script 标签了。 AMD由于 RequireJS 是基于 AMD 规范的,我们需要来了解它是怎么使用的。 1234//hello.jsdefine(['jquery'],function($) { //$('body')}) 这是一个基于的 AMD 规范定义的模块,它接收两个参数,第一个为依赖的字符串数组,第二个为回调的函数。这个例子表明它依赖于 jquery ,RequireJS 在调用加载这个模块时会提前将 jquery 加载进来,并作为参数传进回调函数。 然而我们要怎么使用这个模块呢? var hello = require(['hello']) 这样就可以将一个 define 模块加载进来,并且使用它。然并卵,我们还是结合实例讲解更容易让人理解。 Hello RequireJS以往我们调用 JS 无非是使用外联标签 script 将 JS 文件引入,这样无可避免的会出现如下丑陋的场景 123<script src="a.js" ></script><script src="b.js" ></script><script src="c.js" ></script> 可以想象,当你需要引入的 JS 文件越来越多时会是一副什么景象。正确的做法是 1234567891011121314<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Hello RequireJS</title> <script src="require.js" ></script></head><body></body><script> var a = require('a') var b = require('b') var c = require('c')</script></html> 怎么样?是否简洁了很多呢?然而还不够,我们希望 JS 代码尽量抽离出 HTML ,所以我们可以这样写 1234567891011121314// main.jsdefine(['a', 'b', 'c'], function(a, b, c) {})// index.html<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"> <title>Hello RequireJS</title> <script data-main="main" src="require.js" ></script></head><body></body></html> 实在太简洁了,优美的代码总是让人愉悦,不是么? 开始使用啦由于我们的实际项目没有这么简单,所以我们需要对 RequireJS 进行一些基础配置先看一下项目目录 1234567891011121314151617181920212223242526272829303132// main.jsrequire.config({ // 所有的路径会以 baseUrl 为基础 baseUrl: 'static/lib', // 定义模块 paths: { app: '../js'. jquery: 'jquery-3.0.0' }})// js/hello.jsdefine(['jquery'], function ($) { $('body').append('<p>Hello RequireJS!</p>')})// index.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta content="width=device-width, initial-scale=1" name="viewport"/> <title>Hello RequireJS</title> <script src="static/require.js" ></script></head><body></body><script>require(['static/main'], function () { require(['app/hello'])})</script></html> 完成啦,只需要在 HTML 页面引入 require.js ,并且依赖 main.js 全局配置文件,然后就可以尽情依赖自己的模块了!!打开 index.html 非AMD模块的兼容我们知道 jquery 有很多优秀的插件,它们都依赖于 jquery ,但是很遗憾的是有很多插件并不支持 AMD 规范,这导致我们依赖这些插件时会遇到一些错误,我们该怎么处理呢? 12345678require.config({ //省略 ... shim: { 'jquery-plugin': { deps: ['jquery'] } }}) RequireJS 提供了 shim 选项为非 AMD 模块进行兼容,我们为 jquery-plugin 插件手动配置了其依赖于 jquery ,这样 RequireJS 会确保 jquery 在 jquery-plugin 之前加载!是不是很完美呢?附上源码:requirejs-demo]]></content>
<categories>
<category>JavaScript</category>
</categories>
<tags>
<tag>JavaScript</tag>
<tag>RequireJS</tag>
<tag>前端</tag>
</tags>
</entry>
<entry>
<title><![CDATA[MacBook Pro]]></title>
<url>%2F2016%2F04%2F22%2FMacBook-Pro%2F</url>
<content type="text"><![CDATA[话说从学生时代就盼着的 MacBook Pro 终于入手了 之前在长沙时,本想等着3月份 Apple 春季发布会新款出来买新款的,结果那天晚上看完发布会整个人失望的无以复加。竟然只发布了 iPhone SE , iPad Pro , 还有几个表带,我能说啥?Omg ,我的 RMBP 只能继续等了。 四月 我忍不了,回到家后百无聊赖的,想起了盼望已久的 MacBook Pro ,我的心蠢蠢欲动,终于!在控制不了双手的情况下,点下了下单按钮!!一切都归于平静了。这时,我想起了那句话: 买了悔三天 不买悔三年 至于说为什么我这么迷恋 MBP作为一个正常人,我只能说: 因为它美]]></content>
<categories>
<category>OS X</category>
</categories>
<tags>
<tag>OS X</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Hello Hexo]]></title>
<url>%2F2016%2F04%2F21%2FHello-Hexo%2F</url>
<content type="text"><![CDATA[能说啥!这么久了,终于往 Github 挂了 Blog,归根结底就是懒吧~~~大家好,我来了!]]></content>
<categories>
<category>其他</category>
</categories>
<tags>
<tag>其他</tag>
</tags>
</entry>
</search>