py-19-JTWEB01

京淘1


目录:

day01:京淘- Maven继承聚合

day02:京淘-通用Mapper项目框架搭建

day03:京淘-导入页面,easyUl,实现商品列表查询

day04:京淘-后台系统实现上架下架

day05:京淘-描述表,图片上传, nginx反向代理

day06:京淘-Tomcat集群, Nginx负载均衡,Linux部署

day07:京淘-数据库主从复制amoeba读写分离

day08:京淘-数据库双机热备Mycat读,写分离     

day09:京淘-Redis分片,商品类目缓冲,实现

day10:京淘-Redis哨兵

day11:京淘-Redis集群搭建

day12:京淘-实现跨域、商品详情展现

day13:登录、登出、加入购物车

day14:购物车数量修改


day01: 京淘-Maven继承聚合

今日任务:

  1. 京淘项目,学习意义,业务背景

  2. 搭建环境,画京淘项目系统架构图(基于注解+Maven构建项目+SSM)

学习意义,就业高薪!

先掌握能快速消化,而且面试官不擅长!

学习高薪4/5/6阶段

网络布局图,22台服务器,能近似当当商城(中型商城)

从后台开始一台服务开始,扩充到6个子系统(后台、前台、单点登录技术、购物车、订单、全文检索)

涉猎技术,层层优化

第四阶段:小京淘(中型项目规模)

  1. nginx tomcat(并发数100~150)集群实现负载均衡(配置文件,4句)

  2. 数据库优化,Mysql主从复制(配置文件),读写分离(amoeba/mycat阿里)(配置xml文件)

  3. 缓存redis内存的数据库nosql代表。核心代码10行

第五阶段:大京淘(大型项目、超大型项目)

     4.全文检索技术 lucene+solr离线/elestaicSerach(es)实时(大数据)支持海量数据(亿条数据            量之上)搜索工程师岗位!

     5.消息队列RabbitMQ,软件层面架构优化最后一招 Kakfa MQ大数据

     6.2017 DevOpts开发运维一体化 Docker(go谷歌、解决高并发和编译速度)容器化

     7.微服务Dubbo(rpc)和springboot+springcloud

第六阶段:大数据,基于网络流量日志分析PV、UV、VV等指标的统计

7天 Hadoop(java)、HDFS、hive、kafka、zookeeper、flume、storm(spark scala编译完java)。。

学习方法:

  1. why?(读百文,“走马观花” 碎片学习法:今日头条,百度demo)

  2. 把应用层先掌握(安装、配置、完成业务模块CRUD)

  3. 业务(唛头marks 物流)

  4. 深入底层(高级程序员、架构师,jvm优化,hash工作原理,tomcat优化)

大多数同学初级程序员!

京淘(京东、淘宝一网打尽)

典型电子商务系统,大型互联网架构

典型学习四类技术:分布式(集群)、高并发、高可用、海量数据

系统架构图:

完成业务:User用户表查询

开发步骤:

  1. 创建数据库,创建User表,录入数据(sql给你,导入)

  2. 持久层:pojo类User.java,注解JPA

  3. Mybatis:基于接口开发 UserMapper.xml,项目引入mybatis自动生成SQL语句(单表CRUD)插件,SysMapper(工具类,写好了)接口UserMapper.java,mybatis-config.xml

  4. 业务层:UserService接口、UserServiceImpl实现类、配置文件applicationContext*.xml

  5. 控制层:UserController+Jsp(WEB-INF/views/*.jsp)

怎么导入数据

jtdb.sql

防止中文乱码

练习:

利用备份文件jtdb.sql创建数据库和表和数据

该文件寻贴主得

 最终检查表中要无乱码

准备工作的环境

  1. eclipse创建一个新的工作空间+git(注册账号+外网)

2.项目的编码,默认gbk,修改utf-8

3.配置jdk运行环境,jdk1.7,jdk1.8

4.配置Maven环境,Maven创建工程,高级:继承和聚合

Settings.xml修改内容

  1. 修改存放路径

  2. 修改镜像仓库(私服)http://maven.tedu.cn/nexus/content/groups/public/

Maven继承、聚合(大型)

传统项目,大而全,部署也是在一起war。缺点:类多了业务多了,编译时间变长,部署或者发布时间变长。javaWeb项目频繁启动tomcat中间件和停止的时间非常长,debug调试。

团队开发git、svn版本控制(1.0,1.1.。。。)

运行war包变小,tomcatMaven插件,执行速度

主流新的方式,项目拆分,后台、前台

如何拆分我们的项目?

把传统的项目中的业务模块升级为项目,将来就有很多项目,这种分拆形式称为垂直拆分。

把一个项目又进行分层,这种拆分方式称为水平拆分!

把原来一个大项目拆分成多个小项目会发生什么问题呢?

  1. 原来是我的类之间的调用变成两个系统间的调用,扩展可能会变成两个服务器之间调用(调用jar包)

  2. 分开后各自项目会调用公用的一些jar包(继承)

  3. 工具类,多个项目要调用公用工具类(依赖)

  4. Jar包版本升级,传统一个项目升级还容易些,很多子项目,每个项目都需要升级。(继承)

  1. 需要一个父工程,统一管理“所有”的jar,但是可以有特殊的 (jt-parent,pom.xml)

  2. 全局工具类工程,其他项目依赖(jt-common)

后台项目:jt-manage(pojo、mapper、service、controller)

     3.水平分拆 jt-manage-pojo、jt-manage-mapper、jt-manage-service、jt-manage-web

总共有:

Jt-parent 父工程

  1. Maven工程类型:pom

  2. pom.xml管理“所有”的jar

  3. 版本控制

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jt</groupId>
<artifactId>jt-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>

<!-- 统一配置版本信息 -->
<properties>
<junit.version>4.10</junit.version>
</properties>

<!-- 添加junit依赖 -->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
</project>

Jt-common 工具类工程

浏览文件所在目录

Jt-manage 特殊的父工程,聚合工程

在jt-parent导入jar包

<build>
<finalName>jt-parent</finalName>
</build>
<!-- 集中定义依赖版本号 -->
<properties>
<junit.version>4.10</junit.version>
<spring.version>4.1.3.RELEASE</spring.version>
<mybatis.version>3.2.8</mybatis.version>
<mybatis.spring.version>1.2.2</mybatis.spring.version>
<mybatis.paginator.version>1.2.15</mybatis.paginator.version>
<mysql.version>5.1.32</mysql.version>
<bonecp-spring.version>0.8.0.RELEASE</bonecp-spring.version>
<druid.version>1.0.9</druid.version>
<mapper.version>2.3.2</mapper.version>
<pagehelper.version>3.4.2</pagehelper.version>
<jsqlparser.version>0.9.1</jsqlparser.version>
<slf4j.version>1.6.4</slf4j.version>
<jstl.version>1.2</jstl.version>
<servlet-api.version>2.5</servlet-api.version>
<jsp-api.version>2.0</jsp-api.version>
<joda-time.version>2.5</joda-time.version>
<commons-lang3.version>3.3.2</commons-lang3.version>
<commons-io.version>2.4</commons-io.version>
<commons-fileupload.version>1.3.1</commons-fileupload.version>
<jackson.version>2.4.2</jackson.version>
<httpclient.version>4.3.5</httpclient.version>
<jedis.version>2.6.0</jedis.version>
<druid.version>1.0.29</druid.version>
</properties>
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<dependency>
<groupId>com.github.miemiedev</groupId>
<artifactId>mybatis-paginator</artifactId>
<version>${mybatis.paginator.version}</version>
</dependency>
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--引入阿里druid监控 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- 通用Mapper -->
<dependency>
<groupId>com.github.abel533</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>${jsqlparser.version}</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.jolbox</groupId>
<artifactId>bonecp-spring</artifactId>
<version>${bonecp-spring.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- Jackson Json处理工具包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.3.1</version>
</dependency>
<!-- 消息队列 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<!-- JSP相关 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp-api.version}</version>
<scope>provided</scope>
</dependency>
<!-- 时间操作组件 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
<!-- Apache工具组件 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<!-- 文件上传组件 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
</dependency>
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<!-- 字符加密、解密 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
<!-- 数据校验 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
</dependencies>

报错处理

该文件寻贴主得

第一步

删除jt-common项目中的报错类

Jt-manage-pojo 后台管理系统的pojo子项目

Jt-manage-mapper 只有映射的接口,映射文件jt-manage-web

Jt-manage-service 业务层

Jt-manage-controller/web controller/jsp/资源文件 三大框架配置文件、Mybatis映射文件、静态资源image/js/css/html。。。

搭建环境:

Maven继承、聚合

  1. 继承,在子工程有一个出现<parent>标签

  2. 子工程<groupId><version>消失,它是自动拿父工程的groupId和version

  3. 继承管理整个系统的所有的jar,管理jar版本

  4. 聚合工程有什么特点?类型pom,聚合工程特殊标签<modules>;一键构建(子工程)


day02:京淘-通用Mapper项目框架搭建

知识回顾:

1 )传统架构: tomcat/weblogic/webspare+oracle
2 )小型项目架构: tomcat+mysql+jdbc
                                     tomcat+mysql+struts1/2+ EJB/hibernate(hql)(ssh)
                                     tomcat+mysql+struts2+spring+mybatis
                                     tomcat+mysql+springmvc+spring+mybatis(ssm)
3 )互联网架构
百万级并发:对传统 SSM 框架进行优化
Nginx 负载均衡 +Redis 顶级内存数据库 nosql+MyCat mysqlProxy
亿级并发:对传统 SSM 框架进行优化
RibbitMQ 消息队列架构松耦合 +Dubbo 微服务 RPC 远程过程调用 NIO+ 全文检索 lucene+solr/es+Docker 容器化技术
4 )微服务
微服务: springboot+springcloud

项目构建:

  1. 传统项目大而全,所以的业务功能在一个系统中
  2. 大型项目构建,业务垂直拆分和水平拆分

垂直拆分

  1. jt-parent 公用项目,统一全局管理几乎所有的 jar 和版本
  2. jt-common 公用工具类,各个项目都可以进行访问
  3. jt-manage 后台项目使用聚合,它只管理它的子项目,其他什么都不管,一键构建

水平拆分(后台)

  1. jt-manage-pojo 只管理存放 pojo
  2. jt-manage-mapper 只管理 mapper 接口
  3. jt-manage-service 只管理接口和实现类
  4. jt-manage-web/controller web 项目管理 controller/jsp/ 静态资源

关系:

  1. 继承
  2. 聚合
  3. 依赖

Maven工程类型:常用下面三种

  1. pom 父工程、聚合工程 <module>
  2. jar java 工程,只有类 jt-common …pojo/mapper/service
  3. war javaWeb 工程,有 jsp/controller(request/response)

今天任务:

  1. 实现框架用户信息查询
开发步骤:
通用 Mapper ,它是 mybatis 的一个插件,拦截器,实现单表 CRUD 操作无需写 SQL 语句
SysMapper 简单封装了通用 Mapper ,增加了一个方法,批量删除
BasePojo 类:实现两个日期
实现序列化,将来可能远程调用,持久化保存到文件中,缓存框架
User pojo 类中增加了 JPA java 持久化 api 注解,只需要记住 3 个就可以
业务层基类, BaseService ,就注入 SysMapper ,实现基础的 CRUD ,实现类就必须继承这个 BaseService
JPA SunJavaEE 规范, java 持久化 API ,实质就是一些注解,解决: java 对象和数据库表之间映射
Java                          
User                             user
主键: id                    id
属性 :username         字段: user_name  
怎么配置 dtd 文件?
  1. 先要找到 dtd 文件,存放在本地
2.Eclipse 配置 dtd 支持
3.把 xml 关闭,重新开启才生效
练习:
  1. 配置 dtd 环境
  2. 编写 pojo ,增加 JPA 注解
  3. 实现映射文件和接口文件
  4. 实现 service 接口和映射文件
配置 tomcat 插件
  1. pom.xml 中增加插件支持
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8091</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
2.配置启动项

通用Mapper怎么自动产生sql语句的?

可以通过反射拿到注解
select id,name,birthday,address from user;
  1. 如何获得表名 @Table
  2. 如何获得所有的字段,反射获得所有类的属性(全局驼峰规则配置)

细化问题:

  1. 包扫描时 com.jt 这样 controller 会不会被扫描到?
会的,扫描只要有 @Service@Controller @Component )的都会被扫描进去
扫描是扫描进去了,但是这个都是僵尸!

 2.如何获取数据

Controller 的方法去调用 service 方法, UserServiceImpl 中并没有实现,去它的父类中找, BaseService 中有,就执行, BaseService 调用 SysMapper SysMapper 是封装通用 Mapper
最后通用 Mapper 调用它底层方法实现 mybatis mysql 访问。

业务:后台管理业务,商品分类查询

SELECT * FROM tb_item_cat
WHERE NAME LIKE '% 家用电器 %'
  1. 排序
SELECT * FROM tb_item_cat
ORDER BY parent_id,sort_order

 2.不能使用通用Mapper

开发步骤:

  1. 创建 ItemCat.java+JPA 注解 pojo
package com.jt.manage.pojo;
import java.util.Date;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;
importcom.jt.common.po.BasePojo;
@Table(name="tb_item_cat")
public class ItemCat extends BasePojo{
/**
*
*/
private static final long serialVersionUID = -3043860998664994955L;
//类目ID
@GeneratedValue(strategy=GenerationType.IDENTITY)
private  Integer id;

//父类目ID=0时,代表的是一级的类目
private  Integer parentId;

//类目名称
private  String name;

//状态。可选值:1(正常),2(删除)
private  Integer status;

//排列序号,表示同级类目的展现次序,如数值相等则按名称次序排列。取值范围:大于零的整数
private  Integer sortOrder;

//该类目是否为父类目,1为true,0为false
private  Integer isParent;

//创建时间
private  Date created;

//创建时间
private  Date updated;

getSet方法。。。

}

2、创建映射文件ItemCatMapper.xml,必须写SQL语句

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jt.manage.mapper.ItemCatMapper">
<!-- 查询所有的商品  待排序字段 -->     
<select id="findAll" resultType="ItemCat">
SELECT * FROM tb_item_cat
ORDER BY parent_id,sort_order
</select>

</mapper>

3、创建接口ItemCatMapper.java

package com.jt.manage.mapper;
import java.util.List;
import com.jt.manage.pojo.ItemCat;
public interface ItemCatMapper {
/**
* 查询所有的商品
* @return
*/
public List<ItemCat> findAll();
}

4、创建业务层接口ItemCatService和实现类ItemCatServiceImpl

package com.jt.manage.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.jt.common.service.BaseService;
import com.jt.manage.mapper.ItemCatMapper;
import com.jt.manage.pojo.ItemCat;
@Service
public class ItemCatServiceImpl extends BaseService<ItemCat> implements ItemCatService{
@Autowired
private ItemCatMapper itemCatMapper;

/**
* 查询所有的商品
*/
public List<ItemCat> findAll() {
return itemCatMapper.findAll();
}
}

5、创建控制层ItemCatController

package com.jt.manage.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.jt.manage.pojo.ItemCat;
import com.jt.manage.service.ItemCatService;
@Controller
@RequestMapping("/item/cat")
public class ItemCatController {

@Autowired
private ItemCatService itemCatService;

@RequestMapping("/all")
@ResponseBody
public List<ItemCat> findAll(){
List<ItemCat> itemCatList = itemCatService.findAll();
return itemCatList;
}
}

6、直接访问测试

返回json

效果:

 mysql分页

不同数据库是不同分页方式

Oracle 分页,虚拟列rownum,通过虚拟列实现分页

SqlServer 分页,top关键字 select top 5 from user

Mysql 分页,select * from user limit 10,20

10代表从第11条记录开始,20每页20条记录

引入PageHelper对象,PageInfo,共同封装分页值,使用mybatis拦截器

  1. mybatis-config.xml配置插件

<!-- 分页插件:com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 方言 -->
<property name="dialect" value="mysql" />
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询,查询数据总条数 -->
<property name="rowBoundsWithCount" value="true" />
</plugin>

2.改造查询,在service不能直接使用返回列表

/**
* 查询所有的商品
*/
public List<ItemCat> findAll(Integer page,Integer rows) {
//return itemCatMapper.findAll();

//分页支持,startPage方法是静态
//内部就调用拦截器,startPage相当于事务开启begin,开启分页操作
//它下面第一条的执行的查询的SQL语句
PageHelper.startPage(page, rows);
//第一条查询SQL被拦截,SQL语句拼接 limit page, rows
List<ItemCat> itemCatList = itemCatMapper.findAll();
//返回值不能直接返回必须放在PageInfo对象中
//这里和线程安全有关!直接返回方式它会产生线程安全问题
//怎么解决?利用ThreadLocal,把当前对象和当前线程绑定,每个用户独立线程,
PageInfo <ItemCat> pageInfo = new PageInfo<ItemCat>(itemCatList);
return pageInfo.getList();
}

效果:

3. 分页参数,page查询哪页数据,rows每页条数

@RequestMapping("/all/{page}/{rows}")
@ResponseBody
public List<ItemCat> findAll(@PathVariable Integer page,@PathVariable Integer rows){
List<ItemCat> itemCatList = itemCatService.findAll(page,rows);
return itemCatList;
}

小结:

1.整个采用注解方式,JPA注解@Table,@Id,@GeneratedValue=”IDENTITY”自增主键  @Column配置全局setting驼峰规则就无需使用

         在Service使用@Service

         在Controller使用@Controller

2.使用包扫描方式:

       a.pojo jpa注解通用Mapper

       b.mapper接口,mybatis调用,spring和mybatis整合,扫描service,注入mapper,

       c.service 包扫描@Service。@Controller没用

       d.controller 包扫描@Controller

3.过程:

      a.Pojo+JPA     ItemCat.java

      b.映射文件 ItemCatMapper.xml

      c.映射接口 ItemCatMapper.java 位置=namespare.ItemCatMapper

      d.实现类 ItemCatServiceImpl 注入ItemCatMapper,调用接口方法

      e.接口ItemCatService 把实现所有方法声明

      f.ItemCatController 注入ItemCatService,调用它的方法

4.配置文件加载过程

      a.web.xml 配置DispatcherServlet决中文乱码过滤器filter servlet加载所有的

      b.applicationContext*.xml spring框架文件都被加载,springmvc也被加载

     c.applicationContext.xml spring核心配置文件,加载数据源,sqlSessionFactoryBean,事务,和mybaits整合

      d.mybatis-config.xml 全局配置,插件:通用Mapper插件,分页插件

      e.applicationContext-mvc.xml springmvc的整合配置文件,注解方式,包扫描,扫描所有的controller,内部资源视图解析:

          prefix(/WEB-INF/views/)+logicName(userList)+suffix(.jsp)

         在体系中创建pojo/mapper接口/service/controller都是spring bean,@Autowired这些注解对象都注入到对象当中

怎么访问?在controller中写入@RequestMapping(类/方法名称),用户在浏览器上敲入地址,去匹配系统中对于controller方法,把页面的参数封装到方法到参数中,方法调用service方法。

返回值:

  1. 普通常见方式,返回对象给jsp页面;Model把结果封装到model对象中,本质创建变量放入request中,转向Jsp页面,Jsp通过jstl标签吧把数据从request中获取。通过el表达式${name}

  2. Ajax请求,返回json格式。返回就是业务操作完成数据List<ItemCat>,只要增加一个注解@ResponseBody,springmvc内部支持,它有这个注解标识,调用时自动把java对象转成string字符串,字符串格式json

分页插件

  1. mybati-config.xml中声明插件,本质是一个拦截器。在你执行SQL时进行拦截,加上sql关键字 limit page,rows。执行完成后就是一个分页结果了。

  2. 注意:PageHelper对象,静态方法startPage(page,rows),标识它下面的第一个查询语句增加分页功能。startPage它只是标识开启,并没有实质执行。

  3. 业务执行查询,实际这个业务查询已经被拦截形成分页查询条件

  4. 返回值不能直接返回,必须封装到一个PageInfo对象中,为了高并发下线程安全,ThreadLocal<Page>实现线程安全,它把数据没有共享,私有了?把每个请求的数据跟本地线程绑定,就不会线程数据共享,操作系统底层实现是线程各自的隔离的。

ThreadLocal它比加锁方式效率高。

5)PageInfo。Total可以获取页面总数,底层再次发出一个请求


Day03:京淘-导入页面, easyUl,实现商品列表查询

1.京淘架构重构

  1. 项目拆分

    1. 修改水平拆分方式

在/jt-manager-web/src/main/resources/spring/applicationContext-mvc.xml中添加放行静态资源

<!-- 放行静态资源 -->
<mvc:default-servlet-handler/>

问题:

   超级大型的项目才会 项目水平拆分.而且项目测试时需要频繁的打包,开发效率太低.

