SpringCloud微服務(wù)架構(gòu)實(shí)戰(zhàn):類(lèi)目管理微服務(wù)開(kāi)發(fā)
類(lèi)目管理微服務(wù)開(kāi)發(fā)
從本章開(kāi)始,我們將根據(jù)電商平臺(tái)的各個(gè)實(shí)例項(xiàng)目進(jìn)行具體的微服務(wù)開(kāi)發(fā),主要包括類(lèi)目管理、庫(kù)存管理、訂單管理等。在這幾個(gè)實(shí)例項(xiàng)目中,我們將根據(jù)項(xiàng)目本身的特點(diǎn),使用不同的數(shù)據(jù)庫(kù)進(jìn)行開(kāi)發(fā)。對(duì)于類(lèi)目管理來(lái)說(shuō),我們將使用二級(jí)分類(lèi)設(shè)計(jì),即數(shù)據(jù)實(shí)體之間存在一定的關(guān)聯(lián)關(guān)系,因此最好的選擇就是使用Spring Data JPA進(jìn)行開(kāi)發(fā)。Spring Data JPA是Spring Boot開(kāi)發(fā)框架中一個(gè)默認(rèn)推薦使用的數(shù)據(jù)庫(kù)開(kāi)發(fā)方法,同時(shí),JPA 也是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的一種具體應(yīng)用。
本章的項(xiàng)目工程可以通過(guò)本文的源代碼在IDEA中使用Git檢出。該項(xiàng)目由三個(gè)模塊組成:
catalog-object:類(lèi)目公共對(duì)象設(shè)計(jì)。
catalog-restapi:類(lèi)目接口開(kāi)發(fā)。
catalog-web:類(lèi)目管理的Web應(yīng)用。
了解領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain-Driven Design,DDD)是一種面向?qū)ο蠼?,以業(yè)務(wù)模型為核心展開(kāi)的軟件開(kāi)發(fā)方法。面向?qū)ο蠼5脑O(shè)計(jì)方法,相比于面向過(guò)程和面向數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)方法,從根本上解耦了系統(tǒng)分析與系統(tǒng)設(shè)計(jì)之間相互隔離的狀態(tài),從而提高了軟件開(kāi)發(fā)的工作效率。
我們將使用JPA來(lái)實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的開(kāi)發(fā)方法。JPA通過(guò)實(shí)體定義建立了領(lǐng)域業(yè)務(wù)對(duì)象的數(shù)據(jù)模型,然后通過(guò)使用存儲(chǔ)庫(kù)賦予實(shí)體操作行為,從而可以快速進(jìn)行領(lǐng)域業(yè)務(wù)功能的開(kāi)發(fā)。
DDD的分層結(jié)構(gòu)
DDD將系統(tǒng)分為用戶(hù)接口層、應(yīng)用層、領(lǐng)域?qū)雍突A(chǔ)設(shè)施層,如圖6-1所示。

