转载请注明出处WangYuheng’s Blog

在CentOS环境下搭建一个nginx的服务器,安装过程比较简单,做一个简单的记录。

安装过程

配置repo

vi /etc/yum.repos.d/nginx.repo 

通过命令打开文档,输入如下内容

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1

其中centos表明当前系统,7表示系统版本。

执行yum安装

sudo yum install -y nginx

安装程序执行完毕后,可以通过

nginx -v

查看安装nginx版本,并确认程序安装成功。

启动服务

service nginx start 

此时会提示 Redirecting to /bin/systemctl start nginx.service
因为在centos7中将service 和 chkconfig 命令合并到systemctl中,但仍可通过service启动nginx服务。
此时通过浏览器访问http://localhost,可以看到nginx启动成功的提示。

配置

修改服务目录

vi /etc/nginx/conf.d/default.conf

listen 80; 表示监听80端口
server_name localhost;表示域名或ip,可以以此搭建反向代理服务器,实现负载均衡。这个后续会有文档单独写。
location / {} root表示指向的静态文件目录路径,index表示首页文件,可以自定义修改root目录, 或者直接修改默认目录下的首页文件 /usr/share/nginx/www/index.html
重启nginx服务

service nginx restart

刷新浏览器,可以看见修改以生效。

转载请注明出处WangYuheng’s Blog

在CentOS环境下搭建一个nginx的服务器,安装过程比较简单,做一个简单的记录。

安装过程

配置repo

vi /etc/yum.repos.d/nginx.repo 

通过命令打开文档,输入如下内容

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1

其中centos表明当前系统,7表示系统版本。

执行yum安装

sudo yum install -y nginx

安装程序执行完毕后,可以通过

nginx -v

查看安装nginx版本,并确认程序安装成功。

启动服务

service nginx start 

此时会提示 Redirecting to /bin/systemctl start nginx.service
因为在centos7中将service 和 chkconfig 命令合并到systemctl中,但仍可通过service启动nginx服务。
此时通过浏览器访问http://localhost,可以看到nginx启动成功的提示。

配置

修改服务目录

vi /etc/nginx/conf.d/default.conf

listen 80; 表示监听80端口
server_name localhost;表示域名或ip,可以以此搭建反向代理服务器,实现负载均衡。这个后续会有文档单独写。
location / {} root表示指向的静态文件目录路径,index表示首页文件,可以自定义修改root目录, 或者直接修改默认目录下的首页文件 /usr/share/nginx/www/index.html
重启nginx服务

service nginx restart

刷新浏览器,可以看见修改以生效。

转载请注明出处WangYuheng’s Blog

工作需要,在虚拟机中安装了一个CentOS7的linux环境,用来测试MongoDB。记录一下MongoDB的安装和配置过程。

安装过程

安装过程通过yum方式,需要配置repo。

配置repo

vi /etc/yum.repos.d/mongodb-org-3.2.repo

通过命令打开文档,输入如下内容

[mongodb-org-3.2]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.2/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-3.2.asc

执行yum安装

sudo yum install -y mongodb-org

设置mongodb监听端口

semanage port -a -t mongod_port_t -p tcp 27017

启动服务

sudo service mongod start

开机自启动

chkconfig mongod on

此时会看到服务启动成功的提示 【OK】

开发调试

在虚拟机中安装好环境后,如何用本机环境进行开发访问?
本机为windows环境并没有安装mongodb客户端程序,无法通过命令行直接访问,全部依赖于java代码访问。
此时有个问题,mongodb安装完成后,默认只监听本机ip(localhost),必须要修改绑定

查看本机ip

为了让mongodb监听虚拟机的ip(局域网内ip),必须先将虚拟机的网络连接环境设置为桥接。通过命令查看

ifconfig

注:因为我的安装镜像为mini.iso,默认并没有安装网络工具,此时可能会报错
ifconfig command not found,需要先安装此工具集

yum install net-tools

修改绑定监听IP

vi /etc/mongod.conf

打开文档,并将bind_ip修改为ifconfig查看到的本地ip

重启mongodb服务

sudo service mongod restart

创建集合(数据库)

需要进入mongo后,才可以执行mongo相关命令。进入方式为执行如下命令。默认ip为127.0.0.1,因为修改过bind_ip,所以此处的ip地址为刚配置的bing_ip

mongo ip地址

创建集合

use zero

创建用户

mongodb默认为允许匿名用户访问,可以通过以下命令添加此集合的管理员账户

