微信开发踩过的坑

JSSDK

  • 微信使用方法wx.config时需要输入appid, 否则会直接抛出config:fail而不会有具体的错误提示。如果你的应用的appid来源比较复杂的话有必要检查一下appid
  • 微信在分享时如果link参数非法, 则会出现debug内容一切 正常 但是无法正常进行分享内容自定义。

Linux服务安全——记一次网络攻击

背景

使用linode服务器第二天就经历一次网络攻击,对方通过暴力破解root账号成功攻陷了我密码强度相对较低的服务器并通过我的服务器向外发起DDOS攻击导致在2小时间流失上行流量500G。造成了相当的损失

日志查询

1
# lastb

lastb命令查询试图通过SSH访问服务器密码失败的记录。可以查询到有来自印度,韩国与一个不知名国家的ip的重复尝试请求,由于访问次数多、频率高,可以看出是通过脚本来实现暴力破解的行为

1
# last

last 查询登录记录,在登录记录中查看到在事发时间没有任何登录请求。可能原因有二:

  • 对方在拿到root权限账号后清除了自己的登录记录
  • 对方在之前就拿到了root权限并留下了木马之类的后门,在事发时间通过服务端口远程访问

同时可以通过查看/var/log/secure日志文件查询用户登录记录

安全策略

https://www.linode.com/docs/security/securing-your-server/
https://www.cnblogs.com/alimac/p/5848372.html

相关命令

1
2
3
4
5
# 查看当前网络服务
sudo netstat -tulpn

# 重启sshd
sudo service sshd restart

解决方案

  • yum update
  • 增加第二管理员账号
  • 增加第二管理员sudo权限
  • 禁止root用户直接通过ssh登录
  • 关闭密码登录,使用秘钥对登录
  • 配置iptables(可选)

.ssh文件权限

  • 700 .ssh
  • 600 authorized_keys

python学习笔记

Flask

flask是一个轻量级的web框架。

PIL 相关内容

  • 混合半透明图片与半透明图片时会出现中空透明项目的问题

    imlayer 为两张半透明图,如下图所示:

    im(100x100)

    layer(100x100)

    执行:

    1
    im.paste(layer, mask=layer)

    返回

    会发现里面有透明像素,而不是我们想要的layer叠加到im上的效果
    这是因为layer本身拥有一个渐变的alpha通道,通过mask指定的alpha通道蒙版会在im抠出一个渐变的圆形。然后再把拥有渐变效果的layer叠加到im上。
    这就相当于在两个地方处理了两次透明处理。解决方法很简单,就是在粘贴layer的时候丢弃掉它的alpha通道即可

    返回

前端编写全栈应用技术选型——rn、weex、apicloud到底选哪个

前言

本文主要是经过部分调研,几种技术栈的初步调研与入门使用。以及部分网络资料的整理下得出的带有一部分主观判断的结论,并因互联网发展迅速的关系可能会有一定的时效性,因此在阅读本文的时候请读者酌情根据当前情况进行自己的思考与结论。

关于Apicloud

apicloud是国内一家闭源的html5移动端app解决方案提供商。用纯html的方式生成页面并通过原生注入jsbridge来与js进行交互。处理原生事件与页面切换。对于开发人员来说,是纯html+一个api事件。无需接触到任何底层,并如果有特殊需求可以自行开发原生应用模块来进行拓展。
因为闭源的关系,国内社区并不是很火。因此名气不是很大。
类似产品有AppCanDcloudWeX5

技术比较

首先是一个对比表来区别几种技术栈。

