薛亮的主页

  • 首页

  • 标签

  • 归档

  • 搜索

Git技能树 (持续更新)

发表于 2016-01-15 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

本文所有命令均经过实战检验,请放心使用…

查看远程分支

1
$ git branch -a

显示:

1
2
3
4
5
6
* master
remotes/origin/2015-08-04
remotes/origin/HEAD -> origin/master
remotes/origin/master
remotes/origin/branchName1
remotes/origin/branchName2

删除远程分支

1
$ git push --delete origin branchName

显示:

1
2
To git@git.coding.net:user_name/project.git
- [deleted] branchName

清理本地在远程已不存在的分支

使用 git branch -a 命令可以查看所有本地分支和远程分支(git branch -r 可以只查看远程分支)
发现很多在远程仓库已经删除的分支在本地依然可以看到。
使用命令 git remote show origin,可以查看 remote 地址,远程分支,还有本地分支与之相对应关系等信息。
此时我们可以看到那些远程仓库已经不存在的分支,根据提示,使用 git remote prune origin 命令后显示:

1
2
3
4
Pruning origin
URL: https://xxx@xxx.xxx/xxx/xxx.git
* [pruned] origin/develop
* [pruned] origin/bug

这样就删除了那些远程仓库不存在的分支。

放弃本地修改,强制使用远程覆盖

1
2
$ git fetch --all
$ git reset --hard origin/master

还原项目到指定版本

使用 git status 命令,找到目标版本的commit id,如 37d5d5275591cb17928d155d12a8aecd993df718, 执行如下命令:

1
$ git reset --hard 37d5d5

--hard 选项会清除当前仓库所有修改,若当前仓库还有其他尚未提交的修改,建议先备份已修改内容,或直接备份整个项目,再执行上述命令。

将指定文件,还原到指定的历史版本

找到目标版本的commit id,例如将文件 abc.txt(不考虑该文件已被修改的情况) 还原到 37d5d5275591cb17928d155d12a8aecd993df718 版本,命令如下:

1
$ git reset 37d5d5 abc.txt

重复提示:Unlink of file ‘path/to/file’ failed. Show I try again? (y/n)

执行 git checkout origin/master -- path/to/file时报此异常:
输入y后依旧提示相同问题
此异常有可能是,需要更新的文件被某些进程占用,无法释放,导致文件不能更新。
找到占用此文件的进程,关闭后再更新即可。

导出指定分支的最新版本

1
git archive -o my_project.zip master

导出当前分支的指定版本

commit id 必须为全部,不能省略

1
git archive -o ../my_project.zip c163b0be54d9cd33af6bd7073b22185689e9c698

切换到指定版本

1
git checkout ${commit_id}

切回到最新版本

1
git checkout -

在历史记录中查找已删除的文件

如果不知道已删除文件的完整路径,可以使用下面的命令:

1
git log --all --full-history -- **/thefile.*

如果知道已删除文件的完整路径,可以使用下面的命令:

1
git log --all --full-history -- <path-to-file>

参考:Git: How to search for a deleted file in the project commit history? - Stack Overflow

导出干净的代码

有时需要将最新的项目代码打成干净的zip(或tar.gz)包,不包含版本控制信息,可以执行如下的命令:

1
git archive -o latest.zip HEAD

git archive 还有很多其他用法,可以查询相关资料了解一下。

比较不同分支上的文件的差异

1
git diff branch1 branch2 path-to-file

合并指定分支上指定的commit

比如要将分支 branch1 上的commit id 为 7ba30153b44d5fe49100ae3c7860632777d61ce8 的提交,合并到分支 branch2 上,只需要先切换到 branch2 上,然后执行如下命令即可

1
git cherry-pick 7ba30153b44d5fe49100ae3c7860632777d61ce8

Ant学习 之 [致命错误]

发表于 2016-01-07 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

第一次在CSDN写博客(作者注:本文最初发表在我的CSDN博客上)……

这几天因为要重构项目(之前的项目开发太烂了…),人手又不够,所以我们这两三个码畜是既当码畜又赶鸭子上架做着项目经理的活…为了更好的管理项目,被老板拉着学了些项目版本管理的东西,虽然本行是 “两耳不闻身外事,一心只撸咱的码”,但是学点儿版本控制的这些东西毕竟还是非常有用的。

在老板的建议下从Ant入手,再加上一些SVN的分支管理,应该够平时用的了吧…

Ant这个东西很早就在eclipse见到过,myeclipse更是少不了它:
图片加载中...

刚接触了Ant差不多一个星期,感觉是还是挺简单的。和写java一样,只要理清思路,稍微看一下文档,很容易上手。

废话不扯了,最近碰到一个问题,困了我两天了,不是解决不了,而是网上很少有相应的解决方案……刚刚把这个问题给Kill掉,鸡冻之余,分享一下!

