目标
- 运用SpringTask实现任务调度
- 运用MavenProfile实现并发和生产环境切换
- 了解MongoDB数据库的应用场景
- 说出其他业务功能的需求和实现思路
1. 任务调度SpringTask
1. 什么是任务调度
在企业级应用中,经常会制定一些“计划任务”,即在某个时间点做某件事情,核心是以时间为关注点,即在一个特定的时间点,系统执行指定的一个操作。常见的任务调度框架有Quartz和SpringTask等。
2. SpringTask入门小demo
创建模块pinyougou-task-service,引入spring相关依赖 dao 和common工程,tomcat7端口为9108
添加web.xml
添加配置文件applicationContext-task.xml,内容如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.2.xsd">
<context:component-scan base-package="com.pinyougou.task"/>
<task:annotation-driven/>
</beans>
创建包com.pinyougou.task
编写类
@Component
public class SeckillTask {
@Scheduled(cron = "* * * * * ?")
public void refreshSeckillGoods(){
System.out.println("执行了任务调度:"+new Date());
}
}
执行后控制台每秒都输出当前时间,cron设置的是表达式,执行时间规则
3. Cron表达式
1. 表达式格式
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:
(1)Seconds Minutes Hours DayofMonth Month DayofWeek Year
(2)Seconds Minutes Hours DayofMonth Month DayofWeek
每一个域可出现的字符如下:
Seconds:可出现", - * /"四个字符,有效范围为0-59的整数
Minutes:可出现", - * /"四个字符,有效范围为0-59的整数
Hours:可出现", - * /"四个字符,有效范围为0-23的整数
DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为1-31的整数
Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc
DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一,依次类推
Year:可出现", - * /"四个字符,有效范围为1970-2099年
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
(1)*
:表示匹配该域的任意值,假如在Minutes域使用*, 即表示每分钟都会触发事件。
(2)?
:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
(3)-
:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4)/
:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
(5),
:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
(6)L
:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7)W
: 表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一 到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份
(8)LW
:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9)#
:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
2. 例子
eg. 每分钟:第3秒到第10秒 再从第15秒 到第20秒 第45秒执行一次 `15-20, 3-10, 45
eg. 公司 每月第10天计算(发放)工资 10W
eg. 每月最后一个工作日结账 LW
eg. 母亲节、父亲节发送短信 4#2
表示某月的第二个星期三
4. 秒杀商品列表的增量更新
每分钟执行查询秒杀商品表,将符合条件的记录并且缓存中不存在的秒杀商品存入缓存
@Scheduled(cron = "0 * * * * ?")
public void refreshSeckillGoods(){
System.out.println("执行了秒杀商品增量更新 任务调度:"+new Date());
// 查询缓存中的秒杀商品id集合
List goodsIdList = new ArrayList(redisTemplate.boundHashOps("seckillGoods").keys());
TbSeckillGoodsExample example = new TbSeckillGoodsExample();
TbSeckillGoodsExample.Criteria criteria = example.createCriteria();
criteria.andStatusEqualTo("1");//审核通过的商品
criteria.andStockCountGreaterThan(0);//库存数大于0
criteria.andStartTimeLessThanOrEqualTo(new Date());//开始日期小于等于当前日期
criteria.andEndTimeGreaterThanOrEqualTo(new Date());//截止日期大于等于当前日期
if(goodsIdList.size()>0){
criteria.andIdNotIn(goodsIdList);// 排除缓存中已经存在的商品id集合
}
List<TbSeckillGoods> seckillGoodsList = seckillGoodsMapper.selectByExample(example);
// 将列表数据装入缓存
for (TbSeckillGoods seckillGoods : seckillGoodsList) {
redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getId(),seckillGoods);
System.out.println("增量更新秒杀商品id:"+seckillGoods.getId());
}
}
5. 过期秒杀商品的移除
每秒钟在缓存的秒杀商品列表查询过期的商品,发现过期同步到数据库,并在缓存中移除该秒杀商品
@Scheduled(cron = "* * * * * ?")
public void removeSeckillGoods(){
// 查询出缓存中的数据,扫描每条记录,判断时间,如果当前时间超过截止时间,移除此记录
List<TbSeckillGoods> seckillGoodsList = redisTemplate.boundHashOps("seckillGoods").values();
System.out.println("执行了清除秒杀商品的任务");
for (TbSeckillGoods seckillGoods : seckillGoodsList) {
if(seckillGoods.getEndTime().getTime()<new Date().getTime()){
// 同步到数据库
seckillGoodsMapper.updateByPrimaryKey(seckillGoods);
// 清除缓存
redisTemplate.boundHashOps("seckillGoods").delete(seckillGoods.getId());
System.out.println("秒杀商品"+seckillGoods.getId()+"已过期");
}
}
System.out.println("执行了清除秒杀商品的任务...end...");
}
2. Maven Profile
1. 什么是MavenProfile
在我们平常的java开发中,会经常使用到很多配制文件(xxx.properties,xxx.xml),而当我们在本地开发(dev),测试环境测试(test),线上生产使用(product)时,需要不停的去修改这些配制文件,次数一多,相当麻烦。现在,利用maven的filter和profile功能,我们可实现在编译阶段简单的指定一个参数就能切换配制,提高效率,还不容易出错.。
profile可以让我们定义一系列的配置信息,然后指定其激活条件。这样我们就可以定义多个profile,然后每个profile对应不同的激活条件和配置信息,从而达到不同环境使用不同配置信息的效果。
2. 入门
修改page-web的pom.xml
<properties>
<port>9105</port>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 指定端口 -->
<port>${port}</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
运行发现结果一样,其实就是之前学习的maven的变量
如何切换为生产环境的9205呢?
修改pom文件,添加profile定义
<profiles>
<profile>
<id>dev</id>
<properties>
<port>9105</port>
</properties>
</profile>
<profile>
<id>pro</id>
<properties>
<port>9205</port>
</properties>
</profile>
</profiles>
执行后发现运行的端口改变为9205了
3. 切换数据库连接配置
1. 编写不同环境的配置文件
- 在pinyougou-dao的src/main/resources下创建filter文件夹
- filter文件夹下创建db_dev.properties,用于配置开发环境用到的数据库
env.jdbc.driver=com.mysql.jdbc.Driver
env.jdbc.url=jdbc:mysql://localhost:3306/pinyougoudb?characterEncoding=utf-8
env.jdbc.username=root
env.jdbc.password=root
- filter文件夹下创建db_pro.properties
env.jdbc.driver=com.mysql.jdbc.Driver
env.jdbc.url=jdbc:mysql://localhost:3306/pinyougoudb_pro?characterEncoding=utf-8
env.jdbc.username=root
env.jdbc.password=root
- 修改properties下的db.properties
jdbc.driver=${env.jdbc.driver}
jdbc.url=${env.jdbc.url}
jdbc.username=${env.jdbc.username}
jdbc.password=${env.jdbc.password}
2. 定义profile
修改pom.xml
<properties>
<env>dev</env>
</properties>
<profiles>
<profile>
<id>dev</id>
<properties>
<env>dev</env>
</properties>
</profile>
<profile>
<id>pro</id>
<properties>
<env>pro</env>
</properties>
</profile>
</profiles>
定义了2个profile,分别是开发环境和生产环境
3. 资源过滤和变量替换
修改pom.xml,在build节点中添加如下配置
<build>
<filters>
<filter>src\main\resources\filters\db_${env}.properties</filter>
</filters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
利用filter实现对资源文件(resources)过滤
maven filter可利用指定的xxx.properties中对应的key=value对资源文件中的${key}
进行替换,最终把你的资源文件中username=value
4. 打包
在pinyougou-dao工程执行命令:package -P pro
解压生成的jar包,观察db.properties配置文件内容,替换为生产环境的值
解压后发现db已经变成了pinyougoudb_pro
5. 测试运行
4. 切换注册中心连接配置
1. 集中配置注册中心地址
- 在pinyougou-common工程properties下创建dubbox.properties
address=192.168.25.128:2181
- spring目录下创建spring配置文件applicationContext-dubbox.xml配置如下
<context:property-placeholder location="classpath*:properties/*.properties" />
<dubbo:registry protocol="zookeeper" address="${address}"/>
- 所有服务工程和web工程都要依赖pinyougou-common,并删除每个工程中关于注册中心地址的配置
- 测试运行 正常
2. MavenProfile配置
- 在pinyougou-common工程创建filters目录,目录下建立dubbox_dev.properties和dubbox_pro.properties
env.address=192.168.25.128:2181
env.address=192.168.25.122:2181
- 修改dubbox.properties
address=${env.address}
- 修改pinyougou-common的pom.xml
<properties>
<env>dev</env>
</properties>
<profiles>
<profile>
<id>dev</id>
<properties>
<env>dev</env>
</properties>
</profile>
<profile>
<id>pro</id>
<properties>
<env>pro</env>
</properties>
</profile>
</profiles>
...
<build>
<filters>
<filter>src/main/resources/filters/dubbox_${env}.properties</filter>
</filters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
3. MongoDB简介
1. 什么是mongodb
MongoDB 是一个跨平台的,面向文档的数据库,是当前 NoSQL 数据库产品中最热门的一种。它介于关系数据库和非关系数据库之间,是非关系数据库当中功能最丰富,最像关系数据库的产品。它支持的数据结构非常松散,是类似JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。
2. 特点
MongoDB 最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。它是一个面向集合的,模式自由的文档型数据库。
具体特点总结如下:
(1)面向集合存储,易于存储对象类型的数据
(2)模式自由
(3)支持动态查询
(4)支持完全索引,包含内部对象
(5)支持复制和故障恢复
(6)使用高效的二进制数据存储,包括大型对象(如视频等)
(7)自动处理碎片,以支持云计算层次的扩展性
(8)支持 Python,PHP,Ruby,Java,C,C#,Javascript,Perl 及 C++语言的驱动程序,社区中也提供了对 Erlang 及.NET 等平台的驱动程序
(9)文件存储格式为 BSON(一种 JSON 的扩展)
3. 体系结构
MongoDB 的逻辑结构是一种层次结构。主要由:
文档(document)、集合(collection)、数据库(database)这三部分组成的。逻辑结构是面向用户
的,用户使用 MongoDB 开发应用程序使用的就是逻辑结构。
(1)MongoDB 的文档(document),相当于关系数据库中的一行记录。
(2)多个文档组成一个集合(collection),相当于关系数据库的表。
(3)多个集合(collection),逻辑上组织在一起,就是数据库(database)。
(4)一个 MongoDB 实例支持多个数据库(database)。
4. 在品优购中的应用
品优购的评价系统、收藏系统采用等信息存储在mongodb
4. 品优购-其他业务功能分析
1. 用户中心(WEB)
用户在首页登录系统后会进入到用户中心首页
1. 订单中心
功能需求:
(1)实现对订单的查询功能
(2)未付款订单的付款功能
(3)未付款订单的取消功能
(4)已付款提醒订单发货功能
(5)确认收货
(6)退货
(7)用户评价
(8)物流信息跟踪
2. 秒杀订单中心
同上
3. 我的收藏
购物车中有将我的购物车商品移到我的收藏功能,在用户中心中可以查看我收藏的商品
对于这样的用户收藏数据,我们可以使用mongoDB来实现
(1)我的收藏列表
(2)删除收藏
4. 我的足迹
(1)查看足迹列表
(2)删除我的足迹
5. 个人信息设置
(1)个人信息
(2)地址信息
(3)密码重置
(4)绑定手机
2. 商家后台-订单管理(WEB)
1. 订单管理
(1)订单查询
(2)订单发货
(3)订单退货
2. 秒杀订单管理
(1)秒杀中订单查询(查询redis )
(2)已完成秒杀订单查询(查询数据库)
(3)秒杀订单发货
(4)秒杀订单退货查询
3. 运营商后台-订单管理(WEB)
1. 订单管理
根据商家、订单号、用户ID等信息查询订单列表
2. 秒杀订单管理
(1)查询秒杀中订单
(2)查询已付款订单
4. 评价系统
针对评论这样数据量大并且价值不高的数据,我们通常采用MongoDB来实现存储
1. 评价系统-数据访问层
评价数据访问层-操作mongoDB
2. 评价系统-服务层
3. web工程调用评价系统
(1)在商品详细页显示该商品的所有评论信息(CORS跨域)
(2)用户中心web工程引用评价服务 可以对已收货的订单追加评价。
(3)商家后台web工程引用评价服务 可以查看订单的评价
(4)运营商后台web工程引用评价服务 可以查看订单的评价
(5)任务服务pinyougou-task-service引用评价服务和搜索服务,统计每个商品的评价更新到solr索引库中。
5. 商家首页
构建商家首页工程,引用搜索服务,显示该商家的商品列表
6. 资金结算
用户购买商品是直接付款给平台的,而发货的是商家,那商家如何获得货款呢?这就需要运营商定期将货款转账给商家
1. 佣金和佣金比例
说到平台与商家之间的资金结算,我们必须要提一下佣金。佣金就是运营商以销售额为基础抽取的销售提成。 商品类型不同,设定相应的佣金比例也不同。例如食品类佣金比例为0.5% ,那么商家每产生100元的销售额就需要支付给运营商平台相应比例的佣金