技术特点 Native ReactNative Weex Apicloud
学习成本
开发成本
调试难度 简单
控制台输出 支持 支持 支持 不支持
断点调试 支持 支持 支持 不支持
开发工具 官方提供IDE 官方提供终端 官方提供终端 官方提供IDE
模拟器/真机 必须 必须 非必须 必须
开发硬件需求(ios) Mac Mac
应用硬件需求(ios) All Android 4.1 (API 16), iOS 8.0+ Android 4.1 (API 16), iOS 8.0+ and WebKit 534.30+ (未找到)
可拓展性
优化难度
渲染流畅
渲染方式 原生图形库 Virtual DOM Virtual DOM Webview
布局方式 XML React Vue Html
样式写法 属性 基于css的对象 阉割版css 原生css
代码结构 基于类 基于类 基于类 基于Page
代码架构 MVC MVVM MVVM
技术栈 JAVA+Android SDK;
OC/Swift + cocoa
React+ReactNative Vue+Weex Html+Apicloud api
社区活跃度 活跃 活跃 一般 不活跃
开源闭源 开源(安卓,核心代码闭源);
闭源(ios)
开源 开源 闭源
开源协议 Apache(安卓) BSD Apache /
支持公司 谷歌苹果 Facebook 阿里巴巴 活了3年的小公司
坑量
遇坑概率
专业著作 《Android从入门到翻墙》;
《IOS从买Mac到装Windows》
《ReactNative从入坑到弃坑》 《Weex从信任到骂KPI》

以上大概是我个人总结出来的各个技术栈的区别。

关于KPI

为什么要在这里提一下KPI呢。就不得不说一说阿里传统。阿里的工资水平是基于 关键绩效指标 来进行升迁评估。因此为了个人、小组的工资待遇,必须要给上级做出点成绩来。也就是说要考虑到阿里的项目是不是基于这个原则来开源的项目。
(PS: 据网络流传阿里内部自己都不用weex)
这就是为什么业内普遍对weex很冷淡,而weex本身也没有花太多力气去推广。

关于技术选择

为什么说是技术选择呢?因为技术不是产品,技术是一种创新的态度。产品,要求的是稳,快。而技术,要的是新。如果是考虑技术的话。我会选择ReactNative 或者 Weex。这两者都是基于现代前端的MVVM架构诞生的以HTML技术编写原生应用的产品。它们不是用的手机端的html解释器与渲染引擎,而是以标签与嵌套描述原生组件的技术。因此它们的渲染效率可以直追原生应用。
但是,它们尚不是一个很成熟的技术,不像原生技术有多年沉淀,不像html有厚重的历史。它们作为前端最前沿的技术(应当还要算上NativeScript),它们还比较年轻,换种说法就是坑比较多。在网上找找,大部分文章都是ReactNative踩坑大全、Weex踩过的坑。不可否认它们能够做出比较成熟,渲染效率也不错的APP应用。但是如果是作为一个产品的话,我们不得不计算上使用这个技术所需要花费的时间成本。

关于产品选择

如果只是为了实现一个产品的话。那么我推荐使用Apicloud等以html渲染方式的技术。其内核是cordova,前身是phonegap,也是前段跨平台编写原生应用的老前辈了。技术也是已经达到了一个相对成熟的地步。因为野心不大,所以坑少。牺牲一部分不明显的渲染效率来换取稳定且成熟的实现方式,我认为是一种很明智的选择。举个最简单例子。一个页面,原生渲染需要消耗10ms,而cordova需要消耗50ms。其中渲染效率相差整整5倍,然而实际上用户并不能感受到这之间明显的差别。当然如果是需要比较复杂的动画效果的话,这个问题可能会被放大。(人眼辨别连续运动的物体只需要每秒24帧,但是却可以很明显的感觉到每秒60帧与每秒30帧的区别)
其问题还在于,对于喜欢折腾的技术人员来说,使用旧技术去做产品是一件很无趣的事情。如果是我自己的项目,我绝对不会去使用该类技术。因为真的很无聊。

代码风格

代码风格很重要,因为很明显的可以影响程序员的编写效率。一个好的框架可以极大程度上改变工作的进度。
这里截取一部分代码,来感受下各个技术栈之间的差异。

