Linux常用命令记录

简介

主要是为了记录一下linux使用过程中常用的命令。方便日后检索

快捷键

  • ctrl+z 将当前任务暂停,回到终端页面。可以使用fg回到任务或使用bg将当前任务挂到后台

常用命令

  • cp from to 复制
  • mv from to 移动
  • rm file 删除
  • cat file 输出文件
  • pwd 输出当前工作空间
  • who 当前登录所有用户
  • whoami 当前登录用户名
  • wget url 下载文件
  • curl url 发送请求
  • free -h 查看系统内存
  • df -h 查看硬盘空间
  • du -h ./ 查看文件占用
  • tail -f 持续跟踪文件
  • uname -a 查看系统信息
  • netstat -tulpn 查看系统网络情况
  • lsof -i:80 查看系统端口监听
  • grep -r "str" ./ 在文件中查找字符串
  • find ./ -name filename 在路径下根据文件名查找文件
  • top 任务管理器
  • uptime 启动时间,登录用户数,系统资源占用率
  • kill -9 pid 杀死进程
  • ps aux | grep name 查看某进程情况
  • nohup command & 后台运行某命令
  • scp [[user@]host1:]file1 [[user@]host2:]file2 ssh cp
  • su username 切换用户

有用但不常用命令

  • mount --bind test1 test2 挂载文件
  • tree 生成文件结构
  • history 查看用户历史命令
  • last 查看用户登录记录
  • lastb 查看用户登录失败记录
  • dmesg 查看系统诊断日志
  • dd if=/dev/zero of=/root/swapfile bs=1M count=1024 创建一个1024*1M大小的文件。位置为of,内容为if
  • mkswap /root/swapfile 创建交换空间
  • swapon /root/swapfile 开启交换空间

压缩

  • tar -cvf jpg.tar *.jpg 将目录里所有jpg文件打包成tar.jpg
  • tar -czf jpg.tar.gz *.jpg 将目录里所有jpg文件打包成jpg.tar后,并且将其用gzip压缩,生成一个gzip压缩过的包,命名为jpg.tar.gz
  • tar -cjf jpg.tar.bz2 *.jpg 将目录里所有jpg文件打包成jpg.tar后,并且将其用bzip2压缩,生成一个bzip2压缩过的包,命名为jpg.tar.bz2
  • tar -cZf jpg.tar.Z *.jpg 将目录里所有jpg文件打包成jpg.tar后,并且将其用compress压缩,生成一个umcompress压缩过的包,命名为jpg.tar.Z
  • rar a jpg.rar *.jpg rar格式的压缩,需要先下载rar for linux
  • zip jpg.zip *.jpg zip格式的压缩,需要先下载zip for linux

解压

  • tar -xvf file.tar 解压 tar包
  • tar -zxvf file.tar.gz 解压tar.gz
  • tar -jxvf file.tar.bz2 解压 tar.bz2
  • tar -Zxvf file.tar.Z 解压tar.Z
  • unrar e file.rar 解压rar
  • unzip file.zip 解压zip

微信开发踩过的坑

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来实现更好的更新