db.createUser({user:'zero_admin',pwd:'zero_123456',roles:[{role:'dbOwner',db:'zero'}]}) 

至此,已完成在虚拟机中搭建mongodb服务,并可在本机程序访问调试。下篇文章将介绍如何通过java程序连接mongodb

工作需要,在虚拟机中安装了一个CentOS7的linux环境,用来测试MongoDB。记录一下MongoDB的安装和配置过程。

安装过程

安装过程通过yum方式,需要配置repo。

配置repo

vi /etc/yum.repos.d/mongodb-org-3.2.repo

通过命令打开文档,输入如下内容

[mongodb-org-3.2]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.2/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://www.mongodb.org/static/pgp/server-3.2.asc

执行yum安装

sudo yum install -y mongodb-org

设置mongodb监听端口

semanage port -a -t mongod_port_t -p tcp 27017

启动服务

sudo service mongod start

开机自启动

chkconfig mongod on

此时会看到服务启动成功的提示 【OK】

开发调试

在虚拟机中安装好环境后,如何用本机环境进行开发访问?
本机为windows环境并没有安装mongodb客户端程序,无法通过命令行直接访问,全部依赖于java代码访问。
此时有个问题,mongodb安装完成后,默认只监听本机ip(localhost),必须要修改绑定

查看本机ip

为了让mongodb监听虚拟机的ip(局域网内ip),必须先将虚拟机的网络连接环境设置为桥接。通过命令查看

ifconfig

注:因为我的安装镜像为mini.iso,默认并没有安装网络工具,此时可能会报错
ifconfig command not found,需要先安装此工具集

yum install net-tools

修改绑定监听IP

vi /etc/mongod.conf

打开文档,并将bind_ip修改为ifconfig查看到的本地ip

重启mongodb服务

sudo service mongod restart

创建集合(数据库)

需要进入mongo后,才可以执行mongo相关命令。进入方式为执行如下命令。默认ip为127.0.0.1,因为修改过bind_ip,所以此处的ip地址为刚配置的bing_ip

mongo ip地址

创建集合

use zero

创建用户

mongodb默认为允许匿名用户访问,可以通过以下命令添加此集合的管理员账户

db.createUser({user:'zero_admin',pwd:'zero_123456',roles:[{role:'dbOwner',db:'zero'}]}) 

至此,已完成在虚拟机中搭建mongodb服务,并可在本机程序访问调试。下篇文章将介绍如何通过java程序连接mongodb

时间比较匆忙,可能记录有些乱。

先说明一下问题:我用Spring MVC 4.0+ 编写restful框架, 因为看到了提供有MappingJackson2JsonView类,就没有通过自己转换json, 返回字符串。

但是遇到一个灵异的问题:如果利用javaBean类接收参数,在返回值中,会自动把javaBean的键值对以json格式返回,效果等同于对javaBean参数使用了标签@ModelAttribute


MappingJackson2JsonView-0

会返回

MappingJackson2JsonView-1

使用 View 是因为以后有需要的话,可能会重写一个JsonView。

翻了一天的源码,看到了如下几个部分(以图片中的代码举例):

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest)

初始container的model里有2个对象,但并没有返回的data Map, 而是一个user和一个BindingResult,user是入参,BindingResult用于validation绑定页面元素。


org.springframework.web.servlet.DispatcherServlet#processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)

方法中通过

org.springframework.web.servlet.view.AbstractView#createMergedOutputModel(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)

方法将data Map参数放入model,由ModelAndView对象持有。

然后MappingJackson2JsonView#filterModel 方法会过滤掉BindingResult 以及 指定的filter规则类

protected Object filterModel(Map<String, Object> model) {
    Map<String, Object> result = new HashMap<String, Object>(model.size());
    Set<String> modelKeys = (!CollectionUtils.isEmpty(this.modelKeys) ? this.modelKeys : model.keySet());
    for (Map.Entry<String, Object> entry : model.entrySet()) {
        if (!(entry.getValue() instanceof BindingResult) && modelKeys.contains(entry.getKey()) &&
                !entry.getKey().equals(JsonView.class.getName()) &&
                !entry.getKey().equals(FilterProvider.class.getName())) {
            result.put(entry.getKey(), entry.getValue());
        }
    }
    return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
}