React: F8App

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
var React = require('React');
var AppState = require('AppState');
var LoginScreen = require('./login/LoginScreen');
var PushNotificationsController = require('./PushNotificationsController');
var StyleSheet = require('StyleSheet');
var F8Navigator = require('F8Navigator');
var CodePush = require('react-native-code-push');
var View = require('View');
var StatusBar = require('StatusBar');
var {
loadConfig,
loadMaps,
loadNotifications,
loadSessions,
loadFriendsSchedules,
loadSurveys,
} = require('./actions');
var { updateInstallation } = require('./actions/installation');
var { connect } = require('react-redux');

var { version } = require('./env.js');

var F8App = React.createClass({
componentDidMount: function() {
AppState.addEventListener('change', this.handleAppStateChange);

// TODO: Make this list smaller, we basically download the whole internet
this.props.dispatch(loadNotifications());
this.props.dispatch(loadMaps());
this.props.dispatch(loadConfig());
this.props.dispatch(loadSessions());
this.props.dispatch(loadFriendsSchedules());
this.props.dispatch(loadSurveys());

updateInstallation({version});
CodePush.sync({installMode: CodePush.InstallMode.ON_NEXT_RESUME});
},

componentWillUnmount: function() {
AppState.removeEventListener('change', this.handleAppStateChange);
},

handleAppStateChange: function(appState) {
if (appState === 'active') {
this.props.dispatch(loadSessions());
this.props.dispatch(loadNotifications());
this.props.dispatch(loadSurveys());
CodePush.sync({installMode: CodePush.InstallMode.ON_NEXT_RESUME});
}
},

render: function() {
if (!this.props.isLoggedIn) {
return <LoginScreen />;
}
return (
<View style={styles.container}>
<StatusBar
translucent={true}
backgroundColor="rgba(0, 0, 0, 0.2)"
barStyle="light-content"
/>
<F8Navigator />
<PushNotificationsController />
</View>
);
},

});

var styles = StyleSheet.create({
container: {
flex: 1,
},
});

function select(store) {
return {
isLoggedIn: store.user.isLoggedIn || store.user.hasSkippedLogin,
};
}

module.exports = connect(select)(F8App);

React 应用是基于组件(或者说类),其特点是不直接接触html代码,而是返回一个虚拟dom,根据虚拟dom来修改前端显示。其所有的组件(包括根容器)都是基于React.Component这个类进行实现的,开发者要做的都是复写他的方法(主要是render方法)


Weex: yanxuan-weex-demo

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
<template>
<div class="wrapper">
<text class="tlt iconfont">{{title}} &#xe74b;</text>
<div class="box">
<div class="box-item" v-for="i in items" @click="jumpWeb(i.url)">
<image class="i-image" resize="cover" :src="i.bg"></image>
<text class="i-name">{{i.name}}</text>
<div class="i-price"><text class="i-price-n">{{i.price}}</text><text class="i-price-t">元起</text></div>
<text class="i-state" v-if="i.state">{{i.state}}</text>
</div>
</div>
</div>
</template>
<style scoped>
.iconfont {
font-family:iconfont;
}
.wrapper{
background-color: #fff;
padding-bottom: 6px;
}
.tlt{
text-align: center;
font-size: 30px;
margin-top: 30px;
margin-bottom: 26px;
color:#333;
}
.box{
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
width: 750px;
}
.box-item{
width: 350px;
height: 226px;
margin: 5px;
padding: 20px;
background-color: #efefef;
}
.i-name{
position: relative;
color:#333;
font-size: 28px;
width: 300px;
}
.i-price{
position: relative;
margin-top: 10px;
display: flex;
flex-direction: row;
}
.i-price-n{
color:#333;
font-size: 36px;
}
.i-price-t{
color:#333;
font-size: 24px;
margin-top: 12px;
}
.i-state{
position: relative;
font-size: 20px;
color:#b8a989;
width: 70px;
margin-top: 10px;
padding: 5px;
line-height: 20px;
text-align: center;
border-width: 1px;
border-color: #b8a989;
border-radius: 4px;
}
.i-image{
position: absolute;
top:0;
left: 0;
width: 350px;
height: 226px;
}
</style>
<script>
var navigator = weex.requireModule('navigator')
import util from '../../src/assets/util';
export default {
props:["title","items"],
data () {
return {
}
},
methods: {
jumpWeb (_url) {
if(!_url) return;
const url = this.$getConfig().bundleUrl;
navigator.push({
url: util.setBundleUrl(url, 'page/web.js?weburl='+_url) ,
animated: "true"
});
}
}
}
</script>

