在梳理完用户需求后就要去定义前后端的接口,接口定义后前端和后端就可以依据接口去开发功能了。
本次定义页面查询接口,本接口供前端请求查询页面列表,支持分页及自定义条件查询方式。
具体需求如下:
1、分页查询CmsPage 集合下的数据
2、根据站点Id、模板Id、页面别名查询页面信息
3、接口基于Http Get请求,响应Json数据
接口的定义离不开数据模型,根据前边对需求的分析,整个页面管理模块的数据模型如下:
页面信息如下:
package com.xuecheng.framework.domain.cms;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
import java.util.List;
/**
* @Author: mrt.
* @Description:
* @Date:Created in 2018/1/24 10:04.
* @Modified By:
*/
@Data
@ToString
@Document(collection = "cms_page")
public class CmsPage {
/**
* 页面名称、别名、访问地址、类型(静态/动态)、页面模版、状态
*/
//站点ID
private String siteId;
//页面ID
@Id
private String pageId;
//页面名称
private String pageName;
//别名
private String pageAliase;
//访问地址
private String pageWebPath;
//参数
private String pageParameter;
//物理路径
private String pagePhysicalPath;
//类型(静态/动态)
private String pageType;
//页面模版
private String pageTemplate;
//页面静态化内容
private String pageHtml;
//状态
private String pageStatus;
//创建时间
private Date pageCreateTime;
//模版id
private String templateId;
//参数列表
private List<CmsPageParam> pageParams;
//模版文件Id
// private String templateFileId;
//静态文件Id
private String htmlFileId;
//数据Url
private String dataUrl;
}
属性说明:
1、定义一个页面需要指定页面所属站点
一个站点包括多个页面,比如:学成在线的门户站点(网站)包括了多个页面。
2、定义一个页面需要指定页面使用的模板
多个页面可以使用相同的模板,比如:商品信息模板,每个商品就是一个页面,所有商品使用同一个商品信息模板
注解说明:
@Data、@ToString、@Document注解表示什么意思?
@Data、@ToString
:是Lombok提供的注解@Document
:是Spring Data mongodb提供的注解,里面 collection 的值就是集合
。@Id
:指定集合的主键上边的Data注解表示什么意思呢?Data注解,ToString注解都是Lombok提供的注解。
Lombok是一个实用的java工具,使用它可以消除java代码的臃肿,Lombok提供一系列的注解,使用这些注解可以不用定义getter/setter、equals、构造方法等,它会在编译时在字节码文件自动生成这些通用的方法,简化开发
人员的工作。
项目官方地址:https://www.projectlombok.org/
比如上节创建的UserTest模型,@Data注解可以自动生成getter/setter方法,@ToString生成tostring方法。
使用方法:
1、在项目中添加Lombok的依赖
作用:项目在编译时根据Lombok注解生成通用方法。
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
2、在IDEA开发工具中添加Lombok插件
作用:使用IDEA开发时根据Lombok注解生成通用方法,不报错。
1、定义请求模型QueryPageRequest
,此模型作为查询条件类型
为后期扩展需求,请求类型统一继承RequestData类型。
package com.xuecheng.framework.domain.cms.request;
import lombok.Data;
/**
* 定义请求类型
*/
@Data
public class QueryPageRequest {
//站点id
private String siteId;
//页面ID
private String pageId;
//页面名称
private String pageName;
//别名
private String pageAliase;
//模版id
private String templateId;
}
2、响应结果类型,分页查询统一使用QueryResponseResult
package com.xuecheng.framework.model.response;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class QueryResponseResult extends ResponseResult {
QueryResult queryResult;
public QueryResponseResult(ResultCode resultCode,QueryResult queryResult){
super(resultCode);
this.queryResult = queryResult;
}
}
package com.xuecheng.framework.model.response;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @Author: mrt.
* @Description:
* @Date:Created in 2018/1/24 18:33.
* @Modified By:
*/
@Data
@ToString
@NoArgsConstructor
public class ResponseResult implements Response {
//操作是否成功
boolean success = SUCCESS;
//操作代码
int code = SUCCESS_CODE;
//提示信息
String message;
public ResponseResult(ResultCode resultCode){
this.success = resultCode.success();
this.code = resultCode.code();
this.message = resultCode.message();
}
public static ResponseResult SUCCESS(){
return new ResponseResult(CommonCode.SUCCESS);
}
public static ResponseResult FAIL(){
return new ResponseResult(CommonCode.FAIL);
}
}
package com.xuecheng.framework.model.response;
/**
* Created by admin on 2018/3/5.
*/
public interface Response {
public static final boolean SUCCESS = true;
public static final int SUCCESS_CODE = 10000;
}
在Api接口工程
专门定义接口,在Api工程单独定义接口的原因如下:
1、接口集中管理
2、Api工程的接口将作为各微服务远程调用使用。
页面查询接口定义如下:
package com.xuecheng.api.cms;
import com.xuecheng.framework.domain.cms.request.QueryPageRequest;
import com.xuecheng.framework.model.response.QueryResponseResult;
/**
* 页面查询接口
*/
public interface CmsPageControllerApi {
public QueryResponseResult findList(int page, int size, QueryPageRequest queryPageRequest);
}
此接口编写后会在CMS服务工程编写Controller类实现此接口。
创建maven工程, CMS工程的名称为 xc-service-manage-cms
,父工程为xc-framework-parent。
pom.xml如下:
<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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xc-framework-parentartifactId>
<groupId>com.xuechenggroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>xc-service-manage-cmsartifactId>
<dependencies>
<dependency>
<groupId>com.xuechenggroupId>
<artifactId>xc-service-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.xuechenggroupId>
<artifactId>xc-framework-modelartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.xuechenggroupId>
<artifactId>xc-framework-utilsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>com.xuechenggroupId>
<artifactId>xc-framework-commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>com.squareup.okhttp3groupId>
<artifactId>okhttpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
dependencies>
project>
由于cms工程要连接mongodb所以需要在在cms服务端工程添加如下依赖:
项目使用spring data mongodb操作mongodb数据库
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
2、创建基本的包结构:
3、配置文件
在classpath下配置application.yml
server:
port: 31001
spring:
application:
name: xc-service-manage-cms
data:
mongodb:
uri: mongodb://root:[email protected]:27017
database: xc_cms
创建工程的日志配置文件logback-spring.xml
<configuration>
<property name="LOG_HOME" value="d:/logs"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
<charset>utf8charset>
encoder>
appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/xc.%d{yyyy-MM-dd}.logfileNamePattern>
rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
encoder>
appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0discardingThreshold>
<queueSize>512queueSize>
<appender-ref ref="FILE"/>
appender>
<logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
logger>
<logger name="org.springframework.boot" level="DEBUG"/>
<root level="info">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
root>
configuration>
4、SpringBoot 启动类
Spring Boot应用需要创建一个应用启动类,启动过程中会扫描Bean并注入spring 容器
注意:此类创建在本工程com.xuecheng.manage_cms
包下,因为只会扫描本包或子包下面的类
package com.xuecheng.manage_cms;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.cms")// 扫描xc-framework-model模块下面的实体类
@ComponentScan(basePackages={"com.xuecheng.api"})// 扫描xc-service-api模块下面的接口
@ComponentScan(basePackages={"com.xuecheng.manage_cms"})// 扫描本模块下的所有类
public class ManageCmsApplication {
public static void main(String[] args) {
SpringApplication.run(ManageCmsApplication.class, args);
}
}
使用springMVC完成接口实现开发,这里暂时使用测试数据,稍后会让controller调用service来查询数据。
package com.xuecheng.manage_cms.controller;
import com.xuecheng.api.cms.CmsPageControllerApi;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.framework.domain.cms.request.QueryPageRequest;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.framework.model.response.QueryResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/cms/page")
public class CmsPageController implements CmsPageControllerApi {
@Override
@GetMapping("/list/{page}/{size}")
public QueryResponseResult findList(@PathVariable("page") int page,
@PathVariable("size") int size,
QueryPageRequest queryPageRequest) {
//暂时采用测试数据,测试接口是否可以正常运行
QueryResult<CmsPage> queryResult = new QueryResult();
queryResult.setTotal(2);
//静态数据列表
List<CmsPage> list = new ArrayList();
CmsPage cmsPage = new CmsPage();
cmsPage.setPageName("测试页面");
list.add(cmsPage);
queryResult.setList(list);
QueryResponseResult queryResponseResult = new
QueryResponseResult(CommonCode.SUCCESS, queryResult);
return queryResponseResult;
}
}
使用浏览器测试,输入:http://localhost:31001/cms/page/list/1/10 查询第1页,每页显示10条记录。
{
"success": true,
"code": 10000,
"message": "操作成功!",
"queryResult": {
"list": [
{
"siteId": null,
"pageId": null,
"pageName": "测试页面",
"pageAliase": null,
"pageWebPath": null,
"pageParameter": null,
"pagePhysicalPath": null,
"pageType": null,
"pageTemplate": null,
"pageHtml": null,
"pageStatus": null,
"pageCreateTime": null,
"templateId": null,
"pageParams": null,
"htmlFileId": null,
"dataUrl": null
}
],
"total": 2
}
}
为什么在 api 模块中定义接口,而不直接在 cms 模块中定义接口?
原因一:在 api 模块中统一的管理接口;
原因二:微服务与微服务之间的远程调用都是基于接口调用的,如果将接口定义在不同的微服务中,若微服务A要调用微服务B,那么微服务A依赖微服务B才能拿到该接口,而现在我们将接口统一的定义在了 api 模块中,那么只需要依赖 api 模块,就能拿到所有的接口了;
原因三:如果我们以后接口的实现类不使用 SpringMVC 了,那么只需要修改实现类即可。
本项目使用Spring Data Mongodb完成Mongodb数据库的查询,Spring Data Mongodb提供一套快捷操作mongodb的方法。
创建Dao,继承MongoRepository,并指定实体类型和主键类型。
CmsPageRepository
package com.xuecheng.manage_cms.dao;
import com.xuecheng.framework.domain.cms.CmsPage;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface CmsPageRepository extends MongoRepository<CmsPage, String> {
}
测试程序使用@SpringBootTest和@RunWith(SpringRunner.class)注解,启动测试类会从main下找springBoot启动类,加载spring容器。
分页查询测试:
package com.xuecheng.manage_cms;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.manage_cms.dao.CmsPageRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest
@RunWith(SpringRunner.class)
public class CmsPageRepositoryTest {
@Autowired
CmsPageRepository cmsPageRepository;
// 分页测试
@Test
public void testFindPage() {
int page = 0; // 从0开始
int size = 10; // 每页记录数
Pageable pageable = PageRequest.of(page, size);
Page<CmsPage> all = cmsPageRepository.findAll(pageable);
System.out.println(all); // Page 1 of 3 containing com.xuecheng.framework.domain.cms.CmsPage instances
}
}
这里Dao接口继承了MongoRepository,在MongoRepository中定义了很多现成的方法,如save、delete等,通
过下边的代码来测试这里父类方法。
此小节内容请同学们自行测试。
// 添加
@Test
public void testInsert(){
// 定义实体类
CmsPage cmsPage = new CmsPage();
cmsPage.setSiteId("s01");
cmsPage.setTemplateId("t01");
cmsPage.setPageName("测试页面");
cmsPage.setPageCreateTime(new Date());
List<CmsPageParam> cmsPageParams = new ArrayList<
CmsPageParam cmsPageParam = new CmsPageParam();
cmsPageParam.setPageParamName("param1");
cmsPageParam.setPageParamValue("value1");
cmsPageParams.add(cmsPageParam);
cmsPage.setPageParams(cmsPageParams);
cmsPageRepository.save(cmsPage);
System.out.println(cmsPage);
}
// 删除
@Test
public void testDelete() {
cmsPageRepository.deleteById("5b17a2c511fe5e0c409e5eb3");
}
//修改
@Test
public void testUpdate() {
Optional<CmsPage> optional = cmsPageRepository.findOne("5b17a34211fe5e2ee8c116c9");
if(optional.isPresent()){
CmsPage cmsPage = optional.get();
cmsPage.setPageName("测试页面01");
cmsPageRepository.save(cmsPage);
}
}
关于Optional:
Optional是jdk1.8引入的类型,Optional是一个容器对象,它包括了我们需要的对象,使用isPresent方法判断所包含对象是否为空,isPresent方法返回false则表示Optional包含对象为空,否则可以使用get()取出对象进行操作。
Optional的优点是:
1、提醒你非空判断。
2、将对象非空检测标准化。
同Spring Data JPA一样Spring Data mongodb也提供自定义方法的规则,如下:
按照findByXXX,findByXXXAndYYY、countByXXXAndYYY等规则定义方法,实现查询操作。
public interface CmsPageRepository extends MongoRepository<CmsPage,String> {
//根据页面名称查询
CmsPage findByPageName(String pageName);
//根据页面名称和类型查询
CmsPage findByPageNameAndPageType(String pageName,String pageType);
//根据站点和页面类型查询记录数
int countBySiteIdAndPageType(String siteId,String pageType);
//根据站点和页面类型分页查询
Page<CmsPage> findBySiteIdAndPageType(String siteId,String pageType, Pageable pageable);
}
定义页面查询方法,根据条件查询暂时不实现:
package com.xuecheng.manage_cms.service;
import com.xuecheng.framework.domain.cms.CmsPage;
import com.xuecheng.framework.domain.cms.request.QueryPageRequest;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.framework.model.response.QueryResult;
import com.xuecheng.manage_cms.dao.CmsPageRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
/**
* @author Administrator
* @version 1.0
* @create 2018-09-12 18:32
**/
@Service
public class PageService {
@Autowired
CmsPageRepository cmsPageRepository;
/**
* 页面查询方法
* @param page 页码,从1开始记数;但是调用dao时是从0开始
* @param size 每页记录数
* @param queryPageRequest 查询条件
* @return
*/
public QueryResponseResult findList(int page, int size, QueryPageRequest queryPageRequest){
// 分页参数
if(page <= 0){
page = 1; // 暴露给 controller
}
page = page - 1;
if(size <= 0){
size = 10;
}
Pageable pageable = PageRequest.of(page, size);
Page<CmsPage> all = cmsPageRepository.findAll(pageable);
QueryResult queryResult = new QueryResult();
queryResult.setList(all.getContent()); // 数据列表
queryResult.setTotal(all.getTotalElements()); // 数据总记录数
QueryResponseResult queryResponseResult = new QueryResponseResult(CommonCode.SUCCESS, queryResult);
return queryResponseResult;
}
}
使用springMVC完成接口实现开发。
package com.xuecheng.manage_cms.controller;
import com.xuecheng.api.cms.CmsPageControllerApi;
import com.xuecheng.framework.domain.cms.request.QueryPageRequest;
import com.xuecheng.framework.model.response.QueryResponseResult;
import com.xuecheng.manage_cms.service.PageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cms/page")
public class CmsPageController implements CmsPageControllerApi {
@Autowired
private PageService pageService;
@Override
@GetMapping("/list/{page}/{size}")
public QueryResponseResult findList(@PathVariable("page") int page,
@PathVariable("size") int size,
QueryPageRequest queryPageRequest) {
return pageService.findList(page, size, queryPageRequest);
}
}
使用浏览器测试
输入:http://localhost:31001/cms/page/list/1/10 查询第1页,每页显示10条记录。
{
"success": true,
"code": 10000,
"message": "操作成功!",
"queryResult": {
"list": [
{
"siteId": "5a751fab6abb5044e0d19ea1",
"pageId": "5a754adf6abb500ad05688d9",
"pageName": "index.html",
"pageAliase": "首页",
"pageWebPath": "/index.html",
"pageParameter": null,
"pagePhysicalPath": "F:\\develop\\xc_portal_static\\",
"pageType": "0",
"pageTemplate": null,
"pageHtml": null,
"pageStatus": null,
"pageCreateTime": "2018-02-03T05:37:53.256+0000",
"templateId": "5a962b52b00ffc514038faf7",
"pageParams": null,
"htmlFileId": "5a7c1c54d019f14d90a1fb23",
"dataUrl": null
},
{
"siteId": "5a751fab6abb5044e0d19ea1",
"pageId": "5a795ac7dd573c04508f3a56",
"pageName": "index_banner.html",
"pageAliase": "轮播图",
"pageWebPath": "/include/index_banner.html",
"pageParameter": null,
"pagePhysicalPath": "F:\\develop\\xc_portal_static\\include\\",
"pageType": "0",
"pageTemplate": null,
"pageHtml": null,
"pageStatus": null,
"pageCreateTime": "2018-02-06T07:34:21.255+0000",
"templateId": "5a962bf8b00ffc514038fafa",
"pageParams": null,
"htmlFileId": "5a795bbcdd573c04508f3a59",
"dataUrl": null
}
],
"total": 22
}
}
为了严格按照接口进行开发,提高效率,对请求及响应格式进行规范化。
1、get 请求时,采用key/value格式请求,SpringMVC可采用基本类型的变量接收,也可以采用对象接收。
2、Post请求时,可以提交form表单数据(application/x-www-form-urlencoded
)和Json数据(ContentType=application/json
),文件等多部件类型(multipart/form-data
)三种数据格式,SpringMVC接收Json数据,使用@RequestBody注解解析请求的json数据。
3、响应结果统一信息为:是否成功、操作代码、提示信息及自定义数据。
4、响应结果统一格式为json。
Api定义使用SpringMVC来完成,由于此接口后期将作为微服务远程调用使用,在定义接口时有如下限制:
1、@PathVariable
统一指定参数名称,如:@PathVariable(“id”)
2、@RequestParam
统一指定参数名称,如:@RequestParam(“id”)