modelKeys 可以通过 MappingJackson2JsonView#setModelKeys方法指定,所以如果希望只返回data Map 需要指定

    MappingJackson2JsonView model = new MappingJackson2JsonView();
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("data", object);
    model.setModelKeys(params.keySet());

这种设计默认全部返回,通过Set数组参数 指定需要返回的json对象。如果全部展平为一级的map,是不是就可以不经过转换,而通过此参数指定给客户端返回的参数?

BindingResult 是将校验结果返回给页面展现,包括元素和错误message等信息。restful不需要此信息,所以系统自动忽略。

但是可以通过此规则进行入参校验

转载请注明出处WangYuheng’s Blog

起因

很多web开发者对360浏览器都没有什么好感,拥有着庞大的用户量,但又因为种种特性导致网页兼容问题,尤其是360安全浏览器提供的双内核功能,总是将网站用IE内核去解析,而自己的网站 http://se.360.cn/ 却指定Webkit内核且不能切换。

但是,其实你们都错了,360是一家有着节操和普世情怀的良心企业,在官网中提供了通过meta指定内核的方法,并且希望通过推广,让更多的浏览器厂商认可这一标准

指定内核

360浏览器提供的方式非常简单,只需要在meta中指定希望浏览器解析网页的默认的内核即可

<meta name="renderer" content="webkit|ie-comp|ie-stand">
  • webkit 使用Webkit内核,效果等同于chrome浏览器,别称chrome内核。
  • ie-comp 使用IE兼容内核,相当于IE6、7,这两个现在看来迷一样的浏览器。
  • ie-stand 使用IE标准内核,次模式下会调用用户安装的IE内核进行解析,如用户本地IE版本为IE9,则通过IE9内核进行解析。

通过此方法,就可以让360安全浏览器以Webkit内核解析网页,以便达到对HTML5的良好支持。

<html>
    <head>
        <meta name="renderer" content="webkit">
    </head>
    <body>
    </body>
</html>

总结

强大的360。附官网对此功能的详细介绍http://se.360.cn/v6/help/meta.html

转载请注明出处WangYuheng’s Blog

起因

javascript对象就是各种属性的集合,业务场景为接收用户传来的options参数,如果默认defaultOptions存在传入的参数属性,则进行替换。所以存在一个检查对象是否包含某个属性的操作。
在原型属性中找到了hasOwnProperty和propertyIsEnumerable两个方法。那么这两个方法有什么区别呢?各自的使用场景又是什么呢?

hasOwnProperty

hasOwnProperty()方法可以接收一个字符参数,用来判断对象中是否存在以字符参数命名的属性。它只会查找自身属性,不会根据原型链进行查找。
注:原型链会再另一篇文章中做详细的介绍!

var obj = {x:"1"};
obj.y = function(){};
console.log(obj.hasOwnProperty("x")); //true
console.log(obj.hasOwnProperty("y")); //true 方法也是属性
console.log(obj.hasOwnProperty("z")); //false 属性不存在
console.log(obj.hasOwnProperty("toString")); //false hasOwnProperty是继承Object的属性,自身属性中不存在

propertyIsEnumerable

propertyIsEnumerable()方法在hasOwnProperty()的基础上,校验属性是否为枚举属性。也就是说属性必须满足为自身属性且为枚举属性时,才会返回true。

枚举属性

枚举属性,其实是可被枚举,表示该属性可以在对象中被遍历。javascript属性默认均为枚举属性。如

var obj = {x:"1"};
Object.defineProperty(obj, 'y', {value : '2', enumerable : true });
Object.defineProperty(obj, 'z', {value : '3', enumerable : false });
console.log(obj);//Object {x: "1", y: "2", z: "3"}
for (var i in obj) {
    console.log(i); //x,y
}

上述代码中属性z被手动指定为非枚举属性,因此没有被遍历。
结合上面的2例子,可以清晰的知道propertyIsEnumerable()函数的作用:

var obj = {x:"1"};
obj.y = function(){};
Object.defineProperty(obj, 'z', {value : '2', enumerable : true });
Object.defineProperty(obj, 'w', {value : '3', enumerable : false });
console.log(obj.propertyIsEnumerable("x")); //true 属性默认为枚举属性
console.log(obj.propertyIsEnumerable("y")); //true 方法也是属性
console.log(obj.propertyIsEnumerable("z")); //true
console.log(obj.propertyIsEnumerable("w")); //false 属性不是枚举属性
console.log(obj.propertyIsEnumerable("v")); //false 属性不存在
console.log(obj.propertyIsEnumerable("toString")); //false propertyIsEnumerable是继承Object的属性,自身属性中不存在