Weex2.x 是基于Vue作为前端驱动。一个vue文件是由template, script, style三个标签组成的。相比React更加趋近与网页端的写法。最后返回给解释器一个大对象,来对dom进行操作。当然我个人是不喜欢这种返回一个大对象的方式的。曾经也有一个类似操作一个大对象的前端工具叫grunt,然后被gulp取代了。。。


Apicloud: Answer

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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="maximum-scale=1.0,minimum-scale=1.0,user-scalable=0,width=device-width,initial-scale=1.0"/>
<meta name="format-detection" content="telephone=no,email=no,date=no,address=no">
<title></title>
<link rel="stylesheet" type="text/css" href="../../css/api.css"/>
<link rel="stylesheet" type="text/css" href="../../css/aui.css"/>
<style>
#userHeader {
text-align: center;
padding: 10px 0;
background-color: #FFFFFF;
margin-bottom: 10px;
}
#userHeader img {
border-radius: 100%;
width: 120px;
height: 120px;
}
#userHeader h1 {
font-size: 20px;
}
#userButton {
display: -webkit-box;
padding-bottom: 10px;
}
#userButton .aui-col-xs-6 {
padding: 0 6px;
}
#userButton .aui-col-xs-6 .aui-btn {
width: 100%;
line-height: 26px;
}
#userInfo {
background-color: #FFFFFF;
padding: 0 12px;
margin-bottom: 10px;
}
#userInfo li {
border-bottom: 1px solid #e3e3e3;
line-height: 56px;
}
#userInfo li:last-child {
border-bottom: 0;
}
#userInfo li div {
display: inline-block;
margin-right: 15px;
color: #8f8f94;
}
</style>
</head>
<body>
<div class="aui-content">
<div id="userHeader">
<img id="info-head" src="../../image/default_head.jpg"/>