應(yīng)用層是很薄的一層,負(fù)責(zé)接收用戶(hù)接口層傳來(lái)的參數(shù)和路由到對(duì)應(yīng)的領(lǐng)域?qū)樱到y(tǒng)的業(yè)務(wù)邏輯主要集中在領(lǐng)域?qū)又?,所以領(lǐng)域?qū)釉谙到y(tǒng)架構(gòu)中占據(jù)了很大的面積。上下層之間應(yīng)該通過(guò)接口進(jìn)行通信,這樣接口定義的位置就決定了上下層之間的依賴(lài)關(guān)系。
DDD的基本元素
DDD的基本元素有Entity、Value Object、Service、Aggregate、Repository、Factory、DomainEvent和Moudle等。
Entity:可以表示一個(gè)實(shí)體。
Value Object:表示一個(gè)沒(méi)有狀態(tài)的對(duì)象。Service:可以包含對(duì)象的行為。
Aggregate:一組相關(guān)對(duì)象的集合。Repository:一個(gè)存儲(chǔ)倉(cāng)庫(kù)。
Factory:一個(gè)生成聚合對(duì)象的工廠(chǎng)。Domain Event:表示領(lǐng)域事件。
Moudle:表示模塊。
Spring Data JPA
JPA(Java Persistence API)即Java持久層API,是Java持久層開(kāi)發(fā)的接口規(guī)范。Hibernate、TopLink和 OpenJPA等ORM框架都提供了JPA的實(shí)現(xiàn)。Spring Data JPA 的實(shí)現(xiàn)使用了Hibernate框架,所以在設(shè)計(jì)上與直接使用 Hibernate差別不大。但JPA 并不等同于Hibernate,它是在Hibernate之上的一個(gè)通用規(guī)范。
接下來(lái),我們通過(guò)模塊catalog-restapi來(lái)說(shuō)明Spring Data JPA的開(kāi)發(fā)方法。
Druid數(shù)據(jù)源配置
Druid是阿里巴巴開(kāi)源的一個(gè)數(shù)據(jù)源服務(wù)組件,不僅具有很好的性能,還提供了監(jiān)控和安全過(guò)濾的功能。
我們可以創(chuàng)建一個(gè)配置類(lèi)DruidConfiguration來(lái)啟用Druid 的監(jiān)控和過(guò)濾功能,代碼如下所示:
@Configuration
public class DruidConfiguration {
@Bean
public ServletRegistrationBean statviewServle({
ServletRegistrationBean servletRegistrationBean = new
ServletRegistrationBean(new StatViewServlet (), "/druid/*");
//IP地址白名單
servletRegistrationBean.addInitParameter("allow","192.168.0.1,
127.0.0.1");
//IP地址黑名單(共同存在時(shí),deny優(yōu)先于allow)
servletRegistrationBean.addInitParameter ("deny", "192.168.110.100");//控制臺(tái)管理用戶(hù)
servletRegistrationBean.addInitParameter ("loginUsername" , "druid");servletRegistrationBean.addInitParameter ("loginPassword","12345678");//是否能夠重置數(shù)據(jù)
servletRegistrationBean.addInitParameter("resetEnable" ,"false");return servletRegistrationBean;
}
@Bean
public FilterRegistrationBean statFilter(){
FilterRegistrationBean filterRegistrationBean = new
FilterRegistrationBean (new webStatFilter());
//添加過(guò)濾規(guī)則
filterRegistrationBean.addUrlPatterns("/*");//忽略過(guò)濾的格式
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,* .jpg,* .png,*.Css,* .ico,/druid/* ")F
return filterRegistrationBean;
}
}在使用這個(gè)監(jiān)控配置后,當(dāng)應(yīng)用運(yùn)行時(shí),例如,我們啟動(dòng)catalog-restapi模塊,即可通過(guò)下列鏈接打開(kāi)監(jiān)控控制臺(tái)頁(yè)面:
http://localhost:9095/druid在登錄認(rèn)證中輸入前面代碼中配置的用戶(hù)和密碼“druid/12345678”,即可打開(kāi)如圖6-2所示的操作界面。注意,本地的P地址不在前面代碼設(shè)置的黑名單之中。

在使用這個(gè)監(jiān)控控制臺(tái)之后,通過(guò)查看“SQL監(jiān)控”的結(jié)果,即可為我們對(duì)應(yīng)用的SQL設(shè)計(jì)和優(yōu)化提供有價(jià)值的參考依據(jù)。
我們可以使用項(xiàng)目中的配置文件 application.yml 來(lái)設(shè)置Druid連接數(shù)據(jù)源,代碼如下所示:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driverurl:
jdbc:mysql://localhost:3306/catalogdb?characterEncoding=utf8&useSSL=false
username: root
password:12345678
#初始化大小,最小值為5,最大值為120initialSize: 5
minIdle: 5
maxActive: 20
#配置獲取連接等待超時(shí)的時(shí)間maxwait: 60000
#配置間隔多久進(jìn)行一次檢測(cè),檢測(cè)需要關(guān)閉的空閑連接,單位是mstimeBetweenEvictionRunsMillis: 60000
#配置一個(gè)連接在池中最小生存時(shí)間,單位是msminEvictableIdleTimeMillis:300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: true
testOnBorrow: falsetestOnReturn: false
#打開(kāi)PSCache,指定每個(gè)連接上 PSCache的大小poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
#配置監(jiān)控統(tǒng)計(jì)攔截的filters,如果去掉,則監(jiān)控界面SQL將無(wú)法統(tǒng)計(jì), 'wall'用于防火墻filters: stat, wall, log4j
#通過(guò)connectProperties屬性打開(kāi)mergeSql功能;慢sQL記錄connectionProperties:
druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000在上面的配置中,主要設(shè)定了所連接的數(shù)據(jù)庫(kù),以及數(shù)據(jù)庫(kù)的用戶(hù)名和密碼,確保數(shù)據(jù)庫(kù)的用戶(hù)配置信息正確,并且具有讀寫(xiě)權(quán)限。其他一些配置可由 Druid 的通用參數(shù)來(lái)設(shè)定。
數(shù)據(jù)源的配置同時(shí)適用于MyBatis的開(kāi)發(fā)。
JPA 初始化和基本配置
首先,我們新建一個(gè)配置類(lèi)JpaConfiguration,初始化一些JPA的參數(shù),代碼如下所示:
econfiguration
@EnableTransactionManagement (proxyTargetClass = true)
@EnableJpaRepositories (basePackages = "com.** .repository")CEntityScan(basePackages = "com.* *.entity")
public class JpaConfiguration{
@Bean
PersistenceExceptionTranslationPostProcessorpersistenceExceptionTranslationPostProcessor(){
return new PersistenceExceptionTranslationPostProcessor();
}
}在這里,我們?cè)O(shè)置存儲(chǔ)庫(kù)的存放位置為“com.**.repository”,同時(shí)設(shè)置實(shí)體的存放位置為“com.**.entity”,這樣就能讓JPA找到我們定義的存儲(chǔ)庫(kù)和實(shí)體對(duì)象了。
然后,在應(yīng)用程序的配置文件中,增加如下配置:
spring:
jpa:
database: MYSQLshow-sql: false
## Hibernate ddl auto (validate lcreate l create-drop l update)hibernate:
ddl-auto: update
#naming-strategy:org.hibernate.cfg. ImprovedNamingStrategynaming.physical-strategy:
org.hibernate.boot.model.naming. PhysicalNamingStrategystandardImpl
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialectenable_lazy_load_no_trans: true其中,“ddl-auto”設(shè)置為“update”,表示當(dāng)實(shí)體屬性更改時(shí),將會(huì)更新表結(jié)構(gòu)。如果表結(jié)構(gòu)不存在,則創(chuàng)建表結(jié)構(gòu)。注意,不要把“ddl-auto”設(shè)置為“create”,否則程序每次啟動(dòng)時(shí)都會(huì)重新創(chuàng)建表結(jié)構(gòu),而之前的數(shù)據(jù)也會(huì)丟失。如果不使用自動(dòng)功能,則可以設(shè)置為“none”。上面配置中的最后一行代碼開(kāi)啟了Hibernate的延遲加載功能,這可以提高關(guān)聯(lián)關(guān)系查詢(xún)時(shí)的訪(fǎng)問(wèn)性能。
實(shí)體建模
在使用Spring Data JPA進(jìn)行實(shí)體建模時(shí),主要使用Hibernate的對(duì)象關(guān)系映射(ORM)來(lái)實(shí)現(xiàn)。在類(lèi)目管理項(xiàng)目中我們需要?jiǎng)?chuàng)建兩個(gè)實(shí)體,分別為主類(lèi)和二級(jí)分類(lèi)。
主類(lèi)由名稱(chēng)、操作者和創(chuàng)建日期等屬性組成,實(shí)現(xiàn)代碼如下所示:
@Entity
@Table(name ="tsorts")
public class Sorts implements java.io.Serializable {
CId
@Generatedvalue(strategy = GenerationType. IDENTITY)private Long id;
private string name;
private string operator;
@DateTimeFormat (pattern = "Yyyy-MM-dd HH:mm:ss")
eColumn (name = "created",columnDefinition = "timestamp defaultcurrent timestamp")
@Temporal (TemporalType.TIMESTAMP)private Date created;
coneToMany(cascade = CascadeType.REMOVE)@OrderBy("created asc")
CJoincolumn (name= "sorts id")
private Set subsortses = new HashSet<>(); 從上面代碼中可以看出,我們使用了表“t_sorts”來(lái)存儲(chǔ)數(shù)據(jù),并且它與二級(jí)分類(lèi)以一對(duì)多的方式建立了關(guān)聯(lián)關(guān)系。建立關(guān)聯(lián)關(guān)系的是“sorts_id”,它將被保存在二級(jí)分類(lèi)的表格中。另外,在查詢(xún)這種關(guān)系時(shí),我們指定了以創(chuàng)建時(shí)間“created”進(jìn)行排序。
二級(jí)分類(lèi)實(shí)體由名稱(chēng)、操作者和創(chuàng)建日期等屬性組成,代碼如下所示:
@Entity
@Table(name ="tsubsorts")
public class Subsorts implements java.io.Serializable t
@Id
@Generatedvalue(strategy = GenerationType. IDENTITY)private Long id;
private string name;
private String operator;
@DateTimeFormat (pattern = "Yyyy-MM-dd HH:mm:ss")
@Column (name = "created",columnDefinition = "timestamp defaultcurrent_timestamp")
@Temporal (TemporalType.TIMESTAMP)private Date created;
...
}二級(jí)分類(lèi)使用了表結(jié)構(gòu)“t_subsorts”來(lái)存儲(chǔ)數(shù)據(jù),字段定義與主類(lèi)的定義幾乎相同。
在上面兩個(gè)實(shí)體對(duì)象的設(shè)計(jì)中,我們通過(guò)主類(lèi)使用一對(duì)多的方式與二級(jí)分類(lèi)實(shí)現(xiàn)關(guān)聯(lián)設(shè)計(jì),這樣,當(dāng)在主類(lèi)中進(jìn)行查詢(xún)時(shí),將可以同時(shí)獲取二級(jí)分類(lèi)的數(shù)據(jù);而對(duì)主類(lèi)的存儲(chǔ)和更新,也將自動(dòng)涉及分類(lèi)的相關(guān)操作。
有關(guān)實(shí)體建模的設(shè)計(jì),特別是關(guān)聯(lián)關(guān)系的設(shè)計(jì),我們主要說(shuō)明以下幾個(gè)重要的功能。
(1)實(shí)體對(duì)象必須有一個(gè)唯一標(biāo)識(shí)。
這里使用Long類(lèi)型定義對(duì)象的身份標(biāo)識(shí)“id”,并且這個(gè)“id”將由數(shù)據(jù)庫(kù)自動(dòng)生成。在實(shí)際應(yīng)用中,推薦使用UUID作為對(duì)象的唯一標(biāo)識(shí),這樣不僅可以保持這一字段長(zhǎng)度的一致性,還能保證這一標(biāo)識(shí)在整個(gè)數(shù)據(jù)庫(kù)中的唯一性,而且還將非常有利于數(shù)據(jù)庫(kù)的集群設(shè)計(jì)。
(2)日期屬性要使用正確的格式。
使用注解“@DateTimeFormat”對(duì)日期進(jìn)行格式化,不僅可以保證日期正常顯示,還能保證在參數(shù)傳遞中日期的正確性。注意,上面的創(chuàng)建日期“created”使用了默認(rèn)值設(shè)置。
(3)使用合理的關(guān)聯(lián)設(shè)置。
關(guān)聯(lián)設(shè)置是實(shí)體設(shè)計(jì)的關(guān)鍵,為了避免引起遞歸調(diào)用,最好使用單向關(guān)聯(lián)設(shè)置,即在互相關(guān)聯(lián)的兩個(gè)對(duì)象之中,只在一個(gè)對(duì)象中進(jìn)行關(guān)聯(lián)設(shè)置。
一般來(lái)說(shuō),多對(duì)多的關(guān)聯(lián)可以使用中間表來(lái)存儲(chǔ)關(guān)聯(lián)關(guān)系,而一對(duì)多或多對(duì)一的關(guān)聯(lián)關(guān)系可以使用一個(gè)字段來(lái)存儲(chǔ)關(guān)聯(lián)對(duì)象的外鍵。例如,在上面的實(shí)體設(shè)計(jì)中,我們使用“sorts_id"作為二級(jí)分類(lèi)與主類(lèi)關(guān)聯(lián)的外鍵。
在主類(lèi)實(shí)體的關(guān)聯(lián)設(shè)置中,我們還使用了級(jí)聯(lián)的操作設(shè)置:“CascadeType.REMOVE”。這樣,當(dāng)主類(lèi)中的一個(gè)類(lèi)別被刪除時(shí),將會(huì)自動(dòng)刪除與其關(guān)聯(lián)的所有分類(lèi)。
有關(guān)級(jí)聯(lián)的設(shè)置,可以使用的選項(xiàng)如下所示:
CascadeType.PERSIST:級(jí)聯(lián)保存。
CascadeType.REMOVE:級(jí)聯(lián)刪除。
CascadeType.MERGE:級(jí)聯(lián)合并(更新)。
CascadeType.DETACH:級(jí)聯(lián)脫管/游離。
CascadeType.ALL:以上所有級(jí)聯(lián)操作。
查詢(xún)對(duì)象設(shè)計(jì)
我們將查詢(xún)對(duì)象設(shè)計(jì)放在一個(gè)公共模塊catalog-object中,這樣,其他兩個(gè)模塊都可以進(jìn)行調(diào)用。使用查詢(xún)對(duì)象(Query Object,qo)是為了與vo進(jìn)行區(qū)分。有人把vo看成值對(duì)象(ValueObject),也有人把vo看成視圖對(duì)象(View Object),所以很容易引起誤解。這兩種對(duì)象的意義和用途是不一樣的,值對(duì)象表示的是與實(shí)體不同的一些數(shù)據(jù),它可以作為視圖顯示;而視圖對(duì)象是只能作為視圖顯示的一種數(shù)據(jù)。
因?yàn)閷?shí)體是有生命周期和狀態(tài)的,并且它的狀態(tài)會(huì)直接影響存儲(chǔ)的數(shù)據(jù),所以我們使用一個(gè)無(wú)狀態(tài)的數(shù)據(jù)對(duì)象來(lái)存儲(chǔ)實(shí)體的數(shù)據(jù)。這些數(shù)據(jù)的使用和更改不會(huì)直接影響數(shù)據(jù)存儲(chǔ),因此它的使用是安全的,也可以用之即棄。
我們既可以將查詢(xún)對(duì)象作為值對(duì)象使用,也可以將查詢(xún)對(duì)象作為視圖對(duì)象使用,還可以將查詢(xún)對(duì)象作為查詢(xún)參數(shù)的一個(gè)集合來(lái)使用,即相當(dāng)于一個(gè)數(shù)據(jù)傳輸對(duì)象(Data Transfer Object, dto)。
我們只要使用一個(gè)查詢(xún)對(duì)象qo,就可以包含vo、dto等對(duì)象的功能,這是一種簡(jiǎn)化設(shè)計(jì)。qo有時(shí)會(huì)包含一些冗余數(shù)據(jù),但這對(duì)于使用方來(lái)說(shuō)影響不大。例如,在我們的查詢(xún)對(duì)象中,將會(huì)包含分頁(yè)所需的頁(yè)碼和頁(yè)大小等分頁(yè)屬性數(shù)據(jù),而在視圖顯示中并不需要這些數(shù)據(jù),所以它可以不用理會(huì)這些數(shù)據(jù)。
相對(duì)于主類(lèi)實(shí)體,它的查詢(xún)對(duì)象的設(shè)計(jì)如下所示:
public class SortsQ0 extends PageQ0 {
private Long id;
private String name;
private String operator;
@DateTimeFormat (pattern = "yyyY-MM-dd HH:mm :ss")private Date created;
private List subsortses = new ArrayList<>();
...
} 其中,它所繼承的PageQo查詢(xún)對(duì)象將提供兩個(gè)分頁(yè)查詢(xún)參數(shù),實(shí)現(xiàn)代碼如下所示:
public class PageQ0 [
private Integer page = 0;private Integer size = 10;
...
}在分頁(yè)參數(shù)中,只有一個(gè)頁(yè)碼和每頁(yè)大小的設(shè)定兩個(gè)字段。
數(shù)據(jù)持久化設(shè)計(jì)
使用JPA進(jìn)行實(shí)體數(shù)據(jù)持久化設(shè)計(jì)是比較容易的,只要為實(shí)體創(chuàng)建一個(gè)存儲(chǔ)庫(kù)接口,將實(shí)體對(duì)象與JPA的存儲(chǔ)庫(kù)接口進(jìn)行綁定,就可以實(shí)現(xiàn)實(shí)體的數(shù)據(jù)持久化設(shè)計(jì),相當(dāng)于給實(shí)體賦予了一些訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)的操作行為,包括基本的增刪改查等操作。
除數(shù)據(jù)存儲(chǔ)的基本操作外,我們還可以根據(jù)實(shí)體的字段名稱(chēng)來(lái)聲明查詢(xún)接口,而對(duì)于一些復(fù)雜的查詢(xún),也可以使用SQL查詢(xún)語(yǔ)言設(shè)計(jì)。實(shí)體主類(lèi)的存儲(chǔ)接口設(shè)計(jì)如下所示:
@Repository
public interface SortsRepository extends JpaRepository<Sorts,Long>,JpaSpecificationExecutor<Sorts> {
Page findByNameLike (@Param ( "name ") string name,PageablepageRequest);
@Query("select t from Sorts t where t.name like :name and t.created=:created")
Page findByNameAndCreated(@Param ("name") String name,@Param ("created") Date created, Pageable pageRequest);
Sorts findByName (@Param("name") String name);
@Query ( "select s from Sorts s"+
"left join s.subsortses b"+"where b.id= :id")
Sorts findBySubsortsId(@Param( "id") Long id);
} 這個(gè)接口定義是不用我們實(shí)現(xiàn)的,只要方法定義符合JPA的規(guī)則,后續(xù)的工作就可以交給JPA來(lái)完成。
在JPA中,可以根據(jù)以下方法自定義聲明方法的規(guī)則,即在接口中使用關(guān)鍵字findBy.readBy、getBy等作為方法名的前綴,然后拼接實(shí)體類(lèi)中的屬性字段(首個(gè)字母大寫(xiě)),最后拼接一些SQL查詢(xún)關(guān)鍵字(也可不拼接),組成一個(gè)查詢(xún)方法。下面是一些查詢(xún)關(guān)鍵字的使用實(shí)例:
And,例如findByIdAndName(Long id, String name);
Or,例如findByldOrName (Long id, String name);
Between,例如 findByCreatedBetween(Date start,Date end); LessThan,例如findByCreatedLessThan(Date start);
GreaterThan,例如findByCreatedGreaterThan(Date start); IsNull,例如findByNameIsNull();
IsNotNull,例如 findByNamelsNotNull();
NotNull,與IsNotNull等價(jià);
Like,例如 findByNameLike(String name);
NotLike,例如 findByNameNotLike(String name);
OrderBy,例如findByNameOrderByIdAsc(String name); Not,例如 findByNameNot(String name);
In,例如 findByNameIn(Collection
nameList); NotIn,例如 findByNameNotIn(Collection
nameList)。
通過(guò)注解@Query使用SQL查詢(xún)語(yǔ)言設(shè)計(jì)的查詢(xún),基本與數(shù)據(jù)庫(kù)的查詢(xún)相同,這里只是使用實(shí)體對(duì)象的名字代替了數(shù)據(jù)庫(kù)表的名字。
在上面的存儲(chǔ)庫(kù)接口定義中,我們不但繼承了JPA的基礎(chǔ)存儲(chǔ)庫(kù)JpaRepository,還繼承了一個(gè)比較特別的存儲(chǔ)庫(kù)JpaSpecificationExecutor,通過(guò)這個(gè)存儲(chǔ)庫(kù)可以進(jìn)行一些復(fù)雜的分頁(yè)設(shè)計(jì)。
數(shù)據(jù)管理服務(wù)設(shè)計(jì)
前面的持久化設(shè)計(jì)已經(jīng)在實(shí)體與數(shù)據(jù)庫(kù)之間建立了存取關(guān)系。為了更好地對(duì)外提供數(shù)據(jù)訪(fǎng)問(wèn)服務(wù),我們需要對(duì)存儲(chǔ)庫(kù)的調(diào)用再進(jìn)行一次封裝。在這次封裝中,我們可以實(shí)現(xiàn)統(tǒng)一事務(wù)管理及其分頁(yè)的查詢(xún)?cè)O(shè)計(jì)。分類(lèi)的數(shù)據(jù)管理服務(wù)設(shè)計(jì)代碼如下所示:
@Service
@Transactional
public class SortsService {
@Autowired
private SortsRepository sortsRepository;
public Sorts findOne (Long id){
Sorts sorts =sortsRepository.findById(id).get();return sorts;
public Sorts findByName (string name){
return sortsRepository.findByName (name) ;
}
public String save (Sorts sorts){
tryi
sortsRepository.save(sorts);
return sorts.getId() .toString();}catch(Exception e){
e.printStackTrace();return e.getMessage();
}
public String delete (Long id){
try{
sortsRepository.deleteById(id);return id.toString();
Jcatch(Exception e){
e.printStackTrace();return e.getMessage ();
public Page findAll (SortsQo sortsQo) {
Sort sort = new Sort (Sort.Direction. DESC, "created");
Pageable pageable = PageRequest.of (sortsQo.getPage (), sortsQo.getSize(),
sort);
return sortsRepository.findAll (new Specification()1
@override
public Predicate toPredicate (Root root, CriteriaQuery>query,
CriteriaBuilder criteriaBuilder ) {
List predicatesList =new ArrayList();
if(CommonUtils.isNotNull (sortsQo.getName()){
predicatesList.add (criteriaBuilder.like(root.get ("name"),
"號(hào)"+sorts0o.getName()+"%"));
}
if(CommonUtils.isNotNull (sortsQo.getCreated())){
predicatesList.add (criteriaBuilder.greaterThan (root.get ("created"),sortsQo.getCreated()));
}
query.where(predicatesList.toArray (new
Predicate[predicatesList.size()]));
return query.getRestriction();
}, pageable);
}
} 在上面的代碼中,使用注解@Transactional 實(shí)現(xiàn)了隱式事務(wù)管理,對(duì)于一些基本的數(shù)據(jù)操作,可直接調(diào)用存儲(chǔ)庫(kù)接口的方法。
在上述代碼中,使用findAll方法實(shí)現(xiàn)了分頁(yè)查詢(xún)的設(shè)計(jì)。在這個(gè)設(shè)計(jì)中,可以定義排序的方法和字段,以及對(duì)頁(yè)碼和每頁(yè)行數(shù)的設(shè)定,同時(shí),還可以根據(jù)查詢(xún)參數(shù)動(dòng)態(tài)地設(shè)置查詢(xún)條件。在這里,我們既可以按分類(lèi)的名稱(chēng)進(jìn)行模糊查詢(xún),也可以按分類(lèi)的創(chuàng)建時(shí)間進(jìn)行限定查詢(xún)。
單元測(cè)試
在完成上節(jié)的設(shè)計(jì)之后,我們可以寫(xiě)一個(gè)測(cè)試用例驗(yàn)證領(lǐng)域服務(wù)的設(shè)計(jì)。需要注意的是,因?yàn)樵谇懊娴腏PA配置中已經(jīng)有了更新表結(jié)構(gòu)的配置,所以如果表結(jié)構(gòu)不存在,則會(huì)自動(dòng)生成;如果表結(jié)構(gòu)更新,則啟動(dòng)程序也會(huì)自動(dòng)更新。下面的測(cè)試用例演示了如何插入分類(lèi)和主類(lèi)的數(shù)據(jù):
@RunWith(SpringJUnit4ClassRunner.class)
eContextConfiguration (classes = {UpaConfiguration.class,SortsRestApiApplication.class})
@SpringBootTest
public class SortsTest{
private static Logger logger = LoggerFactory.getLogger (SortsTest.class);
CAutowired
private SortsService sortsService;@Autowired
private SubsortsService subsortsService;
@Test
public void insertData() {
Sorts sorts =new Sorts();sorts.setName("圖書(shū)");
sorts.setOperator( "editor");sorts.setCreated(new Date());
//
Sorts sorts = sortsService.findByName("圖書(shū)");
Subsorts subsorts = new Subsorts();subsorts.setName("計(jì)算機(jī)");
subsorts.setOperator("editor");subsorts.setCreated(new Date());
subsortsService.save (subsorts);
Assert.notNull(subsorts.getId(), "insert sub error");
sorts.addSubsorts(subsorts);
sortsService.save(sorts);
Assert.notNull (sorts.getId(), "not insert sorts");
}
...
}其他查詢(xún)的測(cè)試用例可以參照這個(gè)方法設(shè)計(jì),如果斷言沒(méi)有錯(cuò)誤,則說(shuō)明測(cè)試符合預(yù)期,即不會(huì)提示任何錯(cuò)誤信息。在調(diào)試環(huán)境中,還可以借助控制臺(tái)信息分析測(cè)試的過(guò)程。
類(lèi)目接口微服務(wù)開(kāi)發(fā)
類(lèi)目接口微服務(wù)是一個(gè)獨(dú)立的微服務(wù)應(yīng)用,它將使用基于REST 協(xié)議的方式,對(duì)外提供一些有關(guān)類(lèi)目查詢(xún)和類(lèi)目數(shù)據(jù)管理的接口服務(wù)。這個(gè)接口服務(wù),既可以用于商家后臺(tái)進(jìn)行類(lèi)目管理的設(shè)計(jì)之中,也可以用于移動(dòng)端、App或其他客戶(hù)端程序的設(shè)計(jì)之中。
當(dāng)上面的單元測(cè)試完成之后,我們就可以使用上面設(shè)計(jì)中提供的數(shù)據(jù)服務(wù)進(jìn)行類(lèi)目接口微服務(wù)的開(kāi)發(fā)了。
RESTful接口開(kāi)發(fā)
我們將遵循REST協(xié)議的規(guī)范設(shè)計(jì)基于RESTful的接口開(kāi)發(fā),例如,對(duì)于分類(lèi)來(lái)說(shuō),我們可以設(shè)計(jì)如下請(qǐng)求:
GET/sorts/{id}:根據(jù)ID獲取一個(gè)分類(lèi)的詳細(xì)信息;GET /sorts:查詢(xún)分類(lèi)的分頁(yè)列表;
POST /sorts:創(chuàng)建一個(gè)新分類(lèi); PUT /sorts:更新一個(gè)分類(lèi);
DELETE /sorts/{id}:根據(jù)ID刪除一個(gè)分類(lèi)。
下面的代碼展示了分類(lèi)接口設(shè)計(jì)的部分實(shí)現(xiàn),完整的代碼可以查看項(xiàng)目工程的相關(guān)源代碼:
@RestController
@RequestMapping("/sorts")
public class SortsController {
private static Logger logger = LoggerFactory.getLogger(SortsController.class);
@Autowired
private SortsService sortsService;
@GetMapping (value="/{id] ")
public String fnidById(@PathVariable Long id){
return new Gson().toJson (sortsService.findOne (id));
)
@GetMapping ()
public String findAll(Integer index,Integer size, String name)
try {
SortsQo sortsQ0 = new SortsQ0();if(CommonUtils.isNotNul1(index)){
sortsQo .setPage(index);
}
if(CommonUtils.isNotNull(size)){
sortsQ0.setsize(size);
}
if(CommonUtils.isNotNul1 (name)){
sortsQo. setName(name);
}
Page orderses = sortsService.findAll(sortsQo);
Map<String, 0bject> page = new HashMap<>();
page.put( "content", orderses.getContent();
page.put ("totalPages", orderses.getTotalPages());
page.put ("totalelements",orderses.getTotalElements());
return new Gson() .toJson (page);
}
Jcatch(Exception e){
e.printStackTrace();
}
return null;
)
@PostMapping()
public String save (CRequestBody SortsQo sortsQo) throws Exception{t
Sorts sorts =new sorts();
BeanUtils.copyProperties (sortsQ0,sorts);sorts.setCreated (new Date());
List subsortsList = new ArrayList<>();//轉(zhuǎn)換每個(gè)分類(lèi),然后加入主類(lèi)的分類(lèi)列表中
for(SubsortsQo subsortsQ0 : sortsQo.getSubsortses()){
Subsorts subsorts =new Subsorts();
BeanUtils.copyProperties(subsortsQ0,subsorts);subsortsList.add(subsorts);
)
sorts. setSubsortses (subsortsList);
String ret =sortsService.save(sorts);logger.info("新增="+ ret);
return ret;
}
...
} 在上面微服務(wù)接口設(shè)計(jì)中,使用RestController 定義了對(duì)外提供服務(wù)的URL 接口,而接口之中有關(guān)數(shù)據(jù)的訪(fǎng)問(wèn)則通過(guò)調(diào)用SortsService的各種方法來(lái)實(shí)現(xiàn)。其中,在接口調(diào)用中,都使用JSON方式的數(shù)據(jù)結(jié)構(gòu)來(lái)傳輸數(shù)據(jù),所以在上面代碼中,顯式或隱式地使用了JSON 的數(shù)據(jù)結(jié)構(gòu)。對(duì)于一個(gè)數(shù)據(jù)對(duì)象來(lái)說(shuō),為了保證其數(shù)據(jù)的完整性,我們一般使用GSON 工具對(duì)數(shù)據(jù)進(jìn)行顯式轉(zhuǎn)換。
需要注意的是,因?yàn)樵跀?shù)據(jù)傳輸中使用的是查詢(xún)對(duì)象,所以當(dāng)進(jìn)行數(shù)據(jù)保存和更新操作時(shí),需要將查詢(xún)對(duì)象轉(zhuǎn)換為實(shí)體對(duì)象。
微服務(wù)接口調(diào)試
當(dāng)微服務(wù)接口開(kāi)發(fā)完成之后,即可啟動(dòng)項(xiàng)目的應(yīng)用程序進(jìn)行簡(jiǎn)單調(diào)試。對(duì)于類(lèi)目微服務(wù)接口,我們可以啟動(dòng)catalog-restapi模塊中的主程序SortsRestApiApplication進(jìn)行調(diào)試。
在啟動(dòng)成功之后,對(duì)于一些GET請(qǐng)求,可以直接通過(guò)瀏覽器進(jìn)行調(diào)試。
例如,通過(guò)下列鏈接地址,可以根據(jù)分類(lèi)ID查看一個(gè)分類(lèi)的信息:
http://localhost:9091/sorts/1如果數(shù)據(jù)存在,則返回如圖6-3所示的JSON數(shù)據(jù)。

使用如下鏈接地址可以查詢(xún)分頁(yè)第一頁(yè)的數(shù)據(jù):
http://localhost:9091/sorts如果查詢(xún)成功,則可以看到如圖6-4所示的信息。

因?yàn)镻OST 和 PUT等請(qǐng)求在調(diào)試時(shí)需要傳輸參數(shù),所以不能直接使用瀏覽器進(jìn)行測(cè)試,但是可以通過(guò)Postman等工具進(jìn)行調(diào)試。
基于RESTful的微服務(wù)接口調(diào)用
我們可以使用多種方法調(diào)用基于RESTful接口的服務(wù)。例如,可以使用HTTP 訪(fǎng)問(wèn)(例如HttpClient),或者使用RestTemplate的方式進(jìn)行調(diào)用,等等。但是,在微服務(wù)應(yīng)用中,最好的方法是使用聲明式的FeignClient。
因?yàn)?FeignClient是為其他微服務(wù)進(jìn)行調(diào)用的,所以這里將這些設(shè)計(jì)都放在模塊catalog-object中進(jìn)行開(kāi)發(fā)。
聲明式FeignClient 設(shè)計(jì)
FeignClient是一個(gè)聲明式的客戶(hù)端,為了使用這個(gè)工具組件,我們需要在項(xiàng)目對(duì)象模型中引入 FeignClient的依賴(lài),代碼如下所示:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>dependency>針對(duì)主類(lèi)的接口調(diào)用,我們可以定義一個(gè)接口程序SortsClient,根據(jù)微服務(wù)catalogapi提供的接口服務(wù),使用如下所示的方法聲明一些調(diào)用方法:
@FeignClient ("catalogapi")
public interface SortsClient {
@RequestMapping (method = RequestMethod.GET, value = "/sorts/{id}")String findById(CRequestParam("id") Long id);
@RequestMapping (method = RequestMethod.GET,value = "/sorts/findAll")String findList();
CRequestMapping (method = RequestMethod.GET, value = "/sorts",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
produces =MediaType.APPLICATION_JSON_UTF8_VALUE)
String findPage (CRequestParam("index") Integer index, @RequestParam ("size")Integer size,
@RequestParam( "name") String name);
@RequestMapping (method =RequestMethod.GET, value = "/sorts/findAll",
consumes = MediaType.APPLICATION JSON UTF8_VALUE,
produces = MediaType.APPLICATION_ JSON_UTF8_VALUE)
String findAll();
@RequestMapping (method = RequestMethod. POST, value = "/sorts",
consumes = MediaType.APPLICATION_JSON UTF8 VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
String create(@RequestBody SortsQ0 sortsQ0) ;
@RequestMapping (method = RequestMethod.PUT,value = "/sorts",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
produces = MediaType.APPLICATION_JSON_ UTF8_VALUE)
String update(@RequestBody SortsQo sortsQo);
@RequestMapping (method = RequestMethod. DELETE,value = "/sorts/{id}")String delete (@RequestParam("id") Long id);
}在這個(gè)實(shí)現(xiàn)代碼中,首先通過(guò)注解@FeignClient引用微服務(wù)catalogapi,然后使用其暴露出來(lái)的URL直接聲明調(diào)用方法。需要注意的是,這里的數(shù)據(jù)傳輸,即數(shù)據(jù)的生產(chǎn)和消費(fèi),都是通過(guò)JSON格式進(jìn)行的,所以為了保證中文字符的正確性,我們使用UTF8編碼。
斷路器的使用
基于SortsClient的聲明方法,我們可以創(chuàng)建一個(gè)服務(wù)類(lèi)SortsRestService進(jìn)行調(diào)用。然后,使用SortsRestService提供的功能,就可以像使用本地方法一樣使用微服務(wù)catalogapi 提供的接口方法。服務(wù)類(lèi)SortsRestService的實(shí)現(xiàn)代碼如下所示:
@service
public class SortsRestService {
CAutowired
private SortsClient sortsClient;
@HystrixCommand (fallbackMethod = "findByIdFallback")public String findById(Long id){
return sortsClient.findById(id);
private String findByIdFal1back (Long id){
SortsQo sortsQo = new SortsQ0();
return new Gson() .toJson (sortsQo);
}
...
}在上面的代碼中,我們實(shí)現(xiàn)了對(duì)SortsClient的調(diào)用,同時(shí)增加了一個(gè)注解@HystrixCommand。通過(guò)這個(gè)注解,定義了一個(gè)回退方法。而這一回退方法的設(shè)計(jì),就是SpringCloud組件提供的斷路器功能的實(shí)現(xiàn)方法。斷路器的含義是,當(dāng)服務(wù)調(diào)用過(guò)載或不可用時(shí),通過(guò)降級(jí)調(diào)用或故障轉(zhuǎn)移的方法,減輕服務(wù)的負(fù)載。這里我們使用了回退方法設(shè)計(jì),以快速響應(yīng)來(lái)自客戶(hù)端的訪(fǎng)問(wèn),并保障客戶(hù)端對(duì)微服務(wù)的訪(fǎng)問(wèn)不會(huì)因?yàn)槌霈F(xiàn)故障而崩潰。斷路器的設(shè)計(jì)就像電路的保護(hù)開(kāi)關(guān)一樣,對(duì)系統(tǒng)服務(wù)起到一定的保護(hù)作用。與保護(hù)開(kāi)關(guān)不同的是,當(dāng)系統(tǒng)恢復(fù)正常時(shí),斷路器會(huì)自動(dòng)失效,不用人為干預(yù)。
類(lèi)目管理Web應(yīng)用微服務(wù)開(kāi)發(fā)
這里 類(lèi)目管理是一個(gè)基于 PC 端的 Web 應(yīng)用,它也是一個(gè)獨(dú)立的微服務(wù)應(yīng)用。這個(gè)應(yīng)用在項(xiàng)目工程的模塊catalog-web 中實(shí)現(xiàn),可以把它看成一個(gè)獨(dú)立的項(xiàng)目。
在這個(gè)應(yīng)用中,我們將演示如何使用類(lèi)目管理微服務(wù)接口提供的服務(wù),進(jìn)行相關(guān)應(yīng)用功能的開(kāi)發(fā),從而實(shí)現(xiàn)在PC端提供一個(gè)對(duì)類(lèi)目進(jìn)行操作管理的友好操作界面。
接口調(diào)用引用的相關(guān)配置
上面的接口調(diào)用服務(wù)是在模塊catalog-object 中進(jìn)行開(kāi)發(fā)的,想要在模塊“catalog-web”中使用這些服務(wù),就必須先在項(xiàng)目對(duì)象模型中進(jìn)行引用配置,代碼如下所示:
<dependency>
<groupId>com.demogroupId>
<artifactId>catalog-objectartifactId><version>${project.version}version>dependency>因?yàn)閮蓚€(gè)模塊處于同一個(gè)項(xiàng)目工程之中,所以上面引用配置的版本直接使用了項(xiàng)目的版本。這樣,當(dāng)接口服務(wù)啟動(dòng)之后,我們就可以在接下來(lái)的 Web應(yīng)用中進(jìn)行相關(guān)調(diào)用了。
需要注意的是,如果有多個(gè)FeignClient程序調(diào)用了同一個(gè)微服務(wù)接口服務(wù),則必須在項(xiàng)目的配置文件中使用如下所示的配置進(jìn)行設(shè)置,以支持這種調(diào)用方式。因?yàn)檫@個(gè)Spring Cloud版本的默認(rèn)配置是不開(kāi)啟這種調(diào)用方式的:
#允許多個(gè)接口使用相同的服務(wù)
spring:
main:
allow-bean-definition-overriding: trueSpring MVC控制器設(shè)計(jì)
Spring MVC是 Web應(yīng)用開(kāi)發(fā)的一個(gè)基礎(chǔ)組件,下面我們使用這一組設(shè)計(jì)一個(gè)控制器。在Web應(yīng)用的主類(lèi)控制器設(shè)計(jì)中,我們直接使用上面設(shè)計(jì)的服務(wù)類(lèi):SortsRestService。我們可以像使用本地方法一樣使用SortsRestService類(lèi),直接調(diào)用微服務(wù)提供的接口服務(wù),代碼如下所示:
@GRestController
@RequestMapping ( "/sorts")
public class SortsController {
private static Logger logger =
LoggerFactory.getLogger(SortsController.class);
@Autowired
private SortsRestService sortsRestService;
@GetMapping(value=" /index")
public Mode1AndView index(){
return new ModelAndview( "sorts/index");
@GetMapping (value="/{id]")
public ModelAndView findById(@PathVariable Long id){
return new ModelAndView("sorts/show", "sorts",
new Gson() .fromJson (sortsRestService.findById(id),
SortsQo.class));
}
...
}上面代碼中的findByld方法是一個(gè)使用頁(yè)面來(lái)顯示分類(lèi)信息的設(shè)計(jì)。在這個(gè)設(shè)計(jì)中,一方面引用了上面設(shè)計(jì)的服務(wù)類(lèi)SortsRestService,并調(diào)用了它的findByld 方法,進(jìn)行數(shù)據(jù)查詢(xún);另一方面將查詢(xún)數(shù)據(jù)通過(guò)一個(gè) show頁(yè)面顯示出來(lái)。這個(gè)設(shè)計(jì)與一般的本地調(diào)用不同的是,查詢(xún)數(shù)據(jù)時(shí)得到的返回值是一種ISON結(jié)構(gòu),所以必須將它轉(zhuǎn)化為一個(gè)查詢(xún)對(duì)象,這樣才能方便使用。
接下來(lái)的頁(yè)面設(shè)計(jì)將會(huì)用到Thymeleaf模板的功能。
使用 Thymeleaf模板
在 Web應(yīng)用的頁(yè)面設(shè)計(jì)中,我們將使用Thymeleaf 這個(gè)模板,因此,必須在catolog-web模塊中引入Thymeleaf 的依賴(lài),代碼如下所示:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleafgroupId>
<artifactId>thymeleaf-layout-dialectartifactId><version>2.3.0version>
dependency>有關(guān) Thymeleaf 的配置,使用其默認(rèn)配置即可,即只要在程序的資源目錄中有static和templates這兩個(gè)目錄就可以了。這兩個(gè)目錄分別用來(lái)存放靜態(tài)文件和模板設(shè)計(jì)及其頁(yè)面設(shè)計(jì)文件,頁(yè)面文件的后綴默認(rèn)使用html。
HTML頁(yè)面設(shè)計(jì)
在6.10節(jié)控制器的設(shè)計(jì)中,類(lèi)目信息輸出的是一個(gè)show頁(yè)面,它的設(shè)計(jì)在show.html文件中,代碼如下所示:
<html xmlns:th="http://www.thymeleaf.org"><div class="addInfBtn">
<h3 class="itemTit"><span>類(lèi)目信息span>h3><table class="addNewInfList">
<tr>
<th>名稱(chēng)th>
<td width="240"><input class="inp-list w-200 clear-mr f-left"
type="text" th:value="$ {sorts.namel" readonly="true"/>td>
<th>操作者th>
<td><input class="inp-list w-200 clear-mr f-left" type="text"
th:value="$ {sorts.operator}" readonly="true"/>td>
tr>
<tr>
<th>子類(lèi)th><td>
<select multiple= "multiple" readonly="true">
<option th:each="subsorts:${sorts.subsortses] "
th:text="${#strings. length(subsorts.name)
>20?#strings.substring (subsorts.name,0,20)+'...':subsorts.name} "
th:selected="true"
>option>
select>
td>
<th>日期th><td>
<input onfocus="WdatePicker ({dateFmt:'yyyy-MiM-dd HH :mm:ss'))"
type="text" class="inp-list w-200 clear-mr f-left" th:value="${sorts.created)?$ {#dates.format(sorts.created, 'vyvy-MM-dd HlH:mm:ss')}:''" readonly="true"/>
td>
tr>table>
<div class="bottomBtnBox">
<a class="btn-93x38 backBtn" href="javascript:closeDialog (0)">返回a>div>
div>從上面的代碼可以看出,除用到Thymeleaf特有的地方外,其他設(shè)計(jì)都與一般的HTML標(biāo)簽語(yǔ)言相同。設(shè)計(jì)之后,這個(gè)頁(yè)面的最終效果如圖6-5所示。

統(tǒng)一風(fēng)格模板設(shè)計(jì)
Thymeleaf更強(qiáng)大的功能是提供了一個(gè)統(tǒng)一風(fēng)格的模板設(shè)計(jì),即整個(gè)網(wǎng)站可以使用統(tǒng)一風(fēng)格的框架結(jié)構(gòu)。在類(lèi)目管理這個(gè)項(xiàng)目中,使用了總體頁(yè)面框架設(shè)計(jì)layout.html,代碼如下所示:
<! DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout">
<body>
<div class="headerBox">
<div class="topBox">
<div class="topLogo f-left">
<a href="#"><img th:src="@{/images/logo.pngl "/>a>div>
div>div>
<div class="locationLine" layout:fragment="prompt">
當(dāng)前位置:首頁(yè) > <em>頁(yè)面em>
div>
<table class="globalMainBox" style="position:relative;z-index:1">
<tr>
<td class="columnLeftBoX" valign="top">
<div th:replace="fragments/nav ::nav">div>td>
<td class="whiteSpace">td>
<td class="rightColumnBox" valign="top"><div layout: fragment="content">div>td>
tr>table>
<form th:action="@{/logout}" method="post" id="logoutform">form>
<div class="footBox" th:replace="fragments/footer :: footer">div>body>
html>頁(yè)面上方是狀態(tài)欄,頁(yè)面左側(cè)是導(dǎo)航欄,中間部分是內(nèi)容顯示區(qū)域,底端還有一個(gè)頁(yè)腳設(shè)計(jì)。在引用這個(gè)模板之后,只需對(duì)需要更改的區(qū)域進(jìn)行覆蓋就可以了,而不需要更改的地方使用模板的默認(rèn)設(shè)計(jì)即可。一般來(lái)說(shuō),在使用這個(gè)模板時(shí),只要更改狀態(tài)欄和內(nèi)容顯示區(qū)域就可以了,而導(dǎo)航欄和頁(yè)腳,則可以使用通用的頁(yè)面設(shè)計(jì)。
在這個(gè)例子中,分類(lèi)的主頁(yè)是通過(guò)index.html這個(gè)頁(yè)面設(shè)計(jì)來(lái)引用這個(gè)模板的,代碼如下所示:
<! DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout"
layout:decorator="fragments/layout">
<body>
<div class="locationLine" layout: fragment="prompt">
當(dāng)前位置:首頁(yè)> <em >類(lèi)目管理em>
div>
<div class="statisticBoX w-782"layout:fragment="content">
...
div>body>html>可以看出,在上面的代碼中,我們只更新了狀態(tài)欄和主要內(nèi)容顯示區(qū)域的設(shè)計(jì),其他部分都沿用了模板的設(shè)計(jì)。
在上面的一些設(shè)計(jì)講解和演示中,我們只說(shuō)明了主類(lèi)的設(shè)計(jì),二級(jí)分類(lèi)的設(shè)計(jì)與主類(lèi)的設(shè)計(jì)大同小異,不再贅述。
至此,類(lèi)目管理的微服務(wù)應(yīng)用的開(kāi)發(fā)工作就基本完成了。
現(xiàn)在我們可以體驗(yàn)微服務(wù)之間的調(diào)用了,因?yàn)槭褂昧薙pring Cloud工具組件來(lái)開(kāi)發(fā),所以在各個(gè)方面的實(shí)現(xiàn)都是非常方便的。當(dāng)然,對(duì)于微服務(wù)的調(diào)用,不僅僅是Web應(yīng)用的調(diào)用,還有其他如App應(yīng)用、微信公眾號(hào)或小程序客戶(hù)端,或者其他語(yǔ)言的設(shè)計(jì)、異構(gòu)環(huán)境的調(diào)用,等。不管使用哪種工具來(lái)設(shè)計(jì),只要能用HTTP,就可以輕易實(shí)現(xiàn)對(duì)微服務(wù)的調(diào)用。
總體測(cè)試
在類(lèi)目管理的微服務(wù)接口及其Web微服務(wù)應(yīng)用都開(kāi)發(fā)完成之后,我們就可以進(jìn)行一個(gè)總體測(cè)試了。首先確認(rèn)Consul已經(jīng)運(yùn)行就緒,然后先后啟動(dòng)catalog-restapi和 catalog-web兩個(gè)模塊。啟動(dòng)成功之后,通過(guò)瀏覽器訪(fǎng)問(wèn)如下鏈接地址:
http://localhost:8091如果一切正常,則可以進(jìn)入如圖6-6所示的類(lèi)目管理的主頁(yè)。在這里,我們可以分別對(duì)主類(lèi)和二級(jí)分類(lèi)中的所有類(lèi)目進(jìn)行增刪改查的所有操作。

有關(guān)項(xiàng)目的打包與部署
在使用IDEA開(kāi)發(fā)工具執(zhí)行打包時(shí),可以使用 Maven項(xiàng)目管理器執(zhí)行打包操作,如圖6-7所示。

如果是模塊化的項(xiàng)目,請(qǐng)務(wù)必在項(xiàng)目的根(root)目錄中執(zhí)行打包操作,這樣才能將其所依賴(lài)的模塊同時(shí)打包在一起。
當(dāng)打包完成之后,可以使用命令終端,分別切換到catalog-restapi和 catalog-web模塊的 target目錄中執(zhí)行下列命令,啟動(dòng)應(yīng)用進(jìn)行調(diào)試:
java -jar catalog*.jar以這種方式啟動(dòng)應(yīng)用,與上面使用IDEA工具進(jìn)行調(diào)試時(shí)的效果是一樣的。如果啟動(dòng)正常,則可以進(jìn)行與上面一樣的測(cè)試。
這種啟動(dòng)方式也可以作為一種普通的方式來(lái)發(fā)布微服務(wù),在生產(chǎn)環(huán)境中,可以在上面指令的基礎(chǔ)上增加一些內(nèi)存和日志存儲(chǔ)方面的參數(shù)。
有關(guān)微服務(wù)應(yīng)用的部署,將在運(yùn)維部署部分進(jìn)行詳細(xì)介紹。
小結(jié)
本章介紹了電商平臺(tái)的類(lèi)目管理接口和Web類(lèi)目管理后臺(tái)兩個(gè)微服務(wù)的開(kāi)發(fā)實(shí)例,通過(guò)這個(gè)項(xiàng)目的開(kāi)發(fā)和演示,我們清楚了微服務(wù)之間快速通信和相互調(diào)用的方法。在類(lèi)目管理接口開(kāi)發(fā)中,我們通過(guò)Spring Data JPA開(kāi)發(fā)工具,了解了DDD開(kāi)發(fā)方法在Spring 開(kāi)發(fā)框架中的工作原理和實(shí)現(xiàn)方法。通過(guò)類(lèi)目管理接口的實(shí)現(xiàn),我們將有狀態(tài)的數(shù)據(jù)訪(fǎng)問(wèn)行為,轉(zhuǎn)變成沒(méi)有狀態(tài)的接口服務(wù)。
下一章,我們將介紹另一種數(shù)據(jù)庫(kù)開(kāi)發(fā)工具 MyBatis,體驗(yàn)不同的數(shù)據(jù)庫(kù)開(kāi)發(fā)工具在Spring項(xiàng)目工程中的應(yīng)用方法。
本文給大家講解的內(nèi)容是SpringCloud微服務(wù)架構(gòu)實(shí)戰(zhàn):類(lèi)目管理微服務(wù)開(kāi)發(fā)
下篇文章給大家講解的是SpringCloud微服務(wù)架構(gòu)實(shí)戰(zhàn):庫(kù)存管理與分布式文件系統(tǒng);
覺(jué)得文章不錯(cuò)的朋友可以轉(zhuǎn)發(fā)此文關(guān)注小編;
感謝大家的支持!
本文就是愿天堂沒(méi)有BUG給大家分享的內(nèi)容,大家有收獲的話(huà)可以分享下,想學(xué)習(xí)更多的話(huà)可以到微信公眾號(hào)里找我,我等你哦。