扩展一 !== undefined

有一种简单的方法可以判断属性是否存在,通过属性!== undefined来判断。此时会检测自身和继承来的属性。之所以使用!==而不是!=是因为!==可以区分undefined和null。但是此方法有一个弊端,当属性存在且值为undefined时,无法做出准确判断。如:

var obj = {x:"1", y:undefined, z:null};
console.log(obj.x !== undefined); //true 属性存在
console.log(obj.y !== undefined); //false 此时会出现歧义,不能准确判断属性是不存在还是属性值为undefined
console.log(obj.z !== undefined); //true 属性存在
console.log(obj.z != undefined); //false != 不能区分undefined和null,将两者同等对待
console.log(obj.w !== undefined); //false 属性不存在
console.log(obj.toString !== undefined); //true 存在toString函数属性。

扩展二 in

为了避免!==undefined带来的歧义,可以使用in运算符进行属性存在的检测。in是根据属性,而不是通过属性值来判断是否存在。

var obj = {x:"1", y:undefined, z:null};
console.log("x" in obj); //true 属性存在
console.log("y" in obj); //true 属性存在
console.log("z" in obj); //true 属性存在
console.log("w" in obj); //false 属性不存在
console.log("toString" in obj); //true 属性存在

总结

  • hasOwnProperty 自身存在的属性
  • propertyIsEnumerable 自身存在的属性,且为枚举属性
  • !== undefined 自身存在的属性,继承的属性,不能识别值为undefined的属性
  • in 自身存在的属性,继承的属性

根据具体业务场景,自由选择或组合对应的方法,可以实现对属性的检测。

转载请注明出处WangYuheng’s Blog

XML

XML (eXtensible Markup Language), 可扩展标记语言,发明之初是为了取代HTML,但在使用过程中,开发者发现这种规范的语言格式,在数据传输方面有着明显的优势。
这里只将XML作为一种数据交换格式。
比如说java语言本身的javaBean数据,在程序内使用没有问题,但是如果涉及到与其他语言进行交互,则会出现很多问题。所以通过XML进行数据传输通信,可以拥有更好的跨平台性和可移植性,并且让底层数据预备了可读性。
本篇文章的重点在于介绍如果通过java DOM对XML文件进行解析与操作。

dom

DOM是W3C处理XML的标准API,多种语言都实现了该标准,java对dom的实现在org.w3c.dom包内。很多工具类都是在此基础上进行了封装和扩充,如jdom、dom4j等,这里使用原生实现来完成对xml文档的基本操作。
DOM的实现原理是将XML作为树结构全部读入内存,再进行操作。好处是简单快捷,可以修改结构和数据,而造成的隐患则是是在读取大型XML文件时,可能会造成过多的内存占用。

代码

读取解析xml文件

需要读取的xml文件如下,传递了商品订单信息,包括商品名、价格、购买数量。通过程序读取数据,并计算出订单总价格。
因为本次重点在于xml的解析操作,所以价格直接用float类型处理。如果是生产环境,一定要使用BigDecimal操作,避免float的精度问题!!

<?xml version="1.0" encoding="UTF-8" ?>
<shopping>
    <goods>
        <name>品名1</name>
        <price>3</price>
        <number>4</number>
    </goods>
    <goods>
        <name>品名2</name>
        <price>1.2</price>
        <number>3</number>
    </goods>
</shopping>

java 读取XML代码

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class XmlParser {

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

    public Document parseDoc(String filePath) {
        Document document = null;
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            document = builder.parse(new File(filePath));
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return document;
    }

    public static void main(String[] args) {
        XmlParser parser = new XmlParser();
        Document document = parser.parseDoc("D://shopping.xml");
        Element rootElement = document.getDocumentElement();
        List<Goods> goodsList = new ArrayList<Goods>();
        NodeList goodsNodeList = rootElement.getElementsByTagName("goods");
        for (int i = 0; i < goodsNodeList.getLength(); i++) {
            Element child = (Element) goodsNodeList.item(i);
            Goods goods = new Goods(child);
            goodsList.add(goods);
        }

//        NodeList goodsNodeList = rootElement.getChildNodes();
//        for (int i = 0; i < goodsNodeList.getLength(); i++) {
//            Node node = goodsNodeList.item(i);
//            if (node.getNodeType() == Node.ELEMENT_NODE) {
//                Element child = (Element) node;
//                Goods goods = new Goods(child);
//                goodsList.add(goods);
//            }
//        }
        float total = 0;
        int sum = 0;
        for (Goods goods : goodsList) {
            total += goods.getTotal();
            sum += goods.getNumber();
        }
        System.out.println(total);
        System.out.println(sum);
    }

    static class Goods {
        private float price;
        private int number;
        public Goods(Element element) {
            this.price = Float.parseFloat(element.getElementsByTagName("price").item(0).getTextContent());
            this.number = Integer.parseInt(element.getElementsByTagName("number").item(0).getTextContent());
        }
        public float getTotal(){
            return this.price * this.number;
        }
        public int getNumber(){
            return number;
        }
    }
}