<h1 id="info-name">&nbsp;</h1>
</div>
<ul id="userInfo">
<li>
<div>性别</div>
<span id="info-sex">&nbsp;</span>
</li>
<li>
<div>学校</div>
<span id="info-school">&nbsp;</span>
</li>
<li>
<div>身份</div>
<span id="info-role">&nbsp;</span>
</li>
<li>
<div>简介</div>
<span id="info-intro">&nbsp;</span>
</li>
<!--<li>-->
<!--<div>话题</div>-->
<!--<span id="info-topic-num">&nbsp;</span>-->
<!--</li>-->
<!--<li>-->
<!--<div>回复</div>-->
<!--<span id="info-reply-num">&nbsp;</span>-->
<!--</li>-->
</ul>
<div id="userButton">
<div class="aui-col-xs-6">
<div class="aui-btn aui-btn-default" onclick="AddFriend();">关注</div>
</div>
<div class="aui-col-xs-6">
<div class="aui-btn aui-btn-danger" onclick="SendMessage();">发消息</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="../../script/api.js"></script>
<script type="text/javascript">
var userId;
var userName, headImgUrl;
var defaultHeadImg = '../../image/default_head.jpg';
apiready = function () {
$api.fixStatusBar($api.dom('header'));
var pageParam = api.pageParam;
userId = pageParam.userId;
UpdateUserInfo(userId);
};
function UpdateUserInfo(userId) {
if(!!userId){
var model = api.require('model');
var query = api.require('query');
query.createQuery(function (ret, err) {
if (ret && ret.qid) {
var queryId = ret.qid;
query.whereEqual({qid: ret.qid, column: 'id', value: userId});
query.include({qid: ret.qid, column: 'profile'});
model.findAll({
class: "user",
qid: queryId
}, function (ret, err) {
if (ret) {
var userInfo = ret[0];
var nickname = userName = userInfo.nickname;
var url = headImgUrl = userInfo.avatar ? userInfo.avatar.url : defaultHeadImg;
var profile = userInfo.profile || {
role: '未知',
school: '未知',
intro: '这家伙很懒什么都没写',
sex: '未知'
};
$api.attr($api.byId('info-head'), 'src', url);
$api.html($api.byId('info-name'), nickname);
$api.html($api.byId('info-sex'), profile.sex);
$api.html($api.byId('info-school'), profile.school);
$api.html($api.byId('info-role'), profile.school);
$api.html($api.byId('info-intro'), profile.intro);
}
});
}
});
}
}
//添加好友
function AddFriend() {
var myUserInfo = $api.getStorage('userInfo');
if (!!myUserInfo && !!myUserInfo.userId && !!userId) {
api.showProgress({
style: 'default',
animationType: 'fade',
title: '努力加载中...',
text: '先喝杯茶...'
});
var myUserId = myUserInfo.userId;
var model = api.require('model');
var query = api.require('query');
query.createQuery(function (ret, err) {
if (ret && ret.qid) {
var queryId = ret.qid;
query.whereEqual({qid: queryId, column: 'userId', value: myUserId});
model.findAll({
class: "Friends",
qid: queryId
}, function (ret, err) {
if (!!ret && ret.length > 0) {
//有该条数据
var data = ret[0];
var id = data.id;
var friends = data.friends;
if (!!friends.default) {
if (friends.default.indexOf(userId) >= 0) {
//已经添加过
api.toast({msg: '已经添加过该好友了'});
return;
} else {
friends.default.push(userId);
}
} else {
friends.default = [userId];
}
model.updateById({
class: 'Friends',
id: id,
value: {
friends: friends
}
}, function (ret, err) {
api.hideProgress();
if (!!ret) {
api.toast({msg: '添加好友成功'});
} else {
api.toast({msg: '网络异常'});
}
})
} else if (!!ret && ret.length == 0) {
//没有该条数据
api.hideProgress();
model.insert({
class: 'Friends',
value: {
userId: userId,
friends: {
default: [userId]
}
}
});
api.toast({msg: '添加好友成功'});
}
});
}
});
} else {
api.toast({msg: '您尚未登录'});
}
}
//发送消息
function SendMessage() {
if (!!userId && !!userName && !!headImgUrl) {
api.openWin({
name: 'chattingFrame',
url: '../message/chattingFrame.html',
pageParam: {
targetId: userId,
targetName: userName,
headImgUrl: headImgUrl,
conversationType: 'PRIVATE'
}
});
}
}
</script>
</html>

Apicloud这种技术就是基于html实现的,而每个html对应一个网页对应一个窗口。和普通的网页编写非常类似。这也就是为什么该项技术成熟的原因。因为完全就是玩个的编写方式。当然区别就是提供了一个可以供js调用的原生服务的接口。

总结

具体选择哪项技术,要根据自己的实际需求来决定。任何脱离实际需求的选择都是耍流氓。
但是,如果决定选择ReactNative或者Weex这样注定是未来趋势的新技术的话。就必须做好不断踩坑的打算,毕竟我们需要给予这些技术以发展的时间。当然,reactjs与vue已经是相对成熟的技术了,如果是为了学习的话。顺便学习一门新的技术也是一个不错的决定。

node遇见问题汇总

  • 使用npm adduser出现错误

在确定用户名密码无误的情况下出现如下错误:

1
2
3
4
5
6
7
8
9
10
Username: moonrailgun
Password:
Email: (this IS public) moonrailgun@gmail.com
npm WARN adduser Incorrect username or password
npm WARN adduser You can reset your account by visiting:
npm WARN adduser
npm WARN adduser https://npmjs.org/forgot
npm WARN adduser
npm ERR! code E401
npm ERR! unauthorized Login first: -/user/org.couchdb.user:moonrailgun/-rev/undefined