【前提】:使用Ant打WAR包

【思路】:

1、初始化一些参数(包括项目根目录、src目录、WebContent目录、lib目录以及对应的build的根目录、lib目录、classes目录等等)

2、使用<mkdir />创建build需要的目录

3、使用<javac />编译java源文件,并保存到build的classes目录下

4、使用 <copy />复制项目下的lib目录到build中与之对应WEB-INF目录下

5、使用<war />打包

【问题】:在进行第3步编译src下的java文件时总是提示: [javac] 致命错误: 在类路径或引导类路径中找不到程序包 java.lang

有图有真相:
图片加载中...

部分源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<target name="compile" depends="mkdirs,copy.lib" description="javac"><!-- 编译源代码  同时指定依赖 -->
<javac srcdir="${pty.dir.proj.src}" destdir="${pty.dir.proj.build.classes}" encoding="utf-8">
<bootclasspath>
<fileset dir="${pty.dir.lib.tomcat}">
<patternset refid="pat.find.jar"></patternset>
</fileset>
<fileset dir="${pty.dir.proj.web.lib}">
<patternset refid="pat.find.jar"></patternset>
</fileset>
</bootclasspath>
</javac><!-- 执行编译 指定源代码目录及编译文件输出目录-->
</target>

【解决过程】:因为是周末,一直宅在家里,百度 + Google苦搜无果,换着关键词搜来搜去只有第一页的十来个,也不是我想要的。大哭

中间费了九牛二虎之力偶尔一次可以编译成功了,我还忘了备份一下了。直接在源代码上又接着改了什么,结果又坏掉了………..大哭

一种说法: 找不到 java_home设置不正确;

第二种说法:classpath环设置不正确;

第三种说法:重装JDK…

第四种说法:classpath应该加上dt.jar、rt.jar、tools.jar

第五种说法:环境变量去掉空格(估计有很多人和我一样,JDK是装在X:\Program Files\下的)

第六种说法:忘了…….

总之很多…

在苦苦挣扎中,我还是不放弃…每次只改一个可疑的地方,每改一次都要运行一下,看看会不会对了……

功夫不负有心人,当我把<bootclasspath />换成<classpath /> 时,奇迹再次出现了…

我之前没仔细研究这两者之间的差别,才多走了很多冤枉路。

亲,不知道你是不是和我犯的一样的错误…如果是现在大概明白了吧…如果还不明白就去查下<bootclasspath />和 <classpath />的区别吧…

附:
网上有一段话解释到:【bootclasspath参数(启动类库):它已经包含了jre/lib目录下的rt.jar,以及我们自定义的类库。如果使用classpath参数,仅指我们定义的库。】,所以反推之,如果我们已经在环境变量里指定了rt.jar等,这里只需要指定引入的第三方jar(如servlet-api.jar、log4j.jar、spring.jar、axis.jar等),则在<javac /> 使用classpath指定这些jar即可。如果误用了bootclasspath去指定这些第三方的jar,而没有指定rt.jar等这些很可能会报和我一样的【致命错误】…(我的理解是bootclasspath覆盖了环境变量里的设置)。如果一定要用bootclasspath,则应该指定rt.jar这些JDK自带的jar(不过我没试过…)

碎觉!

ckeditor 笔记(持续更新)

发表于 2015-07-12 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

说明

本笔记基于ckeditor 4.4.7

安装

引入ckeditor/ckeditor.js,给目标textarea添加 name 和 class="ckeditor" ,即可将一个textarea修饰为ckeditor。

扩展允许的内容

示例(扩展允许abcd标签,同时允许abcd附加任何属性内容,任何style内容,任何类内容):

1
CKEDITOR.config.extraAllowedContent = 'abcd[*]{*}(*)';

参考官方API:http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-extraAllowedContent

支持HTML5标签

1
2
3
4
5
6
7
8
CKEDITOR.config.allowedContent = {
'$1': {
elements: CKEDITOR.dtd,
attributes: true,
styles: true,
classes: true
}
};

参考官方API:http://docs.ckeditor.com/#!/guide/dev_disallowed_content-section-how-to-allow-everything-except…

自定义行高插件

本插件源码来自互联网,由于源代码基于ckeditor3.6.6.1,故对源码进行过简单修改,以求适应ckeditor 4.4.7

插件目录如下:

1
2
3
4
5
6
lineheight
└─┬ plugin.js
│
└─ lang
├ en.js
└ zh-cn.js

引入方式1(通过api对ckeditor进行扩展,推荐)

将lineheight文件夹放在项目里,如:project/static/lineheight

在业务层js(如project/static/my.src.js)里添加以下两行:

1
2
CKEDITOR.plugins.addExternal('lineheight', 'static/lineheight/'); // 添加行高插件
CKEDITOR.config.extraPlugins = (CKEDITOR.config.extraPlugins && CKEDITOR.config.extraPlugins.length) ? (CKEDITOR.config.extraPlugins + ',lineheight') : 'lineheight';

