转载请注明出处WangYuheng’s Blog

起因

需要实现一个日历功能,网上找了几个示例,都是根据各种库和插件,居然没有纯净的js完成的日历插件,不免有些诧异,正好有时间,准备通过js编写一个简单的日历demo基础,可自由根据使用的插件进行显示层的定制。

效果图

javascript-calendar-01
javascript-calendar-02

前提

算法

说一下日历的算法

  1. (本月第一天的星期数+本月的天数)/7 可以知道本月需要占据几行。
  2. 将日历看为二维数组,第一级遍历条件为本月行数,第二级为0~7(直观形象为一个日历table,自左向右,自上而下循环)。
  3. n为0~7循环参数, 计算当前日历方格显示数值为当前日期: 行数*7 + n - 本月第一天 + 1。
  4. 二月的天数根据闰年会有不同,闰年的计算方式: 当前年份能被400整除,或者当前的年份能被4整除且不能被100整除。

Date

日历功能实现必须通过js的Date对象提供的基础数值。介绍一下用到的方法功能

new Date().getFullYear(); //当前年份 2015
new Date().getMonth(); //当前月份 0~11 
new Date().getDate(); //当前月中的某一天 1~31
new Date().getDay(); //一周中的某一天 0~6
new Date(year, month, day); //根据构造函数返回Date对象

实现

这里写出来编写代码思路分析的过程,可以跳过直接看下方的完整代码。
先抽象出二维数组模型,7列已经确定,需要先根据算法1算出本月的日历行数,但是二月的天数不一致,所以先要根据是否为闰年算出二月的天数。

var calendar = {
    isLeap: function(vYear) {
        return vYear % 400 == 0 || (vYear % 4 == 0 && vYear % 100 != 0);
    },
    getFebruaryDays: function(vYear) {
        return this.isLeap(vYear) ? 29 : 28;
    }
}

本月第一天的星期数可以根据new Date(currentYear, currentMonth, 1).getDay()得出。
monthFirstDay为本月第一天的星期数,monthDays为12月的天数组成的数组。根据构造函数计算赋值,并返回this对象方便链式调用。

