課程目标
目标1:運用SpringTask實作任務排程
目标2:運用MavenProfile實作開發和生産環境切換
目标3:了解MongoDB資料庫的應用場景
目标4:說出其它業務功能的需求和實作思路
1.任務排程SpringTask
1.1什麼是任務排程
在企業級應用中,經常會制定一些“計劃任務”,即在某個時間點做某件事情,核心是以時間為關注點,即在一個特定的時間點,系統執行指定的一個操作。常見的任務排程架構有Quartz和SpringTask等。
1.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設定的為表達式,是執行的時間規則。
1.3 Cron表達式
1.3.1 Cron表達式格式
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,表示某月的第二個星期三。
1.3.2 Cron表達式例子
0 0 10,14,16 * * ? 每天上午10點,下午2點,4點
0 0/30 9-17 * * ? 朝九晚五工作時間内每半小時
0 0 12 ? * WED 表示每個星期三中午12點
"0 0 12 * * ?" 每天中午12點觸發
"0 15 10 ? * *" 每天上午10:15觸發
"0 15 10 * * ?" 每天上午10:15觸發
"0 15 10 * * ? *" 每天上午10:15觸發
"0 15 10 * * ? 2005" 2005年的每天上午10:15觸發
"0 * 14 * * ?" 在每天下午2點到下午2:59期間的每1分鐘觸發
"0 0/5 14 * * ?" 在每天下午2點到下午2:55期間的每5分鐘觸發
"0 0/5 14,18 * * ?" 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發
"0 0-5 14 * * ?" 在每天下午2點到下午2:05期間的每1分鐘觸發
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44觸發
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15觸發
"0 15 10 15 * ?" 每月15日上午10:15觸發
"0 15 10 L * ?" 每月最後一日的上午10:15觸發
"0 15 10 ? * 6L" 每月的最後一個星期五上午10:15觸發
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最後一個星期五上午10:15觸發
"0 15 10 ? * 6#3" 每月的第三個星期五上午10:15觸發
1.4秒殺商品清單的增量更新
每分鐘執行查詢秒殺商品表,将符合條件的記錄并且緩存中不存在的秒殺商品存入緩存
/**
* 重新整理秒殺商品
*/
@Scheduled(cron="0 * * * * ?")
public void refreshSeckillGoods(){
System.out.println("執行了任務排程"+new Date());
//查詢所有的秒殺商品鍵集合
List ids = new ArrayList( redisTemplate.boundHashOps("seckillGoods").keys());
//查詢正在秒殺的商品清單
TbSeckillGoodsExample example=new TbSeckillGoodsExample();
Criteria criteria = example.createCriteria();
criteria.andStatusEqualTo("1");//稽核通過
criteria.andStockCountGreaterThan(0);//剩餘庫存大于0
criteria.andStartTimeLessThanOrEqualTo(new Date());//開始時間小于等于目前時間
criteria.andEndTimeGreaterThan(new Date());//結束時間大于目前時間
criteria.andIdNotIn(ids);//排除緩存中已經有的商品
List<TbSeckillGoods> seckillGoodsList= seckillGoodsMapper.selectByExample(example);
//裝入緩存
for( TbSeckillGoods seckill:seckillGoodsList ){
redisTemplate.boundHashOps("seckillGoods").put(seckill.getId(), seckill);
}
System.out.println("将"+seckillGoodsList.size()+"條商品裝入緩存");
}
1.5過期秒殺商品的移除
每秒中在緩存的秒殺上皮清單中查詢過期的商品,發現過期同步到資料庫,并在緩存中移除該秒殺商品
/**
* 移除秒殺商品
*/
@Scheduled(cron="* * * * * ?")
public void removeSeckillGoods(){
System.out.println("移除秒殺商品任務在執行");
//掃描緩存中秒殺商品清單,發現過期的移除
List<TbSeckillGoods> seckillGoodsList = redisTemplate.boundHashOps("seckillGoods").values();
for( TbSeckillGoods seckill:seckillGoodsList ){
if(seckill.getEndTime().getTime()<new Date().getTime() ){//如果結束日期小于目前日期,則表示過期
seckillGoodsMapper.updateByPrimaryKey(seckill);//向資料庫儲存記錄
redisTemplate.boundHashOps("seckillGoods").delete(seckill.getId());//移除緩存資料
System.out.println("移除秒殺商品"+seckill.getId());
}
}
System.out.println("移除秒殺商品任務結束");
}
2.Maven Profile
2.1什麼是MavenProfile
在我們平常的java開發中,會經常使用到很多配制檔案(xxx.properties,xxx.xml),而當我們在本地開發(dev),測試環境測試(test),線上生産使用(product)時,需要不停的去修改這些配制檔案,次數一多,相當麻煩。現在,利用maven的filter和profile功能,我們可實作在編譯階段簡單的指定一個參數就能切換配制,提高效率,還不容易出錯.
profile可以讓我們定義一系列的配置資訊,然後指定其激活條件。這樣我們就可以定義多個profile,然後每個profile對應不同的激活條件和配置資訊,進而達到不同環境使用不同配置資訊的效果。
2.2 Maven Profile入門
修改pinyougou-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>
運作tomcat7:run ,發現運作結果是一樣的,因為port是變量,而變量值是定義為9105。這其實就是我們之前學習的maven的變量。
那我們現在思考一下,如果這個端口在開發時使用9105,如果在生産環境(或其他環境)為9205呢?如何解決值的動态切換呢?
這時我們修改pom.xml,增加profile定義
<profiles>
<profile>
<id>dev</id>
<properties>
<port>9105</port>
</properties>
</profile>
<profile>
<id>pro</id>
<properties>
<port>9205</port>
</properties>
</profile>
</profiles>
執行指令 tomcat7:run -P pro 發現以9205端口啟動
執行指令 tomcat7:run -P dev 發現以9105端口啟動
-P 後邊跟的是profile的id
如果我們隻執行指令tomcat7:run ,也是以9105啟動,因為我們一開始定義的變量值就是9105,就是在不指定profileID時的預設值.
2.3切換資料庫連接配接配置
2.3.1編寫不同環境的配置檔案
(1)我們在pinyougou-dao工程中src/main/resources下建立filter檔案夾
(2)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=123456
(3)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=123456
(4)修改properties下的db.properties
jdbc.driver=${env.jdbc.driver}
jdbc.url=${env.jdbc.url}
jdbc.username=${env.jdbc.username}
jdbc.password=${env.jdbc.password}
2.3.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,分别是開發環境和生産環境
2.3.3資源過濾與變量替換
修改pom.xml ,在build節點中添加如下配置
<filters>
<filter>src/main/resources/filters/db_${env}.properties</filter>
</filters>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
這裡我們利用filter實作對資源檔案(resouces) 過濾
maven filter可利用指定的xxx.properties中對應的key=value對資源檔案中的${key}進行替換,最終把你的資源檔案中的username=${key}替換成username=value
2.3.4打包
在pinyougou-dao 工程 執行指令:package -P pro , 解壓生成的jar包,觀察db.properties配置檔案内容,已經替換為生産環境的值。
在pinyougou-sellergoods-service工程 執行指令 pageage ,解壓生成的war包裡的pinyougou-dao的jar包,發現也是生成環境的值。
2.3.5測試運作
【1】連接配接生産資料庫
(1)在pinyougou-dao 工程執行指令:install -P pro
(2)在pinyougou-sellergoods-service:執行指令:tomcat7:run
(3)在pinyougou-shop-web : 執行指令:tomcat7:run
【2】連接配接開發資料庫
(1)在pinyougou-dao 工程執行指令:install -P dev (或 install )
(2)在pinyougou-sellergoods-service:執行指令:tomcat7:run
(3)在pinyougou-shop-web : 執行指令:tomcat7:run
2.4切換注冊中心連接配接配置
2.4.1集中配置注冊中心位址
(1)在pinyougou-common工程中properties下建立dubbox.properties
address=192.168.25.135:2181
(2)Spring目錄下建立spring配置檔案 applicationContext-dubbox.xml 配置如下:
<dubbo:registry protocol="zookeeper" address="${address}"/>
(3)所有的服務工程與web工程都要依賴pinyougou-common . 并删除每個工程中關于注冊中心位址的配置
(4)安裝pinyougou-common到本地倉庫,然後測試運作。
2.4.2 MavenProfile配置
(1)在pinyougou-common工程中建立filters目錄 ,目錄下建立dubbox_dev.properties
env.address=192.168.25.135:2181
(2)建立dubbox_pro.properties
env.address=192.168.25.136:2181
(3)修改dubbox.properties
address=${env.address}
(4)修改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簡介
3.1什麼是MongoDB
MongoDB 是一個跨平台的,面向文檔的資料庫,是目前 NoSQL 資料庫産品中最熱門的一種。它介于關系資料庫和非關系資料庫之間,是非關系資料庫當中功能最豐富,最像關系資料庫的産品。它支援的資料結構非常松散,是類似JSON 的 BSON 格式,是以可以存儲比較複雜的資料類型。
MongoDB 的官方網站位址是:http://www.mongodb.org/
3.2 MongoDB特點
MongoDB 最大的特點是他支援的查詢語言非常強大,其文法有點類似于面向對象的查詢語言,幾乎可以實作類似關系資料庫單表查詢的絕大部分功能,而且還支援對資料建立索引。它是一個面向集合的,模式自由的文檔型資料庫。
具體特點總結如下:
(1)面向集合存儲,易于存儲對象類型的資料
(2)模式自由
(3)支援動态查詢
(4)支援完全索引,包含内部對象
(5)支援複制和故障恢複
(6)使用高效的二進制資料存儲,包括大型對象(如視訊等)
(7)自動處理碎片,以支援雲計算層次的擴充性
(8)支援 Python,PHP,Ruby,Java,C,C#,Javascript,Perl 及 C++語言的驅動程式,社群中也提供了對 Erlang 及.NET 等平台的驅動程式
(9) 檔案存儲格式為 BSON(一種 JSON 的擴充)
3.3 MongoDB體系結構
MongoDB 的邏輯結構是一種層次結構。主要由:
文檔(document)、集合(collection)、資料庫(database)這三部分組成的。邏輯結構是面向使用者
的,使用者使用 MongoDB 開發應用程式使用的就是邏輯結構。
(1)MongoDB 的文檔(document),相當于關系資料庫中的一行記錄。
(2)多個文檔組成一個集合(collection),相當于關系資料庫的表。
(3)多個集合(collection),邏輯上組織在一起,就是資料庫(database)。
(4)一個 MongoDB 執行個體支援多個資料庫(database)。
文檔(document)、集合(collection)、資料庫(database)的層次結構如下圖:
下表是MongoDB與MySQL資料庫邏輯結構概念的對比
MongoDb 關系型資料庫Mysql
資料庫(databases) 資料庫(databases)
集合(collections) 表(table)
文檔(document) 行(row)
3.4 MongoDB在品優購系統中的應用
我們品優購的評價系統、收藏系統采用等資訊存儲在MongoDB . MongoDB安裝及資料庫操作部分屬于自學内容,大家可以根據本課程提供的配套的自學資料學習此部分内容。
4.品優購-其它業務功能分析
4.1使用者中心(WEB)
使用者在首頁登陸系統後會進入到使用者中心首頁。
4.1.1訂單中心
功能需求:
(1)實作對訂單的查詢功能
(2)未付款訂單的付款功能
(3)未付款訂單的取消功能
(4)已付款提醒訂單發貨功能
(5)确認收貨
(6)退貨
(7)使用者評價
(8)物流資訊跟蹤
4.1.2秒殺訂單中心
同上。
4.1.3我的收藏
購物車中有将我的購物車商品移到我的收藏功能,在使用者中心中可以檢視我收藏的商品
對于這樣的使用者收藏資料,我們可以使用mongoDB來實作。
(1)我的收藏清單
(2)删除收藏
4.1.4我的足迹
(1)檢視足迹清單
(2)删除我的足迹
4.1.5個人資訊設定
(1)個人資訊
(2)位址資訊
(3)密碼重置
(4)綁定手機
4.2商家背景-訂單管理(WEB)
4.2.1訂單管理
(1)訂單查詢
(2)訂單發貨
(3)訂單退貨
4.2.2秒殺訂單管理
(1)秒殺中訂單查詢(查詢redis )
(2)已完成秒殺訂單查詢(查詢資料庫)
(3)秒殺訂單發貨
(4)秒殺訂單退貨查詢
4.3營運商背景-訂單管理(WEB)
4.3.1訂單管理
根據商家、訂單号、使用者ID等資訊查詢訂單清單
4.3.2秒殺訂單管理
(1)查詢秒殺中訂單
(2)查詢已付款訂單
4.4評價系統
針對評論這樣資料量大并且價值不高的資料,我們通常采用MongoDB來實作存儲。
4.4.1評價系統-資料通路層
評價資料通路層-操作mongoDB
4.4.2評價系統-服務層
評價服務層
4.4.3 web工程調用評價系統
(1)在商品詳細頁顯示該商品的所有評論資訊(CORS跨域)
(2)使用者中心web工程引用評價服務 可以對已收貨的訂單追加評價。
(3)商家背景web工程引用評價服務 可以檢視訂單的評價
(4)營運商背景web工程引用評價服務 可以檢視訂單的評價
(5)任務服務pinyougou-task-service 引用評價服務和搜尋服務,統計每個商品的評價更新到solr索引庫中。
4.5商家首頁
建構商家首頁工程,引用搜尋服務,顯示該商家的商品清單
4.6資金結算
使用者購買商品是直接付款給平台的,而發貨的是商家,那商家如何獲得貨款呢?這就需要營運商定期将貨款轉賬給商家。
4.6.1傭金與傭金比例
說到平台與商家之間的資金結算,我們必須要提一下傭金。傭金就是營運商以銷售額為基礎抽取的銷售提成。 商品類型不同,設定相應的傭金比例也不同。例如食品類傭金比例為0.5% ,那麼商家每産生100元的銷售額就需要支付給營運商平台相應比例的傭金。