引入方式2(直接修改ckeditor源码,不推荐)

将lineheight文件夹放在ckeditor/plugins/目录下,
然后修改ckeditor/config.js文件:

1
config.extraPlugins = (config.extraPlugins && config.extraPlugins.length ? config.extraPlugins + ',lineheight' : 'lineheight');

ckeditor引入自定义插件方式,参考资料:Can I control where CKEditor finds plugins to load?

下载地址:ckeditor 行高插件

为“图片属性”对话框添加上传图片按钮

示例代码如下

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
 CKEDITOR.on('dialogDefinition', function (e) {
var dialogName = e.data.name;
var dialog = e.data.definition.dialog;
var dialogDefinition = e.data.definition;
// Check if the definition is from the dialog window you are interested in (the "Link" dialog window).
if (dialogName == 'image') {//判断打开的对话框是否为“图片属性对话框”
dialog.on('show', function (e) {
// Get a reference to the "Link Info" tab.
var $upload = $(e.sender.getElement().$).find('.cke_dialog_ui_hbox_last:first');
$uploadCKEditor = $upload;
if ($upload.find('iframe').length == 0){
$upload.empty().append('<iframe style="height:39px;width:110px; margin-top:11px;" src="/upload.htm?callback=uploadfinishedFromCKEditor&width=110&height=39&margin=5" frameborder="0" scrolling="no"></iframe>');
}
$(e.sender.getElement().$).find(':input.cke_dialog_ui_input_text:eq(2)').val('100%');
});
dialog.on('hide', function (e) {
//alert('dialog ' + dialogName + ' closed.');
});
}
});

//定义回调函数,可以根据自己的业务修改
window.uploadfinishedFromCKEditor = function(url){
var $input = $uploadCKEditor.siblings('.cke_dialog_ui_hbox_first:first').find(':input');
$input.val(url);
}

效果如下:
图片加载中

将光标定位(移到)最后位置

首先,要有光标…

1
2
3
4
var editor = CKEDITOR.instances.editor1;
var range = editor.createRange();
range.moveToElementEditablePosition(editor.editable(), true);
editor.getSelection().selectRanges([range]);

参考官方API:http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-createRange
http://docs.ckeditor.com/#!/api/CKEDITOR.dom.range-method-moveToElementEditablePosition
http://docs.ckeditor.com/#!/api/CKEDITOR.editor-method-getSelection
http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection-method-selectRanges