var calendar = {
    year: null,
    month: null,
    date: null,
    day: null,
    monthDays: null,
    monthFirstDay: null,
    ctor: function(vNow) {
        var now = vNow || new Date(); //如果为指定date对象,则为当前日期对象
        this.year = now.getFullYear();
        this.month = now.getMonth();
        this.date = now.getDate();
        this.day = now.getDay();
        this.monthFirstDay = new Date(this.year, this.month, 1).getDay();
        this.monthDays = [
            31, this.getFebruaryDays(this.year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
        ];
        return this;
    },
    isLeap: function(vYear) {
        return vYear % 400 == 0 || (vYear % 4 == 0 && vYear % 100 != 0);
    },
    getFebruaryDays: function(vYear) {
        return this.isLeap(vYear) ? 29 : 28;
    }
}

计算日历行数

getMonthDay: function() {
    return this.monthDays[this.month];
},
getCalTr: function() {
    return (this.monthFirstDay + this.getMonthDay()) / 7;
},

循环二维数组得到日历, *console.log(n);*可结合使用的框架进行输出展示。

cal: function() {
    var that = this;
    for (var i = 0; i < this.getCalTr(); i++) {
        for (var j = 0; j < 7; j++) {
            var n = (i * 7 + j) - that.monthFirstDay + 1;
            
            if (n > 0 && n <= that.getMonthDay()) {
                console.log(n);
            }
        }
    }
}

完整代码,在控制台查看输出的日历

var calendar = {
    year: null,
    month: null,
    date: null,
    day: null,
    monthDays: null,
    monthFirstDay: null,
    days: [
        "星期一",
        "星期二",
        "星期三",
        "星期四",
        "星期五",
        "星期六",
        "星期日"
    ],

    ctor: function(vNow) {
        var now = vNow || new Date(); //如果为指定date对象,则为当前日期对象
        this.year = now.getFullYear();
        this.month = now.getMonth();
        this.date = now.getDate();
        this.day = now.getDay();
        this.monthFirstDay = new Date(this.year, this.month, 1).getDay();
        this.monthDays = [
            31, this.getFebruaryDays(this.year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
        ];
        return this;
    },
    isLeap: function(vYear) {
        return vYear % 400 == 0 || (vYear % 4 == 0 && vYear % 100 != 0);
    },
    getFebruaryDays: function(vYear) {
        return this.isLeap(vYear) ? 29 : 28;
    },
    format: function() {
        return (this.month + 1) + "/" + this.date + "/" + this.year + "  " + this.dayMap[this.day];
    },
    getDay: function() {
        return this.days[this.day - 1]
    },
    getMonthDay: function() {
        return this.monthDays[this.month];
    },
    getCalTr: function() {
        return (this.monthFirstDay + this.getMonthDay()) / 7;
    },
    cal: function() {
        var that = this;
        console.log("%c   日   一   二   三   四   五   六   ",'background: #222; color: #bada55');
        console.log("-----------------------------------");
        for (var i = 0; i < this.getCalTr(); i++) {
            var temp = "";
            for (var j = 0; j < 7; j++) {
                var tempData = (i * 7 + j) - that.monthFirstDay + 1
                if (tempData < 1) {
                    temp = temp + "prev ";
                } else if (tempData > that.getMonthDay()) {
                    temp = temp + " next";
                } else {
                    if (tempData > 9) {
                        temp = temp + "  " + tempData + " ";
                    } else {
                        temp = temp + "   " + tempData + " ";
                    }
                }
            }
            console.log(temp);
            console.log("-----------------------------------");
        }
    }
}
calendar.ctor().cal();
calendar.ctor(new Date(2015, 10)).cal();

总结

不足

本例中的ctor并非构造函数,而是针对一个对象进行变更,重新执行ctor后会替换calendar的属性值。
未对之前和之后的日期进行获取,如需相关功能,可在ctor中设置prevMonth 和 nextMonth的值,需要注意跨年度问题,时间和需求问题,暂未实现。

思考

是否采用构造函数的方式生成多个对象? (如果是工具类的话,不需要构造函数)
方法中采用this.param 还是获取外部参数 如何界定?

收获

用面向对象的方式编写js,思路清晰,语感更好。

转载请注明出处WangYuheng’s Blog

起因

在编写的cms系统中,提供了图片上传功能,但是用户经常(或者说从不)按照推荐的比例上传图片,当然这不是主要问题,关键点在于,经常会上传一些超大分辨率的图片,严重影响整个网站的加载速度。
面对这个问题,除了限制上传图片的大小外,是否还有更自动化的解决方案?

待解决问题:

  1. 图片长宽比
  2. 图片大小
  3. 图片格式
  4. 水印(用户要求)
    关于添加水印的问题,其实用户希望制作一款不能被下载图片的网站,但个人能力有限,实在不能胜任。于是用户提出水印要求,本身可能会觉得水印是本地图片操作的职责,但为了体验系统的自动化,决定增加自动添加水印功能。

解决方案

java 的io操作一直是我较为困扰的部分。原因无它,因为可以实现的方式实在是太多了,在数十个工具类和数个知名类库中该如何选择?而java在图像处理方面,私以为并不占有优势。
经过多方比较,适用,最终采用了thumbnailator这一开源解决方案

thumbnailator

thumbnailator是一个开源的java生成缩略图类库,没有依赖其他外部库,不需要自己去学习Image I/O API, Java 2D API, image processing, image scaling techniques, BufferedImages,Graphics2D 等技术,可以简单方便的实现这些功能。

来源

thumbnailator是一个简单的jar文件,也支持通过maven进行依赖管理

<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.8</version>
</dependency>

使用方法

thumbnailator提供的功能简单、强大,且API规则良好,可以容易的理解接口含义。下面针对上面的问题,介绍一下thumbnailator的主要功能。

重置图片大小

Thumbnails.of(new File("original.jpg"))
    .size(640, 480)
    .outputFormat("jpg")
    .toFiles(Rename.NO_CHANGE);

new File(“original.jpg”) 可以是listFiles()来实现对目录下所有文件的操作,也可以直接输入String类型”original.jpg”
toFiles表示文件输出位置,单个文件可以自定义指定文件路径。
在处理多文件时,可以根据Rename提供的方法Rename.NO_CHANGE表示变更当前图片文件,还有Rename.PREFIX_DOT_THUMBNAIL等增加前缀、后缀等方式,此时会保留原文件和生成后的图片文件。
outputFormat现支持ImageIO.getWriterFormatNames();提供的格式类型,本测试环境为jdk1.6.0_38,提供的类型有BMP bmp jpg JPG wbmp jpeg png PNG JPEG WBMP GIF gif

生成一个旋转90°带水印的缩略图

Thumbnails.of(new File("original.jpg"))
        .size(160, 160)
        .rotate(70)
        .watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File("watermark.png")), 0.5f)
        .outputQuality(0.8)
        .toFile(new File("image-with-watermark.jpg"));
        