node和element的关系

element一定是node但是node不一定是element,node可能是元素节点、属性节点、文本节点,而element表示包含开始标签和结束标签的完整元素。
所以上面的代码中 用

NodeList goodsNodeList = rootElement.getElementsByTagName("goods");

获取了NodeList,可以直接转型为Element: Element child = (Element) node;
如果获取的是node节点

NodeList goodsNodeList = rootElement.getChildNodes();

则必须在循环中增加判断if (node.getNodeType() == Node.ELEMENT_NODE) {} 判断当前节点是否为Element元素。

生成xml文件

将统计后的订单信息以xml格式输出,生成文件格式如下

<?xml version="1.0" encoding="utf-8"?>
<order>
    <total>15.6</total>
    <sums>7</sums>
</order>

生成xml的操作和读取的顺序类似,先创建rootElement,然后添加childElement,再将rootElement放到document中,最后通过io输出xml文件到指定路径。
代码如下:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class XmlParser {

    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    TransformerFactory transformerFactory = TransformerFactory.newInstance();

    public Document parseDoc(String filePath) {
        Document document = null;
        try {
            DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
            document = builder.parse(new File(filePath));
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return document;
    }

    public void generateXml(String filePath, Document document){
        DOMSource source = new DOMSource(document);
        Transformer transformer = createtransformer();
        PrintWriter pw = null;
        try {
            pw = new PrintWriter(new FileOutputStream(filePath));
            StreamResult result = new StreamResult(pw);
            transformer.transform(source, result);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (TransformerConfigurationException e1) {
            e1.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        } finally {
            pw.close();
        }
    }

    public Document createDoc() {
        Document document = null;
        try {
            DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
            document = builder.newDocument();
            document.setXmlStandalone(true);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
        return document;
    }

    public Transformer createtransformer(){
        Transformer transformer = null;
        try {
            transformer = transformerFactory.newTransformer();
            //default former
//            transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
            transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        }
        return transformer;
    }

    public static void main(String[] args) {
        XmlParser parser = new XmlParser();
        Document document = parser.parseDoc("D://shopping.xml");
        Element rootElement = document.getDocumentElement();
        List<Goods> goodsList = new ArrayList<Goods>();
        NodeList goodsNodeList = rootElement.getElementsByTagName("goods");
        for (int i = 0; i < goodsNodeList.getLength(); i++) {
            Element child = (Element) goodsNodeList.item(i);
            Goods goods = new Goods(child);
            goodsList.add(goods);
        }
        float total = 0;
        int sum = 0;
        for (Goods goods : goodsList) {
            total += goods.getTotal();
            sum += goods.getNumber();
        }
        Document orderDocument = parser.createDoc();
        Order order = new Order(total, sum);
        Element orderElement = order.getElement(orderDocument);
        orderDocument.appendChild(orderElement);

        parser.generateXml("D://order.xml", orderDocument);

    }

    static class Order {
        private float total = 0;
        private int   sum   = 0;

        public Order(float total, int sum) {
            this.total = total;
            this.sum = sum;
        }

        public Element getElement(Document document) {
            Element rootElement = document.createElement("order");

            Element totalElement = document.createElement("total");
            totalElement.setTextContent(String.valueOf(this.total));
            rootElement.appendChild(totalElement);

            Element sumElement = document.createElement("sum");
            sumElement.setTextContent(String.valueOf(this.sum));
            rootElement.appendChild(sumElement);

            return rootElement;
        }

    }

    static class Goods {
        private float price;
        private int   number;

        public Goods(Element element) {
            this.price = Float.parseFloat(element.getElementsByTagName("price").item(0).getTextContent());
            this.number = Integer.parseInt(element.getElementsByTagName("number").item(0).getTextContent());
        }

        public float getTotal() {
            return this.price * this.number;
        }

        public int getNumber() {
            return number;
        }
    }
}

standalone

这里有一个小坑 就是生成的xml中有一个属性为standalone=”no”
standalone表示是否为独立文件,也就是说是否依赖于其他外部文件,如果为yes,则表示为不依赖其他外部文件的独立文件,默认为yes。
但是生成之后的standalone=”no”,不符合预期,并且

transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");

设置格式之后standalone=”no”仍然为no。这时需要设置document中的setXmlStandalone属性,

document.setXmlStandalone(true);

再次输出,可以去掉standalone属性。

结语

理解xml的结构之后,和dom的树形结构后,无论是使用原生支持还是通过第三方类库去操作xml文件,都可以很容易的上手。
and 我喜欢用json

转载请注明出处WangYuheng’s Blog

起因

业务场景为一个type=text的表单元素,通过ajax进行搜索。但是遇到一个bug,就是在输入之后按回车,就会自动提交表单。查找之后发现了浏览器的表单提交特性

如果只有一个text表单元素,回车会自动提交表单!!

为了避免这种bug,将表单回车后提交的场景都测试了一遍。

场景一:只有一个type=text表单元素

<form action="http://blog.crick.wang">
    <input type="text" name="username" />
</form>

在此场景下,只要在input中按下回车键,就会自动提交表单。
为了避免自动提交,可以设置一个隐藏的text元素,如

<form action="http://blog.crick.wang">
    <input type="text" style="display:none" />
    <input type="text" name="username" />
</form>

场景二:有n个type=text和一个type=submit表单元素

<form action="http://blog.crick.wang">
    <input type="text" name="username" />
    <input type="text" name="password" />
    <input type="submit" value="Submit!" />
</form>

在此场景下,只要在input中按下回车键,就会自动提交表单。
为了避免自动提交,网上常见的例子是在form表单中监听onkeydown事件,如果是回车输入,则返回false,阻止表单提交。

<form action="http://blog.crick.wang" onkeydown="if(event.keyCode==13){return false;}">
    <input type="text" name="username" />
    <input type="text" name="password" />
    <input type="submit" value="Submit!" />
</form>

但是这种写法存在1个问题:如果是textarea,可能会需要回车操作。

为了解决上述问题,需要增加针对textarea的判断条件,而且还要注意兼容ie。

<script type="text/javascript">
document.onkeydown = function(event) {
    event = event || window.event;
    var param = event.target || event.srcElement;
    // if (param.name=="username"){return true;}
    if (event.keyCode == 13) {
        if ("INPUT" == param.tagName) {
            return false;
        }
    }
};
</script>
<form action="http://blog.crick.wang">
    <input type="text" name="username" />
    <input type="text" name="password" />
    <textarea name="introduction"></textarea>
    <input type="submit" value="Submit!" />
</form>

根据if (param.name==”username”){return true;}就可以自定义在某个表单元素中,点击回车键,就可以提交表单。

场景三:有n个type=text,没有type=submit元素

此时点击回车键时,表单不会提交。可以通过监听onkeydown事件,利用submit()方法,根据keyCode和tagName或者表单元素的name来自定义提交规则

<script type="text/javascript">
document.onkeydown = function(event) {
    event = event || window.event;
    var param = event.target || event.srcElement;
    if (event.keyCode == 13 && param.name=="password"){
        document.getElementById("entityForm").submit();
    }
};
</script>
<form action="http://blog.crick.wang" id="entityForm">
    <input type="text" name="username" />
    <input type="text" name="password" />
    <textarea name="introduction"></textarea>
</form>

场景四:有n个type=text和一个button标签元素

button标签,如果未指定type,则在ie6和ie7下为type=”button”, 在ie8及chrome和火狐下为type=”submit”,为了避免这种混乱,button标签一定要指定type。
如果指定为type=”button”,则和场景三的处理方式一致,
如果是type=”submit”,则同场景二。

结语

ie的特性让web开发者抓狂,本来很多浏览器自身属性可以方便解决的问题,都需要重新造一个轮子,达到浏览器兼容。
制造轮子和规则,为了规避约定或者隐藏特性带来的不可预知的麻烦。

转载请注明出处WangYuheng’s Blog

起因

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

效果图


前提

算法

说一下日历的算法

  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,思路清晰,语感更好。