完整toolbar配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (window.CKEDITOR) {
config.toolbar = [
{ name: 'document', items: [ 'Source', '-', 'Save', 'NewPage', 'Preview', 'Print', '-', 'Templates' ] },
{ name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
{ name: 'editing', items: [ 'Find', 'Replace', '-', 'SelectAll', '-', 'Scayt' ] },
{ name: 'forms', items: [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
'/',
{ name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] },
{ name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl', 'Language' ] },
{ name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
{ name: 'insert', items: [ 'Image', 'Flash', 'Table', 'HorizontalRule', 'Smiley', 'SpecialChar', 'PageBreak', 'Iframe' ] },
'/',
{ name: 'styles', items: [ 'Styles', 'Format', 'Font', 'FontSize' ] },
{ name: 'colors', items: [ 'TextColor', 'BGColor' ] },
{ name: 'tools', items: [ 'Maximize', 'ShowBlocks' ] },
{ name: 'about', items: [ 'About' ] }
];
}

以编程方式选中指定的选项卡

当相应的事件触发后,比如用户点击了某个按钮,以编程的方式,选中对话框中,指定的选项卡,可以使用 dialog 对象的 selectPage(tabId) 方法。
例如当前选中的选项卡 ID 为 tab_3,现在需要切换到 ID 为 tab_0 的选项卡,代码如下:

1
2
var dialog = CKEDITOR.dialog.getCurrent(); // 获取当前打开的对话框对象
dialog.selectPage('tab_0'); // 切换到 ID 为`tab_0` 的选项卡

MySQL 工作笔记(持续更新)

发表于 2015-06-30 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

字符拼接

1
SHOW CREATE TABLE sys_area;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE TABLE `sys_area` (
`id` varchar(50) NOT NULL,
`name` varchar(255) NOT NULL,
`parentId` int(11) DEFAULT NULL,
`code` varchar(1000) DEFAULT NULL COMMENT ''用来快速查询'',
`priority` int(11) DEFAULT NULL,
`fee` float(10,2) DEFAULT ''0.00'',
`valid` int(11) DEFAULT ''1'',
`weight` int(11) DEFAULT ''10'' COMMENT ''排序的权值'',
`level` int(11) DEFAULT NULL,
`available` int(11) DEFAULT ''0'' COMMENT ''0: 不可用, 1: 可用'',
`status` tinyint(4) DEFAULT ''0'',
`createUser` varchar(50) DEFAULT NULL,
`createDate` timestamp NULL DEFAULT NULL,
`updateUser` varchar(50) DEFAULT NULL,
`updateDate` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_code` (`code`(180)) USING BTREE,
KEY `idx_level` (`level`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT
1
UPDATE sys_area SET code = code + ',' WHERE code NOT REGEXP'%,';

Response:

Error Code: 1062. Duplicate entry ‘0’ for key ‘idx_code’

正确语句:

1
UPDATE sys_area SET code = CONCAT(code, ',') WHERE code NOT REGEXP'%,';

使用mysql workbench 6.2.5.0导出5.6.16-log到5.6.23失败

Response:
10:06:31 Restoring game_dev (admin_config)
Running: mysql.exe –defaults-file=”c:\users\liang\appdata\local\temp\tmpkjgddd.cnf” –protocol=tcp –host=127.0.0.1 –user=root –port=3306 –default-character-set=utf8 –comments < “F:\database\dumps\Dump20150518\game_dev_admin_config.sql”
ERROR 1839 (HY000) at line 24: @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_MODE = ON.

Operation failed with exitcode 1

解决方案:

MySQL实现Oracle中的 rownum 功能

示例代码如下:

1
SELECT @curRow := @curRow + 1 as rownum, tn.* FROM database_name.table_name tn INNER JOIN (SELECT @curRow := 0) temp;

删除数据库语句

创建临时数据库时,没注意规范,命名成了a.b的格式,结果在删除该数据库时,提示SQL语句出错,因为 . 在程序中一般用作隶属或者分隔命名空间的作用,同属于关键字,故数据库命名尽量不要含英文句点(.),可以使用下划线代替,估计很少人遇到这个问题。不过,即便遇到这个问题,也有办法解决,很简单,将数据库名称用反引号(`)引起来就行了。
最后执行的代码如下:

1
DROP DATABASE `database_name`;

存储过程示例1

该存储过程主要用于添加测试数据。
展示如何创建及调用存储过程,以及在存储过程中使用游标。

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
DROP PROCEDURE IF EXISTS init_match_menu_data;
delimiter //
CREATE PROCEDURE init_match_menu_data ()
BEGIN
# 声明变量
DECLARE match_id VARCHAR(50);
DECLARE match_icon VARCHAR(50);
DECLARE match_name VARCHAR(50);
DECLARE i INT DEFAULT 0;
DECLARE done TINYINT(1) DEFAULT 0;
# 声明游标
DECLARE matchList CURSOR FOR SELECT id, icon, name FROM `match` WHERE status >= 0 LIMIT 1000;
# 将结束标志绑定到游标
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
# 打开游标
OPEN matchList;
READ_LOOP: LOOP
# 提取游标里的数据
FETCH matchList INTO match_id, match_icon, match_name;
# 声明结束的时候
IF done THEN
LEAVE READ_LOOP;
END IF;
# 做爱做的事
SET i = i + 1;
INSERT INTO bm_match_menu(id, matchId, title, icon, content, content1, seq, status)
VALUES (i, match_id, '赛事规程', match_icon, CONCAT(match_name, '0'), CONCAT(match_name, '0'), 0, 0);

SET i = i + 1;
INSERT INTO bm_match_menu(id, matchId, title, icon, content, content1, seq, status)
VALUES (i, match_id, '赛事规程', match_icon, CONCAT(match_name, '1'), CONCAT(match_name, '1'), 0, 1);

# select match_id, match_icon, match_name, i; # 不能使用select @match_id
END LOOP; # 循环结束
CLOSE matchList; # 关闭游标
END //
delimiter ;

调用存储过程

1
CALL init_match_menu_data();

存储过程示例2

该存储过程是为了查找指定数据库指定表中,字符集不正确的列,并将其更新为指定字符集编码。
主要展示如何在存储过程中预编译并执行动态 SQL。

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
DROP PROCEDURE IF EXISTS update_column_collation_name;
DELIMITER $$
CREATE PROCEDURE `update_column_collation_name`(in in_schema_name varchar(50), in in_tbl_name varchar(50), in in_default_character_set varchar(50), in in_default_collation varchar(50))
BEGIN
DECLARE tbl_name VARCHAR(50) DEFAULT NULL;
DECLARE col_name VARCHAR(50) DEFAULT NULL;
DECLARE col_type VARCHAR(50) DEFAULT NULL;
DECLARE col_comment VARCHAR(50) DEFAULT NULL;
DECLARE is_nullable TINYINT DEFAULT 0;
DECLARE sql_string VARCHAR(1000) DEFAULT NULL;
DECLARE done TINYINT DEFAULT 0;
DECLARE column_list CURSOR FOR SELECT table_name, column_name, column_type, column_comment, IF(IS_NULLABLE = 'NO', 0, 1) is_nullable FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = in_schema_name and TABLE_NAME = in_tbl_name and ((CHARACTER_SET_NAME is not null and CHARACTER_SET_NAME <> in_default_character_set) or (COLLATION_NAME is not null AND COLLATION_NAME <> in_default_collation));
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN column_list;
SET foreign_key_checks = 0; # 因为修改字符集时,可能影响到外键约束,故先关闭外键检查
READ_LOOP:
LOOP
FETCH column_list INTO tbl_name, col_name, col_type, col_comment, is_nullable;
IF done THEN
LEAVE READ_LOOP;
END IF;
SET sql_string = CONCAT('alter table `', in_schema_name, '`.`', tbl_name, '` change `', col_name, '` `', col_name, '` ', col_type, ' character set ', in_default_character_set,' collate ', in_default_collation, ' ', IF(is_nullable = 0, 'NOT NULL', 'NULL'), ' comment \'', col_comment, '\'');
select sql_string; # 生成的动态sql
SET @sql_string = sql_string; # PREPARE语句不能使用局部变量,所以在此处声明一个用户变量
PREPARE stmt FROM @sql_string; # 预编译该SQL字符串
EXECUTE stmt; # 执行
END LOOP;
SET foreign_key_checks = 1; # 恢复外键检查
END$$
DELIMITER ;

调用存储过程:

1
call update_column_collation_name('schema_name', 'table_name', 'utf8mb4', 'utf8mb4_general_ci');

存储过程示例3

该存储过程用于初始化表中的测试数据到指定数量。
主要展示了,如何给存储过程传递参数,以及 WHILE 语句的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
DROP PROCEDURE IF EXISTS init_test_data_screen_log;
delimiter //
CREATE PROCEDURE init_test_data_screen_log (IN max INT)
BEGIN
DECLARE i INT DEFAULT 20;
SELECT MAX(id + 0) INTO i FROM screen_log;
WHILE i < max DO
SET i = i + 1;
INSERT INTO screen_log(id, deviceId, type, code, message, status, createDate)
VALUES (i, 1, '设备', 'DEVICE_INIT', CONCAT(i, ' - 设备请求初始化:Agent-232,ip:192.168.3.20'), 0, NOW());
END WHILE;
END //
delimiter ;

调用存储过程:

1
CALL init_test_data_screen_log(1000); # 初始化表中的测试数据到1000条

存储过程示例4

演示如何在存储过程中,预编译带参数的动态 SQL,以及向存储过程中传递参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DROP PROCEDURE IF EXISTS tmp_init_data; # 删除已经存在的存储过程
delimiter //
CREATE PROCEDURE tmp_init_data(IN prefix VARCHAR(10), IN init INT, IN max INT) # 参数前面加 `IN` 表示,这个参数是入参,即由外界传递给此存储过程的,若为 `OUT` 这表示该参数将会返回给外界,此外还有 `INOUT`
BEGIN
DECLARE _name VARCHAR(10) DEFAULT 'name';
DECLARE i INT DEFAULT init;
DECLARE sql_stmt VARCHAR(1000);
SET @sql_insert = 'insert into test_repl.test_repl_table0(`name`) values(?);';

WHILE i < max DO
SET @name_ = CONCAT(prefix, i); # 声明一个用户变量,以便传递给预编译的 SQL
PREPARE stmt FROM @sql_insert;
EXECUTE stmt USING @name_; # `using` 后跟用户变量(`user variables`)
SET i = i + 1;
END WHILE;
END //
delimiter ;

时间格式化

工作中经常需要将时间格式化后输出,可以使用DATE_FORMAT这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> SELECT DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y');
-> 'Sunday October 2009'
mysql> SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s');
-> '22:23:00'
mysql> SELECT DATE_FORMAT('1900-10-04 22:23:00',
-> '%D %y %a %d %m %b %j');
-> '4th 00 Thu 04 10 Oct 277'
mysql> SELECT DATE_FORMAT('1997-10-04 22:23:00',
-> '%H %k %I %r %T %S %w');
-> '22 22 10 10:23:00 PM 22:23:00 00 6'
mysql> SELECT DATE_FORMAT('1999-01-01', '%X %V');
-> '1998 52'
mysql> SELECT DATE_FORMAT('2006-06-00', '%d');
-> '00'

详细介绍可参考MySQL官网文档:12.7 Date and Time Functions

DELETE语法

单表语法:

1
2
3
4
5
DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name
[PARTITION (partition_name,...)]
[WHERE where_condition]
[ORDER BY ...]
[LIMIT row_count]

多表语法:

1
2
3
4
DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
tbl_name[.*] [, tbl_name[.*]] ...
FROM table_references
[WHERE where_condition]

或者

1
2
3
4
DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
FROM tbl_name[.*] [, tbl_name[.*]] ...
USING table_references
[WHERE where_condition]

Warning: Using a password on the command line interface can be insecure.

在命令行上使用下面的方式登录MySQL时,会报此警告信息:

1
shell> mysql -u francis -pfrank db_name

警告内容翻译过来大概是:在命令行界面上使用密码可以是不安全的。
官方给出的解释:

This is convenient but insecure. On some systems, your password becomes visible to system status programs such as ps that may be invoked by other users to display command lines. MySQL clients typically overwrite the command-line password argument with zeros during their initialization sequence. However, there is still a brief interval during which the value is visible. Also, on some systems this overwriting strategy is ineffective and the password remains visible to ps. (SystemV Unix systems and perhaps others are subject to this problem.)

If your operating environment is set up to display your current command in the title bar of your terminal window, the password remains visible as long as the command is running, even if the command has scrolled out of view in the window content area.

简单来说,MySQL不推荐在命令行上直接使用密码。因为直接显示在命令行上的密码,可能被系统内的其他应用程序捕捉到,比如查看进程的ps命令,以及history命令等,都有可能造成密码泄露。
MySQL官方提供的解决方案有3种:
在命令行上使用 不跟密码值的-p 或者 --password选项 ,例如:

1
2
shell> mysql -u francis -p db_name
Enter password: ********

将密码保存在配置文件里。举个例子,在Unix系统上,你可以在你的家目录的.my.cnf文件的[client]部分,罗列你的密码 ,例如:

1
2
[client]
password=your_pass

为了保证密码的安全,该文件不应该除你之外的任何人或应用程序获取到。为了达到这一点,设置该文件访问权限为400或者600,例如:

1
shell> chmod 600 .my.cnf

把你的密码保存在名为MYSQL_PWD的环境变量中。 详细请移步至Section 2.12, “Environment Variables”.
官方参考文档:MySQL :: MySQL 5.6 Reference Manual :: 6.1.2.1 End-User Guidelines for Password Security:

Warning: World-writable config file ‘/usr/local/mysql/my.cnf’ is ignored

大概意思是:全局可写的/usr/local/mysql/my.cnf配置文件被忽略。
MySQL担心这种文件被其他用户或应用程序恶意修改,所以忽略掉这个配置文件。因此,修改该文件的权限为,该用户可读,其他用户不可写即可,例如:

1
shell> chmod 644 /usr/local/mysql/my.cnf

主从架构模式下,复制失败(中断)

2016年08月12日记:
在配置主从数据库时,给从库配置了主库的 log-bin 的文件名及位置后,并没有立即开启复制,而是在主库上做了其他操作,主要是修改了一个账户的密码和权限,这个账户是给从库登录主库使用的。
处理完成后,开启从库的复制功能,发现并没有执行复制,执行 show slave status,发现 Last_SQL_Error 列有内容,是一个修改密码和权限的的操作,具体内容没有记下来。初步判断是从库在同步主库的修改密码或者权限时,没有找到用户的异常,因为我修改的那个账户,不在从库上,而是在主库上。
翻阅 MySQL 官网文档 发现,Last_SQL_Errno 和 Last_SQL_Error 代表的是最近一次导致 SQL 线程停止的错误编号和对应的错误信息,同一页的上方,可以看到有写到:

Note
When the slave SQL thread receives an error, it reports the error first, then stops the SQL thread. This means that there is a small window of time during which SHOW SLAVE STATUS shows a nonzero value for Last_SQL_Errno even though Slave_SQL_Running still displays Yes.

也就是说当从库的 SQL 线程接收到错误信息时,它会首先报告这个错误,然后停止 SQL 线程。这也就意味着,在报告时并没有停止 SQL 线程,它们之间存在一个很小的窗口期,再具体一点就是,在你执行 SHOW SLAVE STATUS 时,看到的 Last_SQL_Errno 列的值不是 0,但 Slave_SQL_Running 列显示的依旧是 Yes。
到目前基本找到原因,是这个错误导致!原来是这个错误导致了从库停止了同步操作。一把 Google 后得知,可以执行 set global sql_slave_skip_counter = N; (N是一个整数)语句,跳过从主库同步过来的 N 条语句,保险起见,我执行了 set global sql_slave_skip_counter = 1; ,完美解决!
后来发现官网说这个全局变量有 Bug。还好没被我碰上,以后碰上再说…relieved
说了这一堆,最终的解决方案是在发生问题的从库上执行以下命令:

1
2
3
stop slave;
set global sql_slave_skip_counter = 1;
start slave;

之后,使用 show slave status 查看问题是否已解决,若还有其他类似问题,可以多执行几次上面的命令。

Windows下非安装版MySQL的启动与停止

切换到 MySQL bin 目录下

1
cd /d d:\ProgramFiles\mysql-5.6.29-winx64\bin

  • 启动
    启动端口为 3306 的实例

    1
    mysqld
  • 停止
    停止端口为 3306 的实例:

    1
    mysqladmin -u root shutdown

停止端口为 3307 的实例:

1
mysqladmin -u root -P 3307 shutdown

主从复制时,只同步指定的数据库

若只想复制指定的一个或者多个库,可以在命令行或者配置文件中使用 --replicate-do-db 参数,该配置项是从库的配置选项。如果要指定多个数据库,可以使用多次该参数,比如,可以在从库的 my.cnf 中配置:

1
2
3
4
5
[mysqld]
# 忽略其他配置
replicate-do-db=db_name0
replicate-do-db=db_name1
replicate-do-db=db_name2

与之类似的还有 --replicate-ignore-db ,不过它与 --replicate-do-db 相反,--replicate-do-db 是用来指定哪些数据库需要复制,而 --replicate-ignore-db 用来指定哪些数据库不需要复制,两者可以配合使用。
参考官方链接:MySQL :: MySQL 5.6 Reference Manual :: 17.1.4.3 Replication Slave Options and Variables

终止指定的 SQL 线程

今天用存储过程向数据库添加一些测试数据,结果在 while 语句块:

1
2
3
4
5
6
SET @sql_insert = 'insert into test_repl.test_repl_table0(`name`) values(?);';
WHILE i < max DO
SET @name_ = CONCAT(prefix, i);
PREPARE stmt FROM @sql_insert;
EXECUTE stmt USING @name_;
END WHILE;

忘记进行 SET i = i + 1; 了… 结果可想而知,表中的数据失控性的增长,使用 SHOW PROCESSLIST 查看正在运行的线程,结果如下图:
图片加载中...
可以看出 Id 列为 7 的列,就是正在无限执行的语句。

按照以往的做法,也是终极大法:重启 MySQL 服务…毕竟重启能解决 99% 的问题…不过一直感觉这个方法有点水,想换个专业点的…
Google了一把,很快找到了解决方案,使用 MySQL KILL 这个命令,杀死指定的线程。简单说下 KILL 这个命令。
每个与 mysqld 建立的连接,都运行在一个独立的线程里运行,可以使用 KILL 终止指定的线程。
KILL 不仅可以终止线程,还可以终止正在执行的语句。在终止之前,可以使用 SHOW PROCESSLIST 查看已建立连接或正在执行语句的线程 ID ,然后执行 KILL YOUR_PROCESS_ID 即可终止指定线程。
因此可以执行以下语句终止执行 SQL 语句:

1
KILL 7;

官方参考文档:MySQL :: MySQL 5.6 Reference Manual :: 13.7.6.4 KILL Syntax

MySQL Cursor Fetch无法取出值的问题

CentOS 7,MySQL 5.6

问题代码如下,article_id 为局部变量,ARTICLE.ARTICLE_ID 为主键列,在循环中 Fetch 游标中的值时,无法获取数据,SELECT article_id; 结果为空,独立执行定义CURSOR 的SQL语句是有结果的。一说是由 ARTICLE.ARTICLE_ID 为主键的缘故,非主键列未发现此问题。其实不然,经测试,非主键列同样有此问题。
问题的根本原因是 MySQL对 article_id 和 ARTICLE_ID 未区分大小写。十年前就被提出来了…详见 MySQL Bugs: #5967: Stored procedure declared variable used instead of column

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
DROP PROCEDURE IF EXISTS test;
delimiter $$
CREATE PROCEDURE test()
BEGIN
DECLARE article_id VARCHAR(50) DEFAULT NULL;
DECLARE done TINYINT(1) DEFAULT 0;
DECLARE article_list CURSOR FOR SELECT ARTICLE_ID FROM ARTICLE LIMIT 2;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN article_list;
READ_LOOP:
LOOP
FETCH article_list INTO _article_id;
IF done THEN
LEAVE READ_LOOP;
END IF;
SELECT article_id;
END LOOP;
CLOSE article_list;
END$$
delimiter ;

创建支持Emoji表情的数据库

随手记

1
CREATE DATABASE IF NOT EXISTS `mydb` /*!40100 DEFAULT CHARACTER SET utf8mb4 DEFAULT  COLLATE = 'utf8mb4_unicode_ci' */;

jQuery 学习笔记

发表于 2015-06-30 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

Ajax

在不同的callback函数间传递变量

jQuery中的ajax()函数的返回值可以链式调用done()、fail()等函数,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
$.ajax("/exmple.do")
.done(function(data){
//①
alert("done");
})
.done(function(){
//②
alert("done");
})
.fail(function(){
//③
alert("fail");
});

如果想在②处获取①处的数据,只需在①处将数据绑定在this关键字上,即可在②处用过this.exmple获取。

最终实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
$.ajax("/exmple.do")
.done(function(data){
this.name = "liangzai_cool"; //①
alert("done");
})
.done(function(){
alert(this.name); //② 此处会弹出对话框,内容即为:liangzai_cool
alert("done");
})
.fail(function(){
//③
alert("fail");
});

jQuery.fancybox 学习笔记

发表于 2015-06-30 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

设置堆叠顺序(z-index)

用bootstrap+jquery开发前端,需要给弹出的模态框中的图片,增加单击图片,可以对图片进行放大预览的功能,如果仅仅绑定了fancybox()函数,如:

1
$("a.fancybox").fancybox();

而没有配置z-index的值,可能出现弹出层在模态框的后面的情况,如:
点击模态框中的图片,预览图出现在了模态框的后面

解决方案:
配置fancybox的覆盖层(overlay)的堆叠顺序(z-index),大约为12500

最终实现代码如下:

1
2
3
4
5
6
7
$("a.fancybox").fancybox({
helpers: {
overlay : {//z-index 仅能在定位元素(position=absolute||fixed)上奏效 ,此时 z-index值可以设置在12500左右
css : {position: "fixed", "z-index": 12500}
}
}
});

效果:

这里写图片描述

语言本地化

fancybox没有提供完整的国际化语言包,但提供了模版自定义方案,我们可以通过修改模版实现语言本地化,即非完整的国际化。

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
$(".fancybox").fancybox({
// HTML templates
tpl: {
wrap : '<div class="fancybox-wrap" tabIndex="-1"><div class="fancybox-skin"><div class="fancybox-outer"><div class="fancybox-inner"></div></div></div></div>',
image : '<img class="fancybox-image" src="{href}" alt="" />',
iframe : '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" frameborder="0" vspace="0" hspace="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen' + ($.browser.msie ? ' allowtransparency="true"' : '') + '></iframe>',
error : '<p class="fancybox-error">请求的内容未找到。<br/>请稍后再试。</p>',
closeBtn: '<a title="关闭" class="fancybox-item fancybox-close" href="javascript:;"></a>',
next : '<a title="下一个" class="fancybox-nav fancybox-next" href="javascript:;"><span></span></a>',
prev : '<a title="上一个" class="fancybox-nav fancybox-prev" href="javascript:;"><span></span></a>'
}
});

效果如下:
这里写图片描述

javascript 工作笔记

发表于 2015-06-30 | 更新于 2018-12-01 | | 阅读次数: | 阅读人数:

prototype

一个类型如何从另一个类型继承

实例代码:

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
function Class1() {
this.field1 = 1;
this.method1 = function() {
alert(1);
}
}

function Class2() {
this.field2 = 2;
this.method2 = function() {
alert(2);
}
}

Class2.prototype = new Class1(); //关键代码

//测试
var myObject1 = new Class1();
alert(myObject1.field1); //1
if (myObject1.method1) { //true
myObject1.method1();
} else {
alert("myObject1.mehtod1 is undefined");
}

alert(myObject1.field2); //undefined
if (myObject1.method2) {
myObject1.method2();
} else {
alert("myObject1.mehtod2 is undefined");
}

var myObject2 = new Class2();
alert(myObject2.field1); //2
if (myObject2.method1) { //true
myObject2.method1();
} else {
alert("myObject2.mehtod1 is undefined");
}

alert(myObject2.field2); //2
if (myObject2.method2) { //true
myObject2.method2();
} else {
alert("myObject2.mehtod2 is undefined");
}

附:

1
2
3
4
5
6
7
var windowHeight = $(window).height();    //浏览器时下窗口可视区域高度;
var scrollTop = $(document).scrollTop(); //获取滚动条到顶部的垂直高度;
var documentHeight = $(document.body).height(); //浏览器时下窗口文档body的高度;

var windowHeight = $(window).height(); //浏览器时下窗口可视区域高度;
var scrollTop = $(document).scrollTop(); //获取滚动条到顶部的垂直高度;
var documentHeight = $(document.body).height(); //浏览器时下窗口文档body的高度;

URL传递中文之JavaScript encodeURIComponent

使用URL传递中文等特殊符号时,可以使用encodeURIComponent进行encode一下,服务器端接收后,进行dencode,可以避免乱码

通过原生JS获取iframe内容物 示例:

1
console.info(document.getElementById('iframeId').context.contentWindow.document);

将滚动条内的指定元素显示在可视区域

1
2
3
4
var index = parseInt(value) - 1;  // 指定元素的索引
var $container = document.querySelector('ul.list-group'); // 容器
var $item = document.querySelectorAll('ul.list-group li').item(index); // 指定元素
$container.scrollTop = $item.offsetTop - $container.offsetTop;
1…34

薛亮

37 日志
45 标签
© 2015 – 2018 薛亮
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Muse v6.5.0