rotate表示图片旋转角度,如果不是进行直角(n*90°)旋转的话,图片的size会发生变化。
watermark指定了水印,通过Positions指定水印位置,BufferedImage指定水印图片,第三个参数指定了水印的不透明性,范围为(01.0f),1.0f为不透明。
outputQuality 指定输出图片的质量,范围同样为(0
1.0f)。

除了toFile,Thumbnails还可以将图片输出为OutputStream(toOutputStream)和BufferedImage(asBufferedImage),或者是指定的输出目录(toFiles)。

按比例缩略图片

Thumbnails.of(new File("original.jpg"))
    .scale(0.2)
    .outputFormat("jpg")
    .toFiles(Rename.NO_CHANGE);

scale提供了按照比例缩略图片的方法,也可以通过大于1的数字扩大图片。

存在的问题

在测试环境中,发现水印图片过大导致显示不全和旋转图片后的图片比例变更等问题,都是因为使用不当。
在生产环境中,暂时未遇到使用问题。如果有问题,会在这里指出,并提供解决方案。

结语

thumbnailator是难得的让我通过java简单的使用io和图像处理的工具类,很喜欢它提供api方式,正在学习源码。

Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

简单说一下业务场景,前台用户通过input输入内容,在离开焦点时,将内容在div中显示。
这时遇到一个问题,如果用户输入了html标签,则在div显示中,标签被解析。
由于是纯前端操作,不涉及后端,因此需要通过js对输入内容进行转义。

这里提供一个非常简单有效的转义方案,利用了innerHTMLinnerText
注:火狐不支持innerText,需要使用 textContent 属性,而IE早期版本不支持此属性,为了同时兼容IE及火狐,需要进行判断操作.

因为innerText(textContent)会获取纯文本内容,忽略html节点标签,而innerHTML会显示标签内容,
所以我们先将需转义的内容赋值给innerText(textContent),再获取它的innerHTML属性,这时获取到的就是转义后文本内容。
代码如下:

function HTMLEncode(html) {
    var temp = document.createElement("div");
    (temp.textContent != null) ? (temp.textContent = html) : (temp.innerText = html);
    var output = temp.innerHTML;
    temp = null;
    return output;
}

var tagText = "<p><b>123&456</b></p>";
console.log(HTMLEncode(tagText));//&lt;p&gt;&lt;b&gt;123&amp;456&lt;/b&gt;&lt;/p&gt; 

通过测试结果,可以看到html标签及&符都被转义后保存。
同理,反转义的方法为先将转义文本赋值给innerHTML,然后通过innerText(textContent)获取转义前的文本内容

function HTMLDecode(text) { 
    var temp = document.createElement("div"); 
    temp.innerHTML = text; 
    var output = temp.innerText || temp.textContent; 
    temp = null; 
    return output; 
} 
var tagText = "<p><b>123&456</b></p>";
var encodeText = HTMLEncode(tagText);
console.log(encodeText);//&lt;p&gt;&lt;b&gt;123&amp;456&lt;/b&gt;&lt;/p&gt;
console.log(HTMLDecode(encodeText)); //<p><b>123&456</b></p> 

Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

由于网络连接不稳定,在连接局域网内的mysql开发服务器时,连续多次断开,结果无法连接到mysql服务器,报错内容如下:

Host 'host_name' is blocked because of many connection errors.
Unblock with 'mysqladmin flush-hosts'

这是mysql自带的一种保护机制,如果客户端连续连接失败次数max_connect_errors,超过默认值,则会拒绝客户端继续连接。
可以通过

SHOW VARIABLES LIKE 'max_connect_errors'
    

查看允许连接失败的次数,默认为100次。由于网络不稳,可能会让JDBC等连接很快超过此默认值,可以通过修改此默认值来避免出现连接失败的情形,如

SET GLOBAL max_connect_errors=10000;