问题2.如果新建项目路径不全 怎么办?

解决办法:修改JDK即可.

2.项目重新构建

  1. 添加继承和依赖

2.添加tomcat插件

<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8091</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>

3.配置tomcat插件启动项

启动测试:

写com.jt.manage.controller类实现index首页访问

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping("/index")
public String index() {
return "index";
}
}

效果:

2.EasyUI介绍

1.介绍

  1. 文件导入

该文件寻贴主得

2.拖拽

打开jt-manages/src/main/webapp/easy-ui/easyui-1.html

  1. 导入js

<script type="text/javascript"
src="/js/jquery-easyui-1.4.1/jquery.min.js"></script>
<!--easyUI的js主文件  -->
<script type="text/javascript"
src="/js/jquery-easyui-1.4.1/jquery.easyui.min.js"></script>
<!--国际化的js文件  -->
<script type="text/javascript"
src="/js/jquery-easyui-1.4.1/locale/easyui-lang-zh_CN.js"></script>
<!--引入easyUI的样式  -->
<link rel="stylesheet" type="text/css"
href="/js/jquery-easyui-1.4.1/themes/icon.css" />
<link rel="stylesheet" type="text/css"
href="/js/jquery-easyui-1.4.1/themes/default/easyui.css" />

2.编辑页面

<body>
<div class="easyui-draggable">拖动DIV</div>
<div class="easyui-draggable">测试div</div>
</body>

3.进度条

  1. 页面js

$(function(){
$("#b").click(function(){
onload();
})
/*采用递归的方法实现进度条刷新  */
var i = 0;
function onload(){
$('#p').progressbar({ value:i++});
if(i<=100){
/*js原生提供的函数,延时加载
参数1.延时加载的动作
参数2.延时加载的时间  单位毫秒
*/
setTimeout(function(){
onload();
}, 1)
}
}
})

2.页面代码

<body>
<div id="p" class="easyui-progressbar" style="width:400px;"></div>
<input id="b" type="button" value="加载"/>
</body>

4.弹出框

<!--主要展现弹出框  -->
<a id="btn1" href="#" class="easyui-linkbutton" data-options="iconCls:'icon-remove'">搜索</a>
<div id="win1"></div>
<!--定义弹出窗口  -->
<div id="win2" class="easyui-window" title="My Window" style="width:600px;height:400px" data-options="iconCls:'icon-save',modal:true">
我是一个窗口
<a id="btn3" href="#" class="easyui-linkbutton" data-options="iconCls:'icon-back'">关闭</a>
</div>
<div style="float: right">
<a id="btn4" href="#" class="easyui-linkbutton" data-options="iconCls:'icon-cancel'">退出系统</a>
</div>

5.表格样式

总结:easyUI中如果需要进行表格数据的展现.则需要将返回值与属性的定义必须一一对应.

<th data-options="field:'code',width:100">Code</th>
<th data-options="field:'name',width:100">Name</th>
<th data-options="field:'price',width:100,align:'right'">Price</th>
回传JSON

  1. 写死的表格

  2. Js动态的获取数据

  3. 通过js生成完整的表格

6.EasyUI-tree

<script type="text/javascript">
/*通过js创建树形结构 */
$(function(){
$("#tree").tree({
url:"tree.json",      //加载远程JSON数据
method:"POST",        //请求方式  POST
animate:true,         //表示显示折叠端口
checkbox:true,        //表述复选框
lines:true,           //表示显示连接线
dnd:true,          //是否拖拽
onClick:function(node){  //添加点击事件
//控制台
console.info(node);
}
});
})
</script>
</head>
<body>
<h1>EasyUI-树形结构</h1>
<ul id="tree"></ul>
</body>

2.商品后台页面展现

  1. 布局技术

<body class="easyui-layout">
<div id="cc" class="easyui-layout" style="width:600px;height:400px;">  
<div data-options="region:'north',title:'North Title',split:true" style="height:100px;"></div>  
<div data-options="region:'south',title:'South Title',split:true" style="height:100px;"></div>  
<div data-options="region:'east',iconCls:'icon-reload',title:'East',split:true" style="width:100px;"></div>  
<div data-options="region:'west',title:'West',split:true" style="width:100px;"></div>  
<div data-options="region:'center',title:'center title'" style="padding:5px;background:#eee;"></div>  
</div> 

2.树形结构

<ul class="easyui-tree">
<li><span>商品管理</span>
<ul>
<li><a href="/page/item-list">商品查询</a></li>
<li>商品新增</li>
<li>商品更新</li>
</ul>
</li>
<li><span>内容管理</span>
<ul>
<li>内容新增</li>
</ul>
</li>
</ul>

3.选项卡技术

<body class="easyui-layout">
<div data-options="region:'west',title:'菜单',split:true" style="width:10%;">
<ul class="easyui-tree">
<li>
<span>商品管理</span>
<ul>
<li><a onclick="addTab('商品新增','/item-add')">商品新增</a></li>
<li><a onclick="addTab('商品查询','/item-list')">商品查询</a></li>
<li><a onclick="addTab('商品更新','/item-update')">商品更新</a></li>
</ul>
</li>
<li>
<span>网站内容管理</span>
<ul>
<li>内容新增</li>
<li>内容修改</li>
</ul>
</li>
</ul>
</div>
<div id="tt" class="easyui-tabs" data-options="region:'center',title:'首页'"></div>
</body>
<script type="text/javascript">
function addTab(title, url){ 
if ($('#tt').tabs('exists', title)){ 
$('#tt').tabs('select', title); 
} else { 
var content = '<iframe scrolling="auto" frameborder="0"  src="http://www.baidu.com" style="width:100%;height:100%;"></iframe>'; 
$('#tt').tabs('add',{ 
title:title, 
content:content, 
closable:true 
}); 
} 
}
</script>

标签作用: 发起请求.用户页面展现

<iframe scrolling="auto" frameborder="0"  src="http://www.baidu.com" style="width:100%;height:100%;"></iframe>

3.页面通用跳转

  1. 问题

说明:如果有一个页面请求需要在Controller中需要添加一个requstMapping方法.

<li data-options="attributes:{'url':'/page/item-add'}">新增商品</li>
<li data-options="attributes:{'url':'/page/item-list'}">查询商品</li>
<li data-options="attributes:{'url':'/page/item-param-list'}">规格参数</li>

2.实现思路

说明:使用restFul结构实现页面的通用跳转

编辑Controller方法

//实现页面的通用跳转
@RequestMapping("/page/{moduleName}")
public String module(@PathVariable String moduleName){
return moduleName;
}

4.商品列表展现

  1. 编辑pojo对象

2.页面请求分析

  1. url

2.页面JS

data-options="singleSelect:false,collapsible:true,pagination:true,url:'/item/query',method:'get',pageSize:20,toolbar:toolbar">

3.格式要求

说明:因为使用EasyUI的表格用于数据展现.所以返回的数据有特殊的格式要求.返回的数据的属性必须与页面中定义的属性一致,否则数据不能回显

4.封装对象

说明:根据EasyUI返回数据的要求,定义一个vo对象

5.编辑Controller

@Controller
@RequestMapping("/item")
public class ItemController {
@Autowired
private ItemService itemService;
//要求:/find/itemAll要求返回全部商品信息 要求根据修改时间排序
/* @RequestMapping("/find/itemAll")
@ResponseBody
public List<Item> findItem(){
return itemService.findItemAll();
}*/
//http://localhost:8091/item/query?page=1&rows=50
//实现商品分页查询
@RequestMapping("/query")
@ResponseBody
public EasyUIResult findItemByPage(Integer page,Integer rows){
return itemService.findItemByPage(page,rows);
}
}

6.编辑Service

@Override

public EasyUIResult findItemByPage(Integer page, Integer rows) {

/**

* 通用Mapper 查询操作时  如果传入的数据不为null,则会充当where条件

* select count(*) from tb_item

* select * from tb_item limit 0,20

s        select * from tb_item limit 20,20

s          select * from tb_item limit 40,20

*/

int total = itemMapper.selectCount(null);



int start = (page - 1) * rows;



List<Item> itemList = itemMapper.findItemByPage(start,rows);



EasyUIResult result = new EasyUIResult(total, itemList);



return result;

}

7.编辑Mapper接口

public interface ItemMapper extends SysMapper<Item>{

List<Item> findItemAll();

/**

* mybatis中不允许多值传参,必须将多值封装为单值

* 1.封装为对象

* 2.封装为Map  @Param()

* 3.封装为 array或者List

*

* $符:只有以字段名称为参数时才使用$.除此之外都是用#号因为有预编译的效果

* 防止sql注入攻击.

*  说明: 如果使用#号会给参数添加一对""号

* @param start

* @param rows

* @return

*/

@Select("select * from tb_item order by updated desc limit #{start},#{rows}")

List<Item> findItemByPage(@Param("start")int start,@Param("rows") int rows);

//暂时不写
}

8.页面效果

3.补充知识

  1. 关于log4j日志

说明:如果需要打印日志,则可以使用2中方式导入日志配置文件.

  1. 通过配置文件 导入log4j

  2. 默认的加载方式 要求必须在根目录中名称必须为log4j.properties

说明:在log4j代码中,通过static静态代码块的形式实现日志的加载.如果需要人为的修改配置文件,则需要单独的赋值.

2.PowerDesigner

  1. PD介绍

PowerDesigner最初由Xiao-Yun Wang(王晓昀)在SDP Technologies公司开发完成。PowerDesigner是Sybase的企业建模和设计解决方案,采用模型驱动方法,将业务与IT结合起来,可帮助部署有效的企业体系架构,并为研发生命周期管理提供强大的分析与设计技术。PowerDesigner独具匠心地将多种标准数据建模技术(UML、业务流程建模以及市场领先的数据建模)集成一体,并与 .NET、WorkSpace、PowerBuilder、Java™、Eclipse 等主流开发平台集成起来,从而为传统的软件开发周期管理提供业务分析和规范的数据库设计解决方案。此外,它支持60多种关系数据库管理系统(RDBMS)/版本。PowerDesigner运行在Microsoft Windows平台上,并提供了Eclipse插件。 [1] 

总结:

   PD是现在企业中使用最广泛的数据库建模工具,可以以图形化界面的形式展现表与表之间的关系,同时可以根据数据库的版本,自动生成建表语句.

2.PD破解

赋值汉化文件,粘贴到PD安装的根目录即可

实现商品查询分页展示

格式要求说明:

        因为使用EasyUI的表格用于数据展现,所以返回的数据有特殊的格式要求,返回的数据的属性必须与页面中定义的属性一致,

否则数据无法正常回显。

com.jt.common.vo.EasyUIResult类已做封装。

第一步:

创建com.jt.manage.pojo.Item实体类

package com.jt.manage.pojo;
@Table(name="tb_item")
public class Item {

//商品id,同时也是商品编号
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private  long id;
//商品标题
private  String title;
//商品卖点
private  String sellPoint;
//商品价格,单位为:分
private  long price;
//库存数量
private  Integer num;
//商品条形码
private  String barcode;
//商品图片
private  String image;
//所属类目,叶子类目
private  long cid;
//商品状态,1-正常,2-下架,3-删除
private  Integer status;
//创建时间
private  Date created;
//更新时间
private  Date updated;

第二步:

创建com.jt.manage.controller.ItemController类

/**
* 实现商品分页查询
* @param page
* @param rows
* @return
*/
@RequestMapping("/query")
@ResponseBody
public EasyUIResult finItemByPage(Integer page,Integer rows) {
return itemService.finItemByPage(page,rows);
}

第三步:

创建com.jt.manage.service.ItemService接口

/**
* 实现商品分页查询
* @param page
* @param rows
* @return
*/
EasyUIResult finItemByPage(Integer page, Integer rows);
第四步:
创建 com.jt.manage.service.ItemServiceImpl实体类并实现ItemService接口
/**
* 实现商品分页查询
*/
public EasyUIResult finItemByPage(Integer page, Integer rows) {
/**
* 通用Mapper查询操作时如果传入的数据不为null,则会充当where条件
*/
int total = itemMapper.selectCount(null);
int start = (page-1) * rows;
List<Item> itemList = itemMapper.finItemByPage(start, rows);
EasyUIResult result = new EasyUIResult(total,itemList);
return result;
}

第四步:

创建ItemMapper接口并继承 SysMapper<Item>

/**
* 实现商品分页查询
* @param page
* @param rows
* @return
*/
@Select("select * from tb_item order by updated desc limit #{start},#{rows}")
List<Item>  finItemByPage(@Param("start") int start, @Param("rows") Integer rows);

效果:


day04:京淘-后台系统实现上架下架

  1. Maven项目构建

    1. Maven配置文件

      1. 说明

Maven的级别有两种1,用户级别,该级别是默认级别.第二种是全局的配置.一般不生效.但是要求两种级别的配置文件必须相同,否则会有意想不到的异常.

同时.Maven/Nginx等重要的组件不要保存到C盘,因为有的电脑权限不够,导致程序无法运行.

操作:

   在用户级别下和全局的Settings必须一致.

2.Maven配置文件介绍

1.本地仓库

2.私服镜像

采用达内的私服镜像

<mirror>
<id>nexus</id>
<name>Tedu Maven</name>
<mirrorOf>*</mirrorOf>
<url>http://maven.tedu.cn/nexus/content/groups/public/</url>
</mirror>

3.配置JDK

说明:Maven通过骨架创建项目时默认的JDK版本1.5,所以需要手动修改JDK,但是修改繁琐,所以添加如下的配置

<profile>
<id>jdk17</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.7</jdk>
</activation>
<properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<maven.compiler.compilerVersion>1.7</maven.compiler.compilerVersion>
</properties>
</profile>
<!-- 全局jdk配置,settings.xml --> 
<!--<profile>   
<id>jdk18</id>   
<activation>   
<activeByDefault>true</activeByDefault>   
<jdk>1.8</jdk>   
</activation>   
<properties>   
<maven.compiler.source>1.8</maven.compiler.source>   
<maven.compiler.target>1.8</maven.compiler.target>   
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>   
</properties>    
</profile>   -->

2.京淘商品维护

  1. 实现商品分类目录回显

    1. 案例

  1. 实现商品价格回显

  2.实现商品时间回显

2.将商品分类目录回显

1.需求:将商品的分类的Id转化为具体的名称

2.JS文件

findItemName : function(val,row){
var name;
$.ajax({
type:"post",
url:"/item/cat/queryItemName",
data:{itemId:val},
cache:true,    //缓存
async:false,    //表示同步   默认的是异步的true
dataType:"text",
success:function(data){
name = data;
}
});
return name;
},

3.编辑Controller

/**问题:  @ResponseBody 为什么乱码????
* 
*  1.如果回传数据是一个对象使用@ResponseBody 返回时默认以utf-8编码
*  2.如果回传字符串,则默认以iso-8859-1编码
*
* @param itemId
* @param response
* @throws IOException
*/
//实现商品分类目录的回显
@RequestMapping(value="/queryItemName",produces="text/html;charset=utf-8")
@ResponseBody
public String findItemCatNameById(Long itemId,HttpServletResponse response) throws IOException{
//String name = itemCatService.findNameById(itemId);
//response.setContentType("text/html;charset=utf-8");
//response.getWriter().write(name);
return itemCatService.findNameById(itemId);
}

3.编辑Service

@Override
public String findNameById(Long itemId) {
return itemCatMapper.selectByPrimaryKey(itemId).getName();
}

字段对应

效果:

2.商品分类目录实现

  1. 页面分析

2.js分析

2.数据封装格式

说明:如果需要使用Easy_uitree进行数据展现,则必须

  1. Id   唯一标识节点信息

  2. Text  节点信息的名称

  3. state   节点是否打开/关闭

packagecom.jt.common.vo;
/**
* 为了封装EasyUI树形结构
* @author Administrator
*
*/
public class EasyUITree {
private Long id;
private String text;
private String state;
//get set toString方法
}

 3.树形结构展现格式要求

树控件读取URL。子节点的加载依赖于父节点的状态。当展开一个封闭的节点,如果节点没有加载子节点,它将会把节点id的值作为http请求参数并命名为'id',通过URL发送到服务器上面检索子节点。

 4.编辑Controller

//http://localhost:8091/item/cat/list
/**
* @RequestParam(value="id",defaultValue="0",required=true)
*    id表示接收参数的名称
*  defaultValue 默认值
*  required=true 该参数必须传递,否则SpringMVC校验报错.
* @param parentId
* @return
*/
@RequestMapping("/list")
@ResponseBody
public List<EasyUITree> findItemCat(@RequestParam(value="id",defaultValue="0")Long parentId){
//1.查询一级商品分类目录
//Long parentId = 0L;
return itemCatService.findItemCatByParentId(parentId);
}

5.编辑Service

/**
* 1.根据条件查询需要的结果 where parent_id = 0
* 2.需要将ItemCat集合转化为List<EasyUITree>
* 3.通过循环遍历的方式实现List赋值.
*
* state  "open"/"closed"
*/
@Override
public List<EasyUITree> findItemCatByParentId(Long parentId) {
ItemCat itemCat = new ItemCat();
itemCat.setParentId(parentId);
//查询需要的结果
List<ItemCat> itemCatList =
itemCatMapper.select(itemCat);
//2.创建返回集合对象
List<EasyUITree> treeList = new ArrayList<EasyUITree>();
//3.将集合进行转化
for (ItemCat itemCatTemp : itemCatList) {
EasyUITree easyUITree = new EasyUITree();
easyUITree.setId(itemCatTemp.getId());
easyUITree.setText(itemCatTemp.getName());
//如果是父级则暂时先关闭,用户需要时在展开
String state =
itemCatTemp.getIsParent() ? "closed" : "open";
easyUITree.setState(state);
treeList.add(easyUITree);
}
return treeList;
}

6.页面效果

3.商品新增

  1. EasyUI校验

  1. 必填项

    <td><input class="easyui-textbox" type="text" name="title" data-options="required:true" style="width: 280px;"></input></td>
  2. 数字输入 

<td><input class="easyui-numberbox" type="text" name="priceView" data-options="min:1,max:99999999,precision:2,required:true" />

2.商品新增的页面分析

  1. js分析

$.post("/item/save",$("#itemAddForm").serialize(), function(data){
if(data.status == 200){
$.messager.alert('提示','新增商品成功!');
}else{
$.messager.alert("提示","新增商品失败!");
}
});

2.页面分析

3.编辑Controller

@RequestMapping("/save")
@ResponseBody
public SysResult saveItem(Item item){
try {
itemService.saveItem(item);
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201,"商品新增失败");
}

4.编辑Servic

@Override
public void saveItem(Item item) {
//需要补齐数据
item.setStatus(1); //表示商品上架
item.setCreated(new Date());
item.setUpdated(item.getCreated());
itemMapper.insert(item);
}

4.商品修改

  1. 页面分析

  1. 页面JS

<div id="itemEditWindow" class="easyui-window" title="编辑商品" data-options="modal:true,closed:true,iconCls:'icon-save',href:'/page/item-edit'" style="width:80%;height:80%;padding:10px;">
</div>
$.post("/item/update",$("#itemeEditForm").serialize(), function(data){
if(data.status == 200){
$.messager.alert('提示','修改商品成功!','info',function(){
$("#itemEditWindow").window('close');
$("#itemList").datagrid("reload");
});
}else{
$.message.alert("提示",data.msg);
}
});

2.页面分析

2.编辑Controller

//商品修改
@RequestMapping("/update")
@ResponseBody
public SysResult updateItem(Item item){
try {
itemService.updateItem(item);
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201, "商品修改失败");
}

3.编辑Service

@Override
public void updateItem(Item item) {
//为数据赋值
item.setUpdated(new Date());
//表示动态更新操作. 只更新不为null的数据
itemMapper.updateByPrimaryKeySelective(item);
}

5.商品删除

  1. 页面分析