如果你在全局设定过 淘宝镜像,那么你有可能是 淘宝镜像 的受害者。

解决方案
删除在个人用户文件夹目录下的.npmrc文件即可。
window则是在C:\Users\username文件夹下

  • 使用npm install时不安装devDependencies需要的包

如果发生这种情况那么你有可能是因为升级到了npm@5。解决方案要么降级npm,要么进行一下npm配置。因为npm@5以后默认为生产环境。尝试输入:
npm config set -g production false
来解决这个问题

  • 相对路径过长导致无法很方便的定位路径

除了在webpack、babel等工具定义绝对路径的map以外,package.json文件也能提供类似的子包管理的功能。详见文章:How to Use Absolute Paths in React Native

  • 在多包共存的项目中,明明两个对象看上去一模一样但是不相等

需要检查一下这两个对象是否来自于不同的包。这个问题很难被发现,因为没有好的办法去检查一个对象的来源。需要人肉检查。这是node这种树形结构的包管理所必然会遇到的一个问题(即你安装的A包与B包依赖的A包可能是两个同名但不同版本的依赖)。

  • 在Node中经常会需要编译原生模块的包,很多c的环境对于不熟悉的开发者来说搭建这些环境是很没有必要的一件事,特别是window环境下缺失很多编译环境,如python,vs

在window下可以用管理员权限的终端安装npm install --global windows-build-tools,该模块会一键帮你安装大部分的编译环境。更多细节可以查看Github

blender学习笔记 - 权重传递

概述

在使用Blender为角色做衣服,特别是贴身衣物的时候,会出现这样的一个问题:权重不好刷,没法完全的和人物基础模型保持一致。为了解决这个问题,blender给我们提供了一个非常好用的工具:权重传递。

使用前提

权重传递需要几个前提:

  • 一个已经刷好权重的身体
  • 一个适配身体的骨架
  • 一个在基础建模上已经匹配身体的衣服模型

传递权重

首先确保两个模型都绑定在同一个骨骼上(为了防止出现错误与方便测试),然后先选中身体模型,再选中衣服模型,衣服模型切换到权重模式以保证T栏出现权重工具。
点击权重工具,即可完成传递权重的操作。按下F6可以对传递权重进行一些细微的修改上的配置。一般默认即可。
那么现在就可以开始随便动动骨骼测试一下结果啦。

如何配置Apache虚拟主机服务

前言

为什么要配置虚拟主机?

  • 为了让多个项目能同时放在一个服务器上,且相对路径都是服务器根目录
  • 为了在一个服务器上分配多个2级域名指向的项目

配置方法

  • 首先找到Apache安装目录。修改/conf/httpd.conf,将Include conf/extra/httpd-vhosts.conf这行启用。即引入httpd-vhosts.conf配置文件。
  • 修改/conf/extra/httpd-vhosts.conf配置文件。可以看到已经提供了两个demo如下:
    1
    2
    3
    4
    5
    6
    7
    8
    <VirtualHost *:80>
    ServerAdmin webmaster@dummy-host.example.com
    DocumentRoot "E:/XAMPP/htdocs/dummy-host.example.com"
    ServerName dummy-host.example.com
    ServerAlias www.dummy-host.example.com
    ErrorLog "logs/dummy-host.example.com-error.log"
    CustomLog "logs/dummy-host.example.com-access.log" common
    </VirtualHost>
    其中最主要的是DocumentRoot,ServerName两个字段。分别代表了项目路径服务器名。这里需要注意服务器名就是完整的URL路径如test.example.com这样的2级域名或者三级域名。如果有特殊的需求也可以写作顶级域名。这个字段的作用是把监听端口的请求网址为服务名的请求指向对应的虚拟主机项目路径。
  • 重启Apache服务器

访问虚拟主机

如果该服务器配置的顶级域名已经被DNS服务器解析完毕,则可以直接在浏览器中输入ServerName访问。如果该服务器域名未被解析,可以通过修改HOSTS方法强制指向服务器来访问

