二、搭建zookeeper服务注册中心

一、关于ZooKeeper和Dubbo

1. ZooKeeper(动物园管理员)是什么

注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。使用dubbo-2.3.3以上版本,建议使用zookeeper注册中心。
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

2. Dubbo

这个项目是基于SOA架构的,表现层和服务层在不同的系统中,而且还会调用一些其他系统的服务,所以系统之间的通信时不可避免的。
那么如何进行跨系统通信呢?
1、Webservice:效率不高基于soap协议。项目中不推荐使用。
2、使用restful形式的服务:http+json。很多项目中应用。如果服务太多,服务之间调用关系混乱,需要治疗服务。
3、使用dubbo。使用rpc协议进行远程调用,直接使用socket通信。传输效率高,并且可以统计出系统之间的调用关系、调用次数。

什么是dubbo?

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

系统架构演进趋势.jpg
  • 单一应用架构
    当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
    此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。

  • 垂直应用架构
    当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。
    此时,用于加速前端页面开发的Web框架(MVC)是关键。

  • 分布式服务架构
    当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
    此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。

  • 流动计算架构
    当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。
    此时,用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。

Dubbo就是资源调度和治理中心的管理工具。
其核心部分包含:

  1. 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
  2. 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
  3. 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

Dubbo的架构

Dubbo的架构.jpg

节点角色说明:
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。
调用关系说明:

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

dubbo使用方法
Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。

发布服务:

<!-- 和本地服务一样实现远程服务 -->
<bean id="xxxService" class="com.xxx.XxxServiceImpl" />
<!-- 增加暴露远程服务配置 -->
<dubbo:service interface="com.xxx.XxxService" ref="xxxService" />

调用服务:

<!-- 增加引用远程服务配置 -->
<dubbo:reference id="xxxService" interface="com.xxx.XxxService" />
<!-- 和本地服务一样使用远程服务 -->
<bean id="xxxAction" class="com.xxx.XxxAction">
    <property name="xxxService" ref="xxxService" />
</bean>

二、搭建zookeeper服务注册中心

1. zookeeper单机版

第一步:安装jdk

  1. 最好先建一个专门存放上传文件的文件夹:
    命令 mkdir -p /app/tools

  2. 上传jdk的Linux版本到这个文件夹中,最好是1.7版本,和zookeeper比较搭,上次我用了1.8版本,有一些莫名其妙的bug。

    上传命令:我没用命令,直接工具上传的。
    上传界面
  3. 再新建一个文件夹,存放解压后的文件,然后解压到这个文件夹。
    mkdir /usr/local/java
    tar -zxvf jdk-1.7-40.tar.gz -C /usr/local/java/

  4. 配置Linux的jdk环境变量
    修改/etc/profile文件:vi /etc/profile
    在尾部追加:

# java environment
export JAVA_HOME=/usr/java/jdk1.7.0_40 
export CLASSPATH=.:${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar
export PATH=$PATH:${JAVA_HOME}/bin

:wq保存退出

配置Linux的jdk环境变量
  1. 配置完成后刷新配置: source /etc/profile

  2. 测试是否成功: java -version
    image.png

有可能出现的bug :
-bash: /usr/jdk1.7.0_40/bin/java: /lib/ld-linux.so.2: bad ELF interpreter: 没有那个文件或目录
解决方式:
输入命令 yum install ld-linux.so.2

第二步:解压zookeeper安装包
创建zookeeper解压目录:mkdir /usr/local/zookeeper
解压:tar -zxvf zookeeper-3.4.8.tar.gz -C /usr/local/zookeeper/

第三步:将conf文件夹下zoo_sample.cfg复制一份,改名为zoo.cfg
命令:cp zoo_sample.cfg zoo.cfg

第四步:修改配置dataDir属性,指定一个真实目录
在zookeeper根目录中,创建一个data文件夹和logs文件夹,然后修改conf文件夹中的zoo.cfg的dataDir属性和dataLogDir属性:
dataDir=/usr/local/zookeeper/zookeeper-3.4.8/data
dataLogDir=/usr/local/zookeeper/zookeeper-3.4.8/logs

zoo.cfg

第五步:启动zookeeper并进行测试
关闭防火墙:systemctl stop firewalld
禁止防火墙开机启动:systemctl disable firewalld
启动zookeeper:./zookeeper-3.4.8/bin/zkServer.sh start
查看状态:./zookeeper-3.4.8/bin/zkServer.sh status
客户端连接测试以下::./zookeeper-3.4.8/bin/zkCli.sh -server 127.0.0.1:2181

成功状态

进去看一下就行了,一般也不会动它。

第六步,部署dubbo监控中心
首先上传tomcat,解压tomcat;
上传dubbo-admin-2.5.4.war包,放到tomcat的webapps文件夹下面;
启动tomcat:./startup.sh start
访问http://192.168.245.130:8080/dubbo-admin-2.5.4/,输入用户名root和密码root,进入监控页面,没有服务的话,看不出啥效果,我这个已经有服务了。

服务提供者:
提供者

服务消费者:
消费者

2.zookeeper注册中心集群版

单机模式就不废话太多了,开始集群。

第一步,将解压好的zookeeper文件夹复制一份:cp -rf zookeeper-3.4.8/ zk1

第二步,修改zk1中的zoo.cfg文件,并在data文件夹下创建myid文件,写入数字1

进入data文件夹,创建myid:echo 1>>myid

修改zoo.cfg:

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/local/zookeeper/zk1/data
clientPort=2181
dataLogDir=/usr/local/zookeeper/zk1/logs

server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883

这里需要注意,三个端口号是不同的,千万不要有重复,到时候leader选不出来。

  • 3个端口的作用:
    2181:对cline端提供服务
    2881:集群内机器通讯使用(Leader监听此端口)
    3881:选举leader使用

第三步,复制两份zk1,分别为zk2、zk3,修改zk2(2182,2)和zk3(2183,3)的zoo.cfg文件和myid文件。

第四步,启动zookeeper集群,查看状态。
./zk1/bin/zkServer.sh start

集群状态

为什么是zookeeper集群的基数三个呢?准确的说为什么是单数呢?

  1. 容错
    所谓的zookeeper容错是指,当宕掉几个zookeeper服务器之后,剩下的个数必须大于宕掉的个数,也就是剩下的服务数必须大于n/2,zookeeper才可以继续使用,无论奇偶数都可以选举leader。5台机器最多宕掉2台,还可以继续使用,因为剩下3台大于5/2。
    说为什么最好为奇数个,是在以最大容错服务器个数的条件下,会节省资源,比如,最大容错为2的情况下,对应的zookeeper服务数,奇数为5,而偶数为6,也就是6个zookeeper服务的情况下最多能宕掉2个服务,所以从节约资源的角度看,没必要部署6(偶数)个zookeeper服务。

  2. 防脑裂

一个zookeeper集群中,可以有多个follower、observer服务器,但是必需只能有一个leader服务器。

如果leader服务器挂掉了,剩下的服务器集群会通过半数以上投票选出一个新的leader服务器。

集群互不通讯情况:

一个集群3台服务器,全部运行正常,但是其中1台裂开了,和另外2台无法通讯。3台机器里面2台正常运行过半票可以选出一个leader。

一个集群4台服务器,全部运行正常,但是其中2台裂开了,和另外2台无法通讯。4台机器里面2台正常工作没有过半票以上达到3,无法选出leader正常运行。

一个集群5台服务器,全部运行正常,但是其中2台裂开了,和另外3台无法通讯。5台机器里面3台正常运行过半票可以选出一个leader。

一个集群6台服务器,全部运行正常,但是其中3台裂开了,和另外3台无法通讯。6台机器里面3台正常工作没有过半票以上达到4,无法选出leader正常运行。

四、服务的注册和获取

1. 先不管用什么服务测试,我们先使用mybatis-generator工具类自动生成pojo类和mapper类

这是mybatis-generator工具类的项目结构,与这个项目无关。


image.png

需要的jar包图中有。

GeneratorSqlmap类:

package generator;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class GeneratorSqlmap {

    public void generator() throws Exception{

        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        //指定 逆向工程配置文件
        File configFile = new File("generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
                callback, warnings);
        myBatisGenerator.generate(null);

    } 
    public static void main(String[] args) throws Exception {
        try {
            GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
            generatorSqlmap.generator();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }

}

generatorConfig.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="testTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/yysc" userId="root"
            password="root">
        </jdbcConnection>
        <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和 
            NUMERIC 类型解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- targetProject:生成PO类的位置 -->
        <javaModelGenerator targetPackage="com.yzy.yysc.manager.pojo"
            targetProject="D:\IDEAWorkPlace\yysc\yy-parent\generatorSqlmapCustom\src\main\java">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- targetProject:mapper映射文件生成的位置 -->
        <sqlMapGenerator targetPackage="com.yzy.yysc.manager.mapper"
            targetProject="D:\IDEAWorkPlace\yysc\yy-parent\generatorSqlmapCustom\src\main\java">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>
        <!-- targetPackage:mapper接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER"
            targetPackage="com.yzy.yysc.manager.mapper"
            targetProject="D:\IDEAWorkPlace\yysc\yy-parent\generatorSqlmapCustom\src\main\java">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>
        <!-- 指定数据库表 -->
        <table schema="" tableName="tb_content"></table>
        <table schema="" tableName="tb_content_category"></table>
        <table schema="" tableName="tb_item"></table>
        <table schema="" tableName="tb_item_cat"></table>
        <table schema="" tableName="tb_item_desc"></table>
        <table schema="" tableName="tb_item_param"></table>
        <table schema="" tableName="tb_item_param_item"></table>
        <table schema="" tableName="tb_order"></table>
        <table schema="" tableName="tb_order_item"></table>
        <table schema="" tableName="tb_order_shipping"></table>
        <table schema="" tableName="tb_user"></table>

    </context>
</generatorConfiguration>

配置好以后,直接运行GeneratorSqlmap类中的main方法就好了,会自动在src\main\java下生成。
碰到的bug说一下:
bug1:一直报一个找不到generatorConfig.xml文件的错误。
解决:修改working directory,让他和Output path相同


working directory
Output path

bug2:运行良好却不生成文件
解决:targetProject的位置改为绝对位置


targetProject

以前也用过,不过用的maven插件,想用插件的可以搜一下用法。
这些都是它自动生成我复制过来的:


image.png
image.png

2. 就决定测试获取商品的服务好了

商品是这个表:


image.png

商品pojo类,因为涉及到跨系统,必须实现序列化接口,它完全是根据数据库表的字段自动生成的。
我画蛇添足的把外键改成了该外键对应的类,现在一想这是mybatis不是hibernate,没有必要这样做,完全可以使用多表联查。这里改了那么待会mapper.xml文件中也要改。

TbItem类:

package com.yzy.yysc.manager.pojo;

import com.yzy.yysc.common.vo.PageVo;

import java.io.Serializable;
import java.sql.Timestamp;

public class TbItem extends PageVo implements Serializable{
    private Long id;

    private String title;

    private String sellPoint;

    private Long price;

    private Integer num;

    private String barcode;

    private String image;

   //我改动的地方
    private TbItemCat itemCat;

    private Byte status;

    private Timestamp created;

    private Timestamp updated;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title == null ? null : title.trim();
    }

    public String getSellPoint() {
        return sellPoint;
    }

    public void setSellPoint(String sellPoint) {
        this.sellPoint = sellPoint == null ? null : sellPoint.trim();
    }

    public Long getPrice() {
        return price;
    }

    public void setPrice(Long price) {
        this.price = price;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public String getBarcode() {
        return barcode;
    }

    public void setBarcode(String barcode) {
        this.barcode = barcode == null ? null : barcode.trim();
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image == null ? null : image.trim();
    }

    public TbItemCat getItemCat() {
        return itemCat;
    }

    public void setItemCat(TbItemCat itemCat) {
        this.itemCat = itemCat;
    }

    public Byte getStatus() {
        return status;
    }

    public void setStatus(Byte status) {
        this.status = status;
    }

    public Timestamp getCreated() {
        return created;
    }

    public void setCreated(Timestamp created) {

        this.created = created;
    }

    public Timestamp getUpdated() {
        return updated;
    }

    public void setUpdated(Timestamp updated) {
        this.updated = updated;
    }
}

上面的pojo类我继承了一个PageVo类,这个PageVo类是用来接收前台的分页数据的,我放在yysc-common项目中:

package com.yzy.yysc.common.vo;


import java.io.Serializable;

public class PageVo implements Serializable{
    //封装当前页码
    private Integer page;
    //封装每页数据量
    private Integer limit;


    public Integer getPage() {
        return page;
    }

    public void setPage(Integer page) {
        this.page = page;
    }

    public Integer getLimit() {
        return limit;
    }

    public void setLimit(Integer limit) {
        this.limit = limit;
    }

}

我又新建了一个yysc-manager-pojo项目中新建了一个TbItemVo类,并让它继承了TbItem类,这个类是用来封装一些前台查询条件的,如关键字,日期等,我们与前台的业务交互时,一般使用这个类。

package com.yzy.yysc.manager.vo;

import com.yzy.yysc.manager.pojo.TbItem;
import org.apache.commons.lang3.StringUtils;

import java.sql.Timestamp;

public class TbItemVo extends TbItem {
    private String keywords;
    private Timestamp createTime;
    private Timestamp endTime;

    public String getKeywords() {
        return keywords;
    }

    public void setKeywords(String keywords) {
        this.keywords = keywords;
    }

    public Timestamp getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {

        if (StringUtils.isNotBlank(createTime)) {
            Timestamp ts = new Timestamp(System.currentTimeMillis());
            try {
                ts = Timestamp.valueOf(createTime + " 00:00:00");
                endTime = Timestamp.valueOf(createTime + " 23:59:59");
                System.out.println(ts);

            } catch (Exception e) {
                e.printStackTrace();
            }

            this.createTime = ts;
        }else{
            this.createTime = null;
        }
    }

    public Timestamp getEndTime() {
        return endTime;
    }
}

mybatis-generator还自动生成了一个TbItemExample类,与数据库做curd操作的时候就会使用它。
这个类太长了,就不上代码了,但是我们后面需要在它里面添加这样一个方法,方便模糊查询。

   public Criteria andOrItemsLike(String value){
            value="%"+value+"%";
            addCriterion("(title like \""+value+"\" or sell_point like \""+value+"\")");
            return (Criteria) this;
        }

商品类的mapper接口和mapper.xml文件,都是自动生成的,我没有添加任何方法,没有必要,因为与数据库交互都是使用TbItemExample类来完成。
TbItemMapper接口:

package com.yzy.yysc.manager.mapper;

import com.yzy.yysc.manager.pojo.TbItem;
import com.yzy.yysc.manager.pojo.TbItemExample;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface TbItemMapper{
    int countByExample(TbItemExample example);

    int deleteByExample(TbItemExample example);

    int deleteByPrimaryKey(Long id);

    int insert(TbItem record);

    int insertSelective(TbItem record);

    List<TbItem> selectByExample(TbItemExample example);

    TbItem selectByPrimaryKey(Long id);

    int updateByExampleSelective(@Param("record") TbItem record, @Param("example") TbItemExample example);

    int updateByExample(@Param("record") TbItem record, @Param("example") TbItemExample example);

    int updateByPrimaryKeySelective(TbItem record);

    int updateByPrimaryKey(TbItem record);

}

商品的mapper.xml文件也很长,我就不全上了,只上一个做了改动的地方,最后面。

TbItemMapper.xml:

<mapper namespace="com.yzy.yysc.manager.mapper.TbItemMapper" >
    <resultMap id="BaseResultMap" type="com.yzy.yysc.manager.pojo.TbItem">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="title" property="title" jdbcType="VARCHAR"/>
        <result column="sell_point" property="sellPoint" jdbcType="VARCHAR"/>
        <result column="price" property="price" jdbcType="BIGINT"/>
        <result column="num" property="num" jdbcType="INTEGER"/>
        <result column="barcode" property="barcode" jdbcType="VARCHAR"/>
        <result column="image" property="image" jdbcType="VARCHAR"/>
        <result column="status" property="status" jdbcType="TINYINT"/>
        <result column="created" property="created" jdbcType="TIMESTAMP"/>
        <result column="updated" property="updated" jdbcType="TIMESTAMP"/>
        <association property="itemCat" column="cid" javaType="com.yzy.yysc.manager.pojo.TbItemCat"
                     select="queryCatById"/>
    </resultMap>

    <select id="queryCatById" resultType="com.yzy.yysc.manager.pojo.TbItemCat">
        SELECT * from tb_item_cat where id=#{cid}
    </select>

商品的interface接口

package com.yzy.yysc.manager.service;

import com.yzy.yysc.common.model.DataGirdModel;
import com.yzy.yysc.manager.pojo.TbItem;
import com.yzy.yysc.manager.vo.TbItemVo;

public interface ItemService {
  
    public DataGirdModel<TbItemVo> getAllItems(TbItemVo itemVo);

}

这里要打个叉,因为我前段用的layui展示数据,所以我们还需要建一个DataGirdModel类把数据封装成layui框架可以就收的格式。
我们DataGirdModel类放在yysc-common项目中,记住也要实现序列化接口。

package com.yzy.yysc.common.model;

import java.io.Serializable;
import java.util.List;

public class DataGirdModel<T> implements Serializable {
    private Integer code=0;
    private String msg;
    private Integer count;
    private List<T> data;


    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Integer getCount() {
        return count;
    }


    public void setCount(Integer count) {
        this.count = count;
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

商品的interface接口实现类:

package com.yzy.yysc.manager.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.yzy.yysc.manager.mapper.TbItemMapper;
import com.yzy.yysc.common.model.DataGirdModel;
import com.yzy.yysc.manager.pojo.TbItem;
import com.yzy.yysc.manager.pojo.TbItemExample;
import com.yzy.yysc.manager.service.ItemService;
import com.yzy.yysc.manager.vo.TbItemVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class ItemServiceImpl implements ItemService {

    @Autowired
    private TbItemMapper itemMapper;

    @Override
    public DataGirdModel<TbItemVo> getAllItems(TbItemVo itemVo) {

      //分页查询
        Page page= PageHelper.startPage(itemVo.getPage(),itemVo.getLimit());

        DataGirdModel<TbItemVo> dgm=new DataGirdModel<>();
        TbItemExample example = new TbItemExample();
        TbItemExample.Criteria criteria= example.createCriteria();
        //模糊查询
        if(StringUtils.isNotBlank(itemVo.getKeywords())){
            criteria.andOrItemsLike(itemVo.getKeywords());
        }
      //日期条件查询
        if(itemVo.getCreateTime() != null){  
criteria.andCreatedBetween(itemVo.getCreateTime(),itemVo.getEndTime());
        }
        List<TbItem> demos = itemMapper.selectByExample(example);
        List<TbItemVo> tivolist=new ArrayList<>();
        System.out.println(demos);
        for(TbItem item:demos){
            TbItemVo tivo=new TbItemVo();
            BeanUtils.copyProperties(item,tivo);
            tivolist.add(tivo);
        }
        //注意这里,设置总条数一定要放在mapper查询的后面,不然就是0
        dgm.setCount(new Integer((int) page.getTotal()));
        dgm.setData(tivolist);
        return dgm;
    }

}


下面是web层ItemController类

package com.yzy.yysc.manager.controller;

import com.yzy.yysc.common.model.DataGirdModel;
import com.yzy.yysc.manager.pojo.TbItem;
import com.yzy.yysc.manager.pojo.TbItemCat;
import com.yzy.yysc.manager.service.ItemCatService;
import com.yzy.yysc.manager.service.ItemService;
import com.yzy.yysc.manager.vo.TbItemVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Controller
@RequestMapping("/item")
public class ItemController {

    @Autowired
    private ItemService itemService;
    @Autowired
    private ItemCatService itemCatService;

    @RequestMapping("/list")
    public String list(){
        return "item/list";
    }

    @RequestMapping("/new")
    public String newItem(Model model){

        return "item/add";
    }

    @RequestMapping("/pageData")
    @ResponseBody
    public DataGirdModel<TbItemVo> getItemByKeywords(TbItemVo itemVo){
        System.out.println(itemVo.getKeywords());
        System.out.println(itemService.getAllItems(itemVo).getCount());

        return itemService.getAllItems(itemVo);
    }


}

接下来配置service层和web层的spring配置文件,进行服务的发布和获取,测试系统间的通信是否顺畅

  • service层发布服务:
    注意添加文档约束:
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    http://code.alibabatech.com/schema/dubbo
    http://code.alibabatech.com/schema/dubbo/dubbo.xsd
    发布服务:
 <!-- 发布dubbo服务 -->
    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="yy-manager" />
    <!-- 注册中心的地址 -->
    <dubbo:registry protocol="zookeeper" 
address="192.168.245.130:2181,192.168.245.130:2182,192.168.245.130:2183" />
    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />
    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.yzy.yysc.manager.service.ItemService" 
ref="itemServiceImpl" timeout="300000"/>

service层发布服务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
         http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
       ">
        
       <!-- 将业务对象,那入spring容器 -->
       <context:component-scan base-package="com.yzy.yysc.manager.service.impl">
       </context:component-scan>
       
       <!-- 声明事务管理器对象 -->
       <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入dataSource -->
            <property name="dataSource" ref="dataSource"></property>
       </bean>
       
       <!-- 声明事务切面 -->
       <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="save*" propagation="REQUIRED" />
                <tx:method name="add*" propagation="REQUIRED" />
                <tx:method name="update*" propagation="REQUIRED" />
                <tx:method name="delete*" propagation="REQUIRED" />
                <tx:method name="get*" propagation="REQUIRED"  read-only="true"/>
                <tx:method name="load*" propagation="REQUIRED"  read-only="true"/>
                <tx:method name="user*" propagation="REQUIRED"/>
                
                <tx:method name="*" propagation="REQUIRED"  read-only="true"/>
                
            </tx:attributes>
       </tx:advice>
       
       
       <!-- 进行aop配置  -->
       <aop:config>
            <aop:pointcut expression="execution(* com.yzy.yysc.manager.service.impl.*.*(..))" id="pc"/>
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
       </aop:config>

    <!-- 发布dubbo服务 -->
    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="yy-manager" />
    <!-- 注册中心的地址 -->
    <dubbo:registry protocol="zookeeper" 
address="192.168.245.130:2181,192.168.245.130:2182,192.168.245.130:2183" />
    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />
    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="com.yzy.yysc.manager.service.ItemService" 
ref="itemServiceImpl" timeout="300000"/>

</beans>

  • web层获取服务
    同样注意添加文档约束:
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    http://code.alibabatech.com/schema/dubbo
    http://code.alibabatech.com/schema/dubbo/dubbo.xsd
    引用服务:
 <!-- 引用dubbo服务 -->
    <dubbo:application name="yy-manager-web"/>
    <dubbo:registry protocol="zookeeper" 
 address="192.168.245.130:2181,192.168.245.130:2182,192.168.245.130:2183" />

    <dubbo:reference 
    interface="com.yzy.yysc.manager.service.ItemService" id="itemService" />
 
image.png
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
       ">
       <context:component-scan base-package="com.yzy.yysc.manager.controller"></context:component-scan>
         
       <!-- 
            springmvc的注解驱动
        -->
       <mvc:annotation-driven></mvc:annotation-driven>
       
       
      <!--  配置二进制流解析器
       <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
          &lt;!&ndash; 设置允许上传的文件最大的尺寸 &ndash;&gt;
            <property name="maxUploadSize" value="10240000"></property>
            &lt;!&ndash; 设置文件上传过程中,中文的编码 &ndash;&gt;
            <property name="defaultEncoding" value="UTF-8"></property>
            &lt;!&ndash; 设置文件上传的保存目录 &ndash;&gt;
            <property name="uploadTempDir" value="/upload"></property>
       </bean>-->

    <mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>
    <mvc:resources location="/WEB-INF/css/" mapping="/css/**"/>
    <mvc:resources location="/WEB-INF/images/" mapping="/images/**"/>
    <mvc:resources location="/WEB-INF/zTree/" mapping="/zTree/**"/>
    <mvc:resources location="/WEB-INF/layui/" mapping="/layui/**"/>
    <mvc:resources location="/WEB-INF/jsp/" mapping="/jsp/**"/>

    <!-- 配置视图解析器-->
      <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"></property>
            <property name="suffix" value=".jsp"></property>
      </bean>

    <!-- 引用dubbo服务 -->
    <dubbo:application name="yy-manager-web"/>
    <dubbo:registry protocol="zookeeper" 
address="192.168.245.130:2181,192.168.245.130:2182,192.168.245.130:2183" />

    <dubbo:reference 
   interface="com.yzy.yysc.manager.service.ItemService" id="itemService" />

</beans>

这是前端的结构,记住导入layui和zTree树

image.png

商品类的列表页面和js文件


image.png

list.jsp

<%--
  Created by IntelliJ IDEA.
  User: yangoctopus
  Date: 2018/10/11
  Time: 21:04
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>商品管理</title>
    <link rel="stylesheet" href="/layui/css/layui.css">
    <script type="text/javascript" src="/js/jquery.js"></script>
    <script type="text/javascript" src="/js/jquery.form.min.js"></script>
    <script type="text/javascript" src="/layui/layui.js"></script>
    <script type="text/javascript" src="/js/item/itemManager.js"></script>

</head>
<body>
<div class="layui-card">
    <div class="layui-card-body">
        <form class="layui-form" id="queryForm" method="post">
            <div class="layui-form-item">
                <div class="layui-col-md11">
                    <label class="layui-form-label" style="width: auto">模糊查询</label>
                    <div class="layui-input-inline">
                        <input style="width: 250px" type="text"  name="keywords" id="key" class="layui-input" placeholder="请输入商品标题或卖点">
                    </div>

                    <div class="layui-inline" style="margin-left: 50px">
                        <label class="layui-form-label" style="width: auto">入库日期</label>
                        <div class="layui-input-inline" style="width: 130px;">
                            <input type="text" name="createTime" id="createTime"  class="layui-input" autocomplete="off">
                        </div>
                    </div>

                    <div class="layui-btn-group">
                        <button type="button" lay-submit class="layui-btn" lay-filter="sub" lay-submit="" id="queryPage" >查询</button>
                        <button type="button"  class="layui-btn" id="new"><i class="layui-icon">&#xe638;</i>新建</button>
                    </div>

                </div>
            </div>

        </form>
        <div class="contain-img-table"><table id="dataTable" lay-filter="dataTable"></table></div>
    </div>
</div>

</body>
<script type="text/html" id="barDemo">
    <a class="layui-btn layui-btn-primary layui-btn-xs" lay-event="detail">查看</a>
    <a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
</script>

</html>

itemManager.js文件

/*
通过加载事件,发送ajax请求,加载商品列表
*/
$(document).ready(function(){

    layui.use(['laydate', 'table', 'layer', 'form','jquery'], function () {


        var laydate = layui.laydate, table = layui.table, layer = layui.layer, form = layui.form,$= layui.jquery;
        form.render();
        //日期
        laydate.render({
            elem: '#createTime',
            type: 'date'
        });

        var dataTable = table.render({
            elem: '#dataTable',
            url: '/item/pageData', //数据接口
            page: true, //开启分页
            method:'post',
            cols: [[ //表头
                {field: 'id', align: 'center',width : 80,  title: '商品id'},
                {field: 'title', align: 'center',width : 120, title: '商品标题'},
                {field: 'sellPoint', align: 'center',width : 120, title: '商品卖点'},
                {field: 'price', align: 'center', width : 100,title: '商品价格'},
                {field: 'num', align: 'center',width : 80, title: '库存'},
                {align: 'center',width : 100, title: '所属类目',templet:function(d){
                        return d.itemCat.name;
                    }},
                {field: 'status', align: 'center', width : 90,title: '商品状态',templet: function (d) {
                        if(d.status===1){
                            return '正常';
                        }else if(d.status===2){
                            return '下架';
                        }else{
                            return '删除';
                        }

                    }},
                {field: 'created', align: 'center',width : 120, title: '入库时间',templet:function(d){

                        return createTime(d.created);
                    }},
                {field: 'updated', align: 'center',width : 120, title: '最后更新时间',templet:function(d){

                        return createTime(d.updated);
                    }},
                {fixed: 'right',title: '操作', align: 'center',width : 160, toolbar:'#barDemo'}
            ]]

        });

        window.reloadTable = function() {

            var fields = {};
            $.each($('#queryForm').serializeArray(), function(i, field){
                fields[field.name] = field.value;
            });

            dataTable.reload({
                where: fields
            });

            //$("#detai").click(tableOn())

        }
        window.tableOn = function(){

            table.on('tool(dataTable)', function(obj){ //注:tool是工具条事件名,test是table原始容器的属性 lay-filter="对应的值"
                var data = obj.data; //获得当前行数据
                var layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)
                var tr = obj.tr; //获得当前行 tr 的DOM对象

                if(layEvent === 'edit'){ //编辑
                } else if(layEvent === 'del'){ //删除
                    layer.confirm('真的删除行么', function(index){
                        $.ajax({
                            url: '/traceMushrootDibgrow/delete',
                            data: {id: data.id},
                            async: false,
                            success: function (data, status, xhr) {
                                if (data.success) {
                                    layer.close(index);
                                }
                                reloadTable();
                            }
                        });
                    });
                } else if(layEvent === 'detail'){ //查看
                    var img = "<div><img src='/images/11.jpg' height='330'/><br/>data.barcode</div>"
                            layer.open({
                                type: 1,
                                title: '商品图片',
                                offset: ['10px', '230px'],
                                area:['600px', '400px'],
                                content: img //注意,如果str是object,那么需要字符拼接。

                            });

                    //同步更新缓存对应的值
                    // obj.update({username: '123',title: 'xxx'});
                }
            });
        }

        $('#new').click(function () {
            $.ajax({
                url: '/item/new',
                async: false,
                success: function (data, status, xhr) {
                    layer.open({
                        type: 1,
                        title:"商品入库",
                        offset: ['10px', '50px'],
                        area: ['1000px', '500px'],
                        content: data //注意,如果str是object,那么需要字符拼接。

                    });

                }

            });
        });

        $('#queryPage').click(reloadTable);

        // form.render('table','data');
        tableOn()

    });
});

function createTime(v){
    var date = new Date(v);
    var y = date.getFullYear();
    var m = date.getMonth()+1;
    m = m<10?'0'+m:m;
    var d = date.getDate();
    d = d<10?("0"+d):d;
    var h = date.getHours();
    h = h<10?("0"+h):h;
    var M = date.getMinutes();
    M = M<10?("0"+M):M;
    var str = y+"-"+m+"-"+d+" "+h+":"+M;
    return str;
}

最后,顺序启动项目进行测试。
一、启动yysc-manager-service项目,进行服务注册。
二、启动yysc-manager-web项目,进行服务获取。
效果:

初始页面:
初始页面

模糊查询:
模糊查询

日期查询:
日期查询

关键字+日期多条件查询:
关键字+日期多条件查询
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容