  1. js分析

handler:function(){
var ids = getSelectionsIds();
if(ids.length == 0){
$.messager.alert('提示','未选中商品!');
return ;
}
$.messager.confirm('确认','确定删除ID为 '+ids+' 的商品吗?',function(r){
if (r){
var params = {"ids":ids};
$.post("/item/delete",params, function(data){
if(data.status == 200){
$.messager.alert('提示','删除商品成功!',undefined,function(){
$("#itemList").datagrid("reload");
});
}else{
$.messager.alert("提示",data.msg);
}
});
}
});

2.页面分析

2.编辑Controller 

@RequestMapping("/delete")
@ResponseBody
public SysResult deleteItem(Long[] ids){
try {
itemService.deleteItems(ids);
//System.out.println("asdfasdf"); //效率太低
logger.info("{我是一个打桩日志}");
return SysResult.oK();
} catch (Exception e) {
//e.printStackTrace();
logger.error("!!!!!!!!!!!!!!!!!"+e.getMessage());
//logger.error("~~~~~~~~~~"+e.getMessage());
}
return SysResult.build(201, "商品删除失败");
}

3.编辑Service

@Override
public void deleteItems(Long[] ids) {
//根据主键删除
itemMapper.deleteByIDS(ids);
}

6.商品下架和上架

  1. 页面分析

2.编辑Controller

//实现商品上架  /item/reshelf
@RequestMapping("/reshelf")
@ResponseBody
public SysResult reshelf(Long[] ids){
try {
int status = 1;  //商品上架
itemService.updateStatus(status,ids);
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201, "商品上架失败");
}
//商品下架  /item/instock
@RequestMapping("/instock")
@ResponseBody
public SysResult instock(Long[] ids){
try {
int status = 2;  //商品下架
itemService.updateStatus(status,ids);
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201, "商品下架失败");
}

3.编辑Service

@Override
public void updateStatus(int status, Long[] ids) {
//update tb_item set status = #{status},updated = now() where id in (1,2,3,4,5)
itemMapper.updateStatus(status,ids);
}

4.编辑Mapper接口映射文件

void updateStatus(@Param("status")int status,@Param("ids")Long[] ids);

映射文件

<!--实现商品状态修改
核心:mybatis只支持单值传递
collection=""
只需要记住三种情况
1.Map中的key
2.list  参数类型为List集合
3.array 参数类型为Array数组
2.小知识点.Mybatis中参数获取时,名称可以是任意的.但是一般都是见名知意.
-->
<update id="updateStatus">
update tb_item set status = #{status},updated = now() where
id in  (
<foreach collection="ids" item="id" separator=",">
#{id}
</foreach>
)  
</update>

day05: 京淘-描述表,图片上传, nginx反向代理

  1. 富文本编辑器

    1. 富文本编辑器介绍

  1. 页面展现

    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link href="/js/kindeditor-4.1.10/themes/default/default.css" type="text/css" rel="stylesheet">
    <script type="text/javascript" charset="utf-8" src="/js/kindeditor-4.1.10/kindeditor-all-min.js"></script>
    <script type="text/javascript" charset="utf-8" src="/js/kindeditor-4.1.10/lang/zh_CN.js"></script>
    <script type="text/javascript" charset="utf-8" src="/js/jquery-easyui-1.4.1/jquery.min.js"></script>
    <script type="text/javascript">
    $(function(){
    KindEditor.ready(function(){
    KindEditor.create("#editor")
    })
    })
    </script>
    </head>
    <body>
    <h1>富文本编辑器入门案例</h1>
    <textarea style="width:700px;height:350px" id="editor"></textarea>
    </body>

    该文件寻贴主得

2.富文本编辑器使用

说明:富文本编辑器其实获取的就是页面中的HTMl代码.并且可以直接解析编译.但是一般只获取静态的资源数据.在进行入库操作时 ,也保存的是html信息.

2.实现商品描述新增

  1. 定义POJO对象

2.定义Mapper接口

public interface ItemDescMapper extends SysMapper<ItemDesc>{
}

3.编辑Controller

@RequestMapping("/save")
@ResponseBody
public SysResult saveItem(Item item,String desc){
try {
itemService.saveItem(item,desc);
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201,"商品新增失败");
}

4.编辑Service

@Override
public void saveItem(Item item,String desc) {
//需要补齐数据
item.setStatus(1); //表示商品上架
item.setCreated(new Date());
item.setUpdated(item.getCreated());
itemMapper.insert(item);
//SELECT LAST_INSERT_ID() 获取当前线程内Id的最大值
ItemDesc itemDesc = new ItemDesc();
System.out.println(item.getId());
itemDesc.setItemId(item.getId()); //?????有数据吗???
itemDesc.setItemDesc(desc);
itemDesc.setCreated(item.getCreated());
itemDesc.setUpdated(item.getCreated());
itemDescMapper.insert(itemDesc);
}

3.商品描述回显

  1. 页面分析

$.getJSON('/item/query/item/desc/'+data.id,function(_data){
if(_data.status == 200){
//UM.getEditor('itemeEditDescEditor').setContent(_data.data.itemDesc, false);
itemEditEditor.html(_data.data.itemDesc);
}
});

2.编辑Controller

//实现商品描述信息回显
@RequestMapping("/query/item/desc/{itemId}")
@ResponseBody
public SysResult findItemDescById(@PathVariable Long itemId){
try {
ItemDesc itemDesc = itemService.findItemDesc(itemId);
return SysResult.oK(itemDesc);
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201, "商品详情查询失败");
}

3.编辑Service

//根据itemId查询商品详情信息
@Override
public ItemDesc findItemDesc(Long itemId) {
return itemDescMapper.selectByPrimaryKey(itemId);
}

效果点击编辑页面出现效果

4.商品详情修改

  1. 编辑Controlelr

//商品修改
@RequestMapping("/update")
@ResponseBody
public SysResult updateItem(Item item,String desc){
try {
itemService.updateItem(item,desc);
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201, "商品修改失败");
}

2.编辑Service

@Override
public void updateItem(Item item,String desc) {
//为数据赋值
item.setUpdated(new Date());
//表示动态更新操作. 只更新不为null的数据
itemMapper.updateByPrimaryKeySelective(item);
//商品描述信息更新
ItemDesc itemDesc= new ItemDesc();
itemDesc.setItemDesc(desc);
itemDesc.setItemId(item.getId());
itemDesc.setUpdated(item.getUpdated());
itemDescMapper.updateByPrimaryKeySelective(itemDesc);
}

5.商品详情删除

  1. 编辑Service

说明:商品删除需要同时删除2张表数据 tb_item和tb_item_desc表

@Override
public void deleteItems(Long[] ids) {
//根据主键删除
itemMapper.deleteByIDS(ids);
itemDescMapper.deleteByIDS(ids);
}

2.文件上传实现

  1. 文件上传入门案例

    1. 配置文件上传视图解析器

    在spring/applicationContext-mvc.xml配置文件上传视图解析器

<!--配置文件上传视图解析器
说明:id值必须写死,否则需要多余的配置项.
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485670"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>

2.定义文件上传form表单

<body>
<h1>文件上传入门案例</h1>
<form action="http://localhost:8091/file"
method="post"
enctype="multipart/form-data">
<input type="file"  name="file"/><br>
<input type="submit" value="提交"/>
</form>
</body>

3.通过解析器实现文件上传

package com.jt.manage.controller;
import java.io.File;
import java.io.IOException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
@Controller
public class FileController {
/**
* 要求文件上传完成后,再次跳转到文件长传页面
* 参数一定要与提交参数保持一致
* @return
* @throws IOException
* @throws IllegalStateException
*/
@RequestMapping
private String file(MultipartFile file) throws IllegalStateException, IOException{
//准备文件上传的路径
String path = "E:/jt-upload";
//判断文件夹是否存在
File filePath = new File(path);
if(!filePath.exists()){
//如果文件夹不存在,需要创建一个文件夹
filePath.mkdirs();
}
//获取文件名称  abc.jpg
String fileName = file.getOriginalFilename();
//实现文件上传   E:/jt-upload/
file.transferTo(new File(path+"/"+fileName));
return "redirect:/file.jsp";
}

2.商品图片上传

  1. 页面分析

2.定义返回值对象

{"error":0,"url":"图片的保存路径","width":图片的宽度,"height":图片的高度}

packagecom.jt.common.vo;
public class PicUploadResult {
private Integer error=0;          //图片上传错误不能抛出,抛出就无法进行jsp页面回调,所以设置这个标识,0表示无异常,1代表异常
private String url;
private String width;
private String height;
//get set 方法
}

3.实现Service数据动态赋值

说明:根据规则,动态获取本地磁盘路径和虚拟路径,方便以后修改.

  1. 编辑properties

#定义本地磁盘路径
image.localPath = E:/jt-upload/
#定义虚拟路径
image.urlPath = http://image.jt.com/

2.编辑service

4.编辑Controller

 编辑Controller

//实现商品的文件上传
@RequestMapping("/pic/upload")
@ResponseBody
public PicUploadResult uploadFile(MultipartFile uploadFile){
return fileService.upload(uploadFile);
}

5.编辑Service

@Service
public class FileServiceImpl implements FileService {
//要求参数应该动态获取???
//定义文件存储的根目录
@Value("${image.localPath}") //通过注解的方式 为属性动态赋值
private String localPath;  //= "E:/jt-upload/";
//定义虚拟路径的根目录
@Value("${image.urlPath}")
private String urlPath;    //="http://image.jt.com/";
/**
* 问题:
*    1.是否为正确的图片??? jpg|png|gif
*    2.是否为恶意程序???  
* 3.不能将图片保存到同一个文件夹下
*  4.图片的重名问题
*   解决策略:
*  1.正则表达式实现图片的判断.
*  2.使用BufferedImage转化图片 height/weight
*  3.使用分文件夹存储 yyyy/MM/dd 并不是唯一的
*  4.UUID+三位随机数区分图片
*/
@Override
public PicUploadResult upload(MultipartFile uploadFile) {
PicUploadResult result = new PicUploadResult();
//1.获取图片的名称   abc.jpg
String fileName = uploadFile.getOriginalFilename();
fileName = fileName.toLowerCase();
//2.判断是否为图片的类型
if(!fileName.matches("^.*(jpg|png|gif)$")){
result.setError(1); //表示不是图片
}
//3.判断是否为恶意程序
try {
BufferedImage bufferedImage =
ImageIO.read(uploadFile.getInputStream());
int height = bufferedImage.getHeight();
int width = bufferedImage.getWidth();
if(height == 0 || width == 0){
result.setError(1);
return result;
}
//4.将图片分文件存储 yyyy/MM-dd
String DatePath =
new SimpleDateFormat("yyyy/MM/dd").format(new Date());
//判断是否有该文件夹  E:/jt-upload/2018/11/11
String picDir = localPath + DatePath;
File picFile = new File(picDir);
if(!picFile.exists()){
picFile.mkdirs();
}
//防止文件重名  
String uuid = UUID.randomUUID().toString().replace("-", "");
int randomNum = new Random().nextInt(1000);
//.jpg
String fileType = fileName.substring(fileName.lastIndexOf("."));
//拼接文件的名称
String fileNowName = uuid + randomNum + fileType;
//实现文件上传            e:jt-upload/yyyy/MM/dd/1231231231231231231.jpg
String realFilePath = picDir + "/" +fileNowName;
uploadFile.transferTo(new File(realFilePath));
//将真实数据回显
result.setHeight(height+"");
result.setWidth(width+"");
/**
* 实现虚拟路径的拼接
* E:/jt-upload/2018/07/23/e4d5c2667a174477b2ab59158670bbbe816.jpg
* image.jt.com
*/
String realUrl = urlPath + DatePath + "/" + fileNowName;
result.setUrl(realUrl);
} catch (Exception e) {
e.printStackTrace();
result.setError(1); //文件长传有误
}
return result;
}
}

3.Nginx

  1. 需求

    1. 需求分析

说明:应该当用户访问image.jt.com时,应该将我们请求的路径转向到本地磁盘E:/jt-upload

真实的磁盘路径:

E:/jt-upload/2018/07/23/e4d5c2667a174477b2ab59158670bbbe816.jpg

虚拟路径:

Http://image.jt.com/2018/07/23/e4d5c2667a174477b2ab59158670bbbe816.jpg

2.请求实现-Nginx反向代理

调用过程说明:

  1. 当用户访问数据时,发出请求image.jt.com/1.jpg被Nginx所拦截(监听器方式)

  2. 之后nginx在内部将请求转化为E:/jt-upload/1.jpg.请求真实的服务器

  3. Nginx请求真实的图片消息后.将返回的结果返回给客户端

2.Nginx介绍

  1. Nginx介绍

Nginx (engine x) 是一个高性能的HTTP反向代理服务器,也是一个IMAP/POP3/SMTP服务器。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。

Nginx是一款轻量级的 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东新浪网易腾讯淘宝等。

Nginx并发能力:50000次并发/s 真实数据 2-3万之间

2.Nginx下载

说明:将Nginx保存到本地磁盘下 不要放到C盘下和中文路径下.

3.Nginx启动

注意:首先第一次使用时,使用超级管理员权限运行.

说明:Nginx底层是C语言开发的.当以超级管理员权限运行时,会快速的执行,并且在操作系统中开启2个进程.

关于进程说明:

 一个是主进程:主要提供Nginx服务

一个是守护进程:防止主进程意外关闭的.如果主进程意外关闭了,则再次启动主进程.

所以关闭nginx的进程应该先关闭守护进程(内存占用少的),之后关闭主进程(内存占用多的)

2.关于Nginx的命令

   启动Nginx: start nginx

   关闭Nginx: nginx -s stop

   重启Nginx: nginx -s reload

   注意事项:Nginx的命令的执行必须在Nginx的根目录下执行.

3.Nginx实现反向代理

  1. Nginx实现默认跳转

说明:当用户访问localhost:80时,默认跳转的是Nginx首页

问题:如何实现该操作的??

案例分析:

server {
#默认监听80端口
listen       80;
#拦截的请求路径   
server_name  localhost;
#charset koi8-r;
#access_log  logs/host.access.log  main;
location / {
#root表示 将请求转向到具体的文件夹中
root   html;
#index 表示默认的访问页面
index  index.html index.htm;
}
}

4.Nginx实现图片信息回显

  1. 编辑Nginx配置文件

#图片服务器
server {
listen 80;
server_name image.jt.com;
location / {
root H:\jt-upload;
}
}

修改完成后,将Nginx重启

2.编辑HOST文件

说明:让image.jt.com访问时,必须访问本机,才能被Nginx所拦截,实现方向代理技术.

# 京淘电商环境
127.0.0.1       image.jt.com
127.0.0.1       manage.jt.com
127.0.0.1       www.jt.com
127.0.0.1       sso.jt.com
127.0.0.1       cart.jt.com
127.0.0.1       order.jt.com
127.0.0.1       solr.jt.com

 3.页面效果


day06:京淘-Tomcat集群, Nginx负载均衡,Linux部署

  1. Nginx高级

    1. Nginx反向代理

      1. 实现请求路径的代理

要求:

   当用户访问manage.jt.com时访问的是localhost:8091

配置文件:

#定义后台管理系统
server {
listen 80;
server_name manage.jt.com;
location / {
proxy_pass http://localhost:8091;
}
}

说明:修改完成后,需要重启Nginx

3个选中clean

 右键打包

查看打包路径:

拿到war包复制到tomcat中并改明为ROOT.war

启动tomcat查看效果:

2.页面效果

2.Tomcat集群部署

  1. Tomcat服务器搭建

    1. 准备tomcat服务器

说明:将课前资料中的tomcat导入到本机,支行修改配置文件

  1. 修改8005端口

2.修改服务端口

3.修改AJP端口

2.发布项目

说明:将项目分别进行打包.打包的顺序jt-parent/jt-common/jt-manage.之后将包名修改为ROOT.war,分别部署到3台tomcat中.最终实现

Localhost:8091

Localhost:8092

Localhost:8093

   正常范文京淘后台即可

2.Nginx实现负载均衡

  1. 轮询

说明:

   根据配置文件的顺序,依次访问配置的服务器.达到负载均衡的效果,该配置是Nginx默认的策略.

Nginx配置:

#配置nginx负载均衡 1.轮询
upstream jt {
server  localhost:8091;
server  localhost:8092;
server  localhost:8093;
}
#定义后台管理系统
server {
listen 80;
server_name manage.jt.com;
location / {
proxy_pass http://jt;
}
}

修改完成后重启Nginx

效果:

2.权重

说明:根据服务器的性能,让高性能服务器尽量处理多的请求,使用权重的配置.使用weight关键字

#配置nginx负载均衡 1.轮询  2.权重
upstream jt {
server  localhost:8091 weight=6;
server  localhost:8092 weight=3;
server  localhost:8093 weight=1;
}

3.IP_hash(了解)

问题描述:采用集群的部署后,如何实现单点登录是一个典型的问题.因为用户登陆后,需要将用户信息保存到Session中,但是不同的服务器Session不能共享.所以无法实现用户只登陆一次,其他免密登录的效果.

解决方案:

   可以通过IP_hash配置,使用用户单点登录,用户访问服务器时,将IP地址经过计算.之后绑定到特定到某一台服务器上,从而实现了单点登录效果.

补充说明:

   如果配置IP_hash,那么配置的权重和轮询将不起作用.

风险:

   不安全.

4.项目上线的步骤

  1. 修改Nginx,将需要上线的机器先做下线处理.

  2. 之后将无服务停止,之后将包进行部署.

  3. 将服务器启动,启动后先经过测试人员测试

  4. 如果测试没有问题,则将tomcat上线.部署后续的项目.

   问题:

     如果nginx在重启过程中,用户发起请求,那么用户访问是否受限???

   说明:

     Nginx的启动和重启是非常快速的几乎可以实现秒开秒关.

5.Nginx中备用机机制

说明:

   在一般的情况下,服务器会配置备用机,该机器正常的情况下,不会处理请求,只有在主服务遇忙时或者主机宕机时,才会访问备用机.

#配置nginx负载均衡 1.轮询  2.备用机
upstream jt {
#实现ip_hash
#ip_hash;
server  localhost:8091 weight=6 down;
server  localhost:8092 weight=3 down;
server  localhost:8093 weight=1 backup;
}

6.Nginx健康检测(基本高可用)

问题:如果服务器出现意外的宕机现象,这时nginx访问会出现问题.

解决方案:

   Nginx内部有自己的健康检测机制,在指定的检测周期内,如果发现后台服务器出现宕机的现象.那么在该周期内不会再将请求发往该机器.直到下一个检测周期时,才会再次检查服务器是否宕机,如果依然宕机,则该周期内不会发请求给故障机.

最终实现了tomcat服务器宕机后,自动的实现了故障的迁移.

配置文件介绍:

server  localhost:8091 max_fails=1 fail_timeout=300s;

最大的失败的次数允许1次.如果出现宕机那么在60秒内,不会再将请求发往该机器.

Nginx健康检查配置

#配置nginx负载均衡 1.轮询 2.权重
upstream jt {
#实现ip_hash
#ip_hash;
#健康检测 失败的次数允许1次.如果出现宕机那么在60秒内,不会再将请求发往该机器
server  localhost:8091 max_fails=1 fail_timeout=60s;
server  localhost:8092 max_fails=1 fail_timeout=60s;
server  localhost:8093 weight=1 backup;
}
#定义后台管理系统
server {
listen 80;
server_name manage.jt.com;
location / {   
proxy_pass http://jt;
#代理连接超时
proxy_connect_timeout       3;
#代理读取超时
proxy_read_timeout          3;
#代理发送超时
proxy_send_timeout          3;  
}
}

3.项目Linux部署

  1. 虚拟机搭建

    1. 网卡介绍

NAT1:

   选择网络模式为仅主机时使用的网络配置

NAT8:

   选择网络模式为NAT模式时的网络的配置

2.克隆虚拟机

  1. 选择克隆的状态

2.选择链接

   3.选择路径

  

   3.虚拟机的网络模式

  1. 桥接模式

让虚拟机Linux操作系统直接接入当前所在的局域网中.简单的方式

特点:

  1. 相互的通信必须基于交换机/路由器

  2. 连入局域网的设备可以被其他人访问. Root/root

2.NAT模式

在自己的电脑重新开辟了一块局域网(私网空间192.168).该网络只能允许本机访问,其他的机器不允许访问.

4.虚拟机快照

说明:通过虚拟机启动的操作系统有时由于误操作,可能导致不可逆转的情况,这时通过虚拟机快照的方式恢复现场数据.

  

5.指定虚拟机IP地

说明:根据自己虚拟机的IP设定为静态IP地址.

6.XShell连接Linux操作系统

连接成功

2.安装JDK部署

  1. 安装JDK

  1. 导入安装文件

2.解压JDK

tar -xvf jdk-7u51-linux-x64.tar.gz

3.配置环境变量

3.1复制JDK的根目录

/usr/local/src/java/jdk1.7.0_51

3.2配置JDK的环境变量

vim /etc/profile

4. 让JDK的配置立即生效

source /etc/profile

5.检测JDK安装是否成功

3.部署tomcat

  1. 上传tomcat安装包

说明:将文件上传后,解压该文件

2.复制多台tomcat

更改文件名:
mv apache-tomcat-7.0.55 tomcat-8091
拷贝:
cp -r  tomcat-8091 tomcat-8092

3.修改配置文件

说明:修改tomcat 8005/8080/8009端口

4.启动tomcat

说明:在bin文件夹执行启动命令 

sh startup.sh  启动tomcat
sh shutdown.sh 停止tomcat

通过日志检查启动是否成功

5.关闭防火墙

6.虚拟机网络通信规则

1.检查网络通讯的IP地址

2.网络通讯的规则

3.修改JDBC的连接

修改完成后,将项目打包.

7.部署war包

说明:

  1. 将tomcat关闭

2.将原有的ROOT文件夹删除

3.部署war包

 8.  关于Mysql对外访问权限问题

说明:

   Mysql中要求,本机访问不需要任何的权限.但是如果是跨系统之间的访问,那么必须开启访问权限.

   语法:

   grant [权限] on [数据库名].[表名] to ['用户名']@['web服务器的ip地址'] identified by ['密码'];

 grant all on *.* to 'root'@'%' identified by 'root';

    或者指定IP地址

   grant all on *.* to 'root'@'192.168.1.103' identified by 'root';

问题的总结:

   如果出现数据访问问题:

  1. 关闭window和linux的防火墙

  2. 开放mysql对外访问权限.

9.作业:

要求:实现Linux端tomcat的负载均衡.

提示:

   修改Nginx配置文件


day07:京淘-数据庄主从复制amoeba读写分离

  1. JDK版本统一

    1. 修改JDK

  1. eclipse环境JDK

2.windos环境

3.settings环境配置

将JDK1.7改为1.8后更新maven

<profile>   
<id>jdk18</id>   
<activation>   
<activeByDefault>true</activeByDefault>   
<jdk>1.8</jdk>   
</activation>   
<properties>   
<maven.compiler.source>1.8</maven.compiler.source>   
<maven.compiler.target>1.8</maven.compiler.target>   
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>   
</properties>    
</profile>

2.数据库高可用

  1. 数据备份

    1. 冷备份

说明:定期将数据库文件进行转储.

缺点:

  1. 数据库冷备份,则需要手动的人工完成.效率低

  2. 定期数据备份,不能保证数据的安全的.仅仅能够恢复部分数据.

  3. 如果数据量比较庞大,导入导出时耗费的时间较多.

  4. 由于网络传输问题.可能会导致备份多次.

    说明:

        数据库冷备份,是恢复数据的最后有效的手段.

2.热备份

说明:当主数据库中的数据发生”更新”操作时,数据会自动的同步到slave(从数据库中).该操作可以实现实时备份.

备份步骤:

  1. 当主库数据发生改变时,会将更新的消息实时的写入二进制日志文件中.

  2. 从库需要实时的监控主库的二进制日志文件,如果出现更新操作,那么通过IOThread读取更新的内容,将数据写入中继日志中.

  3. 从库开启SqlThread实时的读取中继日志中的内容,实现从库数据的同步.

  4. 最终实现主库和从库的数据的实时备份.

2.数据库安装

  1. 准备工作

说明:克隆新的虚拟机后,将IP地址设置为固定IP

2.上传mysql安装文件

说明:

1.将mysql安装文件上传到指定了文件夹下

     2.将mysql安装文件解压

tar -xvf Percona-Server-5.6.24-72.2-r8d0f85b-el6-x86_64-bundle.tar

3.安装mysql数据库

说明:安装的顺序

  1. debuginfo 2. Shared 3.client 4.server

 命令:

rpm -ivh Percona-Server-56-debuginfo-5.6.24-rel72.2.el6.x86_64.rpm

   安装步骤:

     1.

2.安装shard

rpm -ivh Percona-Server-shared-56-5.6.24-rel72.2.el6.x86_64.rpm

3.安装client客户端

rpm -ivh Percona-Server-client-56-5.6.24-rel72.2.el6.x86_64.rpm

4.安装数据库服务   

rpm -ivh Percona-Server-server-56-5.6.24-rel72.2.el6.x86_64.rpm

4.启动mysql数据库

  1. 导入sql文件

2.启动mysql服务项

service mysql start   启动命令
service mysql stop    停止命令
service mysql restart 重启命令

启动成功:

3.设定mysql用户名和密码

mysqladmin -u root password root

4.导入 jt 数据库
Mysql 客户端中 :source jt.sql;
或者 source /usr/local/src/mysql/jt.sql;
检测数据库是否存在
5.mysql 远程访问
  1. 关闭防火墙
service iptables stop
2.开放 mysql 对外访问权限
grant all on *.* to 'root'@'%' identified by 'root';

3.远程连接 mysql

5.搭建从库

2.实现主从搭建

  1. 配置主库二进制文件

说明 : 主库的二进制文件默认的是关闭的 . 需要手动开启日志文件
编辑文件 :
   vim /etc/my.cnf

配置文件介绍:

数据库文件存储的位置
datadir=/var/lib/mysql

编辑配置文件

说明 : 每个 mysql 数据库的服务的 Id 号应该不同
server-id=1
log-bin=mysql-bin

重启mysql服务:

service mysql restart
检测日志文件是否启动
cd /var/lib/mysql/
ls

 2.修改从库的配置文件

之后重启数据库 . 检测配置文件是否生效

 3.关于数据库报错问题

  1. my.cnf 文件编辑错误

 2.PID或者socket报错

杀死进行后需要将 mysql 服务重启 service mysql start

 4.mysql序列号

cat auto.cnf

 4.实现数据库主从挂载

  1. 需要检测主库的状态
2.实现主从的挂载
#需要将从库挂载到主库时 ip/端口/用户名/密码/二进制文件名称/二进制文件位置
change MASTER to MASTER_HOST="192.168.126.162",
MASTER_PORT=3306,
MASTER_user="root",
MASTER_PASSWORD="root",
MASTER_LOG_FILE="mysql-bin.000001",
MASTER_LOG_POS=120
#启动主从服务
start slave
#检测主从的状态
show SLAVE status
#重新挂载 1.关闭主从服务  挂载  启动主从服务 检测状态
stop slave 

  3.检测从库状态

4.主从测试

修改mysql主库后,检测从库是否同步.

5.关于克隆数据库中问题

说明:由于可控的数据库中auto.cnf文件相同,导致主从挂载失败.需要修改序列号.

select UUID();

5ddea06c-8fd7-11e8-ab4d-000c29a792ac

3.数据库读写分析

  1. 读写分离

    1. 说明

  1. 设定读写分离的策略.1主1从

  2. 指定具体的代理服务器(Amoeba/mycat)

2.Amoeba介绍

Amoeba是一个以MySQL为底层数据存储,并对应用提供MySQL协议接口的proxy。它集中地响应应用的请求,依据用户事先设置的规则,将SQL请求发送到特定的数据库上执行。基于此可以实现负载均衡、读写分离、高可用性等需求。与MySQL官方的MySQL Proxy相比,作者强调的是amoeba配置的方便(基于XML的配置文件,用SQLJEP语法书写规则,比基于lua脚本的MySQL Proxy简单)。

Amoeba相当于一个SQL请求的路由器,目的是为负载均衡、读写分离、高可用性提供机制,而不是完全实现它们。用户需要结合使用MySQL的 Replication等机制来实现副本同步等功能。amoeba对底层数据库连接管理和路由实现也采用了可插拨的机制,第三方可以开发更高级的策略类来替代作者的实现。这个程序总体上比较符合KISS原则的思想。

补充说明:

   Amoeba阿里开源的项目,现在已经停止维护更新了.Amoeba开发时使用JDK1.7

2.Amoeba搭建

  1. 安装JDK1.8

2.让JDK生效

source /etc/profile

3.检测JDK是否有效

2.安装Amoeba

1.上传安装文件

2.修改文件名称

3.修改dbServer.xml文件

该文件寻贴主得

3.修改链接mysql用户名和密码

2.修改主库的IP

3.配置数据库链接

4.修改Amoeba.xml

 该文件寻贴主得

1.Amoeba端口号

2.定义用户名和密码

3.定义读写策略

5.修改JDK内存

JVM_OPTIONS="-server -Xms256m -Xmx1024m -Xss196k -XX:PermSize=16m -XX:MaxPermSize=96m"

参数说明:

-Xms256m 代表初始化内存.

-Xmx1024m 最大内存

-Xss196k  每个线程占用空间的大小

6.Amoeba读写分离测试

  1. 启动服务

    ./launcher

    ./shutdown

  2. 测试服务

JDBC:链接代理服务器

测试负载均衡:

修改从库title中的数据.测试成功后,将数据改为一致

3.作业

通过windos中的nginx实现链接Linux中tomcat.

要求Linux中的tomcat链接代理服务器.

切记服务器挂起,不要关机.


day08: 京淘-数据库双机热备Mycat读,写分离

  1. Mysql搭建错误集合

    1. Mysql服务启动问题

说明:如果mysql启动报错,不能正常的启动,则检查my.cnf文件

1

2.手动的删除日志文件,之后重启mysql

3.删除mysql进程

2.关于mysql关机启动

说明:

     如果mysql意外关机,那么重启时需要注意一下的步骤

  1. 关闭防火墙

  2. 启动主库服务  service mysql start

  3. 启动从库服务

  1. 关于Nginx报错

502

   说明:一定是开启了多个nginx导致报错

   解决:先关闭所有的Nginx 通过管理器关闭

504:

   链接超时.后台的tomcat链接不通.

  1. tomcat启动不正常

  2. 链接tomcat防火墙没关.

  3. Nginx配置文件的IP地址写错了.

2.Mysql实现高可用

  1. 数据库主从问题

    1. 说明

如果数据库搭建主从的配置,可以实现数据的实时备份.但是如果主数据库如果出现宕机现象.则整个服务都会陷入瘫痪.(雪崩效应)

2.数据库双机热备

  1. 双机热备的说明

说明:

   采用双机热备的形式,如果主库发生宕机现象,那么通过代理服务器访问从库,使得服务可以正常执行.当主库修复完成后,启动时会从另一台mysql数据库中实时的同步数据,从而实现数据的一致性.

2.搭建步骤

主机A:192.168.126.162

从机B:192.168.126.163

昨天:A向B同步数据

今天:B向A同步数据

注意事项:数据同步时不会写入二进制.只有当用户使用sql操作数据库时,才会将数据写入二进制日志文件中.

  1. 检测B库的状态

2.实现数据库挂载

#实现数据库主从挂载
change MASTER to MASTER_HOST="192.168.126.163",
MASTER_PORT=3306,
MASTER_USER="root",
MASTER_PASSWORD="root",
MASTER_LOG_FILE="mysql-bin.000001",
MASTER_LOG_POS=452
#启动主从服务
start SLAVE
#检测主从状态
show SLAVE status

3.Mycat

  1. 说明:

说明:各个数据库之间开发时都是基于sql92标准.

不同的:

   Mysql:分页 limit

   Oracle: rows

4.Mycat搭建

说明:开发Mycat时使用JDK1.7版本.

启动:
./mycat start
关闭:
./mycat stop
重启:
./mycat restart
检查状态信息
./mycat status

表示启动成功

  1. 关闭Amoeba

2.解压Mycat

3.Usr/local/src/mycat/conf/ 修改server.xml

4.修改schema.xml

<!--name属性是自定义的  dataNode表示数据库的节点信息-->
<schema name="jtdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="jtdb"/>
<!--定义节点名称/节点主机/数据名称-->
<dataNode name="jtdb" dataHost="localhost1" database="jtdb" />
<!--参数介绍-->
<!--balance 0表示所有的读操作都会发往writeHost主机 --> 
<!--1表示所有的读操作发往readHost和闲置的主节点中-->
<!--writeType=0 所有的写操作都发往第一个writeHost主机-->   
<!--writeType=1 所有的写操作随机发往writeHost中-->
<!--dbType 表示数据库类型 mysql/oracle-->
<!--dbDriver="native"  固定参数 不变-->
<!--switchType=-1 表示不自动切换, 主机宕机后不会自动切换从节点-->
<!--switchType=1  表示会自动切换(默认值)如果第一个主节点宕机后,Mycat会进行3次心跳检测,如果3次都没有响应,则会自动切换到第二个主节点-->
<!--并且会更新/conf/dnindex.properties文件的主节点信息 localhost1=0 表示第一个节点.该文件不要随意修改否则会出现大问题-->
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
<heartbeat>select 1</heartbeat>
<!--配置第一台主机主要进行写库操作,在默认的条件下Mycat主要操作第一台主机在第一台主机中已经实现了读写分离.因为默认写操作会发往137的数据库.读的操作默认发往141.如果从节点比较忙,则主节点分担部分压力.
-->
<writeHost host="hostM1" url="192.168.126.162:3306" user="root" password="root">
<!--读数据库-->
<readHost host="hostS1" url="192.168.126.163:3306" user="root" password="root" />
</writeHost>
<!--定义第二台主机 由于数据库内部已经实现了双机热备.-->
<!--Mycat实现高可用.当第一个主机137宕机后.mycat会自动发出心跳检测.检测3次.-->
<!--如果主机137没有给Mycat响应则判断主机死亡.则回启东第二台主机继续为用户提供服务.-->
<!--如果137主机恢复之后则处于等待状态.如果141宕机则137再次持续为用户提供服务.-->
<!--前提:实现双机热备.-->
<writeHost host="hostM2" url="192.168.126.163:3306" user="root" password="root">
<readHost host="hostS1" url="192.168.126.162:3306" user="root" password="root" />
</writeHost>
</dataHost>

5.Mycat启动

命令:

./mycat start

./mycat stop

./mycat restart

6.检测服务是否启动

命令: 

cd ../logs/

ls

cat wrapper.log

7.测试

将主库关闭后,检查程序是否正确执行,之后将主库启动后,检测数据是否同步

service mysql stop 使主库宕机
进行测试

更改数据:

查看从库数据:

service mysql state 启动主机
查看数据

说明实现了主从复制

关于mycat分库分表(面试问题)

  1. 名称介绍

1.逻辑库

MYCAT中对数据库进行拆分时,指定的数据库的名称,将来需要通过jdbc连接时,必须要与逻辑库的名称一致.

2.逻辑表

3.数据库的垂直拆分

4.数据库的水平拆分

2.数据库垂直拆分

说明:根据业务逻辑,将不同的功能模块的表进行拆分,拆分到多个数据库中,并且具有相同的业务逻辑的表最好保存到一个数据库中.为了关联查询.

3.数据库的水平拆分

说明:由于单表的数据量特别的庞大,直接影响查询效率,所以需要进行分表操作.

根据特定的规则(人为定义),将数据拆分到不同的数据库中,之后查询时根据特定的协议规则,进行数据的查询.

4.数据库如何优化(面试题)

  1. 优化sql 思想:首先快速定位主表信息

  2. 创建索引 不经常修改的数据

  3. 数据库缓存  mybatis中----redis缓存

  4. 定期进行数据转储   当前表和历史表(减少数据表的量)

  5. 分库分表(大型项目才会使用)

3.Redis

  1. 缓存策略

说明:缓存的主要的作用,就是降低用户访问物理设备的访问频次.并且缓存中的数据就是数据库中的数据备份.

2.Redis介绍

  1. 网站介绍

官网:Redis

2.Redis介绍

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings, 散列(hashes, 列表(lists, 集合(sets, 有序集合(sorted sets 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial 索引半径查询。 Redis 内置了 复制(replicationLUA脚本(Lua scripting, LRU驱动事件(LRU eviction事务(transactions 和不同级别的 磁盘持久化(persistence, 并通过 Redis哨兵(Sentinel和自动 分区(Cluster提供高可用性(high availability)。

Redis中存储数据采用K-v结构

Redis底层实现时C语言编辑.并且查询的速度30万/s集合运算

3.Redis下载

说明:官方建议下载redisLinux版本

4.缓存使用的注意事项

  1. 缓存中的数据和数据库如何保证数据一致性??

  2. 缓存中的数据如何持久化??

  3. 缓存中的内存空间应该如何维护??

  4. 单个缓存节点有宕机风险,如何实现高可用??

总结:以上的问题使用Redis可以全部解决.

3.Redis安装

  1. 安装redis

  1. 解压redis

2.编译redis

执行make

3.配置环境变量

make install

2.Redis命令

  1. redis-server

  2. redis-server &

  3. redis-server redis.conf    启动

  4. 关闭redis命令  redis-cli shutdown

  5. redis-cli -p 6379 shutdown

启动ok

3.编辑redis配置文件vim redis.conf

  1. 注释IP绑定

2.关闭保护模式

3.开启后台启动

4.Redis命令

redis-cli:进入客户端 

  1. Redis-String

指令

说明

案例

set

设定key的值

set name tom

get

获取指定 key 的值

get name

strlen

获取key值的长度

strlen name

exists

检查给定 key 是否存在

exists name 返回1存在  0不存在

del

删除指定的key/key1 key2

del name1 name2

keys

命令用于查找所有符合给定模式 pattern 的 key

Keys * 查询全部的key值

Keys n?me 占位符

Keys name* 以name开头的key

mset

赋值多个key-value

mset key1 value1 key2 value2 key3 value3

同时赋值多个值

mget

获取多个key

Mget key1 key2 key3

append

对指定的key进行追加

append key 123456   value123456

append key " 123456" value 123456中间多一个空格

Type

查看key的类型

Type key1 

127.0.0.1:6379> TYPE key1string

Flushdb

清空当前数据库

Flushdb 清空数据库

Select

切换数据库

Select 0-15 redis一共有16个数据库

FLUSHALL

清空全部数据库数据

flushall

Incr

自动增长1

Incr num  数据会自动加1

Incr string 数据库会报错

Decr

自动减1

Decr name 数据会自动减1

incrby

指定步长自增

Incrby 2 每次自增2

Decrby

指定步长自减

Decrby 2每次减2

Expire

指定key的失效时间单位是秒(s)

EXPIRE name1 5   5秒后数据失效

Ttl

查看key的剩余存活时间

Ttl name

-2表示失效

-1没有失效时间

Pexpire

设置失效时间(毫秒)

Pexpire name 1000 用于秒杀业务

Persist

撤销失效时间

撤销失效时间

2.电商中秒杀如何实现???

使用操作:

   1.设定失效时间

   2.准备消息队列

实现原理:

   1.在后台需要将出售的商品根据数量添加到消息队列中

2.当用户点击购买按钮时,首先通过后台服务器获取redis中key.如果该值不为null.则可以执行后续业务逻辑.如果该值为空.则返回商品已售完信息.

3.如果获取key不为null,那么则获取消息队列中的信息.如果获取的数据不为null.证明该用户可以购买商品,之后跳转订单页面

如果获取队列消息为null.表示商品已经售完,则跳转回用户页面,显示商品已售完.

  补充知识:

     问题:redis中如何解决线程安全性问题????

     说明:因为redis操作是原子性操作,所以不会有线程安全性问题.

3.作业:

1.将数据库切换为jtdb;

2.学习List列表类型

List列表类型(list)是一个存储有序的元素的集合类型.List数据类型底层是一个双端列表.可以从左右分别进行写入操作

双端列表的数据特点:查询两端数据时速度较快,查询中间数据较慢.

指令

说明

案例

lpush

将一个或多个值插入到列表左部插入

 LPUSH list1 1 2 3 4

rpush

在列表中添加一个或多个从列表右侧插入

RPUSH list1 5 6 7 8

lpop

从列表左侧移除元素,并且返回结果

 LPOP list1

rpop

从列表右侧移除元素,并且返回结果

RPOP list1

llen

获取list集合的元素个数

Llen list1

Lrange

获取指定区间内的片段值

 LRANGE list1 0 3

获取从左数第1个到第4个值

 LRANGE list1 -3 -1

从右数第三个到第一个数据

Lrange list1 0 -1 查询全部列表数据

Lrem

删除列表中指定的值

Irem key count value

当count>0,从左开始删除前count个值为value的元素

当count<0,从右侧开始删除前count个值为value的元素

当count=0时,删除所有value的元素

 LREM list1 2 2

从左数前2个为2的元素

 LREM list1 -2 3

从右数前2个为3的元素

 LREM list1 0 4

删除全部为4的元素

Lindex

根据指定索引值查询元素

LINDEX list1 0   查找索引值为0的值

 LINDEX list1 -1 查询最右边的值

Lset

为指定索引赋值

 LSET list1 0 10

LINSERT

LINSERT key  before value1 value2

在value1之前插入value2

LINSERT list1 after 1 2

LINSERT list1 before 10 100

从左数第一个为10的元素前插入100

LINSERT list1 after 1 2

从左数第一个为1的值之后插入2


day09:京淘-Redis分片,商品类目缓冲,实现

  1. Redis高级用法

    1. Redis中队列/栈

      1. 队列形式

说明:Redis中的链表是双向循环列表.如果使用队列的形式,规则如下.

从一侧压栈,从另外一侧弹栈.

队列的特点:先进先出

2.栈的形式

说明:从同一侧压栈弹栈

特点:先进后出

 2.Redis入门案例

  1. 导入jar包

<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>

2.入门案例

/**
* 1.实例化jedis对象(IP:端口)
* 2.实现redis取值赋值操作
*/
@Test
public void test01(){
Jedis jedis = new Jedis("192.168.126.166",6379);
jedis.set("name", "tomcat猫");
System.out.println("获取redis数据:"+jedis.get("name"));
}

3.报错信息

     redis.clients.jedis.exceptions.JedisDataException: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 4) Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in

启动控制台 执行

CONFIG SET protected-mode no 即可

3.Redis实际项目应用

  1. 需求说明

特点:经常访问的数据,并且变化不大的数据添加缓存

需求说明:将商品分类信息添加到缓存中.

实现思路:

  1. 当用户点击按钮时,应该先查询缓存

  2. 如果缓存数据为null,这时应该查询后台数据库,将查询到的结果通过工具类转化为JSON串.之后将数据保存到redis中key:value

之后将查询的结果返回.

     3.如果缓存数据不为null,需要通过工具API将JSON串转化为java对象.之后返回数据.

2.编辑配置文件管理jedis对象

  1. 编辑配置文件

redis.host=192.168.126.166
redis.port=6379

2.编辑spring配置文件

<!--实现spring管理redis/jedis 
Jedis jedis = new Jedis("192.168.126.166",6379);
jedis.set("name", "tomcat猫");
System.out.println("获取redis数据:"+jedis.get("name"));
-->
<bean id="jedis" class="redis.clients.jedis.Jedis">
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.port}"/>
</bean>

3.编辑Controller

@RequestMapping("/list")
@ResponseBody
public List<EasyUITree> findItemCat(@RequestParam(value="id",defaultValue="0")Long parentId){
//1.查询一级商品分类目录
//Long parentId = 0L;
//return itemCatService.findItemCatByParentId(parentId);
return itemCatService.findCacheByParentId(parentId);
}

4.编辑Service

@Override
public List<EasyUITree> findCacheByParentId(Long parentId) {
String key = "ITEM_CAT_"+parentId;
String result = jedis.get(key);
List<EasyUITree> easyUITreeList = null;
try {
//判断数据是否为null
if(StringUtils.isEmpty(result)){
//表示查询数据库
easyUITreeList =
findItemCatByParentId(parentId);
//将数据转化为JSON串
String jsonData = objectMapper.writeValueAsString(easyUITreeList);
//将数据保存到缓存中
jedis.set(key, jsonData);
return easyUITreeList;
}else{
//表示缓存数据不为null;
EasyUITree[] easyUITrees =
objectMapper.readValue(result,EasyUITree[].class);
easyUITreeList = Arrays.asList(easyUITrees);
return easyUITreeList;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

5.使用Redis好处

未使用缓存:

使用缓存:

6.作业:

自己测试redis单台的高级测试用法.

4.搭建redis分片

  1. 分片主要作用

说明:

1.由于单台的redis内存的使用量是有限的,如果需要动态的扩容内存空间,需要搭建redis分片.

   2.之前一台redis中保存了服务的全部数据,如果服务器宕机,则会影响整个的内存数据.

2.Redis分片搭建

1.复制配置文件

mkdir shard
cp redis.conf shard/redis-6381.conf

2.文件格式

3.修改端口

分别将6379修改为6380/6381

4.启动redis

[root@localhost shard]# vim redis-6380.conf

[root@localhost shard]# vim redis-6381.conf

[root@localhost shard]# clear

[root@localhost shard]# ls

redis-6379.conf  redis-6380.conf  redis-6381.conf

[root@localhost shard]# redis-server redis-6379.conf

[root@localhost shard]# redis-server redis-6380.conf

[root@localhost shard]# redis-server redis-6381.conf

5.检测redis启动是否正确

3.Redis分片入门案例

//测试redis分片 实现redis内存动态扩容
@Test
public void test02(){
//定义redis池的配置文件
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(1000);
poolConfig.setMaxIdle(200);
poolConfig.setMinIdle(10);
poolConfig.setTestOnBorrow(true); //链接前校验
//定义jedis分片的节点信息
List<JedisShardInfo> shards =
new ArrayList<JedisShardInfo>();
shards.add(new JedisShardInfo("192.168.126.166", 6379));
shards.add(new JedisShardInfo("192.168.126.166", 6380));
shards.add(new JedisShardInfo("192.168.126.166", 6381));
ShardedJedisPool jedisPool =
new ShardedJedisPool(poolConfig, shards);
ShardedJedis shardedJedis = jedisPool.getResource();
shardedJedis.set("name", "我是redis分片");
System.out.println
("获取redis信息:"+shardedJedis.get("name"));
}

5.Spring整合redis分片

  1. 编辑配置文件

redis.host=192.168.126.166
redis.port1=6379
redis.port2=6380
redis.port3=6381
redis.maxTotal=1000
redis.maxIdle=200

2.编辑分片的配置

<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!--调用set注入方式  -->
<property name="maxTotal" value="${redis.maxTotal}"/>
<property name="maxIdle" value="${redis.maxIdle}"/>
</bean>
<!--定义redis分片节点信息  -->
<bean id="info1" class="redis.clients.jedis.JedisShardInfo">
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.port1}"/>
</bean>
<bean id="info2" class="redis.clients.jedis.JedisShardInfo">
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.port2}"/>
</bean>
<bean id="info3" class="redis.clients.jedis.JedisShardInfo">
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.port3}"/>
</bean>
<!--创建分片的连接池对象  -->
<bean id="jedisPool" class="redis.clients.jedis.ShardedJedisPool">
<constructor-arg name="poolConfig" ref="jedisPoolConfig"/>
<constructor-arg name="shards">
<list>
<ref bean="info1"/>
<ref bean="info2"/>
<ref bean="info3"/>
</list>
</constructor-arg>
</bean>

3.编辑工具类

@Service
public class RedisService {
@Autowired(required=false)  //程序启动时暂时不注入,但是如果调用时自动注入
private ShardedJedisPool jedisPool;
public void set(String key,String value){
ShardedJedis shardedJedis = jedisPool.getResource();
shardedJedis.set(key, value);
jedisPool.returnResource(shardedJedis);//将链接还回池中
}
public String get(String key){
ShardedJedis shardedJedis = jedisPool.getResource();
String result = shardedJedis.get(key);
jedisPool.returnResource(shardedJedis);
return result;
}
//为key添加超时时间
public void set(String key,String value,int seconds){
ShardedJedis shardedJedis = jedisPool.getResource();
shardedJedis.setex(key, seconds, value);
jedisPool.returnResource(shardedJedis);//将链接还回池中
}
}

4.客户端调用

1.注入工具类对象

2.工具类对象调用

6.Hash一致算法

  1. 问题

问题描述:Redis分片时如何保存数据的????.redis获取数据时如何快速检索??

2.Redis中数据存取

总结:

   Node节点信息通过ip+端口+算法最终确定了内存的位置.

   当要保存key值时,首先经过hash算法计算,确定唯一的一个地址,之后按照顺时针方向绑定最近的一个节点.进行存值操作.

   如果取值时,方法类似.

3.均衡性

说明:由于所有的节点进行hash一致性的计算,可能获取的结果在内存中位置相邻.可能会导致数据库分片不均的现象.这样会造成某些节点内存溢出.某些节点内存使用率较少.为了解决这类问题.提出了均衡性.

概念:让节点尽可能保证数据的均匀.每个节点只保存其中1/n的数据.

实现:哈希一致性算法中提出了虚拟节点的概念.如果出现数据存储偏差较大,则会为少的节点添加虚拟节点,共同争抢数据,最终达到1/n

4.单调性

说明:由于节点数量可能会新增,那么保证,新增的节点和之前的节点中的数据实现动态的迁移.再一次保证数据的均衡性.

特点:hash一致性算法要求,尽可能让原有节点数据不变.

缺点:

   如果删除节点,那么节点中所保存的数据一并删除.

5.分散性

说明:一个key可能会出现多个位置

前题:由于分布式的开发.可能会导致程序不能看到全部的节点信息.从而导致分散性问题.

6.负载

说明:同一个位置出现多个key.

描述:负载是从另一个角度谈分散性.

总结:

   哈希一致算法要求,应该尽可能的降低分散和负载.所以应该操作redis时尽可能操作全部的节点.

7.Redis中数据持久化

  1. 持久化策略

说明:当数据全部存储到redis中时,如果redis出现宕机的现象.则会丢失内存中的全部数据.所以redis为了保证数据不丢失,redis自身有持久化的策略.

当redis数据操作达到配置的规定,则会将内存中的数据持久化到硬盘中.当redis再次启动时,首先加载持久化的配置文件,恢复内存中的数据.

2.RDB模式

说明:redis中默认的持久化的策略是RDB模式,该模式的持久化的效率是最高的.

命令:

  1. Save   特点:redis会立即持久化数据,其他的线程处于阻塞状态.

  2. Bgsave 特点:执行该操作,会通知redis进行持久化,不会造成线程阻塞. (类似gc)

1.RDB模式持久化策略

save 900 1    当set操作在900秒内执行1次进行持久化

save 300 10   当set操作在300秒内执行10次 进行持久化

save 60 10000 当set操作在60秒内执行10000次进行持久化

规则:当操作越频繁,持久化的周期越短.

2.RDB配置文件说明

3.RDB使用原则

说明:如果redis能够允许丢失部分数据,则使用RDB模式,因为该模式效率是最高的.

3.AOF模式

说明:AOF模式能够实现实时持久化.AOF模式默认是关闭的.需要手动开启

1.开启AOF模式

说明:如果需要开启AOF则将配置文件改为yes即可.如果开启了AOF模式那么RDB模式将不生效.

2.持久化文件名称

3.AOF的持久化策略

# appendfsync always   每做一次set操作持久化一次,效率最低
appendfsync everysec   每秒持久化一次性能略低于RDB
# appendfsync no

4.Redis中持久化文件保存


day10:京淘Redis哨兵 

  1. Redis高级-二

    1. 案例

      1. Redis数据被篡改

问题描述:由于多个节点使用相同的配置文件,那么节点启动时会造成一种现象,所有的分片的redis节点数据一致???????

说明:由于使用持久化文件名称一致,所以恢复数据时数据一致.

解决:一个redis对应各自的持久化文件.

2.Redis中内存管理

  1. 问题

Redis使用内存空间保存数据,如果一致向内存中添加数据,则会导致内存空间不足.直接影响插入的操作.

2.Redis内存设定

1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes

3.内存策略

LRU算法:

   内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据.

  1. volatile-lru  在设定超时时间的数据中采用LRU算法删除不用的数据.

  2. allkeys-lru -> 按照LRU算法在所有key中查找不用的数据进行删除

3.volatile-random -> 随机删除设定了超时时间的数据

4.allkeys-random -> 随机删除所有的key

5.volatile-ttl -> 在已经设定了超时时间的数据中通过ttl排序,将马上要过期的数据进行删除.

6.noeviction -> 不会删除数据,只会返回错误消息

7.Note: 不做操作,返回异常

4.内存规则设定

2.Redis哨兵

  1. 实现redis主从搭建

    1. 创建文件夹

说明:创建redis哨兵的文件夹sentinel.并且将之前的redis节点全部关闭.

杀死进程
kill 3282 3286 3510

2.拷贝配置文件

将redis根目录中的redis.conf分别拷贝3份 6379/6380/6381.拷贝到哨兵的文件夹中,并且修改端口号,之后启动服务器.

[root@localhost redis-3.2.8]# cp redis.conf sentinel/redis-6379.conf
[root@localhost redis-3.2.8]# cp redis.conf sentinel/redis-6380.conf
[root@localhost redis-3.2.8]# cp redis.conf sentinel/redis-6381.conf
[root@localhost redis-3.2.8]#

修改端口号

3.启动redis

4.检测节点状态

命令:info replication

5.搭建主从

定义规则:6379当主机   6380/6381当从机

作用:可以实现数据的实时同步(备份).

搭建命令: slaveof  主机IP 主机端口

Redis-cli -p 6380
SLAVEOF 192.168.126.166 6379

 6.主从测试

1.检测主机是否能够识别自己的从机

[root@localhost sentinel]# redis-cli
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.228.131,port=6380,state=online,offset=393,lag=0
slave1:ip=192.168.228.131,port=6381,state=online,offset=393,lag=0
master_repl_offset:393
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:392
127.0.0.1:6379>

2.当主库set a a时,检测从库是否有数据.表示主从挂载成功.

7.主从配置说明

说明:如果在命令提示行中执行slaveof命令,那么该操作只在内存中生效.当从机重启时,又会变为主机.主从搭建失败. 如果需要长久的保存主从的关系,应该修改配置文件.

2.哨兵工作原理

  1. 原理说明

1.当搭建哨兵时,首先哨兵会通过心跳检测,检测主机是否正常.如果主机长时间没有响应,则断定主机宕机,那么哨兵会进行推选,从之前主机中的从机中选择一个新的主机.并且把其他的配置文件中的slaveof 配置文件修改为新的主机的IP和端口.

2.对于用户而言,需要通过IP:端口链接哨兵即可.用户不需要了解当前状态谁是主机谁是从机.都通过哨兵作为中转将请求实现.

3.对于哨兵而言,负责新节点的选择,一定要避免偶数个节点,否则会出现”脑裂问题”所以哨兵等选举机制一般都是奇数个.

2.哨兵的配置文件

cp sentinel.conf sentinel/sentinel-6379.conf
  1. 修改哨兵的端口号

2.关闭保护模式

3.哨兵监控主机的配置

sentinel monitor mymaster 127.0.0.1 6379 2

mymaster:代表主机变量名称.

IP 端口:代表主机的IP和端口

2: 选举的票数   原则(超过哨兵半数即可)

4.修改推选时间

   当主机宕机10秒后,哨兵开始推选工作

5.哨兵推选失败的超时时间

3.哨兵启动

redis-sentinel sentinel-6379.conf

   

哨兵测试:

   将redis主机关闭后,检测哨兵是否会选举新的主机.

3.多个哨兵搭建

  1. 复制配置文件

2.修改哨兵端口号

3.修改哨兵的序列号

说明:将不同的哨兵 修改序列号和端口

4.修改票数

5.多个哨兵测试

将哨兵启动后,将redis主机关闭后,检测3个哨兵是否能够进行正确的推选.

6400:X 27 Jul 23:16:35.383 * +slave slave 192.168.126.166:6381 192.168.126.166 6381 @ mymaster 192.168.126.166 6379
6400:X 27 Jul 23:16:35.383 * +slave slave 192.168.126.166:6380 192.168.126.166 6380 @ mymaster 192.168.126.166 6379
6400:X 27 Jul 23:16:45.423 # +sdown slave 192.168.126.166:6380 192.168.126.166 6380 @ mymaster 192.168.126.166 6379
6400:X 27 Jul 23:17:31.752 # -sdown slave 192.168.126.166:6380 192.168.126.166 6380 @ mymaster 192.168.126.166 6379
6400:X 27 Jul 23:17:41.746 * +convert-to-slave slave 192.168.126.166:6380 192.168.126.166 6380 @ mymaster 192.168.126.166 6379

4.Spring整合哨兵

  1. Redis哨兵入门案例

//实现哨兵的测试
@Test
public void test03(){
Set<String> sentinels = new HashSet<String>();
//格式演示
//System.out.println
//(new HostAndPort("192.168.126.166", 26379).toString());
sentinels.add("192.168.126.166:26379");
sentinels.add("192.168.126.166:26380");
sentinels.add("192.168.126.166:26381");
//定义哨兵的连接池
JedisSentinelPool sentinelPool =
new JedisSentinelPool("mymaster", sentinels);
Jedis jedis = sentinelPool.getResource();
jedis.set("name", "我是哨兵的redis");
System.out.println("获取redis数据:"+jedis.get("name"));
}

2.编辑properties文件

redis.host=192.168.126.166
redis.port1=6379
redis.port2=6380
redis.port3=6381
redis.sentinel.mastername=mymaster
redis.sentinel.a=192.168.126.166:26379
redis.sentinel.b=192.168.126.166:26380
redis.sentinel.c=192.168.126.166:26381
redis.maxTotal=1000
redis.maxIdle=200

3.Spring整合哨兵

<bean id="sentinelPool"
class="redis.clients.jedis.JedisSentinelPool">
<constructor-arg name="masterName"
value="${redis.sentinel.mastername}"/>
<constructor-arg name="sentinels">
<set>
<value>${redis.sentinel.a}</value>
<value>${redis.sentinel.b}</value>
<value>${redis.sentinel.c}</value>
</set>
</constructor-arg>
</bean>

4.编辑工具类

//是操作redis工具API
@Service
public class RedisService {
@Autowired(required=false)  //程序启动时暂时不注入,但是如果调用时自动注入
//private ShardedJedisPool jedisPool;
private JedisSentinelPool sentinelPool;
public void set(String key,String value){
Jedis jedis = sentinelPool.getResource();
jedis.set(key, value)
sentinelPool.returnResource(jedis);
}
public String get(String key){
Jedis jedis = sentinelPool.getResource();
String result = jedis.get(key);
sentinelPool.returnResource(jedis);
return result;
}
}

5.效果展现

6.补充知识-跳过测试类打包

<build>
<plugins>
<!--跳过测试类打包  -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<!--tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8091</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>

day11:京淘-Redis集群搭建

  1. Redis-集群搭建 

    1. Redis分片和哨兵的问题

      1. 问题说明:

分片的优点:

   可以实现内存的动态的扩容.

哨兵的优点:

   可以实现redis的高可用.

缺点:

  1. 如果一台redis节点宕机,则整个redis分片将不能正常运行.

  2. 由于采用Hash一致性算法,如果分布式的操作,可能会导致 分散性和负载

  3. 哨兵机制中,如果哨兵出现宕机现象,则直接影响整个服务.

  4. 如果哨兵宕机,则可能会出现选举的哨兵偶数台.可能会出现脑裂的现象,使整个redis集群陷入错误中.

2.Redis集群的优点

  1. 总结

Redis集群实际上就是将分片和redis哨兵整合到一起.并且内存不需要启动哨兵的服务,.通过redis管理工具.ruby工具,使得redis内部实现高可用.

  1. 3.Redis集群搭建步骤

  2. 搭建规模

主机:3台 端口7000-7002

从机:6台 端口7003-7008

2.创建9个文件夹

3.修改配置文件

说明:将redis根目录中的redis.conf文件复制到7000中后,进行配置文件修改.

修改内容的说明:

  1. 将ip绑定注释

2.修改端口

3.关闭保护模式

4.开启后台启动

5.修改PID文件位置和名称

6.修改持久化文件的路径

7.修改内存维护策略allkeys-lru

8.开启redis集群的配置

9.启动redis节点的配置信息

10.配置redis集群选举的超时时间

4.复制配置文件

说明:分别将7000/redis.conf复制到7001-7008中.之后采用批量修改命令将7000端口改为各自对应的端口号.

[root@localhost cluster]# cp 7000/redis.conf 7001/redis.conf
[root@localhost cluster]# cp 7000/redis.conf 7002/redis.conf
[root@localhost cluster]# cp 7000/redis.conf 7003/redis.conf

vim 7001/redis.conf

:%s/7000/7001/g
:%s/7000/7002/g
:%s/7000/7003/g
:%s/7000/7004/g
:%s/7000/7005/g
:%s/7000/7006/g
:%s/7000/7007/g
:%s/7000/7008/g

5.Redis脚本启动

说明:在redis/cluster的根目录中创建文件 start.sh

命令: vim start.sh

#!/bin/sh
redis-server 7000/redis.conf &
redis-server 7001/redis.conf &
redis-server 7002/redis.conf &
redis-server 7003/redis.conf &
redis-server 7004/redis.conf &
redis-server 7005/redis.conf &
redis-server 7006/redis.conf &
redis-server 7007/redis.conf &
redis-server 7008/redis.conf &

启动redis节点:

sh start.sh  或./start.sh

关闭redis进程:

pkill -9 redis
      1. 实现集群的创建

集群创建命令

要求:该命令必须在redis的根目录下执行

命令介绍: --replicas 2 一个主机下有2个从机

./src/redis-trib.rb create --replicas 2 192.168.228.131:7000 192.168.228.131:7001 192.168.228.131:7002 192.168.228.131:7003 192.168.228.131:7004 192.168.228.131:7005 192.168.228.131:7006 192.168.228.131:7007 192.168.228.131:7008

 

搭建过程:

 

搭建成功

 

 

7.集群测试

将redis集群中的主机宕机后,检测redis高可用是否正常.之后启动redis主机,检测是否自动实现挂载

8.关于集群启动步骤

说明:如果在以后使用集群的过程中,出现服务器意外宕机的现象,则重启的步骤如下.

  1. 重启物理机

  2. 关闭防火墙

  3. 执行redis启动脚本  sh start.sh

1.Redis集群知识(二)

  1. 专业名称介绍

1.缓存穿透

条件:访问一个不存在的数据

说明:当访问一个不存在的数据时,因为缓存中没有这个key,导致缓存形同虚设.最终访问后台数据库.但是数据库中没有该数据所以返回null.

隐患:如果有人恶意频繁查询一个不存在的数据,可能会导致数据库负载高导致宕机.

总结:业务系统访问一个不存在的数据,称之为缓存穿透.

2.缓存击穿

条件:当缓存key失效/过期/未命中时,高并发访问该key

说明:如果给一个key设定了失效时间,当key失效时有一万的并发请求访问这个key,这时缓存失效,所有的请求都会访问后台数据库.称之为缓存击穿.

场景:微博热点消息访问量很大,如果该缓存失效则会直接访问后台数据库,导致数据库负载过高.

3.缓存雪崩

前提:高并发访问,缓存命中较低或者失效时

说明:假设缓存都设定了失效时间,在同一时间内缓存大量失效.如果这时用户高并发访问.缓存命中率过低.导致全部的用户访问都会访问后台真实的数据库.

场景:在高并发条件下.缓存动态更新时

1.脑裂

说明:由于推选机制同时选举出多台主机,导致程序不能正常的执行.该问题称之为脑裂.

解决办法:

  1. 机器的数量是奇数台

  2. 配置的服务器的量一定要多.

2.关于redis集群说明

  1. 什么时候集群崩溃

说明:如果主节点在宕机时没有从节点则集群崩溃.

问题:3主6从 宕机几次集群崩溃?????

特点:集群中如果主机宕机,那么从机可以继续提供服务,

当主机中没有从机时,则向其他主机借用多余的从机.继续提供服务.

如果主机宕机时没有从机可用,则集群崩溃.

答案:9台机器,宕机5-7次集群崩溃了.

2.Java程序连接异常

问题描述:

   如果java程序链接redis时,报错没有可用的集群节点时.检测虚拟机的IP地址和配置文件的地址是否匹配.

为什么:因为虚拟机的IP地址发生了变化.

9.Redis集群入门案例

/**
* 步骤:
*    1.redis的节点3主6从9个节点
*    2.每个节点需要通过IP:端口的形式进行链接
*  3.创建集群的链接对象API调用
*/
@Test
public void testCluster(){
String host = "192.168.126.166";
//定义集群的集合
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
nodes.add(new HostAndPort(host,7000));
nodes.add(new HostAndPort(host,7001));
nodes.add(new HostAndPort(host,7002));
nodes.add(new HostAndPort(host,7003));
nodes.add(new HostAndPort(host,7004));
nodes.add(new HostAndPort(host,7005));
nodes.add(new HostAndPort(host,7006));
nodes.add(new HostAndPort(host,7007));
nodes.add(new HostAndPort(host,7008));
JedisCluster jedisCluster = new JedisCluster(nodes);
jedisCluster.set("1803", "集群搭建终于完成了");
System.out.println("获取数据:"+jedisCluster.get("1803"));
}

4.Spring中的工厂模式

  1. 为什么使用工厂模式

特点:

  1. 简化代码

  2. 提供完善方法

  3. 某些特殊的对象不能直接实例化,可以使用工厂模式实例化对象

3.1JDK代理

    要求:被代理者必须实现接口.否则不能创建代理对象

3.2cglib代理

    要求:生成的代理对象是目标对象的子类

3.3选择代理的原则

    如果目标对象有接口则使用JDK,如果目标对象没有接口则使用CGLib

      3.4spring框架中可以指定代理对象的创建方式.

2.静态工厂模式

说明:在工厂模式中有一个static静态的方法.

  调用规则:类名.静态方法

  工厂类定义:

public class StaticFactory {
public static Calendar getCalendar(){
return Calendar.getInstance();
}
}

 配置文件:

<bean id="calendar1" class="com.jt.manage.factory.StaticFactory" factory-method="getCalendar"/>

3.实例工厂模式

说明:实例工厂其实就是工厂对象调用工厂方法.

调用规则:对象.方法

工厂类调用:

public class NewInstanceFactory {
public Calendar getCalendar(){
return Calendar.getInstance();
}
}

 配置文件:

<bean id="newInstanceFactory" class="com.jt.manage.factory.NewInstanceFactory"></bean>
<bean id="calendar2" factory-bean="newInstanceFactory" factory-method="getCalendar"></bean>

 工具类测试:

@Test
public void testFactory(){
ApplicationContext context =
new ClassPathXmlApplicationContext("/spring/factory.xml");
Calendar calendar1 = (Calendar) context.getBean("calendar1");
Calendar calendar2 = (Calendar) context.getBean("calendar2");
System.out.println("一:"+calendar1.getTime());
System.out.println("二:"+calendar2.getTime());
}

4.Spring工厂模式

规则:必须实现特定的接口FactoryBean.

扩展: implements BeanNameAware

public class SpringFactory implements FactoryBean<Calendar>{
@Override
public Calendar getObject() throws Exception {
System.out.println("spring调用工厂模式创建对象");
return Calendar.getInstance();
}
//获取对象的类型
@Override
public Class<?> getObjectType() {
return Calendar.class;
}
//通过工厂模式创建的对象 是否是单例的
@Override
public boolean isSingleton() {
return false;
}
}

5.Spring整合redis集群

  1. 编辑properties配置文件

#最小空闲数
redis.minIdle=100
#最大空闲数
redis.maxIdle=300
#最大连接数
redis.maxTotal=1000
#客户端超时时间单位是毫秒
redis.timeout=5000
#最大建立连接等待时间 
redis.maxWait=1000
#是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
redis.testOnBorrow=true
#redis cluster
redis.cluster0=192.168.126.166:7000
redis.cluster1=192.168.126.166:7001
redis.cluster2=192.168.126.166:7002
redis.cluster3=192.168.126.166:7003
redis.cluster4=192.168.126.166:7004
redis.cluster5=192.168.126.166:7005
redis.cluster6=192.168.126.166:7006
redis.cluster7=192.168.126.166:7007
redis.cluster8=192.168.126.166:7008

2.编辑Spring的配置文件

<!-- jedis 配置--> 
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" > 
<!--最大空闲数--> 
<property name="maxIdle" value="${redis.maxIdle}" /> 
<!--最大建立连接等待时间--> 
<property name="maxWaitMillis" value="${redis.maxWait}" /> 
<!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个--> 
<property name="testOnBorrow" value="${redis.testOnBorrow}" /> 
<property name="maxTotal" value="${redis.maxTotal}" /> 
<property name="minIdle" value="${redis.minIdle}" /> 
</bean>
<!--通过工厂模式获取数据  -->
<bean id="jedisCluster" class="com.jt.common.factory.JedisClusterFactory">
<!--引入配置文件源文件  -->
<property name="propertySource">
<value>classpath:/property/redis.properties</value>
</property>
<!--引入池配置文件  -->
<property name="poolConfig" ref="poolConfig"/>
<!--添加配置前缀-->
<property name="redisNodePrefix" value="redis.cluster"/>
</bean>

3.编辑工具类方法

//通过工厂模式创建JedisCluster对象
public class JedisClusterFactory implements FactoryBean<JedisCluster>{
private Resource propertySource; //表示注入properties文件
private JedisPoolConfig poolConfig; //注入池对象
private String redisNodePrefix;      //定义redis节点的前缀
@Override
public JedisCluster getObject() throws Exception {
Set<HostAndPort> nodes = getNodes();  //获取节点信息
JedisCluster jedisCluster =
new JedisCluster(nodes, poolConfig);
return jedisCluster;
}
//获取redis节点Set集合
public Set<HostAndPort> getNodes(){
//1.准备Set集合
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
//2.创建property对象
Properties properties = new Properties();
try {
properties.load(propertySource.getInputStream());
//2.从配置文件中遍历redis节点数据
for (Object key : properties.keySet()) {
String keyStr = (String) key;
//获取redis节点数据
if(keyStr.startsWith(redisNodePrefix)){
//IP:端口
String value = properties.getProperty(keyStr);
String[] args = value.split(":");
HostAndPort hostAndPort =
new HostAndPort(args[0],Integer.parseInt(args[1]));
nodes.add(hostAndPort);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return nodes;
}
@Override
public Class<?> getObjectType() {
return JedisCluster.class;
}
@Override
public boolean isSingleton() {
return false;
}
public Resource getPropertySource() {
return propertySource;
}
public void setPropertySource(Resource propertySource) {
this.propertySource = propertySource;
}
public JedisPoolConfig getPoolConfig() {
return poolConfig;
}
public void setPoolConfig(JedisPoolConfig poolConfig) {
this.poolConfig = poolConfig;
}
public String getRedisNodePrefix() {
return redisNodePrefix;
}
public void setRedisNodePrefix(String redisNodePrefix) {
this.redisNodePrefix = redisNodePrefix;
}
}

4.程序调用

2.JT-WEB

  1. 项目搭建

    1. 选择骨架创建项目

2.添加继承和依赖

1.添加继承

2.依赖

3.添加tomcat插件

<build>
<plugins>
<!--跳过测试类打包  -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<!--tomcat插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8092</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
2. 配置 tomcat 插件
3. 添加源码

 4.项目测试

配置完成后 , 启动项目

5.实现nginx反向代理

修改nginx配置文件

#定义前台管理系统
server {
listen 80;
server_name www.jt.com;
location / {              
proxy_pass http://localhost:8092;
#代理连接超时
proxy_connect_timeout       3;
#代理读取超时
proxy_read_timeout          3;
#代理发送超时
proxy_send_timeout          3;  
}
}

6.修改host文件

7.访问启动:

  1. 添加配置文件

    1. 配置文件说明

说明:jt-web主要负责与用户进行交互.项目中用到的全部的数据都通过特殊的方式从对应的服务器动态获取.前台只需要配置SpringMVC/Spring/静态资源文件即可.

2.导入静态配置文件

3.Web.xml配置文件说明

  1. 以监听器的方式启动Spring容器

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext*.xml</param-value>
</context-param>
<!--Spring的ApplicationContext 载入 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

对于监听器的说明:

有些就的企业现在一直沿用监听器的方式启动spring容器.该方式其实是一种懒加载的方式.当SpringMVC容器需要实例化Controller对象时,并且内部需要注入业务层Service时,该操作会被监听器所拦截.这时启动Spring容器实例化对象.之后将对象注入注入.整个容器启动完成.

2.前端控制器拦截的策略

<servlet-mapping>
<servlet-name>springmvc-web</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<!-- 防止springMVC框架返回json时和html冲突报 406 错误 -->
<servlet-mapping>
<servlet-name>springmvc-web</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>

*.html 该操作拦截所有以.html结尾的请求

/service/* 该配置拦截所有以/Service开头的请求

4.伪静态介绍

静态页面优点:

  1. 加载速度快

  2. 容易被搜索引擎收录,增加页面的友好性

静态页面缺点:

  1. 用户体验感差/交互性差

  2. 静态页面数据不安全.

动态页面优点:

  1. 用户交互性强

  2. 安全性更好AJAX

动态页面缺点:

  1. 搜索引擎不会收录动态页面  .jsp  .asp

伪静态介绍:

伪静态是相对真实静态来讲的,通常我们为了增强搜索引擎的友好面,都将文章内容生成静态页面,但是有的朋友为了实时的显示一些信息。或者还想运用动态脚本解决一些问题。不能用静态的方式来展示网站内容。但是这就损失了对搜索引擎的友好面。怎么样在两者之间找个中间方法呢,这就产生了伪静态技术。就是展示出来的是以html一类的静态页面形式,但其实是用ASP一类的动态脚本来处理的。

总结:以html静态页面展现形式的动态页面技术.

5.修改配置文件

  1. 修改Spring配置文件

2.修改配置文件后效果

6.展现京淘前台首页

  1. 编辑Controller

@Controller
public class IndexController {
@RequestMapping("/index")
public String index(){
return "index";
}
}

2.页面效果展现

5.网站跨域问题

  1. 跨域问题测试

说明:在www.jt.com中调用manage.jt.com时访问不成功.原因该操作是一个跨域请求.

浏览器不允许进行跨域请求.会将成功返回的数据进行拦截.不予显示.一切出于安全性的考虑.

2.什么是同域

规则:

   请求协议/域名/-是否相同,如果三者都一致,那么是同域访问.浏览器可以正常执行.除此之外的全部的请求都是跨域请求.

例子1:

   Http://mange.jt.com/index

   https://manage.jt.com/index

   跨域

例子2:

   前台网站:http://www.jt.com

   访问后台:Http://manage.jt.com/test.json

   跨域

例子3:

   http://manage.jt.com:8091/index

   http://manage.jt.com/index

   跨域

案例:

   Http://localhost:8091/index    http://manage.jt.com/index

   跨域

   

3.作业:

  1. 复习JSON写法  3种

  2. 查看课前资料中js跨域解决.

  3. 根据图中的信息,能否转化为对象 并且考虑用几个对象封装

  4. 将jt-mangelinux部署


day12:京淘-实现跨域 商品详情展现

  1. 跨域问题实现

    1. 用户页面通用跳转

      1. 业务需求

当用户点击登陆页面或者注册页面时.要跳转到指定的页面中去.

  1. /user/register.html

  2. /user/login.html

使用RestFul结构实现页面通用跳转

2.编辑Controller

@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/{moduleName}")
public String index(@PathVariable String moduleName){
return moduleName;
}
}

2.跨域和同域的区别

  1. 跨域测试

<script type="text/javascript">
$(function(){
$.get("http://manage.jt.com/test.json",function(data){
alert(data.name);
})
})
</script>

2.同源策略

说明:如果请求的协议://域名:端口都相同则是同源访问.可以访问数据.

除此之外的全部的访问都是跨域的,.禁止通信.

3.跨域实现

说明:利用script标签中src属性可以实现跨域.

步骤:

  1. 利用src属性请求远程的js

  2. 将返回值的JSON串进行特殊的处理.为返回值添加函数名称 hello(data)格式

  3. 在客户端定义回调函数 function hello(data){…..}

页面编辑:

<script type="text/javascript">
/*声明了一个函数   该函数需要被别人调用  */
function hello(data){
alert(data.name);
}  
</script>
<script type="text/javascript" src="http://manage.jt.com/test.json"></script>
<script type="text/javascript" src="http://manage.jt.com/js/jquery-easyui-1.4.1/jquery.min.js"></script>

定义返回值类型:

hello({"id":"1","name":"tom"})

4.传统的JS中存在的问题

  1. 如果函数的名称和JSON串的返回值不同则无法跨域.

  2. 每次跨域请求都需要借用javaScript中的src属性.调用不直观.而且繁琐.

如何解决:

  1. 发起请求时将函数名称当成参数传递即可

addUser?callback=hello.

后台接收callback参数最终封装为特殊格式的JSON串.hello(Data)

  2.将调用的过程抽取为公共的方法.以AJAX的形式进行封装.并且提供良好品参数传值的方式.

Jquery中的JSONP请求格式.

5.JSONP介绍

JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于server1.example.com的网页无法与不是server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

总结:script利用src属性实现跨域请求就是JSONP.

3.jQuery中JSONP使用

  1. 编辑页面JS

$.ajax({
url:"http://manage.jt.com/web/testJSONP",
type:"get",
dataType:"jsonp",      //返回值的类型
//jsonp: "callback",   //指定参数名称
//jsonpCallback: "hello",  //指定回调函数名称
success:function (data){
alert(data.id);
//转化为字符串使用
//var obj = eval("("+data+")");
//alert(obj.name);
}  
});   

2.编辑服务端Controller

@Controller
@RequestMapping("/web")
public class WebJSONPController {
//http://manage.jt.com/web/testJSONP?callback=jQuery111102185225838437006_1537844552339&_=1537844552340
//@RequestMapping(value="/testJSONP",produces="text/html;charset=utf-8")
//@ResponseBody
public String jsonp(String callback) throws JsonProcessingException{
User user = new User();
user.setId(100);
user.setName("tomcat猫");
ObjectMapper objectMapper = new ObjectMapper();
String userJSON = objectMapper.writeValueAsString(user);
//因为返回值必须添加回调函数,否则无法解析. hello(data)
return callback+"(" + userJSON + ")";
}
//利用Spring中的JSONP返回
@RequestMapping(value="/testJSONP")
@ResponseBody
public MappingJacksonValue jsonpSuper(String callback){
User user = new User();
user.setId(100);
user.setName("tomcat猫");
MappingJacksonValue value = new MappingJacksonValue(user);
value.setJsonpFunction(callback);
return value;
}
}

2.构建jt-sso项目

  1. 构建项目

    1. 创建项目

2.添加继承和依赖

1.添加继承

2.添加依赖

3.添加tomcat插件

  1. 添加tomcat插件

<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8093</port>
<!--项目的发布路径  -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>

2.添加tomcat插件

4.修改Nginx配置文件

#单点登录管理系统
server {
listen 80;
server_name sso.jt.com;
location / {
#实现服务器代理
proxy_pass  http://localhost:8093;
proxy_connect_timeout       3; 
proxy_read_timeout          3; 
proxy_send_timeout          3; 
}
}

修改Hosts文件  127.0.0.1   sso.jt.com

5.效果测试

2.添加配置文件

  1. 导入web.xml

说明:将jt-manage后台中的web.xml导入sso中,因为前天的web.xml中拦截路径.html结尾的请求

2.修改Spring配置文件

3.修改Mybatis配置文件

4.创建POJO对象

5.修改mapper映射文件的路径

3.用户信息校验

  1. 分析页面JS

2.接口文档规范

请求方法

GET

URL

http://sso.jt.com/user/check/{param}/{type}

参数

格式如:chenchen/1

其中chenchen是校验的数据

Type为类型,可选参数1 username、2 phone、3 email

示例

http://sso.jt.com/user/check/chenchen/1

返回值

{

status: 200  //200 成功,201 没有查到

msg: “OK”  //返回信息消息

data: false  //返回数据true用户已存在,false用户不存在,可以

}

3.编辑后台Controller

@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//JSONP格式返回   param 表示校验参数   type表示校验类型
@RequestMapping("/check/{param}/{type}")
@ResponseBody
public MappingJacksonValue checkUser(
@PathVariable String param,
@PathVariable int type,
String callback){
//校验用户名信息是否存在
boolean flag = userService.findCheckUser(param,type);
MappingJacksonValue jacksonValue =
new MappingJacksonValue(SysResult.oK(flag));
jacksonValue.setJsonpFunction(callback);
return jacksonValue;
}
}

4.编辑Service

/**
* 思考:
*    admin/1   表示校验用户名为admin的数据
*    sql: select count(*) from tb_user where username = "admin"
*    Type为类型,可选参数1 username、2 phone、3 email
*/
@Override
public boolean findCheckUser(String param, int type) {
String cloumn = null;
switch (type) {
case 1:
cloumn = "username"; break;
case 2:
cloumn = "phone"; break;
case 3:
cloumn = "email"; break;
}
int count = userMapper.findCheckUser(param,cloumn);
//如果返回true表示用户已经存在
return count == 0 ? false : true;
}

5.编辑Mapper接口/映射文件

int findCheckUser(@Param("param")String param,
@Param("cloumn")String cloumn);

编辑映射文件

<mapper namespace="com.jt.sso.mapper.UserMapper">
<select id="findCheckUser" resultType="int">
select count(*) from tb_user where ${cloumn} = #{param}
</select>
</mapper>

6.页面效果

4. HttpClient学习

  1. HttpClient介绍

HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。现在HttpClient最新版本为 HttpClient 4.5 (GA) (2015-09-11)

总结:HTTPClient是java发起Http请求协议的工具包

2.导入jar包

<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.3.1</version>
</dependency>

3.HttpClient入门案例

@Test
public void test01() throws ClientProtocolException, IOException{
//1.获取HttpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.定义网址
String url  = "https://item.jd.com/188550.html";
//3.定义请求方式 GET/POST
HttpGet httpGet = new HttpGet(url);
HttpPost httpPost = new HttpPost(url);
//4.发送请求
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
//5.判断返回值数据是否正确
if(httpResponse.getStatusLine().getStatusCode() == 200){
System.out.println("表示请求有效的");
//获取返回值实体内容
String result = EntityUtils.toString(httpResponse.getEntity());
System.out.println(result);
}
}

5.Spring整合HttpClient

  1. 编辑配置文件

#从连接池中获取到连接的最长时间
http.request.connectionRequestTimeout=500
#5000
http.request.connectTimeout=5000
#数据传输的最长时间
http.request.socketTimeout=30000
#提交请求前测试连接是否可用
http.request.staleConnectionCheckEnabled=true
#设置连接总数
http.pool.maxTotal=200
#设置每个地址的并发数
http.pool.defaultMaxPerRoute=100

添加配置文件

2.编辑HttpClient配置文件

<!-- 定义httpclient连接池 -->
<bean id="httpClientConnectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" destroy-method="close">
<!-- 设置连接总数 -->
<property name="maxTotal" value="${http.pool.maxTotal}"></property>
<!-- 设置每个地址的并发数 -->
<property name="defaultMaxPerRoute" value="${http.pool.defaultMaxPerRoute}"></property>
</bean>
<!-- 定义 HttpClient工厂,这里使用HttpClientBuilder构建-->
<bean id="httpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder" factory-method="create">
<property name="connectionManager" ref="httpClientConnectionManager"></property>
</bean>
<!-- 得到httpClient的实例 -->
<bean id="httpClient" factory-bean="httpClientBuilder" factory-method="build"/>
<!-- 定期清理无效的连接 -->
<bean class="com.jt.common.util.IdleConnectionEvictor" destroy-method="shutdown">
<constructor-arg index="0" ref="httpClientConnectionManager" />
<!-- 间隔一分钟清理一次 -->
<constructor-arg index="1" value="60000" />
</bean>
<!-- 定义requestConfig的工厂 -->
<bean id="requestConfigBuilder" class="org.apache.http.client.config.RequestConfig.Builder">
<!-- 从连接池中获取到连接的最长时间 -->
<property name="connectionRequestTimeout" value="${http.request.connectionRequestTimeout}"/>
<!-- 创建连接的最长时间 -->
<property name="connectTimeout" value="${http.request.connectTimeout}"/>
<!-- 数据传输的最长时间 -->
<property name="socketTimeout" value="${http.request.socketTimeout}"/>
<!-- 提交请求前测试连接是否可用 -->
<property name="staleConnectionCheckEnabled" value="${http.request.staleConnectionCheckEnabled}"/>
</bean>  
<!-- 得到requestConfig实例  -->
<bean id="requestConfig" factory-bean="requestConfigBuilder" factory-method="build" />

3. 编辑HttpClient工具API-POST

@Service
public class HttpClientService {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientService.class);
@Autowired(required=false)
private CloseableHttpClient httpClient;
@Autowired(required=false)
private RequestConfig requestConfig;
/**
* 1.实现HttpClientPost方法
* 思考:
*       1.需要设定url参数
*       2.Map<String,String> 使用Map数据结构实现参数封装
*       3.设定字符集编码 utf-8
*       
*       难点:POST如何传递参数?????
*       Post请求将参数转化为二进制字节流信息进行数据传输.
*       一般form表单提交试用POST提交
*       回顾: get http://addUser?id=1&name=tom
*/
public String doPost(String url,Map<String,String> params,
String charset){
String result = null;
//1.判断字符集编码是否为null 如果参数为空则默认为UTF-8
if(StringUtils.isEmpty(charset)){
charset = "UTF-8";
}
//2.获取请求对象的实体
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
try {
//3.判断用户是否传递参数
if(params != null){
//将用户传入的数据Map封装到List集合中
List<NameValuePair> parameters = new ArrayList<>();
for (Map.Entry<String, String> entry: params.entrySet()) {
BasicNameValuePair nameValuePair =
new BasicNameValuePair(entry.getKey(), entry.getValue());
parameters.add(nameValuePair);
}
//实现参数封装
UrlEncodedFormEntity formEntity =
new UrlEncodedFormEntity(parameters, charset);
//将参数封装为formEntity进行数据传输
httpPost.setEntity(formEntity);
}
//4.发起Post请求
CloseableHttpResponse httpResponse =
httpClient.execute(httpPost);
//5.判断请求结果是否正确
if(httpResponse.getStatusLine().getStatusCode() == 200){
//6.获取服务端回传数据
result = EntityUtils.toString(httpResponse.getEntity(),charset);
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public String doPost(String url){
return doPost(url, null, null);
}
public String doPost(String url,Map<String,String> params){
return doPost(url, params, null);
}
}

编辑完工具类后将项目打包

6.实现用户注册

  1. 页面JS

2.查看接口文档

   

POST

URL

http://sso.jt.com/user/register

参数

username 用户名

password 密码

phone 手机号

email 邮箱

示例

http://sso.jt.com/user/register

username:

chenchen

password:

123456

phone:

13579003045

email:

chenchen@163.com

返回值

{

status: 200  //200 成功,201 没有查到

msg: “OK”  //返回信息消息

data:username  //返回数据username值

}

3.编辑前台Controller

//http://www.jt.com/service/user/doRegister
@RequestMapping("/doRegister")
@ResponseBody
public SysResult saveUser(User user){
try {
userService.saveUser(user);
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201,"新增用户失败");
}

4.编辑前台Service

/**
* 问题:
*    因为前台项目只负责数据的展现,不负责数据更新操作
*
* 如何将数据传给sso单点登录系统???
* HTTP协议 GET/POST
* HTTPClient技术:java代码中发起Http请求的
*
*/
@Override
public void saveUser(User user) {
String url = "http://sso.jt.com/user/register";
Map<String,String> params = new HashMap<>();
params.put("username", user.getUsername());
params.put("password", user.getPassword());
params.put("phone", user.getPhone());
params.put("email", user.getEmail());
//前台通过httpClient将数据进行远程传输.如果程序在后台执行错误!!
String result = httpClient.doPost(url, params);
try {
//检测返回值结果是否正确
SysResult sysResult = objectMapper.readValue(result, SysResult.class);
if(sysResult.getStatus() != 200){
//表示程序有错
throw new RuntimeException();
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}

5.编辑后台Controller

//编辑sso后台用户新增业务
@RequestMapping("/register")
@ResponseBody
public SysResult saveUser(User user){
try {
userService.saveUser(user);
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201, "新增失败");
}

6.编辑后台Service

//编辑sso新增业务 1.数据不全
@Override
public void saveUser(User user) {
String md5Pass = DigestUtils.md5Hex(user.getPassword());
user.setPassword(md5Pass);//将密码进行加密
user.setEmail(user.getPhone());//暂时代替,否则入库报错
user.setCreated(new Date());
user.setUpdated(user.getCreated());
userMapper.insert(user);
}

7.HttpClient调用流程

7.实现商品信息回显

  1. 页面分析

说明:根据el表达式获取页面数据

2.编辑前台Controller

@Controller
@RequestMapping("/items")
public class ItemController {
@Autowired
private ItemService itemService;
@RequestMapping("/{itemId}")
public String findItemById(@PathVariable Long itemId,Model model){
//获取远程商品信息
Item item = itemService.findItemById(itemId);
model.addAttribute("item", item);
return "item";
}
}  

3.编辑前台Service

@Service
public class ItemServiceImpl implements ItemService {
@Autowired
private HttpClientService httpClient;
private static final ObjectMapper objectMapper = new ObjectMapper();
//前台获取后台的商品信息
@Override
public Item findItemById(Long itemId) {
String url = "http://manage.jt.com/web/item/findItemById";
Map<String, String> params = new HashMap<String, String>();
params.put("itemId", itemId+"");
Item item = null;
//返回的Item的JSON
String resultJSON = httpClient.doPost(url, params);
try {
item = objectMapper.readValue(resultJSON, Item.class);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return item;
}
}

4.编辑前台POJO对象

说明:为了后期添加购物车实现首张图片回显.添加以下操作

5.编辑后台Controller

@Controller
@RequestMapping("/web/item")
public class WebItemController {
@Autowired
private ItemService itemService;
//编辑manage后台实现商品信息查询
@RequestMapping("/findItemById")
@ResponseBody
public Item findItemById(Long itemId){
return itemService.findItemById(itemId);
}
}

6.编辑后台Service

@Override
public Item findItemById(Long itemId) {
return itemMapper.selectByPrimaryKey(itemId);
}

7.页面效果展现


day13:实现商品详情展现

  1. 页面分析

2.编辑Controller

@RequestMapping("/{itemId}")
public String findItemById(@PathVariable Long itemId,Model model){
//获取远程商品信息
Item item = itemService.findItemById(itemId);
ItemDesc itemDesc = itemService.findItemDescById(itemId);
model.addAttribute("item", item);
model.addAttribute("itemDesc", itemDesc);
return "item";
}

3.编辑Service

@Override
public ItemDesc findItemDescById(Long itemId) {
String url = "http://manage.jt.com/web/item/findItemDescById";
Map<String, String> params = new HashMap<String, String>();
params.put("itemId", itemId+"");
ItemDesc itemDesc = null;
//返回的Item的JSON
String resultJSON = httpClient.doPost(url, params);
try {
itemDesc = objectMapper.readValue(resultJSON, ItemDesc.class);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return itemDesc;
}

4.编辑后台Controller

//http://manage.jt.com/web/item/findItemDescById
@RequestMapping("/findItemDescById")
@ResponseBody
public ItemDesc findItemDescById(Long itemId){
return itemService.findItemDescById(itemId);
}

5.编辑后台Service

@Override
public ItemDesc findItemDescById(Long itemId) {
return itemDescMapper.selectByPrimaryKey(itemId);
}

2.单点登录实现

  1. SSO单点登录介绍

    1. SSO介绍

SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。

2.实现单点登录设想

问题:用户的登陆信息只保存到一台业务服务中,其他的服务器需要使用用户信息时,数据无法实现共享.

回忆:

  1. url重写技术  动态拼接sessionId  效率太低

解决Cookie禁用问题.大型公司一般不会考虑该问题.

      2. IP_hash  将用户IP绑定到指定的一台服务器上.

3.单点登录实现

  1. 当用户输入用户名和密码时将数据发送到单点登录系统中.

  2. 单点登录系统接收到用户名和密码后,进行校验.如果用户名和密码不正确,直接错误返回即可.如果用户名和密码是正确的.将用户的信息转化为JSON串,并且生成加密的秘钥token.将数据保存到redis中.token当做key.userJSON当做value.将用户的token数据返回给客户端.

  3. 获取到服务器返回值之后,如果用户名和密码不正确,则给用户进行提示.如果用户登陆成功.需要将token数据保存到客户端的Cookie中.并且实现页面跳转.同时实现数据的回显.

2.用户登陆实现

  1. 页面分析

  1. url分析

2.ajax分析

2.编辑webController

//实现用户登陆操作
@RequestMapping("/doLogin")
@ResponseBody
public SysResult doLogin(User user,HttpServletResponse response){
try {
String token = userService.findUserByUP(user);
//判断登陆是否有效
if(StringUtils.isEmpty(token)){
return SysResult.build(201, "用户登陆失败");
}
//将token数据保存到Cookie中
Cookie cookie = new Cookie("JT_TICKET", token);
cookie.setPath("/");   //cookie保存路劲  一般都是/
/**
* cookie的声明周期  单位是秒
* >0    生命的存活时间
* =0         表示立即删除cookie
* -1     表示当会话结束删除cookie
*/
cookie.setMaxAge(3600 * 24 * 7);
response.addCookie(cookie);
return SysResult.oK();
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201, "用户查询失败");
}

3.接口文档定义

请求方法

POST

URL

http://sso.jt.com/user/login

参数

u 用户名

p 密码

示例

http://sso.jt.com/user/login

u:

chenchen

p:

123456

返回值

{

status: 200

msg: “OK”

data:” e10adc3949ba59abbe56e057f20f883e” //登录成功,返回ticket

}

备注

登录完成,返回ticket,前台系统写入cookie

ticket算法

唯一标识每个用户:动态唯一

安全:混淆md5加密

md5(“JT_TICKET_” + System.currentTime + username)

4.定义webService

/**
* 1.定义远程url
* 2.封装参数
* 3.发起httpClient请求 resultJSON   sysResultJSON
* 4.判断返回值的状态是否为200,如果不是200  抛出异常
* 5.如果状态码为200.获取token数据之后返回.
*/
@Override
public String findUserByUP(User user) {
String token = null;
String url = "http://sso.jt.com/user/login";
Map<String,String> params = new HashMap<>();
params.put("username", user.getUsername());
params.put("password", user.getPassword());
String resultJSON = httpClient.doPost(url, params);
try { 
SysResult sysResult =
objectMapper.readValue(resultJSON, SysResult.class);
if(sysResult.getStatus() == 200){
//获取token数据
token = (String) sysResult.getData();
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return token;
}

5.编辑SSOController

@RequestMapping("/login")
@ResponseBody
public SysResult findUserByUP(User user){
try {
String token = userService.findUserByUP(user);
if(StringUtils.isEmpty(token)){
return SysResult.build(201,"用户查询失败");
}
return SysResult.oK(token);
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201,"用户查询失败");
}

6.编辑SSOService

/**
* 编辑sso 业务实现
* 1.根据用户名和密码查询数据 检查数据是否正确
*     无数据: 用户名和密码不正确  直接返回 null  throw
*     有数据: 用户名和密码正确
* 2.根据加密算法 生成tokenmd5
*    (“JT_TICKET_” + System.currentTime + username)
* 3.将用户信息转化为userJSON数据.将token:userJSON保存到redis中
* 4.将token数据返回.
*
*/
@Override
public String findUserByUP(User user) {
user.setPassword(DigestUtils.md5Hex(user.getPassword()));
User userDB = userMapper.findUserByUP(user);
String returnToken = null;
if(userDB == null){
throw new RuntimeException();
}
String token = "JT_TICKET_" + System.currentTimeMillis() + user.getUsername();
returnToken = DigestUtils.md5Hex(token);
try {
String userJSON = objectMapper.writeValueAsString(userDB);
jedisCluster.setex(returnToken, 3600 * 24 * 7, userJSON);
System.out.println("用户单点登录成功!!!");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return returnToken;
}

7.编辑SSOMapper

@Select("select * from tb_user where username = #{username} and password = #{password}")
User findUserByUP(User user);

3.用户数据回显

  1. 页面分析

1.页面url

2.页面js

2.编辑SSOController

//根据用户Cookie信息查询用户
@RequestMapping("/query/{token}")
@ResponseBody
public MappingJacksonValue findUserByTicket(
@PathVariable String token,
String callback){
String userJSON = jedisCluster.get(token);
MappingJacksonValue jacksonValue = null;
if(!StringUtils.isEmpty(userJSON)){
//redis中的数据不为null
jacksonValue = new MappingJacksonValue(SysResult.oK(userJSON));
}else{
jacksonValue = new MappingJacksonValue(SysResult.build(201,"用户查询失败"));
}
jacksonValue.setJsonpFunction(callback);
return jacksonValue;
}

4.用户登出操作

  1. 编辑webController

/**
* 用户登出操作
* 1.删除redis
*       1.从cookie中获取token
*       2.从redis删除key
* 2.删除cookie
*       JT_TICKET    
* @return
*/
@RequestMapping("/logout")
public String logout(HttpServletRequest request,HttpServletResponse response){
//获取cookie
Cookie[] cookies = request.getCookies();
String token = null;
for (Cookie cookie : cookies) {
if("JT_TICKET".equals(cookie.getName())){
token  = cookie.getValue();
break;
}
}
//删除redis中的数据
jedisCluster.del(token);
//删除Cookie
Cookie cookie = new Cookie("JT_TICKET", "");
cookie.setPath("/");
cookie.setMaxAge(0);  //立即删除Cookie
response.addCookie(cookie);
//重定向到系统首页
return "redirect:/index.html";
}

3.京淘购物车实现

  1. 搭建项目

    1. 创建项目

1.构建项目

2.添加继承和依赖

3.添加tomcat插件

<build>
<plugins>
<!--跳过测试类打包  -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8094</port>
<!--项目的发布路径  -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>

4.配置Nginx实现反向代理

#单点登录管理系统
server {
listen 80;
server_name cart.jt.com;
location / {
#实现服务器代理
proxy_pass  http://localhost:8094;
proxy_connect_timeout       3; 
proxy_read_timeout          3; 
proxy_send_timeout          3; 
}
}

修改hosts文件

2.添加配置文件

  1. 修改web.xml

2.修改Spring配置文件

3.修改Mycatis配置文件

4.修改mybatis映射文件

<mapper namespace="com.jt.cart.mapper.CartMapper">
</mapper>

3.编辑pojo文件

2.购物车展现

  1. 编辑HttpClient中doGet方法

/**
* 实现get请求
* 说明:
*    get请求中的参数是经过拼接形成的.
*    localhost:8091/addUser?id=1&name=tom
* 1   www.jt.com?id=1&name=tom&
String basicUrl = url + "?";
for (Map.Entry<String, String> entry : params.entrySet()) {
basicUrl = basicUrl + entry.getKey() + "="
+ entry.getValue() + "&";
}
url = basicUrl.substring(0, basicUrl.length()-1);*/
public String doGet(String url,Map<String,String> params,
String charset){
String result = null;
//1.判断字符集编码是否为空
if(StringUtils.isEmpty(charset)){
charset = "UTF-8";
}
try {
//2.判断是否有参数   urlwww.jt.com
if(params !=null){
//通过工具类的写法实现路径的自动的拼接
URIBuilder builder = new URIBuilder(url);
for (Map.Entry<String,String> entry : params.entrySet()) {
builder.addParameter(entry.getKey(), entry.getValue());
}
//将路径进行拼接   addUser?id=1&name=tom
url = builder.build().toString();
System.out.println("获取请求路径:"+url);
}
//定义请求类型
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(requestConfig);
//发起请求
CloseableHttpResponse httpResponse  = httpClient.execute(httpGet);
if(httpResponse.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(httpResponse.getEntity(),charset);
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public String doGet(String url,Map<String,String> params){
return doGet(url, params, null);
}
public String doGet(String url){
return doGet(url,null,null);
}

编辑完成后将项目打包

2.页面分析

3.编辑webController

@Controller
@RequestMapping("/cart")
public class CartController {
@Autowired
private CartService cartService;
//http://www.jt.com/cart/show.html
@RequestMapping("/show")
public String findCartByUserId(Model model){   
Long userId = 7L;  //暂时写死
//获取购物车列表信息
List<Cart> cartList = cartService.findCartByUserId(userId);
model.addAttribute("cartList", cartList);
return "cart"; //返回购物车页面
}
}

4、编辑CartServiceImpl

@Service
public class CartServiceImpl implements CartService{

@Autowired
private HttpClientService httpClient;

private static final ObjectMapper objectMapper = new ObjectMapper();

@Override
public List<Cart> findCartByUsedrId(Long userId) {
String url = "http://cart.jt.com/cart/query/"+userId;
String resultJSON = httpClient.doGet(url);
List<Cart>cartList = null;

try {
SysResult sysResult = objectMapper.readValue(resultJSON, SysResult.class);
cartList=(List<Cart>)sysResult.getData();
} catch (Exception e) {
e.printStackTrace();
}
return cartList;
}
}

5.编辑后台CartController

@Controller
@RequestMapping("/cart")
public class CartController {
@Autowired
private CartService cartService;
@RequestMapping("/query/{userId}")
@ResponseBody
public SysResult findCartByUserId(@PathVariable Long userId){
try {
List<Cart> cartList = cartService.findCartByUserId(userId);
return SysResult.oK(cartList);
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201,"购物车查询失败");
}
}

6.编辑后台CartService

@Service
public class CartServiceImpl implements CartService {
@Autowired
private CartMapper cartMapper;
@Override
public List<Cart> findCartByUserId(Long userId) {
Cart cart = new Cart();
cart.setUserId(userId);
return cartMapper.select(cart);
}
}

7.页面效果

3.购物车新增

  1. 页面分析

1.页面url

2.页面JS

<form id="cartForm" method="post">

<input class="text" id="buy-num" name="num" value="1" onkeyup="setAmount.modify('#buy-num');"/>

<input type="hidden" class="text"  name="itemTitle" value="${item.title }"/>

<input type="hidden" class="text" name="itemImage" value="${item.images[0]}"/><!--获取第一张图片 -->

<input type="hidden" class="text" name="itemPrice" value="${item.price}"/>

</form>

//利用post传值

function addCart(){

var url = "http://www.jt.com/cart/add/${item.id}.html";

document.forms[0].action = url;      //js设置提交链接

document.forms[0].submit();       //js表单提交

}

2.编辑前台Controller

@RequestMapping("/add/{itemId}")
public String saveCart(@PathVariable Long itemId,Cart cart){

Long userId = 7L;
cart.setUserId(userId);
cart.setItemId(itemId);
cartService.saveCart(cart);
//跳转到购物车列表页面
return "redirect:/cart/show.html";
}

3.编辑前台Service

//编辑前台 Service业务 将cart数据入库

@Override

public void saveCart(Cart cart) {

String url = "http://cart.jt.com/cart/save";

Map<String, String> params = new HashMap<>();

try {

//为了传参简单 将数据转化为json

String cartJSON = objectMapper.writeValueAsString(cart);

params.put("cartJSON", cartJSON);

httpClient.doPost(url, params);

} catch (Exception e) {

e.printStackTrace();

}

}

4.编辑后台Controller

//编辑后台购物车新增

@RequestMapping("/save")

@ResponseBody

public SysResult saveCart(String cartJSON){

try {

Cart cart = objectMapper.readValue(cartJSON,Cart.class);

cartService.saveCart(cart);

return SysResult.oK();

} catch (Exception e) {

e.printStackTrace();

}

return SysResult.build(201,"购物车新增失败");

}

5.编辑后台Service

/**

* 新增购物车业务逻辑

*  如果用户购买相同的商品应该做数量的更新

*  如果购买新的商品则做新增操作

* 业务实现:

*    通过 userId和ItemId查询数据库,检查用户是否有购买行为

*

*/

@Override

public void saveCart(Cart cart) {

//1.根据itemId和userId查询数据库

Cart cartDB = cartMapper.findCartByUI(cart);



if(cartDB == null){

//表示用户没有购买过该商品 做新增操作

cart.setCreated(new Date());

cart.setUpdated(cart.getCreated());

cartMapper.insert(cart);

}else{

//表示用户购买过商品 做数量的更新

int num = cart.getNum() + cartDB.getNum();

cartDB.setNum(num);

cartDB.setUpdated(new Date());

cartMapper.updateByPrimaryKeySelective(cartDB);

}

}

6.编辑Mapper接口

1.编辑mapper接口

2.编辑mapper映射文件

<mapper namespace="com.jt.cart.mapper.CartMapper">
<select id="findCartByUI" resultType="Cart">

select * from tb_cart where user_id = #{userId} and item_id = #{itemId}

</select>

</mapper>

7.页面效果


day14购物车数量修改

1.406报错

过程描述:

  1. 请求格式

http://www.jt.com/cart/update/num/562379/16.html

    页面报错:

406报错表示浏览器解析数据时,格式不匹配导致的.因为请求是以.html结尾的.那么浏览器在编译时,会动态的加载静态页面.要求服务器的返回值必须返回html页面.

如果服务器没有正确返回html页面信息则回报406错误.

总结:

     如果页面要求跳转一般采用.html请求结尾.

     如果请求是AJAX那么请求不能以.html结尾.所以有如下的配置

<!-- 防止springMVC框架返回json时和html冲突报 406 错误 -->

<servlet-mapping>

<servlet-name>springmvc-web</servlet-name>

<url-pattern>/service/*</url-pattern>

</servlet-mapping>

2.页面分析

知识回顾:

   前提:如果在js中对数据进行操作(加减等运算,或者对象的操作),必须将数据转化为js对象.否则不能操作.

   _thisInput.val(eval(_thisInput.val()) + 1);

1.url定义

2.js定义

itemNumChange : function(){

$(".increment").click(function(){//+

var _thisInput = $(this).siblings("input");

_thisInput.val(eval(_thisInput.val()) + 1);

$.post("/service/cart/update/num/"+_thisInput.attr("itemId")+"/"+_thisInput.val(),function(data){

//$.post("/cart/update/num/"+_thisInput.attr("itemId")+"/"+_thisInput.val()+".html",function(data){

TTCart.refreshTotalPrice(); //将商品的数量和价格乘积完成后回显.

});

});

3.编辑前台Controller

@RequestMapping("/update/num/{itemId}/{num}")

@ResponseBody

public SysResult updateCartNum(@PathVariable Long itemId,@PathVariable Integer num){

try {

//1.获取用户Id

Long userId = 7L;

Cart cart = new Cart();

cart.setUserId(userId);

cart.setItemId(itemId);

cart.setNum(num);

cartService.updateCartNum(cart);

return SysResult.oK();

} catch (Exception e) {

e.printStackTrace();

}



return SysResult.build(201, "购物车数量修改失败");

}

4.修改前台Service

@Override

public void updateCartNum(Cart cart) {

String url = "http://cart.jt.com/cart/update/num/"

+cart.getUserId()+"/"+cart.getItemId()+"/"+cart.getNum();

httpClient.doGet(url);

}

5.编辑后台Controller

//实现cart数量的修改

@RequestMapping("/update/num/{userId}/{itemId}/{num}")

@ResponseBody

public SysResult updateCartNum(

@PathVariable Long userId,

@PathVariable Long itemId,

@PathVariable Integer num){

try {

Cart cart = new Cart();

cart.setUserId(userId);

cart.setItemId(itemId);

cart.setNum(num);

cartService.updateCartNum(cart);

return SysResult.oK();

} catch (Exception e) {

e.printStackTrace();

}

return SysResult.build(201, "购物车数量修改失败");

}

6.编辑后台Service

@Override
public void updateCartNum(Cart cart) {
cart.setUpdated(new Date());
cartMapper.updateCartNum(cart);
}

7.编辑后台Mapper

1.编辑接口文件

2.编辑映射文件

<!--修改购物车数量  -->

<update id="updateCartNum">

update tb_cart set num = #{num}, updated = #{updated} where

item_id = #{itemId} and user_id = #{userId}

</update>

2.实现京淘权限控制

  1. 业务需求

    1. 需求说明

规定:如果用户没有登陆.则不能直接访问购物车列表数据/订单数据/物流数据等.

预案:如果用户没有登陆则先让用户跳转到登陆页面.

实现:使用SpringMVC提供的拦截器

  1. 定义拦截路径(拦截的业务)

  2. 拦截器中具体的操作.

2.使用拦截器步骤

  1. 首先判断用户是否登录

1.1判读用户是否有cookie

1.2根据token数据检测redis缓存中是有用户信息

2.如果用户没有登陆则转化到用户登陆页面.如果用户已经登陆则拦截器放行

3.配置拦截器

<!--springMVC拦截器配置

Content Model : (mapping+, exclude-mapping*, (bean | ref))

/* 表示只拦截一级目录    /cart/show    

/**  表示拦截多级目录 /cart/show/aaa/bbb

<mvc:exclude-mapping path=""/>

-->

<mvc:interceptors>

<mvc:interceptor>

<mvc:mapping path="/cart/**"/>

<bean class="com.jt.web.intercept.UserInterceptor"/>

</mvc:interceptor>

</mvc:interceptors>

4.如何实现动态获取用户

说明:一般公司中可以通过域的方式动态的获取用户信息.

例如:session域.和session不能共享.有没有关系

采用拦截器可以在特定的服务器中实现用户信息通过Session获取.因为操作的是同一个服务器.同一个线程.

用户通过Session获取数据的案例:

if(!StringUtils.isEmpty(userJSON)){
User user = objectMapper.readValue(userJSON, User.class);
request.getSession().setAttribute("JT_USER", user);
return true;//表示用户已经登陆 可以放行
}

服务器端代码

User user = (User) request.getSession().getAttribute("JT_USER");

Long userId = user.getId(); //暂时写死

该方式问题:

  1. 如果需要获取用户信息,那么在Controller的方法中必须添加Request对象.

  2. 如果在业务层想要获取用户信息.那么必须通过传参的方式从controller将参数传递到Service层中.

2.ThreadLocal

  1. 概念

说明:使用ThreadLocal可以实现在一个线程内实现数据共享.它是JDK元素提供的API.并且内部实现时线程是安全的.

说明:一般使用ThreadLocal传递数据都是公用的数据.ThreadLocal底层实现时.利用JVM底层直接创建的线程.所以gc(垃圾回收器)不能回收该线程.所以一般使用完之后需要手动关闭.否则必然内存泄漏.

2.ThreadLocal工具类实现

package com.jt.web.thread;public class UserThreadLocal {
private static ThreadLocal<User> thread = new ThreadLocal<>();
public static void set(User user){
thread.set(user);
}
public static User get(){
return thread.get();
}
public static void remove(){
thread.remove();
}
}

3.实现用户信息动态获取

  1. 编辑拦截器类

package com.jt.web.intercept;public class UserInterceptor implements HandlerInterceptor{
@Autowired
private JedisCluster jedisCluster;
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 1.在调用controller方法之前拦截
* boolean 代表  
*       true代表放行  false表示拦截
* 拦截器使用用户登陆校验
* 1.获取客户端端的Cookie
* 2.判断cookie是否有token数据
* 3.判断redis中是否有用户json数据
* 如果用户都满足要求则放行.否则跳转登录页面
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//1.获取客户端Cookie
Cookie[] cookies = request.getCookies();
//2.获取token数据
String token = null;
for (Cookie cookie : cookies) {
if("JT_TICKET".equals(cookie.getName())){
token = cookie.getValue();
break;
}
}
if(!StringUtils.isEmpty(token)){
//表示用户已经含有token,判断redis中是否有数据
String userJSON = jedisCluster.get(token);
if(!StringUtils.isEmpty(userJSON)){
User user = objectMapper.readValue(userJSON, User.class);
//request.getSession().setAttribute("JT_USER", user);
//将User数据保存到ThreadLocal中
UserThreadLocal.set(user);
return true;//表示用户已经登陆 可以放行
}
}
//如果程序执行到这里表示用户登陆有误,重定向到登陆页面
response.sendRedirect("/user/login.html");
return false;
}
//在业务逻辑执行完成后拦截
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
//在业务逻辑执行完之后返回给客户端之前拦截
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
//为了防止内存泄漏关闭ThreadLocal
UserThreadLocal.remove();
}
}

修改com.jt.web.controller类中的方法:如:

@RequestMapping("/show")
public String findCartByUserId(Model model) {
Long userId = UserThreadLocal.get().getId();
List<Cart> cartList = cartService.findCartByUsedrId(userId);
model.addAttribute("cartList", cartList);
return "cart";
}

2.使用数据动态获取

说明:使用ThreadLocal可以在任何地方获取数据

3.订单模块业务实现

  1. 代码自动生成工具

    1. 添加插件

2.配置文件介绍

#表示是否不生成注释

suppressAllComments=true

#表示数据库驱动包

driverClass=com.mysql.jdbc.Driver

url=jdbc:mysql://localhost:3306/jtdb?characterEncoding=utf-8

username=root

password=root

#代码生成到哪个项目中

targetProject=jt-order

#pojo生成的包路径

modeltargetPackage=com.jt.order.pojo

#映射文件生成的包路径

sqltargetPackage=com.jt.order.mapper

#mapper接口文件所在包路径

clienttargetPackage=com.jt.order.mapper

3.编辑插件配置文件

<generatorConfiguration>
<!--加载配置文件  -->
<properties resource="generatorConfig.properties"/>
<!--数据库驱动包路径  -->
<classPathEntry location="E:\WorkJarSource\connDriver\mysql-connector-java-5.1.10-bin.jar" />
<context id="tarena">
<commentGenerator>
<property name="suppressAllComments" value="${suppressAllComments}"/>
</commentGenerator>
<jdbcConnection driverClass="${driverClass}" connectionURL="${url}" userId="${username}" password="${password}" />
<javaModelGenerator targetPackage="${modeltargetPackage}" targetProject="${targetProject}" />
<sqlMapGenerator targetPackage="${sqltargetPackage}" targetProject="${targetProject}" />  
<javaClientGenerator targetPackage="${clienttargetPackage}" targetProject="${targetProject}" type="XMLMAPPER" />
<!--table标签表示使用的表有哪些  -->
<table  tableName="tb_order" domainObjectName="Order" enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/>
<table  tableName="tb_order_shipping" domainObjectName="OrderShipping" enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/>
<table  tableName="tb_order_item" domainObjectName="OrderItem" enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/>
</context>
</generatorConfiguration>

2.跳转到订单确认页面

  1. 页面分析

说明:当点击去结算按钮时,要跳转到订单确认页面

2.编辑Controller

@Controller
@RequestMapping("/order")
public class OrderController {
@Autowired
private CartService cartService;
//http://www.jt.com/order/create.html
@RequestMapping("/create")
public String toCreate(Model model){
//展现购物车商品数据
Long userId = UserThreadLocal.get().getId();
List<Cart> carts = cartService.findCartByUserId(userId);
model.addAttribute("carts", carts);
return "order-cart";
}
}

3.编辑mvc配置文件

<mvc:interceptors>

<mvc:interceptor>

<mvc:mapping path="/cart/**"/>

<mvc:mapping path="/order/**"/>

<bean class="com.jt.web.intercept.UserInterceptor"/>

</mvc:interceptor>

</mvc:interceptors>

4.页面效果

3.订单入库操作

  1. SpringMVC为参数赋值高级应用

案例1:参数接收必须保证name属性的值与接收参数名称一致

<input name=”username” type=”text” />

Public String add(String username){…}

案例2:利用SpringMVC为对象赋值

<input name=”username” type=”text” />

Public String add(User user){…}

案例3:springMVC可以为对象的引用赋值

Public class dog{dogName…..}

Public class user{

private Dog dog;

}

<input name=”dog.dogName” type=”text” />

Public String show(User user){

}

案例4:利用SpringMVC可以为对象的属性集合赋值

<c:forEach items="${carts}" var="cart" varStatus="status">

<c:set var="totalPrice"  value="${ totalPrice + (cart.itemPrice * cart.num)}"/>

<input type="hidden" name="orderItems[${status.index}].itemId" value="${cart.itemId}"/>

<input type="hidden" name="orderItems[${status.index}].num" value="${cart.num }"/>

<input type="hidden" name="orderItems[${status.index}].price" value="${cart.itemPrice}"/>

<input type="hidden" name="orderItems[${status.index}].totalFee" value="${cart.itemPrice * cart.num}"/>

<input type="hidden" name="orderItems[${status.index}].title" value="${cart.itemTitle}"/>

<input type="hidden" name="orderItems[${status.index}].picPath" value="${cart.itemImage}"/>

</c:forEach>

对象的封装:

2.页面分析

  1. url请求

2.页面JS

3.编辑前台Controller

//实现订单入库操作  /service/order/submit
@RequestMapping("/submit")
@ResponseBody
public SysResult saveOrder(Order order){
try {
Long userId = UserThreadLocal.get().getId();
order.setUserId(userId);
String orderId = orderService.saveOrder(order);
if(StringUtils.isEmpty(orderId)){
throw new RuntimeException();
}
return SysResult.oK(orderId);
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201, "订单入库失败");
}

4.编辑前台Service

@Override
public String saveOrder(Order order) {
String url = "http://order.jt.com/order/create";
String orderId = null;
try {
String orderJSON = objectMapper.writeValueAsString(order);
Map<String, String> params = new HashMap<>();
params.put("orderJSON", orderJSON);
//从后台获取的返回值数据   采用sysResult返回
String resultJSON = httpClient.doPost(url, params);
SysResult sysResult = objectMapper.readValue(resultJSON, SysResult.class);
if(sysResult.getStatus() == 200){
orderId = (String) sysResult.getData();
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return orderId;
}

5.编辑后台Controller

@Controller
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
private static final ObjectMapper objectMapper = new ObjectMapper();
//实现后台订单入库
@RequestMapping("/create")
@ResponseBody
public SysResult saveOrder(String orderJSON){
try {
Order order = objectMapper.readValue(orderJSON, Order.class);
String orderId = orderService.saveOrder(order);
if(StringUtils.isEmpty(orderId)){
throw new RuntimeException();
}
return SysResult.oK(orderId);
} catch (Exception e) {
e.printStackTrace();
}
return SysResult.build(201,"新增订单失败");
}
}

6.编辑后台POJO

7.编辑后台Service

@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private OrderShippingMapper orderShippingMapper;
//登录用户id+当前时间戳
@Override
public String saveOrder(Order order) {
String orderId = ""+order.getUserId() + System.currentTimeMillis();
Date date = new Date();
//实现订单入库操作
order.setOrderId(orderId);
order.setStatus(1);   // 表示未付款
order.setCreated(date);
order.setUpdated(date);
orderMapper.insert(order);
System.out.println("订单数据入库成功!!!!!");
//实现订单物流入库
OrderShipping orderShipping = order.getOrderShipping();
orderShipping.setOrderId(orderId);
orderShipping.setCreated(date);
orderShipping.setUpdated(date);
orderShippingMapper.insert(orderShipping);
System.out.println("订单物流入库成功!!!!!!");
//订单商品入库
List<OrderItem> orderItemList = order.getOrderItems();
for (OrderItem orderItem : orderItemList) {
orderItem.setOrderId(orderId);
orderItem.setCreated(date);
orderItem.setUpdated(date);
orderItemMapper.insert(orderItem);
}
System.out.println("订单操作入库成功!!!!!");
return orderId;
}
}

4.订单数据回显

  1. 页面分析

2.编辑前台Controller

//根据OrderId查询订单信息

@RequestMapping("/success")

public String success(String id,Model model){

Order order = orderService.findOrderById(id);

model.addAttribute("order", order);

return "success";

}

3.编辑前台Service

@Override

public Order findOrderById(String id) {

String url = "http://order.jt.com/order/query/"+id;

String orderJSON = httpClient.doGet(url);

Order order = null;

try {

order = objectMapper.readValue(orderJSON, Order.class);

} catch (Exception e) {

e.printStackTrace();

}

return order;

}

4.编辑后台Controller

//根据orderId查询订单信息
@RequestMapping("/query/{orderId}")
@ResponseBody
public Order findOrderById(@PathVariable String orderId){
return orderService.findOrderById(orderId);
}

5.编辑后台Service

//做关联查询  查询3张表
@Override
public Order findOrderById(String orderId) {
Order order = orderMapper.selectByPrimaryKey(orderId);
OrderShipping orserShipping = orderShippingMapper.selectByPrimaryKey(orderId);
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(orderId);
List<OrderItem> orderItemList = orderItemMapper.select(orderItem) ;
//将数据封装
order.setOrderShipping(orserShipping);
order.setOrderItems(orderItemList);
return order;
}

6.页面效果

4.定时任务

  1. 业务需求

    1. 业务场景介绍

  1. 验证码.

策略:1.将验证码存redis中  2.定时任务将验证码删除

     2.QQ定期祝福生日快乐

     3.定时发送短信

     4.订单超时处理

2.工具介绍

  1. timer

Timer,定时器,功能是在指定的时间间隔内反复触发指定窗口的定时器事件 [1]  。

功能单一:只能指定间隔执行定时任务.

发短信

   2.Quartz 石英钟定时任务

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。Quartz的最新版本为Quartz 2.3.0。

3.业务需求

说明:如果用户新增订单后,.常时间没有支付,则应该将订单关闭.将状态信息由1改为6.

4.定时任务调用原理

调度器:负载时间监控,如果任务到了指定时间则调用器开始工作.

触发器:当调度器执行任务时,会调用触发器开启线程完成任务.

JOB:定时任务统称.

JOBDetail: 定时任务的具体的细节

调用过程:

  1. 新创建定时任务.将任务交给调度器管理

  2. 调用器负责监控时间,当到了指定的时间后.调用触发器开启新的线程去完成任务.

  3. 当触发器接收到执行指令后.开启线程完成具体的任务.

2.Spring整合Quartz

  1. 添加jar包

<!--添加spring-datajar包  -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.4.1.RELEASE</version>
</dependency>

<!-- 定时任务 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<!--定时任务需要依赖c3p0jar包  -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

添加完成后将项目打包

2.查看表达式生成器

3.编辑配置文件

<!-- 定义任务bean -->
<bean name="paymentOrderJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<!-- 指定具体的job类 -->
<property name="jobClass" value="com.jt.order.job.PaymentOrderJob" />
<!-- 指定job的名称 -->
<property name="name" value="paymentOrder" />
<!-- 指定job的分组 -->
<property name="group" value="Order" />
<!-- 必须设置为true,如果为false,当没有活动的触发器与之关联时会在调度器中删除该任务  -->
<property name="durability" value="true"/>
<!-- 指定spring容器的key,如果不设定在job中的jobmap中是获取不到spring容器的 -->
<property name="applicationContextJobDataKey" value="applicationContext"/>
</bean>
<!-- 定义触发器 -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="paymentOrderJobDetail" />
<!-- 每一分钟执行一次 -->
<property name="cronExpression" value="0 0/1 * * * ?" />
</bean>
<!-- 定义调度器 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
</bean>

4.编辑定时任务

public class PaymentOrderJob extends QuartzJobBean{
/**
* 要求:如果用户2天没有支付.则将订单做超时处理
* 业务逻辑:
*     update tb_order set status = 6,updated = now()
*     where status = 1 and   created  <  现在时间-2天
*/
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
//删除2天天的恶意订单
ApplicationContext applicationContext = (ApplicationContext) context.getJobDetail().getJobDataMap().get("applicationContext");
//计算2天前的时间
Date agoDate = new DateTime().minusDays(2).toDate();
//获取orderMapper接口
OrderMapper orderMapper = applicationContext.getBean(OrderMapper.class);
orderMapper.updateStatus(agoDate);
System.out.println("定时任务执行成功!!!!");
}
}

5.编辑Mapper映射文件

<!--执行定时任务  -->

<update id="updateStatus">

<![CDATA[

update tb_order set status = 6,updated  = now() where

status = 1 and created < #{agoDate}

]]>

</update>


作者:Darren

QQ:603026148

以上内容归Darren所有,如果有什么错误或者不足的地方请联系我,希望我们共同进步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从码农到码到成功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值