可能出现的问题

在添加虚拟主机以后,可能会出现原始项目路径不可用的情况。如果想保留原始访问地址可以引入别名模块。
/conf/httd.conf中打开LoadModule vhost_alias_module modules/mod_vhost_alias.so这项来载入模块。

gulp工具使用笔记

gulp是一款非常好用的前端工具,自从用了gulp以后我马上就抛弃了grunt投入的gulp的怀抱。
比起grunt的配置型,我更加喜欢gulp的函数型。通过编写各种各样的gulp任务函数来配置一系列任务来完成各种各样的需求。

总得来说,就很爽

常用的gulp插件

gulp-sourcemaps
gulp-sass
gulp-clean
gulp-rev
gulp-fingerprint
gulp-plumber
gulp-compass

常用函数

gulp.start() //执行任务
gulp.task() //注册任务
gulp.watch() //监听文件
gulp.src() //获取匹配文件

gulp能干什么?

  • gulp可以监听文件修改,自动执行一些任务比如自动编译。提升工作效率
  • gulp可以通过一些插件来开启简易端口,方便前端调试
  • gulp可以压缩JS文件,CSS文件,img图片
  • gulp可以统一封装你的代码。调用node来执行任务。

Webpack打包工具性能优化

前言

为什么要写这篇文章

在最近的项目中顺便学习了一下React.而React推荐使用ES6所以也顺便学习了一下ES6.然后现有浏览器不能直接支持ES6的语法因此需要第三方打包工具.这里学习使用了Webpack.然而React本身大小就有1M+.每次Webpack进行打包操作的时候总是会显得过于臃肿.消费时间近10s.因此写下本篇文章来记录自己的打包优化之路

externals

优化方案

对于React这类第三方库而言.我们是不需要多次进行打包的因为我们本身不会对其源码进行操作修改.因此多次打包同一个包是一件多余的事情.因此我们要告诉Webpack我们不需要打包这个包.而只需要用手动的方式来直接引入预编译好的js版本即可.

解决方案

  • 在网上下载预编译好的js文件
  • 在HTML代码中(在调用打包过后的js文件前)引入预编译好的js文件
  • webpack.config.js中添加externals字段.如以下写法:
    1
    2
    3
    4
    5
    6
    module.exports = {
    externals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
    }
    }
  • 删除不必要的node库,减小项目体积(如React:npm uninstall --save react react-dom)

devtool

如果打包时启用了devtool, 请在生产环境下关闭或打到独立的sourcemap文件中。可以大大减少打包后的文件体积

DllPlugin

dll是一种非常棒的优化手段。它直接以比较粗暴的方式将一些常用的,不修改的第三方库打包到独立的文件,使日常开发中不会去编译他。这种方式非常类似于用CDN引用第三方库不过可以顺便对其进行一些特殊的处理。

首先创建一个 webpack.dll.config.js 文件

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
const path = require('path');
const webpack = require('webpack');

// 原则上是需要全量导入 且共用的模块
const dllModules = ['react', 'react-dom', 'moment'];

module.exports = {
entry: {
vendor: [...dllModules],
},
output: {
filename: 'dll_[name].js',
library: '[name]_[hash]',
path: path.resolve(__dirname, './dll'),
},
plugins: [
new webpack.DllPlugin({
path: path.resolve(__dirname, './dll/[name]-manifest.json'),
name: '[name]_[hash]',
}),
// 该命令使仅打包moment的zh-cn语言文件
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
],
mode: 'production',
};

独立编译

1
$ webpack --config webpack.dll.config.js

生成一个 manifest.json 和 一个 dll_vendor.js 文件

我们在我们正常的webpack.config.js文件中引用他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const path = require('path');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const dllConfig = require('./dll/vendor-manifest.json');

module.exports = {
// ...
plugins: [
new webpack.DllReferencePlugin({
manifest: dllConfig,
}),
new CopyWebpackPlugin([
{
from: path.resolve(BUILD_PATH, './dll/dll_vendor.js'),
to: '',
},
]),
],
}