如果本机已被拒绝连接,可以通过另一台可连接到mysql服务器的主机执行以下命令

 mysqladmin flush-hosts -h localhost -uroot -p *****
 

此命令会清空所有的hosts缓存信息,执行后即可链接

Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

mysql 查询缓存

在sql调优的过程中,发现原本很慢的一条sql(将近1分钟) 在第二次运行时, 瞬间就完成了(0.04sec)。
这是因为mysql自带的缓存机制,将查询结果进行缓存,如果table数据未发生变化,再次使用同一条sql进行查询时,直接从上次的查询结果缓存中读取数据,而不是重新分析、执行sql。
如果table数据发生变化,所有与之相关的缓存都会被释放刷新,这样就不会出现数据脏读问题。

The query cache stores the text of a SELECT statement together with the corresponding result that was sent to the client. If an identical statement is received later, the server retrieves the results from the query cache rather than parsing and executing the statement again. The query cache is shared among sessions, so a result set generated by one client can be sent in response to the same query issued by another client.


是否使用查询缓存

为了避免缓存,可以在sql查询语句的字段前增加 SQL_NO_CACHE 关键字
如:

select * from t_user;

select SQL_NO_CACHE * from t_user;

反之,你也可以使用 SQL_CACHE 关键字,强制mysql从缓存中读取数据

select SQL_CACHE * from t_user;

mysql还提供了一种释放全部缓存的方法

reset query cache;  

设置查询缓存

查看是否有查询缓存。

SHOW VARIABLES LIKE 'have_query_cache';

注意,只要数据库拥有查询缓存功能,这个VALUE就是YES,无论查询缓存是否启用。

则查询缓存为启用状态。mysql默认为启用状态

mysql查询缓存可以通过两个变量来控制,query_cache_typequery_cache_size


query_cache_type

SHOW VARIABLES LIKE 'query_cache_type';

query_cache_type包含三种状态

  • 0 or OFF 此时不会从缓存中读取查询数据
  • 1 or ON 表示除非声明了SELECT SQL_NO_CACHE,否则都会从缓存中读取数据
  • 2 or DEMAND 表示所有语句都会从缓存中读取,相当于所有查询语句都使用了SELECT SQL_CACHE

通过如下命令可以设置查询缓存状态(需要管理员权限),执行后,需要重启mysql服务才能生效。

SET GLOBAL query_cache_type = 1;  

但是此命令会影响所有的使用此mysql服务的client。可以通过如下命令,关闭此客户端的查询缓存状态,但是同样需要重启server后才能生效。

SET SESSION query_cache_type = OFF;

SHOW VARIABLES LIKE 'query_cache_type';

query_cache_size

SHOW VARIABLES LIKE 'query_cache_size';

query_cache_size表示缓存大小,默认为1M。如果设置为0,则相当于query_cache_size=OFF

同样可通过 SET GLOBAL 进行设置

SET GLOBAL query_cache_size=40000;

需要注意的是,设置的query_cache_size,并不全是用于存储数据,还有约40KB的空间来维护查询缓存的结构。


Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

java.lang#split方法简介

java.lang提供的split方法,可以根据指定分隔符,将String字符串,切割并返回String[]字符串数组。

public String[] split(String regex)
public String[] split(String regex, int limit)

regex为指定分隔符,支持正在表达式
limit为切割后的数组长度,默认为0,不会超过实际长度
例:

public static void main(String[] args) {
    String str = "a,b,c,d,e";
    String[] arr1 = str.split(","); // ["a","b","c","d","e"] length=5
    String[] arr2 = str.split(",", 3); //["a", "b", "c,d,e"] length=3
    String[] arr3 = str.split(",", 10); //["a","b","c","d","e"] length=5
    String[] arr4 = str.split("nan"); //["a,b,c,d,e"] length=1
    String[] arr5 = "".split("nan"); //[""] length=1
}

特殊用法

因为split支持正则参数,可能会需要根据多个分隔符进行分隔,如 id=? and gender=? or age>?,希望根据andor两个分隔符进行分隔,则可通过正则实现。

public static void main(String[] args) {
    String str = "id=? and gender=? or age>?";
    String[] arr1 = str.split("and | or"); // ["id=? ","gender=?"," age>?"] length=3
}

注意上述中的空格符, 如果只根据关键字分隔,不能输入空格。**”and|or”**,可实现分隔效果。