然后在HTML模板中手动增加该文件的引入即可

1
<script src="/dll_vendor.js"></script>

你也可以通过dllConfig的name属性来增加hash来实现更好的更新

团队使用git方法简明教程

前言

学习团队使用需要先学习如何个人使用git来管理自己的项目,如何使用git来管理自己的独立项目可以查看我之前的文章
个人使用git方法简明教程

本篇文章使用git bash作为说明工具。是为了让读者能够更好的了解git的原理(作为程序员来说了解原理比仅仅知道如何使用可能更加简单入门)。对于git原理方便以后我会专门开一篇文章介绍更加深入的内容这里不做累述

使用

一般操作流程

初次使用

1
2
3
4
5
#已有在远程仓库有项目
$ git clone [项目地址]

#本地新建项目
$ git init

平时代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#开始写项目前,拉取远程git仓库代码保证不会出现大的错误
$ git pull

#写完部分代码(即完成一阶段任务或者功能)
#一般推荐阶段划分小而多的原则
$ git status #查看变动
$ git add . #添加所有改动到准备提交队列,符号.代表所有文件
##如果仅想要单个文件或者单个文件夹下所有改动可以使用命令 git add [文件完整地址/文件夹地址]

#提交修改
$ git commit
##注意这里会弹出一个命令行编辑框,默认是使用nano,建议修改默认的命令行编辑器为vim
:wq 结束编辑
#简洁提交
$ git commit -m [message]#平时更贱推荐的方法,不方便做出比较复杂的格式只能一行,不允许有空格,换行使用\n转义字符
user
#将代码提交到远程
$ git push
#如果出现文件冲突,会导致push被拒绝。解决方案是git pull远程代码后检查冲突文件并手动解决,然后再走一遍add —> commit —> push

简单的说流程就是pull —> add —> commit —> push

特殊远程
我们在pull和push的时候可以通过在后面添加远程仓库地址 分支名的方式管理pull和push的来源。默认的远程仓库名为origin

分支管理
首先了解几个常用命令:

  • git branch 查看分支,带*号的是当前分支
  • git branch [分支名] 创建一个分支
  • git branch -d [分支名] 删除一个分支
  • git checkout [文件名或文件夹路径] 检出本地库中最新的文件(一般用于撤销文件的修改)
  • git checkout [分支名] 切换到某个分支
  • git checkout -b [分支名] 创建并切换分支
  • git merge [分支名] 将该分支合并到当前分支

操作流程:创建分支 —> 进行改动 —> 完成改动 —> 合并到主分支 —> 如有冲突手动处理冲突 —> 删除开发分支
example:

1
2
3
4
5
6
7
8
9
10
11
12
#创建分支并切换到该分支
$ git branch dev
$ git checkout dev

$ git checkout -b dev

#change file

#融合分支到主分支并删除开发分支
$ git checkout master
$ git merge dev
$ git branch -d dev

暂存工作
很多时候会遇见这样一个问题:我现在在一个独立的分支工作。而另一个分支出现了一个bug需要的紧急修改并提交,而我手头的工作还没完成一阶段内容(即无法提交一个新的commit),那么我们要如何临时保存自己的进度去切换到出现问题的分支呢?git给我们提供了一个非常实用的工具:git stash
用法很简单,简单使用只要记住两个命令即可:

  • git stash 暂存当前工作
  • git statsh pop 还原最近一次的暂存工作
    同时我们可以使用git stash list方法查看git栈中有几个工作节点

不建议在git栈中保存多个节点容易导致混乱


知道以上几点即可简单方便的使用git工具进行团队开发了。

使用GUI进行git管理

在个人版已经推荐过了github的git客户端,但是在团队协作,特别是多人单分支团队协作中优势很不明显,无法很直观的看见各个协作者的提交情况。这里推荐另一款git图形化界面客户端:

gitg
GitKraken
PS:GitKraken默认会自动设置所有项目的作者信息为统一。多身份项目的用户需要注意一下