易现exception

NullPointerException

字符串为””时, 返回结果为String[]{“”},但是字符串为null,会抛出空指针异常java.lang.NullPointerException

ArrayIndexOutOfBoundsException

标题中出现的java.lang.ArrayIndexOutOfBoundsException, 是一个容易出现的bug,因为支持正则参数,如果传入参数为转义符,则会出现非预期的结果数据。
如:

public static void main(String[] args) {
    String str = "a.b.c.d.e";
    String[] arr1 = str.split("."); // length=0 如果读取数组 则会出现数组越界异常ArrayIndexOutOfBoundsException
    String[] arr2 = str.split("\\."); // ["a","b","c","d","e"] length=5
}

同理, 根据**”|”分隔,需要使用“\|”**

java.util.regex.PatternSyntaxException

同样是由于正则表达式参数,**”+”** “*”需要使用“\+” **”\*”**进行转义,否则,会报错java.util.regex.PatternSyntaxException

public static void main(String[] args) {
    String str = "a+b+c+d+e";
    String[] arr1 = str.split("\\+"); // ["a","b","c","d","e"] length=5
    String[] arr2 = str.split("+"); // java.util.regex.PatternSyntaxException: Dangling meta character '+' near index 0
}

Best regards
Wang Yuheng

转载请注明出处WangYuheng’s Blog

Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。

初识markdown

       第一次接触markdown,用在blog的编辑器上,发现并没有传统的富文本支持,自己的代码格式又是一片混乱,然后发现了一句“支持markdown语法”。经过查找之后发现,markdown确实可以提供非常良好的写作体验。
markdown给我的第一感觉是类似于HTML语言,同样都是通过简单的标记实现样式显示,但是markdown要简单的多,并且通过普通的文本编辑器就可书写,也可以非常方便的进行移植。

markdown优点

  • 纯文本实现,兼容各种文本编辑器,移植行好
  • 标签简单,可读性好
  • 插件众多,扩展性强
  • 可导出为HTML、PDF等多种格式

我最看重的一点是,对于blog而言,markdown能让我更专注于文字内容本身,而不用关心布局。这有助于让我摆脱网站布局和繁重的word。


学习markdown

markdown语言简单,容易记忆,只需了解一些常用的标记方式,就可实现文字的编辑工作。
(**注:**markdown的标准语言为:标签和文字内容之间要保留一个空格。)

常用标记

标题

将一句话作为文章的标题,只需在文字前加上**#**即可。
# 一级标题
## 二级标题
### 三级标题
以此类推,markdown和HTML一样,也提供了6种不同大小的标题方式。

列表

列表分为有序列表和无序列表两种:

无序列表只需在文字前加上**-或***,可混合使用,推荐使用**-**
- 列表1
* 列表2
- 列表3

  • 列表1
  • 列表2
  • 列表3

有序列表直接在文字前加上“1.” “2.”,可以全部输入数字“1.” ,markdown会自动实现排序
1. 列表1
1. 列表2
1. 列表3

  1. 列表1
  2. 列表2
  3. 列表3

格式

提供了非常方便的粗体和斜体格式规则 一个* 包含的文字为斜体, 两个*包含则为粗体

*斜体格式*

斜体格式

**粗体格式**

粗体格式

引用

引用外部一段文字的语法非常简单,只需在文字前加上大于号**>**
>这是一段引用的文字

这是一段引用的文字

链接

[链接](http://wangyuheng.github.io)

链接

图片

图片和链接的使用方式类似,差别在于图片需要前面加一个!
![](http://wangyuheng.github.io/images/searchicon.png)

表格

| Tables        | Are           | Cool  |
| ------------- |:-------------:| -----:|
| col 3 is      | right-aligned | $1600 |
| col 2 is      | centered      |   $12 |
| zebra stripes | are neat      |    $1 |
Tables Are Cool
col 3 is right-aligned $1600
col 2 is centered $12
zebra stripes are neat $1

分割线

分割线的语法很简单,只需要三个*****就可以,相当于HTML中的
,文本会另起一行
***


markdown相关软件


结语

通过上面的简单介绍,已经可以在提供了markdown的环境下,简单的使用这一写作神奇了。至于更高级的功能、软件,以及熟练程度,都需要在实际的使用过程中慢慢积累。
最后,祝您写作愉快!


Best regards
Wang Yuheng